aboutsummaryrefslogtreecommitdiff
path: root/src/nvim
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim')
-rw-r--r--[-rwxr-xr-x]src/nvim/CMakeLists.txt1026
-rw-r--r--src/nvim/README.md50
-rw-r--r--src/nvim/api/autocmd.c602
-rw-r--r--src/nvim/api/autocmd.h9
-rw-r--r--src/nvim/api/buffer.c441
-rw-r--r--src/nvim/api/buffer.h12
-rw-r--r--src/nvim/api/command.c567
-rw-r--r--src/nvim/api/command.h11
-rw-r--r--src/nvim/api/deprecated.c288
-rw-r--r--src/nvim/api/deprecated.h8
-rw-r--r--src/nvim/api/extmark.c878
-rw-r--r--src/nvim/api/extmark.h20
-rw-r--r--src/nvim/api/keysets.lua229
-rw-r--r--src/nvim/api/keysets_defs.h315
-rw-r--r--src/nvim/api/options.c717
-rw-r--r--src/nvim/api/options.h12
-rw-r--r--src/nvim/api/private/converter.c30
-rw-r--r--src/nvim/api/private/converter.h9
-rw-r--r--src/nvim/api/private/defs.h25
-rw-r--r--src/nvim/api/private/dispatch.c3
-rw-r--r--src/nvim/api/private/dispatch.h12
-rw-r--r--src/nvim/api/private/helpers.c205
-rw-r--r--src/nvim/api/private/helpers.h66
-rw-r--r--src/nvim/api/private/validate.c76
-rw-r--r--src/nvim/api/private/validate.h96
-rw-r--r--src/nvim/api/tabpage.c4
-rw-r--r--src/nvim/api/tabpage.h6
-rw-r--r--src/nvim/api/ui.c236
-rw-r--r--src/nvim/api/ui.h14
-rw-r--r--src/nvim/api/ui_events.in.h11
-rw-r--r--src/nvim/api/vim.c688
-rw-r--r--src/nvim/api/vim.h9
-rw-r--r--src/nvim/api/vimscript.c195
-rw-r--r--src/nvim/api/vimscript.h9
-rw-r--r--src/nvim/api/win_config.c482
-rw-r--r--src/nvim/api/win_config.h9
-rw-r--r--src/nvim/api/window.c177
-rw-r--r--src/nvim/api/window.h7
-rw-r--r--src/nvim/arabic.c24
-rw-r--r--src/nvim/arabic.h6
-rw-r--r--src/nvim/arglist.c79
-rw-r--r--src/nvim/arglist.h12
-rw-r--r--src/nvim/arglist_defs.h19
-rw-r--r--src/nvim/ascii_defs.h (renamed from src/nvim/ascii.h)7
-rw-r--r--src/nvim/assert_defs.h (renamed from src/nvim/assert.h)8
-rw-r--r--src/nvim/auevents.lua9
-rw-r--r--src/nvim/autocmd.c1210
-rw-r--r--src/nvim/autocmd.h92
-rw-r--r--src/nvim/autocmd_defs.h71
-rw-r--r--src/nvim/base64.c215
-rw-r--r--src/nvim/base64.h7
-rw-r--r--src/nvim/buffer.c529
-rw-r--r--src/nvim/buffer.h81
-rw-r--r--src/nvim/buffer_defs.h455
-rw-r--r--src/nvim/buffer_updates.c42
-rw-r--r--src/nvim/buffer_updates.h12
-rw-r--r--src/nvim/bufwrite.c1943
-rw-r--r--src/nvim/bufwrite.h9
-rw-r--r--src/nvim/change.c262
-rw-r--r--src/nvim/change.h25
-rw-r--r--src/nvim/channel.c110
-rw-r--r--src/nvim/channel.h19
-rw-r--r--src/nvim/charset.c492
-rw-r--r--src/nvim/charset.h13
-rw-r--r--src/nvim/cmdexpand.c475
-rw-r--r--src/nvim/cmdexpand.h11
-rw-r--r--src/nvim/cmdexpand_defs.h113
-rw-r--r--src/nvim/cmdhist.c79
-rw-r--r--src/nvim/cmdhist.h9
-rw-r--r--src/nvim/context.c50
-rw-r--r--src/nvim/context.h6
-rw-r--r--src/nvim/cursor.c80
-rw-r--r--src/nvim/cursor.h10
-rw-r--r--src/nvim/cursor_shape.c66
-rw-r--r--src/nvim/cursor_shape.h14
-rw-r--r--src/nvim/debugger.c96
-rw-r--r--src/nvim/debugger.h8
-rw-r--r--src/nvim/decoration.c1026
-rw-r--r--src/nvim/decoration.h125
-rw-r--r--src/nvim/decoration_defs.h125
-rw-r--r--src/nvim/decoration_provider.c82
-rw-r--r--src/nvim/decoration_provider.h18
-rw-r--r--src/nvim/diff.c168
-rw-r--r--src/nvim/diff.h16
-rw-r--r--src/nvim/digraph.c101
-rw-r--r--src/nvim/digraph.h11
-rw-r--r--src/nvim/drawline.c3196
-rw-r--r--src/nvim/drawline.h27
-rw-r--r--src/nvim/drawscreen.c1031
-rw-r--r--src/nvim/drawscreen.h14
-rw-r--r--src/nvim/edit.c700
-rw-r--r--src/nvim/edit.h53
-rw-r--r--src/nvim/eval.c2885
-rw-r--r--src/nvim/eval.h53
-rw-r--r--src/nvim/eval.lua13263
-rw-r--r--src/nvim/eval/buffer.c43
-rw-r--r--src/nvim/eval/buffer.h9
-rw-r--r--src/nvim/eval/decode.c34
-rw-r--r--src/nvim/eval/decode.h12
-rw-r--r--src/nvim/eval/encode.c29
-rw-r--r--src/nvim/eval/encode.h11
-rw-r--r--src/nvim/eval/executor.c11
-rw-r--r--src/nvim/eval/executor.h8
-rw-r--r--src/nvim/eval/funcs.c2255
-rw-r--r--src/nvim/eval/funcs.h22
-rw-r--r--src/nvim/eval/gc.c3
-rw-r--r--src/nvim/eval/gc.h7
-rw-r--r--src/nvim/eval/typval.c1382
-rw-r--r--src/nvim/eval/typval.h117
-rw-r--r--src/nvim/eval/typval_defs.h109
-rw-r--r--src/nvim/eval/typval_encode.c.h109
-rw-r--r--src/nvim/eval/typval_encode.h53
-rw-r--r--src/nvim/eval/userfunc.c1170
-rw-r--r--src/nvim/eval/userfunc.h27
-rw-r--r--src/nvim/eval/vars.c1064
-rw-r--r--src/nvim/eval/vars.h12
-rw-r--r--src/nvim/eval/window.c113
-rw-r--r--src/nvim/eval/window.h11
-rw-r--r--src/nvim/event/defs.h5
-rw-r--r--src/nvim/event/libuv_process.c22
-rw-r--r--src/nvim/event/libuv_process.h4
-rw-r--r--src/nvim/event/loop.c11
-rw-r--r--src/nvim/event/loop.h9
-rw-r--r--src/nvim/event/multiqueue.c86
-rw-r--r--src/nvim/event/multiqueue.h4
-rw-r--r--src/nvim/event/process.c33
-rw-r--r--src/nvim/event/process.h13
-rw-r--r--src/nvim/event/rstream.c9
-rw-r--r--src/nvim/event/rstream.h4
-rw-r--r--src/nvim/event/signal.c4
-rw-r--r--src/nvim/event/signal.h4
-rw-r--r--src/nvim/event/socket.c29
-rw-r--r--src/nvim/event/socket.h4
-rw-r--r--src/nvim/event/stream.c14
-rw-r--r--src/nvim/event/stream.h4
-rw-r--r--src/nvim/event/time.c4
-rw-r--r--src/nvim/event/time.h4
-rw-r--r--src/nvim/event/wstream.c6
-rw-r--r--src/nvim/event/wstream.h4
-rw-r--r--src/nvim/ex_cmds.c757
-rw-r--r--src/nvim/ex_cmds.h17
-rw-r--r--src/nvim/ex_cmds.lua24
-rw-r--r--src/nvim/ex_cmds2.c103
-rw-r--r--src/nvim/ex_cmds2.h23
-rw-r--r--src/nvim/ex_cmds_defs.h120
-rw-r--r--src/nvim/ex_docmd.c974
-rw-r--r--src/nvim/ex_docmd.h43
-rw-r--r--src/nvim/ex_eval.c196
-rw-r--r--src/nvim/ex_eval.h8
-rw-r--r--src/nvim/ex_eval_defs.h60
-rw-r--r--src/nvim/ex_getln.c616
-rw-r--r--src/nvim/ex_getln.h13
-rw-r--r--src/nvim/ex_session.c150
-rw-r--r--src/nvim/ex_session.h9
-rw-r--r--src/nvim/extmark.c405
-rw-r--r--src/nvim/extmark.h44
-rw-r--r--src/nvim/extmark_defs.h21
-rw-r--r--src/nvim/file_search.c185
-rw-r--r--src/nvim/file_search.h6
-rw-r--r--src/nvim/fileio.c2422
-rw-r--r--src/nvim/fileio.h43
-rw-r--r--src/nvim/fold.c169
-rw-r--r--src/nvim/fold.h28
-rw-r--r--src/nvim/fold_defs.h15
-rw-r--r--src/nvim/func_attr.h29
-rw-r--r--src/nvim/garray.c6
-rw-r--r--src/nvim/garray.h24
-rw-r--r--src/nvim/garray_defs.h17
-rw-r--r--src/nvim/generators/c_grammar.lua13
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua258
-rw-r--r--[-rwxr-xr-x]src/nvim/generators/gen_api_ui_events.lua21
-rw-r--r--[-rwxr-xr-x]src/nvim/generators/gen_declarations.lua28
-rw-r--r--src/nvim/generators/gen_eval.lua53
-rw-r--r--src/nvim/generators/gen_events.lua36
-rw-r--r--src/nvim/generators/gen_ex_cmds.lua29
-rw-r--r--src/nvim/generators/gen_keysets.lua80
-rw-r--r--src/nvim/generators/gen_options.lua61
-rw-r--r--src/nvim/generators/gen_vimvim.lua147
-rw-r--r--src/nvim/generators/preload.lua10
-rw-r--r--src/nvim/getchar.c695
-rw-r--r--src/nvim/getchar.h30
-rw-r--r--src/nvim/getchar_defs.h63
-rw-r--r--src/nvim/gettext.h11
-rw-r--r--src/nvim/globals.h952
-rw-r--r--src/nvim/grid.c885
-rw-r--r--src/nvim/grid.h59
-rw-r--r--src/nvim/grid_defs.h37
-rw-r--r--src/nvim/hashtab.c28
-rw-r--r--src/nvim/hashtab.h66
-rw-r--r--src/nvim/hashtab_defs.h59
-rw-r--r--src/nvim/help.c136
-rw-r--r--src/nvim/help.h8
-rw-r--r--src/nvim/highlight.c349
-rw-r--r--src/nvim/highlight.h15
-rw-r--r--src/nvim/highlight_defs.h52
-rw-r--r--src/nvim/highlight_group.c313
-rw-r--r--src/nvim/highlight_group.h10
-rw-r--r--src/nvim/iconv_defs.h (renamed from src/nvim/iconv.h)7
-rw-r--r--src/nvim/indent.c243
-rw-r--r--src/nvim/indent.h21
-rw-r--r--src/nvim/indent_c.c60
-rw-r--r--src/nvim/indent_c.h7
-rw-r--r--src/nvim/input.c32
-rw-r--r--src/nvim/input.h6
-rw-r--r--src/nvim/insexpand.c416
-rw-r--r--src/nvim/insexpand.h13
-rw-r--r--src/nvim/keycodes.c210
-rw-r--r--src/nvim/keycodes.h9
-rw-r--r--src/nvim/lib/queue.h5
-rw-r--r--src/nvim/lib/ringbuf.h6
-rw-r--r--src/nvim/linematch.c133
-rw-r--r--src/nvim/linematch.h9
-rw-r--r--src/nvim/locale.c377
-rw-r--r--src/nvim/locale.h10
-rw-r--r--src/nvim/log.c43
-rw-r--r--src/nvim/log.h51
-rw-r--r--src/nvim/lua/base64.c70
-rw-r--r--src/nvim/lua/base64.h7
-rw-r--r--src/nvim/lua/converter.c149
-rw-r--r--src/nvim/lua/converter.h17
-rw-r--r--src/nvim/lua/executor.c349
-rw-r--r--src/nvim/lua/executor.h21
-rw-r--r--src/nvim/lua/secure.c119
-rw-r--r--src/nvim/lua/secure.h7
-rw-r--r--src/nvim/lua/spell.c23
-rw-r--r--src/nvim/lua/spell.h9
-rw-r--r--src/nvim/lua/stdlib.c134
-rw-r--r--src/nvim/lua/stdlib.h7
-rw-r--r--src/nvim/lua/treesitter.c517
-rw-r--r--src/nvim/lua/treesitter.h11
-rw-r--r--src/nvim/lua/xdiff.c76
-rw-r--r--src/nvim/lua/xdiff.h9
-rw-r--r--src/nvim/macros_defs.h (renamed from src/nvim/macros.h)38
-rw-r--r--src/nvim/main.c332
-rw-r--r--src/nvim/main.h4
-rw-r--r--src/nvim/map.c284
-rw-r--r--src/nvim/map.h96
-rw-r--r--src/nvim/map_defs.h224
-rw-r--r--src/nvim/map_glyph_cache.c109
-rw-r--r--src/nvim/map_key_impl.c.h159
-rw-r--r--src/nvim/map_value_impl.c.h64
-rw-r--r--src/nvim/mapping.c669
-rw-r--r--src/nvim/mapping.h78
-rw-r--r--src/nvim/mapping_defs.h29
-rw-r--r--src/nvim/mark.c318
-rw-r--r--src/nvim/mark.h16
-rw-r--r--src/nvim/mark_defs.h12
-rw-r--r--src/nvim/marktree.c1638
-rw-r--r--src/nvim/marktree.h183
-rw-r--r--src/nvim/match.c125
-rw-r--r--src/nvim/match.h11
-rw-r--r--src/nvim/math.c6
-rw-r--r--src/nvim/math.h4
-rw-r--r--src/nvim/mbyte.c466
-rw-r--r--src/nvim/mbyte.h13
-rw-r--r--src/nvim/mbyte_defs.h12
-rw-r--r--src/nvim/memfile.c341
-rw-r--r--src/nvim/memfile.h27
-rw-r--r--src/nvim/memfile_defs.h105
-rw-r--r--src/nvim/memline.c1087
-rw-r--r--src/nvim/memline.h11
-rw-r--r--src/nvim/memline_defs.h16
-rw-r--r--src/nvim/memory.c63
-rw-r--r--src/nvim/memory.h42
-rw-r--r--src/nvim/memory_defs.h15
-rw-r--r--src/nvim/menu.c162
-rw-r--r--src/nvim/menu.h13
-rw-r--r--src/nvim/menu_defs.h7
-rw-r--r--src/nvim/message.c825
-rw-r--r--src/nvim/message.h97
-rw-r--r--src/nvim/mouse.c737
-rw-r--r--src/nvim/mouse.h16
-rw-r--r--src/nvim/move.c1049
-rw-r--r--src/nvim/move.h9
-rw-r--r--src/nvim/msgpack_rpc/channel.c263
-rw-r--r--src/nvim/msgpack_rpc/channel.h17
-rw-r--r--src/nvim/msgpack_rpc/channel_defs.h21
-rw-r--r--src/nvim/msgpack_rpc/helpers.c22
-rw-r--r--src/nvim/msgpack_rpc/helpers.h5
-rw-r--r--src/nvim/msgpack_rpc/server.c13
-rw-r--r--src/nvim/msgpack_rpc/server.h6
-rw-r--r--src/nvim/msgpack_rpc/unpacker.c118
-rw-r--r--src/nvim/msgpack_rpc/unpacker.h9
-rw-r--r--src/nvim/normal.c664
-rw-r--r--src/nvim/normal.h89
-rw-r--r--src/nvim/normal_defs.h75
-rw-r--r--src/nvim/ops.c750
-rw-r--r--src/nvim/ops.h146
-rw-r--r--src/nvim/option.c5333
-rw-r--r--src/nvim/option.h126
-rw-r--r--src/nvim/option_defs.h1129
-rw-r--r--src/nvim/option_vars.h948
-rw-r--r--src/nvim/options.lua12962
-rw-r--r--src/nvim/optionstr.c3094
-rw-r--r--src/nvim/optionstr.h11
-rw-r--r--src/nvim/os/dl.c5
-rw-r--r--src/nvim/os/dl.h7
-rw-r--r--src/nvim/os/env.c81
-rw-r--r--src/nvim/os/fileio.c21
-rw-r--r--src/nvim/os/fileio.h4
-rw-r--r--src/nvim/os/fs.c245
-rw-r--r--src/nvim/os/fs.h13
-rw-r--r--src/nvim/os/fs_defs.h5
-rw-r--r--src/nvim/os/input.c46
-rw-r--r--src/nvim/os/input.h11
-rw-r--r--src/nvim/os/lang.c337
-rw-r--r--src/nvim/os/lang.h8
-rw-r--r--src/nvim/os/mem.c3
-rw-r--r--src/nvim/os/nvim.manifest5
-rw-r--r--src/nvim/os/os.h31
-rw-r--r--src/nvim/os/os_defs.h44
-rw-r--r--src/nvim/os/os_win_console.c8
-rw-r--r--src/nvim/os/os_win_console.h5
-rw-r--r--src/nvim/os/process.c33
-rw-r--r--src/nvim/os/process.h11
-rw-r--r--src/nvim/os/pty_conpty_win.c11
-rw-r--r--src/nvim/os/pty_conpty_win.h11
-rw-r--r--src/nvim/os/pty_process.h4
-rw-r--r--src/nvim/os/pty_process_unix.c39
-rw-r--r--src/nvim/os/pty_process_unix.h6
-rw-r--r--src/nvim/os/pty_process_win.c16
-rw-r--r--src/nvim/os/pty_process_win.h6
-rw-r--r--src/nvim/os/shell.c92
-rw-r--r--src/nvim/os/shell.h8
-rw-r--r--src/nvim/os/signal.c18
-rw-r--r--src/nvim/os/signal.h4
-rw-r--r--src/nvim/os/stdpaths.c70
-rw-r--r--src/nvim/os/stdpaths_defs.h5
-rw-r--r--src/nvim/os/time.c71
-rw-r--r--src/nvim/os/time.h11
-rw-r--r--src/nvim/os/time_defs.h4
-rw-r--r--src/nvim/os/tty.c3
-rw-r--r--src/nvim/os/tty.h6
-rw-r--r--src/nvim/os/unix_defs.h16
-rw-r--r--src/nvim/os/users.c24
-rw-r--r--src/nvim/os/win_defs.h6
-rw-r--r--src/nvim/path.c168
-rw-r--r--src/nvim/path.h11
-rw-r--r--src/nvim/plines.c1126
-rw-r--r--src/nvim/plines.h25
-rw-r--r--src/nvim/po/CMakeLists.txt26
-rw-r--r--src/nvim/po/check.vim21
-rw-r--r--src/nvim/po/da.po2
-rw-r--r--src/nvim/po/eo.po2
-rw-r--r--src/nvim/po/fi.po2
-rw-r--r--src/nvim/po/fr.po2
-rw-r--r--src/nvim/po/tr.po1530
-rw-r--r--src/nvim/po/uk.po3829
-rw-r--r--src/nvim/popupmenu.c321
-rw-r--r--src/nvim/popupmenu.h12
-rw-r--r--src/nvim/pos_defs.h (renamed from src/nvim/pos.h)5
-rw-r--r--src/nvim/profile.c146
-rw-r--r--src/nvim/profile.h12
-rw-r--r--src/nvim/quickfix.c609
-rw-r--r--src/nvim/quickfix.h21
-rw-r--r--src/nvim/rbuffer.c6
-rw-r--r--src/nvim/rbuffer.h9
-rw-r--r--src/nvim/regexp.c13510
-rw-r--r--src/nvim/regexp.h17
-rw-r--r--src/nvim/regexp_bt.c5661
-rw-r--r--src/nvim/regexp_defs.h124
-rw-r--r--src/nvim/regexp_nfa.c7639
-rw-r--r--src/nvim/runtime.c1157
-rw-r--r--src/nvim/runtime.h124
-rw-r--r--src/nvim/runtime_defs.h76
-rw-r--r--src/nvim/screen.c1115
-rw-r--r--src/nvim/screen.h19
-rw-r--r--src/nvim/search.c352
-rw-r--r--src/nvim/search.h18
-rw-r--r--src/nvim/sha256.c11
-rw-r--r--src/nvim/sha256.h8
-rw-r--r--src/nvim/shada.c423
-rw-r--r--src/nvim/shada.h6
-rw-r--r--src/nvim/sign.c1907
-rw-r--r--src/nvim/sign.h14
-rw-r--r--src/nvim/sign_defs.h64
-rw-r--r--src/nvim/spell.c464
-rw-r--r--src/nvim/spell.h25
-rw-r--r--src/nvim/spell_defs.h363
-rw-r--r--src/nvim/spellfile.c1072
-rw-r--r--src/nvim/spellfile.h11
-rw-r--r--src/nvim/spellsuggest.c779
-rw-r--r--src/nvim/spellsuggest.h6
-rw-r--r--src/nvim/state.c81
-rw-r--r--src/nvim/state.h19
-rw-r--r--src/nvim/state_defs.h45
-rw-r--r--src/nvim/statusline.c650
-rw-r--r--src/nvim/statusline.h15
-rw-r--r--src/nvim/statusline_defs.h59
-rw-r--r--src/nvim/strings.c1665
-rw-r--r--src/nvim/strings.h34
-rw-r--r--src/nvim/syntax.c285
-rw-r--r--src/nvim/syntax.h13
-rw-r--r--src/nvim/syntax_defs.h7
-rw-r--r--src/nvim/tag.c662
-rw-r--r--src/nvim/tag.h66
-rw-r--r--src/nvim/terminal.c196
-rw-r--r--src/nvim/terminal.h7
-rw-r--r--src/nvim/testdir/Make_all.mak1
-rw-r--r--src/nvim/testdir/Makefile154
-rw-r--r--src/nvim/testdir/README.txt121
-rw-r--r--src/nvim/testdir/check.vim217
-rw-r--r--src/nvim/testdir/dotest.in3
-rw-r--r--src/nvim/testdir/load.vim32
-rw-r--r--src/nvim/testdir/pyxfile/py2_magic.py4
-rw-r--r--src/nvim/testdir/pyxfile/py2_shebang.py4
-rw-r--r--src/nvim/testdir/pyxfile/py3_magic.py4
-rw-r--r--src/nvim/testdir/pyxfile/py3_shebang.py4
-rw-r--r--src/nvim/testdir/pyxfile/pyx.py2
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh87
-rw-r--r--src/nvim/testdir/runnvim.vim58
-rw-r--r--src/nvim/testdir/runtest.vim485
-rw-r--r--src/nvim/testdir/samples/memfile_test.c148
-rw-r--r--src/nvim/testdir/samples/quickfix.txt4
-rw-r--r--src/nvim/testdir/samples/re.freeze.txt6
-rw-r--r--src/nvim/testdir/sautest/autoload/foo.vim15
-rw-r--r--src/nvim/testdir/sautest/autoload/footest.vim5
-rw-r--r--src/nvim/testdir/sautest/autoload/globone.vim1
-rw-r--r--src/nvim/testdir/sautest/autoload/globtwo.vim1
-rw-r--r--src/nvim/testdir/sautest/autoload/sourced.vim3
-rw-r--r--src/nvim/testdir/screendump.vim2
-rw-r--r--src/nvim/testdir/script_util.vim69
-rw-r--r--src/nvim/testdir/setup.vim83
-rw-r--r--src/nvim/testdir/shared.vim389
-rw-r--r--src/nvim/testdir/summarize.vim62
-rw-r--r--src/nvim/testdir/term_util.vim13
-rw-r--r--src/nvim/testdir/test1.in13
-rw-r--r--src/nvim/testdir/test1.ok1
-rw-r--r--src/nvim/testdir/test_alot.vim31
-rw-r--r--src/nvim/testdir/test_alot_latin.vim7
-rw-r--r--src/nvim/testdir/test_alot_utf8.vim14
-rw-r--r--src/nvim/testdir/test_arabic.vim587
-rw-r--r--src/nvim/testdir/test_arglist.vim748
-rw-r--r--src/nvim/testdir/test_assert.vim363
-rw-r--r--src/nvim/testdir/test_autochdir.vim130
-rw-r--r--src/nvim/testdir/test_autocmd.vim3572
-rw-r--r--src/nvim/testdir/test_autoload.vim25
-rw-r--r--src/nvim/testdir/test_backspace_opt.vim141
-rw-r--r--src/nvim/testdir/test_backup.vim89
-rw-r--r--src/nvim/testdir/test_behave.vim29
-rw-r--r--src/nvim/testdir/test_blob.vim367
-rw-r--r--src/nvim/testdir/test_blockedit.vim132
-rw-r--r--src/nvim/testdir/test_breakindent.vim1078
-rw-r--r--src/nvim/testdir/test_buffer.vim454
-rw-r--r--src/nvim/testdir/test_bufline.vim296
-rw-r--r--src/nvim/testdir/test_bufwintabinfo.vim187
-rw-r--r--src/nvim/testdir/test_cd.vim256
-rw-r--r--src/nvim/testdir/test_cdo.vim216
-rw-r--r--src/nvim/testdir/test_changedtick.vim95
-rw-r--r--src/nvim/testdir/test_changelist.vim107
-rw-r--r--src/nvim/testdir/test_charsearch.vim98
-rw-r--r--src/nvim/testdir/test_charsearch_utf8.vim19
-rw-r--r--src/nvim/testdir/test_checkpath.vim121
-rw-r--r--src/nvim/testdir/test_cindent.vim5428
-rw-r--r--src/nvim/testdir/test_cjk_linebreak.vim97
-rw-r--r--src/nvim/testdir/test_clientserver.vim195
-rw-r--r--src/nvim/testdir/test_close_count.vim174
-rw-r--r--src/nvim/testdir/test_cmdline.vim3581
-rw-r--r--src/nvim/testdir/test_command_count.vim196
-rw-r--r--src/nvim/testdir/test_comments.vim277
-rw-r--r--src/nvim/testdir/test_comparators.vim9
-rw-r--r--src/nvim/testdir/test_compiler.vim78
-rw-r--r--src/nvim/testdir/test_conceal.vim284
-rw-r--r--src/nvim/testdir/test_const.vim294
-rw-r--r--src/nvim/testdir/test_cpoptions.vim927
-rw-r--r--src/nvim/testdir/test_cursor_func.vim481
-rw-r--r--src/nvim/testdir/test_cursorline.vim354
-rw-r--r--src/nvim/testdir/test_curswant.vim23
-rw-r--r--src/nvim/testdir/test_debugger.vim1237
-rw-r--r--src/nvim/testdir/test_delete.vim110
-rw-r--r--src/nvim/testdir/test_diffmode.vim1647
-rw-r--r--src/nvim/testdir/test_digraph.vim604
-rw-r--r--src/nvim/testdir/test_display.vim482
-rw-r--r--src/nvim/testdir/test_edit.vim2084
-rw-r--r--src/nvim/testdir/test_environ.vim89
-rw-r--r--src/nvim/testdir/test_erasebackword.vim19
-rw-r--r--src/nvim/testdir/test_escaped_glob.vim34
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim392
-rw-r--r--src/nvim/testdir/test_ex_equal.vim32
-rw-r--r--src/nvim/testdir/test_ex_mode.vim243
-rw-r--r--src/nvim/testdir/test_ex_undo.vim19
-rw-r--r--src/nvim/testdir/test_ex_z.vim108
-rw-r--r--src/nvim/testdir/test_excmd.vim750
-rw-r--r--src/nvim/testdir/test_exec_while_if.vim43
-rw-r--r--src/nvim/testdir/test_execute_func.vim176
-rw-r--r--src/nvim/testdir/test_exists.vim331
-rw-r--r--src/nvim/testdir/test_exists_autocmd.vim26
-rw-r--r--src/nvim/testdir/test_exit.vim135
-rw-r--r--src/nvim/testdir/test_expand.vim229
-rw-r--r--src/nvim/testdir/test_expand_func.vim146
-rw-r--r--src/nvim/testdir/test_expr.vim676
-rw-r--r--src/nvim/testdir/test_expr_utf8.vim34
-rw-r--r--src/nvim/testdir/test_file_perm.vim30
-rw-r--r--src/nvim/testdir/test_file_size.vim58
-rw-r--r--src/nvim/testdir/test_filechanged.vim274
-rw-r--r--src/nvim/testdir/test_fileformat.vim309
-rw-r--r--src/nvim/testdir/test_filetype.vim2040
-rw-r--r--src/nvim/testdir/test_filter_cmd.vim190
-rw-r--r--src/nvim/testdir/test_filter_map.vim108
-rw-r--r--src/nvim/testdir/test_find_complete.vim163
-rw-r--r--src/nvim/testdir/test_findfile.vim255
-rw-r--r--src/nvim/testdir/test_fixeol.vim118
-rw-r--r--src/nvim/testdir/test_flatten.vim81
-rw-r--r--src/nvim/testdir/test_float_func.vim369
-rw-r--r--src/nvim/testdir/test_fnameescape.vim27
-rw-r--r--src/nvim/testdir/test_fnamemodify.vim104
-rw-r--r--src/nvim/testdir/test_fold.vim1499
-rw-r--r--src/nvim/testdir/test_functions.vim2559
-rw-r--r--src/nvim/testdir/test_ga.vim43
-rw-r--r--src/nvim/testdir/test_getcwd.vim112
-rw-r--r--src/nvim/testdir/test_getvar.vim155
-rw-r--r--src/nvim/testdir/test_gf.vim278
-rw-r--r--src/nvim/testdir/test_glob2regpat.vim32
-rw-r--r--src/nvim/testdir/test_global.vim131
-rw-r--r--src/nvim/testdir/test_gn.vim221
-rw-r--r--src/nvim/testdir/test_goto.vim442
-rw-r--r--src/nvim/testdir/test_gui.vim43
-rw-r--r--src/nvim/testdir/test_help.vim229
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim322
-rw-r--r--src/nvim/testdir/test_hide.vim97
-rw-r--r--src/nvim/testdir/test_highlight.vim868
-rw-r--r--src/nvim/testdir/test_history.vim252
-rw-r--r--src/nvim/testdir/test_hlsearch.vim65
-rw-r--r--src/nvim/testdir/test_increment.vim897
-rw-r--r--src/nvim/testdir/test_increment_dbcs.vim31
-rw-r--r--src/nvim/testdir/test_indent.vim279
-rw-r--r--src/nvim/testdir/test_input.vim61
-rw-r--r--src/nvim/testdir/test_ins_complete.vim2192
-rw-r--r--src/nvim/testdir/test_ins_complete_no_halt.vim51
-rw-r--r--src/nvim/testdir/test_interrupt.vim32
-rw-r--r--src/nvim/testdir/test_join.vim447
-rw-r--r--src/nvim/testdir/test_jumplist.vim107
-rw-r--r--src/nvim/testdir/test_lambda.vim334
-rw-r--r--src/nvim/testdir/test_langmap.vim89
-rw-r--r--src/nvim/testdir/test_largefile.vim29
-rw-r--r--src/nvim/testdir/test_let.vim478
-rw-r--r--src/nvim/testdir/test_lineending.vim19
-rw-r--r--src/nvim/testdir/test_lispindent.vim129
-rw-r--r--src/nvim/testdir/test_listchars.vim695
-rw-r--r--src/nvim/testdir/test_listdict.vim1070
-rw-r--r--src/nvim/testdir/test_listlbr.vim309
-rw-r--r--src/nvim/testdir/test_listlbr_utf8.vim282
-rw-r--r--src/nvim/testdir/test_makeencoding.py69
-rw-r--r--src/nvim/testdir/test_makeencoding.vim125
-rw-r--r--src/nvim/testdir/test_maparg.vim325
-rw-r--r--src/nvim/testdir/test_mapping.vim1193
-rw-r--r--src/nvim/testdir/test_marks.vim320
-rw-r--r--src/nvim/testdir/test_match.vim442
-rw-r--r--src/nvim/testdir/test_matchadd_conceal.vim422
-rw-r--r--src/nvim/testdir/test_matchadd_conceal_utf8.vim39
-rw-r--r--src/nvim/testdir/test_matchfuzzy.vim263
-rw-r--r--src/nvim/testdir/test_menu.vim574
-rw-r--r--src/nvim/testdir/test_messages.vim530
-rw-r--r--src/nvim/testdir/test_method.vim174
-rw-r--r--src/nvim/testdir/test_mksession.vim1027
-rw-r--r--src/nvim/testdir/test_mksession_utf8.vim105
-rw-r--r--src/nvim/testdir/test_modeline.vim376
-rw-r--r--src/nvim/testdir/test_move.vim46
-rw-r--r--src/nvim/testdir/test_nested_function.vim63
-rw-r--r--src/nvim/testdir/test_normal.vim3831
-rw-r--r--src/nvim/testdir/test_number.vim359
-rw-r--r--src/nvim/testdir/test_options.vim1304
-rw-r--r--src/nvim/testdir/test_packadd.vim361
-rw-r--r--src/nvim/testdir/test_partial.vim361
-rw-r--r--src/nvim/testdir/test_paste.vim76
-rw-r--r--src/nvim/testdir/test_perl.vim311
-rw-r--r--src/nvim/testdir/test_plus_arg_edit.vim47
-rw-r--r--src/nvim/testdir/test_popup.vim1263
-rw-r--r--src/nvim/testdir/test_preview.vim64
-rw-r--r--src/nvim/testdir/test_profile.vim596
-rw-r--r--src/nvim/testdir/test_prompt_buffer.vim262
-rw-r--r--src/nvim/testdir/test_put.vim239
-rw-r--r--src/nvim/testdir/test_python2.vim173
-rw-r--r--src/nvim/testdir/test_python3.vim192
-rw-r--r--src/nvim/testdir/test_pyx2.vim81
-rw-r--r--src/nvim/testdir/test_pyx3.vim81
-rw-r--r--src/nvim/testdir/test_quickfix.vim6236
-rw-r--r--src/nvim/testdir/test_quotestar.vim155
-rw-r--r--src/nvim/testdir/test_random.vim59
-rw-r--r--src/nvim/testdir/test_recover.vim467
-rw-r--r--src/nvim/testdir/test_regex_char_classes.vim297
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim1094
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim603
-rw-r--r--src/nvim/testdir/test_registers.vim800
-rw-r--r--src/nvim/testdir/test_reltime.vim31
-rw-r--r--src/nvim/testdir/test_rename.vim120
-rw-r--r--src/nvim/testdir/test_retab.vim117
-rw-r--r--src/nvim/testdir/test_ruby.vim417
-rw-r--r--src/nvim/testdir/test_scriptnames.vim32
-rw-r--r--src/nvim/testdir/test_scroll_opt.vim36
-rw-r--r--src/nvim/testdir/test_scrollbind.vim272
-rw-r--r--src/nvim/testdir/test_search.vim2109
-rw-r--r--src/nvim/testdir/test_search_stat.vim425
-rw-r--r--src/nvim/testdir/test_searchpos.vim30
-rw-r--r--src/nvim/testdir/test_selectmode.vim210
-rw-r--r--src/nvim/testdir/test_set.vim48
-rw-r--r--src/nvim/testdir/test_sha256.vim22
-rw-r--r--src/nvim/testdir/test_shell.vim209
-rw-r--r--src/nvim/testdir/test_shift.vim117
-rw-r--r--src/nvim/testdir/test_signals.vim165
-rw-r--r--src/nvim/testdir/test_signs.vim2033
-rw-r--r--src/nvim/testdir/test_sleep.vim27
-rw-r--r--src/nvim/testdir/test_smartindent.vim136
-rw-r--r--src/nvim/testdir/test_sort.vim1524
-rw-r--r--src/nvim/testdir/test_source.vim113
-rw-r--r--src/nvim/testdir/test_source_utf8.vim61
-rw-r--r--src/nvim/testdir/test_spell.vim1467
-rw-r--r--src/nvim/testdir/test_spell_utf8.vim832
-rw-r--r--src/nvim/testdir/test_spellfile.vim1066
-rw-r--r--src/nvim/testdir/test_startup.vim1283
-rw-r--r--src/nvim/testdir/test_startup_utf8.vim82
-rw-r--r--src/nvim/testdir/test_stat.vim228
-rw-r--r--src/nvim/testdir/test_statusline.vim609
-rw-r--r--src/nvim/testdir/test_substitute.vim1385
-rw-r--r--src/nvim/testdir/test_suspend.vim63
-rw-r--r--src/nvim/testdir/test_swap.vim583
-rw-r--r--src/nvim/testdir/test_syn_attr.vim51
-rw-r--r--src/nvim/testdir/test_syntax.vim981
-rw-r--r--src/nvim/testdir/test_system.vim145
-rw-r--r--src/nvim/testdir/test_tab.vim90
-rw-r--r--src/nvim/testdir/test_tabline.vim207
-rw-r--r--src/nvim/testdir/test_tabpage.vim849
-rw-r--r--src/nvim/testdir/test_tagcase.vim74
-rw-r--r--src/nvim/testdir/test_tagfunc.vim417
-rw-r--r--src/nvim/testdir/test_tagjump.vim1615
-rw-r--r--src/nvim/testdir/test_taglist.vim281
-rw-r--r--src/nvim/testdir/test_termcodes.vim63
-rw-r--r--src/nvim/testdir/test_textformat.vim1307
-rw-r--r--src/nvim/testdir/test_textobjects.vim646
-rw-r--r--src/nvim/testdir/test_timers.vim458
-rw-r--r--src/nvim/testdir/test_true_false.vim155
-rw-r--r--src/nvim/testdir/test_trycatch.vim2333
-rw-r--r--src/nvim/testdir/test_undo.vim805
-rw-r--r--src/nvim/testdir/test_unlet.vim67
-rw-r--r--src/nvim/testdir/test_user_func.vim504
-rw-r--r--src/nvim/testdir/test_usercommands.vim750
-rw-r--r--src/nvim/testdir/test_utf8.vim331
-rw-r--r--src/nvim/testdir/test_utf8_comparisons.vim94
-rw-r--r--src/nvim/testdir/test_vartabs.vim448
-rw-r--r--src/nvim/testdir/test_version.vim26
-rw-r--r--src/nvim/testdir/test_viminfo.vim26
-rw-r--r--src/nvim/testdir/test_vimscript.vim7284
-rw-r--r--src/nvim/testdir/test_virtualedit.vim589
-rw-r--r--src/nvim/testdir/test_visual.vim1528
-rw-r--r--src/nvim/testdir/test_winbuf_close.vim231
-rw-r--r--src/nvim/testdir/test_window_cmd.vim1833
-rw-r--r--src/nvim/testdir/test_window_id.vim142
-rw-r--r--src/nvim/testdir/test_windows_home.vim120
-rw-r--r--src/nvim/testdir/test_wnext.vim101
-rw-r--r--src/nvim/testdir/test_wordcount.vim104
-rw-r--r--src/nvim/testdir/test_writefile.vim963
-rw-r--r--src/nvim/testdir/unix.vim15
-rw-r--r--src/nvim/testdir/view_util.vim64
-rw-r--r--src/nvim/testdir/vim9.vim112
-rw-r--r--src/nvim/testing.c206
-rw-r--r--src/nvim/testing.h7
-rw-r--r--src/nvim/textformat.c125
-rw-r--r--src/nvim/textformat.h8
-rw-r--r--src/nvim/textobject.c192
-rw-r--r--src/nvim/textobject.h10
-rw-r--r--src/nvim/tui/input.c400
-rw-r--r--src/nvim/tui/input.h40
-rw-r--r--src/nvim/tui/input_defs.h5
-rw-r--r--src/nvim/tui/terminfo.c5
-rw-r--r--src/nvim/tui/terminfo.h9
-rw-r--r--src/nvim/tui/terminfo_defs.h11
-rw-r--r--src/nvim/tui/tui.c482
-rw-r--r--src/nvim/tui/tui.h21
-rw-r--r--src/nvim/types_defs.h (renamed from src/nvim/types.h)25
-rw-r--r--src/nvim/ugrid.c11
-rw-r--r--src/nvim/ugrid.h28
-rw-r--r--src/nvim/ui.c136
-rw-r--r--src/nvim/ui.h22
-rw-r--r--src/nvim/ui_client.c63
-rw-r--r--src/nvim/ui_client.h32
-rw-r--r--src/nvim/ui_compositor.c66
-rw-r--r--src/nvim/ui_compositor.h7
-rw-r--r--src/nvim/undo.c297
-rw-r--r--src/nvim/undo.h11
-rw-r--r--src/nvim/undo_defs.h90
-rw-r--r--src/nvim/usercmd.c289
-rw-r--r--src/nvim/usercmd.h34
-rw-r--r--src/nvim/version.c496
-rw-r--r--src/nvim/version.h11
-rw-r--r--src/nvim/vim.h273
-rw-r--r--src/nvim/vim_defs.h41
-rw-r--r--src/nvim/viml/parser/expressions.c44
-rw-r--r--src/nvim/viml/parser/expressions.h8
-rw-r--r--src/nvim/viml/parser/parser.c3
-rw-r--r--src/nvim/viml/parser/parser.h5
-rw-r--r--src/nvim/window.c859
-rw-r--r--src/nvim/window.h83
-rw-r--r--src/nvim/winfloat.c289
-rw-r--r--src/nvim/winfloat.h8
694 files changed, 88085 insertions, 177574 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 2361210e59..3505f8be4f 100755..100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -1,51 +1,63 @@
add_library(main_lib INTERFACE)
add_executable(nvim main.c)
-add_library(libuv_lib INTERFACE)
-find_package(LibUV 1.28.0 REQUIRED)
-target_include_directories(libuv_lib SYSTEM BEFORE INTERFACE ${LIBUV_INCLUDE_DIRS})
-target_link_libraries(libuv_lib INTERFACE ${LIBUV_LIBRARIES})
-
-find_package(Msgpack 1.0.0 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${MSGPACK_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${MSGPACK_LIBRARIES})
-
-find_package(LibLUV 1.43.0 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBLUV_INCLUDE_DIRS})
-# Use "luv" as imported library, to work around CMake using "-lluv" for
-# "luv.so". #10407
-add_library(luv UNKNOWN IMPORTED)
-set_target_properties(luv PROPERTIES IMPORTED_LOCATION ${LIBLUV_LIBRARIES})
-target_link_libraries(main_lib INTERFACE luv)
+set_target_properties(nvim
+ PROPERTIES
+ EXPORT_COMPILE_COMMANDS ON
+ ENABLE_EXPORTS TRUE)
-find_package(TreeSitter REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${TreeSitter_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${TreeSitter_LIBRARIES})
+#-------------------------------------------------------------------------------
+# Dependencies
+#-------------------------------------------------------------------------------
-find_package(UNIBILIUM 2.0 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${UNIBILIUM_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${UNIBILIUM_LIBRARIES})
+add_library(libuv INTERFACE)
+find_package(libuv CONFIG QUIET)
+if(TARGET libuv::uv_a)
+ target_link_libraries(libuv INTERFACE libuv::uv_a)
+ mark_as_advanced(libuv_DIR)
+else()
+ # Fall back to find module for libuv versions older than v1.45.0 which don't
+ # provide a config file
+ find_package(Libuv 1.28.0 REQUIRED MODULE)
+ target_include_directories(libuv SYSTEM BEFORE INTERFACE ${LIBUV_INCLUDE_DIR})
+ target_link_libraries(libuv INTERFACE ${LIBUV_LIBRARIES})
+endif()
-find_package(LibTermkey 0.22 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBTERMKEY_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${LIBTERMKEY_LIBRARIES})
+add_library(nlua0 MODULE)
+if(WIN32)
+ target_compile_definitions(nlua0 PUBLIC LUA_BUILD_AS_DLL LUA_LIB)
+ set_target_properties(nlua0 PROPERTIES ENABLE_EXPORTS TRUE)
+elseif(APPLE)
+ set_target_properties(nlua0 PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
+endif()
-find_package(LIBVTERM 0.3 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBVTERM_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${LIBVTERM_LIBRARIES})
+find_package(Luv 1.43.0 REQUIRED)
+target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUV_INCLUDE_DIR})
+target_link_libraries(main_lib INTERFACE ${LUV_LIBRARY})
find_package(Iconv REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${Iconv_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${Iconv_LIBRARIES})
-
-option(ENABLE_LIBINTL "enable libintl" ON)
-if(ENABLE_LIBINTL)
- # LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464
- find_package(LibIntl REQUIRED)
- target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LibIntl_INCLUDE_DIRS})
- if (LibIntl_FOUND)
- target_link_libraries(main_lib INTERFACE ${LibIntl_LIBRARY})
- endif()
+find_package(Lpeg REQUIRED)
+find_package(Libtermkey 0.22 REQUIRED)
+find_package(Libvterm 0.3.3 REQUIRED)
+find_package(Msgpack 1.0.0 REQUIRED)
+find_package(Treesitter 0.20.8 REQUIRED)
+find_package(Unibilium 2.0 REQUIRED)
+
+target_link_libraries(main_lib INTERFACE
+ iconv
+ libtermkey
+ libvterm
+ msgpack
+ treesitter
+ unibilium
+ lpeg)
+target_link_libraries(nlua0 PUBLIC lpeg)
+
+# Libintl (not Intl) selects our FindLibintl.cmake script. #8464
+find_package(Libintl REQUIRED)
+target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBINTL_INCLUDE_DIR})
+if (LIBINTL_LIBRARY)
+ target_link_libraries(main_lib INTERFACE ${LIBINTL_LIBRARY})
endif()
# The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing.
@@ -53,103 +65,114 @@ option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF)
if(PREFER_LUA)
find_package(Lua 5.1 EXACT REQUIRED)
target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUA_INCLUDE_DIR})
+ target_include_directories(nlua0 SYSTEM BEFORE PUBLIC ${LUA_INCLUDE_DIR})
target_link_libraries(main_lib INTERFACE ${LUA_LIBRARIES})
- # Passive (not REQUIRED): if LUAJIT_FOUND is not set, nvim-test is skipped.
- find_package(LuaJit)
+ # Passive (not REQUIRED): if LUAJIT_FOUND is not set, fixtures for unittests is skipped.
+ find_package(Luajit)
else()
- find_package(LuaJit REQUIRED)
- target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUAJIT_INCLUDE_DIRS})
- target_link_libraries(main_lib INTERFACE ${LUAJIT_LIBRARIES})
-endif()
-
-option(ENABLE_IWYU "Run include-what-you-use with the compiler." OFF)
-if(ENABLE_IWYU)
- find_program(IWYU_PRG NAMES include-what-you-use iwyu)
- if(NOT IWYU_PRG)
- message(FATAL_ERROR "ENABLE_IWYU is ON but include-what-you-use is not found!")
+ find_package(Luajit REQUIRED)
+ target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUAJIT_INCLUDE_DIR})
+ target_link_libraries(main_lib INTERFACE ${LUAJIT_LIBRARY})
+ target_include_directories(nlua0 SYSTEM BEFORE PUBLIC ${LUAJIT_INCLUDE_DIR})
+ if(WIN32)
+ target_link_libraries(nlua0 PUBLIC ${LUAJIT_LIBRARY})
endif()
-
- set(iwyu_flags "${IWYU_PRG};")
- string(APPEND iwyu_flags "-Xiwyu;--no_default_mappings;")
- string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/mapping.imp;")
- string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.libc.imp;")
- string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.symbols.imp")
-
- set_target_properties(nvim PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}")
- target_compile_definitions(main_lib INTERFACE EXITFREE)
endif()
-if(MSVC)
- # TODO(dundargoc): bump warning level
- target_compile_options(main_lib INTERFACE -W2)
-
- # Disable warnings that give too many false positives.
- target_compile_options(main_lib INTERFACE -wd4311 -wd4146)
- target_compile_definitions(main_lib INTERFACE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
+#-------------------------------------------------------------------------------
+# Compiler and linker options
+#-------------------------------------------------------------------------------
- target_sources(main_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}/os/nvim.manifest)
-else()
+if(NOT MSVC)
target_compile_options(main_lib INTERFACE -Wall -Wextra -pedantic -Wno-unused-parameter
- -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion
+ -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion -Wvla
-Wdouble-promotion
-Wmissing-noreturn
-Wmissing-format-attribute
- -Wmissing-prototypes)
-endif()
+ -Wmissing-prototypes
+ -fsigned-char)
-# On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang
-# 3.4.1 used there.
-if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND CMAKE_C_COMPILER_ID MATCHES "Clang")
- target_compile_options(main_lib INTERFACE -Wno-c11-extensions)
+ # For O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags on older systems
+ # (pre POSIX.1-2008: glibc 2.11 and earlier). #4042
+ # For ptsname(). #6743
+ target_compile_definitions(main_lib INTERFACE _GNU_SOURCE)
endif()
-check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
-if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
- target_compile_options(main_lib INTERFACE -Wimplicit-fallthrough)
+# -fstack-protector breaks Mingw-w64 builds
+if(NOT MINGW)
+ check_c_compiler_flag(-fstack-protector-strong HAS_FSTACK_PROTECTOR_STRONG_FLAG)
+ if(HAS_FSTACK_PROTECTOR_STRONG_FLAG)
+ target_compile_options(main_lib INTERFACE -fstack-protector-strong)
+ target_link_libraries(main_lib INTERFACE -fstack-protector-strong)
+ else()
+ check_c_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR_FLAG)
+ if(HAS_FSTACK_PROTECTOR_FLAG)
+ target_compile_options(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
+ target_link_libraries(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
+ endif()
+ endif()
endif()
-option(ENABLE_COMPILER_SUGGESTIONS "Enable -Wsuggest compiler warnings" OFF)
-if(ENABLE_COMPILER_SUGGESTIONS)
- # Clang doesn't have -Wsuggest-attribute so check for each one.
- check_c_compiler_flag(-Wsuggest-attribute=pure HAVE_WSUGGEST_ATTRIBUTE_PURE)
- if(HAVE_WSUGGEST_ATTRIBUTE_PURE)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=pure)
- endif()
+# Compiler specific options
+if(MSVC)
+ target_compile_options(main_lib INTERFACE -W3)
- check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST)
- if(HAVE_WSUGGEST_ATTRIBUTE_CONST)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=const)
- endif()
+ # Disable warnings that give too many false positives.
+ target_compile_options(main_lib INTERFACE -wd4311 -wd4146 -wd4003 -wd4715)
+ target_compile_definitions(main_lib INTERFACE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
- check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
- if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=malloc)
- endif()
+ target_sources(main_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}/os/nvim.manifest)
+elseif(MINGW)
+ # Use POSIX compatible stdio in Mingw
+ target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO)
- check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD)
- if(HAVE_WSUGGEST_ATTRIBUTE_COLD)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=cold)
+ # Enable wmain
+ target_link_libraries(nvim PRIVATE -municode)
+elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(main_lib INTERFACE -fno-common
+ $<$<CONFIG:Release>:-Wno-unused-result>
+ $<$<CONFIG:RelWithDebInfo>:-Wno-unused-result>
+ $<$<CONFIG:MinSizeRel>:-Wno-unused-result>)
+elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ # On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang
+ # 3.4.1 used there.
+ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ target_compile_options(main_lib INTERFACE -Wno-c11-extensions)
+ endif()
+
+ # workaround for clang-11 on macOS, supported on later versions
+ if(NOT APPLE)
+ target_link_libraries(nvim PRIVATE -Wl,--no-undefined)
endif()
endif()
-if(MINGW)
- # Use POSIX compatible stdio in Mingw
- target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO)
-endif()
-if(WIN32)
- # Windows Vista is the minimum supported version
- target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0600 MSWIN)
+# Platform specific options
+if(UNIX)
+ target_link_libraries(main_lib INTERFACE m)
+ if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+ target_link_libraries(main_lib INTERFACE util)
+ endif()
endif()
-# OpenBSD's GCC (4.2.1) doesn't have -Wvla
-check_c_compiler_flag(-Wvla HAS_WVLA_FLAG)
-if(HAS_WVLA_FLAG)
- target_compile_options(main_lib INTERFACE -Wvla)
+if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0602 MSWIN)
+ target_link_libraries(main_lib INTERFACE netapi32)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
+ target_link_libraries(nvim PRIVATE "-framework CoreServices")
+
+ # Actually export symbols - symbols may not be visible even though
+ # ENABLE_EXPORTS is set to true. See
+ # https://github.com/neovim/neovim/issues/25295
+ set_target_properties(nvim PROPERTIES LINK_FLAGS "-Wl,-export_dynamic")
+elseif(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ target_link_libraries(main_lib INTERFACE pthread c++abi)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+ target_link_libraries(nvim PRIVATE -lsocket)
endif()
-check_c_compiler_flag(-fno-common HAVE_FNO_COMMON)
-if (HAVE_FNO_COMMON)
- target_compile_options(main_lib INTERFACE -fno-common)
+check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
+if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
+ target_compile_options(main_lib INTERFACE -Wimplicit-fallthrough)
endif()
check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG)
@@ -161,112 +184,6 @@ if(HAS_DIAG_COLOR_FLAG)
endif()
endif()
-option(CI_BUILD "CI, extra flags will be set" OFF)
-if(CI_BUILD)
- message(STATUS "CI build enabled")
- if(MSVC)
- target_compile_options(main_lib INTERFACE -WX)
- else()
- target_compile_options(main_lib INTERFACE -Werror)
- if(DEFINED ENV{BUILD_UCHAR})
- # Get some test coverage for unsigned char
- target_compile_options(main_lib INTERFACE -funsigned-char)
- endif()
- endif()
-endif()
-
-list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}")
-list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}")
-check_c_source_compiles("
-#include <unibilium.h>
-
-int
-main(void)
-{
- unibi_str_from_var(unibi_var_from_str(\"\"));
- return unibi_num_from_var(unibi_var_from_num(0));
-}
-" UNIBI_HAS_VAR_FROM)
-list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}")
-list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}")
-if(UNIBI_HAS_VAR_FROM)
- target_compile_definitions(main_lib INTERFACE NVIM_UNIBI_HAS_VAR_FROM)
-endif()
-
-list(APPEND CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}")
-check_c_source_compiles("
-#include <msgpack.h>
-
-int
-main(void)
-{
- return MSGPACK_OBJECT_FLOAT32;
-}
-" MSGPACK_HAS_FLOAT32)
-list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}")
-if(MSGPACK_HAS_FLOAT32)
- target_compile_definitions(main_lib INTERFACE NVIM_MSGPACK_HAS_FLOAT32)
-endif()
-
-list(APPEND CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}")
-list(APPEND CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}")
-check_c_source_compiles("
-#include <tree_sitter/api.h>
-int
-main(void)
-{
- TSQueryCursor *cursor = ts_query_cursor_new();
- ts_query_cursor_set_match_limit(cursor, 32);
- return 0;
-}
-" TS_HAS_SET_MATCH_LIMIT)
-if(TS_HAS_SET_MATCH_LIMIT)
- target_compile_definitions(main_lib INTERFACE NVIM_TS_HAS_SET_MATCH_LIMIT)
-endif()
-check_c_source_compiles("
-#include <stdlib.h>
-#include <tree_sitter/api.h>
-int
-main(void)
-{
- ts_set_allocator(malloc, calloc, realloc, free);
- return 0;
-}
-" TS_HAS_SET_ALLOCATOR)
-if(TS_HAS_SET_ALLOCATOR)
- target_compile_definitions(main_lib INTERFACE NVIM_TS_HAS_SET_ALLOCATOR)
-endif()
-list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}")
-list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}")
-
-# Include <string.h> because some toolchains define _FORTIFY_SOURCE=2 in
-# internal header files, which should in turn be #included by <string.h>.
-check_c_source_compiles("
-#include <string.h>
-
-#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 1
-#error \"_FORTIFY_SOURCE > 1\"
-#endif
-int
-main(void)
-{
- return 0;
-}
-" HAS_ACCEPTABLE_FORTIFY)
-
-if(NOT HAS_ACCEPTABLE_FORTIFY)
- message(STATUS "Unsupported _FORTIFY_SOURCE found, forcing _FORTIFY_SOURCE=1")
- # Extract possible prefix to _FORTIFY_SOURCE (e.g. -Wp,-D_FORTIFY_SOURCE).
- string(REGEX MATCH "[^\ ]+-D_FORTIFY_SOURCE" _FORTIFY_SOURCE_PREFIX "${CMAKE_C_FLAGS}")
- string(REPLACE "-D_FORTIFY_SOURCE" "" _FORTIFY_SOURCE_PREFIX "${_FORTIFY_SOURCE_PREFIX}" )
- if(NOT _FORTIFY_SOURCE_PREFIX STREQUAL "")
- message(STATUS "Detected _FORTIFY_SOURCE Prefix=${_FORTIFY_SOURCE_PREFIX}")
- endif()
- # -U in add_definitions doesn't end up in the correct spot, so we add it to
- # the flags variable instead.
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_FORTIFY_SOURCE_PREFIX}-U_FORTIFY_SOURCE ${_FORTIFY_SOURCE_PREFIX}-D_FORTIFY_SOURCE=1")
-endif()
-
target_compile_definitions(main_lib INTERFACE INCLUDE_GENERATED_DECLARATIONS)
# Remove --sort-common from linker flags, as this seems to cause bugs (see #2641, #3374).
@@ -287,58 +204,114 @@ if(CMAKE_EXE_LINKER_FLAGS MATCHES "--sort-common" OR
string(REGEX REPLACE "-Wl($| )" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}")
endif()
-if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
- if(CMAKE_SYSTEM_NAME STREQUAL "SunOS")
- target_link_libraries(nvim PRIVATE -Wl,--no-undefined -lsocket)
- elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- target_link_libraries(nvim PRIVATE -Wl,--no-undefined)
+#-------------------------------------------------------------------------------
+# Cmake options
+#-------------------------------------------------------------------------------
+
+if(ENABLE_ASAN_UBSAN)
+ message(STATUS "Enabling address sanitizer and undefined behavior sanitizer for nvim.")
+ if(NOT MSVC)
+ if(CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ target_compile_options(nvim PRIVATE -fsanitize-recover=all)
+ else()
+ target_compile_options(nvim PRIVATE -fno-sanitize-recover=all)
+ endif()
+ target_compile_options(nvim PRIVATE
+ -fno-omit-frame-pointer
+ -fno-optimize-sibling-calls
+ -fsanitize=undefined)
endif()
- # For O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags on older systems
- # (pre POSIX.1-2008: glibc 2.11 and earlier). #4042
- # For ptsname(). #6743
- target_compile_definitions(main_lib INTERFACE _GNU_SOURCE)
+ target_compile_options(nvim PRIVATE -fsanitize=address)
+ target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined)
+ target_compile_definitions(nvim PRIVATE ENABLE_ASAN_UBSAN)
+elseif(ENABLE_MSAN)
+ message(STATUS "Enabling memory sanitizer for nvim.")
+ target_compile_options(nvim PRIVATE
+ -fsanitize=memory
+ -fsanitize-memory-track-origins
+ -fno-omit-frame-pointer
+ -fno-optimize-sibling-calls)
+ target_link_libraries(nvim PRIVATE -fsanitize=memory -fsanitize-memory-track-origins)
+elseif(ENABLE_TSAN)
+ message(STATUS "Enabling thread sanitizer for nvim.")
+ target_compile_options(nvim PRIVATE -fsanitize=thread -fPIE)
+ target_link_libraries(nvim PRIVATE -fsanitize=thread)
endif()
-option(USE_GCOV "Enable gcov support" OFF)
-if(USE_GCOV)
- if(CLANG_TSAN)
- # GCOV and TSAN results in false data race reports
- message(FATAL_ERROR "USE_GCOV cannot be used with CLANG_TSAN")
+option(CI_BUILD "CI, extra flags will be set" OFF)
+if(CI_BUILD)
+ message(STATUS "CI build enabled")
+ if(MSVC)
+ target_compile_options(main_lib INTERFACE -WX)
+ else()
+ target_compile_options(main_lib INTERFACE -Werror)
endif()
- message(STATUS "Enabling gcov support")
- target_compile_options(main_lib INTERFACE --coverage)
- target_link_libraries(main_lib INTERFACE --coverage)
- target_compile_definitions(main_lib INTERFACE USE_GCOV)
endif()
-if(WIN32)
- if(MINGW)
- # Enable wmain
- target_link_libraries(nvim PRIVATE -municode)
+option(ENABLE_IWYU "Run include-what-you-use with the compiler." OFF)
+if(ENABLE_IWYU)
+ find_program(IWYU_PRG NAMES include-what-you-use iwyu)
+ if(NOT IWYU_PRG)
+ message(FATAL_ERROR "ENABLE_IWYU is ON but include-what-you-use is not found!")
endif()
-elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- target_link_libraries(nvim PRIVATE "-framework CoreServices")
+
+ set(iwyu_flags "${IWYU_PRG};")
+ string(APPEND iwyu_flags "-Xiwyu;--no_default_mappings;")
+ string(APPEND iwyu_flags "-Xiwyu;--no_fwd_decls;")
+ string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/mapping.imp")
+
+ set_target_properties(nvim PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}")
+ target_compile_definitions(main_lib INTERFACE EXITFREE)
endif()
-if(UNIX)
- # -fstack-protector breaks non Unix builds even in Mingw-w64
- check_c_compiler_flag(-fstack-protector-strong HAS_FSTACK_PROTECTOR_STRONG_FLAG)
- check_c_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR_FLAG)
- if(HAS_FSTACK_PROTECTOR_STRONG_FLAG)
- target_compile_options(main_lib INTERFACE -fstack-protector-strong)
- target_link_libraries(main_lib INTERFACE -fstack-protector-strong)
- elseif(HAS_FSTACK_PROTECTOR_FLAG)
- target_compile_options(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
- target_link_libraries(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
+option(ENABLE_COMPILER_SUGGESTIONS "Enable -Wsuggest compiler warnings" OFF)
+if(ENABLE_COMPILER_SUGGESTIONS)
+ # Clang doesn't have -Wsuggest-attribute so check for each one.
+ check_c_compiler_flag(-Wsuggest-attribute=pure HAVE_WSUGGEST_ATTRIBUTE_PURE)
+ if(HAVE_WSUGGEST_ATTRIBUTE_PURE)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=pure)
endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST)
+ if(HAVE_WSUGGEST_ATTRIBUTE_CONST)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=const)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
+ if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=malloc)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD)
+ if(HAVE_WSUGGEST_ATTRIBUTE_COLD)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=cold)
+ endif()
+endif()
+
+option(ENABLE_GCOV "Enable gcov support" OFF)
+if(ENABLE_GCOV)
+ if(ENABLE_TSAN)
+ # GCOV and TSAN results in false data race reports
+ message(FATAL_ERROR "ENABLE_GCOV cannot be used with ENABLE_TSAN")
+ endif()
+ message(STATUS "Enabling gcov support")
+ target_compile_options(main_lib INTERFACE --coverage)
+ target_link_libraries(main_lib INTERFACE --coverage)
+ target_compile_definitions(main_lib INTERFACE USE_GCOV)
endif()
+#-------------------------------------------------------------------------------
+# Variables
+#-------------------------------------------------------------------------------
+
set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators)
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim/)
set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua)
set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua)
+set(GENERATOR_PRELOAD ${GENERATOR_DIR}/preload.lua)
set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua)
set(GENERATOR_HASHY ${GENERATOR_DIR}/hashy.lua)
set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack)
@@ -355,7 +328,6 @@ set(GENERATED_UI_EVENTS_METADATA ${GENERATED_DIR}/api/private/ui_events_metadata
set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h)
set(GENERATED_EX_CMDS_DEFS ${GENERATED_DIR}/ex_cmds_defs.generated.h)
set(GENERATED_FUNCS ${GENERATED_DIR}/funcs.generated.h)
-set(GENERATED_KEYSETS ${GENERATED_DIR}/keysets.generated.h)
set(GENERATED_KEYSETS_DEFS ${GENERATED_DIR}/keysets_defs.generated.h)
set(GENERATED_EVENTS_ENUM ${GENERATED_INCLUDES_DIR}/auevents_enum.generated.h)
set(GENERATED_EVENTS_NAMES_MAP ${GENERATED_DIR}/auevents_name_map.generated.h)
@@ -363,21 +335,25 @@ set(GENERATED_OPTIONS ${GENERATED_DIR}/options.generated.h)
set(EX_CMDS_GENERATOR ${GENERATOR_DIR}/gen_ex_cmds.lua)
set(FUNCS_GENERATOR ${GENERATOR_DIR}/gen_eval.lua)
set(EVENTS_GENERATOR ${GENERATOR_DIR}/gen_events.lua)
-set(KEYSETS_GENERATOR ${GENERATOR_DIR}/gen_keysets.lua)
set(OPTIONS_GENERATOR ${GENERATOR_DIR}/gen_options.lua)
set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/src/unicode)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
-set(LUA_EDITOR_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_editor.lua)
-set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
-set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua)
-set(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua)
-set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua)
-set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lua)
-set(LUA_INIT_PACKAGES_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_init_packages.lua)
-set(LUA_KEYMAP_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/keymap.lua)
+set(NVIM_RUNTIME_DIR ${PROJECT_SOURCE_DIR}/runtime)
+set(LUA_EDITOR_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_editor.lua)
+set(LUA_SHARED_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/shared.lua)
+set(LUA_LOADER_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/loader.lua)
+set(LUA_INSPECT_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/inspect.lua)
+set(LUA_FS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/fs.lua)
+set(LUA_F_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/F.lua)
+set(LUA_DEFAULTS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_defaults.lua)
+set(LUA_OPTIONS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_options.lua)
+set(LUA_FILETYPE_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/filetype.lua)
+set(LUA_INIT_PACKAGES_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_init_packages.lua)
+set(LUA_KEYMAP_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/keymap.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
+set(LUAJIT_RUNTIME_DIR ${DEPS_PREFIX}/share/luajit-2.1/jit)
glob_wrapper(UNICODE_FILES ${UNICODE_DIR}/*.txt)
glob_wrapper(API_HEADERS api/*.h)
@@ -390,6 +366,10 @@ target_include_directories(main_lib INTERFACE ${GENERATED_INCLUDES_DIR})
target_include_directories(main_lib INTERFACE "${PROJECT_BINARY_DIR}/cmake.config")
target_include_directories(main_lib INTERFACE "${PROJECT_SOURCE_DIR}/src")
+target_include_directories(nlua0 PUBLIC "${PROJECT_SOURCE_DIR}/src")
+target_include_directories(nlua0 PUBLIC "${PROJECT_BINARY_DIR}/cmake.config")
+target_include_directories(nlua0 PUBLIC ${GENERATED_INCLUDES_DIR})
+
file(MAKE_DIRECTORY ${TOUCHES_DIR})
file(MAKE_DIRECTORY ${GENERATED_DIR})
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR})
@@ -399,6 +379,17 @@ glob_wrapper(NVIM_HEADERS *.h)
glob_wrapper(EXTERNAL_SOURCES ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c)
glob_wrapper(EXTERNAL_HEADERS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h)
+glob_wrapper(NLUA0_SOURCES ../mpack/*.c)
+
+if(PREFER_LUA)
+ # luajit not used, use a vendored copy of the bit module
+ list(APPEND EXTERNAL_SOURCES ${PROJECT_SOURCE_DIR}/src/bit.c)
+ list(APPEND NLUA0_SOURCES ${PROJECT_SOURCE_DIR}/src/bit.c)
+ target_compile_definitions(main_lib INTERFACE NVIM_VENDOR_BIT)
+endif()
+
+list(APPEND NLUA0_SOURCES ${PROJECT_SOURCE_DIR}/src/nlua0.c)
+
foreach(subdir
os
api
@@ -429,12 +420,6 @@ list(APPEND LINT_NVIM_SOURCES ${NVIM_SOURCES} ${NVIM_HEADERS})
foreach(sfile ${NVIM_SOURCES})
get_filename_component(f ${sfile} NAME)
- if(${f} MATCHES "^(regexp_nfa.c)$")
- list(APPEND to_remove ${sfile})
- endif()
- if(${f} MATCHES "^(regexp_bt.c)$")
- list(APPEND to_remove ${sfile})
- endif()
if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
list(APPEND to_remove ${sfile})
endif()
@@ -454,20 +439,29 @@ list(REMOVE_ITEM NVIM_SOURCES ${to_remove})
# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306
if(MSVC)
set_source_files_properties(
- ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} /wd4090 /wd4244")
+ ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -wd4090 -wd4244 -wd4267")
else()
set_source_files_properties(
- ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes")
+ ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes -Wno-misleading-indentation")
endif()
-if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")
- target_compile_definitions(main_lib INTERFACE MIN_LOG_LEVEL=${MIN_LOG_LEVEL})
+# Log level (NVIM_LOG_DEBUG in log.h)
+if(CI_BUILD)
+ # Don't debug log on CI, it gets too verbose in the main build log.
+ # TODO(bfredl): debug log level also exposes some errors with EXITFREE in ASAN build.
+else()
+ # Minimize logging for release-type builds.
+ target_compile_definitions(nvim PRIVATE $<$<CONFIG:Debug>:NVIM_LOG_DEBUG>)
endif()
-if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN)
+if(ENABLE_ASAN_UBSAN OR ENABLE_MSAN OR ENABLE_TSAN)
target_compile_definitions(main_lib INTERFACE EXITFREE)
endif()
+#-------------------------------------------------------------------------------
+# Header generation
+#-------------------------------------------------------------------------------
+
get_target_property(prop main_lib INTERFACE_COMPILE_DEFINITIONS)
foreach(gen_cdef DO_NOT_DEFINE_EMPTY_ATTRIBUTES ${prop})
if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS")
@@ -475,42 +469,54 @@ foreach(gen_cdef DO_NOT_DEFINE_EMPTY_ATTRIBUTES ${prop})
endif()
endforeach()
-get_target_property(prop main_lib INTERFACE_INCLUDE_DIRECTORIES)
-foreach(gen_include ${prop})
- list(APPEND gen_cflags "-I${gen_include}")
+get_directory_property(targets BUILDSYSTEM_TARGETS)
+foreach(target ${targets})
+ get_target_property(prop ${target} INTERFACE_INCLUDE_DIRECTORIES)
+ foreach(gen_include ${prop})
+ list(APPEND gen_cflags "-I${gen_include}")
+ endforeach()
endforeach()
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_SYSROOT)
- list(APPEND gen_cflags "-isysroot")
- list(APPEND gen_cflags "${CMAKE_OSX_SYSROOT}")
+
+if(APPLE AND CMAKE_OSX_SYSROOT)
+ list(APPEND gen_cflags "-isysroot" "${CMAKE_OSX_SYSROOT}")
endif()
-string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
-separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS})
-separate_arguments(C_FLAGS_${build_type}_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS_${build_type}})
-set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY})
+if(MSVC)
+ list(APPEND gen_cflags -wd4003)
+endif()
+list(APPEND gen_cflags -O2)
set(NVIM_VERSION_GIT_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef_git.h)
add_custom_target(update_version_stamp
COMMAND ${CMAKE_COMMAND}
- -DNVIM_VERSION_MAJOR=${NVIM_VERSION_MAJOR}
- -DNVIM_VERSION_MINOR=${NVIM_VERSION_MINOR}
- -DNVIM_VERSION_PATCH=${NVIM_VERSION_PATCH}
- -DNVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE}
- -DOUTPUT=${NVIM_VERSION_GIT_H}
- -DNVIM_SOURCE_DIR=${CMAKE_SOURCE_DIR}
+ -D NVIM_VERSION_MAJOR=${NVIM_VERSION_MAJOR}
+ -D NVIM_VERSION_MINOR=${NVIM_VERSION_MINOR}
+ -D NVIM_VERSION_PATCH=${NVIM_VERSION_PATCH}
+ -D NVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE}
+ -D OUTPUT=${NVIM_VERSION_GIT_H}
+ -D NVIM_SOURCE_DIR=${CMAKE_SOURCE_DIR}
-P ${PROJECT_SOURCE_DIR}/cmake/GenerateVersion.cmake
BYPRODUCTS ${NVIM_VERSION_GIT_H})
+set(NVIM_VERSION_DEF_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef.h)
+add_custom_command(
+ OUTPUT "${NVIM_VERSION_DEF_H}"
+ COMMAND "${CMAKE_COMMAND}"
+ -E copy
+ "${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef-$<CONFIG>.h"
+ "${NVIM_VERSION_DEF_H}"
+ DEPENDS "${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef-$<CONFIG>.h")
+
+set(LUA_GEN ${LUA_GEN_PRG} ${GENERATOR_PRELOAD} ${PROJECT_SOURCE_DIR} $<TARGET_FILE:nlua0>)
+set(LUA_GEN_DEPS ${GENERATOR_PRELOAD} $<TARGET_FILE:nlua0>)
+
# NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers
# NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources
# NVIM_GENERATED_SOURCES: generated source files
# These lists must be mutually exclusive.
foreach(sfile ${NVIM_SOURCES}
- "${CMAKE_CURRENT_LIST_DIR}/regexp_bt.c"
- "${CMAKE_CURRENT_LIST_DIR}/regexp_nfa.c"
${GENERATED_API_DISPATCH}
"${GENERATED_UI_EVENTS_CALL}"
"${GENERATED_UI_EVENTS_REMOTE}"
- "${GENERATED_KEYSETS}"
"${GENERATED_UI_EVENTS_CLIENT}"
)
get_filename_component(full_d ${sfile} DIRECTORY)
@@ -534,15 +540,15 @@ foreach(sfile ${NVIM_SOURCES}
set(PREPROC_OUTPUT -E -o ${gf_i})
endif()
- set(depends "${HEADER_GENERATOR}" "${sfile}")
+ set(depends "${HEADER_GENERATOR}" "${sfile}" "${LUA_GEN_DEPS}")
if("${f}" STREQUAL "version.c")
# Ensure auto/versiondef_git.h exists after "make clean".
- list(APPEND depends update_version_stamp "${NVIM_VERSION_GIT_H}")
+ list(APPEND depends update_version_stamp "${NVIM_VERSION_GIT_H}" "${NVIM_VERSION_DEF_H}")
endif()
add_custom_command(
OUTPUT "${gf_c_h}" "${gf_h_h}"
COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags}
- COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
+ COMMAND ${LUA_GEN} "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
DEPENDS ${depends})
list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}")
list(APPEND NVIM_GENERATED_FOR_HEADERS "${gf_h_h}")
@@ -562,13 +568,15 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES}
add_custom_command(
OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA}
- ${API_METADATA} ${LUA_API_C_BINDINGS}
- COMMAND ${LUA_GEN_PRG} ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
+ ${API_METADATA} ${LUA_API_C_BINDINGS} ${GENERATED_KEYSETS_DEFS}
+ COMMAND ${LUA_GEN} ${API_DISPATCH_GENERATOR}
${GENERATED_API_DISPATCH}
${GENERATED_FUNCS_METADATA} ${API_METADATA}
${LUA_API_C_BINDINGS}
+ ${GENERATED_KEYSETS_DEFS}
${API_HEADERS}
DEPENDS
+ ${LUA_GEN_DEPS}
${API_HEADERS}
${MSGPACK_RPC_HEADERS}
${API_DISPATCH_GENERATOR}
@@ -581,25 +589,32 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env
"LUAC_PRG=${LUAC_PRG}"
${LUA_PRG} ${CHAR_BLOB_GENERATOR} -c ${VIM_MODULE_FILE}
+ # NB: vim._init_packages and vim.inspect must be be first and second ones
+ # respectively, otherwise --luamod-dev won't work properly.
${LUA_INIT_PACKAGES_MODULE_SOURCE} "vim._init_packages"
${LUA_INSPECT_MODULE_SOURCE} "vim.inspect"
${LUA_EDITOR_MODULE_SOURCE} "vim._editor"
- ${LUA_SHARED_MODULE_SOURCE} "vim.shared"
- ${LUA_F_MODULE_SOURCE} "vim.F"
- ${LUA_META_MODULE_SOURCE} "vim._meta"
${LUA_FILETYPE_MODULE_SOURCE} "vim.filetype"
+ ${LUA_FS_MODULE_SOURCE} "vim.fs"
+ ${LUA_F_MODULE_SOURCE} "vim.F"
${LUA_KEYMAP_MODULE_SOURCE} "vim.keymap"
+ ${LUA_LOADER_MODULE_SOURCE} "vim.loader"
+ ${LUA_DEFAULTS_MODULE_SOURCE} "vim._defaults"
+ ${LUA_OPTIONS_MODULE_SOURCE} "vim._options"
+ ${LUA_SHARED_MODULE_SOURCE} "vim.shared"
DEPENDS
${CHAR_BLOB_GENERATOR}
${LUA_INIT_PACKAGES_MODULE_SOURCE}
- ${LUA_EDITOR_MODULE_SOURCE}
- ${LUA_SHARED_MODULE_SOURCE}
${LUA_INSPECT_MODULE_SOURCE}
- ${LUA_F_MODULE_SOURCE}
- ${LUA_META_MODULE_SOURCE}
+ ${LUA_EDITOR_MODULE_SOURCE}
${LUA_FILETYPE_MODULE_SOURCE}
- ${LUA_LOAD_PACKAGE_MODULE_SOURCE}
+ ${LUA_FS_MODULE_SOURCE}
+ ${LUA_F_MODULE_SOURCE}
${LUA_KEYMAP_MODULE_SOURCE}
+ ${LUA_LOADER_MODULE_SOURCE}
+ ${LUA_DEFAULTS_MODULE_SOURCE}
+ ${LUA_OPTIONS_MODULE_SOURCE}
+ ${LUA_SHARED_MODULE_SOURCE}
VERBATIM
)
@@ -612,13 +627,14 @@ add_custom_command(
${GENERATED_UI_EVENTS_REMOTE}
${GENERATED_UI_EVENTS_METADATA}
${GENERATED_UI_EVENTS_CLIENT}
- COMMAND ${LUA_GEN_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
- ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
- ${GENERATED_UI_EVENTS_CALL}
- ${GENERATED_UI_EVENTS_REMOTE}
- ${GENERATED_UI_EVENTS_METADATA}
- ${GENERATED_UI_EVENTS_CLIENT}
+ COMMAND ${LUA_GEN} ${API_UI_EVENTS_GENERATOR}
+ ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
+ ${GENERATED_UI_EVENTS_CALL}
+ ${GENERATED_UI_EVENTS_REMOTE}
+ ${GENERATED_UI_EVENTS_METADATA}
+ ${GENERATED_UI_EVENTS_CLIENT}
DEPENDS
+ ${LUA_GEN_DEPS}
${API_UI_EVENTS_GENERATOR}
${GENERATOR_C_GRAMMAR}
${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
@@ -634,7 +650,6 @@ list(APPEND NVIM_GENERATED_FOR_SOURCES
"${GENERATED_API_DISPATCH}"
"${GENERATED_EX_CMDS_DEFS}"
"${GENERATED_EVENTS_NAMES_MAP}"
- "${GENERATED_KEYSETS}"
"${GENERATED_OPTIONS}"
"${GENERATED_UNICODE_TABLES}"
"${VIM_MODULE_FILE}"
@@ -645,35 +660,25 @@ list(APPEND NVIM_GENERATED_SOURCES
)
add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS}
- COMMAND ${LUA_PRG} ${EX_CMDS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_INCLUDES_DIR} ${GENERATED_DIR}
- DEPENDS ${EX_CMDS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/ex_cmds.lua
+ COMMAND ${LUA_GEN} ${EX_CMDS_GENERATOR} ${GENERATED_INCLUDES_DIR} ${GENERATED_DIR}
+ DEPENDS ${LUA_GEN_DEPS} ${EX_CMDS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/ex_cmds.lua
)
add_custom_command(OUTPUT ${GENERATED_FUNCS} ${FUNCS_DATA}
- COMMAND ${LUA_PRG} ${FUNCS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${LUA_SHARED_MODULE_SOURCE} ${GENERATED_DIR} ${API_METADATA} ${FUNCS_DATA}
- DEPENDS ${FUNCS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/eval.lua ${API_METADATA}
+ COMMAND ${LUA_GEN} ${FUNCS_GENERATOR} ${GENERATED_DIR} ${API_METADATA} ${FUNCS_DATA}
+ DEPENDS ${LUA_GEN_DEPS} ${FUNCS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/eval.lua ${API_METADATA}
)
list(APPEND NVIM_GENERATED_FOR_SOURCES
"${GENERATED_FUNCS}")
add_custom_command(OUTPUT ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP}
- COMMAND ${LUA_PRG} ${EVENTS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP}
- DEPENDS ${EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/auevents.lua
-)
-
-add_custom_command(OUTPUT ${GENERATED_KEYSETS} ${GENERATED_KEYSETS_DEFS}
- COMMAND ${LUA_PRG} ${KEYSETS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${LUA_SHARED_MODULE_SOURCE} ${GENERATED_KEYSETS} ${GENERATED_KEYSETS_DEFS}
- DEPENDS ${KEYSETS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/api/keysets.lua ${GENERATOR_HASHY}
+ COMMAND ${LUA_GEN} ${EVENTS_GENERATOR} ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP}
+ DEPENDS ${LUA_GEN_DEPS} ${EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/auevents.lua
)
add_custom_command(OUTPUT ${GENERATED_OPTIONS}
- COMMAND ${LUA_PRG} ${OPTIONS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_OPTIONS}
- DEPENDS ${OPTIONS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/options.lua
+ COMMAND ${LUA_GEN} ${OPTIONS_GENERATOR} ${GENERATED_OPTIONS}
+ DEPENDS ${LUA_GEN_DEPS} ${OPTIONS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/options.lua
)
# NVIM_GENERATED_FOR_SOURCES and NVIM_GENERATED_FOR_HEADERS must be mutually exclusive.
@@ -684,35 +689,26 @@ foreach(hfile ${NVIM_GENERATED_FOR_HEADERS})
endif()
endforeach()
-if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
- target_link_libraries(main_lib INTERFACE pthread c++abi)
-endif()
-
-if(WIN32)
- target_link_libraries(main_lib INTERFACE netapi32)
-endif()
-
-if(UNIX)
- target_link_libraries(main_lib INTERFACE m)
- if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS")
- target_link_libraries(main_lib INTERFACE util)
- endif()
+if(PREFER_LUA)
+ message(STATUS "luajit not used, skipping unit tests")
+else()
+ glob_wrapper(UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c)
+ target_sources(nvim PRIVATE ${UNIT_TEST_FIXTURES})
+ target_compile_definitions(nvim PRIVATE UNIT_TESTING)
endif()
-target_sources(nvim PRIVATE ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS}
- ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS})
-
-set_target_properties(nvim
- PROPERTIES
- EXPORT_COMPILE_COMMANDS ON
- ENABLE_EXPORTS TRUE)
+target_sources(main_lib INTERFACE
+ ${NVIM_GENERATED_FOR_SOURCES}
+ ${NVIM_GENERATED_FOR_HEADERS}
+ ${NVIM_GENERATED_SOURCES}
+ ${NVIM_SOURCES}
+ ${NVIM_HEADERS}
+ ${EXTERNAL_SOURCES}
+ ${EXTERNAL_HEADERS})
-if(${CMAKE_VERSION} VERSION_LESS 3.20)
- set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-endif()
+target_sources(nlua0 PUBLIC ${NLUA0_SOURCES})
-target_link_libraries(nvim PRIVATE main_lib PUBLIC libuv_lib)
+target_link_libraries(nvim PRIVATE main_lib PUBLIC libuv)
install_helper(TARGETS nvim)
if(MSVC)
install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
@@ -721,8 +717,11 @@ endif()
if(ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT IPO_SUPPORTED)
- if(IPO_SUPPORTED AND (NOT CMAKE_BUILD_TYPE MATCHES Debug))
- set_target_properties(nvim PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
+ if(IPO_SUPPORTED)
+ set_target_properties(nvim PROPERTIES
+ INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE
+ INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE
+ INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)
endif()
endif()
@@ -737,9 +736,10 @@ if(WIN32)
add_custom_target(nvim_dll_deps DEPENDS nvim
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/windows_runtime_deps
COMMAND ${CMAKE_COMMAND}
- "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}"
- -DBINARY="${PROJECT_BINARY_DIR}/bin/nvim${CMAKE_EXECUTABLE_SUFFIX}"
- -DDST=${PROJECT_BINARY_DIR}/windows_runtime_deps
+ -D CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
+ -D BINARY="${PROJECT_BINARY_DIR}/bin/nvim${CMAKE_EXECUTABLE_SUFFIX}"
+ -D DST=${PROJECT_BINARY_DIR}/windows_runtime_deps
+ -D CI_BUILD=${CI_BUILD}
-P ${PROJECT_SOURCE_DIR}/cmake/WindowsDllCopy.cmake)
add_dependencies(nvim_runtime_deps nvim_dll_deps)
@@ -750,77 +750,22 @@ if(WIN32)
set(EXTERNAL_BLOBS_SCRIPT
"file(MAKE_DIRECTORY \"${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms\")")
foreach(DEP_FILE IN ITEMS
- curl-ca-bundle.crt
- curl.exe
- diff.exe
- tee.exe
- win32yank.exe
- xxd.exe
-
- # Dependencies for neovim-qt
- bearer/qgenericbearer.dll
- iconengines/qsvgicon.dll
- imageformats/qgif.dll
- imageformats/qicns.dll
- imageformats/qico.dll
- imageformats/qjpeg.dll
- imageformats/qsvg.dll
- imageformats/qtga.dll
- imageformats/qtiff.dll
- imageformats/qwbmp.dll
- imageformats/qwebp.dll
- platforms/qwindows.dll
- styles/qwindowsvistastyle.dll
- translations/qt_ar.qm
- translations/qt_bg.qm
- translations/qt_ca.qm
- translations/qt_cs.qm
- translations/qt_da.qm
- translations/qt_de.qm
- translations/qt_en.qm
- translations/qt_es.qm
- translations/qt_fi.qm
- translations/qt_fr.qm
- translations/qt_gd.qm
- translations/qt_he.qm
- translations/qt_hu.qm
- translations/qt_it.qm
- translations/qt_ja.qm
- translations/qt_ko.qm
- translations/qt_lv.qm
- translations/qt_pl.qm
- translations/qt_ru.qm
- translations/qt_sk.qm
- translations/qt_uk.qm
- D3Dcompiler_47.dll
- libEGL.dll
- libgcc_s_seh-1.dll
- libGLESv2.dll
- libstdc++-6.dll
- libwinpthread-1.dll
- nvim-qt.exe
- opengl32sw.dll
- Qt5Core.dll
- Qt5Gui.dll
- Qt5Network.dll
- Qt5Svg.dll
- Qt5Widgets.dll
-
- )
- get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY)
- set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n"
- "file(COPY \"${DEPS_PREFIX}/bin/${DEP_FILE}\"
- DESTINATION \"${PROJECT_BINARY_DIR}/windows_runtime_deps/${DEP_FILE_DIR}\")")
+ cat.exe
+ tee.exe
+ win32yank.exe
+ xxd.exe)
+ get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY)
+ set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n"
+ "file(COPY \"${DEPS_PREFIX}/bin/${DEP_FILE}\"
+ DESTINATION \"${PROJECT_BINARY_DIR}/windows_runtime_deps/${DEP_FILE_DIR}\")")
endforeach()
file(WRITE ${PROJECT_BINARY_DIR}/external_blobs.cmake ${EXTERNAL_BLOBS_SCRIPT})
add_custom_target(external_blobs
COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/external_blobs.cmake)
- set_target_properties(external_blobs PROPERTIES FOLDER deps)
add_dependencies(nvim_runtime_deps external_blobs)
else()
add_custom_target(nvim_runtime_deps) # Stub target to avoid CMP0046.
endif()
-set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps)
file(MAKE_DIRECTORY ${BINARY_LIB_DIR})
@@ -836,14 +781,16 @@ install(DIRECTORY ${BINARY_LIB_DIR}
DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/
USE_SOURCE_PERMISSIONS)
-add_library(
- libnvim
- STATIC
- EXCLUDE_FROM_ALL
- ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
- ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}
-)
+if(NOT PREFER_LUA)
+ # install luajit runtime files if bundled
+ if(EXISTS ${LUAJIT_RUNTIME_DIR})
+ install(DIRECTORY ${LUAJIT_RUNTIME_DIR}
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/lua
+ USE_SOURCE_PERMISSIONS)
+ endif()
+endif()
+
+add_library(libnvim STATIC EXCLUDE_FROM_ALL)
if(MSVC)
set(LIBNVIM_NAME libnvim)
else()
@@ -855,115 +802,63 @@ set_target_properties(
OUTPUT_NAME ${LIBNVIM_NAME}
)
target_compile_definitions(libnvim PRIVATE MAKE_LIB)
-target_link_libraries(libnvim PRIVATE main_lib PUBLIC libuv_lib)
+target_link_libraries(libnvim PRIVATE main_lib PUBLIC libuv)
-if(NOT LUAJIT_FOUND)
- message(STATUS "luajit not found, skipping nvim-test (unit tests) target")
-else()
- glob_wrapper(UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c)
- add_library(
- nvim-test
- MODULE
- EXCLUDE_FROM_ALL
- ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
- ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}
- ${UNIT_TEST_FIXTURES}
- )
- target_link_libraries(nvim-test PRIVATE ${LUAJIT_LIBRARIES} main_lib PUBLIC libuv_lib)
- if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- target_link_libraries(nvim-test PRIVATE "-framework CoreServices")
- endif()
- target_include_directories(nvim-test PRIVATE ${LUAJIT_INCLUDE_DIRS})
- target_compile_definitions(nvim-test PRIVATE UNIT_TESTING)
-endif()
-
-if(CLANG_ASAN_UBSAN)
- message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
- if(CI_BUILD)
- # Try to recover from all sanitize issues so we get reports about all failures
- target_compile_options(nvim PRIVATE -fsanitize-recover=all)
- else()
- target_compile_options(nvim PRIVATE -fno-sanitize-recover=all)
- endif()
- target_compile_options(nvim PRIVATE
- -fno-omit-frame-pointer
- -fno-optimize-sibling-calls
- -fsanitize=address
- -fsanitize=undefined
- -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist)
- target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined)
-elseif(CLANG_MSAN)
- message(STATUS "Enabling Clang memory sanitizer for nvim.")
- target_compile_options(nvim PRIVATE
- -fsanitize=memory
- -fsanitize-memory-track-origins
- -fno-omit-frame-pointer
- -fno-optimize-sibling-calls)
- target_link_libraries(nvim PRIVATE -fsanitize=memory -fsanitize-memory-track-origins)
-elseif(CLANG_TSAN)
- message(STATUS "Enabling Clang thread sanitizer for nvim.")
- target_compile_options(nvim PRIVATE -fsanitize=thread -fPIE)
- target_link_libraries(nvim PRIVATE -fsanitize=thread)
-endif()
-
-function(get_test_target prefix sfile relative_path_var target_var)
- get_filename_component(full_d "${sfile}" DIRECTORY)
- file(RELATIVE_PATH d "${PROJECT_SOURCE_DIR}/src/nvim" "${full_d}")
- if(d MATCHES "^[.][.]")
- file(RELATIVE_PATH d "${GENERATED_DIR}" "${full_d}")
- endif()
- get_filename_component(r "${sfile}" NAME)
- if(NOT d MATCHES "^[.]?$")
- set(r "${d}/${r}")
- endif()
- string(REGEX REPLACE "[/.]" "-" suffix "${r}")
- set(${relative_path_var} ${r} PARENT_SCOPE)
- if(prefix STREQUAL "")
- set(${target_var} "${suffix}" PARENT_SCOPE)
- else()
- set(${target_var} "${prefix}-${suffix}" PARENT_SCOPE)
- endif()
-endfunction()
+#-------------------------------------------------------------------------------
+# Lint
+#-------------------------------------------------------------------------------
+find_program(CLANG_TIDY_PRG clang-tidy)
+set(EXCLUDE_CLANG_TIDY typval_encode.c.h ui_events.in.h)
if(WIN32)
- set(NO_SINGLE_CHECK_HEADERS
+ list(APPEND EXCLUDE_CLANG_TIDY
os/pty_process_unix.h
os/unix_defs.h)
else()
- set(NO_SINGLE_CHECK_HEADERS
+ list(APPEND EXCLUDE_CLANG_TIDY
os/win_defs.h
os/pty_process_win.h
os/pty_conpty_win.h
os/os_win_console.h)
endif()
-foreach(hfile ${NVIM_HEADERS})
- get_test_target(test-includes "${hfile}" relative_path texe)
-
- if(NOT ${hfile} MATCHES "[.](c|in)[.]h$")
- set(tsource "${GENERATED_DIR}/${relative_path}.test-include.c")
- write_file("${tsource}" "#include \"${hfile}\"\nint main(int argc, char **argv) { return 0; }")
- add_executable(
- ${texe}
- EXCLUDE_FROM_ALL
- ${tsource} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_HEADERS})
- target_link_libraries(${texe} PRIVATE main_lib)
- set_target_properties(${texe} PROPERTIES FOLDER test)
-
- list(FIND NO_SINGLE_CHECK_HEADERS "${relative_path}" hfile_exclude_idx)
- if(${hfile_exclude_idx} EQUAL -1)
- list(APPEND HEADER_CHECK_TARGETS ${texe})
- endif()
- endif()
-endforeach()
-add_custom_target(check-single-includes DEPENDS ${HEADER_CHECK_TARGETS})
+add_glob_target(
+ TARGET lintc-clang-tidy
+ COMMAND ${CLANG_TIDY_PRG}
+ FILES ${NVIM_SOURCES} ${NVIM_HEADERS}
+ FLAGS --quiet
+ EXCLUDE ${EXCLUDE_CLANG_TIDY})
+
+# These are the same warnings as https://neovim.io/doc/reports/clang/. The
+# checks we ignore are meant to be removed eventually, but we can only do so
+# after we properly fix the problems without breaking CI.
+add_glob_target(
+ TARGET clang-analyzer
+ COMMAND ${CLANG_TIDY_PRG}
+ FILES ${NVIM_SOURCES} ${NVIM_HEADERS}
+ FLAGS --quiet
+ --checks='
+ -*,
+ clang-analyzer-*,
+ -clang-analyzer-core.NonNullParamChecker,
+ -clang-analyzer-core.NullDereference,
+ -clang-analyzer-core.UndefinedBinaryOperatorResult,
+ -clang-analyzer-core.uninitialized.Assign,
+ -clang-analyzer-optin.performance.Padding,
+ -clang-analyzer-security.insecureAPI.strcpy,
+ '
+ EXCLUDE ${EXCLUDE_CLANG_TIDY})
+
+add_custom_target(copy_compile_commands
+ COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/compile_commands.json ${PROJECT_SOURCE_DIR}/compile_commands.json)
+add_dependencies(copy_compile_commands nvim)
+add_dependencies(lintc-clang-tidy copy_compile_commands)
+add_dependencies(clang-analyzer copy_compile_commands)
if(CI_BUILD)
set(LINT_OUTPUT_FORMAT gh_action)
else()
set(LINT_OUTPUT_FORMAT vs7)
endif()
-
add_glob_target(
TARGET lintc-clint
COMMAND ${PROJECT_SOURCE_DIR}/src/clint.py
@@ -972,21 +867,17 @@ add_glob_target(
EXCLUDE
tui/terminfo_defs.h)
-add_custom_target(uncrustify-version
- COMMAND ${CMAKE_COMMAND}
- -D UNCRUSTIFY_PRG=${UNCRUSTIFY_PRG}
- -D CONFIG_FILE=${PROJECT_SOURCE_DIR}/src/uncrustify.cfg
- -P ${PROJECT_SOURCE_DIR}/cmake/CheckUncrustifyVersion.cmake)
+set(UNCRUSTIFY_PRG ${DEPS_BIN_DIR}/uncrustify)
+set(UNCRUSTIFY_CONFIG ${PROJECT_SOURCE_DIR}/src/uncrustify.cfg)
+
+add_custom_target(uncrustify_update_config
+ ${UNCRUSTIFY_PRG} -c ${UNCRUSTIFY_CONFIG} --update-config-with-doc -o ${UNCRUSTIFY_CONFIG})
add_glob_target(
TARGET lintc-uncrustify
COMMAND ${UNCRUSTIFY_PRG}
- FLAGS -c "${PROJECT_SOURCE_DIR}/src/uncrustify.cfg" -q --check
+ FLAGS -c ${UNCRUSTIFY_CONFIG} -q --check
FILES ${LINT_NVIM_SOURCES})
-add_dependencies(lintc-uncrustify uncrustify-version)
-
-add_custom_target(lintc)
-add_dependencies(lintc lintc-clint lintc-uncrustify)
add_custom_target(formatc
COMMAND ${CMAKE_COMMAND}
@@ -994,7 +885,13 @@ add_custom_target(formatc
-D LANG=c
-P ${PROJECT_SOURCE_DIR}/cmake/Format.cmake
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
-add_dependencies(formatc uncrustify-version)
+
+add_dependencies(lintc-uncrustify uncrustify_update_config)
+add_dependencies(formatc uncrustify_update_config)
+add_dependencies(uncrustify_update_config uncrustify)
+
+add_custom_target(lintc)
+add_dependencies(lintc lintc-clint lintc-uncrustify lintc-clang-tidy)
add_custom_target(generated-sources DEPENDS
${NVIM_GENERATED_FOR_SOURCES}
@@ -1004,5 +901,64 @@ add_custom_target(generated-sources DEPENDS
add_subdirectory(po)
-include(GetCompileFlags)
-get_compile_flags(NVIM_VERSION_CFLAGS)
+#-------------------------------------------------------------------------------
+# Docs
+#-------------------------------------------------------------------------------
+
+set(VIMDOC_FILES
+ ${NVIM_RUNTIME_DIR}/doc/api.mpack
+ ${NVIM_RUNTIME_DIR}/doc/api.txt
+ ${NVIM_RUNTIME_DIR}/doc/diagnostic.mpack
+ ${NVIM_RUNTIME_DIR}/doc/diagnostic.txt
+ ${NVIM_RUNTIME_DIR}/doc/lsp.mpack
+ ${NVIM_RUNTIME_DIR}/doc/lsp.txt
+ ${NVIM_RUNTIME_DIR}/doc/lua.mpack
+ ${NVIM_RUNTIME_DIR}/doc/lua.txt
+ ${NVIM_RUNTIME_DIR}/doc/treesitter.mpack
+ ${NVIM_RUNTIME_DIR}/doc/treesitter.txt
+)
+
+glob_wrapper(API_SOURCES ${PROJECT_SOURCE_DIR}/src/nvim/api/*.c)
+
+glob_wrapper(LUA_SOURCES
+ ${NVIM_RUNTIME_DIR}/lua/vim/*.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/filetype/*.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/lsp/*.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/treesitter/*.lua
+)
+
+add_custom_command(
+ OUTPUT ${VIMDOC_FILES}
+ COMMAND ${PROJECT_SOURCE_DIR}/scripts/gen_vimdoc.py
+ DEPENDS
+ nvim
+ ${API_SOURCES}
+ ${LUA_SOURCES}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+)
+
+set(GEN_EVAL_FILES
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/vimfn.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/api.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/api_keysets.lua
+ ${NVIM_RUNTIME_DIR}/doc/builtin.txt
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/options.lua
+ ${NVIM_RUNTIME_DIR}/doc/options.txt
+)
+
+add_custom_command(
+ OUTPUT ${GEN_EVAL_FILES}
+ COMMAND $<TARGET_FILE:nvim> -l ${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua
+ DEPENDS
+ ${API_METADATA}
+ ${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua
+ ${PROJECT_SOURCE_DIR}/src/nvim/eval.lua
+ ${PROJECT_SOURCE_DIR}/src/nvim/options.lua
+ ${NVIM_RUNTIME_DIR}/doc/api.mpack
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+)
+
+add_custom_target(doc-eval DEPENDS ${GEN_EVAL_FILES})
+add_custom_target(doc-vim DEPENDS ${VIMDOC_FILES})
+add_custom_target(doc)
+add_dependencies(doc doc-vim doc-eval)
diff --git a/src/nvim/README.md b/src/nvim/README.md
index 6876227e48..b484d59cb3 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -1,8 +1,7 @@
Nvim core
=========
-Module-specific details are documented at the top of each module (`terminal.c`,
-`screen.c`, …).
+Module-specific details are documented at the top of each module (`terminal.c`, `undo.c`, …).
See `:help dev` for guidelines.
@@ -29,17 +28,17 @@ Logs
Low-level log messages sink to `$NVIM_LOG_FILE`.
-UI events are logged at DEBUG level (`LOGLVL_DBG`).
+UI events are logged at DEBUG level.
rm -rf build/
- make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0"
+ make CMAKE_EXTRA_FLAGS="-DLOG_DEBUG"
Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an
alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires
`-no-pie` ([ref](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860394#15)):
rm -rf build/
- make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0 -DCMAKE_C_FLAGS=-no-pie"
+ make CMAKE_EXTRA_FLAGS="-DLOG_DEBUG -DCMAKE_C_FLAGS=-no-pie"
Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to
filter the log, e.g. at DEBUG level you might want to exclude UI messages:
@@ -60,9 +59,9 @@ Requires clang 3.4 or later, and `llvm-symbolizer` must be in `$PATH`:
Build Nvim with sanitizer instrumentation (choose one):
- CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON"
- CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_MSAN=ON"
- CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_TSAN=ON"
+ CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_ASAN_UBSAN=ON"
+ CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_MSAN=ON"
+ CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_TSAN=ON"
Create a directory to store logs:
@@ -70,10 +69,9 @@ Create a directory to store logs:
Configure the sanitizer(s) via these environment variables:
- # Change to detect_leaks=1 to detect memory leaks (slower).
+ # Change to detect_leaks=1 to detect memory leaks (slower, noisier).
export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan"
# Show backtraces in the logs.
- export UBSAN_OPTIONS=print_stacktrace=1
export MSAN_OPTIONS="log_path=${HOME}/logs/msan"
export TSAN_OPTIONS="log_path=${HOME}/logs/tsan"
@@ -81,6 +79,13 @@ Logs will be written to `${HOME}/logs/*san.PID` then.
For more information: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
+Reproducible build
+------------------
+
+To make a reproducible build of Nvim, set cmake variable `LUA_GEN_PRG` to
+a LuaJIT binary built with `LUAJIT_SECURITY_PRN=0`. See commit
+cb757f2663e6950e655c6306d713338dfa66b18d.
+
Debug: Performance
------------------
@@ -191,6 +196,31 @@ possible to see exactly what terminfo values Nvim is using on any system.
nvim -V3log
+### TUI Debugging with gdb/lldb
+
+Launching the nvim TUI involves two processes, one for main editor state and one
+for rendering the TUI. Both of these processes use the nvim binary, so somewhat
+confusingly setting a breakpoint in either will generally succeed but may not be
+hit depending on which process the breakpoints were set in.
+
+To debug the main process, you can debug the nvim binary with the `--headless`
+flag which does not launch the TUI and will allow you to set breakpoints in code
+not related to TUI rendering like so:
+
+ lldb -- ./build/bin/nvim --headless --listen ~/.cache/nvim/debug-server.pipe
+
+While in lldb, enter `run`. You can then attach to the headless process in a
+new terminal window to interact with the editor like so:
+
+ ./build/bin/nvim --remote-ui --server ~/.cache/nvim/debug-server.pipe
+
+Conversely for debugging TUI rendering, you can start a headless process and
+debug the remote-ui process multiple times without losing editor state.
+
+For details on using nvim-dap and automatically debugging the child (main)
+process, see
+[here](https://zignar.net/2023/02/17/debugging-neovim-with-neovim-and-nvim-dap/)
+
### TUI trace
The ancient `script` command is still the "state of the art" for tracing
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 931363e199..08d9d8e117 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -1,27 +1,27 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "lauxlib.h"
+#include "klib/kvec.h"
#include "nvim/api/autocmd.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/autocmd.c.generated.h"
@@ -32,13 +32,11 @@
// Copy string or array of strings into an empty array.
// Get the event number, unless it is an error. Then goto `goto_name`.
#define GET_ONE_EVENT(event_nr, event_str, goto_name) \
- char *__next_ev; \
event_T event_nr = \
- event_name2nr(event_str.data.string.data, &__next_ev); \
- if (event_nr >= NUM_EVENTS) { \
- api_set_error(err, kErrorTypeValidation, "unexpected event"); \
+ event_name2nr_str(event_str.data.string); \
+ VALIDATE_S((event_nr < NUM_EVENTS), "event", event_str.data.string.data, { \
goto goto_name; \
- }
+ });
// ID for associating autocmds created via nvim_create_autocmd
// Used to delete autocmds from nvim_del_autocmd
@@ -47,19 +45,20 @@ static int64_t next_autocmd_id = 1;
/// Get all autocommands that match the corresponding {opts}.
///
/// These examples will get autocommands matching ALL the given criteria:
-/// <pre>lua
-/// -- Matches all criteria
-/// autocommands = vim.api.nvim_get_autocmds({
-/// group = "MyGroup",
-/// event = {"BufEnter", "BufWinEnter"},
-/// pattern = {"*.c", "*.h"}
-/// })
///
-/// -- All commands from one group
-/// autocommands = vim.api.nvim_get_autocmds({
-/// group = "MyGroup",
-/// })
-/// </pre>
+/// ```lua
+/// -- Matches all criteria
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// event = {"BufEnter", "BufWinEnter"},
+/// pattern = {"*.c", "*.h"}
+/// })
+///
+/// -- All commands from one group
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// })
+/// ```
///
/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that
/// match any combination of them.
@@ -107,25 +106,24 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
break;
case kObjectTypeString:
group = augroup_find(opts->group.data.string.data);
- if (group < 0) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ VALIDATE_S((group >= 0), "group", opts->group.data.string.data, {
goto cleanup;
- }
+ });
break;
case kObjectTypeInteger:
group = (int)opts->group.data.integer;
- char *name = augroup_name(group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ char *name = group == 0 ? NULL : augroup_name(group);
+ VALIDATE_INT(augroup_exists(name), "group", opts->group.data.integer, {
goto cleanup;
- }
+ });
break;
default:
- api_set_error(err, kErrorTypeValidation, "group must be a string or an integer.");
- goto cleanup;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
+ goto cleanup;
+ });
}
- if (opts->event.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, get_autocmds, event)) {
check_event = true;
Object v = opts->event;
@@ -134,57 +132,49 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
event_set[event_nr] = true;
} else if (v.type == kObjectTypeArray) {
FOREACH_ITEM(v.data.array, event_v, {
- if (event_v.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Every event must be a string in 'event'");
+ VALIDATE_T("event item", kObjectTypeString, event_v.type, {
goto cleanup;
- }
+ });
GET_ONE_EVENT(event_nr, event_v, cleanup);
event_set[event_nr] = true;
})
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "Not a valid 'event' value. Must be a string or an array");
- goto cleanup;
+ VALIDATE_EXP(false, "event", "String or Array", NULL, {
+ goto cleanup;
+ });
}
}
- if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Cannot use both 'pattern' and 'buffer'");
+ VALIDATE((!HAS_KEY(opts, get_autocmds, pattern) || !HAS_KEY(opts, get_autocmds, buffer)),
+ "%s", "Cannot use both 'pattern' and 'buffer'", {
goto cleanup;
- }
+ });
int pattern_filter_count = 0;
- if (opts->pattern.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, get_autocmds, pattern)) {
Object v = opts->pattern;
if (v.type == kObjectTypeString) {
pattern_filters[pattern_filter_count] = v.data.string.data;
pattern_filter_count += 1;
} else if (v.type == kObjectTypeArray) {
- if (v.data.array.size > AUCMD_MAX_PATTERNS) {
- api_set_error(err, kErrorTypeValidation,
- "Too many patterns. Please limit yourself to %d or fewer",
- AUCMD_MAX_PATTERNS);
+ VALIDATE((v.data.array.size <= AUCMD_MAX_PATTERNS),
+ "Too many patterns (maximum of %d)", AUCMD_MAX_PATTERNS, {
goto cleanup;
- }
+ });
FOREACH_ITEM(v.data.array, item, {
- if (item.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string");
+ VALIDATE_T("pattern", kObjectTypeString, item.type, {
goto cleanup;
- }
+ });
pattern_filters[pattern_filter_count] = item.data.string.data;
pattern_filter_count += 1;
});
} else {
- api_set_error(err, kErrorTypeValidation,
- "Not a valid 'pattern' value. Must be a string or an array");
- goto cleanup;
+ VALIDATE_EXP(false, "pattern", "String or Array", api_typename(v.type), {
+ goto cleanup;
+ });
}
}
@@ -194,34 +184,33 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
goto cleanup;
}
- snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal));
+ snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
} else if (opts->buffer.type == kObjectTypeArray) {
if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) {
- api_set_error(err,
- kErrorTypeValidation,
- "Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS);
+ api_set_error(err, kErrorTypeValidation, "Too many buffers (maximum of %d)",
+ AUCMD_MAX_PATTERNS);
goto cleanup;
}
FOREACH_ITEM(opts->buffer.data.array, bufnr, {
- if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer");
+ VALIDATE_EXP((bufnr.type == kObjectTypeInteger || bufnr.type == kObjectTypeBuffer),
+ "buffer", "Integer", api_typename(bufnr.type), {
goto cleanup;
- }
+ });
buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
- snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal));
+ snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
+ });
+ } else if (HAS_KEY(opts, get_autocmds, buffer)) {
+ VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), {
+ goto cleanup;
});
- } else if (opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid value for 'buffer': must be an integer or array of integers");
- goto cleanup;
}
FOREACH_ITEM(buffers, bufnr, {
@@ -234,8 +223,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
continue;
}
- for (AutoPat *ap = au_get_autopat_for_event(event); ap != NULL; ap = ap->next) {
- if (ap->cmds == NULL) {
+ AutoCmdVec *acs = au_get_autocmds_for_event(event);
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
+
+ if (ap == NULL) {
continue;
}
@@ -247,19 +240,16 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
// Skip 'pattern' from invalid patterns if passed.
if (pattern_filter_count > 0) {
bool passed = false;
- for (int i = 0; i < pattern_filter_count; i++) {
- assert(i < AUCMD_MAX_PATTERNS);
- assert(pattern_filters[i]);
+ for (int j = 0; j < pattern_filter_count; j++) {
+ assert(j < AUCMD_MAX_PATTERNS);
+ assert(pattern_filters[j]);
- char *pat = pattern_filters[i];
+ char *pat = pattern_filters[j];
int patlen = (int)strlen(pat);
if (aupat_is_buflocal(pat, patlen)) {
- aupat_normalize_buflocal_pat(pattern_buflocal,
- pat,
- patlen,
+ aupat_normalize_buflocal_pat(pattern_buflocal, pat, patlen,
aupat_get_buflocal_nr(pat, patlen));
-
pat = pattern_buflocal;
}
@@ -274,85 +264,71 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
}
}
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (aucmd_exec_is_deleted(ac->exec)) {
- continue;
- }
+ Dictionary autocmd_info = ARRAY_DICT_INIT;
- Dictionary autocmd_info = ARRAY_DICT_INIT;
-
- if (ap->group != AUGROUP_DEFAULT) {
- PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
- PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
- }
+ if (ap->group != AUGROUP_DEFAULT) {
+ PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
+ PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
+ }
- if (ac->id > 0) {
- PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
- }
+ if (ac->id > 0) {
+ PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
+ }
- if (ac->desc != NULL) {
- PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
- }
+ if (ac->desc != NULL) {
+ PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
+ }
- if (ac->exec.type == CALLABLE_CB) {
- PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
+ if (ac->exec.type == CALLABLE_CB) {
+ PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
- Callback *cb = &ac->exec.callable.cb;
- switch (cb->type) {
- case kCallbackLua:
- if (nlua_ref_is_function(cb->data.luaref)) {
- PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
- }
- break;
- case kCallbackFuncref:
- case kCallbackPartial:
- PUT(autocmd_info, "callback", STRING_OBJ(cstr_as_string(callback_to_string(cb))));
- break;
- default:
- abort();
+ Callback *cb = &ac->exec.callable.cb;
+ switch (cb->type) {
+ case kCallbackLua:
+ if (nlua_ref_is_function(cb->data.luaref)) {
+ PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
}
- } else {
- PUT(autocmd_info,
- "command",
- STRING_OBJ(cstr_as_string(xstrdup(ac->exec.callable.cmd))));
+ break;
+ case kCallbackFuncref:
+ case kCallbackPartial:
+ PUT(autocmd_info, "callback", CSTR_AS_OBJ(callback_to_string(cb)));
+ break;
+ case kCallbackNone:
+ abort();
}
+ } else {
+ PUT(autocmd_info, "command", CSTR_TO_OBJ(ac->exec.callable.cmd));
+ }
- PUT(autocmd_info,
- "pattern",
- STRING_OBJ(cstr_to_string((char *)ap->pat)));
-
- PUT(autocmd_info,
- "event",
- STRING_OBJ(cstr_to_string((char *)event_nr2name(event))));
-
- PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
-
- if (ap->buflocal_nr) {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
- PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
- } else {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
- }
+ PUT(autocmd_info, "pattern", CSTR_TO_OBJ(ap->pat));
+ PUT(autocmd_info, "event", CSTR_TO_OBJ(event_nr2name(event)));
+ PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
- // TODO(sctx): It would be good to unify script_ctx to actually work with lua
- // right now it's just super weird, and never really gives you the info that
- // you would expect from this.
- //
- // I think we should be able to get the line number, filename, etc. from lua
- // when we're executing something, and it should be easy to then save that
- // info here.
- //
- // I think it's a big loss not getting line numbers of where options, autocmds,
- // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
- //
- // Once we do that, we can put these into the autocmd_info, but I don't think it's
- // useful to do that at this time.
- //
- // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
- // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
-
- ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
+ if (ap->buflocal_nr) {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
+ PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
+ } else {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
}
+
+ // TODO(sctx): It would be good to unify script_ctx to actually work with lua
+ // right now it's just super weird, and never really gives you the info that
+ // you would expect from this.
+ //
+ // I think we should be able to get the line number, filename, etc. from lua
+ // when we're executing something, and it should be easy to then save that
+ // info here.
+ //
+ // I think it's a big loss not getting line numbers of where options, autocmds,
+ // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
+ //
+ // Once we do that, we can put these into the autocmd_info, but I don't think it's
+ // useful to do that at this time.
+ //
+ // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
+ // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
+
+ ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
}
}
@@ -365,28 +341,31 @@ cleanup:
/// function _name_ string) or `command` (Ex command string).
///
/// Example using Lua callback:
-/// <pre>lua
-/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
-/// pattern = {"*.c", "*.h"},
-/// callback = function(ev)
-/// print(string.format('event fired: %s', vim.inspect(ev)))
-/// end
-/// })
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// callback = function(ev)
+/// print(string.format('event fired: %s', vim.inspect(ev)))
+/// end
+/// })
+/// ```
///
/// Example using an Ex command as the handler:
-/// <pre>lua
-/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
-/// pattern = {"*.c", "*.h"},
-/// command = "echo 'Entering a C or C++ file'",
-/// })
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// command = "echo 'Entering a C or C++ file'",
+/// })
+/// ```
///
/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME"
/// and "~" must be expanded explicitly:
-/// <pre>lua
-/// pattern = vim.fn.expand("~") .. "/some/path/*.py"
-/// </pre>
+///
+/// ```lua
+/// pattern = vim.fn.expand("~") .. "/some/path/*.py"
+/// ```
///
/// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`).
/// @param opts Options dict:
@@ -404,7 +383,7 @@ cleanup:
/// - match: (string) expanded value of |<amatch>|
/// - buf: (number) expanded value of |<abuf>|
/// - file: (string) expanded value of |<afile>|
-/// - data: (any) arbitrary data passed to |nvim_exec_autocmds()|
+/// - data: (any) arbitrary data passed from |nvim_exec_autocmds()|
/// - command (string) optional: Vim command to execute on event. Cannot be used with
/// {callback}
/// - once (boolean) optional: defaults to false. Run the autocommand
@@ -421,10 +400,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
{
int64_t autocmd_id = -1;
char *desc = NULL;
-
Array patterns = ARRAY_DICT_INIT;
Array event_array = ARRAY_DICT_INIT;
-
AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT;
Callback cb = CALLBACK_NONE;
@@ -432,30 +409,23 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
goto cleanup;
}
- if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both");
+ VALIDATE((!HAS_KEY(opts, create_autocmd, callback) || !HAS_KEY(opts, create_autocmd, command)),
+ "%s", "Cannot use both 'callback' and 'command'", {
goto cleanup;
- } else if (opts->callback.type != kObjectTypeNil) {
- // TODO(tjdevries): It's possible we could accept callable tables,
- // but we don't do that many other places, so for the moment let's
- // not do that.
+ });
+
+ if (HAS_KEY(opts, create_autocmd, callback)) {
+ // NOTE: We could accept callable tables, but that isn't common in the API.
Object *callback = &opts->callback;
switch (callback->type) {
case kObjectTypeLuaRef:
- if (callback->data.luaref == LUA_NOREF) {
- api_set_error(err,
- kErrorTypeValidation,
- "must pass an actual value");
+ VALIDATE_S((callback->data.luaref != LUA_NOREF), "callback", "<no value>", {
goto cleanup;
- }
-
- if (!nlua_ref_is_function(callback->data.luaref)) {
- api_set_error(err,
- kErrorTypeValidation,
- "must pass a function for callback");
+ });
+ VALIDATE_S(nlua_ref_is_function(callback->data.luaref), "callback", "<not a function>", {
goto cleanup;
- }
+ });
cb.type = kCallbackLua;
cb.data.luaref = api_new_luaref(callback->data.luaref);
@@ -465,61 +435,50 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
cb.data.funcref = string_to_cstr(callback->data.string);
break;
default:
- api_set_error(err,
- kErrorTypeException,
- "'callback' must be a lua function or name of vim function");
- goto cleanup;
+ VALIDATE_EXP(false, "callback", "Lua function or Vim function name",
+ api_typename(callback->type), {
+ goto cleanup;
+ });
}
aucmd.type = CALLABLE_CB;
aucmd.callable.cb = cb;
- } else if (opts->command.type != kObjectTypeNil) {
- Object *command = &opts->command;
- if (command->type == kObjectTypeString) {
- aucmd.type = CALLABLE_EX;
- aucmd.callable.cmd = string_to_cstr(command->data.string);
- } else {
- api_set_error(err,
- kErrorTypeValidation,
- "'command' must be a string");
- goto cleanup;
- }
+ } else if (HAS_KEY(opts, create_autocmd, command)) {
+ aucmd.type = CALLABLE_EX;
+ aucmd.callable.cmd = string_to_cstr(opts->command);
} else {
- api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'");
- goto cleanup;
+ VALIDATE(false, "%s", "Required: 'command' or 'callback'", {
+ goto cleanup;
+ });
}
- bool is_once = api_object_to_bool(opts->once, "once", false, err);
- bool is_nested = api_object_to_bool(opts->nested, "nested", false, err);
-
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ bool has_buffer = HAS_KEY(opts, create_autocmd, buffer);
+
+ VALIDATE((!HAS_KEY(opts, create_autocmd, pattern) || !has_buffer),
+ "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
+ goto cleanup;
+ });
+
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
- if (opts->desc.type != kObjectTypeNil) {
- if (opts->desc.type == kObjectTypeString) {
- desc = opts->desc.data.string.data;
- } else {
- api_set_error(err,
- kErrorTypeValidation,
- "'desc' must be a string");
- goto cleanup;
- }
+ if (HAS_KEY(opts, create_autocmd, desc)) {
+ desc = opts->desc.data;
}
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ("*"));
}
- if (event_array.size == 0) {
- api_set_error(err, kErrorTypeValidation, "'event' is a required key");
+ VALIDATE_R((event_array.size > 0), "event", {
goto cleanup;
- }
+ });
autocmd_id = next_autocmd_id++;
FOREACH_ITEM(event_array, event_str, {
@@ -535,8 +494,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
pat.data.string.data,
(int)pat.data.string.size,
au_group,
- is_once,
- is_nested,
+ opts->once,
+ opts->nested,
desc,
aucmd);
});
@@ -556,25 +515,22 @@ cleanup:
return autocmd_id;
}
-/// Delete an autocommand by id.
+/// Deletes an autocommand by id.
///
-/// NOTE: Only autocommands created via the API have an id.
-/// @param id Integer The id returned by nvim_create_autocmd
-/// @see |nvim_create_autocmd()|
+/// @param id Integer Autocommand id returned by |nvim_create_autocmd()|
void nvim_del_autocmd(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- if (id <= 0) {
- api_set_error(err, kErrorTypeException, "Invalid autocmd id");
+ VALIDATE_INT((id > 0), "autocmd id", id, {
return;
- }
+ });
if (!autocmd_delete_id(id)) {
api_set_error(err, kErrorTypeException, "Failed to delete autocmd");
}
}
-/// Clear all autocommands that match the corresponding {opts}. To delete
-/// a particular autocmd, see |nvim_del_autocmd()|.
+/// Clears all autocommands selected by {opts}. To delete autocmds see |nvim_del_autocmd()|.
+///
/// @param opts Parameters
/// - event: (string|table)
/// Examples:
@@ -610,25 +566,26 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
goto cleanup;
}
- if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Cannot use both 'pattern' and 'buffer'");
+ bool has_buffer = HAS_KEY(opts, clear_autocmds, buffer);
+
+ VALIDATE((!HAS_KEY(opts, clear_autocmds, pattern) || !has_buffer),
+ "%s", "Cannot use both 'pattern' and 'buffer'", {
goto cleanup;
- }
+ });
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
// When we create the autocmds, we want to say that they are all matched, so that's *
// but when we clear them, we want to say that we didn't pass a pattern, so that's NUL
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ(""));
}
// If we didn't pass any events, that means clear all events.
@@ -636,7 +593,7 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
FOR_ALL_AUEVENTS(event) {
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
- if (!clear_autocmd(event, (char *)pat, au_group, err)) {
+ if (!clear_autocmd(event, pat, au_group, err)) {
goto cleanup;
}
});
@@ -647,7 +604,7 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
- if (!clear_autocmd(event_nr, (char *)pat, au_group, err)) {
+ if (!clear_autocmd(event_nr, pat, au_group, err)) {
goto cleanup;
}
});
@@ -662,11 +619,12 @@ cleanup:
/// Create or get an autocommand group |autocmd-groups|.
///
/// To get an existing group id, do:
-/// <pre>lua
-/// local id = vim.api.nvim_create_augroup("MyGroup", {
-/// clear = false
-/// })
-/// </pre>
+///
+/// ```lua
+/// local id = vim.api.nvim_create_augroup("MyGroup", {
+/// clear = false
+/// })
+/// ```
///
/// @param name String: The name of the group
/// @param opts Dictionary Parameters
@@ -691,7 +649,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
if (clear_autocmds) {
FOR_ALL_AUEVENTS(event) {
- aupat_del_for_event_and_group(event, augroup);
+ aucmd_del_for_event_and_group(event, augroup);
}
}
});
@@ -711,11 +669,9 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
void nvim_del_augroup_by_id(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- TRY_WRAP({
- try_start();
- char *name = augroup_name((int)id);
+ TRY_WRAP(err, {
+ char *name = id == 0 ? NULL : augroup_name((int)id);
augroup_del(name, false);
- try_end(err);
});
}
@@ -728,10 +684,8 @@ void nvim_del_augroup_by_id(Integer id, Error *err)
void nvim_del_augroup_by_name(String name, Error *err)
FUNC_API_SINCE(9)
{
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
augroup_del(name.data, false);
- try_end(err);
});
}
@@ -772,62 +726,59 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
break;
case kObjectTypeString:
au_group = augroup_find(opts->group.data.string.data);
- if (au_group == AUGROUP_ERROR) {
- api_set_error(err,
- kErrorTypeValidation,
- "invalid augroup: %s", opts->group.data.string.data);
+ VALIDATE_S((au_group != AUGROUP_ERROR), "group", opts->group.data.string.data, {
goto cleanup;
- }
+ });
break;
case kObjectTypeInteger:
au_group = (int)opts->group.data.integer;
- char *name = augroup_name(au_group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
+ char *name = au_group == 0 ? NULL : augroup_name(au_group);
+ VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
goto cleanup;
- }
+ });
break;
default:
- api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
- goto cleanup;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
+ goto cleanup;
+ });
}
- if (opts->buffer.type != kObjectTypeNil) {
- Object buf_obj = opts->buffer;
- if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) {
- api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type);
+ bool has_buffer = false;
+ if (HAS_KEY(opts, exec_autocmds, buffer)) {
+ VALIDATE((!HAS_KEY(opts, exec_autocmds, pattern)),
+ "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
goto cleanup;
- }
+ });
- buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err);
+ has_buffer = true;
+ buf = find_buffer_by_handle(opts->buffer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ(""));
}
- if (opts->data.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, exec_autocmds, data)) {
data = &opts->data;
}
- modeline = api_object_to_bool(opts->modeline, "modeline", true, err);
+ modeline = GET_BOOL_OR_TRUE(opts, exec_autocmds, modeline);
bool did_aucmd = false;
FOREACH_ITEM(event_array, event_str, {
GET_ONE_EVENT(event_nr, event_str, cleanup)
FOREACH_ITEM(patterns, pat, {
- char *fname = opts->buffer.type == kObjectTypeNil ? pat.data.string.data : NULL;
- did_aucmd |=
- apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
+ char *fname = !has_buffer ? pat.data.string.data : NULL;
+ did_aucmd |= apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
})
})
@@ -840,45 +791,19 @@ cleanup:
api_free_array(patterns);
}
-static bool check_autocmd_string_array(Array arr, char *k, Error *err)
-{
- FOREACH_ITEM(arr, entry, {
- if (entry.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All entries in '%s' must be strings",
- k);
- return false;
- }
-
- // Disallow newlines in the middle of the line.
- const String l = entry.data.string;
- if (memchr(l.data, NL, l.size)) {
- api_set_error(err, kErrorTypeValidation,
- "String cannot contain newlines");
- return false;
- }
- })
- return true;
-}
-
static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err)
{
if (v->type == kObjectTypeString) {
ADD(*array, copy_object(*v, NULL));
} else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(v->data.array, k, err)) {
+ if (!check_string_array(v->data.array, k, true, err)) {
return false;
}
*array = copy_array(v->data.array, NULL);
} else {
- if (required) {
- api_set_error(err,
- kErrorTypeValidation,
- "'%s' must be an array or a string.",
- k);
+ VALIDATE_EXP(!required, k, "Array or String", api_typename(v->type), {
return false;
- }
+ });
}
return true;
@@ -894,88 +819,71 @@ static int get_augroup_from_object(Object group, Error *err)
return AUGROUP_DEFAULT;
case kObjectTypeString:
au_group = augroup_find(group.data.string.data);
- if (au_group == AUGROUP_ERROR) {
- api_set_error(err,
- kErrorTypeValidation,
- "invalid augroup: %s", group.data.string.data);
-
+ VALIDATE_S((au_group != AUGROUP_ERROR), "group", group.data.string.data, {
return AUGROUP_ERROR;
- }
+ });
return au_group;
case kObjectTypeInteger:
au_group = (int)group.data.integer;
- char *name = augroup_name(au_group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
+ char *name = au_group == 0 ? NULL : augroup_name(au_group);
+ VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
return AUGROUP_ERROR;
- }
-
+ });
return au_group;
default:
- api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
- return AUGROUP_ERROR;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(group.type), {
+ return AUGROUP_ERROR;
+ });
}
}
-static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer,
- Error *err)
+static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, bool has_buffer,
+ Buffer buffer, Error *err)
{
const char pattern_buflocal[BUFLOCAL_PAT_LEN];
- if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "cannot pass both: 'pattern' and 'buffer' for the same autocmd");
- return false;
- } else if (pattern.type != kObjectTypeNil) {
+ if (pattern.type != kObjectTypeNil) {
Object *v = &pattern;
if (v->type == kObjectTypeString) {
- char *pat = v->data.string.data;
+ const char *pat = v->data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
- ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+ ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
} else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(v->data.array, "pattern", err)) {
+ if (!check_string_array(v->data.array, "pattern", true, err)) {
return false;
}
Array array = v->data.array;
FOREACH_ITEM(array, entry, {
- char *pat = entry.data.string.data;
+ const char *pat = entry.data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
- ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+ ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
})
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "'pattern' must be a string or table");
- return false;
- }
- } else if (buffer.type != kObjectTypeNil) {
- if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) {
- api_set_error(err,
- kErrorTypeValidation,
- "'buffer' must be an integer");
- return false;
+ VALIDATE_EXP(false, "pattern", "String or Table", api_typename(v->type), {
+ return false;
+ });
}
-
- buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err);
+ } else if (has_buffer) {
+ buf_T *buf = find_buffer_by_handle(buffer, err);
if (ERROR_SET(err)) {
return false;
}
snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
+ ADD(*patterns, CSTR_TO_OBJ(pattern_buflocal));
}
return true;
diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h
index f9432830d9..4ab3ddb943 100644
--- a/src/nvim/api/autocmd.h
+++ b/src/nvim/api/autocmd.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_API_AUTOCMD_H
-#define NVIM_API_AUTOCMD_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/autocmd.h.generated.h"
#endif
-#endif // NVIM_API_AUTOCMD_H
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index fe9e6077d6..0df231868d 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1,10 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Some of this code was adapted from 'if_py_both.h' from the original
// vim source
-#include <assert.h>
#include <lauxlib.h>
#include <stdbool.h>
#include <stddef.h>
@@ -14,9 +10,11 @@
#include "klib/kvec.h"
#include "lua.h"
#include "nvim/api/buffer.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -26,6 +24,7 @@
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/mapping.h"
@@ -34,10 +33,11 @@
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/ops.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/state_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.c.generated.h"
@@ -47,7 +47,7 @@
///
/// \brief For more information on buffers, see |buffers|
///
-/// Unloaded Buffers:~
+/// Unloaded Buffers: ~
///
/// Buffers may be unloaded by the |:bunload| command or the buffer's
/// |'bufhidden'| option. When a buffer is unloaded its file contents are freed
@@ -83,12 +83,16 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// Activates buffer-update events on a channel, or as Lua callbacks.
///
/// Example (Lua): capture buffer updates in a global `events` variable
-/// (use "print(vim.inspect(events))" to see its contents):
-/// <pre>lua
-/// events = {}
-/// vim.api.nvim_buf_attach(0, false, {
-/// on_lines=function(...) table.insert(events, {...}) end})
-/// </pre>
+/// (use "vim.print(events)" to see its contents):
+///
+/// ```lua
+/// events = {}
+/// vim.api.nvim_buf_attach(0, false, {
+/// on_lines = function(...)
+/// table.insert(events, {...})
+/// end,
+/// })
+/// ```
///
/// @see |nvim_buf_detach()|
/// @see |api-buffer-updates-lua|
@@ -111,7 +115,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// - byte count of previous contents
/// - deleted_codepoints (if `utf_sizes` is true)
/// - deleted_codeunits (if `utf_sizes` is true)
-/// - on_bytes: lua callback invoked on change.
+/// - on_bytes: Lua callback invoked on change.
/// This callback receives more granular information about the
/// change compared to on_lines.
/// Return `true` to detach.
@@ -179,11 +183,9 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer,
if (is_lua) {
for (size_t j = 0; cbs[j].name; j++) {
if (strequal(cbs[j].name, k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[j].name);
+ VALIDATE_T(cbs[j].name, kObjectTypeLuaRef, v->type, {
goto error;
- }
+ });
*(cbs[j].dest) = v->data.luaref;
v->data.luaref = LUA_NOREF;
key_used = true;
@@ -194,26 +196,23 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer,
if (key_used) {
continue;
} else if (strequal("utf_sizes", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
+ VALIDATE_T("utf_sizes", kObjectTypeBoolean, v->type, {
goto error;
- }
+ });
cb.utf_sizes = v->data.boolean;
key_used = true;
} else if (strequal("preview", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "preview must be boolean");
+ VALIDATE_T("preview", kObjectTypeBoolean, v->type, {
goto error;
- }
+ });
cb.preview = v->data.boolean;
key_used = true;
}
}
- if (!key_used) {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ VALIDATE_S(key_used, "'opts' key", k.data, {
goto error;
- }
+ });
}
return buf_updates_register(buf, channel_id, cb, send_buffer);
@@ -252,6 +251,9 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
if (!buf) {
return;
}
+ if (last < 0) {
+ last = buf->b_ml.ml_line_count;
+ }
redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last);
}
@@ -297,10 +299,9 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob);
- if (strict_indexing && oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", {
return rv;
- }
+ });
if (start >= end) {
// Return 0-length array
@@ -325,28 +326,6 @@ end:
return rv;
}
-static bool check_string_array(Array arr, bool disallow_nl, Error *err)
-{
- for (size_t i = 0; i < arr.size; i++) {
- if (arr.items[i].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All items in the replacement array must be strings");
- return false;
- }
- // Disallow newlines in the middle of the line.
- if (disallow_nl) {
- const String l = arr.items[i].data.string;
- if (memchr(l.data, NL, l.size)) {
- api_set_error(err, kErrorTypeValidation,
- "String cannot contain newlines");
- return false;
- }
- }
- }
- return true;
-}
-
/// Sets (replaces) a line-range in the buffer.
///
/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
@@ -371,7 +350,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err)
void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integer end,
Boolean strict_indexing, ArrayOf(String) replacement, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -379,24 +358,27 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
return;
}
+ // load buffer first if it's not loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ if (!buf_ensure_loaded(buf)) {
+ api_set_error(err, kErrorTypeException, "Failed to load buffer");
+ return;
+ }
+ }
+
bool oob = false;
start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob);
- if (strict_indexing && oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", {
return;
- }
-
- if (start > end) {
- api_set_error(err,
- kErrorTypeValidation,
- "Argument \"start\" is higher than \"end\"");
+ });
+ VALIDATE((start <= end), "%s", "'start' is higher than 'end'", {
return;
- }
+ });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
- if (!check_string_array(replacement, disallow_nl, err)) {
+ if (!check_string_array(replacement, "replacement string", disallow_nl, err)) {
return;
}
@@ -415,27 +397,25 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
}
try_start();
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
goto end;
}
- if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) {
+ if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
- bcount_t deleted_bytes = get_region_bytecount(curbuf, (linenr_T)start, (linenr_T)end, 0, 0);
+ bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0);
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
- size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0;
+ size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
- if (ml_delete((linenr_T)start, false) == FAIL) {
+ if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -453,12 +433,11 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start + (int64_t)i;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
+ if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -473,12 +452,11 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start + (int64_t)i - 1;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
+ if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -493,20 +471,21 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
- // Only adjust marks if we managed to switch to a window that holds
- // the buffer, otherwise line numbers will be invalid.
- mark_adjust((linenr_T)start,
- (linenr_T)(end - 1),
- MAXLNUM,
- (linenr_T)extra,
- kExtmarkNOOP);
-
- extmark_splice(curbuf, (int)start - 1, 0, (int)(end - start), 0,
+ linenr_T adjust = end > start ? MAXLNUM : 0;
+ mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra,
+ true, true, kExtmarkNOOP);
+
+ extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0,
deleted_bytes, (int)new_len, 0, inserted_bytes,
kExtmarkUndo);
- changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
- fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
+ changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
+
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_buffer == buf) {
+ fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra);
+ }
+ }
end:
for (size_t i = 0; i < new_len; i++) {
@@ -514,7 +493,6 @@ end:
}
xfree(lines);
- aucmd_restbuf(&aco);
try_end(err);
}
@@ -533,7 +511,10 @@ end:
///
/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
///
+/// Prefer |nvim_put()| if you want to insert text at the cursor position.
+///
/// @see |nvim_buf_set_lines()|
+/// @see |nvim_put()|
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
@@ -546,10 +527,11 @@ end:
void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col,
Integer end_row, Integer end_col, ArrayOf(String) replacement, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
MAXSIZE_TEMP_ARRAY(scratch, 1);
if (replacement.size == 0) {
- ADD_C(scratch, STRING_OBJ(STATIC_CSTR_AS_STRING("")));
+ ADD_C(scratch, STATIC_CSTR_AS_OBJ(""));
replacement = scratch;
}
@@ -558,48 +540,54 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
return;
}
+ // load buffer first if it's not loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ if (!buf_ensure_loaded(buf)) {
+ api_set_error(err, kErrorTypeException, "Failed to load buffer");
+ return;
+ }
+ }
+
bool oob = false;
// check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?)
start_row = normalize_index(buf, start_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
+ VALIDATE_RANGE((!oob), "start_row", {
return;
- }
+ });
end_row = normalize_index(buf, end_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
+ VALIDATE_RANGE((!oob), "end_row", {
return;
- }
+ });
char *str_at_start = NULL;
char *str_at_end = NULL;
// Another call to ml_get_buf() may free the line, so make a copy.
- str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false));
+ str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row));
size_t len_at_start = strlen(str_at_start);
- if (start_col < 0 || (size_t)start_col > len_at_start) {
- api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
+ start_col = start_col < 0 ? (int64_t)len_at_start + start_col + 1 : start_col;
+ VALIDATE_RANGE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col", {
goto early_end;
- }
+ });
// Another call to ml_get_buf() may free the line, so make a copy.
- str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false));
+ str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row));
size_t len_at_end = strlen(str_at_end);
- if (end_col < 0 || (size_t)end_col > len_at_end) {
- api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
+ end_col = end_col < 0 ? (int64_t)len_at_end + end_col + 1 : end_col;
+ VALIDATE_RANGE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col", {
goto early_end;
- }
+ });
- if (start_row > end_row || (end_row == start_row && start_col > end_col)) {
- api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)),
+ "%s", "'start' is higher than 'end'", {
goto early_end;
- }
+ });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
- if (!check_string_array(replacement, disallow_nl, err)) {
+ if (!check_string_array(replacement, "replacement string", disallow_nl, err)) {
goto early_end;
}
@@ -616,7 +604,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (int64_t i = 1; i < end_row - start_row; i++) {
int64_t lnum = start_row + i;
- const char *bufline = ml_get_buf(buf, (linenr_T)lnum, false);
+ const char *bufline = ml_get_buf(buf, (linenr_T)lnum);
old_byte += (bcount_t)(strlen(bufline)) + 1;
}
old_byte += (bcount_t)end_col + 1;
@@ -662,8 +650,6 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
}
try_start();
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
@@ -672,7 +658,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// Small note about undo states: unlike set_lines, we want to save the
// undo state of one past the end_row, since end_row is inclusive.
- if (u_save((linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
+ if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
@@ -683,9 +669,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
- size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0;
+ size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
- if (ml_delete((linenr_T)start_row, false) == FAIL) {
+ if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -702,12 +688,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start_row + (int64_t)i;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
+ if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -720,12 +705,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start_row + (int64_t)i - 1;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
+ if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -736,35 +720,39 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
extra++;
}
+ colnr_T col_extent = (colnr_T)(end_col
+ - ((end_row == start_row) ? start_col : 0));
+
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
- mark_adjust((linenr_T)start_row,
- (linenr_T)end_row,
- MAXLNUM,
- (linenr_T)extra,
- kExtmarkNOOP);
+ // Do not adjust any cursors. need to use column-aware logic (below)
+ linenr_T adjust = end_row >= start_row ? MAXLNUM : 0;
+ mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra,
+ true, true, kExtmarkNOOP);
- colnr_T col_extent = (colnr_T)(end_col
- - ((end_row == start_row) ? start_col : 0));
extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col,
(int)(end_row - start_row), col_extent, old_byte,
(int)new_len - 1, (colnr_T)last_item.size, new_byte,
kExtmarkUndo);
- changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
+ changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
- // adjust cursor like an extmark ( i e it was inside last_part_len)
- if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) {
- curwin->w_cursor.col -= col_extent - (colnr_T)last_item.size;
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_buffer == buf) {
+ if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) {
+ fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row,
+ (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size);
+ } else {
+ fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
+ }
+ }
}
- fix_cursor((linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
end:
for (size_t i = 0; i < new_len; i++) {
xfree(lines[i]);
}
xfree(lines);
- aucmd_restbuf(&aco);
try_end(err);
early_end:
@@ -800,10 +788,9 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
{
Array rv = ARRAY_DICT_INIT;
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return rv;
- }
+ });
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -820,18 +807,16 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
start_row = normalize_index(buf, start_row, false, &oob);
end_row = normalize_index(buf, end_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!oob), "%s", "Index out of bounds", {
return rv;
- }
+ });
// nvim_buf_get_lines doesn't care if the start row is greater than the end
// row (it will just return an empty array), but nvim_buf_get_text does in
// order to maintain symmetry with nvim_buf_set_text.
- if (start_row > end_row) {
- api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ VALIDATE((start_row <= end_row), "%s", "'start' is higher than 'end'", {
return rv;
- }
+ });
bool replace_nl = (channel_id != VIML_INTERNAL_CALL);
@@ -907,10 +892,9 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err)
return -1;
}
- if (index < 0 || index > buf->b_ml.ml_line_count) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "%s", "Index out of bounds", {
return 0;
- }
+ });
return ml_find_line_or_offset(buf, (int)index + 1, NULL, true);
}
@@ -1100,7 +1084,7 @@ Boolean nvim_buf_is_loaded(Buffer buffer)
/// - unload: Unloaded only, do not delete. See |:bunload|
void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
FUNC_API_SINCE(7)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -1118,8 +1102,9 @@ void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
} else if (strequal("unload", k.data)) {
unload = api_object_to_bool(v, "unload", false, err);
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return;
+ VALIDATE_S(false, "'opts' key", k.data, {
+ return;
+ });
}
}
@@ -1174,20 +1159,16 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
return res;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data);
// fm is NULL when there's no mark with the given name
- if (fm == NULL) {
- api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'",
- *name.data);
+ VALIDATE_S((fm != NULL), "mark name", name.data, {
return res;
- }
+ });
// mark.lnum is 0 when the mark is not valid in the buffer, or is not set.
if (fm->mark.lnum != 0 && fm->fnum == buf->handle) {
@@ -1224,19 +1205,18 @@ Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col,
return res;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
res = set_mark(buf, name, line, col, err);
return res;
}
-/// Returns a tuple (row,col) representing the position of the named mark. See
-/// |mark-motions|.
+/// Returns a `(row,col)` tuple representing the position of the named mark.
+/// "End of line" column position is returned as |v:maxcol| (big number).
+/// See |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
@@ -1257,21 +1237,18 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return rv;
- }
+ });
fmark_T *fm;
pos_T pos;
char mark = *name.data;
fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark);
- if (fm == NULL) {
- api_set_error(err, kErrorTypeValidation, "Invalid mark name");
+ VALIDATE_S((fm != NULL), "mark name", name.data, {
return rv;
- }
+ });
// (0, 0) uppercase/file mark set in another buffer.
if (fm->fnum != buf->handle) {
pos.lnum = 0;
@@ -1295,15 +1272,15 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
/// Otherwise a temporary scratch window (called the "autocmd window" for
/// historical reasons) will be used.
///
-/// This is useful e.g. to call vimL functions that only work with the current
-/// buffer/window currently, like |termopen()|.
+/// This is useful e.g. to call Vimscript functions that only work with the
+/// current buffer/window currently, like |termopen()|.
///
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param fun Function to call inside the buffer (currently lua callable
+/// @param fun Function to call inside the buffer (currently Lua callable
/// only)
/// @param[out] err Error details, if any
-/// @return Return value of function. NB: will deepcopy lua values
-/// currently, use upvalues to send lua references in and out.
+/// @return Return value of function. NB: will deepcopy Lua values
+/// currently, use upvalues to send Lua references in and out.
Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
FUNC_API_SINCE(7)
FUNC_API_LUA_ONLY
@@ -1362,42 +1339,92 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// Check if deleting lines made the cursor position invalid.
// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
-static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
+static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra)
{
- if (curwin->w_cursor.lnum >= lo) {
+ if (win->w_cursor.lnum >= lo) {
// Adjust cursor position if it's in/after the changed lines.
- if (curwin->w_cursor.lnum >= hi) {
- curwin->w_cursor.lnum += extra;
- check_cursor_col();
+ if (win->w_cursor.lnum >= hi) {
+ win->w_cursor.lnum += extra;
} else if (extra < 0) {
- check_cursor();
- } else {
- check_cursor_col();
+ check_cursor_lnum(win);
}
- changed_cline_bef_curs();
+ check_cursor_col_win(win);
+ changed_cline_bef_curs(win);
}
- invalidate_botline();
+ invalidate_botline(win);
}
-// Normalizes 0-based indexes to buffer line numbers
-static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
+/// Fix cursor position after replacing text
+/// between (start_row, start_col) and (end_row, end_col).
+///
+/// win->w_cursor.lnum is assumed to be >= start_row and <= end_row.
+static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, linenr_T end_row,
+ colnr_T end_col, linenr_T new_rows, colnr_T new_cols_at_end_row)
{
- assert(buf->b_ml.ml_line_count > 0);
- int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
- // Fix if < 0
- index = index < 0 ? max_index + index + 1 : index;
-
- // Check for oob
- if (index > max_index) {
- *oob = true;
- index = max_index;
- } else if (index < 0) {
- *oob = true;
- index = 0;
- }
- // Convert the index to a vim line number
- index++;
- return index;
+ colnr_T mode_col_adj = win == curwin && (State & MODE_INSERT) ? 0 : 1;
+
+ colnr_T end_row_change_start = new_rows == 1 ? start_col : 0;
+ colnr_T end_row_change_end = end_row_change_start + new_cols_at_end_row;
+
+ // check if cursor is after replaced range or not
+ if (win->w_cursor.lnum == end_row && win->w_cursor.col + mode_col_adj > end_col) {
+ // if cursor is after replaced range, it's shifted
+ // to keep it's position the same, relative to end_col
+
+ linenr_T old_rows = end_row - start_row + 1;
+ win->w_cursor.lnum += new_rows - old_rows;
+ win->w_cursor.col += end_row_change_end - end_col;
+ } else {
+ // if cursor is inside replaced range
+ // and the new range got smaller,
+ // it's shifted to keep it inside the new range
+ //
+ // if cursor is before range or range did not
+ // got smaller, position is not changed
+
+ colnr_T old_coladd = win->w_cursor.coladd;
+
+ // it's easier to work with a single value here.
+ // col and coladd are fixed by a later call
+ // to check_cursor_col_win when necessary
+ win->w_cursor.col += win->w_cursor.coladd;
+ win->w_cursor.coladd = 0;
+
+ linenr_T new_end_row = start_row + new_rows - 1;
+
+ // make sure cursor row is in the new row range
+ if (win->w_cursor.lnum > new_end_row) {
+ win->w_cursor.lnum = new_end_row;
+
+ // don't simply move cursor up, but to the end
+ // of new_end_row, if it's not at or after
+ // it already (in case virtualedit is active)
+ // column might be additionally adjusted below
+ // to keep it inside col range if needed
+ colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row));
+ if (win->w_cursor.col < len) {
+ win->w_cursor.col = len;
+ }
+ }
+
+ // if cursor is at the last row and
+ // it wasn't after eol before, move it exactly
+ // to end_row_change_end
+ if (win->w_cursor.lnum == new_end_row
+ && win->w_cursor.col > end_row_change_end && old_coladd == 0) {
+ win->w_cursor.col = end_row_change_end;
+
+ // make sure cursor is inside range, not after it,
+ // except when doing so would move it before new range
+ if (win->w_cursor.col - mode_col_adj >= end_row_change_start) {
+ win->w_cursor.col -= mode_col_adj;
+ }
+ }
+ }
+
+ check_cursor_col_win(win);
+ changed_cline_bef_curs(win);
+ invalidate_botline(win);
}
/// Initialise a string array either:
@@ -1481,7 +1508,7 @@ bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool
return false;
}
- char *bufstr = ml_get_buf(buf, lnum, false);
+ char *bufstr = ml_get_buf(buf, lnum);
push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl);
}
diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h
index 0814da63cd..f3971c1d30 100644
--- a/src/nvim/api/buffer.h
+++ b/src/nvim/api/buffer.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_API_BUFFER_H
-#define NVIM_API_BUFFER_H
+#pragma once
-#include <lauxlib.h>
+#include <lua.h> // IWYU pragma: keep
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.h.generated.h"
#endif
-#endif // NVIM_API_BUFFER_H
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index abd265f2cf..2a57ce9a19 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -1,36 +1,37 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/command.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
-#include "nvim/decoration.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/ops.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -99,16 +100,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
{
Dictionary result = ARRAY_DICT_INIT;
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return result;
- }
+ });
// Parse command line
exarg_T ea;
CmdParseInfo cmdinfo;
char *cmdline = string_to_cstr(str);
- char *errormsg = NULL;
+ const char *errormsg = NULL;
if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
if (errormsg != NULL) {
@@ -127,7 +127,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
// otherwise split arguments by whitespace.
if (ea.argt & EX_NOSPC) {
if (*ea.arg != NUL) {
- ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
+ ADD(args, STRING_OBJ(cstrn_to_string(ea.arg, length)));
}
} else {
size_t end = 0;
@@ -153,9 +153,9 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
}
if (cmd != NULL) {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+ PUT(result, "cmd", CSTR_TO_OBJ(cmd->uc_name));
} else {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+ PUT(result, "cmd", CSTR_TO_OBJ(get_command_name(NULL, ea.cmdidx)));
}
if (ea.argt & EX_RANGE) {
@@ -237,14 +237,14 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
break;
}
PUT(result, "addr", CSTR_TO_OBJ(addr));
- PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+ PUT(result, "nextcmd", CSTR_TO_OBJ(ea.nextcmd));
Dictionary mods = ARRAY_DICT_INIT;
Dictionary filter = ARRAY_DICT_INIT;
PUT(filter, "pattern", cmdinfo.cmdmod.cmod_filter_pat
? CSTR_TO_OBJ(cmdinfo.cmdmod.cmod_filter_pat)
- : STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ : STATIC_CSTR_TO_OBJ(""));
PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
PUT(mods, "filter", DICTIONARY_OBJ(filter));
@@ -305,9 +305,9 @@ end:
/// make their usage simpler with |vim.cmd()|. For example, instead of
/// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @see |nvim_exec()|
+/// @see |nvim_exec2()|
/// @see |nvim_command()|
///
/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
@@ -340,32 +340,22 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
} \
} while (0)
-#define OBJ_TO_CMOD_FLAG(flag, value, default, varname) \
+#define VALIDATE_MOD(cond, mod_, name_) \
do { \
- if (api_object_to_bool(value, varname, default, err)) { \
- cmdinfo.cmdmod.cmod_flags |= (flag); \
- } \
- if (ERROR_SET(err)) { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, "Command cannot accept %s: %s", (mod_), (name_)); \
goto end; \
} \
} while (0)
-#define VALIDATION_ERROR(...) \
- do { \
- api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
- goto end; \
- } while (0)
-
- bool output;
- OBJ_TO_BOOL(output, opts->output, false, "'output'");
-
- // First, parse the command name and check if it exists and is valid.
- if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
- || cmd->cmd.data.string.data[0] == NUL) {
- VALIDATION_ERROR("'cmd' must be a non-empty String");
- }
+ VALIDATE_R(HAS_KEY(cmd, cmd, cmd), "cmd", {
+ goto end;
+ });
+ VALIDATE_EXP((cmd->cmd.data[0] != NUL), "cmd", "non-empty String", NULL, {
+ goto end;
+ });
- cmdname = string_to_cstr(cmd->cmd.data.string);
+ cmdname = string_to_cstr(cmd->cmd);
ea.cmd = cmdname;
char *p = find_ex_command(&ea, NULL);
@@ -382,12 +372,18 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
}
- if (p == NULL || ea.cmdidx == CMD_SIZE) {
- VALIDATION_ERROR("Command not found: %s", cmdname);
- }
- if (is_cmd_ni(ea.cmdidx)) {
- VALIDATION_ERROR("Command not implemented: %s", cmdname);
- }
+ VALIDATE((p != NULL && ea.cmdidx != CMD_SIZE), "Command not found: %s", cmdname, {
+ goto end;
+ });
+ VALIDATE(!is_cmd_ni(ea.cmdidx), "Command not implemented: %s", cmdname, {
+ goto end;
+ });
+ const char *fullname = IS_USER_CMDIDX(ea.cmdidx)
+ ? get_user_command_name(ea.useridx, ea.cmdidx)
+ : get_command_name(NULL, ea.cmdidx);
+ VALIDATE(strncmp(fullname, cmdname, strlen(cmdname)) == 0, "Invalid command: \"%s\"", cmdname, {
+ goto end;
+ });
// Get the command flags so that we can know what type of arguments the command uses.
// Not required for a user command since `find_ex_command` already deals with it in that case.
@@ -396,15 +392,11 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
// Parse command arguments since it's needed to get the command address type.
- if (HAS_KEY(cmd->args)) {
- if (cmd->args.type != kObjectTypeArray) {
- VALIDATION_ERROR("'args' must be an Array");
- }
-
+ if (HAS_KEY(cmd, cmd, args)) {
// Process all arguments. Convert non-String arguments to String and check if String arguments
// have non-whitespace characters.
- for (size_t i = 0; i < cmd->args.data.array.size; i++) {
- Object elem = cmd->args.data.array.items[i];
+ for (size_t i = 0; i < cmd->args.size; i++) {
+ Object elem = cmd->args.items[i];
char *data_str;
switch (elem.type) {
@@ -421,18 +413,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer);
break;
case kObjectTypeString:
- if (string_iswhite(elem.data.string)) {
- VALIDATION_ERROR("String command argument must have at least one non-whitespace "
- "character");
- }
- data_str = xstrndup(elem.data.string.data, elem.data.string.size);
+ VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, {
+ goto end;
+ });
+ data_str = string_to_cstr(elem.data.string);
break;
default:
- VALIDATION_ERROR("Invalid type for command argument");
+ VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), {
+ goto end;
+ });
break;
}
- ADD(args, STRING_OBJ(cstr_as_string(data_str)));
+ ADD(args, CSTR_AS_OBJ(data_str));
}
bool argc_valid;
@@ -456,32 +449,30 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
break;
}
- if (!argc_valid) {
- VALIDATION_ERROR("Incorrect number of arguments supplied");
- }
+ VALIDATE(argc_valid, "%s", "Wrong number of arguments", {
+ goto end;
+ });
}
// Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
// since it only ever checks the first argument.
set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL);
- if (HAS_KEY(cmd->range)) {
- if (!(ea.argt & EX_RANGE)) {
- VALIDATION_ERROR("Command cannot accept a range");
- } else if (cmd->range.type != kObjectTypeArray) {
- VALIDATION_ERROR("'range' must be an Array");
- } else if (cmd->range.data.array.size > 2) {
- VALIDATION_ERROR("'range' cannot contain more than two elements");
- }
+ if (HAS_KEY(cmd, cmd, range)) {
+ VALIDATE_MOD((ea.argt & EX_RANGE), "range", cmd->cmd.data);
+ VALIDATE_EXP((cmd->range.size <= 2), "range", "<=2 elements", NULL, {
+ goto end;
+ });
- Array range = cmd->range.data.array;
+ Array range = cmd->range;
ea.addr_count = (int)range.size;
for (size_t i = 0; i < range.size; i++) {
Object elem = range.items[i];
- if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
- VALIDATION_ERROR("'range' element must be a non-negative Integer");
- }
+ VALIDATE_EXP((elem.type == kObjectTypeInteger && elem.data.integer >= 0),
+ "range element", "non-negative Integer", NULL, {
+ goto end;
+ });
}
if (range.size > 0) {
@@ -489,9 +480,9 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
}
- if (invalid_range(&ea) != NULL) {
- VALIDATION_ERROR("Invalid range provided");
- }
+ VALIDATE_S((invalid_range(&ea) == NULL), "range", "", {
+ goto end;
+ });
}
if (ea.addr_count == 0) {
if (ea.argt & EX_DFLALL) {
@@ -506,48 +497,42 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
}
- if (HAS_KEY(cmd->count)) {
- if (!(ea.argt & EX_COUNT)) {
- VALIDATION_ERROR("Command cannot accept a count");
- } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
- VALIDATION_ERROR("'count' must be a non-negative Integer");
- }
- set_cmd_count(&ea, cmd->count.data.integer, true);
+ if (HAS_KEY(cmd, cmd, count)) {
+ VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data);
+ VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, {
+ goto end;
+ });
+ set_cmd_count(&ea, (linenr_T)cmd->count, true);
}
- if (HAS_KEY(cmd->reg)) {
- if (!(ea.argt & EX_REGSTR)) {
- VALIDATION_ERROR("Command cannot accept a register");
- } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
- VALIDATION_ERROR("'reg' must be a single character");
- }
- char regname = cmd->reg.data.string.data[0];
- if (regname == '=') {
- VALIDATION_ERROR("Cannot use register \"=");
- } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
- VALIDATION_ERROR("Invalid register: \"%c", regname);
- }
+ if (HAS_KEY(cmd, cmd, reg)) {
+ VALIDATE_MOD((ea.argt & EX_REGSTR), "register", cmd->cmd.data);
+ VALIDATE_EXP((cmd->reg.size == 1),
+ "reg", "single character", cmd->reg.data, {
+ goto end;
+ });
+ char regname = cmd->reg.data[0];
+ VALIDATE((regname != '='), "%s", "Cannot use register \"=", {
+ goto end;
+ });
+ VALIDATE(valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx)),
+ "Invalid register: \"%c", regname, {
+ goto end;
+ });
ea.regname = (uint8_t)regname;
}
- OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
- if (ea.forceit && !(ea.argt & EX_BANG)) {
- VALIDATION_ERROR("Command cannot accept a bang");
- }
-
- if (HAS_KEY(cmd->magic)) {
- if (cmd->magic.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'magic' must be a Dictionary");
- }
+ ea.forceit = cmd->bang;
+ VALIDATE_MOD((!ea.forceit || (ea.argt & EX_BANG)), "bang", cmd->cmd.data);
- Dict(cmd_magic) magic = { 0 };
- if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
- cmd->magic.data.dictionary, err)) {
+ if (HAS_KEY(cmd, cmd, magic)) {
+ Dict(cmd_magic) magic[1] = { 0 };
+ if (!api_dict_to_keydict(magic, KeyDict_cmd_magic_get_field, cmd->magic, err)) {
goto end;
}
- OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'");
- OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'");
+ cmdinfo.magic.file = HAS_KEY(magic, cmd_magic, file) ? magic->file : (ea.argt & EX_XFILE);
+ cmdinfo.magic.bar = HAS_KEY(magic, cmd_magic, bar) ? magic->bar : (ea.argt & EX_TRLBAR);
if (cmdinfo.magic.file) {
ea.argt |= EX_XFILE;
} else {
@@ -558,107 +543,90 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
}
- if (HAS_KEY(cmd->mods)) {
- if (cmd->mods.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods' must be a Dictionary");
- }
-
- Dict(cmd_mods) mods = { 0 };
- if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
+ if (HAS_KEY(cmd, cmd, mods)) {
+ Dict(cmd_mods) mods[1] = { 0 };
+ if (!api_dict_to_keydict(mods, KeyDict_cmd_mods_get_field, cmd->mods, err)) {
goto end;
}
- if (HAS_KEY(mods.filter)) {
- if (mods.filter.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods.filter' must be a Dictionary");
- }
-
- Dict(cmd_mods_filter) filter = { 0 };
+ if (HAS_KEY(mods, cmd_mods, filter)) {
+ Dict(cmd_mods_filter) filter[1] = { 0 };
if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field,
- mods.filter.data.dictionary, err)) {
+ mods->filter, err)) {
goto end;
}
- if (HAS_KEY(filter.pattern)) {
- if (filter.pattern.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.filter.pattern' must be a String");
- }
-
- OBJ_TO_BOOL(cmdinfo.cmdmod.cmod_filter_force, filter.force, false, "'mods.filter.force'");
+ if (HAS_KEY(filter, cmd_mods_filter, pattern)) {
+ cmdinfo.cmdmod.cmod_filter_force = filter->force;
// "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter
// is inverted.
- if (*filter.pattern.data.string.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
- cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter.pattern.data.string);
+ if (*filter->pattern.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
+ cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter->pattern);
cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat,
RE_MAGIC);
}
}
}
- if (HAS_KEY(mods.tab)) {
- if (mods.tab.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.tab' must be an Integer");
- } else if ((int)mods.tab.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, tab)) {
+ if ((int)mods->tab >= 0) {
// Silently ignore negative integers to allow mods.tab to be set to -1.
- cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1;
+ cmdinfo.cmdmod.cmod_tab = (int)mods->tab + 1;
}
}
- if (HAS_KEY(mods.verbose)) {
- if (mods.verbose.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.verbose' must be an Integer");
- } else if ((int)mods.verbose.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, verbose)) {
+ if ((int)mods->verbose >= 0) {
// Silently ignore negative integers to allow mods.verbose to be set to -1.
- cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1;
+ cmdinfo.cmdmod.cmod_verbose = (int)mods->verbose + 1;
}
}
- bool vertical;
- OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
- cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->vertical ? WSP_VERT : 0);
- bool horizontal;
- OBJ_TO_BOOL(horizontal, mods.horizontal, false, "'mods.horizontal'");
- cmdinfo.cmdmod.cmod_split |= (horizontal ? WSP_HOR : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->horizontal ? WSP_HOR : 0);
- if (HAS_KEY(mods.split)) {
- if (mods.split.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.split' must be a String");
- }
-
- if (*mods.split.data.string.data == NUL) {
+ if (HAS_KEY(mods, cmd_mods, split)) {
+ if (*mods->split.data == NUL) {
// Empty string, do nothing.
- } else if (strcmp(mods.split.data.string.data, "aboveleft") == 0
- || strcmp(mods.split.data.string.data, "leftabove") == 0) {
+ } else if (strcmp(mods->split.data, "aboveleft") == 0
+ || strcmp(mods->split.data, "leftabove") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_ABOVE;
- } else if (strcmp(mods.split.data.string.data, "belowright") == 0
- || strcmp(mods.split.data.string.data, "rightbelow") == 0) {
+ } else if (strcmp(mods->split.data, "belowright") == 0
+ || strcmp(mods->split.data, "rightbelow") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BELOW;
- } else if (strcmp(mods.split.data.string.data, "topleft") == 0) {
+ } else if (strcmp(mods->split.data, "topleft") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_TOP;
- } else if (strcmp(mods.split.data.string.data, "botright") == 0) {
+ } else if (strcmp(mods->split.data, "botright") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BOT;
} else {
- VALIDATION_ERROR("Invalid value for 'mods.split'");
+ VALIDATE_S(false, "mods.split", "", {
+ goto end;
+ });
}
}
- OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods.silent, false, "'mods.silent'");
- OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'");
- OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods.unsilent, false, "'mods.unsilent'");
- OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'");
- OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'");
- OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods.browse, false, "'mods.browse'");
- OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods.confirm, false, "'mods.confirm'");
- OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods.hide, false, "'mods.hide'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods.keepalt, false, "'mods.keepalt'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods.keepjumps, false, "'mods.keepjumps'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods.keepmarks, false, "'mods.keepmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods.keeppatterns, false, "'mods.keeppatterns'");
- OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'");
+#define OBJ_TO_CMOD_FLAG(flag, value) \
+ if (value) { \
+ cmdinfo.cmdmod.cmod_flags |= (flag); \
+ }
+
+ OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods->silent);
+ OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods->emsg_silent);
+ OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods->unsilent);
+ OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods->sandbox);
+ OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods->noautocmd);
+ OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods->browse);
+ OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods->confirm);
+ OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods->hide);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods->keepalt);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods->keepjumps);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods->keepmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods->keeppatterns);
+ OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods->lockmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods->noswapfile);
if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) {
// CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't
@@ -666,9 +634,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT;
}
- if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) {
- VALIDATION_ERROR("Command cannot be run in sandbox");
- }
+ VALIDATE(!((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)),
+ "%s", "Command cannot be run in sandbox", {
+ goto end;
+ });
}
// Finally, build the command line string that will be stored inside ea.cmdlinep.
@@ -681,14 +650,13 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
garray_T * const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
- if (output) {
+ if (opts->output) {
ga_init(&capture_local, 1, 80);
capture_ga = &capture_local;
}
- TRY_WRAP({
- try_start();
- if (output) {
+ TRY_WRAP(err, {
+ if (opts->output) {
msg_silent++;
msg_col = 0; // prevent leading spaces
}
@@ -697,21 +665,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
execute_cmd(&ea, &cmdinfo, false);
});
- if (output) {
+ if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
// Put msg_col back where it was, since nothing should have been written.
msg_col = save_msg_col;
}
-
- try_end(err);
});
if (ERROR_SET(err)) {
goto clear_ga;
}
- if (output && capture_local.ga_len > 1) {
+ if (opts->output && capture_local.ga_len > 1) {
retv = (String){
.data = capture_local.ga_data,
.size = (size_t)capture_local.ga_len,
@@ -725,7 +691,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
goto end;
}
clear_ga:
- if (output) {
+ if (opts->output) {
ga_clear(&capture_local);
}
end:
@@ -739,7 +705,7 @@ end:
#undef OBJ_TO_BOOL
#undef OBJ_TO_CMOD_FLAG
-#undef VALIDATION_ERROR
+#undef VALIDATE_MOD
}
/// Check if a string contains only whitespace characters.
@@ -870,8 +836,8 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
offset += eap->arglens[i];
}
// If there isn't an argument, make eap->arg point to end of cmdline.
- eap->arg = argc > 0 ? eap->args[0] :
- cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
+ eap->arg = argc > 0 ? eap->args[0]
+ : cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
// Finally, make cmdlinep point to the cmdline string.
*cmdlinep = cmdline.items;
@@ -888,18 +854,17 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
}
}
-/// Create a new user command |user-commands|
+/// Creates a global |user-commands| command.
///
-/// {name} is the name of the new command. The name must begin with an uppercase letter.
-///
-/// {command} is the replacement text or Lua function to execute.
+/// For Lua usage see |lua-guide-commands-create|.
///
/// Example:
-/// <pre>vim
-/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
-/// :SayHello
-/// Hello world!
-/// </pre>
+///
+/// ```vim
+/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true})
+/// :SayHello
+/// Hello world!
+/// ```
///
/// @param name Name of the new user command. Must begin with an uppercase letter.
/// @param command Replacement command to execute when this user command is executed. When called
@@ -909,6 +874,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - args: (string) The args passed to the command, if any |<args>|
/// - fargs: (table) The args split by unescaped whitespace (when more than one
/// argument is allowed), if any |<f-args>|
+/// - nargs: (string) Number of arguments |:command-nargs|
/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
/// - line1: (number) The starting line of the command range |<line1>|
/// - line2: (number) The final line of the command range |<line2>|
@@ -918,20 +884,22 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - mods: (string) Command modifiers, if any |<mods>|
/// - smods: (table) Command modifiers in a structured format. Has the same
/// structure as the "mods" key of |nvim_parse_cmd()|.
-/// @param opts Optional command attributes. See |command-attributes| for more details. To use
-/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
-/// "true". In addition to the string options listed in |:command-complete|, the
-/// "complete" key also accepts a Lua function which works like the "customlist"
-/// completion mode |:command-completion-customlist|. Additional parameters:
-/// - desc: (string) Used for listing the command when a Lua function is used for
-/// {command}.
-/// - force: (boolean, default true) Override any previous definition.
-/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
+/// @param opts Optional |command-attributes|.
+/// - Set boolean attributes such as |:command-bang| or |:command-bar| to true (but
+/// not |:command-buffer|, use |nvim_buf_create_user_command()| instead).
+/// - "complete" |:command-complete| also accepts a Lua function which works like
+/// |:command-completion-customlist|.
+/// - Other parameters:
+/// - desc: (string) Used for listing the command when a Lua function is used for
+/// {command}.
+/// - force: (boolean, default true) Override any previous definition.
+/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
/// @param[out] err Error details, if any.
-void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
+void nvim_create_user_command(uint64_t channel_id, String name, Object command,
+ Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
- create_user_command(name, command, opts, 0, err);
+ create_user_command(channel_id, name, command, opts, 0, err);
}
/// Delete a user-defined command.
@@ -944,12 +912,12 @@ void nvim_del_user_command(String name, Error *err)
nvim_buf_del_user_command(-1, name, err);
}
-/// Create a new user command |user-commands| in the given buffer.
+/// Creates a buffer-local command |user-commands|.
///
/// @param buffer Buffer handle, or 0 for current buffer.
/// @param[out] err Error details, if any.
/// @see nvim_create_user_command
-void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
+void nvim_buf_create_user_command(uint64_t channel_id, Buffer buffer, String name, Object command,
Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
@@ -960,7 +928,7 @@ void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
buf_T *save_curbuf = curbuf;
curbuf = target_buf;
- create_user_command(name, command, opts, UC_BUFFER, err);
+ create_user_command(channel_id, name, command, opts, UC_BUFFER, err);
curbuf = save_curbuf;
}
@@ -998,36 +966,33 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
}
}
- api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
+ api_set_error(err, kErrorTypeException, "Invalid command (not found): %s", name.data);
}
-void create_user_command(String name, Object command, Dict(user_command) *opts, int flags,
- Error *err)
+void create_user_command(uint64_t channel_id, String name, Object command, Dict(user_command) *opts,
+ int flags, Error *err)
{
uint32_t argt = 0;
- long def = -1;
+ int64_t def = -1;
cmd_addr_T addr_type_arg = ADDR_NONE;
- int compl = EXPAND_NOTHING;
+ int context = EXPAND_NOTHING;
char *compl_arg = NULL;
const char *rep = NULL;
LuaRef luaref = LUA_NOREF;
LuaRef compl_luaref = LUA_NOREF;
LuaRef preview_luaref = LUA_NOREF;
- if (!uc_validate_name(name.data)) {
- api_set_error(err, kErrorTypeValidation, "Invalid command name");
+ VALIDATE_S(uc_validate_name(name.data), "command name", name.data, {
goto err;
- }
-
- if (mb_islower(name.data[0])) {
- api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ });
+ VALIDATE_S(!mb_islower(name.data[0]), "command name (must start with uppercase)",
+ name.data, {
goto err;
- }
-
- if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
+ });
+ VALIDATE((!HAS_KEY(opts, user_command, range) || !HAS_KEY(opts, user_command, count)), "%s",
+ "Cannot use both 'range' and 'count'", {
goto err;
- }
+ });
if (opts->nargs.type == kObjectTypeInteger) {
switch (opts->nargs.data.integer) {
@@ -1038,14 +1003,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_INT(false, "nargs", (int64_t)opts->nargs.data.integer, {
+ goto err;
+ });
}
} else if (opts->nargs.type == kObjectTypeString) {
- if (opts->nargs.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ VALIDATE_S((opts->nargs.data.string.size <= 1), "nargs", opts->nargs.data.string.data, {
goto err;
- }
+ });
switch (opts->nargs.data.string.data[0]) {
case '*':
@@ -1058,18 +1023,20 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_S(false, "nargs", opts->nargs.data.string.data, {
+ goto err;
+ });
}
- } else if (HAS_KEY(opts->nargs)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, nargs)) {
+ VALIDATE_S(false, "nargs", "", {
+ goto err;
+ });
}
- if (HAS_KEY(opts->complete) && !argt) {
- api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
+ VALIDATE((!HAS_KEY(opts, user_command, complete) || argt),
+ "%s", "'complete' used without 'nargs'", {
goto err;
- }
+ });
if (opts->range.type == kObjectTypeBoolean) {
if (opts->range.data.boolean) {
@@ -1077,20 +1044,20 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
addr_type_arg = ADDR_LINES;
}
} else if (opts->range.type == kObjectTypeString) {
- if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
- argt |= EX_RANGE | EX_DFLALL;
- addr_type_arg = ADDR_LINES;
- } else {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ VALIDATE_S((opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1),
+ "range", "", {
goto err;
- }
+ });
+ argt |= EX_RANGE | EX_DFLALL;
+ addr_type_arg = ADDR_LINES;
} else if (opts->range.type == kObjectTypeInteger) {
argt |= EX_RANGE | EX_ZEROR;
def = opts->range.data.integer;
addr_type_arg = ADDR_LINES;
- } else if (HAS_KEY(opts->range)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, range)) {
+ VALIDATE_S(false, "range", "", {
+ goto err;
+ });
}
if (opts->count.type == kObjectTypeBoolean) {
@@ -1103,76 +1070,72 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
addr_type_arg = ADDR_OTHER;
def = opts->count.data.integer;
- } else if (HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, count)) {
+ VALIDATE_S(false, "count", "", {
+ goto err;
+ });
}
- if (opts->addr.type == kObjectTypeString) {
- if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size,
- &addr_type_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ if (HAS_KEY(opts, user_command, addr)) {
+ VALIDATE_T("addr", kObjectTypeString, opts->addr.type, {
goto err;
- }
+ });
+
+ VALIDATE_S(OK == parse_addr_type_arg(opts->addr.data.string.data,
+ (int)opts->addr.data.string.size, &addr_type_arg), "addr",
+ opts->addr.data.string.data, {
+ goto err;
+ });
if (addr_type_arg != ADDR_LINES) {
argt |= EX_ZEROR;
}
- } else if (HAS_KEY(opts->addr)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
- goto err;
}
- if (api_object_to_bool(opts->bang, "bang", false, err)) {
+ if (opts->bang) {
argt |= EX_BANG;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->bar, "bar", false, err)) {
+ if (opts->bar) {
argt |= EX_TRLBAR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->register_, "register", false, err)) {
+ if (opts->register_) {
argt |= EX_REGSTR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ if (opts->keepscript) {
argt |= EX_KEEPSCRIPT;
- } else if (ERROR_SET(err)) {
- goto err;
}
- bool force = api_object_to_bool(opts->force, "force", true, err);
+ bool force = GET_BOOL_OR_TRUE(opts, user_command, force);
if (ERROR_SET(err)) {
goto err;
}
if (opts->complete.type == kObjectTypeLuaRef) {
- compl = EXPAND_USER_LUA;
+ context = EXPAND_USER_LUA;
compl_luaref = api_new_luaref(opts->complete.data.luaref);
} else if (opts->complete.type == kObjectTypeString) {
- if (parse_compl_arg(opts->complete.data.string.data,
- (int)opts->complete.data.string.size, &compl, &argt,
- &compl_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data,
+ (int)opts->complete.data.string.size, &context, &argt,
+ &compl_arg),
+ "complete", opts->complete.data.string.data, {
goto err;
- }
- } else if (HAS_KEY(opts->complete)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
+ });
+ } else if (HAS_KEY(opts, user_command, complete)) {
+ VALIDATE_EXP(false, "complete", "Function or String", NULL, {
+ goto err;
+ });
}
- if (opts->preview.type == kObjectTypeLuaRef) {
+ if (HAS_KEY(opts, user_command, preview)) {
+ VALIDATE_T("preview", kObjectTypeLuaRef, opts->preview.type, {
+ goto err;
+ });
+
argt |= EX_PREVIEW;
preview_luaref = api_new_luaref(opts->preview.data.luaref);
- } else if (HAS_KEY(opts->preview)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
- goto err;
}
switch (command.type) {
@@ -1188,15 +1151,18 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
rep = command.data.string.data;
break;
default:
- api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
- goto err;
+ VALIDATE_EXP(false, "command", "Function or String", NULL, {
+ goto err;
+ });
}
- if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
- preview_luaref, addr_type_arg, luaref, force) != OK) {
- api_set_error(err, kErrorTypeException, "Failed to create user command");
- // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
- }
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg,
+ compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) {
+ api_set_error(err, kErrorTypeException, "Failed to create user command");
+ // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
+ }
+ });
return;
@@ -1209,6 +1175,8 @@ err:
///
/// Currently only |user-commands| are supported, not builtin Ex commands.
///
+/// @see |nvim_get_all_options_info()|
+///
/// @param opts Optional parameters. Currently only supports
/// {"builtin":false}
/// @param[out] err Error details, if any.
@@ -1231,13 +1199,12 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
FUNC_API_SINCE(4)
{
bool global = (buffer == -1);
- bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err);
if (ERROR_SET(err)) {
return (Dictionary)ARRAY_DICT_INIT;
}
if (global) {
- if (builtin) {
+ if (opts->builtin) {
api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
return (Dictionary)ARRAY_DICT_INIT;
}
@@ -1245,7 +1212,7 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
}
buf_T *buf = find_buffer_by_handle(buffer, err);
- if (builtin || !buf) {
+ if (opts->builtin || !buf) {
return (Dictionary)ARRAY_DICT_INIT;
}
return commands_array(buf);
diff --git a/src/nvim/api/command.h b/src/nvim/api/command.h
index b1c9230551..1cccbfb4c7 100644
--- a/src/nvim/api/command.h
+++ b/src/nvim/api/command.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_API_COMMAND_H
-#define NVIM_API_COMMAND_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/decoration.h"
-#include "nvim/ex_cmds.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/command.h.generated.h"
#endif
-#endif // NVIM_API_COMMAND_H
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 332e2b5fc3..2ec11236d7 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -1,36 +1,50 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdint.h>
-#include <stdlib.h>
+#include <string.h>
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/options.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/vimscript.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/highlight.h"
+#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/option.h"
+#include "nvim/pos_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/deprecated.c.generated.h"
#endif
+/// @deprecated Use nvim_exec2() instead.
+/// @see nvim_exec2
+String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ Dict(exec_opts) opts = { .output = output };
+ return exec_impl(channel_id, src, &opts, err);
+}
+
/// @deprecated
-/// @see nvim_exec
+/// @see nvim_exec2
String nvim_command_output(uint64_t channel_id, String command, Error *err)
FUNC_API_SINCE(1)
FUNC_API_DEPRECATED_SINCE(7)
{
- return nvim_exec(channel_id, command, true, err);
+ Dict(exec_opts) opts = { .output = true };
+ return exec_impl(channel_id, command, &opts, err);
}
/// @deprecated Use nvim_exec_lua() instead.
@@ -140,25 +154,71 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return 0;
}
- Decoration *existing = decor_find_virttext(buf, (int)line, ns_id);
+ DecorVirtText *existing = decor_find_virttext(buf, (int)line, ns_id);
if (existing) {
- clear_virttext(&existing->virt_text);
- existing->virt_text = virt_text;
- existing->virt_text_width = width;
+ clear_virttext(&existing->data.virt_text);
+ existing->data.virt_text = virt_text;
+ existing->width = width;
return src_id;
}
- Decoration decor = DECORATION_INIT;
- decor.virt_text = virt_text;
- decor.virt_text_width = width;
- decor.priority = 0;
+ DecorVirtText *vt = xmalloc(sizeof *vt);
+ *vt = (DecorVirtText)DECOR_VIRT_TEXT_INIT;
+ vt->data.virt_text = virt_text;
+ vt->width = width;
+ vt->priority = 0;
- extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true,
- false, kExtmarkNoUndo);
+ DecorInline decor = { .ext = true, .data.ext.vt = vt, .data.ext.sh_idx = DECOR_ID_INVALID };
+
+ extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, 0, true,
+ false, false, false, NULL);
return src_id;
}
+/// Gets a highlight definition by id. |hlID()|
+///
+/// @deprecated use |nvim_get_hl()| instead
+///
+/// @param hl_id Highlight id as returned by |hlID()|
+/// @param rgb Export RGB colors
+/// @param[out] err Error details, if any
+/// @return Highlight definition map
+/// @see nvim_get_hl_by_name
+Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(9)
+{
+ Dictionary dic = ARRAY_DICT_INIT;
+ VALIDATE_INT((syn_get_final_id((int)hl_id) != 0), "highlight id", hl_id, {
+ return dic;
+ });
+ int attrcode = syn_id2attr((int)hl_id);
+ return hl_get_attr_by_id(attrcode, rgb, arena, err);
+}
+
+/// Gets a highlight definition by name.
+///
+/// @deprecated use |nvim_get_hl()| instead
+///
+/// @param name Highlight group name
+/// @param rgb Export RGB colors
+/// @param[out] err Error details, if any
+/// @return Highlight definition map
+/// @see nvim_get_hl_by_id
+Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(9)
+{
+ Dictionary result = ARRAY_DICT_INIT;
+ int id = syn_name2id(name.data);
+
+ VALIDATE_S((id != 0), "highlight name", name.data, {
+ return result;
+ });
+ return nvim_get_hl_by_id(id, rgb, arena, err);
+}
+
/// Inserts a sequence of lines to a buffer at a certain index
///
/// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines)
@@ -449,3 +509,195 @@ static int64_t convert_index(int64_t index)
{
return index < 0 ? index - 1 : index;
}
+
+/// Gets the option information for one option
+///
+/// @deprecated Use @ref nvim_get_option_info2 instead.
+///
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option Information
+Dictionary nvim_get_option_info(String name, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, err);
+}
+
+/// Sets the global value of an option.
+///
+/// @deprecated
+/// @param channel_id
+/// @param name Option name
+/// @param value New option value
+/// @param[out] err Error details, if any
+void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ set_option_to(channel_id, NULL, kOptReqGlobal, name, value, err);
+}
+
+/// Gets the global value of an option.
+///
+/// @deprecated
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value (global)
+Object nvim_get_option(String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ return get_option_from(NULL, kOptReqGlobal, name, err);
+}
+
+/// Gets a buffer option value
+///
+/// @deprecated
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(buf, kOptReqBuf, name, err);
+}
+
+/// Sets a buffer option value. Passing `nil` as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @deprecated
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return;
+ }
+
+ set_option_to(channel_id, buf, kOptReqBuf, name, value, err);
+}
+
+/// Gets a window option value
+///
+/// @deprecated
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(win, kOptReqWin, name, err);
+}
+
+/// Sets a window option value. Passing `nil` as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @deprecated
+/// @param channel_id
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return;
+ }
+
+ set_option_to(channel_id, win, kOptReqWin, name, value, err);
+}
+
+/// Gets the value of a global or local (buffer, window) option.
+///
+/// @param[in] from Pointer to buffer or window for local option value.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param name The option name.
+/// @param[out] err Details of an error that may have occurred.
+///
+/// @return the option value.
+static Object get_option_from(void *from, OptReqScope req_scope, String name, Error *err)
+{
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
+ return (Object)OBJECT_INIT;
+ });
+
+ OptVal value = get_option_value_strict(name.data, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return (Object)OBJECT_INIT;
+ }
+
+ VALIDATE_S(value.type != kOptValTypeNil, "option name", name.data, {
+ return (Object)OBJECT_INIT;
+ });
+
+ return optval_as_object(value);
+}
+
+/// Sets the value of a global or local (buffer, window) option.
+///
+/// @param[in] to Pointer to buffer or window for local option value.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param name The option name.
+/// @param value New option value.
+/// @param[out] err Details of an error that may have occurred.
+static void set_option_to(uint64_t channel_id, void *to, OptReqScope req_scope, String name,
+ Object value, Error *err)
+{
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
+ return;
+ });
+
+ int flags = get_option_attrs(name.data);
+ VALIDATE_S(flags != 0, "option name", name.data, {
+ return;
+ });
+
+ bool error = false;
+ OptVal optval = object_as_optval(value, &error);
+
+ // Handle invalid option value type.
+ // Don't use `name` in the error message here, because `name` can be any String.
+ // No need to check if value type actually matches the types for the option, as set_option_value()
+ // already handles that.
+ VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), {
+ return;
+ });
+
+ // For global-win-local options -> setlocal
+ // For win-local options -> setglobal and setlocal (opt_flags == 0)
+ const int opt_flags = (req_scope == kOptReqWin && !(flags & SOPT_GLOBAL))
+ ? 0
+ : (req_scope == kOptReqGlobal) ? OPT_GLOBAL : OPT_LOCAL;
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ set_option_value_for(name.data, optval, opt_flags, req_scope, to, err);
+ });
+}
diff --git a/src/nvim/api/deprecated.h b/src/nvim/api/deprecated.h
index 79095167e1..e20d8304e0 100644
--- a/src/nvim/api/deprecated.h
+++ b/src/nvim/api/deprecated.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_API_DEPRECATED_H
-#define NVIM_API_DEPRECATED_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/deprecated.h.generated.h"
#endif
-#endif // NVIM_API_DEPRECATED_H
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index ab3b3485e4..d71498d6ed 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -1,29 +1,31 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
+#include "nvim/marktree.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sign.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.c.generated.h"
@@ -32,12 +34,10 @@
void api_extmark_free_all_mem(void)
{
String name;
- handle_T id;
- map_foreach(&namespace_ids, name, id, {
- (void)id;
+ map_foreach_key(&namespace_ids, name, {
xfree(name.data);
})
- map_destroy(String, handle_T)(&namespace_ids);
+ map_destroy(String, &namespace_ids);
}
/// Creates a new namespace or gets an existing one. \*namespace\*
@@ -54,14 +54,14 @@ void api_extmark_free_all_mem(void)
Integer nvim_create_namespace(String name)
FUNC_API_SINCE(5)
{
- handle_T id = map_get(String, handle_T)(&namespace_ids, name);
+ handle_T id = map_get(String, int)(&namespace_ids, name);
if (id > 0) {
return id;
}
id = next_namespace_id++;
if (name.size > 0) {
String name_alloc = copy_string(name, NULL);
- map_put(String, handle_T)(&namespace_ids, name_alloc, id);
+ map_put(String, int)(&namespace_ids, name_alloc, id);
}
return (Integer)id;
}
@@ -83,7 +83,7 @@ Dictionary nvim_get_namespaces(void)
return retval;
}
-const char *describe_ns(NS ns_id)
+const char *describe_ns(NS ns_id, const char *unknown)
{
String name;
handle_T id;
@@ -92,7 +92,7 @@ const char *describe_ns(NS ns_id)
return name.data;
}
})
- return "(UNKNOWN PLUGIN)";
+ return unknown;
}
// Is the Namespace in use?
@@ -104,92 +104,73 @@ bool ns_initialized(uint32_t ns)
return ns < (uint32_t)next_namespace_id;
}
-static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict)
+Array virt_text_to_array(VirtText vt, bool hl_name)
{
+ Array chunks = ARRAY_DICT_INIT;
+ Array hl_array = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < kv_size(vt); i++) {
+ char *text = kv_A(vt, i).text;
+ int hl_id = kv_A(vt, i).hl_id;
+ if (text == NULL) {
+ if (hl_id > 0) {
+ ADD(hl_array, hl_group_name(hl_id, hl_name));
+ }
+ continue;
+ }
+ Array chunk = ARRAY_DICT_INIT;
+ ADD(chunk, CSTR_TO_OBJ(text));
+ if (hl_array.size > 0) {
+ if (hl_id > 0) {
+ ADD(hl_array, hl_group_name(hl_id, hl_name));
+ }
+ ADD(chunk, ARRAY_OBJ(hl_array));
+ hl_array = (Array)ARRAY_DICT_INIT;
+ } else if (hl_id > 0) {
+ ADD(chunk, hl_group_name(hl_id, hl_name));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+ assert(hl_array.size == 0);
+ return chunks;
+}
+
+static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_name)
+{
+ MTKey start = extmark.start;
Array rv = ARRAY_DICT_INIT;
if (id) {
- ADD(rv, INTEGER_OBJ((Integer)extmark->mark_id));
+ ADD(rv, INTEGER_OBJ((Integer)start.id));
}
- ADD(rv, INTEGER_OBJ(extmark->row));
- ADD(rv, INTEGER_OBJ(extmark->col));
+ ADD(rv, INTEGER_OBJ(start.pos.row));
+ ADD(rv, INTEGER_OBJ(start.pos.col));
if (add_dict) {
Dictionary dict = ARRAY_DICT_INIT;
- PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark->right_gravity));
+ PUT(dict, "ns_id", INTEGER_OBJ((Integer)start.ns));
- if (extmark->end_row >= 0) {
- PUT(dict, "end_row", INTEGER_OBJ(extmark->end_row));
- PUT(dict, "end_col", INTEGER_OBJ(extmark->end_col));
- PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark->end_right_gravity));
- }
+ PUT(dict, "right_gravity", BOOLEAN_OBJ(mt_right(start)));
- const Decoration *decor = &extmark->decor;
- if (decor->hl_id) {
- String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
- PUT(dict, "hl_group", STRING_OBJ(name));
- PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol));
- }
- if (decor->hl_mode) {
- PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode])));
+ if (extmark.end_pos.row >= 0) {
+ PUT(dict, "end_row", INTEGER_OBJ(extmark.end_pos.row));
+ PUT(dict, "end_col", INTEGER_OBJ(extmark.end_pos.col));
+ PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity));
}
- if (kv_size(decor->virt_text)) {
- Array chunks = ARRAY_DICT_INIT;
- for (size_t i = 0; i < decor->virt_text.size; i++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &decor->virt_text.items[i];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
- PUT(dict, "virt_text", ARRAY_OBJ(chunks));
- PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide));
- if (decor->virt_text_pos == kVTWinCol) {
- PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col));
- }
- PUT(dict, "virt_text_pos",
- STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos])));
+ if (mt_no_undo(start)) {
+ PUT(dict, "undo_restore", BOOLEAN_OBJ(false));
}
- if (decor->ui_watched) {
- PUT(dict, "ui_watched", BOOLEAN_OBJ(true));
+ if (mt_invalidate(start)) {
+ PUT(dict, "invalidate", BOOLEAN_OBJ(true));
}
-
- if (kv_size(decor->virt_lines)) {
- Array all_chunks = ARRAY_DICT_INIT;
- bool virt_lines_leftcol = false;
- for (size_t i = 0; i < decor->virt_lines.size; i++) {
- Array chunks = ARRAY_DICT_INIT;
- VirtText *vt = &decor->virt_lines.items[i].line;
- virt_lines_leftcol = decor->virt_lines.items[i].left_col;
- for (size_t j = 0; j < vt->size; j++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &vt->items[j];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
- ADD(all_chunks, ARRAY_OBJ(chunks));
- }
- PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks));
- PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above));
- PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ if (mt_invalid(start)) {
+ PUT(dict, "invalid", BOOLEAN_OBJ(true));
}
- if (decor->hl_id || kv_size(decor->virt_text) || decor->ui_watched) {
- PUT(dict, "priority", INTEGER_OBJ(decor->priority));
- }
+ decor_to_dict_legacy(&dict, mt_decor(start), hl_name);
- if (dict.size) {
- ADD(rv, DICTIONARY_OBJ(dict));
- }
+ ADD(rv, DICTIONARY_OBJ(dict));
}
return rv;
@@ -202,6 +183,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict
/// @param id Extmark id
/// @param opts Optional parameters. Keys:
/// - details: Whether to include the details dict
+/// - hl_name: Whether to include highlight group name instead of id, true if omitted
/// @param[out] err Error details, if any
/// @return 0-indexed (row, col) tuple or empty list () if extmark id was
/// absent
@@ -218,80 +200,92 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
- }
+ });
bool details = false;
+ bool hl_name = true;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ details = api_object_to_bool(*v, "details", false, err);
+ if (ERROR_SET(err)) {
+ return rv;
+ }
+ } else if (strequal("hl_name", k.data)) {
+ hl_name = api_object_to_bool(*v, "hl_name", false, err);
+ if (ERROR_SET(err)) {
return rv;
}
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_S(false, "'opts' key", k.data, {
+ return rv;
+ });
}
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
- if (extmark.row < 0) {
+ MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
+ if (extmark.start.pos.row < 0) {
return rv;
}
- return extmark_to_array(&extmark, false, details);
+ return extmark_to_array(extmark, false, details, hl_name);
}
-/// Gets |extmarks| in "traversal order" from a |charwise| region defined by
-/// buffer positions (inclusive, 0-indexed |api-indexing|).
+/// Gets |extmarks| (including |signs|) in "traversal order" from a |charwise|
+/// region defined by buffer positions (inclusive, 0-indexed |api-indexing|).
///
/// Region can be given as (row,col) tuples, or valid extmark ids (whose
/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
/// respectively, thus the following are equivalent:
-/// <pre>lua
-/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
-/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
+/// ```
///
/// If `end` is less than `start`, traversal works backwards. (Useful
/// with `limit`, to get the first marks prior to a given position.)
///
+/// Note: when using extmark ranges (marks with a end_row/end_col position)
+/// the `overlap` option might be useful. Otherwise only the start position
+/// of an extmark will be considered.
+///
/// Example:
-/// <pre>lua
-/// local a = vim.api
-/// local pos = a.nvim_win_get_cursor(0)
-/// local ns = a.nvim_create_namespace('my-plugin')
-/// -- Create new extmark at line 1, column 1.
-/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {})
-/// -- Create new extmark at line 3, column 1.
-/// local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {})
-/// -- Get extmarks only from line 3.
-/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
-/// -- Get all marks in this buffer + namespace.
-/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
-/// print(vim.inspect(ms))
-/// </pre>
+///
+/// ```lua
+/// local api = vim.api
+/// local pos = api.nvim_win_get_cursor(0)
+/// local ns = api.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// vim.print(ms)
+/// ```
///
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param ns_id Namespace id from |nvim_create_namespace()| or -1 for all namespaces
/// @param start Start of range: a 0-indexed (row, col) or valid extmark id
/// (whose position defines the bound). |api-indexing|
/// @param end End of range (inclusive): a 0-indexed (row, col) or valid
/// extmark id (whose position defines the bound). |api-indexing|
/// @param opts Optional parameters. Keys:
/// - limit: Maximum number of marks to return
-/// - details Whether to include the details dict
+/// - details: Whether to include the details dict
+/// - hl_name: Whether to include highlight group name instead of id, true if omitted
+/// - overlap: Also include marks which overlap the range, even if
+/// their start position is less than `start`
+/// - type: Filter marks by type: "highlight", "sign", "virt_text" and "virt_lines"
/// @param[out] err Error details, if any
/// @return List of [extmark_id, row, col] tuples in "traversal order".
-Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts,
- Error *err)
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end,
+ Dict(get_extmarks) *opts, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -301,38 +295,32 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_id == -1 || ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
- }
-
- Integer limit = -1;
- bool details = false;
-
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- if (strequal("limit", k.data)) {
- if (v->type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "limit is not an integer");
- return rv;
- }
- limit = v->data.integer;
- } else if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
- return rv;
- }
+ });
+
+ bool details = opts->details;
+ bool hl_name = GET_BOOL_OR_TRUE(opts, get_extmarks, hl_name);
+
+ ExtmarkType type = kExtmarkNone;
+ if (HAS_KEY(opts, get_extmarks, type)) {
+ if (strequal(opts->type.data, "sign")) {
+ type = kExtmarkSign;
+ } else if (strequal(opts->type.data, "virt_text")) {
+ type = kExtmarkVirtText;
+ } else if (strequal(opts->type.data, "virt_lines")) {
+ type = kExtmarkVirtLines;
+ } else if (strequal(opts->type.data, "highlight")) {
+ type = kExtmarkHighlight;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", opts->type.data, {
+ return rv;
+ });
}
}
+ Integer limit = HAS_KEY(opts, get_extmarks, limit) ? opts->limit : -1;
+
if (limit == 0) {
return rv;
} else if (limit < 0) {
@@ -357,11 +345,12 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
reverse = true;
}
- ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col,
- u_row, u_col, (int64_t)limit, reverse);
+ // note: ns_id=-1 allowed, represented as UINT32_MAX
+ ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row,
+ u_col, (int64_t)limit, reverse, type, opts->overlap);
for (size_t i = 0; i < kv_size(marks); i++) {
- ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, (bool)details)));
+ ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name)));
}
kv_destroy(marks);
@@ -379,6 +368,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// Using the optional arguments, it is possible to use this to highlight
/// a range of text, and also to associate virtual text to the mark.
///
+/// If present, the position defined by `end_col` and `end_row` should be after
+/// the start position in order for the extmark to cover a range.
+/// An earlier end position is not an error, but then it behaves like an empty
+/// range (no highlighting).
+///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param line Line where to place the mark, 0-based. |api-indexing|
@@ -402,24 +396,28 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// either as a string or as an integer, the latter which
/// can be obtained using |nvim_get_hl_id_by_name()|.
/// - virt_text_pos : position of virtual text. Possible values:
-/// - "eol": right after eol character (default)
+/// - "eol": right after eol character (default).
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
/// - "right_align": display right aligned in the window.
+/// - "inline": display at the specified column, and
+/// shift the buffer text to the right as needed.
/// - virt_text_win_col : position the virtual text at a fixed
/// window column (starting from the first
-/// text column)
+/// text column of the screen line) instead
+/// of "virt_text_pos".
/// - virt_text_hide : hide the virtual text when the background
-/// text is selected or hidden due to
-/// horizontal scroll 'nowrap'
+/// text is selected or hidden because of
+/// scrolling with 'nowrap' or 'smoothscroll'.
+/// Currently only affects "overlay" virt_text.
/// - hl_mode : control how highlights are combined with the
/// highlights of the text. Currently only affects
/// virt_text highlights, but might affect `hl_group`
/// in later versions.
-/// - "replace": only show the virt_text color. This is the
-/// default
-/// - "combine": combine with background text color
+/// - "replace": only show the virt_text color. This is the default.
+/// - "combine": combine with background text color.
/// - "blend": blend with background text color.
+/// Not supported for "inline" virt_text.
///
/// - virt_lines : virtual lines to add next to this mark
/// This should be an array over lines, where each line in
@@ -443,11 +441,17 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// buffer.
/// - right_gravity : boolean that indicates the direction
/// the extmark will be shifted in when new text is inserted
-/// (true for right, false for left). defaults to true.
+/// (true for right, false for left). Defaults to true.
/// - end_right_gravity : boolean that indicates the direction
/// the extmark end position (if it exists) will be shifted
/// in when new text is inserted (true for right, false
/// for left). Defaults to false.
+/// - undo_restore : Restore the exact position of the mark
+/// if text around the mark was deleted and then restored by undo.
+/// Defaults to true.
+/// - invalidate : boolean that indicates whether to hide the
+/// extmark if the entirety of its range is deleted. If
+/// "undo_restore" is false, the extmark is deleted instead.
/// - priority: a priority value for the highlight group or sign
/// attribute. For example treesitter highlighting uses a
/// value of 100.
@@ -493,286 +497,255 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
Dict(set_extmark) *opts, Error *err)
FUNC_API_SINCE(7)
{
- Decoration decor = DECORATION_INIT;
- bool has_decor = false;
+ DecorHighlightInline hl = DECOR_HIGHLIGHT_INLINE_INIT;
+ // TODO(bfredl): in principle signs with max one (1) hl group and max 4 bytes of text.
+ // should be a candidate for inlining as well.
+ DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT;
+ DecorVirtText virt_text = DECOR_VIRT_TEXT_INIT;
+ DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT;
+ bool has_hl = false;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
goto error;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
goto error;
- }
+ });
uint32_t id = 0;
- if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
- id = (uint32_t)opts->id.data.integer;
- } else if (HAS_KEY(opts->id)) {
- api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
- goto error;
+ if (HAS_KEY(opts, set_extmark, id)) {
+ VALIDATE_EXP((opts->id > 0), "id", "positive Integer", NULL, {
+ goto error;
+ });
+
+ id = (uint32_t)opts->id;
}
int line2 = -1;
+ bool did_end_line = false;
// For backward compatibility we support "end_line" as an alias for "end_row"
- if (HAS_KEY(opts->end_line)) {
- if (HAS_KEY(opts->end_row)) {
- api_set_error(err, kErrorTypeValidation, "cannot use both end_row and end_line");
+ if (HAS_KEY(opts, set_extmark, end_line)) {
+ VALIDATE(!HAS_KEY(opts, set_extmark, end_row),
+ "%s", "cannot use both 'end_row' and 'end_line'", {
goto error;
- }
- opts->end_row = opts->end_line;
- }
+ });
-#define OPTION_TO_BOOL(target, name, val) \
- target = api_object_to_bool(opts->name, #name, val, err); \
- if (ERROR_SET(err)) { \
- goto error; \
+ opts->end_row = opts->end_line;
+ did_end_line = true;
}
- bool strict = true;
- OPTION_TO_BOOL(strict, strict, true);
+ bool strict = GET_BOOL_OR_TRUE(opts, set_extmark, strict);
- if (opts->end_row.type == kObjectTypeInteger) {
- Integer val = opts->end_row.data.integer;
- if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) {
- api_set_error(err, kErrorTypeValidation, "end_row value outside range");
+ if (HAS_KEY(opts, set_extmark, end_row) || did_end_line) {
+ Integer val = opts->end_row;
+ VALIDATE_RANGE((val >= 0 && !(val > buf->b_ml.ml_line_count && strict)), "end_row", {
goto error;
- } else {
- line2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_row)) {
- api_set_error(err, kErrorTypeValidation, "end_row is not an integer");
- goto error;
+ });
+ line2 = (int)val;
}
colnr_T col2 = -1;
- if (opts->end_col.type == kObjectTypeInteger) {
- Integer val = opts->end_col.data.integer;
- if (val < 0 || val > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ if (HAS_KEY(opts, set_extmark, end_col)) {
+ Integer val = opts->end_col;
+ VALIDATE_RANGE((val >= 0 && val <= MAXCOL), "end_col", {
goto error;
- } else {
- col2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_col)) {
- api_set_error(err, kErrorTypeValidation, "end_col is not an integer");
- goto error;
+ });
+ col2 = (int)val;
}
// uncrustify:off
+ // TODO(bfredl): keyset type alias for hl_group? (nil|int|string)
struct {
const char *name;
Object *opt;
int *dest;
} hls[] = {
- { "hl_group" , &opts->hl_group , &decor.hl_id },
- { "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id },
- { "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id },
- { "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id },
- { "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id },
+ { "hl_group" , &opts->hl_group , &hl.hl_id },
+ { "sign_hl_group" , &opts->sign_hl_group , &sign.hl_id },
+ { "number_hl_group" , &opts->number_hl_group , &sign.number_hl_id },
+ { "line_hl_group" , &opts->line_hl_group , &sign.line_hl_id },
+ { "cursorline_hl_group", &opts->cursorline_hl_group, &sign.cursorline_hl_id },
{ NULL, NULL, NULL },
};
// uncrustify:on
for (int j = 0; hls[j].name && hls[j].dest; j++) {
- if (HAS_KEY(*hls[j].opt)) {
+ if (hls[j].opt->type != kObjectTypeNil) {
+ if (j > 0) {
+ sign.flags |= kSHIsSign;
+ } else {
+ has_hl = true;
+ }
*hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err);
if (ERROR_SET(err)) {
goto error;
}
- has_decor = true;
}
}
- if (opts->conceal.type == kObjectTypeString) {
- String c = opts->conceal.data.string;
- decor.conceal = true;
- if (c.size) {
- decor.conceal_char = utf_ptr2char(c.data);
+ if (HAS_KEY(opts, set_extmark, conceal)) {
+ hl.flags |= kSHConceal;
+ has_hl = true;
+ String c = opts->conceal;
+ if (c.size > 0) {
+ int ch;
+ hl.conceal_char = utfc_ptr2schar_len(c.data, (int)c.size, &ch);
+ if (!hl.conceal_char || !vim_isprintc(ch)) {
+ api_set_error(err, kErrorTypeValidation, "conceal char has to be printable");
+ goto error;
+ }
}
- has_decor = true;
- } else if (HAS_KEY(opts->conceal)) {
- api_set_error(err, kErrorTypeValidation, "conceal is not a String");
- goto error;
}
- if (opts->virt_text.type == kObjectTypeArray) {
- decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
- &decor.virt_text_width);
- has_decor = true;
+ if (HAS_KEY(opts, set_extmark, virt_text)) {
+ virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width);
if (ERROR_SET(err)) {
goto error;
}
- } else if (HAS_KEY(opts->virt_text)) {
- api_set_error(err, kErrorTypeValidation, "virt_text is not an Array");
- goto error;
}
- if (opts->virt_text_pos.type == kObjectTypeString) {
- String str = opts->virt_text_pos.data.string;
+ if (HAS_KEY(opts, set_extmark, virt_text_pos)) {
+ String str = opts->virt_text_pos;
if (strequal("eol", str.data)) {
- decor.virt_text_pos = kVTEndOfLine;
+ virt_text.pos = kVPosEndOfLine;
} else if (strequal("overlay", str.data)) {
- decor.virt_text_pos = kVTOverlay;
+ virt_text.pos = kVPosOverlay;
} else if (strequal("right_align", str.data)) {
- decor.virt_text_pos = kVTRightAlign;
+ virt_text.pos = kVPosRightAlign;
+ } else if (strequal("inline", str.data)) {
+ virt_text.pos = kVPosInline;
} else {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "virt_text_pos", str.data, {
+ goto error;
+ });
}
- } else if (HAS_KEY(opts->virt_text_pos)) {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String");
- goto error;
}
- if (opts->virt_text_win_col.type == kObjectTypeInteger) {
- decor.col = (int)opts->virt_text_win_col.data.integer;
- decor.virt_text_pos = kVTWinCol;
- } else if (HAS_KEY(opts->virt_text_win_col)) {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_win_col is not a Number of the correct size");
- goto error;
+ if (HAS_KEY(opts, set_extmark, virt_text_win_col)) {
+ virt_text.col = (int)opts->virt_text_win_col;
+ virt_text.pos = kVPosWinCol;
}
- OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
- OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
+ hl.flags |= opts->hl_eol ? kSHHlEol : 0;
+ virt_text.flags |= opts->virt_text_hide ? kVTHide : 0;
- if (opts->hl_mode.type == kObjectTypeString) {
- String str = opts->hl_mode.data.string;
+ if (HAS_KEY(opts, set_extmark, hl_mode)) {
+ String str = opts->hl_mode;
if (strequal("replace", str.data)) {
- decor.hl_mode = kHlModeReplace;
+ virt_text.hl_mode = kHlModeReplace;
} else if (strequal("combine", str.data)) {
- decor.hl_mode = kHlModeCombine;
+ virt_text.hl_mode = kHlModeCombine;
} else if (strequal("blend", str.data)) {
- decor.hl_mode = kHlModeBlend;
+ if (virt_text.pos == kVPosInline) {
+ VALIDATE(false, "%s", "cannot use 'blend' hl_mode with inline virtual text", {
+ goto error;
+ });
+ }
+ virt_text.hl_mode = kHlModeBlend;
} else {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "hl_mode", str.data, {
+ goto error;
+ });
}
- } else if (HAS_KEY(opts->hl_mode)) {
- api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
- goto error;
}
- bool virt_lines_leftcol = false;
- OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
+ bool virt_lines_leftcol = opts->virt_lines_leftcol;
- if (opts->virt_lines.type == kObjectTypeArray) {
- Array a = opts->virt_lines.data.array;
+ if (HAS_KEY(opts, set_extmark, virt_lines)) {
+ Array a = opts->virt_lines;
for (size_t j = 0; j < a.size; j++) {
- if (a.items[j].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
+ VALIDATE_T("virt_text_line", kObjectTypeArray, a.items[j].type, {
goto error;
- }
+ });
int dummig;
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
- kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
+ kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
if (ERROR_SET(err)) {
goto error;
}
- has_decor = true;
}
- } else if (HAS_KEY(opts->virt_lines)) {
- api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array");
- goto error;
}
- OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
-
- if (opts->priority.type == kObjectTypeInteger) {
- Integer val = opts->priority.data.integer;
+ virt_lines.flags |= opts->virt_lines_above ? kVTLinesAbove : 0;
- if (val < 0 || val > UINT16_MAX) {
- api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
+ if (HAS_KEY(opts, set_extmark, priority)) {
+ VALIDATE_RANGE((opts->priority >= 0 && opts->priority <= UINT16_MAX), "priority", {
goto error;
- }
- decor.priority = (DecorPriority)val;
- } else if (HAS_KEY(opts->priority)) {
- api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size");
- goto error;
+ });
+ hl.priority = (DecorPriority)opts->priority;
+ sign.priority = (DecorPriority)opts->priority;
+ virt_text.priority = (DecorPriority)opts->priority;
+ virt_lines.priority = (DecorPriority)opts->priority;
}
- if (opts->sign_text.type == kObjectTypeString) {
- if (!init_sign_text(&decor.sign_text,
- opts->sign_text.data.string.data)) {
- api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value");
+ if (HAS_KEY(opts, set_extmark, sign_text)) {
+ sign.text.ptr = NULL;
+ VALIDATE_S(init_sign_text(NULL, &sign.text.ptr, opts->sign_text.data),
+ "sign_text", "", {
goto error;
- }
- has_decor = true;
- } else if (HAS_KEY(opts->sign_text)) {
- api_set_error(err, kErrorTypeValidation, "sign_text is not a String");
- goto error;
+ });
+ sign.flags |= kSHIsSign;
}
- bool right_gravity = true;
- OPTION_TO_BOOL(right_gravity, right_gravity, true);
+ bool right_gravity = GET_BOOL_OR_TRUE(opts, set_extmark, right_gravity);
// Only error out if they try to set end_right_gravity without
// setting end_col or end_row
- if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) {
- api_set_error(err, kErrorTypeValidation,
- "cannot set end_right_gravity without setting end_row or end_col");
+ VALIDATE(!(line2 == -1 && col2 == -1 && HAS_KEY(opts, set_extmark, end_right_gravity)),
+ "%s", "cannot set end_right_gravity without end_row or end_col", {
goto error;
- }
-
- bool end_right_gravity = false;
- OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
+ });
size_t len = 0;
- bool ephemeral = false;
- OPTION_TO_BOOL(ephemeral, ephemeral, false);
-
- if (opts->spell.type == kObjectTypeNil) {
- decor.spell = kNone;
- } else {
- bool spell = false;
- OPTION_TO_BOOL(spell, spell, false);
- decor.spell = spell ? kTrue : kFalse;
- has_decor = true;
+ if (HAS_KEY(opts, set_extmark, spell)) {
+ hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff;
+ has_hl = true;
}
- OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
- if (decor.ui_watched) {
- has_decor = true;
+ if (opts->ui_watched) {
+ hl.flags |= kSHUIWatched;
+ if (virt_text.pos == kVPosOverlay) {
+ // TODO(bfredl): in a revised interface this should be the default.
+ hl.flags |= kSHUIWatchedOverlay;
+ }
+ has_hl = true;
}
- if (line < 0) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
+ VALIDATE_RANGE((line >= 0), "line", {
goto error;
- } else if (line > buf->b_ml.ml_line_count) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
+ });
+
+ if (line > buf->b_ml.ml_line_count) {
+ VALIDATE_RANGE(!strict, "line", {
goto error;
- } else {
- line = buf->b_ml.ml_line_count;
- }
+ });
+ line = buf->b_ml.ml_line_count;
} else if (line < buf->b_ml.ml_line_count) {
- len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false));
+ len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1));
}
if (col == -1) {
col = (Integer)len;
} else if (col > (Integer)len) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
+ VALIDATE_RANGE(!strict, "col", {
goto error;
- } else {
- col = (Integer)len;
- }
+ });
+ col = (Integer)len;
} else if (col < -1) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
- goto error;
+ VALIDATE_RANGE(false, "col", {
+ goto error;
+ });
}
if (col2 >= 0) {
if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
- len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1, false));
+ len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1));
} else if (line2 == buf->b_ml.ml_line_count) {
// We are trying to add an extmark past final newline
len = 0;
@@ -781,36 +754,96 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
line2 = (int)line;
}
if (col2 > (Integer)len) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ VALIDATE_RANGE(!strict, "end_col", {
goto error;
- } else {
- col2 = (int)len;
- }
+ });
+ col2 = (int)len;
}
} else if (line2 >= 0) {
col2 = 0;
}
- // TODO(bfredl): synergize these two branches even more
- if (ephemeral && decor_state.buf == buf) {
- decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id);
+ if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) {
+ int r = (int)line;
+ int c = (int)col;
+ if (line2 == -1) {
+ line2 = r;
+ col2 = c;
+ }
+
+ if (kv_size(virt_text.data.virt_text)) {
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true);
+ }
+ if (kv_size(virt_lines.data.virt_lines)) {
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true);
+ }
+ if (has_hl) {
+ DecorSignHighlight sh = decor_sh_from_inline(hl);
+ decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id);
+ }
} else {
- if (ephemeral) {
+ if (opts->ephemeral) {
api_set_error(err, kErrorTypeException, "not yet implemented");
goto error;
}
+ uint16_t decor_flags = 0;
+
+ DecorVirtText *decor_alloc = NULL;
+ if (kv_size(virt_text.data.virt_text)) {
+ decor_alloc = decor_put_vt(virt_text, decor_alloc);
+ if (virt_text.pos == kVPosInline) {
+ decor_flags |= MT_FLAG_DECOR_VIRT_TEXT_INLINE;
+ }
+ }
+ if (kv_size(virt_lines.data.virt_lines)) {
+ decor_alloc = decor_put_vt(virt_lines, decor_alloc);
+ decor_flags |= MT_FLAG_DECOR_VIRT_LINES;
+ }
+
+ uint32_t decor_indexed = DECOR_ID_INVALID;
+ if (sign.flags & kSHIsSign) {
+ decor_indexed = decor_put_sh(sign);
+ if (sign.text.ptr != NULL) {
+ decor_flags |= MT_FLAG_DECOR_SIGNTEXT;
+ }
+ if (sign.number_hl_id || sign.line_hl_id || sign.cursorline_hl_id) {
+ decor_flags |= MT_FLAG_DECOR_SIGNHL;
+ }
+ }
+
+ DecorInline decor = DECOR_INLINE_INIT;
+ if (decor_alloc || decor_indexed != DECOR_ID_INVALID || schar_high(hl.conceal_char)) {
+ if (has_hl) {
+ DecorSignHighlight sh = decor_sh_from_inline(hl);
+ sh.next = decor_indexed;
+ decor_indexed = decor_put_sh(sh);
+ }
+ decor.ext = true;
+ decor.data.ext = (DecorExt){ .sh_idx = decor_indexed, .vt = decor_alloc };
+ } else {
+ decor.data.hl = hl;
+ }
+
+ if (has_hl) {
+ decor_flags |= MT_FLAG_DECOR_HL;
+ }
+
extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
- has_decor ? &decor : NULL, right_gravity, end_right_gravity,
- kExtmarkNoUndo);
+ decor, decor_flags, right_gravity, opts->end_right_gravity,
+ !GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore),
+ opts->invalidate, err);
+ if (ERROR_SET(err)) {
+ decor_free(decor);
+ return 0;
+ }
}
return (Integer)id;
error:
- clear_virttext(&decor.virt_text);
- xfree(decor.sign_text);
+ clear_virttext(&virt_text.data.virt_text);
+ clear_virtlines(&virt_lines.data.virt_lines);
return 0;
}
@@ -829,12 +862,11 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
if (!buf) {
return false;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return false;
- }
+ });
- return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id);
+ return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id);
}
uint32_t src2ns(Integer *src_id)
@@ -887,14 +919,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
return 0;
}
- if (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", {
return 0;
- }
- if (col_start < 0 || col_start > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ });
+ VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", {
return 0;
- }
+ });
+
if (col_end < 0 || col_end > MAXCOL) {
col_end = MAXCOL;
}
@@ -919,13 +950,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
end_line++;
}
- Decoration decor = DECORATION_INIT;
- decor.hl_id = hl_id;
+ DecorInline decor = DECOR_INLINE_INIT;
+ decor.data.hl.hl_id = hl_id;
- extmark_set(buf, ns, NULL,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- &decor, true, false, kExtmarkNoUndo);
+ extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end,
+ decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL);
return ns_id;
}
@@ -950,10 +979,10 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
return;
}
- if (line_start < 0 || line_start >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ VALIDATE_RANGE((line_start >= 0 && line_start < MAXLNUM), "line number", {
return;
- }
+ });
+
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
@@ -964,14 +993,14 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// Set or change decoration provider for a |namespace|
///
-/// This is a very general purpose interface for having lua callbacks
+/// This is a very general purpose interface for having Lua callbacks
/// being triggered during the redraw code.
///
/// The expected usage is to set |extmarks| for the currently
/// redrawn buffer. |nvim_buf_set_extmark()| can be called to add marks
/// on a per-window or per-lines basis. Use the `ephemeral` key to only
/// use the mark for the current screen redraw (the callback will be called
-/// again for the next redraw ).
+/// again for the next redraw).
///
/// Note: this function should not be called often. Rather, the callbacks
/// themselves can be used to throttle unneeded callbacks. the `on_start`
@@ -983,11 +1012,13 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// for the extmarks set/modified inside the callback anyway.
///
/// Note: doing anything other than setting extmarks is considered experimental.
-/// Doing things like changing options are not expliticly forbidden, but is
+/// Doing things like changing options are not explicitly forbidden, but is
/// likely to have unexpected consequences (such as 100% CPU consumption).
/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
/// for the moment.
///
+/// Note: It is not allowed to remove or update extmarks in 'on_line' callbacks.
+///
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param opts Table of callbacks:
/// - on_start: called first on each screen redraw
@@ -996,7 +1027,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// window callbacks)
/// ["buf", bufnr, tick]
/// - on_win: called when starting to redraw a
-/// specific window.
+/// specific window. botline_guess is an approximation
+/// that does not exceed the last line number.
/// ["win", winid, bufnr, topline, botline_guess]
/// - on_line: called for each buffer line being redrawn.
/// (The interaction with fold lines is subject to change)
@@ -1015,7 +1047,7 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
struct {
const char *name;
- Object *source;
+ LuaRef *source;
LuaRef *dest;
} cbs[] = {
{ "on_start", &opts->on_start, &p->redraw_start },
@@ -1029,26 +1061,18 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
};
for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) {
- Object *v = cbs[i].source;
- if (v->type == kObjectTypeNil) {
+ LuaRef *v = cbs[i].source;
+ if (*v <= 0) {
continue;
}
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[i].name);
- goto error;
- }
- *(cbs[i].dest) = v->data.luaref;
- v->data.luaref = LUA_NOREF;
+ *(cbs[i].dest) = *v;
+ *v = LUA_NOREF;
}
p->active = true;
p->hl_valid++;
p->hl_cached = false;
- return;
-error:
- decor_provider_clear(p);
}
/// Gets the line and column of an |extmark|.
@@ -1075,75 +1099,40 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in
*col = MAXCOL;
return true;
} else if (id < 0) {
- api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
- return false;
+ VALIDATE_INT(false, "mark id", id, {
+ return false;
+ });
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
- if (extmark.row >= 0) {
- *row = extmark.row;
- *col = extmark.col;
- return true;
- } else {
- api_set_error(err, kErrorTypeValidation, "No mark with requested id");
+ MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
+
+ VALIDATE_INT((extmark.start.pos.row >= 0), "mark id (not found)", id, {
return false;
- }
+ });
+ *row = extmark.start.pos.row;
+ *col = extmark.start.pos.col;
+ return true;
// Check if it is a position
} else if (obj.type == kObjectTypeArray) {
Array pos = obj.data.array;
- if (pos.size != 2
- || pos.items[0].type != kObjectTypeInteger
- || pos.items[1].type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Position must have 2 integer elements");
+ VALIDATE_EXP((pos.size == 2
+ && pos.items[0].type == kObjectTypeInteger
+ && pos.items[1].type == kObjectTypeInteger),
+ "mark position", "2 Integer items", NULL, {
return false;
- }
+ });
+
Integer pos_row = pos.items[0].data.integer;
Integer pos_col = pos.items[1].data.integer;
- *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
+ *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
*col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
return true;
} else {
- api_set_error(err, kErrorTypeValidation,
- "Position must be a mark id Integer or position Array");
- return false;
- }
-}
-// adapted from sign.c:sign_define_init_text.
-// TODO(lewis6991): Consider merging
-static int init_sign_text(char **sign_text, char *text)
-{
- char *s;
-
- char *endp = text + (int)strlen(text);
-
- // Count cells and check for non-printable chars
- int cells = 0;
- for (s = text; s < endp; s += utfc_ptr2len(s)) {
- if (!vim_isprintc(utf_ptr2char(s))) {
- break;
- }
- cells += utf_ptr2cells(s);
- }
- // Currently must be empty, one or two display cells
- if (s != endp || cells > 2) {
- return FAIL;
- }
- if (cells < 1) {
- return OK;
- }
-
- // Allocate one byte more if we need to pad up
- // with a space.
- size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0));
- *sign_text = xstrnsave(text, len);
-
- if (cells == 1) {
- STRCPY(*sign_text + len - 1, " ");
+ VALIDATE_EXP(false, "mark position", "mark id Integer or 2-item Array", NULL, {
+ return false;
+ });
}
-
- return OK;
}
VirtText parse_virt_text(Array chunks, Error *err, int *width)
@@ -1151,17 +1140,14 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
VirtText virt_text = KV_INITIAL_VALUE;
int w = 0;
for (size_t i = 0; i < chunks.size; i++) {
- if (chunks.items[i].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ VALIDATE_T("chunk", kObjectTypeArray, chunks.items[i].type, {
goto free_exit;
- }
+ });
Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
+ VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString),
+ "%s", "Invalid chunk: expected Array with 1 or 2 Strings", {
goto free_exit;
- }
+ });
String str = chunk.items[0].data.string;
@@ -1176,8 +1162,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
goto free_exit;
}
if (j < arr.size - 1) {
- kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
- .hl_id = hl_id }));
+ kv_push(virt_text, ((VirtTextChunk){ .text = NULL, .hl_id = hl_id }));
}
}
} else {
@@ -1194,10 +1179,23 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
}
- *width = w;
+ if (width != NULL) {
+ *width = w;
+ }
return virt_text;
free_exit:
clear_virttext(&virt_text);
return virt_text;
}
+
+String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return NULL_STRING;
+ }
+
+ return mt_inspect(buf->b_marktree, keys, dot);
+}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index 0a627a889c..124feaabfb 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -1,17 +1,17 @@
-#ifndef NVIM_API_EXTMARK_H
-#define NVIM_API_EXTMARK_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/decoration.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/decoration_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
-EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
-EXTERN handle_T next_namespace_id INIT(= 1);
+EXTERN Map(String, int) namespace_ids INIT( = MAP_INIT);
+EXTERN handle_T next_namespace_id INIT( = 1);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.h.generated.h"
#endif
-#endif // NVIM_API_EXTMARK_H
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
deleted file mode 100644
index 30dcef6127..0000000000
--- a/src/nvim/api/keysets.lua
+++ /dev/null
@@ -1,229 +0,0 @@
-return {
- { 'context', {
- "types";
- }};
- { 'set_decoration_provider', {
- "on_start";
- "on_buf";
- "on_win";
- "on_line";
- "on_end";
- "_on_hl_def";
- "_on_spell_nav";
- }};
- { 'set_extmark', {
- "id";
- "end_line";
- "end_row";
- "end_col";
- "hl_group";
- "virt_text";
- "virt_text_pos";
- "virt_text_win_col";
- "virt_text_hide";
- "hl_eol";
- "hl_mode";
- "ephemeral";
- "priority";
- "right_gravity";
- "end_right_gravity";
- "virt_lines";
- "virt_lines_above";
- "virt_lines_leftcol";
- "strict";
- "sign_text";
- "sign_hl_group";
- "number_hl_group";
- "line_hl_group";
- "cursorline_hl_group";
- "conceal";
- "spell";
- "ui_watched";
- }};
- { 'keymap', {
- "noremap";
- "nowait";
- "silent";
- "script";
- "expr";
- "unique";
- "callback";
- "desc";
- "replace_keycodes";
- }};
- { 'get_commands', {
- "builtin";
- }};
- { 'user_command', {
- "addr";
- "bang";
- "bar";
- "complete";
- "count";
- "desc";
- "force";
- "keepscript";
- "nargs";
- "preview";
- "range";
- "register";
- }};
- { 'float_config', {
- "row";
- "col";
- "width";
- "height";
- "anchor";
- "relative";
- "win";
- "bufpos";
- "external";
- "focusable";
- "zindex";
- "border";
- "title";
- "title_pos";
- "style";
- "noautocmd";
- }};
- { 'runtime', {
- "is_lua";
- "do_source";
- }};
- { 'eval_statusline', {
- "winid";
- "maxwidth";
- "fillchar";
- "highlights";
- "use_winbar";
- "use_tabline";
- }};
- { 'option', {
- "scope";
- "win";
- "buf";
- }};
- { 'highlight', {
- "bold";
- "standout";
- "strikethrough";
- "underline";
- "undercurl";
- "underdouble";
- "underdotted";
- "underdashed";
- "italic";
- "reverse";
- "altfont";
- "nocombine";
- "default";
- "cterm";
- "foreground"; "fg";
- "background"; "bg";
- "ctermfg";
- "ctermbg";
- "special"; "sp";
- "link";
- "global_link";
- "fallback";
- "blend";
- "fg_indexed";
- "bg_indexed";
- }};
- { 'highlight_cterm', {
- "bold";
- "standout";
- "strikethrough";
- "underline";
- "undercurl";
- "underdouble";
- "underdotted";
- "underdashed";
- "italic";
- "reverse";
- "altfont";
- "nocombine";
- }};
- -- Autocmds
- { 'clear_autocmds', {
- "buffer";
- "event";
- "group";
- "pattern";
- }};
- { 'create_autocmd', {
- "buffer";
- "callback";
- "command";
- "desc";
- "group";
- "nested";
- "once";
- "pattern";
- }};
- { 'exec_autocmds', {
- "buffer";
- "group";
- "modeline";
- "pattern";
- "data";
- }};
- { 'get_autocmds', {
- "event";
- "group";
- "pattern";
- "buffer";
- }};
- { 'create_augroup', {
- "clear";
- }};
- { 'cmd', {
- "cmd";
- "range";
- "count";
- "reg";
- "bang";
- "args";
- "magic";
- "mods";
- "nargs";
- "addr";
- "nextcmd";
- }};
- { 'cmd_magic', {
- "file";
- "bar";
- }};
- { 'cmd_mods', {
- "silent";
- "emsg_silent";
- "unsilent";
- "filter";
- "sandbox";
- "noautocmd";
- "browse";
- "confirm";
- "hide";
- "horizontal";
- "keepalt";
- "keepjumps";
- "keepmarks";
- "keeppatterns";
- "lockmarks";
- "noswapfile";
- "tab";
- "verbose";
- "vertical";
- "split";
- }};
- { 'cmd_mods_filter', {
- "pattern";
- "force";
- }};
- { 'cmd_opts', {
- "output";
- }};
- { 'echo_opts', {
- "verbose";
- }};
-}
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
new file mode 100644
index 0000000000..e59eda5686
--- /dev/null
+++ b/src/nvim/api/keysets_defs.h
@@ -0,0 +1,315 @@
+#pragma once
+
+#include "nvim/api/private/defs.h"
+
+typedef struct {
+ OptionalKeys is_set__context_;
+ Array types;
+} Dict(context);
+
+typedef struct {
+ OptionalKeys is_set__set_decoration_provider_;
+ LuaRef on_start;
+ LuaRef on_buf;
+ LuaRef on_win;
+ LuaRef on_line;
+ LuaRef on_end;
+ LuaRef _on_hl_def;
+ LuaRef _on_spell_nav;
+} Dict(set_decoration_provider);
+
+typedef struct {
+ OptionalKeys is_set__set_extmark_;
+ Integer id;
+ Integer end_line;
+ Integer end_row;
+ Integer end_col;
+ Object hl_group;
+ Array virt_text;
+ String virt_text_pos;
+ Integer virt_text_win_col;
+ Boolean virt_text_hide;
+ Boolean hl_eol;
+ String hl_mode;
+ Boolean invalidate;
+ Boolean ephemeral;
+ Integer priority;
+ Boolean right_gravity;
+ Boolean end_right_gravity;
+ Array virt_lines;
+ Boolean virt_lines_above;
+ Boolean virt_lines_leftcol;
+ Boolean strict;
+ String sign_text;
+ Object sign_hl_group;
+ Object number_hl_group;
+ Object line_hl_group;
+ Object cursorline_hl_group;
+ String conceal;
+ Boolean spell;
+ Boolean ui_watched;
+ Boolean undo_restore;
+} Dict(set_extmark);
+
+typedef struct {
+ OptionalKeys is_set__get_extmarks_;
+ Integer limit;
+ Boolean details;
+ Boolean hl_name;
+ Boolean overlap;
+ String type;
+} Dict(get_extmarks);
+
+typedef struct {
+ OptionalKeys is_set__keymap_;
+ Boolean noremap;
+ Boolean nowait;
+ Boolean silent;
+ Boolean script;
+ Boolean expr;
+ Boolean unique;
+ LuaRef callback;
+ String desc;
+ Boolean replace_keycodes;
+} Dict(keymap);
+
+typedef struct {
+ Boolean builtin;
+} Dict(get_commands);
+
+typedef struct {
+ OptionalKeys is_set__user_command_;
+ Object addr;
+ Boolean bang;
+ Boolean bar;
+ Object complete;
+ Object count;
+ Object desc;
+ Boolean force;
+ Boolean keepscript;
+ Object nargs;
+ Object preview;
+ Object range;
+ Boolean register_;
+} Dict(user_command);
+
+typedef struct {
+ OptionalKeys is_set__float_config_;
+ Float row;
+ Float col;
+ Integer width;
+ Integer height;
+ String anchor;
+ String relative;
+ Window win;
+ Array bufpos;
+ Boolean external;
+ Boolean focusable;
+ Integer zindex;
+ Object border;
+ Object title;
+ String title_pos;
+ Object footer;
+ String footer_pos;
+ String style;
+ Boolean noautocmd;
+ Boolean fixed;
+ Boolean hide;
+} Dict(float_config);
+
+typedef struct {
+ Boolean is_lua;
+ Boolean do_source;
+} Dict(runtime);
+
+typedef struct {
+ OptionalKeys is_set__eval_statusline_;
+ Window winid;
+ Integer maxwidth;
+ String fillchar;
+ Boolean highlights;
+ Boolean use_winbar;
+ Boolean use_tabline;
+ Integer use_statuscol_lnum;
+} Dict(eval_statusline);
+
+typedef struct {
+ OptionalKeys is_set__option_;
+ String scope;
+ Window win;
+ Buffer buf;
+ String filetype;
+} Dict(option);
+
+typedef struct {
+ OptionalKeys is_set__highlight_;
+ Boolean bold;
+ Boolean standout;
+ Boolean strikethrough;
+ Boolean underline;
+ Boolean undercurl;
+ Boolean underdouble;
+ Boolean underdotted;
+ Boolean underdashed;
+ Boolean italic;
+ Boolean reverse;
+ Boolean altfont;
+ Boolean nocombine;
+ Boolean default_;
+ Object cterm;
+ Object foreground;
+ Object fg;
+ Object background;
+ Object bg;
+ Object ctermfg;
+ Object ctermbg;
+ Object special;
+ Object sp;
+ Object link;
+ Object global_link;
+ Boolean fallback;
+ Integer blend;
+ Boolean fg_indexed;
+ Boolean bg_indexed;
+ Boolean force;
+} Dict(highlight);
+
+typedef struct {
+ Boolean bold;
+ Boolean standout;
+ Boolean strikethrough;
+ Boolean underline;
+ Boolean undercurl;
+ Boolean underdouble;
+ Boolean underdotted;
+ Boolean underdashed;
+ Boolean italic;
+ Boolean reverse;
+ Boolean altfont;
+ Boolean nocombine;
+} Dict(highlight_cterm);
+
+typedef struct {
+ OptionalKeys is_set__get_highlight_;
+ Integer id;
+ String name;
+ Boolean link;
+ Boolean create;
+} Dict(get_highlight);
+
+typedef struct {
+ OptionalKeys is_set__get_ns_;
+ Window winid;
+} Dict(get_ns);
+
+typedef struct {
+ OptionalKeys is_set__win_text_height_;
+ Integer start_row;
+ Integer end_row;
+ Integer start_vcol;
+ Integer end_vcol;
+} Dict(win_text_height);
+
+typedef struct {
+ OptionalKeys is_set__clear_autocmds_;
+ Buffer buffer;
+ Object event;
+ Object group;
+ Object pattern;
+} Dict(clear_autocmds);
+
+typedef struct {
+ OptionalKeys is_set__create_autocmd_;
+ Buffer buffer;
+ Object callback;
+ String command;
+ String desc;
+ Object group;
+ Boolean nested;
+ Boolean once;
+ Object pattern;
+} Dict(create_autocmd);
+
+typedef struct {
+ OptionalKeys is_set__exec_autocmds_;
+ Buffer buffer;
+ Object group;
+ Boolean modeline;
+ Object pattern;
+ Object data;
+} Dict(exec_autocmds);
+
+typedef struct {
+ OptionalKeys is_set__get_autocmds_;
+ Object event;
+ Object group;
+ Object pattern;
+ Object buffer;
+} Dict(get_autocmds);
+
+typedef struct {
+ Object clear;
+} Dict(create_augroup);
+
+typedef struct {
+ OptionalKeys is_set__cmd_;
+ String cmd;
+ Array range;
+ Integer count;
+ String reg;
+ Boolean bang;
+ Array args;
+ Dictionary magic;
+ Dictionary mods;
+ Object nargs;
+ Object addr;
+ Object nextcmd;
+} Dict(cmd);
+
+typedef struct {
+ OptionalKeys is_set__cmd_magic_;
+ Boolean file;
+ Boolean bar;
+} Dict(cmd_magic);
+
+typedef struct {
+ OptionalKeys is_set__cmd_mods_;
+ Boolean silent;
+ Boolean emsg_silent;
+ Boolean unsilent;
+ Dictionary filter;
+ Boolean sandbox;
+ Boolean noautocmd;
+ Boolean browse;
+ Boolean confirm;
+ Boolean hide;
+ Boolean horizontal;
+ Boolean keepalt;
+ Boolean keepjumps;
+ Boolean keepmarks;
+ Boolean keeppatterns;
+ Boolean lockmarks;
+ Boolean noswapfile;
+ Integer tab;
+ Integer verbose;
+ Boolean vertical;
+ String split;
+} Dict(cmd_mods);
+
+typedef struct {
+ OptionalKeys is_set__cmd_mods_filter_;
+ String pattern;
+ Boolean force;
+} Dict(cmd_mods_filter);
+
+typedef struct {
+ Boolean output;
+} Dict(cmd_opts);
+
+typedef struct {
+ Boolean verbose;
+} Dict(echo_opts);
+
+typedef struct {
+ Boolean output;
+} Dict(exec_opts);
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index bfcb99754f..c012a69c7b 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -1,80 +1,135 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-#include <inttypes.h>
-#include <limits.h>
+#include <assert.h>
#include <stdbool.h>
#include <string.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/options.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer.h"
#include "nvim/eval/window.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/vim.h"
+#include "nvim/option_vars.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/options.c.generated.h"
#endif
-static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from,
+static int validate_option_value_args(Dict(option) *opts, char *name, int *scope,
+ OptReqScope *req_scope, void **from, char **filetype,
Error *err)
{
- if (opts->scope.type == kObjectTypeString) {
- if (!strcmp(opts->scope.data.string.data, "local")) {
+#define HAS_KEY_X(d, v) HAS_KEY(d, option, v)
+ if (HAS_KEY_X(opts, scope)) {
+ if (!strcmp(opts->scope.data, "local")) {
*scope = OPT_LOCAL;
- } else if (!strcmp(opts->scope.data.string.data, "global")) {
+ } else if (!strcmp(opts->scope.data, "global")) {
*scope = OPT_GLOBAL;
} else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- return FAIL;
+ VALIDATE_EXP(false, "scope", "'local' or 'global'", NULL, {
+ return FAIL;
+ });
}
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- return FAIL;
}
- *opt_type = SREQ_GLOBAL;
+ *req_scope = kOptReqGlobal;
+
+ if (filetype != NULL && HAS_KEY_X(opts, filetype)) {
+ *filetype = opts->filetype.data;
+ }
- if (opts->win.type == kObjectTypeInteger) {
- *opt_type = SREQ_WIN;
- *from = find_window_by_handle((int)opts->win.data.integer, err);
+ if (HAS_KEY_X(opts, win)) {
+ *req_scope = kOptReqWin;
+ *from = find_window_by_handle(opts->win, err);
if (ERROR_SET(err)) {
return FAIL;
}
- } else if (HAS_KEY(opts->win)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
- return FAIL;
}
- if (opts->buf.type == kObjectTypeInteger) {
+ if (HAS_KEY_X(opts, buf)) {
*scope = OPT_LOCAL;
- *opt_type = SREQ_BUF;
- *from = find_buffer_by_handle((int)opts->buf.data.integer, err);
+ *req_scope = kOptReqBuf;
+ *from = find_buffer_by_handle(opts->buf, err);
if (ERROR_SET(err)) {
return FAIL;
}
- } else if (HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
- return FAIL;
}
- if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
+ VALIDATE((!HAS_KEY_X(opts, filetype)
+ || !(HAS_KEY_X(opts, buf) || HAS_KEY_X(opts, scope) || HAS_KEY_X(opts, win))),
+ "%s", "cannot use 'filetype' with 'scope', 'buf' or 'win'", {
return FAIL;
- }
+ });
+
+ VALIDATE((!HAS_KEY_X(opts, scope) || !HAS_KEY_X(opts, buf)), "%s",
+ "cannot use both 'scope' and 'buf'", {
+ return FAIL;
+ });
- if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
+ VALIDATE((!HAS_KEY_X(opts, win) || !HAS_KEY_X(opts, buf)),
+ "%s", "cannot use both 'buf' and 'win'", {
return FAIL;
+ });
+
+ int flags = get_option_attrs(name);
+ if (flags == 0) {
+ // hidden or unknown option
+ api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name);
+ } else if (*req_scope == kOptReqBuf || *req_scope == kOptReqWin) {
+ // if 'buf' or 'win' is passed, make sure the option supports it
+ int req_flags = *req_scope == kOptReqBuf ? SOPT_BUF : SOPT_WIN;
+ if (!(flags & req_flags)) {
+ char *tgt = *req_scope & kOptReqBuf ? "buf" : "win";
+ char *global = flags & SOPT_GLOBAL ? "global " : "";
+ char *req = flags & SOPT_BUF ? "buffer-local "
+ : flags & SOPT_WIN ? "window-local " : "";
+
+ api_set_error(err, kErrorTypeValidation, "'%s' cannot be passed for %s%soption '%s'",
+ tgt, global, req, name);
+ }
}
return OK;
+#undef HAS_KEY_X
+}
+
+/// Create a dummy buffer and run the FileType autocmd on it.
+static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
+{
+ if (filetype == NULL) {
+ return NULL;
+ }
+
+ // Allocate a buffer without putting it in the buffer list.
+ buf_T *ftbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
+ if (ftbuf == NULL) {
+ api_set_error(err, kErrorTypeException, "Could not create internal buffer");
+ return NULL;
+ }
+
+ // Set curwin/curbuf to buf and save a few things.
+ aucmd_prepbuf(aco, ftbuf);
+
+ TRY_WRAP(err, {
+ set_option_value("bufhidden", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value("swapfile", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("modeline", BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline'
+
+ ftbuf->b_p_ft = xstrdup(filetype);
+ do_filetype_autocmd(ftbuf, false);
+ });
+
+ return ftbuf;
}
/// Gets the value of an option. The behavior of this function matches that of
@@ -89,53 +144,61 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
/// - win: |window-ID|. Used for getting window local options.
/// - buf: Buffer number. Used for getting buffer local options.
/// Implies {scope} is "local".
+/// - filetype: |filetype|. Used to get the default option for a
+/// specific filetype. Cannot be used with any other option.
+/// Note: this will trigger |ftplugin| and all |FileType|
+/// autocommands for the corresponding filetype.
/// @param[out] err Error details, if any
/// @return Option value
Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
FUNC_API_SINCE(9)
{
Object rv = OBJECT_INIT;
+ OptVal value = NIL_OPTVAL;
int scope = 0;
- int opt_type = SREQ_GLOBAL;
+ OptReqScope req_scope = kOptReqGlobal;
void *from = NULL;
- if (!validate_option_value_args(opts, &scope, &opt_type, &from, err)) {
- return rv;
+ char *filetype = NULL;
+
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &from, &filetype, err)) {
+ goto err;
}
- long numval = 0;
- char *stringval = NULL;
- getoption_T result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type,
- from, true, err);
+ aco_save_T aco;
+
+ buf_T *ftbuf = do_ft_buf(filetype, &aco, err);
if (ERROR_SET(err)) {
- return rv;
+ goto err;
}
- switch (result) {
- case gov_string:
- rv = STRING_OBJ(cstr_as_string(stringval));
- break;
- case gov_number:
- rv = INTEGER_OBJ(numval);
- break;
- case gov_bool:
- switch (numval) {
- case 0:
- case 1:
- rv = BOOLEAN_OBJ(numval);
- break;
- default:
- // Boolean options that return something other than 0 or 1 should return nil. Currently this
- // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
- rv = NIL;
- break;
- }
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
- return rv;
+ if (ftbuf != NULL) {
+ assert(!from);
+ from = ftbuf;
+ }
+
+ bool hidden;
+ value = get_option_value_for(name.data, NULL, scope, &hidden, req_scope, from, err);
+
+ if (ftbuf != NULL) {
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+
+ assert(curbuf != ftbuf); // safety check
+ wipe_buffer(ftbuf, false);
+ }
+
+ if (ERROR_SET(err)) {
+ goto err;
}
+ VALIDATE_S(!hidden && value.type != kOptValTypeNil, "option", name.data, {
+ goto err;
+ });
+
+ return optval_as_object(value);
+err:
+ optval_free(value);
return rv;
}
@@ -153,13 +216,14 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
/// - win: |window-ID|. Used for setting window local option.
/// - buf: Buffer number. Used for setting buffer local option.
/// @param[out] err Error details, if any
-void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
+void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(option) *opts,
+ Error *err)
FUNC_API_SINCE(9)
{
int scope = 0;
- int opt_type = SREQ_GLOBAL;
+ OptReqScope req_scope = kOptReqGlobal;
void *to = NULL;
- if (!validate_option_value_args(opts, &scope, &opt_type, &to, err)) {
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &to, NULL, err)) {
return;
}
@@ -169,41 +233,35 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error
// - option is global or local to window (global-local)
//
// Then force scope to local since we don't want to change the global option
- if (opt_type == SREQ_WIN && scope == 0) {
- int flags = get_option_value_strict(name.data, NULL, NULL, opt_type, to);
+ if (req_scope == kOptReqWin && scope == 0) {
+ int flags = get_option_attrs(name.data);
if (flags & SOPT_GLOBAL) {
scope = OPT_LOCAL;
}
}
- long numval = 0;
- char *stringval = NULL;
+ bool error = false;
+ OptVal optval = object_as_optval(value, &error);
- switch (value.type) {
- case kObjectTypeInteger:
- numval = value.data.integer;
- break;
- case kObjectTypeBoolean:
- numval = value.data.boolean ? 1 : 0;
- break;
- case kObjectTypeString:
- stringval = value.data.string.data;
- break;
- case kObjectTypeNil:
- scope |= OPT_CLEAR;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "invalid value for option");
+ // Handle invalid option value type.
+ // Don't use `name` in the error message here, because `name` can be any String.
+ // No need to check if value type actually matches the types for the option, as set_option_value()
+ // already handles that.
+ VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), {
return;
- }
+ });
- access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err);
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ set_option_value_for(name.data, optval, scope, req_scope, to, err);
+ });
}
/// Gets the option information for all options.
///
/// The dictionary has the full option names as keys and option metadata
-/// dictionaries as detailed at |nvim_get_option_info()|.
+/// dictionaries as detailed at |nvim_get_option_info2()|.
+///
+/// @see |nvim_get_commands()|
///
/// @return dictionary of all options
Dictionary nvim_get_all_options_info(Error *err)
@@ -212,7 +270,7 @@ Dictionary nvim_get_all_options_info(Error *err)
return get_all_vimoptions();
}
-/// Gets the option information for one option
+/// Gets the option information for one option from arbitrary buffer or window
///
/// Resulting dictionary has keys:
/// - name: Name of the option (like 'filetype')
@@ -231,324 +289,289 @@ Dictionary nvim_get_all_options_info(Error *err)
/// - commalist: List of comma separated values
/// - flaglist: List of single char flags
///
+/// When {scope} is not provided, the last set information applies to the local
+/// value in the current buffer or window if it is available, otherwise the
+/// global value information is returned. This behavior can be disabled by
+/// explicitly specifying {scope} in the {opts} table.
///
-/// @param name Option name
+/// @param name Option name
+/// @param opts Optional parameters
+/// - scope: One of "global" or "local". Analogous to
+/// |:setglobal| and |:setlocal|, respectively.
+/// - win: |window-ID|. Used for getting window local options.
+/// - buf: Buffer number. Used for getting buffer local options.
+/// Implies {scope} is "local".
/// @param[out] err Error details, if any
/// @return Option Information
-Dictionary nvim_get_option_info(String name, Error *err)
- FUNC_API_SINCE(7)
+Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err)
+ FUNC_API_SINCE(11)
{
- return get_vimoption(name, err);
-}
-/// Sets the global value of an option.
-///
-/// @param channel_id
-/// @param name Option name
-/// @param value New option value
-/// @param[out] err Error details, if any
-void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
-}
+ int scope = 0;
+ OptReqScope req_scope = kOptReqGlobal;
+ void *from = NULL;
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &from, NULL, err)) {
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
-/// Gets the global value of an option.
-///
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value (global)
-Object nvim_get_option(String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
-{
- return get_option_from(NULL, SREQ_GLOBAL, name, err);
+ buf_T *buf = (req_scope == kOptReqBuf) ? (buf_T *)from : curbuf;
+ win_T *win = (req_scope == kOptReqWin) ? (win_T *)from : curwin;
+
+ return get_vimoption(name, scope, buf, win, err);
}
-/// Gets a buffer option value
+/// Switch current context to get/set option value for window/buffer.
+///
+/// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
+/// @return true if context was switched, false otherwise.
+static bool switch_option_context(void *const ctx, OptReqScope req_scope, void *const from,
+ Error *err)
{
- buf_T *buf = find_buffer_by_handle(buffer, err);
+ switch (req_scope) {
+ case kOptReqWin: {
+ win_T *const win = (win_T *)from;
+ switchwin_T *const switchwin = (switchwin_T *)ctx;
+
+ if (win == curwin) {
+ return false;
+ }
- if (!buf) {
- return (Object)OBJECT_INIT;
+ if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true)
+ == FAIL) {
+ restore_win_noblock(switchwin, true);
+
+ if (try_end(err)) {
+ return false;
+ }
+ api_set_error(err, kErrorTypeException, "Problem while switching windows");
+ return false;
+ }
+ return true;
}
+ case kOptReqBuf: {
+ buf_T *const buf = (buf_T *)from;
+ aco_save_T *const aco = (aco_save_T *)ctx;
- return get_option_from(buf, SREQ_BUF, name, err);
+ if (buf == curbuf) {
+ return false;
+ }
+ aucmd_prepbuf(aco, buf);
+ return true;
+ }
+ case kOptReqGlobal:
+ return false;
+ }
+ UNREACHABLE;
}
-/// Sets a buffer option value. Passing `nil` as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
+/// Restore context after getting/setting option for window/buffer. See switch_option_context() for
+/// params.
+static void restore_option_context(void *const ctx, OptReqScope req_scope)
{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return;
+ switch (req_scope) {
+ case kOptReqWin:
+ restore_win_noblock((switchwin_T *)ctx, true);
+ break;
+ case kOptReqBuf:
+ aucmd_restbuf((aco_save_T *)ctx);
+ break;
+ case kOptReqGlobal:
+ break;
}
-
- set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
}
-/// Gets a window option value
+/// Get attributes for an option.
///
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
+/// @param name Option name.
+///
+/// @return Option attributes.
+/// 0 for hidden or unknown option.
+/// See SOPT_* in option_defs.h for other flags.
+int get_option_attrs(char *name)
{
- win_T *win = find_window_by_handle(window, err);
+ int opt_idx = findoption(name);
- if (!win) {
- return (Object)OBJECT_INIT;
+ if (opt_idx < 0) {
+ return 0;
}
- return get_option_from(win, SREQ_WIN, name, err);
-}
+ vimoption_T *opt = get_option(opt_idx);
-/// Sets a window option value. Passing `nil` as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- win_T *win = find_window_by_handle(window, err);
+ if (is_tty_option(opt->fullname)) {
+ return SOPT_STRING | SOPT_GLOBAL;
+ }
- if (!win) {
- return;
+ // Hidden option
+ if (opt->var == NULL) {
+ return 0;
}
- set_option_to(channel_id, win, SREQ_WIN, name, value, err);
-}
+ int attrs = 0;
-/// Gets the value of a global or local (buffer, window) option.
-///
-/// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-/// @return the option value
-static Object get_option_from(void *from, int type, String name, Error *err)
-{
- Object rv = OBJECT_INIT;
+ if (opt->flags & P_BOOL) {
+ attrs |= SOPT_BOOL;
+ } else if (opt->flags & P_NUM) {
+ attrs |= SOPT_NUM;
+ } else if (opt->flags & P_STRING) {
+ attrs |= SOPT_STRING;
+ }
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return rv;
- }
-
- // Return values
- int64_t numval;
- char *stringval = NULL;
- int flags = get_option_value_strict(name.data, &numval, &stringval, type, from);
-
- if (!flags) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
- name.data);
- return rv;
- }
-
- if (flags & SOPT_BOOL) {
- rv.type = kObjectTypeBoolean;
- rv.data.boolean = numval ? true : false;
- } else if (flags & SOPT_NUM) {
- rv.type = kObjectTypeInteger;
- rv.data.integer = numval;
- } else if (flags & SOPT_STRING) {
- if (stringval) {
- rv.type = kObjectTypeString;
- rv.data.string.data = stringval;
- rv.data.string.size = strlen(stringval);
- } else {
- api_set_error(err, kErrorTypeException,
- "Failed to get value for option '%s'",
- name.data);
- }
- } else {
- api_set_error(err,
- kErrorTypeException,
- "Unknown type for option '%s'",
- name.data);
+ if (opt->indir == PV_NONE || (opt->indir & PV_BOTH)) {
+ attrs |= SOPT_GLOBAL;
+ }
+ if (opt->indir & PV_WIN) {
+ attrs |= SOPT_WIN;
+ } else if (opt->indir & PV_BUF) {
+ attrs |= SOPT_BUF;
}
- return rv;
+ return attrs;
}
-/// Sets the value of a global or local (buffer, window) option.
+/// Check if option has a value in the requested scope.
///
-/// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
+/// @param name Option name.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+///
+/// @return true if option has a value in the requested scope, false otherwise.
+static bool option_has_scope(char *name, OptReqScope req_scope)
{
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return;
+ int opt_idx = findoption(name);
+
+ if (opt_idx < 0) {
+ return false;
}
- int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
+ vimoption_T *opt = get_option(opt_idx);
- if (flags == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
- name.data);
- return;
+ // Hidden option.
+ if (opt->var == NULL) {
+ return false;
+ }
+ // TTY option.
+ if (is_tty_option(opt->fullname)) {
+ return req_scope == kOptReqGlobal;
}
- if (value.type == kObjectTypeNil) {
- if (type == SREQ_GLOBAL) {
- api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
- name.data);
- return;
- } else if (!(flags & SOPT_GLOBAL)) {
- api_set_error(err,
- kErrorTypeException,
- "Cannot unset option '%s' "
- "because it doesn't have a global value",
- name.data);
- return;
- } else {
- unset_global_local_option(name.data, to);
- return;
- }
+ switch (req_scope) {
+ case kOptReqGlobal:
+ return opt->var != VAR_WIN;
+ case kOptReqBuf:
+ return opt->indir & PV_BUF;
+ case kOptReqWin:
+ return opt->indir & PV_WIN;
}
+ UNREACHABLE;
+}
- long numval = 0;
- char *stringval = NULL;
+/// Get the option value in the requested scope.
+///
+/// @param name Option name.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Pointer to buffer or window for local option value.
+/// @param[out] err Error message, if any.
+///
+/// @return Option value in the requested scope. Returns a Nil option value if option is not found,
+/// hidden or if it isn't present in the requested scope. (i.e. has no global, window-local or
+/// buffer-local value depending on opt_scope).
+OptVal get_option_value_strict(char *name, OptReqScope req_scope, void *from, Error *err)
+{
+ OptVal retv = NIL_OPTVAL;
- if (flags & SOPT_BOOL) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option '%s' requires a Boolean value",
- name.data);
- return;
- }
+ if (!option_has_scope(name, req_scope)) {
+ return retv;
+ }
+ if (get_tty_option(name, &retv.data.string.data)) {
+ retv.type = kOptValTypeString;
+ return retv;
+ }
- numval = value.data.boolean;
- } else if (flags & SOPT_NUM) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires an integer value",
- name.data);
- return;
- }
+ int opt_idx = findoption(name);
+ assert(opt_idx != 0); // option_has_scope() already verifies if option name is valid.
- if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
- api_set_error(err, kErrorTypeValidation,
- "Value for option '%s' is out of range",
- name.data);
- return;
- }
+ vimoption_T *opt = get_option(opt_idx);
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
+ bool switched = switch_option_context(ctx, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return retv;
+ }
- numval = (int)value.data.integer;
- } else {
- if (value.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires a string value",
- name.data);
- return;
- }
+ char *varp = get_varp_scope(opt, req_scope == kOptReqGlobal ? OPT_GLOBAL : OPT_LOCAL);
+ retv = optval_from_varp(opt_idx, varp);
- stringval = value.data.string.data;
+ if (switched) {
+ restore_option_context(ctx, req_scope);
}
- // For global-win-local options -> setlocal
- // For win-local options -> setglobal and setlocal (opt_flags == 0)
- const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) ? 0 :
- (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL;
-
- WITH_SCRIPT_CONTEXT(channel_id, {
- access_option_value_for(name.data, &numval, &stringval, opt_flags, type, to, false, err);
- });
+ return retv;
}
-static getoption_T access_option_value(char *key, long *numval, char **stringval, int opt_flags,
- bool get, Error *err)
+/// Get option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL).
+/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param[out] hidden Whether option is hidden.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+///
+/// @return Option value. Must be freed by caller.
+OptVal get_option_value_for(const char *const name, uint32_t *flagsp, int scope, bool *hidden,
+ const OptReqScope req_scope, void *const from, Error *err)
{
- if (get) {
- return get_option_value(key, numval, stringval, NULL, opt_flags);
- } else {
- char *errmsg;
- if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) {
- if (try_end(err)) {
- return 0;
- }
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
- api_set_error(err, kErrorTypeException, "%s", errmsg);
- }
- return 0;
+ bool switched = switch_option_context(ctx, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return NIL_OPTVAL;
+ }
+
+ OptVal retv = get_option_value(name, flagsp, scope, hidden);
+
+ if (switched) {
+ restore_option_context(ctx, req_scope);
}
+
+ return retv;
}
-static getoption_T access_option_value_for(char *key, long *numval, char **stringval, int opt_flags,
- int opt_type, void *from, bool get, Error *err)
+/// Set option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[in] value Option value.
+/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+void set_option_value_for(const char *const name, OptVal value, const int opt_flags,
+ const OptReqScope req_scope, void *const from, Error *err)
{
- bool need_switch = false;
switchwin_T switchwin;
aco_save_T aco;
- getoption_T result = 0;
-
- try_start();
- switch (opt_type) {
- case SREQ_WIN:
- need_switch = (win_T *)from != curwin;
- if (need_switch) {
- if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
- == FAIL) {
- restore_win_noblock(&switchwin, true);
- if (try_end(err)) {
- return result;
- }
- api_set_error(err, kErrorTypeException, "Problem while switching windows");
- return result;
- }
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- restore_win_noblock(&switchwin, true);
- }
- break;
- case SREQ_BUF:
- need_switch = (buf_T *)from != curbuf;
- if (need_switch) {
- aucmd_prepbuf(&aco, (buf_T *)from);
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- aucmd_restbuf(&aco);
- }
- break;
- case SREQ_GLOBAL:
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- break;
- }
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
+ bool switched = switch_option_context(ctx, req_scope, from, err);
if (ERROR_SET(err)) {
- return result;
+ return;
}
- try_end(err);
+ const char *const errmsg = set_option_value(name, value, opt_flags);
+ if (errmsg) {
+ api_set_error(err, kErrorTypeException, "%s", errmsg);
+ }
- return result;
+ if (switched) {
+ restore_option_context(ctx, req_scope);
+ }
}
diff --git a/src/nvim/api/options.h b/src/nvim/api/options.h
index efbfec3a6c..c16c6088b3 100644
--- a/src/nvim/api/options.h
+++ b/src/nvim/api/options.h
@@ -1,9 +1,11 @@
-#ifndef NVIM_API_OPTIONS_H
-#define NVIM_API_OPTIONS_H
+#pragma once
+
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/options.h.generated.h"
#endif
-
-#endif // NVIM_API_OPTIONS_H
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index 58ff552ab7..90023171e5 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -1,25 +1,21 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
#include "klib/kvec.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
+#include "nvim/assert_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/garray.h"
+#include "nvim/func_attr.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
/// Helper structure for vim_to_object
typedef struct {
@@ -49,9 +45,9 @@ typedef struct {
#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
do { \
const size_t len_ = (size_t)(len); \
- const char *const str_ = (const char *)(str); \
+ const char *const str_ = (str); \
assert(len_ == 0 || str_ != NULL); \
- kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_?str_:""), len_))); \
+ kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_ ? str_ : ""), len_))); \
} while (0)
#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
@@ -204,6 +200,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const
#define TYPVAL_ENCODE_FIRST_ARG_NAME edata
#include "nvim/eval/typval_encode.c.h"
+
#undef TYPVAL_ENCODE_SCOPE
#undef TYPVAL_ENCODE_NAME
#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
@@ -256,12 +253,14 @@ Object vim_to_object(typval_T *obj)
return ret;
}
-/// Converts from type Object to a VimL value.
+/// Converts from type Object to a Vimscript value.
///
/// @param obj Object to convert from.
/// @param tv Conversion result is placed here. On failure member v_type is
/// set to VAR_UNKNOWN (no allocation was made for this variable).
-/// returns true if conversion is successful, otherwise false.
+/// @param err Error object.
+///
+/// @returns true if conversion is successful, otherwise false.
bool object_to_vim(Object obj, typval_T *tv, Error *err)
{
tv->v_type = VAR_UNKNOWN;
@@ -275,7 +274,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
case kObjectTypeBoolean:
tv->v_type = VAR_BOOL;
- tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
+ tv->vval.v_bool = obj.data.boolean ? kBoolVarTrue : kBoolVarFalse;
break;
case kObjectTypeBuffer:
@@ -283,7 +282,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
case kObjectTypeTabpage:
case kObjectTypeInteger:
STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T),
- "Integer size must be <= VimL number size");
+ "Integer size must be <= Vimscript number size");
tv->v_type = VAR_NUMBER;
tv->vval.v_number = (varnumber_T)obj.data.integer;
break;
@@ -363,9 +362,6 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
tv->vval.v_string = xstrdup(name);
break;
}
-
- default:
- abort();
}
return true;
diff --git a/src/nvim/api/private/converter.h b/src/nvim/api/private/converter.h
index 80ee640295..fc82abf332 100644
--- a/src/nvim/api/private/converter.h
+++ b/src/nvim/api/private/converter.h
@@ -1,11 +1,8 @@
-#ifndef NVIM_API_PRIVATE_CONVERTER_H
-#define NVIM_API_PRIVATE_CONVERTER_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/converter.h.generated.h"
#endif
-
-#endif // NVIM_API_PRIVATE_CONVERTER_H
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 8acbf0d9de..25c8377518 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_API_PRIVATE_DEFS_H
-#define NVIM_API_PRIVATE_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
@@ -7,7 +6,7 @@
#include "klib/kvec.h"
#include "nvim/func_attr.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#define ARRAY_DICT_INIT KV_INITIAL_VALUE
#define STRING_INIT { .data = NULL, .size = 0 }
@@ -42,10 +41,10 @@ typedef enum {
/// Mask for all internal calls
#define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1))
-/// Internal call from VimL code
+/// Internal call from Vimscript code
#define VIML_INTERNAL_CALL INTERNAL_CALL_MASK
-/// Internal call from lua code
+/// Internal call from Lua code
#define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1)
static inline bool is_internal_call(uint64_t channel_id)
@@ -124,14 +123,18 @@ struct key_value_pair {
Object value;
};
-typedef Object *(*field_hash)(void *retval, const char *str, size_t len);
+typedef uint64_t OptionalKeys;
+
+// this is the prefix of all keysets with optional keys
+typedef struct {
+ OptionalKeys is_set_;
+} OptKeySet;
+
typedef struct {
char *str;
size_t ptr_off;
+ ObjectType type; // kObjectTypeNil == untyped
+ int opt_index;
} KeySetLink;
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "keysets_defs.generated.h"
-#endif
-
-#endif // NVIM_API_PRIVATE_DEFS_H
+typedef KeySetLink *(*FieldHashfn)(const char *str, size_t len);
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index f427bba00e..53fcd148bd 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stddef.h>
#include "nvim/api/private/defs.h"
diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h
index 4ae61b2bfb..6a2c9eaf54 100644
--- a/src/nvim/api/private/dispatch.h
+++ b/src/nvim/api/private/dispatch.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_API_PRIVATE_DISPATCH_H
-#define NVIM_API_PRIVATE_DISPATCH_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "nvim/api/private/defs.h"
-#include "nvim/memory.h"
-#include "nvim/types.h"
+#include "nvim/memory_defs.h"
+#include "nvim/types_defs.h"
typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error);
@@ -27,7 +26,6 @@ extern const MsgpackRpcRequestHandler method_handlers[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/dispatch.h.generated.h"
-# include "api/private/dispatch_wrappers.h.generated.h"
+# include "api/private/dispatch_wrappers.h.generated.h" // IWYU pragma: export
+# include "keysets_defs.generated.h"
#endif
-
-#endif // NVIM_API_PRIVATE_DISPATCH_H
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 519f2cc5bf..be39836a5b 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1,13 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <inttypes.h>
#include <limits.h>
#include <msgpack/unpack.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -16,21 +13,26 @@
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/version.h"
@@ -40,10 +42,10 @@
# include "api/private/ui_events_metadata.generated.h"
#endif
-/// Start block that may cause VimL exceptions while evaluating another code
+/// Start block that may cause Vimscript exceptions while evaluating another code
///
-/// Used when caller is supposed to be operating when other VimL code is being
-/// processed and that “other VimL code†must not be affected.
+/// Used when caller is supposed to be operating when other Vimscript code is being
+/// processed and that “other Vimscript code†must not be affected.
///
/// @param[out] tstate Location where try state should be saved.
void try_enter(TryState *const tstate)
@@ -234,8 +236,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
// Delete the key
if (di == NULL) {
// Doesn't exist, fail
- api_set_error(err, kErrorTypeValidation, "Key not found: %s",
- key.data);
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
} else {
// Notify watchers
if (watched) {
@@ -264,13 +265,23 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
di = tv_dict_item_alloc_len(key.data, key.size);
tv_dict_add(dict, di);
} else {
- if (watched) {
- tv_copy(&di->di_tv, &oldtv);
- }
// Return the old value
if (retval) {
rv = vim_to_object(&di->di_tv);
}
+ bool type_error = false;
+ if (dict == &vimvardict
+ && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
+ tv_clear(&tv);
+ if (type_error) {
+ api_set_error(err, kErrorTypeValidation,
+ "Setting v:%s to value with wrong type", key.data);
+ }
+ return rv;
+ }
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
tv_clear(&di->di_tv);
}
@@ -478,6 +489,27 @@ Array string_to_array(const String input, bool crlf)
return ret;
}
+/// Normalizes 0-based indexes to buffer line numbers.
+int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
+{
+ assert(buf->b_ml.ml_line_count > 0);
+ int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
+ // A negative index counts from the bottom.
+ index = index < 0 ? max_index + index + 1 : index;
+
+ // Check for oob and clamp.
+ if (index > max_index) {
+ *oob = true;
+ index = max_index;
+ } else if (index < 0) {
+ *oob = true;
+ index = 0;
+ }
+ // Convert the index to a 1-based line number.
+ index++;
+ return index;
+}
+
/// Returns a substring of a buffer line
///
/// @param buf Buffer handle
@@ -495,7 +527,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col
return rv;
}
- char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false);
+ char *bufstr = ml_get_buf(buf, (linenr_T)lnum);
size_t line_length = strlen(bufstr);
start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col;
@@ -577,9 +609,6 @@ void api_free_object(Object value)
case kObjectTypeLuaRef:
api_free_luaref(value.data.luaref);
break;
-
- default:
- abort();
}
}
@@ -660,10 +689,10 @@ static void init_ui_event_metadata(Dictionary *metadata)
msgpack_unpacked_destroy(&unpacked);
PUT(*metadata, "ui_events", ui_events);
Array ui_options = ARRAY_DICT_INIT;
- ADD(ui_options, STRING_OBJ(cstr_to_string("rgb")));
+ ADD(ui_options, CSTR_TO_OBJ("rgb"));
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (ui_ext_names[i][0] != '_') {
- ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i])));
+ ADD(ui_options, CSTR_TO_OBJ(ui_ext_names[i]));
}
}
PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options));
@@ -692,17 +721,17 @@ static void init_type_metadata(Dictionary *metadata)
Dictionary buffer_metadata = ARRAY_DICT_INIT;
PUT(buffer_metadata, "id",
INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT));
- PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_")));
+ PUT(buffer_metadata, "prefix", CSTR_TO_OBJ("nvim_buf_"));
Dictionary window_metadata = ARRAY_DICT_INIT;
PUT(window_metadata, "id",
INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT));
- PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_")));
+ PUT(window_metadata, "prefix", CSTR_TO_OBJ("nvim_win_"));
Dictionary tabpage_metadata = ARRAY_DICT_INIT;
PUT(tabpage_metadata, "id",
INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT));
- PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_")));
+ PUT(tabpage_metadata, "prefix", CSTR_TO_OBJ("nvim_tabpage_"));
PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata));
PUT(types, "Window", DICTIONARY_OBJ(window_metadata));
@@ -767,10 +796,8 @@ Object copy_object(Object obj, Arena *arena)
case kObjectTypeLuaRef:
return LUAREF_OBJ(api_new_luaref(obj.data.luaref));
-
- default:
- abort();
}
+ UNREACHABLE;
}
void api_set_error(Error *err, ErrorType errType, const char *format, ...)
@@ -806,7 +833,7 @@ bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err
} else if (obj.type == kObjectTypeInteger) {
return obj.data.integer; // C semantics: non-zero int is true
} else if (obj.type == kObjectTypeNil) {
- return nil_value; // caller decides what NIL (missing retval in lua) means
+ return nil_value; // caller decides what NIL (missing retval in Lua) means
} else {
api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what);
return false;
@@ -821,12 +848,40 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
} else if (obj.type == kObjectTypeInteger) {
return MAX((int)obj.data.integer, 0);
} else {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a valid highlight", what);
+ api_set_error(err, kErrorTypeValidation, "Invalid highlight: %s", what);
return 0;
}
}
+char *api_typename(ObjectType t)
+{
+ switch (t) {
+ case kObjectTypeNil:
+ return "nil";
+ case kObjectTypeBoolean:
+ return "Boolean";
+ case kObjectTypeInteger:
+ return "Integer";
+ case kObjectTypeFloat:
+ return "Float";
+ case kObjectTypeString:
+ return "String";
+ case kObjectTypeArray:
+ return "Array";
+ case kObjectTypeDictionary:
+ return "Dict";
+ case kObjectTypeLuaRef:
+ return "Function";
+ case kObjectTypeBuffer:
+ return "Buffer";
+ case kObjectTypeWindow:
+ return "Window";
+ case kObjectTypeTabpage:
+ return "Tabpage";
+ }
+ UNREACHABLE;
+}
+
HlMessage parse_hl_msg(Array chunks, Error *err)
{
HlMessage hl_msg = KV_INITIAL_VALUE;
@@ -865,17 +920,84 @@ free_exit:
return (HlMessage)KV_INITIAL_VALUE;
}
-bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
+// see also nlua_pop_keydict for the lua specific implementation
+bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error *err)
{
for (size_t i = 0; i < dict.size; i++) {
String k = dict.items[i].key;
- Object *field = hashy(rv, k.data, k.size);
+ KeySetLink *field = hashy(k.data, k.size);
if (!field) {
api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data);
return false;
}
- *field = dict.items[i].value;
+ if (field->opt_index >= 0) {
+ OptKeySet *ks = (OptKeySet *)retval;
+ ks->is_set_ |= (1ULL << field->opt_index);
+ }
+
+ char *mem = ((char *)retval + field->ptr_off);
+ Object *value = &dict.items[i].value;
+ if (field->type == kObjectTypeNil) {
+ *(Object *)mem = *value;
+ } else if (field->type == kObjectTypeInteger) {
+ VALIDATE_T(field->str, kObjectTypeInteger, value->type, {
+ return false;
+ });
+ *(Integer *)mem = value->data.integer;
+ } else if (field->type == kObjectTypeFloat) {
+ Float *val = (Float *)mem;
+ if (value->type == kObjectTypeInteger) {
+ *val = (Float)value->data.integer;
+ } else {
+ VALIDATE_T(field->str, kObjectTypeFloat, value->type, {
+ return false;
+ });
+ *val = value->data.floating;
+ }
+ } else if (field->type == kObjectTypeBoolean) {
+ // caller should check HAS_KEY to override the nil behavior, or GET_BOOL_OR_TRUE
+ // to directly use true when nil
+ *(Boolean *)mem = api_object_to_bool(*value, field->str, false, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ } else if (field->type == kObjectTypeString) {
+ VALIDATE_T(field->str, kObjectTypeString, value->type, {
+ return false;
+ });
+ *(String *)mem = value->data.string;
+ } else if (field->type == kObjectTypeArray) {
+ VALIDATE_T(field->str, kObjectTypeArray, value->type, {
+ return false;
+ });
+ *(Array *)mem = value->data.array;
+ } else if (field->type == kObjectTypeDictionary) {
+ Dictionary *val = (Dictionary *)mem;
+ // allow empty array as empty dict for lua (directly or via lua-client RPC)
+ if (value->type == kObjectTypeArray && value->data.array.size == 0) {
+ *val = (Dictionary)ARRAY_DICT_INIT;
+ } else if (value->type == kObjectTypeDictionary) {
+ *val = value->data.dictionary;
+ } else {
+ api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ return false;
+ }
+ } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
+ || field->type == kObjectTypeTabpage) {
+ if (value->type == kObjectTypeInteger || value->type == field->type) {
+ *(handle_T *)mem = (handle_T)value->data.integer;
+ } else {
+ api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ return false;
+ }
+ } else if (field->type == kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s' is only allowed from Lua",
+ (int)k.size, k.data);
+ return false;
+ } else {
+ abort();
+ }
}
return true;
@@ -884,7 +1006,18 @@ bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err
void api_free_keydict(void *dict, KeySetLink *table)
{
for (size_t i = 0; table[i].str; i++) {
- api_free_object(*(Object *)((char *)dict + table[i].ptr_off));
+ char *mem = ((char *)dict + table[i].ptr_off);
+ if (table[i].type == kObjectTypeNil) {
+ api_free_object(*(Object *)mem);
+ } else if (table[i].type == kObjectTypeString) {
+ api_free_string(*(String *)mem);
+ } else if (table[i].type == kObjectTypeArray) {
+ api_free_array(*(Array *)mem);
+ } else if (table[i].type == kObjectTypeDictionary) {
+ api_free_dictionary(*(Dictionary *)mem);
+ } else if (table[i].type == kObjectTypeLuaRef) {
+ api_free_luaref(*(LuaRef *)mem);
+ }
}
}
@@ -930,12 +1063,14 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
}
/// Get default statusline highlight for window
-const char *get_default_stl_hl(win_T *wp, bool use_winbar)
+const char *get_default_stl_hl(win_T *wp, bool use_winbar, int stc_hl_id)
{
if (wp == NULL) {
return "TabLineFill";
} else if (use_winbar) {
return (wp == curwin) ? "WinBar" : "WinBarNC";
+ } else if (stc_hl_id > 0) {
+ return syn_id2name(stc_hl_id);
} else {
return (wp == curwin) ? "StatusLine" : "StatusLineNC";
}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index ec97ba9ec6..e61dd5f992 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -1,26 +1,28 @@
-#ifndef NVIM_API_PRIVATE_HELPERS_H
-#define NVIM_API_PRIVATE_HELPERS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/decoration.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_eval_defs.h"
#include "nvim/getchar.h"
+#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/message.h"
#define OBJECT_OBJ(o) o
#define BOOLEAN_OBJ(b) ((Object) { \
.type = kObjectTypeBoolean, \
.data.boolean = b })
-#define BOOL(b) BOOLEAN_OBJ(b)
#define INTEGER_OBJ(i) ((Object) { \
.type = kObjectTypeInteger, \
@@ -34,6 +36,7 @@
.type = kObjectTypeString, \
.data.string = s })
+#define CSTR_AS_OBJ(s) STRING_OBJ(cstr_as_string(s))
#define CSTR_TO_OBJ(s) STRING_OBJ(cstr_to_string(s))
#define BUFFER_OBJ(s) ((Object) { \
@@ -63,8 +66,9 @@
#define NIL ((Object)OBJECT_INIT)
#define NULL_STRING ((String)STRING_INIT)
-// currently treat key=vim.NIL as if the key was missing
-#define HAS_KEY(o) ((o).type != kObjectTypeNil)
+#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1 << KEYSET_OPTIDX_##typ##__##key)) != 0)
+
+#define GET_BOOL_OR_TRUE(d, typ, key) (HAS_KEY(d, typ, key) ? (d)->key : true)
#define PUT(dict, k, v) \
kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v }))
@@ -72,8 +76,6 @@
#define PUT_C(dict, k, v) \
kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v }))
-#define PUT_BOOL(dict, name, condition) PUT(dict, name, BOOLEAN_OBJ(condition));
-
#define ADD(array, item) \
kv_push(array, item)
@@ -94,7 +96,7 @@
#define cbuf_as_string(d, s) ((String) { .data = d, .size = s })
-#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 })
+#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof("" s) - 1 })
/// Create a new String instance, putting data in allocated memory
///
@@ -103,6 +105,9 @@
.data = xmemdupz(s, sizeof(s) - 1), \
.size = sizeof(s) - 1 })
+#define STATIC_CSTR_AS_OBJ(s) STRING_OBJ(STATIC_CSTR_AS_STRING(s))
+#define STATIC_CSTR_TO_OBJ(s) STRING_OBJ(STATIC_CSTR_TO_STRING(s))
+
// Helpers used by the generated msgpack-rpc api wrappers
#define api_init_boolean
#define api_init_integer
@@ -122,18 +127,18 @@
#define api_free_window(value)
#define api_free_tabpage(value)
-EXTERN PMap(handle_T) buffer_handles INIT(= MAP_INIT);
-EXTERN PMap(handle_T) window_handles INIT(= MAP_INIT);
-EXTERN PMap(handle_T) tabpage_handles INIT(= MAP_INIT);
+EXTERN PMap(int) buffer_handles INIT( = MAP_INIT);
+EXTERN PMap(int) window_handles INIT( = MAP_INIT);
+EXTERN PMap(int) tabpage_handles INIT( = MAP_INIT);
-#define handle_get_buffer(h) pmap_get(handle_T)(&buffer_handles, (h))
-#define handle_get_window(h) pmap_get(handle_T)(&window_handles, (h))
-#define handle_get_tabpage(h) pmap_get(handle_T)(&tabpage_handles, (h))
+#define handle_get_buffer(h) pmap_get(int)(&buffer_handles, (h))
+#define handle_get_window(h) pmap_get(int)(&window_handles, (h))
+#define handle_get_tabpage(h) pmap_get(int)(&tabpage_handles, (h))
/// Structure used for saving state for :try
///
-/// Used when caller is supposed to be operating when other VimL code is being
-/// processed and that “other VimL code†must not be affected.
+/// Used when caller is supposed to be operating when other Vimscript code is being
+/// processed and that “other Vimscript code†must not be affected.
typedef struct {
except_T *current_exception;
msglist_T *private_msg_list;
@@ -149,14 +154,26 @@ typedef struct {
// which would otherwise be ignored. This pattern is from do_cmdline().
//
// TODO(bfredl): prepare error-handling at "top level" (nv_event).
-#define TRY_WRAP(code) \
+#define TRY_WRAP(err, code) \
do { \
msglist_T **saved_msg_list = msg_list; \
msglist_T *private_msg_list; \
msg_list = &private_msg_list; \
private_msg_list = NULL; \
- code \
- msg_list = saved_msg_list; /* Restore the exception context. */ \
+ try_start(); \
+ code; \
+ try_end(err); \
+ msg_list = saved_msg_list; /* Restore the exception context. */ \
+ } while (0)
+
+// Execute code with cursor position saved and restored and textlock active.
+#define TEXTLOCK_WRAP(code) \
+ do { \
+ const pos_T save_cursor = curwin->w_cursor; \
+ textlock++; \
+ code; \
+ textlock--; \
+ curwin->w_cursor = save_cursor; \
} while (0)
// Useful macro for executing some `code` for each item in an array.
@@ -169,18 +186,17 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
-# include "keysets.h.generated.h"
#endif
#define WITH_SCRIPT_CONTEXT(channel_id, code) \
do { \
const sctx_T save_current_sctx = current_sctx; \
+ const uint64_t save_channel_id = current_channel_id; \
current_sctx.sc_sid = \
(channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \
current_sctx.sc_lnum = 0; \
current_channel_id = channel_id; \
code; \
+ current_channel_id = save_channel_id; \
current_sctx = save_current_sctx; \
} while (0);
-
-#endif // NVIM_API_PRIVATE_HELPERS_H
diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c
new file mode 100644
index 0000000000..e198c671eb
--- /dev/null
+++ b/src/nvim/api/private/validate.c
@@ -0,0 +1,76 @@
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/globals.h"
+
+/// Creates "Invalid …" message and sets it on `err`.
+void api_err_invalid(Error *err, const char *name, const char *val_s, int64_t val_n, bool quote_val)
+{
+ ErrorType errtype = kErrorTypeValidation;
+ // Treat `name` without whitespace as a parameter (surround in quotes).
+ // Treat `name` with whitespace as a description (no quotes).
+ char *has_space = strchr(name, ' ');
+
+ // No value.
+ if (val_s && val_s[0] == '\0') {
+ api_set_error(err, errtype, has_space ? "Invalid %s" : "Invalid '%s'", name);
+ return;
+ }
+
+ // Number value.
+ if (val_s == NULL) {
+ api_set_error(err, errtype, has_space ? "Invalid %s: %" PRId64 : "Invalid '%s': %" PRId64,
+ name, val_n);
+ return;
+ }
+
+ // String value.
+ if (has_space) {
+ api_set_error(err, errtype, quote_val ? "Invalid %s: '%s'" : "Invalid %s: %s", name, val_s);
+ } else {
+ api_set_error(err, errtype, quote_val ? "Invalid '%s': '%s'" : "Invalid '%s': %s", name, val_s);
+ }
+}
+
+/// Creates "Invalid …: expected …" message and sets it on `err`.
+void api_err_exp(Error *err, const char *name, const char *expected, const char *actual)
+{
+ ErrorType errtype = kErrorTypeValidation;
+ // Treat `name` without whitespace as a parameter (surround in quotes).
+ // Treat `name` with whitespace as a description (no quotes).
+ char *has_space = strchr(name, ' ');
+
+ if (!actual) {
+ api_set_error(err, errtype,
+ has_space ? "Invalid %s: expected %s" : "Invalid '%s': expected %s",
+ name, expected);
+ return;
+ }
+
+ api_set_error(err, errtype,
+ has_space ? "Invalid %s: expected %s, got %s" : "Invalid '%s': expected %s, got %s",
+ name, expected, actual);
+}
+
+bool check_string_array(Array arr, char *name, bool disallow_nl, Error *err)
+{
+ snprintf(IObuff, sizeof(IObuff), "'%s' item", name);
+ for (size_t i = 0; i < arr.size; i++) {
+ VALIDATE_T(IObuff, kObjectTypeString, arr.items[i].type, {
+ return false;
+ });
+ // Disallow newlines in the middle of the line.
+ if (disallow_nl) {
+ const String l = arr.items[i].data.string;
+ VALIDATE(!memchr(l.data, NL, l.size), "'%s' item contains newlines", name, {
+ return false;
+ });
+ }
+ }
+ return true;
+}
diff --git a/src/nvim/api/private/validate.h b/src/nvim/api/private/validate.h
new file mode 100644
index 0000000000..d1c977cd6e
--- /dev/null
+++ b/src/nvim/api/private/validate.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/assert_defs.h"
+#include "nvim/macros_defs.h"
+
+#define VALIDATE(cond, fmt_, fmt_arg1, code) \
+ do { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, fmt_, fmt_arg1); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_INT(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, NULL, val_, false); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_S(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, val_, 0, true); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_EXP(cond, name, expected, actual, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_exp(err, name, expected, actual); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_T(name, expected_t, actual_t, code) \
+ do { \
+ STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ if (expected_t != actual_t) { \
+ api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that `obj_` has type `expected_t`.
+#define VALIDATE_T2(obj_, expected_t, code) \
+ do { \
+ STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ if ((obj_).type != expected_t) { \
+ api_err_exp(err, STR(obj_), api_typename(expected_t), api_typename((obj_).type)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that `obj_` has Dict type. Also allows empty Array in a Lua context.
+#define VALIDATE_T_DICT(name, obj_, code) \
+ do { \
+ if ((obj_).type != kObjectTypeDictionary \
+ && !(channel_id == LUA_INTERNAL_CALL \
+ && (obj_).type == kObjectTypeArray \
+ && (obj_).data.array.size == 0)) { \
+ api_err_exp(err, name, api_typename(kObjectTypeDictionary), api_typename((obj_).type)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that actual_t is either the correct handle type or a type erased handle (integer)
+#define VALIDATE_T_HANDLE(name, expected_t, actual_t, code) \
+ do { \
+ if (expected_t != actual_t && kObjectTypeInteger != actual_t) { \
+ api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_RANGE(cond, name, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, "out of range", 0, false); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_R(cond, name, code) \
+ VALIDATE(cond, "Required: '%s'", name, code);
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/validate.h.generated.h"
+#endif
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 21eb326c3b..c854a22477 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdlib.h>
@@ -9,6 +6,7 @@
#include "nvim/api/tabpage.h"
#include "nvim/api/vim.h"
#include "nvim/buffer_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
#include "nvim/window.h"
diff --git a/src/nvim/api/tabpage.h b/src/nvim/api/tabpage.h
index 2689cf6ae6..2f19845bd9 100644
--- a/src/nvim/api/tabpage.h
+++ b/src/nvim/api/tabpage.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_API_TABPAGE_H
-#define NVIM_API_TABPAGE_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/tabpage.h.generated.h"
#endif
-#endif // NVIM_API_TABPAGE_H
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index e67607a7e4..836a68546c 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -1,10 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <msgpack/pack.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -12,25 +10,27 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/autocmd.h"
#include "nvim/channel.h"
+#include "nvim/eval.h"
#include "nvim/event/loop.h"
#include "nvim/event/wstream.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/option.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf))
@@ -72,6 +72,11 @@ static void mpack_uint(char **buf, uint32_t val)
}
}
+static void mpack_bool(char **buf, bool val)
+{
+ mpack_w(buf, 0xc2 | (val ? 1 : 0));
+}
+
static void mpack_array(char **buf, uint32_t len)
{
if (len < 0x10) {
@@ -110,23 +115,32 @@ void remote_ui_disconnect(uint64_t channel_id)
}
UIData *data = ui->data;
kv_destroy(data->call_buf);
- pmap_del(uint64_t)(&connected_uis, channel_id);
+ pmap_del(uint64_t)(&connected_uis, channel_id, NULL);
ui_detach_impl(ui, channel_id);
+
+ // Destroy `ui`.
+ XFREE_CLEAR(ui->term_name);
xfree(ui);
}
-/// Wait until ui has connected on stdio channel.
-void remote_ui_wait_for_attach(void)
+/// Wait until ui has connected on stdio channel if only_stdio
+/// is true, otherwise any channel.
+void remote_ui_wait_for_attach(bool only_stdio)
{
- Channel *channel = find_channel(CHAN_STDIO);
- if (!channel) {
- // this function should only be called in --embed mode, stdio channel
- // can be assumed.
- abort();
- }
+ if (only_stdio) {
+ Channel *channel = find_channel(CHAN_STDIO);
+ if (!channel) {
+ // this function should only be called in --embed mode, stdio channel
+ // can be assumed.
+ abort();
+ }
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
- pmap_has(uint64_t)(&connected_uis, CHAN_STDIO));
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
+ map_has(uint64_t, &connected_uis, CHAN_STDIO));
+ } else {
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, -1,
+ ui_active());
+ }
}
/// Activates UI events on the channel.
@@ -148,7 +162,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI already attached to channel: %" PRId64, channel_id);
return;
@@ -162,15 +176,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_nlines = 0;
- ui->pum_pos = false;
- ui->pum_width = 0.0;
- ui->pum_height = 0.0;
ui->pum_row = -1.0;
ui->pum_col = -1.0;
ui->rgb = true;
- ui->override = false;
-
CLEAR_FIELD(ui->ui_ext);
for (size_t i = 0; i < options.size; i++) {
@@ -202,6 +210,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
data->flushed_events = false;
data->ncalls_pos = NULL;
data->ncalls = 0;
+ data->ncells_pending = 0;
data->buf_wptr = data->buf;
data->temp_buf = NULL;
data->wildmenu_active = false;
@@ -210,6 +219,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
ui_attach_impl(ui, channel_id);
+
+ may_trigger_vim_suspend_resume(false);
}
/// @deprecated
@@ -226,12 +237,16 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl
void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
}
+ if (gained) {
+ may_trigger_vim_suspend_resume(false);
+ }
+
do_autocmd_focusgained((bool)gained);
}
@@ -244,7 +259,7 @@ void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
void nvim_ui_detach(uint64_t channel_id, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -252,14 +267,15 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
remote_ui_disconnect(channel_id);
}
-// TODO(bfredl): use me to detach a specifc ui from the server
+// TODO(bfredl): use me to detach a specific ui from the server
void remote_ui_stop(UI *ui)
-{}
+{
+}
void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -280,7 +296,7 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Erro
void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *error)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -290,22 +306,20 @@ void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *e
ui_set_option(ui, false, name, value, error);
}
-static void ui_set_option(UI *ui, bool init, String name, Object value, Error *error)
+static void ui_set_option(UI *ui, bool init, String name, Object value, Error *err)
{
if (strequal(name.data, "override")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "override must be a Boolean");
+ VALIDATE_T("override", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->override = value.data.boolean;
return;
}
if (strequal(name.data, "rgb")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
+ VALIDATE_T("rgb", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->rgb = value.data.boolean;
// A little drastic, but only takes effect for legacy uis. For linegrid UI
// only changes metadata for nvim_list_uis(), no refresh needed.
@@ -316,63 +330,53 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
if (strequal(name.data, "term_name")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_name must be a String");
+ VALIDATE_T("term_name", kObjectTypeString, value.type, {
return;
- }
+ });
set_tty_option("term", string_to_cstr(value.data.string));
+ ui->term_name = string_to_cstr(value.data.string);
return;
}
if (strequal(name.data, "term_colors")) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(error, kErrorTypeValidation, "term_colors must be a Integer");
+ VALIDATE_T("term_colors", kObjectTypeInteger, value.type, {
return;
- }
+ });
t_colors = (int)value.data.integer;
- return;
- }
-
- if (strequal(name.data, "term_background")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_background must be a String");
- return;
- }
- set_tty_background(value.data.string.data);
+ ui->term_colors = (int)value.data.integer;
return;
}
if (strequal(name.data, "stdin_fd")) {
- if (value.type != kObjectTypeInteger || value.data.integer < 0) {
- api_set_error(error, kErrorTypeValidation, "stdin_fd must be a non-negative Integer");
+ VALIDATE_T("stdin_fd", kObjectTypeInteger, value.type, {
return;
- }
-
- if (starting != NO_SCREEN) {
- api_set_error(error, kErrorTypeValidation,
- "stdin_fd can only be used with first attached ui");
+ });
+ VALIDATE_INT((value.data.integer >= 0), "stdin_fd", value.data.integer, {
return;
- }
+ });
+ VALIDATE((starting == NO_SCREEN), "%s", "stdin_fd can only be used with first attached UI", {
+ return;
+ });
stdin_fd = (int)value.data.integer;
return;
}
if (strequal(name.data, "stdin_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean");
+ VALIDATE_T("stdin_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdin_isatty = value.data.boolean;
+ ui->stdin_tty = value.data.boolean;
return;
}
if (strequal(name.data, "stdout_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean");
+ VALIDATE_T("stdout_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdout_isatty = value.data.boolean;
+ ui->stdout_tty = value.data.boolean;
return;
}
@@ -382,17 +386,15 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (strequal(name.data, ui_ext_names[i])
|| (i == kUIPopupmenu && is_popupmenu)) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "%s must be a Boolean",
- name.data);
+ VALIDATE_EXP((value.type == kObjectTypeBoolean), name.data, "Boolean",
+ api_typename(value.type), {
return;
- }
+ });
bool boolval = value.data.boolean;
if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
- // There shouldn't be a reason for an UI to do this ever
+ // There shouldn't be a reason for a UI to do this ever
// so explicitly don't support this.
- api_set_error(error, kErrorTypeValidation,
- "ext_linegrid option cannot be changed");
+ api_set_error(err, kErrorTypeValidation, "ext_linegrid option cannot be changed");
}
ui->ui_ext[i] = boolval;
if (!init) {
@@ -402,8 +404,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
}
- api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
- name.data);
+ api_set_error(err, kErrorTypeValidation, "No such UI option: %s", name.data);
}
/// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested
@@ -420,7 +421,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -442,7 +443,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -483,7 +484,7 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
Error *err)
FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -511,6 +512,33 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
ui->pum_pos = true;
}
+/// Tells Nvim when a terminal event has occurred
+///
+/// The following terminal events are supported:
+///
+/// - "termresponse": The terminal sent an OSC or DCS response sequence to
+/// Nvim. The payload is the received response. Sets
+/// |v:termresponse| and fires |TermResponse|.
+///
+/// @param channel_id
+/// @param event Event name
+/// @param payload Event payload
+/// @param[out] err Error details, if any.
+void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *err)
+ FUNC_API_SINCE(12) FUNC_API_REMOTE_ONLY
+{
+ if (strequal("termresponse", event.data)) {
+ if (value.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "termresponse must be a string");
+ return;
+ }
+
+ const String termresponse = value.data.string;
+ set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size);
+ apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value);
+ }
+}
+
static void flush_event(UIData *data)
{
if (data->cur_event) {
@@ -644,6 +672,8 @@ void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
Array args = data->call_buf;
if (ui->ui_ext[kUILinegrid]) {
ADD_C(args, INTEGER_OBJ(grid));
+ } else {
+ data->client_col = -1; // force cursor update
}
ADD_C(args, INTEGER_OBJ(width));
ADD_C(args, INTEGER_OBJ(height));
@@ -731,8 +761,8 @@ void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cte
ADD_C(args, INTEGER_OBJ(id));
MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE);
MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb, rgb_attrs, true);
- hlattrs2dict(&cterm, rgb_attrs, false);
+ hlattrs2dict(&rgb, NULL, rgb_attrs, true, false);
+ hlattrs2dict(&cterm, NULL, rgb_attrs, false, false);
ADD_C(args, DICTIONARY_OBJ(rgb));
ADD_C(args, DICTIONARY_OBJ(cterm));
@@ -755,7 +785,7 @@ void remote_ui_highlight_set(UI *ui, int id)
}
data->hl_id = id;
MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE);
- hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb);
+ hlattrs2dict(&dict, NULL, syn_attr2entry(id), ui->rgb, false);
ADD_C(args, DICTIONARY_OBJ(dict));
push_call(ui, "highlight_set", args);
}
@@ -798,7 +828,7 @@ void remote_ui_put(UI *ui, const char *cell)
UIData *data = ui->data;
data->client_col++;
Array args = data->call_buf;
- ADD_C(args, STRING_OBJ(cstr_as_string((char *)cell)));
+ ADD_C(args, CSTR_AS_OBJ((char *)cell));
push_call(ui, "put", args);
}
@@ -812,7 +842,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
data->ncalls++;
char **buf = &data->buf_wptr;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol);
@@ -822,21 +852,24 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
size_t ncells = (size_t)(endcol - startcol);
int last_hl = -1;
uint32_t nelem = 0;
+ bool was_space = false;
for (size_t i = 0; i < ncells; i++) {
repeat++;
- if (i == ncells - 1 || attrs[i] != attrs[i + 1]
- || strcmp(chunk[i], chunk[i + 1]) != 0) {
- if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) {
+ if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
+ if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) {
// close to overflowing the redraw buffer. finish this event,
// flush, and start a new "grid_line" event at the current position.
// For simplicity leave place for the final "clear" element
// as well, hence the factor of 2 in the check.
mpack_w2(&lenpos, nelem);
+
+ // We only ever set the wrap field on the final "grid_line" event for the line.
+ mpack_bool(buf, false);
remote_ui_flush_buf(ui);
prepare_call(ui, "grid_line");
data->ncalls++;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol + (uint32_t)i - repeat + 1);
@@ -847,31 +880,46 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1);
nelem++;
mpack_array(buf, csize);
- mpack_str(buf, (const char *)chunk[i]);
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ mpack_str(buf, sc_buf);
if (csize >= 2) {
mpack_uint(buf, (uint32_t)attrs[i]);
if (csize >= 3) {
mpack_uint(buf, repeat);
}
}
+ data->ncells_pending += MIN(repeat, 2);
last_hl = attrs[i];
repeat = 0;
+ was_space = chunk[i] == schar_from_ascii(' ');
}
}
- if (endcol < clearcol) {
+ // If the last chunk was all spaces, add a clearing chunk even if there are
+ // no more cells to clear, so there is no ambiguity about what to clear.
+ if (endcol < clearcol || was_space) {
nelem++;
+ data->ncells_pending += 1;
mpack_array(buf, 3);
mpack_str(buf, " ");
mpack_uint(buf, (uint32_t)clearattr);
mpack_uint(buf, (uint32_t)(clearcol - endcol));
}
mpack_w2(&lenpos, nelem);
+ mpack_bool(buf, flags & kLineFlagWrap);
+
+ if (data->ncells_pending > 500) {
+ // pass off cells to UI to let it start processing them
+ remote_ui_flush_buf(ui);
+ }
} else {
for (int i = 0; i < endcol - startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol + i);
remote_ui_highlight_set(ui, attrs[i]);
- remote_ui_put(ui, (const char *)chunk[i]);
- if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ remote_ui_put(ui, sc_buf);
+ if (utf_ambiguous_width(utf_ptr2char(sc_buf))) {
data->client_col = -1; // force cursor update
}
}
@@ -917,6 +965,8 @@ void remote_ui_flush_buf(UI *ui)
// we have sent events to the client, but possibly not yet the final "flush"
// event.
data->flushed_events = true;
+
+ data->ncells_pending = 0;
}
/// An intentional flush (vsync) when Nvim is finished redrawing the screen
@@ -945,7 +995,7 @@ static Array translate_contents(UI *ui, Array contents, Arena *arena)
int attr = (int)item.items[0].data.integer;
if (attr) {
Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb_attrs, syn_attr2entry(attr), ui->rgb);
+ hlattrs2dict(&rgb_attrs, NULL, syn_attr2entry(attr), ui->rgb, false);
ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h
index b3fe0fa2bb..26a91d0dbc 100644
--- a/src/nvim/api/ui.h
+++ b/src/nvim/api/ui.h
@@ -1,14 +1,14 @@
-#ifndef NVIM_API_UI_H
-#define NVIM_API_UI_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/map.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/highlight_defs.h" // IWYU pragma: keep
+#include "nvim/map_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.h.generated.h"
-# include "ui_events_remote.h.generated.h"
+# include "ui_events_remote.h.generated.h" // IWYU pragma: export
#endif
-#endif // NVIM_API_UI_H
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index a08e8dbfeb..bda0c72423 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_API_UI_EVENTS_IN_H
-#define NVIM_API_UI_EVENTS_IN_H
+#pragma once
// This file is not compiled, just parsed for definitions
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -82,7 +81,7 @@ void grid_clear(Integer grid)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_cursor_goto(Integer grid, Integer row, Integer col)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
-void grid_line(Integer grid, Integer row, Integer col_start, Array data)
+void grid_line(Integer grid, Integer row, Integer col_start, Array data, Boolean wrap)
FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL;
void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows,
Integer cols)
@@ -114,7 +113,7 @@ void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IGNORE;
void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline,
- Integer curcol, Integer line_count)
+ Integer curcol, Integer line_count, Integer scroll_delta)
FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE;
void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col)
@@ -167,4 +166,6 @@ void msg_history_show(Array entries)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_history_clear(void)
FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
-#endif // NVIM_API_UI_EVENTS_IN_H
+
+void error_exit(Integer status)
+ FUNC_API_SINCE(12);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index a53b30dd8a..d631b10af9 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1,8 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@@ -11,25 +9,29 @@
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/vim.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/channel.h"
#include "nvim/context.h"
+#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
@@ -38,7 +40,7 @@
#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -47,24 +49,24 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/process.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
+#include "nvim/sign.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#define LINE_BUFFER_MIN_SIZE 4096
@@ -73,44 +75,6 @@
# include "api/vim.c.generated.h"
#endif
-/// Gets a highlight definition by name.
-///
-/// @param name Highlight group name
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_id
-Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
- FUNC_API_SINCE(3)
-{
- Dictionary result = ARRAY_DICT_INIT;
- int id = syn_name2id(name.data);
-
- if (id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
- return result;
- }
- return nvim_get_hl_by_id(id, rgb, arena, err);
-}
-
-/// Gets a highlight definition by id. |hlID()|
-/// @param hl_id Highlight id as returned by |hlID()|
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_name
-Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
- FUNC_API_SINCE(3)
-{
- Dictionary dic = ARRAY_DICT_INIT;
- if (syn_get_final_id((int)hl_id) == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight id: %" PRId64, hl_id);
- return dic;
- }
- int attrcode = syn_id2attr((int)hl_id);
- return hl_get_attr_by_id(attrcode, rgb, arena, err);
-}
-
/// Gets a highlight group by name
///
/// similar to |hlID()|, but allocates a new ID if not present.
@@ -120,12 +84,26 @@ Integer nvim_get_hl_id_by_name(String name)
return syn_check_group(name.data, name.size);
}
-Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
+/// Gets all or specific highlight groups in a namespace.
+///
+/// @note When the `link` attribute is defined in the highlight definition
+/// map, other attributes will not be taking effect (see |:hi-link|).
+///
+/// @param ns_id Get highlight groups for namespace ns_id |nvim_get_namespaces()|.
+/// Use 0 to get global highlight groups |:highlight|.
+/// @param opts Options dict:
+/// - name: (string) Get a highlight definition by name.
+/// - id: (integer) Get a highlight definition by id.
+/// - link: (boolean, default true) Show linked group name instead of effective definition |:hi-link|.
+/// - create: (boolean, default true) When highlight group doesn't exist create it.
+///
+/// @param[out] err Error details, if any.
+/// @return Highlight groups as a map from group name to a highlight definition map as in |nvim_set_hl()|,
+/// or only a single highlight definition map if requested by name or id.
+Dictionary nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
+ FUNC_API_SINCE(11)
{
- if (ns_id == 0) {
- return get_global_hl_defs(arena);
- }
- abort();
+ return ns_get_hl_defs((NS)ns_id, opts, arena, err);
}
/// Sets a highlight group.
@@ -140,8 +118,14 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// values of the Normal group. If the Normal group has not been defined,
/// using these values results in an error.
///
+///
+/// @note If `link` is used in combination with other attributes; only the
+/// `link` will take effect (see |:hi-link|).
+///
/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|.
/// Use 0 to set a highlight group globally |:highlight|.
+/// Highlights from non-global namespaces are not active by default, use
+/// |nvim_set_hl_ns()| or |nvim_win_set_hl_ns()| to activate them.
/// @param name Highlight group name, e.g. "ErrorMsg"
/// @param val Highlight definition map, accepts the following keys:
/// - fg (or foreground): color name or "#RRGGBB", see note.
@@ -166,6 +150,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// - cterm: cterm attribute map, like |highlight-args|. If not set,
/// cterm attributes will match those from the attribute map
/// documented above.
+/// - force: if true force update the highlight group when it exists.
/// @param[out] err Error details, if any
///
// TODO(bfredl): val should take update vs reset flag
@@ -173,10 +158,9 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
FUNC_API_SINCE(7)
{
int hl_id = syn_check_group(name.data, name.size);
- if (hl_id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
+ VALIDATE_S((hl_id != 0), "highlight name", name.data, {
return;
- }
+ });
int link_id = -1;
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
@@ -185,25 +169,47 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
}
}
-/// Set active namespace for highlights. This can be set for a single window,
-/// see |nvim_win_set_hl_ns()|.
+/// Gets the active highlight namespace.
+///
+/// @param opts Optional parameters
+/// - winid: (number) |window-ID| for retrieving a window's highlight
+/// namespace. A value of -1 is returned when |nvim_win_set_hl_ns()|
+/// has not been called for the window (or was called with a namespace
+/// of -1).
+/// @param[out] err Error details, if any
+/// @return Namespace id, or -1
+Integer nvim_get_hl_ns(Dict(get_ns) *opts, Error *err)
+ FUNC_API_SINCE(12)
+{
+ if (HAS_KEY(opts, get_ns, winid)) {
+ win_T *win = find_window_by_handle(opts->winid, err);
+ if (!win) {
+ return 0;
+ }
+ return win->w_ns_hl;
+ } else {
+ return ns_hl_global;
+ }
+}
+
+/// Set active namespace for highlights defined with |nvim_set_hl()|. This can be set for
+/// a single window, see |nvim_win_set_hl_ns()|.
///
/// @param ns_id the namespace to use
/// @param[out] err Error details, if any
void nvim_set_hl_ns(Integer ns_id, Error *err)
FUNC_API_SINCE(10)
{
- if (ns_id < 0) {
- api_set_error(err, kErrorTypeValidation, "no such namespace");
+ VALIDATE_INT((ns_id >= 0), "namespace", ns_id, {
return;
- }
+ });
ns_hl_global = (NS)ns_id;
hl_check_ns();
redraw_all_later(UPD_NOT_VALID);
}
-/// Set active namespace for highlights while redrawing.
+/// Set active namespace for highlights defined with |nvim_set_hl()| while redrawing.
///
/// This function meant to be called while redrawing, primarily from
/// |nvim_set_decoration_provider()| on_win and on_line callbacks, which
@@ -229,10 +235,11 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err)
/// nvim_feedkeys().
///
/// Example:
-/// <pre>vim
-/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
-/// :call nvim_feedkeys(key, 'n', v:false)
-/// </pre>
+///
+/// ```vim
+/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+/// :call nvim_feedkeys(key, 'n', v:false)
+/// ```
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
@@ -323,6 +330,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
Integer nvim_input(String keys)
FUNC_API_SINCE(1) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
return (Integer)input_enqueue(keys);
}
@@ -352,6 +360,8 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
Integer col, Error *err)
FUNC_API_SINCE(6) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
+
if (button.data == NULL || action.data == NULL) {
goto error;
}
@@ -403,11 +413,9 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
continue;
}
int mod = name_to_mod_mask(byte);
- if (mod == 0) {
- api_set_error(err, kErrorTypeValidation,
- "invalid modifier %c", byte);
+ VALIDATE((mod != 0), "Invalid modifier: %c", byte, {
return;
- }
+ });
modmask |= mod;
}
@@ -448,7 +456,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
}
char *ptr = NULL;
- replace_termcodes(str.data, str.size, &ptr, flags, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(str.data, str.size, &ptr, 0, flags, NULL, CPO_TO_CPO_FLAGS);
return cstr_as_string(ptr);
}
@@ -500,15 +508,14 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
Integer nvim_strwidth(String text, Error *err)
FUNC_API_SINCE(1)
{
- if (text.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "String is too long");
+ VALIDATE_S((text.size <= INT_MAX), "text length", "(too long)", {
return 0;
- }
+ });
return (Integer)mb_string2cells(text.data);
}
-/// Gets the paths contained in 'runtimepath'.
+/// Gets the paths contained in |runtime-search-path|.
///
/// @return List of paths
ArrayOf(String) nvim_list_runtime_paths(Error *err)
@@ -542,20 +549,23 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &rv);
- try_end(err);
});
return rv;
}
-static void find_runtime_cb(char *fname, void *cookie)
+static bool find_runtime_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
Array *rv = (Array *)cookie;
- if (fname != NULL) {
- ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+ for (int i = 0; i < num_fnames; i++) {
+ ADD(*rv, CSTR_TO_OBJ(fnames[i]));
+ if (!all) {
+ return true;
+ }
}
+
+ return num_fnames > 0;
}
String nvim__get_lib_dir(void)
@@ -567,28 +577,24 @@ String nvim__get_lib_dir(void)
///
/// @param pat pattern of files to search for
/// @param all whether to return all matches or only the first
-/// @param opts is_lua: only search lua subdirs
+/// @param opts is_lua: only search Lua subdirs
/// @return list of absolute paths to the found files
ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err)
FUNC_API_SINCE(8)
FUNC_API_FAST
{
- bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
- bool source = api_object_to_bool(opts->do_source, "do_source", false, err);
- if (source && !nlua_is_deferred_safe()) {
- api_set_error(err, kErrorTypeValidation, "'do_source' cannot be used in fast callback");
- }
-
+ VALIDATE(!opts->do_source || nlua_is_deferred_safe(), "%s", "'do_source' used in fast callback",
+ {});
if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT;
}
- ArrayOf(String) res = runtime_get_named(is_lua, pat, all);
+ ArrayOf(String) res = runtime_get_named(opts->is_lua, pat, all);
- if (source) {
+ if (opts->do_source) {
for (size_t i = 0; i < res.size; i++) {
String name = res.items[i].data.string;
- (void)do_source(name.data, false, DOSO_NONE);
+ (void)do_source(name.data, false, DOSO_NONE, NULL);
}
}
@@ -602,10 +608,9 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
void nvim_set_current_dir(String dir, Error *err)
FUNC_API_SINCE(1)
{
- if (dir.size >= MAXPATHL) {
- api_set_error(err, kErrorTypeValidation, "Directory name is too long");
+ VALIDATE_S((dir.size < MAXPATHL), "directory name", "(too long)", {
return;
- }
+ });
char string[MAXPATHL];
memcpy(string, dir.data, dir.size);
@@ -639,7 +644,7 @@ String nvim_get_current_line(Error *err)
/// @param[out] err Error details, if any
void nvim_set_current_line(String line, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);
}
@@ -649,7 +654,7 @@ void nvim_set_current_line(String line, Error *err)
/// @param[out] err Error details, if any
void nvim_del_current_line(Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
}
@@ -664,16 +669,15 @@ Object nvim_get_var(String name, Error *err)
{
dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
if (di == NULL) { // try to autoload script
- if (!script_autoload(name.data, name.size, false) || aborting()) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ bool found = script_autoload(name.data, name.size, false) && !aborting();
+ VALIDATE(found, "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
}
- if (di == NULL) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ VALIDATE((di != NULL), "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
return vim_to_object(&di->di_tv);
}
@@ -738,15 +742,13 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err)
goto error;
}
- bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err);
-
- if (verbose) {
+ if (opts->verbose) {
verbose_enter();
}
msg_multiattr(hl_msg, history ? "echomsg" : "echo", history);
- if (verbose) {
+ if (opts->verbose) {
verbose_leave();
verbose_stop(); // flush now
}
@@ -767,7 +769,7 @@ error:
void nvim_out_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, false);
+ write_msg(str, false, false);
}
/// Writes a message to the Vim error buffer. Does not append "\n", the
@@ -777,7 +779,7 @@ void nvim_out_write(String str)
void nvim_err_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, true);
+ write_msg(str, true, false);
}
/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is
@@ -788,8 +790,7 @@ void nvim_err_write(String str)
void nvim_err_writeln(String str)
FUNC_API_SINCE(1)
{
- nvim_err_write(str);
- nvim_err_write((String) { .data = "\n", .size = 1 });
+ write_msg(str, true, true);
}
/// Gets the current list of buffer handles
@@ -832,7 +833,7 @@ Buffer nvim_get_current_buf(void)
/// @param[out] err Error details, if any
void nvim_set_current_buf(Buffer buffer, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -887,7 +888,7 @@ Window nvim_get_current_win(void)
/// @param[out] err Error details, if any
void nvim_set_current_win(Window window, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
win_T *win = find_window_by_handle(window, err);
@@ -918,7 +919,7 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
FUNC_API_SINCE(6)
{
try_start();
- buf_T *buf = buflist_new(NULL, NULL, (linenr_T)0,
+ buf_T *buf = buflist_new(NULL, NULL, 0,
BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
try_end(err);
if (buf == NULL) {
@@ -936,14 +937,23 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
goto fail;
}
+ // Set last_changedtick to avoid triggering a TextChanged autocommand right
+ // after it was added.
+ buf->b_last_changedtick = buf_get_changedtick(buf);
+ buf->b_last_changedtick_i = buf_get_changedtick(buf);
+ buf->b_last_changedtick_pum = buf_get_changedtick(buf);
+
+ // Only strictly needed for scratch, but could just as well be consistent
+ // and do this now. buffer is created NOW, not when it latter first happen
+ // to reach a window or aucmd_prepbuf() ..
+ buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
+
if (scratch) {
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
- set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
- set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
- set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
- set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
- aucmd_restbuf(&aco);
+ set_string_option_direct_in_buf(buf, "bufhidden", -1, "hide", OPT_LOCAL, 0);
+ set_string_option_direct_in_buf(buf, "buftype", -1, "nofile", OPT_LOCAL, 0);
+ assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already
+ buf->b_p_swf = false;
+ buf->b_p_ml = false;
}
return buf->b_fnum;
@@ -970,7 +980,7 @@ fail:
///
/// @param buffer the buffer to use (expected to be empty)
/// @param opts Optional parameters.
-/// - on_input: lua callback for input sent, i e keypresses in terminal
+/// - on_input: Lua callback for input sent, i e keypresses in terminal
/// mode. Note: keypresses are sent raw as they would be to the pty
/// master end. For instance, a carriage return is sent
/// as a "\r", not as a "\n". |textlock| applies. It is possible
@@ -980,27 +990,31 @@ fail:
/// @return Channel id, or 0 on error
Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
}
+ if (cmdwin_type != 0 && buf == curbuf) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return 0;
+ }
+
LuaRef cb = LUA_NOREF;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("on_input", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", "on_input");
+ VALIDATE_T("on_input", kObjectTypeLuaRef, v->type, {
return 0;
- }
+ });
cb = v->data.luaref;
v->data.luaref = LUA_NOREF;
break;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ VALIDATE_S(false, "'opts' key", k.data, {});
}
}
@@ -1016,9 +1030,12 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- Terminal *term = terminal_open(buf, topts);
- terminal_check_size(term);
- chan->term = term;
+ channel_incref(chan);
+ terminal_open(&chan->term, buf, topts);
+ if (chan->term != NULL) {
+ terminal_check_size(chan->term);
+ }
+ channel_decref(chan);
return (Integer)chan->id;
}
@@ -1040,7 +1057,7 @@ static void term_write(char *buf, size_t size, void *data) // NOLINT(readabilit
static void term_resize(uint16_t width, uint16_t height, void *data)
{
- // TODO(bfredl): lua callback
+ // TODO(bfredl): Lua callback
}
static void term_close(void *data)
@@ -1075,9 +1092,7 @@ void nvim_chan_send(Integer chan, String data, Error *err)
channel_send((uint64_t)chan, data.data, data.size,
false, &error);
- if (error) {
- api_set_error(err, kErrorTypeValidation, "%s", error);
- }
+ VALIDATE(!error, "%s", error, {});
}
/// Gets the current list of tabpage handles.
@@ -1117,7 +1132,7 @@ Tabpage nvim_get_current_tabpage(void)
/// @param[out] err Error details, if any
void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
tabpage_T *tp = find_tab_by_handle(tabpage, err);
@@ -1159,15 +1174,14 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
/// - false: Client must cancel the paste.
Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
static bool draining = false;
bool cancel = false;
- if (phase < -1 || phase > 3) {
- api_set_error(err, kErrorTypeValidation, "Invalid phase: %" PRId64, phase);
+ VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, {
return false;
- }
+ });
Array args = ARRAY_DICT_INIT;
Object rv = OBJECT_INIT;
if (phase == -1 || phase == 1) { // Start of paste-stream.
@@ -1231,23 +1245,20 @@ theend:
/// @param[out] err Error details, if any
void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
yankreg_T *reg = xcalloc(1, sizeof(yankreg_T));
- if (!prepare_yankreg_from_object(reg, type, lines.size)) {
- api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'", type.data);
+ VALIDATE_S((prepare_yankreg_from_object(reg, type, lines.size)), "type", type.data, {
goto cleanup;
- }
+ });
if (lines.size == 0) {
goto cleanup; // Nothing to do.
}
for (size_t i = 0; i < lines.size; i++) {
- if (lines.items[i].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid lines (expected array of strings)");
+ VALIDATE_T("line", kObjectTypeString, lines.items[i].type, {
goto cleanup;
- }
+ });
String line = lines.items[i].data.string;
reg->y_array[i] = xmemdupz(line.data, line.size);
memchrsub(reg->y_array[i], NUL, NL, line.size);
@@ -1255,14 +1266,12 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
finish_yankreg_from_object(reg, false);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
bool VIsual_was_active = VIsual_active;
msg_silent++; // Avoid "N more lines" message.
do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0);
msg_silent--;
VIsual_active = VIsual_was_active;
- try_end(err);
});
cleanup:
@@ -1291,9 +1300,9 @@ void nvim_subscribe(uint64_t channel_id, String event)
void nvim_unsubscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- size_t length = (event.size < METHOD_MAXLEN ?
- event.size :
- METHOD_MAXLEN);
+ size_t length = (event.size < METHOD_MAXLEN
+ ? event.size
+ : METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
@@ -1304,10 +1313,11 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
/// "#rrggbb" hexadecimal string.
///
/// Example:
-/// <pre>vim
-/// :echo nvim_get_color_by_name("Pink")
-/// :echo nvim_get_color_by_name("#cbcbcb")
-/// </pre>
+///
+/// ```vim
+/// :echo nvim_get_color_by_name("Pink")
+/// :echo nvim_get_color_by_name("#cbcbcb")
+/// ```
///
/// @param name Color name or "#rrggbb" string
/// @return 24-bit RGB value, or -1 for invalid argument.
@@ -1348,11 +1358,8 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
FUNC_API_SINCE(6)
{
Array types = ARRAY_DICT_INIT;
- if (opts->types.type == kObjectTypeArray) {
- types = opts->types.data.array;
- } else if (opts->types.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: types");
- return (Dictionary)ARRAY_DICT_INIT;
+ if (HAS_KEY(opts, context, types)) {
+ types = opts->types;
}
int int_types = types.size > 0 ? 0 : kCtxAll;
@@ -1373,8 +1380,9 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
} else if (strequal(s, "funcs")) {
int_types |= kCtxFuncs;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected type: %s", s);
- return (Dictionary)ARRAY_DICT_INIT;
+ VALIDATE_S(false, "type", s, {
+ return (Dictionary)ARRAY_DICT_INIT;
+ });
}
}
}
@@ -1390,7 +1398,7 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
/// Sets the current editor state from the given |context| map.
///
/// @param dict |Context| map.
-Object nvim_load_context(Dictionary dict)
+Object nvim_load_context(Dictionary dict, Error *err)
FUNC_API_SINCE(6)
{
Context ctx = CONTEXT_INIT;
@@ -1398,8 +1406,8 @@ Object nvim_load_context(Dictionary dict)
int save_did_emsg = did_emsg;
did_emsg = false;
- ctx_from_dict(dict, &ctx);
- if (!did_emsg) {
+ ctx_from_dict(dict, &ctx, err);
+ if (!ERROR_SET(err)) {
ctx_restore(&ctx, kCtxAll);
}
@@ -1421,7 +1429,7 @@ Dictionary nvim_get_mode(void)
get_mode(modestr);
bool blocked = input_blocking();
- PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr)));
+ PUT(rv, "mode", CSTR_TO_OBJ(modestr));
PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
return rv;
@@ -1446,29 +1454,31 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
/// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual.
///
/// Example:
-/// <pre>vim
-/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
-/// </pre>
+///
+/// ```vim
+/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+/// ```
///
/// is equivalent to:
-/// <pre>vim
-/// nmap <nowait> <Space><NL> <Nop>
-/// </pre>
+///
+/// ```vim
+/// nmap <nowait> <Space><NL> <Nop>
+/// ```
///
/// @param channel_id
/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
/// or "!" for |:map!|, or empty string for |:map|.
+/// "ia", "ca" or "!a" for abbreviation in Insert mode, Cmdline mode, or both, respectively
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param rhs Right-hand-side |{rhs}| of the mapping.
-/// @param opts Optional parameters map: keys are |:map-arguments|, values are booleans (default
-/// false). Accepts all |:map-arguments| as keys excluding |<buffer>| but including
-/// |:noremap| and "desc". Unknown key is an error.
-/// "desc" can be used to give a description to the mapping.
-/// When called from Lua, also accepts a "callback" key that takes a Lua function to
-/// call when the mapping is executed.
-/// When "expr" is true, "replace_keycodes" (boolean) can be used to replace keycodes
-/// in the resulting string (see |nvim_replace_termcodes()|), and a Lua callback
-/// returning `nil` is equivalent to returning an empty string.
+/// @param opts Optional parameters map: Accepts all |:map-arguments| as keys except |<buffer>|,
+/// values are booleans (default false). Also:
+/// - "noremap" disables |recursive_mapping|, like |:noremap|
+/// - "desc" human-readable description.
+/// - "callback" Lua function called in place of {rhs}.
+/// - "replace_keycodes" (boolean) When "expr" is true, replace keycodes in the
+/// resulting string (see |nvim_replace_termcodes()|). Returning nil from the Lua
+/// "callback" is equivalent to returning an empty string.
/// @param[out] err Error details, if any.
void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts,
Error *err)
@@ -1527,7 +1537,10 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
/// - "commit" hash or similar identifier of commit
/// @param type Must be one of the following values. Client libraries should
/// default to "remote" unless overridden by the user.
-/// - "remote" remote client connected to Nvim.
+/// - "remote" remote client connected "Nvim flavored" MessagePack-RPC (responses
+/// must be in reverse order of requests). |msgpack-rpc|
+/// - "msgpack-rpc" remote client connected to Nvim via fully MessagePack-RPC
+/// compliant protocol.
/// - "ui" gui frontend
/// - "embedder" application using Nvim as a component (for example,
/// IDE/editor implementing a vim mode).
@@ -1651,34 +1664,20 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er
size_t i; // also used for freeing the variables
for (i = 0; i < calls.size; i++) {
- if (calls.items[i].type != kObjectTypeArray) {
- api_set_error(err,
- kErrorTypeValidation,
- "Items in calls array must be arrays");
+ VALIDATE_T("'calls' item", kObjectTypeArray, calls.items[i].type, {
goto theend;
- }
+ });
Array call = calls.items[i].data.array;
- if (call.size != 2) {
- api_set_error(err,
- kErrorTypeValidation,
- "Items in calls array must be arrays of size 2");
+ VALIDATE_EXP((call.size == 2), "'calls' item", "2-item Array", NULL, {
goto theend;
- }
-
- if (call.items[0].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Name must be String");
+ });
+ VALIDATE_T("name", kObjectTypeString, call.items[0].type, {
goto theend;
- }
+ });
String name = call.items[0].data.string;
-
- if (call.items[1].type != kObjectTypeArray) {
- api_set_error(err,
- kErrorTypeValidation,
- "Args must be Array");
+ VALIDATE_T("call args", kObjectTypeArray, call.items[1].type, {
goto theend;
- }
+ });
Array args = call.items[1].data.array;
MsgpackRpcRequestHandler handler =
@@ -1726,34 +1725,44 @@ theend:
///
/// @param message Message to write
/// @param to_err true: message is an error (uses `emsg` instead of `msg`)
-static void write_msg(String message, bool to_err)
+/// @param writeln Append a trailing newline
+static void write_msg(String message, bool to_err, bool writeln)
{
static StringBuilder out_line_buf = KV_INITIAL_VALUE;
static StringBuilder err_line_buf = KV_INITIAL_VALUE;
+ StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf;
-#define PUSH_CHAR(i, line_buf, msg) \
- if (kv_max(line_buf) == 0) { \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
+#define PUSH_CHAR(c) \
+ if (kv_max(*line_buf) == 0) { \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
} \
- if (message.data[i] == NL) { \
- kv_push(line_buf, NUL); \
- msg(line_buf.items); \
- kv_drop(line_buf, kv_size(line_buf)); \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
- continue; \
- } \
- kv_push(line_buf, message.data[i]);
+ if (c == NL) { \
+ kv_push(*line_buf, NUL); \
+ if (to_err) { \
+ emsg(line_buf->items); \
+ } else { \
+ msg(line_buf->items, 0); \
+ } \
+ if (msg_silent == 0) { \
+ msg_didout = true; \
+ } \
+ kv_drop(*line_buf, kv_size(*line_buf)); \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
+ } else if (c == NUL) { \
+ kv_push(*line_buf, NL); \
+ } else { \
+ kv_push(*line_buf, c); \
+ }
no_wait_return++;
for (uint32_t i = 0; i < message.size; i++) {
if (got_int) {
break;
}
- if (to_err) {
- PUSH_CHAR(i, err_line_buf, emsg);
- } else {
- PUSH_CHAR(i, out_line_buf, msg);
- }
+ PUSH_CHAR(message.data[i]);
+ }
+ if (writeln) {
+ PUSH_CHAR(NL);
}
no_wait_return--;
msg_end();
@@ -1850,10 +1859,9 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array rvobj = ARRAY_DICT_INIT;
int *proc_list = NULL;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
goto end;
- }
+ });
size_t proc_count;
int rv = os_proc_children((int)pid, &proc_list, &proc_count);
@@ -1892,10 +1900,10 @@ Object nvim_get_proc(Integer pid, Error *err)
rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT;
rvobj.type = kObjectTypeDictionary;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
return NIL;
- }
+ });
+
#ifdef MSWIN
rvobj.data.dictionary = os_proc_info((int)pid);
if (rvobj.data.dictionary.size == 0) { // Process not found.
@@ -1937,10 +1945,9 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di
Error *err)
FUNC_API_SINCE(6)
{
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return;
- }
+ });
if (finish) {
insert = true;
@@ -1961,13 +1968,10 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
g = &pum_grid;
} else if (grid > 1) {
win_T *wp = get_win_by_grid_handle((handle_T)grid);
- if (wp != NULL && wp->w_grid_alloc.chars != NULL) {
- g = &wp->w_grid_alloc;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "No grid with the given handle");
+ VALIDATE_INT((wp != NULL && wp->w_grid_alloc.chars != NULL), "grid handle", grid, {
return ret;
- }
+ });
+ g = &wp->w_grid_alloc;
}
if (row < 0 || row >= g->rows
@@ -1976,7 +1980,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
}
ret = arena_array(arena, 3);
size_t off = g->line_offset[(size_t)row] + (size_t)col;
- ADD_C(ret, STRING_OBJ(cstr_as_string((char *)g->chars[off])));
+ char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false);
+ schar_get(sc_buf, g->chars[off]);
+ ADD_C(ret, CSTR_AS_OBJ(sc_buf));
int attr = g->attrs[off];
ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
@@ -1992,6 +1998,14 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
+/// For testing. The condition in schar_cache_clear_if_full is hard to
+/// reach, so this function can be used to force a cache clear in a test.
+void nvim__invalidate_glyph_cache(void)
+{
+ schar_cache_clear();
+ must_redraw = UPD_CLEAR;
+}
+
Object nvim__unpack(String str, Error *err)
FUNC_API_FAST
{
@@ -2000,7 +2014,7 @@ Object nvim__unpack(String str, Error *err)
/// Deletes an uppercase/file named mark. See |mark-motions|.
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @return true if the mark was deleted, else false.
/// @see |nvim_buf_del_mark()|
@@ -2009,29 +2023,26 @@ Boolean nvim_del_mark(String name, Error *err)
FUNC_API_SINCE(8)
{
bool res = false;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
// Only allow file/uppercase marks
// TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function
- if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) {
- res = set_mark(NULL, name, 0, 0, err);
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
- }
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
+ return res;
+ });
+ res = set_mark(NULL, name, 0, 0, err);
return res;
}
-/// Return a tuple (row, col, buffer, buffername) representing the position of
-/// the uppercase/file named mark. See |mark-motions|.
+/// Returns a `(row, col, buffer, buffername)` tuple representing the position
+/// of the uppercase/file named mark. "End of line" column position is returned
+/// as |v:maxcol| (big number). See |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @param opts Optional parameters. Reserved for future use.
/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
@@ -2043,16 +2054,13 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
{
Array rv = ARRAY_DICT_INIT;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return rv;
- } else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
+ });
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
return rv;
- }
+ });
xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer
pos_T pos = mark->fmark.mark;
@@ -2092,7 +2100,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
ADD(rv, INTEGER_OBJ(row));
ADD(rv, INTEGER_OBJ(col));
ADD(rv, INTEGER_OBJ(bufnr));
- ADD(rv, STRING_OBJ(cstr_to_string(filename)));
+ ADD(rv, CSTR_TO_OBJ(filename));
if (allocated) {
xfree(filename);
@@ -2113,6 +2121,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
/// - use_winbar: (boolean) Evaluate winbar instead of statusline.
/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid}
/// is ignored. Mutually exclusive with {use_winbar}.
+/// - use_statuscol_lnum: (number) Evaluate statuscolumn for this line number instead of statusline.
///
/// @param[out] err Error details, if any.
/// @return Dictionary containing statusline information, with these keys:
@@ -2130,106 +2139,117 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
int maxwidth;
int fillchar = 0;
+ int statuscol_lnum = 0;
Window window = 0;
- bool use_winbar = false;
- bool use_tabline = false;
- bool highlights = false;
if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) {
const char *const errmsg = check_stl_option(str.data);
- if (errmsg) {
- api_set_error(err, kErrorTypeValidation, "%s", errmsg);
+ VALIDATE(!errmsg, "%s", errmsg, {
return result;
- }
+ });
}
- if (HAS_KEY(opts->winid)) {
- if (opts->winid.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "winid must be an integer");
- return result;
- }
-
- window = (Window)opts->winid.data.integer;
+ if (HAS_KEY(opts, eval_statusline, winid)) {
+ window = opts->winid;
}
- if (HAS_KEY(opts->fillchar)) {
- if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
- || ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
- != opts->fillchar.data.string.size)) {
- api_set_error(err, kErrorTypeValidation, "fillchar must be a single character");
+ if (HAS_KEY(opts, eval_statusline, fillchar)) {
+ VALIDATE_EXP((*opts->fillchar.data != 0
+ && ((size_t)utf_ptr2len(opts->fillchar.data) == opts->fillchar.size)),
+ "fillchar", "single character", NULL, {
return result;
- }
- fillchar = utf_ptr2char(opts->fillchar.data.string.data);
+ });
+ fillchar = utf_ptr2char(opts->fillchar.data);
}
- if (HAS_KEY(opts->highlights)) {
- highlights = api_object_to_bool(opts->highlights, "highlights", false, err);
- if (ERROR_SET(err)) {
- return result;
- }
- }
- if (HAS_KEY(opts->use_winbar)) {
- use_winbar = api_object_to_bool(opts->use_winbar, "use_winbar", false, err);
+ int use_bools = (int)opts->use_winbar + (int)opts->use_tabline;
- if (ERROR_SET(err)) {
- return result;
- }
+ win_T *wp = opts->use_tabline ? curwin : find_window_by_handle(window, err);
+ if (wp == NULL) {
+ api_set_error(err, kErrorTypeException, "unknown winid %d", window);
+ return result;
}
- if (HAS_KEY(opts->use_tabline)) {
- use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
- if (ERROR_SET(err)) {
+ if (HAS_KEY(opts, eval_statusline, use_statuscol_lnum)) {
+ statuscol_lnum = (int)opts->use_statuscol_lnum;
+ VALIDATE_RANGE(statuscol_lnum > 0 && statuscol_lnum <= wp->w_buffer->b_ml.ml_line_count,
+ "use_statuscol_lnum", {
return result;
- }
+ });
+ use_bools++;
}
- if (use_winbar && use_tabline) {
- api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive");
+ VALIDATE(use_bools <= 1, "%s",
+ "Can only use one of 'use_winbar', 'use_tabline' and 'use_statuscol_lnum'", {
return result;
- }
+ });
- win_T *wp, *ewp;
+ int stc_hl_id = 0;
+ statuscol_T statuscol = { 0 };
+ SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 };
- if (use_tabline) {
- wp = NULL;
- ewp = curwin;
+ if (opts->use_tabline) {
fillchar = ' ';
} else {
- wp = find_window_by_handle(window, err);
- if (wp == NULL) {
- api_set_error(err, kErrorTypeException, "unknown winid %d", window);
- return result;
- }
- ewp = wp;
-
if (fillchar == 0) {
- if (use_winbar) {
+ if (opts->use_winbar) {
fillchar = wp->w_p_fcs_chars.wbr;
} else {
int attr;
fillchar = fillchar_status(&attr, wp);
}
}
- }
+ if (statuscol_lnum) {
+ int line_id = 0;
+ int cul_id = 0;
+ int num_id = 0;
+ linenr_T lnum = statuscol_lnum;
+ wp->w_scwidth = win_signcol_count(wp);
+ decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id);
+
+ statuscol.sattrs = sattrs;
+ statuscol.foldinfo = fold_info(wp, lnum);
+ wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
+
+ if (wp->w_p_cul) {
+ if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) {
+ wp->w_cursorline = statuscol.foldinfo.fi_lnum;
+ }
+ statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR);
+ }
- if (HAS_KEY(opts->maxwidth)) {
- if (opts->maxwidth.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
- return result;
+ statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0;
+ if (num_id) {
+ stc_hl_id = num_id;
+ } else if (statuscol.use_cul) {
+ stc_hl_id = HLF_CLN + 1;
+ } else if (wp->w_p_rnu) {
+ stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB) + 1;
+ } else {
+ stc_hl_id = HLF_N + 1;
+ }
+
+ set_vim_var_nr(VV_LNUM, lnum);
+ set_vim_var_nr(VV_RELNUM, labs(get_cursor_rel_lnum(wp, lnum)));
+ set_vim_var_nr(VV_VIRTNUM, 0);
}
+ }
- maxwidth = (int)opts->maxwidth.data.integer;
+ if (HAS_KEY(opts, eval_statusline, maxwidth)) {
+ maxwidth = (int)opts->maxwidth;
} else {
- maxwidth = (use_tabline || (!use_winbar && global_stl_height() > 0)) ? Columns : wp->w_width;
+ maxwidth = statuscol_lnum ? win_col_off(wp)
+ : (opts->use_tabline
+ || (!opts->use_winbar
+ && global_stl_height() > 0)) ? Columns : wp->w_width;
}
char buf[MAXPATHL];
stl_hlrec_t *hltab;
- stl_hlrec_t **hltab_ptr = highlights ? &hltab : NULL;
// Temporarily reset 'cursorbind' to prevent side effects from moving the cursor away and back.
- int p_crb_save = ewp->w_p_crb;
- ewp->w_p_crb = false;
+ int p_crb_save = wp->w_p_crb;
+ wp->w_p_crb = false;
- int width = build_stl_str_hl(ewp,
+ int width = build_stl_str_hl(wp,
buf,
sizeof(buf),
str.data,
@@ -2237,25 +2257,25 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
0,
fillchar,
maxwidth,
- hltab_ptr,
+ opts->highlights ? &hltab : NULL,
NULL,
- NULL);
+ statuscol_lnum ? &statuscol : NULL);
PUT(result, "width", INTEGER_OBJ(width));
// Restore original value of 'cursorbind'
- ewp->w_p_crb = p_crb_save;
+ wp->w_p_crb = p_crb_save;
- if (highlights) {
+ if (opts->highlights) {
Array hl_values = ARRAY_DICT_INIT;
const char *grpname;
- char user_group[6];
+ char user_group[15]; // strlen("User") + strlen("2147483647") + NUL
// If first character doesn't have a defined highlight,
// add the default highlight at the beginning of the highlight list
if (hltab->start == NULL || (hltab->start - buf) != 0) {
Dictionary hl_info = ARRAY_DICT_INIT;
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
PUT(hl_info, "start", INTEGER_OBJ(0));
PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
@@ -2266,10 +2286,10 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
Dictionary hl_info = ARRAY_DICT_INIT;
- PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf));
+ PUT(hl_info, "start", INTEGER_OBJ(sp->start - buf));
if (sp->userhl == 0) {
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
} else if (sp->userhl < 0) {
grpname = syn_id2name(-sp->userhl);
} else {
@@ -2281,7 +2301,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
}
PUT(result, "highlights", ARRAY_OBJ(hl_values));
}
- PUT(result, "str", CSTR_TO_OBJ((char *)buf));
+ PUT(result, "str", CSTR_TO_OBJ(buf));
return result;
}
diff --git a/src/nvim/api/vim.h b/src/nvim/api/vim.h
index de56c67665..b620158751 100644
--- a/src/nvim/api/vim.h
+++ b/src/nvim/api/vim.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_VIM_H
-#define NVIM_API_VIM_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vim.h.generated.h"
#endif
-#endif // NVIM_API_VIM_H
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index af1b23b712..c75bf21572 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
@@ -8,23 +5,22 @@
#include <string.h>
#include "klib/kvec.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
#include "nvim/runtime.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
@@ -38,39 +34,57 @@
/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
/// etc.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
/// @see |execute()|
/// @see |nvim_command()|
/// @see |nvim_cmd()|
///
/// @param src Vimscript code
-/// @param output Capture and return all (non-error, non-shell |:!|) output
+/// @param opts Optional parameters.
+/// - output: (boolean, default false) Whether to capture and return
+/// all (non-error, non-shell |:!|) output.
/// @param[out] err Error details (Vim error), if any
-/// @return Output (non-error, non-shell |:!|) if `output` is true,
-/// else empty string.
-String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
- FUNC_API_SINCE(7)
+/// @return Dictionary containing information about execution, with these keys:
+/// - output: (string|nil) Output if `opts.output` is true.
+Dictionary nvim_exec2(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
+ FUNC_API_SINCE(11)
+{
+ Dictionary result = ARRAY_DICT_INIT;
+
+ String output = exec_impl(channel_id, src, opts, err);
+ if (ERROR_SET(err)) {
+ return result;
+ }
+
+ if (opts->output) {
+ PUT(result, "output", STRING_OBJ(output));
+ }
+
+ return result;
+}
+
+String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
{
const int save_msg_silent = msg_silent;
garray_T *const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
garray_T capture_local;
- if (output) {
+ if (opts->output) {
ga_init(&capture_local, 1, 80);
capture_ga = &capture_local;
}
try_start();
- if (output) {
+ if (opts->output) {
msg_silent++;
msg_col = 0; // prevent leading spaces
}
const sctx_T save_current_sctx = api_set_sctx(channel_id);
- do_source_str(src.data, "nvim_exec()");
- if (output) {
+ do_source_str(src.data, "nvim_exec2()");
+ if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
// Put msg_col back where it was, since nothing should have been written.
@@ -84,7 +98,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
goto theend;
}
- if (output && capture_local.ga_len > 1) {
+ if (opts->output && capture_local.ga_len > 1) {
String s = (String){
.data = capture_local.ga_data,
.size = (size_t)capture_local.ga_len,
@@ -98,7 +112,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
return s; // Caller will free the memory.
}
theend:
- if (output) {
+ if (opts->output) {
ga_clear(&capture_local);
}
return (String)STRING_INIT;
@@ -106,10 +120,10 @@ theend:
/// Executes an Ex command.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script
-/// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured
+/// Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate multiple lines of Vim script
+/// or an Ex command directly, use |nvim_exec2()|. To construct an Ex command using a structured
/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use
/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
///
@@ -123,12 +137,12 @@ void nvim_command(String command, Error *err)
try_end(err);
}
-/// Evaluates a VimL |expression|.
+/// Evaluates a Vimscript |expression|.
/// Dictionaries and Lists are recursively expanded.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @param expr VimL expression string
+/// @param expr Vimscript expression string
/// @param[out] err Error details, if any
/// @return Evaluation result or expanded object
Object nvim_eval(String expr, Error *err)
@@ -137,39 +151,42 @@ Object nvim_eval(String expr, Error *err)
static int recursive = 0; // recursion depth
Object rv = OBJECT_INIT;
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
- typval_T rettv;
- int ok = eval0(expr.data, &rettv, NULL, true);
+ recursive++;
- if (!try_end(err)) {
- if (ok == FAIL) {
- // Should never happen, try_end() should get the error. #8371
- api_set_error(err, kErrorTypeException,
- "Failed to evaluate expression: '%.*s'", 256, expr.data);
- } else {
- rv = vim_to_object(&rettv);
- }
- }
+ typval_T rettv;
+ int ok;
- tv_clear(&rettv);
- recursive--;
+ TRY_WRAP(err, {
+ ok = eval0(expr.data, &rettv, NULL, &EVALARG_EVALUATE);
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
});
+ if (!ERROR_SET(err)) {
+ if (ok == FAIL) {
+ // Should never happen, try_end() (in TRY_WRAP) should get the error. #8371
+ api_set_error(err, kErrorTypeException,
+ "Failed to evaluate expression: '%.*s'", 256, expr.data);
+ } else {
+ rv = vim_to_object(&rettv);
+ }
+ }
+
+ tv_clear(&rettv);
+ recursive--;
+
return rv;
}
-/// Calls a VimL function.
+/// Calls a Vimscript function.
///
/// @param fn Function name
/// @param args Function arguments
@@ -196,34 +213,37 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
}
}
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
- typval_T rettv;
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.fe_firstline = curwin->w_cursor.lnum;
- funcexe.fe_lastline = curwin->w_cursor.lnum;
- funcexe.fe_evaluate = true;
- funcexe.fe_selfdict = self;
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
+ recursive++;
+
+ typval_T rettv;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_firstline = curwin->w_cursor.lnum;
+ funcexe.fe_lastline = curwin->w_cursor.lnum;
+ funcexe.fe_evaluate = true;
+ funcexe.fe_selfdict = self;
+
+ TRY_WRAP(err, {
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (see above) to capture abort-causing non-exception errors.
(void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size,
vim_args, &funcexe);
- if (!try_end(err)) {
- rv = vim_to_object(&rettv);
- }
- tv_clear(&rettv);
- recursive--;
});
+ if (!ERROR_SET(err)) {
+ rv = vim_to_object(&rettv);
+ }
+
+ tv_clear(&rettv);
+ recursive--;
+
free_vim_args:
while (i > 0) {
tv_clear(&vim_args[--i]);
@@ -232,9 +252,9 @@ free_vim_args:
return rv;
}
-/// Calls a VimL function with the given arguments.
+/// Calls a Vimscript function with the given arguments.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
/// @param fn Function to call
/// @param args Function arguments packed in an Array
@@ -246,12 +266,12 @@ Object nvim_call_function(String fn, Array args, Error *err)
return _call_function(fn, args, NULL, err);
}
-/// Calls a VimL |Dictionary-function| with the given arguments.
+/// Calls a Vimscript |Dictionary-function| with the given arguments.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @param dict Dictionary, or String evaluating to a VimL |self| dict
-/// @param fn Name of the function defined on the VimL dict
+/// @param dict Dictionary, or String evaluating to a Vimscript |self| dict
+/// @param fn Name of the function defined on the Vimscript dict
/// @param args Function arguments packed in an Array
/// @param[out] err Error details, if any
/// @return Result of the function call
@@ -265,10 +285,11 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
switch (dict.type) {
case kObjectTypeString:
try_start();
- if (eval0(dict.data.string.data, &rettv, NULL, true) == FAIL) {
+ if (eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE) == FAIL) {
api_set_error(err, kErrorTypeException,
"Failed to evaluate dict expression");
}
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
if (try_end(err)) {
return rv;
}
@@ -336,7 +357,7 @@ typedef struct {
typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// @endcond
-/// Parse a VimL expression.
+/// Parse a Vimscript expression.
///
/// @param[in] expr Expression to parse. Always treated as a single line.
/// @param[in] flags Flags:
@@ -375,7 +396,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// stringified without "kExprNode" prefix.
/// - "start": a pair [line, column] describing where node is "started"
/// where "line" is always 0 (will not be 0 if you will be
-/// using nvim_parse_viml() on e.g. ":let", but that is not
+/// using this API on e.g. ":let", but that is not
/// present yet). Both elements are Integers.
/// - "len": “length†of the node. This and "start" are there for
/// debugging purposes primary (debugging parser and providing
@@ -475,7 +496,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
};
err_dict.items[0] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("message"),
- .value = STRING_OBJ(cstr_to_string(east.err.msg)),
+ .value = CSTR_TO_OBJ(east.err.msg),
};
if (east.err.arg == NULL) {
err_dict.items[1] = (KeyValuePair) {
@@ -512,7 +533,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line);
chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col);
chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col);
- chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group));
+ chunk_arr.items[3] = CSTR_TO_OBJ(chunk.group);
hl.items[i] = ARRAY_OBJ(chunk_arr);
}
ret.items[ret.size++] = (KeyValuePair) {
@@ -589,7 +610,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
kv_drop(ast_conv_stack, 1);
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("type"),
- .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])),
+ .value = CSTR_TO_OBJ(east_node_type_tab[node->type]),
};
Array start_array = {
.items = xmalloc(2 * sizeof(start_array.items[0])),
@@ -674,11 +695,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
case kExprNodeComparison:
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("cmp_type"),
- .value = STRING_OBJ(cstr_to_string(eltkn_cmp_type_tab[node->data.cmp.type])),
+ .value = CSTR_TO_OBJ(eltkn_cmp_type_tab[node->data.cmp.type]),
};
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("ccs_strategy"),
- .value = STRING_OBJ(cstr_to_string(ccs_tab[node->data.cmp.ccs])),
+ .value = CSTR_TO_OBJ(ccs_tab[node->data.cmp.ccs]),
};
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("invert"),
diff --git a/src/nvim/api/vimscript.h b/src/nvim/api/vimscript.h
index be808b6b25..c315e932e9 100644
--- a/src/nvim/api/vimscript.h
+++ b/src/nvim/api/vimscript.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_VIMSCRIPT_H
-#define NVIM_API_VIMSCRIPT_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vimscript.h.generated.h"
#endif
-#endif // NVIM_API_VIMSCRIPT_H
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 0ffeac1bff..4e23717dc6 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -1,30 +1,32 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <string.h>
#include "klib/kvec.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/win_config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
-#include "nvim/extmark_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.c.generated.h"
@@ -55,16 +57,19 @@
/// this should not be used to specify arbitrary WM screen positions.
///
/// Example (Lua): window-relative float
-/// <pre>lua
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', row=3, col=3, width=12, height=3})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', row=3, col=3, width=12, height=3})
+/// ```
///
/// Example (Lua): buffer-relative float (travels as buffer is scrolled)
-/// <pre>lua
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', width=12, height=3, bufpos={100,10}})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', width=12, height=3, bufpos={100,10}})
+/// })
+/// ```
///
/// @param buffer Buffer to display, or 0 for current buffer
/// @param enter Enter the window (make it the current window)
@@ -108,8 +113,8 @@
/// The default value for floats are 50. In general, values below 100 are
/// recommended, unless there is a good reason to overshadow builtin
/// elements.
-/// - style: Configure the appearance of the window. Currently only takes
-/// one non-empty value:
+/// - style: (optional) Configure the appearance of the window. Currently
+/// only supports one value:
/// - "minimal" Nvim will display the window with many UI options
/// disabled. This is useful when displaying a temporary
/// float where the text should not be edited. Disables
@@ -129,7 +134,7 @@
/// - "solid": Adds padding by a single whitespace cell.
/// - "shadow": A drop shadow effect by blending with the background.
/// - If it is an array, it should have a length of eight or any divisor of
-/// eight. The array will specifify the eight chars building up the border
+/// eight. The array will specify the eight chars building up the border
/// in a clockwise fashion starting with the top-left corner. As an
/// example, the double box style could be specified as
/// [ "â•”", "â•" ,"â•—", "â•‘", "â•", "â•", "╚", "â•‘" ].
@@ -144,22 +149,41 @@
/// By default, `FloatBorder` highlight is used, which links to `WinSeparator`
/// when not defined. It could also be specified by character:
/// [ ["+", "MyCorner"], ["x", "MyBorder"] ].
-/// - title: Title (optional) in window border, String or list.
-/// List is [text, highlight] tuples. if is string the default
-/// highlight group is `FloatTitle`.
-/// - title_pos: Title position must set with title option.
-/// value can be of `left` `center` `right` default is left.
+/// - title: Title (optional) in window border, string or list.
+/// List should consist of `[text, highlight]` tuples.
+/// If string, the default highlight group is `FloatTitle`.
+/// - title_pos: Title position. Must be set with `title` option.
+/// Value can be one of "left", "center", or "right".
+/// Default is `"left"`.
+/// - footer: Footer (optional) in window border, string or list.
+/// List should consist of `[text, highlight]` tuples.
+/// If string, the default highlight group is `FloatFooter`.
+/// - footer_pos: Footer position. Must be set with `footer` option.
+/// Value can be one of "left", "center", or "right".
+/// Default is `"left"`.
/// - noautocmd: If true then no buffer-related autocommand events such as
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
/// calling this function.
+/// - fixed: If true when anchor is NW or SW, the float window
+/// would be kept fixed even if the window would be truncated.
+/// - hide: If true the floating window will be hidden.
///
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+ if (cmdwin_type != 0 && (enter || buf == curbuf)) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return 0;
+ }
+
FloatConfig fconfig = FLOAT_CONFIG_INIT;
if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0;
@@ -173,7 +197,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
}
// autocmds in win_enter or win_set_buf below may close the window
if (win_valid(wp) && buffer > 0) {
- win_set_buf(wp->handle, buffer, fconfig.noautocmd, err);
+ Boolean noautocmd = !enter || fconfig.noautocmd;
+ win_set_buf(wp, buf, noautocmd, err);
+ if (!fconfig.noautocmd) {
+ apply_autocmds(EVENT_WINNEW, NULL, NULL, false, buf);
+ }
}
if (!win_valid(wp)) {
api_set_error(err, kErrorTypeException, "Window was closed immediately");
@@ -222,12 +250,56 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
win_config_float(win, fconfig);
win->w_pos_changed = true;
}
- if (fconfig.style == kWinStyleMinimal) {
- win_set_minimal_style(win);
- didset_window_options(win, true);
+ if (HAS_KEY(config, float_config, style)) {
+ if (fconfig.style == kWinStyleMinimal) {
+ win_set_minimal_style(win);
+ didset_window_options(win, true);
+ }
}
}
+static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig,
+ BorderTextType bordertext_type)
+{
+ VirtText vt;
+ AlignTextPos align;
+ char *field_name;
+ char *field_pos_name;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ vt = fconfig->title_chunks;
+ align = fconfig->title_pos;
+ field_name = "title";
+ field_pos_name = "title_pos";
+ break;
+ case kBorderTextFooter:
+ vt = fconfig->footer_chunks;
+ align = fconfig->footer_pos;
+ field_name = "footer";
+ field_pos_name = "footer_pos";
+ break;
+ }
+
+ Array bordertext = virt_text_to_array(vt, true);
+ PUT(config, field_name, ARRAY_OBJ(bordertext));
+
+ char *pos;
+ switch (align) {
+ case kAlignLeft:
+ pos = "left";
+ break;
+ case kAlignCenter:
+ pos = "center";
+ break;
+ case kAlignRight:
+ pos = "right";
+ break;
+ }
+ PUT(config, field_pos_name, CSTR_TO_OBJ(pos));
+
+ return config;
+}
+
/// Gets window configuration.
///
/// The returned value may be given to |nvim_open_win()|.
@@ -251,6 +323,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable));
PUT(rv, "external", BOOLEAN_OBJ(config->external));
+ PUT(rv, "hide", BOOLEAN_OBJ(config->hide));
if (wp->w_floating) {
PUT(rv, "width", INTEGER_OBJ(config->width));
@@ -265,7 +338,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
PUT(rv, "bufpos", ARRAY_OBJ(pos));
}
}
- PUT(rv, "anchor", STRING_OBJ(cstr_to_string(float_anchor_str[config->anchor])));
+ PUT(rv, "anchor", CSTR_TO_OBJ(float_anchor_str[config->anchor]));
PUT(rv, "row", FLOAT_OBJ(config->row));
PUT(rv, "col", FLOAT_OBJ(config->col));
PUT(rv, "zindex", INTEGER_OBJ(config->zindex));
@@ -275,13 +348,13 @@ Dictionary nvim_win_get_config(Window window, Error *err)
for (size_t i = 0; i < 8; i++) {
Array tuple = ARRAY_DICT_INIT;
- String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T));
+ String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE);
int hi_id = config->border_hl_ids[i];
char *hi_name = syn_id2name(hi_id);
if (hi_name[0]) {
ADD(tuple, STRING_OBJ(s));
- ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name)));
+ ADD(tuple, CSTR_TO_OBJ(hi_name));
ADD(border, ARRAY_OBJ(tuple));
} else {
ADD(border, STRING_OBJ(s));
@@ -289,34 +362,17 @@ Dictionary nvim_win_get_config(Window window, Error *err)
}
PUT(rv, "border", ARRAY_OBJ(border));
if (config->title) {
- Array titles = ARRAY_DICT_INIT;
- VirtText title_datas = config->title_chunks;
- for (size_t i = 0; i < title_datas.size; i++) {
- Array tuple = ARRAY_DICT_INIT;
- ADD(tuple, CSTR_TO_OBJ((const char *)title_datas.items[i].text));
- if (title_datas.items[i].hl_id > 0) {
- ADD(tuple,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(title_datas.items[i].hl_id))));
- }
- ADD(titles, ARRAY_OBJ(tuple));
- }
- PUT(rv, "title", ARRAY_OBJ(titles));
- char *title_pos;
- if (config->title_pos == kAlignLeft) {
- title_pos = "left";
- } else if (config->title_pos == kAlignCenter) {
- title_pos = "center";
- } else {
- title_pos = "right";
- }
- PUT(rv, "title_pos", CSTR_TO_OBJ(title_pos));
+ rv = config_put_bordertext(rv, config, kBorderTextTitle);
+ }
+ if (config->footer) {
+ rv = config_put_bordertext(rv, config, kBorderTextFooter);
}
}
}
const char *rel = (wp->w_floating && !config->external
? float_relative_str[config->relative] : "");
- PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel)));
+ PUT(rv, "relative", CSTR_TO_OBJ(rel));
return rv;
}
@@ -370,68 +426,91 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
return true;
}
-static void parse_border_title(Object title, Object title_pos, FloatConfig *fconfig, Error *err)
+static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
+ FloatConfig *fconfig, Error *err)
{
- if (!parse_title_pos(title_pos, fconfig, err)) {
- return;
- }
-
- if (title.type == kObjectTypeString) {
- if (title.data.string.size == 0) {
- fconfig->title = false;
+ bool *is_present;
+ VirtText *chunks;
+ int *width;
+ int default_hl_id;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ is_present = &fconfig->title;
+ chunks = &fconfig->title_chunks;
+ width = &fconfig->title_width;
+ default_hl_id = syn_check_group(S_LEN("FloatTitle"));
+ break;
+ case kBorderTextFooter:
+ is_present = &fconfig->footer;
+ chunks = &fconfig->footer_chunks;
+ width = &fconfig->footer_width;
+ default_hl_id = syn_check_group(S_LEN("FloatFooter"));
+ break;
+ }
+
+ if (bordertext.type == kObjectTypeString) {
+ if (bordertext.data.string.size == 0) {
+ *is_present = false;
return;
}
- int hl_id = syn_check_group(S_LEN("FloatTitle"));
- kv_push(fconfig->title_chunks, ((VirtTextChunk){ .text = xstrdup(title.data.string.data),
- .hl_id = hl_id }));
- fconfig->title_width = (int)mb_string2cells(title.data.string.data);
- fconfig->title = true;
+ kv_push(*chunks, ((VirtTextChunk){ .text = xstrdup(bordertext.data.string.data),
+ .hl_id = default_hl_id }));
+ *width = (int)mb_string2cells(bordertext.data.string.data);
+ *is_present = true;
return;
}
- if (title.type != kObjectTypeArray) {
+ if (bordertext.type != kObjectTypeArray) {
api_set_error(err, kErrorTypeValidation, "title must be string or array");
return;
}
- if (title.data.array.size == 0) {
+ if (bordertext.data.array.size == 0) {
api_set_error(err, kErrorTypeValidation, "title cannot be an empty array");
return;
}
- fconfig->title_width = 0;
- fconfig->title_chunks = parse_virt_text(title.data.array, err, &fconfig->title_width);
+ *width = 0;
+ *chunks = parse_virt_text(bordertext.data.array, err, width);
- fconfig->title = true;
+ *is_present = true;
}
-static bool parse_title_pos(Object title_pos, FloatConfig *fconfig, Error *err)
+static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type,
+ FloatConfig *fconfig, Error *err)
{
- if (!HAS_KEY(title_pos)) {
- fconfig->title_pos = kAlignLeft;
+ AlignTextPos *align;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ align = &fconfig->title_pos;
+ break;
+ case kBorderTextFooter:
+ align = &fconfig->footer_pos;
+ break;
+ }
+
+ if (bordertext_pos.size == 0) {
+ *align = kAlignLeft;
return true;
}
- if (title_pos.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "title_pos must be string");
- return false;
- }
-
- if (title_pos.data.string.size == 0) {
- fconfig->title_pos = kAlignLeft;
- return true;
- }
-
- char *pos = title_pos.data.string.data;
+ char *pos = bordertext_pos.data;
if (strequal(pos, "left")) {
- fconfig->title_pos = kAlignLeft;
+ *align = kAlignLeft;
} else if (strequal(pos, "center")) {
- fconfig->title_pos = kAlignCenter;
+ *align = kAlignCenter;
} else if (strequal(pos, "right")) {
- fconfig->title_pos = kAlignRight;
+ *align = kAlignRight;
} else {
- api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
+ break;
+ case kBorderTextFooter:
+ api_set_error(err, kErrorTypeValidation, "invalid footer_pos value");
+ break;
+ }
return false;
}
return true;
@@ -441,7 +520,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{
struct {
const char *name;
- schar_T chars[8];
+ char chars[8][MAX_SCHAR_SIZE];
bool shadow_color;
} defaults[] = {
{ "double", { "â•”", "â•", "â•—", "â•‘", "â•", "â•", "╚", "â•‘" }, false },
@@ -452,7 +531,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ NULL, { { NUL } }, false },
};
- schar_T *chars = fconfig->border_chars;
+ char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars;
int *hl_ids = fconfig->border_hl_ids;
fconfig->border = true;
@@ -521,8 +600,9 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
String str = style.data.string;
if (str.size == 0 || strequal(str.data, "none")) {
fconfig->border = false;
- // title does not work with border equal none
+ // border text does not work with border equal none
fconfig->title = false;
+ fconfig->footer = false;
return;
}
for (size_t i = 0; defaults[i].name; i++) {
@@ -549,110 +629,90 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf,
bool new_win, Error *err)
{
+#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key)
bool has_relative = false, relative_is_win = false;
- if (config->relative.type == kObjectTypeString) {
- // ignore empty string, to match nvim_win_get_config
- if (config->relative.data.string.size > 0) {
- if (!parse_float_relative(config->relative.data.string, &fconfig->relative)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
- return false;
- }
+ // ignore empty string, to match nvim_win_get_config
+ if (HAS_KEY_X(config, relative) && config->relative.size > 0) {
+ if (!parse_float_relative(config->relative, &fconfig->relative)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
+ return false;
+ }
- if (!(HAS_KEY(config->row) && HAS_KEY(config->col)) && !HAS_KEY(config->bufpos)) {
- api_set_error(err, kErrorTypeValidation,
- "'relative' requires 'row'/'col' or 'bufpos'");
- return false;
- }
+ if (!(HAS_KEY_X(config, row) && HAS_KEY_X(config, col)) && !HAS_KEY_X(config, bufpos)) {
+ api_set_error(err, kErrorTypeValidation,
+ "'relative' requires 'row'/'col' or 'bufpos'");
+ return false;
+ }
- has_relative = true;
- fconfig->external = false;
- if (fconfig->relative == kFloatRelativeWindow) {
- relative_is_win = true;
- fconfig->bufpos.lnum = -1;
- }
+ has_relative = true;
+ fconfig->external = false;
+ if (fconfig->relative == kFloatRelativeWindow) {
+ relative_is_win = true;
+ fconfig->bufpos.lnum = -1;
}
- } else if (HAS_KEY(config->relative)) {
- api_set_error(err, kErrorTypeValidation, "'relative' key must be String");
- return false;
}
- if (config->anchor.type == kObjectTypeString) {
- if (!parse_float_anchor(config->anchor.data.string, &fconfig->anchor)) {
+ if (HAS_KEY_X(config, anchor)) {
+ if (!parse_float_anchor(config->anchor, &fconfig->anchor)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key");
return false;
}
- } else if (HAS_KEY(config->anchor)) {
- api_set_error(err, kErrorTypeValidation, "'anchor' key must be String");
- return false;
}
- if (HAS_KEY(config->row)) {
+ if (HAS_KEY_X(config, row)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'");
return false;
- } else if (config->row.type == kObjectTypeInteger) {
- fconfig->row = (double)config->row.data.integer;
- } else if (config->row.type == kObjectTypeFloat) {
- fconfig->row = config->row.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'row' key must be Integer or Float");
- return false;
}
+ fconfig->row = config->row;
}
- if (HAS_KEY(config->col)) {
+ if (HAS_KEY_X(config, col)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'");
return false;
- } else if (config->col.type == kObjectTypeInteger) {
- fconfig->col = (double)config->col.data.integer;
- } else if (config->col.type == kObjectTypeFloat) {
- fconfig->col = config->col.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'col' key must be Integer or Float");
- return false;
}
+ fconfig->col = config->col;
}
- if (HAS_KEY(config->bufpos)) {
+ if (HAS_KEY_X(config, bufpos)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'");
return false;
- } else if (config->bufpos.type == kObjectTypeArray) {
- if (!parse_float_bufpos(config->bufpos.data.array, &fconfig->bufpos)) {
+ } else {
+ if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key");
return false;
}
- if (!HAS_KEY(config->row)) {
+ if (!HAS_KEY_X(config, row)) {
fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
}
- if (!HAS_KEY(config->col)) {
+ if (!HAS_KEY_X(config, col)) {
fconfig->col = 0;
}
- } else {
- api_set_error(err, kErrorTypeValidation, "'bufpos' key must be Array");
- return false;
}
}
- if (config->width.type == kObjectTypeInteger && config->width.data.integer > 0) {
- fconfig->width = (int)config->width.data.integer;
- } else if (HAS_KEY(config->width)) {
- api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, width)) {
+ if (config->width > 0) {
+ fconfig->width = (int)config->width;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
+ return false;
+ }
} else if (!reconf) {
api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
return false;
}
- if (config->height.type == kObjectTypeInteger && config->height.data.integer > 0) {
- fconfig->height = (int)config->height.data.integer;
- } else if (HAS_KEY(config->height)) {
- api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, height)) {
+ if (config->height > 0) {
+ fconfig->height = (int)config->height;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
+ return false;
+ }
} else if (!reconf) {
api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
return false;
@@ -660,26 +720,20 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
if (relative_is_win) {
fconfig->window = curwin->handle;
- if (config->win.type == kObjectTypeInteger || config->win.type == kObjectTypeWindow) {
- if (config->win.data.integer > 0) {
- fconfig->window = (Window)config->win.data.integer;
+ if (HAS_KEY_X(config, win)) {
+ if (config->win > 0) {
+ fconfig->window = config->win;
}
- } else if (HAS_KEY(config->win)) {
- api_set_error(err, kErrorTypeValidation, "'win' key must be Integer or Window");
- return false;
}
} else {
- if (HAS_KEY(config->win)) {
+ if (HAS_KEY_X(config, win)) {
api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'");
return false;
}
}
- if (HAS_KEY(config->external)) {
- fconfig->external = api_object_to_bool(config->external, "'external' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
+ if (HAS_KEY_X(config, external)) {
+ fconfig->external = config->external;
if (has_relative && fconfig->external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' must be used");
@@ -698,30 +752,22 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
return false;
}
- if (HAS_KEY(config->focusable)) {
- fconfig->focusable = api_object_to_bool(config->focusable, "'focusable' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
- }
-
- if (config->zindex.type == kObjectTypeInteger && config->zindex.data.integer > 0) {
- fconfig->zindex = (int)config->zindex.data.integer;
- } else if (HAS_KEY(config->zindex)) {
- api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, focusable)) {
+ fconfig->focusable = config->focusable;
}
- if (HAS_KEY(config->title_pos)) {
- if (!HAS_KEY(config->title)) {
- api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
+ if (HAS_KEY_X(config, zindex)) {
+ if (config->zindex > 0) {
+ fconfig->zindex = (int)config->zindex;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
return false;
}
}
- if (HAS_KEY(config->title)) {
+ if (HAS_KEY_X(config, title)) {
// title only work with border
- if (!HAS_KEY(config->border) && !fconfig->border) {
+ if (!HAS_KEY_X(config, border) && !fconfig->border) {
api_set_error(err, kErrorTypeException, "title requires border to be set");
return false;
}
@@ -729,42 +775,84 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
if (fconfig->title) {
clear_virttext(&fconfig->title_chunks);
}
- parse_border_title(config->title, config->title_pos, fconfig, err);
+
+ parse_bordertext(config->title, kBorderTextTitle, fconfig, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+
+ // handles unset 'title_pos' same as empty string
+ if (!parse_bordertext_pos(config->title_pos, kBorderTextTitle, fconfig, err)) {
+ return false;
+ }
+ } else {
+ if (HAS_KEY_X(config, title_pos)) {
+ api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
+ return false;
+ }
+ }
+
+ if (HAS_KEY_X(config, footer)) {
+ // footer only work with border
+ if (!HAS_KEY_X(config, border) && !fconfig->border) {
+ api_set_error(err, kErrorTypeException, "footer requires border to be set");
+ return false;
+ }
+
+ if (fconfig->footer) {
+ clear_virttext(&fconfig->footer_chunks);
+ }
+
+ parse_bordertext(config->footer, kBorderTextFooter, fconfig, err);
if (ERROR_SET(err)) {
return false;
}
+
+ // handles unset 'footer_pos' same as empty string
+ if (!parse_bordertext_pos(config->footer_pos, kBorderTextFooter, fconfig, err)) {
+ return false;
+ }
+ } else {
+ if (HAS_KEY_X(config, footer_pos)) {
+ api_set_error(err, kErrorTypeException, "footer_pos requires footer to be set");
+ return false;
+ }
}
- if (HAS_KEY(config->border)) {
+ if (HAS_KEY_X(config, border)) {
parse_border_style(config->border, fconfig, err);
if (ERROR_SET(err)) {
return false;
}
}
- if (config->style.type == kObjectTypeString) {
- if (config->style.data.string.data[0] == NUL) {
+ if (HAS_KEY_X(config, style)) {
+ if (config->style.data[0] == NUL) {
fconfig->style = kWinStyleUnused;
- } else if (striequal(config->style.data.string.data, "minimal")) {
+ } else if (striequal(config->style.data, "minimal")) {
fconfig->style = kWinStyleMinimal;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key");
+ return false;
}
- } else if (HAS_KEY(config->style)) {
- api_set_error(err, kErrorTypeValidation, "'style' key must be String");
- return false;
}
- if (HAS_KEY(config->noautocmd)) {
+ if (HAS_KEY_X(config, noautocmd)) {
if (!new_win) {
api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'");
return false;
}
- fconfig->noautocmd = api_object_to_bool(config->noautocmd, "'noautocmd' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
+ fconfig->noautocmd = config->noautocmd;
+ }
+
+ if (HAS_KEY_X(config, fixed)) {
+ fconfig->fixed = config->fixed;
+ }
+
+ if (HAS_KEY_X(config, hide)) {
+ fconfig->hide = config->hide;
}
return true;
+#undef HAS_KEY_X
}
diff --git a/src/nvim/api/win_config.h b/src/nvim/api/win_config.h
index d3e5ede5e9..6df8ed13fa 100644
--- a/src/nvim/api/win_config.h
+++ b/src/nvim/api/win_config.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_WIN_CONFIG_H
-#define NVIM_API_WIN_CONFIG_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.h.generated.h"
#endif
-#endif // NVIM_API_WIN_CONFIG_H
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index e2c234ab29..de5b40940f 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -1,27 +1,30 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/window.h"
-#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/window.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
-#include "nvim/memline_defs.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/window.h"
/// Gets the current buffer in a window
@@ -48,15 +51,26 @@ Buffer nvim_win_get_buf(Window window, Error *err)
/// @param[out] err Error details, if any
void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
FUNC_API_SINCE(5)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
- win_set_buf(window, buffer, false, err);
+ win_T *win = find_window_by_handle(window, err);
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!win || !buf) {
+ return;
+ }
+ if (cmdwin_type != 0 && (win == curwin || win == cmdwin_old_curwin || buf == curbuf)) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return;
+ }
+ win_set_buf(win, buf, false, err);
}
/// Gets the (1,0)-indexed, buffer-relative cursor position for a given window
/// (different windows showing the same buffer have independent cursor
/// positions). |api-indexing|
///
+/// @see |getcurpos()|
+///
/// @param window Window handle, or 0 for current window
/// @param[out] err Error details, if any
/// @return (row, col) tuple
@@ -167,13 +181,8 @@ void nvim_win_set_height(Window window, Integer height, Error *err)
return;
}
- win_T *savewin = curwin;
- curwin = win;
- curbuf = curwin->w_buffer;
try_start();
- win_setheight((int)height);
- curwin = savewin;
- curbuf = curwin->w_buffer;
+ win_setheight_win((int)height, win);
try_end(err);
}
@@ -214,13 +223,8 @@ void nvim_win_set_width(Window window, Integer width, Error *err)
return;
}
- win_T *savewin = curwin;
- curwin = win;
- curbuf = curwin->w_buffer;
try_start();
- win_setwidth((int)width);
- curwin = savewin;
- curbuf = curwin->w_buffer;
+ win_setwidth_win((int)width, win);
try_end(err);
}
@@ -359,10 +363,10 @@ Boolean nvim_win_is_valid(Window window)
/// @param[out] err Error details, if any
void nvim_win_hide(Window window, Error *err)
FUNC_API_SINCE(7)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
win_T *win = find_window_by_handle(window, err);
- if (!win) {
+ if (!win || !can_close_in_cmdwin(win, err)) {
return;
}
@@ -391,19 +395,10 @@ void nvim_win_hide(Window window, Error *err)
/// @param[out] err Error details, if any
void nvim_win_close(Window window, Boolean force, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
win_T *win = find_window_by_handle(window, err);
- if (!win) {
- return;
- }
-
- if (cmdwin_type != 0) {
- if (win == curwin) {
- cmdwin_result = Ctrl_C;
- } else {
- api_set_error(err, kErrorTypeException, "%s", _(e_cmdwin));
- }
+ if (!win || !can_close_in_cmdwin(win, err)) {
return;
}
@@ -420,11 +415,11 @@ void nvim_win_close(Window window, Boolean force, Error *err)
/// @see |nvim_buf_call()|
///
/// @param window Window handle, or 0 for current window
-/// @param fun Function to call inside the window (currently lua callable
+/// @param fun Function to call inside the window (currently Lua callable
/// only)
/// @param[out] err Error details, if any
-/// @return Return value of function. NB: will deepcopy lua values
-/// currently, use upvalues to send lua references in and out.
+/// @return Return value of function. NB: will deepcopy Lua values
+/// currently, use upvalues to send Lua references in and out.
Object nvim_win_call(Window window, LuaRef fun, Error *err)
FUNC_API_SINCE(7)
FUNC_API_LUA_ONLY
@@ -445,8 +440,9 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
return res;
}
-/// Set highlight namespace for a window. This will use highlights defined in
-/// this namespace, but fall back to global highlights (ns=0) when missing.
+/// Set highlight namespace for a window. This will use highlights defined with
+/// |nvim_set_hl()| for this namespace, but fall back to global highlights (ns=0) when
+/// missing.
///
/// This takes precedence over the 'winhighlight' option.
///
@@ -469,3 +465,106 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err)
win->w_hl_needs_update = true;
redraw_later(win, UPD_NOT_VALID);
}
+
+/// Computes the number of screen lines occupied by a range of text in a given window.
+/// Works for off-screen text and takes folds into account.
+///
+/// Diff filler or virtual lines above a line are counted as a part of that line,
+/// unless the line is on "start_row" and "start_vcol" is specified.
+///
+/// Diff filler or virtual lines below the last buffer line are counted in the result
+/// when "end_row" is omitted.
+///
+/// Line indexing is similar to |nvim_buf_get_text()|.
+///
+/// @param window Window handle, or 0 for current window.
+/// @param opts Optional parameters:
+/// - start_row: Starting line index, 0-based inclusive.
+/// When omitted start at the very top.
+/// - end_row: Ending line index, 0-based inclusive.
+/// When omitted end at the very bottom.
+/// - start_vcol: Starting virtual column index on "start_row",
+/// 0-based inclusive, rounded down to full screen lines.
+/// When omitted include the whole line.
+/// - end_vcol: Ending virtual column index on "end_row",
+/// 0-based exclusive, rounded up to full screen lines.
+/// When omitted include the whole line.
+/// @return Dictionary containing text height information, with these keys:
+/// - all: The total number of screen lines occupied by the range.
+/// - fill: The number of diff filler or virtual lines among them.
+///
+/// @see |virtcol()| for text width.
+Dictionary nvim_win_text_height(Window window, Dict(win_text_height) *opts, Arena *arena,
+ Error *err)
+ FUNC_API_SINCE(12)
+{
+ Dictionary rv = arena_dict(arena, 2);
+
+ win_T *const win = find_window_by_handle(window, err);
+ if (!win) {
+ return rv;
+ }
+ buf_T *const buf = win->w_buffer;
+ const linenr_T line_count = buf->b_ml.ml_line_count;
+
+ linenr_T start_lnum = 1;
+ linenr_T end_lnum = line_count;
+ int64_t start_vcol = -1;
+ int64_t end_vcol = -1;
+
+ bool oob = false;
+
+ if (HAS_KEY(opts, win_text_height, start_row)) {
+ start_lnum = (linenr_T)normalize_index(buf, opts->start_row, false, &oob);
+ }
+
+ if (HAS_KEY(opts, win_text_height, end_row)) {
+ end_lnum = (linenr_T)normalize_index(buf, opts->end_row, false, &oob);
+ }
+
+ VALIDATE(!oob, "%s", "Line index out of bounds", {
+ return rv;
+ });
+ VALIDATE((start_lnum <= end_lnum), "%s", "'start_row' is higher than 'end_row'", {
+ return rv;
+ });
+
+ if (HAS_KEY(opts, win_text_height, start_vcol)) {
+ VALIDATE(HAS_KEY(opts, win_text_height, start_row),
+ "%s", "'start_vcol' specified without 'start_row'", {
+ return rv;
+ });
+ start_vcol = opts->start_vcol;
+ VALIDATE_RANGE((start_vcol >= 0 && start_vcol <= MAXCOL), "start_vcol", {
+ return rv;
+ });
+ }
+
+ if (HAS_KEY(opts, win_text_height, end_vcol)) {
+ VALIDATE(HAS_KEY(opts, win_text_height, end_row),
+ "%s", "'end_vcol' specified without 'end_row'", {
+ return rv;
+ });
+ end_vcol = opts->end_vcol;
+ VALIDATE_RANGE((end_vcol >= 0 && end_vcol <= MAXCOL), "end_vcol", {
+ return rv;
+ });
+ }
+
+ if (start_lnum == end_lnum && start_vcol >= 0 && end_vcol >= 0) {
+ VALIDATE((start_vcol <= end_vcol), "%s", "'start_vcol' is higher than 'end_vcol'", {
+ return rv;
+ });
+ }
+
+ int64_t fill = 0;
+ int64_t all = win_text_height(win, start_lnum, start_vcol, end_lnum, end_vcol, &fill);
+ if (!HAS_KEY(opts, win_text_height, end_row)) {
+ const int64_t end_fill = win_get_fill(win, line_count + 1);
+ fill += end_fill;
+ all += end_fill;
+ }
+ PUT_C(rv, "all", INTEGER_OBJ(all));
+ PUT_C(rv, "fill", INTEGER_OBJ(fill));
+ return rv;
+}
diff --git a/src/nvim/api/window.h b/src/nvim/api/window.h
index 0f36c12a9f..a5c9f86225 100644
--- a/src/nvim/api/window.h
+++ b/src/nvim/api/window.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_API_WINDOW_H
-#define NVIM_API_WINDOW_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/window.h.generated.h"
#endif
-#endif // NVIM_API_WINDOW_H
diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c
index 41024cafda..84f4297c99 100644
--- a/src/nvim/arabic.c
+++ b/src/nvim/arabic.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file arabic.c
///
/// Functions for Arabic language.
@@ -22,14 +19,12 @@
#include <stdbool.h>
#include <stddef.h>
-#include <stdint.h>
#include "nvim/arabic.h"
-#include "nvim/ascii.h"
-#include "nvim/macros.h"
-#include "nvim/mbyte.h"
-#include "nvim/option_defs.h"
-#include "nvim/vim.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
+#include "nvim/option_vars.h"
// Unicode values for Arabic characters.
enum {
@@ -297,13 +292,12 @@ static int A_is_valid(int c)
}
// Do Arabic shaping on character "c". Returns the shaped character.
-// out: "ccp" points to the first byte of the character to be shaped.
// in/out: "c1p" points to the first composing char for "c".
// in: "prev_c" is the previous character (not shaped)
// in: "prev_c1" is the first composing char for the previous char
// (not shaped)
// in: "next_c" is the next character (not shaped).
-int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c)
+int arabic_shape(int c, int *c1p, int prev_c, int prev_c1, int next_c)
{
// Deal only with Arabic character, pass back all others
if (!A_is_ok(c)) {
@@ -347,14 +341,6 @@ int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c)
curr_c = c;
}
- if ((curr_c != c) && (ccp != NULL)) {
- char buf[MB_MAXBYTES + 1];
-
- // Update the first byte of the character
- utf_char2bytes(curr_c, buf);
- *ccp = (uint8_t)buf[0];
- }
-
// Return the shaped character
return curr_c;
}
diff --git a/src/nvim/arabic.h b/src/nvim/arabic.h
index 3c34de1449..ac27153a37 100644
--- a/src/nvim/arabic.h
+++ b/src/nvim/arabic.h
@@ -1,11 +1,7 @@
-#ifndef NVIM_ARABIC_H
-#define NVIM_ARABIC_H
-
-#include <stdbool.h>
+#pragma once
#define ARABIC_CHAR(ch) (((ch) & 0xFF00) == 0x0600)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "arabic.h.generated.h"
#endif
-#endif // NVIM_ARABIC_H
diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c
index c6a4be7e13..d2734e6c5a 100644
--- a/src/nvim/arglist.c
+++ b/src/nvim/arglist.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// arglist.c: functions for dealing with the argument list
#include <assert.h>
@@ -10,9 +7,11 @@
#include "auto/config.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
@@ -21,23 +20,25 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// State used by the :all command to open all the files in the argument list in
@@ -63,7 +64,9 @@ typedef struct {
# include "arglist.c.generated.h"
#endif
-static char e_cannot_change_arglist_recursively[]
+static const char e_window_layout_changed_unexpectedly[]
+ = N_("E249: Window layout changed unexpectedly");
+static const char e_cannot_change_arglist_recursively[]
= N_("E1156: Cannot change the argument list recursively");
enum {
@@ -135,7 +138,7 @@ void alist_expand(int *fnum_list, int fnum_len)
// Don't use 'suffixes' here. This should work like the shell did the
// expansion. Also, the vimrc file isn't read yet, thus the user
// can't set the options.
- p_su = empty_option;
+ p_su = empty_string_option;
for (int i = 0; i < GARGCOUNT; i++) {
old_arg_files[i] = xstrdup(GARGLIST[i].ae_fname);
}
@@ -252,9 +255,8 @@ void alist_slash_adjust(void)
static char *do_one_arg(char *str)
{
char *p;
- bool inbacktick;
- inbacktick = false;
+ bool inbacktick = false;
for (p = str; *str; str++) {
// When the backslash is used for escaping the special meaning of a
// character we need to keep it until wildcard expansion.
@@ -388,7 +390,7 @@ static void arglist_del_files(garray_T *alist_ga)
bool didone = false;
for (int match = 0; match < ARGCOUNT; match++) {
- if (vim_regexec(&regmatch, alist_name(&ARGLIST[match]), (colnr_T)0)) {
+ if (vim_regexec(&regmatch, alist_name(&ARGLIST[match]), 0)) {
didone = true;
xfree(ARGLIST[match].ae_fname);
memmove(ARGLIST + match, ARGLIST + match + 1,
@@ -619,8 +621,6 @@ void ex_argument(exarg_T *eap)
/// Edit file "argn" of the argument lists.
void do_argfile(exarg_T *eap, int argn)
{
- int other;
- char *p;
int old_arg_idx = curwin->w_arg_idx;
if (argn < 0 || argn >= ARGCOUNT) {
@@ -646,9 +646,9 @@ void do_argfile(exarg_T *eap, int argn)
} else {
// if 'hidden' set, only check for changed file when re-editing
// the same buffer
- other = true;
+ int other = true;
if (buf_hide(curbuf)) {
- p = fix_fname(alist_name(&ARGLIST[argn]));
+ char *p = fix_fname(alist_name(&ARGLIST[argn]));
other = otherfile(p);
xfree(p);
}
@@ -683,8 +683,6 @@ void do_argfile(exarg_T *eap, int argn)
/// ":next", and commands that behave like it.
void ex_next(exarg_T *eap)
{
- int i;
-
// check for changed buffer now, if this fails the argument list is not
// redefined.
if (buf_hide(curbuf)
@@ -692,6 +690,7 @@ void ex_next(exarg_T *eap)
|| !check_changed(curbuf, CCGD_AW
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)) {
+ int i;
if (*eap->arg != NUL) { // redefine file list
if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) {
return;
@@ -835,10 +834,8 @@ char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
/// Get the file name for an argument list entry.
char *alist_name(aentry_T *aep)
{
- buf_T *bp;
-
// Use the name from the associated buffer if it exists.
- bp = buflist_findnr(aep->ae_fnum);
+ buf_T *bp = buflist_findnr(aep->ae_fnum);
if (bp == NULL || bp->b_fname == NULL) {
return aep->ae_fname;
}
@@ -855,15 +852,20 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall)
if (aall->had_tab > 0) {
goto_tabpage_tp(first_tabpage, true, true);
}
- for (;;) {
+ while (true) {
win_T *wpnext = NULL;
tabpage_T *tpnext = curtab->tp_next;
- for (win_T *wp = firstwin; wp != NULL; wp = wpnext) {
+ // Try to close floating windows first
+ for (win_T *wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) {
int i;
- wpnext = wp->w_next;
+ wpnext = wp->w_floating
+ ? wp->w_prev->w_floating ? wp->w_prev : firstwin
+ : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next;
buf_T *buf = wp->w_buffer;
if (buf->b_ffname == NULL
- || (!aall->keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) {
+ || (!aall->keep_tabs
+ && (buf->b_nwindows > 1 || wp->w_width != Columns
+ || (wp->w_floating && !is_aucmd_win(wp))))) {
i = aall->opened_len;
} else {
// check if the buffer in this window is in the arglist
@@ -918,7 +920,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall)
(void)autowrite(buf, false);
// Check if autocommands removed the window.
if (!win_valid(wp) || !bufref_valid(&bufref)) {
- wpnext = firstwin; // Start all over...
+ wpnext = lastwin->w_floating ? lastwin : firstwin; // Start all over...
continue;
}
}
@@ -931,7 +933,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall)
// check if autocommands removed the next window
if (!win_valid(wpnext)) {
// start all over...
- wpnext = firstwin;
+ wpnext = lastwin->w_floating ? lastwin : firstwin;
}
}
}
@@ -978,8 +980,10 @@ static void arg_all_open_windows(arg_all_state_T *aall, int count)
if (aall->keep_tabs) {
aall->new_curwin = wp;
aall->new_curtab = curtab;
+ } else if (wp->w_floating) {
+ break;
} else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) {
- emsg(_("E249: window layout changed unexpectedly"));
+ emsg(_(e_window_layout_changed_unexpectedly));
i = count;
break;
} else {
@@ -1074,6 +1078,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
aall.alist->al_refcount++;
arglist_locked = true;
+ tabpage_T *const new_lu_tp = curtab;
+
// Try closing all windows that are not in the argument list.
// Also close windows that are not full width;
// When 'hidden' or "forceit" set the buffer becomes hidden.
@@ -1081,6 +1087,11 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
// When the ":tab" modifier was used do this for all tab pages.
arg_all_close_unused_windows(&aall);
+ // Now set the last used tabpage to where we started.
+ if (valid_tabpage(new_lu_tp)) {
+ lastused_tabpage = new_lu_tp;
+ }
+
// Open a window for files in the argument list that don't have one.
// ARGCOUNT may change while doing this, because of autocommands.
if (count > aall.opened_len || count <= 0) {
@@ -1092,7 +1103,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
autocmd_no_leave++;
last_curwin = curwin;
last_curtab = curtab;
- win_enter(lastwin, false);
+ // lastwin may be aucmd_win
+ win_enter(lastwin_nofloating(), false);
// Open up to "count" windows.
arg_all_open_windows(&aall, count);
@@ -1144,7 +1156,7 @@ char *arg_all(void)
// Do this loop two times:
// first time: compute the total length
// second time: concatenate the names
- for (;;) {
+ while (true) {
int len = 0;
for (int idx = 0; idx < ARGCOUNT; idx++) {
char *p = alist_name(&ARGLIST[idx]);
@@ -1233,8 +1245,7 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rett
tv_list_alloc_ret(rettv, argcount);
if (arglist != NULL) {
for (int idx = 0; idx < argcount; idx++) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)alist_name(&arglist[idx]), -1);
+ tv_list_append_string(rettv->vval.v_list, alist_name(&arglist[idx]), -1);
}
}
}
@@ -1270,7 +1281,7 @@ void f_argv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = NULL;
int idx = (int)tv_get_number_chk(&argvars[0], NULL);
if (arglist != NULL && idx >= 0 && idx < argcount) {
- rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx]));
+ rettv->vval.v_string = xstrdup(alist_name(&arglist[idx]));
} else if (idx == -1) {
get_arglist_as_rettv(arglist, argcount, rettv);
}
diff --git a/src/nvim/arglist.h b/src/nvim/arglist.h
index b2e0f411d4..97729f466c 100644
--- a/src/nvim/arglist.h
+++ b/src/nvim/arglist.h
@@ -1,11 +1,11 @@
-#ifndef NVIM_ARGLIST_H
-#define NVIM_ARGLIST_H
+#pragma once
-#include "nvim/eval/typval.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/arglist_defs.h" // IWYU pragma: export
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "arglist.h.generated.h"
#endif
-
-#endif // NVIM_ARGLIST_H
diff --git a/src/nvim/arglist_defs.h b/src/nvim/arglist_defs.h
new file mode 100644
index 0000000000..a79d540a6e
--- /dev/null
+++ b/src/nvim/arglist_defs.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "nvim/garray_defs.h"
+
+/// Argument list: Array of file names.
+/// Used for the global argument list and the argument lists local to a window.
+typedef struct arglist {
+ garray_T al_ga; ///< growarray with the array of file names
+ int al_refcount; ///< number of windows using this arglist
+ int id; ///< id of this arglist
+} alist_T;
+
+/// For each argument remember the file name as it was given, and the buffer
+/// number that contains the expanded file name (required for when ":cd" is
+/// used).
+typedef struct argentry {
+ char *ae_fname; ///< file name as specified
+ int ae_fnum; ///< buffer number with expanded file name
+} aentry_T;
diff --git a/src/nvim/ascii.h b/src/nvim/ascii_defs.h
index 96d6fe214a..4125336796 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii_defs.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_ASCII_H
-#define NVIM_ASCII_H
+#pragma once
#include <stdbool.h>
#include "nvim/func_attr.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/os/os_defs.h"
// Definitions of various common control characters.
@@ -184,5 +183,3 @@ static inline bool ascii_isspace(int c)
{
return (c >= 9 && c <= 13) || c == ' ';
}
-
-#endif // NVIM_ASCII_H
diff --git a/src/nvim/assert.h b/src/nvim/assert_defs.h
index 1941f4c33c..cfc27ee994 100644
--- a/src/nvim/assert.h
+++ b/src/nvim/assert_defs.h
@@ -1,7 +1,7 @@
-#ifndef NVIM_ASSERT_H
-#define NVIM_ASSERT_H
+#pragma once
#include "auto/config.h"
+#include "nvim/log.h"
// support static asserts (aka compile-time asserts)
@@ -57,7 +57,7 @@
// the easiest case, when the mode is C11 (generic compiler) or Clang
// advertises explicit support for c_static_assert, meaning it won't warn.
-#if __STDC_VERSION__ >= 201112L || __has_feature(c_static_assert)
+#if __STDC_VERSION__ >= 201112 || __has_feature(c_static_assert)
# define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg)
// if we're dealing with gcc >= 4.6 in C99 mode, we can still use
// _Static_assert but we need to suppress warnings, this is pretty ugly.
@@ -165,5 +165,3 @@
# define STRICT_SUB(a, b, c, t) \
do { *(c) = (t)((a) - (b)); } while (0)
#endif
-
-#endif // NVIM_ASSERT_H
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index a75ee3bbd5..696df7c534 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -72,6 +72,10 @@ return {
'InsertLeavePre', -- just before leaving Insert mode
'LspAttach', -- after an LSP client attaches to a buffer
'LspDetach', -- after an LSP client detaches from a buffer
+ 'LspRequest', -- after an LSP request is started, canceled, or completed
+ 'LspNotify', -- after an LSP notice has been sent to the server
+ 'LspTokenUpdate', -- after a visible LSP token is updated
+ 'LspProgress', -- after a LSP progress update
'MenuPopup', -- just before popup menu is displayed
'ModeChanged', -- after changing the mode
'OptionSet', -- after setting any option
@@ -81,6 +85,7 @@ return {
'RecordingEnter', -- when starting to record a macro
'RecordingLeave', -- just before a macro stops recording
'RemoteReply', -- upon string reception from a remote vim
+ 'SafeState', -- going to wait for a character
'SearchWrapped', -- after the search wrapped around
'SessionLoadPost', -- after loading a session file
'ShellCmdPost', -- after ":!cmd"
@@ -151,6 +156,10 @@ return {
DiagnosticChanged=true,
LspAttach=true,
LspDetach=true,
+ LspNotify=true,
+ LspRequest=true,
+ LspProgress=true,
+ LspTokenUpdate=true,
RecordingEnter=true,
RecordingLeave=true,
Signal=true,
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 01ebdfdafe..74a1dbdbc3 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -1,45 +1,43 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// autocmd.c: Autocommand related functions
#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <time.h>
+#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
-#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
-#include "nvim/event/defs.h"
-#include "nvim/event/loop.h"
+#include "nvim/event/multiqueue.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
-#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
-#include "nvim/garray.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
+#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/hashtab.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/insexpand.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
-#include "nvim/map.h"
-#include "nvim/memline_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
@@ -48,20 +46,24 @@
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "auevents_name_map.generated.h"
# include "autocmd.c.generated.h"
#endif
+static const char e_autocommand_nesting_too_deep[]
+ = N_("E218: Autocommand nesting too deep");
+
// Naming Conventions:
// - general autocmd behavior start with au_
// - AutoCmd start with aucmd_
@@ -70,41 +72,13 @@
// - Groups start with augroup_
// - Events start with event_
+// The autocommands are stored in a contiguous vector for each event.
//
-// The autocommands are stored in a list for each event.
-// Autocommands for the same pattern, that are consecutive, are joined
-// together, to avoid having to match the pattern too often.
-// The result is an array of Autopat lists, which point to AutoCmd lists:
-//
-// last_autopat[0] -----------------------------+
-// V
-// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL
-// Autopat.cmds Autopat.cmds
-// | |
-// V V
-// AutoCmd.next AutoCmd.next
-// | |
-// V V
-// AutoCmd.next NULL
-// |
-// V
-// NULL
-//
-// last_autopat[1] --------+
-// V
-// first_autopat[1] --> Autopat.next --> NULL
-// Autopat.cmds
-// |
-// V
-// AutoCmd.next
-// |
-// V
-// NULL
-// etc.
-//
-// The order of AutoCmds is important, this is the order in which they were
-// defined and will have to be executed.
+// The order of AutoCmds is important, this is the order in which they
+// were defined and will have to be executed.
//
+// To avoid having to match the pattern too often, patterns are reference
+// counted and reused for consecutive autocommands.
// Code for automatic commands.
static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands
@@ -121,7 +95,7 @@ static int current_augroup = AUGROUP_DEFAULT;
// Whether we need to delete marked patterns.
// While deleting autocmds, they aren't actually remover, just marked.
-static int au_need_clean = false;
+static bool au_need_clean = false;
static int autocmd_blocked = 0; // block all autocmds
@@ -130,27 +104,22 @@ static bool autocmd_include_groups = false;
static char *old_termresponse = NULL;
-/// Iterates over all the AutoPats for a particular event
-#define FOR_ALL_AUPATS_IN_EVENT(event, ap) \
- for (AutoPat *ap = first_autopat[event]; ap != NULL; ap = ap->next) // NOLINT
-
// Map of autocmd group names and ids.
// name -> ID
// ID -> name
static Map(String, int) map_augroup_name_to_id = MAP_INIT;
static Map(int, String) map_augroup_id_to_name = MAP_INIT;
-static void augroup_map_del(int id, char *name)
+static void augroup_map_del(int id, const char *name)
{
if (name != NULL) {
- String key = map_key(String, int)(&map_augroup_name_to_id, cstr_as_string(name));
- map_del(String, int)(&map_augroup_name_to_id, key);
+ String key;
+ map_del(String, int)(&map_augroup_name_to_id, cstr_as_string((char *)name), &key);
api_free_string(key);
}
if (id > 0) {
- String mapped = map_get(int, String)(&map_augroup_id_to_name, id);
+ String mapped = map_del(int, String)(&map_augroup_id_to_name, id, NULL);
api_free_string(mapped);
- map_del(int, String)(&map_augroup_id_to_name, id);
}
}
@@ -162,204 +131,191 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
return deleted_augroup;
}
-// Show the autocommands for one AutoPat.
-static void aupat_show(AutoPat *ap, event_T event, int previous_group)
+static void au_show_for_all_events(int group, const char *pat)
{
- // Check for "got_int" (here and at various places below), which is set
- // when "q" has been hit for the "--more--" prompt
- if (got_int) {
- return;
+ FOR_ALL_AUEVENTS(event) {
+ au_show_for_event(group, event, pat);
}
+}
- // pattern has been removed
- if (ap->pat == NULL) {
+static void au_show_for_event(int group, event_T event, const char *pat)
+{
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ // Return early if there are no autocmds for this event
+ if (kv_size(*acs) == 0) {
return;
}
- char *name = augroup_name(ap->group);
+ char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
+ int patlen;
+ if (*pat != NUL) {
+ patlen = (int)aucmd_pattern_length(pat);
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- // When switching groups, we need to show the new group information.
- if (ap->group != previous_group) {
- // show the group name, if it's not the default group
- if (ap->group != AUGROUP_DEFAULT) {
- if (name == NULL) {
- msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
- } else {
- msg_puts_attr(name, HL_ATTR(HLF_T));
- }
- msg_puts(" ");
+ // detect special <buffer[=X]> buffer-local patterns
+ if (aupat_is_buflocal(pat, patlen)) {
+ // normalize pat into standard "<buffer>#N" form
+ aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen));
+ pat = buflocal_pat;
+ patlen = (int)strlen(buflocal_pat);
}
- // show the event name
- msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
- msg_putchar('\n');
- if (got_int) {
+
+ if (patlen == 0) {
return;
}
+ assert(*pat != NUL);
+ } else {
+ pat = NULL;
+ patlen = 0;
}
- msg_col = 4;
- msg_outtrans(ap->pat);
-
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- // skip removed commands
- if (aucmd_exec_is_deleted(ac->exec)) {
- continue;
- }
+ // Loop through all the specified patterns.
+ while (true) {
+ AutoPat *last_ap = NULL;
+ int last_group = AUGROUP_ERROR;
+ const char *last_group_name = NULL;
- if (msg_col >= 14) {
- msg_putchar('\n');
- }
- msg_col = 14;
- if (got_int) {
- return;
- }
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
- char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
- if (ac->desc != NULL) {
- size_t msglen = 100;
- char *msg = (char *)xmallocz(msglen);
- if (ac->exec.type == CALLABLE_CB) {
- msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
- snprintf(msg, msglen, " [%s]", ac->desc);
- } else {
- snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc);
+ // Skip deleted autocommands.
+ if (ac->pat == NULL) {
+ continue;
}
- msg_outtrans(msg);
- XFREE_CLEAR(msg);
- } else if (ac->exec.type == CALLABLE_CB) {
- msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
- } else {
- msg_outtrans(exec_to_string);
- }
- XFREE_CLEAR(exec_to_string);
- if (p_verbose > 0) {
- last_set_msg(ac->script_ctx);
- }
- if (got_int) {
- return;
- }
- if (ac->next != NULL) {
- msg_putchar('\n');
- if (got_int) {
- return;
+
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if ((group != AUGROUP_ALL && ac->pat->group != group)
+ || (pat != NULL
+ && (ac->pat->patlen != patlen || strncmp(pat, ac->pat->pat, (size_t)patlen) != 0))) {
+ continue;
}
- }
- }
-}
-static void au_show_for_all_events(int group, char *pat)
-{
- FOR_ALL_AUEVENTS(event) {
- au_show_for_event(group, event, pat);
- }
-}
+ // Show event name and group only if one of them changed.
+ if (ac->pat->group != last_group) {
+ last_group = ac->pat->group;
+ last_group_name = augroup_name(ac->pat->group);
-static void au_show_for_event(int group, event_T event, char *pat)
-{
- // Return early if there are no autocmds for this event
- if (au_event_is_empty(event)) {
- return;
- }
+ if (got_int) {
+ return;
+ }
- // always need to show group information before the first pattern for the event
- int previous_group = AUGROUP_ERROR;
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
- if (*pat == NUL) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (group == AUGROUP_ALL || ap->group == group) {
- aupat_show(ap, event, previous_group);
- previous_group = ap->group;
+ // When switching groups, we need to show the new group information.
+ // show the group name, if it's not the default group
+ if (ac->pat->group != AUGROUP_DEFAULT) {
+ if (last_group_name == NULL) {
+ msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
+ } else {
+ msg_puts_attr(last_group_name, HL_ATTR(HLF_T));
+ }
+ msg_puts(" ");
+ }
+ // show the event name
+ msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
}
- }
- return;
- }
- char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
- // Loop through all the specified patterns.
- int patlen = (int)aucmd_pattern_length(pat);
- while (patlen) {
- // detect special <buffer[=X]> buffer-local patterns
- if (aupat_is_buflocal(pat, patlen)) {
- // normalize pat into standard "<buffer>#N" form
- aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen));
- pat = (char *)buflocal_pat;
- patlen = (int)strlen(buflocal_pat);
- }
+ // Show pattern only if it changed.
+ if (last_ap != ac->pat) {
+ last_ap = ac->pat;
- assert(*pat != NUL);
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
- // Find AutoPat entries with this pattern.
- // always goes at or after the last one, so start at the end.
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if ((group == AUGROUP_ALL || ap->group == group)
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- // Show autocmd's for this autopat, or buflocals <buffer=X>
- aupat_show(ap, event, previous_group);
- previous_group = ap->group;
+ msg_col = 4;
+ msg_outtrans(ac->pat->pat, 0);
+ }
+
+ if (got_int) {
+ return;
+ }
+
+ if (msg_col >= 14) {
+ msg_putchar('\n');
+ }
+ msg_col = 14;
+ if (got_int) {
+ return;
+ }
+
+ char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
+ if (ac->desc != NULL) {
+ size_t msglen = 100;
+ char *msg = xmallocz(msglen);
+ if (ac->exec.type == CALLABLE_CB) {
+ msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
+ snprintf(msg, msglen, " [%s]", ac->desc);
+ } else {
+ snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc);
}
+ msg_outtrans(msg, 0);
+ XFREE_CLEAR(msg);
+ } else if (ac->exec.type == CALLABLE_CB) {
+ msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
+ } else {
+ msg_outtrans(exec_to_string, 0);
+ }
+ XFREE_CLEAR(exec_to_string);
+ if (p_verbose > 0) {
+ last_set_msg(ac->script_ctx);
+ }
+
+ if (got_int) {
+ return;
}
}
- pat = aucmd_next_pattern(pat, (size_t)patlen);
- patlen = (int)aucmd_pattern_length(pat);
+ // If a pattern is provided, find next pattern. Otherwise exit after single iteration.
+ if (pat != NULL) {
+ pat = aucmd_next_pattern(pat, (size_t)patlen);
+ patlen = (int)aucmd_pattern_length(pat);
+ if (patlen == 0) {
+ break;
+ }
+ } else {
+ break;
+ }
}
}
-// Mark an autocommand handler for deletion.
-static void aupat_del(AutoPat *ap)
-{
- XFREE_CLEAR(ap->pat);
- ap->buflocal_nr = -1;
- au_need_clean = true;
-}
-
-void aupat_del_for_event_and_group(event_T event, int group)
+// Delete autocommand.
+static void aucmd_del(AutoCmd *ac)
{
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == group) {
- aupat_del(ap);
- }
+ if (ac->pat != NULL && --ac->pat->refcount == 0) {
+ XFREE_CLEAR(ac->pat->pat);
+ vim_regfree(ac->pat->reg_prog);
+ xfree(ac->pat);
}
+ ac->pat = NULL;
+ aucmd_exec_free(&ac->exec);
+ XFREE_CLEAR(ac->desc);
- au_cleanup();
+ au_need_clean = true;
}
-// Mark all commands for a pattern for deletion.
-static void aupat_remove_cmds(AutoPat *ap)
+void aucmd_del_for_event_and_group(event_T event, int group)
{
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- aucmd_exec_free(&ac->exec);
-
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat != NULL && ac->pat->group == group) {
+ aucmd_del(ac);
}
}
- au_need_clean = true;
-}
-// Delete one command from an autocmd pattern.
-static void aucmd_del(AutoCmd *ac)
-{
- aucmd_exec_free(&ac->exec);
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
- }
- au_need_clean = true;
+ au_cleanup();
}
-/// Cleanup autocommands and patterns that have been deleted.
+/// Cleanup autocommands that have been deleted.
/// This is only done when not executing autocommands.
static void au_cleanup(void)
{
@@ -369,72 +325,39 @@ static void au_cleanup(void)
// Loop over all events.
FOR_ALL_AUEVENTS(event) {
- // Loop over all autocommand patterns.
- AutoPat **prev_ap = &(first_autopat[(int)event]);
- for (AutoPat *ap = *prev_ap; ap != NULL; ap = *prev_ap) {
- bool has_cmd = false;
-
- // Loop over all commands for this pattern.
- AutoCmd **prev_ac = &(ap->cmds);
- for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
- // Remove the command if the pattern is to be deleted or when
- // the command has been marked for deletion.
- if (ap->pat == NULL || aucmd_exec_is_deleted(ac->exec)) {
- *prev_ac = ac->next;
- aucmd_exec_free(&ac->exec);
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
- }
-
- xfree(ac);
- } else {
- has_cmd = true;
- prev_ac = &(ac->next);
- }
+ // Loop over all autocommands.
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ size_t nsize = 0;
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (nsize != i) {
+ kv_A(*acs, nsize) = *ac;
}
-
- if (ap->pat != NULL && !has_cmd) {
- // Pattern was not marked for deletion, but all of its commands were.
- // So mark the pattern for deletion.
- aupat_del(ap);
- }
-
- // Remove the pattern if it has been marked for deletion.
- if (ap->pat == NULL) {
- if (ap->next == NULL) {
- if (prev_ap == &(first_autopat[(int)event])) {
- last_autopat[(int)event] = NULL;
- } else {
- // this depends on the "next" field being the first in
- // the struct
- last_autopat[(int)event] = (AutoPat *)prev_ap;
- }
- }
- *prev_ap = ap->next;
- vim_regfree(ap->reg_prog);
- xfree(ap);
- } else {
- prev_ap = &(ap->next);
+ if (ac->pat != NULL) {
+ nsize++;
}
}
+ if (nsize == 0) {
+ kv_destroy(*acs);
+ } else {
+ acs->size = nsize;
+ }
}
au_need_clean = false;
}
-// Get the first AutoPat for a particular event.
-AutoPat *au_get_autopat_for_event(event_T event)
+AutoCmdVec *au_get_autocmds_for_event(event_T event)
FUNC_ATTR_PURE
{
- return first_autopat[(int)event];
+ return &autocmds[(int)event];
}
-// Called when buffer is freed, to remove/invalidate related buffer-local
-// autocmds.
+// Called when buffer is freed, to remove/invalidate related buffer-local autocmds.
void aubuflocal_remove(buf_T *buf)
{
// invalidate currently executing autocommands
- for (AutoPatCmd *apc = active_apc_list; apc; apc = apc->next) {
+ for (AutoPatCmd *apc = active_apc_list; apc != NULL; apc = apc->next) {
if (buf->b_fnum == apc->arg_bufnr) {
apc->arg_bufnr = 0;
}
@@ -442,16 +365,19 @@ void aubuflocal_remove(buf_T *buf)
// invalidate buflocals looping through events
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->buflocal_nr == buf->b_fnum) {
- aupat_del(ap);
-
- if (p_verbose >= 6) {
- verbose_enter();
- smsg(_("auto-removing autocommand: %s <buffer=%d>"),
- event_nr2name(event), buf->b_fnum);
- verbose_leave();
- }
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat == NULL || ac->pat->buflocal_nr != buf->b_fnum) {
+ continue;
+ }
+
+ aucmd_del(ac);
+
+ if (p_verbose >= 6) {
+ verbose_enter();
+ smsg(0, _("auto-removing autocommand: %s <buffer=%d>"), event_nr2name(event), buf->b_fnum);
+ verbose_leave();
}
}
}
@@ -460,7 +386,7 @@ void aubuflocal_remove(buf_T *buf)
// Add an autocmd group name or return existing group matching name.
// Return its ID.
-int augroup_add(char *name)
+int augroup_add(const char *name)
{
assert(STRICMP(name, "end") != 0);
@@ -496,20 +422,21 @@ int augroup_add(char *name)
/// `--- DELETED ---` groups around)
void augroup_del(char *name, bool stupid_legacy_mode)
{
- int i = augroup_find(name);
- if (i == AUGROUP_ERROR) { // the group doesn't exist
+ int group = augroup_find(name);
+ if (group == AUGROUP_ERROR) { // the group doesn't exist
semsg(_("E367: No such group: \"%s\""), name);
return;
- }
- if (i == current_augroup) {
+ } else if (group == current_augroup) {
emsg(_("E936: Cannot delete the current group"));
return;
}
if (stupid_legacy_mode) {
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == i && ap->pat != NULL) {
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ if (ap != NULL && ap->group == group) {
give_warning(_("W19: Deleting augroup that is still in use"), true);
map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED);
augroup_map_del(ap->group, NULL);
@@ -519,16 +446,18 @@ void augroup_del(char *name, bool stupid_legacy_mode)
}
} else {
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == i) {
- aupat_del(ap);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat != NULL && ac->pat->group == group) {
+ aucmd_del(ac);
}
}
}
}
// Remove the group because it's not currently in use.
- augroup_map_del(i, name);
+ augroup_map_del(group, name);
au_cleanup();
}
@@ -637,28 +566,25 @@ void do_augroup(char *arg, int del_group)
void free_all_autocmds(void)
{
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- aupat_del(ap);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ aucmd_del(&kv_A(*acs, i));
}
+ kv_destroy(*acs);
+ au_need_clean = false;
}
- au_need_clean = true;
- au_cleanup();
-
// Delete the augroup_map, including free the data
String name;
- int id;
- map_foreach(&map_augroup_name_to_id, name, id, {
- (void)id;
+ map_foreach_key(&map_augroup_name_to_id, name, {
api_free_string(name);
})
- map_destroy(String, int)(&map_augroup_name_to_id);
+ map_destroy(String, &map_augroup_name_to_id);
- map_foreach(&map_augroup_id_to_name, id, name, {
- (void)id;
+ map_foreach_value(&map_augroup_id_to_name, name, {
api_free_string(name);
})
- map_destroy(int, String)(&map_augroup_id_to_name);
+ map_destroy(int, &map_augroup_id_to_name);
// aucmd_win[] is freed in win_free_all()
}
@@ -675,9 +601,9 @@ bool is_aucmd_win(win_T *win)
return false;
}
-// Return the event number for event name "start".
-// Return NUM_EVENTS if the event name was not found.
-// Return a pointer to the next event name in "end".
+/// Return the event number for event name "start".
+/// Return NUM_EVENTS if the event name was not found.
+/// Return a pointer to the next event name in "end".
event_T event_name2nr(const char *start, char **end)
{
const char *p;
@@ -701,6 +627,18 @@ event_T event_name2nr(const char *start, char **end)
return event_names[i].event;
}
+/// Return the event number for event name "str".
+/// Return NUM_EVENTS if the event name was not found.
+event_T event_name2nr_str(String str)
+{
+ for (int i = 0; event_names[i].name != NULL; i++) {
+ if (str.size == event_names[i].len && STRNICMP(str.data, event_names[i].name, str.size) == 0) {
+ return event_names[i].event;
+ }
+ }
+ return NUM_EVENTS;
+}
+
/// Return the name for event
///
/// @param[in] event Event to return name for.
@@ -728,8 +666,7 @@ static bool event_ignored(event_T event)
while (*p != NUL) {
if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
return true;
- }
- if (event_name2nr(p, &p) == event) {
+ } else if (event_name2nr(p, &p) == event) {
return true;
}
}
@@ -901,7 +838,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
}
}
- bool is_showing = !forceit && *cmd == NUL;
+ const bool is_showing = !forceit && *cmd == NUL;
// Print header when showing autocommands.
if (is_showing) {
@@ -926,8 +863,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
event_T event = event_name2nr(arg, &arg);
assert(event < NUM_EVENTS);
- if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
- == FAIL) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) == FAIL) {
break;
}
}
@@ -940,11 +876,10 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
xfree(envpat);
}
-void do_all_autocmd_events(char *pat, bool once, int nested, char *cmd, bool delete, int group)
+void do_all_autocmd_events(const char *pat, bool once, int nested, char *cmd, bool del, int group)
{
FOR_ALL_AUEVENTS(event) {
- if (do_autocmd_event(event, pat, once, nested, cmd, delete, group)
- == FAIL) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, del, group) == FAIL) {
return;
}
}
@@ -957,30 +892,21 @@ void do_all_autocmd_events(char *pat, bool once, int nested, char *cmd, bool del
// If *cmd == NUL: show entries.
// If forceit == true: delete entries.
// If group is not AUGROUP_ALL: only use this group.
-int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd, bool delete,
+int do_autocmd_event(event_T event, const char *pat, bool once, int nested, char *cmd, bool del,
int group)
FUNC_ATTR_NONNULL_ALL
{
// Cannot be used to show all patterns. See au_show_for_event or au_show_for_all_events
- assert(*pat != NUL || delete);
+ assert(*pat != NUL || del);
- AutoPat *ap;
- AutoPat **prev_ap;
- int findgroup;
- int buflocal_nr;
char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
bool is_adding_cmd = *cmd != NUL;
-
- if (group == AUGROUP_ALL) {
- findgroup = current_augroup;
- } else {
- findgroup = group;
- }
+ const int findgroup = group == AUGROUP_ALL ? current_augroup : group;
// Delete all aupat for an event.
- if (*pat == NUL && delete) {
- aupat_del_for_event_and_group(event, findgroup);
+ if (*pat == NUL && del) {
+ aucmd_del_for_event_and_group(event, findgroup);
return OK;
}
@@ -989,9 +915,8 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
while (patlen) {
// detect special <buffer[=X]> buffer-local patterns
int is_buflocal = aupat_is_buflocal(pat, patlen);
-
if (is_buflocal) {
- buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
+ const int buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
// normalize pat into standard "<buffer>#N" form
aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr);
@@ -1000,34 +925,28 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
patlen = (int)strlen(buflocal_pat);
}
- if (delete) {
+ if (del) {
assert(*pat != NUL);
- // Find AutoPat entries with this pattern.
- prev_ap = &first_autopat[(int)event];
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if (ap->group == findgroup
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- // Remove existing autocommands.
- // If adding any new autocmd's for this AutoPat, don't
- // delete the pattern from the autopat list, append to
- // this list.
- if (is_adding_cmd && ap->next == NULL) {
- aupat_remove_cmds(ap);
- break;
- }
- aupat_del(ap);
- }
+ // Find existing autocommands with this pattern.
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if (ap != NULL && ap->group == findgroup && ap->patlen == patlen
+ && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
+ // Remove existing autocommands.
+ // If adding any new autocmd's for this AutoPat, don't
+ // delete the pattern from the autopat list, append to
+ // this list.
+ aucmd_del(ac);
}
- prev_ap = &ap->next;
}
}
@@ -1046,32 +965,23 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
return OK;
}
-int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group, bool once,
+int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int group, bool once,
bool nested, char *desc, AucmdExecutable aucmd)
{
// 0 is not a valid group.
assert(group != 0);
- AutoPat *ap;
- AutoPat **prev_ap;
- AutoCmd *ac;
- int findgroup;
- char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
-
if (patlen > (int)strlen(pat)) {
return FAIL;
}
- if (group == AUGROUP_ALL) {
- findgroup = current_augroup;
- } else {
- findgroup = group;
- }
+ const int findgroup = group == AUGROUP_ALL ? current_augroup : group;
// detect special <buffer[=X]> buffer-local patterns
- int is_buflocal = aupat_is_buflocal(pat, patlen);
+ const int is_buflocal = aupat_is_buflocal(pat, patlen);
int buflocal_nr = 0;
+ char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
if (is_buflocal) {
buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
@@ -1082,67 +992,52 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
patlen = (int)strlen(buflocal_pat);
}
- // always goes at or after the last one, so start at the end.
- if (last_autopat[(int)event] != NULL) {
- prev_ap = &last_autopat[(int)event];
- } else {
- prev_ap = &first_autopat[(int)event];
- }
-
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if (ap->group == findgroup
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- if (ap->next == NULL) {
- // Add autocmd to this autopat, if it's the last one.
- break;
- }
- }
+ // Try to reuse pattern from the last existing autocommand.
+ AutoPat *ap = NULL;
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (ptrdiff_t i = (ptrdiff_t)kv_size(*acs) - 1; i >= 0; i--) {
+ ap = kv_A(*acs, i).pat;
+ if (ap == NULL) {
+ continue; // Skip deleted autocommands.
+ }
+ // Set result back to NULL if the last pattern doesn't match.
+ if (ap->group != findgroup || ap->patlen != patlen
+ || strncmp(pat, ap->pat, (size_t)patlen) != 0) {
+ ap = NULL;
}
- prev_ap = &ap->next;
+ break;
}
- // If the pattern we want to add a command to does appear at the
- // end of the list (or not is not in the list at all), add the
- // pattern at the end of the list.
+ // No matching pattern found, allocate a new one.
if (ap == NULL) {
// refuse to add buffer-local ap if buffer number is invalid
- if (is_buflocal
- && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
+ if (is_buflocal && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
return FAIL;
}
ap = xmalloc(sizeof(AutoPat));
- ap->pat = xstrnsave(pat, (size_t)patlen);
- ap->patlen = patlen;
if (is_buflocal) {
ap->buflocal_nr = buflocal_nr;
ap->reg_prog = NULL;
} else {
- char *reg_pat;
-
ap->buflocal_nr = 0;
- reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true);
+ char *reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true);
if (reg_pat != NULL) {
ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
}
xfree(reg_pat);
if (reg_pat == NULL || ap->reg_prog == NULL) {
- xfree(ap->pat);
xfree(ap);
return FAIL;
}
}
+ ap->refcount = 0;
+ ap->pat = xmemdupz(pat, (size_t)patlen);
+ ap->patlen = patlen;
+
// need to initialize last_mode for the first ModeChanged autocmd
if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) {
get_mode(last_mode);
@@ -1151,7 +1046,8 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
// If the event is CursorMoved, update the last cursor position
// position to avoid immediately triggering the autocommand
if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) {
- curwin->w_last_cursormoved = curwin->w_cursor;
+ last_cursormoved_win = curwin;
+ last_cursormoved = curwin->w_cursor;
}
// Initialize the fields checked by the WinScrolled and
@@ -1169,53 +1065,34 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
use_tabpage(save_curtab);
}
- ap->cmds = NULL;
- *prev_ap = ap;
- last_autopat[(int)event] = ap;
- ap->next = NULL;
- if (group == AUGROUP_ALL) {
- ap->group = current_augroup;
- } else {
- ap->group = group;
- }
- }
-
- // Add the autocmd at the end of the AutoCmd list.
- AutoCmd **prev_ac = &(ap->cmds);
- while ((ac = *prev_ac) != NULL) {
- prev_ac = &ac->next;
+ ap->group = group == AUGROUP_ALL ? current_augroup : group;
}
- ac = xmalloc(sizeof(AutoCmd));
- *prev_ac = ac;
+ ap->refcount++;
+ // Add the autocmd at the end of the AutoCmd vector.
+ AutoCmd *ac = kv_pushp(autocmds[(int)event]);
+ ac->pat = ap;
ac->id = id;
ac->exec = aucmd_exec_copy(aucmd);
ac->script_ctx = current_sctx;
ac->script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&ac->script_ctx);
- ac->next = NULL;
ac->once = once;
ac->nested = nested;
- ac->desc = NULL;
-
- // TODO(tjdevries): What to do about :autocmd and where/how to show lua stuffs there.
- // perhaps: <lua>DESCRIPTION or similar
- if (desc != NULL) {
- ac->desc = xstrdup(desc);
- }
+ ac->desc = desc == NULL ? NULL : xstrdup(desc);
return OK;
}
-size_t aucmd_pattern_length(char *pat)
+size_t aucmd_pattern_length(const char *pat)
FUNC_ATTR_PURE
{
if (*pat == NUL) {
return 0;
}
- char *endpat;
+ const char *endpat;
for (; *pat; pat = endpat + 1) {
// Find end of the pattern.
@@ -1226,8 +1103,7 @@ size_t aucmd_pattern_length(char *pat)
continue;
}
int brace_level = 0;
- for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
- endpat++) {
+ for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); endpat++) {
if (*endpat == '{') {
brace_level++;
} else if (*endpat == '}') {
@@ -1241,14 +1117,13 @@ size_t aucmd_pattern_length(char *pat)
return strlen(pat);
}
-char *aucmd_next_pattern(char *pat, size_t patlen)
+const char *aucmd_next_pattern(const char *pat, size_t patlen)
FUNC_ATTR_PURE
{
pat = pat + patlen;
if (*pat == ',') {
pat = pat + 1;
}
-
return pat;
}
@@ -1291,7 +1166,7 @@ int do_doautocmd(char *arg_start, bool do_msg, bool *did_something)
}
if (nothing_done && do_msg && !aborting()) {
- smsg(_("No matching autocommands: %s"), arg_start);
+ smsg(0, _("No matching autocommands: %s"), arg_start);
}
if (did_something != NULL) {
*did_something = !nothing_done;
@@ -1422,6 +1297,8 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
aco->save_curwin_handle = curwin->handle;
aco->save_curbuf = curbuf;
aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle;
+ aco->save_State = State;
+
if (win != NULL) {
// There is a window for "buf" in the current tab page, make it the
// curwin. This is preferred, it has the least side effects (esp. if
@@ -1448,7 +1325,7 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
block_autocmds(); // We don't want BufEnter/WinEnter autocommands.
if (need_append) {
win_append(lastwin, auc_win);
- pmap_put(handle_T)(&window_handles, auc_win->handle, auc_win);
+ pmap_put(int)(&window_handles, auc_win->handle, auc_win);
win_config_float(auc_win, auc_win->w_float_config);
}
// Prevent chdir() call in win_enter_ext(), through do_autochdir()
@@ -1480,7 +1357,6 @@ void aucmd_restbuf(aco_save_T *aco)
if (aco->use_aucmd_win_idx >= 0) {
win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win;
- curbuf->b_nwindows--;
// Find "awp", it can't be closed, but it may be in another tab page.
// Do not trigger autocommands here.
block_autocmds();
@@ -1496,9 +1372,17 @@ void aucmd_restbuf(aco_save_T *aco)
}
}
win_found:
-
+ curbuf->b_nwindows--;
+ const bool save_stop_insert_mode = stop_insert_mode;
+ // May need to stop Insert mode if we were in a prompt buffer.
+ leaving_window(curwin);
+ // Do not stop Insert mode when already in Insert mode before.
+ if (aco->save_State & MODE_INSERT) {
+ stop_insert_mode = save_stop_insert_mode;
+ }
+ // Remove the window.
win_remove(curwin, NULL);
- pmap_del(handle_T)(&window_handles, curwin->handle);
+ pmap_del(int)(&window_handles, curwin->handle, NULL);
if (curwin->w_grid_alloc.chars != NULL) {
ui_comp_remove_grid(&curwin->w_grid_alloc);
ui_call_win_hide(curwin->w_grid_alloc.handle);
@@ -1535,6 +1419,7 @@ win_found:
globaldir = aco->globaldir;
// the buffer contents may have changed
+ VIsual_active = aco->save_VIsual_active;
check_cursor();
if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
curwin->w_topline = curbuf->b_ml.ml_line_count;
@@ -1563,14 +1448,16 @@ win_found:
curwin = save_curwin;
curbuf = curwin->w_buffer;
prevwin = win_find_by_handle(aco->save_prevwin_handle);
+
// In case the autocommand moves the cursor to a position that does not
// exist in curbuf
+ VIsual_active = aco->save_VIsual_active;
check_cursor();
}
}
- check_cursor(); // just in case lines got deleted
VIsual_active = aco->save_VIsual_active;
+ check_cursor(); // just in case lines got deleted
if (VIsual_active) {
check_pos(curbuf, &VIsual);
}
@@ -1627,8 +1514,7 @@ bool apply_autocmds_retval(event_T event, char *fname, char *fname_io, bool forc
return false;
}
- bool did_cmd = apply_autocmds_group(event, fname, fname_io, force,
- AUGROUP_ALL, buf, NULL, NULL);
+ bool did_cmd = apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, NULL, NULL);
if (did_cmd && aborting()) {
*retval = FAIL;
}
@@ -1640,7 +1526,7 @@ bool apply_autocmds_retval(event_T event, char *fname, char *fname_io, bool forc
/// @param event the autocommand to check
bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return first_autopat[event] != NULL;
+ return kv_size(autocmds[(int)event]) != 0;
}
/// Return true when there is a CursorHold/CursorHoldI autocommand defined for
@@ -1648,7 +1534,6 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return has_event((get_real_state() == MODE_NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI));
- // return first_autopat[] != NULL;
}
/// Return true if the CursorHold/CursorHoldI event can be triggered.
@@ -1682,9 +1567,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
char *sfname = NULL; // short file name
bool retval = false;
static int nesting = 0;
- AutoPat *ap;
char *save_cmdarg;
- long save_cmdbang;
+ varnumber_T save_cmdbang;
static int filechangeshell_busy = false;
proftime_T wait_time;
bool did_save_redobuff = false;
@@ -1693,8 +1577,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Quickly return if there are no autocommands for this event or
// autocommands are blocked.
- if (event == NUM_EVENTS || first_autopat[(int)event] == NULL
- || is_autocmd_blocked()) {
+ if (event == NUM_EVENTS || kv_size(autocmds[(int)event]) == 0 || is_autocmd_blocked()) {
goto BYPASS_AU;
}
@@ -1712,8 +1595,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// FileChangedShell never nests, because it can create an endless loop.
if (filechangeshell_busy
- && (event == EVENT_FILECHANGEDSHELL
- || event == EVENT_FILECHANGEDSHELLPOST)) {
+ && (event == EVENT_FILECHANGEDSHELL || event == EVENT_FILECHANGEDSHELLPOST)) {
goto BYPASS_AU;
}
@@ -1725,20 +1607,20 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Allow nesting of autocommands, but restrict the depth, because it's
// possible to create an endless loop.
if (nesting == 10) {
- emsg(_("E218: autocommand nesting too deep"));
+ emsg(_(e_autocommand_nesting_too_deep));
goto BYPASS_AU;
}
// Check if these autocommands are disabled. Used when doing ":all" or
// ":ball".
if ((autocmd_no_enter && (event == EVENT_WINENTER || event == EVENT_BUFENTER))
- || (autocmd_no_leave
- && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) {
+ || (autocmd_no_leave && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) {
goto BYPASS_AU;
}
// Save the autocmd_* variables and info about the current buffer.
char *save_autocmd_fname = autocmd_fname;
+ bool save_autocmd_fname_full = autocmd_fname_full;
int save_autocmd_bufnr = autocmd_bufnr;
char *save_autocmd_match = autocmd_match;
int save_autocmd_busy = autocmd_busy;
@@ -1767,13 +1649,10 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Allocate MAXPATHL for when eval_vars() resolves the fullpath.
autocmd_fname = xstrnsave(autocmd_fname, MAXPATHL);
}
+ autocmd_fname_full = false; // call FullName_save() later
// Set the buffer number to be used for <abuf>.
- if (buf == NULL) {
- autocmd_bufnr = 0;
- } else {
- autocmd_bufnr = buf->b_fnum;
- }
+ autocmd_bufnr = buf == NULL ? 0 : buf->b_fnum;
// When the file name is NULL or empty, use the file name of buffer "buf".
// Always use the full path of the file name to match with, in case
@@ -1814,6 +1693,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
|| event == EVENT_USER || event == EVENT_WINCLOSED
|| event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) {
fname = xstrdup(fname);
+ autocmd_fname_full = true; // don't expand it later
} else {
fname = FullName_save(fname, false);
}
@@ -1876,18 +1756,24 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Find first autocommand that matches
AutoPatCmd patcmd = {
- .curpat = first_autopat[(int)event],
- .group = group,
+ // aucmd_next will set lastpat back to NULL if there are no more autocommands left to run
+ .lastpat = NULL,
+ // current autocommand index
+ .auidx = 0,
+ // save vector size, to avoid an endless loop when more patterns
+ // are added when executing autocommands
+ .ausize = kv_size(autocmds[(int)event]),
.fname = fname,
.sfname = sfname,
.tail = tail,
+ .group = group,
.event = event,
.arg_bufnr = autocmd_bufnr,
};
- auto_next_pat(&patcmd, false);
+ aucmd_next(&patcmd);
- // found one, start executing the autocommands
- if (patcmd.curpat != NULL) {
+ // Found first autocommand, start executing them
+ if (patcmd.lastpat != NULL) {
// add to active_apc_list
patcmd.next = active_apc_list;
active_apc_list = &patcmd;
@@ -1896,20 +1782,14 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
patcmd.data = data;
// set v:cmdarg (only when there is a matching pattern)
- save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG);
+ save_cmdbang = get_vim_var_nr(VV_CMDBANG);
if (eap != NULL) {
save_cmdarg = set_cmdarg(eap, NULL);
- set_vim_var_nr(VV_CMDBANG, (long)eap->forceit);
+ set_vim_var_nr(VV_CMDBANG, eap->forceit);
} else {
save_cmdarg = NULL; // avoid gcc warning
}
retval = true;
- // mark the last pattern, to avoid an endless loop when more patterns
- // are added when executing autocommands
- for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) {
- ap->last = false;
- }
- ap->last = true;
// Make sure cursor and topline are valid. The first time the current
// values are saved, restored by reset_lnums(). When nested only the
@@ -1920,9 +1800,14 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
check_lnums_nested(true);
}
+ const int save_did_emsg = did_emsg;
+ const bool save_ex_pressedreturn = get_pressedreturn();
+
// Execute the autocmd. The `getnextac` callback handles iteration.
- do_cmdline(NULL, getnextac, (void *)&patcmd,
- DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
+ do_cmdline(NULL, getnextac, &patcmd, DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
+
+ did_emsg += save_did_emsg;
+ set_pressedreturn(save_ex_pressedreturn);
if (nesting == 1) {
// restore cursor and topline, unless they were changed
@@ -1947,6 +1832,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
estack_pop();
xfree(autocmd_fname);
autocmd_fname = save_autocmd_fname;
+ autocmd_fname_full = save_autocmd_fname_full;
autocmd_bufnr = save_autocmd_bufnr;
autocmd_match = save_autocmd_match;
current_sctx = save_current_sctx;
@@ -2028,8 +1914,7 @@ void unblock_autocmds(void)
// When v:termresponse was set while autocommands were blocked, trigger
// the autocommands now. Esp. useful when executing a shell command
// during startup (nvim -d).
- if (!is_autocmd_blocked()
- && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
+ if (!is_autocmd_blocked() && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, false, curbuf);
}
}
@@ -2040,91 +1925,84 @@ bool is_autocmd_blocked(void)
return autocmd_blocked != 0;
}
-/// Find next autocommand pattern that matches.
-/// stop when 'last' flag is set
-void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
+/// Find next matching autocommand.
+/// If next autocommand was not found, sets lastpat to NULL and cmdidx to SIZE_MAX on apc.
+static void aucmd_next(AutoPatCmd *apc)
{
- AutoPat *ap;
- AutoCmd *cp;
- char *s;
-
estack_T *const entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1;
- // Clear the exestack entry for this ETYPE_AUCMD entry.
- XFREE_CLEAR(entry->es_name);
- entry->es_info.aucmd = NULL;
+ AutoCmdVec *const acs = &autocmds[(int)apc->event];
+ assert(apc->ausize <= kv_size(*acs));
+ for (size_t i = apc->auidx; i < apc->ausize && !got_int; i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
- for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) {
- apc->curpat = NULL;
-
- // Only use a pattern when it has not been removed, has commands and
- // the group matches. For buffer-local autocommands only check the
- // buffer number.
- if (ap->pat != NULL && ap->cmds != NULL
- && (apc->group == AUGROUP_ALL || apc->group == ap->group)) {
- // execution-condition
+ // Skip deleted autocommands.
+ if (ap == NULL) {
+ continue;
+ }
+ // Skip matching if pattern didn't change.
+ if (ap != apc->lastpat) {
+ // Skip autocommands that don't match the group.
+ if (apc->group != AUGROUP_ALL && apc->group != ap->group) {
+ continue;
+ }
+ // Skip autocommands that don't match the pattern or buffer number.
if (ap->buflocal_nr == 0
- ? match_file_pat(NULL,
- &ap->reg_prog,
- apc->fname,
- apc->sfname,
- apc->tail,
- ap->allow_dirs)
- : ap->buflocal_nr == apc->arg_bufnr) {
- const char *const name = event_nr2name(apc->event);
- s = _("%s Autocommands for \"%s\"");
-
- const size_t sourcing_name_len
- = (strlen(s) + strlen(name) + (size_t)ap->patlen + 1);
-
- char *const namep = xmalloc(sourcing_name_len);
- snprintf(namep, sourcing_name_len, s, name, ap->pat);
- if (p_verbose >= 8) {
- verbose_enter();
- smsg(_("Executing %s"), namep);
- verbose_leave();
- }
+ ? !match_file_pat(NULL, &ap->reg_prog, apc->fname, apc->sfname, apc->tail, ap->allow_dirs)
+ : ap->buflocal_nr != apc->arg_bufnr) {
+ continue;
+ }
- // Update the exestack entry for this autocmd.
- entry->es_name = namep;
- entry->es_info.aucmd = apc;
+ const char *const name = event_nr2name(apc->event);
+ const char *const s = _("%s Autocommands for \"%s\"");
- apc->curpat = ap;
- apc->nextcmd = ap->cmds;
- // mark last command
- for (cp = ap->cmds; cp->next != NULL; cp = cp->next) {
- cp->last = false;
- }
- cp->last = true;
+ const size_t sourcing_name_len = strlen(s) + strlen(name) + (size_t)ap->patlen + 1;
+ char *const namep = xmalloc(sourcing_name_len);
+ snprintf(namep, sourcing_name_len, s, name, ap->pat);
+ if (p_verbose >= 8) {
+ verbose_enter();
+ smsg(0, _("Executing %s"), namep);
+ verbose_leave();
}
- line_breakcheck();
- if (apc->curpat != NULL) { // found a match
- break;
- }
- }
- if (stop_at_last && ap->last) {
- break;
+
+ // Update the exestack entry for this autocmd.
+ XFREE_CLEAR(entry->es_name);
+ entry->es_name = namep;
+ entry->es_info.aucmd = apc;
}
+
+ apc->lastpat = ap;
+ apc->auidx = i;
+
+ line_breakcheck();
+ return;
}
+
+ // Clear the exestack entry for this ETYPE_AUCMD entry.
+ XFREE_CLEAR(entry->es_name);
+ entry->es_info.aucmd = NULL;
+
+ apc->lastpat = NULL;
+ apc->auidx = SIZE_MAX;
}
static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
{
- bool ret = false;
Callback callback = ac->exec.callable.cb;
if (callback.type == kCallbackLua) {
Dictionary data = ARRAY_DICT_INIT;
PUT(data, "id", INTEGER_OBJ(ac->id));
PUT(data, "event", CSTR_TO_OBJ(event_nr2name(apc->event)));
- PUT(data, "match", CSTR_TO_OBJ((char *)autocmd_match));
- PUT(data, "file", CSTR_TO_OBJ((char *)autocmd_fname));
+ PUT(data, "match", CSTR_TO_OBJ(autocmd_match));
+ PUT(data, "file", CSTR_TO_OBJ(autocmd_fname));
PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr));
if (apc->data) {
PUT(data, "data", copy_object(*apc->data, NULL));
}
- int group = apc->curpat->group;
+ int group = ac->pat->group;
switch (group) {
case AUGROUP_ERROR:
abort(); // unreachable
@@ -2142,18 +2020,19 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
ADD_C(args, DICTIONARY_OBJ(data));
Object result = nlua_call_ref(callback.data.luaref, NULL, args, true, NULL);
+ bool ret = false;
if (result.type == kObjectTypeBoolean) {
ret = result.data.boolean;
}
api_free_dictionary(data);
api_free_object(result);
+ return ret;
} else {
typval_T argsin = TV_INITIAL_VALUE;
typval_T rettv = TV_INITIAL_VALUE;
callback_call(&callback, 0, &argsin, &rettv);
+ return false;
}
-
- return ret;
}
/// Get next autocommand command.
@@ -2166,51 +2045,23 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
(void)indent;
(void)do_concat;
- AutoPatCmd *acp = (AutoPatCmd *)cookie;
- char *retval;
+ AutoPatCmd *const apc = (AutoPatCmd *)cookie;
+ AutoCmdVec *const acs = &autocmds[(int)apc->event];
- // Can be called again after returning the last line.
- if (acp->curpat == NULL) {
+ aucmd_next(apc);
+ if (apc->lastpat == NULL) {
return NULL;
}
- // repeat until we find an autocommand to execute
- for (;;) {
- // skip removed commands
- while (acp->nextcmd != NULL
- && aucmd_exec_is_deleted(acp->nextcmd->exec)) {
- if (acp->nextcmd->last) {
- acp->nextcmd = NULL;
- } else {
- acp->nextcmd = acp->nextcmd->next;
- }
- }
-
- if (acp->nextcmd != NULL) {
- break;
- }
-
- // at end of commands, find next pattern that matches
- if (acp->curpat->last) {
- acp->curpat = NULL;
- } else {
- acp->curpat = acp->curpat->next;
- }
- if (acp->curpat != NULL) {
- auto_next_pat(acp, true);
- }
- if (acp->curpat == NULL) {
- return NULL;
- }
- }
-
- AutoCmd *ac = acp->nextcmd;
+ assert(apc->auidx < kv_size(*acs));
+ AutoCmd *const ac = &kv_A(*acs, apc->auidx);
+ assert(ac->pat != NULL);
bool oneshot = ac->once;
if (p_verbose >= 9) {
verbose_enter_scroll();
char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
- smsg(_("autocommand %s"), exec_to_string);
+ smsg(0, _("autocommand %s"), exec_to_string);
msg_puts("\n"); // don't overwrite this either
XFREE_CLEAR(exec_to_string);
verbose_leave_scroll();
@@ -2220,10 +2071,12 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
// lua code, so that it works properly
autocmd_nested = ac->nested;
current_sctx = ac->script_ctx;
- acp->script_ctx = current_sctx;
+ apc->script_ctx = current_sctx;
+ char *retval;
if (ac->exec.type == CALLABLE_CB) {
- if (call_autocmd_callback(ac, acp)) {
+ // Can potentially reallocate kvec_t data and invalidate the ac pointer
+ if (call_autocmd_callback(ac, apc)) {
// If an autocommand callback returns true, delete the autocommand
oneshot = true;
}
@@ -2238,19 +2091,20 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
// 2. make where we call do_cmdline for autocmds not have to return anything,
// and instead we loop over all the matches and just execute one-by-one.
// However, my expectation would be that could be expensive.
- retval = xstrdup("");
+ retval = xcalloc(1, 1);
} else {
retval = xstrdup(ac->exec.callable.cmd);
}
// Remove one-shot ("once") autocmd in anticipation of its execution.
if (oneshot) {
- aucmd_del(ac);
+ aucmd_del(&kv_A(*acs, apc->auidx));
}
- if (ac->last) {
- acp->nextcmd = NULL;
+
+ if (apc->auidx < apc->ausize) {
+ apc->auidx++;
} else {
- acp->nextcmd = ac->next;
+ apc->auidx = SIZE_MAX;
}
return retval;
@@ -2282,15 +2136,12 @@ bool has_autocmd(event_T event, char *sfname, buf_T *buf)
forward_slash(fname);
#endif
- for (AutoPat *ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (ap->pat != NULL && ap->cmds != NULL
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ if (ap != NULL
&& (ap->buflocal_nr == 0
- ? match_file_pat(NULL,
- &ap->reg_prog,
- fname,
- sfname,
- tail,
- ap->allow_dirs)
+ ? match_file_pat(NULL, &ap->reg_prog, fname, sfname, tail, ap->allow_dirs)
: buf != NULL && ap->buflocal_nr == buf->b_fnum)) {
retval = true;
break;
@@ -2305,13 +2156,10 @@ bool has_autocmd(event_T event, char *sfname, buf_T *buf)
return retval;
}
-// Function given to ExpandGeneric() to obtain the list of autocommand group
-// names.
+// Function given to ExpandGeneric() to obtain the list of autocommand group names.
char *expand_get_augroup_name(expand_T *xp, int idx)
{
- // Required for ExpandGeneric
- (void)xp;
-
+ (void)xp; // Required for ExpandGeneric
return augroup_name(idx + 1);
}
@@ -2364,8 +2212,7 @@ char *set_context_in_autocmd(expand_T *xp, char *arg, int doautocmd)
// Function given to ExpandGeneric() to obtain the list of event names.
char *expand_get_event_name(expand_T *xp, int idx)
{
- // xp is a required parameter to be used with ExpandGeneric
- (void)xp;
+ (void)xp; // xp is a required parameter to be used with ExpandGeneric
// List group names
char *name = augroup_name(idx + 1);
@@ -2382,6 +2229,13 @@ char *expand_get_event_name(expand_T *xp, int idx)
return event_names[idx - next_augroup_id].name;
}
+/// Function given to ExpandGeneric() to obtain the list of event names. Don't
+/// include groups.
+char *get_event_name_no_group(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ return event_names[idx].name;
+}
+
/// Check whether given autocommand is supported
///
/// @param[in] event Event to check.
@@ -2406,7 +2260,8 @@ bool autocmd_supported(const char *const event)
/// exists("#Event#pat")
///
/// @param arg autocommand string
-bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
+bool au_exists(const char *const arg)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
buf_T *buflocal_buf = NULL;
bool retval = false;
@@ -2453,8 +2308,8 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
// Find the first autocommand for this event.
// If there isn't any, return false;
// If there is one and no pattern given, return true;
- AutoPat *ap = first_autopat[(int)event];
- if (ap == NULL) {
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ if (kv_size(*acs) == 0) {
goto theend;
}
@@ -2465,10 +2320,11 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
}
// Check if there is an autocommand with the given pattern.
- for (; ap != NULL; ap = ap->next) {
- // only use a pattern when it has not been removed and has commands.
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ // Only use a pattern when it has not been removed.
// For buffer-local autocommands, path_fnamecmp() works fine.
- if (ap->pat != NULL && ap->cmds != NULL
+ if (ap != NULL
&& (group == AUGROUP_ALL || ap->group == group)
&& (pattern == NULL
|| (buflocal_buf == NULL
@@ -2485,17 +2341,15 @@ theend:
}
// Checks if a pattern is buflocal
-bool aupat_is_buflocal(char *pat, int patlen)
+bool aupat_is_buflocal(const char *pat, int patlen)
FUNC_ATTR_PURE
{
- return patlen >= 8
- && strncmp(pat, "<buffer", 7) == 0
- && (pat)[patlen - 1] == '>';
+ return patlen >= 8 && strncmp(pat, "<buffer", 7) == 0 && (pat)[patlen - 1] == '>';
}
-int aupat_get_buflocal_nr(char *pat, int patlen)
+int aupat_get_buflocal_nr(const char *pat, int patlen)
{
- assert(aupat_is_buflocal((char *)pat, patlen));
+ assert(aupat_is_buflocal(pat, patlen));
// "<buffer>"
if (patlen == 8) {
@@ -2518,7 +2372,7 @@ int aupat_get_buflocal_nr(char *pat, int patlen)
}
// normalize buffer pattern
-void aupat_normalize_buflocal_pat(char *dest, char *pat, int patlen, int buflocal_nr)
+void aupat_normalize_buflocal_pat(char *dest, const char *pat, int patlen, int buflocal_nr)
{
assert(aupat_is_buflocal(pat, patlen));
@@ -2527,13 +2381,10 @@ void aupat_normalize_buflocal_pat(char *dest, char *pat, int patlen, int bufloca
}
// normalize pat into standard "<buffer>#N" form
- snprintf(dest,
- BUFLOCAL_PAT_LEN,
- "<buffer=%d>",
- buflocal_nr);
+ snprintf(dest, BUFLOCAL_PAT_LEN, "<buffer=%d>", buflocal_nr);
}
-int autocmd_delete_event(int group, event_T event, char *pat)
+int autocmd_delete_event(int group, event_T event, const char *pat)
FUNC_ATTR_NONNULL_ALL
{
return do_autocmd_event(event, pat, false, false, "", true, group);
@@ -2549,12 +2400,12 @@ bool autocmd_delete_id(int64_t id)
// Note that since multiple AutoCmd objects can have the same ID, we need to do a full scan.
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) { // -V756
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (ac->id == id) {
- aucmd_del(ac);
- success = true;
- }
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->id == id) {
+ aucmd_del(ac);
+ success = true;
}
}
}
@@ -2617,25 +2468,10 @@ AucmdExecutable aucmd_exec_copy(AucmdExecutable src)
abort();
}
-bool aucmd_exec_is_deleted(AucmdExecutable acc)
- FUNC_ATTR_PURE
-{
- switch (acc.type) {
- case CALLABLE_EX:
- return acc.callable.cmd == NULL;
- case CALLABLE_CB:
- return acc.callable.cb.type == kCallbackNone;
- case CALLABLE_NONE:
- return true;
- }
-
- abort();
-}
-
bool au_event_is_empty(event_T event)
FUNC_ATTR_PURE
{
- return first_autopat[event] == NULL;
+ return kv_size(autocmds[(int)event]) == 0;
}
// Arg Parsing Functions
@@ -2682,7 +2518,7 @@ static int arg_augroup_get(char **argp)
return AUGROUP_ALL;
}
- char *group_name = xstrnsave(arg, (size_t)(p - arg));
+ char *group_name = xmemdupz(arg, (size_t)(p - arg));
int group = augroup_find(group_name);
if (group == AUGROUP_ERROR) {
group = AUGROUP_ALL; // no match, use all groups
@@ -2709,11 +2545,38 @@ static bool arg_autocmd_flag_get(bool *flag, char **cmd_ptr, char *pattern, int
return false;
}
+/// When kFalse: VimSuspend should be triggered next.
+/// When kTrue: VimResume should be triggered next.
+/// When kNone: Currently triggering VimSuspend or VimResume.
+static TriState pending_vimresume = kFalse;
+
+static void vimresume_event(void **argv)
+{
+ apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
+ pending_vimresume = kFalse;
+}
+
+/// Trigger VimSuspend or VimResume autocommand.
+void may_trigger_vim_suspend_resume(bool suspend)
+{
+ if (suspend && pending_vimresume == kFalse) {
+ pending_vimresume = kNone;
+ apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
+ pending_vimresume = kTrue;
+ } else if (!suspend && pending_vimresume == kTrue) {
+ pending_vimresume = kNone;
+ multiqueue_put(main_loop.events, vimresume_event, 0);
+ }
+}
+
// UI Enter
void do_autocmd_uienter(uint64_t chanid, bool attached)
{
static bool recursive = false;
+ if (starting == NO_SCREEN) {
+ return; // user config hasn't been sourced yet
+ }
if (recursive) {
return; // disallow recursion
}
@@ -2724,8 +2587,7 @@ void do_autocmd_uienter(uint64_t chanid, bool attached)
assert(chanid < VARNUMBER_MAX);
tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid);
tv_dict_set_keys_readonly(dict);
- apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE,
- NULL, NULL, false, curbuf);
+ apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, NULL, NULL, false, curbuf);
restore_v_event(dict, &save_v_event);
recursive = false;
@@ -2736,15 +2598,13 @@ void do_autocmd_uienter(uint64_t chanid, bool attached)
void do_autocmd_focusgained(bool gained)
{
static bool recursive = false;
- static Timestamp last_time = (time_t)0;
- bool need_redraw = false;
+ static Timestamp last_time = 0;
if (recursive) {
return; // disallow recursion
}
recursive = true;
- need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
- NULL, NULL, false, curbuf);
+ apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), NULL, NULL, false, curbuf);
// When activated: Check if any file was modified outside of Vim.
// Only do this when not done within the last two seconds as:
@@ -2752,60 +2612,38 @@ void do_autocmd_focusgained(bool gained)
// has a granularity of 2 seconds.
// 2. We could get multiple notifications in a row.
if (gained && last_time + (Timestamp)2000 < os_now()) {
- need_redraw = check_timestamps(true);
+ check_timestamps(true);
last_time = os_now();
}
- if (need_redraw) {
- // Something was executed, make sure the cursor is put back where it
- // belongs.
- need_wait_return = false;
-
- if (State & MODE_CMDLINE) {
- redrawcmdline();
- } else if ((State & MODE_NORMAL) || (State & MODE_INSERT)) {
- if (must_redraw != 0) {
- update_screen();
- }
+ recursive = false;
+}
- setcursor();
- }
+void do_filetype_autocmd(buf_T *buf, bool force)
+{
+ static int ft_recursive = 0;
- ui_flush();
+ if (ft_recursive > 0 && !force) {
+ return; // disallow recursion
}
- if (need_maketitle) {
- maketitle();
- }
+ char **varp = &buf->b_p_ft;
+ int secure_save = secure;
- recursive = false;
-}
+ // Reset the secure flag, since the value of 'filetype' has
+ // been checked to be safe.
+ secure = 0;
-static void define_autocmd(event_T event, char *pat, char *group, bool once, bool nested, char *cmd)
-{
- AucmdExecutable exec = AUCMD_EXECUTABLE_INIT;
- exec.type = CALLABLE_EX;
- exec.callable.cmd = cmd; // autocmd_register() makes a copy
- int group_id = augroup_add(group);
- autocmd_register(0, event, pat, (int)strlen(pat), group_id, once, nested, NULL, exec);
-}
-
-/// initialization of default autocmds
-void init_default_autocmds(void)
-{
- // open terminals when opening files that start with term://
-#define PROTO "term://"
- define_autocmd(EVENT_BUFREADCMD, PROTO "*", "nvim_terminal", false, true,
- "if !exists('b:term_title')|call termopen("
- // Capture the command string
- "matchstr(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
- // capture the working directory
- "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
- "|endif");
-#undef PROTO
- // limit syntax synchronization in the command window
- define_autocmd(EVENT_CMDWINENTER, "[:>]", "nvim_cmdwin", false, false,
- "syntax sync minlines=1 maxlines=1");
+ ft_recursive++;
+ did_filetype = true;
+ // Only pass true for "force" when it is true or
+ // used recursively, to avoid endless recurrence.
+ apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, force || ft_recursive == 1, buf);
+ ft_recursive--;
+
+ // Just in case the old "buf" is now invalid
+ if (varp != &(buf->b_p_ft)) {
+ varp = NULL;
+ }
+ secure = secure_save;
}
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
index 791b589167..259a56cf5c 100644
--- a/src/nvim/autocmd.h
+++ b/src/nvim/autocmd.h
@@ -1,81 +1,18 @@
-#ifndef NVIM_AUTOCMD_H
-#define NVIM_AUTOCMD_H
+#pragma once
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/autocmd_defs.h" // IWYU pragma: export
#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/macros.h"
-#include "nvim/regexp_defs.h"
-#include "nvim/types.h"
-
-struct AutoCmd_S;
-struct AutoPatCmd_S;
-struct AutoPat_S;
-
-// event_T definition
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "auevents_enum.generated.h"
-#endif
-
-// Struct to save values in before executing autocommands for a buffer that is
-// not the current buffer.
-typedef struct {
- buf_T *save_curbuf; ///< saved curbuf
- int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0
- handle_T save_curwin_handle; ///< ID of saved curwin
- handle_T new_curwin_handle; ///< ID of new curwin
- handle_T save_prevwin_handle; ///< ID of saved prevwin
- bufref_T new_curbuf; ///< new curbuf
- char *globaldir; ///< saved value of globaldir
- bool save_VIsual_active; ///< saved VIsual_active
-} aco_save_T;
-
-typedef struct AutoCmd_S AutoCmd;
-struct AutoCmd_S {
- AucmdExecutable exec;
- bool once; // "One shot": removed after execution
- bool nested; // If autocommands nest here
- bool last; // last command in list
- int64_t id; // ID used for uniquely tracking an autocmd.
- sctx_T script_ctx; // script context where it is defined
- char *desc; // Description for the autocmd.
- AutoCmd *next; // Next AutoCmd in list
-};
-
-typedef struct AutoPat_S AutoPat;
-struct AutoPat_S {
- AutoPat *next; // next AutoPat in AutoPat list; MUST
- // be the first entry
- char *pat; // pattern as typed (NULL when pattern
- // has been removed)
- regprog_T *reg_prog; // compiled regprog for pattern
- AutoCmd *cmds; // list of commands to do
- int group; // group ID
- int patlen; // strlen() of pat
- int buflocal_nr; // !=0 for buffer-local AutoPat
- char allow_dirs; // Pattern may match whole path
- char last; // last pattern for apply_autocmds()
-};
-
-/// Struct used to keep status while executing autocommands for an event.
-typedef struct AutoPatCmd_S AutoPatCmd;
-struct AutoPatCmd_S {
- AutoPat *curpat; // next AutoPat to examine
- AutoCmd *nextcmd; // next AutoCmd to execute
- int group; // group being used
- char *fname; // fname to match with
- char *sfname; // sfname to match with
- char *tail; // tail of fname
- event_T event; // current event
- sctx_T script_ctx; // script context where it is defined
- int arg_bufnr; // initially equal to <abuf>, set to zero when buf is deleted
- Object *data; // arbitrary data
- AutoPatCmd *next; // chain of active apc-s for auto-invalidation
-};
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
// Set by the apply_autocmds_group function if the given event is equal to
// EVENT_FILETYPE. Used by the readfile function in order to determine if
@@ -83,7 +20,12 @@ struct AutoPatCmd_S {
//
// Relying on this value requires one to reset it prior calling
// apply_autocmds_group.
-EXTERN bool au_did_filetype INIT(= false);
+EXTERN bool au_did_filetype INIT( = false);
+
+/// For CursorMoved event
+EXTERN win_T *last_cursormoved_win INIT( = NULL);
+/// For CursorMoved event, only used when last_cursormoved_win == curwin
+EXTERN pos_T last_cursormoved INIT( = { 0, 0, 0 });
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "autocmd.h.generated.h"
@@ -100,5 +42,3 @@ EXTERN bool au_did_filetype INIT(= false);
/// Iterates over all the events for auto commands
#define FOR_ALL_AUEVENTS(event) \
for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) // NOLINT
-
-#endif // NVIM_AUTOCMD_H
diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h
new file mode 100644
index 0000000000..4639ec2731
--- /dev/null
+++ b/src/nvim/autocmd_defs.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "klib/kvec.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/regexp_defs.h"
+#include "nvim/types_defs.h"
+
+// event_T definition
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "auevents_enum.generated.h"
+#endif
+
+/// Struct to save values in before executing autocommands for a buffer that is
+/// not the current buffer.
+typedef struct {
+ buf_T *save_curbuf; ///< saved curbuf
+ int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0
+ handle_T save_curwin_handle; ///< ID of saved curwin
+ handle_T new_curwin_handle; ///< ID of new curwin
+ handle_T save_prevwin_handle; ///< ID of saved prevwin
+ bufref_T new_curbuf; ///< new curbuf
+ char *globaldir; ///< saved value of globaldir
+ bool save_VIsual_active; ///< saved VIsual_active
+ int save_State; ///< saved State
+} aco_save_T;
+
+typedef struct {
+ size_t refcount; ///< Reference count (freed when reaches zero)
+ char *pat; ///< Pattern as typed
+ regprog_T *reg_prog; ///< Compiled regprog for pattern
+ int group; ///< Group ID
+ int patlen; ///< strlen() of pat
+ int buflocal_nr; ///< !=0 for buffer-local AutoPat
+ char allow_dirs; ///< Pattern may match whole path
+} AutoPat;
+
+typedef struct {
+ AucmdExecutable exec; ///< Command or callback function
+ AutoPat *pat; ///< Pattern reference (NULL when autocmd was removed)
+ int64_t id; ///< ID used for uniquely tracking an autocmd
+ char *desc; ///< Description for the autocmd
+ sctx_T script_ctx; ///< Script context where it is defined
+ bool once; ///< "One shot": removed after execution
+ bool nested; ///< If autocommands nest here
+} AutoCmd;
+
+/// Struct used to keep status while executing autocommands for an event.
+typedef struct AutoPatCmd_S AutoPatCmd;
+struct AutoPatCmd_S {
+ AutoPat *lastpat; ///< Last matched AutoPat
+ size_t auidx; ///< Current autocmd index to execute
+ size_t ausize; ///< Saved AutoCmd vector size
+ char *fname; ///< Fname to match with
+ char *sfname; ///< Sfname to match with
+ char *tail; ///< Tail of fname
+ int group; ///< Group being used
+ event_T event; ///< Current event
+ sctx_T script_ctx; ///< Script context where it is defined
+ int arg_bufnr; ///< Initially equal to <abuf>, set to zero when buf is deleted
+ Object *data; ///< Arbitrary data
+ AutoPatCmd *next; ///< Chain of active apc-s for auto-invalidation
+};
+
+typedef kvec_t(AutoCmd) AutoCmdVec;
diff --git a/src/nvim/base64.c b/src/nvim/base64.c
new file mode 100644
index 0000000000..295dedd8d3
--- /dev/null
+++ b/src/nvim/base64.c
@@ -0,0 +1,215 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "auto/config.h" // IWYU pragma: keep
+#include "nvim/base64.h"
+#include "nvim/memory.h"
+
+#ifdef HAVE_BE64TOH
+# include ENDIAN_INCLUDE_FILE
+#endif
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "base64.c.generated.h" // IWYU pragma: export
+#endif
+
+static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+// Indices are 1-based because we use 0 to indicate a letter that is not part of the alphabet
+static const uint8_t char_to_index[256] = {
+ ['A'] = 1, ['B'] = 2, ['C'] = 3, ['D'] = 4, ['E'] = 5, ['F'] = 6, ['G'] = 7, ['H'] = 8,
+ ['I'] = 9, ['J'] = 10, ['K'] = 11, ['L'] = 12, ['M'] = 13, ['N'] = 14, ['O'] = 15, ['P'] = 16,
+ ['Q'] = 17, ['R'] = 18, ['S'] = 19, ['T'] = 20, ['U'] = 21, ['V'] = 22, ['W'] = 23, ['X'] = 24,
+ ['Y'] = 25, ['Z'] = 26, ['a'] = 27, ['b'] = 28, ['c'] = 29, ['d'] = 30, ['e'] = 31, ['f'] = 32,
+ ['g'] = 33, ['h'] = 34, ['i'] = 35, ['j'] = 36, ['k'] = 37, ['l'] = 38, ['m'] = 39, ['n'] = 40,
+ ['o'] = 41, ['p'] = 42, ['q'] = 43, ['r'] = 44, ['s'] = 45, ['t'] = 46, ['u'] = 47, ['v'] = 48,
+ ['w'] = 49, ['x'] = 50, ['y'] = 51, ['z'] = 52, ['0'] = 53, ['1'] = 54, ['2'] = 55, ['3'] = 56,
+ ['4'] = 57, ['5'] = 58, ['6'] = 59, ['7'] = 60, ['8'] = 61, ['9'] = 62, ['+'] = 63, ['/'] = 64,
+};
+
+#ifndef HAVE_BE64TOH
+static inline uint64_t htobe64(uint64_t host_64bits)
+{
+# ifdef ORDER_BIG_ENDIAN
+ return host_64bits;
+# else
+ uint8_t *buf = (uint8_t *)&host_64bits;
+ uint64_t ret = 0;
+ for (size_t i = 8; i; i--) {
+ ret |= ((uint64_t)buf[i - 1]) << ((8 - i) * 8);
+ }
+ return ret;
+# endif
+}
+
+static inline uint32_t htobe32(uint32_t host_32bits)
+{
+# ifdef ORDER_BIG_ENDIAN
+ return host_32bits;
+# else
+ uint8_t *buf = (uint8_t *)&host_32bits;
+ uint32_t ret = 0;
+ for (size_t i = 4; i; i--) {
+ ret |= ((uint32_t)buf[i - 1]) << ((4 - i) * 8);
+ }
+ return ret;
+# endif
+}
+#endif
+
+/// Encode a string using Base64.
+///
+/// @param src String to encode
+/// @param src_len Length of the string
+/// @return Base64 encoded string
+char *base64_encode(const char *src, size_t src_len)
+{
+ assert(src != NULL);
+
+ const size_t out_len = ((src_len + 2) / 3) * 4;
+ char *dest = xmalloc(out_len + 1);
+
+ size_t src_i = 0;
+ size_t out_i = 0;
+
+ const uint8_t *s = (const uint8_t *)src;
+
+ // Read 8 bytes at a time as much as we can
+ for (; src_i + 7 < src_len; src_i += 6) {
+ uint64_t bits_h;
+ memcpy(&bits_h, &s[src_i], sizeof(uint64_t));
+ const uint64_t bits_be = htobe64(bits_h);
+ dest[out_i + 0] = alphabet[(bits_be >> 58) & 0x3F];
+ dest[out_i + 1] = alphabet[(bits_be >> 52) & 0x3F];
+ dest[out_i + 2] = alphabet[(bits_be >> 46) & 0x3F];
+ dest[out_i + 3] = alphabet[(bits_be >> 40) & 0x3F];
+ dest[out_i + 4] = alphabet[(bits_be >> 34) & 0x3F];
+ dest[out_i + 5] = alphabet[(bits_be >> 28) & 0x3F];
+ dest[out_i + 6] = alphabet[(bits_be >> 22) & 0x3F];
+ dest[out_i + 7] = alphabet[(bits_be >> 16) & 0x3F];
+ out_i += sizeof(uint64_t);
+ }
+
+ for (; src_i + 3 < src_len; src_i += 3) {
+ uint32_t bits_h;
+ memcpy(&bits_h, &s[src_i], sizeof(uint32_t));
+ const uint32_t bits_be = htobe32(bits_h);
+ dest[out_i + 0] = alphabet[(bits_be >> 26) & 0x3F];
+ dest[out_i + 1] = alphabet[(bits_be >> 20) & 0x3F];
+ dest[out_i + 2] = alphabet[(bits_be >> 14) & 0x3F];
+ dest[out_i + 3] = alphabet[(bits_be >> 8) & 0x3F];
+ out_i += sizeof(uint32_t);
+ }
+
+ if (src_i + 2 < src_len) {
+ dest[out_i + 0] = alphabet[s[src_i] >> 2];
+ dest[out_i + 1] = alphabet[((s[src_i] & 0x3) << 4) | (s[src_i + 1] >> 4)];
+ dest[out_i + 2] = alphabet[(s[src_i + 1] & 0xF) << 2 | (s[src_i + 2] >> 6)];
+ dest[out_i + 3] = alphabet[(s[src_i + 2] & 0x3F)];
+ out_i += 4;
+ } else if (src_i + 1 < src_len) {
+ dest[out_i + 0] = alphabet[s[src_i] >> 2];
+ dest[out_i + 1] = alphabet[((s[src_i] & 0x3) << 4) | (s[src_i + 1] >> 4)];
+ dest[out_i + 2] = alphabet[(s[src_i + 1] & 0xF) << 2];
+ out_i += 3;
+ } else if (src_i < src_len) {
+ dest[out_i + 0] = alphabet[s[src_i] >> 2];
+ dest[out_i + 1] = alphabet[(s[src_i] & 0x3) << 4];
+ out_i += 2;
+ }
+
+ for (; out_i < out_len; out_i++) {
+ dest[out_i] = '=';
+ }
+
+ dest[out_len] = '\0';
+
+ return dest;
+}
+
+/// Decode a Base64 encoded string.
+///
+/// @param src Base64 encoded string
+/// @param src_len Length of {src}
+/// @return Decoded string
+char *base64_decode(const char *src, size_t src_len)
+{
+ assert(src != NULL);
+
+ char *dest = NULL;
+
+ if (src_len % 4 != 0) {
+ goto invalid;
+ }
+
+ size_t out_len = (src_len / 4) * 3;
+ if (src_len >= 1 && src[src_len - 1] == '=') {
+ out_len--;
+ }
+ if (src_len >= 2 && src[src_len - 2] == '=') {
+ out_len--;
+ }
+
+ const uint8_t *s = (const uint8_t *)src;
+
+ dest = xmalloc(out_len + 1);
+
+ int acc = 0;
+ int acc_len = 0;
+ size_t out_i = 0;
+ size_t src_i = 0;
+ int leftover_i = -1;
+
+ for (; src_i < src_len; src_i++) {
+ const uint8_t c = s[src_i];
+ const uint8_t d = char_to_index[c];
+ if (d == 0) {
+ if (c == '=') {
+ leftover_i = (int)src_i;
+ break;
+ }
+ goto invalid;
+ }
+
+ acc = ((acc << 6) & 0xFFF) + (d - 1);
+ acc_len += 6;
+ if (acc_len >= 8) {
+ acc_len -= 8;
+ dest[out_i] = (char)(acc >> acc_len);
+ out_i += 1;
+ }
+ }
+
+ if (acc_len > 4 || ((acc & ((1 << acc_len) - 1)) != 0)) {
+ goto invalid;
+ }
+
+ if (leftover_i > -1) {
+ int padding_len = acc_len / 2;
+ int padding_chars = 0;
+ for (; (size_t)leftover_i < src_len; leftover_i++) {
+ const uint8_t c = s[leftover_i];
+ if (c != '=') {
+ goto invalid;
+ }
+ padding_chars += 1;
+ }
+
+ if (padding_chars != padding_len) {
+ goto invalid;
+ }
+ }
+
+ dest[out_len] = '\0';
+
+ return dest;
+
+invalid:
+ if (dest) {
+ xfree((void *)dest);
+ }
+
+ return NULL;
+}
diff --git a/src/nvim/base64.h b/src/nvim/base64.h
new file mode 100644
index 0000000000..511aa27d10
--- /dev/null
+++ b/src/nvim/base64.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <stddef.h> // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "base64.h.generated.h"
+#endif
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 5dcb10751f..8a594dea92 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// buffer.c: functions for dealing with the buffer structure
//
@@ -32,8 +29,8 @@
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_updates.h"
@@ -47,6 +44,7 @@
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
@@ -67,48 +65,51 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
+#include "nvim/memfile_defs.h"
#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
-#include "nvim/sign.h"
#include "nvim/spell.h"
+#include "nvim/state_defs.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.c.generated.h"
#endif
-static char *e_auabort = N_("E855: Autocommands caused command to abort");
-static char e_attempt_to_delete_buffer_that_is_in_use_str[]
+static const char *e_auabort = N_("E855: Autocommands caused command to abort");
+static const char e_attempt_to_delete_buffer_that_is_in_use_str[]
= N_("E937: Attempt to delete a buffer that is in use: %s");
// Number of times free_buffer() was called.
@@ -135,21 +136,20 @@ int get_highest_fnum(void)
static int read_buffer(int read_stdin, exarg_T *eap, int flags)
{
int retval = OK;
- linenr_T line_count;
bool silent = shortmess(SHM_FILEINFO);
// Read from the buffer which the text is already filled in and append at
// the end. This makes it possible to retry when 'fileformat' or
// 'fileencoding' was guessed wrong.
- line_count = curbuf->b_ml.ml_line_count;
+ linenr_T line_count = curbuf->b_ml.ml_line_count;
retval = readfile(read_stdin ? NULL : curbuf->b_ffname,
read_stdin ? NULL : curbuf->b_fname,
- line_count, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ line_count, 0, (linenr_T)MAXLNUM, eap,
flags | READ_BUFFER, silent);
if (retval == OK) {
// Delete the binary lines.
while (--line_count >= 0) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
} else {
// Delete the converted lines.
@@ -165,7 +165,7 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags)
// Set or reset 'modified' before executing autocommands, so that
// it can be changed there.
if (!readonlymode && !buf_is_empty(curbuf)) {
- changed();
+ changed(curbuf);
} else if (retval != FAIL) {
unchanged(curbuf, false, true);
}
@@ -176,20 +176,22 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags)
return retval;
}
-/// Ensure buffer "buf" is loaded. Does not trigger the swap-exists action.
-void buffer_ensure_loaded(buf_T *buf)
+/// Ensure buffer "buf" is loaded.
+bool buf_ensure_loaded(buf_T *buf)
{
if (buf->b_ml.ml_mfp != NULL) {
- return;
+ // already open (common case)
+ return true;
}
aco_save_T aco;
// Make sure the buffer is in a window.
aucmd_prepbuf(&aco, buf);
- swap_exists_action = SEA_NONE;
- open_buffer(false, NULL, 0);
+ // status can be OK or NOTDONE (which also means ok/done)
+ int status = open_buffer(false, NULL, 0);
aucmd_restbuf(&aco);
+ return (status != FAIL);
}
/// Open current buffer, that is: open the memfile and read the file into
@@ -205,7 +207,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
int flags = flags_arg;
int retval = OK;
bufref_T old_curbuf;
- long old_tw = curbuf->b_p_tw;
+ OptInt old_tw = curbuf->b_p_tw;
int read_fifo = false;
bool silent = shortmess(SHM_FILEINFO);
@@ -249,6 +251,11 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
return FAIL;
}
+ // Do not sync this buffer yet, may first want to read the file.
+ if (curbuf->b_ml.ml_mfp != NULL) {
+ curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES_NOSYNC;
+ }
+
// The autocommands in readfile() may change the buffer, but only AFTER
// reading the file.
set_bufref(&old_curbuf, curbuf);
@@ -269,7 +276,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
int save_bin = curbuf->b_p_bin;
int perm;
- perm = os_getperm((const char *)curbuf->b_ffname);
+ perm = os_getperm(curbuf->b_ffname);
if (perm >= 0 && (0 || S_ISFIFO(perm)
|| S_ISSOCK(perm)
# ifdef OPEN_CHR_FILES
@@ -285,7 +292,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
#endif
retval = readfile(curbuf->b_ffname, curbuf->b_fname,
- (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ 0, 0, (linenr_T)MAXLNUM, eap,
flags | READ_NEW | (read_fifo ? READ_FIFO : 0), silent);
#ifdef UNIX
if (read_fifo) {
@@ -308,8 +315,8 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
// it possible to retry when 'fileformat' or 'fileencoding' was
// guessed wrong.
curbuf->b_p_bin = true;
- retval = readfile(NULL, NULL, (linenr_T)0,
- (linenr_T)0, (linenr_T)MAXLNUM, NULL,
+ retval = readfile(NULL, NULL, 0,
+ 0, (linenr_T)MAXLNUM, NULL,
flags | (READ_NEW + READ_STDIN), silent);
curbuf->b_p_bin = save_bin;
if (retval == OK) {
@@ -317,6 +324,12 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
}
}
+ // Can now sync this buffer in ml_sync_all().
+ if (curbuf->b_ml.ml_mfp != NULL
+ && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC) {
+ curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES;
+ }
+
// if first time loading this buffer, init b_chartab[]
if (curbuf->b_flags & BF_NEVERLOADED) {
(void)buf_init_chartab(curbuf, false);
@@ -333,7 +346,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
|| modified_was_set // ":set modified" used in autocmd
|| (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)) {
- changed();
+ changed(curbuf);
} else if (retval != FAIL && !read_stdin && !read_fifo) {
unchanged(curbuf, false, true);
}
@@ -342,6 +355,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
// Set last_changedtick to avoid triggering a TextChanged autocommand right
// after it was added.
curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf);
// require "!" to overwrite the file, because it wasn't read completely
@@ -409,8 +423,8 @@ bool bufref_valid(bufref_T *bufref)
FUNC_ATTR_PURE
{
return bufref->br_buf_free_count == buf_free_count
- ? true
- : buf_valid(bufref->br_buf) && bufref->br_fnum == bufref->br_buf->b_fnum;
+ ? true
+ : buf_valid(bufref->br_buf) && bufref->br_fnum == bufref->br_buf->b_fnum;
}
/// Check that "buf" points to a valid buffer in the buffer list.
@@ -465,7 +479,7 @@ static bool can_unload_buffer(buf_T *buf)
/// Possible values:
/// 0 buffer becomes hidden
/// DOBUF_UNLOAD buffer is unloaded
-/// DOBUF_DELETE buffer is unloaded and removed from buffer list
+/// DOBUF_DEL buffer is unloaded and removed from buffer list
/// DOBUF_WIPE buffer is unloaded and really deleted
/// When doing all but the first one on the current buffer, the
/// caller should get a new buffer very soon!
@@ -735,12 +749,11 @@ void buf_clear_file(buf_T *buf)
void buf_clear(void)
{
linenr_T line_count = curbuf->b_ml.ml_line_count;
+ extmark_free_all(curbuf); // delete any extmarks
while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
deleted_lines_mark(1, line_count); // prepare for display
- ml_close(curbuf, true); // free memline_T
- buf_clear_file(curbuf);
}
/// buf_freeall() - free all things allocated for a buffer that are related to
@@ -834,7 +847,7 @@ void buf_freeall(buf_T *buf, int flags)
/// itself (not the file, that must have been done already).
static void free_buffer(buf_T *buf)
{
- pmap_del(handle_T)(&buffer_handles, buf->b_fnum);
+ pmap_del(int)(&buffer_handles, buf->b_fnum, NULL);
buf_free_count++;
// b:changedtick uses an item in buf_T.
free_buffer_stuff(buf, kBffClearWinInfo);
@@ -848,9 +861,9 @@ static void free_buffer(buf_T *buf)
xfree(buf->b_prompt_text);
callback_free(&buf->b_prompt_callback);
callback_free(&buf->b_prompt_interrupt);
- clear_fmark(&buf->b_last_cursor);
- clear_fmark(&buf->b_last_insert);
- clear_fmark(&buf->b_last_change);
+ clear_fmark(&buf->b_last_cursor, 0);
+ clear_fmark(&buf->b_last_insert, 0);
+ clear_fmark(&buf->b_last_change, 0);
for (size_t i = 0; i < NMARKS; i++) {
free_fmark(buf->b_namedm[i]);
}
@@ -906,7 +919,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
buf_init_changedtick(buf);
}
uc_clear(&buf->b_ucmds); // clear local user commands
- buf_delete_signs(buf, "*"); // delete any signs
extmark_free_all(buf); // delete any extmarks
map_clear_mode(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
@@ -918,10 +930,14 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
/// Go to another buffer. Handles the result of the ATTENTION dialog.
void goto_buffer(exarg_T *eap, int start, int dir, int count)
{
+ const int save_sea = swap_exists_action;
+
bufref_T old_curbuf;
set_bufref(&old_curbuf, curbuf);
- swap_exists_action = SEA_DIALOG;
+ if (swap_exists_action == SEA_NONE) {
+ swap_exists_action = SEA_DIALOG;
+ }
(void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
start, dir, count, eap->forceit);
@@ -934,7 +950,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
// Quitting means closing the split window, nothing else.
win_close(curwin, true, false);
- swap_exists_action = SEA_NONE;
+ swap_exists_action = save_sea;
swap_exists_did_quit = true;
// Restore the error/interrupt/exception state if not discarded by a
@@ -953,7 +969,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
void handle_swap_exists(bufref_T *old_curbuf)
{
cleanup_T cs;
- long old_tw = curbuf->b_p_tw;
+ OptInt old_tw = curbuf->b_p_tw;
buf_T *buf;
if (swap_exists_action == SEA_QUIT) {
@@ -972,7 +988,7 @@ void handle_swap_exists(bufref_T *old_curbuf)
|| old_curbuf->br_buf == curbuf) {
// Block autocommands here because curwin->w_buffer is NULL.
block_autocmds();
- buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
+ buf = buflist_new(NULL, NULL, 1, BLN_CURBUF | BLN_LISTED);
unblock_autocmds();
} else {
buf = old_curbuf->br_buf;
@@ -1005,7 +1021,7 @@ void handle_swap_exists(bufref_T *old_curbuf)
// new aborting error, interrupt, or uncaught exception.
leave_cleanup(&cs);
}
- swap_exists_action = SEA_NONE; // -V519
+ swap_exists_action = SEA_NONE;
}
/// do_bufdel() - delete or unload buffer(s)
@@ -1027,9 +1043,8 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
{
int do_current = 0; // delete current buffer?
int deleted = 0; // number of buffers deleted
- char *errormsg = NULL; // return value
+ char *errormsg = NULL; // return value
int bnr; // buffer number
- char *p;
if (addr_count == 0) {
(void)do_buffer(command, DOBUF_CURRENT, FORWARD, 0, forceit);
@@ -1066,7 +1081,7 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
break;
}
if (!ascii_isdigit(*arg)) {
- p = skiptowhite_esc(arg);
+ char *p = skiptowhite_esc(arg);
bnr = buflist_findpat(arg, p, command == DOBUF_WIPE, false, false);
if (bnr < 0) { // failed
break;
@@ -1085,22 +1100,22 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
if (deleted == 0) {
if (command == DOBUF_UNLOAD) {
- STRCPY(IObuff, _("E515: No buffers were unloaded"));
+ xstrlcpy(IObuff, _("E515: No buffers were unloaded"), IOSIZE);
} else if (command == DOBUF_DEL) {
- STRCPY(IObuff, _("E516: No buffers were deleted"));
+ xstrlcpy(IObuff, _("E516: No buffers were deleted"), IOSIZE);
} else {
- STRCPY(IObuff, _("E517: No buffers were wiped out"));
+ xstrlcpy(IObuff, _("E517: No buffers were wiped out"), IOSIZE);
}
errormsg = IObuff;
} else if (deleted >= p_report) {
if (command == DOBUF_UNLOAD) {
- smsg(NGETTEXT("%d buffer unloaded", "%d buffers unloaded", deleted),
+ smsg(0, NGETTEXT("%d buffer unloaded", "%d buffers unloaded", deleted),
deleted);
} else if (command == DOBUF_DEL) {
- smsg(NGETTEXT("%d buffer deleted", "%d buffers deleted", deleted),
+ smsg(0, NGETTEXT("%d buffer deleted", "%d buffers deleted", deleted),
deleted);
} else {
- smsg(NGETTEXT("%d buffer wiped out", "%d buffers wiped out", deleted),
+ smsg(0, NGETTEXT("%d buffer wiped out", "%d buffers wiped out", deleted),
deleted);
}
}
@@ -1113,7 +1128,6 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
/// Used when it is wiped out and it's the last buffer.
static int empty_curbuf(int close_others, int forceit, int action)
{
- int retval;
buf_T *buf = curbuf;
if (action == DOBUF_UNLOAD) {
@@ -1146,8 +1160,7 @@ static int empty_curbuf(int close_others, int forceit, int action)
}
setpcmark();
- retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE,
- forceit ? ECMD_FORCEIT : 0, curwin);
+ int retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, forceit ? ECMD_FORCEIT : 0, curwin);
// do_ecmd() may create a new buffer, then we have to delete
// the old one. But do_ecmd() may have done that already, check
@@ -1404,7 +1417,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
if (buf == NULL) { // No previous buffer, Try 2'nd approach
forward = true;
buf = curbuf->b_next;
- for (;;) {
+ while (true) {
if (buf == NULL) {
if (!forward) { // tried both directions
break;
@@ -1460,16 +1473,11 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
// make "buf" the current buffer
if (action == DOBUF_SPLIT) { // split window first
- // If 'switchbuf' contains "useopen": jump to first window containing
- // "buf" if one exists
- if ((swb_flags & SWB_USEOPEN) && buf_jump_open_win(buf)) {
- return OK;
- }
- // If 'switchbuf' contains "usetab": jump to first window in any tab
- // page containing "buf" if one exists
- if ((swb_flags & SWB_USETAB) && buf_jump_open_tab(buf)) {
+ // If 'switchbuf' is set jump to the window containing "buf".
+ if (swbuf_goto_win_with_buf(buf) != NULL) {
return OK;
}
+
if (win_split(0, 0) == FAIL) {
return FAIL;
}
@@ -1525,7 +1533,7 @@ void set_curbuf(buf_T *buf, int action)
buf_T *prevbuf;
int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
|| action == DOBUF_WIPE);
- long old_tw = curbuf->b_p_tw;
+ OptInt old_tw = curbuf->b_p_tw;
setpcmark();
if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
@@ -1676,7 +1684,7 @@ void enter_buffer(buf_T *buf)
maketitle();
// when autocmds didn't change it
if (curwin->w_topline == 1 && !curwin->w_topline_was_set) {
- scroll_cursor_halfway(false); // redisplay at correct position
+ scroll_cursor_halfway(false, false); // redisplay at correct position
}
// Change directories when the 'acd' option is set.
@@ -1688,7 +1696,7 @@ void enter_buffer(buf_T *buf)
// May need to set the spell language. Can only do this after the buffer
// has been properly setup.
if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
- (void)did_set_spelllang(curwin);
+ (void)parse_spelllang(curwin);
}
curbuf->b_last_used = time(NULL);
@@ -1791,7 +1799,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
xfree(ffname);
if (lnum != 0) {
buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin,
- lnum, (colnr_T)0, false);
+ lnum, 0, false);
}
if ((flags & BLN_NOOPT) == 0) {
// Copy the options now, if 'cpo' doesn't have 's' and not done already.
@@ -1836,7 +1844,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
buf = xcalloc(1, sizeof(buf_T));
// init b: variables
buf->b_vars = tv_dict_alloc();
- buf->b_signcols.valid = false;
+ buf->b_signcols.sentinel = 0;
init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
buf_init_changedtick(buf);
}
@@ -1871,12 +1879,12 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
lastbuf = buf;
buf->b_fnum = top_file_num++;
- pmap_put(handle_T)(&buffer_handles, buf->b_fnum, buf);
+ pmap_put(int)(&buffer_handles, buf->b_fnum, buf);
if (top_file_num < 0) { // wrap around (may cause duplicates)
emsg(_("W14: Warning: List of file names overflow"));
if (emsg_silent == 0 && !in_assert_fails) {
ui_flush();
- os_delay(3001L, true); // make sure it is noticed
+ os_delay(3001, true); // make sure it is noticed
}
top_file_num = 1;
}
@@ -1905,7 +1913,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
buf->b_flags |= BF_DUMMY;
}
buf_clear_file(buf);
- clrallmarks(buf); // clear marks
+ clrallmarks(buf, 0); // clear marks
fmarks_check_names(buf); // check file marks for this file
buf->b_p_bl = (flags & BLN_LISTED) ? true : false; // init 'buflisted'
kv_destroy(buf->update_channels);
@@ -2037,12 +2045,11 @@ void free_buf_options(buf_T *buf, int free_p_ff)
/// Return FAIL for failure, OK for success.
int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
{
- buf_T *buf;
win_T *wp = NULL;
fmark_T *fm = NULL;
colnr_T col;
- buf = buflist_findnr(n);
+ buf_T *buf = buflist_findnr(n);
if (buf == NULL) {
if ((options & GETF_ALT) && n == 0) {
emsg(_(e_noalt));
@@ -2073,17 +2080,8 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
}
if (options & GETF_SWITCH) {
- // If 'switchbuf' contains "useopen": jump to first window containing
- // "buf" if one exists
- if (swb_flags & SWB_USEOPEN) {
- wp = buf_jump_open_win(buf);
- }
-
- // If 'switchbuf' contains "usetab": jump to first window in any tab
- // page containing "buf" if one exists
- if (wp == NULL && (swb_flags & SWB_USETAB)) {
- wp = buf_jump_open_tab(buf);
- }
+ // If 'switchbuf' is set jump to the window containing "buf".
+ wp = swbuf_goto_win_with_buf(buf);
// If 'switchbuf' contains "split", "vsplit" or "newtab" and the
// current buffer isn't empty: open new tab or window
@@ -2123,12 +2121,10 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
/// Go to the last known line number for the current buffer.
void buflist_getfpos(void)
{
- pos_T *fpos;
-
- fpos = &buflist_findfmark(curbuf)->mark;
+ pos_T *fpos = &buflist_findfmark(curbuf)->mark;
curwin->w_cursor.lnum = fpos->lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
if (p_sol) {
curwin->w_cursor.col = 0;
@@ -2145,18 +2141,17 @@ void buflist_getfpos(void)
/// @return buffer or NULL if not found
buf_T *buflist_findname_exp(char *fname)
{
- char *ffname;
buf_T *buf = NULL;
// First make the name into a full path name
- ffname = FullName_save(fname,
+ char *ffname = FullName_save(fname,
#ifdef UNIX
- // force expansion, get rid of symbolic links
- true
+ // force expansion, get rid of symbolic links
+ true
#else
- false
+ false
#endif
- ); // NOLINT(whitespace/parens)
+ ); // NOLINT(whitespace/parens)
if (ffname != NULL) {
buf = buflist_findname(ffname);
xfree(ffname);
@@ -2206,12 +2201,6 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted,
FUNC_ATTR_NONNULL_ARG(1)
{
int match = -1;
- int find_listed;
- char *pat;
- char *patend;
- int attempt;
- char *p;
- int toggledollar;
if (pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) {
if (*pattern == '%') {
@@ -2232,23 +2221,24 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted,
// Repeat this for finding an unlisted buffer if there was no matching
// listed buffer.
- pat = file_pat_to_reg_pat((char *)pattern, (char *)pattern_end, NULL, false);
+ char *pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, false);
if (pat == NULL) {
return -1;
}
- patend = pat + strlen(pat) - 1;
- toggledollar = (patend > pat && *patend == '$');
+ char *patend = pat + strlen(pat) - 1;
+ int toggledollar = (patend > pat && *patend == '$');
// First try finding a listed buffer. If not found and "unlisted"
// is true, try finding an unlisted buffer.
- find_listed = true;
- for (;;) {
- for (attempt = 0; attempt <= 3; attempt++) {
+
+ int find_listed = true;
+ while (true) {
+ for (int attempt = 0; attempt <= 3; attempt++) {
// may add '^' and '$'
if (toggledollar) {
*patend = (attempt < 2) ? NUL : '$'; // add/remove '$'
}
- p = pat;
+ char *p = pat;
if (*p == '^' && !(attempt & 1)) { // add/remove '^'
p++;
}
@@ -2338,7 +2328,6 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
int count = 0;
int round;
char *p;
- int attempt;
bufmatch_T *matches = NULL;
*num_file = 0; // return values in case of FAIL
@@ -2366,7 +2355,7 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
fuzmatch_str_T *fuzmatch = NULL;
// attempt == 0: try match with '\<', match at start of word
// attempt == 1: try match without '\<', match anywhere
- for (attempt = 0; attempt <= (fuzzy ? 0 : 1); attempt++) {
+ for (int attempt = 0; attempt <= (fuzzy ? 0 : 1); attempt++) {
regmatch_T regmatch;
if (!fuzzy) {
if (attempt > 0 && patc == pat) {
@@ -2522,7 +2511,6 @@ static char *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case)
static char *fname_match(regmatch_T *rmp, char *name, bool ignore_case)
{
char *match = NULL;
- char *p;
// extra check for valid arguments
if (name == NULL || rmp->regprog == NULL) {
@@ -2531,12 +2519,12 @@ static char *fname_match(regmatch_T *rmp, char *name, bool ignore_case)
// Ignore case when 'fileignorecase' or the argument is set.
rmp->rm_ic = p_fic || ignore_case;
- if (vim_regexec(rmp, name, (colnr_T)0)) {
+ if (vim_regexec(rmp, name, 0)) {
match = name;
} else if (rmp->regprog != NULL) {
// Replace $(HOME) with '~' and try matching again.
- p = home_replace_save(NULL, name);
- if (vim_regexec(rmp, p, (colnr_T)0)) {
+ char *p = home_replace_save(NULL, name);
+ if (vim_regexec(rmp, p, 0)) {
match = name;
}
xfree(p);
@@ -2793,7 +2781,7 @@ void buflist_list(exarg_T *eap)
for (;
buf != NULL && !got_int;
buf = buflist_data != NULL
- ? (++p < buflist_data + buflist.ga_len ? *p : NULL) : buf->b_next) {
+ ? (++p < buflist_data + buflist.ga_len ? *p : NULL) : buf->b_next) {
const bool is_terminal = buf->terminal;
const bool job_running = buf->terminal && terminal_running(buf->terminal);
@@ -2827,8 +2815,8 @@ void buflist_list(exarg_T *eap)
}
const int changed_char = (buf->b_flags & BF_READERR)
- ? 'x'
- : (bufIsChanged(buf) ? '+' : ' ');
+ ? 'x'
+ : (bufIsChanged(buf) ? '+' : ' ');
int ro_char = !MODIFIABLE(buf) ? '-' : (buf->b_p_ro ? '=' : ' ');
if (buf->terminal) {
ro_char = channel_job_running((uint64_t)buf->b_p_channel) ? 'R' : 'F';
@@ -2860,7 +2848,7 @@ void buflist_list(exarg_T *eap)
buf == curbuf ? (int64_t)curwin->w_cursor.lnum : (int64_t)buflist_findlnum(buf));
}
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
line_breakcheck();
}
@@ -3034,7 +3022,7 @@ char *getaltfname(bool errmsg)
/// Used by qf_init(), main() and doarglist()
int buflist_add(char *fname, int flags)
{
- buf_T *buf = buflist_new(fname, NULL, (linenr_T)0, flags);
+ buf_T *buf = buflist_new(fname, NULL, 0, flags);
if (buf != NULL) {
return buf->b_fnum;
}
@@ -3153,10 +3141,8 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
char *name;
int n;
char *p;
- char *buffer;
- size_t len;
- buffer = xmalloc(IOSIZE);
+ char *buffer = xmalloc(IOSIZE);
if (fullname > 1) { // 2 CTRL-G: include buffer number
vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum);
@@ -3181,11 +3167,11 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
bool dontwrite = bt_dontwrite(curbuf);
vim_snprintf_add(buffer, IOSIZE, "\"%s%s%s%s%s%s",
curbufIsChanged()
- ? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]")) : " ",
+ ? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]")) : " ",
(curbuf->b_flags & BF_NOTEDITED) && !dontwrite
? _("[Not edited]") : "",
(curbuf->b_flags & BF_NEW) && !dontwrite
- ? new_file_message() : "",
+ ? _("[New]") : "",
(curbuf->b_flags & BF_READERR)
? _("[Read errors]") : "",
curbuf->b_p_ro
@@ -3196,12 +3182,12 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
? " " : "");
// With 32 bit longs and more than 21,474,836 lines multiplying by 100
// causes an overflow, thus for large numbers divide instead.
- if (curwin->w_cursor.lnum > 1000000L) {
- n = (int)(((long)curwin->w_cursor.lnum) /
- ((long)curbuf->b_ml.ml_line_count / 100L));
+ if (curwin->w_cursor.lnum > 1000000) {
+ n = ((curwin->w_cursor.lnum) /
+ (curbuf->b_ml.ml_line_count / 100));
} else {
- n = (int)(((long)curwin->w_cursor.lnum * 100L) /
- (long)curbuf->b_ml.ml_line_count);
+ n = ((curwin->w_cursor.lnum * 100) /
+ curbuf->b_ml.ml_line_count);
}
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
vim_snprintf_add(buffer, IOSIZE, "%s", _(no_lines_msg));
@@ -3219,12 +3205,12 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
(int64_t)curbuf->b_ml.ml_line_count,
n);
validate_virtcol();
- len = strlen(buffer);
+ size_t len = strlen(buffer);
col_print(buffer + len, IOSIZE - len,
(int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1);
}
- (void)append_arg_number(curwin, buffer, IOSIZE, !shortmess(SHM_FILE));
+ (void)append_arg_number(curwin, buffer, IOSIZE);
if (dont_truncate) {
// Temporarily set msg_scroll to avoid the message being truncated.
@@ -3232,10 +3218,10 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
msg_start();
n = msg_scroll;
msg_scroll = true;
- msg(buffer);
+ msg(buffer, 0);
msg_scroll = n;
} else {
- p = msg_trunc_attr(buffer, false, 0);
+ p = msg_trunc(buffer, false, 0);
if (restart_edit != 0 || (msg_scrolled && !need_wait_return)) {
// Need to repeat the message after redrawing when:
// - When restart_edit is set (otherwise there will be a delay
@@ -3268,7 +3254,6 @@ void maketitle(void)
char *icon_str = NULL;
int maxlen = 0;
int len;
- int mustset;
char buf[IOSIZE];
if (!redrawing()) {
@@ -3310,8 +3295,7 @@ void maketitle(void)
SPACE_FOR_FNAME + 1);
buf_p += MIN(size, SPACE_FOR_FNAME);
} else {
- buf_p += transstr_buf((const char *)path_tail(curbuf->b_fname),
- buf_p, SPACE_FOR_FNAME + 1, true);
+ buf_p += transstr_buf(path_tail(curbuf->b_fname), -1, buf_p, SPACE_FOR_FNAME + 1, true);
}
switch (bufIsChanged(curbuf)
@@ -3376,7 +3360,7 @@ void maketitle(void)
*buf_p = NUL;
}
- append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)), false);
+ append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)));
xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf)));
@@ -3392,7 +3376,7 @@ void maketitle(void)
#undef SPACE_FOR_ARGNR
}
}
- mustset = value_change(title_str, &lasttitle);
+ int mustset = value_change(title_str, &lasttitle);
if (p_icon) {
icon_str = buf;
@@ -3460,7 +3444,6 @@ void resettitle(void)
{
ui_call_set_icon(cstr_as_string(lasticon));
ui_call_set_title(cstr_as_string(lasttitle));
- ui_flush();
}
#if defined(EXITFREE)
@@ -3472,8 +3455,8 @@ void free_titles(void)
#endif
-/// Get relative cursor position in window into "buf[buflen]", in the form 99%,
-/// using "Top", "Bot" or "All" when appropriate.
+/// Get relative cursor position in window into "buf[buflen]", in the localized
+/// percentage form like %99, 99%; using "Top", "Bot" or "All" when appropriate.
void get_rel_pos(win_T *wp, char *buf, int buflen)
{
// Need at least 3 chars for writing.
@@ -3481,8 +3464,8 @@ void get_rel_pos(win_T *wp, char *buf, int buflen)
return;
}
- long above; // number of lines above window
- long below; // number of lines below window
+ linenr_T above; // number of lines above window
+ linenr_T below; // number of lines below window
above = wp->w_topline - 1;
above += win_get_fill(wp, wp->w_topline) - wp->w_topfill;
@@ -3497,21 +3480,31 @@ void get_rel_pos(win_T *wp, char *buf, int buflen)
} else if (above <= 0) {
xstrlcpy(buf, _("Top"), (size_t)buflen);
} else {
- vim_snprintf(buf, (size_t)buflen, "%2d%%", above > 1000000L
- ? (int)(above / ((above + below) / 100L))
- : (int)(above * 100L / (above + below)));
+ int perc = (above > 1000000
+ ? (above / ((above + below) / 100))
+ : (above * 100 / (above + below)));
+
+ char *p = buf;
+ size_t l = (size_t)buflen;
+ if (perc < 10) {
+ // prepend one space
+ buf[0] = ' ';
+ p++;
+ l--;
+ }
+ // localized percentage value
+ vim_snprintf(p, l, _("%d%%"), perc);
}
}
-/// Append (file 2 of 8) to "buf[buflen]", if editing more than one file.
+/// Append (2 of 8) to "buf[buflen]", if editing more than one file.
///
/// @param wp window whose buffers to check
/// @param[in,out] buf string buffer to add the text to
/// @param buflen length of the string buffer
-/// @param add_file if true, add "file" before the arg number
///
/// @return true if it was appended.
-bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file)
+bool append_arg_number(win_T *wp, char *buf, int buflen)
FUNC_ATTR_NONNULL_ALL
{
// Nothing to do
@@ -3519,23 +3512,10 @@ bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file)
return false;
}
- char *p = buf + strlen(buf); // go to the end of the buffer
-
- // Early out if the string is getting too long
- if (p - buf + 35 >= buflen) {
- return false;
- }
+ const char *msg = wp->w_arg_idx_invalid ? _(" ((%d) of %d)") : _(" (%d of %d)");
- *p++ = ' ';
- *p++ = '(';
- if (add_file) {
- STRCPY(p, "file ");
- p += 5;
- }
- vim_snprintf(p, (size_t)(buflen - (p - buf)),
- wp->w_arg_idx_invalid
- ? "(%d) of %d)"
- : "%d of %d)", wp->w_arg_idx + 1, ARGCOUNT);
+ char *p = buf + strlen(buf); // go to the end of the buffer
+ vim_snprintf(p, (size_t)(buflen - (p - buf)), msg, wp->w_arg_idx + 1, ARGCOUNT);
return true;
}
@@ -3558,7 +3538,7 @@ void fname_expand(buf_T *buf, char **ffname, char **sfname)
#ifdef MSWIN
if (!buf->b_p_bin) {
// If the file name is a shortcut file, use the file it links to.
- char *rfname = os_resolve_shortcut((const char *)(*ffname));
+ char *rfname = os_resolve_shortcut(*ffname);
if (rfname != NULL) {
xfree(*ffname);
*ffname = rfname;
@@ -3584,7 +3564,7 @@ void ex_buffer_all(exarg_T *eap)
bool p_ea_save;
int open_wins = 0;
int r;
- long count; // Maximum number of windows to open.
+ linenr_T count; // Maximum number of windows to open.
int all; // When true also load inactive buffers.
int had_tab = cmdmod.cmod_tab;
tabpage_T *tpnext;
@@ -3600,6 +3580,10 @@ void ex_buffer_all(exarg_T *eap)
all = true;
}
+ // Stop Visual mode, the cursor and "VIsual" may very well be invalid after
+ // switching to another buffer.
+ reset_VIsual_and_resel();
+
setpcmark();
// Close superfluous windows (two windows for the same buffer).
@@ -3607,23 +3591,30 @@ void ex_buffer_all(exarg_T *eap)
if (had_tab > 0) {
goto_tabpage_tp(first_tabpage, true, true);
}
- for (;;) {
+ while (true) {
tpnext = curtab->tp_next;
- for (wp = firstwin; wp != NULL; wp = wpnext) {
- wpnext = wp->w_next;
+ // Try to close floating windows first
+ for (wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) {
+ wpnext = wp->w_floating
+ ? wp->w_prev->w_floating ? wp->w_prev : firstwin
+ : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next;
if ((wp->w_buffer->b_nwindows > 1
+ || wp->w_floating
|| ((cmdmod.cmod_split & WSP_VERT)
? wp->w_height + wp->w_hsep_height + wp->w_status_height < Rows - p_ch
- tabline_height() - global_stl_height()
: wp->w_width != Columns)
|| (had_tab > 0 && wp != firstwin))
&& !ONE_WINDOW
- && !(wp->w_closing
- || wp->w_buffer->b_locked > 0)) {
- win_close(wp, false, false);
- wpnext = firstwin; // just in case an autocommand does
- // something strange with windows
- tpnext = first_tabpage; // start all over...
+ && !(wp->w_closing || wp->w_buffer->b_locked > 0)
+ && !is_aucmd_win(wp)) {
+ if (win_close(wp, false, false) == FAIL) {
+ break;
+ }
+ // Just in case an autocommand does something strange with
+ // windows: start all over...
+ wpnext = lastwin->w_floating ? lastwin : firstwin;
+ tpnext = first_tabpage;
open_wins = 0;
} else {
open_wins++;
@@ -3643,7 +3634,8 @@ void ex_buffer_all(exarg_T *eap)
//
// Don't execute Win/Buf Enter/Leave autocommands here.
autocmd_no_enter++;
- win_enter(lastwin, false);
+ // lastwin may be aucmd_win
+ win_enter(lastwin_nofloating(), false);
autocmd_no_leave++;
for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) {
// Check if this buffer needs a window
@@ -3661,7 +3653,7 @@ void ex_buffer_all(exarg_T *eap)
} else {
// Check if this buffer already has a window
for (wp = firstwin; wp != NULL; wp = wp->w_next) {
- if (wp->w_buffer == buf) {
+ if (!wp->w_floating && wp->w_buffer == buf) {
break;
}
}
@@ -3735,7 +3727,7 @@ void ex_buffer_all(exarg_T *eap)
// Close superfluous windows.
for (wp = lastwin; open_wins > count;) {
r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer)
- || autowrite(wp->w_buffer, false) == OK);
+ || autowrite(wp->w_buffer, false) == OK) && !is_aucmd_win(wp);
if (!win_valid(wp)) {
// BufWrite Autocommands made the window invalid, start over
wp = lastwin;
@@ -3802,12 +3794,10 @@ static int chk_modeline(linenr_T lnum, int flags)
char *s;
char *e;
char *linecopy; // local copy of any modeline found
- int prev;
intmax_t vers;
- int end;
int retval = OK;
- prev = -1;
+ int prev = -1;
for (s = ml_get(lnum); *s != NUL; s++) {
if (prev == -1 || ascii_isspace(prev)) {
if ((prev != -1 && strncmp(s, "ex:", (size_t)3) == 0)
@@ -3853,7 +3843,7 @@ static int chk_modeline(linenr_T lnum, int flags)
// prepare for emsg()
estack_push(ETYPE_MODELINE, "modelines", lnum);
- end = false;
+ bool end = false;
while (end == false) {
s = skipwhite(s);
if (*s == NUL) {
@@ -4036,153 +4026,64 @@ char *buf_spname(buf_T *buf)
return NULL;
}
-static int buf_signcols_inner(buf_T *buf, int maximum)
-{
- sign_entry_T *sign; // a sign in the sign list
- int signcols = 0;
- int linesum = 0;
- linenr_T curline = 0;
-
- buf->b_signcols.sentinel = 0;
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > curline) {
- // Counted all signs, now add extmark signs
- if (curline > 0) {
- linesum += decor_signcols(buf, &decor_state, (int)curline - 1, (int)curline - 1,
- maximum - linesum);
- }
- curline = sign->se_lnum;
- if (linesum > signcols) {
- signcols = linesum;
- buf->b_signcols.sentinel = curline;
- if (signcols >= maximum) {
- return maximum;
- }
- }
- linesum = 0;
- }
- if (sign->se_has_text_or_icon) {
- linesum++;
- }
- }
-
- if (curline > 0) {
- linesum += decor_signcols(buf, &decor_state, (int)curline - 1, (int)curline - 1,
- maximum - linesum);
- }
- if (linesum > signcols) {
- signcols = linesum;
- if (signcols >= maximum) {
- return maximum;
- }
- }
-
- // Check extmarks between signs
- linesum = decor_signcols(buf, &decor_state, 0, (int)buf->b_ml.ml_line_count - 1, maximum);
-
- if (linesum > signcols) {
- signcols = linesum;
- buf->b_signcols.sentinel = curline;
- if (signcols >= maximum) {
- return maximum;
- }
- }
-
- return signcols;
-}
-
-/// Invalidate the signcolumn if needed after deleting
-/// signs between line1 and line2 (inclusive).
-///
-/// @param buf buffer to check
-/// @param line1 start of region being deleted
-/// @param line2 end of region being deleted
+/// Invalidate the signcolumn if needed after deleting a sign ranging from line1 to line2.
void buf_signcols_del_check(buf_T *buf, linenr_T line1, linenr_T line2)
{
- if (!buf->b_signcols.valid) {
- return;
- }
-
- if (!buf->b_signcols.sentinel) {
- buf->b_signcols.valid = false;
- return;
- }
-
linenr_T sent = buf->b_signcols.sentinel;
-
if (sent >= line1 && sent <= line2) {
- // Only invalidate when removing signs at the sentinel line.
- buf->b_signcols.valid = false;
+ // When removed sign overlaps the sentinel line, entire buffer needs to be checked.
+ buf->b_signcols.sentinel = buf->b_signcols.size = 0;
}
}
-/// Re-calculate the signcolumn after adding a sign.
-///
-/// @param buf buffer to check
-/// @param added sign being added
-void buf_signcols_add_check(buf_T *buf, sign_entry_T *added)
+/// Invalidate the signcolumn if needed after adding a sign ranging from line1 to line2.
+void buf_signcols_add_check(buf_T *buf, linenr_T line1, linenr_T line2)
{
- if (!buf->b_signcols.valid) {
- return;
- }
-
- if (!added || !buf->b_signcols.sentinel) {
- buf->b_signcols.valid = false;
+ if (!buf->b_signcols.sentinel) {
return;
}
- if (added->se_lnum == buf->b_signcols.sentinel) {
+ linenr_T sent = buf->b_signcols.sentinel;
+ if (sent >= line1 && sent <= line2) {
+ // If added sign overlaps sentinel line, increment without invalidating.
if (buf->b_signcols.size == buf->b_signcols.max) {
buf->b_signcols.max++;
}
buf->b_signcols.size++;
- redraw_buf_later(buf, UPD_NOT_VALID);
return;
}
- sign_entry_T *s;
-
- // Get first sign for added lnum
- for (s = added; s->se_prev && s->se_lnum == s->se_prev->se_lnum; s = s->se_prev) {}
-
- // Count signs for lnum
- int linesum = 1;
- for (; s->se_next && s->se_lnum == s->se_next->se_lnum; s = s->se_next) {
- linesum++;
+ if (line1 < buf->b_signcols.invalid_top) {
+ buf->b_signcols.invalid_top = line1;
}
- linesum += decor_signcols(buf, &decor_state, (int)s->se_lnum - 1, (int)s->se_lnum - 1,
- SIGN_SHOW_MAX - linesum);
-
- if (linesum > buf->b_signcols.size) {
- buf->b_signcols.size = linesum;
- buf->b_signcols.max = linesum;
- buf->b_signcols.sentinel = added->se_lnum;
- redraw_buf_later(buf, UPD_NOT_VALID);
+ if (line2 > buf->b_signcols.invalid_bot) {
+ buf->b_signcols.invalid_bot = line2;
}
}
-int buf_signcols(buf_T *buf, int maximum)
+int buf_signcols(buf_T *buf, int max)
{
- // The maximum can be determined from 'signcolumn' which is window scoped so
- // need to invalidate signcols if the maximum is greater than the previous
- // maximum.
- if (maximum > buf->b_signcols.max) {
- buf->b_signcols.valid = false;
- }
-
- if (!buf->b_signcols.valid) {
- int signcols = buf_signcols_inner(buf, maximum);
- // Check if we need to redraw
- if (signcols != buf->b_signcols.size) {
- buf->b_signcols.size = signcols;
- buf->b_signcols.max = maximum;
- redraw_buf_later(buf, UPD_NOT_VALID);
+ if (!buf->b_signs_with_text) {
+ buf->b_signcols.size = 0;
+ } else if (max <= 1 && buf->b_signs_with_text >= (size_t)max) {
+ buf->b_signcols.size = max;
+ } else {
+ linenr_T sent = buf->b_signcols.sentinel;
+ if (!sent || max > buf->b_signcols.max) {
+ // Recheck if the window scoped maximum 'signcolumn' is greater than the
+ // previous maximum or if there is no sentinel line yet.
+ buf->b_signcols.invalid_top = sent ? sent : 1;
+ buf->b_signcols.invalid_bot = sent ? sent : buf->b_ml.ml_line_count;
}
- buf->b_signcols.valid = true;
+ if (buf->b_signcols.invalid_bot) {
+ decor_validate_signcols(buf, max);
+ }
}
+ buf->b_signcols.max = max;
+ buf->b_signcols.invalid_top = MAXLNUM;
+ buf->b_signcols.invalid_bot = 0;
return buf->b_signcols.size;
}
@@ -4223,7 +4124,7 @@ bool buf_contents_changed(buf_T *buf)
bool differ = true;
// Allocate a buffer without putting it in the buffer list.
- buf_T *newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ buf_T *newbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
if (newbuf == NULL) {
return true;
}
@@ -4236,15 +4137,19 @@ bool buf_contents_changed(buf_T *buf)
aco_save_T aco;
aucmd_prepbuf(&aco, newbuf);
+ // We don't want to trigger autocommands now, they may have nasty
+ // side-effects like wiping buffers
+ block_autocmds();
+
if (ml_open(curbuf) == OK
&& readfile(buf->b_ffname, buf->b_fname,
- (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM,
+ 0, 0, (linenr_T)MAXLNUM,
&ea, READ_NEW | READ_DUMMY, false) == OK) {
// compare the two files line by line
if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count) {
differ = false;
for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
- if (strcmp(ml_get_buf(buf, lnum, false), ml_get(lnum)) != 0) {
+ if (strcmp(ml_get_buf(buf, lnum), ml_get(lnum)) != 0) {
differ = true;
break;
}
@@ -4260,6 +4165,8 @@ bool buf_contents_changed(buf_T *buf)
wipe_buffer(newbuf, false);
}
+ unblock_autocmds();
+
return differ;
}
@@ -4298,9 +4205,9 @@ int buf_open_scratch(handle_T bufnr, char *bufname)
apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
(void)setfname(curbuf, bufname, NULL, true);
apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
- set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
RESET_BINDING(curwin);
return OK;
}
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
index 610d9e37ec..36e70d1927 100644
--- a/src/nvim/buffer.h
+++ b/src/nvim/buffer.h
@@ -1,76 +1,74 @@
-#ifndef NVIM_BUFFER_H
-#define NVIM_BUFFER_H
+#pragma once
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: export
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/func_attr.h"
-#include "nvim/grid_defs.h" // for StlClickRecord
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memline.h"
#include "nvim/memline_defs.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
-// Values for buflist_getfile()
+/// Values for buflist_getfile()
enum getf_values {
- GETF_SETMARK = 0x01, // set pcmark before jumping
- GETF_ALT = 0x02, // jumping to alternate file (not buf num)
- GETF_SWITCH = 0x04, // respect 'switchbuf' settings when jumping
+ GETF_SETMARK = 0x01, ///< set pcmark before jumping
+ GETF_ALT = 0x02, ///< jumping to alternate file (not buf num)
+ GETF_SWITCH = 0x04, ///< respect 'switchbuf' settings when jumping
};
// Return values of getfile()
enum getf_retvalues {
- GETFILE_ERROR = 1, // normal error
- GETFILE_NOT_WRITTEN = 2, // "not written" error
- GETFILE_SAME_FILE = 0, // success, same file
- GETFILE_OPEN_OTHER = (-1), // success, opened another file
+ GETFILE_ERROR = 1, ///< normal error
+ GETFILE_NOT_WRITTEN = 2, ///< "not written" error
+ GETFILE_SAME_FILE = 0, ///< success, same file
+ GETFILE_OPEN_OTHER = -1, ///< success, opened another file
GETFILE_UNUSED = 8,
};
-// Values for buflist_new() flags
+/// Values for buflist_new() flags
enum bln_values {
- BLN_CURBUF = 1, // May re-use curbuf for new buffer
- BLN_LISTED = 2, // Put new buffer in buffer list
- BLN_DUMMY = 4, // Allocating dummy buffer
- BLN_NEW = 8, // create a new buffer
- BLN_NOOPT = 16, // Don't copy options to existing buffer
+ BLN_CURBUF = 1, ///< May re-use curbuf for new buffer
+ BLN_LISTED = 2, ///< Put new buffer in buffer list
+ BLN_DUMMY = 4, ///< Allocating dummy buffer
+ BLN_NEW = 8, ///< create a new buffer
+ BLN_NOOPT = 16, ///< Don't copy options to existing buffer
// BLN_DUMMY_OK = 32, // also find an existing dummy buffer
// BLN_REUSE = 64, // may re-use number from buf_reuse
- BLN_NOCURWIN = 128, // buffer is not associated with curwin
+ BLN_NOCURWIN = 128, ///< buffer is not associated with curwin
};
-// Values for action argument for do_buffer()
+/// Values for action argument for do_buffer()
enum dobuf_action_values {
- DOBUF_GOTO = 0, // go to specified buffer
- DOBUF_SPLIT = 1, // split window and go to specified buffer
- DOBUF_UNLOAD = 2, // unload specified buffer(s)
- DOBUF_DEL = 3, // delete specified buffer(s) from buflist
- DOBUF_WIPE = 4, // delete specified buffer(s) really
+ DOBUF_GOTO = 0, ///< go to specified buffer
+ DOBUF_SPLIT = 1, ///< split window and go to specified buffer
+ DOBUF_UNLOAD = 2, ///< unload specified buffer(s)
+ DOBUF_DEL = 3, ///< delete specified buffer(s) from buflist
+ DOBUF_WIPE = 4, ///< delete specified buffer(s) really
};
-// Values for start argument for do_buffer()
+/// Values for start argument for do_buffer()
enum dobuf_start_values {
- DOBUF_CURRENT = 0, // "count" buffer from current buffer
- DOBUF_FIRST = 1, // "count" buffer from first buffer
- DOBUF_LAST = 2, // "count" buffer from last buffer
- DOBUF_MOD = 3, // "count" mod. buffer from current buffer
+ DOBUF_CURRENT = 0, ///< "count" buffer from current buffer
+ DOBUF_FIRST = 1, ///< "count" buffer from first buffer
+ DOBUF_LAST = 2, ///< "count" buffer from last buffer
+ DOBUF_MOD = 3, ///< "count" mod. buffer from current buffer
};
-// flags for buf_freeall()
+/// flags for buf_freeall()
enum bfa_values {
- BFA_DEL = 1, // buffer is going to be deleted
- BFA_WIPE = 2, // buffer is going to be wiped out
- BFA_KEEP_UNDO = 4, // do not free undo information
- BFA_IGNORE_ABORT = 8, // do not abort for aborting()
+ BFA_DEL = 1, ///< buffer is going to be deleted
+ BFA_WIPE = 2, ///< buffer is going to be wiped out
+ BFA_KEEP_UNDO = 4, ///< do not free undo information
+ BFA_IGNORE_ABORT = 8, ///< do not abort for aborting()
};
-EXTERN char *msg_loclist INIT(= N_("[Location List]"));
-EXTERN char *msg_qflist INIT(= N_("[Quickfix List]"));
+EXTERN char *msg_loclist INIT( = N_("[Location List]"));
+EXTERN char *msg_qflist INIT( = N_("[Quickfix List]"));
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.h.generated.h"
@@ -137,8 +135,5 @@ static inline void buf_inc_changedtick(buf_T *const buf)
static inline bool buf_is_empty(buf_T *buf)
{
- return buf->b_ml.ml_line_count == 1
- && *ml_get_buf(buf, (linenr_T)1, false) == '\0';
+ return buf->b_ml.ml_line_count == 1 && *ml_get_buf(buf, 1) == '\0';
}
-
-#endif // NVIM_BUFFER_H
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 5ae6f55ad8..0d8d8d581f 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1,14 +1,13 @@
-#ifndef NVIM_BUFFER_DEFS_H
-#define NVIM_BUFFER_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
-typedef struct file_buffer buf_T; // Forward declaration
+typedef struct file_buffer buf_T;
-// Reference to a buffer that stores the value of buf_free_count.
-// bufref_valid() only needs to check "buf" when the count differs.
+/// Reference to a buffer that stores the value of buf_free_count.
+/// bufref_valid() only needs to check "buf" when the count differs.
typedef struct {
buf_T *br_buf;
int br_fnum;
@@ -17,19 +16,19 @@ typedef struct {
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
-#include "nvim/eval/typval.h"
-#include "nvim/garray.h"
+#include "nvim/arglist_defs.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/extmark_defs.h"
+#include "nvim/garray_defs.h"
#include "nvim/grid_defs.h"
-#include "nvim/hashtab.h"
+#include "nvim/hashtab_defs.h"
#include "nvim/highlight_defs.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
+#include "nvim/mapping_defs.h"
#include "nvim/mark_defs.h"
#include "nvim/marktree.h"
-// for float window title
-#include "nvim/extmark_defs.h"
-// for click definitions
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/statusline_defs.h"
#include "nvim/undo_defs.h"
@@ -118,28 +117,6 @@ typedef struct taggy {
char *user_data; // used with tagfunc
} taggy_T;
-typedef struct buffblock buffblock_T;
-typedef struct buffheader buffheader_T;
-
-// structure used to store one block of the stuff/redo/recording buffers
-struct buffblock {
- buffblock_T *b_next; // pointer to next buffblock
- char b_str[1]; // contents (actually longer)
-};
-
-// header used for the stuff buffer and the redo buffer
-struct buffheader {
- buffblock_T bh_first; // first (dummy) block of list
- buffblock_T *bh_curr; // buffblock for appending
- size_t bh_index; // index for reading
- size_t bh_space; // space in bh_curr for appending
-};
-
-typedef struct {
- buffheader_T sr_redobuff;
- buffheader_T sr_old_redobuff;
-} save_redo_T;
-
// Structure that contains all options that are local to a window.
// Used twice in a window: for the current buffer and for all buffers.
// Also used in wininfo_T.
@@ -163,18 +140,18 @@ typedef struct {
#define w_p_fen_save w_onebuf_opt.wo_fen_save
char *wo_fdi;
#define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore'
- long wo_fdl;
+ OptInt wo_fdl;
#define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel'
- long wo_fdl_save;
+ OptInt wo_fdl_save;
// 'foldlevel' state saved for diff mode
#define w_p_fdl_save w_onebuf_opt.wo_fdl_save
char *wo_fdm;
#define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod'
char *wo_fdm_save;
#define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode
- long wo_fml;
+ OptInt wo_fml;
#define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines'
- long wo_fdn;
+ OptInt wo_fdn;
#define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax'
char *wo_fde;
#define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr'
@@ -194,7 +171,7 @@ typedef struct {
#define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit'
unsigned wo_ve_flags;
#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit'
- long wo_nuw;
+ OptInt wo_nuw;
#define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
int wo_wfh;
#define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight'
@@ -206,8 +183,10 @@ typedef struct {
#define w_p_rl w_onebuf_opt.wo_rl // 'rightleft'
char *wo_rlc;
#define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd'
- long wo_scr;
+ OptInt wo_scr;
#define w_p_scr w_onebuf_opt.wo_scr // 'scroll'
+ int wo_sms;
+#define w_p_sms w_onebuf_opt.wo_sms // 'smoothscroll'
int wo_spell;
#define w_p_spell w_onebuf_opt.wo_spell // 'spell'
int wo_cuc;
@@ -238,7 +217,7 @@ typedef struct {
#define w_p_wrap_save w_onebuf_opt.wo_wrap_save
char *wo_cocu; // 'concealcursor'
#define w_p_cocu w_onebuf_opt.wo_cocu
- long wo_cole; // 'conceallevel'
+ OptInt wo_cole; // 'conceallevel'
#define w_p_cole w_onebuf_opt.wo_cole
int wo_crb;
#define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind'
@@ -246,13 +225,17 @@ typedef struct {
#define w_p_crb_save w_onebuf_opt.wo_crb_save
char *wo_scl;
#define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn'
+ OptInt wo_siso;
+#define w_p_siso w_onebuf_opt.wo_siso // 'sidescrolloff' local value
+ OptInt wo_so;
+#define w_p_so w_onebuf_opt.wo_so // 'scrolloff' local value
char *wo_winhl;
#define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight'
char *wo_lcs;
#define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars'
char *wo_fcs;
#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars'
- long wo_winbl;
+ OptInt wo_winbl;
#define w_p_winbl w_onebuf_opt.wo_winbl // 'winblend'
LastSet wo_script_ctx[WV_COUNT]; // SCTXs for window-local options
@@ -279,27 +262,7 @@ struct wininfo_S {
int wi_changelistidx; // copy of w_changelistidx
};
-// Argument list: Array of file names.
-// Used for the global argument list and the argument lists local to a window.
-//
-// TODO(neovim): move struct arglist to another header
-typedef struct arglist {
- garray_T al_ga; // growarray with the array of file names
- int al_refcount; // number of windows using this arglist
- int id; ///< id of this arglist
-} alist_T;
-
-// For each argument remember the file name as it was given, and the buffer
-// number that contains the expanded file name (required for when ":cd" is
-// used).
-//
-// TODO(Felipe): move aentry_T to another header
-typedef struct argentry {
- char *ae_fname; // file name as specified
- int ae_fnum; // buffer number with expanded file name
-} aentry_T;
-
-#define ALIST(win) (win)->w_alist
+#define ALIST(win) (win)->w_alist
#define GARGLIST ((aentry_T *)global_alist.al_ga.ga_data)
#define ARGLIST ((aentry_T *)ALIST(curwin)->al_ga.ga_data)
#define WARGLIST(wp) ((aentry_T *)ALIST(wp)->al_ga.ga_data)
@@ -308,81 +271,6 @@ typedef struct argentry {
#define ARGCOUNT (ALIST(curwin)->al_ga.ga_len)
#define WARGCOUNT(wp) (ALIST(wp)->al_ga.ga_len)
-// Used for the typeahead buffer: typebuf.
-typedef struct {
- uint8_t *tb_buf; // buffer for typed characters
- uint8_t *tb_noremap; // mapping flags for characters in tb_buf[]
- int tb_buflen; // size of tb_buf[]
- int tb_off; // current position in tb_buf[]
- int tb_len; // number of valid bytes in tb_buf[]
- int tb_maplen; // nr of mapped bytes in tb_buf[]
- int tb_silent; // nr of silently mapped bytes in tb_buf[]
- int tb_no_abbr_cnt; // nr of bytes without abbrev. in tb_buf[]
- int tb_change_cnt; // nr of time tb_buf was changed; never zero
-} typebuf_T;
-
-// Struct to hold the saved typeahead for save_typeahead().
-typedef struct {
- typebuf_T save_typebuf;
- bool typebuf_valid; // true when save_typebuf valid
- int old_char;
- int old_mod_mask;
- buffheader_T save_readbuf1;
- buffheader_T save_readbuf2;
- String save_inputbuf;
-} tasave_T;
-
-// Structure used for mappings and abbreviations.
-typedef struct mapblock mapblock_T;
-struct mapblock {
- mapblock_T *m_next; // next mapblock in list
- char *m_keys; // mapped from, lhs
- char *m_str; // mapped to, rhs
- char *m_orig_str; // rhs as entered by the user
- LuaRef m_luaref; // lua function reference as rhs
- int m_keylen; // strlen(m_keys)
- int m_mode; // valid mode
- int m_simplified; // m_keys was simplified, do no use this map
- // if keys are typed
- int m_noremap; // if non-zero no re-mapping for m_str
- char m_silent; // <silent> used, don't echo commands
- char m_nowait; // <nowait> used
- char m_expr; // <expr> used, m_str is an expression
- sctx_T m_script_ctx; // SCTX where map was defined
- char *m_desc; // description of mapping
- bool m_replace_keycodes; // replace keycodes in result of expression
-};
-
-/// Used for highlighting in the status line.
-typedef struct stl_hlrec stl_hlrec_t;
-struct stl_hlrec {
- char *start;
- int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
-};
-
-/// Used for building the status line.
-typedef struct stl_item stl_item_t;
-struct stl_item {
- // Where the item starts in the status line output buffer
- char *start;
- // Function to run for ClickFunc items.
- char *cmd;
- // The minimum width of the item
- int minwid;
- // The maximum width of the item
- int maxwid;
- enum {
- Normal,
- Empty,
- Group,
- Separate,
- Highlight,
- TabPage,
- ClickFunc,
- Trunc,
- } type;
-};
-
// values for b_syn_spell: what to do with toplevel text
#define SYNSPL_DEFAULT 0 // spell check if @Spell not defined
#define SYNSPL_TOP 1 // spell check toplevel text
@@ -392,17 +280,14 @@ struct stl_item {
#define SYNFLD_START 0 // use level of item at start of line
#define SYNFLD_MINIMUM 1 // use lowest local minimum level on line
-// avoid #ifdefs for when b_spell is not available
-#define B_SPELL(buf) ((buf)->b_spell)
-
typedef struct qf_info_S qf_info_T;
// Used for :syntime: timing of executing a syntax pattern.
typedef struct {
proftime_T total; // total time used
proftime_T slowest; // time of slowest call
- long count; // nr of times used
- long match; // nr of times matched
+ int count; // nr of times used
+ int match; // nr of times matched
} syn_time_T;
// These are items normally related to a buffer. But when using ":ownsyntax"
@@ -488,7 +373,7 @@ typedef struct {
#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, false, false }
-EXTERN int curbuf_splice_pending INIT(= 0);
+EXTERN int curbuf_splice_pending INIT( = 0);
#define BUF_HAS_QF_ENTRY 1
#define BUF_HAS_LL_ENTRY 2
@@ -545,10 +430,10 @@ struct file_buffer {
/// This is a dictionary item used to store b:changedtick.
ChangedtickDictItem changedtick_di;
- varnumber_T b_last_changedtick; // b:changedtick when TextChanged or
- // TextChangedI was last triggered.
- varnumber_T b_last_changedtick_pum; // b:changedtick when TextChangedP was
+ varnumber_T b_last_changedtick; // b:changedtick when TextChanged was
// last triggered.
+ varnumber_T b_last_changedtick_i; // b:changedtick for TextChangedI
+ varnumber_T b_last_changedtick_pum; // b:changedtick for TextChangedP
bool b_saving; // Set to true if we are in the middle of
// saving the buffer.
@@ -567,10 +452,10 @@ struct file_buffer {
disptick_T b_mod_tick_decor; // last display tick decoration providers
// where invoked
- long b_mtime; // last change time of original file
- long b_mtime_ns; // nanoseconds of last change time
- long b_mtime_read; // last change time when reading
- long b_mtime_read_ns; // nanoseconds of last read time
+ int64_t b_mtime; // last change time of original file
+ int64_t b_mtime_ns; // nanoseconds of last change time
+ int64_t b_mtime_read; // last change time when reading
+ int64_t b_mtime_read_ns; // nanoseconds of last read time
uint64_t b_orig_size; // size of original file in bytes
int b_orig_mode; // mode of original file
time_t b_last_used; // time when the buffer was last used; used
@@ -615,13 +500,13 @@ struct file_buffer {
u_header_T *b_u_newhead; // pointer to newest header; may not be valid
// if b_u_curhead is not NULL
u_header_T *b_u_curhead; // pointer to current header
- int b_u_numhead; // current number of headers
- bool b_u_synced; // entry lists are synced
- long b_u_seq_last; // last used undo sequence number
- long b_u_save_nr_last; // counter for last file write
- long b_u_seq_cur; // uh_seq of header below which we are now
- time_t b_u_time_cur; // uh_time of header below which we are now
- long b_u_save_nr_cur; // file write nr after which we are now
+ int b_u_numhead; // current number of headers
+ bool b_u_synced; // entry lists are synced
+ int b_u_seq_last; // last used undo sequence number
+ int b_u_save_nr_last; // counter for last file write
+ int b_u_seq_cur; // uh_seq of header below which we are now
+ time_t b_u_time_cur; // uh_time of header below which we are now
+ int b_u_save_nr_cur; // file write nr after which we are now
// variables for "U" command in undo.c
char *b_u_line_ptr; // saved line for "U" command
@@ -631,8 +516,8 @@ struct file_buffer {
bool b_scanned; // ^N/^P have scanned this buffer
// flags for use of ":lmap" and IM control
- long b_p_iminsert; // input mode for insert
- long b_p_imsearch; // input mode for search
+ OptInt b_p_iminsert; // input mode for insert
+ OptInt b_p_imsearch; // input mode for search
#define B_IMODE_USE_INSERT (-1) // Use b_p_iminsert value for search
#define B_IMODE_NONE 0 // Input via none
#define B_IMODE_LMAP 1 // Input via langmap
@@ -653,7 +538,7 @@ struct file_buffer {
int b_p_ai; ///< 'autoindent'
int b_p_ai_nopaste; ///< b_p_ai saved for paste mode
char *b_p_bkc; ///< 'backupco
- unsigned int b_bkc_flags; ///< flags for 'backupco
+ unsigned b_bkc_flags; ///< flags for 'backupco
int b_p_ci; ///< 'copyindent'
int b_p_bin; ///< 'binary'
int b_p_bomb; ///< 'bomb'
@@ -661,7 +546,7 @@ struct file_buffer {
char *b_p_bt; ///< 'buftype'
int b_has_qf_entry; ///< quickfix exists for buffer
int b_p_bl; ///< 'buflisted'
- long b_p_channel; ///< 'channel'
+ OptInt b_p_channel; ///< 'channel'
int b_p_cin; ///< 'cindent'
char *b_p_cino; ///< 'cinoptions'
char *b_p_cink; ///< 'cinkeys'
@@ -714,27 +599,27 @@ struct file_buffer {
int b_p_pi; ///< 'preserveindent'
char *b_p_qe; ///< 'quoteescape'
int b_p_ro; ///< 'readonly'
- long b_p_sw; ///< 'shiftwidth'
- long b_p_scbk; ///< 'scrollback'
+ OptInt b_p_sw; ///< 'shiftwidth'
+ OptInt b_p_scbk; ///< 'scrollback'
int b_p_si; ///< 'smartindent'
- long b_p_sts; ///< 'softtabstop'
- long b_p_sts_nopaste; ///< b_p_sts saved for paste mode
+ OptInt b_p_sts; ///< 'softtabstop'
+ OptInt b_p_sts_nopaste; ///< b_p_sts saved for paste mode
char *b_p_sua; ///< 'suffixesadd'
int b_p_swf; ///< 'swapfile'
- long b_p_smc; ///< 'synmaxcol'
+ OptInt b_p_smc; ///< 'synmaxcol'
char *b_p_syn; ///< 'syntax'
- long b_p_ts; ///< 'tabstop'
- long b_p_tw; ///< 'textwidth'
- long b_p_tw_nobin; ///< b_p_tw saved for binary mode
- long b_p_tw_nopaste; ///< b_p_tw saved for paste mode
- long b_p_wm; ///< 'wrapmargin'
- long b_p_wm_nobin; ///< b_p_wm saved for binary mode
- long b_p_wm_nopaste; ///< b_p_wm saved for paste mode
+ OptInt b_p_ts; ///< 'tabstop'
+ OptInt b_p_tw; ///< 'textwidth'
+ OptInt b_p_tw_nobin; ///< b_p_tw saved for binary mode
+ OptInt b_p_tw_nopaste; ///< b_p_tw saved for paste mode
+ OptInt b_p_wm; ///< 'wrapmargin'
+ OptInt b_p_wm_nobin; ///< b_p_wm saved for binary mode
+ OptInt b_p_wm_nopaste; ///< b_p_wm saved for paste mode
char *b_p_vsts; ///< 'varsofttabstop'
- long *b_p_vsts_array; ///< 'varsofttabstop' in internal format
+ colnr_T *b_p_vsts_array; ///< 'varsofttabstop' in internal format
char *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode
char *b_p_vts; ///< 'vartabstop'
- long *b_p_vts_array; ///< 'vartabstop' in internal format
+ colnr_T *b_p_vts_array; ///< 'vartabstop' in internal format
char *b_p_keymap; ///< 'keymap'
// local values for options which are normally global
@@ -751,7 +636,7 @@ struct file_buffer {
char *b_p_tsr; ///< 'thesaurus' local value
char *b_p_tsrfu; ///< 'thesaurusfunc' local value
Callback b_tsrfu_cb; ///< 'thesaurusfunc' callback
- long b_p_ul; ///< 'undolevels' local value
+ OptInt b_p_ul; ///< 'undolevels' local value
int b_p_udf; ///< 'undofile'
char *b_p_lw; ///< 'lispwords' local value
@@ -823,8 +708,7 @@ struct file_buffer {
bool b_help; // true for help file buffer (when set b_p_bt
// is "help")
bool b_spell; // True for a spell file buffer, most fields
- // are not used! Use the B_SPELL macro to
- // access b_spell without #ifdef.
+ // are not used!
char *b_prompt_text; // set by prompt_setprompt()
Callback b_prompt_callback; // set by prompt_setcallback()
@@ -836,12 +720,12 @@ struct file_buffer {
// normally points to this, but some windows
// may use a different synblock_T.
- sign_entry_T *b_signlist; // list of placed signs
struct {
int size; // last calculated number of sign columns
- bool valid; // calculated sign columns is valid
+ int max; // maximum value size is valid for.
linenr_T sentinel; // a line number which is holding up the signcolumn
- int max; // Maximum value size is valid for.
+ linenr_T invalid_top; // first invalid line number that needs to be checked
+ linenr_T invalid_bot; // last invalid line number that needs to be checked
} b_signcols;
Terminal *terminal; // Terminal instance associated with the buffer
@@ -852,8 +736,10 @@ struct file_buffer {
MarkTree b_marktree[1];
Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces
+ size_t b_virt_text_inline; // number of inline virtual texts
size_t b_virt_line_blocks; // number of virt_line blocks
size_t b_signs; // number of sign extmarks
+ size_t b_signs_with_text; // number of sign extmarks with text
// array of channel_id:s which have asked to receive updates for this
// buffer.
@@ -914,16 +800,16 @@ struct diffblock_S {
typedef struct tabpage_S tabpage_T;
struct tabpage_S {
handle_T handle;
- tabpage_T *tp_next; ///< next tabpage or NULL
- frame_T *tp_topframe; ///< topframe for the windows
- win_T *tp_curwin; ///< current window in this Tab page
- win_T *tp_prevwin; ///< previous window in this Tab page
- win_T *tp_firstwin; ///< first window in this Tab page
- win_T *tp_lastwin; ///< last window in this Tab page
- long tp_old_Rows_avail; ///< ROWS_AVAIL when Tab page was left
- long tp_old_Columns; ///< Columns when Tab page was left, -1 when
- ///< calling win_new_screen_cols() postponed
- long tp_ch_used; ///< value of 'cmdheight' when frame size was set
+ tabpage_T *tp_next; ///< next tabpage or NULL
+ frame_T *tp_topframe; ///< topframe for the windows
+ win_T *tp_curwin; ///< current window in this Tab page
+ win_T *tp_prevwin; ///< previous window in this Tab page
+ win_T *tp_firstwin; ///< first window in this Tab page
+ win_T *tp_lastwin; ///< last window in this Tab page
+ int64_t tp_old_Rows_avail; ///< ROWS_AVAIL when Tab page was left
+ int64_t tp_old_Columns; ///< Columns when Tab page was left, -1 when
+ ///< calling win_new_screen_cols() postponed
+ OptInt tp_ch_used; ///< value of 'cmdheight' when frame size was set
diff_T *tp_first_diff;
buf_T *(tp_diffbuf[DB_COUNT]);
@@ -1038,7 +924,7 @@ enum {
// NE -> kFloatAnchorEast
// SW -> kFloatAnchorSouth
// SE -> kFloatAnchorSouth | kFloatAnchorEast
-EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" });
+EXTERN const char *const float_anchor_str[] INIT( = { "NW", "NE", "SW", "SE" });
typedef enum {
kFloatRelativeEditor = 0,
@@ -1047,8 +933,8 @@ typedef enum {
kFloatRelativeMouse = 3,
} FloatRelative;
-EXTERN const char *const float_relative_str[] INIT(= { "editor", "win",
- "cursor", "mouse" });
+EXTERN const char *const float_relative_str[] INIT( = { "editor", "win",
+ "cursor", "mouse" });
typedef enum {
kWinStyleUnused = 0,
@@ -1061,6 +947,11 @@ typedef enum {
kAlignRight = 2,
} AlignTextPos;
+typedef enum {
+ kBorderTextTitle = 0,
+ kBorderTextFooter = 1,
+} BorderTextType;
+
typedef struct {
Window window;
lpos_T bufpos;
@@ -1073,15 +964,21 @@ typedef struct {
int zindex;
WinStyle style;
bool border;
- bool title;
bool shadow;
- schar_T border_chars[8];
+ char border_chars[8][MAX_SCHAR_SIZE];
int border_hl_ids[8];
int border_attr[8];
+ bool title;
AlignTextPos title_pos;
VirtText title_chunks;
int title_width;
+ bool footer;
+ AlignTextPos footer_pos;
+ VirtText footer_chunks;
+ int footer_width;
bool noautocmd;
+ bool fixed;
+ bool hide;
} FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
@@ -1091,7 +988,9 @@ typedef struct {
.focusable = true, \
.zindex = kZIndexFloatDefault, \
.style = kWinStyleUnused, \
- .noautocmd = false })
+ .noautocmd = false, \
+ .hide = false, \
+ .fixed = false })
// Structure to store last cursor position and topline. Used by check_lnums()
// and reset_lnums().
@@ -1102,6 +1001,45 @@ typedef struct {
pos_T w_cursor_corr; // corrected cursor position
} pos_save_T;
+/// Characters from the 'listchars' option.
+typedef struct {
+ int eol;
+ int ext;
+ int prec;
+ int nbsp;
+ int space;
+ int tab1; ///< first tab character
+ int tab2; ///< second tab character
+ int tab3; ///< third tab character
+ int lead;
+ int trail;
+ int *multispace;
+ int *leadmultispace;
+ int conceal;
+} lcs_chars_T;
+
+/// Characters from the 'fillchars' option.
+typedef struct {
+ int stl;
+ int stlnc;
+ int wbr;
+ int horiz;
+ int horizup;
+ int horizdown;
+ int vert;
+ int vertleft;
+ int vertright;
+ int verthoriz;
+ int fold;
+ int foldopen; ///< when fold is open
+ int foldclosed; ///< when fold is closed
+ int foldsep; ///< continuous fold marker
+ int diff;
+ int msgsep;
+ int eob;
+ int lastline;
+} fcs_chars_T;
+
/// Structure which contains all information that belongs to a window.
///
/// All row numbers are relative to the start of the window, except w_winrow.
@@ -1127,22 +1065,24 @@ struct window_S {
win_T *w_prev; ///< link to previous window
win_T *w_next; ///< link to next window
bool w_closing; ///< window is being closed, don't let
- /// autocommands close it too.
+ ///< autocommands close it too.
frame_T *w_frame; ///< frame containing this window
pos_T w_cursor; ///< cursor position in buffer
colnr_T w_curswant; ///< Column we want to be at. This is
- /// used to try to stay in the same column
- /// for up/down cursor motions.
+ ///< used to try to stay in the same column
+ ///< for up/down cursor motions.
int w_set_curswant; // If set, then update w_curswant the next
// time through cursupdate() to the
// current virtual column
+ linenr_T w_cursorline; ///< Where 'cursorline' should be drawn,
+ ///< can be different from w_cursor.lnum
+ ///< for closed folds.
linenr_T w_last_cursorline; ///< where last 'cursorline' was drawn
- pos_T w_last_cursormoved; ///< for CursorMoved event
// the next seven are used to update the visual part
char w_old_visual_mode; ///< last known VIsual_mode
@@ -1155,44 +1095,11 @@ struct window_S {
linenr_T w_last_cursor_lnum_rnu; ///< cursor lnum when 'rnu' was last redrawn
- // 'listchars' characters. Defaults set in set_chars_option().
- struct {
- int eol;
- int ext;
- int prec;
- int nbsp;
- int space;
- int tab1; ///< first tab character
- int tab2; ///< second tab character
- int tab3; ///< third tab character
- int lead;
- int trail;
- int *multispace;
- int *leadmultispace;
- int conceal;
- } w_p_lcs_chars;
-
- // 'fillchars' characters. Defaults set in set_chars_option().
- struct {
- int stl;
- int stlnc;
- int wbr;
- int horiz;
- int horizup;
- int horizdown;
- int vert;
- int vertleft;
- int vertright;
- int verthoriz;
- int fold;
- int foldopen; ///< when fold is open
- int foldclosed; ///< when fold is closed
- int foldsep; ///< continuous fold marker
- int diff;
- int msgsep;
- int eob;
- int lastline;
- } w_p_fcs_chars;
+ /// 'listchars' characters. Defaults set in set_chars_option().
+ lcs_chars_T w_p_lcs_chars;
+
+ /// 'fillchars' characters. Defaults set in set_chars_option().
+ fcs_chars_T w_p_fcs_chars;
// "w_topline", "w_leftcol" and "w_skipcol" specify the offsets for
// displaying the buffer.
@@ -1205,11 +1112,12 @@ struct window_S {
bool w_botfill; // true when filler lines are actually
// below w_topline (at end of file)
bool w_old_botfill; // w_botfill at last redraw
- colnr_T w_leftcol; // window column number of the left most
+ colnr_T w_leftcol; // screen column number of the left most
// character in the window; used when
// 'wrap' is off
- colnr_T w_skipcol; // starting column when a single line
- // doesn't fit in the window
+ colnr_T w_skipcol; // starting screen column for the first
+ // line in the window; used when 'wrap' is
+ // on; does not include win_col_off()
// six fields that are only used when there is a WinScrolled autocommand
linenr_T w_last_topline; ///< last known value for w_topline
@@ -1235,6 +1143,7 @@ struct window_S {
int w_hsep_height; // Number of horizontal separator rows (0 or 1)
int w_vsep_width; // Number of vertical separator columns (0 or 1).
pos_save_T w_save_cursor; // backup of cursor pos and topline
+ bool w_do_win_fix_cursor; // if true cursor may be invalid
int w_winrow_off; ///< offset from winrow to the inner window area
int w_wincol_off; ///< offset from wincol to the inner window area
@@ -1262,8 +1171,13 @@ struct window_S {
int w_valid;
pos_T w_valid_cursor; // last known position of w_cursor, used to adjust w_valid
colnr_T w_valid_leftcol; // last known w_leftcol
+ colnr_T w_valid_skipcol; // last known w_skipcol
bool w_viewport_invalid;
+ linenr_T w_viewport_last_topline; // topline when the viewport was last updated
+ linenr_T w_viewport_last_botline; // botline when the viewport was last updated
+ linenr_T w_viewport_last_topfill; // topfill when the viewport was last updated
+ linenr_T w_viewport_last_skipcol; // skipcol when the viewport was last updated
// w_cline_height is the number of physical lines taken by the buffer line
// that the cursor is on. We use this to avoid extra calls to plines_win().
@@ -1309,6 +1223,8 @@ struct window_S {
int w_nrwidth; // width of 'number' and 'relativenumber'
// column being used
int w_scwidth; // width of 'signcolumn'
+ int w_minscwidth; // minimum width or SCL_NO/SCL_NUM
+ int w_maxscwidth; // maximum width or SCL_NO/SCL_NUM
// === end of cached values ===
@@ -1321,13 +1237,16 @@ struct window_S {
bool w_redr_border; // if true border must be redrawn
bool w_redr_statuscol; // if true 'statuscolumn' must be redrawn
- // remember what is shown in the ruler for this window (if 'ruler' set)
- pos_T w_ru_cursor; // cursor position shown in ruler
- colnr_T w_ru_virtcol; // virtcol shown in ruler
- linenr_T w_ru_topline; // topline shown in ruler
- linenr_T w_ru_line_count; // line count used for ruler
- int w_ru_topfill; // topfill shown in ruler
- char w_ru_empty; // true if ruler shows 0-1 (empty line)
+ // remember what is shown in the 'statusline'-format elements
+ pos_T w_stl_cursor; // cursor position when last redrawn
+ colnr_T w_stl_virtcol; // virtcol when last redrawn
+ linenr_T w_stl_topline; // topline when last redrawn
+ linenr_T w_stl_line_count; // line count when last redrawn
+ int w_stl_topfill; // topfill when last redrawn
+ char w_stl_empty; // true if elements show 0-1 (empty line)
+ int w_stl_recording; // reg_recording when last redrawn
+ int w_stl_state; // get_real_state() when last redrawn
+ int w_stl_visual_mode; // VIsual_mode when last redrawn
int w_alt_fnum; // alternate file (for # and CTRL-^)
@@ -1345,6 +1264,8 @@ struct window_S {
// this window, w_allbuf_opt is for all buffers in this window.
winopt_T w_onebuf_opt;
winopt_T w_allbuf_opt;
+ // transform a pointer to a "onebuf" option into a "allbuf" option
+#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T))
// A few options have local flags for P_INSECURE.
uint32_t w_p_stl_flags; // flags for 'statusline'
@@ -1353,8 +1274,6 @@ struct window_S {
uint32_t w_p_fdt_flags; // flags for 'foldtext'
colorcol_T *w_p_cc_cols; // array of columns to highlight or NULL
uint8_t w_p_culopt_flags; // flags for cursorline highlighting
- long w_p_siso; // 'sidescrolloff' local value
- long w_p_so; // 'scrolloff' local value
int w_briopt_min; // minimum width for breakindent
int w_briopt_shift; // additional shift for breakindent
@@ -1362,10 +1281,7 @@ struct window_S {
int w_briopt_list; // additional indent for lists
int w_briopt_vcol; // indent for specific column
- // transform a pointer to a "onebuf" option into a "allbuf" option
-#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T))
-
- long w_scbind_pos;
+ int w_scbind_pos;
ScopeDictDictItem w_winvar; ///< Variable for "w:" dictionary.
dict_T *w_vars; ///< Dictionary with w: variables.
@@ -1408,6 +1324,7 @@ struct window_S {
int w_prev_fraction_row;
linenr_T w_nrwidth_line_count; // line count when ml_nrwidth_width was computed.
+ linenr_T w_statuscol_line_count; // line count when 'statuscolumn' width was computed.
int w_nrwidth_width; // nr of chars to print line count.
qf_info_T *w_llist; // Location list for this window
@@ -1431,30 +1348,8 @@ struct window_S {
size_t w_statuscol_click_defs_size;
};
-/// Struct to hold info for 'statuscolumn'
-typedef struct statuscol statuscol_T;
-
-struct statuscol {
- int width; ///< width of the status column
- int cur_attr; ///< current attributes in text
- int num_attr; ///< attributes used for line number
- int fold_attr; ///< attributes used for fold column
- int sign_attr[SIGN_SHOW_MAX + 1]; ///< attributes used for signs
- int truncate; ///< truncated width
- bool draw; ///< draw statuscolumn or not
- char fold_text[9 * 4 + 1]; ///< text in fold column (%C)
- char *sign_text[SIGN_SHOW_MAX + 1]; ///< text in sign column (%s)
- char text[MAXPATHL]; ///< text in status column
- char *textp; ///< current position in text
- char *text_end; ///< end of text (the NUL byte)
- stl_hlrec_t *hlrec; ///< highlight groups
- stl_hlrec_t *hlrecp; ///< current highlight group
-};
-
/// Macros defined in Vim, but not in Neovim
// uncrustify:off
#define CHANGEDTICK(buf) \
(=== Include buffer.h & use buf_(get|set|inc) _changedtick ===)
// uncrustify:on
-
-#endif // NVIM_BUFFER_DEFS_H
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 075ac2adbf..01bcb9d7be 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -1,28 +1,25 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stddef.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
-#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.c.generated.h" // IWYU pragma: export
@@ -188,9 +185,9 @@ void buf_updates_unload(buf_T *buf, bool can_reload)
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
- textlock++;
- nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL);
- textlock--;
+ TEXTLOCK_WRAP({
+ nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL);
+ });
}
if (keep) {
@@ -305,9 +302,11 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints);
args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
}
- textlock++;
- Object res = nlua_call_ref(cb.on_lines, "lines", args, false, NULL);
- textlock--;
+
+ Object res;
+ TEXTLOCK_WRAP({
+ res = nlua_call_ref(cb.on_lines, "lines", args, false, NULL);
+ });
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
@@ -354,9 +353,10 @@ void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcoun
ADD_C(args, INTEGER_OBJ(new_col));
ADD_C(args, INTEGER_OBJ(new_byte));
- textlock++;
- Object res = nlua_call_ref(cb.on_bytes, "bytes", args, false, NULL);
- textlock--;
+ Object res;
+ TEXTLOCK_WRAP({
+ res = nlua_call_ref(cb.on_bytes, "bytes", args, false, NULL);
+ });
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
@@ -389,10 +389,10 @@ void buf_updates_changedtick(buf_T *buf)
// next argument is b:changedtick
ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf)));
- textlock++;
- Object res = nlua_call_ref(cb.on_changedtick, "changedtick",
- args, false, NULL);
- textlock--;
+ Object res;
+ TEXTLOCK_WRAP({
+ res = nlua_call_ref(cb.on_changedtick, "changedtick", args, false, NULL);
+ });
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h
index 961fec879b..e844f3b2a8 100644
--- a/src/nvim/buffer_updates.h
+++ b/src/nvim/buffer_updates.h
@@ -1,11 +1,11 @@
-#ifndef NVIM_BUFFER_UPDATES_H
-#define NVIM_BUFFER_UPDATES_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/extmark.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/extmark_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.h.generated.h"
#endif
-
-#endif // NVIM_BUFFER_UPDATES_H
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
new file mode 100644
index 0000000000..f774fcb057
--- /dev/null
+++ b/src/nvim/bufwrite.c
@@ -0,0 +1,1943 @@
+// bufwrite.c: functions for writing a buffer
+
+#include <fcntl.h>
+#include <iconv.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <uv.h>
+
+#include "auto/config.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
+#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/bufwrite.h"
+#include "nvim/change.h"
+#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_eval.h"
+#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
+#include "nvim/gettext.h"
+#include "nvim/globals.h"
+#include "nvim/highlight.h"
+#include "nvim/iconv_defs.h"
+#include "nvim/input.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mbyte.h"
+#include "nvim/memline.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/input.h"
+#include "nvim/path.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sha256.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+#include "nvim/vim_defs.h"
+
+static const char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')";
+static const char e_patchmode_cant_touch_empty_original_file[]
+ = N_("E206: Patchmode: can't touch empty original file");
+static const char e_write_error_conversion_failed_make_fenc_empty_to_override[]
+ = N_("E513: Write error, conversion failed (make 'fenc' empty to override)");
+static const char e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override[]
+ = N_("E513: Write error, conversion failed in line %" PRIdLINENR
+ " (make 'fenc' empty to override)");
+static const char e_write_error_file_system_full[]
+ = N_("E514: Write error (file system full?)");
+static const char e_no_matching_autocommands_for_buftype_str_buffer[]
+ = N_("E676: No matching autocommands for buftype=%s buffer");
+
+typedef struct {
+ const char *num;
+ char *msg;
+ int arg;
+ bool alloc;
+} Error_T;
+
+#define SMALLBUFSIZE 256 // size of emergency write buffer
+
+// Structure to pass arguments from buf_write() to buf_write_bytes().
+struct bw_info {
+ int bw_fd; // file descriptor
+ char *bw_buf; // buffer with data to be written
+ int bw_len; // length of data
+ int bw_flags; // FIO_ flags
+ uint8_t bw_rest[CONV_RESTLEN]; // not converted bytes
+ int bw_restlen; // nr of bytes in bw_rest[]
+ int bw_first; // first write call
+ char *bw_conv_buf; // buffer for writing converted chars
+ size_t bw_conv_buflen; // size of bw_conv_buf
+ int bw_conv_error; // set for conversion error
+ linenr_T bw_conv_error_lnum; // first line with error or zero
+ linenr_T bw_start_lnum; // line number at start of buffer
+ iconv_t bw_iconv_fd; // descriptor for iconv() or -1
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "bufwrite.c.generated.h"
+#endif
+
+/// Convert a Unicode character to bytes.
+///
+/// @param c character to convert
+/// @param[in,out] pp pointer to store the result at
+/// @param flags FIO_ flags that specify which encoding to use
+///
+/// @return true for an error, false when it's OK.
+static bool ucs2bytes(unsigned c, char **pp, int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ uint8_t *p = (uint8_t *)(*pp);
+ bool error = false;
+
+ if (flags & FIO_UCS4) {
+ if (flags & FIO_ENDIAN_L) {
+ *p++ = (uint8_t)c;
+ *p++ = (uint8_t)(c >> 8);
+ *p++ = (uint8_t)(c >> 16);
+ *p++ = (uint8_t)(c >> 24);
+ } else {
+ *p++ = (uint8_t)(c >> 24);
+ *p++ = (uint8_t)(c >> 16);
+ *p++ = (uint8_t)(c >> 8);
+ *p++ = (uint8_t)c;
+ }
+ } else if (flags & (FIO_UCS2 | FIO_UTF16)) {
+ if (c >= 0x10000) {
+ if (flags & FIO_UTF16) {
+ // Make two words, ten bits of the character in each. First
+ // word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff
+ c -= 0x10000;
+ if (c >= 0x100000) {
+ error = true;
+ }
+ int cc = (int)(((c >> 10) & 0x3ff) + 0xd800);
+ if (flags & FIO_ENDIAN_L) {
+ *p++ = (uint8_t)cc;
+ *p++ = (uint8_t)(cc >> 8);
+ } else {
+ *p++ = (uint8_t)(cc >> 8);
+ *p++ = (uint8_t)cc;
+ }
+ c = (c & 0x3ff) + 0xdc00;
+ } else {
+ error = true;
+ }
+ }
+ if (flags & FIO_ENDIAN_L) {
+ *p++ = (uint8_t)c;
+ *p++ = (uint8_t)(c >> 8);
+ } else {
+ *p++ = (uint8_t)(c >> 8);
+ *p++ = (uint8_t)c;
+ }
+ } else { // Latin1
+ if (c >= 0x100) {
+ error = true;
+ *p++ = 0xBF;
+ } else {
+ *p++ = (uint8_t)c;
+ }
+ }
+
+ *pp = (char *)p;
+ return error;
+}
+
+static int buf_write_convert_with_iconv(struct bw_info *ip, char **bufp, int *lenp)
+{
+ const char *from;
+ size_t fromlen;
+ size_t tolen;
+
+ int len = *lenp;
+
+ // Convert with iconv().
+ if (ip->bw_restlen > 0) {
+ // Need to concatenate the remainder of the previous call and
+ // the bytes of the current call. Use the end of the
+ // conversion buffer for this.
+ fromlen = (size_t)len + (size_t)ip->bw_restlen;
+ char *fp = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen;
+ memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen);
+ memmove(fp + ip->bw_restlen, *bufp, (size_t)len);
+ from = fp;
+ tolen = ip->bw_conv_buflen - fromlen;
+ } else {
+ from = *bufp;
+ fromlen = (size_t)len;
+ tolen = ip->bw_conv_buflen;
+ }
+ char *to = ip->bw_conv_buf;
+
+ if (ip->bw_first) {
+ size_t save_len = tolen;
+
+ // output the initial shift state sequence
+ (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen);
+
+ // There is a bug in iconv() on Linux (which appears to be
+ // wide-spread) which sets "to" to NULL and messes up "tolen".
+ if (to == NULL) {
+ to = ip->bw_conv_buf;
+ tolen = save_len;
+ }
+ ip->bw_first = false;
+ }
+
+ // If iconv() has an error or there is not enough room, fail.
+ if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen)
+ == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL)
+ || fromlen > CONV_RESTLEN) {
+ ip->bw_conv_error = true;
+ return FAIL;
+ }
+
+ // copy remainder to ip->bw_rest[] to be used for the next call.
+ if (fromlen > 0) {
+ memmove(ip->bw_rest, (void *)from, fromlen);
+ }
+ ip->bw_restlen = (int)fromlen;
+
+ *bufp = ip->bw_conv_buf;
+ *lenp = (int)(to - ip->bw_conv_buf);
+
+ return OK;
+}
+
+static int buf_write_convert(struct bw_info *ip, char **bufp, int *lenp)
+{
+ int flags = ip->bw_flags; // extra flags
+
+ if (flags & FIO_UTF8) {
+ // Convert latin1 in the buffer to UTF-8 in the file.
+ char *p = ip->bw_conv_buf; // translate to buffer
+ for (int wlen = 0; wlen < *lenp; wlen++) {
+ p += utf_char2bytes((uint8_t)(*bufp)[wlen], p);
+ }
+ *bufp = ip->bw_conv_buf;
+ *lenp = (int)(p - ip->bw_conv_buf);
+ } else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) {
+ unsigned c;
+ int n = 0;
+ // Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or
+ // Latin1 chars in the file.
+ // translate in-place (can only get shorter) or to buffer
+ char *p = flags & FIO_LATIN1 ? *bufp : ip->bw_conv_buf;
+ for (int wlen = 0; wlen < *lenp; wlen += n) {
+ if (wlen == 0 && ip->bw_restlen != 0) {
+ // Use remainder of previous call. Append the start of
+ // buf[] to get a full sequence. Might still be too
+ // short!
+ int l = MIN(*lenp, CONV_RESTLEN - ip->bw_restlen);
+ memmove(ip->bw_rest + ip->bw_restlen, *bufp, (size_t)l);
+ n = utf_ptr2len_len((char *)ip->bw_rest, ip->bw_restlen + l);
+ if (n > ip->bw_restlen + *lenp) {
+ // We have an incomplete byte sequence at the end to
+ // be written. We can't convert it without the
+ // remaining bytes. Keep them for the next call.
+ if (ip->bw_restlen + *lenp > CONV_RESTLEN) {
+ return FAIL;
+ }
+ ip->bw_restlen += *lenp;
+ break;
+ }
+ if (n > 1) {
+ c = (unsigned)utf_ptr2char((char *)ip->bw_rest);
+ } else {
+ c = ip->bw_rest[0];
+ }
+ if (n >= ip->bw_restlen) {
+ n -= ip->bw_restlen;
+ ip->bw_restlen = 0;
+ } else {
+ ip->bw_restlen -= n;
+ memmove(ip->bw_rest, ip->bw_rest + n,
+ (size_t)ip->bw_restlen);
+ n = 0;
+ }
+ } else {
+ n = utf_ptr2len_len(*bufp + wlen, *lenp - wlen);
+ if (n > *lenp - wlen) {
+ // We have an incomplete byte sequence at the end to
+ // be written. We can't convert it without the
+ // remaining bytes. Keep them for the next call.
+ if (*lenp - wlen > CONV_RESTLEN) {
+ return FAIL;
+ }
+ ip->bw_restlen = *lenp - wlen;
+ memmove(ip->bw_rest, *bufp + wlen,
+ (size_t)ip->bw_restlen);
+ break;
+ }
+ if (n > 1) {
+ c = (unsigned)utf_ptr2char(*bufp + wlen);
+ } else {
+ c = (uint8_t)(*bufp)[wlen];
+ }
+ }
+
+ if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) {
+ ip->bw_conv_error = true;
+ ip->bw_conv_error_lnum = ip->bw_start_lnum;
+ }
+ if (c == NL) {
+ ip->bw_start_lnum++;
+ }
+ }
+ if (flags & FIO_LATIN1) {
+ *lenp = (int)(p - *bufp);
+ } else {
+ *bufp = ip->bw_conv_buf;
+ *lenp = (int)(p - ip->bw_conv_buf);
+ }
+ }
+
+ if (ip->bw_iconv_fd != (iconv_t)-1) {
+ if (buf_write_convert_with_iconv(ip, bufp, lenp) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+/// Call write() to write a number of bytes to the file.
+/// Handles 'encoding' conversion.
+///
+/// @return FAIL for failure, OK otherwise.
+static int buf_write_bytes(struct bw_info *ip)
+{
+ char *buf = ip->bw_buf; // data to write
+ int len = ip->bw_len; // length of data
+ int flags = ip->bw_flags; // extra flags
+
+ // Skip conversion when writing the BOM.
+ if (!(flags & FIO_NOCONVERT)) {
+ if (buf_write_convert(ip, &buf, &len) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ if (ip->bw_fd < 0) {
+ // Only checking conversion, which is OK if we get here.
+ return OK;
+ }
+ int wlen = write_eintr(ip->bw_fd, buf, (size_t)len);
+ return (wlen < len) ? FAIL : OK;
+}
+
+/// Check modification time of file, before writing to it.
+/// The size isn't checked, because using a tool like "gzip" takes care of
+/// using the same timestamp but can't set the size.
+static int check_mtime(buf_T *buf, FileInfo *file_info)
+{
+ if (buf->b_mtime_read != 0
+ && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) {
+ msg_scroll = true; // Don't overwrite messages here.
+ msg_silent = 0; // Must give this prompt.
+ // Don't use emsg() here, don't want to flush the buffers.
+ msg(_("WARNING: The file has been changed since reading it!!!"), HL_ATTR(HLF_E));
+ if (ask_yesno(_("Do you really want to write to it"), true) == 'n') {
+ return FAIL;
+ }
+ msg_scroll = false; // Always overwrite the file message now.
+ }
+ return OK;
+}
+
+/// Generate a BOM in "buf[4]" for encoding "name".
+///
+/// @return the length of the BOM (zero when no BOM).
+static int make_bom(char *buf_in, char *name)
+{
+ uint8_t *buf = (uint8_t *)buf_in;
+ int flags = get_fio_flags(name);
+
+ // Can't put a BOM in a non-Unicode file.
+ if (flags == FIO_LATIN1 || flags == 0) {
+ return 0;
+ }
+
+ if (flags == FIO_UTF8) { // UTF-8
+ buf[0] = 0xef;
+ buf[1] = 0xbb;
+ buf[2] = 0xbf;
+ return 3;
+ }
+ char *p = (char *)buf;
+ (void)ucs2bytes(0xfeff, &p, flags);
+ return (int)((uint8_t *)p - buf);
+}
+
+static int buf_write_do_autocmds(buf_T *buf, char **fnamep, char **sfnamep, char **ffnamep,
+ linenr_T start, linenr_T *endp, exarg_T *eap, bool append,
+ bool filtering, bool reset_changed, bool overwriting, bool whole,
+ const pos_T orig_start, const pos_T orig_end)
+{
+ linenr_T old_line_count = buf->b_ml.ml_line_count;
+ int msg_save = msg_scroll;
+
+ aco_save_T aco;
+ bool did_cmd = false;
+ bool nofile_err = false;
+ bool empty_memline = buf->b_ml.ml_mfp == NULL;
+ bufref_T bufref;
+
+ char *sfname = *sfnamep;
+
+ // Apply PRE autocommands.
+ // Set curbuf to the buffer to be written.
+ // Careful: The autocommands may call buf_write() recursively!
+ bool buf_ffname = *ffnamep == buf->b_ffname;
+ bool buf_sfname = sfname == buf->b_sfname;
+ bool buf_fname_f = *fnamep == buf->b_ffname;
+ bool buf_fname_s = *fnamep == buf->b_sfname;
+
+ // Set curwin/curbuf to buf and save a few things.
+ aucmd_prepbuf(&aco, buf);
+ set_bufref(&bufref, buf);
+
+ if (append) {
+ did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, sfname, sfname, false, curbuf, eap);
+ if (!did_cmd) {
+ if (overwriting && bt_nofilename(curbuf)) {
+ nofile_err = true;
+ } else {
+ apply_autocmds_exarg(EVENT_FILEAPPENDPRE,
+ sfname, sfname, false, curbuf, eap);
+ }
+ }
+ } else if (filtering) {
+ apply_autocmds_exarg(EVENT_FILTERWRITEPRE,
+ NULL, sfname, false, curbuf, eap);
+ } else if (reset_changed && whole) {
+ bool was_changed = curbufIsChanged();
+
+ did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD, sfname, sfname, false, curbuf, eap);
+ if (did_cmd) {
+ if (was_changed && !curbufIsChanged()) {
+ // Written everything correctly and BufWriteCmd has reset
+ // 'modified': Correct the undo information so that an
+ // undo now sets 'modified'.
+ u_unchanged(curbuf);
+ u_update_save_nr(curbuf);
+ }
+ } else {
+ if (overwriting && bt_nofilename(curbuf)) {
+ nofile_err = true;
+ } else {
+ apply_autocmds_exarg(EVENT_BUFWRITEPRE,
+ sfname, sfname, false, curbuf, eap);
+ }
+ }
+ } else {
+ did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD, sfname, sfname, false, curbuf, eap);
+ if (!did_cmd) {
+ if (overwriting && bt_nofilename(curbuf)) {
+ nofile_err = true;
+ } else {
+ apply_autocmds_exarg(EVENT_FILEWRITEPRE,
+ sfname, sfname, false, curbuf, eap);
+ }
+ }
+ }
+
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+
+ // In three situations we return here and don't write the file:
+ // 1. the autocommands deleted or unloaded the buffer.
+ // 2. The autocommands abort script processing.
+ // 3. If one of the "Cmd" autocommands was executed.
+ if (!bufref_valid(&bufref)) {
+ buf = NULL;
+ }
+ if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
+ || did_cmd || nofile_err
+ || aborting()) {
+ if (buf != NULL && (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
+
+ no_wait_return--;
+ msg_scroll = msg_save;
+ if (nofile_err) {
+ semsg(_(e_no_matching_autocommands_for_buftype_str_buffer), curbuf->b_p_bt);
+ }
+
+ if (nofile_err
+ || aborting()) {
+ // An aborting error, interrupt or exception in the
+ // autocommands.
+ return FAIL;
+ }
+ if (did_cmd) {
+ if (buf == NULL) {
+ // The buffer was deleted. We assume it was written
+ // (can't retry anyway).
+ return OK;
+ }
+ if (overwriting) {
+ // Assume the buffer was written, update the timestamp.
+ ml_timestamp(buf);
+ if (append) {
+ buf->b_flags &= ~BF_NEW;
+ } else {
+ buf->b_flags &= ~BF_WRITE_MASK;
+ }
+ }
+ if (reset_changed && buf->b_changed && !append
+ && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
+ // Buffer still changed, the autocommands didn't work properly.
+ return FAIL;
+ }
+ return OK;
+ }
+ if (!aborting()) {
+ emsg(_("E203: Autocommands deleted or unloaded buffer to be written"));
+ }
+ return FAIL;
+ }
+
+ // The autocommands may have changed the number of lines in the file.
+ // When writing the whole file, adjust the end.
+ // When writing part of the file, assume that the autocommands only
+ // changed the number of lines that are to be written (tricky!).
+ if (buf->b_ml.ml_line_count != old_line_count) {
+ if (whole) { // write all
+ *endp = buf->b_ml.ml_line_count;
+ } else if (buf->b_ml.ml_line_count > old_line_count) { // more lines
+ *endp += buf->b_ml.ml_line_count - old_line_count;
+ } else { // less lines
+ *endp -= old_line_count - buf->b_ml.ml_line_count;
+ if (*endp < start) {
+ no_wait_return--;
+ msg_scroll = msg_save;
+ emsg(_("E204: Autocommand changed number of lines in unexpected way"));
+ return FAIL;
+ }
+ }
+ }
+
+ // The autocommands may have changed the name of the buffer, which may
+ // be kept in fname, ffname and sfname.
+ if (buf_ffname) {
+ *ffnamep = buf->b_ffname;
+ }
+ if (buf_sfname) {
+ *sfnamep = buf->b_sfname;
+ }
+ if (buf_fname_f) {
+ *fnamep = buf->b_ffname;
+ }
+ if (buf_fname_s) {
+ *fnamep = buf->b_sfname;
+ }
+ return NOTDONE;
+}
+
+static void buf_write_do_post_autocmds(buf_T *buf, char *fname, exarg_T *eap, bool append,
+ bool filtering, bool reset_changed, bool whole)
+{
+ aco_save_T aco;
+
+ curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read
+
+ // Apply POST autocommands.
+ // Careful: The autocommands may call buf_write() recursively!
+ aucmd_prepbuf(&aco, buf);
+
+ if (append) {
+ apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname,
+ false, curbuf, eap);
+ } else if (filtering) {
+ apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname,
+ false, curbuf, eap);
+ } else if (reset_changed && whole) {
+ apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname,
+ false, curbuf, eap);
+ } else {
+ apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname,
+ false, curbuf, eap);
+ }
+
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+}
+
+static inline Error_T set_err_num(const char *num, const char *msg)
+{
+ return (Error_T){ .num = num, .msg = (char *)msg, .arg = 0 };
+}
+
+static inline Error_T set_err(const char *msg)
+{
+ return (Error_T){ .num = NULL, .msg = (char *)msg, .arg = 0 };
+}
+
+static inline Error_T set_err_arg(const char *msg, int arg)
+{
+ return (Error_T){ .num = NULL, .msg = (char *)msg, .arg = arg };
+}
+
+static void emit_err(Error_T *e)
+{
+ if (e->num != NULL) {
+ if (e->arg != 0) {
+ semsg("%s: %s%s: %s", e->num, IObuff, e->msg, os_strerror(e->arg));
+ } else {
+ semsg("%s: %s%s", e->num, IObuff, e->msg);
+ }
+ } else if (e->arg != 0) {
+ semsg(e->msg, os_strerror(e->arg));
+ } else {
+ emsg(e->msg);
+ }
+ if (e->alloc) {
+ xfree(e->msg);
+ }
+}
+
+#if defined(UNIX)
+
+static int get_fileinfo_os(char *fname, FileInfo *file_info_old, bool overwriting, int *perm,
+ bool *device, bool *newfile, Error_T *err)
+{
+ *perm = -1;
+ if (!os_fileinfo(fname, file_info_old)) {
+ *newfile = true;
+ } else {
+ *perm = (int)file_info_old->stat.st_mode;
+ if (!S_ISREG(file_info_old->stat.st_mode)) { // not a file
+ if (S_ISDIR(file_info_old->stat.st_mode)) {
+ *err = set_err_num("E502", _("is a directory"));
+ return FAIL;
+ }
+ if (os_nodetype(fname) != NODE_WRITABLE) {
+ *err = set_err_num("E503", _("is not a file or writable device"));
+ return FAIL;
+ }
+ // It's a device of some kind (or a fifo) which we can write to
+ // but for which we can't make a backup.
+ *device = true;
+ *newfile = true;
+ *perm = -1;
+ }
+ }
+ return OK;
+}
+
+#else
+
+static int get_fileinfo_os(char *fname, FileInfo *file_info_old, bool overwriting, int *perm,
+ bool *device, bool *newfile, Error_T *err)
+{
+ // Check for a writable device name.
+ char nodetype = fname == NULL ? NODE_OTHER : (char)os_nodetype(fname);
+ if (nodetype == NODE_OTHER) {
+ *err = set_err_num("E503", _("is not a file or writable device"));
+ return FAIL;
+ }
+ if (nodetype == NODE_WRITABLE) {
+ *device = true;
+ *newfile = true;
+ *perm = -1;
+ } else {
+ *perm = os_getperm(fname);
+ if (*perm < 0) {
+ *newfile = true;
+ } else if (os_isdir(fname)) {
+ *err = set_err_num("E502", _("is a directory"));
+ return FAIL;
+ }
+ if (overwriting) {
+ os_fileinfo(fname, file_info_old);
+ }
+ }
+ return OK;
+}
+
+#endif
+
+/// @param buf
+/// @param fname File name
+/// @param overwriting
+/// @param forceit
+/// @param[out] file_info_old
+/// @param[out] perm
+/// @param[out] device
+/// @param[out] newfile
+/// @param[out] readonly
+static int get_fileinfo(buf_T *buf, char *fname, bool overwriting, bool forceit,
+ FileInfo *file_info_old, int *perm, bool *device, bool *newfile,
+ bool *readonly, Error_T *err)
+{
+ if (get_fileinfo_os(fname, file_info_old, overwriting, perm, device, newfile, err) == FAIL) {
+ return FAIL;
+ }
+
+ *readonly = false; // overwritten file is read-only
+
+ if (!*device && !*newfile) {
+ // Check if the file is really writable (when renaming the file to
+ // make a backup we won't discover it later).
+ *readonly = !os_file_is_writable(fname);
+
+ if (!forceit && *readonly) {
+ if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
+ *err = set_err_num("E504", _(err_readonly));
+ } else {
+ *err = set_err_num("E505", _("is read-only (add ! to override)"));
+ }
+ return FAIL;
+ }
+
+ // If 'forceit' is false, check if the timestamp hasn't changed since reading the file.
+ if (overwriting && !forceit) {
+ int retval = check_mtime(buf, file_info_old);
+ if (retval == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+ return OK;
+}
+
+static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_old, vim_acl_T acl,
+ int perm, unsigned bkc, bool file_readonly, bool forceit,
+ int *backup_copyp, char **backupp, Error_T *err)
+{
+ FileInfo file_info;
+ const bool no_prepend_dot = false;
+
+ if ((bkc & BKC_YES) || append) { // "yes"
+ *backup_copyp = true;
+ } else if ((bkc & BKC_AUTO)) { // "auto"
+ // Don't rename the file when:
+ // - it's a hard link
+ // - it's a symbolic link
+ // - we don't have write permission in the directory
+ if (os_fileinfo_hardlinks(file_info_old) > 1
+ || !os_fileinfo_link(fname, &file_info)
+ || !os_fileinfo_id_equal(&file_info, file_info_old)) {
+ *backup_copyp = true;
+ } else {
+ // Check if we can create a file and set the owner/group to
+ // the ones from the original file.
+ // First find a file name that doesn't exist yet (use some
+ // arbitrary numbers).
+ xstrlcpy(IObuff, fname, IOSIZE);
+ for (int i = 4913;; i += 123) {
+ char *tail = path_tail(IObuff);
+ size_t size = (size_t)(tail - IObuff);
+ snprintf(tail, IOSIZE - size, "%d", i);
+ if (!os_fileinfo_link(IObuff, &file_info)) {
+ break;
+ }
+ }
+ int fd = os_open(IObuff,
+ O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
+ if (fd < 0) { // can't write in directory
+ *backup_copyp = true;
+ } else {
+#ifdef UNIX
+ os_fchown(fd, (uv_uid_t)file_info_old->stat.st_uid, (uv_gid_t)file_info_old->stat.st_gid);
+ if (!os_fileinfo(IObuff, &file_info)
+ || file_info.stat.st_uid != file_info_old->stat.st_uid
+ || file_info.stat.st_gid != file_info_old->stat.st_gid
+ || (int)file_info.stat.st_mode != perm) {
+ *backup_copyp = true;
+ }
+#endif
+ // Close the file before removing it, on MS-Windows we
+ // can't delete an open file.
+ close(fd);
+ os_remove(IObuff);
+ }
+ }
+ }
+
+ // Break symlinks and/or hardlinks if we've been asked to.
+ if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) {
+#ifdef UNIX
+ bool file_info_link_ok = os_fileinfo_link(fname, &file_info);
+
+ // Symlinks.
+ if ((bkc & BKC_BREAKSYMLINK)
+ && file_info_link_ok
+ && !os_fileinfo_id_equal(&file_info, file_info_old)) {
+ *backup_copyp = false;
+ }
+
+ // Hardlinks.
+ if ((bkc & BKC_BREAKHARDLINK)
+ && os_fileinfo_hardlinks(file_info_old) > 1
+ && (!file_info_link_ok
+ || os_fileinfo_id_equal(&file_info, file_info_old))) {
+ *backup_copyp = false;
+ }
+#endif
+ }
+
+ // make sure we have a valid backup extension to use
+ char *backup_ext = *p_bex == NUL ? ".bak" : p_bex;
+
+ if (*backup_copyp) {
+ int some_error = false;
+
+ // Try to make the backup in each directory in the 'bdir' option.
+ //
+ // Unix semantics has it, that we may have a writable file,
+ // that cannot be recreated with a simple open(..., O_CREAT, ) e.g:
+ // - the directory is not writable,
+ // - the file may be a symbolic link,
+ // - the file may belong to another user/group, etc.
+ //
+ // For these reasons, the existing writable file must be truncated
+ // and reused. Creation of a backup COPY will be attempted.
+ char *dirp = p_bdir;
+ while (*dirp) {
+ // Isolate one directory name, using an entry in 'bdir'.
+ size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ char *p = IObuff + dir_len;
+ bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
+ if (trailing_pathseps) {
+ IObuff[dir_len - 2] = NUL;
+ }
+ if (*dirp == NUL && !os_isdir(IObuff)) {
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
+ semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ }
+ }
+ if (trailing_pathseps) {
+ // Ends with '//', Use Full path
+ if ((p = make_percent_swname(IObuff, fname))
+ != NULL) {
+ *backupp = modname(p, backup_ext, no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ char *rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ some_error = true; // out of memory
+ goto nobackup;
+ }
+
+ FileInfo file_info_new;
+ {
+ //
+ // Make the backup file name.
+ //
+ if (*backupp == NULL) {
+ *backupp = modname(rootname, backup_ext, no_prepend_dot);
+ }
+
+ if (*backupp == NULL) {
+ xfree(rootname);
+ some_error = true; // out of memory
+ goto nobackup;
+ }
+
+ // Check if backup file already exists.
+ if (os_fileinfo(*backupp, &file_info_new)) {
+ if (os_fileinfo_id_equal(&file_info_new, file_info_old)) {
+ //
+ // Backup file is same as original file.
+ // May happen when modname() gave the same file back (e.g. silly
+ // link). If we don't check here, we either ruin the file when
+ // copying or erase it after writing.
+ //
+ XFREE_CLEAR(*backupp); // no backup file to delete
+ } else if (!p_bk) {
+ // We are not going to keep the backup file, so don't
+ // delete an existing one, and try to use another name instead.
+ // Change one character, just before the extension.
+ //
+ char *wp = *backupp + strlen(*backupp) - 1 - strlen(backup_ext);
+ if (wp < *backupp) { // empty file name ???
+ wp = *backupp;
+ }
+ *wp = 'z';
+ while (*wp > 'a' && os_fileinfo(*backupp, &file_info_new)) {
+ (*wp)--;
+ }
+ // They all exist??? Must be something wrong.
+ if (*wp == 'a') {
+ XFREE_CLEAR(*backupp);
+ }
+ }
+ }
+ }
+ xfree(rootname);
+
+ // Try to create the backup file
+ if (*backupp != NULL) {
+ // remove old backup, if present
+ os_remove(*backupp);
+
+ // set file protection same as original file, but
+ // strip s-bit.
+ (void)os_setperm(*backupp, perm & 0777);
+
+#ifdef UNIX
+ //
+ // Try to set the group of the backup same as the original file. If
+ // this fails, set the protection bits for the group same as the
+ // protection bits for others.
+ //
+ if (file_info_new.stat.st_gid != file_info_old->stat.st_gid
+ && os_chown(*backupp, (uv_uid_t)-1, (uv_gid_t)file_info_old->stat.st_gid) != 0) {
+ os_setperm(*backupp, (perm & 0707) | ((perm & 07) << 3));
+ }
+# ifdef HAVE_XATTR
+ os_copy_xattr(fname, *backupp);
+# endif
+#endif
+
+ // copy the file
+ if (os_copy(fname, *backupp, UV_FS_COPYFILE_FICLONE) != 0) {
+ *err = set_err(_("E509: Cannot create backup file (add ! to override)"));
+ XFREE_CLEAR(*backupp);
+ *backupp = NULL;
+ continue;
+ }
+
+#ifdef UNIX
+ os_file_settime(*backupp,
+ (double)file_info_old->stat.st_atim.tv_sec,
+ (double)file_info_old->stat.st_mtim.tv_sec);
+#endif
+ os_set_acl(*backupp, acl);
+#ifdef HAVE_XATTR
+ os_copy_xattr(fname, *backupp);
+#endif
+ *err = set_err(NULL);
+ break;
+ }
+ }
+
+nobackup:
+ if (*backupp == NULL && err->msg == NULL) {
+ *err = set_err(_("E509: Cannot create backup file (add ! to override)"));
+ }
+ // Ignore errors when forceit is true.
+ if ((some_error || err->msg != NULL) && !forceit) {
+ return FAIL;
+ }
+ *err = set_err(NULL);
+ } else {
+ // Make a backup by renaming the original file.
+
+ // If 'cpoptions' includes the "W" flag, we don't want to
+ // overwrite a read-only file. But rename may be possible
+ // anyway, thus we need an extra check here.
+ if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
+ *err = set_err_num("E504", _(err_readonly));
+ return FAIL;
+ }
+
+ // Form the backup file name - change path/fo.o.h to
+ // path/fo.o.h.bak Try all directories in 'backupdir', first one
+ // that works is used.
+ char *dirp = p_bdir;
+ while (*dirp) {
+ // Isolate one directory name and make the backup file name.
+ size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ char *p = IObuff + dir_len;
+ bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
+ if (trailing_pathseps) {
+ IObuff[dir_len - 2] = NUL;
+ }
+ if (*dirp == NUL && !os_isdir(IObuff)) {
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
+ semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ }
+ }
+ if (trailing_pathseps) {
+ // path ends with '//', use full path
+ if ((p = make_percent_swname(IObuff, fname))
+ != NULL) {
+ *backupp = modname(p, backup_ext, no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ if (*backupp == NULL) {
+ char *rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ *backupp = NULL;
+ } else {
+ *backupp = modname(rootname, backup_ext, no_prepend_dot);
+ xfree(rootname);
+ }
+ }
+
+ if (*backupp != NULL) {
+ // If we are not going to keep the backup file, don't
+ // delete an existing one, try to use another name.
+ // Change one character, just before the extension.
+ if (!p_bk && os_path_exists(*backupp)) {
+ p = *backupp + strlen(*backupp) - 1 - strlen(backup_ext);
+ if (p < *backupp) { // empty file name ???
+ p = *backupp;
+ }
+ *p = 'z';
+ while (*p > 'a' && os_path_exists(*backupp)) {
+ (*p)--;
+ }
+ // They all exist??? Must be something wrong!
+ if (*p == 'a') {
+ XFREE_CLEAR(*backupp);
+ }
+ }
+ }
+ if (*backupp != NULL) {
+ // Delete any existing backup and move the current version
+ // to the backup. For safety, we don't remove the backup
+ // until the write has finished successfully. And if the
+ // 'backup' option is set, leave it around.
+
+ // If the renaming of the original file to the backup file
+ // works, quit here.
+ ///
+ if (vim_rename(fname, *backupp) == 0) {
+ break;
+ }
+
+ XFREE_CLEAR(*backupp); // don't do the rename below
+ }
+ }
+ if (*backupp == NULL && !forceit) {
+ *err = set_err(_("E510: Can't make backup file (add ! to override)"));
+ return FAIL;
+ }
+ }
+ return OK;
+}
+
+/// buf_write() - write to file "fname" lines "start" through "end"
+///
+/// We do our own buffering here because fwrite() is so slow.
+///
+/// If "forceit" is true, we don't care for errors when attempting backups.
+/// In case of an error everything possible is done to restore the original
+/// file. But when "forceit" is true, we risk losing it.
+///
+/// When "reset_changed" is true and "append" == false and "start" == 1 and
+/// "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed.
+///
+/// This function must NOT use NameBuff (because it's called by autowrite()).
+///
+///
+/// @param eap for forced 'ff' and 'fenc', can be NULL!
+/// @param append append to the file
+///
+/// @return FAIL for failure, OK otherwise
+int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T end, exarg_T *eap,
+ int append, int forceit, int reset_changed, int filtering)
+{
+ int retval = OK;
+ int msg_save = msg_scroll;
+ int prev_got_int = got_int;
+ // writing everything
+ int whole = (start == 1 && end == buf->b_ml.ml_line_count);
+ int write_undo_file = false;
+ context_sha256_T sha_ctx;
+ unsigned bkc = get_bkc_value(buf);
+
+ if (fname == NULL || *fname == NUL) { // safety check
+ return FAIL;
+ }
+ if (buf->b_ml.ml_mfp == NULL) {
+ // This can happen during startup when there is a stray "w" in the
+ // vimrc file.
+ emsg(_(e_empty_buffer));
+ return FAIL;
+ }
+
+ // Disallow writing in secure mode.
+ if (check_secure()) {
+ return FAIL;
+ }
+
+ // Avoid a crash for a long name.
+ if (strlen(fname) >= MAXPATHL) {
+ emsg(_(e_longname));
+ return FAIL;
+ }
+
+ // must init bw_conv_buf and bw_iconv_fd before jumping to "fail"
+ struct bw_info write_info; // info for buf_write_bytes()
+ write_info.bw_conv_buf = NULL;
+ write_info.bw_conv_error = false;
+ write_info.bw_conv_error_lnum = 0;
+ write_info.bw_restlen = 0;
+ write_info.bw_iconv_fd = (iconv_t)-1;
+
+ // After writing a file changedtick changes but we don't want to display
+ // the line.
+ ex_no_reprint = true;
+
+ // If there is no file name yet, use the one for the written file.
+ // BF_NOTEDITED is set to reflect this (in case the write fails).
+ // Don't do this when the write is for a filter command.
+ // Don't do this when appending.
+ // Only do this when 'cpoptions' contains the 'F' flag.
+ if (buf->b_ffname == NULL
+ && reset_changed
+ && whole
+ && buf == curbuf
+ && !bt_nofilename(buf)
+ && !filtering
+ && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL)
+ && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) {
+ if (set_rw_fname(fname, sfname) == FAIL) {
+ return FAIL;
+ }
+ buf = curbuf; // just in case autocmds made "buf" invalid
+ }
+
+ if (sfname == NULL) {
+ sfname = fname;
+ }
+
+ // For Unix: Use the short file name whenever possible.
+ // Avoids problems with networks and when directory names are changed.
+ // Don't do this for Windows, a "cd" in a sub-shell may have moved us to
+ // another directory, which we don't detect.
+ char *ffname = fname; // remember full fname
+#ifdef UNIX
+ fname = sfname;
+#endif
+
+// true if writing over original
+ int overwriting = buf->b_ffname != NULL && path_fnamecmp(ffname, buf->b_ffname) == 0;
+
+ no_wait_return++; // don't wait for return yet
+
+ const pos_T orig_start = buf->b_op_start;
+ const pos_T orig_end = buf->b_op_end;
+
+ // Set '[ and '] marks to the lines to be written.
+ buf->b_op_start.lnum = start;
+ buf->b_op_start.col = 0;
+ buf->b_op_end.lnum = end;
+ buf->b_op_end.col = 0;
+
+ int res = buf_write_do_autocmds(buf, &fname, &sfname, &ffname, start, &end, eap, append,
+ filtering, reset_changed, overwriting, whole, orig_start,
+ orig_end);
+ if (res != NOTDONE) {
+ return res;
+ }
+
+ if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
+
+ if (shortmess(SHM_OVER) && !exiting) {
+ msg_scroll = false; // overwrite previous file message
+ } else {
+ msg_scroll = true; // don't overwrite previous file message
+ }
+ if (!filtering) {
+ // show that we are busy
+#ifndef UNIX
+ filemess(buf, sfname, "", 0);
+#else
+ filemess(buf, fname, "", 0);
+#endif
+ }
+ msg_scroll = false; // always overwrite the file message now
+
+ char *buffer = verbose_try_malloc(WRITEBUFSIZE);
+ int bufsize;
+ char smallbuf[SMALLBUFSIZE];
+ // can't allocate big buffer, use small one (to be able to write when out of
+ // memory)
+ if (buffer == NULL) {
+ buffer = smallbuf;
+ bufsize = SMALLBUFSIZE;
+ } else {
+ bufsize = WRITEBUFSIZE;
+ }
+
+ Error_T err = { 0 };
+ int perm; // file permissions
+ bool newfile = false; // true if file doesn't exist yet
+ bool device = false; // writing to a device
+ bool file_readonly = false; // overwritten file is read-only
+ char *backup = NULL;
+ char *fenc_tofree = NULL; // allocated "fenc"
+
+ // Get information about original file (if there is one).
+ FileInfo file_info_old;
+
+ vim_acl_T acl = NULL; // ACL copied from original file to
+ // backup or new file
+
+ if (get_fileinfo(buf, fname, overwriting, forceit, &file_info_old, &perm, &device, &newfile,
+ &file_readonly, &err) == FAIL) {
+ goto fail;
+ }
+
+ // For systems that support ACL: get the ACL from the original file.
+ if (!newfile) {
+ acl = os_get_acl(fname);
+ }
+
+ // If 'backupskip' is not empty, don't make a backup for some files.
+ bool dobackup = (p_wb || p_bk || *p_pm != NUL);
+ if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) {
+ dobackup = false;
+ }
+
+ int backup_copy = false; // copy the original file?
+
+ // Save the value of got_int and reset it. We don't want a previous
+ // interruption cancel writing, only hitting CTRL-C while writing should
+ // abort it.
+ prev_got_int = got_int;
+ got_int = false;
+
+ // Mark the buffer as 'being saved' to prevent changed buffer warnings
+ buf->b_saving = true;
+
+ // If we are not appending or filtering, the file exists, and the
+ // 'writebackup', 'backup' or 'patchmode' option is set, need a backup.
+ // When 'patchmode' is set also make a backup when appending.
+ //
+ // Do not make any backup, if 'writebackup' and 'backup' are both switched
+ // off. This helps when editing large files on almost-full disks.
+ if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) {
+ if (buf_write_make_backup(fname, append, &file_info_old, acl, perm, bkc, file_readonly, forceit,
+ &backup_copy, &backup, &err) == FAIL) {
+ retval = FAIL;
+ goto fail;
+ }
+ }
+
+#if defined(UNIX)
+ int made_writable = false; // 'w' bit has been set
+
+ // When using ":w!" and the file was read-only: make it writable
+ if (forceit && perm >= 0 && !(perm & 0200)
+ && file_info_old.stat.st_uid == getuid()
+ && vim_strchr(p_cpo, CPO_FWRITE) == NULL) {
+ perm |= 0200;
+ (void)os_setperm(fname, perm);
+ made_writable = true;
+ }
+#endif
+
+ // When using ":w!" and writing to the current file, 'readonly' makes no
+ // sense, reset it, unless 'Z' appears in 'cpoptions'.
+ if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) {
+ buf->b_p_ro = false;
+ need_maketitle = true; // set window title later
+ status_redraw_all(); // redraw status lines later
+ }
+
+ if (end > buf->b_ml.ml_line_count) {
+ end = buf->b_ml.ml_line_count;
+ }
+ if (buf->b_ml.ml_flags & ML_EMPTY) {
+ start = end + 1;
+ }
+
+ char *wfname = NULL; // name of file to write to
+
+ // If the original file is being overwritten, there is a small chance that
+ // we crash in the middle of writing. Therefore the file is preserved now.
+ // This makes all block numbers positive so that recovery does not need
+ // the original file.
+ // Don't do this if there is a backup file and we are exiting.
+ if (reset_changed && !newfile && overwriting
+ && !(exiting && backup != NULL)) {
+ ml_preserve(buf, false, !!p_fs);
+ if (got_int) {
+ err = set_err(_(e_interr));
+ goto restore_backup;
+ }
+ }
+
+ // Default: write the file directly. May write to a temp file for
+ // multi-byte conversion.
+ wfname = fname;
+
+ char *fenc; // effective 'fileencoding'
+
+ // Check for forced 'fileencoding' from "++opt=val" argument.
+ if (eap != NULL && eap->force_enc != 0) {
+ fenc = eap->cmd + eap->force_enc;
+ fenc = enc_canonize(fenc);
+ fenc_tofree = fenc;
+ } else {
+ fenc = buf->b_p_fenc;
+ }
+
+ // Check if the file needs to be converted.
+ int converted = need_conversion(fenc);
+ int wb_flags = 0;
+
+ // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or
+ // Latin1 to Unicode conversion. This is handled in buf_write_bytes().
+ // Prepare the flags for it and allocate bw_conv_buf when needed.
+ if (converted) {
+ wb_flags = get_fio_flags(fenc);
+ if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) {
+ // Need to allocate a buffer to translate into.
+ if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) {
+ write_info.bw_conv_buflen = (size_t)bufsize * 2;
+ } else { // FIO_UCS4
+ write_info.bw_conv_buflen = (size_t)bufsize * 4;
+ }
+ write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
+ if (!write_info.bw_conv_buf) {
+ end = 0;
+ }
+ }
+ }
+
+ if (converted && wb_flags == 0) {
+ // Use iconv() conversion when conversion is needed and it's not done
+ // internally.
+ write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, "utf-8");
+ if (write_info.bw_iconv_fd != (iconv_t)-1) {
+ // We're going to use iconv(), allocate a buffer to convert in.
+ write_info.bw_conv_buflen = (size_t)bufsize * ICONV_MULT;
+ write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
+ if (!write_info.bw_conv_buf) {
+ end = 0;
+ }
+ write_info.bw_first = true;
+ } else {
+ // When the file needs to be converted with 'charconvert' after
+ // writing, write to a temp file instead and let the conversion
+ // overwrite the original file.
+ if (*p_ccv != NUL) {
+ wfname = vim_tempname();
+ if (wfname == NULL) { // Can't write without a tempfile!
+ err = set_err(_("E214: Can't find temp file for writing"));
+ goto restore_backup;
+ }
+ }
+ }
+ }
+
+ int notconverted = false;
+
+ if (converted && wb_flags == 0
+ && write_info.bw_iconv_fd == (iconv_t)-1
+ && wfname == fname) {
+ if (!forceit) {
+ err = set_err(_("E213: Cannot convert (add ! to write without conversion)"));
+ goto restore_backup;
+ }
+ notconverted = true;
+ }
+
+ int no_eol = false; // no end-of-line written
+ int nchars;
+ linenr_T lnum;
+ int fileformat;
+ int checking_conversion;
+
+ int fd;
+
+ // If conversion is taking place, we may first pretend to write and check
+ // for conversion errors. Then loop again to write for real.
+ // When not doing conversion this writes for real right away.
+ for (checking_conversion = true;; checking_conversion = false) {
+ // There is no need to check conversion when:
+ // - there is no conversion
+ // - we make a backup file, that can be restored in case of conversion
+ // failure.
+ if (!converted || dobackup) {
+ checking_conversion = false;
+ }
+
+ if (checking_conversion) {
+ // Make sure we don't write anything.
+ fd = -1;
+ write_info.bw_fd = fd;
+ } else {
+ // Open the file "wfname" for writing.
+ // We may try to open the file twice: If we can't write to the file
+ // and forceit is true we delete the existing file and try to
+ // create a new one. If this still fails we may have lost the
+ // original file! (this may happen when the user reached his
+ // quotum for number of files).
+ // Appending will fail if the file does not exist and forceit is
+ // false.
+ const int fflags = O_WRONLY | (append
+ ? (forceit ? (O_APPEND | O_CREAT) : O_APPEND)
+ : (O_CREAT | O_TRUNC));
+ const int mode = perm < 0 ? 0666 : (perm & 0777);
+
+ while ((fd = os_open(wfname, fflags, mode)) < 0) {
+ // A forced write will try to create a new file if the old one
+ // is still readonly. This may also happen when the directory
+ // is read-only. In that case the os_remove() will fail.
+ if (err.msg == NULL) {
+#ifdef UNIX
+ FileInfo file_info;
+
+ // Don't delete the file when it's a hard or symbolic link.
+ if ((!newfile && os_fileinfo_hardlinks(&file_info_old) > 1)
+ || (os_fileinfo_link(fname, &file_info)
+ && !os_fileinfo_id_equal(&file_info, &file_info_old))) {
+ err = set_err(_("E166: Can't open linked file for writing"));
+ } else {
+ err = set_err_arg(_("E212: Can't open file for writing: %s"), fd);
+ if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL && perm >= 0) {
+ // we write to the file, thus it should be marked
+ // writable after all
+ if (!(perm & 0200)) {
+ made_writable = true;
+ }
+ perm |= 0200;
+ if (file_info_old.stat.st_uid != getuid()
+ || file_info_old.stat.st_gid != getgid()) {
+ perm &= 0777;
+ }
+ if (!append) { // don't remove when appending
+ os_remove(wfname);
+ }
+ continue;
+ }
+ }
+#else
+ err = set_err_arg(_("E212: Can't open file for writing: %s"), fd);
+ if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL && perm >= 0) {
+ if (!append) { // don't remove when appending
+ os_remove(wfname);
+ }
+ continue;
+ }
+#endif
+ }
+
+restore_backup:
+ {
+ // If we failed to open the file, we don't need a backup. Throw it
+ // away. If we moved or removed the original file try to put the
+ // backup in its place.
+ if (backup != NULL && wfname == fname) {
+ if (backup_copy) {
+ // There is a small chance that we removed the original,
+ // try to move the copy in its place.
+ // This may not work if the vim_rename() fails.
+ // In that case we leave the copy around.
+ // If file does not exist, put the copy in its place
+ if (!os_path_exists(fname)) {
+ vim_rename(backup, fname);
+ }
+ // if original file does exist throw away the copy
+ if (os_path_exists(fname)) {
+ os_remove(backup);
+ }
+ } else {
+ // try to put the original file back
+ vim_rename(backup, fname);
+ }
+ }
+
+ // if original file no longer exists give an extra warning
+ if (!newfile && !os_path_exists(fname)) {
+ end = 0;
+ }
+ }
+
+ if (wfname != fname) {
+ xfree(wfname);
+ }
+ goto fail;
+ }
+ write_info.bw_fd = fd;
+ }
+ err = set_err(NULL);
+
+ write_info.bw_buf = buffer;
+ nchars = 0;
+
+ // use "++bin", "++nobin" or 'binary'
+ int write_bin;
+ if (eap != NULL && eap->force_bin != 0) {
+ write_bin = (eap->force_bin == FORCE_BIN);
+ } else {
+ write_bin = buf->b_p_bin;
+ }
+
+ // Skip the BOM when appending and the file already existed, the BOM
+ // only makes sense at the start of the file.
+ if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) {
+ write_info.bw_len = make_bom(buffer, fenc);
+ if (write_info.bw_len > 0) {
+ // don't convert
+ write_info.bw_flags = FIO_NOCONVERT | wb_flags;
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0;
+ } else {
+ nchars += write_info.bw_len;
+ }
+ }
+ }
+ write_info.bw_start_lnum = start;
+
+ write_undo_file = (buf->b_p_udf && overwriting && !append
+ && !filtering && reset_changed && !checking_conversion);
+ if (write_undo_file) {
+ // Prepare for computing the hash value of the text.
+ sha256_start(&sha_ctx);
+ }
+
+ write_info.bw_len = bufsize;
+ write_info.bw_flags = wb_flags;
+ fileformat = get_fileformat_force(buf, eap);
+ char *s = buffer;
+ int len = 0;
+ for (lnum = start; lnum <= end; lnum++) {
+ // The next while loop is done once for each character written.
+ // Keep it fast!
+ char *ptr = ml_get_buf(buf, lnum) - 1;
+ if (write_undo_file) {
+ sha256_update(&sha_ctx, (uint8_t *)ptr + 1, (uint32_t)(strlen(ptr + 1) + 1));
+ }
+ char c;
+ while ((c = *++ptr) != NUL) {
+ if (c == NL) {
+ *s = NUL; // replace newlines with NULs
+ } else if (c == CAR && fileformat == EOL_MAC) {
+ *s = NL; // Mac: replace CRs with NLs
+ } else {
+ *s = c;
+ }
+ s++;
+ if (++len != bufsize) {
+ continue;
+ }
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // write error: break loop
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+ write_info.bw_start_lnum = lnum;
+ }
+ // write failed or last line has no EOL: stop here
+ if (end == 0
+ || (lnum == end
+ && (write_bin || !buf->b_p_fixeol)
+ && ((write_bin && lnum == buf->b_no_eol_lnum)
+ || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
+ lnum++; // written the line, count it
+ no_eol = true;
+ break;
+ }
+ if (fileformat == EOL_UNIX) {
+ *s++ = NL;
+ } else {
+ *s++ = CAR; // EOL_MAC or EOL_DOS: write CR
+ if (fileformat == EOL_DOS) { // write CR-NL
+ if (++len == bufsize) {
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // write error: break loop
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+ }
+ *s++ = NL;
+ }
+ }
+ if (++len == bufsize) {
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // Write error: break loop.
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+
+ os_breakcheck();
+ if (got_int) {
+ end = 0; // Interrupted, break loop.
+ break;
+ }
+ }
+ }
+ if (len > 0 && end > 0) {
+ write_info.bw_len = len;
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // write error
+ }
+ nchars += len;
+ }
+
+ if (!buf->b_p_fixeol && buf->b_p_eof) {
+ // write trailing CTRL-Z
+ (void)write_eintr(write_info.bw_fd, "\x1a", 1);
+ }
+
+ // Stop when writing done or an error was encountered.
+ if (!checking_conversion || end == 0) {
+ break;
+ }
+
+ // If no error happened until now, writing should be ok, so loop to
+ // really write the buffer.
+ }
+
+ // If we started writing, finish writing. Also when an error was
+ // encountered.
+ if (!checking_conversion) {
+ // On many journalling file systems there is a bug that causes both the
+ // original and the backup file to be lost when halting the system right
+ // after writing the file. That's because only the meta-data is
+ // journalled. Syncing the file slows down the system, but assures it has
+ // been written to disk and we don't lose it.
+ // For a device do try the fsync() but don't complain if it does not work
+ // (could be a pipe).
+ // If the 'fsync' option is false, don't fsync(). Useful for laptops.
+ int error;
+ if (p_fs && (error = os_fsync(fd)) != 0 && !device
+ // fsync not supported on this storage.
+ && error != UV_ENOTSUP) {
+ err = set_err_arg(e_fsync, error);
+ end = 0;
+ }
+
+ if (!backup_copy) {
+#ifdef HAVE_XATTR
+ os_copy_xattr(backup, wfname);
+#endif
+ }
+
+#ifdef UNIX
+ // When creating a new file, set its owner/group to that of the original
+ // file. Get the new device and inode number.
+ if (backup != NULL && !backup_copy) {
+ // don't change the owner when it's already OK, some systems remove
+ // permission or ACL stuff
+ FileInfo file_info;
+ if (!os_fileinfo(wfname, &file_info)
+ || file_info.stat.st_uid != file_info_old.stat.st_uid
+ || file_info.stat.st_gid != file_info_old.stat.st_gid) {
+ os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid);
+ if (perm >= 0) { // Set permission again, may have changed.
+ (void)os_setperm(wfname, perm);
+ }
+ }
+ buf_set_file_id(buf);
+ } else if (!buf->file_id_valid) {
+ // Set the file_id when creating a new file.
+ buf_set_file_id(buf);
+ }
+#endif
+
+ if ((error = os_close(fd)) != 0) {
+ err = set_err_arg(_("E512: Close failed: %s"), error);
+ end = 0;
+ }
+
+#ifdef UNIX
+ if (made_writable) {
+ perm &= ~0200; // reset 'w' bit for security reasons
+ }
+#endif
+ if (perm >= 0) { // Set perm. of new file same as old file.
+ (void)os_setperm(wfname, perm);
+ }
+ // Probably need to set the ACL before changing the user (can't set the
+ // ACL on a file the user doesn't own).
+ if (!backup_copy) {
+ os_set_acl(wfname, acl);
+ }
+
+ if (wfname != fname) {
+ // The file was written to a temp file, now it needs to be converted
+ // with 'charconvert' to (overwrite) the output file.
+ if (end != 0) {
+ if (eval_charconvert("utf-8", fenc, wfname, fname) == FAIL) {
+ write_info.bw_conv_error = true;
+ end = 0;
+ }
+ }
+ os_remove(wfname);
+ xfree(wfname);
+ }
+ }
+
+ if (end == 0) {
+ // Error encountered.
+ if (err.msg == NULL) {
+ if (write_info.bw_conv_error) {
+ if (write_info.bw_conv_error_lnum == 0) {
+ err = set_err(_(e_write_error_conversion_failed_make_fenc_empty_to_override));
+ } else {
+ err = set_err(xmalloc(300));
+ err.alloc = true;
+ vim_snprintf(err.msg, 300, // NOLINT(runtime/printf)
+ _(e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override),
+ write_info.bw_conv_error_lnum);
+ }
+ } else if (got_int) {
+ err = set_err(_(e_interr));
+ } else {
+ err = set_err(_(e_write_error_file_system_full));
+ }
+ }
+
+ // If we have a backup file, try to put it in place of the new file,
+ // because the new file is probably corrupt. This avoids losing the
+ // original file when trying to make a backup when writing the file a
+ // second time.
+ // When "backup_copy" is set we need to copy the backup over the new
+ // file. Otherwise rename the backup file.
+ // If this is OK, don't give the extra warning message.
+ if (backup != NULL) {
+ if (backup_copy) {
+ // This may take a while, if we were interrupted let the user
+ // know we got the message.
+ if (got_int) {
+ msg(_(e_interr), 0);
+ ui_flush();
+ }
+
+ // copy the file.
+ if (os_copy(backup, fname, UV_FS_COPYFILE_FICLONE)
+ == 0) {
+ end = 1; // success
+ }
+ } else {
+ if (vim_rename(backup, fname) == 0) {
+ end = 1;
+ }
+ }
+ }
+ goto fail;
+ }
+
+ lnum -= start; // compute number of written lines
+ no_wait_return--; // may wait for return now
+
+#if !defined(UNIX)
+ fname = sfname; // use shortname now, for the messages
+#endif
+ if (!filtering) {
+ add_quoted_fname(IObuff, IOSIZE, buf, fname);
+ bool insert_space = false;
+ if (write_info.bw_conv_error) {
+ xstrlcat(IObuff, _(" CONVERSION ERROR"), IOSIZE);
+ insert_space = true;
+ if (write_info.bw_conv_error_lnum != 0) {
+ vim_snprintf_add(IObuff, IOSIZE, _(" in line %" PRId64 ";"),
+ (int64_t)write_info.bw_conv_error_lnum);
+ }
+ } else if (notconverted) {
+ xstrlcat(IObuff, _("[NOT converted]"), IOSIZE);
+ insert_space = true;
+ } else if (converted) {
+ xstrlcat(IObuff, _("[converted]"), IOSIZE);
+ insert_space = true;
+ }
+ if (device) {
+ xstrlcat(IObuff, _("[Device]"), IOSIZE);
+ insert_space = true;
+ } else if (newfile) {
+ xstrlcat(IObuff, _("[New]"), IOSIZE);
+ insert_space = true;
+ }
+ if (no_eol) {
+ xstrlcat(IObuff, _("[noeol]"), IOSIZE);
+ insert_space = true;
+ }
+ // may add [unix/dos/mac]
+ if (msg_add_fileformat(fileformat)) {
+ insert_space = true;
+ }
+ msg_add_lines(insert_space, lnum, nchars); // add line/char count
+ if (!shortmess(SHM_WRITE)) {
+ if (append) {
+ xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended"), IOSIZE);
+ } else {
+ xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"), IOSIZE);
+ }
+ }
+
+ set_keep_msg(msg_trunc(IObuff, false, 0), 0);
+ }
+
+ // When written everything correctly: reset 'modified'. Unless not
+ // writing to the original file and '+' is not in 'cpoptions'.
+ if (reset_changed && whole && !append
+ && !write_info.bw_conv_error
+ && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
+ unchanged(buf, true, false);
+ const varnumber_T changedtick = buf_get_changedtick(buf);
+ if (buf->b_last_changedtick + 1 == changedtick) {
+ // b:changedtick may be incremented in unchanged() but that should not
+ // trigger a TextChanged event.
+ buf->b_last_changedtick = changedtick;
+ }
+ u_unchanged(buf);
+ u_update_save_nr(buf);
+ }
+
+ // If written to the current file, update the timestamp of the swap file
+ // and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime.
+ if (overwriting) {
+ ml_timestamp(buf);
+ if (append) {
+ buf->b_flags &= ~BF_NEW;
+ } else {
+ buf->b_flags &= ~BF_WRITE_MASK;
+ }
+ }
+
+ // If we kept a backup until now, and we are in patch mode, then we make
+ // the backup file our 'original' file.
+ if (*p_pm && dobackup) {
+ char *const org = modname(fname, p_pm, false);
+
+ if (backup != NULL) {
+ // If the original file does not exist yet
+ // the current backup file becomes the original file
+ if (org == NULL) {
+ emsg(_("E205: Patchmode: can't save original file"));
+ } else if (!os_path_exists(org)) {
+ vim_rename(backup, org);
+ XFREE_CLEAR(backup); // don't delete the file
+#ifdef UNIX
+ os_file_settime(org,
+ (double)file_info_old.stat.st_atim.tv_sec,
+ (double)file_info_old.stat.st_mtim.tv_sec);
+#endif
+ }
+ } else {
+ // If there is no backup file, remember that a (new) file was
+ // created.
+ int empty_fd;
+
+ if (org == NULL
+ || (empty_fd = os_open(org,
+ O_CREAT | O_EXCL | O_NOFOLLOW,
+ perm < 0 ? 0666 : (perm & 0777))) < 0) {
+ emsg(_(e_patchmode_cant_touch_empty_original_file));
+ } else {
+ close(empty_fd);
+ }
+ }
+ if (org != NULL) {
+ os_setperm(org, os_getperm(fname) & 0777);
+ xfree(org);
+ }
+ }
+
+ // Remove the backup unless 'backup' option is set
+ if (!p_bk && backup != NULL
+ && !write_info.bw_conv_error
+ && os_remove(backup) != 0) {
+ emsg(_("E207: Can't delete backup file"));
+ }
+
+ goto nofail;
+
+ // Finish up. We get here either after failure or success.
+fail:
+ no_wait_return--; // may wait for return now
+nofail:
+
+ // Done saving, we accept changed buffer warnings again
+ buf->b_saving = false;
+
+ xfree(backup);
+ if (buffer != smallbuf) {
+ xfree(buffer);
+ }
+ xfree(fenc_tofree);
+ xfree(write_info.bw_conv_buf);
+ if (write_info.bw_iconv_fd != (iconv_t)-1) {
+ iconv_close(write_info.bw_iconv_fd);
+ write_info.bw_iconv_fd = (iconv_t)-1;
+ }
+ os_free_acl(acl);
+
+ if (err.msg != NULL) {
+ // - 100 to save some space for further error message
+#ifndef UNIX
+ add_quoted_fname(IObuff, IOSIZE - 100, buf, sfname);
+#else
+ add_quoted_fname(IObuff, IOSIZE - 100, buf, fname);
+#endif
+ emit_err(&err);
+
+ retval = FAIL;
+ if (end == 0) {
+ const int attr = HL_ATTR(HLF_E); // Set highlight for error messages.
+ msg_puts_attr(_("\nWARNING: Original file may be lost or damaged\n"),
+ attr | MSG_HIST);
+ msg_puts_attr(_("don't quit the editor until the file is successfully written!"),
+ attr | MSG_HIST);
+
+ // Update the timestamp to avoid an "overwrite changed file"
+ // prompt when writing again.
+ if (os_fileinfo(fname, &file_info_old)) {
+ buf_store_file_info(buf, &file_info_old);
+ buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
+ }
+ }
+ }
+ msg_scroll = msg_save;
+
+ // When writing the whole file and 'undofile' is set, also write the undo
+ // file.
+ if (retval == OK && write_undo_file) {
+ uint8_t hash[UNDO_HASH_SIZE];
+
+ sha256_finish(&sha_ctx, hash);
+ u_write_undo(NULL, false, buf, hash);
+ }
+
+ if (!should_abort(retval)) {
+ buf_write_do_post_autocmds(buf, fname, eap, append, filtering, reset_changed, whole);
+ if (aborting()) { // autocmds may abort script processing
+ retval = false;
+ }
+ }
+
+ got_int |= prev_got_int;
+
+ return retval;
+}
diff --git a/src/nvim/bufwrite.h b/src/nvim/bufwrite.h
new file mode 100644
index 0000000000..9ed6216847
--- /dev/null
+++ b/src/nvim/bufwrite.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "bufwrite.h.generated.h"
+#endif
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 06696610b0..81a55b92ee 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// change.c: functions related to changing text
#include <assert.h>
@@ -8,8 +5,8 @@
#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -24,14 +21,14 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -39,17 +36,18 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/spell.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/textformat.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "change.c.generated.h"
@@ -91,7 +89,7 @@ void change_warning(buf_T *buf, int col)
(void)msg_end();
if (msg_silent == 0 && !silent_mode && ui_active()) {
ui_flush();
- os_delay(1002L, true); // give the user time to think about it
+ os_delay(1002, true); // give the user time to think about it
}
buf->b_did_warn = true;
redraw_cmdline = false; // don't redraw and erase the message
@@ -101,28 +99,28 @@ void change_warning(buf_T *buf, int col)
}
}
-/// Call this function when something in the current buffer is changed.
+/// Call this function when something in a buffer is changed.
///
/// Most often called through changed_bytes() and changed_lines(), which also
/// mark the area of the display to be redrawn.
///
/// Careful: may trigger autocommands that reload the buffer.
-void changed(void)
+void changed(buf_T *buf)
{
- if (!curbuf->b_changed) {
+ if (!buf->b_changed) {
int save_msg_scroll = msg_scroll;
// Give a warning about changing a read-only file. This may also
// check-out the file, thus change "curbuf"!
- change_warning(curbuf, 0);
+ change_warning(buf, 0);
// Create a swap file if that is wanted.
// Don't do this for "nofile" and "nowrite" buffer types.
- if (curbuf->b_may_swap && !bt_dontwrite(curbuf)) {
+ if (buf->b_may_swap && !bt_dontwrite(buf)) {
bool save_need_wait_return = need_wait_return;
need_wait_return = false;
- ml_open_file(curbuf);
+ ml_open_file(buf);
// The ml_open_file() can cause an ATTENTION message.
// Wait two seconds, to make sure the user reads this unexpected
@@ -130,16 +128,16 @@ void changed(void)
// and don't let the emsg() set msg_scroll.
if (need_wait_return && emsg_silent == 0 && !in_assert_fails) {
ui_flush();
- os_delay(2002L, true);
+ os_delay(2002, true);
wait_return(true);
msg_scroll = save_msg_scroll;
} else {
need_wait_return = save_need_wait_return;
}
}
- changed_internal();
+ changed_internal(buf);
}
- buf_inc_changedtick(curbuf);
+ buf_inc_changedtick(buf);
// If a pattern is highlighted, the position may now be invalid.
highlight_match = false;
@@ -147,12 +145,12 @@ void changed(void)
/// Internal part of changed(), no user interaction.
/// Also used for recovery.
-void changed_internal(void)
+void changed_internal(buf_T *buf)
{
- curbuf->b_changed = true;
- curbuf->b_changed_invalid = true;
- ml_setflags(curbuf);
- redraw_buf_status_later(curbuf);
+ buf->b_changed = true;
+ buf->b_changed_invalid = true;
+ ml_setflags(buf);
+ redraw_buf_status_later(buf);
redraw_tabline = true;
need_maketitle = true; // set window title later
}
@@ -160,13 +158,15 @@ void changed_internal(void)
/// Common code for when a change was made.
/// See changed_lines() for the arguments.
/// Careful: may trigger autocommands that reload the buffer.
-static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra)
+static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra)
{
// mark the buffer as modified
- changed();
+ changed(buf);
- if (curwin->w_p_diff && diff_internal()) {
- curtab->tp_diff_update = true;
+ FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
+ if (win->w_buffer == buf && win->w_p_diff && diff_internal()) {
+ curtab->tp_diff_update = true;
+ }
}
// set the '. mark
@@ -174,22 +174,25 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
fmarkv_T view = INIT_FMARKV;
// Set the markview only if lnum is visible, as changes might be done
// outside of the current window view.
- if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) {
- view = mark_view_make(curwin->w_topline, curwin->w_cursor);
+
+ if (curwin->w_buffer == buf) {
+ if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) {
+ view = mark_view_make(curwin->w_topline, curwin->w_cursor);
+ }
}
- RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), curbuf->handle, view);
+ RESET_FMARK(&buf->b_last_change, ((pos_T) { lnum, col, 0 }), buf->handle, view);
// Create a new entry if a new undo-able change was started or we
// don't have an entry yet.
- if (curbuf->b_new_change || curbuf->b_changelistlen == 0) {
+ if (buf->b_new_change || buf->b_changelistlen == 0) {
int add;
- if (curbuf->b_changelistlen == 0) {
+ if (buf->b_changelistlen == 0) {
add = true;
} else {
// Don't create a new entry when the line number is the same
// as the last one and the column is not too far away. Avoids
// creating many entries for typing "xxxxx".
- pos_T *p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
+ pos_T *p = &buf->b_changelist[buf->b_changelistlen - 1].mark;
if (p->lnum != lnum) {
add = true;
} else {
@@ -204,17 +207,17 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
// This is the first of a new sequence of undo-able changes
// and it's at some distance of the last change. Use a new
// position in the changelist.
- curbuf->b_new_change = false;
+ buf->b_new_change = false;
- if (curbuf->b_changelistlen == JUMPLISTSIZE) {
+ if (buf->b_changelistlen == JUMPLISTSIZE) {
// changelist is full: remove oldest entry
- curbuf->b_changelistlen = JUMPLISTSIZE - 1;
- memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
- sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
+ buf->b_changelistlen = JUMPLISTSIZE - 1;
+ memmove(buf->b_changelist, buf->b_changelist + 1,
+ sizeof(buf->b_changelist[0]) * (JUMPLISTSIZE - 1));
FOR_ALL_TAB_WINDOWS(tp, wp) {
// Correct position in changelist for other windows on
// this buffer.
- if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) {
+ if (wp->w_buffer == buf && wp->w_changelistidx > 0) {
wp->w_changelistidx--;
}
}
@@ -222,37 +225,53 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
FOR_ALL_TAB_WINDOWS(tp, wp) {
// For other windows, if the position in the changelist is
// at the end it stays at the end.
- if (wp->w_buffer == curbuf
- && wp->w_changelistidx == curbuf->b_changelistlen) {
+ if (wp->w_buffer == buf
+ && wp->w_changelistidx == buf->b_changelistlen) {
wp->w_changelistidx++;
}
}
- curbuf->b_changelistlen++;
+ buf->b_changelistlen++;
}
}
- curbuf->b_changelist[curbuf->b_changelistlen - 1] =
- curbuf->b_last_change;
+ buf->b_changelist[buf->b_changelistlen - 1] =
+ buf->b_last_change;
// The current window is always after the last change, so that "g,"
// takes you back to it.
- curwin->w_changelistidx = curbuf->b_changelistlen;
+ if (curwin->w_buffer == buf) {
+ curwin->w_changelistidx = buf->b_changelistlen;
+ }
}
- if (VIsual_active) {
+ if (curwin->w_buffer == buf && VIsual_active) {
check_visual_pos();
}
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == curbuf) {
+ if (wp->w_buffer == buf) {
// Mark this window to be redrawn later.
if (wp->w_redr_type < UPD_VALID) {
wp->w_redr_type = UPD_VALID;
}
+ linenr_T last = lnume + xtra - 1; // last line after the change
+
+ // Reset "w_skipcol" if the topline length has become smaller to
+ // such a degree that nothing will be visible anymore, accounting
+ // for 'smoothscroll' <<< or 'listchars' "precedes" marker.
+ if (wp->w_skipcol > 0
+ && (last < wp->w_topline
+ || (wp->w_topline >= lnum
+ && wp->w_topline < lnume
+ && win_linetabsize(wp, wp->w_topline, ml_get(wp->w_topline), MAXCOL)
+ <= (wp->w_skipcol
+ + sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp)))))) {
+ wp->w_skipcol = 0;
+ }
+
// Check if a change in the buffer has invalidated the cached
// values for the cursor.
// Update the folds for this window. Can't postpone this, because
// a following operator might work on the whole fold: ">>dd".
- linenr_T last = lnume + xtra - 1; // last line after the change
foldUpdate(wp, lnum, last);
// The change may cause lines above or below the change to become
@@ -281,7 +300,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
if (wp->w_cursor.lnum > lnum) {
changed_line_abv_curs_win(wp);
} else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) {
- changed_cline_bef_curs_win(wp);
+ changed_cline_bef_curs(wp);
}
if (wp->w_botline >= lnum) {
// Assume that botline doesn't change (inserted lines make
@@ -296,7 +315,14 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
for (int i = 0; i < wp->w_lines_valid; i++) {
if (wp->w_lines[i].wl_valid) {
if (wp->w_lines[i].wl_lnum >= lnum) {
- if (wp->w_lines[i].wl_lnum < lnume) {
+ // Do not change wl_lnum at index zero, it is used to
+ // compare with w_topline. Invalidate it instead.
+ // If the buffer has virt_lines, invalidate the line
+ // after the changed lines as the virt_lines for a
+ // changed line may become invalid.
+ if (i == 0 || wp->w_lines[i].wl_lnum < lnume
+ || (wp->w_lines[i].wl_lnum == lnume
+ && wp->w_buffer->b_virt_line_blocks > 0)) {
// line included in change
wp->w_lines[i].wl_valid = false;
} else if (xtra != 0) {
@@ -345,9 +371,10 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
}
// when the cursor line is changed always trigger CursorMoved
- if (lnum <= curwin->w_cursor.lnum
+ if (last_cursormoved_win == curwin && curwin->w_buffer == buf
+ && lnum <= curwin->w_cursor.lnum
&& lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) {
- curwin->w_last_cursormoved.lnum = 0;
+ last_cursormoved.lnum = 0;
}
}
@@ -377,7 +404,16 @@ static void changedOneline(buf_T *buf, linenr_T lnum)
void changed_bytes(linenr_T lnum, colnr_T col)
{
changedOneline(curbuf, lnum);
- changed_common(lnum, col, lnum + 1, 0);
+ changed_common(curbuf, lnum, col, lnum + 1, 0);
+ // When text has been changed at the end of the line, possibly the start of
+ // the next line may have SpellCap that should be removed or it needs to be
+ // displayed. Schedule the next line for redrawing just in case.
+ // Don't do this when displaying '$' at the end of changed text.
+ if (spell_check_window(curwin)
+ && lnum < curbuf->b_ml.ml_line_count
+ && vim_strchr(p_cpo, CPO_DOLLAR) == NULL) {
+ redrawWinline(curwin, lnum + 1);
+ }
// notify any channels that are watching
buf_updates_send_changes(curbuf, lnum, 1, 1);
@@ -398,13 +434,13 @@ void changed_bytes(linenr_T lnum, colnr_T col)
/// insert/delete bytes at column
///
/// Like changed_bytes() but also adjust extmark for "new" bytes.
-void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
+void inserted_bytes(linenr_T lnum, colnr_T start_col, int old_col, int new_col)
{
if (curbuf_splice_pending == 0) {
- extmark_splice_cols(curbuf, (int)lnum - 1, col, old, new, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum - 1, start_col, old_col, new_col, kExtmarkUndo);
}
- changed_bytes(lnum, col);
+ changed_bytes(lnum, start_col);
}
/// Appended "count" lines below line "lnum" in the current buffer.
@@ -412,14 +448,14 @@ void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
/// Takes care of marking the buffer to be redrawn and sets the changed flag.
void appended_lines(linenr_T lnum, linenr_T count)
{
- changed_lines(lnum + 1, 0, lnum + 1, count, true);
+ changed_lines(curbuf, lnum + 1, 0, lnum + 1, count, true);
}
/// Like appended_lines(), but adjust marks first.
-void appended_lines_mark(linenr_T lnum, long count)
+void appended_lines_mark(linenr_T lnum, int count)
{
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, kExtmarkUndo);
- changed_lines(lnum + 1, 0, lnum + 1, (linenr_T)count, true);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0, kExtmarkUndo);
+ changed_lines(curbuf, lnum + 1, 0, lnum + 1, (linenr_T)count, true);
}
/// Deleted "count" lines at line "lnum" in the current buffer.
@@ -427,13 +463,13 @@ void appended_lines_mark(linenr_T lnum, long count)
/// Takes care of marking the buffer to be redrawn and sets the changed flag.
void deleted_lines(linenr_T lnum, linenr_T count)
{
- changed_lines(lnum, 0, lnum + count, -count, true);
+ changed_lines(curbuf, lnum, 0, lnum + count, -count, true);
}
/// Like deleted_lines(), but adjust marks first.
/// Make sure the cursor is on a valid line before calling, a GUI callback may
/// be triggered to display the cursor.
-void deleted_lines_mark(linenr_T lnum, long count)
+void deleted_lines_mark(linenr_T lnum, int count)
{
bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY;
@@ -441,16 +477,17 @@ void deleted_lines_mark(linenr_T lnum, long count)
// if we deleted the entire buffer, we need to implicitly add a new empty line
extmark_adjust(curbuf, lnum, (linenr_T)(lnum + count - 1), MAXLNUM,
-(linenr_T)count + (made_empty ? 1 : 0), kExtmarkUndo);
- changed_lines(lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true);
+ changed_lines(curbuf, lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true);
}
/// Marks the area to be redrawn after a change.
+/// Consider also calling changed_line_display_buf().
///
/// @param buf the buffer where lines were changed
/// @param lnum first line with change
/// @param lnume line below last changed line
/// @param xtra number of extra lines (negative when deleting)
-void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
+void buf_redraw_changed_lines_later(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
{
if (buf->b_mod_set) {
// find the maximum area that must be redisplayed
@@ -477,7 +514,7 @@ void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
}
}
-/// Changed lines for the current buffer.
+/// Changed lines for a buffer.
/// Must be called AFTER the change and after mark_adjust().
/// - mark the buffer changed by calling changed()
/// - mark the windows on this buffer to be redisplayed
@@ -495,11 +532,12 @@ void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
/// @param do_buf_event some callers like undo/redo call changed_lines() and
/// then increment changedtick *again*. This flag allows these callers to send
/// the nvim_buf_lines_event events after they're done modifying changedtick.
-void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra, bool do_buf_event)
+void changed_lines(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra,
+ bool do_buf_event)
{
- changed_lines_buf(curbuf, lnum, lnume, xtra);
+ buf_redraw_changed_lines_later(buf, lnum, lnume, xtra);
- if (xtra == 0 && curwin->w_p_diff && !diff_internal()) {
+ if (xtra == 0 && curwin->w_p_diff && curwin->w_buffer == buf && !diff_internal()) {
// When the number of lines doesn't change then mark_adjust() isn't
// called and other diff buffers still need to be marked for
// displaying.
@@ -510,19 +548,19 @@ void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra, bo
redraw_later(wp, UPD_VALID);
wlnum = diff_lnum_win(lnum, wp);
if (wlnum > 0) {
- changed_lines_buf(wp->w_buffer, wlnum,
- lnume - lnum + wlnum, 0L);
+ buf_redraw_changed_lines_later(wp->w_buffer, wlnum,
+ lnume - lnum + wlnum, 0);
}
}
}
}
- changed_common(lnum, col, lnume, xtra);
+ changed_common(buf, lnum, col, lnume, xtra);
if (do_buf_event) {
int64_t num_added = (int64_t)(lnume + xtra - lnum);
int64_t num_removed = lnume - lnum;
- buf_updates_send_changes(curbuf, lnum, num_added, num_removed);
+ buf_updates_send_changes(buf, lnum, num_added, num_removed);
}
}
@@ -582,7 +620,7 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty)
if (ignore_empty
&& (buf->b_flags & BF_NEW)
&& buf->b_ml.ml_line_count == 1
- && *ml_get_buf(buf, (linenr_T)1, false) == NUL) {
+ && *ml_get_buf(buf, 1) == NUL) {
return false;
}
if (buf->b_start_ffc != *buf->b_p_ff) {
@@ -627,7 +665,7 @@ void ins_bytes_len(char *p, size_t len)
/// convert bytes to a character.
void ins_char(int c)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
size_t n = (size_t)utf_char2bytes(c, buf);
// When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
@@ -709,8 +747,7 @@ void ins_char_bytes(char *buf, size_t charlen)
// Copy bytes after the changed character(s).
char *p = newp + col;
if (linelen > col + oldlen) {
- memmove(p + newlen, oldp + col + oldlen,
- (size_t)(linelen - col - oldlen));
+ memmove(p + newlen, oldp + col + oldlen, linelen - col - oldlen);
}
// Insert or overwrite the new character.
@@ -783,15 +820,15 @@ int del_char(bool fixpos)
if (*get_cursor_pos_ptr() == NUL) {
return FAIL;
}
- return del_chars(1L, fixpos);
+ return del_chars(1, fixpos);
}
/// Like del_bytes(), but delete characters instead of bytes.
-int del_chars(long count, int fixpos)
+int del_chars(int count, int fixpos)
{
int bytes = 0;
char *p = get_cursor_pos_ptr();
- for (long i = 0; i < count && *p != NUL; i++) {
+ for (int i = 0; i < count && *p != NUL; i++) {
int l = utfc_ptr2len(p);
bytes += l;
p += l;
@@ -832,12 +869,9 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
// If 'delcombine' is set and deleting (less than) one character, only
// delete the last combining character.
- if (p_deco && use_delcombine
- && utfc_ptr2len(oldp + col) >= count) {
- int cc[MAX_MCO];
-
- (void)utfc_ptr2char(oldp + col, cc);
- if (cc[0] != NUL) {
+ if (p_deco && use_delcombine && utfc_ptr2len(oldp + col) >= count) {
+ char *p0 = oldp + col;
+ if (utf_composinglike(p0, p0 + utf_ptr2len(p0))) {
// Find the last composing char, there can be several.
int n = col;
do {
@@ -949,7 +983,7 @@ int copy_indent(int size, char *src)
// Add tabs required for indent.
if (!curbuf->b_p_et) {
- for (;;) {
+ while (true) {
tab_pad = tabstop_padding(ind_col,
curbuf->b_p_ts,
curbuf->b_p_vts_array);
@@ -1093,7 +1127,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
*p_extra = NUL;
}
- u_clearline(); // cannot do "U" command when adding lines
+ u_clearline(curbuf); // cannot do "U" command when adding lines
did_si = false;
ai_col = 0;
@@ -1160,12 +1194,16 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
if (p[0] == '/' && p[-1] == '*') {
// End of C comment, indent should line up
// with the line containing the start of
- // the comment
+ // the comment.
curwin->w_cursor.col = (colnr_T)(p - ptr);
if ((pos = findmatch(NULL, NUL)) != NULL) {
curwin->w_cursor.lnum = pos->lnum;
newindent = get_indent();
+ break;
}
+ // this may make "ptr" invalid, get it again
+ ptr = ml_get(curwin->w_cursor.lnum);
+ p = ptr + curwin->w_cursor.col;
}
}
}
@@ -1305,7 +1343,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
}
// find start of middle part
- (void)copy_option_part(&p, (char *)lead_middle, COM_MAX_LEN, ",");
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
require_blank = false;
}
@@ -1316,7 +1354,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
}
p++;
}
- (void)copy_option_part(&p, (char *)lead_middle, COM_MAX_LEN, ",");
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
while (*p && p[-1] != ':') { // find end of end flags
// Check whether we allow automatic ending of comments
@@ -1325,7 +1363,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
}
p++;
}
- size_t n = copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
+ size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
if (end_comment_pending == -1) { // we can set it now
end_comment_pending = (unsigned char)lead_end[n - 1];
@@ -1346,7 +1384,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// Doing "o" on a start of comment inserts the middle leader.
if (lead_len > 0) {
if (current_flag == COM_START) {
- lead_repl = (char *)lead_middle;
+ lead_repl = lead_middle;
lead_repl_len = (int)strlen(lead_middle);
}
@@ -1655,14 +1693,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// concatenate leader and p_extra, if there is a leader
if (lead_len > 0) {
if (flags & OPENLINE_COM_LIST && second_line_indent > 0) {
- int i;
int padding = second_line_indent
- (newindent + (int)strlen(leader));
// Here whitespace is inserted after the comment char.
// Below, set_indent(newindent, SIN_INSERT) will insert the
// whitespace needed before the comment char.
- for (i = 0; i < padding; i++) {
+ for (int i = 0; i < padding; i++) {
STRCAT(leader, " ");
less_cols--;
newcol++;
@@ -1682,12 +1719,12 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
curwin->w_cursor.lnum--;
}
if ((State & VREPLACE_FLAG) == 0 || old_cursor.lnum >= orig_line_count) {
- if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
+ if (ml_append(curwin->w_cursor.lnum, p_extra, 0, false) == FAIL) {
goto theend;
}
// Postpone calling changed_lines(), because it would mess up folding
// with markers.
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP);
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1, 0, kExtmarkNOOP);
did_append = true;
} else {
// In MODE_VREPLACE state we are starting to replace the next line.
@@ -1777,15 +1814,15 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
saved_line = NULL;
if (did_append) {
- changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
- curwin->w_cursor.lnum + 1, 1L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
+ curwin->w_cursor.lnum + 1, 1, true);
did_append = false;
// Move marks after the line break to the new line.
if (flags & OPENLINE_MARKFIX) {
mark_col_adjust(curwin->w_cursor.lnum,
curwin->w_cursor.col + less_cols_off,
- 1L, (long)-less_cols, 0);
+ 1, -less_cols, 0);
}
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
@@ -1803,7 +1840,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
curwin->w_cursor.lnum = old_cursor.lnum + 1;
}
if (did_append) {
- changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1, true);
// bail out and just get the final length of the line we just manipulated
bcount_t extra = (bcount_t)strlen(ml_get(curwin->w_cursor.lnum));
extmark_splice(curbuf, (int)curwin->w_cursor.lnum - 1, 0,
@@ -1897,9 +1934,9 @@ void truncate_line(int fixpos)
/// Delete "nlines" lines at the cursor.
/// Saves the lines for undo first if "undo" is true.
-void del_lines(long nlines, bool undo)
+void del_lines(linenr_T nlines, bool undo)
{
- long n;
+ int n;
linenr_T first = curwin->w_cursor.lnum;
if (nlines <= 0) {
@@ -1928,7 +1965,7 @@ void del_lines(long nlines, bool undo)
// Correct the cursor position before calling deleted_lines_mark(), it may
// trigger a callback to display the cursor.
curwin->w_cursor.col = 0;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
// adjust marks, mark the buffer as changed and prepare for displaying
deleted_lines_mark(first, n);
@@ -1946,9 +1983,7 @@ int get_leader_len(char *line, char **flags, bool backward, bool include_space)
int got_com = false;
char part_buf[COM_MAX_LEN]; // buffer for one option part
char *string; // pointer to comment string
- char *list;
int middle_match_len = 0;
- char *prev_list;
char *saved_flags = NULL;
int result = 0;
@@ -1961,13 +1996,13 @@ int get_leader_len(char *line, char **flags, bool backward, bool include_space)
while (line[i] != NUL) {
// scan through the 'comments' option for a match
int found_one = false;
- for (list = curbuf->b_p_com; *list;) {
+ for (char *list = curbuf->b_p_com; *list;) {
// Get one option part into part_buf[]. Advance "list" to next
// one. Put "string" at start of string.
if (!got_com && flags != NULL) {
*flags = list; // remember where flags started
}
- prev_list = list;
+ char *prev_list = list;
(void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
string = vim_strchr(part_buf, ':');
if (string == NULL) { // missing ':', ignore this part
@@ -2163,7 +2198,6 @@ int get_last_leader_offset(char *line, char **flags)
if (found_one) {
char part_buf2[COM_MAX_LEN]; // buffer for one option part
- int len1, len2, off;
result = i;
// If this comment nests, continue searching.
@@ -2181,7 +2215,7 @@ int get_last_leader_offset(char *line, char **flags)
while (ascii_iswhite(*com_leader)) {
com_leader++;
}
- len1 = (int)strlen(com_leader);
+ int len1 = (int)strlen(com_leader);
for (list = curbuf->b_p_com; *list;) {
char *flags_save = list;
@@ -2195,14 +2229,14 @@ int get_last_leader_offset(char *line, char **flags)
while (ascii_iswhite(*string)) {
string++;
}
- len2 = (int)strlen(string);
+ int len2 = (int)strlen(string);
if (len2 == 0) {
continue;
}
// Now we have to verify whether string ends with a substring
// beginning the com_leader.
- for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) {
+ for (int off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) {
off--;
if (!strncmp(string + off, com_leader, (size_t)(len2 - off))) {
if (i - off < lower_check_bound) {
diff --git a/src/nvim/change.h b/src/nvim/change.h
index d1d016c630..06155b6da6 100644
--- a/src/nvim/change.h
+++ b/src/nvim/change.h
@@ -1,19 +1,18 @@
-#ifndef NVIM_CHANGE_H
-#define NVIM_CHANGE_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/pos.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-// flags for open_line()
-#define OPENLINE_DELSPACES 0x01 // delete spaces after cursor
-#define OPENLINE_DO_COM 0x02 // format comments
-#define OPENLINE_KEEPTRAIL 0x04 // keep trailing spaces
-#define OPENLINE_MARKFIX 0x08 // fix mark positions
-#define OPENLINE_COM_LIST 0x10 // format comments with list/2nd line indent
-#define OPENLINE_FORMAT 0x20 // formatting long comment
+/// flags for open_line()
+enum {
+ OPENLINE_DELSPACES = 0x01, ///< delete spaces after cursor
+ OPENLINE_DO_COM = 0x02, ///< format comments
+ OPENLINE_KEEPTRAIL = 0x04, ///< keep trailing spaces
+ OPENLINE_MARKFIX = 0x08, ///< fix mark positions
+ OPENLINE_COM_LIST = 0x10, ///< format comments with list/2nd line indent
+ OPENLINE_FORMAT = 0x20, ///< formatting long comment
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "change.h.generated.h"
#endif
-
-#endif // NVIM_CHANGE_H
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 65bb87bc2c..e8fe80a3b6 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -1,13 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
-#include "lauxlib.h"
+#include "klib/kvec.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
@@ -17,10 +15,10 @@
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
-#include "nvim/event/loop.h"
#include "nvim/event/rstream.h"
#include "nvim/event/socket.h"
#include "nvim/event/wstream.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
@@ -32,12 +30,14 @@
#include "nvim/msgpack_rpc/server.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
+#include "nvim/path.h"
#include "nvim/rbuffer.h"
+
#ifdef MSWIN
+# include "nvim/os/fs.h"
# include "nvim/os/os_win_console.h"
# include "nvim/os/pty_conpty_win.h"
#endif
-#include "nvim/path.h"
static bool did_stdio = false;
@@ -79,7 +79,7 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
// allow double close, even though we can't say what parts was valid.
return true;
}
- *error = (const char *)e_invchan;
+ *error = e_invchan;
return false;
}
@@ -89,19 +89,19 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
if (chan->is_rpc) {
rpc_close(chan);
} else if (part == kChannelPartRpc) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
} else if ((part == kChannelPartStdin || part == kChannelPartStdout)
&& chan->is_rpc) {
- *error = (const char *)e_invstreamrpc;
+ *error = e_invstreamrpc;
return false;
}
switch (chan->streamtype) {
case kChannelStreamSocket:
if (!close_main) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
stream_may_close(&chan->stream.socket);
@@ -132,14 +132,14 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
stream_may_close(&chan->stream.stdio.out);
}
if (part == kChannelPartStderr) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
break;
case kChannelStreamStderr:
if (part != kChannelPartAll && part != kChannelPartStderr) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
if (!chan->stream.err.closed) {
@@ -154,7 +154,7 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
case kChannelStreamInternal:
if (!close_main) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
if (chan->term) {
@@ -166,9 +166,6 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
channel_decref(chan);
}
break;
-
- default:
- abort();
}
return true;
@@ -207,7 +204,7 @@ Channel *channel_alloc(ChannelStreamType type)
void channel_create_event(Channel *chan, const char *ext_source)
{
-#if MIN_LOG_LEVEL <= LOGLVL_INF
+#ifdef NVIM_LOG_DEBUG
const char *source;
if (ext_source) {
@@ -216,7 +213,7 @@ void channel_create_event(Channel *chan, const char *ext_source)
source = ext_source;
} else {
eval_fmt_source_name_line(IObuff, sizeof(IObuff));
- source = (const char *)IObuff;
+ source = IObuff;
}
assert(chan->id <= VARNUMBER_MAX);
@@ -225,6 +222,7 @@ void channel_create_event(Channel *chan, const char *ext_source)
// TODO(bfredl): do the conversion in one step. Also would be nice
// to pretty print top level dict in defined order
(void)object_to_vim(DICTIONARY_OBJ(info), &tv, NULL);
+ assert(tv.v_type == VAR_DICT);
char *str = encode_tv2json(&tv, NULL);
ILOG("new channel %" PRIu64 " (%s) : %s", chan->id, source, str);
xfree(str);
@@ -277,7 +275,7 @@ static void free_channel_event(void **argv)
callback_reader_free(&chan->on_stderr);
callback_free(&chan->on_exit);
- pmap_del(uint64_t)(&channels, chan->id);
+ pmap_del(uint64_t)(&channels, chan->id, NULL);
multiqueue_free(chan->events);
xfree(chan);
}
@@ -287,7 +285,7 @@ static void channel_destroy_early(Channel *chan)
if ((chan->id != --next_chan_id)) {
abort();
}
- pmap_del(uint64_t)(&channels, chan->id);
+ pmap_del(uint64_t)(&channels, chan->id, NULL);
chan->id = 0;
if ((--chan->refcount != 0)) {
@@ -307,6 +305,7 @@ static void close_cb(Stream *stream, void *data)
///
/// @param[in] argv Arguments vector specifying the command to run,
/// NULL-terminated
+/// @param[in] exepath The path to the executable. If NULL, use `argv[0]`.
/// @param[in] on_stdout Callback to read the job's stdout
/// @param[in] on_stderr Callback to read the job's stderr
/// @param[in] on_exit Callback to receive the job's exit status
@@ -328,10 +327,11 @@ static void close_cb(Stream *stream, void *data)
/// < 0 if the job can't start
///
/// @returns [allocated] channel
-Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader on_stderr,
- Callback on_exit, bool pty, bool rpc, bool overlapped, bool detach,
- ChannelStdinMode stdin_mode, const char *cwd, uint16_t pty_width,
- uint16_t pty_height, dict_T *env, varnumber_T *status_out)
+Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_stdout,
+ CallbackReader on_stderr, Callback on_exit, bool pty, bool rpc,
+ bool overlapped, bool detach, ChannelStdinMode stdin_mode,
+ const char *cwd, uint16_t pty_width, uint16_t pty_height, dict_T *env,
+ varnumber_T *status_out)
{
Channel *chan = channel_alloc(kChannelStreamProc);
chan->on_data = on_stdout;
@@ -362,6 +362,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader
Process *proc = &chan->stream.proc;
proc->argv = argv;
+ proc->exepath = exepath;
proc->cb = channel_process_exit_cb;
proc->events = chan->events;
proc->detach = detach;
@@ -369,8 +370,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader
proc->env = env;
proc->overlapped = overlapped;
- char *cmd = xstrdup(proc->argv[0]);
- bool has_in, has_out, has_err;
+ char *cmd = xstrdup(process_get_exepath(proc));
+ bool has_out, has_err;
if (proc->type == kProcessTypePty) {
has_out = true;
has_err = false;
@@ -380,14 +381,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader
proc->fwd_err = chan->on_stderr.fwd_err;
}
- switch (stdin_mode) {
- case kChannelStdinPipe:
- has_in = true;
- break;
- case kChannelStdinNull:
- has_in = false;
- break;
- }
+ bool has_in = stdin_mode == kChannelStdinPipe;
int status = process_spawn(proc, has_in, has_out, has_err);
if (status) {
@@ -790,10 +784,9 @@ void channel_terminal_open(buf_T *buf, Channel *chan)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- buf->b_p_channel = (long)chan->id; // 'channel' option
- Terminal *term = terminal_open(buf, topts);
- chan->term = term;
+ buf->b_p_channel = (OptInt)chan->id; // 'channel' option
channel_incref(chan);
+ terminal_open(&chan->term, buf, topts);
}
static void term_write(char *buf, size_t size, void *data)
@@ -836,13 +829,12 @@ static void term_close(void *data)
multiqueue_put(chan->events, term_delayed_free, 1, data);
}
-void channel_info_changed(Channel *chan, bool new)
+void channel_info_changed(Channel *chan, bool new_chan)
{
- event_T event = new ? EVENT_CHANOPEN : EVENT_CHANINFO;
+ event_T event = new_chan ? EVENT_CHANOPEN : EVENT_CHANINFO;
if (has_event(event)) {
channel_incref(chan);
- multiqueue_put(main_loop.events, set_info_event,
- 2, chan, event);
+ multiqueue_put(main_loop.events, set_info_event, 2, chan, event);
}
}
@@ -856,6 +848,7 @@ static void set_info_event(void **argv)
Dictionary info = channel_info(chan->id);
typval_T retval;
(void)object_to_vim(DICTIONARY_OBJ(info), &retval, NULL);
+ assert(retval.v_type == VAR_DICT);
tv_dict_add_dict(dict, S_LEN("info"), retval.vval.v_dict);
tv_dict_set_keys_readonly(dict);
@@ -890,14 +883,14 @@ Dictionary channel_info(uint64_t id)
stream_desc = "job";
if (chan->stream.proc.type == kProcessTypePty) {
const char *name = pty_process_tty_name(&chan->stream.pty);
- PUT(info, "pty", STRING_OBJ(cstr_to_string(name)));
+ PUT(info, "pty", CSTR_TO_OBJ(name));
}
char **p = chan->stream.proc.argv;
Array argv = ARRAY_DICT_INIT;
if (p != NULL) {
while (*p != NULL) {
- ADD(argv, STRING_OBJ(cstr_to_string(*p)));
+ ADD(argv, CSTR_TO_OBJ(*p));
p++;
}
}
@@ -920,11 +913,8 @@ Dictionary channel_info(uint64_t id)
case kChannelStreamSocket:
stream_desc = "socket";
break;
-
- default:
- abort();
}
- PUT(info, "stream", STRING_OBJ(cstr_to_string(stream_desc)));
+ PUT(info, "stream", CSTR_TO_OBJ(stream_desc));
if (chan->is_rpc) {
mode_desc = "rpc";
@@ -935,17 +925,33 @@ Dictionary channel_info(uint64_t id)
} else {
mode_desc = "bytes";
}
- PUT(info, "mode", STRING_OBJ(cstr_to_string(mode_desc)));
+ PUT(info, "mode", CSTR_TO_OBJ(mode_desc));
return info;
}
+/// Simple int64_t comparison function for use with qsort()
+static int int64_t_cmp(const void *a, const void *b)
+{
+ int64_t diff = *(int64_t *)a - *(int64_t *)b;
+ return (diff < 0) ? -1 : (diff > 0);
+}
+
Array channel_all_info(void)
{
- Channel *channel;
- Array ret = ARRAY_DICT_INIT;
- map_foreach_value(&channels, channel, {
- ADD(ret, DICTIONARY_OBJ(channel_info(channel->id)));
+ // order the items in the array by channel number, for Determinismâ„¢
+ kvec_t(int64_t) ids = KV_INITIAL_VALUE;
+ kv_resize(ids, map_size(&channels));
+ uint64_t id;
+ map_foreach_key(&channels, id, {
+ kv_push(ids, (int64_t)id);
});
+ qsort(ids.items, ids.size, sizeof ids.items[0], int64_t_cmp);
+
+ Array ret = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < ids.size; i++) {
+ ADD(ret, DICTIONARY_OBJ(channel_info((uint64_t)ids.items[i])));
+ }
+ kv_destroy(ids);
return ret;
}
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index 5743eaead5..5c9d708ac2 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -1,26 +1,23 @@
-#ifndef NVIM_CHANNEL_H
-#define NVIM_CHANNEL_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/socket.h"
#include "nvim/event/stream.h"
-#include "nvim/garray.h"
-#include "nvim/macros.h"
+#include "nvim/garray_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
#include "nvim/map_defs.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/os/pty_process.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#define CHAN_STDIO 1
#define CHAN_STDERR 2
@@ -110,9 +107,9 @@ struct Channel {
bool callback_scheduled;
};
-EXTERN PMap(uint64_t) channels INIT(= MAP_INIT);
+EXTERN PMap(uint64_t) channels INIT( = MAP_INIT);
-EXTERN Callback on_print INIT(= CALLBACK_INIT);
+EXTERN Callback on_print INIT( = CALLBACK_INIT);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.h.generated.h"
@@ -121,7 +118,7 @@ EXTERN Callback on_print INIT(= CALLBACK_INIT);
/// @returns Channel with the id or NULL if not found
static inline Channel *find_channel(uint64_t id)
{
- return pmap_get(uint64_t)(&channels, id);
+ return (Channel *)pmap_get(uint64_t)(&channels, id);
}
static inline Stream *channel_instream(Channel *chan)
@@ -163,5 +160,3 @@ static inline Stream *channel_outstream(Channel *chan)
}
abort();
}
-
-#endif // NVIM_CHANNEL_H
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 5aec9ccf9d..48a9808b31 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file charset.c
///
/// Code related to character sets.
@@ -11,33 +8,26 @@
#include <limits.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/indent.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
-#include "nvim/mark.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
-#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/path.h"
-#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/state.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "charset.c.generated.h"
@@ -55,7 +45,7 @@ static bool chartab_initialized = false;
((chartab)[(unsigned)(c) >> 6] & (1ull << ((c) & 0x3f)))
// Table used below, see init_chartab() for an explanation
-static char_u g_chartab[256];
+static uint8_t g_chartab[256];
// Flags for g_chartab[].
#define CT_CELL_MASK 0x07 ///< mask: nr of display cells (1, 2 or 4)
@@ -97,10 +87,6 @@ int init_chartab(void)
int buf_init_chartab(buf_T *buf, int global)
{
int c;
- int c2;
- int i;
- bool tilde;
- bool do_isalpha;
if (global) {
// Set the default size for printable characters:
@@ -119,19 +105,13 @@ int buf_init_chartab(buf_T *buf, int global)
while (c < 256) {
if (c >= 0xa0) {
// UTF-8: bytes 0xa0 - 0xff are printable (latin1)
- g_chartab[c++] = CT_PRINT_CHAR + 1;
+ // Also assume that every multi-byte char is a filename character.
+ g_chartab[c++] = (CT_PRINT_CHAR | CT_FNAME_CHAR) + 1;
} else {
// the rest is unprintable by default
g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2;
}
}
-
- // Assume that every multi-byte char is a filename character.
- for (c = 1; c < 256; c++) {
- if (c >= 0xa0) {
- g_chartab[c] |= CT_FNAME_CHAR;
- }
- }
}
// Init word char flags all to false
@@ -145,7 +125,7 @@ int buf_init_chartab(buf_T *buf, int global)
// Walk through the 'isident', 'iskeyword', 'isfname' and 'isprint'
// options Each option is a list of characters, character numbers or
// ranges, separated by commas, e.g.: "200-210,x,#-178,-"
- for (i = global ? 0 : 3; i <= 3; i++) {
+ for (int i = global ? 0 : 3; i <= 3; i++) {
const char *p;
if (i == 0) {
// first round: 'isident'
@@ -162,8 +142,8 @@ int buf_init_chartab(buf_T *buf, int global)
}
while (*p) {
- tilde = false;
- do_isalpha = false;
+ bool tilde = false;
+ bool do_isalpha = false;
if ((*p == '^') && (p[1] != NUL)) {
tilde = true;
@@ -175,7 +155,7 @@ int buf_init_chartab(buf_T *buf, int global)
} else {
c = mb_ptr2char_adv(&p);
}
- c2 = -1;
+ int c2 = -1;
if ((*p == '-') && (p[1] != NUL)) {
p++;
@@ -286,7 +266,7 @@ void trans_characters(char *buf, int bufsize)
if ((trs_len = utfc_ptr2len(buf)) > 1) {
len -= trs_len;
} else {
- trs = (char *)transchar_byte((uint8_t)(*buf));
+ trs = transchar_byte((uint8_t)(*buf));
trs_len = (int)strlen(trs);
if (trs_len > 1) {
@@ -321,15 +301,13 @@ size_t transstr_len(const char *const s, bool untab)
while (*p) {
const size_t l = (size_t)utfc_ptr2len(p);
if (l > 1) {
- int pcc[MAX_MCO + 1];
- pcc[0] = utfc_ptr2char(p, &pcc[1]);
-
- if (vim_isprintc(pcc[0])) {
+ if (vim_isprintc(utf_ptr2char(p))) {
len += l;
} else {
- for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ for (size_t off = 0; off < l; off += (size_t)utf_ptr2len(p + off)) {
+ int c = utf_ptr2char(p + off);
char hexbuf[9];
- len += transchar_hex(hexbuf, pcc[i]);
+ len += transchar_hex(hexbuf, c);
}
}
p += l;
@@ -354,29 +332,29 @@ size_t transstr_len(const char *const s, bool untab)
/// @param[in] untab remove tab characters
///
/// @return length of the resulting string, without the NUL byte.
-size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool untab)
+size_t transstr_buf(const char *const s, const ssize_t slen, char *const buf, const size_t buflen,
+ bool untab)
FUNC_ATTR_NONNULL_ALL
{
const char *p = s;
char *buf_p = buf;
- char *const buf_e = buf_p + len - 1;
+ char *const buf_e = buf_p + buflen - 1;
- while (*p != NUL && buf_p < buf_e) {
+ while ((slen < 0 || (p - s) < slen) && *p != NUL && buf_p < buf_e) {
const size_t l = (size_t)utfc_ptr2len(p);
if (l > 1) {
if (buf_p + l > buf_e) {
break; // Exceeded `buf` size.
}
- int pcc[MAX_MCO + 1];
- pcc[0] = utfc_ptr2char(p, &pcc[1]);
- if (vim_isprintc(pcc[0])) {
+ if (vim_isprintc(utf_ptr2char(p))) {
memmove(buf_p, p, l);
buf_p += l;
} else {
- for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ for (size_t off = 0; off < l; off += (size_t)utf_ptr2len(p + off)) {
+ int c = utf_ptr2char(p + off);
char hexbuf[9]; // <up to 6 bytes>NUL
- const size_t hexlen = transchar_hex(hexbuf, pcc[i]);
+ const size_t hexlen = transchar_hex(hexbuf, c);
if (buf_p + hexlen > buf_e) {
break;
}
@@ -388,7 +366,7 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool
} else if (*p == TAB && !untab) {
*buf_p++ = *p++;
} else {
- const char *const tb = (const char *)transchar_byte((uint8_t)(*p++));
+ const char *const tb = transchar_byte((uint8_t)(*p++));
const size_t tb_len = strlen(tb);
if (buf_p + tb_len > buf_e) {
break; // Exceeded `buf` size.
@@ -416,7 +394,7 @@ char *transstr(const char *const s, bool untab)
// multi-byte characters.
const size_t len = transstr_len(s, untab) + 1;
char *const buf = xmalloc(len);
- transstr_buf(s, buf, len, untab);
+ transstr_buf(s, -1, buf, len, untab);
return buf;
}
@@ -431,7 +409,7 @@ size_t kv_transstr(StringBuilder *str, const char *const s, bool untab)
// multi-byte characters.
const size_t len = transstr_len(s, untab);
kv_ensure_space(*str, len + 1);
- transstr_buf(s, str->items + str->size, len + 1, untab);
+ transstr_buf(s, -1, str->items + str->size, len + 1, untab);
str->size += len; // do not include NUL byte
return len;
}
@@ -445,7 +423,6 @@ char *str_foldcase(char *str, int orglen, char *buf, int buflen)
FUNC_ATTR_NONNULL_RET
{
garray_T ga;
- int i;
int len = orglen;
#define GA_CHAR(i) ((char *)ga.ga_data)[i]
@@ -475,7 +452,7 @@ char *str_foldcase(char *str, int orglen, char *buf, int buflen)
}
// Make each character lower case.
- i = 0;
+ int i = 0;
while (STR_CHAR(i) != NUL) {
int c = utf_ptr2char(STR_PTR(i));
int olen = utf_ptr2len(STR_PTR(i));
@@ -531,7 +508,7 @@ char *str_foldcase(char *str, int orglen, char *buf, int buflen)
// Does NOT work for multi-byte characters, c must be <= 255.
// Also doesn't work for the first byte of a multi-byte, "c" must be a
// character!
-static char_u transchar_charbuf[11];
+static uint8_t transchar_charbuf[11];
/// Translate a character into a printable one, leaving printable ASCII intact
///
@@ -542,11 +519,10 @@ static char_u transchar_charbuf[11];
/// @return translated character into a static buffer.
char *transchar(int c)
{
- return (char *)transchar_buf(curbuf, c);
+ return transchar_buf(curbuf, c);
}
-char_u *transchar_buf(const buf_T *buf, int c)
- FUNC_ATTR_NONNULL_ALL
+char *transchar_buf(const buf_T *buf, int c)
{
int i = 0;
if (IS_SPECIAL(c)) {
@@ -558,33 +534,46 @@ char_u *transchar_buf(const buf_T *buf, int c)
}
if ((!chartab_initialized && (c >= ' ' && c <= '~'))
- || ((c <= 0xFF) && vim_isprintc_strict(c))) {
+ || ((c <= 0xFF) && vim_isprintc(c))) {
// printable character
- transchar_charbuf[i] = (char_u)c;
+ transchar_charbuf[i] = (uint8_t)c;
transchar_charbuf[i + 1] = NUL;
} else if (c <= 0xFF) {
- transchar_nonprint(buf, transchar_charbuf + i, c);
+ transchar_nonprint(buf, (char *)transchar_charbuf + i, c);
} else {
transchar_hex((char *)transchar_charbuf + i, c);
}
- return transchar_charbuf;
+ return (char *)transchar_charbuf;
+}
+
+/// Like transchar(), but called with a byte instead of a character.
+///
+/// Checks for an illegal UTF-8 byte. Uses 'fileformat' of the current buffer.
+///
+/// @param[in] c Byte to translate.
+///
+/// @return pointer to translated character in transchar_charbuf.
+char *transchar_byte(const int c)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return transchar_byte_buf(curbuf, c);
}
-/// Like transchar(), but called with a byte instead of a character
+/// Like transchar_buf(), but called with a byte instead of a character.
///
-/// Checks for an illegal UTF-8 byte.
+/// Checks for an illegal UTF-8 byte. Uses 'fileformat' of "buf", unless it is NULL.
///
/// @param[in] c Byte to translate.
///
/// @return pointer to translated character in transchar_charbuf.
-char_u *transchar_byte(const int c)
+char *transchar_byte_buf(const buf_T *buf, const int c)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (c >= 0x80) {
- transchar_nonprint(curbuf, transchar_charbuf, c);
- return transchar_charbuf;
+ transchar_nonprint(buf, (char *)transchar_charbuf, c);
+ return (char *)transchar_charbuf;
}
- return (char_u *)transchar(c);
+ return transchar_buf(buf, c);
}
/// Convert non-printable characters to 2..4 printable ones
@@ -596,13 +585,12 @@ char_u *transchar_byte(const int c)
/// at least 5 bytes (conversion result + NUL).
/// @param[in] c Character to convert. NUL is assumed to be NL according to
/// `:h NL-used-for-NUL`.
-void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c)
- FUNC_ATTR_NONNULL_ALL
+void transchar_nonprint(const buf_T *buf, char *charbuf, int c)
{
if (c == NL) {
// we use newline in place of a NUL
c = NUL;
- } else if ((c == CAR) && (get_fileformat(buf) == EOL_MAC)) {
+ } else if (buf != NULL && c == CAR && get_fileformat(buf) == EOL_MAC) {
// we use CR in place of NL in this case
c = NL;
}
@@ -610,12 +598,12 @@ void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c)
if (dy_flags & DY_UHEX || c > 0x7f) {
// 'display' has "uhex"
- transchar_hex((char *)charbuf, c);
+ transchar_hex(charbuf, c);
} else {
// 0x00 - 0x1f and 0x7f
charbuf[0] = '^';
// DEL displayed as ^?
- charbuf[1] = (char_u)(c ^ 0x40);
+ charbuf[1] = (char)(uint8_t)(c ^ 0x40);
charbuf[2] = NUL;
}
@@ -633,8 +621,8 @@ size_t transchar_hex(char *const buf, const int c)
size_t i = 0;
buf[i++] = '<';
- if (c > 255) {
- if (c > 255 * 256) {
+ if (c > 0xFF) {
+ if (c > 0xFFFF) {
buf[i++] = (char)nr2hex((unsigned)c >> 20);
buf[i++] = (char)nr2hex((unsigned)c >> 16);
}
@@ -648,6 +636,17 @@ size_t transchar_hex(char *const buf, const int c)
return i;
}
+/// Mirror text "str" for right-left displaying.
+/// Only works for single-byte characters (e.g., numbers).
+void rl_mirror_ascii(char *str, char *end)
+{
+ for (char *p1 = str, *p2 = (end ? end : str + strlen(str)) - 1; p1 < p2; p1++, p2--) {
+ char t = *p1;
+ *p1 = *p2;
+ *p2 = t;
+ }
+}
+
/// Convert the lower 4 bits of byte "c" to its hex character
///
/// Lower case letters are used to avoid the confusion of <F1> being 0xf1 or
@@ -732,7 +731,7 @@ int ptr2cells(const char *p_in)
/// @param s
///
/// @return number of character cells.
-int vim_strsize(char *s)
+int vim_strsize(const char *s)
{
return vim_strnsize(s, MAXCOL);
}
@@ -746,7 +745,7 @@ int vim_strsize(char *s)
/// @param len
///
/// @return Number of character cells.
-int vim_strnsize(char *s, int len)
+int vim_strnsize(const char *s, int len)
{
assert(s != NULL);
int size = 0;
@@ -835,8 +834,10 @@ bool vim_iswordp_buf(const char *const p, buf_T *const buf)
return vim_iswordc_buf(c, buf);
}
-/// Check that "c" is a valid file-name character.
+/// Check that "c" is a valid file-name character as specified with the
+/// 'isfname' option.
/// Assume characters above 0x100 are valid (multi-byte).
+/// To be used for commands like "gf".
///
/// @param c character to check
bool vim_isfilec(int c)
@@ -845,6 +846,14 @@ bool vim_isfilec(int c)
return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_FNAME_CHAR));
}
+/// Check if "c" is a valid file-name character, including characters left
+/// out of 'isfname' to make "gf" work, such as comma, space, '@', etc.
+bool vim_is_fname_char(int c)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return vim_isfilec(c) || c == ',' || c == ' ' || c == '@';
+}
+
/// Check that "c" is a valid file-name character or a wildcard character
/// Assume characters above 0x100 are valid (multi-byte).
/// Explicitly interpret ']' as a wildcard character as path_has_wildcard("]")
@@ -861,7 +870,6 @@ bool vim_isfilec_or_wc(int c)
}
/// Check that "c" is a printable character.
-/// Assume characters above 0x100 are printable for double-byte encodings.
///
/// @param c character to check
bool vim_isprintc(int c)
@@ -873,316 +881,19 @@ bool vim_isprintc(int c)
return c > 0 && (g_chartab[c] & CT_PRINT_CHAR);
}
-/// Strict version of vim_isprintc(c), don't return true if "c" is the head
-/// byte of a double-byte character.
-///
-/// @param c character to check
-///
-/// @return true if "c" is a printable character.
-bool vim_isprintc_strict(int c)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (c >= 0x100) {
- return utf_printable(c);
- }
- return c > 0 && (g_chartab[c] & CT_PRINT_CHAR);
-}
-
-/// Check that virtual column "vcol" is in the rightmost column of window "wp".
-///
-/// @param wp window
-/// @param vcol column number
-bool in_win_border(win_T *wp, colnr_T vcol)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
-{
- if (wp->w_width_inner == 0) {
- // there is no border
- return false;
- }
- int width1 = wp->w_width_inner - win_col_off(wp); // width of first line (after line number)
-
- if ((int)vcol < width1 - 1) {
- return false;
- }
-
- if ((int)vcol == width1 - 1) {
- return true;
- }
- int width2 = width1 + win_col_off2(wp); // width of further lines
-
- if (width2 <= 0) {
- return false;
- }
- return (vcol - width1) % width2 == width2 - 1;
-}
-
-/// Get virtual column number of pos.
-/// start: on the first position of this character (TAB, ctrl)
-/// cursor: where the cursor is on this character (first char, except for TAB)
-/// end: on the last position of this character (TAB, ctrl)
-///
-/// This is used very often, keep it fast!
-///
-/// @param wp
-/// @param pos
-/// @param start
-/// @param cursor
-/// @param end
-void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
-{
- char *ptr; // points to current char
- char *posptr; // points to char at pos->col
- int incr;
- int head;
- long *vts = wp->w_buffer->b_p_vts_array;
- int ts = (int)wp->w_buffer->b_p_ts;
-
- colnr_T vcol = 0;
- char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); // start of the line
-
- if (pos->col == MAXCOL) {
- // continue until the NUL
- posptr = NULL;
- } else {
- // In a few cases the position can be beyond the end of the line.
- for (colnr_T i = 0; i < pos->col; i++) {
- if (ptr[i] == NUL) {
- pos->col = i;
- break;
- }
- }
- posptr = ptr + pos->col;
- posptr -= utf_head_off(line, posptr);
- }
-
- chartabsize_T cts;
- init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line);
-
- // This function is used very often, do some speed optimizations.
- // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set
- // and there are no virtual text use a simple loop.
- // Also use this when 'list' is set but tabs take their normal size.
- if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL))
- && !wp->w_p_lbr
- && *get_showbreak_value(wp) == NUL
- && !wp->w_p_bri
- && !cts.cts_has_virt_text) {
- for (;;) {
- head = 0;
- int c = (uint8_t)(*ptr);
-
- // make sure we don't go past the end of the line
- if (c == NUL) {
- // NUL at end of line only takes one column
- incr = 1;
- break;
- }
-
- // A tab gets expanded, depending on the current column
- if (c == TAB) {
- incr = tabstop_padding(vcol, ts, vts);
- } else {
- // For utf-8, if the byte is >= 0x80, need to look at
- // further bytes to find the cell width.
- if (c >= 0x80) {
- incr = utf_ptr2cells(ptr);
- } else {
- incr = g_chartab[c] & CT_CELL_MASK;
- }
-
- // If a double-cell char doesn't fit at the end of a line
- // it wraps to the next line, it's like this char is three
- // cells wide.
- if ((incr == 2)
- && wp->w_p_wrap
- && (MB_BYTE2LEN((uint8_t)(*ptr)) > 1)
- && in_win_border(wp, vcol)) {
- incr++;
- head = 1;
- }
- }
-
- if ((posptr != NULL) && (ptr >= posptr)) {
- // character at pos->col
- break;
- }
-
- vcol += incr;
- MB_PTR_ADV(ptr);
- }
- } else {
- for (;;) {
- // A tab gets expanded, depending on the current column
- // Other things also take up space.
- head = 0;
- incr = win_lbr_chartabsize(&cts, &head);
-
- // make sure we don't go past the end of the line
- if (*cts.cts_ptr == NUL) {
- // NUL at end of line only takes one column
- incr = 1;
- break;
- }
-
- if ((posptr != NULL) && (cts.cts_ptr >= posptr)) {
- // character at pos->col
- break;
- }
-
- cts.cts_vcol += incr;
- MB_PTR_ADV(cts.cts_ptr);
- }
- vcol = cts.cts_vcol;
- ptr = cts.cts_ptr;
- }
- clear_chartabsize_arg(&cts);
-
- if (start != NULL) {
- *start = vcol + head;
- }
-
- if (end != NULL) {
- *end = vcol + incr - 1;
- }
-
- if (cursor != NULL) {
- // cursor is after inserted text
- vcol += cts.cts_cur_text_width;
- if ((*ptr == TAB)
- && (State & MODE_NORMAL)
- && !wp->w_p_list
- && !virtual_active()
- && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) {
- // cursor at end
- *cursor = vcol + incr - 1;
- } else {
- // cursor at start
- *cursor = vcol + head;
- }
- }
-}
-
-/// Get virtual cursor column in the current window, pretending 'list' is off.
-///
-/// @param posp
-///
-/// @retujrn The virtual cursor column.
-colnr_T getvcol_nolist(pos_T *posp)
-{
- int list_save = curwin->w_p_list;
- colnr_T vcol;
-
- curwin->w_p_list = false;
- if (posp->coladd) {
- getvvcol(curwin, posp, NULL, &vcol, NULL);
- } else {
- getvcol(curwin, posp, NULL, &vcol, NULL);
- }
- curwin->w_p_list = list_save;
- return vcol;
-}
-
-/// Get virtual column in virtual mode.
-///
-/// @param wp
-/// @param pos
-/// @param start
-/// @param cursor
-/// @param end
-void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
-{
- colnr_T col;
-
- if (virtual_active()) {
- // For virtual mode, only want one value
- getvcol(wp, pos, &col, NULL, NULL);
-
- colnr_T coladd = pos->coladd;
- colnr_T endadd = 0;
-
- // Cannot put the cursor on part of a wide character.
- char *ptr = ml_get_buf(wp->w_buffer, pos->lnum, false);
-
- if (pos->col < (colnr_T)strlen(ptr)) {
- int c = utf_ptr2char(ptr + pos->col);
- if ((c != TAB) && vim_isprintc(c)) {
- endadd = (colnr_T)(char2cells(c) - 1);
- if (coladd > endadd) {
- // past end of line
- endadd = 0;
- } else {
- coladd = 0;
- }
- }
- }
- col += coladd;
-
- if (start != NULL) {
- *start = col;
- }
-
- if (cursor != NULL) {
- *cursor = col;
- }
-
- if (end != NULL) {
- *end = col + endadd;
- }
- } else {
- getvcol(wp, pos, start, cursor, end);
- }
-}
-
-/// Get the leftmost and rightmost virtual column of pos1 and pos2.
-/// Used for Visual block mode.
-///
-/// @param wp
-/// @param pos1
-/// @param pos2
-/// @param left
-/// @param right
-void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right)
-{
- colnr_T from1;
- colnr_T from2;
- colnr_T to1;
- colnr_T to2;
-
- if (lt(*pos1, *pos2)) {
- getvvcol(wp, pos1, &from1, NULL, &to1);
- getvvcol(wp, pos2, &from2, NULL, &to2);
- } else {
- getvvcol(wp, pos2, &from1, NULL, &to1);
- getvvcol(wp, pos1, &from2, NULL, &to2);
- }
-
- if (from2 < from1) {
- *left = from2;
- } else {
- *left = from1;
- }
-
- if (to2 > to1) {
- if ((*p_sel == 'e') && (from2 - 1 >= to1)) {
- *right = from2 - 1;
- } else {
- *right = to2;
- }
- } else {
- *right = to1;
- }
-}
-
/// skipwhite: skip over ' ' and '\t'.
///
/// @param[in] p String to skip in.
///
/// @return Pointer to character after the skipped whitespace.
-char *skipwhite(const char *const p)
+char *skipwhite(const char *p)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
FUNC_ATTR_NONNULL_RET
{
- return skipwhite_len(p, strlen(p));
+ while (ascii_iswhite(*p)) {
+ p++;
+ }
+ return (char *)p;
}
/// Like `skipwhite`, but skip up to `len` characters.
@@ -1337,7 +1048,7 @@ char *skiptowhite(const char *p)
/// @param p
///
/// @return Pointer to the next whitespace character.
-char *skiptowhite_esc(char *p)
+char *skiptowhite_esc(const char *p)
FUNC_ATTR_PURE
{
while (*p != ' ' && *p != '\t' && *p != NUL) {
@@ -1346,7 +1057,7 @@ char *skiptowhite_esc(char *p)
}
p++;
}
- return p;
+ return (char *)p;
}
/// Skip over text until '\n' or NUL.
@@ -1431,14 +1142,14 @@ long getdigits_long(char **pp, bool strict, long def)
/// Gets a int32_t number from a string.
///
/// @see getdigits
-int32_t getdigits_int32(char **pp, bool strict, long def)
+int32_t getdigits_int32(char **pp, bool strict, int32_t def)
{
intmax_t number = getdigits(pp, strict, def);
-#if SIZEOF_INTMAX_T > SIZEOF_INT32_T
+#if SIZEOF_INTMAX_T > 4
if (strict) {
assert(number >= INT32_MIN && number <= INT32_MAX);
} else if (!(number >= INT32_MIN && number <= INT32_MAX)) {
- return (int32_t)def;
+ return def;
}
#endif
return (int32_t)number;
@@ -1488,9 +1199,10 @@ bool vim_isblankline(char *lbuf)
/// @param strict If true, fail if the number has unexpected trailing
/// alphanumeric chars: *len is set to 0 and nothing else is
/// returned.
+/// @param overflow When not NULL, set to true for overflow.
void vim_str2nr(const char *const start, int *const prep, int *const len, const int what,
varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen,
- const bool strict)
+ const bool strict, bool *const overflow)
FUNC_ATTR_NONNULL_ARG(1)
{
const char *ptr = start;
@@ -1591,7 +1303,6 @@ void vim_str2nr(const char *const start, int *const prep, int *const len, const
// Do the conversion manually to avoid sscanf() quirks.
abort(); // Should’ve used goto earlier.
- // -V:PARSE_NUMBER:560
#define PARSE_NUMBER(base, cond, conv) \
do { \
const char *const after_prefix = ptr; \
@@ -1614,6 +1325,9 @@ void vim_str2nr(const char *const start, int *const prep, int *const len, const
un = (base) * un + digit; \
} else { \
un = UVARNUMBER_MAX; \
+ if (overflow != NULL) { \
+ *overflow = true; \
+ } \
} \
ptr++; \
} \
@@ -1652,12 +1366,18 @@ vim_str2nr_proceed:
// avoid ubsan error for overflow
if (un > VARNUMBER_MAX) {
*nptr = VARNUMBER_MIN;
+ if (overflow != NULL) {
+ *overflow = true;
+ }
} else {
*nptr = -(varnumber_T)un;
}
} else {
if (un > VARNUMBER_MAX) {
un = VARNUMBER_MAX;
+ if (overflow != NULL) {
+ *overflow = true;
+ }
}
*nptr = (varnumber_T)un;
}
diff --git a/src/nvim/charset.h b/src/nvim/charset.h
index e1ef06ef1d..cfab0f8517 100644
--- a/src/nvim/charset.h
+++ b/src/nvim/charset.h
@@ -1,14 +1,14 @@
-#ifndef NVIM_CHARSET_H
-#define NVIM_CHARSET_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
/// Return the folded-case equivalent of the given character
///
@@ -54,6 +54,5 @@ static inline bool vim_isbreak(int c)
/// Used very often if 'linebreak' is set
static inline bool vim_isbreak(int c)
{
- return breakat_flags[(char_u)c];
+ return breakat_flags[(uint8_t)c];
}
-#endif // NVIM_CHARSET_H
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index be815151ef..367b86ec55 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -1,21 +1,17 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// cmdexpand.c: functions for command-line completion
#include <assert.h>
-#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
-#include "auto/config.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -30,30 +26,33 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/hashtab.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/locale.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
@@ -63,15 +62,12 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
-/// Type used by ExpandGeneric()
-typedef char *(*CompleteListItemGetter)(expand_T *, int);
-
/// Type used by call_user_expand_func
typedef void *(*user_expand_func_T)(const char *, int, typval_T *);
@@ -107,8 +103,11 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp)
&& xp->xp_context != EXPAND_HELP
&& xp->xp_context != EXPAND_LUA
&& xp->xp_context != EXPAND_OLD_SETTING
+ && xp->xp_context != EXPAND_STRING_SETTING
+ && xp->xp_context != EXPAND_SETTING_SUBTRACT
&& xp->xp_context != EXPAND_OWNSYNTAX
&& xp->xp_context != EXPAND_PACKADD
+ && xp->xp_context != EXPAND_RUNTIME
&& xp->xp_context != EXPAND_SHELLCMD
&& xp->xp_context != EXPAND_TAGS
&& xp->xp_context != EXPAND_TAGS_LISTFILES
@@ -156,8 +155,9 @@ static void wildescape(expand_T *xp, const char *str, int numfiles, char **files
// and wildmatch characters, except '~'.
for (int i = 0; i < numfiles; i++) {
// for ":set path=" we need to escape spaces twice
- if (xp->xp_backslash == XP_BS_THREE) {
- p = vim_strsave_escaped(files[i], " ");
+ if (xp->xp_backslash & XP_BS_THREE) {
+ char *pat = (xp->xp_backslash & XP_BS_COMMA) ? " ," : " ";
+ p = vim_strsave_escaped(files[i], pat);
xfree(files[i]);
files[i] = p;
#if defined(BACKSLASH_IN_FILENAME)
@@ -165,6 +165,12 @@ static void wildescape(expand_T *xp, const char *str, int numfiles, char **files
xfree(files[i]);
files[i] = p;
#endif
+ } else if (xp->xp_backslash & XP_BS_COMMA) {
+ if (vim_strchr(files[i], ',') != NULL) {
+ p = vim_strsave_escaped(files[i], ",");
+ xfree(files[i]);
+ files[i] = p;
+ }
}
#ifdef BACKSLASH_IN_FILENAME
p = vim_strsave_fnameescape(files[i], vse_what);
@@ -221,10 +227,7 @@ static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, in
int nextwild(expand_T *xp, int type, int options, bool escape)
{
CmdlineInfo *const ccline = get_cmdline_info();
- int i, j;
- char *p1;
char *p2;
- int difflen;
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
@@ -247,7 +250,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
ui_flush();
}
- i = (int)(xp->xp_pattern - ccline->cmdbuff);
+ int i = (int)(xp->xp_pattern - ccline->cmdbuff);
assert(ccline->cmdpos >= i);
xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i;
@@ -257,9 +260,10 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
// Get next/previous match for a previous expanded pattern.
p2 = ExpandOne(xp, NULL, NULL, 0, type);
} else {
+ char *p1;
if (cmdline_fuzzy_completion_supported(xp)) {
// If fuzzy matching, don't modify the search string
- p1 = xstrdup(xp->xp_pattern);
+ p1 = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
} else {
p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
}
@@ -281,6 +285,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
// Longest match: make sure it is not shorter, happens with :help.
if (p2 != NULL && type == WILD_LONGEST) {
+ int j;
for (j = 0; (size_t)j < xp->xp_pattern_len; j++) {
if (ccline->cmdbuff[i + j] == '*'
|| ccline->cmdbuff[i + j] == '?') {
@@ -294,7 +299,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
}
if (p2 != NULL && !got_int) {
- difflen = (int)strlen(p2) - (int)(xp->xp_pattern_len);
+ int difflen = (int)strlen(p2) - (int)(xp->xp_pattern_len);
if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) {
realloc_cmdbuff(ccline->cmdlen + difflen + 4);
xp->xp_pattern = ccline->cmdbuff + i;
@@ -440,11 +445,8 @@ static int wildmenu_match_len(expand_T *xp, char *s)
/// @param matches list of matches
static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail)
{
- int row;
- char *buf;
int len;
int clen; // length in screen cells
- int fillchar;
int attr;
int i;
bool highlight = true;
@@ -453,15 +455,13 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
char *selend = NULL;
static int first_match = 0;
bool add_left = false;
- char *s;
- int emenu;
int l;
if (matches == NULL) { // interrupted completion?
return;
}
- buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1);
+ char *buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1);
if (match == -1) { // don't show match but original text
match = 0;
@@ -484,13 +484,13 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
clen += 2;
}
// jumping right, put match at the left
- if ((long)clen > Columns) {
+ if (clen > Columns) {
first_match = match;
// if showing the last match, we can add some on the left
clen = 2;
for (i = match; i < num_matches; i++) {
clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2;
- if ((long)clen >= Columns) {
+ if (clen >= Columns) {
break;
}
}
@@ -502,14 +502,14 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
if (add_left) {
while (first_match > 0) {
clen += wildmenu_match_len(xp, SHOW_MATCH(first_match - 1)) + 2;
- if ((long)clen >= Columns) {
+ if (clen >= Columns) {
break;
}
first_match--;
}
}
- fillchar = fillchar_status(&attr, curwin);
+ int fillchar = fillchar_status(&attr, curwin);
if (first_match == 0) {
*buf = NUL;
@@ -527,10 +527,9 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
selstart_col = clen;
}
- s = SHOW_MATCH(i);
+ char *s = SHOW_MATCH(i);
// Check for menu separators - replace with '|'
- emenu = (xp->xp_context == EXPAND_MENUS
- || xp->xp_context == EXPAND_MENUNAMES);
+ int emenu = (xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES);
if (emenu && menu_is_separator(s)) {
STRCPY(buf + len, transchar('|'));
l = (int)strlen(buf + len);
@@ -569,7 +568,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
buf[len] = NUL;
- row = cmdline_row - 1;
+ int row = cmdline_row - 1;
if (row >= 0) {
if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) {
if (msg_scrolled > 0) {
@@ -600,17 +599,17 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
// Tricky: wildmenu can be drawn either over a status line, or at empty
// scrolled space in the message output
- ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED)
- ? &msg_grid_adj : &default_grid;
+ grid_line_start((wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_grid, row);
- grid_puts(grid, buf, row, 0, attr);
+ grid_line_puts(0, buf, -1, attr);
if (selstart != NULL && highlight) {
*selend = NUL;
- grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM));
+ grid_line_puts(selstart_col, selstart, -1, HL_ATTR(HLF_WM));
}
- grid_fill(grid, row, row + 1, clen, Columns,
- fillchar, fillchar, attr);
+ grid_line_fill(clen, Columns, fillchar, attr);
+
+ grid_line_flush();
}
win_redraw_last_status(topframe);
@@ -618,14 +617,14 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
}
/// Get the next or prev cmdline completion match. The index of the match is set
-/// in "p_findex"
-static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char *orig_save)
+/// in "xp->xp_selected"
+static char *get_next_or_prev_match(int mode, expand_T *xp)
{
if (xp->xp_numfiles <= 0) {
return NULL;
}
- int findex = *p_findex;
+ int findex = xp->xp_selected;
if (mode == WILD_PREV) {
if (findex == -1) {
@@ -679,14 +678,14 @@ static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char
// When wrapping around, return the original string, set findex to -1.
if (findex < 0) {
- if (orig_save == NULL) {
+ if (xp->xp_orig == NULL) {
findex = xp->xp_numfiles - 1;
} else {
findex = -1;
}
}
if (findex >= xp->xp_numfiles) {
- if (orig_save == NULL) {
+ if (xp->xp_orig == NULL) {
findex = 0;
} else {
findex = -1;
@@ -698,9 +697,9 @@ static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char
} else if (p_wmnu) {
redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail);
}
- *p_findex = findex;
+ xp->xp_selected = findex;
- return xstrdup(findex == -1 ? orig_save : xp->xp_files[findex]);
+ return xstrdup(findex == -1 ? xp->xp_orig : xp->xp_files[findex]);
}
/// Start the command-line expansion and get the matches.
@@ -798,7 +797,7 @@ static char *find_longest_match(expand_T *xp, int options)
}
}
- return xstrndup(xp->xp_files[0], len);
+ return xmemdupz(xp->xp_files[0], len);
}
/// Do wildcard expansion on the string "str".
@@ -807,8 +806,8 @@ static char *find_longest_match(expand_T *xp, int options)
/// Return NULL for failure.
///
/// "orig" is the originally expanded string, copied to allocated memory. It
-/// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or
-/// WILD_PREV "orig" should be NULL.
+/// should either be kept in "xp->xp_orig" or freed. When "mode" is WILD_NEXT
+/// or WILD_PREV "orig" should be NULL.
///
/// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode"
/// is WILD_EXPAND_FREE or WILD_ALL.
@@ -843,46 +842,43 @@ static char *find_longest_match(expand_T *xp, int options)
char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
{
char *ss = NULL;
- static int findex;
- static char *orig_save = NULL; // kept value of orig
int orig_saved = false;
- int i;
// first handle the case of using an old match
if (mode == WILD_NEXT || mode == WILD_PREV
|| mode == WILD_PAGEUP || mode == WILD_PAGEDOWN
|| mode == WILD_PUM_WANT) {
- return get_next_or_prev_match(mode, xp, &findex, orig_save);
+ return get_next_or_prev_match(mode, xp);
}
if (mode == WILD_CANCEL) {
- ss = xstrdup(orig_save ? orig_save : "");
+ ss = xstrdup(xp->xp_orig ? xp->xp_orig : "");
} else if (mode == WILD_APPLY) {
- ss = xstrdup(findex == -1
- ? (orig_save ? orig_save : "")
- : xp->xp_files[findex]);
+ ss = xstrdup(xp->xp_selected == -1
+ ? (xp->xp_orig ? xp->xp_orig : "")
+ : xp->xp_files[xp->xp_selected]);
}
// free old names
if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) {
FreeWild(xp->xp_numfiles, xp->xp_files);
xp->xp_numfiles = -1;
- XFREE_CLEAR(orig_save);
+ XFREE_CLEAR(xp->xp_orig);
// The entries from xp_files may be used in the PUM, remove it.
if (compl_match_array != NULL) {
cmdline_pum_remove();
}
}
- findex = 0;
+ xp->xp_selected = 0;
if (mode == WILD_FREE) { // only release file name
return NULL;
}
if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) {
- xfree(orig_save);
- orig_save = orig;
+ xfree(xp->xp_orig);
+ xp->xp_orig = orig;
orig_saved = true;
ss = ExpandOne_start(mode, xp, str, options);
@@ -891,23 +887,38 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
// Find longest common part
if (mode == WILD_LONGEST && xp->xp_numfiles > 0) {
ss = find_longest_match(xp, options);
- findex = -1; // next p_wc gets first one
+ xp->xp_selected = -1; // next p_wc gets first one
}
// Concatenate all matching names. Unless interrupted, this can be slow
// and the result probably won't be used.
- // TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne)
if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) {
size_t len = 0;
- for (i = 0; i < xp->xp_numfiles; i++) {
+ for (int i = 0; i < xp->xp_numfiles; i++) {
+ if (i > 0) {
+ if (xp->xp_prefix == XP_PREFIX_NO) {
+ len += 2; // prefix "no"
+ } else if (xp->xp_prefix == XP_PREFIX_INV) {
+ len += 3; // prefix "inv"
+ }
+ }
len += strlen(xp->xp_files[i]) + 1;
}
ss = xmalloc(len);
*ss = NUL;
- for (i = 0; i < xp->xp_numfiles; i++) {
- STRCAT(ss, xp->xp_files[i]);
+ char *ssp = ss;
+ for (int i = 0; i < xp->xp_numfiles; i++) {
+ if (i > 0) {
+ if (xp->xp_prefix == XP_PREFIX_NO) {
+ ssp = xstpcpy(ssp, "no");
+ } else if (xp->xp_prefix == XP_PREFIX_INV) {
+ ssp = xstpcpy(ssp, "inv");
+ }
+ }
+ ssp = xstpcpy(ssp, xp->xp_files[i]);
+
if (i != xp->xp_numfiles - 1) {
- STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " ");
+ ssp = xstpcpy(ssp, (options & WILD_USE_NL) ? "\n" : " ");
}
}
}
@@ -916,7 +927,7 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
ExpandCleanup(xp);
}
- // Free "orig" if it wasn't stored in "orig_save".
+ // Free "orig" if it wasn't stored in "xp->xp_orig".
if (!orig_saved) {
xfree(orig);
}
@@ -930,6 +941,7 @@ void ExpandInit(expand_T *xp)
{
CLEAR_POINTER(xp);
xp->xp_backslash = XP_BS_NONE;
+ xp->xp_prefix = XP_PREFIX_NONE;
xp->xp_numfiles = -1;
}
@@ -940,6 +952,7 @@ void ExpandCleanup(expand_T *xp)
FreeWild(xp->xp_numfiles, xp->xp_files);
xp->xp_numfiles = -1;
}
+ XFREE_CLEAR(xp->xp_orig);
}
/// Display one line of completion matches. Multiple matches are displayed in
@@ -959,12 +972,12 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in
int lastlen = 999;
for (int j = linenr; j < numMatches; j += lines) {
if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
- msg_outtrans_attr(matches[j], HL_ATTR(HLF_D));
+ msg_outtrans(matches[j], HL_ATTR(HLF_D));
p = matches[j] + strlen(matches[j]) + 1;
msg_advance(maxlen + 1);
- msg_puts((const char *)p);
+ msg_puts(p);
msg_advance(maxlen + 3);
- msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D));
+ msg_outtrans_long(p + 2, HL_ATTR(HLF_D));
break;
}
for (int i = maxlen - lastlen; --i >= 0;) {
@@ -1001,7 +1014,7 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in
isdir = false;
p = SHOW_MATCH(j);
}
- lastlen = msg_outtrans_attr(p, isdir ? dir_attr : 0);
+ lastlen = msg_outtrans(p, isdir ? dir_attr : 0);
}
if (msg_col > 0) { // when not wrapped around
msg_clr_eos();
@@ -1017,7 +1030,7 @@ int showmatches(expand_T *xp, int wildmenu)
CmdlineInfo *const ccline = get_cmdline_info();
int numMatches;
char **matches;
- int i, j;
+ int j;
int maxlen;
int lines;
int columns;
@@ -1026,8 +1039,8 @@ int showmatches(expand_T *xp, int wildmenu)
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
- i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos,
- &numMatches, &matches);
+ int i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos,
+ &numMatches, &matches);
showtail = expand_showtail(xp);
if (i != EXPAND_OK) {
return i;
@@ -1065,7 +1078,7 @@ int showmatches(expand_T *xp, int wildmenu)
} else {
// find the length of the longest file name
maxlen = 0;
- for (i = 0; i < numMatches; i++) {
+ for (int i = 0; i < numMatches; i++) {
if (!showtail && (xp->xp_context == EXPAND_FILES
|| xp->xp_context == EXPAND_SHELLCMD
|| xp->xp_context == EXPAND_BUFFERS)) {
@@ -1101,7 +1114,7 @@ int showmatches(expand_T *xp, int wildmenu)
}
// list the files line by line
- for (i = 0; i < lines; i++) {
+ for (int i = 0; i < lines; i++) {
showmatches_oneline(xp, matches, numMatches, lines, i, maxlen, showtail, attr);
if (got_int) {
got_int = false;
@@ -1125,11 +1138,10 @@ int showmatches(expand_T *xp, int wildmenu)
/// Return the tail of file name path "s", ignoring a trailing "/".
static char *showmatches_gettail(char *s, bool eager)
{
- char *p;
char *t = s;
bool had_sep = false;
- for (p = s; *p != NUL;) {
+ for (char *p = s; *p != NUL;) {
if (vim_ispathsep(*p)
#ifdef BACKSLASH_IN_FILENAME
&& !rem_backslash(p)
@@ -1154,9 +1166,6 @@ static char *showmatches_gettail(char *s, bool eager)
/// returned.
static bool expand_showtail(expand_T *xp)
{
- char *s;
- char *end;
-
// When not completing file names a "/" may mean something different.
if (xp->xp_context != EXPAND_FILES
&& xp->xp_context != EXPAND_SHELLCMD
@@ -1164,12 +1173,12 @@ static bool expand_showtail(expand_T *xp)
return false;
}
- end = path_tail(xp->xp_pattern);
+ char *end = path_tail(xp->xp_pattern);
if (end == xp->xp_pattern) { // there is no path separator
return false;
}
- for (s = xp->xp_pattern; s < end; s++) {
+ for (char *s = xp->xp_pattern; s < end; s++) {
// Skip escaped wildcards. Only when the backslash is not a path
// separator, on DOS the '*' "path\*\file" must not be skipped.
if (rem_backslash(s)) {
@@ -1193,10 +1202,6 @@ char *addstar(char *fname, size_t len, int context)
FUNC_ATTR_NONNULL_RET
{
char *retval;
- size_t i, j;
- size_t new_len;
- char *tail;
- int ends_in_star;
if (context != EXPAND_FILES
&& context != EXPAND_FILES_IN_PATH
@@ -1209,18 +1214,20 @@ char *addstar(char *fname, size_t len, int context)
// For help tags the translation is done in find_help_tags().
// For a tag pattern starting with "/" no translation is needed.
if (context == EXPAND_HELP
- || context == EXPAND_CHECKHEALTH
|| context == EXPAND_COLORS
|| context == EXPAND_COMPILER
|| context == EXPAND_OWNSYNTAX
|| context == EXPAND_FILETYPE
|| context == EXPAND_PACKADD
+ || context == EXPAND_RUNTIME
|| ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS)
- && fname[0] == '/')) {
+ && fname[0] == '/')
+ || context == EXPAND_CHECKHEALTH
+ || context == EXPAND_LUA) {
retval = xstrnsave(fname, len);
} else {
- new_len = len + 2; // +2 for '^' at start, NUL at end
- for (i = 0; i < len; i++) {
+ size_t new_len = len + 2; // +2 for '^' at start, NUL at end
+ for (size_t i = 0; i < len; i++) {
if (fname[i] == '*' || fname[i] == '~') {
new_len++; // '*' needs to be replaced by ".*"
// '~' needs to be replaced by "\~"
@@ -1239,8 +1246,8 @@ char *addstar(char *fname, size_t len, int context)
retval = xmalloc(new_len);
{
retval[0] = '^';
- j = 1;
- for (i = 0; i < len; i++, j++) {
+ size_t j = 1;
+ for (size_t i = 0; i < len; i++, j++) {
// Skip backslash. But why? At least keep it for custom
// expansion.
if (context != EXPAND_USER_DEFINED
@@ -1287,8 +1294,8 @@ char *addstar(char *fname, size_t len, int context)
// $ could be anywhere in the tail.
// ` could be anywhere in the file name.
// When the name ends in '$' don't add a star, remove the '$'.
- tail = path_tail(retval);
- ends_in_star = (len > 0 && retval[len - 1] == '*');
+ char *tail = path_tail(retval);
+ int ends_in_star = (len > 0 && retval[len - 1] == '*');
#ifndef BACKSLASH_IN_FILENAME
for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) {
if (retval[k] != '\\') {
@@ -1329,8 +1336,8 @@ char *addstar(char *fname, size_t len, int context)
/// EXPAND_FILES After command with EX_XFILE set, or after setting
/// with P_EXPAND set. eg :e ^I, :w>>^I
/// EXPAND_DIRECTORIES In some cases this is used instead of the latter
-/// when we know only directories are of interest. eg
-/// :set dir=^I
+/// when we know only directories are of interest.
+/// E.g. :set dir=^I and :cd ^I
/// EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd".
/// EXPAND_SETTINGS Complete variable names. eg :set d^I
/// EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I
@@ -1373,7 +1380,6 @@ void set_expand_context(expand_T *xp)
static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, int *complp)
{
const char *p = NULL;
- size_t len = 0;
const bool fuzzy = cmdline_fuzzy_complete(cmd);
// Isolate the command and search for it in the command table.
@@ -1408,7 +1414,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in
if (p == cmd && vim_strchr("@*!=><&~#", (uint8_t)(*p)) != NULL) {
p++;
}
- len = (size_t)(p - cmd);
+ size_t len = (size_t)(p - cmd);
if (len == 0) {
xp->xp_context = EXPAND_UNSUCCESSFUL;
@@ -1440,7 +1446,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in
p = cmd + 1;
} else if (cmd[0] >= 'A' && cmd[0] <= 'Z') {
eap->cmd = (char *)cmd;
- p = (const char *)find_ucmd(eap, (char *)p, NULL, xp, complp);
+ p = find_ucmd(eap, (char *)p, NULL, xp, complp);
if (p == NULL) {
eap->cmdidx = CMD_SIZE; // Ambiguous user command.
}
@@ -1466,7 +1472,7 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use
// Allow spaces within back-quotes to count as part of the argument
// being expanded.
xp->xp_pattern = skipwhite(arg);
- const char *p = (const char *)xp->xp_pattern;
+ const char *p = xp->xp_pattern;
while (*p != NUL) {
int c = utf_ptr2char(p);
if (c == '\\' && p[1] != NUL) {
@@ -1519,7 +1525,7 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use
// Check for environment variable.
if (*xp->xp_pattern == '$') {
- for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) {
+ for (p = xp->xp_pattern + 1; *p != NUL; p++) {
if (!vim_isIDc((uint8_t)(*p))) {
break;
}
@@ -1535,30 +1541,43 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use
}
// Check for user names.
if (*xp->xp_pattern == '~') {
- for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {}
+ for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {}
// Complete ~user only if it partially matches a user name.
// A full match ~user<Tab> will be replaced by user's home
// directory i.e. something like ~user<Tab> -> /home/user/
- if (*p == NUL && p > (const char *)xp->xp_pattern + 1
- && match_user(xp->xp_pattern + 1) >= 1) {
+ if (*p == NUL && p > xp->xp_pattern + 1 && match_user(xp->xp_pattern + 1) >= 1) {
xp->xp_context = EXPAND_USER;
xp->xp_pattern++;
}
}
}
+/// Set the completion context for the "++opt=arg" argument. Always returns NULL.
+static const char *set_context_in_argopt(expand_T *xp, const char *arg)
+{
+ char *p = vim_strchr(arg, '=');
+ if (p == NULL) {
+ xp->xp_pattern = (char *)arg;
+ } else {
+ xp->xp_pattern = p + 1;
+ }
+
+ xp->xp_context = EXPAND_ARGOPT;
+ return NULL;
+}
+
/// Set the completion context for the :filter command. Returns a pointer to the
/// next command after the :filter command.
static const char *set_context_in_filter_cmd(expand_T *xp, const char *arg)
{
if (*arg != NUL) {
- arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL);
+ arg = skip_vimgrep_pat((char *)arg, NULL, NULL);
}
if (arg == NULL || *arg == NUL) {
xp->xp_context = EXPAND_NOTHING;
return NULL;
}
- return (const char *)skipwhite(arg);
+ return skipwhite(arg);
}
/// Set the completion context for the :match command. Returns a pointer to the
@@ -1568,13 +1587,13 @@ static const char *set_context_in_match_cmd(expand_T *xp, const char *arg)
if (*arg == NUL || !ends_excmd(*arg)) {
// also complete "None"
set_context_in_echohl_cmd(xp, arg);
- arg = (const char *)skipwhite(skiptowhite(arg));
+ arg = skipwhite(skiptowhite(arg));
if (*arg != NUL) {
xp->xp_context = EXPAND_NOTHING;
- arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset());
+ arg = skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset());
}
}
- return (const char *)find_nextcmd(arg);
+ return find_nextcmd(arg);
}
/// Returns a pointer to the next command after a :global or a :v command.
@@ -1607,7 +1626,7 @@ static const char *find_cmd_after_substitute_cmd(const char *arg)
if (delim) {
// Skip "from" part.
arg++;
- arg = (const char *)skip_regexp((char *)arg, delim, magic_isset());
+ arg = skip_regexp((char *)arg, delim, magic_isset());
if (arg[0] != NUL && arg[0] == delim) {
// Skip "to" part.
@@ -1639,7 +1658,7 @@ static const char *find_cmd_after_substitute_cmd(const char *arg)
static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg)
{
// Skip count.
- arg = (const char *)skipwhite(skipdigits(arg));
+ arg = skipwhite(skipdigits(arg));
if (*arg != '/') {
return NULL;
}
@@ -1651,7 +1670,7 @@ static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg)
}
}
if (*arg) {
- arg = (const char *)skipwhite(arg + 1);
+ arg = skipwhite(arg + 1);
// Check for trailing illegal characters.
if (*arg == NUL || strchr("|\"\n", *arg) == NULL) {
@@ -1668,7 +1687,7 @@ static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg)
static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg)
{
while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) {
- arg = (const char *)xp->xp_pattern + 1;
+ arg = xp->xp_pattern + 1;
}
xp->xp_context = EXPAND_USER_VARS;
@@ -1685,7 +1704,7 @@ static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg)
/// Set the completion context for the :language command. Always returns NULL.
static const char *set_context_in_lang_cmd(expand_T *xp, const char *arg)
{
- const char *p = (const char *)skiptowhite(arg);
+ const char *p = skiptowhite(arg);
if (*p == NUL) {
xp->xp_context = EXPAND_LANGUAGE;
xp->xp_pattern = (char *)arg;
@@ -1878,11 +1897,11 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_dsplit:
return find_cmd_after_isearch_cmd(xp, arg);
case CMD_autocmd:
- return (const char *)set_context_in_autocmd(xp, (char *)arg, false);
+ return set_context_in_autocmd(xp, (char *)arg, false);
case CMD_doautocmd:
case CMD_doautoall:
- return (const char *)set_context_in_autocmd(xp, (char *)arg, true);
+ return set_context_in_autocmd(xp, (char *)arg, true);
case CMD_set:
set_context_in_set_cmd(xp, (char *)arg, 0);
break;
@@ -1959,7 +1978,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_bwipeout:
case CMD_bunload:
while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) {
- arg = (const char *)xp->xp_pattern + 1;
+ arg = xp->xp_pattern + 1;
}
FALLTHROUGH;
case CMD_buffer:
@@ -1998,8 +2017,8 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_snoremap:
case CMD_xmap:
case CMD_xnoremap:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
- false, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
+ false, cmdidx);
case CMD_unmap:
case CMD_nunmap:
case CMD_vunmap:
@@ -2009,8 +2028,8 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_lunmap:
case CMD_sunmap:
case CMD_xunmap:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
- true, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
+ true, cmdidx);
case CMD_mapclear:
case CMD_nmapclear:
case CMD_vmapclear:
@@ -2030,13 +2049,13 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_cnoreabbrev:
case CMD_iabbrev:
case CMD_inoreabbrev:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
- false, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
+ false, cmdidx);
case CMD_unabbreviate:
case CMD_cunabbrev:
case CMD_iunabbrev:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
- true, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
+ true, cmdidx);
case CMD_menu:
case CMD_noremenu:
case CMD_unmenu:
@@ -2065,7 +2084,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_tunmenu:
case CMD_popup:
case CMD_emenu:
- return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
+ return set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
case CMD_colorscheme:
xp->xp_context = EXPAND_COLORS;
@@ -2092,10 +2111,13 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
xp->xp_pattern = (char *)arg;
break;
-#ifdef HAVE_WORKING_LIBINTL
+ case CMD_runtime:
+ set_context_in_runtime_cmd(xp, arg);
+ break;
+
case CMD_language:
return set_context_in_lang_cmd(xp, arg);
-#endif
+
case CMD_profile:
set_context_in_profile_cmd(xp, arg);
break;
@@ -2103,10 +2125,6 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
xp->xp_context = EXPAND_CHECKHEALTH;
xp->xp_pattern = (char *)arg;
break;
- case CMD_behave:
- xp->xp_context = EXPAND_BEHAVE;
- xp->xp_pattern = (char *)arg;
- break;
case CMD_messages:
xp->xp_context = EXPAND_MESSAGES;
@@ -2124,7 +2142,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_argdelete:
while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) {
- arg = (const char *)(xp->xp_pattern + 1);
+ arg = (xp->xp_pattern + 1);
}
xp->xp_context = EXPAND_ARGLIST;
xp->xp_pattern = (char *)arg;
@@ -2139,6 +2157,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
return set_context_in_scriptnames_cmd(xp, arg);
case CMD_lua:
+ case CMD_equal:
xp->xp_context = EXPAND_LUA;
break;
@@ -2183,7 +2202,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
}
// 3. skip over a range specifier of the form: addr [,addr] [;addr] ..
- cmd = (const char *)skip_range(cmd, &xp->xp_context);
+ cmd = skip_range(cmd, &xp->xp_context);
xp->xp_pattern = (char *)cmd;
if (*cmd == NUL) {
return NULL;
@@ -2215,15 +2234,25 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
ea.argt = excmd_get_argt(ea.cmdidx);
}
- const char *arg = (const char *)skipwhite(p);
+ const char *arg = skipwhite(p);
- // Skip over ++argopt argument
- if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) {
- p = arg;
- while (*p && !ascii_isspace(*p)) {
- MB_PTR_ADV(p);
+ // Does command allow "++argopt" argument?
+ if (ea.argt & EX_ARGOPT) {
+ while (*arg != NUL && strncmp(arg, "++", 2) == 0) {
+ p = arg + 2;
+ while (*p && !ascii_isspace(*p)) {
+ MB_PTR_ADV(p);
+ }
+
+ // Still touching the command after "++"?
+ if (*p == NUL) {
+ if (ea.argt & EX_ARGOPT) {
+ return set_context_in_argopt(xp, arg + 2);
+ }
+ }
+
+ arg = skipwhite(p);
}
- arg = (const char *)skipwhite(p);
}
if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) {
@@ -2231,7 +2260,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
if (*++arg == '>') {
arg++;
}
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
} else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter
arg++;
usefilter = true;
@@ -2250,14 +2279,14 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
while (*arg == *cmd) { // allow any number of '>' or '<'
arg++;
}
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
}
// Does command allow "+command"?
if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') {
// Check if we're in the +command
p = arg + 1;
- arg = (const char *)skip_cmd_arg((char *)arg, false);
+ arg = skip_cmd_arg((char *)arg, false);
// Still touching the command after '+'?
if (*arg == NUL) {
@@ -2265,7 +2294,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
}
// Skip space(s) after +command to get to the real argument.
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
}
// Check for '|' to separate commands and '"' to start comments.
@@ -2341,7 +2370,7 @@ void set_cmd_context(expand_T *xp, char *str, int len, int col, int use_ccline)
old_char = str[col];
}
str[col] = NUL;
- const char *nextcomm = (const char *)str;
+ const char *nextcomm = str;
if (use_ccline && ccline->cmdfirstc == '=') {
// pass CMD_SIZE because there is no real command
@@ -2428,15 +2457,23 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
pat = xstrdup(pat);
for (int i = 0; pat[i]; i++) {
if (pat[i] == '\\') {
- if (xp->xp_backslash == XP_BS_THREE
+ if (xp->xp_backslash & XP_BS_THREE
&& pat[i + 1] == '\\'
&& pat[i + 2] == '\\'
&& pat[i + 3] == ' ') {
STRMOVE(pat + i, pat + i + 3);
- }
- if (xp->xp_backslash == XP_BS_ONE
- && pat[i + 1] == ' ') {
+ } else if (xp->xp_backslash & XP_BS_ONE
+ && pat[i + 1] == ' ') {
STRMOVE(pat + i, pat + i + 1);
+ } else if ((xp->xp_backslash & XP_BS_COMMA)
+ && pat[i + 1] == '\\'
+ && pat[i + 2] == ',') {
+ STRMOVE(pat + i, pat + i + 2);
+#ifdef BACKSLASH_IN_FILENAME
+ } else if ((xp->xp_backslash & XP_BS_COMMA)
+ && pat[i + 1] == ',') {
+ STRMOVE(pat + i, pat + i + 1);
+#endif
}
}
}
@@ -2477,19 +2514,6 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
}
/// Function given to ExpandGeneric() to obtain the possible arguments of the
-/// ":behave {mswin,xterm}" command.
-static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
-{
- if (idx == 0) {
- return "mswin";
- }
- if (idx == 1) {
- return "xterm";
- }
- return NULL;
-}
-
-/// Function given to ExpandGeneric() to obtain the possible arguments of the
/// ":breakadd {expr, file, func, here}" command.
/// ":breakdel {func, file, here}" command.
static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
@@ -2523,7 +2547,7 @@ static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
return NULL;
}
- scriptitem_T *si = &SCRIPT_ITEM(idx + 1);
+ scriptitem_T *si = SCRIPT_ITEM(idx + 1);
home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, true);
return NameBuff;
}
@@ -2583,7 +2607,6 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches
int escaped;
} tab[] = {
{ EXPAND_COMMANDS, get_command_name, false, true },
- { EXPAND_BEHAVE, get_behave_arg, true, true },
{ EXPAND_MAPCLEAR, get_mapclear_arg, true, true },
{ EXPAND_MESSAGES, get_messages_arg, true, true },
{ EXPAND_HISTORY, get_history_arg, true, true },
@@ -2600,15 +2623,13 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches
{ EXPAND_MENUNAMES, get_menu_names, false, true },
{ EXPAND_SYNTAX, get_syntax_name, true, true },
{ EXPAND_SYNTIME, get_syntime_arg, true, true },
- { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, false },
+ { EXPAND_HIGHLIGHT, get_highlight_name, true, false },
{ EXPAND_EVENTS, expand_get_event_name, true, false },
{ EXPAND_AUGROUP, expand_get_augroup_name, true, false },
{ EXPAND_SIGN, get_sign_name, true, true },
{ EXPAND_PROFILE, get_profile_name, true, true },
-#ifdef HAVE_WORKING_LIBINTL
{ EXPAND_LANGUAGE, get_lang_arg, true, false },
{ EXPAND_LOCALES, get_locales, true, false },
-#endif
{ EXPAND_ENV_VARS, get_env_name, true, true },
{ EXPAND_USER, get_users, true, false },
{ EXPAND_ARGLIST, get_arglist_name, true, false },
@@ -2695,8 +2716,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
return OK;
}
if (xp->xp_context == EXPAND_OLD_SETTING) {
- ExpandOldSetting(numMatches, matches);
- return OK;
+ return ExpandOldSetting(numMatches, matches);
}
if (xp->xp_context == EXPAND_BUFFERS) {
return ExpandBufnames(pat, numMatches, matches, options);
@@ -2733,6 +2753,9 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
if (xp->xp_context == EXPAND_PACKADD) {
return ExpandPackAddDir(pat, numMatches, matches);
}
+ if (xp->xp_context == EXPAND_RUNTIME) {
+ return expand_runtime_cmd(pat, numMatches, matches);
+ }
// When expanding a function name starting with s:, match the <SNR>nr_
// prefix.
@@ -2763,8 +2786,14 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
if (xp->xp_context == EXPAND_SETTINGS
|| xp->xp_context == EXPAND_BOOL_SETTINGS) {
ret = ExpandSettings(xp, &regmatch, pat, numMatches, matches, fuzzy);
+ } else if (xp->xp_context == EXPAND_STRING_SETTING) {
+ ret = ExpandStringSetting(xp, &regmatch, numMatches, matches);
+ } else if (xp->xp_context == EXPAND_SETTING_SUBTRACT) {
+ ret = ExpandSettingSubtract(xp, &regmatch, numMatches, matches);
} else if (xp->xp_context == EXPAND_MAPPINGS) {
ret = ExpandMappings(pat, &regmatch, numMatches, matches);
+ } else if (xp->xp_context == EXPAND_ARGOPT) {
+ ret = expand_argopt(pat, xp, &regmatch, matches, numMatches);
} else if (xp->xp_context == EXPAND_USER_DEFINED) {
ret = ExpandUserDefined(pat, xp, &regmatch, matches, numMatches);
} else {
@@ -2786,9 +2815,8 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
/// program. Matching strings are copied into an array, which is returned.
///
/// @param func returns a string from the list
-static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch,
- char ***matches, int *numMatches, CompleteListItemGetter func,
- int escaped)
+void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, char ***matches,
+ int *numMatches, CompleteListItemGetter func, bool escaped)
{
const bool fuzzy = cmdline_fuzzy_complete(pat);
*matches = NULL;
@@ -2814,7 +2842,7 @@ static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regma
int score = 0;
if (xp->xp_pattern[0] != NUL) {
if (!fuzzy) {
- match = vim_regexec(regmatch, str, (colnr_T)0);
+ match = vim_regexec(regmatch, str, 0);
} else {
score = fuzzy_match_str(str, pat);
match = (score != 0);
@@ -2861,8 +2889,10 @@ static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regma
// in the specified order.
const bool sort_matches = !fuzzy
&& xp->xp_context != EXPAND_MENUNAMES
+ && xp->xp_context != EXPAND_STRING_SETTING
&& xp->xp_context != EXPAND_MENUS
- && xp->xp_context != EXPAND_SCRIPTNAMES;
+ && xp->xp_context != EXPAND_SCRIPTNAMES
+ && xp->xp_context != EXPAND_ARGOPT;
// <SNR> functions should be sorted to the end.
const bool funcsort = xp->xp_context == EXPAND_EXPRESSION
@@ -2916,7 +2946,7 @@ static void expand_shellcmd_onedir(char *buf, char *s, size_t l, char *pat, char
// Check if this name was already found.
hash_T hash = hash_hash(name + l);
hashitem_T *hi =
- hash_lookup(ht, (const char *)(name + l), strlen(name + l), hash);
+ hash_lookup(ht, name + l, strlen(name + l), hash);
if (HASHITEM_EMPTY(hi)) {
// Remove the path that was prepended.
STRMOVE(name, name + l);
@@ -2941,19 +2971,16 @@ static void expand_shellcmd_onedir(char *buf, char *s, size_t l, char *pat, char
static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int flagsarg)
FUNC_ATTR_NONNULL_ALL
{
- char *pat;
- int i;
char *path = NULL;
garray_T ga;
char *buf = xmalloc(MAXPATHL);
- size_t l;
- char *s, *e;
+ char *e;
int flags = flagsarg;
bool did_curdir = false;
// for ":set path=" and ":set tags=" halve backslashes for escaped space
- pat = xstrdup(filepat);
- for (i = 0; pat[i]; i++) {
+ char *pat = xstrdup(filepat);
+ for (int i = 0; pat[i]; i++) {
if (pat[i] == '\\' && pat[i + 1] == ' ') {
STRMOVE(pat + i, pat + i + 1);
}
@@ -2983,7 +3010,7 @@ static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int
ga_init(&ga, (int)sizeof(char *), 10);
hashtab_T found_ht;
hash_init(&found_ht);
- for (s = path;; s = e) {
+ for (char *s = path;; s = e) {
e = vim_strchr(s, ENV_SEPCHAR);
if (e == NULL) {
e = s + strlen(s);
@@ -3004,7 +3031,7 @@ static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int
flags &= ~EW_DIR;
}
- l = (size_t)(e - s);
+ size_t l = (size_t)(e - s);
if (l > MAXPATHL - 5) {
break;
}
@@ -3032,7 +3059,6 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T
CmdlineInfo *const ccline = get_cmdline_info();
char keep = 0;
typval_T args[4];
- char *pat = NULL;
const sctx_T save_current_sctx = current_sctx;
if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) {
@@ -3044,7 +3070,7 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T
ccline->cmdbuff[ccline->cmdlen] = 0;
}
- pat = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
+ char *pat = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
args[0].v_type = VAR_STRING;
args[1].v_type = VAR_STRING;
args[2].v_type = VAR_NUMBER;
@@ -3075,7 +3101,7 @@ static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *re
*matches = NULL;
*numMatches = 0;
- char *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp);
+ char *const retstr = call_user_expand_func(call_func_retstr, xp);
if (retstr == NULL) {
return FAIL;
}
@@ -3099,7 +3125,7 @@ static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *re
int score = 0;
if (xp->xp_pattern[0] != NUL) {
if (!fuzzy) {
- match = vim_regexec(regmatch, s, (colnr_T)0);
+ match = vim_regexec(regmatch, s, 0);
} else {
score = fuzzy_match_str(s, pat);
match = (score != 0);
@@ -3112,11 +3138,11 @@ static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *re
if (match) {
if (!fuzzy) {
- GA_APPEND(char *, &ga, xstrnsave(s, (size_t)(e - s)));
+ GA_APPEND(char *, &ga, xmemdupz(s, (size_t)(e - s)));
} else {
GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){
.idx = ga.ga_len,
- .str = xstrnsave(s, (size_t)(e - s)),
+ .str = xmemdupz(s, (size_t)(e - s)),
.score = score,
}));
}
@@ -3147,7 +3173,7 @@ static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches)
{
*matches = NULL;
*numMatches = 0;
- list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp);
+ list_T *const retlist = call_user_expand_func(call_func_retlist, xp);
if (retlist == NULL) {
return FAIL;
}
@@ -3161,7 +3187,7 @@ static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches)
continue; // Skip non-string items and empty strings.
}
- GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
+ GA_APPEND(char *, &ga, xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string));
});
tv_list_unref(retlist);
@@ -3190,7 +3216,7 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file)
continue; // Skip non-string items and empty strings.
}
- GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
+ GA_APPEND(char *, &ga, xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string));
});
tv_list_unref(retlist);
@@ -3201,11 +3227,12 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file)
/// Expand `file` for all comma-separated directories in `path`.
/// Adds matches to `ga`.
-void globpath(char *path, char *file, garray_T *ga, int expand_options)
+/// If "dirs" is true only expand directory names.
+void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dirs)
{
expand_T xpc;
ExpandInit(&xpc);
- xpc.xp_context = EXPAND_FILES;
+ xpc.xp_context = dirs ? EXPAND_DIRECTORIES : EXPAND_FILES;
char *buf = xmalloc(MAXPATHL);
@@ -3219,8 +3246,7 @@ void globpath(char *path, char *file, garray_T *ga, int expand_options)
char **p;
int num_p = 0;
- (void)ExpandFromContext(&xpc, buf, &p, &num_p,
- WILD_SILENT | expand_options);
+ (void)ExpandFromContext(&xpc, buf, &p, &num_p, WILD_SILENT | expand_options);
if (num_p > 0) {
ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options);
@@ -3228,7 +3254,7 @@ void globpath(char *path, char *file, garray_T *ga, int expand_options)
ga_grow(ga, num_p);
// take over the pointers and put them in "ga"
for (int i = 0; i < num_p; i++) {
- ((char_u **)ga->ga_data)[ga->ga_len] = (char_u *)p[i];
+ ((char **)ga->ga_data)[ga->ga_len] = p[i];
ga->ga_len++;
}
xfree(p);
@@ -3473,14 +3499,12 @@ void wildmenu_cleanup(CmdlineInfo *cclp)
/// "getcompletion()" function
void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- char *pat;
expand_T xpc;
bool filtered = false;
int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
| WILD_NO_BEEP | WILD_HOME_REPLACE;
- if (argvars[1].v_type != VAR_STRING) {
- semsg(_(e_invarg2), "type must be a string");
+ if (tv_check_for_string_arg(argvars, 1) == FAIL) {
return;
}
const char *const type = tv_get_string(&argvars[1]);
@@ -3505,32 +3529,60 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const char *pattern = tv_get_string(&argvars[0]);
if (strcmp(type, "cmdline") == 0) {
- set_one_cmd_context(&xpc, pattern);
+ const int cmdline_len = (int)strlen(pattern);
+ set_cmd_context(&xpc, (char *)pattern, cmdline_len, cmdline_len, false);
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
- xpc.xp_col = (int)strlen(pattern);
+ xpc.xp_col = cmdline_len;
goto theend;
}
ExpandInit(&xpc);
xpc.xp_pattern = (char *)pattern;
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
+ xpc.xp_line = (char *)pattern;
+
xpc.xp_context = cmdcomplete_str_to_type(type);
if (xpc.xp_context == EXPAND_NOTHING) {
semsg(_(e_invarg2), type);
return;
}
+ if (xpc.xp_context == EXPAND_USER_DEFINED) {
+ // Must be "custom,funcname" pattern
+ if (strncmp(type, "custom,", 7) != 0) {
+ semsg(_(e_invarg2), type);
+ return;
+ }
+
+ xpc.xp_arg = (char *)(type + 7);
+ }
+
+ if (xpc.xp_context == EXPAND_USER_LIST) {
+ // Must be "customlist,funcname" pattern
+ if (strncmp(type, "customlist,", 11) != 0) {
+ semsg(_(e_invarg2), type);
+ return;
+ }
+
+ xpc.xp_arg = (char *)(type + 11);
+ }
+
if (xpc.xp_context == EXPAND_MENUS) {
set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false);
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
}
-
if (xpc.xp_context == EXPAND_SIGN) {
set_context_in_sign_cmd(&xpc, xpc.xp_pattern);
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
}
+ if (xpc.xp_context == EXPAND_RUNTIME) {
+ set_context_in_runtime_cmd(&xpc, xpc.xp_pattern);
+ xpc.xp_pattern_len = strlen(xpc.xp_pattern);
+ }
theend:
+ ;
+ char *pat;
if (cmdline_fuzzy_completion_supported(&xpc)) {
// when fuzzy matching, don't modify the search string
pat = xstrdup(xpc.xp_pattern);
@@ -3542,8 +3594,7 @@ theend:
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
+ tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
}
xfree(pat);
ExpandCleanup(&xpc);
diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h
index 810e289f7c..b0772d26a3 100644
--- a/src/nvim/cmdexpand.h
+++ b/src/nvim/cmdexpand.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_CMDEXPAND_H
-#define NVIM_CMDEXPAND_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: export
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/ex_getln.h"
-#include "nvim/garray.h"
-#include "nvim/types.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning.
@@ -44,4 +44,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cmdexpand.h.generated.h"
#endif
-#endif // NVIM_CMDEXPAND_H
diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h
new file mode 100644
index 0000000000..97307c4e50
--- /dev/null
+++ b/src/nvim/cmdexpand_defs.h
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/eval/typval_defs.h"
+#include "nvim/types_defs.h"
+
+typedef enum {
+ XP_PREFIX_NONE, ///< prefix not used
+ XP_PREFIX_NO, ///< "no" prefix for bool option
+ XP_PREFIX_INV, ///< "inv" prefix for bool option
+} xp_prefix_T;
+
+enum { EXPAND_BUF_LEN = 256, };
+
+/// used for completion on the command line
+typedef struct expand {
+ char *xp_pattern; ///< start of item to expand, guaranteed
+ ///< to be part of xp_line
+ int xp_context; ///< type of expansion
+ size_t xp_pattern_len; ///< bytes in xp_pattern before cursor
+ xp_prefix_T xp_prefix;
+ char *xp_arg; ///< completion function
+ LuaRef xp_luaref; ///< Ref to Lua completion function
+ sctx_T xp_script_ctx; ///< SCTX for completion function
+ int xp_backslash; ///< one of the XP_BS_ values
+#ifndef BACKSLASH_IN_FILENAME
+ bool xp_shell; ///< true for a shell command, more
+ ///< characters need to be escaped
+#endif
+ int xp_numfiles; ///< number of files found by file name completion
+ int xp_col; ///< cursor position in line
+ int xp_selected; ///< selected index in completion
+ char *xp_orig; ///< originally expanded string
+ char **xp_files; ///< list of files
+ char *xp_line; ///< text being completed
+ char xp_buf[EXPAND_BUF_LEN]; ///< buffer for returned match
+} expand_T;
+
+/// values for xp_backslash
+enum {
+ XP_BS_NONE = 0, ///< nothing special for backslashes
+ XP_BS_ONE = 0x1, ///< uses one backslash before a space
+ XP_BS_THREE = 0x2, ///< uses three backslashes before a space
+ XP_BS_COMMA = 0x4, ///< commas need to be escaped with a backslash
+};
+
+/// values for xp_context when doing command line completion
+enum {
+ EXPAND_UNSUCCESSFUL = -2,
+ EXPAND_OK = -1,
+ EXPAND_NOTHING = 0,
+ EXPAND_COMMANDS,
+ EXPAND_FILES,
+ EXPAND_DIRECTORIES,
+ EXPAND_SETTINGS,
+ EXPAND_BOOL_SETTINGS,
+ EXPAND_TAGS,
+ EXPAND_OLD_SETTING,
+ EXPAND_HELP,
+ EXPAND_BUFFERS,
+ EXPAND_EVENTS,
+ EXPAND_MENUS,
+ EXPAND_SYNTAX,
+ EXPAND_HIGHLIGHT,
+ EXPAND_AUGROUP,
+ EXPAND_USER_VARS,
+ EXPAND_MAPPINGS,
+ EXPAND_TAGS_LISTFILES,
+ EXPAND_FUNCTIONS,
+ EXPAND_USER_FUNC,
+ EXPAND_EXPRESSION,
+ EXPAND_MENUNAMES,
+ EXPAND_USER_COMMANDS,
+ EXPAND_USER_CMD_FLAGS,
+ EXPAND_USER_NARGS,
+ EXPAND_USER_COMPLETE,
+ EXPAND_ENV_VARS,
+ EXPAND_LANGUAGE,
+ EXPAND_COLORS,
+ EXPAND_COMPILER,
+ EXPAND_USER_DEFINED,
+ EXPAND_USER_LIST,
+ EXPAND_USER_LUA,
+ EXPAND_SHELLCMD,
+ EXPAND_SIGN,
+ EXPAND_PROFILE,
+ EXPAND_FILETYPE,
+ EXPAND_FILES_IN_PATH,
+ EXPAND_OWNSYNTAX,
+ EXPAND_LOCALES,
+ EXPAND_HISTORY,
+ EXPAND_USER,
+ EXPAND_SYNTIME,
+ EXPAND_USER_ADDR_TYPE,
+ EXPAND_PACKADD,
+ EXPAND_MESSAGES,
+ EXPAND_MAPCLEAR,
+ EXPAND_ARGLIST,
+ EXPAND_DIFF_BUFFERS,
+ EXPAND_BREAKPOINT,
+ EXPAND_SCRIPTNAMES,
+ EXPAND_RUNTIME,
+ EXPAND_STRING_SETTING,
+ EXPAND_SETTING_SUBTRACT,
+ EXPAND_ARGOPT,
+ EXPAND_CHECKHEALTH,
+ EXPAND_LUA,
+};
+
+/// Type used by ExpandGeneric()
+typedef char *(*CompleteListItemGetter)(expand_T *, int);
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
index 2df82d9355..4556b74396 100644
--- a/src/nvim/cmdhist.c
+++ b/src/nvim/cmdhist.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// cmdhist.c: Functions for the history of the command-line.
#include <assert.h>
@@ -10,24 +7,25 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cmdhist.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cmdhist.c.generated.h"
@@ -204,7 +202,7 @@ static inline void clear_hist_entry(histentry_T *hisptr)
/// If 'move_to_front' is true, matching entry is moved to end of history.
///
/// @param move_to_front Move the entry to the front if it exists
-static int in_history(int type, char *str, int move_to_front, int sep)
+static int in_history(int type, const char *str, int move_to_front, int sep)
{
int last_i = -1;
@@ -238,7 +236,7 @@ static int in_history(int type, char *str, int move_to_front, int sep)
}
list_T *const list = history[type][i].additional_elements;
- str = history[type][i].hisstr;
+ char *const save_hisstr = history[type][i].hisstr;
while (i != hisidx[type]) {
if (++i >= hislen) {
i = 0;
@@ -248,7 +246,7 @@ static int in_history(int type, char *str, int move_to_front, int sep)
}
tv_list_unref(list);
history[type][i].hisnum = ++hisnum[type];
- history[type][i].hisstr = str;
+ history[type][i].hisstr = save_hisstr;
history[type][i].timestamp = os_time();
history[type][i].additional_elements = NULL;
return true;
@@ -295,7 +293,7 @@ static int last_maptick = -1; // last seen maptick
/// @param histype may be one of the HIST_ values.
/// @param in_map consider maptick when inside a mapping
/// @param sep separator character used (search hist)
-void add_to_history(int histype, char *new_entry, int in_map, int sep)
+void add_to_history(int histype, const char *new_entry, int in_map, int sep)
{
histentry_T *hisptr;
@@ -368,7 +366,6 @@ static int get_history_idx(int histype)
static int calc_hist_idx(int histype, int num)
{
int i;
- int wrapped = false;
if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
|| (i = hisidx[histype]) < 0 || num == 0) {
@@ -377,6 +374,7 @@ static int calc_hist_idx(int histype, int num)
histentry_T *hist = history[histype];
if (num > 0) {
+ int wrapped = false;
while (hist[i].hisnum > num) {
if (--i < 0) {
if (wrapped) {
@@ -454,13 +452,14 @@ static int del_history_entry(int histype, char *str)
regmatch.rm_ic = false; // always match case
bool found = false;
- int i = idx, last = idx;
+ int i = idx;
+ int last = idx;
do {
histentry_T *hisptr = &history[histype][i];
if (hisptr->hisstr == NULL) {
break;
}
- if (vim_regexec(&regmatch, hisptr->hisstr, (colnr_T)0)) {
+ if (vim_regexec(&regmatch, hisptr->hisstr, 0)) {
found = true;
hist_free_entry(hisptr);
} else {
@@ -519,14 +518,12 @@ static int del_history_idx(int histype, int idx)
/// "histadd()" function
void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- HistoryType histype;
-
rettv->vval.v_number = false;
if (check_secure()) {
return;
}
const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
- histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
+ HistoryType histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
if (histype == HIST_INVALID) {
return;
}
@@ -538,7 +535,7 @@ void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
init_history();
- add_to_history(histype, (char *)str, false, NUL);
+ add_to_history(histype, str, false, NUL);
rettv->vval.v_number = true;
}
@@ -568,14 +565,12 @@ void f_histdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "histget()" function
void f_histget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- HistoryType type;
- int idx;
-
const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
if (str == NULL) {
rettv->vval.v_string = NULL;
} else {
- type = get_histtype(str, strlen(str), false);
+ int idx;
+ HistoryType type = get_histtype(str, strlen(str), false);
if (argvars[1].v_type == VAR_UNKNOWN) {
idx = get_history_idx(type);
} else {
@@ -592,8 +587,8 @@ void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
const char *const histname = tv_get_string_chk(&argvars[0]);
HistoryType i = histname == NULL
- ? HIST_INVALID
- : get_histtype(histname, strlen(histname), false);
+ ? HIST_INVALID
+ : get_histtype(histname, strlen(histname), false);
if (i != HIST_INVALID) {
i = get_history_idx(i);
}
@@ -603,18 +598,15 @@ void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// :history command - print a history
void ex_history(exarg_T *eap)
{
- histentry_T *hist;
int histype1 = HIST_CMD;
int histype2 = HIST_CMD;
int hisidx1 = 1;
int hisidx2 = -1;
- int idx;
- int i, j, k;
char *end;
char *arg = eap->arg;
if (hislen == 0) {
- msg(_("'history' option is zero"));
+ msg(_("'history' option is zero"), 0);
return;
}
@@ -626,7 +618,7 @@ void ex_history(exarg_T *eap)
}
histype1 = get_histtype(arg, (size_t)(end - arg), false);
if (histype1 == HIST_INVALID) {
- if (STRNICMP(arg, "all", end - (char *)arg) == 0) {
+ if (STRNICMP(arg, "all", end - arg) == 0) {
histype1 = 0;
histype2 = HIST_COUNT - 1;
} else {
@@ -640,19 +632,24 @@ void ex_history(exarg_T *eap)
end = arg;
}
if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
- semsg(_(e_trailing_arg), end);
+ if (*end != NUL) {
+ semsg(_(e_trailing_arg), end);
+ } else {
+ semsg(_(e_val_too_large), arg);
+ }
return;
}
for (; !got_int && histype1 <= histype2; histype1++) {
- STRCPY(IObuff, "\n # ");
+ xstrlcpy(IObuff, "\n # ", IOSIZE);
assert(history_names[histype1] != NULL);
- STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
+ xstrlcat(IObuff, history_names[histype1], IOSIZE);
+ xstrlcat(IObuff, " history", IOSIZE);
msg_puts_title(IObuff);
- idx = hisidx[histype1];
- hist = history[histype1];
- j = hisidx1;
- k = hisidx2;
+ int idx = hisidx[histype1];
+ histentry_T *hist = history[histype1];
+ int j = hisidx1;
+ int k = hisidx2;
if (j < 0) {
j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum;
}
@@ -660,7 +657,7 @@ void ex_history(exarg_T *eap)
k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum;
}
if (idx >= 0 && j <= k) {
- for (i = idx + 1; !got_int; i++) {
+ for (int i = idx + 1; !got_int; i++) {
if (i == hislen) {
i = 0;
}
@@ -673,9 +670,9 @@ void ex_history(exarg_T *eap)
trunc_string(hist[i].hisstr, IObuff + strlen(IObuff),
Columns - 10, IOSIZE - (int)strlen(IObuff));
} else {
- STRCAT(IObuff, hist[i].hisstr);
+ xstrlcat(IObuff, hist[i].hisstr, IOSIZE);
}
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
}
if (i == idx) {
break;
diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h
index f86a2f855c..cce0f92898 100644
--- a/src/nvim/cmdhist.h
+++ b/src/nvim/cmdhist.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_CMDHIST_H
-#define NVIM_CMDHIST_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: export
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: export
#include "nvim/os/time.h"
+#include "nvim/types_defs.h"
/// Present history tables
typedef enum {
@@ -31,4 +31,3 @@ typedef struct hist_entry {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cmdhist.h.generated.h"
#endif
-#endif // NVIM_CMDHIST_H
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 9de6c16536..59309fcf16 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Context: snapshot of the entire editor state as one big object/map
#include <assert.h>
@@ -9,20 +6,20 @@
#include <stdio.h>
#include <string.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
+#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
#include "nvim/context.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
-#include "nvim/gettext.h"
+#include "nvim/func_attr.h"
#include "nvim/hashtab.h"
#include "nvim/keycodes.h"
#include "nvim/memory.h"
-#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/shada.h"
@@ -142,9 +139,8 @@ bool ctx_restore(Context *ctx, const int flags)
free_ctx = true;
}
- char *op_shada;
- get_option_value("shada", NULL, &op_shada, NULL, OPT_GLOBAL);
- set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
+ OptVal op_shada = get_option_value("shada", NULL, OPT_GLOBAL, NULL);
+ set_option_value("shada", STATIC_CSTR_AS_OPTVAL("!,'100,%"), OPT_GLOBAL);
if (flags & kCtxRegs) {
ctx_restore_regs(ctx);
@@ -170,8 +166,8 @@ bool ctx_restore(Context *ctx, const int flags)
ctx_free(ctx);
}
- set_option_value("shada", 0L, op_shada, OPT_GLOBAL);
- xfree(op_shada);
+ set_option_value("shada", op_shada, OPT_GLOBAL);
+ optval_free(op_shada);
return true;
}
@@ -271,8 +267,9 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
size_t cmd_len = sizeof("func! ") + strlen(name);
char *cmd = xmalloc(cmd_len);
snprintf(cmd, cmd_len, "func! %s", name);
- String func_body = nvim_exec(VIML_INTERNAL_CALL, cstr_as_string(cmd),
- true, &err);
+ Dict(exec_opts) opts = { .output = true };
+ String func_body = exec_impl(VIML_INTERNAL_CALL, cstr_as_string(cmd),
+ &opts, &err);
xfree(cmd);
if (!ERROR_SET(&err)) {
ADD(ctx->funcs, STRING_OBJ(func_body));
@@ -320,24 +317,28 @@ static inline Array sbuf_to_array(msgpack_sbuffer sbuf)
/// Convert readfile()-style array to msgpack_sbuffer.
///
/// @param[in] array readfile()-style array to convert.
+/// @param[out] err Error object.
///
/// @return msgpack_sbuffer with conversion result.
-static inline msgpack_sbuffer array_to_sbuf(Array array)
+static inline msgpack_sbuffer array_to_sbuf(Array array, Error *err)
+ FUNC_ATTR_NONNULL_ALL
{
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
typval_T list_tv;
- Error err = ERROR_INIT;
- object_to_vim(ARRAY_OBJ(array), &list_tv, &err);
+ if (!object_to_vim(ARRAY_OBJ(array), &list_tv, err)) {
+ return sbuf;
+ }
+ assert(list_tv.v_type == VAR_LIST);
if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) {
- emsg(_("E474: Failed to convert list to msgpack string buffer"));
+ api_set_error(err, kErrorTypeException, "%s",
+ "E474: Failed to convert list to msgpack string buffer");
}
sbuf.alloc = sbuf.size;
tv_clear(&list_tv);
- api_clear_error(&err);
return sbuf;
}
@@ -366,31 +367,32 @@ Dictionary ctx_to_dict(Context *ctx)
///
/// @param[in] dict Context Dictionary representation.
/// @param[out] ctx Context object to store conversion result into.
+/// @param[out] err Error object.
///
/// @return types of included context items.
-int ctx_from_dict(Dictionary dict, Context *ctx)
+int ctx_from_dict(Dictionary dict, Context *ctx, Error *err)
FUNC_ATTR_NONNULL_ALL
{
assert(ctx != NULL);
int types = 0;
- for (size_t i = 0; i < dict.size; i++) {
+ for (size_t i = 0; i < dict.size && !ERROR_SET(err); i++) {
KeyValuePair item = dict.items[i];
if (item.value.type != kObjectTypeArray) {
continue;
}
if (strequal(item.key.data, "regs")) {
types |= kCtxRegs;
- ctx->regs = array_to_sbuf(item.value.data.array);
+ ctx->regs = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "jumps")) {
types |= kCtxJumps;
- ctx->jumps = array_to_sbuf(item.value.data.array);
+ ctx->jumps = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "bufs")) {
types |= kCtxBufs;
- ctx->bufs = array_to_sbuf(item.value.data.array);
+ ctx->bufs = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "gvars")) {
types |= kCtxGVars;
- ctx->gvars = array_to_sbuf(item.value.data.array);
+ ctx->gvars = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "funcs")) {
types |= kCtxFuncs;
ctx->funcs = copy_object(item.value, NULL).data.array;
diff --git a/src/nvim/context.h b/src/nvim/context.h
index 7a1224d876..1c18a1af7c 100644
--- a/src/nvim/context.h
+++ b/src/nvim/context.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_CONTEXT_H
-#define NVIM_CONTEXT_H
+#pragma once
-#include <msgpack.h>
#include <msgpack/sbuffer.h>
#include <stddef.h>
@@ -45,5 +43,3 @@ extern int kCtxAll;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "context.h.generated.h"
#endif
-
-#endif // NVIM_CONTEXT_H
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index b1dbc68ea3..1110fe1e64 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -1,32 +1,28 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
-#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/fold.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor.c.generated.h"
@@ -108,14 +104,14 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
|| (VIsual_active && *p_sel != 'o')
|| ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL);
- char *line = ml_get_buf(curbuf, pos->lnum, false);
+ char *line = ml_get_buf(curbuf, pos->lnum);
if (wcol >= MAXCOL) {
idx = (int)strlen(line) - 1 + one_more;
col = wcol;
if ((addspaces || finetune) && !VIsual_active) {
- curwin->w_curswant = linetabsize(line) + one_more;
+ curwin->w_curswant = linetabsize(curwin, pos->lnum) + one_more;
if (curwin->w_curswant > 0) {
curwin->w_curswant--;
}
@@ -129,7 +125,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
&& curwin->w_width_inner != 0
&& wcol >= (colnr_T)width
&& width > 0) {
- csize = linetabsize(line);
+ csize = linetabsize(curwin, pos->lnum);
if (csize > 0) {
csize--;
}
@@ -315,7 +311,7 @@ void check_pos(buf_T *buf, pos_T *pos)
}
if (pos->col > 0) {
- char *line = ml_get_buf(buf, pos->lnum, false);
+ char *line = ml_get_buf(buf, pos->lnum);
colnr_T len = (colnr_T)strlen(line);
if (pos->col > len) {
pos->col = len;
@@ -324,18 +320,18 @@ void check_pos(buf_T *buf, pos_T *pos)
}
/// Make sure curwin->w_cursor.lnum is valid.
-void check_cursor_lnum(void)
+void check_cursor_lnum(win_T *win)
{
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
+ buf_T *buf = win->w_buffer;
+ if (win->w_cursor.lnum > buf->b_ml.ml_line_count) {
// If there is a closed fold at the end of the file, put the cursor in
// its first line. Otherwise in the last line.
- if (!hasFolding(curbuf->b_ml.ml_line_count,
- &curwin->w_cursor.lnum, NULL)) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ if (!hasFolding(buf->b_ml.ml_line_count, &win->w_cursor.lnum, NULL)) {
+ win->w_cursor.lnum = buf->b_ml.ml_line_count;
}
}
- if (curwin->w_cursor.lnum <= 0) {
- curwin->w_cursor.lnum = 1;
+ if (win->w_cursor.lnum <= 0) {
+ win->w_cursor.lnum = 1;
}
}
@@ -351,9 +347,9 @@ void check_cursor_col_win(win_T *win)
{
colnr_T oldcol = win->w_cursor.col;
colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd;
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
- colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false));
+ colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, win->w_cursor.lnum));
if (len == 0) {
win->w_cursor.col = 0;
} else if (win->w_cursor.col >= len) {
@@ -406,7 +402,7 @@ void check_cursor_col_win(win_T *win)
/// Make sure curwin->w_cursor in on a valid character
void check_cursor(void)
{
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
check_cursor_col();
}
@@ -439,24 +435,28 @@ void adjust_cursor_col(void)
}
}
-/// When curwin->w_leftcol has changed, adjust the cursor position.
+/// Set "curwin->w_leftcol" to "leftcol".
+/// Adjust the cursor position if needed.
///
/// @return true if the cursor was moved.
-bool leftcol_changed(void)
+bool set_leftcol(colnr_T leftcol)
{
+ // Return quickly when there is no change.
+ if (curwin->w_leftcol == leftcol) {
+ return false;
+ }
+ curwin->w_leftcol = leftcol;
+
+ changed_cline_bef_curs(curwin);
// TODO(hinidu): I think it should be colnr_T or int, but p_siso is long.
// Perhaps we can change p_siso to int.
- int64_t lastcol;
- colnr_T s, e;
- bool retval = false;
-
- changed_cline_bef_curs();
- lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1;
+ int64_t lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1;
validate_virtcol();
+ bool retval = false;
// If the cursor is right or left of the screen, move it to last or first
- // character.
- long siso = get_sidescrolloff_value(curwin);
+ // visible character.
+ int siso = get_sidescrolloff_value(curwin);
if (curwin->w_virtcol > (colnr_T)(lastcol - siso)) {
retval = true;
coladvance((colnr_T)(lastcol - siso));
@@ -468,6 +468,7 @@ bool leftcol_changed(void)
// If the start of the character under the cursor is not on the screen,
// advance the cursor one more char. If this fails (last char of the
// line) adjust the scrolling.
+ colnr_T s, e;
getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e);
if (e > (colnr_T)lastcol) {
retval = true;
@@ -476,7 +477,7 @@ bool leftcol_changed(void)
retval = true;
if (coladvance(e + 1) == FAIL) { // there isn't another character
curwin->w_leftcol = s; // adjust w_leftcol instead
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
}
}
@@ -494,20 +495,19 @@ int gchar_cursor(void)
/// Write a character at the current cursor position.
/// It is directly written into the block.
-void pchar_cursor(char_u c)
+void pchar_cursor(char c)
{
- *(ml_get_buf(curbuf, curwin->w_cursor.lnum, true)
- + curwin->w_cursor.col) = (char)c;
+ *(ml_get_buf_mut(curbuf, curwin->w_cursor.lnum) + curwin->w_cursor.col) = c;
}
/// @return pointer to cursor line.
char *get_cursor_line_ptr(void)
{
- return ml_get_buf(curbuf, curwin->w_cursor.lnum, false);
+ return ml_get_buf(curbuf, curwin->w_cursor.lnum);
}
/// @return pointer to cursor position.
char *get_cursor_pos_ptr(void)
{
- return ml_get_buf(curbuf, curwin->w_cursor.lnum, false) + curwin->w_cursor.col;
+ return ml_get_buf(curbuf, curwin->w_cursor.lnum) + curwin->w_cursor.col;
}
diff --git a/src/nvim/cursor.h b/src/nvim/cursor.h
index 1cbe8a609e..d50976b598 100644
--- a/src/nvim/cursor.h
+++ b/src/nvim/cursor.h
@@ -1,12 +1,8 @@
-#ifndef NVIM_CURSOR_H
-#define NVIM_CURSOR_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor.h.generated.h"
#endif
-
-#endif // NVIM_CURSOR_H
diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c
index f21e632036..5aff3b5598 100644
--- a/src/nvim/cursor_shape.c
+++ b/src/nvim/cursor_shape.c
@@ -1,52 +1,51 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
-#include "nvim/memory.h"
-#include "nvim/option_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor_shape.c.generated.h"
#endif
+static const char e_digit_expected[] = N_("E548: Digit expected");
+
/// Handling of cursor and mouse pointer shapes in various modes.
cursorentry_T shape_table[SHAPE_IDX_COUNT] = {
// Values are set by 'guicursor' and 'mouseshape'.
// Adjust the SHAPE_IDX_ defines when changing this!
- { "normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE },
- { "visual", 0, 0, 0, 700L, 400L, 250L, 0, 0, "v", SHAPE_CURSOR + SHAPE_MOUSE },
- { "insert", 0, 0, 0, 700L, 400L, 250L, 0, 0, "i", SHAPE_CURSOR + SHAPE_MOUSE },
- { "replace", 0, 0, 0, 700L, 400L, 250L, 0, 0, "r", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "c", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_insert", 0, 0, 0, 700L, 400L, 250L, 0, 0, "ci", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_replace", 0, 0, 0, 700L, 400L, 250L, 0, 0, "cr", SHAPE_CURSOR + SHAPE_MOUSE },
- { "operator", 0, 0, 0, 700L, 400L, 250L, 0, 0, "o", SHAPE_CURSOR + SHAPE_MOUSE },
- { "visual_select", 0, 0, 0, 700L, 400L, 250L, 0, 0, "ve", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "e", SHAPE_MOUSE },
- { "statusline_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "s", SHAPE_MOUSE },
- { "statusline_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "sd", SHAPE_MOUSE },
- { "vsep_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vs", SHAPE_MOUSE },
- { "vsep_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vd", SHAPE_MOUSE },
- { "more", 0, 0, 0, 0L, 0L, 0L, 0, 0, "m", SHAPE_MOUSE },
- { "more_lastline", 0, 0, 0, 0L, 0L, 0L, 0, 0, "ml", SHAPE_MOUSE },
- { "showmatch", 0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR },
+ { "normal", 0, 0, 0, 700, 400, 250, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "visual", 0, 0, 0, 700, 400, 250, 0, 0, "v", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "insert", 0, 0, 0, 700, 400, 250, 0, 0, "i", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "replace", 0, 0, 0, 700, 400, 250, 0, 0, "r", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_normal", 0, 0, 0, 700, 400, 250, 0, 0, "c", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_insert", 0, 0, 0, 700, 400, 250, 0, 0, "ci", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_replace", 0, 0, 0, 700, 400, 250, 0, 0, "cr", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "operator", 0, 0, 0, 700, 400, 250, 0, 0, "o", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "visual_select", 0, 0, 0, 700, 400, 250, 0, 0, "ve", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_hover", 0, 0, 0, 0, 0, 0, 0, 0, "e", SHAPE_MOUSE },
+ { "statusline_hover", 0, 0, 0, 0, 0, 0, 0, 0, "s", SHAPE_MOUSE },
+ { "statusline_drag", 0, 0, 0, 0, 0, 0, 0, 0, "sd", SHAPE_MOUSE },
+ { "vsep_hover", 0, 0, 0, 0, 0, 0, 0, 0, "vs", SHAPE_MOUSE },
+ { "vsep_drag", 0, 0, 0, 0, 0, 0, 0, 0, "vd", SHAPE_MOUSE },
+ { "more", 0, 0, 0, 0, 0, 0, 0, 0, "m", SHAPE_MOUSE },
+ { "more_lastline", 0, 0, 0, 0, 0, 0, 0, 0, "ml", SHAPE_MOUSE },
+ { "showmatch", 0, 0, 0, 100, 100, 100, 0, 0, "sm", SHAPE_CURSOR },
};
/// Converts cursor_shapes into an Array of Dictionaries
@@ -60,8 +59,8 @@ Array mode_style_array(Arena *arena)
for (int i = 0; i < SHAPE_IDX_COUNT; i++) {
cursorentry_T *cur = &shape_table[i];
Dictionary dic = arena_dict(arena, 3 + ((cur->used_for & SHAPE_CURSOR) ? 9 : 0));
- PUT_C(dic, "name", STRING_OBJ(cstr_as_string(cur->full_name)));
- PUT_C(dic, "short_name", STRING_OBJ(cstr_as_string(cur->name)));
+ PUT_C(dic, "name", CSTR_AS_OBJ(cur->full_name));
+ PUT_C(dic, "short_name", CSTR_AS_OBJ(cur->name));
if (cur->used_for & SHAPE_MOUSE) {
PUT_C(dic, "mouse_shape", INTEGER_OBJ(cur->mshape));
}
@@ -101,7 +100,7 @@ Array mode_style_array(Arena *arena)
/// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape')
///
/// @returns error message for an illegal option, NULL otherwise.
-char *parse_shape_opt(int what)
+const char *parse_shape_opt(int what)
{
char *colonp;
char *commap;
@@ -113,10 +112,9 @@ char *parse_shape_opt(int what)
int len;
int i;
int found_ve = false; // found "ve" flag
- int round;
// First round: check for errors; second round: do it for real.
- for (round = 1; round <= 2; round++) {
+ for (int round = 1; round <= 2; round++) {
if (round == 2 || *p_guicursor == NUL) {
// Set all entries to default (block, blinkon0, default color).
// This is the default for anything that is not set.
@@ -194,7 +192,7 @@ char *parse_shape_opt(int what)
if (len != 0) {
p += len;
if (!ascii_isdigit(*p)) {
- return N_("E548: digit expected");
+ return e_digit_expected;
}
int n = getdigits_int(&p, false, 0);
if (len == 3) { // "ver" or "hor"
@@ -362,9 +360,9 @@ static void clear_shape_table(void)
{
for (int idx = 0; idx < SHAPE_IDX_COUNT; idx++) {
shape_table[idx].shape = SHAPE_BLOCK;
- shape_table[idx].blinkwait = 0L;
- shape_table[idx].blinkon = 0L;
- shape_table[idx].blinkoff = 0L;
+ shape_table[idx].blinkwait = 0;
+ shape_table[idx].blinkon = 0;
+ shape_table[idx].blinkoff = 0;
shape_table[idx].id = 0;
shape_table[idx].id_lm = 0;
}
diff --git a/src/nvim/cursor_shape.h b/src/nvim/cursor_shape.h
index 93bddd47c7..4c1d6d4eec 100644
--- a/src/nvim/cursor_shape.h
+++ b/src/nvim/cursor_shape.h
@@ -1,8 +1,7 @@
-#ifndef NVIM_CURSOR_SHAPE_H
-#define NVIM_CURSOR_SHAPE_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/types.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/memory_defs.h" // IWYU pragma: keep
/// struct to store values from 'guicursor' and 'mouseshape'
/// Indexes in shape_table[]
@@ -44,9 +43,9 @@ typedef struct cursor_entry {
CursorShape shape; ///< cursor shape: one of the SHAPE_ defines
int mshape; ///< mouse shape: one of the MSHAPE defines
int percentage; ///< percentage of cell for bar
- long blinkwait; ///< blinking, wait time before blinking starts
- long blinkon; ///< blinking, on time
- long blinkoff; ///< blinking, off time
+ int blinkwait; ///< blinking, wait time before blinking starts
+ int blinkon; ///< blinking, on time
+ int blinkoff; ///< blinking, off time
int id; ///< highlight group ID
int id_lm; ///< highlight group ID for :lmap mode
char *name; ///< mode short name
@@ -58,4 +57,3 @@ extern cursorentry_T shape_table[SHAPE_IDX_COUNT];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor_shape.h.generated.h"
#endif
-#endif // NVIM_CURSOR_SHAPE_H
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index f7e70a78ce..a343c1ad6b 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file debugger.c
///
/// Vim script debugger functions
@@ -10,33 +7,33 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/state_defs.h"
+#include "nvim/vim_defs.h"
/// batch mode debugging: don't save and restore typeahead.
static bool debug_greedy = false;
@@ -75,7 +72,6 @@ void do_debug(char *cmd)
tasave_T typeaheadbuf;
bool typeahead_saved = false;
int save_ignore_script = 0;
- int n;
char *cmdline = NULL;
char *p;
char *tail = NULL;
@@ -103,31 +99,31 @@ void do_debug(char *cmd)
debug_mode = true;
if (!debug_did_msg) {
- msg(_("Entering Debug mode. Type \"cont\" to continue."));
+ msg(_("Entering Debug mode. Type \"cont\" to continue."), 0);
}
if (debug_oldval != NULL) {
- smsg(_("Oldval = \"%s\""), debug_oldval);
+ smsg(0, _("Oldval = \"%s\""), debug_oldval);
xfree(debug_oldval);
debug_oldval = NULL;
}
if (debug_newval != NULL) {
- smsg(_("Newval = \"%s\""), debug_newval);
+ smsg(0, _("Newval = \"%s\""), debug_newval);
xfree(debug_newval);
debug_newval = NULL;
}
char *sname = estack_sfile(ESTACK_NONE);
if (sname != NULL) {
- msg(sname);
+ msg(sname, 0);
}
xfree(sname);
if (SOURCING_LNUM != 0) {
- smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
+ smsg(0, _("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
} else {
- smsg(_("cmd: %s"), cmd);
+ smsg(0, _("cmd: %s"), cmd);
}
// Repeat getting a command and executing it.
- for (;;) {
+ while (true) {
msg_scroll = true;
need_wait_return = false;
@@ -146,7 +142,7 @@ void do_debug(char *cmd)
}
// don't debug any function call, e.g. from an expression mapping
- n = debug_break_level;
+ int n = debug_break_level;
debug_break_level = -1;
xfree(cmdline);
@@ -236,7 +232,7 @@ void do_debug(char *cmd)
}
if (last_cmd != 0) {
- // Execute debug command: decided where to break next and return.
+ // Execute debug command: decide where to break next and return.
switch (last_cmd) {
case CMD_CONT:
debug_break_level = -1;
@@ -346,14 +342,14 @@ static void do_checkbacktracelevel(void)
{
if (debug_backtrace_level < 0) {
debug_backtrace_level = 0;
- msg(_("frame is zero"));
+ msg(_("frame is zero"), 0);
} else {
char *sname = estack_sfile(ESTACK_NONE);
int max = get_maxbacktrace_level(sname);
if (debug_backtrace_level > max) {
debug_backtrace_level = max;
- smsg(_("frame at highest level: %d"), max);
+ smsg(0, _("frame at highest level: %d"), max);
}
xfree(sname);
}
@@ -372,9 +368,9 @@ static void do_showbacktrace(char *cmd)
*next = NUL;
}
if (i == max - debug_backtrace_level) {
- smsg("->%d %s", max - i, cur);
+ smsg(0, "->%d %s", max - i, cur);
} else {
- smsg(" %d %s", max - i, cur);
+ smsg(0, " %d %s", max - i, cur);
}
i++;
if (next == NULL) {
@@ -387,9 +383,9 @@ static void do_showbacktrace(char *cmd)
}
if (SOURCING_LNUM != 0) {
- smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
+ smsg(0, _("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
} else {
- smsg(_("cmd: %s"), cmd);
+ smsg(0, _("cmd: %s"), cmd);
}
}
@@ -433,7 +429,7 @@ void dbg_check_breakpoint(exarg_T *eap)
} else {
p = "";
}
- smsg(_("Breakpoint in \"%s%s\" line %" PRId64),
+ smsg(0, _("Breakpoint in \"%s%s\" line %" PRId64),
p,
debug_breakpoint_name + (*p == NUL ? 0 : 3),
(int64_t)debug_breakpoint_lnum);
@@ -481,6 +477,7 @@ static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL };
#define BREAKP(idx) (((struct debuggy *)dbg_breakp.ga_data)[idx])
#define DEBUGGY(gap, idx) (((struct debuggy *)(gap)->ga_data)[idx])
static int last_breakp = 0; // nr of last defined breakpoint
+static bool has_expr_breakpoint = false;
// Profiling uses file and func names similar to breakpoints.
static garray_T prof_ga = { 0, 0, sizeof(struct debuggy), 4, NULL };
@@ -495,7 +492,7 @@ static typval_T *eval_expr_no_emsg(struct debuggy *const bp)
{
// Disable error messages, a bad expression would make Vim unusable.
emsg_off++;
- typval_T *const tv = eval_expr(bp->dbg_name);
+ typval_T *const tv = eval_expr(bp->dbg_name, NULL);
emsg_off--;
return tv;
}
@@ -510,7 +507,6 @@ static typval_T *eval_expr_no_emsg(struct debuggy *const bp)
static int dbg_parsearg(char *arg, garray_T *gap)
{
char *p = arg;
- char *q;
bool here = false;
ga_grow(gap, 1);
@@ -556,7 +552,7 @@ static int dbg_parsearg(char *arg, garray_T *gap)
}
if (bp->dbg_type == DBG_FUNC) {
- bp->dbg_name = xstrdup(p);
+ bp->dbg_name = xstrdup(strncmp(p, "g:", 2) == 0 ? p + 2 : p);
} else if (here) {
bp->dbg_name = xstrdup(curbuf->b_ffname);
} else if (bp->dbg_type == DBG_EXPR) {
@@ -566,7 +562,7 @@ static int dbg_parsearg(char *arg, garray_T *gap)
// Expand the file name in the same way as do_source(). This means
// doing it twice, so that $DIR/file gets expanded when $DIR is
// "~/dir".
- q = expand_env_save(p);
+ char *q = expand_env_save(p);
if (q == NULL) {
return FAIL;
}
@@ -626,6 +622,9 @@ void ex_breakadd(exarg_T *eap)
// DBG_EXPR
DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp;
debug_tick++;
+ if (gap == &dbg_breakp) {
+ has_expr_breakpoint = true;
+ }
}
}
@@ -639,10 +638,20 @@ void ex_debuggreedy(exarg_T *eap)
}
}
+static void update_has_expr_breakpoint(void)
+{
+ has_expr_breakpoint = false;
+ for (int i = 0; i < dbg_breakp.ga_len; i++) {
+ if (BREAKP(i).dbg_type == DBG_EXPR) {
+ has_expr_breakpoint = true;
+ break;
+ }
+ }
+}
+
/// ":breakdel" and ":profdel".
void ex_breakdel(exarg_T *eap)
{
- struct debuggy *bp, *bpi;
int todel = -1;
bool del_all = false;
linenr_T best_lnum = 0;
@@ -669,9 +678,9 @@ void ex_breakdel(exarg_T *eap)
if (dbg_parsearg(eap->arg, gap) == FAIL) {
return;
}
- bp = &DEBUGGY(gap, gap->ga_len);
+ struct debuggy *bp = &DEBUGGY(gap, gap->ga_len);
for (int i = 0; i < gap->ga_len; i++) {
- bpi = &DEBUGGY(gap, i);
+ struct debuggy *bpi = &DEBUGGY(gap, i);
if (bp->dbg_type == bpi->dbg_type
&& strcmp(bp->dbg_name, bpi->dbg_name) == 0
&& (bp->dbg_lnum == bpi->dbg_lnum
@@ -714,29 +723,32 @@ void ex_breakdel(exarg_T *eap)
if (GA_EMPTY(gap)) {
ga_clear(gap);
}
+ if (gap == &dbg_breakp) {
+ update_has_expr_breakpoint();
+ }
}
/// ":breaklist".
void ex_breaklist(exarg_T *eap)
{
if (GA_EMPTY(&dbg_breakp)) {
- msg(_("No breakpoints defined"));
+ msg(_("No breakpoints defined"), 0);
return;
}
for (int i = 0; i < dbg_breakp.ga_len; i++) {
struct debuggy *bp = &BREAKP(i);
if (bp->dbg_type == DBG_FILE) {
- home_replace(NULL, bp->dbg_name, (char *)NameBuff, MAXPATHL, true);
+ home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true);
}
if (bp->dbg_type != DBG_EXPR) {
- smsg(_("%3d %s %s line %" PRId64),
+ smsg(0, _("%3d %s %s line %" PRId64),
bp->dbg_nr,
bp->dbg_type == DBG_FUNC ? "func" : "file",
bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
(int64_t)bp->dbg_lnum);
} else {
- smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name);
+ smsg(0, _("%3d expr %s"), bp->dbg_nr, bp->dbg_name);
}
}
}
@@ -759,8 +771,8 @@ linenr_T dbg_find_breakpoint(bool file, char *fname, linenr_T after)
/// @returns true if profiling is on for a function or sourced file.
bool has_profiling(bool file, char *fname, bool *fp)
{
- return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp)
- != (linenr_T)0;
+ return debuggy_find(file, fname, 0, &prof_ga, fp)
+ != 0;
}
/// Common code for dbg_find_breakpoint() and has_profiling().
@@ -779,7 +791,7 @@ static linenr_T debuggy_find(bool file, char *fname, linenr_T after, garray_T *g
// Return quickly when there are no breakpoints.
if (GA_EMPTY(gap)) {
- return (linenr_T)0;
+ return 0;
}
// Replace K_SNR in function name with "<SNR>".
@@ -802,7 +814,7 @@ static linenr_T debuggy_find(bool file, char *fname, linenr_T after, garray_T *g
// while matching should abort it.
prev_got_int = got_int;
got_int = false;
- if (vim_regexec_prog(&bp->dbg_prog, false, name, (colnr_T)0)) {
+ if (vim_regexec_prog(&bp->dbg_prog, false, name, 0)) {
lnum = bp->dbg_lnum;
if (fp != NULL) {
*fp = bp->dbg_forceit;
diff --git a/src/nvim/debugger.h b/src/nvim/debugger.h
index 1f1139b3bc..c4caeffd66 100644
--- a/src/nvim/debugger.h
+++ b/src/nvim/debugger.h
@@ -1,11 +1,7 @@
-#ifndef NVIM_DEBUGGER_H
-#define NVIM_DEBUGGER_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "debugger.h.generated.h"
#endif
-#endif // NVIM_DEBUGGER_H
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 63c55ec602..11204a1b31 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -1,23 +1,40 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
#include "nvim/buffer.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/sign_defs.h"
+#include "nvim/move.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sign.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration.c.generated.h"
#endif
+// TODO(bfredl): These should maybe be per-buffer, so that all resources
+// associated with a buffer can be freed when the buffer is unloaded.
+kvec_t(DecorSignHighlight) decor_items = KV_INITIAL_VALUE;
+uint32_t decor_freelist = UINT32_MAX;
+
+// Decorations might be requested to be deleted in a callback in the middle of redrawing.
+// In this case, there might still be live references to the memory allocated for the decoration.
+// Keep a "to free" list which can be safely processed when redrawing is done.
+DecorVirtText *to_free_virt = NULL;
+uint32_t to_free_sh = UINT32_MAX;
+
/// Add highlighting to a buffer, bounded by two cursor positions,
/// with an offset.
///
@@ -36,8 +53,8 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
{
colnr_T hl_start = 0;
colnr_T hl_end = 0;
- Decoration decor = DECORATION_INIT;
- decor.hl_id = hl_id;
+ DecorInline decor = DECOR_INLINE_INIT;
+ decor.data.hl.hl_id = hl_id;
// TODO(bfredl): if decoration had blocky mode, we could avoid this loop
for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) {
@@ -62,68 +79,265 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
hl_start = pos_start.col + offset;
hl_end = pos_end.col + offset;
}
+
extmark_set(buf, (uint32_t)src_id, NULL,
(int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end,
- &decor, true, false, kExtmarkNoUndo);
+ decor, MT_FLAG_DECOR_HL, true, false, true, false, NULL);
}
}
-void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
+void decor_redraw(buf_T *buf, int row1, int row2, DecorInline decor)
{
if (row2 >= row1) {
- if (!decor
- || decor->hl_id
- || decor_has_sign(decor)
- || decor->conceal
- || decor->spell != kNone) {
- redraw_buf_range_later(buf, row1 + 1, row2 + 1);
+ redraw_buf_range_later(buf, row1 + 1, row2 + 1);
+ }
+
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ redraw_buf_line_later(buf, row1 + 1 + ((vt->flags & kVTLinesAbove) ? 0 : 1), true);
+ changed_line_display_buf(buf);
+ } else {
+ if (vt->pos == kVPosInline) {
+ changed_line_display_buf(buf);
+ }
+ }
+ vt = vt->next;
+ }
+
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ decor_redraw_sh(buf, row1, row2, *sh);
+ idx = sh->next;
}
+ } else {
+ decor_redraw_sh(buf, row1, row2, decor_sh_from_inline(decor.data.hl));
}
+}
- if (decor && decor_virt_pos(*decor)) {
+void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh)
+{
+ if (sh.hl_id || (sh.flags & (kSHIsSign|kSHSpellOn|kSHSpellOff))) {
+ if (row2 >= row1) {
+ redraw_buf_range_later(buf, row1 + 1, row2 + 1);
+ }
+ }
+ if (sh.flags & kSHUIWatched) {
redraw_buf_line_later(buf, row1 + 1, false);
}
+}
- if (decor && kv_size(decor->virt_lines)) {
- redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above?0:1), true);
+uint32_t decor_put_sh(DecorSignHighlight item)
+{
+ if (decor_freelist != UINT32_MAX) {
+ uint32_t pos = decor_freelist;
+ decor_freelist = kv_A(decor_items, decor_freelist).next;
+ kv_A(decor_items, pos) = item;
+ return pos;
+ } else {
+ uint32_t pos = (uint32_t)kv_size(decor_items);
+ kv_push(decor_items, item);
+ return pos;
}
}
-void decor_remove(buf_T *buf, int row, int row2, Decoration *decor)
+DecorVirtText *decor_put_vt(DecorVirtText vt, DecorVirtText *next)
+{
+ DecorVirtText *decor_alloc = xmalloc(sizeof *decor_alloc);
+ *decor_alloc = vt;
+ decor_alloc->next = next;
+ return decor_alloc;
+}
+
+DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item)
+{
+ // TODO(bfredl): Eventually simple signs will be inlinable as well
+ assert(!(item.flags & kSHIsSign));
+ DecorSignHighlight conv = {
+ .flags = item.flags,
+ .priority = item.priority,
+ .text.sc[0] = item.conceal_char,
+ .hl_id = item.hl_id,
+ .number_hl_id = 0,
+ .line_hl_id = 0,
+ .cursorline_hl_id = 0,
+ .next = DECOR_ID_INVALID,
+ };
+
+ return conv;
+}
+
+void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2)
+{
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ buf_put_decor_virt(buf, vt);
+ vt = vt->next;
+ }
+
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ buf_put_decor_sh(buf, sh, row, row2);
+ idx = sh->next;
+ }
+ }
+}
+
+void buf_put_decor_virt(buf_T *buf, DecorVirtText *vt)
+{
+ if (vt->flags &kVTIsLines) {
+ buf->b_virt_line_blocks++;
+ } else {
+ if (vt->pos == kVPosInline) {
+ buf->b_virt_text_inline++;
+ }
+ }
+ if (vt->next) {
+ buf_put_decor_virt(buf, vt->next);
+ }
+}
+
+static int sign_add_id = 0;
+void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row, int row2)
+{
+ if (sh->flags & kSHIsSign) {
+ sh->sign_add_id = sign_add_id++;
+ buf->b_signs++;
+ if (sh->text.ptr) {
+ buf->b_signs_with_text++;
+ buf_signcols_add_check(buf, row + 1, row2 + 1);
+ }
+ }
+}
+
+void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool free)
{
decor_redraw(buf, row, row2, decor);
- if (decor) {
- if (kv_size(decor->virt_lines)) {
- assert(buf->b_virt_line_blocks > 0);
- buf->b_virt_line_blocks--;
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ buf_remove_decor_virt(buf, vt);
+ vt = vt->next;
}
- if (decor_has_sign(decor)) {
- assert(buf->b_signs > 0);
- buf->b_signs--;
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ buf_remove_decor_sh(buf, row, row2, sh);
+ idx = sh->next;
}
- if (row2 >= row && decor->sign_text) {
- buf_signcols_del_check(buf, row + 1, row2 + 1);
+ if (free) {
+ decor_free(decor);
}
}
- decor_free(decor);
}
-void decor_free(Decoration *decor)
+void buf_remove_decor_virt(buf_T *buf, DecorVirtText *vt)
{
- if (decor) {
- clear_virttext(&decor->virt_text);
- for (size_t i = 0; i < kv_size(decor->virt_lines); i++) {
- clear_virttext(&kv_A(decor->virt_lines, i).line);
+ if (vt->flags &kVTIsLines) {
+ assert(buf->b_virt_line_blocks > 0);
+ buf->b_virt_line_blocks--;
+ } else {
+ if (vt->pos == kVPosInline) {
+ assert(buf->b_virt_text_inline > 0);
+ buf->b_virt_text_inline--;
+ }
+ }
+}
+
+void buf_remove_decor_sh(buf_T *buf, int row, int row2, DecorSignHighlight *sh)
+{
+ if (sh->flags & kSHIsSign) {
+ assert(buf->b_signs > 0);
+ buf->b_signs--;
+ if (sh->text.ptr) {
+ assert(buf->b_signs_with_text > 0);
+ buf->b_signs_with_text--;
+ if (row2 >= row) {
+ buf_signcols_del_check(buf, row + 1, row2 + 1);
+ }
+ }
+ }
+}
+
+void decor_free(DecorInline decor)
+{
+ if (!decor.ext) {
+ return;
+ }
+ DecorVirtText *vt = decor.data.ext.vt;
+ uint32_t idx = decor.data.ext.sh_idx;
+
+ if (decor_state.running_decor_provider) {
+ while (vt) {
+ if (vt->next == NULL) {
+ vt->next = to_free_virt;
+ to_free_virt = decor.data.ext.vt;
+ break;
+ }
+ vt = vt->next;
+ }
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ if (sh->next == DECOR_ID_INVALID) {
+ sh->next = to_free_sh;
+ to_free_sh = decor.data.ext.sh_idx;
+ break;
+ }
+ idx = sh->next;
+ }
+ } else {
+ // safe to delete right now
+ decor_free_inner(vt, idx);
+ }
+}
+
+void decor_free_inner(DecorVirtText *vt, uint32_t first_idx)
+{
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ clear_virtlines(&vt->data.virt_lines);
+ } else {
+ clear_virttext(&vt->data.virt_text);
+ }
+ DecorVirtText *tofree = vt;
+ vt = vt->next;
+ xfree(tofree);
+ }
+
+ uint32_t idx = first_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ if (sh->flags & kSHIsSign) {
+ xfree(sh->text.ptr);
+ }
+ if (sh->flags & kSHIsSign) {
+ xfree(sh->sign_name);
}
- kv_destroy(decor->virt_lines);
- xfree(decor->sign_text);
- xfree(decor);
+ sh->flags = 0;
+ if (sh->next == DECOR_ID_INVALID) {
+ sh->next = decor_freelist;
+ decor_freelist = first_idx;
+ break;
+ }
+ idx = sh->next;
}
}
+void decor_check_to_be_deleted(void)
+{
+ assert(!decor_state.running_decor_provider);
+ decor_free_inner(to_free_virt, to_free_sh);
+ to_free_virt = NULL;
+ to_free_sh = DECOR_ID_INVALID;
+}
+
void decor_state_free(DecorState *state)
{
- xfree(state->active.items);
+ kv_destroy(state->active);
}
void clear_virttext(VirtText *text)
@@ -135,20 +349,59 @@ void clear_virttext(VirtText *text)
*text = (VirtText)KV_INITIAL_VALUE;
}
-Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
+void clear_virtlines(VirtLines *lines)
+{
+ for (size_t i = 0; i < kv_size(*lines); i++) {
+ clear_virttext(&kv_A(*lines, i).line);
+ }
+ kv_destroy(*lines);
+ *lines = (VirtLines)KV_INITIAL_VALUE;
+}
+
+void decor_check_invalid_glyphs(void)
+{
+ for (size_t i = 0; i < kv_size(decor_items); i++) {
+ DecorSignHighlight *it = &kv_A(decor_items, i);
+ if ((it->flags & kSHConceal) && schar_high(it->text.sc[0])) {
+ it->text.sc[0] = schar_from_char(schar_get_first_codepoint(it->text.sc[0]));
+ }
+ }
+}
+
+/// Get the next chunk of a virtual text item.
+///
+/// @param[in] vt The virtual text item
+/// @param[in,out] pos Position in the virtual text item
+/// @param[in,out] attr Highlight attribute
+///
+/// @return The text of the chunk, or NULL if there are no more chunks
+char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr)
+{
+ char *text = NULL;
+ for (; text == NULL && *pos < kv_size(vt); (*pos)++) {
+ text = kv_A(vt, *pos).text;
+ int hl_id = kv_A(vt, *pos).hl_id;
+ *attr = hl_combine_attr(*attr, hl_id > 0 ? syn_id2attr(hl_id) : 0);
+ }
+ return text;
+}
+
+DecorVirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
{
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row > row) {
break;
- } else if (marktree_decor_level(mark) < kDecorLevelVisible) {
+ } else if (mt_invalid(mark) || !(mark.flags & MT_FLAG_DECOR_EXT)) {
goto next_mark;
}
- Decoration *decor = mark.decor_full;
- if ((ns_id == 0 || ns_id == mark.ns)
- && decor && kv_size(decor->virt_text)) {
+ DecorVirtText *decor = mark.decor_data.ext.vt;
+ while (decor && (decor->flags & kVTIsLines)) {
+ decor = decor->next;
+ }
+ if ((ns_id == 0 || ns_id == mark.ns) && decor) {
return decor;
}
next_mark:
@@ -157,115 +410,107 @@ next_mark:
return NULL;
}
-bool decor_redraw_reset(buf_T *buf, DecorState *state)
+bool decor_redraw_reset(win_T *wp, DecorState *state)
{
state->row = -1;
- state->buf = buf;
+ state->win = wp;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i);
- if (item.virt_text_owned) {
- clear_virttext(&item.decor.virt_text);
+ if (item.owned && item.kind == kDecorKindVirtText) {
+ clear_virttext(&item.data.vt->data.virt_text);
+ xfree(item.data.vt);
}
}
kv_size(state->active) = 0;
- return buf->b_marktree->n_keys;
+ return wp->w_buffer->b_marktree->n_keys;
}
-Decoration get_decor(mtkey_t mark)
+/// @return true if decor has a virtual position (virtual text or ui_watched)
+bool decor_virt_pos(const DecorRange *decor)
{
- if (mark.decor_full) {
- return *mark.decor_full;
- }
- Decoration fake = DECORATION_INIT;
- fake.hl_id = mark.hl_id;
- fake.priority = mark.priority;
- fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL);
- return fake;
+ return (decor->kind == kDecorKindVirtText || decor->kind == kDecorKindUIWatched);
}
-/// @return true if decor has a virtual position (virtual text or ui_watched)
-static bool decor_virt_pos(Decoration decor)
+VirtTextPos decor_virt_pos_kind(const DecorRange *decor)
{
- return kv_size(decor.virt_text) || decor.ui_watched;
+ if (decor->kind == kDecorKindVirtText) {
+ return decor->data.vt->pos;
+ }
+ if (decor->kind == kDecorKindUIWatched) {
+ return decor->data.ui.pos;
+ }
+ return kVPosEndOfLine; // not used; return whatever
}
-bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state)
+bool decor_redraw_start(win_T *wp, int top_row, DecorState *state)
{
+ buf_T *buf = wp->w_buffer;
state->top_row = top_row;
- marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
- if (!state->itr->node) {
+ if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) {
return false;
}
- marktree_itr_rewind(buf->b_marktree, state->itr);
- while (true) {
- mtkey_t mark = marktree_itr_current(state->itr);
- if (mark.pos.row < 0) { // || mark.row > end_row
- break;
- }
- if ((mark.pos.row < top_row && mt_end(mark))
- || marktree_decor_level(mark) < kDecorLevelVisible) {
- goto next_mark;
- }
+ MTPair pair;
- Decoration decor = get_decor(mark);
-
- mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
-
- // Exclude start marks if the end mark position is above the top row
- // Exclude end marks if we have already added the start mark
- if ((mt_start(mark) && altpos.row < top_row && !decor_virt_pos(decor))
- || (mt_end(mark) && altpos.row >= top_row)) {
- goto next_mark;
+ while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) {
+ MTKey m = pair.start;
+ if (mt_invalid(m) || !mt_decor_any(m)) {
+ continue;
}
- if (mt_end(mark)) {
- decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col,
- &decor, false, mark.ns, mark.id);
- } else {
- if (altpos.row == -1) {
- altpos.row = mark.pos.row;
- altpos.col = mark.pos.col;
- }
- decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col,
- &decor, false, mark.ns, mark.id);
- }
-
-next_mark:
- if (marktree_itr_node_done(state->itr)) {
- marktree_itr_next(buf->b_marktree, state->itr);
- break;
- }
- marktree_itr_next(buf->b_marktree, state->itr);
+ decor_range_add_from_inline(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row,
+ pair.end_pos.col,
+ mt_decor(m), false, m.ns, m.id);
}
return true; // TODO(bfredl): check if available in the region
}
-bool decor_redraw_line(buf_T *buf, int row, DecorState *state)
+bool decor_redraw_line(win_T *wp, int row, DecorState *state)
{
if (state->row == -1) {
- decor_redraw_start(buf, row, state);
+ decor_redraw_start(wp, row, state);
}
state->row = row;
state->col_until = -1;
state->eol_col = -1;
- return true; // TODO(bfredl): be more precise
+
+ if (kv_size(state->active)) {
+ return true;
+ }
+
+ MTKey k = marktree_itr_current(state->itr);
+ return (k.pos.row >= 0 && k.pos.row <= row);
}
-static void decor_add(DecorState *state, int start_row, int start_col, int end_row, int end_col,
- Decoration *decor, bool owned, uint64_t ns_id, uint64_t mark_id)
+static void decor_range_add_from_inline(DecorState *state, int start_row, int start_col,
+ int end_row, int end_col, DecorInline decor, bool owned,
+ uint32_t ns, uint32_t mark_id)
{
- int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
-
- DecorRange range = { start_row, start_col, end_row, end_col,
- *decor, attr_id,
- kv_size(decor->virt_text) && owned, -1, ns_id, mark_id };
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ decor_range_add_virt(state, start_row, start_col, end_row, end_col, vt, owned);
+ vt = vt->next;
+ }
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id);
+ idx = sh->next;
+ }
+ } else {
+ DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl);
+ decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id);
+ }
+}
+static void decor_range_insert(DecorState *state, DecorRange range)
+{
kv_pushp(state->active);
size_t index;
for (index = kv_size(state->active) - 1; index > 0; index--) {
DecorRange item = kv_A(state->active, index - 1);
- if (item.decor.priority <= range.decor.priority) {
+ if (item.priority <= range.priority) {
break;
}
kv_A(state->active, index) = kv_A(state->active, index - 1);
@@ -273,8 +518,82 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r
kv_A(state->active, index) = range;
}
-int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *state)
+void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col,
+ DecorVirtText *vt, bool owned)
+{
+ bool is_lines = vt->flags & kVTIsLines;
+ DecorRange range = {
+ .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col,
+ .kind = is_lines ? kDecorKindVirtLines : kDecorKindVirtText,
+ .data.vt = vt,
+ .attr_id = 0,
+ .owned = owned,
+ .priority = vt->priority,
+ .draw_col = -10,
+ };
+ decor_range_insert(state, range);
+}
+
+void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col,
+ DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id)
+{
+ if (sh->flags & kSHIsSign) {
+ return;
+ }
+
+ DecorRange range = {
+ .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col,
+ .kind = kDecorKindHighlight,
+ .data.sh = *sh,
+ .attr_id = 0,
+ .owned = owned,
+ .priority = sh->priority,
+ .draw_col = -10,
+ };
+
+ if (sh->hl_id || (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) {
+ if (sh->hl_id) {
+ range.attr_id = syn_id2attr(sh->hl_id);
+ }
+ decor_range_insert(state, range);
+ }
+
+ if (sh->flags & (kSHUIWatched)) {
+ range.kind = kDecorKindUIWatched;
+ range.data.ui.ns_id = ns;
+ range.data.ui.mark_id = mark_id;
+ range.data.ui.pos = (sh->flags & kSHUIWatchedOverlay) ? kVPosOverlay : kVPosEndOfLine;
+ decor_range_insert(state, range);
+ }
+}
+
+/// Initialize the draw_col of a newly-added virtual text item.
+static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item)
{
+ DecorVirtText *vt = item->kind == kDecorKindVirtText ? item->data.vt : NULL;
+ VirtTextPos pos = decor_virt_pos_kind(item);
+ if (win_col < 0 && pos != kVPosInline) {
+ item->draw_col = win_col;
+ } else if (pos == kVPosOverlay) {
+ item->draw_col = (vt && (vt->flags & kVTHide) && hidden) ? INT_MIN : win_col;
+ } else {
+ item->draw_col = -1;
+ }
+}
+
+void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state)
+{
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->draw_col == -3) {
+ decor_init_draw_col(win_col, hidden, item);
+ }
+ }
+}
+
+int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state)
+{
+ buf_T *buf = wp->w_buffer;
if (col <= state->col_until) {
return state->current;
}
@@ -282,7 +601,7 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *
while (true) {
// TODO(bfredl): check duplicate entry in "intersection"
// branch
- mtkey_t mark = marktree_itr_current(state->itr);
+ MTKey mark = marktree_itr_current(state->itr);
if (mark.pos.row < 0 || mark.pos.row > state->row) {
break;
} else if (mark.pos.row == state->row && mark.pos.col > col) {
@@ -290,21 +609,17 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *
break;
}
- if (mt_end(mark)
- || marktree_decor_level(mark) < kDecorLevelVisible) {
+ if (mt_invalid(mark) || mt_end(mark) || !mt_decor_any(mark)) {
goto next_mark;
}
- Decoration decor = get_decor(mark);
-
- mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
-
+ MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
if (endpos.row == -1) {
endpos = mark.pos;
}
- decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col,
- &decor, false, mark.ns, mark.id);
+ decor_range_add_from_inline(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col,
+ mt_decor(mark), false, mark.ns, mark.id);
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
@@ -312,8 +627,8 @@ next_mark:
int attr = 0;
size_t j = 0;
- bool conceal = 0;
- int conceal_char = 0;
+ int conceal = 0;
+ schar_T conceal_char = 0;
int conceal_attr = 0;
TriState spell = kNone;
@@ -322,7 +637,7 @@ next_mark:
bool active = false, keep = true;
if (item.end_row < state->row
|| (item.end_row == state->row && item.end_col <= col)) {
- if (!(item.start_row >= state->row && decor_virt_pos(item.decor))) {
+ if (!(item.start_row >= state->row && decor_virt_pos(&item))) {
keep = false;
}
} else {
@@ -341,26 +656,32 @@ next_mark:
if (active && item.attr_id > 0) {
attr = hl_combine_attr(attr, item.attr_id);
}
- if (active && item.decor.conceal) {
- conceal = true;
- if (item.start_row == state->row && item.start_col == col && item.decor.conceal_char) {
- conceal_char = item.decor.conceal_char;
+ if (active && item.kind == kDecorKindHighlight && (item.data.sh.flags & kSHConceal)) {
+ conceal = 1;
+ if (item.start_row == state->row && item.start_col == col) {
+ DecorSignHighlight *sh = &item.data.sh;
+ conceal = 2;
+ conceal_char = sh->text.sc[0];
state->col_until = MIN(state->col_until, item.start_col);
conceal_attr = item.attr_id;
}
}
- if (active && item.decor.spell != kNone) {
- spell = item.decor.spell;
+ if (active && item.kind == kDecorKindHighlight) {
+ if (item.data.sh.flags & kSHSpellOn) {
+ spell = kTrue;
+ } else if (item.data.sh.flags & kSHSpellOff) {
+ spell = kFalse;
+ }
}
- if ((item.start_row == state->row && item.start_col <= col)
- && decor_virt_pos(item.decor)
- && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) {
- item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col;
+ if (item.start_row == state->row && item.start_col <= col
+ && decor_virt_pos(&item) && item.draw_col == -10) {
+ decor_init_draw_col(win_col, hidden, &item);
}
if (keep) {
kv_A(state->active, j++) = item;
- } else if (item.virt_text_owned) {
- clear_virttext(&item.decor.virt_text);
+ } else if (item.owned && item.kind == kDecorKindVirtText) {
+ clear_virttext(&item.data.vt->data.virt_text);
+ xfree(item.data.vt);
}
}
kv_size(state->active) = j;
@@ -372,183 +693,172 @@ next_mark:
return attr;
}
-void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[],
- HlPriAttr *num_attrs, HlPriAttr *line_attrs, HlPriAttr *cul_attrs)
+typedef struct {
+ DecorSignHighlight *sh;
+ uint32_t id;
+} SignItem;
+
+int sign_item_cmp(const void *p1, const void *p2)
+{
+ const SignItem *s1 = (SignItem *)p1;
+ const SignItem *s2 = (SignItem *)p2;
+ int n = s2->sh->priority - s1->sh->priority;
+
+ return n ? n : (n = (int)(s2->id - s1->id))
+ ? n : (s2->sh->sign_add_id - s1->sh->sign_add_id);
+}
+
+/// Return the sign attributes on the currently refreshed row.
+///
+/// @param[out] sattrs Output array for sign text and texthl id
+/// @param[out] line_attr Highest priority linehl id
+/// @param[out] cul_attr Highest priority culhl id
+/// @param[out] num_attr Highest priority numhl id
+void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id,
+ int *cul_id, int *num_id)
{
- if (!buf->b_signs) {
+ MarkTreeIter itr[1];
+ if (!buf->b_signs || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) {
return;
}
- MarkTreeIter itr[1] = { 0 };
- marktree_itr_get(buf->b_marktree, row, 0, itr);
+ MTPair pair;
+ int num_text = 0;
+ kvec_t(SignItem) signs = KV_INITIAL_VALUE;
+ // TODO(bfredl): integrate with main decor loop.
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) {
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start));
+ num_text += (sh->text.ptr != NULL);
+ kv_push(signs, ((SignItem){ sh, pair.start.id }));
+ }
+ }
- while (true) {
- mtkey_t mark = marktree_itr_current(itr);
- if (mark.pos.row < 0 || mark.pos.row > row) {
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (mark.pos.row != row) {
break;
}
-
- if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) {
- goto next_mark;
+ if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)) {
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
+ num_text += (sh->text.ptr != NULL);
+ kv_push(signs, ((SignItem){ sh, mark.id }));
}
- Decoration *decor = mark.decor_full;
+ marktree_itr_next(buf->b_marktree, itr);
+ }
- if (!decor || !decor_has_sign(decor)) {
- goto next_mark;
- }
+ if (kv_size(signs)) {
+ int width = wp->w_minscwidth == SCL_NUM ? 1 : wp->w_scwidth;
+ int idx = MIN(width, num_text) - 1;
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(kv_A(signs, 0)), sign_item_cmp);
- if (decor->sign_text) {
- int j;
- for (j = (*num_signs); j > 0; j--) {
- if (sattrs[j - 1].priority >= decor->priority) {
- break;
- }
- sattrs[j] = sattrs[j - 1];
+ for (size_t i = 0; i < kv_size(signs); i++) {
+ DecorSignHighlight *sh = kv_A(signs, i).sh;
+ if (idx >= 0 && sh->text.ptr) {
+ sattrs[idx].text = sh->text.ptr;
+ sattrs[idx--].hl_id = sh->hl_id;
}
- if (j < SIGN_SHOW_MAX) {
- sattrs[j] = (SignTextAttrs) {
- .text = decor->sign_text,
- .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id),
- .priority = decor->priority
- };
- (*num_signs)++;
+ if (*num_id == 0) {
+ *num_id = sh->number_hl_id;
}
- }
-
- struct { HlPriAttr *dest; int hl; } cattrs[] = {
- { line_attrs, decor->line_hl_id },
- { num_attrs, decor->number_hl_id },
- { cul_attrs, decor->cursorline_hl_id },
- { NULL, -1 },
- };
- for (int i = 0; cattrs[i].dest; i++) {
- if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) {
- *cattrs[i].dest = (HlPriAttr) {
- .attr_id = syn_id2attr(cattrs[i].hl),
- .priority = decor->priority
- };
+ if (*line_id == 0) {
+ *line_id = sh->line_hl_id;
+ }
+ if (*cul_id == 0) {
+ *cul_id = sh->cursorline_hl_id;
}
}
-
-next_mark:
- marktree_itr_next(buf->b_marktree, itr);
+ kv_destroy(signs);
}
}
-// Get the maximum required amount of sign columns needed between row and
-// end_row.
-int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max)
+DecorSignHighlight *decor_find_sign(DecorInline decor)
{
- int count = 0; // count for the number of signs on a given row
- int count_remove = 0; // how much to decrement count by when iterating marks for a new row
- int signcols = 0; // highest value of count
- int currow = -1; // current row
-
- if (max <= 1 && buf->b_signs >= (size_t)max) {
- return max;
- }
-
- if (buf->b_signs == 0) {
- return 0;
+ if (!decor.ext) {
+ return NULL;
}
-
- MarkTreeIter itr[1] = { 0 };
- marktree_itr_get(buf->b_marktree, 0, -1, itr);
+ uint32_t decor_id = decor.data.ext.sh_idx;
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
- if (mark.pos.row < 0 || mark.pos.row > end_row) {
- break;
+ if (decor_id == DECOR_ID_INVALID) {
+ return NULL;
}
-
- if ((mark.pos.row < row && mt_end(mark))
- || marktree_decor_level(mark) < kDecorLevelVisible
- || !mark.decor_full) {
- goto next_mark;
- }
-
- Decoration decor = get_decor(mark);
-
- if (!decor.sign_text) {
- goto next_mark;
+ DecorSignHighlight *sh = &kv_A(decor_items, decor_id);
+ if (sh->flags & kSHIsSign) {
+ return sh;
}
+ decor_id = sh->next;
+ }
+}
- if (mark.pos.row > currow) {
- count -= count_remove;
- count_remove = 0;
- currow = mark.pos.row;
+// Increase the signcolumn size and update the sentinel line if necessary for
+// the invalidated range.
+void decor_validate_signcols(buf_T *buf, int max)
+{
+ int signcols = 0; // highest value of count
+ int currow = buf->b_signcols.invalid_top - 1;
+ // TODO(bfredl): only need to use marktree_itr_get_overlap once.
+ // then we can process both start and end events and update state for each row
+ for (; currow < buf->b_signcols.invalid_bot; currow++) {
+ MarkTreeIter itr[1];
+ if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) {
+ continue;
}
- if (!mt_paired(mark)) {
- if (mark.pos.row >= row) {
+ int count = 0;
+ MTPair pair;
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ if (!mt_invalid(pair.start) && (pair.start.flags & MT_FLAG_DECOR_SIGNTEXT)) {
count++;
- if (count > signcols) {
- signcols = count;
- if (signcols >= max) {
- return max;
- }
- }
- count_remove++;
}
- goto next_mark;
}
- mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
-
- if (mt_end(mark)) {
- if (mark.pos.row >= row && altpos.row <= end_row) {
- count_remove++;
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (mark.pos.row != currow) {
+ break;
}
- } else {
- if (altpos.row >= row) {
+ if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) {
count++;
- if (count > signcols) {
- signcols = count;
- if (signcols >= max) {
- return max;
- }
- }
}
+ marktree_itr_next(buf->b_marktree, itr);
}
-next_mark:
- marktree_itr_next(buf->b_marktree, itr);
+ if (count > signcols) {
+ if (count >= buf->b_signcols.size) {
+ buf->b_signcols.size = count;
+ buf->b_signcols.sentinel = currow + 1;
+ }
+ if (count >= max) {
+ return;
+ }
+ signcols = count;
+ }
}
-
- return signcols;
}
void decor_redraw_end(DecorState *state)
{
- state->buf = NULL;
+ state->win = NULL;
}
-bool decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, int eol_col)
+bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
{
- decor_redraw_col(buf, MAXCOL, MAXCOL, false, state);
+ decor_redraw_col(wp, MAXCOL, MAXCOL, false, state);
state->eol_col = eol_col;
- bool has_virttext = false;
+ bool has_virt_pos = false;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i);
- if (item.start_row == state->row && decor_virt_pos(item.decor)) {
- has_virttext = true;
+ if (item.start_row == state->row && decor_virt_pos(&item)) {
+ has_virt_pos = true;
}
- if (item.decor.hl_eol && item.start_row <= state->row) {
+ if (item.kind == kDecorKindHighlight
+ && (item.data.sh.flags & kSHHlEol) && item.start_row <= state->row) {
*eol_attr = hl_combine_attr(*eol_attr, item.attr_id);
}
}
- return has_virttext;
-}
-
-void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor,
- uint64_t ns_id, uint64_t mark_id)
-{
- if (end_row == -1) {
- end_row = start_row;
- end_col = start_col;
- }
- decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id);
+ return has_virt_pos;
}
/// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet
@@ -561,30 +871,42 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
return 0;
}
- int virt_lines = 0;
- int row = MAX(lnum - 2, 0);
- int end_row = (int)lnum;
- MarkTreeIter itr[1] = { 0 };
- marktree_itr_get(buf->b_marktree, row, 0, itr);
+ assert(lnum > 0);
bool below_fold = lnum > 1 && hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL);
if (has_fold == kNone) {
has_fold = hasFoldingWin(wp, lnum, NULL, NULL, true, NULL);
}
+
+ const int row = lnum - 1;
+ const int start_row = below_fold ? row : MAX(row - 1, 0);
+ const int end_row = has_fold ? row : row + 1;
+ if (start_row >= end_row) {
+ return 0;
+ }
+
+ int virt_lines = 0;
+ MarkTreeIter itr[1] = { 0 };
+ marktree_itr_get(buf->b_marktree, start_row, 0, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row >= end_row) {
break;
- } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) {
+ } else if (mt_end(mark) || !(mark.flags & MT_FLAG_DECOR_VIRT_LINES)) {
goto next_mark;
}
- bool above = mark.pos.row > (lnum - 2);
- bool has_fold_cur = above ? has_fold : below_fold;
- Decoration *decor = mark.decor_full;
- if (!has_fold_cur && decor && decor->virt_lines_above == above) {
- virt_lines += (int)kv_size(decor->virt_lines);
- if (lines) {
- kv_splice(*lines, decor->virt_lines);
+ DecorVirtText *vt = mark.decor_data.ext.vt;
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ bool above = vt->flags & kVTLinesAbove;
+ int draw_row = mark.pos.row + (above ? 0 : 1);
+ if (draw_row == row) {
+ virt_lines += (int)kv_size(vt->data.virt_lines);
+ if (lines) {
+ kv_splice(*lines, vt->data.virt_lines);
+ }
+ }
}
+ vt = vt->next;
}
next_mark:
marktree_itr_next(buf->b_marktree, itr);
@@ -592,3 +914,153 @@ next_mark:
return virt_lines;
}
+
+/// This assumes maximum one entry of each kind, which will not always be the case.
+void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
+{
+ DecorSignHighlight sh_hl = DECOR_SIGN_HIGHLIGHT_INIT;
+ DecorSignHighlight sh_sign = DECOR_SIGN_HIGHLIGHT_INIT;
+ DecorVirtText *virt_text = NULL;
+ DecorVirtText *virt_lines = NULL;
+ int32_t priority = -1; // sentinel value which cannot actually be set
+
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ virt_lines = vt;
+ } else {
+ virt_text = vt;
+ }
+ vt = vt->next;
+ }
+
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ if (sh->flags & (kSHIsSign)) {
+ sh_sign = *sh;
+ } else {
+ sh_hl = *sh;
+ }
+ idx = sh->next;
+ }
+ } else {
+ sh_hl = decor_sh_from_inline(decor.data.hl);
+ }
+
+ if (sh_hl.hl_id) {
+ PUT(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name));
+ PUT(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol));
+ if (sh_hl.flags & kSHConceal) {
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, sh_hl.text.sc[0]);
+ PUT(*dict, "conceal", CSTR_TO_OBJ(buf));
+ }
+
+ if (sh_hl.flags & kSHSpellOn) {
+ PUT(*dict, "spell", BOOLEAN_OBJ(true));
+ } else if (sh_hl.flags & kSHSpellOff) {
+ PUT(*dict, "spell", BOOLEAN_OBJ(false));
+ }
+
+ priority = sh_hl.priority;
+ }
+
+ if (sh_hl.flags & kSHUIWatched) {
+ PUT(*dict, "ui_watched", BOOLEAN_OBJ(true));
+ }
+
+ if (virt_text) {
+ if (virt_text->hl_mode) {
+ PUT(*dict, "hl_mode", CSTR_TO_OBJ(hl_mode_str[virt_text->hl_mode]));
+ }
+
+ Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name);
+ PUT(*dict, "virt_text", ARRAY_OBJ(chunks));
+ PUT(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide));
+ if (virt_text->pos == kVPosWinCol) {
+ PUT(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col));
+ }
+ PUT(*dict, "virt_text_pos",
+ CSTR_TO_OBJ(virt_text_pos_str[virt_text->pos]));
+ priority = virt_text->priority;
+ }
+
+ if (virt_lines) {
+ Array all_chunks = ARRAY_DICT_INIT;
+ bool virt_lines_leftcol = false;
+ for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) {
+ virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col;
+ Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name);
+ ADD(all_chunks, ARRAY_OBJ(chunks));
+ }
+ PUT(*dict, "virt_lines", ARRAY_OBJ(all_chunks));
+ PUT(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove));
+ PUT(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ priority = virt_lines->priority;
+ }
+
+ if (sh_sign.flags & kSHIsSign) {
+ if (sh_sign.text.ptr) {
+ PUT(*dict, "sign_text", CSTR_TO_OBJ(sh_sign.text.ptr));
+ }
+
+ if (sh_sign.sign_name) {
+ PUT(*dict, "sign_name", CSTR_TO_OBJ(sh_sign.sign_name));
+ }
+
+ // uncrustify:off
+
+ struct { char *name; const int val; } hls[] = {
+ { "sign_hl_group" , sh_sign.hl_id },
+ { "number_hl_group" , sh_sign.number_hl_id },
+ { "line_hl_group" , sh_sign.line_hl_id },
+ { "cursorline_hl_group", sh_sign.cursorline_hl_id },
+ { NULL, 0 },
+ };
+
+ // uncrustify:on
+
+ for (int j = 0; hls[j].name; j++) {
+ if (hls[j].val) {
+ PUT(*dict, hls[j].name, hl_group_name(hls[j].val, hl_name));
+ }
+ }
+ priority = sh_sign.priority;
+ }
+
+ if (priority != -1) {
+ PUT(*dict, "priority", INTEGER_OBJ(priority));
+ }
+}
+
+uint16_t decor_type_flags(DecorInline decor)
+{
+ if (decor.ext) {
+ uint16_t type_flags = kExtmarkNone;
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ type_flags |= (vt->flags & kVTIsLines) ? kExtmarkVirtLines : kExtmarkVirtText;
+ vt = vt->next;
+ }
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ type_flags |= (sh->flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight;
+ idx = sh->next;
+ }
+ return type_flags;
+ } else {
+ return (decor.data.hl.flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight;
+ }
+}
+
+Object hl_group_name(int hl_id, bool hl_name)
+{
+ if (hl_name) {
+ return CSTR_TO_OBJ(syn_id2name(hl_id));
+ } else {
+ return INTEGER_OBJ(hl_id);
+ }
+}
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index c9ec8ede7f..f5448c051b 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -1,120 +1,81 @@
-#ifndef NVIM_DECORATION_H
-#define NVIM_DECORATION_H
+#pragma once
#include <stdbool.h>
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/buffer_defs.h"
-#include "nvim/extmark_defs.h"
-#include "nvim/macros.h"
+#include "nvim/decoration_defs.h" // IWYU pragma: export
+#include "nvim/macros_defs.h"
#include "nvim/marktree.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h"
-// actual Decoration data is in extmark_defs.h
+// actual Decor* data is in decoration_defs.h
-typedef uint16_t DecorPriority;
-#define DECOR_PRIORITY_BASE 0x1000
+EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "overlay", "win_col", "right_align",
+ "inline" });
-typedef enum {
- kVTEndOfLine,
- kVTOverlay,
- kVTWinCol,
- kVTRightAlign,
-} VirtTextPos;
-
-EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align" });
+EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" });
typedef enum {
- kHlModeUnknown,
- kHlModeReplace,
- kHlModeCombine,
- kHlModeBlend,
-} HlMode;
-
-EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" });
-
-#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
-
-typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;
-
-struct Decoration {
- VirtText virt_text;
- VirtLines virt_lines;
-
- int hl_id; // highlight group
- VirtTextPos virt_text_pos;
- HlMode hl_mode;
-
- // TODO(bfredl): at some point turn this into FLAGS
- bool virt_text_hide;
- bool hl_eol;
- bool virt_lines_above;
- bool conceal;
- TriState spell;
- // TODO(bfredl): style, etc
- DecorPriority priority;
- int col; // fixed col value, like win_col
- int virt_text_width; // width of virt_text
- char *sign_text;
- int sign_hl_id;
- int number_hl_id;
- int line_hl_id;
- int cursorline_hl_id;
- // TODO(bfredl): in principle this should be a schar_T, but we
- // probably want some kind of glyph cache for that..
- int conceal_char;
- bool ui_watched; // watched for win_extmark
-};
-#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \
- kHlModeUnknown, false, false, false, false, kNone, \
- DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false }
+ kDecorKindHighlight,
+ kDecorKindSign,
+ kDecorKindVirtText,
+ kDecorKindVirtLines,
+ kDecorKindUIWatched,
+} DecorRangeKind;
typedef struct {
int start_row;
int start_col;
int end_row;
int end_col;
- Decoration decor;
- int attr_id; // cached lookup of decor.hl_id
- bool virt_text_owned;
- int win_col;
- uint64_t ns_id;
- uint64_t mark_id;
+ // next pointers MUST NOT be used, these are separate ranges
+ // vt->next could be pointing to freelist memory at this point
+ union {
+ DecorSignHighlight sh;
+ DecorVirtText *vt;
+ struct {
+ uint32_t ns_id;
+ uint32_t mark_id;
+ VirtTextPos pos;
+ } ui;
+ } data;
+ int attr_id; // cached lookup of inl.hl_id if it was a highlight
+ bool owned; // ephemeral decoration, free memory immediately
+ DecorPriority priority;
+ DecorRangeKind kind;
+ /// Screen column to draw the virtual text.
+ /// When -1, the virtual text may be drawn after deciding where.
+ /// When -3, the virtual text should be drawn on the next screen line.
+ /// When -10, the virtual text has just been added.
+ /// When INT_MIN, the virtual text should no longer be drawn.
+ int draw_col;
} DecorRange;
typedef struct {
MarkTreeIter itr[1];
kvec_t(DecorRange) active;
- buf_T *buf;
+ win_T *win;
int top_row;
int row;
int col_until;
int current;
int eol_col;
- bool conceal;
- int conceal_char;
+ int conceal;
+ schar_T conceal_char;
int conceal_attr;
TriState spell;
-} DecorState;
-EXTERN DecorState decor_state INIT(= { 0 });
+ bool running_decor_provider;
+} DecorState;
-static inline bool decor_has_sign(Decoration *decor)
-{
- return decor->sign_text
- || decor->sign_hl_id
- || decor->number_hl_id
- || decor->line_hl_id
- || decor->cursorline_hl_id;
-}
+EXTERN DecorState decor_state INIT( = { 0 });
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration.h.generated.h"
#endif
-
-#endif // NVIM_DECORATION_H
diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h
new file mode 100644
index 0000000000..dc5d7b9ae4
--- /dev/null
+++ b/src/nvim/decoration_defs.h
@@ -0,0 +1,125 @@
+#pragma once
+
+#include <stdint.h>
+
+#include "klib/kvec.h"
+#include "nvim/types_defs.h"
+
+#define DECOR_ID_INVALID UINT32_MAX
+
+typedef struct {
+ char *text;
+ int hl_id;
+} VirtTextChunk;
+
+typedef kvec_t(VirtTextChunk) VirtText;
+#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
+
+typedef enum {
+ kVPosEndOfLine,
+ kVPosOverlay,
+ kVPosWinCol,
+ kVPosRightAlign,
+ kVPosInline,
+} VirtTextPos;
+
+typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;
+
+typedef uint16_t DecorPriority;
+#define DECOR_PRIORITY_BASE 0x1000
+
+typedef enum {
+ kHlModeUnknown,
+ kHlModeReplace,
+ kHlModeCombine,
+ kHlModeBlend,
+} HlMode;
+
+enum {
+ kSHIsSign = 1,
+ kSHHlEol = 2,
+ kSHUIWatched = 4,
+ kSHUIWatchedOverlay = 8,
+ kSHSpellOn = 16,
+ kSHSpellOff = 32,
+ kSHConceal = 64,
+};
+
+typedef struct {
+ uint16_t flags;
+ DecorPriority priority;
+ int hl_id;
+ schar_T conceal_char;
+} DecorHighlightInline;
+
+#define DECOR_HIGHLIGHT_INLINE_INIT { 0, DECOR_PRIORITY_BASE, 0, 0 }
+typedef struct {
+ uint16_t flags;
+ DecorPriority priority;
+ int hl_id; // if sign: highlight of sign text
+ // TODO(bfredl): Later signs should use sc[2] as well.
+ union {
+ char *ptr; // sign
+ schar_T sc[2]; // conceal text (only sc[0] used)
+ } text;
+ // NOTE: if more functionality is added to a Highlight these should be overloaded
+ // or restructured
+ char *sign_name;
+ int sign_add_id;
+ int number_hl_id;
+ int line_hl_id;
+ int cursorline_hl_id;
+ uint32_t next;
+} DecorSignHighlight;
+
+#define DECOR_SIGN_HIGHLIGHT_INIT { 0, DECOR_PRIORITY_BASE, 0, { .ptr = NULL }, NULL, 0, 0, 0, 0, \
+ DECOR_ID_INVALID }
+
+enum {
+ kVTIsLines = 1,
+ kVTHide = 2,
+ kVTLinesAbove = 4,
+};
+
+typedef struct DecorVirtText DecorVirtText;
+struct DecorVirtText {
+ uint8_t flags;
+ uint8_t hl_mode;
+ DecorPriority priority;
+ int width; // width of virt_text
+ int col;
+ VirtTextPos pos;
+ // TODO(bfredl): reduce this to one datatype, later
+ union {
+ VirtText virt_text;
+ VirtLines virt_lines;
+ } data;
+ DecorVirtText *next;
+};
+#define DECOR_VIRT_TEXT_INIT { 0, kHlModeUnknown, DECOR_PRIORITY_BASE, 0, 0, kVPosEndOfLine, \
+ { .virt_text = KV_INITIAL_VALUE }, NULL, }
+#define DECOR_VIRT_LINES_INIT { kVTIsLines, kHlModeUnknown, DECOR_PRIORITY_BASE, 0, 0, \
+ kVPosEndOfLine, { .virt_lines = KV_INITIAL_VALUE }, NULL, }
+
+typedef struct {
+ uint32_t sh_idx;
+ DecorVirtText *vt;
+} DecorExt;
+
+// Stored inline in marktree, with MT_FLAG_DECOR_EXT in MTKey.flags
+typedef union {
+ DecorHighlightInline hl;
+ DecorExt ext;
+} DecorInlineData;
+
+// Not stored in the marktree, but used when passing around args
+//
+// Convention: an empty "no decoration" value should always be encoded
+// with ext=false and an unset DecorHighlightInline (no flags, no hl_id)
+typedef struct {
+ bool ext;
+ DecorInlineData data;
+} DecorInline;
+
+// initializes in a valid state for the DecorHighlightInline branch
+#define DECOR_INLINE_INIT { .ext = false, .data.hl = DECOR_HIGHLIGHT_INLINE_INIT }
diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c
index ed21474935..172eb569c9 100644
--- a/src/nvim/decoration_provider.c
+++ b/src/nvim/decoration_provider.c
@@ -1,33 +1,37 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <stdio.h>
+#include <lauxlib.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/buffer_defs.h"
+#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/message.h"
+#include "nvim/pos_defs.h"
static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, false, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, \
- LUA_NOREF, -1, false, false }
+ LUA_NOREF, -1, false, false, 0 }
+
+static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg)
+{
+ const char *ns_name = describe_ns(provider->ns_id, "(UNKNOWN PLUGIN)");
+ ELOG("error in provider %s.%s: %s", ns_name, name, msg);
+ msg_schedule_semsg_multiline("Error in decoration provider %s.%s:\n%s", ns_name, name, msg);
+}
-static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args,
- bool default_true, char **perr)
+static bool decor_provider_invoke(DecorProvider *provider, const char *name, LuaRef ref, Array args,
+ bool default_true)
{
Error err = ERROR_INIT;
@@ -39,26 +43,25 @@ static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array
if (!ERROR_SET(&err)
&& api_object_to_bool(ret, "provider %s retval", default_true, &err)) {
+ provider->error_count = 0;
return true;
}
if (ERROR_SET(&err)) {
- const char *ns_name = describe_ns(ns_id);
- ELOG("error in provider %s:%s: %s", ns_name, name, err.msg);
- bool verbose_errs = true; // TODO(bfredl):
- if (verbose_errs && perr && *perr == NULL) {
- static char errbuf[IOSIZE];
- snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg);
- *perr = xstrdup(errbuf);
+ decor_provider_error(provider, name, err.msg);
+ provider->error_count++;
+
+ if (provider->error_count >= DP_MAX_ERROR) {
+ provider->active = false;
}
}
+ api_clear_error(&err);
api_free_object(ret);
return false;
}
-void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int end_row, int end_col,
- char **err)
+void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int end_row, int end_col)
{
for (size_t i = 0; i < kv_size(decor_providers); i++) {
DecorProvider *p = &kv_A(decor_providers, i);
@@ -74,7 +77,7 @@ void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int e
ADD_C(args, INTEGER_OBJ(start_col));
ADD_C(args, INTEGER_OBJ(end_row));
ADD_C(args, INTEGER_OBJ(end_col));
- decor_provider_invoke(p->ns_id, "spell", p->spell_nav, args, true, err);
+ decor_provider_invoke(p, "spell", p->spell_nav, args, true);
}
}
}
@@ -83,7 +86,7 @@ void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int e
///
/// @param[out] providers Decoration providers
/// @param[out] err Provider err
-void decor_providers_start(DecorProviders *providers, char **err)
+void decor_providers_start(DecorProviders *providers)
{
kvi_init(*providers);
@@ -97,7 +100,7 @@ void decor_providers_start(DecorProviders *providers, char **err)
if (p->redraw_start != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, INTEGER_OBJ((int)display_tick));
- active = decor_provider_invoke(p->ns_id, "start", p->redraw_start, args, true, err);
+ active = decor_provider_invoke(p, "start", p->redraw_start, args, true);
} else {
active = true;
}
@@ -116,13 +119,17 @@ void decor_providers_start(DecorProviders *providers, char **err)
/// @param[out] line_providers Enabled line providers to invoke in win_line
/// @param[out] err Provider error
void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
- DecorProviders *line_providers, char **err)
+ DecorProviders *line_providers)
{
kvi_init(*line_providers);
+ // this might change in the future
+ // then we would need decor_state.running_decor_provider just like "on_line" below
+ assert(kv_size(decor_state.active) == 0);
- linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
- ? wp->w_botline
- : (wp->w_topline + wp->w_height_inner));
+ linenr_T knownmax = MIN(wp->w_buffer->b_ml.ml_line_count,
+ ((wp->w_valid & VALID_BOTLINE)
+ ? wp->w_botline
+ : MAX(wp->w_topline + wp->w_height_inner, wp->w_botline)));
for (size_t k = 0; k < kv_size(*providers); k++) {
DecorProvider *p = kv_A(*providers, k);
@@ -132,8 +139,8 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
// TODO(bfredl): we are not using this, but should be first drawn line?
ADD_C(args, INTEGER_OBJ(wp->w_topline - 1));
- ADD_C(args, INTEGER_OBJ(knownmax));
- if (decor_provider_invoke(p->ns_id, "win", p->redraw_win, args, true, err)) {
+ ADD_C(args, INTEGER_OBJ(knownmax - 1));
+ if (decor_provider_invoke(p, "win", p->redraw_win, args, true)) {
kvi_push(*line_providers, p);
}
}
@@ -147,9 +154,9 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
/// @param row Row to invoke line callback for
/// @param[out] has_decor Set when at least one provider invokes a line callback
/// @param[out] err Provider error
-void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor,
- char **err)
+void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor)
{
+ decor_state.running_decor_provider = true;
for (size_t k = 0; k < kv_size(*providers); k++) {
DecorProvider *p = kv_A(*providers, k);
if (p && p->redraw_line != LUA_NOREF) {
@@ -157,7 +164,7 @@ void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row,
ADD_C(args, WINDOW_OBJ(wp->handle));
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
ADD_C(args, INTEGER_OBJ(row));
- if (decor_provider_invoke(p->ns_id, "line", p->redraw_line, args, true, err)) {
+ if (decor_provider_invoke(p, "line", p->redraw_line, args, true)) {
*has_decor = true;
} else {
// return 'false' or error: skip rest of this window
@@ -167,6 +174,7 @@ void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row,
hl_check_ns();
}
}
+ decor_state.running_decor_provider = false;
}
/// For each provider invoke the 'buf' callback for a given buffer.
@@ -174,14 +182,15 @@ void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row,
/// @param buf Buffer
/// @param providers Decoration providers
/// @param[out] err Provider error
-void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers, char **err)
+void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers)
{
for (size_t i = 0; i < kv_size(*providers); i++) {
DecorProvider *p = kv_A(*providers, i);
if (p && p->redraw_buf != LUA_NOREF) {
- MAXSIZE_TEMP_ARRAY(args, 1);
+ MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, BUFFER_OBJ(buf->handle));
- decor_provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true, err);
+ ADD_C(args, INTEGER_OBJ((int64_t)display_tick));
+ decor_provider_invoke(p, "buf", p->redraw_buf, args, true);
}
}
}
@@ -191,16 +200,17 @@ void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers, char **er
/// @param providers Decoration providers
/// @param displaytick Display tick
/// @param[out] err Provider error
-void decor_providers_invoke_end(DecorProviders *providers, char **err)
+void decor_providers_invoke_end(DecorProviders *providers)
{
for (size_t i = 0; i < kv_size(*providers); i++) {
DecorProvider *p = kv_A(*providers, i);
if (p && p->active && p->redraw_end != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 1);
ADD_C(args, INTEGER_OBJ((int)display_tick));
- decor_provider_invoke(p->ns_id, "end", p->redraw_end, args, true, err);
+ decor_provider_invoke(p, "end", p->redraw_end, args, true);
}
}
+ decor_check_to_be_deleted();
}
/// Mark all cached state of per-namespace highlights as invalid. Revalidate
diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h
index b91ddabdfd..e0dd67a6f7 100644
--- a/src/nvim/decoration_provider.h
+++ b/src/nvim/decoration_provider.h
@@ -1,12 +1,14 @@
-#ifndef NVIM_DECORATION_PROVIDER_H
-#define NVIM_DECORATION_PROVIDER_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
#include "klib/kvec.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
+
+#define DP_MAX_ERROR 3
typedef struct {
NS ns_id;
@@ -20,14 +22,14 @@ typedef struct {
LuaRef spell_nav;
int hl_valid;
bool hl_cached;
+
+ uint8_t error_count;
} DecorProvider;
typedef kvec_withinit_t(DecorProvider *, 4) DecorProviders;
-EXTERN bool provider_active INIT(= false);
+EXTERN bool provider_active INIT( = false);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration_provider.h.generated.h"
#endif
-
-#endif // NVIM_DECORATION_PROVIDER_H
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 032de561b3..0b7f6f266b 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file diff.c
///
/// Code for diff'ing two, three or four buffers.
@@ -19,9 +16,10 @@
#include <string.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
+#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
@@ -31,9 +29,10 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
-#include "nvim/extmark_defs.h"
+#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -46,17 +45,18 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#include "xdiff/xdiff.h"
@@ -80,7 +80,7 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
-static long diff_algorithm = 0;
+static int diff_algorithm = 0;
static int linematch_lines = 0;
#define LBUFLEN 50 // length of line in diff file
@@ -104,9 +104,9 @@ typedef struct {
// used for recording hunks from xdiff
typedef struct {
linenr_T lnum_orig;
- long count_orig;
+ int count_orig;
linenr_T lnum_new;
- long count_new;
+ int count_new;
} diffhunk_T;
// two diff inputs and one result
@@ -264,24 +264,25 @@ void diff_invalidate(buf_T *buf)
}
}
-/// Called by mark_adjust(): update line numbers in "curbuf".
+/// Called by mark_adjust(): update line numbers in "buf".
///
/// @param line1
/// @param line2
/// @param amount
/// @param amount_after
-void diff_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after)
+void diff_mark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount,
+ linenr_T amount_after)
{
- // Handle all tab pages that use the current buffer in a diff.
+ // Handle all tab pages that use "buf" in a diff.
FOR_ALL_TABS(tp) {
- int idx = diff_buf_idx_tp(curbuf, tp);
+ int idx = diff_buf_idx_tp(buf, tp);
if (idx != DB_COUNT) {
diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after);
}
}
}
-/// Update line numbers in tab page "tp" for "curbuf" with index "idx".
+/// Update line numbers in tab page "tp" for the buffer with index "idx".
///
/// This attempts to update the changes as much as possible:
/// When inserting/deleting lines outside of existing change blocks, create a
@@ -326,7 +327,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
diff_T *dp = tp->tp_first_diff;
linenr_T lnum_deleted = line1; // lnum of remaining deletion
- for (;;) {
+ while (true) {
// If the change is after the previous diff block and before the next
// diff block, thus not touching an existing change, create a new diff
// block. Don't do this when ex_diffgetput() is busy.
@@ -340,8 +341,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
dnext->df_lnum[idx] = line1;
dnext->df_count[idx] = inserted;
- int i;
- for (i = 0; i < DB_COUNT; i++) {
+ for (int i = 0; i < DB_COUNT; i++) {
if ((tp->tp_diffbuf[i] != NULL) && (i != idx)) {
if (dprev == NULL) {
dnext->df_lnum[i] = line1;
@@ -472,8 +472,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
// check if this block touches the previous one, may merge them.
if ((dprev != NULL) && !dp->is_linematched
&& (dprev->df_lnum[idx] + dprev->df_count[idx] == dp->df_lnum[idx])) {
- int i;
- for (i = 0; i < DB_COUNT; i++) {
+ for (int i = 0; i < DB_COUNT; i++) {
if (tp->tp_diffbuf[i] != NULL) {
dprev->df_count[i] += dp->df_count[i];
}
@@ -586,7 +585,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp)
linenr_T off_org = 0;
linenr_T off_new = 0;
int dir = FORWARD;
- for (;;) {
+ while (true) {
// Repeat until a line is found which is different or the number of
// lines has become zero.
while (dp->df_count[i_org] > 0) {
@@ -594,9 +593,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp)
if (dir == BACKWARD) {
off_org = dp->df_count[i_org] - 1;
}
- char *line_org = xstrdup(ml_get_buf(tp->tp_diffbuf[i_org],
- dp->df_lnum[i_org] + off_org,
- false));
+ char *line_org = xstrdup(ml_get_buf(tp->tp_diffbuf[i_org], dp->df_lnum[i_org] + off_org));
int i_new;
for (i_new = i_org + 1; i_new < DB_COUNT; i_new++) {
@@ -614,8 +611,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp)
}
if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new],
- dp->df_lnum[i_new] + off_new,
- false)) != 0) {
+ dp->df_lnum[i_new] + off_new)) != 0) {
break;
}
}
@@ -754,7 +750,7 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e
// xdiff requires one big block of memory with all the text.
for (linenr_T lnum = start; lnum <= end; lnum++) {
- len += strlen(ml_get_buf(buf, lnum, false)) + 1;
+ len += strlen(ml_get_buf(buf, lnum)) + 1;
}
char *ptr = try_malloc(len);
if (ptr == NULL) {
@@ -764,18 +760,18 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e
buf->b_diff_failed = true;
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Not enough memory to use internal diff for buffer \"%s\""),
+ smsg(0, _("Not enough memory to use internal diff for buffer \"%s\""),
buf->b_fname);
verbose_leave();
}
return FAIL;
}
m->ptr = ptr;
- m->size = (long)len;
+ m->size = (int)len;
len = 0;
for (linenr_T lnum = start; lnum <= end; lnum++) {
- char *s = ml_get_buf(buf, lnum, false);
+ char *s = ml_get_buf(buf, lnum);
if (diff_flags & DIFF_ICASE) {
while (*s != NUL) {
char cbuf[MB_MAXBYTES + 1];
@@ -824,7 +820,7 @@ static int diff_write(buf_T *buf, diffin_T *din)
// so it shouldn't update the '[ and '] marks.
cmdmod.cmod_flags |= CMOD_LOCKMARKS;
int r = buf_write(buf, din->din_fname, NULL,
- (linenr_T)1, buf->b_ml.ml_line_count,
+ 1, buf->b_ml.ml_line_count,
NULL, false, false, false, true);
cmdmod.cmod_flags = save_cmod_flags;
free_string_option(buf->b_p_ff);
@@ -1010,7 +1006,7 @@ static int check_external_diff(diffio_T *diffio)
// May try twice, first with "-a" and then without.
int io_error = false;
TriState ok = kFalse;
- for (;;) {
+ while (true) {
ok = kFalse;
FILE *fd = os_fopen(diffio->dio_orig.din_fname, "w");
@@ -1040,7 +1036,7 @@ static int check_external_diff(diffio_T *diffio)
} else {
char linebuf[LBUFLEN];
- for (;;) {
+ while (true) {
// For normal diff there must be a line that contains
// "1c1". For unified diff "@@ -1 +1 @@".
if (vim_fgets(linebuf, LBUFLEN, fd)) {
@@ -1208,7 +1204,7 @@ void ex_diffpatch(exarg_T *eap)
// Write the current buffer to "tmp_orig".
if (buf_write(curbuf, tmp_orig, NULL,
- (linenr_T)1, curbuf->b_ml.ml_line_count,
+ 1, curbuf->b_ml.ml_line_count,
NULL, false, false, false, true) == FAIL) {
goto theend;
}
@@ -1387,14 +1383,14 @@ void ex_diffthis(exarg_T *eap)
diff_win_options(curwin, true);
}
-static void set_diff_option(win_T *wp, int value)
+static void set_diff_option(win_T *wp, bool value)
{
win_T *old_curwin = curwin;
curwin = wp;
curbuf = curwin->w_buffer;
curbuf->b_ro_locked++;
- set_option_value_give_err("diff", (long)value, NULL, OPT_LOCAL);
+ set_option_value_give_err("diff", BOOLEAN_OPTVAL(value), OPT_LOCAL);
curbuf->b_ro_locked--;
curwin = old_curwin;
curbuf = curwin->w_buffer;
@@ -1558,7 +1554,7 @@ static bool extract_hunk_internal(diffout_T *dout, diffhunk_T *hunk, int *line_i
// Extract hunk by parsing the diff output from file and calculate the diffstyle.
static bool extract_hunk(FILE *fd, diffhunk_T *hunk, diffstyle_T *diffstyle)
{
- for (;;) {
+ while (true) {
char line[LBUFLEN]; // only need to hold the diff line
if (vim_fgets(line, LBUFLEN, fd)) {
return true; // end of file
@@ -1579,10 +1575,10 @@ static bool extract_hunk(FILE *fd, diffhunk_T *hunk, diffstyle_T *diffstyle)
*diffstyle = DIFF_ED;
} else if ((strncmp(line, "@@ ", 3) == 0)) {
*diffstyle = DIFF_UNIFIED;
- } else if ((strncmp(line, "--- ", 4) == 0) // -V501
- && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501
+ } else if ((strncmp(line, "--- ", 4) == 0)
+ && (vim_fgets(line, LBUFLEN, fd) == 0)
&& (strncmp(line, "+++ ", 4) == 0)
- && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501
+ && (vim_fgets(line, LBUFLEN, fd) == 0)
&& (strncmp(line, "@@ ", 3) == 0)) {
*diffstyle = DIFF_UNIFIED;
} else {
@@ -1745,7 +1741,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio)
}
}
- for (;;) {
+ while (true) {
diffhunk_T hunk = { 0 };
bool eof = dio->dio_internal
? extract_hunk_internal(dout, &hunk, &line_idx)
@@ -2242,11 +2238,9 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2)
}
for (int i = 0; i < dp->df_count[idx1]; i++) {
- char *line = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx1],
- dp->df_lnum[idx1] + i, false));
+ char *line = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx1], dp->df_lnum[idx1] + i));
- int cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2],
- dp->df_lnum[idx2] + i, false));
+ int cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2], dp->df_lnum[idx2] + i));
xfree(line);
if (cmp != 0) {
@@ -2382,7 +2376,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin)
towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]);
if (lnum >= dp->df_lnum[fromidx]) {
- if (diff_flags & DIFF_LINEMATCH) {
+ if (dp->is_linematched) {
calculate_topfill_and_topline(fromidx, toidx, fromwin->w_topline,
fromwin->w_topfill, &towin->w_topfill, &towin->w_topline);
} else {
@@ -2447,7 +2441,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin)
}
// When w_topline changes need to recompute w_botline and cursor position
- invalidate_botline_win(towin);
+ invalidate_botline(towin);
changed_line_abv_curs_win(towin);
check_topfill(towin, false);
@@ -2464,11 +2458,12 @@ int diffopt_changed(void)
int linematch_lines_new = 0;
int diff_flags_new = 0;
int diff_foldcolumn_new = 2;
- long diff_algorithm_new = 0;
- long diff_indent_heuristic = 0;
+ int diff_algorithm_new = 0;
+ int diff_indent_heuristic = 0;
char *p = p_dip;
while (*p != NUL) {
+ // Note: Keep this in sync with p_dip_values
if (strncmp(p, "filler", 6) == 0) {
p += 6;
diff_flags_new |= DIFF_FILLER;
@@ -2515,6 +2510,7 @@ int diffopt_changed(void)
p += 8;
diff_flags_new |= DIFF_INTERNAL;
} else if (strncmp(p, "algorithm:", 10) == 0) {
+ // Note: Keep this in sync with p_dip_algorithm_values.
p += 10;
if (strncmp(p, "myers", 5) == 0) {
p += 5;
@@ -2571,7 +2567,7 @@ int diffopt_changed(void)
// recompute the scroll binding with the new option value, may
// remove or add filler lines
- check_scrollbind((linenr_T)0, 0L);
+ check_scrollbind(0, 0);
return OK;
}
@@ -2615,7 +2611,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
// Make a copy of the line, the next ml_get() will invalidate it.
- char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum, false));
+ char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum));
int idx = diff_buf_idx(wp->w_buffer);
if (idx == DB_COUNT) {
@@ -2652,15 +2648,14 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
bool added = true;
linenr_T off = lnum - dp->df_lnum[idx];
- int i;
- for (i = 0; i < DB_COUNT; i++) {
+ for (int i = 0; i < DB_COUNT; i++) {
if ((curtab->tp_diffbuf[i] != NULL) && (i != idx)) {
// Skip lines that are not in the other change (filler lines).
if (off >= dp->df_count[i]) {
continue;
}
added = false;
- char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off, false);
+ char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off);
// Search for start of difference
si_org = si_new = 0;
@@ -2951,7 +2946,7 @@ void ex_diffgetput(exarg_T *eap)
}
const int idx_from = eap->cmdidx == CMD_diffget ? idx_other : idx_cur;
- const int idx_to = eap->cmdidx == CMD_diffget ? idx_cur : idx_other;
+ const int idx_to = eap->cmdidx == CMD_diffget ? idx_cur : idx_other;
// May give the warning for a changed buffer here, which can trigger the
// FileChangedRO autocommand, which may do nasty things and mess
@@ -3096,15 +3091,18 @@ static void diffgetput(const int addr_count, const int idx_cur, const int idx_fr
if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) {
break;
}
- char *p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false));
+ char *p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr));
ml_append(lnum + i - 1, p, 0, false);
xfree(p);
added++;
if (buf_empty && (curbuf->b_ml.ml_line_count == 2)) {
// Added the first line into an empty buffer, need to
// delete the dummy empty line.
+ // This has a side effect of incrementing curbuf->deleted_bytes,
+ // which results in inaccurate reporting of the byte count of
+ // previous contents in buffer-update events.
buf_empty = false;
- ml_delete((linenr_T)2, false);
+ ml_delete(2, false);
}
}
linenr_T new_count = dp->df_count[idx_to] + added;
@@ -3133,7 +3131,7 @@ static void diffgetput(const int addr_count, const int idx_cur, const int idx_fr
if (added != 0) {
// Adjust marks. This will change the following entries!
- mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, added, kExtmarkUndo);
+ mark_adjust(lnum, lnum + count - 1, MAXLNUM, added, kExtmarkNOOP);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
// lines.
@@ -3144,7 +3142,8 @@ static void diffgetput(const int addr_count, const int idx_cur, const int idx_fr
}
}
}
- changed_lines(lnum, 0, lnum + count, added, true);
+ extmark_adjust(curbuf, lnum, lnum + count - 1, MAXLNUM, added, kExtmarkUndo);
+ changed_lines(curbuf, lnum, 0, lnum + count, added, true);
if (did_free) {
// Diff is deleted, update folds in other windows.
@@ -3214,7 +3213,7 @@ bool diff_mode_buf(buf_T *buf)
/// @param count
///
/// @return FAIL if there isn't such a diff block.
-int diff_move_to(int dir, long count)
+int diff_move_to(int dir, int count)
{
linenr_T lnum = curwin->w_cursor.lnum;
int idx = diff_buf_idx(curbuf);
@@ -3357,7 +3356,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
if (idx == DB_COUNT) {
// safety check
- return (linenr_T)0;
+ return 0;
}
if (curtab->tp_diff_invalid) {
@@ -3383,7 +3382,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
if (i == DB_COUNT) {
// safety check
- return (linenr_T)0;
+ return 0;
}
linenr_T n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]);
@@ -3393,13 +3392,12 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
return n;
}
-///
/// Handle an ED style diff line.
-/// Return FAIL if the line does not contain diff info.
///
+/// @return FAIL if the line does not contain diff info.
static int parse_diff_ed(char *line, diffhunk_T *hunk)
{
- long l1, l2;
+ int l1, l2;
// The line must be one of three formats:
// change: {first}[,{last}]c{first}[,{last}]
@@ -3409,7 +3407,7 @@ static int parse_diff_ed(char *line, diffhunk_T *hunk)
linenr_T f1 = getdigits_int32(&p, true, 0);
if (*p == ',') {
p++;
- l1 = getdigits(&p, true, 0);
+ l1 = getdigits_int(&p, true, 0);
} else {
l1 = f1;
}
@@ -3417,10 +3415,10 @@ static int parse_diff_ed(char *line, diffhunk_T *hunk)
return FAIL; // invalid diff format
}
int difftype = (uint8_t)(*p++);
- long f2 = getdigits(&p, true, 0);
+ int f2 = getdigits_int(&p, true, 0);
if (*p == ',') {
p++;
- l2 = getdigits(&p, true, 0);
+ l2 = getdigits_int(&p, true, 0);
} else {
l2 = f2;
}
@@ -3445,31 +3443,29 @@ static int parse_diff_ed(char *line, diffhunk_T *hunk)
return OK;
}
-///
/// Parses unified diff with zero(!) context lines.
/// Return FAIL if there is no diff information in "line".
-///
static int parse_diff_unified(char *line, diffhunk_T *hunk)
{
// Parse unified diff hunk header:
// @@ -oldline,oldcount +newline,newcount @@
char *p = line;
if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') {
- long oldcount;
- long newline;
- long newcount;
- long oldline = getdigits(&p, true, 0);
+ int oldcount;
+ linenr_T newline;
+ int newcount;
+ linenr_T oldline = getdigits_int32(&p, true, 0);
if (*p == ',') {
p++;
- oldcount = getdigits(&p, true, 0);
+ oldcount = getdigits_int(&p, true, 0);
} else {
oldcount = 1;
}
if (*p++ == ' ' && *p++ == '+') {
- newline = getdigits(&p, true, 0);
+ newline = getdigits_int(&p, true, 0);
if (*p == ',') {
p++;
- newcount = getdigits(&p, true, 0);
+ newcount = getdigits_int(&p, true, 0);
} else {
newcount = 1;
}
@@ -3487,9 +3483,9 @@ static int parse_diff_unified(char *line, diffhunk_T *hunk)
newline = 1;
}
- hunk->lnum_orig = (linenr_T)oldline;
+ hunk->lnum_orig = oldline;
hunk->count_orig = oldcount;
- hunk->lnum_new = (linenr_T)newline;
+ hunk->lnum_new = newline;
hunk->count_new = newcount;
return OK;
@@ -3498,18 +3494,16 @@ static int parse_diff_unified(char *line, diffhunk_T *hunk)
return FAIL;
}
-///
/// Callback function for the xdl_diff() function.
/// Stores the diff output in a grow array.
-///
-static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv)
+static int xdiff_out(int start_a, int count_a, int start_b, int count_b, void *priv)
{
diffout_T *dout = (diffout_T *)priv;
GA_APPEND(diffhunk_T, &(dout->dout_ga), ((diffhunk_T){
- .lnum_orig = (linenr_T)start_a + 1,
+ .lnum_orig = (linenr_T)start_a + 1,
.count_orig = count_a,
- .lnum_new = (linenr_T)start_b + 1,
- .count_new = count_b,
+ .lnum_new = (linenr_T)start_b + 1,
+ .count_new = count_b,
}));
return 0;
}
diff --git a/src/nvim/diff.h b/src/nvim/diff.h
index 1f64465336..8b58887890 100644
--- a/src/nvim/diff.h
+++ b/src/nvim/diff.h
@@ -1,20 +1,18 @@
-#ifndef NVIM_DIFF_H
-#define NVIM_DIFF_H
+#pragma once
#include <stdbool.h>
#include "nvim/ex_cmds_defs.h"
-#include "nvim/macros.h"
-#include "nvim/pos.h"
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
// Value set from 'diffopt'.
-EXTERN int diff_context INIT(= 6); // context for folds
-EXTERN int diff_foldcolumn INIT(= 2); // 'foldcolumn' for diff mode
-EXTERN bool diff_need_scrollbind INIT(= false);
+EXTERN int diff_context INIT( = 6); // context for folds
+EXTERN int diff_foldcolumn INIT( = 2); // 'foldcolumn' for diff mode
+EXTERN bool diff_need_scrollbind INIT( = false);
-EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw()
+EXTERN bool need_diff_redraw INIT( = false); // need to call diff_redraw()
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.h.generated.h"
#endif
-#endif // NVIM_DIFF_H
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index a057978a5e..99d5cf1035 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file digraph.c
///
/// code for digraphs
@@ -10,47 +7,49 @@
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/normal.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/runtime.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
typedef int result_T;
typedef struct digraph {
- char_u char1;
- char_u char2;
+ uint8_t char1;
+ uint8_t char2;
result_T result;
} digr_T;
-static char e_digraph_must_be_just_two_characters_str[]
+static const char e_digraph_must_be_just_two_characters_str[]
= N_("E1214: Digraph must be just two characters: %s");
-static char e_digraph_argument_must_be_one_character_str[]
+static const char e_digraph_argument_must_be_one_character_str[]
= N_("E1215: Digraph must be one character: %s");
-static char e_digraph_setlist_argument_must_be_list_of_lists_with_two_items[]
+static const char e_digraph_setlist_argument_must_be_list_of_lists_with_two_items[]
= N_("E1216: digraph_setlist() argument must be a list of lists with two items");
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -879,6 +878,7 @@ static digr_T digraphdefault[] =
{ '1', '\'', 0x2032 },
{ '2', '\'', 0x2033 },
{ '3', '\'', 0x2034 },
+ { '4', '\'', 0x2057 },
{ '1', '"', 0x2035 },
{ '2', '"', 0x2036 },
{ '3', '"', 0x2037 },
@@ -1493,7 +1493,7 @@ char *get_digraph_for_char(int val_arg)
{
const int val = val_arg;
const digr_T *dp;
- static char_u r[3];
+ static char r[3];
for (int use_defaults = 0; use_defaults <= 1; use_defaults++) {
if (use_defaults == 0) {
@@ -1503,10 +1503,10 @@ char *get_digraph_for_char(int val_arg)
}
for (int i = 0; use_defaults ? dp->char1 != NUL : i < user_digraphs.ga_len; i++) {
if (dp->result == val) {
- r[0] = dp->char1;
- r[1] = dp->char2;
+ r[0] = (char)dp->char1;
+ r[1] = (char)dp->char2;
r[2] = NUL;
- return (char *)r;
+ return r;
}
dp++;
}
@@ -1623,7 +1623,7 @@ int digraph_get(int char1, int char2, bool meta_char)
if (((retval = getexactdigraph(char1, char2, meta_char)) == char2)
&& (char1 != char2)
- && ((retval = getexactdigraph(char2, char1, meta_char)) // -V764
+ && ((retval = getexactdigraph(char2, char1, meta_char))
== char1)) {
return char2;
}
@@ -1645,8 +1645,8 @@ static void registerdigraph(int char1, int char2, int n)
// Add a new digraph to the table.
dp = GA_APPEND_VIA_PTR(digr_T, &user_digraphs);
- dp->char1 = (char_u)char1;
- dp->char2 = (char_u)char2;
+ dp->char1 = (uint8_t)char1;
+ dp->char2 = (uint8_t)char2;
dp->result = n;
}
@@ -1656,7 +1656,7 @@ static void registerdigraph(int char1, int char2, int n)
bool check_digraph_chars_valid(int char1, int char2)
{
if (char2 == 0) {
- char msg[MB_MAXBYTES + 1];
+ char msg[MB_MAXCHAR + 1];
msg[utf_char2bytes(char1, msg)] = NUL;
semsg(_(e_digraph_must_be_just_two_characters_str), msg);
return false;
@@ -1705,7 +1705,7 @@ static void digraph_header(const char *msg)
if (msg_col > 0) {
msg_putchar('\n');
}
- msg_outtrans_attr(msg, HL_ATTR(HLF_CM));
+ msg_outtrans(msg, HL_ATTR(HLF_CM));
msg_putchar('\n');
}
@@ -1859,7 +1859,7 @@ static void printdigraph(const digr_T *dp, result_T *previous)
*p++ = (char)dp->char2;
*p++ = ' ';
*p = NUL;
- msg_outtrans(buf);
+ msg_outtrans(buf, 0);
p = buf;
// add a space to draw a composing char on
@@ -1869,14 +1869,14 @@ static void printdigraph(const digr_T *dp, result_T *previous)
p += utf_char2bytes(dp->result, p);
*p = NUL;
- msg_outtrans_attr(buf, HL_ATTR(HLF_8));
+ msg_outtrans(buf, HL_ATTR(HLF_8));
p = buf;
if (char2cells(dp->result) == 1) {
*p++ = ' ';
}
assert(p >= buf);
vim_snprintf(p, sizeof(buf) - (size_t)(p - buf), " %3d", dp->result);
- msg_outtrans(buf);
+ msg_outtrans(buf, 0);
}
/// Get the two digraph characters from a typval.
@@ -2076,8 +2076,6 @@ char *keymap_init(void)
/// @param eap
void ex_loadkeymap(exarg_T *eap)
{
- char *s;
-
#define KMAP_LLEN 200 // max length of "to" and "from" together
char buf[KMAP_LLEN + 11];
char *save_cpo = p_cpo;
@@ -2097,7 +2095,7 @@ void ex_loadkeymap(exarg_T *eap)
p_cpo = "C";
// Get each line of the sourced file, break at the end.
- for (;;) {
+ while (true) {
char *line = eap->getline(0, eap->cookie, 0, true);
if (line == NULL) {
@@ -2108,11 +2106,11 @@ void ex_loadkeymap(exarg_T *eap)
if ((*p != '"') && (*p != NUL)) {
kmap_T *kp = GA_APPEND_VIA_PTR(kmap_T, &curbuf->b_kmap_ga);
- s = skiptowhite(p);
- kp->from = xstrnsave(p, (size_t)(s - p));
+ char *s = skiptowhite(p);
+ kp->from = xmemdupz(p, (size_t)(s - p));
p = skipwhite(s);
s = skiptowhite(p);
- kp->to = xstrnsave(p, (size_t)(s - p));
+ kp->to = xmemdupz(p, (size_t)(s - p));
if ((strlen(kp->from) + strlen(kp->to) >= KMAP_LLEN)
|| (*kp->from == NUL)
@@ -2180,3 +2178,42 @@ static void keymap_unload(void)
curbuf->b_kmap_state &= ~KEYMAP_LOADED;
status_redraw_curbuf();
}
+
+/// Get the value to show for the language mappings, active 'keymap'.
+///
+/// @param fmt format string containing one %s item
+/// @param buf buffer for the result
+/// @param len length of buffer
+bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len)
+{
+ char *p;
+
+ if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP) {
+ return false;
+ }
+
+ buf_T *old_curbuf = curbuf;
+ win_T *old_curwin = curwin;
+ char *s;
+
+ curbuf = wp->w_buffer;
+ curwin = wp;
+ STRCPY(buf, "b:keymap_name"); // must be writable
+ emsg_skip++;
+ s = p = eval_to_string(buf, false);
+ emsg_skip--;
+ curbuf = old_curbuf;
+ curwin = old_curwin;
+ if (p == NULL || *p == NUL) {
+ if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) {
+ p = wp->w_buffer->b_p_keymap;
+ } else {
+ p = "lang";
+ }
+ }
+ if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) {
+ buf[0] = NUL;
+ }
+ xfree(s);
+ return buf[0] != NUL;
+}
diff --git a/src/nvim/digraph.h b/src/nvim/digraph.h
index 71330ae9b1..267004124b 100644
--- a/src/nvim/digraph.h
+++ b/src/nvim/digraph.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_DIGRAPH_H
-#define NVIM_DIGRAPH_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "digraph.h.generated.h"
#endif
-#endif // NVIM_DIGRAPH_H
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 031ad39f75..c2f0eb9e0e 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -1,18 +1,15 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// drawline.c: Functions for drawing window lines on the screen.
-// This is the middle level, drawscreen.c is the top and grid.c/screen.c the lower level.
+// This is the middle level, drawscreen.c is the top and grid.c the lower level.
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/arabic.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
@@ -21,10 +18,10 @@
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
#include "nvim/drawline.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
-#include "nvim/extmark_defs.h"
#include "nvim/fold.h"
-#include "nvim/garray.h"
+#include "nvim/fold_defs.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@@ -37,10 +34,10 @@
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
#include "nvim/state.h"
@@ -48,12 +45,11 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
-#define MB_FILLER_CHAR '<' // character used when a double-width character
- // doesn't fit.
+#define MB_FILLER_CHAR '<' // character used when a double-width character doesn't fit.
/// possible draw states in win_line(), drawn in sequence.
typedef enum {
@@ -68,19 +64,104 @@ typedef enum {
WL_LINE, // text in the line
} LineDrawState;
-/// for line_putchar. Contains the state that needs to be remembered from
-/// putting one character to the next.
+/// structure with variables passed between win_line() and other functions
typedef struct {
- const char *p;
- int prev_c; ///< previous Arabic character
- int prev_c1; ///< first composing char for prev_c
-} LineState;
-#define LINE_STATE(p) { p, 0, 0 }
+ LineDrawState draw_state; ///< what to draw next
+
+ linenr_T lnum; ///< line number to be drawn
+ foldinfo_T foldinfo; ///< fold info for this line
+
+ int startrow; ///< first row in the window to be drawn
+ int row; ///< row in the window, excl w_winrow
+
+ colnr_T vcol; ///< virtual column, before wrapping
+ int col; ///< visual column on screen, after wrapping
+ int boguscols; ///< nonexistent columns added to "col" to force wrapping
+ int vcol_off; ///< offset for concealed characters
+
+ int off; ///< offset relative start of line
+
+ int cul_attr; ///< set when 'cursorline' active
+ int line_attr; ///< attribute for the whole line
+ int line_attr_lowprio; ///< low-priority attribute for the line
+
+ int fromcol; ///< start of inverting
+ int tocol; ///< end of inverting
+
+ colnr_T vcol_sbr; ///< virtual column after showbreak
+ bool need_showbreak; ///< overlong line, skipping first x chars
+
+ int char_attr; ///< attributes for next character
+
+ int n_extra; ///< number of extra bytes
+ int n_attr; ///< chars with special attr
+ char *p_extra; ///< string of extra chars, plus NUL, only used
+ ///< when c_extra and c_final are NUL
+ int extra_attr; ///< attributes for p_extra
+ int c_extra; ///< extra chars, all the same
+ int c_final; ///< final char, mandatory if set
+
+ int n_closing; ///< number of chars in fdc which will be closing
+
+ bool extra_for_extmark; ///< n_extra set for inline virtual text
+
+ // saved "extra" items for when draw_state becomes WL_LINE (again)
+ int saved_n_extra;
+ char *saved_p_extra;
+ bool saved_extra_for_extmark;
+ int saved_c_extra;
+ int saved_c_final;
+ int saved_char_attr;
+
+ char extra[57]; ///< sign, line number and 'fdc' must fit in here
+
+ hlf_T diff_hlf; ///< type of diff highlighting
+
+ int n_virt_lines; ///< nr of virtual lines
+ int filler_lines; ///< nr of filler lines to be drawn
+ int filler_todo; ///< nr of filler lines still to do + 1
+ SignTextAttrs sattrs[SIGN_SHOW_MAX]; ///< sign attributes for the sign column
+ /// do consider wrapping in linebreak mode only after encountering
+ /// a non whitespace char
+ bool need_lbr;
+
+ VirtText virt_inline;
+ size_t virt_inline_i;
+ HlMode virt_inline_hl_mode;
+
+ bool reset_extra_attr;
+
+ int skip_cells; ///< nr of cells to skip for w_leftcol
+ ///< or w_skipcol or concealing
+ int skipped_cells; ///< nr of skipped cells for virtual text
+ ///< to be added to wlv.vcol later
+} winlinevars_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "drawline.c.generated.h"
#endif
+static char *extra_buf = NULL;
+static size_t extra_buf_size = 0;
+
+static char *get_extra_buf(size_t size)
+{
+ size = MAX(size, 64);
+ if (extra_buf_size < size) {
+ xfree(extra_buf);
+ extra_buf = xmalloc(size);
+ extra_buf_size = size;
+ }
+ return extra_buf;
+}
+
+#ifdef EXITFREE
+void drawline_free_all_mem(void)
+{
+ xfree(extra_buf);
+}
+#endif
+
/// Advance **color_cols
///
/// @return true when there are columns to draw.
@@ -114,7 +195,7 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
return;
}
- width1 = wp->w_width - cur_col_off;
+ width1 = wp->w_width_inner - cur_col_off;
width2 = width1 + win_col_off2(wp);
*left_col = 0;
@@ -137,110 +218,96 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
/// Put a single char from an UTF-8 buffer into a line buffer.
///
-/// Handles composing chars and arabic shaping state.
-static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol)
+/// If `*pp` is a double-width char and only one cell is left, emit a space,
+/// and don't advance *pp
+///
+/// Handles composing chars
+static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells, int vcol)
{
- const char *p = s->p;
+ const char *p = *pp;
int cells = utf_ptr2cells(p);
int c_len = utfc_ptr2len(p);
- int u8c, u8cc[MAX_MCO];
+ assert(maxcells > 0);
if (cells > maxcells) {
- return -1;
+ dest[0] = schar_from_ascii(' ');
+ return 1;
}
- u8c = utfc_ptr2char(p, u8cc);
+
if (*p == TAB) {
cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
+ }
+
+ if (cells < maxcells && dest[cells] == 0) {
+ dest[cells] = schar_from_ascii(' ');
+ }
+ if (*p == TAB) {
for (int c = 0; c < cells; c++) {
- schar_from_ascii(dest[c], ' ');
+ dest[c] = schar_from_ascii(' ');
}
- goto done;
- } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) {
- schar_from_ascii(dest[0], *p);
- s->prev_c = u8c;
} else {
- if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
- // Do Arabic shaping.
- int pc, pc1, nc;
- int pcc[MAX_MCO];
- int firstbyte = (uint8_t)(*p);
-
- // The idea of what is the previous and next
- // character depends on 'rightleft'.
- if (rl) {
- pc = s->prev_c;
- pc1 = s->prev_c1;
- nc = utf_ptr2char(p + c_len);
- s->prev_c1 = u8cc[0];
- } else {
- pc = utfc_ptr2char(p + c_len, pcc);
- nc = s->prev_c;
- pc1 = pcc[0];
- }
- s->prev_c = u8c;
-
- u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc);
- } else {
- s->prev_c = u8c;
+ int u8c;
+ dest[0] = utfc_ptr2schar(p, &u8c);
+ if (cells > 1) {
+ dest[1] = 0;
}
- schar_from_cc(dest[0], u8c, u8cc);
- }
- if (cells > 1) {
- dest[1][0] = 0;
}
-done:
- s->p += c_len;
- return cells;
-}
-static inline void provider_err_virt_text(linenr_T lnum, char *err)
-{
- Decoration err_decor = DECORATION_INIT;
- int hl_err = syn_check_group(S_LEN("ErrorMsg"));
- kv_push(err_decor.virt_text,
- ((VirtTextChunk){ .text = err,
- .hl_id = hl_err }));
- err_decor.virt_text_width = (int)mb_string2cells(err);
- decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0);
+ *pp += c_len;
+ return cells;
}
-static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col,
- int win_row)
+static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int win_row)
{
DecorState *state = &decor_state;
+ const int max_col = wp->w_grid.cols;
int right_pos = max_col;
bool do_eol = state->eol_col > -1;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(state->active, i);
- if (!(item->start_row == state->row
- && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) {
+ if (!(item->start_row == state->row && decor_virt_pos(item))) {
continue;
}
- if (item->win_col == -1) {
- if (item->decor.virt_text_pos == kVTRightAlign) {
- right_pos -= item->decor.virt_text_width;
- item->win_col = right_pos;
- } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
- item->win_col = state->eol_col;
- } else if (item->decor.virt_text_pos == kVTWinCol) {
- item->win_col = MAX(item->decor.col + col_off, 0);
+
+ DecorVirtText *vt = NULL;
+ if (item->kind == kDecorKindVirtText) {
+ assert(item->data.vt);
+ vt = item->data.vt;
+ }
+ if (decor_virt_pos(item) && item->draw_col == -1) {
+ bool updated = true;
+ VirtTextPos pos = decor_virt_pos_kind(item);
+ if (pos == kVPosRightAlign) {
+ right_pos -= vt->width;
+ item->draw_col = right_pos;
+ } else if (pos == kVPosEndOfLine && do_eol) {
+ item->draw_col = state->eol_col;
+ } else if (pos == kVPosWinCol) {
+ item->draw_col = MAX(col_off + vt->col, 0);
+ } else {
+ updated = false;
+ }
+ if (updated && (item->draw_col < 0 || item->draw_col >= wp->w_grid.cols)) {
+ // Out of window, don't draw at all.
+ item->draw_col = INT_MIN;
}
}
- if (item->win_col < 0) {
+ if (item->draw_col < 0) {
continue;
}
- int col;
- if (item->decor.ui_watched) {
+ int col = 0;
+ if (item->kind == kDecorKindUIWatched) {
// send mark position to UI
- col = item->win_col;
- WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col };
+ col = item->draw_col;
+ WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, col };
kv_push(win_extmark_arr, m);
}
- if (kv_size(item->decor.virt_text)) {
- col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text,
- item->decor.hl_mode, max_col, item->win_col - col_off);
+ if (vt) {
+ int vcol = item->draw_col - col_off;
+ col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
+ vt->hl_mode, max_col, vcol);
}
- item->win_col = -2; // deactivate
- if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
+ item->draw_col = INT_MIN; // deactivate
+ if (vt && vt->pos == kVPosEndOfLine && do_eol) {
state->eol_col = col + 1;
}
@@ -251,28 +318,22 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col,
int vcol)
{
- LineState s = LINE_STATE("");
+ const char *p = "";
int virt_attr = 0;
size_t virt_pos = 0;
while (col < max_col) {
- if (!*s.p) {
+ if (!*p) {
if (virt_pos >= kv_size(vt)) {
break;
}
virt_attr = 0;
- do {
- s.p = kv_A(vt, virt_pos).text;
- int hl_id = kv_A(vt, virt_pos).hl_id;
- virt_attr = hl_combine_attr(virt_attr,
- hl_id > 0 ? syn_id2attr(hl_id) : 0);
- virt_pos++;
- } while (!s.p && virt_pos < kv_size(vt));
- if (!s.p) {
+ p = next_virt_text_chunk(vt, &virt_pos, &virt_attr);
+ if (p == NULL) {
break;
}
}
- if (!*s.p) {
+ if (*p == NUL) {
continue;
}
int attr;
@@ -280,19 +341,18 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
if (hl_mode == kHlModeCombine) {
attr = hl_combine_attr(linebuf_attr[col], virt_attr);
} else if (hl_mode == kHlModeBlend) {
- through = (*s.p == ' ');
+ through = (*p == ' ');
attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
} else {
attr = virt_attr;
}
schar_T dummy[2];
- int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col],
- max_col - col, false, vcol);
- // if we failed to emit a char, we still need to advance
- cells = MAX(cells, 1);
-
+ int maxcells = max_col - col;
+ int cells = line_putchar(buf, &p, through ? dummy : &linebuf_char[col],
+ maxcells, vcol);
for (int c = 0; c < cells; c++) {
- linebuf_attr[col++] = attr;
+ linebuf_attr[col] = attr;
+ col++;
}
vcol += cells;
}
@@ -300,102 +360,243 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
}
/// Return true if CursorLineSign highlight is to be used.
-static bool use_cursor_line_sign(win_T *wp, linenr_T lnum)
+static bool use_cursor_line_highlight(win_T *wp, linenr_T lnum)
{
return wp->w_p_cul
- && lnum == wp->w_cursor.lnum
+ && lnum == wp->w_cursorline
&& (wp->w_p_culopt_flags & CULOPT_NBR);
}
-// Get information needed to display the sign in line 'lnum' in window 'wp'.
-// If 'nrcol' is true, the sign is going to be displayed in the number column.
-// Otherwise the sign is going to be displayed in the sign column.
-//
-// @param count max number of signs
-// @param[out] n_extrap number of characters from pp_extra to display
-// @param sign_idxp Index of the displayed sign
-static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignTextAttrs sattrs[],
- int row, int startrow, int filler_lines, int filler_todo,
- int *c_extrap, int *c_finalp, char *extra, size_t extra_size,
- char **pp_extra, int *n_extrap, int *char_attrp, int sign_idx,
- int cul_attr)
+static char fdc_buf[MB_MAXCHAR * 10 + 1];
+
+/// Setup for drawing the 'foldcolumn', if there is one.
+static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv)
{
- // Draw cells with the sign value or blank.
- *c_extrap = ' ';
- *c_finalp = NUL;
- if (nrcol) {
- *n_extrap = number_width(wp) + 1;
+ int fdc = compute_foldcolumn(wp, 0);
+ if (fdc <= 0) {
+ return;
+ }
+
+ // Use a separate buffer as `extra_buf` might be in use.
+ wlv->n_extra = (int)fill_foldcolumn(fdc_buf, wp, wlv->foldinfo, wlv->lnum,
+ &wlv->n_closing);
+ fdc_buf[wlv->n_extra] = NUL;
+ wlv->p_extra = fdc_buf;
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
+ if (use_cursor_line_highlight(wp, wlv->lnum)) {
+ wlv->char_attr = win_hl_attr(wp, HLF_CLF);
} else {
- if (use_cursor_line_sign(wp, lnum)) {
- *char_attrp = win_hl_attr(wp, HLF_CLS);
+ wlv->char_attr = win_hl_attr(wp, HLF_FC);
+ }
+}
+
+/// Fills the foldcolumn at "p" for window "wp".
+/// Only to be called when 'foldcolumn' > 0.
+///
+/// @param[out] p Char array to write into
+/// @param lnum Absolute current line number
+/// @param closed Whether it is in 'foldcolumn' mode
+///
+/// Assume monocell characters
+/// @return number of chars added to \param p
+size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int *n_closing)
+{
+ int i = 0;
+ int fdc = compute_foldcolumn(wp, 0); // available cell width
+ size_t char_counter = 0;
+ int symbol = 0;
+ int len = 0;
+ bool closed = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
+ // Init to all spaces.
+ memset(p, ' ', MB_MAXCHAR * (size_t)fdc + 1);
+
+ int level = foldinfo.fi_level;
+
+ // If the column is too narrow, we start at the lowest level that
+ // fits and use numbers to indicate the depth.
+ int first_level = level - fdc - closed + 1;
+ if (first_level < 1) {
+ first_level = 1;
+ }
+
+ for (i = 0; i < MIN(fdc, level); i++) {
+ if (foldinfo.fi_lnum == lnum
+ && first_level + i >= foldinfo.fi_low_level) {
+ symbol = wp->w_p_fcs_chars.foldopen;
+ } else if (first_level == 1) {
+ symbol = wp->w_p_fcs_chars.foldsep;
+ } else if (first_level + i <= 9) {
+ symbol = '0' + first_level + i;
} else {
- *char_attrp = win_hl_attr(wp, HLF_SC);
+ symbol = '>';
+ }
+
+ len = utf_char2bytes(symbol, &p[char_counter]);
+ char_counter += (size_t)len;
+ if (first_level + i >= level) {
+ i++;
+ break;
}
- *n_extrap = win_signcol_width(wp);
}
- if (row == startrow + filler_lines && filler_todo <= 0) {
- SignTextAttrs *sattr = sign_get_attr(sign_idx, sattrs, wp->w_scwidth);
- if (sattr != NULL) {
- *pp_extra = sattr->text;
- if (*pp_extra != NULL) {
- *c_extrap = NUL;
- *c_finalp = NUL;
-
- if (nrcol) {
- int n, width = number_width(wp) - 2;
- for (n = 0; n < width; n++) {
- extra[n] = ' ';
- }
- extra[n] = NUL;
- STRCAT(extra, *pp_extra);
- STRCAT(extra, " ");
- *pp_extra = extra;
- *n_extrap = (int)strlen(*pp_extra);
- } else {
- size_t symbol_blen = strlen(*pp_extra);
+ int n_closing_val = i;
- // TODO(oni-link): Is sign text already extended to
- // full cell width?
- assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra)));
- // symbol(s) bytes + (filling spaces) (one byte each)
- *n_extrap = (int)symbol_blen + win_signcol_width(wp) -
- (int)mb_string2cells(*pp_extra);
+ if (closed) {
+ if (symbol != 0) {
+ // rollback previous write
+ char_counter -= (size_t)len;
+ memset(&p[char_counter], ' ', (size_t)len);
+ n_closing_val--;
+ }
+ len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
+ char_counter += (size_t)len;
+ }
- assert(extra_size > symbol_blen);
- memset(extra, ' ', extra_size);
- memcpy(extra, *pp_extra, symbol_blen);
+ if (n_closing) {
+ *n_closing = n_closing_val;
+ }
- *pp_extra = extra;
- (*pp_extra)[*n_extrap] = NUL;
- }
- }
+ return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc);
+}
- if (use_cursor_line_sign(wp, lnum) && cul_attr > 0) {
- *char_attrp = cul_attr;
- } else {
- *char_attrp = sattr->hl_attr_id;
- }
+/// Get information needed to display the sign in line "wlv->lnum" in window "wp".
+/// If "nrcol" is true, the sign is going to be displayed in the number column.
+/// Otherwise the sign is going to be displayed in the sign column. If there is no
+/// sign, draw blank cells instead.
+static void get_sign_display_info(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx,
+ int sign_cul_attr)
+{
+ SignTextAttrs sattr = wlv->sattrs[sign_idx];
+ wlv->c_final = NUL;
+
+ if (sattr.text && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) {
+ size_t fill = nrcol ? (size_t)number_width(wp) - SIGN_WIDTH : 0;
+ size_t sign_len = strlen(sattr.text);
+
+ // Spaces + sign: " " + ">>" + ' '
+ wlv->n_extra = (int)(fill + sign_len + nrcol);
+ if (nrcol) {
+ memset(wlv->extra, ' ', (size_t)wlv->n_extra);
+ }
+ memcpy(wlv->extra + fill, sattr.text, sign_len);
+ wlv->p_extra = wlv->extra;
+ wlv->c_extra = NUL;
+ wlv->char_attr = (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr)
+ ? sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0;
+ } else {
+ wlv->c_extra = ' ';
+ wlv->n_extra = nrcol ? number_width(wp) + 1 : SIGN_WIDTH;
+ if (!nrcol) {
+ wlv->char_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
}
}
}
-static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int *line_attr,
- int *num_attr, int *cul_attr)
+static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len)
{
- HlPriAttr line_attrs = { *line_attr, 0 };
- HlPriAttr num_attrs = { *num_attr, 0 };
- HlPriAttr cul_attrs = { *cul_attr, 0 };
+ linenr_T num;
+ char *fmt = "%*" PRIdLINENR " ";
- // TODO(bfredl, vigoux): line_attr should not take priority over decoration!
- int num_signs = buf_get_signattrs(buf, lnum, sattrs, &num_attrs, &line_attrs, &cul_attrs);
- decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs, &num_attrs, &line_attrs, &cul_attrs);
+ if (wp->w_p_nu && !wp->w_p_rnu) {
+ // 'number' + 'norelativenumber'
+ num = lnum;
+ } else {
+ // 'relativenumber', don't use negative numbers
+ num = abs(get_cursor_rel_lnum(wp, lnum));
+ if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
+ // 'number' + 'relativenumber'
+ num = lnum;
+ fmt = "%-*" PRIdLINENR " ";
+ }
+ }
- *line_attr = line_attrs.attr_id;
- *num_attr = num_attrs.attr_id;
- *cul_attr = cul_attrs.attr_id;
+ snprintf(buf, buf_len, fmt, number_width(wp), num);
+}
- return num_signs;
+/// Return true if CursorLineNr highlight is to be used for the number column.
+/// - 'cursorline' must be set
+/// - "wlv->lnum" must be the cursor line
+/// - 'cursorlineopt' has "number"
+/// - don't highlight filler lines (when in diff mode)
+/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number
+/// itself on the first screenline of the wrapped line, otherwise highlight the number column of
+/// all screenlines of the wrapped line.
+static bool use_cursor_line_nr(win_T *wp, winlinevars_T *wlv)
+{
+ return wp->w_p_cul
+ && wlv->lnum == wp->w_cursorline
+ && (wp->w_p_culopt_flags & CULOPT_NBR)
+ && (wlv->row == wlv->startrow + wlv->filler_lines
+ || (wlv->row > wlv->startrow + wlv->filler_lines
+ && (wp->w_p_culopt_flags & CULOPT_LINE)));
+}
+
+static int get_line_number_attr(win_T *wp, winlinevars_T *wlv)
+{
+ if (use_cursor_line_nr(wp, wlv)) {
+ // TODO(vim): Can we use CursorLine instead of CursorLineNr
+ // when CursorLineNr isn't set?
+ return win_hl_attr(wp, HLF_CLN);
+ }
+
+ if (wp->w_p_rnu) {
+ if (wlv->lnum < wp->w_cursor.lnum) {
+ // Use LineNrAbove
+ return win_hl_attr(wp, HLF_LNA);
+ }
+ if (wlv->lnum > wp->w_cursor.lnum) {
+ // Use LineNrBelow
+ return win_hl_attr(wp, HLF_LNB);
+ }
+ }
+
+ return win_hl_attr(wp, HLF_N);
+}
+
+/// Display the absolute or relative line number. After the first row fill with
+/// blanks when the 'n' flag isn't in 'cpo'.
+static void handle_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int sign_cul_attr)
+{
+ bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL;
+
+ if ((wp->w_p_nu || wp->w_p_rnu)
+ && (wlv->row == wlv->startrow + wlv->filler_lines || !has_cpo_n)
+ // there is no line number in a wrapped line when "n" is in
+ // 'cpoptions', but 'breakindent' assumes it anyway.
+ && !((has_cpo_n && !wp->w_p_bri) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) {
+ // If 'signcolumn' is set to 'number' and a sign is present in "lnum",
+ // then display the sign instead of the line number.
+ if (wp->w_minscwidth == SCL_NUM && wlv->sattrs[0].text) {
+ get_sign_display_info(true, wp, wlv, 0, sign_cul_attr);
+ } else {
+ // Draw the line number (empty space after wrapping).
+ if (wlv->row == wlv->startrow + wlv->filler_lines
+ && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) {
+ get_line_number_str(wp, wlv->lnum, wlv->extra, sizeof(wlv->extra));
+ if (wp->w_skipcol > 0 && wlv->startrow == 0) {
+ for (wlv->p_extra = wlv->extra; *wlv->p_extra == ' '; wlv->p_extra++) {
+ *wlv->p_extra = '-';
+ }
+ }
+ if (wp->w_p_rl) { // reverse line numbers
+ char *num = skipwhite(wlv->extra);
+ rl_mirror_ascii(num, skiptowhite(num));
+ }
+ wlv->p_extra = wlv->extra;
+ wlv->c_extra = NUL;
+ } else {
+ wlv->c_extra = ' ';
+ }
+ wlv->c_final = NUL;
+ wlv->n_extra = number_width(wp) + 1;
+ if (sign_num_attr > 0) {
+ wlv->char_attr = sign_num_attr;
+ } else {
+ wlv->char_attr = get_line_number_attr(wp, wlv);
+ }
+ }
+ }
}
/// Prepare and build the 'statuscolumn' string for line "lnum" in window "wp".
@@ -404,44 +605,34 @@ static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int
/// the start of the buffer line "lnum" and once for the wrapped lines.
///
/// @param[out] stcp Status column attributes
-static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines,
- int cul_attr, int sign_num_attr, int sign_cul_attr, statuscol_T *stcp,
- foldinfo_T foldinfo, SignTextAttrs *sattrs)
+static void get_statuscol_str(win_T *wp, linenr_T lnum, int virtnum, statuscol_T *stcp)
{
- long relnum = -1;
- bool use_cul = use_cursor_line_sign(wp, lnum);
- int virtnum = row - startrow - filler_lines;
-
- set_vim_var_nr(VV_VIRTNUM, virtnum);
- // When called the first time for line "lnum" set num_attr
- if (stcp->num_attr == 0) {
- stcp->num_attr = sign_num_attr ? sign_num_attr
- : get_line_number_attr(wp, lnum, row, startrow, filler_lines);
- }
- // When called for the first non-filler row of line "lnum" set num v:vars and fold column
- if (virtnum == 0) {
- relnum = labs(get_cursor_rel_lnum(wp, lnum));
- if (compute_foldcolumn(wp, 0)) {
- size_t n = fill_foldcolumn(stcp->fold_text, wp, foldinfo, lnum);
- stcp->fold_text[n] = NUL;
- stcp->fold_attr = win_hl_attr(wp, use_cul ? HLF_CLF : HLF_FC);
+ // When called for the first non-filler row of line "lnum" set num v:vars
+ linenr_T relnum = virtnum == 0 ? abs(get_cursor_rel_lnum(wp, lnum)) : -1;
+
+ // When a buffer's line count has changed, make a best estimate for the full
+ // width of the status column by building with "w_nrwidth_line_count". Add
+ // potentially truncated width and rebuild before drawing anything.
+ if (wp->w_statuscol_line_count != wp->w_nrwidth_line_count) {
+ wp->w_statuscol_line_count = wp->w_nrwidth_line_count;
+ set_vim_var_nr(VV_VIRTNUM, 0);
+ build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp);
+ if (stcp->truncate > 0) {
+ // Add truncated width to avoid unnecessary redraws
+ int addwidth = MIN(stcp->truncate, MAX_NUMBERWIDTH - wp->w_nrwidth);
+ stcp->truncate = 0;
+ stcp->width += addwidth;
+ wp->w_nrwidth += addwidth;
+ wp->w_nrwidth_width = wp->w_nrwidth;
+ wp->w_valid &= ~VALID_WCOL;
}
}
- // Make sure to clear->set->clear sign column for filler->first->wrapped lines
- int i = 0;
- for (; i < wp->w_scwidth; i++) {
- SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, sattrs, wp->w_scwidth);
- stcp->sign_text[i] = sattr && sattr->text ? sattr->text : " ";
- stcp->sign_attr[i] = sattr ? (use_cul && sign_cul_attr ? sign_cul_attr : sattr->hl_attr_id)
- : win_hl_attr(wp, use_cul ? HLF_CLS : HLF_SC);
- }
- stcp->sign_text[i] = NULL;
+ set_vim_var_nr(VV_VIRTNUM, virtnum);
- int width = build_statuscol_str(wp, lnum, relnum, stcp->width,
- ' ', stcp->text, &stcp->hlrec, stcp);
+ int width = build_statuscol_str(wp, lnum, relnum, stcp);
// Force a redraw in case of error or when truncated
if (*wp->w_p_stc == NUL || (stcp->truncate > 0 && wp->w_nrwidth < MAX_NUMBERWIDTH)) {
- if (stcp->truncate) { // Avoid truncating 'statuscolumn'
+ if (stcp->truncate > 0) { // Avoid truncating 'statuscolumn'
wp->w_nrwidth = MIN(MAX_NUMBERWIDTH, wp->w_nrwidth + stcp->truncate);
wp->w_nrwidth_width = wp->w_nrwidth;
} else { // 'statuscolumn' reset due to error
@@ -467,201 +658,374 @@ static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, i
}
/// Get information needed to display the next segment in the 'statuscolumn'.
-/// If not yet at the end, prepare for next segment and decrement "draw_state".
+/// If not yet at the end, prepare for next segment and decrement "wlv->draw_state".
///
/// @param stcp Status column attributes
-/// @param[out] draw_state Current draw state in win_line()
-static void get_statuscol_display_info(statuscol_T *stcp, LineDrawState *draw_state, int *char_attr,
- int *n_extrap, int *c_extrap, int *c_finalp, char **pp_extra)
+/// @param[in,out] wlv
+static void get_statuscol_display_info(statuscol_T *stcp, winlinevars_T *wlv)
{
- *c_extrap = NUL;
- *c_finalp = NUL;
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
do {
- *draw_state = WL_STC;
- *char_attr = stcp->cur_attr;
- *pp_extra = stcp->textp;
- *n_extrap = (int)((stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end) - stcp->textp);
+ wlv->draw_state = WL_STC;
+ wlv->char_attr = stcp->cur_attr;
+ wlv->p_extra = stcp->textp;
+ char *const section_end = stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end;
+ wlv->n_extra = (int)(section_end - stcp->textp);
// Prepare for next highlight section if not yet at the end
- if (stcp->textp + *n_extrap < stcp->text_end) {
+ if (section_end < stcp->text_end) {
int hl = stcp->hlrecp->userhl;
stcp->textp = stcp->hlrecp->start;
- stcp->cur_attr = hl < 0 ? syn_id2attr(-stcp->hlrecp->userhl)
- : hl > 0 ? hl : stcp->num_attr;
+ stcp->cur_attr = hl < 0 ? syn_id2attr(-hl) : stcp->num_attr;
stcp->hlrecp++;
- *draw_state = WL_STC - 1;
+ wlv->draw_state = WL_STC - 1;
}
// Skip over empty highlight sections
- } while (*n_extrap == 0 && stcp->textp < stcp->text_end);
-}
-
-/// Return true if CursorLineNr highlight is to be used for the number column.
-///
-/// - 'cursorline' must be set
-/// - lnum must be the cursor line
-/// - 'cursorlineopt' has "number"
-/// - don't highlight filler lines (when in diff mode)
-/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number
-/// itself on the first screenline of the wrapped line, otherwise highlight the number column of
-/// all screenlines of the wrapped line.
-static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines)
-{
- return wp->w_p_cul
- && lnum == wp->w_cursor.lnum
- && (wp->w_p_culopt_flags & CULOPT_NBR)
- && (row == startrow + filler_lines
- || (row > startrow + filler_lines
- && (wp->w_p_culopt_flags & CULOPT_LINE)));
+ } while (wlv->n_extra == 0 && stcp->textp < stcp->text_end);
+ if (wlv->n_extra > 0) {
+ static char transbuf[(MAX_NUMBERWIDTH + 9 + 9 * 2) * MB_MAXBYTES + 1];
+ wlv->n_extra = (int)transstr_buf(wlv->p_extra, wlv->n_extra, transbuf, sizeof transbuf, true);
+ wlv->p_extra = transbuf;
+ }
}
-static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len)
+static void handle_breakindent(win_T *wp, winlinevars_T *wlv)
{
- long num;
- char *fmt = "%*ld ";
+ if (wp->w_briopt_sbr && wlv->draw_state == WL_BRI - 1
+ && *get_showbreak_value(wp) != NUL) {
+ // draw indent after showbreak value
+ wlv->draw_state = WL_BRI;
+ } else if (wp->w_briopt_sbr && wlv->draw_state == WL_SBR) {
+ // after the showbreak, draw the breakindent
+ wlv->draw_state = WL_BRI - 1;
+ }
- if (wp->w_p_nu && !wp->w_p_rnu) {
- // 'number' + 'norelativenumber'
- num = (long)lnum;
- } else {
- // 'relativenumber', don't use negative numbers
- num = labs((long)get_cursor_rel_lnum(wp, lnum));
- if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
- // 'number' + 'relativenumber'
- num = lnum;
- fmt = "%-*ld ";
+ // draw 'breakindent': indent wrapped text accordingly
+ if (wlv->draw_state == WL_BRI - 1 && wlv->n_extra == 0) {
+ wlv->draw_state = WL_BRI;
+ // if wlv->need_showbreak is set, breakindent also applies
+ if (wp->w_p_bri && (wlv->row != wlv->startrow || wlv->need_showbreak)
+ && wlv->filler_lines == 0) {
+ wlv->char_attr = 0;
+ if (wlv->diff_hlf != (hlf_T)0) {
+ wlv->char_attr = win_hl_attr(wp, (int)wlv->diff_hlf);
+ }
+ wlv->p_extra = NULL;
+ wlv->c_extra = ' ';
+ wlv->c_final = NUL;
+ wlv->n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, wlv->lnum));
+ if (wlv->row == wlv->startrow) {
+ wlv->n_extra -= win_col_off2(wp);
+ if (wlv->n_extra < 0) {
+ wlv->n_extra = 0;
+ }
+ }
+ if (wp->w_skipcol > 0 && wlv->startrow == 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
+ wlv->need_showbreak = false;
+ }
+ // Correct end of highlighted area for 'breakindent',
+ // required wen 'linebreak' is also set.
+ if (wlv->tocol == wlv->vcol) {
+ wlv->tocol += wlv->n_extra;
+ }
}
}
-
- snprintf(buf, buf_len, fmt, number_width(wp), num);
}
-static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines)
+static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv)
{
- if (wp->w_p_rnu) {
- if (lnum < wp->w_cursor.lnum) {
- // Use LineNrAbove
- return win_hl_attr(wp, HLF_LNA);
- }
- if (lnum > wp->w_cursor.lnum) {
- // Use LineNrBelow
- return win_hl_attr(wp, HLF_LNB);
+ if (wlv->filler_todo > wlv->filler_lines - wlv->n_virt_lines) {
+ // TODO(bfredl): check this doesn't inhibit TUI-style
+ // clear-to-end-of-line.
+ wlv->c_extra = ' ';
+ wlv->c_final = NUL;
+ wlv->n_extra = wp->w_grid.cols - wlv->col;
+ wlv->char_attr = 0;
+ } else if (wlv->filler_todo > 0) {
+ // Draw "deleted" diff line(s)
+ if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
+ wlv->c_extra = '-';
+ wlv->c_final = NUL;
+ } else {
+ wlv->c_extra = wp->w_p_fcs_chars.diff;
+ wlv->c_final = NUL;
}
+ wlv->n_extra = wp->w_grid.cols - wlv->col;
+ wlv->char_attr = win_hl_attr(wp, HLF_DED);
}
- if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) {
- // TODO(vim): Can we use CursorLine instead of CursorLineNr
- // when CursorLineNr isn't set?
- return win_hl_attr(wp, HLF_CLN);
- }
+ char *const sbr = get_showbreak_value(wp);
+ if (*sbr != NUL && wlv->need_showbreak) {
+ // Draw 'showbreak' at the start of each broken line.
+ wlv->p_extra = sbr;
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
+ wlv->n_extra = (int)strlen(sbr);
+ wlv->char_attr = win_hl_attr(wp, HLF_AT);
+ if (wp->w_skipcol == 0 || wlv->startrow != 0 || !wp->w_p_wrap) {
+ wlv->need_showbreak = false;
+ }
+ wlv->vcol_sbr = wlv->vcol + mb_charlen(sbr);
- return win_hl_attr(wp, HLF_N);
+ // Correct start of highlighted area for 'showbreak'.
+ if (wlv->fromcol >= wlv->vcol && wlv->fromcol < wlv->vcol_sbr) {
+ wlv->fromcol = wlv->vcol_sbr;
+ }
+
+ // Correct end of highlighted area for 'showbreak',
+ // required when 'linebreak' is also set.
+ if (wlv->tocol == wlv->vcol) {
+ wlv->tocol += wlv->n_extra;
+ }
+ // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
+ if (wlv->cul_attr) {
+ wlv->char_attr = hl_combine_attr(wlv->cul_attr, wlv->char_attr);
+ }
+ }
}
-static void apply_cursorline_highlight(win_T *wp, linenr_T lnum, int *line_attr, int *cul_attr,
- int *line_attr_lowprio)
+static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv)
{
- *cul_attr = win_hl_attr(wp, HLF_CUL);
- HlAttrs ae = syn_attr2entry(*cul_attr);
+ wlv->cul_attr = win_hl_attr(wp, HLF_CUL);
+ HlAttrs ae = syn_attr2entry(wlv->cul_attr);
// We make a compromise here (#7383):
// * low-priority CursorLine if fg is not set
// * high-priority ("same as Vim" priority) CursorLine if fg is set
if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
- *line_attr_lowprio = *cul_attr;
+ wlv->line_attr_lowprio = wlv->cul_attr;
} else {
if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer)
- && qf_current_entry(wp) == lnum) {
- *line_attr = hl_combine_attr(*cul_attr, *line_attr);
+ && qf_current_entry(wp) == wlv->lnum) {
+ wlv->line_attr = hl_combine_attr(wlv->cul_attr, wlv->line_attr);
} else {
- *line_attr = *cul_attr;
+ wlv->line_attr = wlv->cul_attr;
}
}
}
-static bool check_mb_utf8(int *c, int *u8cc)
+/// Checks if there is more inline virtual text that need to be drawn.
+static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v)
{
- if (utf_char2len(*c) > 1) {
- *u8cc = 0;
- *c = 0xc0;
+ if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) {
return true;
}
+ DecorState *state = &decor_state;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->start_row != state->row
+ || item->kind != kDecorKindVirtText
+ || item->data.vt->pos != kVPosInline
+ || item->data.vt->width == 0) {
+ continue;
+ }
+ if (item->draw_col >= -1 && item->start_col >= v) {
+ return true;
+ }
+ }
return false;
}
-/// Display line "lnum" of window 'wp' on the screen.
+static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v)
+{
+ while (wlv->n_extra == 0) {
+ if (wlv->virt_inline_i >= kv_size(wlv->virt_inline)) {
+ // need to find inline virtual text
+ wlv->virt_inline = VIRTTEXT_EMPTY;
+ wlv->virt_inline_i = 0;
+ DecorState *state = &decor_state;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->start_row != state->row
+ || item->kind != kDecorKindVirtText
+ || item->data.vt->pos != kVPosInline
+ || item->data.vt->width == 0) {
+ continue;
+ }
+ if (item->draw_col >= -1 && item->start_col == v) {
+ wlv->virt_inline = item->data.vt->data.virt_text;
+ wlv->virt_inline_hl_mode = item->data.vt->hl_mode;
+ item->draw_col = INT_MIN;
+ break;
+ }
+ }
+ if (!kv_size(wlv->virt_inline)) {
+ // no more inline virtual text here
+ break;
+ }
+ } else {
+ // already inside existing inline virtual text with multiple chunks
+ int attr = 0;
+ char *text = next_virt_text_chunk(wlv->virt_inline, &wlv->virt_inline_i, &attr);
+ if (text == NULL) {
+ continue;
+ }
+ wlv->p_extra = text;
+ wlv->n_extra = (int)strlen(text);
+ if (wlv->n_extra == 0) {
+ continue;
+ }
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
+ wlv->extra_attr = attr;
+ wlv->n_attr = mb_charlen(text);
+ // If the text didn't reach until the first window
+ // column we need to skip cells.
+ if (wlv->skip_cells > 0) {
+ // FIXME: this should use virt_text_width instead
+ int virt_text_len = wlv->n_attr;
+ if (virt_text_len > wlv->skip_cells) {
+ int len = mb_charlen2bytelen(wlv->p_extra, wlv->skip_cells);
+ wlv->n_extra -= len;
+ wlv->p_extra += len;
+ wlv->n_attr -= wlv->skip_cells;
+ // Skipped cells needed to be accounted for in vcol.
+ wlv->skipped_cells += wlv->skip_cells;
+ wlv->skip_cells = 0;
+ } else {
+ // the whole text is left of the window, drop
+ // it and advance to the next one
+ wlv->skip_cells -= virt_text_len;
+ // Skipped cells needed to be accounted for in vcol.
+ wlv->skipped_cells += virt_text_len;
+ wlv->n_attr = 0;
+ wlv->n_extra = 0;
+ // go to the start so the next virtual text chunk can be selected.
+ continue;
+ }
+ }
+ assert(wlv->n_extra > 0);
+ wlv->extra_for_extmark = true;
+ }
+ }
+}
+
+static colnr_T get_trailcol(win_T *wp, const char *ptr, const char *line)
+{
+ colnr_T trailcol = MAXCOL;
+ // find start of trailing whitespace
+ if (wp->w_p_lcs_chars.trail) {
+ trailcol = (colnr_T)strlen(ptr);
+ while (trailcol > 0 && ascii_iswhite(ptr[trailcol - 1])) {
+ trailcol--;
+ }
+ trailcol += (colnr_T)(ptr - line);
+ }
+
+ return trailcol;
+}
+
+static colnr_T get_leadcol(win_T *wp, const char *ptr, const char *line)
+{
+ colnr_T leadcol = 0;
+
+ // find end of leading whitespace
+ if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
+ leadcol = 0;
+ while (ascii_iswhite(ptr[leadcol])) {
+ leadcol++;
+ }
+ if (ptr[leadcol] == NUL) {
+ // in a line full of spaces all of them are treated as trailing
+ leadcol = 0;
+ } else {
+ // keep track of the first column not filled with spaces
+ leadcol += (colnr_T)(ptr - line + 1);
+ }
+ }
+
+ return leadcol;
+}
+
+/// Start a screen line at column zero.
+static void win_line_start(win_T *wp, winlinevars_T *wlv, bool save_extra)
+{
+ wlv->col = 0;
+ wlv->off = 0;
+ wlv->need_lbr = false;
+
+ if (save_extra) {
+ // reset the drawing state for the start of a wrapped line
+ wlv->draw_state = WL_START;
+ wlv->saved_n_extra = wlv->n_extra;
+ wlv->saved_p_extra = wlv->p_extra;
+ wlv->saved_extra_for_extmark = wlv->extra_for_extmark;
+ wlv->saved_c_extra = wlv->c_extra;
+ wlv->saved_c_final = wlv->c_final;
+ wlv->need_lbr = true;
+ wlv->saved_char_attr = wlv->char_attr;
+
+ wlv->n_extra = 0;
+ }
+}
+
+/// Called when wlv->draw_state is set to WL_LINE.
+static void win_line_continue(winlinevars_T *wlv)
+{
+ if (wlv->saved_n_extra > 0) {
+ // Continue item from end of wrapped line.
+ wlv->n_extra = wlv->saved_n_extra;
+ wlv->saved_n_extra = 0;
+ wlv->c_extra = wlv->saved_c_extra;
+ wlv->c_final = wlv->saved_c_final;
+ wlv->p_extra = wlv->saved_p_extra;
+ wlv->extra_for_extmark = wlv->saved_extra_for_extmark;
+ wlv->char_attr = wlv->saved_char_attr;
+ } else {
+ wlv->char_attr = 0;
+ }
+}
+
+/// Display line "lnum" of window "wp" on the screen.
/// wp->w_virtcol needs to be valid.
///
/// @param lnum line to display
/// @param startrow first row relative to window grid
/// @param endrow last grid row to be redrawn
-/// @param nochange not updating for changed text
/// @param number_only only update the number column
+/// @param spv 'spell' related variables kept between calls for "wp"
/// @param foldinfo fold info for this line
/// @param[in, out] providers decoration providers active this line
/// items will be disables if they cause errors
/// or explicitly return `false`.
///
/// @return the number of last row the line occupies.
-int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool number_only,
- foldinfo_T foldinfo, DecorProviders *providers, char **provider_err)
+int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_only, spellvars_T *spv,
+ foldinfo_T foldinfo, DecorProviders *providers)
{
- int c = 0; // init for GCC
- long vcol = 0; // virtual column (for tabs)
- long vcol_sbr = -1; // virtual column after showbreak
- long vcol_prev = -1; // "vcol" of previous character
+ winlinevars_T wlv; // variables passed between functions
+
+ colnr_T vcol_prev = -1; // "wlv.vcol" of previous character
char *line; // current line
char *ptr; // current position in "line"
- int row; // row in the window, excl w_winrow
ScreenGrid *grid = &wp->w_grid; // grid specific to the window
- char extra[57]; // sign, line number and 'fdc' must
- // fit in here
- int n_extra = 0; // number of extra chars
- char *p_extra = NULL; // string of extra chars, plus NUL
- char *p_extra_free = NULL; // p_extra needs to be freed
- int c_extra = NUL; // extra chars, all the same
- int c_final = NUL; // final char, mandatory if set
- int extra_attr = 0; // attributes when n_extra != 0
static char *at_end_str = ""; // used for p_extra when displaying curwin->w_p_lcs_chars.eol
// at end-of-line
- int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used
- int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used
- bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
-
- // saved "extra" items for when draw_state becomes WL_LINE (again)
- int saved_n_extra = 0;
- char *saved_p_extra = NULL;
- int saved_c_extra = 0;
- int saved_c_final = 0;
- int saved_char_attr = 0;
+ const bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
- int n_attr = 0; // chars with special attr
int saved_attr2 = 0; // char_attr saved for n_attr
int n_attr3 = 0; // chars with overruling special attr
int saved_attr3 = 0; // char_attr saved for n_attr3
- int n_skip = 0; // nr of chars to skip for 'nowrap'
-
- int fromcol = -10; // start of inverting
- int tocol = MAXCOL; // end of inverting
int fromcol_prev = -2; // start of inverting after cursor
bool noinvcur = false; // don't invert the cursor
bool lnum_in_visual_area = false;
pos_T pos;
- long v;
+ ptrdiff_t v;
- int char_attr = 0; // attributes for next character
bool attr_pri = false; // char_attr has priority
bool area_highlighting = false; // Visual or incsearch highlighting in this line
int vi_attr = 0; // attributes for Visual and incsearch highlighting
int area_attr = 0; // attributes desired by highlighting
int search_attr = 0; // attributes desired by 'hlsearch'
int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
- int syntax_attr = 0; // attributes desired by syntax
+ int decor_attr = 0; // attributes desired by syntax and extmarks
bool has_syntax = false; // this buffer has syntax highl.
+ int folded_attr = 0; // attributes for folded line
int save_did_emsg;
int eol_hl_off = 0; // 1 if highlighted char after EOL
bool draw_color_col = false; // highlight colorcolumn
colorcol_T *color_cols = NULL; // pointer to according columns array
- bool has_spell = false; // this buffer has spell checking
#define SPWORDLEN 150
char nextline[SPWORDLEN * 2]; // text with start of the next line
int nextlinecol = 0; // column where nextline[] starts
@@ -669,44 +1033,35 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// starts
int spell_attr = 0; // attributes desired by spelling
int word_end = 0; // last byte with same spell_attr
- static linenr_T checked_lnum = 0; // line number for "checked_col"
- static int checked_col = 0; // column in "checked_lnum" up to which
- // there are no spell errors
- static int cap_col = -1; // column to check for Cap word
- static linenr_T capcol_lnum = 0; // line number where "cap_col"
int cur_checked_col = 0; // checked column for current line
int extra_check = 0; // has syntax or linebreak
int multi_attr = 0; // attributes desired by multibyte
int mb_l = 1; // multi-byte byte length
int mb_c = 0; // decoded multi-byte character
- bool mb_utf8 = false; // screen char is UTF-8 char
- int u8cc[MAX_MCO]; // composing UTF-8 chars
- int filler_lines; // nr of filler lines to be drawn
- int filler_todo; // nr of filler lines still to do + 1
- hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting
+ schar_T mb_schar; // complete screen char
int change_start = MAXCOL; // first col of changed area
int change_end = -1; // last col of changed area
- colnr_T trailcol = MAXCOL; // start of trailing spaces
- colnr_T leadcol = 0; // start of leading spaces
bool in_multispace = false; // in multiple consecutive spaces
int multispace_pos = 0; // position in lcs-multispace string
- bool need_showbreak = false; // overlong line, skip first x chars
- int line_attr = 0; // attribute for the whole line
int line_attr_save;
- int line_attr_lowprio = 0; // low-priority attribute for the line
int line_attr_lowprio_save;
- int prev_c = 0; // previous Arabic character
- int prev_c1 = 0; // first composing char for prev_c
bool search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
+
+ int saved_search_attr = 0; // search_attr to be used when n_extra goes to zero
+ int saved_area_attr = 0; // idem for area_attr
+ int saved_decor_attr = 0; // idem for decor_attr
+ bool saved_search_attr_from_match = false;
+
int win_col_offset = 0; // offset for window columns
+ bool area_active = false; // whether in Visual selection, for virtual text
+ bool decor_need_recheck = false; // call decor_recheck_draw_col() at next char
char buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext
+ VirtText fold_vt = VIRTTEXT_EMPTY;
+ char *foldtext_free = NULL;
- bool area_active = false;
-
- int cul_attr = 0; // set when 'cursorline' active
// 'cursorlineopt' has "screenline" and cursor is in this line
bool cul_screenline = false;
// margin columns for the screen line, needed for when 'cursorlineopt'
@@ -714,39 +1069,40 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int left_curline_col = 0;
int right_curline_col = 0;
- LineDrawState draw_state = WL_START; // what to draw next
-
- int match_conc = 0; ///< cchar for match functions
- bool on_last_col = false;
- int syntax_flags = 0;
- int syntax_seqnr = 0;
- int prev_syntax_id = 0;
- int conceal_attr = win_hl_attr(wp, HLF_CONCEAL);
- bool is_concealing = false;
- int boguscols = 0; ///< nonexistent columns added to
- ///< force wrapping
- int vcol_off = 0; ///< offset for concealed characters
- int did_wcol = false;
+ int match_conc = 0; ///< cchar for match functions
+ bool on_last_col = false;
+ int syntax_flags = 0;
+ int syntax_seqnr = 0;
+ int prev_syntax_id = 0;
+ int conceal_attr = win_hl_attr(wp, HLF_CONCEAL);
+ bool is_concealing = false;
+ int did_wcol = false;
int old_boguscols = 0;
-#define VCOL_HLC (vcol - vcol_off)
+#define VCOL_HLC (wlv.vcol - wlv.vcol_off)
#define FIX_FOR_BOGUSCOLS \
{ \
- n_extra += vcol_off; \
- vcol -= vcol_off; \
- vcol_off = 0; \
- col -= boguscols; \
- old_boguscols = boguscols; \
- boguscols = 0; \
+ wlv.n_extra += wlv.vcol_off; \
+ wlv.vcol -= wlv.vcol_off; \
+ wlv.vcol_off = 0; \
+ wlv.col -= wlv.boguscols; \
+ old_boguscols = wlv.boguscols; \
+ wlv.boguscols = 0; \
}
- if (startrow > endrow) { // past the end already!
- return startrow;
- }
+ assert(startrow < endrow);
- row = startrow;
+ CLEAR_FIELD(wlv);
+
+ wlv.lnum = lnum;
+ wlv.foldinfo = foldinfo;
+ wlv.startrow = startrow;
+ wlv.row = startrow;
+ wlv.fromcol = -10;
+ wlv.tocol = MAXCOL;
+ wlv.vcol_sbr = -1;
buf_T *buf = wp->w_buffer;
- bool end_fill = (lnum == buf->b_ml.ml_line_count + 1);
+ const bool end_fill = (lnum == buf->b_ml.ml_line_count + 1);
if (!number_only) {
// To speed up the loop below, set extra_check when there is linebreak,
@@ -770,15 +1126,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
}
- has_decor = decor_redraw_line(buf, lnum - 1, &decor_state);
-
- decor_providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err);
+ has_decor = decor_redraw_line(wp, lnum - 1, &decor_state);
- if (*provider_err) {
- provider_err_virt_text(lnum, *provider_err);
- has_decor = true;
- *provider_err = NULL;
- }
+ decor_providers_invoke_line(wp, providers, lnum - 1, &has_decor);
if (has_decor) {
extra_check = true;
@@ -787,45 +1137,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Check for columns to display for 'colorcolumn'.
color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
if (color_cols != NULL) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
- }
-
- if (wp->w_p_spell
- && !has_fold
- && !end_fill
- && *wp->w_s->b_p_spl != NUL
- && !GA_EMPTY(&wp->w_s->b_langp)
- && *(char **)(wp->w_s->b_langp.ga_data) != NULL) {
- // Prepare for spell checking.
- has_spell = true;
- extra_check = true;
-
- // Get the start of the next line, so that words that wrap to the next
- // line are found too: "et<line-break>al.".
- // Trick: skip a few chars for C/shell/Vim comments
- nextline[SPWORDLEN] = NUL;
- if (lnum < wp->w_buffer->b_ml.ml_line_count) {
- line = ml_get_buf(wp->w_buffer, lnum + 1, false);
- spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
- }
-
- // When a word wrapped from the previous line the start of the current
- // line is valid.
- if (lnum == checked_lnum) {
- cur_checked_col = checked_col;
- }
- checked_lnum = 0;
-
- // When there was a sentence end in the previous line may require a
- // word starting with capital in this line. In line 1 always check
- // the first word.
- if (lnum != capcol_lnum) {
- cap_col = -1;
- }
- if (lnum == 1) {
- cap_col = 0;
- }
- capcol_lnum = 0;
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
// handle Visual active in this window
@@ -845,37 +1157,37 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (VIsual_mode == Ctrl_V) {
// block mode
if (lnum_in_visual_area) {
- fromcol = wp->w_old_cursor_fcol;
- tocol = wp->w_old_cursor_lcol;
+ wlv.fromcol = wp->w_old_cursor_fcol;
+ wlv.tocol = wp->w_old_cursor_lcol;
}
} else {
// non-block mode
if (lnum > top->lnum && lnum <= bot->lnum) {
- fromcol = 0;
+ wlv.fromcol = 0;
} else if (lnum == top->lnum) {
if (VIsual_mode == 'V') { // linewise
- fromcol = 0;
+ wlv.fromcol = 0;
} else {
- getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL);
+ getvvcol(wp, top, (colnr_T *)&wlv.fromcol, NULL, NULL);
if (gchar_pos(top) == NUL) {
- tocol = fromcol + 1;
+ wlv.tocol = wlv.fromcol + 1;
}
}
}
if (VIsual_mode != 'V' && lnum == bot->lnum) {
if (*p_sel == 'e' && bot->col == 0
&& bot->coladd == 0) {
- fromcol = -10;
- tocol = MAXCOL;
+ wlv.fromcol = -10;
+ wlv.tocol = MAXCOL;
} else if (bot->col == MAXCOL) {
- tocol = MAXCOL;
+ wlv.tocol = MAXCOL;
} else {
pos = *bot;
if (*p_sel == 'e') {
- getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL);
+ getvvcol(wp, &pos, (colnr_T *)&wlv.tocol, NULL, NULL);
} else {
- getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol);
- tocol++;
+ getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&wlv.tocol);
+ wlv.tocol++;
}
}
}
@@ -888,7 +1200,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
// if inverting in this line set area_highlighting
- if (fromcol >= 0) {
+ if (wlv.fromcol >= 0) {
area_highlighting = true;
vi_attr = win_hl_attr(wp, HLF_V);
}
@@ -900,18 +1212,18 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& lnum <= curwin->w_cursor.lnum + search_match_lines) {
if (lnum == curwin->w_cursor.lnum) {
getvcol(curwin, &(curwin->w_cursor),
- (colnr_T *)&fromcol, NULL, NULL);
+ (colnr_T *)&wlv.fromcol, NULL, NULL);
} else {
- fromcol = 0;
+ wlv.fromcol = 0;
}
if (lnum == curwin->w_cursor.lnum + search_match_lines) {
pos.lnum = lnum;
pos.col = search_match_endcol;
- getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL);
+ getvcol(curwin, &pos, (colnr_T *)&wlv.tocol, NULL, NULL);
}
// do at least one character; happens when past end of line
- if (fromcol == tocol && search_match_endcol) {
- tocol = fromcol + 1;
+ if (wlv.fromcol == wlv.tocol && search_match_endcol) {
+ wlv.tocol = wlv.fromcol + 1;
}
area_highlighting = true;
vi_attr = win_hl_attr(wp, HLF_I);
@@ -921,88 +1233,135 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int bg_attr = win_bg_attr(wp);
int linestatus = 0;
- filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
- if (filler_lines < 0 || linestatus < 0) {
- if (filler_lines == -1 || linestatus == -1) {
+ wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
+ if (wlv.filler_lines < 0 || linestatus < 0) {
+ if (wlv.filler_lines == -1 || linestatus == -1) {
if (diff_find_change(wp, lnum, &change_start, &change_end)) {
- diff_hlf = HLF_ADD; // added line
+ wlv.diff_hlf = HLF_ADD; // added line
} else if (change_start == 0) {
- diff_hlf = HLF_TXD; // changed text
+ wlv.diff_hlf = HLF_TXD; // changed text
} else {
- diff_hlf = HLF_CHD; // changed line
+ wlv.diff_hlf = HLF_CHD; // changed line
}
} else {
- diff_hlf = HLF_ADD; // added line
+ wlv.diff_hlf = HLF_ADD; // added line
}
if (linestatus == 0) {
- filler_lines = 0;
+ wlv.filler_lines = 0;
}
area_highlighting = true;
}
VirtLines virt_lines = KV_INITIAL_VALUE;
- int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines, has_fold);
- filler_lines += n_virt_lines;
+ wlv.n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines, has_fold);
+ wlv.filler_lines += wlv.n_virt_lines;
if (lnum == wp->w_topline) {
- filler_lines = wp->w_topfill;
- n_virt_lines = MIN(n_virt_lines, filler_lines);
+ wlv.filler_lines = wp->w_topfill;
+ wlv.n_virt_lines = MIN(wlv.n_virt_lines, wlv.filler_lines);
}
- filler_todo = filler_lines;
+ wlv.filler_todo = wlv.filler_lines;
// Cursor line highlighting for 'cursorline' in the current window.
- if (lnum == wp->w_cursor.lnum) {
- // Do not show the cursor line in the text when Visual mode is active,
- // because it's not clear what is selected then.
- if (wp->w_p_cul && !(wp == curwin && VIsual_active)
- && wp->w_p_culopt_flags != CULOPT_NBR) {
- cul_screenline = (wp->w_p_wrap
- && (wp->w_p_culopt_flags & CULOPT_SCRLINE));
- if (!cul_screenline) {
- apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio);
- } else {
- margin_columns_win(wp, &left_curline_col, &right_curline_col);
- }
- area_highlighting = true;
+ if (wp->w_p_cul && wp->w_p_culopt_flags != CULOPT_NBR && lnum == wp->w_cursorline
+ // Do not show the cursor line in the text when Visual mode is active,
+ // because it's not clear what is selected then.
+ && !(wp == curwin && VIsual_active)) {
+ cul_screenline = (wp->w_p_wrap && (wp->w_p_culopt_flags & CULOPT_SCRLINE));
+ if (!cul_screenline) {
+ apply_cursorline_highlight(wp, &wlv);
+ } else {
+ margin_columns_win(wp, &left_curline_col, &right_curline_col);
}
+ area_highlighting = true;
}
- SignTextAttrs sattrs[SIGN_SHOW_MAX]; // sign attributes for the sign column
- int sign_num_attr = 0; // sign attribute for the number column
- int sign_cul_attr = 0; // sign attribute for cursorline
- CLEAR_FIELD(sattrs);
- int num_signs = get_sign_attrs(buf, lnum, sattrs, &line_attr, &sign_num_attr, &sign_cul_attr);
+ int line_attr = 0;
+ int sign_cul_attr = 0;
+ int sign_num_attr = 0;
+ // TODO(bfredl, vigoux): line_attr should not take priority over decoration!
+ decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs, &line_attr, &sign_cul_attr, &sign_num_attr);
+
+ statuscol_T statuscol = { 0 };
+ if (*wp->w_p_stc != NUL) {
+ // Draw the 'statuscolumn' if option is set.
+ statuscol.draw = true;
+ statuscol.sattrs = wlv.sattrs;
+ statuscol.foldinfo = foldinfo;
+ statuscol.width = win_col_off(wp) - (cmdwin_type != 0 && wp == curwin);
+ statuscol.use_cul = use_cursor_line_highlight(wp, lnum);
+ statuscol.sign_cul_id = statuscol.use_cul ? sign_cul_attr : 0;
+ statuscol.num_attr = sign_num_attr > 0 ? syn_id2attr(sign_num_attr) : 0;
+ } else {
+ if (sign_cul_attr > 0) {
+ sign_cul_attr = syn_id2attr(sign_cul_attr);
+ }
+ if (sign_num_attr > 0) {
+ sign_num_attr = syn_id2attr(sign_num_attr);
+ }
+ }
+ if (line_attr > 0) {
+ wlv.line_attr = syn_id2attr(line_attr);
+ }
// Highlight the current line in the quickfix window.
if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) {
- line_attr = win_hl_attr(wp, HLF_QFL);
+ wlv.line_attr = win_hl_attr(wp, HLF_QFL);
}
- if (line_attr_lowprio || line_attr) {
+ if (wlv.line_attr_lowprio || wlv.line_attr) {
area_highlighting = true;
}
if (cul_screenline) {
- line_attr_save = line_attr;
- line_attr_lowprio_save = line_attr_lowprio;
+ line_attr_save = wlv.line_attr;
+ line_attr_lowprio_save = wlv.line_attr_lowprio;
}
- line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum, false);
- ptr = line;
-
- if (has_spell && !number_only) {
- // For checking first word with a capital skip white space.
- if (cap_col == 0) {
- cap_col = (int)getwhitecols(line);
- }
+ if (spv->spv_has_spell && !number_only) {
+ // Prepare for spell checking.
+ extra_check = true;
- // To be able to spell-check over line boundaries copy the end of the
- // current line into nextline[]. Above the start of the next line was
- // copied to nextline[SPWORDLEN].
+ // When a word wrapped from the previous line the start of the
+ // current line is valid.
+ if (lnum == spv->spv_checked_lnum) {
+ cur_checked_col = spv->spv_checked_col;
+ }
+ // Previous line was not spell checked, check for capital. This happens
+ // for the first line in an updated region or after a closed fold.
+ if (spv->spv_capcol_lnum == 0 && check_need_cap(wp, lnum, 0)) {
+ spv->spv_cap_col = 0;
+ } else if (lnum != spv->spv_capcol_lnum) {
+ spv->spv_cap_col = -1;
+ }
+ spv->spv_checked_lnum = 0;
+
+ // Get the start of the next line, so that words that wrap to the
+ // next line are found too: "et<line-break>al.".
+ // Trick: skip a few chars for C/shell/Vim comments
+ nextline[SPWORDLEN] = NUL;
+ if (lnum < wp->w_buffer->b_ml.ml_line_count) {
+ line = ml_get_buf(wp->w_buffer, lnum + 1);
+ spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
+ }
+ assert(!end_fill);
+ line = ml_get_buf(wp->w_buffer, lnum);
+
+ // If current line is empty, check first word in next line for capital.
+ ptr = skipwhite(line);
+ if (*ptr == NUL) {
+ spv->spv_cap_col = 0;
+ spv->spv_capcol_lnum = lnum + 1;
+ } else if (spv->spv_cap_col == 0) {
+ // For checking first word with a capital skip white space.
+ spv->spv_cap_col = (int)(ptr - line);
+ }
+
+ // Copy the end of the current line into nextline[].
if (nextline[SPWORDLEN] == NUL) {
// No next line or it is empty.
nextlinecol = MAXCOL;
nextline_idx = 0;
} else {
- v = (long)strlen(line);
+ v = (ptrdiff_t)strlen(line);
if (v < SPWORDLEN) {
// Short line, use it completely and append the start of the
// next line.
@@ -1013,12 +1372,21 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
} else {
// Long line, use only the last SPWORDLEN bytes.
nextlinecol = (int)v - SPWORDLEN;
- memmove(nextline, line + nextlinecol, SPWORDLEN); // -V1086
+ memmove(nextline, line + nextlinecol, SPWORDLEN);
nextline_idx = SPWORDLEN + 1;
}
}
}
+ line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum);
+ ptr = line;
+
+ colnr_T trailcol = MAXCOL; // start of trailing spaces
+ colnr_T leadcol = 0; // start of leading spaces
+
+ int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used
+ int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used
+
if (wp->w_p_list && !has_fold && !end_fill) {
if (wp->w_p_lcs_chars.space
|| wp->w_p_lcs_chars.multispace != NULL
@@ -1028,50 +1396,52 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
|| wp->w_p_lcs_chars.nbsp) {
extra_check = true;
}
- // find start of trailing whitespace
- if (wp->w_p_lcs_chars.trail) {
- trailcol = (colnr_T)strlen(ptr);
- while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) {
- trailcol--;
- }
- trailcol += (colnr_T)(ptr - line);
- }
- // find end of leading whitespace
- if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
- leadcol = 0;
- while (ascii_iswhite(ptr[leadcol])) {
- leadcol++;
- }
- if (ptr[leadcol] == NUL) {
- // in a line full of spaces all of them are treated as trailing
- leadcol = (colnr_T)0;
- } else {
- // keep track of the first column not filled with spaces
- leadcol += (colnr_T)(ptr - line) + 1;
- }
- }
+ trailcol = get_trailcol(wp, ptr, line);
+ leadcol = get_leadcol(wp, ptr, line);
}
// 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
// first character to be displayed.
if (wp->w_p_wrap) {
- v = wp->w_skipcol;
+ v = startrow == 0 ? wp->w_skipcol : 0;
} else {
v = wp->w_leftcol;
}
if (v > 0 && !number_only) {
char *prev_ptr = ptr;
chartabsize_T cts;
- int charsize;
+ int charsize = 0;
+ int head = 0;
- init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, ptr);
+ init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, ptr);
+ cts.cts_max_head_vcol = (int)v;
while (cts.cts_vcol < v && *cts.cts_ptr != NUL) {
- charsize = win_lbr_chartabsize(&cts, NULL);
+ head = 0;
+ charsize = win_lbr_chartabsize(&cts, &head);
cts.cts_vcol += charsize;
prev_ptr = cts.cts_ptr;
MB_PTR_ADV(cts.cts_ptr);
+ if (wp->w_p_list) {
+ in_multispace = *prev_ptr == ' ' && (*cts.cts_ptr == ' '
+ || (prev_ptr > line && prev_ptr[-1] == ' '));
+ if (!in_multispace) {
+ multispace_pos = 0;
+ } else if (cts.cts_ptr >= line + leadcol
+ && wp->w_p_lcs_chars.multispace != NULL) {
+ multispace_pos++;
+ if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ } else if (cts.cts_ptr < line + leadcol
+ && wp->w_p_lcs_chars.leadmultispace != NULL) {
+ multispace_pos++;
+ if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ }
+ }
}
- vcol = cts.cts_vcol;
+ wlv.vcol = cts.cts_vcol;
ptr = cts.cts_ptr;
clear_chartabsize_arg(&cts);
@@ -1081,40 +1451,39 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// - 'virtualedit' is set, or
// - the visual mode is active,
// the end of the line may be before the start of the displayed part.
- if (vcol < v && (wp->w_p_cuc
- || draw_color_col
- || virtual_active()
- || (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
- vcol = v;
+ if (wlv.vcol < v && (wp->w_p_cuc
+ || draw_color_col
+ || virtual_active()
+ || (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
+ wlv.vcol = (colnr_T)v;
}
// Handle a character that's not completely on the screen: Put ptr at
// that character but skip the first few screen characters.
- if (vcol > v) {
- vcol -= charsize;
+ if (wlv.vcol > v) {
+ wlv.vcol -= charsize;
ptr = prev_ptr;
- // If the character fits on the screen, don't need to skip it.
- // Except for a TAB.
- if (utf_ptr2cells(ptr) >= charsize || *ptr == TAB) {
- n_skip = (int)(v - vcol);
- }
+ }
+
+ if (v > wlv.vcol) {
+ wlv.skip_cells = (int)v - wlv.vcol - head;
}
// Adjust for when the inverted text is before the screen,
// and when the start of the inverted text is before the screen.
- if (tocol <= vcol) {
- fromcol = 0;
- } else if (fromcol >= 0 && fromcol < vcol) {
- fromcol = (int)vcol;
+ if (wlv.tocol <= wlv.vcol) {
+ wlv.fromcol = 0;
+ } else if (wlv.fromcol >= 0 && wlv.fromcol < wlv.vcol) {
+ wlv.fromcol = wlv.vcol;
}
// When w_skipcol is non-zero, first line needs 'showbreak'
if (wp->w_p_wrap) {
- need_showbreak = true;
+ wlv.need_showbreak = true;
}
// When spell checking a word we need to figure out the start of the
// word and if it's badly spelled or not.
- if (has_spell) {
+ if (spv->spv_has_spell) {
size_t len;
colnr_T linecol = (colnr_T)(ptr - line);
hlf_T spell_hlf = HLF_COUNT;
@@ -1125,7 +1494,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
len = spell_move_to(wp, FORWARD, true, true, &spell_hlf);
// spell_move_to() may call ml_get() and make "line" invalid
- line = ml_get_buf(wp->w_buffer, lnum, false);
+ line = ml_get_buf(wp->w_buffer, lnum);
ptr = line + linecol;
if (len == 0 || (int)wp->w_cursor.col > ptr - line) {
@@ -1154,20 +1523,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Correct highlighting for cursor that can't be disabled.
// Avoids having to check this for each character.
- if (fromcol >= 0) {
+ if (wlv.fromcol >= 0) {
if (noinvcur) {
- if ((colnr_T)fromcol == wp->w_virtcol) {
+ if ((colnr_T)wlv.fromcol == wp->w_virtcol) {
// highlighting starts at cursor, let it start just after the
// cursor
- fromcol_prev = fromcol;
- fromcol = -1;
- } else if ((colnr_T)fromcol < wp->w_virtcol) {
+ fromcol_prev = wlv.fromcol;
+ wlv.fromcol = -1;
+ } else if ((colnr_T)wlv.fromcol < wp->w_virtcol) {
// restart highlighting after the cursor
fromcol_prev = wp->w_virtcol;
}
}
- if (fromcol >= tocol) {
- fromcol = -1;
+ if (wlv.fromcol >= wlv.tocol) {
+ wlv.fromcol = -1;
}
}
@@ -1179,15 +1548,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
ptr = line + v; // "line" may have been updated
}
- int off = 0; // Offset relative start of line
- int col = 0; // Visual column on screen.
- if (wp->w_p_rl) {
- // Rightleft window: process the text in the normal direction, but put
- // it in linebuf_char[off] from right to left. Start at the
- // rightmost column of the window.
- col = grid->cols - 1;
- off += col;
- }
+ win_line_start(wp, &wlv, false);
// won't highlight after TERM_ATTRS_MAX columns
int term_attrs[TERM_ATTRS_MAX] = { 0 };
@@ -1196,375 +1557,230 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
extra_check = true;
}
- statuscol_T statuscol = { 0 };
- if (*wp->w_p_stc != NUL) {
- // Draw the 'statuscolumn' if option is set.
- statuscol.draw = true;
- statuscol.width = win_col_off(wp);
- }
-
int sign_idx = 0;
int virt_line_index;
int virt_line_offset = -1;
// Repeat for the whole displayed line.
- for (;;) {
+ while (true) {
int has_match_conc = 0; ///< match wants to conceal
int decor_conceal = 0;
bool did_decrement_ptr = false;
// Skip this quickly when working on the text.
- if (draw_state != WL_LINE) {
+ if (wlv.draw_state != WL_LINE) {
if (cul_screenline) {
- cul_attr = 0;
- line_attr = line_attr_save;
- line_attr_lowprio = line_attr_lowprio_save;
+ wlv.cul_attr = 0;
+ wlv.line_attr = line_attr_save;
+ wlv.line_attr_lowprio = line_attr_lowprio_save;
}
- if (draw_state == WL_CMDLINE - 1 && n_extra == 0) {
- draw_state = WL_CMDLINE;
+ if (wlv.draw_state == WL_CMDLINE - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_CMDLINE;
if (cmdwin_type != 0 && wp == curwin) {
// Draw the cmdline character.
- n_extra = 1;
- c_extra = cmdwin_type;
- c_final = NUL;
- char_attr = win_hl_attr(wp, HLF_AT);
+ wlv.n_extra = 1;
+ wlv.c_extra = cmdwin_type;
+ wlv.c_final = NUL;
+ wlv.char_attr = win_hl_attr(wp, HLF_AT);
}
}
- if (draw_state == WL_FOLD - 1 && n_extra == 0) {
- if (filler_todo > 0) {
- int index = filler_todo - (filler_lines - n_virt_lines);
+ if (wlv.draw_state == WL_FOLD - 1 && wlv.n_extra == 0) {
+ if (wlv.filler_todo > 0) {
+ int index = wlv.filler_todo - (wlv.filler_lines - wlv.n_virt_lines);
if (index > 0) {
virt_line_index = (int)kv_size(virt_lines) - index;
assert(virt_line_index >= 0);
virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp);
}
}
- if (!virt_line_offset) {
+ if (virt_line_offset == 0) {
// Skip the column states if there is a "virt_left_col" line.
- draw_state = WL_BRI - 1;
+ wlv.draw_state = WL_BRI - 1;
} else if (statuscol.draw) {
// Skip fold, sign and number states if 'statuscolumn' is set.
- draw_state = WL_STC - 1;
- }
- }
-
- if (draw_state == WL_FOLD - 1 && n_extra == 0) {
- int fdc = compute_foldcolumn(wp, 0);
- draw_state = WL_FOLD;
- if (fdc > 0) {
- // Draw the 'foldcolumn'. Allocate a buffer, "extra" may
- // already be in use.
- xfree(p_extra_free);
- p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1);
- n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
- p_extra_free[n_extra] = NUL;
- p_extra = p_extra_free;
- c_extra = NUL;
- c_final = NUL;
- if (use_cursor_line_sign(wp, lnum)) {
- char_attr = win_hl_attr(wp, HLF_CLF);
- } else {
- char_attr = win_hl_attr(wp, HLF_FC);
- }
+ wlv.draw_state = WL_STC - 1;
}
}
+ if (wlv.draw_state == WL_FOLD - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_FOLD;
+ handle_foldcolumn(wp, &wlv);
+ }
+
// sign column, this is hit until sign_idx reaches count
- if (draw_state == WL_SIGN - 1 && n_extra == 0) {
- draw_state = WL_SIGN;
- // Show the sign column when there are any signs in this buffer
+ if (wlv.draw_state == WL_SIGN - 1 && wlv.n_extra == 0) {
+ // Show the sign column when desired.
+ wlv.draw_state = WL_SIGN;
if (wp->w_scwidth > 0) {
- get_sign_display_info(false, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo,
- &c_extra, &c_final, extra, sizeof(extra),
- &p_extra, &n_extra, &char_attr, sign_idx,
- sign_cul_attr);
- sign_idx++;
- if (sign_idx < wp->w_scwidth) {
- draw_state = WL_SIGN - 1;
+ get_sign_display_info(false, wp, &wlv, sign_idx, sign_cul_attr);
+ if (++sign_idx < wp->w_scwidth) {
+ wlv.draw_state = WL_SIGN - 1;
} else {
sign_idx = 0;
}
}
}
- if (draw_state == WL_NR - 1 && n_extra == 0) {
- draw_state = WL_NR;
- // Display the absolute or relative line number. After the
- // first fill with blanks when the 'n' flag isn't in 'cpo'
- if ((wp->w_p_nu || wp->w_p_rnu)
- && (row == startrow + filler_lines
- || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) {
- // If 'signcolumn' is set to 'number' and a sign is present
- // in 'lnum', then display the sign instead of the line
- // number.
- if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) {
- get_sign_display_info(true, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo,
- &c_extra, &c_final, extra, sizeof(extra),
- &p_extra, &n_extra, &char_attr, sign_idx,
- sign_cul_attr);
- } else {
- // Draw the line number (empty space after wrapping).
- if (row == startrow + filler_lines) {
- get_line_number_str(wp, lnum, extra, sizeof(extra));
- if (wp->w_skipcol > 0) {
- for (p_extra = extra; *p_extra == ' '; p_extra++) {
- *p_extra = '-';
- }
- }
- if (wp->w_p_rl) { // reverse line numbers
- // like rl_mirror(), but keep the space at the end
- char *p2 = skipwhite(extra);
- p2 = skiptowhite(p2) - 1;
- for (char *p1 = skipwhite(extra); p1 < p2; p1++, p2--) {
- const char t = *p1;
- *p1 = *p2;
- *p2 = t;
- }
- }
- p_extra = extra;
- c_extra = NUL;
- } else {
- c_extra = ' ';
- }
- c_final = NUL;
- n_extra = number_width(wp) + 1;
- if (sign_num_attr > 0) {
- char_attr = sign_num_attr;
- } else {
- char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines);
- }
- }
- }
+ if (wlv.draw_state == WL_NR - 1 && wlv.n_extra == 0) {
+ // Show the line number, if desired.
+ wlv.draw_state = WL_NR;
+ handle_lnum_col(wp, &wlv, sign_num_attr, sign_cul_attr);
}
- if (draw_state == WL_STC - 1 && n_extra == 0) {
- draw_state = WL_STC;
+ if (wlv.draw_state == WL_STC - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_STC;
// Draw the 'statuscolumn' if option is set.
if (statuscol.draw) {
+ if (sign_num_attr == 0) {
+ statuscol.num_attr = get_line_number_attr(wp, &wlv);
+ }
if (statuscol.textp == NULL) {
- get_statuscol_str(wp, lnum, row, startrow, filler_lines, cul_attr,
- sign_num_attr, sign_cul_attr, &statuscol, foldinfo, sattrs);
+ v = (ptr - line);
+ get_statuscol_str(wp, lnum, wlv.row - startrow - wlv.filler_lines, &statuscol);
+ if (!end_fill) {
+ // Get the line again as evaluating 'statuscolumn' may free it.
+ line = ml_get_buf(wp->w_buffer, lnum);
+ ptr = line + v;
+ }
if (wp->w_redr_statuscol) {
break;
}
}
- get_statuscol_display_info(&statuscol, &draw_state, &char_attr,
- &n_extra, &c_extra, &c_final, &p_extra);
+ get_statuscol_display_info(&statuscol, &wlv);
}
}
- if (draw_state == WL_STC && n_extra == 0) {
- win_col_offset = off;
+ if (wlv.draw_state == WL_STC && wlv.n_extra == 0) {
+ win_col_offset = wlv.off;
}
- if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
- && n_extra == 0 && *get_showbreak_value(wp) != NUL) {
- // draw indent after showbreak value
- draw_state = WL_BRI;
- } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) {
- // after the showbreak, draw the breakindent
- draw_state = WL_BRI - 1;
- }
-
- // draw 'breakindent': indent wrapped text accordingly
- if (draw_state == WL_BRI - 1 && n_extra == 0) {
- draw_state = WL_BRI;
- // if need_showbreak is set, breakindent also applies
- if (wp->w_p_bri && (row != startrow || need_showbreak)
- && filler_lines == 0) {
- char_attr = 0;
-
- if (diff_hlf != (hlf_T)0) {
- char_attr = win_hl_attr(wp, (int)diff_hlf);
- }
- p_extra = NULL;
- c_extra = ' ';
- c_final = NUL;
- n_extra =
- get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false));
- if (row == startrow) {
- n_extra -= win_col_off2(wp);
- if (n_extra < 0) {
- n_extra = 0;
- }
- }
- if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
- need_showbreak = false;
- }
- // Correct end of highlighted area for 'breakindent',
- // required wen 'linebreak' is also set.
- if (tocol == vcol) {
- tocol += n_extra;
- }
- }
+ // Check if 'breakindent' applies and show it.
+ // May change wlv.draw_state to WL_BRI or WL_BRI - 1.
+ if (wlv.n_extra == 0) {
+ handle_breakindent(wp, &wlv);
}
- if (draw_state == WL_SBR - 1 && n_extra == 0) {
- draw_state = WL_SBR;
- if (filler_todo > filler_lines - n_virt_lines) {
- // TODO(bfredl): check this doesn't inhibit TUI-style
- // clear-to-end-of-line.
- c_extra = ' ';
- c_final = NUL;
- if (wp->w_p_rl) {
- n_extra = col + 1;
- } else {
- n_extra = grid->cols - col;
- }
- char_attr = 0;
- } else if (filler_todo > 0) {
- // Draw "deleted" diff line(s)
- if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
- c_extra = '-';
- c_final = NUL;
- } else {
- c_extra = wp->w_p_fcs_chars.diff;
- c_final = NUL;
- }
- if (wp->w_p_rl) {
- n_extra = col + 1;
- } else {
- n_extra = grid->cols - col;
- }
- char_attr = win_hl_attr(wp, HLF_DED);
- }
- char *const sbr = get_showbreak_value(wp);
- if (*sbr != NUL && need_showbreak) {
- // Draw 'showbreak' at the start of each broken line.
- p_extra = sbr;
- c_extra = NUL;
- c_final = NUL;
- n_extra = (int)strlen(sbr);
- char_attr = win_hl_attr(wp, HLF_AT);
- if (wp->w_skipcol == 0 || !wp->w_p_wrap) {
- need_showbreak = false;
- }
- vcol_sbr = vcol + mb_charlen(sbr);
- // Correct end of highlighted area for 'showbreak',
- // required when 'linebreak' is also set.
- if (tocol == vcol) {
- tocol += n_extra;
- }
- // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
- if (cul_attr) {
- char_attr = hl_combine_attr(cul_attr, char_attr);
- }
- }
+ if (wlv.draw_state == WL_SBR - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_SBR;
+ handle_showbreak_and_filler(wp, &wlv);
}
- if (draw_state == WL_LINE - 1 && n_extra == 0) {
+ if (wlv.draw_state == WL_LINE - 1 && wlv.n_extra == 0) {
sign_idx = 0;
- draw_state = WL_LINE;
-
- if (has_decor && row == startrow + filler_lines) {
- // hide virt_text on text hidden by 'nowrap'
- decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state);
- }
-
- if (saved_n_extra) {
- // Continue item from end of wrapped line.
- n_extra = saved_n_extra;
- c_extra = saved_c_extra;
- c_final = saved_c_final;
- p_extra = saved_p_extra;
- char_attr = saved_char_attr;
- } else {
- char_attr = 0;
+ wlv.draw_state = WL_LINE;
+ if (has_decor && wlv.row == startrow + wlv.filler_lines) {
+ // hide virt_text on text hidden by 'nowrap' or 'smoothscroll'
+ decor_redraw_col(wp, (colnr_T)(ptr - line) - 1, wlv.off, true, &decor_state);
}
+ win_line_continue(&wlv); // use wlv.saved_ values
}
}
- if (cul_screenline && draw_state == WL_LINE
- && vcol >= left_curline_col
- && vcol < right_curline_col) {
- apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio);
+ if (cul_screenline && wlv.draw_state == WL_LINE
+ && wlv.vcol >= left_curline_col
+ && wlv.vcol < right_curline_col) {
+ apply_cursorline_highlight(wp, &wlv);
}
// When still displaying '$' of change command, stop at cursor
if (((dollar_vcol >= 0
&& wp == curwin
&& lnum == wp->w_cursor.lnum
- && vcol >= (long)wp->w_virtcol)
- || (number_only && draw_state > WL_STC))
- && filler_todo <= 0) {
- draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
- grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, bg_attr, false);
+ && wlv.vcol >= wp->w_virtcol)
+ || (number_only && wlv.draw_state > WL_STC))
+ && wlv.filler_todo <= 0) {
+ draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
+ // don't clear anything after wlv.col
+ win_put_linebuf(wp, wlv.row, 0, wlv.col, wlv.col, bg_attr, false);
// Pretend we have finished updating the window. Except when
// 'cursorcolumn' is set.
if (wp->w_p_cuc) {
- row = wp->w_cline_row + wp->w_cline_height;
+ wlv.row = wp->w_cline_row + wp->w_cline_height;
} else {
- row = grid->rows;
+ wlv.row = grid->rows;
}
break;
}
- if (draw_state == WL_LINE
- && has_fold
- && col == win_col_offset
- && n_extra == 0
- && row == startrow + filler_lines) {
- char_attr = win_hl_attr(wp, HLF_FL);
-
- linenr_T lnume = lnum + foldinfo.fi_lines - 1;
- memset(buf_fold, ' ', FOLD_TEXT_LEN);
- p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold);
- n_extra = (int)strlen(p_extra);
-
- if (p_extra != buf_fold) {
- xfree(p_extra_free);
- p_extra_free = p_extra;
- }
- c_extra = NUL;
- c_final = NUL;
- p_extra[n_extra] = NUL;
+ const bool draw_folded = wlv.draw_state == WL_LINE && has_fold
+ && wlv.row == startrow + wlv.filler_lines;
+ if (draw_folded && wlv.n_extra == 0) {
+ wlv.char_attr = folded_attr = win_hl_attr(wp, HLF_FL);
}
- if (draw_state == WL_LINE
- && has_fold
- && col < grid->cols
- && n_extra == 0
- && row == startrow + filler_lines) {
- // fill rest of line with 'fold'
- c_extra = wp->w_p_fcs_chars.fold;
- c_final = NUL;
+ int extmark_attr = 0;
+ if (wlv.draw_state == WL_LINE
+ && (area_highlighting || spv->spv_has_spell || extra_check)) {
+ if (wlv.n_extra == 0 || !wlv.extra_for_extmark) {
+ wlv.reset_extra_attr = false;
+ }
- n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col);
- }
+ if (has_decor && wlv.n_extra == 0) {
+ // Duplicate the Visual area check after this block,
+ // but don't check inside p_extra here.
+ if (wlv.vcol == wlv.fromcol
+ || (wlv.vcol + 1 == wlv.fromcol
+ && (wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1))
+ || (vcol_prev == fromcol_prev
+ && vcol_prev < wlv.vcol
+ && wlv.vcol < wlv.tocol)) {
+ area_active = true;
+ } else if (area_active
+ && (wlv.vcol == wlv.tocol
+ || (noinvcur && wlv.vcol == wp->w_virtcol))) {
+ area_active = false;
+ }
+
+ bool selected = (area_active || (area_highlighting && noinvcur
+ && wlv.vcol == wp->w_virtcol));
+ if (decor_need_recheck) {
+ decor_recheck_draw_col(wlv.off, selected, &decor_state);
+ decor_need_recheck = false;
+ }
+ extmark_attr = decor_redraw_col(wp, (colnr_T)v, wlv.off, selected, &decor_state);
+
+ if (!has_fold && wp->w_buffer->b_virt_text_inline > 0) {
+ handle_inline_virtual_text(wp, &wlv, v);
+ if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) {
+ // restore search_attr and area_attr when n_extra is down to zero
+ // TODO(bfredl): this is ugly as fuck. look if we can do this some other way.
+ saved_search_attr = search_attr;
+ saved_area_attr = area_attr;
+ saved_decor_attr = decor_attr;
+ saved_search_attr_from_match = search_attr_from_match;
+ search_attr = 0;
+ area_attr = 0;
+ decor_attr = 0;
+ search_attr_from_match = false;
+ }
+ }
+ }
- if (draw_state == WL_LINE
- && has_fold
- && col >= grid->cols
- && n_extra != 0
- && row == startrow + filler_lines) {
- // Truncate the folding.
- n_extra = 0;
- }
+ int *area_attr_p
+ = wlv.extra_for_extmark && wlv.virt_inline_hl_mode <= kHlModeReplace
+ ? &saved_area_attr : &area_attr;
- if (draw_state == WL_LINE && (area_highlighting || has_spell)) {
// handle Visual or match highlighting in this line
- if (vcol == fromcol
- || (vcol + 1 == fromcol && n_extra == 0
- && utf_ptr2cells(ptr) > 1)
- || ((int)vcol_prev == fromcol_prev
- && vcol_prev < vcol // not at margin
- && vcol < tocol)) {
- area_attr = vi_attr; // start highlighting
- if (area_highlighting) {
- area_active = true;
- }
- } else if (area_attr != 0 && (vcol == tocol
- || (noinvcur
- && (colnr_T)vcol == wp->w_virtcol))) {
- area_attr = 0; // stop highlighting
+ if (wlv.vcol == wlv.fromcol
+ || (wlv.vcol + 1 == wlv.fromcol
+ && ((wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1)
+ || (wlv.n_extra > 0 && wlv.p_extra != NULL
+ && utf_ptr2cells(wlv.p_extra) > 1)))
+ || (vcol_prev == fromcol_prev
+ && vcol_prev < wlv.vcol // not at margin
+ && wlv.vcol < wlv.tocol)) {
+ *area_attr_p = vi_attr; // start highlighting
+ area_active = true;
+ } else if (*area_attr_p != 0
+ && (wlv.vcol == wlv.tocol
+ || (noinvcur && wlv.vcol == wp->w_virtcol))) {
+ *area_attr_p = 0; // stop highlighting
area_active = false;
}
- if (!n_extra) {
+ if (!has_fold && wlv.n_extra == 0) {
// Check for start/end of 'hlsearch' and other matches.
// After end, check for start/end of next match.
// When another match, have to check for start again.
@@ -1581,22 +1797,24 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
}
- if (diff_hlf != (hlf_T)0) {
- if (diff_hlf == HLF_CHD && ptr - line >= change_start
- && n_extra == 0) {
- diff_hlf = HLF_TXD; // changed text
+ if (wlv.diff_hlf != (hlf_T)0) {
+ // When there is extra text (eg: virtual text) it gets the
+ // diff highlighting for the line, but not for changed text.
+ if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start
+ && wlv.n_extra == 0) {
+ wlv.diff_hlf = HLF_TXD; // changed text
}
- if (diff_hlf == HLF_TXD && ptr - line > change_end
- && n_extra == 0) {
- diff_hlf = HLF_CHD; // changed line
+ if (wlv.diff_hlf == HLF_TXD && ((ptr - line > change_end && wlv.n_extra == 0)
+ || (wlv.n_extra > 0 && wlv.extra_for_extmark))) {
+ wlv.diff_hlf = HLF_CHD; // changed line
}
- line_attr = win_hl_attr(wp, (int)diff_hlf);
+ wlv.line_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
// Overlay CursorLine onto diff-mode highlight.
- if (cul_attr) {
- line_attr = 0 != line_attr_lowprio // Low-priority CursorLine
- ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr),
- hl_get_underline())
- : hl_combine_attr(line_attr, cul_attr);
+ if (wlv.cul_attr) {
+ wlv.line_attr = 0 != wlv.line_attr_lowprio // Low-priority CursorLine
+ ? hl_combine_attr(hl_combine_attr(wlv.cul_attr, wlv.line_attr),
+ hl_get_underline())
+ : hl_combine_attr(wlv.line_attr, wlv.cul_attr);
}
}
@@ -1604,29 +1822,61 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
attr_pri = true;
if (area_attr != 0) {
- char_attr = hl_combine_attr(line_attr, area_attr);
+ wlv.char_attr = hl_combine_attr(wlv.line_attr, area_attr);
if (!highlight_match) {
// let search highlight show in Visual area if possible
- char_attr = hl_combine_attr(search_attr, char_attr);
+ wlv.char_attr = hl_combine_attr(search_attr, wlv.char_attr);
}
} else if (search_attr != 0) {
- char_attr = hl_combine_attr(line_attr, search_attr);
- } else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL)
- || vcol < fromcol || vcol_prev < fromcol_prev
- || vcol >= tocol)) {
- // Use line_attr when not in the Visual or 'incsearch' area
+ wlv.char_attr = hl_combine_attr(wlv.line_attr, search_attr);
+ } else if (wlv.line_attr != 0
+ && ((wlv.fromcol == -10 && wlv.tocol == MAXCOL)
+ || wlv.vcol < wlv.fromcol
+ || vcol_prev < fromcol_prev
+ || wlv.vcol >= wlv.tocol)) {
+ // Use wlv.line_attr when not in the Visual or 'incsearch' area
// (area_attr may be 0 when "noinvcur" is set).
- char_attr = line_attr;
+ wlv.char_attr = wlv.line_attr;
} else {
attr_pri = false;
- if (has_syntax) {
- char_attr = syntax_attr;
- } else {
- char_attr = 0;
- }
+ wlv.char_attr = decor_attr;
+ }
+
+ if (folded_attr != 0) {
+ wlv.char_attr = hl_combine_attr(folded_attr, wlv.char_attr);
}
}
+ if (draw_folded && wlv.n_extra == 0 && wlv.col == win_col_offset) {
+ linenr_T lnume = lnum + foldinfo.fi_lines - 1;
+ memset(buf_fold, ' ', FOLD_TEXT_LEN);
+ wlv.p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold, &fold_vt);
+ wlv.n_extra = (int)strlen(wlv.p_extra);
+
+ if (wlv.p_extra != buf_fold) {
+ foldtext_free = wlv.p_extra;
+ }
+ wlv.c_extra = NUL;
+ wlv.c_final = NUL;
+ wlv.p_extra[wlv.n_extra] = NUL;
+
+ // Get the line again as evaluating 'foldtext' may free it.
+ line = ml_get_buf(wp->w_buffer, lnum);
+ ptr = line + v;
+ }
+
+ if (draw_folded && wlv.n_extra == 0 && wlv.col < grid->cols) {
+ // Fill rest of line with 'fold'.
+ wlv.c_extra = wp->w_p_fcs_chars.fold;
+ wlv.c_final = NUL;
+ wlv.n_extra = grid->cols - wlv.col;
+ }
+
+ if (draw_folded && wlv.n_extra != 0 && wlv.col >= grid->cols) {
+ // Truncate the folding.
+ wlv.n_extra = 0;
+ }
+
// Get the next character to put on the screen.
//
// The "p_extra" points to the extra stuff that is inserted to
@@ -1636,151 +1886,119 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past
// "p_extra[n_extra]".
// For the '$' of the 'list' option, n_extra == 1, p_extra == "".
- if (n_extra > 0) {
- if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) {
- c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra;
- mb_c = c; // doesn't handle non-utf-8 multi-byte!
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ if (wlv.n_extra > 0) {
+ if (wlv.c_extra != NUL || (wlv.n_extra == 1 && wlv.c_final != NUL)) {
+ mb_c = (wlv.n_extra == 1 && wlv.c_final != NUL) ? wlv.c_final : wlv.c_extra;
+ mb_schar = schar_from_char(mb_c);
+ wlv.n_extra--;
} else {
- assert(p_extra != NULL);
- c = (uint8_t)(*p_extra);
- mb_c = c;
- // If the UTF-8 character is more than one byte:
- // Decode it into "mb_c".
- mb_l = utfc_ptr2len(p_extra);
- mb_utf8 = false;
- if (mb_l > n_extra) {
- mb_l = 1;
- } else if (mb_l > 1) {
- mb_c = utfc_ptr2char(p_extra, u8cc);
- mb_utf8 = true;
- c = 0xc0;
- }
- if (mb_l == 0) { // at the NUL at end-of-line
+ assert(wlv.p_extra != NULL);
+ mb_l = utfc_ptr2len(wlv.p_extra);
+ mb_schar = utfc_ptr2schar(wlv.p_extra, &mb_c);
+ // mb_l=0 at the end-of-line NUL
+ if (mb_l > wlv.n_extra || mb_l == 0) {
mb_l = 1;
}
// If a double-width char doesn't fit display a '>' in the last column.
- if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1))
- && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
+ // Don't advance the pointer but put the character at the start of the next line.
+ if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
+ mb_c = '>';
mb_l = 1;
(void)mb_l;
+ mb_schar = schar_from_ascii(mb_c);
multi_attr = win_hl_attr(wp, HLF_AT);
- if (cul_attr) {
- multi_attr = 0 != line_attr_lowprio
- ? hl_combine_attr(cul_attr, multi_attr)
- : hl_combine_attr(multi_attr, cul_attr);
+ if (wlv.cul_attr) {
+ multi_attr = 0 != wlv.line_attr_lowprio
+ ? hl_combine_attr(wlv.cul_attr, multi_attr)
+ : hl_combine_attr(multi_attr, wlv.cul_attr);
}
-
- // put the pointer back to output the double-width
- // character at the start of the next line.
- n_extra++;
- p_extra--;
} else {
- n_extra -= mb_l - 1;
- p_extra += mb_l - 1;
+ wlv.n_extra -= mb_l;
+ wlv.p_extra += mb_l;
}
- p_extra++;
}
- n_extra--;
- } else if (foldinfo.fi_lines > 0) {
+
+ // Only restore search_attr and area_attr after "n_extra" in
+ // the next screen line is also done.
+ if (wlv.n_extra <= 0) {
+ if (wlv.saved_n_extra <= 0) {
+ if (search_attr == 0) {
+ search_attr = saved_search_attr;
+ saved_search_attr = 0;
+ }
+ if (area_attr == 0 && *ptr != NUL) {
+ area_attr = saved_area_attr;
+ saved_area_attr = 0;
+ }
+ if (decor_attr == 0) {
+ decor_attr = saved_decor_attr;
+ saved_decor_attr = 0;
+ }
+
+ if (wlv.extra_for_extmark) {
+ // wlv.extra_attr should be used at this position but not
+ // any further.
+ wlv.reset_extra_attr = true;
+ }
+ }
+ wlv.extra_for_extmark = false;
+ }
+ } else if (has_fold) {
// skip writing the buffer line itself
- c = NUL;
- XFREE_CLEAR(p_extra_free);
+ mb_c = NUL;
} else {
- int c0;
+ char *prev_ptr = ptr;
- XFREE_CLEAR(p_extra_free);
+ // first byte of next char
+ int c0 = (uint8_t)(*ptr);
+ if (c0 == NUL) {
+ // no more cells to skip
+ wlv.skip_cells = 0;
+ }
// Get a character from the line itself.
- c0 = c = (uint8_t)(*ptr);
- mb_c = c;
- // If the UTF-8 character is more than one byte: Decode it
- // into "mb_c".
mb_l = utfc_ptr2len(ptr);
- mb_utf8 = false;
- if (mb_l > 1) {
- mb_c = utfc_ptr2char(ptr, u8cc);
- // Overlong encoded ASCII or ASCII with composing char
- // is displayed normally, except a NUL.
- if (mb_c < 0x80) {
- c0 = c = mb_c;
- }
- mb_utf8 = true;
-
- // At start of the line we can have a composing char.
- // Draw it as a space with a composing char.
- if (utf_iscomposing(mb_c)) {
- int i;
-
- for (i = MAX_MCO - 1; i > 0; i--) {
- u8cc[i] = u8cc[i - 1];
- }
- u8cc[0] = mb_c;
- mb_c = ' ';
- }
+ mb_schar = utfc_ptr2schar(ptr, &mb_c);
+
+ // Overlong encoded ASCII or ASCII with composing char
+ // is displayed normally, except a NUL.
+ if (mb_l > 1 && mb_c < 0x80) {
+ c0 = mb_c;
}
- if ((mb_l == 1 && c >= 0x80)
+ if ((mb_l == 1 && c0 >= 0x80)
|| (mb_l >= 1 && mb_c == 0)
|| (mb_l > 1 && (!vim_isprintc(mb_c)))) {
// Illegal UTF-8 byte: display as <xx>.
- // Non-BMP character : display as ? or fullwidth ?.
- transchar_hex(extra, mb_c);
+ // Non-printable character : display as ? or fullwidth ?.
+ transchar_hex(wlv.extra, mb_c);
if (wp->w_p_rl) { // reverse
- rl_mirror(extra);
+ rl_mirror_ascii(wlv.extra, NULL);
}
- p_extra = extra;
- c = (uint8_t)(*p_extra);
- mb_c = mb_ptr2char_adv((const char **)&p_extra);
- mb_utf8 = (c >= 0x80);
- n_extra = (int)strlen(p_extra);
- c_extra = NUL;
- c_final = NUL;
+ wlv.p_extra = wlv.extra;
+ mb_c = mb_ptr2char_adv((const char **)&wlv.p_extra);
+ mb_schar = schar_from_char(mb_c);
+ wlv.n_extra = (int)strlen(wlv.p_extra);
+ wlv.c_extra = NUL;
+ wlv.c_final = NUL;
if (area_attr == 0 && search_attr == 0) {
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_8);
- saved_attr2 = char_attr; // save current attr
+ wlv.n_attr = wlv.n_extra + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_8);
+ saved_attr2 = wlv.char_attr; // save current attr
}
} else if (mb_l == 0) { // at the NUL at end-of-line
mb_l = 1;
- } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) {
- // Do Arabic shaping.
- int pc, pc1, nc;
- int pcc[MAX_MCO];
-
- // The idea of what is the previous and next
- // character depends on 'rightleft'.
- if (wp->w_p_rl) {
- pc = prev_c;
- pc1 = prev_c1;
- nc = utf_ptr2char(ptr + mb_l);
- prev_c1 = u8cc[0];
- } else {
- pc = utfc_ptr2char(ptr + mb_l, pcc);
- nc = prev_c;
- pc1 = pcc[0];
- }
- prev_c = mb_c;
-
- mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc);
- } else {
- prev_c = mb_c;
}
// If a double-width char doesn't fit display a '>' in the
// last column; the character is displayed at the start of the
// next line.
- if ((wp->w_p_rl ? (col <= 0) :
- (col >= grid->cols - 1))
- && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
- mb_utf8 = false;
+ if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
+ mb_c = '>';
mb_l = 1;
+ mb_schar = schar_from_ascii(mb_c);
multi_attr = win_hl_attr(wp, HLF_AT);
// Put pointer back so that the character will be
// displayed at the start of the next line.
@@ -1792,27 +2010,27 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// If a double-width char doesn't fit at the left side display a '<' in
// the first column. Don't do this for unprintable characters.
- if (n_skip > 0 && mb_l > 1 && n_extra == 0) {
- n_extra = 1;
- c_extra = MB_FILLER_CHAR;
- c_final = NUL;
- c = ' ';
+ if (wlv.skip_cells > 0 && mb_l > 1 && wlv.n_extra == 0) {
+ wlv.n_extra = 1;
+ wlv.c_extra = MB_FILLER_CHAR;
+ wlv.c_final = NUL;
+ mb_c = ' ';
+ mb_l = 1;
+ mb_schar = schar_from_ascii(mb_c);
if (area_attr == 0 && search_attr == 0) {
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_AT);
- saved_attr2 = char_attr; // save current attr
+ wlv.n_attr = wlv.n_extra + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_AT);
+ saved_attr2 = wlv.char_attr; // save current attr
}
- mb_c = c;
- mb_utf8 = false;
- mb_l = 1;
}
ptr++;
+ decor_attr = 0;
if (extra_check) {
bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0;
bool can_spell = !no_plain_buffer;
- // Get syntax attribute, unless still at the start of the line
+ // Get extmark and syntax attributes, unless still at the start of the line
// (double-wide char that doesn't fit).
v = (ptr - line);
if (has_syntax && v > 0) {
@@ -1821,10 +2039,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
save_did_emsg = did_emsg;
did_emsg = false;
- syntax_attr = get_syntax_attr((colnr_T)v - 1,
- has_spell ? &can_spell : NULL, false);
+ decor_attr = get_syntax_attr((colnr_T)v - 1,
+ spv->spv_has_spell ? &can_spell : NULL, false);
- if (did_emsg) { // -V547
+ if (did_emsg) {
wp->w_s->b_syn_error = true;
has_syntax = false;
} else {
@@ -1837,50 +2055,35 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Need to get the line again, a multi-line regexp may
// have made it invalid.
- line = ml_get_buf(wp->w_buffer, lnum, false);
+ line = ml_get_buf(wp->w_buffer, lnum);
ptr = line + v;
- if (!attr_pri) {
- if (cul_attr) {
- char_attr = 0 != line_attr_lowprio
- ? hl_combine_attr(cul_attr, syntax_attr)
- : hl_combine_attr(syntax_attr, cul_attr);
- } else {
- char_attr = syntax_attr;
- }
- } else {
- char_attr = hl_combine_attr(syntax_attr, char_attr);
- }
// no concealing past the end of the line, it interferes
// with line highlighting.
- if (c == NUL) {
- syntax_flags = 0;
- } else {
- syntax_flags = get_syntax_info(&syntax_seqnr);
- }
- } else if (!attr_pri) {
- char_attr = 0;
+ syntax_flags = (mb_c == 0) ? 0 : get_syntax_info(&syntax_seqnr);
}
if (has_decor && v > 0) {
- bool selected = (area_active || (area_highlighting && noinvcur
- && (colnr_T)vcol == wp->w_virtcol));
- int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off,
- selected, &decor_state);
- if (extmark_attr != 0) {
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, extmark_attr);
+ // extmarks take preceedence over syntax.c
+ decor_attr = hl_combine_attr(decor_attr, extmark_attr);
+ decor_conceal = decor_state.conceal;
+ can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell);
+ }
+
+ if (decor_attr) {
+ if (!attr_pri) {
+ if (wlv.cul_attr) {
+ wlv.char_attr = 0 != wlv.line_attr_lowprio
+ ? hl_combine_attr(wlv.cul_attr, decor_attr)
+ : hl_combine_attr(decor_attr, wlv.cul_attr);
} else {
- char_attr = hl_combine_attr(extmark_attr, char_attr);
+ wlv.char_attr = decor_attr;
}
+ } else {
+ wlv.char_attr = hl_combine_attr(decor_attr, wlv.char_attr);
}
-
- decor_conceal = decor_state.conceal;
- if (decor_conceal && decor_state.conceal_char) {
- decor_conceal = 2; // really??
- }
-
- can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell);
+ } else if (!attr_pri) {
+ wlv.char_attr = 0;
}
// Check spelling (unless at the end of the line).
@@ -1888,17 +2091,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// @Spell cluster is not used or the current syntax item
// contains the @Spell cluster.
v = (ptr - line);
- if (has_spell && v >= word_end && v > cur_checked_col) {
+ if (spv->spv_has_spell && v >= word_end && v > cur_checked_col) {
spell_attr = 0;
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, syntax_attr);
- }
- if (c != 0 && ((!has_syntax && !no_plain_buffer) || can_spell)) {
- char *prev_ptr;
+ // do not calculate cap_col at the end of the line or when
+ // only white space is following
+ if (mb_c != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) {
char *p;
- int len;
hlf_T spell_hlf = HLF_COUNT;
- prev_ptr = ptr - mb_l;
v -= mb_l - 1;
// Use nextline[] if possible, it has the start of the
@@ -1908,10 +2107,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
} else {
p = prev_ptr;
}
- cap_col -= (int)(prev_ptr - line);
- size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange);
+ spv->spv_cap_col -= (int)(prev_ptr - line);
+ size_t tmplen = spell_check(wp, p, &spell_hlf, &spv->spv_cap_col, spv->spv_unchanged);
assert(tmplen <= INT_MAX);
- len = (int)tmplen;
+ int len = (int)tmplen;
word_end = (int)v + len;
// In Insert mode only highlight a word that
@@ -1930,8 +2129,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& (p - nextline) + len > nextline_idx) {
// Remember that the good word continues at the
// start of the next line.
- checked_lnum = lnum + 1;
- checked_col = (int)((p - nextline) + len - nextline_idx);
+ spv->spv_checked_lnum = lnum + 1;
+ spv->spv_checked_col = (int)((p - nextline) + len - nextline_idx);
}
// Turn index into actual attributes.
@@ -1939,212 +2138,212 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
spell_attr = highlight_attr[spell_hlf];
}
- if (cap_col > 0) {
- if (p != prev_ptr
- && (p - nextline) + cap_col >= nextline_idx) {
+ if (spv->spv_cap_col > 0) {
+ if (p != prev_ptr && (p - nextline) + spv->spv_cap_col >= nextline_idx) {
// Remember that the word in the next line
// must start with a capital.
- capcol_lnum = lnum + 1;
- cap_col = (int)((p - nextline) + cap_col
- - nextline_idx);
+ spv->spv_capcol_lnum = lnum + 1;
+ spv->spv_cap_col = (int)((p - nextline) + spv->spv_cap_col - nextline_idx);
} else {
// Compute the actual column.
- cap_col += (int)(prev_ptr - line);
+ spv->spv_cap_col += (int)(prev_ptr - line);
}
}
}
}
if (spell_attr != 0) {
if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, spell_attr);
+ wlv.char_attr = hl_combine_attr(wlv.char_attr, spell_attr);
} else {
- char_attr = hl_combine_attr(spell_attr, char_attr);
+ wlv.char_attr = hl_combine_attr(spell_attr, wlv.char_attr);
}
}
if (wp->w_buffer->terminal) {
- char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
+ wlv.char_attr = hl_combine_attr(term_attrs[wlv.vcol], wlv.char_attr);
}
+ // we don't want linebreak to apply for lines that start with
+ // leading spaces, followed by long letters (since it would add
+ // a break at the beginning of a line and this might be unexpected)
+ //
+ // So only allow to linebreak, once we have found chars not in
+ // 'breakat' in the line.
+ if (wp->w_p_lbr && !wlv.need_lbr && mb_c != NUL
+ && !vim_isbreak((uint8_t)(*ptr))) {
+ wlv.need_lbr = true;
+ }
// Found last space before word: check for line break.
- if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
- && !vim_isbreak((int)(*ptr))) {
+ if (wp->w_p_lbr && c0 == mb_c && mb_c < 128 && wlv.need_lbr
+ && vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) {
int mb_off = utf_head_off(line, ptr - 1);
char *p = ptr - (mb_off + 1);
chartabsize_T cts;
- init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, p);
- n_extra = win_lbr_chartabsize(&cts, NULL) - 1;
+ init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, p);
+ // do not want virtual text to be counted here
+ cts.cts_has_virt_text = false;
+ wlv.n_extra = win_lbr_chartabsize(&cts, NULL) - 1;
+ clear_chartabsize_arg(&cts);
- // We have just drawn the showbreak value, no need to add
- // space for it again.
- if (vcol == vcol_sbr) {
- n_extra -= mb_charlen(get_showbreak_value(wp));
- if (n_extra < 0) {
- n_extra = 0;
- }
- }
- if (on_last_col && c != TAB) {
+ if (on_last_col && mb_c != TAB) {
// Do not continue search/match highlighting over the
// line break, but for TABs the highlighting should
// include the complete width of the character
search_attr = 0;
}
- if (c == TAB && n_extra + col > grid->cols) {
- n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts,
- wp->w_buffer->b_p_vts_array) - 1;
+ if (mb_c == TAB && wlv.n_extra + wlv.col > grid->cols) {
+ wlv.n_extra = tabstop_padding(wlv.vcol, wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array) - 1;
}
- c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
- c_final = NUL;
- if (ascii_iswhite(c)) {
- if (c == TAB) {
+ wlv.c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
+ wlv.c_final = NUL;
+ if (mb_c < 128 && ascii_iswhite(mb_c)) {
+ if (mb_c == TAB) {
// See "Tab alignment" below.
FIX_FOR_BOGUSCOLS;
}
if (!wp->w_p_list) {
- c = ' ';
+ mb_c = ' ';
+ mb_schar = schar_from_ascii(mb_c);
}
}
- clear_chartabsize_arg(&cts);
}
- in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' ');
- if (!in_multispace) {
- multispace_pos = 0;
+ if (wp->w_p_list) {
+ in_multispace = mb_c == ' ' && (*ptr == ' ' || (prev_ptr > line && prev_ptr[-1] == ' '));
+ if (!in_multispace) {
+ multispace_pos = 0;
+ }
}
// 'list': Change char 160 to 'nbsp' and space to 'space'.
// But not when the character is followed by a composing
// character (use mb_l to check that).
if (wp->w_p_list
- && ((((c == 160 && mb_l == 1)
- || (mb_utf8
- && ((mb_c == 160 && mb_l == 2)
- || (mb_c == 0x202f && mb_l == 3))))
+ && ((((mb_c == 160 && mb_l == 2) || (mb_c == 0x202f && mb_l == 3))
&& wp->w_p_lcs_chars.nbsp)
- || (c == ' '
+ || (mb_c == ' '
&& mb_l == 1
&& (wp->w_p_lcs_chars.space
|| (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
&& ptr - line >= leadcol
&& ptr - line <= trailcol))) {
if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
- c = wp->w_p_lcs_chars.multispace[multispace_pos++];
+ mb_c = wp->w_p_lcs_chars.multispace[multispace_pos++];
if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
} else {
- c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
+ mb_c = (mb_c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
}
- n_attr = 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ wlv.n_attr = 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = wlv.char_attr; // save current attr
+ mb_schar = schar_from_char(mb_c);
}
- if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol)
- || (leadcol != 0 && ptr < line + leadcol))) {
+ if (mb_c == ' ' && mb_l == 1 && ((trailcol != MAXCOL && ptr > line + trailcol)
+ || (leadcol != 0 && ptr < line + leadcol))) {
if (leadcol != 0 && in_multispace && ptr < line + leadcol
&& wp->w_p_lcs_chars.leadmultispace != NULL) {
- c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
+ mb_c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
} else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) {
- c = wp->w_p_lcs_chars.trail;
+ mb_c = wp->w_p_lcs_chars.trail;
} else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) {
- c = wp->w_p_lcs_chars.lead;
+ mb_c = wp->w_p_lcs_chars.lead;
} else if (leadcol != 0 && wp->w_p_lcs_chars.space) {
- c = wp->w_p_lcs_chars.space;
+ mb_c = wp->w_p_lcs_chars.space;
}
- n_attr = 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ wlv.n_attr = 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = wlv.char_attr; // save current attr
+ mb_schar = schar_from_char(mb_c);
}
}
// Handling of non-printable characters.
- if (!vim_isprintc(c)) {
+ if (!vim_isprintc(mb_c)) {
// when getting a character from the file, we may have to
// turn it into something else on the way to putting it on the screen.
- if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
+ if (mb_c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
int tab_len = 0;
- long vcol_adjusted = vcol; // removed showbreak length
+ colnr_T vcol_adjusted = wlv.vcol; // removed showbreak length
char *const sbr = get_showbreak_value(wp);
// Only adjust the tab_len, when at the first column after the
// showbreak value was drawn.
- if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) {
- vcol_adjusted = vcol - mb_charlen(sbr);
+ if (*sbr != NUL && wlv.vcol == wlv.vcol_sbr && wp->w_p_wrap) {
+ vcol_adjusted = wlv.vcol - mb_charlen(sbr);
}
// tab amount depends on current column
- tab_len = tabstop_padding((colnr_T)vcol_adjusted,
+ tab_len = tabstop_padding(vcol_adjusted,
wp->w_buffer->b_p_ts,
wp->w_buffer->b_p_vts_array) - 1;
if (!wp->w_p_lbr || !wp->w_p_list) {
- n_extra = tab_len;
+ wlv.n_extra = tab_len;
} else {
char *p;
- int i;
- int saved_nextra = n_extra;
+ int saved_nextra = wlv.n_extra;
- if (vcol_off > 0) {
+ if (wlv.vcol_off > 0) {
// there are characters to conceal
- tab_len += vcol_off;
+ tab_len += wlv.vcol_off;
}
// boguscols before FIX_FOR_BOGUSCOLS macro from above.
if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0
- && n_extra > tab_len) {
- tab_len += n_extra - tab_len;
+ && wlv.n_extra > tab_len) {
+ tab_len += wlv.n_extra - tab_len;
}
- // If n_extra > 0, it gives the number of chars
- // to use for a tab, else we need to calculate the width
- // for a tab.
- int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2));
- if (wp->w_p_lcs_chars.tab3) {
- len += utf_char2len(wp->w_p_lcs_chars.tab3);
- }
- if (n_extra > 0) {
- len += n_extra - tab_len;
- }
- c = wp->w_p_lcs_chars.tab1;
- p = xmalloc((size_t)len + 1);
- memset(p, ' ', (size_t)len);
- p[len] = NUL;
- xfree(p_extra_free);
- p_extra_free = p;
- for (i = 0; i < tab_len; i++) {
- if (*p == NUL) {
- tab_len = i;
- break;
+ if (tab_len > 0) {
+ // If wlv.n_extra > 0, it gives the number of chars
+ // to use for a tab, else we need to calculate the
+ // width for a tab.
+ int tab2_len = utf_char2len(wp->w_p_lcs_chars.tab2);
+ int len = tab_len * tab2_len;
+ if (wp->w_p_lcs_chars.tab3) {
+ len += utf_char2len(wp->w_p_lcs_chars.tab3) - tab2_len;
+ }
+ if (wlv.n_extra > 0) {
+ len += wlv.n_extra - tab_len;
}
- int lcs = wp->w_p_lcs_chars.tab2;
+ mb_c = wp->w_p_lcs_chars.tab1;
+ p = get_extra_buf((size_t)len + 1);
+ memset(p, ' ', (size_t)len);
+ p[len] = NUL;
+ wlv.p_extra = p;
+ for (int i = 0; i < tab_len; i++) {
+ if (*p == NUL) {
+ tab_len = i;
+ break;
+ }
+ int lcs = wp->w_p_lcs_chars.tab2;
- // if tab3 is given, use it for the last char
- if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
- lcs = wp->w_p_lcs_chars.tab3;
+ // if tab3 is given, use it for the last char
+ if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
+ lcs = wp->w_p_lcs_chars.tab3;
+ }
+ p += utf_char2bytes(lcs, p);
+ wlv.n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
}
- p += utf_char2bytes(lcs, p);
- n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
- }
- p_extra = p_extra_free;
- // n_extra will be increased by FIX_FOX_BOGUSCOLS
- // macro below, so need to adjust for that here
- if (vcol_off > 0) {
- n_extra -= vcol_off;
+ // n_extra will be increased by FIX_FOX_BOGUSCOLS
+ // macro below, so need to adjust for that here
+ if (wlv.vcol_off > 0) {
+ wlv.n_extra -= wlv.vcol_off;
+ }
}
}
{
- int vc_saved = vcol_off;
+ int vc_saved = wlv.vcol_off;
// Tab alignment should be identical regardless of
// 'conceallevel' value. So tab compensates of all
@@ -2157,108 +2356,100 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Make sure, the highlighting for the tab char will be
// correctly set further below (effectively reverts the
// FIX_FOR_BOGSUCOLS macro).
- if (n_extra == tab_len + vc_saved && wp->w_p_list
+ if (wlv.n_extra == tab_len + vc_saved && wp->w_p_list
&& wp->w_p_lcs_chars.tab1) {
tab_len += vc_saved;
}
}
- mb_utf8 = false; // don't draw as UTF-8
if (wp->w_p_list) {
- c = (n_extra == 0 && wp->w_p_lcs_chars.tab3)
- ? wp->w_p_lcs_chars.tab3
- : wp->w_p_lcs_chars.tab1;
- if (wp->w_p_lbr) {
- c_extra = NUL; // using p_extra from above
+ mb_c = (wlv.n_extra == 0 && wp->w_p_lcs_chars.tab3)
+ ? wp->w_p_lcs_chars.tab3 : wp->w_p_lcs_chars.tab1;
+ if (wp->w_p_lbr && wlv.p_extra != NULL && *wlv.p_extra != NUL) {
+ wlv.c_extra = NUL; // using p_extra from above
} else {
- c_extra = wp->w_p_lcs_chars.tab2;
+ wlv.c_extra = wp->w_p_lcs_chars.tab2;
}
- c_final = wp->w_p_lcs_chars.tab3;
- n_attr = tab_len + 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ wlv.c_final = wp->w_p_lcs_chars.tab3;
+ wlv.n_attr = tab_len + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = wlv.char_attr; // save current attr
} else {
- c_final = NUL;
- c_extra = ' ';
- c = ' ';
+ wlv.c_final = NUL;
+ wlv.c_extra = ' ';
+ mb_c = ' ';
}
- } else if (c == NUL
+ mb_schar = schar_from_char(mb_c);
+ } else if (mb_c == NUL
&& (wp->w_p_list
- || ((fromcol >= 0 || fromcol_prev >= 0)
- && tocol > vcol
+ || ((wlv.fromcol >= 0 || fromcol_prev >= 0)
+ && wlv.tocol > wlv.vcol
&& VIsual_mode != Ctrl_V
- && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))
+ && wlv.col < grid->cols
&& !(noinvcur
&& lnum == wp->w_cursor.lnum
- && (colnr_T)vcol == wp->w_virtcol)))
+ && wlv.vcol == wp->w_virtcol)))
&& lcs_eol_one > 0) {
// Display a '$' after the line or highlight an extra
// character if the line break is included.
// For a diff line the highlighting continues after the "$".
- if (diff_hlf == (hlf_T)0
- && line_attr == 0
- && line_attr_lowprio == 0) {
+ if (wlv.diff_hlf == (hlf_T)0
+ && wlv.line_attr == 0
+ && wlv.line_attr_lowprio == 0) {
// In virtualedit, visual selections may extend beyond end of line
- if (area_highlighting && virtual_active()
- && tocol != MAXCOL && vcol < tocol) {
- n_extra = 0;
- } else {
- p_extra = at_end_str;
- n_extra = 1;
- c_extra = NUL;
- c_final = NUL;
+ if (!(area_highlighting && virtual_active()
+ && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol)) {
+ wlv.p_extra = at_end_str;
}
+ wlv.n_extra = 0;
}
if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
- c = wp->w_p_lcs_chars.eol;
+ mb_c = wp->w_p_lcs_chars.eol;
} else {
- c = ' ';
+ mb_c = ' ';
}
lcs_eol_one = -1;
ptr--; // put it back at the NUL
- extra_attr = win_hl_attr(wp, HLF_AT);
- n_attr = 1;
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
- } else if (c != NUL) {
- p_extra = (char *)transchar_buf(wp->w_buffer, c);
- if (n_extra == 0) {
- n_extra = byte2cells(c) - 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_AT);
+ wlv.n_attr = 1;
+ mb_schar = schar_from_char(mb_c);
+ } else if (mb_c != NUL) {
+ wlv.p_extra = transchar_buf(wp->w_buffer, mb_c);
+ if (wlv.n_extra == 0) {
+ wlv.n_extra = byte2cells(mb_c) - 1;
}
if ((dy_flags & DY_UHEX) && wp->w_p_rl) {
- rl_mirror(p_extra); // reverse "<12>"
+ rl_mirror_ascii(wlv.p_extra, NULL); // reverse "<12>"
}
- c_extra = NUL;
- c_final = NUL;
+ wlv.c_extra = NUL;
+ wlv.c_final = NUL;
if (wp->w_p_lbr) {
char *p;
- c = (uint8_t)(*p_extra);
- p = xmalloc((size_t)n_extra + 1);
- memset(p, ' ', (size_t)n_extra);
+ mb_c = (uint8_t)(*wlv.p_extra);
+ p = get_extra_buf((size_t)wlv.n_extra + 1);
+ memset(p, ' ', (size_t)wlv.n_extra);
strncpy(p, // NOLINT(runtime/printf)
- p_extra + 1,
- (size_t)strlen(p_extra) - 1);
- p[n_extra] = NUL;
- xfree(p_extra_free);
- p_extra_free = p_extra = p;
+ wlv.p_extra + 1,
+ (size_t)strlen(wlv.p_extra) - 1);
+ p[wlv.n_extra] = NUL;
+ wlv.p_extra = p;
} else {
- n_extra = byte2cells(c) - 1;
- c = (uint8_t)(*p_extra++);
+ wlv.n_extra = byte2cells(mb_c) - 1;
+ mb_c = (uint8_t)(*wlv.p_extra++);
}
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_8);
- saved_attr2 = char_attr; // save current attr
- mb_utf8 = false; // don't draw as UTF-8
+ wlv.n_attr = wlv.n_extra + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_8);
+ saved_attr2 = wlv.char_attr; // save current attr
+ mb_schar = schar_from_ascii(mb_c);
} else if (VIsual_active
&& (VIsual_mode == Ctrl_V || VIsual_mode == 'v')
&& virtual_active()
- && tocol != MAXCOL
- && vcol < tocol
- && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) {
- c = ' ';
+ && wlv.tocol != MAXCOL
+ && wlv.vcol < wlv.tocol
+ && wlv.col < grid->cols) {
+ mb_c = ' ';
+ mb_schar = schar_from_char(mb_c);
ptr--; // put it back at the NUL
}
}
@@ -2267,7 +2458,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp))
&& ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0)
&& !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) {
- char_attr = conceal_attr;
+ wlv.char_attr = conceal_attr;
+ bool is_conceal_char = false;
if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0)
|| has_match_conc > 1 || decor_conceal > 1)
&& (syn_get_sub_char() != NUL
@@ -2278,49 +2470,47 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// First time at this concealed item: display one
// character.
if (has_match_conc && match_conc) {
- c = match_conc;
+ mb_c = match_conc;
} else if (decor_conceal && decor_state.conceal_char) {
- c = decor_state.conceal_char;
+ mb_schar = decor_state.conceal_char;
+ mb_c = schar_get_first_codepoint(mb_schar);
+ is_conceal_char = true;
if (decor_state.conceal_attr) {
- char_attr = decor_state.conceal_attr;
+ wlv.char_attr = decor_state.conceal_attr;
}
} else if (syn_get_sub_char() != NUL) {
- c = syn_get_sub_char();
+ mb_c = syn_get_sub_char();
} else if (wp->w_p_lcs_chars.conceal != NUL) {
- c = wp->w_p_lcs_chars.conceal;
+ mb_c = wp->w_p_lcs_chars.conceal;
} else {
- c = ' ';
+ mb_c = ' ';
}
prev_syntax_id = syntax_seqnr;
- if (n_extra > 0) {
- vcol_off += n_extra;
+ if (wlv.n_extra > 0) {
+ wlv.vcol_off += wlv.n_extra;
}
- vcol += n_extra;
- if (wp->w_p_wrap && n_extra > 0) {
- if (wp->w_p_rl) {
- col -= n_extra;
- boguscols -= n_extra;
- } else {
- boguscols += n_extra;
- col += n_extra;
- }
+ wlv.vcol += wlv.n_extra;
+ if (wp->w_p_wrap && wlv.n_extra > 0) {
+ wlv.boguscols += wlv.n_extra;
+ wlv.col += wlv.n_extra;
}
- n_extra = 0;
- n_attr = 0;
- } else if (n_skip == 0) {
+ wlv.n_extra = 0;
+ wlv.n_attr = 0;
+ } else if (wlv.skip_cells == 0) {
is_concealing = true;
- n_skip = 1;
+ wlv.skip_cells = 1;
+ }
+ if (!is_conceal_char) {
+ mb_schar = schar_from_char(mb_c);
}
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
} else {
prev_syntax_id = 0;
is_concealing = false;
}
- if (n_skip > 0 && did_decrement_ptr) {
+ if (wlv.skip_cells > 0 && did_decrement_ptr) {
// not showing the '>', put pointer back to avoid getting stuck
ptr++;
}
@@ -2328,23 +2518,25 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// In the cursor line and we may be concealing characters: correct
// the cursor column when we reach its position.
- if (!did_wcol && draw_state == WL_LINE
+ if (!did_wcol && wlv.draw_state == WL_LINE
&& wp == curwin && lnum == wp->w_cursor.lnum
&& conceal_cursor_line(wp)
- && (int)wp->w_virtcol <= vcol + n_skip) {
- if (wp->w_p_rl) {
- wp->w_wcol = grid->cols - col + boguscols - 1;
- } else {
- wp->w_wcol = col - boguscols;
- }
- wp->w_wrow = row;
+ && (int)wp->w_virtcol <= wlv.vcol + wlv.skip_cells) {
+ wp->w_wcol = wlv.col - wlv.boguscols;
+ wp->w_wrow = wlv.row;
did_wcol = true;
wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
// Don't override visual selection highlighting.
- if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) {
- char_attr = hl_combine_attr(char_attr, extra_attr);
+ if (wlv.n_attr > 0 && wlv.draw_state == WL_LINE && !search_attr_from_match) {
+ wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr);
+ if (wlv.reset_extra_attr) {
+ wlv.reset_extra_attr = false;
+ wlv.extra_attr = 0;
+ // search_attr_from_match can be restored now that the extra_attr has been applied
+ search_attr_from_match = saved_search_attr_from_match;
+ }
}
// Handle the case where we are in column 0 but not on the first
@@ -2352,41 +2544,40 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// special character (via 'listchars' option "precedes:<char>".
if (lcs_prec_todo != NUL
&& wp->w_p_list
- && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0)
- && filler_todo <= 0
- && draw_state > WL_STC
- && c != NUL) {
- c = wp->w_p_lcs_chars.prec;
+ && (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0) : wp->w_leftcol > 0)
+ && wlv.filler_todo <= 0
+ && wlv.draw_state > WL_STC
+ && mb_c != NUL) {
+ mb_c = wp->w_p_lcs_chars.prec;
lcs_prec_todo = NUL;
if (utf_char2cells(mb_c) > 1) {
// Double-width character being overwritten by the "precedes"
// character, need to fill up half the character.
- c_extra = MB_FILLER_CHAR;
- c_final = NUL;
- n_extra = 1;
- n_attr = 2;
- extra_attr = win_hl_attr(wp, HLF_AT);
- }
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
- saved_attr3 = char_attr; // save current attr
- char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr
+ wlv.c_extra = MB_FILLER_CHAR;
+ wlv.c_final = NUL;
+ wlv.n_extra = 1;
+ wlv.n_attr = 2;
+ wlv.extra_attr = win_hl_attr(wp, HLF_AT);
+ }
+ mb_schar = schar_from_char(mb_c);
+ saved_attr3 = wlv.char_attr; // save current attr
+ wlv.char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr
n_attr3 = 1;
}
// At end of the text line or just after the last character.
- if (c == NUL && eol_hl_off == 0) {
+ if (mb_c == NUL && eol_hl_off == 0) {
// flag to indicate whether prevcol equals startcol of search_hl or
// one of the matches
bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl,
- (long)(ptr - line) - 1); // NOLINT(google-readability-casting)
+ (colnr_T)(ptr - line) - 1);
// Invert at least one char, used for Visual and empty line or
// highlight match at end of line. If it's beyond the last
// char on the screen, just overwrite that one (tricky!) Not
// needed when a '$' was displayed for 'list'.
if (wp->w_p_lcs_chars.eol == lcs_eol_one
- && ((area_attr != 0 && vcol == fromcol
+ && ((area_attr != 0 && wlv.vcol == wlv.fromcol
&& (VIsual_mode != Ctrl_V
|| lnum == VIsual.lnum
|| lnum == curwin->w_cursor.lnum))
@@ -2394,66 +2585,60 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
|| prevcol_hl_flag)) {
int n = 0;
- if (wp->w_p_rl) {
- if (col < 0) {
- n = 1;
- }
- } else {
- if (col >= grid->cols) {
- n = -1;
- }
+ if (wlv.col >= grid->cols) {
+ n = -1;
}
if (n != 0) {
// At the window boundary, highlight the last character
// instead (better than nothing).
- off += n;
- col += n;
+ wlv.off += n;
+ wlv.col += n;
} else {
// Add a blank character to highlight.
- schar_from_ascii(linebuf_char[off], ' ');
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
}
if (area_attr == 0 && !has_fold) {
// Use attributes from match with highest priority among
// 'search_hl' and the match list.
get_search_match_hl(wp,
&screen_search_hl,
- (long)(ptr - line), // NOLINT(google-readability-casting)
- &char_attr);
+ (colnr_T)(ptr - line),
+ &wlv.char_attr);
}
- int eol_attr = char_attr;
- if (cul_attr) {
- eol_attr = hl_combine_attr(cul_attr, eol_attr);
+ int eol_attr = wlv.char_attr;
+ if (wlv.cul_attr) {
+ eol_attr = hl_combine_attr(wlv.cul_attr, eol_attr);
}
- linebuf_attr[off] = eol_attr;
- if (wp->w_p_rl) {
- col--;
- off--;
- } else {
- col++;
- off++;
- }
- vcol++;
+ linebuf_attr[wlv.off] = eol_attr;
+ linebuf_vcol[wlv.off] = MAXCOL;
+ wlv.col++;
+ wlv.off++;
+ wlv.vcol++;
eol_hl_off = 1;
}
+ }
+
+ // At end of the text line.
+ if (mb_c == NUL) {
// Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
if (wp->w_p_wrap) {
- v = wp->w_skipcol;
+ v = wlv.startrow == 0 ? wp->w_skipcol : 0;
} else {
v = wp->w_leftcol;
}
// check if line ends before left margin
- if (vcol < v + col - win_col_off(wp)) {
- vcol = v + col - win_col_off(wp);
+ if (wlv.vcol < v + wlv.col - win_col_off(wp)) {
+ wlv.vcol = (colnr_T)v + wlv.col - win_col_off(wp);
}
// Get rid of the boguscols now, we want to draw until the right
// edge for 'cursorcolumn'.
- col -= boguscols;
- // boguscols = 0; // Disabled because value never read after this
+ wlv.col -= wlv.boguscols;
+ wlv.boguscols = 0;
if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
bool has_virttext = false;
@@ -2463,19 +2648,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
? 1 : 0);
if (has_decor) {
- has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
- col + eol_skip);
+ has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip);
}
if (((wp->w_p_cuc
- && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
- && (int)wp->w_virtcol <
- (long)grid->cols * (row - startrow + 1) + v
+ && wp->w_virtcol >= VCOL_HLC - eol_hl_off
+ && wp->w_virtcol < grid->cols * (ptrdiff_t)(wlv.row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
- || draw_color_col || line_attr_lowprio || line_attr
- || diff_hlf != (hlf_T)0 || has_virttext)) {
+ || draw_color_col || wlv.line_attr_lowprio || wlv.line_attr
+ || wlv.diff_hlf != 0 || has_virttext)) {
int rightmost_vcol = 0;
- int i;
if (wp->w_p_cuc) {
rightmost_vcol = wp->w_virtcol;
@@ -2483,7 +2665,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (draw_color_col) {
// determine rightmost colorcolumn to possibly draw
- for (i = 0; color_cols[i].col >= 0; i++) {
+ for (int i = 0; color_cols[i].col >= 0; i++) {
if (rightmost_vcol < color_cols[i].col) {
rightmost_vcol = color_cols[i].col;
}
@@ -2493,74 +2675,74 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int cuc_attr = win_hl_attr(wp, HLF_CUC);
int diff_attr = 0;
- if (diff_hlf == HLF_TXD) {
- diff_hlf = HLF_CHD;
+ if (wlv.diff_hlf == HLF_TXD) {
+ wlv.diff_hlf = HLF_CHD;
}
- if (diff_hlf != 0) {
- diff_attr = win_hl_attr(wp, (int)diff_hlf);
+ if (wlv.diff_hlf != 0) {
+ diff_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
}
- int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
- if (base_attr || line_attr || has_virttext) {
+ int base_attr = hl_combine_attr(wlv.line_attr_lowprio, diff_attr);
+ if (base_attr || wlv.line_attr || has_virttext) {
rightmost_vcol = INT_MAX;
}
- int col_stride = wp->w_p_rl ? -1 : 1;
-
- while (wp->w_p_rl ? col >= 0 : col < grid->cols) {
- schar_from_ascii(linebuf_char[off], ' ');
- col += col_stride;
+ while (wlv.col < grid->cols) {
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
+ linebuf_vcol[wlv.off] = MAXCOL;
+ wlv.col++;
if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
int col_attr = base_attr;
- if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) {
+ if (wp->w_p_cuc && VCOL_HLC == wp->w_virtcol) {
col_attr = cuc_attr;
} else if (draw_color_col && VCOL_HLC == color_cols->col) {
col_attr = color_cols->syn_attr;
- c = color_cols->ch;
- schar_from_char(linebuf_char[off], c);
+ linebuf_char[wlv.off] = schar_from_char(color_cols->ch);
}
- col_attr = hl_combine_attr(col_attr, line_attr);
+ col_attr = hl_combine_attr(col_attr, wlv.line_attr);
- linebuf_attr[off] = col_attr;
- off += col_stride;
+ linebuf_attr[wlv.off] = col_attr;
+ wlv.off++;
if (VCOL_HLC >= rightmost_vcol) {
break;
}
- vcol += 1;
+ wlv.vcol += 1;
}
}
// TODO(bfredl): integrate with the common beyond-the-end-loop
if (wp->w_buffer->terminal) {
- // terminal buffers may need to highlight beyond the end of the
- // logical line
- int n = wp->w_p_rl ? -1 : 1;
- while (col >= 0 && col < grid->cols) {
- schar_from_ascii(linebuf_char[off], ' ');
- linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol];
- off += n;
- vcol += n;
- col += n;
+ // terminal buffers may need to highlight beyond the end of the logical line
+ while (wlv.col >= 0 && wlv.col < grid->cols) {
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
+ linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol];
+ linebuf_vcol[wlv.off] = wlv.vcol;
+ wlv.off++;
+ wlv.vcol++;
+ wlv.col++;
}
}
- draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
- grid_put_linebuf(grid, row, 0, col, grid->cols, wp->w_p_rl, wp, bg_attr, false);
- row++;
+ if (kv_size(fold_vt) > 0) {
+ draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0);
+ }
+ draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
+ win_put_linebuf(wp, wlv.row, 0, wlv.col, grid->cols, bg_attr, false);
+ wlv.row++;
// Update w_cline_height and w_cline_folded if the cursor line was
// updated (saves a call to plines_win() later).
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
curwin->w_cline_row = startrow;
- curwin->w_cline_height = row - startrow;
- curwin->w_cline_folded = foldinfo.fi_lines > 0;
+ curwin->w_cline_height = wlv.row - startrow;
+ curwin->w_cline_folded = has_fold;
curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
conceal_cursor_used = conceal_cursor_line(curwin);
}
@@ -2570,24 +2752,29 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Show "extends" character from 'listchars' if beyond the line end and
// 'list' is set.
if (wp->w_p_lcs_chars.ext != NUL
- && draw_state == WL_LINE
+ && wlv.draw_state == WL_LINE
&& wp->w_p_list
&& !wp->w_p_wrap
- && filler_todo <= 0
- && (wp->w_p_rl ? col == 0 : col == grid->cols - 1)
- && !has_fold
- && (*ptr != NUL
- || lcs_eol_one > 0
- || (n_extra && (c_extra != NUL || *p_extra != NUL)))) {
- c = wp->w_p_lcs_chars.ext;
- char_attr = win_hl_attr(wp, HLF_AT);
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ && wlv.filler_todo <= 0
+ && wlv.col == grid->cols - 1
+ && !has_fold) {
+ if (has_decor && *ptr == NUL && lcs_eol_one == 0) {
+ // Tricky: there might be a virtual text just _after_ the last char
+ decor_redraw_col(wp, (colnr_T)v, wlv.off, false, &decor_state);
+ }
+ if (*ptr != NUL
+ || lcs_eol_one > 0
+ || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL))
+ || has_more_inline_virt(&wlv, v)) {
+ mb_c = wp->w_p_lcs_chars.ext;
+ wlv.char_attr = win_hl_attr(wp, HLF_AT);
+ mb_schar = schar_from_char(mb_c);
+ }
}
// advance to the next 'colorcolumn'
if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
// Highlight the cursor column if 'cursorcolumn' is set. But don't
@@ -2597,89 +2784,89 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak'
// options are set
vcol_save_attr = -1;
- if ((draw_state == WL_LINE
- || draw_state == WL_BRI
- || draw_state == WL_SBR)
+ if ((wlv.draw_state == WL_LINE
+ || wlv.draw_state == WL_BRI
+ || wlv.draw_state == WL_SBR)
&& !lnum_in_visual_area
&& search_attr == 0
&& area_attr == 0
- && filler_todo <= 0) {
- if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol
+ && wlv.filler_todo <= 0) {
+ if (wp->w_p_cuc && VCOL_HLC == wp->w_virtcol
&& lnum != wp->w_cursor.lnum) {
- vcol_save_attr = char_attr;
- char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr);
+ vcol_save_attr = wlv.char_attr;
+ wlv.char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), wlv.char_attr);
} else if (draw_color_col && VCOL_HLC == color_cols->col) {
- vcol_save_attr = char_attr;
+ vcol_save_attr = wlv.char_attr;
if (color_cols->flags & kColorcolForeground) {
- char_attr = hl_combine_attr(char_attr, color_cols->syn_attr);
+ wlv.char_attr = hl_combine_attr(wlv.char_attr, color_cols->syn_attr);
} else if (!(color_cols->flags & kColorcolBehind)) {
- char_attr = hl_combine_attr(color_cols->syn_attr, char_attr);
+ wlv.char_attr = hl_combine_attr(color_cols->syn_attr, wlv.char_attr);
}
}
}
// Apply lowest-priority line attr now, so everything can override it.
- if (draw_state == WL_LINE) {
- char_attr = hl_combine_attr(line_attr_lowprio, char_attr);
+ if (wlv.draw_state == WL_LINE) {
+ wlv.char_attr = hl_combine_attr(wlv.line_attr_lowprio, wlv.char_attr);
+ }
+
+ if (wlv.draw_state == WL_LINE) {
+ vcol_prev = wlv.vcol;
}
// Store character to be displayed.
// Skip characters that are left of the screen for 'nowrap'.
- vcol_prev = vcol;
- if (draw_state < WL_LINE || n_skip <= 0) {
- //
+ if (wlv.draw_state < WL_LINE || wlv.skip_cells <= 0) {
// Store the character.
- //
- if (wp->w_p_rl && utf_char2cells(mb_c) > 1) {
- // A double-wide character is: put first half in left cell.
- off--;
- col--;
- }
- if (mb_utf8) {
- schar_from_cc(linebuf_char[off], mb_c, u8cc);
- } else {
- schar_from_ascii(linebuf_char[off], (char)c);
- }
+ linebuf_char[wlv.off] = mb_schar;
if (multi_attr) {
- linebuf_attr[off] = multi_attr;
+ linebuf_attr[wlv.off] = multi_attr;
multi_attr = 0;
} else {
- linebuf_attr[off] = char_attr;
+ linebuf_attr[wlv.off] = wlv.char_attr;
+ }
+
+ if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) {
+ linebuf_vcol[wlv.off] = wlv.vcol;
+ } else if (wlv.draw_state == WL_FOLD) {
+ if (wlv.n_closing > 0) {
+ linebuf_vcol[wlv.off] = -3;
+ wlv.n_closing--;
+ } else {
+ linebuf_vcol[wlv.off] = -2;
+ }
+ } else {
+ linebuf_vcol[wlv.off] = -1;
}
if (utf_char2cells(mb_c) > 1) {
// Need to fill two screen columns.
- off++;
- col++;
+ wlv.off++;
+ wlv.col++;
// UTF-8: Put a 0 in the second screen char.
- linebuf_char[off][0] = 0;
- if (draw_state > WL_STC && filler_todo <= 0) {
- vcol++;
- }
- // When "tocol" is halfway through a character, set it to the end of
- // the character, otherwise highlighting won't stop.
- if (tocol == vcol) {
- tocol++;
+ linebuf_char[wlv.off] = 0;
+ linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1];
+
+ if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) {
+ linebuf_vcol[wlv.off] = ++wlv.vcol;
+ } else {
+ linebuf_vcol[wlv.off] = -1;
}
- if (wp->w_p_rl) {
- // now it's time to backup one cell
- off--;
- col--;
+
+ // When "wlv.tocol" is halfway through a character, set it to the end
+ // of the character, otherwise highlighting won't stop.
+ if (wlv.tocol == wlv.vcol) {
+ wlv.tocol++;
}
}
- if (wp->w_p_rl) {
- off--;
- col--;
- } else {
- off++;
- col++;
- }
+ wlv.off++;
+ wlv.col++;
} else if (wp->w_p_cole > 0 && is_concealing) {
- n_skip--;
- vcol_off++;
- if (n_extra > 0) {
- vcol_off += n_extra;
+ wlv.skip_cells--;
+ wlv.vcol_off++;
+ if (wlv.n_extra > 0) {
+ wlv.vcol_off += wlv.n_extra;
}
if (wp->w_p_wrap) {
// Special voodoo required if 'wrap' is on.
@@ -2689,182 +2876,205 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// take up the same screen space when parts are concealed,
// so that cursor line computations aren't messed up.
//
- // To avoid the fictitious advance of 'col' causing
+ // To avoid the fictitious advance of 'wlv.col' causing
// trailing junk to be written out of the screen line
// we are building, 'boguscols' keeps track of the number
// of bad columns we have advanced.
- if (n_extra > 0) {
- vcol += n_extra;
- if (wp->w_p_rl) {
- col -= n_extra;
- boguscols -= n_extra;
- } else {
- col += n_extra;
- boguscols += n_extra;
- }
- n_extra = 0;
- n_attr = 0;
+ if (wlv.n_extra > 0) {
+ wlv.vcol += wlv.n_extra;
+ wlv.col += wlv.n_extra;
+ wlv.boguscols += wlv.n_extra;
+ wlv.n_extra = 0;
+ wlv.n_attr = 0;
}
if (utf_char2cells(mb_c) > 1) {
// Need to fill two screen columns.
- if (wp->w_p_rl) {
- boguscols--;
- col--;
- } else {
- boguscols++;
- col++;
- }
+ wlv.boguscols++;
+ wlv.col++;
}
- if (wp->w_p_rl) {
- boguscols--;
- col--;
- } else {
- boguscols++;
- col++;
- }
+ wlv.boguscols++;
+ wlv.col++;
} else {
- if (n_extra > 0) {
- vcol += n_extra;
- n_extra = 0;
- n_attr = 0;
+ if (wlv.n_extra > 0) {
+ wlv.vcol += wlv.n_extra;
+ wlv.n_extra = 0;
+ wlv.n_attr = 0;
}
}
} else {
- n_skip--;
+ wlv.skip_cells--;
}
- // Only advance the "vcol" when after the 'number' or 'relativenumber'
- // column.
- if (draw_state > WL_STC
- && filler_todo <= 0) {
- vcol++;
+ // The skipped cells need to be accounted for in vcol.
+ if (wlv.draw_state > WL_STC && wlv.skipped_cells > 0) {
+ wlv.vcol += wlv.skipped_cells;
+ wlv.skipped_cells = 0;
+ }
+
+ // Only advance the "wlv.vcol" when after the 'number' or
+ // 'relativenumber' column.
+ if (wlv.draw_state > WL_STC
+ && wlv.filler_todo <= 0) {
+ wlv.vcol++;
}
if (vcol_save_attr >= 0) {
- char_attr = vcol_save_attr;
+ wlv.char_attr = vcol_save_attr;
}
// restore attributes after "predeces" in 'listchars'
- if (draw_state > WL_STC && n_attr3 > 0 && --n_attr3 == 0) {
- char_attr = saved_attr3;
+ if (wlv.draw_state > WL_STC && n_attr3 > 0 && --n_attr3 == 0) {
+ wlv.char_attr = saved_attr3;
}
// restore attributes after last 'listchars' or 'number' char
- if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) {
- char_attr = saved_attr2;
+ if (wlv.n_attr > 0 && wlv.draw_state == WL_LINE && --wlv.n_attr == 0) {
+ wlv.char_attr = saved_attr2;
+ }
+
+ if (has_decor && wlv.filler_todo <= 0 && wlv.col >= grid->cols) {
+ // At the end of screen line: might need to peek for decorations just after
+ // this position.
+ if (!has_fold && wp->w_p_wrap && wlv.n_extra == 0) {
+ decor_redraw_col(wp, (int)(ptr - line), -3, false, &decor_state);
+ // Check position/hiding of virtual text again on next screen line.
+ decor_need_recheck = true;
+ } else if (has_fold || !wp->w_p_wrap) {
+ // Without wrapping, we might need to display right_align and win_col
+ // virt_text for the entire text line.
+ decor_redraw_col(wp, MAXCOL, -1, true, &decor_state);
+ }
}
// At end of screen line and there is more to come: Display the line
// so far. If there is no more to display it is caught above.
- if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols))
- && (!has_fold || virt_line_offset >= 0)
- && (draw_state != WL_LINE
+ if (wlv.col >= grid->cols && (!has_fold || virt_line_offset >= 0)
+ && (wlv.draw_state != WL_LINE
|| *ptr != NUL
- || filler_todo > 0
+ || wlv.filler_todo > 0
|| (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
- && p_extra != at_end_str)
- || (n_extra != 0
- && (c_extra != NUL || *p_extra != NUL)))) {
+ && wlv.p_extra != at_end_str)
+ || (wlv.n_extra != 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL))
+ || has_more_inline_virt(&wlv, v))) {
bool wrap = wp->w_p_wrap // Wrapping enabled.
- && filler_todo <= 0 // Not drawing diff filler lines.
+ && wlv.filler_todo <= 0 // Not drawing diff filler lines.
&& lcs_eol_one != -1 // Haven't printed the lcs_eol character.
- && row != endrow - 1 // Not the last line being displayed.
+ && wlv.row != endrow - 1 // Not the last line being displayed.
&& (grid->cols == Columns // Window spans the width of the screen,
|| ui_has(kUIMultigrid)) // or has dedicated grid.
&& !wp->w_p_rl; // Not right-to-left.
- int draw_col = col - boguscols;
+ int draw_col = wlv.col - wlv.boguscols;
if (virt_line_offset >= 0) {
draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line,
kHlModeReplace, grid->cols, 0);
- } else {
- draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row);
+ } else if (wlv.filler_todo <= 0) {
+ draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row);
}
- grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap);
+ win_put_linebuf(wp, wlv.row, 0, draw_col, grid->cols, bg_attr, wrap);
if (wrap) {
ScreenGrid *current_grid = grid;
- int current_row = row, dummy_col = 0; // dummy_col unused
+ int current_row = wlv.row;
+ int dummy_col = 0; // unused
grid_adjust(&current_grid, &current_row, &dummy_col);
// Force a redraw of the first column of the next line.
current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1;
-
- // Remember that the line wraps, used for modeless copy.
- current_grid->line_wraps[current_row] = true;
}
- boguscols = 0;
- row++;
+ wlv.boguscols = 0;
+ wlv.vcol_off = 0;
+ wlv.row++;
// When not wrapping and finished diff lines, or when displayed
// '$' and highlighting until last column, break here.
- if ((!wp->w_p_wrap && filler_todo <= 0) || lcs_eol_one == -1) {
+ if ((!wp->w_p_wrap && wlv.filler_todo <= 0) || lcs_eol_one == -1) {
break;
}
// When the window is too narrow draw all "@" lines.
- if (draw_state != WL_LINE && filler_todo <= 0) {
- win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT);
- set_empty_rows(wp, row);
- row = endrow;
+ if (wlv.draw_state != WL_LINE && wlv.filler_todo <= 0) {
+ win_draw_end(wp, '@', ' ', true, wlv.row, wp->w_grid.rows, HLF_AT);
+ set_empty_rows(wp, wlv.row);
+ wlv.row = endrow;
}
// When line got too long for screen break here.
- if (row == endrow) {
- row++;
+ if (wlv.row == endrow) {
+ wlv.row++;
break;
}
- col = 0;
- off = 0;
- if (wp->w_p_rl) {
- col = grid->cols - 1; // col is not used if breaking!
- off += col;
- }
+ win_line_start(wp, &wlv, true);
- // reset the drawing state for the start of a wrapped line
- draw_state = WL_START;
- saved_n_extra = n_extra;
- saved_p_extra = p_extra;
- saved_c_extra = c_extra;
- saved_c_final = c_final;
- saved_char_attr = char_attr;
- n_extra = 0;
lcs_prec_todo = wp->w_p_lcs_chars.prec;
- if (filler_todo <= 0) {
- need_showbreak = true;
+ if (wlv.filler_todo <= 0) {
+ wlv.need_showbreak = true;
}
if (statuscol.draw) {
- if (row == startrow + filler_lines + 1 || row == startrow + filler_lines) {
- // Re-evaluate 'statuscolumn' for the first wrapped row and non filler line
- statuscol.textp = NULL;
- } else if (statuscol.textp) {
- // Draw the already built 'statuscolumn' on the next wrapped or filler line
- statuscol.textp = statuscol.text;
- statuscol.hlrecp = statuscol.hlrec;
- } // Fall back to default columns if the 'n' flag isn't in 'cpo'
- statuscol.draw = vim_strchr(p_cpo, CPO_NUMCOL) == NULL;
- }
- filler_todo--;
+ if (vim_strchr(p_cpo, CPO_NUMCOL) && wlv.row > startrow + wlv.filler_lines) {
+ statuscol.draw = false; // don't draw status column if "n" is in 'cpo'
+ } else {
+ statuscol.textp = NULL; // re-evaluate with new v:virtnum
+ }
+ }
+ wlv.filler_todo--;
virt_line_offset = -1;
// When the filler lines are actually below the last line of the
// file, don't draw the line itself, break here.
- if (filler_todo == 0 && (wp->w_botfill || end_fill)) {
+ if (wlv.filler_todo == 0 && (wp->w_botfill || end_fill)) {
break;
}
}
} // for every character in the line
- // After an empty line check first word for capital.
- if (*skipwhite(line) == NUL) {
- capcol_lnum = lnum + 1;
- cap_col = 0;
+ clear_virttext(&fold_vt);
+ kv_destroy(virt_lines);
+ xfree(foldtext_free);
+ return wlv.row;
+}
+
+static void win_put_linebuf(win_T *wp, int row, int coloff, int endcol, int clear_width,
+ int bg_attr, bool wrap)
+{
+ ScreenGrid *grid = &wp->w_grid;
+
+ int start_col = 0;
+
+ if (wp->w_p_rl) {
+ linebuf_mirror(&start_col, &clear_width, grid->cols);
+ endcol = grid->cols - 1 - endcol;
}
- kv_destroy(virt_lines);
- xfree(p_extra_free);
- return row;
+ // Take care of putting "<<<" on the first line for 'smoothscroll'.
+ if (row == 0 && wp->w_skipcol > 0
+ // do not overwrite the 'showbreak' text with "<<<"
+ && *get_showbreak_value(wp) == NUL
+ // do not overwrite the 'listchars' "precedes" text with "<<<"
+ && !(wp->w_p_list && wp->w_p_lcs_chars.prec != 0)) {
+ int off = 0;
+ if (wp->w_p_nu && wp->w_p_rnu) {
+ // do not overwrite the line number, change "123 text" to "123<<<xt".
+ while (off < grid->cols && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) {
+ off++;
+ }
+ }
+
+ for (int i = 0; i < 3 && off < grid->cols; i++) {
+ if (off + 1 < grid->cols && linebuf_char[off + 1] == NUL) {
+ // When the first half of a double-width character is
+ // overwritten, change the second half to a space.
+ linebuf_char[off + 1] = schar_from_ascii(' ');
+ }
+ linebuf_char[off] = schar_from_ascii('<');
+ linebuf_attr[off] = HL_ATTR(HLF_AT);
+ off++;
+ }
+ }
+
+ grid_adjust(&grid, &row, &coloff);
+ grid_put_linebuf(grid, row, coloff, start_col, endcol, clear_width, wp->w_p_rl, bg_attr, wrap);
}
diff --git a/src/nvim/drawline.h b/src/nvim/drawline.h
index 9f60b46e1b..5a7f220a13 100644
--- a/src/nvim/drawline.h
+++ b/src/nvim/drawline.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_DRAWLINE_H
-#define NVIM_DRAWLINE_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/decoration_provider.h"
-#include "nvim/fold.h"
-#include "nvim/macros.h"
-#include "nvim/screen.h"
-#include "nvim/types.h"
+#include "nvim/fold_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
// Maximum columns for terminal highlight attributes
#define TERM_ATTRS_MAX 1024
@@ -20,11 +19,21 @@ typedef struct {
int win_row;
int win_col;
} WinExtmark;
-EXTERN kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE);
+EXTERN kvec_t(WinExtmark) win_extmark_arr INIT( = KV_INITIAL_VALUE);
-EXTERN bool conceal_cursor_used INIT(= false);
+EXTERN bool conceal_cursor_used INIT( = false);
+
+// Spell checking variables passed from win_update() to win_line().
+typedef struct {
+ bool spv_has_spell; ///< drawn window has spell checking
+ bool spv_unchanged; ///< not updating for changed text
+ int spv_checked_col; ///< column in "checked_lnum" up to
+ ///< which there are no spell errors
+ linenr_T spv_checked_lnum; ///< line number for "checked_col"
+ int spv_cap_col; ///< column to check for Cap word
+ linenr_T spv_capcol_lnum; ///< line number for "cap_col"
+} spellvars_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "drawline.h.generated.h"
#endif
-#endif // NVIM_DRAWLINE_H
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index f5f72b711f..9995ab8345 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// drawscreen.c: Code for updating all the windows on the screen.
// This is the top level, drawline.c is the middle and grid.c/screen.c the lower level.
@@ -56,24 +53,32 @@
#include <assert.h>
#include <inttypes.h>
+#include <limits.h>
#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
+#include "nvim/digraph.h"
#include "nvim/drawline.h"
#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/ex_getln.h"
-#include "nvim/extmark_defs.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
+#include "nvim/getchar.h"
+#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@@ -86,20 +91,25 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
+#include "nvim/search.h"
+#include "nvim/sign_defs.h"
+#include "nvim/spell.h"
+#include "nvim/state.h"
#include "nvim/statusline.h"
+#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// corner value flags for hsep_connected and vsep_connected
@@ -118,8 +128,6 @@ static bool redraw_popupmenu = false;
static bool msg_grid_invalid = false;
static bool resizing_autocmd = false;
-static char *provider_err = NULL;
-
/// Check if the cursor line needs to be redrawn because of 'concealcursor'.
///
/// When cursor is moved at the same time, both lines will be redrawn regardless.
@@ -202,7 +210,7 @@ bool default_grid_alloc(void)
void screenclear(void)
{
- check_for_delay(false);
+ msg_check_for_delay(false);
if (starting == NO_SCREEN || default_grid.chars == NULL) {
return;
@@ -212,7 +220,6 @@ void screenclear(void)
for (int i = 0; i < default_grid.rows; i++) {
grid_clear_line(&default_grid, default_grid.line_offset[i],
default_grid.cols, true);
- default_grid.line_wraps[i] = false;
}
ui_call_grid_clear(1); // clear the display
@@ -318,11 +325,15 @@ void screen_resize(int width, int height)
resizing_autocmd = false;
redraw_all_later(UPD_CLEAR);
+ if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) {
+ screenclear();
+ }
+
if (starting != NO_SCREEN) {
maketitle();
changed_line_abv_curs();
- invalidate_botline();
+ invalidate_botline(curwin);
// We only redraw when it's needed:
// - While at the more prompt or executing an external command, don't
@@ -370,6 +381,32 @@ void screen_resize(int width, int height)
resizing_screen = false;
}
+/// Check if the new Nvim application "screen" dimensions are valid.
+/// Correct it if it's too small or way too big.
+void check_screensize(void)
+{
+ // Limit Rows and Columns to avoid an overflow in Rows * Columns.
+ if (Rows < min_rows()) {
+ // need room for one window and command line
+ Rows = min_rows();
+ } else if (Rows > 1000) {
+ Rows = 1000;
+ }
+
+ if (Columns < MIN_COLUMNS) {
+ Columns = MIN_COLUMNS;
+ } else if (Columns > 10000) {
+ Columns = 10000;
+ }
+}
+
+/// Return true if redrawing should currently be done.
+bool redrawing(void)
+{
+ return !RedrawingDisabled
+ && !(p_lz && char_avail() && !KeyTyped && !do_redraw);
+}
+
/// Redraw the parts of the screen that is marked for redraw.
///
/// Most code shouldn't call this directly, rather use redraw_later() and
@@ -411,6 +448,15 @@ int update_screen(void)
display_tick++; // let syntax code know we're in a next round of
// display updating
+ // glyph cache full, very rare
+ if (schar_cache_clear_if_full()) {
+ // must use CLEAR, as the contents of screen buffers cannot be
+ // compared to their previous state here.
+ // TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs)
+ // we need to revalidate these here as well!
+ type = MAX(type, UPD_CLEAR);
+ }
+
// Tricky: vim code can reset msg_scrolled behind our back, so need
// separate bookkeeping for now.
if (msg_did_scroll) {
@@ -430,7 +476,7 @@ int update_screen(void)
// non-displayed part of msg_grid is considered invalid.
for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) {
grid_clear_line(&msg_grid, msg_grid.line_offset[i],
- msg_grid.cols, false);
+ msg_grid.cols, i < p_ch);
}
}
msg_grid.throttled = false;
@@ -507,7 +553,7 @@ int update_screen(void)
ui_comp_set_screen_valid(true);
DecorProviders providers;
- decor_providers_start(&providers, &provider_err);
+ decor_providers_start(&providers);
// "start" callback could have changed highlights for global elements
if (win_check_ns_hl(NULL)) {
@@ -516,7 +562,7 @@ int update_screen(void)
}
if (clear_cmdline) { // going to clear cmdline (done below)
- check_for_delay(false);
+ msg_check_for_delay(false);
}
// Force redraw when width of 'number' or 'relativenumber' column
@@ -540,9 +586,9 @@ int update_screen(void)
draw_tabline();
}
- // Correct stored syntax highlighting info for changes in each displayed
- // buffer. Each buffer must only be done once.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ // Correct stored syntax highlighting info for changes in each displayed
+ // buffer. Each buffer must only be done once.
update_window_hl(wp, type >= UPD_NOT_VALID || hl_changed);
buf_T *buf = wp->w_buffer;
@@ -554,10 +600,19 @@ int update_screen(void)
}
if (buf->b_mod_tick_decor < display_tick) {
- decor_providers_invoke_buf(buf, &providers, &provider_err);
+ decor_providers_invoke_buf(buf, &providers);
buf->b_mod_tick_decor = display_tick;
}
}
+
+ // Reset 'statuscolumn' if there is no dedicated signcolumn but it is invalid.
+ if (*wp->w_p_stc != NUL && wp->w_minscwidth <= SCL_NO
+ && (wp->w_buffer->b_signcols.invalid_bot || !wp->w_buffer->b_signcols.sentinel)) {
+ wp->w_nrwidth_line_count = 0;
+ wp->w_valid &= ~VALID_WCOL;
+ wp->w_redr_type = UPD_NOT_VALID;
+ wp->w_buffer->b_signcols.invalid_bot = 0;
+ }
}
// Go from top to bottom through the windows, redrawing the ones that need it.
@@ -624,7 +679,7 @@ int update_screen(void)
}
did_intro = true;
- decor_providers_invoke_end(&providers, &provider_err);
+ decor_providers_invoke_end(&providers);
kvi_destroy(providers);
// either cmdline is cleared, not drawn or mode is last drawn
@@ -632,20 +687,56 @@ int update_screen(void)
return OK;
}
-static void win_border_redr_title(win_T *wp, ScreenGrid *grid, int col)
+/// Prepare for 'hlsearch' highlighting.
+void start_search_hl(void)
+{
+ if (!p_hls || no_hlsearch) {
+ return;
+ }
+
+ end_search_hl(); // just in case it wasn't called before
+ last_pat_prog(&screen_search_hl.rm);
+ // Set the time limit to 'redrawtime'.
+ screen_search_hl.tm = profile_setlimit(p_rdt);
+}
+
+/// Clean up for 'hlsearch' highlighting.
+void end_search_hl(void)
{
- VirtText title_chunks = wp->w_float_config.title_chunks;
+ if (screen_search_hl.rm.regprog == NULL) {
+ return;
+ }
- for (size_t i = 0; i < title_chunks.size; i++) {
- char *text = title_chunks.items[i].text;
- int cell = (int)mb_string2cells(text);
- int hl_id = title_chunks.items[i].hl_id;
- int attr = hl_id ? syn_id2attr(hl_id) : 0;
- grid_puts(grid, text, 0, col, attr);
- col += cell;
+ vim_regfree(screen_search_hl.rm.regprog);
+ screen_search_hl.rm.regprog = NULL;
+}
+
+static void win_redr_bordertext(win_T *wp, VirtText vt, int col)
+{
+ for (size_t i = 0; i < kv_size(vt);) {
+ int attr = 0;
+ char *text = next_virt_text_chunk(vt, &i, &attr);
+ if (text == NULL) {
+ break;
+ }
+ attr = hl_apply_winblend(wp, attr);
+ col += grid_line_puts(col, text, -1, attr);
}
}
+int win_get_bordertext_col(int total_col, int text_width, AlignTextPos align)
+{
+ switch (align) {
+ case kAlignLeft:
+ return 1;
+ case kAlignCenter:
+ return MAX((total_col - text_width) / 2 + 1, 1);
+ case kAlignRight:
+ return MAX(total_col - text_width + 1, 1);
+ }
+ UNREACHABLE;
+}
+
static void win_redr_border(win_T *wp)
{
wp->w_redr_border = false;
@@ -655,102 +746,459 @@ static void win_redr_border(win_T *wp)
ScreenGrid *grid = &wp->w_grid_alloc;
- schar_T *chars = wp->w_float_config.border_chars;
+ schar_T chars[8];
+ for (int i = 0; i < 8; i++) {
+ chars[i] = schar_from_str(wp->w_float_config.border_chars[i]);
+ }
int *attrs = wp->w_float_config.border_attr;
int *adj = wp->w_border_adj;
- int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner;
+ int irow = wp->w_height_inner + wp->w_winbar_height;
+ int icol = wp->w_width_inner;
if (adj[0]) {
- grid_puts_line_start(grid, 0);
+ grid_line_start(grid, 0);
if (adj[3]) {
- grid_put_schar(grid, 0, 0, chars[0], attrs[0]);
+ grid_line_put_schar(0, chars[0], attrs[0]);
}
for (int i = 0; i < icol; i++) {
- grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]);
+ grid_line_put_schar(i + adj[3], chars[1], attrs[1]);
}
if (wp->w_float_config.title) {
- int title_col = 0;
- int title_width = wp->w_float_config.title_width;
- AlignTextPos title_pos = wp->w_float_config.title_pos;
-
- if (title_pos == kAlignCenter) {
- title_col = (icol - title_width) / 2 + 1;
- } else {
- title_col = title_pos == kAlignLeft ? 1 : icol - title_width + 1;
- }
-
- win_border_redr_title(wp, grid, title_col);
+ int title_col = win_get_bordertext_col(icol, wp->w_float_config.title_width,
+ wp->w_float_config.title_pos);
+ win_redr_bordertext(wp, wp->w_float_config.title_chunks, title_col);
}
if (adj[1]) {
- grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]);
+ grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
}
- grid_puts_line_flush(false);
+ grid_line_flush();
}
for (int i = 0; i < irow; i++) {
if (adj[3]) {
- grid_puts_line_start(grid, i + adj[0]);
- grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]);
- grid_puts_line_flush(false);
+ grid_line_start(grid, i + adj[0]);
+ grid_line_put_schar(0, chars[7], attrs[7]);
+ grid_line_flush();
}
if (adj[1]) {
- int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3;
- grid_puts_line_start(grid, i + adj[0]);
- grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]);
- grid_puts_line_flush(false);
+ int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
+ grid_line_start(grid, i + adj[0]);
+ grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
+ grid_line_flush();
}
}
if (adj[2]) {
- grid_puts_line_start(grid, irow + adj[0]);
+ grid_line_start(grid, irow + adj[0]);
if (adj[3]) {
- grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]);
+ grid_line_put_schar(0, chars[6], attrs[6]);
}
+
for (int i = 0; i < icol; i++) {
- int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5;
- grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]);
+ int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5;
+ grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]);
+ }
+
+ if (wp->w_float_config.footer) {
+ int footer_col = win_get_bordertext_col(icol, wp->w_float_config.footer_width,
+ wp->w_float_config.footer_pos);
+ win_redr_bordertext(wp, wp->w_float_config.footer_chunks, footer_col);
}
if (adj[1]) {
- grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]);
+ grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
+ }
+ grid_line_flush();
+ }
+}
+
+/// Set cursor to its position in the current window.
+void setcursor(void)
+{
+ setcursor_mayforce(false);
+}
+
+/// Set cursor to its position in the current window.
+/// @param force when true, also when not redrawing.
+void setcursor_mayforce(bool force)
+{
+ if (force || redrawing()) {
+ validate_cursor();
+
+ ScreenGrid *grid = &curwin->w_grid;
+ int row = curwin->w_wrow;
+ int col = curwin->w_wcol;
+ if (curwin->w_p_rl) {
+ // With 'rightleft' set and the cursor on a double-wide character,
+ // position it on the leftmost column.
+ col = curwin->w_width_inner - curwin->w_wcol
+ - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2
+ && vim_isprintc(gchar_cursor())) ? 2 : 1);
}
- grid_puts_line_flush(false);
+
+ grid_adjust(&grid, &row, &col);
+ ui_grid_cursor_goto(grid->handle, row, col);
}
}
/// Show current cursor info in ruler and various other places
///
/// @param always if false, only show ruler if position has changed.
-void show_cursor_info(bool always)
+void show_cursor_info_later(bool force)
{
- if (!always && !redrawing()) {
- return;
+ int state = get_real_state();
+ int empty_line = (State & MODE_INSERT) == 0
+ && *ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum) == NUL;
+
+ // Only draw when something changed.
+ validate_virtcol_win(curwin);
+ if (force
+ || curwin->w_cursor.lnum != curwin->w_stl_cursor.lnum
+ || curwin->w_cursor.col != curwin->w_stl_cursor.col
+ || curwin->w_virtcol != curwin->w_stl_virtcol
+ || curwin->w_cursor.coladd != curwin->w_stl_cursor.coladd
+ || curwin->w_topline != curwin->w_stl_topline
+ || curwin->w_buffer->b_ml.ml_line_count != curwin->w_stl_line_count
+ || curwin->w_topfill != curwin->w_stl_topfill
+ || empty_line != curwin->w_stl_empty
+ || reg_recording != curwin->w_stl_recording
+ || state != curwin->w_stl_state
+ || (VIsual_active && VIsual_mode != curwin->w_stl_visual_mode)) {
+ if (curwin->w_status_height || global_stl_height()) {
+ curwin->w_redr_status = true;
+ } else {
+ redraw_cmdline = true;
+ }
+
+ if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) {
+ curwin->w_redr_status = true;
+ }
+
+ if ((p_icon && (stl_syntax & STL_IN_ICON))
+ || (p_title && (stl_syntax & STL_IN_TITLE))) {
+ need_maketitle = true;
+ }
+ }
+
+ curwin->w_stl_cursor = curwin->w_cursor;
+ curwin->w_stl_virtcol = curwin->w_virtcol;
+ curwin->w_stl_empty = (char)empty_line;
+ curwin->w_stl_topline = curwin->w_topline;
+ curwin->w_stl_line_count = curwin->w_buffer->b_ml.ml_line_count;
+ curwin->w_stl_topfill = curwin->w_topfill;
+ curwin->w_stl_recording = reg_recording;
+ curwin->w_stl_state = state;
+ if (VIsual_active) {
+ curwin->w_stl_visual_mode = VIsual_mode;
}
+}
+
+/// @return true when postponing displaying the mode message: when not redrawing
+/// or inside a mapping.
+bool skip_showmode(void)
+{
+ // Call char_avail() only when we are going to show something, because it
+ // takes a bit of time. redrawing() may also call char_avail().
+ if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) {
+ redraw_mode = true; // show mode later
+ return true;
+ }
+ return false;
+}
+
+/// Show the current mode and ruler.
+///
+/// If clear_cmdline is true, clear the rest of the cmdline.
+/// If clear_cmdline is false there may be a message there that needs to be
+/// cleared only if a mode is shown.
+/// If redraw_mode is true show or clear the mode.
+/// @return the length of the message (0 if no message).
+int showmode(void)
+{
+ int length = 0;
+
+ if (ui_has(kUIMessages) && clear_cmdline) {
+ msg_ext_clear(true);
+ }
+
+ // don't make non-flushed message part of the showmode
+ msg_ext_ui_flush();
+
+ msg_grid_validate();
+
+ int do_mode = ((p_smd && msg_silent == 0)
+ && ((State & MODE_TERMINAL)
+ || (State & MODE_INSERT)
+ || restart_edit != NUL
+ || VIsual_active));
+
+ bool can_show_mode = (p_ch != 0 || ui_has(kUIMessages));
+ if ((do_mode || reg_recording != 0) && can_show_mode) {
+ int sub_attr;
+ if (skip_showmode()) {
+ return 0; // show mode later
+ }
+
+ bool nwr_save = need_wait_return;
+
+ // wait a bit before overwriting an important message
+ msg_check_for_delay(false);
+
+ // if the cmdline is more than one line high, erase top lines
+ bool need_clear = clear_cmdline;
+ if (clear_cmdline && cmdline_row < Rows - 1) {
+ msg_clr_cmdline(); // will reset clear_cmdline
+ }
+
+ // Position on the last line in the window, column 0
+ msg_pos_mode();
+ int attr = HL_ATTR(HLF_CM); // Highlight mode
+
+ // When the screen is too narrow to show the entire mode message,
+ // avoid scrolling and truncate instead.
+ msg_no_more = true;
+ int save_lines_left = lines_left;
+ lines_left = 0;
+
+ if (do_mode) {
+ msg_puts_attr("--", attr);
+ // CTRL-X in Insert mode
+ if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) {
+ // These messages can get long, avoid a wrap in a narrow window.
+ // Prefer showing edit_submode_extra. With external messages there
+ // is no imposed limit.
+ if (ui_has(kUIMessages)) {
+ length = INT_MAX;
+ } else {
+ length = (Rows - msg_row) * Columns - 3;
+ }
+ if (edit_submode_extra != NULL) {
+ length -= vim_strsize(edit_submode_extra);
+ }
+ if (length > 0) {
+ if (edit_submode_pre != NULL) {
+ length -= vim_strsize(edit_submode_pre);
+ }
+ if (length - vim_strsize(edit_submode) > 0) {
+ if (edit_submode_pre != NULL) {
+ msg_puts_attr(edit_submode_pre, attr);
+ }
+ msg_puts_attr(edit_submode, attr);
+ }
+ if (edit_submode_extra != NULL) {
+ msg_puts_attr(" ", attr); // Add a space in between.
+ if ((int)edit_submode_highl < HLF_COUNT) {
+ sub_attr = win_hl_attr(curwin, (int)edit_submode_highl);
+ } else {
+ sub_attr = attr;
+ }
+ msg_puts_attr(edit_submode_extra, sub_attr);
+ }
+ }
+ } else {
+ if (State & MODE_TERMINAL) {
+ msg_puts_attr(_(" TERMINAL"), attr);
+ } else if (State & VREPLACE_FLAG) {
+ msg_puts_attr(_(" VREPLACE"), attr);
+ } else if (State & REPLACE_FLAG) {
+ msg_puts_attr(_(" REPLACE"), attr);
+ } else if (State & MODE_INSERT) {
+ if (p_ri) {
+ msg_puts_attr(_(" REVERSE"), attr);
+ }
+ msg_puts_attr(_(" INSERT"), attr);
+ } else if (restart_edit == 'I' || restart_edit == 'i'
+ || restart_edit == 'a' || restart_edit == 'A') {
+ if (curbuf->terminal) {
+ msg_puts_attr(_(" (terminal)"), attr);
+ } else {
+ msg_puts_attr(_(" (insert)"), attr);
+ }
+ } else if (restart_edit == 'R') {
+ msg_puts_attr(_(" (replace)"), attr);
+ } else if (restart_edit == 'V') {
+ msg_puts_attr(_(" (vreplace)"), attr);
+ }
+ if (State & MODE_LANGMAP) {
+ if (curwin->w_p_arab) {
+ msg_puts_attr(_(" Arabic"), attr);
+ } else if (get_keymap_str(curwin, " (%s)",
+ NameBuff, MAXPATHL)) {
+ msg_puts_attr(NameBuff, attr);
+ }
+ }
+ if ((State & MODE_INSERT) && p_paste) {
+ msg_puts_attr(_(" (paste)"), attr);
+ }
+
+ if (VIsual_active) {
+ char *p;
+
+ // Don't concatenate separate words to avoid translation
+ // problems.
+ switch ((VIsual_select ? 4 : 0)
+ + (VIsual_mode == Ctrl_V) * 2
+ + (VIsual_mode == 'V')) {
+ case 0:
+ p = N_(" VISUAL"); break;
+ case 1:
+ p = N_(" VISUAL LINE"); break;
+ case 2:
+ p = N_(" VISUAL BLOCK"); break;
+ case 4:
+ p = N_(" SELECT"); break;
+ case 5:
+ p = N_(" SELECT LINE"); break;
+ default:
+ p = N_(" SELECT BLOCK"); break;
+ }
+ msg_puts_attr(_(p), attr);
+ }
+ msg_puts_attr(" --", attr);
+ }
+
+ need_clear = true;
+ }
+ if (reg_recording != 0
+ && edit_submode == NULL // otherwise it gets too long
+ ) {
+ recording_mode(attr);
+ need_clear = true;
+ }
+
+ mode_displayed = true;
+ if (need_clear || clear_cmdline || redraw_mode) {
+ msg_clr_eos();
+ }
+ msg_didout = false; // overwrite this message
+ length = msg_col;
+ msg_col = 0;
+ msg_no_more = false;
+ lines_left = save_lines_left;
+ need_wait_return = nwr_save; // never ask for hit-return for this
+ } else if (clear_cmdline && msg_silent == 0) {
+ // Clear the whole command line. Will reset "clear_cmdline".
+ msg_clr_cmdline();
+ } else if (redraw_mode) {
+ msg_pos_mode();
+ msg_clr_eos();
+ }
+
+ // NB: also handles clearing the showmode if it was empty or disabled
+ msg_ext_flush_showmode();
+
+ // In Visual mode the size of the selected area must be redrawn.
+ if (VIsual_active) {
+ clear_showcmd();
+ }
+
+ // If the current or last window has no status line and global statusline is disabled,
+ // the ruler is after the mode message and must be redrawn
+ win_T *ruler_win = curwin->w_status_height == 0 ? curwin : lastwin_nofloating();
+ if (redrawing() && ruler_win->w_status_height == 0 && global_stl_height() == 0
+ && !(p_ch == 0 && !ui_has(kUIMessages))) {
+ grid_line_start(&msg_grid_adj, Rows - 1);
+ win_redr_ruler(ruler_win);
+ grid_line_flush();
+ }
+
+ redraw_cmdline = false;
+ redraw_mode = false;
+ clear_cmdline = false;
+
+ return length;
+}
+
+/// Position for a mode message.
+static void msg_pos_mode(void)
+{
+ msg_col = 0;
+ msg_row = Rows - 1;
+}
- win_check_ns_hl(curwin);
- if ((*p_stl != NUL || *curwin->w_p_stl != NUL)
- && (curwin->w_status_height || global_stl_height())) {
- redraw_custom_statusline(curwin);
+/// Delete mode message. Used when ESC is typed which is expected to end
+/// Insert mode (but Insert mode didn't end yet!).
+/// Caller should check "mode_displayed".
+void unshowmode(bool force)
+{
+ // Don't delete it right now, when not redrawing or inside a mapping.
+ if (!redrawing() || (!force && char_avail() && !KeyTyped)) {
+ redraw_cmdline = true; // delete mode later
} else {
- win_redr_ruler(curwin, always);
+ clearmode();
}
- if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) {
- win_redr_winbar(curwin);
+}
+
+// Clear the mode message.
+void clearmode(void)
+{
+ const int save_msg_row = msg_row;
+ const int save_msg_col = msg_col;
+
+ msg_ext_ui_flush();
+ msg_pos_mode();
+ if (reg_recording != 0) {
+ recording_mode(HL_ATTR(HLF_CM));
}
+ msg_clr_eos();
+ msg_ext_flush_showmode();
- if (need_maketitle
- || (p_icon && (stl_syntax & STL_IN_ICON))
- || (p_title && (stl_syntax & STL_IN_TITLE))) {
- maketitle();
+ msg_col = save_msg_col;
+ msg_row = save_msg_row;
+}
+
+static void recording_mode(int attr)
+{
+ msg_puts_attr(_("recording"), attr);
+ if (shortmess(SHM_RECORDING)) {
+ return;
}
- win_check_ns_hl(NULL);
- // Redraw the tab pages line if needed.
- if (redraw_tabline) {
- draw_tabline();
+ char s[4];
+ snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
+ msg_puts_attr(s, attr);
+}
+
+#define COL_RULER 17 // columns needed by standard ruler
+
+/// Compute columns for ruler and shown command. 'sc_col' is also used to
+/// decide what the maximum length of a message on the status line can be.
+/// If there is a status line for the last window, 'sc_col' is independent
+/// of 'ru_col'.
+void comp_col(void)
+{
+ bool last_has_status = last_stl_height(false) > 0;
+
+ sc_col = 0;
+ ru_col = 0;
+ if (p_ru) {
+ ru_col = (ru_wid ? ru_wid : COL_RULER) + 1;
+ // no last status line, adjust sc_col
+ if (!last_has_status) {
+ sc_col = ru_col;
+ }
+ }
+ if (p_sc) {
+ sc_col += SHOWCMD_COLS;
+ if (!p_ru || last_has_status) { // no need for separating space
+ sc_col++;
+ }
+ }
+ assert(sc_col >= 0
+ && INT_MIN + sc_col <= Columns);
+ sc_col = Columns - sc_col;
+ assert(ru_col >= 0
+ && INT_MIN + ru_col <= Columns);
+ ru_col = Columns - ru_col;
+ if (sc_col <= 0) { // screen too narrow, will become a mess
+ sc_col = 1;
}
+ if (ru_col <= 0) {
+ ru_col = 1;
+ }
+ set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
}
static void redraw_win_signcol(win_T *wp)
@@ -762,6 +1210,7 @@ static void redraw_win_signcol(win_T *wp)
wp->w_scwidth = win_signcol_count(wp);
if (wp->w_scwidth != scwidth) {
changed_line_abv_curs_win(wp);
+ redraw_later(wp, UPD_NOT_VALID);
}
}
@@ -844,8 +1293,8 @@ static void draw_vsep_win(win_T *wp)
}
// draw the vertical separator right of this window
- int hl;
- int c = fillchar_vsep(wp, &hl);
+ int hl = win_hl_attr(wp, HLF_C);
+ int c = wp->w_p_fcs_chars.vert;
grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp),
W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl);
}
@@ -858,30 +1307,32 @@ static void draw_hsep_win(win_T *wp)
}
// draw the horizontal separator below this window
- int hl;
- int c = fillchar_hsep(wp, &hl);
+ int hl = win_hl_attr(wp, HLF_C);
+ int c = wp->w_p_fcs_chars.horiz;
grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1,
wp->w_wincol, W_ENDCOL(wp), c, c, hl);
}
/// Get the separator connector for specified window corner of window "wp"
-static int get_corner_sep_connector(win_T *wp, WindowCorner corner)
+static schar_T get_corner_sep_connector(win_T *wp, WindowCorner corner)
{
// It's impossible for windows to be connected neither vertically nor horizontally
// So if they're not vertically connected, assume they're horizontally connected
+ int c;
if (vsep_connected(wp, corner)) {
if (hsep_connected(wp, corner)) {
- return wp->w_p_fcs_chars.verthoriz;
+ c = wp->w_p_fcs_chars.verthoriz;
} else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) {
- return wp->w_p_fcs_chars.vertright;
+ c = wp->w_p_fcs_chars.vertright;
} else {
- return wp->w_p_fcs_chars.vertleft;
+ c = wp->w_p_fcs_chars.vertleft;
}
} else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) {
- return wp->w_p_fcs_chars.horizdown;
+ c = wp->w_p_fcs_chars.horizdown;
} else {
- return wp->w_p_fcs_chars.horizup;
+ c = wp->w_p_fcs_chars.horizup;
}
+ return schar_from_char(c);
}
/// Draw separator connecting characters on the corners of window "wp"
@@ -917,21 +1368,31 @@ static void draw_sep_connectors_win(win_T *wp)
win_at_left = frp->fr_parent == NULL;
// Draw the appropriate separator connector in every corner where drawing them is necessary
- if (!(win_at_top || win_at_left)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT),
- wp->w_winrow - 1, wp->w_wincol - 1, hl);
- }
- if (!(win_at_top || win_at_right)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT),
- wp->w_winrow - 1, W_ENDCOL(wp), hl);
- }
- if (!(win_at_bottom || win_at_left)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT),
- W_ENDROW(wp), wp->w_wincol - 1, hl);
- }
- if (!(win_at_bottom || win_at_right)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT),
- W_ENDROW(wp), W_ENDCOL(wp), hl);
+ // Make sure not to send cursor position updates to ui.
+ bool top_left = !(win_at_top || win_at_left);
+ bool top_right = !(win_at_top || win_at_right);
+ bool bot_left = !(win_at_bottom || win_at_left);
+ bool bot_right = !(win_at_bottom || win_at_right);
+
+ if (top_left) {
+ grid_line_start(&default_grid, wp->w_winrow - 1);
+ grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl);
+ grid_line_flush();
+ }
+ if (top_right) {
+ grid_line_start(&default_grid, wp->w_winrow - 1);
+ grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl);
+ grid_line_flush();
+ }
+ if (bot_left) {
+ grid_line_start(&default_grid, W_ENDROW(wp));
+ grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl);
+ grid_line_flush();
+ }
+ if (bot_right) {
+ grid_line_start(&default_grid, W_ENDROW(wp));
+ grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl);
+ grid_line_flush();
}
}
@@ -992,9 +1453,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
int type = wp->w_redr_type;
if (type >= UPD_NOT_VALID) {
- // TODO(bfredl): should only be implied for CLEAR, not NOT_VALID!
wp->w_redr_status = true;
-
wp->w_lines_valid = 0;
}
@@ -1040,25 +1499,41 @@ static void win_update(win_T *wp, DecorProviders *providers)
win_extmark_arr.size = 0;
- decor_redraw_reset(buf, &decor_state);
+ decor_redraw_reset(wp, &decor_state);
DecorProviders line_providers;
- decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
+ decor_providers_invoke_win(wp, providers, &line_providers);
redraw_win_signcol(wp);
init_search_hl(wp, &screen_search_hl);
- // Force redraw when width of 'number' or 'relativenumber' column
- // changes.
- int nrwidth = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0;
- if (wp->w_nrwidth != nrwidth) {
- type = UPD_NOT_VALID;
- wp->w_nrwidth = nrwidth;
-
- if (buf->terminal) {
- terminal_check_size(buf->terminal);
+ // Make sure skipcol is valid, it depends on various options and the window
+ // width.
+ if (wp->w_skipcol > 0) {
+ int w = 0;
+ int width1 = wp->w_width_inner - win_col_off(wp);
+ int width2 = width1 + win_col_off2(wp);
+ int add = width1;
+
+ while (w < wp->w_skipcol) {
+ if (w > 0) {
+ add = width2;
+ }
+ w += add;
+ }
+ if (w != wp->w_skipcol) {
+ // always round down, the higher value may not be valid
+ wp->w_skipcol = w - add;
}
+ }
+
+ const int nrwidth_before = wp->w_nrwidth;
+ int nrwidth_new = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0;
+ // Force redraw when width of 'number' or 'relativenumber' column changes.
+ if (wp->w_nrwidth != nrwidth_new) {
+ type = UPD_NOT_VALID;
+ wp->w_nrwidth = nrwidth_new;
} else if (buf->b_mod_set
&& buf->b_mod_xlines != 0
&& wp->w_redraw_top != 0) {
@@ -1112,8 +1587,6 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
}
if (mod_top != 0 && hasAnyFolding(wp)) {
- linenr_T lnumt, lnumb;
-
// A change in a line can cause lines above it to become folded or
// unfolded. Find the top most buffer line that may be affected.
// If the line was previously folded and displayed, get the first
@@ -1124,8 +1597,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
// the line below it. If there is no valid entry, use w_topline.
// Find the first valid w_lines[] entry below mod_bot. Set lnumb
// to this line. If there is no valid entry, use MAXLNUM.
- lnumt = wp->w_topline;
- lnumb = MAXLNUM;
+ linenr_T lnumt = wp->w_topline;
+ linenr_T lnumb = MAXLNUM;
for (int i = 0; i < wp->w_lines_valid; i++) {
if (wp->w_lines[i].wl_valid) {
if (wp->w_lines[i].wl_lastlnum < mod_top) {
@@ -1181,11 +1654,11 @@ static void win_update(win_T *wp, DecorProviders *providers)
// When only displaying the lines at the top, set top_end. Used when
// window has scrolled down for msg_scrolled.
if (type == UPD_REDRAW_TOP) {
- long j = 0;
+ int j = 0;
for (int i = 0; i < wp->w_lines_valid; i++) {
j += wp->w_lines[i].wl_size;
if (j >= wp->w_upd_rows) {
- top_end = (int)j;
+ top_end = j;
break;
}
}
@@ -1218,7 +1691,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
|| (wp->w_topline == wp->w_lines[0].wl_lnum
&& wp->w_topfill > wp->w_old_topfill))) {
// New topline is above old topline: May scroll down.
- long j;
+ int j;
if (hasAnyFolding(wp)) {
linenr_T ln;
@@ -1236,7 +1709,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
j = wp->w_lines[0].wl_lnum - wp->w_topline;
}
if (j < wp->w_grid.rows - 2) { // not too far off
- int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1);
+ int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1, true);
// insert extra lines for previously invisible filler lines
if (wp->w_lines[0].wl_lnum != wp->w_topline) {
i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill;
@@ -1278,7 +1751,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// needs updating.
// try to find wp->w_topline in wp->w_lines[].wl_lnum
- long j = -1;
+ int j = -1;
int row = 0;
for (int i = 0; i < wp->w_lines_valid; i++) {
if (wp->w_lines[i].wl_valid
@@ -1317,7 +1790,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// bot_start to the first row that needs redrawing.
bot_start = 0;
int idx = 0;
- for (;;) {
+ while (true) {
wp->w_lines[idx] = wp->w_lines[j];
// stop at line that didn't fit, unless it is still
// valid (no lines deleted)
@@ -1430,7 +1903,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// First compute the actual start and end column.
if (VIsual_mode == Ctrl_V) {
colnr_T fromc, toc;
- unsigned int save_ve_flags = curwin->w_ve_flags;
+ unsigned save_ve_flags = curwin->w_ve_flags;
if (curwin->w_p_lbr) {
curwin->w_ve_flags = VE_ALL;
@@ -1454,7 +1927,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
pos.lnum += cursor_above ? 1 : -1) {
colnr_T t;
- pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum, false));
+ pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum));
getvvcol(wp, &pos, NULL, NULL, &t);
if (toc < t) {
toc = t;
@@ -1561,19 +2034,34 @@ static void win_update(win_T *wp, DecorProviders *providers)
wp->w_old_visual_col = 0;
}
- bool cursorline_standout = win_cursorline_standout(wp);
+ foldinfo_T cursorline_fi = { 0 };
+ wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
+ if (wp->w_p_cul) {
+ // Make sure that the cursorline on a closed fold is redrawn
+ cursorline_fi = fold_info(wp, wp->w_cursor.lnum);
+ if (cursorline_fi.fi_level != 0 && cursorline_fi.fi_lines > 0) {
+ wp->w_cursorline = cursorline_fi.fi_lnum;
+ }
+ }
win_check_ns_hl(wp);
+ spellvars_T spv = { 0 };
+ linenr_T lnum = wp->w_topline; // first line shown in window
+ // Initialize spell related variables for the first drawn line.
+ if (spell_check_window(wp)) {
+ spv.spv_has_spell = true;
+ spv.spv_unchanged = mod_top == 0;
+ }
+
// Update all the window rows.
int idx = 0; // first entry in w_lines[].wl_size
int row = 0; // current window row to display
int srow = 0; // starting row of the current line
- linenr_T lnum = wp->w_topline; // first line shown in window
bool eof = false; // if true, we hit the end of the file
bool didline = false; // if true, we finished the last line
- for (;;) {
+ while (true) {
// stop updating when reached the end of the window (check for _past_
// the end of the window is at the end of the loop)
if (row == wp->w_grid.rows) {
@@ -1617,7 +2105,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// if lines were inserted or deleted
|| (wp->w_match_head != NULL
&& buf->b_mod_xlines != 0)))))
- || (cursorline_standout && lnum == wp->w_cursor.lnum)
+ || lnum == wp->w_cursorline
|| lnum == wp->w_last_cursorline) {
if (lnum == mod_top) {
top_to_mod = false;
@@ -1633,8 +2121,6 @@ static void win_update(win_T *wp, DecorProviders *providers)
&& !(dollar_vcol >= 0 && mod_bot == mod_top + 1)
&& row >= top_end) {
int old_rows = 0;
- int new_rows = 0;
- int xtra_rows;
linenr_T l;
int i;
@@ -1669,14 +2155,20 @@ static void win_update(win_T *wp, DecorProviders *providers)
bot_start = 0;
bot_scroll_start = 0;
} else {
+ int new_rows = 0;
// Able to count old number of rows: Count new window
// rows, and may insert/delete lines
- long j = idx;
+ int j = idx;
for (l = lnum; l < mod_bot; l++) {
if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) {
new_rows++;
} else if (l == wp->w_topline) {
- new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill;
+ int n = plines_win_nofill(wp, l, false) + wp->w_topfill;
+ n -= adjust_plines_for_skipcol(wp);
+ if (n > wp->w_height_inner) {
+ n = wp->w_height_inner;
+ }
+ new_rows += n;
} else {
new_rows += plines_win(wp, l, true);
}
@@ -1687,7 +2179,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
break;
}
}
- xtra_rows = new_rows - old_rows;
+ int xtra_rows = new_rows - old_rows;
if (xtra_rows < 0) {
// May scroll text up. If there is not enough
// remaining text or scrolling fails, must redraw the
@@ -1724,17 +2216,17 @@ static void win_update(win_T *wp, DecorProviders *providers)
int x = row + new_rows;
// move entries in w_lines[] upwards
- for (;;) {
+ while (true) {
// stop at last valid entry in w_lines[]
if (i >= wp->w_lines_valid) {
- wp->w_lines_valid = (int)j;
+ wp->w_lines_valid = j;
break;
}
wp->w_lines[j] = wp->w_lines[i];
// stop at a line that won't fit
if (x + (int)wp->w_lines[j].wl_size
> wp->w_grid.rows) {
- wp->w_lines_valid = (int)j + 1;
+ wp->w_lines_valid = j + 1;
break;
}
x += wp->w_lines[j++].wl_size;
@@ -1769,7 +2261,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
// When lines are folded, display one line for all of them.
// Otherwise, display normally (can be several display lines when
// 'wrap' is on).
- foldinfo_T foldinfo = fold_info(wp, lnum);
+ foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum
+ ? cursorline_fi : fold_info(wp, lnum);
if (foldinfo.fi_lines == 0
&& idx < wp->w_lines_valid
@@ -1791,9 +2284,10 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
// Display one line
- row = win_line(wp, lnum, srow,
- foldinfo.fi_lines ? srow : wp->w_grid.rows,
- mod_top == 0, false, foldinfo, &line_providers, &provider_err);
+ spellvars_T zero_spv = { 0 };
+ row = win_line(wp, lnum, srow, wp->w_grid.rows, false,
+ foldinfo.fi_lines > 0 ? &zero_spv : &spv,
+ foldinfo, &line_providers);
if (foldinfo.fi_lines == 0) {
wp->w_lines[idx].wl_folded = false;
@@ -1805,6 +2299,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
wp->w_lines[idx].wl_folded = true;
wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
did_update = DID_FOLD;
+ spv.spv_capcol_lnum = 0;
}
}
@@ -1828,9 +2323,9 @@ static void win_update(win_T *wp, DecorProviders *providers)
if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) {
// 'relativenumber' set and cursor moved vertically: The
// text doesn't need to be drawn, but the number column does.
- foldinfo_T info = fold_info(wp, lnum);
- (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true,
- info, &line_providers, &provider_err);
+ foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum
+ ? cursorline_fi : fold_info(wp, lnum);
+ (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, &spv, info, &line_providers);
}
// This line does not need to be drawn, advance to the next one.
@@ -1840,6 +2335,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
lnum = wp->w_lines[idx - 1].wl_lastlnum + 1;
did_update = DID_NONE;
+ spv.spv_capcol_lnum = 0;
}
// 'statuscolumn' width has changed or errored, start from the top.
@@ -1850,7 +2346,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
lnum = wp->w_topline;
wp->w_lines_valid = 0;
wp->w_valid &= ~VALID_WCOL;
- decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
+ decor_redraw_reset(wp, &decor_state);
+ decor_providers_invoke_win(wp, providers, &line_providers);
continue;
}
@@ -1863,7 +2360,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// Now that the window has been redrawn with the old and new cursor line,
// update w_last_cursorline.
- wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0;
+ wp->w_last_cursorline = wp->w_cursorline;
wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0;
@@ -1893,24 +2390,21 @@ static void win_update(win_T *wp, DecorProviders *providers)
wp->w_botline = lnum;
wp->w_filler_rows = wp->w_grid.rows - srow;
} else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate"
- int scr_row = wp->w_grid.rows - 1;
- int symbol = wp->w_p_fcs_chars.lastline;
- char fillbuf[12]; // 2 characters of 6 bytes
- int charlen = utf_char2bytes(symbol, &fillbuf[0]);
- utf_char2bytes(symbol, &fillbuf[charlen]);
-
// Last line isn't finished: Display "@@@" in the last screen line.
- grid_puts_len(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr);
- grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, symbol, ' ', at_attr);
+ grid_line_start(&wp->w_grid, wp->w_grid.rows - 1);
+ grid_line_fill(0, MIN(wp->w_grid.cols, 3), wp->w_p_fcs_chars.lastline, at_attr);
+ grid_line_fill(3, wp->w_grid.cols, ' ', at_attr);
+ grid_line_flush();
set_empty_rows(wp, srow);
wp->w_botline = lnum;
} else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline"
- int start_col = wp->w_grid.cols - 3;
- int symbol = wp->w_p_fcs_chars.lastline;
-
// Last line isn't finished: Display "@@@" at the end.
- grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows,
- MAX(start_col, 0), wp->w_grid.cols, symbol, symbol, at_attr);
+ // If this would split a doublewidth char in two, we need to display "@@@@" instead
+ grid_line_start(&wp->w_grid, wp->w_grid.rows - 1);
+ int width = grid_line_getchar(MAX(wp->w_grid.cols - 3, 0), NULL) == NUL ? 4 : 3;
+ grid_line_fill(MAX(wp->w_grid.cols - width, 0), wp->w_grid.cols,
+ wp->w_p_fcs_chars.lastline, at_attr);
+ grid_line_flush();
set_empty_rows(wp, srow);
wp->w_botline = lnum;
} else {
@@ -1921,13 +2415,14 @@ static void win_update(win_T *wp, DecorProviders *providers)
} else {
if (eof) { // we hit the end of the file
wp->w_botline = buf->b_ml.ml_line_count + 1;
- long j = win_get_fill(wp, wp->w_botline);
+ int j = win_get_fill(wp, wp->w_botline);
if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) {
// Display filler text below last line. win_line() will check
// for ml_line_count+1 and only draw filler lines
- foldinfo_T info = { 0 };
- row = win_line(wp, wp->w_botline, row, wp->w_grid.rows,
- false, false, info, &line_providers, &provider_err);
+ spellvars_T zero_spv = { 0 };
+ foldinfo_T zero_foldinfo = { 0 };
+ row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, false, &zero_spv,
+ zero_foldinfo, &line_providers);
}
} else if (dollar_vcol == -1) {
wp->w_botline = lnum;
@@ -2001,12 +2496,162 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
}
+ if (nrwidth_before != wp->w_nrwidth && buf->terminal) {
+ terminal_check_size(buf->terminal);
+ }
+
// restore got_int, unless CTRL-C was hit while redrawing
if (!got_int) {
got_int = save_got_int;
}
}
+/// Scroll `line_count` lines at 'row' in window 'wp'.
+///
+/// Positive `line_count` means scrolling down, so that more space is available
+/// at 'row'. Negative `line_count` implies deleting lines at `row`.
+void win_scroll_lines(win_T *wp, int row, int line_count)
+{
+ if (!redrawing() || line_count == 0) {
+ return;
+ }
+
+ // No lines are being moved, just draw over the entire area
+ if (row + abs(line_count) >= wp->w_grid.rows) {
+ return;
+ }
+
+ if (line_count < 0) {
+ grid_del_lines(&wp->w_grid, row, -line_count,
+ wp->w_grid.rows, 0, wp->w_grid.cols);
+ } else {
+ grid_ins_lines(&wp->w_grid, row, line_count,
+ wp->w_grid.rows, 0, wp->w_grid.cols);
+ }
+}
+
+/// Call grid_fill() with columns adjusted for 'rightleft' if needed.
+/// Return the new offset.
+static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, int endrow,
+ int attr)
+{
+ int nn = off + width;
+ const int endcol = wp->w_grid.cols;
+
+ if (nn > endcol) {
+ nn = endcol;
+ }
+
+ if (wp->w_p_rl) {
+ grid_fill(&wp->w_grid, row, endrow, endcol - nn, endcol - off, c1, c2, attr);
+ } else {
+ grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr);
+ }
+
+ return nn;
+}
+
+/// Clear lines near the end of the window and mark the unused lines with "c1".
+/// Use "c2" as filler character.
+/// When "draw_margin" is true, then draw the sign/fold/number columns.
+void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl)
+{
+ assert(hl >= 0 && hl < HLF_COUNT);
+ int n = 0;
+
+ if (draw_margin) {
+ // draw the fold column
+ int fdc = compute_foldcolumn(wp, 0);
+ if (fdc > 0) {
+ n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow,
+ win_hl_attr(wp, HLF_FC));
+ }
+ // draw the sign column
+ int count = wp->w_scwidth;
+ if (count > 0) {
+ n = win_fill_end(wp, ' ', ' ', n, SIGN_WIDTH * count, row,
+ endrow, win_hl_attr(wp, HLF_SC));
+ }
+ // draw the number column
+ if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) {
+ n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow,
+ win_hl_attr(wp, HLF_N));
+ }
+ }
+
+ int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl));
+
+ const int endcol = wp->w_grid.cols;
+ if (wp->w_p_rl) {
+ grid_fill(&wp->w_grid, row, endrow, 0, endcol - 1 - n, c2, c2, attr);
+ grid_fill(&wp->w_grid, row, endrow, endcol - 1 - n, endcol - n, c1, c2, attr);
+ } else {
+ grid_fill(&wp->w_grid, row, endrow, n, endcol, c1, c2, attr);
+ }
+}
+
+/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
+/// space is available for window "wp", minus "col".
+int compute_foldcolumn(win_T *wp, int col)
+{
+ int fdc = win_fdccol_count(wp);
+ int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw;
+ int wwidth = wp->w_grid.cols;
+
+ if (fdc > wwidth - (col + wmw)) {
+ fdc = wwidth - (col + wmw);
+ }
+ return fdc;
+}
+
+/// Return the width of the 'number' and 'relativenumber' column.
+/// Caller may need to check if 'number' or 'relativenumber' is set.
+/// Otherwise it depends on 'numberwidth' and the line count.
+int number_width(win_T *wp)
+{
+ linenr_T lnum;
+
+ if (wp->w_p_rnu && !wp->w_p_nu) {
+ // cursor line shows "0"
+ lnum = wp->w_height_inner;
+ } else {
+ // cursor line shows absolute line number
+ lnum = wp->w_buffer->b_ml.ml_line_count;
+ }
+
+ if (lnum == wp->w_nrwidth_line_count) {
+ return wp->w_nrwidth_width;
+ }
+ wp->w_nrwidth_line_count = lnum;
+
+ // reset for 'statuscolumn'
+ if (*wp->w_p_stc != NUL) {
+ wp->w_statuscol_line_count = 0; // make sure width is re-estimated
+ wp->w_nrwidth_width = (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw;
+ return wp->w_nrwidth_width;
+ }
+
+ int n = 0;
+ do {
+ lnum /= 10;
+ n++;
+ } while (lnum > 0);
+
+ // 'numberwidth' gives the minimal width plus one
+ if (n < wp->w_p_nuw - 1) {
+ n = (int)wp->w_p_nuw - 1;
+ }
+
+ // If 'signcolumn' is set to 'number' and there is a sign to display, then
+ // the minimal width for the number column is 2.
+ if (n < 2 && wp->w_buffer->b_signs_with_text && wp->w_minscwidth == SCL_NUM) {
+ n = 2;
+ }
+
+ wp->w_nrwidth_width = n;
+ return n;
+}
+
/// Redraw a window later, with wp->w_redr_type >= type.
///
/// Set must_redraw only if not already set to a higher value.
@@ -2110,7 +2755,7 @@ void status_redraw_all(void)
bool is_stl_global = global_stl_height() != 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin)
+ if ((!is_stl_global && wp->w_status_height) || wp == curwin
|| wp->w_winbar_height) {
wp->w_redr_status = true;
redraw_later(wp, UPD_VALID);
@@ -2136,6 +2781,11 @@ void status_redraw_buf(buf_T *buf)
redraw_later(wp, UPD_VALID);
}
}
+ // Redraw the ruler if it is in the command line and was not marked for redraw above
+ if (p_ru && !curwin->w_status_height && !curwin->w_redr_status) {
+ redraw_cmdline = true;
+ redraw_later(curwin, UPD_VALID);
+ }
}
/// Redraw all status lines that need to be redrawn.
@@ -2195,3 +2845,48 @@ void redrawWinline(win_T *wp, linenr_T lnum)
redraw_later(wp, UPD_VALID);
}
}
+
+/// Return true if the cursor line in window "wp" may be concealed, according
+/// to the 'concealcursor' option.
+bool conceal_cursor_line(const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int c;
+
+ if (*wp->w_p_cocu == NUL) {
+ return false;
+ }
+ if (get_real_state() & MODE_VISUAL) {
+ c = 'v';
+ } else if (State & MODE_INSERT) {
+ c = 'i';
+ } else if (State & MODE_NORMAL) {
+ c = 'n';
+ } else if (State & MODE_CMDLINE) {
+ c = 'c';
+ } else {
+ return false;
+ }
+ return vim_strchr(wp->w_p_cocu, c) != NULL;
+}
+
+/// Whether cursorline is drawn in a special way
+///
+/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows.
+bool win_cursorline_standout(const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
+}
+
+/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
+/// Also when concealing is on and 'concealcursor' is not active.
+void redraw_for_cursorline(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
+ && (wp->w_p_rnu || win_cursorline_standout(wp))) {
+ // win_line() will redraw the number column and cursorline only.
+ redraw_later(wp, UPD_VALID);
+ }
+}
diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h
index c14703dfa9..565b01bcd1 100644
--- a/src/nvim/drawscreen.h
+++ b/src/nvim/drawscreen.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_DRAWSCREEN_H
-#define NVIM_DRAWSCREEN_H
+#pragma once
#include <stdbool.h>
+#include "nvim/buffer_defs.h"
#include "nvim/drawline.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
/// flags for update_screen()
/// The higher the value, the higher the priority
@@ -20,9 +20,13 @@ enum {
/// While redrawing the screen this flag is set. It means the screen size
/// ('lines' and 'rows') must not be changed.
-EXTERN bool updating_screen INIT(= 0);
+EXTERN bool updating_screen INIT( = 0);
+
+EXTERN match_T screen_search_hl INIT( = { 0 }); // used for 'hlsearch' highlight matching
+
+#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width)
+#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "drawscreen.h.generated.h"
#endif
-#endif // NVIM_DRAWSCREEN_H
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 095d73f53f..71a12ea1b0 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// edit.c: functions for Insert mode
#include <assert.h>
@@ -10,8 +7,9 @@
#include <string.h>
#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
+#include "nvim/autocmd_defs.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
@@ -26,17 +24,18 @@
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -48,11 +47,11 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
@@ -60,10 +59,10 @@
#include "nvim/terminal.h"
#include "nvim/textformat.h"
#include "nvim/textobject.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
typedef struct insert_state {
@@ -73,7 +72,7 @@ typedef struct insert_state {
int cmdchar;
int cmdchar_todo; // cmdchar to handle once in init_prompt
int startln;
- long count;
+ int count;
int c;
int lastc;
int i;
@@ -138,7 +137,7 @@ static void insert_enter(InsertState *s)
did_restart_edit = restart_edit;
// sleep before redrawing, needed for "CTRL-O :" that results in an
// error message
- check_for_delay(true);
+ msg_check_for_delay(true);
// set Insstart_orig to Insstart
update_Insstart_orig = true;
@@ -194,7 +193,7 @@ static void insert_enter(InsertState *s)
}
}
- Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
+ Insstart_textlen = linetabsize_str(get_cursor_line_ptr());
Insstart_blank_vcol = MAXCOL;
if (!did_ai) {
@@ -232,8 +231,9 @@ static void insert_enter(InsertState *s)
may_trigger_modechanged();
stop_insert_mode = false;
- // need to position cursor again when on a TAB
- if (gchar_cursor() == TAB) {
+ // need to position cursor again when on a TAB and
+ // when on a char with inline virtual text
+ if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
}
@@ -355,6 +355,7 @@ static void insert_enter(InsertState *s)
ins_apply_autocmds(EVENT_INSERTLEAVE);
}
did_cursorhold = false;
+ curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
}
static int insert_check(VimState *state)
@@ -394,6 +395,13 @@ static int insert_check(VimState *state)
Insstart_orig = Insstart;
}
+ if (curbuf->terminal) {
+ // Exit Insert mode and go to Terminal mode.
+ stop_insert_mode = true;
+ restart_edit = 'I';
+ stuffcharReadbuff(K_NOP);
+ }
+
if (stop_insert_mode && !ins_compl_active()) {
// ":stopinsert" used
s->count = 0;
@@ -523,10 +531,6 @@ static int insert_execute(VimState *state, int key)
did_cursorhold = true;
}
- if (p_hkmap && KeyTyped) {
- s->c = hkmap(s->c); // Hebrew mode mapping
- }
-
// Special handling of keys while the popup menu is visible or wanted
// and the cursor is still in the completed word. Only when there is
// a match, skip this when no matches were found.
@@ -558,10 +562,9 @@ static int insert_execute(VimState *state, int key)
if (ins_compl_accept_char(s->c)) {
// Trigger InsertCharPre.
char *str = do_insert_char_pre(s->c);
- char *p;
if (str != NULL) {
- for (p = str; *p != NUL; MB_PTR_ADV(p)) {
+ for (char *p = str; *p != NUL; MB_PTR_ADV(p)) {
ins_compl_addleader(utf_ptr2char(p));
}
xfree(str);
@@ -615,7 +618,9 @@ static int insert_execute(VimState *state, int key)
}
}
- s->c = do_digraph(s->c);
+ if (s->c != K_EVENT) {
+ s->c = do_digraph(s->c);
+ }
if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode_cmdline()) {
insert_do_complete(s);
@@ -754,7 +759,7 @@ static int insert_handle_key(InsertState *s)
case Ctrl_A:
// For ^@ the trailing ESC will end the insert, unless there is an
// error.
- if (stuff_inserted(NUL, 1L, (s->c == Ctrl_A)) == FAIL
+ if (stuff_inserted(NUL, 1, (s->c == Ctrl_A)) == FAIL
&& s->c != Ctrl_A) {
return 0; // exit insert mode
}
@@ -880,14 +885,18 @@ static int insert_handle_key(InsertState *s)
case K_EVENT: // some event
state_handle_k_event();
+ // If CTRL-G U was used apply it to the next typed key.
+ if (dont_sync_undo == kTrue) {
+ dont_sync_undo = kNone;
+ }
goto check_pum;
- case K_COMMAND: // some command
+ case K_COMMAND: // <Cmd>command<CR>
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
goto check_pum;
case K_LUA:
- map_execute_lua();
+ map_execute_lua(false);
check_pum:
// nvim_select_popupmenu_item() can be called from the handling of
@@ -1122,12 +1131,11 @@ normalchar:
if (!p_paste) {
// Trigger InsertCharPre.
char *str = do_insert_char_pre(s->c);
- char *p;
if (str != NULL) {
if (*str != NUL && stop_arrow() != FAIL) {
// Insert the new value of v:char literally.
- for (p = str; *p != NUL; MB_PTR_ADV(p)) {
+ for (char *p = str; *p != NUL; MB_PTR_ADV(p)) {
s->c = utf_ptr2char(p);
if (s->c == CAR || s->c == K_KENTER || s->c == NL) {
ins_eol(s->c);
@@ -1229,7 +1237,7 @@ static void insert_do_cindent(InsertState *s)
/// @param count repeat count for the command
///
/// @return true if a CTRL-O command caused the return (insert mode pending).
-bool edit(int cmdchar, bool startln, long count)
+bool edit(int cmdchar, bool startln, int count)
{
if (curbuf->terminal) {
if (ex_normal_busy) {
@@ -1252,12 +1260,14 @@ bool edit(int cmdchar, bool startln, long count)
// Don't allow changes in the buffer while editing the cmdline. The
// caller of getcmdline() may get confused.
// Don't allow recursive insert mode when busy with completion.
- if (textlock != 0 || ins_compl_active() || compl_busy || pum_visible()) {
+ // Allow in dummy buffers since they are only used internally
+ if (textlock != 0 || ins_compl_active() || compl_busy || pum_visible()
+ || expr_map_locked()) {
emsg(_(e_textlock));
return false;
}
- InsertState state, *s = &state;
+ InsertState s[1];
memset(s, 0, sizeof(InsertState));
s->state.execute = insert_execute;
s->state.check = insert_check;
@@ -1289,7 +1299,8 @@ void ins_redraw(bool ready)
// Trigger CursorMoved if the cursor moved. Not when the popup menu is
// visible, the command might delete it.
if (ready && has_event(EVENT_CURSORMOVEDI)
- && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)
+ && (last_cursormoved_win != curwin
+ || !equalpos(last_cursormoved, curwin->w_cursor))
&& !pum_visible()) {
// Need to update the screen first, to make sure syntax
// highlighting is correct after making a change (e.g., inserting
@@ -1302,12 +1313,13 @@ void ins_redraw(bool ready)
// getcurpos()
update_curswant();
ins_apply_autocmds(EVENT_CURSORMOVEDI);
- curwin->w_last_cursormoved = curwin->w_cursor;
+ last_cursormoved_win = curwin;
+ last_cursormoved = curwin->w_cursor;
}
- // Trigger TextChangedI if changedtick differs.
+ // Trigger TextChangedI if changedtick_i differs.
if (ready && has_event(EVENT_TEXTCHANGEDI)
- && curbuf->b_last_changedtick != buf_get_changedtick(curbuf)
+ && curbuf->b_last_changedtick_i != buf_get_changedtick(curbuf)
&& !pum_visible()) {
aco_save_T aco;
varnumber_T tick = buf_get_changedtick(curbuf);
@@ -1316,16 +1328,16 @@ void ins_redraw(bool ready)
aucmd_prepbuf(&aco, curbuf);
apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf);
aucmd_restbuf(&aco);
- curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
if (tick != buf_get_changedtick(curbuf)) { // see ins_apply_autocmds()
u_save(curwin->w_cursor.lnum,
(linenr_T)(curwin->w_cursor.lnum + 1));
}
}
- // Trigger TextChangedP if changedtick differs. When the popupmenu closes
- // TextChangedI will need to trigger for backwards compatibility, thus use
- // different b_last_changedtick* variables.
+ // Trigger TextChangedP if changedtick_pum differs. When the popupmenu
+ // closes TextChangedI will need to trigger for backwards compatibility,
+ // thus use different b_last_changedtick* variables.
if (ready && has_event(EVENT_TEXTCHANGEDP)
&& curbuf->b_last_changedtick_pum != buf_get_changedtick(curbuf)
&& pum_visible()) {
@@ -1355,13 +1367,21 @@ void ins_redraw(bool ready)
curbuf->b_changed_invalid = false;
}
+ // Trigger SafeState if nothing is pending.
+ may_trigger_safestate(ready
+ && !ins_compl_active()
+ && !pum_visible());
+
pum_check_clear();
+ show_cursor_info_later(false);
if (must_redraw) {
update_screen();
- } else if (clear_cmdline || redraw_cmdline) {
- showmode(); // clear cmdline and show mode
+ } else {
+ redraw_statuslines();
+ if (clear_cmdline || redraw_cmdline || redraw_mode) {
+ showmode(); // clear cmdline and show mode
+ }
}
- show_cursor_info(false);
setcursor();
emsg_on_display = false; // may remove error message now
}
@@ -1369,7 +1389,6 @@ void ins_redraw(bool ready)
// Handle a CTRL-V or CTRL-Q typed in Insert mode.
static void ins_ctrl_v(void)
{
- int c;
bool did_putchar = false;
// may need to redraw when no more chars available now
@@ -1384,7 +1403,7 @@ static void ins_ctrl_v(void)
add_to_showcmd_c(Ctrl_V);
// Do not include modifiers into the key for CTRL-SHIFT-V.
- c = get_literal(mod_mask & MOD_MASK_SHIFT);
+ int c = get_literal(mod_mask & MOD_MASK_SHIFT);
if (did_putchar) {
// when the line fits in 'columns' the '^' is at the start of the next
// line and will not removed by the redraw
@@ -1399,20 +1418,19 @@ static void ins_ctrl_v(void)
// Put a character directly onto the screen. It's not stored in a buffer.
// Used while handling CTRL-K, CTRL-V, etc. in Insert mode.
static int pc_status;
-#define PC_STATUS_UNSET 0 // pc_bytes was not set
-#define PC_STATUS_RIGHT 1 // right half of double-wide char
-#define PC_STATUS_LEFT 2 // left half of double-wide char
-#define PC_STATUS_SET 3 // pc_bytes was filled
-static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes
+#define PC_STATUS_UNSET 0 // nothing was put on screen
+#define PC_STATUS_RIGHT 1 // right half of double-wide char
+#define PC_STATUS_LEFT 2 // left half of double-wide char
+#define PC_STATUS_SET 3 // pc_schar was filled
+static schar_T pc_schar; // saved char
static int pc_attr;
static int pc_row;
static int pc_col;
void edit_putchar(int c, bool highlight)
{
- int attr;
-
if (curwin->w_grid_alloc.chars != NULL || default_grid.chars != NULL) {
+ int attr;
update_topline(curwin); // just in case w_topline isn't valid
validate_cursor();
if (highlight) {
@@ -1421,30 +1439,34 @@ void edit_putchar(int c, bool highlight)
attr = 0;
}
pc_row = curwin->w_wrow;
- pc_col = 0;
pc_status = PC_STATUS_UNSET;
+ grid_line_start(&curwin->w_grid, pc_row);
if (curwin->w_p_rl) {
- pc_col += curwin->w_grid.cols - 1 - curwin->w_wcol;
- const int fix_col = grid_fix_col(&curwin->w_grid, pc_col, pc_row);
+ pc_col = curwin->w_grid.cols - 1 - curwin->w_wcol;
- if (fix_col != pc_col) {
- grid_putchar(&curwin->w_grid, ' ', pc_row, fix_col, attr);
+ if (grid_line_getchar(pc_col, NULL) == NUL) {
+ grid_line_put_schar(pc_col - 1, schar_from_ascii(' '), attr);
curwin->w_wcol--;
pc_status = PC_STATUS_RIGHT;
}
} else {
- pc_col += curwin->w_wcol;
- if (grid_lefthalve(&curwin->w_grid, pc_row, pc_col)) {
+ pc_col = curwin->w_wcol;
+
+ if (grid_line_getchar(pc_col + 1, NULL) == NUL) {
+ // pc_col is the left half of a double-width char
pc_status = PC_STATUS_LEFT;
}
}
// save the character to be able to put it back
if (pc_status == PC_STATUS_UNSET) {
- grid_getbytes(&curwin->w_grid, pc_row, pc_col, (char *)pc_bytes, &pc_attr);
+ pc_schar = grid_line_getchar(pc_col, &pc_attr);
pc_status = PC_STATUS_SET;
}
- grid_putchar(&curwin->w_grid, c, pc_row, pc_col, attr);
+
+ char buf[MB_MAXCHAR + 1];
+ grid_line_puts(pc_col, buf, utf_char2bytes(c, buf), attr);
+ grid_line_flush();
}
}
@@ -1470,10 +1492,9 @@ char *prompt_text(void)
static void init_prompt(int cmdchar_todo)
{
char *prompt = prompt_text();
- char *text;
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- text = get_cursor_line_ptr();
+ char *text = get_cursor_line_ptr();
if (strncmp(text, prompt, strlen(prompt)) != 0) {
// prompt is missing, insert it or append a line with it
if (*text == NUL) {
@@ -1483,7 +1504,7 @@ static void init_prompt(int cmdchar_todo)
}
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
coladvance(MAXCOL);
- changed_bytes(curbuf->b_ml.ml_line_count, 0);
+ inserted_bytes(curbuf->b_ml.ml_line_count, 0, 0, (colnr_T)strlen(prompt));
}
// Insert always starts after the prompt, allow editing text after it.
@@ -1517,29 +1538,32 @@ bool prompt_curpos_editable(void)
// Undo the previous edit_putchar().
void edit_unputchar(void)
{
- if (pc_status != PC_STATUS_UNSET && pc_row >= msg_scrolled) {
+ if (pc_status != PC_STATUS_UNSET) {
if (pc_status == PC_STATUS_RIGHT) {
curwin->w_wcol++;
}
if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) {
redrawWinline(curwin, curwin->w_cursor.lnum);
} else {
- grid_puts(&curwin->w_grid, (char *)pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr);
+ // TODO(bfredl): this could be smarter and also handle the dubyawidth case
+ grid_line_start(&curwin->w_grid, pc_row);
+ grid_line_put_schar(pc_col, pc_schar, pc_attr);
+ grid_line_flush();
}
}
}
-// Called when p_dollar is set: display a '$' at the end of the changed text
-// Only works when cursor is in the line that changes.
-void display_dollar(colnr_T col)
+/// Called when "$" is in 'cpoptions': display a '$' at the end of the changed
+/// text. Only works when cursor is in the line that changes.
+void display_dollar(colnr_T col_arg)
{
- colnr_T save_col;
+ colnr_T col = col_arg < 0 ? 0 : col_arg;
if (!redrawing()) {
return;
}
- save_col = curwin->w_cursor.col;
+ colnr_T save_col = curwin->w_cursor.col;
curwin->w_cursor.col = col;
// If on the last byte of a multi-byte move to the first byte.
@@ -1574,16 +1598,9 @@ void undisplay_dollar(void)
/// @param call_changed_bytes call changed_bytes()
void change_indent(int type, int amount, int round, int replaced, int call_changed_bytes)
{
- int vcol;
- int last_vcol;
int insstart_less; // reduction for Insstart.col
- int new_cursor_col;
- char *ptr;
- int save_p_list;
- int start_col;
- colnr_T vc;
colnr_T orig_col = 0; // init for GCC
- char *new_line, *orig_line = NULL; // init for GCC
+ char *orig_line = NULL; // init for GCC
// MODE_VREPLACE state needs to know what the line was like before changing
if (State & VREPLACE_FLAG) {
@@ -1592,18 +1609,18 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
}
// for the following tricks we don't want list mode
- save_p_list = curwin->w_p_list;
+ int save_p_list = curwin->w_p_list;
curwin->w_p_list = false;
- vc = getvcol_nolist(&curwin->w_cursor);
- vcol = vc;
+ colnr_T vc = getvcol_nolist(&curwin->w_cursor);
+ int vcol = vc;
// For Replace mode we need to fix the replace stack later, which is only
// possible when the cursor is in the indent. Remember the number of
// characters before the cursor if it's possible.
- start_col = curwin->w_cursor.col;
+ int start_col = curwin->w_cursor.col;
// determine offset from first non-blank
- new_cursor_col = curwin->w_cursor.col;
+ int new_cursor_col = curwin->w_cursor.col;
beginline(BL_WHITE);
new_cursor_col -= curwin->w_cursor.col;
@@ -1656,8 +1673,8 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol);
// Advance the cursor until we reach the right screen column.
- last_vcol = 0;
- ptr = get_cursor_line_ptr();
+ int last_vcol = 0;
+ char *ptr = get_cursor_line_ptr();
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr);
while (cts.cts_vcol <= (int)curwin->w_virtcol) {
@@ -1699,7 +1716,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
curwin->w_cursor.col = (colnr_T)new_cursor_col;
}
curwin->w_set_curswant = true;
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
// May have to adjust the start of the insert.
if (State & MODE_INSERT) {
@@ -1742,7 +1759,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
// then put it back again the way we wanted it.
if (State & VREPLACE_FLAG) {
// Save new line
- new_line = xstrdup(get_cursor_line_ptr());
+ char *new_line = xstrdup(get_cursor_line_ptr());
// We only put back the new line up to the cursor
new_line[curwin->w_cursor.col] = NUL;
@@ -1847,9 +1864,7 @@ static bool del_char_after_col(int limit_col)
/// @param no_simplify do not include modifiers into the key
int get_literal(bool no_simplify)
{
- int cc;
int nc;
- int i;
bool hex = false;
bool octal = false;
int unicode = 0;
@@ -1859,9 +1874,9 @@ int get_literal(bool no_simplify)
}
no_mapping++; // don't map the next key hits
- cc = 0;
- i = 0;
- for (;;) {
+ int cc = 0;
+ int i = 0;
+ while (true) {
nc = plain_vgetc();
if (!no_simplify) {
nc = merge_modifiers(nc, &mod_mask);
@@ -1949,9 +1964,6 @@ int get_literal(bool no_simplify)
/// @param ctrlv `c` was typed after CTRL-V
static void insert_special(int c, int allow_modmask, int ctrlv)
{
- char *p;
- int len;
-
// Special function key, translate into "<Key>". Up to the last '>' is
// inserted with ins_str(), so as not to replace characters in replace
// mode.
@@ -1961,8 +1973,8 @@ static void insert_special(int c, int allow_modmask, int ctrlv)
allow_modmask = true;
}
if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) {
- p = (char *)get_special_key_name(c, mod_mask);
- len = (int)strlen(p);
+ char *p = get_special_key_name(c, mod_mask);
+ int len = (int)strlen(p);
c = (uint8_t)p[len - 1];
if (len > 2) {
if (stop_arrow() == FAIL) {
@@ -2041,7 +2053,7 @@ void insertchar(int c, int flags, int second_indent)
if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0
&& (force_format || virtcol > (colnr_T)textwidth)) {
- do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0);
+ do_internal = (fex_format(curwin->w_cursor.lnum, 1, c) != 0);
// It may be required to save for undo again, e.g. when setline()
// was called.
ins_need_undo = true;
@@ -2057,7 +2069,7 @@ void insertchar(int c, int flags, int second_indent)
// Check whether this character should end a comment.
if (did_ai && c == end_comment_pending) {
- char_u lead_end[COM_MAX_LEN]; // end-comment string
+ char lead_end[COM_MAX_LEN]; // end-comment string
// Need to remove existing (middle) comment leader and insert end
// comment leader. First, check what comment leader we can find.
@@ -2068,7 +2080,7 @@ void insertchar(int c, int flags, int second_indent)
while (*p && p[-1] != ':') { // find end of middle flags
p++;
}
- int middle_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
+ int middle_len = (int)copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
// Don't count trailing white space for middle_len
while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) {
middle_len--;
@@ -2078,7 +2090,7 @@ void insertchar(int c, int flags, int second_indent)
while (*p && p[-1] != ':') { // find end of end flags
p++;
}
- int end_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
+ int end_len = (int)copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
// Skip white space before the cursor
i = curwin->w_cursor.col;
@@ -2089,13 +2101,13 @@ void insertchar(int c, int flags, int second_indent)
i -= middle_len;
// Check some expected things before we go on
- if (i >= 0 && lead_end[end_len - 1] == end_comment_pending) {
+ if (i >= 0 && (uint8_t)lead_end[end_len - 1] == end_comment_pending) {
// Backspace over all the stuff we want to replace
backspace_until_column(i);
// Insert the end-comment string, except for the last
// character, which will get inserted as normal later.
- ins_bytes_len((char *)lead_end, (size_t)(end_len - 1));
+ ins_bytes_len(lead_end, (size_t)(end_len - 1));
}
}
}
@@ -2146,9 +2158,6 @@ void insertchar(int c, int flags, int second_indent)
|| (virtcol += byte2cells((uint8_t)buf[i - 1])) < (colnr_T)textwidth)
&& !(!no_abbr && !vim_iswordc(c) && vim_iswordc((uint8_t)buf[i - 1]))) {
c = vgetc();
- if (p_hkmap && KeyTyped) {
- c = hkmap(c); // Hebrew mode mapping
- }
buf[i++] = (char)c;
}
@@ -2169,7 +2178,7 @@ void insertchar(int c, int flags, int second_indent)
int cc;
if ((cc = utf_char2len(c)) > 1) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
utf_char2bytes(c, buf);
buf[cc] = NUL;
@@ -2260,7 +2269,7 @@ int stop_arrow(void)
// right, except when nothing was inserted yet.
update_Insstart_orig = false;
}
- Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
+ Insstart_textlen = linetabsize_str(get_cursor_line_ptr());
if (u_save_cursor() == OK) {
arrow_used = false;
@@ -2294,26 +2303,24 @@ int stop_arrow(void)
/// @param nomove <c-\><c-o>, don't move cursor
static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
{
- int cc;
- char *ptr;
-
stop_redo_ins();
replace_flush(); // abandon replace stack
// Save the inserted text for later redo with ^@ and CTRL-A.
// Don't do it when "restart_edit" was set and nothing was inserted,
// otherwise CTRL-O w and then <Left> will clear "last_insert".
- ptr = get_inserted();
- if (did_restart_edit == 0 || (ptr != NULL
- && (int)strlen(ptr) > new_insert_skip)) {
+ char *ptr = get_inserted();
+ int added = ptr == NULL ? 0 : (int)strlen(ptr) - new_insert_skip;
+ if (did_restart_edit == 0 || added > 0) {
xfree(last_insert);
last_insert = ptr;
- last_insert_skip = new_insert_skip;
+ last_insert_skip = added < 0 ? 0 : new_insert_skip;
} else {
xfree(ptr);
}
if (!arrow_used && end_insert_pos != NULL) {
+ int cc;
// Auto-format now. It may seem strange to do this when stopping an
// insertion (or moving the cursor), but it's required when appending
// a line and having it end in a space. But only do it when something
@@ -2365,7 +2372,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
curwin->w_cursor = *end_insert_pos;
check_cursor_col(); // make sure it is not past the line
- for (;;) {
+ while (true) {
if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) {
curwin->w_cursor.col--;
}
@@ -2413,16 +2420,14 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
// Used for the replace command.
void set_last_insert(int c)
{
- char *s;
-
xfree(last_insert);
last_insert = xmalloc(MB_MAXBYTES * 3 + 5);
- s = last_insert;
+ char *s = last_insert;
// Use the CTRL-V only when entering a special char
if (c < ' ' || c == DEL) {
*s++ = Ctrl_V;
}
- s = (char *)add_char2buf(c, (char_u *)s);
+ s = add_char2buf(c, s);
*s++ = ESC;
*s++ = NUL;
last_insert_skip = 0;
@@ -2449,15 +2454,16 @@ void beginline(int flags)
curwin->w_cursor.coladd = 0;
if (flags & (BL_WHITE | BL_SOL)) {
- char_u *ptr;
+ char *ptr;
- for (ptr = (char_u *)get_cursor_line_ptr(); ascii_iswhite(*ptr)
+ for (ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr)
&& !((flags & BL_FIX) && ptr[1] == NUL); ptr++) {
curwin->w_cursor.col++;
}
}
curwin->w_set_curswant = true;
}
+ adjust_skipcol();
}
// oneright oneleft cursor_down cursor_up
@@ -2469,15 +2475,14 @@ void beginline(int flags)
int oneright(void)
{
char *ptr;
- int l;
if (virtual_active()) {
pos_T prevpos = curwin->w_cursor;
// Adjust for multi-wide char (excluding TAB)
ptr = get_cursor_pos_ptr();
- coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) ?
- ptr2cells(ptr) : 1));
+ coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr)))
+ ? ptr2cells(ptr) : 1));
curwin->w_set_curswant = true;
// Return OK if the cursor moved, FAIL otherwise (at window edge).
return (prevpos.col != curwin->w_cursor.col
@@ -2489,7 +2494,7 @@ int oneright(void)
return FAIL; // already at the very end
}
- l = utfc_ptr2len(ptr);
+ int l = utfc_ptr2len(ptr);
// move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
// contains "onemore".
@@ -2499,13 +2504,13 @@ int oneright(void)
curwin->w_cursor.col += l;
curwin->w_set_curswant = true;
+ adjust_skipcol();
return OK;
}
int oneleft(void)
{
if (virtual_active()) {
- int width;
int v = getviscol();
if (v == 0) {
@@ -2513,8 +2518,8 @@ int oneleft(void)
}
// We might get stuck on 'showbreak', skip over it.
- width = 1;
- for (;;) {
+ int width = 1;
+ while (true) {
coladvance(v - width);
// getviscol() is slow, skip it when 'showbreak' is empty,
// 'breakindent' is not set and there are no multi-byte
@@ -2534,6 +2539,7 @@ int oneleft(void)
}
curwin->w_set_curswant = true;
+ adjust_skipcol();
return OK;
}
@@ -2547,20 +2553,16 @@ int oneleft(void)
// if the character on the left of the current cursor is a multi-byte
// character, move to its first byte
mb_adjust_cursor();
+ adjust_skipcol();
return OK;
}
/// Move the cursor up "n" lines in window "wp".
/// Takes care of closed folds.
-/// Returns the new cursor line or zero for failure.
-linenr_T cursor_up_inner(win_T *wp, long n)
+void cursor_up_inner(win_T *wp, linenr_T n)
{
linenr_T lnum = wp->w_cursor.lnum;
- // This fails if the cursor is already in the first line.
- if (lnum <= 1) {
- return 0;
- }
if (n >= lnum) {
lnum = 1;
} else if (hasAnyFolding(wp)) {
@@ -2586,19 +2588,20 @@ linenr_T cursor_up_inner(win_T *wp, long n)
lnum = 1;
}
} else {
- lnum -= (linenr_T)n;
+ lnum -= n;
}
wp->w_cursor.lnum = lnum;
- return lnum;
}
/// @param upd_topline When true: update topline
-int cursor_up(long n, int upd_topline)
+int cursor_up(linenr_T n, int upd_topline)
{
- if (n > 0 && cursor_up_inner(curwin, n) == 0) {
+ // This fails if the cursor is already in the first line.
+ if (n > 0 && curwin->w_cursor.lnum <= 1) {
return FAIL;
}
+ cursor_up_inner(curwin, n);
// try to advance to the column we want to be at
coladvance(curwin->w_curswant);
@@ -2612,18 +2615,11 @@ int cursor_up(long n, int upd_topline)
/// Move the cursor down "n" lines in window "wp".
/// Takes care of closed folds.
-/// Returns the new cursor line or zero for failure.
-linenr_T cursor_down_inner(win_T *wp, long n)
+void cursor_down_inner(win_T *wp, int n)
{
linenr_T lnum = wp->w_cursor.lnum;
linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
- // Move to last line of fold, will fail if it's the end-of-file.
- (void)hasFoldingWin(wp, lnum, NULL, &lnum, true, NULL);
- // This fails if the cursor is already in the last line.
- if (lnum >= line_count) {
- return FAIL;
- }
if (lnum + n >= line_count) {
lnum = line_count;
} else if (hasAnyFolding(wp)) {
@@ -2631,6 +2627,7 @@ linenr_T cursor_down_inner(win_T *wp, long n)
// count each sequence of folded lines as one logical line
while (n--) {
+ // Move to last line of fold, will fail if it's the end-of-file.
if (hasFoldingWin(wp, lnum, NULL, &last, true, NULL)) {
lnum = last + 1;
} else {
@@ -2648,15 +2645,16 @@ linenr_T cursor_down_inner(win_T *wp, long n)
}
wp->w_cursor.lnum = lnum;
- return lnum;
}
/// @param upd_topline When true: update topline
-int cursor_down(long n, int upd_topline)
+int cursor_down(int n, int upd_topline)
{
- if (n > 0 && cursor_down_inner(curwin, n) == 0) {
+ // This fails if the cursor is already in the last line.
+ if (n > 0 && curwin->w_cursor.lnum >= curwin->w_buffer->b_ml.ml_line_count) {
return FAIL;
}
+ cursor_down_inner(curwin, n);
// try to advance to the column we want to be at
coladvance(curwin->w_curswant);
@@ -2675,14 +2673,12 @@ int cursor_down(long n, int upd_topline)
/// @param c Command character to be inserted
/// @param count Repeat this many times
/// @param no_esc Don't add an ESC at the end
-int stuff_inserted(int c, long count, int no_esc)
+int stuff_inserted(int c, int count, int no_esc)
{
char *esc_ptr;
- char *ptr;
- char *last_ptr;
char last = NUL;
- ptr = (char *)get_last_insert();
+ char *ptr = get_last_insert();
if (ptr == NULL) {
emsg(_(e_noinstext));
return FAIL;
@@ -2700,7 +2696,7 @@ int stuff_inserted(int c, long count, int no_esc)
// when the last char is either "0" or "^" it will be quoted if no ESC
// comes after it OR if it will inserted more than once and "ptr"
// starts with ^D. -- Acevedo
- last_ptr = (esc_ptr ? esc_ptr : ptr + strlen(ptr)) - 1;
+ char *last_ptr = (esc_ptr ? esc_ptr : ptr + strlen(ptr)) - 1;
if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^')
&& (no_esc || (*ptr == Ctrl_D && count > 1))) {
last = *last_ptr;
@@ -2708,7 +2704,7 @@ int stuff_inserted(int c, long count, int no_esc)
}
do {
- stuffReadbuff((const char *)ptr);
+ stuffReadbuff(ptr);
// A trailing "0" is inserted as "<C-V>048", "^" as "<C-V>^".
if (last) {
stuffReadbuff(last == '0' ? "\026\060\064\070" : "\026^");
@@ -2731,27 +2727,24 @@ int stuff_inserted(int c, long count, int no_esc)
return OK;
}
-char_u *get_last_insert(void)
+char *get_last_insert(void)
FUNC_ATTR_PURE
{
if (last_insert == NULL) {
return NULL;
}
- return (char_u *)last_insert + last_insert_skip;
+ return last_insert + last_insert_skip;
}
// Get last inserted string, and remove trailing <Esc>.
// Returns pointer to allocated memory (must be freed) or NULL.
char *get_last_insert_save(void)
{
- char *s;
- int len;
-
if (last_insert == NULL) {
return NULL;
}
- s = xstrdup(last_insert + last_insert_skip);
- len = (int)strlen(s);
+ char *s = xstrdup(last_insert + last_insert_skip);
+ int len = (int)strlen(s);
if (len > 0 && s[len - 1] == ESC) { // remove trailing ESC
s[len - 1] = NUL;
}
@@ -2793,11 +2786,11 @@ static bool echeck_abbr(int c)
// that the NL replaced. The extra one stores the characters after the cursor
// that were deleted (always white space).
-static char_u *replace_stack = NULL;
+static uint8_t *replace_stack = NULL;
static ssize_t replace_stack_nr = 0; // next entry in replace stack
static ssize_t replace_stack_len = 0; // max. number of entries
-/// Push character that is replaced onto the the replace stack.
+/// Push character that is replaced onto the replace stack.
///
/// replace_offset is normally 0, in which case replace_push will add a new
/// character at the end of the stack. If replace_offset is not 0, that many
@@ -2814,11 +2807,11 @@ void replace_push(int c)
replace_stack_len += 50;
replace_stack = xrealloc(replace_stack, (size_t)replace_stack_len);
}
- char_u *p = replace_stack + replace_stack_nr - replace_offset;
+ uint8_t *p = replace_stack + replace_stack_nr - replace_offset;
if (replace_offset) {
memmove(p + 1, p, (size_t)replace_offset);
}
- *p = (char_u)c;
+ *p = (uint8_t)c;
replace_stack_nr++;
}
@@ -2829,9 +2822,8 @@ void replace_push(int c)
int replace_push_mb(char *p)
{
int l = utfc_ptr2len(p);
- int j;
- for (j = l - 1; j >= 0; j--) {
+ for (int j = l - 1; j >= 0; j--) {
replace_push(p[j]);
}
return l;
@@ -2881,14 +2873,12 @@ static void replace_pop_ins(void)
static void mb_replace_pop_ins(int cc)
{
int n;
- char_u buf[MB_MAXBYTES + 1];
- int i;
- int c;
+ uint8_t buf[MB_MAXBYTES + 1];
if ((n = MB_BYTE2LEN(cc)) > 1) {
- buf[0] = (char_u)cc;
- for (i = 1; i < n; i++) {
- buf[i] = (char_u)replace_pop();
+ buf[0] = (uint8_t)cc;
+ for (int i = 1; i < n; i++) {
+ buf[i] = (uint8_t)replace_pop();
}
ins_bytes_len((char *)buf, (size_t)n);
} else {
@@ -2896,8 +2886,8 @@ static void mb_replace_pop_ins(int cc)
}
// Handle composing chars.
- for (;;) {
- c = replace_pop();
+ while (true) {
+ int c = replace_pop();
if (c == -1) { // stack empty
break;
}
@@ -2907,16 +2897,16 @@ static void mb_replace_pop_ins(int cc)
break;
}
- buf[0] = (char_u)c;
+ buf[0] = (uint8_t)c;
assert(n > 1);
- for (i = 1; i < n; i++) {
- buf[i] = (char_u)replace_pop();
+ for (int i = 1; i < n; i++) {
+ buf[i] = (uint8_t)replace_pop();
}
if (utf_iscomposing(utf_ptr2char((char *)buf))) {
ins_bytes_len((char *)buf, (size_t)n);
} else {
// Not a composing char, put it back.
- for (i = n - 1; i >= 0; i--) {
+ for (int i = n - 1; i >= 0; i--) {
replace_push(buf[i]);
}
break;
@@ -2942,18 +2932,13 @@ static void replace_flush(void)
// using composing characters, use del_char_after_col() instead of del_char().
static void replace_do_bs(int limit_col)
{
- int cc;
- int orig_len = 0;
- int ins_len;
- int orig_vcols = 0;
colnr_T start_vcol;
- char *p;
- int i;
- int vcol;
const int l_State = State;
- cc = replace_pop();
+ int cc = replace_pop();
if (cc > 0) {
+ int orig_len = 0;
+ int orig_vcols = 0;
if (l_State & VREPLACE_FLAG) {
// Get the number of screen cells used by the character we are
// going to delete.
@@ -2969,10 +2954,10 @@ static void replace_do_bs(int limit_col)
if (l_State & VREPLACE_FLAG) {
// Get the number of screen cells used by the inserted characters
- p = get_cursor_pos_ptr();
- ins_len = (int)strlen(p) - orig_len;
- vcol = start_vcol;
- for (i = 0; i < ins_len; i++) {
+ char *p = get_cursor_pos_ptr();
+ int ins_len = (int)strlen(p) - orig_len;
+ int vcol = start_vcol;
+ for (int i = 0; i < ins_len; i++) {
vcol += win_chartabsize(curwin, p + i, vcol);
i += utfc_ptr2len(p) - 1;
}
@@ -3131,7 +3116,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
return true;
}
- if (keytyped == get_special_key_code((char_u *)look + 1)) {
+ if (keytyped == get_special_key_code(look + 1)) {
return true;
}
}
@@ -3223,105 +3208,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
return false;
}
-// Map Hebrew keyboard when in hkmap mode.
-int hkmap(int c)
- FUNC_ATTR_PURE
-{
- if (p_hkmapp) { // phonetic mapping, by Ilya Dogolazky
- enum {
- hALEF = 0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD,
- KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN,
- PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV,
- };
- static char_u map[26] = {
- (char_u)hALEF, // a
- (char_u)BET, // b
- (char_u)hKAF, // c
- (char_u)DALET, // d
- (char_u) - 1, // e
- (char_u)PEIsofit, // f
- (char_u)GIMEL, // g
- (char_u)HEI, // h
- (char_u)IUD, // i
- (char_u)HET, // j
- (char_u)KOF, // k
- (char_u)LAMED, // l
- (char_u)MEM, // m
- (char_u)NUN, // n
- (char_u)SAMEH, // o
- (char_u)PEI, // p
- (char_u) - 1, // q
- (char_u)RESH, // r
- (char_u)ZAIN, // s
- (char_u)TAV, // t
- (char_u)TET, // u
- (char_u)VAV, // v
- (char_u)hSHIN, // w
- (char_u) - 1, // x
- (char_u)AIN, // y
- (char_u)ZADI, // z
- };
-
- if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') {
- return (int)(map[CHAR_ORD(c)] - 1 + p_aleph);
- } else if (c == 'x') { // '-1'='sofit'
- return 'X';
- } else if (c == 'q') {
- return '\''; // {geresh}={'}
- } else if (c == 246) {
- return ' '; // \"o --> ' ' for a german keyboard
- } else if (c == 228) {
- return ' '; // \"a --> ' ' -- / --
- } else if (c == 252) {
- return ' '; // \"u --> ' ' -- / --
- } else if (c >= 'a' && c <= 'z') {
- // NOTE: islower() does not do the right thing for us on Linux so we
- // do this the same was as 5.7 and previous, so it works correctly on
- // all systems. Specifically, the e.g. Delete and Arrow keys are
- // munged and won't work if e.g. searching for Hebrew text.
- return (int)(map[CHAR_ORD_LOW(c)] + p_aleph);
- } else {
- return c;
- }
- } else {
- switch (c) {
- case '`':
- return ';';
- case '/':
- return '.';
- case '\'':
- return ',';
- case 'q':
- return '/';
- case 'w':
- return '\'';
-
- // Hebrew letters - set offset from 'a'
- case ',':
- c = '{'; break;
- case '.':
- c = 'v'; break;
- case ';':
- c = 't'; break;
- default: {
- static char_u str[] = "zqbcxlsjphmkwonu ydafe rig";
-
- if (c < 'a' || c > 'z') {
- return c;
- }
- c = str[CHAR_ORD_LOW(c)];
- break;
- }
- }
-
- return (int)(CHAR_ORD_LOW(c) + p_aleph);
- }
-}
-
static void ins_reg(void)
{
bool need_redraw = false;
- int regname;
int literally = 0;
int vis_active = VIsual_active;
@@ -3339,7 +3228,7 @@ static void ins_reg(void)
// deleted when ESC is hit.
no_mapping++;
allow_keys++;
- regname = plain_vgetc();
+ int regname = plain_vgetc();
LANGMAP_ADJUST(regname, true);
if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) {
// Get a third key for literal register insertion
@@ -3410,8 +3299,6 @@ static void ins_reg(void)
// CTRL-G commands in Insert mode.
static void ins_ctrl_g(void)
{
- int c;
-
// Right after CTRL-X the cursor will be after the ruler.
setcursor();
@@ -3419,7 +3306,7 @@ static void ins_ctrl_g(void)
// deleted when ESC is hit.
no_mapping++;
allow_keys++;
- c = plain_vgetc();
+ int c = plain_vgetc();
no_mapping--;
allow_keys--;
switch (c) {
@@ -3455,6 +3342,10 @@ static void ins_ctrl_g(void)
dont_sync_undo = kNone;
break;
+ case ESC:
+ // Esc after CTRL-G cancels it.
+ break;
+
// Unknown CTRL-G command, reserved for future expansion.
default:
vim_beep(BO_CTRLG);
@@ -3487,7 +3378,7 @@ static void ins_ctrl_hat(void)
/// @param nomove when true, don't move the cursor
///
/// @return true when leaving insert mode, false when repeating the insert.
-static bool ins_esc(long *count, int cmdchar, bool nomove)
+static bool ins_esc(int *count, int cmdchar, bool nomove)
FUNC_ATTR_NONNULL_ARG(1)
{
static bool disabled_redraw = false;
@@ -3562,6 +3453,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
}
} else {
curwin->w_cursor.col--;
+ curwin->w_valid &= ~(VALID_WCOL|VALID_VIRTCOL);
// Correct cursor for multi-byte character.
mb_adjust_cursor();
}
@@ -3569,8 +3461,9 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
State = MODE_NORMAL;
may_trigger_modechanged();
- // need to position cursor again when on a TAB
- if (gchar_cursor() == TAB) {
+ // need to position cursor again when on a TAB and
+ // when on a char with inline virtual text
+ if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
}
@@ -3581,14 +3474,15 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
// Otherwise remove the mode message.
if (reg_recording != 0 || restart_edit != NUL) {
showmode();
- } else if (p_smd && (got_int || !skip_showmode())) {
- msg("");
+ } else if (p_smd && (got_int || !skip_showmode())
+ && !(p_ch == 0 && !ui_has(kUIMessages))) {
+ msg("", 0);
}
// Exit Insert mode
return true;
}
-// Toggle language: hkmap and revins_on.
+// Toggle language: revins_on.
// Move to end of reverse inserted text.
static void ins_ctrl_(void)
{
@@ -3607,7 +3501,6 @@ static void ins_ctrl_(void)
} else {
revins_scol = -1;
}
- p_hkmap = curwin->w_p_rl ^ p_ri; // be consistent!
showmode();
}
@@ -3659,8 +3552,9 @@ static bool ins_start_select(int c)
// <Insert> key in Insert mode: toggle insert/replace mode.
static void ins_insert(int replaceState)
{
- set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG) ? "i" :
- replaceState == MODE_VREPLACE ? "v" : "r"), 1);
+ set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG)
+ ? "i"
+ : replaceState == MODE_VREPLACE ? "v" : "r"), 1);
ins_apply_autocmds(EVENT_INSERTCHANGE);
if (State & REPLACE_FLAG) {
State = MODE_INSERT | (State & MODE_LANGMAP);
@@ -3676,6 +3570,7 @@ static void ins_insert(int replaceState)
// Pressed CTRL-O in Insert mode.
static void ins_ctrl_o(void)
{
+ restart_VIsual_select = 0;
if (State & VREPLACE_FLAG) {
restart_edit = 'V';
} else if (State & REPLACE_FLAG) {
@@ -3786,15 +3681,9 @@ static void ins_bs_one(colnr_T *vcolp)
static bool ins_bs(int c, int mode, int *inserted_space_p)
FUNC_ATTR_NONNULL_ARG(3)
{
- linenr_T lnum;
int cc;
int temp = 0; // init for GCC
- colnr_T save_col;
- colnr_T mincol;
bool did_backspace = false;
- int in_indent;
- int oldState;
- int cpc[MAX_MCO]; // composing characters
bool call_fix_indent = false;
// can't delete anything in an empty file
@@ -3818,7 +3707,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (stop_arrow() == FAIL) {
return false;
}
- in_indent = inindent(0);
+ int in_indent = inindent(0);
if (in_indent) {
can_cindent = false;
}
@@ -3844,7 +3733,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// Delete newline!
if (curwin->w_cursor.col == 0) {
- lnum = Insstart.lnum;
+ linenr_T lnum = Insstart.lnum;
if (curwin->w_cursor.lnum == lnum || revins_on) {
if (u_save((linenr_T)(curwin->w_cursor.lnum - 2),
(linenr_T)(curwin->w_cursor.lnum + 1)) == FAIL) {
@@ -3875,7 +3764,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// again when auto-formatting.
if (has_format_option(FO_AUTO)
&& has_format_option(FO_WHITE_PAR)) {
- char *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true);
+ char *ptr = ml_get_buf_mut(curbuf, curwin->w_cursor.lnum);
int len;
len = (int)strlen(ptr);
@@ -3900,11 +3789,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// Do the next ins_char() in MODE_NORMAL state, to
// prevent ins_char() from replacing characters and
// avoiding showmatch().
- oldState = State;
+ int oldState = State;
State = MODE_NORMAL;
// restore characters (blanks) deleted after cursor
while (cc > 0) {
- save_col = curwin->w_cursor.col;
+ colnr_T save_col = curwin->w_cursor.col;
mb_replace_pop_ins(cc);
curwin->w_cursor.col = save_col;
cc = replace_pop();
@@ -3920,12 +3809,12 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (revins_on) { // put cursor on last inserted char
dec_cursor();
}
- mincol = 0;
+ colnr_T mincol = 0;
// keep indent
if (mode == BACKSPACE_LINE
&& (curbuf->b_p_ai || cindent_on())
&& !revins_on) {
- save_col = curwin->w_cursor.col;
+ colnr_T save_col = curwin->w_cursor.col;
beginline(BL_WHITE);
if (curwin->w_cursor.col < save_col) {
mincol = curwin->w_cursor.col;
@@ -3944,22 +3833,20 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
&& (*(get_cursor_pos_ptr() - 1) == TAB
|| (*(get_cursor_pos_ptr() - 1) == ' '
&& (!*inserted_space_p || arrow_used)))))) {
- int ts;
colnr_T vcol;
colnr_T want_vcol;
- colnr_T start_vcol;
*inserted_space_p = false;
// Compute the virtual column where we want to be. Since
// 'showbreak' may get in the way, need to get the last column of
// the previous character.
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
- start_vcol = vcol;
+ colnr_T start_vcol = vcol;
dec_cursor();
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
inc_cursor();
if (p_sta && in_indent) {
- ts = get_sw_value(curbuf);
+ int ts = get_sw_value(curbuf);
want_vcol = (want_vcol / ts) * ts;
} else {
want_vcol = tabstop_start(want_vcol,
@@ -3999,7 +3886,6 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
}
} else {
// Delete up to starting point, start of line or previous word.
- int prev_cclass = 0;
int cclass = mb_get_class(get_cursor_pos_ptr());
do {
@@ -4008,7 +3894,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
}
cc = gchar_cursor();
// look multi-byte character class
- prev_cclass = cclass;
+ int prev_cclass = cclass;
cclass = mb_get_class(get_cursor_pos_ptr());
if (mode == BACKSPACE_WORD && !ascii_isspace(cc)) { // start of word?
mode = BACKSPACE_WORD_NOT_SPACE;
@@ -4026,15 +3912,15 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (State & REPLACE_FLAG) {
replace_do_bs(-1);
} else {
- const int l_p_deco = p_deco;
- if (l_p_deco) {
- (void)utfc_ptr2char(get_cursor_pos_ptr(), cpc);
+ bool has_composing = false;
+ if (p_deco) {
+ char *p0 = get_cursor_pos_ptr();
+ has_composing = utf_composinglike(p0, p0 + utf_ptr2len(p0));
}
(void)del_char(false);
// If there are combining characters and 'delcombine' is set
- // move the cursor back. Don't back up before the base
- // character.
- if (l_p_deco && cpc[0] != NUL) {
+ // move the cursor back. Don't back up before the base character.
+ if (has_composing) {
inc_cursor();
}
if (revins_chars) {
@@ -4099,92 +3985,15 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
return did_backspace;
}
-static void ins_mouse(int c)
-{
- pos_T tpos;
- win_T *old_curwin = curwin;
-
- undisplay_dollar();
- tpos = curwin->w_cursor;
- if (do_mouse(NULL, c, BACKWARD, 1, 0)) {
- win_T *new_curwin = curwin;
-
- if (curwin != old_curwin && win_valid(old_curwin)) {
- // Mouse took us to another window. We need to go back to the
- // previous one to stop insert there properly.
- curwin = old_curwin;
- curbuf = curwin->w_buffer;
- if (bt_prompt(curbuf)) {
- // Restart Insert mode when re-entering the prompt buffer.
- curbuf->b_prompt_insert = 'A';
- }
- }
- start_arrow(curwin == old_curwin ? &tpos : NULL);
- if (curwin != new_curwin && win_valid(new_curwin)) {
- curwin = new_curwin;
- curbuf = curwin->w_buffer;
- }
- can_cindent = true;
- }
-
- // redraw status lines (in case another window became active)
- redraw_statuslines();
-}
-
-static void ins_mousescroll(int dir)
-{
- win_T *const old_curwin = curwin;
- pos_T tpos = curwin->w_cursor;
-
- if (mouse_row >= 0 && mouse_col >= 0) {
- int row = mouse_row, col = mouse_col, grid = mouse_grid;
-
- // find the window at the pointer coordinates
- win_T *wp = mouse_find_win(&grid, &row, &col);
- if (wp == NULL) {
- return;
- }
- curwin = wp;
- curbuf = curwin->w_buffer;
- }
- if (curwin == old_curwin) {
- undisplay_dollar();
- }
-
- // Don't scroll the window in which completion is being done.
- if (!pum_visible() || curwin != old_curwin) {
- if (dir == MSCR_DOWN || dir == MSCR_UP) {
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline));
- } else if (p_mousescroll_vert > 0) {
- scroll_redraw(dir, p_mousescroll_vert);
- }
- } else {
- mouse_scroll_horiz(dir);
- }
- }
-
- curwin->w_redr_status = true;
-
- curwin = old_curwin;
- curbuf = curwin->w_buffer;
-
- if (!equalpos(curwin->w_cursor, tpos)) {
- start_arrow(&tpos);
- can_cindent = true;
- }
-}
-
static void ins_left(void)
{
- pos_T tpos;
const bool end_change = dont_sync_undo == kFalse; // end undoable change
if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
}
undisplay_dollar();
- tpos = curwin->w_cursor;
+ pos_T tpos = curwin->w_cursor;
if (oneleft() == OK) {
start_arrow_with_change(&tpos, end_change);
if (!end_change) {
@@ -4210,13 +4019,11 @@ static void ins_left(void)
static void ins_home(int c)
{
- pos_T tpos;
-
if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
}
undisplay_dollar();
- tpos = curwin->w_cursor;
+ pos_T tpos = curwin->w_cursor;
if (c == K_C_HOME) {
curwin->w_cursor.lnum = 1;
}
@@ -4228,13 +4035,11 @@ static void ins_home(int c)
static void ins_end(int c)
{
- pos_T tpos;
-
if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
}
undisplay_dollar();
- tpos = curwin->w_cursor;
+ pos_T tpos = curwin->w_cursor;
if (c == K_C_END) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
@@ -4256,7 +4061,7 @@ static void ins_s_left(void)
if (!end_change) {
AppendCharToRedobuff(K_S_LEFT);
}
- (void)bck_word(1L, false, false);
+ (void)bck_word(1, false, false);
curwin->w_set_curswant = true;
} else {
vim_beep(BO_CRSR);
@@ -4315,7 +4120,7 @@ static void ins_s_right(void)
if (!end_change) {
AppendCharToRedobuff(K_S_RIGHT);
}
- (void)fwd_word(1L, false, 0);
+ (void)fwd_word(1, false, 0);
curwin->w_set_curswant = true;
} else {
vim_beep(BO_CRSR);
@@ -4326,13 +4131,12 @@ static void ins_s_right(void)
/// @param startcol when true move to Insstart.col
static void ins_up(bool startcol)
{
- pos_T tpos;
linenr_T old_topline = curwin->w_topline;
int old_topfill = curwin->w_topfill;
undisplay_dollar();
- tpos = curwin->w_cursor;
- if (cursor_up(1L, true) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (cursor_up(1, true) == OK) {
if (startcol) {
coladvance(getvcol_nolist(&Insstart));
}
@@ -4349,8 +4153,6 @@ static void ins_up(bool startcol)
static void ins_pageup(void)
{
- pos_T tpos;
-
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
@@ -4362,8 +4164,8 @@ static void ins_pageup(void)
return;
}
- tpos = curwin->w_cursor;
- if (onepage(BACKWARD, 1L) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (onepage(BACKWARD, 1) == OK) {
start_arrow(&tpos);
can_cindent = true;
} else {
@@ -4374,13 +4176,12 @@ static void ins_pageup(void)
/// @param startcol when true move to Insstart.col
static void ins_down(bool startcol)
{
- pos_T tpos;
linenr_T old_topline = curwin->w_topline;
int old_topfill = curwin->w_topfill;
undisplay_dollar();
- tpos = curwin->w_cursor;
- if (cursor_down(1L, true) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (cursor_down(1, true) == OK) {
if (startcol) {
coladvance(getvcol_nolist(&Insstart));
}
@@ -4397,8 +4198,6 @@ static void ins_down(bool startcol)
static void ins_pagedown(void)
{
- pos_T tpos;
-
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
@@ -4410,8 +4209,8 @@ static void ins_pagedown(void)
return;
}
- tpos = curwin->w_cursor;
- if (onepage(FORWARD, 1L) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (onepage(FORWARD, 1) == OK) {
start_arrow(&tpos);
can_cindent = true;
} else {
@@ -4425,7 +4224,6 @@ static void ins_pagedown(void)
static bool ins_tab(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
- int i;
int temp;
if (Insstart_blank_vcol == MAXCOL && curwin->w_cursor.lnum == Insstart.lnum) {
@@ -4504,7 +4302,6 @@ static bool ins_tab(void)
char *ptr;
char *saved_line = NULL; // init for GCC
pos_T pos;
- pos_T fpos;
pos_T *cursor;
colnr_T want_vcol, vcol;
int change_col = -1;
@@ -4528,7 +4325,7 @@ static bool ins_tab(void)
}
// Find first white before the cursor
- fpos = curwin->w_cursor;
+ pos_T fpos = curwin->w_cursor;
while (fpos.col > 0 && ascii_iswhite(ptr[-1])) {
fpos.col--;
ptr--;
@@ -4553,7 +4350,7 @@ static bool ins_tab(void)
// Use as many TABs as possible. Beware of 'breakindent', 'showbreak'
// and 'linebreak' adding extra virtual columns.
while (ascii_iswhite(*ptr)) {
- i = lbr_chartabsize(&cts);
+ int i = lbr_chartabsize(&cts);
if (cts.cts_vcol + i > want_vcol) {
break;
}
@@ -4595,9 +4392,9 @@ static bool ins_tab(void)
fpos.col += repl_off;
// Delete following spaces.
- i = cursor->col - fpos.col;
+ int i = cursor->col - fpos.col;
if (i > 0) {
- STRMOVE(ptr, (char *)ptr + i);
+ STRMOVE(ptr, ptr + i);
// correct replace stack.
if ((State & REPLACE_FLAG)
&& !(State & VREPLACE_FLAG)) {
@@ -4606,9 +4403,8 @@ static bool ins_tab(void)
}
}
if (!(State & VREPLACE_FLAG)) {
- extmark_splice_cols(curbuf, (int)fpos.lnum - 1, change_col,
- cursor->col - change_col, fpos.col - change_col,
- kExtmarkUndo);
+ inserted_bytes(fpos.lnum, change_col,
+ cursor->col - change_col, fpos.col - change_col);
}
}
cursor->col -= i;
@@ -4689,8 +4485,6 @@ bool ins_eol(int c)
// done.
static int ins_digraph(void)
{
- int c;
- int cc;
bool did_putchar = false;
pc_status = PC_STATUS_UNSET;
@@ -4707,7 +4501,7 @@ static int ins_digraph(void)
// mode message to be deleted when ESC is hit
no_mapping++;
allow_keys++;
- c = plain_vgetc();
+ int c = plain_vgetc();
no_mapping--;
allow_keys--;
if (did_putchar) {
@@ -4736,7 +4530,7 @@ static int ins_digraph(void)
}
no_mapping++;
allow_keys++;
- cc = plain_vgetc();
+ int cc = plain_vgetc();
no_mapping--;
allow_keys--;
if (did_putchar) {
@@ -4759,9 +4553,7 @@ static int ins_digraph(void)
// Returns the char to be inserted, or NUL if none found.
int ins_copychar(linenr_T lnum)
{
- int c;
- char *ptr, *prev_ptr;
- char *line;
+ char *ptr;
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
vim_beep(BO_COPY);
@@ -4769,9 +4561,9 @@ int ins_copychar(linenr_T lnum)
}
// try to advance to the cursor column
- line = ml_get(lnum);
- prev_ptr = line;
validate_virtcol();
+ char *line = ml_get(lnum);
+ char *prev_ptr = line;
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, lnum, 0, line, line);
@@ -4787,7 +4579,7 @@ int ins_copychar(linenr_T lnum)
}
clear_chartabsize_arg(&cts);
- c = utf_ptr2char(ptr);
+ int c = utf_ptr2char(ptr);
if (c == NUL) {
vim_beep(BO_COPY);
}
@@ -4809,8 +4601,6 @@ static int ins_ctrl_ey(int tc)
} else {
c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1));
if (c != NUL) {
- long tw_save;
-
// The character must be taken literally, insert like it
// was typed after a CTRL-V, and pretend 'textwidth'
// wasn't set. Digits, 'o' and 'x' are special after a
@@ -4818,7 +4608,7 @@ static int ins_ctrl_ey(int tc)
if (c < 256 && !isalnum(c)) {
AppendToRedobuff(CTRL_V_STR);
}
- tw_save = curbuf->b_p_tw;
+ OptInt tw_save = curbuf->b_p_tw;
curbuf->b_p_tw = -1;
insert_special(c, true, false);
curbuf->b_p_tw = tw_save;
@@ -4835,13 +4625,14 @@ static int ins_ctrl_ey(int tc)
// Used when inserting a "normal" character.
static void ins_try_si(int c)
{
- pos_T *pos, old_pos;
- char *ptr;
- int i;
- bool temp;
+ pos_T *pos;
// do some very smart indenting when entering '{' or '}'
if (((did_si || can_si_back) && c == '{') || (can_si && c == '}' && inindent(0))) {
+ pos_T old_pos;
+ char *ptr;
+ int i;
+ bool temp;
// for '}' set indent equal to indent of line containing matching '{'
if (c == '}' && (pos = findmatch(NULL, '{')) != NULL) {
old_pos = curwin->w_cursor;
@@ -4974,9 +4765,8 @@ void set_can_cindent(bool val)
int ins_apply_autocmds(event_T event)
{
varnumber_T tick = buf_get_changedtick(curbuf);
- int r;
- r = apply_autocmds(event, NULL, NULL, false, curbuf);
+ int r = apply_autocmds(event, NULL, NULL, false, curbuf);
// If u_savesub() was called then we are not prepared to start
// a new line. Call u_save() with no contents to fix that.
diff --git a/src/nvim/edit.h b/src/nvim/edit.h
index 91c519f015..434b653f7b 100644
--- a/src/nvim/edit.h
+++ b/src/nvim/edit.h
@@ -1,32 +1,39 @@
-#ifndef NVIM_EDIT_H
-#define NVIM_EDIT_H
+#pragma once
-#include "nvim/autocmd.h"
-#include "nvim/vim.h"
+#include "nvim/autocmd_defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-// Values for in_cinkeys()
-#define KEY_OPEN_FORW 0x101
-#define KEY_OPEN_BACK 0x102
-#define KEY_COMPLETE 0x103 // end of completion
+/// Values for in_cinkeys()
+enum {
+ KEY_OPEN_FORW = 0x101,
+ KEY_OPEN_BACK = 0x102,
+ KEY_COMPLETE = 0x103, ///< end of completion
+};
-// Values for change_indent()
-#define INDENT_SET 1 // set indent
-#define INDENT_INC 2 // increase indent
-#define INDENT_DEC 3 // decrease indent
+/// Values for change_indent()
+enum {
+ INDENT_SET = 1, ///< set indent
+ INDENT_INC = 2, ///< increase indent
+ INDENT_DEC = 3, ///< decrease indent
+};
-// flags for beginline()
-#define BL_WHITE 1 // cursor on first non-white in the line
-#define BL_SOL 2 // use 'sol' option
-#define BL_FIX 4 // don't leave cursor on a NUL
+/// flags for beginline()
+enum {
+ BL_WHITE = 1, ///< cursor on first non-white in the line
+ BL_SOL = 2, ///< use 'sol' option
+ BL_FIX = 4, ///< don't leave cursor on a NUL
+};
-// flags for insertchar()
-#define INSCHAR_FORMAT 1 // force formatting
-#define INSCHAR_DO_COM 2 // format comments
-#define INSCHAR_CTRLV 4 // char typed just after CTRL-V
-#define INSCHAR_NO_FEX 8 // don't use 'formatexpr'
-#define INSCHAR_COM_LIST 16 // format comments with list/2nd line indent
+/// flags for insertchar()
+enum {
+ INSCHAR_FORMAT = 1, ///< force formatting
+ INSCHAR_DO_COM = 2, ///< format comments
+ INSCHAR_CTRLV = 4, ///< char typed just after CTRL-V
+ INSCHAR_NO_FEX = 8, ///< don't use 'formatexpr'
+ INSCHAR_COM_LIST = 16, ///< format comments with list/2nd line indent
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "edit.h.generated.h"
#endif
-#endif // NVIM_EDIT_H
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 4392ea306f..f4479d06a6 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1,23 +1,24 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval.c: Expression evaluation.
#include <assert.h>
#include <ctype.h>
-#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include "auto/config.h"
+#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
@@ -28,7 +29,6 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/ex_cmds.h"
@@ -36,20 +36,21 @@
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_session.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid_defs.h"
+#include "nvim/hashtab.h"
#include "nvim/highlight_group.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lib/queue.h"
-#include "nvim/locale.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -59,14 +60,16 @@
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
@@ -74,28 +77,43 @@
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/usercmd.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead
#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
-static char *e_missbrac = N_("E111: Missing ']'");
-static char *e_list_end = N_("E697: Missing end of List ']': %s");
-static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
-static char *e_nowhitespace
+#define MAX_CALLBACK_DEPTH 20
+
+static const char *e_missbrac = N_("E111: Missing ']'");
+static const char *e_list_end = N_("E697: Missing end of List ']': %s");
+static const char e_cannot_slice_dictionary[]
+ = N_("E719: Cannot slice a Dictionary");
+static const char e_cannot_index_special_variable[]
+ = N_("E909: Cannot index a special variable");
+static const char *e_nowhitespace
= N_("E274: No white space allowed before parenthesis");
-static char *e_write2 = N_("E80: Error while writing: %s");
-static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required");
-static char e_expression_too_recursive_str[] = N_("E1169: Expression too recursive: %s");
-static char e_dot_can_only_be_used_on_dictionary_str[]
+static const char *e_write2 = N_("E80: Error while writing: %s");
+static const char e_cannot_index_a_funcref[]
+ = N_("E695: Cannot index a Funcref");
+static const char e_variable_nested_too_deep_for_making_copy[]
+ = N_("E698: Variable nested too deep for making a copy");
+static const char e_string_list_or_blob_required[]
+ = N_("E1098: String, List or Blob required");
+static const char e_expression_too_recursive_str[]
+ = N_("E1169: Expression too recursive: %s");
+static const char e_dot_can_only_be_used_on_dictionary_str[]
= N_("E1203: Dot can only be used on a dictionary: %s");
+static const char e_empty_function_name[]
+ = N_("E1192: Empty function name");
+static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[]
+ = N_("E1250: Argument of %s must be a List, String, Dictionary or Blob");
static char * const namespace_char = "abglstvw";
@@ -109,22 +127,11 @@ static hashtab_T compat_hashtab;
/// Used for checking if local variables or arguments used in a lambda.
bool *eval_lavars_used = NULL;
-/// Array to hold the hashtab with variables local to each sourced script.
-/// Each item holds a variable (nameless) that points to the dict_T.
-typedef struct {
- ScopeDictDictItem sv_var;
- dict_T sv_dict;
-} scriptvar_T;
-
-static garray_T ga_scripts = { 0, 0, sizeof(scriptvar_T *), 4, NULL };
-#define SCRIPT_SV(id) (((scriptvar_T **)ga_scripts.ga_data)[(id) - 1])
+#define SCRIPT_SV(id) (SCRIPT_ITEM(id)->sn_vars)
#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab)
static int echo_attr = 0; // attributes used for ":echo"
-// The names of packages that once were loaded are remembered.
-static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL };
-
/// Info used by a ":for" loop.
typedef struct {
int fi_semicolon; // true if ending in '; var]'
@@ -259,6 +266,7 @@ static struct vimvar {
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
VV(VV_COLLATE, "collate", VAR_STRING, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
+ VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO),
// Neovim
VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
@@ -293,6 +301,13 @@ static partial_T *vvlua_partial;
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
+/// Enum used by filter(), map() and mapnew()
+typedef enum {
+ FILTERMAP_FILTER,
+ FILTERMAP_MAP,
+ FILTERMAP_MAPNEW,
+} filtermap_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.c.generated.h"
#endif
@@ -406,11 +421,11 @@ void eval_init(void)
// add to v: scope dict, unless the value is not always available
if (p->vv_type != VAR_UNKNOWN) {
- hash_add(&vimvarht, (char *)p->vv_di.di_key);
+ hash_add(&vimvarht, p->vv_di.di_key);
}
if (p->vv_flags & VV_COMPAT) {
// add to compat scope dict
- hash_add(&compat_hashtab, (char *)p->vv_di.di_key);
+ hash_add(&compat_hashtab, p->vv_di.di_key);
}
}
vimvars[VV_VERSION].vv_nr = VIM_VERSION_100;
@@ -440,8 +455,8 @@ void eval_init(void)
set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED));
set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown));
set_vim_var_nr(VV_STDERR, CHAN_STDERR);
- set_vim_var_nr(VV_SEARCHFORWARD, 1L);
- set_vim_var_nr(VV_HLSEARCH, 1L);
+ set_vim_var_nr(VV_SEARCHFORWARD, 1);
+ set_vim_var_nr(VV_HLSEARCH, 1);
set_vim_var_nr(VV_COUNT1, 1);
set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER);
set_vim_var_nr(VV_TYPE_STRING, VAR_TYPE_STRING);
@@ -459,6 +474,7 @@ void eval_init(void)
set_vim_var_nr(VV_NUMBERMIN, VARNUMBER_MIN);
set_vim_var_nr(VV_NUMBERSIZE, sizeof(varnumber_T) * 8);
set_vim_var_special(VV_EXITING, kSpecialVarNull);
+ set_vim_var_nr(VV_MAXCOL, MAXCOL);
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
@@ -473,7 +489,7 @@ void eval_init(void)
}
#if defined(EXITFREE)
-void eval_clear(void)
+static void evalvars_clear(void)
{
for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {
struct vimvar *p = &vimvars[i];
@@ -488,27 +504,28 @@ void eval_clear(void)
hash_init(&vimvarht); // garbage_collect() will access it
hash_clear(&compat_hashtab);
- free_scriptnames();
-# ifdef HAVE_WORKING_LIBINTL
- free_locales();
-# endif
-
// global variables
vars_clear(&globvarht);
- // autoloaded script names
- ga_clear_strings(&ga_loaded);
-
- // Script-local variables. First clear all the variables and in a second
- // loop free the scriptvar_T, because a variable in one script might hold
- // a reference to the whole scope of another script.
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
+ // Script-local variables. Clear all the variables here.
+ // The scriptvar_T is cleared later in free_scriptnames(), because a
+ // variable in one script might hold a reference to the whole scope of
+ // another script.
+ for (int i = 1; i <= script_items.ga_len; i++) {
vars_clear(&SCRIPT_VARS(i));
}
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
- xfree(SCRIPT_SV(i));
- }
- ga_clear(&ga_scripts);
+}
+
+void eval_clear(void)
+{
+ evalvars_clear();
+ free_scriptnames(); // must come after evalvars_clear().
+# ifdef HAVE_WORKING_LIBINTL
+ free_locales();
+# endif
+
+ // autoloaded script names
+ free_autoload_scriptnames();
// unreferenced lists and dicts
(void)garbage_collect(false);
@@ -604,7 +621,7 @@ int var_redir_start(char *name, int append)
/// :redir => foo
/// :let foo
/// :redir END
-void var_redir_str(char *value, int value_len)
+void var_redir_str(const char *value, int value_len)
{
if (redir_lval == NULL) {
return;
@@ -654,19 +671,27 @@ void var_redir_stop(void)
int eval_charconvert(const char *const enc_from, const char *const enc_to,
const char *const fname_from, const char *const fname_to)
{
- bool err = false;
+ const sctx_T saved_sctx = current_sctx;
set_vim_var_string(VV_CC_FROM, enc_from, -1);
set_vim_var_string(VV_CC_TO, enc_to, -1);
set_vim_var_string(VV_FNAME_IN, fname_from, -1);
set_vim_var_string(VV_FNAME_OUT, fname_to, -1);
+ sctx_T *ctx = get_option_sctx("charconvert");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
+
+ bool err = false;
if (eval_to_bool(p_ccv, &err, NULL, false)) {
err = true;
}
+
set_vim_var_string(VV_CC_FROM, NULL, -1);
set_vim_var_string(VV_CC_TO, NULL, -1);
set_vim_var_string(VV_FNAME_IN, NULL, -1);
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
+ current_sctx = saved_sctx;
if (err) {
return FAIL;
@@ -676,28 +701,57 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to,
void eval_diff(const char *const origfile, const char *const newfile, const char *const outfile)
{
- bool err = false;
-
+ const sctx_T saved_sctx = current_sctx;
set_vim_var_string(VV_FNAME_IN, origfile, -1);
set_vim_var_string(VV_FNAME_NEW, newfile, -1);
set_vim_var_string(VV_FNAME_OUT, outfile, -1);
- (void)eval_to_bool(p_dex, &err, NULL, false);
+
+ sctx_T *ctx = get_option_sctx("diffexpr");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
+
+ // errors are ignored
+ typval_T *tv = eval_expr(p_dex, NULL);
+ tv_free(tv);
+
set_vim_var_string(VV_FNAME_IN, NULL, -1);
set_vim_var_string(VV_FNAME_NEW, NULL, -1);
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
+ current_sctx = saved_sctx;
}
void eval_patch(const char *const origfile, const char *const difffile, const char *const outfile)
{
- bool err = false;
-
+ const sctx_T saved_sctx = current_sctx;
set_vim_var_string(VV_FNAME_IN, origfile, -1);
set_vim_var_string(VV_FNAME_DIFF, difffile, -1);
set_vim_var_string(VV_FNAME_OUT, outfile, -1);
- (void)eval_to_bool(p_pex, &err, NULL, false);
+
+ sctx_T *ctx = get_option_sctx("patchexpr");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
+
+ // errors are ignored
+ typval_T *tv = eval_expr(p_pex, NULL);
+ tv_free(tv);
+
set_vim_var_string(VV_FNAME_IN, NULL, -1);
set_vim_var_string(VV_FNAME_DIFF, NULL, -1);
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
+ current_sctx = saved_sctx;
+}
+
+void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip)
+{
+ *evalarg = (evalarg_T){ .eval_flags = skip ? 0 : EVAL_EVALUATE };
+ if (eap != NULL) {
+ if (getline_equal(eap->getline, eap->cookie, getsourceline)) {
+ evalarg->eval_getline = eap->getline;
+ evalarg->eval_cookie = eap->cookie;
+ }
+ }
}
/// Top level evaluation function, returning a boolean.
@@ -706,15 +760,18 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch
/// @param skip only parse, don't execute
///
/// @return true or false.
-int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip)
+int eval_to_bool(char *arg, bool *error, exarg_T *eap, int skip)
{
typval_T tv;
bool retval = false;
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, skip);
if (skip) {
emsg_skip++;
}
- if (eval0(arg, &tv, nextcmd, !skip) == FAIL) {
+ if (eval0(arg, &tv, eap, &evalarg) == FAIL) {
*error = true;
} else {
*error = false;
@@ -726,19 +783,23 @@ int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip)
if (skip) {
emsg_skip--;
}
+ clear_evalarg(&evalarg, eap);
return retval;
}
/// Call eval1() and give an error message if not done at a lower level.
-static int eval1_emsg(char **arg, typval_T *rettv, bool evaluate)
+static int eval1_emsg(char **arg, typval_T *rettv, exarg_T *eap)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
const char *const start = *arg;
const int did_emsg_before = did_emsg;
const int called_emsg_before = called_emsg;
+ evalarg_T evalarg;
- const int ret = eval1(arg, rettv, evaluate);
+ fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip);
+
+ const int ret = eval1(arg, rettv, &evalarg);
if (ret == FAIL) {
// Report the invalid expression unless the expression evaluation has
// been cancelled due to an aborting error, an interrupt, or an
@@ -750,6 +811,7 @@ static int eval1_emsg(char **arg, typval_T *rettv, bool evaluate)
semsg(_(e_invexpr2), start);
}
}
+ clear_evalarg(&evalarg, eap);
return ret;
}
@@ -762,39 +824,50 @@ bool eval_expr_valid_arg(const typval_T *const tv)
&& (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL));
}
-int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
- FUNC_ATTR_NONNULL_ARG(1, 2, 4)
+/// Evaluate an expression, which can be a function, partial or string.
+/// Pass arguments "argv[argc]".
+/// Return the result in "rettv" and OK or FAIL.
+///
+/// @param want_func if true, treat a string as a function name, not an expression
+int eval_expr_typval(const typval_T *expr, bool want_func, typval_T *argv, int argc,
+ typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
{
+ char buf[NUMBUFLEN];
funcexe_T funcexe = FUNCEXE_INIT;
- if (expr->v_type == VAR_FUNC) {
- const char *const s = expr->vval.v_string;
+ if (expr->v_type == VAR_PARTIAL) {
+ partial_T *const partial = expr->vval.v_partial;
+ if (partial == NULL) {
+ return FAIL;
+ }
+ const char *const s = partial_name(partial);
if (s == NULL || *s == NUL) {
return FAIL;
}
funcexe.fe_evaluate = true;
+ funcexe.fe_partial = partial;
if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
return FAIL;
}
- } else if (expr->v_type == VAR_PARTIAL) {
- partial_T *const partial = expr->vval.v_partial;
- const char *const s = partial_name(partial);
+ } else if (expr->v_type == VAR_FUNC || want_func) {
+ const char *const s = (expr->v_type == VAR_FUNC
+ ? expr->vval.v_string
+ : tv_get_string_buf_chk(expr, buf));
if (s == NULL || *s == NUL) {
return FAIL;
}
funcexe.fe_evaluate = true;
- funcexe.fe_partial = partial;
if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
return FAIL;
}
} else {
- char buf[NUMBUFLEN];
char *s = (char *)tv_get_string_buf_chk(expr, buf);
if (s == NULL) {
return FAIL;
}
s = skipwhite(s);
- if (eval1_emsg(&s, rettv, true) == FAIL) {
+ if (eval1_emsg(&s, rettv, NULL) == FAIL) {
return FAIL;
}
if (*skipwhite(s) != NUL) { // check for trailing chars after expr
@@ -813,7 +886,7 @@ bool eval_expr_to_bool(const typval_T *expr, bool *error)
{
typval_T argv, rettv;
- if (eval_expr_typval(expr, &argv, 0, &rettv) == FAIL) {
+ if (eval_expr_typval(expr, false, &argv, 0, &rettv) == FAIL) {
*error = true;
return false;
}
@@ -825,22 +898,23 @@ bool eval_expr_to_bool(const typval_T *expr, bool *error)
/// Top level evaluation function, returning a string
///
/// @param[in] arg String to evaluate.
-/// @param nextcmd Pointer to the start of the next Ex command.
/// @param[in] skip If true, only do parsing to nextcmd without reporting
/// errors or actually evaluating anything.
///
/// @return [allocated] string result of evaluation or NULL in case of error or
/// when skipping.
-char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip)
+char *eval_to_string_skip(char *arg, exarg_T *eap, const bool skip)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
{
typval_T tv;
char *retval;
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, skip);
if (skip) {
emsg_skip++;
}
- if (eval0((char *)arg, &tv, (char **)nextcmd, !skip) == FAIL || skip) {
+ if (eval0(arg, &tv, eap, &evalarg) == FAIL || skip) {
retval = NULL;
} else {
retval = xstrdup(tv_get_string(&tv));
@@ -849,6 +923,7 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip
if (skip) {
emsg_skip--;
}
+ clear_evalarg(&evalarg, eap);
return retval;
}
@@ -856,48 +931,65 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip
/// Skip over an expression at "*pp".
///
/// @return FAIL for an error, OK otherwise.
-int skip_expr(char **pp)
+int skip_expr(char **pp, evalarg_T *const evalarg)
{
- typval_T rettv;
+ const int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags;
+
+ // Don't evaluate the expression.
+ if (evalarg != NULL) {
+ evalarg->eval_flags &= ~EVAL_EVALUATE;
+ }
*pp = skipwhite(*pp);
- return eval1(pp, &rettv, false);
+ typval_T rettv;
+ int res = eval1(pp, &rettv, NULL);
+
+ if (evalarg != NULL) {
+ evalarg->eval_flags = save_flags;
+ }
+
+ return res;
+}
+
+/// Convert "tv" to a string.
+///
+/// @param convert when true convert a List into a sequence of lines.
+///
+/// @return an allocated string.
+static char *typval2string(typval_T *tv, bool convert)
+{
+ if (convert && tv->v_type == VAR_LIST) {
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+ if (tv->vval.v_list != NULL) {
+ tv_list_join(&ga, tv->vval.v_list, "\n");
+ if (tv_list_len(tv->vval.v_list) > 0) {
+ ga_append(&ga, NL);
+ }
+ }
+ ga_append(&ga, NUL);
+ return (char *)ga.ga_data;
+ }
+ return xstrdup(tv_get_string(tv));
}
/// Top level evaluation function, returning a string.
///
-/// @param convert when true convert a List into a sequence of lines and convert
-/// a Float to a String.
+/// @param convert when true convert a List into a sequence of lines.
///
-/// @return pointer to allocated memory, or NULL for failure.
-char *eval_to_string(char *arg, char **nextcmd, bool convert)
+/// @return pointer to allocated memory, or NULL for failure.
+char *eval_to_string(char *arg, bool convert)
{
typval_T tv;
char *retval;
- garray_T ga;
- if (eval0(arg, &tv, nextcmd, true) == FAIL) {
+ if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
retval = NULL;
} else {
- if (convert && tv.v_type == VAR_LIST) {
- ga_init(&ga, (int)sizeof(char), 80);
- if (tv.vval.v_list != NULL) {
- tv_list_join(&ga, tv.vval.v_list, "\n");
- if (tv_list_len(tv.vval.v_list) > 0) {
- ga_append(&ga, NL);
- }
- }
- ga_append(&ga, NUL);
- retval = (char *)ga.ga_data;
- } else if (convert && tv.v_type == VAR_FLOAT) {
- char numbuf[NUMBUFLEN];
- vim_snprintf(numbuf, NUMBUFLEN, "%g", tv.vval.v_float);
- retval = xstrdup(numbuf);
- } else {
- retval = xstrdup(tv_get_string(&tv));
- }
+ retval = typval2string(&tv, convert);
tv_clear(&tv);
}
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
return retval;
}
@@ -906,7 +998,7 @@ char *eval_to_string(char *arg, char **nextcmd, bool convert)
/// textlock.
///
/// @param use_sandbox when true, use the sandbox.
-char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox)
+char *eval_to_string_safe(char *arg, const bool use_sandbox)
{
char *retval;
funccal_entry_T funccal_entry;
@@ -916,7 +1008,7 @@ char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox)
sandbox++;
}
textlock++;
- retval = eval_to_string(arg, nextcmd, false);
+ retval = eval_to_string(arg, false);
if (use_sandbox) {
sandbox--;
}
@@ -937,7 +1029,7 @@ varnumber_T eval_to_number(char *expr)
emsg_off++;
- if (eval1(&p, &rettv, true) == FAIL) {
+ if (eval1(&p, &rettv, &EVALARG_EVALUATE) == FAIL) {
retval = -1;
} else {
retval = tv_get_number_chk(&rettv, NULL);
@@ -952,12 +1044,18 @@ varnumber_T eval_to_number(char *expr)
///
/// @return an allocated typval_T with the result or
/// NULL when there is an error.
-typval_T *eval_expr(char *arg)
+typval_T *eval_expr(char *arg, exarg_T *eap)
{
typval_T *tv = xmalloc(sizeof(*tv));
- if (eval0(arg, tv, NULL, true) == FAIL) {
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip);
+
+ if (eval0(arg, tv, eap, &evalarg) == FAIL) {
XFREE_CLEAR(tv);
}
+
+ clear_evalarg(&evalarg, eap);
return tv;
}
@@ -970,7 +1068,7 @@ void list_vim_vars(int *first)
/// List script-local variables, if there is a script.
void list_script_vars(int *first)
{
- if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= ga_scripts.ga_len) {
+ if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
list_hashtable_vars(&SCRIPT_VARS(current_sctx.sc_sid), "s:", false, first);
}
}
@@ -992,7 +1090,7 @@ void prepare_vimvar(int idx, typval_T *save_tv)
{
*save_tv = vimvars[idx].vv_tv;
if (vimvars[idx].vv_type == VAR_UNKNOWN) {
- hash_add(&vimvarht, (char *)vimvars[idx].vv_di.di_key);
+ hash_add(&vimvarht, vimvars[idx].vv_di.di_key);
}
}
@@ -1005,7 +1103,7 @@ void restore_vimvar(int idx, typval_T *save_tv)
return;
}
- hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key);
+ hashitem_T *hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key);
if (HASHITEM_EMPTY(hi)) {
internal_error("restore_vimvar()");
} else {
@@ -1023,6 +1121,7 @@ list_T *eval_spell_expr(char *badword, char *expr)
typval_T rettv;
list_T *list = NULL;
char *p = skipwhite(expr);
+ const sctx_T saved_sctx = current_sctx;
// Set "v:val" to the bad word.
prepare_vimvar(VV_VAL, &save_val);
@@ -1031,8 +1130,12 @@ list_T *eval_spell_expr(char *badword, char *expr)
if (p_verbose == 0) {
emsg_off++;
}
+ sctx_T *ctx = get_option_sctx("spellsuggest");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
- if (eval1(&p, &rettv, true) == OK) {
+ if (eval1(&p, &rettv, &EVALARG_EVALUATE) == OK) {
if (rettv.v_type != VAR_LIST) {
tv_clear(&rettv);
} else {
@@ -1044,6 +1147,7 @@ list_T *eval_spell_expr(char *badword, char *expr)
emsg_off--;
}
restore_vimvar(VV_VAL, &save_val);
+ current_sctx = saved_sctx;
return list;
}
@@ -1123,7 +1227,7 @@ fail:
///
/// @return [allocated] NULL when calling function fails, allocated string
/// otherwise.
-char *call_func_retstr(const char *const func, int argc, typval_T *argv)
+void *call_func_retstr(const char *const func, int argc, typval_T *argv)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
typval_T rettv;
@@ -1153,7 +1257,7 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv)
typval_T rettv;
// All arguments are passed as strings, no conversion to number.
- if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) {
+ if (call_vim_function(func, argc, argv, &rettv) == FAIL) {
return NULL;
}
@@ -1167,11 +1271,13 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv)
/// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding
/// it in "*cp". Doesn't give error messages.
-int eval_foldexpr(char *arg, int *cp)
+int eval_foldexpr(win_T *wp, int *cp)
{
- typval_T tv;
- varnumber_T retval;
- int use_sandbox = was_set_insecurely(curwin, "foldexpr", OPT_LOCAL);
+ const sctx_T saved_sctx = current_sctx;
+ const bool use_sandbox = was_set_insecurely(wp, "foldexpr", OPT_LOCAL);
+
+ char *arg = wp->w_p_fde;
+ current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx;
emsg_off++;
if (use_sandbox) {
@@ -1179,7 +1285,10 @@ int eval_foldexpr(char *arg, int *cp)
}
textlock++;
*cp = NUL;
- if (eval0(arg, &tv, NULL, true) == FAIL) {
+
+ typval_T tv;
+ varnumber_T retval;
+ if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
retval = 0;
} else {
// If the result is a number, just return the number.
@@ -1191,22 +1300,61 @@ int eval_foldexpr(char *arg, int *cp)
// If the result is a string, check if there is a non-digit before
// the number.
char *s = tv.vval.v_string;
- if (!ascii_isdigit(*s) && *s != '-') {
- *cp = (char_u)(*s++);
+ if (*s != NUL && !ascii_isdigit(*s) && *s != '-') {
+ *cp = (uint8_t)(*s++);
}
retval = atol(s);
}
tv_clear(&tv);
}
+
emsg_off--;
if (use_sandbox) {
sandbox--;
}
textlock--;
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
+ current_sctx = saved_sctx;
return (int)retval;
}
+/// Evaluate 'foldtext', returning an Array or a String (NULL_STRING on failure).
+Object eval_foldtext(win_T *wp)
+{
+ const bool use_sandbox = was_set_insecurely(wp, "foldtext", OPT_LOCAL);
+ char *arg = wp->w_p_fdt;
+ funccal_entry_T funccal_entry;
+
+ save_funccal(&funccal_entry);
+ if (use_sandbox) {
+ sandbox++;
+ }
+ textlock++;
+
+ typval_T tv;
+ Object retval;
+ if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
+ retval = STRING_OBJ(NULL_STRING);
+ } else {
+ if (tv.v_type == VAR_LIST) {
+ retval = vim_to_object(&tv);
+ } else {
+ retval = STRING_OBJ(cstr_to_string(tv_get_string(&tv)));
+ }
+ tv_clear(&tv);
+ }
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
+
+ if (use_sandbox) {
+ sandbox--;
+ }
+ textlock--;
+ restore_funccal();
+
+ return retval;
+}
+
/// Get an lvalue
///
/// Lvalue may be
@@ -1242,9 +1390,8 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
if (skip) {
// When skipping just find the end of the name.
- lp->ll_name = (const char *)name;
- return (char *)find_name_end(name, NULL, NULL,
- FNE_INCL_BR | fne_flags);
+ lp->ll_name = name;
+ return (char *)find_name_end(name, NULL, NULL, FNE_INCL_BR | fne_flags);
}
// Find the end of the name.
@@ -1277,8 +1424,8 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_name_len = strlen(lp->ll_name);
}
} else {
- lp->ll_name = (const char *)name;
- lp->ll_name_len = (size_t)((const char *)p - lp->ll_name);
+ lp->ll_name = name;
+ lp->ll_name_len = (size_t)(p - lp->ll_name);
}
// Without [idx] or .key we are done.
@@ -1321,14 +1468,22 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
}
return NULL;
}
- if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)
- && !(lp->ll_tv->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL)
- && !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) {
+ if (lp->ll_tv->v_type != VAR_LIST
+ && lp->ll_tv->v_type != VAR_DICT
+ && lp->ll_tv->v_type != VAR_BLOB) {
if (!quiet) {
emsg(_("E689: Can only index a List, Dictionary or Blob"));
}
return NULL;
}
+
+ // a NULL list/blob works like an empty list/blob, allocate one now.
+ if (lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list == NULL) {
+ tv_list_alloc_ret(lp->ll_tv, kListLenUnknown);
+ } else if (lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob == NULL) {
+ tv_blob_alloc_ret(lp->ll_tv);
+ }
+
if (lp->ll_range) {
if (!quiet) {
emsg(_("E708: [:] must come last"));
@@ -1355,7 +1510,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
empty1 = true;
} else {
empty1 = false;
- if (eval1(&p, &var1, true) == FAIL) { // Recursive!
+ if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) { // Recursive!
return NULL;
}
if (!tv_check_str(&var1)) {
@@ -1370,7 +1525,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
if (*p == ':') {
if (lp->ll_tv->v_type == VAR_DICT) {
if (!quiet) {
- emsg(_(e_dictrange));
+ emsg(_(e_cannot_slice_dictionary));
}
tv_clear(&var1);
return NULL;
@@ -1389,7 +1544,8 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_empty2 = true;
} else {
lp->ll_empty2 = false;
- if (eval1(&p, &var2, true) == FAIL) { // Recursive!
+ // Recursive!
+ if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) {
tv_clear(&var1);
return NULL;
}
@@ -1425,25 +1581,23 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
}
lp->ll_list = NULL;
lp->ll_dict = lp->ll_tv->vval.v_dict;
- lp->ll_di = tv_dict_find(lp->ll_dict, (const char *)key, len);
+ lp->ll_di = tv_dict_find(lp->ll_dict, key, len);
// When assigning to a scope dictionary check that a function and
// variable name is valid (only variable name unless it is l: or
// g: dictionary). Disallow overwriting a builtin function.
if (rettv != NULL && lp->ll_dict->dv_scope != 0) {
char prevval;
- int wrong;
-
if (len != -1) {
prevval = key[len];
key[len] = NUL;
} else {
prevval = 0; // Avoid compiler warning.
}
- wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
- && tv_is_func(*rettv)
- && var_wrong_func_name((const char *)key, lp->ll_di == NULL))
- || !valid_varname((const char *)key));
+ bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
+ && tv_is_func(*rettv)
+ && var_wrong_func_name(key, lp->ll_di == NULL))
+ || !valid_varname(key));
if (len != -1) {
key[len] = prevval;
}
@@ -1480,7 +1634,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
if (len == -1) {
lp->ll_newkey = xstrdup(key);
} else {
- lp->ll_newkey = xstrnsave(key, (size_t)len);
+ lp->ll_newkey = xmemdupz(key, (size_t)len);
}
tv_clear(&var1);
break;
@@ -1500,26 +1654,19 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_n1 = 0;
} else {
// Is number or string.
- lp->ll_n1 = (long)tv_get_number(&var1);
+ lp->ll_n1 = (int)tv_get_number(&var1);
}
tv_clear(&var1);
const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
- if (lp->ll_n1 < 0 || lp->ll_n1 > bloblen
- || (lp->ll_range && lp->ll_n1 == bloblen)) {
- if (!quiet) {
- semsg(_(e_blobidx), (int64_t)lp->ll_n1);
- }
+ if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) {
tv_clear(&var2);
return NULL;
}
if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (long)tv_get_number(&var2);
+ lp->ll_n2 = (int)tv_get_number(&var2);
tv_clear(&var2);
- if (lp->ll_n2 < 0 || lp->ll_n2 >= bloblen || lp->ll_n2 < lp->ll_n1) {
- if (!quiet) {
- semsg(_(e_blobidx), (int64_t)lp->ll_n2);
- }
+ if (tv_blob_check_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) {
return NULL;
}
}
@@ -1532,24 +1679,15 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_n1 = 0;
} else {
// Is number or string.
- lp->ll_n1 = (long)tv_get_number(&var1);
+ lp->ll_n1 = (int)tv_get_number(&var1);
}
tv_clear(&var1);
lp->ll_dict = NULL;
lp->ll_list = lp->ll_tv->vval.v_list;
- lp->ll_li = tv_list_find(lp->ll_list, (int)lp->ll_n1);
- if (lp->ll_li == NULL) {
- if (lp->ll_n1 < 0) {
- lp->ll_n1 = 0;
- lp->ll_li = tv_list_find(lp->ll_list, (int)lp->ll_n1);
- }
- }
+ lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
if (lp->ll_li == NULL) {
tv_clear(&var2);
- if (!quiet) {
- semsg(_(e_listidx), (int64_t)lp->ll_n1);
- }
return NULL;
}
@@ -1558,27 +1696,11 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
// When no index given: "lp->ll_empty2" is true.
// Otherwise "lp->ll_n2" is set to the second index.
if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string.
+ lp->ll_n2 = (int)tv_get_number(&var2); // Is number or string.
tv_clear(&var2);
- if (lp->ll_n2 < 0) {
- listitem_T *ni = tv_list_find(lp->ll_list, (int)lp->ll_n2);
- if (ni == NULL) {
- if (!quiet) {
- semsg(_(e_listidx), (int64_t)lp->ll_n2);
- }
- return NULL;
- }
- lp->ll_n2 = tv_list_idx_of_item(lp->ll_list, ni);
- }
-
- // Check that lp->ll_n2 isn't before lp->ll_n1.
- if (lp->ll_n1 < 0) {
- lp->ll_n1 = tv_list_idx_of_item(lp->ll_list, lp->ll_li);
- }
- if (lp->ll_n2 < lp->ll_n1) {
- if (!quiet) {
- semsg(_(e_listidx), (int64_t)lp->ll_n2);
- }
+ if (tv_list_check_range_index_two(lp->ll_list,
+ &lp->ll_n1, lp->ll_li,
+ &lp->ll_n2, quiet) == FAIL) {
return NULL;
}
}
@@ -1607,11 +1729,10 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
const char *op)
{
int cc;
- listitem_T *ri;
dictitem_T *di;
if (lp->ll_tv == NULL) {
- cc = (char_u)(*endp);
+ cc = (uint8_t)(*endp);
*endp = NUL;
if (lp->ll_blob != NULL) {
if (op != NULL && *op != '=') {
@@ -1627,33 +1748,14 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
lp->ll_n2 = tv_blob_len(lp->ll_blob) - 1;
}
- if (lp->ll_n2 - lp->ll_n1 + 1 != tv_blob_len(rettv->vval.v_blob)) {
- emsg(_("E972: Blob value does not have the right number of bytes"));
+ if (tv_blob_set_range(lp->ll_blob, lp->ll_n1, lp->ll_n2, rettv) == FAIL) {
return;
}
- if (lp->ll_empty2) {
- lp->ll_n2 = tv_blob_len(lp->ll_blob);
- }
-
- for (int il = (int)lp->ll_n1, ir = 0; il <= (int)lp->ll_n2; il++) {
- tv_blob_set(lp->ll_blob, il, tv_blob_get(rettv->vval.v_blob, ir++));
- }
} else {
bool error = false;
const char val = (char)tv_get_number_chk(rettv, &error);
if (!error) {
- garray_T *const gap = &lp->ll_blob->bv_ga;
-
- // Allow for appending a byte. Setting a byte beyond
- // the end is an error otherwise.
- if (lp->ll_n1 < gap->ga_len || lp->ll_n1 == gap->ga_len) {
- ga_grow(&lp->ll_blob->bv_ga, 1);
- tv_blob_set(lp->ll_blob, (int)lp->ll_n1, (uint8_t)val);
- if (lp->ll_n1 == gap->ga_len) {
- gap->ga_len++;
- }
- }
- // error for invalid range was already given in get_lval()
+ tv_blob_set_append(lp->ll_blob, lp->ll_n1, (uint8_t)val);
}
}
} else if (op != NULL && *op != '=') {
@@ -1667,8 +1769,8 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
// handle +=, -=, *=, /=, %= and .=
di = NULL;
- if (get_var_tv(lp->ll_name, (int)strlen(lp->ll_name),
- &tv, &di, true, false) == OK) {
+ if (eval_variable(lp->ll_name, (int)strlen(lp->ll_name),
+ &tv, &di, true, false) == OK) {
if ((di == NULL
|| (!var_check_ro(di->di_flags, lp->ll_name, TV_CSTRING)
&& !tv_check_lock(&di->di_tv, lp->ll_name, TV_CSTRING)))
@@ -1687,60 +1789,13 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
lp->ll_name, TV_CSTRING)) {
// Skip
} else if (lp->ll_range) {
- listitem_T *ll_li = lp->ll_li;
- int ll_n1 = (int)lp->ll_n1;
-
if (is_const) {
emsg(_("E996: Cannot lock a range"));
return;
}
- // Check whether any of the list items is locked
- for (ri = tv_list_first(rettv->vval.v_list);
- ri != NULL && ll_li != NULL;) {
- if (value_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name,
- TV_CSTRING)) {
- return;
- }
- ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);
- if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) {
- break;
- }
- ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li);
- ll_n1++;
- }
-
- // Assign the List values to the list items.
- for (ri = tv_list_first(rettv->vval.v_list); ri != NULL;) {
- if (op != NULL && *op != '=') {
- eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op);
- } else {
- tv_clear(TV_LIST_ITEM_TV(lp->ll_li));
- tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li));
- }
- ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);
- if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) {
- break;
- }
- assert(lp->ll_li != NULL);
- if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) {
- // Need to add an empty item.
- tv_list_append_number(lp->ll_list, 0);
- // ll_li may have become invalid after append, don’t use it.
- lp->ll_li = tv_list_last(lp->ll_list); // Valid again.
- } else {
- lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
- }
- lp->ll_n1++;
- }
- if (ri != NULL) {
- emsg(_("E710: List value has more items than target"));
- } else if (lp->ll_empty2
- ? (lp->ll_li != NULL
- && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL)
- : lp->ll_n1 != lp->ll_n2) {
- emsg(_("E711: List value has not enough items"));
- }
+ (void)tv_list_assign_range(lp->ll_list, rettv->vval.v_list,
+ lp->ll_n1, lp->ll_n2, lp->ll_empty2, op, lp->ll_name);
} else {
typval_T oldtv = TV_INITIAL_VALUE;
dict_T *dict = lp->ll_dict;
@@ -1762,7 +1817,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
}
// Need to add an item to the Dictionary.
- di = tv_dict_item_alloc((const char *)lp->ll_newkey);
+ di = tv_dict_item_alloc(lp->ll_newkey);
if (tv_dict_add(lp->ll_tv->vval.v_dict, di) == FAIL) {
xfree(di);
return;
@@ -1798,7 +1853,7 @@ notify:
} else {
dictitem_T *di_ = lp->ll_di;
assert(di_->di_key != NULL);
- tv_dict_watcher_notify(dict, (char *)di_->di_key, lp->ll_tv, &oldtv);
+ tv_dict_watcher_notify(dict, di_->di_key, lp->ll_tv, &oldtv);
tv_clear(&oldtv);
}
}
@@ -1811,22 +1866,23 @@ notify:
/// @param[out] *errp set to true for an error, false otherwise;
///
/// @return a pointer that holds the info. Null when there is an error.
-void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
+void *eval_for_line(const char *arg, bool *errp, exarg_T *eap, evalarg_T *const evalarg)
{
forinfo_T *fi = xcalloc(1, sizeof(forinfo_T));
- const char *expr;
typval_T tv;
list_T *l;
+ const bool skip = !(evalarg->eval_flags & EVAL_EVALUATE);
*errp = true; // Default: there is an error.
- expr = skip_var_list((char *)arg, &fi->fi_varcount, &fi->fi_semicolon);
+ const char *expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon);
if (expr == NULL) {
return fi;
}
expr = skipwhite(expr);
- if (expr[0] != 'i' || expr[1] != 'n' || !ascii_iswhite(expr[2])) {
+ if (expr[0] != 'i' || expr[1] != 'n'
+ || !(expr[2] == NUL || ascii_iswhite(expr[2]))) {
emsg(_("E690: Missing \"in\" after :for"));
return fi;
}
@@ -1834,7 +1890,8 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
if (skip) {
emsg_skip++;
}
- if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) {
+ expr = skipwhite(expr + 2);
+ if (eval0((char *)expr, &tv, eap, evalarg) == OK) {
*errp = false;
if (!skip) {
if (tv.v_type == VAR_LIST) {
@@ -1856,7 +1913,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
// Make a copy, so that the iteration still works when the
// blob is changed.
- tv_blob_copy(&tv, &btv);
+ tv_blob_copy(tv.vval.v_blob, &btv);
fi->fi_blob = btv.vval.v_blob;
}
tv_clear(&tv);
@@ -1909,7 +1966,7 @@ bool next_for_item(void *fi_void, char *arg)
typval_T tv;
tv.v_type = VAR_STRING;
tv.v_lock = VAR_FIXED;
- tv.vval.v_string = xstrnsave(fi->fi_string + fi->fi_byte_idx, (size_t)len);
+ tv.vval.v_string = xmemdupz(fi->fi_string + fi->fi_byte_idx, (size_t)len);
fi->fi_byte_idx += len;
const int result
= ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
@@ -1949,14 +2006,12 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
FUNC_ATTR_NONNULL_ALL
{
bool got_eq = false;
- int c;
- char *p;
if (cmdidx == CMD_let || cmdidx == CMD_const) {
xp->xp_context = EXPAND_USER_VARS;
if (strpbrk(arg, "\"'+-*/%.=!?~|&$([<>,#") == NULL) {
// ":let var1 var2 ...": find last space.
- for (p = arg + strlen(arg); p >= arg;) {
+ for (char *p = arg + strlen(arg); p >= arg;) {
xp->xp_pattern = p;
MB_PTR_BACK(arg, p);
if (ascii_iswhite(*p)) {
@@ -1970,7 +2025,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
: EXPAND_EXPRESSION;
}
while ((xp->xp_pattern = strpbrk(arg, "\"'+-*/%.=!?~|&$([<>,#")) != NULL) {
- c = (uint8_t)(*xp->xp_pattern);
+ int c = (uint8_t)(*xp->xp_pattern);
if (c == '&') {
c = (uint8_t)xp->xp_pattern[1];
if (c == '&') {
@@ -2027,7 +2082,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
}
arg = xp->xp_pattern;
if (*arg != NUL) {
- while ((c = (char_u)(*++arg)) != NUL && (c == ' ' || c == '\t')) {}
+ while ((c = (uint8_t)(*++arg)) != NUL && (c == ' ' || c == '\t')) {}
}
}
@@ -2037,7 +2092,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
|| cmdidx == CMD_echon
|| cmdidx == CMD_echomsg)
&& xp->xp_context == EXPAND_EXPRESSION) {
- for (;;) {
+ while (true) {
char *const n = skiptowhite(arg);
if (n == arg || ascii_iswhite_or_nul(*skipwhite(n))) {
@@ -2181,11 +2236,11 @@ int pattern_match(const char *pat, const char *text, bool ic)
// avoid 'l' flag in 'cpoptions'
char *save_cpo = p_cpo;
- p_cpo = empty_option;
- regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING);
+ p_cpo = empty_string_option;
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog != NULL) {
regmatch.rm_ic = ic;
- matches = vim_regexec_nl(&regmatch, (char *)text, (colnr_T)0);
+ matches = vim_regexec_nl(&regmatch, text, 0);
vim_regfree(regmatch.regprog);
}
p_cpo = save_cpo;
@@ -2199,21 +2254,24 @@ int pattern_match(const char *pat, const char *text, bool ic)
/// @param basetv "expr" for "expr->name(arg)"
///
/// @return OK or FAIL.
-static int eval_func(char **const arg, char *const name, const int name_len, typval_T *const rettv,
- const bool evaluate, typval_T *const basetv)
- FUNC_ATTR_NONNULL_ARG(1, 2, 4)
+static int eval_func(char **const arg, evalarg_T *const evalarg, char *const name,
+ const int name_len, typval_T *const rettv, const int flags,
+ typval_T *const basetv)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5)
{
+ const bool evaluate = flags & EVAL_EVALUATE;
char *s = name;
int len = name_len;
+ bool found_var = false;
if (!evaluate) {
- check_vars((const char *)s, (size_t)len);
+ check_vars(s, (size_t)len);
}
// If "s" is the name of a variable of type VAR_FUNC
// use its contents.
partial_T *partial;
- s = deref_func_name((const char *)s, &len, &partial, !evaluate);
+ s = deref_func_name(s, &len, &partial, !evaluate, &found_var);
// Need to make a copy, in case evaluating the arguments makes
// the name invalid.
@@ -2226,7 +2284,8 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
funcexe.fe_evaluate = evaluate;
funcexe.fe_partial = partial;
funcexe.fe_basetv = basetv;
- int ret = get_func_tv(s, len, rettv, arg, &funcexe);
+ funcexe.fe_found_var = found_var;
+ int ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe);
xfree(s);
@@ -2250,6 +2309,26 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
return ret;
}
+/// After using "evalarg" filled from "eap": free the memory.
+void clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
+{
+ if (evalarg != NULL) {
+ if (evalarg->eval_tofree != NULL) {
+ if (eap != NULL) {
+ // We may need to keep the original command line, e.g. for
+ // ":let" it has the variable names. But we may also need the
+ // new one, "nextcmd" points into it. Keep both.
+ xfree(eap->cmdline_tofree);
+ eap->cmdline_tofree = *eap->cmdlinep;
+ *eap->cmdlinep = evalarg->eval_tofree;
+ } else {
+ xfree(evalarg->eval_tofree);
+ }
+ evalarg->eval_tofree = NULL;
+ }
+ }
+}
+
/// The "evaluate" argument: When false, the argument is only parsed but not
/// executed. The function may return OK, but the rettv will be of type
/// VAR_UNKNOWN. The function still returns FAIL for a syntax error.
@@ -2259,17 +2338,17 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
/// Put the result in "rettv" when returning OK and "evaluate" is true.
/// Note: "rettv.v_lock" is not set.
///
+/// @param evalarg can be NULL, &EVALARG_EVALUATE or a pointer.
+///
/// @return OK or FAIL.
-int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
+int eval0(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg)
{
- int ret;
- char *p;
const int did_emsg_before = did_emsg;
const int called_emsg_before = called_emsg;
bool end_error = false;
- p = skipwhite(arg);
- ret = eval1(&p, rettv, evaluate);
+ char *p = skipwhite(arg);
+ int ret = eval1(&p, rettv, evalarg);
if (ret != FAIL) {
end_error = !ends_excmd(*p);
@@ -2282,7 +2361,8 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
// been cancelled due to an aborting error, an interrupt, or an
// exception, or we already gave a more specific error.
// Also check called_emsg for when using assert_fails().
- if (!aborting() && did_emsg == did_emsg_before
+ if (!aborting()
+ && did_emsg == did_emsg_before
&& called_emsg == called_emsg_before) {
if (end_error) {
semsg(_(e_trailing_arg), p);
@@ -2290,10 +2370,21 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
semsg(_(e_invexpr2), arg);
}
}
- ret = FAIL;
+
+ if (eap != NULL && p != NULL) {
+ // Some of the expression may not have been consumed.
+ // Only execute a next command if it cannot be a "||" operator.
+ // The next command may be "catch".
+ char *nextcmd = check_nextcmd(p);
+ if (nextcmd != NULL && *nextcmd != '|') {
+ eap->nextcmd = nextcmd;
+ }
+ }
+ return FAIL;
}
- if (nextcmd != NULL) {
- *nextcmd = check_nextcmd(p);
+
+ if (eap != NULL) {
+ eap->nextcmd = check_nextcmd(p);
}
return ret;
@@ -2301,6 +2392,7 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
/// Handle top level expression:
/// expr2 ? expr1 : expr1
+/// expr2 ?? expr1
///
/// "arg" must point to the first non-white of the expression.
/// "arg" is advanced to the next non-white after the recognized expression.
@@ -2308,55 +2400,89 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
/// Note: "rettv.v_lock" is not set.
///
/// @return OK or FAIL.
-int eval1(char **arg, typval_T *rettv, int evaluate)
+int eval1(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- bool result;
- typval_T var2;
-
// Get the first variable.
- if (eval2(arg, rettv, evaluate) == FAIL) {
+ if (eval2(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- if ((*arg)[0] == '?') {
- result = false;
+ char *p = *arg;
+ if (*p == '?') {
+ const bool op_falsy = p[1] == '?';
+ evalarg_T *evalarg_used = evalarg;
+ evalarg_T local_evalarg;
+ if (evalarg == NULL) {
+ local_evalarg = (evalarg_T){ .eval_flags = 0 };
+ evalarg_used = &local_evalarg;
+ }
+ const int orig_flags = evalarg_used->eval_flags;
+ const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE;
+
+ bool result = false;
if (evaluate) {
bool error = false;
- if (tv_get_number_chk(rettv, &error) != 0) {
+ if (op_falsy) {
+ result = tv2bool(rettv);
+ } else if (tv_get_number_chk(rettv, &error) != 0) {
result = true;
}
- tv_clear(rettv);
+ if (error || !op_falsy || !result) {
+ tv_clear(rettv);
+ }
if (error) {
return FAIL;
}
}
- // Get the second variable.
+ // Get the second variable. Recursive!
+ if (op_falsy) {
+ (*arg)++;
+ }
*arg = skipwhite(*arg + 1);
- if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive!
+ evalarg_used->eval_flags = (op_falsy ? !result : result)
+ ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ typval_T var2;
+ if (eval1(arg, &var2, evalarg_used) == FAIL) {
+ evalarg_used->eval_flags = orig_flags;
return FAIL;
}
+ if (!op_falsy || !result) {
+ *rettv = var2;
+ }
- // Check for the ":".
- if ((*arg)[0] != ':') {
- emsg(_("E109: Missing ':' after '?'"));
- if (evaluate && result) {
- tv_clear(rettv);
+ if (!op_falsy) {
+ // Check for the ":".
+ p = *arg;
+ if (*p != ':') {
+ emsg(_("E109: Missing ':' after '?'"));
+ if (evaluate && result) {
+ tv_clear(rettv);
+ }
+ evalarg_used->eval_flags = orig_flags;
+ return FAIL;
}
- return FAIL;
- }
- // Get the third variable.
- *arg = skipwhite(*arg + 1);
- if (eval1(arg, &var2, evaluate && !result) == FAIL) { // Recursive!
- if (evaluate && result) {
- tv_clear(rettv);
+ // Get the third variable. Recursive!
+ *arg = skipwhite(*arg + 1);
+ evalarg_used->eval_flags = !result ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ if (eval1(arg, &var2, evalarg_used) == FAIL) {
+ if (evaluate && result) {
+ tv_clear(rettv);
+ }
+ evalarg_used->eval_flags = orig_flags;
+ return FAIL;
+ }
+ if (evaluate && !result) {
+ *rettv = var2;
}
- return FAIL;
}
- if (evaluate && !result) {
- *rettv = var2;
+
+ if (evalarg == NULL) {
+ clear_evalarg(&local_evalarg, NULL);
+ } else {
+ evalarg->eval_flags = orig_flags;
}
}
@@ -2370,21 +2496,29 @@ int eval1(char **arg, typval_T *rettv, int evaluate)
/// "arg" is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval2(char **arg, typval_T *rettv, int evaluate)
+static int eval2(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- typval_T var2;
- bool error = false;
-
// Get the first variable.
- if (eval3(arg, rettv, evaluate) == FAIL) {
+ if (eval3(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- // Repeat until there is no following "||".
- bool first = true;
- bool result = false;
- while ((*arg)[0] == '|' && (*arg)[1] == '|') {
- if (evaluate && first) {
+ // Handle the "||" operator.
+ char *p = *arg;
+ if (p[0] == '|' && p[1] == '|') {
+ evalarg_T *evalarg_used = evalarg;
+ evalarg_T local_evalarg;
+ if (evalarg == NULL) {
+ local_evalarg = (evalarg_T){ .eval_flags = 0 };
+ evalarg_used = &local_evalarg;
+ }
+ const int orig_flags = evalarg_used->eval_flags;
+ const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE;
+
+ bool result = false;
+
+ if (evaluate) {
+ bool error = false;
if (tv_get_number_chk(rettv, &error) != 0) {
result = true;
}
@@ -2392,28 +2526,41 @@ static int eval2(char **arg, typval_T *rettv, int evaluate)
if (error) {
return FAIL;
}
- first = false;
}
- // Get the second variable.
- *arg = skipwhite(*arg + 2);
- if (eval3(arg, &var2, evaluate && !result) == FAIL) {
- return FAIL;
- }
+ // Repeat until there is no following "||".
+ while (p[0] == '|' && p[1] == '|') {
+ // Get the second variable.
+ *arg = skipwhite(*arg + 2);
+ evalarg_used->eval_flags = !result ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ typval_T var2;
+ if (eval3(arg, &var2, evalarg_used) == FAIL) {
+ return FAIL;
+ }
- // Compute the result.
- if (evaluate && !result) {
- if (tv_get_number_chk(&var2, &error) != 0) {
- result = true;
+ // Compute the result.
+ if (evaluate && !result) {
+ bool error = false;
+ if (tv_get_number_chk(&var2, &error) != 0) {
+ result = true;
+ }
+ tv_clear(&var2);
+ if (error) {
+ return FAIL;
+ }
}
- tv_clear(&var2);
- if (error) {
- return FAIL;
+ if (evaluate) {
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = result;
}
+
+ p = *arg;
}
- if (evaluate) {
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = result;
+
+ if (evalarg == NULL) {
+ clear_evalarg(&local_evalarg, NULL);
+ } else {
+ evalarg->eval_flags = orig_flags;
}
}
@@ -2427,21 +2574,29 @@ static int eval2(char **arg, typval_T *rettv, int evaluate)
/// `arg` is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval3(char **arg, typval_T *rettv, int evaluate)
+static int eval3(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- typval_T var2;
- bool error = false;
-
// Get the first variable.
- if (eval4(arg, rettv, evaluate) == FAIL) {
+ if (eval4(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- // Repeat until there is no following "&&".
- bool first = true;
- bool result = true;
- while ((*arg)[0] == '&' && (*arg)[1] == '&') {
- if (evaluate && first) {
+ char *p = *arg;
+ // Handle the "&&" operator.
+ if (p[0] == '&' && p[1] == '&') {
+ evalarg_T *evalarg_used = evalarg;
+ evalarg_T local_evalarg;
+ if (evalarg == NULL) {
+ local_evalarg = (evalarg_T){ .eval_flags = 0 };
+ evalarg_used = &local_evalarg;
+ }
+ const int orig_flags = evalarg_used->eval_flags;
+ const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE;
+
+ bool result = true;
+
+ if (evaluate) {
+ bool error = false;
if (tv_get_number_chk(rettv, &error) == 0) {
result = false;
}
@@ -2449,28 +2604,41 @@ static int eval3(char **arg, typval_T *rettv, int evaluate)
if (error) {
return FAIL;
}
- first = false;
}
- // Get the second variable.
- *arg = skipwhite(*arg + 2);
- if (eval4(arg, &var2, evaluate && result) == FAIL) {
- return FAIL;
- }
+ // Repeat until there is no following "&&".
+ while (p[0] == '&' && p[1] == '&') {
+ // Get the second variable.
+ *arg = skipwhite(*arg + 2);
+ evalarg_used->eval_flags = result ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ typval_T var2;
+ if (eval4(arg, &var2, evalarg_used) == FAIL) {
+ return FAIL;
+ }
- // Compute the result.
- if (evaluate && result) {
- if (tv_get_number_chk(&var2, &error) == 0) {
- result = false;
+ // Compute the result.
+ if (evaluate && result) {
+ bool error = false;
+ if (tv_get_number_chk(&var2, &error) == 0) {
+ result = false;
+ }
+ tv_clear(&var2);
+ if (error) {
+ return FAIL;
+ }
}
- tv_clear(&var2);
- if (error) {
- return FAIL;
+ if (evaluate) {
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = result;
}
+
+ p = *arg;
}
- if (evaluate) {
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = result;
+
+ if (evalarg == NULL) {
+ clear_evalarg(&local_evalarg, NULL);
+ } else {
+ evalarg->eval_flags = orig_flags;
}
}
@@ -2493,20 +2661,18 @@ static int eval3(char **arg, typval_T *rettv, int evaluate)
/// "arg" is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval4(char **arg, typval_T *rettv, int evaluate)
+static int eval4(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
typval_T var2;
- char *p;
exprtype_T type = EXPR_UNKNOWN;
int len = 2;
- bool ic;
// Get the first variable.
- if (eval5(arg, rettv, evaluate) == FAIL) {
+ if (eval5(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- p = *arg;
+ char *p = *arg;
switch (p[0]) {
case '=':
if (p[1] == '=') {
@@ -2552,6 +2718,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
// If there is a comparative operator, use it.
if (type != EXPR_UNKNOWN) {
+ bool ic;
// extra question mark appended: ignore case
if (p[len] == '?') {
ic = true;
@@ -2565,11 +2732,11 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
// Get the second variable.
*arg = skipwhite(p + len);
- if (eval5(arg, &var2, evaluate) == FAIL) {
+ if (eval5(arg, &var2, evalarg) == FAIL) {
tv_clear(rettv);
return FAIL;
}
- if (evaluate) {
+ if (evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE)) {
const int ret = typval_compare(rettv, &var2, type, ic);
tv_clear(&var2);
@@ -2580,8 +2747,41 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
return OK;
}
+/// Make a copy of blob "tv1" and append blob "tv2".
+static void eval_addblob(typval_T *tv1, typval_T *tv2)
+{
+ const blob_T *const b1 = tv1->vval.v_blob;
+ const blob_T *const b2 = tv2->vval.v_blob;
+ blob_T *const b = tv_blob_alloc();
+
+ for (int i = 0; i < tv_blob_len(b1); i++) {
+ ga_append(&b->bv_ga, tv_blob_get(b1, i));
+ }
+ for (int i = 0; i < tv_blob_len(b2); i++) {
+ ga_append(&b->bv_ga, tv_blob_get(b2, i));
+ }
+
+ tv_clear(tv1);
+ tv_blob_set_ret(tv1, b);
+}
+
+/// Make a copy of list "tv1" and append list "tv2".
+static int eval_addlist(typval_T *tv1, typval_T *tv2)
+{
+ typval_T var3;
+ // Concatenate Lists.
+ if (tv_list_concat(tv1->vval.v_list, tv2->vval.v_list, &var3) == FAIL) {
+ tv_clear(tv1);
+ tv_clear(tv2);
+ return FAIL;
+ }
+ tv_clear(tv1);
+ *tv1 = var3;
+ return OK;
+}
+
/// Handle fourth level expression:
-/// + number addition
+/// + number addition, concatenation of list or blob
/// - number subtraction
/// . string concatenation
/// .. string concatenation
@@ -2590,29 +2790,24 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
/// `arg` is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval5(char **arg, typval_T *rettv, int evaluate)
+static int eval5(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- typval_T var2;
- typval_T var3;
- int op;
- varnumber_T n1, n2;
- float_T f1 = 0, f2 = 0;
- char *p;
-
// Get the first variable.
- if (eval6(arg, rettv, evaluate, false) == FAIL) {
+ if (eval6(arg, rettv, evalarg, false) == FAIL) {
return FAIL;
}
// Repeat computing, until no '+', '-' or '.' is following.
- for (;;) {
- op = (char_u)(**arg);
- if (op != '+' && op != '-' && op != '.') {
+ while (true) {
+ int op = (uint8_t)(**arg);
+ bool concat = op == '.';
+ if (op != '+' && op != '-' && !concat) {
break;
}
+ const bool evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE);
if ((op != '+' || (rettv->v_type != VAR_LIST && rettv->v_type != VAR_BLOB))
- && (op == '.' || rettv->v_type != VAR_FLOAT)) {
+ && (op == '.' || rettv->v_type != VAR_FLOAT) && evaluate) {
// For "list + ...", an illegal use of the first operand as
// a number cannot be determined before evaluating the 2nd
// operand: if this is also a list, all is ok.
@@ -2620,7 +2815,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
// we know that the first operand needs to be a string or number
// without evaluating the 2nd operand. So check before to avoid
// side effects after an error.
- if (evaluate && !tv_check_str(rettv)) {
+ if ((op == '.' && !tv_check_str(rettv)) || (op != '.' && !tv_check_num(rettv))) {
tv_clear(rettv);
return FAIL;
}
@@ -2631,7 +2826,8 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
(*arg)++;
}
*arg = skipwhite(*arg + 1);
- if (eval6(arg, &var2, evaluate, op == '.') == FAIL) {
+ typval_T var2;
+ if (eval6(arg, &var2, evalarg, op == '.') == FAIL) {
tv_clear(rettv);
return FAIL;
}
@@ -2649,38 +2845,20 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
tv_clear(&var2);
return FAIL;
}
- p = concat_str(s1, s2);
+ char *p = concat_str(s1, s2);
tv_clear(rettv);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = p;
- } else if (op == '+' && rettv->v_type == VAR_BLOB
- && var2.v_type == VAR_BLOB) {
- const blob_T *const b1 = rettv->vval.v_blob;
- const blob_T *const b2 = var2.vval.v_blob;
- blob_T *const b = tv_blob_alloc();
-
- for (int i = 0; i < tv_blob_len(b1); i++) {
- ga_append(&b->bv_ga, tv_blob_get(b1, i));
- }
- for (int i = 0; i < tv_blob_len(b2); i++) {
- ga_append(&b->bv_ga, tv_blob_get(b2, i));
- }
-
- tv_clear(rettv);
- tv_blob_set_ret(rettv, b);
- } else if (op == '+' && rettv->v_type == VAR_LIST
- && var2.v_type == VAR_LIST) {
- // Concatenate Lists.
- if (tv_list_concat(rettv->vval.v_list, var2.vval.v_list, &var3)
- == FAIL) {
- tv_clear(rettv);
- tv_clear(&var2);
+ } else if (op == '+' && rettv->v_type == VAR_BLOB && var2.v_type == VAR_BLOB) {
+ eval_addblob(rettv, &var2);
+ } else if (op == '+' && rettv->v_type == VAR_LIST && var2.v_type == VAR_LIST) {
+ if (eval_addlist(rettv, &var2) == FAIL) {
return FAIL;
}
- tv_clear(rettv);
- *rettv = var3;
} else {
bool error = false;
+ varnumber_T n1, n2;
+ float_T f1 = 0, f2 = 0;
if (rettv->v_type == VAR_FLOAT) {
f1 = rettv->vval.v_float;
@@ -2750,32 +2928,30 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
/// expression. Is advanced to the next non-whitespace
/// character after the recognized expression.
/// @param[out] rettv Location where result is saved.
-/// @param[in] evaluate If not true, rettv is not populated.
/// @param[in] want_string True if "." is string_concatenation, otherwise
/// float
/// @return OK or FAIL.
-static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
+static int eval6(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool want_string)
FUNC_ATTR_NO_SANITIZE_UNDEFINED
{
- typval_T var2;
- int op;
- varnumber_T n1, n2;
bool use_float = false;
- float_T f1 = 0, f2 = 0;
- bool error = false;
// Get the first variable.
- if (eval7(arg, rettv, evaluate, want_string) == FAIL) {
+ if (eval7(arg, rettv, evalarg, want_string) == FAIL) {
return FAIL;
}
// Repeat computing, until no '*', '/' or '%' is following.
- for (;;) {
- op = (char_u)(**arg);
+ while (true) {
+ int op = (uint8_t)(**arg);
if (op != '*' && op != '/' && op != '%') {
break;
}
+ varnumber_T n1, n2;
+ float_T f1 = 0, f2 = 0;
+ bool error = false;
+ const bool evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE);
if (evaluate) {
if (rettv->v_type == VAR_FLOAT) {
f1 = rettv->vval.v_float;
@@ -2794,7 +2970,8 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
// Get the second variable.
*arg = skipwhite(*arg + 1);
- if (eval7(arg, &var2, evaluate, false) == FAIL) {
+ typval_T var2;
+ if (eval7(arg, &var2, evalarg, false) == FAIL) {
return FAIL;
}
@@ -2885,8 +3062,9 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
/// @param want_string after "." operator
///
/// @return OK or FAIL.
-static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
+static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool want_string)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
int ret = OK;
static int recurse = 0;
@@ -2927,29 +3105,35 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
case '7':
case '8':
case '9':
- ret = get_number_tv(arg, rettv, evaluate, want_string);
+ ret = eval_number(arg, rettv, evaluate, want_string);
+
+ // Apply prefixed "-" and "+" now. Matters especially when
+ // "->" follows.
+ if (ret == OK && evaluate && end_leader > start_leader) {
+ ret = eval7_leader(rettv, true, start_leader, &end_leader);
+ }
break;
// String constant: "string".
case '"':
- ret = get_string_tv(arg, rettv, evaluate);
+ ret = eval_string(arg, rettv, evaluate, false);
break;
// Literal string constant: 'str''ing'.
case '\'':
- ret = get_lit_string_tv(arg, rettv, evaluate);
+ ret = eval_lit_string(arg, rettv, evaluate, false);
break;
// List: [expr, expr]
case '[':
- ret = get_list_tv(arg, rettv, evaluate);
+ ret = eval_list(arg, rettv, evalarg);
break;
// Dictionary: #{key: val, key: val}
case '#':
if ((*arg)[1] == '{') {
(*arg)++;
- ret = eval_dict(arg, rettv, evaluate, true);
+ ret = eval_dict(arg, rettv, evalarg, true);
} else {
ret = NOTDONE;
}
@@ -2958,19 +3142,24 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// Lambda: {arg, arg -> expr}
// Dictionary: {'key': val, 'key': val}
case '{':
- ret = get_lambda_tv(arg, rettv, evaluate);
+ ret = get_lambda_tv(arg, rettv, evalarg);
if (ret == NOTDONE) {
- ret = eval_dict(arg, rettv, evaluate, false);
+ ret = eval_dict(arg, rettv, evalarg, false);
}
break;
// Option value: &name
case '&':
- ret = get_option_tv((const char **)arg, rettv, evaluate);
+ ret = eval_option((const char **)arg, rettv, evaluate);
break;
// Environment variable: $VAR.
+ // Interpolated string: $"string" or $'string'.
case '$':
- ret = get_env_tv(arg, rettv, evaluate);
+ if ((*arg)[1] == '"' || (*arg)[1] == '\'') {
+ ret = eval_interp_string(arg, rettv, evaluate);
+ } else {
+ ret = eval_env_var(arg, rettv, evaluate);
+ }
break;
// Register contents: @r.
@@ -2988,7 +3177,8 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// nested expression: (expression).
case '(':
*arg = skipwhite(*arg + 1);
- ret = eval1(arg, rettv, evaluate); // recursive!
+
+ ret = eval1(arg, rettv, evalarg); // recursive!
if (**arg == ')') {
(*arg)++;
} else if (ret == OK) {
@@ -3016,12 +3206,17 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
if (len <= 0) {
ret = FAIL;
} else {
- if (**arg == '(') { // recursive!
- ret = eval_func(arg, s, len, rettv, evaluate, NULL);
+ const int flags = evalarg == NULL ? 0 : evalarg->eval_flags;
+ if (*skipwhite(*arg) == '(') {
+ // "name(..." recursive!
+ *arg = skipwhite(*arg);
+ ret = eval_func(arg, evalarg, s, len, rettv, flags, NULL);
} else if (evaluate) {
- ret = get_var_tv((const char *)s, len, rettv, NULL, true, false);
+ // get value of variable
+ ret = eval_variable(s, len, rettv, NULL, true, false);
} else {
- check_vars((const char *)s, (size_t)len);
+ // skip the name
+ check_vars(s, (size_t)len);
ret = OK;
}
}
@@ -3033,13 +3228,12 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// Handle following '[', '(' and '.' for expr[expr], expr.name,
// expr(expr), expr->name(expr)
if (ret == OK) {
- ret = handle_subscript((const char **)arg, rettv, evaluate, true,
- (char *)start_leader, &end_leader);
+ ret = handle_subscript((const char **)arg, rettv, evalarg, true);
}
// Apply logical NOT and unary '-', from right to left, ignore '+'.
if (ret == OK && evaluate && end_leader > start_leader) {
- ret = eval7_leader(rettv, (char *)start_leader, &end_leader);
+ ret = eval7_leader(rettv, false, start_leader, &end_leader);
}
recurse--;
@@ -3049,12 +3243,14 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
/// Apply the leading "!" and "-" before an eval7 expression to "rettv".
/// Adjusts "end_leaderp" until it is at "start_leader".
///
+/// @param numeric_only if true only handle "+" and "-".
+///
/// @return OK on success, FAIL on failure.
-static int eval7_leader(typval_T *const rettv, const char *const start_leader,
- const char **const end_leaderp)
+static int eval7_leader(typval_T *const rettv, const bool numeric_only,
+ const char *const start_leader, const char **const end_leaderp)
FUNC_ATTR_NONNULL_ALL
{
- const char *end_leader = (char *)(*end_leaderp);
+ const char *end_leader = *end_leaderp;
int ret = OK;
bool error = false;
varnumber_T val = 0;
@@ -3072,6 +3268,10 @@ static int eval7_leader(typval_T *const rettv, const char *const start_leader,
while (end_leader > start_leader) {
end_leader--;
if (*end_leader == '!') {
+ if (numeric_only) {
+ end_leader++;
+ break;
+ }
if (rettv->v_type == VAR_FLOAT) {
f = !(bool)f;
} else {
@@ -3104,15 +3304,16 @@ static int eval7_leader(typval_T *const rettv, const char *const start_leader,
/// to the name of the Lua function to call (after the
/// "v:lua." prefix).
/// @return OK on success, FAIL on failure.
-static int call_func_rettv(char **const arg, typval_T *const rettv, const bool evaluate,
- dict_T *const selfdict, typval_T *const basetv,
+static int call_func_rettv(char **const arg, evalarg_T *const evalarg, typval_T *const rettv,
+ const bool evaluate, dict_T *const selfdict, typval_T *const basetv,
const char *const lua_funcname)
- FUNC_ATTR_NONNULL_ARG(1, 2)
+ FUNC_ATTR_NONNULL_ARG(1, 3)
{
partial_T *pt = NULL;
typval_T functv;
const char *funcname;
bool is_lua = false;
+ int ret;
// need to copy the funcref so that we can clear rettv
if (evaluate) {
@@ -3126,6 +3327,11 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e
funcname = is_lua ? lua_funcname : partial_name(pt);
} else {
funcname = functv.vval.v_string;
+ if (funcname == NULL || *funcname == NUL) {
+ emsg(_(e_empty_function_name));
+ ret = FAIL;
+ goto theend;
+ }
}
} else {
funcname = "";
@@ -3138,9 +3344,10 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e
funcexe.fe_partial = pt;
funcexe.fe_selfdict = selfdict;
funcexe.fe_basetv = basetv;
- const int ret = get_func_tv(funcname, is_lua ? (int)(*arg - funcname) : -1, rettv,
- arg, &funcexe);
+ ret = get_func_tv(funcname, is_lua ? (int)(*arg - funcname) : -1, rettv,
+ arg, evalarg, &funcexe);
+theend:
// Clear the funcref afterwards, so that deleting it while
// evaluating the arguments is possible (see test55).
if (evaluate) {
@@ -3158,16 +3365,17 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e
/// @return FAIL or OK.
///
/// @note "*arg" is advanced to after the ')'.
-static int eval_lambda(char **const arg, typval_T *const rettv, const bool evaluate,
+static int eval_lambda(char **const arg, typval_T *const rettv, evalarg_T *const evalarg,
const bool verbose)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
// Skip over the ->.
*arg += 2;
typval_T base = *rettv;
rettv->v_type = VAR_UNKNOWN;
- int ret = get_lambda_tv(arg, rettv, evaluate);
+ int ret = get_lambda_tv(arg, rettv, evalarg);
if (ret != OK) {
return FAIL;
} else if (**arg != '(') {
@@ -3181,7 +3389,7 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu
tv_clear(rettv);
ret = FAIL;
} else {
- ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, NULL);
+ ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, NULL);
}
// Clear the funcref afterwards, so that deleting it while
@@ -3198,10 +3406,12 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu
/// @param *arg points to the '-'.
///
/// @return FAIL or OK. "*arg" is advanced to after the ')'.
-static int eval_method(char **const arg, typval_T *const rettv, const bool evaluate,
+static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const evalarg,
const bool verbose)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
+
// Skip over the ->.
*arg += 2;
typval_T base = *rettv;
@@ -3213,7 +3423,7 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu
char *lua_funcname = NULL;
if (strncmp(name, "v:lua.", 6) == 0) {
lua_funcname = name + 6;
- *arg = (char *)skip_luafunc_name((const char *)lua_funcname);
+ *arg = (char *)skip_luafunc_name(lua_funcname);
*arg = skipwhite(*arg); // to detect trailing whitespace later
len = (int)(*arg - lua_funcname);
} else {
@@ -3251,9 +3461,9 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu
rettv->vval.v_partial = vvlua_partial;
rettv->vval.v_partial->pt_refcount++;
}
- ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, lua_funcname);
+ ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname);
} else {
- ret = eval_func(arg, name, len, rettv, evaluate, &base);
+ ret = eval_func(arg, evalarg, name, len, rettv, evaluate ? EVAL_EVALUATE : 0, &base);
}
}
@@ -3272,44 +3482,17 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu
/// @param verbose give error messages
///
/// @returns FAIL or OK. "*arg" is advanced to after the ']'.
-static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
+static int eval_index(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool verbose)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
bool empty1 = false;
bool empty2 = false;
- long n1, n2 = 0;
- ptrdiff_t len = -1;
- int range = false;
- char *key = NULL;
+ bool range = false;
+ const char *key = NULL;
+ ptrdiff_t keylen = -1;
- switch (rettv->v_type) {
- case VAR_FUNC:
- case VAR_PARTIAL:
- if (verbose) {
- emsg(_("E695: Cannot index a Funcref"));
- }
- return FAIL;
- case VAR_FLOAT:
- if (verbose) {
- emsg(_(e_float_as_string));
- }
- return FAIL;
- case VAR_BOOL:
- case VAR_SPECIAL:
- if (verbose) {
- emsg(_("E909: Cannot index a special variable"));
- }
+ if (check_can_index(rettv, evaluate, verbose) == FAIL) {
return FAIL;
- case VAR_UNKNOWN:
- if (evaluate) {
- return FAIL;
- }
- FALLTHROUGH;
- case VAR_STRING:
- case VAR_NUMBER:
- case VAR_LIST:
- case VAR_DICT:
- case VAR_BLOB:
- break;
}
typval_T var1 = TV_INITIAL_VALUE;
@@ -3317,11 +3500,11 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
if (**arg == '.') {
// dict.name
key = *arg + 1;
- for (len = 0; eval_isdictc(key[len]); len++) {}
- if (len == 0) {
+ for (keylen = 0; eval_isdictc(key[keylen]); keylen++) {}
+ if (keylen == 0) {
return FAIL;
}
- *arg = skipwhite(key + len);
+ *arg = skipwhite(key + keylen);
} else {
// something[idx]
//
@@ -3329,7 +3512,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
*arg = skipwhite(*arg + 1);
if (**arg == ':') {
empty1 = true;
- } else if (eval1(arg, &var1, evaluate) == FAIL) { // Recursive!
+ } else if (eval1(arg, &var1, evalarg) == FAIL) { // Recursive!
return FAIL;
} else if (evaluate && !tv_check_str(&var1)) {
// Not a number or string.
@@ -3343,7 +3526,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
*arg = skipwhite(*arg + 1);
if (**arg == ']') {
empty2 = true;
- } else if (eval1(arg, &var2, evaluate) == FAIL) { // Recursive!
+ } else if (eval1(arg, &var2, evalarg) == FAIL) { // Recursive!
if (!empty1) {
tv_clear(&var1);
}
@@ -3373,195 +3556,194 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
}
if (evaluate) {
- n1 = 0;
- if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) {
- n1 = tv_get_number(&var1);
+ int res = eval_index_inner(rettv, range,
+ empty1 ? NULL : &var1, empty2 ? NULL : &var2, false,
+ key, keylen, verbose);
+ if (!empty1) {
tv_clear(&var1);
}
if (range) {
- if (empty2) {
- n2 = -1;
- } else {
- n2 = tv_get_number(&var2);
- tv_clear(&var2);
- }
+ tv_clear(&var2);
}
+ return res;
+ }
+ return OK;
+}
- switch (rettv->v_type) {
- case VAR_NUMBER:
- case VAR_STRING: {
- const char *const s = tv_get_string(rettv);
- char *v;
- len = (ptrdiff_t)strlen(s);
- if (range) {
- // The resulting variable is a substring. If the indexes
- // are out of range the result is empty.
- if (n1 < 0) {
- n1 = len + n1;
- if (n1 < 0) {
- n1 = 0;
- }
- }
- if (n2 < 0) {
- n2 = len + n2;
- } else if (n2 >= len) {
- n2 = len;
- }
- if (n1 >= len || n2 < 0 || n1 > n2) {
- v = NULL;
- } else {
- v = xmemdupz(s + n1, (size_t)(n2 - n1 + 1));
- }
- } else {
- // The resulting variable is a string of a single
- // character. If the index is too big or negative the
- // result is empty.
- if (n1 >= len || n1 < 0) {
- v = NULL;
- } else {
- v = xmemdupz(s + n1, 1);
- }
+/// Check if "rettv" can have an [index] or [sli:ce]
+static int check_can_index(typval_T *rettv, bool evaluate, bool verbose)
+{
+ switch (rettv->v_type) {
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ if (verbose) {
+ emsg(_(e_cannot_index_a_funcref));
+ }
+ return FAIL;
+ case VAR_FLOAT:
+ if (verbose) {
+ emsg(_(e_using_float_as_string));
+ }
+ return FAIL;
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ if (verbose) {
+ emsg(_(e_cannot_index_special_variable));
+ }
+ return FAIL;
+ case VAR_UNKNOWN:
+ if (evaluate) {
+ emsg(_(e_cannot_index_special_variable));
+ return FAIL;
+ }
+ FALLTHROUGH;
+ case VAR_STRING:
+ case VAR_NUMBER:
+ case VAR_LIST:
+ case VAR_DICT:
+ case VAR_BLOB:
+ break;
+ }
+ return OK;
+}
+
+/// slice() function
+void f_slice(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (check_can_index(argvars, true, false) == OK) {
+ tv_copy(argvars, rettv);
+ eval_index_inner(rettv, true, argvars + 1,
+ argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2,
+ true, NULL, 0, false);
+ }
+}
+
+/// Apply index or range to "rettv".
+///
+/// @param var1 the first index, NULL for [:expr].
+/// @param var2 the second index, NULL for [expr] and [expr: ]
+/// @param exclusive true for slice(): second index is exclusive, use character
+/// index for string.
+/// Alternatively, "key" is not NULL, then key[keylen] is the dict index.
+static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typval_T *var2,
+ bool exclusive, const char *key, ptrdiff_t keylen, bool verbose)
+{
+ varnumber_T n1 = 0;
+ varnumber_T n2 = 0;
+ if (var1 != NULL && rettv->v_type != VAR_DICT) {
+ n1 = tv_get_number(var1);
+ }
+
+ if (is_range) {
+ if (rettv->v_type == VAR_DICT) {
+ if (verbose) {
+ emsg(_(e_cannot_slice_dictionary));
}
- tv_clear(rettv);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = v;
- break;
+ return FAIL;
}
- case VAR_BLOB:
- len = tv_blob_len(rettv->vval.v_blob);
- if (range) {
- // The resulting variable is a sub-blob. If the indexes
- // are out of range the result is empty.
- if (n1 < 0) {
- n1 = len + n1;
- if (n1 < 0) {
- n1 = 0;
- }
- }
- if (n2 < 0) {
- n2 = len + n2;
- } else if (n2 >= len) {
- n2 = len - 1;
- }
- if (n1 >= len || n2 < 0 || n1 > n2) {
- tv_clear(rettv);
- rettv->v_type = VAR_BLOB;
- rettv->vval.v_blob = NULL;
- } else {
- blob_T *const blob = tv_blob_alloc();
- ga_grow(&blob->bv_ga, (int)(n2 - n1 + 1));
- blob->bv_ga.ga_len = (int)(n2 - n1 + 1);
- for (long i = n1; i <= n2; i++) {
- tv_blob_set(blob, (int)(i - n1), tv_blob_get(rettv->vval.v_blob, (int)i));
- }
- tv_clear(rettv);
- tv_blob_set_ret(rettv, blob);
- }
+ if (var2 != NULL) {
+ n2 = tv_get_number(var2);
+ } else {
+ n2 = VARNUMBER_MAX;
+ }
+ }
+
+ switch (rettv->v_type) {
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ case VAR_FUNC:
+ case VAR_FLOAT:
+ case VAR_PARTIAL:
+ case VAR_UNKNOWN:
+ break; // Not evaluating, skipping over subscript
+
+ case VAR_NUMBER:
+ case VAR_STRING: {
+ const char *const s = tv_get_string(rettv);
+ char *v;
+ int len = (int)strlen(s);
+ if (exclusive) {
+ if (is_range) {
+ v = string_slice(s, n1, n2, exclusive);
} else {
- // The resulting variable is a byte value.
- // If the index is too big or negative that is an error.
- if (n1 < 0) {
- n1 = len + n1;
- }
- if (n1 < len && n1 >= 0) {
- const int v = (int)tv_blob_get(rettv->vval.v_blob, (int)n1);
- tv_clear(rettv);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = v;
- } else {
- semsg(_(e_blobidx), (int64_t)n1);
- }
+ v = char_from_string(s, n1);
}
- break;
- case VAR_LIST:
- len = tv_list_len(rettv->vval.v_list);
+ } else if (is_range) {
+ // The resulting variable is a substring. If the indexes
+ // are out of range the result is empty.
if (n1 < 0) {
n1 = len + n1;
- }
- if (!empty1 && (n1 < 0 || n1 >= len)) {
- // For a range we allow invalid values and return an empty
- // list. A list index out of range is an error.
- if (!range) {
- if (verbose) {
- semsg(_(e_listidx), (int64_t)n1);
- }
- return FAIL;
+ if (n1 < 0) {
+ n1 = 0;
}
- n1 = len;
}
- if (range) {
- list_T *l;
- listitem_T *item;
-
- if (n2 < 0) {
- n2 = len + n2;
- } else if (n2 >= len) {
- n2 = len - 1;
- }
- if (!empty2 && (n2 < 0 || n2 + 1 < n1)) {
- n2 = -1;
- }
- l = tv_list_alloc(n2 - n1 + 1);
- item = tv_list_find(rettv->vval.v_list, (int)n1);
- while (n1++ <= n2) {
- tv_list_append_tv(l, TV_LIST_ITEM_TV(item));
- item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item);
- }
- tv_clear(rettv);
- tv_list_set_ret(rettv, l);
+ if (n2 < 0) {
+ n2 = len + n2;
+ } else if (n2 >= len) {
+ n2 = len;
+ }
+ if (n1 >= len || n2 < 0 || n1 > n2) {
+ v = NULL;
} else {
- tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, (int)n1)), &var1);
- tv_clear(rettv);
- *rettv = var1;
+ v = xmemdupz(s + n1, (size_t)n2 - (size_t)n1 + 1);
}
- break;
- case VAR_DICT: {
- if (range) {
- if (verbose) {
- emsg(_(e_dictrange));
- }
- if (len == -1) {
- tv_clear(&var1);
- }
- return FAIL;
+ } else {
+ // The resulting variable is a string of a single
+ // character. If the index is too big or negative the
+ // result is empty.
+ if (n1 >= len || n1 < 0) {
+ v = NULL;
+ } else {
+ v = xmemdupz(s + n1, 1);
}
+ }
+ tv_clear(rettv);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = v;
+ break;
+ }
- if (len == -1) {
- key = (char *)tv_get_string_chk(&var1);
- if (key == NULL) {
- tv_clear(&var1);
- return FAIL;
- }
- }
+ case VAR_BLOB:
+ tv_blob_slice_or_index(rettv->vval.v_blob, is_range, n1, n2, exclusive, rettv);
+ break;
- dictitem_T *const item = tv_dict_find(rettv->vval.v_dict,
- (const char *)key, len);
+ case VAR_LIST:
+ if (var1 == NULL) {
+ n1 = 0;
+ }
+ if (var2 == NULL) {
+ n2 = VARNUMBER_MAX;
+ }
+ if (tv_list_slice_or_index(rettv->vval.v_list,
+ is_range, n1, n2, exclusive, rettv, verbose) == FAIL) {
+ return FAIL;
+ }
+ break;
- if (item == NULL && verbose) {
- semsg(_(e_dictkey), key);
- }
- if (len == -1) {
- tv_clear(&var1);
- }
- if (item == NULL || tv_is_luafunc(&item->di_tv)) {
+ case VAR_DICT: {
+ if (key == NULL) {
+ key = tv_get_string_chk(var1);
+ if (key == NULL) {
return FAIL;
}
+ }
- tv_copy(&item->di_tv, &var1);
- tv_clear(rettv);
- *rettv = var1;
- break;
+ dictitem_T *const item = tv_dict_find(rettv->vval.v_dict, key, keylen);
+
+ if (item == NULL && verbose) {
+ semsg(_(e_dictkey), key);
}
- case VAR_BOOL:
- case VAR_SPECIAL:
- case VAR_FUNC:
- case VAR_FLOAT:
- case VAR_PARTIAL:
- case VAR_UNKNOWN:
- break; // Not evaluating, skipping over subscript
+ if (item == NULL || tv_is_luafunc(&item->di_tv)) {
+ return FAIL;
}
- }
+ typval_T tmp;
+ tv_copy(&item->di_tv, &tmp);
+ tv_clear(rettv);
+ *rettv = tmp;
+ break;
+ }
+ }
return OK;
}
@@ -3573,7 +3755,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
/// @param[in] evaluate If not true, rettv is not populated.
///
/// @return OK or FAIL.
-int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate)
+int eval_option(const char **const arg, typval_T *const rettv, const bool evaluate)
FUNC_ATTR_NONNULL_ARG(1)
{
const bool working = (**arg == '+'); // has("+option")
@@ -3593,38 +3775,38 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval
return OK;
}
- long numval;
- char *stringval;
int ret = OK;
-
+ bool hidden;
char c = *option_end;
*option_end = NUL;
- getoption_T opt_type = get_option_value(*arg, &numval,
- rettv == NULL ? NULL : &stringval, NULL, scope);
+ OptVal value = get_option_value(*arg, NULL, scope, &hidden);
- if (opt_type == gov_unknown) {
- if (rettv != NULL) {
+ if (rettv != NULL) {
+ switch (value.type) {
+ case kOptValTypeNil:
semsg(_("E113: Unknown option: %s"), *arg);
- }
- ret = FAIL;
- } else if (rettv != NULL) {
- if (opt_type == gov_hidden_string) {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- } else if (opt_type == gov_hidden_bool || opt_type == gov_hidden_number) {
+ ret = FAIL;
+ break;
+ case kOptValTypeBoolean:
rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- } else if (opt_type == gov_bool || opt_type == gov_number) {
+ rettv->vval.v_number = value.data.boolean;
+ break;
+ case kOptValTypeNumber:
rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = numval;
- } else { // string option
+ rettv->vval.v_number = value.data.number;
+ break;
+ case kOptValTypeString:
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = stringval;
+ rettv->vval.v_string = value.data.string.data;
+ break;
+ }
+ } else {
+ // Value isn't being used, free it.
+ optval_free(value);
+
+ if (value.type == kOptValTypeNil || (working && hidden)) {
+ ret = FAIL;
}
- } else if (working && (opt_type == gov_hidden_bool
- || opt_type == gov_hidden_number
- || opt_type == gov_hidden_string)) {
- ret = FAIL;
}
*option_end = c; // put back for error messages
@@ -3636,7 +3818,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval
/// Allocate a variable for a number constant. Also deals with "0z" for blob.
///
/// @return OK or FAIL.
-static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_string)
+static int eval_number(char **arg, typval_T *rettv, bool evaluate, bool want_string)
{
char *p = skipdigits(*arg + 1);
bool get_float = false;
@@ -3702,7 +3884,7 @@ static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_s
// decimal, hex or octal number
int len;
varnumber_T n;
- vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true);
+ vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true, NULL);
if (len == 0) {
if (evaluate) {
semsg(_(e_invexpr2), *arg);
@@ -3718,60 +3900,88 @@ static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_s
return OK;
}
-/// Allocate a variable for a string constant.
+/// Evaluate a string constant and put the result in "rettv".
+/// "*arg" points to the double quote or to after it when "interpolate" is true.
+/// When "interpolate" is true reduce "{{" to "{", reduce "}}" to "}" and stop
+/// at a single "{".
///
/// @return OK or FAIL.
-static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
+static int eval_string(char **arg, typval_T *rettv, bool evaluate, bool interpolate)
{
char *p;
- unsigned int extra = 0;
+ const char *const arg_end = *arg + strlen(*arg);
+ unsigned extra = interpolate ? 1 : 0;
+ const int off = interpolate ? 0 : 1;
// Find the end of the string, skipping backslashed characters.
- for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
+ for (p = *arg + off; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
if (*p == '\\' && p[1] != NUL) {
p++;
// A "\<x>" form occupies at least 4 characters, and produces up
// to 9 characters (6 for the char and 3 for a modifier):
// reserve space for 5 extra.
if (*p == '<') {
+ int modifiers = 0;
+ int flags = FSK_KEYCODE | FSK_IN_STRING;
+
extra += 5;
+
+ // Skip to the '>' to avoid using '{' inside for string
+ // interpolation.
+ if (p[1] != '*') {
+ flags |= FSK_SIMPLIFY;
+ }
+ if (find_special_key((const char **)&p, (size_t)(arg_end - p),
+ &modifiers, flags, NULL) != 0) {
+ p--; // leave "p" on the ">"
+ }
+ }
+ } else if (interpolate && (*p == '{' || *p == '}')) {
+ if (*p == '{' && p[1] != '{') { // start of expression
+ break;
}
+ p++;
+ if (p[-1] == '}' && *p != '}') { // single '}' is an error
+ semsg(_(e_stray_closing_curly_str), *arg);
+ return FAIL;
+ }
+ extra--; // "{{" becomes "{", "}}" becomes "}"
}
}
- if (*p != '"') {
+ if (*p != '"' && !(interpolate && *p == '{')) {
semsg(_("E114: Missing quote: %s"), *arg);
return FAIL;
}
// If only parsing, set *arg and return here
if (!evaluate) {
- *arg = p + 1;
+ *arg = p + off;
return OK;
}
// Copy the string into allocated memory, handling backslashed
// characters.
- const int len = (int)(p - *arg + extra);
- char *name = xmalloc((size_t)len);
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = name;
+ const int len = (int)(p - *arg + extra);
+ rettv->vval.v_string = xmalloc((size_t)len);
+ char *end = rettv->vval.v_string;
- for (p = *arg + 1; *p != NUL && *p != '"';) {
+ for (p = *arg + off; *p != NUL && *p != '"';) {
if (*p == '\\') {
switch (*++p) {
case 'b':
- *name++ = BS; ++p; break;
+ *end++ = BS; ++p; break;
case 'e':
- *name++ = ESC; ++p; break;
+ *end++ = ESC; ++p; break;
case 'f':
- *name++ = FF; ++p; break;
+ *end++ = FF; ++p; break;
case 'n':
- *name++ = NL; ++p; break;
+ *end++ = NL; ++p; break;
case 'r':
- *name++ = CAR; ++p; break;
+ *end++ = CAR; ++p; break;
case 't':
- *name++ = TAB; ++p; break;
+ *end++ = TAB; ++p; break;
case 'X': // hex: "\x1", "\x12"
case 'x':
@@ -3797,9 +4007,9 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
// For "\u" store the number according to
// 'encoding'.
if (c != 'X') {
- name += utf_char2bytes(nr, name);
+ end += utf_char2bytes(nr, end);
} else {
- *name++ = (char)nr;
+ *end++ = (char)nr;
}
}
break;
@@ -3813,14 +4023,14 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
case '5':
case '6':
case '7':
- *name = (char)(*p++ - '0');
+ *end = (char)(*p++ - '0');
if (*p >= '0' && *p <= '7') {
- *name = (char)((*name << 3) + *p++ - '0');
+ *end = (char)((*end << 3) + *p++ - '0');
if (*p >= '0' && *p <= '7') {
- *name = (char)((*name << 3) + *p++ - '0');
+ *end = (char)((*end << 3) + *p++ - '0');
}
}
- name++;
+ end++;
break;
// Special key, e.g.: "\<C-W>"
@@ -3830,11 +4040,12 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
if (p[1] != '*') {
flags |= FSK_SIMPLIFY;
}
- extra = trans_special((const char **)&p, strlen(p), name, flags, false, NULL);
+ extra = trans_special((const char **)&p, (size_t)(arg_end - p),
+ end, flags, false, NULL);
if (extra != 0) {
- name += extra;
- if (name >= rettv->vval.v_string + len) {
- iemsg("get_string_tv() used more space than allocated");
+ end += extra;
+ if (end >= rettv->vval.v_string + len) {
+ iemsg("eval_string() used more space than allocated");
}
break;
}
@@ -3842,15 +4053,21 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
FALLTHROUGH;
default:
- mb_copy_char((const char **)&p, &name);
+ mb_copy_char((const char **)&p, &end);
break;
}
} else {
- mb_copy_char((const char **)&p, &name);
+ if (interpolate && (*p == '{' || *p == '}')) {
+ if (*p == '{' && p[1] != '{') { // start of expression
+ break;
+ }
+ p++; // reduce "{{" to "{" and "}}" to "}"
+ }
+ mb_copy_char((const char **)&p, &end);
}
}
- *name = NUL;
- if (*p != NUL) { // just in case
+ *end = NUL;
+ if (*p == '"' && !interpolate) {
p++;
}
*arg = p;
@@ -3859,63 +4076,146 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
}
/// Allocate a variable for a 'str''ing' constant.
+/// When "interpolate" is true reduce "{{" to "{" and stop at a single "{".
///
-/// @return OK or FAIL.
-static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate)
+/// @return OK when a "rettv" was set to the string.
+/// FAIL on error, "rettv" is not set.
+static int eval_lit_string(char **arg, typval_T *rettv, bool evaluate, bool interpolate)
{
char *p;
- int reduce = 0;
+ int reduce = interpolate ? -1 : 0;
+ const int off = interpolate ? 0 : 1;
// Find the end of the string, skipping ''.
- for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p)) {
+ for (p = *arg + off; *p != NUL; MB_PTR_ADV(p)) {
if (*p == '\'') {
if (p[1] != '\'') {
break;
}
reduce++;
p++;
+ } else if (interpolate) {
+ if (*p == '{') {
+ if (p[1] != '{') {
+ break;
+ }
+ p++;
+ reduce++;
+ } else if (*p == '}') {
+ p++;
+ if (*p != '}') {
+ semsg(_(e_stray_closing_curly_str), *arg);
+ return FAIL;
+ }
+ reduce++;
+ }
}
}
- if (*p != '\'') {
+ if (*p != '\'' && !(interpolate && *p == '{')) {
semsg(_("E115: Missing quote: %s"), *arg);
return FAIL;
}
// If only parsing return after setting "*arg"
if (!evaluate) {
- *arg = p + 1;
+ *arg = p + off;
return OK;
}
- // Copy the string into allocated memory, handling '' to ' reduction.
+ // Copy the string into allocated memory, handling '' to ' reduction and
+ // any expressions.
char *str = xmalloc((size_t)((p - *arg) - reduce));
rettv->v_type = VAR_STRING;
rettv->vval.v_string = str;
- for (p = *arg + 1; *p != NUL;) {
+ for (p = *arg + off; *p != NUL;) {
if (*p == '\'') {
if (p[1] != '\'') {
break;
}
p++;
+ } else if (interpolate && (*p == '{' || *p == '}')) {
+ if (*p == '{' && p[1] != '{') {
+ break;
+ }
+ p++;
}
mb_copy_char((const char **)&p, &str);
}
*str = NUL;
- *arg = p + 1;
+ *arg = p + off;
return OK;
}
+/// Evaluate a single or double quoted string possibly containing expressions.
+/// "arg" points to the '$'. The result is put in "rettv".
+///
+/// @return OK or FAIL.
+int eval_interp_string(char **arg, typval_T *rettv, bool evaluate)
+{
+ int ret = OK;
+
+ garray_T ga;
+ ga_init(&ga, 1, 80);
+
+ // *arg is on the '$' character, move it to the first string character.
+ (*arg)++;
+ const int quote = (uint8_t)(**arg);
+ (*arg)++;
+
+ while (true) {
+ typval_T tv;
+ // Get the string up to the matching quote or to a single '{'.
+ // "arg" is advanced to either the quote or the '{'.
+ if (quote == '"') {
+ ret = eval_string(arg, &tv, evaluate, true);
+ } else {
+ ret = eval_lit_string(arg, &tv, evaluate, true);
+ }
+ if (ret == FAIL) {
+ break;
+ }
+ if (evaluate) {
+ ga_concat(&ga, tv.vval.v_string);
+ tv_clear(&tv);
+ }
+
+ if (**arg != '{') {
+ // found terminating quote
+ (*arg)++;
+ break;
+ }
+ char *p = eval_one_expr_in_str(*arg, &ga, evaluate);
+ if (p == NULL) {
+ ret = FAIL;
+ break;
+ }
+ *arg = p;
+ }
+
+ rettv->v_type = VAR_STRING;
+ if (ret != FAIL && evaluate) {
+ ga_append(&ga, NUL);
+ }
+ rettv->vval.v_string = ga.ga_data;
+ return OK;
+}
+
/// @return the function name of the partial.
char *partial_name(partial_T *pt)
FUNC_ATTR_PURE
{
- if (pt->pt_name != NULL) {
- return pt->pt_name;
+ if (pt != NULL) {
+ if (pt->pt_name != NULL) {
+ return pt->pt_name;
+ }
+ if (pt->pt_func != NULL) {
+ return pt->pt_func->uf_name;
+ }
}
- return (char *)pt->pt_func->uf_name;
+ return "";
}
static void partial_free(partial_T *pt)
@@ -3945,9 +4245,11 @@ void partial_unref(partial_T *pt)
/// Allocate a variable for a List and fill it from "*arg".
///
+/// @param arg "*arg" points to the "[".
/// @return OK or FAIL.
-static int get_list_tv(char **arg, typval_T *rettv, int evaluate)
+static int eval_list(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
+ const bool evaluate = evalarg == NULL ? false : evalarg->eval_flags & EVAL_EVALUATE;
list_T *l = NULL;
if (evaluate) {
@@ -3957,7 +4259,7 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate)
*arg = skipwhite(*arg + 1);
while (**arg != ']' && **arg != NUL) {
typval_T tv;
- if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive!
+ if (eval1(arg, &tv, evalarg) == FAIL) { // Recursive!
goto failret;
}
if (evaluate) {
@@ -3965,14 +4267,20 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate)
tv_list_append_owned_tv(l, tv);
}
+ // the comma must come after the value
+ bool had_comma = **arg == ',';
+ if (had_comma) {
+ *arg = skipwhite(*arg + 1);
+ }
+
if (**arg == ']') {
break;
}
- if (**arg != ',') {
+
+ if (!had_comma) {
semsg(_("E696: Missing comma in List: %s"), *arg);
goto failret;
}
- *arg = skipwhite(*arg + 1);
}
if (**arg != ']') {
@@ -4119,7 +4427,7 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_in_previous_funccal)(copyID);
// script-local variables
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
+ for (int i = 1; i <= script_items.ga_len; i++) {
ABORTING(set_ref_in_ht)(&SCRIPT_VARS(i), copyID, NULL);
}
@@ -4368,7 +4676,7 @@ bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
ht_stack_T *ht_stack = NULL;
hashtab_T *cur_ht = ht;
- for (;;) {
+ while (true) {
if (!abort) {
// Mark each item in the hashtab. If the item contains a hashtab
// it is added to ht_stack, if it contains a list it is added to
@@ -4406,7 +4714,7 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
list_stack_T *list_stack = NULL;
list_T *cur_l = l;
- for (;;) {
+ while (true) {
// Mark each item in the list. If the item contains a hashtab
// it is added to ht_stack, if it contains a list it is added to
// list_stack.
@@ -4582,18 +4890,21 @@ static int get_literal_key(char **arg, typval_T *tv)
}
for (p = *arg; ASCII_ISALNUM(*p) || *p == '_' || *p == '-'; p++) {}
tv->v_type = VAR_STRING;
- tv->vval.v_string = xstrnsave(*arg, (size_t)(p - *arg));
+ tv->vval.v_string = xmemdupz(*arg, (size_t)(p - *arg));
*arg = skipwhite(p);
return OK;
}
/// Allocate a variable for a Dictionary and fill it from "*arg".
-/// "literal" is true for #{key: val}
+///
+/// @param arg "*arg" points to the "{".
+/// @param literal true for #{key: val}
///
/// @return OK or FAIL. Returns NOTDONE for {expr}.
-static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
+static int eval_dict(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool literal)
{
+ const bool evaluate = evalarg == NULL ? false : evalarg->eval_flags & EVAL_EVALUATE;
typval_T tv;
char *key = NULL;
char *curly_expr = skipwhite(*arg + 1);
@@ -4607,7 +4918,7 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
// "#{abc}" is never a curly-braces expression.
if (*curly_expr != '}'
&& !literal
- && eval1(&curly_expr, &tv, false) == OK
+ && eval1(&curly_expr, &tv, NULL) == OK
&& *skipwhite(curly_expr) == '}') {
return NOTDONE;
}
@@ -4624,7 +4935,7 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
while (**arg != '}' && **arg != NUL) {
if ((literal
? get_literal_key(arg, &tvkey)
- : eval1(arg, &tvkey, evaluate)) == FAIL) { // recursive!
+ : eval1(arg, &tvkey, evalarg)) == FAIL) { // recursive!
goto failret;
}
if (**arg != ':') {
@@ -4642,21 +4953,21 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
}
*arg = skipwhite(*arg + 1);
- if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive!
+ if (eval1(arg, &tv, evalarg) == FAIL) { // Recursive!
if (evaluate) {
tv_clear(&tvkey);
}
goto failret;
}
if (evaluate) {
- dictitem_T *item = tv_dict_find(d, (const char *)key, -1);
+ dictitem_T *item = tv_dict_find(d, key, -1);
if (item != NULL) {
semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key);
tv_clear(&tvkey);
tv_clear(&tv);
goto failret;
}
- item = tv_dict_item_alloc((const char *)key);
+ item = tv_dict_item_alloc(key);
item->di_tv = tv;
item->di_tv.v_lock = VAR_UNLOCKED;
if (tv_dict_add(d, item) == FAIL) {
@@ -4665,14 +4976,19 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
}
tv_clear(&tvkey);
+ // the comma must come after the value
+ bool had_comma = **arg == ',';
+ if (had_comma) {
+ *arg = skipwhite(*arg + 1);
+ }
+
if (**arg == '}') {
break;
}
- if (**arg != ',') {
+ if (!had_comma) {
semsg(_("E722: Missing comma in Dictionary: %s"), *arg);
goto failret;
}
- *arg = skipwhite(*arg + 1);
}
if (**arg != '}') {
@@ -4729,7 +5045,7 @@ size_t string2float(const char *const text, float_T *const ret_value)
/// @param arg Points to the '$'. It is advanced to after the name.
///
/// @return FAIL if the name is invalid.
-static int get_env_tv(char **arg, typval_T *rettv, int evaluate)
+static int eval_env_var(char **arg, typval_T *rettv, int evaluate)
{
(*arg)++;
char *name = *arg;
@@ -4755,6 +5071,7 @@ static int get_env_tv(char **arg, typval_T *rettv, int evaluate)
name[len] = (char)cc;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = string;
+ rettv->v_lock = VAR_UNLOCKED;
}
return OK;
@@ -4769,44 +5086,280 @@ void assert_error(garray_T *gap)
// Make sure v:errors is a list.
set_vim_var_list(VV_ERRORS, tv_list_alloc(1));
}
- tv_list_append_string(vimvars[VV_ERRORS].vv_list,
- (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len);
+ tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len);
}
-/// Implementation of map() and filter().
-void filter_map(typval_T *argvars, typval_T *rettv, int map)
+/// Implementation of map() and filter() for a Dict.
+static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name,
+ const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{
- list_T *l = NULL;
- dict_T *d = NULL;
- blob_T *b = NULL;
- int rem = false;
- char *ermsg = map ? "map()" : "filter()";
- const char *const arg_errmsg = (map
- ? N_("map() argument")
- : N_("filter() argument"));
- int save_did_emsg;
+ if (filtermap == FILTERMAP_MAPNEW) {
+ rettv->v_type = VAR_DICT;
+ rettv->vval.v_dict = NULL;
+ }
+ if (d == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
+ return;
+ }
+
+ dict_T *d_ret = NULL;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_dict_alloc_ret(rettv);
+ d_ret = rettv->vval.v_dict;
+ }
+
+ vimvars[VV_KEY].vv_type = VAR_STRING;
+
+ const VarLockStatus prev_lock = d->dv_lock;
+ if (d->dv_lock == VAR_UNLOCKED) {
+ d->dv_lock = VAR_LOCKED;
+ }
+ hash_lock(&d->dv_hashtab);
+ TV_DICT_ITER(d, di, {
+ if (filtermap == FILTERMAP_MAP
+ && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
+ || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
+ break;
+ }
+ vimvars[VV_KEY].vv_str = xstrdup(di->di_key);
+ typval_T newtv;
+ bool rem;
+ int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
+ tv_clear(&vimvars[VV_KEY].vv_tv);
+ if (r == FAIL || did_emsg) {
+ tv_clear(&newtv);
+ break;
+ }
+ if (filtermap == FILTERMAP_MAP) {
+ // map(): replace the dict item value
+ tv_clear(&di->di_tv);
+ newtv.v_lock = VAR_UNLOCKED;
+ di->di_tv = newtv;
+ } else if (filtermap == FILTERMAP_MAPNEW) {
+ // mapnew(): add the item value to the new dict
+ r = tv_dict_add_tv(d_ret, di->di_key, strlen(di->di_key), &newtv);
+ tv_clear(&newtv);
+ if (r == FAIL) {
+ break;
+ }
+ } else if (filtermap == FILTERMAP_FILTER && rem) {
+ // filter(false): remove the item from the dict
+ if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
+ || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
+ break;
+ }
+ tv_dict_item_remove(d, di);
+ }
+ });
+ hash_unlock(&d->dv_hashtab);
+ d->dv_lock = prev_lock;
+}
+
+/// Implementation of map() and filter() for a Blob.
+static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
+ const char *arg_errmsg, typval_T *rettv)
+{
+ if (filtermap == FILTERMAP_MAPNEW) {
+ rettv->v_type = VAR_BLOB;
+ rettv->vval.v_blob = NULL;
+ }
+ blob_T *b = blob_arg;
+ if (b == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE))) {
+ return;
+ }
+
+ blob_T *b_ret = b;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_blob_copy(b, rettv);
+ b_ret = rettv->vval.v_blob;
+ }
+
+ vimvars[VV_KEY].vv_type = VAR_NUMBER;
+
+ const VarLockStatus prev_lock = b->bv_lock;
+ if (b->bv_lock == 0) {
+ b->bv_lock = VAR_LOCKED;
+ }
+
+ for (int i = 0, idx = 0; i < b->bv_ga.ga_len; i++) {
+ const varnumber_T val = tv_blob_get(b, i);
+ typval_T tv = {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_number = val,
+ };
+ vimvars[VV_KEY].vv_nr = idx;
+ typval_T newtv;
+ bool rem;
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+ || did_emsg) {
+ break;
+ }
+ if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
+ tv_clear(&newtv);
+ emsg(_(e_invalblob));
+ break;
+ }
+ if (filtermap != FILTERMAP_FILTER) {
+ if (newtv.vval.v_number != val) {
+ tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
+ }
+ } else if (rem) {
+ char *const p = (char *)blob_arg->bv_ga.ga_data;
+ memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
+ b->bv_ga.ga_len--;
+ i--;
+ }
+ idx++;
+ }
+
+ b->bv_lock = prev_lock;
+}
+
+/// Implementation of map() and filter() for a String.
+static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr,
+ typval_T *rettv)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ vimvars[VV_KEY].vv_type = VAR_NUMBER;
+
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+ int len = 0;
int idx = 0;
+ for (const char *p = str; *p != NUL; p += len) {
+ len = utfc_ptr2len(p);
+ typval_T tv = {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = xmemdupz(p, (size_t)len),
+ };
- // Always return the first argument, also on failure.
- tv_copy(&argvars[0], rettv);
+ vimvars[VV_KEY].vv_nr = idx;
+ typval_T newtv;
+ bool rem;
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+ || did_emsg) {
+ tv_clear(&newtv);
+ tv_clear(&tv);
+ break;
+ } else if (filtermap != FILTERMAP_FILTER) {
+ if (newtv.v_type != VAR_STRING) {
+ tv_clear(&newtv);
+ tv_clear(&tv);
+ emsg(_(e_stringreq));
+ break;
+ } else {
+ ga_concat(&ga, newtv.vval.v_string);
+ }
+ } else if (!rem) {
+ ga_concat(&ga, tv.vval.v_string);
+ }
- if (argvars[0].v_type == VAR_BLOB) {
- if ((b = argvars[0].vval.v_blob) == NULL) {
- return;
+ tv_clear(&newtv);
+ tv_clear(&tv);
+
+ idx++;
+ }
+ ga_append(&ga, NUL);
+ rettv->vval.v_string = ga.ga_data;
+}
+
+/// Implementation of map() and filter() for a List.
+static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name,
+ const char *arg_errmsg, typval_T *expr, typval_T *rettv)
+{
+ if (filtermap == FILTERMAP_MAPNEW) {
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = NULL;
+ }
+ if (l == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
+ return;
+ }
+
+ list_T *l_ret = NULL;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ l_ret = rettv->vval.v_list;
+ }
+
+ vimvars[VV_KEY].vv_type = VAR_NUMBER;
+
+ const VarLockStatus prev_lock = tv_list_locked(l);
+ if (tv_list_locked(l) == VAR_UNLOCKED) {
+ tv_list_set_lock(l, VAR_LOCKED);
+ }
+
+ int idx = 0;
+ for (listitem_T *li = tv_list_first(l); li != NULL;) {
+ if (filtermap == FILTERMAP_MAP
+ && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, TV_TRANSLATE)) {
+ break;
}
- } else if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) == NULL
- || (!map
- && value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
- return;
+ vimvars[VV_KEY].vv_nr = idx;
+ typval_T newtv;
+ bool rem;
+ if (filter_map_one(TV_LIST_ITEM_TV(li), expr, filtermap, &newtv, &rem) == FAIL) {
+ break;
}
- } else if (argvars[0].v_type == VAR_DICT) {
- if ((d = argvars[0].vval.v_dict) == NULL
- || (!map && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
- return;
+ if (did_emsg) {
+ tv_clear(&newtv);
+ break;
}
- } else {
- semsg(_(e_listdictblobarg), ermsg);
+ if (filtermap == FILTERMAP_MAP) {
+ // map(): replace the list item value
+ tv_clear(TV_LIST_ITEM_TV(li));
+ newtv.v_lock = VAR_UNLOCKED;
+ *TV_LIST_ITEM_TV(li) = newtv;
+ } else if (filtermap == FILTERMAP_MAPNEW) {
+ // mapnew(): append the list item value
+ tv_list_append_owned_tv(l_ret, newtv);
+ }
+ if (filtermap == FILTERMAP_FILTER && rem) {
+ li = tv_list_item_remove(l, li);
+ } else {
+ li = TV_LIST_ITEM_NEXT(l, li);
+ }
+ idx++;
+ }
+
+ tv_list_set_lock(l, prev_lock);
+}
+
+/// Implementation of map() and filter().
+static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
+{
+ const char *const func_name = (filtermap == FILTERMAP_MAP
+ ? "map()"
+ : (filtermap == FILTERMAP_MAPNEW
+ ? "mapnew()"
+ : "filter()"));
+ const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
+ ? N_("map() argument")
+ : (filtermap == FILTERMAP_MAPNEW
+ ? N_("mapnew() argument")
+ : N_("filter() argument")));
+
+ // map() and filter() return the first argument, also on failure.
+ if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
+ tv_copy(&argvars[0], rettv);
+ }
+
+ if (argvars[0].v_type != VAR_BLOB
+ && argvars[0].v_type != VAR_LIST
+ && argvars[0].v_type != VAR_DICT
+ && argvars[0].v_type != VAR_STRING) {
+ semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob), func_name);
return;
}
@@ -4820,104 +5373,22 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
// We reset "did_emsg" to be able to detect whether an error
// occurred during evaluation of the expression.
- save_did_emsg = did_emsg;
+ int save_did_emsg = did_emsg;
did_emsg = false;
typval_T save_key;
prepare_vimvar(VV_KEY, &save_key);
if (argvars[0].v_type == VAR_DICT) {
- vimvars[VV_KEY].vv_type = VAR_STRING;
-
- const VarLockStatus prev_lock = d->dv_lock;
- if (map && d->dv_lock == VAR_UNLOCKED) {
- d->dv_lock = VAR_LOCKED;
- }
- hashtab_T *ht = &d->dv_hashtab;
- hash_lock(ht);
- int todo = (int)ht->ht_used;
- for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
-
- dictitem_T *di = TV_DICT_HI2DI(hi);
- if (map
- && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
- || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
- break;
- }
-
- vimvars[VV_KEY].vv_str = xstrdup((char *)di->di_key);
- int r = filter_map_one(&di->di_tv, expr, map, &rem);
- tv_clear(&vimvars[VV_KEY].vv_tv);
- if (r == FAIL || did_emsg) {
- break;
- }
- if (!map && rem) {
- if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
- || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
- break;
- }
- tv_dict_item_remove(d, di);
- }
- }
- }
- hash_unlock(ht);
- d->dv_lock = prev_lock;
+ filter_map_dict(argvars[0].vval.v_dict, filtermap, func_name,
+ arg_errmsg, expr, rettv);
} else if (argvars[0].v_type == VAR_BLOB) {
- vimvars[VV_KEY].vv_type = VAR_NUMBER;
-
- for (int i = 0; i < b->bv_ga.ga_len; i++) {
- typval_T tv;
- tv.v_type = VAR_NUMBER;
- const varnumber_T val = tv_blob_get(b, i);
- tv.vval.v_number = val;
- vimvars[VV_KEY].vv_nr = idx;
- if (filter_map_one(&tv, expr, map, &rem) == FAIL || did_emsg) {
- break;
- }
- if (tv.v_type != VAR_NUMBER) {
- emsg(_(e_invalblob));
- return;
- }
- if (map) {
- if (tv.vval.v_number != val) {
- tv_blob_set(b, i, (uint8_t)tv.vval.v_number);
- }
- } else if (rem) {
- char *const p = argvars[0].vval.v_blob->bv_ga.ga_data;
- memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
- b->bv_ga.ga_len--;
- i--;
- }
- idx++;
- }
+ filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, arg_errmsg, rettv);
+ } else if (argvars[0].v_type == VAR_STRING) {
+ filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, rettv);
} else {
assert(argvars[0].v_type == VAR_LIST);
- vimvars[VV_KEY].vv_type = VAR_NUMBER;
-
- const VarLockStatus prev_lock = tv_list_locked(l);
- if (map && tv_list_locked(l) == VAR_UNLOCKED) {
- tv_list_set_lock(l, VAR_LOCKED);
- }
- for (listitem_T *li = tv_list_first(l); li != NULL;) {
- if (map
- && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
- TV_TRANSLATE)) {
- break;
- }
- vimvars[VV_KEY].vv_nr = idx;
- if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL
- || did_emsg) {
- break;
- }
- if (!map && rem) {
- li = tv_list_item_remove(l, li);
- } else {
- li = TV_LIST_ITEM_NEXT(l, li);
- }
- idx++;
- }
- tv_list_set_lock(l, prev_lock);
+ filter_map_list(argvars[0].vval.v_list, filtermap, func_name,
+ arg_errmsg, expr, rettv);
}
restore_vimvar(VV_KEY, &save_key);
@@ -4927,30 +5398,32 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
}
-static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
- FUNC_ATTR_NONNULL_ARG(1, 2)
+/// Handle one item for map() and filter().
+/// Sets v:val to "tv". Caller must set v:key.
+///
+/// @param tv original value
+/// @param expr callback
+/// @param newtv for map() an mapnew(): new value
+/// @param remp for filter(): remove flag
+static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
+ typval_T *newtv, bool *remp)
+ FUNC_ATTR_NONNULL_ALL
{
- typval_T rettv;
typval_T argv[3];
int retval = FAIL;
tv_copy(tv, &vimvars[VV_VAL].vv_tv);
argv[0] = vimvars[VV_KEY].vv_tv;
argv[1] = vimvars[VV_VAL].vv_tv;
- if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) {
+ if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) {
goto theend;
}
- if (map) {
- // map(): replace the list item value.
- tv_clear(tv);
- rettv.v_lock = VAR_UNLOCKED;
- *tv = rettv;
- } else {
+ if (filtermap == FILTERMAP_FILTER) {
bool error = false;
// filter(): when expr is zero remove the item
- *remp = (tv_get_number_chk(&rettv, &error) == 0);
- tv_clear(&rettv);
+ *remp = (tv_get_number_chk(newtv, &error) == 0);
+ tv_clear(newtv);
// On type error, nothing has been removed; return FAIL to stop the
// loop. The error message was given by tv_get_number_chk().
if (error) {
@@ -4963,6 +5436,26 @@ theend:
return retval;
}
+/// "filter()" function
+void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_FILTER);
+}
+
+/// "map()" function
+void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_MAP);
+}
+
+/// "mapnew()" function
+void f_mapnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_MAPNEW);
+}
+
+/// "function()" function
+/// "funcref()" function
void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
{
char *s;
@@ -4996,14 +5489,12 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
}
if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))
|| (is_funcref && trans_name == NULL)) {
- semsg(_(e_invarg2), (use_string
- ? tv_get_string(&argvars[0])
- : (const char *)s));
+ semsg(_(e_invarg2), (use_string ? tv_get_string(&argvars[0]) : s));
// Don't check an autoload name for existence here.
} else if (trans_name != NULL
&& (is_funcref
? find_func(trans_name) == NULL
- : !translated_function_exists((const char *)trans_name))) {
+ : !translated_function_exists(trans_name))) {
semsg(_("E700: Unknown function: %s"), s);
} else {
int dict_idx = 0;
@@ -5032,8 +5523,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
arg_idx = 1;
}
if (dict_idx > 0) {
- if (argvars[dict_idx].v_type != VAR_DICT) {
- emsg(_("E922: expected a dict"));
+ if (tv_check_for_dict_arg(argvars, dict_idx) == FAIL) {
xfree(name);
goto theend;
}
@@ -5052,7 +5542,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
if (tv_list_len(list) == 0) {
arg_idx = 0;
} else if (tv_list_len(list) > MAX_FUNC_ARGS) {
- emsg_funcname((char *)e_toomanyarg, s);
+ emsg_funcname(e_toomanyarg, s);
xfree(name);
goto theend;
}
@@ -5122,7 +5612,7 @@ theend:
xfree(trans_name);
}
-/// Get the line number from VimL object
+/// Get the line number from Vimscript object
///
/// @note Unlike tv_get_lnum(), this one supports only "$" special string.
///
@@ -5156,7 +5646,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- const char *prompt = "";
+ const char *prompt;
const char *defstr = "";
typval_T *cancelreturn = NULL;
typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE;
@@ -5247,7 +5737,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
p = lastnl + 1;
msg_start();
msg_clr_eos();
- msg_puts_attr_len(prompt, p - prompt, echo_attr);
+ msg_puts_len(prompt, p - prompt, echo_attr);
msg_didout = false;
msg_starthere();
}
@@ -5275,9 +5765,9 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
cmd_silent = cmd_silent_save;
}
-/// Builds a process argument vector from a VimL object (typval_T).
+/// Builds a process argument vector from a Vimscript object (typval_T).
///
-/// @param[in] cmd_tv VimL object
+/// @param[in] cmd_tv Vimscript object
/// @param[out] cmd Returns the command or executable name.
/// @param[out] executable Returns `false` if argv[0] is not executable.
///
@@ -5379,15 +5869,15 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
return;
}
const void *iter = NULL;
+ const char *appname = get_appname();
do {
size_t dir_len;
const char *dir;
iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
char *dir_with_nvim = xmemdupz(dir, dir_len);
- dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true);
- tv_list_append_string(list, dir_with_nvim, (ssize_t)strlen(dir_with_nvim));
- xfree(dir_with_nvim);
+ dir_with_nvim = concat_fnames_realloc(dir_with_nvim, appname, true);
+ tv_list_append_allocated_string(list, dir_with_nvim);
}
} while (iter != NULL);
xfree(dirs);
@@ -5429,7 +5919,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
char **argv = tv_to_argv(&argvars[0], NULL, &executable);
if (!argv) {
if (!executable) {
- set_vim_var_nr(VV_SHELL_ERROR, (long)-1);
+ set_vim_var_nr(VV_SHELL_ERROR, -1);
}
xfree(input);
return; // Already did emsg.
@@ -5438,7 +5928,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
if (p_verbose > 3) {
char *cmdstr = shell_argv_to_str(argv);
verbose_enter_scroll();
- smsg(_("Executing command: \"%s\""), cmdstr);
+ smsg(0, _("Executing command: \"%s\""), cmdstr);
msg_puts("\n\n");
verbose_leave_scroll();
xfree(cmdstr);
@@ -5459,7 +5949,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
xfree(input);
- set_vim_var_nr(VV_SHELL_ERROR, (long)status);
+ set_vim_var_nr(VV_SHELL_ERROR, status);
if (res == NULL) {
if (retlist) {
@@ -5559,11 +6049,23 @@ bool callback_from_typval(Callback *const callback, const typval_T *const arg)
return true;
}
+static int callback_depth = 0;
+
+int get_callback_depth(void)
+{
+ return callback_depth;
+}
+
/// @return whether the callback could be called.
bool callback_call(Callback *const callback, const int argcount_in, typval_T *const argvars_in,
typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL
{
+ if (callback_depth > MAX_CALLBACK_DEPTH) {
+ emsg(_(e_command_too_recursive));
+ return false;
+ }
+
partial_T *partial;
char *name;
Array args = ARRAY_DICT_INIT;
@@ -5596,9 +6098,6 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
case kCallbackNone:
return false;
break;
-
- default:
- abort();
}
funcexe_T funcexe = FUNCEXE_INIT;
@@ -5606,7 +6105,11 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
funcexe.fe_lastline = curwin->w_cursor.lnum;
funcexe.fe_evaluate = true;
funcexe.fe_partial = partial;
- return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
+
+ callback_depth++;
+ int ret = call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
+ callback_depth--;
+ return ret;
}
bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack,
@@ -5624,7 +6127,7 @@ bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack,
return set_ref_in_item(&tv, copyID, ht_stack, list_stack);
break;
- default:
+ case kCallbackLua:
abort();
}
return false;
@@ -5678,7 +6181,7 @@ void add_timer_info_all(typval_T *rettv)
tv_list_alloc_ret(rettv, map_size(&timers));
timer_T *timer;
map_foreach_value(&timers, timer, {
- if (!timer->stopped) {
+ if (!timer->stopped || timer->refcount > 1) {
add_timer_info(rettv, timer);
}
})
@@ -5735,7 +6238,7 @@ void timer_due_cb(TimeWatcher *tw, void *data)
timer_decref(timer);
}
-uint64_t timer_start(const long timeout, const int repeat_count, const Callback *const callback)
+uint64_t timer_start(const int64_t timeout, const int repeat_count, const Callback *const callback)
{
timer_T *timer = xmalloc(sizeof *timer);
timer->refcount = 1;
@@ -5775,7 +6278,7 @@ static void timer_close_cb(TimeWatcher *tw, void *data)
timer_T *timer = (timer_T *)data;
multiqueue_free(timer->tw.events);
callback_free(&timer->callback);
- pmap_del(uint64_t)(&timers, (uint64_t)timer->timer_id);
+ pmap_del(uint64_t)(&timers, (uint64_t)timer->timer_id, NULL);
timer_decref(timer);
}
@@ -5883,27 +6386,63 @@ write_blob_error:
return false;
}
-/// Read a blob from a file `fd`.
+/// Read blob from file "fd".
+/// Caller has allocated a blob in "rettv".
///
/// @param[in] fd File to read from.
-/// @param[in,out] blob Blob to write to.
+/// @param[in,out] rettv Blob to write to.
+/// @param[in] offset Read the file from the specified offset.
+/// @param[in] size Read the specified size, or -1 if no limit.
///
-/// @return true on success, or false on failure.
-bool read_blob(FILE *const fd, blob_T *const blob)
+/// @return OK on success, or FAIL on failure.
+int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg)
FUNC_ATTR_NONNULL_ALL
{
+ blob_T *const blob = rettv->vval.v_blob;
FileInfo file_info;
if (!os_fileinfo_fd(fileno(fd), &file_info)) {
- return false;
+ return FAIL; // can't read the file, error
}
- const int size = (int)os_fileinfo_size(&file_info);
- ga_grow(&blob->bv_ga, size);
- blob->bv_ga.ga_len = size;
+
+ int whence;
+ off_T size = size_arg;
+ const off_T file_size = (off_T)os_fileinfo_size(&file_info);
+ if (offset >= 0) {
+ // The size defaults to the whole file. If a size is given it is
+ // limited to not go past the end of the file.
+ if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) {
+ // size may become negative, checked below
+ size = (off_T)os_fileinfo_size(&file_info) - offset;
+ }
+ whence = SEEK_SET;
+ } else {
+ // limit the offset to not go before the start of the file
+ if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) {
+ offset = -file_size;
+ }
+ // Size defaults to reading until the end of the file.
+ if (size == -1 || size > -offset) {
+ size = -offset;
+ }
+ whence = SEEK_END;
+ }
+ if (size <= 0) {
+ return OK;
+ }
+ if (offset != 0 && vim_fseek(fd, offset, whence) != 0) {
+ return OK;
+ }
+
+ ga_grow(&blob->bv_ga, (int)size);
+ blob->bv_ga.ga_len = (int)size;
if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len) {
- return false;
+ // An empty blob is returned on error.
+ tv_blob_free(rettv->vval.v_blob);
+ rettv->vval.v_blob = NULL;
+ return FAIL;
}
- return true;
+ return OK;
}
/// Saves a typval_T as a string.
@@ -5914,7 +6453,7 @@ bool read_blob(FILE *const fd, blob_T *const blob)
/// @param[out] len Length of the resulting string or -1 on error.
/// @param[in] endnl If true, the output will end in a newline (if a list).
/// @param[in] crlf If true, list items will be joined with CRLF (if a list).
-/// @returns an allocated string if `tv` represents a VimL string, list, or
+/// @returns an allocated string if `tv` represents a Vimscript string, list, or
/// number; NULL otherwise.
char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crlf)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
@@ -5941,7 +6480,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crl
buf_T *buf = buflist_findnr((int)tv->vval.v_number);
if (buf) {
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- for (char *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) {
+ for (char *p = ml_get_buf(buf, lnum); *p != NUL; p++) {
*len += 1;
}
*len += 1;
@@ -5959,7 +6498,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crl
char *ret = xmalloc((size_t)(*len) + 1);
char *end = ret;
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- for (char *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) {
+ for (char *p = ml_get_buf(buf, lnum); *p != NUL; p++) {
*end++ = (*p == '\n') ? NUL : *p;
}
*end++ = '\n';
@@ -6011,7 +6550,7 @@ int buf_byteidx_to_charidx(buf_T *buf, linenr_T lnum, int byteidx)
lnum = buf->b_ml.ml_line_count;
}
- char *str = ml_get_buf(buf, lnum, false);
+ char *str = ml_get_buf(buf, lnum);
if (*str == NUL) {
return 0;
@@ -6049,7 +6588,7 @@ int buf_charidx_to_byteidx(buf_T *buf, linenr_T lnum, int charidx)
lnum = buf->b_ml.ml_line_count;
}
- char *str = ml_get_buf(buf, lnum, false);
+ char *str = ml_get_buf(buf, lnum);
// Convert the character offset to a byte offset
char *t = str;
@@ -6060,7 +6599,7 @@ int buf_charidx_to_byteidx(buf_T *buf, linenr_T lnum, int charidx)
return (int)(t - str);
}
-/// Translate a VimL object into a position
+/// Translate a Vimscript object into a position
///
/// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid
/// type.
@@ -6087,14 +6626,14 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
// Get the line number.
- pos.lnum = (linenr_T)tv_list_find_nr(l, 0L, &error);
+ pos.lnum = (linenr_T)tv_list_find_nr(l, 0, &error);
if (error || pos.lnum <= 0 || pos.lnum > curbuf->b_ml.ml_line_count) {
// Invalid line number.
return NULL;
}
// Get the column number.
- pos.col = (colnr_T)tv_list_find_nr(l, 1L, &error);
+ pos.col = (colnr_T)tv_list_find_nr(l, 1, &error);
if (error) {
return NULL;
}
@@ -6106,7 +6645,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
// We accept "$" for the column number: last column.
- listitem_T *li = tv_list_find(l, 1L);
+ listitem_T *li = tv_list_find(l, 1);
if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING
&& TV_LIST_ITEM_TV(li)->vval.v_string != NULL
&& strcmp(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) {
@@ -6121,7 +6660,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos.col--;
// Get the virtual offset. Defaults to zero.
- pos.coladd = (colnr_T)tv_list_find_nr(l, 2L, &error);
+ pos.coladd = (colnr_T)tv_list_find_nr(l, 2, &error);
if (error) {
pos.coladd = 0;
}
@@ -6154,7 +6693,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
pos = fm->mark;
// Vimscript behavior, only provide fnum if mark is global.
- *ret_fnum = ASCII_ISUPPER(mname) || ascii_isdigit(mname) ? fm->fnum: *ret_fnum;
+ *ret_fnum = ASCII_ISUPPER(mname) || ascii_isdigit(mname) ? fm->fnum : *ret_fnum;
}
if (pos.lnum != 0) {
if (charcol) {
@@ -6166,6 +6705,10 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos.coladd = 0;
if (name[0] == 'w' && dollar_lnum) {
+ // the "w_valid" flags are not reset when moving the cursor, but they
+ // do matter for update_topline() and validate_botline().
+ check_cursor_moved(curwin);
+
pos.col = 0;
if (name[1] == '0') { // "w0": first visible line
update_topline(curwin);
@@ -6220,25 +6763,25 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c
}
int i = 0;
- long n;
+ int n;
if (fnump != NULL) {
- n = tv_list_find_nr(l, i++, NULL); // fnum
+ n = (int)tv_list_find_nr(l, i++, NULL); // fnum
if (n < 0) {
return FAIL;
}
if (n == 0) {
n = curbuf->b_fnum; // Current buffer.
}
- *fnump = (int)n;
+ *fnump = n;
}
- n = tv_list_find_nr(l, i++, NULL); // lnum
+ n = (int)tv_list_find_nr(l, i++, NULL); // lnum
if (n < 0) {
return FAIL;
}
- posp->lnum = (linenr_T)n;
+ posp->lnum = n;
- n = tv_list_find_nr(l, i++, NULL); // col
+ n = (int)tv_list_find_nr(l, i++, NULL); // col
if (n < 0) {
return FAIL;
}
@@ -6252,15 +6795,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c
}
n = buf_charidx_to_byteidx(buf,
posp->lnum == 0 ? curwin->w_cursor.lnum : posp->lnum,
- (int)n) + 1;
+ n) + 1;
}
- posp->col = (colnr_T)n;
+ posp->col = n;
- n = tv_list_find_nr(l, i, NULL); // off
+ n = (int)tv_list_find_nr(l, i, NULL); // off
if (n < 0) {
posp->coladd = 0;
} else {
- posp->coladd = (colnr_T)n;
+ posp->coladd = n;
}
if (curswantp != NULL) {
@@ -6314,7 +6857,7 @@ int get_id_len(const char **const arg)
}
len = (int)(p - *arg);
- *arg = (const char *)skipwhite(p);
+ *arg = skipwhite(p);
return len;
}
@@ -6352,7 +6895,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo
if (expr_start != NULL) {
if (!evaluate) {
len += (int)(p - *arg);
- *arg = (const char *)skipwhite(p);
+ *arg = skipwhite(p);
return len;
}
@@ -6363,7 +6906,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo
return -1;
}
*alias = temp_string;
- *arg = (const char *)skipwhite(p);
+ *arg = skipwhite(p);
return (int)strlen(temp_string);
}
@@ -6482,15 +7025,14 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex
}
char *retval = NULL;
- char *nextcmd = NULL;
*expr_start = NUL;
*expr_end = NUL;
char c1 = *in_end;
*in_end = NUL;
- char *temp_result = eval_to_string(expr_start + 1, &nextcmd, false);
- if (temp_result != NULL && nextcmd == NULL) {
+ char *temp_result = eval_to_string(expr_start + 1, false);
+ if (temp_result != NULL) {
retval = xmalloc(strlen(temp_result) + (size_t)(expr_start - in_start)
+ (size_t)(in_end - expr_end) + 1);
STRCPY(retval, in_start);
@@ -6578,7 +7120,7 @@ dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE
/// Set v:char to character "c".
void set_vim_var_char(int c)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
buf[utf_char2bytes(c, buf)] = NUL;
set_vim_var_string(VV_CHAR, buf, -1);
@@ -6587,7 +7129,7 @@ void set_vim_var_char(int c)
/// Set v:count to "count" and v:count1 to "count1".
///
/// @param set_prevcount if true, first set v:prevcount from v:count.
-void set_vcount(long count, long count1, int set_prevcount)
+void set_vcount(int64_t count, int64_t count1, bool set_prevcount)
{
if (set_prevcount) {
vimvars[VV_PREVCOUNT].vv_nr = vimvars[VV_COUNT].vv_nr;
@@ -6681,14 +7223,23 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val)
tv_dict_set_keys_readonly(val);
}
+/// Set v:variable to tv.
+///
+/// @param[in] idx Index of variable to set.
+/// @param[in] val Value to set to. Will be copied.
+void set_vim_var_tv(const VimVarIndex idx, typval_T *const tv)
+{
+ tv_clear(&vimvars[idx].vv_di.di_tv);
+ tv_copy(tv, &vimvars[idx].vv_di.di_tv);
+}
+
/// Set the v:argv list.
void set_argv_var(char **argv, int argc)
{
list_T *l = tv_list_alloc(argc);
- int i;
tv_list_set_lock(l, VAR_FIXED);
- for (i = 0; i < argc; i++) {
+ for (int i = 0; i < argc; i++) {
tv_list_append_string(l, (const char *const)argv[i], -1);
TV_LIST_ITEM_TV(tv_list_last(l))->v_lock = VAR_FIXED;
}
@@ -6747,20 +7298,18 @@ char *set_cmdarg(exarg_T *eap, char *oldarg)
{
char *oldval = vimvars[VV_CMDARG].vv_str;
if (eap == NULL) {
- xfree(oldval);
- vimvars[VV_CMDARG].vv_str = oldarg;
- return NULL;
+ goto error;
}
size_t len = 0;
if (eap->force_bin == FORCE_BIN) {
- len = 6;
+ len += 6; // " ++bin"
} else if (eap->force_bin == FORCE_NOBIN) {
- len = 8;
+ len += 8; // " ++nobin"
}
if (eap->read_edit) {
- len += 7;
+ len += 7; // " ++edit"
}
if (eap->force_ff != 0) {
@@ -6773,48 +7322,89 @@ char *set_cmdarg(exarg_T *eap, char *oldarg)
len += 7 + 4; // " ++bad=" + "keep" or "drop"
}
if (eap->mkdir_p != 0) {
- len += 4;
+ len += 4; // " ++p"
}
const size_t newval_len = len + 1;
char *newval = xmalloc(newval_len);
+ size_t xlen = 0;
+ int rc = 0;
if (eap->force_bin == FORCE_BIN) {
- snprintf(newval, newval_len, " ++bin");
+ rc = snprintf(newval, newval_len, " ++bin");
} else if (eap->force_bin == FORCE_NOBIN) {
- snprintf(newval, newval_len, " ++nobin");
+ rc = snprintf(newval, newval_len, " ++nobin");
} else {
*newval = NUL;
}
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
if (eap->read_edit) {
- STRCAT(newval, " ++edit");
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++edit");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
if (eap->force_ff != 0) {
- snprintf(newval + strlen(newval), newval_len, " ++ff=%s",
- eap->force_ff == 'u' ? "unix" :
- eap->force_ff == 'd' ? "dos" : "mac");
+ rc = snprintf(newval + xlen,
+ newval_len - xlen,
+ " ++ff=%s",
+ eap->force_ff == 'u' ? "unix"
+ : eap->force_ff == 'd' ? "dos" : "mac");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
if (eap->force_enc != 0) {
- snprintf(newval + strlen(newval), newval_len, " ++enc=%s",
- eap->cmd + eap->force_enc);
+ rc = snprintf(newval + (xlen), newval_len - xlen, " ++enc=%s", eap->cmd + eap->force_enc);
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
+
if (eap->bad_char == BAD_KEEP) {
- STRCPY(newval + strlen(newval), " ++bad=keep");
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=keep");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
} else if (eap->bad_char == BAD_DROP) {
- STRCPY(newval + strlen(newval), " ++bad=drop");
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=drop");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
} else if (eap->bad_char != 0) {
- snprintf(newval + strlen(newval), newval_len, " ++bad=%c",
- eap->bad_char);
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=%c", eap->bad_char);
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
- if (eap->mkdir_p) {
- snprintf(newval, newval_len, " ++p");
+ if (eap->mkdir_p != 0) {
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++p");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
+ assert(xlen <= newval_len);
vimvars[VV_CMDARG].vv_str = newval;
return oldval;
+
+error:
+ xfree(oldval);
+ vimvars[VV_CMDARG].vv_str = oldarg;
+ return NULL;
}
/// Check if variable "name[len]" is a local variable or an argument.
@@ -6871,6 +7461,88 @@ int check_luafunc_name(const char *const str, const bool paren)
return (int)(p - str);
}
+/// Return the character "str[index]" where "index" is the character index. If
+/// "index" is out of range NULL is returned.
+char *char_from_string(const char *str, varnumber_T index)
+{
+ size_t nbyte = 0;
+ varnumber_T nchar = index;
+
+ if (str == NULL || index < 0) {
+ return NULL;
+ }
+ size_t slen = strlen(str);
+ while (nchar > 0 && nbyte < slen) {
+ nbyte += (size_t)utf_ptr2len(str + nbyte);
+ nchar--;
+ }
+ if (nbyte >= slen) {
+ return NULL;
+ }
+ return xmemdupz(str + nbyte, (size_t)utf_ptr2len(str + nbyte));
+}
+
+/// Get the byte index for character index "idx" in string "str" with length
+/// "str_len".
+/// If going over the end return "str_len".
+/// If "idx" is negative count from the end, -1 is the last character.
+/// When going over the start return -1.
+static ssize_t char_idx2byte(const char *str, size_t str_len, varnumber_T idx)
+{
+ varnumber_T nchar = idx;
+ size_t nbyte = 0;
+
+ if (nchar >= 0) {
+ while (nchar > 0 && nbyte < str_len) {
+ nbyte += (size_t)utf_ptr2len(str + nbyte);
+ nchar--;
+ }
+ } else {
+ nbyte = str_len;
+ while (nchar < 0 && nbyte > 0) {
+ nbyte--;
+ nbyte -= (size_t)utf_head_off(str, str + nbyte);
+ nchar++;
+ }
+ if (nchar < 0) {
+ return -1;
+ }
+ }
+ return (ssize_t)nbyte;
+}
+
+/// Return the slice "str[first:last]" using character indexes.
+///
+/// @param exclusive true for slice().
+///
+/// Return NULL when the result is empty.
+char *string_slice(const char *str, varnumber_T first, varnumber_T last, bool exclusive)
+{
+ if (str == NULL) {
+ return NULL;
+ }
+ size_t slen = strlen(str);
+ ssize_t start_byte = char_idx2byte(str, slen, first);
+ if (start_byte < 0) {
+ start_byte = 0; // first index very negative: use zero
+ }
+ ssize_t end_byte;
+ if ((last == -1 && !exclusive) || last == VARNUMBER_MAX) {
+ end_byte = (ssize_t)slen;
+ } else {
+ end_byte = char_idx2byte(str, slen, last);
+ if (!exclusive && end_byte >= 0 && end_byte < (ssize_t)slen) {
+ // end index is inclusive
+ end_byte += utf_ptr2len(str + end_byte);
+ }
+ }
+
+ if (start_byte >= (ssize_t)slen || end_byte <= start_byte) {
+ return NULL;
+ }
+ return xmemdupz(str + start_byte, (size_t)(end_byte - start_byte));
+}
+
/// Handle:
/// - expr[expr], expr[expr:expr] subscript
/// - ".name" lookup
@@ -6879,13 +7551,13 @@ int check_luafunc_name(const char *const str, const bool paren)
///
/// Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len()
///
-/// @param evaluate do more than finding the end
/// @param verbose give error messages
/// @param start_leader start of '!' and '-' prefixes
/// @param end_leaderp end of '!' and '-' prefixes
-int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int verbose,
- const char *const start_leader, const char **const end_leaderp)
+int handle_subscript(const char **const arg, typval_T *rettv, evalarg_T *const evalarg,
+ bool verbose)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
int ret = OK;
dict_T *selfdict = NULL;
const char *lua_funcname = NULL;
@@ -6914,7 +7586,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int
&& !ascii_iswhite(*(*arg - 1)))
|| (**arg == '-' && (*arg)[1] == '>'))) {
if (**arg == '(') {
- ret = call_func_rettv((char **)arg, rettv, evaluate, selfdict, NULL, lua_funcname);
+ ret = call_func_rettv((char **)arg, evalarg, rettv, evaluate, selfdict, NULL, lua_funcname);
// Stop the expression evaluation when immediately aborting on
// error, or when an interrupt occurred or an exception was thrown
@@ -6928,19 +7600,12 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int
tv_dict_unref(selfdict);
selfdict = NULL;
} else if (**arg == '-') {
- // Expression "-1.0->method()" applies the leader "-" before
- // applying ->.
- if (evaluate && *end_leaderp > start_leader) {
- ret = eval7_leader(rettv, (char *)start_leader, end_leaderp);
- }
- if (ret == OK) {
- if ((*arg)[2] == '{') {
- // expr->{lambda}()
- ret = eval_lambda((char **)arg, rettv, evaluate, verbose);
- } else {
- // expr->name()
- ret = eval_method((char **)arg, rettv, evaluate, verbose);
- }
+ if ((*arg)[2] == '{') {
+ // expr->{lambda}()
+ ret = eval_lambda((char **)arg, rettv, evalarg, verbose);
+ } else {
+ // expr->name()
+ ret = eval_method((char **)arg, rettv, evalarg, verbose);
}
} else { // **arg == '[' || **arg == '.'
tv_dict_unref(selfdict);
@@ -6952,7 +7617,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int
} else {
selfdict = NULL;
}
- if (eval_index((char **)arg, rettv, evaluate, verbose) == FAIL) {
+ if (eval_index((char **)arg, rettv, evalarg, verbose) == FAIL) {
tv_clear(rettv);
ret = FAIL;
}
@@ -7105,7 +7770,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
if (funccal == NULL) { // global variable
*d = &globvardict;
} else { // l: variable
- *d = &funccal->l_vars;
+ *d = &funccal->fc_l_vars;
}
goto end;
}
@@ -7129,13 +7794,13 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
} else if (*name == 'v') { // v: variable
*d = &vimvardict;
} else if (*name == 'a' && funccal != NULL) { // function argument
- *d = &funccal->l_avars;
+ *d = &funccal->fc_l_avars;
} else if (*name == 'l' && funccal != NULL) { // local variable
- *d = &funccal->l_vars;
+ *d = &funccal->fc_l_vars;
} else if (*name == 's' // script variable
&& (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR
|| current_sctx.sc_sid == SID_LUA)
- && current_sctx.sc_sid <= ga_scripts.ga_len) {
+ && current_sctx.sc_sid <= script_items.ga_len) {
// For anonymous scripts without a script item, create one now so script vars can be used
if (current_sctx.sc_sid == SID_LUA) {
// try to resolve lua filename & line no so it can be shown in lastset messages.
@@ -7186,27 +7851,9 @@ hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **var
/// sourcing this script and when executing functions defined in the script.
void new_script_vars(scid_T id)
{
- scriptvar_T *sv;
-
- ga_grow(&ga_scripts, id - ga_scripts.ga_len);
-
- // Re-allocating ga_data means that an ht_array pointing to
- // ht_smallarray becomes invalid. We can recognize this: ht_mask is
- // at its init value. Also reset "v_dict", it's always the same.
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
- hashtab_T *ht = &SCRIPT_VARS(i);
- if (ht->ht_mask == HT_INIT_SIZE - 1) {
- ht->ht_array = ht->ht_smallarray;
- }
- sv = SCRIPT_SV(i);
- sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict;
- }
-
- while (ga_scripts.ga_len < id) {
- sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T));
- init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
- ga_scripts.ga_len++;
- }
+ scriptvar_T *sv = xcalloc(1, sizeof(scriptvar_T));
+ init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
+ SCRIPT_ITEM(id)->sn_vars = sv;
}
/// Initialize dictionary "dict" as a scope and set variable "dict_var" to
@@ -7259,7 +7906,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c
int ret = OK;
if (recurse >= DICT_MAXNEST) {
- emsg(_("E698: variable nested too deep for making a copy"));
+ emsg(_(e_variable_nested_too_deep_for_making_copy));
return FAIL;
}
recurse++;
@@ -7305,7 +7952,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c
}
break;
case VAR_BLOB:
- tv_blob_copy(from, to);
+ tv_blob_copy(from->vval.v_blob, to);
break;
case VAR_DICT:
to->v_type = VAR_DICT;
@@ -7342,6 +7989,9 @@ void ex_echo(exarg_T *eap)
bool need_clear = true;
const int did_emsg_before = did_emsg;
const int called_emsg_before = called_emsg;
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
if (eap->skip) {
emsg_skip++;
@@ -7353,7 +8003,7 @@ void ex_echo(exarg_T *eap)
{
char *p = arg;
- if (eval1(&arg, &rettv, !eap->skip) == FAIL) {
+ if (eval1(&arg, &rettv, &evalarg) == FAIL) {
// Report the invalid expression unless the expression evaluation
// has been cancelled due to an aborting error, an interrupt, or an
// exception.
@@ -7385,7 +8035,7 @@ void ex_echo(exarg_T *eap)
char *tofree = encode_tv2echo(&rettv, NULL);
if (*tofree != NUL) {
msg_ext_set_kind("echo");
- msg_multiline_attr(tofree, echo_attr, true, &need_clear);
+ msg_multiline(tofree, echo_attr, true, &need_clear);
}
xfree(tofree);
}
@@ -7393,6 +8043,7 @@ void ex_echo(exarg_T *eap)
arg = skipwhite(arg);
}
eap->nextcmd = check_nextcmd(arg);
+ clear_evalarg(&evalarg, eap);
if (eap->skip) {
emsg_skip--;
@@ -7431,23 +8082,23 @@ void ex_execute(exarg_T *eap)
emsg_skip++;
}
while (*arg != NUL && *arg != '|' && *arg != '\n') {
- ret = eval1_emsg(&arg, &rettv, !eap->skip);
+ ret = eval1_emsg(&arg, &rettv, eap);
if (ret == FAIL) {
break;
}
if (!eap->skip) {
const char *const argstr = eap->cmdidx == CMD_execute
- ? tv_get_string(&rettv)
- : rettv.v_type == VAR_STRING
- ? encode_tv2echo(&rettv, NULL)
- : encode_tv2string(&rettv, NULL);
+ ? tv_get_string(&rettv)
+ : rettv.v_type == VAR_STRING
+ ? encode_tv2echo(&rettv, NULL)
+ : encode_tv2string(&rettv, NULL);
const size_t len = strlen(argstr);
ga_grow(&ga, (int)len + 2);
if (!GA_EMPTY(&ga)) {
- ((char_u *)(ga.ga_data))[ga.ga_len++] = ' ';
+ ((char *)(ga.ga_data))[ga.ga_len++] = ' ';
}
- memcpy((char_u *)(ga.ga_data) + ga.ga_len, argstr, len + 1);
+ memcpy((char *)(ga.ga_data) + ga.ga_len, argstr, len + 1);
if (eap->cmdidx != CMD_execute) {
xfree((void *)argstr);
}
@@ -7459,22 +8110,14 @@ void ex_execute(exarg_T *eap)
}
if (ret != FAIL && ga.ga_data != NULL) {
- if (eap->cmdidx == CMD_echomsg || eap->cmdidx == CMD_echoerr) {
- // Mark the already saved text as finishing the line, so that what
- // follows is displayed on a new line when scrolling back at the
- // more prompt.
- msg_sb_eol();
- }
-
if (eap->cmdidx == CMD_echomsg) {
msg_ext_set_kind("echomsg");
- msg_attr(ga.ga_data, echo_attr);
- ui_flush();
+ msg(ga.ga_data, echo_attr);
} else if (eap->cmdidx == CMD_echoerr) {
// We don't want to abort following commands, restore did_emsg.
int save_did_emsg = did_emsg;
msg_ext_set_kind("echoerr");
- emsg(ga.ga_data);
+ emsg_multiline(ga.ga_data, true);
if (!force_abort) {
did_emsg = save_did_emsg;
}
@@ -7528,80 +8171,6 @@ const char *find_option_end(const char **const arg, int *const scope)
return p;
}
-/// Return the autoload script name for a function or variable name
-/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
-///
-/// @param[in] name Variable/function name.
-/// @param[in] name_len Name length.
-///
-/// @return [allocated] autoload script name.
-char *autoload_name(const char *const name, const size_t name_len)
- FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
-{
- // Get the script file name: replace '#' with '/', append ".vim".
- char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
- memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
- memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
- size_t auchar_idx = 0;
- for (size_t i = sizeof("autoload/") - 1;
- i - sizeof("autoload/") + 1 < name_len;
- i++) {
- if (scriptname[i] == AUTOLOAD_CHAR) {
- scriptname[i] = '/';
- auchar_idx = i;
- }
- }
- memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
-
- return scriptname;
-}
-
-/// If name has a package name try autoloading the script for it
-///
-/// @param[in] name Variable/function name.
-/// @param[in] name_len Name length.
-/// @param[in] reload If true, load script again when already loaded.
-///
-/// @return true if a package was loaded.
-bool script_autoload(const char *const name, const size_t name_len, const bool reload)
-{
- // If there is no '#' after name[0] there is no package name.
- const char *p = memchr(name, AUTOLOAD_CHAR, name_len);
- if (p == NULL || p == name) {
- return false;
- }
-
- bool ret = false;
- char *tofree = autoload_name(name, name_len);
- char *scriptname = tofree;
-
- // Find the name in the list of previously loaded package names. Skip
- // "autoload/", it's always the same.
- int i = 0;
- for (; i < ga_loaded.ga_len; i++) {
- if (strcmp(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) {
- break;
- }
- }
- if (!reload && i < ga_loaded.ga_len) {
- ret = false; // Was loaded already.
- } else {
- // Remember the name if it wasn't loaded already.
- if (i == ga_loaded.ga_len) {
- GA_APPEND(char *, &ga_loaded, scriptname);
- tofree = NULL;
- }
-
- // Try loading the package from $VIMRUNTIME/autoload/<name>.vim
- if (source_runtime(scriptname, 0) == OK) {
- ret = true;
- }
- }
-
- xfree(tofree);
- return ret;
-}
-
static var_flavour_T var_flavour(char *varname)
FUNC_ATTR_PURE
{
@@ -7650,7 +8219,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, typv
} else {
hi = (const hashitem_T *)iter;
}
- *name = (char *)TV_DICT_HI2DI(hi)->di_key;
+ *name = TV_DICT_HI2DI(hi)->di_key;
tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv);
while ((size_t)(++hi - hifirst) < hinum) {
if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) {
@@ -7674,10 +8243,10 @@ int store_session_globals(FILE *fd)
TV_DICT_ITER(&globvardict, this_var, {
if ((this_var->di_tv.v_type == VAR_NUMBER
|| this_var->di_tv.v_type == VAR_STRING)
- && var_flavour((char *)this_var->di_key) == VAR_FLAVOUR_SESSION) {
+ && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) {
// Escape special characters with a backslash. Turn a LF and
// CR into \n and \r.
- char *const p = (char *)vim_strsave_escaped(tv_get_string(&this_var->di_tv), "\\\"\n\r");
+ char *const p = vim_strsave_escaped(tv_get_string(&this_var->di_tv), "\\\"\n\r");
for (char *t = p; *t != NUL; t++) {
if (*t == '\n') {
*t = 'n';
@@ -7696,7 +8265,7 @@ int store_session_globals(FILE *fd)
}
xfree(p);
} else if (this_var->di_tv.v_type == VAR_FLOAT
- && var_flavour((char *)this_var->di_key) == VAR_FLAVOUR_SESSION) {
+ && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) {
float_T f = this_var->di_tv.vval.v_float;
int sign = ' ';
@@ -7737,7 +8306,7 @@ void option_last_set_msg(LastSet last_set)
msg_puts(p);
if (last_set.script_ctx.sc_lnum > 0) {
msg_puts(_(line_msg));
- msg_outnum((long)last_set.script_ctx.sc_lnum);
+ msg_outnum(last_set.script_ctx.sc_lnum);
}
if (should_free) {
xfree(p);
@@ -7843,7 +8412,7 @@ repeat:
// ":~" - path relative to the home directory
// ":8" - shortname path - postponed till after
while (src[*usedlen] == ':'
- && ((c = (char_u)src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
+ && ((c = (uint8_t)src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
*usedlen += 2;
if (c == '8') {
continue;
@@ -7987,7 +8556,7 @@ repeat:
// "path/to/this.file.ext" :r:r:r
// ^ ^------------- tail
// +--------------------- *fnamep
- if (s > MAX(tail, (char *)(*fnamep))) {
+ if (s > MAX(tail, *fnamep)) {
*fnamelen = (size_t)(s - *fnamep);
}
}
@@ -8013,13 +8582,13 @@ repeat:
// find end of pattern
p = vim_strchr(s, sep);
if (p != NULL) {
- char *const pat = xstrnsave(s, (size_t)(p - s));
+ char *const pat = xmemdupz(s, (size_t)(p - s));
s = p + 1;
// find end of substitution
p = vim_strchr(s, sep);
if (p != NULL) {
- char *const sub = xstrnsave(s, (size_t)(p - s));
- char *const str = xstrnsave(*fnamep, *fnamelen);
+ char *const sub = xmemdupz(s, (size_t)(p - s));
+ char *const str = xmemdupz(*fnamep, *fnamelen);
*usedlen = (size_t)(p + 1 - src);
s = do_string_sub(str, pat, sub, NULL, flags);
*fnamep = s;
@@ -8065,14 +8634,13 @@ repeat:
/// @return an allocated string, NULL for error.
char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char *flags)
{
- int sublen;
regmatch_T regmatch;
garray_T ga;
char *zero_width = NULL;
// Make 'cpoptions' empty, so that the 'l' flag doesn't work here
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
ga_init(&ga, 1, 200);
@@ -8081,6 +8649,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
regmatch.rm_ic = p_ic;
regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog != NULL) {
+ int sublen;
char *tail = str;
char *end = str + strlen(str);
while (vim_regexec_nl(&regmatch, str, (colnr_T)(tail - str))) {
@@ -8089,7 +8658,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
if (zero_width == regmatch.startp[0]) {
// avoid getting stuck on a match with an empty string
int i = utfc_ptr2len(tail);
- memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
+ memmove((char *)ga.ga_data + ga.ga_len, tail, (size_t)i);
ga.ga_len += i;
tail += i;
continue;
@@ -8103,12 +8672,16 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
// - The substituted text.
// - The text after the match.
sublen = vim_regsub(&regmatch, sub, expr, tail, 0, REGSUB_MAGIC);
+ if (sublen <= 0) {
+ ga_clear(&ga);
+ break;
+ }
ga_grow(&ga, (int)((end - tail) + sublen -
(regmatch.endp[0] - regmatch.startp[0])));
// copy the text up to where the match is
int i = (int)(regmatch.startp[0] - tail);
- memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
+ memmove((char *)ga.ga_data + ga.ga_len, tail, (size_t)i);
// add the substituted text
(void)vim_regsub(&regmatch, sub, expr,
(char *)ga.ga_data + ga.ga_len + i, sublen,
@@ -8132,14 +8705,14 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
char *ret = xstrdup(ga.ga_data == NULL ? str : ga.ga_data);
ga_clear(&ga);
- if (p_cpo == empty_option) {
+ if (p_cpo == empty_string_option) {
p_cpo = save_cpo;
} else {
// Darn, evaluating {sub} expression or {expr} changed the value.
// If it's still empty it was changed and restored, need to restore in
// the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
free_string_option(save_cpo);
}
@@ -8203,7 +8776,7 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
}
list_T *args = tv_list_alloc(1);
- tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1);
+ tv_list_append_string(args, argvars[0].vval.v_string, -1);
*rettv = eval_call_provider(name, "eval", args, false);
}
@@ -8217,7 +8790,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo
return (typval_T){
.v_type = VAR_NUMBER,
.v_lock = VAR_UNLOCKED,
- .vval.v_number = (varnumber_T)0
+ .vval.v_number = 0
};
}
@@ -8231,6 +8804,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo
.es_entry = ((estack_T *)exestack.ga_data)[exestack.ga_len - 1],
.autocmd_fname = autocmd_fname,
.autocmd_match = autocmd_match,
+ .autocmd_fname_full = autocmd_fname_full,
.autocmd_bufnr = autocmd_bufnr,
.funccalp = (void *)get_current_funccal()
};
@@ -8281,7 +8855,7 @@ bool eval_has_provider(const char *feat)
return false;
}
- char name[32]; // Normalized: "python_compiled" => "python".
+ char name[32]; // Normalized: "python3_compiled" => "python3".
snprintf(name, sizeof(name), "%s", feat);
strchrsub(name, '_', '\0'); // Chop any "_xx" suffix.
@@ -8289,14 +8863,14 @@ bool eval_has_provider(const char *feat)
typval_T tv;
// Get the g:loaded_xx_provider variable.
int len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
- if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
+ if (eval_variable(buf, len, &tv, NULL, false, true) == FAIL) {
// Trigger autoload once.
len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name);
script_autoload(buf, (size_t)len, false);
// Retry the (non-autoload-style) variable.
len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
- if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
+ if (eval_variable(buf, len, &tv, NULL, false, true) == FAIL) {
// Show a hint if Call() is defined but g:loaded_xx_provider is missing.
snprintf(buf, sizeof(buf), "provider#%s#Call", name);
if (!!find_func(buf) && p_lpl) {
@@ -8308,8 +8882,8 @@ bool eval_has_provider(const char *feat)
}
bool ok = (tv.v_type == VAR_NUMBER)
- ? 2 == tv.vval.v_number // Value of 2 means "loaded and working".
- : false;
+ ? 2 == tv.vval.v_number // Value of 2 means "loaded and working".
+ : false;
if (ok) {
// Call() must be defined if provider claims to be working.
@@ -8337,33 +8911,27 @@ void eval_fmt_source_name_line(char *buf, size_t bufsize)
/// ":checkhealth [plugins]"
void ex_checkhealth(exarg_T *eap)
{
- bool found = !!find_func("health#check");
- if (!found
- && script_autoload("health#check", sizeof("health#check") - 1, false)) {
- found = !!find_func("health#check");
+ Error err = ERROR_INIT;
+ MAXSIZE_TEMP_ARRAY(args, 1);
+ ADD_C(args, CSTR_AS_OBJ(eap->arg));
+ NLUA_EXEC_STATIC("return vim.health._check(...)", args, &err);
+ if (!ERROR_SET(&err)) {
+ return;
}
- if (!found) {
- const char *vimruntime_env = os_getenv("VIMRUNTIME");
- if (vimruntime_env == NULL) {
- emsg(_("E5009: $VIMRUNTIME is empty or unset"));
+
+ const char *vimruntime_env = os_getenv("VIMRUNTIME");
+ if (vimruntime_env == NULL) {
+ emsg(_("E5009: $VIMRUNTIME is empty or unset"));
+ } else {
+ bool rtp_ok = NULL != strstr(p_rtp, vimruntime_env);
+ if (rtp_ok) {
+ semsg(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env);
} else {
- bool rtp_ok = NULL != strstr(p_rtp, vimruntime_env);
- if (rtp_ok) {
- semsg(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env);
- } else {
- emsg(_("E5009: Invalid 'runtimepath'"));
- }
+ emsg(_("E5009: Invalid 'runtimepath'"));
}
- return;
}
-
- size_t bufsize = strlen(eap->arg) + sizeof("call health#check('')");
- char *buf = xmalloc(bufsize);
- snprintf(buf, bufsize, "call health#check('%s')", eap->arg);
-
- do_cmdline_cmd(buf);
-
- xfree(buf);
+ semsg_multiline(err.msg);
+ api_clear_error(&err);
}
void invoke_prompt_callback(void)
@@ -8425,7 +8993,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
const bool type_is = type == EXPR_IS || type == EXPR_ISNOT;
if (type_is && typ1->v_type != typ2->v_type) {
- // For "is" a different type always means false, for "notis"
+ // For "is" a different type always means false, for "isnot"
// it means true.
n1 = type == EXPR_ISNOT;
} else if (typ1->v_type == VAR_BLOB || typ2->v_type == VAR_BLOB) {
@@ -8506,8 +9074,9 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
}
if ((typ1->v_type == VAR_PARTIAL && typ1->vval.v_partial == NULL)
|| (typ2->v_type == VAR_PARTIAL && typ2->vval.v_partial == NULL)) {
- // when a partial is NULL assume not equal
- n1 = false;
+ // When both partials are NULL, then they are equal.
+ // Otherwise they are not equal.
+ n1 = (typ1->vval.v_partial == typ2->vval.v_partial);
} else if (type_is) {
if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC) {
// strings are considered the same if their value is
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 86bc76e793..1fc2891917 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -1,15 +1,18 @@
-#ifndef NVIM_EVAL_H
-#define NVIM_EVAL_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds_defs.h"
-#include "nvim/hashtab.h"
+#include "nvim/globals.h"
+#include "nvim/hashtab_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/os/fileio.h"
#include "nvim/os/stdpaths_defs.h"
@@ -51,11 +54,11 @@ typedef struct lval_S {
list_T *ll_list; ///< The list or NULL.
bool ll_range; ///< true when a [i:j] range was used.
bool ll_empty2; ///< Second index is empty: [i:].
- long ll_n1; ///< First index for list.
- long ll_n2; ///< Second index for list range.
+ int ll_n1; ///< First index for list.
+ int ll_n2; ///< Second index for list range.
dict_T *ll_dict; ///< The Dictionary or NULL.
dictitem_T *ll_di; ///< The dictitem or NULL.
- char *ll_newkey; ///< New key for Dict in allocated memory or NULL.
+ char *ll_newkey; ///< New key for Dict in allocated memory or NULL.
blob_T *ll_blob; ///< The Blob or NULL.
} lval_T;
@@ -157,6 +160,7 @@ typedef enum {
VV_ARGV,
VV_COLLATE,
VV_EXITING,
+ VV_MAXCOL,
// Nvim
VV_STDERR,
VV_MSGPACK_TYPES,
@@ -222,22 +226,12 @@ typedef struct {
int repeat_count;
int refcount;
int emsg_count; ///< Errors in a repeating timer.
- long timeout;
+ int64_t timeout;
bool stopped;
bool paused;
Callback callback;
} timer_T;
-/// Type of assert_* check being performed
-typedef enum {
- ASSERT_EQUAL,
- ASSERT_NOTEQUAL,
- ASSERT_MATCH,
- ASSERT_NOTMATCH,
- ASSERT_INRANGE,
- ASSERT_OTHER,
-} assert_type_T;
-
/// types for expressions.
typedef enum {
EXPR_UNKNOWN = 0,
@@ -265,7 +259,30 @@ typedef int (*ex_unletlock_callback)(lval_T *, char *, exarg_T *, int);
// Used for checking if local variables or arguments used in a lambda.
extern bool *eval_lavars_used;
+/// Struct passed through eval() functions.
+/// See EVALARG_EVALUATE for a fixed value with eval_flags set to EVAL_EVALUATE.
+typedef struct {
+ int eval_flags; ///< EVAL_ flag values below
+
+ /// copied from exarg_T when "getline" is "getsourceline". Can be NULL.
+ LineGetter eval_getline;
+ void *eval_cookie; ///< argument for eval_getline()
+
+ /// pointer to the last line obtained with getsourceline()
+ char *eval_tofree;
+} evalarg_T;
+
+/// Flag for expression evaluation.
+enum {
+ EVAL_EVALUATE = 1, ///< when missing don't actually evaluate
+};
+
+// Character used as separated in autoload function/variable names.
+#define AUTOLOAD_CHAR '#'
+
+/// Passed to an eval() function to enable evaluation.
+EXTERN evalarg_T EVALARG_EVALUATE INIT( = { EVAL_EVALUATE, NULL, NULL, NULL });
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
#endif
-#endif // NVIM_EVAL_H
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index c17a44b990..55f4721c3a 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -2,462 +2,12821 @@
--
-- Keys:
--
--- args Number of arguments, list with maximum and minimum number of arguments
--- or list with a minimum number of arguments only. Defaults to zero
--- arguments.
--- base For methods: the argument to use as the base argument (1-indexed):
--- base->method()
--- Defaults to BASE_NONE (function cannot be used as a method).
--- func Name of the C function which implements the VimL function. Defaults to
--- `f_{funcname}`.
--- fast Function can run in |api-fast| events. Defaults to false.
-
-local varargs = function(nr)
- return {nr}
-end
+--- @class vim.EvalFn
+--- @field name? string
+--- @field args? integer|integer[] Number of arguments, list with maximum and minimum number of arguments
+--- or list with a minimum number of arguments only. Defaults to zero
+--- arguments.
+--- @field base? integer For methods: the argument to use as the base argument (1-indexed):
+--- base->method()
+--- Defaults to BASE_NONE (function cannot be used as a method).
+--- @field func? string Name of the C function which implements the Vimscript function. Defaults to
+--- `f_{funcname}`.
+--- @field float_func? string
+--- @field fast? boolean Function can run in |api-fast| events. Defaults to false.
+--- @field deprecated? true
+--- @field returns? string|false
+--- @field returns_desc? string
+--- @field signature string
+--- @field desc? string
+--- @field params {[1]:string, [2]:string, [3]:string}[]
+--- @field lua? false Do not render type information
+--- @field tags? string[] Extra tags
+--- @field data? string Used by gen_eval.lua
-- Usable with the base key: use the last function argument as the method base.
-- Value is from funcs.h file. "BASE_" prefix is omitted.
-- local LAST = "BASE_LAST" (currently unused after port of v8.2.1168)
-return {
- funcs={
- abs={args=1, base=1},
- acos={args=1, base=1, float_func="acos"}, -- WJMc
- add={args=2, base=1},
- ['and']={args=2, base=1},
- api_info={},
- append={args=2, base=2},
- appendbufline={args=3, base=3},
- argc={args={0, 1}},
- argidx={},
- arglistid={args={0, 2}},
- argv={args={0, 2}},
- asin={args=1, base=1, float_func="asin"}, -- WJMc
- assert_beeps={args=1, base=1},
- assert_equal={args={2, 3}, base=2},
- assert_equalfile={args={2, 3}, base=1},
- assert_exception={args={1, 2}},
- assert_fails={args={1, 5}, base=1},
- assert_false={args={1, 2}, base=1},
- assert_inrange={args={3, 4}, base=3},
- assert_match={args={2, 3}, base=2},
- assert_nobeep={args=1, base=1},
- assert_notequal={args={2, 3}, base=2},
- assert_notmatch={args={2, 3}, base=2},
- assert_report={args=1, base=1},
- assert_true={args={1, 2}, base=1},
- atan={args=1, base=1, float_func="atan"},
- atan2={args=2, base=1},
- browse={args=4},
- browsedir={args=2},
- bufadd={args=1, base=1},
- bufexists={args=1, base=1},
- buffer_exists={args=1, base=1, func='f_bufexists'}, -- obsolete
- buffer_name={args={0, 1}, base=1, func='f_bufname'}, -- obsolete
- buffer_number={args={0, 1}, base=1, func='f_bufnr'}, -- obsolete
- buflisted={args=1, base=1},
- bufload={args=1, base=1},
- bufloaded={args=1, base=1},
- bufname={args={0, 1}, base=1},
- bufnr={args={0, 2}, base=1},
- bufwinid={args=1, base=1},
- bufwinnr={args=1, base=1},
- byte2line={args=1, base=1},
- byteidx={args=2, base=1},
- byteidxcomp={args=2, base=1},
- call={args={2, 3}, base=1},
- ceil={args=1, base=1, float_func="ceil"},
- changenr={},
- chanclose={args={1, 2}},
- chansend={args=2},
- char2nr={args={1, 2}, base=1},
- charclass={args=1, base=1},
- charcol={args={1, 2}, base=1},
- charidx={args={2, 3}, base=1},
- chdir={args=1, base=1},
- cindent={args=1, base=1},
- clearmatches={args={0, 1}, base=1},
- col={args={1, 2}, base=1},
- complete={args=2, base=2},
- complete_add={args=1, base=1},
- complete_check={},
- complete_info={args={0, 1}, base=1},
- confirm={args={1, 4}, base=1},
- copy={args=1, base=1},
- cos={args=1, base=1, float_func="cos"},
- cosh={args=1, base=1, float_func="cosh"},
- count={args={2, 4}, base=1},
- ctxget={args={0, 1}},
- ctxpop={},
- ctxpush={args={0, 1}},
- ctxset={args={1, 2}},
- ctxsize={},
- cursor={args={1, 3}, base=1},
- debugbreak={args={1, 1}, base=1},
- deepcopy={args={1, 2}, base=1},
- delete={args={1,2}, base=1},
- deletebufline={args={2,3}, base=1},
- dictwatcheradd={args=3},
- dictwatcherdel={args=3},
- did_filetype={},
- diff_filler={args=1, base=1},
- diff_hlID={args=2, base=1},
- digraph_get={args=1, base=1},
- digraph_getlist={args={0, 1}, base=1},
- digraph_set={args=2, base=1},
- digraph_setlist={args=1, base=1},
- empty={args=1, base=1},
- environ={},
- escape={args=2, base=1},
- eval={args=1, base=1},
- eventhandler={},
- executable={args=1, base=1},
- execute={args={1, 2}, base=1},
- exepath={args=1, base=1},
- exists={args=1, base=1},
- exp={args=1, base=1, float_func="exp"},
- expand={args={1, 3}, base=1},
- expandcmd={args={1, 2}, base=1},
- extend={args={2, 3}, base=1},
- feedkeys={args={1, 2}, base=1},
- file_readable={args=1, base=1, func='f_filereadable'}, -- obsolete
- filereadable={args=1, base=1},
- filewritable={args=1, base=1},
- filter={args=2, base=1},
- finddir={args={1, 3}, base=1},
- findfile={args={1, 3}, base=1},
- flatten={args={1, 2}, base=1},
- float2nr={args=1, base=1},
- floor={args=1, base=1, float_func="floor"},
- fmod={args=2, base=1},
- fnameescape={args=1, base=1},
- fnamemodify={args=2, base=1},
- foldclosed={args=1, base=1},
- foldclosedend={args=1, base=1},
- foldlevel={args=1, base=1},
- foldtext={},
- foldtextresult={args=1, base=1},
- foreground={},
- fullcommand={args=1, base=1},
- funcref={args={1, 3}, base=1},
- ['function']={args={1, 3}, base=1},
- garbagecollect={args={0, 1}},
- get={args={2, 3}, base=1},
- getbufinfo={args={0, 1}, base=1},
- getbufline={args={2, 3}, base=1},
- getbufoneline={args=2, base=1},
- getbufvar={args={2, 3}, base=1},
- getcellwidths={},
- getchangelist={args={0, 1}, base=1},
- getchar={args={0, 1}},
- getcharmod={},
- getcharpos={args=1, base=1},
- getcharsearch={},
- getcharstr={args={0, 1}},
- getcmdcompltype={},
- getcmdline={},
- getcmdpos={},
- getcmdscreenpos={},
- getcmdtype={},
- getcmdwintype={},
- getcompletion={args={2, 3}, base=1},
- getcurpos={args={0, 1}, base=1},
- getcursorcharpos={args={0, 1}, base=1},
- getcwd={args={0, 2}, base=1},
- getenv={args=1, base=1},
- getfontname={args={0, 1}},
- getfperm={args=1, base=1},
- getfsize={args=1, base=1},
- getftime={args=1, base=1},
- getftype={args=1, base=1},
- getjumplist={args={0, 2}, base=1},
- getline={args={1, 2}, base=1},
- getloclist={args={1, 2}},
- getmarklist={args={0, 1}, base=1},
- getmatches={args={0, 1}},
- getmousepos={},
- getpid={},
- getpos={args=1, base=1},
- getqflist={args={0, 1}},
- getreg={args={0, 3}, base=1},
- getreginfo={args={0, 1}, base=1},
- getregtype={args={0, 1}, base=1},
- gettabinfo={args={0, 1}, base=1},
- gettabvar={args={2, 3}, base=1},
- gettabwinvar={args={3, 4}, base=1},
- gettagstack={args={0, 1}, base=1},
- gettext={args=1, base=1},
- getwininfo={args={0, 1}, base=1},
- getwinpos={args={0, 1}, base=1},
- getwinposx={},
- getwinposy={},
- getwinvar={args={2, 3}, base=1},
- glob={args={1, 4}, base=1},
- glob2regpat={args=1, base=1},
- globpath={args={2, 5}, base=2},
- has={args=1},
- has_key={args=2, base=1},
- haslocaldir={args={0, 2}, base=1},
- hasmapto={args={1, 3}, base=1},
- highlightID={args=1, base=1, func='f_hlID'}, -- obsolete
- highlight_exists={args=1, base=1, func='f_hlexists'}, -- obsolete
- histadd={args=2, base=2},
- histdel={args={1, 2}, base=1},
- histget={args={1, 2}, base=1},
- histnr={args=1, base=1},
- hlID={args=1, base=1},
- hlexists={args=1, base=1},
- hostname={},
- iconv={args=3, base=1, fast=true},
- indent={args=1, base=1},
- index={args={2, 4}, base=1},
- input={args={1, 3}, base=1},
- inputdialog={args={1, 3}, base=1},
- inputlist={args=1, base=1},
- inputrestore={},
- inputsave={},
- inputsecret={args={1, 2}, base=1},
- insert={args={2, 3}, base=1},
- interrupt={args=0},
- invert={args=1, base=1},
- isdirectory={args=1, base=1},
- isinf={args=1, base=1},
- islocked={args=1, base=1},
- isnan={args=1, base=1},
- id={args=1},
- items={args=1, base=1},
- jobclose={args={1, 2}, func="f_chanclose"},
- jobpid={args=1},
- jobresize={args=3},
- jobsend={args=2, func="f_chansend"},
- jobstart={args={1, 2}},
- jobstop={args=1},
- jobwait={args={1, 2}},
- join={args={1, 2}, base=1},
- json_decode={args=1, base=1},
- json_encode={args=1, base=1},
- keys={args=1, base=1},
- keytrans={args=1, base=1},
- last_buffer_nr={}, -- obsolete
- len={args=1, base=1},
- libcall={args=3, base=3},
- libcallnr={args=3, base=3},
- line={args={1, 2}, base=1},
- line2byte={args=1, base=1},
- lispindent={args=1, base=1},
- list2str={args={1, 2}, base=1},
- localtime={},
- log={args=1, base=1, float_func="log"},
- log10={args=1, base=1, float_func="log10"},
- luaeval={args={1, 2}, base=1},
- map={args=2, base=1},
- maparg={args={1, 4}, base=1},
- mapcheck={args={1, 3}, base=1},
- mapset={args=3, base=1},
- match={args={2, 4}, base=1},
- matchadd={args={2, 5}, base=1},
- matchaddpos={args={2, 5}, base=1},
- matcharg={args=1, base=1},
- matchdelete={args={1, 2}, base=1},
- matchend={args={2, 4}, base=1},
- matchfuzzy={args={2, 3}, base=1},
- matchfuzzypos={args={2, 3}, base=1},
- matchlist={args={2, 4}, base=1},
- matchstr={args={2, 4}, base=1},
- matchstrpos={args={2,4}, base=1},
- max={args=1, base=1},
- menu_get={args={1, 2}},
- menu_info={args={1, 2}, base=1},
- min={args=1, base=1},
- mkdir={args={1, 3}, base=1},
- mode={args={0, 1}, base=1},
- msgpackdump={args={1, 2}},
- msgpackparse={args=1},
- nextnonblank={args=1, base=1},
- nr2char={args={1, 2}, base=1},
- ['or']={args=2, base=1},
- pathshorten={args={1, 2}, base=1},
- pow={args=2, base=1},
- prevnonblank={args=1, base=1},
- printf={args=varargs(1), base=2},
- prompt_getprompt={args=1, base=1},
- prompt_setcallback={args={2, 2}, base=1},
- prompt_setinterrupt={args={2, 2}, base=1},
- prompt_setprompt={args={2, 2}, base=1},
- pum_getpos={},
- pumvisible={},
- py3eval={args=1, base=1},
- pyeval={args=1, base=1, func="f_py3eval"},
- pyxeval={args=1, base=1, func="f_py3eval"},
- perleval={args=1, base=1},
- rand={args={0, 1}, base=1},
- range={args={1, 3}, base=1},
- readblob={args=1, base=1},
- readdir={args={1, 2}, base=1},
- readfile={args={1, 3}, base=1},
- reduce={args={2, 3}, base=1},
- reg_executing={},
- reg_recording={},
- reg_recorded={},
- reltime={args={0, 2}, base=1},
- reltimefloat={args=1, base=1},
- reltimestr={args=1, base=1},
- remove={args={2, 3}, base=1},
- rename={args=2, base=1},
- ['repeat']={args=2, base=1},
- resolve={args=1, base=1},
- reverse={args=1, base=1},
- round={args=1, base=1, float_func="round"},
- rpcnotify={args=varargs(2)},
- rpcrequest={args=varargs(2)},
- rpcstart={args={1, 2}},
- rpcstop={args=1},
- rubyeval={args=1, base=1},
- screenattr={args=2, base=1},
- screenchar={args=2, base=1},
- screenchars={args=2, base=1},
- screencol={},
- screenpos={args=3, base=1},
- screenrow={},
- screenstring={args=2, base=1},
- search={args={1, 5}, base=1},
- searchcount={args={0, 1}, base=1},
- searchdecl={args={1, 3}, base=1},
- searchpair={args={3, 7}},
- searchpairpos={args={3, 7}},
- searchpos={args={1, 5}, base=1},
- serverlist={},
- serverstart={args={0, 1}},
- serverstop={args=1},
- setbufline={args=3, base=3},
- setbufvar={args=3, base=3},
- setcellwidths={args=1, base=1},
- setcharpos={args=2, base=2},
- setcharsearch={args=1, base=1},
- setcmdpos={args=1, base=1},
- setcmdline={args={1, 2}, base=1},
- setcursorcharpos={args={1, 3}, base=1},
- setenv={args=2, base=2},
- setfperm={args=2, base=1},
- setline={args=2, base=2},
- setloclist={args={2, 4}, base=2},
- setmatches={args={1, 2}, base=1},
- setpos={args=2, base=2},
- setqflist={args={1, 3}, base=1},
- setreg={args={2, 3}, base=2},
- settabvar={args=3, base=3},
- settabwinvar={args=4, base=4},
- settagstack={args={2, 3}, base=2},
- setwinvar={args=3, base=3},
- sha256={args=1, base=1},
- shellescape={args={1, 2}, base=1},
- shiftwidth={args={0, 1}, base=1},
- sign_define={args={1, 2}, base=1},
- sign_getdefined={args={0, 1}, base=1},
- sign_getplaced={args={0, 2}, base=1},
- sign_jump={args=3, base=1},
- sign_place={args={4, 5}, base=1},
- sign_placelist={args=1, base=1},
- sign_undefine={args={0, 1}, base=1},
- sign_unplace={args={1, 2}, base=1},
- sign_unplacelist={args=1, base=1},
- simplify={args=1, base=1},
- sin={args=1, base=1, float_func="sin"},
- sinh={args=1, base=1, float_func="sinh"},
- sockconnect={args={2,3}},
- sort={args={1, 3}, base=1},
- soundfold={args=1, base=1},
- stdioopen={args=1},
- spellbadword={args={0, 1}, base=1},
- spellsuggest={args={1, 3}, base=1},
- split={args={1, 3}, base=1},
- sqrt={args=1, base=1, float_func="sqrt"},
- srand={args={0, 1}, base=1},
- stdpath={args=1},
- str2float={args=1, base=1},
- str2list={args={1, 2}, base=1},
- str2nr={args={1, 3}, base=1},
- strcharlen={args=1, base=1},
- strcharpart={args={2, 3}, base=1},
- strchars={args={1, 2}, base=1},
- strdisplaywidth={args={1, 2}, base=1},
- strftime={args={1, 2}, base=1},
- strgetchar={args=2, base=1},
- stridx={args={2, 3}, base=1},
- string={args=1, base=1},
- strlen={args=1, base=1},
- strpart={args={2, 4}, base=1},
- strptime={args=2, base=1},
- strridx={args={2, 3}, base=1},
- strtrans={args=1, base=1},
- strwidth={args=1, base=1},
- submatch={args={1, 2}, base=1},
- substitute={args=4, base=1},
- swapinfo={args=1, base=1},
- swapname={args=1, base=1},
- synID={args=3},
- synIDattr={args={2, 3}, base=1},
- synIDtrans={args=1, base=1},
- synconcealed={args=2},
- synstack={args=2},
- system={args={1, 2}, base=1},
- systemlist={args={1, 3}, base=1},
- tabpagebuflist={args={0, 1}, base=1},
- tabpagenr={args={0, 1}},
- tabpagewinnr={args={1, 2}, base=1},
- tagfiles={},
- taglist={args={1, 2}, base=1},
- tan={args=1, base=1, float_func="tan"},
- tanh={args=1, base=1, float_func="tanh"},
- tempname={},
- termopen={args={1, 2}},
- test_garbagecollect_now={},
- test_write_list_log={args=1},
- timer_info={args={0, 1}, base=1},
- timer_pause={args=2, base=1},
- timer_start={args={2, 3}, base=1},
- timer_stop={args=1, base=1},
- timer_stopall={args=0},
- tolower={args=1, base=1},
- toupper={args=1, base=1},
- tr={args=3, base=1},
- trim={args={1, 3}, base=1},
- trunc={args=1, base=1, float_func="trunc"},
- type={args=1, base=1},
- undofile={args=1, base=1},
- undotree={},
- uniq={args={1, 3}, base=1},
- values={args=1, base=1},
- virtcol={args=1, base=1},
- virtcol2col={args=3, base=1},
- visualmode={args={0, 1}},
- wait={args={2,3}},
- wildmenumode={},
- win_execute={args={2, 3}, base=2},
- win_findbuf={args=1, base=1},
- win_getid={args={0, 2}, base=1},
- win_gettype={args={0, 1}, base=1},
- win_gotoid={args=1, base=1},
- win_id2tabwin={args=1, base=1},
- win_id2win={args=1, base=1},
- win_move_separator={args=2, base=1},
- win_move_statusline={args=2, base=1},
- win_screenpos={args=1, base=1},
- win_splitmove={args={2, 3}, base=1},
- winbufnr={args=1, base=1},
- wincol={},
- windowsversion={},
- winheight={args=1, base=1},
- winlayout={args={0, 1}, base=1},
- winline={},
- winnr={args={0, 1}, base=1},
- winrestcmd={},
- winrestview={args=1, base=1},
- winsaveview={},
- winwidth={args=1, base=1},
- wordcount={},
- writefile={args={2, 3}, base=1},
- xor={args=2, base=1},
+local M = {}
+
+local VARARGS = { { '...', 'any' } }
+
+--- @type table<string,vim.EvalFn>
+M.funcs = {
+ abs = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the absolute value of {expr}. When {expr} evaluates to
+ a |Float| abs() returns a |Float|. When {expr} can be
+ converted to a |Number| abs() returns a |Number|. Otherwise
+ abs() gives an error message and returns -1.
+ Examples: >vim
+ echo abs(1.456)
+ < 1.456 >vim
+ echo abs(-5.456)
+ < 5.456 >vim
+ echo abs(-4)
+ < 4
+
+ ]=],
+ name = 'abs',
+ params = { { 'expr', 'any' } },
+ signature = 'abs({expr})',
+ returns = 'number',
+ },
+ acos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the arc cosine of {expr} measured in radians, as a
+ |Float| in the range of [0, pi].
+ {expr} must evaluate to a |Float| or a |Number| in the range
+ [-1, 1].
+ Returns NaN if {expr} is outside the range [-1, 1]. Returns
+ 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo acos(0)
+ < 1.570796 >vim
+ echo acos(-0.5)
+ < 2.094395
+
+ ]=],
+ float_func = 'acos',
+ name = 'acos',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'acos({expr})',
+ },
+ add = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Append the item {expr} to |List| or |Blob| {object}. Returns
+ the resulting |List| or |Blob|. Examples: >vim
+ let alist = add([1, 2, 3], item)
+ call add(mylist, "woodstock")
+ <Note that when {expr} is a |List| it is appended as a single
+ item. Use |extend()| to concatenate |Lists|.
+ When {object} is a |Blob| then {expr} must be a number.
+ Use |insert()| to add an item at another position.
+ Returns 1 if {object} is not a |List| or a |Blob|.
+
+ ]=],
+ name = 'add',
+ params = { { 'object', 'any' }, { 'expr', 'any' } },
+ returns = 'any',
+ signature = 'add({object}, {expr})',
+ },
+ ['and'] = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Bitwise AND on the two arguments. The arguments are converted
+ to a number. A List, Dict or Float argument causes an error.
+ Also see `or()` and `xor()`.
+ Example: >vim
+ let flag = and(bits, 0x80)
+ <
+ ]=],
+ name = 'and',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ returns = 'integer',
+ signature = 'and({expr}, {expr})',
+ },
+ api_info = {
+ desc = [=[
+ Returns Dictionary of |api-metadata|.
+
+ View it in a nice human-readable format: >vim
+ lua vim.print(vim.fn.api_info())
+ <
+ ]=],
+ fast = true,
+ name = 'api_info',
+ params = {},
+ returns = 'table',
+ signature = 'api_info()',
+ },
+ append = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ When {text} is a |List|: Append each item of the |List| as a
+ text line below line {lnum} in the current buffer.
+ Otherwise append {text} as one text line below line {lnum} in
+ the current buffer.
+ Any type of item is accepted and converted to a String.
+ {lnum} can be zero to insert a line before the first one.
+ {lnum} is used like with |getline()|.
+ Returns 1 for failure ({lnum} out of range or out of memory),
+ 0 for success. When {text} is an empty list zero is returned,
+ no matter the value of {lnum}. Example: >vim
+ let failed = append(line('$'), "# THE END")
+ let failed = append(0, ["Chapter 1", "the beginning"])
+ <
+
+ ]=],
+ name = 'append',
+ params = { { 'lnum', 'integer' }, { 'text', 'any' } },
+ returns = '0|1',
+ signature = 'append({lnum}, {text})',
+ },
+ appendbufline = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Like |append()| but append the text in buffer {expr}.
+
+ This function works only for loaded buffers. First call
+ |bufload()| if needed.
+
+ For the use of {buf}, see |bufname()|.
+
+ {lnum} is the line number to append below. Note that using
+ |line()| would use the current buffer, not the one appending
+ to. Use "$" to append at the end of the buffer. Other string
+ values are not supported.
+
+ On success 0 is returned, on failure 1 is returned.
+
+ If {buf} is not a valid buffer or {lnum} is not valid, an
+ error message is given. Example: >vim
+ let failed = appendbufline(13, 0, "# THE START")
+ <However, when {text} is an empty list then no error is given
+ for an invalid {lnum}, since {lnum} isn't actually used.
+
+ ]=],
+ name = 'appendbufline',
+ params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'text', 'string' } },
+ returns = '0|1',
+ signature = 'appendbufline({buf}, {lnum}, {text})',
+ },
+ argc = {
+ args = { 0, 1 },
+ desc = [=[
+ The result is the number of files in the argument list. See
+ |arglist|.
+ If {winid} is not supplied, the argument list of the current
+ window is used.
+ If {winid} is -1, the global argument list is used.
+ Otherwise {winid} specifies the window of which the argument
+ list is used: either the window number or the window ID.
+ Returns -1 if the {winid} argument is invalid.
+ ]=],
+ name = 'argc',
+ params = { { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'argc([{winid}])',
+ },
+ argidx = {
+ desc = [=[
+ The result is the current index in the argument list. 0 is
+ the first file. argc() - 1 is the last one. See |arglist|.
+ ]=],
+ name = 'argidx',
+ params = {},
+ returns = 'integer',
+ signature = 'argidx()',
+ },
+ arglistid = {
+ args = { 0, 2 },
+ desc = [=[
+ Return the argument list ID. This is a number which
+ identifies the argument list being used. Zero is used for the
+ global argument list. See |arglist|.
+ Returns -1 if the arguments are invalid.
+
+ Without arguments use the current window.
+ With {winnr} only use this window in the current tab page.
+ With {winnr} and {tabnr} use the window in the specified tab
+ page.
+ {winnr} can be the window number or the |window-ID|.
+ ]=],
+ name = 'arglistid',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ returns = 'integer',
+ signature = 'arglistid([{winnr} [, {tabnr}]])',
+ },
+ argv = {
+ args = { 0, 2 },
+ desc = [=[
+ The result is the {nr}th file in the argument list. See
+ |arglist|. "argv(0)" is the first one. Example: >vim
+ let i = 0
+ while i < argc()
+ let f = escape(fnameescape(argv(i)), '.')
+ exe 'amenu Arg.' .. f .. ' :e ' .. f .. '<CR>'
+ let i = i + 1
+ endwhile
+ <Without the {nr} argument, or when {nr} is -1, a |List| with
+ the whole |arglist| is returned.
+
+ The {winid} argument specifies the window ID, see |argc()|.
+ For the Vim command line arguments see |v:argv|.
+
+ Returns an empty string if {nr}th argument is not present in
+ the argument list. Returns an empty List if the {winid}
+ argument is invalid.
+ ]=],
+ name = 'argv',
+ params = { { 'nr', 'integer' }, { 'winid', 'integer' } },
+ returns = 'string|string[]',
+ signature = 'argv([{nr} [, {winid}]])',
+ },
+ asin = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the arc sine of {expr} measured in radians, as a |Float|
+ in the range of [-pi/2, pi/2].
+ {expr} must evaluate to a |Float| or a |Number| in the range
+ [-1, 1].
+ Returns NaN if {expr} is outside the range [-1, 1]. Returns
+ 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo asin(0.8)
+ < 0.927295 >vim
+ echo asin(-0.5)
+ < -0.523599
+
+ ]=],
+ float_func = 'asin',
+ name = 'asin',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'asin({expr})',
+ },
+ assert_beeps = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Run {cmd} and add an error message to |v:errors| if it does
+ NOT produce a beep or visual bell.
+ Also see |assert_fails()|, |assert_nobeep()| and
+ |assert-return|.
+
+ ]=],
+ name = 'assert_beeps',
+ params = { { 'cmd', 'any' } },
+ returns = '0|1',
+ signature = 'assert_beeps({cmd})',
+ },
+ assert_equal = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ When {expected} and {actual} are not equal an error message is
+ added to |v:errors| and 1 is returned. Otherwise zero is
+ returned. |assert-return|
+ The error is in the form "Expected {expected} but got
+ {actual}". When {msg} is present it is prefixed to that.
+
+ There is no automatic conversion, the String "4" is different
+ from the Number 4. And the number 4 is different from the
+ Float 4.0. The value of 'ignorecase' is not used here, case
+ always matters.
+ Example: >vim
+ assert_equal('foo', 'bar')
+ <Will result in a string to be added to |v:errors|:
+ test.vim line 12: Expected 'foo' but got 'bar' ~
+
+ ]=],
+ name = 'assert_equal',
+ params = { { 'expected', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_equal({expected}, {actual} [, {msg}])',
+ },
+ assert_equalfile = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ When the files {fname-one} and {fname-two} do not contain
+ exactly the same text an error message is added to |v:errors|.
+ Also see |assert-return|.
+ When {fname-one} or {fname-two} does not exist the error will
+ mention that.
+
+ ]=],
+ name = 'assert_equalfile',
+ params = {},
+ returns = '0|1',
+ signature = 'assert_equalfile({fname-one}, {fname-two})',
+ },
+ assert_exception = {
+ args = { 1, 2 },
+ desc = [=[
+ When v:exception does not contain the string {error} an error
+ message is added to |v:errors|. Also see |assert-return|.
+ This can be used to assert that a command throws an exception.
+ Using the error number, followed by a colon, avoids problems
+ with translations: >vim
+ try
+ commandthatfails
+ call assert_false(1, 'command should have failed')
+ catch
+ call assert_exception('E492:')
+ endtry
+ <
+ ]=],
+ name = 'assert_exception',
+ params = { { 'error', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_exception({error} [, {msg}])',
+ },
+ assert_fails = {
+ args = { 1, 5 },
+ base = 1,
+ desc = [=[
+ Run {cmd} and add an error message to |v:errors| if it does
+ NOT produce an error or when {error} is not found in the
+ error message. Also see |assert-return|.
+
+ When {error} is a string it must be found literally in the
+ first reported error. Most often this will be the error code,
+ including the colon, e.g. "E123:". >vim
+ assert_fails('bad cmd', 'E987:')
+ <
+ When {error} is a |List| with one or two strings, these are
+ used as patterns. The first pattern is matched against the
+ first reported error: >vim
+ assert_fails('cmd', ['E987:.*expected bool'])
+ <The second pattern, if present, is matched against the last
+ reported error. To only match the last error use an empty
+ string for the first error: >vim
+ assert_fails('cmd', ['', 'E987:'])
+ <
+ If {msg} is empty then it is not used. Do this to get the
+ default message when passing the {lnum} argument.
+
+ When {lnum} is present and not negative, and the {error}
+ argument is present and matches, then this is compared with
+ the line number at which the error was reported. That can be
+ the line number in a function or in a script.
+
+ When {context} is present it is used as a pattern and matched
+ against the context (script name or function name) where
+ {lnum} is located in.
+
+ Note that beeping is not considered an error, and some failing
+ commands only beep. Use |assert_beeps()| for those.
+
+ ]=],
+ name = 'assert_fails',
+ params = {
+ { 'cmd', 'any' },
+ { 'error', 'any' },
+ { 'msg', 'any' },
+ { 'lnum', 'integer' },
+ { 'context', 'any' },
+ },
+ returns = '0|1',
+ signature = 'assert_fails({cmd} [, {error} [, {msg} [, {lnum} [, {context}]]]])',
+ },
+ assert_false = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ When {actual} is not false an error message is added to
+ |v:errors|, like with |assert_equal()|.
+ The error is in the form "Expected False but got {actual}".
+ When {msg} is present it is prepended to that.
+ Also see |assert-return|.
+
+ A value is false when it is zero. When {actual} is not a
+ number the assert fails.
+
+ ]=],
+ name = 'assert_false',
+ params = { { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_false({actual} [, {msg}])',
+ },
+ assert_inrange = {
+ args = { 3, 4 },
+ base = 3,
+ desc = [=[
+ This asserts number and |Float| values. When {actual} is lower
+ than {lower} or higher than {upper} an error message is added
+ to |v:errors|. Also see |assert-return|.
+ The error is in the form "Expected range {lower} - {upper},
+ but got {actual}". When {msg} is present it is prefixed to
+ that.
+ ]=],
+ name = 'assert_inrange',
+ params = { { 'lower', 'any' }, { 'upper', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_inrange({lower}, {upper}, {actual} [, {msg}])',
+ },
+ assert_match = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ When {pattern} does not match {actual} an error message is
+ added to |v:errors|. Also see |assert-return|.
+ The error is in the form "Pattern {pattern} does not match
+ {actual}". When {msg} is present it is prefixed to that.
+
+ {pattern} is used as with |expr-=~|: The matching is always done
+ like 'magic' was set and 'cpoptions' is empty, no matter what
+ the actual value of 'magic' or 'cpoptions' is.
+
+ {actual} is used as a string, automatic conversion applies.
+ Use "^" and "$" to match with the start and end of the text.
+ Use both to match the whole text.
+
+ Example: >vim
+ assert_match('^f.*o$', 'foobar')
+ <Will result in a string to be added to |v:errors|:
+ test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~
+
+ ]=],
+ name = 'assert_match',
+ params = { { 'pattern', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_match({pattern}, {actual} [, {msg}])',
+ },
+ assert_nobeep = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Run {cmd} and add an error message to |v:errors| if it
+ produces a beep or visual bell.
+ Also see |assert_beeps()|.
+
+ ]=],
+ name = 'assert_nobeep',
+ params = { { 'cmd', 'any' } },
+ returns = '0|1',
+ signature = 'assert_nobeep({cmd})',
+ },
+ assert_notequal = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ The opposite of `assert_equal()`: add an error message to
+ |v:errors| when {expected} and {actual} are equal.
+ Also see |assert-return|.
+
+ ]=],
+ name = 'assert_notequal',
+ params = { { 'expected', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_notequal({expected}, {actual} [, {msg}])',
+ },
+ assert_notmatch = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ The opposite of `assert_match()`: add an error message to
+ |v:errors| when {pattern} matches {actual}.
+ Also see |assert-return|.
+
+ ]=],
+ name = 'assert_notmatch',
+ params = { { 'pattern', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_notmatch({pattern}, {actual} [, {msg}])',
+ },
+ assert_report = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Report a test failure directly, using String {msg}.
+ Always returns one.
+
+ ]=],
+ name = 'assert_report',
+ params = { { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_report({msg})',
+ },
+ assert_true = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ When {actual} is not true an error message is added to
+ |v:errors|, like with |assert_equal()|.
+ Also see |assert-return|.
+ A value is |TRUE| when it is a non-zero number or |v:true|.
+ When {actual} is not a number or |v:true| the assert fails.
+ When {msg} is given it precedes the default message.
+
+ ]=],
+ name = 'assert_true',
+ params = { { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_true({actual} [, {msg}])',
+ },
+ atan = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the principal value of the arc tangent of {expr}, in
+ the range [-pi/2, +pi/2] radians, as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo atan(100)
+ < 1.560797 >vim
+ echo atan(-4.01)
+ < -1.326405
+
+ ]=],
+ float_func = 'atan',
+ name = 'atan',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'atan({expr})',
+ },
+ atan2 = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Return the arc tangent of {expr1} / {expr2}, measured in
+ radians, as a |Float| in the range [-pi, pi].
+ {expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+ |Number|.
+ Examples: >vim
+ echo atan2(-1, 1)
+ < -0.785398 >vim
+ echo atan2(1, -1)
+ < 2.356194
+
+ ]=],
+ name = 'atan2',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ returns = 'number',
+ signature = 'atan2({expr1}, {expr2})',
+ },
+ blob2list = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a List containing the number value of each byte in Blob
+ {blob}. Examples: >vim
+ blob2list(0z0102.0304) " returns [1, 2, 3, 4]
+ blob2list(0z) " returns []
+ <Returns an empty List on error. |list2blob()| does the
+ opposite.
+
+ ]=],
+ name = 'blob2list',
+ params = { { 'blob', 'any' } },
+ returns = 'any[]',
+ signature = 'blob2list({blob})',
+ },
+ browse = {
+ args = 4,
+ desc = [=[
+ Put up a file requester. This only works when "has("browse")"
+ returns |TRUE| (only in some GUI versions).
+ The input fields are:
+ {save} when |TRUE|, select file to write
+ {title} title for the requester
+ {initdir} directory to start browsing in
+ {default} default file name
+ An empty string is returned when the "Cancel" button is hit,
+ something went wrong, or browsing is not possible.
+ ]=],
+ name = 'browse',
+ params = { { 'save', 'any' }, { 'title', 'any' }, { 'initdir', 'any' }, { 'default', 'any' } },
+ returns = '0|1',
+ signature = 'browse({save}, {title}, {initdir}, {default})',
+ },
+ browsedir = {
+ args = 2,
+ desc = [=[
+ Put up a directory requester. This only works when
+ "has("browse")" returns |TRUE| (only in some GUI versions).
+ On systems where a directory browser is not supported a file
+ browser is used. In that case: select a file in the directory
+ to be used.
+ The input fields are:
+ {title} title for the requester
+ {initdir} directory to start browsing in
+ When the "Cancel" button is hit, something went wrong, or
+ browsing is not possible, an empty string is returned.
+ ]=],
+ name = 'browsedir',
+ params = { { 'title', 'any' }, { 'initdir', 'any' } },
+ returns = '0|1',
+ signature = 'browsedir({title}, {initdir})',
+ },
+ bufadd = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Add a buffer to the buffer list with name {name} (must be a
+ String).
+ If a buffer for file {name} already exists, return that buffer
+ number. Otherwise return the buffer number of the newly
+ created buffer. When {name} is an empty string then a new
+ buffer is always created.
+ The buffer will not have 'buflisted' set and not be loaded
+ yet. To add some text to the buffer use this: >vim
+ let bufnr = bufadd('someName')
+ call bufload(bufnr)
+ call setbufline(bufnr, 1, ['some', 'text'])
+ <Returns 0 on error.
+ ]=],
+ name = 'bufadd',
+ params = { { 'name', 'string' } },
+ returns = 'integer',
+ signature = 'bufadd({name})',
+ },
+ bufexists = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if a buffer called
+ {buf} exists.
+ If the {buf} argument is a number, buffer numbers are used.
+ Number zero is the alternate buffer for the current window.
+
+ If the {buf} argument is a string it must match a buffer name
+ exactly. The name can be:
+ - Relative to the current directory.
+ - A full path.
+ - The name of a buffer with 'buftype' set to "nofile".
+ - A URL name.
+ Unlisted buffers will be found.
+ Note that help files are listed by their short name in the
+ output of |:buffers|, but bufexists() requires using their
+ long name to be able to find them.
+ bufexists() may report a buffer exists, but to use the name
+ with a |:buffer| command you may need to use |expand()|. Esp
+ for MS-Windows 8.3 names in the form "c:\DOCUME~1"
+ Use "bufexists(0)" to test for the existence of an alternate
+ file name.
+
+ ]=],
+ name = 'bufexists',
+ params = { { 'buf', 'any' } },
+ returns = '0|1',
+ signature = 'bufexists({buf})',
+ },
+ buffer_exists = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |bufexists()|.
+ ]=],
+ func = 'f_bufexists',
+ name = 'buffer_exists',
+ params = VARARGS,
+ returns = '0|1',
+ signature = 'buffer_exists({buf})',
+ },
+ buffer_name = {
+ args = { 0, 1 },
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |bufname()|.
+ ]=],
+ func = 'f_bufname',
+ name = 'buffer_name',
+ params = VARARGS,
+ returns = 'string',
+ signature = 'buffer_name([{buf}])',
+ },
+ buffer_number = {
+ args = { 0, 1 },
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |bufnr()|.
+ ]=],
+ func = 'f_bufnr',
+ name = 'buffer_number',
+ params = VARARGS,
+ returns = 'integer',
+ signature = 'buffer_number([{buf} [, {create}]])',
+ },
+ buflisted = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if a buffer called
+ {buf} exists and is listed (has the 'buflisted' option set).
+ The {buf} argument is used like with |bufexists()|.
+
+ ]=],
+ name = 'buflisted',
+ params = { { 'buf', 'any' } },
+ returns = '0|1',
+ signature = 'buflisted({buf})',
+ },
+ bufload = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Ensure the buffer {buf} is loaded. When the buffer name
+ refers to an existing file then the file is read. Otherwise
+ the buffer will be empty. If the buffer was already loaded
+ then there is no change. If the buffer is not related to a
+ file then no file is read (e.g., when 'buftype' is "nofile").
+ If there is an existing swap file for the file of the buffer,
+ there will be no dialog, the buffer will be loaded anyway.
+ The {buf} argument is used like with |bufexists()|.
+
+ ]=],
+ name = 'bufload',
+ params = { { 'buf', 'any' } },
+ returns = false,
+ signature = 'bufload({buf})',
+ },
+ bufloaded = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if a buffer called
+ {buf} exists and is loaded (shown in a window or hidden).
+ The {buf} argument is used like with |bufexists()|.
+
+ ]=],
+ name = 'bufloaded',
+ params = { { 'buf', 'any' } },
+ returns = '0|1',
+ signature = 'bufloaded({buf})',
+ },
+ bufname = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is the name of a buffer. Mostly as it is displayed
+ by the `:ls` command, but not using special names such as
+ "[No Name]".
+ If {buf} is omitted the current buffer is used.
+ If {buf} is a Number, that buffer number's name is given.
+ Number zero is the alternate buffer for the current window.
+ If {buf} is a String, it is used as a |file-pattern| to match
+ with the buffer names. This is always done like 'magic' is
+ set and 'cpoptions' is empty. When there is more than one
+ match an empty string is returned.
+ "" or "%" can be used for the current buffer, "#" for the
+ alternate buffer.
+ A full match is preferred, otherwise a match at the start, end
+ or middle of the buffer name is accepted. If you only want a
+ full match then put "^" at the start and "$" at the end of the
+ pattern.
+ Listed buffers are found first. If there is a single match
+ with a listed buffer, that one is returned. Next unlisted
+ buffers are searched for.
+ If the {buf} is a String, but you want to use it as a buffer
+ number, force it to be a Number by adding zero to it: >vim
+ echo bufname("3" + 0)
+ <If the buffer doesn't exist, or doesn't have a name, an empty
+ string is returned. >vim
+ echo bufname("#") " alternate buffer name
+ echo bufname(3) " name of buffer 3
+ echo bufname("%") " name of current buffer
+ echo bufname("file2") " name of buffer where "file2" matches.
+ <
+ ]=],
+ name = 'bufname',
+ params = { { 'buf', 'any' } },
+ returns = 'string',
+ signature = 'bufname([{buf}])',
+ },
+ bufnr = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ The result is the number of a buffer, as it is displayed by
+ the `:ls` command. For the use of {buf}, see |bufname()|
+ above.
+ If the buffer doesn't exist, -1 is returned. Or, if the
+ {create} argument is present and TRUE, a new, unlisted,
+ buffer is created and its number is returned.
+ bufnr("$") is the last buffer: >vim
+ let last_buffer = bufnr("$")
+ <The result is a Number, which is the highest buffer number
+ of existing buffers. Note that not all buffers with a smaller
+ number necessarily exist, because ":bwipeout" may have removed
+ them. Use bufexists() to test for the existence of a buffer.
+
+ ]=],
+ name = 'bufnr',
+ params = { { 'buf', 'any' }, { 'create', 'any' } },
+ returns = 'integer',
+ signature = 'bufnr([{buf} [, {create}]])',
+ },
+ bufwinid = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the |window-ID| of the first
+ window associated with buffer {buf}. For the use of {buf},
+ see |bufname()| above. If buffer {buf} doesn't exist or
+ there is no such window, -1 is returned. Example: >vim
+
+ echo "A window containing buffer 1 is " .. (bufwinid(1))
+ <
+ Only deals with the current tab page. See |win_findbuf()| for
+ finding more.
+
+ ]=],
+ name = 'bufwinid',
+ params = { { 'buf', 'any' } },
+ returns = 'integer',
+ signature = 'bufwinid({buf})',
+ },
+ bufwinnr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Like |bufwinid()| but return the window number instead of the
+ |window-ID|.
+ If buffer {buf} doesn't exist or there is no such window, -1
+ is returned. Example: >vim
+
+ echo "A window containing buffer 1 is " .. (bufwinnr(1))
+
+ <The number can be used with |CTRL-W_w| and ":wincmd w"
+ |:wincmd|.
+
+ ]=],
+ name = 'bufwinnr',
+ params = { { 'buf', 'any' } },
+ returns = 'integer',
+ signature = 'bufwinnr({buf})',
+ },
+ byte2line = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the line number that contains the character at byte
+ count {byte} in the current buffer. This includes the
+ end-of-line character, depending on the 'fileformat' option
+ for the current buffer. The first character has byte count
+ one.
+ Also see |line2byte()|, |go| and |:goto|.
+
+ Returns -1 if the {byte} value is invalid.
+
+ ]=],
+ name = 'byte2line',
+ params = { { 'byte', 'any' } },
+ returns = 'integer',
+ signature = 'byte2line({byte})',
+ },
+ byteidx = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Return byte index of the {nr}th character in the String
+ {expr}. Use zero for the first character, it then returns
+ zero.
+ If there are no multibyte characters the returned value is
+ equal to {nr}.
+ Composing characters are not counted separately, their byte
+ length is added to the preceding base character. See
+ |byteidxcomp()| below for counting composing characters
+ separately.
+ When {utf16} is present and TRUE, {nr} is used as the UTF-16
+ index in the String {expr} instead of as the character index.
+ The UTF-16 index is the index in the string when it is encoded
+ with 16-bit words. If the specified UTF-16 index is in the
+ middle of a character (e.g. in a 4-byte character), then the
+ byte index of the first byte in the character is returned.
+ Refer to |string-offset-encoding| for more information.
+ Example : >vim
+ echo matchstr(str, ".", byteidx(str, 3))
+ <will display the fourth character. Another way to do the
+ same: >vim
+ let s = strpart(str, byteidx(str, 3))
+ echo strpart(s, 0, byteidx(s, 1))
+ <Also see |strgetchar()| and |strcharpart()|.
+
+ If there are less than {nr} characters -1 is returned.
+ If there are exactly {nr} characters the length of the string
+ in bytes is returned.
+ See |charidx()| and |utf16idx()| for getting the character and
+ UTF-16 index respectively from the byte index.
+ Examples: >vim
+ echo byteidx('a😊😊', 2) " returns 5
+ echo byteidx('a😊😊', 2, 1) " returns 1
+ echo byteidx('a😊😊', 3, 1) " returns 5
+ <
+ ]=],
+ fast = true,
+ name = 'byteidx',
+ params = { { 'expr', 'any' }, { 'nr', 'integer' }, { 'utf16', 'any' } },
+ returns = 'integer',
+ signature = 'byteidx({expr}, {nr} [, {utf16}])',
+ },
+ byteidxcomp = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Like byteidx(), except that a composing character is counted
+ as a separate character. Example: >vim
+ let s = 'e' .. nr2char(0x301)
+ echo byteidx(s, 1)
+ echo byteidxcomp(s, 1)
+ echo byteidxcomp(s, 2)
+ <The first and third echo result in 3 ('e' plus composing
+ character is 3 bytes), the second echo results in 1 ('e' is
+ one byte).
+
+ ]=],
+ fast = true,
+ name = 'byteidxcomp',
+ params = { { 'expr', 'any' }, { 'nr', 'integer' }, { 'utf16', 'any' } },
+ returns = 'integer',
+ signature = 'byteidxcomp({expr}, {nr} [, {utf16}])',
+ },
+ call = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Call function {func} with the items in |List| {arglist} as
+ arguments.
+ {func} can either be a |Funcref| or the name of a function.
+ a:firstline and a:lastline are set to the cursor line.
+ Returns the return value of the called function.
+ {dict} is for functions with the "dict" attribute. It will be
+ used to set the local variable "self". |Dictionary-function|
+
+ ]=],
+ name = 'call',
+ params = { { 'func', 'any' }, { 'arglist', 'any' }, { 'dict', 'any' } },
+ returns = 'any',
+ signature = 'call({func}, {arglist} [, {dict}])',
+ tags = { 'E699' },
+ },
+ ceil = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the smallest integral value greater than or equal to
+ {expr} as a |Float| (round up).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Examples: >vim
+ echo ceil(1.456)
+ < 2.0 >vim
+ echo ceil(-5.456)
+ < -5.0 >vim
+ echo ceil(4.0)
+ < 4.0
+
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+
+ ]=],
+ float_func = 'ceil',
+ name = 'ceil',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'ceil({expr})',
+ },
+ chanclose = {
+ args = { 1, 2 },
+ desc = [=[
+ Close a channel or a specific stream associated with it.
+ For a job, {stream} can be one of "stdin", "stdout",
+ "stderr" or "rpc" (closes stdin/stdout for a job started
+ with `"rpc":v:true`) If {stream} is omitted, all streams
+ are closed. If the channel is a pty, this will then close the
+ pty master, sending SIGHUP to the job process.
+ For a socket, there is only one stream, and {stream} should be
+ omitted.
+ ]=],
+ name = 'chanclose',
+ params = { { 'id', 'any' }, { 'stream', 'any' } },
+ returns = '0|1',
+ signature = 'chanclose({id} [, {stream}])',
+ },
+ changenr = {
+ desc = [=[
+ Return the number of the most recent change. This is the same
+ number as what is displayed with |:undolist| and can be used
+ with the |:undo| command.
+ When a change was made it is the number of that change. After
+ redo it is the number of the redone change. After undo it is
+ one less than the number of the undone change.
+ Returns 0 if the undo list is empty.
+ ]=],
+ name = 'changenr',
+ params = {},
+ returns = 'integer',
+ signature = 'changenr()',
+ },
+ chansend = {
+ args = 2,
+ desc = [=[
+ Send data to channel {id}. For a job, it writes it to the
+ stdin of the process. For the stdio channel |channel-stdio|,
+ it writes to Nvim's stdout. Returns the number of bytes
+ written if the write succeeded, 0 otherwise.
+ See |channel-bytes| for more information.
+
+ {data} may be a string, string convertible, |Blob|, or a list.
+ If {data} is a list, the items will be joined by newlines; any
+ newlines in an item will be sent as NUL. To send a final
+ newline, include a final empty string. Example: >vim
+ call chansend(id, ["abc", "123\n456", ""])
+ <will send "abc<NL>123<NUL>456<NL>".
+
+ chansend() writes raw data, not RPC messages. If the channel
+ was created with `"rpc":v:true` then the channel expects RPC
+ messages, use |rpcnotify()| and |rpcrequest()| instead.
+ ]=],
+ name = 'chansend',
+ params = { { 'id', 'any' }, { 'data', 'any' } },
+ returns = '0|1',
+ signature = 'chansend({id}, {data})',
+ },
+ char2nr = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return Number value of the first char in {string}.
+ Examples: >vim
+ echo char2nr(" ") " returns 32
+ echo char2nr("ABC") " returns 65
+ echo char2nr("á") " returns 225
+ echo char2nr("á"[0]) " returns 195
+ echo char2nr("\<M-x>") " returns 128
+ <Non-ASCII characters are always treated as UTF-8 characters.
+ {utf8} is ignored, it exists only for backwards-compatibility.
+ A combining character is a separate character.
+ |nr2char()| does the opposite.
+
+ Returns 0 if {string} is not a |String|.
+
+ ]=],
+ fast = true,
+ name = 'char2nr',
+ params = { { 'string', 'string' }, { 'utf8', 'any' } },
+ returns = '0|1',
+ signature = 'char2nr({string} [, {utf8}])',
+ },
+ charclass = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the character class of the first character in {string}.
+ The character class is one of:
+ 0 blank
+ 1 punctuation
+ 2 word character
+ 3 emoji
+ other specific Unicode class
+ The class is used in patterns and word motions.
+ Returns 0 if {string} is not a |String|.
+ ]=],
+ name = 'charclass',
+ params = { { 'string', 'string' } },
+ returns = "0|1|2|3|'other'",
+ signature = 'charclass({string})',
+ },
+ charcol = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Same as |col()| but returns the character index of the column
+ position given with {expr} instead of the byte position.
+
+ Example:
+ With the cursor on '세' in line 5 with text "여보세요": >vim
+ echo charcol('.') " returns 3
+ echo col('.') " returns 7
+
+ ]=],
+ name = 'charcol',
+ params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'charcol({expr} [, {winid}])',
+ },
+ charidx = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Return the character index of the byte at {idx} in {string}.
+ The index of the first character is zero.
+ If there are no multibyte characters the returned value is
+ equal to {idx}.
+
+ When {countcc} is omitted or |FALSE|, then composing characters
+ are not counted separately, their byte length is added to the
+ preceding base character.
+ When {countcc} is |TRUE|, then composing characters are
+ counted as separate characters.
+
+ When {utf16} is present and TRUE, {idx} is used as the UTF-16
+ index in the String {expr} instead of as the byte index.
+
+ Returns -1 if the arguments are invalid or if there are less
+ than {idx} bytes. If there are exactly {idx} bytes the length
+ of the string in characters is returned.
+
+ An error is given and -1 is returned if the first argument is
+ not a string, the second argument is not a number or when the
+ third argument is present and is not zero or one.
+
+ See |byteidx()| and |byteidxcomp()| for getting the byte index
+ from the character index and |utf16idx()| for getting the
+ UTF-16 index from the character index.
+ Refer to |string-offset-encoding| for more information.
+ Examples: >vim
+ echo charidx('aÌbÌcÌ', 3) " returns 1
+ echo charidx('aÌbÌcÌ', 6, 1) " returns 4
+ echo charidx('aÌbÌcÌ', 16) " returns -1
+ echo charidx('a😊😊', 4, 0, 1) " returns 2
+ <
+ ]=],
+ name = 'charidx',
+ params = {
+ { 'string', 'string' },
+ { 'idx', 'integer' },
+ { 'countcc', 'any' },
+ { 'utf16', 'any' },
+ },
+ returns = 'integer',
+ signature = 'charidx({string}, {idx} [, {countcc} [, {utf16}]])',
+ },
+ chdir = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Change the current working directory to {dir}. The scope of
+ the directory change depends on the directory of the current
+ window:
+ - If the current window has a window-local directory
+ (|:lcd|), then changes the window local directory.
+ - Otherwise, if the current tabpage has a local
+ directory (|:tcd|) then changes the tabpage local
+ directory.
+ - Otherwise, changes the global directory.
+ {dir} must be a String.
+ If successful, returns the previous working directory. Pass
+ this to another chdir() to restore the directory.
+ On failure, returns an empty string.
+
+ Example: >vim
+ let save_dir = chdir(newdir)
+ if save_dir != ""
+ " ... do some work
+ call chdir(save_dir)
+ endif
+
+ ]=],
+ name = 'chdir',
+ params = { { 'dir', 'string' } },
+ returns = 'string',
+ signature = 'chdir({dir})',
+ },
+ cindent = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the amount of indent for line {lnum} according the C
+ indenting rules, as with 'cindent'.
+ The indent is counted in spaces, the value of 'tabstop' is
+ relevant. {lnum} is used just like in |getline()|.
+ When {lnum} is invalid -1 is returned.
+ See |C-indenting|.
+
+ ]=],
+ name = 'cindent',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'cindent({lnum})',
+ },
+ clearmatches = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Clears all matches previously defined for the current window
+ by |matchadd()| and the |:match| commands.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window.
+
+ ]=],
+ name = 'clearmatches',
+ params = { { 'win', 'any' } },
+ returns = false,
+ signature = 'clearmatches([{win}])',
+ },
+ col = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the byte index of the column
+ position given with {expr}. The accepted positions are:
+ . the cursor position
+ $ the end of the cursor line (the result is the
+ number of bytes in the cursor line plus one)
+ 'x position of mark x (if the mark is not set, 0 is
+ returned)
+ v In Visual mode: the start of the Visual area (the
+ cursor is the end). When not in Visual mode
+ returns the cursor position. Differs from |'<| in
+ that it's updated right away.
+ Additionally {expr} can be [lnum, col]: a |List| with the line
+ and column number. Most useful when the column is "$", to get
+ the last column of a specific line. When "lnum" or "col" is
+ out of range then col() returns zero.
+ With the optional {winid} argument the values are obtained for
+ that window instead of the current window.
+ To get the line number use |line()|. To get both use
+ |getpos()|.
+ For the screen column position use |virtcol()|. For the
+ character position use |charcol()|.
+ Note that only marks in the current file can be used.
+ Examples: >vim
+ echo col(".") " column of cursor
+ echo col("$") " length of cursor line plus one
+ echo col("'t") " column of mark t
+ echo col("'" .. markname) " column of mark markname
+ <The first column is 1. Returns 0 if {expr} is invalid or when
+ the window with ID {winid} is not found.
+ For an uppercase mark the column may actually be in another
+ buffer.
+ For the cursor position, when 'virtualedit' is active, the
+ column is one higher if the cursor is after the end of the
+ line. Also, when using a <Cmd> mapping the cursor isn't
+ moved, this can be used to obtain the column in Insert mode: >vim
+ imap <F2> <Cmd>echo col(".").."\n"<CR>
+
+ ]=],
+ name = 'col',
+ params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'col({expr} [, {winid}])',
+ },
+ complete = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set the matches for Insert mode completion.
+ Can only be used in Insert mode. You need to use a mapping
+ with CTRL-R = (see |i_CTRL-R|). It does not work after CTRL-O
+ or with an expression mapping.
+ {startcol} is the byte offset in the line where the completed
+ text start. The text up to the cursor is the original text
+ that will be replaced by the matches. Use col('.') for an
+ empty string. "col('.') - 1" will replace one character by a
+ match.
+ {matches} must be a |List|. Each |List| item is one match.
+ See |complete-items| for the kind of items that are possible.
+ "longest" in 'completeopt' is ignored.
+ Note that the after calling this function you need to avoid
+ inserting anything that would cause completion to stop.
+ The match can be selected with CTRL-N and CTRL-P as usual with
+ Insert mode completion. The popup menu will appear if
+ specified, see |ins-completion-menu|.
+ Example: >vim
+ inoremap <F5> <C-R>=ListMonths()<CR>
+
+ func ListMonths()
+ call complete(col('.'), ['January', 'February', 'March',
+ \ 'April', 'May', 'June', 'July', 'August', 'September',
+ \ 'October', 'November', 'December'])
+ return ''
+ endfunc
+ <This isn't very useful, but it shows how it works. Note that
+ an empty string is returned to avoid a zero being inserted.
+
+ ]=],
+ name = 'complete',
+ params = { { 'startcol', 'any' }, { 'matches', 'any' } },
+ returns = false,
+ signature = 'complete({startcol}, {matches})',
+ tags = { 'E785' },
+ },
+ complete_add = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Add {expr} to the list of matches. Only to be used by the
+ function specified with the 'completefunc' option.
+ Returns 0 for failure (empty string or out of memory),
+ 1 when the match was added, 2 when the match was already in
+ the list.
+ See |complete-functions| for an explanation of {expr}. It is
+ the same as one item in the list that 'omnifunc' would return.
+
+ ]=],
+ name = 'complete_add',
+ params = { { 'expr', 'any' } },
+ returns = '0|1|2',
+ signature = 'complete_add({expr})',
+ },
+ complete_check = {
+ desc = [=[
+ Check for a key typed while looking for completion matches.
+ This is to be used when looking for matches takes some time.
+ Returns |TRUE| when searching for matches is to be aborted,
+ zero otherwise.
+ Only to be used by the function specified with the
+ 'completefunc' option.
+ ]=],
+ name = 'complete_check',
+ params = {},
+ returns = '0|1',
+ signature = 'complete_check()',
+ },
+ complete_info = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns a |Dictionary| with information about Insert mode
+ completion. See |ins-completion|.
+ The items are:
+ mode Current completion mode name string.
+ See |complete_info_mode| for the values.
+ pum_visible |TRUE| if popup menu is visible.
+ See |pumvisible()|.
+ items List of completion matches. Each item is a
+ dictionary containing the entries "word",
+ "abbr", "menu", "kind", "info" and "user_data".
+ See |complete-items|.
+ selected Selected item index. First index is zero.
+ Index is -1 if no item is selected (showing
+ typed text only, or the last completion after
+ no item is selected when using the <Up> or
+ <Down> keys)
+ inserted Inserted string. [NOT IMPLEMENTED YET]
+
+ *complete_info_mode*
+ mode values are:
+ "" Not in completion mode
+ "keyword" Keyword completion |i_CTRL-X_CTRL-N|
+ "ctrl_x" Just pressed CTRL-X |i_CTRL-X|
+ "scroll" Scrolling with |i_CTRL-X_CTRL-E| or
+ |i_CTRL-X_CTRL-Y|
+ "whole_line" Whole lines |i_CTRL-X_CTRL-L|
+ "files" File names |i_CTRL-X_CTRL-F|
+ "tags" Tags |i_CTRL-X_CTRL-]|
+ "path_defines" Definition completion |i_CTRL-X_CTRL-D|
+ "path_patterns" Include completion |i_CTRL-X_CTRL-I|
+ "dictionary" Dictionary |i_CTRL-X_CTRL-K|
+ "thesaurus" Thesaurus |i_CTRL-X_CTRL-T|
+ "cmdline" Vim Command line |i_CTRL-X_CTRL-V|
+ "function" User defined completion |i_CTRL-X_CTRL-U|
+ "omni" Omni completion |i_CTRL-X_CTRL-O|
+ "spell" Spelling suggestions |i_CTRL-X_s|
+ "eval" |complete()| completion
+ "unknown" Other internal modes
+
+ If the optional {what} list argument is supplied, then only
+ the items listed in {what} are returned. Unsupported items in
+ {what} are silently ignored.
+
+ To get the position and size of the popup menu, see
+ |pum_getpos()|. It's also available in |v:event| during the
+ |CompleteChanged| event.
+
+ Returns an empty |Dictionary| on error.
+
+ Examples: >vim
+ " Get all items
+ call complete_info()
+ " Get only 'mode'
+ call complete_info(['mode'])
+ " Get only 'mode' and 'pum_visible'
+ call complete_info(['mode', 'pum_visible'])
+
+ ]=],
+ name = 'complete_info',
+ params = { { 'what', 'any' } },
+ returns = 'table',
+ signature = 'complete_info([{what}])',
+ },
+ confirm = {
+ args = { 1, 4 },
+ base = 1,
+ desc = [=[
+ confirm() offers the user a dialog, from which a choice can be
+ made. It returns the number of the choice. For the first
+ choice this is 1.
+
+ {msg} is displayed in a dialog with {choices} as the
+ alternatives. When {choices} is missing or empty, "&OK" is
+ used (and translated).
+ {msg} is a String, use '\n' to include a newline. Only on
+ some systems the string is wrapped when it doesn't fit.
+
+ {choices} is a String, with the individual choices separated
+ by '\n', e.g. >vim
+ confirm("Save changes?", "&Yes\n&No\n&Cancel")
+ <The letter after the '&' is the shortcut key for that choice.
+ Thus you can type 'c' to select "Cancel". The shortcut does
+ not need to be the first letter: >vim
+ confirm("file has been modified", "&Save\nSave &All")
+ <For the console, the first letter of each choice is used as
+ the default shortcut key. Case is ignored.
+
+ The optional {type} String argument gives the type of dialog.
+ It can be one of these values: "Error", "Question", "Info",
+ "Warning" or "Generic". Only the first character is relevant.
+ When {type} is omitted, "Generic" is used.
+
+ The optional {type} argument gives the type of dialog. This
+ is only used for the icon of the Win32 GUI. It can be one of
+ these values: "Error", "Question", "Info", "Warning" or
+ "Generic". Only the first character is relevant.
+ When {type} is omitted, "Generic" is used.
+
+ If the user aborts the dialog by pressing <Esc>, CTRL-C,
+ or another valid interrupt key, confirm() returns 0.
+
+ An example: >vim
+ let choice = confirm("What do you want?",
+ \ "&Apples\n&Oranges\n&Bananas", 2)
+ if choice == 0
+ echo "make up your mind!"
+ elseif choice == 3
+ echo "tasteful"
+ else
+ echo "I prefer bananas myself."
+ endif
+ <In a GUI dialog, buttons are used. The layout of the buttons
+ depends on the 'v' flag in 'guioptions'. If it is included,
+ the buttons are always put vertically. Otherwise, confirm()
+ tries to put the buttons in one horizontal line. If they
+ don't fit, a vertical layout is used anyway. For some systems
+ the horizontal layout is always used.
+
+ ]=],
+ name = 'confirm',
+ params = { { 'msg', 'any' }, { 'choices', 'any' }, { 'default', 'any' }, { 'type', 'any' } },
+ returns = 'integer',
+ signature = 'confirm({msg} [, {choices} [, {default} [, {type}]]])',
+ },
+ copy = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Make a copy of {expr}. For Numbers and Strings this isn't
+ different from using {expr} directly.
+ When {expr} is a |List| a shallow copy is created. This means
+ that the original |List| can be changed without changing the
+ copy, and vice versa. But the items are identical, thus
+ changing an item changes the contents of both |Lists|.
+ A |Dictionary| is copied in a similar way as a |List|.
+ Also see |deepcopy()|.
+ ]=],
+ name = 'copy',
+ params = { { 'expr', 'any' } },
+ returns = 'any',
+ signature = 'copy({expr})',
+ },
+ cos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the cosine of {expr}, measured in radians, as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo cos(100)
+ < 0.862319 >vim
+ echo cos(-4.01)
+ < -0.646043
+
+ ]=],
+ float_func = 'cos',
+ name = 'cos',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'cos({expr})',
+ },
+ cosh = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the hyperbolic cosine of {expr} as a |Float| in the range
+ [1, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo cosh(0.5)
+ < 1.127626 >vim
+ echo cosh(-0.5)
+ < -1.127626
+
+ ]=],
+ float_func = 'cosh',
+ name = 'cosh',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'cosh({expr})',
+ },
+ count = {
+ args = { 2, 4 },
+ base = 1,
+ tags = { 'E706' },
+ desc = [=[
+ Return the number of times an item with value {expr} appears
+ in |String|, |List| or |Dictionary| {comp}.
+
+ If {start} is given then start with the item with this index.
+ {start} can only be used with a |List|.
+
+ When {ic} is given and it's |TRUE| then case is ignored.
+
+ When {comp} is a string then the number of not overlapping
+ occurrences of {expr} is returned. Zero is returned when
+ {expr} is an empty string.
+
+ ]=],
+ name = 'count',
+ params = { { 'comp', 'any' }, { 'expr', 'any' }, { 'ic', 'any' }, { 'start', 'any' } },
+ returns = 'integer',
+ signature = 'count({comp}, {expr} [, {ic} [, {start}]])',
+ },
+ ctxget = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |Dictionary| representing the |context| at {index}
+ from the top of the |context-stack| (see |context-dict|).
+ If {index} is not given, it is assumed to be 0 (i.e.: top).
+ ]=],
+ name = 'ctxget',
+ params = { { 'index', 'any' } },
+ returns = 'table',
+ signature = 'ctxget([{index}])',
+ },
+ ctxpop = {
+ desc = [=[
+ Pops and restores the |context| at the top of the
+ |context-stack|.
+ ]=],
+ name = 'ctxpop',
+ params = {},
+ signature = 'ctxpop()',
+ },
+ ctxpush = {
+ args = { 0, 1 },
+ desc = [=[
+ Pushes the current editor state (|context|) on the
+ |context-stack|.
+ If {types} is given and is a |List| of |String|s, it specifies
+ which |context-types| to include in the pushed context.
+ Otherwise, all context types are included.
+ ]=],
+ name = 'ctxpush',
+ params = { { 'types', 'any' } },
+ signature = 'ctxpush([{types}])',
+ },
+ ctxset = {
+ args = { 1, 2 },
+ desc = [=[
+ Sets the |context| at {index} from the top of the
+ |context-stack| to that represented by {context}.
+ {context} is a Dictionary with context data (|context-dict|).
+ If {index} is not given, it is assumed to be 0 (i.e.: top).
+ ]=],
+ name = 'ctxset',
+ params = { { 'context', 'any' }, { 'index', 'any' } },
+ signature = 'ctxset({context} [, {index}])',
+ },
+ ctxsize = {
+ desc = [=[
+ Returns the size of the |context-stack|.
+ ]=],
+ name = 'ctxsize',
+ params = {},
+ signature = 'ctxsize()',
+ },
+ cursor = {
+ args = { 1, 3 },
+ base = 1,
+ name = 'cursor',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'any' } },
+ signature = 'cursor({lnum}, {col} [, {off}])',
+ },
+ cursor__1 = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Positions the cursor at the column (byte count) {col} in the
+ line {lnum}. The first column is one.
+
+ When there is one argument {list} this is used as a |List|
+ with two, three or four item:
+ [{lnum}, {col}]
+ [{lnum}, {col}, {off}]
+ [{lnum}, {col}, {off}, {curswant}]
+ This is like the return value of |getpos()| or |getcurpos()|,
+ but without the first item.
+
+ To position the cursor using {col} as the character count, use
+ |setcursorcharpos()|.
+
+ Does not change the jumplist.
+ {lnum} is used like with |getline()|, except that if {lnum} is
+ zero, the cursor will stay in the current line.
+ If {lnum} is greater than the number of lines in the buffer,
+ the cursor will be positioned at the last line in the buffer.
+ If {col} is greater than the number of bytes in the line,
+ the cursor will be positioned at the last character in the
+ line.
+ If {col} is zero, the cursor will stay in the current column.
+ If {curswant} is given it is used to set the preferred column
+ for vertical movement. Otherwise {col} is used.
+
+ When 'virtualedit' is used {off} specifies the offset in
+ screen columns from the start of the character. E.g., a
+ position within a <Tab> or after the last character.
+ Returns 0 when the position could be set, -1 otherwise.
+
+ ]=],
+ name = 'cursor',
+ params = { { 'list', 'any' } },
+ signature = 'cursor({list})',
+ },
+ debugbreak = {
+ args = { 1, 1 },
+ base = 1,
+ desc = [=[
+ Specifically used to interrupt a program being debugged. It
+ will cause process {pid} to get a SIGTRAP. Behavior for other
+ processes is undefined. See |terminal-debug|.
+ (Sends a SIGINT to a process {pid} other than MS-Windows)
+
+ Returns |TRUE| if successfully interrupted the program.
+ Otherwise returns |FALSE|.
+
+ ]=],
+ name = 'debugbreak',
+ params = { { 'pid', 'any' } },
+ signature = 'debugbreak({pid})',
+ },
+ deepcopy = {
+ args = { 1, 2 },
+ base = 1,
+ tags = { 'E698' },
+ desc = [=[
+ Make a copy of {expr}. For Numbers and Strings this isn't
+ different from using {expr} directly.
+ When {expr} is a |List| a full copy is created. This means
+ that the original |List| can be changed without changing the
+ copy, and vice versa. When an item is a |List|, a copy for it
+ is made, recursively. Thus changing an item in the copy does
+ not change the contents of the original |List|.
+
+ When {noref} is omitted or zero a contained |List| or
+ |Dictionary| is only copied once. All references point to
+ this single copy. With {noref} set to 1 every occurrence of a
+ |List| or |Dictionary| results in a new copy. This also means
+ that a cyclic reference causes deepcopy() to fail.
+ *E724*
+ Nesting is possible up to 100 levels. When there is an item
+ that refers back to a higher level making a deep copy with
+ {noref} set to 1 will fail.
+ Also see |copy()|.
+
+ ]=],
+ name = 'deepcopy',
+ params = { { 'expr', 'any' }, { 'noref', 'any' } },
+ signature = 'deepcopy({expr} [, {noref}])',
+ },
+ delete = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Without {flags} or with {flags} empty: Deletes the file by the
+ name {fname}.
+
+ This also works when {fname} is a symbolic link. The symbolic
+ link itself is deleted, not what it points to.
+
+ When {flags} is "d": Deletes the directory by the name
+ {fname}. This fails when directory {fname} is not empty.
+
+ When {flags} is "rf": Deletes the directory by the name
+ {fname} and everything in it, recursively. BE CAREFUL!
+ Note: on MS-Windows it is not possible to delete a directory
+ that is being used.
+
+ The result is a Number, which is 0/false if the delete
+ operation was successful and -1/true when the deletion failed
+ or partly failed.
+
+ ]=],
+ name = 'delete',
+ params = { { 'fname', 'string' }, { 'flags', 'string' } },
+ returns = 'integer',
+ signature = 'delete({fname} [, {flags}])',
+ },
+ deletebufline = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Delete lines {first} to {last} (inclusive) from buffer {buf}.
+ If {last} is omitted then delete line {first} only.
+ On success 0 is returned, on failure 1 is returned.
+
+ This function works only for loaded buffers. First call
+ |bufload()| if needed.
+
+ For the use of {buf}, see |bufname()| above.
+
+ {first} and {last} are used like with |getline()|. Note that
+ when using |line()| this refers to the current buffer. Use "$"
+ to refer to the last line in buffer {buf}.
+
+ ]=],
+ name = 'deletebufline',
+ params = { { 'buf', 'any' }, { 'first', 'any' }, { 'last', 'any' } },
+ signature = 'deletebufline({buf}, {first} [, {last}])',
+ },
+ dictwatcheradd = {
+ args = 3,
+ desc = [=[
+ Adds a watcher to a dictionary. A dictionary watcher is
+ identified by three components:
+
+ - A dictionary({dict});
+ - A key pattern({pattern}).
+ - A function({callback}).
+
+ After this is called, every change on {dict} and on keys
+ matching {pattern} will result in {callback} being invoked.
+
+ For example, to watch all global variables: >vim
+ silent! call dictwatcherdel(g:, '*', 'OnDictChanged')
+ function! OnDictChanged(d,k,z)
+ echomsg string(a:k) string(a:z)
+ endfunction
+ call dictwatcheradd(g:, '*', 'OnDictChanged')
+ <
+ For now {pattern} only accepts very simple patterns that can
+ contain a "*" at the end of the string, in which case it will
+ match every key that begins with the substring before the "*".
+ That means if "*" is not the last character of {pattern}, only
+ keys that are exactly equal as {pattern} will be matched.
+
+ The {callback} receives three arguments:
+
+ - The dictionary being watched.
+ - The key which changed.
+ - A dictionary containing the new and old values for the key.
+
+ The type of change can be determined by examining the keys
+ present on the third argument:
+
+ - If contains both `old` and `new`, the key was updated.
+ - If it contains only `new`, the key was added.
+ - If it contains only `old`, the key was deleted.
+
+ This function can be used by plugins to implement options with
+ validation and parsing logic.
+ ]=],
+ name = 'dictwatcheradd',
+ params = { { 'dict', 'any' }, { 'pattern', 'any' }, { 'callback', 'any' } },
+ signature = 'dictwatcheradd({dict}, {pattern}, {callback})',
+ },
+ dictwatcherdel = {
+ args = 3,
+ desc = [=[
+ Removes a watcher added with |dictwatcheradd()|. All three
+ arguments must match the ones passed to |dictwatcheradd()| in
+ order for the watcher to be successfully deleted.
+ ]=],
+ name = 'dictwatcherdel',
+ params = { { 'dict', 'any' }, { 'pattern', 'any' }, { 'callback', 'any' } },
+ signature = 'dictwatcherdel({dict}, {pattern}, {callback})',
+ },
+ did_filetype = {
+ desc = [=[
+ Returns |TRUE| when autocommands are being executed and the
+ FileType event has been triggered at least once. Can be used
+ to avoid triggering the FileType event again in the scripts
+ that detect the file type. |FileType|
+ Returns |FALSE| when `:setf FALLBACK` was used.
+ When editing another file, the counter is reset, thus this
+ really checks if the FileType event has been triggered for the
+ current buffer. This allows an autocommand that starts
+ editing another buffer to set 'filetype' and load a syntax
+ file.
+ ]=],
+ fast = true,
+ name = 'did_filetype',
+ params = {},
+ signature = 'did_filetype()',
+ },
+ diff_filler = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the number of filler lines above line {lnum}.
+ These are the lines that were inserted at this point in
+ another diff'ed window. These filler lines are shown in the
+ display but don't exist in the buffer.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+ Returns 0 if the current window is not in diff mode.
+
+ ]=],
+ name = 'diff_filler',
+ params = { { 'lnum', 'integer' } },
+ signature = 'diff_filler({lnum})',
+ },
+ diff_hlID = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Returns the highlight ID for diff mode at line {lnum} column
+ {col} (byte index). When the current line does not have a
+ diff change zero is returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+ {col} is 1 for the leftmost column, {lnum} is 1 for the first
+ line.
+ The highlight ID can be used with |synIDattr()| to obtain
+ syntax information about the highlighting.
+
+ ]=],
+ name = 'diff_hlID',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
+ signature = 'diff_hlID({lnum}, {col})',
+ },
+ digraph_get = {
+ args = 1,
+ base = 1,
+ tags = { 'E1214' },
+ desc = [=[
+ Return the digraph of {chars}. This should be a string with
+ exactly two characters. If {chars} are not just two
+ characters, or the digraph of {chars} does not exist, an error
+ is given and an empty string is returned.
+
+ Also see |digraph_getlist()|.
+
+ Examples: >vim
+ " Get a built-in digraph
+ echo digraph_get('00') " Returns '∞'
+
+ " Get a user-defined digraph
+ call digraph_set('aa', 'ã‚')
+ echo digraph_get('aa') " Returns 'ã‚'
+ <
+ ]=],
+ name = 'digraph_get',
+ params = { { 'chars', 'any' } },
+ signature = 'digraph_get({chars})',
+ },
+ digraph_getlist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return a list of digraphs. If the {listall} argument is given
+ and it is TRUE, return all digraphs, including the default
+ digraphs. Otherwise, return only user-defined digraphs.
+
+ Also see |digraph_get()|.
+
+ Examples: >vim
+ " Get user-defined digraphs
+ echo digraph_getlist()
+
+ " Get all the digraphs, including default digraphs
+ echo digraph_getlist(1)
+ <
+ ]=],
+ name = 'digraph_getlist',
+ params = { { 'listall', 'any' } },
+ signature = 'digraph_getlist([{listall}])',
+ },
+ digraph_set = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Add digraph {chars} to the list. {chars} must be a string
+ with two characters. {digraph} is a string with one UTF-8
+ encoded character. *E1215*
+ Be careful, composing characters are NOT ignored. This
+ function is similar to |:digraphs| command, but useful to add
+ digraphs start with a white space.
+
+ The function result is v:true if |digraph| is registered. If
+ this fails an error message is given and v:false is returned.
+
+ If you want to define multiple digraphs at once, you can use
+ |digraph_setlist()|.
+
+ Example: >vim
+ call digraph_set(' ', 'ã‚')
+ <
+ Can be used as a |method|: >vim
+ GetString()->digraph_set('ã‚')
+ <
+ ]=],
+ name = 'digraph_set',
+ params = { { 'chars', 'any' }, { 'digraph', 'any' } },
+ signature = 'digraph_set({chars}, {digraph})',
+ },
+ digraph_setlist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Similar to |digraph_set()| but this function can add multiple
+ digraphs at once. {digraphlist} is a list composed of lists,
+ where each list contains two strings with {chars} and
+ {digraph} as in |digraph_set()|. *E1216*
+ Example: >vim
+ call digraph_setlist([['aa', 'ã‚'], ['ii', 'ã„']])
+ <
+ It is similar to the following: >vim
+ for [chars, digraph] in [['aa', 'ã‚'], ['ii', 'ã„']]
+ call digraph_set(chars, digraph)
+ endfor
+ <Except that the function returns after the first error,
+ following digraphs will not be added.
+
+ Can be used as a |method|: >vim
+ GetList()->digraph_setlist()
+ <
+ ]=],
+ name = 'digraph_setlist',
+ params = { { 'digraphlist', 'any' } },
+ signature = 'digraph_setlist({digraphlist})',
+ },
+ empty = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the Number 1 if {expr} is empty, zero otherwise.
+ - A |List| or |Dictionary| is empty when it does not have any
+ items.
+ - A |String| is empty when its length is zero.
+ - A |Number| and |Float| are empty when their value is zero.
+ - |v:false| and |v:null| are empty, |v:true| is not.
+ - A |Blob| is empty when its length is zero.
+
+ ]=],
+ name = 'empty',
+ params = { { 'expr', 'any' } },
+ signature = 'empty({expr})',
+ },
+ environ = {
+ desc = [=[
+ Return all of environment variables as dictionary. You can
+ check if an environment variable exists like this: >vim
+ echo has_key(environ(), 'HOME')
+ <Note that the variable name may be CamelCase; to ignore case
+ use this: >vim
+ echo index(keys(environ()), 'HOME', 0, 1) != -1
+ <
+ ]=],
+ fast = true,
+ name = 'environ',
+ params = {},
+ signature = 'environ()',
+ },
+ escape = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Escape the characters in {chars} that occur in {string} with a
+ backslash. Example: >vim
+ echo escape('c:\program files\vim', ' \')
+ <results in: >
+ c:\\program\ files\\vim
+ <Also see |shellescape()| and |fnameescape()|.
+
+ ]=],
+ fast = true,
+ name = 'escape',
+ params = { { 'string', 'string' }, { 'chars', 'any' } },
+ signature = 'escape({string}, {chars})',
+ },
+ eval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate {string} and return the result. Especially useful to
+ turn the result of |string()| back into the original value.
+ This works for Numbers, Floats, Strings, Blobs and composites
+ of them. Also works for |Funcref|s that refer to existing
+ functions.
+
+ ]=],
+ name = 'eval',
+ params = { { 'string', 'string' } },
+ signature = 'eval({string})',
+ },
+ eventhandler = {
+ desc = [=[
+ Returns 1 when inside an event handler. That is that Vim got
+ interrupted while waiting for the user to type a character,
+ e.g., when dropping a file on Vim. This means interactive
+ commands cannot be used. Otherwise zero is returned.
+ ]=],
+ name = 'eventhandler',
+ params = {},
+ signature = 'eventhandler()',
+ },
+ executable = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ This function checks if an executable with the name {expr}
+ exists. {expr} must be the name of the program without any
+ arguments.
+ executable() uses the value of $PATH and/or the normal
+ searchpath for programs. *PATHEXT*
+ On MS-Windows the ".exe", ".bat", etc. can optionally be
+ included. Then the extensions in $PATHEXT are tried. Thus if
+ "foo.exe" does not exist, "foo.exe.bat" can be found. If
+ $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot
+ by itself can be used in $PATHEXT to try using the name
+ without an extension. When 'shell' looks like a Unix shell,
+ then the name is also tried without adding an extension.
+ On MS-Windows it only checks if the file exists and is not a
+ directory, not if it's really executable.
+ On Windows an executable in the same directory as Vim is
+ always found (it is added to $PATH at |startup|).
+ The result is a Number:
+ 1 exists
+ 0 does not exist
+ -1 not implemented on this system
+ |exepath()| can be used to get the full path of an executable.
+
+ ]=],
+ fast = true,
+ name = 'executable',
+ params = { { 'expr', 'any' } },
+ returns = '0|1|-1',
+ signature = 'executable({expr})',
+ },
+ execute = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Execute {command} and capture its output.
+ If {command} is a |String|, returns {command} output.
+ If {command} is a |List|, returns concatenated outputs.
+ Line continuations in {command} are not recognized.
+ Examples: >vim
+ echo execute('echon "foo"')
+ < foo >vim
+ echo execute(['echon "foo"', 'echon "bar"'])
+ < foobar
+
+ The optional {silent} argument can have these values:
+ "" no `:silent` used
+ "silent" `:silent` used
+ "silent!" `:silent!` used
+ The default is "silent". Note that with "silent!", unlike
+ `:redir`, error messages are dropped.
+
+ To get a list of lines use `split()` on the result: >vim
+ execute('args')->split("\n")
+
+ <This function is not available in the |sandbox|.
+ Note: If nested, an outer execute() will not observe output of
+ the inner calls.
+ Note: Text attributes (highlights) are not captured.
+ To execute a command in another window than the current one
+ use `win_execute()`.
+
+ ]=],
+ name = 'execute',
+ params = {
+ { 'command', 'string|string[]' },
+ { 'silent', "''|'silent'|'silent!'" }
+ },
+ returns = 'string',
+ signature = 'execute({command} [, {silent}])',
+ },
+ exepath = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the full path of {expr} if it is an executable and
+ given as a (partial or full) path or is found in $PATH.
+ Returns empty string otherwise.
+ If {expr} starts with "./" the |current-directory| is used.
+
+ ]=],
+ name = 'exepath',
+ params = { { 'expr', 'any' } },
+ signature = 'exepath({expr})',
+ },
+ exists = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if {expr} is
+ defined, zero otherwise.
+
+ For checking for a supported feature use |has()|.
+ For checking if a file exists use |filereadable()|.
+
+ The {expr} argument is a string, which contains one of these:
+ varname internal variable (see
+ dict.key |internal-variables|). Also works
+ list[i] for |curly-braces-names|, |Dictionary|
+ entries, |List| items, etc.
+ Beware that evaluating an index may
+ cause an error message for an invalid
+ expression. E.g.: >vim
+ let l = [1, 2, 3]
+ echo exists("l[5]")
+ < 0 >vim
+ echo exists("l[xx]")
+ < E121: Undefined variable: xx
+ 0
+ &option-name Vim option (only checks if it exists,
+ not if it really works)
+ +option-name Vim option that works.
+ $ENVNAME environment variable (could also be
+ done by comparing with an empty
+ string)
+ `*funcname` built-in function (see |functions|)
+ or user defined function (see
+ |user-function|). Also works for a
+ variable that is a Funcref.
+ :cmdname Ex command: built-in command, user
+ command or command modifier |:command|.
+ Returns:
+ 1 for match with start of a command
+ 2 full match with a command
+ 3 matches several user commands
+ To check for a supported command
+ always check the return value to be 2.
+ :2match The |:2match| command.
+ :3match The |:3match| command (but you
+ probably should not use it, it is
+ reserved for internal usage)
+ #event autocommand defined for this event
+ #event#pattern autocommand defined for this event and
+ pattern (the pattern is taken
+ literally and compared to the
+ autocommand patterns character by
+ character)
+ #group autocommand group exists
+ #group#event autocommand defined for this group and
+ event.
+ #group#event#pattern
+ autocommand defined for this group,
+ event and pattern.
+ ##event autocommand for this event is
+ supported.
+
+ Examples: >vim
+ echo exists("&mouse")
+ echo exists("$HOSTNAME")
+ echo exists("*strftime")
+ echo exists("*s:MyFunc")
+ echo exists("*MyFunc")
+ echo exists("bufcount")
+ echo exists(":Make")
+ echo exists("#CursorHold")
+ echo exists("#BufReadPre#*.gz")
+ echo exists("#filetypeindent")
+ echo exists("#filetypeindent#FileType")
+ echo exists("#filetypeindent#FileType#*")
+ echo exists("##ColorScheme")
+ <There must be no space between the symbol (&/$/*/#) and the
+ name.
+ There must be no extra characters after the name, although in
+ a few cases this is ignored. That may become stricter in the
+ future, thus don't count on it!
+ Working example: >vim
+ echo exists(":make")
+ <NOT working example: >vim
+ echo exists(":make install")
+
+ <Note that the argument must be a string, not the name of the
+ variable itself. For example: >vim
+ echo exists(bufcount)
+ <This doesn't check for existence of the "bufcount" variable,
+ but gets the value of "bufcount", and checks if that exists.
+
+ ]=],
+ name = 'exists',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'exists({expr})',
+ },
+ exp = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the exponential of {expr} as a |Float| in the range
+ [0, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo exp(2)
+ < 7.389056 >vim
+ echo exp(-1)
+ < 0.367879
+
+ ]=],
+ float_func = 'exp',
+ name = 'exp',
+ params = { { 'expr', 'any' } },
+ signature = 'exp({expr})',
+ },
+ expand = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Expand wildcards and the following special keywords in
+ {string}. 'wildignorecase' applies.
+
+ If {list} is given and it is |TRUE|, a List will be returned.
+ Otherwise the result is a String and when there are several
+ matches, they are separated by <NL> characters.
+
+ If the expansion fails, the result is an empty string. A name
+ for a non-existing file is not included, unless {string} does
+ not start with '%', '#' or '<', see below.
+
+ When {string} starts with '%', '#' or '<', the expansion is
+ done like for the |cmdline-special| variables with their
+ associated modifiers. Here is a short overview:
+
+ % current file name
+ # alternate file name
+ #n alternate file name n
+ <cfile> file name under the cursor
+ <afile> autocmd file name
+ <abuf> autocmd buffer number (as a String!)
+ <amatch> autocmd matched name
+ <cexpr> C expression under the cursor
+ <sfile> sourced script file or function name
+ <slnum> sourced script line number or function
+ line number
+ <sflnum> script file line number, also when in
+ a function
+ <SID> "<SNR>123_" where "123" is the
+ current script ID |<SID>|
+ <script> sourced script file, or script file
+ where the current function was defined
+ <stack> call stack
+ <cword> word under the cursor
+ <cWORD> WORD under the cursor
+ <client> the {clientid} of the last received
+ message
+ Modifiers:
+ :p expand to full path
+ :h head (last path component removed)
+ :t tail (last path component only)
+ :r root (one extension removed)
+ :e extension only
+
+ Example: >vim
+ let &tags = expand("%:p:h") .. "/tags"
+ <Note that when expanding a string that starts with '%', '#' or
+ '<', any following text is ignored. This does NOT work: >vim
+ let doesntwork = expand("%:h.bak")
+ <Use this: >vim
+ let doeswork = expand("%:h") .. ".bak"
+ <Also note that expanding "<cfile>" and others only returns the
+ referenced file name without further expansion. If "<cfile>"
+ is "~/.cshrc", you need to do another expand() to have the
+ "~/" expanded into the path of the home directory: >vim
+ echo expand(expand("<cfile>"))
+ <
+ There cannot be white space between the variables and the
+ following modifier. The |fnamemodify()| function can be used
+ to modify normal file names.
+
+ When using '%' or '#', and the current or alternate file name
+ is not defined, an empty string is used. Using "%:p" in a
+ buffer with no name, results in the current directory, with a
+ '/' added.
+ When 'verbose' is set then expanding '%', '#' and <> items
+ will result in an error message if the argument cannot be
+ expanded.
+
+ When {string} does not start with '%', '#' or '<', it is
+ expanded like a file name is expanded on the command line.
+ 'suffixes' and 'wildignore' are used, unless the optional
+ {nosuf} argument is given and it is |TRUE|.
+ Names for non-existing files are included. The "**" item can
+ be used to search in a directory tree. For example, to find
+ all "README" files in the current directory and below: >vim
+ echo expand("**/README")
+ <
+ expand() can also be used to expand variables and environment
+ variables that are only known in a shell. But this can be
+ slow, because a shell may be used to do the expansion. See
+ |expr-env-expand|.
+ The expanded variable is still handled like a list of file
+ names. When an environment variable cannot be expanded, it is
+ left unchanged. Thus ":echo expand('$FOOBAR')" results in
+ "$FOOBAR".
+
+ See |glob()| for finding existing files. See |system()| for
+ getting the raw output of an external command.
+
+ ]=],
+ name = 'expand',
+ params = { { 'string', 'string' }, { 'nosuf', 'boolean' }, { 'list', 'any' } },
+ returns = 'string|string[]',
+ signature = 'expand({string} [, {nosuf} [, {list}]])',
+ },
+ expandcmd = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Expand special items in String {string} like what is done for
+ an Ex command such as `:edit`. This expands special keywords,
+ like with |expand()|, and environment variables, anywhere in
+ {string}. "~user" and "~/path" are only expanded at the
+ start.
+
+ The following items are supported in the {options} Dict
+ argument:
+ errmsg If set to TRUE, error messages are displayed
+ if an error is encountered during expansion.
+ By default, error messages are not displayed.
+
+ Returns the expanded string. If an error is encountered
+ during expansion, the unmodified {string} is returned.
+
+ Example: >vim
+ echo expandcmd('make %<.o')
+ < >
+ make /path/runtime/doc/builtin.o
+ < >vim
+ echo expandcmd('make %<.o', {'errmsg': v:true})
+ <
+ ]=],
+ name = 'expandcmd',
+ params = { { 'string', 'string' }, { 'options', 'table' } },
+ signature = 'expandcmd({string} [, {options}])',
+ },
+ extend = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ {expr1} and {expr2} must be both |Lists| or both
+ |Dictionaries|.
+
+ If they are |Lists|: Append {expr2} to {expr1}.
+ If {expr3} is given insert the items of {expr2} before the
+ item with index {expr3} in {expr1}. When {expr3} is zero
+ insert before the first item. When {expr3} is equal to
+ len({expr1}) then {expr2} is appended.
+ Examples: >vim
+ echo sort(extend(mylist, [7, 5]))
+ call extend(mylist, [2, 3], 1)
+ <When {expr1} is the same List as {expr2} then the number of
+ items copied is equal to the original length of the List.
+ E.g., when {expr3} is 1 you get N new copies of the first item
+ (where N is the original length of the List).
+ Use |add()| to concatenate one item to a list. To concatenate
+ two lists into a new list use the + operator: >vim
+ let newlist = [1, 2, 3] + [4, 5]
+ <
+ If they are |Dictionaries|:
+ Add all entries from {expr2} to {expr1}.
+ If a key exists in both {expr1} and {expr2} then {expr3} is
+ used to decide what to do:
+ {expr3} = "keep": keep the value of {expr1}
+ {expr3} = "force": use the value of {expr2}
+ {expr3} = "error": give an error message *E737*
+ When {expr3} is omitted then "force" is assumed.
+
+ {expr1} is changed when {expr2} is not empty. If necessary
+ make a copy of {expr1} first.
+ {expr2} remains unchanged.
+ When {expr1} is locked and {expr2} is not empty the operation
+ fails.
+ Returns {expr1}. Returns 0 on error.
+
+ ]=],
+ name = 'extend',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' }, { 'expr3', 'any' } },
+ signature = 'extend({expr1}, {expr2} [, {expr3}])',
+ },
+ extendnew = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Like |extend()| but instead of adding items to {expr1} a new
+ List or Dictionary is created and returned. {expr1} remains
+ unchanged.
+ ]=],
+ name = 'extendnew',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' }, { 'expr3', 'any' } },
+ signature = 'extendnew({expr1}, {expr2} [, {expr3}])',
+ },
+ feedkeys = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Characters in {string} are queued for processing as if they
+ come from a mapping or were typed by the user.
+
+ By default the string is added to the end of the typeahead
+ buffer, thus if a mapping is still being executed the
+ characters come after them. Use the 'i' flag to insert before
+ other characters, they will be executed next, before any
+ characters from a mapping.
+
+ The function does not wait for processing of keys contained in
+ {string}.
+
+ To include special keys into {string}, use double-quotes
+ and "\..." notation |expr-quote|. For example,
+ feedkeys("\<CR>") simulates pressing of the <Enter> key. But
+ feedkeys('\<CR>') pushes 5 characters.
+ The |<Ignore>| keycode may be used to exit the
+ wait-for-character without doing anything.
+
+ {mode} is a String, which can contain these character flags:
+ 'm' Remap keys. This is default. If {mode} is absent,
+ keys are remapped.
+ 'n' Do not remap keys.
+ 't' Handle keys as if typed; otherwise they are handled as
+ if coming from a mapping. This matters for undo,
+ opening folds, etc.
+ 'i' Insert the string instead of appending (see above).
+ 'x' Execute commands until typeahead is empty. This is
+ similar to using ":normal!". You can call feedkeys()
+ several times without 'x' and then one time with 'x'
+ (possibly with an empty {string}) to execute all the
+ typeahead. Note that when Vim ends in Insert mode it
+ will behave as if <Esc> is typed, to avoid getting
+ stuck, waiting for a character to be typed before the
+ script continues.
+ Note that if you manage to call feedkeys() while
+ executing commands, thus calling it recursively, then
+ all typeahead will be consumed by the last call.
+ '!' When used with 'x' will not end Insert mode. Can be
+ used in a test when a timer is set to exit Insert mode
+ a little later. Useful for testing CursorHoldI.
+
+ Return value is always 0.
+
+ ]=],
+ name = 'feedkeys',
+ params = { { 'string', 'string' }, { 'mode', 'string' } },
+ signature = 'feedkeys({string} [, {mode}])',
+ },
+ file_readable = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |filereadable()|.
+ ]=],
+ func = 'f_filereadable',
+ name = 'file_readable',
+ params = { { 'file', 'string' } },
+ signature = 'file_readable({file})',
+ },
+ filereadable = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| when a file with the
+ name {file} exists, and can be read. If {file} doesn't exist,
+ or is a directory, the result is |FALSE|. {file} is any
+ expression, which is used as a String.
+ If you don't care about the file being readable you can use
+ |glob()|.
+ {file} is used as-is, you may want to expand wildcards first: >vim
+ echo filereadable('~/.vimrc')
+ < >
+ 0
+ < >vim
+ echo filereadable(expand('~/.vimrc'))
+ < >
+ 1
+ <
+
+ ]=],
+ fast = true,
+ name = 'filereadable',
+ params = { { 'file', 'string' } },
+ returns = '0|1',
+ signature = 'filereadable({file})',
+ },
+ filewritable = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is 1 when a file with the
+ name {file} exists, and can be written. If {file} doesn't
+ exist, or is not writable, the result is 0. If {file} is a
+ directory, and we can write to it, the result is 2.
+
+ ]=],
+ fast = true,
+ name = 'filewritable',
+ params = { { 'file', 'string' } },
+ returns = '0|1',
+ signature = 'filewritable({file})',
+ },
+ filter = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+ For each item in {expr1} evaluate {expr2} and when the result
+ is zero or false remove the item from the |List| or
+ |Dictionary|. Similarly for each byte in a |Blob| and each
+ character in a |String|.
+
+ {expr2} must be a |string| or |Funcref|.
+
+ If {expr2} is a |string|, inside {expr2} |v:val| has the value
+ of the current item. For a |Dictionary| |v:key| has the key
+ of the current item and for a |List| |v:key| has the index of
+ the current item. For a |Blob| |v:key| has the index of the
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Examples: >vim
+ call filter(mylist, 'v:val !~ "OLD"')
+ <Removes the items where "OLD" appears. >vim
+ call filter(mydict, 'v:key >= 8')
+ <Removes the items with a key below 8. >vim
+ call filter(var, 0)
+ <Removes all the items, thus clears the |List| or |Dictionary|.
+
+ Note that {expr2} is the result of expression and is then
+ used as an expression again. Often it is good to use a
+ |literal-string| to avoid having to double backslashes.
+
+ If {expr2} is a |Funcref| it must take two arguments:
+ 1. the key or the index of the current item.
+ 2. the value of the current item.
+ The function must return |TRUE| if the item should be kept.
+ Example that keeps the odd items of a list: >vim
+ func Odd(idx, val)
+ return a:idx % 2 == 1
+ endfunc
+ call filter(mylist, function('Odd'))
+ <It is shorter when using a |lambda|: >vim
+ call filter(myList, {idx, val -> idx * val <= 42})
+ <If you do not use "val" you can leave it out: >vim
+ call filter(myList, {idx -> idx % 2 == 1})
+ <
+ For a |List| and a |Dictionary| the operation is done
+ in-place. If you want it to remain unmodified make a copy
+ first: >vim
+ let l = filter(copy(mylist), 'v:val =~ "KEEP"')
+
+ <Returns {expr1}, the |List| or |Dictionary| that was filtered,
+ or a new |Blob| or |String|.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
+ unless it was defined with the "abort" flag.
+
+ ]=],
+ name = 'filter',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'filter({expr1}, {expr2})',
+ },
+ finddir = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Find directory {name} in {path}. Supports both downwards and
+ upwards recursive directory searches. See |file-searching|
+ for the syntax of {path}.
+
+ Returns the path of the first found match. When the found
+ directory is below the current directory a relative path is
+ returned. Otherwise a full path is returned.
+ If {path} is omitted or empty then 'path' is used.
+
+ If the optional {count} is given, find {count}'s occurrence of
+ {name} in {path} instead of the first one.
+ When {count} is negative return all the matches in a |List|.
+
+ Returns an empty string if the directory is not found.
+
+ This is quite similar to the ex-command `:find`.
+
+ ]=],
+ name = 'finddir',
+ params = { { 'name', 'string' }, { 'path', 'string' }, { 'count', 'any' } },
+ signature = 'finddir({name} [, {path} [, {count}]])',
+ },
+ findfile = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Just like |finddir()|, but find a file instead of a directory.
+ Uses 'suffixesadd'.
+ Example: >vim
+ echo findfile("tags.vim", ".;")
+ <Searches from the directory of the current file upwards until
+ it finds the file "tags.vim".
+
+ ]=],
+ name = 'findfile',
+ params = { { 'name', 'string' }, { 'path', 'string' }, { 'count', 'any' } },
+ signature = 'findfile({name} [, {path} [, {count}]])',
+ },
+ flatten = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Flatten {list} up to {maxdepth} levels. Without {maxdepth}
+ the result is a |List| without nesting, as if {maxdepth} is
+ a very large number.
+ The {list} is changed in place, use |flattennew()| if you do
+ not want that.
+ *E900*
+ {maxdepth} means how deep in nested lists changes are made.
+ {list} is not modified when {maxdepth} is 0.
+ {maxdepth} must be positive number.
+
+ If there is an error the number zero is returned.
+
+ Example: >vim
+ echo flatten([1, [2, [3, 4]], 5])
+ < [1, 2, 3, 4, 5] >vim
+ echo flatten([1, [2, [3, 4]], 5], 1)
+ < [1, 2, [3, 4], 5]
+
+ ]=],
+ name = 'flatten',
+ params = { { 'list', 'any' }, { 'maxdepth', 'any' } },
+ returns = 'any[]|0',
+ signature = 'flatten({list} [, {maxdepth}])',
+ },
+ flattennew = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Like |flatten()| but first make a copy of {list}.
+ ]=],
+ name = 'flattennew',
+ params = { { 'list', 'any' }, { 'maxdepth', 'any' } },
+ returns = 'any[]|0',
+ signature = 'flattennew({list} [, {maxdepth}])',
+ },
+ float2nr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert {expr} to a Number by omitting the part after the
+ decimal point.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0 if {expr} is not a |Float| or a |Number|.
+ When the value of {expr} is out of range for a |Number| the
+ result is truncated to 0x7fffffff or -0x7fffffff (or when
+ 64-bit Number support is enabled, 0x7fffffffffffffff or
+ -0x7fffffffffffffff). NaN results in -0x80000000 (or when
+ 64-bit Number support is enabled, -0x8000000000000000).
+ Examples: >vim
+ echo float2nr(3.95)
+ < 3 >vim
+ echo float2nr(-23.45)
+ < -23 >vim
+ echo float2nr(1.0e100)
+ < 2147483647 (or 9223372036854775807) >vim
+ echo float2nr(-1.0e150)
+ < -2147483647 (or -9223372036854775807) >vim
+ echo float2nr(1.0e-100)
+ < 0
+
+ ]=],
+ name = 'float2nr',
+ params = { { 'expr', 'any' } },
+ signature = 'float2nr({expr})',
+ },
+ floor = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the largest integral value less than or equal to
+ {expr} as a |Float| (round down).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo floor(1.856)
+ < 1.0 >vim
+ echo floor(-5.456)
+ < -6.0 >vim
+ echo floor(4.0)
+ < 4.0
+
+ ]=],
+ float_func = 'floor',
+ name = 'floor',
+ params = { { 'expr', 'any' } },
+ signature = 'floor({expr})',
+ },
+ fmod = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Return the remainder of {expr1} / {expr2}, even if the
+ division is not representable. Returns {expr1} - i * {expr2}
+ for some integer i such that if {expr2} is non-zero, the
+ result has the same sign as {expr1} and magnitude less than
+ the magnitude of {expr2}. If {expr2} is zero, the value
+ returned is zero. The value returned is a |Float|.
+ {expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+ |Number|.
+ Examples: >vim
+ echo fmod(12.33, 1.22)
+ < 0.13 >vim
+ echo fmod(-12.33, 1.22)
+ < -0.13
+
+ ]=],
+ name = 'fmod',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'fmod({expr1}, {expr2})',
+ },
+ fnameescape = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Escape {string} for use as file name command argument. All
+ characters that have a special meaning, such as `'%'` and `'|'`
+ are escaped with a backslash.
+ For most systems the characters escaped are
+ " \t\n*?[{`$\\%#'\"|!<". For systems where a backslash
+ appears in a filename, it depends on the value of 'isfname'.
+ A leading '+' and '>' is also escaped (special after |:edit|
+ and |:write|). And a "-" by itself (special after |:cd|).
+ Returns an empty string on error.
+ Example: >vim
+ let fname = '+some str%nge|name'
+ exe "edit " .. fnameescape(fname)
+ <results in executing: >vim
+ edit \+some\ str\%nge\|name
+ <
+ ]=],
+ fast = true,
+ name = 'fnameescape',
+ params = { { 'string', 'string' } },
+ returns = 'string',
+ signature = 'fnameescape({string})',
+ },
+ fnamemodify = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Modify file name {fname} according to {mods}. {mods} is a
+ string of characters like it is used for file names on the
+ command line. See |filename-modifiers|.
+ Example: >vim
+ echo fnamemodify("main.c", ":p:h")
+ <results in: >
+ /home/user/vim/vim/src
+ <If {mods} is empty or an unsupported modifier is used then
+ {fname} is returned.
+ When {fname} is empty then with {mods} ":h" returns ".", so
+ that `:cd` can be used with it. This is different from
+ expand('%:h') without a buffer name, which returns an empty
+ string.
+ Note: Environment variables don't work in {fname}, use
+ |expand()| first then.
+
+ ]=],
+ fast = true,
+ name = 'fnamemodify',
+ params = { { 'fname', 'string' }, { 'mods', 'string' } },
+ returns = 'string',
+ signature = 'fnamemodify({fname}, {mods})',
+ },
+ foldclosed = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number. If the line {lnum} is in a closed
+ fold, the result is the number of the first line in that fold.
+ If the line {lnum} is not in a closed fold, -1 is returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+
+ ]=],
+ name = 'foldclosed',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'foldclosed({lnum})',
+ },
+ foldclosedend = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number. If the line {lnum} is in a closed
+ fold, the result is the number of the last line in that fold.
+ If the line {lnum} is not in a closed fold, -1 is returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+
+ ]=],
+ name = 'foldclosedend',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'foldclosedend({lnum})',
+ },
+ foldlevel = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the foldlevel of line {lnum}
+ in the current buffer. For nested folds the deepest level is
+ returned. If there is no fold at line {lnum}, zero is
+ returned. It doesn't matter if the folds are open or closed.
+ When used while updating folds (from 'foldexpr') -1 is
+ returned for lines where folds are still to be updated and the
+ foldlevel is unknown. As a special case the level of the
+ previous line is usually available.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+
+ ]=],
+ name = 'foldlevel',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'foldlevel({lnum})',
+ },
+ foldtext = {
+ desc = [=[
+ Returns a String, to be displayed for a closed fold. This is
+ the default function used for the 'foldtext' option and should
+ only be called from evaluating 'foldtext'. It uses the
+ |v:foldstart|, |v:foldend| and |v:folddashes| variables.
+ The returned string looks like this: >
+ +-- 45 lines: abcdef
+ <The number of leading dashes depends on the foldlevel. The
+ "45" is the number of lines in the fold. "abcdef" is the text
+ in the first non-blank line of the fold. Leading white space,
+ "//" or "/*" and the text from the 'foldmarker' and
+ 'commentstring' options is removed.
+ When used to draw the actual foldtext, the rest of the line
+ will be filled with the fold char from the 'fillchars'
+ setting.
+ Returns an empty string when there is no fold.
+ ]=],
+ name = 'foldtext',
+ params = {},
+ returns = 'string',
+ signature = 'foldtext()',
+ },
+ foldtextresult = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the text that is displayed for the closed fold at line
+ {lnum}. Evaluates 'foldtext' in the appropriate context.
+ When there is no closed fold at {lnum} an empty string is
+ returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+ Useful when exporting folded text, e.g., to HTML.
+
+ ]=],
+ name = 'foldtextresult',
+ params = { { 'lnum', 'integer' } },
+ returns = 'string',
+ signature = 'foldtextresult({lnum})',
+ },
+ foreground = {
+ args = 0,
+ params = {},
+ signature = '',
+ lua = false,
+ },
+ fullcommand = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the full command name from a short abbreviated command
+ name; see |20.2| for details on command abbreviations.
+
+ The string argument {name} may start with a `:` and can
+ include a [range], these are skipped and not returned.
+ Returns an empty string if a command doesn't exist or if it's
+ ambiguous (for user-defined commands).
+
+ For example `fullcommand('s')`, `fullcommand('sub')`,
+ `fullcommand(':%substitute')` all return "substitute".
+
+ ]=],
+ name = 'fullcommand',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'fullcommand({name})',
+ },
+ funcref = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Just like |function()|, but the returned Funcref will lookup
+ the function by reference, not by name. This matters when the
+ function {name} is redefined later.
+
+ Unlike |function()|, {name} must be an existing user function.
+ It only works for an autoloaded function if it has already
+ been loaded (to avoid mistakenly loading the autoload script
+ when only intending to use the function name, use |function()|
+ instead). {name} cannot be a builtin function.
+ Returns 0 on error.
+
+ ]=],
+ name = 'funcref',
+ params = { { 'name', 'string' }, { 'arglist', 'any' }, { 'dict', 'any' } },
+ signature = 'funcref({name} [, {arglist}] [, {dict}])',
+ },
+ ['function'] = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Return a |Funcref| variable that refers to function {name}.
+ {name} can be the name of a user defined function or an
+ internal function.
+
+ {name} can also be a Funcref or a partial. When it is a
+ partial the dict stored in it will be used and the {dict}
+ argument is not allowed. E.g.: >vim
+ let FuncWithArg = function(dict.Func, [arg])
+ let Broken = function(dict.Func, [arg], dict)
+ <
+ When using the Funcref the function will be found by {name},
+ also when it was redefined later. Use |funcref()| to keep the
+ same function.
+
+ When {arglist} or {dict} is present this creates a partial.
+ That means the argument list and/or the dictionary is stored in
+ the Funcref and will be used when the Funcref is called.
+
+ The arguments are passed to the function in front of other
+ arguments, but after any argument from |method|. Example: >vim
+ func Callback(arg1, arg2, name)
+ "...
+ endfunc
+ let Partial = function('Callback', ['one', 'two'])
+ "...
+ call Partial('name')
+ <Invokes the function as with: >vim
+ call Callback('one', 'two', 'name')
+
+ <With a |method|: >vim
+ func Callback(one, two, three)
+ "...
+ endfunc
+ let Partial = function('Callback', ['two'])
+ "...
+ eval 'one'->Partial('three')
+ <Invokes the function as with: >vim
+ call Callback('one', 'two', 'three')
+
+ <The function() call can be nested to add more arguments to the
+ Funcref. The extra arguments are appended to the list of
+ arguments. Example: >vim
+ func Callback(arg1, arg2, name)
+ "...
+ endfunc
+ let Func = function('Callback', ['one'])
+ let Func2 = function(Func, ['two'])
+ "...
+ call Func2('name')
+ <Invokes the function as with: >vim
+ call Callback('one', 'two', 'name')
+
+ <The Dictionary is only useful when calling a "dict" function.
+ In that case the {dict} is passed in as "self". Example: >vim
+ function Callback() dict
+ echo "called for " .. self.name
+ endfunction
+ "...
+ let context = {"name": "example"}
+ let Func = function('Callback', context)
+ "...
+ call Func() " will echo: called for example
+ <The use of function() is not needed when there are no extra
+ arguments, these two are equivalent, if Callback() is defined
+ as context.Callback(): >vim
+ let Func = function('Callback', context)
+ let Func = context.Callback
+
+ <The argument list and the Dictionary can be combined: >vim
+ function Callback(arg1, count) dict
+ "...
+ endfunction
+ let context = {"name": "example"}
+ let Func = function('Callback', ['one'], context)
+ "...
+ call Func(500)
+ <Invokes the function as with: >vim
+ call context.Callback('one', 500)
+ <
+ Returns 0 on error.
+
+ ]=],
+ name = 'function',
+ params = { { 'name', 'string' }, { 'arglist', 'any' }, { 'dict', 'any' } },
+ signature = 'function({name} [, {arglist}] [, {dict}])',
+ tags = { 'partial', 'E700', 'E923' },
+ },
+ garbagecollect = {
+ args = { 0, 1 },
+ desc = [=[
+ Cleanup unused |Lists| and |Dictionaries| that have circular
+ references.
+
+ There is hardly ever a need to invoke this function, as it is
+ automatically done when Vim runs out of memory or is waiting
+ for the user to press a key after 'updatetime'. Items without
+ circular references are always freed when they become unused.
+ This is useful if you have deleted a very big |List| and/or
+ |Dictionary| with circular references in a script that runs
+ for a long time.
+
+ When the optional {atexit} argument is one, garbage
+ collection will also be done when exiting Vim, if it wasn't
+ done before. This is useful when checking for memory leaks.
+
+ The garbage collection is not done immediately but only when
+ it's safe to perform. This is when waiting for the user to
+ type a character.
+ ]=],
+ name = 'garbagecollect',
+ params = { { 'atexit', 'any' } },
+ signature = 'garbagecollect([{atexit}])',
+ },
+ get = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get item {idx} from |List| {list}. When this item is not
+ available return {default}. Return zero when {default} is
+ omitted.
+ ]=],
+ name = 'get',
+ params = { { 'list', 'any[]' }, { 'idx', 'integer' }, { 'default', 'any' } },
+ signature = 'get({list}, {idx} [, {default}])',
+ },
+ get__1 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get byte {idx} from |Blob| {blob}. When this byte is not
+ available return {default}. Return -1 when {default} is
+ omitted.
+ ]=],
+ name = 'get',
+ params = { { 'blob', 'string' }, { 'idx', 'integer' }, { 'default', 'any' } },
+ signature = 'get({blob}, {idx} [, {default}])',
+ },
+ get__2 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get item with key {key} from |Dictionary| {dict}. When this
+ item is not available return {default}. Return zero when
+ {default} is omitted. Useful example: >vim
+ let val = get(g:, 'var_name', 'default')
+ <This gets the value of g:var_name if it exists, and uses
+ "default" when it does not exist.
+ ]=],
+ name = 'get',
+ params = { { 'dict', 'table<string,any>' }, { 'key', 'string' }, { 'default', 'any' } },
+ signature = 'get({dict}, {key} [, {default}])',
+ },
+ get__3 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get item {what} from Funcref {func}. Possible values for
+ {what} are:
+ "name" The function name
+ "func" The function
+ "dict" The dictionary
+ "args" The list with arguments
+ Returns zero on error.
+ ]=],
+ name = 'get',
+ params = { { 'func', 'function' }, { 'what', 'string' } },
+ returns = 'any',
+ signature = 'get({func}, {what})',
+ },
+ getbufinfo = {
+ args = { 0, 1 },
+ base = 1,
+ name = 'getbufinfo',
+ params = { { 'buf', 'integer|string' } },
+ signature = 'getbufinfo([{buf}])',
+ returns = 'vim.fn.getbufinfo.ret.item[]',
+ },
+ getbufinfo__1 = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get information about buffers as a List of Dictionaries.
+
+ Without an argument information about all the buffers is
+ returned.
+
+ When the argument is a |Dictionary| only the buffers matching
+ the specified criteria are returned. The following keys can
+ be specified in {dict}:
+ buflisted include only listed buffers.
+ bufloaded include only loaded buffers.
+ bufmodified include only modified buffers.
+
+ Otherwise, {buf} specifies a particular buffer to return
+ information for. For the use of {buf}, see |bufname()|
+ above. If the buffer is found the returned List has one item.
+ Otherwise the result is an empty list.
+
+ Each returned List item is a dictionary with the following
+ entries:
+ bufnr Buffer number.
+ changed TRUE if the buffer is modified.
+ changedtick Number of changes made to the buffer.
+ hidden TRUE if the buffer is hidden.
+ lastused Timestamp in seconds, like
+ |localtime()|, when the buffer was
+ last used.
+ listed TRUE if the buffer is listed.
+ lnum Line number used for the buffer when
+ opened in the current window.
+ Only valid if the buffer has been
+ displayed in the window in the past.
+ If you want the line number of the
+ last known cursor position in a given
+ window, use |line()|: >vim
+ echo line('.', {winid})
+ <
+ linecount Number of lines in the buffer (only
+ valid when loaded)
+ loaded TRUE if the buffer is loaded.
+ name Full path to the file in the buffer.
+ signs List of signs placed in the buffer.
+ Each list item is a dictionary with
+ the following fields:
+ id sign identifier
+ lnum line number
+ name sign name
+ variables A reference to the dictionary with
+ buffer-local variables.
+ windows List of |window-ID|s that display this
+ buffer
+
+ Examples: >vim
+ for buf in getbufinfo()
+ echo buf.name
+ endfor
+ for buf in getbufinfo({'buflisted':1})
+ if buf.changed
+ " ....
+ endif
+ endfor
+ <
+ To get buffer-local options use: >vim
+ getbufvar({bufnr}, '&option_name')
+ <
+ ]=],
+ name = 'getbufinfo',
+ params = { { 'dict', 'vim.fn.getbufinfo.dict' } },
+ signature = 'getbufinfo([{dict}])',
+ returns = 'vim.fn.getbufinfo.ret.item[]',
+ },
+ getbufline = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Return a |List| with the lines starting from {lnum} to {end}
+ (inclusive) in the buffer {buf}. If {end} is omitted, a
+ |List| with only the line {lnum} is returned. See
+ `getbufoneline()` for only getting the line.
+
+ For the use of {buf}, see |bufname()| above.
+
+ For {lnum} and {end} "$" can be used for the last line of the
+ buffer. Otherwise a number must be used.
+
+ When {lnum} is smaller than 1 or bigger than the number of
+ lines in the buffer, an empty |List| is returned.
+
+ When {end} is greater than the number of lines in the buffer,
+ it is treated as {end} is set to the number of lines in the
+ buffer. When {end} is before {lnum} an empty |List| is
+ returned.
+
+ This function works only for loaded buffers. For unloaded and
+ non-existing buffers, an empty |List| is returned.
+
+ Example: >vim
+ let lines = getbufline(bufnr("myfile"), 1, "$")
+
+ ]=],
+ name = 'getbufline',
+ params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'end', 'integer' } },
+ signature = 'getbufline({buf}, {lnum} [, {end}])',
+ },
+ getbufoneline = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Just like `getbufline()` but only get one line and return it
+ as a string.
+ ]=],
+ name = 'getbufoneline',
+ params = { { 'buf', 'integer|string' }, { 'lnum', 'integer' } },
+ signature = 'getbufoneline({buf}, {lnum})',
+ returns = 'string',
+ },
+ getbufvar = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is the value of option or local buffer variable
+ {varname} in buffer {buf}. Note that the name without "b:"
+ must be used.
+ The {varname} argument is a string.
+ When {varname} is empty returns a |Dictionary| with all the
+ buffer-local variables.
+ When {varname} is equal to "&" returns a |Dictionary| with all
+ the buffer-local options.
+ Otherwise, when {varname} starts with "&" returns the value of
+ a buffer-local option.
+ This also works for a global or buffer-local option, but it
+ doesn't work for a global variable, window-local variable or
+ window-local option.
+ For the use of {buf}, see |bufname()| above.
+ When the buffer or variable doesn't exist {def} or an empty
+ string is returned, there is no error message.
+ Examples: >vim
+ let bufmodified = getbufvar(1, "&mod")
+ echo "todo myvar = " .. getbufvar("todo", "myvar")
+
+ ]=],
+ name = 'getbufvar',
+ params = { { 'buf', 'any' }, { 'varname', 'string' }, { 'def', 'any' } },
+ signature = 'getbufvar({buf}, {varname} [, {def}])',
+ },
+ getcellwidths = {
+ desc = [=[
+ Returns a |List| of cell widths of character ranges overridden
+ by |setcellwidths()|. The format is equal to the argument of
+ |setcellwidths()|. If no character ranges have their cell
+ widths overridden, an empty List is returned.
+ ]=],
+ name = 'getcellwidths',
+ params = {},
+ signature = 'getcellwidths()',
+ },
+ getchangelist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns the |changelist| for the buffer {buf}. For the use
+ of {buf}, see |bufname()| above. If buffer {buf} doesn't
+ exist, an empty list is returned.
+
+ The returned list contains two entries: a list with the change
+ locations and the current position in the list. Each
+ entry in the change list is a dictionary with the following
+ entries:
+ col column number
+ coladd column offset for 'virtualedit'
+ lnum line number
+ If buffer {buf} is the current buffer, then the current
+ position refers to the position in the list. For other
+ buffers, it is set to the length of the list.
+
+ ]=],
+ name = 'getchangelist',
+ params = { { 'buf', 'integer|string' } },
+ returns = 'table[]',
+ signature = 'getchangelist([{buf}])',
+ },
+ getchar = {
+ args = { 0, 1 },
+ desc = [=[
+ Get a single character from the user or input stream.
+ If [expr] is omitted, wait until a character is available.
+ If [expr] is 0, only get a character when one is available.
+ Return zero otherwise.
+ If [expr] is 1, only check if a character is available, it is
+ not consumed. Return zero if no character available.
+ If you prefer always getting a string use |getcharstr()|.
+
+ Without [expr] and when [expr] is 0 a whole character or
+ special key is returned. If it is a single character, the
+ result is a Number. Use |nr2char()| to convert it to a String.
+ Otherwise a String is returned with the encoded character.
+ For a special key it's a String with a sequence of bytes
+ starting with 0x80 (decimal: 128). This is the same value as
+ the String "\<Key>", e.g., "\<Left>". The returned value is
+ also a String when a modifier (shift, control, alt) was used
+ that is not included in the character.
+
+ When [expr] is 0 and Esc is typed, there will be a short delay
+ while Vim waits to see if this is the start of an escape
+ sequence.
+
+ When [expr] is 1 only the first byte is returned. For a
+ one-byte character it is the character itself as a number.
+ Use nr2char() to convert it to a String.
+
+ Use getcharmod() to obtain any additional modifiers.
+
+ When the user clicks a mouse button, the mouse event will be
+ returned. The position can then be found in |v:mouse_col|,
+ |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|.
+ |getmousepos()| can also be used. Mouse move events will be
+ ignored.
+ This example positions the mouse as it would normally happen: >vim
+ let c = getchar()
+ if c == "\<LeftMouse>" && v:mouse_win > 0
+ exe v:mouse_win .. "wincmd w"
+ exe v:mouse_lnum
+ exe "normal " .. v:mouse_col .. "|"
+ endif
+ <
+ There is no prompt, you will somehow have to make clear to the
+ user that a character has to be typed. The screen is not
+ redrawn, e.g. when resizing the window.
+
+ There is no mapping for the character.
+ Key codes are replaced, thus when the user presses the <Del>
+ key you get the code for the <Del> key, not the raw character
+ sequence. Examples: >vim
+ getchar() == "\<Del>"
+ getchar() == "\<S-Left>"
+ <This example redefines "f" to ignore case: >vim
+ nmap f :call FindChar()<CR>
+ function FindChar()
+ let c = nr2char(getchar())
+ while col('.') < col('$') - 1
+ normal l
+ if getline('.')[col('.') - 1] ==? c
+ break
+ endif
+ endwhile
+ endfunction
+ <
+ ]=],
+ name = 'getchar',
+ params = {},
+ returns = 'integer',
+ signature = 'getchar([expr])',
+ },
+ getcharmod = {
+ desc = [=[
+ The result is a Number which is the state of the modifiers for
+ the last obtained character with getchar() or in another way.
+ These values are added together:
+ 2 shift
+ 4 control
+ 8 alt (meta)
+ 16 meta (when it's different from ALT)
+ 32 mouse double click
+ 64 mouse triple click
+ 96 mouse quadruple click (== 32 + 64)
+ 128 command (Macintosh only)
+ Only the modifiers that have not been included in the
+ character itself are obtained. Thus Shift-a results in "A"
+ without a modifier. Returns 0 if no modifiers are used.
+ ]=],
+ name = 'getcharmod',
+ params = {},
+ returns = 'integer',
+ signature = 'getcharmod()',
+ },
+ getcharpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the position for String {expr}. Same as |getpos()| but the
+ column number in the returned List is a character index
+ instead of a byte index.
+ If |getpos()| returns a very large column number, equal to
+ |v:maxcol|, then getcharpos() will return the character index
+ of the last character.
+
+ Example:
+ With the cursor on '세' in line 5 with text "여보세요": >vim
+ getcharpos('.') returns [0, 5, 3, 0]
+ getpos('.') returns [0, 5, 7, 0]
+ <
+ ]=],
+ name = 'getcharpos',
+ params = { { 'expr', 'any' } },
+ returns = 'integer[]',
+ signature = 'getcharpos({expr})',
+ },
+ getcharsearch = {
+ desc = [=[
+ Return the current character search information as a {dict}
+ with the following entries:
+
+ char character previously used for a character
+ search (|t|, |f|, |T|, or |F|); empty string
+ if no character search has been performed
+ forward direction of character search; 1 for forward,
+ 0 for backward
+ until type of character search; 1 for a |t| or |T|
+ character search, 0 for an |f| or |F|
+ character search
+
+ This can be useful to always have |;| and |,| search
+ forward/backward regardless of the direction of the previous
+ character search: >vim
+ nnoremap <expr> ; getcharsearch().forward ? ';' : ','
+ nnoremap <expr> , getcharsearch().forward ? ',' : ';'
+ <Also see |setcharsearch()|.
+ ]=],
+ name = 'getcharsearch',
+ params = {},
+ returns = 'table[]',
+ signature = 'getcharsearch()',
+ },
+ getcharstr = {
+ args = { 0, 1 },
+ desc = [=[
+ Get a single character from the user or input stream as a
+ string.
+ If [expr] is omitted, wait until a character is available.
+ If [expr] is 0 or false, only get a character when one is
+ available. Return an empty string otherwise.
+ If [expr] is 1 or true, only check if a character is
+ available, it is not consumed. Return an empty string
+ if no character is available.
+ Otherwise this works like |getchar()|, except that a number
+ result is converted to a string.
+ ]=],
+ name = 'getcharstr',
+ params = {},
+ returns = 'string',
+ signature = 'getcharstr([expr])',
+ },
+ getcmdcompltype = {
+ desc = [=[
+ Return the type of the current command-line completion.
+ Only works when the command line is being edited, thus
+ requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
+ See |:command-completion| for the return string.
+ Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
+ |setcmdline()|.
+ Returns an empty string when completion is not defined.
+ ]=],
+ name = 'getcmdcompltype',
+ params = {},
+ returns = 'string',
+ signature = 'getcmdcompltype()',
+ },
+ getcmdline = {
+ desc = [=[
+ Return the current command-line. Only works when the command
+ line is being edited, thus requires use of |c_CTRL-\_e| or
+ |c_CTRL-R_=|.
+ Example: >vim
+ cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR>
+ <Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()| and
+ |setcmdline()|.
+ Returns an empty string when entering a password or using
+ |inputsecret()|.
+ ]=],
+ name = 'getcmdline',
+ params = {},
+ returns = 'string',
+ signature = 'getcmdline()',
+ },
+ getcmdpos = {
+ desc = [=[
+ Return the position of the cursor in the command line as a
+ byte count. The first column is 1.
+ Only works when editing the command line, thus requires use of
+ |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+ Returns 0 otherwise.
+ Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
+ |setcmdline()|.
+ ]=],
+ name = 'getcmdpos',
+ params = {},
+ returns = 'integer',
+ signature = 'getcmdpos()',
+ },
+ getcmdscreenpos = {
+ desc = [=[
+ Return the screen position of the cursor in the command line
+ as a byte count. The first column is 1.
+ Instead of |getcmdpos()|, it adds the prompt position.
+ Only works when editing the command line, thus requires use of
+ |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+ Returns 0 otherwise.
+ Also see |getcmdpos()|, |setcmdpos()|, |getcmdline()| and
+ |setcmdline()|.
+ ]=],
+ name = 'getcmdscreenpos',
+ params = {},
+ signature = 'getcmdscreenpos()',
+ },
+ getcmdtype = {
+ desc = [=[
+ Return the current command-line type. Possible return values
+ are:
+ : normal Ex command
+ > debug mode command |debug-mode|
+ / forward search command
+ ? backward search command
+ @ |input()| command
+ `-` |:insert| or |:append| command
+ = |i_CTRL-R_=|
+ Only works when editing the command line, thus requires use of
+ |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+ Returns an empty string otherwise.
+ Also see |getcmdpos()|, |setcmdpos()| and |getcmdline()|.
+ ]=],
+ name = 'getcmdtype',
+ params = {},
+ returns = "':'|'>'|'/'|'?'|'@'|'-'|'='",
+ signature = 'getcmdtype()',
+ },
+ getcmdwintype = {
+ desc = [=[
+ Return the current |command-line-window| type. Possible return
+ values are the same as |getcmdtype()|. Returns an empty string
+ when not in the command-line window.
+ ]=],
+ name = 'getcmdwintype',
+ params = {},
+ returns = "':'|'>'|'/'|'?'|'@'|'-'|'='",
+ signature = 'getcmdwintype()',
+ },
+ getcompletion = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Return a list of command-line completion matches. The String
+ {type} argument specifies what for. The following completion
+ types are supported:
+
+ arglist file names in argument list
+ augroup autocmd groups
+ buffer buffer names
+ breakpoint |:breakadd| and |:breakdel| suboptions
+ cmdline |cmdline-completion| result
+ color color schemes
+ command Ex command
+ compiler compilers
+ custom,{func} custom completion, defined via {func}
+ customlist,{func} custom completion, defined via {func}
+ diff_buffer |:diffget| and |:diffput| completion
+ dir directory names
+ environment environment variable names
+ event autocommand events
+ expression Vim expression
+ file file and directory names
+ file_in_path file and directory names in |'path'|
+ filetype filetype names |'filetype'|
+ function function name
+ help help subjects
+ highlight highlight groups
+ history |:history| suboptions
+ locale locale names (as output of locale -a)
+ mapclear buffer argument
+ mapping mapping name
+ menu menus
+ messages |:messages| suboptions
+ option options
+ packadd optional package |pack-add| names
+ runtime |:runtime| completion
+ scriptnames sourced script names |:scriptnames|
+ shellcmd Shell command
+ sign |:sign| suboptions
+ syntax syntax file names |'syntax'|
+ syntime |:syntime| suboptions
+ tag tags
+ tag_listfiles tags, file names
+ user user names
+ var user variables
+
+ If {pat} is an empty string, then all the matches are
+ returned. Otherwise only items matching {pat} are returned.
+ See |wildcards| for the use of special characters in {pat}.
+
+ If the optional {filtered} flag is set to 1, then 'wildignore'
+ is applied to filter the results. Otherwise all the matches
+ are returned. The 'wildignorecase' option always applies.
+
+ If the 'wildoptions' option contains "fuzzy", then fuzzy
+ matching is used to get the completion matches. Otherwise
+ regular expression matching is used. Thus this function
+ follows the user preference, what happens on the command line.
+ If you do not want this you can make 'wildoptions' empty
+ before calling getcompletion() and restore it afterwards.
+
+ If {type} is "cmdline", then the |cmdline-completion| result is
+ returned. For example, to complete the possible values after
+ a ":call" command: >vim
+ echo getcompletion('call ', 'cmdline')
+ <
+ If there are no matches, an empty list is returned. An
+ invalid value for {type} produces an error.
+
+ ]=],
+ name = 'getcompletion',
+ params = { { 'pat', 'any' }, { 'type', 'any' }, { 'filtered', 'any' } },
+ returns = 'string[]',
+ signature = 'getcompletion({pat}, {type} [, {filtered}])',
+ },
+ getcurpos = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get the position of the cursor. This is like getpos('.'), but
+ includes an extra "curswant" item in the list:
+ [0, lnum, col, off, curswant] ~
+ The "curswant" number is the preferred column when moving the
+ cursor vertically. After |$| command it will be a very large
+ number equal to |v:maxcol|. Also see |getcursorcharpos()| and
+ |getpos()|.
+ The first "bufnum" item is always zero. The byte position of
+ the cursor is returned in "col". To get the character
+ position, use |getcursorcharpos()|.
+
+ The optional {winid} argument can specify the window. It can
+ be the window number or the |window-ID|. The last known
+ cursor position is returned, this may be invalid for the
+ current value of the buffer if it is not the current window.
+ If {winid} is invalid a list with zeroes is returned.
+
+ This can be used to save and restore the cursor position: >vim
+ let save_cursor = getcurpos()
+ MoveTheCursorAround
+ call setpos('.', save_cursor)
+ <Note that this only works within the window. See
+ |winrestview()| for restoring more state.
+
+ ]=],
+ name = 'getcurpos',
+ params = { { 'winid', 'integer' } },
+ signature = 'getcurpos([{winid}])',
+ },
+ getcursorcharpos = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Same as |getcurpos()| but the column number in the returned
+ List is a character index instead of a byte index.
+
+ Example:
+ With the cursor on '보' in line 3 with text "여보세요": >vim
+ getcursorcharpos() " returns [0, 3, 2, 0, 3]
+ getcurpos() " returns [0, 3, 4, 0, 3]
+ <
+ ]=],
+ name = 'getcursorcharpos',
+ params = { { 'winid', 'integer' } },
+ signature = 'getcursorcharpos([{winid}])',
+ },
+ getcwd = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ With no arguments, returns the name of the effective
+ |current-directory|. With {winnr} or {tabnr} the working
+ directory of that scope is returned, and 'autochdir' is
+ ignored.
+ Tabs and windows are identified by their respective numbers,
+ 0 means current tab or window. Missing tab number implies 0.
+ Thus the following are equivalent: >vim
+ getcwd(0)
+ getcwd(0, 0)
+ <If {winnr} is -1 it is ignored, only the tab is resolved.
+ {winnr} can be the window number or the |window-ID|.
+ If both {winnr} and {tabnr} are -1 the global working
+ directory is returned.
+ Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
+
+ ]=],
+ name = 'getcwd',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ returns = 'string',
+ signature = 'getcwd([{winnr} [, {tabnr}]])',
+ },
+ getenv = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the value of environment variable {name}. The {name}
+ argument is a string, without a leading '$'. Example: >vim
+ myHome = getenv('HOME')
+
+ <When the variable does not exist |v:null| is returned. That
+ is different from a variable set to an empty string.
+ See also |expr-env|.
+
+ ]=],
+ name = 'getenv',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'getenv({name})',
+ },
+ getfontname = {
+ args = { 0, 1 },
+ desc = [=[
+ Without an argument returns the name of the normal font being
+ used. Like what is used for the Normal highlight group
+ |hl-Normal|.
+ With an argument a check is done whether String {name} is a
+ valid font name. If not then an empty string is returned.
+ Otherwise the actual font name is returned, or {name} if the
+ GUI does not support obtaining the real name.
+ Only works when the GUI is running, thus not in your vimrc or
+ gvimrc file. Use the |GUIEnter| autocommand to use this
+ function just after the GUI has started.
+ ]=],
+ name = 'getfontname',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'getfontname([{name}])',
+ },
+ getfperm = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a String, which is the read, write, and execute
+ permissions of the given file {fname}.
+ If {fname} does not exist or its directory cannot be read, an
+ empty string is returned.
+ The result is of the form "rwxrwxrwx", where each group of
+ "rwx" flags represent, in turn, the permissions of the owner
+ of the file, the group the file belongs to, and other users.
+ If a user does not have a given permission the flag for this
+ is replaced with the string "-". Examples: >vim
+ echo getfperm("/etc/passwd")
+ echo getfperm(expand("~/.config/nvim/init.vim"))
+ <This will hopefully (from a security point of view) display
+ the string "rw-r--r--" or even "rw-------".
+
+ For setting permissions use |setfperm()|.
+ ]=],
+ fast = true,
+ name = 'getfperm',
+ params = { { 'fname', 'string' } },
+ returns = 'string',
+ signature = 'getfperm({fname})',
+ },
+ getfsize = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the size in bytes of the
+ given file {fname}.
+ If {fname} is a directory, 0 is returned.
+ If the file {fname} can't be found, -1 is returned.
+ If the size of {fname} is too big to fit in a Number then -2
+ is returned.
+
+ ]=],
+ fast = true,
+ name = 'getfsize',
+ params = { { 'fname', 'string' } },
+ returns = 'integer',
+ signature = 'getfsize({fname})',
+ },
+ getftime = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the last modification time of
+ the given file {fname}. The value is measured as seconds
+ since 1st Jan 1970, and may be passed to strftime(). See also
+ |localtime()| and |strftime()|.
+ If the file {fname} can't be found -1 is returned.
+
+ ]=],
+ fast = true,
+ name = 'getftime',
+ params = { { 'fname', 'string' } },
+ returns = 'integer',
+ signature = 'getftime({fname})',
+ },
+ getftype = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a String, which is a description of the kind of
+ file of the given file {fname}.
+ If {fname} does not exist an empty string is returned.
+ Here is a table over different kinds of files and their
+ results:
+ Normal file "file"
+ Directory "dir"
+ Symbolic link "link"
+ Block device "bdev"
+ Character device "cdev"
+ Socket "socket"
+ FIFO "fifo"
+ All other "other"
+ Example: >vim
+ getftype("/home")
+ <Note that a type such as "link" will only be returned on
+ systems that support it. On some systems only "dir" and
+ "file" are returned.
+
+ ]=],
+ fast = true,
+ name = 'getftype',
+ params = { { 'fname', 'string' } },
+ returns = "'file'|'dir'|'link'|'bdev'|'cdev'|'socket'|'fifo'|'other'",
+ signature = 'getftype({fname})',
+ },
+ getjumplist = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Returns the |jumplist| for the specified window.
+
+ Without arguments use the current window.
+ With {winnr} only use this window in the current tab page.
+ {winnr} can also be a |window-ID|.
+ With {winnr} and {tabnr} use the window in the specified tab
+ page. If {winnr} or {tabnr} is invalid, an empty list is
+ returned.
+
+ The returned list contains two entries: a list with the jump
+ locations and the last used jump position number in the list.
+ Each entry in the jump location list is a dictionary with
+ the following entries:
+ bufnr buffer number
+ col column number
+ coladd column offset for 'virtualedit'
+ filename filename if available
+ lnum line number
+
+ ]=],
+ name = 'getjumplist',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ signature = 'getjumplist([{winnr} [, {tabnr}]])',
+ returns = 'vim.fn.getjumplist.ret',
+ },
+ getline = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Without {end} the result is a String, which is line {lnum}
+ from the current buffer. Example: >vim
+ getline(1)
+ <When {lnum} is a String that doesn't start with a
+ digit, |line()| is called to translate the String into a Number.
+ To get the line under the cursor: >vim
+ getline(".")
+ <When {lnum} is a number smaller than 1 or bigger than the
+ number of lines in the buffer, an empty string is returned.
+
+ When {end} is given the result is a |List| where each item is
+ a line from the current buffer in the range {lnum} to {end},
+ including line {end}.
+ {end} is used in the same way as {lnum}.
+ Non-existing lines are silently omitted.
+ When {end} is before {lnum} an empty |List| is returned.
+ Example: >vim
+ let start = line('.')
+ let end = search("^$") - 1
+ let lines = getline(start, end)
+
+ <To get lines from another buffer see |getbufline()| and
+ |getbufoneline()|
+ ]=],
+ name = 'getline',
+ params = { { 'lnum', 'integer' }, { 'end', 'any' } },
+ returns = 'string|string[]',
+ signature = 'getline({lnum} [, {end}])',
+ },
+ getloclist = {
+ args = { 1, 2 },
+ desc = [=[
+ Returns a |List| with all the entries in the location list for
+ window {nr}. {nr} can be the window number or the |window-ID|.
+ When {nr} is zero the current window is used.
+
+ For a location list window, the displayed location list is
+ returned. For an invalid window number {nr}, an empty list is
+ returned. Otherwise, same as |getqflist()|.
+
+ If the optional {what} dictionary argument is supplied, then
+ returns the items listed in {what} as a dictionary. Refer to
+ |getqflist()| for the supported items in {what}.
+
+ In addition to the items supported by |getqflist()| in {what},
+ the following item is supported by |getloclist()|:
+
+ filewinid id of the window used to display files
+ from the location list. This field is
+ applicable only when called from a
+ location list window. See
+ |location-list-file-window| for more
+ details.
+
+ Returns a |Dictionary| with default values if there is no
+ location list for the window {nr}.
+ Returns an empty Dictionary if window {nr} does not exist.
+
+ Examples (See also |getqflist-examples|): >vim
+ echo getloclist(3, {'all': 0})
+ echo getloclist(5, {'filewinid': 0})
+ <
+ ]=],
+ name = 'getloclist',
+ params = { { 'nr', 'integer' }, { 'what', 'any' } },
+ signature = 'getloclist({nr} [, {what}])',
+ },
+ getmarklist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Without the {buf} argument returns a |List| with information
+ about all the global marks. |mark|
+
+ If the optional {buf} argument is specified, returns the
+ local marks defined in buffer {buf}. For the use of {buf},
+ see |bufname()|. If {buf} is invalid, an empty list is
+ returned.
+
+ Each item in the returned List is a |Dict| with the following:
+ mark name of the mark prefixed by "'"
+ pos a |List| with the position of the mark:
+ [bufnum, lnum, col, off]
+ Refer to |getpos()| for more information.
+ file file name
+
+ Refer to |getpos()| for getting information about a specific
+ mark.
+
+ ]=],
+ name = 'getmarklist',
+ params = { { 'buf', 'any' } },
+ signature = 'getmarklist([{buf}])',
+ },
+ getmatches = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |List| with all matches previously defined for the
+ current window by |matchadd()| and the |:match| commands.
+ |getmatches()| is useful in combination with |setmatches()|,
+ as |setmatches()| can restore a list of matches saved by
+ |getmatches()|.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window. If {win} is invalid,
+ an empty list is returned.
+ Example: >vim
+ echo getmatches()
+ < >
+ [{"group": "MyGroup1", "pattern": "TODO",
+ "priority": 10, "id": 1}, {"group": "MyGroup2",
+ "pattern": "FIXME", "priority": 10, "id": 2}]
+ < >vim
+ let m = getmatches()
+ call clearmatches()
+ echo getmatches()
+ < >
+ []
+ < >vim
+ call setmatches(m)
+ echo getmatches()
+ < >
+ [{"group": "MyGroup1", "pattern": "TODO",
+ "priority": 10, "id": 1}, {"group": "MyGroup2",
+ "pattern": "FIXME", "priority": 10, "id": 2}]
+ < >vim
+ unlet m
+ <
+ ]=],
+ name = 'getmatches',
+ params = { { 'win', 'any' } },
+ signature = 'getmatches([{win}])',
+ },
+ getmousepos = {
+ desc = [=[
+ Returns a |Dictionary| with the last known position of the
+ mouse. This can be used in a mapping for a mouse click. The
+ items are:
+ screenrow screen row
+ screencol screen column
+ winid Window ID of the click
+ winrow row inside "winid"
+ wincol column inside "winid"
+ line text line inside "winid"
+ column text column inside "winid"
+ coladd offset (in screen columns) from the
+ start of the clicked char
+ All numbers are 1-based.
+
+ If not over a window, e.g. when in the command line, then only
+ "screenrow" and "screencol" are valid, the others are zero.
+
+ When on the status line below a window or the vertical
+ separator right of a window, the "line" and "column" values
+ are zero.
+
+ When the position is after the text then "column" is the
+ length of the text in bytes plus one.
+
+ If the mouse is over a focusable floating window then that
+ window is used.
+
+ When using |getchar()| the Vim variables |v:mouse_lnum|,
+ |v:mouse_col| and |v:mouse_winid| also provide these values.
+ ]=],
+ name = 'getmousepos',
+ params = {},
+ signature = 'getmousepos()',
+ returns = 'vim.fn.getmousepos.ret',
+ },
+ getpid = {
+ desc = [=[
+ Return a Number which is the process ID of the Vim process.
+ This is a unique number, until Vim exits.
+ ]=],
+ fast = true,
+ name = 'getpid',
+ params = {},
+ returns = 'integer',
+ signature = 'getpid()',
+ },
+ getpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the position for String {expr}. For possible values of
+ {expr} see |line()|. For getting the cursor position see
+ |getcurpos()|.
+ The result is a |List| with four numbers:
+ [bufnum, lnum, col, off]
+ "bufnum" is zero, unless a mark like '0 or 'A is used, then it
+ is the buffer number of the mark.
+ "lnum" and "col" are the position in the buffer. The first
+ column is 1.
+ The "off" number is zero, unless 'virtualedit' is used. Then
+ it is the offset in screen columns from the start of the
+ character. E.g., a position within a <Tab> or after the last
+ character.
+ Note that for '< and '> Visual mode matters: when it is "V"
+ (visual line mode) the column of '< is zero and the column of
+ '> is a large number equal to |v:maxcol|.
+ The column number in the returned List is the byte position
+ within the line. To get the character position in the line,
+ use |getcharpos()|.
+ A very large column number equal to |v:maxcol| can be returned,
+ in which case it means "after the end of the line".
+ If {expr} is invalid, returns a list with all zeros.
+ This can be used to save and restore the position of a mark: >vim
+ let save_a_mark = getpos("'a")
+ " ...
+ call setpos("'a", save_a_mark)
+ <Also see |getcharpos()|, |getcurpos()| and |setpos()|.
+
+ ]=],
+ name = 'getpos',
+ params = { { 'expr', 'string' } },
+ returns = 'integer[]',
+ signature = 'getpos({expr})',
+ },
+ getqflist = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |List| with all the current quickfix errors. Each
+ list item is a dictionary with these entries:
+ bufnr number of buffer that has the file name, use
+ bufname() to get the name
+ module module name
+ lnum line number in the buffer (first line is 1)
+ end_lnum
+ end of line number if the item is multiline
+ col column number (first column is 1)
+ end_col end of column number if the item has range
+ vcol |TRUE|: "col" is visual column
+ |FALSE|: "col" is byte index
+ nr error number
+ pattern search pattern used to locate the error
+ text description of the error
+ type type of the error, 'E', '1', etc.
+ valid |TRUE|: recognized error message
+ user_data
+ custom data associated with the item, can be
+ any type.
+
+ When there is no error list or it's empty, an empty list is
+ returned. Quickfix list entries with a non-existing buffer
+ number are returned with "bufnr" set to zero (Note: some
+ functions accept buffer number zero for the alternate buffer,
+ you may need to explicitly check for zero).
+
+ Useful application: Find pattern matches in multiple files and
+ do something with them: >vim
+ vimgrep /theword/jg *.c
+ for d in getqflist()
+ echo bufname(d.bufnr) ':' d.lnum '=' d.text
+ endfor
+ <
+ If the optional {what} dictionary argument is supplied, then
+ returns only the items listed in {what} as a dictionary. The
+ following string items are supported in {what}:
+ changedtick get the total number of changes made
+ to the list |quickfix-changedtick|
+ context get the |quickfix-context|
+ efm errorformat to use when parsing "lines". If
+ not present, then the 'errorformat' option
+ value is used.
+ id get information for the quickfix list with
+ |quickfix-ID|; zero means the id for the
+ current list or the list specified by "nr"
+ idx get information for the quickfix entry at this
+ index in the list specified by "id" or "nr".
+ If set to zero, then uses the current entry.
+ See |quickfix-index|
+ items quickfix list entries
+ lines parse a list of lines using 'efm' and return
+ the resulting entries. Only a |List| type is
+ accepted. The current quickfix list is not
+ modified. See |quickfix-parse|.
+ nr get information for this quickfix list; zero
+ means the current quickfix list and "$" means
+ the last quickfix list
+ qfbufnr number of the buffer displayed in the quickfix
+ window. Returns 0 if the quickfix buffer is
+ not present. See |quickfix-buffer|.
+ size number of entries in the quickfix list
+ title get the list title |quickfix-title|
+ winid get the quickfix |window-ID|
+ all all of the above quickfix properties
+ Non-string items in {what} are ignored. To get the value of a
+ particular item, set it to zero.
+ If "nr" is not present then the current quickfix list is used.
+ If both "nr" and a non-zero "id" are specified, then the list
+ specified by "id" is used.
+ To get the number of lists in the quickfix stack, set "nr" to
+ "$" in {what}. The "nr" value in the returned dictionary
+ contains the quickfix stack size.
+ When "lines" is specified, all the other items except "efm"
+ are ignored. The returned dictionary contains the entry
+ "items" with the list of entries.
+
+ The returned dictionary contains the following entries:
+ changedtick total number of changes made to the
+ list |quickfix-changedtick|
+ context quickfix list context. See |quickfix-context|
+ If not present, set to "".
+ id quickfix list ID |quickfix-ID|. If not
+ present, set to 0.
+ idx index of the quickfix entry in the list. If not
+ present, set to 0.
+ items quickfix list entries. If not present, set to
+ an empty list.
+ nr quickfix list number. If not present, set to 0
+ qfbufnr number of the buffer displayed in the quickfix
+ window. If not present, set to 0.
+ size number of entries in the quickfix list. If not
+ present, set to 0.
+ title quickfix list title text. If not present, set
+ to "".
+ winid quickfix |window-ID|. If not present, set to 0
+
+ Examples (See also |getqflist-examples|): >vim
+ echo getqflist({'all': 1})
+ echo getqflist({'nr': 2, 'title': 1})
+ echo getqflist({'lines' : ["F1:10:L10"]})
+ <
+ ]=],
+ name = 'getqflist',
+ params = { { 'what', 'any' } },
+ signature = 'getqflist([{what}])',
+ },
+ getreg = {
+ args = { 0, 3 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is the contents of register
+ {regname}. Example: >vim
+ let cliptext = getreg('*')
+ <When register {regname} was not set the result is an empty
+ string.
+ The {regname} argument must be a string.
+
+ getreg('=') returns the last evaluated value of the expression
+ register. (For use in maps.)
+ getreg('=', 1) returns the expression itself, so that it can
+ be restored with |setreg()|. For other registers the extra
+ argument is ignored, thus you can always give it.
+
+ If {list} is present and |TRUE|, the result type is changed
+ to |List|. Each list item is one text line. Use it if you care
+ about zero bytes possibly present inside register: without
+ third argument both NLs and zero bytes are represented as NLs
+ (see |NL-used-for-Nul|).
+ When the register was not set an empty list is returned.
+
+ If {regname} is not specified, |v:register| is used.
+
+ ]=],
+ name = 'getreg',
+ params = { { 'regname', 'string' }, { 'list', 'any' } },
+ returns = 'string|string[]',
+ signature = 'getreg([{regname} [, 1 [, {list}]]])',
+ },
+ getreginfo = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns detailed information about register {regname} as a
+ Dictionary with the following entries:
+ regcontents List of lines contained in register
+ {regname}, like
+ getreg({regname}, 1, 1).
+ regtype the type of register {regname}, as in
+ |getregtype()|.
+ isunnamed Boolean flag, v:true if this register
+ is currently pointed to by the unnamed
+ register.
+ points_to for the unnamed register, gives the
+ single letter name of the register
+ currently pointed to (see |quotequote|).
+ For example, after deleting a line
+ with `dd`, this field will be "1",
+ which is the register that got the
+ deleted text.
+
+ The {regname} argument is a string. If {regname} is invalid
+ or not set, an empty Dictionary will be returned.
+ If {regname} is not specified, |v:register| is used.
+ The returned Dictionary can be passed to |setreg()|.
+
+ ]=],
+ name = 'getreginfo',
+ params = { { 'regname', 'string' } },
+ returns = 'table',
+ signature = 'getreginfo([{regname}])',
+ },
+ getregtype = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is type of register {regname}.
+ The value will be one of:
+ "v" for |charwise| text
+ "V" for |linewise| text
+ "<CTRL-V>{width}" for |blockwise-visual| text
+ "" for an empty or unknown register
+ <CTRL-V> is one character with value 0x16.
+ The {regname} argument is a string. If {regname} is not
+ specified, |v:register| is used.
+
+ ]=],
+ name = 'getregtype',
+ params = { { 'regname', 'string' } },
+ returns = 'string',
+ signature = 'getregtype([{regname}])',
+ },
+ getscriptinfo = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |List| with information about all the sourced Vim
+ scripts in the order they were sourced, like what
+ `:scriptnames` shows.
+
+ The optional Dict argument {opts} supports the following
+ optional items:
+ name Script name match pattern. If specified,
+ and "sid" is not specified, information about
+ scripts with a name that match the pattern
+ "name" are returned.
+ sid Script ID |<SID>|. If specified, only
+ information about the script with ID "sid" is
+ returned and "name" is ignored.
+
+ Each item in the returned List is a |Dict| with the following
+ items:
+ autoload Always set to FALSE.
+ functions List of script-local function names defined in
+ the script. Present only when a particular
+ script is specified using the "sid" item in
+ {opts}.
+ name Vim script file name.
+ sid Script ID |<SID>|.
+ variables A dictionary with the script-local variables.
+ Present only when a particular script is
+ specified using the "sid" item in {opts}.
+ Note that this is a copy, the value of
+ script-local variables cannot be changed using
+ this dictionary.
+ version Vim script version, always 1
+
+ Examples: >vim
+ echo getscriptinfo({'name': 'myscript'})
+ echo getscriptinfo({'sid': 15}).variables
+ <
+ ]=],
+ name = 'getscriptinfo',
+ params = { { 'opts', 'table' } },
+ signature = 'getscriptinfo([{opts}])',
+ },
+ gettabinfo = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ If {tabnr} is not specified, then information about all the
+ tab pages is returned as a |List|. Each List item is a
+ |Dictionary|. Otherwise, {tabnr} specifies the tab page
+ number and information about that one is returned. If the tab
+ page does not exist an empty List is returned.
+
+ Each List item is a |Dictionary| with the following entries:
+ tabnr tab page number.
+ variables a reference to the dictionary with
+ tabpage-local variables
+ windows List of |window-ID|s in the tab page.
+
+ ]=],
+ name = 'gettabinfo',
+ params = { { 'tabnr', 'integer' } },
+ signature = 'gettabinfo([{tabnr}])',
+ },
+ gettabvar = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get the value of a tab-local variable {varname} in tab page
+ {tabnr}. |t:var|
+ Tabs are numbered starting with one.
+ The {varname} argument is a string. When {varname} is empty a
+ dictionary with all tab-local variables is returned.
+ Note that the name without "t:" must be used.
+ When the tab or variable doesn't exist {def} or an empty
+ string is returned, there is no error message.
+
+ ]=],
+ name = 'gettabvar',
+ params = { { 'tabnr', 'integer' }, { 'varname', 'string' }, { 'def', 'any' } },
+ signature = 'gettabvar({tabnr}, {varname} [, {def}])',
+ },
+ gettabwinvar = {
+ args = { 3, 4 },
+ base = 1,
+ desc = [=[
+ Get the value of window-local variable {varname} in window
+ {winnr} in tab page {tabnr}.
+ The {varname} argument is a string. When {varname} is empty a
+ dictionary with all window-local variables is returned.
+ When {varname} is equal to "&" get the values of all
+ window-local options in a |Dictionary|.
+ Otherwise, when {varname} starts with "&" get the value of a
+ window-local option.
+ Note that {varname} must be the name without "w:".
+ Tabs are numbered starting with one. For the current tabpage
+ use |getwinvar()|.
+ {winnr} can be the window number or the |window-ID|.
+ When {winnr} is zero the current window is used.
+ This also works for a global option, buffer-local option and
+ window-local option, but it doesn't work for a global variable
+ or buffer-local variable.
+ When the tab, window or variable doesn't exist {def} or an
+ empty string is returned, there is no error message.
+ Examples: >vim
+ let list_is_on = gettabwinvar(1, 2, '&list')
+ echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
+ <
+ To obtain all window-local variables use: >vim
+ gettabwinvar({tabnr}, {winnr}, '&')
+ <
+ ]=],
+ name = 'gettabwinvar',
+ params = {
+ { 'tabnr', 'integer' },
+ { 'winnr', 'integer' },
+ { 'varname', 'string' },
+ { 'def', 'any' },
+ },
+ signature = 'gettabwinvar({tabnr}, {winnr}, {varname} [, {def}])',
+ },
+ gettagstack = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a Dict, which is the tag stack of window {winnr}.
+ {winnr} can be the window number or the |window-ID|.
+ When {winnr} is not specified, the current window is used.
+ When window {winnr} doesn't exist, an empty Dict is returned.
+
+ The returned dictionary contains the following entries:
+ curidx Current index in the stack. When at
+ top of the stack, set to (length + 1).
+ Index of bottom of the stack is 1.
+ items List of items in the stack. Each item
+ is a dictionary containing the
+ entries described below.
+ length Number of entries in the stack.
+
+ Each item in the stack is a dictionary with the following
+ entries:
+ bufnr buffer number of the current jump
+ from cursor position before the tag jump.
+ See |getpos()| for the format of the
+ returned list.
+ matchnr current matching tag number. Used when
+ multiple matching tags are found for a
+ name.
+ tagname name of the tag
+
+ See |tagstack| for more information about the tag stack.
+
+ ]=],
+ name = 'gettagstack',
+ params = { { 'winnr', 'integer' } },
+ signature = 'gettagstack([{winnr}])',
+ },
+ gettext = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Translate String {text} if possible.
+ This is mainly for use in the distributed Vim scripts. When
+ generating message translations the {text} is extracted by
+ xgettext, the translator can add the translated message in the
+ .po file and Vim will lookup the translation when gettext() is
+ called.
+ For {text} double quoted strings are preferred, because
+ xgettext does not understand escaping in single quoted
+ strings.
+ ]=],
+ name = 'gettext',
+ params = { { 'text', 'any' } },
+ signature = 'gettext({text})',
+ },
+ getwininfo = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns information about windows as a |List| with Dictionaries.
+
+ If {winid} is given Information about the window with that ID
+ is returned, as a |List| with one item. If the window does not
+ exist the result is an empty list.
+
+ Without {winid} information about all the windows in all the
+ tab pages is returned.
+
+ Each List item is a |Dictionary| with the following entries:
+ botline last complete displayed buffer line
+ bufnr number of buffer in the window
+ height window height (excluding winbar)
+ loclist 1 if showing a location list
+ quickfix 1 if quickfix or location list window
+ terminal 1 if a terminal window
+ tabnr tab page number
+ topline first displayed buffer line
+ variables a reference to the dictionary with
+ window-local variables
+ width window width
+ winbar 1 if the window has a toolbar, 0
+ otherwise
+ wincol leftmost screen column of the window;
+ "col" from |win_screenpos()|
+ textoff number of columns occupied by any
+ 'foldcolumn', 'signcolumn' and line
+ number in front of the text
+ winid |window-ID|
+ winnr window number
+ winrow topmost screen line of the window;
+ "row" from |win_screenpos()|
+
+ ]=],
+ name = 'getwininfo',
+ params = { { 'winid', 'integer' } },
+ signature = 'getwininfo([{winid}])',
+ returns = 'vim.fn.getwininfo.ret.item[]'
+ },
+ getwinpos = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a |List| with two numbers, the result of
+ |getwinposx()| and |getwinposy()| combined:
+ [x-pos, y-pos]
+ {timeout} can be used to specify how long to wait in msec for
+ a response from the terminal. When omitted 100 msec is used.
+
+ Use a longer time for a remote terminal.
+ When using a value less than 10 and no response is received
+ within that time, a previously reported position is returned,
+ if available. This can be used to poll for the position and
+ do some work in the meantime: >vim
+ while 1
+ let res = getwinpos(1)
+ if res[0] >= 0
+ break
+ endif
+ " Do some work here
+ endwhile
+ <
+ ]=],
+ name = 'getwinpos',
+ params = { { 'timeout', 'integer' } },
+ signature = 'getwinpos([{timeout}])',
+ },
+ getwinposx = {
+ desc = [=[
+ The result is a Number, which is the X coordinate in pixels of
+ the left hand side of the GUI Vim window. The result will be
+ -1 if the information is not available.
+ The value can be used with `:winpos`.
+ ]=],
+ name = 'getwinposx',
+ params = {},
+ returns = 'integer',
+ signature = 'getwinposx()',
+ },
+ getwinposy = {
+ desc = [=[
+ The result is a Number, which is the Y coordinate in pixels of
+ the top of the GUI Vim window. The result will be -1 if the
+ information is not available.
+ The value can be used with `:winpos`.
+ ]=],
+ name = 'getwinposy',
+ params = {},
+ returns = 'integer',
+ signature = 'getwinposy()',
+ },
+ getwinvar = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Like |gettabwinvar()| for the current tabpage.
+ Examples: >vim
+ let list_is_on = getwinvar(2, '&list')
+ echo "myvar = " .. getwinvar(1, 'myvar')
+
+ ]=],
+ name = 'getwinvar',
+ params = { { 'winnr', 'integer' }, { 'varname', 'string' }, { 'def', 'any' } },
+ signature = 'getwinvar({winnr}, {varname} [, {def}])',
+ },
+ glob = {
+ args = { 1, 4 },
+ base = 1,
+ desc = [=[
+ Expand the file wildcards in {expr}. See |wildcards| for the
+ use of special characters.
+
+ Unless the optional {nosuf} argument is given and is |TRUE|,
+ the 'suffixes' and 'wildignore' options apply: Names matching
+ one of the patterns in 'wildignore' will be skipped and
+ 'suffixes' affect the ordering of matches.
+ 'wildignorecase' always applies.
+
+ When {list} is present and it is |TRUE| the result is a |List|
+ with all matching files. The advantage of using a List is,
+ you also get filenames containing newlines correctly.
+ Otherwise the result is a String and when there are several
+ matches, they are separated by <NL> characters.
+
+ If the expansion fails, the result is an empty String or List.
+
+ You can also use |readdir()| if you need to do complicated
+ things, such as limiting the number of matches.
+
+ A name for a non-existing file is not included. A symbolic
+ link is only included if it points to an existing file.
+ However, when the {alllinks} argument is present and it is
+ |TRUE| then all symbolic links are included.
+
+ For most systems backticks can be used to get files names from
+ any external command. Example: >vim
+ let tagfiles = glob("`find . -name tags -print`")
+ let &tags = substitute(tagfiles, "\n", ",", "g")
+ <The result of the program inside the backticks should be one
+ item per line. Spaces inside an item are allowed.
+
+ See |expand()| for expanding special Vim variables. See
+ |system()| for getting the raw output of an external command.
+
+ ]=],
+ name = 'glob',
+ params = { { 'expr', 'any' }, { 'nosuf', 'boolean' }, { 'list', 'any' }, { 'alllinks', 'any' } },
+ signature = 'glob({expr} [, {nosuf} [, {list} [, {alllinks}]]])',
+ },
+ glob2regpat = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert a file pattern, as used by glob(), into a search
+ pattern. The result can be used to match with a string that
+ is a file name. E.g. >vim
+ if filename =~ glob2regpat('Make*.mak')
+ " ...
+ endif
+ <This is equivalent to: >vim
+ if filename =~ '^Make.*\.mak$'
+ " ...
+ endif
+ <When {string} is an empty string the result is "^$", match an
+ empty string.
+ Note that the result depends on the system. On MS-Windows
+ a backslash usually means a path separator.
+
+ ]=],
+ name = 'glob2regpat',
+ params = { { 'string', 'string' } },
+ signature = 'glob2regpat({string})',
+ },
+ globpath = {
+ args = { 2, 5 },
+ base = 2,
+ desc = [=[
+ Perform glob() for String {expr} on all directories in {path}
+ and concatenate the results. Example: >vim
+ echo globpath(&rtp, "syntax/c.vim")
+ <
+ {path} is a comma-separated list of directory names. Each
+ directory name is prepended to {expr} and expanded like with
+ |glob()|. A path separator is inserted when needed.
+ To add a comma inside a directory name escape it with a
+ backslash. Note that on MS-Windows a directory may have a
+ trailing backslash, remove it if you put a comma after it.
+ If the expansion fails for one of the directories, there is no
+ error message.
+
+ Unless the optional {nosuf} argument is given and is |TRUE|,
+ the 'suffixes' and 'wildignore' options apply: Names matching
+ one of the patterns in 'wildignore' will be skipped and
+ 'suffixes' affect the ordering of matches.
+
+ When {list} is present and it is |TRUE| the result is a |List|
+ with all matching files. The advantage of using a List is, you
+ also get filenames containing newlines correctly. Otherwise
+ the result is a String and when there are several matches,
+ they are separated by <NL> characters. Example: >vim
+ echo globpath(&rtp, "syntax/c.vim", 0, 1)
+ <
+ {allinks} is used as with |glob()|.
+
+ The "**" item can be used to search in a directory tree.
+ For example, to find all "README.txt" files in the directories
+ in 'runtimepath' and below: >vim
+ echo globpath(&rtp, "**/README.txt")
+ <Upwards search and limiting the depth of "**" is not
+ supported, thus using 'path' will not always work properly.
+
+ ]=],
+ name = 'globpath',
+ params = {
+ { 'path', 'string' },
+ { 'expr', 'any' },
+ { 'nosuf', 'boolean' },
+ { 'list', 'any' },
+ { 'allinks', 'any' },
+ },
+ signature = 'globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]])',
+ },
+ has = {
+ args = 1,
+ desc = [=[
+ Returns 1 if {feature} is supported, 0 otherwise. The
+ {feature} argument is a feature name like "nvim-0.2.1" or
+ "win32", see below. See also |exists()|.
+
+ To get the system name use |vim.uv|.os_uname() in Lua: >lua
+ print(vim.uv.os_uname().sysname)
+
+ <If the code has a syntax error then Vimscript may skip the
+ rest of the line. Put |:if| and |:endif| on separate lines to
+ avoid the syntax error: >vim
+ if has('feature')
+ let x = this_breaks_without_the_feature()
+ endif
+ <
+ Vim's compile-time feature-names (prefixed with "+") are not
+ recognized because Nvim is always compiled with all possible
+ features. |feature-compile|
+
+ Feature names can be:
+ 1. Nvim version. For example the "nvim-0.2.1" feature means
+ that Nvim is version 0.2.1 or later: >vim
+ if has("nvim-0.2.1")
+ " ...
+ endif
+
+ <2. Runtime condition or other pseudo-feature. For example the
+ "win32" feature checks if the current system is Windows: >vim
+ if has("win32")
+ " ...
+ endif
+ < *feature-list*
+ List of supported pseudo-feature names:
+ acl |ACL| support.
+ bsd BSD system (not macOS, use "mac" for that).
+ clipboard |clipboard| provider is available.
+ fname_case Case in file names matters (for Darwin and MS-Windows
+ this is not present).
+ gui_running Nvim has a GUI.
+ iconv Can use |iconv()| for conversion.
+ linux Linux system.
+ mac MacOS system.
+ nvim This is Nvim.
+ python3 Legacy Vim |python3| interface. |has-python|
+ pythonx Legacy Vim |python_x| interface. |has-pythonx|
+ sun SunOS system.
+ ttyin input is a terminal (tty).
+ ttyout output is a terminal (tty).
+ unix Unix system.
+ *vim_starting* True during |startup|.
+ win32 Windows system (32 or 64 bit).
+ win64 Windows system (64 bit).
+ wsl WSL (Windows Subsystem for Linux) system.
+
+ *has-patch*
+ 3. Vim patch. For example the "patch123" feature means that
+ Vim patch 123 at the current |v:version| was included: >vim
+ if v:version > 602 || v:version == 602 && has("patch148")
+ " ...
+ endif
+
+ <4. Vim version. For example the "patch-7.4.237" feature means
+ that Nvim is Vim-compatible to version 7.4.237 or later. >vim
+ if has("patch-7.4.237")
+ " ...
+ endif
+ <
+ ]=],
+ name = 'has',
+ params = { { 'feature', 'any' } },
+ returns = '0|1',
+ signature = 'has({feature})',
+ },
+ has_key = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is TRUE if |Dictionary| {dict}
+ has an entry with key {key}. FALSE otherwise. The {key}
+ argument is a string.
+
+ ]=],
+ name = 'has_key',
+ params = { { 'dict', 'any' }, { 'key', 'any' } },
+ returns = '0|1',
+ signature = 'has_key({dict}, {key})',
+ },
+ haslocaldir = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is 1 when the window has set a
+ local path via |:lcd| or when {winnr} is -1 and the tabpage
+ has set a local path via |:tcd|, otherwise 0.
+
+ Tabs and windows are identified by their respective numbers,
+ 0 means current tab or window. Missing argument implies 0.
+ Thus the following are equivalent: >vim
+ echo haslocaldir()
+ echo haslocaldir(0)
+ echo haslocaldir(0, 0)
+ <With {winnr} use that window in the current tabpage.
+ With {winnr} and {tabnr} use the window in that tabpage.
+ {winnr} can be the window number or the |window-ID|.
+ If {winnr} is -1 it is ignored, only the tab is resolved.
+ Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
+
+ ]=],
+ name = 'haslocaldir',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ returns = '0|1',
+ signature = 'haslocaldir([{winnr} [, {tabnr}]])',
+ },
+ hasmapto = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is TRUE if there is a mapping
+ that contains {what} in somewhere in the rhs (what it is
+ mapped to) and this mapping exists in one of the modes
+ indicated by {mode}.
+ The arguments {what} and {mode} are strings.
+ When {abbr} is there and it is |TRUE| use abbreviations
+ instead of mappings. Don't forget to specify Insert and/or
+ Command-line mode.
+ Both the global mappings and the mappings local to the current
+ buffer are checked for a match.
+ If no matching mapping is found FALSE is returned.
+ The following characters are recognized in {mode}:
+ n Normal mode
+ v Visual and Select mode
+ x Visual mode
+ s Select mode
+ o Operator-pending mode
+ i Insert mode
+ l Language-Argument ("r", "f", "t", etc.)
+ c Command-line mode
+ When {mode} is omitted, "nvo" is used.
+
+ This function is useful to check if a mapping already exists
+ to a function in a Vim script. Example: >vim
+ if !hasmapto('\ABCdoit')
+ map <Leader>d \ABCdoit
+ endif
+ <This installs the mapping to "\ABCdoit" only if there isn't
+ already a mapping to "\ABCdoit".
+
+ ]=],
+ name = 'hasmapto',
+ params = { { 'what', 'any' }, { 'mode', 'string' }, { 'abbr', 'any' } },
+ returns = '0|1',
+ signature = 'hasmapto({what} [, {mode} [, {abbr}]])',
+ },
+ highlightID = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |hlID()|.
+ ]=],
+ func = 'f_hlID',
+ params = { { 'name', 'string' } },
+ signature = 'highlightID({name})',
+ },
+ highlight_exists = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |hlexists()|.
+ ]=],
+ func = 'f_hlexists',
+ params = { { 'name', 'string' } },
+ signature = 'highlight_exists({name})',
+ },
+ histadd = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Add the String {item} to the history {history} which can be
+ one of: *hist-names*
+ "cmd" or ":" command line history
+ "search" or "/" search pattern history
+ "expr" or "=" typed expression history
+ "input" or "@" input line history
+ "debug" or ">" debug command history
+ empty the current or last used history
+ The {history} string does not need to be the whole name, one
+ character is sufficient.
+ If {item} does already exist in the history, it will be
+ shifted to become the newest entry.
+ The result is a Number: TRUE if the operation was successful,
+ otherwise FALSE is returned.
+
+ Example: >vim
+ call histadd("input", strftime("%Y %b %d"))
+ let date=input("Enter date: ")
+ <This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'histadd',
+ params = { { 'history', 'any' }, { 'item', 'any' } },
+ returns = '0|1',
+ signature = 'histadd({history}, {item})',
+ },
+ histdel = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Clear {history}, i.e. delete all its entries. See |hist-names|
+ for the possible values of {history}.
+
+ If the parameter {item} evaluates to a String, it is used as a
+ regular expression. All entries matching that expression will
+ be removed from the history (if there are any).
+ Upper/lowercase must match, unless "\c" is used |/\c|.
+ If {item} evaluates to a Number, it will be interpreted as
+ an index, see |:history-indexing|. The respective entry will
+ be removed if it exists.
+
+ The result is TRUE for a successful operation, otherwise FALSE
+ is returned.
+
+ Examples:
+ Clear expression register history: >vim
+ call histdel("expr")
+ <
+ Remove all entries starting with "*" from the search history: >vim
+ call histdel("/", '^\*')
+ <
+ The following three are equivalent: >vim
+ call histdel("search", histnr("search"))
+ call histdel("search", -1)
+ call histdel("search", '^' .. histget("search", -1) .. '$')
+ <
+ To delete the last search pattern and use the last-but-one for
+ the "n" command and 'hlsearch': >vim
+ call histdel("search", -1)
+ let @/ = histget("search", -1)
+ <
+ ]=],
+ name = 'histdel',
+ params = { { 'history', 'any' }, { 'item', 'any' } },
+ returns = '0|1',
+ signature = 'histdel({history} [, {item}])',
+ },
+ histget = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a String, the entry with Number {index} from
+ {history}. See |hist-names| for the possible values of
+ {history}, and |:history-indexing| for {index}. If there is
+ no such entry, an empty String is returned. When {index} is
+ omitted, the most recent item from the history is used.
+
+ Examples:
+ Redo the second last search from history. >vim
+ execute '/' .. histget("search", -2)
+
+ <Define an Ex command ":H {num}" that supports re-execution of
+ the {num}th entry from the output of |:history|. >vim
+ command -nargs=1 H execute histget("cmd", 0+<args>)
+ <
+ ]=],
+ name = 'histget',
+ params = { { 'history', 'any' }, { 'index', 'any' } },
+ returns = 'string',
+ signature = 'histget({history} [, {index}])',
+ },
+ histnr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is the Number of the current entry in {history}.
+ See |hist-names| for the possible values of {history}.
+ If an error occurred, -1 is returned.
+
+ Example: >vim
+ let inp_index = histnr("expr")
+
+ ]=],
+ name = 'histnr',
+ params = { { 'history', 'any' } },
+ returns = 'integer',
+ signature = 'histnr({history})',
+ },
+ hlID = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the ID of the highlight group
+ with name {name}. When the highlight group doesn't exist,
+ zero is returned.
+ This can be used to retrieve information about the highlight
+ group. For example, to get the background color of the
+ "Comment" group: >vim
+ echo synIDattr(synIDtrans(hlID("Comment")), "bg")
+ <
+ ]=],
+ name = 'hlID',
+ params = { { 'name', 'string' } },
+ returns = 'integer',
+ signature = 'hlID({name})',
+ },
+ hlexists = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is TRUE if a highlight group
+ called {name} exists. This is when the group has been
+ defined in some way. Not necessarily when highlighting has
+ been defined for it, it may also have been used for a syntax
+ item.
+
+ ]=],
+ name = 'hlexists',
+ params = { { 'name', 'string' } },
+ returns = '0|1',
+ signature = 'hlexists({name})',
+ },
+ hostname = {
+ desc = [=[
+ The result is a String, which is the name of the machine on
+ which Vim is currently running. Machine names greater than
+ 256 characters long are truncated.
+ ]=],
+ fast = true,
+ name = 'hostname',
+ params = {},
+ returns = 'string',
+ signature = 'hostname()',
+ },
+ iconv = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a String, which is the text {string} converted
+ from encoding {from} to encoding {to}.
+ When the conversion completely fails an empty string is
+ returned. When some characters could not be converted they
+ are replaced with "?".
+ The encoding names are whatever the iconv() library function
+ can accept, see ":!man 3 iconv".
+ Note that Vim uses UTF-8 for all Unicode encodings, conversion
+ from/to UCS-2 is automatically changed to use UTF-8. You
+ cannot use UCS-2 in a string anyway, because of the NUL bytes.
+
+ ]=],
+ fast = true,
+ name = 'iconv',
+ params = { { 'string', 'string' }, { 'from', 'any' }, { 'to', 'any' } },
+ signature = 'iconv({string}, {from}, {to})',
+ },
+ id = {
+ args = 1,
+ desc = [=[
+ Returns a |String| which is a unique identifier of the
+ container type (|List|, |Dict|, |Blob| and |Partial|). It is
+ guaranteed that for the mentioned types `id(v1) ==# id(v2)`
+ returns true iff `type(v1) == type(v2) && v1 is v2`.
+ Note that `v:_null_string`, `v:_null_list`, `v:_null_dict` and
+ `v:_null_blob` have the same `id()` with different types
+ because they are internally represented as NULL pointers.
+ `id()` returns a hexadecimal representanion of the pointers to
+ the containers (i.e. like `0x994a40`), same as `printf("%p",
+ {expr})`, but it is advised against counting on the exact
+ format of the return value.
+
+ It is not guaranteed that `id(no_longer_existing_container)`
+ will not be equal to some other `id()`: new containers may
+ reuse identifiers of the garbage-collected ones.
+ ]=],
+ name = 'id',
+ params = { { 'expr', 'any' } },
+ signature = 'id({expr})',
+ },
+ indent = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is indent of line {lnum} in the
+ current buffer. The indent is counted in spaces, the value
+ of 'tabstop' is relevant. {lnum} is used just like in
+ |getline()|.
+ When {lnum} is invalid -1 is returned.
+
+ ]=],
+ name = 'indent',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'indent({lnum})',
+ },
+ index = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Find {expr} in {object} and return its index. See
+ |indexof()| for using a lambda to select the item.
+
+ If {object} is a |List| return the lowest index where the item
+ has a value equal to {expr}. There is no automatic
+ conversion, so the String "4" is different from the Number 4.
+ And the Number 4 is different from the Float 4.0. The value
+ of 'ignorecase' is not used here, case matters as indicated by
+ the {ic} argument.
+
+ If {object} is a |Blob| return the lowest index where the byte
+ value is equal to {expr}.
+
+ If {start} is given then start looking at the item with index
+ {start} (may be negative for an item relative to the end).
+
+ When {ic} is given and it is |TRUE|, ignore case. Otherwise
+ case must match.
+
+ -1 is returned when {expr} is not found in {object}.
+ Example: >vim
+ let idx = index(words, "the")
+ if index(numbers, 123) >= 0
+ " ...
+ endif
+
+ ]=],
+ name = 'index',
+ params = { { 'object', 'any' }, { 'expr', 'any' }, { 'start', 'any' }, { 'ic', 'any' } },
+ signature = 'index({object}, {expr} [, {start} [, {ic}]])',
+ },
+ indexof = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Returns the index of an item in {object} where {expr} is
+ v:true. {object} must be a |List| or a |Blob|.
+
+ If {object} is a |List|, evaluate {expr} for each item in the
+ List until the expression is v:true and return the index of
+ this item.
+
+ If {object} is a |Blob| evaluate {expr} for each byte in the
+ Blob until the expression is v:true and return the index of
+ this byte.
+
+ {expr} must be a |string| or |Funcref|.
+
+ If {expr} is a |string|: If {object} is a |List|, inside
+ {expr} |v:key| has the index of the current List item and
+ |v:val| has the value of the item. If {object} is a |Blob|,
+ inside {expr} |v:key| has the index of the current byte and
+ |v:val| has the byte value.
+
+ If {expr} is a |Funcref| it must take two arguments:
+ 1. the key or the index of the current item.
+ 2. the value of the current item.
+ The function must return |TRUE| if the item is found and the
+ search should stop.
+
+ The optional argument {opts} is a Dict and supports the
+ following items:
+ startidx start evaluating {expr} at the item with this
+ index; may be negative for an item relative to
+ the end
+ Returns -1 when {expr} evaluates to v:false for all the items.
+ Example: >vim
+ let l = [#{n: 10}, #{n: 20}, #{n: 30}]
+ echo indexof(l, "v:val.n == 20")
+ echo indexof(l, {i, v -> v.n == 30})
+ echo indexof(l, "v:val.n == 20", #{startidx: 1})
+
+ ]=],
+ name = 'indexof',
+ params = { { 'object', 'any' }, { 'expr', 'any' }, { 'opts', 'table' } },
+ signature = 'indexof({object}, {expr} [, {opts}])',
+ },
+ input = {
+ args = { 1, 3 },
+ base = 1,
+ desc = '',
+ name = 'input',
+ params = { { 'prompt', 'any' }, { 'text', 'any' }, { 'completion', 'any' } },
+ signature = 'input({prompt} [, {text} [, {completion}]])',
+ },
+ input__1 = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is whatever the user typed on
+ the command-line. The {prompt} argument is either a prompt
+ string, or a blank string (for no prompt). A '\n' can be used
+ in the prompt to start a new line.
+
+ In the second form it accepts a single dictionary with the
+ following keys, any of which may be omitted:
+
+ Key Default Description ~
+ prompt "" Same as {prompt} in the first form.
+ default "" Same as {text} in the first form.
+ completion nothing Same as {completion} in the first form.
+ cancelreturn "" The value returned when the dialog is
+ cancelled.
+ highlight nothing Highlight handler: |Funcref|.
+
+ The highlighting set with |:echohl| is used for the prompt.
+ The input is entered just like a command-line, with the same
+ editing commands and mappings. There is a separate history
+ for lines typed for input().
+ Example: >vim
+ if input("Coffee or beer? ") == "beer"
+ echo "Cheers!"
+ endif
+ <
+ If the optional {text} argument is present and not empty, this
+ is used for the default reply, as if the user typed this.
+ Example: >vim
+ let color = input("Color? ", "white")
+
+ <The optional {completion} argument specifies the type of
+ completion supported for the input. Without it completion is
+ not performed. The supported completion types are the same as
+ that can be supplied to a user-defined command using the
+ "-complete=" argument. Refer to |:command-completion| for
+ more information. Example: >vim
+ let fname = input("File: ", "", "file")
+
+ < *input()-highlight* *E5400* *E5402*
+ The optional `highlight` key allows specifying function which
+ will be used for highlighting user input. This function
+ receives user input as its only argument and must return
+ a list of 3-tuples [hl_start_col, hl_end_col + 1, hl_group]
+ where
+ hl_start_col is the first highlighted column,
+ hl_end_col is the last highlighted column (+ 1!),
+ hl_group is |:hi| group used for highlighting.
+ *E5403* *E5404* *E5405* *E5406*
+ Both hl_start_col and hl_end_col + 1 must point to the start
+ of the multibyte character (highlighting must not break
+ multibyte characters), hl_end_col + 1 may be equal to the
+ input length. Start column must be in range [0, len(input)),
+ end column must be in range (hl_start_col, len(input)],
+ sections must be ordered so that next hl_start_col is greater
+ then or equal to previous hl_end_col.
+
+ Example (try some input with parentheses): >vim
+ highlight RBP1 guibg=Red ctermbg=red
+ highlight RBP2 guibg=Yellow ctermbg=yellow
+ highlight RBP3 guibg=Green ctermbg=green
+ highlight RBP4 guibg=Blue ctermbg=blue
+ let g:rainbow_levels = 4
+ function! RainbowParens(cmdline)
+ let ret = []
+ let i = 0
+ let lvl = 0
+ while i < len(a:cmdline)
+ if a:cmdline[i] is# '('
+ call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)])
+ let lvl += 1
+ elseif a:cmdline[i] is# ')'
+ let lvl -= 1
+ call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)])
+ endif
+ let i += 1
+ endwhile
+ return ret
+ endfunction
+ call input({'prompt':'>','highlight':'RainbowParens'})
+ <
+ Highlight function is called at least once for each new
+ displayed input string, before command-line is redrawn. It is
+ expected that function is pure for the duration of one input()
+ call, i.e. it produces the same output for the same input, so
+ output may be memoized. Function is run like under |:silent|
+ modifier. If the function causes any errors, it will be
+ skipped for the duration of the current input() call.
+
+ Highlighting is disabled if command-line contains arabic
+ characters.
+
+ NOTE: This function must not be used in a startup file, for
+ the versions that only run in GUI mode (e.g., the Win32 GUI).
+ Note: When input() is called from within a mapping it will
+ consume remaining characters from that mapping, because a
+ mapping is handled like the characters were typed.
+ Use |inputsave()| before input() and |inputrestore()|
+ after input() to avoid that. Another solution is to avoid
+ that further characters follow in the mapping, e.g., by using
+ |:execute| or |:normal|.
+
+ Example with a mapping: >vim
+ nmap \x :call GetFoo()<CR>:exe "/" .. Foo<CR>
+ function GetFoo()
+ call inputsave()
+ let g:Foo = input("enter search pattern: ")
+ call inputrestore()
+ endfunction
+
+ ]=],
+ name = 'input',
+ params = { { 'opts', 'table' } },
+ signature = 'input({opts})',
+ },
+ inputdialog = {
+ args = { 1, 3 },
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Use |input()| instead.
+ ]=],
+ params = VARARGS,
+ signature = 'input(...)',
+ },
+ inputlist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ {textlist} must be a |List| of strings. This |List| is
+ displayed, one string per line. The user will be prompted to
+ enter a number, which is returned.
+ The user can also select an item by clicking on it with the
+ mouse, if the mouse is enabled in the command line ('mouse' is
+ "a" or includes "c"). For the first string 0 is returned.
+ When clicking above the first item a negative number is
+ returned. When clicking on the prompt one more than the
+ length of {textlist} is returned.
+ Make sure {textlist} has less than 'lines' entries, otherwise
+ it won't work. It's a good idea to put the entry number at
+ the start of the string. And put a prompt in the first item.
+ Example: >vim
+ let color = inputlist(['Select color:', '1. red',
+ \ '2. green', '3. blue'])
+
+ ]=],
+ name = 'inputlist',
+ params = { { 'textlist', 'any' } },
+ signature = 'inputlist({textlist})',
+ },
+ inputrestore = {
+ desc = [=[
+ Restore typeahead that was saved with a previous |inputsave()|.
+ Should be called the same number of times inputsave() is
+ called. Calling it more often is harmless though.
+ Returns TRUE when there is nothing to restore, FALSE otherwise.
+ ]=],
+ name = 'inputrestore',
+ params = {},
+ signature = 'inputrestore()',
+ },
+ inputsave = {
+ desc = [=[
+ Preserve typeahead (also from mappings) and clear it, so that
+ a following prompt gets input from the user. Should be
+ followed by a matching inputrestore() after the prompt. Can
+ be used several times, in which case there must be just as
+ many inputrestore() calls.
+ Returns TRUE when out of memory, FALSE otherwise.
+ ]=],
+ name = 'inputsave',
+ params = {},
+ signature = 'inputsave()',
+ },
+ inputsecret = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ This function acts much like the |input()| function with but
+ two exceptions:
+ a) the user's response will be displayed as a sequence of
+ asterisks ("*") thereby keeping the entry secret, and
+ b) the user's response will not be recorded on the input
+ |history| stack.
+ The result is a String, which is whatever the user actually
+ typed on the command-line in response to the issued prompt.
+ NOTE: Command-line completion is not supported.
+
+ ]=],
+ name = 'inputsecret',
+ params = { { 'prompt', 'any' }, { 'text', 'any' } },
+ signature = 'inputsecret({prompt} [, {text}])',
+ },
+ insert = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ When {object} is a |List| or a |Blob| insert {item} at the start
+ of it.
+
+ If {idx} is specified insert {item} before the item with index
+ {idx}. If {idx} is zero it goes before the first item, just
+ like omitting {idx}. A negative {idx} is also possible, see
+ |list-index|. -1 inserts just before the last item.
+
+ Returns the resulting |List| or |Blob|. Examples: >vim
+ let mylist = insert([2, 3, 5], 1)
+ call insert(mylist, 4, -1)
+ call insert(mylist, 6, len(mylist))
+ <The last example can be done simpler with |add()|.
+ Note that when {item} is a |List| it is inserted as a single
+ item. Use |extend()| to concatenate |Lists|.
+
+ ]=],
+ name = 'insert',
+ params = { { 'object', 'any' }, { 'item', 'any' }, { 'idx', 'integer' } },
+ signature = 'insert({object}, {item} [, {idx}])',
+ },
+ interrupt = {
+ args = 0,
+ desc = [=[
+ Interrupt script execution. It works more or less like the
+ user typing CTRL-C, most commands won't execute and control
+ returns to the user. This is useful to abort execution
+ from lower down, e.g. in an autocommand. Example: >vim
+ function s:check_typoname(file)
+ if fnamemodify(a:file, ':t') == '['
+ echomsg 'Maybe typo'
+ call interrupt()
+ endif
+ endfunction
+ au BufWritePre * call s:check_typoname(expand('<amatch>'))
+ <
+ ]=],
+ name = 'interrupt',
+ params = {},
+ signature = 'interrupt()',
+ },
+ invert = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Bitwise invert. The argument is converted to a number. A
+ List, Dict or Float argument causes an error. Example: >vim
+ let bits = invert(bits)
+ <
+ ]=],
+ name = 'invert',
+ params = { { 'expr', 'any' } },
+ signature = 'invert({expr})',
+ },
+ isdirectory = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| when a directory
+ with the name {directory} exists. If {directory} doesn't
+ exist, or isn't a directory, the result is |FALSE|. {directory}
+ is any expression, which is used as a String.
+
+ ]=],
+ fast = true,
+ name = 'isdirectory',
+ params = { { 'directory', 'any' } },
+ returns = '0|1',
+ signature = 'isdirectory({directory})',
+ },
+ isinf = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return 1 if {expr} is a positive infinity, or -1 a negative
+ infinity, otherwise 0. >vim
+ echo isinf(1.0 / 0.0)
+ < 1 >vim
+ echo isinf(-1.0 / 0.0)
+ < -1
+
+ ]=],
+ name = 'isinf',
+ params = { { 'expr', 'any' } },
+ returns = '1|0|-1',
+ signature = 'isinf({expr})',
+ },
+ islocked = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| when {expr} is the
+ name of a locked variable.
+ The string argument {expr} must be the name of a variable,
+ |List| item or |Dictionary| entry, not the variable itself!
+ Example: >vim
+ let alist = [0, ['a', 'b'], 2, 3]
+ lockvar 1 alist
+ echo islocked('alist') " 1
+ echo islocked('alist[1]') " 0
+
+ <When {expr} is a variable that does not exist you get an error
+ message. Use |exists()| to check for existence.
+
+ ]=],
+ name = 'islocked',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'islocked({expr})',
+ tags = { 'E786' },
+ },
+ isnan = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return |TRUE| if {expr} is a float with value NaN. >vim
+ echo isnan(0.0 / 0.0)
+ < 1
+
+ ]=],
+ name = 'isnan',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'isnan({expr})',
+ },
+ items = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a |List| with all the key-value pairs of {dict}. Each
+ |List| item is a list with two items: the key of a {dict}
+ entry and the value of this entry. The |List| is in arbitrary
+ order. Also see |keys()| and |values()|.
+ Example: >vim
+ for [key, value] in items(mydict)
+ echo key .. ': ' .. value
+ endfor
+
+ ]=],
+ name = 'items',
+ params = { { 'dict', 'any' } },
+ signature = 'items({dict})',
+ },
+ jobclose = {
+ args = { 1, 2 },
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |chanclose()|
+ ]=],
+ func = 'f_chanclose',
+ params = VARARGS,
+ signature = 'jobclose({id} [, {stream}])',
+ },
+ jobpid = {
+ args = 1,
+ desc = [=[
+ Return the PID (process id) of |job-id| {job}.
+ ]=],
+ name = 'jobpid',
+ params = { { 'job', 'any' } },
+ returns = 'integer',
+ signature = 'jobpid({job})',
+ },
+ jobresize = {
+ args = 3,
+ desc = [=[
+ Resize the pseudo terminal window of |job-id| {job} to {width}
+ columns and {height} rows.
+ Fails if the job was not started with `"pty":v:true`.
+ ]=],
+ name = 'jobresize',
+ params = { { 'job', 'any' }, { 'width', 'integer' }, { 'height', 'integer' } },
+ signature = 'jobresize({job}, {width}, {height})',
+ },
+ jobsend = {
+ args = 2,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |chansend()|
+ ]=],
+ func = 'f_chansend',
+ params = VARARGS,
+ signature = 'jobsend({id}, {data})',
+ },
+ jobstart = {
+ args = { 1, 2 },
+ desc = [=[
+ Note: Prefer |vim.system()| in Lua (unless using the `pty` option).
+
+ Spawns {cmd} as a job.
+ If {cmd} is a List it runs directly (no 'shell').
+ If {cmd} is a String it runs in the 'shell', like this: >vim
+ call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+ <(See |shell-unquoting| for details.)
+
+ Example: >vim
+ call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
+ <
+ Returns |job-id| on success, 0 on invalid arguments (or job
+ table is full), -1 if {cmd}[0] or 'shell' is not executable.
+ The returned job-id is a valid |channel-id| representing the
+ job's stdio streams. Use |chansend()| (or |rpcnotify()| and
+ |rpcrequest()| if "rpc" was enabled) to send data to stdin and
+ |chanclose()| to close the streams without stopping the job.
+
+ See |job-control| and |RPC|.
+
+ NOTE: on Windows if {cmd} is a List:
+ - cmd[0] must be an executable (not a "built-in"). If it is
+ in $PATH it can be called by name, without an extension: >vim
+ call jobstart(['ping', 'neovim.io'])
+ < If it is a full or partial path, extension is required: >vim
+ call jobstart(['System32\ping.exe', 'neovim.io'])
+ < - {cmd} is collapsed to a string of quoted args as expected
+ by CommandLineToArgvW https://msdn.microsoft.com/bb776391
+ unless cmd[0] is some form of "cmd.exe".
+
+ *jobstart-env*
+ The job environment is initialized as follows:
+ $NVIM is set to |v:servername| of the parent Nvim
+ $NVIM_LISTEN_ADDRESS is unset
+ $NVIM_LOG_FILE is unset
+ $VIM is unset
+ $VIMRUNTIME is unset
+ You can set these with the `env` option.
+
+ *jobstart-options*
+ {opts} is a dictionary with these keys:
+ clear_env: (boolean) `env` defines the job environment
+ exactly, instead of merging current environment.
+ cwd: (string, default=|current-directory|) Working
+ directory of the job.
+ detach: (boolean) Detach the job process: it will not be
+ killed when Nvim exits. If the process exits
+ before Nvim, `on_exit` will be invoked.
+ env: (dict) Map of environment variable name:value
+ pairs extending (or replace with "clear_env")
+ the current environment. |jobstart-env|
+ height: (number) Height of the `pty` terminal.
+ |on_exit|: (function) Callback invoked when the job exits.
+ |on_stdout|: (function) Callback invoked when the job emits
+ stdout data.
+ |on_stderr|: (function) Callback invoked when the job emits
+ stderr data.
+ overlapped: (boolean) Sets FILE_FLAG_OVERLAPPED for the
+ stdio passed to the child process. Only on
+ MS-Windows; ignored on other platforms.
+ pty: (boolean) Connect the job to a new pseudo
+ terminal, and its streams to the master file
+ descriptor. `on_stdout` receives all output,
+ `on_stderr` is ignored. |terminal-start|
+ rpc: (boolean) Use |msgpack-rpc| to communicate with
+ the job over stdio. Then `on_stdout` is ignored,
+ but `on_stderr` can still be used.
+ stderr_buffered: (boolean) Collect data until EOF (stream closed)
+ before invoking `on_stderr`. |channel-buffered|
+ stdout_buffered: (boolean) Collect data until EOF (stream
+ closed) before invoking `on_stdout`. |channel-buffered|
+ stdin: (string) Either "pipe" (default) to connect the
+ job's stdin to a channel or "null" to disconnect
+ stdin.
+ width: (number) Width of the `pty` terminal.
+
+ {opts} is passed as |self| dictionary to the callback; the
+ caller may set other keys to pass application-specific data.
+
+ Returns:
+ - |channel-id| on success
+ - 0 on invalid arguments
+ - -1 if {cmd}[0] is not executable.
+ See also |job-control|, |channel|, |msgpack-rpc|.
+ ]=],
+ name = 'jobstart',
+ params = { { 'cmd', 'any' }, { 'opts', 'table' } },
+ signature = 'jobstart({cmd} [, {opts}])',
+ },
+ jobstop = {
+ args = 1,
+ desc = [=[
+ Stop |job-id| {id} by sending SIGTERM to the job process. If
+ the process does not terminate after a timeout then SIGKILL
+ will be sent. When the job terminates its |on_exit| handler
+ (if any) will be invoked.
+ See |job-control|.
+
+ Returns 1 for valid job id, 0 for invalid id, including jobs have
+ exited or stopped.
+ ]=],
+ name = 'jobstop',
+ params = { { 'id', 'any' } },
+ signature = 'jobstop({id})',
+ },
+ jobwait = {
+ args = { 1, 2 },
+ desc = [=[
+ Waits for jobs and their |on_exit| handlers to complete.
+
+ {jobs} is a List of |job-id|s to wait for.
+ {timeout} is the maximum waiting time in milliseconds. If
+ omitted or -1, wait forever.
+
+ Timeout of 0 can be used to check the status of a job: >vim
+ let running = jobwait([{job-id}], 0)[0] == -1
+ <
+ During jobwait() callbacks for jobs not in the {jobs} list may
+ be invoked. The screen will not redraw unless |:redraw| is
+ invoked by a callback.
+
+ Returns a list of len({jobs}) integers, where each integer is
+ the status of the corresponding job:
+ Exit-code, if the job exited
+ -1 if the timeout was exceeded
+ -2 if the job was interrupted (by |CTRL-C|)
+ -3 if the job-id is invalid
+ ]=],
+ name = 'jobwait',
+ params = { { 'jobs', 'any' }, { 'timeout', 'integer' } },
+ signature = 'jobwait({jobs} [, {timeout}])',
+ },
+ join = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Join the items in {list} together into one String.
+ When {sep} is specified it is put in between the items. If
+ {sep} is omitted a single space is used.
+ Note that {sep} is not added at the end. You might want to
+ add it there too: >vim
+ let lines = join(mylist, "\n") .. "\n"
+ <String items are used as-is. |Lists| and |Dictionaries| are
+ converted into a string like with |string()|.
+ The opposite function is |split()|.
+
+ ]=],
+ name = 'join',
+ params = { { 'list', 'any' }, { 'sep', 'any' } },
+ signature = 'join({list} [, {sep}])',
+ },
+ json_decode = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert {expr} from JSON object. Accepts |readfile()|-style
+ list as the input, as well as regular string. May output any
+ Vim value. In the following cases it will output
+ |msgpack-special-dict|:
+ 1. Dictionary contains duplicate key.
+ 2. Dictionary contains empty key.
+ 3. String contains NUL byte. Two special dictionaries: for
+ dictionary and for string will be emitted in case string
+ with NUL byte was a dictionary key.
+
+ Note: function treats its input as UTF-8 always. The JSON
+ standard allows only a few encodings, of which UTF-8 is
+ recommended and the only one required to be supported.
+ Non-UTF-8 characters are an error.
+
+ ]=],
+ name = 'json_decode',
+ params = { { 'expr', 'any' } },
+ signature = 'json_decode({expr})',
+ },
+ json_encode = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert {expr} into a JSON string. Accepts
+ |msgpack-special-dict| as the input. Will not convert
+ |Funcref|s, mappings with non-string keys (can be created as
+ |msgpack-special-dict|), values with self-referencing
+ containers, strings which contain non-UTF-8 characters,
+ pseudo-UTF-8 strings which contain codepoints reserved for
+ surrogate pairs (such strings are not valid UTF-8 strings).
+ Non-printable characters are converted into "\u1234" escapes
+ or special escapes like "\t", other are dumped as-is.
+ |Blob|s are converted to arrays of the individual bytes.
+
+ ]=],
+ name = 'json_encode',
+ params = { { 'expr', 'any' } },
+ signature = 'json_encode({expr})',
+ },
+ keys = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a |List| with all the keys of {dict}. The |List| is in
+ arbitrary order. Also see |items()| and |values()|.
+
+ ]=],
+ name = 'keys',
+ params = { { 'dict', 'any' } },
+ signature = 'keys({dict})',
+ },
+ keytrans = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Turn the internal byte representation of keys into a form that
+ can be used for |:map|. E.g. >vim
+ let xx = "\<C-Home>"
+ echo keytrans(xx)
+ < <C-Home>
+
+ ]=],
+ name = 'keytrans',
+ params = { { 'string', 'string' } },
+ signature = 'keytrans({string})',
+ },
+ last_buffer_nr = {
+ deprecated = true,
+ desc = [=[
+ Obsolete name for bufnr("$").
+ ]=],
+ params = {},
+ signature = 'last_buffer_nr()',
+ },
+ len = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the length of the argument.
+ When {expr} is a String or a Number the length in bytes is
+ used, as with |strlen()|.
+ When {expr} is a |List| the number of items in the |List| is
+ returned.
+ When {expr} is a |Blob| the number of bytes is returned.
+ When {expr} is a |Dictionary| the number of entries in the
+ |Dictionary| is returned.
+ Otherwise an error is given and returns zero.
+
+ ]=],
+ name = 'len',
+ params = { { 'expr', 'any' } },
+ signature = 'len({expr})',
+ tags = { 'E701' },
+ },
+ libcall = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Call function {funcname} in the run-time library {libname}
+ with single argument {argument}.
+ This is useful to call functions in a library that you
+ especially made to be used with Vim. Since only one argument
+ is possible, calling standard library functions is rather
+ limited.
+ The result is the String returned by the function. If the
+ function returns NULL, this will appear as an empty string ""
+ to Vim.
+ If the function returns a number, use libcallnr()!
+ If {argument} is a number, it is passed to the function as an
+ int; if {argument} is a string, it is passed as a
+ null-terminated string.
+
+ libcall() allows you to write your own 'plug-in' extensions to
+ Vim without having to recompile the program. It is NOT a
+ means to call system functions! If you try to do so Vim will
+ very probably crash.
+
+ For Win32, the functions you write must be placed in a DLL
+ and use the normal C calling convention (NOT Pascal which is
+ used in Windows System DLLs). The function must take exactly
+ one parameter, either a character pointer or a long integer,
+ and must return a character pointer or NULL. The character
+ pointer returned must point to memory that will remain valid
+ after the function has returned (e.g. in static data in the
+ DLL). If it points to allocated memory, that memory will
+ leak away. Using a static buffer in the function should work,
+ it's then freed when the DLL is unloaded.
+
+ WARNING: If the function returns a non-valid pointer, Vim may
+ crash! This also happens if the function returns a number,
+ because Vim thinks it's a pointer.
+ For Win32 systems, {libname} should be the filename of the DLL
+ without the ".DLL" suffix. A full path is only required if
+ the DLL is not in the usual places.
+ For Unix: When compiling your own plugins, remember that the
+ object code must be compiled as position-independent ('PIC').
+ Examples: >vim
+ echo libcall("libc.so", "getenv", "HOME")
+
+ ]=],
+ name = 'libcall',
+ params = { { 'libname', 'string' }, { 'funcname', 'string' }, { 'argument', 'any' } },
+ signature = 'libcall({libname}, {funcname}, {argument})',
+ tags = { 'E364', 'E368' },
+ },
+ libcallnr = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Just like |libcall()|, but used for a function that returns an
+ int instead of a string.
+ Examples: >vim
+ echo libcallnr("/usr/lib/libc.so", "getpid", "")
+ call libcallnr("libc.so", "printf", "Hello World!\n")
+ call libcallnr("libc.so", "sleep", 10)
+ <
+ ]=],
+ name = 'libcallnr',
+ params = { { 'libname', 'string' }, { 'funcname', 'string' }, { 'argument', 'any' } },
+ signature = 'libcallnr({libname}, {funcname}, {argument})',
+ },
+ line = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the line number of the file
+ position given with {expr}. The {expr} argument is a string.
+ The accepted positions are:
+ . the cursor position
+ $ the last line in the current buffer
+ 'x position of mark x (if the mark is not set, 0 is
+ returned)
+ w0 first line visible in current window (one if the
+ display isn't updated, e.g. in silent Ex mode)
+ w$ last line visible in current window (this is one
+ less than "w0" if no lines are visible)
+ v In Visual mode: the start of the Visual area (the
+ cursor is the end). When not in Visual mode
+ returns the cursor position. Differs from |'<| in
+ that it's updated right away.
+ Note that a mark in another file can be used. The line number
+ then applies to another buffer.
+ To get the column number use |col()|. To get both use
+ |getpos()|.
+ With the optional {winid} argument the values are obtained for
+ that window instead of the current window.
+ Returns 0 for invalid values of {expr} and {winid}.
+ Examples: >vim
+ echo line(".") " line number of the cursor
+ echo line(".", winid) " idem, in window "winid"
+ echo line("'t") " line number of mark t
+ echo line("'" .. marker) " line number of mark marker
+ <
+ To jump to the last known position when opening a file see
+ |last-position-jump|.
+
+ ]=],
+ name = 'line',
+ params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'line({expr} [, {winid}])',
+ },
+ line2byte = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the byte count from the start of the buffer for line
+ {lnum}. This includes the end-of-line character, depending on
+ the 'fileformat' option for the current buffer. The first
+ line returns 1. UTF-8 encoding is used, 'fileencoding' is
+ ignored. This can also be used to get the byte count for the
+ line just below the last line: >vim
+ echo line2byte(line("$") + 1)
+ <This is the buffer size plus one. If 'fileencoding' is empty
+ it is the file size plus one. {lnum} is used like with
+ |getline()|. When {lnum} is invalid -1 is returned.
+ Also see |byte2line()|, |go| and |:goto|.
+
+ ]=],
+ name = 'line2byte',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'line2byte({lnum})',
+ },
+ lispindent = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the amount of indent for line {lnum} according the lisp
+ indenting rules, as with 'lisp'.
+ The indent is counted in spaces, the value of 'tabstop' is
+ relevant. {lnum} is used just like in |getline()|.
+ When {lnum} is invalid, -1 is returned.
+
+ ]=],
+ name = 'lispindent',
+ params = { { 'lnum', 'integer' } },
+ signature = 'lispindent({lnum})',
+ },
+ list2blob = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a Blob concatenating all the number values in {list}.
+ Examples: >vim
+ echo list2blob([1, 2, 3, 4]) " returns 0z01020304
+ echo list2blob([]) " returns 0z
+ <Returns an empty Blob on error. If one of the numbers is
+ negative or more than 255 error *E1239* is given.
+
+ |blob2list()| does the opposite.
+
+ ]=],
+ name = 'list2blob',
+ params = { { 'list', 'any' } },
+ signature = 'list2blob({list})',
+ },
+ list2str = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Convert each number in {list} to a character string can
+ concatenate them all. Examples: >vim
+ echo list2str([32]) " returns " "
+ echo list2str([65, 66, 67]) " returns "ABC"
+ <The same can be done (slowly) with: >vim
+ echo join(map(list, {nr, val -> nr2char(val)}), '')
+ <|str2list()| does the opposite.
+
+ UTF-8 encoding is always used, {utf8} option has no effect,
+ and exists only for backwards-compatibility.
+ With UTF-8 composing characters work as expected: >vim
+ echo list2str([97, 769]) " returns "aÌ"
+ <
+ Returns an empty string on error.
+
+ ]=],
+ name = 'list2str',
+ params = { { 'list', 'any' }, { 'utf8', 'any' } },
+ signature = 'list2str({list} [, {utf8}])',
+ },
+ localtime = {
+ desc = [=[
+ Return the current time, measured as seconds since 1st Jan
+ 1970. See also |strftime()|, |strptime()| and |getftime()|.
+ ]=],
+ name = 'localtime',
+ params = {},
+ signature = 'localtime()',
+ },
+ log = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the natural logarithm (base e) of {expr} as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number| in the range
+ (0, inf].
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo log(10)
+ < 2.302585 >vim
+ echo log(exp(5))
+ < 5.0
+
+ ]=],
+ float_func = 'log',
+ name = 'log',
+ params = { { 'expr', 'any' } },
+ signature = 'log({expr})',
+ },
+ log10 = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the logarithm of Float {expr} to base 10 as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo log10(1000)
+ < 3.0 >vim
+ echo log10(0.01)
+ < -2.0
+
+ ]=],
+ float_func = 'log10',
+ name = 'log10',
+ params = { { 'expr', 'any' } },
+ signature = 'log10({expr})',
+ },
+ luaeval = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Evaluate Lua expression {expr} and return its result converted
+ to Vim data structures. See |lua-eval| for more details.
+
+ ]=],
+ lua = false,
+ name = 'luaeval',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ signature = 'luaeval({expr} [, {expr}])',
+ },
+ map = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+ When {expr1} is a |List|| or |Dictionary|, replace each
+ item in {expr1} with the result of evaluating {expr2}.
+ For a |Blob| each byte is replaced.
+ For a |String|, each character, including composing
+ characters, is replaced.
+ If the item type changes you may want to use |mapnew()| to
+ create a new List or Dictionary.
+
+ {expr2} must be a |String| or |Funcref|.
+
+ If {expr2} is a |String|, inside {expr2} |v:val| has the value
+ of the current item. For a |Dictionary| |v:key| has the key
+ of the current item and for a |List| |v:key| has the index of
+ the current item. For a |Blob| |v:key| has the index of the
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Example: >vim
+ call map(mylist, '"> " .. v:val .. " <"')
+ <This puts "> " before and " <" after each item in "mylist".
+
+ Note that {expr2} is the result of an expression and is then
+ used as an expression again. Often it is good to use a
+ |literal-string| to avoid having to double backslashes. You
+ still have to double ' quotes
+
+ If {expr2} is a |Funcref| it is called with two arguments:
+ 1. The key or the index of the current item.
+ 2. the value of the current item.
+ The function must return the new value of the item. Example
+ that changes each value by "key-value": >vim
+ func KeyValue(key, val)
+ return a:key .. '-' .. a:val
+ endfunc
+ call map(myDict, function('KeyValue'))
+ <It is shorter when using a |lambda|: >vim
+ call map(myDict, {key, val -> key .. '-' .. val})
+ <If you do not use "val" you can leave it out: >vim
+ call map(myDict, {key -> 'item: ' .. key})
+ <If you do not use "key" you can use a short name: >vim
+ call map(myDict, {_, val -> 'item: ' .. val})
+ <
+ The operation is done in-place for a |List| and |Dictionary|.
+ If you want it to remain unmodified make a copy first: >vim
+ let tlist = map(copy(mylist), ' v:val .. "\t"')
+
+ <Returns {expr1}, the |List| or |Dictionary| that was filtered,
+ or a new |Blob| or |String|.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
+ unless it was defined with the "abort" flag.
+
+ ]=],
+ name = 'map',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'map({expr1}, {expr2})',
+ },
+ maparg = {
+ args = { 1, 4 },
+ base = 1,
+ desc = [=[
+ When {dict} is omitted or zero: Return the rhs of mapping
+ {name} in mode {mode}. The returned String has special
+ characters translated like in the output of the ":map" command
+ listing. When {dict} is TRUE a dictionary is returned, see
+ below. To get a list of all mappings see |maplist()|.
+
+ When there is no mapping for {name}, an empty String is
+ returned if {dict} is FALSE, otherwise returns an empty Dict.
+ When the mapping for {name} is empty, then "<Nop>" is
+ returned.
+
+ The {name} can have special key names, like in the ":map"
+ command.
+
+ {mode} can be one of these strings:
+ "n" Normal
+ "v" Visual (including Select)
+ "o" Operator-pending
+ "i" Insert
+ "c" Cmd-line
+ "s" Select
+ "x" Visual
+ "l" langmap |language-mapping|
+ "t" Terminal
+ "" Normal, Visual and Operator-pending
+ When {mode} is omitted, the modes for "" are used.
+
+ When {abbr} is there and it is |TRUE| use abbreviations
+ instead of mappings.
+
+ When {dict} is there and it is |TRUE| return a dictionary
+ containing all the information of the mapping with the
+ following items: *mapping-dict*
+ "lhs" The {lhs} of the mapping as it would be typed
+ "lhsraw" The {lhs} of the mapping as raw bytes
+ "lhsrawalt" The {lhs} of the mapping as raw bytes, alternate
+ form, only present when it differs from "lhsraw"
+ "rhs" The {rhs} of the mapping as typed.
+ "silent" 1 for a |:map-silent| mapping, else 0.
+ "noremap" 1 if the {rhs} of the mapping is not remappable.
+ "script" 1 if mapping was defined with <script>.
+ "expr" 1 for an expression mapping (|:map-<expr>|).
+ "buffer" 1 for a buffer local mapping (|:map-local|).
+ "mode" Modes for which the mapping is defined. In
+ addition to the modes mentioned above, these
+ characters will be used:
+ " " Normal, Visual and Operator-pending
+ "!" Insert and Commandline mode
+ (|mapmode-ic|)
+ "sid" The script local ID, used for <sid> mappings
+ (|<SID>|). Negative for special contexts.
+ "scriptversion" The version of the script, always 1.
+ "lnum" The line number in "sid", zero if unknown.
+ "nowait" Do not wait for other, longer mappings.
+ (|:map-<nowait>|).
+ "abbr" True if this is an |abbreviation|.
+ "mode_bits" Nvim's internal binary representation of "mode".
+ |mapset()| ignores this; only "mode" is used.
+ See |maplist()| for usage examples. The values
+ are from src/nvim/state_defs.h and may change in
+ the future.
+
+ The dictionary can be used to restore a mapping with
+ |mapset()|.
+
+ The mappings local to the current buffer are checked first,
+ then the global mappings.
+ This function can be used to map a key even when it's already
+ mapped, and have it do the original mapping too. Sketch: >vim
+ exe 'nnoremap <Tab> ==' .. maparg('<Tab>', 'n')
+
+ ]=],
+ name = 'maparg',
+ params = {
+ { 'name', 'string' },
+ { 'mode', 'string' },
+ { 'abbr', 'boolean' },
+ { 'dict', 'boolean' },
+ },
+ returns = 'string|table<string,any>',
+ signature = 'maparg({name} [, {mode} [, {abbr} [, {dict}]]])',
+ },
+ mapcheck = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Check if there is a mapping that matches with {name} in mode
+ {mode}. See |maparg()| for {mode} and special names in
+ {name}.
+ When {abbr} is there and it is non-zero use abbreviations
+ instead of mappings.
+ A match happens with a mapping that starts with {name} and
+ with a mapping which is equal to the start of {name}.
+
+ matches mapping "a" "ab" "abc" ~
+ mapcheck("a") yes yes yes
+ mapcheck("abc") yes yes yes
+ mapcheck("ax") yes no no
+ mapcheck("b") no no no
+
+ The difference with maparg() is that mapcheck() finds a
+ mapping that matches with {name}, while maparg() only finds a
+ mapping for {name} exactly.
+ When there is no mapping that starts with {name}, an empty
+ String is returned. If there is one, the RHS of that mapping
+ is returned. If there are several mappings that start with
+ {name}, the RHS of one of them is returned. This will be
+ "<Nop>" if the RHS is empty.
+ The mappings local to the current buffer are checked first,
+ then the global mappings.
+ This function can be used to check if a mapping can be added
+ without being ambiguous. Example: >vim
+ if mapcheck("_vv") == ""
+ map _vv :set guifont=7x13<CR>
+ endif
+ <This avoids adding the "_vv" mapping when there already is a
+ mapping for "_v" or for "_vvv".
+
+ ]=],
+ name = 'mapcheck',
+ params = { { 'name', 'string' }, { 'mode', 'string' }, { 'abbr', 'any' } },
+ signature = 'mapcheck({name} [, {mode} [, {abbr}]])',
+ },
+ maplist = {
+ args = { 0, 1 },
+ desc = [[
+ Returns a |List| of all mappings. Each List item is a |Dict|,
+ the same as what is returned by |maparg()|, see
+ |mapping-dict|. When {abbr} is there and it is |TRUE| use
+ abbreviations instead of mappings.
+
+ Example to show all mappings with "MultiMatch" in rhs: >vim
+ echo maplist()->filter({_, m ->
+ \ match(get(m, 'rhs', ''), 'MultiMatch') >= 0
+ \ })
+ <It can be tricky to find mappings for particular |:map-modes|.
+ |mapping-dict|'s "mode_bits" can simplify this. For example,
+ the mode_bits for Normal, Insert or Command-line modes are
+ 0x19. To find all the mappings available in those modes you
+ can do: >vim
+ let saved_maps = []
+ for m in maplist()
+ if and(m.mode_bits, 0x19) != 0
+ eval saved_maps->add(m)
+ endif
+ endfor
+ echo saved_maps->mapnew({_, m -> m.lhs})
+ <The values of the mode_bits are defined in Nvim's
+ src/nvim/state_defs.h file and they can be discovered at
+ runtime using |:map-commands| and "maplist()". Example: >vim
+ omap xyzzy <Nop>
+ let op_bit = maplist()->filter(
+ \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
+ ounmap xyzzy
+ echo printf("Operator-pending mode bit: 0x%x", op_bit)
+ ]],
+ name = 'maplist',
+ params = {},
+ signature = 'maplist([{abbr}])'
+ },
+ mapnew = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Like |map()| but instead of replacing items in {expr1} a new
+ List or Dictionary is created and returned. {expr1} remains
+ unchanged. Items can still be changed by {expr2}, if you
+ don't want that use |deepcopy()| first.
+ ]=],
+ name = 'mapnew',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'mapnew({expr1}, {expr2})',
+ },
+ mapset = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Restore a mapping from a dictionary, possibly returned by
+ |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
+ is true, is set on the current buffer; it is up to the caller
+ to ensure that the intended buffer is the current buffer. This
+ feature allows copying mappings from one buffer to another.
+ The dict.mode value may restore a single mapping that covers
+ more than one mode, like with mode values of '!', ' ', "nox",
+ or 'v'. *E1276*
+
+ In the first form, {mode} and {abbr} should be the same as
+ for the call to |maparg()|. *E460*
+ {mode} is used to define the mode in which the mapping is set,
+ not the "mode" entry in {dict}.
+ Example for saving and restoring a mapping: >vim
+ let save_map = maparg('K', 'n', 0, 1)
+ nnoremap K somethingelse
+ " ...
+ call mapset('n', 0, save_map)
+ <Note that if you are going to replace a map in several modes,
+ e.g. with `:map!`, you need to save/restore the mapping for
+ all of them, when they might differ.
+
+ In the second form, with {dict} as the only argument, mode
+ and abbr are taken from the dict.
+ Example: >vim
+ let save_maps = maplist()->filter(
+ \ {_, m -> m.lhs == 'K'})
+ nnoremap K somethingelse
+ cnoremap K somethingelse2
+ " ...
+ unmap K
+ for d in save_maps
+ call mapset(d)
+ endfor
+ ]=],
+ name = 'mapset',
+ params = { { 'mode', 'string' }, { 'abbr', 'any' }, { 'dict', 'any' } },
+ signature = 'mapset({mode}, {abbr}, {dict})',
+ },
+ match = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ When {expr} is a |List| then this returns the index of the
+ first item where {pat} matches. Each item is used as a
+ String, |Lists| and |Dictionaries| are used as echoed.
+
+ Otherwise, {expr} is used as a String. The result is a
+ Number, which gives the index (byte offset) in {expr} where
+ {pat} matches.
+
+ A match at the first character or |List| item returns zero.
+ If there is no match -1 is returned.
+
+ For getting submatches see |matchlist()|.
+ Example: >vim
+ echo match("testing", "ing") " results in 4
+ echo match([1, 'x'], '\a') " results in 1
+ <See |string-match| for how {pat} is used.
+ *strpbrk()*
+ Vim doesn't have a strpbrk() function. But you can do: >vim
+ let sepidx = match(line, '[.,;: \t]')
+ < *strcasestr()*
+ Vim doesn't have a strcasestr() function. But you can add
+ "\c" to the pattern to ignore case: >vim
+ let idx = match(haystack, '\cneedle')
+ <
+ If {start} is given, the search starts from byte index
+ {start} in a String or item {start} in a |List|.
+ The result, however, is still the index counted from the
+ first character/item. Example: >vim
+ echo match("testing", "ing", 2)
+ <result is again "4". >vim
+ echo match("testing", "ing", 4)
+ <result is again "4". >vim
+ echo match("testing", "t", 2)
+ <result is "3".
+ For a String, if {start} > 0 then it is like the string starts
+ {start} bytes later, thus "^" will match at {start}. Except
+ when {count} is given, then it's like matches before the
+ {start} byte are ignored (this is a bit complicated to keep it
+ backwards compatible).
+ For a String, if {start} < 0, it will be set to 0. For a list
+ the index is counted from the end.
+ If {start} is out of range ({start} > strlen({expr}) for a
+ String or {start} > len({expr}) for a |List|) -1 is returned.
+
+ When {count} is given use the {count}th match. When a match
+ is found in a String the search for the next one starts one
+ character further. Thus this example results in 1: >vim
+ echo match("testing", "..", 0, 2)
+ <In a |List| the search continues in the next item.
+ Note that when {count} is added the way {start} works changes,
+ see above.
+
+ See |pattern| for the patterns that are accepted.
+ The 'ignorecase' option is used to set the ignore-caseness of
+ the pattern. 'smartcase' is NOT used. The matching is always
+ done like 'magic' is set and 'cpoptions' is empty.
+ Note that a match at the start is preferred, thus when the
+ pattern is using "*" (any number of matches) it tends to find
+ zero matches at the start instead of a number of matches
+ further down in the text.
+
+ ]=],
+ name = 'match',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'match({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchadd = {
+ args = { 2, 5 },
+ base = 1,
+ desc = [=[
+ Defines a pattern to be highlighted in the current window (a
+ "match"). It will be highlighted with {group}. Returns an
+ identification number (ID), which can be used to delete the
+ match using |matchdelete()|. The ID is bound to the window.
+ Matching is case sensitive and magic, unless case sensitivity
+ or magicness are explicitly overridden in {pattern}. The
+ 'magic', 'smartcase' and 'ignorecase' options are not used.
+ The "Conceal" value is special, it causes the match to be
+ concealed.
+
+ The optional {priority} argument assigns a priority to the
+ match. A match with a high priority will have its
+ highlighting overrule that of a match with a lower priority.
+ A priority is specified as an integer (negative numbers are no
+ exception). If the {priority} argument is not specified, the
+ default priority is 10. The priority of 'hlsearch' is zero,
+ hence all matches with a priority greater than zero will
+ overrule it. Syntax highlighting (see 'syntax') is a separate
+ mechanism, and regardless of the chosen priority a match will
+ always overrule syntax highlighting.
+
+ The optional {id} argument allows the request for a specific
+ match ID. If a specified ID is already taken, an error
+ message will appear and the match will not be added. An ID
+ is specified as a positive integer (zero excluded). IDs 1, 2
+ and 3 are reserved for |:match|, |:2match| and |:3match|,
+ respectively. 3 is reserved for use by the |matchparen|
+ plugin.
+ If the {id} argument is not specified or -1, |matchadd()|
+ automatically chooses a free ID, which is at least 1000.
+
+ The optional {dict} argument allows for further custom
+ values. Currently this is used to specify a match specific
+ conceal character that will be shown for |hl-Conceal|
+ highlighted matches. The dict can have the following members:
+
+ conceal Special character to show instead of the
+ match (only for |hl-Conceal| highlighted
+ matches, see |:syn-cchar|)
+ window Instead of the current window use the
+ window with this number or window ID.
+
+ The number of matches is not limited, as it is the case with
+ the |:match| commands.
+
+ Returns -1 on error.
+
+ Example: >vim
+ highlight MyGroup ctermbg=green guibg=green
+ let m = matchadd("MyGroup", "TODO")
+ <Deletion of the pattern: >vim
+ call matchdelete(m)
+
+ <A list of matches defined by |matchadd()| and |:match| are
+ available from |getmatches()|. All matches can be deleted in
+ one operation by |clearmatches()|.
+
+ ]=],
+ name = 'matchadd',
+ params = {
+ { 'group', 'any' },
+ { 'pattern', 'any' },
+ { 'priority', 'any' },
+ { 'id', 'any' },
+ { 'dict', 'any' },
+ },
+ signature = 'matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])',
+ tags = { 'E798', 'E799', 'E801', 'E957' },
+ },
+ matchaddpos = {
+ args = { 2, 5 },
+ base = 1,
+ desc = [=[
+ Same as |matchadd()|, but requires a list of positions {pos}
+ instead of a pattern. This command is faster than |matchadd()|
+ because it does not require to handle regular expressions and
+ sets buffer line boundaries to redraw screen. It is supposed
+ to be used when fast match additions and deletions are
+ required, for example to highlight matching parentheses.
+ *E5030* *E5031*
+ {pos} is a list of positions. Each position can be one of
+ these:
+ - A number. This whole line will be highlighted. The first
+ line has number 1.
+ - A list with one number, e.g., [23]. The whole line with this
+ number will be highlighted.
+ - A list with two numbers, e.g., [23, 11]. The first number is
+ the line number, the second one is the column number (first
+ column is 1, the value must correspond to the byte index as
+ |col()| would return). The character at this position will
+ be highlighted.
+ - A list with three numbers, e.g., [23, 11, 3]. As above, but
+ the third number gives the length of the highlight in bytes.
+
+ Entries with zero and negative line numbers are silently
+ ignored, as well as entries with negative column numbers and
+ lengths.
+
+ Returns -1 on error.
+
+ Example: >vim
+ highlight MyGroup ctermbg=green guibg=green
+ let m = matchaddpos("MyGroup", [[23, 24], 34])
+ <Deletion of the pattern: >vim
+ call matchdelete(m)
+
+ <Matches added by |matchaddpos()| are returned by
+ |getmatches()|.
+
+ ]=],
+ name = 'matchaddpos',
+ params = {
+ { 'group', 'any' },
+ { 'pos', 'any' },
+ { 'priority', 'any' },
+ { 'id', 'any' },
+ { 'dict', 'any' },
+ },
+ signature = 'matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]])',
+ },
+ matcharg = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Selects the {nr} match item, as set with a |:match|,
+ |:2match| or |:3match| command.
+ Return a |List| with two elements:
+ The name of the highlight group used
+ The pattern used.
+ When {nr} is not 1, 2 or 3 returns an empty |List|.
+ When there is no match item set returns ['', ''].
+ This is useful to save and restore a |:match|.
+ Highlighting matches using the |:match| commands are limited
+ to three matches. |matchadd()| does not have this limitation.
+
+ ]=],
+ name = 'matcharg',
+ params = { { 'nr', 'integer' } },
+ signature = 'matcharg({nr})',
+ },
+ matchdelete = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Deletes a match with ID {id} previously defined by |matchadd()|
+ or one of the |:match| commands. Returns 0 if successful,
+ otherwise -1. See example for |matchadd()|. All matches can
+ be deleted in one operation by |clearmatches()|.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window.
+
+ ]=],
+ name = 'matchdelete',
+ params = { { 'id', 'any' }, { 'win', 'any' } },
+ signature = 'matchdelete({id} [, {win}])',
+ tags = { 'E802', 'E803' },
+ },
+ matchend = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |match()|, but return the index of first character
+ after the match. Example: >vim
+ echo matchend("testing", "ing")
+ <results in "7".
+ *strspn()* *strcspn()*
+ Vim doesn't have a strspn() or strcspn() function, but you can
+ do it with matchend(): >vim
+ let span = matchend(line, '[a-zA-Z]')
+ let span = matchend(line, '[^a-zA-Z]')
+ <Except that -1 is returned when there are no matches.
+
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchend("testing", "ing", 2)
+ <results in "7". >vim
+ echo matchend("testing", "ing", 5)
+ <result is "-1".
+ When {expr} is a |List| the result is equal to |match()|.
+
+ ]=],
+ name = 'matchend',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchend({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchfuzzy = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ If {list} is a list of strings, then returns a |List| with all
+ the strings in {list} that fuzzy match {str}. The strings in
+ the returned list are sorted based on the matching score.
+
+ The optional {dict} argument always supports the following
+ items:
+ matchseq When this item is present return only matches
+ that contain the characters in {str} in the
+ given sequence.
+ limit Maximum number of matches in {list} to be
+ returned. Zero means no limit.
+
+ If {list} is a list of dictionaries, then the optional {dict}
+ argument supports the following additional items:
+ key Key of the item which is fuzzy matched against
+ {str}. The value of this item should be a
+ string.
+ text_cb |Funcref| that will be called for every item
+ in {list} to get the text for fuzzy matching.
+ This should accept a dictionary item as the
+ argument and return the text for that item to
+ use for fuzzy matching.
+
+ {str} is treated as a literal string and regular expression
+ matching is NOT supported. The maximum supported {str} length
+ is 256.
+
+ When {str} has multiple words each separated by white space,
+ then the list of strings that have all the words is returned.
+
+ If there are no matching strings or there is an error, then an
+ empty list is returned. If length of {str} is greater than
+ 256, then returns an empty list.
+
+ When {limit} is given, matchfuzzy() will find up to this
+ number of matches in {list} and return them in sorted order.
+
+ Refer to |fuzzy-matching| for more information about fuzzy
+ matching strings.
+
+ Example: >vim
+ echo matchfuzzy(["clay", "crow"], "cay")
+ <results in ["clay"]. >vim
+ echo getbufinfo()->map({_, v -> v.name})->matchfuzzy("ndl")
+ <results in a list of buffer names fuzzy matching "ndl". >vim
+ echo getbufinfo()->matchfuzzy("ndl", {'key' : 'name'})
+ <results in a list of buffer information dicts with buffer
+ names fuzzy matching "ndl". >vim
+ echo getbufinfo()->matchfuzzy("spl",
+ \ {'text_cb' : {v -> v.name}})
+ <results in a list of buffer information dicts with buffer
+ names fuzzy matching "spl". >vim
+ echo v:oldfiles->matchfuzzy("test")
+ <results in a list of file names fuzzy matching "test". >vim
+ let l = readfile("buffer.c")->matchfuzzy("str")
+ <results in a list of lines in "buffer.c" fuzzy matching "str". >vim
+ echo ['one two', 'two one']->matchfuzzy('two one')
+ <results in `['two one', 'one two']` . >vim
+ echo ['one two', 'two one']->matchfuzzy('two one',
+ \ {'matchseq': 1})
+ <results in `['two one']`.
+ ]=],
+ name = 'matchfuzzy',
+ params = { { 'list', 'any' }, { 'str', 'any' }, { 'dict', 'any' } },
+ signature = 'matchfuzzy({list}, {str} [, {dict}])',
+ },
+ matchfuzzypos = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Same as |matchfuzzy()|, but returns the list of matched
+ strings, the list of character positions where characters
+ in {str} matches and a list of matching scores. You can
+ use |byteidx()| to convert a character position to a byte
+ position.
+
+ If {str} matches multiple times in a string, then only the
+ positions for the best match is returned.
+
+ If there are no matching strings or there is an error, then a
+ list with three empty list items is returned.
+
+ Example: >vim
+ echo matchfuzzypos(['testing'], 'tsg')
+ <results in [["testing"], [[0, 2, 6]], [99]] >vim
+ echo matchfuzzypos(['clay', 'lacy'], 'la')
+ <results in [["lacy", "clay"], [[0, 1], [1, 2]], [153, 133]] >vim
+ echo [{'text': 'hello', 'id' : 10}]
+ \ ->matchfuzzypos('ll', {'key' : 'text'})
+ <results in `[[{"id": 10, "text": "hello"}], [[2, 3]], [127]]`
+ ]=],
+ name = 'matchfuzzypos',
+ params = { { 'list', 'any' }, { 'str', 'any' }, { 'dict', 'any' } },
+ signature = 'matchfuzzypos({list}, {str} [, {dict}])',
+ },
+ matchlist = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |match()|, but return a |List|. The first item in the
+ list is the matched string, same as what matchstr() would
+ return. Following items are submatches, like "\1", "\2", etc.
+ in |:substitute|. When an optional submatch didn't match an
+ empty string is used. Example: >vim
+ echo matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)')
+ <Results in: ['acd', 'a', '', 'c', 'd', '', '', '', '', '']
+ When there is no match an empty list is returned.
+
+ You can pass in a List, but that is not very useful.
+
+ ]=],
+ name = 'matchlist',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchlist({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchstr = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |match()|, but return the matched string. Example: >vim
+ echo matchstr("testing", "ing")
+ <results in "ing".
+ When there is no match "" is returned.
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchstr("testing", "ing", 2)
+ <results in "ing". >vim
+ echo matchstr("testing", "ing", 5)
+ <result is "".
+ When {expr} is a |List| then the matching item is returned.
+ The type isn't changed, it's not necessarily a String.
+
+ ]=],
+ name = 'matchstr',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchstr({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchstrpos = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |matchstr()|, but return the matched string, the start
+ position and the end position of the match. Example: >vim
+ echo matchstrpos("testing", "ing")
+ <results in ["ing", 4, 7].
+ When there is no match ["", -1, -1] is returned.
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchstrpos("testing", "ing", 2)
+ <results in ["ing", 4, 7]. >vim
+ echo matchstrpos("testing", "ing", 5)
+ <result is ["", -1, -1].
+ When {expr} is a |List| then the matching item, the index
+ of first item where {pat} matches, the start position and the
+ end position of the match are returned. >vim
+ echo matchstrpos([1, '__x'], '\a')
+ <result is ["x", 1, 2, 3].
+ The type isn't changed, it's not necessarily a String.
+
+ ]=],
+ name = 'matchstrpos',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchstrpos({expr}, {pat} [, {start} [, {count}]])',
+ },
+ max = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the maximum value of all items in {expr}. Example: >vim
+ echo max([apples, pears, oranges])
+
+ <{expr} can be a |List| or a |Dictionary|. For a Dictionary,
+ it returns the maximum of all values in the Dictionary.
+ If {expr} is neither a List nor a Dictionary, or one of the
+ items in {expr} cannot be used as a Number this results in
+ an error. An empty |List| or |Dictionary| results in zero.
+
+ ]=],
+ name = 'max',
+ params = { { 'expr', 'any' } },
+ signature = 'max({expr})',
+ },
+ menu_get = {
+ args = { 1, 2 },
+ desc = [=[
+ Returns a |List| of |Dictionaries| describing |menus| (defined
+ by |:menu|, |:amenu|, …), including |hidden-menus|.
+
+ {path} matches a menu by name, or all menus if {path} is an
+ empty string. Example: >vim
+ echo menu_get('File','')
+ echo menu_get('')
+ <
+ {modes} is a string of zero or more modes (see |maparg()| or
+ |creating-menus| for the list of modes). "a" means "all".
+
+ Example: >vim
+ nnoremenu &Test.Test inormal
+ inoremenu Test.Test insert
+ vnoremenu Test.Test x
+ echo menu_get("")
+
+ <returns something like this: >
+
+ [ {
+ "hidden": 0,
+ "name": "Test",
+ "priority": 500,
+ "shortcut": 84,
+ "submenus": [ {
+ "hidden": 0,
+ "mappings": {
+ i": {
+ "enabled": 1,
+ "noremap": 1,
+ "rhs": "insert",
+ "sid": 1,
+ "silent": 0
+ },
+ n": { ... },
+ s": { ... },
+ v": { ... }
+ },
+ "name": "Test",
+ "priority": 500,
+ "shortcut": 0
+ } ]
+ } ]
+ <
+ ]=],
+ name = 'menu_get',
+ params = { { 'path', 'string' }, { 'modes', 'any' } },
+ signature = 'menu_get({path} [, {modes}])',
+ },
+ menu_info = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return information about the specified menu {name} in
+ mode {mode}. The menu name should be specified without the
+ shortcut character ('&'). If {name} is "", then the top-level
+ menu names are returned.
+
+ {mode} can be one of these strings:
+ "n" Normal
+ "v" Visual (including Select)
+ "o" Operator-pending
+ "i" Insert
+ "c" Cmd-line
+ "s" Select
+ "x" Visual
+ "t" Terminal-Job
+ "" Normal, Visual and Operator-pending
+ "!" Insert and Cmd-line
+ When {mode} is omitted, the modes for "" are used.
+
+ Returns a |Dictionary| containing the following items:
+ accel menu item accelerator text |menu-text|
+ display display name (name without '&')
+ enabled v:true if this menu item is enabled
+ Refer to |:menu-enable|
+ icon name of the icon file (for toolbar)
+ |toolbar-icon|
+ iconidx index of a built-in icon
+ modes modes for which the menu is defined. In
+ addition to the modes mentioned above, these
+ characters will be used:
+ " " Normal, Visual and Operator-pending
+ name menu item name.
+ noremenu v:true if the {rhs} of the menu item is not
+ remappable else v:false.
+ priority menu order priority |menu-priority|
+ rhs right-hand-side of the menu item. The returned
+ string has special characters translated like
+ in the output of the ":menu" command listing.
+ When the {rhs} of a menu item is empty, then
+ "<Nop>" is returned.
+ script v:true if script-local remapping of {rhs} is
+ allowed else v:false. See |:menu-script|.
+ shortcut shortcut key (character after '&' in
+ the menu name) |menu-shortcut|
+ silent v:true if the menu item is created
+ with <silent> argument |:menu-silent|
+ submenus |List| containing the names of
+ all the submenus. Present only if the menu
+ item has submenus.
+
+ Returns an empty dictionary if the menu item is not found.
+
+ Examples: >vim
+ echo menu_info('Edit.Cut')
+ echo menu_info('File.Save', 'n')
+
+ " Display the entire menu hierarchy in a buffer
+ func ShowMenu(name, pfx)
+ let m = menu_info(a:name)
+ call append(line('$'), a:pfx .. m.display)
+ for child in m->get('submenus', [])
+ call ShowMenu(a:name .. '.' .. escape(child, '.'),
+ \ a:pfx .. ' ')
+ endfor
+ endfunc
+ new
+ for topmenu in menu_info('').submenus
+ call ShowMenu(topmenu, '')
+ endfor
+ <
+ ]=],
+ name = 'menu_info',
+ params = { { 'name', 'string' }, { 'mode', 'string' } },
+ signature = 'menu_info({name} [, {mode}])',
+ },
+ min = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the minimum value of all items in {expr}. Example: >vim
+ echo min([apples, pears, oranges])
+
+ <{expr} can be a |List| or a |Dictionary|. For a Dictionary,
+ it returns the minimum of all values in the Dictionary.
+ If {expr} is neither a List nor a Dictionary, or one of the
+ items in {expr} cannot be used as a Number this results in
+ an error. An empty |List| or |Dictionary| results in zero.
+
+ ]=],
+ name = 'min',
+ params = { { 'expr', 'any' } },
+ signature = 'min({expr})',
+ },
+ mkdir = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Create directory {name}.
+
+ When {flags} is present it must be a string. An empty string
+ has no effect.
+
+ If {flags} contains "p" then intermediate directories are
+ created as necessary.
+
+ If {flags} contains "D" then {name} is deleted at the end of
+ the current function, as with: >vim
+ defer delete({name}, 'd')
+ <
+ If {flags} contains "R" then {name} is deleted recursively at
+ the end of the current function, as with: >vim
+ defer delete({name}, 'rf')
+ <Note that when {name} has more than one part and "p" is used
+ some directories may already exist. Only the first one that
+ is created and what it contains is scheduled to be deleted.
+ E.g. when using: >vim
+ call mkdir('subdir/tmp/autoload', 'pR')
+ <and "subdir" already exists then "subdir/tmp" will be
+ scheduled for deletion, like with: >vim
+ defer delete('subdir/tmp', 'rf')
+ <
+ If {prot} is given it is used to set the protection bits of
+ the new directory. The default is 0o755 (rwxr-xr-x: r/w for
+ the user, readable for others). Use 0o700 to make it
+ unreadable for others.
+
+ {prot} is applied for all parts of {name}. Thus if you create
+ /tmp/foo/bar then /tmp/foo will be created with 0o700. Example: >vim
+ call mkdir($HOME .. "/tmp/foo/bar", "p", 0o700)
+
+ <This function is not available in the |sandbox|.
+
+ If you try to create an existing directory with {flags} set to
+ "p" mkdir() will silently exit.
+
+ The function result is a Number, which is TRUE if the call was
+ successful or FALSE if the directory creation failed or partly
+ failed.
+
+ ]=],
+ name = 'mkdir',
+ params = { { 'name', 'string' }, { 'flags', 'string' }, { 'prot', 'any' } },
+ signature = 'mkdir({name} [, {flags} [, {prot}]])',
+ tags = { 'E739' },
+ },
+ mode = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return a string that indicates the current mode.
+ If [expr] is supplied and it evaluates to a non-zero Number or
+ a non-empty String (|non-zero-arg|), then the full mode is
+ returned, otherwise only the first letter is returned.
+ Also see |state()|.
+
+ n Normal
+ no Operator-pending
+ nov Operator-pending (forced charwise |o_v|)
+ noV Operator-pending (forced linewise |o_V|)
+ noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|)
+ CTRL-V is one character
+ niI Normal using |i_CTRL-O| in |Insert-mode|
+ niR Normal using |i_CTRL-O| in |Replace-mode|
+ niV Normal using |i_CTRL-O| in |Virtual-Replace-mode|
+ nt Normal in |terminal-emulator| (insert goes to
+ Terminal mode)
+ ntT Normal using |t_CTRL-\_CTRL-O| in |Terminal-mode|
+ v Visual by character
+ vs Visual by character using |v_CTRL-O| in Select mode
+ V Visual by line
+ Vs Visual by line using |v_CTRL-O| in Select mode
+ CTRL-V Visual blockwise
+ CTRL-Vs Visual blockwise using |v_CTRL-O| in Select mode
+ s Select by character
+ S Select by line
+ CTRL-S Select blockwise
+ i Insert
+ ic Insert mode completion |compl-generic|
+ ix Insert mode |i_CTRL-X| completion
+ R Replace |R|
+ Rc Replace mode completion |compl-generic|
+ Rx Replace mode |i_CTRL-X| completion
+ Rv Virtual Replace |gR|
+ Rvc Virtual Replace mode completion |compl-generic|
+ Rvx Virtual Replace mode |i_CTRL-X| completion
+ c Command-line editing
+ cr Command-line editing overstrike mode |c_<Insert>|
+ cv Vim Ex mode |gQ|
+ cvr Vim Ex mode while in overstrike mode |c_<Insert>|
+ r Hit-enter prompt
+ rm The -- more -- prompt
+ r? A |:confirm| query of some sort
+ ! Shell or external command is executing
+ t Terminal mode: keys go to the job
+
+ This is useful in the 'statusline' option or RPC calls. In
+ most other places it always returns "c" or "n".
+ Note that in the future more modes and more specific modes may
+ be added. It's better not to compare the whole string but only
+ the leading character(s).
+ Also see |visualmode()|.
+
+ ]=],
+ name = 'mode',
+ params = {},
+ signature = 'mode([expr])',
+ },
+ msgpackdump = {
+ args = { 1, 2 },
+ desc = [=[
+ Convert a list of Vimscript objects to msgpack. Returned value is a
+ |readfile()|-style list. When {type} contains "B", a |Blob| is
+ returned instead. Example: >vim
+ call writefile(msgpackdump([{}]), 'fname.mpack', 'b')
+ <or, using a |Blob|: >vim
+ call writefile(msgpackdump([{}], 'B'), 'fname.mpack')
+ <
+ This will write the single 0x80 byte to a `fname.mpack` file
+ (dictionary with zero items is represented by 0x80 byte in
+ messagepack).
+
+ Limitations: *E5004* *E5005*
+ 1. |Funcref|s cannot be dumped.
+ 2. Containers that reference themselves cannot be dumped.
+ 3. Dictionary keys are always dumped as STR strings.
+ 4. Other strings and |Blob|s are always dumped as BIN strings.
+ 5. Points 3. and 4. do not apply to |msgpack-special-dict|s.
+ ]=],
+ name = 'msgpackdump',
+ params = { { 'list', 'any' }, { 'type', 'any' } },
+ signature = 'msgpackdump({list} [, {type}])',
+ },
+ msgpackparse = {
+ args = 1,
+ desc = [=[
+ Convert a |readfile()|-style list or a |Blob| to a list of
+ Vimscript objects.
+ Example: >vim
+ let fname = expand('~/.config/nvim/shada/main.shada')
+ let mpack = readfile(fname, 'b')
+ let shada_objects = msgpackparse(mpack)
+ <This will read ~/.config/nvim/shada/main.shada file to
+ `shada_objects` list.
+
+ Limitations:
+ 1. Mapping ordering is not preserved unless messagepack
+ mapping is dumped using generic mapping
+ (|msgpack-special-map|).
+ 2. Since the parser aims to preserve all data untouched
+ (except for 1.) some strings are parsed to
+ |msgpack-special-dict| format which is not convenient to
+ use.
+ *msgpack-special-dict*
+ Some messagepack strings may be parsed to special
+ dictionaries. Special dictionaries are dictionaries which
+
+ 1. Contain exactly two keys: `_TYPE` and `_VAL`.
+ 2. `_TYPE` key is one of the types found in |v:msgpack_types|
+ variable.
+ 3. Value for `_VAL` has the following format (Key column
+ contains name of the key from |v:msgpack_types|):
+
+ Key Value ~
+ nil Zero, ignored when dumping. Not returned by
+ |msgpackparse()| since |v:null| was introduced.
+ boolean One or zero. When dumping it is only checked that
+ value is a |Number|. Not returned by |msgpackparse()|
+ since |v:true| and |v:false| were introduced.
+ integer |List| with four numbers: sign (-1 or 1), highest two
+ bits, number with bits from 62nd to 31st, lowest 31
+ bits. I.e. to get actual number one will need to use
+ code like >
+ _VAL[0] * ((_VAL[1] << 62)
+ & (_VAL[2] << 31)
+ & _VAL[3])
+ < Special dictionary with this type will appear in
+ |msgpackparse()| output under one of the following
+ circumstances:
+ 1. |Number| is 32-bit and value is either above
+ INT32_MAX or below INT32_MIN.
+ 2. |Number| is 64-bit and value is above INT64_MAX. It
+ cannot possibly be below INT64_MIN because msgpack
+ C parser does not support such values.
+ float |Float|. This value cannot possibly appear in
+ |msgpackparse()| output.
+ string |readfile()|-style list of strings. This value will
+ appear in |msgpackparse()| output if string contains
+ zero byte or if string is a mapping key and mapping is
+ being represented as special dictionary for other
+ reasons.
+ binary |String|, or |Blob| if binary string contains zero
+ byte. This value cannot appear in |msgpackparse()|
+ output since blobs were introduced.
+ array |List|. This value cannot appear in |msgpackparse()|
+ output.
+ *msgpack-special-map*
+ map |List| of |List|s with two items (key and value) each.
+ This value will appear in |msgpackparse()| output if
+ parsed mapping contains one of the following keys:
+ 1. Any key that is not a string (including keys which
+ are binary strings).
+ 2. String with NUL byte inside.
+ 3. Duplicate key.
+ 4. Empty key.
+ ext |List| with two values: first is a signed integer
+ representing extension type. Second is
+ |readfile()|-style list of strings.
+ ]=],
+ name = 'msgpackparse',
+ params = { { 'data', 'any' } },
+ signature = 'msgpackparse({data})',
+ },
+ nextnonblank = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the line number of the first line at or below {lnum}
+ that is not blank. Example: >vim
+ if getline(nextnonblank(1)) =~ "Java" | endif
+ <When {lnum} is invalid or there is no non-blank line at or
+ below it, zero is returned.
+ {lnum} is used like with |getline()|.
+ See also |prevnonblank()|.
+
+ ]=],
+ name = 'nextnonblank',
+ params = { { 'lnum', 'integer' } },
+ signature = 'nextnonblank({lnum})',
+ },
+ nr2char = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return a string with a single character, which has the number
+ value {expr}. Examples: >vim
+ echo nr2char(64) " returns '@'
+ echo nr2char(32) " returns ' '
+ <Example for "utf-8": >vim
+ echo nr2char(300) " returns I with bow character
+ <
+ UTF-8 encoding is always used, {utf8} option has no effect,
+ and exists only for backwards-compatibility.
+ Note that a NUL character in the file is specified with
+ nr2char(10), because NULs are represented with newline
+ characters. nr2char(0) is a real NUL and terminates the
+ string, thus results in an empty string.
+
+ ]=],
+ name = 'nr2char',
+ params = { { 'expr', 'any' }, { 'utf8', 'any' } },
+ signature = 'nr2char({expr} [, {utf8}])',
+ },
+ nvim_api__ = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Call nvim |api| functions. The type checking of arguments will
+ be stricter than for most other builtins. For instance,
+ if Integer is expected, a |Number| must be passed in, a
+ |String| will not be autoconverted.
+ Buffer numbers, as returned by |bufnr()| could be used as
+ first argument to nvim_buf_... functions. All functions
+ expecting an object (buffer, window or tabpage) can
+ also take the numerical value 0 to indicate the current
+ (focused) object.
+ ]=],
+ lua = false,
+ name = 'nvim_...',
+ params = VARARGS,
+ signature = 'nvim_...({...})',
+ tags = { 'E5555', 'eval-api' },
+ },
+ ['or'] = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Bitwise OR on the two arguments. The arguments are converted
+ to a number. A List, Dict or Float argument causes an error.
+ Also see `and()` and `xor()`.
+ Example: >vim
+ let bits = or(bits, 0x80)
+
+ <Rationale: The reason this is a function and not using the "|"
+ character like many languages, is that Vi has always used "|"
+ to separate commands. In many places it would not be clear if
+ "|" is an operator or a command separator.
+ ]=],
+ name = 'or',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ signature = 'or({expr}, {expr})',
+ },
+ pathshorten = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Shorten directory names in the path {path} and return the
+ result. The tail, the file name, is kept as-is. The other
+ components in the path are reduced to {len} letters in length.
+ If {len} is omitted or smaller than 1 then 1 is used (single
+ letters). Leading '~' and '.' characters are kept. Examples: >vim
+ echo pathshorten('~/.config/nvim/autoload/file1.vim')
+ < ~/.c/n/a/file1.vim ~
+ >vim
+ echo pathshorten('~/.config/nvim/autoload/file2.vim', 2)
+ < ~/.co/nv/au/file2.vim ~
+ It doesn't matter if the path exists or not.
+ Returns an empty string on error.
+
+ ]=],
+ name = 'pathshorten',
+ params = { { 'path', 'string' }, { 'len', 'any' } },
+ signature = 'pathshorten({path} [, {len}])',
+ },
+ perleval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate |perl| expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers and strings are returned as they are (strings are
+ copied though).
+ Lists are represented as Vim |List| type.
+ Dictionaries are represented as Vim |Dictionary| type,
+ non-string keys result in error.
+
+ Note: If you want an array or hash, {expr} must return a
+ reference to it.
+ Example: >vim
+ echo perleval('[1 .. 4]')
+ < [1, 2, 3, 4]
+
+ ]=],
+ name = 'perleval',
+ params = { { 'expr', 'any' } },
+ signature = 'perleval({expr})',
+ },
+ pow = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Return the power of {x} to the exponent {y} as a |Float|.
+ {x} and {y} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {x} or {y} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo pow(3, 3)
+ < 27.0 >vim
+ echo pow(2, 16)
+ < 65536.0 >vim
+ echo pow(32, 0.20)
+ < 2.0
+
+ ]=],
+ name = 'pow',
+ params = { { 'x', 'any' }, { 'y', 'any' } },
+ signature = 'pow({x}, {y})',
+ },
+ prevnonblank = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the line number of the first line at or above {lnum}
+ that is not blank. Example: >vim
+ let ind = indent(prevnonblank(v:lnum - 1))
+ <When {lnum} is invalid or there is no non-blank line at or
+ above it, zero is returned.
+ {lnum} is used like with |getline()|.
+ Also see |nextnonblank()|.
+
+ ]=],
+ name = 'prevnonblank',
+ params = { { 'lnum', 'integer' } },
+ signature = 'prevnonblank({lnum})',
+ },
+ printf = {
+ args = { 1 },
+ base = 2,
+ desc = [=[
+ Return a String with {fmt}, where "%" items are replaced by
+ the formatted form of their respective arguments. Example: >vim
+ echo printf("%4d: E%d %.30s", lnum, errno, msg)
+ <May result in:
+ " 99: E42 asdfasdfasdfasdfasdfasdfasdfas" ~
+
+ When used as a |method| the base is passed as the second
+ argument: >vim
+ Compute()->printf("result: %d")
+ <
+ You can use `call()` to pass the items as a list.
+
+ Often used items are:
+ %s string
+ %6S string right-aligned in 6 display cells
+ %6s string right-aligned in 6 bytes
+ %.9s string truncated to 9 bytes
+ %c single byte
+ %d decimal number
+ %5d decimal number padded with spaces to 5 characters
+ %b binary number
+ %08b binary number padded with zeros to at least 8 characters
+ %B binary number using upper case letters
+ %x hex number
+ %04x hex number padded with zeros to at least 4 characters
+ %X hex number using upper case letters
+ %o octal number
+ %f floating point number as 12.23, inf, -inf or nan
+ %F floating point number as 12.23, INF, -INF or NAN
+ %e floating point number as 1.23e3, inf, -inf or nan
+ %E floating point number as 1.23E3, INF, -INF or NAN
+ %g floating point number, as %f or %e depending on value
+ %G floating point number, as %F or %E depending on value
+ %% the % character itself
+ %p representation of the pointer to the container
+
+ Conversion specifications start with '%' and end with the
+ conversion type. All other characters are copied unchanged to
+ the result.
+
+ The "%" starts a conversion specification. The following
+ arguments appear in sequence:
+
+ % [pos-argument] [flags] [field-width] [.precision] type
+
+ pos-argument
+ At most one positional argument specifier. These
+ take the form {n$}, where n is >= 1.
+
+ flags
+ Zero or more of the following flags:
+
+ # The value should be converted to an "alternate
+ form". For c, d, and s conversions, this option
+ has no effect. For o conversions, the precision
+ of the number is increased to force the first
+ character of the output string to a zero (except
+ if a zero value is printed with an explicit
+ precision of zero).
+ For x and X conversions, a non-zero result has
+ the string "0x" (or "0X" for X conversions)
+ prepended to it.
+
+ 0 (zero) Zero padding. For all conversions the converted
+ value is padded on the left with zeros rather
+ than blanks. If a precision is given with a
+ numeric conversion (d, o, x, and X), the 0 flag
+ is ignored.
+
+ - A negative field width flag; the converted value
+ is to be left adjusted on the field boundary.
+ The converted value is padded on the right with
+ blanks, rather than on the left with blanks or
+ zeros. A - overrides a 0 if both are given.
+
+ ' ' (space) A blank should be left before a positive
+ number produced by a signed conversion (d).
+
+ + A sign must always be placed before a number
+ produced by a signed conversion. A + overrides
+ a space if both are used.
+
+ field-width
+ An optional decimal digit string specifying a minimum
+ field width. If the converted value has fewer bytes
+ than the field width, it will be padded with spaces on
+ the left (or right, if the left-adjustment flag has
+ been given) to fill out the field width. For the S
+ conversion the count is in cells.
+
+ .precision
+ An optional precision, in the form of a period '.'
+ followed by an optional digit string. If the digit
+ string is omitted, the precision is taken as zero.
+ This gives the minimum number of digits to appear for
+ d, o, x, and X conversions, the maximum number of
+ bytes to be printed from a string for s conversions,
+ or the maximum number of cells to be printed from a
+ string for S conversions.
+ For floating point it is the number of digits after
+ the decimal point.
+
+ type
+ A character that specifies the type of conversion to
+ be applied, see below.
+
+ A field width or precision, or both, may be indicated by an
+ asterisk "*" instead of a digit string. In this case, a
+ Number argument supplies the field width or precision. A
+ negative field width is treated as a left adjustment flag
+ followed by a positive field width; a negative precision is
+ treated as though it were missing. Example: >vim
+ echo printf("%d: %.*s", nr, width, line)
+ <This limits the length of the text used from "line" to
+ "width" bytes.
+
+ If the argument to be formatted is specified using a posional
+ argument specifier, and a '*' is used to indicate that a
+ number argument is to be used to specify the width or
+ precision, the argument(s) to be used must also be specified
+ using a {n$} positional argument specifier. See |printf-$|.
+
+ The conversion specifiers and their meanings are:
+
+ *printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X*
+ dbBoxX The Number argument is converted to signed decimal (d),
+ unsigned binary (b and B), unsigned octal (o), or
+ unsigned hexadecimal (x and X) notation. The letters
+ "abcdef" are used for x conversions; the letters
+ "ABCDEF" are used for X conversions. The precision, if
+ any, gives the minimum number of digits that must
+ appear; if the converted value requires fewer digits, it
+ is padded on the left with zeros. In no case does a
+ non-existent or small field width cause truncation of a
+ numeric field; if the result of a conversion is wider
+ than the field width, the field is expanded to contain
+ the conversion result.
+ The 'h' modifier indicates the argument is 16 bits.
+ The 'l' modifier indicates the argument is a long
+ integer. The size will be 32 bits or 64 bits
+ depending on your platform.
+ The "ll" modifier indicates the argument is 64 bits.
+ The b and B conversion specifiers never take a width
+ modifier and always assume their argument is a 64 bit
+ integer.
+ Generally, these modifiers are not useful. They are
+ ignored when type is known from the argument.
+
+ i alias for d
+ D alias for ld
+ U alias for lu
+ O alias for lo
+
+ *printf-c*
+ c The Number argument is converted to a byte, and the
+ resulting character is written.
+
+ *printf-s*
+ s The text of the String argument is used. If a
+ precision is specified, no more bytes than the number
+ specified are used.
+ If the argument is not a String type, it is
+ automatically converted to text with the same format
+ as ":echo".
+ *printf-S*
+ S The text of the String argument is used. If a
+ precision is specified, no more display cells than the
+ number specified are used.
+
+ *printf-f* *E807*
+ f F The Float argument is converted into a string of the
+ form 123.456. The precision specifies the number of
+ digits after the decimal point. When the precision is
+ zero the decimal point is omitted. When the precision
+ is not specified 6 is used. A really big number
+ (out of range or dividing by zero) results in "inf"
+ or "-inf" with %f (INF or -INF with %F).
+ "0.0 / 0.0" results in "nan" with %f (NAN with %F).
+ Example: >vim
+ echo printf("%.2f", 12.115)
+ < 12.12
+ Note that roundoff depends on the system libraries.
+ Use |round()| when in doubt.
+
+ *printf-e* *printf-E*
+ e E The Float argument is converted into a string of the
+ form 1.234e+03 or 1.234E+03 when using 'E'. The
+ precision specifies the number of digits after the
+ decimal point, like with 'f'.
+
+ *printf-g* *printf-G*
+ g G The Float argument is converted like with 'f' if the
+ value is between 0.001 (inclusive) and 10000000.0
+ (exclusive). Otherwise 'e' is used for 'g' and 'E'
+ for 'G'. When no precision is specified superfluous
+ zeroes and '+' signs are removed, except for the zero
+ immediately after the decimal point. Thus 10000000.0
+ results in 1.0e7.
+
+ *printf-%*
+ % A '%' is written. No argument is converted. The
+ complete conversion specification is "%%".
+
+ When a Number argument is expected a String argument is also
+ accepted and automatically converted.
+ When a Float or String argument is expected a Number argument
+ is also accepted and automatically converted.
+ Any other argument type results in an error message.
+
+ *E766* *E767*
+ The number of {exprN} arguments must exactly match the number
+ of "%" items. If there are not sufficient or too many
+ arguments an error is given. Up to 18 arguments can be used.
+
+ *printf-$*
+ In certain languages, error and informative messages are
+ more readable when the order of words is different from the
+ corresponding message in English. To accommodate translations
+ having a different word order, positional arguments may be
+ used to indicate this. For instance: >vim
+
+ #, c-format
+ msgid "%s returning %s"
+ msgstr "waarde %2$s komt terug van %1$s"
+ <
+ In this example, the sentence has its 2 string arguments
+ reversed in the output. >vim
+
+ echo printf(
+ "In The Netherlands, vim's creator's name is: %1$s %2$s",
+ "Bram", "Moolenaar")
+ < In The Netherlands, vim's creator's name is: Bram Moolenaar >vim
+
+ echo printf(
+ "In Belgium, vim's creator's name is: %2$s %1$s",
+ "Bram", "Moolenaar")
+ < In Belgium, vim's creator's name is: Moolenaar Bram
+
+ Width (and precision) can be specified using the '*' specifier.
+ In this case, you must specify the field width position in the
+ argument list. >vim
+
+ echo printf("%1$*2$.*3$d", 1, 2, 3)
+ < 001 >vim
+ echo printf("%2$*3$.*1$d", 1, 2, 3)
+ < 2 >vim
+ echo printf("%3$*1$.*2$d", 1, 2, 3)
+ < 03 >vim
+ echo printf("%1$*2$.*3$g", 1.4142, 2, 3)
+ < 1.414
+
+ You can mix specifying the width and/or precision directly
+ and via positional arguments: >vim
+
+ echo printf("%1$4.*2$f", 1.4142135, 6)
+ < 1.414214 >vim
+ echo printf("%1$*2$.4f", 1.4142135, 6)
+ < 1.4142 >vim
+ echo printf("%1$*2$.*3$f", 1.4142135, 6, 2)
+ < 1.41
+
+ *E1500*
+ You cannot mix positional and non-positional arguments: >vim
+ echo printf("%s%1$s", "One", "Two")
+ < E1500: Cannot mix positional and non-positional arguments:
+ %s%1$s
+
+ *E1501*
+ You cannot skip a positional argument in a format string: >vim
+ echo printf("%3$s%1$s", "One", "Two", "Three")
+ < E1501: format argument 2 unused in $-style format:
+ %3$s%1$s
+
+ *E1502*
+ You can re-use a [field-width] (or [precision]) argument: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+ < 1 at width 2 is: 01
+
+ However, you can't use it as a different type: >vim
+ echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+ < E1502: Positional argument 2 used as field width reused as
+ different type: long int/int
+
+ *E1503*
+ When a positional argument is used, but not the correct number
+ or arguments is given, an error is raised: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+ < E1503: Positional argument 3 out of bounds: %1$d at width
+ %2$d is: %01$*2$.*3$d
+
+ Only the first error is reported: >vim
+ echo printf("%01$*2$.*3$d %4$d", 1, 2)
+ < E1503: Positional argument 3 out of bounds: %01$*2$.*3$d
+ %4$d
+
+ *E1504*
+ A positional argument can be used more than once: >vim
+ echo printf("%1$s %2$s %1$s", "One", "Two")
+ < One Two One
+
+ However, you can't use a different type the second time: >vim
+ echo printf("%1$s %2$s %1$d", "One", "Two")
+ < E1504: Positional argument 1 type used inconsistently:
+ int/string
+
+ *E1505*
+ Various other errors that lead to a format string being
+ wrongly formatted lead to: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$.3$d", 1, 2)
+ < E1505: Invalid format specifier: %1$d at width %2$d is:
+ %01$*2$.3$d
+
+ *E1507*
+ This internal error indicates that the logic to parse a
+ positional format argument ran into a problem that couldn't be
+ otherwise reported. Please file a bug against Vim if you run
+ into this, copying the exact format string and parameters that
+ were used.
+
+ ]=],
+ name = 'printf',
+ params = { { 'fmt', 'any' }, { 'expr1', 'any' } },
+ signature = 'printf({fmt}, {expr1} ...)',
+ },
+ prompt_getprompt = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the effective prompt text for buffer {buf}. {buf} can
+ be a buffer name or number. See |prompt-buffer|.
+
+ If the buffer doesn't exist or isn't a prompt buffer, an empty
+ string is returned.
+
+ ]=],
+ name = 'prompt_getprompt',
+ params = { { 'buf', 'any' } },
+ signature = 'prompt_getprompt({buf})',
+ },
+ prompt_setcallback = {
+ args = { 2, 2 },
+ base = 1,
+ desc = [=[
+ Set prompt callback for buffer {buf} to {expr}. When {expr}
+ is an empty string the callback is removed. This has only
+ effect if {buf} has 'buftype' set to "prompt".
+
+ The callback is invoked when pressing Enter. The current
+ buffer will always be the prompt buffer. A new line for a
+ prompt is added before invoking the callback, thus the prompt
+ for which the callback was invoked will be in the last but one
+ line.
+ If the callback wants to add text to the buffer, it must
+ insert it above the last line, since that is where the current
+ prompt is. This can also be done asynchronously.
+ The callback is invoked with one argument, which is the text
+ that was entered at the prompt. This can be an empty string
+ if the user only typed Enter.
+ Example: >vim
+ func s:TextEntered(text)
+ if a:text == 'exit' || a:text == 'quit'
+ stopinsert
+ " Reset 'modified' to allow the buffer to be closed.
+ " We assume there is nothing useful to be saved.
+ set nomodified
+ close
+ else
+ " Do something useful with "a:text". In this example
+ " we just repeat it.
+ call append(line('$') - 1, 'Entered: "' .. a:text .. '"')
+ endif
+ endfunc
+ call prompt_setcallback(bufnr(), function('s:TextEntered'))
+
+ ]=],
+ name = 'prompt_setcallback',
+ params = { { 'buf', 'any' }, { 'expr', 'any' } },
+ signature = 'prompt_setcallback({buf}, {expr})',
+ },
+ prompt_setinterrupt = {
+ args = { 2, 2 },
+ base = 1,
+ desc = [=[
+ Set a callback for buffer {buf} to {expr}. When {expr} is an
+ empty string the callback is removed. This has only effect if
+ {buf} has 'buftype' set to "prompt".
+
+ This callback will be invoked when pressing CTRL-C in Insert
+ mode. Without setting a callback Vim will exit Insert mode,
+ as in any buffer.
+
+ ]=],
+ name = 'prompt_setinterrupt',
+ params = { { 'buf', 'any' }, { 'expr', 'any' } },
+ signature = 'prompt_setinterrupt({buf}, {expr})',
+ },
+ prompt_setprompt = {
+ args = { 2, 2 },
+ base = 1,
+ desc = [=[
+ Set prompt for buffer {buf} to {text}. You most likely want
+ {text} to end in a space.
+ The result is only visible if {buf} has 'buftype' set to
+ "prompt". Example: >vim
+ call prompt_setprompt(bufnr(''), 'command: ')
+ <
+ ]=],
+ name = 'prompt_setprompt',
+ params = { { 'buf', 'any' }, { 'text', 'any' } },
+ signature = 'prompt_setprompt({buf}, {text})',
+ },
+ pum_getpos = {
+ desc = [=[
+ If the popup menu (see |ins-completion-menu|) is not visible,
+ returns an empty |Dictionary|, otherwise, returns a
+ |Dictionary| with the following keys:
+ height nr of items visible
+ width screen cells
+ row top screen row (0 first row)
+ col leftmost screen column (0 first col)
+ size total nr of items
+ scrollbar |TRUE| if scrollbar is visible
+
+ The values are the same as in |v:event| during |CompleteChanged|.
+ ]=],
+ name = 'pum_getpos',
+ params = {},
+ signature = 'pum_getpos()',
+ },
+ pumvisible = {
+ desc = [=[
+ Returns non-zero when the popup menu is visible, zero
+ otherwise. See |ins-completion-menu|.
+ This can be used to avoid some things that would remove the
+ popup menu.
+ ]=],
+ name = 'pumvisible',
+ params = {},
+ signature = 'pumvisible()',
+ },
+ py3eval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Python expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers and strings are returned as they are (strings are
+ copied though, Unicode strings are additionally converted to
+ UTF-8).
+ Lists are represented as Vim |List| type.
+ Dictionaries are represented as Vim |Dictionary| type with
+ keys converted to strings.
+
+ ]=],
+ name = 'py3eval',
+ params = { { 'expr', 'any' } },
+ signature = 'py3eval({expr})',
+ },
+ pyeval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Python expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers and strings are returned as they are (strings are
+ copied though).
+ Lists are represented as Vim |List| type.
+ Dictionaries are represented as Vim |Dictionary| type,
+ non-string keys result in error.
+
+ ]=],
+ func = 'f_py3eval',
+ name = 'pyeval',
+ params = { { 'expr', 'any' } },
+ signature = 'pyeval({expr})',
+ tags = { 'E858', 'E859' },
+ },
+ pyxeval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Python expression {expr} and return its result
+ converted to Vim data structures.
+ Uses Python 2 or 3, see |python_x| and 'pyxversion'.
+ See also: |pyeval()|, |py3eval()|
+
+ ]=],
+ func = 'f_py3eval',
+ name = 'pyxeval',
+ params = { { 'expr', 'any' } },
+ signature = 'pyxeval({expr})',
+ },
+ rand = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return a pseudo-random Number generated with an xoshiro128**
+ algorithm using seed {expr}. The returned number is 32 bits,
+ also on 64 bits systems, for consistency.
+ {expr} can be initialized by |srand()| and will be updated by
+ rand(). If {expr} is omitted, an internal seed value is used
+ and updated.
+ Returns -1 if {expr} is invalid.
+
+ Examples: >vim
+ echo rand()
+ let seed = srand()
+ echo rand(seed)
+ echo rand(seed) % 16 " random number 0 - 15
+ <
+ ]=],
+ name = 'rand',
+ params = { { 'expr', 'any' } },
+ signature = 'rand([{expr}])',
+ },
+ range = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Returns a |List| with Numbers:
+ - If only {expr} is specified: [0, 1, ..., {expr} - 1]
+ - If {max} is specified: [{expr}, {expr} + 1, ..., {max}]
+ - If {stride} is specified: [{expr}, {expr} + {stride}, ...,
+ {max}] (increasing {expr} with {stride} each time, not
+ producing a value past {max}).
+ When the maximum is one before the start the result is an
+ empty list. When the maximum is more than one before the
+ start this is an error.
+ Examples: >vim
+ echo range(4) " [0, 1, 2, 3]
+ echo range(2, 4) " [2, 3, 4]
+ echo range(2, 9, 3) " [2, 5, 8]
+ echo range(2, -2, -1) " [2, 1, 0, -1, -2]
+ echo range(0) " []
+ echo range(2, 0) " error!
+ <
+ ]=],
+ name = 'range',
+ params = { { 'expr', 'any' }, { 'max', 'any' }, { 'stride', 'any' } },
+ signature = 'range({expr} [, {max} [, {stride}]])',
+ tags = { 'E726', 'E727' },
+ },
+ readblob = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Read file {fname} in binary mode and return a |Blob|.
+ If {offset} is specified, read the file from the specified
+ offset. If it is a negative value, it is used as an offset
+ from the end of the file. E.g., to read the last 12 bytes: >vim
+ echo readblob('file.bin', -12)
+ <If {size} is specified, only the specified size will be read.
+ E.g. to read the first 100 bytes of a file: >vim
+ echo readblob('file.bin', 0, 100)
+ <If {size} is -1 or omitted, the whole data starting from
+ {offset} will be read.
+ This can be also used to read the data from a character device
+ on Unix when {size} is explicitly set. Only if the device
+ supports seeking {offset} can be used. Otherwise it should be
+ zero. E.g. to read 10 bytes from a serial console: >vim
+ echo readblob('/dev/ttyS0', 0, 10)
+ <When the file can't be opened an error message is given and
+ the result is an empty |Blob|.
+ When the offset is beyond the end of the file the result is an
+ empty blob.
+ When trying to read more bytes than are available the result
+ is truncated.
+ Also see |readfile()| and |writefile()|.
+ ]=],
+ name = 'readblob',
+ params = { { 'fname', 'string' }, { 'offset', 'any' }, { 'size', 'any' } },
+ signature = 'readblob({fname} [, {offset} [, {size}]])',
+ },
+ readdir = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return a list with file and directory names in {directory}.
+ You can also use |glob()| if you don't need to do complicated
+ things, such as limiting the number of matches.
+
+ When {expr} is omitted all entries are included.
+ When {expr} is given, it is evaluated to check what to do:
+ If {expr} results in -1 then no further entries will
+ be handled.
+ If {expr} results in 0 then this entry will not be
+ added to the list.
+ If {expr} results in 1 then this entry will be added
+ to the list.
+ Each time {expr} is evaluated |v:val| is set to the entry name.
+ When {expr} is a function the name is passed as the argument.
+ For example, to get a list of files ending in ".txt": >vim
+ echo readdir(dirname, {n -> n =~ '.txt$'})
+ <To skip hidden and backup files: >vim
+ echo readdir(dirname, {n -> n !~ '^\.\|\~$'})
+
+ <If you want to get a directory tree: >vim
+ function! s:tree(dir)
+ return {a:dir : map(readdir(a:dir),
+ \ {_, x -> isdirectory(x) ?
+ \ {x : s:tree(a:dir .. '/' .. x)} : x})}
+ endfunction
+ echo s:tree(".")
+ <
+ Returns an empty List on error.
+
+ ]=],
+ name = 'readdir',
+ params = { { 'directory', 'any' }, { 'expr', 'any' } },
+ signature = 'readdir({directory} [, {expr}])',
+ },
+ readfile = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Read file {fname} and return a |List|, each line of the file
+ as an item. Lines are broken at NL characters. Macintosh
+ files separated with CR will result in a single long line
+ (unless a NL appears somewhere).
+ All NUL characters are replaced with a NL character.
+ When {type} contains "b" binary mode is used:
+ - When the last line ends in a NL an extra empty list item is
+ added.
+ - No CR characters are removed.
+ Otherwise:
+ - CR characters that appear before a NL are removed.
+ - Whether the last line ends in a NL or not does not matter.
+ - Any UTF-8 byte order mark is removed from the text.
+ When {max} is given this specifies the maximum number of lines
+ to be read. Useful if you only want to check the first ten
+ lines of a file: >vim
+ for line in readfile(fname, '', 10)
+ if line =~ 'Date' | echo line | endif
+ endfor
+ <When {max} is negative -{max} lines from the end of the file
+ are returned, or as many as there are.
+ When {max} is zero the result is an empty list.
+ Note that without {max} the whole file is read into memory.
+ Also note that there is no recognition of encoding. Read a
+ file into a buffer if you need to.
+ Deprecated (use |readblob()| instead): When {type} contains
+ "B" a |Blob| is returned with the binary data of the file
+ unmodified.
+ When the file can't be opened an error message is given and
+ the result is an empty list.
+ Also see |writefile()|.
+
+ ]=],
+ name = 'readfile',
+ params = { { 'fname', 'string' }, { 'type', 'any' }, { 'max', 'any' } },
+ signature = 'readfile({fname} [, {type} [, {max}]])',
+ },
+ reduce = {
+ args = { 2, 3 },
+ base = 1,
+ tags = { 'E998' },
+ desc = [=[
+ {func} is called for every item in {object}, which can be a
+ |String|, |List| or a |Blob|. {func} is called with two
+ arguments: the result so far and current item. After
+ processing all items the result is returned.
+
+ {initial} is the initial result. When omitted, the first item
+ in {object} is used and {func} is first called for the second
+ item. If {initial} is not given and {object} is empty no
+ result can be computed, an E998 error is given.
+
+ Examples: >vim
+ echo reduce([1, 3, 5], { acc, val -> acc + val })
+ echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
+ echo reduce(0z1122, { acc, val -> 2 * acc + val })
+ echo reduce('xyz', { acc, val -> acc .. ',' .. val })
+ <
+ ]=],
+ name = 'reduce',
+ params = { { 'object', 'any' }, { 'func', 'any' }, { 'initial', 'any' } },
+ signature = 'reduce({object}, {func} [, {initial}])',
+ },
+ reg_executing = {
+ desc = [=[
+ Returns the single letter name of the register being executed.
+ Returns an empty string when no register is being executed.
+ See |@|.
+ ]=],
+ name = 'reg_executing',
+ params = {},
+ signature = 'reg_executing()',
+ },
+ reg_recorded = {
+ desc = [=[
+ Returns the single letter name of the last recorded register.
+ Returns an empty string when nothing was recorded yet.
+ See |q| and |Q|.
+ ]=],
+ name = 'reg_recorded',
+ params = {},
+ signature = 'reg_recorded()',
+ },
+ reg_recording = {
+ desc = [=[
+ Returns the single letter name of the register being recorded.
+ Returns an empty string when not recording. See |q|.
+ ]=],
+ name = 'reg_recording',
+ params = {},
+ signature = 'reg_recording()',
+ },
+ reltime = {
+ args = { 0, 2 },
+ base = 1,
+ fast = true,
+ name = 'reltime',
+ params = {},
+ signature = 'reltime()',
+ },
+ reltime__1 = {
+ args = { 0, 2 },
+ base = 1,
+ fast = true,
+ name = 'reltime',
+ params = { { 'start', 'any' } },
+ signature = 'reltime({start})',
+ },
+ reltime__2 = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Return an item that represents a time value. The item is a
+ list with items that depend on the system.
+ The item can be passed to |reltimestr()| to convert it to a
+ string or |reltimefloat()| to convert to a Float.
+
+ Without an argument it returns the current "relative time", an
+ implementation-defined value meaningful only when used as an
+ argument to |reltime()|, |reltimestr()| and |reltimefloat()|.
+
+ With one argument it returns the time passed since the time
+ specified in the argument.
+ With two arguments it returns the time passed between {start}
+ and {end}.
+
+ The {start} and {end} arguments must be values returned by
+ reltime(). Returns zero on error.
+
+ Note: |localtime()| returns the current (non-relative) time.
+ ]=],
+ fast = true,
+ name = 'reltime',
+ params = { { 'start', 'any' }, { 'end', 'any' } },
+ signature = 'reltime({start}, {end})',
+ },
+ reltimefloat = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a Float that represents the time value of {time}.
+ Unit of time is seconds.
+ Example:
+ let start = reltime()
+ call MyFunction()
+ let seconds = reltimefloat(reltime(start))
+ See the note of reltimestr() about overhead.
+ Also see |profiling|.
+ If there is an error an empty string is returned
+
+ ]=],
+ fast = true,
+ name = 'reltimefloat',
+ params = { { 'time', 'any' } },
+ signature = 'reltimefloat({time})',
+ },
+ reltimestr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a String that represents the time value of {time}.
+ This is the number of seconds, a dot and the number of
+ microseconds. Example: >vim
+ let start = reltime()
+ call MyFunction()
+ echo reltimestr(reltime(start))
+ <Note that overhead for the commands will be added to the time.
+ Leading spaces are used to make the string align nicely. You
+ can use split() to remove it. >vim
+ echo split(reltimestr(reltime(start)))[0]
+ <Also see |profiling|.
+ If there is an error an empty string is returned
+
+ ]=],
+ fast = true,
+ name = 'reltimestr',
+ params = { { 'time', 'any' } },
+ signature = 'reltimestr({time})',
+ },
+ remove = {
+ args = { 2, 3 },
+ base = 1,
+ name = 'remove',
+ params = { { 'list', 'any' }, { 'idx', 'integer' } },
+ signature = 'remove({list}, {idx})',
+ },
+ remove__1 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Without {end}: Remove the item at {idx} from |List| {list} and
+ return the item.
+ With {end}: Remove items from {idx} to {end} (inclusive) and
+ return a |List| with these items. When {idx} points to the same
+ item as {end} a list with one item is returned. When {end}
+ points to an item before {idx} this is an error.
+ See |list-index| for possible values of {idx} and {end}.
+ Returns zero on error.
+ Example: >vim
+ echo "last item: " .. remove(mylist, -1)
+ call remove(mylist, 0, 9)
+ <
+ Use |delete()| to remove a file.
+
+ ]=],
+ name = 'remove',
+ params = { { 'list', 'any' }, { 'idx', 'integer' }, { 'end', 'any' } },
+ signature = 'remove({list}, {idx}, {end})',
+ },
+ remove__2 = {
+ args = { 2, 3 },
+ base = 1,
+ name = 'remove',
+ params = { { 'blob', 'any' }, { 'idx', 'integer' } },
+ signature = 'remove({blob}, {idx})',
+ },
+ remove__3 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Without {end}: Remove the byte at {idx} from |Blob| {blob} and
+ return the byte.
+ With {end}: Remove bytes from {idx} to {end} (inclusive) and
+ return a |Blob| with these bytes. When {idx} points to the same
+ byte as {end} a |Blob| with one byte is returned. When {end}
+ points to a byte before {idx} this is an error.
+ Returns zero on error.
+ Example: >vim
+ echo "last byte: " .. remove(myblob, -1)
+ call remove(mylist, 0, 9)
+ <
+ ]=],
+ name = 'remove',
+ params = { { 'blob', 'any' }, { 'idx', 'integer' }, { 'end', 'any' } },
+ signature = 'remove({blob}, {idx}, {end})',
+ },
+ remove__4 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Remove the entry from {dict} with key {key} and return it.
+ Example: >vim
+ echo "removed " .. remove(dict, "one")
+ <If there is no {key} in {dict} this is an error.
+ Returns zero on error.
+ ]=],
+ name = 'remove',
+ params = { { 'dict', 'any' }, { 'key', 'any' } },
+ signature = 'remove({dict}, {key})',
+ },
+ rename = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Rename the file by the name {from} to the name {to}. This
+ should also work to move files across file systems. The
+ result is a Number, which is 0 if the file was renamed
+ successfully, and non-zero when the renaming failed.
+ NOTE: If {to} exists it is overwritten without warning.
+ This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'rename',
+ params = { { 'from', 'any' }, { 'to', 'any' } },
+ signature = 'rename({from}, {to})',
+ },
+ ['repeat'] = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Repeat {expr} {count} times and return the concatenated
+ result. Example: >vim
+ let separator = repeat('-', 80)
+ <When {count} is zero or negative the result is empty.
+ When {expr} is a |List| or a |Blob| the result is {expr}
+ concatenated {count} times. Example: >vim
+ let longlist = repeat(['a', 'b'], 3)
+ <Results in ['a', 'b', 'a', 'b', 'a', 'b'].
+
+ ]=],
+ fast = true,
+ name = 'repeat',
+ params = { { 'expr', 'any' }, { 'count', 'any' } },
+ signature = 'repeat({expr}, {count})',
+ },
+ resolve = {
+ args = 1,
+ base = 1,
+ tags = { 'E655' },
+ desc = [=[
+ On MS-Windows, when {filename} is a shortcut (a .lnk file),
+ returns the path the shortcut points to in a simplified form.
+ On Unix, repeat resolving symbolic links in all path
+ components of {filename} and return the simplified result.
+ To cope with link cycles, resolving of symbolic links is
+ stopped after 100 iterations.
+ On other systems, return the simplified {filename}.
+ The simplification step is done as by |simplify()|.
+ resolve() keeps a leading path component specifying the
+ current directory (provided the result is still a relative
+ path name) and also keeps a trailing path separator.
+
+ ]=],
+ fast = true,
+ name = 'resolve',
+ params = { { 'filename', 'any' } },
+ signature = 'resolve({filename})',
+ },
+ reverse = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Reverse the order of items in {object}. {object} can be a
+ |List|, a |Blob| or a |String|. For a List and a Blob the
+ items are reversed in-place and {object} is returned.
+ For a String a new String is returned.
+ Returns zero if {object} is not a List, Blob or a String.
+ If you want a List or Blob to remain unmodified make a copy
+ first: >vim
+ let revlist = reverse(copy(mylist))
+ <
+ ]=],
+ name = 'reverse',
+ params = { { 'object', 'any' } },
+ signature = 'reverse({object})',
+ },
+ round = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Round off {expr} to the nearest integral value and return it
+ as a |Float|. If {expr} lies halfway between two integral
+ values, then use the larger one (away from zero).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo round(0.456)
+ < 0.0 >vim
+ echo round(4.5)
+ < 5.0 >vim
+ echo round(-4.5)
+ < -5.0
+
+ ]=],
+ float_func = 'round',
+ name = 'round',
+ params = { { 'expr', 'any' } },
+ signature = 'round({expr})',
+ },
+ rpcnotify = {
+ args = { 2 },
+ desc = [=[
+ Sends {event} to {channel} via |RPC| and returns immediately.
+ If {channel} is 0, the event is broadcast to all channels.
+ Example: >vim
+ au VimLeave call rpcnotify(0, "leaving")
+ <
+ ]=],
+ name = 'rpcnotify',
+ params = { { 'channel', 'any' }, { 'event', 'any' }, { 'args', 'any' } },
+ signature = 'rpcnotify({channel}, {event} [, {args}...])',
+ },
+ rpcrequest = {
+ args = { 2 },
+ desc = [=[
+ Sends a request to {channel} to invoke {method} via
+ |RPC| and blocks until a response is received.
+ Example: >vim
+ let result = rpcrequest(rpc_chan, "func", 1, 2, 3)
+ <
+ ]=],
+ name = 'rpcrequest',
+ params = { { 'channel', 'any' }, { 'method', 'any' }, { 'args', 'any' } },
+ signature = 'rpcrequest({channel}, {method} [, {args}...])',
+ },
+ rpcstart = {
+ args = { 1, 2 },
+ desc = [=[
+ Deprecated. Replace >vim
+ let id = rpcstart('prog', ['arg1', 'arg2'])
+ <with >vim
+ let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true})
+ <
+ ]=],
+ name = 'rpcstart',
+ params = { { 'prog', 'any' }, { 'argv', 'any' } },
+ signature = 'rpcstart({prog} [, {argv}])',
+ },
+ rpcstop = {
+ args = 1,
+ deprecated = true,
+ desc = [=[
+ Use |jobstop()| instead to stop any job, or
+ `chanclose(id, "rpc")` to close RPC communication
+ without stopping the job. Use chanclose(id) to close
+ any socket.
+ ]=],
+ params = VARARGS,
+ signature = 'rpcstop(...)',
+ },
+ rubyeval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Ruby expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers, floats and strings are returned as they are (strings
+ are copied though).
+ Arrays are represented as Vim |List| type.
+ Hashes are represented as Vim |Dictionary| type.
+ Other objects are represented as strings resulted from their
+ "Object#to_s" method.
+
+ ]=],
+ name = 'rubyeval',
+ params = { { 'expr', 'any' } },
+ signature = 'rubyeval({expr})',
+ },
+ screenattr = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Like |screenchar()|, but return the attribute. This is a rather
+ arbitrary number that can only be used to compare to the
+ attribute at other positions.
+ Returns -1 when row or col is out of range.
+
+ ]=],
+ name = 'screenattr',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenattr({row}, {col})',
+ },
+ screenchar = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the character at position
+ [row, col] on the screen. This works for every possible
+ screen position, also status lines, window separators and the
+ command line. The top left position is row one, column one
+ The character excludes composing characters. For double-byte
+ encodings it may only be the first byte.
+ This is mainly to be used for testing.
+ Returns -1 when row or col is out of range.
+
+ ]=],
+ name = 'screenchar',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenchar({row}, {col})',
+ },
+ screenchars = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a |List| of Numbers. The first number is the same
+ as what |screenchar()| returns. Further numbers are
+ composing characters on top of the base character.
+ This is mainly to be used for testing.
+ Returns an empty List when row or col is out of range.
+
+ ]=],
+ name = 'screenchars',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenchars({row}, {col})',
+ },
+ screencol = {
+ desc = [=[
+ The result is a Number, which is the current screen column of
+ the cursor. The leftmost column has number 1.
+ This function is mainly used for testing.
+
+ Note: Always returns the current screen column, thus if used
+ in a command (e.g. ":echo screencol()") it will return the
+ column inside the command line, which is 1 when the command is
+ executed. To get the cursor position in the file use one of
+ the following mappings: >vim
+ nnoremap <expr> GG ":echom " .. screencol() .. "\n"
+ nnoremap <silent> GG :echom screencol()<CR>
+ noremap GG <Cmd>echom screencol()<Cr>
+ <
+ ]=],
+ name = 'screencol',
+ params = {},
+ signature = 'screencol()',
+ },
+ screenpos = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a Dict with the screen position of the text
+ character in window {winid} at buffer line {lnum} and column
+ {col}. {col} is a one-based byte index.
+ The Dict has these members:
+ row screen row
+ col first screen column
+ endcol last screen column
+ curscol cursor screen column
+ If the specified position is not visible, all values are zero.
+ The "endcol" value differs from "col" when the character
+ occupies more than one screen cell. E.g. for a Tab "col" can
+ be 1 and "endcol" can be 8.
+ The "curscol" value is where the cursor would be placed. For
+ a Tab it would be the same as "endcol", while for a double
+ width character it would be the same as "col".
+ The |conceal| feature is ignored here, the column numbers are
+ as if 'conceallevel' is zero. You can set the cursor to the
+ right position and use |screencol()| to get the value with
+ |conceal| taken into account.
+ If the position is in a closed fold the screen position of the
+ first character is returned, {col} is not used.
+ Returns an empty Dict if {winid} is invalid.
+
+ ]=],
+ name = 'screenpos',
+ params = { { 'winid', 'integer' }, { 'lnum', 'integer' }, { 'col', 'integer' } },
+ signature = 'screenpos({winid}, {lnum}, {col})',
+ },
+ screenrow = {
+ desc = [=[
+ The result is a Number, which is the current screen row of the
+ cursor. The top line has number one.
+ This function is mainly used for testing.
+ Alternatively you can use |winline()|.
+
+ Note: Same restrictions as with |screencol()|.
+ ]=],
+ name = 'screenrow',
+ params = {},
+ signature = 'screenrow()',
+ },
+ screenstring = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a String that contains the base character and
+ any composing characters at position [row, col] on the screen.
+ This is like |screenchars()| but returning a String with the
+ characters.
+ This is mainly to be used for testing.
+ Returns an empty String when row or col is out of range.
+
+ ]=],
+ name = 'screenstring',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenstring({row}, {col})',
+ },
+ search = {
+ args = { 1, 5 },
+ base = 1,
+ desc = [=[
+ Search for regexp pattern {pattern}. The search starts at the
+ cursor position (you can use |cursor()| to set it).
+
+ When a match has been found its line number is returned.
+ If there is no match a 0 is returned and the cursor doesn't
+ move. No error message is given.
+
+ {flags} is a String, which can contain these character flags:
+ 'b' search Backward instead of forward
+ 'c' accept a match at the Cursor position
+ 'e' move to the End of the match
+ 'n' do Not move the cursor
+ 'p' return number of matching sub-Pattern (see below)
+ 's' Set the ' mark at the previous location of the cursor
+ 'w' Wrap around the end of the file
+ 'W' don't Wrap around the end of the file
+ 'z' start searching at the cursor column instead of Zero
+ If neither 'w' or 'W' is given, the 'wrapscan' option applies.
+
+ If the 's' flag is supplied, the ' mark is set, only if the
+ cursor is moved. The 's' flag cannot be combined with the 'n'
+ flag.
+
+ 'ignorecase', 'smartcase' and 'magic' are used.
+
+ When the 'z' flag is not given, forward searching always
+ starts in column zero and then matches before the cursor are
+ skipped. When the 'c' flag is present in 'cpo' the next
+ search starts after the match. Without the 'c' flag the next
+ search starts one column after the start of the match. This
+ matters for overlapping matches. See |cpo-c|. You can also
+ insert "\ze" to change where the match ends, see |/\ze|.
+
+ When searching backwards and the 'z' flag is given then the
+ search starts in column zero, thus no match in the current
+ line will be found (unless wrapping around the end of the
+ file).
+
+ When the {stopline} argument is given then the search stops
+ after searching this line. This is useful to restrict the
+ search to a range of lines. Examples: >vim
+ let match = search('(', 'b', line("w0"))
+ let end = search('END', '', line("w$"))
+ <When {stopline} is used and it is not zero this also implies
+ that the search does not wrap around the end of the file.
+ A zero value is equal to not giving the argument.
+
+ When the {timeout} argument is given the search stops when
+ more than this many milliseconds have passed. Thus when
+ {timeout} is 500 the search stops after half a second.
+ The value must not be negative. A zero value is like not
+ giving the argument.
+
+ If the {skip} expression is given it is evaluated with the
+ cursor positioned on the start of a match. If it evaluates to
+ non-zero this match is skipped. This can be used, for
+ example, to skip a match in a comment or a string.
+ {skip} can be a string, which is evaluated as an expression, a
+ function reference or a lambda.
+ When {skip} is omitted or empty, every match is accepted.
+ When evaluating {skip} causes an error the search is aborted
+ and -1 returned.
+ *search()-sub-match*
+ With the 'p' flag the returned value is one more than the
+ first sub-match in \(\). One if none of them matched but the
+ whole pattern did match.
+ To get the column number too use |searchpos()|.
+
+ The cursor will be positioned at the match, unless the 'n'
+ flag is used.
+
+ Example (goes over all files in the argument list): >vim
+ let n = 1
+ while n <= argc() " loop over all files in arglist
+ exe "argument " .. n
+ " start at the last char in the file and wrap for the
+ " first search to find match at start of file
+ normal G$
+ let flags = "w"
+ while search("foo", flags) > 0
+ s/foo/bar/g
+ let flags = "W"
+ endwhile
+ update " write the file if modified
+ let n = n + 1
+ endwhile
+ <
+ Example for using some flags: >vim
+ echo search('\<if\|\(else\)\|\(endif\)', 'ncpe')
+ <This will search for the keywords "if", "else", and "endif"
+ under or after the cursor. Because of the 'p' flag, it
+ returns 1, 2, or 3 depending on which keyword is found, or 0
+ if the search fails. With the cursor on the first word of the
+ line:
+ if (foo == 0) | let foo = foo + 1 | endif ~
+ the function returns 1. Without the 'c' flag, the function
+ finds the "endif" and returns 3. The same thing happens
+ without the 'e' flag if the cursor is on the "f" of "if".
+ The 'n' flag tells the function not to move the cursor.
+
+ ]=],
+ name = 'search',
+ params = {
+ { 'pattern', 'any' },
+ { 'flags', 'string' },
+ { 'stopline', 'any' },
+ { 'timeout', 'integer' },
+ { 'skip', 'any' },
+ },
+ signature = 'search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])',
+ },
+ searchcount = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get or update the last search count, like what is displayed
+ without the "S" flag in 'shortmess'. This works even if
+ 'shortmess' does contain the "S" flag.
+
+ This returns a |Dictionary|. The dictionary is empty if the
+ previous pattern was not set and "pattern" was not specified.
+
+ key type meaning ~
+ current |Number| current position of match;
+ 0 if the cursor position is
+ before the first match
+ exact_match |Boolean| 1 if "current" is matched on
+ "pos", otherwise 0
+ total |Number| total count of matches found
+ incomplete |Number| 0: search was fully completed
+ 1: recomputing was timed out
+ 2: max count exceeded
+
+ For {options} see further down.
+
+ To get the last search count when |n| or |N| was pressed, call
+ this function with `recompute: 0` . This sometimes returns
+ wrong information because |n| and |N|'s maximum count is 99.
+ If it exceeded 99 the result must be max count + 1 (100). If
+ you want to get correct information, specify `recompute: 1`: >vim
+
+ " result == maxcount + 1 (100) when many matches
+ let result = searchcount(#{recompute: 0})
+
+ " Below returns correct result (recompute defaults
+ " to 1)
+ let result = searchcount()
+ <
+ The function is useful to add the count to 'statusline': >vim
+ function! LastSearchCount() abort
+ let result = searchcount(#{recompute: 0})
+ if empty(result)
+ return ''
+ endif
+ if result.incomplete ==# 1 " timed out
+ return printf(' /%s [?/??]', @/)
+ elseif result.incomplete ==# 2 " max count exceeded
+ if result.total > result.maxcount &&
+ \ result.current > result.maxcount
+ return printf(' /%s [>%d/>%d]', @/,
+ \ result.current, result.total)
+ elseif result.total > result.maxcount
+ return printf(' /%s [%d/>%d]', @/,
+ \ result.current, result.total)
+ endif
+ endif
+ return printf(' /%s [%d/%d]', @/,
+ \ result.current, result.total)
+ endfunction
+ let &statusline ..= '%{LastSearchCount()}'
+
+ " Or if you want to show the count only when
+ " 'hlsearch' was on
+ " let &statusline ..=
+ " \ '%{v:hlsearch ? LastSearchCount() : ""}'
+ <
+ You can also update the search count, which can be useful in a
+ |CursorMoved| or |CursorMovedI| autocommand: >vim
+
+ autocmd CursorMoved,CursorMovedI *
+ \ let s:searchcount_timer = timer_start(
+ \ 200, function('s:update_searchcount'))
+ function! s:update_searchcount(timer) abort
+ if a:timer ==# s:searchcount_timer
+ call searchcount(#{
+ \ recompute: 1, maxcount: 0, timeout: 100})
+ redrawstatus
+ endif
+ endfunction
+ <
+ This can also be used to count matched texts with specified
+ pattern in the current buffer using "pattern": >vim
+
+ " Count '\<foo\>' in this buffer
+ " (Note that it also updates search count)
+ let result = searchcount(#{pattern: '\<foo\>'})
+
+ " To restore old search count by old pattern,
+ " search again
+ call searchcount()
+ <
+ {options} must be a |Dictionary|. It can contain:
+ key type meaning ~
+ recompute |Boolean| if |TRUE|, recompute the count
+ like |n| or |N| was executed.
+ otherwise returns the last
+ computed result (when |n| or
+ |N| was used when "S" is not
+ in 'shortmess', or this
+ function was called).
+ (default: |TRUE|)
+ pattern |String| recompute if this was given
+ and different with |@/|.
+ this works as same as the
+ below command is executed
+ before calling this function >vim
+ let @/ = pattern
+ < (default: |@/|)
+ timeout |Number| 0 or negative number is no
+ timeout. timeout milliseconds
+ for recomputing the result
+ (default: 0)
+ maxcount |Number| 0 or negative number is no
+ limit. max count of matched
+ text while recomputing the
+ result. if search exceeded
+ total count, "total" value
+ becomes `maxcount + 1`
+ (default: 0)
+ pos |List| `[lnum, col, off]` value
+ when recomputing the result.
+ this changes "current" result
+ value. see |cursor()|, |getpos()|
+ (default: cursor's position)
+
+ ]=],
+ name = 'searchcount',
+ params = { { 'options', 'table' } },
+ signature = 'searchcount([{options}])',
+ },
+ searchdecl = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Search for the declaration of {name}.
+
+ With a non-zero {global} argument it works like |gD|, find
+ first match in the file. Otherwise it works like |gd|, find
+ first match in the function.
+
+ With a non-zero {thisblock} argument matches in a {} block
+ that ends before the cursor position are ignored. Avoids
+ finding variable declarations only valid in another scope.
+
+ Moves the cursor to the found match.
+ Returns zero for success, non-zero for failure.
+ Example: >vim
+ if searchdecl('myvar') == 0
+ echo getline('.')
+ endif
+ <
+ ]=],
+ name = 'searchdecl',
+ params = { { 'name', 'string' }, { 'global', 'any' }, { 'thisblock', 'any' } },
+ signature = 'searchdecl({name} [, {global} [, {thisblock}]])',
+ },
+ searchpair = {
+ args = { 3, 7 },
+ desc = [=[
+ Search for the match of a nested start-end pair. This can be
+ used to find the "endif" that matches an "if", while other
+ if/endif pairs in between are ignored.
+ The search starts at the cursor. The default is to search
+ forward, include 'b' in {flags} to search backward.
+ If a match is found, the cursor is positioned at it and the
+ line number is returned. If no match is found 0 or -1 is
+ returned and the cursor doesn't move. No error message is
+ given.
+
+ {start}, {middle} and {end} are patterns, see |pattern|. They
+ must not contain \( \) pairs. Use of \%( \) is allowed. When
+ {middle} is not empty, it is found when searching from either
+ direction, but only when not in a nested start-end pair. A
+ typical use is: >vim
+ echo searchpair('\<if\>', '\<else\>', '\<endif\>')
+ <By leaving {middle} empty the "else" is skipped.
+
+ {flags} 'b', 'c', 'n', 's', 'w' and 'W' are used like with
+ |search()|. Additionally:
+ 'r' Repeat until no more matches found; will find the
+ outer pair. Implies the 'W' flag.
+ 'm' Return number of matches instead of line number with
+ the match; will be > 1 when 'r' is used.
+ Note: it's nearly always a good idea to use the 'W' flag, to
+ avoid wrapping around the end of the file.
+
+ When a match for {start}, {middle} or {end} is found, the
+ {skip} expression is evaluated with the cursor positioned on
+ the start of the match. It should return non-zero if this
+ match is to be skipped. E.g., because it is inside a comment
+ or a string.
+ When {skip} is omitted or empty, every match is accepted.
+ When evaluating {skip} causes an error the search is aborted
+ and -1 returned.
+ {skip} can be a string, a lambda, a funcref or a partial.
+ Anything else makes the function fail.
+
+ For {stopline} and {timeout} see |search()|.
+
+ The value of 'ignorecase' is used. 'magic' is ignored, the
+ patterns are used like it's on.
+
+ The search starts exactly at the cursor. A match with
+ {start}, {middle} or {end} at the next character, in the
+ direction of searching, is the first one found. Example: >vim
+ if 1
+ if 2
+ endif 2
+ endif 1
+ <When starting at the "if 2", with the cursor on the "i", and
+ searching forwards, the "endif 2" is found. When starting on
+ the character just before the "if 2", the "endif 1" will be
+ found. That's because the "if 2" will be found first, and
+ then this is considered to be a nested if/endif from "if 2" to
+ "endif 2".
+ When searching backwards and {end} is more than one character,
+ it may be useful to put "\zs" at the end of the pattern, so
+ that when the cursor is inside a match with the end it finds
+ the matching start.
+
+ Example, to find the "endif" command in a Vim script: >vim
+
+ echo searchpair('\<if\>', '\<el\%[seif]\>', '\<en\%[dif]\>', 'W',
+ \ 'getline(".") =~ "^\\s*\""')
+
+ <The cursor must be at or after the "if" for which a match is
+ to be found. Note that single-quote strings are used to avoid
+ having to double the backslashes. The skip expression only
+ catches comments at the start of a line, not after a command.
+ Also, a word "en" or "if" halfway through a line is considered
+ a match.
+ Another example, to search for the matching "{" of a "}": >vim
+
+ echo searchpair('{', '', '}', 'bW')
+
+ <This works when the cursor is at or before the "}" for which a
+ match is to be found. To reject matches that syntax
+ highlighting recognized as strings: >vim
+
+ echo searchpair('{', '', '}', 'bW',
+ \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"')
+ <
+ ]=],
+ name = 'searchpair',
+ params = {},
+ signature = 'searchpair({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])',
+ },
+ searchpairpos = {
+ args = { 3, 7 },
+ desc = [=[
+ Same as |searchpair()|, but returns a |List| with the line and
+ column position of the match. The first element of the |List|
+ is the line number and the second element is the byte index of
+ the column position of the match. If no match is found,
+ returns [0, 0]. >vim
+
+ let [lnum,col] = searchpairpos('{', '', '}', 'n')
+ <
+ See |match-parens| for a bigger and more useful example.
+ ]=],
+ name = 'searchpairpos',
+ params = {},
+ signature = 'searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])',
+ },
+ searchpos = {
+ args = { 1, 5 },
+ base = 1,
+ desc = [=[
+ Same as |search()|, but returns a |List| with the line and
+ column position of the match. The first element of the |List|
+ is the line number and the second element is the byte index of
+ the column position of the match. If no match is found,
+ returns [0, 0].
+ Example: >vim
+ let [lnum, col] = searchpos('mypattern', 'n')
+
+ <When the 'p' flag is given then there is an extra item with
+ the sub-pattern match number |search()-sub-match|. Example: >vim
+ let [lnum, col, submatch] = searchpos('\(\l\)\|\(\u\)', 'np')
+ <In this example "submatch" is 2 when a lowercase letter is
+ found |/\l|, 3 when an uppercase letter is found |/\u|.
+
+ ]=],
+ name = 'searchpos',
+ params = {
+ { 'pattern', 'any' },
+ { 'flags', 'string' },
+ { 'stopline', 'any' },
+ { 'timeout', 'integer' },
+ { 'skip', 'any' },
+ },
+ signature = 'searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])',
+ },
+ serverlist = {
+ desc = [=[
+ Returns a list of server addresses, or empty if all servers
+ were stopped. |serverstart()| |serverstop()|
+ Example: >vim
+ echo serverlist()
+ <
+ ]=],
+ name = 'serverlist',
+ params = {},
+ signature = 'serverlist()',
+ },
+ serverstart = {
+ args = { 0, 1 },
+ desc = [=[
+ Opens a socket or named pipe at {address} and listens for
+ |RPC| messages. Clients can send |API| commands to the
+ returned address to control Nvim.
+
+ Returns the address string (which may differ from the
+ {address} argument, see below).
+
+ - If {address} has a colon (":") it is a TCP/IPv4/IPv6 address
+ where the last ":" separates host and port (empty or zero
+ assigns a random port).
+ - Else {address} is the path to a named pipe (except on Windows).
+ - If {address} has no slashes ("/") it is treated as the
+ "name" part of a generated path in this format: >vim
+ stdpath("run").."/{name}.{pid}.{counter}"
+ < - If {address} is omitted the name is "nvim". >vim
+ echo serverstart()
+ < >
+ => /tmp/nvim.bram/oknANW/nvim.15430.5
+ <
+ Example bash command to list all Nvim servers: >bash
+ ls ${XDG_RUNTIME_DIR:-${TMPDIR}nvim.${USER}}/*/nvim.*.0
+
+ <Example named pipe: >vim
+ if has('win32')
+ echo serverstart('\\.\pipe\nvim-pipe-1234')
+ else
+ echo serverstart('nvim.sock')
+ endif
+ <
+ Example TCP/IP address: >vim
+ echo serverstart('::1:12345')
+ <
+ ]=],
+ name = 'serverstart',
+ params = { { 'address', 'any' } },
+ signature = 'serverstart([{address}])',
+ },
+ serverstop = {
+ args = 1,
+ desc = [=[
+ Closes the pipe or socket at {address}.
+ Returns TRUE if {address} is valid, else FALSE.
+ If |v:servername| is stopped it is set to the next available
+ address in |serverlist()|.
+ ]=],
+ name = 'serverstop',
+ params = { { 'address', 'any' } },
+ signature = 'serverstop({address})',
+ },
+ setbufline = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Set line {lnum} to {text} in buffer {buf}. This works like
+ |setline()| for the specified buffer.
+
+ This function works only for loaded buffers. First call
+ |bufload()| if needed.
+
+ To insert lines use |appendbufline()|.
+
+ {text} can be a string to set one line, or a List of strings
+ to set multiple lines. If the List extends below the last
+ line then those lines are added. If the List is empty then
+ nothing is changed and zero is returned.
+
+ For the use of {buf}, see |bufname()| above.
+
+ {lnum} is used like with |setline()|.
+ Use "$" to refer to the last line in buffer {buf}.
+ When {lnum} is just below the last line the {text} will be
+ added below the last line.
+ On success 0 is returned, on failure 1 is returned.
+
+ If {buf} is not a valid buffer or {lnum} is not valid, an
+ error message is given.
+
+ ]=],
+ name = 'setbufline',
+ params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'text', 'any' } },
+ signature = 'setbufline({buf}, {lnum}, {text})',
+ },
+ setbufvar = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Set option or local variable {varname} in buffer {buf} to
+ {val}.
+ This also works for a global or local window option, but it
+ doesn't work for a global or local window variable.
+ For a local window option the global value is unchanged.
+ For the use of {buf}, see |bufname()| above.
+ The {varname} argument is a string.
+ Note that the variable name without "b:" must be used.
+ Examples: >vim
+ call setbufvar(1, "&mod", 1)
+ call setbufvar("todo", "myvar", "foobar")
+ <This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'setbufvar',
+ params = { { 'buf', 'any' }, { 'varname', 'string' }, { 'val', 'any' } },
+ signature = 'setbufvar({buf}, {varname}, {val})',
+ },
+ setcellwidths = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Specify overrides for cell widths of character ranges. This
+ tells Vim how wide characters are when displayed in the
+ terminal, counted in screen cells. The values override
+ 'ambiwidth'. Example: >vim
+ call setcellwidths([
+ \ [0x111, 0x111, 1],
+ \ [0x2194, 0x2199, 2],
+ \ ])
+
+ <The {list} argument is a List of Lists with each three
+ numbers: [{low}, {high}, {width}]. *E1109* *E1110*
+ {low} and {high} can be the same, in which case this refers to
+ one character. Otherwise it is the range of characters from
+ {low} to {high} (inclusive). *E1111* *E1114*
+ Only characters with value 0x80 and higher can be used.
+
+ {width} must be either 1 or 2, indicating the character width
+ in screen cells. *E1112*
+ An error is given if the argument is invalid, also when a
+ range overlaps with another. *E1113*
+
+ If the new value causes 'fillchars' or 'listchars' to become
+ invalid it is rejected and an error is given.
+
+ To clear the overrides pass an empty {list}: >vim
+ call setcellwidths([])
+
+ <You can use the script $VIMRUNTIME/tools/emoji_list.vim to see
+ the effect for known emoji characters. Move the cursor
+ through the text to check if the cell widths of your terminal
+ match with what Vim knows about each emoji. If it doesn't
+ look right you need to adjust the {list} argument.
+ ]=],
+ name = 'setcellwidths',
+ params = { { 'list', 'any' } },
+ signature = 'setcellwidths({list})',
+ },
+ setcharpos = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Same as |setpos()| but uses the specified column number as the
+ character index instead of the byte index in the line.
+
+ Example:
+ With the text "여보세요" in line 8: >vim
+ call setcharpos('.', [0, 8, 4, 0])
+ <positions the cursor on the fourth character 'ìš”'. >vim
+ call setpos('.', [0, 8, 4, 0])
+ <positions the cursor on the second character 'ë³´'.
+
+ ]=],
+ name = 'setcharpos',
+ params = { { 'expr', 'any' }, { 'list', 'any' } },
+ signature = 'setcharpos({expr}, {list})',
+ },
+ setcharsearch = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Set the current character search information to {dict},
+ which contains one or more of the following entries:
+
+ char character which will be used for a subsequent
+ |,| or |;| command; an empty string clears the
+ character search
+ forward direction of character search; 1 for forward,
+ 0 for backward
+ until type of character search; 1 for a |t| or |T|
+ character search, 0 for an |f| or |F|
+ character search
+
+ This can be useful to save/restore a user's character search
+ from a script: >vim
+ let prevsearch = getcharsearch()
+ " Perform a command which clobbers user's search
+ call setcharsearch(prevsearch)
+ <Also see |getcharsearch()|.
+
+ ]=],
+ name = 'setcharsearch',
+ params = { { 'dict', 'any' } },
+ signature = 'setcharsearch({dict})',
+ },
+ setcmdline = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Set the command line to {str} and set the cursor position to
+ {pos}.
+ If {pos} is omitted, the cursor is positioned after the text.
+ Returns 0 when successful, 1 when not editing the command
+ line.
+
+ ]=],
+ name = 'setcmdline',
+ params = { { 'str', 'any' }, { 'pos', 'any' } },
+ signature = 'setcmdline({str} [, {pos}])',
+ },
+ setcmdpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Set the cursor position in the command line to byte position
+ {pos}. The first position is 1.
+ Use |getcmdpos()| to obtain the current position.
+ Only works while editing the command line, thus you must use
+ |c_CTRL-\_e|, |c_CTRL-R_=| or |c_CTRL-R_CTRL-R| with '='. For
+ |c_CTRL-\_e| and |c_CTRL-R_CTRL-R| with '=' the position is
+ set after the command line is set to the expression. For
+ |c_CTRL-R_=| it is set after evaluating the expression but
+ before inserting the resulting text.
+ When the number is too big the cursor is put at the end of the
+ line. A number smaller than one has undefined results.
+ Returns 0 when successful, 1 when not editing the command
+ line.
+
+ ]=],
+ name = 'setcmdpos',
+ params = { { 'pos', 'any' } },
+ signature = 'setcmdpos({pos})',
+ },
+ setcursorcharpos = {
+ args = { 1, 3 },
+ base = 1,
+ name = 'setcursorcharpos',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'any' } },
+ signature = 'setcursorcharpos({lnum}, {col} [, {off}])',
+ },
+ setcursorcharpos__1 = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Same as |cursor()| but uses the specified column number as the
+ character index instead of the byte index in the line.
+
+ Example:
+ With the text "여보세요" in line 4: >vim
+ call setcursorcharpos(4, 3)
+ <positions the cursor on the third character '세'. >vim
+ call cursor(4, 3)
+ <positions the cursor on the first character 'ì—¬'.
+
+ ]=],
+ name = 'setcursorcharpos',
+ params = { { 'list', 'any' } },
+ signature = 'setcursorcharpos({list})',
+ },
+ setenv = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set environment variable {name} to {val}. Example: >vim
+ call setenv('HOME', '/home/myhome')
+
+ <When {val} is |v:null| the environment variable is deleted.
+ See also |expr-env|.
+
+ ]=],
+ name = 'setenv',
+ params = { { 'name', 'string' }, { 'val', 'any' } },
+ signature = 'setenv({name}, {val})',
+ },
+ setfperm = {
+ args = 2,
+ base = 1,
+ tags = { 'chmod' },
+ desc = [=[
+ Set the file permissions for {fname} to {mode}.
+ {mode} must be a string with 9 characters. It is of the form
+ "rwxrwxrwx", where each group of "rwx" flags represent, in
+ turn, the permissions of the owner of the file, the group the
+ file belongs to, and other users. A '-' character means the
+ permission is off, any other character means on. Multi-byte
+ characters are not supported.
+
+ For example "rw-r-----" means read-write for the user,
+ readable by the group, not accessible by others. "xx-x-----"
+ would do the same thing.
+
+ Returns non-zero for success, zero for failure.
+
+ To read permissions see |getfperm()|.
+ ]=],
+ name = 'setfperm',
+ params = { { 'fname', 'string' }, { 'mode', 'string' } },
+ signature = 'setfperm({fname}, {mode})',
+ },
+ setline = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set line {lnum} of the current buffer to {text}. To insert
+ lines use |append()|. To set lines in another buffer use
+ |setbufline()|.
+
+ {lnum} is used like with |getline()|.
+ When {lnum} is just below the last line the {text} will be
+ added below the last line.
+ {text} can be any type or a List of any type, each item is
+ converted to a String. When {text} is an empty List then
+ nothing is changed and FALSE is returned.
+
+ If this succeeds, FALSE is returned. If this fails (most likely
+ because {lnum} is invalid) TRUE is returned.
+
+ Example: >vim
+ call setline(5, strftime("%c"))
+
+ <When {text} is a |List| then line {lnum} and following lines
+ will be set to the items in the list. Example: >vim
+ call setline(5, ['aaa', 'bbb', 'ccc'])
+ <This is equivalent to: >vim
+ for [n, l] in [[5, 'aaa'], [6, 'bbb'], [7, 'ccc']]
+ call setline(n, l)
+ endfor
+
+ <Note: The '[ and '] marks are not set.
+
+ ]=],
+ name = 'setline',
+ params = { { 'lnum', 'integer' }, { 'text', 'any' } },
+ signature = 'setline({lnum}, {text})',
+ },
+ setloclist = {
+ args = { 2, 4 },
+ base = 2,
+ desc = [=[
+ Create or replace or add to the location list for window {nr}.
+ {nr} can be the window number or the |window-ID|.
+ When {nr} is zero the current window is used.
+
+ For a location list window, the displayed location list is
+ modified. For an invalid window number {nr}, -1 is returned.
+ Otherwise, same as |setqflist()|.
+ Also see |location-list|.
+
+ For {action} see |setqflist-action|.
+
+ If the optional {what} dictionary argument is supplied, then
+ only the items listed in {what} are set. Refer to |setqflist()|
+ for the list of supported keys in {what}.
+
+ ]=],
+ name = 'setloclist',
+ params = { { 'nr', 'integer' }, { 'list', 'any' }, { 'action', 'any' }, { 'what', 'any' } },
+ signature = 'setloclist({nr}, {list} [, {action} [, {what}]])',
+ },
+ setmatches = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Restores a list of matches saved by |getmatches()| for the
+ current window. Returns 0 if successful, otherwise -1. All
+ current matches are cleared before the list is restored. See
+ example for |getmatches()|.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window.
+
+ ]=],
+ name = 'setmatches',
+ params = { { 'list', 'any' }, { 'win', 'any' } },
+ signature = 'setmatches({list} [, {win}])',
+ },
+ setpos = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set the position for String {expr}. Possible values:
+ . the cursor
+ 'x mark x
+
+ {list} must be a |List| with four or five numbers:
+ [bufnum, lnum, col, off]
+ [bufnum, lnum, col, off, curswant]
+
+ "bufnum" is the buffer number. Zero can be used for the
+ current buffer. When setting an uppercase mark "bufnum" is
+ used for the mark position. For other marks it specifies the
+ buffer to set the mark in. You can use the |bufnr()| function
+ to turn a file name into a buffer number.
+ For setting the cursor and the ' mark "bufnum" is ignored,
+ since these are associated with a window, not a buffer.
+ Does not change the jumplist.
+
+ "lnum" and "col" are the position in the buffer. The first
+ column is 1. Use a zero "lnum" to delete a mark. If "col" is
+ smaller than 1 then 1 is used. To use the character count
+ instead of the byte count, use |setcharpos()|.
+
+ The "off" number is only used when 'virtualedit' is set. Then
+ it is the offset in screen columns from the start of the
+ character. E.g., a position within a <Tab> or after the last
+ character.
+
+ The "curswant" number is only used when setting the cursor
+ position. It sets the preferred column for when moving the
+ cursor vertically. When the "curswant" number is missing the
+ preferred column is not set. When it is present and setting a
+ mark position it is not used.
+
+ Note that for '< and '> changing the line number may result in
+ the marks to be effectively be swapped, so that '< is always
+ before '>.
+
+ Returns 0 when the position could be set, -1 otherwise.
+ An error message is given if {expr} is invalid.
+
+ Also see |setcharpos()|, |getpos()| and |getcurpos()|.
+
+ This does not restore the preferred column for moving
+ vertically; if you set the cursor position with this, |j| and
+ |k| motions will jump to previous columns! Use |cursor()| to
+ also set the preferred column. Also see the "curswant" key in
+ |winrestview()|.
+
+ ]=],
+ name = 'setpos',
+ params = { { 'expr', 'any' }, { 'list', 'any' } },
+ signature = 'setpos({expr}, {list})',
+ },
+ setqflist = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Create or replace or add to the quickfix list.
+
+ If the optional {what} dictionary argument is supplied, then
+ only the items listed in {what} are set. The first {list}
+ argument is ignored. See below for the supported items in
+ {what}.
+ *setqflist-what*
+ When {what} is not present, the items in {list} are used. Each
+ item must be a dictionary. Non-dictionary items in {list} are
+ ignored. Each dictionary item can contain the following
+ entries:
+
+ bufnr buffer number; must be the number of a valid
+ buffer
+ filename name of a file; only used when "bufnr" is not
+ present or it is invalid.
+ module name of a module; if given it will be used in
+ quickfix error window instead of the filename.
+ lnum line number in the file
+ end_lnum end of lines, if the item spans multiple lines
+ pattern search pattern used to locate the error
+ col column number
+ vcol when non-zero: "col" is visual column
+ when zero: "col" is byte index
+ end_col end column, if the item spans multiple columns
+ nr error number
+ text description of the error
+ type single-character error type, 'E', 'W', etc.
+ valid recognized error message
+ user_data
+ custom data associated with the item, can be
+ any type.
+
+ The "col", "vcol", "nr", "type" and "text" entries are
+ optional. Either "lnum" or "pattern" entry can be used to
+ locate a matching error line.
+ If the "filename" and "bufnr" entries are not present or
+ neither the "lnum" or "pattern" entries are present, then the
+ item will not be handled as an error line.
+ If both "pattern" and "lnum" are present then "pattern" will
+ be used.
+ If the "valid" entry is not supplied, then the valid flag is
+ set when "bufnr" is a valid buffer or "filename" exists.
+ If you supply an empty {list}, the quickfix list will be
+ cleared.
+ Note that the list is not exactly the same as what
+ |getqflist()| returns.
+
+ {action} values: *setqflist-action* *E927*
+ 'a' The items from {list} are added to the existing
+ quickfix list. If there is no existing list, then a
+ new list is created.
+
+ 'r' The items from the current quickfix list are replaced
+ with the items from {list}. This can also be used to
+ clear the list: >vim
+ call setqflist([], 'r')
+ <
+ 'f' All the quickfix lists in the quickfix stack are
+ freed.
+
+ If {action} is not present or is set to ' ', then a new list
+ is created. The new quickfix list is added after the current
+ quickfix list in the stack and all the following lists are
+ freed. To add a new quickfix list at the end of the stack,
+ set "nr" in {what} to "$".
+
+ The following items can be specified in dictionary {what}:
+ context quickfix list context. See |quickfix-context|
+ efm errorformat to use when parsing text from
+ "lines". If this is not present, then the
+ 'errorformat' option value is used.
+ See |quickfix-parse|
+ id quickfix list identifier |quickfix-ID|
+ idx index of the current entry in the quickfix
+ list specified by "id" or "nr". If set to '$',
+ then the last entry in the list is set as the
+ current entry. See |quickfix-index|
+ items list of quickfix entries. Same as the {list}
+ argument.
+ lines use 'errorformat' to parse a list of lines and
+ add the resulting entries to the quickfix list
+ {nr} or {id}. Only a |List| value is supported.
+ See |quickfix-parse|
+ nr list number in the quickfix stack; zero
+ means the current quickfix list and "$" means
+ the last quickfix list.
+ quickfixtextfunc
+ function to get the text to display in the
+ quickfix window. The value can be the name of
+ a function or a funcref or a lambda. Refer to
+ |quickfix-window-function| for an explanation
+ of how to write the function and an example.
+ title quickfix list title text. See |quickfix-title|
+ Unsupported keys in {what} are ignored.
+ If the "nr" item is not present, then the current quickfix list
+ is modified. When creating a new quickfix list, "nr" can be
+ set to a value one greater than the quickfix stack size.
+ When modifying a quickfix list, to guarantee that the correct
+ list is modified, "id" should be used instead of "nr" to
+ specify the list.
+
+ Examples (See also |setqflist-examples|): >vim
+ call setqflist([], 'r', {'title': 'My search'})
+ call setqflist([], 'r', {'nr': 2, 'title': 'Errors'})
+ call setqflist([], 'a', {'id':qfid, 'lines':["F1:10:L10"]})
+ <
+ Returns zero for success, -1 for failure.
+
+ This function can be used to create a quickfix list
+ independent of the 'errorformat' setting. Use a command like
+ `:cc 1` to jump to the first position.
+
+ ]=],
+ name = 'setqflist',
+ params = { { 'list', 'any' }, { 'action', 'any' }, { 'what', 'any' } },
+ signature = 'setqflist({list} [, {action} [, {what}]])',
+ },
+ setreg = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ Set the register {regname} to {value}.
+ If {regname} is "" or "@", the unnamed register '"' is used.
+ The {regname} argument is a string.
+
+ {value} may be any value returned by |getreg()| or
+ |getreginfo()|, including a |List| or |Dict|.
+ If {options} contains "a" or {regname} is upper case,
+ then the value is appended.
+
+ {options} can also contain a register type specification:
+ "c" or "v" |charwise| mode
+ "l" or "V" |linewise| mode
+ "b" or "<CTRL-V>" |blockwise-visual| mode
+ If a number immediately follows "b" or "<CTRL-V>" then this is
+ used as the width of the selection - if it is not specified
+ then the width of the block is set to the number of characters
+ in the longest line (counting a <Tab> as 1 character).
+ If {options} contains "u" or '"', then the unnamed register is
+ set to point to register {regname}.
+
+ If {options} contains no register settings, then the default
+ is to use character mode unless {value} ends in a <NL> for
+ string {value} and linewise mode for list {value}. Blockwise
+ mode is never selected automatically.
+ Returns zero for success, non-zero for failure.
+
+ *E883*
+ Note: you may not use |List| containing more than one item to
+ set search and expression registers. Lists containing no
+ items act like empty strings.
+
+ Examples: >vim
+ call setreg(v:register, @*)
+ call setreg('*', @%, 'ac')
+ call setreg('a', "1\n2\n3", 'b5')
+ call setreg('"', { 'points_to': 'a'})
+
+ <This example shows using the functions to save and restore a
+ register: >vim
+ let var_a = getreginfo()
+ call setreg('a', var_a)
+ <or: >vim
+ let var_a = getreg('a', 1, 1)
+ let var_amode = getregtype('a')
+ " ....
+ call setreg('a', var_a, var_amode)
+ <Note: you may not reliably restore register value
+ without using the third argument to |getreg()| as without it
+ newlines are represented as newlines AND Nul bytes are
+ represented as newlines as well, see |NL-used-for-Nul|.
+
+ You can also change the type of a register by appending
+ nothing: >vim
+ call setreg('a', '', 'al')
+
+ ]=],
+ name = 'setreg',
+ params = { { 'regname', 'string' }, { 'value', 'any' }, { 'options', 'table' } },
+ signature = 'setreg({regname}, {value} [, {options}])',
+ },
+ settabvar = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Set tab-local variable {varname} to {val} in tab page {tabnr}.
+ |t:var|
+ The {varname} argument is a string.
+ Note that the variable name without "t:" must be used.
+ Tabs are numbered starting with one.
+ This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'settabvar',
+ params = { { 'tabnr', 'integer' }, { 'varname', 'string' }, { 'val', 'any' } },
+ signature = 'settabvar({tabnr}, {varname}, {val})',
+ },
+ settabwinvar = {
+ args = 4,
+ base = 4,
+ desc = [=[
+ Set option or local variable {varname} in window {winnr} to
+ {val}.
+ Tabs are numbered starting with one. For the current tabpage
+ use |setwinvar()|.
+ {winnr} can be the window number or the |window-ID|.
+ When {winnr} is zero the current window is used.
+ This also works for a global or local buffer option, but it
+ doesn't work for a global or local buffer variable.
+ For a local buffer option the global value is unchanged.
+ Note that the variable name without "w:" must be used.
+ Examples: >vim
+ call settabwinvar(1, 1, "&list", 0)
+ call settabwinvar(3, 2, "myvar", "foobar")
+ <This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'settabwinvar',
+ params = {
+ { 'tabnr', 'integer' },
+ { 'winnr', 'integer' },
+ { 'varname', 'string' },
+ { 'val', 'any' },
+ },
+ signature = 'settabwinvar({tabnr}, {winnr}, {varname}, {val})',
+ },
+ settagstack = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ Modify the tag stack of the window {nr} using {dict}.
+ {nr} can be the window number or the |window-ID|.
+
+ For a list of supported items in {dict}, refer to
+ |gettagstack()|. "curidx" takes effect before changing the tag
+ stack.
+ *E962*
+ How the tag stack is modified depends on the {action}
+ argument:
+ - If {action} is not present or is set to 'r', then the tag
+ stack is replaced.
+ - If {action} is set to 'a', then new entries from {dict} are
+ pushed (added) onto the tag stack.
+ - If {action} is set to 't', then all the entries from the
+ current entry in the tag stack or "curidx" in {dict} are
+ removed and then new entries are pushed to the stack.
+
+ The current index is set to one after the length of the tag
+ stack after the modification.
+
+ Returns zero for success, -1 for failure.
+
+ Examples (for more examples see |tagstack-examples|):
+ Empty the tag stack of window 3: >vim
+ call settagstack(3, {'items' : []})
+
+ < Save and restore the tag stack: >vim
+ let stack = gettagstack(1003)
+ " do something else
+ call settagstack(1003, stack)
+ unlet stack
+ <
+ ]=],
+ name = 'settagstack',
+ params = { { 'nr', 'integer' }, { 'dict', 'any' }, { 'action', 'any' } },
+ signature = 'settagstack({nr}, {dict} [, {action}])',
+ },
+ setwinvar = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Like |settabwinvar()| for the current tab page.
+ Examples: >vim
+ call setwinvar(1, "&list", 0)
+ call setwinvar(2, "myvar", "foobar")
+
+ ]=],
+ name = 'setwinvar',
+ params = { { 'nr', 'integer' }, { 'varname', 'string' }, { 'val', 'any' } },
+ signature = 'setwinvar({nr}, {varname}, {val})',
+ },
+ sha256 = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns a String with 64 hex characters, which is the SHA256
+ checksum of {string}.
+
+ ]=],
+ name = 'sha256',
+ params = { { 'string', 'string' } },
+ signature = 'sha256({string})',
+ },
+ shellescape = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Escape {string} for use as a shell command argument.
+
+ On Windows when 'shellslash' is not set, encloses {string} in
+ double-quotes and doubles all double-quotes within {string}.
+ Otherwise encloses {string} in single-quotes and replaces all
+ "'" with "'\''".
+
+ If {special} is a |non-zero-arg|:
+ - Special items such as "!", "%", "#" and "<cword>" will be
+ preceded by a backslash. The backslash will be removed again
+ by the |:!| command.
+ - The <NL> character is escaped.
+
+ If 'shell' contains "csh" in the tail:
+ - The "!" character will be escaped. This is because csh and
+ tcsh use "!" for history replacement even in single-quotes.
+ - The <NL> character is escaped (twice if {special} is
+ a |non-zero-arg|).
+
+ If 'shell' contains "fish" in the tail, the "\" character will
+ be escaped because in fish it is used as an escape character
+ inside single quotes.
+
+ Example of use with a |:!| command: >vim
+ exe '!dir ' .. shellescape(expand('<cfile>'), 1)
+ <This results in a directory listing for the file under the
+ cursor. Example of use with |system()|: >vim
+ call system("chmod +w -- " .. shellescape(expand("%")))
+ <See also |::S|.
+
+ ]=],
+ name = 'shellescape',
+ params = { { 'string', 'string' }, { 'special', 'any' } },
+ signature = 'shellescape({string} [, {special}])',
+ },
+ shiftwidth = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns the effective value of 'shiftwidth'. This is the
+ 'shiftwidth' value unless it is zero, in which case it is the
+ 'tabstop' value. To be backwards compatible in indent
+ plugins, use this: >vim
+ if exists('*shiftwidth')
+ func s:sw()
+ return shiftwidth()
+ endfunc
+ else
+ func s:sw()
+ return &sw
+ endfunc
+ endif
+ <And then use s:sw() instead of &sw.
+
+ When there is one argument {col} this is used as column number
+ for which to return the 'shiftwidth' value. This matters for the
+ 'vartabstop' feature. If no {col} argument is given, column 1
+ will be assumed.
+
+ ]=],
+ name = 'shiftwidth',
+ params = { { 'col', 'integer' } },
+ signature = 'shiftwidth([{col}])',
+ returns = 'integer',
+ },
+ sign_define = {
+ args = { 1, 2 },
+ base = 1,
+ name = 'sign_define',
+ params = { { 'name', 'string' }, { 'dict', 'vim.fn.sign_define.dict' } },
+ signature = 'sign_define({name} [, {dict}])',
+ returns = '0|-1',
+ },
+ sign_define__1 = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Define a new sign named {name} or modify the attributes of an
+ existing sign. This is similar to the |:sign-define| command.
+
+ Prefix {name} with a unique text to avoid name collisions.
+ There is no {group} like with placing signs.
+
+ The {name} can be a String or a Number. The optional {dict}
+ argument specifies the sign attributes. The following values
+ are supported:
+ icon full path to the bitmap file for the sign.
+ linehl highlight group used for the whole line the
+ sign is placed in.
+ numhl highlight group used for the line number where
+ the sign is placed.
+ text text that is displayed when there is no icon
+ or the GUI is not being used.
+ texthl highlight group used for the text item
+ culhl highlight group used for the text item when
+ the cursor is on the same line as the sign and
+ 'cursorline' is enabled.
+
+ If the sign named {name} already exists, then the attributes
+ of the sign are updated.
+
+ The one argument {list} can be used to define a list of signs.
+ Each list item is a dictionary with the above items in {dict}
+ and a "name" item for the sign name.
+
+ Returns 0 on success and -1 on failure. When the one argument
+ {list} is used, then returns a List of values one for each
+ defined sign.
+
+ Examples: >vim
+ call sign_define("mySign", {
+ \ "text" : "=>",
+ \ "texthl" : "Error",
+ \ "linehl" : "Search"})
+ call sign_define([
+ \ {'name' : 'sign1',
+ \ 'text' : '=>'},
+ \ {'name' : 'sign2',
+ \ 'text' : '!!'}
+ \ ])
+ <
+ ]=],
+ name = 'sign_define',
+ params = { { 'list', 'vim.fn.sign_define.dict[]' } },
+ signature = 'sign_define({list})',
+ returns = '(0|-1)[]',
+ },
+ sign_getdefined = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get a list of defined signs and their attributes.
+ This is similar to the |:sign-list| command.
+
+ If the {name} is not supplied, then a list of all the defined
+ signs is returned. Otherwise the attribute of the specified
+ sign is returned.
+
+ Each list item in the returned value is a dictionary with the
+ following entries:
+ icon full path to the bitmap file of the sign
+ linehl highlight group used for the whole line the
+ sign is placed in; not present if not set.
+ name name of the sign
+ numhl highlight group used for the line number where
+ the sign is placed; not present if not set.
+ text text that is displayed when there is no icon
+ or the GUI is not being used.
+ texthl highlight group used for the text item; not
+ present if not set.
+ culhl highlight group used for the text item when
+ the cursor is on the same line as the sign and
+ 'cursorline' is enabled; not present if not
+ set.
+
+ Returns an empty List if there are no signs and when {name} is
+ not found.
+
+ Examples: >vim
+ " Get a list of all the defined signs
+ echo sign_getdefined()
+
+ " Get the attribute of the sign named mySign
+ echo sign_getdefined("mySign")
+ <
+ ]=],
+ name = 'sign_getdefined',
+ params = { { 'name', 'string' } },
+ signature = 'sign_getdefined([{name}])',
+ returns = 'vim.fn.sign_getdefined.ret.item[]',
+ },
+ sign_getplaced = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Return a list of signs placed in a buffer or all the buffers.
+ This is similar to the |:sign-place-list| command.
+
+ If the optional buffer name {buf} is specified, then only the
+ list of signs placed in that buffer is returned. For the use
+ of {buf}, see |bufname()|. The optional {dict} can contain
+ the following entries:
+ group select only signs in this group
+ id select sign with this identifier
+ lnum select signs placed in this line. For the use
+ of {lnum}, see |line()|.
+ If {group} is "*", then signs in all the groups including the
+ global group are returned. If {group} is not supplied or is an
+ empty string, then only signs in the global group are
+ returned. If no arguments are supplied, then signs in the
+ global group placed in all the buffers are returned.
+ See |sign-group|.
+
+ Each list item in the returned value is a dictionary with the
+ following entries:
+ bufnr number of the buffer with the sign
+ signs list of signs placed in {bufnr}. Each list
+ item is a dictionary with the below listed
+ entries
+
+ The dictionary for each sign contains the following entries:
+ group sign group. Set to '' for the global group.
+ id identifier of the sign
+ lnum line number where the sign is placed
+ name name of the defined sign
+ priority sign priority
+
+ The returned signs in a buffer are ordered by their line
+ number and priority.
+
+ Returns an empty list on failure or if there are no placed
+ signs.
+
+ Examples: >vim
+ " Get a List of signs placed in eval.c in the
+ " global group
+ echo sign_getplaced("eval.c")
+
+ " Get a List of signs in group 'g1' placed in eval.c
+ echo sign_getplaced("eval.c", {'group' : 'g1'})
+
+ " Get a List of signs placed at line 10 in eval.c
+ echo sign_getplaced("eval.c", {'lnum' : 10})
+
+ " Get sign with identifier 10 placed in a.py
+ echo sign_getplaced("a.py", {'id' : 10})
+
+ " Get sign with id 20 in group 'g1' placed in a.py
+ echo sign_getplaced("a.py", {'group' : 'g1',
+ \ 'id' : 20})
+
+ " Get a List of all the placed signs
+ echo sign_getplaced()
+ <
+ ]=],
+ name = 'sign_getplaced',
+ params = { { 'buf', 'any' }, { 'dict', 'vim.fn.sign_getplaced.dict' } },
+ signature = 'sign_getplaced([{buf} [, {dict}]])',
+ returns = 'vim.fn.sign_getplaced.ret.item[]',
+ },
+ sign_jump = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ Open the buffer {buf} or jump to the window that contains
+ {buf} and position the cursor at sign {id} in group {group}.
+ This is similar to the |:sign-jump| command.
+
+ If {group} is an empty string, then the global group is used.
+ For the use of {buf}, see |bufname()|.
+
+ Returns the line number of the sign. Returns -1 if the
+ arguments are invalid.
+
+ Example: >vim
+ " Jump to sign 10 in the current buffer
+ call sign_jump(10, '', '')
+ <
+ ]=],
+ name = 'sign_jump',
+ params = { { 'id', 'integer' }, { 'group', 'string' }, { 'buf', 'integer|string' } },
+ signature = 'sign_jump({id}, {group}, {buf})',
+ returns = 'integer'
+ },
+ sign_place = {
+ args = { 4, 5 },
+ base = 1,
+ desc = [=[
+ Place the sign defined as {name} at line {lnum} in file or
+ buffer {buf} and assign {id} and {group} to sign. This is
+ similar to the |:sign-place| command.
+
+ If the sign identifier {id} is zero, then a new identifier is
+ allocated. Otherwise the specified number is used. {group} is
+ the sign group name. To use the global sign group, use an
+ empty string. {group} functions as a namespace for {id}, thus
+ two groups can use the same IDs. Refer to |sign-identifier|
+ and |sign-group| for more information.
+
+ {name} refers to a defined sign.
+ {buf} refers to a buffer name or number. For the accepted
+ values, see |bufname()|.
+
+ The optional {dict} argument supports the following entries:
+ lnum line number in the file or buffer
+ {buf} where the sign is to be placed.
+ For the accepted values, see |line()|.
+ priority priority of the sign. See
+ |sign-priority| for more information.
+
+ If the optional {dict} is not specified, then it modifies the
+ placed sign {id} in group {group} to use the defined sign
+ {name}.
+
+ Returns the sign identifier on success and -1 on failure.
+
+ Examples: >vim
+ " Place a sign named sign1 with id 5 at line 20 in
+ " buffer json.c
+ call sign_place(5, '', 'sign1', 'json.c',
+ \ {'lnum' : 20})
+
+ " Updates sign 5 in buffer json.c to use sign2
+ call sign_place(5, '', 'sign2', 'json.c')
+
+ " Place a sign named sign3 at line 30 in
+ " buffer json.c with a new identifier
+ let id = sign_place(0, '', 'sign3', 'json.c',
+ \ {'lnum' : 30})
+
+ " Place a sign named sign4 with id 10 in group 'g3'
+ " at line 40 in buffer json.c with priority 90
+ call sign_place(10, 'g3', 'sign4', 'json.c',
+ \ {'lnum' : 40, 'priority' : 90})
+ <
+ ]=],
+ name = 'sign_place',
+ params = {
+ { 'id', 'any' },
+ { 'group', 'any' },
+ { 'name', 'string' },
+ { 'buf', 'any' },
+ { 'dict', 'vim.fn.sign_place.dict' },
+ },
+ signature = 'sign_place({id}, {group}, {name}, {buf} [, {dict}])',
+ returns = 'integer'
+ },
+ sign_placelist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Place one or more signs. This is similar to the
+ |sign_place()| function. The {list} argument specifies the
+ List of signs to place. Each list item is a dict with the
+ following sign attributes:
+ buffer Buffer name or number. For the accepted
+ values, see |bufname()|.
+ group Sign group. {group} functions as a namespace
+ for {id}, thus two groups can use the same
+ IDs. If not specified or set to an empty
+ string, then the global group is used. See
+ |sign-group| for more information.
+ id Sign identifier. If not specified or zero,
+ then a new unique identifier is allocated.
+ Otherwise the specified number is used. See
+ |sign-identifier| for more information.
+ lnum Line number in the buffer where the sign is to
+ be placed. For the accepted values, see
+ |line()|.
+ name Name of the sign to place. See |sign_define()|
+ for more information.
+ priority Priority of the sign. When multiple signs are
+ placed on a line, the sign with the highest
+ priority is used. If not specified, the
+ default value of 10 is used. See
+ |sign-priority| for more information.
+
+ If {id} refers to an existing sign, then the existing sign is
+ modified to use the specified {name} and/or {priority}.
+
+ Returns a List of sign identifiers. If failed to place a
+ sign, the corresponding list item is set to -1.
+
+ Examples: >vim
+ " Place sign s1 with id 5 at line 20 and id 10 at line
+ " 30 in buffer a.c
+ let [n1, n2] = sign_placelist([
+ \ {'id' : 5,
+ \ 'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 20},
+ \ {'id' : 10,
+ \ 'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 30}
+ \ ])
+
+ " Place sign s1 in buffer a.c at line 40 and 50
+ " with auto-generated identifiers
+ let [n1, n2] = sign_placelist([
+ \ {'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 40},
+ \ {'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 50}
+ \ ])
+ <
+ ]=],
+ name = 'sign_placelist',
+ params = { { 'list', 'vim.fn.sign_placelist.list.item[]' } },
+ signature = 'sign_placelist({list})',
+ returns = 'integer[]'
+ },
+ sign_undefine = {
+ args = { 0, 1 },
+ base = 1,
+ name = 'sign_undefine',
+ params = { { 'name', 'string' } },
+ signature = 'sign_undefine([{name}])',
+ returns = '0|-1',
+ },
+ sign_undefine__1 = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Deletes a previously defined sign {name}. This is similar to
+ the |:sign-undefine| command. If {name} is not supplied, then
+ deletes all the defined signs.
+
+ The one argument {list} can be used to undefine a list of
+ signs. Each list item is the name of a sign.
+
+ Returns 0 on success and -1 on failure. For the one argument
+ {list} call, returns a list of values one for each undefined
+ sign.
+
+ Examples: >vim
+ " Delete a sign named mySign
+ call sign_undefine("mySign")
+
+ " Delete signs 'sign1' and 'sign2'
+ call sign_undefine(["sign1", "sign2"])
+
+ " Delete all the signs
+ call sign_undefine()
+ <
+ ]=],
+ name = 'sign_undefine',
+ params = { { 'list', 'string[]' } },
+ signature = 'sign_undefine({list})',
+ returns = 'integer[]',
+ },
+ sign_unplace = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Remove a previously placed sign in one or more buffers. This
+ is similar to the |:sign-unplace| command.
+
+ {group} is the sign group name. To use the global sign group,
+ use an empty string. If {group} is set to "*", then all the
+ groups including the global group are used.
+ The signs in {group} are selected based on the entries in
+ {dict}. The following optional entries in {dict} are
+ supported:
+ buffer buffer name or number. See |bufname()|.
+ id sign identifier
+ If {dict} is not supplied, then all the signs in {group} are
+ removed.
+
+ Returns 0 on success and -1 on failure.
+
+ Examples: >vim
+ " Remove sign 10 from buffer a.vim
+ call sign_unplace('', {'buffer' : "a.vim", 'id' : 10})
+
+ " Remove sign 20 in group 'g1' from buffer 3
+ call sign_unplace('g1', {'buffer' : 3, 'id' : 20})
+
+ " Remove all the signs in group 'g2' from buffer 10
+ call sign_unplace('g2', {'buffer' : 10})
+
+ " Remove sign 30 in group 'g3' from all the buffers
+ call sign_unplace('g3', {'id' : 30})
+
+ " Remove all the signs placed in buffer 5
+ call sign_unplace('*', {'buffer' : 5})
+
+ " Remove the signs in group 'g4' from all the buffers
+ call sign_unplace('g4')
+
+ " Remove sign 40 from all the buffers
+ call sign_unplace('*', {'id' : 40})
+
+ " Remove all the placed signs from all the buffers
+ call sign_unplace('*')
+
+ ]=],
+ name = 'sign_unplace',
+ params = { { 'group', 'string' }, { 'dict', 'vim.fn.sign_unplace.dict' } },
+ signature = 'sign_unplace({group} [, {dict}])',
+ returns = '0|-1',
+ },
+ sign_unplacelist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Remove previously placed signs from one or more buffers. This
+ is similar to the |sign_unplace()| function.
+
+ The {list} argument specifies the List of signs to remove.
+ Each list item is a dict with the following sign attributes:
+ buffer buffer name or number. For the accepted
+ values, see |bufname()|. If not specified,
+ then the specified sign is removed from all
+ the buffers.
+ group sign group name. If not specified or set to an
+ empty string, then the global sign group is
+ used. If set to "*", then all the groups
+ including the global group are used.
+ id sign identifier. If not specified, then all
+ the signs in the specified group are removed.
+
+ Returns a List where an entry is set to 0 if the corresponding
+ sign was successfully removed or -1 on failure.
+
+ Example: >vim
+ " Remove sign with id 10 from buffer a.vim and sign
+ " with id 20 from buffer b.vim
+ call sign_unplacelist([
+ \ {'id' : 10, 'buffer' : "a.vim"},
+ \ {'id' : 20, 'buffer' : 'b.vim'},
+ \ ])
+ <
+ ]=],
+ name = 'sign_unplacelist',
+ params = { { 'list', 'vim.fn.sign_unplacelist.list.item' } },
+ signature = 'sign_unplacelist({list})',
+ returns = '(0|-1)[]',
+ },
+ simplify = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Simplify the file name as much as possible without changing
+ the meaning. Shortcuts (on MS-Windows) or symbolic links (on
+ Unix) are not resolved. If the first path component in
+ {filename} designates the current directory, this will be
+ valid for the result as well. A trailing path separator is
+ not removed either. On Unix "//path" is unchanged, but
+ "///path" is simplified to "/path" (this follows the Posix
+ standard).
+ Example: >vim
+ simplify("./dir/.././/file/") == "./file/"
+ <Note: The combination "dir/.." is only removed if "dir" is
+ a searchable directory or does not exist. On Unix, it is also
+ removed when "dir" is a symbolic link within the same
+ directory. In order to resolve all the involved symbolic
+ links before simplifying the path name, use |resolve()|.
+
+ ]=],
+ name = 'simplify',
+ params = { { 'filename', 'any' } },
+ signature = 'simplify({filename})',
+ },
+ sin = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the sine of {expr}, measured in radians, as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo sin(100)
+ < -0.506366 >vim
+ echo sin(-4.01)
+ < 0.763301
+
+ ]=],
+ float_func = 'sin',
+ name = 'sin',
+ params = { { 'expr', 'any' } },
+ signature = 'sin({expr})',
+ },
+ sinh = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the hyperbolic sine of {expr} as a |Float| in the range
+ [-inf, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo sinh(0.5)
+ < 0.521095 >vim
+ echo sinh(-0.9)
+ < -1.026517
+
+ ]=],
+ float_func = 'sinh',
+ name = 'sinh',
+ params = { { 'expr', 'any' } },
+ signature = 'sinh({expr})',
+ },
+ slice = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Similar to using a |slice| "expr[start : end]", but "end" is
+ used exclusive. And for a string the indexes are used as
+ character indexes instead of byte indexes.
+ Also, composing characters are not counted.
+ When {end} is omitted the slice continues to the last item.
+ When {end} is -1 the last item is omitted.
+ Returns an empty value if {start} or {end} are invalid.
+
+ ]=],
+ name = 'slice',
+ params = { { 'expr', 'any' }, { 'start', 'any' }, { 'end', 'any' } },
+ signature = 'slice({expr}, {start} [, {end}])',
+ },
+ sockconnect = {
+ args = { 2, 3 },
+ desc = [=[
+ Connect a socket to an address. If {mode} is "pipe" then
+ {address} should be the path of a local domain socket (on
+ unix) or named pipe (on Windows). If {mode} is "tcp" then
+ {address} should be of the form "host:port" where the host
+ should be an ip address or host name, and port the port
+ number.
+
+ For "pipe" mode, see |luv-pipe-handle|. For "tcp" mode, see
+ |luv-tcp-handle|.
+
+ Returns a |channel| ID. Close the socket with |chanclose()|.
+ Use |chansend()| to send data over a bytes socket, and
+ |rpcrequest()| and |rpcnotify()| to communicate with a RPC
+ socket.
+
+ {opts} is an optional dictionary with these keys:
+ |on_data| : callback invoked when data was read from socket
+ data_buffered : read socket data in |channel-buffered| mode.
+ rpc : If set, |msgpack-rpc| will be used to communicate
+ over the socket.
+ Returns:
+ - The channel ID on success (greater than zero)
+ - 0 on invalid arguments or connection failure.
+ ]=],
+ name = 'sockconnect',
+ params = { { 'mode', 'string' }, { 'address', 'any' }, { 'opts', 'table' } },
+ signature = 'sockconnect({mode}, {address} [, {opts}])',
+ },
+ sort = {
+ args = { 1, 3 },
+ base = 1,
+ tags = { 'E702' },
+ desc = [=[
+ Sort the items in {list} in-place. Returns {list}.
+
+ If you want a list to remain unmodified make a copy first: >vim
+ let sortedlist = sort(copy(mylist))
+
+ <When {how} is omitted or is a string, then sort() uses the
+ string representation of each item to sort on. Numbers sort
+ after Strings, |Lists| after Numbers. For sorting text in the
+ current buffer use |:sort|.
+
+ When {how} is given and it is 'i' then case is ignored.
+ For backwards compatibility, the value one can be used to
+ ignore case. Zero means to not ignore case.
+
+ When {how} is given and it is 'l' then the current collation
+ locale is used for ordering. Implementation details: strcoll()
+ is used to compare strings. See |:language| check or set the
+ collation locale. |v:collate| can also be used to check the
+ current locale. Sorting using the locale typically ignores
+ case. Example: >vim
+ " ö is sorted similarly to o with English locale.
+ language collate en_US.UTF8
+ echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+ < ['n', 'o', 'O', 'ö', 'p', 'z'] ~
+ >vim
+ " ö is sorted after z with Swedish locale.
+ language collate sv_SE.UTF8
+ echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+ < ['n', 'o', 'O', 'p', 'z', 'ö'] ~
+ This does not work properly on Mac.
+
+ When {how} is given and it is 'n' then all items will be
+ sorted numerical (Implementation detail: this uses the
+ strtod() function to parse numbers, Strings, Lists, Dicts and
+ Funcrefs will be considered as being 0).
+
+ When {how} is given and it is 'N' then all items will be
+ sorted numerical. This is like 'n' but a string containing
+ digits will be used as the number they represent.
+
+ When {how} is given and it is 'f' then all items will be
+ sorted numerical. All values must be a Number or a Float.
+
+ When {how} is a |Funcref| or a function name, this function
+ is called to compare items. The function is invoked with two
+ items as argument and must return zero if they are equal, 1 or
+ bigger if the first one sorts after the second one, -1 or
+ smaller if the first one sorts before the second one.
+
+ {dict} is for functions with the "dict" attribute. It will be
+ used to set the local variable "self". |Dictionary-function|
+
+ The sort is stable, items which compare equal (as number or as
+ string) will keep their relative position. E.g., when sorting
+ on numbers, text strings will sort next to each other, in the
+ same order as they were originally.
+
+
+ Example: >vim
+ func MyCompare(i1, i2)
+ return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1
+ endfunc
+ eval mylist->sort("MyCompare")
+ <A shorter compare version for this specific simple case, which
+ ignores overflow: >vim
+ func MyCompare(i1, i2)
+ return a:i1 - a:i2
+ endfunc
+ <For a simple expression you can use a lambda: >vim
+ eval mylist->sort({i1, i2 -> i1 - i2})
+ <
+ ]=],
+ name = 'sort',
+ params = { { 'list', 'any' }, { 'how', 'any' }, { 'dict', 'any' } },
+ signature = 'sort({list} [, {how} [, {dict}]])',
+ },
+ soundfold = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the sound-folded equivalent of {word}. Uses the first
+ language in 'spelllang' for the current window that supports
+ soundfolding. 'spell' must be set. When no sound folding is
+ possible the {word} is returned unmodified.
+ This can be used for making spelling suggestions. Note that
+ the method can be quite slow.
+
+ ]=],
+ name = 'soundfold',
+ params = { { 'word', 'any' } },
+ signature = 'soundfold({word})',
+ },
+ spellbadword = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Without argument: The result is the badly spelled word under
+ or after the cursor. The cursor is moved to the start of the
+ bad word. When no bad word is found in the cursor line the
+ result is an empty string and the cursor doesn't move.
+
+ With argument: The result is the first word in {sentence} that
+ is badly spelled. If there are no spelling mistakes the
+ result is an empty string.
+
+ The return value is a list with two items:
+ - The badly spelled word or an empty string.
+ - The type of the spelling error:
+ "bad" spelling mistake
+ "rare" rare word
+ "local" word only valid in another region
+ "caps" word should start with Capital
+ Example: >vim
+ echo spellbadword("the quik brown fox")
+ < ['quik', 'bad'] ~
+
+ The spelling information for the current window and the value
+ of 'spelllang' are used.
+
+ ]=],
+ name = 'spellbadword',
+ params = { { 'sentence', 'any' } },
+ signature = 'spellbadword([{sentence}])',
+ },
+ spellsuggest = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Return a |List| with spelling suggestions to replace {word}.
+ When {max} is given up to this number of suggestions are
+ returned. Otherwise up to 25 suggestions are returned.
+
+ When the {capital} argument is given and it's non-zero only
+ suggestions with a leading capital will be given. Use this
+ after a match with 'spellcapcheck'.
+
+ {word} can be a badly spelled word followed by other text.
+ This allows for joining two words that were split. The
+ suggestions also include the following text, thus you can
+ replace a line.
+
+ {word} may also be a good word. Similar words will then be
+ returned. {word} itself is not included in the suggestions,
+ although it may appear capitalized.
+
+ The spelling information for the current window is used. The
+ values of 'spelllang' and 'spellsuggest' are used.
+
+ ]=],
+ name = 'spellsuggest',
+ params = { { 'word', 'any' }, { 'max', 'any' }, { 'capital', 'any' } },
+ signature = 'spellsuggest({word} [, {max} [, {capital}]])',
+ },
+ split = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Make a |List| out of {string}. When {pattern} is omitted or
+ empty each white-separated sequence of characters becomes an
+ item.
+ Otherwise the string is split where {pattern} matches,
+ removing the matched characters. 'ignorecase' is not used
+ here, add \c to ignore case. |/\c|
+ When the first or last item is empty it is omitted, unless the
+ {keepempty} argument is given and it's non-zero.
+ Other empty items are kept when {pattern} matches at least one
+ character or when {keepempty} is non-zero.
+ Example: >vim
+ let words = split(getline('.'), '\W\+')
+ <To split a string in individual characters: >vim
+ for c in split(mystring, '\zs') | endfor
+ <If you want to keep the separator you can also use '\zs' at
+ the end of the pattern: >vim
+ echo split('abc:def:ghi', ':\zs')
+ < >
+ ['abc:', 'def:', 'ghi']
+ <
+ Splitting a table where the first element can be empty: >vim
+ let items = split(line, ':', 1)
+ <The opposite function is |join()|.
+
+ ]=],
+ name = 'split',
+ params = { { 'string', 'string' }, { 'pattern', 'any' }, { 'keepempty', 'any' } },
+ signature = 'split({string} [, {pattern} [, {keepempty}]])',
+ },
+ sqrt = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the non-negative square root of Float {expr} as a
+ |Float|.
+ {expr} must evaluate to a |Float| or a |Number|. When {expr}
+ is negative the result is NaN (Not a Number). Returns 0.0 if
+ {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo sqrt(100)
+ < 10.0 >vim
+ echo sqrt(-4.01)
+ < str2float("nan")
+ NaN may be different, it depends on system libraries.
+
+ ]=],
+ float_func = 'sqrt',
+ name = 'sqrt',
+ params = { { 'expr', 'any' } },
+ signature = 'sqrt({expr})',
+ },
+ srand = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Initialize seed used by |rand()|:
+ - If {expr} is not given, seed values are initialized by
+ reading from /dev/urandom, if possible, or using time(NULL)
+ a.k.a. epoch time otherwise; this only has second accuracy.
+ - If {expr} is given it must be a Number. It is used to
+ initialize the seed values. This is useful for testing or
+ when a predictable sequence is intended.
+
+ Examples: >vim
+ let seed = srand()
+ let seed = srand(userinput)
+ echo rand(seed)
+ <
+ ]=],
+ name = 'srand',
+ params = { { 'expr', 'any' } },
+ signature = 'srand([{expr}])',
+ },
+ stdioopen = {
+ args = 1,
+ desc = [=[
+ With |--headless| this opens stdin and stdout as a |channel|.
+ May be called only once. See |channel-stdio|. stderr is not
+ handled by this function, see |v:stderr|.
+
+ Close the stdio handles with |chanclose()|. Use |chansend()|
+ to send data to stdout, and |rpcrequest()| and |rpcnotify()|
+ to communicate over RPC.
+
+ {opts} is a dictionary with these keys:
+ |on_stdin| : callback invoked when stdin is written to.
+ on_print : callback invoked when Nvim needs to print a
+ message, with the message (whose type is string)
+ as sole argument.
+ stdin_buffered : read stdin in |channel-buffered| mode.
+ rpc : If set, |msgpack-rpc| will be used to communicate
+ over stdio
+ Returns:
+ - |channel-id| on success (value is always 1)
+ - 0 on invalid arguments
+ ]=],
+ name = 'stdioopen',
+ params = { { 'opts', 'table' } },
+ signature = 'stdioopen({opts})',
+ },
+ stdpath = {
+ args = 1,
+ tags = { 'E6100' },
+ desc = [=[
+ Returns |standard-path| locations of various default files and
+ directories.
+
+ {what} Type Description ~
+ cache String Cache directory: arbitrary temporary
+ storage for plugins, etc.
+ config String User configuration directory. |init.vim|
+ is stored here.
+ config_dirs List Other configuration directories.
+ data String User data directory.
+ data_dirs List Other data directories.
+ log String Logs directory (for use by plugins too).
+ run String Run directory: temporary, local storage
+ for sockets, named pipes, etc.
+ state String Session state directory: storage for file
+ drafts, swap, undo, |shada|.
+
+ Example: >vim
+ echo stdpath("config")
+ <
+ ]=],
+ fast = true,
+ name = 'stdpath',
+ params = { { 'what', "'cache'|'config'|'config_dirs'|'data'|'data_dirs'|'log'|'run'|'state'" } },
+ returns = 'string|string[]',
+ signature = 'stdpath({what})',
+ },
+ state = {
+ args = {0, 1},
+ base = 1,
+ desc = [=[
+ Return a string which contains characters indicating the
+ current state. Mostly useful in callbacks that want to do
+ work that may not always be safe. Roughly this works like:
+ - callback uses state() to check if work is safe to do.
+ Yes: then do it right away.
+ No: add to work queue and add a |SafeState| autocommand.
+ - When SafeState is triggered and executes your autocommand,
+ check with `state()` if the work can be done now, and if yes
+ remove it from the queue and execute.
+ Remove the autocommand if the queue is now empty.
+ Also see |mode()|.
+
+ When {what} is given only characters in this string will be
+ added. E.g, this checks if the screen has scrolled: >vim
+ if state('s') == ''
+ " screen has not scrolled
+ <
+ These characters indicate the state, generally indicating that
+ something is busy:
+ m halfway a mapping, :normal command, feedkeys() or
+ stuffed command
+ o operator pending, e.g. after |d|
+ a Insert mode autocomplete active
+ x executing an autocommand
+ S not triggering SafeState, e.g. after |f| or a count
+ c callback invoked, including timer (repeats for
+ recursiveness up to "ccc")
+ s screen has scrolled for messages
+ ]=],
+ fast = true,
+ name = 'state',
+ params = { { 'what', 'string' } },
+ signature = 'state([{what}])',
+ },
+ str2float = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert String {string} to a Float. This mostly works the
+ same as when using a floating point number in an expression,
+ see |floating-point-format|. But it's a bit more permissive.
+ E.g., "1e40" is accepted, while in an expression you need to
+ write "1.0e40". The hexadecimal form "0x123" is also
+ accepted, but not others, like binary or octal.
+ When {quoted} is present and non-zero then embedded single
+ quotes before the dot are ignored, thus "1'000.0" is a
+ thousand.
+ Text after the number is silently ignored.
+ The decimal point is always '.', no matter what the locale is
+ set to. A comma ends the number: "12,345.67" is converted to
+ 12.0. You can strip out thousands separators with
+ |substitute()|: >vim
+ let f = str2float(substitute(text, ',', '', 'g'))
+ <
+ Returns 0.0 if the conversion fails.
+
+ ]=],
+ name = 'str2float',
+ params = { { 'string', 'string' }, { 'quoted', 'any' } },
+ signature = 'str2float({string} [, {quoted}])',
+ },
+ str2list = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return a list containing the number values which represent
+ each character in String {string}. Examples: >vim
+ echo str2list(" ") " returns [32]
+ echo str2list("ABC") " returns [65, 66, 67]
+ <|list2str()| does the opposite.
+
+ UTF-8 encoding is always used, {utf8} option has no effect,
+ and exists only for backwards-compatibility.
+ With UTF-8 composing characters are handled properly: >vim
+ echo str2list("aÌ") " returns [97, 769]
+
+ ]=],
+ name = 'str2list',
+ params = { { 'string', 'string' }, { 'utf8', 'any' } },
+ signature = 'str2list({string} [, {utf8}])',
+ },
+ str2nr = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Convert string {string} to a number.
+ {base} is the conversion base, it can be 2, 8, 10 or 16.
+ When {quoted} is present and non-zero then embedded single
+ quotes are ignored, thus "1'000'000" is a million.
+
+ When {base} is omitted base 10 is used. This also means that
+ a leading zero doesn't cause octal conversion to be used, as
+ with the default String to Number conversion. Example: >vim
+ let nr = str2nr('0123')
+ <
+ When {base} is 16 a leading "0x" or "0X" is ignored. With a
+ different base the result will be zero. Similarly, when
+ {base} is 8 a leading "0", "0o" or "0O" is ignored, and when
+ {base} is 2 a leading "0b" or "0B" is ignored.
+ Text after the number is silently ignored.
+
+ Returns 0 if {string} is empty or on error.
+
+ ]=],
+ name = 'str2nr',
+ params = { { 'string', 'string' }, { 'base', 'any' } },
+ signature = 'str2nr({string} [, {base}])',
+ },
+ strcharlen = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of characters
+ in String {string}. Composing characters are ignored.
+ |strchars()| can count the number of characters, counting
+ composing characters separately.
+
+ Returns 0 if {string} is empty or on error.
+
+ Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
+
+ ]=],
+ name = 'strcharlen',
+ params = { { 'string', 'string' } },
+ signature = 'strcharlen({string})',
+ },
+ strcharpart = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Like |strpart()| but using character index and length instead
+ of byte index and length.
+ When {skipcc} is omitted or zero, composing characters are
+ counted separately.
+ When {skipcc} set to 1, Composing characters are ignored,
+ similar to |slice()|.
+ When a character index is used where a character does not
+ exist it is omitted and counted as one character. For
+ example: >vim
+ echo strcharpart('abc', -1, 2)
+ <results in 'a'.
+
+ Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'strcharpart',
+ params = { { 'src', 'any' }, { 'start', 'any' }, { 'len', 'any' }, { 'skipcc', 'any' } },
+ signature = 'strcharpart({src}, {start} [, {len} [, {skipcc}]])',
+ },
+ strchars = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of characters
+ in String {string}.
+ When {skipcc} is omitted or zero, composing characters are
+ counted separately.
+ When {skipcc} set to 1, Composing characters are ignored.
+ |strcharlen()| always does this.
+
+ Returns zero on error.
+
+ Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
+
+ {skipcc} is only available after 7.4.755. For backward
+ compatibility, you can define a wrapper function: >vim
+ if has("patch-7.4.755")
+ function s:strchars(str, skipcc)
+ return strchars(a:str, a:skipcc)
+ endfunction
+ else
+ function s:strchars(str, skipcc)
+ if a:skipcc
+ return strlen(substitute(a:str, ".", "x", "g"))
+ else
+ return strchars(a:str)
+ endif
+ endfunction
+ endif
+ <
+ ]=],
+ name = 'strchars',
+ params = { { 'string', 'string' }, { 'skipcc', 'any' } },
+ returns = 'integer',
+ signature = 'strchars({string} [, {skipcc}])',
+ },
+ strdisplaywidth = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of display cells
+ String {string} occupies on the screen when it starts at {col}
+ (first column is zero). When {col} is omitted zero is used.
+ Otherwise it is the screen column where to start. This
+ matters for Tab characters.
+ The option settings of the current window are used. This
+ matters for anything that's displayed differently, such as
+ 'tabstop' and 'display'.
+ When {string} contains characters with East Asian Width Class
+ Ambiguous, this function's return value depends on 'ambiwidth'.
+ Returns zero on error.
+ Also see |strlen()|, |strwidth()| and |strchars()|.
+
+ ]=],
+ name = 'strdisplaywidth',
+ params = { { 'string', 'string' }, { 'col', 'integer' } },
+ returns = 'integer',
+ signature = 'strdisplaywidth({string} [, {col}])',
+ },
+ strftime = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is a formatted date and time, as
+ specified by the {format} string. The given {time} is used,
+ or the current time if no time is given. The accepted
+ {format} depends on your system, thus this is not portable!
+ See the manual page of the C function strftime() for the
+ format. The maximum length of the result is 80 characters.
+ See also |localtime()|, |getftime()| and |strptime()|.
+ The language can be changed with the |:language| command.
+ Examples: >vim
+ echo strftime("%c") " Sun Apr 27 11:49:23 1997
+ echo strftime("%Y %b %d %X") " 1997 Apr 27 11:53:25
+ echo strftime("%y%m%d %T") " 970427 11:53:55
+ echo strftime("%H:%M") " 11:55
+ echo strftime("%c", getftime("file.c"))
+ " Show mod time of file.c.
+
+ ]=],
+ name = 'strftime',
+ params = { { 'format', 'any' }, { 'time', 'any' } },
+ returns = 'string',
+ signature = 'strftime({format} [, {time}])',
+ },
+ strgetchar = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Get a Number corresponding to the character at {index} in
+ {str}. This uses a zero-based character index, not a byte
+ index. Composing characters are considered separate
+ characters here. Use |nr2char()| to convert the Number to a
+ String.
+ Returns -1 if {index} is invalid.
+ Also see |strcharpart()| and |strchars()|.
+
+ ]=],
+ name = 'strgetchar',
+ params = { { 'str', 'string' }, { 'index', 'integer' } },
+ returns = 'integer',
+ signature = 'strgetchar({str}, {index})',
+ },
+ stridx = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which gives the byte index in
+ {haystack} of the first occurrence of the String {needle}.
+ If {start} is specified, the search starts at index {start}.
+ This can be used to find a second match: >vim
+ let colon1 = stridx(line, ":")
+ let colon2 = stridx(line, ":", colon1 + 1)
+ <The search is done case-sensitive.
+ For pattern searches use |match()|.
+ -1 is returned if the {needle} does not occur in {haystack}.
+ See also |strridx()|.
+ Examples: >vim
+ echo stridx("An Example", "Example") " 3
+ echo stridx("Starting point", "Start") " 0
+ echo stridx("Starting point", "start") " -1
+ < *strstr()* *strchr()*
+ stridx() works similar to the C function strstr(). When used
+ with a single character it works similar to strchr().
+
+ ]=],
+ fast = true,
+ name = 'stridx',
+ params = { { 'haystack', 'string' }, { 'needle', 'string' }, { 'start', 'integer' } },
+ returns = 'integer',
+ signature = 'stridx({haystack}, {needle} [, {start}])',
+ },
+ string = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return {expr} converted to a String. If {expr} is a Number,
+ Float, String, Blob or a composition of them, then the result
+ can be parsed back with |eval()|.
+ {expr} type result ~
+ String 'string'
+ Number 123
+ Float 123.123456 or 1.123456e8 or
+ `str2float('inf')`
+ Funcref `function('name')`
+ Blob 0z00112233.44556677.8899
+ List [item, item]
+ Dictionary `{key: value, key: value}`
+ Note that in String values the ' character is doubled.
+ Also see |strtrans()|.
+ Note 2: Output format is mostly compatible with YAML, except
+ for infinite and NaN floating-point values representations
+ which use |str2float()|. Strings are also dumped literally,
+ only single quote is escaped, which does not allow using YAML
+ for parsing back binary strings. |eval()| should always work for
+ strings and floats though and this is the only official
+ method, use |msgpackdump()| or |json_encode()| if you need to
+ share data with other application.
+
+ ]=],
+ name = 'string',
+ params = { { 'expr', 'any' } },
+ returns = 'string',
+ signature = 'string({expr})',
+ },
+ strlen = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the length of the String
+ {string} in bytes.
+ If the argument is a Number it is first converted to a String.
+ For other types an error is given and zero is returned.
+ If you want to count the number of multibyte characters use
+ |strchars()|.
+ Also see |len()|, |strdisplaywidth()| and |strwidth()|.
+
+ ]=],
+ name = 'strlen',
+ params = { { 'string', 'string' } },
+ returns = 'integer',
+ signature = 'strlen({string})',
+ },
+ strpart = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is part of {src}, starting from
+ byte {start}, with the byte length {len}.
+ When {chars} is present and TRUE then {len} is the number of
+ characters positions (composing characters are not counted
+ separately, thus "1" means one base character and any
+ following composing characters).
+ To count {start} as characters instead of bytes use
+ |strcharpart()|.
+
+ When bytes are selected which do not exist, this doesn't
+ result in an error, the bytes are simply omitted.
+ If {len} is missing, the copy continues from {start} till the
+ end of the {src}. >vim
+ echo strpart("abcdefg", 3, 2) " returns 'de'
+ echo strpart("abcdefg", -2, 4) " returns 'ab'
+ echo strpart("abcdefg", 5, 4) " returns 'fg'
+ echo strpart("abcdefg", 3) " returns 'defg'
+
+ <Note: To get the first character, {start} must be 0. For
+ example, to get the character under the cursor: >vim
+ strpart(getline("."), col(".") - 1, 1, v:true)
+ <
+ Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'strpart',
+ params = {
+ { 'src', 'string' },
+ { 'start', 'integer' },
+ { 'len', 'integer' },
+ { 'chars', '0|1' },
+ },
+ returns = 'string',
+ signature = 'strpart({src}, {start} [, {len} [, {chars}]])',
+ },
+ strptime = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is a unix timestamp representing
+ the date and time in {timestring}, which is expected to match
+ the format specified in {format}.
+
+ The accepted {format} depends on your system, thus this is not
+ portable! See the manual page of the C function strptime()
+ for the format. Especially avoid "%c". The value of $TZ also
+ matters.
+
+ If the {timestring} cannot be parsed with {format} zero is
+ returned. If you do not know the format of {timestring} you
+ can try different {format} values until you get a non-zero
+ result.
+
+ See also |strftime()|.
+ Examples: >vim
+ echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23")
+ < 862156163 >vim
+ echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55"))
+ < Sun Apr 27 11:53:55 1997 >vim
+ echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600)
+ < Sun Apr 27 12:53:55 1997
+
+ ]=],
+ name = 'strptime',
+ params = { { 'format', 'string' }, { 'timestring', 'string' } },
+ returns = 'integer',
+ signature = 'strptime({format}, {timestring})',
+ },
+ strridx = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which gives the byte index in
+ {haystack} of the last occurrence of the String {needle}.
+ When {start} is specified, matches beyond this index are
+ ignored. This can be used to find a match before a previous
+ match: >vim
+ let lastcomma = strridx(line, ",")
+ let comma2 = strridx(line, ",", lastcomma - 1)
+ <The search is done case-sensitive.
+ For pattern searches use |match()|.
+ -1 is returned if the {needle} does not occur in {haystack}.
+ If the {needle} is empty the length of {haystack} is returned.
+ See also |stridx()|. Examples: >vim
+ echo strridx("an angry armadillo", "an") 3
+ < *strrchr()*
+ When used with a single character it works similar to the C
+ function strrchr().
+
+ ]=],
+ name = 'strridx',
+ params = {
+ { 'haystack', 'string' },
+ { 'needle', 'string' },
+ { 'start', 'integer' },
+ },
+ returns = 'integer',
+ signature = 'strridx({haystack}, {needle} [, {start}])',
+ },
+ strtrans = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a String, which is {string} with all unprintable
+ characters translated into printable characters |'isprint'|.
+ Like they are shown in a window. Example: >vim
+ echo strtrans(@a)
+ <This displays a newline in register a as "^@" instead of
+ starting a new line.
+
+ Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'strtrans',
+ params = { { 'string', 'string' } },
+ returns = 'string',
+ signature = 'strtrans({string})',
+ },
+ strutf16len = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of UTF-16 code
+ units in String {string} (after converting it to UTF-16).
+
+ When {countcc} is TRUE, composing characters are counted
+ separately.
+ When {countcc} is omitted or FALSE, composing characters are
+ ignored.
+
+ Returns zero on error.
+
+ Also see |strlen()| and |strcharlen()|.
+ Examples: >vim
+ echo strutf16len('a') " returns 1
+ echo strutf16len('©') " returns 1
+ echo strutf16len('😊') " returns 2
+ echo strutf16len('ąÌ') " returns 1
+ echo strutf16len('ąÌ', v:true) " returns 3
+ <
+ ]=],
+ name = 'strutf16len',
+ params = { { 'string', 'string' }, { 'countcc', '0|1' } },
+ returns = 'integer',
+ signature = 'strutf16len({string} [, {countcc}])',
+ },
+ strwidth = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of display cells
+ String {string} occupies. A Tab character is counted as one
+ cell, alternatively use |strdisplaywidth()|.
+ When {string} contains characters with East Asian Width Class
+ Ambiguous, this function's return value depends on 'ambiwidth'.
+ Returns zero on error.
+ Also see |strlen()|, |strdisplaywidth()| and |strchars()|.
+
+ ]=],
+ fast = true,
+ name = 'strwidth',
+ params = { { 'string', 'string' } },
+ returns = 'integer',
+ signature = 'strwidth({string})',
+ },
+ submatch = {
+ args = { 1, 2 },
+ base = 1,
+ tags = { 'E935' },
+ desc = [=[
+ Only for an expression in a |:substitute| command or
+ substitute() function.
+ Returns the {nr}th submatch of the matched text. When {nr}
+ is 0 the whole matched text is returned.
+ Note that a NL in the string can stand for a line break of a
+ multi-line match or a NUL character in the text.
+ Also see |sub-replace-expression|.
+
+ If {list} is present and non-zero then submatch() returns
+ a list of strings, similar to |getline()| with two arguments.
+ NL characters in the text represent NUL characters in the
+ text.
+ Only returns more than one item for |:substitute|, inside
+ |substitute()| this list will always contain one or zero
+ items, since there are no real line breaks.
+
+ When substitute() is used recursively only the submatches in
+ the current (deepest) call can be obtained.
+
+ Returns an empty string or list on error.
+
+ Examples: >vim
+ s/\d\+/\=submatch(0) + 1/
+ echo substitute(text, '\d\+', '\=submatch(0) + 1', '')
+ <This finds the first number in the line and adds one to it.
+ A line break is included as a newline character.
+
+ ]=],
+ name = 'submatch',
+ params = { { 'nr', 'integer' }, { 'list', 'integer' } },
+ returns = 'string|string[]',
+ signature = 'submatch({nr} [, {list}])',
+ },
+ substitute = {
+ args = 4,
+ base = 1,
+ desc = [=[
+ The result is a String, which is a copy of {string}, in which
+ the first match of {pat} is replaced with {sub}.
+ When {flags} is "g", all matches of {pat} in {string} are
+ replaced. Otherwise {flags} should be "".
+
+ This works like the ":substitute" command (without any flags).
+ But the matching with {pat} is always done like the 'magic'
+ option is set and 'cpoptions' is empty (to make scripts
+ portable). 'ignorecase' is still relevant, use |/\c| or |/\C|
+ if you want to ignore or match case and ignore 'ignorecase'.
+ 'smartcase' is not used. See |string-match| for how {pat} is
+ used.
+
+ A "~" in {sub} is not replaced with the previous {sub}.
+ Note that some codes in {sub} have a special meaning
+ |sub-replace-special|. For example, to replace something with
+ "\n" (two characters), use "\\\\n" or '\\n'.
+
+ When {pat} does not match in {string}, {string} is returned
+ unmodified.
+
+ Example: >vim
+ let &path = substitute(&path, ",\\=[^,]*$", "", "")
+ <This removes the last component of the 'path' option. >vim
+ echo substitute("testing", ".*", "\\U\\0", "")
+ <results in "TESTING".
+
+ When {sub} starts with "\=", the remainder is interpreted as
+ an expression. See |sub-replace-expression|. Example: >vim
+ echo substitute(s, '%\(\x\x\)',
+ \ '\=nr2char("0x" .. submatch(1))', 'g')
+
+ <When {sub} is a Funcref that function is called, with one
+ optional argument. Example: >vim
+ echo substitute(s, '%\(\x\x\)', SubNr, 'g')
+ <The optional argument is a list which contains the whole
+ matched string and up to nine submatches, like what
+ |submatch()| returns. Example: >vim
+ echo substitute(s, '%\(\x\x\)', {m -> '0x' .. m[1]}, 'g')
+
+ <Returns an empty string on error.
+
+ ]=],
+ name = 'substitute',
+ params = {
+ { 'string', 'string' },
+ { 'pat', 'string' },
+ { 'sub', 'string' },
+ { 'flags', 'string' },
+ },
+ returns = 'string',
+ signature = 'substitute({string}, {pat}, {sub}, {flags})',
+ },
+ swapfilelist = {
+ desc = [=[
+ Returns a list of swap file names, like what "vim -r" shows.
+ See the |-r| command argument. The 'directory' option is used
+ for the directories to inspect. If you only want to get a
+ list of swap files in the current directory then temporarily
+ set 'directory' to a dot: >vim
+ let save_dir = &directory
+ let &directory = '.'
+ let swapfiles = swapfilelist()
+ let &directory = save_dir
+ ]=],
+ name = 'swapfilelist',
+ params = {},
+ returns = 'string[]',
+ signature = 'swapfilelist()',
+ },
+ swapinfo = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a dictionary, which holds information about the
+ swapfile {fname}. The available fields are:
+ version Vim version
+ user user name
+ host host name
+ fname original file name
+ pid PID of the Nvim process that created the swap
+ file, or zero if not running.
+ mtime last modification time in seconds
+ inode Optional: INODE number of the file
+ dirty 1 if file was modified, 0 if not
+ In case of failure an "error" item is added with the reason:
+ Cannot open file: file not found or in accessible
+ Cannot read file: cannot read first block
+ Not a swap file: does not contain correct block ID
+ Magic number mismatch: Info in first block is invalid
+
+ ]=],
+ name = 'swapinfo',
+ params = { { 'fname', 'string' } },
+ signature = 'swapinfo({fname})',
+ },
+ swapname = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is the swap file path of the buffer {buf}.
+ For the use of {buf}, see |bufname()| above.
+ If buffer {buf} is the current buffer, the result is equal to
+ |:swapname| (unless there is no swap file).
+ If buffer {buf} has no swap file, returns an empty string.
+
+ ]=],
+ name = 'swapname',
+ params = { { 'buf', 'integer|string' } },
+ returns = 'string',
+ signature = 'swapname({buf})',
+ },
+ synID = {
+ args = 3,
+ desc = [=[
+ The result is a Number, which is the syntax ID at the position
+ {lnum} and {col} in the current window.
+ The syntax ID can be used with |synIDattr()| and
+ |synIDtrans()| to obtain syntax information about text.
+
+ {col} is 1 for the leftmost column, {lnum} is 1 for the first
+ line. 'synmaxcol' applies, in a longer line zero is returned.
+ Note that when the position is after the last character,
+ that's where the cursor can be in Insert mode, synID() returns
+ zero. {lnum} is used like with |getline()|.
+
+ When {trans} is |TRUE|, transparent items are reduced to the
+ item that they reveal. This is useful when wanting to know
+ the effective color. When {trans} is |FALSE|, the transparent
+ item is returned. This is useful when wanting to know which
+ syntax item is effective (e.g. inside parens).
+ Warning: This function can be very slow. Best speed is
+ obtained by going through the file in forward direction.
+
+ Returns zero on error.
+
+ Example (echoes the name of the syntax item under the cursor): >vim
+ echo synIDattr(synID(line("."), col("."), 1), "name")
+ <
+ ]=],
+ name = 'synID',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'trans', '0|1' } },
+ returns = 'integer',
+ signature = 'synID({lnum}, {col}, {trans})',
+ },
+ synIDattr = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is the {what} attribute of
+ syntax ID {synID}. This can be used to obtain information
+ about a syntax item.
+ {mode} can be "gui" or "cterm", to get the attributes
+ for that mode. When {mode} is omitted, or an invalid value is
+ used, the attributes for the currently active highlighting are
+ used (GUI or cterm).
+ Use synIDtrans() to follow linked highlight groups.
+ {what} result
+ "name" the name of the syntax item
+ "fg" foreground color (GUI: color name used to set
+ the color, cterm: color number as a string,
+ term: empty string)
+ "bg" background color (as with "fg")
+ "font" font name (only available in the GUI)
+ |highlight-font|
+ "sp" special color (as with "fg") |guisp|
+ "fg#" like "fg", but for the GUI and the GUI is
+ running the name in "#RRGGBB" form
+ "bg#" like "fg#" for "bg"
+ "sp#" like "fg#" for "sp"
+ "bold" "1" if bold
+ "italic" "1" if italic
+ "reverse" "1" if reverse
+ "inverse" "1" if inverse (= reverse)
+ "standout" "1" if standout
+ "underline" "1" if underlined
+ "undercurl" "1" if undercurled
+ "underdouble" "1" if double underlined
+ "underdotted" "1" if dotted underlined
+ "underdashed" "1" if dashed underlined
+ "strikethrough" "1" if struckthrough
+ "altfont" "1" if alternative font
+ "nocombine" "1" if nocombine
+
+ Returns an empty string on error.
+
+ Example (echoes the color of the syntax item under the
+ cursor): >vim
+ echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg")
+ <
+ Can also be used as a |method|: >vim
+ echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg")
+ <
+ ]=],
+ name = 'synIDattr',
+ params = { { 'synID', 'integer' }, { 'what', 'string' }, { 'mode', 'string' } },
+ returns = 'string',
+ signature = 'synIDattr({synID}, {what} [, {mode}])',
+ },
+ synIDtrans = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the translated syntax ID of
+ {synID}. This is the syntax group ID of what is being used to
+ highlight the character. Highlight links given with
+ ":highlight link" are followed.
+
+ Returns zero on error.
+
+ ]=],
+ name = 'synIDtrans',
+ params = { { 'synID', 'integer' } },
+ returns = 'integer',
+ signature = 'synIDtrans({synID})',
+ },
+ synconcealed = {
+ args = 2,
+ desc = [=[
+ The result is a |List| with currently three items:
+ 1. The first item in the list is 0 if the character at the
+ position {lnum} and {col} is not part of a concealable
+ region, 1 if it is. {lnum} is used like with |getline()|.
+ 2. The second item in the list is a string. If the first item
+ is 1, the second item contains the text which will be
+ displayed in place of the concealed text, depending on the
+ current setting of 'conceallevel' and 'listchars'.
+ 3. The third and final item in the list is a number
+ representing the specific syntax region matched in the
+ line. When the character is not concealed the value is
+ zero. This allows detection of the beginning of a new
+ concealable region if there are two consecutive regions
+ with the same replacement character. For an example, if
+ the text is "123456" and both "23" and "45" are concealed
+ and replaced by the character "X", then:
+ call returns ~
+ synconcealed(lnum, 1) [0, '', 0]
+ synconcealed(lnum, 2) [1, 'X', 1]
+ synconcealed(lnum, 3) [1, 'X', 1]
+ synconcealed(lnum, 4) [1, 'X', 2]
+ synconcealed(lnum, 5) [1, 'X', 2]
+ synconcealed(lnum, 6) [0, '', 0]
+ ]=],
+ name = 'synconcealed',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
+ returns = '{[1]: integer, [2]: string, [3]: integer}[]',
+ signature = 'synconcealed({lnum}, {col})',
+ },
+ synstack = {
+ args = 2,
+ desc = [=[
+ Return a |List|, which is the stack of syntax items at the
+ position {lnum} and {col} in the current window. {lnum} is
+ used like with |getline()|. Each item in the List is an ID
+ like what |synID()| returns.
+ The first item in the List is the outer region, following are
+ items contained in that one. The last one is what |synID()|
+ returns, unless not the whole item is highlighted or it is a
+ transparent item.
+ This function is useful for debugging a syntax file.
+ Example that shows the syntax stack under the cursor: >vim
+ for id in synstack(line("."), col("."))
+ echo synIDattr(id, "name")
+ endfor
+ <When the position specified with {lnum} and {col} is invalid
+ an empty list is returned. The position just after the last
+ character in a line and the first column in an empty line are
+ valid positions.
+ ]=],
+ name = 'synstack',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
+ returns = 'integer[]',
+ signature = 'synstack({lnum}, {col})',
+ },
+ system = {
+ args = { 1, 2 },
+ base = 1,
+ tags = { 'E677' },
+ desc = [=[
+ Note: Prefer |vim.system()| in Lua.
+
+ Gets the output of {cmd} as a |string| (|systemlist()| returns
+ a |List|) and sets |v:shell_error| to the error code.
+ {cmd} is treated as in |jobstart()|:
+ If {cmd} is a List it runs directly (no 'shell').
+ If {cmd} is a String it runs in the 'shell', like this: >vim
+ call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+
+ <Not to be used for interactive commands.
+
+ Result is a String, filtered to avoid platform-specific quirks:
+ - <CR><NL> is replaced with <NL>
+ - NUL characters are replaced with SOH (0x01)
+
+ Example: >vim
+ echo system(['ls', expand('%:h')])
+
+ <If {input} is a string it is written to a pipe and passed as
+ stdin to the command. The string is written as-is, line
+ separators are not changed.
+ If {input} is a |List| it is written to the pipe as
+ |writefile()| does with {binary} set to "b" (i.e. with
+ a newline between each list item, and newlines inside list
+ items converted to NULs).
+ When {input} is given and is a valid buffer id, the content of
+ the buffer is written to the file line by line, each line
+ terminated by NL (and NUL where the text has NL).
+ *E5677*
+ Note: system() cannot write to or read from backgrounded ("&")
+ shell commands, e.g.: >vim
+ echo system("cat - &", "foo")
+ <which is equivalent to: >
+ $ echo foo | bash -c 'cat - &'
+ <The pipes are disconnected (unless overridden by shell
+ redirection syntax) before input can reach it. Use
+ |jobstart()| instead.
+
+ Note: Use |shellescape()| or |::S| with |expand()| or
+ |fnamemodify()| to escape special characters in a command
+ argument. 'shellquote' and 'shellxquote' must be properly
+ configured. Example: >vim
+ echo system('ls '..shellescape(expand('%:h')))
+ echo system('ls '..expand('%:h:S'))
+
+ <Unlike ":!cmd" there is no automatic check for changed files.
+ Use |:checktime| to force a check.
+
+ ]=],
+ name = 'system',
+ params = {
+ { 'cmd', 'string|string[]' },
+ { 'input', 'string|string[]|integer' },
+ },
+ returns = 'string',
+ signature = 'system({cmd} [, {input}])',
+ },
+ systemlist = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Same as |system()|, but returns a |List| with lines (parts of
+ output separated by NL) with NULs transformed into NLs. Output
+ is the same as |readfile()| will output with {binary} argument
+ set to "b", except that a final newline is not preserved,
+ unless {keepempty} is non-zero.
+ Note that on MS-Windows you may get trailing CR characters.
+
+ To see the difference between "echo hello" and "echo -n hello"
+ use |system()| and |split()|: >vim
+ echo split(system('echo hello'), '\n', 1)
+ <
+ Returns an empty string on error.
+
+ ]=],
+ name = 'systemlist',
+ params = {
+ { 'cmd', 'string|string[]' },
+ { 'input', 'string|string[]|integer' },
+ { 'keepempty', 'integer' },
+ },
+ -- TODO(lewis6991): Not sure the '' return case is possible via vim.fn
+ -- returns = "string[]|''",
+ returns = 'string[]',
+ signature = 'systemlist({cmd} [, {input} [, {keepempty}]])',
+ },
+ tabpagebuflist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a |List|, where each item is the number of the
+ buffer associated with each window in the current tab page.
+ {arg} specifies the number of the tab page to be used. When
+ omitted the current tab page is used.
+ When {arg} is invalid the number zero is returned.
+ To get a list of all buffers in all tabs use this: >vim
+ let buflist = []
+ for i in range(tabpagenr('$'))
+ call extend(buflist, tabpagebuflist(i + 1))
+ endfor
+ <Note that a buffer may appear in more than one window.
+
+ ]=],
+ name = 'tabpagebuflist',
+ params = { { 'arg', 'any' } },
+ signature = 'tabpagebuflist([{arg}])',
+ },
+ tabpagenr = {
+ args = { 0, 1 },
+ desc = [=[
+ The result is a Number, which is the number of the current
+ tab page. The first tab page has number 1.
+
+ The optional argument {arg} supports the following values:
+ $ the number of the last tab page (the tab page
+ count).
+ # the number of the last accessed tab page
+ (where |g<Tab>| goes to). If there is no
+ previous tab page, 0 is returned.
+ The number can be used with the |:tab| command.
+
+ Returns zero on error.
+ ]=],
+ name = 'tabpagenr',
+ params = { { 'arg', "'$'|'#'" } },
+ returns = 'integer',
+ signature = 'tabpagenr([{arg}])',
+ },
+ tabpagewinnr = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Like |winnr()| but for tab page {tabarg}.
+ {tabarg} specifies the number of tab page to be used.
+ {arg} is used like with |winnr()|:
+ - When omitted the current window number is returned. This is
+ the window which will be used when going to this tab page.
+ - When "$" the number of windows is returned.
+ - When "#" the previous window nr is returned.
+ Useful examples: >vim
+ tabpagewinnr(1) " current window of tab page 1
+ tabpagewinnr(4, '$') " number of windows in tab page 4
+ <When {tabarg} is invalid zero is returned.
+
+ ]=],
+ name = 'tabpagewinnr',
+ params = { { 'tabarg', 'integer' }, { 'arg', "'$'|'#'" } },
+ returns = 'integer',
+ signature = 'tabpagewinnr({tabarg} [, {arg}])',
+ },
+ tagfiles = {
+ desc = [=[
+ Returns a |List| with the file names used to search for tags
+ for the current buffer. This is the 'tags' option expanded.
+ ]=],
+ name = 'tagfiles',
+ params = {},
+ returns = 'string[]',
+ signature = 'tagfiles()',
+ },
+ taglist = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Returns a |List| of tags matching the regular expression {expr}.
+
+ If {filename} is passed it is used to prioritize the results
+ in the same way that |:tselect| does. See |tag-priority|.
+ {filename} should be the full path of the file.
+
+ Each list item is a dictionary with at least the following
+ entries:
+ name Name of the tag.
+ filename Name of the file where the tag is
+ defined. It is either relative to the
+ current directory or a full path.
+ cmd Ex command used to locate the tag in
+ the file.
+ kind Type of the tag. The value for this
+ entry depends on the language specific
+ kind values. Only available when
+ using a tags file generated by
+ Universal/Exuberant ctags or hdrtag.
+ static A file specific tag. Refer to
+ |static-tag| for more information.
+ More entries may be present, depending on the content of the
+ tags file: access, implementation, inherits and signature.
+ Refer to the ctags documentation for information about these
+ fields. For C code the fields "struct", "class" and "enum"
+ may appear, they give the name of the entity the tag is
+ contained in.
+
+ The ex-command "cmd" can be either an ex search pattern, a
+ line number or a line number followed by a byte number.
+
+ If there are no matching tags, then an empty list is returned.
+
+ To get an exact tag match, the anchors '^' and '$' should be
+ used in {expr}. This also make the function work faster.
+ Refer to |tag-regexp| for more information about the tag
+ search regular expression pattern.
+
+ Refer to |'tags'| for information about how the tags file is
+ located by Vim. Refer to |tags-file-format| for the format of
+ the tags file generated by the different ctags tools.
+
+ ]=],
+ name = 'taglist',
+ params = { { 'expr', 'any' }, { 'filename', 'string' } },
+ signature = 'taglist({expr} [, {filename}])',
+ },
+ tan = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the tangent of {expr}, measured in radians, as a |Float|
+ in the range [-inf, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo tan(10)
+ < 0.648361 >vim
+ echo tan(-4.01)
+ < -1.181502
+
+ ]=],
+ float_func = 'tan',
+ name = 'tan',
+ params = { { 'expr', 'number' } },
+ returns = 'number',
+ signature = 'tan({expr})',
+ },
+ tanh = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the hyperbolic tangent of {expr} as a |Float| in the
+ range [-1, 1].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo tanh(0.5)
+ < 0.462117 >vim
+ echo tanh(-1)
+ < -0.761594
+
+ ]=],
+ float_func = 'tanh',
+ name = 'tanh',
+ params = { { 'expr', 'number' } },
+ returns = 'number',
+ signature = 'tanh({expr})',
+ },
+ tempname = {
+ desc = [=[
+ Generates a (non-existent) filename located in the Nvim root
+ |tempdir|. Scripts can use the filename as a temporary file.
+ Example: >vim
+ let tmpfile = tempname()
+ exe "redir > " .. tmpfile
+ <
+ ]=],
+ name = 'tempname',
+ params = {},
+ returns = 'string',
+ signature = 'tempname()',
+ },
+ termopen = {
+ args = { 1, 2 },
+ desc = [=[
+ Spawns {cmd} in a new pseudo-terminal session connected
+ to the current (unmodified) buffer. Parameters and behavior
+ are the same as |jobstart()| except "pty", "width", "height",
+ and "TERM" are ignored: "height" and "width" are taken from
+ the current window. Note that termopen() implies a "pty" arg
+ to jobstart(), and thus has the implications documented at
+ |jobstart()|.
+
+ Returns the same values as jobstart().
+
+ Terminal environment is initialized as in |jobstart-env|,
+ except $TERM is set to "xterm-256color". Full behavior is
+ described in |terminal|.
+ ]=],
+ name = 'termopen',
+ params = { { 'cmd', 'any' }, { 'opts', 'table' } },
+ signature = 'termopen({cmd} [, {opts}])',
+ },
+ test_garbagecollect_now = {
+ args = 0,
+ desc = [=[
+ Like |garbagecollect()|, but executed right away. This must
+ only be called directly to avoid any structure to exist
+ internally, and |v:testing| must have been set before calling
+ any function.
+ ]=],
+ params = {},
+ signature = 'test_garbagecollect_now()',
+ lua = false,
+ },
+ test_write_list_log = {
+ args = 1,
+ params = { { 'fname', 'string' } },
+ signature = '',
+ lua = false,
+ },
+ timer_info = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return a list with information about timers.
+ When {id} is given only information about this timer is
+ returned. When timer {id} does not exist an empty list is
+ returned.
+ When {id} is omitted information about all timers is returned.
+
+ For each timer the information is stored in a |Dictionary| with
+ these items:
+ "id" the timer ID
+ "time" time the timer was started with
+ "repeat" number of times the timer will still fire;
+ -1 means forever
+ "callback" the callback
+
+ ]=],
+ name = 'timer_info',
+ params = { { 'id', 'any' } },
+ signature = 'timer_info([{id}])',
+ },
+ timer_pause = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Pause or unpause a timer. A paused timer does not invoke its
+ callback when its time expires. Unpausing a timer may cause
+ the callback to be invoked almost immediately if enough time
+ has passed.
+
+ Pausing a timer is useful to avoid the callback to be called
+ for a short time.
+
+ If {paused} evaluates to a non-zero Number or a non-empty
+ String, then the timer is paused, otherwise it is unpaused.
+ See |non-zero-arg|.
+
+ ]=],
+ name = 'timer_pause',
+ params = { { 'timer', 'any' }, { 'paused', 'any' } },
+ signature = 'timer_pause({timer}, {paused})',
+ },
+ timer_start = {
+ args = { 2, 3 },
+ base = 1,
+ tags = { 'timer' },
+ desc = [=[
+ Create a timer and return the timer ID.
+
+ {time} is the waiting time in milliseconds. This is the
+ minimum time before invoking the callback. When the system is
+ busy or Vim is not waiting for input the time will be longer.
+ Zero can be used to execute the callback when Vim is back in
+ the main loop.
+
+ {callback} is the function to call. It can be the name of a
+ function or a |Funcref|. It is called with one argument, which
+ is the timer ID. The callback is only invoked when Vim is
+ waiting for input.
+
+ {options} is a dictionary. Supported entries:
+ "repeat" Number of times to repeat the callback.
+ -1 means forever. Default is 1.
+ If the timer causes an error three times in a
+ row the repeat is cancelled.
+
+ Returns -1 on error.
+
+ Example: >vim
+ func MyHandler(timer)
+ echo 'Handler called'
+ endfunc
+ let timer = timer_start(500, 'MyHandler',
+ \ {'repeat': 3})
+ <This invokes MyHandler() three times at 500 msec intervals.
+
+ ]=],
+ name = 'timer_start',
+ params = { { 'time', 'any' }, { 'callback', 'any' }, { 'options', 'table' } },
+ signature = 'timer_start({time}, {callback} [, {options}])',
+ },
+ timer_stop = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Stop a timer. The timer callback will no longer be invoked.
+ {timer} is an ID returned by timer_start(), thus it must be a
+ Number. If {timer} does not exist there is no error.
+
+ ]=],
+ name = 'timer_stop',
+ params = { { 'timer', 'any' } },
+ signature = 'timer_stop({timer})',
+ },
+ timer_stopall = {
+ args = 0,
+ desc = [=[
+ Stop all timers. The timer callbacks will no longer be
+ invoked. Useful if some timers is misbehaving. If there are
+ no timers there is no error.
+ ]=],
+ name = 'timer_stopall',
+ params = {},
+ signature = 'timer_stopall()',
+ },
+ tolower = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a copy of the String given, with all uppercase
+ characters turned into lowercase (just like applying |gu| to
+ the string). Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'tolower',
+ params = { { 'expr', 'any' } },
+ returns = 'string',
+ signature = 'tolower({expr})',
+ },
+ toupper = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a copy of the String given, with all lowercase
+ characters turned into uppercase (just like applying |gU| to
+ the string). Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'toupper',
+ params = { { 'expr', 'any' } },
+ returns = 'string',
+ signature = 'toupper({expr})',
+ },
+ tr = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a copy of the {src} string with all characters
+ which appear in {fromstr} replaced by the character in that
+ position in the {tostr} string. Thus the first character in
+ {fromstr} is translated into the first character in {tostr}
+ and so on. Exactly like the unix "tr" command.
+ This code also deals with multibyte characters properly.
+
+ Returns an empty string on error.
+
+ Examples: >vim
+ echo tr("hello there", "ht", "HT")
+ <returns "Hello THere" >vim
+ echo tr("<blob>", "<>", "{}")
+ <returns "{blob}"
+
+ ]=],
+ name = 'tr',
+ params = { { 'src', 'string' }, { 'fromstr', 'string' }, { 'tostr', 'string' } },
+ returns = 'string',
+ signature = 'tr({src}, {fromstr}, {tostr})',
+ },
+ trim = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Return {text} as a String where any character in {mask} is
+ removed from the beginning and/or end of {text}.
+
+ If {mask} is not given, or is an empty string, {mask} is all
+ characters up to 0x20, which includes Tab, space, NL and CR,
+ plus the non-breaking space character 0xa0.
+
+ The optional {dir} argument specifies where to remove the
+ characters:
+ 0 remove from the beginning and end of {text}
+ 1 remove only at the beginning of {text}
+ 2 remove only at the end of {text}
+ When omitted both ends are trimmed.
+
+ This function deals with multibyte characters properly.
+ Returns an empty string on error.
+
+ Examples: >vim
+ echo trim(" some text ")
+ <returns "some text" >vim
+ echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") .. "_TAIL"
+ <returns "RESERVE_TAIL" >vim
+ echo trim("rm<Xrm<>X>rrm", "rm<>")
+ <returns "Xrm<>X" (characters in the middle are not removed) >vim
+ echo trim(" vim ", " ", 2)
+ <returns " vim"
+
+ ]=],
+ name = 'trim',
+ params = { { 'text', 'any' }, { 'mask', 'string' }, { 'dir', '0|1|2' } },
+ returns = 'string',
+ signature = 'trim({text} [, {mask} [, {dir}]])',
+ },
+ trunc = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the largest integral value with magnitude less than or
+ equal to {expr} as a |Float| (truncate towards zero).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo trunc(1.456)
+ < 1.0 >vim
+ echo trunc(-5.456)
+ < -5.0 >vim
+ echo trunc(4.0)
+ < 4.0
+
+ ]=],
+ float_func = 'trunc',
+ name = 'trunc',
+ params = { { 'expr', 'any' } },
+ returns = 'integer',
+ signature = 'trunc({expr})',
+ },
+ type = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number representing the type of {expr}.
+ Instead of using the number directly, it is better to use the
+ v:t_ variable that has the value:
+ Number: 0 |v:t_number|
+ String: 1 |v:t_string|
+ Funcref: 2 |v:t_func|
+ List: 3 |v:t_list|
+ Dictionary: 4 |v:t_dict|
+ Float: 5 |v:t_float|
+ Boolean: 6 |v:t_bool| (|v:false| and |v:true|)
+ Null: 7 (|v:null|)
+ Blob: 10 |v:t_blob|
+ For backward compatibility, this method can be used: >vim
+ if type(myvar) == type(0) | endif
+ if type(myvar) == type("") | endif
+ if type(myvar) == type(function("tr")) | endif
+ if type(myvar) == type([]) | endif
+ if type(myvar) == type({}) | endif
+ if type(myvar) == type(0.0) | endif
+ if type(myvar) == type(v:true) | endif
+ <In place of checking for |v:null| type it is better to check
+ for |v:null| directly as it is the only value of this type: >vim
+ if myvar is v:null | endif
+ <To check if the v:t_ variables exist use this: >vim
+ if exists('v:t_number') | endif
+
+ ]=],
+ fast = true,
+ name = 'type',
+ params = { { 'expr', 'any' } },
+ returns = 'integer',
+ signature = 'type({expr})',
+ },
+ undofile = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the name of the undo file that would be used for a file
+ with name {name} when writing. This uses the 'undodir'
+ option, finding directories that exist. It does not check if
+ the undo file exists.
+ {name} is always expanded to the full path, since that is what
+ is used internally.
+ If {name} is empty undofile() returns an empty string, since a
+ buffer without a file name will not write an undo file.
+ Useful in combination with |:wundo| and |:rundo|.
+
+ ]=],
+ name = 'undofile',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'undofile({name})',
+ },
+ undotree = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return the current state of the undo tree for the current
+ buffer, or for a specific buffer if {buf} is given. The
+ result is a dictionary with the following items:
+ "seq_last" The highest undo sequence number used.
+ "seq_cur" The sequence number of the current position in
+ the undo tree. This differs from "seq_last"
+ when some changes were undone.
+ "time_cur" Time last used for |:earlier| and related
+ commands. Use |strftime()| to convert to
+ something readable.
+ "save_last" Number of the last file write. Zero when no
+ write yet.
+ "save_cur" Number of the current position in the undo
+ tree.
+ "synced" Non-zero when the last undo block was synced.
+ This happens when waiting from input from the
+ user. See |undo-blocks|.
+ "entries" A list of dictionaries with information about
+ undo blocks.
+
+ The first item in the "entries" list is the oldest undo item.
+ Each List item is a |Dictionary| with these items:
+ "seq" Undo sequence number. Same as what appears in
+ |:undolist|.
+ "time" Timestamp when the change happened. Use
+ |strftime()| to convert to something readable.
+ "newhead" Only appears in the item that is the last one
+ that was added. This marks the last change
+ and where further changes will be added.
+ "curhead" Only appears in the item that is the last one
+ that was undone. This marks the current
+ position in the undo tree, the block that will
+ be used by a redo command. When nothing was
+ undone after the last change this item will
+ not appear anywhere.
+ "save" Only appears on the last block before a file
+ write. The number is the write count. The
+ first write has number 1, the last one the
+ "save_last" mentioned above.
+ "alt" Alternate entry. This is again a List of undo
+ blocks. Each item may again have an "alt"
+ item.
+ ]=],
+ name = 'undotree',
+ params = { { 'buf', 'integer|string' } },
+ signature = 'undotree([{buf}])',
+ },
+ uniq = {
+ args = { 1, 3 },
+ base = 1,
+ tags = { 'E882' },
+ desc = [=[
+ Remove second and succeeding copies of repeated adjacent
+ {list} items in-place. Returns {list}. If you want a list
+ to remain unmodified make a copy first: >vim
+ let newlist = uniq(copy(mylist))
+ <The default compare function uses the string representation of
+ each item. For the use of {func} and {dict} see |sort()|.
+
+ Returns zero if {list} is not a |List|.
+
+ ]=],
+ name = 'uniq',
+ params = { { 'list', 'any' }, { 'func', 'any' }, { 'dict', 'any' } },
+ returns = 'any[]|0',
+ signature = 'uniq({list} [, {func} [, {dict}]])',
+ },
+ utf16idx = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |charidx()| but returns the UTF-16 code unit index of
+ the byte at {idx} in {string} (after converting it to UTF-16).
+
+ When {charidx} is present and TRUE, {idx} is used as the
+ character index in the String {string} instead of as the byte
+ index.
+ An {idx} in the middle of a UTF-8 sequence is rounded
+ downwards to the beginning of that sequence.
+
+ Returns -1 if the arguments are invalid or if there are less
+ than {idx} bytes in {string}. If there are exactly {idx} bytes
+ the length of the string in UTF-16 code units is returned.
+
+ See |byteidx()| and |byteidxcomp()| for getting the byte index
+ from the UTF-16 index and |charidx()| for getting the
+ character index from the UTF-16 index.
+ Refer to |string-offset-encoding| for more information.
+ Examples: >vim
+ echo utf16idx('a😊😊', 3) " returns 2
+ echo utf16idx('a😊😊', 7) " returns 4
+ echo utf16idx('a😊😊', 1, 0, 1) " returns 2
+ echo utf16idx('a😊😊', 2, 0, 1) " returns 4
+ echo utf16idx('aąÌc', 6) " returns 2
+ echo utf16idx('aąÌc', 6, 1) " returns 4
+ echo utf16idx('a😊😊', 9) " returns -1
+ <
+ ]=],
+ name = 'utf16idx',
+ params = {
+ { 'string', 'string' },
+ { 'idx', 'integer' },
+ { 'countcc', 'any' },
+ { 'charidx', 'any' },
+ },
+ returns = 'integer',
+ signature = 'utf16idx({string}, {idx} [, {countcc} [, {charidx}]])',
+ },
+ values = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a |List| with all the values of {dict}. The |List| is
+ in arbitrary order. Also see |items()| and |keys()|.
+ Returns zero if {dict} is not a |Dict|.
+
+ ]=],
+ name = 'values',
+ params = { { 'dict', 'any' } },
+ signature = 'values({dict})',
+ },
+ virtcol = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the screen column of the file
+ position given with {expr}. That is, the last screen position
+ occupied by the character at that position, when the screen
+ would be of unlimited width. When there is a <Tab> at the
+ position, the returned Number will be the column at the end of
+ the <Tab>. For example, for a <Tab> in column 1, with 'ts'
+ set to 8, it returns 8. |conceal| is ignored.
+ For the byte position use |col()|.
+
+ For the use of {expr} see |col()|.
+
+ When 'virtualedit' is used {expr} can be [lnum, col, off],
+ where "off" is the offset in screen columns from the start of
+ the character. E.g., a position within a <Tab> or after the
+ last character. When "off" is omitted zero is used. When
+ Virtual editing is active in the current mode, a position
+ beyond the end of the line can be returned. Also see
+ |'virtualedit'|
+
+ The accepted positions are:
+ . the cursor position
+ $ the end of the cursor line (the result is the
+ number of displayed characters in the cursor line
+ plus one)
+ 'x position of mark x (if the mark is not set, 0 is
+ returned)
+ v In Visual mode: the start of the Visual area (the
+ cursor is the end). When not in Visual mode
+ returns the cursor position. Differs from |'<| in
+ that it's updated right away.
+
+ If {list} is present and non-zero then virtcol() returns a
+ List with the first and last screen position occupied by the
+ character.
+
+ With the optional {winid} argument the values are obtained for
+ that window instead of the current window.
+
+ Note that only marks in the current file can be used.
+ Examples: >vim
+ " With text "foo^Lbar" and cursor on the "^L":
+
+ echo virtcol(".") " returns 5
+ echo virtcol(".", 1) " returns [4, 5]
+ echo virtcol("$") " returns 9
+
+ " With text " there", with 't at 'h':
+
+ echo virtcol("'t") " returns 6
+ <The first column is 1. 0 or [0, 0] is returned for an error.
+ A more advanced example that echoes the maximum length of
+ all lines: >vim
+ echo max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
+
+ ]=],
+ name = 'virtcol',
+ params = { { 'expr', 'any' }, { 'list', 'any' }, { 'winid', 'integer' } },
+ signature = 'virtcol({expr} [, {list} [, {winid}]])',
+ },
+ virtcol2col = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the byte index of the
+ character in window {winid} at buffer line {lnum} and virtual
+ column {col}.
+
+ If buffer line {lnum} is an empty line, 0 is returned.
+
+ If {col} is greater than the last virtual column in line
+ {lnum}, then the byte index of the character at the last
+ virtual column is returned.
+
+ For a multi-byte character, the column number of the first
+ byte in the character is returned.
+
+ The {winid} argument can be the window number or the
+ |window-ID|. If this is zero, then the current window is used.
+
+ Returns -1 if the window {winid} doesn't exist or the buffer
+ line {lnum} or virtual column {col} is invalid.
+
+ See also |screenpos()|, |virtcol()| and |col()|.
+
+ ]=],
+ name = 'virtcol2col',
+ params = { { 'winid', 'integer' }, { 'lnum', 'integer' }, { 'col', 'integer' } },
+ signature = 'virtcol2col({winid}, {lnum}, {col})',
+ },
+ visualmode = {
+ args = { 0, 1 },
+ desc = [=[
+ The result is a String, which describes the last Visual mode
+ used in the current buffer. Initially it returns an empty
+ string, but once Visual mode has been used, it returns "v",
+ "V", or "<CTRL-V>" (a single CTRL-V character) for
+ character-wise, line-wise, or block-wise Visual mode
+ respectively.
+ Example: >vim
+ exe "normal " .. visualmode()
+ <This enters the same Visual mode as before. It is also useful
+ in scripts if you wish to act differently depending on the
+ Visual mode that was used.
+ If Visual mode is active, use |mode()| to get the Visual mode
+ (e.g., in a |:vmap|).
+ If {expr} is supplied and it evaluates to a non-zero Number or
+ a non-empty String, then the Visual mode will be cleared and
+ the old value is returned. See |non-zero-arg|.
+ ]=],
+ name = 'visualmode',
+ params = { { 'expr', 'any' } },
+ signature = 'visualmode([{expr}])',
+ },
+ wait = {
+ args = { 2, 3 },
+ desc = [=[
+ Waits until {condition} evaluates to |TRUE|, where {condition}
+ is a |Funcref| or |string| containing an expression.
+
+ {timeout} is the maximum waiting time in milliseconds, -1
+ means forever.
+
+ Condition is evaluated on user events, internal events, and
+ every {interval} milliseconds (default: 200).
+
+ Returns a status integer:
+ 0 if the condition was satisfied before timeout
+ -1 if the timeout was exceeded
+ -2 if the function was interrupted (by |CTRL-C|)
+ -3 if an error occurred
+ ]=],
+ name = 'wait',
+ params = { { 'timeout', 'integer' }, { 'condition', 'any' }, { 'interval', 'any' } },
+ signature = 'wait({timeout}, {condition} [, {interval}])',
+ },
+ wildmenumode = {
+ desc = [=[
+ Returns |TRUE| when the wildmenu is active and |FALSE|
+ otherwise. See 'wildmenu' and 'wildmode'.
+ This can be used in mappings to handle the 'wildcharm' option
+ gracefully. (Makes only sense with |mapmode-c| mappings).
+
+ For example to make <c-j> work like <down> in wildmode, use: >vim
+ cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>"
+ <
+ (Note, this needs the 'wildcharm' option set appropriately).
+ ]=],
+ name = 'wildmenumode',
+ params = {},
+ signature = 'wildmenumode()',
+ },
+ win_execute = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ Like `execute()` but in the context of window {id}.
+ The window will temporarily be made the current window,
+ without triggering autocommands or changing directory. When
+ executing {command} autocommands will be triggered, this may
+ have unexpected side effects. Use `:noautocmd` if needed.
+ Example: >vim
+ call win_execute(winid, 'syntax enable')
+ <Doing the same with `setwinvar()` would not trigger
+ autocommands and not actually show syntax highlighting.
+
+ When window {id} does not exist then no error is given and
+ an empty string is returned.
+
+ ]=],
+ name = 'win_execute',
+ params = { { 'id', 'any' }, { 'command', 'any' }, { 'silent', 'boolean' } },
+ signature = 'win_execute({id}, {command} [, {silent}])',
+ },
+ win_findbuf = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns a |List| with |window-ID|s for windows that contain
+ buffer {bufnr}. When there is none the list is empty.
+
+ ]=],
+ name = 'win_findbuf',
+ params = { { 'bufnr', 'any' } },
+ returns = 'integer[]',
+ signature = 'win_findbuf({bufnr})',
+ },
+ win_getid = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Get the |window-ID| for the specified window.
+ When {win} is missing use the current window.
+ With {win} this is the window number. The top window has
+ number 1.
+ Without {tab} use the current tab, otherwise the tab with
+ number {tab}. The first tab has number one.
+ Return zero if the window cannot be found.
+
+ ]=],
+ name = 'win_getid',
+ params = { { 'win', 'any' }, { 'tab', 'any' } },
+ returns = 'integer',
+ signature = 'win_getid([{win} [, {tab}]])',
+ },
+ win_gettype = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return the type of the window:
+ "autocmd" autocommand window. Temporary window
+ used to execute autocommands.
+ "command" command-line window |cmdwin|
+ (empty) normal window
+ "loclist" |location-list-window|
+ "popup" floating window |api-floatwin|
+ "preview" preview window |preview-window|
+ "quickfix" |quickfix-window|
+ "unknown" window {nr} not found
+
+ When {nr} is omitted return the type of the current window.
+ When {nr} is given return the type of this window by number or
+ |window-ID|.
+
+ Also see the 'buftype' option.
+
+ ]=],
+ name = 'win_gettype',
+ params = { { 'nr', 'integer' } },
+ returns = "'autocmd'|'command'|''|'loclist'|'popup'|'preview'|'quickfix'|'unknown'",
+ signature = 'win_gettype([{nr}])',
+ },
+ win_gotoid = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Go to window with ID {expr}. This may also change the current
+ tabpage.
+ Return TRUE if successful, FALSE if the window cannot be found.
+
+ ]=],
+ name = 'win_gotoid',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'win_gotoid({expr})',
+ },
+ win_id2tabwin = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a list with the tab number and window number of window
+ with ID {expr}: [tabnr, winnr].
+ Return [0, 0] if the window cannot be found.
+
+ ]=],
+ name = 'win_id2tabwin',
+ params = { { 'expr', 'any' } },
+ signature = 'win_id2tabwin({expr})',
+ },
+ win_id2win = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the window number of window with ID {expr}.
+ Return 0 if the window cannot be found in the current tabpage.
+
+ ]=],
+ name = 'win_id2win',
+ params = { { 'expr', 'any' } },
+ signature = 'win_id2win({expr})',
+ },
+ win_move_separator = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Move window {nr}'s vertical separator (i.e., the right border)
+ by {offset} columns, as if being dragged by the mouse. {nr}
+ can be a window number or |window-ID|. A positive {offset}
+ moves right and a negative {offset} moves left. Moving a
+ window's vertical separator will change the width of the
+ window and the width of other windows adjacent to the vertical
+ separator. The magnitude of movement may be smaller than
+ specified (e.g., as a consequence of maintaining
+ 'winminwidth'). Returns TRUE if the window can be found and
+ FALSE otherwise.
+ This will fail for the rightmost window and a full-width
+ window, since it has no separator on the right.
+ Only works for the current tab page. *E1308*
+
+ ]=],
+ name = 'win_move_separator',
+ params = { { 'nr', 'integer' }, { 'offset', 'any' } },
+ signature = 'win_move_separator({nr}, {offset})',
+ },
+ win_move_statusline = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Move window {nr}'s status line (i.e., the bottom border) by
+ {offset} rows, as if being dragged by the mouse. {nr} can be a
+ window number or |window-ID|. A positive {offset} moves down
+ and a negative {offset} moves up. Moving a window's status
+ line will change the height of the window and the height of
+ other windows adjacent to the status line. The magnitude of
+ movement may be smaller than specified (e.g., as a consequence
+ of maintaining 'winminheight'). Returns TRUE if the window can
+ be found and FALSE otherwise.
+ Only works for the current tab page.
+
+ ]=],
+ name = 'win_move_statusline',
+ params = { { 'nr', 'integer' }, { 'offset', 'any' } },
+ signature = 'win_move_statusline({nr}, {offset})',
+ },
+ win_screenpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the screen position of window {nr} as a list with two
+ numbers: [row, col]. The first window always has position
+ [1, 1], unless there is a tabline, then it is [2, 1].
+ {nr} can be the window number or the |window-ID|. Use zero
+ for the current window.
+ Returns [0, 0] if the window cannot be found in the current
+ tabpage.
+
+ ]=],
+ name = 'win_screenpos',
+ params = { { 'nr', 'integer' } },
+ signature = 'win_screenpos({nr})',
+ },
+ win_splitmove = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Move the window {nr} to a new split of the window {target}.
+ This is similar to moving to {target}, creating a new window
+ using |:split| but having the same contents as window {nr}, and
+ then closing {nr}.
+
+ Both {nr} and {target} can be window numbers or |window-ID|s.
+ Both must be in the current tab page.
+
+ Returns zero for success, non-zero for failure.
+
+ {options} is a |Dictionary| with the following optional entries:
+ "vertical" When TRUE, the split is created vertically,
+ like with |:vsplit|.
+ "rightbelow" When TRUE, the split is made below or to the
+ right (if vertical). When FALSE, it is done
+ above or to the left (if vertical). When not
+ present, the values of 'splitbelow' and
+ 'splitright' are used.
+
+ ]=],
+ name = 'win_splitmove',
+ params = { { 'nr', 'integer' }, { 'target', 'any' }, { 'options', 'table' } },
+ signature = 'win_splitmove({nr}, {target} [, {options}])',
+ },
+ winbufnr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of the buffer
+ associated with window {nr}. {nr} can be the window number or
+ the |window-ID|.
+ When {nr} is zero, the number of the buffer in the current
+ window is returned.
+ When window {nr} doesn't exist, -1 is returned.
+ Example: >vim
+ echo "The file in the current window is " .. bufname(winbufnr(0))
+ <
+ ]=],
+ name = 'winbufnr',
+ params = { { 'nr', 'integer' } },
+ returns = 'integer',
+ signature = 'winbufnr({nr})',
+ },
+ wincol = {
+ desc = [=[
+ The result is a Number, which is the virtual column of the
+ cursor in the window. This is counting screen cells from the
+ left side of the window. The leftmost column is one.
+ ]=],
+ name = 'wincol',
+ params = {},
+ returns = 'integer',
+ signature = 'wincol()',
+ },
+ windowsversion = {
+ desc = [=[
+ The result is a String. For MS-Windows it indicates the OS
+ version. E.g, Windows 10 is "10.0", Windows 8 is "6.2",
+ Windows XP is "5.1". For non-MS-Windows systems the result is
+ an empty string.
+ ]=],
+ fast = true,
+ name = 'windowsversion',
+ params = {},
+ returns = 'string',
+ signature = 'windowsversion()',
+ },
+ winheight = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the height of window {nr}.
+ {nr} can be the window number or the |window-ID|.
+ When {nr} is zero, the height of the current window is
+ returned. When window {nr} doesn't exist, -1 is returned.
+ An existing window always has a height of zero or more.
+ This excludes any window toolbar line.
+ Examples: >vim
+ echo "The current window has " .. winheight(0) .. " lines."
+
+ ]=],
+ name = 'winheight',
+ params = { { 'nr', 'integer' } },
+ returns = 'integer',
+ signature = 'winheight({nr})',
+ },
+ winlayout = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a nested List containing the layout of windows
+ in a tabpage.
+
+ Without {tabnr} use the current tabpage, otherwise the tabpage
+ with number {tabnr}. If the tabpage {tabnr} is not found,
+ returns an empty list.
+
+ For a leaf window, it returns: >
+ ["leaf", {winid}]
+ <
+ For horizontally split windows, which form a column, it
+ returns: >
+ ["col", [{nested list of windows}]]
+ <For vertically split windows, which form a row, it returns: >
+ ["row", [{nested list of windows}]]
+ <
+ Example: >vim
+ " Only one window in the tab page
+ echo winlayout()
+ < >
+ ['leaf', 1000]
+ < >vim
+ " Two horizontally split windows
+ echo winlayout()
+ < >
+ ['col', [['leaf', 1000], ['leaf', 1001]]]
+ < >vim
+ " The second tab page, with three horizontally split
+ " windows, with two vertically split windows in the
+ " middle window
+ echo winlayout(2)
+ < >
+ ['col', [['leaf', 1002], ['row', [['leaf', 1003],
+ ['leaf', 1001]]], ['leaf', 1000]]]
+ <
+ ]=],
+ name = 'winlayout',
+ params = { { 'tabnr', 'integer' } },
+ signature = 'winlayout([{tabnr}])',
+ },
+ winline = {
+ desc = [=[
+ The result is a Number, which is the screen line of the cursor
+ in the window. This is counting screen lines from the top of
+ the window. The first line is one.
+ If the cursor was moved the view on the file will be updated
+ first, this may cause a scroll.
+ ]=],
+ name = 'winline',
+ params = {},
+ returns = 'integer',
+ signature = 'winline()',
+ },
+ winnr = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of the current
+ window. The top window has number 1.
+ Returns zero for a popup window.
+
+ The optional argument {arg} supports the following values:
+ $ the number of the last window (the window
+ count).
+ # the number of the last accessed window (where
+ |CTRL-W_p| goes to). If there is no previous
+ window or it is in another tab page 0 is
+ returned.
+ {N}j the number of the Nth window below the
+ current window (where |CTRL-W_j| goes to).
+ {N}k the number of the Nth window above the current
+ window (where |CTRL-W_k| goes to).
+ {N}h the number of the Nth window left of the
+ current window (where |CTRL-W_h| goes to).
+ {N}l the number of the Nth window right of the
+ current window (where |CTRL-W_l| goes to).
+ The number can be used with |CTRL-W_w| and ":wincmd w"
+ |:wincmd|.
+ When {arg} is invalid an error is given and zero is returned.
+ Also see |tabpagewinnr()| and |win_getid()|.
+ Examples: >vim
+ let window_count = winnr('$')
+ let prev_window = winnr('#')
+ let wnum = winnr('3k')
+
+ ]=],
+ name = 'winnr',
+ params = { { 'arg', 'any' } },
+ signature = 'winnr([{arg}])',
+ },
+ winrestcmd = {
+ desc = [=[
+ Returns a sequence of |:resize| commands that should restore
+ the current window sizes. Only works properly when no windows
+ are opened or closed and the current window and tab page is
+ unchanged.
+ Example: >vim
+ let cmd = winrestcmd()
+ call MessWithWindowSizes()
+ exe cmd
+ <
+ ]=],
+ name = 'winrestcmd',
+ params = {},
+ signature = 'winrestcmd()',
+ },
+ winrestview = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Uses the |Dictionary| returned by |winsaveview()| to restore
+ the view of the current window.
+ Note: The {dict} does not have to contain all values, that are
+ returned by |winsaveview()|. If values are missing, those
+ settings won't be restored. So you can use: >vim
+ call winrestview({'curswant': 4})
+ <
+ This will only set the curswant value (the column the cursor
+ wants to move on vertical movements) of the cursor to column 5
+ (yes, that is 5), while all other settings will remain the
+ same. This is useful, if you set the cursor position manually.
+
+ If you have changed the values the result is unpredictable.
+ If the window size changed the result won't be the same.
+
+ ]=],
+ name = 'winrestview',
+ params = { { 'dict', 'vim.fn.winrestview.dict' } },
+ signature = 'winrestview({dict})',
+ },
+ winsaveview = {
+ desc = [=[
+ Returns a |Dictionary| that contains information to restore
+ the view of the current window. Use |winrestview()| to
+ restore the view.
+ This is useful if you have a mapping that jumps around in the
+ buffer and you want to go back to the original view.
+ This does not save fold information. Use the 'foldenable'
+ option to temporarily switch off folding, so that folds are
+ not opened when moving around. This may have side effects.
+ The return value includes:
+ lnum cursor line number
+ col cursor column (Note: the first column
+ zero, as opposed to what |getcurpos()|
+ returns)
+ coladd cursor column offset for 'virtualedit'
+ curswant column for vertical movement (Note:
+ the first column is zero, as opposed
+ to what |getcurpos()| returns). After
+ |$| command it will be a very large
+ number equal to |v:maxcol|.
+ topline first line in the window
+ topfill filler lines, only in diff mode
+ leftcol first column displayed; only used when
+ 'wrap' is off
+ skipcol columns skipped
+ Note that no option values are saved.
+ ]=],
+ name = 'winsaveview',
+ params = {},
+ signature = 'winsaveview()',
+ returns = 'vim.fn.winsaveview.ret'
+ },
+ winwidth = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the width of window {nr}.
+ {nr} can be the window number or the |window-ID|.
+ When {nr} is zero, the width of the current window is
+ returned. When window {nr} doesn't exist, -1 is returned.
+ An existing window always has a width of zero or more.
+ Examples: >vim
+ echo "The current window has " .. winwidth(0) .. " columns."
+ if winwidth(0) <= 50
+ 50 wincmd |
+ endif
+ <For getting the terminal or screen size, see the 'columns'
+ option.
+
+ ]=],
+ name = 'winwidth',
+ params = { { 'nr', 'integer' } },
+ signature = 'winwidth({nr})',
+ },
+ wordcount = {
+ desc = [=[
+ The result is a dictionary of byte/chars/word statistics for
+ the current buffer. This is the same info as provided by
+ |g_CTRL-G|
+ The return value includes:
+ bytes Number of bytes in the buffer
+ chars Number of chars in the buffer
+ words Number of words in the buffer
+ cursor_bytes Number of bytes before cursor position
+ (not in Visual mode)
+ cursor_chars Number of chars before cursor position
+ (not in Visual mode)
+ cursor_words Number of words before cursor position
+ (not in Visual mode)
+ visual_bytes Number of bytes visually selected
+ (only in Visual mode)
+ visual_chars Number of chars visually selected
+ (only in Visual mode)
+ visual_words Number of words visually selected
+ (only in Visual mode)
+ ]=],
+ name = 'wordcount',
+ params = {},
+ signature = 'wordcount()',
+ },
+ writefile = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ When {object} is a |List| write it to file {fname}. Each list
+ item is separated with a NL. Each list item must be a String
+ or Number.
+ All NL characters are replaced with a NUL character.
+ Inserting CR characters needs to be done before passing {list}
+ to writefile().
+
+ When {object} is a |Blob| write the bytes to file {fname}
+ unmodified, also when binary mode is not specified.
+
+ {flags} must be a String. These characters are recognized:
+
+ 'b' Binary mode is used: There will not be a NL after the
+ last list item. An empty item at the end does cause the
+ last line in the file to end in a NL.
+
+ 'a' Append mode is used, lines are appended to the file: >vim
+ call writefile(["foo"], "event.log", "a")
+ call writefile(["bar"], "event.log", "a")
+ <
+ 'D' Delete the file when the current function ends. This
+ works like: >vim
+ defer delete({fname})
+ < Fails when not in a function. Also see |:defer|.
+
+ 's' fsync() is called after writing the file. This flushes
+ the file to disk, if possible. This takes more time but
+ avoids losing the file if the system crashes.
+
+ 'S' fsync() is not called, even when 'fsync' is set.
+
+ When {flags} does not contain "S" or "s" then fsync() is
+ called if the 'fsync' option is set.
+
+ An existing file is overwritten, if possible.
+
+ When the write fails -1 is returned, otherwise 0. There is an
+ error message if the file can't be created or when writing
+ fails.
+
+ Also see |readfile()|.
+ To copy a file byte for byte: >vim
+ let fl = readfile("foo", "b")
+ call writefile(fl, "foocopy", "b")
+
+ ]=],
+ name = 'writefile',
+ params = { { 'object', 'any' }, { 'fname', 'string' }, { 'flags', 'string' } },
+ signature = 'writefile({object}, {fname} [, {flags}])',
+ },
+ xor = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Bitwise XOR on the two arguments. The arguments are converted
+ to a number. A List, Dict or Float argument causes an error.
+ Also see `and()` and `or()`.
+ Example: >vim
+ let bits = xor(bits, 0x80)
+ <
+ ]=],
+ name = 'xor',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ signature = 'xor({expr}, {expr})',
},
}
+
+return M
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
index 2f37d1ba2e..c60a104381 100644
--- a/src/nvim/eval/buffer.c
+++ b/src/nvim/eval/buffer.c
@@ -1,12 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval/buffer.c: Buffer related builtin functions
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -17,17 +14,18 @@
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/sign.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
typedef struct {
win_T *cob_curwin_save;
@@ -126,7 +124,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_
FUNC_ATTR_NONNULL_ARG(4, 5)
{
linenr_T lnum = lnum_arg + (append ? 1 : 0);
- long added = 0;
+ int added = 0;
// When using the current buffer ml_mfp will be set if needed. Useful when
// setline() is used on startup. For other buffers the buffer must be
@@ -160,10 +158,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_
if (lines->v_type == VAR_LIST) {
l = lines->vval.v_list;
if (l == NULL || tv_list_len(l) == 0) {
- // set proper return code
- if (lnum > curbuf->b_ml.ml_line_count) {
- rettv->vval.v_number = 1; // FAIL
- }
+ // not appending anything always succeeds
goto cleanup;
}
li = tv_list_first(l);
@@ -172,7 +167,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_
}
// Default result is zero == OK.
- for (;;) {
+ while (true) {
if (lines->v_type == VAR_LIST) {
// List argument, get next string.
if (li == NULL) {
@@ -304,7 +299,10 @@ void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr)
buf_T *buf = get_buf_arg(&argvars[0]);
if (buf != NULL) {
- buffer_ensure_loaded(buf);
+ if (swap_exists_action != SEA_READONLY) {
+ swap_exists_action = SEA_NONE;
+ }
+ buf_ensure_loaded(buf);
}
}
@@ -442,7 +440,7 @@ void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (last > curbuf->b_ml.ml_line_count) {
last = curbuf->b_ml.ml_line_count;
}
- const long count = last - first + 1;
+ const int count = last - first + 1;
// When coming here from Insert mode, sync undo, so that this can be
// undone separately from what was previously inserted.
@@ -487,8 +485,7 @@ static dict_T *get_buffer_info(buf_T *buf)
dict_T *const dict = tv_dict_alloc();
tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum);
- tv_dict_add_str(dict, S_LEN("name"),
- buf->b_ffname != NULL ? (const char *)buf->b_ffname : "");
+ tv_dict_add_str(dict, S_LEN("name"), buf->b_ffname != NULL ? buf->b_ffname : "");
tv_dict_add_nr(dict, S_LEN("lnum"),
buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf));
tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count);
@@ -496,8 +493,7 @@ static dict_T *get_buffer_info(buf_T *buf)
tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl);
tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf));
tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf));
- tv_dict_add_nr(dict, S_LEN("hidden"),
- buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0);
+ tv_dict_add_nr(dict, S_LEN("hidden"), buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0);
// Get a reference to buffer variables
tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars);
@@ -511,7 +507,7 @@ static dict_T *get_buffer_info(buf_T *buf)
}
tv_dict_add_list(dict, S_LEN("windows"), windows);
- if (buf->b_signlist != NULL) {
+ if (buf->b_signs) {
// List of signs placed in this buffer
tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf));
}
@@ -609,13 +605,12 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli
}
tv_list_alloc_ret(rettv, end - start + 1);
while (start <= end) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)ml_get_buf(buf, start++, false), -1);
+ tv_list_append_string(rettv->vval.v_list, ml_get_buf(buf, start++), -1);
}
} else {
rettv->v_type = VAR_STRING;
rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count)
- ? xstrdup(ml_get_buf(buf, start, false)) : NULL);
+ ? xstrdup(ml_get_buf(buf, start)) : NULL);
}
}
diff --git a/src/nvim/eval/buffer.h b/src/nvim/eval/buffer.h
index 4a2f8f9e94..1d346b99a5 100644
--- a/src/nvim/eval/buffer.h
+++ b/src/nvim/eval/buffer.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_EVAL_BUFFER_H
-#define NVIM_EVAL_BUFFER_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/buffer.h.generated.h"
#endif
-#endif // NVIM_EVAL_BUFFER_H
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index cd1479f150..03f79fca84 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <msgpack/object.h>
#include <stdbool.h>
@@ -10,22 +7,22 @@
#include <string.h>
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
-#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
/// Helper structure for container_struct
typedef struct {
@@ -148,7 +145,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
assert(!(key.is_special_string
|| key.val.vval.v_string == NULL
|| *key.val.vval.v_string == NUL));
- dictitem_T *const obj_di = tv_dict_item_alloc((const char *)key.val.vval.v_string);
+ dictitem_T *const obj_di = tv_dict_item_alloc(key.val.vval.v_string);
tv_clear(&key.val);
if (tv_dict_add(last_container.container.vval.v_dict, obj_di)
== FAIL) {
@@ -179,8 +176,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
&& (obj.is_special_string
|| obj.val.vval.v_string == NULL
|| *obj.val.vval.v_string == NUL
- || tv_dict_find(last_container.container.vval.v_dict,
- (const char *)obj.val.vval.v_string, -1))) {
+ || tv_dict_find(last_container.container.vval.v_dict, obj.val.vval.v_string, -1))) {
tv_clear(&obj.val);
// Restart
@@ -228,7 +224,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
///
/// @param[out] ret_tv Address where new special dictionary is saved.
/// @param[in] len Expected number of items to be populated before list
-/// becomes accessible from VimL. It is still valid to
+/// becomes accessible from Vimscript. It is still valid to
/// underpopulate a list, value only controls how many elements
/// will be allocated in advance. @see ListLenSpecials.
///
@@ -439,7 +435,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
t += 4;
uvarnumber_T ch;
vim_str2nr(ubuf, NULL, NULL,
- STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true);
+ STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true, NULL);
if (ch == 0) {
hasnul = true;
}
@@ -608,7 +604,7 @@ parse_json_number_check:
// Convert integer
varnumber_T nr;
int num_len;
- vim_str2nr(s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true);
+ vim_str2nr(s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true, NULL);
if ((int)exp_num_len != num_len) {
semsg(_("E685: internal error: while converting number \"%.*s\" "
"to integer vim_str2nr consumed %i bytes in place of %zu"),
@@ -646,7 +642,7 @@ parse_json_number_ret:
} \
} while (0)
-/// Convert JSON string into VimL object
+/// Convert JSON string into Vimscript object
///
/// @param[in] buf String to convert. UTF-8 encoding is assumed.
/// @param[in] buf_len Length of the string.
@@ -922,7 +918,7 @@ json_decode_string_ret:
#undef DICT_LEN
-/// Convert msgpack object to a VimL one
+/// Convert msgpack object to a Vimscript one
int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -966,7 +962,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
}
break;
case MSGPACK_OBJECT_NEGATIVE_INTEGER:
- if (mobj.via.i64 >= VARNUMBER_MIN) { // -V547
+ if (mobj.via.i64 >= VARNUMBER_MIN) {
*rettv = (typval_T) {
.v_type = VAR_NUMBER,
.v_lock = VAR_UNLOCKED,
@@ -987,12 +983,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF));
}
break;
-#ifdef NVIM_MSGPACK_HAS_FLOAT32
case MSGPACK_OBJECT_FLOAT32:
case MSGPACK_OBJECT_FLOAT64:
-#else
- case MSGPACK_OBJECT_FLOAT:
-#endif
*rettv = (typval_T) {
.v_type = VAR_FLOAT,
.v_lock = VAR_UNLOCKED,
diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h
index f1be5a1f69..c0d10a469a 100644
--- a/src/nvim/eval/decode.h
+++ b/src/nvim/eval/decode.h
@@ -1,13 +1,11 @@
-#ifndef NVIM_EVAL_DECODE_H
-#define NVIM_EVAL_DECODE_H
+#pragma once
-#include <msgpack.h>
-#include <stddef.h>
+#include <msgpack.h> // IWYU pragma: keep
+#include <stddef.h> // IWYU pragma: keep
-#include "nvim/eval/typval.h"
-#include "nvim/globals.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/decode.h.generated.h"
#endif
-#endif // NVIM_EVAL_DECODE_H
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index c2f1eae8af..8505c30fad 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -1,9 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file encode.c
///
-/// File containing functions for encoding and decoding VimL values.
+/// File containing functions for encoding and decoding Vimscript values.
///
/// Split out from eval.c.
@@ -17,7 +14,7 @@
#include "klib/kvec.h"
#include "msgpack/pack.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
@@ -25,14 +22,14 @@
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h" // For _()
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h" // For _()
const char *const encode_bool_var_names[] = {
[kBoolVarTrue] = "v:true",
@@ -152,9 +149,9 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack,
? 0
: (v.data.l.li == NULL
? tv_list_len(v.data.l.list) - 1
- : (int)tv_list_idx_of_item(v.data.l.list,
- TV_LIST_ITEM_PREV(v.data.l.list,
- v.data.l.li))));
+ : tv_list_idx_of_item(v.data.l.list,
+ TV_LIST_ITEM_PREV(v.data.l.list,
+ v.data.l.li))));
const listitem_T *const li = (v.data.l.li == NULL
? tv_list_last(v.data.l.list)
: TV_LIST_ITEM_PREV(v.data.l.list,
@@ -298,7 +295,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
- const char *const buf_ = (const char *)(buf); \
+ const char *const buf_ = (buf); \
if ((buf) == NULL) { \
ga_concat(gap, "''"); \
} else { \
@@ -376,7 +373,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
- const char *const fun_ = (const char *)(fun); \
+ const char *const fun_ = (fun); \
if (fun_ == NULL) { \
internal_error("string(): NULL function name"); \
ga_concat(gap, "function(NULL"); \
@@ -418,7 +415,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
ga_concat(gap, "v:null")
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- ga_concat(gap, ((num)? "v:true": "v:false"))
+ ga_concat(gap, ((num) ? "v:true" : "v:false"))
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num)
@@ -544,7 +541,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#undef TYPVAL_ENCODE_CONV_BOOL
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- ga_concat(gap, ((num)? "true": "false"))
+ ga_concat(gap, ((num) ? "true" : "false"))
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \
@@ -712,7 +709,7 @@ static inline int convert_to_json_string(garray_T *const gap, const char *const
#undef TYPVAL_ENCODE_CONV_STRING
#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
- if (convert_to_json_string(gap, (const char *)(buf), (len)) != OK) { \
+ if (convert_to_json_string(gap, (buf), (len)) != OK) { \
return FAIL; \
} \
} while (0)
diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h
index 41e7614fc0..26a3286f2b 100644
--- a/src/nvim/eval/encode.h
+++ b/src/nvim/eval/encode.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVAL_ENCODE_H
-#define NVIM_EVAL_ENCODE_H
+#pragma once
#include <msgpack.h>
#include <msgpack/pack.h>
@@ -9,10 +8,9 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/garray.h"
-#include "nvim/vim.h"
+#include "nvim/garray_defs.h"
-/// Convert VimL value to msgpack string
+/// Convert Vimscript value to msgpack string
///
/// @param[out] packer Packer to save results in.
/// @param[in] tv Dumped value.
@@ -21,7 +19,7 @@
/// @return OK in case of success, FAIL otherwise.
int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname);
-/// Convert VimL value to :echo output
+/// Convert Vimscript value to :echo output
///
/// @param[out] packer Packer to save results in.
/// @param[in] tv Dumped value.
@@ -74,4 +72,3 @@ extern const char *const encode_special_var_names[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/encode.h.generated.h"
#endif
-#endif // NVIM_EVAL_ENCODE_H
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 9caea2fef1..dc23fcdc72 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <stdlib.h>
@@ -8,19 +5,21 @@
#include "nvim/eval/executor.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/message.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/executor.c.generated.h" // IWYU pragma: export
#endif
-char *e_listidx = N_("E684: list index out of range: %" PRId64);
+char *e_list_index_out_of_range_nr
+ = N_("E684: List index out of range: %" PRId64);
/// Handle tv1 += tv2, -=, *=, /=, %=, .=
///
diff --git a/src/nvim/eval/executor.h b/src/nvim/eval/executor.h
index 3d789f76a5..d36ce08542 100644
--- a/src/nvim/eval/executor.h
+++ b/src/nvim/eval/executor.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_EVAL_EXECUTOR_H
-#define NVIM_EVAL_EXECUTOR_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
-extern char *e_listidx;
+extern char *e_list_index_out_of_range_nr;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/executor.h.generated.h"
#endif
-#endif // NVIM_EVAL_EXECUTOR_H
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 5a31288ecd..13425b21d1 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <fcntl.h>
#include <float.h>
@@ -11,11 +8,13 @@
#include <msgpack/pack.h>
#include <msgpack/unpack.h>
#include <signal.h>
+#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <time.h>
#include <uv.h>
@@ -25,14 +24,15 @@
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/context.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -44,6 +44,7 @@
#include "nvim/eval/executor.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
@@ -57,41 +58,40 @@
#include "nvim/ex_getln.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/hashtab.h"
+#include "nvim/grid.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/input.h"
+#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
-#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/dl.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/shell.h"
@@ -100,7 +100,7 @@
#include "nvim/path.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
@@ -113,9 +113,8 @@
#include "nvim/syntax.h"
#include "nvim/tag.h"
#include "nvim/ui.h"
-#include "nvim/undo.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// Describe data to return from find_some_match()
@@ -145,11 +144,17 @@ PRAGMA_DIAG_POP
PRAGMA_DIAG_POP
#endif
-static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
-static char *e_invalwindow = N_("E957: Invalid window number");
-static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
-static char e_using_number_as_bool_nr[]
- = N_("E1023: Using a Number as a Bool: %d");
+static const char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
+static const char *e_invalwindow = N_("E957: Invalid window number");
+static const char e_argument_of_str_must_be_list_string_or_dictionary[]
+ = N_("E706: Argument of %s must be a List, String or Dictionary");
+static const char e_invalid_submatch_number_nr[]
+ = N_("E935: Invalid submatch number: %d");
+static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
+static const char e_string_list_or_blob_required[]
+ = N_("E1098: String, List or Blob required");
+static const char e_missing_function_argument[]
+ = N_("E1132: Missing function argument");
/// Dummy va_list for passing to vim_snprintf
///
@@ -226,6 +231,31 @@ const EvalFuncDef *find_internal_func(const char *const name)
return index >= 0 ? &functions[index] : NULL;
}
+/// Check the argument count to use for internal function "fdef".
+/// @return -1 for failure, 0 if no method base accepted, 1 if method base is
+/// first argument, 2 if method base is second argument, etc.
+int check_internal_func(const EvalFuncDef *const fdef, const int argcount)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int res;
+
+ if (argcount < fdef->min_argc) {
+ res = FCERR_TOOFEW;
+ } else if (argcount > fdef->max_argc) {
+ res = FCERR_TOOMANY;
+ } else {
+ return fdef->base_arg;
+ }
+
+ const char *const name = fdef->name;
+ if (res == FCERR_TOOMANY) {
+ semsg(_(e_toomanyarg), name);
+ } else {
+ semsg(_(e_toofewarg), name);
+ }
+ return -1;
+}
+
int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars,
typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL
@@ -261,6 +291,9 @@ int call_internal_method(const char *const fname, const int argcount, typval_T *
typval_T argv[MAX_FUNC_ARGS + 1];
const ptrdiff_t base_index = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1;
+ if (argcount < base_index) {
+ return FCERR_TOOFEW;
+ }
memcpy(argv, argvars, (size_t)base_index * sizeof(typval_T));
argv[base_index] = *basetv;
memcpy(argv + base_index + 1, argvars + base_index,
@@ -319,11 +352,12 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err);
if (ERROR_SET(&err)) {
- semsg_multiline((const char *)e_api_error, err.msg);
+ semsg_multiline(e_api_error, err.msg);
goto end;
}
if (!object_to_vim(result, rettv, &err)) {
+ assert(ERROR_SET(&err));
semsg(_("Error converting the call result: %s"), err.msg);
}
@@ -448,7 +482,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only)
int save_magic = p_magic;
p_magic = true;
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
buf_T *buf = buflist_findnr(buflist_findpat(name, name + strlen(name),
true, false, curtab_only));
@@ -492,7 +526,7 @@ buf_T *get_buf_arg(typval_T *arg)
/// "byte2line(byte)" function
static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- long boff = tv_get_number(&argvars[0]) - 1;
+ int boff = (int)tv_get_number(&argvars[0]) - 1;
if (boff < 0) {
rettv->vval.v_number = -1;
} else {
@@ -501,41 +535,6 @@ static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
-{
- const char *const str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- rettv->vval.v_number = -1;
- if (str == NULL || idx < 0) {
- return;
- }
-
- const char *t = str;
- for (; idx > 0; idx--) {
- if (*t == NUL) { // EOL reached.
- return;
- }
- if (comp) {
- t += utf_ptr2len(t);
- } else {
- t += utfc_ptr2len(t);
- }
- }
- rettv->vval.v_number = (varnumber_T)(t - str);
-}
-
-/// "byteidx()" function
-static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- byteidx(argvars, rettv, false);
-}
-
-/// "byteidxcomp()" function
-static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- byteidx(argvars, rettv, true);
-}
-
/// "call(func, arglist [, dict])" function
static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -563,14 +562,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
func = (char *)tv_get_string(&argvars[0]);
}
- if (*func == NUL) {
- return; // type error or empty name
+ if (func == NULL || *func == NUL) {
+ return; // type error, empty name or null function
}
dict_T *selfdict = NULL;
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
if (owned) {
func_unref(func);
}
@@ -653,7 +651,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool crlf = false;
#else
Channel *chan = find_channel(id);
- bool crlf = (chan != NULL && chan->term) ? true: false;
+ bool crlf = (chan != NULL && chan->term) ? true : false;
#endif
if (argvars[1].v_type == VAR_BLOB) {
@@ -761,53 +759,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_col(argvars, rettv, true);
}
-/// "charidx()" function
-static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_STRING
- || argvars[1].v_type != VAR_NUMBER
- || (argvars[2].v_type != VAR_UNKNOWN
- && argvars[2].v_type != VAR_NUMBER
- && argvars[2].v_type != VAR_BOOL)) {
- emsg(_(e_invarg));
- return;
- }
-
- const char *str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- if (str == NULL || idx < 0) {
- return;
- }
- int countcc = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- countcc = (int)tv_get_number(&argvars[2]);
- }
- if (countcc < 0 || countcc > 1) {
- semsg(_(e_using_number_as_bool_nr), countcc);
- return;
- }
-
- int (*ptr2len)(const char *);
- if (countcc) {
- ptr2len = utf_ptr2len;
- } else {
- ptr2len = utfc_ptr2len;
- }
-
- const char *p;
- int len;
- for (p = str, len = 0; p <= str + idx; len++) {
- if (*p == NUL) {
- return;
- }
- p += ptr2len(p);
- }
-
- rettv->vval.v_number = len > 0 ? len - 1 : 0;
-}
-
/// "chdir(dir)" function
static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -925,8 +876,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (!error) {
- rettv->vval.v_number = do_dialog(type, NULL, (char *)message, (char *)buttons, def, NULL,
- false);
+ rettv->vval.v_number = do_dialog(type, NULL, message, buttons, def, NULL, false);
}
}
@@ -936,10 +886,90 @@ static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
var_item_copy(NULL, &argvars[0], rettv, false, 0);
}
+/// Count the number of times "needle" occurs in string "haystack".
+///
+/// @param ic ignore case
+static varnumber_T count_string(const char *haystack, const char *needle, bool ic)
+{
+ varnumber_T n = 0;
+ const char *p = haystack;
+
+ if (p == NULL || needle == NULL || *needle == NUL) {
+ return 0;
+ }
+
+ if (ic) {
+ const size_t len = strlen(needle);
+
+ while (*p != NUL) {
+ if (mb_strnicmp(p, needle, len) == 0) {
+ n++;
+ p += len;
+ } else {
+ MB_PTR_ADV(p);
+ }
+ }
+ } else {
+ const char *next;
+ while ((next = strstr(p, needle)) != NULL) {
+ n++;
+ p = next + strlen(needle);
+ }
+ }
+
+ return n;
+}
+
+/// Count the number of times item "needle" occurs in List "l" starting at index "idx".
+///
+/// @param ic ignore case
+static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic)
+{
+ if (tv_list_len(l) == 0) {
+ return 0;
+ }
+
+ listitem_T *li = tv_list_find(l, (int)idx);
+ if (li == NULL) {
+ semsg(_(e_list_index_out_of_range_nr), idx);
+ return 0;
+ }
+
+ varnumber_T n = 0;
+
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic, false)) {
+ n++;
+ }
+ }
+
+ return n;
+}
+
+/// Count the number of times item "needle" occurs in Dict "d".
+///
+/// @param ic ignore case
+static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic)
+{
+ if (d == NULL) {
+ return 0;
+ }
+
+ varnumber_T n = 0;
+
+ TV_DICT_ITER(d, di, {
+ if (tv_equal(&di->di_tv, needle, ic, false)) {
+ n++;
+ }
+ });
+
+ return n;
+}
+
/// "count()" function
static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- long n = 0;
+ varnumber_T n = 0;
int ic = 0;
bool error = false;
@@ -947,78 +977,30 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ic = (int)tv_get_number_chk(&argvars[2], &error);
}
- if (argvars[0].v_type == VAR_STRING) {
- const char *expr = tv_get_string_chk(&argvars[1]);
- const char *p = argvars[0].vval.v_string;
-
- if (!error && expr != NULL && *expr != NUL && p != NULL) {
- if (ic) {
- const size_t len = strlen(expr);
-
- while (*p != NUL) {
- if (mb_strnicmp((char *)p, (char *)expr, len) == 0) {
- n++;
- p += len;
- } else {
- MB_PTR_ADV(p);
- }
- }
- } else {
- char *next;
- while ((next = strstr((char *)p, (char *)expr)) != NULL) {
- n++;
- p = next + strlen(expr);
- }
- }
+ if (!error && argvars[0].v_type == VAR_STRING) {
+ n = count_string(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic);
+ } else if (!error && argvars[0].v_type == VAR_LIST) {
+ int64_t idx = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ idx = (int64_t)tv_get_number_chk(&argvars[3], &error);
}
- } else if (argvars[0].v_type == VAR_LIST) {
- list_T *l = argvars[0].vval.v_list;
-
- if (l != NULL) {
- listitem_T *li = tv_list_first(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- long idx = tv_get_number_chk(&argvars[3], &error);
- if (!error) {
- li = tv_list_find(l, (int)idx);
- if (li == NULL) {
- semsg(_(e_listidx), (int64_t)idx);
- }
- }
- }
- if (error) {
- li = NULL;
- }
- }
-
- for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
- n++;
- }
- }
+ if (!error) {
+ n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
}
- } else if (argvars[0].v_type == VAR_DICT) {
+ } else if (!error && argvars[0].v_type == VAR_DICT) {
dict_T *d = argvars[0].vval.v_dict;
if (d != NULL) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- emsg(_(e_invarg));
- }
- }
-
- int todo = error ? 0 : (int)d->dv_hashtab.ht_used;
- for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
- n++;
- }
- }
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ emsg(_(e_invarg));
+ } else {
+ n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
}
}
- } else {
- semsg(_(e_listdictarg), "count()");
+ } else if (!error) {
+ semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()");
}
rettv->vval.v_number = n;
}
@@ -1042,7 +1024,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
Dictionary ctx_dict = ctx_to_dict(ctx);
Error err = ERROR_INIT;
- object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
+ (void)object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
api_free_dictionary(ctx_dict);
api_clear_error(&err);
}
@@ -1064,17 +1046,17 @@ static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
TV_LIST_ITER(argvars[0].vval.v_list, li, {
typval_T *tv_li = TV_LIST_ITEM_TV(li);
if (tv_li->v_type == VAR_STRING) {
- if (strequal((char *)tv_li->vval.v_string, "regs")) {
+ if (strequal(tv_li->vval.v_string, "regs")) {
types |= kCtxRegs;
- } else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
+ } else if (strequal(tv_li->vval.v_string, "jumps")) {
types |= kCtxJumps;
- } else if (strequal((char *)tv_li->vval.v_string, "bufs")) {
+ } else if (strequal(tv_li->vval.v_string, "bufs")) {
types |= kCtxBufs;
- } else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
+ } else if (strequal(tv_li->vval.v_string, "gvars")) {
types |= kCtxGVars;
- } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
+ } else if (strequal(tv_li->vval.v_string, "sfuncs")) {
types |= kCtxSFuncs;
- } else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
+ } else if (strequal(tv_li->vval.v_string, "funcs")) {
types |= kCtxFuncs;
}
}
@@ -1108,14 +1090,16 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- int save_did_emsg = did_emsg;
+ const int save_did_emsg = did_emsg;
did_emsg = false;
Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
Context tmp = CONTEXT_INIT;
- ctx_from_dict(dict, &tmp);
+ Error err = ERROR_INIT;
+ ctx_from_dict(dict, &tmp, &err);
- if (did_emsg) {
+ if (ERROR_SET(&err)) {
+ semsg("%s", err.msg);
ctx_free(&tmp);
} else {
ctx_free(ctx);
@@ -1123,6 +1107,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
api_free_dictionary(dict);
+ api_clear_error(&err);
did_emsg = save_did_emsg;
}
@@ -1134,12 +1119,13 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// Set the cursor position.
-/// If 'charcol' is true, then use the column number as a character offset.
+/// If "charcol" is true, then use the column number as a character offset.
/// Otherwise use the column number as a byte offset.
static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
{
- long lnum, col;
- long coladd = 0;
+ linenr_T lnum;
+ colnr_T col;
+ colnr_T coladd = 0;
bool set_curswant = true;
rettv->vval.v_number = -1;
@@ -1167,12 +1153,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
} else if (lnum == 0) {
lnum = curwin->w_cursor.lnum;
}
- col = (long)tv_get_number_chk(&argvars[1], NULL);
+ col = (colnr_T)tv_get_number_chk(&argvars[1], NULL);
if (charcol) {
- col = buf_charidx_to_byteidx(curbuf, (linenr_T)lnum, (int)col) + 1;
+ col = buf_charidx_to_byteidx(curbuf, lnum, (int)col) + 1;
}
if (argvars[2].v_type != VAR_UNKNOWN) {
- coladd = (long)tv_get_number_chk(&argvars[2], NULL);
+ coladd = (colnr_T)tv_get_number_chk(&argvars[2], NULL);
}
} else {
emsg(_(e_invarg));
@@ -1182,12 +1168,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
return; // type error; errmsg already given
}
if (lnum > 0) {
- curwin->w_cursor.lnum = (linenr_T)lnum;
+ curwin->w_cursor.lnum = lnum;
}
if (col > 0) {
- curwin->w_cursor.col = (colnr_T)col - 1;
+ curwin->w_cursor.col = col - 1;
}
- curwin->w_cursor.coladd = (colnr_T)coladd;
+ curwin->w_cursor.coladd = coladd;
// Make sure the cursor is in a valid position.
check_cursor();
@@ -1236,18 +1222,16 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "deepcopy()" function
static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int noref = 0;
+ if (tv_check_for_opt_bool_arg(argvars, 1) == FAIL) {
+ return;
+ }
+ varnumber_T noref = 0;
if (argvars[1].v_type != VAR_UNKNOWN) {
- noref = (int)tv_get_bool_chk(&argvars[1], NULL);
- }
- if (noref < 0 || noref > 1) {
- semsg(_(e_using_number_as_bool_nr), noref);
- } else {
- var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
- ? get_copyID()
- : 0));
+ noref = tv_get_bool_chk(&argvars[1], NULL);
}
+
+ var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() : 0));
}
/// "delete()" function
@@ -1546,11 +1530,11 @@ static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
const char *s = tv_get_string_chk(&argvars[0]);
if (s != NULL) {
- s = (const char *)skipwhite(s);
+ s = skipwhite(s);
}
const char *const expr_start = s;
- if (s == NULL || eval1((char **)&s, rettv, true) == FAIL) {
+ if (s == NULL || eval1((char **)&s, rettv, &EVALARG_EVALUATE) == FAIL) {
if (expr_start != NULL && !aborting()) {
semsg(_(e_invexpr2), expr_start);
}
@@ -1722,7 +1706,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(exp);
}
} else if (*p == '&' || *p == '+') { // Option.
- n = (get_option_tv(&p, NULL, true) == OK);
+ n = (eval_option(&p, NULL, true) == OK);
if (*skipwhite(p) != NUL) {
n = false; // Trailing garbage.
}
@@ -1752,7 +1736,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char *p_csl_save = p_csl;
// avoid using 'completeslash' here
- p_csl = empty_option;
+ p_csl = empty_string_option;
#endif
rettv->v_type = VAR_STRING;
@@ -1769,7 +1753,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
emsg_off++;
}
size_t len;
- char *errormsg = NULL;
+ const char *errormsg = NULL;
char *result = eval_vars((char *)s, s, &len, NULL, &errormsg, NULL, false);
if (p_verbose == 0) {
emsg_off--;
@@ -1779,7 +1763,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (rettv->v_type == VAR_LIST) {
tv_list_alloc_ret(rettv, (result != NULL));
if (result != NULL) {
- tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);
+ tv_list_append_string(rettv->vval.v_list, result, -1);
}
XFREE_CLEAR(result);
} else {
@@ -1805,8 +1789,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ExpandOne(&xpc, (char *)s, NULL, options, WILD_ALL_KEEP);
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)xpc.xp_files[i], -1);
+ tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
}
ExpandCleanup(&xpc);
}
@@ -1835,7 +1818,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// Expand all the special characters in a command string.
static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- char *errormsg = NULL;
+ const char *errormsg = NULL;
bool emsgoff = true;
if (argvars[1].v_type == VAR_DICT
@@ -1870,8 +1853,8 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = cmdstr;
}
-/// "flatten(list[, {maxdepth}])" function
-static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// "flatten()" and "flattennew()" functions
+static void flatten_common(typval_T *argvars, typval_T *rettv, bool make_copy)
{
bool error = false;
@@ -1880,11 +1863,11 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- long maxdepth;
+ int maxdepth;
if (argvars[1].v_type == VAR_UNKNOWN) {
maxdepth = 999999;
} else {
- maxdepth = (long)tv_get_number_chk(&argvars[1], &error);
+ maxdepth = (int)tv_get_number_chk(&argvars[1], &error);
if (error) {
return;
}
@@ -1895,92 +1878,179 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
list_T *list = argvars[0].vval.v_list;
- if (list != NULL
- && !value_check_lock(tv_list_locked(list),
- N_("flatten() argument"),
- TV_TRANSLATE)
- && tv_list_flatten(list, maxdepth) == OK) {
- tv_copy(&argvars[0], rettv);
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = list;
+ if (list == NULL) {
+ return;
}
+
+ if (make_copy) {
+ list = tv_list_copy(NULL, list, false, get_copyID());
+ rettv->vval.v_list = list;
+ if (list == NULL) {
+ return;
+ }
+ } else {
+ if (value_check_lock(tv_list_locked(list), N_("flatten() argument"), TV_TRANSLATE)) {
+ return;
+ }
+ tv_list_ref(list);
+ }
+
+ tv_list_flatten(list, NULL, tv_list_len(list), maxdepth);
}
-/// "extend(list, list [, idx])" function
-/// "extend(dict, dict [, action])" function
-static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// "flatten(list[, {maxdepth}])" function
+static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *const arg_errmsg = N_("extend() argument");
+ flatten_common(argvars, rettv, false);
+}
- if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
- bool error = false;
+/// "flattennew(list[, {maxdepth}])" function
+static void f_flattennew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ flatten_common(argvars, rettv, true);
+}
- list_T *const l1 = argvars[0].vval.v_list;
- list_T *const l2 = argvars[1].vval.v_list;
- if (!value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
- listitem_T *item;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- long before = (long)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return; // Type error; errmsg already given.
- }
+/// extend() a List. Append List argvars[1] to List argvars[0] before index
+/// argvars[3] and return the resulting list in "rettv".
+///
+/// @param is_new true for extendnew()
+static void extend_list(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
+{
+ bool error = false;
- if (before == tv_list_len(l1)) {
- item = NULL;
- } else {
- item = tv_list_find(l1, (int)before);
- if (item == NULL) {
- semsg(_(e_listidx), (int64_t)before);
- return;
- }
- }
- } else {
+ list_T *l1 = argvars[0].vval.v_list;
+ list_T *const l2 = argvars[1].vval.v_list;
+ if (is_new || !value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
+ if (is_new) {
+ l1 = tv_list_copy(NULL, l1, false, get_copyID());
+ if (l1 == NULL) {
+ return;
+ }
+ }
+
+ listitem_T *item;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int before = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (before == tv_list_len(l1)) {
item = NULL;
+ } else {
+ item = tv_list_find(l1, before);
+ if (item == NULL) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)before);
+ return;
+ }
}
- tv_list_extend(l1, l2, item);
+ } else {
+ item = NULL;
+ }
+ tv_list_extend(l1, l2, item);
+ if (is_new) {
+ *rettv = (typval_T){
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_list = l1,
+ };
+ } else {
tv_copy(&argvars[0], rettv);
}
- } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type ==
- VAR_DICT) {
- dict_T *const d1 = argvars[0].vval.v_dict;
- dict_T *const d2 = argvars[1].vval.v_dict;
- if (d1 == NULL) {
- const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
- (void)locked;
- assert(locked == true);
- } else if (d2 == NULL) {
- // Do nothing
- tv_copy(&argvars[0], rettv);
- } else if (!value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
- const char *action = "force";
- // Check the third argument.
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const av[] = { "keep", "force", "error" };
+ }
+}
- action = tv_get_string_chk(&argvars[2]);
- if (action == NULL) {
- return; // Type error; error message already given.
- }
- size_t i;
- for (i = 0; i < ARRAY_SIZE(av); i++) {
- if (strcmp(action, av[i]) == 0) {
- break;
- }
- }
- if (i == 3) {
- semsg(_(e_invarg2), action);
- return;
+/// extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
+/// resulting Dict in "rettv".
+///
+/// @param is_new true for extendnew()
+static void extend_dict(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
+{
+ dict_T *d1 = argvars[0].vval.v_dict;
+ dict_T *const d2 = argvars[1].vval.v_dict;
+ if (d1 == NULL) {
+ const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
+ (void)locked;
+ assert(locked == true);
+ } else if (d2 == NULL) {
+ // Do nothing
+ tv_copy(&argvars[0], rettv);
+ } else if (is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ if (is_new) {
+ d1 = tv_dict_copy(NULL, d1, false, get_copyID());
+ if (d1 == NULL) {
+ return;
+ }
+ }
+
+ const char *action = "force";
+ // Check the third argument.
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const av[] = { "keep", "force", "error" };
+
+ action = tv_get_string_chk(&argvars[2]);
+ if (action == NULL) {
+ return; // Type error; error message already given.
+ }
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(av); i++) {
+ if (strcmp(action, av[i]) == 0) {
+ break;
}
}
+ if (i == 3) {
+ semsg(_(e_invarg2), action);
+ return;
+ }
+ }
- tv_dict_extend(d1, d2, action);
+ tv_dict_extend(d1, d2, action);
+ if (is_new) {
+ *rettv = (typval_T){
+ .v_type = VAR_DICT,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_dict = d1,
+ };
+ } else {
tv_copy(&argvars[0], rettv);
}
+ }
+}
+
+/// "extend()" or "extendnew()" function.
+///
+/// @param is_new true for extendnew()
+static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new)
+{
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
+ extend_list(argvars, arg_errmsg, is_new, rettv);
+ } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) {
+ extend_dict(argvars, arg_errmsg, is_new, rettv);
} else {
- semsg(_(e_listdictarg), "extend()");
+ semsg(_(e_listdictarg), is_new ? "extendnew()" : "extend()");
}
}
+/// "extend(list, list [, idx])" function
+/// "extend(dict, dict [, action])" function
+static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *errmsg = N_("extend() argument");
+ extend(argvars, rettv, errmsg, false);
+}
+
+/// "extendnew(list, list [, idx])" function
+/// "extendnew(dict, dict [, action])" function
+static void f_extendnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *errmsg = N_("extendnew() argument");
+ extend(argvars, rettv, errmsg, true);
+}
+
/// "feedkeys()" function
static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2053,6 +2123,9 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
}
if (*fname != NUL && !error) {
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+
do {
if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) {
xfree(fresult);
@@ -2063,13 +2136,17 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
find_what, curbuf->b_ffname,
(find_what == FINDFILE_DIR
? ""
- : curbuf->b_p_sua));
+ : curbuf->b_p_sua),
+ &file_to_find, &search_ctx);
first = false;
if (fresult != NULL && rettv->v_type == VAR_LIST) {
- tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1);
+ tv_list_append_string(rettv->vval.v_list, fresult, -1);
}
} while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
+
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
}
if (rettv->v_type == VAR_STRING) {
@@ -2077,12 +2154,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
}
}
-/// "filter()" function
-static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, false);
-}
-
/// "finddir({fname}[, {path}[, {count}]])" function
static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2164,7 +2235,8 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "foreground()" function
static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{}
+{
+}
static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2433,7 +2505,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
[kCdScopeTabpage] = 0, // Number of tab to look at.
};
- char *cwd = NULL; // Current working directory to print
+ char *cwd = NULL; // Current working directory to print
char *from = NULL; // The original string to copy
tabpage_T *tp = curtab; // The tabpage to look at.
@@ -2681,47 +2753,6 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_buf_local_marks(buf, rettv->vval.v_list);
}
-/// "getmousepos()" function
-static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int row = mouse_row;
- int col = mouse_col;
- int grid = mouse_grid;
- varnumber_T winid = 0;
- varnumber_T winrow = 0;
- varnumber_T wincol = 0;
- linenr_T lnum = 0;
- varnumber_T column = 0;
-
- tv_dict_alloc_ret(rettv);
- dict_T *d = rettv->vval.v_dict;
-
- tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1);
- tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1);
-
- win_T *wp = mouse_find_win(&grid, &row, &col);
- if (wp != NULL) {
- int height = wp->w_height + wp->w_hsep_height + wp->w_status_height;
- // The height is adjusted by 1 when there is a bottom border. This is not
- // necessary for a top border since `row` starts at -1 in that case.
- if (row < height + wp->w_border_adj[2]) {
- winid = wp->handle;
- winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border
- wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border
- if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) {
- (void)mouse_comp_pos(wp, &row, &col, &lnum);
- col = vcol2col(wp, lnum, col);
- column = col + 1;
- }
- }
- }
- tv_dict_add_nr(d, S_LEN("winid"), winid);
- tv_dict_add_nr(d, S_LEN("winrow"), winrow);
- tv_dict_add_nr(d, S_LEN("wincol"), wincol);
- tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum);
- tv_dict_add_nr(d, S_LEN("column"), column);
-}
-
/// "getpid()" function
static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2840,7 +2871,8 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// Dummy timer callback. Used by f_wait().
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
-{}
+{
+}
/// Dummy timer close callback. Used by f_wait().
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
@@ -2867,8 +2899,8 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
int timeout = (int)argvars[0].vval.v_number;
typval_T expr = argvars[1];
int interval = argvars[2].v_type == VAR_NUMBER
- ? (int)argvars[2].vval.v_number
- : 200; // Default.
+ ? (int)argvars[2].vval.v_number
+ : 200; // Default.
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
// Start dummy timer.
@@ -2882,8 +2914,11 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool error = false;
const int called_emsg_before = called_emsg;
+ // Flush screen updates before blocking.
+ ui_flush();
+
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
- eval_expr_typval(&expr, &argv, 0, &exprval) != OK
+ eval_expr_typval(&expr, false, &argv, 0, &exprval) != OK
|| tv_get_number_chk(&exprval, &error)
|| called_emsg > called_emsg_before || error || got_int);
@@ -2941,8 +2976,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
WILD_ALL_KEEP);
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
+ tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
}
ExpandCleanup(&xpc);
}
@@ -2983,7 +3017,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (file != NULL && !error) {
garray_T ga;
ga_init(&ga, (int)sizeof(char *), 10);
- globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags);
+ globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false);
if (rettv->v_type == VAR_STRING) {
rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
@@ -3013,14 +3047,12 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "gettext()" function
static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_STRING
- || argvars[0].vval.v_string == NULL
- || *argvars[0].vval.v_string == NUL) {
- semsg(_(e_invarg2), tv_get_string(&argvars[0]));
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string));
+ if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) {
+ return;
}
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string));
}
/// "has()" function
@@ -3064,9 +3096,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"conceal",
"cursorbind",
"cursorshape",
-#ifdef DEBUG
- "debug",
-#endif
"dialog_con",
"diff",
"digraphs",
@@ -3149,6 +3178,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"windows",
"winaltkeys",
"writebackup",
+#ifdef HAVE_XATTR
+ "xattr",
+#endif
"nvim",
};
@@ -3164,7 +3196,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (!n) {
- if (STRNICMP(name, "patch", 5) == 0) {
+ if (STRNICMP(name, "gui_running", 11) == 0) {
+ n = ui_gui_attached();
+ } else if (STRNICMP(name, "patch", 5) == 0) {
if (name[5] == '-'
&& strlen(name) >= 11
&& ascii_isdigit(name[6])
@@ -3219,7 +3253,7 @@ static bool has_wsl(void)
static TriState has_wsl = kNone;
if (has_wsl == kNone) {
Error err = ERROR_INIT;
- Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.loop.os_uname()['release']:lower()"
+ Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.uv.os_uname()['release']:lower()"
":match('microsoft') and true or false"),
(Array)ARRAY_DICT_INIT, &err);
assert(!ERROR_SET(&err));
@@ -3253,7 +3287,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
[kCdScopeTabpage] = 0, // Number of tab to look at.
};
- tabpage_T *tp = curtab; // The tabpage to look at.
+ tabpage_T *tp = curtab; // The tabpage to look at.
win_T *win = curwin; // The window to look at.
rettv->v_type = VAR_NUMBER;
@@ -3352,34 +3386,6 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = xstrdup(hostname);
}
-/// iconv() function
-static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- vimconv_T vimconv;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const str = tv_get_string(&argvars[0]);
- char buf1[NUMBUFLEN];
- char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1)));
- char buf2[NUMBUFLEN];
- char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2)));
- vimconv.vc_type = CONV_NONE;
- convert_setup(&vimconv, from, to);
-
- // If the encodings are equal, no conversion needed.
- if (vimconv.vc_type == CONV_NONE) {
- rettv->vval.v_string = xstrdup(str);
- } else {
- rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL);
- }
-
- convert_setup(&vimconv, NULL, NULL);
- xfree(from);
- xfree(to);
-}
-
/// "indent()" function
static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -3394,7 +3400,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "index()" function
static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- long idx = 0;
+ int idx = 0;
bool ic = false;
rettv->vval.v_number = -1;
@@ -3421,7 +3427,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
for (idx = start; idx < tv_blob_len(b); idx++) {
typval_T tv;
tv.v_type = VAR_NUMBER;
- tv.vval.v_number = tv_blob_get(b, (int)idx);
+ tv.vval.v_number = tv_blob_get(b, idx);
if (tv_equal(&tv, &argvars[1], ic, false)) {
rettv->vval.v_number = idx;
return;
@@ -3447,7 +3453,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (error || idx == -1) {
item = NULL;
} else {
- item = tv_list_find(l, (int)idx);
+ item = tv_list_find(l, idx);
assert(item != NULL);
}
if (argvars[3].v_type != VAR_UNKNOWN) {
@@ -3466,6 +3472,138 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
+/// Evaluate "expr" with the v:key and v:val arguments and return the result.
+/// The expression is expected to return a boolean value. The caller should set
+/// the VV_KEY and VV_VAL vim variables before calling this function.
+static varnumber_T indexof_eval_expr(typval_T *expr)
+{
+ typval_T argv[3];
+ argv[0] = *get_vim_var_tv(VV_KEY);
+ argv[1] = *get_vim_var_tv(VV_VAL);
+ typval_T newtv;
+ newtv.v_type = VAR_UNKNOWN;
+
+ if (eval_expr_typval(expr, false, argv, 2, &newtv) == FAIL) {
+ return false;
+ }
+
+ bool error = false;
+ varnumber_T found = tv_get_bool_chk(&newtv, &error);
+ tv_clear(&newtv);
+
+ return error ? false : found;
+}
+
+/// Evaluate "expr" for each byte in the Blob "b" starting with the byte at
+/// "startidx" and return the index of the byte where "expr" is TRUE. Returns
+/// -1 if "expr" doesn't evaluate to TRUE for any of the bytes.
+static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr)
+{
+ if (b == NULL) {
+ return -1;
+ }
+
+ if (startidx < 0) {
+ // negative index: index from the last byte
+ startidx = tv_blob_len(b) + startidx;
+ if (startidx < 0) {
+ startidx = 0;
+ }
+ }
+
+ for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) {
+ set_vim_var_nr(VV_KEY, idx);
+ set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx));
+
+ if (indexof_eval_expr(expr)) {
+ return idx;
+ }
+ }
+
+ return -1;
+}
+
+/// Evaluate "expr" for each item in the List "l" starting with the item at
+/// "startidx" and return the index of the item where "expr" is TRUE. Returns
+/// -1 if "expr" doesn't evaluate to TRUE for any of the items.
+static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr)
+{
+ if (l == NULL) {
+ return -1;
+ }
+
+ listitem_T *item;
+ varnumber_T idx = 0;
+ if (startidx == 0) {
+ item = tv_list_first(l);
+ } else {
+ // Start at specified item.
+ idx = tv_list_uidx(l, (int)startidx);
+ if (idx == -1) {
+ item = NULL;
+ } else {
+ item = tv_list_find(l, (int)idx);
+ assert(item != NULL);
+ }
+ }
+
+ for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
+ set_vim_var_nr(VV_KEY, idx);
+ tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL));
+
+ bool found = indexof_eval_expr(expr);
+ tv_clear(get_vim_var_tv(VV_VAL));
+
+ if (found) {
+ return idx;
+ }
+ }
+
+ return -1;
+}
+
+/// "indexof()" function
+static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_list_or_blob_arg(argvars, 0) == FAIL
+ || tv_check_for_string_or_func_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_dict_arg(argvars, 2) == FAIL) {
+ return;
+ }
+
+ if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL)
+ || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) {
+ return;
+ }
+
+ varnumber_T startidx = 0;
+ if (argvars[2].v_type == VAR_DICT) {
+ startidx = tv_dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0);
+ }
+
+ typval_T save_val;
+ typval_T save_key;
+ prepare_vimvar(VV_VAL, &save_val);
+ prepare_vimvar(VV_KEY, &save_key);
+
+ // We reset "did_emsg" to be able to detect whether an error occurred
+ // during evaluation of the expression.
+ const int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ if (argvars[0].v_type == VAR_BLOB) {
+ rettv->vval.v_number = indexof_blob(argvars[0].vval.v_blob, startidx, &argvars[1]);
+ } else {
+ rettv->vval.v_number = indexof_list(argvars[0].vval.v_list, startidx, &argvars[1]);
+ }
+
+ restore_vimvar(VV_KEY, &save_key);
+ restore_vimvar(VV_VAL, &save_val);
+ did_emsg |= save_did_emsg;
+}
+
static bool inputsecret_flag = false;
/// "input()" function
@@ -3547,7 +3685,6 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "insert()" function
static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- list_T *l;
bool error = false;
if (argvars[0].v_type == VAR_BLOB) {
@@ -3559,11 +3696,11 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- long before = 0;
+ int before = 0;
const int len = tv_blob_len(b);
if (argvars[2].v_type != VAR_UNKNOWN) {
- before = (long)tv_get_number_chk(&argvars[2], &error);
+ before = (int)tv_get_number_chk(&argvars[2], &error);
if (error) {
return; // type error; errmsg already given
}
@@ -3590,9 +3727,13 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_copy(&argvars[0], rettv);
} else if (argvars[0].v_type != VAR_LIST) {
semsg(_(e_listblobarg), "insert()");
- } else if (!value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("insert() argument"), TV_TRANSLATE)) {
- long before = 0;
+ } else {
+ list_T *l = argvars[0].vval.v_list;
+ if (value_check_lock(tv_list_locked(l), N_("insert() argument"), TV_TRANSLATE)) {
+ return;
+ }
+
+ int64_t before = 0;
if (argvars[2].v_type != VAR_UNKNOWN) {
before = tv_get_number_chk(&argvars[2], &error);
}
@@ -3605,7 +3746,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (before != tv_list_len(l)) {
item = tv_list_find(l, (int)before);
if (item == NULL) {
- semsg(_(e_listidx), (int64_t)before);
+ semsg(_(e_list_index_out_of_range_nr), before);
l = NULL;
}
}
@@ -3617,8 +3758,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// "interrupt()" function
-static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED,
- EvalFuncData fptr FUNC_ATTR_UNUSED)
+static void f_interrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
got_int = true;
}
@@ -3795,7 +3935,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
if (!clear_env) {
typval_T temp_env = TV_INITIAL_VALUE;
- f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL });
+ f_environ(NULL, &temp_env, (EvalFuncData){ .null = NULL });
tv_dict_extend(env, temp_env.vval.v_dict, "force");
tv_dict_free(temp_env.vval.v_dict);
@@ -3812,12 +3952,13 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
}
}
#ifndef MSWIN
- // Set COLORTERM to "truecolor" if termguicolors is set and 256
- // otherwise, but only if it was set in the parent terminal at all
- dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM"));
- if (dv) {
- tv_dict_item_remove(env, dv);
- tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256");
+ // Set COLORTERM to "truecolor" if termguicolors is set
+ if (p_tgc) {
+ dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM"));
+ if (dv) {
+ tv_dict_item_remove(env, dv);
+ }
+ tv_dict_add_str(env, S_LEN("COLORTERM"), "truecolor");
}
#endif
}
@@ -3849,7 +3990,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
#ifdef MSWIN
TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
// Always use upper-case keys for Windows so we detect duplicate keys
- char *const key = strcase_save((const char *)var->di_key, true);
+ char *const key = strcase_save(var->di_key, true);
size_t len = strlen(key);
dictitem_T *dv = tv_dict_find(env, key, len);
if (dv) {
@@ -3995,7 +4136,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
env = create_environment(job_env, clear_env, pty, term_name);
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
+ Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty,
rpc, overlapped, detach, stdin_mode, cwd,
width, height, env, &rettv->vval.v_number);
if (chan) {
@@ -4052,6 +4193,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
ui_busy_start();
+ ui_flush();
list_T *args = argvars[0].vval.v_list;
Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs));
MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
@@ -4364,30 +4506,24 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv);
}
-/// "map()" function
-static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, true);
-}
-
static void find_some_match(typval_T *const argvars, typval_T *const rettv,
const SomeMatchType type)
{
char *str = NULL;
- long len = 0;
+ int64_t len = 0;
char *expr = NULL;
regmatch_T regmatch;
- long start = 0;
- long nth = 1;
+ int64_t start = 0;
+ int64_t nth = 1;
colnr_T startcol = 0;
bool match = false;
list_T *l = NULL;
- long idx = 0;
+ int idx = 0;
char *tofree = NULL;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
rettv->vval.v_number = -1;
switch (type) {
@@ -4421,7 +4557,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
li = tv_list_first(l);
} else {
expr = str = (char *)tv_get_string(&argvars[0]);
- len = (long)strlen(str);
+ len = (int64_t)strlen(str);
}
char patbuf[NUMBUFLEN];
@@ -4442,7 +4578,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
if (idx == -1) {
goto theend;
}
- li = tv_list_find(l, (int)idx);
+ li = tv_list_find(l, idx);
} else {
if (start < 0) {
start = 0;
@@ -4469,11 +4605,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
}
}
- regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING);
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog != NULL) {
regmatch.rm_ic = p_ic;
- for (;;) {
+ while (true) {
if (l != NULL) {
if (li == NULL) {
match = false;
@@ -4534,8 +4670,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
if (regmatch.endp[i] == NULL) {
tv_list_append_string(rettv->vval.v_list, NULL, 0);
} else {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)regmatch.startp[i],
+ tv_list_append_string(rettv->vval.v_list, regmatch.startp[i],
(regmatch.endp[i] - regmatch.startp[i]));
}
}
@@ -4545,7 +4680,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
if (l != NULL) {
tv_copy(TV_LIST_ITEM_TV(li), rettv);
} else {
- rettv->vval.v_string = xmemdupz((const char *)regmatch.startp[0],
+ rettv->vval.v_string = xmemdupz(regmatch.startp[0],
(size_t)(regmatch.endp[0] -
regmatch.startp[0]));
}
@@ -4674,7 +4809,7 @@ static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "mkdir()" function
static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int prot = 0755; // -V536
+ int prot = 0755;
rettv->vval.v_number = FAIL;
if (check_secure()) {
@@ -4692,6 +4827,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
*path_tail_with_sep((char *)dir) = NUL;
}
+ bool defer = false;
+ bool defer_recurse = false;
+ char *created = NULL;
if (argvars[1].v_type != VAR_UNKNOWN) {
if (argvars[2].v_type != VAR_UNKNOWN) {
prot = (int)tv_get_number_chk(&argvars[2], NULL);
@@ -4699,9 +4837,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
}
- if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
+ const char *arg2 = tv_get_string(&argvars[1]);
+ defer = vim_strchr(arg2, 'D') != NULL;
+ defer_recurse = vim_strchr(arg2, 'R') != NULL;
+ if ((defer || defer_recurse) && !can_add_defer()) {
+ return;
+ }
+
+ if (vim_strchr(arg2, 'p') != NULL) {
char *failed_dir;
- int ret = os_mkdir_recurse(dir, prot, &failed_dir);
+ int ret = os_mkdir_recurse(dir, prot, &failed_dir,
+ defer || defer_recurse ? &created : NULL);
if (ret != 0) {
semsg(_(e_mkdir), failed_dir, os_strerror(ret));
xfree(failed_dir);
@@ -4709,10 +4855,27 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
rettv->vval.v_number = OK;
- return;
}
}
- rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
+ if (rettv->vval.v_number == FAIL) {
+ rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
+ }
+
+ // Handle "D" and "R": deferred deletion of the created directory.
+ if (rettv->vval.v_number == OK
+ && created == NULL && (defer || defer_recurse)) {
+ created = FullName_save(dir, false);
+ }
+ if (created != NULL) {
+ typval_T tv[2];
+ tv[0].v_type = VAR_STRING;
+ tv[0].v_lock = VAR_UNLOCKED;
+ tv[0].vval.v_string = created;
+ tv[1].v_type = VAR_STRING;
+ tv[1].v_lock = VAR_UNLOCKED;
+ tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d");
+ add_defer("delete", 2, tv);
+ }
}
/// "mode()" function
@@ -4732,6 +4895,50 @@ static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->v_type = VAR_STRING;
}
+static void may_add_state_char(garray_T *gap, const char *include, uint8_t c)
+{
+ if (include == NULL || vim_strchr(include, c) != NULL) {
+ ga_append(gap, c);
+ }
+}
+
+/// "state()" function
+static void f_state(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ garray_T ga;
+ ga_init(&ga, 1, 20);
+ const char *include = NULL;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ include = tv_get_string(&argvars[0]);
+ }
+
+ if (!(stuff_empty() && typebuf.tb_len == 0 && !using_script())) {
+ may_add_state_char(&ga, include, 'm');
+ }
+ if (op_pending()) {
+ may_add_state_char(&ga, include, 'o');
+ }
+ if (autocmd_busy) {
+ may_add_state_char(&ga, include, 'x');
+ }
+ if (ins_compl_active()) {
+ may_add_state_char(&ga, include, 'a');
+ }
+ if (!get_was_safe_state()) {
+ may_add_state_char(&ga, include, 'S');
+ }
+ for (int i = 0; i < get_callback_depth() && i < 3; i++) {
+ may_add_state_char(&ga, include, 'c');
+ }
+ if (msg_scrolled > 0) {
+ may_add_state_char(&ga, include, 's');
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = ga.ga_data;
+}
+
/// "msgpackdump()" function
static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
FUNC_ATTR_NONNULL_ALL
@@ -4755,7 +4962,7 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
int idx = 0;
TV_LIST_ITER(list, li, {
- vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
+ vim_snprintf(msgbuf, sizeof(msgbuf), msg, idx);
idx++;
if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
break;
@@ -4790,9 +4997,10 @@ static int msgpackparse_convert_item(const msgpack_object data, const msgpack_un
tv_list_append_owned_tv(ret_list, tv);
return OK;
}
- default:
+ case MSGPACK_UNPACK_EXTRA_BYTES:
abort();
}
+ UNREACHABLE;
}
static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret_list)
@@ -4813,7 +5021,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret
}
msgpack_unpacked unpacked;
msgpack_unpacked_init(&unpacked);
- do {
+ while (true) {
if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
emsg(_(e_outofmem));
goto end;
@@ -4843,7 +5051,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret
if (rlret == OK) {
break;
}
- } while (true);
+ }
end:
msgpack_unpacker_free(unpacker);
@@ -4928,7 +5136,7 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- char buf[MB_MAXBYTES];
+ char buf[MB_MAXCHAR];
const int len = utf_char2bytes((int)num, buf);
rettv->v_type = VAR_STRING;
@@ -5057,7 +5265,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncDa
}
callback_free(&buf->b_prompt_interrupt);
- buf->b_prompt_interrupt= interrupt_callback;
+ buf->b_prompt_interrupt = interrupt_callback;
}
/// "prompt_getprompt({buffer})" function
@@ -5213,10 +5421,10 @@ static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
goto theend;
}
- typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L));
- typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L));
- typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L));
- typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L));
+ typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0));
+ typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1));
+ typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2));
+ typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3));
if (tvx->v_type != VAR_NUMBER) {
goto theend;
}
@@ -5344,7 +5552,7 @@ static varnumber_T readdir_checkitem(void *context, const char *name)
argv[0].vval.v_string = (char *)name;
typval_T rettv;
- if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
+ if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) {
goto theend;
}
@@ -5388,18 +5596,27 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
int io_size = sizeof(buf);
char *prev = NULL; // previously read bytes, if any
- ptrdiff_t prevlen = 0; // length of data in prev
+ ptrdiff_t prevlen = 0; // length of data in prev
ptrdiff_t prevsize = 0; // size of prev buffer
- long maxline = MAXLNUM;
+ int64_t maxline = MAXLNUM;
+ off_T offset = 0;
+ off_T size = -1;
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
- binary = true;
- } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
- blob = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- maxline = tv_get_number(&argvars[2]);
+ if (always_blob) {
+ offset = (off_T)tv_get_number(&argvars[1]);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ size = (off_T)tv_get_number(&argvars[2]);
+ }
+ } else {
+ if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
+ binary = true;
+ } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
+ blob = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ maxline = tv_get_number(&argvars[2]);
+ }
}
}
@@ -5418,11 +5635,8 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
if (blob) {
tv_blob_alloc_ret(rettv);
- if (!read_blob(fd, rettv->vval.v_blob)) {
+ if (read_blob(fd, rettv, offset, size) == FAIL) {
semsg(_(e_notread), fname);
- // An empty blob is returned on error.
- tv_blob_free(rettv->vval.v_blob);
- rettv->vval.v_blob = NULL;
}
fclose(fd);
return;
@@ -5444,7 +5658,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
p++) {
if (readlen <= 0 || *p == '\n') {
- char *s = NULL;
+ char *s = NULL;
size_t len = (size_t)(p - start);
// Finished a line. Remove CRs before NL.
@@ -5461,7 +5675,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
}
if (prevlen == 0) {
assert(len < INT_MAX);
- s = xstrnsave(start, len);
+ s = xmemdupz(start, len);
} else {
// Change "prev" buffer to be the right size. This way
// the bytes are only copied once, and very long lines are
@@ -5500,11 +5714,11 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
// Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
// these may be in the "prev" string.
char back1 = p >= buf + 1 ? p[-1]
- : prevlen >= 1 ? prev[prevlen - 1] : NUL;
+ : prevlen >= 1 ? prev[prevlen - 1] : NUL;
char back2 = p >= buf + 2 ? p[-2]
- : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
- : prevlen >=
- 2 ? prev[prevlen - 2] : NUL;
+ : (p == buf + 1 && prevlen >= 1
+ ? prev[prevlen - 1]
+ : prevlen >= 2 ? prev[prevlen - 2] : NUL);
if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) {
char *dest = p - 2;
@@ -5517,9 +5731,9 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
// have to shuffle buf to close gap
int adjust_prevlen = 0;
- if (dest < buf) { // -V782
+ if (dest < buf) {
// adjust_prevlen must be 1 or 2.
- adjust_prevlen = (int)(buf - dest); // -V782
+ adjust_prevlen = (int)(buf - dest);
dest = buf;
}
if (readlen > p - buf + 1) {
@@ -5547,7 +5761,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
prevsize = p - start;
} else {
ptrdiff_t grow50pc = (prevsize * 3) / 2;
- ptrdiff_t growmin = (p - start) * 2 + prevlen;
+ ptrdiff_t growmin = (p - start) * 2 + prevlen;
prevsize = grow50pc > growmin ? grow50pc : growmin;
}
prev = xrealloc(prev, (size_t)prevsize);
@@ -5654,8 +5868,8 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL
}
bool error = false;
- varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error);
- varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error);
+ varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0, &error);
+ varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1, &error);
if (error) {
return FAIL;
}
@@ -5769,6 +5983,37 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
while (n-- > 0) {
tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);
}
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ tv_blob_alloc_ret(rettv);
+ if (argvars[0].vval.v_blob == NULL || n <= 0) {
+ return;
+ }
+
+ const int slen = argvars[0].vval.v_blob->bv_ga.ga_len;
+ const int len = (int)(slen * n);
+ if (len <= 0) {
+ return;
+ }
+
+ ga_grow(&rettv->vval.v_blob->bv_ga, len);
+
+ rettv->vval.v_blob->bv_ga.ga_len = len;
+
+ int i;
+ for (i = 0; i < slen; i++) {
+ if (tv_blob_get(argvars[0].vval.v_blob, i) != 0) {
+ break;
+ }
+ }
+
+ if (i == slen) {
+ // No need to copy since all bytes are already zero
+ return;
+ }
+
+ for (i = 0; i < n; i++) {
+ tv_blob_set_range(rettv->vval.v_blob, i * slen, (i + 1) * slen - 1, argvars);
+ }
} else {
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
@@ -5842,8 +6087,8 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char *const buf = xmallocz(MAXPATHL);
char *cpy;
- for (;;) {
- for (;;) {
+ while (true) {
+ while (true) {
len = readlink(p, buf, MAXPATHL);
if (len <= 0) {
break;
@@ -5860,13 +6105,13 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
// Ensure that the result will have a trailing path separator
- // if the argument has one. */
+ // if the argument has one.
if (remain == NULL && has_trailing_pathsep) {
add_pathsep(buf);
}
// Separate the first path component in the link value and
- // concatenate the remainders. */
+ // concatenate the remainders.
q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
if (*q != NUL) {
cpy = remain;
@@ -5880,7 +6125,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
q = path_tail(p);
if (q > p && *q == NUL) {
// Ignore trailing path separator.
- q[-1] = NUL;
+ p[q - p - 1] = NUL;
q = path_tail(p);
}
if (q > p && !path_is_absolute(buf)) {
@@ -5959,7 +6204,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
# else
char *v = os_realpath(fname, NULL);
- rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v);
+ rettv->vval.v_string = v == NULL ? xstrdup(fname) : v;
# endif
#endif
@@ -5969,6 +6214,10 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "reverse({list})" function
static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ if (tv_check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
if (argvars[0].v_type == VAR_BLOB) {
blob_T *const b = argvars[0].vval.v_blob;
const int len = tv_blob_len(b);
@@ -5979,9 +6228,14 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_blob_set(b, len - i - 1, tmp);
}
tv_blob_set_ret(rettv, b);
- } else if (argvars[0].v_type != VAR_LIST) {
- semsg(_(e_listblobarg), "reverse()");
- } else {
+ } else if (argvars[0].v_type == VAR_STRING) {
+ rettv->v_type = VAR_STRING;
+ if (argvars[0].vval.v_string != NULL) {
+ rettv->vval.v_string = reverse_text(argvars[0].vval.v_string);
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+ } else if (argvars[0].v_type == VAR_LIST) {
list_T *const l = argvars[0].vval.v_list;
if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"),
TV_TRANSLATE)) {
@@ -5991,102 +6245,181 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "reduce(list, { accumulator, element -> value } [, initial])" function
-static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// Implementation of reduce() for list "argvars[0]", using the function "expr"
+/// starting with the optional initial value argvars[2] and return the result in
+/// "rettv".
+static void reduce_list(typval_T *argvars, typval_T *expr, typval_T *rettv)
{
- if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
- emsg(_(e_listblobreq));
- return;
- }
+ list_T *const l = argvars[0].vval.v_list;
+ const int called_emsg_start = called_emsg;
- const char *func_name;
- partial_T *partial = NULL;
- if (argvars[1].v_type == VAR_FUNC) {
- func_name = argvars[1].vval.v_string;
- } else if (argvars[1].v_type == VAR_PARTIAL) {
- partial = argvars[1].vval.v_partial;
- func_name = partial_name(partial);
+ typval_T initial;
+ const listitem_T *li = NULL;
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_list_len(l) == 0) {
+ semsg(_(e_reduceempty), "List");
+ return;
+ }
+ const listitem_T *const first = tv_list_first(l);
+ initial = *TV_LIST_ITEM_TV(first);
+ li = TV_LIST_ITEM_NEXT(l, first);
} else {
- func_name = tv_get_string(&argvars[1]);
+ initial = argvars[2];
+ li = tv_list_first(l);
}
- if (*func_name == NUL) {
- return; // type error or empty name
+
+ tv_copy(&initial, rettv);
+
+ if (l == NULL) {
+ return;
}
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.fe_evaluate = true;
- funcexe.fe_partial = partial;
+ const VarLockStatus prev_locked = tv_list_locked(l);
- typval_T initial;
- typval_T argv[3];
- if (argvars[0].v_type == VAR_LIST) {
- list_T *const l = argvars[0].vval.v_list;
- const listitem_T *li;
+ tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ typval_T argv[3];
+ argv[0] = *rettv;
+ argv[1] = *TV_LIST_ITEM_TV(li);
+ rettv->v_type = VAR_UNKNOWN;
- if (argvars[2].v_type == VAR_UNKNOWN) {
- if (tv_list_len(l) == 0) {
- semsg(_(e_reduceempty), "List");
- return;
- }
- const listitem_T *const first = tv_list_first(l);
- initial = *TV_LIST_ITEM_TV(first);
- li = TV_LIST_ITEM_NEXT(l, first);
- } else {
- initial = argvars[2];
- li = tv_list_first(l);
+ const int r = eval_expr_typval(expr, true, argv, 2, rettv);
+
+ tv_clear(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
}
+ }
+ tv_list_set_lock(l, prev_locked);
+}
- tv_copy(&initial, rettv);
+/// Implementation of reduce() for String "argvars[0]" using the function "expr"
+/// starting with the optional initial value "argvars[2]" and return the result
+/// in "rettv".
+static void reduce_string(typval_T *argvars, typval_T *expr, typval_T *rettv)
+{
+ const char *p = tv_get_string(&argvars[0]);
+ int len;
+ const int called_emsg_start = called_emsg;
- if (l != NULL) {
- const VarLockStatus prev_locked = tv_list_locked(l);
- const int called_emsg_start = called_emsg;
-
- tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
- for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- argv[0] = *rettv;
- argv[1] = *TV_LIST_ITEM_TV(li);
- rettv->v_type = VAR_UNKNOWN;
- const int r = call_func((char *)func_name, -1, rettv, 2, argv, &funcexe);
- tv_clear(&argv[0]);
- if (r == FAIL || called_emsg != called_emsg_start) {
- break;
- }
- }
- tv_list_set_lock(l, prev_locked);
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (*p == NUL) {
+ semsg(_(e_reduceempty), "String");
+ return;
}
+ len = utfc_ptr2len(p);
+ *rettv = (typval_T){
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = xmemdupz(p, (size_t)len),
+ };
+ p += len;
+ } else if (tv_check_for_string_arg(argvars, 2) == FAIL) {
+ return;
} else {
- const blob_T *const b = argvars[0].vval.v_blob;
- int i;
+ tv_copy(&argvars[2], rettv);
+ }
- if (argvars[2].v_type == VAR_UNKNOWN) {
- if (tv_blob_len(b) == 0) {
- semsg(_(e_reduceempty), "Blob");
- return;
- }
- initial.v_type = VAR_NUMBER;
- initial.vval.v_number = tv_blob_get(b, 0);
- i = 1;
- } else if (argvars[2].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
+ for (; *p != NUL; p += len) {
+ typval_T argv[3];
+ argv[0] = *rettv;
+ len = utfc_ptr2len(p);
+ argv[1] = (typval_T){
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = xmemdupz(p, (size_t)len),
+ };
+
+ const int r = eval_expr_typval(expr, true, argv, 2, rettv);
+
+ tv_clear(&argv[0]);
+ tv_clear(&argv[1]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
+ }
+ }
+}
+
+/// Implementation of reduce() for Blob "argvars[0]" using the function "expr"
+/// starting with the optional initial value "argvars[2]" and return the result
+/// in "rettv".
+static void reduce_blob(typval_T *argvars, typval_T *expr, typval_T *rettv)
+{
+ const blob_T *const b = argvars[0].vval.v_blob;
+ const int called_emsg_start = called_emsg;
+
+ typval_T initial;
+ int i;
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_blob_len(b) == 0) {
+ semsg(_(e_reduceempty), "Blob");
return;
- } else {
- initial = argvars[2];
- i = 0;
}
+ initial = (typval_T){
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_number = tv_blob_get(b, 0),
+ };
+ i = 1;
+ } else if (tv_check_for_number_arg(argvars, 2) == FAIL) {
+ return;
+ } else {
+ initial = argvars[2];
+ i = 0;
+ }
+
+ tv_copy(&initial, rettv);
+ for (; i < tv_blob_len(b); i++) {
+ typval_T argv[3];
+ argv[0] = *rettv;
+ argv[1] = (typval_T){
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_number = tv_blob_get(b, i),
+ };
- tv_copy(&initial, rettv);
- for (; i < tv_blob_len(b); i++) {
- argv[0] = *rettv;
- argv[1].v_type = VAR_NUMBER;
- argv[1].vval.v_number = tv_blob_get(b, i);
- if (call_func((char *)func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
- return;
- }
+ const int r = eval_expr_typval(expr, true, argv, 2, rettv);
+
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ return;
}
}
}
+/// "reduce(list, { accumulator, element -> value } [, initial])" function
+/// "reduce(blob, { accumulator, element -> value } [, initial])" function
+/// "reduce(string, { accumulator, element -> value } [, initial])" function
+static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (argvars[0].v_type != VAR_STRING
+ && argvars[0].v_type != VAR_LIST
+ && argvars[0].v_type != VAR_BLOB) {
+ emsg(_(e_string_list_or_blob_required));
+ return;
+ }
+
+ const char *func_name;
+ if (argvars[1].v_type == VAR_FUNC) {
+ func_name = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ func_name = partial_name(argvars[1].vval.v_partial);
+ } else {
+ func_name = tv_get_string(&argvars[1]);
+ }
+ if (func_name == NULL || *func_name == NUL) {
+ emsg(_(e_missing_function_argument));
+ return;
+ }
+
+ if (argvars[0].v_type == VAR_LIST) {
+ reduce_list(argvars, &argvars[1], rettv);
+ } else if (argvars[0].v_type == VAR_STRING) {
+ reduce_string(argvars, &argvars[1], rettv);
+ } else {
+ reduce_blob(argvars, &argvars[1], rettv);
+ }
+}
+
#define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount
@@ -6164,8 +6497,8 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
{
bool save_p_ws = p_ws;
int retval = 0; // default: FAIL
- long lnum_stop = 0;
- long time_limit = 0;
+ linenr_T lnum_stop = 0;
+ int64_t time_limit = 0;
int options = SEARCH_KEEP;
bool use_skip = false;
@@ -6187,7 +6520,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
// Optional arguments: line number to stop searching, timeout and skip.
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[2], NULL);
+ lnum_stop = (linenr_T)tv_get_number_chk(&argvars[2], NULL);
if (lnum_stop < 0) {
goto theend;
}
@@ -6217,14 +6550,14 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
pos_T pos = save_cursor = curwin->w_cursor;
pos_T firstpos = { 0 };
searchit_arg_T sia = {
- .sa_stop_lnum = (linenr_T)lnum_stop,
+ .sa_stop_lnum = lnum_stop,
.sa_tm = &tm,
};
int subpatnum;
// Repeat until {skip} returns false.
- for (;;) {
+ while (true) {
subpatnum
= searchit(curwin, curbuf, &pos, NULL, dir, (char *)pat, 1, options, RE_SEARCH, &sia);
// finding the first match again means there is no match where {skip}
@@ -6362,6 +6695,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
sctx_T save_current_sctx;
char *save_autocmd_fname, *save_autocmd_match;
+ bool save_autocmd_fname_full;
int save_autocmd_bufnr;
funccal_entry_T funccal_entry;
@@ -6371,6 +6705,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
save_current_sctx = current_sctx;
save_autocmd_fname = autocmd_fname;
save_autocmd_match = autocmd_match;
+ save_autocmd_fname_full = autocmd_fname_full;
save_autocmd_bufnr = autocmd_bufnr;
save_funccal(&funccal_entry);
@@ -6379,6 +6714,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry;
autocmd_fname = provider_caller_scope.autocmd_fname;
autocmd_match = provider_caller_scope.autocmd_match;
+ autocmd_fname_full = provider_caller_scope.autocmd_fname_full;
autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
set_current_funccal((funccall_T *)(provider_caller_scope.funccalp));
}
@@ -6396,6 +6732,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
exestack.ga_len--;
autocmd_fname = save_autocmd_fname;
autocmd_match = save_autocmd_match;
+ autocmd_fname_full = save_autocmd_fname_full;
autocmd_bufnr = save_autocmd_bufnr;
restore_funccal();
}
@@ -6404,7 +6741,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const char *name = NULL;
Channel *chan = find_channel(chan_id);
if (chan) {
- name = rpc_client_name(chan);
+ name = get_client_info(chan, "name");
}
msg_ext_set_kind("rpc_error");
if (name) {
@@ -6419,6 +6756,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (!object_to_vim(result, rettv, &err)) {
+ assert(ERROR_SET(&err));
semsg(_("Error converting the call result: %s"), err.msg);
}
@@ -6484,7 +6822,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// The last item of argv must be NULL
argv[i] = NULL;
- Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
+ Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
false, true, false, false,
kChannelStdinPipe, NULL, 0, 0, NULL,
@@ -6555,7 +6893,9 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
c = -1;
} else {
- c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col]);
+ char buf[MAX_SCHAR_SIZE + 1];
+ schar_get(buf, grid_getchar(grid, row, col, NULL));
+ c = utf_ptr2char(buf);
}
rettv->vval.v_number = c;
}
@@ -6569,21 +6909,22 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ScreenGrid *grid;
screenchar_adjust(&grid, &row, &col);
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
- tv_list_alloc_ret(rettv, 0);
return;
}
- int pcc[MAX_MCO];
- int c = utfc_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col], pcc);
- int composing_len = 0;
- while (pcc[composing_len] != 0) {
- composing_len++;
- }
- tv_list_alloc_ret(rettv, composing_len + 1);
- tv_list_append_number(rettv->vval.v_list, c);
- for (int i = 0; i < composing_len; i++) {
- tv_list_append_number(rettv->vval.v_list, pcc[i]);
- }
+
+ char buf[MAX_SCHAR_SIZE + 1];
+ schar_get(buf, grid_getchar(grid, row, col, NULL));
+
+ // schar values are already processed chars which are always NUL-terminated.
+ // A single [0] is expected when char is NUL.
+ size_t i = 0;
+ do {
+ int c = utf_ptr2char(buf + i);
+ tv_list_append_number(rettv->vval.v_list, c);
+ i += (size_t)utf_ptr2len(buf + i);
+ } while (buf[i] != NUL);
}
/// "screencol()" function
@@ -6616,7 +6957,9 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
return;
}
- rettv->vval.v_string = xstrdup((char *)grid->chars[grid->line_offset[row] + (size_t)col]);
+ char buf[MAX_SCHAR_SIZE + 1];
+ schar_get(buf, grid_getchar(grid, row, col, NULL));
+ rettv->vval.v_string = xstrdup(buf);
}
/// "search()" function
@@ -6655,8 +6998,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
bool save_p_ws = p_ws;
int flags = 0;
int retval = 0; // default: FAIL
- long lnum_stop = 0;
- long time_limit = 0;
+ linenr_T lnum_stop = 0;
+ int64_t time_limit = 0;
// Get the three pattern arguments: start, middle, end. Will result in an
// error if not a valid argument.
@@ -6698,7 +7041,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
skip = &argvars[4];
if (argvars[5].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[5], NULL);
+ lnum_stop = (linenr_T)tv_get_number_chk(&argvars[5], NULL);
if (lnum_stop < 0) {
semsg(_(e_invarg2), tv_get_string(&argvars[5]));
goto theend;
@@ -6713,8 +7056,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
}
}
- retval = (int)do_searchpair(spat, mpat, epat, dir, skip,
- flags, match_pos, (linenr_T)lnum_stop, time_limit);
+ retval = do_searchpair(spat, mpat, epat, dir, skip,
+ flags, match_pos, lnum_stop, time_limit);
theend:
p_ws = save_p_ws;
@@ -6759,19 +7102,19 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
/// @param time_limit stop after this many msec
///
/// @returns 0 or -1 for no match,
-long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir,
- const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop,
- long time_limit)
+int do_searchpair(const char *spat, const char *mpat, const char *epat, int dir,
+ const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop,
+ int64_t time_limit)
FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
- long retval = 0;
+ int retval = 0;
int nest = 1;
bool use_skip = false;
int options = SEARCH_KEEP;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
// Set the time limit, if there is one.
proftime_T tm = profile_setlimit(time_limit);
@@ -6804,13 +7147,13 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
pos_T foundpos;
clearpos(&foundpos);
char *pat = pat3;
- for (;;) {
+ while (true) {
searchit_arg_T sia = {
.sa_stop_lnum = lnum_stop,
.sa_tm = &tm,
};
- int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
+ int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1,
options, RE_SEARCH, &sia);
if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
// didn't find it or found the first match again: FAIL
@@ -6897,14 +7240,14 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
xfree(pat2);
xfree(pat3);
- if (p_cpo == empty_option) {
+ if (p_cpo == empty_string_option) {
p_cpo = save_cpo;
} else {
// Darn, evaluating the {skip} expression changed the value.
// If it's still empty it was changed and restored, need to restore in
// the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
free_string_option(save_cpo);
}
@@ -7011,7 +7354,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// Set the cursor or mark position.
-/// If 'charpos' is true, then use the column number as a character offset.
+/// If "charpos" is true, then use the column number as a character offset.
/// Otherwise use the column number as a byte offset.
static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
{
@@ -7059,8 +7402,7 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 0) == FAIL) {
return;
}
@@ -7071,8 +7413,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
char *const csearch = tv_dict_get_string(d, "char", false);
if (csearch != NULL) {
- int pcc[MAX_MCO];
- const int c = utfc_ptr2char(csearch, pcc);
+ int c = utf_ptr2char(csearch);
set_last_csearch(c, csearch, utfc_ptr2len(csearch));
}
@@ -7100,6 +7441,13 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char valbuf[NUMBUFLEN];
const char *name = tv_get_string_buf(&argvars[0], namebuf);
+ // setting an environment variable may be dangerous, e.g. you could
+ // setenv GCONV_PATH=/tmp and then have iconv() unexpectedly call
+ // a shell command using some shared library:
+ if (check_secure()) {
+ return;
+ }
+
if (argvars[1].v_type == VAR_SPECIAL
&& argvars[1].vval.v_special == kSpecialVarNull) {
vim_unsetenv_ext(name);
@@ -7146,7 +7494,7 @@ static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// Translate a register type string to the yank type and block length
-static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len)
+static int get_yank_type(char **const pp, MotionType *const yank_type, int *const block_len)
FUNC_ATTR_NONNULL_ALL
{
char *stropt = *pp;
@@ -7164,7 +7512,7 @@ static int get_yank_type(char **const pp, MotionType *const yank_type, long *con
*yank_type = kMTBlockWise;
if (ascii_isdigit(stropt[1])) {
stropt++;
- *block_len = getdigits_long(&stropt, false, 0) - 1;
+ *block_len = getdigits_int(&stropt, false, 0) - 1;
stropt--;
}
break;
@@ -7180,7 +7528,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
bool append = false;
- long block_len = -1;
+ int block_len = -1;
MotionType yank_type = kMTUnknown;
rettv->vval.v_number = 1; // FAIL is default.
@@ -7319,7 +7667,7 @@ free_lstval:
/// "settagstack()" function
static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- static char *e_invact2 = N_("E962: Invalid action: '%s'");
+ static const char *e_invact2 = N_("E962: Invalid action: '%s'");
char action = 'r';
rettv->vval.v_number = -1;
@@ -7331,8 +7679,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
// second argument: dict with items to set in the tag stack
- if (argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 1) == FAIL) {
return;
}
dict_T *d = argvars[1].vval.v_dict;
@@ -7343,8 +7690,10 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// third argument: action - 'a' for append and 'r' for replace.
// default is to replace the stack.
if (argvars[2].v_type == VAR_UNKNOWN) {
- action = 'r';
- } else if (argvars[2].v_type == VAR_STRING) {
+ // action = 'r';
+ } else if (tv_check_for_string_arg(argvars, 2) == FAIL) {
+ return;
+ } else {
const char *actstr;
actstr = tv_get_string_chk(&argvars[2]);
if (actstr == NULL) {
@@ -7357,9 +7706,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
semsg(_(e_invact2), actstr);
return;
}
- } else {
- emsg(_(e_stringreq));
- return;
}
if (set_tagstack(wp, d, action) == OK) {
@@ -7394,11 +7740,11 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = 0;
if (argvars[0].v_type != VAR_UNKNOWN) {
- long col = (long)tv_get_number_chk(argvars, NULL);
+ colnr_T col = (colnr_T)tv_get_number_chk(argvars, NULL);
if (col < 0) {
return; // type error; errmsg already given
}
- rettv->vval.v_number = get_sw_value_col(curbuf, (colnr_T)col);
+ rettv->vval.v_number = get_sw_value_col(curbuf, col);
return;
}
rettv->vval.v_number = get_sw_value(curbuf);
@@ -7526,7 +7872,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -7570,10 +7916,11 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
tv_list_alloc_ret(rettv, 2);
tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len);
tv_list_append_string(rettv->vval.v_list,
- (attr == HLF_SPB ? "bad" :
- attr == HLF_SPR ? "rare" :
- attr == HLF_SPL ? "local" :
- attr == HLF_SPC ? "caps" : NULL), -1);
+ (attr == HLF_SPB
+ ? "bad" : (attr == HLF_SPR
+ ? "rare" : (attr == HLF_SPL
+ ? "local" : (attr == HLF_SPC
+ ? "caps" : NULL)))), -1);
}
/// "spellsuggest()" function
@@ -7583,7 +7930,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -7632,7 +7979,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
const char *str = tv_get_string(&argvars[0]);
const char *pat = NULL;
@@ -7657,7 +8004,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
regmatch_T regmatch = {
- .regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING),
+ .regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING),
.startp = { NULL },
.endp = { NULL },
.rm_ic = false,
@@ -7668,18 +8015,18 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (*str == NUL) {
match = false; // Empty item at the end.
} else {
- match = vim_regexec_nl(&regmatch, (char *)str, col);
+ match = vim_regexec_nl(&regmatch, str, col);
}
const char *end;
if (match) {
- end = (const char *)regmatch.startp[0];
+ end = regmatch.startp[0];
} else {
end = str + strlen(str);
}
if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0
&& *str != NUL
&& match
- && end < (const char *)regmatch.endp[0])) {
+ && end < regmatch.endp[0])) {
tv_list_append_string(rettv->vval.v_list, str, end - str);
}
if (!match) {
@@ -7692,7 +8039,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Don't get stuck at the same match.
col = utfc_ptr2len(regmatch.endp[0]);
}
- str = (const char *)regmatch.endp[0];
+ str = regmatch.endp[0];
}
vim_regfree(regmatch.regprog);
@@ -7750,60 +8097,6 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->v_type = VAR_FLOAT;
}
-/// "str2list()" function
-static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- tv_list_alloc_ret(rettv, kListLenUnknown);
- const char *p = tv_get_string(&argvars[0]);
-
- for (; *p != NUL; p += utf_ptr2len(p)) {
- tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
- }
-}
-
-/// "str2nr()" function
-static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int base = 10;
- int what = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- base = (int)tv_get_number(&argvars[1]);
- if (base != 2 && base != 8 && base != 10 && base != 16) {
- emsg(_(e_invarg));
- return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) {
- what |= STR2NR_QUOTE;
- }
- }
-
- char *p = skipwhite(tv_get_string(&argvars[0]));
- bool isneg = (*p == '-');
- if (*p == '+' || *p == '-') {
- p = skipwhite(p + 1);
- }
- switch (base) {
- case 2:
- what |= STR2NR_BIN | STR2NR_FORCE;
- break;
- case 8:
- what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE;
- break;
- case 16:
- what |= STR2NR_HEX | STR2NR_FORCE;
- break;
- }
- varnumber_T n;
- vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false);
- // Text after the number is silently ignored.
- if (isneg) {
- rettv->vval.v_number = -n;
- } else {
- rettv->vval.v_number = n;
- }
-}
-
/// "strftime({format}[, {time}])" function
static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -7823,263 +8116,35 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// MSVC returns NULL for an invalid value of seconds.
if (curtime_ptr == NULL) {
rettv->vval.v_string = xstrdup(_("(Invalid)"));
- } else {
- vimconv_T conv;
-
- conv.vc_type = CONV_NONE;
- char *enc = enc_locale();
- convert_setup(&conv, p_enc, enc);
- if (conv.vc_type != CONV_NONE) {
- p = string_convert(&conv, p, NULL);
- }
- char result_buf[256];
- if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) {
- 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, result_buf, NULL);
- } else {
- rettv->vval.v_string = 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, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- const char *const str = tv_get_string_chk(&argvars[0]);
- if (str == NULL) {
return;
}
- bool error = false;
- varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
-
- const size_t len = strlen(str);
- size_t byteidx = 0;
-
- while (charidx >= 0 && byteidx < len) {
- if (charidx == 0) {
- rettv->vval.v_number = utf_ptr2char(str + byteidx);
- break;
- }
- charidx--;
- byteidx += (size_t)utf_ptr2len(str + byteidx);
- }
-}
-
-/// "stridx()" function
-static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
- const char *const haystack_start = haystack;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
- &error);
- if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
- return;
- }
- if (start_idx >= 0) {
- haystack += start_idx;
- }
- }
-
- const char *pos = strstr(haystack, needle);
- if (pos != NULL) {
- rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
- }
-}
-
-/// "string()" function
-static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = encode_tv2string(&argvars[0], NULL);
-}
-
-/// "strlen()" function
-static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
-}
-
-static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc)
-{
- const char *s = tv_get_string(&argvars[0]);
- varnumber_T len = 0;
- int (*func_mb_ptr2char_adv)(const char **pp);
-
- func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
- while (*s != NUL) {
- func_mb_ptr2char_adv(&s);
- len++;
- }
- rettv->vval.v_number = len;
-}
-
-/// "strcharlen()" function
-static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- strchar_common(argvars, rettv, true);
-}
-
-/// "strchars()" function
-static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int skipcc = false;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- skipcc = (int)tv_get_bool(&argvars[1]);
- }
- if (skipcc < 0 || skipcc > 1) {
- semsg(_(e_using_number_as_bool_nr), skipcc);
- } else {
- strchar_common(argvars, rettv, skipcc);
- }
-}
-
-/// "strdisplaywidth()" function
-static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
- int col = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- col = (int)tv_get_number(&argvars[1]);
- }
-
- rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col);
-}
-
-/// "strwidth()" function
-static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = (varnumber_T)mb_string2cells(s);
-}
-/// "strcharpart()" function
-static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
+ vimconv_T conv;
- int nbyte = 0;
- bool error = false;
- varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
- if (!error) {
- if (nchar > 0) {
- while (nchar > 0 && (size_t)nbyte < slen) {
- nbyte += utf_ptr2len(p + nbyte);
- nchar--;
- }
- } else {
- nbyte = (int)nchar;
- }
+ conv.vc_type = CONV_NONE;
+ char *enc = enc_locale();
+ convert_setup(&conv, p_enc, enc);
+ if (conv.vc_type != CONV_NONE) {
+ p = string_convert(&conv, p, NULL);
}
- int len = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- int charlen = (int)tv_get_number(&argvars[2]);
- while (charlen > 0 && nbyte + len < (int)slen) {
- int off = nbyte + len;
-
- if (off < 0) {
- len += 1;
- } else {
- len += utf_ptr2len(p + off);
- }
- charlen--;
- }
- } else {
- len = (int)slen - nbyte; // default: all bytes that are available.
+ char result_buf[256];
+ if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) {
+ result_buf[0] = NUL;
}
- // Only return the overlap between the specified part and the actual
- // string.
- if (nbyte < 0) {
- len += nbyte;
- nbyte = 0;
- } else if ((size_t)nbyte > slen) {
- nbyte = (int)slen;
- }
- if (len < 0) {
- len = 0;
- } else if (nbyte + len > (int)slen) {
- len = (int)slen - nbyte;
+ if (conv.vc_type != CONV_NONE) {
+ xfree(p);
}
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrndup(p + nbyte, (size_t)len);
-}
-
-/// "strpart()" function
-static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- bool error = false;
-
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
-
- varnumber_T n = tv_get_number_chk(&argvars[1], &error);
- varnumber_T len;
- if (error) {
- len = 0;
- } else if (argvars[2].v_type != VAR_UNKNOWN) {
- len = tv_get_number(&argvars[2]);
+ convert_setup(&conv, enc, p_enc);
+ if (conv.vc_type != CONV_NONE) {
+ rettv->vval.v_string = string_convert(&conv, result_buf, NULL);
} else {
- len = (varnumber_T)slen - n; // Default len: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (n < 0) {
- len += n;
- n = 0;
- } else if (n > (varnumber_T)slen) {
- n = (varnumber_T)slen;
- }
- if (len < 0) {
- len = 0;
- } else if (n + len > (varnumber_T)slen) {
- len = (varnumber_T)slen - n;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
- int off;
-
- // length in characters
- for (off = (int)n; off < (int)slen && len > 0; len--) {
- off += utfc_ptr2len(p + off);
- }
- len = off - n;
+ rettv->vval.v_string = xstrdup(result_buf);
}
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmemdupz(p + n, (size_t)len);
+ // Release conversion descriptors.
+ convert_setup(&conv, NULL, NULL);
+ xfree(enc);
}
/// "strptime({format}, {timestring})" function
@@ -8114,56 +8179,6 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(enc);
}
-/// "strridx()" function
-static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
-
- rettv->vval.v_number = -1;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- const size_t haystack_len = strlen(haystack);
- ptrdiff_t end_idx;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // Third argument: upper limit for index.
- end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
- if (end_idx < 0) {
- return; // Can never find a match.
- }
- } else {
- end_idx = (ptrdiff_t)haystack_len;
- }
-
- const char *lastmatch = NULL;
- if (*needle == NUL) {
- // Empty string matches past the end.
- lastmatch = haystack + end_idx;
- } else {
- for (const char *rest = haystack; *rest != NUL; rest++) {
- rest = strstr(rest, needle);
- if (rest == NULL || rest > haystack + end_idx) {
- break;
- }
- lastmatch = rest;
- }
- }
-
- if (lastmatch != NULL) {
- rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
- }
-}
-
-/// "strtrans()" function
-static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true);
-}
-
/// "submatch()" function
static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8174,7 +8189,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (no < 0 || no >= NSUBEXP) {
- semsg(_("E935: invalid submatch number: %d"), no);
+ semsg(_(e_invalid_submatch_number_nr), no);
return;
}
int retList = 0;
@@ -8224,11 +8239,18 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
+/// "swapfilelist()" function
+static void f_swapfilelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ recover_names(NULL, false, rettv->vval.v_list, 0, NULL);
+}
+
/// "swapinfo(swap_filename)" function
static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
tv_dict_alloc_ret(rettv);
- get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
+ swapfile_dict(tv_get_string(argvars), rettv->vval.v_dict);
}
/// "swapname(expr)" function
@@ -8348,7 +8370,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char *)(p == NULL ? p : xstrdup(p));
+ rettv->vval.v_string = p == NULL ? NULL : xstrdup(p);
}
/// "synIDtrans(id)" function
@@ -8391,8 +8413,8 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
cchar = syn_get_sub_char();
if (cchar == NUL && curwin->w_p_cole == 1) {
cchar = (curwin->w_p_lcs_chars.conceal == NUL)
- ? ' '
- : curwin->w_p_lcs_chars.conceal;
+ ? ' '
+ : curwin->w_p_lcs_chars.conceal;
}
if (cchar != NUL) {
utf_char2bytes(cchar, str);
@@ -8431,7 +8453,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// f_system - the VimL system() function
+/// f_system - the Vimscript system() function
static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
get_system_output_as_rettv(argvars, rettv, false);
@@ -8512,7 +8534,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (check_secure()) {
return;
}
-
+ if (text_locked()) {
+ text_locked_msg();
+ return;
+ }
if (curbuf->b_changed) {
emsg(_("Can only call this function in an unmodified buffer"));
return;
@@ -8579,7 +8604,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const bool detach = false;
ChannelStdinMode stdin_mode = kChannelStdinPipe;
uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin));
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
+ Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit,
pty, rpc, overlapped, detach, stdin_mode,
cwd, term_width, (uint16_t)curwin->w_height_inner,
env, &rettv->vval.v_number);
@@ -8625,21 +8650,24 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
INTEGER_OBJ(pid), false, false, &err);
api_clear_error(&err);
+ channel_incref(chan);
channel_terminal_open(curbuf, chan);
channel_create_event(chan, NULL);
+ channel_decref(chan);
}
/// "timer_info([timer])" function
static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ if (tv_check_for_opt_number_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
- return;
- }
- tv_list_alloc_ret(rettv, 1);
timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
- if (timer != NULL && !timer->stopped) {
+ if (timer != NULL && (!timer->stopped || timer->refcount > 1)) {
add_timer_info(rettv, timer);
}
} else {
@@ -8654,6 +8682,7 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, EvalFuncData fptr
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) {
@@ -8678,11 +8707,10 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (argvars[2].v_type != VAR_UNKNOWN) {
- dict_T *dict = argvars[2].vval.v_dict;
- if (argvars[2].v_type != VAR_DICT || dict == NULL) {
- semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
+ dict_T *dict = argvars[2].vval.v_dict;
dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
if (di != NULL) {
repeat = (int)tv_get_number(&di->di_tv);
@@ -8702,8 +8730,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "timer_stop(timerid)" function
static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
+ if (tv_check_for_number_arg(argvars, 0) == FAIL) {
return;
}
@@ -8720,186 +8747,6 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fp
timer_stop_all();
}
-/// "tolower(string)" function
-static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false);
-}
-
-/// "toupper(string)" function
-static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true);
-}
-
-/// "tr(string, fromstr, tostr)" function
-static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf[NUMBUFLEN];
- char buf2[NUMBUFLEN];
-
- const char *in_str = tv_get_string(&argvars[0]);
- const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
- const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
-
- // Default return value: empty string.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (fromstr == NULL || tostr == NULL) {
- return; // Type error; errmsg already given.
- }
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
-
- // fromstr and tostr have to contain the same number of chars.
- bool first = true;
- while (*in_str != NUL) {
- const char *cpstr = in_str;
- const int inlen = utfc_ptr2len(in_str);
- int cplen = inlen;
- int idx = 0;
- int fromlen;
- for (const char *p = fromstr; *p != NUL; p += fromlen) {
- fromlen = utfc_ptr2len(p);
- if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) {
- int tolen;
- for (p = tostr; *p != NUL; p += tolen) {
- tolen = utfc_ptr2len(p);
- if (idx-- == 0) {
- cplen = tolen;
- cpstr = (char *)p;
- break;
- }
- }
- if (*p == NUL) { // tostr is shorter than fromstr.
- goto error;
- }
- break;
- }
- idx++;
- }
-
- if (first && cpstr == in_str) {
- // Check that fromstr and tostr have the same number of
- // (multi-byte) characters. Done only once when a character
- // of in_str doesn't appear in fromstr.
- first = false;
- int tolen;
- for (const char *p = tostr; *p != NUL; p += tolen) {
- tolen = utfc_ptr2len(p);
- idx--;
- }
- if (idx != 0) {
- goto error;
- }
- }
-
- ga_grow(&ga, cplen);
- memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
- ga.ga_len += cplen;
-
- in_str += inlen;
- }
-
- // add a terminating NUL
- ga_append(&ga, NUL);
-
- rettv->vval.v_string = ga.ga_data;
- return;
-error:
- semsg(_(e_invarg2), fromstr);
- ga_clear(&ga);
-}
-
-/// "trim({expr})" function
-static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char *head = tv_get_string_buf_chk(&argvars[0], buf1);
- const char *mask = NULL;
- const char *prev;
- const char *p;
- int dir = 0;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (head == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) {
- semsg(_(e_invarg2), tv_get_string(&argvars[1]));
- return;
- }
-
- if (argvars[1].v_type == VAR_STRING) {
- mask = tv_get_string_buf_chk(&argvars[1], buf2);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
- // leading or trailing characters to trim
- dir = (int)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return;
- }
- if (dir < 0 || dir > 2) {
- semsg(_(e_invarg2), tv_get_string(&argvars[2]));
- return;
- }
- }
- }
-
- int c1;
- if (dir == 0 || dir == 1) {
- // Trim leading characters
- while (*head != NUL) {
- c1 = utf_ptr2char((char *)head);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == utf_ptr2char((char *)p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- MB_PTR_ADV(head);
- }
- }
-
- const char *tail = head + strlen(head);
- if (dir == 0 || dir == 2) {
- // Trim trailing characters
- for (; tail > head; tail = prev) {
- prev = tail;
- MB_PTR_BACK(head, prev);
- c1 = utf_ptr2char((char *)prev);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == utf_ptr2char((char *)p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- }
- }
- rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head));
-}
-
/// "type(expr)" function
static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8932,49 +8779,31 @@ static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = n;
}
-/// "undofile(name)" function
-static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// "virtcol({expr}, [, {list} [, {winid}]])" function
+static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- rettv->v_type = VAR_STRING;
- const char *const fname = tv_get_string(&argvars[0]);
-
- if (*fname == NUL) {
- // If there is no file name there will be no undo file.
- rettv->vval.v_string = NULL;
- } else {
- char *ffname = FullName_save(fname, true);
+ colnr_T vcol_start = 0;
+ colnr_T vcol_end = 0;
+ switchwin_T switchwin;
+ bool winchanged = false;
- if (ffname != NULL) {
- rettv->vval.v_string = u_get_undo_file_name(ffname, false);
+ if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
+ // use the window specified in the third argument
+ tabpage_T *tp;
+ win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[2]), &tp);
+ if (wp == NULL || tp == NULL) {
+ goto theend;
}
- xfree(ffname);
- }
-}
-/// "undotree()" function
-static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- dict_T *dict = rettv->vval.v_dict;
-
- tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);
- tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last);
- tv_dict_add_nr(dict, S_LEN("save_last"),
- (varnumber_T)curbuf->b_u_save_nr_last);
- tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur);
- tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);
- tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur);
+ if (switch_win_noblock(&switchwin, wp, tp, true) != OK) {
+ goto theend;
+ }
- tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
-}
+ check_cursor();
+ winchanged = true;
+ }
-/// "virtcol(string)" function
-static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- colnr_T vcol = 0;
int fnum = curbuf->b_fnum;
-
pos_T *fp = var2fpos(&argvars[0], false, &fnum, false);
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
&& fnum == curbuf->b_fnum) {
@@ -8987,11 +8816,23 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
fp->col = (colnr_T)len;
}
}
- getvvcol(curwin, fp, NULL, NULL, &vcol);
- vcol++;
+ getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end);
+ vcol_start++;
+ vcol_end++;
}
- rettv->vval.v_number = vcol;
+theend:
+ if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1])) {
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, vcol_start);
+ tv_list_append_number(rettv->vval.v_list, vcol_end);
+ } else {
+ rettv->vval.v_number = vcol_end;
+ }
+
+ if (winchanged) {
+ restore_win_noblock(&switchwin, true);
+ }
}
/// "visualmode()" function
@@ -9055,6 +8896,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool binary = false;
bool append = false;
+ bool defer = false;
bool do_fsync = !!p_fs;
bool mkdir_p = false;
if (argvars[2].v_type != VAR_UNKNOWN) {
@@ -9068,6 +8910,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
binary = true; break;
case 'a':
append = true; break;
+ case 'D':
+ defer = true; break;
case 's':
do_fsync = true; break;
case 'S':
@@ -9087,6 +8931,11 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (fname == NULL) {
return;
}
+
+ if (defer && !can_add_defer()) {
+ return;
+ }
+
FileDescriptor fp;
int error;
if (*fname == NUL) {
@@ -9095,9 +8944,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
((append ? kFileAppend : kFileTruncate)
| (mkdir_p ? kFileMkDir : kFileCreate)
| kFileCreate), 0666)) != 0) {
- semsg(_("E482: Can't open file %s for writing: %s"),
- fname, os_strerror(error));
+ semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error));
} else {
+ if (defer) {
+ typval_T tv = {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = FullName_save(fname, false),
+ };
+ add_defer("delete", 1, &tv);
+ }
+
bool write_ok;
if (argvars[0].v_type == VAR_BLOB) {
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h
index 1ae031a952..0c345dacb4 100644
--- a/src/nvim/eval/funcs.h
+++ b/src/nvim/eval/funcs.h
@@ -1,23 +1,24 @@
-#ifndef NVIM_EVAL_FUNCS_H
-#define NVIM_EVAL_FUNCS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
-#include "nvim/api/private/dispatch.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h"
-/// Prototype of C function that implements VimL function
+/// Prototype of C function that implements Vimscript function
typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, EvalFuncData data);
/// Special flags for base_arg @see EvalFuncDef
-#define BASE_NONE 0 ///< Not a method (no base argument).
-#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base.
+enum {
+ BASE_NONE = 0, ///< Not a method (no base argument).
+ BASE_LAST = UINT8_MAX, ///< Use the last argument as the method base.
+};
-/// Structure holding VimL function definition
+/// Structure holding Vimscript function definition
typedef struct {
char *name; ///< Name of the function.
uint8_t min_argc; ///< Minimal number of arguments.
@@ -31,4 +32,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/funcs.h.generated.h"
#endif
-#endif // NVIM_EVAL_FUNCS_H
diff --git a/src/nvim/eval/gc.c b/src/nvim/eval/gc.c
index 6a54c4ddc1..bcebd87f71 100644
--- a/src/nvim/eval/gc.c
+++ b/src/nvim/eval/gc.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stddef.h>
#include "nvim/eval/gc.h"
diff --git a/src/nvim/eval/gc.h b/src/nvim/eval/gc.h
index 3185750c3b..36149ec060 100644
--- a/src/nvim/eval/gc.h
+++ b/src/nvim/eval/gc.h
@@ -1,13 +1,10 @@
-#ifndef NVIM_EVAL_GC_H
-#define NVIM_EVAL_GC_H
+#pragma once
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
extern dict_T *gc_first_dict;
extern list_T *gc_first_list;
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "eval/gc.h.generated.h"
+# include "eval/gc.h.generated.h" // IWYU pragma: export
#endif
-#endif // NVIM_EVAL_GC_H
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 05b4737206..069cdced34 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1,16 +1,14 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
-#include "lauxlib.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
@@ -21,33 +19,82 @@
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/mbyte_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/input.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+/// struct storing information about current sort
+typedef struct {
+ int item_compare_ic;
+ bool item_compare_lc;
+ bool item_compare_numeric;
+ bool item_compare_numbers;
+ bool item_compare_float;
+ const char *item_compare_func;
+ partial_T *item_compare_partial;
+ dict_T *item_compare_selfdict;
+ bool item_compare_func_err;
+} sortinfo_T;
+
+/// Structure representing one list item, used for sort array.
+typedef struct {
+ listitem_T *item; ///< Sorted list item.
+ int idx; ///< Sorted list item index.
+} ListSortItem;
+
+typedef int (*ListSorter)(const void *, const void *);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.c.generated.h"
#endif
-static char e_string_required_for_argument_nr[]
+static const char e_variable_nested_too_deep_for_unlock[]
+ = N_("E743: Variable nested too deep for (un)lock");
+static const char e_using_invalid_value_as_string[]
+ = N_("E908: Using an invalid value as a String");
+static const char e_string_required_for_argument_nr[]
= N_("E1174: String required for argument %d");
-static char e_non_empty_string_required_for_argument_nr[]
+static const char e_non_empty_string_required_for_argument_nr[]
= N_("E1175: Non-empty string required for argument %d");
-static char e_number_required_for_argument_nr[]
+static const char e_dict_required_for_argument_nr[]
+ = N_("E1206: Dictionary required for argument %d");
+static const char e_number_required_for_argument_nr[]
= N_("E1210: Number required for argument %d");
-static char e_string_or_list_required_for_argument_nr[]
+static const char e_list_required_for_argument_nr[]
+ = N_("E1211: List required for argument %d");
+static const char e_bool_required_for_argument_nr[]
+ = N_("E1212: Bool required for argument %d");
+static const char e_float_or_number_required_for_argument_nr[]
+ = N_("E1219: Float or Number required for argument %d");
+static const char e_string_or_number_required_for_argument_nr[]
+ = N_("E1220: String or Number required for argument %d");
+static const char e_string_or_list_required_for_argument_nr[]
= N_("E1222: String or List required for argument %d");
+static const char e_list_or_blob_required_for_argument_nr[]
+ = N_("E1226: List or Blob required for argument %d");
+static const char e_blob_required_for_argument_nr[]
+ = N_("E1238: Blob required for argument %d");
+static const char e_invalid_value_for_blob_nr[]
+ = N_("E1239: Invalid value for blob: %d");
+static const char e_string_list_or_blob_required_for_argument_nr[]
+ = N_("E1252: String, List or Blob required for argument %d");
+static const char e_string_or_function_required_for_argument_nr[]
+ = N_("E1256: String or function required for argument %d");
+static const char e_non_null_dict_required_for_argument_nr[]
+ = N_("E1297: Non-NULL Dictionary required for argument %d");
bool tv_in_free_unref_items = false;
@@ -58,70 +105,6 @@ bool tv_in_free_unref_items = false;
const char *const tv_empty_string = "";
//{{{1 Lists
-//{{{2 List log
-#ifdef LOG_LIST_ACTIONS
-ListLog *list_log_first = NULL;
-ListLog *list_log_last = NULL;
-
-/// Write list log to the given file
-///
-/// @param[in] fname File to write log to. Will be appended to if already
-/// present.
-void list_write_log(const char *const fname)
- FUNC_ATTR_NONNULL_ALL
-{
- FileDescriptor fp;
- const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600);
- if (fo_ret != 0) {
- semsg(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret));
- return;
- }
- for (ListLog *chunk = list_log_first; chunk != NULL;) {
- for (size_t i = 0; i < chunk->size; i++) {
- char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2];
- // act : hex " c:" len "[]" "\n\0"
- const ListLogEntry entry = chunk->entries[i];
- const size_t snp_len = (size_t)snprintf(buf, sizeof(buf),
- "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR
- "\n",
- entry.action, entry.l, entry.len, entry.li1,
- entry.li2);
- assert(snp_len + 1 == sizeof(buf));
- const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len);
- if (fw_ret != (ptrdiff_t)snp_len) {
- assert(fw_ret < 0);
- if (i) {
- memmove(chunk->entries, chunk->entries + i,
- sizeof(chunk->entries[0]) * (chunk->size - i));
- chunk->size -= i;
- }
- semsg(_("E5143: Failed to write to file %s: %s"),
- fname, os_strerror((int)fw_ret));
- return;
- }
- }
- list_log_first = chunk->next;
- xfree(chunk);
- chunk = list_log_first;
- }
- const int fc_ret = file_close(&fp, true);
- if (fc_ret != 0) {
- semsg(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret));
- }
-}
-
-# ifdef EXITFREE
-/// Free list log
-void list_free_log(void)
-{
- for (ListLog *chunk = list_log_first; chunk != NULL;) {
- list_log_first = chunk->next;
- xfree(chunk);
- chunk = list_log_first;
- }
-}
-# endif
-#endif
//{{{2 List item
/// Allocate a list item
@@ -210,7 +193,7 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item)
/// Caller should take care of the reference count.
///
/// @param[in] len Expected number of items to be populated before list
-/// becomes accessible from VimL. It is still valid to
+/// becomes accessible from Vimscript. It is still valid to
/// underpopulate a list, value only controls how many elements
/// will be allocated in advance. Currently does nothing.
/// @see ListLenSpecials.
@@ -228,7 +211,6 @@ list_T *tv_list_alloc(const ptrdiff_t len)
list->lv_used_prev = NULL;
list->lv_used_next = gc_first_list;
gc_first_list = list;
- list_log(list, NULL, (void *)(uintptr_t)len, "alloc");
list->lua_table_ref = LUA_NOREF;
return list;
}
@@ -259,8 +241,6 @@ void tv_list_init_static10(staticList10_T *const sl)
li->li_prev = li - 1;
li->li_next = li + 1;
}
- list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1],
- "s10init");
#undef SL_SIZE
}
@@ -272,7 +252,6 @@ void tv_list_init_static(list_T *const l)
{
CLEAR_POINTER(l);
l->lv_refcount = DO_NOT_FREE_CNT;
- list_log(l, NULL, NULL, "sinit");
}
/// Free items contained in a list
@@ -281,7 +260,6 @@ void tv_list_init_static(list_T *const l)
void tv_list_free_contents(list_T *const l)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, NULL, NULL, "freecont");
for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) {
// Remove the item before deleting it.
l->lv_first = item->li_next;
@@ -311,7 +289,6 @@ void tv_list_free_list(list_T *const l)
if (l->lv_used_next != NULL) {
l->lv_used_next->lv_used_prev = l->lv_used_prev;
}
- list_log(l, NULL, NULL, "freelist");
NLUA_CLEAR_REF(l->lua_table_ref);
xfree(l);
@@ -358,7 +335,6 @@ void tv_list_unref(list_T *const l)
void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *const item2)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, item2, "drop");
// Notify watchers.
for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) {
l->lv_len--;
@@ -376,14 +352,12 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *con
item->li_prev->li_next = item2->li_next;
}
l->lv_idx_item = NULL;
- list_log(l, l->lv_first, l->lv_last, "afterdrop");
}
/// Like tv_list_drop_items, but also frees all removed items
void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, item2, "remove");
tv_list_drop_items(l, item, item2);
for (listitem_T *li = item;;) {
tv_clear(TV_LIST_ITEM_TV(li));
@@ -407,7 +381,6 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, listitem_T *con
list_T *const tgt_l, const int cnt)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, item2, "move");
tv_list_drop_items(l, item, item2);
item->li_prev = tgt_l->lv_last;
item2->li_next = NULL;
@@ -418,7 +391,6 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, listitem_T *con
}
tgt_l->lv_last = item2;
tgt_l->lv_len += cnt;
- list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt");
}
/// Insert list item
@@ -446,11 +418,10 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, listitem_T *const ite
}
item->li_prev = ni;
l->lv_len++;
- list_log(l, ni, item, "insert");
}
}
-/// Insert VimL value into a list
+/// Insert Vimscript value into a list
///
/// @param[out] l List to insert to.
/// @param[in,out] tv Value to insert. Is copied (@see tv_copy()) to an
@@ -472,7 +443,6 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, listitem_T *const it
void tv_list_append(list_T *const l, listitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, NULL, "append");
if (l->lv_last == NULL) {
// empty list
l->lv_first = item;
@@ -487,7 +457,7 @@ void tv_list_append(list_T *const l, listitem_T *const item)
item->li_next = NULL;
}
-/// Append VimL value to the end of list
+/// Append Vimscript value to the end of list
///
/// @param[out] l List to append to.
/// @param[in,out] tv Value to append. Is copied (@see tv_copy()) to an
@@ -647,55 +617,166 @@ tv_list_copy_error:
return NULL;
}
-/// Flatten "list" in place to depth "maxdepth".
+/// Get the list item in "l" with index "n1". "n1" is adjusted if needed.
+/// Return NULL if there is no such item.
+listitem_T *tv_list_check_range_index_one(list_T *const l, int *const n1, const bool quiet)
+{
+ listitem_T *li = tv_list_find_index(l, n1);
+ if (li == NULL) {
+ if (!quiet) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n1));
+ }
+ return NULL;
+ }
+ return li;
+}
+
+/// Check that "n2" can be used as the second index in a range of list "l".
+/// If "n1" or "n2" is negative it is changed to the positive index.
+/// "li1" is the item for item "n1".
+/// Return OK or FAIL.
+int tv_list_check_range_index_two(list_T *const l, int *const n1, const listitem_T *const li1,
+ int *const n2, const bool quiet)
+{
+ if (*n2 < 0) {
+ listitem_T *ni = tv_list_find(l, *n2);
+ if (ni == NULL) {
+ if (!quiet) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2));
+ }
+ return FAIL;
+ }
+ *n2 = tv_list_idx_of_item(l, ni);
+ }
+
+ // Check that n2 isn't before n1.
+ if (*n1 < 0) {
+ *n1 = tv_list_idx_of_item(l, li1);
+ }
+ if (*n2 < *n1) {
+ if (!quiet) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2));
+ }
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Assign values from list "src" into a range of "dest".
+/// "idx1_arg" is the index of the first item in "dest" to be replaced.
+/// "idx2" is the index of last item to be replaced, but when "empty_idx2" is
+/// true then replace all items after "idx1".
+/// "op" is the operator, normally "=" but can be "+=" and the like.
+/// "varname" is used for error messages.
+/// Returns OK or FAIL.
+int tv_list_assign_range(list_T *const dest, list_T *const src, const int idx1_arg, const int idx2,
+ const bool empty_idx2, const char *const op, const char *const varname)
+{
+ int idx1 = idx1_arg;
+ listitem_T *const first_li = tv_list_find_index(dest, &idx1);
+ listitem_T *src_li;
+
+ // Check whether any of the list items is locked before making any changes.
+ int idx = idx1;
+ listitem_T *dest_li = first_li;
+ for (src_li = tv_list_first(src); src_li != NULL && dest_li != NULL;) {
+ if (value_check_lock(TV_LIST_ITEM_TV(dest_li)->v_lock, varname, TV_CSTRING)) {
+ return FAIL;
+ }
+ src_li = TV_LIST_ITEM_NEXT(src, src_li);
+ if (src_li == NULL || (!empty_idx2 && idx2 == idx)) {
+ break;
+ }
+ dest_li = TV_LIST_ITEM_NEXT(dest, dest_li);
+ idx++;
+ }
+
+ // Assign the List values to the list items.
+ idx = idx1;
+ dest_li = first_li;
+ for (src_li = tv_list_first(src); src_li != NULL;) {
+ assert(dest_li != NULL);
+ if (op != NULL && *op != '=') {
+ eexe_mod_op(TV_LIST_ITEM_TV(dest_li), TV_LIST_ITEM_TV(src_li), op);
+ } else {
+ tv_clear(TV_LIST_ITEM_TV(dest_li));
+ tv_copy(TV_LIST_ITEM_TV(src_li), TV_LIST_ITEM_TV(dest_li));
+ }
+ src_li = TV_LIST_ITEM_NEXT(src, src_li);
+ if (src_li == NULL || (!empty_idx2 && idx2 == idx)) {
+ break;
+ }
+ if (TV_LIST_ITEM_NEXT(dest, dest_li) == NULL) {
+ // Need to add an empty item.
+ tv_list_append_number(dest, 0);
+ // "dest_li" may have become invalid after append, don’t use it.
+ dest_li = tv_list_last(dest); // Valid again.
+ } else {
+ dest_li = TV_LIST_ITEM_NEXT(dest, dest_li);
+ }
+ idx++;
+ }
+ if (src_li != NULL) {
+ emsg(_("E710: List value has more items than target"));
+ return FAIL;
+ }
+ if (empty_idx2
+ ? (dest_li != NULL && TV_LIST_ITEM_NEXT(dest, dest_li) != NULL)
+ : idx != idx2) {
+ emsg(_("E711: List value has not enough items"));
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Flatten up to "maxitems" in "list", starting at "first" to depth "maxdepth".
+/// When "first" is NULL use the first item.
/// Does nothing if "maxdepth" is 0.
///
/// @param[in,out] list List to flatten
/// @param[in] maxdepth Maximum depth that will be flattened
///
/// @return OK or FAIL
-int tv_list_flatten(list_T *list, long maxdepth)
- FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
+void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t maxdepth)
+ FUNC_ATTR_NONNULL_ARG(1)
{
listitem_T *item;
- listitem_T *to_free;
- int n;
+ int done = 0;
if (maxdepth == 0) {
- return OK;
+ return;
+ }
+
+ if (first == NULL) {
+ item = list->lv_first;
+ } else {
+ item = first;
}
- n = 0;
- item = list->lv_first;
- while (item != NULL) {
+ while (item != NULL && done < maxitems) {
+ listitem_T *next = item->li_next;
+
fast_breakcheck();
if (got_int) {
- return FAIL;
+ return;
}
if (item->li_tv.v_type == VAR_LIST) {
- listitem_T *next = item->li_next;
+ list_T *itemlist = item->li_tv.vval.v_list;
tv_list_drop_items(list, item, item);
- tv_list_extend(list, item->li_tv.vval.v_list, next);
- tv_clear(&item->li_tv);
- to_free = item;
+ tv_list_extend(list, itemlist, next);
- if (item->li_prev == NULL) {
- item = list->lv_first;
- } else {
- item = item->li_prev->li_next;
- }
- xfree(to_free);
-
- if (++n >= maxdepth) {
- n = 0;
- item = next;
+ if (maxdepth > 0) {
+ tv_list_flatten(list,
+ item->li_prev == NULL ? list->lv_first : item->li_prev->li_next,
+ itemlist->lv_len, maxdepth - 1);
}
- } else {
- n = 0;
- item = item->li_next;
+ tv_clear(&item->li_tv);
+ xfree(item);
}
+
+ done++;
+ item = next;
}
- return OK;
}
/// Extend first list with the second
@@ -750,9 +831,67 @@ int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv)
return OK;
}
+static list_T *tv_list_slice(list_T *ol, varnumber_T n1, varnumber_T n2)
+{
+ list_T *l = tv_list_alloc(n2 - n1 + 1);
+ listitem_T *item = tv_list_find(ol, (int)n1);
+ for (; n1 <= n2; n1++) {
+ tv_list_append_tv(l, TV_LIST_ITEM_TV(item));
+ item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item);
+ }
+ return l;
+}
+
+int tv_list_slice_or_index(list_T *list, bool range, varnumber_T n1_arg, varnumber_T n2_arg,
+ bool exclusive, typval_T *rettv, bool verbose)
+{
+ int len = tv_list_len(rettv->vval.v_list);
+ varnumber_T n1 = n1_arg;
+ varnumber_T n2 = n2_arg;
+
+ if (n1 < 0) {
+ n1 = len + n1;
+ }
+ if (n1 < 0 || n1 >= len) {
+ // For a range we allow invalid values and return an empty list.
+ // A list index out of range is an error.
+ if (!range) {
+ if (verbose) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)n1);
+ }
+ return FAIL;
+ }
+ n1 = len;
+ }
+ if (range) {
+ if (n2 < 0) {
+ n2 = len + n2;
+ } else if (n2 >= len) {
+ n2 = len - (exclusive ? 0 : 1);
+ }
+ if (exclusive) {
+ n2--;
+ }
+ if (n2 < 0 || n2 + 1 < n1) {
+ n2 = -1;
+ }
+ list_T *l = tv_list_slice(rettv->vval.v_list, n1, n2);
+ tv_clear(rettv);
+ tv_list_set_ret(rettv, l);
+ } else {
+ // copy the item to "var1" to avoid that freeing the list makes it
+ // invalid.
+ typval_T var1;
+ tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, (int)n1)), &var1);
+ tv_clear(rettv);
+ *rettv = var1;
+ }
+ return OK;
+}
+
typedef struct {
- char_u *s;
- char_u *tofree;
+ char *s;
+ char *tofree;
} Join;
/// Join list into a string, helper function
@@ -785,7 +924,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, const char *con
sumlen += len;
Join *const p = GA_APPEND_VIA_PTR(Join, join_gap);
- p->tofree = p->s = (char_u *)s;
+ p->tofree = p->s = s;
line_breakcheck();
});
@@ -806,7 +945,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, const char *con
const Join *const p = ((const Join *)join_gap->ga_data) + i;
if (p->s != NULL) {
- ga_concat(gap, (char *)p->s);
+ ga_concat(gap, p->s);
}
line_breakcheck();
}
@@ -886,8 +1025,8 @@ void f_list2str(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char buf[MB_MAXBYTES + 1];
TV_LIST_ITER_CONST(l, li, {
- buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), (char *)buf)] = NUL;
- ga_concat(&ga, (char *)buf);
+ buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), buf)] = NUL;
+ ga_concat(&ga, buf);
});
ga_append(&ga, NUL);
@@ -905,14 +1044,14 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
return;
}
- long idx = tv_get_number_chk(&argvars[1], &error);
+ int64_t idx = tv_get_number_chk(&argvars[1], &error);
listitem_T *item;
if (error) {
// Type error: do nothing, errmsg already given.
} else if ((item = tv_list_find(l, (int)idx)) == NULL) {
- semsg(_(e_listidx), (int64_t)idx);
+ semsg(_(e_list_index_out_of_range_nr), idx);
} else {
if (argvars[2].v_type == VAR_UNKNOWN) {
// Remove one item, return its value.
@@ -922,11 +1061,11 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
} else {
listitem_T *item2;
// Remove range of items, return list with values.
- long end = tv_get_number_chk(&argvars[2], &error);
+ int64_t end = tv_get_number_chk(&argvars[2], &error);
if (error) {
// Type error: do nothing.
} else if ((item2 = tv_list_find(l, (int)end)) == NULL) {
- semsg(_(e_listidx), (int64_t)end);
+ semsg(_(e_list_index_out_of_range_nr), end);
} else {
int cnt = 0;
@@ -948,18 +1087,6 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
}
}
-/// struct storing information about current sort
-typedef struct {
- int item_compare_ic;
- bool item_compare_lc;
- bool item_compare_numeric;
- bool item_compare_numbers;
- bool item_compare_float;
- const char *item_compare_func;
- partial_T *item_compare_partial;
- dict_T *item_compare_selfdict;
- bool item_compare_func_err;
-} sortinfo_T;
static sortinfo_T *sortinfo = NULL;
#define ITEM_COMPARE_FAIL 999
@@ -1027,12 +1154,11 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero)
if (sortinfo->item_compare_lc) {
res = strcoll(p1, p2);
} else {
- res = sortinfo->item_compare_ic ? STRICMP(p1, p2): strcmp(p1, p2);
+ res = sortinfo->item_compare_ic ? STRICMP(p1, p2) : strcmp(p1, p2);
}
} else {
- double n1, n2;
- n1 = strtod(p1, &p1);
- n2 = strtod(p2, &p2);
+ double n1 = strtod(p1, &p1);
+ double n2 = strtod(p2, &p2);
res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
}
@@ -1062,8 +1188,6 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2)
static int item_compare2(const void *s1, const void *s2, bool keep_zero)
{
- ListSortItem *si1, *si2;
- int res;
typval_T rettv;
typval_T argv[3];
const char *func_name;
@@ -1074,13 +1198,13 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
return 0;
}
- si1 = (ListSortItem *)s1;
- si2 = (ListSortItem *)s2;
+ ListSortItem *si1 = (ListSortItem *)s1;
+ ListSortItem *si2 = (ListSortItem *)s2;
if (partial == NULL) {
func_name = sortinfo->item_compare_func;
} else {
- func_name = (const char *)partial_name(partial);
+ func_name = partial_name(partial);
}
// Copy the values. This is needed to be able to set v_lock to VAR_FIXED
@@ -1093,7 +1217,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
funcexe.fe_evaluate = true;
funcexe.fe_partial = partial;
funcexe.fe_selfdict = sortinfo->item_compare_selfdict;
- res = call_func(func_name, -1, &rettv, 2, argv, &funcexe);
+ int res = call_func(func_name, -1, &rettv, 2, argv, &funcexe);
tv_clear(&argv[0]);
tv_clear(&argv[1]);
@@ -1135,149 +1259,188 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2)
return item_compare2(s1, s2, false);
}
-/// "sort({list})" function
-static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
+/// sort() List "l"
+static void do_sort(list_T *l, sortinfo_T *info)
{
- ListSortItem *ptrs;
- long len;
- long i;
+ const int len = tv_list_len(l);
- // Pointer to current info struct used in compare function. Save and restore
- // the current one for nested calls.
- sortinfo_T info;
- sortinfo_T *old_sortinfo = sortinfo;
- sortinfo = &info;
+ // Make an array with each entry pointing to an item in the List.
+ ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
+
+ // f_sort(): ptrs will be the list to sort
+ int i = 0;
+ TV_LIST_ITER(l, li, {
+ ptrs[i].item = li;
+ ptrs[i].idx = i;
+ i++;
+ });
- const char *const arg_errmsg = (sort
- ? N_("sort() argument")
- : N_("uniq() argument"));
+ info->item_compare_func_err = false;
+ ListSorter item_compare_func = ((info->item_compare_func == NULL
+ && info->item_compare_partial == NULL)
+ ? item_compare_not_keeping_zero
+ : item_compare2_not_keeping_zero);
- if (argvars[0].v_type != VAR_LIST) {
- semsg(_(e_listarg), sort ? "sort()" : "uniq()");
- } else {
- list_T *const l = argvars[0].vval.v_list;
- if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
- goto theend;
+ // Sort the array with item pointers.
+ qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func);
+ if (!info->item_compare_func_err) {
+ // Clear the list and append the items in the sorted order.
+ l->lv_first = NULL;
+ l->lv_last = NULL;
+ l->lv_idx_item = NULL;
+ l->lv_len = 0;
+ for (i = 0; i < len; i++) {
+ tv_list_append(l, ptrs[i].item);
}
- tv_list_set_ret(rettv, l);
+ }
+ if (info->item_compare_func_err) {
+ emsg(_("E702: Sort compare function failed"));
+ }
- len = tv_list_len(l);
- if (len <= 1) {
- goto theend; // short list sorts pretty quickly
- }
-
- info.item_compare_ic = false;
- info.item_compare_lc = false;
- info.item_compare_numeric = false;
- info.item_compare_numbers = false;
- info.item_compare_float = false;
- info.item_compare_func = NULL;
- info.item_compare_partial = NULL;
- info.item_compare_selfdict = NULL;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- // optional second argument: {func}
- if (argvars[1].v_type == VAR_FUNC) {
- info.item_compare_func = (const char *)argvars[1].vval.v_string;
- } else if (argvars[1].v_type == VAR_PARTIAL) {
- info.item_compare_partial = argvars[1].vval.v_partial;
- } else {
- bool error = false;
+ xfree(ptrs);
+}
- i = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- goto theend; // type error; errmsg already given
- }
- if (i == 1) {
- info.item_compare_ic = true;
- } else if (argvars[1].v_type != VAR_NUMBER) {
- info.item_compare_func = tv_get_string(&argvars[1]);
- } else if (i != 0) {
- emsg(_(e_invarg));
- goto theend;
- }
- if (info.item_compare_func != NULL) {
- if (*info.item_compare_func == NUL) {
- // empty string means default sort
- info.item_compare_func = NULL;
- } else if (strcmp(info.item_compare_func, "n") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numeric = true;
- } else if (strcmp(info.item_compare_func, "N") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numbers = true;
- } else if (strcmp(info.item_compare_func, "f") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_float = true;
- } else if (strcmp(info.item_compare_func, "i") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_ic = true;
- } else if (strcmp(info.item_compare_func, "l") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_lc = true;
- }
- }
- }
+/// uniq() List "l"
+static void do_uniq(list_T *l, sortinfo_T *info)
+{
+ const int len = tv_list_len(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // optional third argument: {dict}
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- goto theend;
- }
- info.item_compare_selfdict = argvars[2].vval.v_dict;
- }
- }
+ // Make an array with each entry pointing to an item in the List.
+ ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
- // Make an array with each entry pointing to an item in the List.
- ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
+ // f_uniq(): ptrs will be a stack of items to remove.
- if (sort) {
- info.item_compare_func_err = false;
- tv_list_item_sort(l, ptrs,
- ((info.item_compare_func == NULL
- && info.item_compare_partial == NULL)
- ? item_compare_not_keeping_zero
- : item_compare2_not_keeping_zero),
- &info.item_compare_func_err);
- if (info.item_compare_func_err) {
- emsg(_("E702: Sort compare function failed"));
- }
+ info->item_compare_func_err = false;
+ ListSorter item_compare_func = ((info->item_compare_func == NULL
+ && info->item_compare_partial == NULL)
+ ? item_compare_keeping_zero
+ : item_compare2_keeping_zero);
+
+ for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)); li != NULL;) {
+ listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
+ if (item_compare_func(&prev_li, &li) == 0) {
+ li = tv_list_item_remove(l, li);
} else {
- ListSorter item_compare_func_ptr;
+ li = TV_LIST_ITEM_NEXT(l, li);
+ }
+ if (info->item_compare_func_err) {
+ emsg(_("E882: Uniq compare function failed"));
+ break;
+ }
+ }
- // f_uniq(): ptrs will be a stack of items to remove.
- info.item_compare_func_err = false;
- if (info.item_compare_func != NULL
- || info.item_compare_partial != NULL) {
- item_compare_func_ptr = item_compare2_keeping_zero;
- } else {
- item_compare_func_ptr = item_compare_keeping_zero;
- }
+ xfree(ptrs);
+}
- for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l))
- ; li != NULL;) {
- listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
- if (item_compare_func_ptr(&prev_li, &li) == 0) {
- li = tv_list_item_remove(l, li);
- } else {
- li = TV_LIST_ITEM_NEXT(l, li);
- }
- if (info.item_compare_func_err) {
- emsg(_("E882: Uniq compare function failed"));
- break;
- }
+/// Parse the optional arguments to sort() and uniq() and return the values in "info".
+static int parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info)
+{
+ info->item_compare_ic = false;
+ info->item_compare_lc = false;
+ info->item_compare_numeric = false;
+ info->item_compare_numbers = false;
+ info->item_compare_float = false;
+ info->item_compare_func = NULL;
+ info->item_compare_partial = NULL;
+ info->item_compare_selfdict = NULL;
+
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ return OK;
+ }
+
+ // optional second argument: {func}
+ if (argvars[1].v_type == VAR_FUNC) {
+ info->item_compare_func = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ info->item_compare_partial = argvars[1].vval.v_partial;
+ } else {
+ bool error = false;
+ int nr = (int)tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return FAIL; // type error; errmsg already given
+ }
+ if (nr == 1) {
+ info->item_compare_ic = true;
+ } else if (argvars[1].v_type != VAR_NUMBER) {
+ info->item_compare_func = tv_get_string(&argvars[1]);
+ } else if (nr != 0) {
+ emsg(_(e_invarg));
+ return FAIL;
+ }
+ if (info->item_compare_func != NULL) {
+ if (*info->item_compare_func == NUL) {
+ // empty string means default sort
+ info->item_compare_func = NULL;
+ } else if (strcmp(info->item_compare_func, "n") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_numeric = true;
+ } else if (strcmp(info->item_compare_func, "N") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_numbers = true;
+ } else if (strcmp(info->item_compare_func, "f") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_float = true;
+ } else if (strcmp(info->item_compare_func, "i") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_ic = true;
+ } else if (strcmp(info->item_compare_func, "l") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_lc = true;
}
}
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // optional third argument: {dict}
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
+ return FAIL;
+ }
+ info->item_compare_selfdict = argvars[2].vval.v_dict;
+ }
+
+ return OK;
+}
+
+/// "sort()" or "uniq()" function
+static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ semsg(_(e_listarg), sort ? "sort()" : "uniq()");
+ return;
+ }
- xfree(ptrs);
+ // Pointer to current info struct used in compare function. Save and restore
+ // the current one for nested calls.
+ sortinfo_T info;
+ sortinfo_T *old_sortinfo = sortinfo;
+ sortinfo = &info;
+
+ const char *const arg_errmsg = (sort ? N_("sort() argument") : N_("uniq() argument"));
+ list_T *const l = argvars[0].vval.v_list;
+ if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
+ goto theend;
+ }
+ tv_list_set_ret(rettv, l);
+
+ const int len = tv_list_len(l);
+ if (len <= 1) {
+ goto theend; // short list sorts pretty quickly
+ }
+ if (parse_sort_uniq_args(argvars, &info) == FAIL) {
+ goto theend;
+ }
+
+ if (sort) {
+ do_sort(l, &info);
+ } else {
+ do_uniq(l, &info);
}
theend:
sortinfo = old_sortinfo;
}
-/// "sort"({list})" function
+/// "sort({list})" function
void f_sort(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
do_sort_uniq(argvars, rettv, true);
@@ -1336,7 +1499,6 @@ void tv_list_reverse(list_T *const l)
if (tv_list_len(l) <= 1) {
return;
}
- list_log(l, NULL, NULL, "reverse");
#define SWAP(a, b) \
do { \
tmp = (a); \
@@ -1354,47 +1516,6 @@ void tv_list_reverse(list_T *const l)
l->lv_idx = l->lv_len - l->lv_idx - 1;
}
-// FIXME Add unit tests for tv_list_item_sort().
-
-/// Sort list using libc qsort
-///
-/// @param[in,out] l List to sort, will be sorted in-place.
-/// @param ptrs Preallocated array of items to sort, must have at least
-/// tv_list_len(l) entries. Should not be initialized.
-/// @param[in] item_compare_func Function used to compare list items.
-/// @param errp Location where information about whether error occurred is
-/// saved by item_compare_func. If boolean there appears to be
-/// true list will not be modified. Must be initialized to false
-/// by the caller.
-void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs,
- const ListSorter item_compare_func, const bool *errp)
- FUNC_ATTR_NONNULL_ARG(3, 4)
-{
- const int len = tv_list_len(l);
- if (len <= 1) {
- return;
- }
- list_log(l, NULL, NULL, "sort");
- int i = 0;
- TV_LIST_ITER(l, li, {
- ptrs[i].item = li;
- ptrs[i].idx = i;
- i++;
- });
- // Sort the array with item pointers.
- qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func);
- if (!(*errp)) {
- // Clear the list and append the items in the sorted order.
- l->lv_first = NULL;
- l->lv_last = NULL;
- l->lv_idx_item = NULL;
- l->lv_len = 0;
- for (i = 0; i < len; i++) {
- tv_list_append(l, ptrs[i].item);
- }
- }
-}
-
//{{{2 Indexing/searching
/// Locate item with a given index in a list and return it
@@ -1463,7 +1584,6 @@ listitem_T *tv_list_find(list_T *const l, int n)
// Cache the used index.
l->lv_idx = idx;
l->lv_idx_item = item;
- list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find");
return item;
}
@@ -1501,19 +1621,34 @@ const char *tv_list_find_str(list_T *const l, const int n)
{
const listitem_T *const li = tv_list_find(l, n);
if (li == NULL) {
- semsg(_(e_listidx), (int64_t)n);
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)n);
return NULL;
}
return tv_get_string(TV_LIST_ITEM_TV(li));
}
+/// Like tv_list_find() but when a negative index is used that is not found use
+/// zero and set "idx" to zero. Used for first index of a range.
+static listitem_T *tv_list_find_index(list_T *const l, int *const idx)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ listitem_T *li = tv_list_find(l, *idx);
+ if (li == NULL) {
+ if (*idx < 0) {
+ *idx = 0;
+ li = tv_list_find(l, *idx);
+ }
+ }
+ return li;
+}
+
/// Locate item in a list and return its index
///
/// @param[in] l List to search.
/// @param[in] item Item to search for.
///
/// @return Index of an item or -1 if item is not in the list.
-long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item)
+int tv_list_idx_of_item(const list_T *const l, const listitem_T *const item)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
if (l == NULL) {
@@ -1673,7 +1808,7 @@ char *callback_to_string(Callback *cb)
}
const size_t msglen = 100;
- char *msg = (char *)xmallocz(msglen);
+ char *msg = xmallocz(msglen);
switch (cb->type) {
case kCallbackFuncref:
@@ -1883,7 +2018,7 @@ void tv_dict_item_free(dictitem_T *const item)
dictitem_T *tv_dict_item_copy(dictitem_T *const di)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- dictitem_T *const new_di = tv_dict_item_alloc((const char *)di->di_key);
+ dictitem_T *const new_di = tv_dict_item_alloc(di->di_key);
tv_copy(&di->di_tv, &new_di->di_tv);
return new_di;
}
@@ -1895,7 +2030,7 @@ dictitem_T *tv_dict_item_copy(dictitem_T *const di)
void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
- hashitem_T *const hi = hash_find(&dict->dv_hashtab, (char *)item->di_key);
+ hashitem_T *const hi = hash_find(&dict->dv_hashtab, item->di_key);
if (HASHITEM_EMPTY(hi)) {
semsg(_(e_intern2), "tv_dict_item_remove()");
} else {
@@ -2086,6 +2221,16 @@ varnumber_T tv_dict_get_number_def(const dict_T *const d, const char *const key,
return tv_get_number(&di->di_tv);
}
+varnumber_T tv_dict_get_bool(const dict_T *const d, const char *const key, const int def)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ dictitem_T *const di = tv_dict_find(d, key, -1);
+ if (di == NULL) {
+ return def;
+ }
+ return tv_get_bool(&di->di_tv);
+}
+
/// Converts a dict to an environment
char **tv_dict_to_env(dict_T *denv)
{
@@ -2100,9 +2245,9 @@ char **tv_dict_to_env(dict_T *denv)
TV_DICT_ITER(denv, var, {
const char *str = tv_get_string(&var->di_tv);
assert(str);
- size_t len = strlen((char *)var->di_key) + strlen(str) + strlen("=") + 1;
+ size_t len = strlen(var->di_key) + strlen(str) + strlen("=") + 1;
env[i] = xmalloc(len);
- snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ snprintf(env[i], len, "%s=%s", var->di_key, str);
i++;
});
@@ -2229,10 +2374,10 @@ int tv_dict_wrong_func_name(dict_T *d, typval_T *tv, const char *name)
int tv_dict_add(dict_T *const d, dictitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
- if (tv_dict_wrong_func_name(d, &item->di_tv, (const char *)item->di_key)) {
+ if (tv_dict_wrong_func_name(d, &item->di_tv, item->di_key)) {
return FAIL;
}
- return hash_add(&d->dv_hashtab, (char *)item->di_key);
+ return hash_add(&d->dv_hashtab, item->di_key);
}
/// Add a list entry to dictionary
@@ -2466,9 +2611,9 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
HASHTAB_ITER(&d2->dv_hashtab, hi2, {
dictitem_T *const di2 = TV_DICT_HI2DI(hi2);
- dictitem_T *const di1 = tv_dict_find(d1, (const char *)di2->di_key, -1);
+ dictitem_T *const di1 = tv_dict_find(d1, di2->di_key, -1);
// Check the key to be valid when adding to any scope.
- if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname((const char *)di2->di_key)) {
+ if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname(di2->di_key)) {
break;
}
if (di1 == NULL) {
@@ -2478,14 +2623,14 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
dictitem_T *const new_di = di2;
if (tv_dict_add(d1, new_di) == OK) {
hash_remove(&d2->dv_hashtab, hi2);
- tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL);
+ tv_dict_watcher_notify(d1, new_di->di_key, &new_di->di_tv, NULL);
}
} else {
dictitem_T *const new_di = tv_dict_item_copy(di2);
if (tv_dict_add(d1, new_di) == FAIL) {
tv_dict_item_free(new_di);
} else if (watched) {
- tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL);
+ tv_dict_watcher_notify(d1, new_di->di_key, &new_di->di_tv, NULL);
}
}
} else if (*action == 'e') {
@@ -2499,7 +2644,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
break;
}
// Disallow replacing a builtin function.
- if (tv_dict_wrong_func_name(d1, &di2->di_tv, (const char *)di2->di_key)) {
+ if (tv_dict_wrong_func_name(d1, &di2->di_tv, di2->di_key)) {
break;
}
@@ -2511,8 +2656,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
tv_copy(&di2->di_tv, &di1->di_tv);
if (watched) {
- tv_dict_watcher_notify(d1, (const char *)di1->di_key, &di1->di_tv,
- &oldtv);
+ tv_dict_watcher_notify(d1, di1->di_key, &di1->di_tv, &oldtv);
tv_clear(&oldtv);
}
}
@@ -2547,7 +2691,7 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool
}
TV_DICT_ITER(d1, di1, {
- dictitem_T *const di2 = tv_dict_find(d2, (const char *)di1->di_key, -1);
+ dictitem_T *const di2 = tv_dict_find(d2, di1->di_key, -1);
if (di2 == NULL) {
return false;
}
@@ -2586,12 +2730,12 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, dict_T *const orig, const bool
}
dictitem_T *new_di;
if (conv == NULL || conv->vc_type == CONV_NONE) {
- new_di = tv_dict_item_alloc((const char *)di->di_key);
+ new_di = tv_dict_item_alloc(di->di_key);
} else {
- size_t len = strlen((char *)di->di_key);
- char *const key = (char *)string_convert(conv, (char *)di->di_key, &len);
+ size_t len = strlen(di->di_key);
+ char *const key = string_convert(conv, di->di_key, &len);
if (key == NULL) {
- new_di = tv_dict_item_alloc_len((const char *)di->di_key, len);
+ new_di = tv_dict_item_alloc_len(di->di_key, len);
} else {
new_di = tv_dict_item_alloc_len(key, len);
xfree(key);
@@ -2705,6 +2849,136 @@ bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2)
return true;
}
+/// Returns a slice of "blob" from index "n1" to "n2" in "rettv". The length of
+/// the blob is "len". Returns an empty blob if the indexes are out of range.
+static int tv_blob_slice(const blob_T *blob, int len, varnumber_T n1, varnumber_T n2,
+ bool exclusive, typval_T *rettv)
+{
+ // The resulting variable is a sub-blob. If the indexes
+ // are out of range the result is empty.
+ if (n1 < 0) {
+ n1 = len + n1;
+ if (n1 < 0) {
+ n1 = 0;
+ }
+ }
+ if (n2 < 0) {
+ n2 = len + n2;
+ } else if (n2 >= len) {
+ n2 = len - (exclusive ? 0 : 1);
+ }
+ if (exclusive) {
+ n2--;
+ }
+ if (n1 >= len || n2 < 0 || n1 > n2) {
+ tv_clear(rettv);
+ rettv->v_type = VAR_BLOB;
+ rettv->vval.v_blob = NULL;
+ } else {
+ blob_T *const new_blob = tv_blob_alloc();
+ ga_grow(&new_blob->bv_ga, (int)(n2 - n1 + 1));
+ new_blob->bv_ga.ga_len = (int)(n2 - n1 + 1);
+ for (int i = (int)n1; i <= (int)n2; i++) {
+ tv_blob_set(new_blob, i - (int)n1, tv_blob_get(rettv->vval.v_blob, i));
+ }
+ tv_clear(rettv);
+ tv_blob_set_ret(rettv, new_blob);
+ }
+
+ return OK;
+}
+
+/// Return the byte value in "blob" at index "idx" in "rettv". If the index is
+/// too big or negative that is an error. The length of the blob is "len".
+static int tv_blob_index(const blob_T *blob, int len, varnumber_T idx, typval_T *rettv)
+{
+ // The resulting variable is a byte value.
+ // If the index is too big or negative that is an error.
+ if (idx < 0) {
+ idx = len + idx;
+ }
+ if (idx < len && idx >= 0) {
+ const int v = (int)tv_blob_get(rettv->vval.v_blob, (int)idx);
+ tv_clear(rettv);
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = v;
+ } else {
+ semsg(_(e_blobidx), idx);
+ return FAIL;
+ }
+
+ return OK;
+}
+
+int tv_blob_slice_or_index(const blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2,
+ bool exclusive, typval_T *rettv)
+{
+ int len = tv_blob_len(rettv->vval.v_blob);
+
+ if (is_range) {
+ return tv_blob_slice(blob, len, n1, n2, exclusive, rettv);
+ } else {
+ return tv_blob_index(blob, len, n1, rettv);
+ }
+}
+
+/// Check if "n1" is a valid index for a blob with length "bloblen".
+int tv_blob_check_index(int bloblen, varnumber_T n1, bool quiet)
+{
+ if (n1 < 0 || n1 > bloblen) {
+ if (!quiet) {
+ semsg(_(e_blobidx), n1);
+ }
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check if "n1"-"n2" is a valid range for a blob with length "bloblen".
+int tv_blob_check_range(int bloblen, varnumber_T n1, varnumber_T n2, bool quiet)
+{
+ if (n2 < 0 || n2 >= bloblen || n2 < n1) {
+ if (!quiet) {
+ semsg(_(e_blobidx), n2);
+ }
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src".
+/// Caller must make sure "src" is a blob.
+/// Returns FAIL if the number of bytes does not match.
+int tv_blob_set_range(blob_T *dest, varnumber_T n1, varnumber_T n2, typval_T *src)
+{
+ if (n2 - n1 + 1 != tv_blob_len(src->vval.v_blob)) {
+ emsg(_("E972: Blob value does not have the right number of bytes"));
+ return FAIL;
+ }
+
+ for (int il = (int)n1, ir = 0; il <= (int)n2; il++) {
+ tv_blob_set(dest, il, tv_blob_get(src->vval.v_blob, ir++));
+ }
+ return OK;
+}
+
+/// Store one byte "byte" in blob "blob" at "idx".
+/// Append one byte if needed.
+void tv_blob_set_append(blob_T *blob, int idx, uint8_t byte)
+{
+ garray_T *gap = &blob->bv_ga;
+
+ // Allow for appending a byte. Setting a byte beyond
+ // the end is an error otherwise.
+ if (idx <= gap->ga_len) {
+ if (idx == gap->ga_len) {
+ ga_grow(gap, 1);
+ gap->ga_len++;
+ }
+ tv_blob_set(blob, idx, byte);
+ }
+}
+
/// "remove({blob})" function
void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
{
@@ -2715,7 +2989,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
}
bool error = false;
- long idx = tv_get_number_chk(&argvars[1], &error);
+ int64_t idx = tv_get_number_chk(&argvars[1], &error);
if (!error) {
const int len = tv_blob_len(b);
@@ -2725,7 +2999,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
idx = len + idx;
}
if (idx < 0 || idx >= len) {
- semsg(_(e_blobidx), (int64_t)idx);
+ semsg(_(e_blobidx), idx);
return;
}
if (argvars[2].v_type == VAR_UNKNOWN) {
@@ -2736,7 +3010,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
b->bv_ga.ga_len--;
} else {
// Remove range of items, return blob with values.
- long end = tv_get_number_chk(&argvars[2], &error);
+ int64_t end = tv_get_number_chk(&argvars[2], &error);
if (error) {
return;
}
@@ -2745,7 +3019,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
end = len + end;
}
if (end >= len || idx > end) {
- semsg(_(e_blobidx), (int64_t)end);
+ semsg(_(e_blobidx), end);
return;
}
blob_T *const blob = tv_blob_alloc();
@@ -2764,6 +3038,51 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
}
}
+/// blob2list() function
+void f_blob2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (tv_check_for_blob_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ blob_T *const blob = argvars->vval.v_blob;
+ list_T *const l = rettv->vval.v_list;
+ for (int i = 0; i < tv_blob_len(blob); i++) {
+ tv_list_append_number(l, tv_blob_get(blob, i));
+ }
+}
+
+/// list2blob() function
+void f_list2blob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_blob_alloc_ret(rettv);
+ blob_T *const blob = rettv->vval.v_blob;
+
+ if (tv_check_for_list_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ list_T *const l = argvars->vval.v_list;
+ if (l == NULL) {
+ return;
+ }
+
+ TV_LIST_ITER_CONST(l, li, {
+ bool error = false;
+ varnumber_T n = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error || n < 0 || n > 255) {
+ if (!error) {
+ semsg(_(e_invalid_value_for_blob_nr), (int)n);
+ }
+ ga_clear(&blob->bv_ga);
+ return;
+ }
+ ga_append(&blob->bv_ga, (uint8_t)n);
+ });
+}
+
//{{{1 Generic typval operations
//{{{2 Init/alloc/clear
//{{{3 Alloc
@@ -2774,7 +3093,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
///
/// @param[out] ret_tv Structure where list is saved.
/// @param[in] len Expected number of items to be populated before list
-/// becomes accessible from VimL. It is still valid to
+/// becomes accessible from Vimscript. It is still valid to
/// underpopulate a list, value only controls how many elements
/// will be allocated in advance. @see ListLenSpecials.
///
@@ -2820,19 +3139,20 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi
emsg(_(e_dictreq));
return;
}
+
+ tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict));
if (tv->vval.v_dict == NULL) {
+ // NULL dict behaves like an empty dict
return;
}
- tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict));
-
TV_DICT_ITER(tv->vval.v_dict, di, {
typval_T tv_item = { .v_lock = VAR_UNLOCKED };
switch (what) {
case kDictListKeys:
tv_item.v_type = VAR_STRING;
- tv_item.vval.v_string = xstrdup((char *)di->di_key);
+ tv_item.vval.v_string = xstrdup(di->di_key);
break;
case kDictListValues:
tv_copy(&di->di_tv, &tv_item);
@@ -2847,7 +3167,7 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi
tv_list_append_owned_tv(sub_l, (typval_T) {
.v_type = VAR_STRING,
.v_lock = VAR_UNLOCKED,
- .vval.v_string = xstrdup((const char *)di->di_key),
+ .vval.v_string = xstrdup(di->di_key),
});
tv_list_append_tv(sub_l, &di->di_tv);
@@ -2881,10 +3201,10 @@ void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "has_key()" function
void f_has_key(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 0) == FAIL) {
return;
}
+
if (argvars[0].vval.v_dict == NULL) {
return;
}
@@ -2936,22 +3256,19 @@ void tv_blob_alloc_ret(typval_T *const ret_tv)
///
/// @param[in] from Blob object to copy from.
/// @param[out] to Blob object to copy to.
-void tv_blob_copy(typval_T *const from, typval_T *const to)
- FUNC_ATTR_NONNULL_ALL
+void tv_blob_copy(blob_T *const from, typval_T *const to)
+ FUNC_ATTR_NONNULL_ARG(2)
{
- assert(from->v_type == VAR_BLOB);
-
to->v_type = VAR_BLOB;
to->v_lock = VAR_UNLOCKED;
- if (from->vval.v_blob == NULL) {
+ if (from == NULL) {
to->vval.v_blob = NULL;
} else {
tv_blob_alloc_ret(to);
- int len = from->vval.v_blob->bv_ga.ga_len;
+ int len = from->bv_ga.ga_len;
if (len > 0) {
- to->vval.v_blob->bv_ga.ga_data
- = xmemdup(from->vval.v_blob->bv_ga.ga_data, (size_t)len);
+ to->vval.v_blob->bv_ga.ga_data = xmemdup(from->bv_ga.ga_data, (size_t)len);
}
to->vval.v_blob->bv_ga.ga_len = len;
to->vval.v_blob->bv_ga.ga_maxlen = len;
@@ -3019,7 +3336,7 @@ static inline int _nothing_conv_func_start(typval_T *const tv, char *const fun)
}
} else {
func_unref(fun);
- if ((const char *)fun != tv_empty_string) {
+ if (fun != tv_empty_string) {
xfree(fun);
}
tv->vval.v_string = NULL;
@@ -3208,55 +3525,59 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic
/// @param[in,out] tv Value to free.
void tv_clear(typval_T *const tv)
{
- if (tv != NULL && tv->v_type != VAR_UNKNOWN) {
- // WARNING: do not translate the string here, gettext is slow and function
- // is used *very* often. At the current state encode_vim_to_nothing() does
- // not error out and does not use the argument anywhere.
- //
- // If situation changes and this argument will be used, translate it in the
- // place where it is used.
- const int evn_ret = encode_vim_to_nothing(NULL, tv, "tv_clear() argument");
- (void)evn_ret;
- assert(evn_ret == OK);
+ if (tv == NULL || tv->v_type == VAR_UNKNOWN) {
+ return;
}
+
+ // WARNING: do not translate the string here, gettext is slow and function
+ // is used *very* often. At the current state encode_vim_to_nothing() does
+ // not error out and does not use the argument anywhere.
+ //
+ // If situation changes and this argument will be used, translate it in the
+ // place where it is used.
+ const int evn_ret = encode_vim_to_nothing(NULL, tv, "tv_clear() argument");
+ (void)evn_ret;
+ assert(evn_ret == OK);
}
//{{{3 Free
-/// Free allocated VimL object and value stored inside
+/// Free allocated Vimscript object and value stored inside
///
/// @param tv Object to free.
void tv_free(typval_T *tv)
{
- if (tv != NULL) {
- switch (tv->v_type) {
- case VAR_PARTIAL:
- partial_unref(tv->vval.v_partial);
- break;
- case VAR_FUNC:
- func_unref(tv->vval.v_string);
- FALLTHROUGH;
- case VAR_STRING:
- xfree(tv->vval.v_string);
- break;
- case VAR_BLOB:
- tv_blob_unref(tv->vval.v_blob);
- break;
- case VAR_LIST:
- tv_list_unref(tv->vval.v_list);
- break;
- case VAR_DICT:
- tv_dict_unref(tv->vval.v_dict);
- break;
- case VAR_BOOL:
- case VAR_SPECIAL:
- case VAR_NUMBER:
- case VAR_FLOAT:
- case VAR_UNKNOWN:
- break;
- }
- xfree(tv);
+ if (tv == NULL) {
+ return;
}
+
+ switch (tv->v_type) {
+ case VAR_PARTIAL:
+ partial_unref(tv->vval.v_partial);
+ break;
+ case VAR_FUNC:
+ func_unref(tv->vval.v_string);
+ FALLTHROUGH;
+ case VAR_STRING:
+ xfree(tv->vval.v_string);
+ break;
+ case VAR_BLOB:
+ tv_blob_unref(tv->vval.v_blob);
+ break;
+ case VAR_LIST:
+ tv_list_unref(tv->vval.v_list);
+ break;
+ case VAR_DICT:
+ tv_dict_unref(tv->vval.v_dict);
+ break;
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ case VAR_NUMBER:
+ case VAR_FLOAT:
+ case VAR_UNKNOWN:
+ break;
+ }
+ xfree(tv);
}
//{{{3 Copy
@@ -3331,7 +3652,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock, const boo
static int recurse = 0;
if (recurse >= DICT_MAXNEST) {
- emsg(_("E743: variable nested too deep for (un)lock"));
+ emsg(_(e_variable_nested_too_deep_for_unlock));
return;
}
if (deep == 0) {
@@ -3399,7 +3720,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock, const boo
recurse--;
}
-/// Check whether VimL value is locked itself or refers to a locked container
+/// Check whether Vimscript value is locked itself or refers to a locked container
///
/// @warning Fixed container is not the same as locked.
///
@@ -3498,7 +3819,7 @@ bool value_check_lock(VarLockStatus lock, const char *name, size_t name_len)
static int tv_equal_recurse_limit;
-/// Compare two VimL values
+/// Compare two Vimscript values
///
/// Like "==", but strings and numbers are different, as well as floats and
/// numbers.
@@ -3639,13 +3960,13 @@ bool tv_check_str_or_nr(const typval_T *const tv)
#define FUNC_ERROR "E703: Using a Funcref as a Number"
static const char *const num_errors[] = {
- [VAR_PARTIAL]= N_(FUNC_ERROR),
- [VAR_FUNC]= N_(FUNC_ERROR),
- [VAR_LIST]= N_("E745: Using a List as a Number"),
- [VAR_DICT]= N_("E728: Using a Dictionary as a Number"),
- [VAR_FLOAT]= N_("E805: Using a Float as a Number"),
- [VAR_BLOB]= N_("E974: Using a Blob as a Number"),
- [VAR_UNKNOWN]= N_("E685: using an invalid value as a Number"),
+ [VAR_PARTIAL] = N_(FUNC_ERROR),
+ [VAR_FUNC] = N_(FUNC_ERROR),
+ [VAR_LIST] = N_("E745: Using a List as a Number"),
+ [VAR_DICT] = N_("E728: Using a Dictionary as a Number"),
+ [VAR_FLOAT] = N_("E805: Using a Float as a Number"),
+ [VAR_BLOB] = N_("E974: Using a Blob as a Number"),
+ [VAR_UNKNOWN] = N_("E685: using an invalid value as a Number"),
};
#undef FUNC_ERROR
@@ -3681,21 +4002,20 @@ bool tv_check_num(const typval_T *const tv)
return false;
}
-#define FUNC_ERROR "E729: using Funcref as a String"
+#define FUNC_ERROR "E729: Using a Funcref as a String"
static const char *const str_errors[] = {
- [VAR_PARTIAL]= N_(FUNC_ERROR),
- [VAR_FUNC]= N_(FUNC_ERROR),
- [VAR_LIST]= N_("E730: using List as a String"),
- [VAR_DICT]= N_("E731: using Dictionary as a String"),
- [VAR_FLOAT]= ((const char *)e_float_as_string),
- [VAR_BLOB]= N_("E976: using Blob as a String"),
- [VAR_UNKNOWN]= N_("E908: using an invalid value as a String"),
+ [VAR_PARTIAL] = N_(FUNC_ERROR),
+ [VAR_FUNC] = N_(FUNC_ERROR),
+ [VAR_LIST] = N_("E730: Using a List as a String"),
+ [VAR_DICT] = N_("E731: Using a Dictionary as a String"),
+ [VAR_BLOB] = N_("E976: Using a Blob as a String"),
+ [VAR_UNKNOWN] = e_using_invalid_value_as_string,
};
#undef FUNC_ERROR
-/// Check that given value is a VimL String or can be "cast" to it.
+/// Check that given value is a Vimscript String or can be "cast" to it.
///
/// Error messages are compatible with tv_get_string_chk() previously used for
/// the same purpose.
@@ -3711,12 +4031,12 @@ bool tv_check_str(const typval_T *const tv)
case VAR_BOOL:
case VAR_SPECIAL:
case VAR_STRING:
+ case VAR_FLOAT:
return true;
case VAR_PARTIAL:
case VAR_FUNC:
case VAR_LIST:
case VAR_DICT:
- case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN:
emsg(_(str_errors[tv->v_type]));
@@ -3728,7 +4048,7 @@ bool tv_check_str(const typval_T *const tv)
//{{{2 Get
-/// Get the number value of a VimL object
+/// Get the number value of a Vimscript object
///
/// @note Use tv_get_number_chk() if you need to determine whether there was an
/// error.
@@ -3744,7 +4064,7 @@ varnumber_T tv_get_number(const typval_T *const tv)
return tv_get_number_chk(tv, &error);
}
-/// Get the number value of a VimL object
+/// Get the number value of a Vimscript object
///
/// @param[in] tv Object to get value from.
/// @param[out] ret_error If type error occurred then `true` will be written
@@ -3773,7 +4093,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
case VAR_STRING: {
varnumber_T n = 0;
if (tv->vval.v_string != NULL) {
- vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, false);
+ vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, false, NULL);
}
return n;
}
@@ -3791,7 +4111,19 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
return (ret_error == NULL ? -1 : 0);
}
-/// Get the line number from VimL object
+varnumber_T tv_get_bool(const typval_T *const tv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return tv_get_number_chk(tv, NULL);
+}
+
+varnumber_T tv_get_bool_chk(const typval_T *const tv, bool *const ret_error)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+{
+ return tv_get_number_chk(tv, ret_error);
+}
+
+/// Get the line number from Vimscript object
///
/// @param[in] tv Object to get value from. Is expected to be a number or
/// a special string like ".", "$", … (works with current buffer
@@ -3814,7 +4146,7 @@ linenr_T tv_get_lnum(const typval_T *const tv)
return lnum;
}
-/// Get the floating-point value of a VimL object
+/// Get the floating-point value of a Vimscript object
///
/// Raises an error if object is not number or floating-point.
///
@@ -3883,6 +4215,14 @@ int tv_check_for_nonempty_string_arg(const typval_T *const args, const int idx)
return OK;
}
+/// Check for an optional string argument at "idx"
+int tv_check_for_opt_string_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return (args[idx].v_type == VAR_UNKNOWN
+ || tv_check_for_string_arg(args, idx) != FAIL) ? OK : FAIL;
+}
+
/// Give an error and return FAIL unless "args[idx]" is a number.
int tv_check_for_number_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
@@ -3902,6 +4242,109 @@ int tv_check_for_opt_number_arg(const typval_T *const args, const int idx)
|| tv_check_for_number_arg(args, idx) != FAIL) ? OK : FAIL;
}
+/// Give an error and return FAIL unless "args[idx]" is a float or a number.
+int tv_check_for_float_or_nr_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_FLOAT && args[idx].v_type != VAR_NUMBER) {
+ semsg(_(e_float_or_number_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a bool.
+int tv_check_for_bool_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_BOOL
+ && !(args[idx].v_type == VAR_NUMBER
+ && (args[idx].vval.v_number == 0
+ || args[idx].vval.v_number == 1))) {
+ semsg(_(e_bool_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check for an optional bool argument at "idx".
+/// Return FAIL if the type is wrong.
+int tv_check_for_opt_bool_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type == VAR_UNKNOWN) {
+ return OK;
+ }
+ return tv_check_for_bool_arg(args, idx);
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a blob.
+int tv_check_for_blob_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_BLOB) {
+ semsg(_(e_blob_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a list.
+int tv_check_for_list_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_LIST) {
+ semsg(_(e_list_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a dict.
+int tv_check_for_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_DICT) {
+ semsg(_(e_dict_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a non-NULL dict.
+int tv_check_for_nonnull_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (tv_check_for_dict_arg(args, idx) == FAIL) {
+ return FAIL;
+ }
+ if (args[idx].vval.v_dict == NULL) {
+ semsg(_(e_non_null_dict_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check for an optional dict argument at "idx"
+int tv_check_for_opt_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return (args[idx].v_type == VAR_UNKNOWN
+ || tv_check_for_dict_arg(args, idx) != FAIL) ? OK : FAIL;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a string or
+/// a number.
+int tv_check_for_string_or_number_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER) {
+ semsg(_(e_string_or_number_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Give an error and return FAIL unless "args[idx]" is a string or a list.
int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
@@ -3913,7 +4356,53 @@ int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx)
return OK;
}
-/// Get the string value of a "stringish" VimL object.
+/// Give an error and return FAIL unless "args[idx]" is a string, a list or a blob.
+int tv_check_for_string_or_list_or_blob_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_STRING
+ && args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_BLOB) {
+ semsg(_(e_string_list_or_blob_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check for an optional string or list argument at "idx"
+int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return (args[idx].v_type == VAR_UNKNOWN
+ || tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a string
+/// or a function reference.
+int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_PARTIAL
+ && args[idx].v_type != VAR_FUNC
+ && args[idx].v_type != VAR_STRING) {
+ semsg(_(e_string_or_function_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a list or a blob.
+int tv_check_for_list_or_blob_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) {
+ semsg(_(e_list_or_blob_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Get the string value of a "stringish" Vimscript object.
///
/// @param[in] tv Object to get value of.
/// @param buf Buffer used to hold numbers and special variables converted to
@@ -3929,11 +4418,14 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
{
switch (tv->v_type) {
case VAR_NUMBER:
- snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number); // -V576
+ snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number);
+ return buf;
+ case VAR_FLOAT:
+ vim_snprintf(buf, NUMBUFLEN, "%g", tv->vval.v_float);
return buf;
case VAR_STRING:
if (tv->vval.v_string != NULL) {
- return (const char *)tv->vval.v_string;
+ return tv->vval.v_string;
}
return "";
case VAR_BOOL:
@@ -3946,7 +4438,6 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
case VAR_FUNC:
case VAR_LIST:
case VAR_DICT:
- case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN:
emsg(_(str_errors[tv->v_type]));
@@ -3956,7 +4447,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
return NULL;
}
-/// Get the string value of a "stringish" VimL object.
+/// Get the string value of a "stringish" Vimscript object.
///
/// @warning For number and special values it uses a single, static buffer. It
/// may be used only once, next call to tv_get_string may reuse it. Use
@@ -3975,7 +4466,7 @@ const char *tv_get_string_chk(const typval_T *const tv)
return tv_get_string_buf_chk(tv, mybuf);
}
-/// Get the string value of a "stringish" VimL object.
+/// Get the string value of a "stringish" Vimscript object.
///
/// @warning For number and special values it uses a single, static buffer. It
/// may be used only once, next call to tv_get_string may reuse it. Use
@@ -3997,7 +4488,7 @@ const char *tv_get_string(const typval_T *const tv)
return tv_get_string_buf((typval_T *)tv, mybuf);
}
-/// Get the string value of a "stringish" VimL object.
+/// Get the string value of a "stringish" Vimscript object.
///
/// @note tv_get_string_chk() and tv_get_string_buf_chk() are similar, but
/// return NULL on error.
@@ -4019,3 +4510,34 @@ const char *tv_get_string_buf(const typval_T *const tv, char *const buf)
return res != NULL ? res : "";
}
+
+/// Return true when "tv" is not falsy: non-zero, non-empty string, non-empty
+/// list, etc. Mostly like what JavaScript does, except that empty list and
+/// empty dictionary are false.
+bool tv2bool(const typval_T *const tv)
+{
+ switch (tv->v_type) {
+ case VAR_NUMBER:
+ return tv->vval.v_number != 0;
+ case VAR_FLOAT:
+ return tv->vval.v_float != 0.0;
+ case VAR_PARTIAL:
+ return tv->vval.v_partial != NULL;
+ case VAR_FUNC:
+ case VAR_STRING:
+ return tv->vval.v_string != NULL && *tv->vval.v_string != NUL;
+ case VAR_LIST:
+ return tv->vval.v_list != NULL && tv->vval.v_list->lv_len > 0;
+ case VAR_DICT:
+ return tv->vval.v_dict != NULL && tv->vval.v_dict->dv_hashtab.ht_used > 0;
+ case VAR_BOOL:
+ return tv->vval.v_bool == kBoolVarTrue;
+ case VAR_SPECIAL:
+ return tv->vval.v_special != kSpecialVarNull;
+ case VAR_BLOB:
+ return tv->vval.v_blob != NULL && tv->vval.v_blob->bv_ga.ga_len > 0;
+ case VAR_UNKNOWN:
+ break;
+ }
+ return false;
+}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 3f59cd3547..58f74a9796 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVAL_TYPVAL_H
-#define NVIM_EVAL_TYPVAL_H
+#pragma once
#include <assert.h>
#include <stdbool.h>
@@ -7,84 +6,16 @@
#include <stdint.h>
#include <string.h>
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: export
#include "nvim/func_attr.h"
-#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte_defs.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-
-#ifdef LOG_LIST_ACTIONS
-# include "nvim/memory.h"
-
-extern ListLog *list_log_first; ///< First list log chunk, NULL if missing
-extern ListLog *list_log_last; ///< Last list log chunk
-
-static inline ListLog *list_log_alloc(const size_t size)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT;
-
-/// Allocate a new log chunk and update globals
-///
-/// @param[in] size Number of entries in a new chunk.
-///
-/// @return [allocated] Newly allocated chunk.
-static inline ListLog *list_log_new(const size_t size)
-{
- ListLog *ret = xmalloc(offsetof(ListLog, entries)
- + size * sizeof(ret->entries[0]));
- ret->size = 0;
- ret->capacity = size;
- ret->next = NULL;
- if (list_log_first == NULL) {
- list_log_first = ret;
- } else {
- list_log_last->next = ret;
- }
- list_log_last = ret;
- return ret;
-}
-
-static inline void list_log(const list_T *const l, const listitem_T *const li1,
- const listitem_T *const li2, const char *const action)
- REAL_FATTR_ALWAYS_INLINE;
-
-/// Add new entry to log
-///
-/// If last chunk was filled it uses twice as much memory to allocate the next
-/// chunk.
-///
-/// @param[in] l List to which entry belongs.
-/// @param[in] li1 List item 1.
-/// @param[in] li2 List item 2, often used for integers and not list items.
-/// @param[in] action Logged action.
-static inline void list_log(const list_T *const l, const listitem_T *const li1,
- const listitem_T *const li2, const char *const action)
-{
- ListLog *tgt;
- if (list_log_first == NULL) {
- tgt = list_log_new(128);
- } else if (list_log_last->size == list_log_last->capacity) {
- tgt = list_log_new(list_log_last->capacity * 2);
- } else {
- tgt = list_log_last;
- }
- tgt->entries[tgt->size++] = (ListLogEntry) {
- .l = (uintptr_t)l,
- .li1 = (uintptr_t)li1,
- .li2 = (uintptr_t)li2,
- .len = (l == NULL ? 0 : l->lv_len),
- .action = action,
- };
-}
-#else
-# define list_log(...)
-# define list_write_log(...)
-# define list_free_log()
-#endif
+#include "nvim/types_defs.h"
// In a hashtab item "hi_key" points to "di_key" in a dictitem.
// This avoids adding a pointer to the hashtab item.
@@ -174,7 +105,6 @@ static inline int tv_list_len(const list_T *l)
/// @param[in] l List to check.
static inline int tv_list_len(const list_T *const l)
{
- list_log(l, NULL, NULL, "len");
if (l == NULL) {
return 0;
}
@@ -258,10 +188,8 @@ static inline listitem_T *tv_list_first(const list_T *l)
static inline listitem_T *tv_list_first(const list_T *const l)
{
if (l == NULL) {
- list_log(l, NULL, NULL, "first");
return NULL;
}
- list_log(l, l->lv_first, NULL, "first");
return l->lv_first;
}
@@ -276,10 +204,8 @@ static inline listitem_T *tv_list_last(const list_T *l)
static inline listitem_T *tv_list_last(const list_T *const l)
{
if (l == NULL) {
- list_log(l, NULL, NULL, "last");
return NULL;
}
- list_log(l, l->lv_last, NULL, "last");
return l->lv_last;
}
@@ -308,7 +234,7 @@ static inline long tv_dict_len(const dict_T *d)
static inline long tv_dict_len(const dict_T *const d)
{
if (d == NULL) {
- return 0L;
+ return 0;
}
return (long)d->dv_hashtab.ht_used;
}
@@ -372,7 +298,7 @@ static inline uint8_t tv_blob_get(const blob_T *const b, int idx)
return ((uint8_t *)b->bv_ga.ga_data)[idx];
}
-static inline void tv_blob_set(blob_T *b, int idx, uint8_t c)
+static inline void tv_blob_set(blob_T *blob, int idx, uint8_t c)
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
/// Store the byte `c` at index `idx` in the blob.
@@ -380,12 +306,12 @@ static inline void tv_blob_set(blob_T *b, int idx, uint8_t c)
/// @param[in] b Blob to index. Cannot be NULL.
/// @param[in] idx Index in a blob. Must be valid.
/// @param[in] c Value to store.
-static inline void tv_blob_set(blob_T *const b, int idx, uint8_t c)
+static inline void tv_blob_set(blob_T *const blob, int idx, uint8_t c)
{
- ((uint8_t *)b->bv_ga.ga_data)[idx] = c;
+ ((uint8_t *)blob->bv_ga.ga_data)[idx] = c;
}
-/// Initialize VimL object
+/// Initialize Vimscript object
///
/// Initializes to unlocked VAR_UNKNOWN object.
///
@@ -413,10 +339,9 @@ extern bool tv_in_free_unref_items;
/// @param[in] l List to iterate over.
/// @param li Name of the variable with current listitem_T entry.
/// @param code Cycle body.
-#define _TV_LIST_ITER_MOD(modifier, l, li, code) \
+#define TV_LIST_ITER_MOD(modifier, l, li, code) \
do { \
modifier list_T *const l_ = (l); \
- list_log(l_, NULL, NULL, "iter" #modifier); \
if (l_ != NULL) { \
for (modifier listitem_T *li = l_->lv_first; \
li != NULL; li = li->li_next) { \
@@ -434,7 +359,7 @@ extern bool tv_in_free_unref_items;
/// @param li Name of the variable with current listitem_T entry.
/// @param code Cycle body.
#define TV_LIST_ITER(l, li, code) \
- _TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens)
+ TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens)
/// Iterate over a list
///
@@ -445,7 +370,7 @@ extern bool tv_in_free_unref_items;
/// @param li Name of the variable with current listitem_T entry.
/// @param code Cycle body.
#define TV_LIST_ITER_CONST(l, li, code) \
- _TV_LIST_ITER_MOD(const, l, li, code)
+ TV_LIST_ITER_MOD(const, l, li, code)
// Below macros are macros to avoid duplicating code for functionally identical
// const and non-const function variants.
@@ -498,7 +423,7 @@ static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f)
///
/// Raises an error if object is not number or floating-point.
///
-/// @param[in] tv VimL object to get value from.
+/// @param[in] tv Vimscript object to get value from.
/// @param[out] ret_f Location where resulting float is stored.
///
/// @return true in case of success, false if tv is not a number or float.
@@ -518,13 +443,15 @@ static inline bool tv_get_float_chk(const typval_T *const tv, float_T *const ret
static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q)
REAL_FATTR_NONNULL_ALL REAL_FATTR_NONNULL_RET REAL_FATTR_PURE
- REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
+ REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE
+ FUNC_ATTR_NO_SANITIZE_ADDRESS;
/// Compute the `DictWatcher` address from a QUEUE node.
///
/// This only exists for .asan-blacklist (ASAN doesn't handle QUEUE_DATA pointer
/// arithmetic).
static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q)
+ FUNC_ATTR_NO_SANITIZE_ADDRESS
{
return QUEUE_DATA(q, DictWatcher, node);
}
@@ -557,16 +484,10 @@ static inline bool tv_is_func(const typval_T tv)
#ifdef UNIT_TESTING
// Do not use enum constants, see commit message.
-EXTERN const size_t kTVCstring INIT(= TV_CSTRING);
-EXTERN const size_t kTVTranslate INIT(= TV_TRANSLATE);
+EXTERN const size_t kTVCstring INIT( = TV_CSTRING);
+EXTERN const size_t kTVTranslate INIT( = TV_TRANSLATE);
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.h.generated.h"
#endif
-
-#define tv_get_bool tv_get_number
-#define tv_get_bool_chk tv_get_number_chk
-#define tv_dict_get_bool tv_dict_get_number_def
-
-#endif // NVIM_EVAL_TYPVAL_H
diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h
index 4615198441..c6bd11ccdb 100644
--- a/src/nvim/eval/typval_defs.h
+++ b/src/nvim/eval/typval_defs.h
@@ -1,16 +1,15 @@
-#ifndef NVIM_EVAL_TYPVAL_DEFS_H
-#define NVIM_EVAL_TYPVAL_DEFS_H
+#pragma once
#include <inttypes.h>
#include <limits.h>
-#include "nvim/garray.h"
-#include "nvim/hashtab.h"
+#include "nvim/garray_defs.h"
+#include "nvim/hashtab_defs.h"
#include "nvim/lib/queue.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-/// Type used for VimL VAR_NUMBER values
+/// Type used for Vimscript VAR_NUMBER values
typedef int64_t varnumber_T;
typedef uint64_t uvarnumber_T;
@@ -100,7 +99,7 @@ typedef enum {
VAR_FIXED = 2, ///< Locked forever.
} VarLockStatus;
-/// VimL variable types, for use in typval_T.v_type
+/// Vimscript variable types, for use in typval_T.v_type
typedef enum {
VAR_UNKNOWN = 0, ///< Unknown (unspecified) value.
VAR_NUMBER, ///< Number, .v_number is used.
@@ -115,6 +114,19 @@ typedef enum {
VAR_BLOB, ///< Blob, .v_blob is used.
} VarType;
+/// Type values for type().
+enum {
+ VAR_TYPE_NUMBER = 0,
+ VAR_TYPE_STRING = 1,
+ VAR_TYPE_FUNC = 2,
+ VAR_TYPE_LIST = 3,
+ VAR_TYPE_DICT = 4,
+ VAR_TYPE_FLOAT = 5,
+ VAR_TYPE_BOOL = 6,
+ VAR_TYPE_SPECIAL = 7,
+ VAR_TYPE_BLOB = 10,
+};
+
/// Structure that holds an internal variable value
typedef struct {
VarType v_type; ///< Variable type.
@@ -206,7 +218,7 @@ typedef struct {
struct { \
typval_T di_tv; /* Structure that holds scope dictionary itself. */ \
uint8_t di_flags; /* Flags. */ \
- char_u di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \
+ char di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \
}
/// Structure to hold a scope dictionary
@@ -273,6 +285,12 @@ typedef struct {
linenr_T sc_lnum; ///< line number
} sctx_T;
+/// Stores an identifier of a script or channel that last set an option.
+typedef struct {
+ sctx_T script_ctx; /// script context where the option was last set
+ uint64_t channel_id; /// Only used when script_id is SID_API_CLIENT.
+} LastSet;
+
/// Maximum number of function arguments
enum { MAX_FUNC_ARGS = 20, };
/// Short variable name length
@@ -284,27 +302,27 @@ enum { FIXVAR_CNT = 12, };
typedef struct funccall_S funccall_T;
struct funccall_S {
- ufunc_T *func; ///< Function being called.
- int linenr; ///< Next line to be executed.
- int returned; ///< ":return" used.
- /// Fixed variables for arguments.
- TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT];
- dict_T l_vars; ///< l: local function variables.
- ScopeDictDictItem l_vars_var; ///< Variable for l: scope.
- dict_T l_avars; ///< a: argument variables.
- ScopeDictDictItem l_avars_var; ///< Variable for a: scope.
- list_T l_varlist; ///< List for a:000.
- listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
- typval_T *rettv; ///< Return value.
- linenr_T breakpoint; ///< Next line with breakpoint or zero.
- int dbg_tick; ///< debug_tick when breakpoint was set.
- int level; ///< Top nesting level of executed function.
- proftime_T prof_child; ///< Time spent in a child.
- funccall_T *caller; ///< Calling function or NULL; or next funccal in
- ///< list pointed to by previous_funccal.
- int fc_refcount; ///< Number of user functions that reference this funccall.
- int fc_copyID; ///< CopyID used for garbage collection.
- garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func".
+ ufunc_T *fc_func; ///< Function being called.
+ int fc_linenr; ///< Next line to be executed.
+ int fc_returned; ///< ":return" used.
+ TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fc_fixvar[FIXVAR_CNT]; ///< Fixed variables for arguments.
+ dict_T fc_l_vars; ///< l: local function variables.
+ ScopeDictDictItem fc_l_vars_var; ///< Variable for l: scope.
+ dict_T fc_l_avars; ///< a: argument variables.
+ ScopeDictDictItem fc_l_avars_var; ///< Variable for a: scope.
+ list_T fc_l_varlist; ///< List for a:000.
+ listitem_T fc_l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
+ typval_T *fc_rettv; ///< Return value.
+ linenr_T fc_breakpoint; ///< Next line with breakpoint or zero.
+ int fc_dbg_tick; ///< "debug_tick" when breakpoint was set.
+ int fc_level; ///< Top nesting level of executed function.
+ garray_T fc_defer; ///< Functions to be called on return.
+ proftime_T fc_prof_child; ///< Time spent in a child.
+ funccall_T *fc_caller; ///< Calling function or NULL; or next funccal in
+ ///< list pointed to by previous_funccal.
+ int fc_refcount; ///< Number of user functions that reference this funccall.
+ int fc_copyID; ///< CopyID used for garbage collection.
+ garray_T fc_ufuncs; ///< List of ufunc_T* which keep a reference to "fc_func".
};
/// Structure to hold info for a user function.
@@ -366,34 +384,3 @@ typedef struct list_stack_S {
list_T *list;
struct list_stack_S *prev;
} list_stack_T;
-
-/// Structure representing one list item, used for sort array.
-typedef struct {
- listitem_T *item; ///< Sorted list item.
- int idx; ///< Sorted list item index.
-} ListSortItem;
-
-typedef int (*ListSorter)(const void *, const void *);
-
-#ifdef LOG_LIST_ACTIONS
-/// List actions log entry
-typedef struct {
- uintptr_t l; ///< List log entry belongs to.
- uintptr_t li1; ///< First list item log entry belongs to, if applicable.
- uintptr_t li2; ///< Second list item log entry belongs to, if applicable.
- int len; ///< List length when log entry was created.
- const char *action; ///< Logged action.
-} ListLogEntry;
-
-typedef struct list_log ListLog;
-
-/// List actions log
-struct list_log {
- ListLog *next; ///< Next chunk or NULL.
- size_t capacity; ///< Number of entries in current chunk.
- size_t size; ///< Current chunk size.
- ListLogEntry entries[]; ///< Actual log entries.
-};
-#endif
-
-#endif // NVIM_EVAL_TYPVAL_DEFS_H
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 6d29286a58..2e0b68d486 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -252,15 +252,13 @@
#include "nvim/func_attr.h"
#include "klib/kvec.h"
-// -V::1063
-
/// Dummy variable used because some macros need lvalue
///
/// Must not be written to, if needed one must check that address of the
/// macros argument is (not) equal to `&TYPVAL_ENCODE_NODICT_VAR`.
const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL;
-static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
+static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
void *const val, int *const val_copyID,
const MPConvStack *const mpstack, const int copyID,
@@ -283,7 +281,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
/// @param[in] objname Object name, used for error reporting.
///
/// @return NOTDONE in case of success, what to return in case of failure.
-static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
+static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, void *const val, int *const val_copyID,
const MPConvStack *const mpstack, const int copyID, const MPConvStackValType conv_type,
const char *const objname)
@@ -296,7 +294,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
return NOTDONE;
}
-static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
+static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv,
typval_T *const tv, const int copyID,
@@ -320,7 +318,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
/// @param[in] objname Object name, used for error reporting.
///
/// @return OK in case of success, FAIL in case of failure.
-static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
+static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack,
MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname)
{
@@ -347,8 +345,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_PARTIAL: {
partial_T *const pt = tv->vval.v_partial;
(void)pt;
- TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt))); // -V547
- _mp_push(*mpstack, ((MPConvStackVal) { // -V779
+ TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt)));
+ kvi_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvPartial,
.tv = tv,
.saved_copyID = copyID - 1,
@@ -367,11 +365,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
break;
}
const int saved_copyID = tv_list_copyid(tv->vval.v_list);
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID,
- kMPConvList);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID,
+ kMPConvList);
TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list));
assert(saved_copyID != copyID);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvList,
.tv = tv,
.saved_copyID = saved_copyID,
@@ -382,7 +380,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
},
},
}));
- TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack));
+ TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, kv_last(*mpstack));
break;
}
case VAR_BOOL:
@@ -396,7 +394,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_SPECIAL:
switch (tv->vval.v_special) {
case kSpecialVarNull:
- TYPVAL_ENCODE_CONV_NIL(tv); // -V1037
+ TYPVAL_ENCODE_CONV_NIL(tv);
break;
}
break;
@@ -509,7 +507,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
if (is_string) {
TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len);
- } else { // -V523
+ } else {
TYPVAL_ENCODE_CONV_STRING(tv, buf, len);
}
xfree(buf);
@@ -520,12 +518,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
goto _convert_one_value_regular_dict;
}
const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list);
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list,
- lv_copyID, copyID,
- kMPConvList);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list,
+ lv_copyID, copyID,
+ kMPConvList);
TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(val_di->di_tv.vval.v_list));
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvList,
.saved_copyID = saved_copyID,
@@ -544,8 +542,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
list_T *const val_list = val_di->di_tv.vval.v_list;
if (val_list == NULL || tv_list_len(val_list) == 0) {
- TYPVAL_ENCODE_CONV_EMPTY_DICT( // -V501
- tv, TYPVAL_ENCODE_NODICT_VAR);
+ TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR);
break;
}
TV_LIST_ITER_CONST(val_list, li, {
@@ -555,12 +552,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
});
const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list);
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID,
- kMPConvPairs);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID,
+ kMPConvPairs);
TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR,
tv_list_len(val_list));
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvPairs,
.saved_copyID = saved_copyID,
@@ -603,12 +600,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
_convert_one_value_regular_dict: {}
const int saved_copyID = tv->vval.v_dict->dv_copyID;
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID,
- kMPConvDict);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID,
+ kMPConvDict);
TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
tv->vval.v_dict->dv_hashtab.ht_used);
assert(saved_copyID != copyID);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvDict,
.saved_copyID = saved_copyID,
@@ -622,20 +619,20 @@ _convert_one_value_regular_dict: {}
},
}));
TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict,
- _mp_last(*mpstack));
+ kv_last(*mpstack));
break;
}
case VAR_UNKNOWN:
- internal_error(STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()");
+ internal_error(STR(TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()");
return FAIL;
}
typval_encode_stop_converting_one_item:
return OK;
// Prevent “unused label†warnings.
- goto typval_encode_stop_converting_one_item; // -V779
+ goto typval_encode_stop_converting_one_item;
}
-TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
+TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
typval_T *const tv, const char *const objname)
REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT;
@@ -649,29 +646,29 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
/// @param[in] objname Object name, used for error reporting.
///
/// @return OK in case of success, FAIL in case of failure.
-TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
+TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const top_tv,
const char *const objname)
{
const int copyID = get_copyID();
MPConvStack mpstack;
- _mp_init(mpstack);
- if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
- NULL,
- top_tv, copyID, objname)
+ kvi_init(mpstack);
+ if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
+ NULL,
+ top_tv, copyID, objname)
== FAIL) {
goto encode_vim_to__error_ret;
}
/// Label common for this and convert_one_value functions, used for escaping
/// from macros like TYPVAL_ENCODE_CONV_DICT_START.
typval_encode_stop_converting_one_item:
- while (_mp_size(mpstack)) {
- MPConvStackVal *cur_mpsv = &_mp_last(mpstack);
+ while (kv_size(mpstack)) {
+ MPConvStackVal *cur_mpsv = &kv_last(mpstack);
typval_T *tv = NULL;
switch (cur_mpsv->type) {
case kMPConvDict: {
if (!cur_mpsv->data.d.todo) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
cur_mpsv->data.d.dict->dv_copyID = cur_mpsv->saved_copyID;
TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, *cur_mpsv->data.d.dictp);
continue;
@@ -695,7 +692,7 @@ typval_encode_stop_converting_one_item:
}
case kMPConvList:
if (cur_mpsv->data.l.li == NULL) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID);
TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv);
continue;
@@ -709,7 +706,7 @@ typval_encode_stop_converting_one_item:
break;
case kMPConvPairs: {
if (cur_mpsv->data.l.li == NULL) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID);
TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR);
continue;
@@ -722,8 +719,8 @@ typval_encode_stop_converting_one_item:
TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(encode_vim_to__error_ret,
*TV_LIST_ITEM_TV(tv_list_first(kv_pair)));
if (
- _TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv,
- TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname)
+ TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv,
+ TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname)
== FAIL) {
goto encode_vim_to__error_ret;
}
@@ -745,7 +742,7 @@ typval_encode_stop_converting_one_item:
cur_mpsv->data.p.stage = kMPConvPartialSelf;
if (pt != NULL && pt->pt_argc > 0) {
TYPVAL_ENCODE_CONV_LIST_START(NULL, pt->pt_argc);
- _mp_push(mpstack, ((MPConvStackVal) {
+ kvi_push(mpstack, ((MPConvStackVal) {
.type = kMPConvPartialList,
.tv = NULL,
.saved_copyID = copyID - 1,
@@ -769,10 +766,10 @@ typval_encode_stop_converting_one_item:
continue;
}
const int saved_copyID = dict->dv_copyID;
- const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME,
- dict, &dict->dv_copyID,
- &mpstack, copyID, kMPConvDict,
- objname);
+ const int te_csr_ret = TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME,
+ dict, &dict->dv_copyID,
+ &mpstack, copyID, kMPConvDict,
+ objname);
if (te_csr_ret != NOTDONE) {
if (te_csr_ret == FAIL) {
goto encode_vim_to__error_ret;
@@ -783,7 +780,7 @@ typval_encode_stop_converting_one_item:
TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict,
dict->dv_hashtab.ht_used);
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
- _mp_push(mpstack, ((MPConvStackVal) {
+ kvi_push(mpstack, ((MPConvStackVal) {
.type = kMPConvDict,
.tv = NULL,
.saved_copyID = saved_copyID,
@@ -797,7 +794,7 @@ typval_encode_stop_converting_one_item:
},
}));
TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict,
- _mp_last(mpstack));
+ kv_last(mpstack));
} else {
TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1);
}
@@ -805,14 +802,14 @@ typval_encode_stop_converting_one_item:
}
case kMPConvPartialEnd:
TYPVAL_ENCODE_CONV_FUNC_END(tv);
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
break;
}
continue;
}
case kMPConvPartialList:
if (!cur_mpsv->data.a.todo) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
TYPVAL_ENCODE_CONV_LIST_END(NULL);
continue;
} else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) {
@@ -823,17 +820,17 @@ typval_encode_stop_converting_one_item:
break;
}
assert(tv != NULL);
- if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
- cur_mpsv, tv, copyID, objname)
+ if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
+ cur_mpsv, tv, copyID, objname)
== FAIL) {
goto encode_vim_to__error_ret;
}
}
- _mp_destroy(mpstack);
+ kvi_destroy(mpstack);
return OK;
encode_vim_to__error_ret:
- _mp_destroy(mpstack);
+ kvi_destroy(mpstack);
return FAIL;
// Prevent “unused label†warnings.
- goto typval_encode_stop_converting_one_item; // -V779
+ goto typval_encode_stop_converting_one_item;
}
diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h
index 2f19144da3..a6e0bd4b2b 100644
--- a/src/nvim/eval/typval_encode.h
+++ b/src/nvim/eval/typval_encode.h
@@ -2,8 +2,7 @@
///
/// Contains common definitions for eval/typval_encode.c.h. Most of time should
/// not be included directly.
-#ifndef NVIM_EVAL_TYPVAL_ENCODE_H
-#define NVIM_EVAL_TYPVAL_ENCODE_H
+#pragma once
#include <assert.h>
#include <inttypes.h>
@@ -11,7 +10,7 @@
#include <string.h>
#include "klib/kvec.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/func_attr.h"
/// Type of the stack entry
@@ -30,7 +29,7 @@ typedef enum {
kMPConvPartialEnd, ///< Already converted everything.
} MPConvPartialStage;
-/// Structure representing current VimL to messagepack conversion state
+/// Structure representing current Vimscript to messagepack conversion state
typedef struct {
MPConvStackValType type; ///< Type of the stack entry.
typval_T *tv; ///< Currently converted typval_T.
@@ -60,17 +59,9 @@ typedef struct {
} data; ///< Data to convert.
} MPConvStackVal;
-/// Stack used to convert VimL values to messagepack.
+/// Stack used to convert Vimscript values to messagepack.
typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack;
-// Defines for MPConvStack
-#define _mp_size kv_size
-#define _mp_init kvi_init
-#define _mp_destroy kvi_destroy
-#define _mp_push kvi_push
-#define _mp_pop kv_pop
-#define _mp_last kv_last
-
static inline size_t tv_strlen(const typval_T *tv)
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT
REAL_FATTR_NONNULL_ALL;
@@ -96,21 +87,21 @@ static inline size_t tv_strlen(const typval_T *const tv)
/// copyID (variable) it is set to copyID.
/// @param[in] copyID CopyID used by the caller.
/// @param conv_type Type of the conversion, @see MPConvStackValType.
-#define _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \
- conv_type) \
+#define TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \
+ conv_type) \
do { \
- const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, \
- (val), &(val)->copyID_attr, mpstack, \
- copyID, conv_type, objname); \
+ const int te_csr_ret = TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, \
+ (val), &(val)->copyID_attr, mpstack, \
+ copyID, conv_type, objname); \
if (te_csr_ret != NOTDONE) { \
return te_csr_ret; \
} \
} while (0)
-#define _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \
+#define TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \
pref##name##suf
-#define _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \
- _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf)
+#define TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \
+ TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf)
/// Construct function name, possibly using macros
///
@@ -122,23 +113,21 @@ static inline size_t tv_strlen(const typval_T *const tv)
/// @param[in] suf Suffix.
///
/// @return Concat: pref + #TYPVAL_ENCODE_NAME + suf.
-#define _TYPVAL_ENCODE_FUNC_NAME(pref, suf) \
- _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf)
+#define TYPVAL_ENCODE_FUNC_NAME(pref, suf) \
+ TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf)
/// Self reference checker function name
-#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE \
- _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference)
+#define TYPVAL_ENCODE_CHECK_SELF_REFERENCE \
+ TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference)
/// Entry point function name
-#define _TYPVAL_ENCODE_ENCODE \
- _TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, )
+#define TYPVAL_ENCODE_ENCODE \
+ TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, )
/// Name of the …convert_one_value function
-#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE \
- _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value)
+#define TYPVAL_ENCODE_CONVERT_ONE_VALUE \
+ TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value)
/// Name of the dummy const dict_T *const variable
#define TYPVAL_ENCODE_NODICT_VAR \
- _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var)
-
-#endif // NVIM_EVAL_TYPVAL_ENCODE_H
+ TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var)
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 22c5b1954d..23b3c4e1b2 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -1,20 +1,17 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// User defined function support
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "lauxlib.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
@@ -26,18 +23,20 @@
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
+#include "nvim/hashtab.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/path.h"
#include "nvim/profile.h"
@@ -45,14 +44,21 @@
#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/userfunc.c.generated.h"
#endif
+/// structure used as item in "fc_defer"
+typedef struct {
+ char *dr_name; ///< function name, allocated
+ typval_T dr_argvars[MAX_FUNC_ARGS + 1];
+ int dr_argcount;
+} defer_T;
+
static hashtab_T func_hashtab;
// Used by get_func_tv()
@@ -65,12 +71,21 @@ static funccall_T *current_funccal = NULL;
// item in it is still being used.
static funccall_T *previous_funccal = NULL;
-static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it");
-static char *e_funcdict = N_("E717: Dictionary entry already exists");
-static char *e_funcref = N_("E718: Funcref required");
-static char *e_nofunc = N_("E130: Unknown function: %s");
-static char e_no_white_space_allowed_before_str_str[]
+static const char *e_unknown_function_str = N_("E117: Unknown function: %s");
+static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it");
+static const char *e_funcdict = N_("E717: Dictionary entry already exists");
+static const char *e_funcref = N_("E718: Funcref required");
+static const char *e_nofunc = N_("E130: Unknown function: %s");
+static const char e_function_list_was_modified[]
+ = N_("E454: Function list was modified");
+static const char e_function_nesting_too_deep[]
+ = N_("E1058: Function nesting too deep");
+static const char e_no_white_space_allowed_before_str_str[]
= N_("E1068: No white space allowed before '%s': %s");
+static const char e_missing_heredoc_end_marker_str[]
+ = N_("E1145: Missing heredoc end marker: %s");
+static const char e_cannot_use_partial_with_dictionary_for_defer[]
+ = N_("E1300: Cannot use a partial with dictionary for :defer");
void func_init(void)
{
@@ -90,7 +105,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int *
bool mustend = false;
char *arg = *argp;
char *p = arg;
- char_u c;
+ uint8_t c;
int i;
if (newargs != NULL) {
@@ -128,7 +143,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int *
}
if (newargs != NULL) {
ga_grow(newargs, 1);
- c = (char_u)(*p);
+ c = (uint8_t)(*p);
*p = NUL;
arg = xstrdup(arg);
@@ -152,14 +167,14 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int *
p = skipwhite(p) + 1;
p = skipwhite(p);
char *expr = p;
- if (eval1(&p, &rettv, false) != FAIL) {
+ if (eval1(&p, &rettv, NULL) != FAIL) {
ga_grow(default_args, 1);
// trim trailing whitespace
while (p > expr && ascii_iswhite(p[-1])) {
p--;
}
- c = (char_u)(*p);
+ c = (uint8_t)(*p);
*p = NUL;
expr = xstrdup(expr);
((char **)(default_args->ga_data))[default_args->ga_len] = expr;
@@ -223,9 +238,9 @@ static void register_closure(ufunc_T *fp)
funccal_unref(fp->uf_scoped, fp, false);
fp->uf_scoped = current_funccal;
current_funccal->fc_refcount++;
- ga_grow(&current_funccal->fc_funcs, 1);
- ((ufunc_T **)current_funccal->fc_funcs.ga_data)
- [current_funccal->fc_funcs.ga_len++] = fp;
+ ga_grow(&current_funccal->fc_ufuncs, 1);
+ ((ufunc_T **)current_funccal->fc_ufuncs.ga_data)
+ [current_funccal->fc_ufuncs.ga_len++] = fp;
}
/// @return a name for a lambda. Returned in static memory.
@@ -252,22 +267,22 @@ static void set_ufunc_name(ufunc_T *fp, char *name)
/// Parse a lambda expression and get a Funcref from "*arg".
///
/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
-int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
+int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
garray_T newargs = GA_EMPTY_INIT_VALUE;
garray_T *pnewargs;
ufunc_T *fp = NULL;
partial_T *pt = NULL;
int varargs;
- int ret;
- char *start = skipwhite(*arg + 1);
- char *s, *e;
bool *old_eval_lavars = eval_lavars_used;
bool eval_lavars = false;
+ char *tofree = NULL;
// First, check if this is a lambda expression. "->" must exists.
- ret = get_function_args(&start, '-', NULL, NULL, NULL, true);
- if (ret == FAIL || *start != '>') {
+ char *s = skipwhite(*arg + 1);
+ int ret = get_function_args(&s, '-', NULL, NULL, NULL, true);
+ if (ret == FAIL || *s != '>') {
return NOTDONE;
}
@@ -290,12 +305,18 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
// Get the start and the end of the expression.
*arg = skipwhite((*arg) + 1);
- s = *arg;
- ret = skip_expr(arg);
+ char *start = *arg;
+ ret = skip_expr(arg, evalarg);
+ char *end = *arg;
if (ret == FAIL) {
goto errret;
}
- e = *arg;
+ if (evalarg != NULL) {
+ // avoid that the expression gets freed when another line break follows
+ tofree = evalarg->eval_tofree;
+ evalarg->eval_tofree = NULL;
+ }
+
*arg = skipwhite(*arg);
if (**arg != '}') {
semsg(_("E451: Expected }: %s"), *arg);
@@ -317,11 +338,11 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
ga_grow(&newlines, 1);
// Add "return " before the expression.
- size_t len = (size_t)(7 + e - s + 1);
+ size_t len = (size_t)(7 + end - start + 1);
p = xmalloc(len);
((char **)(newlines.ga_data))[newlines.ga_len++] = p;
STRCPY(p, "return ");
- xstrlcpy(p + 7, s, (size_t)(e - s) + 1);
+ xstrlcpy(p + 7, start, (size_t)(end - start) + 1);
if (strstr(p + 7, "a:") == NULL) {
// No a: variables are used for sure.
flags |= FC_NOARGS;
@@ -359,12 +380,22 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
}
eval_lavars_used = old_eval_lavars;
+ if (evalarg != NULL && evalarg->eval_tofree == NULL) {
+ evalarg->eval_tofree = tofree;
+ } else {
+ xfree(tofree);
+ }
return OK;
errret:
ga_clear_strings(&newargs);
xfree(fp);
xfree(pt);
+ if (evalarg != NULL && evalarg->eval_tofree == NULL) {
+ evalarg->eval_tofree = tofree;
+ } else {
+ xfree(tofree);
+ }
eval_lavars_used = old_eval_lavars;
return FAIL;
}
@@ -382,9 +413,11 @@ errret:
/// is not needed.
/// @param[in] no_autoload If true, do not source autoload scripts if function
/// was not found.
+/// @param[out] found_var If not NULL and a variable was found set it to true.
///
/// @return name of the function.
-char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload)
+char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload,
+ bool *found_var)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
if (partialp != NULL) {
@@ -392,18 +425,25 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b
}
dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload);
- if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
- if (v->di_tv.vval.v_string == NULL) { // just in case
+ if (v == NULL) {
+ return (char *)name;
+ }
+ typval_T *const tv = &v->di_tv;
+ if (found_var != NULL) {
+ *found_var = true;
+ }
+
+ if (tv->v_type == VAR_FUNC) {
+ if (tv->vval.v_string == NULL) { // just in case
*lenp = 0;
return "";
}
- *lenp = (int)strlen(v->di_tv.vval.v_string);
- return v->di_tv.vval.v_string;
+ *lenp = (int)strlen(tv->vval.v_string);
+ return tv->vval.v_string;
}
- if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) {
- partial_T *const pt = v->di_tv.vval.v_partial;
-
+ if (tv->v_type == VAR_PARTIAL) {
+ partial_T *const pt = tv->vval.v_partial;
if (pt == NULL) { // just in case
*lenp = 0;
return "";
@@ -421,63 +461,82 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b
/// Give an error message with a function name. Handle <SNR> things.
///
-/// @param ermsg must be passed without translation (use N_() instead of _()).
+/// @param errmsg must be passed without translation (use N_() instead of _()).
/// @param name function name
-void emsg_funcname(char *ermsg, const char *name)
+void emsg_funcname(const char *errmsg, const char *name)
{
- char *p;
+ char *p = (char *)name;
- if ((uint8_t)(*name) == K_SPECIAL) {
+ if ((uint8_t)name[0] == K_SPECIAL && name[1] != NUL && name[2] != NUL) {
p = concat_str("<SNR>", name + 3);
- } else {
- p = (char *)name;
}
- semsg(_(ermsg), p);
+ semsg(_(errmsg), p);
if (p != name) {
xfree(p);
}
}
-/// Allocate a variable for the result of a function.
-///
-/// @param name name of the function
-/// @param len length of "name" or -1 to use strlen()
-/// @param arg argument, pointing to the '('
-/// @param funcexe various values
-///
-/// @return OK or FAIL.
-int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe)
+/// Get function arguments at "*arg" and advance it.
+/// Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
+/// On failure FAIL is returned but the "argvars[argcount]" are still set.
+static int get_func_arguments(char **arg, evalarg_T *const evalarg, int partial_argc,
+ typval_T *argvars, int *argcount)
{
- char *argp;
+ char *argp = *arg;
int ret = OK;
- typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
- int argcount = 0; // number of arguments found
// Get the arguments.
- argp = *arg;
- while (argcount < MAX_FUNC_ARGS
- - (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc)) {
+ while (*argcount < MAX_FUNC_ARGS - partial_argc) {
argp = skipwhite(argp + 1); // skip the '(' or ','
+
if (*argp == ')' || *argp == ',' || *argp == NUL) {
break;
}
- if (eval1(&argp, &argvars[argcount], funcexe->fe_evaluate) == FAIL) {
+ if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) {
ret = FAIL;
break;
}
- argcount++;
+ (*argcount)++;
if (*argp != ',') {
break;
}
}
+
+ argp = skipwhite(argp);
if (*argp == ')') {
argp++;
} else {
ret = FAIL;
}
+ *arg = argp;
+ return ret;
+}
+
+/// Call a function and put the result in "rettv".
+///
+/// @param name name of the function
+/// @param len length of "name" or -1 to use strlen()
+/// @param arg argument, pointing to the '('
+/// @param funcexe various values
+///
+/// @return OK or FAIL.
+int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg,
+ funcexe_T *funcexe)
+{
+ typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
+ int argcount = 0; // number of arguments found
+ const bool evaluate = evalarg == NULL ? false : (evalarg->eval_flags & EVAL_EVALUATE);
+
+ char *argp = *arg;
+ int ret = get_func_arguments(&argp, evalarg,
+ (funcexe->fe_partial == NULL
+ ? 0
+ : funcexe->fe_partial->pt_argc),
+ argvars, &argcount);
+ assert(ret == OK || ret == FAIL); // suppress clang false positive
if (ret == OK) {
int i = 0;
@@ -495,7 +554,7 @@ int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_
ret = call_func(name, len, rettv, argcount, argvars, funcexe);
funcargs.ga_len -= i;
- } else if (!aborting()) {
+ } else if (!aborting() && evaluate) {
if (argcount == MAX_FUNC_ARGS) {
emsg_funcname(N_("E740: Too many arguments for function %s"), name);
} else {
@@ -594,24 +653,28 @@ ufunc_T *find_func(const char *name)
/// Copy the function name of "fp" to buffer "buf".
/// "buf" must be able to hold the function name plus three bytes.
/// Takes care of script-local function names.
-static void cat_func_name(char *buf, ufunc_T *fp)
+static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp)
{
- if ((uint8_t)fp->uf_name[0] == K_SPECIAL) {
- STRCPY(buf, "<SNR>");
- STRCAT(buf, fp->uf_name + 3);
+ int len = -1;
+ size_t uflen = strlen(fp->uf_name);
+ assert(uflen > 0);
+
+ if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) {
+ len = snprintf(buf, buflen, "<SNR>%s", fp->uf_name + 3);
} else {
- STRCPY(buf, fp->uf_name);
+ len = snprintf(buf, buflen, "%s", fp->uf_name);
}
+
+ (void)len; // Avoid unused warning on release builds
+ assert(len > 0);
}
/// Add a number variable "name" to dict "dp" with value "nr".
static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
{
-#ifndef __clang_analyzer__
STRCPY(v->di_key, name);
-#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&dp->dv_hashtab, (char *)v->di_key);
+ hash_add(&dp->dv_hashtab, v->di_key);
v->di_tv.v_type = VAR_NUMBER;
v->di_tv.v_lock = VAR_FIXED;
v->di_tv.vval.v_number = nr;
@@ -620,8 +683,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
/// Free "fc"
static void free_funccal(funccall_T *fc)
{
- for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
- ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
+ for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) {
+ ufunc_T *fp = ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i];
// When garbage collecting a funccall_T may be freed before the
// function that references it, clear its uf_scoped field.
@@ -631,9 +694,9 @@ static void free_funccal(funccall_T *fc)
fp->uf_scoped = NULL;
}
}
- ga_clear(&fc->fc_funcs);
+ ga_clear(&fc->fc_ufuncs);
- func_ptr_unref(fc->func);
+ func_ptr_unref(fc->fc_func);
xfree(fc);
}
@@ -643,13 +706,13 @@ static void free_funccal(funccall_T *fc)
static void free_funccal_contents(funccall_T *fc)
{
// Free all l: variables.
- vars_clear(&fc->l_vars.dv_hashtab);
+ vars_clear(&fc->fc_l_vars.dv_hashtab);
// Free all a: variables.
- vars_clear(&fc->l_avars.dv_hashtab);
+ vars_clear(&fc->fc_l_avars.dv_hashtab);
// Free the a:000 variables.
- TV_LIST_ITER(&fc->l_varlist, li, {
+ TV_LIST_ITER(&fc->fc_l_varlist, li, {
tv_clear(TV_LIST_ITEM_TV(li));
});
@@ -663,11 +726,11 @@ static void cleanup_function_call(funccall_T *fc)
bool may_free_fc = fc->fc_refcount <= 0;
bool free_fc = true;
- current_funccal = fc->caller;
+ current_funccal = fc->fc_caller;
// Free all l: variables if not referred.
- if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) {
- vars_clear(&fc->l_vars.dv_hashtab);
+ if (may_free_fc && fc->fc_l_vars.dv_refcount == DO_NOT_FREE_CNT) {
+ vars_clear(&fc->fc_l_vars.dv_hashtab);
} else {
free_fc = false;
}
@@ -675,25 +738,25 @@ static void cleanup_function_call(funccall_T *fc)
// If the a:000 list and the l: and a: dicts are not referenced and
// there is no closure using it, we can free the funccall_T and what's
// in it.
- if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) {
- vars_clear_ext(&fc->l_avars.dv_hashtab, false);
+ if (may_free_fc && fc->fc_l_avars.dv_refcount == DO_NOT_FREE_CNT) {
+ vars_clear_ext(&fc->fc_l_avars.dv_hashtab, false);
} else {
free_fc = false;
// Make a copy of the a: variables, since we didn't do that above.
- TV_DICT_ITER(&fc->l_avars, di, {
+ TV_DICT_ITER(&fc->fc_l_avars, di, {
tv_copy(&di->di_tv, &di->di_tv);
});
}
- if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ if (may_free_fc && fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated)
== DO_NOT_FREE_CNT) {
- fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated)
+ fc->fc_l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated)
} else {
free_fc = false;
// Make a copy of the a:000 items, since we didn't do that above.
- TV_LIST_ITER(&fc->l_varlist, li, {
+ TV_LIST_ITER(&fc->fc_l_varlist, li, {
tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
});
}
@@ -706,7 +769,7 @@ static void cleanup_function_call(funccall_T *fc)
// "fc" is still in use. This can happen when returning "a:000",
// assigning "l:" to a global variable or defining a closure.
// Link "fc" in the list for garbage collection later.
- fc->caller = previous_funccal;
+ fc->fc_caller = previous_funccal;
previous_funccal = fc;
if (want_garbage_collect) {
@@ -729,26 +792,23 @@ static void cleanup_function_call(funccall_T *fc)
/// @param[in] force When true, we are exiting.
static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
{
- funccall_T **pfc;
- int i;
-
if (fc == NULL) {
return;
}
fc->fc_refcount--;
if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) {
- for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
+ for (funccall_T **pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->fc_caller) {
if (fc == *pfc) {
- *pfc = fc->caller;
+ *pfc = fc->fc_caller;
free_funccal_contents(fc);
return;
}
}
}
- for (i = 0; i < fc->fc_funcs.ga_len; i++) {
- if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) {
- ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
+ for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) {
+ if (((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] == fp) {
+ ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] = NULL;
}
}
}
@@ -759,14 +819,13 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
/// @return true if the entry was deleted, false if it wasn't found.
static bool func_remove(ufunc_T *fp)
{
- hashitem_T *hi = hash_find(&func_hashtab, (char *)UF2HIKEY(fp));
-
- if (!HASHITEM_EMPTY(hi)) {
- hash_remove(&func_hashtab, hi);
- return true;
+ hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
+ if (HASHITEM_EMPTY(hi)) {
+ return false;
}
- return false;
+ hash_remove(&func_hashtab, hi);
+ return true;
}
static void func_clear_items(ufunc_T *fp)
@@ -825,6 +884,27 @@ static void func_clear_free(ufunc_T *fp, bool force)
func_free(fp);
}
+/// Allocate a funccall_T, link it in current_funccal and fill in "fp" and "rettv".
+/// Must be followed by one call to remove_funccal() or cleanup_function_call().
+funccall_T *create_funccal(ufunc_T *fp, typval_T *rettv)
+{
+ funccall_T *fc = xcalloc(1, sizeof(funccall_T));
+ fc->fc_caller = current_funccal;
+ current_funccal = fc;
+ fc->fc_func = fp;
+ func_ptr_ref(fp);
+ fc->fc_rettv = rettv;
+ return fc;
+}
+
+/// Restore current_funccal.
+void remove_funccal(void)
+{
+ funccall_T *fc = current_funccal;
+ current_funccal = fc->fc_caller;
+ free_funccal(fc);
+}
+
/// Call a user function
///
/// @param fp Function to call.
@@ -839,11 +919,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
FUNC_ATTR_NONNULL_ARG(1, 3, 4)
{
bool using_sandbox = false;
- funccall_T *fc;
int save_did_emsg;
static int depth = 0;
dictitem_T *v;
- int fixvar_idx = 0; // index in fixvar[]
+ int fixvar_idx = 0; // index in fc_fixvar[]
int ai;
bool islambda = false;
char numbuf[NUMBUFLEN];
@@ -874,40 +953,32 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// check for CTRL-C hit
line_breakcheck();
// prepare the funccall_T structure
- fc = xcalloc(1, sizeof(funccall_T));
- fc->caller = current_funccal;
- current_funccal = fc;
- fc->func = fp;
- fc->rettv = rettv;
- fc->level = ex_nesting_level;
+ funccall_T *fc = create_funccal(fp, rettv);
+ fc->fc_level = ex_nesting_level;
// Check if this function has a breakpoint.
- fc->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, (linenr_T)0);
- fc->dbg_tick = debug_tick;
-
+ fc->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, 0);
+ fc->fc_dbg_tick = debug_tick;
// Set up fields for closure.
- ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
- func_ptr_ref(fp);
+ ga_init(&fc->fc_ufuncs, sizeof(ufunc_T *), 1);
if (strncmp(fp->uf_name, "<lambda>", 8) == 0) {
islambda = true;
}
- // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
+ // Note about using fc->fc_fixvar[]: This is an array of FIXVAR_CNT variables
// with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
// each argument variable and saves a lot of time.
//
// Init l: variables.
- init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
+ init_var_dict(&fc->fc_l_vars, &fc->fc_l_vars_var, VAR_DEF_SCOPE);
if (selfdict != NULL) {
// Set l:self to "selfdict". Use "name" to avoid a warning from
// some compiler that checks the destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
+ v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++];
name = (char *)v->di_key;
STRCPY(name, "self");
-#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key);
v->di_tv.v_type = VAR_DICT;
v->di_tv.v_lock = VAR_UNLOCKED;
v->di_tv.vval.v_dict = selfdict;
@@ -917,38 +988,36 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// Init a: variables, unless none found (in lambda).
// Set a:0 to "argcount" less number of named arguments, if >= 0.
// Set a:000 to a list with room for the "..." arguments.
- init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
+ init_var_dict(&fc->fc_l_avars, &fc->fc_l_avars_var, VAR_SCOPE);
if ((fp->uf_flags & FC_NOARGS) == 0) {
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
+ add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "0",
(varnumber_T)(argcount >= fp->uf_args.ga_len
? argcount - fp->uf_args.ga_len : 0));
}
- fc->l_avars.dv_lock = VAR_FIXED;
+ fc->fc_l_avars.dv_lock = VAR_FIXED;
if ((fp->uf_flags & FC_NOARGS) == 0) {
// Use "name" to avoid a warning from some compiler that checks the
// destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
+ v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++];
name = (char *)v->di_key;
STRCPY(name, "000");
-#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key);
v->di_tv.v_type = VAR_LIST;
v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_list = &fc->l_varlist;
+ v->di_tv.vval.v_list = &fc->fc_l_varlist;
}
- tv_list_init_static(&fc->l_varlist);
- tv_list_set_lock(&fc->l_varlist, VAR_FIXED);
+ tv_list_init_static(&fc->fc_l_varlist);
+ tv_list_set_lock(&fc->fc_l_varlist, VAR_FIXED);
// Set a:firstline to "firstline" and a:lastline to "lastline".
// Set a:name to named arguments.
// Set a:N to the "..." arguments.
// Skipped when no a: variables used (in lambda).
if ((fp->uf_flags & FC_NOARGS) == 0) {
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++],
"firstline", (varnumber_T)firstline);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++],
"lastline", (varnumber_T)lastline);
}
bool default_arg_err = false;
@@ -974,7 +1043,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
default_expr = ((char **)(fp->uf_def_args.ga_data))
[ai + fp->uf_def_args.ga_len];
- if (eval1(&default_expr, &def_rettv, true) == FAIL) {
+ if (eval1(&default_expr, &def_rettv, &EVALARG_EVALUATE) == FAIL) {
default_arg_err = true;
break;
}
@@ -989,7 +1058,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
name = numbuf;
}
if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) {
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+ v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++];
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
} else {
v = xmalloc(sizeof(dictitem_T) + strlen(name));
@@ -1011,17 +1080,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// Named arguments can be accessed without the "a:" prefix in lambda
// expressions. Add to the l: dict.
tv_copy(&v->di_tv, &v->di_tv);
- hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key);
} else {
- hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key);
}
if (ai >= 0 && ai < MAX_FUNC_ARGS) {
- listitem_T *li = &fc->l_listitems[ai];
+ listitem_T *li = &fc->fc_l_listitems[ai];
*TV_LIST_ITEM_TV(li) = argvars[i];
TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED;
- tv_list_append(&fc->l_varlist, li);
+ tv_list_append(&fc->fc_l_varlist, li);
}
}
@@ -1038,7 +1107,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
no_wait_return++;
verbose_enter_scroll();
- smsg(_("calling %s"), SOURCING_NAME);
+ smsg(0, _("calling %s"), SOURCING_NAME);
if (p_verbose >= 14) {
msg_puts("(");
for (int i = 0; i < argcount; i++) {
@@ -1046,7 +1115,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
msg_puts(", ");
}
if (argvars[i].v_type == VAR_NUMBER) {
- msg_outnum((long)argvars[i].vval.v_number);
+ msg_outnum((int)argvars[i].vval.v_number);
} else {
// Do not want errors such as E724 here.
emsg_off++;
@@ -1076,7 +1145,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
bool func_not_yet_profiling_but_should =
do_profiling_yes
- && !fp->uf_profiling && has_profiling(false, (char *)fp->uf_name, NULL);
+ && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);
if (func_not_yet_profiling_but_should) {
started_profiling = true;
@@ -1086,7 +1155,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
bool func_or_func_caller_profiling =
do_profiling_yes
&& (fp->uf_profiling
- || (fc->caller != NULL && fc->caller->func->uf_profiling));
+ || (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling));
if (func_or_func_caller_profiling) {
fp->uf_tm_count++;
@@ -1111,7 +1180,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// A Lambda always has the command "return {expr}". It is much faster
// to evaluate {expr} directly.
ex_nesting_level++;
- (void)eval1(&p, rettv, true);
+ (void)eval1(&p, rettv, &EVALARG_EVALUATE);
ex_nesting_level--;
} else {
// call do_cmdline() to execute the lines
@@ -1119,6 +1188,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
}
+ // Invoke functions added with ":defer".
+ handle_defer_one(current_funccal);
+
RedrawingDisabled--;
// when the function was aborted because of an error, return -1
@@ -1131,15 +1203,15 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
if (func_or_func_caller_profiling) {
call_start = profile_end(call_start);
- call_start = profile_sub_wait(wait_start, call_start); // -V614
+ call_start = profile_sub_wait(wait_start, call_start);
fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start);
fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start,
fp->uf_tm_children);
- if (fc->caller != NULL && fc->caller->func->uf_profiling) {
- fc->caller->func->uf_tm_children =
- profile_add(fc->caller->func->uf_tm_children, call_start);
- fc->caller->func->uf_tml_children =
- profile_add(fc->caller->func->uf_tml_children, call_start);
+ if (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling) {
+ fc->fc_caller->fc_func->uf_tm_children =
+ profile_add(fc->fc_caller->fc_func->uf_tm_children, call_start);
+ fc->fc_caller->fc_func->uf_tml_children =
+ profile_add(fc->fc_caller->fc_func->uf_tml_children, call_start);
}
if (started_profiling) {
// make a ":profdel func" stop profiling the function
@@ -1153,10 +1225,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
verbose_enter_scroll();
if (aborting()) {
- smsg(_("%s aborted"), SOURCING_NAME);
- } else if (fc->rettv->v_type == VAR_NUMBER) {
- smsg(_("%s returning #%" PRId64 ""),
- SOURCING_NAME, (int64_t)fc->rettv->vval.v_number);
+ smsg(0, _("%s aborted"), SOURCING_NAME);
+ } else if (fc->fc_rettv->v_type == VAR_NUMBER) {
+ smsg(0, _("%s returning #%" PRId64 ""),
+ SOURCING_NAME, (int64_t)fc->fc_rettv->vval.v_number);
} else {
char buf[MSG_BUF_LEN];
@@ -1164,7 +1236,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// have some idea how it starts and ends. smsg() would always
// truncate it at the end. Don't want errors such as E724 here.
emsg_off++;
- char *s = encode_tv2string(fc->rettv, NULL);
+ char *s = encode_tv2string(fc->fc_rettv, NULL);
char *tofree = s;
emsg_off--;
if (s != NULL) {
@@ -1172,7 +1244,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN);
s = buf;
}
- smsg(_("%s returning %s"), SOURCING_NAME, s);
+ smsg(0, _("%s returning %s"), SOURCING_NAME, s);
xfree(tofree);
}
}
@@ -1195,7 +1267,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
no_wait_return++;
verbose_enter_scroll();
- smsg(_("continuing in %s"), SOURCING_NAME);
+ smsg(0, _("continuing in %s"), SOURCING_NAME);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
@@ -1231,6 +1303,21 @@ static bool func_name_refcount(const char *name)
return isdigit((uint8_t)(*name)) || *name == '<';
}
+/// Check the argument count for user function "fp".
+/// @return FCERR_UNKNOWN if OK, FCERR_TOOFEW or FCERR_TOOMANY otherwise.
+static int check_user_func_argcount(ufunc_T *fp, int argcount)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const int regular_args = fp->uf_args.ga_len;
+
+ if (argcount < regular_args - fp->uf_def_args.ga_len) {
+ return FCERR_TOOFEW;
+ } else if (!fp->uf_varargs && argcount > regular_args) {
+ return FCERR_TOOMANY;
+ }
+ return FCERR_UNKNOWN;
+}
+
/// Call a user function after checking the arguments.
static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv,
funcexe_T *funcexe, dict_T *selfdict)
@@ -1243,12 +1330,11 @@ static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, ty
if ((fp->uf_flags & FC_RANGE) && funcexe->fe_doesrange != NULL) {
*funcexe->fe_doesrange = true;
}
- int error;
- if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
- error = FCERR_TOOFEW;
- } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
- error = FCERR_TOOMANY;
- } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
+ int error = check_user_func_argcount(fp, argcount);
+ if (error != FCERR_UNKNOWN) {
+ return error;
+ }
+ if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
error = FCERR_DICT;
} else {
// Call the user function.
@@ -1302,8 +1388,8 @@ void free_all_functions(void)
// Clean up the current_funccal chain and the funccal stack.
while (current_funccal != NULL) {
- tv_clear(current_funccal->rettv);
- cleanup_function_call(current_funccal); // -V595
+ tv_clear(current_funccal->fc_rettv);
+ cleanup_function_call(current_funccal);
if (current_funccal == NULL && funccal_stack != NULL) {
restore_funccal();
}
@@ -1435,12 +1521,16 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv
/// Give an error message for the result of a function.
/// Nothing if "error" is FCERR_NONE.
-static void user_func_error(int error, const char *name)
- FUNC_ATTR_NONNULL_ALL
+static void user_func_error(int error, const char *name, funcexe_T *funcexe)
+ FUNC_ATTR_NONNULL_ARG(2)
{
switch (error) {
case FCERR_UNKNOWN:
- emsg_funcname(N_("E117: Unknown function: %s"), name);
+ if (funcexe->fe_found_var) {
+ semsg(_(e_not_callable_type_str), name);
+ } else {
+ emsg_funcname(e_unknown_function_str, name);
+ }
break;
case FCERR_NOTMETHOD:
emsg_funcname(N_("E276: Cannot use function as a method: %s"), name);
@@ -1452,7 +1542,7 @@ static void user_func_error(int error, const char *name)
emsg_funcname(_(e_toomanyarg), name);
break;
case FCERR_TOOFEW:
- emsg_funcname(N_("E119: Not enough arguments for function: %s"), name);
+ emsg_funcname(_(e_toofewarg), name);
break;
case FCERR_SCRIPT:
emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name);
@@ -1511,7 +1601,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
int argv_base = 0;
partial_T *partial = funcexe->fe_partial;
- // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
+ // Initialize rettv so that it is safe for caller to invoke tv_clear(rettv)
// even when call_func() returns FAIL.
rettv->v_type = VAR_UNKNOWN;
@@ -1524,7 +1614,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
if (fp == NULL) {
// Make a copy of the name, if it comes from a funcref variable it could
// be changed or deleted in the called function.
- name = xstrnsave(funcname, (size_t)len);
+ name = xmemdupz(funcname, (size_t)len);
fname = fname_trans_sid(name, fname_buf, &tofree, &error);
}
@@ -1577,7 +1667,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
XFREE_CLEAR(name);
funcname = "v:lua";
}
- } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) {
+ } else if (fp != NULL || !builtin_function(rfname, -1)) {
// User defined function.
if (fp == NULL) {
fp = find_func(rfname);
@@ -1591,8 +1681,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
fp = find_func(rfname);
}
// Try loading a package.
- if (fp == NULL && script_autoload((const char *)rfname, strlen(rfname),
- true) && !aborting()) {
+ if (fp == NULL && script_autoload(rfname, strlen(rfname), true) && !aborting()) {
// Loaded a package, search for the function again.
fp = find_func(rfname);
}
@@ -1636,7 +1725,7 @@ theend:
// Report an error unless the argument evaluation or function call has been
// cancelled due to an aborting error, an interrupt, or an exception.
if (!aborting()) {
- user_func_error(error, (name != NULL) ? name : funcname);
+ user_func_error(error, (name != NULL) ? name : funcname, funcexe);
}
// clear the copies made from the partial
@@ -1655,22 +1744,41 @@ char *printable_func_name(ufunc_T *fp)
return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
}
+/// When "prev_ht_changed" does not equal "ht_changed" give an error and return
+/// true. Otherwise return false.
+static int function_list_modified(const int prev_ht_changed)
+{
+ if (prev_ht_changed != func_hashtab.ht_changed) {
+ emsg(_(e_function_list_was_modified));
+ return true;
+ }
+ return false;
+}
+
/// List the head of the function: "name(arg1, arg2)".
///
/// @param[in] fp Function pointer.
/// @param[in] indent Indent line.
/// @param[in] force Include bang "!" (i.e.: "function!").
-static void list_func_head(ufunc_T *fp, int indent, bool force)
+static int list_func_head(ufunc_T *fp, bool indent, bool force)
{
+ const int prev_ht_changed = func_hashtab.ht_changed;
+
msg_start();
+
+ // a callback at the more prompt may have deleted the function
+ if (function_list_modified(prev_ht_changed)) {
+ return FAIL;
+ }
+
if (indent) {
msg_puts(" ");
}
msg_puts(force ? "function! " : "function ");
if (fp->uf_name_exp != NULL) {
- msg_puts((const char *)fp->uf_name_exp);
+ msg_puts(fp->uf_name_exp);
} else {
- msg_puts((const char *)fp->uf_name);
+ msg_puts(fp->uf_name);
}
msg_putchar('(');
int j;
@@ -1678,7 +1786,7 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
if (j) {
msg_puts(", ");
}
- msg_puts((const char *)FUNCARG(fp, j));
+ msg_puts(FUNCARG(fp, j));
if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
msg_puts(" = ");
msg_puts(((char **)(fp->uf_def_args.ga_data))
@@ -1708,6 +1816,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
if (p_verbose > 0) {
last_set_msg(fp->uf_script_ctx);
}
+
+ return OK;
}
/// Get a function name, translating "<SID>" and "<SNR>".
@@ -1728,21 +1838,17 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
FUNC_ATTR_NONNULL_ARG(1)
{
char *name = NULL;
- const char *start;
- const char *end;
- int lead;
int len;
lval_T lv;
if (fdp != NULL) {
CLEAR_POINTER(fdp);
}
- start = *pp;
+ const char *start = *pp;
// Check for hard coded <SNR>: already translated function ID (from a user
// command).
- if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA
- && (*pp)[2] == KE_SNR) {
+ if ((uint8_t)(*pp)[0] == K_SPECIAL && (uint8_t)(*pp)[1] == KS_EXTRA && (*pp)[2] == KE_SNR) {
*pp += 3;
len = get_id_len((const char **)pp) + 3;
return xmemdupz(start, (size_t)len);
@@ -1750,14 +1856,14 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
// A name starting with "<SID>" or "<SNR>" is local to a script. But
// don't skip over "s:", get_lval() needs it for "s:dict.func".
- lead = eval_fname_script(start);
+ int lead = eval_fname_script(start);
if (lead > 2) {
start += lead;
}
// Note that TFN_ flags use the same values as GLV_ flags.
- end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
- lead > 2 ? 0 : FNE_CHECK_START);
+ const char *end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
+ lead > 2 ? 0 : FNE_CHECK_START);
if (end == start) {
if (!skip) {
emsg(_("E129: Function name required"));
@@ -1773,7 +1879,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
semsg(_(e_invarg2), start);
}
} else {
- *pp = (char *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR);
+ *pp = (char *)find_name_end(start, NULL, NULL, FNE_INCL_BR);
}
goto theend;
}
@@ -1828,14 +1934,13 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
// Check if the name is a Funcref. If so, use the value.
if (lv.ll_exp_name != NULL) {
len = (int)strlen(lv.ll_exp_name);
- name = deref_func_name(lv.ll_exp_name, &len, partial,
- flags & TFN_NO_AUTOLOAD);
- if ((const char *)name == lv.ll_exp_name) {
+ name = deref_func_name(lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD, NULL);
+ if (name == lv.ll_exp_name) {
name = NULL;
}
} else if (!(flags & TFN_NO_DEREF)) {
len = (int)(end - *pp);
- name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD);
+ name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD, NULL);
if (name == *pp) {
name = NULL;
}
@@ -1883,8 +1988,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
lead = 0; // do nothing
} else if (lead > 0) {
lead = 3;
- if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
- || eval_fname_sid((const char *)(*pp))) {
+ if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) {
// It's "s:" or "<SID>".
if (current_sctx.sc_sid <= 0) {
emsg(_(e_usingsid));
@@ -1971,7 +2075,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
if (strncmp(p, "<lambda>", 8) == 0) {
p += 8;
(void)getdigits(&p, false, 0);
- saved = xstrndup(*name, (size_t)(p - *name));
+ saved = xmemdupz(*name, (size_t)(p - *name));
if (fudi != NULL) {
CLEAR_POINTER(fudi);
}
@@ -1990,7 +2094,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
/// Otherwise functions matching "regmatch".
static void list_functions(regmatch_T *regmatch)
{
- const int changed = func_hashtab.ht_changed;
+ const int prev_ht_changed = func_hashtab.ht_changed;
size_t todo = func_hashtab.ht_used;
const hashitem_T *const ht_array = func_hashtab.ht_array;
@@ -1998,15 +2102,15 @@ static void list_functions(regmatch_T *regmatch)
if (!HASHITEM_EMPTY(hi)) {
ufunc_T *fp = HI2UF(hi);
todo--;
- if ((fp->uf_flags & FC_DEAD) == 0
- && (regmatch == NULL
- ? (!message_filtered((char *)fp->uf_name)
- && !func_name_refcount(fp->uf_name))
- : (!isdigit((uint8_t)(*fp->uf_name))
- && vim_regexec(regmatch, (char *)fp->uf_name, 0)))) {
- list_func_head(fp, false, false);
- if (changed != func_hashtab.ht_changed) {
- emsg(_("E454: function list was modified"));
+ if (regmatch == NULL
+ ? (!message_filtered(fp->uf_name)
+ && !func_name_refcount(fp->uf_name))
+ : (!isdigit((uint8_t)(*fp->uf_name))
+ && vim_regexec(regmatch, fp->uf_name, 0))) {
+ if (list_func_head(fp, false, false) == FAIL) {
+ return;
+ }
+ if (function_list_modified(prev_ht_changed)) {
return;
}
}
@@ -2019,11 +2123,7 @@ void ex_function(exarg_T *eap)
{
char *theline;
char *line_to_free = NULL;
- char c;
- int saved_did_emsg;
bool saved_wait_return = need_wait_return;
- char *name = NULL;
- char *p;
char *arg;
char *line_arg = NULL;
garray_T newargs;
@@ -2033,16 +2133,9 @@ void ex_function(exarg_T *eap)
int flags = 0;
ufunc_T *fp;
bool overwrite = false;
- int indent;
- int nesting;
- dictitem_T *v;
funcdict_T fudi;
static int func_nr = 0; // number for nameless function
- int paren;
hashtab_T *ht;
- hashitem_T *hi;
- linenr_T sourcing_lnum_off;
- linenr_T sourcing_lnum_top;
bool is_heredoc = false;
char *skip_until = NULL;
char *heredoc_trimmed = NULL;
@@ -2060,11 +2153,11 @@ void ex_function(exarg_T *eap)
// ":function /pat": list functions matching pattern.
if (*eap->arg == '/') {
- p = skip_regexp(eap->arg + 1, '/', true);
+ char *p = skip_regexp(eap->arg + 1, '/', true);
if (!eap->skip) {
regmatch_T regmatch;
- c = *p;
+ char c = *p;
*p = NUL;
regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
*p = c;
@@ -2095,9 +2188,9 @@ void ex_function(exarg_T *eap)
// "fudi.fd_di" set, "fudi.fd_newkey" == NULL
// s:func script-local function name
// g:func global function name, same as "func"
- p = eap->arg;
- name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
- paren = (vim_strchr(p, '(') != NULL);
+ char *p = eap->arg;
+ char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
+ int paren = (vim_strchr(p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
// Return on an invalid expression in braces, unless the expression
// evaluation has been cancelled due to an aborting error, an
@@ -2114,7 +2207,7 @@ void ex_function(exarg_T *eap)
// An error in a function call during evaluation of an expression in magic
// braces should not cause the function not to be defined.
- saved_did_emsg = did_emsg;
+ const int saved_did_emsg = did_emsg;
did_emsg = false;
//
@@ -2135,28 +2228,37 @@ void ex_function(exarg_T *eap)
if (!eap->skip && !got_int) {
fp = find_func(name);
if (fp != NULL) {
- list_func_head(fp, !eap->forceit, eap->forceit);
- for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
- if (FUNCLINE(fp, j) == NULL) {
- continue;
- }
- msg_putchar('\n');
- if (!eap->forceit) {
- msg_outnum((long)j + 1);
- if (j < 9) {
- msg_putchar(' ');
+ // Check no function was added or removed from a callback, e.g. at
+ // the more prompt. "fp" may then be invalid.
+ const int prev_ht_changed = func_hashtab.ht_changed;
+
+ if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) {
+ for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
+ if (FUNCLINE(fp, j) == NULL) {
+ continue;
}
- if (j < 99) {
- msg_putchar(' ');
+ msg_putchar('\n');
+ if (!eap->forceit) {
+ msg_outnum(j + 1);
+ if (j < 9) {
+ msg_putchar(' ');
+ }
+ if (j < 99) {
+ msg_putchar(' ');
+ }
+ if (function_list_modified(prev_ht_changed)) {
+ break;
+ }
+ }
+ msg_prt_line(FUNCLINE(fp, j), false);
+ line_breakcheck(); // show multiple lines at a time!
+ }
+ if (!got_int) {
+ msg_putchar('\n');
+ if (!function_list_modified(prev_ht_changed)) {
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
}
- msg_prt_line(FUNCLINE(fp, j), false);
- ui_flush(); // show a line at a time
- os_breakcheck();
- }
- if (!got_int) {
- msg_putchar('\n');
- msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
} else {
emsg_funcname(N_("E123: Undefined function: %s"), name);
@@ -2196,7 +2298,7 @@ void ex_function(exarg_T *eap)
j++;
}
if (arg[j] != NUL) {
- emsg_funcname((char *)e_invarg2, arg);
+ emsg_funcname(e_invarg2, arg);
}
}
// Disallow using the g: dict.
@@ -2212,11 +2314,11 @@ void ex_function(exarg_T *eap)
if (KeyTyped && ui_has(kUICmdline)) {
show_block = true;
- ui_ext_cmdline_block_append(0, (const char *)eap->cmd);
+ ui_ext_cmdline_block_append(0, eap->cmd);
}
// find extra arguments "range", "dict", "abort" and "closure"
- for (;;) {
+ while (true) {
p = skipwhite(p);
if (strncmp(p, "range", 5) == 0) {
flags |= FC_RANGE;
@@ -2272,11 +2374,11 @@ void ex_function(exarg_T *eap)
}
// Save the starting line number.
- sourcing_lnum_top = SOURCING_LNUM;
+ linenr_T sourcing_lnum_top = SOURCING_LNUM;
- indent = 2;
- nesting = 0;
- for (;;) {
+ int indent = 2;
+ int nesting = 0;
+ while (true) {
if (KeyTyped) {
msg_scroll = true;
saved_wait_return = false;
@@ -2296,7 +2398,7 @@ void ex_function(exarg_T *eap)
} else {
xfree(line_to_free);
if (eap->getline == NULL) {
- theline = getcmdline(':', 0L, indent, do_concat);
+ theline = getcmdline(':', 0, indent, do_concat);
} else {
theline = eap->getline(':', eap->cookie, indent, do_concat);
}
@@ -2306,16 +2408,20 @@ void ex_function(exarg_T *eap)
lines_left = Rows - 1;
}
if (theline == NULL) {
- emsg(_("E126: Missing :endfunction"));
+ if (skip_until != NULL) {
+ semsg(_(e_missing_heredoc_end_marker_str), skip_until);
+ } else {
+ emsg(_("E126: Missing :endfunction"));
+ }
goto erret;
}
if (show_block) {
assert(indent >= 0);
- ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
+ ui_ext_cmdline_block_append((size_t)indent, theline);
}
// Detect line continuation: SOURCING_LNUM increased more than one.
- sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+ linenr_T sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
if (SOURCING_LNUM < sourcing_lnum_off) {
sourcing_lnum_off -= SOURCING_LNUM;
} else {
@@ -2335,7 +2441,7 @@ void ex_function(exarg_T *eap)
p = theline;
} else if (is_heredoc) {
p = skipwhite(theline) == theline
- ? theline : theline + strlen(heredoc_trimmed);
+ ? theline : theline + strlen(heredoc_trimmed);
} else {
p = theline + strlen(heredoc_trimmed);
}
@@ -2361,7 +2467,7 @@ void ex_function(exarg_T *eap)
} else if (line_arg != NULL && *skipwhite(line_arg) != NUL) {
nextcmd = line_arg;
} else if (*p != NUL && *p != '"' && p_verbose > 0) {
- give_warning2(_("W22: Text found after :endfunction: %s"), p, true);
+ swmsg(true, _("W22: Text found after :endfunction: %s"), p);
}
if (nextcmd != NULL) {
// Another command follows. If the line came from "eap" we
@@ -2393,11 +2499,11 @@ void ex_function(exarg_T *eap)
if (*p == '!') {
p = skipwhite(p + 1);
}
- p += eval_fname_script((const char *)p);
+ p += eval_fname_script(p);
xfree(trans_function_name(&p, true, 0, NULL, NULL));
if (*skipwhite(p) == '(') {
if (nesting == MAX_FUNC_NESTING - 1) {
- emsg(_("E1058: function nesting too deep"));
+ emsg(_(e_function_nesting_too_deep));
} else {
nesting++;
indent += 2;
@@ -2440,35 +2546,45 @@ void ex_function(exarg_T *eap)
&& (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) {
// ":python <<" continues until a dot, like ":append"
p = skipwhite(arg + 2);
+ if (strncmp(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline));
+ }
if (*p == NUL) {
skip_until = xstrdup(".");
} else {
- skip_until = xstrdup(p);
+ skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p));
}
+ do_concat = false;
+ is_heredoc = true;
}
// Check for ":let v =<< [trim] EOF"
// and ":let [a, b] =<< [trim] EOF"
- arg = skipwhite(skiptowhite(p));
- if (*arg == '[') {
- arg = vim_strchr(arg, ']');
- }
- if (arg != NULL) {
- arg = skipwhite(skiptowhite(arg));
- if (arg[0] == '='
- && arg[1] == '<'
- && arg[2] == '<'
- && (p[0] == 'l'
- && p[1] == 'e'
- && (!ASCII_ISALNUM(p[2])
- || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
+ arg = p;
+ if (checkforcmd(&arg, "let", 2)) {
+ while (vim_strchr("$@&", *arg) != NULL) {
+ arg++;
+ }
+ arg = skipwhite(find_name_end(arg, NULL, NULL, FNE_INCL_BR));
+ if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<') {
p = skipwhite(arg + 3);
- if (strncmp(p, "trim", 4) == 0) {
- // Ignore leading white space.
- p = skipwhite(p + 4);
- heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline));
+ while (true) {
+ if (strncmp(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline));
+ continue;
+ }
+ if (strncmp(p, "eval", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ continue;
+ }
+ break;
}
- skip_until = xstrnsave(p, (size_t)(skiptowhite(p) - p));
+ skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p));
do_concat = false;
is_heredoc = true;
}
@@ -2504,7 +2620,7 @@ void ex_function(exarg_T *eap)
// If there are no errors, add the function
if (fudi.fd_dict == NULL) {
- v = find_var((const char *)name, strlen(name), &ht, false);
+ dictitem_T *v = find_var(name, strlen(name), &ht, false);
if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
emsg_funcname(N_("E707: Function name conflicts with variable: %s"), name);
goto erret;
@@ -2551,13 +2667,11 @@ void ex_function(exarg_T *eap)
goto erret;
}
if (fudi.fd_di == NULL) {
- if (value_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
- TV_CSTRING)) {
+ if (value_check_lock(fudi.fd_dict->dv_lock, eap->arg, TV_CSTRING)) {
// Can't add a function to a locked dictionary
goto erret;
}
- } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
- TV_CSTRING)) {
+ } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, TV_CSTRING)) {
// Can't change an existing function if it is locked
goto erret;
}
@@ -2565,22 +2679,19 @@ void ex_function(exarg_T *eap)
// Give the function a sequential number. Can only be used with a
// Funcref!
xfree(name);
- sprintf(numbuf, "%d", ++func_nr); // NOLINT(runtime/printf)
+ snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr);
name = xstrdup(numbuf);
}
if (fp == NULL) {
if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) {
- int slen, plen;
- char *scriptname;
-
// Check that the autoload name matches the script name.
int j = FAIL;
if (SOURCING_NAME != NULL) {
- scriptname = autoload_name(name, strlen(name));
+ char *scriptname = autoload_name(name, strlen(name));
p = vim_strchr(scriptname, '/');
- plen = (int)strlen(p);
- slen = (int)strlen(SOURCING_NAME);
+ int plen = (int)strlen(p);
+ int slen = (int)strlen(SOURCING_NAME);
if (slen > plen && path_fnamecmp(p, SOURCING_NAME + slen - plen) == 0) {
j = OK;
}
@@ -2598,7 +2709,7 @@ void ex_function(exarg_T *eap)
if (fudi.fd_dict != NULL) {
if (fudi.fd_di == NULL) {
// Add new dict entry
- fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey);
+ fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey);
if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) {
xfree(fudi.fd_di);
xfree(fp);
@@ -2618,8 +2729,8 @@ void ex_function(exarg_T *eap)
// insert the new function in the function list
set_ufunc_name(fp, name);
if (overwrite) {
- hi = hash_find(&func_hashtab, name);
- hi->hi_key = (char *)UF2HIKEY(fp);
+ hashitem_T *hi = hash_find(&func_hashtab, name);
+ hi->hi_key = UF2HIKEY(fp);
} else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
xfree(fp);
goto erret;
@@ -2688,7 +2799,7 @@ int eval_fname_script(const char *const p)
bool translated_function_exists(const char *name)
{
if (builtin_function(name, -1)) {
- return find_internal_func((char *)name) != NULL;
+ return find_internal_func(name) != NULL;
}
return find_func(name) != NULL;
}
@@ -2727,7 +2838,6 @@ char *get_user_func_name(expand_T *xp, int idx)
static size_t done;
static int changed;
static hashitem_T *hi;
- ufunc_T *fp;
if (idx == 0) {
done = 0;
@@ -2742,7 +2852,7 @@ char *get_user_func_name(expand_T *xp, int idx)
while (HASHITEM_EMPTY(hi)) {
hi++;
}
- fp = HI2UF(hi);
+ ufunc_T *fp = HI2UF(hi);
if ((fp->uf_flags & FC_DICT)
|| strncmp(fp->uf_name, "<lambda>", 8) == 0) {
@@ -2750,14 +2860,14 @@ char *get_user_func_name(expand_T *xp, int idx)
}
if (strlen(fp->uf_name) + 4 >= IOSIZE) {
- return (char *)fp->uf_name; // Prevent overflow.
+ return fp->uf_name; // Prevent overflow.
}
- cat_func_name(IObuff, fp);
+ cat_func_name(IObuff, IOSIZE, fp);
if (xp->xp_context != EXPAND_USER_FUNC) {
- STRCAT(IObuff, "(");
+ xstrlcat(IObuff, "(", IOSIZE);
if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) {
- STRCAT(IObuff, ")");
+ xstrlcat(IObuff, ")", IOSIZE);
}
}
return IObuff;
@@ -2769,12 +2879,10 @@ char *get_user_func_name(expand_T *xp, int idx)
void ex_delfunction(exarg_T *eap)
{
ufunc_T *fp = NULL;
- char *p;
- char *name;
funcdict_T fudi;
- p = eap->arg;
- name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
+ char *p = eap->arg;
+ char *name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
xfree(fudi.fd_newkey);
if (name == NULL) {
if (fudi.fd_dict != NULL && !eap->skip) {
@@ -2851,13 +2959,11 @@ void ex_delfunction(exarg_T *eap)
/// becomes zero.
void func_unref(char *name)
{
- ufunc_T *fp = NULL;
-
if (name == NULL || !func_name_refcount(name)) {
return;
}
- fp = find_func(name);
+ ufunc_T *fp = find_func(name);
if (fp == NULL && isdigit((uint8_t)(*name))) {
#ifdef EXITFREE
if (!entered_free_all_mem) {
@@ -2893,12 +2999,10 @@ void func_ptr_unref(ufunc_T *fp)
/// Count a reference to a Function.
void func_ref(char *name)
{
- ufunc_T *fp;
-
if (name == NULL || !func_name_refcount(name)) {
return;
}
- fp = find_func(name);
+ ufunc_T *fp = find_func(name);
if (fp != NULL) {
(fp->uf_refcount)++;
} else if (isdigit((uint8_t)(*name))) {
@@ -2925,10 +3029,10 @@ static inline bool fc_referenced(const funccall_T *const fc)
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
FUNC_ATTR_NONNULL_ALL
{
- return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ return ((fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated)
!= DO_NOT_FREE_CNT)
- || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT
- || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->fc_l_vars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->fc_l_avars.dv_refcount != DO_NOT_FREE_CNT
|| fc->fc_refcount > 0);
}
@@ -2936,9 +3040,9 @@ static inline bool fc_referenced(const funccall_T *const fc)
/// referenced from anywhere that is in use.
static int can_free_funccal(funccall_T *fc, int copyID)
{
- return fc->l_varlist.lv_copyID != copyID
- && fc->l_vars.dv_copyID != copyID
- && fc->l_avars.dv_copyID != copyID
+ return fc->fc_l_varlist.lv_copyID != copyID
+ && fc->fc_l_vars.dv_copyID != copyID
+ && fc->fc_l_avars.dv_copyID != copyID
&& fc->fc_copyID != copyID;
}
@@ -2954,13 +3058,15 @@ void ex_return(exarg_T *eap)
return;
}
+ evalarg_T evalarg = { .eval_flags = eap->skip ? 0 : EVAL_EVALUATE };
+
if (eap->skip) {
emsg_skip++;
}
eap->nextcmd = NULL;
if ((*arg != NUL && *arg != '|' && *arg != '\n')
- && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) {
+ && eval0(arg, &rettv, eap, &evalarg) != FAIL) {
if (!eap->skip) {
returning = do_return(eap, false, true, &rettv);
} else {
@@ -2989,36 +3095,234 @@ void ex_return(exarg_T *eap)
if (eap->skip) {
emsg_skip--;
}
+ clear_evalarg(&evalarg, eap);
+}
+
+/// Lower level implementation of "call". Only called when not skipping.
+static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg,
+ const funcexe_T *const funcexe_init, evalarg_T *const evalarg)
+{
+ bool doesrange;
+ bool failed = false;
+
+ for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
+ if (eap->addr_count > 0) {
+ if (lnum > curbuf->b_ml.ml_line_count) {
+ // If the function deleted lines or switched to another buffer
+ // the line number may become invalid.
+ emsg(_(e_invrange));
+ break;
+ }
+ curwin->w_cursor.lnum = lnum;
+ curwin->w_cursor.col = 0;
+ curwin->w_cursor.coladd = 0;
+ }
+ *arg = startarg;
+
+ funcexe_T funcexe = *funcexe_init;
+ funcexe.fe_doesrange = &doesrange;
+ typval_T rettv;
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
+ if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) {
+ failed = true;
+ break;
+ }
+
+ // Handle a function returning a Funcref, Dictionary or List.
+ if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) {
+ failed = true;
+ break;
+ }
+
+ tv_clear(&rettv);
+ if (doesrange) {
+ break;
+ }
+
+ // Stop when immediately aborting on error, or when an interrupt
+ // occurred or an exception was thrown but not caught.
+ // get_func_tv() returned OK, so that the check for trailing
+ // characters below is executed.
+ if (aborting()) {
+ break;
+ }
+ }
+
+ return failed;
+}
+
+/// Core part of ":defer func(arg)". "arg" points to the "(" and is advanced.
+///
+/// @return FAIL or OK.
+static int ex_defer_inner(char *name, char **arg, const partial_T *const partial,
+ evalarg_T *const evalarg)
+{
+ typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
+ int partial_argc = 0; // number of partial arguments
+ int argcount = 0; // number of arguments found
+
+ if (current_funccal == NULL) {
+ semsg(_(e_str_not_inside_function), "defer");
+ return FAIL;
+ }
+ if (partial != NULL) {
+ if (partial->pt_dict != NULL) {
+ emsg(_(e_cannot_use_partial_with_dictionary_for_defer));
+ return FAIL;
+ }
+ if (partial->pt_argc > 0) {
+ partial_argc = partial->pt_argc;
+ for (int i = 0; i < partial_argc; i++) {
+ tv_copy(&partial->pt_argv[i], &argvars[i]);
+ }
+ }
+ }
+ int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount);
+ argcount += partial_argc;
+
+ if (r == OK) {
+ if (builtin_function(name, -1)) {
+ const EvalFuncDef *const fdef = find_internal_func(name);
+ if (fdef == NULL) {
+ emsg_funcname(e_unknown_function_str, name);
+ r = FAIL;
+ } else if (check_internal_func(fdef, argcount) == -1) {
+ r = FAIL;
+ }
+ } else {
+ ufunc_T *ufunc = find_func(name);
+ // we tolerate an unknown function here, it might be defined later
+ if (ufunc != NULL) {
+ int error = check_user_func_argcount(ufunc, argcount);
+ if (error != FCERR_UNKNOWN) {
+ user_func_error(error, name, NULL);
+ r = FAIL;
+ }
+ }
+ }
+ }
+
+ if (r == FAIL) {
+ while (--argcount >= 0) {
+ tv_clear(&argvars[argcount]);
+ }
+ return FAIL;
+ }
+ add_defer(name, argcount, argvars);
+ return OK;
+}
+
+/// Return true if currently inside a function call.
+/// Give an error message and return false when not.
+bool can_add_defer(void)
+{
+ if (get_current_funccal() == NULL) {
+ semsg(_(e_str_not_inside_function), "defer");
+ return false;
+ }
+ return true;
+}
+
+/// Add a deferred call for "name" with arguments "argvars[argcount]".
+/// Consumes "argvars[]".
+/// Caller must check that current_funccal is not NULL.
+void add_defer(char *name, int argcount_arg, typval_T *argvars)
+{
+ char *saved_name = xstrdup(name);
+ int argcount = argcount_arg;
+
+ if (current_funccal->fc_defer.ga_itemsize == 0) {
+ ga_init(&current_funccal->fc_defer, sizeof(defer_T), 10);
+ }
+ defer_T *dr = GA_APPEND_VIA_PTR(defer_T, &current_funccal->fc_defer);
+ dr->dr_name = saved_name;
+ dr->dr_argcount = argcount;
+ while (argcount > 0) {
+ argcount--;
+ dr->dr_argvars[argcount] = argvars[argcount];
+ }
+}
+
+/// Invoked after a function has finished: invoke ":defer" functions.
+static void handle_defer_one(funccall_T *funccal)
+{
+ for (int idx = funccal->fc_defer.ga_len - 1; idx >= 0; idx--) {
+ defer_T *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx;
+
+ if (dr->dr_name == NULL) {
+ // already being called, can happen if function does ":qa"
+ continue;
+ }
+
+ funcexe_T funcexe = { .fe_evaluate = true };
+
+ typval_T rettv;
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
+
+ char *name = dr->dr_name;
+ dr->dr_name = NULL;
+
+ // If the deferred function is called after an exception, then only the
+ // first statement in the function will be executed (because of the
+ // exception). So save and restore the try/catch/throw exception
+ // state.
+ exception_state_T estate;
+ exception_state_save(&estate);
+ exception_state_clear();
+
+ call_func(name, -1, &rettv, dr->dr_argcount, dr->dr_argvars, &funcexe);
+
+ exception_state_restore(&estate);
+
+ tv_clear(&rettv);
+ xfree(name);
+ for (int i = dr->dr_argcount - 1; i >= 0; i--) {
+ tv_clear(&dr->dr_argvars[i]);
+ }
+ }
+ ga_clear(&funccal->fc_defer);
+}
+
+/// Called when exiting: call all defer functions.
+void invoke_all_defer(void)
+{
+ for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->fc_caller) {
+ handle_defer_one(fc);
+ }
+
+ for (funccal_entry_T *fce = funccal_stack; fce != NULL; fce = fce->next) {
+ for (funccall_T *fc = fce->top_funccal; fc != NULL; fc = fc->fc_caller) {
+ handle_defer_one(fc);
+ }
+ }
}
/// ":1,25call func(arg1, arg2)" function call.
+/// ":defer func(arg1, arg2)" deferred function call.
void ex_call(exarg_T *eap)
{
char *arg = eap->arg;
- char *startarg;
- char *name;
- char *tofree;
- int len;
- typval_T rettv;
- linenr_T lnum;
- bool doesrange;
bool failed = false;
funcdict_T fudi;
partial_T *partial = NULL;
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
if (eap->skip) {
+ typval_T rettv;
// trans_function_name() doesn't work well when skipping, use eval0()
// instead to skip to any following command, e.g. for:
// :if 0 | call dict.foo().bar() | endif.
emsg_skip++;
- if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) {
+ if (eval0(eap->arg, &rettv, eap, &evalarg) != FAIL) {
tv_clear(&rettv);
}
emsg_skip--;
+ clear_evalarg(&evalarg, eap);
return;
}
- tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
+ char *tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
if (fudi.fd_newkey != NULL) {
// Still need to give an error message for missing key.
semsg(_(e_dictkey), fudi.fd_newkey);
@@ -3037,66 +3341,31 @@ void ex_call(exarg_T *eap)
// If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its
// contents. For VAR_PARTIAL get its partial, unless we already have one
// from trans_function_name().
- len = (int)strlen(tofree);
- name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false);
+ int len = (int)strlen(tofree);
+ bool found_var = false;
+ char *name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false, &found_var);
// Skip white space to allow ":call func ()". Not good, but required for
// backward compatibility.
- startarg = skipwhite(arg);
- rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
+ char *startarg = skipwhite(arg);
if (*startarg != '(') {
semsg(_(e_missingparen), eap->arg);
goto end;
}
- lnum = eap->line1;
- for (; lnum <= eap->line2; lnum++) {
- if (eap->addr_count > 0) { // -V560
- if (lnum > curbuf->b_ml.ml_line_count) {
- // If the function deleted lines or switched to another buffer
- // the line number may become invalid.
- emsg(_(e_invrange));
- break;
- }
- curwin->w_cursor.lnum = lnum;
- curwin->w_cursor.col = 0;
- curwin->w_cursor.coladd = 0;
- }
+ if (eap->cmdidx == CMD_defer) {
arg = startarg;
-
+ failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL;
+ } else {
funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_partial = partial;
+ funcexe.fe_selfdict = fudi.fd_dict;
funcexe.fe_firstline = eap->line1;
funcexe.fe_lastline = eap->line2;
- funcexe.fe_doesrange = &doesrange;
+ funcexe.fe_found_var = found_var;
funcexe.fe_evaluate = true;
- funcexe.fe_partial = partial;
- funcexe.fe_selfdict = fudi.fd_dict;
- if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) {
- failed = true;
- break;
- }
-
- // Handle a function returning a Funcref, Dictionary or List.
- if (handle_subscript((const char **)&arg, &rettv, true, true,
- (const char *)name, (const char **)&name)
- == FAIL) {
- failed = true;
- break;
- }
-
- tv_clear(&rettv);
- if (doesrange) {
- break;
- }
-
- // Stop when immediately aborting on error, or when an interrupt
- // occurred or an exception was thrown but not caught.
- // get_func_tv() returned OK, so that the check for trailing
- // characters below is executed.
- if (aborting()) {
- break;
- }
+ failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg);
}
// When inside :try we need to check for following "| catch" or "| endtry".
@@ -3112,6 +3381,7 @@ void ex_call(exarg_T *eap)
eap->nextcmd = check_nextcmd(arg);
}
}
+ clear_evalarg(&evalarg, eap);
end:
tv_dict_unref(fudi.fd_dict);
@@ -3130,19 +3400,18 @@ end:
/// false when the return gets pending.
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
{
- int idx;
cstack_T *const cstack = eap->cstack;
if (reanimate) {
// Undo the return.
- current_funccal->returned = false;
+ current_funccal->fc_returned = false;
}
// Cleanup (and deactivate) conditionals, but stop when a try conditional
// not in its finally clause (which then is to be executed next) is found.
// In this case, make the ":return" pending for execution at the ":endtry".
// Otherwise, return normally.
- idx = cleanup_conditionals(eap->cstack, 0, true);
+ int idx = cleanup_conditionals(eap->cstack, 0, true);
if (idx >= 0) {
cstack->cs_pending[idx] = CSTP_RETURN;
@@ -3155,8 +3424,8 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
// When undoing a return in order to make it pending, get the stored
// return rettv.
if (reanimate) {
- assert(current_funccal->rettv);
- rettv = current_funccal->rettv;
+ assert(current_funccal->fc_rettv);
+ rettv = current_funccal->fc_rettv;
}
if (rettv != NULL) {
@@ -3171,20 +3440,20 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
// The pending return value could be overwritten by a ":return"
// without argument in a finally clause; reset the default
// return value.
- current_funccal->rettv->v_type = VAR_NUMBER;
- current_funccal->rettv->vval.v_number = 0;
+ current_funccal->fc_rettv->v_type = VAR_NUMBER;
+ current_funccal->fc_rettv->vval.v_number = 0;
}
}
report_make_pending(CSTP_RETURN, rettv);
} else {
- current_funccal->returned = true;
+ current_funccal->fc_returned = true;
// If the return is carried out now, store the return value. For
// a return immediately after reanimation, the value is already
// there.
if (!reanimate && rettv != NULL) {
- tv_clear(current_funccal->rettv);
- *current_funccal->rettv = *(typval_T *)rettv;
+ tv_clear(current_funccal->fc_rettv);
+ *current_funccal->fc_rettv = *(typval_T *)rettv;
if (!is_cmd) {
xfree(rettv);
}
@@ -3208,7 +3477,7 @@ char *get_return_cmd(void *rettv)
s = "";
}
- STRCPY(IObuff, ":return ");
+ xstrlcpy(IObuff, ":return ", IOSIZE);
xstrlcpy(IObuff + 8, s, IOSIZE - 8);
if (strlen(s) + 8 >= IOSIZE) {
STRCPY(IObuff + IOSIZE - 4, "...");
@@ -3224,34 +3493,33 @@ char *get_return_cmd(void *rettv)
char *get_func_line(int c, void *cookie, int indent, bool do_concat)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
char *retval;
- garray_T *gap; // growarray with function lines
// If breakpoints have been added/deleted need to check for it.
- if (fcp->dbg_tick != debug_tick) {
- fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM);
- fcp->dbg_tick = debug_tick;
+ if (fcp->fc_dbg_tick != debug_tick) {
+ fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM);
+ fcp->fc_dbg_tick = debug_tick;
}
if (do_profiling == PROF_YES) {
func_line_end(cookie);
}
- gap = &fp->uf_lines;
+ garray_T *gap = &fp->uf_lines; // growarray with function lines
if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned) {
+ || fcp->fc_returned) {
retval = NULL;
} else {
// Skip NULL lines (continuation lines).
- while (fcp->linenr < gap->ga_len
- && ((char **)(gap->ga_data))[fcp->linenr] == NULL) {
- fcp->linenr++;
+ while (fcp->fc_linenr < gap->ga_len
+ && ((char **)(gap->ga_data))[fcp->fc_linenr] == NULL) {
+ fcp->fc_linenr++;
}
- if (fcp->linenr >= gap->ga_len) {
+ if (fcp->fc_linenr >= gap->ga_len) {
retval = NULL;
} else {
- retval = xstrdup(((char **)(gap->ga_data))[fcp->linenr++]);
- SOURCING_LNUM = fcp->linenr;
+ retval = xstrdup(((char **)(gap->ga_data))[fcp->fc_linenr++]);
+ SOURCING_LNUM = fcp->fc_linenr;
if (do_profiling == PROF_YES) {
func_line_start(cookie);
}
@@ -3259,11 +3527,11 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat)
}
// Did we encounter a breakpoint?
- if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) {
- dbg_breakpoint((char *)fp->uf_name, SOURCING_LNUM);
+ if (fcp->fc_breakpoint != 0 && fcp->fc_breakpoint <= SOURCING_LNUM) {
+ dbg_breakpoint(fp->uf_name, SOURCING_LNUM);
// Find next breakpoint.
- fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM);
- fcp->dbg_tick = debug_tick;
+ fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM);
+ fcp->fc_dbg_tick = debug_tick;
}
return retval;
@@ -3277,21 +3545,20 @@ int func_has_ended(void *cookie)
// Ignore the "abort" flag if the abortion behavior has been changed due to
// an error inside a try conditional.
- return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned;
+ return ((fcp->fc_func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
+ || fcp->fc_returned;
}
/// @return true if cookie indicates a function which "abort"s on errors.
int func_has_abort(void *cookie)
{
- return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT;
+ return ((funccall_T *)cookie)->fc_func->uf_flags & FC_ABORT;
}
/// Turn "dict.Func" into a partial for "Func" bound to "dict".
/// Changes "rettv" in-place.
void make_partial(dict_T *const selfdict, typval_T *const rettv)
{
- char *fname;
char *tofree = NULL;
ufunc_T *fp;
char fname_buf[FLEN_FIXED + 1];
@@ -3300,9 +3567,9 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
fp = rettv->vval.v_partial->pt_func;
} else {
- fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
- ? rettv->vval.v_string
- : rettv->vval.v_partial->pt_name;
+ char *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
+ ? rettv->vval.v_string
+ : rettv->vval.v_partial->pt_name;
// Translate "s:func" to the stored function name.
fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
fp = find_func(fname);
@@ -3321,7 +3588,6 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
pt->pt_name = rettv->vval.v_string;
} else {
partial_T *ret_pt = rettv->vval.v_partial;
- int i;
// Partial: copy the function name, use selfdict and copy
// args. Can't take over name or args, the partial might
@@ -3337,7 +3603,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
size_t arg_size = sizeof(typval_T) * (size_t)ret_pt->pt_argc;
pt->pt_argv = (typval_T *)xmalloc(arg_size);
pt->pt_argc = ret_pt->pt_argc;
- for (i = 0; i < pt->pt_argc; i++) {
+ for (int i = 0; i < pt->pt_argc; i++) {
tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
}
}
@@ -3351,31 +3617,31 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
/// @return the name of the executed function.
char *func_name(void *cookie)
{
- return ((funccall_T *)cookie)->func->uf_name;
+ return ((funccall_T *)cookie)->fc_func->uf_name;
}
/// @return the address holding the next breakpoint line for a funccall cookie.
linenr_T *func_breakpoint(void *cookie)
{
- return &((funccall_T *)cookie)->breakpoint;
+ return &((funccall_T *)cookie)->fc_breakpoint;
}
/// @return the address holding the debug tick for a funccall cookie.
int *func_dbg_tick(void *cookie)
{
- return &((funccall_T *)cookie)->dbg_tick;
+ return &((funccall_T *)cookie)->fc_dbg_tick;
}
/// @return the nesting level for a funccall cookie.
int func_level(void *cookie)
{
- return ((funccall_T *)cookie)->level;
+ return ((funccall_T *)cookie)->fc_level;
}
/// @return true when a function was ended by a ":return" command.
int current_func_returned(void)
{
- return current_funccal->returned;
+ return current_funccal->fc_returned;
}
bool free_unref_funccal(int copyID, int testing)
@@ -3386,12 +3652,12 @@ bool free_unref_funccal(int copyID, int testing)
for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
if (can_free_funccal(*pfc, copyID)) {
funccall_T *fc = *pfc;
- *pfc = fc->caller;
+ *pfc = fc->fc_caller;
free_funccal_contents(fc);
did_free = true;
did_free_funccal = true;
} else {
- pfc = &(*pfc)->caller;
+ pfc = &(*pfc)->fc_caller;
}
}
if (did_free_funccal) {
@@ -3408,7 +3674,7 @@ funccall_T *get_funccal(void)
funccall_T *funccal = current_funccal;
if (debug_backtrace_level > 0) {
for (int i = 0; i < debug_backtrace_level; i++) {
- funccall_T *temp_funccal = funccal->caller;
+ funccall_T *temp_funccal = funccal->fc_caller;
if (temp_funccal) {
funccal = temp_funccal;
} else {
@@ -3428,7 +3694,7 @@ hashtab_T *get_funccal_local_ht(void)
if (current_funccal == NULL) {
return NULL;
}
- return &get_funccal()->l_vars.dv_hashtab;
+ return &get_funccal()->fc_l_vars.dv_hashtab;
}
/// @return the l: scope variable or
@@ -3438,7 +3704,7 @@ dictitem_T *get_funccal_local_var(void)
if (current_funccal == NULL) {
return NULL;
}
- return (dictitem_T *)&get_funccal()->l_vars_var;
+ return (dictitem_T *)&get_funccal()->fc_l_vars_var;
}
/// @return the hashtable used for argument in the current funccal or
@@ -3448,7 +3714,7 @@ hashtab_T *get_funccal_args_ht(void)
if (current_funccal == NULL) {
return NULL;
}
- return &get_funccal()->l_avars.dv_hashtab;
+ return &get_funccal()->fc_l_avars.dv_hashtab;
}
/// @return the a: scope variable or
@@ -3458,14 +3724,14 @@ dictitem_T *get_funccal_args_var(void)
if (current_funccal == NULL) {
return NULL;
}
- return (dictitem_T *)&current_funccal->l_avars_var;
+ return (dictitem_T *)&current_funccal->fc_l_avars_var;
}
/// List function variables, if there is a function.
void list_func_vars(int *first)
{
if (current_funccal != NULL) {
- list_hashtable_vars(&current_funccal->l_vars.dv_hashtab, "l:", false,
+ list_hashtable_vars(&current_funccal->fc_l_vars.dv_hashtab, "l:", false,
first);
}
}
@@ -3474,8 +3740,8 @@ void list_func_vars(int *first)
/// funccal, return the dict that contains it. Otherwise return NULL.
dict_T *get_current_funccal_dict(hashtab_T *ht)
{
- if (current_funccal != NULL && ht == &current_funccal->l_vars.dv_hashtab) {
- return &current_funccal->l_vars;
+ if (current_funccal != NULL && ht == &current_funccal->fc_l_vars.dv_hashtab) {
+ return &current_funccal->fc_l_vars;
}
return NULL;
}
@@ -3483,7 +3749,7 @@ dict_T *get_current_funccal_dict(hashtab_T *ht)
/// Search hashitem in parent scope.
hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) {
return NULL;
}
@@ -3493,7 +3759,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
const char *varname;
// Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
while (current_funccal != NULL) {
hashtab_T *ht = find_var_ht(name, namelen, &varname);
if (ht != NULL && *varname != NUL) {
@@ -3503,10 +3769,10 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
break;
}
}
- if (current_funccal == current_funccal->func->uf_scoped) {
+ if (current_funccal == current_funccal->fc_func->uf_scoped) {
break;
}
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
}
current_funccal = old_current_funccal;
@@ -3516,7 +3782,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
/// Search variable in parent scope.
dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no_autoload)
{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) {
return NULL;
}
@@ -3525,7 +3791,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no
const char *varname;
// Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
while (current_funccal) {
hashtab_T *ht = find_var_ht(name, namelen, &varname);
if (ht != NULL && *varname != NUL) {
@@ -3535,10 +3801,10 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no
break;
}
}
- if (current_funccal == current_funccal->func->uf_scoped) {
+ if (current_funccal == current_funccal->fc_func->uf_scoped) {
break;
}
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
}
current_funccal = old_current_funccal;
@@ -3549,11 +3815,11 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no
bool set_ref_in_previous_funccal(int copyID)
{
for (funccall_T *fc = previous_funccal; fc != NULL;
- fc = fc->caller) {
+ fc = fc->fc_caller) {
fc->fc_copyID = copyID + 1;
- if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL)
- || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL)
- || set_ref_in_list(&fc->l_varlist, copyID + 1, NULL)) {
+ if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_list(&fc->fc_l_varlist, copyID + 1, NULL)) {
return true;
}
}
@@ -3564,10 +3830,10 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)
{
if (fc->fc_copyID != copyID) {
fc->fc_copyID = copyID;
- if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL)
- || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL)
- || set_ref_in_list(&fc->l_varlist, copyID, NULL)
- || set_ref_in_func(NULL, fc->func, copyID)) {
+ if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID, NULL)
+ || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID, NULL)
+ || set_ref_in_list(&fc->fc_l_varlist, copyID, NULL)
+ || set_ref_in_func(NULL, fc->fc_func, copyID)) {
return true;
}
}
@@ -3578,7 +3844,7 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)
bool set_ref_in_call_stack(int copyID)
{
for (funccall_T *fc = current_funccal; fc != NULL;
- fc = fc->caller) {
+ fc = fc->fc_caller) {
if (set_ref_in_funccal(fc, copyID)) {
return true;
}
@@ -3588,7 +3854,7 @@ bool set_ref_in_call_stack(int copyID)
for (funccal_entry_T *entry = funccal_stack; entry != NULL;
entry = entry->next) {
for (funccall_T *fc = entry->top_funccal; fc != NULL;
- fc = fc->caller) {
+ fc = fc->fc_caller) {
if (set_ref_in_funccal(fc, copyID)) {
return true;
}
@@ -3601,15 +3867,11 @@ bool set_ref_in_call_stack(int copyID)
/// Set "copyID" in all functions available by name.
bool set_ref_in_functions(int copyID)
{
- int todo;
- hashitem_T *hi = NULL;
- ufunc_T *fp;
-
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
+ int todo = (int)func_hashtab.ht_used;
+ for (hashitem_T *hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- fp = HI2UF(hi);
+ ufunc_T *fp = HI2UF(hi);
if (!func_name_refcount(fp->uf_name)
&& set_ref_in_func(NULL, fp, copyID)) {
return true;
@@ -3639,22 +3901,20 @@ bool set_ref_in_func_args(int copyID)
bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID)
{
ufunc_T *fp = fp_in;
- funccall_T *fc;
int error = FCERR_NONE;
char fname_buf[FLEN_FIXED + 1];
char *tofree = NULL;
- char *fname;
bool abort = false;
if (name == NULL && fp_in == NULL) {
return false;
}
if (fp_in == NULL) {
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ char *fname = fname_trans_sid(name, fname_buf, &tofree, &error);
fp = find_func(fname);
}
if (fp != NULL) {
- for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
+ for (funccall_T *fc = fp->uf_scoped; fc != NULL; fc = fc->fc_func->uf_scoped) {
abort = abort || set_ref_in_funccal(fc, copyID);
}
}
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
index c8583f232c..8050caab2b 100644
--- a/src/nvim/eval/userfunc.h
+++ b/src/nvim/eval/userfunc.h
@@ -1,22 +1,21 @@
-#ifndef NVIM_EVAL_USERFUNC_H
-#define NVIM_EVAL_USERFUNC_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/garray.h"
-#include "nvim/hashtab.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/hashtab_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
struct funccal_entry;
// From user function to hashitem and back.
#define UF2HIKEY(fp) ((fp)->uf_name)
-#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
+#define HIKEY2UF(p) ((ufunc_T *)((p) - offsetof(ufunc_T, uf_name)))
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
// flags used in uf_flags
@@ -27,10 +26,10 @@ struct funccal_entry;
#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
#define FC_SANDBOX 0x40 // function defined in the sandbox
-#define FC_DEAD 0x80 // function kept only for reference to dfunc
-#define FC_EXPORT 0x100 // "export def Func()"
+// #define FC_DEAD 0x80 // function kept only for reference to dfunc
+// #define FC_EXPORT 0x100 // "export def Func()"
#define FC_NOARGS 0x200 // no a: variables in lambda
-#define FC_VIM9 0x400 // defined in vim9 script file
+// #define FC_VIM9 0x400 // defined in vim9 script file
#define FC_LUAREF 0x800 // luaref callback
/// Structure used by trans_function_name()
@@ -74,6 +73,8 @@ typedef struct {
partial_T *fe_partial; ///< for extra arguments
dict_T *fe_selfdict; ///< Dictionary for "self"
typval_T *fe_basetv; ///< base for base->method()
+ bool fe_found_var; ///< if the function is not found then give an
+ ///< error that a variable is not callable.
} funcexe_T;
#define FUNCEXE_INIT (funcexe_T) { \
@@ -85,6 +86,7 @@ typedef struct {
.fe_partial = NULL, \
.fe_selfdict = NULL, \
.fe_basetv = NULL, \
+ .fe_found_var = false, \
}
#define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j]
@@ -93,4 +95,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/userfunc.h.generated.h"
#endif
-#endif // NVIM_EVAL_USERFUNC_H
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 3e593151fc..2968f75f4d 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval/vars.c: functions for dealing with variables
#include <assert.h>
@@ -8,9 +5,11 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
@@ -19,7 +18,6 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
@@ -27,19 +25,23 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os.h"
#include "nvim/search.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -50,8 +52,102 @@
#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
-static char *e_letunexp = N_("E18: Unexpected characters in :let");
-static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
+static const char *e_letunexp = N_("E18: Unexpected characters in :let");
+static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
+static const char e_setting_v_str_to_value_with_wrong_type[]
+ = N_("E963: Setting v:%s to value with wrong type");
+static const char e_cannot_use_heredoc_here[]
+ = N_("E991: Cannot use =<< here");
+
+/// Evaluate one Vim expression {expr} in string "p" and append the
+/// resulting string to "gap". "p" points to the opening "{".
+/// When "evaluate" is false only skip over the expression.
+/// Return a pointer to the character after "}", NULL for an error.
+char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate)
+{
+ char *block_start = skipwhite(p + 1); // skip the opening {
+ char *block_end = block_start;
+
+ if (*block_start == NUL) {
+ semsg(_(e_missing_close_curly_str), p);
+ return NULL;
+ }
+ if (skip_expr(&block_end, NULL) == FAIL) {
+ return NULL;
+ }
+ block_end = skipwhite(block_end);
+ if (*block_end != '}') {
+ semsg(_(e_missing_close_curly_str), p);
+ return NULL;
+ }
+ if (evaluate) {
+ *block_end = NUL;
+ char *expr_val = eval_to_string(block_start, true);
+ *block_end = '}';
+ if (expr_val == NULL) {
+ return NULL;
+ }
+ ga_concat(gap, expr_val);
+ xfree(expr_val);
+ }
+
+ return block_end + 1;
+}
+
+/// Evaluate all the Vim expressions {expr} in "str" and return the resulting
+/// string in allocated memory. "{{" is reduced to "{" and "}}" to "}".
+/// Used for a heredoc assignment.
+/// Returns NULL for an error.
+char *eval_all_expr_in_str(char *str)
+{
+ garray_T ga;
+ ga_init(&ga, 1, 80);
+ char *p = str;
+
+ while (*p != NUL) {
+ bool escaped_brace = false;
+
+ // Look for a block start.
+ char *lit_start = p;
+ while (*p != '{' && *p != '}' && *p != NUL) {
+ p++;
+ }
+
+ if (*p != NUL && *p == p[1]) {
+ // Escaped brace, unescape and continue.
+ // Include the brace in the literal string.
+ p++;
+ escaped_brace = true;
+ } else if (*p == '}') {
+ semsg(_(e_stray_closing_curly_str), str);
+ ga_clear(&ga);
+ return NULL;
+ }
+
+ // Append the literal part.
+ ga_concat_len(&ga, lit_start, (size_t)(p - lit_start));
+
+ if (*p == NUL) {
+ break;
+ }
+
+ if (escaped_brace) {
+ // Skip the second brace.
+ p++;
+ continue;
+ }
+
+ // Evaluate the expression and append the result.
+ p = eval_one_expr_in_str(p, &ga, true);
+ if (p == NULL) {
+ ga_clear(&ga);
+ return NULL;
+ }
+ }
+ ga_append(&ga, NUL);
+
+ return ga.ga_data;
+}
/// Get a list of lines from a HERE document. The here document is a list of
/// lines surrounded by a marker.
@@ -65,64 +161,91 @@ static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
/// marker, then the leading indentation before the lines (matching the
/// indentation in the 'cmd' line) is stripped.
///
-/// @return a List with {lines} or NULL.
-static list_T *heredoc_get(exarg_T *eap, char *cmd)
+/// When getting lines for an embedded script (e.g. python, lua, perl, ruby,
+/// tcl, mzscheme), "script_get" is set to true. In this case, if the marker is
+/// missing, then '.' is accepted as a marker.
+///
+/// @return a List with {lines} or NULL on failure.
+list_T *heredoc_get(exarg_T *eap, char *cmd, bool script_get)
{
char *marker;
- char *p;
int marker_indent_len = 0;
int text_indent_len = 0;
char *text_indent = NULL;
+ char dot[] = ".";
if (eap->getline == NULL) {
- emsg(_("E991: cannot use =<< here"));
+ emsg(_(e_cannot_use_heredoc_here));
return NULL;
}
// Check for the optional 'trim' word before the marker
cmd = skipwhite(cmd);
- if (strncmp(cmd, "trim", 4) == 0
- && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
- cmd = skipwhite(cmd + 4);
-
- // Trim the indentation from all the lines in the here document.
- // The amount of indentation trimmed is the same as the indentation of
- // the first line after the :let command line. To find the end marker
- // the indent of the :let command line is trimmed.
- p = *eap->cmdlinep;
- while (ascii_iswhite(*p)) {
- p++;
- marker_indent_len++;
+ bool evalstr = false;
+ bool eval_failed = false;
+ while (true) {
+ if (strncmp(cmd, "trim", 4) == 0
+ && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
+ cmd = skipwhite(cmd + 4);
+
+ // Trim the indentation from all the lines in the here document.
+ // The amount of indentation trimmed is the same as the indentation
+ // of the first line after the :let command line. To find the end
+ // marker the indent of the :let command line is trimmed.
+ char *p = *eap->cmdlinep;
+ while (ascii_iswhite(*p)) {
+ p++;
+ marker_indent_len++;
+ }
+ text_indent_len = -1;
+
+ continue;
+ }
+ if (strncmp(cmd, "eval", 4) == 0
+ && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
+ cmd = skipwhite(cmd + 4);
+ evalstr = true;
+ continue;
}
- text_indent_len = -1;
+ break;
}
// The marker is the next word.
if (*cmd != NUL && *cmd != '"') {
marker = skipwhite(cmd);
- p = skiptowhite(marker);
+ char *p = skiptowhite(marker);
if (*skipwhite(p) != NUL && *skipwhite(p) != '"') {
semsg(_(e_trailing_arg), p);
return NULL;
}
*p = NUL;
- if (islower((uint8_t)(*marker))) {
+ if (!script_get && islower((uint8_t)(*marker))) {
emsg(_("E221: Marker cannot start with lower case letter"));
return NULL;
}
} else {
- emsg(_("E172: Missing marker"));
- return NULL;
+ // When getting lines for an embedded script, if the marker is missing,
+ // accept '.' as the marker.
+ if (script_get) {
+ marker = dot;
+ } else {
+ emsg(_("E172: Missing marker"));
+ return NULL;
+ }
}
+ char *theline = NULL;
list_T *l = tv_list_alloc(0);
- for (;;) {
+ while (true) {
int mi = 0;
int ti = 0;
- char *theline = eap->getline(NUL, eap->cookie, 0, false);
+ xfree(theline);
+ theline = eap->getline(NUL, eap->cookie, 0, false);
if (theline == NULL) {
- semsg(_("E990: Missing end marker '%s'"), marker);
+ if (!script_get) {
+ semsg(_("E990: Missing end marker '%s'"), marker);
+ }
break;
}
@@ -133,18 +256,24 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
mi = marker_indent_len;
}
if (strcmp(marker, theline + mi) == 0) {
- xfree(theline);
break;
}
+
+ // If expression evaluation failed in the heredoc, then skip till the
+ // end marker.
+ if (eval_failed) {
+ continue;
+ }
+
if (text_indent_len == -1 && *theline != NUL) {
// set the text indent from the first line.
- p = theline;
+ char *p = theline;
text_indent_len = 0;
while (ascii_iswhite(*p)) {
p++;
text_indent_len++;
}
- text_indent = xstrnsave(theline, (size_t)text_indent_len);
+ text_indent = xmemdupz(theline, (size_t)text_indent_len);
}
// with "trim": skip the indent matching the first line
if (text_indent != NULL) {
@@ -155,11 +284,28 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
}
}
- tv_list_append_string(l, theline + ti, -1);
- xfree(theline);
+ char *str = theline + ti;
+ if (evalstr && !eap->skip) {
+ str = eval_all_expr_in_str(str);
+ if (str == NULL) {
+ // expression evaluation failed
+ eval_failed = true;
+ continue;
+ }
+ xfree(theline);
+ theline = str;
+ }
+
+ tv_list_append_string(l, str, -1);
}
+ xfree(theline);
xfree(text_indent);
+ if (eval_failed) {
+ // expression evaluation in the heredoc failed
+ tv_list_free(l);
+ return NULL;
+ }
return l;
}
@@ -175,32 +321,23 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
/// ":let var ..= expr" assignment command.
/// ":let [var1, var2] = expr" unpack list.
/// ":let [name, ..., ; lastname] = expr" unpack list.
-void ex_let(exarg_T *eap)
-{
- ex_let_const(eap, false);
-}
-
+///
/// ":cons[t] var = expr1" define constant
/// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list
/// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list
-void ex_const(exarg_T *eap)
-{
- ex_let_const(eap, true);
-}
-
-static void ex_let_const(exarg_T *eap, const bool is_const)
+void ex_let(exarg_T *eap)
{
+ const bool is_const = eap->cmdidx == CMD_const;
char *arg = eap->arg;
char *expr = NULL;
typval_T rettv;
- int i;
int var_count = 0;
int semicolon = 0;
char op[2];
- char *argend;
+ const char *argend;
int first = true;
- argend = (char *)skip_var_list(arg, &var_count, &semicolon);
+ argend = skip_var_list(arg, &var_count, &semicolon);
if (argend == NULL) {
return;
}
@@ -208,14 +345,16 @@ static void ex_let_const(exarg_T *eap, const bool is_const)
argend--;
}
expr = skipwhite(argend);
- if (*expr != '=' && !((vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL
- && expr[1] == '=') || strncmp(expr, "..=", 3) == 0)) {
+ bool concat = strncmp(expr, "..=", 3) == 0;
+ bool has_assign = *expr == '=' || (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL
+ && expr[1] == '=');
+ if (!has_assign && !concat) {
// ":let" without "=": list variables
if (*arg == '[') {
emsg(_(e_invarg));
} else if (!ends_excmd(*arg)) {
// ":let var1 var2"
- arg = (char *)list_arg_vars(eap, (const char *)arg, &first);
+ arg = (char *)list_arg_vars(eap, arg, &first);
} else if (!eap->skip) {
// ":let"
list_glob_vars(&first);
@@ -227,50 +366,58 @@ static void ex_let_const(exarg_T *eap, const bool is_const)
list_vim_vars(&first);
}
eap->nextcmd = check_nextcmd(arg);
- } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
+ return;
+ }
+
+ if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
// HERE document
- list_T *l = heredoc_get(eap, expr + 3);
+ list_T *l = heredoc_get(eap, expr + 3, false);
if (l != NULL) {
tv_list_set_ret(&rettv, l);
if (!eap->skip) {
op[0] = '=';
op[1] = NUL;
- (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
- is_const, (char *)op);
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
}
tv_clear(&rettv);
}
- } else {
- op[0] = '=';
- op[1] = NUL;
- if (*expr != '=') {
- if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) {
- op[0] = *expr; // +=, -=, *=, /=, %= or .=
- if (expr[0] == '.' && expr[1] == '.') { // ..=
- expr++;
- }
- }
- expr += 2;
- } else {
- expr += 1;
- }
+ return;
+ }
- expr = skipwhite(expr);
+ rettv.v_type = VAR_UNKNOWN;
- if (eap->skip) {
- emsg_skip++;
- }
- i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip);
- if (eap->skip) {
- if (i != FAIL) {
- tv_clear(&rettv);
+ op[0] = '=';
+ op[1] = NUL;
+ if (*expr != '=') {
+ if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) {
+ op[0] = *expr; // +=, -=, *=, /=, %= or .=
+ if (expr[0] == '.' && expr[1] == '.') { // ..=
+ expr++;
}
- emsg_skip--;
- } else if (i != FAIL) {
- (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
- is_const, (char *)op);
- tv_clear(&rettv);
}
+ expr += 2;
+ } else {
+ expr += 1;
+ }
+
+ expr = skipwhite(expr);
+
+ if (eap->skip) {
+ emsg_skip++;
+ }
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
+ int eval_res = eval0(expr, &rettv, eap, &evalarg);
+ if (eap->skip) {
+ emsg_skip--;
+ }
+ clear_evalarg(&evalarg, eap);
+
+ if (!eap->skip && eval_res != FAIL) {
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
+ }
+ if (eval_res != FAIL) {
+ tv_clear(&rettv);
}
}
@@ -369,15 +516,13 @@ int ex_let_vars(char *arg_start, typval_T *tv, int copy, int semicolon, int var_
/// @return NULL for an error.
const char *skip_var_list(const char *arg, int *var_count, int *semicolon)
{
- const char *p;
- const char *s;
-
if (*arg == '[') {
+ const char *s;
// "[var, var]": find the matching ']'.
- p = arg;
- for (;;) {
+ const char *p = arg;
+ while (true) {
p = skipwhite(p + 1); // skip whites after '[', ';' or ','
- s = skip_var_one((char *)p);
+ s = skip_var_one(p);
if (s == p) {
semsg(_(e_invarg2), p);
return NULL;
@@ -400,7 +545,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon)
}
return p + 1;
}
- return skip_var_one((char *)arg);
+ return skip_var_one(arg);
}
/// Skip one (assignable) variable name, including @r, $VAR, &option, d.key,
@@ -410,8 +555,8 @@ static const char *skip_var_one(const char *arg)
if (*arg == '@' && arg[1] != NUL) {
return arg + 2;
}
- return (char *)find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
- NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
+ return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
+ NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
}
/// List variables for hashtab "ht" with prefix "prefix".
@@ -432,7 +577,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs
// apply :filter /pat/ to variable name
xstrlcpy(buf, prefix, IOSIZE);
- xstrlcat(buf, (char *)di->di_key, IOSIZE);
+ xstrlcat(buf, di->di_key, IOSIZE);
if (message_filtered(buf)) {
continue;
}
@@ -504,13 +649,12 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
if (tofree != NULL) {
name = tofree;
}
- if (get_var_tv(name, len, &tv, NULL, true, false)
- == FAIL) {
+ if (eval_variable(name, len, &tv, NULL, true, false) == FAIL) {
error = true;
} else {
// handle d.key, l[idx], f(expr)
const char *const arg_subsc = arg;
- if (handle_subscript(&arg, &tv, true, true, name, &name) == FAIL) {
+ if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, true) == FAIL) {
error = true;
} else {
if (arg == arg_subsc && len == 2 && name[1] == ':') {
@@ -553,12 +697,191 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
xfree(tofree);
}
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
}
return arg;
}
+/// Set an environment variable, part of ex_let_one().
+static char *ex_let_env(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock an environment variable"));
+ return NULL;
+ }
+
+ // Find the end of the name.
+ char *arg_end = NULL;
+ arg++;
+ char *name = arg;
+ int len = get_env_len((const char **)&arg);
+ if (len == 0) {
+ semsg(_(e_invarg2), name - 1);
+ } else {
+ if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ semsg(_(e_letwrong), op);
+ } else if (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) {
+ emsg(_(e_letunexp));
+ } else if (!check_secure()) {
+ char *tofree = NULL;
+ const char c1 = name[len];
+ name[len] = NUL;
+ const char *p = tv_get_string_chk(tv);
+ if (p != NULL && op != NULL && *op == '.') {
+ char *s = vim_getenv(name);
+ if (s != NULL) {
+ tofree = concat_str(s, p);
+ p = tofree;
+ xfree(s);
+ }
+ }
+ if (p != NULL) {
+ vim_setenv_ext(name, p);
+ arg_end = arg;
+ }
+ name[len] = c1;
+ xfree(tofree);
+ }
+ }
+ return arg_end;
+}
+
+/// Set an option, part of ex_let_one().
+static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock an option"));
+ return NULL;
+ }
+
+ // Find the end of the name.
+ char *arg_end = NULL;
+ int scope;
+ char *const p = (char *)find_option_end((const char **)&arg, &scope);
+ if (p == NULL
+ || (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
+ emsg(_(e_letunexp));
+ return NULL;
+ }
+
+ const char c1 = *p;
+ *p = NUL;
+
+ uint32_t opt_p_flags;
+ bool hidden;
+ OptVal curval = get_option_value(arg, &opt_p_flags, scope, &hidden);
+ OptVal newval = NIL_OPTVAL;
+ if (curval.type == kOptValTypeNil && arg[0] != 't' && arg[1] != '_') {
+ semsg(_(e_unknown_option2), arg);
+ goto theend;
+ }
+ if (op != NULL && *op != '='
+ && ((curval.type != kOptValTypeString && *op == '.')
+ || (curval.type == kOptValTypeString && *op != '.'))) {
+ semsg(_(e_letwrong), op);
+ goto theend;
+ }
+
+ bool error;
+ newval = tv_to_optval(tv, arg, opt_p_flags, &error);
+ if (error) {
+ goto theend;
+ }
+
+ // Don't assume current and new values are of the same type in order to future-proof the code for
+ // when an option can have multiple types.
+ const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean)
+ && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean));
+ const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString;
+
+ if (op != NULL && *op != '=') {
+ if (!hidden && is_num) { // number or bool
+ OptInt cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean;
+ OptInt new_n = newval.type == kOptValTypeNumber ? newval.data.number : newval.data.boolean;
+
+ switch (*op) {
+ case '+':
+ new_n = cur_n + new_n; break;
+ case '-':
+ new_n = cur_n - new_n; break;
+ case '*':
+ new_n = cur_n * new_n; break;
+ case '/':
+ new_n = num_divide(cur_n, new_n); break;
+ case '%':
+ new_n = num_modulus(cur_n, new_n); break;
+ }
+
+ if (curval.type == kOptValTypeNumber) {
+ newval = NUMBER_OPTVAL(new_n);
+ } else {
+ newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n));
+ }
+ } else if (!hidden && is_string
+ && curval.data.string.data != NULL && newval.data.string.data != NULL) { // string
+ OptVal newval_old = newval;
+ newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
+ optval_free(newval_old);
+ }
+ }
+
+ const char *err = set_option_value(arg, newval, scope);
+ arg_end = p;
+ if (err != NULL) {
+ emsg(_(err));
+ }
+
+theend:
+ *p = c1;
+ optval_free(curval);
+ optval_free(newval);
+ return arg_end;
+}
+
+/// Set a register, part of ex_let_one().
+static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock a register"));
+ return NULL;
+ }
+
+ char *arg_end = NULL;
+ arg++;
+ if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ semsg(_(e_letwrong), op);
+ } else if (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
+ emsg(_(e_letunexp));
+ } else {
+ char *ptofree = NULL;
+ const char *p = tv_get_string_chk(tv);
+ if (p != NULL && op != NULL && *op == '.') {
+ char *s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
+ if (s != NULL) {
+ ptofree = concat_str(s, p);
+ p = ptofree;
+ xfree(s);
+ }
+ }
+ if (p != NULL) {
+ write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
+ arg_end = arg + 1;
+ }
+ xfree(ptofree);
+ }
+ return arg_end;
+}
+
/// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value
///
/// @param[in] arg Start of the variable name.
@@ -575,176 +898,22 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
{
char *arg_end = NULL;
- int len;
- // ":let $VAR = expr": Set environment variable.
if (*arg == '$') {
- if (is_const) {
- emsg(_("E996: Cannot lock an environment variable"));
- return NULL;
- }
- // Find the end of the name.
- arg++;
- char *name = arg;
- len = get_env_len((const char **)&arg);
- if (len == 0) {
- semsg(_(e_invarg2), name - 1);
- } else {
- if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- semsg(_(e_letwrong), op);
- } else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) {
- emsg(_(e_letunexp));
- } else if (!check_secure()) {
- char *tofree = NULL;
- const char c1 = name[len];
- name[len] = NUL;
- const char *p = tv_get_string_chk(tv);
- if (p != NULL && op != NULL && *op == '.') {
- char *s = vim_getenv(name);
- if (s != NULL) {
- tofree = concat_str(s, p);
- p = (const char *)tofree;
- xfree(s);
- }
- }
- if (p != NULL) {
- vim_setenv_ext(name, p);
- arg_end = arg;
- }
- name[len] = c1;
- xfree(tofree);
- }
- }
+ // ":let $VAR = expr": Set environment variable.
+ return ex_let_env(arg, tv, is_const, endchars, op);
+ } else if (*arg == '&') {
// ":let &option = expr": Set option value.
// ":let &l:option = expr": Set local option value.
// ":let &g:option = expr": Set global option value.
- } else if (*arg == '&') {
- if (is_const) {
- emsg(_("E996: Cannot lock an option"));
- return NULL;
- }
- // Find the end of the name.
- int scope;
- char *const p = (char *)find_option_end((const char **)&arg, &scope);
- if (p == NULL
- || (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
- emsg(_(e_letunexp));
- } else {
- varnumber_T n = 0;
- getoption_T opt_type;
- long numval;
- char *stringval = NULL;
- const char *s = NULL;
- bool failed = false;
- uint32_t opt_p_flags;
- char *tofree = NULL;
-
- const char c1 = *p;
- *p = NUL;
-
- opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope);
- if (opt_type == gov_bool
- || opt_type == gov_number
- || opt_type == gov_hidden_bool
- || opt_type == gov_hidden_number) {
- // number, possibly hidden
- n = (long)tv_get_number(tv);
- }
-
- if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) {
- // If the option can be set to a function reference or a lambda
- // and the passed value is a function reference, then convert it to
- // the name (string) of the function reference.
- s = tofree = encode_tv2string(tv, NULL);
- } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
- // Avoid setting a string option to the text "v:false" or similar.
- s = tv_get_string_chk(tv);
- }
-
- if (op != NULL && *op != '=') {
- if (((opt_type == gov_bool || opt_type == gov_number) && *op == '.')
- || (opt_type == gov_string && *op != '.')) {
- semsg(_(e_letwrong), op);
- failed = true; // don't set the value
- } else {
- // number or bool
- if (opt_type == gov_number || opt_type == gov_bool) {
- switch (*op) {
- case '+':
- n = numval + n; break;
- case '-':
- n = numval - n; break;
- case '*':
- n = numval * n; break;
- case '/':
- n = num_divide(numval, n); break;
- case '%':
- n = num_modulus(numval, n); break;
- }
- s = NULL;
- } else if (opt_type == gov_string && stringval != NULL && s != NULL) {
- // string
- char *const oldstringval = stringval;
- stringval = concat_str(stringval, s);
- xfree(oldstringval);
- s = stringval;
- }
- }
- }
-
- if (!failed) {
- if (opt_type != gov_string || s != NULL) {
- char *err = set_option_value(arg, n, s, scope);
- arg_end = p;
- if (err != NULL) {
- emsg(_(err));
- }
- } else {
- emsg(_(e_stringreq));
- }
- }
- *p = c1;
- xfree(stringval);
- xfree(tofree);
- }
- // ":let @r = expr": Set register contents.
+ return ex_let_option(arg, tv, is_const, endchars, op);
} else if (*arg == '@') {
- if (is_const) {
- emsg(_("E996: Cannot lock a register"));
- return NULL;
- }
- arg++;
- if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- semsg(_(e_letwrong), op);
- } else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
- emsg(_(e_letunexp));
- } else {
- char *s;
-
- char *ptofree = NULL;
- const char *p = tv_get_string_chk(tv);
- if (p != NULL && op != NULL && *op == '.') {
- s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
- if (s != NULL) {
- ptofree = concat_str(s, p);
- p = (const char *)ptofree;
- xfree(s);
- }
- }
- if (p != NULL) {
- write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
- arg_end = arg + 1;
- }
- xfree(ptofree);
- }
+ // ":let @r = expr": Set register contents.
+ return ex_let_register(arg, tv, is_const, endchars, op);
+ } else if (eval_isnamec1(*arg) || *arg == '{') {
// ":let var = expr": Set internal variable.
// ":let {expr} = expr": Idem, name made with curly braces
- } else if (eval_isnamec1(*arg) || *arg == '{') {
lval_T lv;
-
char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START);
if (p != NULL && lv.ll_name != NULL) {
if (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL) {
@@ -803,7 +972,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca
do {
if (*arg == '$') {
- lv.ll_name = (const char *)arg;
+ lv.ll_name = arg;
lv.ll_tv = NULL;
arg++;
if (get_env_len((const char **)&arg) == 0) {
@@ -861,10 +1030,9 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_
{
int forceit = eap->forceit;
int ret = OK;
- int cc;
if (lp->ll_tv == NULL) {
- cc = (uint8_t)(*name_end);
+ int cc = (uint8_t)(*name_end);
*name_end = NUL;
// Environment variable, normal name or expanded name.
@@ -886,57 +1054,57 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_
lp->ll_name_len))) {
return FAIL;
} else if (lp->ll_range) {
- assert(lp->ll_list != NULL);
- // Delete a range of List items.
- listitem_T *const first_li = lp->ll_li;
- listitem_T *last_li = first_li;
- for (;;) {
- listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
- if (value_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock,
- lp->ll_name,
- lp->ll_name_len)) {
- return false;
- }
- lp->ll_li = li;
- lp->ll_n1++;
- if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) {
- break;
- }
- last_li = lp->ll_li;
- }
- tv_list_remove_items(lp->ll_list, first_li, last_li);
+ tv_list_unlet_range(lp->ll_list, lp->ll_li, lp->ll_n1, !lp->ll_empty2, lp->ll_n2);
+ } else if (lp->ll_list != NULL) {
+ // unlet a List item.
+ tv_list_item_remove(lp->ll_list, lp->ll_li);
} else {
- if (lp->ll_list != NULL) {
- // unlet a List item.
- tv_list_item_remove(lp->ll_list, lp->ll_li);
- } else {
- // unlet a Dictionary item.
- dict_T *d = lp->ll_dict;
- assert(d != NULL);
- dictitem_T *di = lp->ll_di;
- bool watched = tv_dict_is_watched(d);
- char *key = NULL;
- typval_T oldtv;
+ // unlet a Dictionary item.
+ dict_T *d = lp->ll_dict;
+ assert(d != NULL);
+ dictitem_T *di = lp->ll_di;
+ bool watched = tv_dict_is_watched(d);
+ char *key = NULL;
+ typval_T oldtv;
- if (watched) {
- tv_copy(&di->di_tv, &oldtv);
- // need to save key because dictitem_remove will free it
- key = xstrdup((char *)di->di_key);
- }
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ // need to save key because dictitem_remove will free it
+ key = xstrdup(di->di_key);
+ }
- tv_dict_item_remove(d, di);
+ tv_dict_item_remove(d, di);
- if (watched) {
- tv_dict_watcher_notify(d, key, NULL, &oldtv);
- tv_clear(&oldtv);
- xfree(key);
- }
+ if (watched) {
+ tv_dict_watcher_notify(d, key, NULL, &oldtv);
+ tv_clear(&oldtv);
+ xfree(key);
}
}
return ret;
}
+/// Unlet one item or a range of items from a list.
+/// Return OK or FAIL.
+static void tv_list_unlet_range(list_T *const l, listitem_T *const li_first, const int n1_arg,
+ const bool has_n2, const int n2)
+{
+ assert(l != NULL);
+ // Delete a range of List items.
+ listitem_T *li_last = li_first;
+ int n1 = n1_arg;
+ while (true) {
+ listitem_T *const li = TV_LIST_ITEM_NEXT(l, li_last);
+ n1++;
+ if (li == NULL || (has_n2 && n2 < n1)) {
+ break;
+ }
+ li_last = li;
+ }
+ tv_list_remove_items(l, li_first, li_last);
+}
+
/// unlet a variable
///
/// @param[in] name Variable name to unlet.
@@ -1081,8 +1249,8 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap
/// @param dip non-NULL when typval's dict item is needed
/// @param verbose may give error message
/// @param no_autoload do not use script autoloading
-int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose,
- bool no_autoload)
+int eval_variable(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose,
+ bool no_autoload)
{
int ret = OK;
typval_T *tv = NULL;
@@ -1157,7 +1325,7 @@ void vars_clear_ext(hashtab_T *ht, int free_val)
}
}
hash_clear(ht);
- ht->ht_used = 0;
+ hash_init(ht);
}
/// Delete a variable from hashtab "ht" at item "hi".
@@ -1175,7 +1343,7 @@ void delete_var(hashtab_T *ht, hashitem_T *hi)
static void list_one_var(dictitem_T *v, const char *prefix, int *first)
{
char *const s = encode_tv2echo(&v->di_tv, NULL);
- list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)strlen((char *)v->di_key),
+ list_one_var_a(prefix, v->di_key, (ptrdiff_t)strlen(v->di_key),
v->di_tv.v_type, (s == NULL ? "" : s), first);
xfree(s);
}
@@ -1186,11 +1354,11 @@ static void list_one_var(dictitem_T *v, const char *prefix, int *first)
static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len,
const VarType type, const char *string, int *first)
{
- // don't use msg() or msg_attr() to avoid overwriting "v:statusmsg"
+ // don't use msg() to avoid overwriting "v:statusmsg"
msg_start();
msg_puts(prefix);
if (name != NULL) { // "a:" vars don't have a name stored
- msg_puts_attr_len(name, name_len, 0);
+ msg_puts_len(name, name_len, 0);
}
msg_putchar(' ');
msg_advance(22);
@@ -1212,7 +1380,7 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t
msg_putchar(' ');
}
- msg_outtrans((char *)string);
+ msg_outtrans(string, 0);
if (type == VAR_FUNC || type == VAR_PARTIAL) {
msg_puts("()");
@@ -1223,6 +1391,62 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t
}
}
+/// Additional handling for setting a v: variable.
+///
+/// @return true if the variable should be set normally,
+/// false if nothing else needs to be done.
+bool before_set_vvar(const char *const varname, dictitem_T *const di, typval_T *const tv,
+ const bool copy, const bool watched, bool *const type_error)
+{
+ if (di->di_tv.v_type == VAR_STRING) {
+ typval_T oldtv = TV_INITIAL_VALUE;
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
+ XFREE_CLEAR(di->di_tv.vval.v_string);
+ if (copy || tv->v_type != VAR_STRING) {
+ const char *const val = tv_get_string(tv);
+ // Careful: when assigning to v:errmsg and tv_get_string()
+ // causes an error message the variable will already be set.
+ if (di->di_tv.vval.v_string == NULL) {
+ di->di_tv.vval.v_string = xstrdup(val);
+ }
+ } else {
+ // Take over the string to avoid an extra alloc/free.
+ di->di_tv.vval.v_string = tv->vval.v_string;
+ tv->vval.v_string = NULL;
+ }
+ // Notify watchers
+ if (watched) {
+ tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
+ tv_clear(&oldtv);
+ }
+ return false;
+ } else if (di->di_tv.v_type == VAR_NUMBER) {
+ typval_T oldtv = TV_INITIAL_VALUE;
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
+ di->di_tv.vval.v_number = tv_get_number(tv);
+ if (strcmp(varname, "searchforward") == 0) {
+ set_search_direction(di->di_tv.vval.v_number ? '/' : '?');
+ } else if (strcmp(varname, "hlsearch") == 0) {
+ no_hlsearch = !di->di_tv.vval.v_number;
+ redraw_all_later(UPD_SOME_VALID);
+ }
+ // Notify watchers
+ if (watched) {
+ tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
+ tv_clear(&oldtv);
+ }
+ return false;
+ } else if (di->di_tv.v_type != tv->v_type) {
+ *type_error = true;
+ return false;
+ }
+ return true;
+}
+
/// Set variable to the given value
///
/// If the variable already exists, the value is updated. Otherwise the variable
@@ -1252,31 +1476,29 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
const bool is_const)
FUNC_ATTR_NONNULL_ALL
{
- dictitem_T *v;
- hashtab_T *ht;
- dict_T *dict;
-
const char *varname;
- ht = find_var_ht_dict(name, name_len, &varname, &dict);
+ dict_T *dict;
+ hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict);
const bool watched = tv_dict_is_watched(dict);
if (ht == NULL || *varname == NUL) {
semsg(_(e_illvar), name);
return;
}
- v = find_var_in_ht(ht, 0, varname, name_len - (size_t)(varname - name), true);
+ const size_t varname_len = name_len - (size_t)(varname - name);
+ dictitem_T *di = find_var_in_ht(ht, 0, varname, varname_len, true);
// Search in parent scope which is possible to reference from lambda
- if (v == NULL) {
- v = find_var_in_scoped_ht(name, name_len, true);
+ if (di == NULL) {
+ di = find_var_in_scoped_ht(name, name_len, true);
}
- if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) {
+ if (tv_is_func(*tv) && var_wrong_func_name(name, di == NULL)) {
return;
}
typval_T oldtv = TV_INITIAL_VALUE;
- if (v != NULL) {
+ if (di != NULL) {
if (is_const) {
emsg(_(e_cannot_mod));
return;
@@ -1286,9 +1508,9 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// - Whether the variable is read-only
// - Whether the variable value is locked
// - Whether the variable is locked
- if (var_check_ro(v->di_flags, name, name_len)
- || value_check_lock(v->di_tv.v_lock, name, name_len)
- || var_check_lock(v->di_flags, name, name_len)) {
+ if (var_check_ro(di->di_flags, name, name_len)
+ || value_check_lock(di->di_tv.v_lock, name, name_len)
+ || var_check_lock(di->di_flags, name, name_len)) {
return;
}
@@ -1296,42 +1518,19 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Handle setting internal v: variables separately where needed to
// prevent changing the type.
- if (is_vimvarht(ht)) {
- if (v->di_tv.v_type == VAR_STRING) {
- XFREE_CLEAR(v->di_tv.vval.v_string);
- if (copy || tv->v_type != VAR_STRING) {
- const char *const val = tv_get_string(tv);
-
- // Careful: when assigning to v:errmsg and tv_get_string()
- // causes an error message the variable will already be set.
- if (v->di_tv.vval.v_string == NULL) {
- v->di_tv.vval.v_string = xstrdup(val);
- }
- } else {
- // Take over the string to avoid an extra alloc/free.
- v->di_tv.vval.v_string = tv->vval.v_string;
- tv->vval.v_string = NULL;
- }
- return;
- } else if (v->di_tv.v_type == VAR_NUMBER) {
- v->di_tv.vval.v_number = tv_get_number(tv);
- if (strcmp(varname, "searchforward") == 0) {
- set_search_direction(v->di_tv.vval.v_number ? '/' : '?');
- } else if (strcmp(varname, "hlsearch") == 0) {
- no_hlsearch = !v->di_tv.vval.v_number;
- redraw_all_later(UPD_SOME_VALID);
- }
- return;
- } else if (v->di_tv.v_type != tv->v_type) {
- semsg(_("E963: setting %s to value with wrong type"), name);
- return;
+ bool type_error = false;
+ if (is_vimvarht(ht)
+ && !before_set_vvar(varname, di, tv, copy, watched, &type_error)) {
+ if (type_error) {
+ semsg(_(e_setting_v_str_to_value_with_wrong_type), varname);
}
+ return;
}
if (watched) {
- tv_copy(&v->di_tv, &oldtv);
+ tv_copy(&di->di_tv, &oldtv);
}
- tv_clear(&v->di_tv);
+ tv_clear(&di->di_tv);
} else { // Add a new variable.
// Can't add "v:" or "a:" variable.
if (is_vimvarht(ht) || ht == get_funccal_args_ht()) {
@@ -1347,28 +1546,28 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Make sure dict is valid
assert(dict != NULL);
- v = xmalloc(sizeof(dictitem_T) + strlen(varname));
- STRCPY(v->di_key, varname);
- if (hash_add(ht, (char *)v->di_key) == FAIL) {
- xfree(v);
+ di = xmalloc(offsetof(dictitem_T, di_key) + varname_len + 1);
+ memcpy(di->di_key, varname, varname_len + 1);
+ if (hash_add(ht, di->di_key) == FAIL) {
+ xfree(di);
return;
}
- v->di_flags = DI_FLAGS_ALLOC;
+ di->di_flags = DI_FLAGS_ALLOC;
if (is_const) {
- v->di_flags |= DI_FLAGS_LOCK;
+ di->di_flags |= DI_FLAGS_LOCK;
}
}
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
- tv_copy(tv, &v->di_tv);
+ tv_copy(tv, &di->di_tv);
} else {
- v->di_tv = *tv;
- v->di_tv.v_lock = VAR_UNLOCKED;
+ di->di_tv = *tv;
+ di->di_tv.v_lock = VAR_UNLOCKED;
tv_init(tv);
}
if (watched) {
- tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv);
+ tv_dict_watcher_notify(dict, di->di_key, &di->di_tv, &oldtv);
tv_clear(&oldtv);
}
@@ -1376,7 +1575,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Like :lockvar! name: lock the value and what it contains, but only
// if the reference count is up to one. That locks only literal
// values.
- tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true);
+ tv_item_lock(&di->di_tv, DICT_MAXNEST, true, true);
}
}
@@ -1569,7 +1768,7 @@ static void get_var_from(const char *varname, typval_T *rettv, typval_T *deftv,
tv_dict_set_ret(rettv, opts);
done = true;
}
- } else if (get_option_tv(&varname, rettv, true) == OK) {
+ } else if (eval_option(&varname, rettv, true) == OK) {
// Local option
done = true;
}
@@ -1639,28 +1838,121 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off)
get_var_from(varname, rettv, &argvars[off + 2], 'w', tp, win, NULL);
}
-/// Set option "varname" to the value of "varp" for the current buffer/window.
-static void set_option_from_tv(const char *varname, typval_T *varp)
+/// Convert typval to option value for a particular option.
+///
+/// @param[in] tv typval to convert.
+/// @param[in] option Option name.
+/// @param[in] flags Option flags.
+/// @param[out] error Whether an error occurred.
+///
+/// @return Typval converted to OptVal. Must be freed by caller.
+/// Returns NIL_OPTVAL for invalid option name.
+static OptVal tv_to_optval(typval_T *tv, const char *option, uint32_t flags, bool *error)
{
- long numval = 0;
- const char *strval;
- bool error = false;
+ OptVal value = NIL_OPTVAL;
char nbuf[NUMBUFLEN];
-
- if (varp->v_type == VAR_BOOL) {
- if (is_string_option(varname)) {
+ bool err = false;
+
+ if ((flags & P_FUNC) && tv_is_func(*tv)) {
+ // If the option can be set to a function reference or a lambda
+ // and the passed value is a function reference, then convert it to
+ // the name (string) of the function reference.
+ char *strval = encode_tv2string(tv, NULL);
+ err = strval == NULL;
+ value = CSTR_AS_OPTVAL(strval);
+ } else if (flags & (P_NUM | P_BOOL)) {
+ varnumber_T n = (flags & P_NUM) ? tv_get_number_chk(tv, &err)
+ : tv_get_bool_chk(tv, &err);
+ // This could be either "0" or a string that's not a number.
+ // So we need to check if it's actually a number.
+ if (!err && tv->v_type == VAR_STRING && n == 0) {
+ unsigned idx;
+ for (idx = 0; tv->vval.v_string[idx] == '0'; idx++) {}
+ if (tv->vval.v_string[idx] != NUL || idx == 0) {
+ // There's another character after zeros or the string is empty.
+ // In both cases, we are trying to set a num option using a string.
+ err = true;
+ semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string);
+ }
+ }
+ value = (flags & P_NUM) ? NUMBER_OPTVAL((OptInt)n) : BOOLEAN_OPTVAL(TRISTATE_FROM_INT(n));
+ } else if ((flags & P_STRING) || is_tty_option(option)) {
+ // Avoid setting string option to a boolean or a special value.
+ if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
+ const char *strval = tv_get_string_buf_chk(tv, nbuf);
+ err = strval == NULL;
+ value = CSTR_TO_OPTVAL(strval);
+ } else if (flags & P_STRING) {
+ err = true;
emsg(_(e_stringreq));
- return;
}
- numval = (long)varp->vval.v_number;
- strval = "0"; // avoid using "false"
} else {
- numval = (long)tv_get_number_chk(varp, &error);
- strval = tv_get_string_buf_chk(varp, nbuf);
+ abort(); // This should never happen.
+ }
+
+ if (error != NULL) {
+ *error = err;
}
- if (!error && strval != NULL) {
- set_option_value_give_err(varname, numval, strval, OPT_LOCAL);
+ return value;
+}
+
+/// Convert an option value to typval.
+///
+/// @param[in] value Option value to convert.
+///
+/// @return OptVal converted to typval.
+typval_T optval_as_tv(OptVal value)
+{
+ typval_T rettv = { .v_type = VAR_SPECIAL, .vval = { .v_special = kSpecialVarNull } };
+
+ switch (value.type) {
+ case kOptValTypeNil:
+ break;
+ case kOptValTypeBoolean:
+ switch (value.data.boolean) {
+ case kTrue:
+ rettv.v_type = VAR_BOOL;
+ rettv.vval.v_bool = kBoolVarTrue;
+ break;
+ case kFalse:
+ rettv.v_type = VAR_BOOL;
+ rettv.vval.v_bool = kBoolVarFalse;
+ break;
+ case kNone:
+ break; // return v:null for None boolean value
+ }
+ break;
+ case kOptValTypeNumber:
+ rettv.v_type = VAR_NUMBER;
+ rettv.vval.v_number = value.data.number;
+ break;
+ case kOptValTypeString:
+ rettv.v_type = VAR_STRING;
+ rettv.vval.v_string = value.data.string.data;
+ break;
+ }
+
+ return rettv;
+}
+
+/// Set option "varname" to the value of "varp" for the current buffer/window.
+static void set_option_from_tv(const char *varname, typval_T *varp)
+{
+ int opt_idx = findoption(varname);
+ if (opt_idx < 0) {
+ semsg(_(e_unknown_option2), varname);
+ return;
}
+ uint32_t opt_p_flags = get_option(opt_idx)->flags;
+
+ bool error = false;
+ OptVal value = tv_to_optval(varp, varname, opt_p_flags, &error);
+
+ if (!error) {
+ set_option_value_give_err(varname, value, OPT_LOCAL);
+ }
+
+ optval_free(value);
}
/// "setwinvar()" and "settabwinvar()" functions
@@ -1718,10 +2010,10 @@ bool var_exists(const char *var)
if (tofree != NULL) {
name = tofree;
}
- n = get_var_tv(name, len, &tv, NULL, false, true) == OK;
+ n = eval_variable(name, len, &tv, NULL, false, true) == OK;
if (n) {
// Handle d.key, l[idx], f(expr).
- n = handle_subscript(&var, &tv, true, false, name, &name) == OK;
+ n = handle_subscript(&var, &tv, &EVALARG_EVALUATE, false) == OK;
if (n) {
tv_clear(&tv);
}
diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h
index b87c9d62cb..6ddf449ff1 100644
--- a/src/nvim/eval/vars.h
+++ b/src/nvim/eval/vars.h
@@ -1,9 +1,13 @@
-#ifndef NVIM_EVAL_VARS_H
-#define NVIM_EVAL_VARS_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
+#include <stddef.h> // IWYU pragma: keep
+
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/hashtab_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/vars.h.generated.h"
#endif
-#endif // NVIM_EVAL_VARS_H
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
index f58a0c488a..e0abbad477 100644
--- a/src/nvim/eval/window.c
+++ b/src/nvim/eval/window.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval/window.c: Window related builtin functions
#include <stdbool.h>
@@ -9,7 +6,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -21,23 +18,22 @@
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
-#include "nvim/memline_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/window.c.generated.h"
#endif
-static char *e_invalwindow = N_("E957: Invalid window number");
-static char e_cannot_resize_window_in_another_tab_page[]
+static const char *e_invalwindow = N_("E957: Invalid window number");
+static const char e_cannot_resize_window_in_another_tab_page[]
= N_("E1308: Cannot resize a window in another tab page");
static int win_getid(typval_T *argvars)
@@ -98,6 +94,7 @@ win_T *win_id2wp(int id)
}
/// Return the window and tab pointer of window "id".
+/// Returns NULL when not found.
win_T *win_id2wp_tp(int id, tabpage_T **tpp)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
@@ -192,9 +189,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
if (wvp->v_type != VAR_UNKNOWN) {
if (tvp->v_type != VAR_UNKNOWN) {
- long n = tv_get_number(tvp);
+ int n = (int)tv_get_number(tvp);
if (n >= 0) {
- tp = find_tabpage((int)n);
+ tp = find_tabpage(n);
}
} else {
tp = curtab;
@@ -265,7 +262,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
} else {
// Extract the window count (if specified). e.g. winnr('3j')
char *endp;
- long count = strtol((char *)arg, &endp, 10);
+ int count = (int)strtol(arg, &endp, 10);
if (count <= 0) {
// if count is not specified, default to 1
count = 1;
@@ -639,7 +636,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (wp == NULL || targetwin == NULL || wp == targetwin
|| !win_valid(wp) || !win_valid(targetwin)
- || win_valid_floating(wp) || win_valid_floating(targetwin)) {
+ || win_float_valid(wp) || win_float_valid(targetwin)) {
emsg(_(e_invalwindow));
rettv->vval.v_number = -1;
return;
@@ -651,8 +648,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
dict_T *d;
dictitem_T *di;
- if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
- emsg(_(e_invarg));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
@@ -796,51 +792,50 @@ void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "winrestview()" function
void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- dict_T *dict = argvars[0].vval.v_dict;
+ if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) {
+ return;
+ }
- if (argvars[0].v_type != VAR_DICT || dict == NULL) {
- emsg(_(e_invarg));
- } else {
- dictitem_T *di;
- if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
- curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
- curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
- curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
- curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv);
- curwin->w_set_curswant = false;
- }
- if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
- set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv));
- }
- if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
- curwin->w_topfill = (int)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
- curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
- curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv);
- }
+ dict_T *dict = argvars[0].vval.v_dict;
+ dictitem_T *di;
+ if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
+ curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
+ curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
+ curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
+ curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv);
+ curwin->w_set_curswant = false;
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
+ set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv));
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
+ curwin->w_topfill = (int)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
+ curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
+ curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv);
+ }
- check_cursor();
- win_new_height(curwin, curwin->w_height);
- win_new_width(curwin, curwin->w_width);
- changed_window_setting();
+ check_cursor();
+ win_new_height(curwin, curwin->w_height);
+ win_new_width(curwin, curwin->w_width);
+ changed_window_setting();
- if (curwin->w_topline <= 0) {
- curwin->w_topline = 1;
- }
- if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
- curwin->w_topline = curbuf->b_ml.ml_line_count;
- }
- check_topfill(curwin, true);
+ if (curwin->w_topline <= 0) {
+ curwin->w_topline = 1;
+ }
+ if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
+ curwin->w_topline = curbuf->b_ml.ml_line_count;
}
+ check_topfill(curwin, true);
}
/// "winsaveview()" function
diff --git a/src/nvim/eval/window.h b/src/nvim/eval/window.h
index 995f0a55a9..ed879c895a 100644
--- a/src/nvim/eval/window.h
+++ b/src/nvim/eval/window.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVAL_WINDOW_H
-#define NVIM_EVAL_WINDOW_H
+#pragma once
#include <stdbool.h>
#include <string.h>
@@ -11,9 +10,10 @@
#include "nvim/globals.h"
#include "nvim/mark.h"
#include "nvim/option_defs.h"
-#include "nvim/os/os.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// Structure used by switch_win() to pass values to restore_win()
@@ -75,4 +75,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/window.h.generated.h"
#endif
-#endif // NVIM_EVAL_WINDOW_H
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
index cf079681d0..571f61dfdb 100644
--- a/src/nvim/event/defs.h
+++ b/src/nvim/event/defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_DEFS_H
-#define NVIM_EVENT_DEFS_H
+#pragma once
#include <assert.h>
#include <stdarg.h>
@@ -34,5 +33,3 @@ static inline Event event_create(argv_callback cb, int argc, ...)
VA_EVENT_INIT(&event, cb, argc);
return event;
}
-
-#endif // NVIM_EVENT_DEFS_H
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index e528d21a71..be48b39af1 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -1,17 +1,14 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <locale.h>
#include <stdint.h>
#include <uv.h>
#include "nvim/eval/typval.h"
#include "nvim/event/libuv_process.h"
-#include "nvim/event/loop.h"
#include "nvim/event/process.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/os/os.h"
#include "nvim/ui_client.h"
@@ -24,7 +21,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
FUNC_ATTR_NONNULL_ALL
{
Process *proc = (Process *)uvproc;
- uvproc->uvopts.file = proc->argv[0];
+ uvproc->uvopts.file = process_get_exepath(proc);
uvproc->uvopts.args = proc->argv;
uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
#ifdef MSWIN
@@ -68,25 +65,22 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#ifdef MSWIN
uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0;
#endif
- uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t,
- &proc->in.uv.pipe);
+ uvproc->uvstdio[0].data.stream = (uv_stream_t *)(&proc->in.uv.pipe);
}
if (!proc->out.closed) {
uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
#ifdef MSWIN
// pipe must be readable for IOCP to work on Windows.
- uvproc->uvstdio[1].flags |= proc->overlapped ?
- (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
+ uvproc->uvstdio[1].flags |= proc->overlapped
+ ? (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
#endif
- uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t,
- &proc->out.uv.pipe);
+ uvproc->uvstdio[1].data.stream = (uv_stream_t *)(&proc->out.uv.pipe);
}
if (!proc->err.closed) {
uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
- uvproc->uvstdio[2].data.stream = STRUCT_CAST(uv_stream_t,
- &proc->err.uv.pipe);
+ uvproc->uvstdio[2].data.stream = (uv_stream_t *)(&proc->err.uv.pipe);
} else if (proc->fwd_err) {
uvproc->uvstdio[2].flags = UV_INHERIT_FD;
uvproc->uvstdio[2].data.fd = STDERR_FILENO;
diff --git a/src/nvim/event/libuv_process.h b/src/nvim/event/libuv_process.h
index 4472839944..e3e2bfeb76 100644
--- a/src/nvim/event/libuv_process.h
+++ b/src/nvim/event/libuv_process.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_LIBUV_PROCESS_H
-#define NVIM_EVENT_LIBUV_PROCESS_H
+#pragma once
#include <uv.h>
@@ -24,4 +23,3 @@ static inline LibuvProcess libuv_process_init(Loop *loop, void *data)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/libuv_process.h.generated.h"
#endif
-#endif // NVIM_EVENT_LIBUV_PROCESS_H
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index ab2524c1a9..d61666e6d4 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
@@ -20,6 +17,7 @@ void loop_init(Loop *loop, void *data)
{
uv_loop_init(&loop->uv);
loop->recursive = 0;
+ loop->closing = false;
loop->uv.data = loop;
loop->children = kl_init(WatcherPtr);
loop->events = multiqueue_new_parent(loop_on_put, loop);
@@ -61,9 +59,9 @@ bool loop_uv_run(Loop *loop, int64_t ms, bool once)
mode = UV_RUN_NOWAIT;
}
- do { // -V1044
+ do {
uv_run(&loop->uv, mode);
- } while (ms > 0 && !once && !*timeout_expired); // -V560
+ } while (ms > 0 && !once && !*timeout_expired);
if (ms > 0) {
uv_timer_stop(&loop->poll_timer);
@@ -152,6 +150,7 @@ static void loop_walk_cb(uv_handle_t *handle, void *arg)
bool loop_close(Loop *loop, bool wait)
{
bool rv = true;
+ loop->closing = true;
uv_mutex_destroy(&loop->mutex);
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
@@ -163,7 +162,7 @@ bool loop_close(Loop *loop, bool wait)
while (true) {
// Run the loop to tickle close-callbacks (which should then free memory).
// Use UV_RUN_NOWAIT to avoid a hang. #11820
- uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT); // -V547
+ uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) {
break;
}
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index b2265a726d..5665332e95 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -1,6 +1,6 @@
-#ifndef NVIM_EVENT_LOOP_H
-#define NVIM_EVENT_LOOP_H
+#pragma once
+#include <stdbool.h>
#include <stdint.h>
#include <uv.h>
@@ -41,6 +41,7 @@ typedef struct loop {
uv_async_t async;
uv_mutex_t mutex;
int recursive;
+ bool closing; ///< Set to true if loop_close() has been called
} Loop;
#define CREATE_EVENT(multiqueue, handler, argc, ...) \
@@ -53,8 +54,6 @@ typedef struct loop {
} \
} while (0)
-// -V:LOOP_PROCESS_EVENTS_UNTIL:547
-
// Poll for events until a condition or timeout
#define LOOP_PROCESS_EVENTS_UNTIL(loop, multiqueue, timeout, condition) \
do { \
@@ -87,5 +86,3 @@ typedef struct loop {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/loop.h.generated.h"
#endif
-
-#endif // NVIM_EVENT_LOOP_H
diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c
index e05084b656..3ab41bd299 100644
--- a/src/nvim/event/multiqueue.c
+++ b/src/nvim/event/multiqueue.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Multi-level queue for selective async event processing.
// Not threadsafe; access must be synchronized externally.
//
@@ -40,7 +37,7 @@
//
// The main reason for this queue hierarchy is to allow focusing on a single
// event emitter while blocking the main loop. For example, if the `jobwait`
-// VimL function is called on job1, the main loop will temporarily stop polling
+// Vimscript function is called on job1, the main loop will temporarily stop polling
// the event loop queue and poll job1 queue instead. Same with channels, when
// calling `rpcrequest` we want to temporarily stop processing events from
// other sources and focus on a specific channel.
@@ -48,10 +45,10 @@
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
-#include <uv.h>
#include "nvim/event/defs.h"
#include "nvim/event/multiqueue.h"
+#include "nvim/func_attr.h"
#include "nvim/lib/queue.h"
#include "nvim/memory.h"
@@ -112,13 +109,13 @@ static MultiQueue *multiqueue_new(MultiQueue *parent, PutCallback put_cb, void *
return rv;
}
-void multiqueue_free(MultiQueue *this)
+void multiqueue_free(MultiQueue *self)
{
- assert(this);
+ assert(self);
QUEUE *q;
- QUEUE_FOREACH(q, &this->headtail, {
+ QUEUE_FOREACH(q, &self->headtail, {
MultiQueueItem *item = multiqueue_node_data(q);
- if (this->parent) {
+ if (self->parent) {
QUEUE_REMOVE(&item->data.item.parent_item->node);
xfree(item->data.item.parent_item);
}
@@ -126,29 +123,29 @@ void multiqueue_free(MultiQueue *this)
xfree(item);
})
- xfree(this);
+ xfree(self);
}
/// Removes the next item and returns its Event.
-Event multiqueue_get(MultiQueue *this)
+Event multiqueue_get(MultiQueue *self)
{
- return multiqueue_empty(this) ? NILEVENT : multiqueue_remove(this);
+ return multiqueue_empty(self) ? NILEVENT : multiqueue_remove(self);
}
-void multiqueue_put_event(MultiQueue *this, Event event)
+void multiqueue_put_event(MultiQueue *self, Event event)
{
- assert(this);
- multiqueue_push(this, event);
- if (this->parent && this->parent->put_cb) {
- this->parent->put_cb(this->parent, this->parent->data);
+ assert(self);
+ multiqueue_push(self, event);
+ if (self->parent && self->parent->put_cb) {
+ self->parent->put_cb(self->parent, self->parent->data);
}
}
-void multiqueue_process_events(MultiQueue *this)
+void multiqueue_process_events(MultiQueue *self)
{
- assert(this);
- while (!multiqueue_empty(this)) {
- Event event = multiqueue_remove(this);
+ assert(self);
+ while (!multiqueue_empty(self)) {
+ Event event = multiqueue_remove(self);
if (event.handler) {
event.handler(event.argv);
}
@@ -156,30 +153,30 @@ void multiqueue_process_events(MultiQueue *this)
}
/// Removes all events without processing them.
-void multiqueue_purge_events(MultiQueue *this)
+void multiqueue_purge_events(MultiQueue *self)
{
- assert(this);
- while (!multiqueue_empty(this)) {
- (void)multiqueue_remove(this);
+ assert(self);
+ while (!multiqueue_empty(self)) {
+ (void)multiqueue_remove(self);
}
}
-bool multiqueue_empty(MultiQueue *this)
+bool multiqueue_empty(MultiQueue *self)
{
- assert(this);
- return QUEUE_EMPTY(&this->headtail);
+ assert(self);
+ return QUEUE_EMPTY(&self->headtail);
}
-void multiqueue_replace_parent(MultiQueue *this, MultiQueue *new_parent)
+void multiqueue_replace_parent(MultiQueue *self, MultiQueue *new_parent)
{
- assert(multiqueue_empty(this));
- this->parent = new_parent;
+ assert(multiqueue_empty(self));
+ self->parent = new_parent;
}
/// Gets the count of all events currently in the queue.
-size_t multiqueue_size(MultiQueue *this)
+size_t multiqueue_size(MultiQueue *self)
{
- return this->size;
+ return self->size;
}
/// Gets an Event from an item.
@@ -213,38 +210,39 @@ static Event multiqueueitem_get_event(MultiQueueItem *item, bool remove)
return ev;
}
-static Event multiqueue_remove(MultiQueue *this)
+static Event multiqueue_remove(MultiQueue *self)
{
- assert(!multiqueue_empty(this));
- QUEUE *h = QUEUE_HEAD(&this->headtail);
+ assert(!multiqueue_empty(self));
+ QUEUE *h = QUEUE_HEAD(&self->headtail);
QUEUE_REMOVE(h);
MultiQueueItem *item = multiqueue_node_data(h);
- assert(!item->link || !this->parent); // Only a parent queue has link-nodes
+ assert(!item->link || !self->parent); // Only a parent queue has link-nodes
Event ev = multiqueueitem_get_event(item, true);
- this->size--;
+ self->size--;
xfree(item);
return ev;
}
-static void multiqueue_push(MultiQueue *this, Event event)
+static void multiqueue_push(MultiQueue *self, Event event)
{
MultiQueueItem *item = xmalloc(sizeof(MultiQueueItem));
item->link = false;
item->data.item.event = event;
item->data.item.parent_item = NULL;
- QUEUE_INSERT_TAIL(&this->headtail, &item->node);
- if (this->parent) {
+ QUEUE_INSERT_TAIL(&self->headtail, &item->node);
+ if (self->parent) {
// push link node to the parent queue
item->data.item.parent_item = xmalloc(sizeof(MultiQueueItem));
item->data.item.parent_item->link = true;
- item->data.item.parent_item->data.queue = this;
- QUEUE_INSERT_TAIL(&this->parent->headtail,
+ item->data.item.parent_item->data.queue = self;
+ QUEUE_INSERT_TAIL(&self->parent->headtail,
&item->data.item.parent_item->node);
}
- this->size++;
+ self->size++;
}
static MultiQueueItem *multiqueue_node_data(QUEUE *q)
+ FUNC_ATTR_NO_SANITIZE_ADDRESS
{
return QUEUE_DATA(q, MultiQueueItem, node);
}
diff --git a/src/nvim/event/multiqueue.h b/src/nvim/event/multiqueue.h
index 2c5ba9d436..e01ee1e710 100644
--- a/src/nvim/event/multiqueue.h
+++ b/src/nvim/event/multiqueue.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_MULTIQUEUE_H
-#define NVIM_EVENT_MULTIQUEUE_H
+#pragma once
#include <uv.h>
@@ -15,4 +14,3 @@ typedef void (*PutCallback)(MultiQueue *multiq, void *data);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/multiqueue.h.generated.h"
#endif
-#endif // NVIM_EVENT_MULTIQUEUE_H
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 1a524a56ca..864fc2c1d8 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -1,19 +1,15 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <signal.h>
-#include <stdlib.h>
#include <uv.h>
#include "klib/klist.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/loop.h"
#include "nvim/event/process.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/os/process.h"
#include "nvim/os/pty_process.h"
@@ -78,8 +74,6 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
case kProcessTypePty:
status = pty_process_spawn((PtyProcess *)proc);
break;
- default:
- abort();
}
if (status) {
@@ -104,24 +98,21 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
}
if (in) {
- stream_init(NULL, &proc->in, -1,
- STRUCT_CAST(uv_stream_t, &proc->in.uv.pipe));
+ stream_init(NULL, &proc->in, -1, (uv_stream_t *)&proc->in.uv.pipe);
proc->in.internal_data = proc;
proc->in.internal_close_cb = on_process_stream_close;
proc->refcount++;
}
if (out) {
- stream_init(NULL, &proc->out, -1,
- STRUCT_CAST(uv_stream_t, &proc->out.uv.pipe));
+ stream_init(NULL, &proc->out, -1, (uv_stream_t *)&proc->out.uv.pipe);
proc->out.internal_data = proc;
proc->out.internal_close_cb = on_process_stream_close;
proc->refcount++;
}
if (err) {
- stream_init(NULL, &proc->err, -1,
- STRUCT_CAST(uv_stream_t, &proc->err.uv.pipe));
+ stream_init(NULL, &proc->err, -1, (uv_stream_t *)&proc->err.uv.pipe);
proc->err.internal_data = proc;
proc->err.internal_close_cb = on_process_stream_close;
proc->refcount++;
@@ -131,7 +122,7 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
proc->internal_close_cb = decref;
proc->refcount++;
kl_push(WatcherPtr, proc->loop->children, proc);
- DLOG("new: pid=%d argv=[%s]", proc->pid, proc->argv[0]);
+ DLOG("new: pid=%d exepath=[%s]", proc->pid, process_get_exepath(proc));
return 0;
}
@@ -239,8 +230,6 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
process_close_streams(proc);
pty_process_close_master((PtyProcess *)proc);
break;
- default:
- abort();
}
// (Re)start timer to verify that stopped process(es) died.
@@ -340,8 +329,6 @@ static void process_close(Process *proc)
case kProcessTypePty:
pty_process_close((PtyProcess *)proc);
break;
- default:
- abort();
}
}
@@ -382,7 +369,7 @@ static void flush_stream(Process *proc, Stream *stream)
}
// Stream can be closed if it is empty.
- if (num_bytes == stream->num_bytes) { // -V547
+ if (num_bytes == stream->num_bytes) {
if (stream->read_cb && !stream->did_eof) {
// Stream callback could miss EOF handling if a child keeps the stream
// open. But only send EOF if we haven't already.
@@ -422,7 +409,13 @@ static void exit_event(void **argv)
}
if (!exiting) {
- os_exit(status);
+ if (ui_client_channel_id) {
+ ui_client_exit_status = status;
+ os_exit(status);
+ } else {
+ assert(status == 0); // Called from rpc_close(), which passes 0 as status.
+ preserve_exit(NULL);
+ }
}
}
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index e0057faffb..234fc815af 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_EVENT_PROCESS_H
-#define NVIM_EVENT_PROCESS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
@@ -33,6 +31,7 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
+ const char *exepath;
dict_T *env;
Stream in, out, err;
/// Exit handler. If set, user must call process_free().
@@ -55,6 +54,7 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
.stopped_time = 0,
.cwd = NULL,
.argv = NULL,
+ .exepath = NULL,
.in = { .closed = false },
.out = { .closed = false },
.err = { .closed = false },
@@ -67,6 +67,12 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
};
}
+/// Get the path to the executable of the process.
+static inline const char *process_get_exepath(Process *proc)
+{
+ return proc->exepath != NULL ? proc->exepath : proc->argv[0];
+}
+
static inline bool process_is_stopped(Process *proc)
{
bool exited = (proc->status >= 0);
@@ -76,4 +82,3 @@ static inline bool process_is_stopped(Process *proc)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/process.h.generated.h"
#endif
-#endif // NVIM_EVENT_PROCESS_H
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index a88d62fd6b..73828a2271 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -1,17 +1,16 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <sys/types.h>
#include <uv.h>
#include "nvim/event/loop.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/os/os_defs.h"
#include "nvim/rbuffer.h"
@@ -155,7 +154,7 @@ static void fread_idle_cb(uv_idle_t *handle)
uintmax_t fpos_intmax = stream->fpos;
if (fpos_intmax > INT64_MAX) {
ELOG("stream offset overflow");
- preserve_exit();
+ preserve_exit("stream offset overflow");
}
// Synchronous read
diff --git a/src/nvim/event/rstream.h b/src/nvim/event/rstream.h
index 23ed00bfea..b2a62acf83 100644
--- a/src/nvim/event/rstream.h
+++ b/src/nvim/event/rstream.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_RSTREAM_H
-#define NVIM_EVENT_RSTREAM_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -11,4 +10,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/rstream.h.generated.h"
#endif
-#endif // NVIM_EVENT_RSTREAM_H
diff --git a/src/nvim/event/signal.c b/src/nvim/event/signal.c
index 8256ca2091..e64d526856 100644
--- a/src/nvim/event/signal.c
+++ b/src/nvim/event/signal.c
@@ -1,11 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stddef.h>
#include <uv.h>
#include "nvim/event/loop.h"
#include "nvim/event/signal.h"
+#include "nvim/func_attr.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/signal.c.generated.h"
diff --git a/src/nvim/event/signal.h b/src/nvim/event/signal.h
index f9adf62c20..946de1b4f0 100644
--- a/src/nvim/event/signal.h
+++ b/src/nvim/event/signal.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_SIGNAL_H
-#define NVIM_EVENT_SIGNAL_H
+#pragma once
#include <uv.h>
@@ -23,4 +22,3 @@ struct signal_watcher {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/signal.h.generated.h"
#endif
-#endif // NVIM_EVENT_SIGNAL_H
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index 10756015ad..e787e023f0 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -8,16 +5,17 @@
#include <string.h>
#include <uv.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/event/loop.h"
#include "nvim/event/socket.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/memory.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
@@ -64,10 +62,10 @@ int socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint
uv_tcp_init(&loop->uv, &watcher->uv.tcp.handle);
uv_tcp_nodelay(&watcher->uv.tcp.handle, true);
- watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.tcp.handle);
+ watcher->stream = (uv_stream_t *)(&watcher->uv.tcp.handle);
} else {
uv_pipe_init(&loop->uv, &watcher->uv.pipe.handle, 0);
- watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.pipe.handle);
+ watcher->stream = (uv_stream_t *)(&watcher->uv.pipe.handle);
}
watcher->stream->data = watcher;
@@ -102,9 +100,8 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
// contain 0 in this case, unless uv_tcp_getsockname() is used first.
uv_tcp_getsockname(&watcher->uv.tcp.handle, (struct sockaddr *)&sas,
&(int){ sizeof(sas) });
- uint16_t port = (uint16_t)((sas.ss_family == AF_INET)
- ? (STRUCT_CAST(struct sockaddr_in, &sas))->sin_port
- : (STRUCT_CAST(struct sockaddr_in6, &sas))->sin6_port);
+ uint16_t port = (sas.ss_family == AF_INET) ? ((struct sockaddr_in *)(&sas))->sin_port
+ : ((struct sockaddr_in6 *)(&sas))->sin6_port;
// v:servername uses the string from watcher->addr
size_t len = strlen(watcher->addr);
snprintf(watcher->addr + len, sizeof(watcher->addr) - len, ":%" PRIu16,
@@ -142,11 +139,11 @@ int socket_watcher_accept(SocketWatcher *watcher, Stream *stream)
uv_stream_t *client;
if (watcher->stream->type == UV_TCP) {
- client = STRUCT_CAST(uv_stream_t, &stream->uv.tcp);
+ client = (uv_stream_t *)(&stream->uv.tcp);
uv_tcp_init(watcher->uv.tcp.handle.loop, (uv_tcp_t *)client);
uv_tcp_nodelay((uv_tcp_t *)client, true);
} else {
- client = STRUCT_CAST(uv_stream_t, &stream->uv.pipe);
+ client = (uv_stream_t *)&stream->uv.pipe;
uv_pipe_init(watcher->uv.pipe.handle.loop, (uv_pipe_t *)client, 0);
}
@@ -165,7 +162,7 @@ void socket_watcher_close(SocketWatcher *watcher, socket_close_cb cb)
FUNC_ATTR_NONNULL_ARG(1)
{
watcher->close_cb = cb;
- uv_close(STRUCT_CAST(uv_handle_t, watcher->stream), close_cb);
+ uv_close((uv_handle_t *)watcher->stream, close_cb);
}
static void connection_event(void **argv)
@@ -224,7 +221,7 @@ bool socket_connect(Loop *loop, Stream *stream, bool is_tcp, const char *address
const struct addrinfo hints = { .ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
- .ai_flags = AI_NUMERICSERV };
+ .ai_flags = AI_NUMERICSERV };
int retval = uv_getaddrinfo(&loop->uv, &addr_req, NULL,
addr, host_end + 1, &hints);
if (retval != 0) {
@@ -242,11 +239,11 @@ tcp_retry:
uv_pipe_t *pipe = &stream->uv.pipe;
uv_pipe_init(&loop->uv, pipe, 0);
uv_pipe_connect(&req, pipe, address, connect_cb);
- uv_stream = STRUCT_CAST(uv_stream_t, pipe);
+ uv_stream = (uv_stream_t *)pipe;
}
status = 1;
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, timeout, status != 1);
- if (status == 0) { // -V547
+ if (status == 0) {
stream_init(NULL, stream, -1, uv_stream);
success = true;
} else if (is_tcp && addrinfo->ai_next) {
diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h
index c6fcdec4bb..504af3c7a8 100644
--- a/src/nvim/event/socket.h
+++ b/src/nvim/event/socket.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_SOCKET_H
-#define NVIM_EVENT_SOCKET_H
+#pragma once
#include <uv.h>
@@ -39,4 +38,3 @@ struct socket_watcher {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/socket.h.generated.h"
#endif
-#endif // NVIM_EVENT_SOCKET_H
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 0a4918636a..aff116bad9 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
@@ -9,8 +6,8 @@
#include "nvim/event/loop.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/rbuffer.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
@@ -37,9 +34,8 @@ int stream_set_blocking(int fd, bool blocking)
uv_loop_init(&loop);
uv_pipe_init(&loop, &stream, 0);
uv_pipe_open(&stream, fd);
- int retval = uv_stream_set_blocking(STRUCT_CAST(uv_stream_t, &stream),
- blocking);
- uv_close(STRUCT_CAST(uv_handle_t, &stream), NULL);
+ int retval = uv_stream_set_blocking((uv_stream_t *)&stream, blocking);
+ uv_close((uv_handle_t *)&stream, NULL);
uv_run(&loop, UV_RUN_NOWAIT); // not necessary, but couldn't hurt.
uv_loop_close(&loop);
return retval;
@@ -71,12 +67,12 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
SetConsoleMode(stream->uv.tty.handle, dwMode);
}
- stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.tty);
+ stream->uvstream = (uv_stream_t *)&stream->uv.tty;
} else {
#endif
uv_pipe_init(&loop->uv, &stream->uv.pipe, 0);
uv_pipe_open(&stream->uv.pipe, fd);
- stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.pipe);
+ stream->uvstream = (uv_stream_t *)&stream->uv.pipe;
#ifdef MSWIN
}
#endif
diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h
index 33d2d6e775..d02707dc45 100644
--- a/src/nvim/event/stream.h
+++ b/src/nvim/event/stream.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_STREAM_H
-#define NVIM_EVENT_STREAM_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -61,4 +60,3 @@ struct stream {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/stream.h.generated.h"
#endif
-#endif // NVIM_EVENT_STREAM_H
diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c
index c997e3c558..f678f25f3f 100644
--- a/src/nvim/event/time.c
+++ b/src/nvim/event/time.c
@@ -1,11 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdint.h>
#include <uv.h>
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
+#include "nvim/func_attr.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/time.c.generated.h"
diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h
index e84488fdd6..3514566901 100644
--- a/src/nvim/event/time.h
+++ b/src/nvim/event/time.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_TIME_H
-#define NVIM_EVENT_TIME_H
+#pragma once
#include <stdbool.h>
#include <uv.h>
@@ -23,4 +22,3 @@ struct time_watcher {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/time.h.generated.h"
#endif
-#endif // NVIM_EVENT_TIME_H
diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c
index 65391ba5cf..e8f757874b 100644
--- a/src/nvim/event/wstream.c
+++ b/src/nvim/event/wstream.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <uv.h>
@@ -8,7 +5,8 @@
#include "nvim/event/loop.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
-#include "nvim/macros.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#define DEFAULT_MAXMEM 1024 * 1024 * 2000
diff --git a/src/nvim/event/wstream.h b/src/nvim/event/wstream.h
index ef1311c619..4cba7bde8f 100644
--- a/src/nvim/event/wstream.h
+++ b/src/nvim/event/wstream.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_WSTREAM_H
-#define NVIM_EVENT_WSTREAM_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -23,4 +22,3 @@ struct wbuffer {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/wstream.h.generated.h"
#endif
-#endif // NVIM_EVENT_WSTREAM_H
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 437a05f61d..e369397047 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ex_cmds.c: some functions for command line commands
#include <assert.h>
#include <ctype.h>
#include <float.h>
#include <inttypes.h>
+#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
@@ -18,11 +16,12 @@
#include "auto/config.h"
#include "klib/kvec.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
+#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
@@ -44,17 +43,16 @@
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/input.h"
-#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -66,26 +64,28 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// Case matching style to use for :substitute
@@ -126,20 +126,28 @@ typedef struct {
# include "ex_cmds.c.generated.h"
#endif
+static const char e_non_numeric_argument_to_z[]
+ = N_("E144: Non-numeric argument to :z");
+
/// ":ascii" and "ga" implementation
-void do_ascii(const exarg_T *const eap)
+void do_ascii(exarg_T *eap)
{
- char *dig;
- int cc[MAX_MCO];
- int c = utfc_ptr2char(get_cursor_pos_ptr(), cc);
- if (c == NUL) {
- msg("NUL");
+ char *data = get_cursor_pos_ptr();
+ size_t len = (size_t)utfc_ptr2len(data);
+
+ if (len == 0) {
+ msg("NUL", 0);
return;
}
- size_t iobuff_len = 0;
+ bool need_clear = true;
+ msg_sb_eol();
+ msg_start();
+
+ int c = utf_ptr2char(data);
+ size_t off = 0;
- int ci = 0;
+ // TODO(bfredl): merge this with the main loop
if (c < 0x80) {
if (c == NL) { // NUL is stored as NL.
c = NUL;
@@ -148,56 +156,39 @@ void do_ascii(const exarg_T *const eap)
? NL // NL is stored as CR.
: c);
char buf1[20];
- if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) {
+ if (vim_isprintc(c) && (c < ' ' || c > '~')) {
char buf3[7];
- transchar_nonprint(curbuf, (char_u *)buf3, c);
- vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3);
+ transchar_nonprint(curbuf, buf3, c);
+ vim_snprintf(buf1, sizeof(buf1), " <%s>", buf3);
} else {
buf1[0] = NUL;
}
char buf2[20];
buf2[0] = NUL;
- dig = get_digraph_for_char(cval);
+ char *dig = get_digraph_for_char(cval);
if (dig != NULL) {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"),
- transchar(c), buf1, buf2, cval, cval, cval, dig);
+ vim_snprintf(IObuff, sizeof(IObuff),
+ _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"),
+ transchar(c), buf1, buf2, cval, cval, cval, dig);
} else {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- _("<%s>%s%s %d, Hex %02x, Octal %03o"),
- transchar(c), buf1, buf2, cval, cval, cval);
- }
-
- c = cc[ci++];
- }
-
-#define SPACE_FOR_DESC (1 + 1 + 1 + MB_MAXBYTES + 16 + 4 + 3 + 3 + 1)
- // Space for description:
- // - 1 byte for separator (starting from second entry)
- // - 1 byte for "<"
- // - 1 byte for space to draw composing character on (optional, but really
- // mostly required)
- // - up to MB_MAXBYTES bytes for character itself
- // - 16 bytes for raw text ("> , Hex , Octal ").
- // - at least 4 bytes for hexadecimal representation
- // - at least 3 bytes for decimal representation
- // - at least 3 bytes for octal representation
- // - 1 byte for NUL
- //
- // Taking into account MAX_MCO and characters which need 8 bytes for
- // hexadecimal representation, but not taking translation into account:
- // resulting string will occupy less then 400 bytes (conservative estimate).
- //
- // Less then 1000 bytes if translation multiplies number of bytes needed for
- // raw text by 6, so it should always fit into 1025 bytes reserved for IObuff.
+ vim_snprintf(IObuff, sizeof(IObuff),
+ _("<%s>%s%s %d, Hex %02x, Octal %03o"),
+ transchar(c), buf1, buf2, cval, cval, cval);
+ }
+
+ msg_multiline(IObuff, 0, true, &need_clear);
+
+ off += (size_t)utf_ptr2len(data); // needed for overlong ascii?
+ }
// Repeat for combining characters, also handle multiby here.
- while (c >= 0x80 && iobuff_len < sizeof(IObuff) - SPACE_FOR_DESC) {
+ while (off < len) {
+ c = utf_ptr2char(data + off);
+
+ size_t iobuff_len = 0;
// This assumes every multi-byte char is printable...
- if (iobuff_len > 0) {
+ if (off > 0) {
IObuff[iobuff_len++] = ' ';
}
IObuff[iobuff_len++] = '<';
@@ -206,43 +197,37 @@ void do_ascii(const exarg_T *const eap)
}
iobuff_len += (size_t)utf_char2bytes(c, IObuff + iobuff_len);
- dig = get_digraph_for_char(c);
+ char *dig = get_digraph_for_char(c);
if (dig != NULL) {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- (c < 0x10000
- ? _("> %d, Hex %04x, Oct %o, Digr %s")
- : _("> %d, Hex %08x, Oct %o, Digr %s")),
- c, c, c, dig);
+ vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
+ (c < 0x10000
+ ? _("> %d, Hex %04x, Oct %o, Digr %s")
+ : _("> %d, Hex %08x, Oct %o, Digr %s")),
+ c, c, c, dig);
} else {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- (c < 0x10000
- ? _("> %d, Hex %04x, Octal %o")
- : _("> %d, Hex %08x, Octal %o")),
- c, c, c);
- }
- if (ci == MAX_MCO) {
- break;
+ vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
+ (c < 0x10000
+ ? _("> %d, Hex %04x, Octal %o")
+ : _("> %d, Hex %08x, Octal %o")),
+ c, c, c);
}
- c = cc[ci++];
- }
- if (ci != MAX_MCO && c != 0) {
- xstrlcpy(IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len);
+
+ msg_multiline(IObuff, 0, true, &need_clear);
+
+ off += (size_t)utf_ptr2len(data + off); // needed for overlong ascii?
}
- msg(IObuff);
+ if (need_clear) {
+ msg_clr_eos();
+ }
+ msg_end();
}
/// ":left", ":center" and ":right": align text.
void ex_align(exarg_T *eap)
{
- pos_T save_curpos;
- int len;
int indent = 0;
int new_indent;
- int has_tab;
- int width;
if (curwin->w_p_rl) {
// switch left and right aligning
@@ -253,8 +238,8 @@ void ex_align(exarg_T *eap)
}
}
- width = atoi(eap->arg);
- save_curpos = curwin->w_cursor;
+ int width = atoi(eap->arg);
+ pos_T save_curpos = curwin->w_cursor;
if (eap->cmdidx == CMD_left) { // width is used for new indent
if (width >= 0) {
indent = width;
@@ -283,8 +268,8 @@ void ex_align(exarg_T *eap)
if (eap->cmdidx == CMD_left) { // left align
new_indent = indent;
} else {
- has_tab = false; // avoid uninit warnings
- len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent();
+ int has_tab = false; // avoid uninit warnings
+ int len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent();
if (len <= 0) { // skip blank lines
continue;
@@ -319,7 +304,7 @@ void ex_align(exarg_T *eap)
}
(void)set_indent(new_indent, 0); // set indent
}
- changed_lines(eap->line1, 0, eap->line2 + 1, 0L, true);
+ changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, 0, true);
curwin->w_cursor = save_curpos;
beginline(BL_WHITE | BL_FIX);
}
@@ -327,29 +312,24 @@ void ex_align(exarg_T *eap)
/// @return the length of the current line, excluding trailing white space.
static int linelen(int *has_tab)
{
- char *line;
- char *first;
char *last;
- int len;
// Get the line. If it's empty bail out early (could be the empty string
// for an unloaded buffer).
- line = get_cursor_line_ptr();
+ char *line = get_cursor_line_ptr();
if (*line == NUL) {
return 0;
}
// find the first non-blank character
- first = skipwhite(line);
+ char *first = skipwhite(line);
// find the character after the last non-blank character
for (last = first + strlen(first);
last > first && ascii_iswhite(last[-1]); last--) {}
char save = *last;
*last = NUL;
- // Get line length.
- len = linetabsize(line);
- // Check for embedded TAB.
- if (has_tab != NULL) {
+ int len = linetabsize_str(line); // Get line length.
+ if (has_tab != NULL) { // Check for embedded TAB.
*has_tab = vim_strchr(first, TAB) != NULL;
}
*last = save;
@@ -389,7 +369,7 @@ typedef struct {
static int string_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL
{
if (sort_lc) {
- return strcoll((char *)s1, (char *)s2);
+ return strcoll((const char *)s1, (const char *)s2);
}
return sort_ic ? STRICMP(s1, s2) : strcmp(s1, s2);
}
@@ -418,10 +398,10 @@ static int sort_compare(const void *s1, const void *s2)
result = l1.st_u.num.is_number - l2.st_u.num.is_number;
} else {
result = l1.st_u.num.value == l2.st_u.num.value
- ? 0
- : l1.st_u.num.value > l2.st_u.num.value
- ? 1
- : -1;
+ ? 0
+ : l1.st_u.num.value > l2.st_u.num.value
+ ? 1
+ : -1;
}
} else if (sort_flt) {
result = l1.st_u.value_flt == l2.st_u.value_flt
@@ -452,19 +432,10 @@ static int sort_compare(const void *s1, const void *s2)
void ex_sort(exarg_T *eap)
{
regmatch_T regmatch;
- int len;
- linenr_T lnum;
- long maxlen = 0;
+ int maxlen = 0;
size_t count = (size_t)(eap->line2 - eap->line1) + 1;
size_t i;
- char *p;
- char *s;
- char *s2;
- char c; // temporary character storage
bool unique = false;
- long deleted;
- colnr_T start_col;
- colnr_T end_col;
int sort_what = 0;
// Sorting one line is really quick!
@@ -484,7 +455,7 @@ void ex_sort(exarg_T *eap)
size_t format_found = 0;
bool change_occurred = false; // Buffer contents changed.
- for (p = eap->arg; *p != NUL; p++) {
+ for (char *p = eap->arg; *p != NUL; p++) {
if (ascii_iswhite(*p)) {
// Skip
} else if (*p == 'i') {
@@ -517,7 +488,7 @@ void ex_sort(exarg_T *eap)
eap->nextcmd = check_nextcmd(p);
break;
} else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) {
- s = skip_regexp_err(p + 1, *p, true);
+ char *s = skip_regexp_err(p + 1, *p, true);
if (s == NULL) {
goto sortend;
}
@@ -559,15 +530,15 @@ void ex_sort(exarg_T *eap)
// numbers sorting it's the number to sort on. This means the pattern
// matching and number conversion only has to be done once per line.
// Also get the longest line length for allocating "sortbuf".
- for (lnum = eap->line1; lnum <= eap->line2; lnum++) {
- s = ml_get(lnum);
- len = (int)strlen(s);
+ for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
+ char *s = ml_get(lnum);
+ int len = (int)strlen(s);
if (maxlen < len) {
maxlen = len;
}
- start_col = 0;
- end_col = len;
+ colnr_T start_col = 0;
+ colnr_T end_col = len;
if (regmatch.regprog != NULL && vim_regexec(&regmatch, s, 0)) {
if (sort_rx) {
start_col = (colnr_T)(regmatch.startp[0] - s);
@@ -580,13 +551,13 @@ void ex_sort(exarg_T *eap)
}
if (sort_nr || sort_flt) {
- // Make sure vim_str2nr doesn't read any digits past the end
+ // Make sure vim_str2nr() doesn't read any digits past the end
// of the match, by temporarily terminating the string there
- s2 = s + end_col;
- c = *s2;
+ char *s2 = s + end_col;
+ char c = *s2; // temporary character storage
*s2 = NUL;
// Sorting on number: Store the number itself.
- p = s + start_col;
+ char *p = s + start_col;
if (sort_nr) {
if (sort_what & STR2NR_HEX) {
s = skiptohex(p);
@@ -605,7 +576,7 @@ void ex_sort(exarg_T *eap)
} else {
nrs[lnum - eap->line1].st_u.num.is_number = true;
vim_str2nr(s, NULL, NULL, sort_what,
- &nrs[lnum - eap->line1].st_u.num.value, NULL, 0, false);
+ &nrs[lnum - eap->line1].st_u.num.value, NULL, 0, false, NULL);
}
} else {
s = skipwhite(p);
@@ -651,7 +622,7 @@ void ex_sort(exarg_T *eap)
bcount_t old_count = 0, new_count = 0;
// Insert the lines in the sorted order below the last one.
- lnum = eap->line2;
+ linenr_T lnum = eap->line2;
for (i = 0; i < count; i++) {
const linenr_T get_lnum = nrs[eap->forceit ? count - i - 1 : i].lnum;
@@ -661,14 +632,14 @@ void ex_sort(exarg_T *eap)
change_occurred = true;
}
- s = ml_get(get_lnum);
+ char *s = ml_get(get_lnum);
size_t bytelen = strlen(s) + 1; // include EOL in bytelen
old_count += (bcount_t)bytelen;
if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) {
// Copy the line into a buffer, it may become invalid in
// ml_append(). And it's needed for "unique".
STRCPY(sortbuf1, s);
- if (ml_append(lnum++, sortbuf1, (colnr_T)0, false) == FAIL) {
+ if (ml_append(lnum++, sortbuf1, 0, false) == FAIL) {
break;
}
new_count += (bcount_t)bytelen;
@@ -689,13 +660,12 @@ void ex_sort(exarg_T *eap)
}
// Adjust marks for deleted (or added) lines and prepare for displaying.
- deleted = (long)count - (lnum - eap->line2);
+ linenr_T deleted = (linenr_T)count - (lnum - eap->line2);
if (deleted > 0) {
- mark_adjust(eap->line2 - (linenr_T)deleted, eap->line2, (long)MAXLNUM, (linenr_T)(-deleted),
- kExtmarkNOOP);
+ mark_adjust(eap->line2 - deleted, eap->line2, MAXLNUM, -deleted, kExtmarkNOOP);
msgmore(-deleted);
} else if (deleted < 0) {
- mark_adjust(eap->line2, MAXLNUM, (linenr_T)(-deleted), 0L, kExtmarkNOOP);
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0, kExtmarkNOOP);
}
if (change_occurred || deleted != 0) {
@@ -703,7 +673,7 @@ void ex_sort(exarg_T *eap)
(int)count, 0, old_count,
lnum - eap->line2, 0, new_count, kExtmarkUndo);
- changed_lines(eap->line1, 0, eap->line2 + 1, (linenr_T)(-deleted), true);
+ changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, -deleted, true);
}
curwin->w_cursor.lnum = eap->line1;
@@ -724,7 +694,6 @@ sortend:
/// @return FAIL for failure, OK otherwise
int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
{
- char *str;
linenr_T l;
linenr_T extra; // Num lines added before line1
linenr_T num_lines; // Num lines moved
@@ -761,8 +730,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return FAIL;
}
for (extra = 0, l = line1; l <= line2; l++) {
- str = xstrdup(ml_get(l + extra));
- ml_append(dest + l - line1, str, (colnr_T)0, false);
+ char *str = xstrdup(ml_get(l + extra));
+ ml_append(dest + l - line1, str, 0, false);
xfree(str);
if (dest < line1) {
extra++;
@@ -783,16 +752,16 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
// And Finally we adjust the marks we put at the end of the file back to
// their final destination at the new text position -- webb
last_line = curbuf->b_ml.ml_line_count;
- mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
+ mark_adjust_nofold(line1, line2, last_line - line2, 0, kExtmarkNOOP);
disable_fold_update++;
- changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
+ changed_lines(curbuf, last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
disable_fold_update--;
int line_off = 0;
bcount_t byte_off = 0;
if (dest >= line2) {
- mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
+ mark_adjust_nofold(line2 + 1, dest, -num_lines, 0, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(win, &win->w_folds, line1, line2, dest);
@@ -805,7 +774,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
line_off = -num_lines;
byte_off = -extent_byte;
} else {
- mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
+ mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2);
@@ -820,10 +789,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
mark_adjust_nofold(last_line - num_lines + 1, last_line,
- -(last_line - dest - extra), 0L, kExtmarkNOOP);
+ -(last_line - dest - extra), 0, kExtmarkNOOP);
disable_fold_update++;
- changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
+ changed_lines(curbuf, last_line - num_lines + 1, 0, last_line + 1, -extra, false);
disable_fold_update--;
// send update regarding the new lines that were added
@@ -838,8 +807,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
ml_delete(line1 + extra, true);
}
if (!global_busy && num_lines > p_report) {
- smsg(NGETTEXT("%" PRId64 " line moved",
- "%" PRId64 " lines moved", num_lines),
+ smsg(0, NGETTEXT("%" PRId64 " line moved",
+ "%" PRId64 " lines moved", num_lines),
(int64_t)num_lines);
}
@@ -861,9 +830,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
if (dest > last_line + 1) {
dest = last_line + 1;
}
- changed_lines(line1, 0, dest, 0L, false);
+ changed_lines(curbuf, line1, 0, dest, 0, false);
} else {
- changed_lines(dest + 1, 0, line1 + num_lines, 0L, false);
+ changed_lines(curbuf, dest + 1, 0, line1 + num_lines, 0, false);
}
// send nvim_buf_lines_event regarding lines that were deleted
@@ -875,10 +844,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
/// ":copy"
void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
{
- linenr_T count;
- char *p;
-
- count = line2 - line1 + 1;
+ linenr_T count = line2 - line1 + 1;
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
curbuf->b_op_start.lnum = n + 1;
curbuf->b_op_end.lnum = n + count;
@@ -902,8 +868,8 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
while (line1 <= line2) {
// need to use xstrdup() because the line will be unlocked within
// ml_append()
- p = xstrdup(ml_get(line1));
- ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, false);
+ char *p = xstrdup(ml_get(line1));
+ ml_append(curwin->w_cursor.lnum, p, 0, false);
xfree(p);
// situation 2: skip already copied lines
@@ -925,7 +891,7 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
check_pos(curbuf, &VIsual);
}
- msgmore((long)count);
+ msgmore(count);
}
static char *prevcmd = NULL; // the previous command
@@ -938,6 +904,17 @@ void free_prev_shellcmd(void)
#endif
+/// Check that "prevcmd" is not NULL. If it is NULL then give an error message
+/// and return false.
+static int prevcmd_is_set(void)
+{
+ if (prevcmd == NULL) {
+ emsg(_(e_noprev));
+ return false;
+ }
+ return true;
+}
+
/// Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd"
/// Bangs in the argument are replaced with the previously entered command.
/// Remember the argument.
@@ -949,15 +926,9 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
linenr_T line2 = eap->line2; // end of range
char *newcmd = NULL; // the new command
bool free_newcmd = false; // need to free() newcmd
- char *t;
- char *p;
- char *trailarg;
- size_t len;
int scroll_save = msg_scroll;
- //
// Disallow shell commands in secure mode
- //
if (check_secure()) {
return;
}
@@ -973,21 +944,20 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
bool ins_prevcmd = forceit;
// Skip leading white space to avoid a strange error with some shells.
- trailarg = skipwhite(arg);
+ char *trailarg = skipwhite(arg);
do {
- len = strlen(trailarg) + 1;
+ size_t len = strlen(trailarg) + 1;
if (newcmd != NULL) {
len += strlen(newcmd);
}
if (ins_prevcmd) {
- if (prevcmd == NULL) {
- emsg(_(e_noprev));
+ if (!prevcmd_is_set()) {
xfree(newcmd);
return;
}
len += strlen(prevcmd);
}
- t = xmalloc(len);
+ char *t = xmalloc(len);
*t = NUL;
if (newcmd != NULL) {
STRCAT(t, newcmd);
@@ -995,7 +965,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
if (ins_prevcmd) {
STRCAT(t, prevcmd);
}
- p = t + strlen(t);
+ char *p = t + strlen(t);
STRCAT(t, trailarg);
xfree(newcmd);
newcmd = t;
@@ -1018,10 +988,20 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
}
} while (trailarg != NULL);
- xfree(prevcmd);
- prevcmd = newcmd;
+ // Only set "prevcmd" if there is a command to run, otherwise keep te one
+ // we have.
+ if (strlen(newcmd) > 0) {
+ xfree(prevcmd);
+ prevcmd = newcmd;
+ } else {
+ free_newcmd = true;
+ }
if (bangredo) { // put cmd in redo buffer for ! command
+ if (!prevcmd_is_set()) {
+ goto theend;
+ }
+
// If % or # appears in the command, it must have been escaped.
// Reescape them, so that redoing them does not substitute them by the
// buffername.
@@ -1034,6 +1014,9 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
}
// Add quotes around the command, for shells that need them.
if (*p_shq != NUL) {
+ if (free_newcmd) {
+ xfree(newcmd);
+ }
newcmd = xmalloc(strlen(prevcmd) + 2 * strlen(p_shq) + 1);
STRCPY(newcmd, p_shq);
STRCAT(newcmd, prevcmd);
@@ -1045,7 +1028,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
msg_start();
msg_putchar(':');
msg_putchar('!');
- msg_outtrans(newcmd);
+ msg_outtrans(newcmd, 0);
msg_clr_eos();
ui_cursor_goto(msg_row, msg_col);
@@ -1056,6 +1039,8 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
do_filter(line1, line2, eap, newcmd, do_in, do_out);
apply_autocmds(EVENT_SHELLFILTERPOST, NULL, NULL, false, curbuf);
}
+
+theend:
if (free_newcmd) {
xfree(newcmd);
}
@@ -1081,10 +1066,6 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
{
char *itmp = NULL;
char *otmp = NULL;
- linenr_T linecount;
- linenr_T read_linecount;
- pos_T cursor_save;
- char *cmd_buf;
buf_T *old_curbuf = curbuf;
int shell_flags = 0;
const pos_T orig_start = curbuf->b_op_start;
@@ -1100,12 +1081,12 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
// regions of the buffer for foldUpdate(), linecount, etc.
cmdmod.cmod_flags &= ~CMOD_LOCKMARKS;
- cursor_save = curwin->w_cursor;
- linecount = line2 - line1 + 1;
+ pos_T cursor_save = curwin->w_cursor;
+ linenr_T linecount = line2 - line1 + 1;
curwin->w_cursor.lnum = line1;
curwin->w_cursor.col = 0;
changed_line_abv_curs();
- invalidate_botline();
+ invalidate_botline(curwin);
// When using temp files:
// 1. * Form temp file names
@@ -1166,7 +1147,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
}
// Create the shell command in allocated memory.
- cmd_buf = make_filter_cmd(cmd, itmp, otmp);
+ char *cmd_buf = make_filter_cmd(cmd, itmp, otmp);
ui_cursor_goto(Rows - 1, 0);
if (do_out) {
@@ -1176,7 +1157,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
}
redraw_curbuf_later(UPD_VALID);
}
- read_linecount = curbuf->b_ml.ml_line_count;
+ linenr_T read_linecount = curbuf->b_ml.ml_line_count;
// Pass on the kShellOptDoOut flag when the output is being redirected.
call_shell(cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL);
@@ -1193,7 +1174,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
if (do_out) {
if (otmp != NULL) {
- if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ if (readfile(otmp, NULL, line2, 0, (linenr_T)MAXLNUM, eap,
READ_FILTER, false) != OK) {
if (!aborting()) {
msg_putchar('\n');
@@ -1222,13 +1203,13 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
// end of each line?
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
- mark_adjust(line1, line2, linecount, 0L, kExtmarkNOOP);
+ mark_adjust(line1, line2, linecount, 0, kExtmarkNOOP);
} else {
// move marks from old lines to new lines, delete marks
// that are in deleted lines
- mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L,
+ mark_adjust(line1, line1 + read_linecount - 1, linecount, 0,
kExtmarkNOOP);
- mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L,
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0,
kExtmarkNOOP);
}
}
@@ -1255,12 +1236,12 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
if (do_in) {
vim_snprintf(msg_buf, sizeof(msg_buf),
_("%" PRId64 " lines filtered"), (int64_t)linecount);
- if (msg(msg_buf) && !msg_scroll) {
+ if (msg(msg_buf, 0) && !msg_scroll) {
// save message to display it after redraw
set_keep_msg(msg_buf, 0);
}
} else {
- msgmore((long)linecount);
+ msgmore(linecount);
}
}
} else {
@@ -1325,7 +1306,9 @@ void do_shell(char *cmd, int flags)
// 1" command to the terminal.
ui_cursor_goto(msg_row, msg_col);
(void)call_shell(cmd, (ShellOpts)flags, NULL);
- msg_didout = true;
+ if (msg_silent == 0) {
+ msg_didout = true;
+ }
did_check_timestamps = false;
need_check_timestamps = true;
@@ -1367,12 +1350,12 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
{
bool is_fish_shell =
#if defined(UNIX)
- strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
+ strncmp(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
#else
false;
#endif
- bool is_pwsh = strncmp((char *)invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0
- || strncmp((char *)invocation_path_tail(p_sh, NULL), "powershell",
+ bool is_pwsh = strncmp(invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0
+ || strncmp(invocation_path_tail(p_sh, NULL), "powershell",
10) == 0;
size_t len = strlen(cmd) + 1; // At least enough space for cmd + NULL.
@@ -1382,8 +1365,8 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
: 0;
if (itmp != NULL) {
- len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6 // +6: #20530
- : strlen(itmp) + sizeof(" { " " < " " } ") - 1;
+ len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6 // +6: #20530
+ : strlen(itmp) + sizeof(" { " " < " " } ") - 1;
}
if (otmp != NULL) {
len += strlen(otmp) + strlen(p_srr) + 2; // two extra spaces (" "),
@@ -1394,7 +1377,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
if (is_pwsh) {
if (itmp != NULL) {
xstrlcpy(buf, "& { Get-Content ", len - 1); // FIXME: should we add "-Encoding utf8"?
- xstrlcat(buf, (const char *)itmp, len - 1);
+ xstrlcat(buf, itmp, len - 1);
xstrlcat(buf, " | & ", len - 1); // FIXME: add `&` ourself or leave to user?
xstrlcat(buf, cmd, len - 1);
xstrlcat(buf, " }", len - 1);
@@ -1407,7 +1390,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
// redirecting input and/or output.
if (itmp != NULL || otmp != NULL) {
char *fmt = is_fish_shell ? "begin; %s; end"
- : "(%s)";
+ : "(%s)";
vim_snprintf(buf, len, fmt, cmd);
} else {
xstrlcpy(buf, cmd, len);
@@ -1415,7 +1398,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
if (itmp != NULL) {
xstrlcat(buf, " < ", len - 1);
- xstrlcat(buf, (const char *)itmp, len - 1);
+ xstrlcat(buf, itmp, len - 1);
}
#else
// For shells that don't understand braces around commands, at least allow
@@ -1432,9 +1415,9 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
}
}
xstrlcat(buf, " < ", len);
- xstrlcat(buf, (const char *)itmp, len);
+ xstrlcat(buf, itmp, len);
if (*p_shq == NUL) {
- const char *const p = find_pipe((const char *)cmd);
+ const char *const p = find_pipe(cmd);
if (p != NULL) {
xstrlcat(buf, " ", len - 1); // Insert a space before the '|' for DOS
xstrlcat(buf, p, len - 1);
@@ -1507,7 +1490,6 @@ void print_line(linenr_T lnum, int use_number, int list)
print_line_no_prefix(lnum, use_number, list);
if (save_silent) {
msg_putchar('\n');
- ui_flush();
silent_mode = save_silent;
}
info_message = false;
@@ -1515,10 +1497,7 @@ void print_line(linenr_T lnum, int use_number, int list)
int rename_buffer(char *new_fname)
{
- char *fname, *sfname, *xfname;
- buf_T *buf;
-
- buf = curbuf;
+ buf_T *buf = curbuf;
apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
// buffer changed, don't change name now
if (buf != curbuf) {
@@ -1532,9 +1511,9 @@ int rename_buffer(char *new_fname)
// name, which will become the alternate file name.
// But don't set the alternate file name if the buffer didn't have a
// name.
- fname = curbuf->b_ffname;
- sfname = curbuf->b_sfname;
- xfname = curbuf->b_fname;
+ char *fname = curbuf->b_ffname;
+ char *sfname = curbuf->b_sfname;
+ char *xfname = curbuf->b_fname;
curbuf->b_ffname = NULL;
curbuf->b_sfname = NULL;
if (setfname(curbuf, new_fname, NULL, true) == FAIL) {
@@ -1628,17 +1607,15 @@ int do_write(exarg_T *eap)
{
int other;
char *fname = NULL; // init to shut up gcc
- char *ffname;
int retval = FAIL;
char *free_fname = NULL;
buf_T *alt_buf = NULL;
- int name_was_missing;
if (not_writing()) { // check 'write' option
return FAIL;
}
- ffname = eap->arg;
+ char *ffname = eap->arg;
if (*ffname == NUL) {
if (eap->cmdidx == CMD_saveas) {
emsg(_(e_argreq));
@@ -1660,7 +1637,7 @@ int do_write(exarg_T *eap)
if (other) {
if (vim_strchr(p_cpo, CPO_ALTWRITE) != NULL
|| eap->cmdidx == CMD_saveas) {
- alt_buf = setaltfname(ffname, fname, (linenr_T)1);
+ alt_buf = setaltfname(ffname, fname, 1);
} else {
alt_buf = buflist_findname(ffname);
}
@@ -1764,7 +1741,7 @@ int do_write(exarg_T *eap)
}
}
- name_was_missing = curbuf->b_ffname == NULL;
+ int name_was_missing = curbuf->b_ffname == NULL;
retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2,
eap, eap->append, eap->forceit, true, false);
@@ -1824,7 +1801,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth
if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
char buff[DIALOG_MSG_SIZE];
- dialog_msg((char *)buff, _("Overwrite existing file \"%s\"?"), fname);
+ dialog_msg(buff, _("Overwrite existing file \"%s\"?"), fname);
if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) != VIM_YES) {
return FAIL;
}
@@ -1860,7 +1837,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth
if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
char buff[DIALOG_MSG_SIZE];
- dialog_msg((char *)buff,
+ dialog_msg(buff,
_("Swap file \"%s\" exists, overwrite anyway?"),
swapname);
if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2)
@@ -1906,6 +1883,9 @@ void do_wqall(exarg_T *eap)
int save_forceit = eap->forceit;
if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) {
+ if (before_quit_all(eap) == FAIL) {
+ return;
+ }
exiting = true;
}
@@ -1980,11 +1960,11 @@ static int check_readonly(int *forceit, buf_T *buf)
char buff[DIALOG_MSG_SIZE];
if (buf->b_p_ro) {
- dialog_msg((char *)buff,
+ dialog_msg(buff,
_("'readonly' option is set for \"%s\".\nDo you wish to write anyway?"),
buf->b_fname);
} else {
- dialog_msg((char *)buff,
+ dialog_msg(buff,
_("File permissions of \"%s\" are read-only.\nIt may still be possible to "
"write it.\nDo you wish to try?"),
buf->b_fname);
@@ -2067,7 +2047,7 @@ int getfile(int fnum, char *ffname_arg, char *sfname_arg, int setpm, linenr_T ln
if (lnum != 0) {
curwin->w_cursor.lnum = lnum;
}
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_SOL | BL_FIX);
retval = GETFILE_SAME_FILE; // it's in the same file
} else if (do_ecmd(fnum, ffname, sfname, NULL, lnum,
@@ -2127,17 +2107,14 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
bufref_T old_curbuf;
char *free_fname = NULL;
int retval = FAIL;
- long n;
- pos_T orig_pos;
linenr_T topline = 0;
int newcol = -1;
int solcol = -1;
- pos_T *pos;
char *command = NULL;
bool did_get_winopts = false;
int readfile_flags = 0;
bool did_inc_redrawing_disabled = false;
- long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ OptInt *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
if (eap != NULL) {
command = eap->do_ecmd_cmd;
@@ -2209,9 +2186,17 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// End Visual mode before switching to another buffer, so the text can be
// copied into the GUI selection buffer.
+ // Careful: may trigger ModeChanged() autocommand
+
+ // Should we block autocommands here?
reset_VIsual();
- if ((command != NULL || newlnum > (linenr_T)0)
+ // autocommands freed window :(
+ if (oldwin != NULL && !win_valid(oldwin)) {
+ oldwin = NULL;
+ }
+
+ if ((command != NULL || newlnum > 0)
&& *get_vim_var_str(VV_SWAPCOMMAND) == NUL) {
// Set v:swapcommand for the SwapExists autocommands.
const size_t len = (command != NULL) ? strlen(command) + 3 : 30;
@@ -2251,7 +2236,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
if (command != NULL) {
tlnum = (linenr_T)atol(command);
if (tlnum <= 0) {
- tlnum = 1L;
+ tlnum = 1;
}
}
// Add BLN_NOCURWIN to avoid a new wininfo items are associated
@@ -2263,7 +2248,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
}
goto theend;
}
- buf = buflist_new(ffname, sfname, 0L,
+ buf = buflist_new(ffname, sfname, 0,
BLN_CURBUF | (flags & ECMD_SET_HELP ? 0 : BLN_LISTED));
// Autocmds may change curwin and curbuf.
if (oldwin != NULL) {
@@ -2300,7 +2285,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// May jump to last used line number for a loaded buffer or when asked
// for explicitly
if ((oldbuf && newlnum == ECMD_LASTL) || newlnum == ECMD_LAST) {
- pos = &buflist_findfmark(buf)->mark;
+ pos_T *pos = &buflist_findfmark(buf)->mark;
newlnum = pos->lnum;
solcol = pos->col;
}
@@ -2555,7 +2540,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// Careful: open_buffer() and apply_autocmds() may change the current
// buffer and window.
- orig_pos = curwin->w_cursor;
+ pos_T orig_pos = curwin->w_cursor;
topline = curwin->w_topline;
if (!oldbuf) { // need to read the file
swap_exists_action = SEA_DIALOG;
@@ -2621,7 +2606,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// If the window options were changed may need to set the spell language.
// Can only do this after the buffer has been properly setup.
if (did_get_winopts && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
- (void)did_set_spelllang(curwin);
+ (void)parse_spelllang(curwin);
}
if (command == NULL) {
@@ -2631,7 +2616,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
check_cursor();
} else if (newlnum > 0) { // line number from caller or old position
curwin->w_cursor.lnum = newlnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
if (solcol >= 0 && !p_sol) {
// 'sol' is off: Use last known column.
curwin->w_cursor.col = solcol;
@@ -2660,11 +2645,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// Obey the 'O' flag in 'cpoptions': overwrite any previous file
// message.
- if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) {
+ if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
msg_scroll = false;
}
if (!msg_scroll) { // wait a bit when overwriting an error msg
- check_for_delay(false);
+ msg_check_for_delay(false);
}
msg_start();
msg_scroll = msg_scroll_save;
@@ -2690,7 +2675,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
RedrawingDisabled--;
did_inc_redrawing_disabled = false;
if (!skip_redraw) {
- n = *so_ptr;
+ OptInt n = *so_ptr;
if (topline == 0 && command == NULL) {
*so_ptr = 999; // force cursor to be vertically centered in the window
}
@@ -2737,7 +2722,6 @@ void ex_append(exarg_T *eap)
linenr_T lnum = eap->line2;
int indent = 0;
char *p;
- int vcol;
int empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
// the ! flag toggles autoindent
@@ -2764,7 +2748,7 @@ void ex_append(exarg_T *eap)
State |= MODE_LANGMAP;
}
- for (;;) {
+ while (true) {
msg_scroll = true;
need_wait_return = false;
if (curbuf->b_p_ai) {
@@ -2785,7 +2769,7 @@ void ex_append(exarg_T *eap)
if (p == NULL) {
p = eap->nextcmd + strlen(eap->nextcmd);
}
- theline = xstrnsave(eap->nextcmd, (size_t)(p - eap->nextcmd));
+ theline = xmemdupz(eap->nextcmd, (size_t)(p - eap->nextcmd));
if (*p != NUL) {
p++;
}
@@ -2804,7 +2788,7 @@ void ex_append(exarg_T *eap)
}
// Look for the "." after automatic indent.
- vcol = 0;
+ int vcol = 0;
for (p = theline; indent > vcol; p++) {
if (*p == ' ') {
vcol++;
@@ -2827,19 +2811,19 @@ void ex_append(exarg_T *eap)
}
did_undo = true;
- ml_append(lnum, theline, (colnr_T)0, false);
+ ml_append(lnum, theline, 0, false);
if (empty) {
// there are no marks below the inserted lines
- appended_lines(lnum, 1L);
+ appended_lines(lnum, 1);
} else {
- appended_lines_mark(lnum, 1L);
+ appended_lines_mark(lnum, 1);
}
xfree(theline);
lnum++;
if (empty) {
- ml_delete(2L, false);
+ ml_delete(2, false);
empty = 0;
}
}
@@ -2863,7 +2847,7 @@ void ex_append(exarg_T *eap)
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_SOL | BL_FIX);
need_wait_return = false; // don't use wait_return() now
@@ -2893,7 +2877,7 @@ void ex_change(exarg_T *eap)
}
// make sure the cursor is not beyond the end of the file now
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
deleted_lines_mark(eap->line1, (eap->line2 - lnum));
// ":append" on the line above the deleted lines.
@@ -2903,12 +2887,9 @@ void ex_change(exarg_T *eap)
void ex_z(exarg_T *eap)
{
- char *x;
int64_t bigness;
- char *kind;
int minus = 0;
- linenr_T start, end, curs, i;
- int j;
+ linenr_T start, end, curs;
linenr_T lnum = eap->line2;
// Vi compatible: ":z!" uses display height, without a count uses
@@ -2924,8 +2905,8 @@ void ex_z(exarg_T *eap)
bigness = 1;
}
- x = eap->arg;
- kind = x;
+ char *x = eap->arg;
+ char *kind = x;
if (*kind == '-' || *kind == '+' || *kind == '='
|| *kind == '^' || *kind == '.') {
x++;
@@ -2936,7 +2917,7 @@ void ex_z(exarg_T *eap)
if (*x != 0) {
if (!ascii_isdigit(*x)) {
- emsg(_("E144: non-numeric argument to :z"));
+ emsg(_(e_non_numeric_argument_to_z));
return;
}
bigness = atol(x);
@@ -2946,7 +2927,7 @@ void ex_z(exarg_T *eap)
bigness = 2 * curbuf->b_ml.ml_line_count;
}
- p_window = bigness;
+ p_window = (int)bigness;
if (*kind == '=') {
bigness += 2;
}
@@ -3009,11 +2990,11 @@ void ex_z(exarg_T *eap)
curs = 1;
}
- for (i = start; i <= end; i++) {
+ for (linenr_T i = start; i <= end; i++) {
if (minus && i == lnum) {
msg_putchar('\n');
- for (j = 1; j < Columns; j++) {
+ for (int j = 1; j < Columns; j++) {
msg_putchar('-');
}
}
@@ -3023,7 +3004,7 @@ void ex_z(exarg_T *eap)
if (minus && i == lnum) {
msg_putchar('\n');
- for (j = 1; j < Columns; j++) {
+ for (int j = 1; j < Columns; j++) {
msg_putchar('-');
}
}
@@ -3149,21 +3130,21 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, const char *sub, const ch
/// Slightly more memory that is strictly necessary is allocated to reduce the
/// frequency of memory (re)allocation.
///
-/// @param[in,out] new_start pointer to the memory for the replacement text
-/// @param[in] needed_len amount of memory needed
+/// @param[in,out] new_start pointer to the memory for the replacement text
+/// @param[in,out] new_start_len pointer to length of new_start
+/// @param[in] needed_len amount of memory needed
///
/// @returns pointer to the end of the allocated memory
-static char *sub_grow_buf(char **new_start, int needed_len)
- FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_RET
+static char *sub_grow_buf(char **new_start, int *new_start_len, int needed_len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- int new_start_len = 0;
char *new_end;
if (*new_start == NULL) {
// Get some space for a temporary buffer to do the
// substitution into (and some extra space to avoid
// too many calls to xmalloc()/free()).
- new_start_len = needed_len + 50;
- *new_start = xmalloc((size_t)new_start_len);
+ *new_start_len = needed_len + 50;
+ *new_start = xmalloc((size_t)(*new_start_len));
**new_start = NUL;
new_end = *new_start;
} else {
@@ -3172,9 +3153,9 @@ static char *sub_grow_buf(char **new_start, int needed_len)
// extra to avoid too many calls to xmalloc()/free()).
size_t len = strlen(*new_start);
needed_len += (int)len;
- if (needed_len > new_start_len) {
- new_start_len = needed_len + 50;
- *new_start = xrealloc(*new_start, (size_t)new_start_len);
+ if (needed_len > *new_start_len) {
+ *new_start_len = needed_len + 50;
+ *new_start = xrealloc(*new_start, (size_t)(*new_start_len));
}
new_end = *new_start + len;
}
@@ -3242,6 +3223,25 @@ static char *sub_parse_flags(char *cmd, subflags_T *subflags, int *which_pat)
return cmd;
}
+/// Skip over the "sub" part in :s/pat/sub/ where "delimiter" is the separating
+/// character.
+static char *skip_substitute(char *start, int delimiter)
+{
+ char *p = start;
+
+ while (p[0]) {
+ if (p[0] == delimiter) { // end delimiter found
+ *p++ = NUL; // replace it with a NUL
+ break;
+ }
+ if (p[0] == '\\' && p[1] != 0) { // skip escaped characters
+ p++;
+ }
+ MB_PTR_ADV(p);
+ }
+ return p;
+}
+
static int check_regexp_delim(int c)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -3259,9 +3259,11 @@ static int check_regexp_delim(int c)
///
/// The usual escapes are supported as described in the regexp docs.
///
-/// @param do_buf_event If `true`, send buffer updates.
+/// @param cmdpreview_ns The namespace to show 'inccommand' preview highlights.
+/// If <= 0, preview shouldn't be shown.
/// @return 0, 1 or 2. See show_cmdpreview() for more information on what the return value means.
-static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_ns,
+ const handle_T cmdpreview_bufnr)
{
#define ADJUST_SUB_FIRSTLNUM() \
do { \
@@ -3287,7 +3289,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
} \
} while (0)
- long i = 0;
+ int i = 0;
regmmatch_T regmatch;
static subflags_T subflags = {
.do_all = false,
@@ -3308,14 +3310,14 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
int which_pat;
char *cmd = eap->arg;
linenr_T first_line = 0; // first changed line
- linenr_T last_line= 0; // below last changed line AFTER the change
+ linenr_T last_line = 0; // below last changed line AFTER the change
linenr_T old_line_count = curbuf->b_ml.ml_line_count;
char *sub_firstline; // allocated copy of first sub line
bool endcolumn = false; // cursor in last column when done
PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 };
static int pre_hl_id = 0;
pos_T old_cursor = curwin->w_cursor;
- long start_nsubs;
+ int start_nsubs;
bool did_save = false;
@@ -3366,20 +3368,11 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// Small incompatibility: vi sees '\n' as end of the command, but in
// Vim we want to use '\n' to find/substitute a NUL.
- sub = cmd; // remember the start of the substitution
-
- while (cmd[0]) {
- if (cmd[0] == delimiter) { // end delimiter found
- *cmd++ = NUL; // replace it with a NUL
- break;
- }
- if (cmd[0] == '\\' && cmd[1] != 0) { // skip escaped characters
- cmd++;
- }
- MB_PTR_ADV(cmd);
- }
+ char *p = cmd; // remember the start of the substitution
+ cmd = skip_substitute(cmd, delimiter);
+ sub = xstrdup(p);
- if (!eap->skip && !cmdpreview) {
+ if (!eap->skip && cmdpreview_ns <= 0) {
sub_set_replacement((SubReplacementString) {
.sub = xstrdup(sub),
.timestamp = os_time(),
@@ -3392,14 +3385,15 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
return 0;
}
pat = NULL; // search_regcomp() will use previous pattern
- sub = old_sub.sub;
+ sub = xstrdup(old_sub.sub);
// Vi compatibility quirk: repeating with ":s" keeps the cursor in the
// last column after using "$".
endcolumn = (curwin->w_curswant == MAXCOL);
}
- if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, !cmdpreview)) {
+ if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, cmdpreview_ns <= 0)) {
+ xfree(sub);
return 0;
}
@@ -3411,9 +3405,16 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// check for a trailing count
cmd = skipwhite(cmd);
if (ascii_isdigit(*cmd)) {
- i = getdigits_long(&cmd, true, 0);
+ i = getdigits_int(&cmd, true, INT_MAX);
if (i <= 0 && !eap->skip && subflags.do_error) {
emsg(_(e_zerocount));
+ xfree(sub);
+ return 0;
+ } else if (i >= INT_MAX) {
+ char buf[20];
+ vim_snprintf(buf, sizeof(buf), "%d", i);
+ semsg(_(e_val_too_large), buf);
+ xfree(sub);
return 0;
}
eap->line1 = eap->line2;
@@ -3429,25 +3430,29 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
eap->nextcmd = check_nextcmd(cmd);
if (eap->nextcmd == NULL) {
semsg(_(e_trailing_arg), cmd);
+ xfree(sub);
return 0;
}
}
if (eap->skip) { // not executing commands, only parsing
+ xfree(sub);
return 0;
}
if (!subflags.do_count && !MODIFIABLE(curbuf)) {
// Substitution is not allowed in non-'modifiable' buffer
emsg(_(e_modifiable));
+ xfree(sub);
return 0;
}
if (search_regcomp(pat, NULL, RE_SUBST, which_pat,
- (cmdpreview ? 0 : SEARCH_HIS), &regmatch) == FAIL) {
+ (cmdpreview_ns > 0 ? 0 : SEARCH_HIS), &regmatch) == FAIL) {
if (subflags.do_error) {
emsg(_(e_invcmd));
}
+ xfree(sub);
return 0;
}
@@ -3462,22 +3467,20 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
assert(sub != NULL);
- char *sub_copy = NULL;
-
// If the substitute pattern starts with "\=" then it's an expression.
// Make a copy, a recursive function may free it.
// Otherwise, '~' in the substitute pattern is replaced with the old
// pattern. We do it here once to avoid it to be replaced over and over
// again.
if (sub[0] == '\\' && sub[1] == '=') {
- sub = xstrdup(sub);
- sub_copy = sub;
+ char *p = xstrdup(sub);
+ xfree(sub);
+ sub = p;
} else {
- char *newsub = regtilde(sub, magic_isset(), cmdpreview);
- if (newsub != sub) {
- // newsub was allocated, free it later.
- sub_copy = newsub;
- sub = newsub;
+ char *p = regtilde(sub, magic_isset(), cmdpreview_ns > 0);
+ if (p != sub) {
+ xfree(sub);
+ sub = p;
}
}
@@ -3487,20 +3490,21 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
- && (!cmdpreview || preview_lines.lines_needed <= (linenr_T)p_cwh
+ && (cmdpreview_ns <= 0 || preview_lines.lines_needed <= (linenr_T)p_cwh
|| lnum <= curwin->w_botline);
lnum++) {
- long nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
- (colnr_T)0, NULL, NULL);
+ int nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
+ 0, NULL, NULL);
if (nmatch) {
colnr_T copycol;
colnr_T matchcol;
colnr_T prev_matchcol = MAXCOL;
char *new_end, *new_start = NULL;
+ int new_start_len = 0;
char *p1;
bool did_sub = false;
int lastone;
- long nmatch_tl = 0; // nr of lines matched below lnum
+ linenr_T nmatch_tl = 0; // nr of lines matched below lnum
int do_again; // do it again after joining lines
bool skip_match = false;
linenr_T sub_firstlnum; // nr of first sub line
@@ -3542,7 +3546,8 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// accordingly.
//
// The new text is built up in new_start[]. It has some extra
- // room to avoid using xmalloc()/free() too often.
+ // room to avoid using xmalloc()/free() too often. new_start_len is
+ // the length of the allocated memory at new_start.
//
// Make a copy of the old line, so it won't be taken away when
// updating the screen or handling a multi-line match. The "old_"
@@ -3563,10 +3568,10 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// 3. substitute the string.
// 4. if subflags.do_all is set, find next match
// 5. break if there isn't another match in this line
- for (;;) {
+ while (true) {
SubResult current_match = {
.start = { 0, 0 },
- .end = { 0, 0 },
+ .end = { 0, 0 },
.pre_match = 0,
};
// lnum is where the match start, but maybe not the pattern match,
@@ -3648,7 +3653,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
}
}
- if (subflags.do_ask && !cmdpreview) {
+ if (subflags.do_ask && cmdpreview_ns <= 0) {
int typed = 0;
// change State to MODE_CONFIRM, so that the mouse works
@@ -3754,6 +3759,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
update_topline(curwin);
validate_cursor();
redraw_later(curwin, UPD_SOME_VALID);
+ show_cursor_info_later(true);
update_screen();
highlight_match = false;
redraw_later(curwin, UPD_SOME_VALID);
@@ -3768,11 +3774,10 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// needed
msg_no_more = true;
msg_ext_set_kind("confirm_sub");
- smsg_attr(HL_ATTR(HLF_R), // Same highlight as wait_return().
- _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
+ // Same highlight as wait_return().
+ smsg(HL_ATTR(HLF_R), _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
msg_no_more = false;
- msg_scroll = (int)i;
- show_cursor_info(true);
+ msg_scroll = i;
if (!ui_has(kUIMessages)) {
ui_cursor_goto(msg_row, msg_col);
}
@@ -3856,17 +3861,21 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1;
current_match.end.lnum = sub_firstlnum + (linenr_T)nmatch;
skip_match = true;
+ // safety check
+ if (nmatch < 0) {
+ goto skip;
+ }
}
// Save the line numbers for the preview buffer
// NOTE: If the pattern matches a final newline, the next line will
// be shown also, but should not be highlighted. Intentional for now.
- if (cmdpreview && !has_second_delim) {
+ if (cmdpreview_ns > 0 && !has_second_delim) {
current_match.start.col = regmatch.startpos[0].col;
if (current_match.end.lnum == 0) {
current_match.end.lnum = sub_firstlnum + (linenr_T)nmatch - 1;
}
- current_match.end.col = regmatch.endpos[0].col;
+ current_match.end.col = regmatch.endpos[0].col;
ADJUST_SUB_FIRSTLNUM();
lnum += (linenr_T)nmatch - 1;
@@ -3876,8 +3885,8 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// 3. Substitute the string. During 'inccommand' preview only do this if
// there is a replace pattern.
- if (!cmdpreview || has_second_delim) {
- long lnum_start = lnum; // save the start lnum
+ if (cmdpreview_ns <= 0 || has_second_delim) {
+ linenr_T lnum_start = lnum; // save the start lnum
int save_ma = curbuf->b_p_ma;
int save_sandbox = sandbox;
if (subflags.do_count) {
@@ -3921,15 +3930,19 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
p1 = ml_get(sub_firstlnum + (linenr_T)nmatch - 1);
nmatch_tl += nmatch - 1;
}
- size_t copy_len = (size_t)(regmatch.startpos[0].col - copycol);
- new_end = sub_grow_buf(&new_start,
+ int copy_len = regmatch.startpos[0].col - copycol;
+ new_end = sub_grow_buf(&new_start, &new_start_len,
(colnr_T)strlen(p1) - regmatch.endpos[0].col
- + (colnr_T)copy_len + sublen + 1);
+ + copy_len + sublen + 1);
// copy the text up to the part that matched
- memmove(new_end, sub_firstline + copycol, copy_len);
+ memmove(new_end, sub_firstline + copycol, (size_t)copy_len);
new_end += copy_len;
+ if (new_start_len - copy_len < sublen) {
+ sublen = new_start_len - copy_len - 1;
+ }
+
// Finally, at this point we can know where the match actually will
// start in the new text
int start_col = (int)(new_end - new_start);
@@ -3977,10 +3990,10 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
*p1 = NUL; // truncate up to the CR
ml_append(lnum - 1, new_start,
(colnr_T)(p1 - new_start + 1), false);
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1, 0, kExtmarkNOOP);
if (subflags.do_ask) {
- appended_lines(lnum - 1, 1L);
+ appended_lines(lnum - 1, 1);
} else {
if (first_line == 0) {
first_line = lnum;
@@ -4015,7 +4028,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
}
extmark_splice(curbuf, (int)lnum_start - 1, start_col,
end.lnum - start.lnum, matchcols, replaced_bytes,
- lnum - (linenr_T)lnum_start, subcols, sublen - 1, kExtmarkUndo);
+ lnum - lnum_start, subcols, sublen - 1, kExtmarkUndo);
}
// 4. If subflags.do_all is set, find next match.
@@ -4078,13 +4091,12 @@ skip:
for (i = 0; i < nmatch_tl; i++) {
ml_delete(lnum, false);
}
- mark_adjust(lnum, lnum + (linenr_T)nmatch_tl - 1,
- (long)MAXLNUM, (linenr_T)(-nmatch_tl), kExtmarkNOOP);
+ mark_adjust(lnum, lnum + nmatch_tl - 1, MAXLNUM, -nmatch_tl, kExtmarkNOOP);
if (subflags.do_ask) {
- deleted_lines(lnum, (linenr_T)nmatch_tl);
+ deleted_lines(lnum, nmatch_tl);
}
lnum--;
- line2 -= (linenr_T)nmatch_tl; // nr of lines decreases
+ line2 -= nmatch_tl; // nr of lines decreases
nmatch_tl = 0;
}
@@ -4126,7 +4138,7 @@ skip:
#define PUSH_PREVIEW_LINES() \
do { \
- if (cmdpreview) { \
+ if (cmdpreview_ns > 0) { \
linenr_T match_lines = current_match.end.lnum \
- current_match.start.lnum +1; \
if (preview_lines.subresults.size > 0) { \
@@ -4178,7 +4190,7 @@ skip:
// the line number before the change (same as adding the number of
// deleted lines).
i = curbuf->b_ml.ml_line_count - old_line_count;
- changed_lines(first_line, 0, last_line - (linenr_T)i, (linenr_T)i, false);
+ changed_lines(curbuf, first_line, 0, last_line - (linenr_T)i, (linenr_T)i, false);
int64_t num_added = last_line - first_line;
int64_t num_removed = num_added - i;
@@ -4209,8 +4221,8 @@ skip:
beginline(BL_WHITE | BL_FIX);
}
}
- if (!cmdpreview && !do_sub_msg(subflags.do_count) && subflags.do_ask && p_ch > 0) {
- msg("");
+ if (cmdpreview_ns <= 0 && !do_sub_msg(subflags.do_count) && subflags.do_ask && p_ch > 0) {
+ msg("", 0);
}
} else {
global_need_beginline = true;
@@ -4225,7 +4237,7 @@ skip:
} else if (got_match) {
// did find something but nothing substituted
if (p_ch > 0) {
- msg("");
+ msg("", 0);
}
} else if (subflags.do_error) {
// nothing found
@@ -4239,7 +4251,7 @@ skip:
}
vim_regfree(regmatch.regprog);
- xfree(sub_copy);
+ xfree(sub);
// Restore the flag values, they can be used for ":&&".
subflags.do_all = save_do_all;
@@ -4248,7 +4260,7 @@ skip:
int retv = 0;
// Show 'inccommand' preview if there are matched lines.
- if (cmdpreview && !aborting()) {
+ if (cmdpreview_ns > 0 && !aborting()) {
if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable.
set_string_option_direct("icm", -1, "", OPT_FREE, SID_NONE);
} else if (*p_icm != NUL && pat != NULL) {
@@ -4287,19 +4299,19 @@ bool do_sub_msg(bool count_only)
}
char *msg_single = count_only
- ? NGETTEXT("%" PRId64 " match on %" PRId64 " line",
- "%" PRId64 " matches on %" PRId64 " line", sub_nsubs)
- : NGETTEXT("%" PRId64 " substitution on %" PRId64 " line",
- "%" PRId64 " substitutions on %" PRId64 " line", sub_nsubs);
+ ? NGETTEXT("%" PRId64 " match on %" PRId64 " line",
+ "%" PRId64 " matches on %" PRId64 " line", sub_nsubs)
+ : NGETTEXT("%" PRId64 " substitution on %" PRId64 " line",
+ "%" PRId64 " substitutions on %" PRId64 " line", sub_nsubs);
char *msg_plural = count_only
- ? NGETTEXT("%" PRId64 " match on %" PRId64 " lines",
- "%" PRId64 " matches on %" PRId64 " lines", sub_nsubs)
- : NGETTEXT("%" PRId64 " substitution on %" PRId64 " lines",
- "%" PRId64 " substitutions on %" PRId64 " lines", sub_nsubs);
- vim_snprintf_add((char *)msg_buf, sizeof(msg_buf),
+ ? NGETTEXT("%" PRId64 " match on %" PRId64 " lines",
+ "%" PRId64 " matches on %" PRId64 " lines", sub_nsubs)
+ : NGETTEXT("%" PRId64 " substitution on %" PRId64 " lines",
+ "%" PRId64 " substitutions on %" PRId64 " lines", sub_nsubs);
+ vim_snprintf_add(msg_buf, sizeof(msg_buf),
NGETTEXT(msg_single, msg_plural, sub_nlines),
(int64_t)sub_nsubs, (int64_t)sub_nlines);
- if (msg(msg_buf)) {
+ if (msg(msg_buf, 0)) {
// save message to display it after redraw
set_keep_msg(msg_buf, 0);
}
@@ -4340,15 +4352,12 @@ static void global_exe_one(char *const cmd, const linenr_T lnum)
void ex_global(exarg_T *eap)
{
linenr_T lnum; // line number according to old situation
- int ndone = 0;
int type; // first char of cmd: 'v' or 'g'
- char *cmd; // command argument
+ char *cmd; // command argument
char delim; // delimiter, normally '/'
char *pat;
regmmatch_T regmatch;
- int match;
- int which_pat;
// When nesting the command works on one line. This allows for
// ":g/found/v/notfound/command".
@@ -4365,7 +4374,7 @@ void ex_global(exarg_T *eap)
type = (uint8_t)(*eap->cmd);
}
cmd = eap->arg;
- which_pat = RE_LAST; // default: use last used regexp
+ int which_pat = RE_LAST; // default: use last used regexp
// undocumented vi feature:
// "\/" and "\?": use previous search pattern.
@@ -4407,15 +4416,16 @@ void ex_global(exarg_T *eap)
if (global_busy) {
lnum = curwin->w_cursor.lnum;
- match = (int)vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
+ int match = vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
if ((type == 'g' && match) || (type == 'v' && !match)) {
global_exe_one(cmd, lnum);
}
} else {
+ int ndone = 0;
// pass 1: set marks for each (not) matching line
for (lnum = eap->line1; lnum <= eap->line2 && !got_int; lnum++) {
// a match on this line?
- match = (int)vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
+ int match = vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
if (regmatch.regprog == NULL) {
break; // re-compiling regprog failed
}
@@ -4428,12 +4438,12 @@ void ex_global(exarg_T *eap)
// pass 2: execute the command for each line that has been marked
if (got_int) {
- msg(_(e_interr));
+ msg(_(e_interr), 0);
} else if (ndone == 0) {
if (type == 'v') {
- smsg(_("Pattern found in every line: %s"), used_pat);
+ smsg(0, _("Pattern found in every line: %s"), used_pat);
} else {
- smsg(_("Pattern not found: %s"), used_pat);
+ smsg(0, _("Pattern not found: %s"), used_pat);
}
} else {
global_exe(cmd);
@@ -4541,7 +4551,7 @@ bool prepare_tagpreview(bool undo_sync)
///
/// @return 1 if preview window isn't needed, 2 if preview window is needed.
static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, int hl_id,
- long cmdpreview_ns, handle_T cmdpreview_bufnr)
+ int cmdpreview_ns, handle_T cmdpreview_bufnr)
FUNC_ATTR_NONNULL_ALL
{
char *save_shm_p = xstrdup(p_shm);
@@ -4589,15 +4599,15 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i
linenr_T linenr_origbuf = 0; // last line added to original buffer
linenr_T next_linenr = 0; // next line to show for the match
- // Temporarily switch to preview buffer
- aco_save_T aco;
-
for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
SubResult match = lines.subresults.items[matchidx];
if (cmdpreview_buf) {
lpos_T p_start = { 0, match.start.col }; // match starts here in preview
- lpos_T p_end = { 0, match.end.col }; // ... and ends here
+ lpos_T p_end = { 0, match.end.col }; // ... and ends here
+
+ // You Might Gonna Need It
+ buf_ensure_loaded(cmdpreview_buf);
if (match.pre_match == 0) {
next_linenr = match.start.lnum;
@@ -4622,7 +4632,7 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i
if (next_linenr == orig_buf->b_ml.ml_line_count + 1) {
line = "";
} else {
- line = ml_get_buf(orig_buf, next_linenr, false);
+ line = ml_get_buf(orig_buf, next_linenr);
line_size = strlen(line) + (size_t)col_width + 1;
// Reallocate if line not long enough
@@ -4634,21 +4644,18 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i
// Put "|lnum| line" into `str` and append it to the preview buffer.
snprintf(str, line_size, "|%*" PRIdLINENR "| %s", col_width - 3,
next_linenr, line);
- // Temporarily switch to preview buffer
- aucmd_prepbuf(&aco, cmdpreview_buf);
if (linenr_preview == 0) {
- ml_replace(1, str, true);
+ ml_replace_buf(cmdpreview_buf, 1, str, true);
} else {
- ml_append(linenr_preview, str, (colnr_T)line_size, false);
+ ml_append_buf(cmdpreview_buf, linenr_preview, str, (colnr_T)line_size, false);
}
- aucmd_restbuf(&aco);
linenr_preview += 1;
}
linenr_origbuf = match.end.lnum;
- bufhl_add_hl_pos_offset(cmdpreview_buf, (int)cmdpreview_ns, hl_id, p_start, p_end, col_width);
+ bufhl_add_hl_pos_offset(cmdpreview_buf, cmdpreview_ns, hl_id, p_start, p_end, col_width);
}
- bufhl_add_hl_pos_offset(orig_buf, (int)cmdpreview_ns, hl_id, match.start, match.end, 0);
+ bufhl_add_hl_pos_offset(orig_buf, cmdpreview_ns, hl_id, match.start, match.end, 0);
}
xfree(str);
@@ -4666,7 +4673,7 @@ void ex_substitute(exarg_T *eap)
}
/// :substitute command preview callback.
-int ex_substitute_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+int ex_substitute_preview(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr)
{
// Only preview once the pattern delimiter has been typed
if (*eap->arg && !ASCII_ISALNUM(*eap->arg)) {
@@ -4688,8 +4695,6 @@ int ex_substitute_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_
/// @return a pointer to the char just past the pattern plus flags.
char *skip_vimgrep_pat(char *p, char **s, int *flags)
{
- int c;
-
if (vim_isIDc((uint8_t)(*p))) {
// ":vimgrep pattern fname"
if (s != NULL) {
@@ -4704,7 +4709,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags)
if (s != NULL) {
*s = p + 1;
}
- c = (uint8_t)(*p);
+ int c = (uint8_t)(*p);
p = skip_regexp(p + 1, c, true);
if (*p != c) {
return NULL;
@@ -4737,10 +4742,10 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags)
void ex_oldfiles(exarg_T *eap)
{
list_T *l = get_vim_var_list(VV_OLDFILES);
- long nr = 0;
+ int nr = 0;
if (l == NULL) {
- msg(_("No old files"));
+ msg(_("No old files"), 0);
return;
}
@@ -4752,10 +4757,10 @@ void ex_oldfiles(exarg_T *eap)
}
nr++;
const char *fname = tv_get_string(TV_LIST_ITEM_TV(li));
- if (!message_filtered((char *)fname)) {
+ if (!message_filtered(fname)) {
msg_outnum(nr);
msg_puts(": ");
- msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li)));
+ msg_outtrans(tv_get_string(TV_LIST_ITEM_TV(li)), 0);
msg_clr_eos();
msg_putchar('\n');
os_breakcheck();
@@ -4771,7 +4776,7 @@ void ex_oldfiles(exarg_T *eap)
nr = prompt_for_number(false);
msg_starthere();
if (nr > 0 && nr <= tv_list_len(l)) {
- const char *const p = tv_list_find_str(l, (int)nr - 1);
+ const char *const p = tv_list_find_str(l, nr - 1);
if (p == NULL) {
return;
}
@@ -4784,29 +4789,3 @@ void ex_oldfiles(exarg_T *eap)
}
}
}
-
-void ex_trust(exarg_T *eap)
-{
- const char *const p = skiptowhite(eap->arg);
- char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg));
- const char *action = "allow";
- const char *path = skipwhite(p);
-
- if (strcmp(arg1, "++deny") == 0) {
- action = "deny";
- } else if (strcmp(arg1, "++remove") == 0) {
- action = "remove";
- } else if (*arg1 != '\0') {
- semsg(e_invarg2, arg1);
- goto theend;
- }
-
- if (path[0] == '\0') {
- path = NULL;
- }
-
- nlua_trust(action, path);
-
-theend:
- xfree(arg1);
-}
diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h
index 39bff3e35d..de13f03197 100644
--- a/src/nvim/ex_cmds.h
+++ b/src/nvim/ex_cmds.h
@@ -1,14 +1,12 @@
-#ifndef NVIM_EX_CMDS_H
-#define NVIM_EX_CMDS_H
+#pragma once
#include <stdbool.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: export
#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
// flags for do_ecmd()
#define ECMD_HIDE 0x01 // don't free the current buffer
@@ -21,9 +19,9 @@
#define ECMD_NOWINENTER 0x40 // do not trigger BufWinEnter
// for lnum argument in do_ecmd()
-#define ECMD_LASTL (linenr_T)0 // use last position in loaded file
-#define ECMD_LAST ((linenr_T)(-1)) // use last position in all files
-#define ECMD_ONE (linenr_T)1 // use first line
+#define ECMD_LASTL 0 // use last position in loaded file
+#define ECMD_LAST (-1) // use last position in all files
+#define ECMD_ONE 1 // use first line
/// Previous :substitute replacement string definition
typedef struct {
@@ -35,4 +33,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.h.generated.h"
#endif
-#endif // NVIM_EX_CMDS_H
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index c8b6ceab69..4859a70553 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -197,12 +197,6 @@ module.cmds = {
func='ex_bunload',
},
{
- command='behave',
- flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK),
- addr_type='ADDR_NONE',
- func='ex_behave',
- },
- {
command='belowright',
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM),
addr_type='ADDR_NONE',
@@ -640,7 +634,7 @@ module.cmds = {
command='const',
flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK),
addr_type='ADDR_NONE',
- func='ex_const',
+ func='ex_let',
},
{
command='copen',
@@ -721,6 +715,12 @@ module.cmds = {
func='ex_debuggreedy',
},
{
+ command='defer',
+ flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK),
+ addr_type='ADDR_NONE',
+ func='ex_call',
+ },
+ {
command='delcommand',
flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK),
addr_type='ADDR_NONE',
@@ -1045,6 +1045,12 @@ module.cmds = {
func='ex_function',
},
{
+ command='fclose',
+ flags=bit.bor(BANG, RANGE),
+ addr_type='ADDR_OTHER',
+ func='ex_fclose',
+ },
+ {
command='global',
flags=bit.bor(RANGE, WHOLEFOLD, BANG, EXTRA, DFLALL, SBOXOK, CMDWIN, LOCK_OK),
addr_type='ADDR_LINES',
@@ -2212,7 +2218,7 @@ module.cmds = {
},
{
command='registers',
- flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, CMDWIN, LOCK_OK),
+ flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, SBOXOK, CMDWIN, LOCK_OK),
addr_type='ADDR_NONE',
func='ex_display',
},
@@ -3319,7 +3325,7 @@ module.cmds = {
{
command='=',
enum='CMD_equal',
- flags=bit.bor(RANGE, TRLBAR, DFLALL, FLAGS, CMDWIN, LOCK_OK),
+ flags=bit.bor(RANGE, EXTRA, DFLALL, ARGOPT, CMDWIN, LOCK_OK),
addr_type='ADDR_LINES',
func='ex_equal',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index d08823bc30..1722b7902b 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file ex_cmds2.c
///
/// Some more functions for command line commands
@@ -12,14 +9,14 @@
#include <string.h>
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
+#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
@@ -29,28 +26,30 @@
#include "nvim/fileio.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/macros.h"
+#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
-#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds2.c.generated.h"
#endif
+static const char e_compiler_not_supported_str[]
+ = N_("E666: Compiler not supported: %s");
+
void ex_ruby(exarg_T *eap)
{
script_host_execute("ruby", eap);
@@ -102,7 +101,6 @@ void ex_perldo(exarg_T *eap)
/// @return FAIL for failure, OK otherwise
int autowrite(buf_T *buf, int forceit)
{
- int r;
bufref_T bufref;
if (!(p_aw || p_awa) || !p_write
@@ -112,7 +110,7 @@ int autowrite(buf_T *buf, int forceit)
return FAIL;
}
set_bufref(&bufref, buf);
- r = buf_write_all(buf, forceit);
+ int r = buf_write_all(buf, forceit);
// Writing may succeed but the buffer still changed, e.g., when there is a
// conversion error. We do want to return FAIL then.
@@ -201,7 +199,7 @@ void dialog_changed(buf_T *buf, bool checkall)
.forceit = false,
};
- dialog_msg((char *)buff, _("Save changes to \"%s\"?"), buf->b_fname);
+ dialog_msg(buff, _("Save changes to \"%s\"?"), buf->b_fname);
if (checkall) {
ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, buff, 1);
} else {
@@ -274,9 +272,7 @@ bool can_abandon(buf_T *buf, int forceit)
/// Add a buffer number to "bufnrs", unless it's already there.
static void add_bufnum(int *bufnrs, int *bufnump, int nr)
{
- int i;
-
- for (i = 0; i < *bufnump; i++) {
+ for (int i = 0; i < *bufnump; i++) {
if (bufnrs[i] == nr) {
return;
}
@@ -297,11 +293,9 @@ static void add_bufnum(int *bufnrs, int *bufnump, int nr)
bool check_changed_any(bool hidden, bool unload)
{
bool ret = false;
- int save;
int i;
int bufnum = 0;
size_t bufcount = 0;
- int *bufnrs;
// Make a list of all buffers, with the most important ones first.
FOR_ALL_BUFFERS(buf) {
@@ -312,7 +306,7 @@ bool check_changed_any(bool hidden, bool unload)
return false;
}
- bufnrs = xmalloc(sizeof(*bufnrs) * bufcount);
+ int *bufnrs = xmalloc(sizeof(*bufnrs) * bufcount);
// curbuf
bufnrs[bufnum++] = curbuf->b_fnum;
@@ -380,7 +374,7 @@ bool check_changed_any(bool hidden, bool unload)
? semsg(_("E947: Job still running in buffer \"%s\""), buf->b_fname)
: semsg(_("E162: No write since last change for buffer \"%s\""),
buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname)) {
- save = no_wait_return;
+ int save = no_wait_return;
no_wait_return = false;
wait_return(false);
no_wait_return = save;
@@ -430,15 +424,14 @@ int check_fname(void)
/// @return FAIL for failure, OK otherwise
int buf_write_all(buf_T *buf, int forceit)
{
- int retval;
buf_T *old_curbuf = curbuf;
- retval = (buf_write(buf, buf->b_ffname, buf->b_fname,
- (linenr_T)1, buf->b_ml.ml_line_count, NULL,
- false, forceit, true, false));
+ int retval = (buf_write(buf, buf->b_ffname, buf->b_fname,
+ 1, buf->b_ml.ml_line_count, NULL,
+ false, forceit, true, false));
if (curbuf != old_curbuf) {
msg_source(HL_ATTR(HLF_W));
- msg(_("Warning: Entered other buffer unexpectedly (check autocommands)"));
+ msg(_("Warning: Entered other buffer unexpectedly (check autocommands)"), 0);
}
return retval;
}
@@ -446,12 +439,11 @@ int buf_write_all(buf_T *buf, int forceit)
/// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo"
void ex_listdo(exarg_T *eap)
{
- int i;
- win_T *wp;
- tabpage_T *tp;
- int next_fnum = 0;
char *save_ei = NULL;
- char *p_shm_save;
+
+ // Temporarily override SHM_OVER and SHM_OVERALL to avoid that file
+ // message overwrites output from the command.
+ msg_listdo_overwrite++;
if (eap->cmdidx != CMD_windo && eap->cmdidx != CMD_tabdo) {
// Don't do syntax HL autocommands. Skipping the syntax file is a
@@ -469,10 +461,11 @@ void ex_listdo(exarg_T *eap)
|| !check_changed(curbuf, CCGD_AW
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)) {
- i = 0;
+ int next_fnum = 0;
+ int i = 0;
// start at the eap->line1 argument/window/buffer
- wp = firstwin;
- tp = first_tabpage;
+ win_T *wp = firstwin;
+ tabpage_T *tp = first_tabpage;
switch (eap->cmdidx) {
case CMD_windo:
for (; wp != NULL && i + 1 < eap->line1; wp = wp->w_next) {
@@ -542,11 +535,7 @@ void ex_listdo(exarg_T *eap)
if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) {
// Clear 'shm' to avoid that the file message overwrites
// any output from the command.
- p_shm_save = xstrdup(p_shm);
- set_option_value_give_err("shm", 0L, "", 0);
do_argfile(eap, i);
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
- xfree(p_shm_save);
}
if (curwin->w_arg_idx != i) {
break;
@@ -609,13 +598,8 @@ void ex_listdo(exarg_T *eap)
break;
}
- // Go to the next buffer. Clear 'shm' to avoid that the file
- // message overwrites any output from the command.
- p_shm_save = xstrdup(p_shm);
- set_option_value_give_err("shm", 0L, "", 0);
+ // Go to the next buffer.
goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum);
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
- xfree(p_shm_save);
// If autocommands took us elsewhere, quit here.
if (curbuf->b_fnum != next_fnum) {
@@ -632,13 +616,7 @@ void ex_listdo(exarg_T *eap)
size_t qf_idx = qf_get_cur_idx(eap);
- // Clear 'shm' to avoid that the file message overwrites
- // any output from the command.
- p_shm_save = xstrdup(p_shm);
- set_option_value_give_err("shm", 0L, "", 0);
ex_cnext(eap);
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
- xfree(p_shm_save);
// If jumping to the next quickfix entry fails, quit here.
if (qf_get_cur_idx(eap) == qf_idx) {
@@ -665,6 +643,7 @@ void ex_listdo(exarg_T *eap)
listcmd_busy = false;
}
+ msg_listdo_overwrite--;
if (save_ei != NULL) {
buf_T *bnext;
aco_save_T aco;
@@ -697,9 +676,7 @@ void ex_listdo(exarg_T *eap)
/// ":compiler[!] {name}"
void ex_compiler(exarg_T *eap)
{
- char *buf;
char *old_cur_comp = NULL;
- char *p;
if (*eap->arg == NUL) {
// List all compiler scripts.
@@ -709,7 +686,7 @@ void ex_compiler(exarg_T *eap)
}
size_t bufsize = strlen(eap->arg) + 14;
- buf = xmalloc(bufsize);
+ char *buf = xmalloc(bufsize);
if (eap->forceit) {
// ":compiler! {name}" sets global options
@@ -730,20 +707,16 @@ void ex_compiler(exarg_T *eap)
do_unlet(S_LEN("g:current_compiler"), true);
do_unlet(S_LEN("b:current_compiler"), true);
- snprintf(buf, bufsize, "compiler/%s.vim", eap->arg);
- if (source_runtime(buf, DIP_ALL) == FAIL) {
- // Try lua compiler
- snprintf(buf, bufsize, "compiler/%s.lua", eap->arg);
- if (source_runtime(buf, DIP_ALL) == FAIL) {
- semsg(_("E666: compiler not supported: %s"), eap->arg);
- }
+ snprintf(buf, bufsize, "compiler/%s.*", eap->arg);
+ if (source_runtime_vim_lua(buf, DIP_ALL) == FAIL) {
+ semsg(_(e_compiler_not_supported_str), eap->arg);
}
xfree(buf);
do_cmdline_cmd(":delcommand CompilerSet");
// Set "b:current_compiler" from "current_compiler".
- p = get_var_value("g:current_compiler");
+ char *p = get_var_value("g:current_compiler");
if (p != NULL) {
set_internal_string_var("b:current_compiler", p);
}
@@ -762,14 +735,13 @@ void ex_compiler(exarg_T *eap)
/// ":checktime [buffer]"
void ex_checktime(exarg_T *eap)
{
- buf_T *buf;
int save_no_check_timestamps = no_check_timestamps;
no_check_timestamps = 0;
if (eap->addr_count == 0) { // default is all buffers
check_timestamps(false);
} else {
- buf = buflist_findnr((int)eap->line2);
+ buf_T *buf = buflist_findnr((int)eap->line2);
if (buf != NULL) { // cannot happen?
(void)buf_check_timestamp(buf);
}
@@ -816,7 +788,7 @@ static void script_host_do_range(char *name, exarg_T *eap)
list_T *args = tv_list_alloc(3);
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- tv_list_append_string(args, (const char *)eap->arg, -1);
+ tv_list_append_string(args, eap->arg, -1);
(void)eval_call_provider(name, "do_range", args, true);
}
}
@@ -827,7 +799,6 @@ static void script_host_do_range(char *name, exarg_T *eap)
void ex_drop(exarg_T *eap)
{
bool split = false;
- buf_T *buf;
// Check if the first argument is already being edited in a window. If
// so, jump to that window.
@@ -855,7 +826,7 @@ void ex_drop(exarg_T *eap)
// ":drop file ...": Edit the first argument. Jump to an existing
// window if possible, edit in current window if the current buffer
// can be abandoned, otherwise open a new window.
- buf = buflist_findnr(ARGLIST[0].ae_fnum);
+ buf_T *buf = buflist_findnr(ARGLIST[0].ae_fnum);
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == buf) {
diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h
index 3a41e105f3..4f41f2cc41 100644
--- a/src/nvim/ex_cmds2.h
+++ b/src/nvim/ex_cmds2.h
@@ -1,18 +1,17 @@
-#ifndef NVIM_EX_CMDS2_H
-#define NVIM_EX_CMDS2_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
-//
-// flags for check_changed()
-//
-#define CCGD_AW 1 // do autowrite if buffer was changed
-#define CCGD_MULTWIN 2 // check also when several wins for the buf
-#define CCGD_FORCEIT 4 // ! used
-#define CCGD_ALLBUF 8 // may write all buffers
-#define CCGD_EXCMD 16 // may suggest using !
+/// flags for check_changed()
+enum {
+ CCGD_AW = 1, ///< do autowrite if buffer was changed
+ CCGD_MULTWIN = 2, ///< check also when several wins for the buf
+ CCGD_FORCEIT = 4, ///< ! used
+ CCGD_ALLBUF = 8, ///< may write all buffers
+ CCGD_EXCMD = 16, ///< may suggest using !
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds2.h.generated.h"
#endif
-#endif // NVIM_EX_CMDS2_H
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 629aaf14cf..00363884ec 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_EX_CMDS_DEFS_H
-#define NVIM_EX_CMDS_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
-#include "nvim/eval/typval.h"
-#include "nvim/normal.h"
-#include "nvim/pos.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_eval_defs.h"
+#include "nvim/normal_defs.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -69,20 +69,20 @@
#define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file
#define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed
-// values for cmd_addr_type
+/// values for cmd_addr_type
typedef enum {
- ADDR_LINES, // buffer line numbers
- ADDR_WINDOWS, // window number
- ADDR_ARGUMENTS, // argument number
- ADDR_LOADED_BUFFERS, // buffer number of loaded buffer
- ADDR_BUFFERS, // buffer number
- ADDR_TABS, // tab page number
- ADDR_TABS_RELATIVE, // Tab page that only relative
- ADDR_QUICKFIX_VALID, // quickfix list valid entry number
- ADDR_QUICKFIX, // quickfix list entry number
- ADDR_UNSIGNED, // positive count or zero, defaults to 1
- ADDR_OTHER, // something else, use line number for '$', '%', etc.
- ADDR_NONE, // no range used
+ ADDR_LINES, ///< buffer line numbers
+ ADDR_WINDOWS, ///< window number
+ ADDR_ARGUMENTS, ///< argument number
+ ADDR_LOADED_BUFFERS, ///< buffer number of loaded buffer
+ ADDR_BUFFERS, ///< buffer number
+ ADDR_TABS, ///< tab page number
+ ADDR_TABS_RELATIVE, ///< Tab page that only relative
+ ADDR_QUICKFIX_VALID, ///< quickfix list valid entry number
+ ADDR_QUICKFIX, ///< quickfix list entry number
+ ADDR_UNSIGNED, ///< positive count or zero, defaults to 1
+ ADDR_OTHER, ///< something else, use line number for '$', '%', etc.
+ ADDR_NONE, ///< no range used
} cmd_addr_T;
typedef struct exarg exarg_T;
@@ -93,7 +93,7 @@ typedef struct exarg exarg_T;
#define BAD_DROP (-2) // erase it
typedef void (*ex_func_T)(exarg_T *eap);
-typedef int (*ex_preview_func_T)(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr);
+typedef int (*ex_preview_func_T)(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr);
// NOTE: These possible could be removed and changed so that
// Callback could take a "command" style string, and simply
@@ -128,54 +128,13 @@ typedef char *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition.
typedef struct cmdname {
- char *cmd_name; ///< Name of the command.
- ex_func_T cmd_func; ///< Function with implementation of this command.
- ex_preview_func_T cmd_preview_func; ///< Preview callback function of this command.
- uint32_t cmd_argt; ///< Relevant flags from the declared above.
- cmd_addr_T cmd_addr_type; ///< Flag for address type.
+ char *cmd_name; ///< Name of the command.
+ ex_func_T cmd_func; ///< Function with implementation of this command.
+ ex_preview_func_T cmd_preview_func; ///< Preview callback function of this command.
+ uint32_t cmd_argt; ///< Relevant flags from the declared above.
+ cmd_addr_T cmd_addr_type; ///< Flag for address type.
} CommandDefinition;
-// A list used for saving values of "emsg_silent". Used by ex_try() to save the
-// value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT
-// flag below is set.
-typedef struct eslist_elem eslist_T;
-struct eslist_elem {
- int saved_emsg_silent; // saved value of "emsg_silent"
- eslist_T *next; // next element on the list
-};
-
-// For conditional commands a stack is kept of nested conditionals.
-// When cs_idx < 0, there is no conditional command.
-enum {
- CSTACK_LEN = 50,
-};
-
-typedef struct {
- int cs_flags[CSTACK_LEN]; // CSF_ flags
- char cs_pending[CSTACK_LEN]; // CSTP_: what's pending in ":finally"
- union {
- void *csp_rv[CSTACK_LEN]; // return typeval for pending return
- void *csp_ex[CSTACK_LEN]; // exception for pending throw
- } cs_pend;
- void *cs_forinfo[CSTACK_LEN]; // info used by ":for"
- int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line
- int cs_idx; // current entry, or -1 if none
- int cs_looplevel; // nr of nested ":while"s and ":for"s
- int cs_trylevel; // nr of nested ":try"s
- eslist_T *cs_emsg_silent_list; // saved values of "emsg_silent"
- int cs_lflags; // loop flags: CSL_ flags
-} cstack_T;
-#define cs_rettv cs_pend.csp_rv
-#define cs_exception cs_pend.csp_ex
-
-// Flags for the cs_lflags item in cstack_T.
-enum {
- CSL_HAD_LOOP = 1, // just found ":while" or ":for"
- CSL_HAD_ENDLOOP = 2, // just found ":endwhile" or ":endfor"
- CSL_HAD_CONT = 4, // just found ":continue"
- CSL_HAD_FINA = 8, // just found ":finally"
-};
-
/// Arguments used for Ex commands.
struct exarg {
char *arg; ///< argument of the command
@@ -185,6 +144,7 @@ struct exarg {
char *nextcmd; ///< next command (NULL if none)
char *cmd; ///< the name of the command (except for :make)
char **cmdlinep; ///< pointer to pointer of allocated cmdline
+ char *cmdline_tofree; ///< free later
cmdidx_T cmdidx; ///< the index for the command
uint32_t argt; ///< flags for the command
int skip; ///< don't execute the command, only parse it
@@ -221,32 +181,6 @@ struct exarg {
#define EXFLAG_NR 0x02 // '#': number
#define EXFLAG_PRINT 0x04 // 'p': print
-// used for completion on the command line
-struct expand {
- char *xp_pattern; // start of item to expand
- int xp_context; // type of expansion
- size_t xp_pattern_len; // bytes in xp_pattern before cursor
- char *xp_arg; // completion function
- LuaRef xp_luaref; // Ref to Lua completion function
- sctx_T xp_script_ctx; // SCTX for completion function
- int xp_backslash; // one of the XP_BS_ values
-#ifndef BACKSLASH_IN_FILENAME
- int xp_shell; // true for a shell command, more
- // characters need to be escaped
-#endif
- int xp_numfiles; // number of files found by file name completion
- int xp_col; // cursor position in line
- char **xp_files; // list of files
- char *xp_line; // text being completed
-#define EXPAND_BUF_LEN 256
- char xp_buf[EXPAND_BUF_LEN]; // buffer for returned match
-};
-
-// values for xp_backslash
-#define XP_BS_NONE 0 // nothing special for backslashes
-#define XP_BS_ONE 1 // uses one backslash before a space
-#define XP_BS_THREE 2 // uses three backslashes before a space
-
enum {
CMOD_SANDBOX = 0x0001, ///< ":sandbox"
CMOD_SILENT = 0x0002, ///< ":silent"
@@ -281,7 +215,7 @@ typedef struct {
// values for undo_cmdmod()
char *cmod_save_ei; ///< saved value of 'eventignore'
int cmod_did_sandbox; ///< set when "sandbox" was incremented
- long cmod_verbose_save; ///< if 'verbose' was set: value of p_verbose plus one
+ OptInt cmod_verbose_save; ///< if 'verbose' was set: value of p_verbose plus one
int cmod_save_msg_silent; ///< if non-zero: saved value of msg_silent + 1
int cmod_save_msg_scroll; ///< for restoring msg_scroll
int cmod_did_esilent; ///< incremented when emsg_silent is
@@ -295,5 +229,3 @@ typedef struct {
bool bar;
} magic;
} CmdParseInfo;
-
-#endif // NVIM_EX_CMDS_DEFS_H
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a24e8458a6..0b466bbe4e 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ex_docmd.c: functions for executing an Ex command line.
#include <assert.h>
@@ -12,15 +9,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "auto/config.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/debugger.h"
#include "nvim/digraph.h"
@@ -28,7 +28,6 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/ex_cmds.h"
@@ -40,19 +39,20 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/input.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/lua/executor.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
@@ -61,40 +61,52 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/shada.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
-static char e_ambiguous_use_of_user_defined_command[]
+static const char e_ambiguous_use_of_user_defined_command[]
= N_("E464: Ambiguous use of user-defined command");
-static char e_not_an_editor_command[]
+static const char e_no_call_stack_to_substitute_for_stack[]
+ = N_("E489: No call stack to substitute for \"<stack>\"");
+static const char e_not_an_editor_command[]
= N_("E492: Not an editor command");
-static char e_no_source_file_name_to_substitute_for_sfile[]
- = N_("E498: no :source file name to substitute for \"<sfile>\"");
-static char e_no_call_stack_to_substitute_for_stack[]
- = N_("E489: no call stack to substitute for \"<stack>\"");
-static char e_no_script_file_name_to_substitute_for_script[]
+static const char e_no_autocommand_file_name_to_substitute_for_afile[]
+ = N_("E495: No autocommand file name to substitute for \"<afile>\"");
+static const char e_no_autocommand_buffer_number_to_substitute_for_abuf[]
+ = N_("E496: No autocommand buffer number to substitute for \"<abuf>\"");
+static const char e_no_autocommand_match_name_to_substitute_for_amatch[]
+ = N_("E497: No autocommand match name to substitute for \"<amatch>\"");
+static const char e_no_source_file_name_to_substitute_for_sfile[]
+ = N_("E498: No :source file name to substitute for \"<sfile>\"");
+static const char e_no_line_number_to_use_for_slnum[]
+ = N_("E842: No line number to use for \"<slnum>\"");
+static const char e_no_line_number_to_use_for_sflnum[]
+ = N_("E961: No line number to use for \"<sflnum>\"");
+static const char e_no_script_file_name_to_substitute_for_script[]
= N_("E1274: No script file name to substitute for \"<script>\"");
static int quitmore = 0;
@@ -139,10 +151,6 @@ struct dbg_stuff {
# include "ex_docmd.c.generated.h"
#endif
-#ifndef HAVE_WORKING_LIBINTL
-# define ex_language ex_ni
-#endif
-
// Declare cmdnames[].
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds_defs.generated.h"
@@ -152,19 +160,28 @@ static char dollar_command[2] = { '$', 0 };
static void save_dbg_stuff(struct dbg_stuff *dsp)
{
- dsp->trylevel = trylevel; trylevel = 0;
- dsp->force_abort = force_abort; force_abort = false;
- dsp->caught_stack = caught_stack; caught_stack = NULL;
- dsp->vv_exception = v_exception(NULL);
- dsp->vv_throwpoint = v_throwpoint(NULL);
+ dsp->trylevel = trylevel;
+ trylevel = 0;
+ dsp->force_abort = force_abort;
+ force_abort = false;
+ dsp->caught_stack = caught_stack;
+ caught_stack = NULL;
+ dsp->vv_exception = v_exception(NULL);
+ dsp->vv_throwpoint = v_throwpoint(NULL);
// Necessary for debugging an inactive ":catch", ":finally", ":endtry".
- dsp->did_emsg = did_emsg; did_emsg = false;
- dsp->got_int = got_int; got_int = false;
- dsp->did_throw = did_throw; did_throw = false;
- dsp->need_rethrow = need_rethrow; need_rethrow = false;
- dsp->check_cstack = check_cstack; check_cstack = false;
- dsp->current_exception = current_exception; current_exception = NULL;
+ dsp->did_emsg = did_emsg;
+ did_emsg = false;
+ dsp->got_int = got_int;
+ got_int = false;
+ dsp->did_throw = did_throw;
+ did_throw = false;
+ dsp->need_rethrow = need_rethrow;
+ need_rethrow = false;
+ dsp->check_cstack = check_cstack;
+ check_cstack = false;
+ dsp->current_exception = current_exception;
+ current_exception = NULL;
}
static void restore_dbg_stuff(struct dbg_stuff *dsp)
@@ -200,7 +217,7 @@ void do_exmode(void)
RedrawingDisabled++; // don't redisplay the window
no_wait_return++; // don't wait for return
- msg(_("Entering Ex mode. Type \"visual\" to go to Normal mode."));
+ msg(_("Entering Ex mode. Type \"visual\" to go to Normal mode."), 0);
while (exmode_active) {
// Check for a ":normal" command and no more characters left.
if (ex_normal_busy > 0 && typebuf.tb_len == 0) {
@@ -221,7 +238,7 @@ void do_exmode(void)
if ((prev_line != curwin->w_cursor.lnum
|| changedtick != buf_get_changedtick(curbuf)) && !ex_no_reprint) {
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- emsg(_(e_emptybuf));
+ emsg(_(e_empty_buffer));
} else {
if (ex_pressedreturn) {
// Make sure the message overwrites the right line and isn't throttled.
@@ -239,7 +256,7 @@ void do_exmode(void)
}
} else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- emsg(_(e_emptybuf));
+ emsg(_(e_empty_buffer));
} else {
emsg(_("E501: At end-of-file"));
}
@@ -264,9 +281,9 @@ static void msg_verbose_cmd(linenr_T lnum, char *cmd)
verbose_enter_scroll();
if (lnum == 0) {
- smsg(_("Executing: %s"), cmd);
+ smsg(0, _("Executing: %s"), cmd);
} else {
- smsg(_("line %" PRIdLINENR ": %s"), lnum, cmd);
+ smsg(0, _("line %" PRIdLINENR ": %s"), lnum, cmd);
}
if (msg_silent == 0) {
msg_puts("\n"); // don't overwrite this
@@ -279,7 +296,7 @@ static void msg_verbose_cmd(linenr_T lnum, char *cmd)
/// Execute a simple command line. Used for translated commands like "*".
int do_cmdline_cmd(const char *cmd)
{
- return do_cmdline((char *)cmd, NULL, NULL, DOCMD_NOWAIT|DOCMD_KEYTYPED);
+ return do_cmdline((char *)cmd, NULL, NULL, DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
}
/// do_cmdline(): execute one Ex command line
@@ -320,16 +337,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie
int *dbg_tick = NULL; // ptr to dbg_tick field in cookie
struct dbg_stuff debug_saved; // saved things for debug mode
- int initial_trylevel;
- msglist_T **saved_msg_list = NULL;
msglist_T *private_msg_list;
// "fgetline" and "cookie" passed to do_one_cmd()
char *(*cmd_getline)(int, void *, int, bool);
void *cmd_cookie;
struct loop_cookie cmd_loop_cookie;
- void *real_cookie;
- int getline_is_func;
static int call_depth = 0; // recursiveness
// For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory
@@ -338,7 +351,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// combine the messages stored by an earlier invocation of do_one_cmd()
// with the command name of the later one. This would happen when
// BufWritePost autocommands are executed after a write error.
- saved_msg_list = msg_list;
+ msglist_T **saved_msg_list = msg_list;
msg_list = &private_msg_list;
private_msg_list = NULL;
@@ -358,10 +371,10 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
ga_init(&lines_ga, (int)sizeof(wcmd_T), 10);
- real_cookie = getline_cookie(fgetline, cookie);
+ void *real_cookie = getline_cookie(fgetline, cookie);
// Inside a function use a higher nesting level.
- getline_is_func = getline_equal(fgetline, cookie, get_func_line);
+ bool getline_is_func = getline_equal(fgetline, cookie, get_func_line);
if (getline_is_func && ex_nesting_level == func_level(real_cookie)) {
ex_nesting_level++;
}
@@ -393,7 +406,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
CLEAR_FIELD(debug_saved);
}
- initial_trylevel = trylevel;
+ int initial_trylevel = trylevel;
// "did_throw" will be set to true when an exception is being thrown.
did_throw = false;
@@ -485,24 +498,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
}
- if (cstack.cs_looplevel > 0) {
- // Inside a while/for loop we need to store the lines and use them
- // again. Pass a different "fgetline" function to do_one_cmd()
- // below, so that it stores lines in or reads them from
- // "lines_ga". Makes it possible to define a function inside a
- // while/for loop.
- cmd_getline = get_loop_line;
- cmd_cookie = (void *)&cmd_loop_cookie;
- cmd_loop_cookie.lines_gap = &lines_ga;
- cmd_loop_cookie.current_line = current_line;
- cmd_loop_cookie.getline = fgetline;
- cmd_loop_cookie.cookie = cookie;
- cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len);
- } else {
- cmd_getline = fgetline;
- cmd_cookie = cookie;
- }
-
// 2. If no line given, get an allocated line with fgetline().
if (next_cmdline == NULL) {
// Need to set msg_didout for the first line after an ":if",
@@ -541,15 +536,37 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
cmdline_copy = next_cmdline;
- // Save the current line when inside a ":while" or ":for", and when
- // the command looks like a ":while" or ":for", because we may need it
- // later. When there is a '|' and another command, it is stored
- // separately, because we need to be able to jump back to it from an
+ int current_line_before = 0;
+ // Inside a while/for loop, and when the command looks like a ":while"
+ // or ":for", the line is stored, because we may need it later when
+ // looping.
+ //
+ // When there is a '|' and another command, it is stored separately,
+ // because we need to be able to jump back to it from an
// :endwhile/:endfor.
- if (current_line == lines_ga.ga_len
- && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) {
- store_loop_line(&lines_ga, next_cmdline);
+ //
+ // Pass a different "fgetline" function to do_one_cmd() below,
+ // that it stores lines in or reads them from "lines_ga". Makes it
+ // possible to define a function inside a while/for loop.
+ if ((cstack.cs_looplevel > 0 || has_loop_cmd(next_cmdline))) {
+ cmd_getline = get_loop_line;
+ cmd_cookie = (void *)&cmd_loop_cookie;
+ cmd_loop_cookie.lines_gap = &lines_ga;
+ cmd_loop_cookie.current_line = current_line;
+ cmd_loop_cookie.getline = fgetline;
+ cmd_loop_cookie.cookie = cookie;
+ cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len);
+
+ // Save the current line when encountering it the first time.
+ if (current_line == lines_ga.ga_len) {
+ store_loop_line(&lines_ga, next_cmdline);
+ }
+ current_line_before = current_line;
+ } else {
+ cmd_getline = fgetline;
+ cmd_cookie = cookie;
}
+
did_endif = false;
if (count++ == 0) {
@@ -652,7 +669,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
} else if (cstack.cs_lflags & CSL_HAD_LOOP) {
// For a ":while" or ":for" we need to remember the line number.
cstack.cs_lflags &= ~CSL_HAD_LOOP;
- cstack.cs_line[cstack.cs_idx] = current_line - 1;
+ cstack.cs_line[cstack.cs_idx] = current_line_before;
}
}
@@ -819,8 +836,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// Cleanup if "cs_emsg_silent_list" remains.
if (cstack.cs_emsg_silent_list != NULL) {
- eslist_T *elem, *temp;
- for (elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) {
+ eslist_T *temp;
+ for (eslist_T *elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) {
temp = elem->next;
xfree(elem);
}
@@ -893,7 +910,7 @@ void handle_did_throw(void)
if (messages != NULL) {
do {
msglist_T *next = messages->next;
- emsg(messages->msg);
+ emsg_multiline(messages->msg, messages->multiline);
xfree(messages->msg);
xfree(messages->sfile);
xfree(messages);
@@ -919,7 +936,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat)
char *line;
// First time inside the ":while"/":for": get line normally.
if (cp->getline == NULL) {
- line = getcmdline(c, 0L, indent, do_concat);
+ line = getcmdline(c, 0, indent, do_concat);
} else {
line = cp->getline(c, cp->cookie, indent, do_concat);
}
@@ -986,10 +1003,10 @@ void *getline_cookie(LineGetter fgetline, void *cookie)
/// ":bwipeout", etc.
///
/// @return the buffer number.
-static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, long offset)
+static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, int offset)
{
buf_T *nextbuf;
- long count = offset;
+ int count = offset;
buf_T *buf = firstbuf;
while (buf->b_next != NULL && buf->b_fnum < lnum) {
@@ -1107,7 +1124,7 @@ static void get_wincmd_addr_type(const char *arg, exarg_T *eap)
case 'd':
case Ctrl_D:
// window size or any count
- eap->addr_type = ADDR_OTHER; // -V1037
+ eap->addr_type = ADDR_OTHER;
break;
case Ctrl_HAT:
@@ -1311,16 +1328,16 @@ static void parse_register(exarg_T *eap)
}
// Change line1 and line2 of Ex command to use count
-void set_cmd_count(exarg_T *eap, long count, bool validate)
+void set_cmd_count(exarg_T *eap, linenr_T count, bool validate)
{
if (eap->addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3
- eap->line2 = (linenr_T)count;
+ eap->line2 = count;
if (eap->addr_count == 0) {
eap->addr_count = 1;
}
} else {
eap->line1 = eap->line2;
- eap->line2 += (linenr_T)count - 1;
+ eap->line2 += count - 1;
eap->addr_count++;
// Be vi compatible: no error message for out of range.
if (validate && eap->line2 > curbuf->b_ml.ml_line_count) {
@@ -1329,7 +1346,7 @@ void set_cmd_count(exarg_T *eap, long count, bool validate)
}
}
-static int parse_count(exarg_T *eap, char **errormsg, bool validate)
+static int parse_count(exarg_T *eap, const char **errormsg, bool validate)
{
// Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a
// count, it's a buffer name.
@@ -1338,7 +1355,7 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate)
if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg)
&& (!(eap->argt & EX_BUFNAME) || *(p = skipdigits(eap->arg + 1)) == NUL
|| ascii_iswhite(*p))) {
- long n = getdigits_long(&eap->arg, false, -1);
+ linenr_T n = getdigits_int32(&eap->arg, false, -1);
eap->arg = skipwhite(eap->arg);
if (eap->args != NULL) {
@@ -1383,7 +1400,7 @@ bool is_cmd_ni(cmdidx_T cmdidx)
/// @param[out] errormsg Error message, if any
///
/// @return Success or failure
-bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg)
+bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const char **errormsg)
{
char *after_modifier = NULL;
bool retval = false;
@@ -1441,7 +1458,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
}
// Fail if command is invalid
if (eap->cmdidx == CMD_SIZE) {
- STRCPY(IObuff, _(e_not_an_editor_command));
+ xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE);
// If the modifier was parsed OK the error must be in the following command
char *cmdname = after_modifier ? after_modifier : cmdline;
append_command(cmdname);
@@ -1551,7 +1568,7 @@ static void shift_cmd_args(exarg_T *eap)
xfree(oldarglens);
}
-static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview)
+static int execute_cmd0(int *retv, exarg_T *eap, const char **errormsg, bool preview)
{
// If filename expansion is enabled, expand filenames
if (eap->argt & EX_XFILE) {
@@ -1636,7 +1653,7 @@ static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview)
/// @param preview Execute command preview callback instead of actual command
int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
{
- char *errormsg = NULL;
+ const char *errormsg = NULL;
int retv = 0;
#undef ERROR
@@ -1677,7 +1694,7 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
&& !(eap->cmdidx == CMD_file && *eap->arg == NUL)
&& !IS_USER_CMDIDX(eap->cmdidx)
&& curbuf_locked()) {
- ERROR(_(e_cannot_edit_other_buf));
+ goto end;
}
correct_range(eap);
@@ -1849,7 +1866,8 @@ static bool skip_cmd(const exarg_T *eap)
/// Execute one Ex command.
///
-/// If 'sourcing' is true, the command will be included in the error message.
+/// If "flags" has DOCMD_VERBOSE, the command will be included in the error
+/// message.
///
/// 1. skip comment lines and leading space
/// 2. handle command modifiers
@@ -1867,7 +1885,7 @@ static bool skip_cmd(const exarg_T *eap)
static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter fgetline,
void *cookie)
{
- char *errormsg = NULL; // error message
+ const char *errormsg = NULL; // error message
const int save_reg_executing = reg_executing;
const bool save_pending_end_reg_executing = pending_end_reg_executing;
@@ -1882,8 +1900,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// avoid that a function call in 'statusline' does this
&& !getline_equal(fgetline, cookie, get_func_line)
// avoid that an autocommand, e.g. QuitPre, does this
- && !getline_equal(fgetline, cookie,
- getnextac)) {
+ && !getline_equal(fgetline, cookie, getnextac)) {
quitmore--;
}
@@ -2013,7 +2030,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
while (ASCII_ISALNUM(*p)) {
p++;
}
- p = xstrnsave(ea.cmd, (size_t)(p - ea.cmd));
+ p = xmemdupz(ea.cmd, (size_t)(p - ea.cmd));
int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
xfree(p);
// If the autocommands did something and didn't cause an error, try
@@ -2031,7 +2048,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// Check for wrong commands.
if (ea.cmdidx == CMD_SIZE) {
if (!ea.skip) {
- STRCPY(IObuff, _(e_not_an_editor_command));
+ xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE);
// If the modifier was parsed OK the error must be in the following
// command
char *cmdname = after_modifier ? after_modifier : *cmdlinep;
@@ -2308,7 +2325,7 @@ doend:
if (errormsg != NULL && *errormsg != NUL && !did_emsg) {
if (flags & DOCMD_VERBOSE) {
if (errormsg != IObuff) {
- STRCPY(IObuff, errormsg);
+ xstrlcpy(IObuff, errormsg, IOSIZE);
errormsg = IObuff;
}
append_command(*ea.cmdlinep);
@@ -2329,6 +2346,7 @@ doend:
}
ex_nesting_level--;
+ xfree(ea.cmdline_tofree);
return ea.nextcmd;
}
@@ -2362,12 +2380,12 @@ char *ex_errmsg(const char *const msg, const char *const arg)
/// - set 'eventignore' to "all" for ":noautocmd"
///
/// @return FAIL when the command is not to be executed.
-int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool skip_only)
+int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod, bool skip_only)
{
CLEAR_POINTER(cmod);
// Repeat until no more command modifiers are found.
- for (;;) {
+ while (true) {
while (*eap->cmd == ' '
|| *eap->cmd == '\t'
|| *eap->cmd == ':') {
@@ -2397,7 +2415,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool
char *p = skip_range(eap->cmd, NULL);
switch (*p) {
- // When adding an entry, also modify cmd_exists().
+ // When adding an entry, also modify cmdmods[]
case 'a':
if (!checkforcmd(&eap->cmd, "aboveleft", 3)) {
break;
@@ -2543,7 +2561,10 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool
if (checkforcmd(&p, "tab", 3)) {
if (!skip_only) {
int tabnr = (int)get_address(eap, &eap->cmd, ADDR_TABS, eap->skip, skip_only,
- false, 1);
+ false, 1, errormsg);
+ if (eap->cmd == NULL) {
+ return false;
+ }
if (tabnr == MAXLNUM) {
cmod->cmod_tab = tabpage_index(curtab) + 1;
@@ -2687,7 +2708,7 @@ void undo_cmdmod(cmdmod_T *cmod)
/// May set the last search pattern, unless "silent" is true.
///
/// @return FAIL and set "errormsg" or return OK.
-int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
+int parse_cmd_address(exarg_T *eap, const char **errormsg, bool silent)
FUNC_ATTR_NONNULL_ALL
{
int address_count = 1;
@@ -2696,12 +2717,12 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
int ret = FAIL;
// Repeat for all ',' or ';' separated addresses.
- for (;;) {
+ while (true) {
eap->line1 = eap->line2;
eap->line2 = get_cmd_default_range(eap);
eap->cmd = skipwhite(eap->cmd);
lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
- eap->addr_count == 0, address_count++);
+ eap->addr_count == 0, address_count++, errormsg);
if (eap->cmd == NULL) { // error detected
goto theend;
}
@@ -2737,7 +2758,7 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
if (IS_USER_CMDIDX(eap->cmdidx)) {
eap->line1 = 1;
eap->line2 = eap->addr_type == ADDR_WINDOWS
- ? LAST_WIN_NR : LAST_TAB_NR;
+ ? LAST_WIN_NR : LAST_TAB_NR;
} else {
// there is no Vim command which uses '%' and
// ADDR_WINDOWS or ADDR_TABS
@@ -2780,13 +2801,13 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
eap->cmd++;
if (!eap->skip) {
fmark_T *fm = mark_get_visual(curbuf, '<');
- if (!mark_check(fm)) {
+ if (!mark_check(fm, errormsg)) {
goto theend;
}
assert(fm != NULL);
eap->line1 = fm->mark.lnum;
fm = mark_get_visual(curbuf, '>');
- if (!mark_check(fm)) {
+ if (!mark_check(fm, errormsg)) {
goto theend;
}
assert(fm != NULL);
@@ -2862,10 +2883,10 @@ bool checkforcmd(char **pp, const char *cmd, int len)
/// Append "cmd" to the error message in IObuff.
/// Takes care of limiting the length and handling 0xa0, which would be
/// invisible otherwise.
-static void append_command(char *cmd)
+static void append_command(const char *cmd)
{
size_t len = strlen(IObuff);
- char *s = cmd;
+ const char *s = cmd;
char *d;
if (len > IOSIZE - 100) {
@@ -2874,7 +2895,7 @@ static void append_command(char *cmd)
d -= utf_head_off(IObuff, d);
STRCPY(d, "...");
}
- STRCAT(IObuff, ": ");
+ xstrlcat(IObuff, ": ", IOSIZE);
d = IObuff + strlen(IObuff);
while (*s != NUL && d - IObuff + 5 < IOSIZE) {
if ((uint8_t)s[0] == 0xc2 && (uint8_t)s[1] == 0xa0) {
@@ -2884,7 +2905,7 @@ static void append_command(char *cmd)
} else if (d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) {
break;
} else {
- mb_copy_char((const char **)&s, &d);
+ mb_copy_char(&s, &d);
}
}
*d = NUL;
@@ -2988,6 +3009,11 @@ char *find_ex_command(exarg_T *eap, int *full)
}
assert(eap->cmdidx >= 0);
+ if (len == 3 && strncmp("def", eap->cmd, 3) == 0) {
+ // Make :def an unknown command to avoid confusing behavior. #23149
+ eap->cmdidx = CMD_SIZE;
+ }
+
for (; (int)eap->cmdidx < CMD_SIZE;
eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) {
if (strncmp(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd,
@@ -3029,6 +3055,7 @@ static struct cmdmod {
{ "confirm", 4, false },
{ "filter", 4, false },
{ "hide", 3, false },
+ { "horizontal", 3, false },
{ "keepalt", 5, false },
{ "keepjumps", 5, false },
{ "keepmarks", 3, false },
@@ -3094,7 +3121,7 @@ int cmd_exists(const char *const name)
// For ":2match" and ":3match" we need to skip the number.
exarg_T ea;
ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name);
- ea.cmdidx = (cmdidx_T)0;
+ ea.cmdidx = 0;
ea.flags = 0;
int full = false;
char *p = find_ex_command(&ea, &full);
@@ -3113,13 +3140,10 @@ int cmd_exists(const char *const name)
/// "fullcommand" function
void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- char *name = argvars[0].vval.v_string;
+ char *name = (char *)tv_get_string(&argvars[0]);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- if (name == NULL) {
- return;
- }
while (*name == ':') {
name++;
@@ -3128,7 +3152,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
exarg_T ea;
ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
- ea.cmdidx = (cmdidx_T)0;
+ ea.cmdidx = 0;
ea.flags = 0;
char *p = find_ex_command(&ea, NULL);
if (p == NULL || ea.cmdidx == CMD_SIZE) {
@@ -3142,10 +3166,15 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
cmdidx_T excmd_get_cmdidx(const char *cmd, size_t len)
{
+ if (len == 3 && strncmp("def", cmd, 3) == 0) {
+ // Make :def an unknown command to avoid confusing behavior. #23149
+ return CMD_SIZE;
+ }
+
cmdidx_T idx;
if (!one_letter_cmd(cmd, &idx)) {
- for (idx = (cmdidx_T)0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) {
+ for (idx = 0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) {
if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) {
break;
}
@@ -3200,34 +3229,35 @@ char *skip_range(const char *cmd, int *ctx)
}
// Skip ":" and white space.
- cmd = skip_colon_white((char *)cmd, false);
+ cmd = skip_colon_white(cmd, false);
return (char *)cmd;
}
-static void addr_error(cmd_addr_T addr_type)
+static const char *addr_error(cmd_addr_T addr_type)
{
if (addr_type == ADDR_NONE) {
- emsg(_(e_norange));
+ return _(e_norange);
} else {
- emsg(_(e_invrange));
+ return _(e_invrange);
}
}
-/// Get a single EX address
+/// Gets a single EX address.
///
-/// Set ptr to the next character after the part that was interpreted.
-/// Set ptr to NULL when an error is encountered.
-/// This may set the last used search pattern.
+/// Sets ptr to the next character after the part that was interpreted.
+/// Sets ptr to NULL when an error is encountered (stored in `errormsg`).
+/// May set the last used search pattern.
///
/// @param skip only skip the address, don't use it
/// @param silent no errors or side effects
/// @param to_other_file flag: may jump to other file
/// @param address_count 1 for first, >1 after comma
+/// @param errormsg Error message, if any
///
/// @return MAXLNUM when no Ex address was found.
static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int skip, bool silent,
- int to_other_file, int address_count)
+ int to_other_file, int address_count, const char **errormsg)
FUNC_ATTR_NONNULL_ALL
{
int c;
@@ -3263,7 +3293,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case ADDR_NONE:
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
break;
@@ -3308,7 +3338,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case ADDR_NONE:
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
break;
@@ -3333,7 +3363,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
goto error;
}
if (addr_type != ADDR_LINES) {
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
@@ -3346,10 +3376,11 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd);
cmd++;
if (fm != NULL && fm->fnum != curbuf->handle) {
+ (void)mark_move_to(fm, 0);
// Jumped to another file.
lnum = curwin->w_cursor.lnum;
} else {
- if (!mark_check(fm)) {
+ if (!mark_check(fm, errormsg)) {
cmd = NULL;
goto error;
}
@@ -3363,7 +3394,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case '?': // '/' or '?' - search
c = (uint8_t)(*cmd++);
if (addr_type != ADDR_LINES) {
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
@@ -3397,7 +3428,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
}
searchcmdlen = 0;
flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
- if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) {
+ if (!do_search(NULL, c, c, cmd, 1, flags, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -3412,7 +3443,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case '\\': // "\?", "\/" or "\&", repeat search
cmd++;
if (addr_type != ADDR_LINES) {
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
@@ -3421,7 +3452,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
} else if (*cmd == '?' || *cmd == '/') {
i = RE_SEARCH;
} else {
- emsg(_(e_backslash));
+ *errormsg = _(e_backslash);
cmd = NULL;
goto error;
}
@@ -3434,7 +3465,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
pos.coladd = 0;
if (searchit(curwin, curbuf, &pos, NULL,
*cmd == '?' ? BACKWARD : FORWARD,
- "", 1L, SEARCH_MSG, i, NULL) != FAIL) {
+ "", 1, SEARCH_MSG, i, NULL) != FAIL) {
lnum = pos.lnum;
} else {
cmd = NULL;
@@ -3450,7 +3481,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
}
}
- for (;;) {
+ while (true) {
cmd = skipwhite(cmd);
if (*cmd != '-' && *cmd != '+' && !ascii_isdigit(*cmd)) {
break;
@@ -3503,13 +3534,13 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
// "number", "+number" or "-number"
n = getdigits_int32(&cmd, false, MAXLNUM);
if (n == MAXLNUM) {
- emsg(_(e_line_number_out_of_range));
+ *errormsg = _(e_line_number_out_of_range);
goto error;
}
}
if (addr_type == ADDR_TABS_RELATIVE) {
- emsg(_(e_invrange));
+ *errormsg = _(e_invrange);
cmd = NULL;
goto error;
} else if (addr_type == ADDR_LOADED_BUFFERS || addr_type == ADDR_BUFFERS) {
@@ -3524,8 +3555,8 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
if (i == '-') {
lnum -= n;
} else {
- if (n >= INT32_MAX - lnum) {
- emsg(_(e_line_number_out_of_range));
+ if (lnum >= 0 && n >= INT32_MAX - lnum) {
+ *errormsg = _(e_line_number_out_of_range);
goto error;
}
lnum += n;
@@ -3738,7 +3769,7 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep)
/// When an error is detected, "errormsgp" is set to a non-NULL pointer.
///
/// @return FAIL for failure, OK otherwise.
-int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
+int expand_filename(exarg_T *eap, char **cmdlinep, const char **errormsgp)
{
// Skip a regexp pattern for ":vimgrep[add] pat file..."
char *p = skip_grep_pat(eap);
@@ -3751,7 +3782,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
// Skip over `=expr`, wildcards in it are not expanded.
if (p[0] == '`' && p[1] == '=') {
p += 2;
- (void)skip_expr(&p);
+ (void)skip_expr(&p, NULL);
if (*p == '`') {
p++;
}
@@ -3970,7 +4001,7 @@ void separate_nextcmd(exarg_T *eap)
} else if (p[0] == '`' && p[1] == '=' && (eap->argt & EX_XFILE)) {
// Skip over `=expr` when wildcards are expanded.
p += 2;
- (void)skip_expr(&p);
+ (void)skip_expr(&p, NULL);
if (*p == NUL) { // stop at NUL after CTRL-V
break;
}
@@ -4011,7 +4042,7 @@ static char *getargcmd(char **argp)
if (*arg == '+') { // +[command]
arg++;
if (ascii_isspace(*arg) || *arg == '\0') {
- command = (char *)dollar_command;
+ command = dollar_command;
} else {
command = arg;
arg = skip_cmd_arg(command, true);
@@ -4059,6 +4090,22 @@ int get_bad_opt(const char *p, exarg_T *eap)
return OK;
}
+/// Function given to ExpandGeneric() to obtain the list of bad= names.
+static char *get_bad_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ // Note: Keep this in sync with get_bad_opt().
+ static char *(p_bad_values[]) = {
+ "?",
+ "keep",
+ "drop",
+ };
+
+ if (idx < (int)ARRAY_SIZE(p_bad_values)) {
+ return p_bad_values[idx];
+ }
+ return NULL;
+}
+
/// Get "++opt=arg" argument.
///
/// @return FAIL or OK.
@@ -4068,6 +4115,8 @@ static int getargopt(exarg_T *eap)
int *pp = NULL;
int bad_char_idx;
+ // Note: Keep this in sync with get_argopt_name.
+
// ":edit ++[no]bin[ary] file"
if (strncmp(arg, "bin", 3) == 0 || strncmp(arg, "nobin", 5) == 0) {
if (*arg == 'n') {
@@ -4146,6 +4195,71 @@ static int getargopt(exarg_T *eap)
return OK;
}
+/// Function given to ExpandGeneric() to obtain the list of ++opt names.
+static char *get_argopt_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ // Note: Keep this in sync with getargopt().
+ static char *(p_opt_values[]) = {
+ "fileformat=",
+ "encoding=",
+ "binary",
+ "nobinary",
+ "bad=",
+ "edit",
+ "p",
+ };
+
+ if (idx < (int)ARRAY_SIZE(p_opt_values)) {
+ return p_opt_values[idx];
+ }
+ return NULL;
+}
+
+/// Command-line expansion for ++opt=name.
+int expand_argopt(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches)
+{
+ if (xp->xp_pattern > xp->xp_line && *(xp->xp_pattern - 1) == '=') {
+ CompleteListItemGetter cb = NULL;
+
+ char *name_end = xp->xp_pattern - 1;
+ if (name_end - xp->xp_line >= 2
+ && strncmp(name_end - 2, "ff", 2) == 0) {
+ cb = get_fileformat_name;
+ } else if (name_end - xp->xp_line >= 10
+ && strncmp(name_end - 10, "fileformat", 10) == 0) {
+ cb = get_fileformat_name;
+ } else if (name_end - xp->xp_line >= 3
+ && strncmp(name_end - 3, "enc", 3) == 0) {
+ cb = get_encoding_name;
+ } else if (name_end - xp->xp_line >= 8
+ && strncmp(name_end - 8, "encoding", 8) == 0) {
+ cb = get_encoding_name;
+ } else if (name_end - xp->xp_line >= 3
+ && strncmp(name_end - 3, "bad", 3) == 0) {
+ cb = get_bad_name;
+ }
+
+ if (cb != NULL) {
+ ExpandGeneric(pat, xp, rmp, matches, numMatches, cb, false);
+ return OK;
+ }
+ return FAIL;
+ }
+
+ // Special handling of "ff" which acts as a short form of
+ // "fileformat", as "ff" is not a substring of it.
+ if (xp->xp_pattern_len == 2
+ && strncmp(xp->xp_pattern, "ff", xp->xp_pattern_len) == 0) {
+ *matches = xmalloc(sizeof(char *));
+ *numMatches = 1;
+ (*matches)[0] = xstrdup("fileformat=");
+ return OK;
+ }
+
+ ExpandGeneric(pat, xp, rmp, matches, numMatches, get_argopt_name, false);
+ return OK;
+}
+
/// Handle the argument for a tabpage related ex command.
/// When an error is encountered then eap->errmsg is set.
///
@@ -4401,7 +4515,7 @@ static int check_more(int message, bool forceit)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && curbuf->b_fname != NULL) {
char buff[DIALOG_MSG_SIZE];
- vim_snprintf((char *)buff, DIALOG_MSG_SIZE,
+ vim_snprintf(buff, DIALOG_MSG_SIZE,
NGETTEXT("%d more file to edit. Quit anyway?",
"%d more files to edit. Quit anyway?", n), n);
if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) {
@@ -4433,15 +4547,15 @@ static void ex_colorscheme(exarg_T *eap)
char *expr = xstrdup("g:colors_name");
emsg_off++;
- char *p = eval_to_string(expr, NULL, false);
+ char *p = eval_to_string(expr, false);
emsg_off--;
xfree(expr);
if (p != NULL) {
- msg(p);
+ msg(p, 0);
xfree(p);
} else {
- msg("default");
+ msg("default", 0);
}
} else if (load_colors(eap->arg) == FAIL) {
semsg(_("E185: Cannot find color scheme '%s'"), eap->arg);
@@ -4451,9 +4565,9 @@ static void ex_colorscheme(exarg_T *eap)
static void ex_highlight(exarg_T *eap)
{
if (*eap->arg == NUL && eap->cmd[2] == '!') {
- msg(_("Greetings, Vim user!"));
+ msg(_("Greetings, Vim user!"), 0);
}
- do_highlight((const char *)eap->arg, eap->forceit, false);
+ do_highlight(eap->arg, eap->forceit, false);
}
/// Call this function if we thought we were going to exit, but we won't
@@ -4562,11 +4676,14 @@ static void ex_cquit(exarg_T *eap)
FUNC_ATTR_NORETURN
{
// this does not always pass on the exit code to the Manx compiler. why?
- getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE);
+ int status = eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE;
+ ui_call_error_exit(status);
+ getout(status);
}
-/// ":qall": try to quit all windows
-static void ex_quit_all(exarg_T *eap)
+/// Do preparations for "qall" and "wqall".
+/// Returns FAIL when quitting should be aborted.
+int before_quit_all(exarg_T *eap)
{
if (cmdwin_type != 0) {
if (eap->forceit) {
@@ -4574,19 +4691,28 @@ static void ex_quit_all(exarg_T *eap)
} else {
cmdwin_result = K_XF2;
}
- return;
+ return FAIL;
}
// Don't quit while editing the command line.
if (text_locked()) {
text_locked_msg();
- return;
+ return FAIL;
}
if (before_quit_autocmds(curwin, true, eap->forceit)) {
- return;
+ return FAIL;
}
+ return OK;
+}
+
+/// ":qall": try to quit all windows
+static void ex_quit_all(exarg_T *eap)
+{
+ if (before_quit_all(eap) == FAIL) {
+ return;
+ }
exiting = true;
if (eap->forceit || !check_changed_any(false, false)) {
getout(0);
@@ -4710,7 +4836,7 @@ static void ex_tabonly(exarg_T *eap)
}
if (first_tabpage->tp_next == NULL) {
- msg(_("Already only one tab page"));
+ msg(_("Already only one tab page"), 0);
return;
}
@@ -4769,7 +4895,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
// Limit to 1000 windows, autocommands may add a window while we close
// one. OK, so I'm paranoid...
while (++done < 1000) {
- snprintf((char *)prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
+ snprintf(prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
win_T *wp = tp->tp_lastwin;
ex_win_close(forceit, wp, tp);
@@ -4836,19 +4962,9 @@ static void ex_stop(exarg_T *eap)
if (!eap->forceit) {
autowrite_all();
}
- apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
-
- // TODO(bfredl): the TUI should do this on suspend
- ui_cursor_goto(Rows - 1, 0);
- ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0);
- ui_flush();
- ui_call_suspend(); // call machine specific function
-
+ may_trigger_vim_suspend_resume(true);
+ ui_call_suspend();
ui_flush();
- maketitle();
- resettitle(); // force updating the title
- ui_refresh(); // may have resized window
- apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
}
/// ":exit", ":xit" and ":wq": Write file and quit the current window.
@@ -4891,7 +5007,7 @@ static void ex_exit(exarg_T *eap)
static void ex_print(exarg_T *eap)
{
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- emsg(_(e_emptybuf));
+ emsg(_(e_empty_buffer));
} else {
for (; !got_int; os_breakcheck()) {
print_line(eap->line1,
@@ -4976,8 +5092,13 @@ void ex_splitview(exarg_T *eap)
}
if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) {
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
fname = find_file_in_path(eap->arg, strlen(eap->arg),
- FNAME_MESS, true, curbuf->b_ffname);
+ FNAME_MESS, true, curbuf->b_ffname,
+ &file_to_find, &search_ctx);
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
if (fname == NULL) {
goto theend;
}
@@ -5091,8 +5212,8 @@ static void ex_tabs(exarg_T *eap)
msg_scroll = true;
win_T *lastused_win = valid_tabpage(lastused_tabpage)
- ? lastused_tabpage->tp_curwin
- : NULL;
+ ? lastused_tabpage->tp_curwin
+ : NULL;
FOR_ALL_TABS(tp) {
if (got_int) {
@@ -5101,7 +5222,7 @@ static void ex_tabs(exarg_T *eap)
msg_putchar('\n');
vim_snprintf(IObuff, IOSIZE, _("Tab page %d"), tabcount++);
- msg_outtrans_attr(IObuff, HL_ATTR(HLF_T));
+ msg_outtrans(IObuff, HL_ATTR(HLF_T));
os_breakcheck();
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
@@ -5119,7 +5240,7 @@ static void ex_tabs(exarg_T *eap)
} else {
home_replace(wp->w_buffer, wp->w_buffer->b_fname, IObuff, IOSIZE, true);
}
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
os_breakcheck();
}
}
@@ -5169,17 +5290,23 @@ static void ex_resize(exarg_T *eap)
/// ":find [+command] <file>" command.
static void ex_find(exarg_T *eap)
{
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
char *fname = find_file_in_path(eap->arg, strlen(eap->arg),
- FNAME_MESS, true, curbuf->b_ffname);
+ FNAME_MESS, true, curbuf->b_ffname,
+ &file_to_find, &search_ctx);
if (eap->addr_count > 0) {
- // Repeat finding the file "count" times. This matters when it
- // appears several times in the path.
+ // Repeat finding the file "count" times. This matters when it appears
+ // several times in the path.
linenr_T count = eap->line2;
while (fname != NULL && --count > 0) {
xfree(fname);
- fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname);
+ fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname,
+ &file_to_find, &search_ctx);
}
}
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
if (fname == NULL) {
return;
@@ -5201,8 +5328,6 @@ static void ex_edit(exarg_T *eap)
/// @param old_curwin curwin before doing a split or NULL
void do_exedit(exarg_T *eap, win_T *old_curwin)
{
- int n;
-
// ":vi" command ends Ex mode.
if (exmode_active && (eap->cmdidx == CMD_visual
|| eap->cmdidx == CMD_view)) {
@@ -5211,18 +5336,17 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
if (*eap->arg == NUL) {
// Special case: ":global/pat/visual\NLvi-commands"
if (global_busy) {
- int rd = RedrawingDisabled;
- int nwr = no_wait_return;
- int ms = msg_scroll;
-
if (eap->nextcmd != NULL) {
- stuffReadbuff((const char *)eap->nextcmd);
+ stuffReadbuff(eap->nextcmd);
eap->nextcmd = NULL;
}
+ const int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
+ const int save_nwr = no_wait_return;
no_wait_return = 0;
need_wait_return = false;
+ const int save_ms = msg_scroll;
msg_scroll = 0;
redraw_all_later(UPD_NOT_VALID);
pending_exmode_active = true;
@@ -5230,9 +5354,9 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
normal_enter(false, true);
pending_exmode_active = false;
- RedrawingDisabled = rd;
- no_wait_return = nwr;
- msg_scroll = ms;
+ RedrawingDisabled = save_rd;
+ no_wait_return = save_nwr;
+ msg_scroll = save_ms;
}
return;
}
@@ -5254,7 +5378,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
if (*eap->arg != NUL && text_or_buf_locked()) {
return;
}
- n = readonlymode;
+ int n = readonlymode;
if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview) {
readonlymode = true;
} else if (eap->cmdidx == CMD_enew) {
@@ -5302,7 +5426,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
- n = curwin->w_arg_idx_invalid;
+ int n = curwin->w_arg_idx_invalid;
check_arg_idx(curwin);
if (n != curwin->w_arg_idx_invalid) {
maketitle();
@@ -5337,9 +5461,9 @@ static void ex_popup(exarg_T *eap)
static void ex_swapname(exarg_T *eap)
{
if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) {
- msg(_("No swap file"));
+ msg(_("No swap file"), 0);
} else {
- msg(curbuf->b_ml.ml_mfp->mf_fname);
+ msg(curbuf->b_ml.ml_mfp->mf_fname, 0);
}
}
@@ -5350,8 +5474,8 @@ static void ex_syncbind(exarg_T *eap)
{
win_T *save_curwin = curwin;
buf_T *save_curbuf = curbuf;
- long topline;
- long y;
+ linenr_T topline;
+ int y;
linenr_T old_linenr = curwin->w_cursor.lnum;
setpcmark();
@@ -5425,13 +5549,13 @@ static void ex_read(exarg_T *eap)
return;
}
i = readfile(curbuf->b_ffname, curbuf->b_fname,
- eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false);
+ eap->line2, 0, (linenr_T)MAXLNUM, eap, 0, false);
} else {
if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) {
- (void)setaltfname(eap->arg, eap->arg, (linenr_T)1);
+ (void)setaltfname(eap->arg, eap->arg, 1);
}
i = readfile(eap->arg, NULL,
- eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false);
+ eap->line2, 0, (linenr_T)MAXLNUM, eap, 0, false);
}
if (i != OK) {
if (!aborting()) {
@@ -5447,13 +5571,13 @@ static void ex_read(exarg_T *eap)
} else {
lnum = 1;
}
- if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) {
+ if (*ml_get(lnum) == NUL && u_savedel(lnum, 1) == OK) {
ml_delete(lnum, false);
if (curwin->w_cursor.lnum > 1
&& curwin->w_cursor.lnum >= lnum) {
curwin->w_cursor.lnum--;
}
- deleted_lines_mark(lnum, 1L);
+ deleted_lines_mark(lnum, 1);
}
}
redraw_curbuf_later(UPD_VALID);
@@ -5651,9 +5775,9 @@ static void ex_pwd(exarg_T *eap)
} else if (curtab->tp_localdir != NULL) {
context = "tabpage";
}
- smsg("[%s] %s", context, NameBuff);
+ smsg(0, "[%s] %s", context, NameBuff);
} else {
- msg(NameBuff);
+ msg(NameBuff, 0);
}
} else {
emsg(_("E187: Unknown"));
@@ -5663,25 +5787,27 @@ static void ex_pwd(exarg_T *eap)
/// ":=".
static void ex_equal(exarg_T *eap)
{
- smsg("%" PRId64, (int64_t)eap->line2);
- ex_may_print(eap);
+ if (*eap->arg != NUL && *eap->arg != '|') {
+ // equivalent to :lua= expr
+ ex_lua(eap);
+ } else {
+ eap->nextcmd = find_nextcmd(eap->arg);
+ smsg(0, "%" PRId64, (int64_t)eap->line2);
+ }
}
static void ex_sleep(exarg_T *eap)
{
if (cursor_valid()) {
- int n = curwin->w_winrow + curwin->w_wrow - msg_scrolled;
- if (n >= 0) {
- ui_cursor_goto(n, curwin->w_wincol + curwin->w_wcol);
- }
+ setcursor_mayforce(true);
}
- long len = eap->line2;
+ int64_t len = eap->line2;
switch (*eap->arg) {
case 'm':
break;
case NUL:
- len *= 1000L; break;
+ len *= 1000; break;
default:
semsg(_(e_invarg2), eap->arg); return;
}
@@ -5689,7 +5815,7 @@ static void ex_sleep(exarg_T *eap)
}
/// Sleep for "msec" milliseconds, but return early on CTRL-C.
-void do_sleep(long msec)
+void do_sleep(int64_t msec)
{
ui_flush(); // flush before waiting
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, msec, got_int);
@@ -5746,7 +5872,7 @@ static void ex_wincmd(exarg_T *eap)
// Pass flags on for ":vertical wincmd ]".
postponed_split_flags = cmdmod.cmod_split;
postponed_split_tab = cmdmod.cmod_tab;
- do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0L, xchar);
+ do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0, xchar);
postponed_split_flags = 0;
postponed_split_tab = 0;
}
@@ -5816,8 +5942,12 @@ static void ex_put(exarg_T *eap)
/// Handle ":copy" and ":move".
static void ex_copymove(exarg_T *eap)
{
- long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1);
+ const char *errormsg = NULL;
+ linenr_T n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1, &errormsg);
if (eap->arg == NULL) { // error detected
+ if (errormsg != NULL) {
+ emsg(errormsg);
+ }
eap->nextcmd = NULL;
return;
}
@@ -5830,13 +5960,13 @@ static void ex_copymove(exarg_T *eap)
}
if (eap->cmdidx == CMD_move) {
- if (do_move(eap->line1, eap->line2, (linenr_T)n) == FAIL) {
+ if (do_move(eap->line1, eap->line2, n) == FAIL) {
return;
}
} else {
- ex_copy(eap->line1, eap->line2, (linenr_T)n);
+ ex_copy(eap->line1, eap->line2, n);
}
- u_clearline();
+ u_clearline(curbuf);
beginline(BL_SOL | BL_FIX);
ex_may_print(eap);
}
@@ -5862,7 +5992,7 @@ static void ex_submagic(exarg_T *eap)
}
/// ":smagic" and ":snomagic" preview callback.
-static int ex_submagic_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+static int ex_submagic_preview(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr)
{
const optmagic_T saved = magic_overruled;
@@ -5944,7 +6074,7 @@ static void ex_undo(exarg_T *eap)
return;
}
- long step = eap->line2;
+ linenr_T step = eap->line2;
if (eap->forceit) { // undo! 123
// change number for "undo!" must be lesser than current change number
@@ -5995,7 +6125,7 @@ static void ex_redo(exarg_T *eap)
/// ":earlier" and ":later".
static void ex_later(exarg_T *eap)
{
- long count = 0;
+ int count = 0;
bool sec = false;
bool file = false;
char *p = eap->arg;
@@ -6003,7 +6133,7 @@ static void ex_later(exarg_T *eap)
if (*p == NUL) {
count = 1;
} else if (isdigit((uint8_t)(*p))) {
- count = getdigits_long(&p, false, 0);
+ count = getdigits_int(&p, false, 0);
switch (*p) {
case 's':
p++; sec = true; break;
@@ -6334,6 +6464,11 @@ void restore_current_state(save_state_T *sst)
ui_cursor_shape(); // may show different cursor shape
}
+bool expr_map_locked(void)
+{
+ return expr_map_lock > 0 && !(curbuf->b_flags & BF_DUMMY);
+}
+
/// ":normal[!] {commands}": Execute normal mode commands.
static void ex_normal(exarg_T *eap)
{
@@ -6343,10 +6478,11 @@ static void ex_normal(exarg_T *eap)
}
char *arg = NULL;
- if (ex_normal_lock > 0) {
+ if (expr_map_locked()) {
emsg(_(e_secure));
return;
}
+
if (ex_normal_busy >= p_mmd) {
emsg(_("E192: Recursive use of :normal too deep"));
return;
@@ -6360,8 +6496,7 @@ static void ex_normal(exarg_T *eap)
// Count the number of characters to be escaped.
int l;
- char *p;
- for (p = eap->arg; *p != NUL; p++) {
+ for (char *p = eap->arg; *p != NUL; p++) {
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
if (*++p == (char)K_SPECIAL) { // trailbyte K_SPECIAL
len += 2;
@@ -6371,7 +6506,7 @@ static void ex_normal(exarg_T *eap)
if (len > 0) {
arg = xmalloc(strlen(eap->arg) + (size_t)len + 1);
len = 0;
- for (p = eap->arg; *p != NUL; p++) {
+ for (char *p = eap->arg; *p != NUL; p++) {
arg[len++] = *p;
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
arg[len++] = *++p;
@@ -6489,9 +6624,9 @@ void exec_normal(bool was_typed)
static void ex_checkpath(exarg_T *eap)
{
- find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1L,
+ find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1,
eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
- (linenr_T)1, (linenr_T)MAXLNUM);
+ 1, (linenr_T)MAXLNUM);
}
/// ":psearch"
@@ -6526,9 +6661,9 @@ static void ex_findpat(exarg_T *eap)
break;
}
- long n = 1;
+ int n = 1;
if (ascii_isdigit(*eap->arg)) { // get count
- n = getdigits_long(&eap->arg, false, 0);
+ n = getdigits_int(&eap->arg, false, 0);
eap->arg = skipwhite(eap->arg);
}
if (*eap->arg == '/') { // Match regexp, not just whole words
@@ -6549,7 +6684,7 @@ static void ex_findpat(exarg_T *eap)
}
if (!eap->skip) {
find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit,
- *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
+ *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
n, action, eap->line1, eap->line2);
}
}
@@ -6725,8 +6860,8 @@ ssize_t find_cmdline_var(const char *src, size_t *usedlen)
/// @return an allocated string if a valid match was found.
/// Returns NULL if no match was found. "usedlen" then still contains the
/// number of characters to skip.
-char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg,
- int *escaped, bool empty_is_error)
+char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnump,
+ const char **errormsg, int *escaped, bool empty_is_error)
{
char *result;
char *resultbuf = NULL;
@@ -6752,7 +6887,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
// Note: In "\\%" the % is also not recognized!
if (src > srcstart && src[-1] == '\\') {
*usedlen = 0;
- STRMOVE(src - 1, (char *)src); // remove backslash
+ STRMOVE(src - 1, src); // remove backslash
return NULL;
}
@@ -6845,7 +6980,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
break;
case SPEC_CFILE: // file name under cursor
- result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL);
+ result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1, NULL);
if (result == NULL) {
*errormsg = "";
return NULL;
@@ -6854,12 +6989,10 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
break;
case SPEC_AFILE: // file name for autocommand
- if (autocmd_fname != NULL
- && !path_is_absolute(autocmd_fname)
- // For CmdlineEnter and related events, <afile> is not a path! #9348
- && !strequal("/", autocmd_fname)) {
+ if (autocmd_fname != NULL && !autocmd_fname_full) {
// Still need to turn the fname into a full path. It was
// postponed to avoid a delay when <afile> is not used.
+ autocmd_fname_full = true;
result = FullName_save(autocmd_fname, false);
// Copy into `autocmd_fname`, don't reassign it. #8165
xstrlcpy(autocmd_fname, result, MAXPATHL);
@@ -6867,7 +7000,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
}
result = autocmd_fname;
if (result == NULL) {
- *errormsg = _("E495: no autocommand file name to substitute for \"<afile>\"");
+ *errormsg = _(e_no_autocommand_file_name_to_substitute_for_afile);
return NULL;
}
result = path_try_shorten_fname(result);
@@ -6875,7 +7008,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_ABUF: // buffer number for autocommand
if (autocmd_bufnr <= 0) {
- *errormsg = _("E496: no autocommand buffer number to substitute for \"<abuf>\"");
+ *errormsg = _(e_no_autocommand_buffer_number_to_substitute_for_abuf);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "%d", autocmd_bufnr);
@@ -6885,7 +7018,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_AMATCH: // match name for autocommand
result = autocmd_match;
if (result == NULL) {
- *errormsg = _("E497: no autocommand match name to substitute for \"<amatch>\"");
+ *errormsg = _(e_no_autocommand_match_name_to_substitute_for_amatch);
return NULL;
}
break;
@@ -6917,7 +7050,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_SLNUM: // line in file for ":so" command
if (SOURCING_NAME == NULL || SOURCING_LNUM == 0) {
- *errormsg = _("E842: no line number to use for \"<slnum>\"");
+ *errormsg = _(e_no_line_number_to_use_for_slnum);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, SOURCING_LNUM);
@@ -6926,10 +7059,10 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_SFLNUM: // line in script file
if (current_sctx.sc_lnum + SOURCING_LNUM == 0) {
- *errormsg = _("E961: no line number to use for \"<sflnum>\"");
+ *errormsg = _(e_no_line_number_to_use_for_sflnum);
return NULL;
}
- snprintf((char *)strbuf, sizeof(strbuf), "%" PRIdLINENR,
+ snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR,
current_sctx.sc_lnum + SOURCING_LNUM);
result = strbuf;
break;
@@ -6982,7 +7115,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
}
result = NULL;
} else {
- result = xstrnsave(result, resultlen);
+ result = xmemdupz(result, resultlen);
}
xfree(resultbuf);
return result;
@@ -7001,7 +7134,7 @@ char *expand_sfile(char *arg)
} else {
// replace "<sfile>" with the sourced file name, and do ":" stuff
size_t srclen;
- char *errormsg;
+ const char *errormsg;
char *repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL, true);
if (errormsg != NULL) {
if (*errormsg) {
@@ -7055,24 +7188,6 @@ void dialog_msg(char *buff, char *format, char *fname)
vim_snprintf(buff, DIALOG_MSG_SIZE, format, fname);
}
-/// ":behave {mswin,xterm}"
-static void ex_behave(exarg_T *eap)
-{
- if (strcmp(eap->arg, "mswin") == 0) {
- set_option_value_give_err("selection", 0L, "exclusive", 0);
- set_option_value_give_err("selectmode", 0L, "mouse,key", 0);
- set_option_value_give_err("mousemodel", 0L, "popup", 0);
- set_option_value_give_err("keymodel", 0L, "startsel,stopsel", 0);
- } else if (strcmp(eap->arg, "xterm") == 0) {
- set_option_value_give_err("selection", 0L, "inclusive", 0);
- set_option_value_give_err("selectmode", 0L, "", 0);
- set_option_value_give_err("mousemodel", 0L, "extend", 0);
- set_option_value_give_err("keymodel", 0L, "", 0);
- } else {
- semsg(_(e_invarg2), eap->arg);
- }
-}
-
static TriState filetype_detect = kNone;
static TriState filetype_plugin = kNone;
static TriState filetype_indent = kNone;
@@ -7088,7 +7203,7 @@ static void ex_filetype(exarg_T *eap)
{
if (*eap->arg == NUL) {
// Print current status.
- smsg("filetype detection:%s plugin:%s indent:%s",
+ smsg(0, "filetype detection:%s plugin:%s indent:%s",
filetype_detect == kTrue ? "ON" : "OFF",
filetype_plugin == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)") : "OFF",
filetype_indent == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)") : "OFF");
@@ -7100,7 +7215,7 @@ static void ex_filetype(exarg_T *eap)
bool indent = false;
// Accept "plugin" and "indent" in any order.
- for (;;) {
+ while (true) {
if (strncmp(arg, "plugin", 6) == 0) {
plugin = true;
arg = skipwhite(arg + 6);
@@ -7189,7 +7304,7 @@ static void ex_setfiletype(exarg_T *eap)
arg += 9;
}
- set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL);
+ set_option_value_give_err("filetype", CSTR_AS_OPTVAL(arg), OPT_LOCAL);
if (arg != eap->arg) {
did_filetype = false;
}
@@ -7271,12 +7386,31 @@ void set_pressedreturn(bool val)
static void ex_terminal(exarg_T *eap)
{
char ex_cmd[1024];
+ size_t len = 0;
+
+ if (cmdmod.cmod_tab > 0 || cmdmod.cmod_split != 0) {
+ bool multi_mods = false;
+
+ // ex_cmd must be a null terminated string before passing to add_win_cmd_modifiers
+ ex_cmd[0] = '\0';
+
+ len = add_win_cmd_modifiers(ex_cmd, &cmdmod, &multi_mods);
+ assert(len < sizeof(ex_cmd));
+ int result = snprintf(ex_cmd + len, sizeof(ex_cmd) - len, " new");
+ assert(result > 0);
+ len += (size_t)result;
+ } else {
+ int result = snprintf(ex_cmd, sizeof(ex_cmd), "enew%s", eap->forceit ? "!" : "");
+ assert(result > 0);
+ len += (size_t)result;
+ }
+
+ assert(len < sizeof(ex_cmd));
if (*eap->arg != NUL) { // Run {cmd} in 'shell'.
char *name = vim_strsave_escaped(eap->arg, "\"\\");
- snprintf(ex_cmd, sizeof(ex_cmd),
- ":enew%s | call termopen(\"%s\")",
- eap->forceit ? "!" : "", name);
+ snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
+ " | call termopen(\"%s\")", name);
xfree(name);
} else { // No {cmd}: run the job with tokenized 'shell'.
if (*p_sh == NUL) {
@@ -7296,302 +7430,308 @@ static void ex_terminal(exarg_T *eap)
}
shell_free_argv(argv);
- snprintf(ex_cmd, sizeof(ex_cmd),
- ":enew%s | call termopen([%s])",
- eap->forceit ? "!" : "", shell_argv + 1);
+ snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
+ " | call termopen([%s])", shell_argv + 1);
}
do_cmdline_cmd(ex_cmd);
}
+/// ":fclose"
+static void ex_fclose(exarg_T *eap)
+{
+ win_float_remove(eap->forceit, eap->line1);
+}
+
void verify_command(char *cmd)
{
if (strcmp("smile", cmd) != 0) {
return; // acceptable non-existing command
}
+ int a = HL_ATTR(HLF_E);
msg(" #xxn` #xnxx` ,+x@##@Mz;` .xxx"
- "xxxxxxnz+, znnnnnnnnnnnnnnnn.");
+ "xxxxxxnz+, znnnnnnnnnnnnnnnn.", a);
msg(" n###z x####` :x##########W+` ,###"
- "##########M; W################.");
+ "##########M; W################.", a);
msg(" n####; x####` `z##############W: ,###"
- "############# W################.");
+ "############# W################.", a);
msg(" n####W. x####` ,W#################+ ,###"
- "############## W################.");
+ "############## W################.", a);
msg(" n#####n x####` @################### ,###"
- "##############i W################.");
+ "##############i W################.", a);
msg(" n######i x####` .#########@W@########* ,###"
- "##############W`W################.");
+ "##############W`W################.", a);
msg(" n######@. x####` x######W*. `;n#######: ,###"
- "#x,,,,:*M######iW###@:,,,,,,,,,,,`");
+ "#x,,,,:*M######iW###@:,,,,,,,,,,,`", a);
msg(" n#######n x####` *######+` :M#####M ,###"
- "#n `x#####xW###@`");
+ "#n `x#####xW###@`", a);
msg(" n########* x####``@####@; `x#####i ,###"
- "#n ,#####@W###@`");
+ "#n ,#####@W###@`", a);
msg(" n########@ x####`*#####i `M####M ,###"
- "#n x#########@`");
+ "#n x#########@`", a);
msg(" n######### x####`M####z :#####:,###"
- "#n z#########@`");
+ "#n z#########@`", a);
msg(" n#########* x####,#####. n####+,###"
- "#n n#########@`");
+ "#n n#########@`", a);
msg(" n####@####@, x####i####x ;####x,###"
- "#n `W#####@####+++++++++++i");
+ "#n `W#####@####+++++++++++i", a);
msg(" n####*#####M` x#########* `####@,###"
- "#n i#####MW###############W");
+ "#n i#####MW###############W", a);
msg(" n####.######+ x####z####; W####,###"
- "#n i@######W###############W");
+ "#n i@######W###############W", a);
msg(" n####.`W#####: x####n####: M####:###"
- "#@nnnnnW#######,W###############W");
+ "#@nnnnnW#######,W###############W", a);
msg(" n####. :#####M`x####z####; W####,###"
- "##############z W###############W");
+ "##############z W###############W", a);
msg(" n####. #######x#########* `####W,###"
- "#############W` W###############W");
+ "#############W` W###############W", a);
msg(" n####. `M#####W####i####x ;####x,###"
- "############W, W####+**********i");
+ "############W, W####+**********i", a);
msg(" n####. ,##########,#####. n####+,###"
- "###########n. W###@`");
+ "###########n. W###@`", a);
msg(" n####. ##########`M####z :#####:,###"
- "########Wz: W###@`");
+ "########Wz: W###@`", a);
msg(" n####. x#########`*#####i `M####M ,###"
- "#x.....` W###@`");
+ "#x.....` W###@`", a);
msg(" n####. ,@########``@####@; `x#####i ,###"
- "#n W###@`");
+ "#n W###@`", a);
msg(" n####. *########` *#####@+` ,M#####M ,###"
- "#n W###@`");
+ "#n W###@`", a);
msg(" n####. x#######` x######W*. `;n######@: ,###"
- "#n W###@,,,,,,,,,,,,`");
+ "#n W###@,,,,,,,,,,,,`", a);
msg(" n####. .@######` .#########@W@########* ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. i######` @################### ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. n#####` ,W#################+ ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. .@####` .n##############W; ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. i####` :x##########W+` ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" +nnnn` +nnn` ,+x@##@Mz;` .nnn"
- "n+ zxxxxxxxxxxxxxxxx.");
- msg(" ");
+ "n+ zxxxxxxxxxxxxxxxx.", a);
+ msg(" ", a);
msg(" "
- " ,+M@#Mi");
+ " ,+M@#Mi", a);
msg(" "
- " .z########");
+ " .z########", a);
msg(" "
- " i@#########i");
+ " i@#########i", a);
msg(" "
- " `############W`");
+ " `############W`", a);
msg(" "
- " `n#############i");
+ " `n#############i", a);
msg(" "
- " `n##############n");
+ " `n##############n", a);
msg(" `` "
- " z###############@`");
+ " z###############@`", a);
msg(" `W@z, "
- " ##################,");
+ " ##################,", a);
msg(" *#####` "
- " i############@x@###i");
+ " i############@x@###i", a);
msg(" ######M. "
- " :#############n`,W##+");
+ " :#############n`,W##+", a);
msg(" +######@: "
- " .W#########M@##+ *##z");
+ " .W#########M@##+ *##z", a);
msg(" :#######@: "
- " `x########@#x###* ,##n");
+ " `x########@#x###* ,##n", a);
msg(" `@#######@; "
- " z#########M*@nW#i .##x");
+ " z#########M*@nW#i .##x", a);
msg(" z########@i "
- " *###########WM#@#, `##x");
+ " *###########WM#@#, `##x", a);
msg(" i##########+ "
- " ;###########*n###@ `##x");
+ " ;###########*n###@ `##x", a);
msg(" `@#MM#######x, "
- " ,@#########zM,`z##M `@#x");
+ " ,@#########zM,`z##M `@#x", a);
msg(" n##M#W#######n. "
- " `.:i*+#zzzz##+i:.` ,W#########Wii,`n@#@` n@##n");
+ " `.:i*+#zzzz##+i:.` ,W#########Wii,`n@#@` n@##n", a);
msg(" ;###@#x#######n `,i"
- "#nW@#####@@WWW@@####@Mzi. ,W##########@z.. ;zM#+i####z");
+ "#nW@#####@@WWW@@####@Mzi. ,W##########@z.. ;zM#+i####z", a);
msg(" x####nz######## .;#x@##"
- "@Wn#*;,.` ``,:*#x@##M+, ;@########xz@WM+#` `n@#######");
+ "@Wn#*;,.` ``,:*#x@##M+, ;@########xz@WM+#` `n@#######", a);
msg(" ,@####M########xi#@##@Mzi,"
- "` .+x###Mi:n##########Mz```.:i *@######*");
+ "` .+x###Mi:n##########Mz```.:i *@######*", a);
msg(" *#####W#########ix+:` "
- " :n#############z: `*.`M######i");
+ " :n#############z: `*.`M######i", a);
msg(" i#W##nW@+@##@#M@; "
- " ;W@@##########W, i`x@#####,");
+ " ;W@@##########W, i`x@#####,", a);
msg(" `@@n@Wn#@iMW*#*: "
- " `iz#z@######x. M######`");
+ " `iz#z@######x. M######`", a);
msg(" z##zM###x`*, .` "
- " `iW#####W;:` +#####M");
+ " `iW#####W;:` +#####M", a);
msg(" ,###nn##n` "
- " ,#####x;` ,;@######");
+ " ,#####x;` ,;@######", a);
msg(" x###xz#. "
- " in###+ `:######@.");
+ " in###+ `:######@.", a);
msg(" ;####n+ "
- " `Mnx##xi` , zM#######");
+ " `Mnx##xi` , zM#######", a);
msg(" `W####+ "
- "i. `.+x###@#. :n,z######:");
+ "i. `.+x###@#. :n,z######:", a);
msg(" z####@` ;"
- "#: .ii@###@;.*M*z####@`");
+ "#: .ii@###@;.*M*z####@`", a);
msg(" i####M ` `i@"
- "#, :: +#n##@+@##W####n");
+ "#, :: +#n##@+@##W####n", a);
msg(" :####x ,i. ##xzM###"
- "@` i. .@@, .z####x#######*");
+ "@` i. .@@, .z####x#######*", a);
msg(" ,###W; i##Wz########"
- "# :## z##n ,@########x###:");
+ "# :## z##n ,@########x###:", a);
msg(" n##n `W###########M"
- "`;n, i#x ,###@i *W########W#@`");
+ "`;n, i#x ,###@i *W########W#@`", a);
msg(" .@##+ `x###########@."
- " z#+ .M#W``x#####n` `;#######@z#x");
+ " z#+ .M#W``x#####n` `;#######@z#x", a);
msg(" n###z :W############@ "
- " z#* @##xM#######@n; `########nW+");
+ " z#* @##xM#######@n; `########nW+", a);
msg(" ;####nW##############W "
- ":@#* `@#############* :########z@i`");
+ ":@#* `@#############* :########z@i`", a);
msg(" M##################### "
- "M##: @#############@: *W########M#");
+ "M##: @#############@: *W########M#", a);
msg(" ;#####################i."
- "##x` W#############W, :n########zx");
+ "##x` W#############W, :n########zx", a);
msg(" x####################@.`"
- "x; @#############z. .@########W#");
+ "x; @#############z. .@########W#", a);
msg(" ,######################` "
- " W###############x*,` W######zM#i");
+ " W###############x*,` W######zM#i", a);
msg(" #######################: "
- " z##################@x+*#zzi `@#########.");
+ " z##################@x+*#zzi `@#########.", a);
msg(" W########W#z#M#########; "
- " *##########################z :@#######@`");
+ " *##########################z :@#######@`", a);
msg(" `@#######x`;#z ,x#######; "
- " z###########M###xnM@########* :M######@");
+ " z###########M###xnM@########* :M######@", a);
msg(" i########, x#@` z######; "
- " *##########i *#@` `+########+` n######.");
+ " *##########i *#@` `+########+` n######.", a);
msg(" n#######@` M##, `W#####. "
- " *#########z ###; z########M: :W####n");
+ " *#########z ###; z########M: :W####n", a);
msg(" M#######M n##. x####x "
- " `x########: z##+ M#########@; .n###+");
+ " `x########: z##+ M#########@; .n###+", a);
msg(" W#######@` :#W `@####: "
- " `@######W i### ;###########@. n##n");
+ " `@######W i### ;###########@. n##n", a);
msg(" W########z` ,, .x####z "
- " @######@` `W#; `W############* *###;");
+ " @######@` `W#; `W############* *###;", a);
msg(" `@#########Mi,:*n@####W` "
- " W#######* .. `n#############i i###x");
+ " W#######* .. `n#############i i###x", a);
msg(" .#####################z "
- " `@#######@*` .x############n:` ;####.");
+ " `@#######@*` .x############n:` ;####.", a);
msg(" :####################x`,,` "
- " `W#########@x#+#@#############i ,####:");
+ " `W#########@x#+#@#############i ,####:", a);
msg(" ;###################x#@###x"
- "i` *############################: `####i");
+ "i` *############################: `####i", a);
msg(" i##################+#######"
- "#M, x##########################@` W###i");
+ "#M, x##########################@` W###i", a);
msg(" *################@; @######"
- "##@, .W#########################@ x###:");
+ "##@, .W#########################@ x###:", a);
msg(" .+M#############z. M######"
- "###x ,W########################@` ####.");
+ "###x ,W########################@` ####.", a);
msg(" *M*;z@########x: :W#####"
- "##i .M########################i i###:");
+ "##i .M########################i i###:", a);
msg(" *##@z;#@####x: :z###"
- "@i `########################x .###;");
+ "@i `########################x .###;", a);
msg(" *#####n;#@## ;##"
- "* ,x#####################@` W##*");
+ "* ,x#####################@` W##*", a);
msg(" *#######n;* :M##"
- "W*, *W####################` n##z");
+ "W*, *W####################` n##z", a);
msg(" i########@. ,*n####"
- "###M*` `###################M *##M");
+ "###M*` `###################M *##M", a);
msg(" i########n `z#####@@"
- "#####Wi ,M################; ,##@`");
+ "#####Wi ,M################; ,##@`", a);
msg(" ;WMWW@###* .x##@ni.``"
- ".:+zW##z` `n##############z @##,");
+ ".:+zW##z` `n##############z @##,", a);
msg(" .*++*i;;;. .M#@+` "
- " .##n `x############x` n##i");
+ " .##n `x############x` n##i", a);
msg(" :########* x#W, "
- " *#+ *###########M` +##+");
+ " *#+ *###########M` +##+", a);
msg(" ,######### :#@: "
- " ##: #nzzzzzzzzzz. :##x");
+ " ##: #nzzzzzzzzzz. :##x", a);
msg(" .#####Wz+` ##+ "
- " `MM` .znnnnnnnnn. `@#@`");
+ " `MM` .znnnnnnnnn. `@#@`", a);
msg(" `@@ni;*nMz` @W` "
- " :#+ .x#######n x##,");
+ " :#+ .x#######n x##,", a);
msg(" i;z@#####, .#* "
- " z#: ;;;*zW##; ###i");
+ " z#: ;;;*zW##; ###i", a);
msg(" z########: :#; "
- " `Wx +###Wni;n. ;##z");
+ " `Wx +###Wni;n. ;##z", a);
msg(" n########W: .#* "
- " ,#, ;#######@+ `@#M");
+ " ,#, ;#######@+ `@#M", a);
msg(" .###########n;.MM "
- " n* ;iM#######* x#@`");
+ " n* ;iM#######* x#@`", a);
msg(" :#############@;; "
- " .n` ,#W*iW#####W` +##,");
+ " .n` ,#W*iW#####W` +##,", a);
msg(" ,##############. "
- " ix. `x###M;####### ,##i");
+ " ix. `x###M;####### ,##i", a);
msg(" .#############@` "
- " x@n**#W######z;M###@. W##");
+ " x@n**#W######z;M###@. W##", a);
msg(" .##############W: "
- " .x############@*;zW#; z#x");
+ " .x############@*;zW#; z#x", a);
msg(" ,###############@; "
- " `##############@n*;. i#@");
+ " `##############@n*;. i#@", a);
msg(" ,#################i "
- " :n##############W` .##,");
+ " :n##############W` .##,", a);
msg(" ,###################` "
- " .+W##########W, `##i");
+ " .+W##########W, `##i", a);
msg(" :###################@zi,` "
- " ;zM@@@WMn*` @#z");
+ " ;zM@@@WMn*` @#z", a);
msg(" :#######################@x+"
- "*i;;:i#M, `` M#W");
+ "*i;;:i#M, `` M#W", a);
msg(" ;##########################"
- "######@x. n##,");
+ "######@x. n##,", a);
msg(" i#####################@W@@@"
- "@Wxz*:` *##+");
+ "@Wxz*:` *##+", a);
msg(" *######################+```"
- " :##M");
+ " :##M", a);
msg(" ########################M; "
- " `@##,");
+ " `@##,", a);
msg(" z#########################x"
- ", z###");
+ ", z###", a);
msg(" n##########################"
- "#n: ;##W`");
+ "#n: ;##W`", a);
msg(" x##########################"
- "###Mz#++##* `W##i");
+ "###Mz#++##* `W##i", a);
msg(" M##########################"
- "##########@` ###x");
+ "##########@` ###x", a);
msg(" W##########################"
- "###########` .###,");
+ "###########` .###,", a);
msg(" @##########################"
- "##########M n##z");
+ "##########M n##z", a);
msg(" @##################z*i@WMMM"
- "x#x@#####,. :##@.");
+ "x#x@#####,. :##@.", a);
msg(" `#####################@xi` "
- " `::,* x##+");
+ " `::,* x##+", a);
msg(" .#####################@#M. "
- " ;##@`");
+ " ;##@`", a);
msg(" ,#####################:. "
- " M##i");
+ " M##i", a);
msg(" ;###################ni` "
- " i##M");
+ " i##M", a);
msg(" *#################W#` "
- " `W##,");
+ " `W##,", a);
msg(" z#################@Wx+. "
- " +###");
+ " +###", a);
msg(" x######################z. "
- " .@#@`");
+ " .@#@`", a);
msg(" `@#######################@; "
- " z##;");
+ " z##;", a);
msg(" :##########################: "
- " :##z");
+ " :##z", a);
msg(" +#########################W# "
- " M#W");
+ " M#W", a);
msg(" W################@n+*i;:,` "
- " +##,");
+ " +##,", a);
msg(" :##################WMxz+, "
- " ,##i");
+ " ,##i", a);
msg(" n#######################W.., "
- " W##");
+ " W##", a);
msg(" +#########################WW@+. .:. "
- " z#x");
+ " z#x", a);
msg(" `@#############################@@###: "
- " *#W");
+ " *#W", a);
msg(" #################################Wz: "
- " :#@");
+ " :#@", a);
msg(",@###############################i "
- " .##");
+ " .##", a);
msg("n@@@@@@@#########################+ "
- " `##");
+ " `##", a);
msg("` `.:.`.,:iii;;;;;;;;iii;;;:` `.`` "
- " `nW");
+ " `nW", a);
}
/// Get argt of command with id
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index 19dd9e96ca..698153e8df 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -1,36 +1,44 @@
-#ifndef NVIM_EX_DOCMD_H
-#define NVIM_EX_DOCMD_H
+#pragma once
#include <stdbool.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/getchar_defs.h"
#include "nvim/globals.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
-// flags for do_cmdline()
-#define DOCMD_VERBOSE 0x01 // included command in error message
-#define DOCMD_NOWAIT 0x02 // don't call wait_return() and friends
-#define DOCMD_REPEAT 0x04 // repeat exec. until getline() returns NULL
-#define DOCMD_KEYTYPED 0x08 // don't reset KeyTyped
-#define DOCMD_EXCRESET 0x10 // reset exception environment (for debugging
-#define DOCMD_KEEPLINE 0x20 // keep typed line for repeating with "."
+/// flags for do_cmdline()
+enum {
+ DOCMD_VERBOSE = 0x01, ///< included command in error message
+ DOCMD_NOWAIT = 0x02, ///< don't call wait_return() and friends
+ DOCMD_REPEAT = 0x04, ///< repeat exec. until getline() returns NULL
+ DOCMD_KEYTYPED = 0x08, ///< don't reset KeyTyped
+ DOCMD_EXCRESET = 0x10, ///< reset exception environment (for debugging
+ DOCMD_KEEPLINE = 0x20, ///< keep typed line for repeating with "."
+};
-// defines for eval_vars()
-#define VALID_PATH 1
-#define VALID_HEAD 2
+/// defines for eval_vars()
+enum {
+ VALID_PATH = 1,
+ VALID_HEAD = 2,
+};
// Whether a command index indicates a user command.
#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
-// Structure used to save the current state. Used when executing Normal mode
-// commands while in any other mode.
+enum { DIALOG_MSG_SIZE = 1000, }; ///< buffer size for dialog_msg()
+
+/// Structure used to save the current state. Used when executing Normal mode
+/// commands while in any other mode.
typedef struct {
int save_msg_scroll;
int save_restart_edit;
bool save_msg_didout;
int save_State;
bool save_finish_op;
- long save_opcount;
+ int save_opcount;
int save_reg_executing;
bool save_pending_end_reg_executing;
tasave_T tabuf;
@@ -39,4 +47,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.h.generated.h"
#endif
-#endif // NVIM_EX_DOCMD_H
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index f76e60f6c5..d2a1d53b78 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file ex_eval.c
///
/// Functions for Ex command line for the +eval feature.
@@ -11,33 +8,34 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_eval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_eval.c.generated.h"
#endif
+static const char e_multiple_else[] = N_("E583: Multiple :else");
+static const char e_multiple_finally[] = N_("E607: Multiple :finally");
+
// Exception handling terms:
//
// :try ":try" command ─â”
@@ -154,11 +152,10 @@ int aborted_in_try(void)
/// When several messages appear in the same command, the first is usually the
/// most specific one and used as the exception value. The "severe" flag can be
/// set to true, if a later but severer message should be used instead.
-bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
+bool cause_errthrow(const char *mesg, bool multiline, bool severe, bool *ignore)
FUNC_ATTR_NONNULL_ALL
{
msglist_T *elem;
- msglist_T **plist;
// Do nothing when displaying the interrupt message or reporting an
// uncaught exception (which has already been discarded then) at the top
@@ -239,21 +236,20 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
// returned. - Throw only the first of several errors in a row, except
// a severe error is following.
if (msg_list != NULL) {
- plist = msg_list;
+ msglist_T **plist = msg_list;
while (*plist != NULL) {
plist = &(*plist)->next;
}
elem = xmalloc(sizeof(msglist_T));
elem->msg = xstrdup(mesg);
+ elem->multiline = multiline;
elem->next = NULL;
elem->throw_msg = NULL;
*plist = elem;
if (plist == msg_list || severe) {
- char *tmsg;
-
// Skip the extra "Vim " prefix for message "E458".
- tmsg = elem->msg;
+ char *tmsg = elem->msg;
if (strncmp(tmsg, "Vim E", 5) == 0
&& ascii_isdigit(tmsg[5])
&& ascii_isdigit(tmsg[6])
@@ -278,11 +274,9 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
/// Free a "msg_list" and the messages it contains.
static void free_msglist(msglist_T *l)
{
- msglist_T *messages, *next;
-
- messages = l;
+ msglist_T *messages = l;
while (messages != NULL) {
- next = messages->next;
+ msglist_T *next = messages->next;
xfree(messages->msg);
xfree(messages->sfile);
xfree(messages);
@@ -378,12 +372,12 @@ int do_intthrow(cstack_T *cstack)
/// Get an exception message that is to be stored in current_exception->value.
char *get_exception_string(void *value, except_type_T type, char *cmdname, int *should_free)
{
- char *ret, *mesg;
- char *p, *val;
+ char *ret;
if (type == ET_ERROR) {
+ char *val;
*should_free = true;
- mesg = ((msglist_T *)value)->throw_msg;
+ char *mesg = ((msglist_T *)value)->throw_msg;
if (cmdname != NULL && *cmdname != NUL) {
size_t cmdlen = strlen(cmdname);
ret = xstrnsave("Vim(", 4 + cmdlen + 2 + strlen(mesg));
@@ -398,7 +392,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int *
// msg_add_fname may have been used to prefix the message with a file
// name in quotes. In the exception value, put the file name in
// parentheses and move it to the end.
- for (p = mesg;; p++) {
+ for (char *p = mesg;; p++) {
if (*p == NUL
|| (*p == 'E'
&& ascii_isdigit(p[1])
@@ -441,9 +435,6 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int *
/// exception.
static int throw_exception(void *value, except_type_T type, char *cmdname)
{
- except_T *excp;
- int should_free;
-
// Disallow faking Interrupt or error exceptions as user exceptions. They
// would be treated differently from real interrupt or error exceptions
// when no active try block is found, see do_cmdline().
@@ -456,7 +447,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
}
}
- excp = xmalloc(sizeof(except_T));
+ except_T *excp = xmalloc(sizeof(except_T));
if (type == ET_ERROR) {
// Store the original message and prefix the exception value with
@@ -464,6 +455,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
excp->messages = (msglist_T *)value;
}
+ int should_free;
excp->value = get_exception_string(value, type, cmdname, &should_free);
if (excp->value == NULL && should_free) {
goto nomem;
@@ -495,7 +487,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = true; // always scroll up, don't overwrite
}
- smsg(_("Exception thrown: %s"), excp->value);
+ smsg(0, _("Exception thrown: %s"), excp->value);
msg_puts("\n"); // don't overwrite this either
if (debug_break_level > 0 || *p_vfile == NUL) {
@@ -525,8 +517,6 @@ fail:
/// caught and the catch clause has been ended normally.
static void discard_exception(except_T *excp, bool was_finished)
{
- char *saved_IObuff;
-
if (current_exception == excp) {
current_exception = NULL;
}
@@ -538,7 +528,7 @@ static void discard_exception(except_T *excp, bool was_finished)
if (p_verbose >= 13 || debug_break_level > 0) {
int save_msg_silent = msg_silent;
- saved_IObuff = xstrdup(IObuff);
+ char *saved_IObuff = xstrdup(IObuff);
if (debug_break_level > 0) {
msg_silent = false; // display messages
} else {
@@ -548,7 +538,7 @@ static void discard_exception(except_T *excp, bool was_finished)
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = true; // always scroll up, don't overwrite
}
- smsg(was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), excp->value);
+ smsg(0, was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), excp->value);
msg_puts("\n"); // don't overwrite this either
if (debug_break_level > 0 || *p_vfile == NUL) {
cmdline_row = msg_row;
@@ -615,7 +605,7 @@ static void catch_exception(except_T *excp)
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = true; // always scroll up, don't overwrite
}
- smsg(_("Exception caught: %s"), excp->value);
+ smsg(0, _("Exception caught: %s"), excp->value);
msg_puts("\n"); // don't overwrite this either
if (debug_break_level > 0 || *p_vfile == NUL) {
@@ -663,6 +653,40 @@ static void finish_exception(except_T *excp)
discard_exception(excp, true);
}
+/// Save the current exception state in "estate"
+void exception_state_save(exception_state_T *estate)
+{
+ estate->estate_current_exception = current_exception;
+ estate->estate_did_throw = did_throw;
+ estate->estate_need_rethrow = need_rethrow;
+ estate->estate_trylevel = trylevel;
+ estate->estate_did_emsg = did_emsg;
+}
+
+/// Restore the current exception state from "estate"
+void exception_state_restore(exception_state_T *estate)
+{
+ // Handle any outstanding exceptions before restoring the state
+ if (did_throw) {
+ handle_did_throw();
+ }
+ current_exception = estate->estate_current_exception;
+ did_throw = estate->estate_did_throw;
+ need_rethrow = estate->estate_need_rethrow;
+ trylevel = estate->estate_trylevel;
+ did_emsg = estate->estate_did_emsg;
+}
+
+/// Clear the current exception state
+void exception_state_clear(void)
+{
+ current_exception = NULL;
+ did_throw = false;
+ need_rethrow = false;
+ trylevel = 0;
+ did_emsg = 0;
+}
+
// Flags specifying the message displayed by report_pending.
#define RP_MAKE 0
#define RP_RESUME 1
@@ -677,7 +701,6 @@ static void report_pending(int action, int pending, void *value)
{
char *mesg;
char *s;
- int save_msg_silent;
assert(value || !(pending & CSTP_THROW));
@@ -727,13 +750,13 @@ static void report_pending(int action, int pending, void *value)
}
}
- save_msg_silent = msg_silent;
+ int save_msg_silent = msg_silent;
if (debug_break_level > 0) {
msg_silent = false; // display messages
}
no_wait_return++;
msg_scroll = true; // always scroll up, don't overwrite
- smsg(mesg, s);
+ smsg(0, mesg, s);
msg_puts("\n"); // don't overwrite this either
cmdline_row = msg_row;
no_wait_return--;
@@ -797,17 +820,20 @@ void report_discard_pending(int pending, void *value)
void ex_eval(exarg_T *eap)
{
typval_T tv;
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
- if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) {
+ if (eval0(eap->arg, &tv, eap, &evalarg) == OK) {
tv_clear(&tv);
}
+
+ clear_evalarg(&evalarg, eap);
}
/// Handle ":if".
void ex_if(exarg_T *eap)
{
- int skip;
- int result;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1) {
@@ -816,10 +842,10 @@ void ex_if(exarg_T *eap)
cstack->cs_idx++;
cstack->cs_flags[cstack->cs_idx] = 0;
- skip = CHECK_SKIP;
+ int skip = CHECK_SKIP;
bool error;
- result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip);
+ int result = eval_to_bool(eap->arg, &error, eap, skip);
if (!skip && !error) {
if (result) {
@@ -860,7 +886,6 @@ void ex_endif(exarg_T *eap)
/// Handle ":else" and ":elseif".
void ex_else(exarg_T *eap)
{
- bool result = false;
cstack_T *const cstack = eap->cstack;
bool skip = CHECK_SKIP;
@@ -876,7 +901,7 @@ void ex_else(exarg_T *eap)
skip = true;
} else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) {
if (eap->cmdidx == CMD_else) {
- eap->errmsg = _("E583: multiple :else");
+ eap->errmsg = _(e_multiple_else);
return;
}
eap->errmsg = _("E584: :elseif after :else");
@@ -907,6 +932,7 @@ void ex_else(exarg_T *eap)
}
if (eap->cmdidx == CMD_elseif) {
+ bool result = false;
bool error;
// When skipping we ignore most errors, but a missing expression is
// wrong, perhaps it should have been "else".
@@ -914,7 +940,7 @@ void ex_else(exarg_T *eap)
if (skip && *eap->arg != '"' && ends_excmd(*eap->arg)) {
semsg(_(e_invexpr2), eap->arg);
} else {
- result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip);
+ result = eval_to_bool(eap->arg, &error, eap, skip);
}
// When throwing error exceptions, we want to throw always the first
@@ -941,13 +967,12 @@ void ex_else(exarg_T *eap)
void ex_while(exarg_T *eap)
{
bool error;
- int skip;
- int result;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1) {
eap->errmsg = _("E585: :while/:for nesting too deep");
} else {
+ int result;
// The loop flag is set when we have jumped back from the matching
// ":endwhile" or ":endfor". When not set, need to initialise this
// cstack entry.
@@ -959,14 +984,13 @@ void ex_while(exarg_T *eap)
cstack->cs_flags[cstack->cs_idx] =
eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR;
- skip = CHECK_SKIP;
- if (eap->cmdidx == CMD_while) {
- // ":while bool-expr"
- result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip);
- } else {
+ int skip = CHECK_SKIP;
+ if (eap->cmdidx == CMD_while) { // ":while bool-expr"
+ result = eval_to_bool(eap->arg, &error, eap, skip);
+ } else { // ":for var in list-expr"
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, skip);
void *fi;
-
- // ":for var in list-expr"
if ((cstack->cs_lflags & CSL_HAD_LOOP) != 0) {
// Jumping here from a ":continue" or ":endfor": use the
// previously evaluated list.
@@ -974,7 +998,7 @@ void ex_while(exarg_T *eap)
error = false;
} else {
// Evaluate the argument and get the info in a structure.
- fi = eval_for_line(eap->arg, &error, &eap->nextcmd, skip);
+ fi = eval_for_line(eap->arg, &error, eap, &evalarg);
cstack->cs_forinfo[cstack->cs_idx] = fi;
}
@@ -989,6 +1013,7 @@ void ex_while(exarg_T *eap)
free_for_info(fi);
cstack->cs_forinfo[cstack->cs_idx] = NULL;
}
+ clear_evalarg(&evalarg, eap);
}
// If this cstack entry was just initialised and is active, set the
@@ -1013,7 +1038,6 @@ void ex_while(exarg_T *eap)
/// Handle ":continue"
void ex_continue(exarg_T *eap)
{
- int idx;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) {
@@ -1023,7 +1047,7 @@ void ex_continue(exarg_T *eap)
// conditional not in its finally clause (which is then to be executed
// next). Therefore, deactivate all conditionals except the ":while"
// itself (if reached).
- idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, false);
+ int idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, false);
assert(idx >= 0);
if (cstack->cs_flags[idx] & (CSF_WHILE | CSF_FOR)) {
rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel);
@@ -1043,7 +1067,6 @@ void ex_continue(exarg_T *eap)
/// Handle ":break"
void ex_break(exarg_T *eap)
{
- int idx;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) {
@@ -1053,7 +1076,7 @@ void ex_break(exarg_T *eap)
// conditional not in its finally clause (which is then to be
// executed next) is found. In the latter case, make the ":break"
// pending for execution at the ":endtry".
- idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, true);
+ int idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, true);
if (idx >= 0 && !(cstack->cs_flags[idx] & (CSF_WHILE | CSF_FOR))) {
cstack->cs_pending[idx] = CSTP_BREAK;
report_make_pending(CSTP_BREAK, NULL);
@@ -1065,10 +1088,8 @@ void ex_break(exarg_T *eap)
void ex_endwhile(exarg_T *eap)
{
cstack_T *const cstack = eap->cstack;
- int idx;
- char *err;
+ const char *err;
int csf;
- int fl;
if (eap->cmdidx == CMD_endwhile) {
err = e_while;
@@ -1081,7 +1102,7 @@ void ex_endwhile(exarg_T *eap)
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) {
eap->errmsg = _(err);
} else {
- fl = cstack->cs_flags[cstack->cs_idx];
+ int fl = cstack->cs_flags[cstack->cs_idx];
if (!(fl & csf)) {
// If we are in a ":while" or ":for" but used the wrong endloop
// command, do not rewind to the next enclosing ":for"/":while".
@@ -1098,6 +1119,7 @@ void ex_endwhile(exarg_T *eap)
eap->errmsg = _(e_endtry);
}
// Try to find the matching ":while" and report what's missing.
+ int idx;
for (idx = cstack->cs_idx; idx > 0; idx--) {
fl = cstack->cs_flags[idx];
if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) {
@@ -1136,12 +1158,11 @@ void ex_endwhile(exarg_T *eap)
/// Handle ":throw expr"
void ex_throw(exarg_T *eap)
{
- const char *arg = (const char *)eap->arg;
+ char *arg = eap->arg;
char *value;
if (*arg != NUL && *arg != '|' && *arg != '\n') {
- value = eval_to_string_skip(arg, (const char **)&eap->nextcmd,
- (bool)eap->skip);
+ value = eval_to_string_skip(arg, eap, eap->skip);
} else {
emsg(_(e_argreq));
value = NULL;
@@ -1163,10 +1184,8 @@ void ex_throw(exarg_T *eap)
/// used for rethrowing an uncaught exception.
void do_throw(cstack_T *cstack)
{
- int idx;
int inactivate_try = false;
- //
// Cleanup and deactivate up to the next surrounding try conditional that
// is not in its finally clause. Normally, do not deactivate the try
// conditional itself, so that its ACTIVE flag can be tested below. But
@@ -1174,7 +1193,7 @@ void do_throw(cstack_T *cstack)
// deactivate the try conditional, too, as if the conversion had been done,
// and reset the did_emsg or got_int flag, so this won't happen again at
// the next surrounding try conditional.
- //
+
#ifndef THROW_ON_ERROR_TRUE
if (did_emsg && !THROW_ON_ERROR) {
inactivate_try = true;
@@ -1187,7 +1206,7 @@ void do_throw(cstack_T *cstack)
got_int = false;
}
#endif
- idx = cleanup_conditionals(cstack, 0, inactivate_try);
+ int idx = cleanup_conditionals(cstack, 0, inactivate_try);
if (idx >= 0) {
// If this try conditional is active and we are before its first
// ":catch", set THROWN so that the ":catch" commands will check
@@ -1220,7 +1239,6 @@ void do_throw(cstack_T *cstack)
/// Handle ":try"
void ex_try(exarg_T *eap)
{
- int skip;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1) {
@@ -1231,7 +1249,7 @@ void ex_try(exarg_T *eap)
cstack->cs_flags[cstack->cs_idx] = CSF_TRY;
cstack->cs_pending[cstack->cs_idx] = CSTP_NONE;
- skip = CHECK_SKIP;
+ int skip = CHECK_SKIP;
if (!skip) {
// Set ACTIVE and TRUE. TRUE means that the corresponding ":catch"
@@ -1271,12 +1289,9 @@ void ex_catch(exarg_T *eap)
int idx = 0;
bool give_up = false;
bool skip = false;
- bool caught = false;
char *end;
- char save_char = 0;
char *save_cpo;
regmatch_T regmatch;
- int prev_got_int;
cstack_T *const cstack = eap->cstack;
char *pat;
@@ -1319,6 +1334,7 @@ void ex_catch(exarg_T *eap)
}
if (!give_up) {
+ bool caught = false;
// Don't do something when no exception has been thrown or when the
// corresponding try block never got active (because of an inactive
// surrounding conditional or after an error or interrupt or throw).
@@ -1344,6 +1360,7 @@ void ex_catch(exarg_T *eap)
// the original exception, replace it by an interrupt exception,
// and don't catch it in this try block.
if (!dbg_check_skipped(eap) || !do_intthrow(cstack)) {
+ char save_char = 0;
// Terminate the pattern and avoid the 'l' flag in 'cpoptions'
// while compiling it.
if (end != NULL) {
@@ -1351,7 +1368,7 @@ void ex_catch(exarg_T *eap)
*end = NUL;
}
save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
// Disable error messages, it will make current exception
// invalid
emsg_off++;
@@ -1365,14 +1382,13 @@ void ex_catch(exarg_T *eap)
if (regmatch.regprog == NULL) {
semsg(_(e_invarg2), pat);
} else {
- //
// Save the value of got_int and reset it. We don't want
// a previous interruption cancel matching, only hitting
// CTRL-C while matching should abort it.
- //
- prev_got_int = got_int;
+
+ int prev_got_int = got_int;
got_int = false;
- caught = vim_regexec_nl(&regmatch, current_exception->value, (colnr_T)0);
+ caught = vim_regexec_nl(&regmatch, current_exception->value, 0);
got_int |= prev_got_int;
vim_regfree(regmatch.regprog);
}
@@ -1415,7 +1431,6 @@ void ex_catch(exarg_T *eap)
void ex_finally(exarg_T *eap)
{
int idx;
- int skip = false;
int pending = CSTP_NONE;
cstack_T *const cstack = eap->cstack;
@@ -1439,7 +1454,7 @@ void ex_finally(exarg_T *eap)
if (cstack->cs_flags[idx] & CSF_FINALLY) {
// Give up for a multiple ":finally" and ignore it.
- eap->errmsg = _("E607: multiple :finally");
+ eap->errmsg = _(e_multiple_finally);
return;
}
rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
@@ -1451,7 +1466,7 @@ void ex_finally(exarg_T *eap)
// ":finally". After every other error (did_emsg or the conditional
// errors detected above) or after an interrupt (got_int) or an
// exception (did_throw), the finally clause must be executed.
- skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
+ int skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
if (!skip) {
// When debugging or a breakpoint was encountered, display the
@@ -1497,8 +1512,8 @@ void ex_finally(exarg_T *eap)
} else {
pending |= (did_throw ? CSTP_THROW : 0);
}
- pending |= did_emsg ? CSTP_ERROR : 0;
- pending |= got_int ? CSTP_INTERRUPT : 0;
+ pending |= did_emsg ? CSTP_ERROR : 0;
+ pending |= got_int ? CSTP_INTERRUPT : 0;
assert(pending >= CHAR_MIN && pending <= CHAR_MAX);
cstack->cs_pending[cstack->cs_idx] = (char)pending;
@@ -1644,8 +1659,9 @@ void ex_endtry(exarg_T *eap)
if (!skip) {
report_resume_pending(pending,
- (pending == CSTP_RETURN) ? rettv :
- (pending & CSTP_THROW) ? (void *)current_exception : NULL);
+ (pending == CSTP_RETURN)
+ ? rettv
+ : (pending & CSTP_THROW) ? (void *)current_exception : NULL);
switch (pending) {
case CSTP_NONE:
break;
@@ -1979,20 +1995,18 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev
/// Handle ":endfunction" when not after a ":function"
void ex_endfunction(exarg_T *eap)
{
- emsg(_("E193: :endfunction not inside a function"));
+ semsg(_(e_str_not_inside_function), ":endfunction");
}
/// @return true if the string "p" looks like a ":while" or ":for" command.
int has_loop_cmd(char *p)
{
- int len;
-
// skip modifiers, white space and ':'
- for (;;) {
+ while (true) {
while (*p == ' ' || *p == '\t' || *p == ':') {
p++;
}
- len = modifier_len(p);
+ int len = modifier_len(p);
if (len == 0) {
break;
}
diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h
index d3053ae0d4..0294acd109 100644
--- a/src/nvim/ex_eval.h
+++ b/src/nvim/ex_eval.h
@@ -1,10 +1,8 @@
-#ifndef NVIM_EX_EVAL_H
-#define NVIM_EX_EVAL_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/ex_eval_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/ex_eval_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_eval.h.generated.h"
#endif
-#endif // NVIM_EX_EVAL_H
diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h
index 6b3c426722..c7231bb315 100644
--- a/src/nvim/ex_eval_defs.h
+++ b/src/nvim/ex_eval_defs.h
@@ -1,7 +1,39 @@
-#ifndef NVIM_EX_EVAL_DEFS_H
-#define NVIM_EX_EVAL_DEFS_H
+#pragma once
-#include "nvim/pos.h"
+#include <stdbool.h>
+
+#include "nvim/pos_defs.h"
+
+/// A list used for saving values of "emsg_silent". Used by ex_try() to save the
+/// value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT
+/// flag below is set.
+typedef struct eslist_elem eslist_T;
+struct eslist_elem {
+ int saved_emsg_silent; ///< saved value of "emsg_silent"
+ eslist_T *next; ///< next element on the list
+};
+
+/// For conditional commands a stack is kept of nested conditionals.
+/// When cs_idx < 0, there is no conditional command.
+enum { CSTACK_LEN = 50, };
+
+typedef struct {
+ int cs_flags[CSTACK_LEN]; ///< CSF_ flags
+ char cs_pending[CSTACK_LEN]; ///< CSTP_: what's pending in ":finally"
+ union {
+ void *csp_rv[CSTACK_LEN]; ///< return typeval for pending return
+ void *csp_ex[CSTACK_LEN]; ///< exception for pending throw
+ } cs_pend;
+ void *cs_forinfo[CSTACK_LEN]; ///< info used by ":for"
+ int cs_line[CSTACK_LEN]; ///< line nr of ":while"/":for" line
+ int cs_idx; ///< current entry, or -1 if none
+ int cs_looplevel; ///< nr of nested ":while"s and ":for"s
+ int cs_trylevel; ///< nr of nested ":try"s
+ eslist_T *cs_emsg_silent_list; ///< saved values of "emsg_silent"
+ int cs_lflags; ///< loop flags: CSL_ flags
+} cstack_T;
+#define cs_rettv cs_pend.csp_rv
+#define cs_exception cs_pend.csp_ex
/// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if"
/// was used.
@@ -35,17 +67,26 @@ enum {
CSTP_FINISH = 32, ///< ":finish" is pending
};
+/// Flags for the cs_lflags item in cstack_T.
+enum {
+ CSL_HAD_LOOP = 1, ///< just found ":while" or ":for"
+ CSL_HAD_ENDLOOP = 2, ///< just found ":endwhile" or ":endfor"
+ CSL_HAD_CONT = 4, ///< just found ":continue"
+ CSL_HAD_FINA = 8, ///< just found ":finally"
+};
+
/// A list of error messages that can be converted to an exception. "throw_msg"
/// is only set in the first element of the list. Usually, it points to the
/// original message stored in that element, but sometimes it points to a later
/// message in the list. See cause_errthrow().
typedef struct msglist msglist_T;
struct msglist {
+ msglist_T *next; ///< next of several messages in a row
char *msg; ///< original message, allocated
char *throw_msg; ///< msg to throw: usually original one
char *sfile; ///< value from estack_sfile(), allocated
linenr_T slnum; ///< line number for "sfile"
- msglist_T *next; ///< next of several messages in a row
+ bool multiline; ///< whether this is a multiline message
};
/// The exception types.
@@ -76,4 +117,13 @@ struct cleanup_stuff {
except_T *exception; ///< exception value
};
-#endif // NVIM_EX_EVAL_DEFS_H
+/// Exception state that is saved and restored when calling timer callback
+/// functions and deferred functions.
+typedef struct exception_state_S exception_state_T;
+struct exception_state_S {
+ except_T *estate_current_exception;
+ bool estate_did_throw;
+ bool estate_need_rethrow;
+ int estate_trylevel;
+ int estate_did_emsg;
+};
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 76c3680742..64ef17b157 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ex_getln.c: Functions for entering and editing an Ex command line.
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -17,7 +15,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/arabic.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -29,21 +27,23 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -55,22 +55,24 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
#include "nvim/window.h"
@@ -82,6 +84,7 @@ static unsigned last_prompt_id = 0;
typedef struct {
colnr_T vs_curswant;
colnr_T vs_leftcol;
+ colnr_T vs_skipcol;
linenr_T vs_topline;
int vs_topfill;
linenr_T vs_botline;
@@ -104,7 +107,7 @@ typedef struct {
typedef struct command_line_state {
VimState state;
int firstc;
- long count;
+ int count;
int indent;
int c;
int gotesc; // true when <ESC> just typed
@@ -126,9 +129,34 @@ typedef struct command_line_state {
int ignore_drag_release;
int break_ctrl_c;
expand_T xpc;
- long *b_im_ptr;
+ OptInt *b_im_ptr;
+ buf_T *b_im_ptr_buf; ///< buffer where b_im_ptr is valid
} CommandLineState;
+typedef struct cmdpreview_undo_info {
+ u_header_T *save_b_u_oldhead;
+ u_header_T *save_b_u_newhead;
+ u_header_T *save_b_u_curhead;
+ int save_b_u_numhead;
+ bool save_b_u_synced;
+ int save_b_u_seq_last;
+ int save_b_u_save_nr_last;
+ int save_b_u_seq_cur;
+ time_t save_b_u_time_cur;
+ int save_b_u_save_nr_cur;
+ char *save_b_u_line_ptr;
+ linenr_T save_b_u_line_lnum;
+ colnr_T save_b_u_line_colnr;
+} CpUndoInfo;
+
+typedef struct cmdpreview_buf_info {
+ buf_T *buf;
+ OptInt save_b_p_ul;
+ int save_b_changed;
+ varnumber_T save_changedtick;
+ CpUndoInfo undo_info;
+} CpBufInfo;
+
typedef struct cmdpreview_win_info {
win_T *win;
pos_T save_w_cursor;
@@ -137,17 +165,6 @@ typedef struct cmdpreview_win_info {
int save_w_p_cuc;
} CpWinInfo;
-typedef struct cmdpreview_buf_info {
- buf_T *buf;
- bool save_b_u_synced;
- time_t save_b_u_time_cur;
- long save_b_u_seq_cur;
- u_header_T *save_b_u_newhead;
- long save_b_p_ul;
- int save_b_changed;
- varnumber_T save_changedtick;
-} CpBufInfo;
-
typedef struct cmdpreview_info {
kvec_t(CpWinInfo) win_info;
kvec_t(CpBufInfo) buf_info;
@@ -187,15 +204,14 @@ static int cedit_key = -1; ///< key value of 'cedit' option
#endif
static handle_T cmdpreview_bufnr = 0;
-static long cmdpreview_ns = 0;
-
-static int cmd_hkmap = 0; // Hebrew mapping during command line
+static int cmdpreview_ns = 0;
static void save_viewstate(win_T *wp, viewstate_T *vs)
FUNC_ATTR_NONNULL_ALL
{
vs->vs_curswant = wp->w_curswant;
vs->vs_leftcol = wp->w_leftcol;
+ vs->vs_skipcol = wp->w_skipcol;
vs->vs_topline = wp->w_topline;
vs->vs_topfill = wp->w_topfill;
vs->vs_botline = wp->w_botline;
@@ -207,6 +223,7 @@ static void restore_viewstate(win_T *wp, viewstate_T *vs)
{
wp->w_curswant = vs->vs_curswant;
wp->w_leftcol = vs->vs_leftcol;
+ wp->w_skipcol = vs->vs_skipcol;
wp->w_topline = vs->vs_topline;
wp->w_topfill = vs->vs_topfill;
wp->w_botline = vs->vs_botline;
@@ -232,14 +249,9 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
int *skiplen, int *patlen)
FUNC_ATTR_NONNULL_ALL
{
- char *cmd;
char *p;
bool delim_optional = false;
- int delim;
- char *end;
- char *dummy;
- pos_T save_cursor;
- bool use_last_pat;
+ const char *dummy;
bool retval = false;
magic_T magic = 0;
@@ -273,7 +285,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
cmdmod_T dummy_cmdmod;
parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true);
- cmd = skip_range(ea.cmd, NULL);
+ char *cmd = skip_range(ea.cmd, NULL);
if (vim_strchr("sgvl", (uint8_t)(*cmd)) == NULL) {
goto theend;
}
@@ -298,7 +310,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
if (*p == '!') {
p = skipwhite(p + 1);
}
- while (ASCII_ISALPHA(*(p = skipwhite((char *)p)))) {
+ while (ASCII_ISALPHA(*(p = skipwhite(p)))) {
p++;
}
if (*p == NUL) {
@@ -324,11 +336,11 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
}
p = skipwhite(p);
- delim = (delim_optional && vim_isIDc((uint8_t)(*p))) ? ' ' : *p++;
+ int delim = (delim_optional && vim_isIDc((uint8_t)(*p))) ? ' ' : *p++;
*search_delim = delim;
- end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
+ char *end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
- use_last_pat = end == p && *end == delim;
+ bool use_last_pat = end == p && *end == delim;
if (end == p && !use_last_pat) {
goto theend;
}
@@ -349,7 +361,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
*patlen = (int)(end - p);
// parse the address range
- save_cursor = curwin->w_cursor;
+ pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor = s->search_start;
parse_cmd_address(&ea, &dummy, true);
if (ea.addr_count > 0) {
@@ -375,13 +387,10 @@ theend:
}
// May do 'incsearch' highlighting if desired.
-static void may_do_incsearch_highlighting(int firstc, long count, incsearch_state_T *s)
+static void may_do_incsearch_highlighting(int firstc, int count, incsearch_state_T *s)
{
pos_T end_pos;
- proftime_T tm;
int skiplen, patlen;
- char next_char;
- bool use_last_pat;
int search_delim;
// Parsing range may already set the last search pattern.
@@ -418,9 +427,9 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
int found; // do_search() result
// Use the previous pattern for ":s//".
- next_char = ccline.cmdbuff[skiplen + patlen];
- use_last_pat = patlen == 0 && skiplen > 0
- && ccline.cmdbuff[skiplen - 1] == next_char;
+ char next_char = ccline.cmdbuff[skiplen + patlen];
+ bool use_last_pat = patlen == 0 && skiplen > 0
+ && ccline.cmdbuff[skiplen - 1] == next_char;
// If there is no pattern, don't do anything.
if (patlen == 0 && !use_last_pat) {
@@ -433,7 +442,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
ui_flush();
emsg_off++; // So it doesn't beep if bad expr
// Set the time limit to half a second.
- tm = profile_setlimit(500L);
+ proftime_T tm = profile_setlimit(500);
if (!p_hls) {
search_flags += SEARCH_KEEP;
}
@@ -477,7 +486,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
// first restore the old curwin values, so the screen is
// positioned in the same way as the actual search command
restore_viewstate(curwin, &s->old_viewstate);
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
update_topline(curwin);
if (found != 0) {
@@ -506,6 +515,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
}
validate_cursor();
+
// May redraw the status line to show the cursor position.
if (p_ru && (curwin->w_status_height > 0 || global_stl_height() > 0)) {
curwin->w_redr_status = true;
@@ -588,7 +598,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
curwin->w_cursor = s->save_cursor;
setpcmark();
}
- curwin->w_cursor = s->search_start; // -V519
+ curwin->w_cursor = s->search_start;
}
restore_viewstate(curwin, &s->old_viewstate);
highlight_match = false;
@@ -600,6 +610,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
magic_overruled = s->magic_overruled_save;
validate_cursor(); // needed for TAB
+ status_redraw_all();
redraw_all_later(UPD_SOME_VALID);
if (call_update_screen) {
update_screen();
@@ -641,7 +652,7 @@ static void init_ccline(int firstc, int indent)
/// @param count only used for incremental search
/// @param indent indent for inside conditionals
/// @param clear_ccline clear ccline first
-static uint8_t *command_line_enter(int firstc, long count, int indent, bool clear_ccline)
+static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear_ccline)
{
// can be invoked recursively, identify each level
static int cmdline_level = 0;
@@ -680,11 +691,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
s->break_ctrl_c = true;
}
- // start without Hebrew mapping for a command line
- if (s->firstc == ':' || s->firstc == '=' || s->firstc == '>') {
- cmd_hkmap = 0;
- }
-
init_ccline(s->firstc, s->indent);
ccline.prompt_id = last_prompt_id++;
ccline.level = cmdline_level;
@@ -698,12 +704,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
ExpandInit(&s->xpc);
ccline.xpc = &s->xpc;
- if (curwin->w_p_rl && *curwin->w_p_rlc == 's'
- && (s->firstc == '/' || s->firstc == '?')) {
- cmdmsg_rl = true;
- } else {
- cmdmsg_rl = false;
- }
+ cmdmsg_rl = (curwin->w_p_rl && *curwin->w_p_rlc == 's'
+ && (s->firstc == '/' || s->firstc == '?'));
msg_grid_validate();
@@ -741,7 +743,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
} else {
s->b_im_ptr = &curbuf->b_p_imsearch;
}
-
+ s->b_im_ptr_buf = curbuf;
if (*s->b_im_ptr == B_IMODE_LMAP) {
State |= MODE_LANGMAP;
}
@@ -795,7 +797,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
// Redraw the statusline in case it uses the current mode using the mode()
// function.
- if (!cmd_silent) {
+ if (!cmd_silent && !exmode_active) {
bool found_one = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -844,6 +846,16 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
cmdmsg_rl = false;
+ // We could have reached here without having a chance to clean up wild menu
+ // if certain special keys like <Esc> or <C-\> were used as wildchar. Make
+ // sure to still clean up to avoid memory corruption.
+ if (cmdline_pum_active()) {
+ cmdline_pum_remove();
+ }
+ wildmenu_cleanup(&ccline);
+ s->did_wild_list = false;
+ s->wim_index = 0;
+
ExpandCleanup(&s->xpc);
ccline.xpc = NULL;
@@ -914,6 +926,9 @@ theend:
ui_call_cmdline_hide(ccline.level);
msg_ext_clear_later();
}
+ if (!cmd_silent) {
+ status_redraw_all(); // redraw to show mode change
+ }
cmdline_level--;
@@ -928,6 +943,8 @@ theend:
static int command_line_check(VimState *state)
{
+ CommandLineState *s = (CommandLineState *)state;
+
redir_off = true; // Don't redirect the typed command.
// Repeated, because a ":redir" inside
// completion may switch it on.
@@ -937,6 +954,9 @@ static int command_line_check(VimState *state)
// that occurs while typing a command should
// cause the command not to be executed.
+ // Trigger SafeState if nothing is pending.
+ may_trigger_safestate(s->xpc.xp_numfiles <= 0);
+
cursorcmd(); // set the cursor on the right spot
ui_cursor_shape();
return 1;
@@ -1142,7 +1162,7 @@ static int command_line_execute(VimState *state, int key)
} else if (s->c == K_COMMAND) {
do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
} else {
- map_execute_lua();
+ map_execute_lua(false);
}
// nvim_select_popupmenu_item() can be called from the handling of
@@ -1166,9 +1186,6 @@ static int command_line_execute(VimState *state, int key)
if (KeyTyped) {
s->some_key_typed = true;
- if (cmd_hkmap) {
- s->c = hkmap(s->c);
- }
if (cmdmsg_rl && !KeyStuffed) {
// Invert horizontal movements and operations. Only when
@@ -1225,13 +1242,14 @@ static int command_line_execute(VimState *state, int key)
s->c = wildmenu_translate_key(&ccline, s->c, &s->xpc, s->did_wild_list);
}
- if (cmdline_pum_active() || s->did_wild_list) {
+ int wild_type = 0;
+ const bool key_is_wc = (s->c == p_wc && KeyTyped) || s->c == p_wcm;
+ if ((cmdline_pum_active() || s->did_wild_list) && !key_is_wc) {
// Ctrl-Y: Accept the current selection and close the popup menu.
// Ctrl-E: cancel the cmdline popup menu and return the original text.
if (s->c == Ctrl_E || s->c == Ctrl_Y) {
- const int wild_type = (s->c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY;
+ wild_type = (s->c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY;
(void)nextwild(&s->xpc, wild_type, WILD_NO_BEEP, s->firstc != '@');
- s->c = Ctrl_E;
}
}
@@ -1240,7 +1258,7 @@ static int command_line_execute(VimState *state, int key)
// 'wildcharm' or Ctrl-N or Ctrl-P or Ctrl-A or Ctrl-L).
// If the popup menu is displayed, then PageDown and PageUp keys are
// also used to navigate the menu.
- bool end_wildmenu = (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z
+ bool end_wildmenu = (!key_is_wc && s->c != Ctrl_Z
&& s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A
&& s->c != Ctrl_L);
end_wildmenu = end_wildmenu && (!cmdline_pum_active()
@@ -1307,7 +1325,7 @@ static int command_line_execute(VimState *state, int key)
if (!cmd_silent) {
if (!ui_has(kUICmdline)) {
- cmd_cursor_goto(msg_row, 0);
+ msg_cursor_goto(msg_row, 0);
}
ui_flush();
}
@@ -1344,13 +1362,19 @@ static int command_line_execute(VimState *state, int key)
}
s->do_abbr = true; // default: check for abbreviation
+
+ // If already used to cancel/accept wildmenu, don't process the key further.
+ if (wild_type == WILD_CANCEL || wild_type == WILD_APPLY) {
+ return command_line_not_changed(s);
+ }
+
return command_line_handle_key(s);
}
// May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next
// or previous match.
// Returns FAIL when calling command_line_not_changed.
-static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_state_T *s,
+static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_state_T *s,
bool next_match)
FUNC_ATTR_NONNULL_ALL
{
@@ -1376,7 +1400,6 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
pos_T t;
char *pat;
int search_flags = SEARCH_NOOF;
- char save;
if (search_delim == ccline.cmdbuff[skiplen]) {
pat = last_search_pattern();
@@ -1405,7 +1428,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
search_flags += SEARCH_KEEP;
}
emsg_off++;
- save = pat[patlen];
+ char save = pat[patlen];
pat[patlen] = NUL;
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
@@ -1443,7 +1466,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
set_search_match(&s->match_end);
curwin->w_cursor = s->match_start;
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
update_topline(curwin);
validate_cursor();
highlight_match = true;
@@ -1479,10 +1502,8 @@ static int command_line_erase_chars(CommandLineState *s)
}
if (ccline.cmdpos > 0) {
- char *p;
-
int j = ccline.cmdpos;
- p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j);
+ char *p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j);
if (s->c == Ctrl_W) {
while (p > ccline.cmdbuff && ascii_isspace(*p)) {
@@ -1525,11 +1546,7 @@ static int command_line_erase_chars(CommandLineState *s)
XFREE_CLEAR(ccline.cmdbuff); // no commandline to return
if (!cmd_silent && !ui_has(kUICmdline)) {
- if (cmdmsg_rl) {
- msg_col = Columns;
- } else {
- msg_col = 0;
- }
+ msg_col = 0;
msg_putchar(' '); // delete ':'
}
s->is_state.search_start = s->is_state.save_cursor;
@@ -1543,20 +1560,21 @@ static int command_line_erase_chars(CommandLineState *s)
/// language :lmap mappings and/or Input Method.
static void command_line_toggle_langmap(CommandLineState *s)
{
+ OptInt *b_im_ptr = buf_valid(s->b_im_ptr_buf) ? s->b_im_ptr : NULL;
if (map_to_exists_mode("", MODE_LANGMAP, false)) {
// ":lmap" mappings exists, toggle use of mappings.
State ^= MODE_LANGMAP;
- if (s->b_im_ptr != NULL) {
+ if (b_im_ptr != NULL) {
if (State & MODE_LANGMAP) {
- *s->b_im_ptr = B_IMODE_LMAP;
+ *b_im_ptr = B_IMODE_LMAP;
} else {
- *s->b_im_ptr = B_IMODE_NONE;
+ *b_im_ptr = B_IMODE_NONE;
}
}
}
- if (s->b_im_ptr != NULL) {
- if (s->b_im_ptr == &curbuf->b_p_iminsert) {
+ if (b_im_ptr != NULL) {
+ if (b_im_ptr == &curbuf->b_p_iminsert) {
set_iminsert_global(curbuf);
} else {
set_imsearch_global(curbuf);
@@ -1598,8 +1616,10 @@ static int command_line_insert_reg(CommandLineState *s)
}
}
+ bool literally = false;
if (s->c != ESC) { // use ESC to cancel inserting register
- cmdline_paste(s->c, i == Ctrl_R, false);
+ literally = i == Ctrl_R || is_literal_register(s->c);
+ cmdline_paste(s->c, literally, false);
// When there was a serious error abort getting the
// command line.
@@ -1624,8 +1644,9 @@ static int command_line_insert_reg(CommandLineState *s)
ccline.special_char = NUL;
redrawcmd();
- // The text has been stuffed, the command line didn't change yet.
- return CMDLINE_NOT_CHANGED;
+ // With "literally": the command line has already changed.
+ // Else: the text has been stuffed, but the command line didn't change yet.
+ return literally ? CMDLINE_CHANGED : CMDLINE_NOT_CHANGED;
}
/// Handle the Left and Right mouse clicks in the command-line mode.
@@ -1656,7 +1677,7 @@ static void command_line_left_right_mouse(CommandLineState *s)
static void command_line_next_histidx(CommandLineState *s, bool next_match)
{
int j = (int)strlen(s->lookfor);
- for (;;) {
+ while (true) {
// one step backwards
if (!next_match) {
if (s->hiscnt == get_hislen()) {
@@ -1729,7 +1750,6 @@ static int command_line_browse_history(CommandLineState *s)
if (s->hiscnt != s->save_hiscnt) {
// jumped to other entry
char *p;
- int len = 0;
int old_firstc;
XFREE_CLEAR(ccline.cmdbuff);
@@ -1743,6 +1763,7 @@ static int command_line_browse_history(CommandLineState *s)
if (s->histype == HIST_SEARCH
&& p != s->lookfor
&& (old_firstc = (uint8_t)p[strlen(p) + 1]) != s->firstc) {
+ int len = 0;
// Correct for the separator character used when
// adding the history entry vs the one used now.
// First loop: count length.
@@ -1814,8 +1835,10 @@ static int command_line_handle_key(CommandLineState *s)
case K_INS:
case K_KINS:
ccline.overstrike = !ccline.overstrike;
-
ui_cursor_shape(); // may show different cursor shape
+ may_trigger_modechanged();
+ status_redraw_curbuf();
+ redraw_statuslines();
return command_line_not_changed(s);
case Ctrl_HAT:
@@ -1857,12 +1880,12 @@ static int command_line_handle_key(CommandLineState *s)
case Ctrl_R: // insert register
switch (command_line_insert_reg(s)) {
- case CMDLINE_NOT_CHANGED:
- return command_line_not_changed(s);
case GOTO_NORMAL_MODE:
return 0; // back to cmd mode
- default:
+ case CMDLINE_CHANGED:
return command_line_changed(s);
+ default:
+ return command_line_not_changed(s);
}
case Ctrl_D:
@@ -2015,7 +2038,7 @@ static int command_line_handle_key(CommandLineState *s)
if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) {
break;
}
- return command_line_not_changed(s);
+ return command_line_changed(s);
}
FALLTHROUGH;
@@ -2037,7 +2060,7 @@ static int command_line_handle_key(CommandLineState *s)
if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) {
break;
}
- return command_line_not_changed(s);
+ return command_line_changed(s);
} else {
switch (command_line_browse_history(s)) {
case CMDLINE_CHANGED:
@@ -2098,7 +2121,6 @@ static int command_line_handle_key(CommandLineState *s)
if (!p_ari) {
break;
}
- cmd_hkmap = !cmd_hkmap;
return command_line_not_changed(s);
default:
@@ -2123,7 +2145,7 @@ static int command_line_handle_key(CommandLineState *s)
// put the character in the command line
if (IS_SPECIAL(s->c) || mod_mask != 0) {
- put_on_cmdline((char *)get_special_key_name(s->c, mod_mask), -1, true);
+ put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true);
} else {
int j = utf_char2bytes(s->c, IObuff);
IObuff[j] = NUL; // exclude composing chars
@@ -2183,7 +2205,7 @@ handle_T cmdpreview_get_bufnr(void)
return cmdpreview_bufnr;
}
-long cmdpreview_get_ns(void)
+int cmdpreview_get_ns(void)
{
return cmdpreview_ns;
}
@@ -2278,9 +2300,51 @@ static void cmdpreview_close_win(void)
}
}
+/// Save the undo state of a buffer for command preview.
+static void cmdpreview_save_undo(CpUndoInfo *cp_undoinfo, buf_T *buf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ cp_undoinfo->save_b_u_synced = buf->b_u_synced;
+ cp_undoinfo->save_b_u_oldhead = buf->b_u_oldhead;
+ cp_undoinfo->save_b_u_newhead = buf->b_u_newhead;
+ cp_undoinfo->save_b_u_curhead = buf->b_u_curhead;
+ cp_undoinfo->save_b_u_numhead = buf->b_u_numhead;
+ cp_undoinfo->save_b_u_seq_last = buf->b_u_seq_last;
+ cp_undoinfo->save_b_u_save_nr_last = buf->b_u_save_nr_last;
+ cp_undoinfo->save_b_u_seq_cur = buf->b_u_seq_cur;
+ cp_undoinfo->save_b_u_time_cur = buf->b_u_time_cur;
+ cp_undoinfo->save_b_u_save_nr_cur = buf->b_u_save_nr_cur;
+ cp_undoinfo->save_b_u_line_ptr = buf->b_u_line_ptr;
+ cp_undoinfo->save_b_u_line_lnum = buf->b_u_line_lnum;
+ cp_undoinfo->save_b_u_line_colnr = buf->b_u_line_colnr;
+}
+
+/// Restore the undo state of a buffer for command preview.
+static void cmdpreview_restore_undo(const CpUndoInfo *cp_undoinfo, buf_T *buf)
+{
+ buf->b_u_oldhead = cp_undoinfo->save_b_u_oldhead;
+ buf->b_u_newhead = cp_undoinfo->save_b_u_newhead;
+ buf->b_u_curhead = cp_undoinfo->save_b_u_curhead;
+ buf->b_u_numhead = cp_undoinfo->save_b_u_numhead;
+ buf->b_u_seq_last = cp_undoinfo->save_b_u_seq_last;
+ buf->b_u_save_nr_last = cp_undoinfo->save_b_u_save_nr_last;
+ buf->b_u_seq_cur = cp_undoinfo->save_b_u_seq_cur;
+ buf->b_u_time_cur = cp_undoinfo->save_b_u_time_cur;
+ buf->b_u_save_nr_cur = cp_undoinfo->save_b_u_save_nr_cur;
+ buf->b_u_line_ptr = cp_undoinfo->save_b_u_line_ptr;
+ buf->b_u_line_lnum = cp_undoinfo->save_b_u_line_lnum;
+ buf->b_u_line_colnr = cp_undoinfo->save_b_u_line_colnr;
+ if (buf->b_u_curhead == NULL) {
+ buf->b_u_synced = cp_undoinfo->save_b_u_synced;
+ }
+}
+
/// Save current state and prepare windows and buffers for command preview.
static void cmdpreview_prepare(CpInfo *cpinfo)
+ FUNC_ATTR_NONNULL_ALL
{
+ Set(ptr_t) saved_bufs = SET_INIT;
+
kv_init(cpinfo->buf_info);
kv_init(cpinfo->win_info);
@@ -2292,20 +2356,19 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
continue;
}
- CpBufInfo cp_bufinfo;
- cp_bufinfo.buf = buf;
+ if (!set_has(ptr_t, &saved_bufs, buf)) {
+ CpBufInfo cp_bufinfo;
+ cp_bufinfo.buf = buf;
+ cp_bufinfo.save_b_p_ul = buf->b_p_ul;
+ cp_bufinfo.save_b_changed = buf->b_changed;
+ cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
+ cmdpreview_save_undo(&cp_bufinfo.undo_info, buf);
+ kv_push(cpinfo->buf_info, cp_bufinfo);
+ set_put(ptr_t, &saved_bufs, buf);
- cp_bufinfo.save_b_u_synced = buf->b_u_synced;
- cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur;
- cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur;
- cp_bufinfo.save_b_u_newhead = buf->b_u_newhead;
- cp_bufinfo.save_b_p_ul = buf->b_p_ul;
- cp_bufinfo.save_b_changed = buf->b_changed;
- cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
-
- kv_push(cpinfo->buf_info, cp_bufinfo);
-
- buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
+ u_clearall(buf);
+ buf->b_p_ul = INT_MAX; // Make sure we can undo all changes
+ }
CpWinInfo cp_wininfo;
cp_wininfo.win = win;
@@ -2324,6 +2387,8 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
win->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
}
+ set_destroy(ptr_t, &saved_bufs);
+
cpinfo->save_hls = p_hls;
cpinfo->save_cmdmod = cmdmod;
win_size_save(&cpinfo->save_view);
@@ -2337,8 +2402,9 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
u_sync(true);
}
-// Restore the state of buffers and windows before command preview.
+/// Restore the state of buffers and windows for command preview.
static void cmdpreview_restore_state(CpInfo *cpinfo)
+ FUNC_ATTR_NONNULL_ALL
{
for (size_t i = 0; i < cpinfo->buf_info.size; i++) {
CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i];
@@ -2346,41 +2412,40 @@ static void cmdpreview_restore_state(CpInfo *cpinfo)
buf->b_changed = cp_bufinfo.save_b_changed;
- if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) {
+ // Clear preview highlights.
+ extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
+
+ if (buf->b_u_seq_cur != cp_bufinfo.undo_info.save_b_u_seq_cur) {
int count = 0;
// Calculate how many undo steps are necessary to restore earlier state.
for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead;
- uhp != NULL && uhp->uh_seq > cp_bufinfo.save_b_u_seq_cur;
+ uhp != NULL;
uhp = uhp->uh_next.ptr, ++count) {}
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
+ // Ensure all the entries will be undone
+ if (curbuf->b_u_synced == false) {
+ u_sync(true);
+ }
// Undo invisibly. This also moves the cursor!
if (!u_undo_and_forget(count, false)) {
abort();
}
aucmd_restbuf(&aco);
-
- // Restore newhead. It is meaningless when curhead is valid, but we must
- // restore it so that undotree() is identical before/after the preview.
- buf->b_u_newhead = cp_bufinfo.save_b_u_newhead;
- buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur;
}
- if (buf->b_u_curhead == NULL) {
- buf->b_u_synced = cp_bufinfo.save_b_u_synced;
- }
+ u_blockfree(buf);
+ cmdpreview_restore_undo(&cp_bufinfo.undo_info, buf);
if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) {
buf_set_changedtick(buf, cp_bufinfo.save_changedtick);
}
buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels'
-
- // Clear preview highlights.
- extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
}
+
for (size_t i = 0; i < cpinfo->win_info.size; i++) {
CpWinInfo cp_wininfo = cpinfo->win_info.items[i];
win_T *win = cp_wininfo.win;
@@ -2429,7 +2494,7 @@ static bool cmdpreview_may_show(CommandLineState *s)
// Copy the command line so we can modify it.
int cmdpreview_type = 0;
char *cmdline = xstrdup(ccline.cmdbuff);
- char *errormsg = NULL;
+ const char *errormsg = NULL;
emsg_off++; // Block errors when parsing the command line, and don't update v:errmsg
if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
emsg_off--;
@@ -2452,8 +2517,8 @@ static bool cmdpreview_may_show(CommandLineState *s)
CpInfo cpinfo;
bool icm_split = *p_icm == 's'; // inccommand=split
- buf_T *cmdpreview_buf;
- win_T *cmdpreview_win;
+ buf_T *cmdpreview_buf = NULL;
+ win_T *cmdpreview_win = NULL;
emsg_silent++; // Block error reporting as the command may be incomplete,
// but still update v:errmsg
@@ -2579,7 +2644,7 @@ static int command_line_changed(CommandLineState *s)
}
}
- if (cmdmsg_rl || (p_arshape && !p_tbidi)) {
+ if (p_arshape && !p_tbidi) {
// Always redraw the whole command line to fix shaping and
// right-left typing. Not efficient, but it works.
// Do it only when there are no characters left to read
@@ -2601,7 +2666,7 @@ static void abandon_cmdline(void)
if (msg_scrolled == 0) {
compute_cmdrow();
}
- msg("");
+ msg("", 0);
redraw_cmdline = true;
}
@@ -2625,7 +2690,7 @@ static void abandon_cmdline(void)
///
/// @param count only used for incremental search
/// @param indent indent for inside conditionals
-char *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED)
+char *getcmdline(int firstc, int count, int indent, bool do_concat FUNC_ATTR_UNUSED)
{
return (char *)command_line_enter(firstc, count, indent, true);
}
@@ -2670,7 +2735,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at
int msg_silent_saved = msg_silent;
msg_silent = 0;
- char *const ret = (char *)command_line_enter(firstc, 1L, 0, false);
+ char *const ret = (char *)command_line_enter(firstc, 1, 0, false);
if (did_save_ccline) {
restore_cmdline(&save_ccline);
@@ -2690,7 +2755,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at
/// Read the 'wildmode' option, fill wim_flags[].
int check_opt_wim(void)
{
- char_u new_wim_flags[4];
+ uint8_t new_wim_flags[4];
int i;
int idx = 0;
@@ -2699,6 +2764,7 @@ int check_opt_wim(void)
}
for (char *p = p_wim; *p; p++) {
+ // Note: Keep this in sync with p_wim_values.
for (i = 0; ASCII_ISALPHA(p[i]); i++) {}
if (p[i] != NUL && p[i] != ',' && p[i] != ':') {
return FAIL;
@@ -2746,6 +2812,9 @@ bool text_locked(void)
if (cmdwin_type != 0) {
return true;
}
+ if (expr_map_locked()) {
+ return true;
+ }
return textlock != 0;
}
@@ -2756,7 +2825,7 @@ void text_locked_msg(void)
emsg(_(get_text_locked_msg()));
}
-char *get_text_locked_msg(void)
+const char *get_text_locked_msg(void)
{
if (cmdwin_type != 0) {
return e_cmdwin;
@@ -2866,7 +2935,7 @@ char *getexline(int c, void *cookie, int indent, bool do_concat)
(void)vgetc();
}
- return getcmdline(c, 1L, indent, do_concat);
+ return getcmdline(c, 1, indent, do_concat);
}
bool cmdline_overstrike(void)
@@ -2926,16 +2995,6 @@ void realloc_cmdbuff(int len)
}
}
-static char *arshape_buf = NULL;
-
-#if defined(EXITFREE)
-void free_arshape_buf(void)
-{
- xfree(arshape_buf);
-}
-
-#endif
-
enum { MAX_CB_ERRORS = 1, };
/// Color expression cmdline using built-in expressions parser
@@ -2950,7 +3009,7 @@ static void color_expr_cmdline(const CmdlineInfo *const colored_ccline,
{
ParserLine parser_lines[] = {
{
- .data = (const char *)colored_ccline->cmdbuff,
+ .data = colored_ccline->cmdbuff,
.size = strlen(colored_ccline->cmdbuff),
.allocated = false,
},
@@ -3054,7 +3113,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
bool can_free_cb = false;
TryState tstate;
Error err = ERROR_INIT;
- const char *err_errmsg = (const char *)e_intern2;
+ const char *err_errmsg = e_intern2;
bool dgc_ret = true;
bool tl_ret = true;
@@ -3087,8 +3146,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
}
if (colored_ccline->cmdbuff[colored_ccline->cmdlen] != NUL) {
arg_allocated = true;
- arg.vval.v_string = xmemdupz((const char *)colored_ccline->cmdbuff,
- (size_t)colored_ccline->cmdlen);
+ arg.vval.v_string = xmemdupz(colored_ccline->cmdbuff, (size_t)colored_ccline->cmdlen);
}
// msg_start() called by e.g. :echo may shift command-line to the first column
// even though msg_silent is here. Two ways to workaround this problem without
@@ -3207,8 +3265,7 @@ color_cmdline_end:
if (arg_allocated) {
ccline_colors->cmdbuff = arg.vval.v_string;
} else {
- ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff,
- (size_t)colored_ccline->cmdlen);
+ ccline_colors->cmdbuff = xmemdupz(colored_ccline->cmdbuff, (size_t)colored_ccline->cmdlen);
}
tv_clear(&tv);
return ret;
@@ -3247,98 +3304,7 @@ static void draw_cmdline(int start, int len)
msg_putchar('*');
i += utfc_ptr2len(ccline.cmdbuff + start + i) - 1;
}
- } else if (p_arshape && !p_tbidi && len > 0) {
- bool do_arabicshape = false;
- int mb_l;
- for (int i = start; i < start + len; i += mb_l) {
- char *p = ccline.cmdbuff + i;
- int u8cc[MAX_MCO];
- int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
- mb_l = utfc_ptr2len_len(p, start + len - i);
- if (ARABIC_CHAR(u8c)) {
- do_arabicshape = true;
- break;
- }
- }
- if (!do_arabicshape) {
- goto draw_cmdline_no_arabicshape;
- }
-
- static size_t buflen = 0;
- assert(len >= 0);
-
- // Do arabic shaping into a temporary buffer. This is very
- // inefficient!
- if ((size_t)len * 2 + 2 > buflen) {
- // Re-allocate the buffer. We keep it around to avoid a lot of
- // alloc()/free() calls.
- xfree(arshape_buf);
- buflen = (size_t)len * 2 + 2;
- arshape_buf = xmalloc(buflen);
- }
-
- int newlen = 0;
- if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) {
- // Prepend a space to draw the leading composing char on.
- arshape_buf[0] = ' ';
- newlen = 1;
- }
-
- int prev_c = 0;
- int prev_c1 = 0;
- for (int i = start; i < start + len; i += mb_l) {
- char *p = ccline.cmdbuff + i;
- int u8cc[MAX_MCO];
- int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
- mb_l = utfc_ptr2len_len(p, start + len - i);
- if (ARABIC_CHAR(u8c)) {
- int pc;
- int pc1 = 0;
- int nc = 0;
- // Do Arabic shaping.
- if (cmdmsg_rl) {
- // Displaying from right to left.
- pc = prev_c;
- pc1 = prev_c1;
- prev_c1 = u8cc[0];
- if (i + mb_l >= start + len) {
- nc = NUL;
- } else {
- nc = utf_ptr2char(p + mb_l);
- }
- } else {
- // Displaying from left to right.
- if (i + mb_l >= start + len) {
- pc = NUL;
- } else {
- int pcc[MAX_MCO];
-
- pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l);
- pc1 = pcc[0];
- }
- nc = prev_c;
- }
- prev_c = u8c;
-
- u8c = arabic_shape(u8c, NULL, &u8cc[0], pc, pc1, nc);
-
- newlen += utf_char2bytes(u8c, arshape_buf + newlen);
- if (u8cc[0] != 0) {
- newlen += utf_char2bytes(u8cc[0], arshape_buf + newlen);
- if (u8cc[1] != 0) {
- newlen += utf_char2bytes(u8cc[1], arshape_buf + newlen);
- }
- }
- } else {
- prev_c = u8c;
- memmove(arshape_buf + newlen, p, (size_t)mb_l);
- newlen += mb_l;
- }
- }
-
- msg_outtrans_len(arshape_buf, newlen);
} else {
-draw_cmdline_no_arabicshape:
if (kv_size(ccline.last_colors.colors)) {
for (size_t i = 0; i < kv_size(ccline.last_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(ccline.last_colors.colors, i);
@@ -3346,12 +3312,10 @@ draw_cmdline_no_arabicshape:
continue;
}
const int chunk_start = MAX(chunk.start, start);
- msg_outtrans_len_attr(ccline.cmdbuff + chunk_start,
- chunk.end - chunk_start,
- chunk.attr);
+ msg_outtrans_len(ccline.cmdbuff + chunk_start, chunk.end - chunk_start, chunk.attr);
}
} else {
- msg_outtrans_len(ccline.cmdbuff + start, len);
+ msg_outtrans_len(ccline.cmdbuff + start, len, 0);
}
}
}
@@ -3380,14 +3344,14 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
ADD_C(item, INTEGER_OBJ(chunk.attr));
assert(chunk.end >= chunk.start);
- ADD_C(item, STRING_OBJ(cbuf_as_string((char *)line->cmdbuff + chunk.start,
+ ADD_C(item, STRING_OBJ(cbuf_as_string(line->cmdbuff + chunk.start,
(size_t)(chunk.end - chunk.start))));
ADD_C(content, ARRAY_OBJ(item));
}
} else {
Array item = arena_array(&arena, 2);
ADD_C(item, INTEGER_OBJ(0));
- ADD_C(item, STRING_OBJ(cstr_as_string((char *)(line->cmdbuff))));
+ ADD_C(item, CSTR_AS_OBJ(line->cmdbuff));
content = arena_array(&arena, 1);
ADD_C(content, ARRAY_OBJ(item));
}
@@ -3410,11 +3374,11 @@ void ui_ext_cmdline_block_append(size_t indent, const char *line)
{
char *buf = xmallocz(indent + strlen(line));
memset(buf, ' ', indent);
- memcpy(buf + indent, line, strlen(line)); // -V575
+ memcpy(buf + indent, line, strlen(line));
Array item = ARRAY_DICT_INIT;
ADD(item, INTEGER_OBJ(0));
- ADD(item, STRING_OBJ(cstr_as_string(buf)));
+ ADD(item, CSTR_AS_OBJ(buf));
Array content = ARRAY_DICT_INIT;
ADD(content, ARRAY_OBJ(item));
ADD(cmdline_block, ARRAY_OBJ(content));
@@ -3530,7 +3494,7 @@ void unputcmdline(void)
// part will be redrawn, otherwise it will not. If this function is called
// twice in a row, then 'redraw' should be false and redrawcmd() should be
// called afterwards.
-void put_on_cmdline(char *str, int len, int redraw)
+void put_on_cmdline(const char *str, int len, int redraw)
{
int i;
int m;
@@ -3676,7 +3640,6 @@ static void restore_cmdline(CmdlineInfo *ccp)
static bool cmdline_paste(int regname, bool literally, bool remcr)
{
char *arg;
- char *p;
bool allocated;
// check for valid regname; also accept special characters for CTRL-R in
@@ -3708,7 +3671,7 @@ static bool cmdline_paste(int regname, bool literally, bool remcr)
// When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate
// part of the word.
- p = arg;
+ char *p = arg;
if (p_is && regname == Ctrl_W) {
char *w;
int len;
@@ -3741,19 +3704,17 @@ static bool cmdline_paste(int regname, bool literally, bool remcr)
// When "literally" is true, insert literally.
// When "literally" is false, insert as typed, but don't leave the command
// line.
-void cmdline_paste_str(char *s, int literally)
+void cmdline_paste_str(const char *s, int literally)
{
- int c, cv;
-
if (literally) {
put_on_cmdline(s, -1, true);
} else {
while (*s != NUL) {
- cv = (uint8_t)(*s);
+ int cv = (uint8_t)(*s);
if (cv == Ctrl_V && s[1]) {
s++;
}
- c = mb_cptr2char_adv((const char **)&s);
+ int c = mb_cptr2char_adv(&s);
if (cv == Ctrl_V || c == ESC || c == Ctrl_C
|| c == CAR || c == NL || c == Ctrl_L
|| (c == Ctrl_BSL && *s == Ctrl_N)) {
@@ -3781,8 +3742,6 @@ void redrawcmdline(void)
static void redrawcmdprompt(void)
{
- int i;
-
if (cmd_silent) {
return;
}
@@ -3801,7 +3760,7 @@ static void redrawcmdprompt(void)
ccline.cmdindent--;
}
} else {
- for (i = ccline.cmdindent; i > 0; i--) {
+ for (int i = ccline.cmdindent; i > 0; i--) {
msg_putchar(' ');
}
}
@@ -3821,7 +3780,7 @@ void redrawcmd(void)
// when 'incsearch' is set there may be no command line while redrawing
if (ccline.cmdbuff == NULL) {
- cmd_cursor_goto(cmdline_row, 0);
+ msg_cursor_goto(cmdline_row, 0);
msg_clr_eos();
return;
}
@@ -3884,28 +3843,13 @@ void cursorcmd(void)
return;
}
- if (cmdmsg_rl) {
- msg_row = cmdline_row + (ccline.cmdspos / (Columns - 1));
- msg_col = Columns - (ccline.cmdspos % (Columns - 1)) - 1;
- if (msg_row <= 0) {
- msg_row = Rows - 1;
- }
- } else {
- msg_row = cmdline_row + (ccline.cmdspos / Columns);
- msg_col = ccline.cmdspos % Columns;
- if (msg_row >= Rows) {
- msg_row = Rows - 1;
- }
+ msg_row = cmdline_row + (ccline.cmdspos / Columns);
+ msg_col = ccline.cmdspos % Columns;
+ if (msg_row >= Rows) {
+ msg_row = Rows - 1;
}
- cmd_cursor_goto(msg_row, msg_col);
-}
-
-static void cmd_cursor_goto(int row, int col)
-{
- ScreenGrid *grid = &msg_grid_adj;
- grid_adjust(&grid, &row, &col);
- ui_grid_cursor_goto(grid->handle, row, col);
+ msg_cursor_goto(msg_row, msg_col);
}
void gotocmdline(bool clr)
@@ -3914,15 +3858,11 @@ void gotocmdline(bool clr)
return;
}
msg_start();
- if (cmdmsg_rl) {
- msg_col = Columns - 1;
- } else {
- msg_col = 0; // always start in column 0
- }
+ msg_col = 0; // always start in column 0
if (clr) { // clear the bottom line(s)
msg_clr_eos(); // will reset clear_cmdline
}
- cmd_cursor_goto(cmdline_row, 0);
+ msg_cursor_goto(cmdline_row, 0);
}
// Check the word in front of the cursor for an abbreviation.
@@ -4022,11 +3962,9 @@ void escape_fname(char **pp)
/// If 'orig_pat' starts with "~/", replace the home directory with "~".
void tilde_replace(char *orig_pat, int num_files, char **files)
{
- char *p;
-
if (orig_pat[0] == '~' && vim_ispathsep(orig_pat[1])) {
for (int i = 0; i < num_files; i++) {
- p = home_replace_save(NULL, files[i]);
+ char *p = home_replace_save(NULL, files[i]);
xfree(files[i]);
files[i] = p;
}
@@ -4106,12 +4044,24 @@ static char *get_cmdline_completion(void)
}
set_expand_context(p->xpc);
+ if (p->xpc->xp_context == EXPAND_UNSUCCESSFUL) {
+ return NULL;
+ }
+
char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context);
- if (cmd_compl != NULL) {
- return xstrdup(cmd_compl);
+ if (cmd_compl == NULL) {
+ return NULL;
}
- return NULL;
+ if (p->xpc->xp_context == EXPAND_USER_LIST
+ || p->xpc->xp_context == EXPAND_USER_DEFINED) {
+ size_t buflen = strlen(cmd_compl) + strlen(p->xpc->xp_arg) + 2;
+ char *buffer = xmalloc(buflen);
+ snprintf(buffer, buflen, "%s,%s", cmd_compl, p->xpc->xp_arg);
+ return buffer;
+ }
+
+ return xstrdup(cmd_compl);
}
/// "getcmdcompltype()" function
@@ -4219,7 +4169,8 @@ void f_setcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
- rettv->vval.v_number = set_cmdline_str(argvars[0].vval.v_string, pos);
+ // Use tv_get_string() to handle a NULL string like an empty string.
+ rettv->vval.v_number = set_cmdline_str(tv_get_string(&argvars[0]), pos);
}
/// "setcmdpos()" function
@@ -4254,18 +4205,28 @@ int get_list_range(char **str, int *num1, int *num2)
*str = skipwhite((*str));
if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range
- vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
+ vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false, NULL);
*str += len;
+ // overflow
+ if (num > INT_MAX) {
+ return FAIL;
+ }
+
*num1 = (int)num;
first = true;
}
*str = skipwhite((*str));
if (**str == ',') { // parse "to" part of range
*str = skipwhite((*str) + 1);
- vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
+ vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false, NULL);
if (len > 0) {
- *num2 = (int)num;
*str = skipwhite((*str) + len);
+ // overflow
+ if (num > INT_MAX) {
+ return FAIL;
+ }
+
+ *num2 = (int)num;
} else if (!first) { // no number given at all
return FAIL;
}
@@ -4282,14 +4243,12 @@ void cmdline_init(void)
/// Check value of 'cedit' and set cedit_key.
/// Returns NULL if value is OK, error message otherwise.
-char *check_cedit(void)
+const char *did_set_cedit(optset_T *args)
{
- int n;
-
if (*p_cedit == NUL) {
cedit_key = -1;
} else {
- n = string_to_key(p_cedit);
+ int n = string_to_key(p_cedit);
if (vim_isprintc(n)) {
return e_invarg;
}
@@ -4309,9 +4268,7 @@ static int open_cmdwin(void)
bufref_T old_curbuf;
bufref_T bufref;
win_T *old_curwin = curwin;
- win_T *wp;
int i;
- linenr_T lnum;
garray_T winsizes;
char typestr[2];
int save_restart_edit = restart_edit;
@@ -4351,6 +4308,7 @@ static int open_cmdwin(void)
// Set "cmdwin_type" before any autocommands may mess things up.
cmdwin_type = get_cmdline_type();
cmdwin_level = ccline.level;
+ cmdwin_old_curwin = old_curwin;
// Create empty command-line buffer.
if (buf_open_scratch(0, _("[Command Line]")) == FAIL) {
@@ -4358,10 +4316,11 @@ static int open_cmdwin(void)
win_close(curwin, true, false);
ga_clear(&winsizes);
cmdwin_type = 0;
+ cmdwin_old_curwin = NULL;
return Ctrl_C;
}
// Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer.
- set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL);
curbuf->b_p_ma = true;
curwin->w_p_fen = false;
curwin->w_p_rl = cmdmsg_rl;
@@ -4379,7 +4338,7 @@ static int open_cmdwin(void)
add_map("<Tab>", "<C-X><C-V>", MODE_INSERT, true);
add_map("<Tab>", "a<C-X><C-V>", MODE_NORMAL, true);
}
- set_option_value_give_err("ft", 0L, "vim", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("vim"), OPT_LOCAL);
}
curbuf->b_ro_locked--;
@@ -4392,13 +4351,13 @@ static int open_cmdwin(void)
if (get_hislen() > 0 && histtype != HIST_INVALID) {
i = *get_hisidx(histtype);
if (i >= 0) {
- lnum = 0;
+ linenr_T lnum = 0;
do {
if (++i == get_hislen()) {
i = 0;
}
if (get_histentry(histtype)[i].hisstr != NULL) {
- ml_append(lnum++, get_histentry(histtype)[i].hisstr, (colnr_T)0, false);
+ ml_append(lnum++, get_histentry(histtype)[i].hisstr, 0, false);
}
} while (i != *get_hisidx(histtype));
}
@@ -4410,7 +4369,7 @@ static int open_cmdwin(void)
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
curwin->w_cursor.col = ccline.cmdpos;
changed_line_abv_curs();
- invalidate_botline();
+ invalidate_botline(curwin);
if (ui_has(kUICmdline)) {
ccline.redraw_state = kCmdRedrawNone;
ui_call_cmdline_hide(ccline.level);
@@ -4454,6 +4413,7 @@ static int open_cmdwin(void)
cmdwin_type = 0;
cmdwin_level = 0;
+ cmdwin_old_curwin = NULL;
exmode_active = save_exmode;
@@ -4463,6 +4423,7 @@ static int open_cmdwin(void)
cmdwin_result = Ctrl_C;
emsg(_("E199: Active window or buffer deleted"));
} else {
+ win_T *wp;
// autocmds may abort script processing
if (aborting() && cmdwin_result != K_IGNORE) {
cmdwin_result = Ctrl_C;
@@ -4500,7 +4461,8 @@ static int open_cmdwin(void)
ccline.cmdlen = (int)strlen(ccline.cmdbuff);
ccline.cmdbufflen = ccline.cmdlen + 1;
ccline.cmdpos = curwin->w_cursor.col;
- if (ccline.cmdpos > ccline.cmdlen) {
+ // If the cursor is on the last character, it probably should be after it.
+ if (ccline.cmdpos == ccline.cmdlen - 1 || ccline.cmdpos > ccline.cmdlen) {
ccline.cmdpos = ccline.cmdlen;
}
if (cmdwin_result == K_IGNORE) {
@@ -4569,39 +4531,37 @@ bool is_in_cmdwin(void)
char *script_get(exarg_T *const eap, size_t *const lenp)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
- const char *const cmd = (const char *)eap->arg;
+ char *cmd = eap->arg;
if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) {
*lenp = strlen(eap->arg);
return eap->skip ? NULL : xmemdupz(eap->arg, *lenp);
}
+ cmd += 2;
garray_T ga = { .ga_data = NULL, .ga_len = 0 };
+
+ list_T *const l = heredoc_get(eap, cmd, true);
+ if (l == NULL) {
+ return NULL;
+ }
+
if (!eap->skip) {
ga_init(&ga, 1, 0x400);
}
- const char *const end_pattern = (cmd[2] != NUL ? (const char *)skipwhite(cmd + 2) : ".");
- for (;;) {
- char *const theline = eap->getline(eap->cstack->cs_looplevel > 0 ? -1 : NUL, eap->cookie, 0,
- true);
-
- if (theline == NULL || strcmp(end_pattern, theline) == 0) {
- xfree(theline);
- break;
- }
-
+ TV_LIST_ITER_CONST(l, li, {
if (!eap->skip) {
- ga_concat(&ga, theline);
+ ga_concat(&ga, tv_get_string(TV_LIST_ITEM_TV(li)));
ga_append(&ga, '\n');
}
- xfree(theline);
- }
+ });
*lenp = (size_t)ga.ga_len; // Set length without trailing NUL.
if (!eap->skip) {
ga_append(&ga, NUL);
}
+ tv_list_free(l);
return (char *)ga.ga_data;
}
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 61ac4b69c5..93bdd2299f 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -1,15 +1,13 @@
-#ifndef NVIM_EX_GETLN_H
-#define NVIM_EX_GETLN_H
+#pragma once
#include <stdbool.h>
#include "klib/kvec.h"
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
-
-struct cmdline_info;
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
/// Command-line colors: one chunk
///
@@ -83,4 +81,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.h.generated.h"
#endif
-#endif // NVIM_EX_GETLN_H
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 3de5e1db52..71c01922bc 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -1,21 +1,16 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Functions for creating a session file, i.e. implementing:
// :mkexrc
// :mkvimrc
// :mkview
// :mksession
-#include <assert.h>
#include <inttypes.h>
-#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds_defs.h"
@@ -25,20 +20,22 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
-#include "nvim/mark_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -65,11 +62,9 @@ static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces)
static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
{
- int n = 0;
- win_T *wp;
-
if (restore_size && (ssop_flags & SSOP_WINSIZE)) {
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ int n = 0;
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (!ses_do_win(wp)) {
continue;
}
@@ -109,7 +104,6 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
/// @return FAIL when writing the commands to "fd" fails.
static int ses_win_rec(FILE *fd, frame_T *fr)
{
- frame_T *frc;
int count = 0;
if (fr->fr_layout == FR_LEAF) {
@@ -118,7 +112,7 @@ static int ses_win_rec(FILE *fd, frame_T *fr)
// Find first frame that's not skipped and then create a window for
// each following one (first frame is already there).
- frc = ses_skipframe(fr->fr_child);
+ frame_T *frc = ses_skipframe(fr->fr_child);
if (frc != NULL) {
while ((frc = ses_skipframe(frc->fr_next)) != NULL) {
// Make window as big as possible so that we have lots of room
@@ -218,14 +212,13 @@ static int ses_do_win(win_T *wp)
static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigned *flagp)
{
char *buf = NULL;
- char *s;
if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) {
return FAIL;
}
for (int i = 0; i < gap->ga_len; i++) {
// NULL file names are skipped (only happens when out of memory).
- s = alist_name(&((aentry_T *)gap->ga_data)[i]);
+ char *s = alist_name(&((aentry_T *)gap->ga_data)[i]);
if (s != NULL) {
if (fullname) {
buf = xmalloc(MAXPATHL);
@@ -321,18 +314,14 @@ static int ses_put_fname(FILE *fd, char *name, unsigned *flagp)
/// @param current_arg_idx current argument index of the window, use -1 if unknown
static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int current_arg_idx)
{
- win_T *save_curwin;
int f;
- int do_cursor;
int did_next = false;
// Always restore cursor position for ":mksession". For ":mkview" only
// when 'viewoptions' contains "cursor".
- do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
+ int do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
- //
// Local argument list.
- //
if (wp->w_alist == &global_alist) {
PUTLINE_FAIL("argglobal");
} else {
@@ -425,22 +414,18 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
}
}
- //
// Local mappings and abbreviations.
- //
if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
&& makemap(fd, wp->w_buffer) == FAIL) {
return FAIL;
}
- //
// Local options. Need to go to the window temporarily.
// Store only local values when using ":mkview" and when ":mksession" is
// used and 'sessionoptions' doesn't include "nvim/options".
// Some folding options are always stored when "folds" is included,
// otherwise the folds would not be restored correctly.
- //
- save_curwin = curwin;
+ win_T *save_curwin = curwin;
curwin = wp;
curbuf = curwin->w_buffer;
if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) {
@@ -457,9 +442,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
return FAIL;
}
- //
// Save Folds when 'buftype' is empty and for help files.
- //
if ((*flagp & SSOP_FOLDS)
&& wp->w_buffer->b_ffname != NULL
&& (bt_normal(wp->w_buffer)
@@ -469,9 +452,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
}
}
- //
// Set the cursor after creating folds, since that moves the cursor.
- //
if (do_cursor) {
// Restore the cursor line in the file and relatively in the
// window. Don't use "G", it changes the jumplist.
@@ -522,10 +503,8 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
}
}
- //
// Local directory, if the current flag is not view options or the "curdir"
// option is included.
- //
if (wp->w_localdir != NULL
&& (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) {
if (fputs("lcd ", fd) < 0
@@ -551,12 +530,8 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
static int makeopens(FILE *fd, char *dirnow)
{
int only_save_windows = true;
- int nr;
int restore_size = true;
- win_T *wp;
- char *sname;
win_T *edited_win = NULL;
- int tabnr;
win_T *tab_firstwin;
frame_T *tab_topframe;
int cur_arg_idx = 0;
@@ -581,13 +556,11 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
- //
// Now a :cd command to the session directory or the current directory
- //
if (ssop_flags & SSOP_SESDIR) {
PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')");
} else if (ssop_flags & SSOP_CURDIR) {
- sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
+ char *sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
char *fname_esc = ses_escape_fname(sname, &ssop_flags);
if (fprintf(fd, "cd %s\n", fname_esc) < 0) {
xfree(fname_esc);
@@ -633,7 +606,7 @@ static int makeopens(FILE *fd, char *dirnow)
&& buf->b_p_bl) {
if (fprintf(fd, "badd +%" PRId64 " ",
buf->b_wininfo == NULL
- ? (int64_t)1L
+ ? 1
: (int64_t)buf->b_wininfo->wi_mark.mark.lnum) < 0
|| ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
return FAIL;
@@ -665,16 +638,10 @@ static int makeopens(FILE *fd, char *dirnow)
restore_stal = true;
}
- //
- // For each tab:
- // - Put windows for each tab, when "tabpages" is in 'sessionoptions'.
- // - Don't use goto_tabpage(), it may change CWD and trigger autocommands.
- //
- tab_firstwin = firstwin; // First window in tab page "tabnr".
- tab_topframe = topframe;
if ((ssop_flags & SSOP_TABPAGES)) {
- // Similar to ses_win_rec() below, populate the tab pages first so
- // later local options won't be copied to the new tabs.
+ // "tabpages" is in 'sessionoptions': Similar to ses_win_rec() below,
+ // populate the tab pages first so later local options won't be copied
+ // to the new tabs.
FOR_ALL_TABS(tp) {
// Use `bufhidden=wipe` to remove empty "placeholder" buffers once
// they are not needed. This prevents creating extra buffers (see
@@ -688,15 +655,17 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
}
- for (tabnr = 1;; tabnr++) {
- tabpage_T *tp = find_tabpage(tabnr);
- if (tp == NULL) {
- break; // done all tab pages
- }
+ // Assume "tabpages" is in 'sessionoptions'. If not then we only do
+ // "curtab" and bail out of the loop.
+ FOR_ALL_TABS(tp) {
bool need_tabnext = false;
int cnr = 1;
+ // May repeat putting Windows for each tab, when "tabpages" is in
+ // 'sessionoptions'.
+ // Don't use goto_tabpage(), it may change directory and trigger
+ // autocommands.
if ((ssop_flags & SSOP_TABPAGES)) {
if (tp == curtab) {
tab_firstwin = firstwin;
@@ -705,17 +674,19 @@ static int makeopens(FILE *fd, char *dirnow)
tab_firstwin = tp->tp_firstwin;
tab_topframe = tp->tp_topframe;
}
- if (tabnr > 1) {
+ if (tp != first_tabpage) {
need_tabnext = true;
}
+ } else {
+ tp = curtab;
+ tab_firstwin = firstwin;
+ tab_topframe = topframe;
}
- //
// Before creating the window layout, try loading one file. If this
// is aborted we don't end up with a number of useless windows.
// This may have side effects! (e.g., compressed or network file).
- //
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (ses_do_win(wp)
&& wp->w_buffer->b_ffname != NULL
&& !bt_help(wp->w_buffer)
@@ -753,12 +724,10 @@ static int makeopens(FILE *fd, char *dirnow)
PUTLINE_FAIL("let &splitright = s:save_splitright");
}
- //
// Check if window sizes can be restored (no windows omitted).
// Remember the window number of the current window after restoring.
- //
- nr = 0;
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ int nr = 0;
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (ses_do_win(wp)) {
nr++;
} else {
@@ -794,10 +763,20 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
- //
+ // Restore the tab-local working directory if specified
+ // Do this before the windows, so that the window-local directory can
+ // override the tab-local directory.
+ if ((ssop_flags & SSOP_CURDIR) && tp->tp_localdir != NULL) {
+ if (fputs("tcd ", fd) < 0
+ || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
+ || put_eol(fd) == FAIL) {
+ return FAIL;
+ }
+ did_lcd = true;
+ }
+
// Restore the view of the window (options, file, cursor, etc.).
- //
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (!ses_do_win(wp)) {
continue;
}
@@ -816,31 +795,17 @@ static int makeopens(FILE *fd, char *dirnow)
// "tabedit".
cur_arg_idx = next_arg_idx;
- //
// Restore cursor to the current window if it's not the first one.
- //
if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) {
return FAIL;
}
- //
// Restore window sizes again after jumping around in windows, because
// the current window has a minimum size while others may not.
- //
if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
return FAIL;
}
- // Take care of tab-local working directories if applicable
- if (tp->tp_localdir) {
- if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0
- || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
- || fputs(" | endif\n", fd) < 0) {
- return FAIL;
- }
- did_lcd = true;
- }
-
// Don't continue in another tab page when doing only the current one
// or when at the last tab page.
if (!(ssop_flags & SSOP_TABPAGES)) {
@@ -857,9 +822,7 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
- //
// Wipe out an empty unnamed buffer we started in.
- //
if (fprintf(fd, "%s",
"if exists('s:wipebuf') "
"&& len(win_findbuf(s:wipebuf)) == 0 "
@@ -891,9 +854,7 @@ static int makeopens(FILE *fd, char *dirnow)
PUTLINE_FAIL("let &winminwidth = s:save_winminwidth");
}
- //
// Lastly, execute the x.vim file if it exists.
- //
if (fprintf(fd, "%s",
"let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n"
"if filereadable(s:sx)\n"
@@ -913,7 +874,7 @@ void ex_loadview(exarg_T *eap)
return;
}
- if (do_source(fname, false, DOSO_NONE) == FAIL) {
+ if (do_source(fname, false, DOSO_NONE, NULL) == FAIL) {
semsg(_(e_notopen), fname);
}
xfree(fname);
@@ -926,12 +887,9 @@ void ex_loadview(exarg_T *eap)
/// - SSOP_SLASH: filenames are written with "/" slash
void ex_mkrc(exarg_T *eap)
{
- FILE *fd;
- int failed = false;
int view_session = false; // :mkview, :mksession
int using_vdir = false; // using 'viewdir'?
char *viewFile = NULL;
- unsigned *flagp;
if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) {
view_session = true;
@@ -965,11 +923,13 @@ void ex_mkrc(exarg_T *eap)
// When using 'viewdir' may have to create the directory.
if (using_vdir && !os_isdir(p_vdir)) {
- vim_mkdir_emsg((const char *)p_vdir, 0755);
+ vim_mkdir_emsg(p_vdir, 0755);
}
- fd = open_exfile(fname, eap->forceit, WRITEBIN);
+ FILE *fd = open_exfile(fname, eap->forceit, WRITEBIN);
if (fd != NULL) {
+ int failed = false;
+ unsigned *flagp;
if (eap->cmdidx == CMD_mkview) {
flagp = &vop_flags;
} else {
@@ -1008,9 +968,8 @@ void ex_mkrc(exarg_T *eap)
char *dirnow; // current directory
dirnow = xmalloc(MAXPATHL);
- //
+
// Change to session file's dir.
- //
if (os_dirname(dirnow, MAXPATHL) == FAIL
|| os_chdir(dirnow) != 0) {
*dirnow = NUL;
@@ -1084,7 +1043,7 @@ void ex_mkrc(exarg_T *eap)
}
/// @return the name of the view file for the current buffer.
-static char *get_view_file(int c)
+static char *get_view_file(char c)
{
if (curbuf->b_ffname == NULL) {
emsg(_(e_noname));
@@ -1123,8 +1082,7 @@ static char *get_view_file(int c)
}
}
*s++ = '=';
- assert(c >= CHAR_MIN && c <= CHAR_MAX);
- *s++ = (char)c;
+ *s++ = c;
xstrlcpy(s, ".vim", 5);
xfree(sname);
diff --git a/src/nvim/ex_session.h b/src/nvim/ex_session.h
index 7ee2894c6e..275f20e4a8 100644
--- a/src/nvim/ex_session.h
+++ b/src/nvim/ex_session.h
@@ -1,12 +1,9 @@
-#ifndef NVIM_EX_SESSION_H
-#define NVIM_EX_SESSION_H
+#pragma once
-#include <stdio.h>
+#include <stdio.h> // IWYU pragma: keep
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_session.h.generated.h"
#endif
-
-#endif // NVIM_EX_SESSION_H
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 3e059bcc6c..d9c1993f32 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Implements extended marks for plugins. Marks sit in a MarkTree
// datastructure which provides both efficient mark insertations/lookups
// and adjustment to text changes. See marktree.c for more details.
@@ -29,91 +26,58 @@
// code for redrawing the line with the deleted decoration.
#include <assert.h>
-#include <sys/types.h>
+#include <stddef.h>
-#include "nvim/buffer.h"
+#include "nvim/api/private/defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/decoration.h"
#include "nvim/extmark.h"
#include "nvim/extmark_defs.h"
#include "nvim/globals.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/marktree.h"
#include "nvim/memline.h"
-#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/undo.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "extmark.c.generated.h"
#endif
-static uint32_t *buf_ns_ref(buf_T *buf, uint32_t ns_id, bool put)
-{
- return map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, put);
-}
-
/// Create or update an extmark
///
/// must not be used during iteration!
void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row,
- colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
- ExtmarkOp op)
+ colnr_T end_col, DecorInline decor, uint16_t decor_flags, bool right_gravity,
+ bool end_right_gravity, bool no_undo, bool invalidate, Error *err)
{
- uint32_t *ns = buf_ns_ref(buf, ns_id, true);
+ uint32_t *ns = map_put_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL, NULL);
uint32_t id = idp ? *idp : 0;
- bool decor_full = false;
-
- uint8_t decor_level = kDecorLevelNone; // no decor
- if (decor) {
- if (kv_size(decor->virt_text)
- || kv_size(decor->virt_lines)
- || decor->conceal
- || decor_has_sign(decor)
- || decor->ui_watched
- || decor->spell != kNone) {
- decor_full = true;
- decor = xmemdup(decor, sizeof *decor);
- }
- decor_level = kDecorLevelVisible; // decor affects redraw
- if (kv_size(decor->virt_lines)) {
- decor_level = kDecorLevelVirtLine; // decor affects horizontal size
- }
- }
+ uint16_t flags = mt_flags(right_gravity, no_undo, invalidate, decor.ext) | decor_flags;
if (id == 0) {
id = ++*ns;
} else {
MarkTreeIter itr[1] = { 0 };
- mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ MTKey old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
if (old_mark.id) {
if (mt_paired(old_mark) || end_row > -1) {
- extmark_del(buf, ns_id, id);
+ extmark_del_id(buf, ns_id, id);
} else {
- // TODO(bfredl): we need to do more if "revising" a decoration mark.
- assert(itr->node);
+ assert(marktree_itr_valid(itr));
if (old_mark.pos.row == row && old_mark.pos.col == col) {
- if (marktree_decor_level(old_mark) > kDecorLevelNone) {
- decor_remove(buf, row, row, old_mark.decor_full);
- old_mark.decor_full = NULL;
- }
- old_mark.flags = 0;
- if (decor_full) {
- old_mark.decor_full = decor;
- } else if (decor) {
- old_mark.hl_id = decor->hl_id;
- // Workaround: the gcc compiler of functionaltest-lua build
- // apparently incapable of handling basic integer constants.
- // This can be underanged as soon as we bump minimal gcc version.
- old_mark.flags = (uint16_t)(old_mark.flags
- | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
- old_mark.priority = decor->priority;
+ if (mt_decor_any(old_mark)) {
+ buf_decor_remove(buf, row, row, mt_decor(old_mark), true);
}
- marktree_revise(buf->b_marktree, itr, decor_level, old_mark);
+
+ // not paired: we can revise in place
+ mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
+ mt_itr_rawkey(itr).flags |= flags;
+ mt_itr_rawkey(itr).decor_data = decor.data;
goto revised;
}
- decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.decor_full);
+ buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, mt_decor(old_mark), true);
marktree_del_itr(buf->b_marktree, itr, false);
}
} else {
@@ -121,40 +85,13 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
}
}
- mtkey_t mark = { { row, col }, ns_id, id, 0,
- mt_flags(right_gravity, decor_level), 0, NULL };
- if (decor_full) {
- mark.decor_full = decor;
- } else if (decor) {
- mark.hl_id = decor->hl_id;
- // workaround: see above
- mark.flags = (uint16_t)(mark.flags | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
- mark.priority = decor->priority;
- }
+ MTKey mark = { { row, col }, ns_id, id, flags, decor.data };
marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity);
revised:
- if (op != kExtmarkNoUndo) {
- // TODO(bfredl): this doesn't cover all the cases and probably shouldn't
- // be done "prematurely". Any movement in undo history might necessitate
- // adding new marks to old undo headers. add a test case for this (doesn't
- // fail extmark_spec.lua, and it should)
- uint64_t mark_id = mt_lookup_id(ns_id, id, false);
- u_extmark_set(buf, mark_id, row, col);
- }
-
- if (decor) {
- if (kv_size(decor->virt_lines)) {
- buf->b_virt_line_blocks++;
- }
- if (decor_has_sign(decor)) {
- buf->b_signs++;
- }
- if (decor->sign_text) {
- // TODO(lewis6991): smarter invalidation
- buf_signcols_add_check(buf, NULL);
- }
+ if (decor_flags || decor.ext) {
+ buf_put_decor(buf, decor, row, end_row > -1 ? end_row : row);
decor_redraw(buf, row, end_row > -1 ? end_row : row, decor);
}
@@ -166,7 +103,7 @@ revised:
static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
MarkTreeIter itr[1] = { 0 };
- mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr);
+ MTKey key = marktree_lookup(buf->b_marktree, mark, itr);
if (key.pos.row == -1) {
return false;
}
@@ -179,33 +116,41 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
return true;
}
-/// Remove an extmark
+/// Remove an extmark in "ns_id" by "id"
///
-/// @return 0 on missing id
-bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
+/// @return false on missing id
+bool extmark_del_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
MarkTreeIter itr[1] = { 0 };
- mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
- if (!key.id) {
- return false;
+ MTKey key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ if (key.id) {
+ extmark_del(buf, itr, key, false);
}
- assert(key.pos.row >= 0);
- marktree_del_itr(buf->b_marktree, itr, false);
- mtkey_t key2 = key;
+ return key.id > 0;
+}
+
+/// Remove a (paired) extmark "key" pointed to by "itr"
+void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore)
+{
+ assert(key.pos.row >= 0);
- if (mt_paired(key)) {
- key2 = marktree_lookup_ns(buf->b_marktree, ns_id, id, true, itr);
+ MTKey key2 = key;
+ uint64_t other = marktree_del_itr(buf->b_marktree, itr, false);
+ if (other) {
+ key2 = marktree_lookup(buf->b_marktree, other, itr);
assert(key2.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
+ if (restore) {
+ marktree_itr_get(buf->b_marktree, key.pos.row, key.pos.col, itr);
+ }
}
- if (marktree_decor_level(key) > kDecorLevelNone) {
- decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full);
+ if (mt_decor_any(key)) {
+ buf_decor_remove(buf, key.pos.row, key2.pos.row, mt_decor(key), true);
}
// TODO(bfredl): delete it from current undo header, opportunistically?
- return true;
}
/// Free extmarks in a ns between lines
@@ -216,79 +161,33 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
return false;
}
- bool marks_cleared = false;
-
bool all_ns = (ns_id == 0);
uint32_t *ns = NULL;
if (!all_ns) {
- ns = buf_ns_ref(buf, ns_id, false);
+ ns = map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL);
if (!ns) {
// nothing to do
return false;
}
}
- // the value is either zero or the lnum (row+1) if highlight was present.
- static Map(uint64_t, ssize_t) delete_set = MAP_INIT;
- typedef struct { int row1; } DecorItem;
- static kvec_t(DecorItem) decors;
-
+ bool marks_cleared = false;
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
- ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark),
- false);
- if (del_status) {
- marktree_del_itr(buf->b_marktree, itr, false);
- if (*del_status >= 0) { // we had a decor_id
- DecorItem it = kv_A(decors, *del_status);
- decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
- }
- map_del(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark));
- continue;
- }
-
- assert(mark.ns > 0 && mark.id > 0);
if (mark.ns == ns_id || all_ns) {
marks_cleared = true;
- if (mt_paired(mark)) {
- uint64_t other = mt_lookup_id(mark.ns, mark.id, !mt_end(mark));
- ssize_t decor_id = -1;
- if (marktree_decor_level(mark) > kDecorLevelNone) {
- // Save the decoration and the first pos. Clear the decoration
- // later when we know the full range.
- decor_id = (ssize_t)kv_size(decors);
- kv_push(decors,
- ((DecorItem) { .row1 = mark.pos.row }));
- }
- map_put(uint64_t, ssize_t)(&delete_set, other, decor_id);
- } else if (mark.decor_full) {
- decor_remove(buf, mark.pos.row, mark.pos.row, mark.decor_full);
- }
- marktree_del_itr(buf->b_marktree, itr, false);
+ extmark_del(buf, itr, mark, true);
} else {
marktree_itr_next(buf->b_marktree, itr);
}
}
- uint64_t id;
- ssize_t decor_id;
- map_foreach(&delete_set, id, decor_id, {
- mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr);
- assert(itr->node);
- marktree_del_itr(buf->b_marktree, itr, false);
- if (decor_id >= 0) {
- DecorItem it = kv_A(decors, decor_id);
- decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
- }
- });
- map_clear(uint64_t, ssize_t)(&delete_set);
- kv_size(decors) = 0;
return marks_cleared;
}
@@ -297,19 +196,34 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
///
/// if upper_lnum or upper_col are negative the buffer
/// will be searched to the start, or end
-/// dir can be set to control the order of the array
-/// amount = amount of marks to find or -1 for all
+/// reverse can be set to control the order of the array
+/// amount = amount of marks to find or INT64_MAX for all
ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
- colnr_T u_col, int64_t amount, bool reverse)
+ colnr_T u_col, int64_t amount, bool reverse, ExtmarkType type_filter,
+ bool overlap)
{
ExtmarkInfoArray array = KV_INITIAL_VALUE;
MarkTreeIter itr[1];
- // Find all the marks
- marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
- itr, reverse, false, NULL);
+
+ if (overlap) {
+ // Find all the marks overlapping the start position
+ if (!marktree_itr_get_overlap(buf->b_marktree, l_row, l_col, itr)) {
+ return array;
+ }
+
+ MTPair pair;
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ push_mark(&array, ns_id, type_filter, pair);
+ }
+ } else {
+ // Find all the marks beginning with the start position
+ marktree_itr_get_ext(buf->b_marktree, MTPos(l_row, l_col),
+ itr, reverse, false, NULL);
+ }
+
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| (mark.pos.row - u_row) * order > 0
|| (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) {
@@ -319,17 +233,8 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_co
goto next_mark;
}
- if (mark.ns == ns_id) {
- mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
- kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns,
- .mark_id = mark.id,
- .row = mark.pos.row, .col = mark.pos.col,
- .end_row = end.pos.row,
- .end_col = end.pos.col,
- .right_gravity = mt_right(mark),
- .end_right_gravity = mt_right(end),
- .decor = get_decor(mark) }));
- }
+ MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
+ push_mark(&array, ns_id, type_filter, mtpair_from(mark, end));
next_mark:
if (reverse) {
marktree_itr_prev(buf->b_marktree, itr);
@@ -340,28 +245,36 @@ next_mark:
return array;
}
+static void push_mark(ExtmarkInfoArray *array, uint32_t ns_id, ExtmarkType type_filter, MTPair mark)
+{
+ if (!(ns_id == UINT32_MAX || mark.start.ns == ns_id)) {
+ return;
+ }
+ if (type_filter != kExtmarkNone) {
+ if (!mt_decor_any(mark.start)) {
+ return;
+ }
+ uint16_t type_flags = decor_type_flags(mt_decor(mark.start));
+
+ if (!(type_flags & type_filter)) {
+ return;
+ }
+ }
+
+ kv_push(*array, mark);
+}
+
/// Lookup an extmark by id
-ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
+MTPair extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
- ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT };
- mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
+ MTKey mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
if (!mark.id) {
- return ret;
+ return mtpair_from(mark, mark); // invalid
}
assert(mark.pos.row >= 0);
- mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
-
- ret.ns_id = ns_id;
- ret.mark_id = id;
- ret.row = mark.pos.row;
- ret.col = mark.pos.col;
- ret.end_row = end.pos.row;
- ret.end_col = end.pos.col;
- ret.right_gravity = mt_right(mark);
- ret.end_right_gravity = mt_right(end);
- ret.decor = get_decor(mark);
-
- return ret;
+ MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
+
+ return mtpair_from(mark, end);
}
/// free extmarks from the buffer
@@ -374,14 +287,14 @@ void extmark_free_all(buf_T *buf)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, 0, 0, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0) {
break;
}
- // don't free mark.decor_full twice for a paired mark.
+ // don't free mark.decor twice for a paired mark.
if (!(mt_paired(mark) && mt_end(mark))) {
- decor_free(mark.decor_full);
+ decor_free(mt_decor(mark));
}
marktree_itr_next(buf->b_marktree, itr);
@@ -389,63 +302,65 @@ void extmark_free_all(buf_T *buf)
marktree_clear(buf->b_marktree);
- map_destroy(uint32_t, uint32_t)(buf->b_extmark_ns);
- map_init(uint32_t, uint32_t, buf->b_extmark_ns);
+ map_destroy(uint32_t, buf->b_extmark_ns);
+ *buf->b_extmark_ns = (Map(uint32_t, uint32_t)) MAP_INIT;
}
-/// Save info for undo/redo of set marks
-static void u_extmark_set(buf_T *buf, uint64_t mark, int row, colnr_T col)
-{
- u_header_T *uhp = u_force_get_undo_header(buf);
- if (!uhp) {
- return;
- }
-
- ExtmarkSavePos pos;
- pos.mark = mark;
- pos.old_row = -1;
- pos.old_col = -1;
- pos.row = row;
- pos.col = col;
-
- ExtmarkUndoObject undo = { .type = kExtmarkSavePos,
- .data.savepos = pos };
-
- kv_push(uhp->uh_extmark, undo);
-}
-
-/// copy extmarks data between range
+/// invalidate extmarks between range and copy to undo header
///
-/// useful when we cannot simply reverse the operation. This will do nothing on
-/// redo, enforces correct position when undo.
-void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
+/// copying is useful when we cannot simply reverse the operation. This will do
+/// nothing on redo, enforces correct position when undo.
+void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_col,
+ ExtmarkOp op)
{
u_header_T *uhp = u_force_get_undo_header(buf);
- if (!uhp) {
- return;
- }
-
+ MarkTreeIter itr[1] = { 0 };
ExtmarkUndoObject undo;
- MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, (int32_t)l_row, l_col, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
- ExtmarkSavePos pos;
- pos.mark = mt_lookup_key(mark);
- pos.old_row = mark.pos.row;
- pos.old_col = mark.pos.col;
- pos.row = -1;
- pos.col = -1;
- undo.data.savepos = pos;
- undo.type = kExtmarkSavePos;
- kv_push(uhp->uh_extmark, undo);
+ bool invalidated = false;
+ // Invalidate/delete mark
+ if (!mt_invalid(mark) && mt_invalidate(mark) && !mt_end(mark)) {
+ MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
+ if (endpos.row < 0) {
+ endpos = mark.pos;
+ }
+ if ((endpos.col <= u_col || (!u_col && endpos.row == mark.pos.row))
+ && mark.pos.col >= l_col
+ && mark.pos.row >= l_row && endpos.row <= u_row - (u_col ? 0 : 1)) {
+ if (mt_no_undo(mark)) {
+ extmark_del(buf, itr, mark, true);
+ continue;
+ } else {
+ invalidated = true;
+ mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID;
+ buf_decor_remove(buf, mark.pos.row, endpos.row, mt_decor(mark), false);
+ }
+ }
+ }
+
+ // Push mark to undo header
+ if (uhp && op == kExtmarkUndo && !mt_no_undo(mark)) {
+ ExtmarkSavePos pos;
+ pos.mark = mt_lookup_key(mark);
+ pos.invalidated = invalidated;
+ pos.old_row = mark.pos.row;
+ pos.old_col = mark.pos.col;
+ pos.row = -1;
+ pos.col = -1;
+
+ undo.data.savepos = pos;
+ undo.type = kExtmarkSavePos;
+ kv_push(uhp->uh_extmark, undo);
+ }
marktree_itr_next(buf->b_marktree, itr);
}
@@ -475,6 +390,13 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
} else if (undo_info.type == kExtmarkSavePos) {
ExtmarkSavePos pos = undo_info.data.savepos;
if (undo) {
+ if (pos.invalidated) {
+ MarkTreeIter itr[1] = { 0 };
+ MTKey mark = marktree_lookup(curbuf->b_marktree, pos.mark, itr);
+ mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID;
+ MTPos end = marktree_get_altpos(curbuf->b_marktree, mark, itr);
+ buf_put_decor(curbuf, mt_decor(mark), mark.pos.row, end.row < 0 ? mark.pos.row : end.row);
+ }
if (pos.old_row >= 0) {
extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col);
}
@@ -516,7 +438,6 @@ void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount,
old_row = line2 - line1 + 1;
// TODO(bfredl): ej kasta?
old_byte = (bcount_t)buf->deleted_bytes2;
-
new_row = amount_after + old_row;
} else {
// A region is either deleted (amount == MAXLNUM) or
@@ -557,7 +478,7 @@ void extmark_splice(buf_T *buf, int start_row, colnr_T start_col, int old_row, c
bcount_t old_byte, int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
- long offset = ml_find_line_or_offset(buf, start_row + 1, NULL, true);
+ int offset = ml_find_line_or_offset(buf, start_row + 1, NULL, true);
// On empty buffers, when editing the first line, the line is buffered,
// causing offset to be < 0. While the buffer is not actually empty, the
@@ -582,15 +503,29 @@ void extmark_splice_impl(buf_T *buf, int start_row, colnr_T start_col, bcount_t
old_row, old_col, old_byte,
new_row, new_col, new_byte);
- if (undo == kExtmarkUndo && (old_row > 0 || old_col > 0)) {
- // Copy marks that would be effected by delete
+ if (old_row > 0 || old_col > 0) {
+ // Copy and invalidate marks that would be effected by delete
// TODO(bfredl): Be "smart" about gravity here, left-gravity at the
// beginning and right-gravity at the end need not be preserved.
// Also be smart about marks that already have been saved (important for
// merge!)
int end_row = start_row + old_row;
int end_col = (old_row ? 0 : start_col) + old_col;
- u_extmark_copy(buf, start_row, start_col, end_row, end_col);
+ extmark_splice_delete(buf, start_row, start_col, end_row, end_col, undo);
+ }
+
+ // Move the signcolumn sentinel line
+ if (buf->b_signs_with_text && buf->b_signcols.sentinel) {
+ linenr_T se_lnum = buf->b_signcols.sentinel;
+ if (se_lnum >= start_row) {
+ if (old_row != 0 && se_lnum > old_row + start_row) {
+ buf->b_signcols.sentinel += new_row - old_row;
+ } else if (new_row == 0) {
+ buf->b_signcols.sentinel = 0;
+ } else {
+ buf->b_signcols.sentinel += new_row;
+ }
+ }
}
marktree_splice(buf->b_marktree, (int32_t)start_row, start_col,
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index 657e99a938..061cd0ed5f 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EXTMARK_H
-#define NVIM_EXTMARK_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -8,30 +7,15 @@
#include "klib/kvec.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
-#include "nvim/extmark_defs.h"
-#include "nvim/macros.h"
+#include "nvim/extmark_defs.h" // IWYU pragma: export
+#include "nvim/macros_defs.h"
#include "nvim/marktree.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-EXTERN int extmark_splice_pending INIT(= 0);
+EXTERN int extmark_splice_pending INIT( = 0);
-typedef struct {
- uint64_t ns_id;
- uint64_t mark_id;
- int row;
- colnr_T col;
- int end_row;
- colnr_T end_col;
- bool right_gravity;
- bool end_right_gravity;
- Decoration decor; // TODO(bfredl): CHONKY
-} ExtmarkInfo;
-
-typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
-
-// TODO(bfredl): good enough name for now.
-typedef ptrdiff_t bcount_t;
+typedef kvec_t(MTPair) ExtmarkInfoArray;
// delete the columns between mincol and endcol
typedef struct {
@@ -66,6 +50,7 @@ typedef struct {
colnr_T old_col;
int row;
colnr_T col;
+ bool invalidated;
} ExtmarkSavePos;
typedef enum {
@@ -76,6 +61,17 @@ typedef enum {
kExtmarkClear,
} UndoObjectType;
+// TODO(bfredl): if possible unify these with marktree flags,
+// so it is possible to filter extmarks directly on top-level flags
+typedef enum {
+ kExtmarkNone = 0x1,
+ kExtmarkSign = 0x2,
+ kExtmarkSignHL = 0x4,
+ kExtmarkVirtText = 0x8,
+ kExtmarkVirtLines = 0x10,
+ kExtmarkHighlight = 0x20,
+} ExtmarkType;
+
// TODO(bfredl): reduce the number of undo action types
struct undo_object {
UndoObjectType type;
@@ -89,5 +85,3 @@ struct undo_object {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "extmark.h.generated.h"
#endif
-
-#endif // NVIM_EXTMARK_H
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index 51b60dcf6d..c5a8684545 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -1,15 +1,10 @@
-#ifndef NVIM_EXTMARK_DEFS_H
-#define NVIM_EXTMARK_DEFS_H
+#pragma once
#include "klib/kvec.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
-typedef struct {
- char *text;
- int hl_id;
-} VirtTextChunk;
-
-typedef kvec_t(VirtTextChunk) VirtText;
+// TODO(bfredl): good enough name for now.
+typedef ptrdiff_t bcount_t;
typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
@@ -22,11 +17,3 @@ typedef enum {
kExtmarkNoUndo, // Operation should not be reversible
kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
} ExtmarkOp;
-
-typedef enum {
- kDecorLevelNone = 0,
- kDecorLevelVisible = 1,
- kDecorLevelVirtLine = 2,
-} DecorLevel;
-
-#endif // NVIM_EXTMARK_DEFS_H
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index e236f23895..460cd48fc5 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// File searching functions for 'path', 'tags' and 'cdpath' options.
//
// External visible functions:
@@ -47,31 +44,30 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/file_search.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static char *ff_expand_buffer = NULL; // used for expanding filenames
@@ -117,7 +113,7 @@ typedef struct ff_visited {
FileID file_id;
// The memory for this struct is allocated according to the length of
// ffv_fname.
- char ffv_fname[1]; // actually longer
+ char ffv_fname[];
} ff_visited_T;
// We might have to manage several visited lists during a search.
@@ -182,7 +178,8 @@ typedef struct ff_search_ctx_T {
# include "file_search.c.generated.h"
#endif
-static char e_pathtoolong[] = N_("E854: path too long for completion");
+static const char e_path_too_long_for_completion[]
+ = N_("E854: Path too long for completion");
/// Initialization routine for vim_findfile().
///
@@ -293,7 +290,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
xstrlcpy(ff_expand_buffer, rel_fname, len + 1);
search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, false);
} else {
- search_ctx->ffsc_start_dir = xstrnsave(rel_fname, len);
+ search_ctx->ffsc_start_dir = xmemdupz(rel_fname, len);
}
if (*++path != NUL) {
path++;
@@ -395,7 +392,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
len = 0;
while (*wc_part != NUL) {
if (len + 5 >= MAXPATHL) {
- emsg(_(e_pathtoolong));
+ emsg(_(e_path_too_long_for_completion));
break;
}
if (strncmp(wc_part, "**", 2) == 0) {
@@ -438,7 +435,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
// create an absolute path
if (strlen(search_ctx->ffsc_start_dir)
+ strlen(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) {
- emsg(_(e_pathtoolong));
+ emsg(_(e_path_too_long_for_completion));
goto error_return;
}
STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir);
@@ -576,9 +573,9 @@ char *vim_findfile(void *search_ctx_arg)
}
// upward search loop
- for (;;) {
+ while (true) {
// downward search loop
- for (;;) {
+ while (true) {
// check if user wants to stop the search
os_breakcheck();
if (got_int) {
@@ -614,7 +611,7 @@ char *vim_findfile(void *search_ctx_arg)
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("Already Searched: %s (%s)",
+ smsg(0, "Already Searched: %s (%s)",
stackp->ffs_fix_path, stackp->ffs_wc_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
@@ -625,7 +622,7 @@ char *vim_findfile(void *search_ctx_arg)
#ifdef FF_VERBOSE
} else if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("Searching: %s (%s)",
+ smsg(0, "Searching: %s (%s)",
stackp->ffs_fix_path, stackp->ffs_wc_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
@@ -780,7 +777,7 @@ char *vim_findfile(void *search_ctx_arg)
} else {
suf = curbuf->b_p_sua;
}
- for (;;) {
+ while (true) {
// if file exists and we didn't already find it
if ((path_with_url(file_path)
|| (os_path_exists(file_path)
@@ -794,10 +791,10 @@ char *vim_findfile(void *search_ctx_arg)
) {
#ifdef FF_VERBOSE
if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list,
- file_path, (char_u *)"") == FAIL) {
+ file_path, "") == FAIL) {
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("Already: %s", file_path);
+ smsg(0, "Already: %s", file_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -822,7 +819,7 @@ char *vim_findfile(void *search_ctx_arg)
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("HIT: %s", file_path);
+ smsg(0, "HIT: %s", file_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -985,7 +982,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename,
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("ff_get_visited_list: FOUND list for %s", filename);
+ smsg(0, "ff_get_visited_list: FOUND list for %s", filename);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -999,7 +996,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename,
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("ff_get_visited_list: new list for %s", filename);
+ smsg(0, "ff_get_visited_list: new list for %s", filename);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -1026,8 +1023,6 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename,
static bool ff_wc_equal(char *s1, char *s2)
{
int i, j;
- int c1 = NUL;
- int c2 = NUL;
int prev1 = NUL;
int prev2 = NUL;
@@ -1040,8 +1035,8 @@ static bool ff_wc_equal(char *s1, char *s2)
}
for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;) {
- c1 = utf_ptr2char(s1 + i);
- c2 = utf_ptr2char(s2 + j);
+ int c1 = utf_ptr2char(s1 + i);
+ int c2 = utf_ptr2char(s2 + j);
if ((p_fic ? mb_tolower(c1) != mb_tolower(c2) : c1 != c2)
&& (prev1 != '*' || prev2 != '*')) {
@@ -1092,7 +1087,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p
}
// New file/dir. Add it to the list of visited files/dirs.
- vp = xmalloc(sizeof(ff_visited_T) + strlen(ff_expand_buffer));
+ vp = xmalloc(offsetof(ff_visited_T, ffv_fname) + strlen(ff_expand_buffer) + 1);
if (!url) {
vp->file_id_valid = true;
@@ -1119,28 +1114,28 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p
static ff_stack_T *ff_create_stack_element(char *fix_part, char *wc_part, int level,
int star_star_empty)
{
- ff_stack_T *new = xmalloc(sizeof(ff_stack_T));
+ ff_stack_T *stack = xmalloc(sizeof(ff_stack_T));
- new->ffs_prev = NULL;
- new->ffs_filearray = NULL;
- new->ffs_filearray_size = 0;
- new->ffs_filearray_cur = 0;
- new->ffs_stage = 0;
- new->ffs_level = level;
- new->ffs_star_star_empty = star_star_empty;
+ stack->ffs_prev = NULL;
+ stack->ffs_filearray = NULL;
+ stack->ffs_filearray_size = 0;
+ stack->ffs_filearray_cur = 0;
+ stack->ffs_stage = 0;
+ stack->ffs_level = level;
+ stack->ffs_star_star_empty = star_star_empty;
// the following saves NULL pointer checks in vim_findfile
if (fix_part == NULL) {
fix_part = "";
}
- new->ffs_fix_path = xstrdup(fix_part);
+ stack->ffs_fix_path = xstrdup(fix_part);
if (wc_part == NULL) {
wc_part = "";
}
- new->ffs_wc_path = xstrdup(wc_part);
+ stack->ffs_wc_path = xstrdup(wc_part);
- return new;
+ return stack;
}
/// Push a dir on the directory stack.
@@ -1283,28 +1278,26 @@ static int ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v)
/// @param len length of file name
/// @param first use count'th matching file name
/// @param rel_fname file name searching relative to
+/// @param[in,out] file_to_find modified copy of file name
+/// @param[in,out] search_ctx state of the search
///
/// @return an allocated string for the file name. NULL for error.
-char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname)
+char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname,
+ char **file_to_find, char **search_ctx)
{
return find_file_in_path_option(ptr, len, options, first,
(*curbuf->b_p_path == NUL
? p_path
: curbuf->b_p_path),
- FINDFILE_BOTH, rel_fname, curbuf->b_p_sua);
+ FINDFILE_BOTH, rel_fname, curbuf->b_p_sua,
+ file_to_find, search_ctx);
}
-static char *ff_file_to_find = NULL;
-static void *fdip_search_ctx = NULL;
-
#if defined(EXITFREE)
void free_findfile(void)
{
- xfree(ff_file_to_find);
- vim_findfile_cleanup(fdip_search_ctx);
- xfree(ff_expand_buffer);
+ XFREE_CLEAR(ff_expand_buffer);
}
-
#endif
/// Find the directory name "ptr[len]" in the path.
@@ -1318,12 +1311,16 @@ void free_findfile(void)
/// @param ptr file name
/// @param len length of file name
/// @param rel_fname file name searching relative to
+/// @param[in,out] file_to_find modified copy of file name
+/// @param[in,out] search_ctx state of the search
///
/// @return an allocated string for the file name. NULL for error.
-char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname)
+char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname,
+ char **file_to_find, char **search_ctx)
{
return find_file_in_path_option(ptr, len, options, true, p_cdpath,
- FINDFILE_DIR, rel_fname, "");
+ FINDFILE_DIR, rel_fname, "",
+ file_to_find, search_ctx);
}
/// @param ptr file name
@@ -1333,9 +1330,13 @@ char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname
/// @param find_what FINDFILE_FILE, _DIR or _BOTH
/// @param rel_fname file name we are looking relative to.
/// @param suffixes list of suffixes, 'suffixesadd' option
+/// @param[in,out] file_to_find modified copy of file name
+/// @param[in,out] search_ctx_arg state of the search
char *find_file_in_path_option(char *ptr, size_t len, int options, int first, char *path_option,
- int find_what, char *rel_fname, char *suffixes)
+ int find_what, char *rel_fname, char *suffixes, char **file_to_find,
+ char **search_ctx_arg)
{
+ ff_search_ctx_T **search_ctx = (ff_search_ctx_T **)search_ctx_arg;
static char *dir;
static int did_findfile_init = false;
char save_char;
@@ -1343,7 +1344,7 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
char *buf = NULL;
int rel_to_curdir;
- if (rel_fname != NULL && path_with_url((const char *)rel_fname)) {
+ if (rel_fname != NULL && path_with_url(rel_fname)) {
// Do not attempt to search "relative" to a URL. #6009
rel_fname = NULL;
}
@@ -1359,11 +1360,11 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
expand_env_esc(ptr, NameBuff, MAXPATHL, false, true, NULL);
ptr[len] = save_char;
- xfree(ff_file_to_find);
- ff_file_to_find = xstrdup(NameBuff);
+ xfree(*file_to_find);
+ *file_to_find = xstrdup(NameBuff);
if (options & FNAME_UNESC) {
// Change all "\ " to " ".
- for (ptr = ff_file_to_find; *ptr != NUL; ptr++) {
+ for (ptr = *file_to_find; *ptr != NUL; ptr++) {
if (ptr[0] == '\\' && ptr[1] == ' ') {
memmove(ptr, ptr + 1, strlen(ptr));
}
@@ -1371,51 +1372,51 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
}
}
- rel_to_curdir = (ff_file_to_find[0] == '.'
- && (ff_file_to_find[1] == NUL
- || vim_ispathsep(ff_file_to_find[1])
- || (ff_file_to_find[1] == '.'
- && (ff_file_to_find[2] == NUL
- || vim_ispathsep(ff_file_to_find[2])))));
- if (vim_isAbsName(ff_file_to_find)
+ rel_to_curdir = ((*file_to_find)[0] == '.'
+ && ((*file_to_find)[1] == NUL
+ || vim_ispathsep((*file_to_find)[1])
+ || ((*file_to_find)[1] == '.'
+ && ((*file_to_find)[2] == NUL
+ || vim_ispathsep((*file_to_find)[2])))));
+ if (vim_isAbsName(*file_to_find)
// "..", "../path", "." and "./path": don't use the path_option
|| rel_to_curdir
#if defined(MSWIN)
// handle "\tmp" as absolute path
- || vim_ispathsep(ff_file_to_find[0])
+ || vim_ispathsep((*file_to_find)[0])
// handle "c:name" as absolute path
- || (ff_file_to_find[0] != NUL && ff_file_to_find[1] == ':')
+ || ((*file_to_find)[0] != NUL && (*file_to_find)[1] == ':')
#endif
) {
// Absolute path, no need to use "path_option".
// If this is not a first call, return NULL. We already returned a
// filename on the first call.
if (first == true) {
- if (path_with_url(ff_file_to_find)) {
- file_name = xstrdup(ff_file_to_find);
+ if (path_with_url(*file_to_find)) {
+ file_name = xstrdup(*file_to_find);
goto theend;
}
// When FNAME_REL flag given first use the directory of the file.
// Otherwise or when this fails use the current directory.
for (int run = 1; run <= 2; run++) {
- size_t l = strlen(ff_file_to_find);
+ size_t l = strlen(*file_to_find);
if (run == 1
&& rel_to_curdir
&& (options & FNAME_REL)
&& rel_fname != NULL
&& strlen(rel_fname) + l < MAXPATHL) {
STRCPY(NameBuff, rel_fname);
- STRCPY(path_tail(NameBuff), ff_file_to_find);
+ STRCPY(path_tail(NameBuff), *file_to_find);
l = strlen(NameBuff);
} else {
- STRCPY(NameBuff, ff_file_to_find);
+ STRCPY(NameBuff, *file_to_find);
run = 2;
}
// When the file doesn't exist, try adding parts of 'suffixesadd'.
buf = suffixes;
- for (;;) {
+ while (true) {
if ((os_path_exists(NameBuff)
&& (find_what == FINDFILE_BOTH
|| ((find_what == FINDFILE_DIR)
@@ -1437,14 +1438,14 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
// Otherwise continue to find the next match.
if (first == true) {
// vim_findfile_free_visited can handle a possible NULL pointer
- vim_findfile_free_visited(fdip_search_ctx);
+ vim_findfile_free_visited(*search_ctx);
dir = path_option;
did_findfile_init = false;
}
- for (;;) {
+ while (true) {
if (did_findfile_init) {
- file_name = vim_findfile(fdip_search_ctx);
+ file_name = vim_findfile(*search_ctx);
if (file_name != NULL) {
break;
}
@@ -1455,8 +1456,8 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
if (dir == NULL || *dir == NUL) {
// We searched all paths of the option, now we can free the search context.
- vim_findfile_cleanup(fdip_search_ctx);
- fdip_search_ctx = NULL;
+ vim_findfile_cleanup(*search_ctx);
+ *search_ctx = NULL;
break;
}
@@ -1468,10 +1469,10 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
// get the stopdir string
r_ptr = vim_findfile_stopdir(buf);
- fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find,
- r_ptr, 100, false, find_what,
- fdip_search_ctx, false, rel_fname);
- if (fdip_search_ctx != NULL) {
+ *search_ctx = vim_findfile_init(buf, *file_to_find,
+ r_ptr, 100, false, find_what,
+ *search_ctx, false, rel_fname);
+ if (*search_ctx != NULL) {
did_findfile_init = true;
}
xfree(buf);
@@ -1481,19 +1482,15 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
if (file_name == NULL && (options & FNAME_MESS)) {
if (first == true) {
if (find_what == FINDFILE_DIR) {
- semsg(_("E344: Can't find directory \"%s\" in cdpath"),
- ff_file_to_find);
+ semsg(_("E344: Can't find directory \"%s\" in cdpath"), *file_to_find);
} else {
- semsg(_("E345: Can't find file \"%s\" in path"),
- ff_file_to_find);
+ semsg(_("E345: Can't find file \"%s\" in path"), *file_to_find);
}
} else {
if (find_what == FINDFILE_DIR) {
- semsg(_("E346: No more directory \"%s\" found in cdpath"),
- ff_file_to_find);
+ semsg(_("E346: No more directory \"%s\" found in cdpath"), *file_to_find);
} else {
- semsg(_("E347: No more file \"%s\" found in path"),
- ff_file_to_find);
+ semsg(_("E347: No more file \"%s\" found in path"), *file_to_find);
}
}
}
@@ -1547,7 +1544,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre
} else {
tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
}
- tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
+ tv_dict_add_str(dict, S_LEN("scope"), buf);
tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow);
tv_dict_set_keys_readonly(dict);
@@ -1608,8 +1605,12 @@ int vim_chdirfile(char *fname, CdCause cause)
/// Change directory to "new_dir". Search 'cdpath' for relative directory names.
int vim_chdir(char *new_dir)
{
- char *dir_name = find_directory_in_path(new_dir, strlen(new_dir),
- FNAME_MESS, curbuf->b_ffname);
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+ char *dir_name = find_directory_in_path(new_dir, strlen(new_dir), FNAME_MESS,
+ curbuf->b_ffname, &file_to_find, &search_ctx);
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
if (dir_name == NULL) {
return -1;
}
diff --git a/src/nvim/file_search.h b/src/nvim/file_search.h
index b69a6fa154..d4b5c5d352 100644
--- a/src/nvim/file_search.h
+++ b/src/nvim/file_search.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_FILE_SEARCH_H
-#define NVIM_FILE_SEARCH_H
+#pragma once
#include <stdlib.h>
#include "nvim/globals.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Flags for find_file_*() functions.
#define FINDFILE_FILE 0 // only files
@@ -14,4 +13,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "file_search.h.generated.h"
#endif
-#endif // NVIM_FILE_SEARCH_H
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 9da9d6199e..0bb664bcf5 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// fileio.c: read from and write to a file
#include <assert.h>
@@ -10,13 +7,16 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -27,19 +27,20 @@
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
-#include "nvim/ex_cmds.h"
+#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/iconv.h"
-#include "nvim/input.h"
+#include "nvim/highlight.h"
+#include "nvim/iconv_defs.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
#include "nvim/memline.h"
@@ -47,24 +48,30 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/fs_defs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/sha256.h"
#include "nvim/shada.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
+
+#ifdef BACKSLASH_IN_FILENAME
+# include "nvim/charset.h"
+#endif
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
# include <dirent.h>
# include <sys/file.h>
#endif
@@ -73,87 +80,48 @@
# include "nvim/charset.h"
#endif
-#define BUFSIZE 8192 // size of normal write buffer
-#define SMBUFSIZE 256 // size of emergency write buffer
-
// For compatibility with libuv < 1.20.0 (tested on 1.18.0)
#ifndef UV_FS_COPYFILE_FICLONE
# define UV_FS_COPYFILE_FICLONE 0
#endif
-#define HAS_BW_FLAGS
-enum {
- FIO_LATIN1 = 0x01, // convert Latin1
- FIO_UTF8 = 0x02, // convert UTF-8
- FIO_UCS2 = 0x04, // convert UCS-2
- FIO_UCS4 = 0x08, // convert UCS-4
- FIO_UTF16 = 0x10, // convert UTF-16
- FIO_ENDIAN_L = 0x80, // little endian
- FIO_NOCONVERT = 0x2000, // skip encoding conversion
- FIO_UCSBOM = 0x4000, // check for BOM at start of file
- FIO_ALL = -1, // allow all formats
-};
-
-// When converting, a read() or write() may leave some bytes to be converted
-// for the next call. The value is guessed...
-#define CONV_RESTLEN 30
-
-// We have to guess how much a sequence of bytes may expand when converting
-// with iconv() to be able to allocate a buffer.
-#define ICONV_MULT 8
-
-// Structure to pass arguments from buf_write() to buf_write_bytes().
-struct bw_info {
- int bw_fd; // file descriptor
- char *bw_buf; // buffer with data to be written
- int bw_len; // length of data
-#ifdef HAS_BW_FLAGS
- int bw_flags; // FIO_ flags
-#endif
- char_u bw_rest[CONV_RESTLEN]; // not converted bytes
- int bw_restlen; // nr of bytes in bw_rest[]
- int bw_first; // first write call
- char *bw_conv_buf; // buffer for writing converted chars
- size_t bw_conv_buflen; // size of bw_conv_buf
- int bw_conv_error; // set for conversion error
- linenr_T bw_conv_error_lnum; // first line with error or zero
- linenr_T bw_start_lnum; // line number at start of buffer
- iconv_t bw_iconv_fd; // descriptor for iconv() or -1
-};
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "fileio.c.generated.h"
#endif
-static char *e_auchangedbuf = N_("E812: Autocommands changed buffer or buffer name");
-static char e_no_matching_autocommands_for_buftype_str_buffer[]
- = N_("E676: No matching autocommands for buftype=%s buffer");
+static const char *e_auchangedbuf = N_("E812: Autocommands changed buffer or buffer name");
void filemess(buf_T *buf, char *name, char *s, int attr)
{
- int msg_scroll_save;
+ int prev_msg_col = msg_col;
if (msg_silent != 0) {
return;
}
- add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)name);
+
+ add_quoted_fname(IObuff, IOSIZE - 100, buf, name);
+
// Avoid an over-long translation to cause trouble.
xstrlcat(IObuff, s, IOSIZE);
+
// For the first message may have to start a new line.
// For further ones overwrite the previous one, reset msg_scroll before
// calling filemess().
- msg_scroll_save = msg_scroll;
- if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) {
+ int msg_scroll_save = msg_scroll;
+ if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
msg_scroll = false;
}
if (!msg_scroll) { // wait a bit when overwriting an error msg
- check_for_delay(false);
+ msg_check_for_delay(false);
}
msg_start();
+ if (prev_msg_col != 0 && msg_col == 0) {
+ msg_putchar('\r'); // overwrite any previous message.
+ }
msg_scroll = msg_scroll_save;
msg_scrolled_ign = true;
// may truncate the message to avoid a hit-return prompt
- msg_outtrans_attr(msg_may_trunc(false, IObuff), attr);
+ msg_outtrans(msg_may_trunc(false, IObuff), attr);
msg_clr_eos();
ui_flush();
msg_scrolled_ign = false;
@@ -188,6 +156,7 @@ void filemess(buf_T *buf, char *name, char *s, int attr)
int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
linenr_T lines_to_read, exarg_T *eap, int flags, bool silent)
{
+ int retval = FAIL; // jump to "theend" instead of returning
int fd = stdin_fd >= 0 ? stdin_fd : 0;
int newfile = (flags & READ_NEW);
int check_readonly;
@@ -207,7 +176,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
char *line_start = NULL; // init to shut up gcc
int wasempty; // buffer was empty before reading
colnr_T len;
- long size = 0;
+ ptrdiff_t size = 0;
uint8_t *p = NULL;
off_T filesize = 0;
bool skip_read = false;
@@ -217,7 +186,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
linenr_T linecnt;
bool error = false; // errors encountered
int ff_error = EOL_UNKNOWN; // file format with errors
- long linerest = 0; // remaining chars in line
+ ptrdiff_t linerest = 0; // remaining chars in line
int perm = 0;
#ifdef UNIX
int swap_mode = -1; // protection bits for swap file
@@ -245,7 +214,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
bool fenc_alloced; // fenc_next is in allocated memory
char *fenc_next = NULL; // next item in 'fencs' or NULL
bool advance_fenc = false;
- long real_size = 0;
+ int real_size = 0;
iconv_t iconv_fd = (iconv_t)-1; // descriptor for iconv() or -1
bool did_iconv = false; // true when iconv() failed and trying
// 'charconvert' next
@@ -275,7 +244,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
&& vim_strchr(p_cpo, CPO_FNAMER) != NULL
&& !(flags & READ_DUMMY)) {
if (set_rw_fname(fname, sfname) == FAIL) {
- return FAIL;
+ goto theend;
}
}
@@ -319,10 +288,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (newfile) {
if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname,
false, curbuf, eap)) {
- int status = OK;
-
+ retval = OK;
if (aborting()) {
- status = FAIL;
+ retval = FAIL;
}
// The BufReadCmd code usually uses ":read" to get the text and
@@ -330,14 +298,15 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
// consider this to work like ":edit", thus reset the
// BF_NOTEDITED flag. Then ":write" will work to overwrite the
// same file.
- if (status == OK) {
+ if (retval == OK) {
curbuf->b_flags &= ~BF_NOTEDITED;
}
- return status;
+ goto theend;
}
} else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname,
false, NULL, eap)) {
- return aborting() ? FAIL : OK;
+ retval = aborting() ? FAIL : OK;
+ goto theend;
}
curbuf->b_op_start = orig_start;
@@ -345,11 +314,12 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (flags & READ_NOFILE) {
// Return NOTDONE instead of FAIL so that BufEnter can be triggered
// and other operations don't fail.
- return NOTDONE;
+ retval = NOTDONE;
+ goto theend;
}
}
- if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) {
+ if (((shortmess(SHM_OVER) && !msg_listdo_overwrite) || curbuf->b_help) && p_verbose == 0) {
msg_scroll = false; // overwrite previous file message
} else {
msg_scroll = true; // don't overwrite previous file message
@@ -363,7 +333,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
filemess(curbuf, fname, _("Illegal file name"), 0);
msg_end();
msg_scroll = msg_save;
- return FAIL;
+ goto theend;
}
// If the name ends in a path separator, we can't open it. Check here,
@@ -375,7 +345,8 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
}
msg_end();
msg_scroll = msg_save;
- return NOTDONE;
+ retval = NOTDONE;
+ goto theend;
}
}
@@ -383,24 +354,30 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
perm = os_getperm(fname);
// On Unix it is possible to read a directory, so we have to
// check for it before os_open().
+
+#ifdef OPEN_CHR_FILES
+# define IS_CHR_DEV(perm, fname) S_ISCHR(perm) && is_dev_fd_file(fname)
+#else
+# define IS_CHR_DEV(perm, fname) false
+#endif
+
if (perm >= 0 && !S_ISREG(perm) // not a regular file ...
&& !S_ISFIFO(perm) // ... or fifo
&& !S_ISSOCK(perm) // ... or socket
-#ifdef OPEN_CHR_FILES
- && !(S_ISCHR(perm) && is_dev_fd_file(fname))
+ && !(IS_CHR_DEV(perm, fname))
// ... or a character special file named /dev/fd/<n>
-#endif
) {
if (S_ISDIR(perm)) {
if (!silent) {
filemess(curbuf, fname, _(msg_is_a_directory), 0);
}
+ retval = NOTDONE;
} else {
filemess(curbuf, fname, _("is not a file"), 0);
}
msg_end();
msg_scroll = msg_save;
- return S_ISDIR(perm) ? NOTDONE : FAIL;
+ goto theend;
}
}
@@ -461,7 +438,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (fd < 0) { // cannot open at all
msg_scroll = msg_save;
if (!newfile) {
- return FAIL;
+ goto theend;
}
if (perm == UV_ENOENT) { // check if the file exists
// Set the 'new-file' flag, so that when the file has
@@ -480,12 +457,12 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
|| (using_b_fname
&& (old_b_fname != curbuf->b_fname))) {
emsg(_(e_auchangedbuf));
- return FAIL;
+ goto theend;
}
}
if (!silent) {
if (dir_of_file_exists(fname)) {
- filemess(curbuf, sfname, new_file_message(), 0);
+ filemess(curbuf, sfname, _("[New]"), 0);
} else {
filemess(curbuf, sfname, _("[New DIRECTORY]"), 0);
}
@@ -502,23 +479,27 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
// remember the current fileformat
save_file_ff(curbuf);
- if (aborting()) { // autocmds may abort script processing
- return FAIL;
+ if (!aborting()) { // autocmds may abort script processing
+ retval = OK; // a new file is not an error
}
- return OK; // a new file is not an error
+ goto theend;
}
- filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]") :
#if defined(UNIX) && defined(EOVERFLOW)
+ filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]")
+ :
// libuv only returns -errno
// in Unix and in Windows
// open() does not set
// EOVERFLOW
- (fd == -EOVERFLOW) ? _("[File too big]") :
+ (fd == -EOVERFLOW) ? _("[File too big]")
+ : _("[Permission Denied]")), 0);
+#else
+ filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]")
+ : _("[Permission Denied]")), 0);
#endif
- _("[Permission Denied]")), 0);
curbuf->b_p_ro = true; // must use "w!" now
- return FAIL;
+ goto theend;
}
// Only set the 'ro' flag for readonly files the first time they are
@@ -553,13 +534,13 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (!read_buffer) {
close(fd);
}
- return FAIL;
+ goto theend;
}
#ifdef UNIX
// Set swap file protection bits after creating it.
if (swap_mode > 0 && curbuf->b_ml.ml_mfp != NULL
&& curbuf->b_ml.ml_mfp->mf_fname != NULL) {
- const char *swap_fname = (const char *)curbuf->b_ml.ml_mfp->mf_fname;
+ const char *swap_fname = curbuf->b_ml.ml_mfp->mf_fname;
// If the group-read bit is set but not the world-read bit, then
// the group must be equal to the group of the original file. If
@@ -588,7 +569,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (!read_buffer && !read_stdin) {
close(fd);
}
- return FAIL;
+ goto theend;
}
no_wait_return++; // don't wait for return yet
@@ -644,7 +625,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
no_wait_return--;
msg_scroll = msg_save;
curbuf->b_p_ro = true; // must use "w!" now
- return FAIL;
+ goto theend;
}
// Don't allow the autocommands to change the current buffer.
// Try to re-open the file.
@@ -663,7 +644,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
emsg(_("E201: *ReadPre autocommands must not change current buffer"));
}
curbuf->b_p_ro = true; // must use "w!" now
- return FAIL;
+ goto theend;
}
}
@@ -740,7 +721,7 @@ retry:
if (read_buffer) {
read_buf_lnum = 1;
read_buf_col = 0;
- } else if (read_stdin || vim_lseek(fd, (off_T)0L, SEEK_SET) != 0) {
+ } else if (read_stdin || vim_lseek(fd, 0, SEEK_SET) != 0) {
// Can't rewind the file, give up.
error = true;
goto failed;
@@ -902,9 +883,9 @@ retry:
// Use buffer >= 64K. Add linerest to double the size if the
// line gets very long, to avoid a lot of copying. But don't
// read more than 1 Mbyte at a time, so we can be interrupted.
- size = 0x10000L + linerest;
- if (size > 0x100000L) {
- size = 0x100000L;
+ size = 0x10000 + linerest;
+ if (size > 0x100000) {
+ size = 0x100000;
}
}
@@ -957,7 +938,7 @@ retry:
if (conv_restlen > 0) {
// Insert unconverted bytes from previous line.
- memmove(ptr, conv_rest, (size_t)conv_restlen); // -V614
+ memmove(ptr, conv_rest, (size_t)conv_restlen);
ptr += conv_restlen;
size -= conv_restlen;
}
@@ -968,14 +949,12 @@ retry:
if (read_buf_lnum > from) {
size = 0;
} else {
- int n, ni;
- long tlen;
-
- tlen = 0;
- for (;;) {
- p = (char_u *)ml_get(read_buf_lnum) + read_buf_col;
- n = (int)strlen((char *)p);
- if ((int)tlen + n + 1 > size) {
+ int ni;
+ int tlen = 0;
+ while (true) {
+ p = (uint8_t *)ml_get(read_buf_lnum) + read_buf_col;
+ int n = (int)strlen((char *)p);
+ if (tlen + n + 1 > size) {
// Filled up to "size", append partial line.
// Change NL to NUL to reverse the effect done
// below.
@@ -1015,7 +994,8 @@ retry:
}
} else {
// Read bytes from the file.
- size = read_eintr(fd, ptr, (size_t)size);
+ size_t read_size = (size_t)size;
+ size = read_eintr(fd, ptr, read_size);
}
if (size <= 0) {
@@ -1081,7 +1061,7 @@ retry:
if (size < 2 || curbuf->b_p_bin) {
ccname = NULL;
} else {
- ccname = check_for_bom(ptr, size, &blen,
+ ccname = check_for_bom(ptr, (int)size, &blen,
fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags(fenc));
}
if (ccname != NULL) {
@@ -1170,7 +1150,7 @@ retry:
}
if (fio_flags != 0) {
- unsigned int u8c;
+ unsigned u8c;
char *dest;
char *tail = NULL;
@@ -1349,7 +1329,6 @@ retry:
// Reading UTF-8: Check if the bytes are valid UTF-8.
for (p = (uint8_t *)ptr;; p++) {
int todo = (int)(((uint8_t *)ptr + size) - p);
- int l;
if (todo <= 0) {
break;
@@ -1359,7 +1338,7 @@ retry:
// an incomplete character at the end though, the next
// read() will get the next bytes, we'll check it
// then.
- l = utf_ptr2len_len((char *)p, todo);
+ int l = utf_ptr2len_len((char *)p, todo);
if (l > todo && !incomplete_tail) {
// Avoid retrying with a different encoding when
// a truncated file is more likely, or attempting
@@ -1554,7 +1533,7 @@ rewind_retry:
if (try_unix
&& !read_stdin
&& (read_buffer
- || vim_lseek(fd, (off_T)0L, SEEK_SET) == 0)) {
+ || vim_lseek(fd, 0, SEEK_SET) == 0)) {
fileformat = EOL_UNIX;
if (set_options) {
set_fileformat(EOL_UNIX, OPT_LOCAL);
@@ -1713,50 +1692,51 @@ failed:
}
msg_scroll = msg_save;
check_marks_read();
- return OK; // an interrupt isn't really an error
+ retval = OK; // an interrupt isn't really an error
+ goto theend;
}
if (!filtering && !(flags & READ_DUMMY) && !silent) {
- add_quoted_fname(IObuff, IOSIZE, curbuf, (const char *)sfname);
+ add_quoted_fname(IObuff, IOSIZE, curbuf, sfname);
c = false;
#ifdef UNIX
if (S_ISFIFO(perm)) { // fifo
- STRCAT(IObuff, _("[fifo]"));
+ xstrlcat(IObuff, _("[fifo]"), IOSIZE);
c = true;
}
if (S_ISSOCK(perm)) { // or socket
- STRCAT(IObuff, _("[socket]"));
+ xstrlcat(IObuff, _("[socket]"), IOSIZE);
c = true;
}
# ifdef OPEN_CHR_FILES
if (S_ISCHR(perm)) { // or character special
- STRCAT(IObuff, _("[character special]"));
+ xstrlcat(IObuff, _("[character special]"), IOSIZE);
c = true;
}
# endif
#endif
if (curbuf->b_p_ro) {
- STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"));
+ xstrlcat(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"), IOSIZE);
c = true;
}
if (read_no_eol_lnum) {
- msg_add_eol();
+ xstrlcat(IObuff, _("[noeol]"), IOSIZE);
c = true;
}
if (ff_error == EOL_DOS) {
- STRCAT(IObuff, _("[CR missing]"));
+ xstrlcat(IObuff, _("[CR missing]"), IOSIZE);
c = true;
}
if (split) {
- STRCAT(IObuff, _("[long lines split]"));
+ xstrlcat(IObuff, _("[long lines split]"), IOSIZE);
c = true;
}
if (notconverted) {
- STRCAT(IObuff, _("[NOT converted]"));
+ xstrlcat(IObuff, _("[NOT converted]"), IOSIZE);
c = true;
} else if (converted) {
- STRCAT(IObuff, _("[converted]"));
+ xstrlcat(IObuff, _("[converted]"), IOSIZE);
c = true;
}
if (conv_error != 0) {
@@ -1768,21 +1748,24 @@ failed:
_("[ILLEGAL BYTE in line %" PRId64 "]"), (int64_t)illegal_byte);
c = true;
} else if (error) {
- STRCAT(IObuff, _("[READ ERRORS]"));
+ xstrlcat(IObuff, _("[READ ERRORS]"), IOSIZE);
c = true;
}
if (msg_add_fileformat(fileformat)) {
c = true;
}
- msg_add_lines(c, (long)linecnt, filesize);
+ msg_add_lines(c, linecnt, filesize);
XFREE_CLEAR(keep_msg);
p = NULL;
msg_scrolled_ign = true;
if (!read_stdin && !read_buffer) {
- p = (char_u *)msg_trunc_attr(IObuff, false, 0);
+ if (msg_col > 0) {
+ msg_putchar('\r'); // overwrite previous message
+ }
+ p = (uint8_t *)msg_trunc(IObuff, false, 0);
}
if (read_stdin || read_buffer || restart_edit != 0
@@ -1804,7 +1787,7 @@ failed:
curbuf->b_p_ro = true;
}
- u_clearline(); // cannot use "U" command after adding lines
+ u_clearline(curbuf); // cannot use "U" command after adding lines
// In Ex mode: cursor at last new line.
// Otherwise: cursor at first new line.
@@ -1813,7 +1796,7 @@ failed:
} else {
curwin->w_cursor.lnum = from + 1;
}
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_WHITE | BL_FIX); // on first non-blank
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
@@ -1843,7 +1826,7 @@ failed:
// When opening a new file locate undo info and read it.
if (read_undo_file) {
- char_u hash[UNDO_HASH_SIZE];
+ uint8_t hash[UNDO_HASH_SIZE];
sha256_finish(&sha_ctx, hash);
u_read_undo(NULL, hash, fname);
@@ -1886,10 +1869,18 @@ failed:
}
}
- if (recoverymode && error) {
- return FAIL;
+ if (!(recoverymode && error)) {
+ retval = OK;
}
- return OK;
+
+theend:
+ if (curbuf->b_ml.ml_mfp != NULL
+ && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC) {
+ // OK to sync the swap file now
+ curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES;
+ }
+
+ return retval;
}
#ifdef OPEN_CHR_FILES
@@ -1919,11 +1910,8 @@ bool is_dev_fd_file(char *fname)
/// @param endp end of more bytes read
static linenr_T readfile_linenr(linenr_T linecnt, char *p, const char *endp)
{
- char *s;
- linenr_T lnum;
-
- lnum = curbuf->b_ml.ml_line_count - linecnt + 1;
- for (s = p; s < endp; s++) {
+ linenr_T lnum = curbuf->b_ml.ml_line_count - linecnt + 1;
+ for (char *s = p; s < endp; s++) {
if (*s == '\n') {
lnum++;
}
@@ -1991,7 +1979,6 @@ void set_forced_fenc(exarg_T *eap)
static char *next_fenc(char **pp, bool *alloced)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- char *p;
char *r;
*alloced = false;
@@ -1999,12 +1986,12 @@ static char *next_fenc(char **pp, bool *alloced)
*pp = NULL;
return "";
}
- p = vim_strchr(*pp, ',');
+ char *p = vim_strchr(*pp, ',');
if (p == NULL) {
r = enc_canonize(*pp);
*pp += strlen(*pp);
} else {
- r = xstrnsave(*pp, (size_t)(p - *pp));
+ r = xmemdupz(*pp, (size_t)(p - *pp));
*pp = p + 1;
p = enc_canonize(r);
xfree(r);
@@ -2026,10 +2013,9 @@ static char *next_fenc(char **pp, bool *alloced)
/// Returns NULL if the conversion failed ("*fdp" is not set) .
static char *readfile_charconvert(char *fname, char *fenc, int *fdp)
{
- char *tmpname;
char *errmsg = NULL;
- tmpname = vim_tempname();
+ char *tmpname = vim_tempname();
if (tmpname == NULL) {
errmsg = _("Can't find temp file for conversion");
} else {
@@ -2047,7 +2033,7 @@ static char *readfile_charconvert(char *fname, char *fenc, int *fdp)
if (errmsg != NULL) {
// Don't use emsg(), it breaks mappings, the retry with
// another type of conversion might still work.
- msg(errmsg);
+ msg(errmsg, 0);
if (tmpname != NULL) {
os_remove(tmpname); // delete converted file
XFREE_CLEAR(tmpname);
@@ -2076,1530 +2062,9 @@ static void check_marks_read(void)
curbuf->b_marks_read = true;
}
-char *new_file_message(void)
-{
- return shortmess(SHM_NEW) ? _("[New]") : _("[New File]");
-}
-
-/// buf_write() - write to file "fname" lines "start" through "end"
-///
-/// We do our own buffering here because fwrite() is so slow.
-///
-/// If "forceit" is true, we don't care for errors when attempting backups.
-/// In case of an error everything possible is done to restore the original
-/// file. But when "forceit" is true, we risk losing it.
-///
-/// When "reset_changed" is true and "append" == false and "start" == 1 and
-/// "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed.
-///
-/// This function must NOT use NameBuff (because it's called by autowrite()).
-///
-///
-/// @param eap for forced 'ff' and 'fenc', can be NULL!
-/// @param append append to the file
-///
-/// @return FAIL for failure, OK otherwise
-int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T end, exarg_T *eap,
- int append, int forceit, int reset_changed, int filtering)
-{
- int fd;
- char *backup = NULL;
- int backup_copy = false; // copy the original file?
- int dobackup;
- char *ffname;
- char *wfname = NULL; // name of file to write to
- char *s;
- char *ptr;
- char c;
- int len;
- linenr_T lnum;
- long nchars;
-#define SET_ERRMSG_NUM(num, msg) \
- errnum = (num), errmsg = (msg), errmsgarg = 0
-#define SET_ERRMSG_ARG(msg, error) \
- errnum = NULL, errmsg = (msg), errmsgarg = error
-#define SET_ERRMSG(msg) \
- errnum = NULL, errmsg = (msg), errmsgarg = 0
- const char *errnum = NULL;
- char *errmsg = NULL;
- int errmsgarg = 0;
- bool errmsg_allocated = false;
- char *buffer;
- char smallbuf[SMBUFSIZE];
- char *backup_ext;
- int bufsize;
- long perm; // file permissions
- int retval = OK;
- int newfile = false; // true if file doesn't exist yet
- int msg_save = msg_scroll;
- int overwriting; // true if writing over original
- int no_eol = false; // no end-of-line written
- int device = false; // writing to a device
- int prev_got_int = got_int;
- int checking_conversion;
- bool file_readonly = false; // overwritten file is read-only
- static char *err_readonly =
- "is read-only (cannot override: \"W\" in 'cpoptions')";
-#if defined(UNIX)
- int made_writable = false; // 'w' bit has been set
-#endif
- // writing everything
- int whole = (start == 1 && end == buf->b_ml.ml_line_count);
- linenr_T old_line_count = buf->b_ml.ml_line_count;
- int fileformat;
- int write_bin;
- struct bw_info write_info; // info for buf_write_bytes()
- int converted = false;
- int notconverted = false;
- char *fenc; // effective 'fileencoding'
- char *fenc_tofree = NULL; // allocated "fenc"
-#ifdef HAS_BW_FLAGS
- int wb_flags = 0;
-#endif
-#ifdef HAVE_ACL
- vim_acl_T acl = NULL; // ACL copied from original file to
- // backup or new file
-#endif
- int write_undo_file = false;
- context_sha256_T sha_ctx;
- unsigned int bkc = get_bkc_value(buf);
- const pos_T orig_start = buf->b_op_start;
- const pos_T orig_end = buf->b_op_end;
-
- if (fname == NULL || *fname == NUL) { // safety check
- return FAIL;
- }
- if (buf->b_ml.ml_mfp == NULL) {
- // This can happen during startup when there is a stray "w" in the
- // vimrc file.
- emsg(_(e_emptybuf));
- return FAIL;
- }
-
- // Disallow writing in secure mode.
- if (check_secure()) {
- return FAIL;
- }
-
- // Avoid a crash for a long name.
- if (strlen(fname) >= MAXPATHL) {
- emsg(_(e_longname));
- return FAIL;
- }
-
- // must init bw_conv_buf and bw_iconv_fd before jumping to "fail"
- write_info.bw_conv_buf = NULL;
- write_info.bw_conv_error = false;
- write_info.bw_conv_error_lnum = 0;
- write_info.bw_restlen = 0;
- write_info.bw_iconv_fd = (iconv_t)-1;
-
- // After writing a file changedtick changes but we don't want to display
- // the line.
- ex_no_reprint = true;
-
- // If there is no file name yet, use the one for the written file.
- // BF_NOTEDITED is set to reflect this (in case the write fails).
- // Don't do this when the write is for a filter command.
- // Don't do this when appending.
- // Only do this when 'cpoptions' contains the 'F' flag.
- if (buf->b_ffname == NULL
- && reset_changed
- && whole
- && buf == curbuf
- && !bt_nofilename(buf)
- && !filtering
- && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL)
- && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) {
- if (set_rw_fname(fname, sfname) == FAIL) {
- return FAIL;
- }
- buf = curbuf; // just in case autocmds made "buf" invalid
- }
-
- if (sfname == NULL) {
- sfname = fname;
- }
-
- // For Unix: Use the short file name whenever possible.
- // Avoids problems with networks and when directory names are changed.
- // Don't do this for Windows, a "cd" in a sub-shell may have moved us to
- // another directory, which we don't detect.
- ffname = fname; // remember full fname
-#ifdef UNIX
- fname = sfname;
-#endif
-
- if (buf->b_ffname != NULL && path_fnamecmp(ffname, buf->b_ffname) == 0) {
- overwriting = true;
- } else {
- overwriting = false;
- }
-
- no_wait_return++; // don't wait for return yet
-
- // Set '[ and '] marks to the lines to be written.
- buf->b_op_start.lnum = start;
- buf->b_op_start.col = 0;
- buf->b_op_end.lnum = end;
- buf->b_op_end.col = 0;
-
- {
- aco_save_T aco;
- int buf_ffname = false;
- int buf_sfname = false;
- int buf_fname_f = false;
- int buf_fname_s = false;
- int did_cmd = false;
- int nofile_err = false;
- int empty_memline = (buf->b_ml.ml_mfp == NULL);
- bufref_T bufref;
-
- // Apply PRE autocommands.
- // Set curbuf to the buffer to be written.
- // Careful: The autocommands may call buf_write() recursively!
- if (ffname == buf->b_ffname) {
- buf_ffname = true;
- }
- if (sfname == buf->b_sfname) {
- buf_sfname = true;
- }
- if (fname == buf->b_ffname) {
- buf_fname_f = true;
- }
- if (fname == buf->b_sfname) {
- buf_fname_s = true;
- }
-
- // Set curwin/curbuf to buf and save a few things.
- aucmd_prepbuf(&aco, buf);
- set_bufref(&bufref, buf);
-
- if (append) {
- if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD,
- sfname, sfname, false, curbuf, eap))) {
- if (overwriting && bt_nofilename(curbuf)) {
- nofile_err = true;
- } else {
- apply_autocmds_exarg(EVENT_FILEAPPENDPRE,
- sfname, sfname, false, curbuf, eap);
- }
- }
- } else if (filtering) {
- apply_autocmds_exarg(EVENT_FILTERWRITEPRE,
- NULL, sfname, false, curbuf, eap);
- } else if (reset_changed && whole) {
- int was_changed = curbufIsChanged();
-
- did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD,
- sfname, sfname, false, curbuf, eap);
- if (did_cmd) {
- if (was_changed && !curbufIsChanged()) {
- // Written everything correctly and BufWriteCmd has reset
- // 'modified': Correct the undo information so that an
- // undo now sets 'modified'.
- u_unchanged(curbuf);
- u_update_save_nr(curbuf);
- }
- } else {
- if (overwriting && bt_nofilename(curbuf)) {
- nofile_err = true;
- } else {
- apply_autocmds_exarg(EVENT_BUFWRITEPRE,
- sfname, sfname, false, curbuf, eap);
- }
- }
- } else {
- if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD,
- sfname, sfname, false, curbuf, eap))) {
- if (overwriting && bt_nofilename(curbuf)) {
- nofile_err = true;
- } else {
- apply_autocmds_exarg(EVENT_FILEWRITEPRE,
- sfname, sfname, false, curbuf, eap);
- }
- }
- }
-
- // restore curwin/curbuf and a few other things
- aucmd_restbuf(&aco);
-
- // In three situations we return here and don't write the file:
- // 1. the autocommands deleted or unloaded the buffer.
- // 2. The autocommands abort script processing.
- // 3. If one of the "Cmd" autocommands was executed.
- if (!bufref_valid(&bufref)) {
- buf = NULL;
- }
- if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
- || did_cmd || nofile_err
- || aborting()) {
- if (buf != NULL && (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
- // restore the original '[ and '] positions
- buf->b_op_start = orig_start;
- buf->b_op_end = orig_end;
- }
-
- no_wait_return--;
- msg_scroll = msg_save;
- if (nofile_err) {
- semsg(_(e_no_matching_autocommands_for_buftype_str_buffer), curbuf->b_p_bt);
- }
-
- if (nofile_err
- || aborting()) {
- // An aborting error, interrupt or exception in the
- // autocommands.
- return FAIL;
- }
- if (did_cmd) {
- if (buf == NULL) {
- // The buffer was deleted. We assume it was written
- // (can't retry anyway).
- return OK;
- }
- if (overwriting) {
- // Assume the buffer was written, update the timestamp.
- ml_timestamp(buf);
- if (append) {
- buf->b_flags &= ~BF_NEW;
- } else {
- buf->b_flags &= ~BF_WRITE_MASK;
- }
- }
- if (reset_changed && buf->b_changed && !append
- && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
- // Buffer still changed, the autocommands didn't work properly.
- return FAIL;
- }
- return OK;
- }
- if (!aborting()) {
- emsg(_("E203: Autocommands deleted or unloaded buffer to be written"));
- }
- return FAIL;
- }
-
- // The autocommands may have changed the number of lines in the file.
- // When writing the whole file, adjust the end.
- // When writing part of the file, assume that the autocommands only
- // changed the number of lines that are to be written (tricky!).
- if (buf->b_ml.ml_line_count != old_line_count) {
- if (whole) { // write all
- end = buf->b_ml.ml_line_count;
- } else if (buf->b_ml.ml_line_count > old_line_count) { // more lines
- end += buf->b_ml.ml_line_count - old_line_count;
- } else { // less lines
- end -= old_line_count - buf->b_ml.ml_line_count;
- if (end < start) {
- no_wait_return--;
- msg_scroll = msg_save;
- emsg(_("E204: Autocommand changed number of lines in unexpected way"));
- return FAIL;
- }
- }
- }
-
- // The autocommands may have changed the name of the buffer, which may
- // be kept in fname, ffname and sfname.
- if (buf_ffname) {
- ffname = buf->b_ffname;
- }
- if (buf_sfname) {
- sfname = buf->b_sfname;
- }
- if (buf_fname_f) {
- fname = buf->b_ffname;
- }
- if (buf_fname_s) {
- fname = buf->b_sfname;
- }
- }
-
- if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
- // restore the original '[ and '] positions
- buf->b_op_start = orig_start;
- buf->b_op_end = orig_end;
- }
-
- if (shortmess(SHM_OVER) && !exiting) {
- msg_scroll = false; // overwrite previous file message
- } else {
- msg_scroll = true; // don't overwrite previous file message
- }
- if (!filtering) {
- filemess(buf,
-#ifndef UNIX
- sfname,
-#else
- fname,
-#endif
- "", 0); // show that we are busy
- }
- msg_scroll = false; // always overwrite the file message now
-
- buffer = verbose_try_malloc(BUFSIZE);
- // can't allocate big buffer, use small one (to be able to write when out of
- // memory)
- if (buffer == NULL) {
- buffer = smallbuf;
- bufsize = SMBUFSIZE;
- } else {
- bufsize = BUFSIZE;
- }
-
- // Get information about original file (if there is one).
- FileInfo file_info_old;
-#if defined(UNIX)
- perm = -1;
- if (!os_fileinfo(fname, &file_info_old)) {
- newfile = true;
- } else {
- perm = (long)file_info_old.stat.st_mode;
- if (!S_ISREG(file_info_old.stat.st_mode)) { // not a file
- if (S_ISDIR(file_info_old.stat.st_mode)) {
- SET_ERRMSG_NUM("E502", _("is a directory"));
- goto fail;
- }
- if (os_nodetype(fname) != NODE_WRITABLE) {
- SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
- goto fail;
- }
- // It's a device of some kind (or a fifo) which we can write to
- // but for which we can't make a backup.
- device = true;
- newfile = true;
- perm = -1;
- }
- }
-#else // win32
- // Check for a writable device name.
- c = fname == NULL ? NODE_OTHER : os_nodetype(fname);
- if (c == NODE_OTHER) {
- SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
- goto fail;
- }
- if (c == NODE_WRITABLE) {
- device = true;
- newfile = true;
- perm = -1;
- } else {
- perm = os_getperm((const char *)fname);
- if (perm < 0) {
- newfile = true;
- } else if (os_isdir(fname)) {
- SET_ERRMSG_NUM("E502", _("is a directory"));
- goto fail;
- }
- if (overwriting) {
- os_fileinfo(fname, &file_info_old);
- }
- }
-#endif // !UNIX
-
- if (!device && !newfile) {
- // Check if the file is really writable (when renaming the file to
- // make a backup we won't discover it later).
- file_readonly = !os_file_is_writable(fname);
-
- if (!forceit && file_readonly) {
- if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
- SET_ERRMSG_NUM("E504", _(err_readonly));
- } else {
- SET_ERRMSG_NUM("E505", _("is read-only (add ! to override)"));
- }
- goto fail;
- }
-
- // If 'forceit' is false, check if the timestamp hasn't changed since reading the file.
- if (overwriting && !forceit) {
- retval = check_mtime(buf, &file_info_old);
- if (retval == FAIL) {
- goto fail;
- }
- }
- }
-
-#ifdef HAVE_ACL
- // For systems that support ACL: get the ACL from the original file.
- if (!newfile) {
- acl = os_get_acl(fname);
- }
-#endif
-
- // If 'backupskip' is not empty, don't make a backup for some files.
- dobackup = (p_wb || p_bk || *p_pm != NUL);
- if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) {
- dobackup = false;
- }
-
- // Save the value of got_int and reset it. We don't want a previous
- // interruption cancel writing, only hitting CTRL-C while writing should
- // abort it.
- prev_got_int = got_int;
- got_int = false;
-
- // Mark the buffer as 'being saved' to prevent changed buffer warnings
- buf->b_saving = true;
-
- // If we are not appending or filtering, the file exists, and the
- // 'writebackup', 'backup' or 'patchmode' option is set, need a backup.
- // When 'patchmode' is set also make a backup when appending.
- //
- // Do not make any backup, if 'writebackup' and 'backup' are both switched
- // off. This helps when editing large files on almost-full disks.
- if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) {
- FileInfo file_info;
- const bool no_prepend_dot = false;
-
- if ((bkc & BKC_YES) || append) { // "yes"
- backup_copy = true;
- } else if ((bkc & BKC_AUTO)) { // "auto"
- int i;
-
- // Don't rename the file when:
- // - it's a hard link
- // - it's a symbolic link
- // - we don't have write permission in the directory
- if (os_fileinfo_hardlinks(&file_info_old) > 1
- || !os_fileinfo_link(fname, &file_info)
- || !os_fileinfo_id_equal(&file_info, &file_info_old)) {
- backup_copy = true;
- } else {
- // Check if we can create a file and set the owner/group to
- // the ones from the original file.
- // First find a file name that doesn't exist yet (use some
- // arbitrary numbers).
- STRCPY(IObuff, fname);
- for (i = 4913;; i += 123) {
- char *tail = path_tail(IObuff);
- size_t size = (size_t)(tail - IObuff);
- snprintf(tail, IOSIZE - size, "%d", i);
- if (!os_fileinfo_link(IObuff, &file_info)) {
- break;
- }
- }
- fd = os_open(IObuff,
- O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, (int)perm);
- if (fd < 0) { // can't write in directory
- backup_copy = true;
- } else {
-#ifdef UNIX
- os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid);
- if (!os_fileinfo(IObuff, &file_info)
- || file_info.stat.st_uid != file_info_old.stat.st_uid
- || file_info.stat.st_gid != file_info_old.stat.st_gid
- || (long)file_info.stat.st_mode != perm) {
- backup_copy = true;
- }
-#endif
- // Close the file before removing it, on MS-Windows we
- // can't delete an open file.
- close(fd);
- os_remove(IObuff);
- }
- }
- }
-
- // Break symlinks and/or hardlinks if we've been asked to.
- if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) {
-#ifdef UNIX
- bool file_info_link_ok = os_fileinfo_link(fname, &file_info);
-
- // Symlinks.
- if ((bkc & BKC_BREAKSYMLINK)
- && file_info_link_ok
- && !os_fileinfo_id_equal(&file_info, &file_info_old)) {
- backup_copy = false;
- }
-
- // Hardlinks.
- if ((bkc & BKC_BREAKHARDLINK)
- && os_fileinfo_hardlinks(&file_info_old) > 1
- && (!file_info_link_ok
- || os_fileinfo_id_equal(&file_info, &file_info_old))) {
- backup_copy = false;
- }
-#endif
- }
-
- // make sure we have a valid backup extension to use
- if (*p_bex == NUL) {
- backup_ext = ".bak";
- } else {
- backup_ext = p_bex;
- }
-
- if (backup_copy) {
- char *wp;
- int some_error = false;
- char *dirp;
- char *rootname;
- char *p;
-
- // Try to make the backup in each directory in the 'bdir' option.
- //
- // Unix semantics has it, that we may have a writable file,
- // that cannot be recreated with a simple open(..., O_CREAT, ) e.g:
- // - the directory is not writable,
- // - the file may be a symbolic link,
- // - the file may belong to another user/group, etc.
- //
- // For these reasons, the existing writable file must be truncated
- // and reused. Creation of a backup COPY will be attempted.
- dirp = p_bdir;
- while (*dirp) {
- // Isolate one directory name, using an entry in 'bdir'.
- size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
- p = IObuff + dir_len;
- bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
- if (trailing_pathseps) {
- IObuff[dir_len - 2] = NUL;
- }
- if (*dirp == NUL && !os_isdir(IObuff)) {
- int ret;
- char *failed_dir;
- if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
- semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
- failed_dir, os_strerror(ret));
- xfree(failed_dir);
- }
- }
- if (trailing_pathseps) {
- // Ends with '//', Use Full path
- if ((p = make_percent_swname(IObuff, fname))
- != NULL) {
- backup = modname(p, backup_ext, no_prepend_dot);
- xfree(p);
- }
- }
-
- rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL) {
- some_error = true; // out of memory
- goto nobackup;
- }
-
- FileInfo file_info_new;
- {
- //
- // Make the backup file name.
- //
- if (backup == NULL) {
- backup = modname(rootname, backup_ext, no_prepend_dot);
- }
-
- if (backup == NULL) {
- xfree(rootname);
- some_error = true; // out of memory
- goto nobackup;
- }
-
- // Check if backup file already exists.
- if (os_fileinfo(backup, &file_info_new)) {
- if (os_fileinfo_id_equal(&file_info_new, &file_info_old)) {
- //
- // Backup file is same as original file.
- // May happen when modname() gave the same file back (e.g. silly
- // link). If we don't check here, we either ruin the file when
- // copying or erase it after writing.
- //
- XFREE_CLEAR(backup); // no backup file to delete
- } else if (!p_bk) {
- // We are not going to keep the backup file, so don't
- // delete an existing one, and try to use another name instead.
- // Change one character, just before the extension.
- //
- wp = backup + strlen(backup) - 1 - strlen(backup_ext);
- if (wp < backup) { // empty file name ???
- wp = backup;
- }
- *wp = 'z';
- while (*wp > 'a' && os_fileinfo(backup, &file_info_new)) {
- (*wp)--;
- }
- // They all exist??? Must be something wrong.
- if (*wp == 'a') {
- XFREE_CLEAR(backup);
- }
- }
- }
- }
- xfree(rootname);
-
- // Try to create the backup file
- if (backup != NULL) {
- // remove old backup, if present
- os_remove(backup);
-
- // set file protection same as original file, but
- // strip s-bit.
- (void)os_setperm((const char *)backup, perm & 0777);
-
-#ifdef UNIX
- //
- // Try to set the group of the backup same as the original file. If
- // this fails, set the protection bits for the group same as the
- // protection bits for others.
- //
- if (file_info_new.stat.st_gid != file_info_old.stat.st_gid
- && os_chown(backup, (uv_uid_t)-1, (uv_gid_t)file_info_old.stat.st_gid) != 0) {
- os_setperm((const char *)backup,
- ((int)perm & 0707) | (((int)perm & 07) << 3));
- }
-#endif
-
- // copy the file
- if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) != 0) {
- SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)"));
- XFREE_CLEAR(backup);
- backup = NULL;
- continue;
- }
-
-#ifdef UNIX
- os_file_settime(backup,
- (double)file_info_old.stat.st_atim.tv_sec,
- (double)file_info_old.stat.st_mtim.tv_sec);
-#endif
-#ifdef HAVE_ACL
- os_set_acl(backup, acl);
-#endif
- SET_ERRMSG(NULL);
- break;
- }
- }
-
-nobackup:
- if (backup == NULL && errmsg == NULL) {
- SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)"));
- }
- // Ignore errors when forceit is true.
- if ((some_error || errmsg != NULL) && !forceit) {
- retval = FAIL;
- goto fail;
- }
- SET_ERRMSG(NULL);
- } else {
- char *dirp;
- char *p;
- char *rootname;
-
- // Make a backup by renaming the original file.
-
- // If 'cpoptions' includes the "W" flag, we don't want to
- // overwrite a read-only file. But rename may be possible
- // anyway, thus we need an extra check here.
- if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
- SET_ERRMSG_NUM("E504", _(err_readonly));
- goto fail;
- }
-
- // Form the backup file name - change path/fo.o.h to
- // path/fo.o.h.bak Try all directories in 'backupdir', first one
- // that works is used.
- dirp = p_bdir;
- while (*dirp) {
- // Isolate one directory name and make the backup file name.
- size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
- p = IObuff + dir_len;
- bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
- if (trailing_pathseps) {
- IObuff[dir_len - 2] = NUL;
- }
- if (*dirp == NUL && !os_isdir(IObuff)) {
- int ret;
- char *failed_dir;
- if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
- semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
- failed_dir, os_strerror(ret));
- xfree(failed_dir);
- }
- }
- if (trailing_pathseps) {
- // path ends with '//', use full path
- if ((p = make_percent_swname(IObuff, fname))
- != NULL) {
- backup = modname(p, backup_ext, no_prepend_dot);
- xfree(p);
- }
- }
-
- if (backup == NULL) {
- rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL) {
- backup = NULL;
- } else {
- backup = modname(rootname, backup_ext, no_prepend_dot);
- xfree(rootname);
- }
- }
-
- if (backup != NULL) {
- // If we are not going to keep the backup file, don't
- // delete an existing one, try to use another name.
- // Change one character, just before the extension.
- if (!p_bk && os_path_exists(backup)) {
- p = backup + strlen(backup) - 1 - strlen(backup_ext);
- if (p < backup) { // empty file name ???
- p = backup;
- }
- *p = 'z';
- while (*p > 'a' && os_path_exists(backup)) {
- (*p)--;
- }
- // They all exist??? Must be something wrong!
- if (*p == 'a') {
- XFREE_CLEAR(backup);
- }
- }
- }
- if (backup != NULL) {
- // Delete any existing backup and move the current version
- // to the backup. For safety, we don't remove the backup
- // until the write has finished successfully. And if the
- // 'backup' option is set, leave it around.
-
- // If the renaming of the original file to the backup file
- // works, quit here.
- ///
- if (vim_rename(fname, backup) == 0) {
- break;
- }
-
- XFREE_CLEAR(backup); // don't do the rename below
- }
- }
- if (backup == NULL && !forceit) {
- SET_ERRMSG(_("E510: Can't make backup file (add ! to override)"));
- goto fail;
- }
- }
- }
-
-#if defined(UNIX)
- // When using ":w!" and the file was read-only: make it writable
- if (forceit && perm >= 0 && !(perm & 0200)
- && file_info_old.stat.st_uid == getuid()
- && vim_strchr(p_cpo, CPO_FWRITE) == NULL) {
- perm |= 0200;
- (void)os_setperm((const char *)fname, (int)perm);
- made_writable = true;
- }
-#endif
-
- // When using ":w!" and writing to the current file, 'readonly' makes no
- // sense, reset it, unless 'Z' appears in 'cpoptions'.
- if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) {
- buf->b_p_ro = false;
- need_maketitle = true; // set window title later
- status_redraw_all(); // redraw status lines later
- }
-
- if (end > buf->b_ml.ml_line_count) {
- end = buf->b_ml.ml_line_count;
- }
- if (buf->b_ml.ml_flags & ML_EMPTY) {
- start = end + 1;
- }
-
- // If the original file is being overwritten, there is a small chance that
- // we crash in the middle of writing. Therefore the file is preserved now.
- // This makes all block numbers positive so that recovery does not need
- // the original file.
- // Don't do this if there is a backup file and we are exiting.
- if (reset_changed && !newfile && overwriting
- && !(exiting && backup != NULL)) {
- ml_preserve(buf, false, !!p_fs);
- if (got_int) {
- SET_ERRMSG(_(e_interr));
- goto restore_backup;
- }
- }
-
- // Default: write the file directly. May write to a temp file for
- // multi-byte conversion.
- wfname = fname;
-
- // Check for forced 'fileencoding' from "++opt=val" argument.
- if (eap != NULL && eap->force_enc != 0) {
- fenc = eap->cmd + eap->force_enc;
- fenc = enc_canonize(fenc);
- fenc_tofree = fenc;
- } else {
- fenc = buf->b_p_fenc;
- }
-
- // Check if the file needs to be converted.
- converted = need_conversion(fenc);
-
- // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or
- // Latin1 to Unicode conversion. This is handled in buf_write_bytes().
- // Prepare the flags for it and allocate bw_conv_buf when needed.
- if (converted) {
- wb_flags = get_fio_flags(fenc);
- if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) {
- // Need to allocate a buffer to translate into.
- if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) {
- write_info.bw_conv_buflen = (size_t)bufsize * 2;
- } else { // FIO_UCS4
- write_info.bw_conv_buflen = (size_t)bufsize * 4;
- }
- write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
- if (!write_info.bw_conv_buf) {
- end = 0;
- }
- }
- }
-
- if (converted && wb_flags == 0) {
- // Use iconv() conversion when conversion is needed and it's not done
- // internally.
- write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, "utf-8");
- if (write_info.bw_iconv_fd != (iconv_t)-1) {
- // We're going to use iconv(), allocate a buffer to convert in.
- write_info.bw_conv_buflen = (size_t)bufsize * ICONV_MULT;
- write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
- if (!write_info.bw_conv_buf) {
- end = 0;
- }
- write_info.bw_first = true;
- } else {
- // When the file needs to be converted with 'charconvert' after
- // writing, write to a temp file instead and let the conversion
- // overwrite the original file.
- if (*p_ccv != NUL) {
- wfname = vim_tempname();
- if (wfname == NULL) { // Can't write without a tempfile!
- SET_ERRMSG(_("E214: Can't find temp file for writing"));
- goto restore_backup;
- }
- }
- }
- }
-
- if (converted && wb_flags == 0
- && write_info.bw_iconv_fd == (iconv_t)-1
- && wfname == fname) {
- if (!forceit) {
- SET_ERRMSG(_("E213: Cannot convert (add ! to write without conversion)"));
- goto restore_backup;
- }
- notconverted = true;
- }
-
- // If conversion is taking place, we may first pretend to write and check
- // for conversion errors. Then loop again to write for real.
- // When not doing conversion this writes for real right away.
- for (checking_conversion = true;; checking_conversion = false) {
- // There is no need to check conversion when:
- // - there is no conversion
- // - we make a backup file, that can be restored in case of conversion
- // failure.
- if (!converted || dobackup) {
- checking_conversion = false;
- }
-
- if (checking_conversion) {
- // Make sure we don't write anything.
- fd = -1;
- write_info.bw_fd = fd;
- } else {
- // Open the file "wfname" for writing.
- // We may try to open the file twice: If we can't write to the file
- // and forceit is true we delete the existing file and try to
- // create a new one. If this still fails we may have lost the
- // original file! (this may happen when the user reached his
- // quotum for number of files).
- // Appending will fail if the file does not exist and forceit is
- // false.
- while ((fd = os_open(wfname,
- O_WRONLY |
- (append
- ? (forceit
- ? (O_APPEND | O_CREAT)
- : O_APPEND)
- : (O_CREAT | O_TRUNC)),
- perm < 0 ? 0666 : (perm & 0777))) < 0) {
- // A forced write will try to create a new file if the old one
- // is still readonly. This may also happen when the directory
- // is read-only. In that case the mch_remove() will fail.
- if (errmsg == NULL) {
-#ifdef UNIX
- FileInfo file_info;
-
- // Don't delete the file when it's a hard or symbolic link.
- if ((!newfile && os_fileinfo_hardlinks(&file_info_old) > 1)
- || (os_fileinfo_link(fname, &file_info)
- && !os_fileinfo_id_equal(&file_info, &file_info_old))) {
- SET_ERRMSG(_("E166: Can't open linked file for writing"));
- } else {
-#endif
- SET_ERRMSG_ARG(_("E212: Can't open file for writing: %s"), fd);
- if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL
- && perm >= 0) {
-#ifdef UNIX
- // we write to the file, thus it should be marked
- // writable after all
- if (!(perm & 0200)) {
- made_writable = true;
- }
- perm |= 0200;
- if (file_info_old.stat.st_uid != getuid()
- || file_info_old.stat.st_gid != getgid()) {
- perm &= 0777;
- }
-#endif
- if (!append) { // don't remove when appending
- os_remove(wfname);
- }
- continue;
- }
-#ifdef UNIX
- }
-#endif
- }
-
-restore_backup:
- {
- // If we failed to open the file, we don't need a backup. Throw it
- // away. If we moved or removed the original file try to put the
- // backup in its place.
- if (backup != NULL && wfname == fname) {
- if (backup_copy) {
- // There is a small chance that we removed the original,
- // try to move the copy in its place.
- // This may not work if the vim_rename() fails.
- // In that case we leave the copy around.
- // If file does not exist, put the copy in its place
- if (!os_path_exists(fname)) {
- vim_rename(backup, fname);
- }
- // if original file does exist throw away the copy
- if (os_path_exists(fname)) {
- os_remove(backup);
- }
- } else {
- // try to put the original file back
- vim_rename(backup, fname);
- }
- }
-
- // if original file no longer exists give an extra warning
- if (!newfile && !os_path_exists(fname)) {
- end = 0;
- }
- }
-
- if (wfname != fname) {
- xfree(wfname);
- }
- goto fail;
- }
- write_info.bw_fd = fd;
- }
- SET_ERRMSG(NULL);
-
- write_info.bw_buf = buffer;
- nchars = 0;
-
- // use "++bin", "++nobin" or 'binary'
- if (eap != NULL && eap->force_bin != 0) {
- write_bin = (eap->force_bin == FORCE_BIN);
- } else {
- write_bin = buf->b_p_bin;
- }
-
- // Skip the BOM when appending and the file already existed, the BOM
- // only makes sense at the start of the file.
- if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) {
- write_info.bw_len = make_bom((char_u *)buffer, fenc);
- if (write_info.bw_len > 0) {
- // don't convert
- write_info.bw_flags = FIO_NOCONVERT | wb_flags;
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0;
- } else {
- nchars += write_info.bw_len;
- }
- }
- }
- write_info.bw_start_lnum = start;
-
- write_undo_file = (buf->b_p_udf && overwriting && !append
- && !filtering && reset_changed && !checking_conversion);
- if (write_undo_file) {
- // Prepare for computing the hash value of the text.
- sha256_start(&sha_ctx);
- }
-
- write_info.bw_len = bufsize;
-#ifdef HAS_BW_FLAGS
- write_info.bw_flags = wb_flags;
-#endif
- fileformat = get_fileformat_force(buf, eap);
- s = buffer;
- len = 0;
- for (lnum = start; lnum <= end; lnum++) {
- // The next while loop is done once for each character written.
- // Keep it fast!
- ptr = ml_get_buf(buf, lnum, false) - 1;
- if (write_undo_file) {
- sha256_update(&sha_ctx, (uint8_t *)ptr + 1, (uint32_t)(strlen(ptr + 1) + 1));
- }
- while ((c = *++ptr) != NUL) {
- if (c == NL) {
- *s = NUL; // replace newlines with NULs
- } else if (c == CAR && fileformat == EOL_MAC) {
- *s = NL; // Mac: replace CRs with NLs
- } else {
- *s = c;
- }
- s++;
- if (++len != bufsize) {
- continue;
- }
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // write error: break loop
- break;
- }
- nchars += bufsize;
- s = buffer;
- len = 0;
- write_info.bw_start_lnum = lnum;
- }
- // write failed or last line has no EOL: stop here
- if (end == 0
- || (lnum == end
- && (write_bin || !buf->b_p_fixeol)
- && ((write_bin && lnum == buf->b_no_eol_lnum)
- || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
- lnum++; // written the line, count it
- no_eol = true;
- break;
- }
- if (fileformat == EOL_UNIX) {
- *s++ = NL;
- } else {
- *s++ = CAR; // EOL_MAC or EOL_DOS: write CR
- if (fileformat == EOL_DOS) { // write CR-NL
- if (++len == bufsize) {
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // write error: break loop
- break;
- }
- nchars += bufsize;
- s = buffer;
- len = 0;
- }
- *s++ = NL;
- }
- }
- if (++len == bufsize) {
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // Write error: break loop.
- break;
- }
- nchars += bufsize;
- s = buffer;
- len = 0;
-
- os_breakcheck();
- if (got_int) {
- end = 0; // Interrupted, break loop.
- break;
- }
- }
- }
- if (len > 0 && end > 0) {
- write_info.bw_len = len;
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // write error
- }
- nchars += len;
- }
-
- if (!buf->b_p_fixeol && buf->b_p_eof) {
- // write trailing CTRL-Z
- (void)write_eintr(write_info.bw_fd, "\x1a", 1);
- }
-
- // Stop when writing done or an error was encountered.
- if (!checking_conversion || end == 0) {
- break;
- }
-
- // If no error happened until now, writing should be ok, so loop to
- // really write the buffer.
- }
-
- // If we started writing, finish writing. Also when an error was
- // encountered.
- if (!checking_conversion) {
- // On many journalling file systems there is a bug that causes both the
- // original and the backup file to be lost when halting the system right
- // after writing the file. That's because only the meta-data is
- // journalled. Syncing the file slows down the system, but assures it has
- // been written to disk and we don't lose it.
- // For a device do try the fsync() but don't complain if it does not work
- // (could be a pipe).
- // If the 'fsync' option is false, don't fsync(). Useful for laptops.
- int error;
- if (p_fs && (error = os_fsync(fd)) != 0 && !device
- // fsync not supported on this storage.
- && error != UV_ENOTSUP) {
- SET_ERRMSG_ARG(e_fsync, error);
- end = 0;
- }
-
-#ifdef UNIX
- // When creating a new file, set its owner/group to that of the original
- // file. Get the new device and inode number.
- if (backup != NULL && !backup_copy) {
- // don't change the owner when it's already OK, some systems remove
- // permission or ACL stuff
- FileInfo file_info;
- if (!os_fileinfo(wfname, &file_info)
- || file_info.stat.st_uid != file_info_old.stat.st_uid
- || file_info.stat.st_gid != file_info_old.stat.st_gid) {
- os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid);
- if (perm >= 0) { // Set permission again, may have changed.
- (void)os_setperm(wfname, (int)perm);
- }
- }
- buf_set_file_id(buf);
- } else if (!buf->file_id_valid) {
- // Set the file_id when creating a new file.
- buf_set_file_id(buf);
- }
-#endif
-
- if ((error = os_close(fd)) != 0) {
- SET_ERRMSG_ARG(_("E512: Close failed: %s"), error);
- end = 0;
- }
-
-#ifdef UNIX
- if (made_writable) {
- perm &= ~0200; // reset 'w' bit for security reasons
- }
-#endif
- if (perm >= 0) { // Set perm. of new file same as old file.
- (void)os_setperm((const char *)wfname, (int)perm);
- }
-#ifdef HAVE_ACL
- // Probably need to set the ACL before changing the user (can't set the
- // ACL on a file the user doesn't own).
- if (!backup_copy) {
- os_set_acl(wfname, acl);
- }
-#endif
-
- if (wfname != fname) {
- // The file was written to a temp file, now it needs to be converted
- // with 'charconvert' to (overwrite) the output file.
- if (end != 0) {
- if (eval_charconvert("utf-8", fenc, wfname, fname) == FAIL) {
- write_info.bw_conv_error = true;
- end = 0;
- }
- }
- os_remove(wfname);
- xfree(wfname);
- }
- }
-
- if (end == 0) {
- // Error encountered.
- if (errmsg == NULL) {
- if (write_info.bw_conv_error) {
- if (write_info.bw_conv_error_lnum == 0) {
- SET_ERRMSG(_("E513: write error, conversion failed "
- "(make 'fenc' empty to override)"));
- } else {
- errmsg_allocated = true;
- SET_ERRMSG(xmalloc(300));
- vim_snprintf(errmsg, 300, // NOLINT(runtime/printf)
- _("E513: write error, conversion failed in line %" PRIdLINENR
- " (make 'fenc' empty to override)"),
- write_info.bw_conv_error_lnum);
- }
- } else if (got_int) {
- SET_ERRMSG(_(e_interr));
- } else {
- SET_ERRMSG(_("E514: write error (file system full?)"));
- }
- }
-
- // If we have a backup file, try to put it in place of the new file,
- // because the new file is probably corrupt. This avoids losing the
- // original file when trying to make a backup when writing the file a
- // second time.
- // When "backup_copy" is set we need to copy the backup over the new
- // file. Otherwise rename the backup file.
- // If this is OK, don't give the extra warning message.
- if (backup != NULL) {
- if (backup_copy) {
- // This may take a while, if we were interrupted let the user
- // know we got the message.
- if (got_int) {
- msg(_(e_interr));
- ui_flush();
- }
-
- // copy the file.
- if (os_copy(backup, fname, UV_FS_COPYFILE_FICLONE)
- == 0) {
- end = 1; // success
- }
- } else {
- if (vim_rename(backup, fname) == 0) {
- end = 1;
- }
- }
- }
- goto fail;
- }
-
- lnum -= start; // compute number of written lines
- no_wait_return--; // may wait for return now
-
-#if !defined(UNIX)
- fname = sfname; // use shortname now, for the messages
-#endif
- if (!filtering) {
- add_quoted_fname(IObuff, IOSIZE, buf, (const char *)fname);
- c = false;
- if (write_info.bw_conv_error) {
- STRCAT(IObuff, _(" CONVERSION ERROR"));
- c = true;
- if (write_info.bw_conv_error_lnum != 0) {
- vim_snprintf_add(IObuff, IOSIZE, _(" in line %" PRId64 ";"),
- (int64_t)write_info.bw_conv_error_lnum);
- }
- } else if (notconverted) {
- STRCAT(IObuff, _("[NOT converted]"));
- c = true;
- } else if (converted) {
- STRCAT(IObuff, _("[converted]"));
- c = true;
- }
- if (device) {
- STRCAT(IObuff, _("[Device]"));
- c = true;
- } else if (newfile) {
- STRCAT(IObuff, new_file_message());
- c = true;
- }
- if (no_eol) {
- msg_add_eol();
- c = true;
- }
- // may add [unix/dos/mac]
- if (msg_add_fileformat(fileformat)) {
- c = true;
- }
- msg_add_lines(c, (long)lnum, nchars); // add line/char count
- if (!shortmess(SHM_WRITE)) {
- if (append) {
- STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended"));
- } else {
- STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"));
- }
- }
-
- set_keep_msg(msg_trunc_attr(IObuff, false, 0), 0);
- }
-
- // When written everything correctly: reset 'modified'. Unless not
- // writing to the original file and '+' is not in 'cpoptions'.
- if (reset_changed && whole && !append
- && !write_info.bw_conv_error
- && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
- unchanged(buf, true, false);
- const varnumber_T changedtick = buf_get_changedtick(buf);
- if (buf->b_last_changedtick + 1 == changedtick) {
- // b:changedtick may be incremented in unchanged() but that
- // should not trigger a TextChanged event.
- buf->b_last_changedtick = changedtick;
- }
- u_unchanged(buf);
- u_update_save_nr(buf);
- }
-
- // If written to the current file, update the timestamp of the swap file
- // and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime.
- if (overwriting) {
- ml_timestamp(buf);
- if (append) {
- buf->b_flags &= ~BF_NEW;
- } else {
- buf->b_flags &= ~BF_WRITE_MASK;
- }
- }
-
- // If we kept a backup until now, and we are in patch mode, then we make
- // the backup file our 'original' file.
- if (*p_pm && dobackup) {
- char *const org = modname(fname, p_pm, false);
-
- if (backup != NULL) {
- // If the original file does not exist yet
- // the current backup file becomes the original file
- if (org == NULL) {
- emsg(_("E205: Patchmode: can't save original file"));
- } else if (!os_path_exists(org)) {
- vim_rename(backup, org);
- XFREE_CLEAR(backup); // don't delete the file
-#ifdef UNIX
- os_file_settime(org,
- (double)file_info_old.stat.st_atim.tv_sec,
- (double)file_info_old.stat.st_mtim.tv_sec);
-#endif
- }
- } else {
- // If there is no backup file, remember that a (new) file was
- // created.
- int empty_fd;
-
- if (org == NULL
- || (empty_fd = os_open(org,
- O_CREAT | O_EXCL | O_NOFOLLOW,
- perm < 0 ? 0666 : (perm & 0777))) < 0) {
- emsg(_("E206: patchmode: can't touch empty original file"));
- } else {
- close(empty_fd);
- }
- }
- if (org != NULL) {
- os_setperm(org, os_getperm((const char *)fname) & 0777);
- xfree(org);
- }
- }
-
- // Remove the backup unless 'backup' option is set
- if (!p_bk && backup != NULL
- && !write_info.bw_conv_error
- && os_remove(backup) != 0) {
- emsg(_("E207: Can't delete backup file"));
- }
-
- goto nofail;
-
- // Finish up. We get here either after failure or success.
-fail:
- no_wait_return--; // may wait for return now
-nofail:
-
- // Done saving, we accept changed buffer warnings again
- buf->b_saving = false;
-
- xfree(backup);
- if (buffer != smallbuf) {
- xfree(buffer);
- }
- xfree(fenc_tofree);
- xfree(write_info.bw_conv_buf);
- if (write_info.bw_iconv_fd != (iconv_t)-1) {
- iconv_close(write_info.bw_iconv_fd);
- write_info.bw_iconv_fd = (iconv_t)-1;
- }
-#ifdef HAVE_ACL
- os_free_acl(acl);
-#endif
-
- if (errmsg != NULL) {
- // - 100 to save some space for further error message
-#ifndef UNIX
- add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)sfname);
-#else
- add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)fname);
-#endif
- if (errnum != NULL) {
- if (errmsgarg != 0) {
- semsg("%s: %s%s: %s", errnum, IObuff, errmsg, os_strerror(errmsgarg));
- } else {
- semsg("%s: %s%s", errnum, IObuff, errmsg);
- }
- } else if (errmsgarg != 0) {
- semsg(errmsg, os_strerror(errmsgarg));
- } else {
- emsg(errmsg);
- }
- if (errmsg_allocated) {
- xfree(errmsg);
- }
-
- retval = FAIL;
- if (end == 0) {
- const int attr = HL_ATTR(HLF_E); // Set highlight for error messages.
- msg_puts_attr(_("\nWARNING: Original file may be lost or damaged\n"),
- attr | MSG_HIST);
- msg_puts_attr(_("don't quit the editor until the file is successfully written!"),
- attr | MSG_HIST);
-
- // Update the timestamp to avoid an "overwrite changed file"
- // prompt when writing again.
- if (os_fileinfo(fname, &file_info_old)) {
- buf_store_file_info(buf, &file_info_old);
- buf->b_mtime_read = buf->b_mtime;
- buf->b_mtime_read_ns = buf->b_mtime_ns;
- }
- }
- }
- msg_scroll = msg_save;
-
- // When writing the whole file and 'undofile' is set, also write the undo
- // file.
- if (retval == OK && write_undo_file) {
- uint8_t hash[UNDO_HASH_SIZE];
-
- sha256_finish(&sha_ctx, hash);
- u_write_undo(NULL, false, buf, hash);
- }
-
- if (!should_abort(retval)) {
- aco_save_T aco;
-
- curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read
-
- // Apply POST autocommands.
- // Careful: The autocommands may call buf_write() recursively!
- aucmd_prepbuf(&aco, buf);
-
- if (append) {
- apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname,
- false, curbuf, eap);
- } else if (filtering) {
- apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname,
- false, curbuf, eap);
- } else if (reset_changed && whole) {
- apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname,
- false, curbuf, eap);
- } else {
- apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname,
- false, curbuf, eap);
- }
-
- // restore curwin/curbuf and a few other things
- aucmd_restbuf(&aco);
-
- if (aborting()) { // autocmds may abort script processing
- retval = false;
- }
- }
-
- got_int |= prev_got_int;
-
- return retval;
-#undef SET_ERRMSG
-#undef SET_ERRMSG_ARG
-#undef SET_ERRMSG_NUM
-}
-
/// Set the name of the current buffer. Use when the buffer doesn't have a
/// name and a ":r" or ":w" command with a file name is used.
-static int set_rw_fname(char *fname, char *sfname)
+int set_rw_fname(char *fname, char *sfname)
{
buf_T *buf = curbuf;
@@ -3649,8 +2114,8 @@ static int set_rw_fname(char *fname, char *sfname)
/// @param[in] buf_len ret_buf length.
/// @param[in] buf buf_T file name is coming from.
/// @param[in] fname File name to write.
-static void add_quoted_fname(char *const ret_buf, const size_t buf_len, const buf_T *const buf,
- const char *fname)
+void add_quoted_fname(char *const ret_buf, const size_t buf_len, const buf_T *const buf,
+ const char *fname)
FUNC_ATTR_NONNULL_ARG(1)
{
if (fname == NULL) {
@@ -3666,21 +2131,21 @@ static void add_quoted_fname(char *const ret_buf, const size_t buf_len, const bu
/// @param eol_type line ending type
///
/// @return true if something was appended.
-static bool msg_add_fileformat(int eol_type)
+bool msg_add_fileformat(int eol_type)
{
#ifndef USE_CRNL
if (eol_type == EOL_DOS) {
- STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]"));
+ xstrlcat(IObuff, _("[dos]"), IOSIZE);
return true;
}
#endif
if (eol_type == EOL_MAC) {
- STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]"));
+ xstrlcat(IObuff, _("[mac]"), IOSIZE);
return true;
}
#ifdef USE_CRNL
if (eol_type == EOL_UNIX) {
- STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]"));
+ xstrlcat(IObuff, _("[unix]"), IOSIZE);
return true;
}
#endif
@@ -3688,11 +2153,9 @@ static bool msg_add_fileformat(int eol_type)
}
/// Append line and character count to IObuff.
-void msg_add_lines(int insert_space, long lnum, off_T nchars)
+void msg_add_lines(int insert_space, linenr_T lnum, off_T nchars)
{
- char *p;
-
- p = IObuff + strlen(IObuff);
+ char *p = IObuff + strlen(IObuff);
if (insert_space) {
*p++ = ' ';
@@ -3711,302 +2174,32 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
}
}
-/// Append message for missing line separator to IObuff.
-static void msg_add_eol(void)
-{
- STRCAT(IObuff,
- shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]"));
-}
-
-/// Check modification time of file, before writing to it.
-/// The size isn't checked, because using a tool like "gzip" takes care of
-/// using the same timestamp but can't set the size.
-static int check_mtime(buf_T *buf, FileInfo *file_info)
-{
- if (buf->b_mtime_read != 0
- && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) {
- msg_scroll = true; // Don't overwrite messages here.
- msg_silent = 0; // Must give this prompt.
- // Don't use emsg() here, don't want to flush the buffers.
- msg_attr(_("WARNING: The file has been changed since reading it!!!"),
- HL_ATTR(HLF_E));
- if (ask_yesno(_("Do you really want to write to it"), true) == 'n') {
- return FAIL;
- }
- msg_scroll = false; // Always overwrite the file message now.
- }
- return OK;
-}
-
-static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST
+bool time_differs(const FileInfo *file_info, int64_t mtime, int64_t mtime_ns)
+ FUNC_ATTR_CONST
{
- return file_info->stat.st_mtim.tv_nsec != mtime_ns
#if defined(__linux__) || defined(MSWIN)
+ return file_info->stat.st_mtim.tv_nsec != mtime_ns
// On a FAT filesystem, esp. under Linux, there are only 5 bits to store
// the seconds. Since the roundoff is done when flushing the inode, the
// time may change unexpectedly by one second!!!
|| file_info->stat.st_mtim.tv_sec - mtime > 1
|| mtime - file_info->stat.st_mtim.tv_sec > 1;
#else
+ return file_info->stat.st_mtim.tv_nsec != mtime_ns
|| file_info->stat.st_mtim.tv_sec != mtime;
#endif
}
-/// Call write() to write a number of bytes to the file.
-/// Handles 'encoding' conversion.
-///
-/// @return FAIL for failure, OK otherwise.
-static int buf_write_bytes(struct bw_info *ip)
-{
- int wlen;
- char *buf = ip->bw_buf; // data to write
- int len = ip->bw_len; // length of data
-#ifdef HAS_BW_FLAGS
- int flags = ip->bw_flags; // extra flags
-#endif
-
- // Skip conversion when writing the BOM.
- if (!(flags & FIO_NOCONVERT)) {
- char *p;
- unsigned c;
- int n;
-
- if (flags & FIO_UTF8) {
- // Convert latin1 in the buffer to UTF-8 in the file.
- p = ip->bw_conv_buf; // translate to buffer
- for (wlen = 0; wlen < len; wlen++) {
- p += utf_char2bytes((uint8_t)buf[wlen], p);
- }
- buf = ip->bw_conv_buf;
- len = (int)(p - ip->bw_conv_buf);
- } else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) {
- // Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or
- // Latin1 chars in the file.
- if (flags & FIO_LATIN1) {
- p = buf; // translate in-place (can only get shorter)
- } else {
- p = ip->bw_conv_buf; // translate to buffer
- }
- for (wlen = 0; wlen < len; wlen += n) {
- if (wlen == 0 && ip->bw_restlen != 0) {
- int l;
-
- // Use remainder of previous call. Append the start of
- // buf[] to get a full sequence. Might still be too
- // short!
- l = CONV_RESTLEN - ip->bw_restlen;
- if (l > len) {
- l = len;
- }
- memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l);
- n = utf_ptr2len_len((char *)ip->bw_rest, ip->bw_restlen + l);
- if (n > ip->bw_restlen + len) {
- // We have an incomplete byte sequence at the end to
- // be written. We can't convert it without the
- // remaining bytes. Keep them for the next call.
- if (ip->bw_restlen + len > CONV_RESTLEN) {
- return FAIL;
- }
- ip->bw_restlen += len;
- break;
- }
- if (n > 1) {
- c = (unsigned)utf_ptr2char((char *)ip->bw_rest);
- } else {
- c = ip->bw_rest[0];
- }
- if (n >= ip->bw_restlen) {
- n -= ip->bw_restlen;
- ip->bw_restlen = 0;
- } else {
- ip->bw_restlen -= n;
- memmove(ip->bw_rest, ip->bw_rest + n,
- (size_t)ip->bw_restlen);
- n = 0;
- }
- } else {
- n = utf_ptr2len_len(buf + wlen, len - wlen);
- if (n > len - wlen) {
- // We have an incomplete byte sequence at the end to
- // be written. We can't convert it without the
- // remaining bytes. Keep them for the next call.
- if (len - wlen > CONV_RESTLEN) {
- return FAIL;
- }
- ip->bw_restlen = len - wlen;
- memmove(ip->bw_rest, buf + wlen,
- (size_t)ip->bw_restlen);
- break;
- }
- if (n > 1) {
- c = (unsigned)utf_ptr2char(buf + wlen);
- } else {
- c = (uint8_t)buf[wlen];
- }
- }
-
- if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) {
- ip->bw_conv_error = true;
- ip->bw_conv_error_lnum = ip->bw_start_lnum;
- }
- if (c == NL) {
- ip->bw_start_lnum++;
- }
- }
- if (flags & FIO_LATIN1) {
- len = (int)(p - buf);
- } else {
- buf = ip->bw_conv_buf;
- len = (int)(p - ip->bw_conv_buf);
- }
- }
-
- if (ip->bw_iconv_fd != (iconv_t)-1) {
- const char *from;
- size_t fromlen;
- char *to;
- size_t tolen;
-
- // Convert with iconv().
- if (ip->bw_restlen > 0) {
- char *fp;
-
- // Need to concatenate the remainder of the previous call and
- // the bytes of the current call. Use the end of the
- // conversion buffer for this.
- fromlen = (size_t)len + (size_t)ip->bw_restlen;
- fp = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen;
- memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen);
- memmove(fp + ip->bw_restlen, buf, (size_t)len);
- from = fp;
- tolen = ip->bw_conv_buflen - fromlen;
- } else {
- from = buf;
- fromlen = (size_t)len;
- tolen = ip->bw_conv_buflen;
- }
- to = ip->bw_conv_buf;
-
- if (ip->bw_first) {
- size_t save_len = tolen;
-
- // output the initial shift state sequence
- (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen);
-
- // There is a bug in iconv() on Linux (which appears to be
- // wide-spread) which sets "to" to NULL and messes up "tolen".
- if (to == NULL) {
- to = ip->bw_conv_buf;
- tolen = save_len;
- }
- ip->bw_first = false;
- }
-
- // If iconv() has an error or there is not enough room, fail.
- if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen)
- == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL)
- || fromlen > CONV_RESTLEN) {
- ip->bw_conv_error = true;
- return FAIL;
- }
-
- // copy remainder to ip->bw_rest[] to be used for the next call.
- if (fromlen > 0) {
- memmove(ip->bw_rest, (void *)from, fromlen);
- }
- ip->bw_restlen = (int)fromlen;
-
- buf = ip->bw_conv_buf;
- len = (int)(to - ip->bw_conv_buf);
- }
- }
-
- if (ip->bw_fd < 0) {
- // Only checking conversion, which is OK if we get here.
- return OK;
- }
- wlen = (int)write_eintr(ip->bw_fd, buf, (size_t)len);
- return (wlen < len) ? FAIL : OK;
-}
-
-/// Convert a Unicode character to bytes.
-///
-/// @param c character to convert
-/// @param[in,out] pp pointer to store the result at
-/// @param flags FIO_ flags that specify which encoding to use
-///
-/// @return true for an error, false when it's OK.
-static bool ucs2bytes(unsigned c, char **pp, int flags) FUNC_ATTR_NONNULL_ALL
-{
- char_u *p = (char_u *)(*pp);
- bool error = false;
- int cc;
-
- if (flags & FIO_UCS4) {
- if (flags & FIO_ENDIAN_L) {
- *p++ = (uint8_t)c;
- *p++ = (uint8_t)(c >> 8);
- *p++ = (uint8_t)(c >> 16);
- *p++ = (uint8_t)(c >> 24);
- } else {
- *p++ = (uint8_t)(c >> 24);
- *p++ = (uint8_t)(c >> 16);
- *p++ = (uint8_t)(c >> 8);
- *p++ = (uint8_t)c;
- }
- } else if (flags & (FIO_UCS2 | FIO_UTF16)) {
- if (c >= 0x10000) {
- if (flags & FIO_UTF16) {
- // Make two words, ten bits of the character in each. First
- // word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff
- c -= 0x10000;
- if (c >= 0x100000) {
- error = true;
- }
- cc = (int)(((c >> 10) & 0x3ff) + 0xd800);
- if (flags & FIO_ENDIAN_L) {
- *p++ = (uint8_t)cc;
- *p++ = (uint8_t)(cc >> 8);
- } else {
- *p++ = (uint8_t)(cc >> 8);
- *p++ = (uint8_t)cc;
- }
- c = (c & 0x3ff) + 0xdc00;
- } else {
- error = true;
- }
- }
- if (flags & FIO_ENDIAN_L) {
- *p++ = (uint8_t)c;
- *p++ = (uint8_t)(c >> 8);
- } else {
- *p++ = (uint8_t)(c >> 8);
- *p++ = (uint8_t)c;
- }
- } else { // Latin1
- if (c >= 0x100) {
- error = true;
- *p++ = 0xBF;
- } else {
- *p++ = (uint8_t)c;
- }
- }
-
- *pp = (char *)p;
- return error;
-}
-
/// Return true if file encoding "fenc" requires conversion from or to
/// 'encoding'.
///
/// @param fenc file encoding to check
///
/// @return true if conversion is required
-static bool need_conversion(const char *fenc)
+bool need_conversion(const char *fenc)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
int same_encoding;
- int enc_flags;
int fenc_flags;
if (*fenc == NUL || strcmp(p_enc, fenc) == 0) {
@@ -4015,7 +2208,7 @@ static bool need_conversion(const char *fenc)
} else {
// Ignore difference between "ansi" and "latin1", "ucs-4" and
// "ucs-4be", etc.
- enc_flags = get_fio_flags(p_enc);
+ int enc_flags = get_fio_flags(p_enc);
fenc_flags = get_fio_flags(fenc);
same_encoding = (enc_flags != 0 && fenc_flags == enc_flags);
}
@@ -4034,14 +2227,12 @@ static bool need_conversion(const char *fenc)
/// use 'encoding'.
///
/// @param name string to check for encoding
-static int get_fio_flags(const char *name)
+int get_fio_flags(const char *name)
{
- int prop;
-
if (*name == NUL) {
name = p_enc;
}
- prop = enc_canon_props(name);
+ int prop = enc_canon_props(name);
if (prop & ENC_UNICODE) {
if (prop & ENC_2BYTE) {
if (prop & ENC_ENDIAN_L) {
@@ -4075,7 +2266,7 @@ static int get_fio_flags(const char *name)
///
/// @return the name of the encoding and set "*lenp" to the length or,
/// NULL when no BOM found.
-static char *check_for_bom(const char *p_in, long size, int *lenp, int flags)
+static char *check_for_bom(const char *p_in, int size, int *lenp, int flags)
{
const uint8_t *p = (const uint8_t *)p_in;
char *name = NULL;
@@ -4116,32 +2307,6 @@ static char *check_for_bom(const char *p_in, long size, int *lenp, int flags)
return name;
}
-/// Generate a BOM in "buf[4]" for encoding "name".
-///
-/// @return the length of the BOM (zero when no BOM).
-static int make_bom(char_u *buf, char *name)
-{
- int flags;
- char *p;
-
- flags = get_fio_flags(name);
-
- // Can't put a BOM in a non-Unicode file.
- if (flags == FIO_LATIN1 || flags == 0) {
- return 0;
- }
-
- if (flags == FIO_UTF8) { // UTF-8
- buf[0] = 0xef;
- buf[1] = 0xbb;
- buf[2] = 0xbf;
- return 3;
- }
- p = (char *)buf;
- (void)ucs2bytes(0xfeff, &p, flags);
- return (int)((char_u *)p - buf);
-}
-
/// Shorten filename of a buffer.
///
/// @param force when true: Use full path from now on for files currently being
@@ -4153,8 +2318,6 @@ static int make_bom(char_u *buf, char *name)
/// name.
void shorten_buf_fname(buf_T *buf, char *dirname, int force)
{
- char *p;
-
if (buf->b_fname != NULL
&& !bt_nofilename(buf)
&& !path_with_url(buf->b_fname)
@@ -4164,7 +2327,7 @@ void shorten_buf_fname(buf_T *buf, char *dirname, int force)
if (buf->b_sfname != buf->b_ffname) {
XFREE_CLEAR(buf->b_sfname);
}
- p = path_shorten_fname(buf->b_ffname, dirname);
+ char *p = path_shorten_fname(buf->b_ffname, dirname);
if (p != NULL) {
buf->b_sfname = xstrdup(p);
buf->b_fname = buf->b_sfname;
@@ -4182,7 +2345,7 @@ void shorten_fnames(int force)
os_dirname(dirname, MAXPATHL);
FOR_ALL_BUFFERS(buf) {
- shorten_buf_fname(buf, (char *)dirname, force);
+ shorten_buf_fname(buf, dirname, force);
// Always make the swap file name a full path, a "nofile" buffer may
// also have a swap file.
@@ -4316,7 +2479,7 @@ bool vim_fgets(char *buf, int size, FILE *fp)
do {
tbuf[sizeof(tbuf) - 2] = NUL;
errno = 0;
- retval = fgets((char *)tbuf, sizeof(tbuf), fp);
+ retval = fgets(tbuf, sizeof(tbuf), fp);
if (retval == NULL && (feof(fp) || errno != EINTR)) {
break;
}
@@ -4452,6 +2615,38 @@ int put_time(FILE *fd, time_t time_)
return fwrite(buf, sizeof(uint8_t), ARRAY_SIZE(buf), fd) == 1 ? OK : FAIL;
}
+static int rename_with_tmp(const char *const from, const char *const to)
+{
+ // Find a name that doesn't exist and is in the same directory.
+ // Rename "from" to "tempname" and then rename "tempname" to "to".
+ if (strlen(from) >= MAXPATHL - 5) {
+ return -1;
+ }
+
+ char tempname[MAXPATHL + 1];
+ STRCPY(tempname, from);
+ for (int n = 123; n < 99999; n++) {
+ char *tail = path_tail(tempname);
+ snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - tempname - 1)), "%d", n);
+
+ if (!os_path_exists(tempname)) {
+ if (os_rename(from, tempname) == OK) {
+ if (os_rename(tempname, to) == OK) {
+ return 0;
+ }
+ // Strange, the second step failed. Try moving the
+ // file back and return failure.
+ (void)os_rename(tempname, from);
+ return -1;
+ }
+ // If it fails for one temp name it will most likely fail
+ // for any temp name, give up.
+ return -1;
+ }
+ }
+ return -1;
+}
+
/// os_rename() only works if both files are on the same file system, this
/// function will (attempts to?) copy the file across if rename fails -- webb
///
@@ -4459,23 +2654,14 @@ int put_time(FILE *fd, time_t time_)
int vim_rename(const char *from, const char *to)
FUNC_ATTR_NONNULL_ALL
{
- int fd_in;
- int fd_out;
- int n;
char *errmsg = NULL;
- char *buffer;
- long perm;
-#ifdef HAVE_ACL
- vim_acl_T acl; // ACL from original file
-#endif
bool use_tmp_file = false;
// When the names are identical, there is nothing to do. When they refer
// to the same file (ignoring case and slash/backslash differences) but
// the file name differs we need to go through a temp file.
if (path_fnamecmp(from, to) == 0) {
- if (p_fic && (strcmp(path_tail((char *)from), path_tail((char *)to))
- != 0)) {
+ if (p_fic && (strcmp(path_tail(from), path_tail(to)) != 0)) {
use_tmp_file = true;
} else {
return 0;
@@ -4484,7 +2670,7 @@ int vim_rename(const char *from, const char *to)
// Fail if the "from" file doesn't exist. Avoids that "to" is deleted.
FileInfo from_info;
- if (!os_fileinfo((char *)from, &from_info)) {
+ if (!os_fileinfo(from, &from_info)) {
return -1;
}
@@ -4492,47 +2678,19 @@ int vim_rename(const char *from, const char *to)
// This happens when "from" and "to" differ in case and are on a FAT32
// filesystem. In that case go through a temp file name.
FileInfo to_info;
- if (os_fileinfo((char *)to, &to_info)
- && os_fileinfo_id_equal(&from_info, &to_info)) {
+ if (os_fileinfo(to, &to_info) && os_fileinfo_id_equal(&from_info, &to_info)) {
use_tmp_file = true;
}
if (use_tmp_file) {
- char tempname[MAXPATHL + 1];
-
- // Find a name that doesn't exist and is in the same directory.
- // Rename "from" to "tempname" and then rename "tempname" to "to".
- if (strlen(from) >= MAXPATHL - 5) {
- return -1;
- }
- STRCPY(tempname, from);
- for (n = 123; n < 99999; n++) {
- char *tail = path_tail(tempname);
- snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - tempname - 1)), "%d", n);
-
- if (!os_path_exists(tempname)) {
- if (os_rename(from, tempname) == OK) {
- if (os_rename(tempname, to) == OK) {
- return 0;
- }
- // Strange, the second step failed. Try moving the
- // file back and return failure.
- (void)os_rename(tempname, from);
- return -1;
- }
- // If it fails for one temp name it will most likely fail
- // for any temp name, give up.
- return -1;
- }
- }
- return -1;
+ return rename_with_tmp(from, to);
}
// Delete the "to" file, this is required on some systems to make the
// os_rename() work, on other systems it makes sure that we don't have
// two files when the os_rename() fails.
- os_remove((char *)to);
+ os_remove(to);
// First try a normal rename, return if it works.
if (os_rename(from, to) == OK) {
@@ -4540,43 +2698,35 @@ int vim_rename(const char *from, const char *to)
}
// Rename() failed, try copying the file.
- perm = os_getperm(from);
-#ifdef HAVE_ACL
+ int perm = os_getperm(from);
// For systems that support ACL: get the ACL from the original file.
- acl = os_get_acl(from);
-#endif
- fd_in = os_open((char *)from, O_RDONLY, 0);
+ vim_acl_T acl = os_get_acl(from);
+ int fd_in = os_open(from, O_RDONLY, 0);
if (fd_in < 0) {
-#ifdef HAVE_ACL
os_free_acl(acl);
-#endif
return -1;
}
// Create the new file with same permissions as the original.
- fd_out = os_open((char *)to,
- O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, (int)perm);
+ int fd_out = os_open(to, O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, perm);
if (fd_out < 0) {
close(fd_in);
-#ifdef HAVE_ACL
os_free_acl(acl);
-#endif
return -1;
}
// Avoid xmalloc() here as vim_rename() is called by buf_write() when nvim
// is `preserve_exit()`ing.
- buffer = try_malloc(BUFSIZE);
+ char *buffer = try_malloc(WRITEBUFSIZE);
if (buffer == NULL) {
close(fd_out);
close(fd_in);
-#ifdef HAVE_ACL
os_free_acl(acl);
-#endif
return -1;
}
- while ((n = (int)read_eintr(fd_in, buffer, BUFSIZE)) > 0) {
+ int n;
+ while ((n = read_eintr(fd_in, buffer, WRITEBUFSIZE)) > 0) {
if (write_eintr(fd_out, buffer, (size_t)n) != n) {
errmsg = _("E208: Error writing to \"%s\"");
break;
@@ -4595,15 +2745,13 @@ int vim_rename(const char *from, const char *to)
#ifndef UNIX // For Unix os_open() already set the permission.
os_setperm(to, perm);
#endif
-#ifdef HAVE_ACL
os_set_acl(to, acl);
os_free_acl(acl);
-#endif
if (errmsg != NULL) {
semsg(errmsg, to);
return -1;
}
- os_remove((char *)from);
+ os_remove(from);
return 0;
}
@@ -4619,8 +2767,6 @@ static int already_warned = false;
/// @return true if some message was written (screen should be redrawn and cursor positioned).
int check_timestamps(int focus)
{
- int didit = 0;
-
// Don't check timestamps while system() or another low-level function may
// cause us to lose and gain focus.
if (no_check_timestamps > 0) {
@@ -4635,6 +2781,8 @@ int check_timestamps(int focus)
return false;
}
+ int didit = 0;
+
if (!stuff_empty() || global_busy || !typebuf_typed()
|| autocmd_busy || curbuf->b_ro_locked > 0
|| allbuf_lock > 0) {
@@ -4678,13 +2826,11 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
{
buf_T *tbuf = curbuf;
int retval = OK;
- linenr_T lnum;
- char *p;
// Copy the lines in "frombuf" to "tobuf".
curbuf = tobuf;
- for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; lnum++) {
- p = xstrdup(ml_get_buf(frombuf, lnum, false));
+ for (linenr_T lnum = 1; lnum <= frombuf->b_ml.ml_line_count; lnum++) {
+ char *p = xstrdup(ml_get_buf(frombuf, lnum));
if (ml_append(lnum - 1, p, 0, false) == FAIL) {
xfree(p);
retval = FAIL;
@@ -4696,7 +2842,7 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
// Delete all the lines in "frombuf".
if (retval != FAIL) {
curbuf = frombuf;
- for (lnum = curbuf->b_ml.ml_line_count; lnum > 0; lnum--) {
+ for (linenr_T lnum = curbuf->b_ml.ml_line_count; lnum > 0; lnum--) {
if (ml_delete(lnum, false) == FAIL) {
// Oops! We could try putting back the saved lines, but that
// might fail again...
@@ -4720,7 +2866,6 @@ int buf_check_timestamp(buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
int retval = 0;
- char *path;
char *mesg = NULL;
char *mesg2 = "";
bool helpmesg = false;
@@ -4735,8 +2880,6 @@ int buf_check_timestamp(buf_T *buf)
uint64_t orig_size = buf->b_orig_size;
int orig_mode = buf->b_orig_mode;
static bool busy = false;
- char *s;
- char *reason;
bufref_T bufref;
set_bufref(&bufref, buf);
@@ -4760,7 +2903,7 @@ int buf_check_timestamp(buf_T *buf)
&& (!(file_info_ok = os_fileinfo(buf->b_ffname, &file_info))
|| time_differs(&file_info, buf->b_mtime, buf->b_mtime_ns)
|| (int)file_info.stat.st_mode != buf->b_orig_mode)) {
- const long prev_b_mtime = buf->b_mtime;
+ const int64_t prev_b_mtime = buf->b_mtime;
retval = 1;
@@ -4784,6 +2927,7 @@ int buf_check_timestamp(buf_T *buf)
// was set, the global option value otherwise.
reload = RELOAD_NORMAL;
} else {
+ char *reason;
if (!file_info_ok) {
reason = "deleted";
} else if (bufIsChanged(buf)) {
@@ -4810,7 +2954,7 @@ int buf_check_timestamp(buf_T *buf)
if (!bufref_valid(&bufref)) {
emsg(_("E246: FileChangedShell autocommand deleted buffer"));
}
- s = get_vim_var_str(VV_FCS_CHOICE);
+ char *s = get_vim_var_str(VV_FCS_CHOICE);
if (strcmp(s, "reload") == 0 && *reason != 'd') {
reload = RELOAD_NORMAL;
} else if (strcmp(s, "edit") == 0) {
@@ -4836,8 +2980,8 @@ int buf_check_timestamp(buf_T *buf)
// checked out of CVS). Always warn when the buffer was
// changed.
if (reason[2] == 'n') {
- mesg = _(
- "W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well");
+ mesg =
+ _("W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well");
mesg2 = _("See \":help W12\" for more info.");
} else if (reason[1] == 'h') {
mesg = _("W11: Warning: File \"%s\" has changed since editing started");
@@ -4863,7 +3007,7 @@ int buf_check_timestamp(buf_T *buf)
}
if (mesg != NULL) {
- path = home_replace_save(buf, buf->b_fname);
+ char *path = home_replace_save(buf, buf->b_fname);
if (!helpmesg) {
mesg2 = "";
}
@@ -4907,7 +3051,7 @@ int buf_check_timestamp(buf_T *buf)
if (emsg_silent == 0 && !in_assert_fails) {
ui_flush();
// give the user some time to think about it
- os_delay(1004L, true);
+ os_delay(1004, true);
// don't redraw and erase the message
redraw_cmdline = false;
@@ -4924,7 +3068,7 @@ int buf_check_timestamp(buf_T *buf)
// Reload the buffer.
buf_reload(buf, orig_mode, reload == RELOAD_DETECT);
if (buf->b_p_udf && buf->b_ffname != NULL) {
- char_u hash[UNDO_HASH_SIZE];
+ uint8_t hash[UNDO_HASH_SIZE];
// Any existing undo file is unusable, write it now.
u_compute_hash(buf, hash);
@@ -4946,8 +3090,6 @@ int buf_check_timestamp(buf_T *buf)
void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
{
exarg_T ea;
- pos_T old_cursor;
- linenr_T old_topline;
int old_ro = buf->b_p_ro;
buf_T *savebuf;
bufref_T bufref;
@@ -4967,8 +3109,8 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
prep_exarg(&ea, buf);
}
- old_cursor = curwin->w_cursor;
- old_topline = curwin->w_topline;
+ pos_T old_cursor = curwin->w_cursor;
+ linenr_T old_topline = curwin->w_topline;
if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) {
// Save all the text, so that the reload can be undone.
@@ -4987,7 +3129,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
savebuf = NULL;
} else {
// Allocate a buffer without putting it in the buffer list.
- savebuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ savebuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
set_bufref(&bufref, savebuf);
if (savebuf != NULL && buf == curbuf) {
// Open the memline.
@@ -5008,7 +3150,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
if (saved == OK) {
curbuf->b_flags |= BF_CHECK_RO; // check for RO again
keep_filetype = true; // don't detect 'filetype'
- if (readfile(buf->b_ffname, buf->b_fname, (linenr_T)0, (linenr_T)0,
+ if (readfile(buf->b_ffname, buf->b_fname, 0, 0,
(linenr_T)MAXLNUM, &ea, flags, false) != OK) {
if (!aborting()) {
semsg(_("E321: Could not reload \"%s\""), buf->b_fname);
@@ -5104,12 +3246,10 @@ void write_lnum_adjust(linenr_T offset)
/// unless when it looks like a URL.
void forward_slash(char *fname)
{
- char *p;
-
if (path_with_url(fname)) {
return;
}
- for (p = fname; *p != NUL; p++) {
+ for (char *p = fname; *p != NUL; p++) {
if (*p == '\\') {
*p = '/';
}
@@ -5119,7 +3259,7 @@ void forward_slash(char *fname)
/// Path to Nvim's own temp dir. Ends in a slash.
static char *vim_tempdir = NULL;
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
DIR *vim_tempdir_dp = NULL; ///< File descriptor of temp dir
#endif
@@ -5136,12 +3276,18 @@ static void vim_mktempdir(void)
char tmp[TEMP_FILE_PATH_MAXLEN];
char path[TEMP_FILE_PATH_MAXLEN];
char user[40] = { 0 };
+ char appname[40] = { 0 };
(void)os_get_username(user, sizeof(user));
// Usernames may contain slashes! #19240
memchrsub(user, '/', '_', sizeof(user));
memchrsub(user, '\\', '_', sizeof(user));
+ // Appname may be a relative path, replace slashes to make it name-like.
+ xstrlcpy(appname, get_appname(), sizeof(appname));
+ memchrsub(appname, '/', '%', sizeof(appname));
+ memchrsub(appname, '\\', '%', sizeof(appname));
+
// Make sure the umask doesn't remove the executable bit.
// "repl" has been reported to use "0177".
mode_t umask_save = umask(0077);
@@ -5154,7 +3300,9 @@ static void vim_mktempdir(void)
// "/tmp/" exists, now try to create "/tmp/nvim.<user>/".
add_pathsep(tmp);
- xstrlcat(tmp, "nvim.", sizeof(tmp));
+
+ xstrlcat(tmp, appname, sizeof(tmp));
+ xstrlcat(tmp, ".", sizeof(tmp));
xstrlcat(tmp, user, sizeof(tmp));
(void)os_mkdir(tmp, 0700); // Always create, to avoid a race.
bool owned = os_file_owned(tmp);
@@ -5212,11 +3360,11 @@ int readdir_core(garray_T *gap, const char *path, void *context, CheckItem check
Directory dir;
if (!os_scandir(&dir, path)) {
- smsg(_(e_notopen), path);
+ smsg(0, _(e_notopen), path);
return FAIL;
}
- for (;;) {
+ while (true) {
const char *p = os_scandir_next(&dir);
if (p == NULL) {
break;
@@ -5264,7 +3412,7 @@ int delete_recursive(const char *name)
if (readdir_core(&ga, exp, NULL, NULL) == OK) {
for (int i = 0; i < ga.ga_len; i++) {
vim_snprintf(NameBuff, MAXPATHL, "%s/%s", exp, ((char **)ga.ga_data)[i]);
- if (delete_recursive((const char *)NameBuff) != 0) {
+ if (delete_recursive(NameBuff) != 0) {
// Remember the failure but continue deleting any further
// entries.
result = -1;
@@ -5286,7 +3434,7 @@ int delete_recursive(const char *name)
return result;
}
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
/// Open temporary directory and take file lock to prevent
/// to be auto-cleaned.
static void vim_opentempdir(void)
@@ -5323,7 +3471,7 @@ void vim_deltempdir(void)
return;
}
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
vim_closetempdir();
#endif
// remove the trailing path separator
@@ -5337,10 +3485,20 @@ void vim_deltempdir(void)
/// Creates the directory on the first call.
char *vim_gettempdir(void)
{
- if (vim_tempdir == NULL) {
+ static int notfound = 0;
+ if (vim_tempdir == NULL || !os_isdir(vim_tempdir)) {
+ if (vim_tempdir != NULL) {
+ notfound++;
+ if (notfound == 1) {
+ ELOG("tempdir disappeared (antivirus or broken cleanup job?): %s", vim_tempdir);
+ }
+ if (notfound > 1) {
+ msg_schedule_semsg("E5431: tempdir disappeared (%d times)", notfound);
+ }
+ XFREE_CLEAR(vim_tempdir);
+ }
vim_mktempdir();
}
-
return vim_tempdir;
}
@@ -5361,7 +3519,7 @@ static bool vim_settempdir(char *tempdir)
vim_FullName(tempdir, buf, MAXPATHL, false);
add_pathsep(buf);
vim_tempdir = xstrdup(buf);
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
vim_opentempdir();
#endif
xfree(buf);
@@ -5386,10 +3544,9 @@ char *vim_tempname(void)
// There is no need to check if the file exists, because we own the directory
// and nobody else creates a file in it.
- char template[TEMP_FILE_PATH_MAXLEN];
- snprintf(template, TEMP_FILE_PATH_MAXLEN,
- "%s%" PRIu64, tempdir, temp_count++);
- return xstrdup(template);
+ char templ[TEMP_FILE_PATH_MAXLEN];
+ snprintf(templ, TEMP_FILE_PATH_MAXLEN, "%s%" PRIu64, tempdir, temp_count++);
+ return xstrdup(templ);
}
/// Tries matching a filename with a "pattern" ("prog" is NULL), or use the
@@ -5413,13 +3570,7 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname,
bool result = false;
regmatch.rm_ic = p_fic; // ignore case if 'fileignorecase' is set
- {
- if (prog != NULL) {
- regmatch.regprog = *prog;
- } else {
- regmatch.regprog = vim_regcomp(pattern, RE_MAGIC);
- }
- }
+ regmatch.regprog = prog != NULL ? *prog : vim_regcomp(pattern, RE_MAGIC);
// Try for a match with the pattern with:
// 1. the full file name, when the pattern has a '/'.
@@ -5427,10 +3578,10 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname,
// 3. the tail of the file name, when the pattern has no '/'.
if (regmatch.regprog != NULL
&& ((allow_dirs
- && (vim_regexec(&regmatch, fname, (colnr_T)0)
+ && (vim_regexec(&regmatch, fname, 0)
|| (sfname != NULL
- && vim_regexec(&regmatch, sfname, (colnr_T)0))))
- || (!allow_dirs && vim_regexec(&regmatch, tail, (colnr_T)0)))) {
+ && vim_regexec(&regmatch, sfname, 0))))
+ || (!allow_dirs && vim_regexec(&regmatch, tail, 0)))) {
result = true;
}
@@ -5454,24 +3605,19 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname,
bool match_file_list(char *list, char *sfname, char *ffname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3)
{
- char buf[100];
- char *tail;
- char *regpat;
- char allow_dirs;
- bool match;
- char *p;
-
- tail = path_tail(sfname);
+ char *tail = path_tail(sfname);
// try all patterns in 'wildignore'
- p = list;
+ char *p = list;
while (*p) {
+ char buf[MAXPATHL];
copy_option_part(&p, buf, ARRAY_SIZE(buf), ",");
- regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, false);
+ char allow_dirs;
+ char *regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, false);
if (regpat == NULL) {
break;
}
- match = match_file_pat(regpat, NULL, ffname, sfname, tail, (int)allow_dirs);
+ bool match = match_file_pat(regpat, NULL, ffname, sfname, tail, (int)allow_dirs);
xfree(regpat);
if (match) {
return true;
@@ -5494,15 +3640,10 @@ bool match_file_list(char *list, char *sfname, char *ffname)
char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs, int no_bslash)
FUNC_ATTR_NONNULL_ARG(1)
{
- const char *endp;
- char *reg_pat;
- const char *p;
- int nested = 0;
- bool add_dollar = true;
-
if (allow_dirs != NULL) {
*allow_dirs = false;
}
+
if (pat_end == NULL) {
pat_end = pat + strlen(pat);
}
@@ -5513,7 +3654,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
size_t size = 2; // '^' at start, '$' at end.
- for (p = pat; p < pat_end; p++) {
+ for (const char *p = pat; p < pat_end; p++) {
switch (*p) {
case '*':
case '.':
@@ -5534,7 +3675,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
break;
}
}
- reg_pat = xmalloc(size + 1);
+ char *reg_pat = xmalloc(size + 1);
size_t i = 0;
@@ -5545,14 +3686,16 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
} else {
reg_pat[i++] = '^';
}
- endp = pat_end - 1;
+ const char *endp = pat_end - 1;
+ bool add_dollar = true;
if (endp >= pat && *endp == '*') {
while (endp - pat > 0 && *endp == '*') {
endp--;
}
add_dollar = false;
}
- for (p = pat; *p && nested >= 0 && p <= endp; p++) {
+ int nested = 0;
+ for (const char *p = pat; *p && nested >= 0 && p <= endp; p++) {
switch (*p) {
case '*':
reg_pat[i++] = '.';
@@ -5602,11 +3745,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
// regexp.
// An escaped { must be unescaped since we use magic not
// verymagic. Use "\\\{n,m\}"" to get "\{n,m}".
- if (*++p == '?'
-#ifdef BACKSLASH_IN_FILENAME
- && no_bslash
-#endif
- ) {
+ if (*++p == '?' && (!BACKSLASH_IN_FILENAME_BOOL || no_bslash)) {
reg_pat[i++] = '?';
} else if (*p == ',' || *p == '%' || *p == '#'
|| ascii_isspace(*p) || *p == '{' || *p == '}') {
@@ -5617,10 +3756,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
p += 2;
} else {
if (allow_dirs != NULL && vim_ispathsep(*p)
-#ifdef BACKSLASH_IN_FILENAME
- && (!no_bslash || *p != '\\')
-#endif
- ) {
+ && (!BACKSLASH_IN_FILENAME_BOOL || (!no_bslash || *p != '\\'))) {
*allow_dirs = true;
}
reg_pat[i++] = '\\';
@@ -5683,35 +3819,35 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
/// Version of read() that retries when interrupted by EINTR (possibly
/// by a SIGWINCH).
-long read_eintr(int fd, void *buf, size_t bufsize)
+int read_eintr(int fd, void *buf, size_t bufsize)
{
- long ret;
+ ssize_t ret;
- for (;;) {
- ret = read(fd, buf, bufsize);
+ while (true) {
+ ret = read(fd, buf, (unsigned)bufsize);
if (ret >= 0 || errno != EINTR) {
break;
}
}
- return ret;
+ return (int)ret;
}
/// Version of write() that retries when interrupted by EINTR (possibly
/// by a SIGWINCH).
-long write_eintr(int fd, void *buf, size_t bufsize)
+int write_eintr(int fd, void *buf, size_t bufsize)
{
- long ret = 0;
+ int ret = 0;
// Repeat the write() so long it didn't fail, other than being interrupted
// by a signal.
- while (ret < (long)bufsize) {
- long wlen = write(fd, (char *)buf + ret, bufsize - (size_t)ret);
+ while (ret < (int)bufsize) {
+ ssize_t wlen = write(fd, (char *)buf + ret, (unsigned)(bufsize - (size_t)ret));
if (wlen < 0) {
if (errno != EINTR) {
break;
}
} else {
- ret += wlen;
+ ret += (int)wlen;
}
}
return ret;
diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h
index dabcda5bf2..d1f6561507 100644
--- a/src/nvim/fileio.h
+++ b/src/nvim/fileio.h
@@ -1,11 +1,16 @@
-#ifndef NVIM_FILEIO_H
-#define NVIM_FILEIO_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include <stdint.h> // IWYU pragma: keep
+#include <stdio.h> // IWYU pragma: keep
+#include <time.h> // IWYU pragma: keep
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
-#include "nvim/garray.h"
-#include "nvim/os/os.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/globals.h"
+#include "nvim/os/fs_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h"
// Values for readfile() flags
#define READ_NEW 0x01 // read a file into a new buffer
@@ -18,12 +23,30 @@
#define READ_NOWINENTER 0x80 // do not trigger BufWinEnter
#define READ_NOFILE 0x100 // do not read a file, do trigger BufReadCmd
-#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
-
typedef varnumber_T (*CheckItem)(void *expr, const char *name);
+enum {
+ FIO_LATIN1 = 0x01, // convert Latin1
+ FIO_UTF8 = 0x02, // convert UTF-8
+ FIO_UCS2 = 0x04, // convert UCS-2
+ FIO_UCS4 = 0x08, // convert UCS-4
+ FIO_UTF16 = 0x10, // convert UTF-16
+ FIO_ENDIAN_L = 0x80, // little endian
+ FIO_NOCONVERT = 0x2000, // skip encoding conversion
+ FIO_UCSBOM = 0x4000, // check for BOM at start of file
+ FIO_ALL = -1, // allow all formats
+};
+
+// When converting, a read() or write() may leave some bytes to be converted
+// for the next call. The value is guessed...
+#define CONV_RESTLEN 30
+
+#define WRITEBUFSIZE 8192 // size of normal write buffer
+
+// We have to guess how much a sequence of bytes may expand when converting
+// with iconv() to be able to allocate a buffer.
+#define ICONV_MULT 8
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
-// Events for autocommands
# include "fileio.h.generated.h"
#endif
-#endif // NVIM_FILEIO_H
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 7306131574..c905b2d3ed 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// vim: set fdm=marker fdl=1 fdc=3
// fold.c: code for folding
@@ -12,21 +9,27 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "klib/kvec.h"
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_session.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
@@ -37,15 +40,17 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/ops.h"
-#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// local declarations. {{{1
// typedef fold_T {{{2
@@ -101,12 +106,12 @@ typedef void (*LevelGetter)(fline_T *);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "fold.c.generated.h"
#endif
-static char *e_nofold = N_("E490: No fold found");
+static const char *e_nofold = N_("E490: No fold found");
// While updating the folds lines between invalid_top and invalid_bot have an
// undefined fold level. Only used for the window currently being updated.
-static linenr_T invalid_top = (linenr_T)0;
-static linenr_T invalid_bot = (linenr_T)0;
+static linenr_T invalid_top = 0;
+static linenr_T invalid_bot = 0;
// When using 'foldexpr' we sometimes get the level of the next line, which
// calls foldlevel() to get the level of the current line, which hasn't been
@@ -201,7 +206,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp
if (first == 0) {
// Recursively search for a fold that contains "lnum".
garray_T *gap = &win->w_folds;
- for (;;) {
+ while (true) {
if (!foldFind(gap, lnum_rel, &fp)) {
break;
}
@@ -263,7 +268,7 @@ static int foldLevel(linenr_T lnum)
{
// While updating the folds lines between invalid_top and invalid_bot have
// an undefined fold level. Otherwise update the folds first.
- if (invalid_top == (linenr_T)0) {
+ if (invalid_top == 0) {
checkupdate(curwin);
} else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {
return prev_lnum_lvl;
@@ -358,7 +363,7 @@ int foldmethodIsDiff(win_T *wp)
// closeFold() {{{2
/// Close fold for current window at position "pos".
/// Repeat "count" times.
-void closeFold(pos_T pos, long count)
+void closeFold(pos_T pos, int count)
{
setFoldRepeat(pos, count, false);
}
@@ -412,7 +417,7 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int ha
// openFold() {{{2
/// Open fold for current window at position "pos".
/// Repeat "count" times.
-void openFold(pos_T pos, long count)
+void openFold(pos_T pos, int count)
{
setFoldRepeat(pos, count, true);
}
@@ -430,7 +435,7 @@ void foldOpenCursor(void)
{
checkupdate(curwin);
if (hasAnyFolding(curwin)) {
- for (;;) {
+ while (true) {
int done = DONE_NOTHING;
(void)setManualFold(curwin->w_cursor, true, false, &done);
if (!(done & DONE_ACTION)) {
@@ -562,7 +567,7 @@ void foldCreate(win_T *wp, pos_T start, pos_T end)
i = 0;
} else {
fold_T *fp;
- for (;;) {
+ while (true) {
if (!foldFind(gap, start_rel.lnum, &fp)) {
break;
}
@@ -645,7 +650,7 @@ void foldCreate(win_T *wp, pos_T start, pos_T end)
// We want the new fold to be closed. If it would remain open because
// of using 'foldlevel', need to adjust fd_flags of containing folds.
if (use_level && !closed && level < wp->w_p_fdl) {
- closeFold(start, 1L);
+ closeFold(start, 1);
}
if (!use_level) {
wp->w_fold_manual = true;
@@ -683,7 +688,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const
garray_T *found_ga = NULL;
linenr_T lnum_off = 0;
bool use_level = false;
- for (;;) {
+ while (true) {
fold_T *fp;
if (!foldFind(gap, lnum - lnum_off, &fp)) {
break;
@@ -743,8 +748,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const
}
if (last_lnum > 0) {
- // TODO(teto): pass the buffer
- changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false);
+ changed_lines(wp->w_buffer, first_lnum, 0, last_lnum, 0, false);
// send one nvim_buf_lines_event at the end
// last_lnum is the line *after* the last line of the outermost fold
@@ -771,7 +775,7 @@ void clearFolding(win_T *win)
/// The changes in lines from top to bot (inclusive).
void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
{
- if (disable_fold_update || State & MODE_INSERT) {
+ if (disable_fold_update || (State & MODE_INSERT && !foldmethodIsIndent(wp))) {
return;
}
@@ -843,7 +847,7 @@ void foldUpdateAll(win_T *win)
/// @return FAIL if not moved.
///
/// @param dir FORWARD or BACKWARD
-int foldMoveTo(const bool updown, const int dir, const long count)
+int foldMoveTo(const bool updown, const int dir, const int count)
{
int retval = FAIL;
linenr_T lnum;
@@ -852,7 +856,7 @@ int foldMoveTo(const bool updown, const int dir, const long count)
checkupdate(curwin);
// Repeat "count" times.
- for (long n = 0; n < count; n++) {
+ for (int n = 0; n < count; n++) {
// Find nested folds. Stop when a fold is closed. The deepest fold
// that moves the cursor is used.
linenr_T lnum_off = 0;
@@ -865,7 +869,7 @@ int foldMoveTo(const bool updown, const int dir, const long count)
linenr_T lnum_found = curwin->w_cursor.lnum;
int level = 0;
bool last = false;
- for (;;) {
+ while (true) {
if (!foldFind(gap, curwin->w_cursor.lnum - lnum_off, &fp)) {
if (!updown || gap->ga_len == 0) {
break;
@@ -1104,7 +1108,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum)
// Recursively search for a fold that contains "lnum".
garray_T *gap = &wp->w_folds;
- for (;;) {
+ while (true) {
if (!foldFind(gap, lnum_rel, &fp)) {
break;
}
@@ -1125,14 +1129,14 @@ static void checkupdate(win_T *wp)
return;
}
- foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); // will update all
+ foldUpdate(wp, 1, (linenr_T)MAXLNUM); // will update all
wp->w_foldinvalid = false;
}
// setFoldRepeat() {{{2
/// Open or close fold for current window at position `pos`.
/// Repeat "count" times.
-static void setFoldRepeat(pos_T pos, long count, int do_open)
+static void setFoldRepeat(pos_T pos, int count, int do_open)
{
for (int n = 0; n < count; n++) {
int done = DONE_NOTHING;
@@ -1201,7 +1205,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu
// Find the fold, open or close it.
garray_T *gap = &wp->w_folds;
- for (;;) {
+ while (true) {
if (!foldFind(gap, lnum, &fp)) {
// If there is a following fold, continue there next time.
if (fp != NULL && fp < (fold_T *)gap->ga_data + gap->ga_len) {
@@ -1360,7 +1364,7 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
}
// If appending a line in Insert mode, it should be included in the fold
// just above the line.
- if ((State & MODE_INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) {
+ if ((State & MODE_INSERT) && amount == 1 && line2 == MAXLNUM) {
line1--;
}
foldMarkAdjustRecurse(wp, &wp->w_folds, line1, line2, amount, amount_after);
@@ -1378,7 +1382,7 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line
// In Insert mode an inserted line at the top of a fold is considered part
// of the fold, otherwise it isn't.
- if ((State & MODE_INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) {
+ if ((State & MODE_INSERT) && amount == 1 && line2 == MAXLNUM) {
top = line1 + 1;
} else {
top = line1;
@@ -1580,8 +1584,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end)
// Update both changes here, to avoid all folds after the start are
// changed when the start marker is inserted and the end isn't.
- // TODO(teto): pass the buffer
- changed_lines(start.lnum, (colnr_T)0, end.lnum, 0L, false);
+ changed_lines(buf, start.lnum, 0, end.lnum, 0, false);
// Note: foldAddMarker() may not actually change start and/or end if
// u_save() is unable to save the buffer line, but we send the
@@ -1601,7 +1604,7 @@ static void foldAddMarker(buf_T *buf, pos_T pos, const char *marker, size_t mark
linenr_T lnum = pos.lnum;
// Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end
- char *line = ml_get_buf(buf, lnum, false);
+ char *line = ml_get_buf(buf, lnum);
size_t line_len = strlen(line);
size_t added = 0;
@@ -1661,7 +1664,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker
}
char *cms = buf->b_p_cms;
- char *line = ml_get_buf(buf, lnum, false);
+ char *line = ml_get_buf(buf, lnum);
for (char *p = line; *p != NUL; p++) {
if (strncmp(p, marker, markerlen) != 0) {
continue;
@@ -1704,8 +1707,9 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker
/// @return the text for a closed fold
///
/// Otherwise the result is in allocated memory.
-char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char *buf)
- FUNC_ATTR_NONNULL_ARG(1)
+char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char *buf,
+ VirtText *vt)
+ FUNC_ATTR_NONNULL_ALL
{
char *text = NULL;
// an error occurred when evaluating 'fdt' setting
@@ -1742,15 +1746,33 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo
set_vim_var_string(VV_FOLDDASHES, dashes, -1);
set_vim_var_nr(VV_FOLDLEVEL, (varnumber_T)level);
- // skip evaluating foldtext on errors
+ // skip evaluating 'foldtext' on errors
if (!got_fdt_error) {
- win_T *save_curwin = curwin;
+ win_T *const save_curwin = curwin;
+ const sctx_T saved_sctx = current_sctx;
+
curwin = wp;
curbuf = wp->w_buffer;
+ current_sctx = wp->w_p_script_ctx[WV_FDT].script_ctx;
+
+ emsg_off++; // handle exceptions, but don't display errors
+
+ Object obj = eval_foldtext(wp);
+ if (obj.type == kObjectTypeArray) {
+ Error err = ERROR_INIT;
+ *vt = parse_virt_text(obj.data.array, &err, NULL);
+ if (!ERROR_SET(&err)) {
+ *buf = NUL;
+ text = buf;
+ }
+ api_clear_error(&err);
+ } else if (obj.type == kObjectTypeString) {
+ text = obj.data.string.data;
+ obj = NIL;
+ }
+ api_free_object(obj);
- emsg_silent++; // handle exceptions, but don't display errors
- text = eval_to_string_safe(wp->w_p_fdt, NULL, was_set_insecurely(wp, "foldtext", OPT_LOCAL));
- emsg_silent--;
+ emsg_off--;
if (text == NULL || did_emsg) {
got_fdt_error = true;
@@ -1758,9 +1780,10 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo
curwin = save_curwin;
curbuf = curwin->w_buffer;
+ current_sctx = saved_sctx;
}
last_lnum = lnum;
- last_wp = wp;
+ last_wp = wp;
set_vim_var_string(VV_FOLDDASHES, NULL, -1);
if (!did_emsg && save_did_emsg) {
@@ -1786,18 +1809,18 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo
}
}
if (*p != NUL) {
- p = transstr((const char *)text, true);
+ p = transstr(text, true);
xfree(text);
text = p;
}
}
}
if (text == NULL) {
- long count = lnume - lnum + 1;
+ int count = lnume - lnum + 1;
vim_snprintf(buf, FOLD_TEXT_LEN,
- NGETTEXT("+--%3ld line folded",
- "+--%3ld lines folded ", count),
+ NGETTEXT("+--%3d line folded",
+ "+--%3d lines folded ", count),
count);
text = buf;
}
@@ -1888,7 +1911,7 @@ static void foldtext_cleanup(char *str)
static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
{
// Avoid problems when being called recursively.
- if (invalid_top != (linenr_T)0) {
+ if (invalid_top != 0) {
return;
}
@@ -2109,7 +2132,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
}
}
- invalid_top = (linenr_T)0;
+ invalid_top = 0;
}
// foldUpdateIEMSRecurse() {{{2
@@ -2271,12 +2294,12 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
// We will move the start of this fold up, hence we move all
// nested folds (with relative line numbers) down.
foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
- (linenr_T)0, (linenr_T)MAXLNUM,
- (fp->fd_top - firstlnum), 0L);
+ 0, (linenr_T)MAXLNUM,
+ (fp->fd_top - firstlnum), 0);
} else {
// Will move fold down, move nested folds relatively up.
foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
- (linenr_T)0,
+ 0,
(firstlnum - fp->fd_top - 1),
(linenr_T)MAXLNUM,
(fp->fd_top - firstlnum));
@@ -2344,7 +2367,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
fp->fd_len = startlnum - fp->fd_top;
foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
fp->fd_len, (linenr_T)MAXLNUM,
- (linenr_T)MAXLNUM, 0L);
+ (linenr_T)MAXLNUM, 0);
fold_changed = true;
}
} else {
@@ -2504,7 +2527,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
}
// delete following folds that end before the current line
- for (;;) {
+ while (true) {
fp2 = fp + 1;
if (fp2 >= (fold_T *)gap->ga_data + gap->ga_len
|| fp2->fd_top > flp->lnum) {
@@ -2514,7 +2537,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
if (fp2->fd_top < flp->lnum) {
// Make fold that includes lnum start at lnum.
foldMarkAdjustRecurse(flp->wp, &fp2->fd_nested,
- (linenr_T)0, (flp->lnum - fp2->fd_top - 1),
+ 0, (flp->lnum - fp2->fd_top - 1),
(linenr_T)MAXLNUM, (fp2->fd_top - flp->lnum));
fp2->fd_len -= flp->lnum - fp2->fd_top;
fp2->fd_top = flp->lnum;
@@ -2652,7 +2675,7 @@ static void foldRemove(win_T *const wp, garray_T *gap, linenr_T top, linenr_T bo
if (fp->fd_top + fp->fd_len - 1 > bot) {
// 5: Make fold that includes bot start below bot.
foldMarkAdjustRecurse(wp, &fp->fd_nested,
- (linenr_T)0, (bot - fp->fd_top),
+ 0, (bot - fp->fd_top),
(linenr_T)MAXLNUM, (fp->fd_top - bot - 1));
fp->fd_len -= bot - fp->fd_top + 1;
fp->fd_top = bot + 1;
@@ -2718,7 +2741,6 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end)
}
#define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1)
-// -V:VALID_FOLD:V560
#define VALID_FOLD(fp, gap) \
((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
#define FOLD_INDEX(fp, gap) ((size_t)((fp) - ((fold_T *)(gap)->ga_data)))
@@ -2838,7 +2860,7 @@ static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2)
// If the last nested fold in fp1 touches the first nested fold in fp2,
// merge them recursively.
- if (foldFind(gap1, fp1->fd_len - 1, &fp3) && foldFind(gap2, 0L, &fp4)) {
+ if (foldFind(gap1, fp1->fd_len - 1, &fp3) && foldFind(gap2, 0, &fp4)) {
foldMerge(wp, fp3, gap2, fp4);
}
@@ -2869,7 +2891,7 @@ static void foldlevelIndent(fline_T *flp)
linenr_T lnum = flp->lnum + flp->off;
buf_T *buf = flp->wp->w_buffer;
- char *s = skipwhite(ml_get_buf(buf, lnum, false));
+ char *s = skipwhite(ml_get_buf(buf, lnum));
// empty line or lines starting with a character in 'foldignore': level
// depends on surrounding lines
@@ -2926,7 +2948,7 @@ static void foldlevelExpr(fline_T *flp)
const bool save_keytyped = KeyTyped;
int c;
- const int n = eval_foldexpr(flp->wp->w_p_fde, &c);
+ const int n = eval_foldexpr(flp->wp, &c);
KeyTyped = save_keytyped;
switch (c) {
@@ -3031,7 +3053,7 @@ static void foldlevelMarker(fline_T *flp)
flp->start = 0;
flp->lvl_next = flp->lvl;
- char *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, false);
+ char *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off);
while (*s) {
if (*s == cstart
&& strncmp(s + 1, startmarker, foldstartmarkerlen - 1) == 0) {
@@ -3109,7 +3131,7 @@ int put_folds(FILE *fd, win_T *wp)
{
if (foldmethodIsManual(wp)) {
if (put_line(fd, "silent! normal! zE") == FAIL
- || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL
+ || put_folds_recurse(fd, &wp->w_folds, 0) == FAIL
|| put_line(fd, "let &fdl = &fdl") == FAIL) {
return FAIL;
}
@@ -3117,7 +3139,7 @@ int put_folds(FILE *fd, win_T *wp)
// If some folds are manually opened/closed, need to restore that.
if (wp->w_fold_manual) {
- return put_foldopen_recurse(fd, wp, &wp->w_folds, (linenr_T)0);
+ return put_foldopen_recurse(fd, wp, &wp->w_folds, 0);
}
return OK;
@@ -3281,8 +3303,8 @@ void f_foldtext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
}
- long count = foldend - foldstart + 1;
- char *txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
+ int count = foldend - foldstart + 1;
+ char *txt = NGETTEXT("+-%s%3d line: ", "+-%s%3d lines: ", count);
size_t len = strlen(txt)
+ strlen(dashes) // for %s
+ 20 // for %3ld
@@ -3317,10 +3339,25 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
foldinfo_T info = fold_info(curwin, lnum);
if (info.fi_lines > 0) {
- char *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf);
+ VirtText vt = VIRTTEXT_EMPTY;
+ char *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf, &vt);
if (text == buf) {
text = xstrdup(text);
}
+ if (kv_size(vt) > 0) {
+ assert(*text == NUL);
+ for (size_t i = 0; i < kv_size(vt);) {
+ int attr = 0;
+ char *new_text = next_virt_text_chunk(vt, &i, &attr);
+ if (new_text == NULL) {
+ break;
+ }
+ new_text = concat_str(text, new_text);
+ xfree(text);
+ text = new_text;
+ }
+ }
+ clear_virttext(&vt);
rettv->vval.v_string = text;
}
diff --git a/src/nvim/fold.h b/src/nvim/fold.h
index ac1e8c9419..3a70c11792 100644
--- a/src/nvim/fold.h
+++ b/src/nvim/fold.h
@@ -1,28 +1,16 @@
-#ifndef NVIM_FOLD_H
-#define NVIM_FOLD_H
+#pragma once
#include <stdio.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/garray.h"
-#include "nvim/macros.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/fold_defs.h" // IWYU pragma: export
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-// Info used to pass info about a fold from the fold-detection code to the
-// code that displays the foldcolumn.
-typedef struct foldinfo {
- linenr_T fi_lnum; // line number where fold starts
- int fi_level; // level of the fold; when this is zero the
- // other fields are invalid
- int fi_low_level; // lowest fold level that starts in the same
- // line
- linenr_T fi_lines;
-} foldinfo_T;
-
-EXTERN int disable_fold_update INIT(= 0);
+EXTERN int disable_fold_update INIT( = 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "fold.h.generated.h"
#endif
-#endif // NVIM_FOLD_H
diff --git a/src/nvim/fold_defs.h b/src/nvim/fold_defs.h
new file mode 100644
index 0000000000..68ecd9cc7e
--- /dev/null
+++ b/src/nvim/fold_defs.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "nvim/pos_defs.h"
+
+/// Info used to pass info about a fold from the fold-detection code to the
+/// code that displays the foldcolumn.
+typedef struct foldinfo {
+ linenr_T fi_lnum; ///< line number where fold starts
+ int fi_level; ///< level of the fold; when this is zero the
+ ///< other fields are invalid
+ int fi_low_level; ///< lowest fold level that starts in the same line
+ linenr_T fi_lines;
+} foldinfo_T;
+
+enum { FOLD_TEXT_LEN = 51, }; ///< buffer size for get_foldtext()
diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h
index 6c049df6ff..15370dcb3e 100644
--- a/src/nvim/func_attr.h
+++ b/src/nvim/func_attr.h
@@ -41,7 +41,7 @@
// $ gcc -E -dM - </dev/null
// $ echo | clang -dM -E -
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#ifdef FUNC_ATTR_MALLOC
# undef FUNC_ATTR_MALLOC
@@ -99,6 +99,10 @@
# undef FUNC_ATTR_NO_SANITIZE_UNDEFINED
#endif
+#ifdef FUNC_ATTR_NO_SANITIZE_ADDRESS
+# undef FUNC_ATTR_NO_SANITIZE_ADDRESS
+#endif
+
#ifdef FUNC_ATTR_PRINTF
# undef FUNC_ATTR_PRINTF
#endif
@@ -121,7 +125,7 @@
# define REAL_FATTR_NONNULL_ALL __attribute__((nonnull))
# define REAL_FATTR_NONNULL_ARG(...) __attribute__((nonnull(__VA_ARGS__)))
# define REAL_FATTR_NORETURN __attribute__((noreturn))
-# define REAL_FATTR_PRINTF(x, y) __attribute__((format (printf, x, y)))
+# define REAL_FATTR_PRINTF(x, y) __attribute__((format(printf, x, y)))
# if NVIM_HAS_ATTRIBUTE(returns_nonnull)
# define REAL_FATTR_NONNULL_RET __attribute__((returns_nonnull))
@@ -139,6 +143,11 @@
# define REAL_FATTR_NO_SANITIZE_UNDEFINED \
__attribute__((no_sanitize("undefined")))
# endif
+
+# if NVIM_HAS_ATTRIBUTE(no_sanitize_address)
+# define REAL_FATTR_NO_SANITIZE_ADDRESS \
+ __attribute__((no_sanitize_address))
+# endif
# endif
// Define attributes that are not defined for this compiler.
@@ -199,6 +208,10 @@
# define REAL_FATTR_NO_SANITIZE_UNDEFINED
# endif
+# ifndef REAL_FATTR_NO_SANITIZE_ADDRESS
+# define REAL_FATTR_NO_SANITIZE_ADDRESS
+# endif
+
# ifndef REAL_FATTR_PRINTF
# define REAL_FATTR_PRINTF(x, y)
# endif
@@ -209,12 +222,14 @@
# define FUNC_API_FAST
/// Internal C function not exposed in the RPC API.
# define FUNC_API_NOEXPORT
-/// API function not exposed in VimL/eval.
+/// API function not exposed in Vimscript/eval.
# define FUNC_API_REMOTE_ONLY
-/// API function not exposed in VimL/remote.
+/// API function not exposed in Vimscript/remote.
# define FUNC_API_LUA_ONLY
-/// API function checked textlock.
-# define FUNC_API_CHECK_TEXTLOCK
+/// API function fails during textlock.
+# define FUNC_API_TEXTLOCK
+/// API function fails during textlock, but allows cmdwin.
+# define FUNC_API_TEXTLOCK_ALLOW_CMDWIN
/// API function introduced at the given API level.
# define FUNC_API_SINCE(X)
/// API function deprecated since the given API level.
@@ -233,6 +248,7 @@
# define FUNC_ATTR_NONNULL_RET REAL_FATTR_NONNULL_RET
# define FUNC_ATTR_NORETURN REAL_FATTR_NORETURN
# define FUNC_ATTR_NO_SANITIZE_UNDEFINED REAL_FATTR_NO_SANITIZE_UNDEFINED
+# define FUNC_ATTR_NO_SANITIZE_ADDRESS REAL_FATTR_NO_SANITIZE_ADDRESS
# define FUNC_ATTR_PRINTF(x, y) REAL_FATTR_PRINTF(x, y)
#elif !defined(DO_NOT_DEFINE_EMPTY_ATTRIBUTES)
# define FUNC_ATTR_MALLOC
@@ -249,5 +265,6 @@
# define FUNC_ATTR_NONNULL_RET
# define FUNC_ATTR_NORETURN
# define FUNC_ATTR_NO_SANITIZE_UNDEFINED
+# define FUNC_ATTR_NO_SANITIZE_ADDRESS
# define FUNC_ATTR_PRINTF(x, y)
#endif
diff --git a/src/nvim/garray.c b/src/nvim/garray.c
index aa9a44d410..24b6fb0007 100644
--- a/src/nvim/garray.c
+++ b/src/nvim/garray.c
@@ -1,18 +1,16 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file garray.c
///
/// Functions for handling growing arrays.
+#include <stdint.h>
#include <string.h>
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/path.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "garray.c.generated.h" // IWYU pragma: export
diff --git a/src/nvim/garray.h b/src/nvim/garray.h
index 1623c4db7b..a96deda759 100644
--- a/src/nvim/garray.h
+++ b/src/nvim/garray.h
@@ -1,26 +1,12 @@
-#ifndef NVIM_GARRAY_H
-#define NVIM_GARRAY_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
+#include "nvim/garray_defs.h" // IWYU pragma: export
#include "nvim/log.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
-
-/// Structure used for growing arrays.
-/// This is used to store information that only grows, is deleted all at
-/// once, and needs to be accessed by index. See ga_clear() and ga_grow().
-typedef struct growarray {
- int ga_len; // current number of items used
- int ga_maxlen; // maximum number of items possible
- int ga_itemsize; // sizeof(item)
- int ga_growsize; // number of items to grow each time
- void *ga_data; // pointer to the first item
-} garray_T;
-
-#define GA_EMPTY_INIT_VALUE { 0, 0, 0, 1, NULL }
-#define GA_INIT(itemsize, growsize) { 0, 0, (itemsize), (growsize), NULL }
+#include "nvim/types_defs.h"
#define GA_EMPTY(ga_ptr) ((ga_ptr)->ga_len <= 0)
@@ -52,7 +38,7 @@ static inline void *ga_append_via_ptr(garray_T *gap, size_t item_size)
///
/// @param gap the garray to be freed
/// @param item_type type of the item in the garray
-/// @param free_item_fn free function that takes (*item_type) as parameter
+/// @param free_item_fn free function that takes (item_type *) as parameter
#define GA_DEEP_CLEAR(gap, item_type, free_item_fn) \
do { \
garray_T *_gap = (gap); \
@@ -72,5 +58,3 @@ static inline void *ga_append_via_ptr(garray_T *gap, size_t item_size)
///
/// @param gap the garray to be freed
#define GA_DEEP_CLEAR_PTR(gap) GA_DEEP_CLEAR(gap, void *, FREE_PTR_PTR)
-
-#endif // NVIM_GARRAY_H
diff --git a/src/nvim/garray_defs.h b/src/nvim/garray_defs.h
new file mode 100644
index 0000000000..5f4032884e
--- /dev/null
+++ b/src/nvim/garray_defs.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <stddef.h>
+
+/// Structure used for growing arrays.
+/// This is used to store information that only grows, is deleted all at
+/// once, and needs to be accessed by index. See ga_clear() and ga_grow().
+typedef struct growarray {
+ int ga_len; // current number of items used
+ int ga_maxlen; // maximum number of items possible
+ int ga_itemsize; // sizeof(item)
+ int ga_growsize; // number of items to grow each time
+ void *ga_data; // pointer to the first item
+} garray_T;
+
+#define GA_EMPTY_INIT_VALUE { 0, 0, 0, 1, NULL }
+#define GA_INIT(itemsize, growsize) { 0, 0, (itemsize), (growsize), NULL }
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index 3e89b60b4a..f33da452ff 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -1,4 +1,4 @@
-local lpeg = require('lpeg')
+local lpeg = vim.lpeg
-- lpeg grammar for building api metadata from a set of header files. It
-- ignores comments and preprocessor commands and parses a very small subset
@@ -47,7 +47,8 @@ local c_proto = Ct(
(fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) *
(fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) *
- (fill * Cg((P('FUNC_API_CHECK_TEXTLOCK') * Cc(true)), 'check_textlock') ^ -1) *
+ (fill * (Cg(P('FUNC_API_TEXTLOCK_ALLOW_CMDWIN') * Cc(true), 'textlock_allow_cmdwin') +
+ Cg(P('FUNC_API_TEXTLOCK') * Cc(true), 'textlock')) ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) *
(fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) *
(fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) *
@@ -55,5 +56,11 @@ local c_proto = Ct(
fill * P(';')
)
-local grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
+local c_field = Ct(Cg(c_id, 'type') * ws * Cg(c_id, 'name') * fill * P(';') * fill)
+local c_keyset = Ct(
+ P('typedef') * ws * P('struct') * fill * P('{') * fill *
+ Cg(Ct(c_field ^ 1), 'fields') *
+ P('}') * fill * P('Dict') * fill * P('(') * Cg(c_id, 'keyset_name') * fill * P(')') * P(';'))
+
+local grammar = Ct((c_proto + c_comment + c_preproc + ws + c_keyset) ^ 1)
return {grammar=grammar, typed_container=typed_container}
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 35f6bf8455..9720cca477 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -1,38 +1,23 @@
-local mpack = require('mpack')
-
--- we need at least 4 arguments since the last two are output files
-if arg[1] == '--help' then
- print('Usage: genmsgpack.lua args')
- print('Args: 1: source directory')
- print(' 2: dispatch output file (dispatch_wrappers.generated.h)')
- print(' 3: functions metadata output file (funcs_metadata.generated.h)')
- print(' 4: API metadata output file (api_metadata.mpack)')
- print(' 5: lua C bindings output file (lua_api_c_bindings.generated.c)')
- print(' rest: C files where API functions are defined')
-end
-assert(#arg >= 4)
-local functions = {}
+local mpack = vim.mpack
-local nvimdir = arg[1]
-package.path = nvimdir .. '/?.lua;' .. package.path
+local hashy = require'generators.hashy'
-_G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')()
-_G.vim.inspect = loadfile(nvimdir..'/../../runtime/lua/vim/inspect.lua')()
+assert(#arg >= 5)
+-- output h file with generated dispatch functions (dispatch_wrappers.generated.h)
+local dispatch_outputf = arg[1]
+-- output h file with packed metadata (funcs_metadata.generated.h)
+local funcs_metadata_outputf = arg[2]
+-- output metadata mpack file, for use by other build scripts (api_metadata.mpack)
+local mpack_outputf = arg[3]
+local lua_c_bindings_outputf = arg[4] -- lua_api_c_bindings.generated.c
+local keysets_outputf = arg[5] -- keysets_defs.generated.h
-local hashy = require'generators.hashy'
+local functions = {}
-- names of all headers relative to the source root (for inclusion in the
-- generated file)
local headers = {}
--- output h file with generated dispatch functions
-local dispatch_outputf = arg[2]
--- output h file with packed metadata
-local funcs_metadata_outputf = arg[3]
--- output metadata mpack file, for use by other build scripts
-local mpack_outputf = arg[4]
-local lua_c_bindings_outputf = arg[5]
-
-- set of function names, used to detect duplicates
local function_names = {}
@@ -42,6 +27,69 @@ local function startswith(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
+local function add_function(fn)
+ local public = startswith(fn.name, "nvim_") or fn.deprecated_since
+ if public and not fn.noexport then
+ functions[#functions + 1] = fn
+ function_names[fn.name] = true
+ if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then
+ -- function receives the "args" as a parameter
+ fn.receives_array_args = true
+ -- remove the args parameter
+ table.remove(fn.parameters, 2)
+ end
+ if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
+ -- this function should receive the channel id
+ fn.receives_channel_id = true
+ -- remove the parameter since it won't be passed by the api client
+ table.remove(fn.parameters, 1)
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then
+ -- function can fail if the last parameter type is 'Error'
+ fn.can_fail = true
+ -- remove the error parameter, msgpack has it's own special field
+ -- for specifying errors
+ fn.parameters[#fn.parameters] = nil
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then
+ -- return value is allocated in an arena
+ fn.arena_return = true
+ fn.parameters[#fn.parameters] = nil
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
+ fn.has_lua_imp = true
+ fn.parameters[#fn.parameters] = nil
+ end
+ end
+end
+
+local keysets = {}
+
+local function add_keyset(val)
+ local keys = {}
+ local types = {}
+ local is_set_name = 'is_set__' .. val.keyset_name .. '_'
+ local has_optional = false
+ for i,field in ipairs(val.fields) do
+ if field.type ~= 'Object' then
+ types[field.name] = field.type
+ end
+ if field.name ~= is_set_name and field.type ~= 'OptionalKeys' then
+ table.insert(keys, field.name)
+ else
+ if i > 1 then
+ error("'is_set__{type}_' must be first if present")
+ elseif field.name ~= is_set_name then
+ error(val.keyset_name..": name of first key should be "..is_set_name)
+ elseif field.type ~= 'OptionalKeys' then
+ error("'"..is_set_name.."' must have type 'OptionalKeys'")
+ end
+ has_optional = true
+ end
+ end
+ table.insert(keysets, {name=val.keyset_name, keys=keys, types=types, has_optional=has_optional})
+end
+
-- read each input file, parse and append to the api metadata
for i = 6, #arg do
local full_path = arg[i]
@@ -55,39 +103,11 @@ for i = 6, #arg do
local tmp = c_grammar.grammar:match(input:read('*all'))
for j = 1, #tmp do
- local fn = tmp[j]
- local public = startswith(fn.name, "nvim_") or fn.deprecated_since
- if public and not fn.noexport then
- functions[#functions + 1] = tmp[j]
- function_names[fn.name] = true
- if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then
- -- function receives the "args" as a parameter
- fn.receives_array_args = true
- -- remove the args parameter
- table.remove(fn.parameters, 2)
- end
- if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
- -- this function should receive the channel id
- fn.receives_channel_id = true
- -- remove the parameter since it won't be passed by the api client
- table.remove(fn.parameters, 1)
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then
- -- function can fail if the last parameter type is 'Error'
- fn.can_fail = true
- -- remove the error parameter, msgpack has it's own special field
- -- for specifying errors
- fn.parameters[#fn.parameters] = nil
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then
- -- return value is allocated in an arena
- fn.arena_return = true
- fn.parameters[#fn.parameters] = nil
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
- fn.has_lua_imp = true
- fn.parameters[#fn.parameters] = nil
- end
+ local val = tmp[j]
+ if val.keyset_name then
+ add_keyset(val)
+ else
+ add_function(val)
end
end
input:close()
@@ -187,7 +207,7 @@ end
-- serialize the API metadata using msgpack and embed into the resulting
-- binary for easy querying by clients
local funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb')
-local packed = mpack.pack(exported_functions)
+local packed = mpack.encode(exported_functions)
local dump_bin_array = require("generators.dump_bin_array")
dump_bin_array(funcs_metadata_output, 'funcs_metadata', packed)
funcs_metadata_output:close()
@@ -195,6 +215,7 @@ funcs_metadata_output:close()
-- start building the dispatch wrapper output
local output = io.open(dispatch_outputf, 'wb')
+local keysets_defs = io.open(keysets_outputf, 'wb')
-- ===========================================================================
-- NEW API FILES MUST GO HERE.
@@ -203,10 +224,11 @@ local output = io.open(dispatch_outputf, 'wb')
-- so that the dispatcher can find the C functions that you are creating!
-- ===========================================================================
output:write([[
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
#include "nvim/log.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/vim.h"
#include "nvim/api/autocmd.h"
#include "nvim/api/buffer.h"
@@ -224,6 +246,62 @@ output:write([[
]])
+for _,k in ipairs(keysets) do
+ local c_name = {}
+
+ for i = 1,#k.keys do
+ -- some keys, like "register" are c keywords and get
+ -- escaped with a trailing _ in the struct.
+ if vim.endswith(k.keys[i], "_") then
+ local orig = k.keys[i]
+ k.keys[i] = string.sub(k.keys[i],1, #(k.keys[i]) - 1)
+ c_name[k.keys[i]] = orig
+ k.types[k.keys[i]] = k.types[orig]
+ end
+ end
+
+ local neworder, hashfun = hashy.hashy_hash(k.name, k.keys, function (idx)
+ return k.name.."_table["..idx.."].str"
+ end)
+
+ keysets_defs:write("extern KeySetLink "..k.name.."_table[];\n")
+
+ local function typename(type)
+ if type ~= nil then
+ return "kObjectType"..type
+ else
+ return "kObjectTypeNil"
+ end
+ end
+
+ output:write("KeySetLink "..k.name.."_table[] = {\n")
+ for i, key in ipairs(neworder) do
+ local ind = -1
+ if k.has_optional then
+ ind = i
+ keysets_defs:write("#define KEYSET_OPTIDX_"..k.name.."__"..key.." "..ind.."\n")
+ end
+ output:write(' {"'..key..'", offsetof(KeyDict_'..k.name..", "..(c_name[key] or key).."), "..typename(k.types[key])..", "..ind.."},\n")
+ end
+ output:write(' {NULL, 0, kObjectTypeNil, -1},\n')
+ output:write("};\n\n")
+
+ output:write(hashfun)
+
+ output:write([[
+KeySetLink *KeyDict_]]..k.name..[[_get_field(const char *str, size_t len)
+{
+ int hash = ]]..k.name..[[_hash(str, len);
+ if (hash == -1) {
+ return NULL;
+ }
+ return &]]..k.name..[[_table[hash];
+}
+
+]])
+ keysets_defs:write("#define api_free_keydict_"..k.name.."(x) api_free_keydict(x, "..k.name.."_table)\n")
+end
+
local function real_type(type)
local rv = type
local rmatch = string.match(type, "Dict%(([_%w]+)%)")
@@ -257,9 +335,8 @@ for i = 1, #functions do
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Arena* arena, Error *error)')
output:write('\n{')
- output:write('\n#if MIN_LOG_LEVEL <= LOGLVL_DBG')
- output:write('\n logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke '
- ..fn.name..'", channel_id);')
+ output:write('\n#ifdef NVIM_LOG_DEBUG')
+ output:write('\n DLOG("RPC: ch %" PRIu64 ": invoke '..fn.name..'", channel_id);')
output:write('\n#endif')
output:write('\n Object ret = NIL;')
-- Declare/initialize variables that will hold converted arguments
@@ -337,8 +414,13 @@ for i = 1, #functions do
args[#args + 1] = converted
end
- if fn.check_textlock then
- output:write('\n if (textlock != 0) {')
+ if fn.textlock then
+ output:write('\n if (text_locked()) {')
+ output:write('\n api_set_error(error, kErrorTypeException, "%s", get_text_locked_msg());')
+ output:write('\n goto cleanup;')
+ output:write('\n }\n')
+ elseif fn.textlock_allow_cmdwin then
+ output:write('\n if (textlock != 0 || expr_map_locked()) {')
output:write('\n api_set_error(error, kErrorTypeException, "%s", e_textlock);')
output:write('\n goto cleanup;')
output:write('\n }\n')
@@ -444,8 +526,9 @@ output:write(hashfun)
output:close()
+functions.keysets = keysets
local mpack_output = io.open(mpack_outputf, 'wb')
-mpack_output:write(mpack.pack(functions))
+mpack_output:write(mpack.encode(functions))
mpack_output:close()
local function include_headers(output_handle, headers_to_include)
@@ -467,15 +550,16 @@ end
output = io.open(lua_c_bindings_outputf, 'wb')
output:write([[
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
#include "nvim/func_attr.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
@@ -493,6 +577,7 @@ local function process_function(fn)
static int %s(lua_State *lstate)
{
Error err = ERROR_INIT;
+ char *err_param = 0;
if (lua_gettop(lstate) != %i) {
api_set_error(&err, kErrorTypeValidation, "Expected %i argument%s");
goto exit_0;
@@ -512,9 +597,16 @@ local function process_function(fn)
]], fn.name))
end
- if fn.check_textlock then
+ if fn.textlock then
write_shifted_output(output, [[
- if (textlock != 0) {
+ if (text_locked()) {
+ api_set_error(&err, kErrorTypeException, "%s", get_text_locked_msg());
+ goto exit_0;
+ }
+ ]])
+ elseif fn.textlock_allow_cmdwin then
+ write_shifted_output(output, [[
+ if (textlock != 0 || expr_map_locked()) {
api_set_error(&err, kErrorTypeException, "%s", e_textlock);
goto exit_0;
}
@@ -533,19 +625,22 @@ local function process_function(fn)
extra = "true, "
end
local errshift = 0
+ local seterr = ''
if string.match(param_type, '^KeyDict_') then
write_shifted_output(output, string.format([[
- %s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, %s&err);]], param_type, cparam, cparam, param_type, extra))
+ %s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &err);]], param_type, cparam, cparam, param_type))
cparam = '&'..cparam
errshift = 1 -- free incomplete dict on error
else
write_shifted_output(output, string.format([[
const %s %s = nlua_pop_%s(lstate, %s&err);]], param[1], cparam, param_type, extra))
+ seterr = [[
+ err_param = "]]..param[2]..[[";]]
end
write_shifted_output(output, string.format([[
- if (ERROR_SET(&err)) {
+ if (ERROR_SET(&err)) {]]..seterr..[[
goto exit_%u;
}
@@ -589,9 +684,14 @@ local function process_function(fn)
exit_0:
if (ERROR_SET(&err)) {
luaL_where(lstate, 1);
+ if (err_param) {
+ lua_pushstring(lstate, "Invalid '");
+ lua_pushstring(lstate, err_param);
+ lua_pushstring(lstate, "': ");
+ }
lua_pushstring(lstate, err.msg);
api_clear_error(&err);
- lua_concat(lstate, 2);
+ lua_concat(lstate, err_param ? 5 : 2);
return lua_error(lstate);
}
]]
@@ -620,9 +720,10 @@ local function process_function(fn)
}
]], return_type))
else
+ local special = (fn.since ~= nil and fn.since < 11)
write_shifted_output(output, string.format([[
- nlua_push_%s(lstate, ret, true);
- ]], return_type))
+ nlua_push_%s(lstate, ret, %s);
+ ]], return_type, tostring(special)))
end
write_shifted_output(output, string.format([[
@@ -670,3 +771,4 @@ output:write([[
]])
output:close()
+keysets_defs:close()
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index 827097f69d..e2af5f8d44 100755..100644
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -1,19 +1,15 @@
-local mpack = require('mpack')
+local mpack = vim.mpack
-local nvimdir = arg[1]
-package.path = nvimdir .. '/?.lua;' .. package.path
-
-assert(#arg == 6)
-local input = io.open(arg[2], 'rb')
-local call_output = io.open(arg[3], 'wb')
-local remote_output = io.open(arg[4], 'wb')
-local metadata_output = io.open(arg[5], 'wb')
-local client_output = io.open(arg[6], 'wb')
+assert(#arg == 5)
+local input = io.open(arg[1], 'rb')
+local call_output = io.open(arg[2], 'wb')
+local remote_output = io.open(arg[3], 'wb')
+local metadata_output = io.open(arg[4], 'wb')
+local client_output = io.open(arg[5], 'wb')
local c_grammar = require('generators.c_grammar')
local events = c_grammar.grammar:match(input:read('*all'))
-_G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')()
local hashy = require'generators.hashy'
local function write_signature(output, ev, prefix, notype)
@@ -120,7 +116,6 @@ for i = 1, #events do
if ev.remote_only then
call_output:write(' Array args = call_buf;\n')
write_arglist(call_output, ev)
- call_output:write(' UI_LOG('..ev.name..');\n')
call_output:write(' ui_call_event("'..ev.name..'", args);\n')
elseif ev.compositor_impl then
call_output:write(' ui_comp_'..ev.name)
@@ -195,7 +190,7 @@ for _,ev in ipairs(events) do
end
end
-local packed = mpack.pack(exported_events)
+local packed = mpack.encode(exported_events)
local dump_bin_array = require("generators.dump_bin_array")
dump_bin_array(metadata_output, 'ui_events_metadata', packed)
metadata_output:close()
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index 4097ff7dc5..f9e9c6b0a8 100755..100644
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -1,12 +1,9 @@
-#!/usr/bin/lua
-
local fname = arg[1]
local static_fname = arg[2]
local non_static_fname = arg[3]
local preproc_fname = arg[4]
-
-local lpeg = require('lpeg')
+local lpeg = vim.lpeg
local fold = function (func, ...)
local result = nil
@@ -174,7 +171,7 @@ static functions into static.h and declarations for non-static functions into
non-static.h. File `definitions.i' should contain an already preprocessed
version of definitions.c and it is the only one which is actually parsed,
definitions.c is needed only to determine functions from which file out of all
-functions found in definitions.i are needed.
+functions found in definitions.i are needed and to generate an IWYU comment.
Additionally uses the following environment variables:
@@ -227,6 +224,18 @@ local non_static = header .. [[
local static = header
+if fname:find('.*/src/nvim/.*%.c$') then
+ -- Add an IWYU pragma comment if the corresponding .h file exists.
+ local header_fname = fname:sub(1, -3) .. '.h'
+ local header_f = io.open(header_fname, 'r')
+ if header_f ~= nil then
+ header_f:close()
+ non_static = ([[
+// IWYU pragma: private, include "%s"
+]]):format(header_fname:gsub('.*/src/nvim/', 'nvim/')) .. non_static
+ end
+end
+
local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"'
local init = 1
@@ -244,12 +253,7 @@ while init ~= nil do
curfile = file
is_needed_file = (curfile == neededfile)
declline = tonumber(line) - 1
- local curdir_start = dir:find('src/nvim/')
- if curdir_start ~= nil then
- curdir = dir:sub(curdir_start + #('src/nvim/'))
- else
- curdir = dir
- end
+ curdir = dir:gsub('.*/src/nvim/', '')
else
declline = declline - 1
end
@@ -311,7 +315,7 @@ F = io.open(static_fname, 'w')
F:write(static)
F:close()
--- Before generating the non-static headers, check if the current file(if
+-- Before generating the non-static headers, check if the current file (if
-- exists) is different from the new one. If they are the same, we won't touch
-- the current version to avoid triggering an unnecessary rebuilds of modules
-- that depend on this one
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index baed6a74c2..7b272c337e 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -1,37 +1,23 @@
-local mpack = require('mpack')
+local mpack = vim.mpack
-local nvimsrcdir = arg[1]
-local shared_file = arg[2]
-local autodir = arg[3]
-local metadata_file = arg[4]
-local funcs_file = arg[5]
-
-_G.vim = loadfile(shared_file)()
-
-if nvimsrcdir == '--help' then
- print([[
-Usage:
- lua gen_eval.lua src/nvim build/src/nvim/auto
-
-Will generate build/src/nvim/auto/funcs.generated.h with definition of functions
-static const array.
-]])
- os.exit(0)
-end
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+local autodir = arg[1]
+local metadata_file = arg[2]
+local funcs_file = arg[3]
local funcsfname = autodir .. '/funcs.generated.h'
+--Will generate funcs.generated.h with definition of functions static const array.
+
local hashy = require'generators.hashy'
-local hashpipe = io.open(funcsfname, 'wb')
+local hashpipe = assert(io.open(funcsfname, 'wb'))
hashpipe:write([[
#include "nvim/arglist.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdhist.h"
#include "nvim/digraph.h"
+#include "nvim/eval.h"
#include "nvim/eval/buffer.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
@@ -46,11 +32,16 @@ hashpipe:write([[
#include "nvim/match.h"
#include "nvim/mbyte.h"
#include "nvim/menu.h"
+#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/quickfix.h"
+#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/state.h"
+#include "nvim/strings.h"
#include "nvim/sign.h"
#include "nvim/testing.h"
+#include "nvim/undo.h"
]])
@@ -62,7 +53,7 @@ for _, func in pairs(funcs) do
end
end
-local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all"))
+local metadata = mpack.decode(io.open(metadata_file, 'rb'):read("*all"))
for _,fun in ipairs(metadata) do
if fun.eval then
funcs[fun.name] = {
@@ -73,16 +64,22 @@ for _,fun in ipairs(metadata) do
end
end
-local func_names = vim.tbl_keys(funcs)
+local func_names = vim.tbl_filter(function(name)
+ return name:match('__%d*$') == nil
+end, vim.tbl_keys(funcs))
+
table.sort(func_names)
-local funcsdata = io.open(funcs_file, 'w')
-funcsdata:write(mpack.pack(func_names))
+
+local funcsdata = assert(io.open(funcs_file, 'w'))
+funcsdata:write(mpack.encode(func_names))
funcsdata:close()
local neworder, hashfun = hashy.hashy_hash("find_internal_func", func_names, function (idx)
return "functions["..idx.."].name"
end)
+
hashpipe:write("static const EvalFuncDef functions[] = {\n")
+
for _, name in ipairs(neworder) do
local def = funcs[name]
local args = def.args or 0
@@ -93,12 +90,12 @@ for _, name in ipairs(neworder) do
end
local base = def.base or "BASE_NONE"
local func = def.func or ('f_' .. name)
- local data = def.data or "{ .nullptr = NULL }"
+ local data = def.data or "{ .null = NULL }"
local fast = def.fast and 'true' or 'false'
hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, %s },\n')
:format(name, args[1], args[2], base, fast, func, data))
end
-hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, { .nullptr = NULL } },\n')
+hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, { .null = NULL } },\n')
hashpipe:write("};\n\n")
hashpipe:write(hashfun)
hashpipe:close()
diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua
index 8db7f22452..4763a2f463 100644
--- a/src/nvim/generators/gen_events.lua
+++ b/src/nvim/generators/gen_events.lua
@@ -1,13 +1,5 @@
-if arg[1] == '--help' then
- print('Usage: gen_events.lua src/nvim enum_file event_names_file')
- os.exit(0)
-end
-
-local nvimsrcdir = arg[1]
-local fileio_enum_file = arg[2]
-local names_file = arg[3]
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+local fileio_enum_file = arg[1]
+local names_file = arg[2]
local auevents = require('auevents')
local events = auevents.events
@@ -16,7 +8,10 @@ local aliases = auevents.aliases
local enum_tgt = io.open(fileio_enum_file, 'w')
local names_tgt = io.open(names_file, 'w')
-enum_tgt:write('typedef enum auto_event {')
+enum_tgt:write([[
+// IWYU pragma: private, include "nvim/autocmd_defs.h"
+
+typedef enum auto_event {]])
names_tgt:write([[
static const struct event_name {
size_t len;
@@ -43,27 +38,24 @@ names_tgt:write('\n {0, NULL, (event_T)0},')
enum_tgt:write('\n} event_T;\n')
names_tgt:write('\n};\n')
-local gen_autopat_events = function(name)
- names_tgt:write(string.format('\nstatic AutoPat *%s[NUM_EVENTS] = {\n ', name))
+do
+ names_tgt:write('\nstatic AutoCmdVec autocmds[NUM_EVENTS] = {\n ')
local line_len = 1
for _ = 1,((#events) - 1) do
- line_len = line_len + #(' NULL,')
+ line_len = line_len + #(' KV_INITIAL_VALUE,')
if line_len > 80 then
names_tgt:write('\n ')
- line_len = 1 + #(' NULL,')
+ line_len = 1 + #(' KV_INITIAL_VALUE,')
end
- names_tgt:write(' NULL,')
+ names_tgt:write(' KV_INITIAL_VALUE,')
end
- if line_len + #(' NULL') > 80 then
- names_tgt:write('\n NULL')
+ if line_len + #(' KV_INITIAL_VALUE') > 80 then
+ names_tgt:write('\n KV_INITIAL_VALUE')
else
- names_tgt:write(' NULL')
+ names_tgt:write(' KV_INITIAL_VALUE')
end
names_tgt:write('\n};\n')
end
-gen_autopat_events("first_autopat")
-gen_autopat_events("last_autopat")
-
enum_tgt:close()
names_tgt:close()
diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua
index 0c1051b04e..ae8c952648 100644
--- a/src/nvim/generators/gen_ex_cmds.lua
+++ b/src/nvim/generators/gen_ex_cmds.lua
@@ -1,20 +1,8 @@
-local nvimsrcdir = arg[1]
-local includedir = arg[2]
-local autodir = arg[3]
+local includedir = arg[1]
+local autodir = arg[2]
-if nvimsrcdir == '--help' then
- print ([[
-Usage:
- lua genex_cmds.lua src/nvim build/include build/src/nvim/auto
-
-Will generate files build/include/ex_cmds_enum.generated.h with cmdidx_T
-enum and build/src/nvim/auto/ex_cmds_defs.generated.h with main Ex commands
-definitions.
-]])
- os.exit(0)
-end
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+-- Will generate files ex_cmds_enum.generated.h with cmdidx_T enum
+-- and ex_cmds_defs.generated.h with main Ex commands definitions.
local enumfname = includedir .. '/ex_cmds_enum.generated.h'
local defsfname = autodir .. '/ex_cmds_defs.generated.h'
@@ -41,11 +29,13 @@ static const uint16_t cmdidxs1[%u] = {
-- Values in cmdidxs2[c1][c2] are relative to cmdidxs1[c1] so that they
-- fit in a byte.
local cmdidxs2_out = string.format([[
-static const char_u cmdidxs2[%u][%u] = {
+static const uint8_t cmdidxs2[%u][%u] = {
/* a b c d e f g h i j k l m n o p q r s t u v w x y z */
]], a_to_z, a_to_z)
enumfile:write([[
+// IWYU pragma: private, include "nvim/ex_cmds_defs.h"
+
typedef enum CMD_index {
]])
defsfile:write(string.format([[
@@ -66,8 +56,8 @@ defsfile:write(string.format([[
#include "nvim/ex_session.h"
#include "nvim/help.h"
#include "nvim/indent.h"
-#include "nvim/locale.h"
#include "nvim/lua/executor.h"
+#include "nvim/lua/secure.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/match.h"
@@ -75,6 +65,7 @@ defsfile:write(string.format([[
#include "nvim/message.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/os/lang.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
@@ -113,7 +104,7 @@ for _, cmd in ipairs(defs) do
end
local preview_func
if cmd.preview_func then
- preview_func = string.format("(ex_preview_func_T)&%s", cmd.preview_func)
+ preview_func = string.format("&%s", cmd.preview_func)
else
preview_func = "NULL"
end
diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua
deleted file mode 100644
index b1c1f3e2d8..0000000000
--- a/src/nvim/generators/gen_keysets.lua
+++ /dev/null
@@ -1,80 +0,0 @@
-local nvimsrcdir = arg[1]
-local shared_file = arg[2]
-local funcs_file = arg[3]
-local defs_file = arg[4]
-
-_G.vim = loadfile(shared_file)()
-
-if nvimsrcdir == '--help' then
- print([[
-Usage:
- lua gen_keyset.lua TODOFIXUPDATETHIS
-
-Will generate build/src/nvim/auto/keyset.generated.h with definition of functions
-static const array.
-]])
- os.exit(0)
-end
-
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
-local hashy = require'generators.hashy'
-
-local funcspipe = io.open(funcs_file, 'wb')
-local defspipe = io.open(defs_file, 'wb')
-
-local keysets = require'api.keysets'
-
-local keywords = {
- register = true;
- default = true;
-}
-
-local function sanitize(key)
- if keywords[key] then
- return key .. "_"
- end
- return key
-end
-
-for _, v in ipairs(keysets) do
- local name = v[1]
- local keys = v[2]
- local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx)
- return name.."_table["..idx.."].str"
- end)
-
- defspipe:write("typedef struct {\n")
- for _, key in ipairs(neworder) do
- defspipe:write(" Object "..sanitize(key)..";\n")
- end
- defspipe:write("} KeyDict_"..name..";\n\n")
-
- defspipe:write("extern KeySetLink "..name.."_table[];\n")
-
- funcspipe:write("KeySetLink "..name.."_table[] = {\n")
- for _, key in ipairs(neworder) do
- funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n")
- end
- funcspipe:write(' {NULL, 0},\n')
- funcspipe:write("};\n\n")
-
- funcspipe:write(hashfun)
-
- funcspipe:write([[
-Object *KeyDict_]]..name..[[_get_field(void *retval, const char *str, size_t len)
-{
- int hash = ]]..name..[[_hash(str, len);
- if (hash == -1) {
- return NULL;
- }
-
- return (Object *)((char *)retval + ]]..name..[[_table[hash].ptr_off);
-}
-
-]])
- defspipe:write("#define api_free_keydict_"..name.."(x) api_free_keydict(x, "..name.."_table)\n")
-end
-
-funcspipe:close()
-defspipe:close()
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index edb7dae159..26ade2745d 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -1,14 +1,6 @@
-if arg[1] == '--help' then
- print('Usage: genoptions.lua src/nvim options_file')
- os.exit(0)
-end
-
-local nvimsrcdir = arg[1]
-local options_file = arg[2]
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+local options_file = arg[1]
-local opt_fd = io.open(options_file, 'w')
+local opt_fd = assert(io.open(options_file, 'w'))
local w = function(s)
if s:match('^ %.') then
@@ -18,6 +10,7 @@ local w = function(s)
end
end
+--- @module 'nvim.options'
local options = require('options')
local cstr = options.cstr
@@ -42,11 +35,16 @@ local redraw_flags={
local list_flags={
comma='P_COMMA',
onecomma='P_ONECOMMA',
+ commacolon='P_COMMA|P_COLON',
+ onecommacolon='P_ONECOMMA|P_COLON',
flags='P_FLAGLIST',
flagscomma='P_COMMA|P_FLAGLIST',
}
-local get_flags = function(o)
+--- @param o vim.option_meta
+--- @return string
+local function get_flags(o)
+ --- @type string[]
local ret = {type_flags[o.type]}
local add_flag = function(f)
ret[1] = ret[1] .. '|' .. f
@@ -89,8 +87,10 @@ local get_flags = function(o)
return ret[1]
end
-local get_cond
-get_cond = function(c, base_string)
+--- @param c string|string[]
+--- @param base_string? string
+--- @return string
+local function get_cond(c, base_string)
local cond_string = base_string or '#if '
if type(c) == 'table' then
cond_string = cond_string .. get_cond(c[1], '')
@@ -112,11 +112,11 @@ local value_dumpers = {
string=cstr,
boolean=function(v) return v and 'true' or 'false' end,
number=function(v) return ('%iL'):format(v) end,
- ['nil']=function(_) return '0L' end,
+ ['nil']=function(_) return '0' end,
}
local get_value = function(v)
- return '(char *) ' .. value_dumpers[type(v)](v)
+ return '(void *) ' .. value_dumpers[type(v)](v)
end
local get_defaults = function(d,n)
@@ -126,9 +126,12 @@ local get_defaults = function(d,n)
return get_value(d)
end
+--- @type {[1]:string,[2]:string}[]
local defines = {}
-local dump_option = function(i, o)
+--- @param i integer
+--- @param o vim.option_meta
+local function dump_option(i, o)
w(' [' .. ('%u'):format(i - 1) .. ']={')
w(' .fullname=' .. cstr(o.full_name))
if o.abbreviation then
@@ -139,10 +142,14 @@ local dump_option = function(i, o)
w(get_cond(o.enable_if))
end
if o.varname then
- w(' .var=(char_u *)&' .. o.varname)
+ w(' .var=&' .. o.varname)
+ -- Immutable options should directly point to the default value
+ elseif o.immutable then
+ w((' .var=&options[%u].def_val'):format(i - 1))
elseif #o.scope == 1 and o.scope[1] == 'window' then
w(' .var=VAR_WIN')
end
+ w(' .immutable=' .. (o.immutable and 'true' or 'false'))
if #o.scope == 1 and o.scope[1] == 'global' then
w(' .indir=PV_NONE')
else
@@ -162,6 +169,12 @@ local dump_option = function(i, o)
table.insert(defines, { 'PV_' .. varname:sub(3):upper() , pv_name})
w(' .indir=' .. pv_name)
end
+ if o.cb then
+ w(' .opt_did_set_cb=' .. o.cb)
+ end
+ if o.expand_cb then
+ w(' .opt_expand_cb=' .. o.expand_cb)
+ end
if o.enable_if then
w('#else')
w(' .var=NULL')
@@ -184,7 +197,19 @@ local dump_option = function(i, o)
w(' },')
end
-w('static vimoption_T options[] = {')
+w([[
+#include "nvim/ex_getln.h"
+#include "nvim/insexpand.h"
+#include "nvim/mapping.h"
+#include "nvim/ops.h"
+#include "nvim/option.h"
+#include "nvim/optionstr.h"
+#include "nvim/quickfix.h"
+#include "nvim/runtime.h"
+#include "nvim/tag.h"
+#include "nvim/window.h"
+
+static vimoption_T options[] = {]])
for i, o in ipairs(options.options) do
dump_option(i, o)
end
diff --git a/src/nvim/generators/gen_vimvim.lua b/src/nvim/generators/gen_vimvim.lua
new file mode 100644
index 0000000000..29355d3cda
--- /dev/null
+++ b/src/nvim/generators/gen_vimvim.lua
@@ -0,0 +1,147 @@
+local mpack = vim.mpack
+
+local syntax_file = arg[1]
+local funcs_file = arg[2]
+
+local lld = {}
+local syn_fd = io.open(syntax_file, 'w')
+lld.line_length = 0
+local function w(s)
+ syn_fd:write(s)
+ if s:find('\n') then
+ lld.line_length = #(s:gsub('.*\n', ''))
+ else
+ lld.line_length = lld.line_length + #s
+ end
+end
+
+local options = require('options')
+local auevents = require('auevents')
+local ex_cmds = require('ex_cmds')
+
+local function cmd_kw(prev_cmd, cmd)
+ if not prev_cmd then
+ return cmd:sub(1, 1) .. '[' .. cmd:sub(2) .. ']'
+ else
+ local shift = 1
+ while cmd:sub(shift, shift) == prev_cmd:sub(shift, shift) do
+ shift = shift + 1
+ end
+ if cmd:sub(1, shift) == 'def' then
+ shift = shift + 1
+ end
+ if shift >= #cmd then
+ return cmd
+ else
+ return cmd:sub(1, shift) .. '[' .. cmd:sub(shift + 1) .. ']'
+ end
+ end
+end
+
+-- Exclude these from the vimCommand keyword list, they are handled specially
+-- in syntax/vim.vim (vimAugroupKey, vimAutoCmd, vimGlobal, vimSubst). #9327
+local function is_special_cased_cmd(cmd)
+ return (cmd == 'augroup'
+ or cmd == 'autocmd'
+ or cmd == 'doautocmd'
+ or cmd == 'doautoall'
+ or cmd == 'global'
+ or cmd == 'substitute')
+end
+
+local vimcmd_start = 'syn keyword vimCommand contained '
+w(vimcmd_start)
+local prev_cmd = nil
+for _, cmd_desc in ipairs(ex_cmds.cmds) do
+ if lld.line_length > 850 then
+ w('\n' .. vimcmd_start)
+ end
+ local cmd = cmd_desc.command
+ if cmd:match('%w') and cmd ~= 'z' and not is_special_cased_cmd(cmd) then
+ w(' ' .. cmd_kw(prev_cmd, cmd))
+ end
+ if cmd == 'delete' then
+ -- Add special abbreviations of :delete
+ w(' ' .. cmd_kw('d', 'dl'))
+ w(' ' .. cmd_kw('del', 'dell'))
+ w(' ' .. cmd_kw('dele', 'delel'))
+ w(' ' .. cmd_kw('delet', 'deletl'))
+ w(' ' .. cmd_kw('delete', 'deletel'))
+ w(' ' .. cmd_kw('d', 'dp'))
+ w(' ' .. cmd_kw('de', 'dep'))
+ w(' ' .. cmd_kw('del', 'delp'))
+ w(' ' .. cmd_kw('dele', 'delep'))
+ w(' ' .. cmd_kw('delet', 'deletp'))
+ w(' ' .. cmd_kw('delete', 'deletep'))
+ end
+ prev_cmd = cmd
+end
+
+local vimopt_start = 'syn keyword vimOption contained '
+w('\n\n' .. vimopt_start)
+
+for _, opt_desc in ipairs(options.options) do
+ if not opt_desc.varname or opt_desc.varname:sub(1, 7) ~= 'p_force' then
+ if lld.line_length > 850 then
+ w('\n' .. vimopt_start)
+ end
+ w(' ' .. opt_desc.full_name)
+ if opt_desc.abbreviation then
+ w(' ' .. opt_desc.abbreviation)
+ end
+ if opt_desc.type == 'bool' then
+ w(' inv' .. opt_desc.full_name)
+ w(' no' .. opt_desc.full_name)
+ if opt_desc.abbreviation then
+ w(' inv' .. opt_desc.abbreviation)
+ w(' no' .. opt_desc.abbreviation)
+ end
+ end
+ end
+end
+
+w('\n\nsyn case ignore')
+local vimau_start = 'syn keyword vimAutoEvent contained '
+w('\n\n' .. vimau_start)
+
+for _, au in ipairs(auevents.events) do
+ if not auevents.nvim_specific[au] then
+ if lld.line_length > 850 then
+ w('\n' .. vimau_start)
+ end
+ w(' ' .. au)
+ end
+end
+for _, au in pairs(auevents.aliases) do
+ if lld.line_length > 850 then
+ w('\n' .. vimau_start)
+ end
+ -- au[1] is aliased to au[2]
+ w(' ' .. au[1])
+end
+
+local nvimau_start = 'syn keyword nvimAutoEvent contained '
+w('\n\n' .. nvimau_start)
+
+for au, _ in vim.spairs(auevents.nvim_specific) do
+ if lld.line_length > 850 then
+ w('\n' .. nvimau_start)
+ end
+ w(' ' .. au)
+end
+
+w('\n\nsyn case match')
+local vimfun_start = 'syn keyword vimFuncName contained '
+w('\n\n' .. vimfun_start)
+local funcs = mpack.decode(io.open(funcs_file, 'rb'):read("*all"))
+for _, name in ipairs(funcs) do
+ if name then
+ if lld.line_length > 850 then
+ w('\n' .. vimfun_start)
+ end
+ w(' ' .. name)
+ end
+end
+
+w('\n')
+syn_fd:close()
diff --git a/src/nvim/generators/preload.lua b/src/nvim/generators/preload.lua
new file mode 100644
index 0000000000..4b7fde2c39
--- /dev/null
+++ b/src/nvim/generators/preload.lua
@@ -0,0 +1,10 @@
+local srcdir = table.remove(arg, 1)
+local nlualib = table.remove(arg, 1)
+package.path = srcdir .. '/src/nvim/?.lua;' ..srcdir .. '/runtime/lua/?.lua;' .. package.path
+_G.vim = require'vim.shared'
+_G.vim.inspect = require 'vim.inspect'
+package.cpath = package.cpath .. ';' .. nlualib
+require 'nlua0'
+
+arg[0] = table.remove(arg, 1)
+return loadfile(arg[0])()
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 9c5364e1b1..73af78d3e2 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1,21 +1,19 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// getchar.c: Code related to getting a character from the user or a script
// file, manipulations with redo buffer and stuff buffer.
#include <assert.h>
-#include <inttypes.h>
+#include <lauxlib.h>
+#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "lauxlib.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
@@ -24,11 +22,11 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -37,7 +35,7 @@
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
@@ -48,19 +46,18 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
-#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
/// Index in scriptin
static int curscript = 0;
@@ -85,61 +82,73 @@ static buffheader_T redobuff = { { NULL, { NUL } }, NULL, 0, 0 };
static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 };
static buffheader_T recordbuff = { { NULL, { NUL } }, NULL, 0, 0 };
-// First read ahead buffer. Used for translated commands.
+/// First read ahead buffer. Used for translated commands.
static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 };
-// Second read ahead buffer. Used for redo.
+/// Second read ahead buffer. Used for redo.
static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 };
-static int typeahead_char = 0; // typeahead char that's not flushed
+static int typeahead_char = 0; ///< typeahead char that's not flushed
-// when block_redo is true redo buffer will not be changed
-// used by edit() to repeat insertions and 'V' command for redoing
+/// When block_redo is true the redo buffer will not be changed.
+/// Used by edit() to repeat insertions.
static int block_redo = false;
-static int KeyNoremap = 0; // remapping flags
+static int KeyNoremap = 0; ///< remapping flags
-// Variables used by vgetorpeek() and flush_buffers()
-//
-// typebuf.tb_buf[] contains all characters that are not consumed yet.
-// typebuf.tb_buf[typebuf.tb_off] is the first valid character.
-// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char.
-// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL.
-// The head of the buffer may contain the result of mappings, abbreviations
-// and @a commands. The length of this part is typebuf.tb_maplen.
-// typebuf.tb_silent is the part where <silent> applies.
-// After the head are characters that come from the terminal.
-// typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that
-// should not be considered for abbreviations.
-// Some parts of typebuf.tb_buf may not be mapped. These parts are remembered
-// in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and
-// contains RM_NONE for the characters that are not to be remapped.
-// typebuf.tb_noremap[typebuf.tb_off] is the first valid flag.
-// (typebuf has been put in globals.h, because check_termcode() needs it).
-#define RM_YES 0 // tb_noremap: remap
-#define RM_NONE 1 // tb_noremap: don't remap
-#define RM_SCRIPT 2 // tb_noremap: remap local script mappings
-#define RM_ABBR 4 // tb_noremap: don't remap, do abbrev.
+/// Variables used by vgetorpeek() and flush_buffers()
+///
+/// typebuf.tb_buf[] contains all characters that are not consumed yet.
+/// typebuf.tb_buf[typebuf.tb_off] is the first valid character.
+/// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char.
+/// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL.
+/// The head of the buffer may contain the result of mappings, abbreviations
+/// and @a commands. The length of this part is typebuf.tb_maplen.
+/// typebuf.tb_silent is the part where <silent> applies.
+/// After the head are characters that come from the terminal.
+/// typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that
+/// should not be considered for abbreviations.
+/// Some parts of typebuf.tb_buf may not be mapped. These parts are remembered
+/// in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and
+/// contains RM_NONE for the characters that are not to be remapped.
+/// typebuf.tb_noremap[typebuf.tb_off] is the first valid flag.
+enum {
+ RM_YES = 0, ///< tb_noremap: remap
+ RM_NONE = 1, ///< tb_noremap: don't remap
+ RM_SCRIPT = 2, ///< tb_noremap: remap local script mappings
+ RM_ABBR = 4, ///< tb_noremap: don't remap, do abbrev.
+};
// typebuf.tb_buf has three parts: room in front (for result of mappings), the
// middle for typeahead and room for new characters (which needs to be 3 *
// MAXMAPLEN for the Amiga).
#define TYPELEN_INIT (5 * (MAXMAPLEN + 3))
-static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf
-static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap
+static uint8_t typebuf_init[TYPELEN_INIT]; ///< initial typebuf.tb_buf
+static uint8_t noremapbuf_init[TYPELEN_INIT]; ///< initial typebuf.tb_noremap
-static size_t last_recorded_len = 0; // number of last recorded chars
+static size_t last_recorded_len = 0; ///< number of last recorded chars
+
+enum {
+ KEYLEN_PART_KEY = -1, ///< keylen value for incomplete key-code
+ KEYLEN_PART_MAP = -2, ///< keylen value for incomplete mapping
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "getchar.c.generated.h"
#endif
-// Free and clear a buffer.
-void free_buff(buffheader_T *buf)
+static const char e_recursive_mapping[] = N_("E223: Recursive mapping");
+static const char e_cmd_mapping_must_end_with_cr[]
+ = N_("E1255: <Cmd> mapping must end with <CR>");
+static const char e_cmd_mapping_must_end_with_cr_before_second_cmd[]
+ = N_("E1136: <Cmd> mapping must end with <CR> before second <Cmd>");
+
+/// Free and clear a buffer.
+static void free_buff(buffheader_T *buf)
{
- buffblock_T *p, *np;
+ buffblock_T *np;
- for (p = buf->bh_first.b_next; p != NULL; p = np) {
+ for (buffblock_T *p = buf->bh_first.b_next; p != NULL; p = np) {
np = p->b_next;
xfree(p);
}
@@ -155,7 +164,6 @@ static char *get_buffcont(buffheader_T *buffer, int dozero)
{
size_t count = 0;
char *p = NULL;
- char *p2;
// compute the total length of the string
for (const buffblock_T *bp = buffer->bh_first.b_next;
@@ -163,9 +171,9 @@ static char *get_buffcont(buffheader_T *buffer, int dozero)
count += strlen(bp->b_str);
}
- if (count || dozero) {
+ if (count > 0 || dozero) {
p = xmalloc(count + 1);
- p2 = p;
+ char *p2 = p;
for (const buffblock_T *bp = buffer->bh_first.b_next;
bp != NULL; bp = bp->b_next) {
for (const char *str = bp->b_str; *str;) {
@@ -180,7 +188,7 @@ static char *get_buffcont(buffheader_T *buffer, int dozero)
/// Return the contents of the record buffer as a single string
/// and clear the record buffer.
/// K_SPECIAL in the returned string is escaped.
-char_u *get_recorded(void)
+char *get_recorded(void)
{
char *p;
size_t len;
@@ -202,7 +210,7 @@ char_u *get_recorded(void)
p[len - 1] = NUL;
}
- return (char_u *)p;
+ return p;
}
/// Return the contents of the redo buffer as a single string.
@@ -252,7 +260,7 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle
} else {
len = (size_t)slen;
}
- buffblock_T *p = xmalloc(sizeof(buffblock_T) + len);
+ buffblock_T *p = xmalloc(offsetof(buffblock_T, b_str) + len + 1);
buf->bh_space = len - (size_t)slen;
xstrlcpy(p->b_str, s, (size_t)slen + 1);
@@ -281,11 +289,11 @@ static void delete_buff_tail(buffheader_T *buf, int slen)
}
/// Add number "n" to buffer "buf".
-static void add_num_buff(buffheader_T *buf, long n)
+static void add_num_buff(buffheader_T *buf, int n)
{
char number[32];
- snprintf(number, sizeof(number), "%ld", n);
- add_buff(buf, number, -1L);
+ snprintf(number, sizeof(number), "%d", n);
+ add_buff(buf, number, -1);
}
/// Add character 'c' to buffer "buf".
@@ -317,7 +325,7 @@ static void add_char_buff(buffheader_T *buf, int c)
temp[0] = (char)c;
temp[1] = NUL;
}
- add_buff(buf, temp, -1L);
+ add_buff(buf, temp, -1);
}
}
@@ -338,14 +346,12 @@ static int read_readbuffers(int advance)
static int read_readbuf(buffheader_T *buf, int advance)
{
- char_u c;
-
if (buf->bh_first.b_next == NULL) { // buffer is empty
return NUL;
}
buffblock_T *const curr = buf->bh_first.b_next;
- c = (char_u)curr->b_str[buf->bh_index];
+ uint8_t c = (uint8_t)curr->b_str[buf->bh_index];
if (advance) {
if (curr->b_str[++buf->bh_index] == NUL) {
@@ -357,7 +363,7 @@ static int read_readbuf(buffheader_T *buf, int advance)
return c;
}
-// Prepare the read buffers for reading (if they contain something).
+/// Prepare the read buffers for reading (if they contain something).
static void start_stuff(void)
{
if (readbuf1.bh_first.b_next != NULL) {
@@ -385,15 +391,15 @@ int readbuf1_empty(void)
return (readbuf1.bh_first.b_next == NULL);
}
-// Set a typeahead character that won't be flushed.
+/// Set a typeahead character that won't be flushed.
void typeahead_noflush(int c)
{
typeahead_char = c;
}
-// Remove the contents of the stuff buffer and the mapped characters in the
-// typeahead buffer (used in case of an error). If "flush_typeahead" is true,
-// flush all typeahead characters (used when interrupted by a CTRL-C).
+/// Remove the contents of the stuff buffer and the mapped characters in the
+/// typeahead buffer (used in case of an error). If "flush_typeahead" is true,
+/// flush all typeahead characters (used when interrupted by a CTRL-C).
void flush_buffers(flush_buffers_T flush_typeahead)
{
init_typebuf();
@@ -411,7 +417,7 @@ void flush_buffers(flush_buffers_T flush_typeahead)
// We have to get all characters, because we may delete the first
// part of an escape sequence. In an xterm we get one char at a
// time and we have to get them all.
- while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10L) != 0) {}
+ while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10) != 0) {}
}
typebuf.tb_off = MAXMAPLEN;
typebuf.tb_len = 0;
@@ -437,8 +443,8 @@ void beep_flush(void)
}
}
-// The previous contents of the redo buffer is kept in old_redobuffer.
-// This is used for the CTRL-O <.> command in insert mode.
+/// The previous contents of the redo buffer is kept in old_redobuffer.
+/// This is used for the CTRL-O <.> command in insert mode.
void ResetRedobuff(void)
{
if (block_redo) {
@@ -450,8 +456,8 @@ void ResetRedobuff(void)
redobuff.bh_first.b_next = NULL;
}
-// Discard the contents of the redo buffer and restore the previous redo
-// buffer.
+/// Discard the contents of the redo buffer and restore the previous redo
+/// buffer.
void CancelRedo(void)
{
if (block_redo) {
@@ -480,7 +486,7 @@ void saveRedobuff(save_redo_T *save_redo)
return;
}
- add_buff(&redobuff, s, -1L);
+ add_buff(&redobuff, s, -1);
xfree(s);
}
@@ -499,7 +505,7 @@ void restoreRedobuff(save_redo_T *save_redo)
void AppendToRedobuff(const char *s)
{
if (!block_redo) {
- add_buff(&redobuff, s, -1L);
+ add_buff(&redobuff, s, -1);
}
}
@@ -545,13 +551,32 @@ void AppendToRedobuffLit(const char *str, int len)
// CTRL-V '0' must be inserted as CTRL-V 048.
if (*s == NUL && c == '0') {
- add_buff(&redobuff, "048", 3L);
+ add_buff(&redobuff, "048", 3);
} else {
add_char_buff(&redobuff, c);
}
}
}
+/// Append "s" to the redo buffer, leaving 3-byte special key codes unmodified
+/// and escaping other K_SPECIAL bytes.
+void AppendToRedobuffSpec(const char *s)
+{
+ if (block_redo) {
+ return;
+ }
+
+ while (*s != NUL) {
+ if ((uint8_t)(*s) == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
+ // Insert special key literally.
+ add_buff(&redobuff, s, 3);
+ s += 3;
+ } else {
+ add_char_buff(&redobuff, mb_cptr2char_adv(&s));
+ }
+ }
+}
+
/// Append a character to the redo buffer.
/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
void AppendCharToRedobuff(int c)
@@ -562,7 +587,7 @@ void AppendCharToRedobuff(int c)
}
// Append a number to the redo buffer.
-void AppendNumberToRedobuff(long n)
+void AppendNumberToRedobuff(int n)
{
if (!block_redo) {
add_num_buff(&redobuff, n);
@@ -573,17 +598,17 @@ void AppendNumberToRedobuff(long n)
/// K_SPECIAL must already have been escaped.
void stuffReadbuff(const char *s)
{
- add_buff(&readbuf1, s, -1L);
+ add_buff(&readbuf1, s, -1);
}
/// Append string "s" to the redo stuff buffer.
/// @remark K_SPECIAL must already have been escaped.
void stuffRedoReadbuff(const char *s)
{
- add_buff(&readbuf2, s, -1L);
+ add_buff(&readbuf2, s, -1);
}
-void stuffReadbuffLen(const char *s, long len)
+void stuffReadbuffLen(const char *s, ptrdiff_t len)
{
add_buff(&readbuf1, s, len);
}
@@ -616,7 +641,7 @@ void stuffcharReadbuff(int c)
}
// Append a number to the stuff buffer.
-void stuffnumReadbuff(long n)
+void stuffnumReadbuff(int n)
{
add_num_buff(&readbuf1, n);
}
@@ -635,7 +660,7 @@ void stuffescaped(const char *arg, bool literally)
arg++;
}
if (arg > start) {
- stuffReadbuffLen(start, (arg - start));
+ stuffReadbuffLen(start, arg - start);
}
// stuff a single special character
@@ -658,18 +683,17 @@ void stuffescaped(const char *arg, bool literally)
static int read_redo(bool init, bool old_redo)
{
static buffblock_T *bp;
- static char_u *p;
+ static uint8_t *p;
int c;
int n;
- char_u buf[MB_MAXBYTES + 1];
- int i;
+ uint8_t buf[MB_MAXBYTES + 1];
if (init) {
bp = old_redo ? old_redobuff.bh_first.b_next : redobuff.bh_first.b_next;
if (bp == NULL) {
return FAIL;
}
- p = (char_u *)bp->b_str;
+ p = (uint8_t *)bp->b_str;
return OK;
}
if ((c = *p) == NUL) {
@@ -683,16 +707,16 @@ static int read_redo(bool init, bool old_redo)
} else {
n = 1;
}
- for (i = 0;; i++) {
+ for (int i = 0;; i++) {
if (c == K_SPECIAL) { // special key or escaped K_SPECIAL
c = TO_SPECIAL(p[1], p[2]);
p += 2;
}
if (*++p == NUL && bp->b_next != NULL) {
bp = bp->b_next;
- p = (char_u *)bp->b_str;
+ p = (uint8_t *)bp->b_str;
}
- buf[i] = (char_u)c;
+ buf[i] = (uint8_t)c;
if (i == n - 1) { // last byte of a character
if (n != 1) {
c = utf_ptr2char((char *)buf);
@@ -720,27 +744,25 @@ static void copy_redo(bool old_redo)
}
}
-// Stuff the redo buffer into readbuf2.
-// Insert the redo count into the command.
-// If "old_redo" is true, the last but one command is repeated
-// instead of the last command (inserting text). This is used for
-// CTRL-O <.> in insert mode
-//
-// return FAIL for failure, OK otherwise
-int start_redo(long count, bool old_redo)
+/// Stuff the redo buffer into readbuf2.
+/// Insert the redo count into the command.
+/// If "old_redo" is true, the last but one command is repeated
+/// instead of the last command (inserting text). This is used for
+/// CTRL-O <.> in insert mode
+///
+/// @return FAIL for failure, OK otherwise
+int start_redo(int count, bool old_redo)
{
- int c;
-
// init the pointers; return if nothing to redo
if (read_redo(true, old_redo) == FAIL) {
return FAIL;
}
- c = read_redo(false, old_redo);
+ int c = read_redo(false, old_redo);
// copy the buffer name, if present
if (c == '"') {
- add_buff(&readbuf2, "\"", 1L);
+ add_buff(&readbuf2, "\"", 1);
c = read_redo(false, old_redo);
// if a numbered buffer is used, increment the number
@@ -781,9 +803,10 @@ int start_redo(long count, bool old_redo)
return OK;
}
-// Repeat the last insert (R, o, O, a, A, i or I command) by stuffing
-// the redo buffer into readbuf2.
-// return FAIL for failure, OK otherwise
+/// Repeat the last insert (R, o, O, a, A, i or I command) by stuffing
+/// the redo buffer into readbuf2.
+///
+/// @return FAIL for failure, OK otherwise
int start_redo_ins(void)
{
int c;
@@ -797,7 +820,7 @@ int start_redo_ins(void)
while ((c = read_redo(false, false)) != NUL) {
if (vim_strchr("AaIiRrOo", c) != NULL) {
if (c == 'O' || c == 'o') {
- add_buff(&readbuf2, NL_STR, -1L);
+ add_buff(&readbuf2, NL_STR, -1);
}
break;
}
@@ -814,9 +837,9 @@ void stop_redo_ins(void)
block_redo = false;
}
-// Initialize typebuf.tb_buf to point to typebuf_init.
-// alloc() cannot be used here: In out-of-memory situations it would
-// be impossible to type anything.
+/// Initialize typebuf.tb_buf to point to typebuf_init.
+/// alloc() cannot be used here: In out-of-memory situations it would
+/// be impossible to type anything.
static void init_typebuf(void)
{
if (typebuf.tb_buf != NULL) {
@@ -837,28 +860,24 @@ bool noremap_keys(void)
return KeyNoremap & (RM_NONE|RM_SCRIPT);
}
-// Insert a string in position 'offset' in the typeahead buffer (for "@r"
-// and ":normal" command, vgetorpeek() and check_termcode())
-//
-// If noremap is REMAP_YES, new string can be mapped again.
-// If noremap is REMAP_NONE, new string cannot be mapped again.
-// If noremap is REMAP_SKIP, first char of new string cannot be mapped again,
-// but abbreviations are allowed.
-// If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for
-// script-local mappings.
-// If noremap is > 0, that many characters of the new string cannot be mapped.
-//
-// If nottyped is true, the string does not return KeyTyped (don't use when
-// offset is non-zero!).
-//
-// If silent is true, cmd_silent is set when the characters are obtained.
-//
-// return FAIL for failure, OK otherwise
+/// Insert a string in position "offset" in the typeahead buffer.
+///
+/// If "noremap" is REMAP_YES, new string can be mapped again.
+/// If "noremap" is REMAP_NONE, new string cannot be mapped again.
+/// If "noremap" is REMAP_SKIP, first char of new string cannot be mapped again,
+/// but abbreviations are allowed.
+/// If "noremap" is REMAP_SCRIPT, new string cannot be mapped again, except for
+/// script-local mappings.
+/// If "noremap" is > 0, that many characters of the new string cannot be mapped.
+///
+/// If "nottyped" is true, the string does not return KeyTyped (don't use when
+/// "offset" is non-zero!).
+///
+/// If "silent" is true, cmd_silent is set when the characters are obtained.
+///
+/// @return FAIL for failure, OK otherwise
int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
{
- char_u *s1, *s2;
- int addlen;
- int i;
int val;
int nrm;
@@ -866,8 +885,9 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
if (++typebuf.tb_change_cnt == 0) {
typebuf.tb_change_cnt = 1;
}
+ state_no_longer_safe("ins_typebuf()");
- addlen = (int)strlen(str);
+ int addlen = (int)strlen(str);
if (offset == 0 && addlen <= typebuf.tb_off) {
// Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off]
@@ -893,8 +913,8 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
return FAIL;
}
int newlen = typebuf.tb_len + extra;
- s1 = xmalloc((size_t)newlen);
- s2 = xmalloc((size_t)newlen);
+ uint8_t *s1 = xmalloc((size_t)newlen);
+ uint8_t *s2 = xmalloc((size_t)newlen);
typebuf.tb_buflen = newlen;
// copy the old chars, before the insertion point
@@ -948,7 +968,7 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
} else {
nrm = noremap;
}
- for (i = 0; i < addlen; i++) {
+ for (int i = 0; i < addlen; i++) {
typebuf.tb_noremap[typebuf.tb_off + i + offset] =
(uint8_t)((--nrm >= 0) ? val : RM_YES);
}
@@ -978,11 +998,11 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
/// @return the length of what was inserted
int ins_char_typebuf(int c, int modifiers)
{
- char_u buf[MB_MAXBYTES * 3 + 4];
- unsigned int len = special_to_buf(c, modifiers, true, buf);
+ char buf[MB_MAXBYTES * 3 + 4];
+ unsigned len = special_to_buf(c, modifiers, true, buf);
assert(len < sizeof(buf));
buf[len] = NUL;
- (void)ins_typebuf((char *)buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
+ (void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
return (int)len;
}
@@ -1010,18 +1030,16 @@ int typebuf_typed(void)
return typebuf.tb_maplen == 0;
}
-// Return the number of characters that are mapped (or not typed).
+/// Get the number of characters that are mapped (or not typed).
int typebuf_maplen(void)
FUNC_ATTR_PURE
{
return typebuf.tb_maplen;
}
-// remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset]
+/// Remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset]
void del_typebuf(int len, int offset)
{
- int i;
-
if (len == 0) {
return; // nothing to do
}
@@ -1034,7 +1052,7 @@ void del_typebuf(int len, int offset)
typebuf.tb_off += len;
} else {
// Have to move the characters in typebuf.tb_buf[] and typebuf.tb_noremap[]
- i = typebuf.tb_off + offset;
+ int i = typebuf.tb_off + offset;
// Leave some extra room at the end to avoid reallocation.
if (typebuf.tb_off > MAXMAPLEN) {
memmove(typebuf.tb_buf + MAXMAPLEN,
@@ -1084,13 +1102,13 @@ void del_typebuf(int len, int offset)
}
}
-// Write typed characters to script file.
-// If recording is on put the character in the recordbuffer.
-static void gotchars(const char_u *chars, size_t len)
+/// Write typed characters to script file.
+/// If recording is on put the character in the recordbuffer.
+static void gotchars(const uint8_t *chars, size_t len)
FUNC_ATTR_NONNULL_ALL
{
- const char_u *s = chars;
- static char_u buf[4] = { 0 };
+ const uint8_t *s = chars;
+ static uint8_t buf[4] = { 0 };
static size_t buflen = 0;
size_t todo = len;
@@ -1130,6 +1148,13 @@ static void gotchars(const char_u *chars, size_t len)
maptick++;
}
+/// Record a <Nop> key.
+void gotchars_nop(void)
+{
+ uint8_t nop_buf[3] = { K_SPECIAL, KS_EXTRA, KE_NOP };
+ gotchars(nop_buf, 3);
+}
+
/// Undo the last gotchars() for "len" bytes. To be used when putting a typed
/// character back into the typeahead buffer, thus gotchars() will be called
/// again.
@@ -1144,12 +1169,12 @@ void ungetchars(int len)
last_recorded_len -= (size_t)len;
}
-// Sync undo. Called when typed characters are obtained from the typeahead
-// buffer, or when a menu is used.
-// Do not sync:
-// - In Insert mode, unless cursor key has been used.
-// - While reading a script file.
-// - When no_u_sync is non-zero.
+/// Sync undo. Called when typed characters are obtained from the typeahead
+/// buffer, or when a menu is used.
+/// Do not sync:
+/// - In Insert mode, unless cursor key has been used.
+/// - While reading a script file.
+/// - When no_u_sync is non-zero.
void may_sync_undo(void)
{
if ((!(State & (MODE_INSERT | MODE_CMDLINE)) || arrow_used)
@@ -1158,7 +1183,7 @@ void may_sync_undo(void)
}
}
-// Make "typebuf" empty and allocate new buffers.
+/// Make "typebuf" empty and allocate new buffers.
void alloc_typebuf(void)
{
typebuf.tb_buf = xmalloc(TYPELEN_INIT);
@@ -1174,7 +1199,7 @@ void alloc_typebuf(void)
}
}
-// Free the buffers of "typebuf".
+/// Free the buffers of "typebuf".
void free_typebuf(void)
{
if (typebuf.tb_buf == typebuf_init) {
@@ -1189,8 +1214,8 @@ void free_typebuf(void)
}
}
-// When doing ":so! file", the current typeahead needs to be saved, and
-// restored when "file" has been read completely.
+/// When doing ":so! file", the current typeahead needs to be saved, and
+/// restored when "file" has been read completely.
static typebuf_T saved_typebuf[NSCRIPT];
void save_typebuf(void)
@@ -1200,12 +1225,12 @@ void save_typebuf(void)
alloc_typebuf();
}
-static int old_char = -1; // character put back by vungetc()
-static int old_mod_mask; // mod_mask for ungotten character
-static int old_mouse_grid; // mouse_grid related to old_char
-static int old_mouse_row; // mouse_row related to old_char
-static int old_mouse_col; // mouse_col related to old_char
-static int old_KeyStuffed; // whether old_char was stuffed
+static int old_char = -1; ///< character put back by vungetc()
+static int old_mod_mask; ///< mod_mask for ungotten character
+static int old_mouse_grid; ///< mouse_grid related to old_char
+static int old_mouse_row; ///< mouse_row related to old_char
+static int old_mouse_col; ///< mouse_col related to old_char
+static int old_KeyStuffed; ///< whether old_char was stuffed
static bool can_get_old_char(void)
{
@@ -1214,7 +1239,7 @@ static bool can_get_old_char(void)
return old_char != -1 && (old_KeyStuffed || stuff_empty());
}
-// Save all three kinds of typeahead, so that the user must type at a prompt.
+/// Save all three kinds of typeahead, so that the user must type at a prompt.
void save_typeahead(tasave_T *tp)
{
tp->save_typebuf = typebuf;
@@ -1230,8 +1255,8 @@ void save_typeahead(tasave_T *tp)
readbuf2.bh_first.b_next = NULL;
}
-// Restore the typeahead to what it was before calling save_typeahead().
-// The allocated memory is freed, can only be called once!
+/// Restore the typeahead to what it was before calling save_typeahead().
+/// The allocated memory is freed, can only be called once!
void restore_typeahead(tasave_T *tp)
{
if (tp->typebuf_valid) {
@@ -1317,7 +1342,7 @@ void openscript(char *name, bool directly)
}
}
-// Close the currently active input script.
+/// Close the currently active input script.
static void closescript(void)
{
free_typebuf();
@@ -1357,8 +1382,8 @@ void before_blocking(void)
}
}
-/// updatescript() is called when a character can be written to the script file
-/// or when we have waited some time for a character (c == 0).
+/// updatescript() is called when a character can be written to the script
+/// file or when we have waited some time for a character (c == 0).
///
/// All the changed memfiles are synced if c == 0 or when the number of typed
/// characters reaches 'updatecount' and 'updatecount' is non-zero.
@@ -1408,10 +1433,8 @@ int merge_modifiers(int c_arg, int *modifiers)
/// Returns the modifiers in the global "mod_mask".
int vgetc(void)
{
- int c, c2;
- int n;
- char_u buf[MB_MAXBYTES + 1];
- int i;
+ int c;
+ uint8_t buf[MB_MAXBYTES + 1];
// Do garbage collection when garbagecollect() was called previously and
// we are now at the toplevel.
@@ -1429,6 +1452,8 @@ int vgetc(void)
mouse_row = old_mouse_row;
mouse_col = old_mouse_col;
} else {
+ int c2;
+ int n;
// number of characters recorded from the last vgetc() call
static size_t last_vgetc_recorded_len = 0;
@@ -1440,7 +1465,7 @@ int vgetc(void)
// if peeking records more
last_recorded_len -= last_vgetc_recorded_len;
- for (;;) { // this is done twice if there are modifiers
+ while (true) { // this is done twice if there are modifiers
bool did_inc = false;
if (mod_mask) { // no mapping after modifier has been read
no_mapping++;
@@ -1553,9 +1578,9 @@ int vgetc(void)
// Note: This will loop until enough bytes are received!
if ((n = MB_BYTE2LEN_CHECK(c)) > 1) {
no_mapping++;
- buf[0] = (char_u)c;
- for (i = 1; i < n; i++) {
- buf[i] = (char_u)vgetorpeek(true);
+ buf[0] = (uint8_t)c;
+ for (int i = 1; i < n; i++) {
+ buf[i] = (uint8_t)vgetorpeek(true);
if (buf[i] == K_SPECIAL) {
// Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence,
// which represents a K_SPECIAL (0x80).
@@ -1601,11 +1626,17 @@ int vgetc(void)
// Execute Lua on_key callbacks.
nlua_execute_on_key(c);
+ // Need to process the character before we know it's safe to do something
+ // else.
+ if (c != K_IGNORE) {
+ state_no_longer_safe("key typed");
+ }
+
return c;
}
-// Like vgetc(), but never return a NUL when called recursively, get a key
-// directly from the user (ignoring typeahead).
+/// Like vgetc(), but never return a NUL when called recursively, get a key
+/// directly from the user (ignoring typeahead).
int safe_vgetc(void)
{
int c;
@@ -1617,8 +1648,8 @@ int safe_vgetc(void)
return c;
}
-// Like safe_vgetc(), but loop to handle K_IGNORE.
-// Also ignore scrollbar events.
+/// Like safe_vgetc(), but loop to handle K_IGNORE.
+/// Also ignore scrollbar events.
int plain_vgetc(void)
{
int c;
@@ -1631,10 +1662,10 @@ int plain_vgetc(void)
return c;
}
-// Check if a character is available, such that vgetc() will not block.
-// If the next character is a special character or multi-byte, the returned
-// character is not valid!.
-// Returns NUL if no character is available.
+/// Check if a character is available, such that vgetc() will not block.
+/// If the next character is a special character or multi-byte, the returned
+/// character is not valid!.
+/// Returns NUL if no character is available.
int vpeekc(void)
{
if (can_get_old_char()) {
@@ -1643,9 +1674,9 @@ int vpeekc(void)
return vgetorpeek(false);
}
-// Check if any character is available, also half an escape sequence.
-// Trick: when no typeahead found, but there is something in the typeahead
-// buffer, it must be an ESC that is recognized as the start of a key code.
+/// Check if any character is available, also half an escape sequence.
+/// Trick: when no typeahead found, but there is something in the typeahead
+/// buffer, it must be an ESC that is recognized as the start of a key code.
int vpeekc_any(void)
{
int c;
@@ -1657,9 +1688,9 @@ int vpeekc_any(void)
return c;
}
-// Call vpeekc() without causing anything to be mapped.
-// Return true if a character is available, false otherwise.
-int char_avail(void)
+/// Call vpeekc() without causing anything to be mapped.
+/// @return true if a character is available, false otherwise.
+bool char_avail(void)
{
int retval;
@@ -1678,7 +1709,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
no_mapping++;
allow_keys++;
- for (;;) {
+ while (true) {
if (msg_col > 0) {
// Position the cursor. Needed after a message that ends in a space.
ui_cursor_goto(msg_row, msg_col);
@@ -1688,7 +1719,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
// getchar(): blocking wait.
// TODO(bfredl): deduplicate shared logic with state_enter ?
if (!char_avail()) {
- // flush output before waiting
+ // Flush screen updates before blocking.
ui_flush();
(void)os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
@@ -1754,9 +1785,9 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
int grid = mouse_grid;
linenr_T lnum;
win_T *wp;
- int winnr = 1;
if (row >= 0 && col >= 0) {
+ int winnr = 1;
// Find the window at the mouse coordinates and compute the
// text position.
win_T *const win = mouse_find_win(&grid, &row, &col);
@@ -1796,7 +1827,7 @@ void f_getcharstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
int i = 0;
if (n != 0) {
- i += utf_char2bytes((int)n, (char *)temp);
+ i += utf_char2bytes((int)n, temp);
}
assert(i < 7);
temp[i++] = NUL;
@@ -1820,7 +1851,7 @@ typedef enum {
/// Put "string[new_slen]" in typebuf.
/// Remove "slen" bytes.
/// @return FAIL for error, OK otherwise.
-static int put_string_in_typebuf(int offset, int slen, char_u *string, int new_slen)
+static int put_string_in_typebuf(int offset, int slen, uint8_t *string, int new_slen)
{
int extra = new_slen - slen;
string[new_slen] = NUL;
@@ -1843,7 +1874,7 @@ static int put_string_in_typebuf(int offset, int slen, char_u *string, int new_s
/// in Insert mode completion. This includes the form with a CTRL modifier.
static bool at_ins_compl_key(void)
{
- char_u *p = typebuf.tb_buf + typebuf.tb_off;
+ uint8_t *p = typebuf.tb_buf + typebuf.tb_off;
int c = *p;
if (typebuf.tb_len > 3 && c == K_SPECIAL && p[1] == KS_MODIFIER && (p[2] & MOD_MASK_CTRL)) {
@@ -1863,7 +1894,7 @@ static int check_simplify_modifier(int max_offset)
if (offset + 3 >= typebuf.tb_len) {
break;
}
- char_u *tp = typebuf.tb_buf + typebuf.tb_off + offset;
+ uint8_t *tp = typebuf.tb_buf + typebuf.tb_off + offset;
if (tp[0] == K_SPECIAL && tp[1] == KS_MODIFIER) {
// A modifier was not used for a mapping, apply it to ASCII
// keys. Shift would already have been applied.
@@ -1879,12 +1910,12 @@ static int check_simplify_modifier(int max_offset)
vgetc_char = c;
vgetc_mod_mask = tp[2];
}
- char_u new_string[MB_MAXBYTES];
+ uint8_t new_string[MB_MAXBYTES];
int len;
if (IS_SPECIAL(new_c)) {
new_string[0] = K_SPECIAL;
- new_string[1] = (char_u)K_SECOND(new_c);
- new_string[2] = (char_u)K_THIRD(new_c);
+ new_string[1] = (uint8_t)K_SECOND(new_c);
+ new_string[2] = (uint8_t)K_THIRD(new_c);
len = 3;
} else {
len = utf_char2bytes(new_c, (char *)new_string);
@@ -1894,7 +1925,7 @@ static int check_simplify_modifier(int max_offset)
return -1;
}
} else {
- tp[2] = (char_u)modifier;
+ tp[2] = (uint8_t)modifier;
if (put_string_in_typebuf(offset + 3, 1, new_string, len) == FAIL) {
return -1;
}
@@ -1920,10 +1951,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
int mp_match_len = 0;
int max_mlen = 0;
int tb_c1;
- int mlen;
- int nolmaplen;
int keylen = *keylenp;
- int i;
int local_State = get_real_state();
bool is_plug_map = false;
@@ -1956,6 +1984,8 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
&& State != MODE_ASKMORE
&& State != MODE_CONFIRM
&& !at_ins_compl_key()) {
+ int mlen;
+ int nolmaplen;
if (tb_c1 == K_SPECIAL) {
nolmaplen = 2;
} else {
@@ -2016,10 +2046,10 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// Don't allow mapping the first byte(s) of a multi-byte char.
// Happens when mapping <M-a> and then changing 'encoding'.
// Beware that 0x80 is escaped.
- char_u *p1 = (char_u *)mp->m_keys;
- char_u *p2 = (char_u *)mb_unescape((const char **)&p1);
+ const char *p1 = mp->m_keys;
+ const char *p2 = mb_unescape(&p1);
- if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len((char *)p2)) {
+ if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len(p2)) {
mlen = 0;
}
@@ -2081,39 +2111,6 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
}
}
- // Check for match with 'pastetoggle'
- if (*p_pt != NUL && mp == NULL && (State & (MODE_INSERT | MODE_NORMAL))) {
- bool match = typebuf_match_len((char_u *)p_pt, &mlen);
- if (match) {
- // write chars to script file(s)
- if (mlen > typebuf.tb_maplen) {
- gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen,
- (size_t)(mlen - typebuf.tb_maplen));
- }
-
- del_typebuf(mlen, 0); // remove the chars
- set_option_value_give_err("paste", !p_paste, NULL, 0);
- if (!(State & MODE_INSERT)) {
- msg_col = 0;
- msg_row = Rows - 1;
- msg_clr_eos(); // clear ruler
- }
- status_redraw_all();
- redraw_statuslines();
- showmode();
- setcursor();
- *keylenp = keylen;
- return map_result_retry;
- }
- // Need more chars for partly match.
- if (mlen == typebuf.tb_len) {
- keylen = KEYLEN_PART_KEY;
- } else if (max_mlen < mlen) {
- // no match, may have to check for termcode at next character
- max_mlen = mlen + 1;
- }
- }
-
if ((mp == NULL || max_mlen > mp_match_len) && keylen != KEYLEN_PART_MAP) {
// When no matching mapping found or found a non-matching mapping that
// matches at least what the matching mapping matched:
@@ -2124,13 +2121,6 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
|| (typebuf.tb_buf[typebuf.tb_off + 1] == KS_MODIFIER && typebuf.tb_len < 4))) {
// Incomplete modifier sequence: cannot decide whether to simplify yet.
keylen = KEYLEN_PART_KEY;
- } else if (keylen == KEYLEN_PART_KEY && !*timedout) {
- // If 'pastetoggle' matched partially, don't simplify.
- // When the last characters were not typed, don't wait for a typed character to
- // complete 'pastetoggle'.
- if (typebuf.tb_len == typebuf.tb_maplen) {
- keylen = 0;
- }
} else {
// Try to include the modifier into the key.
keylen = check_simplify_modifier(max_mlen + 1);
@@ -2168,6 +2158,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// complete match
if (keylen >= 0 && keylen <= typebuf.tb_len) {
+ int i;
char *map_str = NULL;
// Write chars to script file(s).
@@ -2183,7 +2174,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// Put the replacement string in front of mapstr.
// The depth check catches ":map x y" and ":map y x".
if (++*mapdepth >= p_mmd) {
- emsg(_("E223: recursive mapping"));
+ emsg(_(e_recursive_mapping));
if (State & MODE_CMDLINE) {
redrawcmdline();
} else {
@@ -2199,7 +2190,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// mode temporarily. Append K_SELECT to switch back to Select mode.
if (VIsual_active && VIsual_select && (mp->m_mode & MODE_VISUAL)) {
VIsual_select = false;
- (void)ins_typebuf((char *)K_SELECT_STRING, REMAP_NONE, 0, true, false);
+ (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false);
}
// Copy the values from *mp that are used, because evaluating the
@@ -2218,9 +2209,6 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
if (mp->m_expr) {
const int save_vgetc_busy = vgetc_busy;
const bool save_may_garbage_collect = may_garbage_collect;
- const int save_cursor_row = ui_current_row();
- const int save_cursor_col = ui_current_col();
- const handle_T save_cursor_grid = ui_cursor_grid();
const int prev_did_emsg = did_emsg;
vgetc_busy = 0;
@@ -2232,28 +2220,28 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
}
map_str = eval_map_expr(mp, NUL);
- // The mapping may do anything, but we expect it to take care of
- // redrawing. Do put the cursor back where it was.
- ui_grid_cursor_goto(save_cursor_grid, save_cursor_row, save_cursor_col);
- ui_flush();
-
- // If an error was displayed and the expression returns an empty
- // string, generate a <Nop> to allow for a redraw.
- if (prev_did_emsg != did_emsg && (map_str == NULL || *map_str == NUL)) {
- char buf[4];
- xfree(map_str);
- buf[0] = (char)K_SPECIAL;
- buf[1] = (char)KS_EXTRA;
- buf[2] = KE_IGNORE;
- buf[3] = NUL;
- map_str = xstrdup(buf);
- if (State & MODE_CMDLINE) {
- // redraw the command below the error
- msg_didout = true;
- if (msg_row < cmdline_row) {
- msg_row = cmdline_row;
+ if ((map_str == NULL || *map_str == NUL)) {
+ // If an error was displayed and the expression returns an empty
+ // string, generate a <Nop> to allow for a redraw.
+ if (prev_did_emsg != did_emsg) {
+ char buf[4];
+ xfree(map_str);
+ buf[0] = (char)K_SPECIAL;
+ buf[1] = (char)KS_EXTRA;
+ buf[2] = KE_IGNORE;
+ buf[3] = NUL;
+ map_str = xstrdup(buf);
+ if (State & MODE_CMDLINE) {
+ // redraw the command below the error
+ msg_didout = true;
+ if (msg_row < cmdline_row) {
+ msg_row = cmdline_row;
+ }
+ redrawcmd();
}
- redrawcmd();
+ } else if (State & (MODE_NORMAL | MODE_INSERT)) {
+ // otherwise, just put back the cursor
+ setcursor();
}
}
@@ -2275,7 +2263,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// If this is a LANGMAP mapping, then we didn't record the keys
// at the start of the function and have to record them now.
if (keylen > typebuf.tb_maplen && (mp->m_mode & MODE_LANGMAP) != 0) {
- gotchars((char_u *)map_str, strlen(map_str));
+ gotchars((uint8_t *)map_str, strlen(map_str));
}
if (save_m_noremap != REMAP_YES) {
@@ -2355,16 +2343,12 @@ void check_end_reg_executing(bool advance)
/// K_SPECIAL may be escaped, need to get two more bytes then.
static int vgetorpeek(bool advance)
{
- int c, c1;
+ int c;
bool timedout = false; // waited for more than 'timeoutlen'
// for mapping to complete or
// 'ttimeoutlen' for complete key code
int mapdepth = 0; // check for recursive mapping
bool mode_deleted = false; // set when mode has been deleted
- int new_wcol, new_wrow;
- int n;
- int old_wcol, old_wrow;
- int wait_tb_len;
// This function doesn't work very well when called recursively. This may
// happen though, because of:
@@ -2414,7 +2398,7 @@ static int vgetorpeek(bool advance)
// are sure that it is not a mapped key.
// If a mapped key sequence is found we go back to the start to
// try re-mapping.
- for (;;) {
+ while (true) {
check_end_reg_executing(advance);
// os_breakcheck() is slow, don't use it too often when
// inside a mapping. But call it each time for typed
@@ -2432,7 +2416,7 @@ static int vgetorpeek(bool advance)
int keylen = 0;
if (got_int) {
// flush all input
- c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L);
+ c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0);
// If inchar() returns true (script file was active) or we
// are inside a mapping, get out of Insert mode.
@@ -2450,7 +2434,7 @@ static int vgetorpeek(bool advance)
if (advance) {
// Also record this character, it might be needed to
// get out of Insert mode.
- *typebuf.tb_buf = (char_u)c;
+ *typebuf.tb_buf = (uint8_t)c;
gotchars(typebuf.tb_buf, 1);
}
cmd_silent = false;
@@ -2501,8 +2485,8 @@ static int vgetorpeek(bool advance)
// have to redisplay the mode. That the cursor is in the wrong
// place does not matter.
c = 0;
- new_wcol = curwin->w_wcol;
- new_wrow = curwin->w_wrow;
+ int new_wcol = curwin->w_wcol;
+ int new_wrow = curwin->w_wrow;
if (advance
&& typebuf.tb_len == 1
&& typebuf.tb_buf[typebuf.tb_off] == ESC
@@ -2511,31 +2495,31 @@ static int vgetorpeek(bool advance)
&& typebuf.tb_maplen == 0
&& (State & MODE_INSERT)
&& (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout))
- && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) {
- colnr_T col = 0;
- char_u *ptr;
-
+ && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25)) == 0) {
if (mode_displayed) {
unshowmode(true);
mode_deleted = true;
}
validate_cursor();
- old_wcol = curwin->w_wcol;
- old_wrow = curwin->w_wrow;
+ int old_wcol = curwin->w_wcol;
+ int old_wrow = curwin->w_wrow;
// move cursor left, if possible
if (curwin->w_cursor.col != 0) {
+ colnr_T col = 0;
+ char *ptr;
if (curwin->w_wcol > 0) {
- if (did_ai) {
- // We are expecting to truncate the trailing
- // white-space, so find the last non-white
- // character -- webb
+ // After auto-indenting and no text is following,
+ // we are expecting to truncate the trailing
+ // white-space, so find the last non-white
+ // character -- webb
+ if (did_ai
+ && *skipwhite(get_cursor_line_ptr() + curwin->w_cursor.col) == NUL) {
curwin->w_wcol = 0;
- ptr = (char_u *)get_cursor_line_ptr();
+ ptr = get_cursor_line_ptr();
chartabsize_T cts;
- init_chartabsize_arg(&cts, curwin,
- curwin->w_cursor.lnum, 0, (char *)ptr, (char *)ptr);
- while ((char_u *)cts.cts_ptr < ptr + curwin->w_cursor.col) {
+ init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, ptr, ptr);
+ while (cts.cts_ptr < ptr + curwin->w_cursor.col) {
if (!ascii_iswhite(*cts.cts_ptr)) {
curwin->w_wcol = cts.cts_vcol;
}
@@ -2561,9 +2545,9 @@ static int vgetorpeek(bool advance)
if (col > 0 && curwin->w_wcol > 0) {
// Correct when the cursor is on the right halve
// of a double-wide character.
- ptr = (char_u *)get_cursor_line_ptr();
- col -= utf_head_off((char *)ptr, (char *)ptr + col);
- if (utf_ptr2cells((char *)ptr + col) > 1) {
+ ptr = get_cursor_line_ptr();
+ col -= utf_head_off(ptr, ptr + col);
+ if (utf_ptr2cells(ptr + col) > 1) {
curwin->w_wcol--;
}
}
@@ -2581,7 +2565,7 @@ static int vgetorpeek(bool advance)
// Allow mapping for just typed characters. When we get here c
// is the number of extra bytes and typebuf.tb_len is 1.
- for (n = 1; n <= c; n++) {
+ for (int n = 1; n <= c; n++) {
typebuf.tb_noremap[typebuf.tb_off + n] = RM_YES;
}
typebuf.tb_len += c;
@@ -2649,7 +2633,7 @@ static int vgetorpeek(bool advance)
// input from the user), show the partially matched characters
// to the user with showcmd.
int showcmd_idx = 0;
- c1 = 0;
+ bool showing_partial = false;
if (typebuf.tb_len > 0 && advance && !exmode_active) {
if (((State & (MODE_NORMAL | MODE_INSERT)) || State == MODE_LANGMAP)
&& State != MODE_HITRETURN) {
@@ -2658,11 +2642,11 @@ static int vgetorpeek(bool advance)
&& ptr2cells((char *)typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1) == 1) {
edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], false);
setcursor(); // put cursor back where it belongs
- c1 = 1;
+ showing_partial = true;
}
// need to use the col and row from above here
- old_wcol = curwin->w_wcol;
- old_wrow = curwin->w_wrow;
+ int old_wcol = curwin->w_wcol;
+ int old_wrow = curwin->w_wrow;
curwin->w_wcol = new_wcol;
curwin->w_wrow = new_wrow;
push_showcmd();
@@ -2676,12 +2660,15 @@ static int vgetorpeek(bool advance)
curwin->w_wrow = old_wrow;
}
- // this looks nice when typing a dead character map
- if ((State & MODE_CMDLINE) && cmdline_star == 0) {
+ // This looks nice when typing a dead character map.
+ // There is no actual command line for get_number().
+ if ((State & MODE_CMDLINE)
+ && get_cmdline_info()->cmdbuff != NULL
+ && cmdline_star == 0) {
char *p = (char *)typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1;
if (ptr2cells(p) == 1 && (uint8_t)(*p) < 128) {
putcmdline(*p, false);
- c1 = 1;
+ showing_partial = true;
}
}
}
@@ -2693,20 +2680,20 @@ static int vgetorpeek(bool advance)
timedout = false;
}
- long wait_time = 0;
+ int wait_time = 0;
if (advance) {
if (typebuf.tb_len == 0 || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) {
// blocking wait
- wait_time = -1L;
+ wait_time = -1;
} else if (keylen == KEYLEN_PART_KEY && p_ttm >= 0) {
- wait_time = p_ttm;
+ wait_time = (int)p_ttm;
} else {
- wait_time = p_tm;
+ wait_time = (int)p_tm;
}
}
- wait_tb_len = typebuf.tb_len;
+ int wait_tb_len = typebuf.tb_len;
c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len,
typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1,
wait_time);
@@ -2714,11 +2701,12 @@ static int vgetorpeek(bool advance)
if (showcmd_idx != 0) {
pop_showcmd();
}
- if (c1 == 1) {
+ if (showing_partial == 1) {
if (State & MODE_INSERT) {
edit_unputchar();
}
- if (State & MODE_CMDLINE) {
+ if ((State & MODE_CMDLINE)
+ && get_cmdline_info()->cmdbuff != NULL) {
unputcmdline();
} else {
setcursor(); // put cursor back where it belongs
@@ -2741,7 +2729,7 @@ static int vgetorpeek(bool advance)
typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES;
}
}
- } // for (;;)
+ } // while (true)
} // if (!character from stuffbuf)
// if advance is false don't loop on NULs
@@ -2767,14 +2755,9 @@ static int vgetorpeek(bool advance)
}
if (timedout && c == ESC) {
- char_u nop_buf[3];
-
// When recording there will be no timeout. Add a <Nop> after the ESC
// to avoid that it forms a key code with following characters.
- nop_buf[0] = K_SPECIAL;
- nop_buf[1] = KS_EXTRA;
- nop_buf[2] = KE_NOP;
- gotchars(nop_buf, 3);
+ gotchars_nop();
}
vgetc_busy--;
@@ -2805,13 +2788,13 @@ static int vgetorpeek(bool advance)
/// Return -1 when end of input script reached.
///
/// @param wait_time milliseconds
-int inchar(char_u *buf, int maxlen, long wait_time)
+int inchar(uint8_t *buf, int maxlen, long wait_time)
{
int len = 0; // Init for GCC.
int retesc = false; // Return ESC with gotint.
const int tb_change_cnt = typebuf.tb_change_cnt;
- if (wait_time == -1L || wait_time > 100L) {
+ if (wait_time == -1 || wait_time > 100) {
// flush output before waiting
ui_flush();
}
@@ -2844,7 +2827,7 @@ int inchar(char_u *buf, int maxlen, long wait_time)
return -1;
}
} else {
- buf[0] = (char_u)script_char;
+ buf[0] = (uint8_t)script_char;
len = 1;
}
}
@@ -2858,10 +2841,10 @@ int inchar(char_u *buf, int maxlen, long wait_time)
// and buf may be pointing inside typebuf.tb_buf[].
if (got_int) {
#define DUM_LEN (MAXMAPLEN * 3 + 3)
- char_u dum[DUM_LEN + 1];
+ uint8_t dum[DUM_LEN + 1];
- for (;;) {
- len = os_inchar(dum, DUM_LEN, 0L, 0, NULL);
+ while (true) {
+ len = os_inchar(dum, DUM_LEN, 0, 0, NULL);
if (len == 0 || (len == 1 && dum[0] == Ctrl_C)) {
break;
}
@@ -2870,8 +2853,10 @@ int inchar(char_u *buf, int maxlen, long wait_time)
}
// Always flush the output characters when getting input characters
- // from the user.
- ui_flush();
+ // from the user and not just peeking.
+ if (wait_time == -1 || wait_time > 10) {
+ ui_flush();
+ }
// Fill up to a third of the buffer, because each character may be
// tripled below.
@@ -2894,10 +2879,10 @@ int inchar(char_u *buf, int maxlen, long wait_time)
return fix_input_buffer(buf, len);
}
-// Fix typed characters for use by vgetc() and check_termcode().
-// "buf[]" must have room to triple the number of bytes!
-// Returns the new length.
-int fix_input_buffer(char_u *buf, int len)
+/// Fix typed characters for use by vgetc().
+/// "buf[]" must have room to triple the number of bytes!
+/// Returns the new length.
+int fix_input_buffer(uint8_t *buf, int len)
FUNC_ATTR_NONNULL_ALL
{
if (!using_script()) {
@@ -2908,19 +2893,18 @@ int fix_input_buffer(char_u *buf, int len)
}
// Reading from script, need to process special bytes
- int i;
- char_u *p = buf;
+ uint8_t *p = buf;
// Two characters are special: NUL and K_SPECIAL.
// Replace NUL by K_SPECIAL KS_ZERO KE_FILLER
// Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER
- for (i = len; --i >= 0; p++) {
+ for (int i = len; --i >= 0; p++) {
if (p[0] == NUL
|| (p[0] == K_SPECIAL
&& (i < 2 || p[1] != KS_EXTRA))) {
memmove(p + 3, p + 1, (size_t)i);
- p[2] = (char_u)K_THIRD(p[0]);
- p[1] = (char_u)K_SECOND(p[0]);
+ p[2] = (uint8_t)K_THIRD(p[0]);
+ p[1] = (uint8_t)K_SECOND(p[0]);
p[0] = K_SPECIAL;
p += 2;
len += 2;
@@ -2930,28 +2914,19 @@ int fix_input_buffer(char_u *buf, int len)
return len;
}
-static bool typebuf_match_len(const uint8_t *str, int *mlen)
-{
- int i;
- for (i = 0; i < typebuf.tb_len && str[i]; i++) {
- if (str[i] != typebuf.tb_buf[typebuf.tb_off + i]) {
- break;
- }
- }
- *mlen = i;
- return str[i] == NUL; // matched the whole string
-}
-
-/// Get command argument for <Cmd> key
+/// Function passed to do_cmdline() to get the command after a <Cmd> key from
+/// typeahead.
char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
{
garray_T line_ga;
- int c1 = -1, c2;
+ int c1 = -1;
+ int c2;
int cmod = 0;
bool aborted = false;
ga_init(&line_ga, 1, 32);
+ // no mapping for these characters
no_mapping++;
got_int = false;
@@ -2961,16 +2936,17 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
if (vgetorpeek(false) == NUL) {
// incomplete <Cmd> is an error, because there is not much the user
// could do in this state.
- emsg(e_cmdmap_err);
+ emsg(_(e_cmd_mapping_must_end_with_cr));
aborted = true;
break;
}
// Get one character at a time.
c1 = vgetorpeek(true);
+
// Get two extra bytes for special keys
if (c1 == K_SPECIAL) {
- c1 = vgetorpeek(true); // no mapping for these chars
+ c1 = vgetorpeek(true);
c2 = vgetorpeek(true);
if (c1 == KS_MODIFIER) {
cmod = c2;
@@ -2986,8 +2962,8 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
} else if (c1 == ESC) {
aborted = true;
} else if (c1 == K_COMMAND) {
- // special case to give nicer error message
- emsg(e_cmdmap_repeated);
+ // give a nicer error message for this special case
+ emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd));
aborted = true;
} else if (c1 == K_SNR) {
ga_concat(&line_ga, "<SNR>");
@@ -3018,7 +2994,12 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
return line_ga.ga_data;
}
-bool map_execute_lua(void)
+/// Handle a Lua mapping: get its LuaRef from typeahead and execute it.
+///
+/// @param may_repeat save the LuaRef for redoing with "." later
+///
+/// @return false if getting the LuaRef was aborted, true otherwise
+bool map_execute_lua(bool may_repeat)
{
garray_T line_ga;
int c1 = -1;
@@ -3050,6 +3031,10 @@ bool map_execute_lua(void)
}
LuaRef ref = (LuaRef)atoi(line_ga.ga_data);
+ if (may_repeat) {
+ repeat_luaref = ref;
+ }
+
Error err = ERROR_INIT;
Array args = ARRAY_DICT_INIT;
nlua_call_ref(ref, NULL, args, false, &err);
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 6996b00c6e..177a021706 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -1,29 +1,20 @@
-#ifndef NVIM_GETCHAR_H
-#define NVIM_GETCHAR_H
+#pragma once
-#include "nvim/os/fileio.h"
-#include "nvim/vim.h"
+#include <stdbool.h>
+#include <stdint.h>
-/// Values for "noremap" argument of ins_typebuf()
-///
-/// Also used for map->m_noremap and menu->noremap[].
-enum RemapValues {
- REMAP_YES = 0, ///< Allow remapping.
- REMAP_NONE = -1, ///< No remapping.
- REMAP_SCRIPT = -2, ///< Remap script-local mappings only.
- REMAP_SKIP = -3, ///< No remapping for first char.
-};
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/getchar_defs.h" // IWYU pragma: export
+#include "nvim/os/fileio.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
-// Argument for flush_buffers().
+/// Argument for flush_buffers().
typedef enum {
FLUSH_MINIMAL,
- FLUSH_TYPEAHEAD, // flush current typebuf contents
- FLUSH_INPUT, // flush typebuf and inchar() input
+ FLUSH_TYPEAHEAD, ///< flush current typebuf contents
+ FLUSH_INPUT, ///< flush typebuf and inchar() input
} flush_buffers_T;
-#define KEYLEN_PART_KEY (-1) // keylen value for incomplete key-code
-#define KEYLEN_PART_MAP (-2) // keylen value for incomplete mapping
-
/// Maximum number of streams to read script from
enum { NSCRIPT = 15, };
@@ -33,4 +24,3 @@ extern FileDescriptor *scriptin[NSCRIPT];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "getchar.h.generated.h"
#endif
-#endif // NVIM_GETCHAR_H
diff --git a/src/nvim/getchar_defs.h b/src/nvim/getchar_defs.h
new file mode 100644
index 0000000000..5e6db7d9f3
--- /dev/null
+++ b/src/nvim/getchar_defs.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+
+typedef struct buffblock buffblock_T;
+typedef struct buffheader buffheader_T;
+
+/// structure used to store one block of the stuff/redo/recording buffers
+struct buffblock {
+ buffblock_T *b_next; ///< pointer to next buffblock
+ char b_str[1]; ///< contents (actually longer)
+};
+
+/// header used for the stuff buffer and the redo buffer
+struct buffheader {
+ buffblock_T bh_first; ///< first (dummy) block of list
+ buffblock_T *bh_curr; ///< buffblock for appending
+ size_t bh_index; ///< index for reading
+ size_t bh_space; ///< space in bh_curr for appending
+};
+
+typedef struct {
+ buffheader_T sr_redobuff;
+ buffheader_T sr_old_redobuff;
+} save_redo_T;
+
+/// Used for the typeahead buffer: typebuf.
+typedef struct {
+ uint8_t *tb_buf; ///< buffer for typed characters
+ uint8_t *tb_noremap; ///< mapping flags for characters in tb_buf[]
+ int tb_buflen; ///< size of tb_buf[]
+ int tb_off; ///< current position in tb_buf[]
+ int tb_len; ///< number of valid bytes in tb_buf[]
+ int tb_maplen; ///< nr of mapped bytes in tb_buf[]
+ int tb_silent; ///< nr of silently mapped bytes in tb_buf[]
+ int tb_no_abbr_cnt; ///< nr of bytes without abbrev. in tb_buf[]
+ int tb_change_cnt; ///< nr of time tb_buf was changed; never zero
+} typebuf_T;
+
+/// Struct to hold the saved typeahead for save_typeahead().
+typedef struct {
+ typebuf_T save_typebuf;
+ bool typebuf_valid; ///< true when save_typebuf valid
+ int old_char;
+ int old_mod_mask;
+ buffheader_T save_readbuf1;
+ buffheader_T save_readbuf2;
+ String save_inputbuf;
+} tasave_T;
+
+/// Values for "noremap" argument of ins_typebuf()
+///
+/// Also used for map->m_noremap and menu->noremap[].
+enum RemapValues {
+ REMAP_YES = 0, ///< Allow remapping.
+ REMAP_NONE = -1, ///< No remapping.
+ REMAP_SCRIPT = -2, ///< Remap script-local mappings only.
+ REMAP_SKIP = -3, ///< No remapping for first char.
+};
diff --git a/src/nvim/gettext.h b/src/nvim/gettext.h
index 6d2a55124e..2c7b5626d2 100644
--- a/src/nvim/gettext.h
+++ b/src/nvim/gettext.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_GETTEXT_H
-#define NVIM_GETTEXT_H
+#pragma once
#ifdef HAVE_WORKING_LIBINTL
-# include <libintl.h>
-# define _(x) gettext((char *)(x))
+# include <libintl.h> // IWYU pragma: export
+# define _(x) gettext(x) // NOLINT(bugprone-reserved-identifier)
// XXX do we actually need this?
# ifdef gettext_noop
# define N_(x) gettext_noop(x)
@@ -17,12 +16,10 @@
# undef setlocale
# endif
#else
-# define _(x) ((char *)(x))
+# define _(x) ((char *)(x)) // NOLINT(bugprone-reserved-identifier)
# define N_(x) x
# define NGETTEXT(x, xs, n) ((n) == 1 ? (x) : (xs))
# define bindtextdomain(x, y) // empty
# define bind_textdomain_codeset(x, y) // empty
# define textdomain(x) // empty
#endif
-
-#endif // NVIM_GETTEXT_H
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index df4ae05522..270ffe4fa0 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1,21 +1,23 @@
-#ifndef NVIM_GLOBALS_H
-#define NVIM_GLOBALS_H
+#pragma once
#include <inttypes.h>
#include <stdbool.h>
-#include "nvim/ascii.h"
+#include "nvim/arglist_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/event/loop.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval_defs.h"
-#include "nvim/iconv.h"
-#include "nvim/macros.h"
+#include "nvim/getchar_defs.h"
+#include "nvim/iconv_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/menu_defs.h"
#include "nvim/os/os_defs.h"
#include "nvim/runtime.h"
+#include "nvim/state_defs.h"
#include "nvim/syntax_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#define IOSIZE (1024 + 1) // file I/O and sprintf buffer size
@@ -23,14 +25,15 @@
#define MSG_BUF_CLEN (MSG_BUF_LEN / 6) // cell length (worst case: utf-8
// takes 6 bytes for one cell)
-#ifdef MSWIN
-# define _PATHSEPSTR "\\"
-#else
-# define _PATHSEPSTR "/"
-#endif
+// FILETYPE_FILE used for file type detection
+// FTPLUGIN_FILE used for loading filetype plugin files
+// INDENT_FILE used for loading indent files
+// FTOFF_FILE used for file type detection
+// FTPLUGOF_FILE used for loading settings files
+// INDOFF_FILE used for loading indent files
#ifndef FILETYPE_FILE
-# define FILETYPE_FILE "filetype.lua filetype.vim"
+# define FILETYPE_FILE "filetype.lua filetype.vim"
#endif
#ifndef FTPLUGIN_FILE
@@ -56,15 +59,15 @@
#define DFLT_ERRORFILE "errors.err"
#ifndef SYS_VIMRC_FILE
-# define SYS_VIMRC_FILE "$VIM" _PATHSEPSTR "sysinit.vim"
+# define SYS_VIMRC_FILE "$VIM/sysinit.vim"
#endif
#ifndef DFLT_HELPFILE
-# define DFLT_HELPFILE "$VIMRUNTIME" _PATHSEPSTR "doc" _PATHSEPSTR "help.txt"
+# define DFLT_HELPFILE "$VIMRUNTIME/doc/help.txt"
#endif
#ifndef SYNTAX_FNAME
-# define SYNTAX_FNAME "$VIMRUNTIME" _PATHSEPSTR "syntax" _PATHSEPSTR "%s.vim"
+# define SYNTAX_FNAME "$VIMRUNTIME/syntax/%s.vim"
#endif
#ifndef EXRC_FILE
@@ -83,7 +86,7 @@ EXTERN struct nvim_stats_s {
int64_t fsync;
int64_t redraw;
int16_t log_skip; // How many logs were tried and skipped before log_init.
-} g_stats INIT(= { 0, 0, 0 });
+} g_stats INIT( = { 0, 0, 0 });
// Values for "starting".
#define NO_SCREEN 2 // no screen updating yet
@@ -97,8 +100,8 @@ EXTERN struct nvim_stats_s {
// up).
#define DFLT_COLS 80 // default value for 'columns'
#define DFLT_ROWS 24 // default value for 'lines'
-EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen
-EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen
+EXTERN int Rows INIT( = DFLT_ROWS); // nr of rows in the screen
+EXTERN int Columns INIT( = DFLT_COLS); // nr of columns in the screen
// We use 64-bit file functions here, if available. E.g. ftello() returns
// off_t instead of long, which helps if long is 32 bit and off_t is 64 bit.
@@ -130,11 +133,11 @@ typedef off_t off_T;
// When vgetc() is called, it sets mod_mask to the set of modifiers that are
// held down based on the MOD_MASK_* symbols that are read first.
-EXTERN int mod_mask INIT(= 0); // current key modifiers
+EXTERN int mod_mask INIT( = 0); // current key modifiers
// The value of "mod_mask" and the unmodified character before calling merge_modifiers().
-EXTERN int vgetc_mod_mask INIT(= 0);
-EXTERN int vgetc_char INIT(= 0);
+EXTERN int vgetc_mod_mask INIT( = 0);
+EXTERN int vgetc_char INIT( = 0);
// Cmdline_row is the row where the command line starts, just below the
// last window.
@@ -145,65 +148,65 @@ EXTERN int vgetc_char INIT(= 0);
// update_screen().
EXTERN int cmdline_row;
-EXTERN bool redraw_cmdline INIT(= false); // cmdline must be redrawn
-EXTERN bool redraw_mode INIT(= false); // mode must be redrawn
-EXTERN bool clear_cmdline INIT(= false); // cmdline must be cleared
-EXTERN bool mode_displayed INIT(= false); // mode is being displayed
-EXTERN int cmdline_star INIT(= false); // cmdline is encrypted
-EXTERN bool redrawing_cmdline INIT(= false); // cmdline is being redrawn
-EXTERN bool cmdline_was_last_drawn INIT(= false); // cmdline was last drawn
+EXTERN bool redraw_cmdline INIT( = false); // cmdline must be redrawn
+EXTERN bool redraw_mode INIT( = false); // mode must be redrawn
+EXTERN bool clear_cmdline INIT( = false); // cmdline must be cleared
+EXTERN bool mode_displayed INIT( = false); // mode is being displayed
+EXTERN int cmdline_star INIT( = false); // cmdline is encrypted
+EXTERN bool redrawing_cmdline INIT( = false); // cmdline is being redrawn
+EXTERN bool cmdline_was_last_drawn INIT( = false); // cmdline was last drawn
-EXTERN bool exec_from_reg INIT(= false); // executing register
+EXTERN bool exec_from_reg INIT( = false); // executing register
// When '$' is included in 'cpoptions' option set:
// When a change command is given that deletes only part of a line, a dollar
// is put at the end of the changed text. dollar_vcol is set to the virtual
// column of this '$'. -1 is used to indicate no $ is being displayed.
-EXTERN colnr_T dollar_vcol INIT(= -1);
+EXTERN colnr_T dollar_vcol INIT( = -1);
// Variables for Insert mode completion.
-EXTERN char *edit_submode INIT(= NULL); // msg for CTRL-X submode
-EXTERN char *edit_submode_pre INIT(= NULL); // prepended to edit_submode
-EXTERN char *edit_submode_extra INIT(= NULL); // appended to edit_submode
+EXTERN char *edit_submode INIT( = NULL); // msg for CTRL-X submode
+EXTERN char *edit_submode_pre INIT( = NULL); // prepended to edit_submode
+EXTERN char *edit_submode_extra INIT( = NULL); // appended to edit_submode
EXTERN hlf_T edit_submode_highl; // highl. method for extra info
// state for putting characters in the message area
-EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left
+EXTERN bool cmdmsg_rl INIT( = false); // cmdline is drawn right to left
EXTERN int msg_col;
EXTERN int msg_row;
EXTERN int msg_scrolled; // Number of screen lines that windows have
// scrolled because of printing messages.
// when true don't set need_wait_return in msg_puts_attr()
// when msg_scrolled is non-zero
-EXTERN bool msg_scrolled_ign INIT(= false);
+EXTERN bool msg_scrolled_ign INIT( = false);
// Whether the screen is damaged due to scrolling. Sometimes msg_scrolled
// is reset before the screen is redrawn, so we need to keep track of this.
-EXTERN bool msg_did_scroll INIT(= false);
-
-EXTERN char *keep_msg INIT(= NULL); // msg to be shown after redraw
-EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg
-EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw
-EXTERN int msg_scroll INIT(= false); // msg_start() will scroll
-EXTERN bool msg_didout INIT(= false); // msg_outstr() was used in line
-EXTERN bool msg_didany INIT(= false); // msg_outstr() was used at all
-EXTERN bool msg_nowait INIT(= false); // don't wait for this msg
-EXTERN int emsg_off INIT(= 0); // don't display errors for now,
- // unless 'debug' is set.
-EXTERN bool info_message INIT(= false); // printing informative message
-EXTERN bool msg_hist_off INIT(= false); // don't add messages to history
-EXTERN bool need_clr_eos INIT(= false); // need to clear text before
- // displaying a message.
-EXTERN int emsg_skip INIT(= 0); // don't display errors for
- // expression that is skipped
-EXTERN bool emsg_severe INIT(= false); // use message of next of several
- // emsg() calls for throw
+EXTERN bool msg_did_scroll INIT( = false);
+
+EXTERN char *keep_msg INIT( = NULL); // msg to be shown after redraw
+EXTERN int keep_msg_attr INIT( = 0); // highlight attr for keep_msg
+EXTERN bool need_fileinfo INIT( = false); // do fileinfo() after redraw
+EXTERN int msg_scroll INIT( = false); // msg_start() will scroll
+EXTERN bool msg_didout INIT( = false); // msg_outstr() was used in line
+EXTERN bool msg_didany INIT( = false); // msg_outstr() was used at all
+EXTERN bool msg_nowait INIT( = false); // don't wait for this msg
+EXTERN int emsg_off INIT( = 0); // don't display errors for now,
+ // unless 'debug' is set.
+EXTERN bool info_message INIT( = false); // printing informative message
+EXTERN bool msg_hist_off INIT( = false); // don't add messages to history
+EXTERN bool need_clr_eos INIT( = false); // need to clear text before
+ // displaying a message.
+EXTERN int emsg_skip INIT( = 0); // don't display errors for
+ // expression that is skipped
+EXTERN bool emsg_severe INIT( = false); // use message of next of several
+ // emsg() calls for throw
// used by assert_fails()
-EXTERN char *emsg_assert_fails_msg INIT(= NULL);
-EXTERN long emsg_assert_fails_lnum INIT(= 0);
-EXTERN char *emsg_assert_fails_context INIT(= NULL);
+EXTERN char *emsg_assert_fails_msg INIT( = NULL);
+EXTERN long emsg_assert_fails_lnum INIT( = 0);
+EXTERN char *emsg_assert_fails_context INIT( = NULL);
-EXTERN bool did_endif INIT(= false); // just had ":endif"
+EXTERN bool did_endif INIT( = false); // just had ":endif"
EXTERN dict_T vimvardict; // Dictionary with v: variables
EXTERN dict_T globvardict; // Dictionary with g: variables
/// g: value
@@ -214,39 +217,39 @@ EXTERN bool called_vim_beep; // set if vim_beep() is called
EXTERN bool did_emsg_syntax; // did_emsg set because of a
// syntax error
EXTERN int called_emsg; // always incremented by emsg()
-EXTERN int ex_exitval INIT(= 0); // exit value for ex mode
-EXTERN bool emsg_on_display INIT(= false); // there is an error message
-EXTERN bool rc_did_emsg INIT(= false); // vim_regcomp() called emsg()
+EXTERN int ex_exitval INIT( = 0); // exit value for ex mode
+EXTERN bool emsg_on_display INIT( = false); // there is an error message
+EXTERN bool rc_did_emsg INIT( = false); // vim_regcomp() called emsg()
-EXTERN int no_wait_return INIT(= 0); // don't wait for return for now
-EXTERN bool need_wait_return INIT(= false); // need to wait for return later
-EXTERN bool did_wait_return INIT(= false); // wait_return() was used and
- // nothing written since then
-EXTERN bool need_maketitle INIT(= true); // call maketitle() soon
+EXTERN int no_wait_return INIT( = 0); // don't wait for return for now
+EXTERN bool need_wait_return INIT( = false); // need to wait for return later
+EXTERN bool did_wait_return INIT( = false); // wait_return() was used and
+ // nothing written since then
+EXTERN bool need_maketitle INIT( = true); // call maketitle() soon
-EXTERN int quit_more INIT(= false); // 'q' hit at "--more--" msg
-EXTERN int vgetc_busy INIT(= 0); // when inside vgetc() then > 0
+EXTERN bool quit_more INIT( = false); // 'q' hit at "--more--" msg
+EXTERN int vgetc_busy INIT( = 0); // when inside vgetc() then > 0
-EXTERN bool didset_vim INIT(= false); // did set $VIM ourselves
-EXTERN bool didset_vimruntime INIT(= false); // idem for $VIMRUNTIME
+EXTERN bool didset_vim INIT( = false); // did set $VIM ourselves
+EXTERN bool didset_vimruntime INIT( = false); // idem for $VIMRUNTIME
/// Lines left before a "more" message. Ex mode needs to be able to reset this
/// after you type something.
-EXTERN int lines_left INIT(= -1); // lines left for listing
-EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate
- // messages
+EXTERN int lines_left INIT( = -1); // lines left for listing
+EXTERN bool msg_no_more INIT( = false); // don't use more prompt, truncate
+ // messages
-EXTERN int ex_nesting_level INIT(= 0); // nesting level
-EXTERN int debug_break_level INIT(= -1); // break below this level
-EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message
-EXTERN int debug_tick INIT(= 0); // breakpoint change count
-EXTERN int debug_backtrace_level INIT(= 0); // breakpoint backtrace level
+EXTERN int ex_nesting_level INIT( = 0); // nesting level
+EXTERN int debug_break_level INIT( = -1); // break below this level
+EXTERN bool debug_did_msg INIT( = false); // did "debug mode" message
+EXTERN int debug_tick INIT( = 0); // breakpoint change count
+EXTERN int debug_backtrace_level INIT( = 0); // breakpoint backtrace level
// Values for "do_profiling".
#define PROF_NONE 0 ///< profiling not started
#define PROF_YES 1 ///< profiling busy
#define PROF_PAUSED 2 ///< profiling paused
-EXTERN int do_profiling INIT(= PROF_NONE); ///< PROF_ values
+EXTERN int do_profiling INIT( = PROF_NONE); ///< PROF_ values
/// Exception currently being thrown. Used to pass an exception to a different
/// cstack. Also used for discarding an exception before it is caught or made
@@ -255,19 +258,19 @@ EXTERN except_T *current_exception;
/// An exception is being thrown. Reset when the exception is caught or as
/// long as it is pending in a finally clause.
-EXTERN bool did_throw INIT(= false);
+EXTERN bool did_throw INIT( = false);
/// Set when a throw that cannot be handled in do_cmdline() must be propagated
/// to the cstack of the previously called do_cmdline().
-EXTERN bool need_rethrow INIT(= false);
+EXTERN bool need_rethrow INIT( = false);
/// Set when a ":finish" or ":return" that cannot be handled in do_cmdline()
/// must be propagated to the cstack of the previously called do_cmdline().
-EXTERN bool check_cstack INIT(= false);
+EXTERN bool check_cstack INIT( = false);
/// Number of nested try conditionals (across function calls and ":source"
/// commands).
-EXTERN int trylevel INIT(= 0);
+EXTERN int trylevel INIT( = 0);
/// When "force_abort" is true, always skip commands after an error message,
/// even after the outermost ":endif", ":endwhile" or ":endfor" or for a
@@ -275,7 +278,7 @@ EXTERN int trylevel INIT(= 0);
/// non-zero (and ":silent!" was not used) or an exception is being thrown at
/// the time an error is detected. It is set to false when "trylevel" gets
/// zero again and there was no error or interrupt or throw.
-EXTERN int force_abort INIT(= false);
+EXTERN bool force_abort INIT( = false);
/// "msg_list" points to a variable in the stack of do_cmdline() which keeps
/// the list of arguments of several emsg() calls, one of which is to be
@@ -285,19 +288,19 @@ EXTERN int force_abort INIT(= false);
/// same as the "msg" field of that element, but can be identical to the "msg"
/// field of a later list element, when the "emsg_severe" flag was set when the
/// emsg() call was made.
-EXTERN msglist_T **msg_list INIT(= NULL);
+EXTERN msglist_T **msg_list INIT( = NULL);
/// When set, don't convert an error to an exception. Used when displaying the
/// interrupt message or reporting an exception that is still uncaught at the
/// top level (which has already been discarded then). Also used for the error
/// message when no exception can be thrown.
-EXTERN bool suppress_errthrow INIT(= false);
+EXTERN bool suppress_errthrow INIT( = false);
/// The stack of all caught and not finished exceptions. The exception on the
/// top of the stack is the one got by evaluation of v:exception. The complete
/// stack of all caught and pending exceptions is embedded in the various
/// cstacks; the pending exceptions, however, are not on the caught stack.
-EXTERN except_T *caught_stack INIT(= NULL);
+EXTERN except_T *caught_stack INIT( = NULL);
///
/// Garbage collection can only take place when we are sure there are no Lists
@@ -307,9 +310,9 @@ EXTERN except_T *caught_stack INIT(= NULL);
/// we do garbage collection before waiting for a char at the toplevel.
/// "garbage_collect_at_exit" indicates garbagecollect(1) was called.
///
-EXTERN bool may_garbage_collect INIT(= false);
-EXTERN int want_garbage_collect INIT(= false);
-EXTERN int garbage_collect_at_exit INIT(= false);
+EXTERN bool may_garbage_collect INIT( = false);
+EXTERN bool want_garbage_collect INIT( = false);
+EXTERN bool garbage_collect_at_exit INIT( = false);
// Special values for current_SID.
#define SID_MODELINE (-1) // when using a modeline
@@ -324,11 +327,11 @@ EXTERN int garbage_collect_at_exit INIT(= false);
#define SID_STR (-10) // for sourcing a string with no script item
// Script CTX being sourced or was sourced to define the current function.
-EXTERN sctx_T current_sctx INIT(= { 0, 0, 0 });
+EXTERN sctx_T current_sctx INIT( = { 0, 0, 0 });
// ID of the current channel making a client API call
-EXTERN uint64_t current_channel_id INIT(= 0);
+EXTERN uint64_t current_channel_id INIT( = 0);
-EXTERN bool did_source_packages INIT(= false);
+EXTERN bool did_source_packages INIT( = false);
// Scope information for the code that indirectly triggered the current
// provider function call
@@ -336,76 +339,77 @@ EXTERN struct caller_scope {
sctx_T script_ctx;
estack_T es_entry;
char *autocmd_fname, *autocmd_match;
+ bool autocmd_fname_full;
int autocmd_bufnr;
void *funccalp;
} provider_caller_scope;
-EXTERN int provider_call_nesting INIT(= 0);
+EXTERN int provider_call_nesting INIT( = 0);
-EXTERN int t_colors INIT(= 256); // int value of T_CCO
+EXTERN int t_colors INIT( = 256); // int value of T_CCO
// Flags to indicate an additional string for highlight name completion.
-EXTERN int include_none INIT(= 0); // when 1 include "None"
-EXTERN int include_default INIT(= 0); // when 1 include "default"
-EXTERN int include_link INIT(= 0); // when 2 include "link" and "clear"
+EXTERN int include_none INIT( = 0); // when 1 include "None"
+EXTERN int include_default INIT( = 0); // when 1 include "default"
+EXTERN int include_link INIT( = 0); // when 2 include "link" and "clear"
// When highlight_match is true, highlight a match, starting at the cursor
// position. Search_match_lines is the number of lines after the match (0 for
// a match within one line), search_match_endcol the column number of the
// character just after the match in the last line.
-EXTERN bool highlight_match INIT(= false); // show search match pos
+EXTERN bool highlight_match INIT( = false); // show search match pos
EXTERN linenr_T search_match_lines; // lines of matched string
EXTERN colnr_T search_match_endcol; // col nr of match end
-EXTERN linenr_T search_first_line INIT(= 0); // for :{FIRST},{last}s/pat
-EXTERN linenr_T search_last_line INIT(= MAXLNUM); // for :{first},{LAST}s/pat
+EXTERN linenr_T search_first_line INIT( = 0); // for :{FIRST},{last}s/pat
+EXTERN linenr_T search_last_line INIT( = MAXLNUM); // for :{first},{LAST}s/pat
-EXTERN bool no_smartcase INIT(= false); // don't use 'smartcase' once
+EXTERN bool no_smartcase INIT( = false); // don't use 'smartcase' once
-EXTERN int need_check_timestamps INIT(= false); // need to check file
- // timestamps asap
-EXTERN int did_check_timestamps INIT(= false); // did check timestamps
- // recently
-EXTERN int no_check_timestamps INIT(= 0); // Don't check timestamps
+EXTERN bool need_check_timestamps INIT( = false); // need to check file
+ // timestamps asap
+EXTERN bool did_check_timestamps INIT( = false); // did check timestamps
+ // recently
+EXTERN int no_check_timestamps INIT( = 0); // Don't check timestamps
-EXTERN int autocmd_busy INIT(= false); // Is apply_autocmds() busy?
-EXTERN int autocmd_no_enter INIT(= false); // *Enter autocmds disabled
-EXTERN int autocmd_no_leave INIT(= false); // *Leave autocmds disabled
+EXTERN bool autocmd_busy INIT( = false); // Is apply_autocmds() busy?
+EXTERN int autocmd_no_enter INIT( = false); // *Enter autocmds disabled
+EXTERN int autocmd_no_leave INIT( = false); // *Leave autocmds disabled
EXTERN int modified_was_set; // did ":set modified"
-EXTERN int did_filetype INIT(= false); // FileType event found
+EXTERN bool did_filetype INIT( = false); // FileType event found
// value for did_filetype when starting to execute autocommands
-EXTERN int keep_filetype INIT(= false);
+EXTERN bool keep_filetype INIT( = false);
// When deleting the current buffer, another one must be loaded.
// If we know which one is preferred, au_new_curbuf is set to it.
-EXTERN bufref_T au_new_curbuf INIT(= { NULL, 0, 0 });
+EXTERN bufref_T au_new_curbuf INIT( = { NULL, 0, 0 });
// When deleting a buffer/window and autocmd_busy is true, do not free the
// buffer/window. but link it in the list starting with
// au_pending_free_buf/ap_pending_free_win, using b_next/w_next.
// Free the buffer/window when autocmd_busy is being set to false.
-EXTERN buf_T *au_pending_free_buf INIT(= NULL);
-EXTERN win_T *au_pending_free_win INIT(= NULL);
+EXTERN buf_T *au_pending_free_buf INIT( = NULL);
+EXTERN win_T *au_pending_free_win INIT( = NULL);
// Mouse coordinates, set by handle_mouse_event()
EXTERN int mouse_grid;
EXTERN int mouse_row;
EXTERN int mouse_col;
-EXTERN bool mouse_past_bottom INIT(= false); // mouse below last line
-EXTERN bool mouse_past_eol INIT(= false); // mouse right of line
-EXTERN int mouse_dragging INIT(= 0); // extending Visual area with
- // mouse dragging
+EXTERN bool mouse_past_bottom INIT( = false); // mouse below last line
+EXTERN bool mouse_past_eol INIT( = false); // mouse right of line
+EXTERN int mouse_dragging INIT( = 0); // extending Visual area with
+ // mouse dragging
// The root of the menu hierarchy.
-EXTERN vimmenu_T *root_menu INIT(= NULL);
+EXTERN vimmenu_T *root_menu INIT( = NULL);
// While defining the system menu, sys_menu is true. This avoids
// overruling of menus that the user already defined.
-EXTERN int sys_menu INIT(= false);
+EXTERN bool sys_menu INIT( = false);
// All windows are linked in a list. firstwin points to the first entry,
// lastwin to the last entry (can be the same as firstwin) and curwin to the
// currently active window.
EXTERN win_T *firstwin; // first window
EXTERN win_T *lastwin; // last window
-EXTERN win_T *prevwin INIT(= NULL); // previous window
+EXTERN win_T *prevwin INIT( = NULL); // previous window
#define ONE_WINDOW (firstwin == lastwin)
#define FOR_ALL_FRAMES(frp, first_frame) \
for ((frp) = first_frame; (frp) != NULL; (frp) = (frp)->fr_next) // NOLINT
@@ -416,10 +420,9 @@ EXTERN win_T *prevwin INIT(= NULL); // previous window
FOR_ALL_TABS(tp) \
FOR_ALL_WINDOWS_IN_TAB(wp, tp)
-// -V:FOR_ALL_WINDOWS_IN_TAB:501
#define FOR_ALL_WINDOWS_IN_TAB(wp, tp) \
- for (win_T *wp = ((tp) == curtab) \
- ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next)
+ for (win_T *wp = ((tp) == curtab) ? firstwin : (tp)->tp_firstwin; \
+ wp != NULL; wp = wp->w_next)
EXTERN win_T *curwin; // currently active window
@@ -432,7 +435,7 @@ typedef struct {
/// When executing autocommands for a buffer that is not in any window, a
/// special window is created to handle the side effects. When autocommands
/// nest we may need more than one.
-EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT(= KV_INITIAL_VALUE);
+EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT( = KV_INITIAL_VALUE);
#define aucmd_win (aucmd_win_vec.items)
#define AUCMD_WIN_COUNT ((int)aucmd_win_vec.size)
@@ -446,16 +449,16 @@ EXTERN frame_T *topframe; // top of the window frame tree
EXTERN tabpage_T *first_tabpage;
EXTERN tabpage_T *curtab;
EXTERN tabpage_T *lastused_tabpage;
-EXTERN bool redraw_tabline INIT(= false); // need to redraw tabline
+EXTERN bool redraw_tabline INIT( = false); // need to redraw tabline
// Iterates over all tabs in the tab list
#define FOR_ALL_TABS(tp) for (tabpage_T *(tp) = first_tabpage; (tp) != NULL; (tp) = (tp)->tp_next)
// All buffers are linked in a list. 'firstbuf' points to the first entry,
// 'lastbuf' to the last entry and 'curbuf' to the currently active buffer.
-EXTERN buf_T *firstbuf INIT(= NULL); // first buffer
-EXTERN buf_T *lastbuf INIT(= NULL); // last buffer
-EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
+EXTERN buf_T *firstbuf INIT( = NULL); // first buffer
+EXTERN buf_T *lastbuf INIT( = NULL); // last buffer
+EXTERN buf_T *curbuf INIT( = NULL); // currently active buffer
// Iterates over all buffers in the buffer list.
#define FOR_ALL_BUFFERS(buf) \
@@ -466,16 +469,12 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
#define FOR_ALL_BUF_WININFO(buf, wip) \
for ((wip) = (buf)->b_wininfo; (wip) != NULL; (wip) = (wip)->wi_next) // NOLINT
-// Iterate through all the signs placed in a buffer
-#define FOR_ALL_SIGNS_IN_BUF(buf, sign) \
- for ((sign) = (buf)->b_signlist; (sign) != NULL; (sign) = (sign)->se_next) // NOLINT
-
// List of files being edited (global argument list). curwin->w_alist points
// to this when the window is using the global argument list.
EXTERN alist_T global_alist; // global argument list
-EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id
-EXTERN bool arg_had_last INIT(= false); // accessed last file in
- // global_alist
+EXTERN int max_alist_id INIT( = 0); ///< the previous argument list id
+EXTERN bool arg_had_last INIT( = false); // accessed last file in
+ // global_alist
EXTERN int ru_col; // column for ruler
EXTERN int ru_wid; // 'rulerfmt' width of ruler when non-zero
@@ -485,61 +484,61 @@ EXTERN int sc_col; // column for shown command
// updating).
// First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished.
-EXTERN int starting INIT(= NO_SCREEN);
+EXTERN int starting INIT( = NO_SCREEN);
// true when planning to exit. Might keep running if there is a changed buffer.
-EXTERN bool exiting INIT(= false);
+EXTERN bool exiting INIT( = false);
// internal value of v:dying
-EXTERN int v_dying INIT(= 0);
+EXTERN int v_dying INIT( = 0);
// is stdin a terminal?
-EXTERN bool stdin_isatty INIT(= true);
+EXTERN bool stdin_isatty INIT( = true);
// is stdout a terminal?
-EXTERN bool stdout_isatty INIT(= true);
+EXTERN bool stdout_isatty INIT( = true);
// is stderr a terminal?
-EXTERN bool stderr_isatty INIT(= true);
+EXTERN bool stderr_isatty INIT( = true);
/// filedesc set by embedder for reading first buffer like `cmd | nvim -`
-EXTERN int stdin_fd INIT(= -1);
+EXTERN int stdin_fd INIT( = -1);
// true when doing full-screen output, otherwise only writing some messages.
-EXTERN int full_screen INIT(= false);
+EXTERN bool full_screen INIT( = false);
/// Non-zero when only "safe" commands are allowed
-EXTERN int secure INIT(= 0);
+EXTERN int secure INIT( = 0);
/// Non-zero when changing text and jumping to another window or editing another buffer is not
/// allowed.
-EXTERN int textlock INIT(= 0);
+EXTERN int textlock INIT( = 0);
/// Non-zero when no buffer name can be changed, no buffer can be deleted and
/// current directory can't be changed. Used for SwapExists et al.
-EXTERN int allbuf_lock INIT(= 0);
+EXTERN int allbuf_lock INIT( = 0);
/// Non-zero when evaluating an expression in a "sandbox". Several things are
/// not allowed then.
-EXTERN int sandbox INIT(= 0);
+EXTERN int sandbox INIT( = 0);
/// Batch-mode: "-es", "-Es", "-l" commandline argument was given.
-EXTERN int silent_mode INIT(= false);
+EXTERN bool silent_mode INIT( = false);
/// Start position of active Visual selection.
EXTERN pos_T VIsual;
/// Whether Visual mode is active.
-EXTERN int VIsual_active INIT(= false);
+EXTERN bool VIsual_active INIT( = false);
/// Whether Select mode is active.
-EXTERN int VIsual_select INIT(= false);
+EXTERN bool VIsual_select INIT( = false);
/// Register name for Select mode
-EXTERN int VIsual_select_reg INIT(= 0);
+EXTERN int VIsual_select_reg INIT( = 0);
/// Restart Select mode when next cmd finished
-EXTERN int restart_VIsual_select INIT(= 0);
+EXTERN int restart_VIsual_select INIT( = 0);
/// Whether to restart the selection after a Select-mode mapping or menu.
EXTERN int VIsual_reselect;
/// Type of Visual mode.
-EXTERN int VIsual_mode INIT(= 'v');
+EXTERN int VIsual_mode INIT( = 'v');
/// true when redoing Visual.
-EXTERN int redo_VIsual_busy INIT(= false);
+EXTERN bool redo_VIsual_busy INIT( = false);
// The Visual area is remembered for reselection.
-EXTERN int resel_VIsual_mode INIT(= NUL); // 'v', 'V', or Ctrl-V
+EXTERN int resel_VIsual_mode INIT( = NUL); // 'v', 'V', or Ctrl-V
EXTERN linenr_T resel_VIsual_line_count; // number of lines
EXTERN colnr_T resel_VIsual_vcol; // nr of cols or end col
@@ -551,40 +550,40 @@ EXTERN pos_T where_paste_started;
// <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and
// reset when any other editing is done on the line. If an <ESC> or <RETURN>
// is received, and did_ai is true, the line is truncated.
-EXTERN bool did_ai INIT(= false);
+EXTERN bool did_ai INIT( = false);
// Column of first char after autoindent. 0 when no autoindent done. Used
// when 'backspace' is 0, to avoid backspacing over autoindent.
-EXTERN colnr_T ai_col INIT(= 0);
+EXTERN colnr_T ai_col INIT( = 0);
// This is a character which will end a start-middle-end comment when typed as
// the first character on a new line. It is taken from the last character of
// the "end" comment leader when the COM_AUTO_END flag is given for that
// comment end in 'comments'. It is only valid when did_ai is true.
-EXTERN int end_comment_pending INIT(= NUL);
+EXTERN int end_comment_pending INIT( = NUL);
// This flag is set after a ":syncbind" to let the check_scrollbind() function
// know that it should not attempt to perform scrollbinding due to the scroll
// that was a result of the ":syncbind." (Otherwise, check_scrollbind() will
// undo some of the work done by ":syncbind.") -ralston
-EXTERN bool did_syncbind INIT(= false);
+EXTERN bool did_syncbind INIT( = false);
// This flag is set when a smart indent has been performed. When the next typed
// character is a '{' the inserted tab will be deleted again.
-EXTERN bool did_si INIT(= false);
+EXTERN bool did_si INIT( = false);
// This flag is set after an auto indent. If the next typed character is a '}'
// one indent will be removed.
-EXTERN bool can_si INIT(= false);
+EXTERN bool can_si INIT( = false);
// This flag is set after an "O" command. If the next typed character is a '{'
// one indent will be removed.
-EXTERN bool can_si_back INIT(= false);
+EXTERN bool can_si_back INIT( = false);
-EXTERN int old_indent INIT(= 0); ///< for ^^D command in insert mode
+EXTERN int old_indent INIT( = 0); ///< for ^^D command in insert mode
// w_cursor before formatting text.
-EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 });
+EXTERN pos_T saved_cursor INIT( = { 0, 0, 0 });
// Stuff for insert mode.
EXTERN pos_T Insstart; // This is where the latest
@@ -596,11 +595,11 @@ EXTERN pos_T Insstart; // This is where the latest
EXTERN pos_T Insstart_orig;
// Stuff for MODE_VREPLACE state.
-EXTERN linenr_T orig_line_count INIT(= 0); // Line count when "gR" started
-EXTERN int vr_lines_changed INIT(= 0); // #Lines changed by "gR" so far
+EXTERN linenr_T orig_line_count INIT( = 0); // Line count when "gR" started
+EXTERN int vr_lines_changed INIT( = 0); // #Lines changed by "gR" so far
// increase around internal delete/replace
-EXTERN int inhibit_delete_count INIT(= 0);
+EXTERN int inhibit_delete_count INIT( = 0);
// These flags are set based upon 'fileencoding'.
// The characters are internally stored as UTF-8
@@ -617,7 +616,7 @@ EXTERN int inhibit_delete_count INIT(= 0);
#define DBCS_DEBUG (-1)
/// Encoding used when 'fencs' is set to "default"
-EXTERN char *fenc_default INIT(= NULL);
+EXTERN char *fenc_default INIT( = NULL);
/// "State" is the main state of Vim.
/// There are other variables that modify the state:
@@ -626,69 +625,70 @@ EXTERN char *fenc_default INIT(= NULL);
/// before typing the motion command.
/// motion_force: Last motion_force from do_pending_operator()
/// debug_mode: Debug mode
-EXTERN int State INIT(= MODE_NORMAL);
+EXTERN int State INIT( = MODE_NORMAL);
-EXTERN bool debug_mode INIT(= false);
-EXTERN bool finish_op INIT(= false); // true while an operator is pending
-EXTERN long opcount INIT(= 0); // count for pending operator
-EXTERN int motion_force INIT(= 0); // motion force for pending operator
+EXTERN bool debug_mode INIT( = false);
+EXTERN bool finish_op INIT( = false); // true while an operator is pending
+EXTERN int opcount INIT( = 0); // count for pending operator
+EXTERN int motion_force INIT( = 0); // motion force for pending operator
// Ex Mode (Q) state
-EXTERN bool exmode_active INIT(= false); // true if Ex mode is active
+EXTERN bool exmode_active INIT( = false); // true if Ex mode is active
/// Flag set when normal_check() should return 0 when entering Ex mode.
-EXTERN bool pending_exmode_active INIT(= false);
+EXTERN bool pending_exmode_active INIT( = false);
-EXTERN bool ex_no_reprint INIT(= false); // No need to print after z or p.
+EXTERN bool ex_no_reprint INIT( = false); // No need to print after z or p.
// 'inccommand' command preview state
-EXTERN bool cmdpreview INIT(= false);
+EXTERN bool cmdpreview INIT( = false);
-EXTERN int reg_recording INIT(= 0); // register for recording or zero
-EXTERN int reg_executing INIT(= 0); // register being executed or zero
+EXTERN int reg_recording INIT( = 0); // register for recording or zero
+EXTERN int reg_executing INIT( = 0); // register being executed or zero
// Flag set when peeking a character and found the end of executed register
-EXTERN bool pending_end_reg_executing INIT(= false);
-EXTERN int reg_recorded INIT(= 0); // last recorded register or zero
-
-EXTERN int no_mapping INIT(= false); // currently no mapping allowed
-EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed
-EXTERN int allow_keys INIT(= false); // allow key codes when no_mapping is set
-EXTERN int no_u_sync INIT(= 0); // Don't call u_sync()
-EXTERN int u_sync_once INIT(= 0); // Call u_sync() once when evaluating
- // an expression.
-
-EXTERN bool force_restart_edit INIT(= false); // force restart_edit after
- // ex_normal returns
-EXTERN int restart_edit INIT(= 0); // call edit when next cmd finished
+EXTERN bool pending_end_reg_executing INIT( = false);
+EXTERN int reg_recorded INIT( = 0); // last recorded register or zero
+
+EXTERN int no_mapping INIT( = false); // currently no mapping allowed
+EXTERN int no_zero_mapping INIT( = 0); // mapping zero not allowed
+EXTERN int allow_keys INIT( = false); // allow key codes when no_mapping is set
+EXTERN int no_u_sync INIT( = 0); // Don't call u_sync()
+EXTERN int u_sync_once INIT( = 0); // Call u_sync() once when evaluating
+ // an expression.
+
+EXTERN bool force_restart_edit INIT( = false); // force restart_edit after
+ // ex_normal returns
+EXTERN int restart_edit INIT( = 0); // call edit when next cmd finished
EXTERN int arrow_used; // Normally false, set to true after
// hitting cursor key in insert mode.
// Used by vgetorpeek() to decide when
// to call u_sync()
-EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when
- // restarting edit after CTRL-O
+EXTERN bool ins_at_eol INIT( = false); // put cursor after eol when
+ // restarting edit after CTRL-O
-EXTERN bool no_abbr INIT(= true); // true when no abbreviations loaded
+EXTERN bool no_abbr INIT( = true); // true when no abbreviations loaded
-EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped.
-EXTERN bool ctrl_c_interrupts INIT(= true); // CTRL-C sets got_int
+EXTERN int mapped_ctrl_c INIT( = 0); // Modes where CTRL-C is mapped.
+EXTERN bool ctrl_c_interrupts INIT( = true); // CTRL-C sets got_int
EXTERN cmdmod_T cmdmod; // Ex command modifiers
-EXTERN int msg_silent INIT(= 0); // don't print messages
-EXTERN int emsg_silent INIT(= 0); // don't print error messages
-EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages
-EXTERN bool cmd_silent INIT(= false); // don't echo the command line
+EXTERN int msg_silent INIT( = 0); // don't print messages
+EXTERN int emsg_silent INIT( = 0); // don't print error messages
+EXTERN bool emsg_noredir INIT( = false); // don't redirect error messages
+EXTERN bool cmd_silent INIT( = false); // don't echo the command line
-EXTERN bool in_assert_fails INIT(= false); // assert_fails() active
+EXTERN bool in_assert_fails INIT( = false); // assert_fails() active
// Values for swap_exists_action: what to do when swap file already exists
#define SEA_NONE 0 // don't use dialog
#define SEA_DIALOG 1 // use dialog when possible
#define SEA_QUIT 2 // quit editing the file
#define SEA_RECOVER 3 // recover the file
+#define SEA_READONLY 4 // no dialog, mark buffer as read-only
-EXTERN int swap_exists_action INIT(= SEA_NONE); ///< For dialog when swap file already exists.
-EXTERN bool swap_exists_did_quit INIT(= false); ///< Selected "quit" at the dialog.
+EXTERN int swap_exists_action INIT( = SEA_NONE); ///< For dialog when swap file already exists.
+EXTERN bool swap_exists_did_quit INIT( = false); ///< Selected "quit" at the dialog.
EXTERN char IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc.
EXTERN char NameBuff[MAXPATHL]; ///< Buffer for expanding file names
@@ -702,338 +702,340 @@ EXTERN char os_buf[ ///< Buffer for the os/ layer
];
// When non-zero, postpone redrawing.
-EXTERN int RedrawingDisabled INIT(= 0);
+EXTERN int RedrawingDisabled INIT( = 0);
-EXTERN int readonlymode INIT(= false); // Set to true for "view"
-EXTERN int recoverymode INIT(= false); // Set to true for "-r" option
+EXTERN bool readonlymode INIT( = false); // Set to true for "view"
+EXTERN bool recoverymode INIT( = false); // Set to true for "-r" option
// typeahead buffer
-EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 });
+EXTERN typebuf_T typebuf INIT( = { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 });
/// Flag used to indicate that vgetorpeek() returned a char like Esc when the
/// :normal argument was exhausted.
-EXTERN bool typebuf_was_empty INIT(= false);
+EXTERN bool typebuf_was_empty INIT( = false);
-EXTERN int ex_normal_busy INIT(= 0); // recursiveness of ex_normal()
-EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal()
-EXTERN int ignore_script INIT(= false); // ignore script input
-EXTERN int stop_insert_mode; // for ":stopinsert"
-EXTERN bool KeyTyped; // true if user typed current char
-EXTERN int KeyStuffed; // true if current char from stuffbuf
-EXTERN int maptick INIT(= 0); // tick for each non-mapped char
+EXTERN int ex_normal_busy INIT( = 0); // recursiveness of ex_normal()
+EXTERN int expr_map_lock INIT( = 0); // running expr mapping, prevent use of ex_normal() and text changes
+EXTERN bool ignore_script INIT( = false); // ignore script input
+EXTERN int stop_insert_mode; // for ":stopinsert"
+EXTERN bool KeyTyped; // true if user typed current char
+EXTERN int KeyStuffed; // true if current char from stuffbuf
+EXTERN int maptick INIT( = 0); // tick for each non-mapped char
-EXTERN int must_redraw INIT(= 0); // type of redraw necessary
-EXTERN bool skip_redraw INIT(= false); // skip redraw once
-EXTERN bool do_redraw INIT(= false); // extra redraw once
-EXTERN bool must_redraw_pum INIT(= false); // redraw pum. NB: must_redraw
- // should also be set.
+EXTERN int must_redraw INIT( = 0); // type of redraw necessary
+EXTERN bool skip_redraw INIT( = false); // skip redraw once
+EXTERN bool do_redraw INIT( = false); // extra redraw once
+EXTERN bool must_redraw_pum INIT( = false); // redraw pum. NB: must_redraw
+ // should also be set.
-EXTERN bool need_highlight_changed INIT(= true);
+EXTERN bool need_highlight_changed INIT( = true);
-EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to.
+EXTERN FILE *scriptout INIT( = NULL); ///< Stream to write script to.
// Note that even when handling SIGINT, volatile is not necessary because the
// callback is not called directly from the signal handlers.
-EXTERN bool got_int INIT(= false); // set to true when interrupt signal occurred
-EXTERN bool bangredo INIT(= false); // set to true with ! command
+EXTERN bool got_int INIT( = false); // set to true when interrupt signal occurred
+EXTERN bool bangredo INIT( = false); // set to true with ! command
EXTERN int searchcmdlen; // length of previous search cmd
-EXTERN int reg_do_extmatch INIT(= 0); // Used when compiling regexp:
- // REX_SET to allow \z\(...\),
- // REX_USE to allow \z\1 et al.
+EXTERN int reg_do_extmatch INIT( = 0); // Used when compiling regexp:
+ // REX_SET to allow \z\(...\),
+ // REX_USE to allow \z\1 et al.
// Used by vim_regexec(): strings for \z\1...\z\9
-EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL);
+EXTERN reg_extmatch_T *re_extmatch_in INIT( = NULL);
// Set by vim_regexec() to store \z\(...\) matches
-EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL);
+EXTERN reg_extmatch_T *re_extmatch_out INIT( = NULL);
-EXTERN bool did_outofmem_msg INIT(= false); ///< set after out of memory msg
-EXTERN bool did_swapwrite_msg INIT(= false); ///< set after swap write error msg
-EXTERN int global_busy INIT(= 0); ///< set when :global is executing
-EXTERN bool listcmd_busy INIT(= false); ///< set when :argdo, :windo or :bufdo is executing
-EXTERN bool need_start_insertmode INIT(= false); ///< start insert mode soon
+EXTERN bool did_outofmem_msg INIT( = false); ///< set after out of memory msg
+EXTERN bool did_swapwrite_msg INIT( = false); ///< set after swap write error msg
+EXTERN int global_busy INIT( = 0); ///< set when :global is executing
+EXTERN bool listcmd_busy INIT( = false); ///< set when :argdo, :windo or :bufdo is executing
+EXTERN bool need_start_insertmode INIT( = false); ///< start insert mode soon
#define MODE_MAX_LENGTH 4 // max mode length returned in get_mode(),
// including the terminating NUL
-EXTERN char last_mode[MODE_MAX_LENGTH] INIT(= "n");
-EXTERN char *last_cmdline INIT(= NULL); // last command line (for ":)
-EXTERN char *repeat_cmdline INIT(= NULL); // command line for "."
-EXTERN char *new_last_cmdline INIT(= NULL); // new value for last_cmdline
-EXTERN char *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline
-EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline
-EXTERN char *autocmd_match INIT(= NULL); // name for <amatch> on cmdline
-EXTERN bool did_cursorhold INIT(= false); // set when CursorHold t'gerd
-
-EXTERN int postponed_split INIT(= 0); // for CTRL-W CTRL-] command
-EXTERN int postponed_split_flags INIT(= 0); // args for win_split()
-EXTERN int postponed_split_tab INIT(= 0); // cmdmod.cmod_tab
-EXTERN int g_do_tagpreview INIT(= 0); // for tag preview commands:
- // height of preview window
-EXTERN bool g_tag_at_cursor INIT(= false); // whether the tag command comes
- // from the command line (0) or was
- // invoked as a normal command (1)
-
-EXTERN int replace_offset INIT(= 0); // offset for replace_push()
-
-EXTERN char *escape_chars INIT(= " \t\\\"|"); // need backslash in cmd line
-
-EXTERN int keep_help_flag INIT(= false); // doing :ta from help file
-
-// When a string option is NULL (which only happens in out-of-memory
-// situations), it is set to empty_option, to avoid having to check for NULL
-// everywhere.
-EXTERN char *empty_option INIT(= "");
-
-EXTERN bool redir_off INIT(= false); // no redirection for a moment
-EXTERN FILE *redir_fd INIT(= NULL); // message redirection file
-EXTERN int redir_reg INIT(= 0); // message redirection register
-EXTERN int redir_vname INIT(= 0); // message redirection variable
-EXTERN garray_T *capture_ga INIT(= NULL); // captured output for execute()
-
-EXTERN char_u langmap_mapchar[256]; // mapping for language keys
-
-EXTERN int save_p_ls INIT(= -1); // Save 'laststatus' setting
-EXTERN int save_p_wmh INIT(= -1); // Save 'winminheight' setting
-EXTERN int wild_menu_showing INIT(= 0);
+EXTERN char last_mode[MODE_MAX_LENGTH] INIT( = "n");
+EXTERN char *last_cmdline INIT( = NULL); // last command line (for ":)
+EXTERN char *repeat_cmdline INIT( = NULL); // command line for "."
+EXTERN char *new_last_cmdline INIT( = NULL); // new value for last_cmdline
+EXTERN char *autocmd_fname INIT( = NULL); // fname for <afile> on cmdline
+EXTERN bool autocmd_fname_full INIT( = false); // autocmd_fname is full path
+EXTERN int autocmd_bufnr INIT( = 0); // fnum for <abuf> on cmdline
+EXTERN char *autocmd_match INIT( = NULL); // name for <amatch> on cmdline
+EXTERN bool did_cursorhold INIT( = false); // set when CursorHold t'gerd
+
+EXTERN int postponed_split INIT( = 0); // for CTRL-W CTRL-] command
+EXTERN int postponed_split_flags INIT( = 0); // args for win_split()
+EXTERN int postponed_split_tab INIT( = 0); // cmdmod.cmod_tab
+EXTERN int g_do_tagpreview INIT( = 0); // for tag preview commands:
+ // height of preview window
+EXTERN bool g_tag_at_cursor INIT( = false); // whether the tag command comes
+ // from the command line (0) or was
+ // invoked as a normal command (1)
+
+EXTERN int replace_offset INIT( = 0); // offset for replace_push()
+
+EXTERN char *escape_chars INIT( = " \t\\\"|"); // need backslash in cmd line
+
+EXTERN bool keep_help_flag INIT( = false); // doing :ta from help file
+
+// When a string option is NULL (which only happens in out-of-memory situations), it is set to
+// empty_string_option, to avoid having to check for NULL everywhere.
+//
+// TODO(famiu): Remove this when refcounted strings are used for string options.
+EXTERN char *empty_string_option INIT( = "");
+
+EXTERN bool redir_off INIT( = false); // no redirection for a moment
+EXTERN FILE *redir_fd INIT( = NULL); // message redirection file
+EXTERN int redir_reg INIT( = 0); // message redirection register
+EXTERN int redir_vname INIT( = 0); // message redirection variable
+EXTERN garray_T *capture_ga INIT( = NULL); // captured output for execute()
+
+EXTERN uint8_t langmap_mapchar[256]; // mapping for language keys
+
+EXTERN int save_p_ls INIT( = -1); // Save 'laststatus' setting
+EXTERN int save_p_wmh INIT( = -1); // Save 'winminheight' setting
+EXTERN int wild_menu_showing INIT( = 0);
enum {
WM_SHOWN = 1, ///< wildmenu showing
WM_SCROLLED = 2, ///< wildmenu showing with scroll
WM_LIST = 3, ///< cmdline CTRL-D
};
-// Some file names are stored in pathdef.c, which is generated from the
-// Makefile to make their value depend on the Makefile.
-#ifdef HAVE_PATHDEF
-extern char *default_vim_dir;
-extern char *default_vimruntime_dir;
-extern char *default_lib_dir;
-extern char *compiled_user;
-extern char *compiled_sys;
-#endif
-
// When a window has a local directory, the absolute path of the global
// current directory is stored here (in allocated memory). If the current
// directory is not a local directory, globaldir is NULL.
-EXTERN char *globaldir INIT(= NULL);
+EXTERN char *globaldir INIT( = NULL);
-EXTERN char *last_chdir_reason INIT(= NULL);
+EXTERN char *last_chdir_reason INIT( = NULL);
// Whether 'keymodel' contains "stopsel" and "startsel".
-EXTERN bool km_stopsel INIT(= false);
-EXTERN bool km_startsel INIT(= false);
+EXTERN bool km_stopsel INIT( = false);
+EXTERN bool km_startsel INIT( = false);
-EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0
-EXTERN int cmdwin_result INIT(= 0); ///< result of cmdline window or 0
-EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level
+EXTERN int cmdwin_type INIT( = 0); ///< type of cmdline window or 0
+EXTERN int cmdwin_result INIT( = 0); ///< result of cmdline window or 0
+EXTERN int cmdwin_level INIT( = 0); ///< cmdline recursion level
+EXTERN win_T *cmdwin_old_curwin INIT( = NULL); ///< curwin before opening cmdline window or NULL
-EXTERN char no_lines_msg[] INIT(= N_("--No lines in buffer--"));
+EXTERN char no_lines_msg[] INIT( = N_("--No lines in buffer--"));
// When ":global" is used to number of substitutions and changed lines is
// accumulated until it's finished.
// Also used for ":spellrepall".
-EXTERN long sub_nsubs; // total number of substitutions
+EXTERN int sub_nsubs; // total number of substitutions
EXTERN linenr_T sub_nlines; // total number of lines changed
// table to store parsed 'wildmode'
-EXTERN char_u wim_flags[4];
+EXTERN uint8_t wim_flags[4];
// whether titlestring and iconstring contains statusline syntax
#define STL_IN_ICON 1
#define STL_IN_TITLE 2
-EXTERN int stl_syntax INIT(= 0);
+EXTERN int stl_syntax INIT( = 0);
// don't use 'hlsearch' temporarily
-EXTERN bool no_hlsearch INIT(= false);
+EXTERN bool no_hlsearch INIT( = false);
-EXTERN bool typebuf_was_filled INIT(= false); // received text from client
- // or from feedkeys()
+EXTERN bool typebuf_was_filled INIT( = false); // received text from client
+ // or from feedkeys()
#ifdef BACKSLASH_IN_FILENAME
-EXTERN char psepc INIT(= '\\'); // normal path separator character
-EXTERN char psepcN INIT(= '/'); // abnormal path separator character
-EXTERN char pseps[2] INIT(= { '\\', 0 }); // normal path separator string
+EXTERN char psepc INIT( = '\\'); // normal path separator character
+EXTERN char psepcN INIT( = '/'); // abnormal path separator character
+EXTERN char pseps[2] INIT( = { '\\', 0 }); // normal path separator string
#endif
// Set to kTrue when an operator is being executed with virtual editing
// kNone when no operator is being executed, kFalse otherwise.
-EXTERN TriState virtual_op INIT(= kNone);
+EXTERN TriState virtual_op INIT( = kNone);
// Display tick, incremented for each call to update_screen()
-EXTERN disptick_T display_tick INIT(= 0);
+EXTERN disptick_T display_tick INIT( = 0);
// Line in which spell checking wasn't highlighted because it touched the
// cursor position in Insert mode.
-EXTERN linenr_T spell_redraw_lnum INIT(= 0);
+EXTERN linenr_T spell_redraw_lnum INIT( = 0);
// uncrustify:off
// The error messages that can be shared are included here.
// Excluded are errors that are only used once and debugging messages.
-EXTERN char e_abort[] INIT(= N_("E470: Command aborted"));
-EXTERN char e_afterinit[] INIT(= N_("E905: Cannot set this option after startup"));
-EXTERN char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
-EXTERN char e_argreq[] INIT(= N_("E471: Argument required"));
-EXTERN char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
-EXTERN char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits"));
-EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search"));
-EXTERN char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
-EXTERN char e_endif[] INIT(= N_("E171: Missing :endif"));
-EXTERN char e_endtry[] INIT(= N_("E600: Missing :endtry"));
-EXTERN char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
-EXTERN char e_endfor[] INIT(= N_("E170: Missing :endfor"));
-EXTERN char e_while[] INIT(= N_("E588: :endwhile without :while"));
-EXTERN char e_for[] INIT(= N_("E588: :endfor without :for"));
-EXTERN char e_exists[] INIT(= N_("E13: File exists (add ! to override)"));
-EXTERN char e_failed[] INIT(= N_("E472: Command failed"));
-EXTERN char e_internal[] INIT(= N_("E473: Internal error"));
-EXTERN char e_intern2[] INIT(= N_("E685: Internal error: %s"));
-EXTERN char e_interr[] INIT(= N_("Interrupted"));
-EXTERN char e_invarg[] INIT(= N_("E474: Invalid argument"));
-EXTERN char e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
-EXTERN char e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
-EXTERN char e_invargNval[] INIT(= N_("E475: Invalid value for argument %s: %s"));
-EXTERN char e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
-EXTERN char e_invexpr2[] INIT(= N_("E15: Invalid expression: %s"));
-EXTERN char e_invrange[] INIT(= N_("E16: Invalid range"));
-EXTERN char e_invcmd[] INIT(= N_("E476: Invalid command"));
-EXTERN char e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
-EXTERN char e_no_spell[] INIT(= N_("E756: Spell checking is not possible"));
-EXTERN char e_invchan[] INIT(= N_("E900: Invalid channel id"));
-EXTERN char e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job"));
-EXTERN char e_jobtblfull[] INIT(= N_("E901: Job table is full"));
-EXTERN char e_jobspawn[] INIT(= N_("E903: Process failed to start: %s: \"%s\""));
-EXTERN char e_channotpty[] INIT(= N_("E904: channel is not a pty"));
-EXTERN char e_stdiochan2[] INIT(= N_("E905: Couldn't open stdio channel: %s"));
-EXTERN char e_invstream[] INIT(= N_("E906: invalid stream for channel"));
-EXTERN char e_invstreamrpc[] INIT(= N_("E906: invalid stream for rpc channel, use 'rpc'"));
-EXTERN char e_streamkey[] INIT(= N_("E5210: dict key '%s' already set for buffered stream in channel %" PRIu64));
-EXTERN char e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
-EXTERN char e_fsync[] INIT(= N_("E667: Fsync failed: %s"));
-EXTERN char e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
-EXTERN char e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
-EXTERN char e_marknotset[] INIT(= N_("E20: Mark not set"));
-EXTERN char e_modifiable[] INIT(= N_("E21: Cannot make changes, 'modifiable' is off"));
-EXTERN char e_nesting[] INIT(= N_("E22: Scripts nested too deep"));
-EXTERN char e_noalt[] INIT(= N_("E23: No alternate file"));
-EXTERN char e_noabbr[] INIT(= N_("E24: No such abbreviation"));
-EXTERN char e_nobang[] INIT(= N_("E477: No ! allowed"));
-EXTERN char e_nogroup[] INIT(= N_("E28: No such highlight group name: %s"));
-EXTERN char e_noinstext[] INIT(= N_("E29: No inserted text yet"));
-EXTERN char e_nolastcmd[] INIT(= N_("E30: No previous command line"));
-EXTERN char e_nomap[] INIT(= N_("E31: No such mapping"));
-EXTERN char e_nomatch[] INIT(= N_("E479: No match"));
-EXTERN char e_nomatch2[] INIT(= N_("E480: No match: %s"));
-EXTERN char e_noname[] INIT(= N_("E32: No file name"));
-EXTERN char e_nopresub[] INIT(= N_("E33: No previous substitute regular expression"));
-EXTERN char e_noprev[] INIT(= N_("E34: No previous command"));
-EXTERN char e_noprevre[] INIT(= N_("E35: No previous regular expression"));
-EXTERN char e_norange[] INIT(= N_("E481: No range allowed"));
-EXTERN char e_noroom[] INIT(= N_("E36: Not enough room"));
-EXTERN char e_notmp[] INIT(= N_("E483: Can't get temp file name"));
-EXTERN char e_notopen[] INIT(= N_("E484: Can't open file %s"));
-EXTERN char e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
-EXTERN char e_notread[] INIT(= N_("E485: Can't read file %s"));
-EXTERN char e_null[] INIT(= N_("E38: Null argument"));
-EXTERN char e_number_exp[] INIT(= N_("E39: Number expected"));
-EXTERN char e_openerrf[] INIT(= N_("E40: Can't open errorfile %s"));
-EXTERN char e_outofmem[] INIT(= N_("E41: Out of memory!"));
-EXTERN char e_patnotf[] INIT(= N_("Pattern not found"));
-EXTERN char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s"));
-EXTERN char e_positive[] INIT(= N_("E487: Argument must be positive"));
-EXTERN char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory"));
-
-EXTERN char e_no_errors[] INIT(= N_("E42: No Errors"));
-EXTERN char e_loclist[] INIT(= N_("E776: No location list"));
-EXTERN char e_re_damg[] INIT(= N_("E43: Damaged match string"));
-EXTERN char e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
-EXTERN char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)"));
-EXTERN char e_letwrong[] INIT(= N_("E734: Wrong variable type for %s="));
-EXTERN char e_illvar[] INIT(= N_("E461: Illegal variable name: %s"));
-EXTERN char e_cannot_mod[] INIT(= N_("E995: Cannot modify existing variable"));
-EXTERN char e_readonlyvar[] INIT(= N_("E46: Cannot change read-only variable \"%.*s\""));
-EXTERN char e_stringreq[] INIT(= N_("E928: String required"));
-EXTERN char e_dictreq[] INIT(= N_("E715: Dictionary required"));
-EXTERN char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
-EXTERN char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
-EXTERN char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
-EXTERN char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
-EXTERN char e_listreq[] INIT(= N_("E714: List required"));
-EXTERN char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
-EXTERN char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
-EXTERN char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
-EXTERN char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
-EXTERN char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
-EXTERN char e_secure[] INIT(= N_("E523: Not allowed here"));
-EXTERN char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window"));
-EXTERN char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported"));
-EXTERN char e_scroll[] INIT(= N_("E49: Invalid scroll size"));
-EXTERN char e_shellempty[] INIT(= N_("E91: 'shell' option is empty"));
-EXTERN char e_signdata[] INIT(= N_("E255: Couldn't read in sign data!"));
-EXTERN char e_swapclose[] INIT(= N_("E72: Close error on swap file"));
-EXTERN char e_tagstack[] INIT(= N_("E73: tag stack empty"));
-EXTERN char e_toocompl[] INIT(= N_("E74: Command too complex"));
-EXTERN char e_longname[] INIT(= N_("E75: Name too long"));
-EXTERN char e_toomsbra[] INIT(= N_("E76: Too many ["));
-EXTERN char e_toomany[] INIT(= N_("E77: Too many file names"));
-EXTERN char e_trailing[] INIT(= N_("E488: Trailing characters"));
-EXTERN char e_trailing_arg[] INIT(= N_("E488: Trailing characters: %s"));
-EXTERN char e_umark[] INIT(= N_("E78: Unknown mark"));
-EXTERN char e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards"));
-EXTERN char e_winheight[] INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'"));
-EXTERN char e_winwidth[] INIT(= N_("E592: 'winwidth' cannot be smaller than 'winminwidth'"));
-EXTERN char e_write[] INIT(= N_("E80: Error while writing"));
-EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required"));
-EXTERN char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
-EXTERN char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
-EXTERN char e_maxmempat[] INIT(= N_("E363: pattern uses more memory than 'maxmempattern'"));
-EXTERN char e_emptybuf[] INIT(= N_("E749: empty buffer"));
-EXTERN char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
-
-EXTERN char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
-EXTERN char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
-EXTERN char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
-EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
-EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
-EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
-EXTERN char e_menuothermode[] INIT(= N_("E328: Menu only exists in another mode"));
-EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
-EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
-EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
-EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
-EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String"));
-EXTERN char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
-
-EXTERN char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>"));
-EXTERN char e_cmdmap_repeated[]
-INIT(= N_("E5521: <Cmd> mapping must end with <CR> before second <Cmd>"));
-
-EXTERN char e_api_error[] INIT(= N_("E5555: API call: %s"));
-
-EXTERN char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback"));
-
-EXTERN char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
-EXTERN char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
-
-EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
-
-EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
-
-EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
-
-EXTERN char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name"));
-
-EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
-
-EXTERN char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
-
-EXTERN char e_undobang_cannot_redo_or_move_branch[]
+EXTERN const char e_abort[] INIT(= N_("E470: Command aborted"));
+EXTERN const char e_afterinit[] INIT(= N_("E905: Cannot set this option after startup"));
+EXTERN const char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
+EXTERN const char e_argreq[] INIT(= N_("E471: Argument required"));
+EXTERN const char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
+EXTERN const char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits"));
+EXTERN const char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search"));
+EXTERN const char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
+EXTERN const char e_endif[] INIT(= N_("E171: Missing :endif"));
+EXTERN const char e_endtry[] INIT(= N_("E600: Missing :endtry"));
+EXTERN const char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
+EXTERN const char e_endfor[] INIT(= N_("E170: Missing :endfor"));
+EXTERN const char e_while[] INIT(= N_("E588: :endwhile without :while"));
+EXTERN const char e_for[] INIT(= N_("E588: :endfor without :for"));
+EXTERN const char e_exists[] INIT(= N_("E13: File exists (add ! to override)"));
+EXTERN const char e_failed[] INIT(= N_("E472: Command failed"));
+EXTERN const char e_internal[] INIT(= N_("E473: Internal error"));
+EXTERN const char e_intern2[] INIT(= N_("E685: Internal error: %s"));
+EXTERN const char e_interr[] INIT(= N_("Interrupted"));
+EXTERN const char e_invarg[] INIT(= N_("E474: Invalid argument"));
+EXTERN const char e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
+EXTERN const char e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
+EXTERN const char e_invargNval[] INIT(= N_("E475: Invalid value for argument %s: %s"));
+EXTERN const char e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
+EXTERN const char e_invexpr2[] INIT(= N_("E15: Invalid expression: \"%s\""));
+EXTERN const char e_invrange[] INIT(= N_("E16: Invalid range"));
+EXTERN const char e_invcmd[] INIT(= N_("E476: Invalid command"));
+EXTERN const char e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
+EXTERN const char e_no_spell[] INIT(= N_("E756: Spell checking is not possible"));
+EXTERN const char e_invchan[] INIT(= N_("E900: Invalid channel id"));
+EXTERN const char e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job"));
+EXTERN const char e_jobtblfull[] INIT(= N_("E901: Job table is full"));
+EXTERN const char e_jobspawn[] INIT(= N_("E903: Process failed to start: %s: \"%s\""));
+EXTERN const char e_channotpty[] INIT(= N_("E904: channel is not a pty"));
+EXTERN const char e_stdiochan2[] INIT(= N_("E905: Couldn't open stdio channel: %s"));
+EXTERN const char e_invstream[] INIT(= N_("E906: invalid stream for channel"));
+EXTERN const char e_invstreamrpc[] INIT(= N_("E906: invalid stream for rpc channel, use 'rpc'"));
+EXTERN const char e_streamkey[] INIT(= N_("E5210: dict key '%s' already set for buffered stream in channel %" PRIu64));
+EXTERN const char e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
+EXTERN const char e_fsync[] INIT(= N_("E667: Fsync failed: %s"));
+EXTERN const char e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
+EXTERN const char e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
+EXTERN const char e_marknotset[] INIT(= N_("E20: Mark not set"));
+EXTERN const char e_modifiable[] INIT(= N_("E21: Cannot make changes, 'modifiable' is off"));
+EXTERN const char e_nesting[] INIT(= N_("E22: Scripts nested too deep"));
+EXTERN const char e_noalt[] INIT(= N_("E23: No alternate file"));
+EXTERN const char e_noabbr[] INIT(= N_("E24: No such abbreviation"));
+EXTERN const char e_nobang[] INIT(= N_("E477: No ! allowed"));
+EXTERN const char e_nogroup[] INIT(= N_("E28: No such highlight group name: %s"));
+EXTERN const char e_noinstext[] INIT(= N_("E29: No inserted text yet"));
+EXTERN const char e_nolastcmd[] INIT(= N_("E30: No previous command line"));
+EXTERN const char e_nomap[] INIT(= N_("E31: No such mapping"));
+EXTERN const char e_nomatch[] INIT(= N_("E479: No match"));
+EXTERN const char e_nomatch2[] INIT(= N_("E480: No match: %s"));
+EXTERN const char e_noname[] INIT(= N_("E32: No file name"));
+EXTERN const char e_nopresub[] INIT(= N_("E33: No previous substitute regular expression"));
+EXTERN const char e_noprev[] INIT(= N_("E34: No previous command"));
+EXTERN const char e_noprevre[] INIT(= N_("E35: No previous regular expression"));
+EXTERN const char e_norange[] INIT(= N_("E481: No range allowed"));
+EXTERN const char e_noroom[] INIT(= N_("E36: Not enough room"));
+EXTERN const char e_notmp[] INIT(= N_("E483: Can't get temp file name"));
+EXTERN const char e_notopen[] INIT(= N_("E484: Can't open file %s"));
+EXTERN const char e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
+EXTERN const char e_notread[] INIT(= N_("E485: Can't read file %s"));
+EXTERN const char e_null[] INIT(= N_("E38: Null argument"));
+EXTERN const char e_number_exp[] INIT(= N_("E39: Number expected"));
+EXTERN const char e_openerrf[] INIT(= N_("E40: Can't open errorfile %s"));
+EXTERN const char e_outofmem[] INIT(= N_("E41: Out of memory!"));
+EXTERN const char e_patnotf[] INIT(= N_("Pattern not found"));
+EXTERN const char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s"));
+EXTERN const char e_positive[] INIT(= N_("E487: Argument must be positive"));
+EXTERN const char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory"));
+
+EXTERN const char e_no_errors[] INIT(= N_("E42: No Errors"));
+EXTERN const char e_loclist[] INIT(= N_("E776: No location list"));
+EXTERN const char e_re_damg[] INIT(= N_("E43: Damaged match string"));
+EXTERN const char e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
+EXTERN const char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)"));
+EXTERN const char e_letwrong[] INIT(= N_("E734: Wrong variable type for %s="));
+EXTERN const char e_illvar[] INIT(= N_("E461: Illegal variable name: %s"));
+EXTERN const char e_cannot_mod[] INIT(= N_("E995: Cannot modify existing variable"));
+EXTERN const char e_readonlyvar[] INIT(= N_("E46: Cannot change read-only variable \"%.*s\""));
+EXTERN const char e_stringreq[] INIT(= N_("E928: String required"));
+EXTERN const char e_dictreq[] INIT(= N_("E715: Dictionary required"));
+EXTERN const char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
+EXTERN const char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
+EXTERN const char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
+EXTERN const char e_toofewarg[] INIT(= N_("E119: Not enough arguments for function: %s"));
+EXTERN const char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
+EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
+EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
+EXTERN const char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
+EXTERN const char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
+EXTERN const char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
+EXTERN const char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
+EXTERN const char e_secure[] INIT(= N_("E523: Not allowed here"));
+EXTERN const char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window"));
+EXTERN const char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported"));
+EXTERN const char e_scroll[] INIT(= N_("E49: Invalid scroll size"));
+EXTERN const char e_shellempty[] INIT(= N_("E91: 'shell' option is empty"));
+EXTERN const char e_signdata[] INIT(= N_("E255: Couldn't read in sign data!"));
+EXTERN const char e_swapclose[] INIT(= N_("E72: Close error on swap file"));
+EXTERN const char e_toocompl[] INIT(= N_("E74: Command too complex"));
+EXTERN const char e_longname[] INIT(= N_("E75: Name too long"));
+EXTERN const char e_toomsbra[] INIT(= N_("E76: Too many ["));
+EXTERN const char e_toomany[] INIT(= N_("E77: Too many file names"));
+EXTERN const char e_trailing[] INIT(= N_("E488: Trailing characters"));
+EXTERN const char e_trailing_arg[] INIT(= N_("E488: Trailing characters: %s"));
+EXTERN const char e_umark[] INIT(= N_("E78: Unknown mark"));
+EXTERN const char e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards"));
+EXTERN const char e_winheight[] INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'"));
+EXTERN const char e_winwidth[] INIT(= N_("E592: 'winwidth' cannot be smaller than 'winminwidth'"));
+EXTERN const char e_write[] INIT(= N_("E80: Error while writing"));
+EXTERN const char e_zerocount[] INIT(= N_("E939: Positive count required"));
+EXTERN const char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
+EXTERN const char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
+EXTERN const char e_empty_buffer[] INIT(= N_("E749: Empty buffer"));
+EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
+
+EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function"));
+
+EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
+EXTERN const char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
+EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
+EXTERN const char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
+EXTERN const char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
+EXTERN const char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
+EXTERN const char e_menu_only_exists_in_another_mode[]
+INIT(= N_("E328: Menu only exists in another mode"));
+EXTERN const char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
+EXTERN const char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
+EXTERN const char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
+EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
+EXTERN const char e_using_float_as_string[] INIT(= N_("E806: Using a Float as a String"));
+EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
+EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d"));
+EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s"));
+
+EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s"));
+
+EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback"));
+
+EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
+EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
+
+EXTERN const char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
+
+EXTERN const char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
+
+EXTERN const char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
+
+EXTERN const char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name"));
+
+EXTERN const char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
+
+EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
+
+EXTERN const char e_stray_closing_curly_str[]
+INIT(= N_("E1278: Stray '}' without a matching '{': %s"));
+EXTERN const char e_missing_close_curly_str[]
+INIT(= N_("E1279: Missing '}': %s"));
+
+EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s"));
+
+EXTERN const char e_undobang_cannot_redo_or_move_branch[]
INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));
-EXTERN char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
+EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
+
+EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s"));
-EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
-EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
+EXTERN const char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
+EXTERN const char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
-EXTERN char line_msg[] INIT(= N_(" line "));
+EXTERN const char line_msg[] INIT(= N_(" line "));
EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing
@@ -1082,17 +1084,15 @@ typedef enum {
} CdCause;
// Only filled for Win32.
-EXTERN char windowsVersion[20] INIT(= { 0 });
+EXTERN char windowsVersion[20] INIT( = { 0 });
/// While executing a regexp and set to OPTION_MAGIC_ON or OPTION_MAGIC_OFF this
/// overrules p_magic. Otherwise set to OPTION_MAGIC_NOT_SET.
-EXTERN optmagic_T magic_overruled INIT(= OPTION_MAGIC_NOT_SET);
+EXTERN optmagic_T magic_overruled INIT( = OPTION_MAGIC_NOT_SET);
/// Skip win_fix_cursor() call for 'splitkeep' when cmdwin is closed.
-EXTERN bool skip_win_fix_cursor INIT(= false);
+EXTERN bool skip_win_fix_cursor INIT( = false);
/// Skip win_fix_scroll() call for 'splitkeep' when closing tab page.
-EXTERN bool skip_win_fix_scroll INIT(= false);
+EXTERN bool skip_win_fix_scroll INIT( = false);
/// Skip update_topline() call while executing win_fix_scroll().
-EXTERN bool skip_update_topline INIT(= false);
-
-#endif // NVIM_GLOBALS_H
+EXTERN bool skip_update_topline INIT( = false);
diff --git a/src/nvim/grid.c b/src/nvim/grid.c
index 46f8a59710..2ef89b778e 100644
--- a/src/nvim/grid.c
+++ b/src/nvim/grid.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Most of the routines in this file perform screen (grid) manipulations. The
// given operation is performed physically on the screen. The corresponding
// change is also made to the internal screen image. In this way, the editor
@@ -13,19 +10,25 @@
#include <assert.h>
#include <limits.h>
+#include <stdint.h>
#include <stdlib.h>
+#include <string.h>
+#include "nvim/api/private/defs.h"
#include "nvim/arabic.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
+#include "nvim/decoration.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/log.h"
+#include "nvim/map_defs.h"
+#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/types.h"
+#include "nvim/option_vars.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "grid.c.generated.h"
@@ -36,6 +39,15 @@
// Per-cell attributes
static size_t linebuf_size = 0;
+// Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string.
+// Then it instead stores an index into glyph_cache.keys[] which is a flat char array.
+// The hash part is used by schar_from_buf() to quickly lookup glyphs which already
+// has been interned. schar_get() should used to convert a schar_T value
+// back to a string buffer.
+//
+// The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL).
+static Set(glyph) glyph_cache = SET_INIT;
+
/// Determine if dedicated window grid should be used or the default_grid
///
/// If UI did not request multigrid support, draw all windows on the
@@ -55,320 +67,469 @@ void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off)
}
}
-/// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell.
-int schar_from_cc(char *p, int c, int u8cc[MAX_MCO])
+schar_T schar_from_str(char *str)
{
- int len = utf_char2bytes(c, p);
- for (int i = 0; i < MAX_MCO; i++) {
- if (u8cc[i] == 0) {
- break;
- }
- len += utf_char2bytes(u8cc[i], p + len);
+ if (str == NULL) {
+ return 0;
}
- p[len] = 0;
- return len;
+ return schar_from_buf(str, strlen(str));
}
-/// clear a line in the grid starting at "off" until "width" characters
-/// are cleared.
-void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
+/// @param buf need not be NUL terminated, but may not contain embedded NULs.
+///
+/// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte)
+schar_T schar_from_buf(const char *buf, size_t len)
{
- for (int col = 0; col < width; col++) {
- schar_from_ascii(grid->chars[off + (size_t)col], ' ');
+ assert(len < MAX_SCHAR_SIZE);
+ if (len <= 4) {
+ schar_T sc = 0;
+ memcpy((char *)&sc, buf, len);
+ return sc;
+ } else {
+ String str = { .data = (char *)buf, .size = len };
+
+ MHPutStatus status;
+ uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status);
+ assert(idx < 0xFFFFFF);
+#ifdef ORDER_BIG_ENDIAN
+ return idx + ((uint32_t)0xFF << 24);
+#else
+ return 0xFF + (idx << 8);
+#endif
}
- int fill = valid ? 0 : -1;
- (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
}
-void grid_invalidate(ScreenGrid *grid)
+/// Check if cache is full, and if it is, clear it.
+///
+/// This should normally only be called in update_screen()
+///
+/// @return true if cache was clered, and all your screen buffers now are hosed
+/// and you need to use UPD_CLEAR
+bool schar_cache_clear_if_full(void)
{
- (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols);
+ // note: critical max is really (1<<24)-1. This gives us some marginal
+ // until next time update_screen() is called
+ if (glyph_cache.h.n_keys > (1<<21)) {
+ schar_cache_clear();
+ return true;
+ }
+ return false;
}
-bool grid_invalid_row(ScreenGrid *grid, int row)
+void schar_cache_clear(void)
{
- return grid->attrs[grid->line_offset[row]] < 0;
+ decor_check_invalid_glyphs();
+ set_clear(glyph, &glyph_cache);
}
-static int line_off2cells(schar_T *line, size_t off, size_t max_off)
+bool schar_high(schar_T sc)
{
- return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1;
+#ifdef ORDER_BIG_ENDIAN
+ return ((sc & 0xFF000000) == 0xFF000000);
+#else
+ return ((sc & 0xFF) == 0xFF);
+#endif
}
-/// Return number of display cells for char at grid->chars[off].
-/// We make sure that the offset used is less than "max_off".
-static int grid_off2cells(ScreenGrid *grid, size_t off, size_t max_off)
+#ifdef ORDER_BIG_ENDIAN
+# define schar_idx(sc) (sc & (0x00FFFFFF))
+#else
+# define schar_idx(sc) (sc >> 8)
+#endif
+
+void schar_get(char *buf_out, schar_T sc)
{
- return line_off2cells(grid->chars, off, max_off);
+ if (schar_high(sc)) {
+ uint32_t idx = schar_idx(sc);
+ assert(idx < glyph_cache.h.n_keys);
+ xstrlcpy(buf_out, &glyph_cache.keys[idx], 32);
+ } else {
+ memcpy(buf_out, (char *)&sc, 4);
+ buf_out[4] = NUL;
+ }
}
-/// Return true if the character at "row"/"col" on the screen is the left side
-/// of a double-width character.
-///
-/// Caller must make sure "row" and "col" are not invalid!
-bool grid_lefthalve(ScreenGrid *grid, int row, int col)
+/// gets first raw UTF-8 byte of an schar
+static char schar_get_first_byte(schar_T sc)
{
- grid_adjust(&grid, &row, &col);
+ assert(!(schar_high(sc) && schar_idx(sc) >= glyph_cache.h.n_keys));
+ return schar_high(sc) ? glyph_cache.keys[schar_idx(sc)] : *(char *)&sc;
+}
- return grid_off2cells(grid, grid->line_offset[row] + (size_t)col,
- grid->line_offset[row] + (size_t)grid->cols) > 1;
+int schar_get_first_codepoint(schar_T sc)
+{
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, sc);
+ return utf_ptr2char(sc_buf);
}
-/// Correct a position on the screen, if it's the right half of a double-wide
-/// char move it to the left half. Returns the corrected column.
-int grid_fix_col(ScreenGrid *grid, int col, int row)
+/// @return ascii char or NUL if not ascii
+char schar_get_ascii(schar_T sc)
{
- int coloff = 0;
- grid_adjust(&grid, &row, &coloff);
+#ifdef ORDER_BIG_ENDIAN
+ return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL;
+#else
+ return (sc < 0x80) ? (char)sc : NUL;
+#endif
+}
- col += coloff;
- if (grid->chars != NULL && col > 0
- && grid->chars[grid->line_offset[row] + (size_t)col][0] == 0) {
- return col - 1 - coloff;
- }
- return col - coloff;
+static bool schar_in_arabic_block(schar_T sc)
+{
+ char first_byte = schar_get_first_byte(sc);
+ return ((uint8_t)first_byte & 0xFE) == 0xD8;
}
-/// output a single character directly to the grid
-void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr)
+/// Get the first two codepoints of an schar, or NUL when not available
+static void schar_get_first_two_codepoints(schar_T sc, int *c0, int *c1)
{
- char buf[MB_MAXBYTES + 1];
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, sc);
- buf[utf_char2bytes(c, buf)] = NUL;
- grid_puts(grid, buf, row, col, attr);
+ *c0 = utf_ptr2char(sc_buf);
+ int len = utf_ptr2len(sc_buf);
+ if (*c0 == NUL) {
+ *c1 = NUL;
+ } else {
+ *c1 = utf_ptr2char(sc_buf + len);
+ }
}
-/// get a single character directly from grid.chars into "bytes[]".
-/// Also return its attribute in *attrp;
-void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp)
+void line_do_arabic_shape(schar_T *buf, int cols)
{
- grid_adjust(&grid, &row, &col);
+ int i = 0;
- // safety check
- if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) {
+ for (i = 0; i < cols; i++) {
+ // quickly skip over non-arabic text
+ if (schar_in_arabic_block(buf[i])) {
+ break;
+ }
+ }
+
+ if (i == cols) {
return;
}
- size_t off = grid->line_offset[row] + (size_t)col;
- *attrp = grid->attrs[off];
- schar_copy(bytes, grid->chars[off]);
+ int c0prev = 0;
+ int c0, c1;
+ schar_get_first_two_codepoints(buf[i], &c0, &c1);
+
+ for (; i < cols; i++) {
+ int c0next, c1next;
+ schar_get_first_two_codepoints(i + 1 < cols ? buf[i + 1] : 0, &c0next, &c1next);
+
+ if (!ARABIC_CHAR(c0)) {
+ goto next;
+ }
+
+ int c1new = c1;
+ int c0new = arabic_shape(c0, &c1new, c0next, c1next, c0prev);
+
+ if (c0new == c0 && c1new == c1) {
+ goto next; // unchanged
+ }
+
+ char scbuf[MAX_SCHAR_SIZE];
+ schar_get(scbuf, buf[i]);
+
+ char scbuf_new[MAX_SCHAR_SIZE];
+ size_t len = (size_t)utf_char2bytes(c0new, scbuf_new);
+ if (c1new) {
+ len += (size_t)utf_char2bytes(c1new, scbuf_new + len);
+ }
+
+ int off = utf_char2len(c0) + (c1 ? utf_char2len(c1) : 0);
+ size_t rest = strlen(scbuf + off);
+ if (rest + len + 1 > MAX_SCHAR_SIZE) {
+ // Too bigly, discard one code-point.
+ // This should be enough as c0 cannot grow more than from 2 to 4 bytes
+ // (base arabic to extended arabic)
+ rest -= (size_t)utf_cp_head_off(scbuf + off, scbuf + off + rest - 1) + 1;
+ }
+ memcpy(scbuf_new + len, scbuf + off, rest);
+ buf[i] = schar_from_buf(scbuf_new, len + rest);
+
+next:
+ c0prev = c0;
+ c0 = c0next;
+ c1 = c1next;
+ }
}
-/// put string '*text' on the window grid at position 'row' and 'col', with
-/// attributes 'attr', and update chars[] and attrs[].
-/// Note: only outputs within one row, message is truncated at grid boundary!
-/// Note: if grid, row and/or col is invalid, nothing is done.
-void grid_puts(ScreenGrid *grid, char *text, int row, int col, int attr)
+/// clear a line in the grid starting at "off" until "width" characters
+/// are cleared.
+void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
{
- grid_puts_len(grid, text, -1, row, col, attr);
+ for (int col = 0; col < width; col++) {
+ grid->chars[off + (size_t)col] = schar_from_ascii(' ');
+ }
+ int fill = valid ? 0 : -1;
+ (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
+ (void)memset(grid->vcols + off, -1, (size_t)width * sizeof(colnr_T));
}
-static ScreenGrid *put_dirty_grid = NULL;
-static int put_dirty_row = -1;
-static int put_dirty_first = INT_MAX;
-static int put_dirty_last = 0;
+void grid_invalidate(ScreenGrid *grid)
+{
+ (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols);
+}
-/// Start a group of grid_puts_len calls that builds a single grid line.
-///
-/// Must be matched with a grid_puts_line_flush call before moving to
-/// another line.
-void grid_puts_line_start(ScreenGrid *grid, int row)
+static bool grid_invalid_row(ScreenGrid *grid, int row)
{
- int col = 0; // unused
- grid_adjust(&grid, &row, &col);
- assert(put_dirty_row == -1);
- put_dirty_row = row;
- put_dirty_grid = grid;
+ return grid->attrs[grid->line_offset[row]] < 0;
}
-void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr)
+/// Get a single character directly from grid.chars
+///
+/// @param[out] attrp set to the character's attribute (optional)
+schar_T grid_getchar(ScreenGrid *grid, int row, int col, int *attrp)
{
- assert(put_dirty_row == row);
- size_t off = grid->line_offset[row] + (size_t)col;
- if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar) || rdb_flags & RDB_NODELTA) {
- schar_copy(grid->chars[off], schar);
- grid->attrs[off] = attr;
+ grid_adjust(&grid, &row, &col);
+
+ // safety check
+ if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) {
+ return NUL;
+ }
- put_dirty_first = MIN(put_dirty_first, col);
- // TODO(bfredl): Y U NO DOUBLEWIDTH?
- put_dirty_last = MAX(put_dirty_last, col + 1);
+ size_t off = grid->line_offset[row] + (size_t)col;
+ if (attrp != NULL) {
+ *attrp = grid->attrs[off];
}
+ return grid->chars[off];
}
-/// like grid_puts(), but output "text[len]". When "len" is -1 output up to
-/// a NUL.
-void grid_puts_len(ScreenGrid *grid, char *text, int textlen, int row, int col, int attr)
-{
- size_t off;
- char *ptr = text;
- int len = textlen;
- int c;
- size_t max_off;
- int mbyte_blen = 1;
- int mbyte_cells = 1;
- int u8c = 0;
- int u8cc[MAX_MCO];
- bool clear_next_cell = false;
- int prev_c = 0; // previous Arabic character
- int pc, nc, nc1;
- int pcc[MAX_MCO];
- int need_redraw;
- bool do_flush = false;
+static ScreenGrid *grid_line_grid = NULL;
+static int grid_line_row = -1;
+static int grid_line_coloff = 0;
+static int grid_line_maxcol = 0;
+static int grid_line_first = INT_MAX;
+static int grid_line_last = 0;
+/// Start a group of grid_line_puts calls that builds a single grid line.
+///
+/// Must be matched with a grid_line_flush call before moving to
+/// another line.
+void grid_line_start(ScreenGrid *grid, int row)
+{
+ int col = 0;
grid_adjust(&grid, &row, &col);
-
- // Safety check. The check for negative row and column is to fix issue
- // vim/vim#4102. TODO(neovim): find out why row/col could be negative.
- if (grid->chars == NULL
- || row >= grid->rows || row < 0
- || col >= grid->cols || col < 0) {
- return;
+ assert(grid_line_grid == NULL);
+ grid_line_row = row;
+ grid_line_grid = grid;
+ grid_line_coloff = col;
+ grid_line_first = (int)linebuf_size;
+ grid_line_maxcol = grid->cols - grid_line_coloff;
+ grid_line_last = 0;
+
+ assert((size_t)grid_line_maxcol <= linebuf_size);
+
+ if (rdb_flags & RDB_INVALID) {
+ // Current batch must not depend on previous contents of linebuf_char.
+ // Set invalid values which will cause assertion failures later if they are used.
+ memset(linebuf_char, 0xFF, sizeof(schar_T) * linebuf_size);
+ memset(linebuf_attr, 0xFF, sizeof(sattr_T) * linebuf_size);
}
+}
- if (put_dirty_row == -1) {
- grid_puts_line_start(grid, row);
- do_flush = true;
- } else {
- if (grid != put_dirty_grid || row != put_dirty_row) {
- abort();
+/// Get present char from current rendered screen line
+///
+/// This indicates what already is on screen, not the pending render buffer.
+///
+/// @return char or space if out of bounds
+schar_T grid_line_getchar(int col, int *attr)
+{
+ if (col < grid_line_maxcol) {
+ col += grid_line_coloff;
+ size_t off = grid_line_grid->line_offset[grid_line_row] + (size_t)col;
+ if (attr != NULL) {
+ *attr = grid_line_grid->attrs[off];
}
+ return grid_line_grid->chars[off];
+ } else {
+ // NUL is a very special value (right-half of double width), space is True Neutralâ„¢
+ return schar_from_ascii(' ');
}
- off = grid->line_offset[row] + (size_t)col;
+}
- // When drawing over the right half of a double-wide char clear out the
- // left half. Only needed in a terminal.
- if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) {
- // redraw the previous cell, make it empty
- put_dirty_first = -1;
- put_dirty_last = MAX(put_dirty_last, 1);
+void grid_line_put_schar(int col, schar_T schar, int attr)
+{
+ assert(grid_line_grid);
+ if (col >= grid_line_maxcol) {
+ return;
}
- max_off = grid->line_offset[row] + (size_t)grid->cols;
- while (col < grid->cols
- && (len < 0 || (int)(ptr - text) < len)
- && *ptr != NUL) {
- c = (unsigned char)(*ptr);
+ linebuf_char[col] = schar;
+ linebuf_attr[col] = attr;
+
+ grid_line_first = MIN(grid_line_first, col);
+ // TODO(bfredl): Y U NO DOUBLEWIDTH?
+ grid_line_last = MAX(grid_line_last, col + 1);
+ linebuf_vcol[col] = -1;
+}
+
+/// Put string "text" at "col" position relative to the grid line from the
+/// recent grid_line_start() call.
+///
+/// @param textlen length of string or -1 to use strlen(text)
+/// Note: only outputs within one row!
+///
+/// @return number of grid cells used
+int grid_line_puts(int col, const char *text, int textlen, int attr)
+{
+ const char *ptr = text;
+ int len = textlen;
+
+ assert(grid_line_grid);
+
+ int start_col = col;
+
+ const int max_col = grid_line_maxcol;
+ while (col < max_col && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) {
// check if this is the first byte of a multibyte
- mbyte_blen = len > 0
- ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr))
- : utfc_ptr2len(ptr);
- u8c = len >= 0
- ? utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr))
- : utfc_ptr2char(ptr, u8cc);
- mbyte_cells = utf_char2cells(u8c);
- if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
- // Do Arabic shaping.
- if (len >= 0 && (int)(ptr - text) + mbyte_blen >= len) {
- // Past end of string to be displayed.
- nc = NUL;
- nc1 = NUL;
- } else {
- nc = len >= 0
- ? utfc_ptr2char_len(ptr + mbyte_blen, pcc,
- (int)((text + len) - ptr - mbyte_blen))
- : utfc_ptr2char(ptr + mbyte_blen, pcc);
- nc1 = pcc[0];
- }
- pc = prev_c;
- prev_c = u8c;
- u8c = arabic_shape(u8c, &c, &u8cc[0], nc, nc1, pc);
- } else {
- prev_c = u8c;
+ int mbyte_blen = len > 0
+ ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr))
+ : utfc_ptr2len(ptr);
+ int firstc;
+ schar_T schar = len >= 0
+ ? utfc_ptr2schar_len(ptr, (int)((text + len) - ptr), &firstc)
+ : utfc_ptr2schar(ptr, &firstc);
+ int mbyte_cells = utf_char2cells(firstc);
+ if (mbyte_cells > 2) {
+ mbyte_cells = 1;
+
+ schar = schar_from_char(0xFFFD);
}
- if (col + mbyte_cells > grid->cols) {
+
+ if (col + mbyte_cells > max_col) {
// Only 1 cell left, but character requires 2 cells:
// display a '>' in the last column to avoid wrapping. */
- c = '>';
- u8c = '>';
- u8cc[0] = 0;
+ schar = schar_from_ascii('>');
mbyte_cells = 1;
}
- schar_T buf;
- schar_from_cc(buf, u8c, u8cc);
-
- need_redraw = schar_cmp(grid->chars[off], buf)
- || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0)
- || grid->attrs[off] != attr
- || exmode_active
- || rdb_flags & RDB_NODELTA;
-
- if (need_redraw) {
- // When at the end of the text and overwriting a two-cell
- // character with a one-cell character, need to clear the next
- // cell. Also when overwriting the left half of a two-cell char
- // with the right half of a two-cell char. Do this only once
- // (utf8_off2cells() may return 2 on the right half).
- if (clear_next_cell) {
- clear_next_cell = false;
- } else if ((len < 0 ? ptr[mbyte_blen] == NUL : ptr + mbyte_blen >= text + len)
- && ((mbyte_cells == 1
- && grid_off2cells(grid, off, max_off) > 1)
- || (mbyte_cells == 2
- && grid_off2cells(grid, off, max_off) == 1
- && grid_off2cells(grid, off + 1, max_off) > 1))) {
- clear_next_cell = true;
- }
-
- // When at the start of the text and overwriting the right half of a
- // two-cell character in the same grid, truncate that into a '>'.
- if (ptr == text && col > 0 && grid->chars[off][0] == 0) {
- grid->chars[off - 1][0] = '>';
- grid->chars[off - 1][1] = 0;
- }
+ // When at the start of the text and overwriting the right half of a
+ // two-cell character in the same grid, truncate that into a '>'.
+ if (ptr == text && col > grid_line_first && col < grid_line_last
+ && linebuf_char[col] == 0) {
+ linebuf_char[col - 1] = schar_from_ascii('>');
+ }
- schar_copy(grid->chars[off], buf);
- grid->attrs[off] = attr;
- if (mbyte_cells == 2) {
- grid->chars[off + 1][0] = 0;
- grid->attrs[off + 1] = attr;
- }
- put_dirty_first = MIN(put_dirty_first, col);
- put_dirty_last = MAX(put_dirty_last, col + mbyte_cells);
+ linebuf_char[col] = schar;
+ linebuf_attr[col] = attr;
+ linebuf_vcol[col] = -1;
+ if (mbyte_cells == 2) {
+ linebuf_char[col + 1] = 0;
+ linebuf_attr[col + 1] = attr;
+ linebuf_vcol[col + 1] = -1;
}
- off += (size_t)mbyte_cells;
col += mbyte_cells;
ptr += mbyte_blen;
- if (clear_next_cell) {
- // This only happens at the end, display one space next.
- ptr = " ";
- len = -1;
+ }
+
+ if (col > start_col) {
+ grid_line_first = MIN(grid_line_first, start_col);
+ grid_line_last = MAX(grid_line_last, col);
+ }
+
+ return col - start_col;
+}
+
+void grid_line_fill(int start_col, int end_col, int c, int attr)
+{
+ end_col = MIN(end_col, grid_line_maxcol);
+ if (start_col >= end_col) {
+ return;
+ }
+
+ schar_T sc = schar_from_char(c);
+ for (int col = start_col; col < end_col; col++) {
+ linebuf_char[col] = sc;
+ linebuf_attr[col] = attr;
+ linebuf_vcol[col] = -1;
+ }
+
+ grid_line_first = MIN(grid_line_first, start_col);
+ grid_line_last = MAX(grid_line_last, end_col);
+}
+
+/// move the cursor to a position in a currently rendered line.
+void grid_line_cursor_goto(int col)
+{
+ ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, col);
+}
+
+void grid_line_mirror(void)
+{
+ if (grid_line_first >= grid_line_last) {
+ return;
+ }
+ linebuf_mirror(&grid_line_first, &grid_line_last, grid_line_maxcol);
+}
+
+void linebuf_mirror(int *firstp, int *lastp, int maxcol)
+{
+ int first = *firstp;
+ int last = *lastp;
+
+ size_t n = (size_t)(last - first);
+ int mirror = maxcol - 1; // Mirrors are more fun than television.
+ schar_T *scratch_char = (schar_T *)linebuf_scratch;
+ memcpy(scratch_char + first, linebuf_char + first, n * sizeof(schar_T));
+ for (int col = first; col < last; col++) {
+ int rev = mirror - col;
+ if (col + 1 < last && scratch_char[col + 1] == 0) {
+ linebuf_char[rev - 1] = scratch_char[col];
+ linebuf_char[rev] = 0;
+ col++;
+ } else {
+ linebuf_char[rev] = scratch_char[col];
}
}
- if (do_flush) {
- grid_puts_line_flush(true);
+ // for attr and vcol: assumes doublewidth chars are self-consistent
+ sattr_T *scratch_attr = (sattr_T *)linebuf_scratch;
+ memcpy(scratch_attr + first, linebuf_attr + first, n * sizeof(sattr_T));
+ for (int col = first; col < last; col++) {
+ linebuf_attr[mirror - col] = scratch_attr[col];
+ }
+
+ colnr_T *scratch_vcol = (colnr_T *)linebuf_scratch;
+ memcpy(scratch_vcol + first, linebuf_vcol + first, n * sizeof(colnr_T));
+ for (int col = first; col < last; col++) {
+ linebuf_vcol[mirror - col] = scratch_vcol[col];
+ }
+
+ *lastp = maxcol - first;
+ *firstp = maxcol - last;
+}
+
+/// End a group of grid_line_puts calls and send the screen buffer to the UI layer.
+void grid_line_flush(void)
+{
+ ScreenGrid *grid = grid_line_grid;
+ grid_line_grid = NULL;
+ assert(grid_line_last <= grid_line_maxcol);
+ if (grid_line_first >= grid_line_last) {
+ return;
}
+
+ grid_put_linebuf(grid, grid_line_row, grid_line_coloff, grid_line_first, grid_line_last,
+ grid_line_last, false, 0, false);
}
-/// End a group of grid_puts_len calls and send the screen buffer to the UI
-/// layer.
+/// flush grid line but only if on a valid row
///
-/// @param set_cursor Move the visible cursor to the end of the changed region.
-/// This is a workaround for not yet refactored code paths
-/// and shouldn't be used in new code.
-void grid_puts_line_flush(bool set_cursor)
-{
- assert(put_dirty_row != -1);
- if (put_dirty_first < put_dirty_last) {
- if (set_cursor) {
- ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row,
- MIN(put_dirty_last, put_dirty_grid->cols - 1));
- }
- if (!put_dirty_grid->throttled) {
- ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last,
- put_dirty_last, 0, false);
- } else if (put_dirty_grid->dirty_col) {
- if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) {
- put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last;
- }
+/// This is a stopgap until message.c has been refactored to behave
+void grid_line_flush_if_valid_row(void)
+{
+ if (grid_line_row < 0 || grid_line_row >= grid_line_grid->rows) {
+ if (rdb_flags & RDB_INVALID) {
+ abort();
+ } else {
+ grid_line_grid = NULL;
+ return;
}
- put_dirty_first = INT_MAX;
- put_dirty_last = 0;
}
- put_dirty_row = -1;
- put_dirty_grid = NULL;
+ grid_line_flush();
}
/// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col"
@@ -377,8 +538,6 @@ void grid_puts_line_flush(bool set_cursor)
void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int end_col, int c1,
int c2, int attr)
{
- schar_T sc;
-
int row_off = 0, col_off = 0;
grid_adjust(&grid, &row_off, &col_off);
start_row += row_off;
@@ -400,47 +559,46 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
}
for (int row = start_row; row < end_row; row++) {
+ int dirty_first = INT_MAX;
+ int dirty_last = 0;
+ size_t lineoff = grid->line_offset[row];
+
// When drawing over the right half of a double-wide char clear
// out the left half. When drawing over the left half of a
// double wide-char clear out the right half. Only needed in a
// terminal.
- if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) {
- grid_puts_len(grid, " ", 1, row, start_col - 1, 0);
+ if (start_col > 0 && grid->chars[lineoff + (size_t)start_col] == NUL) {
+ size_t off = lineoff + (size_t)start_col - 1;
+ grid->chars[off] = schar_from_ascii(' ');
+ grid->attrs[off] = attr;
+ dirty_first = start_col - 1;
}
- if (end_col < grid->cols
- && grid_fix_col(grid, end_col, row) != end_col) {
- grid_puts_len(grid, " ", 1, row, end_col, 0);
+ if (end_col < grid->cols && grid->chars[lineoff + (size_t)end_col] == NUL) {
+ size_t off = lineoff + (size_t)end_col;
+ grid->chars[off] = schar_from_ascii(' ');
+ grid->attrs[off] = attr;
+ dirty_last = end_col + 1;
}
- // if grid was resized (in ext_multigrid mode), the UI has no redraw updates
- // for the newly resized grid. It is better mark everything as dirty and
- // send all the updates.
- int dirty_first = INT_MAX;
- int dirty_last = 0;
-
int col = start_col;
- schar_from_char(sc, c1);
- size_t lineoff = grid->line_offset[row];
+ schar_T sc = schar_from_char(c1);
for (col = start_col; col < end_col; col++) {
size_t off = lineoff + (size_t)col;
- if (schar_cmp(grid->chars[off], sc) || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
- schar_copy(grid->chars[off], sc);
+ if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
+ grid->chars[off] = sc;
grid->attrs[off] = attr;
if (dirty_first == INT_MAX) {
dirty_first = col;
}
dirty_last = col + 1;
}
+ grid->vcols[off] = -1;
if (col == start_col) {
- schar_from_char(sc, c2);
+ sc = schar_from_char(c2);
}
}
if (dirty_last > dirty_first) {
- // TODO(bfredl): support a cleared suffix even with a batched line?
- if (put_dirty_row == row) {
- put_dirty_first = MIN(put_dirty_first, dirty_first);
- put_dirty_last = MAX(put_dirty_last, dirty_last);
- } else if (grid->throttled) {
+ if (grid->throttled) {
// Note: assumes msg_grid is the only throttled grid
assert(grid == &msg_grid);
int dirty = 0;
@@ -457,10 +615,6 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
ui_line(grid, row, dirty_first, last, dirty_last, attr, false);
}
}
-
- if (end_col == grid->cols) {
- grid->line_wraps[row] = false;
- }
}
}
@@ -469,14 +623,14 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
/// - the attributes are different
/// - the character is multi-byte and the next byte is different
/// - the character is two cells wide and the second cell differs.
-static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_to, int cols)
+static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int cols)
{
return (cols > 0
- && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to])
- || linebuf_attr[off_from] != grid->attrs[off_to]
- || (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1
- && schar_cmp(linebuf_char[off_from + 1],
- grid->chars[off_to + 1])))
+ && ((linebuf_char[col] != grid->chars[off_to]
+ || linebuf_attr[col] != grid->attrs[off_to]
+ || (cols > 1 && linebuf_char[col + 1] == 0
+ && linebuf_char[col + 1] != grid->chars[off_to + 1]))
+ || exmode_active // TODO(bfredl): what in the actual fuck
|| rdb_flags & RDB_NODELTA));
}
@@ -486,53 +640,47 @@ static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_
/// "endcol" gives the columns where valid characters are.
/// "clear_width" is the width of the window. It's > 0 if the rest of the line
/// needs to be cleared, negative otherwise.
-/// "rlflag" is true in a rightleft window:
+/// "rl" is true for rightleft text, like a window with 'rightleft' option set
/// When true and "clear_width" > 0, clear columns 0 to "endcol"
/// When false and "clear_width" > 0, clear columns "endcol" to "clear_width"
/// If "wrap" is true, then hint to the UI that "row" contains a line
/// which has wrapped into the next row.
-void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width,
- int rlflag, win_T *wp, int bg_attr, bool wrap)
+void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol, int clear_width,
+ bool rl, int bg_attr, bool wrap)
{
- size_t max_off_from;
- size_t max_off_to;
- int col = 0;
- bool redraw_this; // Does character need redraw?
bool redraw_next; // redraw_this for next character
bool clear_next = false;
int char_cells; // 1: normal char
// 2: occupies two display cells
- int start_dirty = -1, end_dirty = 0;
-
+ assert(0 <= row && row < grid->rows);
// TODO(bfredl): check all callsites and eliminate
- // Check for illegal row and col, just in case
- if (row >= grid->rows) {
- row = grid->rows - 1;
- }
+ // Check for illegal col, just in case
if (endcol > grid->cols) {
endcol = grid->cols;
}
- grid_adjust(&grid, &row, &coloff);
-
// Safety check. Avoids clang warnings down the call stack.
if (grid->chars == NULL || row >= grid->rows || coloff >= grid->cols) {
DLOG("invalid state, skipped");
return;
}
- size_t off_from = 0;
+ bool invalid_row = grid != &default_grid && grid_invalid_row(grid, row) && col == 0;
size_t off_to = grid->line_offset[row] + (size_t)coloff;
- max_off_from = linebuf_size;
- max_off_to = grid->line_offset[row] + (size_t)grid->cols;
+ const size_t max_off_to = grid->line_offset[row] + (size_t)grid->cols;
+
+ // When at the start of the text and overwriting the right half of a
+ // two-cell character in the same grid, truncate that into a '>'.
+ if (col > 0 && grid->chars[off_to + (size_t)col] == 0) {
+ linebuf_char[col - 1] = schar_from_ascii('>');
+ col--;
+ }
- if (rlflag) {
+ if (rl) {
// Clear rest first, because it's left of the text.
if (clear_width > 0) {
- while (col <= endcol && grid->chars[off_to][0] == ' '
- && grid->chars[off_to][1] == NUL
- && grid->attrs[off_to] == bg_attr) {
- off_to++;
+ while (col <= endcol && grid->chars[off_to + (size_t)col] == schar_from_ascii(' ')
+ && grid->attrs[off_to + (size_t)col] == bg_attr) {
col++;
}
if (col <= endcol) {
@@ -540,28 +688,32 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
}
}
col = endcol + 1;
- off_to = grid->line_offset[row] + (size_t)col + (size_t)coloff;
- off_from += (size_t)col;
- endcol = (clear_width > 0 ? clear_width : -clear_width);
+ endcol = clear_width;
+ }
+
+ if (p_arshape && !p_tbidi && endcol > col) {
+ line_do_arabic_shape(linebuf_char + col, endcol - col);
}
if (bg_attr) {
for (int c = col; c < endcol; c++) {
- linebuf_attr[off_from + (size_t)c] =
- hl_combine_attr(bg_attr, linebuf_attr[off_from + (size_t)c]);
+ linebuf_attr[c] = hl_combine_attr(bg_attr, linebuf_attr[c]);
}
}
- redraw_next = grid_char_needs_redraw(grid, off_from, off_to, endcol - col);
+ redraw_next = grid_char_needs_redraw(grid, col, (size_t)col + off_to, endcol - col);
+
+ int start_dirty = -1, end_dirty = 0;
while (col < endcol) {
char_cells = 1;
- if (col + 1 < endcol) {
- char_cells = line_off2cells(linebuf_char, off_from, max_off_from);
+ if (col + 1 < endcol && linebuf_char[col + 1] == 0) {
+ char_cells = 2;
}
- redraw_this = redraw_next;
- redraw_next = grid_char_needs_redraw(grid, off_from + (size_t)char_cells,
- off_to + (size_t)char_cells,
+ bool redraw_this = redraw_next; // Does character need redraw?
+ size_t off = (size_t)col + off_to;
+ redraw_next = grid_char_needs_redraw(grid, col + char_cells,
+ off + (size_t)char_cells,
endcol - col - char_cells);
if (redraw_this) {
@@ -574,52 +726,50 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
// the right half of the old character.
// Also required when writing the right half of a double-width
// char over the left half of an existing one
- if (col + char_cells == endcol
- && ((char_cells == 1
- && grid_off2cells(grid, off_to, max_off_to) > 1)
- || (char_cells == 2
- && grid_off2cells(grid, off_to, max_off_to) == 1
- && grid_off2cells(grid, off_to + 1, max_off_to) > 1))) {
+ if (col + char_cells == endcol && off + (size_t)char_cells < max_off_to
+ && grid->chars[off + (size_t)char_cells] == NUL) {
clear_next = true;
}
- schar_copy(grid->chars[off_to], linebuf_char[off_from]);
+ grid->chars[off] = linebuf_char[col];
if (char_cells == 2) {
- schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]);
+ grid->chars[off + 1] = linebuf_char[col + 1];
}
- grid->attrs[off_to] = linebuf_attr[off_from];
+ grid->attrs[off] = linebuf_attr[col];
// For simplicity set the attributes of second half of a
// double-wide character equal to the first half.
if (char_cells == 2) {
- grid->attrs[off_to + 1] = linebuf_attr[off_from];
+ grid->attrs[off + 1] = linebuf_attr[col];
}
}
- off_to += (size_t)char_cells;
- off_from += (size_t)char_cells;
+ grid->vcols[off] = linebuf_vcol[col];
+ if (char_cells == 2) {
+ grid->vcols[off + 1] = linebuf_vcol[col + 1];
+ }
+
col += char_cells;
}
if (clear_next) {
// Clear the second half of a double-wide character of which the left
// half was overwritten with a single-wide character.
- schar_from_ascii(grid->chars[off_to], ' ');
+ grid->chars[(size_t)col + off_to] = schar_from_ascii(' ');
end_dirty++;
}
int clear_end = -1;
- if (clear_width > 0 && !rlflag) {
+ if (clear_width > 0 && !rl) {
// blank out the rest of the line
// TODO(bfredl): we could cache winline widths
while (col < clear_width) {
- if (grid->chars[off_to][0] != ' '
- || grid->chars[off_to][1] != NUL
- || grid->attrs[off_to] != bg_attr
+ size_t off = (size_t)col + off_to;
+ if (grid->chars[off] != schar_from_ascii(' ')
+ || grid->attrs[off] != bg_attr
|| rdb_flags & RDB_NODELTA) {
- grid->chars[off_to][0] = ' ';
- grid->chars[off_to][1] = NUL;
- grid->attrs[off_to] = bg_attr;
+ grid->chars[off] = schar_from_ascii(' ');
+ grid->attrs[off] = bg_attr;
if (start_dirty == -1) {
start_dirty = col;
end_dirty = col;
@@ -628,17 +778,11 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
}
clear_end = col + 1;
}
+ grid->vcols[off] = MAXCOL;
col++;
- off_to++;
}
}
- if (clear_width > 0 || wp->w_width != grid->cols) {
- // If we cleared after the end of the line, it did not wrap.
- // For vsplit, line wrapping is not possible.
- grid->line_wraps[row] = false;
- }
-
if (clear_end < end_dirty) {
clear_end = end_dirty;
}
@@ -646,30 +790,44 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
start_dirty = end_dirty;
}
if (clear_end > start_dirty) {
- ui_line(grid, row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end,
- bg_attr, wrap);
+ if (!grid->throttled) {
+ int start_pos = coloff + start_dirty;
+ // When drawing over the right half of a double-wide char clear out the
+ // left half. Only needed in a terminal.
+ if (invalid_row && start_pos == 0) {
+ start_pos = -1;
+ }
+ ui_line(grid, row, start_pos, coloff + end_dirty, coloff + clear_end,
+ bg_attr, wrap);
+ } else if (grid->dirty_col) {
+ // TODO(bfredl): really get rid of the extra pseudo terminal in message.c
+ // by using a linebuf_char copy for "throttled message line"
+ if (clear_end > grid->dirty_col[row]) {
+ grid->dirty_col[row] = clear_end;
+ }
+ }
}
}
void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
{
int new_row;
- ScreenGrid new = *grid;
+ ScreenGrid ngrid = *grid;
assert(rows >= 0 && columns >= 0);
size_t ncells = (size_t)rows * (size_t)columns;
- new.chars = xmalloc(ncells * sizeof(schar_T));
- new.attrs = xmalloc(ncells * sizeof(sattr_T));
- new.line_offset = xmalloc((size_t)rows * sizeof(*new.line_offset));
- new.line_wraps = xmalloc((size_t)rows * sizeof(*new.line_wraps));
+ ngrid.chars = xmalloc(ncells * sizeof(schar_T));
+ ngrid.attrs = xmalloc(ncells * sizeof(sattr_T));
+ ngrid.vcols = xmalloc(ncells * sizeof(colnr_T));
+ memset(ngrid.vcols, -1, ncells * sizeof(colnr_T));
+ ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset));
- new.rows = rows;
- new.cols = columns;
+ ngrid.rows = rows;
+ ngrid.cols = columns;
- for (new_row = 0; new_row < new.rows; new_row++) {
- new.line_offset[new_row] = (size_t)new_row * (size_t)new.cols;
- new.line_wraps[new_row] = false;
+ for (new_row = 0; new_row < ngrid.rows; new_row++) {
+ ngrid.line_offset[new_row] = (size_t)new_row * (size_t)ngrid.cols;
- grid_clear_line(&new, new.line_offset[new_row], columns, valid);
+ grid_clear_line(&ngrid, ngrid.line_offset[new_row], columns, valid);
if (copy) {
// If the screen is not going to be cleared, copy as much as
@@ -677,26 +835,33 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
// (used when resizing the window at the "--more--" prompt or when
// executing an external command, for the GUI).
if (new_row < grid->rows && grid->chars != NULL) {
- int len = MIN(grid->cols, new.cols);
- memmove(new.chars + new.line_offset[new_row],
+ int len = MIN(grid->cols, ngrid.cols);
+ memmove(ngrid.chars + ngrid.line_offset[new_row],
grid->chars + grid->line_offset[new_row],
(size_t)len * sizeof(schar_T));
- memmove(new.attrs + new.line_offset[new_row],
+ memmove(ngrid.attrs + ngrid.line_offset[new_row],
grid->attrs + grid->line_offset[new_row],
(size_t)len * sizeof(sattr_T));
+ memmove(ngrid.vcols + ngrid.line_offset[new_row],
+ grid->vcols + grid->line_offset[new_row],
+ (size_t)len * sizeof(colnr_T));
}
}
}
grid_free(grid);
- *grid = new;
+ *grid = ngrid;
// Share a single scratch buffer for all grids, by
// ensuring it is as wide as the widest grid.
if (linebuf_size < (size_t)columns) {
xfree(linebuf_char);
xfree(linebuf_attr);
+ xfree(linebuf_vcol);
+ xfree(linebuf_scratch);
linebuf_char = xmalloc((size_t)columns * sizeof(schar_T));
linebuf_attr = xmalloc((size_t)columns * sizeof(sattr_T));
+ linebuf_vcol = xmalloc((size_t)columns * sizeof(colnr_T));
+ linebuf_scratch = xmalloc((size_t)columns * sizeof(sscratch_T));
linebuf_size = (size_t)columns;
}
}
@@ -705,13 +870,13 @@ void grid_free(ScreenGrid *grid)
{
xfree(grid->chars);
xfree(grid->attrs);
+ xfree(grid->vcols);
xfree(grid->line_offset);
- xfree(grid->line_wraps);
grid->chars = NULL;
grid->attrs = NULL;
+ grid->vcols = NULL;
grid->line_offset = NULL;
- grid->line_wraps = NULL;
}
/// Doesn't allow reinit, so must only be called by free_all_mem!
@@ -720,6 +885,8 @@ void grid_free_all_mem(void)
grid_free(&default_grid);
xfree(linebuf_char);
xfree(linebuf_attr);
+ xfree(linebuf_vcol);
+ xfree(linebuf_scratch);
}
/// (Re)allocates a window grid if size changed while in ext_multigrid mode.
@@ -788,6 +955,7 @@ void win_grid_alloc(win_T *wp)
if ((resizing_screen || was_resized) && want_allocation) {
ui_call_grid_resize(grid_allocated->handle,
grid_allocated->cols, grid_allocated->rows);
+ ui_check_cursor_grid(grid_allocated->handle);
}
}
@@ -810,7 +978,6 @@ void grid_assign_handle(ScreenGrid *grid)
/// 'row', 'col' and 'end' are relative to the start of the region.
void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
{
- int i;
int j;
unsigned temp;
@@ -825,7 +992,7 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
// Shift line_offset[] line_count down to reflect the inserted lines.
// Clear the inserted lines.
- for (i = 0; i < line_count; i++) {
+ for (int i = 0; i < line_count; i++) {
if (width != grid->cols) {
// need to copy part of a line
j = end - 1 - i;
@@ -834,16 +1001,13 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
}
j += line_count;
grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
- grid->line_wraps[j] = false;
} else {
j = end - 1 - i;
temp = (unsigned)grid->line_offset[j];
while ((j -= line_count) >= row) {
grid->line_offset[j + line_count] = grid->line_offset[j];
- grid->line_wraps[j + line_count] = grid->line_wraps[j];
}
grid->line_offset[j + line_count] = temp;
- grid->line_wraps[j + line_count] = false;
grid_clear_line(grid, temp, grid->cols, false);
}
}
@@ -860,7 +1024,6 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
{
int j;
- int i;
unsigned temp;
int row_off = 0;
@@ -874,7 +1037,7 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
// Now shift line_offset[] line_count up to reflect the deleted lines.
// Clear the inserted lines.
- for (i = 0; i < line_count; i++) {
+ for (int i = 0; i < line_count; i++) {
if (width != grid->cols) {
// need to copy part of a line
j = row + i;
@@ -883,17 +1046,14 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
}
j -= line_count;
grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
- grid->line_wraps[j] = false;
} else {
// whole width, moving the line pointers is faster
j = row + i;
temp = (unsigned)grid->line_offset[j];
while ((j += line_count) <= end - 1) {
grid->line_offset[j - line_count] = grid->line_offset[j];
- grid->line_wraps[j - line_count] = grid->line_wraps[j];
}
grid->line_offset[j - line_count] = temp;
- grid->line_wraps[j - line_count] = false;
grid_clear_line(grid, temp, grid->cols, false);
}
}
@@ -910,6 +1070,7 @@ static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T));
memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T));
+ memmove(grid->vcols + off_to, grid->vcols + off_from, (size_t)width * sizeof(colnr_T));
}
win_T *get_win_by_grid_handle(handle_T handle)
diff --git a/src/nvim/grid.h b/src/nvim/grid.h
index deb3d3785d..9d8e395dae 100644
--- a/src/nvim/grid.h
+++ b/src/nvim/grid.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_GRID_H
-#define NVIM_GRID_H
+#pragma once
#include <stdbool.h>
+#include <stddef.h> // IWYU pragma: keep
#include <string.h>
-#include "nvim/ascii.h"
#include "nvim/buffer_defs.h"
-#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
+#include "nvim/grid_defs.h" // IWYU pragma: export
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
-#include "nvim/memory.h"
+#include "nvim/pos_defs.h"
/// By default, all windows are drawn on a single rectangular grid, represented by
/// this ScreenGrid instance. In multigrid mode each window will have its own
@@ -18,47 +17,43 @@
///
/// Note: before the screen is initialized and when out of memory these can be
/// NULL.
-EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT);
+EXTERN ScreenGrid default_grid INIT( = SCREEN_GRID_INIT);
#define DEFAULT_GRID_HANDLE 1 // handle for the default_grid
/// While resizing the screen this flag is set.
-EXTERN bool resizing_screen INIT(= 0);
+EXTERN bool resizing_screen INIT( = 0);
-EXTERN schar_T *linebuf_char INIT(= NULL);
-EXTERN sattr_T *linebuf_attr INIT(= NULL);
+EXTERN schar_T *linebuf_char INIT( = NULL);
+EXTERN sattr_T *linebuf_attr INIT( = NULL);
+EXTERN colnr_T *linebuf_vcol INIT( = NULL);
+EXTERN char *linebuf_scratch INIT( = NULL);
// Low-level functions to manipulate individual character cells on the
// screen grid.
/// Put a ASCII character in a screen cell.
-static inline void schar_from_ascii(char *p, const char c)
-{
- p[0] = c;
- p[1] = 0;
-}
+///
+/// If `x` is a compile time constant, schar_from_ascii(x) will also be.
+/// But the specific value varies per platform.
+#ifdef ORDER_BIG_ENDIAN
+# define schar_from_ascii(x) ((schar_T)((x) << 24))
+#else
+# define schar_from_ascii(x) ((schar_T)(x))
+#endif
/// Put a unicode character in a screen cell.
-static inline int schar_from_char(char *p, int c)
-{
- int len = utf_char2bytes(c, p);
- p[len] = NUL;
- return len;
-}
-
-/// compare the contents of two screen cells.
-static inline int schar_cmp(char *sc1, char *sc2)
-{
- return strncmp(sc1, sc2, sizeof(schar_T));
-}
-
-/// copy the contents of screen cell `sc2` into cell `sc1`
-static inline void schar_copy(char *sc1, char *sc2)
+static inline schar_T schar_from_char(int c)
{
- xstrlcpy(sc1, sc2, sizeof(schar_T));
+ schar_T sc = 0;
+ if (c >= 0x200000) {
+ // TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences
+ c = 0xFFFD;
+ }
+ utf_char2bytes(c, (char *)&sc);
+ return sc;
}
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "grid.h.generated.h"
#endif
-#endif // NVIM_GRID_H
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index db31f7b984..10a6161171 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -1,17 +1,15 @@
-#ifndef NVIM_GRID_DEFS_H
-#define NVIM_GRID_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-#define MAX_MCO 6 // fixed value for 'maxcombine'
-
-// The characters and attributes drawn on grids.
-typedef char schar_T[(MAX_MCO + 1) * 4 + 1];
-typedef int sattr_T;
+// Includes final NUL. MAX_MCO is no longer used, but at least 4*(MAX_MCO+1)+1=29
+// ensures we can fit all composed chars which did fit before.
+#define MAX_SCHAR_SIZE 32
enum {
kZIndexDefaultGrid = 0,
@@ -29,7 +27,7 @@ enum {
/// we can avoid sending bigger updates than necessary to the Ul layer.
///
/// Screen cells are stored as NUL-terminated UTF-8 strings, and a cell can
-/// contain up to MAX_MCO composing characters after the base character.
+/// contain composing characters as many as fits in MAX_SCHAR_SIZE-1 bytes
/// The composing characters are to be drawn on top of the original character.
/// The content after the NUL is not defined (so comparison must be done a
/// single cell at a time). Double-width characters are stored in the left cell,
@@ -37,20 +35,24 @@ enum {
/// screen is cleared, the cells should be filled with a single whitespace char.
///
/// attrs[] contains the highlighting attribute for each cell.
-/// line_offset[n] is the offset from chars[] and attrs[] for the
-/// start of line 'n'. These offsets are in general not linear, as full screen
-/// scrolling is implemented by rotating the offsets in the line_offset array.
-/// line_wraps[] is an array of boolean flags indicating if the screen line
-/// wraps to the next line. It can only be true if a window occupies the entire
-/// screen width.
+///
+/// vcols[] contains the virtual columns in the line. -1 means not available
+/// or before buffer text, MAXCOL means after the end of the line.
+/// -2 or -3 means in fold column and a mouse click should:
+/// -2: open a fold
+/// -3: close a fold
+///
+/// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start
+/// of line 'n'. These offsets are in general not linear, as full screen scrolling
+/// is implemented by rotating the offsets in the line_offset array.
typedef struct ScreenGrid ScreenGrid;
struct ScreenGrid {
handle_T handle;
schar_T *chars;
sattr_T *attrs;
+ colnr_T *vcols;
size_t *line_offset;
- char_u *line_wraps;
// last column that was drawn (not cleared with the default background).
// only used when "throttled" is set. Not allocated by grid_alloc!
@@ -117,6 +119,5 @@ typedef struct {
int coloff;
int cur_attr;
int clear_width;
+ bool wrap;
} GridLineEvent;
-
-#endif // NVIM_GRID_DEFS_H
diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c
index 851e70caca..475666be5e 100644
--- a/src/nvim/hashtab.c
+++ b/src/nvim/hashtab.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file hashtab.c
///
/// Handling of a hashtable with Vim-specific properties.
@@ -26,12 +23,13 @@
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/func_attr.h"
+#include "nvim/gettext.h"
#include "nvim/hashtab.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Magic value for algorithm that walks through the array.
#define PERTURB_SHIFT 5
@@ -65,7 +63,7 @@ void hash_clear(hashtab_T *ht)
/// Free the array of a hash table and all contained values.
///
/// @param off the offset from start of value to start of key (@see hashitem_T).
-void hash_clear_all(hashtab_T *ht, unsigned int off)
+void hash_clear_all(hashtab_T *ht, unsigned off)
{
size_t todo = ht->ht_used;
for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
@@ -207,7 +205,7 @@ int hash_add(hashtab_T *ht, char *key)
hash_T hash = hash_hash(key);
hashitem_T *hi = hash_lookup(ht, key, strlen(key), hash);
if (!HASHITEM_EMPTY(hi)) {
- internal_error("hash_add()");
+ siemsg(_("E685: Internal error: hash_add(): duplicate key \"%s\""), key);
return FAIL;
}
hash_add_item(ht, hi, key, hash);
@@ -350,15 +348,15 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems)
// so that copying is possible.
hashitem_T temparray[HT_INIT_SIZE];
hashitem_T *oldarray = keep_smallarray
- ? memcpy(temparray, ht->ht_smallarray, sizeof(temparray))
- : ht->ht_array;
+ ? memcpy(temparray, ht->ht_smallarray, sizeof(temparray))
+ : ht->ht_array;
if (newarray_is_small) {
CLEAR_FIELD(ht->ht_smallarray);
}
hashitem_T *newarray = newarray_is_small
- ? ht->ht_smallarray
- : xcalloc(newsize, sizeof(hashitem_T));
+ ? ht->ht_smallarray
+ : xcalloc(newsize, sizeof(hashitem_T));
// Move all the items from the old array to the new one, placing them in
// the right spot. The new array won't have any removed items, thus this
@@ -411,7 +409,7 @@ hash_T hash_hash(const char *key)
hash_T hash = (uint8_t)(*key);
if (hash == 0) {
- return (hash_T)0;
+ return 0;
}
// A simplistic algorithm that appears to do very well.
@@ -457,8 +455,8 @@ hash_T hash_hash_len(const char *key, const size_t len)
///
/// Used for testing because luajit ffi does not allow getting addresses of
/// globals.
-const char_u *_hash_key_removed(void)
+const char *_hash_key_removed(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return (char_u *)HI_KEY_REMOVED;
+ return HI_KEY_REMOVED;
}
diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h
index 0a50fb2ef8..d14eb6944e 100644
--- a/src/nvim/hashtab.h
+++ b/src/nvim/hashtab.h
@@ -1,76 +1,18 @@
-#ifndef NVIM_HASHTAB_H
-#define NVIM_HASHTAB_H
+#pragma once
#include <stddef.h>
-#include "nvim/types.h"
+#include "nvim/hashtab_defs.h" // IWYU pragma: export
/// Magic number used for hashitem "hi_key" value indicating a deleted item
///
/// Only the address is used.
extern char hash_removed;
-/// Type for hash number (hash calculation result).
-typedef size_t hash_T;
-
/// The address of "hash_removed" is used as a magic number
/// for hi_key to indicate a removed item.
#define HI_KEY_REMOVED (&hash_removed)
-#define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \
- || (hi)->hi_key == &hash_removed)
-
-/// Hashtable item.
-///
-/// Each item has a NUL terminated string key.
-/// A key can appear only once in the table.
-///
-/// A hash number is computed from the key for quick lookup. When the hashes
-/// of two different keys point to the same entry an algorithm is used to
-/// iterate over other entries in the table until the right one is found.
-/// To make the iteration work removed keys are different from entries where a
-/// key was never present.
-///
-/// Note that this does not contain a pointer to the key and another pointer to
-/// the value. Instead, it is assumed that the key is contained within the
-/// value, so that you can get a pointer to the value subtracting an offset from
-/// the pointer to the key.
-/// This reduces the size of this item by 1/3.
-typedef struct hashitem_S {
- /// Cached hash number for hi_key.
- hash_T hi_hash;
-
- /// Item key.
- ///
- /// Possible values mean the following:
- /// NULL : Item was never used.
- /// HI_KEY_REMOVED : Item was removed.
- /// (Any other pointer value) : Item is currently being used.
- char *hi_key;
-} hashitem_T;
-
-/// Initial size for a hashtable.
-/// Our items are relatively small and growing is expensive, thus start with 16.
-/// Must be a power of 2.
-/// This allows for storing 10 items (2/3 of 16) before a resize is needed.
-#define HT_INIT_SIZE 16
-
-/// An array-based hashtable.
-///
-/// Keys are NUL terminated strings. They cannot be repeated within a table.
-/// Values are of any type.
-///
-/// The hashtable grows to accommodate more entries when needed.
-typedef struct hashtable_S {
- hash_T ht_mask; ///< mask used for hash value
- ///< (nr of items in array is "ht_mask" + 1)
- size_t ht_used; ///< number of items used
- size_t ht_filled; ///< number of items used or removed
- int ht_changed; ///< incremented when adding or removing an item
- int ht_locked; ///< counter for hash_lock()
- hashitem_T *ht_array; ///< points to the array, allocated when it's
- ///< not "ht_smallarray"
- hashitem_T ht_smallarray[HT_INIT_SIZE]; ///< initial array
-} hashtab_T;
+#define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL || (hi)->hi_key == &hash_removed)
/// Iterate over a hashtab
///
@@ -94,5 +36,3 @@ typedef struct hashtable_S {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "hashtab.h.generated.h"
#endif
-
-#endif // NVIM_HASHTAB_H
diff --git a/src/nvim/hashtab_defs.h b/src/nvim/hashtab_defs.h
new file mode 100644
index 0000000000..089838fcae
--- /dev/null
+++ b/src/nvim/hashtab_defs.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <stddef.h>
+
+/// Type for hash number (hash calculation result).
+typedef size_t hash_T;
+
+/// Hashtable item.
+///
+/// Each item has a NUL terminated string key.
+/// A key can appear only once in the table.
+///
+/// A hash number is computed from the key for quick lookup. When the hashes
+/// of two different keys point to the same entry an algorithm is used to
+/// iterate over other entries in the table until the right one is found.
+/// To make the iteration work removed keys are different from entries where a
+/// key was never present.
+///
+/// Note that this does not contain a pointer to the key and another pointer to
+/// the value. Instead, it is assumed that the key is contained within the
+/// value, so that you can get a pointer to the value subtracting an offset from
+/// the pointer to the key.
+/// This reduces the size of this item by 1/3.
+typedef struct hashitem_S {
+ /// Cached hash number for hi_key.
+ hash_T hi_hash;
+
+ /// Item key.
+ ///
+ /// Possible values mean the following:
+ /// NULL : Item was never used.
+ /// HI_KEY_REMOVED : Item was removed.
+ /// (Any other pointer value) : Item is currently being used.
+ char *hi_key;
+} hashitem_T;
+
+/// Initial size for a hashtable.
+/// Our items are relatively small and growing is expensive, thus start with 16.
+/// Must be a power of 2.
+/// This allows for storing 10 items (2/3 of 16) before a resize is needed.
+enum { HT_INIT_SIZE = 16, };
+
+/// An array-based hashtable.
+///
+/// Keys are NUL terminated strings. They cannot be repeated within a table.
+/// Values are of any type.
+///
+/// The hashtable grows to accommodate more entries when needed.
+typedef struct hashtable_S {
+ hash_T ht_mask; ///< mask used for hash value
+ ///< (nr of items in array is "ht_mask" + 1)
+ size_t ht_used; ///< number of items used
+ size_t ht_filled; ///< number of items used or removed
+ int ht_changed; ///< incremented when adding or removing an item
+ int ht_locked; ///< counter for hash_lock()
+ hashitem_T *ht_array; ///< points to the array, allocated when it's
+ ///< not "ht_smallarray"
+ hashitem_T ht_smallarray[HT_INIT_SIZE]; ///< initial array
+} hashtab_T;
diff --git a/src/nvim/help.c b/src/nvim/help.c
index bbc552fa4c..c23dc7fd9d 100644
--- a/src/nvim/help.c
+++ b/src/nvim/help.c
@@ -1,42 +1,46 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// help.c: functions for Vim help
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/extmark_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/help.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -47,19 +51,12 @@
void ex_help(exarg_T *eap)
{
char *arg;
- char *tag;
FILE *helpfd; // file descriptor of help file
- int n;
- int i;
win_T *wp;
int num_matches;
char **matches;
- char *p;
int empty_fnum = 0;
int alt_fnum = 0;
- buf_T *buf;
- int len;
- char *lang;
const bool old_KeyTyped = KeyTyped;
if (eap != NULL) {
@@ -88,13 +85,13 @@ void ex_help(exarg_T *eap)
}
// remove trailing blanks
- p = arg + strlen(arg) - 1;
+ char *p = arg + strlen(arg) - 1;
while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') {
*p-- = NUL;
}
// Check for a specified language
- lang = check_help_lang(arg);
+ char *lang = check_help_lang(arg);
// When no argument given go to the index.
if (*arg == NUL) {
@@ -102,13 +99,13 @@ void ex_help(exarg_T *eap)
}
// Check if there is a match for the argument.
- n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit);
+ int n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit);
- i = 0;
+ int i = 0;
if (n != FAIL && lang != NULL) {
// Find first item with the requested language.
for (i = 0; i < num_matches; i++) {
- len = (int)strlen(matches[i]);
+ int len = (int)strlen(matches[i]);
if (len > 3 && matches[i][len - 3] == '@'
&& STRICMP(matches[i] + len - 2, lang) == 0) {
break;
@@ -128,7 +125,7 @@ void ex_help(exarg_T *eap)
}
// The first match (in the requested language) is the best match.
- tag = xstrdup(matches[i]);
+ char *tag = xstrdup(matches[i]);
FreeWild(num_matches, matches);
// Re-use an existing help window or open a new one.
@@ -151,7 +148,7 @@ void ex_help(exarg_T *eap)
// There is no help window yet.
// Try to open the file specified by the "helpfile" option.
if ((helpfd = os_fopen(p_hf, READBIN)) == NULL) {
- smsg(_("Sorry, help file \"%s\" not found"), p_hf);
+ smsg(0, _("Sorry, help file \"%s\" not found"), p_hf);
goto erret;
}
fclose(helpfd);
@@ -199,7 +196,7 @@ void ex_help(exarg_T *eap)
// may have jumped to another window, check that the buffer is not in a
// window.
if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) {
- buf = buflist_findnr(empty_fnum);
+ buf_T *buf = buflist_findnr(empty_fnum);
if (buf != NULL && buf->b_nwindows == 0) {
wipe_buffer(buf, true);
}
@@ -259,11 +256,8 @@ char *check_help_lang(char *arg)
int help_heuristic(char *matched_string, int offset, int wrong_case)
FUNC_ATTR_PURE
{
- int num_letters;
- char *p;
-
- num_letters = 0;
- for (p = matched_string; *p; p++) {
+ int num_letters = 0;
+ for (char *p = matched_string; *p; p++) {
if (ASCII_ISALNUM(*p)) {
num_letters++;
}
@@ -298,11 +292,8 @@ int help_heuristic(char *matched_string, int offset, int wrong_case)
/// that has been put after the tagname by find_tags().
static int help_compare(const void *s1, const void *s2)
{
- char *p1;
- char *p2;
-
- p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
- p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
+ char *p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
+ char *p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
// Compare by help heuristic number first.
int cmp = strcmp(p1, p2);
@@ -320,8 +311,6 @@ static int help_compare(const void *s1, const void *s2)
/// When "keep_lang" is true try keeping the language of the current buffer.
int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang)
{
- int i;
-
// Specific tags that either have a specific replacement or won't go
// through the generic rules.
static char *(except_tbl[][2]) = {
@@ -379,7 +368,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep
// When the string starting with "expr-" and containing '?' and matches
// the table, it is taken literally (but ~ is escaped). Otherwise '?'
// is recognized as a wildcard.
- for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) {
+ for (int i = (int)ARRAY_SIZE(expr_table); --i >= 0;) {
if (strcmp(arg + 5, expr_table[i]) == 0) {
for (int si = 0, di = 0;; si++) {
if (arg[si] == '~') {
@@ -396,7 +385,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep
} else {
// Recognize a few exceptions to the rule. Some strings that contain
// '*'are changed to "star", otherwise '*' is recognized as a wildcard.
- for (i = 0; except_tbl[i][0] != NULL; i++) {
+ for (int i = 0; except_tbl[i][0] != NULL; i++) {
if (strcmp(arg, except_tbl[i][0]) == 0) {
STRCPY(d, except_tbl[i][1]);
break;
@@ -469,7 +458,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep
// Replace "^x" by "CTRL-X". Don't do this for "^_" to make
// ":help i_^_CTRL-D" work.
// Insert '-' before and after "CTRL-X" when applicable.
- if (*s < ' '
+ if ((uint8_t)(*s) < ' '
|| (*s == '^' && s[1]
&& (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", (uint8_t)s[1]) != NULL))) {
if (d > IObuff && d[-1] != '_' && d[-1] != '\\') {
@@ -658,26 +647,23 @@ void prepare_help_buffer(void)
/// highlighting is not used.
void fix_help_buffer(void)
{
- linenr_T lnum;
- char *line;
- bool in_example = false;
-
// Set filetype to "help".
if (strcmp(curbuf->b_p_ft, "help") != 0) {
curbuf->b_ro_locked++;
- set_option_value_give_err("ft", 0L, "help", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("help"), OPT_LOCAL);
curbuf->b_ro_locked--;
}
if (!syntax_present(curwin)) {
- for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
- line = ml_get_buf(curbuf, lnum, false);
+ bool in_example = false;
+ for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
+ char *line = ml_get_buf(curbuf, lnum);
const size_t len = strlen(line);
if (in_example && len > 0 && !ascii_iswhite(line[0])) {
// End of example: non-white or '<' in first column.
if (line[0] == '<') {
// blank-out a '<' in the first column
- line = ml_get_buf(curbuf, lnum, true);
+ line = ml_get_buf_mut(curbuf, lnum);
line[0] = ' ';
}
in_example = false;
@@ -685,12 +671,12 @@ void fix_help_buffer(void)
if (!in_example && len > 0) {
if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) {
// blank-out a '>' in the last column (start of example)
- line = ml_get_buf(curbuf, lnum, true);
+ line = ml_get_buf_mut(curbuf, lnum);
line[len - 1] = ' ';
in_example = true;
} else if (line[len - 1] == '~') {
// blank-out a '~' at the end of line (header marker)
- line = ml_get_buf(curbuf, lnum, true);
+ line = ml_get_buf_mut(curbuf, lnum);
line[len - 1] = ' ';
}
}
@@ -706,12 +692,14 @@ void fix_help_buffer(void)
&& ASCII_ISALPHA(fname[6])
&& TOLOWER_ASC(fname[7]) == 'x'
&& fname[8] == NUL)) {
- for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) {
- line = ml_get_buf(curbuf, lnum, false);
+ for (linenr_T lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) {
+ char *line = ml_get_buf(curbuf, lnum);
if (strstr(line, "*local-additions*") == NULL) {
continue;
}
+ int lnum_start = lnum;
+
// Go through all directories in 'runtimepath', skipping
// $VIMRUNTIME.
char *p = p_rtp;
@@ -722,9 +710,7 @@ void fix_help_buffer(void)
&& path_full_compare(rt, NameBuff, false, true) != kEqualFiles) {
int fcount;
char **fnames;
- char *s;
vimconv_T vc;
- char *cp;
// Find all "doc/ *.txt" files in this directory.
if (!add_pathsep(NameBuff)
@@ -740,6 +726,8 @@ void fix_help_buffer(void)
if (gen_expand_wildcards(1, buff_list, &fcount,
&fnames, EW_FILE|EW_SILENT) == OK
&& fcount > 0) {
+ char *s;
+ char *cp;
// If foo.abx is found use it instead of foo.txt in
// the same directory.
for (int i1 = 0; i1 < fcount; i1++) {
@@ -829,7 +817,7 @@ void fix_help_buffer(void)
}
convert_setup(&vc, NULL, NULL);
- ml_append(lnum, cp, (colnr_T)0, false);
+ ml_append(lnum, cp, 0, false);
if (cp != IObuff) {
xfree(cp);
}
@@ -842,6 +830,11 @@ void fix_help_buffer(void)
}
xfree(rt);
}
+ linenr_T appended = lnum - lnum_start;
+ if (appended) {
+ mark_adjust(lnum_start + 1, (linenr_T)MAXLNUM, appended, 0, kExtmarkUndo);
+ buf_redraw_changed_lines_later(curbuf, lnum_start + 1, lnum_start + 1, appended);
+ }
break;
}
}
@@ -874,7 +867,6 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
garray_T ga;
int filecount;
char **files;
- char *p1, *p2;
char *s;
TriState utf8 = kNone;
bool mix = false; // detected mixed encodings
@@ -979,9 +971,9 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
}
in_example = false;
}
- p1 = vim_strchr(IObuff, '*'); // find first '*'
+ char *p1 = vim_strchr(IObuff, '*'); // find first '*'
while (p1 != NULL) {
- p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'.
+ char *p2 = strchr(p1 + 1, '*'); // Find second '*'.
if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**".
for (s = p1 + 1; s < p2; s++) {
if (*s == ' ' || *s == '\t' || *s == '|') {
@@ -1028,14 +1020,14 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
// Check for duplicates.
for (int i = 1; i < ga.ga_len; i++) {
- p1 = ((char **)ga.ga_data)[i - 1];
- p2 = ((char **)ga.ga_data)[i];
+ char *p1 = ((char **)ga.ga_data)[i - 1];
+ char *p2 = ((char **)ga.ga_data)[i];
while (*p1 == *p2) {
if (*p2 == '\t') {
*p2 = NUL;
vim_snprintf(NameBuff, MAXPATHL,
_("E154: Duplicate tag \"%s\" in file %s/%s"),
- ((char_u **)ga.ga_data)[i], dir, p2 + 1);
+ ((char **)ga.ga_data)[i], dir, p2 + 1);
emsg(NameBuff);
*p2 = '\t';
break;
@@ -1057,7 +1049,7 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
fputs(s, fd_tags);
} else {
fprintf(fd_tags, "%s\t/" "*", s);
- for (p1 = s; *p1 != '\t'; p1++) {
+ for (char *p1 = s; *p1 != '\t'; p1++) {
// insert backslash before '\\' and '/'
if (*p1 == '\\' || *p1 == '/') {
putc('\\', fd_tags);
@@ -1080,7 +1072,6 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
FUNC_ATTR_NONNULL_ALL
{
- int len;
garray_T ga;
char lang[2];
char ext[5];
@@ -1090,7 +1081,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
// Get a list of all files in the help directory and in subdirectories.
xstrlcpy(NameBuff, dirname, sizeof(NameBuff));
- if (!add_pathsep((char *)NameBuff)
+ if (!add_pathsep(NameBuff)
|| xstrlcat(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) {
emsg(_(e_fnametoolong));
return;
@@ -1111,7 +1102,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
int j;
ga_init(&ga, 1, 10);
for (int i = 0; i < filecount; i++) {
- len = (int)strlen(files[i]);
+ int len = (int)strlen(files[i]);
if (len <= 4) {
continue;
}
@@ -1160,24 +1151,30 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
ext[1] = fname[5];
ext[2] = fname[6];
}
- helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr);
+ helptags_one(dirname, ext, fname, add_help_tags, ignore_writeerr);
}
ga_clear(&ga);
FreeWild(filecount, files);
}
-static void helptags_cb(char *fname, void *cookie)
+static bool helptags_cb(int num_fnames, char **fnames, bool all, void *cookie)
FUNC_ATTR_NONNULL_ALL
{
- do_helptags(fname, *(bool *)cookie, true);
+ for (int i = 0; i < num_fnames; i++) {
+ do_helptags(fnames[i], *(bool *)cookie, true);
+ if (!all) {
+ return true;
+ }
+ }
+
+ return num_fnames > 0;
}
/// ":helptags"
void ex_helptags(exarg_T *eap)
{
expand_T xpc;
- char *dirname;
bool add_help_tags = false;
// Check for ":helptags ++t {dir}".
@@ -1187,11 +1184,12 @@ void ex_helptags(exarg_T *eap)
}
if (strcmp(eap->arg, "ALL") == 0) {
- do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags);
+ do_in_path(p_rtp, "", "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags);
} else {
ExpandInit(&xpc);
xpc.xp_context = EXPAND_DIRECTORIES;
- dirname = ExpandOne(&xpc, eap->arg, NULL, WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
+ char *dirname =
+ ExpandOne(&xpc, eap->arg, NULL, WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
if (dirname == NULL || !os_isdir(dirname)) {
semsg(_("E150: Not a directory: %s"), eap->arg);
} else {
diff --git a/src/nvim/help.h b/src/nvim/help.h
index 21e11392ee..f60f14c748 100644
--- a/src/nvim/help.h
+++ b/src/nvim/help.h
@@ -1,11 +1,7 @@
-#ifndef NVIM_HELP_H
-#define NVIM_HELP_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "help.h.generated.h"
#endif
-#endif // NVIM_HELP_H
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 9dab91cc2b..141761c52e 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -1,17 +1,15 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// highlight.c: low level code for UI and syntax highlighting
#include <assert.h>
#include <inttypes.h>
-#include <limits.h>
+#include <lauxlib.h>
#include <string.h>
-#include "klib/kvec.h"
-#include "lauxlib.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
@@ -20,17 +18,17 @@
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
-#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/popupmenu.h"
-#include "nvim/types.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight.c.generated.h"
@@ -38,23 +36,23 @@
static bool hlstate_active = false;
-static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE;
-
-static Map(HlEntry, int) attr_entry_ids = MAP_INIT;
+static Set(HlEntry) attr_entries = SET_INIT;
static Map(int, int) combine_attr_entries = MAP_INIT;
static Map(int, int) blend_attr_entries = MAP_INIT;
static Map(int, int) blendthrough_attr_entries = MAP_INIT;
+#define attr_entry(i) attr_entries.keys[i]
+
/// highlight entries private to a namespace
static Map(ColorKey, ColorItem) ns_hls;
typedef int NSHlAttr[HLF_COUNT + 1];
-static PMap(handle_T) ns_hl_attr;
+static PMap(int) ns_hl_attr;
void highlight_init(void)
{
// index 0 is no attribute, add dummy entry:
- kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown,
- .id1 = 0, .id2 = 0 }));
+ set_put(HlEntry, &attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlInvalid,
+ .id1 = 0, .id2 = 0 }));
}
/// @return true if hl table was reset
@@ -75,6 +73,7 @@ bool highlight_use_hlstate(void)
/// @return 0 for error.
static int get_attr_entry(HlEntry entry)
{
+ bool retried = false;
if (!hlstate_active) {
// This information will not be used, erase it and reduce the table size.
entry.kind = kHlUnknown;
@@ -82,17 +81,19 @@ static int get_attr_entry(HlEntry entry)
entry.id2 = 0;
}
- int id = map_get(HlEntry, int)(&attr_entry_ids, entry);
- if (id > 0) {
- return id;
+retry: {}
+ MHPutStatus status;
+ uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status);
+ if (status == kMHExisting) {
+ return (int)k;
}
static bool recursive = false;
- if (kv_size(attr_entries) > MAX_TYPENR) {
+ if (set_size(&attr_entries) > MAX_TYPENR) {
// Running out of attribute entries! remove all attributes, and
// compute new ones for all groups.
// When called recursively, we are really out of numbers.
- if (recursive) {
+ if (recursive || retried) {
emsg(_("E424: Too many different highlighting attributes in use"));
return 0;
}
@@ -105,17 +106,12 @@ static int get_attr_entry(HlEntry entry)
// This entry is now invalid, don't put it
return 0;
}
+ retried = true;
+ goto retry;
}
- size_t next_id = kv_size(attr_entries);
- if (next_id > INT_MAX) {
- ELOG("The index on attr_entries has overflowed");
- return 0;
- }
- id = (int)next_id;
- kv_push(attr_entries, entry);
-
- map_put(HlEntry, int)(&attr_entry_ids, entry, id);
+ // new attr id, send event to remote ui:s
+ int id = (int)k;
Array inspect = hl_inspect(id);
@@ -129,10 +125,10 @@ static int get_attr_entry(HlEntry entry)
/// When a UI connects, we need to send it the table of highlights used so far.
void ui_send_all_hls(UI *ui)
{
- for (size_t i = 1; i < kv_size(attr_entries); i++) {
+ for (size_t i = 1; i < set_size(&attr_entries); i++) {
Array inspect = hl_inspect((int)i);
- remote_ui_hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
- kv_A(attr_entries, i).attr, inspect);
+ HlAttrs attr = attr_entry(i).attr;
+ remote_ui_hl_attr_define(ui, (Integer)i, attr, attr, inspect);
api_free_array(inspect);
}
for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) {
@@ -165,7 +161,7 @@ void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight)
return;
}
if ((attrs.rgb_ae_attr & HL_DEFAULT)
- && map_has(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id))) {
+ && map_has(ColorKey, &ns_hls, (ColorKey(ns_id, hl_id)))) {
return;
}
DecorProvider *p = get_decor_provider(ns_id, true);
@@ -205,7 +201,7 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
if (!valid_item && p->hl_def != LUA_NOREF && !recursive) {
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ((Integer)ns_id));
- ADD_C(args, STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id))));
+ ADD_C(args, CSTR_TO_OBJ(syn_id2name(hl_id)));
ADD_C(args, BOOLEAN_OBJ(link));
// TODO(bfredl): preload the "global" attr dict?
@@ -224,8 +220,8 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field,
ret.data.dictionary, &err)) {
attrs = dict2hlattrs(&dict, true, &it.link_id, &err);
- fallback = api_object_to_bool(dict.fallback, "fallback", true, &err);
- tmp = api_object_to_bool(dict.fallback, "tmp", false, &err);
+ fallback = GET_BOOL_OR_TRUE(&dict, highlight, fallback);
+ tmp = dict.fallback; // or false
if (it.link_id >= 0) {
fallback = true;
}
@@ -275,7 +271,7 @@ bool hl_check_ns(void)
hl_attr_active = highlight_attr;
if (ns > 0) {
update_ns_hl(ns);
- NSHlAttr *hl_def = (NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns);
+ NSHlAttr *hl_def = (NSHlAttr *)pmap_get(int)(&ns_hl_attr, ns);
if (hl_def) {
hl_attr_active = *hl_def;
}
@@ -302,7 +298,7 @@ int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
bool available = false;
if (final_id > 0) {
- int syn_attr = syn_ns_id2attr(ns_id, final_id, optional);
+ int syn_attr = syn_ns_id2attr(ns_id, final_id, &optional);
if (syn_attr > 0) {
attrs = syn_attr2entry(syn_attr);
available = true;
@@ -325,6 +321,23 @@ int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
.id1 = idx, .id2 = final_id });
}
+/// Apply 'winblend' to highlight attributes.
+///
+/// @param wp The window to get 'winblend' value from.
+/// @param attr The original attribute code.
+///
+/// @return The attribute code with 'winblend' applied.
+int hl_apply_winblend(win_T *wp, int attr)
+{
+ HlEntry entry = attr_entry(attr);
+ // if blend= attribute is not set, 'winblend' value overrides it.
+ if (entry.attr.hl_blend == -1 && wp->w_p_winbl > 0) {
+ entry.attr.hl_blend = (int)wp->w_p_winbl;
+ attr = get_attr_entry(entry);
+ }
+ return attr;
+}
+
void update_window_hl(win_T *wp, bool invalid)
{
int ns_id = wp->w_ns_hl;
@@ -333,7 +346,7 @@ void update_window_hl(win_T *wp, bool invalid)
if (ns_id != wp->w_ns_hl_active || wp->w_ns_hl_attr == NULL) {
wp->w_ns_hl_active = ns_id;
- wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns_id);
+ wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(int)(&ns_hl_attr, ns_id);
if (!wp->w_ns_hl_attr) {
// No specific highlights, use the defaults.
wp->w_ns_hl_attr = highlight_attr;
@@ -360,13 +373,8 @@ void update_window_hl(win_T *wp, bool invalid)
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
}
- // if blend= attribute is not set, 'winblend' value overrides it.
- if (wp->w_floating && wp->w_p_winbl > 0) {
- HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal);
- if (entry.attr.hl_blend == -1) {
- entry.attr.hl_blend = (int)wp->w_p_winbl;
- wp->w_hl_attr_normal = get_attr_entry(entry);
- }
+ if (wp->w_floating) {
+ wp->w_hl_attr_normal = hl_apply_winblend(wp, wp->w_hl_attr_normal);
}
wp->w_float_config.shadow = false;
@@ -376,10 +384,10 @@ void update_window_hl(win_T *wp, bool invalid)
if (wp->w_float_config.border_hl_ids[i]) {
attr = hl_get_ui_attr(ns_id, HLF_BORDER,
wp->w_float_config.border_hl_ids[i], false);
- HlAttrs a = syn_attr2entry(attr);
- if (a.hl_blend) {
- wp->w_float_config.shadow = true;
- }
+ }
+ attr = hl_apply_winblend(wp, attr);
+ if (syn_attr2entry(attr).hl_blend > 0) {
+ wp->w_float_config.shadow = true;
}
wp->w_float_config.border_attr[i] = attr;
}
@@ -396,6 +404,10 @@ void update_window_hl(win_T *wp, bool invalid)
} else {
wp->w_hl_attr_normalnc = hl_def[HLF_INACTIVE];
}
+
+ if (wp->w_floating) {
+ wp->w_hl_attr_normalnc = hl_apply_winblend(wp, wp->w_hl_attr_normalnc);
+ }
}
void update_ns_hl(int ns_id)
@@ -408,7 +420,7 @@ void update_ns_hl(int ns_id)
return;
}
- NSHlAttr **alloc = (NSHlAttr **)pmap_ref(handle_T)(&ns_hl_attr, ns_id, true);
+ NSHlAttr **alloc = (NSHlAttr **)pmap_put_ref(int)(&ns_hl_attr, ns_id, NULL, NULL);
if (*alloc == NULL) {
*alloc = xmalloc(sizeof(**alloc));
}
@@ -471,7 +483,7 @@ int hl_get_underline(void)
/// Get attribute code for forwarded :terminal highlights.
int hl_get_term_attr(HlAttrs *aep)
{
- return get_attr_entry((HlEntry){ .attr= *aep, .kind = kHlTerminal,
+ return get_attr_entry((HlEntry){ .attr = *aep, .kind = kHlTerminal,
.id1 = 0, .id2 = 0 });
}
@@ -479,33 +491,42 @@ int hl_get_term_attr(HlAttrs *aep)
void clear_hl_tables(bool reinit)
{
if (reinit) {
- kv_size(attr_entries) = 1;
- map_clear(HlEntry, int)(&attr_entry_ids);
- map_clear(int, int)(&combine_attr_entries);
- map_clear(int, int)(&blend_attr_entries);
- map_clear(int, int)(&blendthrough_attr_entries);
+ set_clear(HlEntry, &attr_entries);
+ highlight_init();
+ map_clear(int, &combine_attr_entries);
+ map_clear(int, &blend_attr_entries);
+ map_clear(int, &blendthrough_attr_entries);
memset(highlight_attr_last, -1, sizeof(highlight_attr_last));
highlight_attr_set_all();
highlight_changed();
screen_invalidate_highlights();
} else {
- kv_destroy(attr_entries);
- map_destroy(HlEntry, int)(&attr_entry_ids);
- map_destroy(int, int)(&combine_attr_entries);
- map_destroy(int, int)(&blend_attr_entries);
- map_destroy(int, int)(&blendthrough_attr_entries);
- map_destroy(ColorKey, ColorItem)(&ns_hls);
+ set_destroy(HlEntry, &attr_entries);
+ map_destroy(int, &combine_attr_entries);
+ map_destroy(int, &blend_attr_entries);
+ map_destroy(int, &blendthrough_attr_entries);
+ map_destroy(ColorKey, &ns_hls);
}
}
void hl_invalidate_blends(void)
{
- map_clear(int, int)(&blend_attr_entries);
- map_clear(int, int)(&blendthrough_attr_entries);
+ map_clear(int, &blend_attr_entries);
+ map_clear(int, &blendthrough_attr_entries);
highlight_changed();
update_window_hl(curwin, true);
}
+/// Combine HlAttrFlags.
+/// The underline attribute in "prim_ae" overrules the one in "char_ae" if both are present.
+static int16_t hl_combine_ae(int16_t char_ae, int16_t prim_ae)
+{
+ int16_t char_ul = char_ae & HL_UNDERLINE_MASK;
+ int16_t prim_ul = prim_ae & HL_UNDERLINE_MASK;
+ int16_t new_ul = prim_ul ? prim_ul : char_ul;
+ return (char_ae & ~HL_UNDERLINE_MASK) | (prim_ae & ~HL_UNDERLINE_MASK) | new_ul;
+}
+
// Combine special attributes (e.g., for spelling) with other attributes
// (e.g., for syntax highlighting).
// "prim_attr" overrules "char_attr".
@@ -536,12 +557,12 @@ int hl_combine_attr(int char_attr, int prim_attr)
if (prim_aep.cterm_ae_attr & HL_NOCOMBINE) {
new_en.cterm_ae_attr = prim_aep.cterm_ae_attr;
} else {
- new_en.cterm_ae_attr |= prim_aep.cterm_ae_attr;
+ new_en.cterm_ae_attr = hl_combine_ae(new_en.cterm_ae_attr, prim_aep.cterm_ae_attr);
}
if (prim_aep.rgb_ae_attr & HL_NOCOMBINE) {
new_en.rgb_ae_attr = prim_aep.rgb_ae_attr;
} else {
- new_en.rgb_ae_attr |= prim_aep.rgb_ae_attr;
+ new_en.rgb_ae_attr = hl_combine_ae(new_en.rgb_ae_attr, prim_aep.rgb_ae_attr);
}
if (prim_aep.cterm_fg_color > 0) {
@@ -663,7 +684,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through)
} else {
cattrs = fattrs;
if (ratio >= 50) {
- cattrs.rgb_ae_attr |= battrs.rgb_ae_attr;
+ cattrs.rgb_ae_attr = hl_combine_ae(battrs.rgb_ae_attr, cattrs.rgb_ae_attr);
}
cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color,
fattrs.rgb_fg_color);
@@ -788,11 +809,11 @@ static int hl_cterm2rgb_color(int nr)
/// Get highlight attributes for a attribute code
HlAttrs syn_attr2entry(int attr)
{
- if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
+ if (attr <= 0 || attr >= (int)set_size(&attr_entries)) {
// invalid attribute code, or the tables were cleared
return HLATTRS_INIT;
}
- return kv_A(attr_entries, attr).attr;
+ return attr_entry(attr).attr;
}
/// Gets highlight description for id `attr_id` as a map.
@@ -804,13 +825,13 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *
return dic;
}
- if (attr_id <= 0 || attr_id >= (int)kv_size(attr_entries)) {
+ if (attr_id <= 0 || attr_id >= (int)set_size(&attr_entries)) {
api_set_error(err, kErrorTypeException,
"Invalid attribute id: %" PRId64, attr_id);
return dic;
}
Dictionary retval = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&retval, syn_attr2entry((int)attr_id), rgb);
+ hlattrs2dict(&retval, NULL, syn_attr2entry((int)attr_id), rgb, false);
return retval;
}
@@ -819,101 +840,105 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *
/// @param[in/out] hl Dictionary with pre-allocated space for HLATTRS_DICT_SIZE elements
/// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
-void hlattrs2dict(Dictionary *dict, HlAttrs ae, bool use_rgb)
+/// @param short_keys change (foreground, background, special) to (fg, bg, sp) for 'gui*' settings
+/// (foreground, background) to (ctermfg, ctermbg) for 'cterm*' settings
+void hlattrs2dict(Dictionary *hl, Dictionary *hl_attrs, HlAttrs ae, bool use_rgb, bool short_keys)
{
- assert(dict->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
- Dictionary hl = *dict;
- int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr;
+ hl_attrs = hl_attrs ? hl_attrs : hl;
+ assert(hl->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
+ assert(hl_attrs->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
+ int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr;
if (mask & HL_INVERSE) {
- PUT_C(hl, "reverse", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "reverse", BOOLEAN_OBJ(true));
}
if (mask & HL_BOLD) {
- PUT_C(hl, "bold", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "bold", BOOLEAN_OBJ(true));
}
if (mask & HL_ITALIC) {
- PUT_C(hl, "italic", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "italic", BOOLEAN_OBJ(true));
}
switch (mask & HL_UNDERLINE_MASK) {
case HL_UNDERLINE:
- PUT_C(hl, "underline", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "underline", BOOLEAN_OBJ(true));
break;
- case HL_UNDERDOUBLE:
- PUT_C(hl, "underdouble", BOOLEAN_OBJ(true));
+ case HL_UNDERCURL:
+ PUT_C(*hl_attrs, "undercurl", BOOLEAN_OBJ(true));
break;
- case HL_UNDERCURL:
- PUT_C(hl, "undercurl", BOOLEAN_OBJ(true));
+ case HL_UNDERDOUBLE:
+ PUT_C(*hl_attrs, "underdouble", BOOLEAN_OBJ(true));
break;
case HL_UNDERDOTTED:
- PUT_C(hl, "underdotted", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "underdotted", BOOLEAN_OBJ(true));
break;
case HL_UNDERDASHED:
- PUT_C(hl, "underdashed", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "underdashed", BOOLEAN_OBJ(true));
break;
}
if (mask & HL_STANDOUT) {
- PUT_C(hl, "standout", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "standout", BOOLEAN_OBJ(true));
}
if (mask & HL_STRIKETHROUGH) {
- PUT_C(hl, "strikethrough", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "strikethrough", BOOLEAN_OBJ(true));
}
if (mask & HL_ALTFONT) {
- PUT_C(hl, "altfont", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "altfont", BOOLEAN_OBJ(true));
}
if (mask & HL_NOCOMBINE) {
- PUT_C(hl, "nocombine", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "nocombine", BOOLEAN_OBJ(true));
}
if (use_rgb) {
- if (mask & HL_FG_INDEXED) {
- PUT_C(hl, "fg_indexed", BOOLEAN_OBJ(true));
- }
-
- if (mask & HL_BG_INDEXED) {
- PUT_C(hl, "bg_indexed", BOOLEAN_OBJ(true));
- }
-
if (ae.rgb_fg_color != -1) {
- PUT_C(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color));
+ PUT_C(*hl, short_keys ? "fg" : "foreground", INTEGER_OBJ(ae.rgb_fg_color));
}
if (ae.rgb_bg_color != -1) {
- PUT_C(hl, "background", INTEGER_OBJ(ae.rgb_bg_color));
+ PUT_C(*hl, short_keys ? "bg" : "background", INTEGER_OBJ(ae.rgb_bg_color));
}
if (ae.rgb_sp_color != -1) {
- PUT_C(hl, "special", INTEGER_OBJ(ae.rgb_sp_color));
+ PUT_C(*hl, short_keys ? "sp" : "special", INTEGER_OBJ(ae.rgb_sp_color));
+ }
+
+ if (!short_keys) {
+ if (mask & HL_FG_INDEXED) {
+ PUT_C(*hl, "fg_indexed", BOOLEAN_OBJ(true));
+ }
+
+ if (mask & HL_BG_INDEXED) {
+ PUT_C(*hl, "bg_indexed", BOOLEAN_OBJ(true));
+ }
}
} else {
if (ae.cterm_fg_color != 0) {
- PUT_C(hl, "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1));
+ PUT_C(*hl, short_keys ? "ctermfg" : "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1));
}
if (ae.cterm_bg_color != 0) {
- PUT_C(hl, "background", INTEGER_OBJ(ae.cterm_bg_color - 1));
+ PUT_C(*hl, short_keys ? "ctermbg" : "background", INTEGER_OBJ(ae.cterm_bg_color - 1));
}
}
- if (ae.hl_blend > -1) {
- PUT_C(hl, "blend", INTEGER_OBJ(ae.hl_blend));
+ if (ae.hl_blend > -1 && (use_rgb || !short_keys)) {
+ PUT_C(*hl, "blend", INTEGER_OBJ(ae.hl_blend));
}
-
- *dict = hl;
}
HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err)
{
+#define HAS_KEY_X(d, key) HAS_KEY(d, highlight, key)
HlAttrs hlattrs = HLATTRS_INIT;
int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1;
int blend = -1;
@@ -922,16 +947,19 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
bool cterm_mask_provided = false;
#define CHECK_FLAG(d, m, name, extra, flag) \
- if (api_object_to_bool(d->name##extra, #name, false, err)) { \
- m = m | flag; \
+ if (d->name##extra) { \
+ if (flag & HL_UNDERLINE_MASK) { \
+ m &= ~HL_UNDERLINE_MASK; \
+ } \
+ m |= flag; \
}
CHECK_FLAG(dict, mask, reverse, , HL_INVERSE);
CHECK_FLAG(dict, mask, bold, , HL_BOLD);
CHECK_FLAG(dict, mask, italic, , HL_ITALIC);
CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE);
- CHECK_FLAG(dict, mask, underdouble, , HL_UNDERDOUBLE);
CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL);
+ CHECK_FLAG(dict, mask, underdouble, , HL_UNDERDOUBLE);
CHECK_FLAG(dict, mask, underdotted, , HL_UNDERDOTTED);
CHECK_FLAG(dict, mask, underdashed, , HL_UNDERDASHED);
CHECK_FLAG(dict, mask, standout, , HL_STANDOUT);
@@ -944,62 +972,56 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE);
CHECK_FLAG(dict, mask, default, _, HL_DEFAULT);
- if (HAS_KEY(dict->fg)) {
+ if (HAS_KEY_X(dict, fg)) {
fg = object_to_color(dict->fg, "fg", use_rgb, err);
- } else if (HAS_KEY(dict->foreground)) {
+ } else if (HAS_KEY_X(dict, foreground)) {
fg = object_to_color(dict->foreground, "foreground", use_rgb, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
- if (HAS_KEY(dict->bg)) {
+ if (HAS_KEY_X(dict, bg)) {
bg = object_to_color(dict->bg, "bg", use_rgb, err);
- } else if (HAS_KEY(dict->background)) {
+ } else if (HAS_KEY_X(dict, background)) {
bg = object_to_color(dict->background, "background", use_rgb, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
- if (HAS_KEY(dict->sp)) {
+ if (HAS_KEY_X(dict, sp)) {
sp = object_to_color(dict->sp, "sp", true, err);
- } else if (HAS_KEY(dict->special)) {
+ } else if (HAS_KEY_X(dict, special)) {
sp = object_to_color(dict->special, "special", true, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
- if (dict->blend.type == kObjectTypeInteger) {
- Integer blend0 = dict->blend.data.integer;
- if (blend0 < 0 || blend0 > 100) {
- api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100");
- } else {
- blend = (int)blend0;
- }
- } else if (HAS_KEY(dict->blend)) {
- api_set_error(err, kErrorTypeValidation, "'blend' must be an integer");
- }
- if (ERROR_SET(err)) {
- return hlattrs;
+ if (HAS_KEY_X(dict, blend)) {
+ Integer blend0 = dict->blend;
+ VALIDATE_RANGE((blend0 >= 0 && blend0 <= 100), "blend", {
+ return hlattrs;
+ });
+ blend = (int)blend0;
}
- if (HAS_KEY(dict->link) || HAS_KEY(dict->global_link)) {
- if (link_id) {
- if (HAS_KEY(dict->global_link)) {
- *link_id = object_to_hl_id(dict->global_link, "link", err);
- mask |= HL_GLOBAL;
- } else {
- *link_id = object_to_hl_id(dict->link, "link", err);
- }
-
- if (ERROR_SET(err)) {
- return hlattrs;
- }
- } else {
+ if (HAS_KEY_X(dict, link) || HAS_KEY_X(dict, global_link)) {
+ if (!link_id) {
api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'",
- HAS_KEY(dict->global_link) ? "global_link" : "link");
+ HAS_KEY_X(dict, global_link) ? "global_link" : "link");
+ return hlattrs;
+ }
+ if (HAS_KEY_X(dict, global_link)) {
+ *link_id = object_to_hl_id(dict->global_link, "link", err);
+ mask |= HL_GLOBAL;
+ } else {
+ *link_id = object_to_hl_id(dict->link, "link", err);
+ }
+
+ if (ERROR_SET(err)) {
+ return hlattrs;
}
}
@@ -1025,19 +1047,21 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
// empty list from Lua API should clear all cterm attributes
// TODO(clason): handle via gen_api_dispatch
cterm_mask_provided = true;
- } else if (HAS_KEY(dict->cterm)) {
- api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary.");
+ } else if (HAS_KEY_X(dict, cterm)) {
+ VALIDATE_EXP(false, "cterm", "Dict", api_typename(dict->cterm.type), {
+ return hlattrs;
+ });
}
#undef CHECK_FLAG
- if (HAS_KEY(dict->ctermfg)) {
+ if (HAS_KEY_X(dict, ctermfg)) {
ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err);
if (ERROR_SET(err)) {
return hlattrs;
}
}
- if (HAS_KEY(dict->ctermbg)) {
+ if (HAS_KEY_X(dict, ctermbg)) {
ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err);
if (ERROR_SET(err)) {
return hlattrs;
@@ -1064,6 +1088,7 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
}
return hlattrs;
+#undef HAS_KEY_X
}
int object_to_color(Object val, char *key, bool rgb, Error *err)
@@ -1083,13 +1108,14 @@ int object_to_color(Object val, char *key, bool rgb, Error *err)
} else {
color = name_to_ctermcolor(str.data);
}
- if (color < 0) {
- api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data);
- }
+ VALIDATE_S((color >= 0), "highlight color", str.data, {
+ return color;
+ });
return color;
} else {
- api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key);
- return 0;
+ VALIDATE_EXP(false, key, "String or Integer", NULL, {
+ return 0;
+ });
}
}
@@ -1106,28 +1132,28 @@ Array hl_inspect(int attr)
static void hl_inspect_impl(Array *arr, int attr)
{
Dictionary item = ARRAY_DICT_INIT;
- if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
+ if (attr <= 0 || attr >= (int)set_size(&attr_entries)) {
return;
}
- HlEntry e = kv_A(attr_entries, attr);
+ HlEntry e = attr_entry(attr);
switch (e.kind) {
case kHlSyntax:
- PUT(item, "kind", STRING_OBJ(cstr_to_string("syntax")));
+ PUT(item, "kind", CSTR_TO_OBJ("syntax"));
PUT(item, "hi_name",
- STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id1))));
+ CSTR_TO_OBJ(syn_id2name(e.id1)));
break;
case kHlUI:
- PUT(item, "kind", STRING_OBJ(cstr_to_string("ui")));
+ PUT(item, "kind", CSTR_TO_OBJ("ui"));
const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1];
- PUT(item, "ui_name", STRING_OBJ(cstr_to_string(ui_name)));
+ PUT(item, "ui_name", CSTR_TO_OBJ(ui_name));
PUT(item, "hi_name",
- STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id2))));
+ CSTR_TO_OBJ(syn_id2name(e.id2)));
break;
case kHlTerminal:
- PUT(item, "kind", STRING_OBJ(cstr_to_string("term")));
+ PUT(item, "kind", CSTR_TO_OBJ("term"));
break;
case kHlCombine:
@@ -1139,6 +1165,7 @@ static void hl_inspect_impl(Array *arr, int attr)
return;
case kHlUnknown:
+ case kHlInvalid:
return;
}
PUT(item, "id", INTEGER_OBJ(attr));
diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h
index 4da7dd65bb..e5d3f3d1ca 100644
--- a/src/nvim/highlight.h
+++ b/src/nvim/highlight.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_HIGHLIGHT_H
-#define NVIM_HIGHLIGHT_H
+#pragma once
#include <stdbool.h>
-#include "nvim/api/private/defs.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/option_defs.h"
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/highlight_defs.h" // IWYU pragma: export
+#include "nvim/option_vars.h"
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -30,4 +30,5 @@ static inline int win_hl_attr(win_T *wp, int hlf)
rgb_sp = rgb_sp != -1 ? rgb_sp : 0xFF0000; \
} while (0);
-#endif // NVIM_HIGHLIGHT_H
+// Enums need a typecast to be used as array index.
+#define HL_ATTR(n) hl_attr_active[(int)(n)]
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index a4dcf6eb60..24070199ee 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_HIGHLIGHT_DEFS_H
-#define NVIM_HIGHLIGHT_DEFS_H
+#pragma once
#include <inttypes.h>
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
typedef int32_t RgbValue;
@@ -18,8 +17,8 @@ typedef enum {
// The next three bits are all underline styles
HL_UNDERLINE_MASK = 0x38,
HL_UNDERLINE = 0x08,
- HL_UNDERDOUBLE = 0x10,
- HL_UNDERCURL = 0x18,
+ HL_UNDERCURL = 0x10,
+ HL_UNDERDOUBLE = 0x18,
HL_UNDERDOTTED = 0x20,
HL_UNDERDASHED = 0x28,
// 0x30 and 0x38 spare for underline styles
@@ -100,6 +99,10 @@ typedef enum {
HLF_SPL, // SpellLocal
HLF_PNI, // popup menu normal item
HLF_PSI, // popup menu selected item
+ HLF_PNK, // popup menu normal item "kind"
+ HLF_PSK, // popup menu selected item "kind"
+ HLF_PNX, // popup menu normal item "menu" (extra text)
+ HLF_PSX, // popup menu selected item "menu" (extra text)
HLF_PSB, // popup menu scrollbar
HLF_PST, // popup menu scrollbar thumb
HLF_TP, // tabpage line
@@ -119,10 +122,11 @@ typedef enum {
HLF_WBRNC, // Window bars of not-current windows
HLF_CU, // Cursor
HLF_BTITLE, // Float Border Title
+ HLF_BFOOTER, // Float Border Footer
HLF_COUNT, // MUST be the last one
} hlf_T;
-EXTERN const char *hlf_names[] INIT(= {
+EXTERN const char *hlf_names[] INIT( = {
[HLF_8] = "SpecialKey",
[HLF_EOB] = "EndOfBuffer",
[HLF_TERM] = "TermCursor",
@@ -165,6 +169,10 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_SPL] = "SpellLocal",
[HLF_PNI] = "Pmenu",
[HLF_PSI] = "PmenuSel",
+ [HLF_PNK] = "PmenuKind",
+ [HLF_PSK] = "PmenuKindSel",
+ [HLF_PNX] = "PmenuExtra",
+ [HLF_PSX] = "PmenuExtraSel",
[HLF_PSB] = "PmenuSbar",
[HLF_PST] = "PmenuThumb",
[HLF_TP] = "TabLine",
@@ -184,24 +192,25 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_WBRNC] = "WinBarNC",
[HLF_CU] = "Cursor",
[HLF_BTITLE] = "FloatTitle",
+ [HLF_BFOOTER] = "FloatFooter",
});
EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context.
EXTERN int highlight_attr_last[HLF_COUNT]; // copy for detecting changed groups
EXTERN int highlight_user[9]; // User[1-9] attributes
EXTERN int highlight_stlnc[9]; // On top of user
-EXTERN int cterm_normal_fg_color INIT(= 0);
-EXTERN int cterm_normal_bg_color INIT(= 0);
-EXTERN RgbValue normal_fg INIT(= -1);
-EXTERN RgbValue normal_bg INIT(= -1);
-EXTERN RgbValue normal_sp INIT(= -1);
+EXTERN int cterm_normal_fg_color INIT( = 0);
+EXTERN int cterm_normal_bg_color INIT( = 0);
+EXTERN RgbValue normal_fg INIT( = -1);
+EXTERN RgbValue normal_bg INIT( = -1);
+EXTERN RgbValue normal_sp INIT( = -1);
-EXTERN NS ns_hl_global INIT(= 0); // global highlight namespace
-EXTERN NS ns_hl_win INIT(= -1); // highlight namespace for the current window
-EXTERN NS ns_hl_fast INIT(= -1); // highlight namespace specified in a fast callback
-EXTERN NS ns_hl_active INIT(= 0); // currently active/cached namespace
+EXTERN NS ns_hl_global INIT( = 0); // global highlight namespace
+EXTERN NS ns_hl_win INIT( = -1); // highlight namespace for the current window
+EXTERN NS ns_hl_fast INIT( = -1); // highlight namespace specified in a fast callback
+EXTERN NS ns_hl_active INIT( = 0); // currently active/cached namespace
-EXTERN int *hl_attr_active INIT(= highlight_attr);
+EXTERN int *hl_attr_active INIT( = highlight_attr);
typedef enum {
kHlUnknown,
@@ -211,6 +220,7 @@ typedef enum {
kHlCombine,
kHlBlend,
kHlBlendThrough,
+ kHlInvalid,
} HlKind;
typedef struct {
@@ -236,11 +246,3 @@ typedef struct {
} ColorItem;
#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1, \
.is_default = false, .link_global = false }
-
-/// highlight attributes with associated priorities
-typedef struct {
- int attr_id;
- int priority;
-} HlPriAttr;
-
-#endif // NVIM_HIGHLIGHT_DEFS_H
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 5b1ea9967d..3f1758894e 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// highlight_group.c: code for managing highlight groups
#include <ctype.h>
@@ -10,38 +7,42 @@
#include <stdlib.h>
#include <string.h>
+#include "klib/kvec.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor_shape.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
-#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
/// \addtogroup SG_SET
/// @{
@@ -118,6 +119,17 @@ enum {
# include "highlight_group.c.generated.h"
#endif
+static const char e_highlight_group_name_not_found_str[]
+ = N_("E411: Highlight group not found: %s");
+static const char e_group_has_settings_highlight_link_ignored[]
+ = N_("E414: Group has settings, highlight link ignored");
+static const char e_unexpected_equal_sign_str[]
+ = N_("E415: Unexpected equal sign: %s");
+static const char e_missing_equal_sign_str_2[]
+ = N_("E416: Missing equal sign: %s");
+static const char e_missing_argument_str[]
+ = N_("E417: Missing argument: %s");
+
#define hl_table ((HlGroup *)((highlight_ga.ga_data)))
// The default highlight groups. These are compiled-in for fast startup and
@@ -150,12 +162,18 @@ static const char *highlight_init_both[] = {
"default link QuickFixLine Search",
"default link CursorLineSign SignColumn",
"default link CursorLineFold FoldColumn",
+ "default link CurSearch Search",
+ "default link PmenuKind Pmenu",
+ "default link PmenuKindSel PmenuSel",
+ "default link PmenuExtra Pmenu",
+ "default link PmenuExtraSel PmenuSel",
"default link Substitute Search",
"default link Whitespace NonText",
"default link MsgSeparator StatusLine",
"default link NormalFloat Pmenu",
"default link FloatBorder WinSeparator",
"default link FloatTitle Title",
+ "default link FloatFooter Title",
"default FloatShadow blend=80 guibg=Black",
"default FloatShadowThrough blend=100 guibg=Black",
"RedrawDebugNormal cterm=reverse gui=reverse",
@@ -213,6 +231,10 @@ static const char *highlight_init_both[] = {
"default link DiagnosticSignInfo DiagnosticInfo",
"default link DiagnosticSignHint DiagnosticHint",
"default link DiagnosticSignOk DiagnosticOk",
+ "default DiagnosticDeprecated cterm=strikethrough gui=strikethrough guisp=Red",
+ "default link DiagnosticUnnecessary Comment",
+ "default link LspInlayHint NonText",
+ "default link SnippetTabstop Visual",
// Text
"default link @text.literal Comment",
@@ -270,16 +292,23 @@ static const char *highlight_init_both[] = {
"default link @tag Tag",
// LSP semantic tokens
- "default link @class Structure",
- "default link @struct Structure",
- "default link @enum Type",
- "default link @enumMember Constant",
- "default link @event Identifier",
- "default link @interface Identifier",
- "default link @modifier Identifier",
- "default link @regexp SpecialChar",
- "default link @typeParameter Type",
- "default link @decorator Identifier",
+ "default link @lsp.type.class Structure",
+ "default link @lsp.type.comment Comment",
+ "default link @lsp.type.decorator Function",
+ "default link @lsp.type.enum Structure",
+ "default link @lsp.type.enumMember Constant",
+ "default link @lsp.type.function Function",
+ "default link @lsp.type.interface Structure",
+ "default link @lsp.type.macro Macro",
+ "default link @lsp.type.method Function",
+ "default link @lsp.type.namespace Structure",
+ "default link @lsp.type.parameter Identifier",
+ "default link @lsp.type.property Identifier",
+ "default link @lsp.type.struct Structure",
+ "default link @lsp.type.type Type",
+ "default link @lsp.type.typeParameter TypeDef",
+ "default link @lsp.type.variable Identifier",
+
NULL
};
@@ -671,12 +700,8 @@ int load_colors(char *name)
size_t buflen = strlen(name) + 12;
char *buf = xmalloc(buflen);
apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf);
- snprintf(buf, buflen, "colors/%s.vim", name);
- int retval = source_runtime(buf, DIP_START + DIP_OPT);
- if (retval == FAIL) {
- snprintf(buf, buflen, "colors/%s.lua", name);
- retval = source_runtime(buf, DIP_START + DIP_OPT);
- }
+ snprintf(buf, buflen, "colors/%s.*", name);
+ int retval = source_runtime_vim_lua(buf, DIP_START + DIP_OPT);
xfree(buf);
if (retval == OK) {
apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf);
@@ -775,18 +800,17 @@ int lookup_color(const int idx, const bool foreground, TriState *const boldp)
void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
{
int idx = id - 1; // Index is ID minus one.
-
bool is_default = attrs.rgb_ae_attr & HL_DEFAULT;
// Return if "default" was used and the group already has settings
- if (is_default && hl_has_settings(idx, true)) {
+ if (is_default && hl_has_settings(idx, true) && !dict->force) {
return;
}
HlGroup *g = &hl_table[idx];
+ g->sg_cleared = false;
if (link_id > 0) {
- g->sg_cleared = false;
g->sg_link = link_id;
g->sg_script_ctx = current_sctx;
g->sg_script_ctx.sc_lnum += SOURCING_LNUM;
@@ -796,12 +820,11 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
g->sg_deflink_sctx = current_sctx;
g->sg_deflink_sctx.sc_lnum += SOURCING_LNUM;
}
- goto update;
+ } else {
+ g->sg_link = 0;
}
- g->sg_cleared = false;
- g->sg_link = 0;
- g->sg_gui = attrs.rgb_ae_attr;
+ g->sg_gui = attrs.rgb_ae_attr &~HL_DEFAULT;
g->sg_rgb_fg = attrs.rgb_fg_color;
g->sg_rgb_bg = attrs.rgb_bg_color;
@@ -810,9 +833,11 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
struct {
int *dest; RgbValue val; Object name;
} cattrs[] = {
- { &g->sg_rgb_fg_idx, g->sg_rgb_fg, HAS_KEY(dict->fg) ? dict->fg : dict->foreground },
- { &g->sg_rgb_bg_idx, g->sg_rgb_bg, HAS_KEY(dict->bg) ? dict->bg : dict->background },
- { &g->sg_rgb_sp_idx, g->sg_rgb_sp, HAS_KEY(dict->sp) ? dict->sp : dict->special },
+ { &g->sg_rgb_fg_idx, g->sg_rgb_fg,
+ HAS_KEY(dict, highlight, fg) ? dict->fg : dict->foreground },
+ { &g->sg_rgb_bg_idx, g->sg_rgb_bg,
+ HAS_KEY(dict, highlight, bg) ? dict->bg : dict->background },
+ { &g->sg_rgb_sp_idx, g->sg_rgb_sp, HAS_KEY(dict, highlight, sp) ? dict->sp : dict->special },
{ NULL, -1, NIL },
};
@@ -826,7 +851,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
}
}
- g->sg_cterm = attrs.cterm_ae_attr;
+ g->sg_cterm = attrs.cterm_ae_attr &~HL_DEFAULT;
g->sg_cterm_bg = attrs.cterm_bg_color;
g->sg_cterm_fg = attrs.cterm_fg_color;
g->sg_cterm_bold = g->sg_cterm & HL_BOLD;
@@ -841,9 +866,17 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
if (strcmp(g->sg_name_u, "NORMAL") == 0) {
cterm_normal_fg_color = g->sg_cterm_fg;
cterm_normal_bg_color = g->sg_cterm_bg;
+ bool did_changed = false;
+ if (normal_bg != g->sg_rgb_bg || normal_fg != g->sg_rgb_fg || normal_sp != g->sg_rgb_sp) {
+ did_changed = true;
+ }
normal_fg = g->sg_rgb_fg;
normal_bg = g->sg_rgb_bg;
normal_sp = g->sg_rgb_sp;
+
+ if (did_changed) {
+ highlight_attr_set_all();
+ }
ui_default_colors_set();
} else {
// a cursor style uses this syn_id, make sure its attribute is updated.
@@ -852,7 +885,6 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
}
}
-update:
if (!updating_screen) {
redraw_all_later(UPD_NOT_VALID);
}
@@ -883,15 +915,15 @@ void do_highlight(const char *line, const bool forceit, const bool init)
bool dodefault = false;
// Isolate the name.
- const char *name_end = (const char *)skiptowhite(line);
- const char *linep = (const char *)skipwhite(name_end);
+ const char *name_end = skiptowhite(line);
+ const char *linep = skipwhite(name_end);
// Check for "default" argument.
if (strncmp(line, "default", (size_t)(name_end - line)) == 0) {
dodefault = true;
line = linep;
- name_end = (const char *)skiptowhite(line);
- linep = (const char *)skipwhite(name_end);
+ name_end = skiptowhite(line);
+ linep = skipwhite(name_end);
}
bool doclear = false;
@@ -908,7 +940,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) {
int id = syn_name2id_len(line, (size_t)(name_end - line));
if (id == 0) {
- semsg(_("E411: highlight group not found: %s"), line);
+ semsg(_(e_highlight_group_name_not_found_str), line);
} else {
highlight_list_one(id);
}
@@ -925,9 +957,9 @@ void do_highlight(const char *line, const bool forceit, const bool init)
int to_id;
HlGroup *hlgroup = NULL;
- from_end = (const char *)skiptowhite(from_start);
- to_start = (const char *)skipwhite(from_end);
- to_end = (const char *)skiptowhite(to_start);
+ from_end = skiptowhite(from_start);
+ to_start = skipwhite(from_end);
+ to_end = skiptowhite(to_start);
if (ends_excmd((uint8_t)(*from_start))
|| ends_excmd((uint8_t)(*to_start))) {
@@ -964,7 +996,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (to_id > 0 && !forceit && !init
&& hl_has_settings(from_id - 1, dodefault)) {
if (SOURCING_NAME == NULL && !dodefault) {
- emsg(_("E414: group has settings, highlight link ignored"));
+ emsg(_(e_group_has_settings_highlight_link_ignored));
}
} else if (hlgroup->sg_link != to_id
|| hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid
@@ -991,7 +1023,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// ":highlight clear [group]" command.
line = linep;
if (ends_excmd((uint8_t)(*line))) {
- do_unlet(S_LEN("colors_name"), true);
+ do_unlet(S_LEN("g:colors_name"), true);
restore_cterm_colors();
// Clear all default highlight groups and load the defaults.
@@ -1003,8 +1035,8 @@ void do_highlight(const char *line, const bool forceit, const bool init)
redraw_all_later(UPD_NOT_VALID);
return;
}
- name_end = (const char *)skiptowhite(line);
- linep = (const char *)skipwhite(name_end);
+ name_end = skiptowhite(line);
+ linep = skipwhite(name_end);
}
// Find the group name in the table. If it does not exist yet, add it.
@@ -1042,7 +1074,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
while (!ends_excmd((uint8_t)(*linep))) {
const char *key_start = linep;
if (*linep == '=') {
- semsg(_("E415: unexpected equal sign: %s"), key_start);
+ semsg(_(e_unexpected_equal_sign_str), key_start);
error = true;
break;
}
@@ -1061,7 +1093,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
memcpy(key, key_start, key_len);
key[key_len] = NUL;
vim_strup(key);
- linep = (const char *)skipwhite(linep);
+ linep = skipwhite(linep);
if (strcmp(key, "NONE") == 0) {
if (!init || hl_table[idx].sg_set == 0) {
@@ -1075,14 +1107,14 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// Check for the equal sign.
if (*linep != '=') {
- semsg(_("E416: missing equal sign: %s"), key_start);
+ semsg(_(e_missing_equal_sign_str_2), key_start);
error = true;
break;
}
linep++;
// Isolate the argument.
- linep = (const char *)skipwhite(linep);
+ linep = skipwhite(linep);
if (*linep == '\'') { // guifg='color name'
arg_start = ++linep;
linep = strchr(linep, '\'');
@@ -1093,10 +1125,10 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
} else {
arg_start = linep;
- linep = (const char *)skiptowhite(linep);
+ linep = skiptowhite(linep);
}
if (linep == arg_start) {
- semsg(_("E417: missing argument: %s"), key_start);
+ semsg(_(e_missing_argument_str), key_start);
error = true;
break;
}
@@ -1124,6 +1156,9 @@ void do_highlight(const char *line, const bool forceit, const bool init)
for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) {
int len = (int)strlen(hl_name_table[i]);
if (STRNICMP(arg + off, hl_name_table[i], len) == 0) {
+ if (hl_attr_table[i] & HL_UNDERLINE_MASK) {
+ attr &= ~HL_UNDERLINE_MASK;
+ }
attr |= hl_attr_table[i];
off += len;
break;
@@ -1246,7 +1281,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (dark != -1
&& dark != (*p_bg == 'd')
&& !option_was_set("bg")) {
- set_option_value_give_err("bg", 0L, (dark ? "dark" : "light"), 0);
+ set_option_value_give_err("bg", CSTR_AS_OPTVAL(dark ? "dark" : "light"), 0);
reset_option_was_set("bg");
}
}
@@ -1346,7 +1381,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
// Continue with next argument.
- linep = (const char *)skipwhite(linep);
+ linep = skipwhite(linep);
}
}
@@ -1394,7 +1429,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
void free_highlight(void)
{
ga_clear(&highlight_ga);
- map_destroy(cstr_t, int)(&highlight_unames);
+ map_destroy(cstr_t, &highlight_unames);
arena_mem_free(arena_finish(&highlight_arena));
}
@@ -1414,7 +1449,7 @@ void restore_cterm_colors(void)
/// @param check_link if true also check for an existing link.
///
/// @return true if highlight group "idx" has any settings.
-static int hl_has_settings(int idx, bool check_link)
+static bool hl_has_settings(int idx, bool check_link)
{
return hl_table[idx].sg_cleared == 0
&& (hl_table[idx].sg_attr != 0
@@ -1497,7 +1532,7 @@ static void highlight_list_one(const int id)
didh = true;
msg_puts_attr("links to", HL_ATTR(HLF_D));
msg_putchar(' ');
- msg_outtrans(hl_table[hl_table[id - 1].sg_link - 1].sg_name);
+ msg_outtrans(hl_table[hl_table[id - 1].sg_link - 1].sg_name, 0);
}
if (!didh) {
@@ -1508,24 +1543,79 @@ static void highlight_list_one(const int id)
}
}
-Dictionary get_global_hl_defs(Arena *arena)
+static bool hlgroup2dict(Dictionary *hl, NS ns_id, int hl_id, Arena *arena)
+{
+ HlGroup *sgp = &hl_table[hl_id - 1];
+ int link = ns_id == 0 ? sgp->sg_link : ns_get_hl(&ns_id, hl_id, true, sgp->sg_set);
+ if (link == -1) {
+ return false;
+ }
+ if (ns_id == 0 && sgp->sg_cleared && sgp->sg_set == 0) {
+ // table entry was created but not ever set
+ return false;
+ }
+ HlAttrs attr =
+ syn_attr2entry(ns_id == 0 ? sgp->sg_attr : ns_get_hl(&ns_id, hl_id, false, sgp->sg_set));
+ *hl = arena_dict(arena, HLATTRS_DICT_SIZE + 1);
+ if (attr.rgb_ae_attr & HL_DEFAULT) {
+ PUT_C(*hl, "default", BOOLEAN_OBJ(true));
+ }
+ if (link > 0) {
+ PUT_C(*hl, "link", CSTR_AS_OBJ(hl_table[link - 1].sg_name));
+ }
+ Dictionary hl_cterm = arena_dict(arena, HLATTRS_DICT_SIZE);
+ hlattrs2dict(hl, NULL, attr, true, true);
+ hlattrs2dict(hl, &hl_cterm, attr, false, true);
+ if (kv_size(hl_cterm)) {
+ PUT_C(*hl, "cterm", DICTIONARY_OBJ(hl_cterm));
+ }
+ return true;
+}
+
+Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
{
+ Boolean link = GET_BOOL_OR_TRUE(opts, get_highlight, link);
+ int id = -1;
+ if (HAS_KEY(opts, get_highlight, name)) {
+ Boolean create = GET_BOOL_OR_TRUE(opts, get_highlight, create);
+ id = create ? syn_check_group(opts->name.data, opts->name.size)
+ : syn_name2id_len(opts->name.data, opts->name.size);
+ if (id == 0 && !create) {
+ Dictionary attrs = ARRAY_DICT_INIT;
+ return attrs;
+ }
+ } else if (HAS_KEY(opts, get_highlight, id)) {
+ id = (int)opts->id;
+ }
+
+ if (id != -1) {
+ VALIDATE(1 <= id && id <= highlight_ga.ga_len, "%s", "Highlight id out of bounds", {
+ goto cleanup;
+ });
+ Dictionary attrs = ARRAY_DICT_INIT;
+ hlgroup2dict(&attrs, ns_id, link ? id : syn_get_final_id(id), arena);
+ return attrs;
+ }
+ if (ERROR_SET(err)) {
+ goto cleanup;
+ }
+
Dictionary rv = arena_dict(arena, (size_t)highlight_ga.ga_len);
for (int i = 1; i <= highlight_ga.ga_len; i++) {
Dictionary attrs = ARRAY_DICT_INIT;
- HlGroup *h = &hl_table[i - 1];
- if (h->sg_attr > 0) {
- attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&attrs, syn_attr2entry(h->sg_attr), true);
- } else if (h->sg_link > 0) {
- attrs = arena_dict(arena, 1);
- char *link = hl_table[h->sg_link - 1].sg_name;
- PUT_C(attrs, "link", STRING_OBJ(cstr_as_string(link)));
+ if (!hlgroup2dict(&attrs, ns_id, i, arena)) {
+ continue;
}
- PUT_C(rv, (char *)h->sg_name, DICTIONARY_OBJ(attrs));
+ PUT_C(rv, hl_table[(link ? i : syn_get_final_id(i)) - 1].sg_name, DICTIONARY_OBJ(attrs));
}
return rv;
+
+cleanup:
+ api_free_integer(id);
+ api_free_boolean(link);
+ Dictionary empty = ARRAY_DICT_INIT;
+ return empty;
}
/// Outputs a highlight when doing ":hi MyHighlight"
@@ -1547,30 +1637,35 @@ static bool highlight_list_arg(const int id, bool didh, const int type, int iarg
char buf[100];
const char *ts = buf;
if (type == LIST_INT) {
- snprintf((char *)buf, sizeof(buf), "%d", iarg - 1);
+ snprintf(buf, sizeof(buf), "%d", iarg - 1);
} else if (type == LIST_STRING) {
ts = sarg;
} else { // type == LIST_ATTR
buf[0] = NUL;
for (int i = 0; hl_attr_table[i] != 0; i++) {
- if (iarg & hl_attr_table[i]) {
+ if (((hl_attr_table[i] & HL_UNDERLINE_MASK)
+ && ((iarg & HL_UNDERLINE_MASK) == hl_attr_table[i]))
+ || (!(hl_attr_table[i] & HL_UNDERLINE_MASK)
+ && (iarg & hl_attr_table[i]))) {
if (buf[0] != NUL) {
xstrlcat(buf, ",", 100);
}
xstrlcat(buf, hl_name_table[i], 100);
- iarg &= ~hl_attr_table[i]; // don't want "inverse"
+ if (!(hl_attr_table[i] & HL_UNDERLINE_MASK)) {
+ iarg &= ~hl_attr_table[i]; // don't want "inverse"
+ }
}
}
}
- (void)syn_list_header(didh, vim_strsize((char *)ts) + (int)strlen(name) + 1, id, false);
+ (void)syn_list_header(didh, vim_strsize(ts) + (int)strlen(name) + 1, id, false);
didh = true;
if (!got_int) {
if (*name != NUL) {
msg_puts_attr(name, HL_ATTR(HLF_D));
msg_puts_attr("=", HL_ATTR(HLF_D));
}
- msg_outtrans((char *)ts);
+ msg_outtrans(ts, 0);
}
return didh;
}
@@ -1700,7 +1795,7 @@ bool syn_list_header(const bool did_header, const int outlen, const int id, bool
if (got_int) {
return true;
}
- msg_outtrans(hl_table[id - 1].sg_name);
+ msg_outtrans(hl_table[id - 1].sg_name, 0);
name_col = msg_col;
endcol = 15;
} else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) {
@@ -1856,13 +1951,13 @@ int syn_check_group(const char *name, size_t len)
/// @see syn_check_group
static int syn_add_group(const char *name, size_t len)
{
- // Check that the name is ASCII letters, digits and underscore.
+ // Check that the name is valid (ASCII letters, digits, '_', '.', '@', '-').
for (size_t i = 0; i < len; i++) {
int c = (uint8_t)name[i];
if (!vim_isprintc(c)) {
emsg(_("E669: Unprintable character in group name"));
return 0;
- } else if (!ASCII_ISALNUM(c) && c != '_' && c != '.' && c != '@') {
+ } else if (!ASCII_ISALNUM(c) && c != '_' && c != '.' && c != '@' && c != '-') {
// '.' and '@' are allowed characters for use with treesitter capture names.
msg_source(HL_ATTR(HLF_W));
emsg(_(e_highlight_group_name_invalid_char));
@@ -1919,18 +2014,23 @@ static int syn_add_group(const char *name, size_t len)
/// @see syn_attr2entry
int syn_id2attr(int hl_id)
{
- return syn_ns_id2attr(-1, hl_id, false);
+ bool optional = false;
+ return syn_ns_id2attr(-1, hl_id, &optional);
}
-int syn_ns_id2attr(int ns_id, int hl_id, bool optional)
+int syn_ns_id2attr(int ns_id, int hl_id, bool *optional)
+ FUNC_ATTR_NONNULL_ALL
{
- hl_id = syn_ns_get_final_id(&ns_id, hl_id);
+ if (syn_ns_get_final_id(&ns_id, &hl_id)) {
+ // If the namespace explicitly defines a group to be empty, it is not optional
+ *optional = false;
+ }
HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one
int attr = ns_get_hl(&ns_id, hl_id, false, sgp->sg_set);
// if a highlight group is optional, don't use the global value
- if (attr >= 0 || (optional && ns_id > 0)) {
+ if (attr >= 0 || (*optional && ns_id > 0)) {
return attr;
}
return sgp->sg_attr;
@@ -1939,16 +2039,20 @@ int syn_ns_id2attr(int ns_id, int hl_id, bool optional)
/// Translate a group ID to the final group ID (following links).
int syn_get_final_id(int hl_id)
{
- int id = curwin->w_ns_hl_active;
- return syn_ns_get_final_id(&id, hl_id);
+ int ns_id = curwin->w_ns_hl_active;
+ syn_ns_get_final_id(&ns_id, &hl_id);
+ return hl_id;
}
-int syn_ns_get_final_id(int *ns_id, int hl_id)
+bool syn_ns_get_final_id(int *ns_id, int *hl_idp)
{
int count;
+ int hl_id = *hl_idp;
+ bool used = false;
if (hl_id > highlight_ga.ga_len || hl_id < 1) {
- return 0; // Can be called from eval!!
+ *hl_idp = 0;
+ return false; // Can be called from eval!!
}
// Follow links until there is no more.
@@ -1961,8 +2065,10 @@ int syn_ns_get_final_id(int *ns_id, int hl_id)
// syn_id2attr time
int check = ns_get_hl(ns_id, hl_id, true, sgp->sg_set);
if (check == 0) {
- return hl_id; // how dare! it broke the link!
+ *hl_idp = hl_id;
+ return true; // how dare! it broke the link!
} else if (check > 0) {
+ used = true;
hl_id = check;
continue;
}
@@ -1976,7 +2082,8 @@ int syn_ns_get_final_id(int *ns_id, int hl_id)
}
}
- return hl_id;
+ *hl_idp = hl_id;
+ return used;
}
/// Refresh the color attributes of all highlight groups.
@@ -2059,15 +2166,15 @@ void highlight_changed(void)
abort();
}
int ns_id = -1;
- int final_id = syn_ns_get_final_id(&ns_id, id);
+ int final_id = id;
+ syn_ns_get_final_id(&ns_id, &final_id);
if (hlf == HLF_SNC) {
id_SNC = final_id;
} else if (hlf == HLF_S) {
id_S = final_id;
}
- highlight_attr[hlf] = hl_get_ui_attr(ns_id, hlf, final_id,
- (hlf == HLF_INACTIVE || hlf == HLF_LC));
+ highlight_attr[hlf] = hl_get_ui_attr(ns_id, hlf, final_id, hlf == HLF_INACTIVE);
if (highlight_attr[hlf] != highlight_attr_last[hlf]) {
if (hlf == HLF_MSG) {
@@ -2129,7 +2236,7 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg)
}
// (part of) subcommand already typed
- const char *p = (const char *)skiptowhite(arg);
+ const char *p = skiptowhite(arg);
if (*p == NUL) {
return;
}
@@ -2137,9 +2244,9 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg)
// past "default" or group name
include_default = 0;
if (strncmp("default", arg, (unsigned)(p - arg)) == 0) {
- arg = (const char *)skipwhite(p);
+ arg = skipwhite(p);
xp->xp_pattern = (char *)arg;
- p = (const char *)skiptowhite(arg);
+ p = skiptowhite(arg);
}
if (*p == NUL) {
return;
@@ -2153,10 +2260,10 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg)
if (strncmp("link", arg, (unsigned)(p - arg)) == 0
|| strncmp("clear", arg, (unsigned)(p - arg)) == 0) {
xp->xp_pattern = skipwhite(p);
- p = (const char *)skiptowhite(xp->xp_pattern);
+ p = skiptowhite(xp->xp_pattern);
if (*p != NUL) { // past first group name
xp->xp_pattern = skipwhite(p);
- p = (const char *)skiptowhite(xp->xp_pattern);
+ p = skiptowhite(xp->xp_pattern);
}
}
if (*p != NUL) { // past group name(s)
@@ -2180,14 +2287,14 @@ static void highlight_list_two(int cnt, int attr)
msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr);
msg_clr_eos();
ui_flush();
- os_delay(cnt == 99 ? 40L : (uint64_t)cnt * 50L, false);
+ os_delay(cnt == 99 ? 40 : (uint64_t)cnt * 50, false);
}
/// Function given to ExpandGeneric() to obtain the list of group names.
-const char *get_highlight_name(expand_T *const xp, int idx)
+char *get_highlight_name(expand_T *const xp, int idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
- return get_highlight_name_ext(xp, idx, true);
+ return (char *)get_highlight_name_ext(xp, idx, true);
}
/// Obtain a highlight group name.
@@ -2219,7 +2326,7 @@ const char *get_highlight_name_ext(expand_T *xp, int idx, bool skip_cleared)
} else if (idx >= highlight_ga.ga_len) {
return NULL;
}
- return (const char *)hl_table[idx].sg_name;
+ return hl_table[idx].sg_name;
}
color_name_table_T color_name_table[] = {
@@ -2920,7 +3027,7 @@ RgbValue name_to_color(const char *name, int *idx)
&& isxdigit((uint8_t)name[6]) && name[7] == NUL) {
// rgb hex string
*idx = kColorIdxHex;
- return (RgbValue)strtol((char *)(name + 1), NULL, 16);
+ return (RgbValue)strtol(name + 1, NULL, 16);
} else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) {
*idx = kColorIdxBg;
return normal_bg;
diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h
index bf6bad1a86..ca7bd36271 100644
--- a/src/nvim/highlight_group.h
+++ b/src/nvim/highlight_group.h
@@ -1,9 +1,11 @@
-#ifndef NVIM_HIGHLIGHT_GROUP_H
-#define NVIM_HIGHLIGHT_GROUP_H
+#pragma once
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/api/private/helpers.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/highlight_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
#define MAX_HL_ID 20000 // maximum value for a highlight ID.
@@ -16,5 +18,3 @@ extern color_name_table_T color_name_table[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight_group.h.generated.h"
#endif
-
-#endif // NVIM_HIGHLIGHT_GROUP_H
diff --git a/src/nvim/iconv.h b/src/nvim/iconv_defs.h
index f5f3f25786..b6456b94ba 100644
--- a/src/nvim/iconv.h
+++ b/src/nvim/iconv_defs.h
@@ -1,11 +1,8 @@
-#ifndef NVIM_ICONV_H
-#define NVIM_ICONV_H
+#pragma once
#include <errno.h>
#include <iconv.h>
-#include "auto/config.h"
-
// define some missing constants if necessary
#ifndef EILSEQ
# define EILSEQ 123
@@ -14,5 +11,3 @@
#define ICONV_E2BIG E2BIG
#define ICONV_EINVAL EINVAL
#define ICONV_EILSEQ EILSEQ
-
-#endif // NVIM_ICONV_H
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index ec6c72da6d..348f3a6528 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -1,14 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
@@ -20,6 +17,7 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
@@ -31,18 +29,19 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/textformat.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent.c.generated.h"
@@ -52,9 +51,9 @@
/// "array" will be set, caller must free it if needed.
///
/// @return false for an error.
-bool tabstop_set(char *var, long **array)
+bool tabstop_set(char *var, colnr_T **array)
{
- long valcount = 1;
+ int valcount = 1;
int t;
char *cp;
@@ -88,8 +87,8 @@ bool tabstop_set(char *var, long **array)
return false;
}
- *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long));
- (*array)[0] = valcount;
+ *array = (colnr_T *)xmalloc((unsigned)(valcount + 1) * sizeof(int));
+ (*array)[0] = (colnr_T)valcount;
t = 1;
for (cp = var; *cp != NUL;) {
@@ -116,21 +115,21 @@ bool tabstop_set(char *var, long **array)
/// Calculate the number of screen spaces a tab will occupy.
/// If "vts" is set then the tab widths are taken from that array,
/// otherwise the value of ts is used.
-int tabstop_padding(colnr_T col, long ts_arg, const long *vts)
+int tabstop_padding(colnr_T col, OptInt ts_arg, const colnr_T *vts)
{
- long ts = ts_arg == 0 ? 8 : ts_arg;
+ OptInt ts = ts_arg == 0 ? 8 : ts_arg;
colnr_T tabcol = 0;
int t;
- long padding = 0;
+ int padding = 0;
if (vts == NULL || vts[0] == 0) {
return (int)(ts - (col % ts));
}
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > col) {
padding = tabcol - col;
break;
@@ -140,23 +139,23 @@ int tabstop_padding(colnr_T col, long ts_arg, const long *vts)
padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]);
}
- return (int)padding;
+ return padding;
}
/// Find the size of the tab that covers a particular column.
-int tabstop_at(colnr_T col, long ts, const long *vts)
+int tabstop_at(colnr_T col, OptInt ts, const colnr_T *vts)
{
colnr_T tabcol = 0;
int t;
- long tab_size = 0;
+ int tab_size = 0;
if (vts == NULL || vts[0] == 0) {
return (int)ts;
}
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > col) {
tab_size = vts[t];
break;
@@ -166,53 +165,53 @@ int tabstop_at(colnr_T col, long ts, const long *vts)
tab_size = vts[tabcount];
}
- return (int)tab_size;
+ return tab_size;
}
/// Find the column on which a tab starts.
-colnr_T tabstop_start(colnr_T col, long ts, long *vts)
+colnr_T tabstop_start(colnr_T col, int ts, colnr_T *vts)
{
colnr_T tabcol = 0;
int t;
if (vts == NULL || vts[0] == 0) {
- return (int)((col / ts) * ts);
+ return ((col / ts) * ts);
}
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > col) {
- return (int)(tabcol - vts[t]);
+ return (tabcol - vts[t]);
}
}
- const int excess = (int)(tabcol % vts[tabcount]);
- return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]);
+ const int excess = (tabcol % vts[tabcount]);
+ return (excess + ((col - excess) / vts[tabcount]) * vts[tabcount]);
}
/// Find the number of tabs and spaces necessary to get from one column
/// to another.
-void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long *vts, int *ntabs,
+void tabstop_fromto(colnr_T start_col, colnr_T end_col, int ts_arg, const colnr_T *vts, int *ntabs,
int *nspcs)
{
int spaces = end_col - start_col;
colnr_T tabcol = 0;
- long padding = 0;
+ int padding = 0;
int t;
- long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg;
+ int ts = ts_arg == 0 ? (int)curbuf->b_p_ts : ts_arg;
assert(ts != 0); // suppress clang "Division by zero"
if (vts == NULL || vts[0] == 0) {
int tabs = 0;
- const int initspc = (int)(ts - (start_col % ts));
+ const int initspc = (ts - (start_col % ts));
if (spaces >= initspc) {
spaces -= initspc;
tabs++;
}
- tabs += (int)(spaces / ts);
- spaces -= (int)((spaces / ts) * ts);
+ tabs += (spaces / ts);
+ spaces -= ((spaces / ts) * ts);
*ntabs = tabs;
*nspcs = spaces;
@@ -220,9 +219,9 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
}
// Find the padding needed to reach the next tabstop.
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > start_col) {
padding = tabcol - start_col;
break;
@@ -240,7 +239,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
}
*ntabs = 1;
- spaces -= (int)padding;
+ spaces -= padding;
// At least one tab has been used. See if any more will fit.
while (spaces != 0 && ++t <= tabcount) {
@@ -250,7 +249,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
return;
}
*ntabs += 1;
- spaces -= (int)padding;
+ spaces -= padding;
}
*ntabs += spaces / (int)vts[tabcount];
@@ -258,7 +257,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
}
/// See if two tabstop arrays contain the same values.
-bool tabstop_eq(const long *ts1, const long *ts2)
+bool tabstop_eq(const colnr_T *ts1, const colnr_T *ts2)
{
int t;
@@ -282,31 +281,31 @@ bool tabstop_eq(const long *ts1, const long *ts2)
}
/// Copy a tabstop array, allocating space for the new array.
-int *tabstop_copy(const long *oldts)
+int *tabstop_copy(const int *oldts)
{
- long *newts;
+ int *newts;
int t;
if (oldts == 0) {
return 0;
}
- newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long));
+ newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(int));
for (t = 0; t <= oldts[0]; t++) {
newts[t] = oldts[t];
}
- return (int *)newts;
+ return newts;
}
/// Return a count of the number of tabstops.
-int tabstop_count(long *ts)
+int tabstop_count(colnr_T *ts)
{
return ts != NULL ? (int)ts[0] : 0;
}
/// Return the first tabstop, or 8 if there are no tabstops defined.
-int tabstop_first(long *ts)
+int tabstop_first(colnr_T *ts)
{
return ts != NULL ? (int)ts[1] : 8;
}
@@ -315,25 +314,23 @@ int tabstop_first(long *ts)
/// 'tabstop' value when 'shiftwidth' is zero.
int get_sw_value(buf_T *buf)
{
- long result = get_sw_value_col(buf, 0);
- assert(result >= 0 && result <= INT_MAX);
- return (int)result;
+ int result = get_sw_value_col(buf, 0);
+ return result;
}
/// Idem, using "pos".
-long get_sw_value_pos(buf_T *buf, pos_T *pos)
+int get_sw_value_pos(buf_T *buf, pos_T *pos)
{
pos_T save_cursor = curwin->w_cursor;
- long sw_value;
curwin->w_cursor = *pos;
- sw_value = get_sw_value_col(buf, get_nolist_virtcol());
+ int sw_value = get_sw_value_col(buf, get_nolist_virtcol());
curwin->w_cursor = save_cursor;
return sw_value;
}
/// Idem, using the first non-black in the current line.
-long get_sw_value_indent(buf_T *buf)
+int get_sw_value_indent(buf_T *buf)
{
pos_T pos = curwin->w_cursor;
@@ -342,9 +339,9 @@ long get_sw_value_indent(buf_T *buf)
}
/// Idem, using virtual column "col".
-long get_sw_value_col(buf_T *buf, colnr_T col)
+int get_sw_value_col(buf_T *buf, colnr_T col)
{
- return buf->b_p_sw ? buf->b_p_sw
+ return buf->b_p_sw ? (int)buf->b_p_sw
: tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array);
}
@@ -352,9 +349,8 @@ long get_sw_value_col(buf_T *buf, colnr_T col)
/// using the shiftwidth value when 'softtabstop' is negative.
int get_sts_value(void)
{
- long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
- assert(result >= 0 && result <= INT_MAX);
- return (int)result;
+ int result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : (int)curbuf->b_p_sts;
+ return result;
}
// Count the size (in window cells) of the indent in the current line.
@@ -379,10 +375,7 @@ int get_indent_lnum(linenr_T lnum)
// "buf".
int get_indent_buf(buf_T *buf, linenr_T lnum)
{
- return get_indent_str_vtab(ml_get_buf(buf, lnum, false),
- buf->b_p_ts,
- buf->b_p_vts_array,
- false);
+ return get_indent_str_vtab(ml_get_buf(buf, lnum), buf->b_p_ts, buf->b_p_vts_array, false);
}
/// Count the size (in window cells) of the indent in line "ptr", with
@@ -417,7 +410,7 @@ int get_indent_str(const char *ptr, int ts, bool list)
/// Count the size (in window cells) of the indent in line "ptr", using
/// variable tabstops.
/// if "list" is true, count only screen size for tabs.
-int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list)
+int get_indent_str_vtab(const char *ptr, OptInt ts, colnr_T *vts, bool list)
{
int count = 0;
@@ -428,7 +421,7 @@ int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list)
} else {
// In list mode, when tab is not set, count screen char width
// for Tab, displays: ^I
- count += ptr2cells((char *)ptr);
+ count += ptr2cells(ptr);
}
} else if (*ptr == ' ') {
count++; // count a space for one
@@ -460,7 +453,6 @@ int set_indent(int size, int flags)
int line_len;
int doit = false;
int ind_done = 0; // Measured in spaces.
- int ind_col = 0;
int tab_pad;
int retval = false;
@@ -479,6 +471,7 @@ int set_indent(int size, int flags)
// 'preserveindent' are set count the number of characters at the
// beginning of the line to be copied.
if (!curbuf->b_p_et || (!(flags & SIN_INSERT) && curbuf->b_p_pi)) {
+ int ind_col = 0;
// If 'preserveindent' is set then reuse as much as possible of
// the existing indent structure for the new indent.
if (!(flags & SIN_INSERT) && curbuf->b_p_pi) {
@@ -528,7 +521,7 @@ int set_indent(int size, int flags)
}
// Count tabs required for indent.
- for (;;) {
+ while (true) {
tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts, curbuf->b_p_vts_array);
if (todo < tab_pad) {
break;
@@ -651,7 +644,7 @@ int set_indent(int size, int flags)
p = skipwhite(p);
}
- for (;;) {
+ while (true) {
tab_pad = tabstop_padding(ind_done,
curbuf->b_p_ts,
curbuf->b_p_vts_array);
@@ -736,7 +729,7 @@ int get_number_indent(linenr_T lnum)
// vim_regexec() expects a pointer to a line. This lets us
// start matching for the flp beyond any comment leader...
- if (vim_regexec(&regmatch, ml_get(lnum) + lead_len, (colnr_T)0)) {
+ if (vim_regexec(&regmatch, ml_get(lnum) + lead_len, 0)) {
pos.lnum = lnum;
pos.col = (colnr_T)(*regmatch.endp - ml_get(lnum));
pos.coladd = 0;
@@ -763,6 +756,7 @@ bool briopt_check(win_T *wp)
char *p = wp->w_p_briopt;
while (*p != NUL) {
+ // Note: Keep this in sync with p_briopt_values
if (strncmp(p, "shift:", 6) == 0
&& ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) {
p += 6;
@@ -803,11 +797,12 @@ bool briopt_check(win_T *wp)
int get_breakindent_win(win_T *wp, char *line)
FUNC_ATTR_NONNULL_ALL
{
- static int prev_indent = 0; // Cached indent value.
- static long prev_ts = 0L; // Cached tabstop value.
- static const char *prev_line = NULL; // cached pointer to line.
- static varnumber_T prev_tick = 0; // Changedtick of cached value.
- static long *prev_vts = NULL; // Cached vartabs values.
+ static int prev_indent = 0; // cached indent value
+ static OptInt prev_ts = 0; // cached tabstop value
+ static int prev_fnum = 0; // cached buffer number
+ static char *prev_line = NULL; // cached copy of "line"
+ static varnumber_T prev_tick = 0; // changedtick of cached value
+ static colnr_T *prev_vts = NULL; // cached vartabs values
static int prev_list = 0; // cached list value
static int prev_listopt = 0; // cached w_p_briopt_list value
static char *prev_flp = NULL; // cached formatlistpat value
@@ -818,16 +813,24 @@ int get_breakindent_win(win_T *wp, char *line)
&& (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0);
// used cached indent, unless
- // - line pointer changed
+ // - buffer changed
// - 'tabstop' changed
+ // - buffer was changed
// - 'briopt_list changed' changed or
// - 'formatlistpattern' changed
- if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
+ // - line changed
+ // - 'vartabs' changed
+ if (prev_fnum != wp->w_buffer->b_fnum
+ || prev_ts != wp->w_buffer->b_p_ts
|| prev_tick != buf_get_changedtick(wp->w_buffer)
|| prev_listopt != wp->w_briopt_list
- || (prev_flp == NULL || (strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0))
+ || prev_flp == NULL
+ || strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0
+ || prev_line == NULL || strcmp(prev_line, line) != 0
|| prev_vts != wp->w_buffer->b_p_vts_array) {
- prev_line = line;
+ prev_fnum = wp->w_buffer->b_fnum;
+ xfree(prev_line);
+ prev_line = xstrdup(line);
prev_ts = wp->w_buffer->b_p_ts;
prev_tick = buf_get_changedtick(wp->w_buffer);
prev_vts = wp->w_buffer->b_p_vts_array;
@@ -887,7 +890,7 @@ int get_breakindent_win(win_T *wp, char *line)
// always leave at least bri_min characters on the left,
// if text width is sufficient
bri = (eff_wwidth - wp->w_briopt_min < 0)
- ? 0 : eff_wwidth - wp->w_briopt_min;
+ ? 0 : eff_wwidth - wp->w_briopt_min;
}
return bri;
@@ -933,18 +936,14 @@ void ex_retab(exarg_T *eap)
{
linenr_T lnum;
bool got_tab = false;
- long num_spaces = 0;
- long num_tabs;
- long len;
- long col;
- long vcol;
- long start_col = 0; // For start of white-space string
- long start_vcol = 0; // For start of white-space string
- long old_len;
- char *ptr;
+ int num_spaces = 0;
+ int num_tabs;
+ int len;
+ int start_col = 0; // For start of white-space string
+ int64_t start_vcol = 0; // For start of white-space string
+ int old_len;
char *new_line = (char *)1; // init to non-NULL
- bool did_undo; // called u_save for current line
- long *new_vts_array = NULL;
+ colnr_T *new_vts_array = NULL;
char *new_ts_str; // string value of tab argument
int save_list;
@@ -959,7 +958,7 @@ void ex_retab(exarg_T *eap)
return;
}
while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') {
- (eap->arg)++;
+ eap->arg++;
}
// This ensures that either new_vts_array and new_ts_str are freshly
@@ -969,14 +968,14 @@ void ex_retab(exarg_T *eap)
new_vts_array = curbuf->b_p_vts_array;
new_ts_str = NULL;
} else {
- new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str));
+ new_ts_str = xmemdupz(new_ts_str, (size_t)(eap->arg - new_ts_str));
}
for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) {
- ptr = ml_get(lnum);
- col = 0;
- vcol = 0;
- did_undo = false;
- for (;;) {
+ char *ptr = ml_get(lnum);
+ int col = 0;
+ int64_t vcol = 0;
+ bool did_undo = false; // called u_save for current line
+ while (true) {
if (ascii_iswhite(ptr[col])) {
if (!got_tab && num_spaces == 0) {
// First consecutive white-space
@@ -993,13 +992,13 @@ void ex_retab(exarg_T *eap)
// Retabulate this string of white-space
// len is virtual length of white string
- len = num_spaces = vcol - start_vcol;
+ len = num_spaces = (int)(vcol - start_vcol);
num_tabs = 0;
if (!curbuf->b_p_et) {
int t, s;
tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol,
- curbuf->b_p_ts, new_vts_array, &t, &s);
+ (int)curbuf->b_p_ts, new_vts_array, &t, &s);
num_tabs = t;
num_spaces = s;
}
@@ -1016,8 +1015,8 @@ void ex_retab(exarg_T *eap)
// len is actual number of white characters used
len = num_spaces + num_tabs;
- old_len = (long)strlen(ptr);
- const long new_len = old_len - col + start_col + len + 1;
+ old_len = (int)strlen(ptr);
+ const int new_len = old_len - col + start_col + len + 1;
if (new_len <= 0 || new_len >= MAXCOL) {
emsg_text_too_long();
break;
@@ -1028,7 +1027,7 @@ void ex_retab(exarg_T *eap)
memmove(new_line, ptr, (size_t)start_col);
}
memmove(new_line + start_col + len,
- ptr + col, (size_t)(old_len - col + 1));
+ ptr + col, (size_t)old_len - (size_t)col + 1);
ptr = new_line + start_col;
for (col = 0; col < len; col++) {
ptr[col] = (col < num_tabs) ? '\t' : ' ';
@@ -1082,7 +1081,7 @@ void ex_retab(exarg_T *eap)
redraw_curbuf_later(UPD_NOT_VALID);
}
if (first_line != 0) {
- changed_lines(first_line, 0, last_line + 1, 0L, true);
+ changed_lines(curbuf, first_line, 0, last_line + 1, 0, true);
}
curwin->w_p_list = save_list; // restore 'list'
@@ -1090,7 +1089,7 @@ void ex_retab(exarg_T *eap)
if (new_ts_str != NULL) { // set the new tabstop
// If 'vartabstop' is in use or if the value given to retab has more
// than one tabstop then update 'vartabstop'.
- long *old_vts_ary = curbuf->b_p_vts_array;
+ colnr_T *old_vts_ary = curbuf->b_p_vts_array;
if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) {
set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0);
@@ -1106,7 +1105,7 @@ void ex_retab(exarg_T *eap)
}
coladvance(curwin->w_curswant);
- u_clearline();
+ u_clearline(curbuf);
}
/// Get indent level from 'indentexpr'.
@@ -1118,6 +1117,7 @@ int get_expr_indent(void)
int save_set_curswant;
int save_State;
int use_sandbox = was_set_insecurely(curwin, "indentexpr", OPT_LOCAL);
+ const sctx_T save_sctx = current_sctx;
// Save and restore cursor position and curswant, in case it was changed
// * via :normal commands.
@@ -1130,6 +1130,7 @@ int get_expr_indent(void)
sandbox++;
}
textlock++;
+ current_sctx = curbuf->b_p_script_ctx[BV_INDE].script_ctx;
// Need to make a copy, the 'indentexpr' option could be changed while
// evaluating it.
@@ -1141,6 +1142,7 @@ int get_expr_indent(void)
sandbox--;
}
textlock--;
+ current_sctx = save_sctx;
// Restore the cursor position so that 'indentexpr' doesn't need to.
// Pretend to be in Insert mode, allow cursor past end of line for "o"
@@ -1183,19 +1185,15 @@ int get_expr_indent(void)
// I tried to fix the first two issues.
int get_lisp_indent(void)
{
- pos_T *pos, realpos, paren;
+ pos_T *pos;
+ pos_T paren;
int amount;
char *that;
- colnr_T col;
- colnr_T firsttry;
- int parencount;
- int quotecount;
- int vi_lisp;
// Set vi_lisp to use the vi-compatible method.
- vi_lisp = (vim_strchr(p_cpo, CPO_LISP) != NULL);
+ int vi_lisp = (vim_strchr(p_cpo, CPO_LISP) != NULL);
- realpos = curwin->w_cursor;
+ pos_T realpos = curwin->w_cursor;
curwin->w_cursor.col = 0;
if ((pos = findmatch(NULL, '(')) == NULL) {
@@ -1213,7 +1211,7 @@ int get_lisp_indent(void)
// Extra trick: Take the indent of the first previous non-white
// line that is at the same () level.
amount = -1;
- parencount = 0;
+ int parencount = 0;
while (--curwin->w_cursor.lnum >= pos->lnum) {
if (linewhite(curwin->w_cursor.lnum)) {
@@ -1268,7 +1266,7 @@ int get_lisp_indent(void)
if (amount == -1) {
curwin->w_cursor.lnum = pos->lnum;
curwin->w_cursor.col = pos->col;
- col = pos->col;
+ colnr_T col = pos->col;
that = get_cursor_line_ptr();
@@ -1298,7 +1296,7 @@ int get_lisp_indent(void)
that++;
amount++;
}
- firsttry = amount;
+ colnr_T firsttry = amount;
init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line),
amount, line, that);
@@ -1319,13 +1317,13 @@ int get_lisp_indent(void)
}
parencount = 0;
- quotecount = 0;
init_chartabsize_arg(&cts, curwin,
(colnr_T)(that - line), amount, line, that);
if (vi_lisp || ((*that != '"') && (*that != '\'')
&& (*that != '#')
&& (((uint8_t)(*that) < '0') || ((uint8_t)(*that) > '9')))) {
+ int quotecount = 0;
while (*cts.cts_ptr
&& (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount)
&& (!((*cts.cts_ptr == '(' || *cts.cts_ptr == '[')
@@ -1372,13 +1370,12 @@ int get_lisp_indent(void)
static int lisp_match(char *p)
{
- char buf[LSIZE];
- int len;
+ char buf[512];
char *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords;
while (*word != NUL) {
- (void)copy_option_part(&word, buf, LSIZE, ",");
- len = (int)strlen(buf);
+ (void)copy_option_part(&word, buf, sizeof(buf), ",");
+ int len = (int)strlen(buf);
if ((strncmp(buf, p, (size_t)len) == 0) && ascii_iswhite_or_nul(p[len])) {
return true;
diff --git a/src/nvim/indent.h b/src/nvim/indent.h
index f807bbb42b..b64015958c 100644
--- a/src/nvim/indent.h
+++ b/src/nvim/indent.h
@@ -1,17 +1,20 @@
-#ifndef NVIM_INDENT_H
-#define NVIM_INDENT_H
+#pragma once
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
typedef int (*IndentGetter)(void);
-// flags for set_indent()
-#define SIN_CHANGED 1 // call changed_bytes() when line changed
-#define SIN_INSERT 2 // insert indent before existing text
-#define SIN_UNDO 4 // save line for undo before changing it
-#define SIN_NOMARK 8 // don't adjust extmarks
+/// flags for set_indent()
+enum {
+ SIN_CHANGED = 1, ///< call changed_bytes() when line changed
+ SIN_INSERT = 2, ///< insert indent before existing text
+ SIN_UNDO = 4, ///< save line for undo before changing it
+ SIN_NOMARK = 8, ///< don't adjust extmarks
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent.h.generated.h"
#endif
-#endif // NVIM_INDENT_H
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 1c771073b2..c140d468d8 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -1,29 +1,30 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Find result cache for cpp_baseclass
typedef struct {
@@ -47,7 +48,7 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX
pos_T *pos;
int64_t cur_maxcomment = ind_maxcomment;
- for (;;) {
+ while (true) {
pos = findmatchlimit(NULL, '*', FM_BACKWARD, cur_maxcomment);
if (pos == NULL) {
break;
@@ -106,9 +107,9 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw)
static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
{
pos_T *pos;
- long cur_maxcomment = ind_maxcomment;
+ int cur_maxcomment = ind_maxcomment;
- for (;;) {
+ while (true) {
pos = findmatchlimit(NULL, 'R', FM_BACKWARD, cur_maxcomment);
if (pos == NULL) {
break;
@@ -412,7 +413,7 @@ static int cin_isinit(void)
s = cin_skipcomment(s + 7);
}
- for (;;) {
+ while (true) {
int i, l;
for (i = 0; i < (int)ARRAY_SIZE(skip); i++) {
@@ -525,7 +526,9 @@ static bool cin_is_cpp_namespace(const char *s)
s = cin_skipcomment(s);
- if (strncmp(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) {
+ // skip over "inline" and "export" in any order
+ while ((strncmp(s, "inline", 6) == 0 || strncmp(s, "export", 6) == 0)
+ && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) {
s = cin_skipcomment(skipwhite(s + 6));
}
@@ -754,7 +757,7 @@ static int cin_ispreproc_cont(const char **pp, linenr_T *lnump, int *amount)
candidate_amount = get_indent_lnum(lnum);
}
- for (;;) {
+ while (true) {
if (cin_ispreproc(line)) {
retval = true;
*lnump = lnum;
@@ -927,7 +930,7 @@ static int cin_isfuncdecl(const char **sp, linenr_T first_lnum, linenr_T min_lnu
// At the end: check for ',' in the next line, for this style:
// func(arg1
// , arg2)
- for (;;) {
+ while (true) {
if (lnum >= curbuf->b_ml.ml_line_count) {
break;
}
@@ -1184,7 +1187,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached)
pos->lnum = lnum;
line = ml_get(lnum);
s = line;
- for (;;) {
+ while (true) {
if (*s == NUL) {
if (lnum == curwin->w_cursor.lnum) {
break;
@@ -1504,10 +1507,10 @@ static pos_T *find_match_paren_after_brace(int ind_maxparen)
// looking a few lines further.
static int corr_ind_maxparen(pos_T *startpos)
{
- long n = (long)startpos->lnum - (long)curwin->w_cursor.lnum;
+ int n = startpos->lnum - curwin->w_cursor.lnum;
if (n > 0 && n < curbuf->b_ind_maxparen / 2) {
- return curbuf->b_ind_maxparen - (int)n;
+ return curbuf->b_ind_maxparen - n;
}
return curbuf->b_ind_maxparen;
}
@@ -2499,7 +2502,7 @@ int get_c_indent(void)
// the usual amount relative to the conditional
// that opens the block.
curwin->w_cursor = cur_curpos;
- for (;;) {
+ while (true) {
curwin->w_cursor.lnum--;
curwin->w_cursor.col = 0;
@@ -2529,8 +2532,6 @@ int get_c_indent(void)
break;
}
- l = get_cursor_line_ptr();
-
// If we're in a comment or raw string now, skip to
// the start of it.
trypos = ind_find_start_CORS(NULL);
@@ -2540,9 +2541,9 @@ int get_c_indent(void)
continue;
}
- //
+ l = get_cursor_line_ptr();
+
// Skip preprocessor directives and blank lines.
- //
if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {
continue;
}
@@ -2640,8 +2641,6 @@ int get_c_indent(void)
break;
}
- l = get_cursor_line_ptr();
-
// If we're in a comment or raw string now, skip
// to the start of it.
trypos = ind_find_start_CORS(NULL);
@@ -2651,6 +2650,8 @@ int get_c_indent(void)
continue;
}
+ l = get_cursor_line_ptr();
+
// Skip preprocessor directives and blank lines.
if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {
continue;
@@ -2916,11 +2917,15 @@ int get_c_indent(void)
trypos = NULL;
}
+ l = get_cursor_line_ptr();
+
// If we are looking for ',', we also look for matching
// braces.
- if (trypos == NULL && terminated == ','
- && find_last_paren(l, '{', '}')) {
- trypos = find_start_brace();
+ if (trypos == NULL && terminated == ',') {
+ if (find_last_paren(l, '{', '}')) {
+ trypos = find_start_brace();
+ }
+ l = get_cursor_line_ptr();
}
if (trypos != NULL) {
@@ -2951,6 +2956,7 @@ int get_c_indent(void)
curwin->w_cursor.lnum--;
curwin->w_cursor.col = 0;
}
+ l = get_cursor_line_ptr();
}
// Get indent and pointer to text for current line,
diff --git a/src/nvim/indent_c.h b/src/nvim/indent_c.h
index bd6eeed67d..64ba67a42b 100644
--- a/src/nvim/indent_c.h
+++ b/src/nvim/indent_c.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_INDENT_C_H
-#define NVIM_INDENT_C_H
+#pragma once
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent_c.h.generated.h"
#endif
-#endif // NVIM_INDENT_C_H
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 96214d45c2..fb25968071 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -1,19 +1,17 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// input.c: high level functions for prompting the user or input
// like yes/no or number prompts.
+#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/input.h"
#include "nvim/keycodes.h"
#include "nvim/mbyte.h"
@@ -21,9 +19,8 @@
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/os/input.h"
-#include "nvim/types.h"
+#include "nvim/state_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "input.c.generated.h" // IWYU pragma: export
@@ -56,7 +53,7 @@ int ask_yesno(const char *const str, const bool direct)
int r = ' ';
while (r != 'y' && r != 'n') {
// same highlighting as for wait_return()
- smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str);
+ smsg(HL_ATTR(HLF_R), "%s (y/n)?", str);
if (direct) {
r = get_keystroke(NULL);
} else {
@@ -86,21 +83,20 @@ int ask_yesno(const char *const str, const bool direct)
/// Translates the interrupt character for unix to ESC.
int get_keystroke(MultiQueue *events)
{
- char_u *buf = NULL;
+ uint8_t *buf = NULL;
int buflen = 150;
- int maxlen;
int len = 0;
int n;
int save_mapped_ctrl_c = mapped_ctrl_c;
mapped_ctrl_c = 0; // mappings are not used here
- for (;;) {
+ while (true) {
// flush output before waiting
ui_flush();
// Leave some room for check_termcode() to insert a key code into (max
// 5 chars plus NUL). And fix_input_buffer() can triple the number of
// bytes.
- maxlen = (buflen - 6 - len) / 3;
+ int maxlen = (buflen - 6 - len) / 3;
if (buf == NULL) {
buf = xmalloc((size_t)buflen);
} else if (maxlen < 10) {
@@ -113,7 +109,7 @@ int get_keystroke(MultiQueue *events)
// First time: blocking wait. Second time: wait up to 100ms for a
// terminal code to complete.
- n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events);
+ n = os_inchar(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
if (n > 0) {
// Replace zero and K_SPECIAL by a special key code.
n = fix_input_buffer(buf + len, n);
@@ -166,7 +162,6 @@ int get_keystroke(MultiQueue *events)
int get_number(int colon, int *mouse_used)
{
int n = 0;
- int c;
int typed = 0;
if (mouse_used != NULL) {
@@ -181,10 +176,13 @@ int get_number(int colon, int *mouse_used)
no_mapping++;
allow_keys++; // no mapping here, but recognize keys
- for (;;) {
+ while (true) {
ui_cursor_goto(msg_row, msg_col);
- c = safe_vgetc();
+ int c = safe_vgetc();
if (ascii_isdigit(c)) {
+ if (n > INT_MAX / 10) {
+ return 0;
+ }
n = n * 10 + c - '0';
msg_putchar(c);
typed++;
diff --git a/src/nvim/input.h b/src/nvim/input.h
index 7975f21215..3d948fa4ca 100644
--- a/src/nvim/input.h
+++ b/src/nvim/input.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_INPUT_H
-#define NVIM_INPUT_H
+#pragma once
-#include "nvim/vim.h"
+#include "nvim/event/multiqueue.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "input.h.generated.h"
#endif
-#endif // NVIM_INPUT_H
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 6de3b0a9d0..12543a2d42 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// insexpand.c: functions for Insert mode completion
#include <assert.h>
@@ -12,7 +9,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
@@ -23,23 +20,21 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -47,24 +42,26 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/textformat.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
+#include "nvim/window.h"
// Definitions used for CTRL-X submode.
// Note: If you change CTRL-X submode, you must also maintain ctrl_x_msgs[]
@@ -91,6 +88,7 @@ enum {
CTRL_X_LOCAL_MSG = 15, ///< only used in "ctrl_x_msgs"
CTRL_X_EVAL = 16, ///< for builtin function complete()
CTRL_X_CMDLINE_CTRL_X = 17, ///< CTRL-X typed in CTRL_X_CMDLINE
+ CTRL_X_BUFNAMES = 18,
};
#define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]
@@ -162,7 +160,8 @@ struct compl_S {
/// state information used for getting the next set of insert completion
/// matches.
typedef struct {
- char *e_cpt; ///< current entry in 'complete'
+ char *e_cpt_copy; ///< copy of 'complete'
+ char *e_cpt; ///< current entry in "e_cpt_copy"
buf_T *ins_buf; ///< buffer being scanned
pos_T *cur_match_pos; ///< current match position
pos_T prev_match_pos; ///< previous match position
@@ -188,8 +187,8 @@ typedef enum {
CP_FAST = 32, ///< use fast_breakcheck instead of os_breakcheck
} cp_flags_T;
-static char e_hitend[] = N_("Hit end of paragraph");
-static char e_compldel[] = N_("E840: Completion function deleted text");
+static const char e_hitend[] = N_("Hit end of paragraph");
+static const char e_compldel[] = N_("E840: Completion function deleted text");
// All the current matches are stored in a list.
// "compl_first_match" points to the start of the list.
@@ -466,14 +465,13 @@ bool check_compl_option(bool dict_opt)
&& *curbuf->b_p_tsrfu == NUL && *p_tsrfu == NUL)) {
ctrl_x_mode = CTRL_X_NORMAL;
edit_submode = NULL;
- msg_attr((dict_opt
- ? _("'dictionary' option is empty")
- : _("'thesaurus' option is empty")), HL_ATTR(HLF_E));
+ msg((dict_opt ? _("'dictionary' option is empty") : _("'thesaurus' option is empty")),
+ HL_ATTR(HLF_E));
if (emsg_silent == 0 && !in_assert_fails) {
vim_beep(BO_COMPL);
setcursor();
ui_flush();
- os_delay(2004L, false);
+ os_delay(2004, false);
}
return false;
}
@@ -536,6 +534,8 @@ bool vim_is_ctrl_x_key(int c)
return c == Ctrl_S || c == Ctrl_P || c == Ctrl_N;
case CTRL_X_EVAL:
return (c == Ctrl_P || c == Ctrl_N);
+ case CTRL_X_BUFNAMES:
+ return (c == Ctrl_P || c == Ctrl_N);
}
internal_error("vim_is_ctrl_x_key()");
return false;
@@ -909,8 +909,6 @@ static bool ins_compl_equal(compl_T *match, char *str, size_t len)
/// Reduce the longest common string for match "match".
static void ins_compl_longest_match(compl_T *match)
{
- char *p, *s;
- int c1, c2;
int had_match;
if (compl_leader == NULL) {
@@ -933,11 +931,11 @@ static void ins_compl_longest_match(compl_T *match)
}
// Reduce the text if this match differs from compl_leader.
- p = compl_leader;
- s = match->cp_str;
+ char *p = compl_leader;
+ char *s = match->cp_str;
while (*p != NUL) {
- c1 = utf_ptr2char(p);
- c2 = utf_ptr2char(s);
+ int c1 = utf_ptr2char(p);
+ int c2 = utf_ptr2char(s);
if ((match->cp_flags & CP_ICASE)
? (mb_tolower(c1) != mb_tolower(c2))
@@ -1068,14 +1066,14 @@ static bool pum_enough_matches(void)
{
// Don't display the popup menu if there are no matches or there is only
// one (ignoring the original text).
- compl_T *compl = compl_first_match;
+ compl_T *comp = compl_first_match;
int i = 0;
do {
- if (compl == NULL || (!match_at_original_text(compl) && ++i == 2)) {
+ if (comp == NULL || (!match_at_original_text(comp) && ++i == 2)) {
break;
}
- compl = compl->cp_next;
- } while (!is_first_match(compl));
+ comp = comp->cp_next;
+ } while (!is_first_match(comp));
if (strstr(p_cot, "menuone") != NULL) {
return i >= 1;
@@ -1139,7 +1137,7 @@ static int ins_compl_build_pum(void)
{
// Need to build the popup menu list.
compl_match_arraysize = 0;
- compl_T *compl = compl_first_match;
+ compl_T *comp = compl_first_match;
// If it's user complete function and refresh_always,
// do not use "compl_leader" as prefix filter.
@@ -1150,13 +1148,13 @@ static int ins_compl_build_pum(void)
const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0;
do {
- if (!match_at_original_text(compl)
+ if (!match_at_original_text(comp)
&& (compl_leader == NULL
- || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) {
+ || ins_compl_equal(comp, compl_leader, (size_t)lead_len))) {
compl_match_arraysize++;
}
- compl = compl->cp_next;
- } while (compl != NULL && !is_first_match(compl));
+ comp = comp->cp_next;
+ } while (comp != NULL && !is_first_match(comp));
if (compl_match_arraysize == 0) {
return -1;
@@ -1173,46 +1171,46 @@ static int ins_compl_build_pum(void)
bool did_find_shown_match = false;
int cur = -1;
int i = 0;
- compl = compl_first_match;
+ comp = compl_first_match;
do {
- if (!match_at_original_text(compl)
+ if (!match_at_original_text(comp)
&& (compl_leader == NULL
- || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) {
+ || ins_compl_equal(comp, compl_leader, (size_t)lead_len))) {
if (!shown_match_ok) {
- if (compl == compl_shown_match || did_find_shown_match) {
+ if (comp == compl_shown_match || did_find_shown_match) {
// This item is the shown match or this is the
// first displayed item after the shown match.
- compl_shown_match = compl;
+ compl_shown_match = comp;
did_find_shown_match = true;
shown_match_ok = true;
} else {
// Remember this displayed match for when the
// shown match is just below it.
- shown_compl = compl;
+ shown_compl = comp;
}
cur = i;
}
- if (compl->cp_text[CPT_ABBR] != NULL) {
- compl_match_array[i].pum_text = compl->cp_text[CPT_ABBR];
+ if (comp->cp_text[CPT_ABBR] != NULL) {
+ compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR];
} else {
- compl_match_array[i].pum_text = compl->cp_str;
+ compl_match_array[i].pum_text = comp->cp_str;
}
- compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND];
- compl_match_array[i].pum_info = compl->cp_text[CPT_INFO];
- if (compl->cp_text[CPT_MENU] != NULL) {
- compl_match_array[i++].pum_extra = compl->cp_text[CPT_MENU];
+ compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND];
+ compl_match_array[i].pum_info = comp->cp_text[CPT_INFO];
+ if (comp->cp_text[CPT_MENU] != NULL) {
+ compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU];
} else {
- compl_match_array[i++].pum_extra = compl->cp_fname;
+ compl_match_array[i++].pum_extra = comp->cp_fname;
}
}
- if (compl == compl_shown_match) {
+ if (comp == compl_shown_match) {
did_find_shown_match = true;
// When the original text is the shown match don't set
// compl_shown_match.
- if (match_at_original_text(compl)) {
+ if (match_at_original_text(comp)) {
shown_match_ok = true;
}
@@ -1223,8 +1221,8 @@ static int ins_compl_build_pum(void)
shown_match_ok = true;
}
}
- compl = compl->cp_next;
- } while (compl != NULL && !is_first_match(compl));
+ comp = comp->cp_next;
+ } while (comp != NULL && !is_first_match(comp));
if (!shown_match_ok) { // no displayed match at all
cur = -1;
@@ -1241,9 +1239,6 @@ void ins_compl_show_pum(void)
return;
}
- // Dirty hard-coded hack: remove any matchparen highlighting.
- do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif");
-
// Update the screen before drawing the popup menu over it.
update_screen();
@@ -1298,11 +1293,9 @@ static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, int t
{
char *dict = dict_start;
char *ptr;
- char *buf;
regmatch_T regmatch;
char **files;
int count;
- int save_p_scs;
Direction dir = compl_direction;
if (*dict == NUL) {
@@ -1315,11 +1308,11 @@ static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, int t
}
}
- buf = xmalloc(LSIZE);
+ char *buf = xmalloc(LSIZE);
regmatch.regprog = NULL; // so that we can goto theend
// If 'infercase' is set, don't use 'smartcase' here
- save_p_scs = p_scs;
+ int save_p_scs = p_scs;
if (curbuf->b_p_inf) {
p_scs = false;
}
@@ -1415,7 +1408,7 @@ static int thesaurus_add_words_in_line(char *fname, char **buf_arg, int dir, con
// different classes, only separate words
// with single-byte non-word characters.
while (*ptr != NUL) {
- const int l = utfc_ptr2len((const char *)ptr);
+ const int l = utfc_ptr2len(ptr);
if (l < 2 && !vim_iswordc((uint8_t)(*ptr))) {
break;
@@ -1443,18 +1436,13 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r
char *buf, Direction *dir)
FUNC_ATTR_NONNULL_ARG(2, 7)
{
- char *ptr;
- int i;
- FILE *fp;
- int add_r;
-
- for (i = 0; i < count && !got_int && !compl_interrupted; i++) {
- fp = os_fopen(files[i], "r"); // open dictionary file
+ for (int i = 0; i < count && !got_int && !compl_interrupted; i++) {
+ FILE *fp = os_fopen(files[i], "r"); // open dictionary file
if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE,
_("Scanning dictionary: %s"), files[i]);
- (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ (void)msg_trunc(IObuff, true, HL_ATTR(HLF_R));
}
if (fp == NULL) {
@@ -1464,7 +1452,7 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r
// Read dictionary file line by line.
// Check each line for a match.
while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) {
- ptr = buf;
+ char *ptr = buf;
while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) {
ptr = regmatch->startp[0];
if (ctrl_x_mode_line_or_eval()) {
@@ -1472,9 +1460,9 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r
} else {
ptr = find_word_end(ptr);
}
- add_r = ins_compl_add_infercase(regmatch->startp[0],
- (int)(ptr - regmatch->startp[0]),
- p_ic, files[i], *dir, false);
+ int add_r = ins_compl_add_infercase(regmatch->startp[0],
+ (int)(ptr - regmatch->startp[0]),
+ p_ic, files[i], *dir, false);
if (thesaurus) {
// For a thesaurus, add all the words in the line
ptr = buf;
@@ -1532,9 +1520,7 @@ char *find_word_end(char *ptr)
/// @return a pointer to just after the line.
static char *find_line_end(char *ptr)
{
- char *s;
-
- s = ptr + strlen(ptr);
+ char *s = ptr + strlen(ptr);
while (s > ptr && (s[-1] == CAR || s[-1] == NL)) {
s--;
}
@@ -1544,8 +1530,6 @@ static char *find_line_end(char *ptr)
/// Free the list of completions
static void ins_compl_free(void)
{
- compl_T *match;
-
XFREE_CLEAR(compl_pattern);
XFREE_CLEAR(compl_leader);
@@ -1558,7 +1542,7 @@ static void ins_compl_free(void)
compl_curr_match = compl_first_match;
do {
- match = compl_curr_match;
+ compl_T *match = compl_curr_match;
compl_curr_match = compl_curr_match->cp_next;
xfree(match->cp_str);
// several entries may use the same fname, free it just once.
@@ -1749,9 +1733,9 @@ void ins_compl_addleader(int c)
return;
}
if ((cc = utf_char2len(c)) > 1) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
- utf_char2bytes(c, (char *)buf);
+ utf_char2bytes(c, buf);
buf[cc] = NUL;
ins_char_bytes(buf, (size_t)cc);
} else {
@@ -1805,12 +1789,9 @@ static void ins_compl_set_original_text(char *str)
/// matches.
void ins_compl_addfrommatch(void)
{
- char *p;
int len = (int)curwin->w_cursor.col - (int)compl_col;
- int c;
- compl_T *cp;
assert(compl_shown_match != NULL);
- p = compl_shown_match->cp_str;
+ char *p = compl_shown_match->cp_str;
if ((int)strlen(p) <= len) { // the match is too short
// When still at the original match use the first entry that matches
// the leader.
@@ -1819,7 +1800,7 @@ void ins_compl_addfrommatch(void)
}
p = NULL;
- for (cp = compl_shown_match->cp_next; cp != NULL
+ for (compl_T *cp = compl_shown_match->cp_next; cp != NULL
&& !is_first_match(cp); cp = cp->cp_next) {
if (compl_leader == NULL
|| ins_compl_equal(cp, compl_leader, strlen(compl_leader))) {
@@ -1832,7 +1813,7 @@ void ins_compl_addfrommatch(void)
}
}
p += len;
- c = utf_ptr2char(p);
+ int c = utf_ptr2char(p);
ins_compl_addleader(c);
}
@@ -2096,10 +2077,10 @@ bool ins_compl_prep(int c)
edit_submode_extra = NULL;
}
- // Ignore end of Select mode mapping and mouse scroll buttons.
+ // Ignore end of Select mode mapping and mouse scroll/movement.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
- || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT
- || c == K_COMMAND || c == K_LUA) {
+ || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_MOUSEMOVE
+ || c == K_EVENT || c == K_COMMAND || c == K_LUA) {
return retval;
}
@@ -2184,7 +2165,6 @@ bool ins_compl_prep(int c)
static void ins_compl_fixRedoBufForLeader(char *ptr_arg)
{
int len;
- char *p;
char *ptr = ptr_arg;
if (ptr == NULL) {
@@ -2195,7 +2175,7 @@ static void ins_compl_fixRedoBufForLeader(char *ptr_arg)
}
}
if (compl_orig_text != NULL) {
- p = compl_orig_text;
+ char *p = compl_orig_text;
for (len = 0; p[len] != NUL && p[len] == ptr[len]; len++) {}
if (len > 0) {
len -= utf_head_off(p, p + len);
@@ -2220,7 +2200,8 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag)
static win_T *wp = NULL;
if (flag == 'w') { // just windows
- if (buf == curbuf || wp == NULL) { // first call for this flag/expansion
+ if (buf == curbuf || !win_valid(wp)) {
+ // first call for this flag/expansion or window was closed
wp = curwin;
}
assert(wp);
@@ -2258,14 +2239,14 @@ static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb)
/// Invoked when the 'completefunc' option is set. The option value can be a
/// name of a function (string), or function(<name>) or funcref(<name>) or a
/// lambda expression.
-void set_completefunc_option(char **errmsg)
+const char *did_set_completefunc(optset_T *args FUNC_ATTR_UNUSED)
{
if (option_set_callback_func(curbuf->b_p_cfu, &cfu_cb) == FAIL) {
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
set_buflocal_cfu_callback(curbuf);
+ return NULL;
}
/// Copy the global 'completefunc' callback function to the buffer-local
@@ -2279,13 +2260,14 @@ void set_buflocal_cfu_callback(buf_T *buf)
/// Invoked when the 'omnifunc' option is set. The option value can be a
/// name of a function (string), or function(<name>) or funcref(<name>) or a
/// lambda expression.
-void set_omnifunc_option(buf_T *buf, char **errmsg)
+const char *did_set_omnifunc(optset_T *args)
{
+ buf_T *buf = (buf_T *)args->os_buf;
if (option_set_callback_func(buf->b_p_ofu, &ofu_cb) == FAIL) {
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
set_buflocal_ofu_callback(buf);
+ return NULL;
}
/// Copy the global 'omnifunc' callback function to the buffer-local 'omnifunc'
@@ -2299,7 +2281,7 @@ void set_buflocal_ofu_callback(buf_T *buf)
/// Invoked when the 'thesaurusfunc' option is set. The option value can be a
/// name of a function (string), or function(<name>) or funcref(<name>) or a
/// lambda expression.
-void set_thesaurusfunc_option(char **errmsg)
+const char *did_set_thesaurusfunc(optset_T *args FUNC_ATTR_UNUSED)
{
int retval;
@@ -2311,9 +2293,7 @@ void set_thesaurusfunc_option(char **errmsg)
retval = option_set_callback_func(p_tsrfu, &tsrfu_cb);
}
- if (retval == FAIL) {
- *errmsg = e_invarg;
- }
+ return retval == FAIL ? e_invarg : NULL;
}
/// Mark the global 'completefunc' 'omnifunc' and 'thesaurusfunc' callbacks with
@@ -2363,13 +2343,11 @@ static void expand_by_function(int type, char *base)
{
list_T *matchlist = NULL;
dict_T *matchdict = NULL;
- char *funcname;
- pos_T pos;
typval_T rettv;
const int save_State = State;
assert(curbuf != NULL);
- funcname = get_complete_funcname(type);
+ char *funcname = get_complete_funcname(type);
if (*funcname == NUL) {
return;
}
@@ -2382,7 +2360,7 @@ static void expand_by_function(int type, char *base)
args[0].vval.v_number = 0;
args[1].vval.v_string = base != NULL ? base : "";
- pos = curwin->w_cursor;
+ pos_T pos = curwin->w_cursor;
// Lock the text to avoid weird things from happening. Also disallow
// switching to another window, it should not be needed and may end up in
// Insert mode in another buffer.
@@ -2434,7 +2412,7 @@ theend:
}
}
-/// Add a match to the list of matches from VimL object
+/// Add a match to the list of matches from Vimscript object
///
/// @param[in] tv Object to get matches from.
/// @param[in] dir Completion direction.
@@ -2509,14 +2487,11 @@ static void ins_compl_add_list(list_T *const list)
/// Add completions from a dict.
static void ins_compl_add_dict(dict_T *dict)
{
- dictitem_T *di_refresh;
- dictitem_T *di_words;
-
// Check for optional "refresh" item.
compl_opt_refresh_always = false;
- di_refresh = tv_dict_find(dict, S_LEN("refresh"));
+ dictitem_T *di_refresh = tv_dict_find(dict, S_LEN("refresh"));
if (di_refresh != NULL && di_refresh->di_tv.v_type == VAR_STRING) {
- const char *v = (const char *)di_refresh->di_tv.vval.v_string;
+ const char *v = di_refresh->di_tv.vval.v_string;
if (v != NULL && strcmp(v, "always") == 0) {
compl_opt_refresh_always = true;
@@ -2524,7 +2499,7 @@ static void ins_compl_add_dict(dict_T *dict)
}
// Add completions from a "words" list.
- di_words = tv_dict_find(dict, S_LEN("words"));
+ dictitem_T *di_words = tv_dict_find(dict, S_LEN("words"));
if (di_words != NULL && di_words->di_tv.v_type == VAR_LIST) {
ins_compl_add_list(di_words->di_tv.vval.v_list);
}
@@ -2652,7 +2627,7 @@ static void ins_compl_update_sequence_numbers(void)
compl_T *match;
if (compl_dir_forward()) {
- // search backwards for the first valid (!= -1) number.
+ // Search backwards for the first valid (!= -1) number.
// This should normally succeed already at the first loop
// cycle, so it's fast!
for (match = compl_curr_match->cp_prev;
@@ -2672,7 +2647,7 @@ static void ins_compl_update_sequence_numbers(void)
}
} else { // BACKWARD
assert(compl_direction == BACKWARD);
- // search forwards (upwards) for the first valid (!= -1)
+ // Search forwards (upwards) for the first valid (!= -1)
// number. This should normally succeed already at the
// first loop cycle, so it's fast!
for (match = compl_curr_match->cp_next;
@@ -2683,8 +2658,7 @@ static void ins_compl_update_sequence_numbers(void)
}
}
if (match != NULL) {
- // go down and assign all numbers which are not
- // assigned yet
+ // go down and assign all numbers which are not assigned yet
for (match = match->cp_prev;
match && match->cp_number == -1;
match = match->cp_prev) {
@@ -2694,6 +2668,70 @@ static void ins_compl_update_sequence_numbers(void)
}
}
+static int info_add_completion_info(list_T *li)
+{
+ if (compl_first_match == NULL) {
+ return OK;
+ }
+
+ bool forward = compl_dir_forward();
+ compl_T *match = compl_first_match;
+ // There are four cases to consider here:
+ // 1) when just going forward through the menu,
+ // compl_first_match should point to the initial entry with
+ // number zero and CP_ORIGINAL_TEXT flag set
+ // 2) when just going backwards,
+ // compl-first_match should point to the last entry before
+ // the entry with the CP_ORIGINAL_TEXT flag set
+ // 3) when first going forwards and then backwards, e.g.
+ // pressing C-N, C-P, compl_first_match points to the
+ // last entry before the entry with the CP_ORIGINAL_TEXT
+ // flag set and next-entry moves opposite through the list
+ // compared to case 2, so pretend the direction is forward again
+ // 4) when first going backwards and then forwards, e.g.
+ // pressing C-P, C-N, compl_first_match points to the
+ // first entry with the CP_ORIGINAL_TEXT
+ // flag set and next-entry moves in opposite direction through the list
+ // compared to case 1, so pretend the direction is backwards again
+ //
+ // But only do this when the 'noselect' option is not active!
+
+ if (!compl_no_select) {
+ if (forward && !match_at_original_text(match)) {
+ forward = false;
+ } else if (!forward && match_at_original_text(match)) {
+ forward = true;
+ }
+ }
+
+ // Skip the element with the CP_ORIGINAL_TEXT flag at the beginning, in case of
+ // forward completion, or at the end, in case of backward completion.
+ match = forward ? match->cp_next
+ : (compl_no_select && match_at_original_text(match)
+ ? match->cp_prev : match->cp_prev->cp_prev);
+
+ while (match != NULL && !match_at_original_text(match)) {
+ dict_T *di = tv_dict_alloc();
+
+ tv_list_append_dict(li, di);
+ tv_dict_add_str(di, S_LEN("word"), EMPTY_IF_NULL(match->cp_str));
+ tv_dict_add_str(di, S_LEN("abbr"), EMPTY_IF_NULL(match->cp_text[CPT_ABBR]));
+ tv_dict_add_str(di, S_LEN("menu"), EMPTY_IF_NULL(match->cp_text[CPT_MENU]));
+ tv_dict_add_str(di, S_LEN("kind"), EMPTY_IF_NULL(match->cp_text[CPT_KIND]));
+ tv_dict_add_str(di, S_LEN("info"), EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
+ if (match->cp_user_data.v_type == VAR_UNKNOWN) {
+ // Add an empty string for backwards compatibility
+ tv_dict_add_str(di, S_LEN("user_data"), "");
+ } else {
+ tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data);
+ }
+
+ match = forward ? match->cp_next : match->cp_prev;
+ }
+
+ return OK;
+}
+
/// Get complete information
static void get_complete_info(list_T *what_list, dict_T *retdict)
{
@@ -2739,29 +2777,9 @@ static void get_complete_info(list_T *what_list, dict_T *retdict)
if (ret == OK && (what_flag & CI_WHAT_ITEMS)) {
list_T *li = tv_list_alloc(get_compl_len());
-
ret = tv_dict_add_list(retdict, S_LEN("items"), li);
- if (ret == OK && compl_first_match != NULL) {
- compl_T *match = compl_first_match;
- do {
- if (!match_at_original_text(match)) {
- dict_T *di = tv_dict_alloc();
-
- tv_list_append_dict(li, di);
- tv_dict_add_str(di, S_LEN("word"), EMPTY_IF_NULL(match->cp_str));
- tv_dict_add_str(di, S_LEN("abbr"), EMPTY_IF_NULL(match->cp_text[CPT_ABBR]));
- tv_dict_add_str(di, S_LEN("menu"), EMPTY_IF_NULL(match->cp_text[CPT_MENU]));
- tv_dict_add_str(di, S_LEN("kind"), EMPTY_IF_NULL(match->cp_text[CPT_KIND]));
- tv_dict_add_str(di, S_LEN("info"), EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
- if (match->cp_user_data.v_type == VAR_UNKNOWN) {
- // Add an empty string for backwards compatibility
- tv_dict_add_str(di, S_LEN("user_data"), "");
- } else {
- tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data);
- }
- }
- match = match->cp_next;
- } while (match != NULL && !is_first_match(match));
+ if (ret == OK) {
+ ret = info_add_completion_info(li);
}
}
@@ -2878,20 +2896,20 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
st->dict_f = DICT_EXACT;
}
if (!shortmess(SHM_COMPLETIONSCAN)) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"),
st->ins_buf->b_fname == NULL
? buf_spname(st->ins_buf)
: st->ins_buf->b_sfname == NULL
? st->ins_buf->b_fname
: st->ins_buf->b_sfname);
- (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ (void)msg_trunc(IObuff, true, HL_ATTR(HLF_R));
}
} else if (*st->e_cpt == NUL) {
status = INS_COMPL_CPT_END;
} else {
if (ctrl_x_mode_line_or_eval()) {
- compl_type = -1;
+ // compl_type = -1;
} else if (*st->e_cpt == 'k' || *st->e_cpt == 's') {
if (*st->e_cpt == 'k') {
compl_type = CTRL_X_DICTIONARY;
@@ -2906,15 +2924,15 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
compl_type = CTRL_X_PATH_PATTERNS;
} else if (*st->e_cpt == 'd') {
compl_type = CTRL_X_PATH_DEFINES;
+ } else if (*st->e_cpt == 'f') {
+ compl_type = CTRL_X_BUFNAMES;
} else if (*st->e_cpt == ']' || *st->e_cpt == 't') {
compl_type = CTRL_X_TAGS;
if (!shortmess(SHM_COMPLETIONSCAN)) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags."));
- (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ (void)msg_trunc(IObuff, true, HL_ATTR(HLF_R));
}
- } else {
- compl_type = -1;
}
// in any case e_cpt is advanced to the next entry
@@ -2940,7 +2958,7 @@ static void get_next_include_file_completion(int compl_type)
((compl_type == CTRL_X_PATH_DEFINES
&& !(compl_cont_status & CONT_SOL))
? FIND_DEFINE : FIND_ANY),
- 1L, ACTION_EXPAND, 1, MAXLNUM);
+ 1, ACTION_EXPAND, 1, MAXLNUM);
}
/// Get the next set of words matching "compl_pattern" in dictionary or
@@ -2950,11 +2968,11 @@ static void get_next_dict_tsr_completion(int compl_type, char *dict, int dict_f)
if (thesaurus_func_complete(compl_type)) {
expand_by_function(compl_type, compl_pattern);
} else {
- ins_compl_dictionaries(dict != NULL ? dict
+ ins_compl_dictionaries(dict != NULL
+ ? dict
: (compl_type == CTRL_X_THESAURUS
? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr)
- : (*curbuf->b_p_dict ==
- NUL ? p_dict : curbuf->b_p_dict)),
+ : (*curbuf->b_p_dict == NUL ? p_dict : curbuf->b_p_dict)),
compl_pattern,
dict != NULL ? dict_f : 0,
compl_type == CTRL_X_THESAURUS);
@@ -3043,18 +3061,18 @@ static void get_next_spell_completion(linenr_T lnum)
/// @param cur_match_pos current match position
/// @param match_len
/// @param cont_s_ipos next ^X<> will set initial_pos
-static char *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len,
- bool *cont_s_ipos)
+static char *ins_compl_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len,
+ bool *cont_s_ipos)
{
*match_len = 0;
- char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum, false) + cur_match_pos->col;
+ char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum) + cur_match_pos->col;
int len;
if (ctrl_x_mode_line_or_eval()) {
if (compl_status_adding()) {
if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) {
return NULL;
}
- ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false);
+ ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1);
if (!p_paste) {
ptr = skipwhite(ptr);
}
@@ -3082,7 +3100,7 @@ static char *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos
// normal command "J" was used. IOSIZE is always greater than
// compl_length, so the next strncpy always works -- Acevedo
strncpy(IObuff, ptr, (size_t)len); // NOLINT(runtime/printf)
- ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false);
+ ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1);
tmp_ptr = ptr = skipwhite(ptr);
// Find start of next word.
tmp_ptr = find_word_start(tmp_ptr);
@@ -3151,7 +3169,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
}
bool looped_around = false;
int found_new_match = FAIL;
- for (;;) {
+ while (true) {
bool cont_s_ipos = false;
msg_silent++; // Don't want messages for wrapscan.
@@ -3162,7 +3180,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
compl_direction, compl_pattern);
} else {
found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos,
- NULL, compl_direction, compl_pattern, 1L,
+ NULL, compl_direction, compl_pattern, 1,
SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL);
}
msg_silent--;
@@ -3206,8 +3224,8 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
continue;
}
int len;
- char *ptr = ins_comp_get_next_word_or_line(st->ins_buf, st->cur_match_pos,
- &len, &cont_s_ipos);
+ char *ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos,
+ &len, &cont_s_ipos);
if (ptr == NULL) {
continue;
}
@@ -3265,6 +3283,9 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
case CTRL_X_SPELL:
get_next_spell_completion(st->first_match_pos.lnum);
break;
+ case CTRL_X_BUFNAMES:
+ get_next_bufname_token();
+ break;
default: // normal ^P/^N and ^X^L
found_new_match = get_next_default_completion(st, ini);
@@ -3282,6 +3303,19 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
return found_new_match;
}
+static void get_next_bufname_token(void)
+{
+ FOR_ALL_BUFFERS(b) {
+ if (b->b_p_bl && b->b_sfname != NULL) {
+ char *tail = path_tail(b->b_sfname);
+ if (strncmp(tail, compl_orig_text, strlen(compl_orig_text)) == 0) {
+ ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0,
+ p_ic ? CP_ICASE : 0, false);
+ }
+ }
+ }
+}
+
/// Get the next expansion(s), using "compl_pattern".
/// The search starts at position "ini" in curbuf and in the direction
/// compl_direction.
@@ -3292,7 +3326,7 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
static int ins_compl_get_exp(pos_T *ini)
{
static ins_compl_next_state_T st;
- int i;
+ static bool st_cleared = false;
int found_new_match;
int type = ctrl_x_mode;
@@ -3302,9 +3336,16 @@ static int ins_compl_get_exp(pos_T *ini)
FOR_ALL_BUFFERS(buf) {
buf->b_scanned = false;
}
+ if (!st_cleared) {
+ CLEAR_FIELD(st);
+ st_cleared = true;
+ }
st.found_all = false;
st.ins_buf = curbuf;
- st.e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt;
+ xfree(st.e_cpt_copy);
+ // Make a copy of 'complete', in case the buffer is wiped out.
+ st.e_cpt_copy = xstrdup((compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt);
+ st.e_cpt = st.e_cpt_copy;
st.last_match_pos = st.first_match_pos = *ini;
} else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) {
st.ins_buf = curbuf; // In case the buffer was wiped out.
@@ -3315,7 +3356,7 @@ static int ins_compl_get_exp(pos_T *ini)
st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos;
// For ^N/^P loop over all the flags/windows/buffers in 'complete'
- for (;;) {
+ while (true) {
found_new_match = FAIL;
st.set_match_pos = false;
@@ -3361,7 +3402,7 @@ static int ins_compl_get_exp(pos_T *ini)
compl_started = true;
} else {
// Mark a buffer scanned when it has been scanned completely
- if (type == 0 || type == CTRL_X_PATH_PATTERNS) {
+ if (buf_valid(st.ins_buf) && (type == 0 || type == CTRL_X_PATH_PATTERNS)) {
assert(st.ins_buf);
st.ins_buf->b_scanned = true;
}
@@ -3376,7 +3417,7 @@ static int ins_compl_get_exp(pos_T *ini)
found_new_match = FAIL;
}
- i = -1; // total of matches, unknown
+ int i = -1; // total of matches, unknown
if (found_new_match == FAIL
|| (ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval())) {
i = ins_compl_make_cyclic();
@@ -3387,8 +3428,8 @@ static int ins_compl_get_exp(pos_T *ini)
// just been made cyclic then we have to move compl_curr_match to the
// next or previous entry (if any) -- Acevedo
compl_curr_match = compl_dir_forward()
- ? compl_old_match->cp_next
- : compl_old_match->cp_prev;
+ ? compl_old_match->cp_next
+ : compl_old_match->cp_prev;
if (compl_curr_match == NULL) {
compl_curr_match = compl_old_match;
}
@@ -3426,11 +3467,9 @@ static void ins_compl_update_shown_match(void)
/// Delete the old text being completed.
void ins_compl_delete(void)
{
- int col;
-
// In insert mode: Delete the typed part.
// In replace mode: Put the old characters back, if any.
- col = compl_col + (compl_status_adding() ? compl_length : 0);
+ int col = compl_col + (compl_status_adding() ? compl_length : 0);
if ((int)curwin->w_cursor.col > col) {
if (stop_arrow() == FAIL) {
return;
@@ -3440,7 +3479,7 @@ void ins_compl_delete(void)
// TODO(vim): is this sufficient for redrawing? Redrawing everything
// causes flicker, thus we can't do that.
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
// clear v:completed_item
set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
}
@@ -3484,7 +3523,7 @@ static void ins_compl_show_filename(void)
msg_hist_off = true;
vim_snprintf(IObuff, IOSIZE, "%s %s%s", lead,
s > compl_shown_match->cp_fname ? "<" : "", s);
- msg(IObuff);
+ msg(IObuff, 0);
msg_hist_off = false;
redraw_cmdline = false; // don't overwrite!
}
@@ -3604,6 +3643,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
int num_matches = -1;
int todo = count;
const bool started = compl_started;
+ buf_T *const orig_curbuf = curbuf;
// When user complete function return -1 for findstart which is next
// time of 'always', compl_shown_match become NULL.
@@ -3617,7 +3657,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
}
if (allow_get_expansion && insert_match
- && (!(compl_get_longest || compl_restarting) || compl_used_match)) {
+ && (!compl_get_longest || compl_used_match)) {
// Delete old text to be replaced
ins_compl_delete();
}
@@ -3639,6 +3679,12 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
return -1;
}
+ if (curbuf != orig_curbuf) {
+ // In case some completion function switched buffer, don't want to
+ // insert the completion elsewhere.
+ return -1;
+ }
+
// Insert the text of the new completion, or the compl_leader.
if (compl_no_insert && !started) {
ins_bytes(compl_orig_text + get_compl_len());
@@ -3768,15 +3814,13 @@ static bool ins_compl_pum_key(int c)
/// Returns 1 for most keys, height of the popup menu for page-up/down keys.
static int ins_compl_key2count(int c)
{
- int h;
-
if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
int offset = pum_want.item - pum_selected_item;
return abs(offset);
}
if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) {
- h = pum_get_height();
+ int h = pum_get_height();
if (h > 3) {
h -= 2; // keep some context
}
@@ -4304,9 +4348,8 @@ static void ins_compl_show_statusmsg(void)
if (edit_submode_extra != NULL) {
if (!p_smd) {
msg_hist_off = true;
- msg_attr((const char *)edit_submode_extra,
- (edit_submode_highl < HLF_COUNT
- ? HL_ATTR(edit_submode_highl) : 0));
+ msg(edit_submode_extra, (edit_submode_highl < HLF_COUNT
+ ? HL_ATTR(edit_submode_highl) : 0));
msg_hist_off = false;
}
} else {
@@ -4320,13 +4363,8 @@ static void ins_compl_show_statusmsg(void)
/// Returns OK if completion was done, FAIL if something failed.
int ins_complete(int c, bool enable_pum)
{
- int n;
- int save_w_wrow;
- int save_w_leftcol;
- int insert_match;
-
compl_direction = ins_compl_key2dir(c);
- insert_match = ins_compl_use_match(c);
+ int insert_match = ins_compl_use_match(c);
if (!compl_started) {
if (ins_compl_start() == FAIL) {
@@ -4340,9 +4378,9 @@ int ins_complete(int c, bool enable_pum)
compl_shows_dir = compl_direction;
// Find next match (and following matches).
- save_w_wrow = curwin->w_wrow;
- save_w_leftcol = curwin->w_leftcol;
- n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false);
+ int save_w_wrow = curwin->w_wrow;
+ int save_w_leftcol = curwin->w_leftcol;
+ int n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false);
if (n > 1) { // all matches have been found
compl_matches = n;
diff --git a/src/nvim/insexpand.h b/src/nvim/insexpand.h
index 83ba14e0d2..121d5568ff 100644
--- a/src/nvim/insexpand.h
+++ b/src/nvim/insexpand.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_INSEXPAND_H
-#define NVIM_INSEXPAND_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/macros.h"
-#include "nvim/vim.h"
+#include "nvim/macros_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "insexpand.h.generated.h"
#endif
-#endif // NVIM_INSEXPAND_H
diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c
index e19806e464..745500fe39 100644
--- a/src/nvim/keycodes.c
+++ b/src/nvim/keycodes.c
@@ -1,29 +1,25 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
-#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "keycodes.c.generated.h"
@@ -34,18 +30,18 @@
static const struct modmasktable {
uint16_t mod_mask; ///< Bit-mask for particular key modifier.
uint16_t mod_flag; ///< Bit(s) for particular key modifier.
- char_u name; ///< Single letter name of modifier.
+ char name; ///< Single letter name of modifier.
} mod_mask_table[] = {
- { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M' },
- { MOD_MASK_META, MOD_MASK_META, (char_u)'T' },
- { MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C' },
- { MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S' },
- { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2' },
- { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3' },
- { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4' },
- { MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D' },
+ { MOD_MASK_ALT, MOD_MASK_ALT, 'M' },
+ { MOD_MASK_META, MOD_MASK_META, 'T' },
+ { MOD_MASK_CTRL, MOD_MASK_CTRL, 'C' },
+ { MOD_MASK_SHIFT, MOD_MASK_SHIFT, 'S' },
+ { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, '2' },
+ { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, '3' },
+ { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, '4' },
+ { MOD_MASK_CMD, MOD_MASK_CMD, 'D' },
// 'A' must be the last one
- { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A' },
+ { MOD_MASK_ALT, MOD_MASK_ALT, 'A' },
{ 0, 0, NUL }
// NOTE: when adding an entry, update MAX_KEY_NAME_LEN!
};
@@ -386,7 +382,7 @@ int name_to_mod_mask(int c)
{
c = TOUPPER_ASC(c);
for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) {
- if (c == mod_mask_table[i].name) {
+ if (c == (uint8_t)mod_mask_table[i].name) {
return mod_mask_table[i].mod_flag;
}
}
@@ -468,16 +464,12 @@ int handle_x_keys(const int key)
}
/// @return a string which contains the name of the given key when the given modifiers are down.
-char_u *get_special_key_name(int c, int modifiers)
+char *get_special_key_name(int c, int modifiers)
{
- static char_u string[MAX_KEY_NAME_LEN + 1];
-
- int i, idx;
- int table_idx;
- char *s;
+ static char string[MAX_KEY_NAME_LEN + 1];
string[0] = '<';
- idx = 1;
+ int idx = 1;
// Key that stands for a normal character.
if (IS_SPECIAL(c) && KEY2TERMCAP0(c) == KS_KEY) {
@@ -487,7 +479,7 @@ char_u *get_special_key_name(int c, int modifiers)
// Translate shifted special keys into unshifted keys and set modifier.
// Same for CTRL and ALT modifiers.
if (IS_SPECIAL(c)) {
- for (i = 0; modifier_keys_table[i] != 0; i += MOD_KEYS_ENTRY_SIZE) {
+ for (int i = 0; modifier_keys_table[i] != 0; i += MOD_KEYS_ENTRY_SIZE) {
if (KEY2TERMCAP0(c) == (int)modifier_keys_table[i + 1]
&& (int)KEY2TERMCAP1(c) == (int)modifier_keys_table[i + 2]) {
modifiers |= modifier_keys_table[i];
@@ -499,7 +491,7 @@ char_u *get_special_key_name(int c, int modifiers)
}
// try to find the key in the special key table
- table_idx = find_special_key_in_table(c);
+ int table_idx = find_special_key_in_table(c);
// When not a known special key, and not a printable character, try to
// extract modifiers.
@@ -520,11 +512,11 @@ char_u *get_special_key_name(int c, int modifiers)
}
// translate the modifier into a string
- for (i = 0; mod_mask_table[i].name != 'A'; i++) {
+ for (int i = 0; mod_mask_table[i].name != 'A'; i++) {
if ((modifiers & mod_mask_table[i].mod_mask)
== mod_mask_table[i].mod_flag) {
string[idx++] = mod_mask_table[i].name;
- string[idx++] = (char_u)'-';
+ string[idx++] = '-';
}
}
@@ -532,18 +524,18 @@ char_u *get_special_key_name(int c, int modifiers)
if (IS_SPECIAL(c)) {
string[idx++] = 't';
string[idx++] = '_';
- string[idx++] = (char_u)KEY2TERMCAP0(c);
- string[idx++] = KEY2TERMCAP1(c);
+ string[idx++] = (char)(uint8_t)KEY2TERMCAP0(c);
+ string[idx++] = (char)(uint8_t)KEY2TERMCAP1(c);
} else {
// Not a special key, only modifiers, output directly.
if (utf_char2len(c) > 1) {
- idx += utf_char2bytes(c, (char *)string + idx);
+ idx += utf_char2bytes(c, string + idx);
} else if (vim_isprintc(c)) {
- string[idx++] = (char_u)c;
+ string[idx++] = (char)(uint8_t)c;
} else {
- s = transchar(c);
+ char *s = transchar(c);
while (*s) {
- string[idx++] = (uint8_t)(*s++);
+ string[idx++] = *s++;
}
}
}
@@ -572,8 +564,8 @@ char_u *get_special_key_name(int c, int modifiers)
/// @param[out] did_simplify found <C-H>, etc.
///
/// @return Number of characters added to dst, zero for no match.
-unsigned int trans_special(const char **const srcp, const size_t src_len, char *const dst,
- const int flags, const bool escape_ks, bool *const did_simplify)
+unsigned trans_special(const char **const srcp, const size_t src_len, char *const dst,
+ const int flags, const bool escape_ks, bool *const did_simplify)
FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
int modifiers = 0;
@@ -582,7 +574,7 @@ unsigned int trans_special(const char **const srcp, const size_t src_len, char *
return 0;
}
- return special_to_buf(key, modifiers, escape_ks, (char_u *)dst);
+ return special_to_buf(key, modifiers, escape_ks, dst);
}
/// Put the character sequence for "key" with "modifiers" into "dst" and return
@@ -590,27 +582,27 @@ unsigned int trans_special(const char **const srcp, const size_t src_len, char *
/// When "escape_ks" is true escape K_SPECIAL bytes in the character.
/// The sequence is not NUL terminated.
/// This is how characters in a string are encoded.
-unsigned int special_to_buf(int key, int modifiers, bool escape_ks, char_u *dst)
+unsigned special_to_buf(int key, int modifiers, bool escape_ks, char *dst)
{
- unsigned int dlen = 0;
+ unsigned dlen = 0;
// Put the appropriate modifier in a string.
if (modifiers != 0) {
- dst[dlen++] = K_SPECIAL;
- dst[dlen++] = KS_MODIFIER;
- dst[dlen++] = (char_u)modifiers;
+ dst[dlen++] = (char)(uint8_t)K_SPECIAL;
+ dst[dlen++] = (char)(uint8_t)KS_MODIFIER;
+ dst[dlen++] = (char)(uint8_t)modifiers;
}
if (IS_SPECIAL(key)) {
- dst[dlen++] = K_SPECIAL;
- dst[dlen++] = (char_u)KEY2TERMCAP0(key);
- dst[dlen++] = KEY2TERMCAP1(key);
+ dst[dlen++] = (char)(uint8_t)K_SPECIAL;
+ dst[dlen++] = (char)(uint8_t)KEY2TERMCAP0(key);
+ dst[dlen++] = (char)(uint8_t)KEY2TERMCAP1(key);
} else if (escape_ks) {
- char_u *after = add_char2buf(key, dst + dlen);
+ char *after = add_char2buf(key, dst + dlen);
assert(after >= dst && (uintmax_t)(after - dst) <= UINT_MAX);
- dlen = (unsigned int)(after - dst);
+ dlen = (unsigned)(after - dst);
} else {
- dlen += (unsigned int)utf_char2bytes(key, (char *)dst + dlen);
+ dlen += (unsigned)utf_char2bytes(key, dst + dlen);
}
return dlen;
@@ -629,15 +621,9 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
const int flags, bool *const did_simplify)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3)
{
- const char *last_dash;
- const char *end_of_name;
- const char *src;
const char *bp;
const char *const end = *srcp + src_len - 1;
const bool in_string = flags & FSK_IN_STRING;
- int modifiers;
- int bit;
- int key;
uvarnumber_T n;
int l;
@@ -645,7 +631,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
return 0;
}
- src = *srcp;
+ const char *src = *srcp;
if (src[0] != '<') {
return 0;
}
@@ -654,7 +640,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
}
// Find end of modifier list
- last_dash = src;
+ const char *last_dash = src;
for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) {
if (*bp == '-') {
last_dash = bp;
@@ -674,7 +660,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') {
bp += 3; // skip t_xx, xx may be '-' or '>'
} else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) {
- vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, true);
+ vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, true, NULL);
if (l == 0) {
emsg(_(e_invarg));
return 0;
@@ -685,13 +671,14 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
}
if (bp <= end && *bp == '>') { // found matching '>'
- end_of_name = bp + 1;
+ int key;
+ const char *end_of_name = bp + 1;
// Which modifiers are given?
- modifiers = 0x0;
+ int modifiers = 0x0;
for (bp = src + 1; bp < last_dash; bp++) {
if (*bp != '-') {
- bit = name_to_mod_mask((uint8_t)(*bp));
+ int bit = name_to_mod_mask((uint8_t)(*bp));
if (bit == 0x0) {
break; // Illegal modifier name
}
@@ -704,7 +691,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
if (STRNICMP(last_dash + 1, "char-", 5) == 0
&& ascii_isdigit(last_dash[6])) {
// <Char-123> or <Char-033> or <Char-0x33>
- vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL, &n, 0, true);
+ vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL, &n, 0, true, NULL);
if (l == 0) {
emsg(_(e_invarg));
return 0;
@@ -723,7 +710,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
if (modifiers != 0 && last_dash[l + 1] == '>') {
key = utf_ptr2char(last_dash + off);
} else {
- key = get_special_key_code((char_u *)last_dash + off);
+ key = get_special_key_code(last_dash + off);
if (!(flags & FSK_KEEP_X_KEY)) {
key = handle_x_keys(key);
}
@@ -822,18 +809,22 @@ int find_special_key_in_table(int c)
/// a termcap name.
///
/// @return Key code or 0 if not found.
-int get_special_key_code(const char_u *name)
+int get_special_key_code(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
+ if (name[0] == 't' && name[1] == '_' && name[2] != NUL && name[3] != NUL) {
+ return TERMCAP2KEY((uint8_t)name[2], (uint8_t)name[3]);
+ }
+
for (int i = 0; key_names_table[i].name != NULL; i++) {
const char *const table_name = key_names_table[i].name;
int j;
- for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) {
- if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) {
+ for (j = 0; ascii_isident((uint8_t)name[j]) && table_name[j] != NUL; j++) {
+ if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC((uint8_t)name[j])) {
break;
}
}
- if (!ascii_isident(name[j]) && table_name[j] == NUL) {
+ if (!ascii_isident((uint8_t)name[j]) && table_name[j] == NUL) {
return key_names_table[i].key;
}
}
@@ -845,9 +836,7 @@ int get_special_key_code(const char_u *name)
/// @return which button is down or was released.
int get_mouse_button(int code, bool *is_click, bool *is_drag)
{
- int i;
-
- for (i = 0; mouse_table[i].pseudo_code; i++) {
+ for (int i = 0; mouse_table[i].pseudo_code; i++) {
if (code == mouse_table[i].pseudo_code) {
*is_click = mouse_table[i].is_click;
*is_drag = mouse_table[i].is_drag;
@@ -873,25 +862,24 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag)
/// If `*bufp` is non-NULL, it will be used directly,
/// and is assumed to be 128 bytes long (enough for transcoding LHS of mapping),
/// and will be set to NULL in case of failure.
+/// @param[in] sid_arg Script ID to use for <SID>, or 0 to use current_sctx
/// @param[in] flags REPTERM_FROM_PART see above
/// REPTERM_DO_LT also translate <lt>
/// REPTERM_NO_SPECIAL do not accept <key> notation
/// REPTERM_NO_SIMPLIFY do not simplify <C-H> into 0x08, etc.
-/// @param[out] did_simplify set when some <C-H> code was simplied, unless it is NULL.
+/// @param[out] did_simplify set when some <C-H> code was simplified, unless it is NULL.
/// @param[in] cpo_flags Relevant flags derived from p_cpo, see CPO_TO_CPO_FLAGS.
///
/// @return The same as what `*bufp` is set to.
char *replace_termcodes(const char *const from, const size_t from_len, char **const bufp,
- const int flags, bool *const did_simplify, const int cpo_flags)
+ const scid_T sid_arg, const int flags, bool *const did_simplify,
+ const int cpo_flags)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
- ssize_t i;
- size_t slen;
char key;
size_t dlen = 0;
const char *src;
const char *const end = from + from_len - 1;
- char *result; // buffer for resulting string
const bool do_backslash = !(cpo_flags & FLAG_CPO_BSLASH); // backslash is a special character
const bool do_special = !(flags & REPTERM_NO_SPECIAL);
@@ -901,23 +889,10 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
// Allocate space for the translation. Worst case a single character is
// replaced by 6 bytes (shifted special key), plus a NUL at the end.
const size_t buf_len = allocated ? from_len * 6 + 1 : 128;
- result = allocated ? xmalloc(buf_len) : *bufp;
+ char *result = allocated ? xmalloc(buf_len) : *bufp; // buffer for resulting string
src = from;
- // Check for #n at start only: function key n
- if ((flags & REPTERM_FROM_PART) && from_len > 1 && src[0] == '#'
- && ascii_isdigit(src[1])) { // function key
- result[dlen++] = (char)K_SPECIAL;
- result[dlen++] = 'k';
- if (src[1] == '0') {
- result[dlen++] = ';'; // #0 is F10 is "k;"
- } else {
- result[dlen++] = src[1]; // #3 is F3 is "k3"
- }
- src += 2;
- }
-
// Copy each byte from *from to result[dlen]
while (src <= end) {
if (!allocated && dlen + 64 > buf_len) {
@@ -926,27 +901,28 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
// Check for special <> keycodes, like "<C-S-LeftMouse>"
if (do_special && ((flags & REPTERM_DO_LT) || ((end - src) >= 3
&& strncmp(src, "<lt>", 4) != 0))) {
- // Replace <SID> by K_SNR <script-nr> _.
+ // Change <SID>Func to K_SNR <script-nr> _Func. This name is used
+ // for script-local user functions.
// (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14)
if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) {
- if (current_sctx.sc_sid <= 0) {
+ if (sid_arg < 0 || (sid_arg == 0 && current_sctx.sc_sid <= 0)) {
emsg(_(e_usingsid));
} else {
+ const scid_T sid = sid_arg != 0 ? sid_arg : current_sctx.sc_sid;
src += 5;
result[dlen++] = (char)K_SPECIAL;
result[dlen++] = (char)KS_EXTRA;
result[dlen++] = KE_SNR;
- snprintf(result + dlen, buf_len - dlen, "%" PRId64,
- (int64_t)current_sctx.sc_sid);
+ snprintf(result + dlen, buf_len - dlen, "%" PRId64, (int64_t)sid);
dlen += strlen(result + dlen);
result[dlen++] = '_';
continue;
}
}
- slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen,
- FSK_KEYCODE | ((flags & REPTERM_NO_SIMPLIFY) ? 0 : FSK_SIMPLIFY),
- true, did_simplify);
+ size_t slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen,
+ FSK_KEYCODE | ((flags & REPTERM_NO_SIMPLIFY) ? 0 : FSK_SIMPLIFY),
+ true, did_simplify);
if (slen) {
dlen += slen;
continue;
@@ -1002,7 +978,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
}
// skip multibyte char correctly
- for (i = utfc_ptr2len_len((char *)src, (int)(end - src) + 1); i > 0; i--) {
+ for (ssize_t i = utfc_ptr2len_len(src, (int)(end - src) + 1); i > 0; i--) {
// If the character is K_SPECIAL, replace it with K_SPECIAL
// KS_SPECIAL KE_FILLER.
if (*src == (char)K_SPECIAL) {
@@ -1033,20 +1009,20 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes.
///
/// @return Pointer to after the added bytes.
-char_u *add_char2buf(int c, char_u *s)
+char *add_char2buf(int c, char *s)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- char_u temp[MB_MAXBYTES + 1];
- const int len = utf_char2bytes(c, (char *)temp);
+ char temp[MB_MAXBYTES + 1];
+ const int len = utf_char2bytes(c, temp);
for (int i = 0; i < len; i++) {
c = (uint8_t)temp[i];
// Need to escape K_SPECIAL like in the typeahead buffer.
if (c == K_SPECIAL) {
- *s++ = K_SPECIAL;
- *s++ = KS_SPECIAL;
+ *s++ = (char)(uint8_t)K_SPECIAL;
+ *s++ = (char)(uint8_t)KS_SPECIAL;
*s++ = KE_FILLER;
} else {
- *s++ = (char_u)c;
+ *s++ = (char)(uint8_t)c;
}
}
return s;
@@ -1060,9 +1036,9 @@ char *vim_strsave_escape_ks(char *p)
// illegal utf-8 byte:
// 0xc0 -> 0xc3 - 0x80 -> 0xc3 K_SPECIAL KS_SPECIAL KE_FILLER
char *res = xmalloc(strlen(p) * 4 + 1);
- char_u *d = (char_u *)res;
- for (char_u *s = (char_u *)p; *s != NUL;) {
- if (s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
+ char *d = res;
+ for (char *s = p; *s != NUL;) {
+ if ((uint8_t)s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
// Copy special key unmodified.
*d++ = *s++;
*d++ = *s++;
@@ -1070,8 +1046,8 @@ char *vim_strsave_escape_ks(char *p)
} else {
// Add character, possibly multi-byte to destination, escaping
// K_SPECIAL. Be careful, it can be an illegal byte!
- d = add_char2buf(utf_ptr2char((char *)s), d);
- s += utf_ptr2len((char *)s);
+ d = add_char2buf(utf_ptr2char(s), d);
+ s += utf_ptr2len(s);
}
}
*d = NUL;
@@ -1081,9 +1057,9 @@ char *vim_strsave_escape_ks(char *p)
/// Remove escaping from K_SPECIAL characters. Reverse of
/// vim_strsave_escape_ks(). Works in-place.
-void vim_unescape_ks(char_u *p)
+void vim_unescape_ks(char *p)
{
- char_u *s = p, *d = p;
+ uint8_t *s = (uint8_t *)p, *d = (uint8_t *)p;
while (*s != NUL) {
if (s[0] == K_SPECIAL && s[1] == KS_SPECIAL && s[2] == KE_FILLER) {
@@ -1095,15 +1071,3 @@ void vim_unescape_ks(char_u *p)
}
*d = NUL;
}
-
-/// Logs a single key as a human-readable keycode.
-void log_key(int log_level, int key)
-{
- if (log_level < MIN_LOG_LEVEL) {
- return;
- }
- char *keyname = key == K_EVENT
- ? "K_EVENT"
- : (char *)get_special_key_name(key, mod_mask);
- LOG(log_level, "input: %s", keyname);
-}
diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h
index 7842dee92c..db9ef38cc3 100644
--- a/src/nvim/keycodes.h
+++ b/src/nvim/keycodes.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_KEYCODES_H
-#define NVIM_KEYCODES_H
+#pragma once
#include <stddef.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/strings.h"
// Keycode definitions for special keys.
@@ -59,7 +59,7 @@
// Used for switching Select mode back on after a mapping or menu.
#define KS_SELECT 245
-#define K_SELECT_STRING (char_u *)"\200\365X"
+#define K_SELECT_STRING "\200\365X"
/// Used a termcap entry that produces a normal character.
#define KS_KEY 242
@@ -500,4 +500,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "keycodes.h.generated.h"
#endif
-#endif // NVIM_KEYCODES_H
diff --git a/src/nvim/lib/queue.h b/src/nvim/lib/queue.h
index c6d3f74ec1..40769e44b5 100644
--- a/src/nvim/lib/queue.h
+++ b/src/nvim/lib/queue.h
@@ -17,8 +17,7 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#ifndef NVIM_LIB_QUEUE_H
-#define NVIM_LIB_QUEUE_H
+#pragma once
#include <stddef.h>
@@ -91,5 +90,3 @@ static inline void QUEUE_REMOVE(QUEUE *const q) FUNC_ATTR_ALWAYS_INLINE
q->prev->next = q->next;
q->next->prev = q->prev;
}
-
-#endif // NVIM_LIB_QUEUE_H
diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h
index 907018a06e..c8abccfeb4 100644
--- a/src/nvim/lib/ringbuf.h
+++ b/src/nvim/lib/ringbuf.h
@@ -12,8 +12,8 @@
/// - idx_p: get pointer to the element at given index.
/// - insert: insert element at given position.
/// - remove: remove element from given position.
-#ifndef NVIM_LIB_RINGBUF_H
-#define NVIM_LIB_RINGBUF_H
+
+#pragma once
#include <assert.h>
#include <stddef.h>
@@ -288,5 +288,3 @@
rb->first = _RINGBUF_NEXT(rb, rb->first); \
} \
}
-
-#endif // NVIM_LIB_RINGBUF_H
diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c
index a9dac40731..c34b303193 100644
--- a/src/nvim/linematch.c
+++ b/src/nvim/linematch.c
@@ -1,23 +1,28 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <math.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <string.h>
#include "nvim/linematch.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
+#include "nvim/pos_defs.h"
+
+#define LN_MAX_BUFS 8
+#define LN_DECISION_MAX 255 // pow(2, LN_MAX_BUFS(8)) - 1 = 255
// struct for running the diff linematch algorithm
-typedef struct {
- int *df_decision; // to keep track of this path traveled
+typedef struct diffcmppath_S diffcmppath_T;
+struct diffcmppath_S {
int df_lev_score; // to keep track of the total score of this path
- size_t df_path_idx; // current index of this path
-} diffcmppath_T;
-
-#define LN_MAX_BUFS 8
+ size_t df_path_n; // current index of this path
+ int df_choice_mem[LN_DECISION_MAX + 1];
+ int df_choice[LN_DECISION_MAX];
+ diffcmppath_T *df_decision[LN_DECISION_MAX]; // to keep track of this path traveled
+ size_t df_optimal_choice;
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "linematch.c.generated.h"
@@ -64,26 +69,6 @@ static int matching_chars_iwhite(const char *s1, const char *s2)
return matching;
}
-/// update the path of a point in the diff linematch algorithm
-/// @param diffcmppath
-/// @param score
-/// @param to
-/// @param from
-/// @param choice
-static void update_path_flat(diffcmppath_T *diffcmppath, int score, size_t to, size_t from,
- const int choice)
-{
- size_t path_idx = diffcmppath[from].df_path_idx;
-
- for (size_t k = 0; k < path_idx; k++) {
- diffcmppath[to].df_decision[k] = diffcmppath[from].df_decision[k];
- }
-
- diffcmppath[to].df_decision[path_idx] = choice;
- diffcmppath[to].df_lev_score = score;
- diffcmppath[to].df_path_idx = path_idx + 1;
-}
-
#define MATCH_CHAR_MAX_LEN 800
/// Return matching characters between "s1" and "s2" whilst respecting sequence order.
@@ -157,10 +142,13 @@ static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
return matched_chars;
}
-void fastforward_buf_to_lnum(const char **s, long lnum)
+void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
{
- for (long i = 0; i < lnum - 1; i++) {
+ for (int i = 0; i < lnum - 1; i++) {
*s = strchr(*s, '\n');
+ if (!*s) {
+ return;
+ }
(*s)++;
}
}
@@ -183,7 +171,7 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
{
if (path_idx == npaths) {
if ((*choice) > 0) {
- int from_vals[LN_MAX_BUFS];
+ int from_vals[LN_MAX_BUFS] = { 0 };
const int *to_vals = df_iters;
const char *current_lines[LN_MAX_BUFS];
for (size_t k = 0; k < ndiffs; k++) {
@@ -203,17 +191,14 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
int matched_chars = count_n_matched_chars(current_lines, ndiffs, iwhite);
int score = diffcmppath[unwrapped_idx_from].df_lev_score + matched_chars;
if (score > diffcmppath[unwrapped_idx_to].df_lev_score) {
- update_path_flat(diffcmppath, score, unwrapped_idx_to, unwrapped_idx_from, *choice);
- }
- } else {
- // initialize the 0, 0, 0 ... choice
- size_t i = 0;
- while (i < ndiffs && df_iters[i] == 0) {
- i++;
- if (i == ndiffs) {
- diffcmppath[0].df_lev_score = 0;
- diffcmppath[0].df_path_idx = 0;
- }
+ diffcmppath[unwrapped_idx_to].df_path_n = 1;
+ diffcmppath[unwrapped_idx_to].df_decision[0] = &diffcmppath[unwrapped_idx_from];
+ diffcmppath[unwrapped_idx_to].df_choice[0] = *choice;
+ diffcmppath[unwrapped_idx_to].df_lev_score = score;
+ } else if (score == diffcmppath[unwrapped_idx_to].df_lev_score) {
+ size_t k = diffcmppath[unwrapped_idx_to].df_path_n++;
+ diffcmppath[unwrapped_idx_to].df_decision[k] = &diffcmppath[unwrapped_idx_from];
+ diffcmppath[unwrapped_idx_to].df_choice[k] = *choice;
}
}
return;
@@ -242,8 +227,7 @@ static size_t unwrap_indexes(const int *values, const int *diff_len, const size_
for (size_t k = 0; k < ndiffs; k++) {
num_unwrap_scalar /= (size_t)diff_len[k] + 1;
- // (k == 0) space optimization
- int n = k == 0 ? values[k] % 2 : values[k];
+ int n = values[k];
path_idx += num_unwrap_scalar * (size_t)n;
}
return path_idx;
@@ -351,7 +335,7 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
size_t memsize_decisions = 0;
for (size_t i = 0; i < ndiffs; i++) {
assert(diff_len[i] >= 0);
- memsize *= i == 0 ? 2 : (size_t)(diff_len[i] + 1);
+ memsize *= (size_t)(diff_len[i] + 1);
memsize_decisions += (size_t)diff_len[i];
}
@@ -359,7 +343,11 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
diffcmppath_T *diffcmppath = xmalloc(sizeof(diffcmppath_T) * memsize);
// allocate memory here
for (size_t i = 0; i < memsize; i++) {
- diffcmppath[i].df_decision = xmalloc(memsize_decisions * sizeof(int));
+ diffcmppath[i].df_lev_score = 0;
+ diffcmppath[i].df_path_n = 0;
+ for (size_t j = 0; j < (size_t)pow(2, (double)ndiffs); j++) {
+ diffcmppath[i].df_choice_mem[j] = -1;
+ }
}
// memory for avoiding repetitive calculations of score
@@ -367,18 +355,49 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
const size_t u = unwrap_indexes(diff_len, diff_len, ndiffs);
- const size_t best_path_idx = diffcmppath[u].df_path_idx;
- const int *best_path_decisions = diffcmppath[u].df_decision;
+ diffcmppath_T *startNode = &diffcmppath[u];
- *decisions = xmalloc(sizeof(int) * best_path_idx);
- for (size_t i = 0; i < best_path_idx; i++) {
- (*decisions)[i] = best_path_decisions[i];
+ *decisions = xmalloc(sizeof(int) * memsize_decisions);
+ size_t n_optimal = 0;
+ test_charmatch_paths(startNode, 0);
+ while (startNode->df_path_n > 0) {
+ size_t j = startNode->df_optimal_choice;
+ (*decisions)[n_optimal++] = startNode->df_choice[j];
+ startNode = startNode->df_decision[j];
}
-
- for (size_t i = 0; i < memsize; i++) {
- xfree(diffcmppath[i].df_decision);
+ // reverse array
+ for (size_t i = 0; i < (n_optimal / 2); i++) {
+ int tmp = (*decisions)[i];
+ (*decisions)[i] = (*decisions)[n_optimal - 1 - i];
+ (*decisions)[n_optimal - 1 - i] = tmp;
}
+
xfree(diffcmppath);
- return best_path_idx;
+ return n_optimal;
+}
+
+// returns the minimum amount of path changes from start to end
+static size_t test_charmatch_paths(diffcmppath_T *node, int lastdecision)
+{
+ // memoization
+ if (node->df_choice_mem[lastdecision] == -1) {
+ if (node->df_path_n == 0) {
+ // we have reached the end of the tree
+ node->df_choice_mem[lastdecision] = 0;
+ } else {
+ size_t minimum_turns = SIZE_MAX; // the minimum amount of turns required to reach the end
+ for (size_t i = 0; i < node->df_path_n; i++) {
+ // recurse
+ size_t t = test_charmatch_paths(node->df_decision[i], node->df_choice[i]) +
+ (lastdecision != node->df_choice[i] ? 1 : 0);
+ if (t < minimum_turns) {
+ node->df_optimal_choice = i;
+ minimum_turns = t;
+ }
+ }
+ node->df_choice_mem[lastdecision] = (int)minimum_turns;
+ }
+ }
+ return (size_t)node->df_choice_mem[lastdecision];
}
diff --git a/src/nvim/linematch.h b/src/nvim/linematch.h
index 052d438617..eaf0d54bec 100644
--- a/src/nvim/linematch.h
+++ b/src/nvim/linematch.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_LINEMATCH_H
-#define NVIM_LINEMATCH_H
+#pragma once
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
+
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "linematch.h.generated.h"
#endif
-
-#endif // NVIM_LINEMATCH_H
diff --git a/src/nvim/locale.c b/src/nvim/locale.c
deleted file mode 100644
index c3cfd3bedb..0000000000
--- a/src/nvim/locale.c
+++ /dev/null
@@ -1,377 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// locale.c: functions for language/locale configuration
-
-#include <stdbool.h>
-#include <stdio.h>
-
-#include "auto/config.h"
-#ifdef HAVE_LOCALE_H
-# include <locale.h>
-#endif
-
-#include "nvim/ascii.h"
-#include "nvim/buffer.h"
-#include "nvim/charset.h"
-#include "nvim/eval.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/garray.h"
-#include "nvim/gettext.h"
-#include "nvim/locale.h"
-#include "nvim/macros.h"
-#include "nvim/memory.h"
-#include "nvim/message.h"
-#include "nvim/option.h"
-#include "nvim/os/os.h"
-#include "nvim/os/shell.h"
-#include "nvim/path.h"
-#include "nvim/profile.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "locale.c.generated.h"
-#endif
-
-#if defined(HAVE_LOCALE_H)
-# define HAVE_GET_LOCALE_VAL
-
-static char *get_locale_val(int what)
-{
- // Obtain the locale value from the libraries.
- char *loc = setlocale(what, NULL);
-
- return loc;
-}
-#endif
-
-/// @return true when "lang" starts with a valid language name.
-/// Rejects NULL, empty string, "C", "C.UTF-8" and others.
-static bool is_valid_mess_lang(const char *lang)
-{
- return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]);
-}
-
-/// Obtain the current messages language. Used to set the default for
-/// 'helplang'. May return NULL or an empty string.
-char *get_mess_lang(void)
-{
- char *p;
-
-#ifdef HAVE_GET_LOCALE_VAL
-# if defined(LC_MESSAGES)
- p = get_locale_val(LC_MESSAGES);
-# else
- // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG
- // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME
- // and LC_MONETARY may be set differently for a Japanese working in the
- // US.
- p = get_locale_val(LC_COLLATE);
-# endif
-#else
- p = os_getenv("LC_ALL");
- if (!is_valid_mess_lang(p)) {
- p = os_getenv("LC_MESSAGES");
- if (!is_valid_mess_lang(p)) {
- p = os_getenv("LANG");
- }
- }
-#endif
- return is_valid_mess_lang(p) ? p : NULL;
-}
-
-// Complicated #if; matches with where get_mess_env() is used below.
-#ifdef HAVE_WORKING_LIBINTL
-/// Get the language used for messages from the environment.
-static char *get_mess_env(void)
-{
- char *p;
-
- p = (char *)os_getenv("LC_ALL");
- if (p == NULL) {
- p = (char *)os_getenv("LC_MESSAGES");
- if (p == NULL) {
- p = (char *)os_getenv("LANG");
- if (p != NULL && ascii_isdigit(*p)) {
- p = NULL; // ignore something like "1043"
- }
-# ifdef HAVE_GET_LOCALE_VAL
- if (p == NULL) {
- p = get_locale_val(LC_CTYPE);
- }
-# endif
- }
- }
- return p;
-}
-#endif
-
-/// Set the "v:lang" variable according to the current locale setting.
-/// Also do "v:lc_time"and "v:ctype".
-void set_lang_var(void)
-{
- const char *loc;
-
-#ifdef HAVE_GET_LOCALE_VAL
- loc = get_locale_val(LC_CTYPE);
-#else
- // setlocale() not supported: use the default value
- loc = "C";
-#endif
- set_vim_var_string(VV_CTYPE, loc, -1);
-
- // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall
- // back to LC_CTYPE if it's empty.
-#ifdef HAVE_WORKING_LIBINTL
- loc = get_mess_env();
-#elif defined(LC_MESSAGES)
- loc = get_locale_val(LC_MESSAGES);
-#else
- // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE
- loc = get_locale_val(LC_CTYPE);
-#endif
- set_vim_var_string(VV_LANG, loc, -1);
-
-#ifdef HAVE_GET_LOCALE_VAL
- loc = get_locale_val(LC_TIME);
-#endif
- set_vim_var_string(VV_LC_TIME, loc, -1);
-
-#ifdef HAVE_GET_LOCALE_VAL
- loc = get_locale_val(LC_COLLATE);
-#else
- // setlocale() not supported: use the default value
- loc = "C";
-#endif
- set_vim_var_string(VV_COLLATE, loc, -1);
-}
-
-#if defined(HAVE_LOCALE_H)
-/// Setup to use the current locale (for ctype() and many other things).
-void init_locale(void)
-{
- setlocale(LC_ALL, "");
-
-# ifdef LC_NUMERIC
- // Make sure strtod() uses a decimal point, not a comma.
- setlocale(LC_NUMERIC, "C");
-# endif
-
- char localepath[MAXPATHL] = { 0 };
- snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH));
- char *tail = path_tail_with_sep(localepath);
- *tail = NUL;
- tail = path_tail(localepath);
- xstrlcpy(tail, "share/locale",
- sizeof(localepath) - (size_t)(tail - localepath));
- bindtextdomain(PROJECT_NAME, localepath);
- textdomain(PROJECT_NAME);
- TIME_MSG("locale set");
-}
-#endif
-
-#ifdef HAVE_WORKING_LIBINTL
-
-/// ":language": Set the language (locale).
-///
-/// @param eap
-void ex_language(exarg_T *eap)
-{
- char *loc;
- char *p;
- char *name;
- int what = LC_ALL;
- char *whatstr = "";
-# ifdef LC_MESSAGES
-# define VIM_LC_MESSAGES LC_MESSAGES
-# else
-# define VIM_LC_MESSAGES 6789
-# endif
-
- name = eap->arg;
-
- // Check for "messages {name}", "ctype {name}" or "time {name}" argument.
- // Allow abbreviation, but require at least 3 characters to avoid
- // confusion with a two letter language name "me" or "ct".
- p = skiptowhite(eap->arg);
- if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) {
- if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) {
- what = VIM_LC_MESSAGES;
- name = skipwhite(p);
- whatstr = "messages ";
- } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) {
- what = LC_CTYPE;
- name = skipwhite(p);
- whatstr = "ctype ";
- } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) {
- what = LC_TIME;
- name = skipwhite(p);
- whatstr = "time ";
- } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) {
- what = LC_COLLATE;
- name = skipwhite(p);
- whatstr = "collate ";
- }
- }
-
- if (*name == NUL) {
- if (what == VIM_LC_MESSAGES) {
- p = get_mess_env();
- } else {
- p = setlocale(what, NULL);
- }
- if (p == NULL || *p == NUL) {
- p = "Unknown";
- }
- smsg(_("Current %slanguage: \"%s\""), whatstr, p);
- } else {
-# ifndef LC_MESSAGES
- if (what == VIM_LC_MESSAGES) {
- loc = "";
- } else {
-# endif
- loc = setlocale(what, name);
-# ifdef LC_NUMERIC
- // Make sure strtod() uses a decimal point, not a comma.
- setlocale(LC_NUMERIC, "C");
-# endif
-# ifndef LC_MESSAGES
- }
-# endif
- if (loc == NULL) {
- semsg(_("E197: Cannot set language to \"%s\""), name);
- } else {
-# ifdef HAVE_NL_MSG_CAT_CNTR
- // Need to do this for GNU gettext, otherwise cached translations
- // will be used again.
- extern int _nl_msg_cat_cntr;
-
- _nl_msg_cat_cntr++;
-# endif
- // Reset $LC_ALL, otherwise it would overrule everything.
- os_setenv("LC_ALL", "", 1);
-
- if (what != LC_TIME && what != LC_COLLATE) {
- // Tell gettext() what to translate to. It apparently doesn't
- // use the currently effective locale.
- if (what == LC_ALL) {
- os_setenv("LANG", name, 1);
-
- // Clear $LANGUAGE because GNU gettext uses it.
- os_setenv("LANGUAGE", "", 1);
- }
- if (what != LC_CTYPE) {
- os_setenv("LC_MESSAGES", name, 1);
- set_helplang_default(name);
- }
- }
-
- // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
- set_lang_var();
- maketitle();
- }
- }
-}
-
-static char **locales = NULL; // Array of all available locales
-
-# ifndef MSWIN
-static bool did_init_locales = false;
-
-/// @return an array of strings for all available locales + NULL for the
-/// last element or,
-/// NULL in case of error.
-static char **find_locales(void)
-{
- garray_T locales_ga;
- char *loc;
- char *saveptr = NULL;
-
- // Find all available locales by running command "locale -a". If this
- // doesn't work we won't have completion.
- char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL);
- if (locale_a == NULL) {
- return NULL;
- }
- ga_init(&locales_ga, sizeof(char *), 20);
-
- // Transform locale_a string where each locale is separated by "\n"
- // into an array of locale strings.
- loc = os_strtok(locale_a, "\n", &saveptr);
-
- while (loc != NULL) {
- loc = xstrdup(loc);
- GA_APPEND(char *, &locales_ga, loc);
- loc = os_strtok(NULL, "\n", &saveptr);
- }
- xfree(locale_a);
- // Guarantee that .ga_data is NULL terminated
- ga_grow(&locales_ga, 1);
- ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL;
- return locales_ga.ga_data;
-}
-# endif
-
-/// Lazy initialization of all available locales.
-static void init_locales(void)
-{
-# ifndef MSWIN
- if (did_init_locales) {
- return;
- }
-
- did_init_locales = true;
- locales = find_locales();
-# endif
-}
-
-# if defined(EXITFREE)
-void free_locales(void)
-{
- if (locales == NULL) {
- return;
- }
-
- for (int i = 0; locales[i] != NULL; i++) {
- xfree(locales[i]);
- }
- XFREE_CLEAR(locales);
-}
-# endif
-
-/// Function given to ExpandGeneric() to obtain the possible arguments of the
-/// ":language" command.
-char *get_lang_arg(expand_T *xp, int idx)
-{
- if (idx == 0) {
- return "messages";
- }
- if (idx == 1) {
- return "ctype";
- }
- if (idx == 2) {
- return "time";
- }
- if (idx == 3) {
- return "collate";
- }
-
- init_locales();
- if (locales == NULL) {
- return NULL;
- }
- return locales[idx - 4];
-}
-
-/// Function given to ExpandGeneric() to obtain the available locales.
-char *get_locales(expand_T *xp, int idx)
-{
- init_locales();
- if (locales == NULL) {
- return NULL;
- }
- return locales[idx];
-}
-
-#endif
diff --git a/src/nvim/locale.h b/src/nvim/locale.h
deleted file mode 100644
index 39735d371f..0000000000
--- a/src/nvim/locale.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef NVIM_LOCALE_H
-#define NVIM_LOCALE_H
-
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "locale.h.generated.h"
-#endif
-#endif // NVIM_LOCALE_H
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 2c214aa32d..aeee088cd3 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// Log module
//
@@ -20,12 +17,14 @@
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/os/time.h"
@@ -76,7 +75,7 @@ static void log_path_init(void)
char *failed_dir = NULL;
bool log_dir_failure = false;
if (!os_isdir(loghome)) {
- log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0);
+ log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir, NULL) != 0);
}
XFREE_CLEAR(loghome);
// Invalid $NVIM_LOG_FILE or failed to expand; fall back to default.
@@ -131,7 +130,7 @@ void log_unlock(void)
/// @return true if log was emitted normally, false if failed or recursive
bool logmsg(int log_level, const char *context, const char *func_name, int line_num, bool eol,
const char *fmt, ...)
- FUNC_ATTR_UNUSED FUNC_ATTR_PRINTF(6, 7)
+ FUNC_ATTR_PRINTF(6, 7)
{
static bool recursive = false;
static bool did_msg = false; // Showed recursion message?
@@ -141,14 +140,28 @@ bool logmsg(int log_level, const char *context, const char *func_name, int line_
return false;
}
- if (log_level < MIN_LOG_LEVEL) {
+#ifndef NVIM_LOG_DEBUG
+ // This should rarely happen (callsites are compiled out), but to be sure.
+ // TODO(bfredl): allow log levels to be configured at runtime
+ if (log_level < LOGLVL_WRN) {
return false;
}
+#endif
#ifdef EXITFREE
// Logging after we've already started freeing all our memory will only cause
// pain. We need access to VV_PROGPATH, homedir, etc.
- assert(!entered_free_all_mem);
+ if (entered_free_all_mem) {
+ fprintf(stderr, "FATAL: error in free_all_mem\n %s %s %d: ", context, func_name, line_num);
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ if (eol) {
+ fprintf(stderr, "\n");
+ }
+ abort();
+ }
#endif
log_lock();
@@ -328,13 +341,13 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
// Print the log message.
int rv = (line_num == -1 || func_name == NULL)
- ? fprintf(log_file, "%s %s.%03d %-10s %s",
- log_levels[log_level], date_time, millis, name,
- (context == NULL ? "?:" : context))
- : fprintf(log_file, "%s %s.%03d %-10s %s%s:%d: ",
- log_levels[log_level], date_time, millis, name,
- (context == NULL ? "" : context),
- func_name, line_num);
+ ? fprintf(log_file, "%s %s.%03d %-10s %s",
+ log_levels[log_level], date_time, millis, name,
+ (context == NULL ? "?:" : context))
+ : fprintf(log_file, "%s %s.%03d %-10s %s%s:%d: ",
+ log_levels[log_level], date_time, millis, name,
+ (context == NULL ? "" : context),
+ func_name, line_num);
if (name[0] == '?') {
// No v:servername yet. Clear `name` so that the next log can try again.
name[0] = '\0';
diff --git a/src/nvim/log.h b/src/nvim/log.h
index 14d46c2ea7..cac074d146 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_LOG_H
-#define NVIM_LOG_H
+#pragma once
#include <stdbool.h>
#include <stdio.h>
#include "auto/config.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
// USDT probes. Example invocation:
// NVIM_PROBE(nvim_foo_bar, 1, string.data);
@@ -17,54 +16,29 @@
# define NVIM_PROBE(name, n, ...)
#endif
-#define LOGLVL_TRC 0
#define LOGLVL_DBG 1
#define LOGLVL_INF 2
#define LOGLVL_WRN 3
#define LOGLVL_ERR 4
-#define DLOG(...)
-#define DLOGN(...)
-#define ILOG(...)
-#define ILOGN(...)
-#define WLOG(...)
-#define WLOGN(...)
-#define ELOG(...)
-#define ELOGN(...)
-
-#ifndef MIN_LOG_LEVEL
-# define MIN_LOG_LEVEL LOGLVL_INF
-#endif
-
#define LOG(level, ...) logmsg((level), NULL, __func__, __LINE__, true, __VA_ARGS__)
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
-# undef DLOG
-# undef DLOGN
+#ifdef NVIM_LOG_DEBUG
# define DLOG(...) logmsg(LOGLVL_DBG, NULL, __func__, __LINE__, true, __VA_ARGS__)
# define DLOGN(...) logmsg(LOGLVL_DBG, NULL, __func__, __LINE__, false, __VA_ARGS__)
-#endif
-
-#if MIN_LOG_LEVEL <= LOGLVL_INF
-# undef ILOG
-# undef ILOGN
# define ILOG(...) logmsg(LOGLVL_INF, NULL, __func__, __LINE__, true, __VA_ARGS__)
# define ILOGN(...) logmsg(LOGLVL_INF, NULL, __func__, __LINE__, false, __VA_ARGS__)
+#else
+# define DLOG(...)
+# define DLOGN(...)
+# define ILOG(...)
+# define ILOGN(...)
#endif
-#if MIN_LOG_LEVEL <= LOGLVL_WRN
-# undef WLOG
-# undef WLOGN
-# define WLOG(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, true, __VA_ARGS__)
-# define WLOGN(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, false, __VA_ARGS__)
-#endif
-
-#if MIN_LOG_LEVEL <= LOGLVL_ERR
-# undef ELOG
-# undef ELOGN
-# define ELOG(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, true, __VA_ARGS__)
-# define ELOGN(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, false, __VA_ARGS__)
-#endif
+#define WLOG(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, true, __VA_ARGS__)
+#define WLOGN(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, false, __VA_ARGS__)
+#define ELOG(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, true, __VA_ARGS__)
+#define ELOGN(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, false, __VA_ARGS__)
#ifdef HAVE_EXECINFO_BACKTRACE
# define LOG_CALLSTACK() log_callstack(__func__, __LINE__)
@@ -78,4 +52,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "log.h.generated.h"
#endif
-#endif // NVIM_LOG_H
diff --git a/src/nvim/lua/base64.c b/src/nvim/lua/base64.c
new file mode 100644
index 0000000000..c1f43a37d7
--- /dev/null
+++ b/src/nvim/lua/base64.c
@@ -0,0 +1,70 @@
+#include <assert.h>
+#include <lauxlib.h>
+#include <lua.h>
+#include <stddef.h>
+
+#include "nvim/base64.h"
+#include "nvim/lua/base64.h"
+#include "nvim/memory.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/base64.c.generated.h"
+#endif
+
+static int nlua_base64_encode(lua_State *L)
+{
+ if (lua_gettop(L) < 1) {
+ return luaL_error(L, "Expected 1 argument");
+ }
+
+ if (lua_type(L, 1) != LUA_TSTRING) {
+ luaL_argerror(L, 1, "expected string");
+ }
+
+ size_t src_len = 0;
+ const char *src = lua_tolstring(L, 1, &src_len);
+
+ const char *ret = base64_encode(src, src_len);
+ assert(ret != NULL);
+ lua_pushstring(L, ret);
+ xfree((void *)ret);
+
+ return 1;
+}
+
+static int nlua_base64_decode(lua_State *L)
+{
+ if (lua_gettop(L) < 1) {
+ return luaL_error(L, "Expected 1 argument");
+ }
+
+ if (lua_type(L, 1) != LUA_TSTRING) {
+ luaL_argerror(L, 1, "expected string");
+ }
+
+ size_t src_len = 0;
+ const char *src = lua_tolstring(L, 1, &src_len);
+
+ const char *ret = base64_decode(src, src_len);
+ if (ret == NULL) {
+ return luaL_error(L, "Invalid input");
+ }
+
+ lua_pushstring(L, ret);
+ xfree((void *)ret);
+
+ return 1;
+}
+
+static const luaL_Reg base64_functions[] = {
+ { "encode", nlua_base64_encode },
+ { "decode", nlua_base64_decode },
+ { NULL, NULL },
+};
+
+int luaopen_base64(lua_State *L)
+{
+ lua_newtable(L);
+ luaL_register(L, NULL, base64_functions);
+ return 1;
+}
diff --git a/src/nvim/lua/base64.h b/src/nvim/lua/base64.h
new file mode 100644
index 0000000000..3c95968cda
--- /dev/null
+++ b/src/nvim/lua/base64.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <lua.h> // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/base64.h.generated.h"
+#endif
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 6160b84485..4598d48c4a 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
@@ -10,26 +7,24 @@
#include <stdlib.h>
#include <string.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/memory.h"
-// FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is
-// redefined
-#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/typval_encode.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/garray.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
/// Determine, which keys lua table contains
typedef struct {
@@ -183,7 +178,7 @@ typedef struct {
int idx; ///< Container index (used to detect self-referencing structures).
} TVPopStackItem;
-/// Convert lua object to VimL typval_T
+/// Convert lua object to Vimscript typval_T
///
/// Should pop exactly one value from lua stack.
///
@@ -459,7 +454,7 @@ static bool typval_conv_special = false;
TYPVAL_ENCODE_CONV_NUMBER(tv, flt)
#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
- lua_pushlstring(lstate, (const char *)(str), (len))
+ lua_pushlstring(lstate, (str), (len))
#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
@@ -469,9 +464,7 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
const blob_T *const blob_ = (blob); \
- lua_pushlstring(lstate, \
- blob_ != NULL ? (const char *)blob_->bv_ga.ga_data : "", \
- (size_t)(len)); \
+ lua_pushlstring(lstate, blob_ != NULL ? blob_->bv_ga.ga_data : "", (size_t)(len)); \
} while (0)
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
@@ -603,7 +596,7 @@ static bool typval_conv_special = false;
#undef TYPVAL_ENCODE_CONV_RECURSE
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
-/// Convert VimL typval_T to lua value
+/// Convert Vimscript typval_T to lua value
///
/// Should leave single value in lua stack. May only fail if lua failed to grow
/// stack.
@@ -630,8 +623,7 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special)
/// Push value which is a type index
///
-/// Used for all “typed†tables: i.e. for all tables which represent VimL
-/// values.
+/// Used for all “typed†tables: i.e. for all tables which represent Vimscript values.
static inline void nlua_push_type_idx(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
@@ -659,7 +651,7 @@ static inline void nlua_push_type(lua_State *lstate, ObjectType type)
lua_pushnumber(lstate, (lua_Number)type);
}
-/// Create lua table which has an entry that determines its VimL type
+/// Create Lua table which has an entry that determines its Vimscript type
///
/// @param[out] lstate Lua state.
/// @param[in] narr Number of “array†entries to be populated later.
@@ -816,7 +808,7 @@ String nlua_pop_String(lua_State *lstate, Error *err)
{
if (lua_type(lstate, -1) != LUA_TSTRING) {
lua_pop(lstate, 1);
- api_set_error(err, kErrorTypeValidation, "Expected lua string");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua string");
return (String) { .size = 0, .data = NULL };
}
String ret;
@@ -837,7 +829,7 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err)
{
if (lua_type(lstate, -1) != LUA_TNUMBER) {
lua_pop(lstate, 1);
- api_set_error(err, kErrorTypeValidation, "Expected lua number");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua number");
return 0;
}
const lua_Number n = lua_tonumber(lstate, -1);
@@ -852,6 +844,9 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err)
/// Convert lua value to boolean
///
+/// Despite the name of the function, this uses lua semantics for booleans.
+/// thus `err` is never set as any lua value can be co-erced into a lua bool
+///
/// Always pops one value from the stack.
Boolean nlua_pop_Boolean(lua_State *lstate, Error *err)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
@@ -861,6 +856,36 @@ Boolean nlua_pop_Boolean(lua_State *lstate, Error *err)
return ret;
}
+/// Convert lua value to boolean
+///
+/// This follows API conventions for a Boolean value, compare api_object_to_bool
+///
+/// Always pops one value from the stack.
+Boolean nlua_pop_Boolean_strict(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ Boolean ret = false;
+ switch (lua_type(lstate, -1)) {
+ case LUA_TBOOLEAN:
+ ret = lua_toboolean(lstate, -1);
+ break;
+
+ case LUA_TNUMBER:
+ ret = (lua_tonumber(lstate, -1) != 0);
+ break;
+
+ case LUA_TNIL:
+ ret = false;
+ break;
+
+ default:
+ api_set_error(err, kErrorTypeValidation, "not a boolean");
+ }
+
+ lua_pop(lstate, 1);
+ return ret;
+}
+
/// Check whether typed table on top of the stack has given type
///
/// @param[in] lstate Lua state.
@@ -874,7 +899,8 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons
{
if (lua_type(lstate, -1) != LUA_TTABLE) {
if (err) {
- api_set_error(err, kErrorTypeValidation, "Expected lua table");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua %s",
+ (type == kObjectTypeFloat) ? "number" : "table");
}
return (LuaTableProps) { .type = kObjectTypeNil };
}
@@ -887,7 +913,7 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons
if (table_props.type != type) {
if (err) {
- api_set_error(err, kErrorTypeValidation, "Unexpected type");
+ api_set_error(err, kErrorTypeValidation, "Expected %s-like Lua table", api_typename(type));
}
}
@@ -1171,7 +1197,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
case kObjectTypeNil:
api_set_error(err, kErrorTypeValidation,
- "Cannot convert given lua table");
+ "Cannot convert given Lua table");
break;
default:
abort();
@@ -1227,26 +1253,19 @@ LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
return rv;
}
-#define GENERATE_INDEX_FUNCTION(type) \
- type nlua_pop_##type(lua_State *lstate, Error *err) \
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
- { \
- type ret; \
- if (lua_type(lstate, -1) != LUA_TNUMBER) { \
- api_set_error(err, kErrorTypeValidation, "Expected Lua number"); \
- ret = (type) - 1; \
- } else { \
- ret = (type)lua_tonumber(lstate, -1); \
- } \
- lua_pop(lstate, 1); \
- return ret; \
+handle_T nlua_pop_handle(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ handle_T ret;
+ if (lua_type(lstate, -1) != LUA_TNUMBER) {
+ api_set_error(err, kErrorTypeValidation, "Expected Lua number");
+ ret = (handle_T) - 1;
+ } else {
+ ret = (handle_T)lua_tonumber(lstate, -1);
}
-
-GENERATE_INDEX_FUNCTION(Buffer)
-GENERATE_INDEX_FUNCTION(Window)
-GENERATE_INDEX_FUNCTION(Tabpage)
-
-#undef GENERATE_INDEX_FUNCTION
+ lua_pop(lstate, 1);
+ return ret;
+}
/// Record some auxiliary values in vim module
///
@@ -1295,10 +1314,11 @@ void nlua_init_types(lua_State *const lstate)
lua_rawset(lstate, -3);
}
-void nlua_pop_keydict(lua_State *L, void *retval, field_hash hashy, Error *err)
+// lua specific variant of api_dict_to_keydict
+void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_opt, Error *err)
{
if (!lua_istable(L, -1)) {
- api_set_error(err, kErrorTypeValidation, "Expected lua table");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua table");
lua_pop(L, -1);
return;
}
@@ -1308,14 +1328,45 @@ void nlua_pop_keydict(lua_State *L, void *retval, field_hash hashy, Error *err)
// [dict, key, value]
size_t len;
const char *s = lua_tolstring(L, -2, &len);
- Object *field = hashy(retval, s, len);
+ KeySetLink *field = hashy(s, len);
if (!field) {
api_set_error(err, kErrorTypeValidation, "invalid key: %.*s", (int)len, s);
lua_pop(L, 3); // []
return;
}
- *field = nlua_pop_Object(L, true, err);
+ if (field->opt_index >= 0) {
+ OptKeySet *ks = (OptKeySet *)retval;
+ ks->is_set_ |= (1ULL << field->opt_index);
+ }
+ char *mem = ((char *)retval + field->ptr_off);
+
+ if (field->type == kObjectTypeNil) {
+ *(Object *)mem = nlua_pop_Object(L, true, err);
+ } else if (field->type == kObjectTypeInteger) {
+ *(Integer *)mem = nlua_pop_Integer(L, err);
+ } else if (field->type == kObjectTypeBoolean) {
+ *(Boolean *)mem = nlua_pop_Boolean_strict(L, err);
+ } else if (field->type == kObjectTypeString) {
+ *(String *)mem = nlua_pop_String(L, err);
+ } else if (field->type == kObjectTypeFloat) {
+ *(Float *)mem = nlua_pop_Float(L, err);
+ } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
+ || field->type == kObjectTypeTabpage) {
+ *(handle_T *)mem = nlua_pop_handle(L, err);
+ } else if (field->type == kObjectTypeArray) {
+ *(Array *)mem = nlua_pop_Array(L, err);
+ } else if (field->type == kObjectTypeDictionary) {
+ *(Dictionary *)mem = nlua_pop_Dictionary(L, false, err);
+ } else if (field->type == kObjectTypeLuaRef) {
+ *(LuaRef *)mem = nlua_pop_LuaRef(L, err);
+ } else {
+ abort();
+ }
+ if (ERROR_SET(err)) {
+ *err_opt = field->str;
+ break;
+ }
}
// [dict]
lua_pop(L, 1);
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
index ddc0acfbfa..a502df80d9 100644
--- a/src/nvim/lua/converter.h
+++ b/src/nvim/lua/converter.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_LUA_CONVERTER_H
-#define NVIM_LUA_CONVERTER_H
+#pragma once
-#include <lua.h>
-#include <stdbool.h>
-#include <stdint.h>
+#include <lua.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/eval/typval.h"
-#include "nvim/func_attr.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+
+#define nlua_pop_Buffer nlua_pop_handle
+#define nlua_pop_Window nlua_pop_handle
+#define nlua_pop_Tabpage nlua_pop_handle
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/converter.h.generated.h"
#endif
-#endif // NVIM_LUA_CONVERTER_H
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 5ffd90fddd..06d16efb05 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1,12 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <tree_sitter/api.h>
#include <uv.h>
@@ -16,9 +15,9 @@
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/change.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
@@ -42,28 +41,28 @@
#include "nvim/lua/executor.h"
#include "nvim/lua/stdlib.h"
#include "nvim/lua/treesitter.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
-#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static int in_fast_callback = 0;
+static bool in_script = false;
// Initialized in nlua_init().
static lua_State *global_lstate = NULL;
@@ -108,11 +107,16 @@ typedef enum luv_err_type {
kThreadCallback,
} luv_err_t;
+lua_State *get_global_lstate(void)
+{
+ return global_lstate;
+}
+
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
-/// @param[in] msg Message base, must contain one `%s`.
-static void nlua_error(lua_State *const lstate, const char *const msg)
+/// @param[in] msg Message base, must contain one `%*s`.
+void nlua_error(lua_State *const lstate, const char *const msg)
FUNC_ATTR_NONNULL_ALL
{
size_t len;
@@ -133,8 +137,13 @@ static void nlua_error(lua_State *const lstate, const char *const msg)
str = lua_tolstring(lstate, -1, &len);
}
- msg_ext_set_kind("lua_error");
- semsg_multiline(msg, (int)len, str);
+ if (in_script) {
+ fprintf(stderr, msg, (int)len, str);
+ fprintf(stderr, "\n");
+ } else {
+ msg_ext_set_kind("lua_error");
+ semsg_multiline(msg, (int)len, str);
+ }
lua_pop(lstate, 1);
}
@@ -144,7 +153,7 @@ static void nlua_error(lua_State *const lstate, const char *const msg)
/// @param lstate Lua interpreter state
/// @param[in] nargs Number of arguments expected by the function being called.
/// @param[in] nresults Number of results the function returns.
-static int nlua_pcall(lua_State *lstate, int nargs, int nresults)
+int nlua_pcall(lua_State *lstate, int nargs, int nresults)
{
lua_getglobal(lstate, "debug");
lua_getfield(lstate, -1, "traceback");
@@ -159,17 +168,6 @@ static int nlua_pcall(lua_State *lstate, int nargs, int nresults)
return status;
}
-/// Gets the version of the current Nvim build.
-///
-/// @param lstate Lua interpreter state.
-static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
-{
- Dictionary version = version_dict();
- nlua_push_Dictionary(lstate, version, true);
- api_free_dictionary(version);
- return 1;
-}
-
static void nlua_luv_error_event(void **argv)
{
char *error = (char *)argv[0];
@@ -205,9 +203,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
if (status) {
if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
// consider out of memory errors unrecoverable, just like xmalloc()
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
const char *error = lua_tostring(lstate, -1);
@@ -343,7 +339,7 @@ static int nlua_init_argv(lua_State *const L, char **argv, int argc, int lua_arg
lua_pushstring(L, argv[lua_arg0 - 1]);
lua_rawseti(L, -2, 0); // _G.arg[0] = "foo.lua"
- for (; lua_arg0 >= 0 && i + lua_arg0 < argc; i++) {
+ for (; i + lua_arg0 < argc; i++) {
lua_pushstring(L, argv[i + lua_arg0]);
lua_rawseti(L, -2, i + 1); // _G.arg[i+1] = "--foo"
}
@@ -375,6 +371,12 @@ static int nlua_schedule(lua_State *const lstate)
return lua_error(lstate);
}
+ // If main_loop is closing don't schedule tasks to run in the future,
+ // otherwise any refs allocated here will not be cleaned up.
+ if (main_loop.closing) {
+ return 0;
+ }
+
LuaRef cb = nlua_ref_global(lstate, 1);
multiqueue_put(main_loop.events, nlua_schedule_event,
@@ -384,7 +386,8 @@ static int nlua_schedule(lua_State *const lstate)
// Dummy timer callback. Used by f_wait().
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
-{}
+{
+}
// Dummy timer close callback. Used by f_wait().
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
@@ -408,6 +411,10 @@ static bool nlua_wait_condition(lua_State *lstate, int *status, bool *callback_r
static int nlua_wait(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
+ if (in_fast_callback) {
+ return luaL_error(lstate, e_luv_api_disabled, "vim.wait");
+ }
+
intptr_t timeout = luaL_checkinteger(lstate, 1);
if (timeout < 0) {
return luaL_error(lstate, "timeout must be >= 0");
@@ -446,8 +453,7 @@ static int nlua_wait(lua_State *lstate)
fast_only = lua_toboolean(lstate, 4);
}
- MultiQueue *loop_events = fast_only || in_fast_callback > 0
- ? main_loop.fast_events : main_loop.events;
+ MultiQueue *loop_events = fast_only ? main_loop.fast_events : main_loop.events;
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
@@ -463,12 +469,16 @@ static int nlua_wait(lua_State *lstate)
int pcall_status = 0;
bool callback_result = false;
+ // Flush screen updates before blocking.
+ ui_flush();
+
LOOP_PROCESS_EVENTS_UNTIL(&main_loop,
loop_events,
(int)timeout,
got_int || (is_function ? nlua_wait_condition(lstate,
&pcall_status,
- &callback_result) : false));
+ &callback_result)
+ : false));
// Stop dummy timer
time_watcher_stop(tw);
@@ -534,7 +544,7 @@ int nlua_get_global_ref_count(void)
return nlua_global_refs->ref_count;
}
-static void nlua_common_vim_init(lua_State *lstate, bool is_thread)
+static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_standalone)
FUNC_ATTR_NONNULL_ARG(1)
{
nlua_ref_state_t *ref_state = nlua_new_ref_state(lstate, is_thread);
@@ -566,8 +576,10 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread)
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
lua_setfield(lstate, -2, "_empty_dict_mt");
- // vim.loop
- if (is_thread) {
+ // vim.uv
+ if (is_standalone) {
+ // do nothing, use libluv like in a standalone interpreter
+ } else if (is_thread) {
luv_set_callback(lstate, nlua_luv_thread_cb_cfpcall);
luv_set_thread(lstate, nlua_luv_thread_cfpcall);
luv_set_cthread(lstate, nlua_luv_thread_cfcpcall);
@@ -577,9 +589,12 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread)
}
luaopen_luv(lstate);
lua_pushvalue(lstate, -1);
- lua_setfield(lstate, -3, "loop");
+ lua_setfield(lstate, -3, "uv");
+
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, -3, "loop"); // deprecated
- // package.loaded.luv = vim.loop
+ // package.loaded.luv = vim.uv
// otherwise luv will be reinitialized when require'luv'
lua_getglobal(lstate, "package");
lua_getfield(lstate, -1, "loaded");
@@ -606,7 +621,7 @@ static int nlua_module_preloader(lua_State *lstate)
return 1;
}
-static bool nlua_init_packages(lua_State *lstate)
+static bool nlua_init_packages(lua_State *lstate, bool is_standalone)
FUNC_ATTR_NONNULL_ALL
{
// put builtin packages in preload
@@ -614,11 +629,11 @@ static bool nlua_init_packages(lua_State *lstate)
lua_getfield(lstate, -1, "preload"); // [package, preload]
for (size_t i = 0; i < ARRAY_SIZE(builtin_modules); i++) {
ModuleDef def = builtin_modules[i];
- lua_pushinteger(lstate, (long)i); // [package, preload, i]
+ lua_pushinteger(lstate, (lua_Integer)i); // [package, preload, i]
lua_pushcclosure(lstate, nlua_module_preloader, 1); // [package, preload, cclosure]
lua_setfield(lstate, -2, def.name); // [package, preload]
- if (nlua_disable_preload && strequal(def.name, "vim.inspect")) {
+ if ((nlua_disable_preload && !is_standalone) && strequal(def.name, "vim.inspect")) {
break;
}
}
@@ -628,7 +643,7 @@ static bool nlua_init_packages(lua_State *lstate)
lua_getglobal(lstate, "require");
lua_pushstring(lstate, "vim._init_packages");
if (nlua_pcall(lstate, 1, 0)) {
- os_errmsg((char *)lua_tostring(lstate, -1));
+ os_errmsg(lua_tostring(lstate, -1));
os_errmsg("\n");
return false;
}
@@ -733,10 +748,6 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// vim.types, vim.type_idx, vim.val_idx
nlua_init_types(lstate);
- // neovim version
- lua_pushcfunction(lstate, &nlua_nvim_version);
- lua_setfield(lstate, -2, "version");
-
// schedule
lua_pushcfunction(lstate, &nlua_schedule);
lua_setfield(lstate, -2, "schedule");
@@ -769,7 +780,7 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_ui_detach);
lua_setfield(lstate, -2, "ui_detach");
- nlua_common_vim_init(lstate, false);
+ nlua_common_vim_init(lstate, false, false);
// patch require() (only for --startuptime)
if (time_fd != NULL) {
@@ -788,7 +799,7 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setglobal(lstate, "vim");
- if (!nlua_init_packages(lstate)) {
+ if (!nlua_init_packages(lstate, false)) {
return false;
}
@@ -813,6 +824,9 @@ void nlua_init(char **argv, int argc, int lua_arg0)
luaL_openlibs(lstate);
if (!nlua_state_init(lstate)) {
os_errmsg(_("E970: Failed to initialize builtin lua modules\n"));
+#ifdef EXITFREE
+ nlua_common_free_all_mem(lstate);
+#endif
os_exit(1);
}
@@ -824,9 +838,28 @@ void nlua_init(char **argv, int argc, int lua_arg0)
static lua_State *nlua_thread_acquire_vm(void)
{
+ return nlua_init_state(true);
+}
+
+void nlua_run_script(char **argv, int argc, int lua_arg0)
+ FUNC_ATTR_NORETURN
+{
+ in_script = true;
+ global_lstate = nlua_init_state(false);
+ luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem);
+ nlua_init_argv(global_lstate, argv, argc, lua_arg0);
+ bool lua_ok = nlua_exec_file(argv[lua_arg0 - 1]);
+#ifdef EXITFREE
+ nlua_free_all_mem();
+#endif
+ exit(lua_ok ? 0 : 1);
+}
+
+static lua_State *nlua_init_state(bool thread)
+{
// If it is called from the main thread, it will attempt to rebuild the cache.
const uv_thread_t self = uv_thread_self();
- if (uv_thread_equal(&main_thread, &self)) {
+ if (!in_script && uv_thread_equal(&main_thread, &self)) {
runtime_search_path_validate();
}
@@ -835,9 +868,11 @@ static lua_State *nlua_thread_acquire_vm(void)
// Add in the lua standard libraries
luaL_openlibs(lstate);
- // print
- lua_pushcfunction(lstate, &nlua_print);
- lua_setglobal(lstate, "print");
+ if (!in_script) {
+ // print
+ lua_pushcfunction(lstate, &nlua_print);
+ lua_setglobal(lstate, "print");
+ }
lua_pushinteger(lstate, 0);
lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount");
@@ -845,18 +880,20 @@ static lua_State *nlua_thread_acquire_vm(void)
// vim
lua_newtable(lstate);
- nlua_common_vim_init(lstate, true);
+ nlua_common_vim_init(lstate, thread, in_script);
nlua_state_add_stdlib(lstate, true);
- lua_createtable(lstate, 0, 0);
- lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime);
- lua_setfield(lstate, -2, "nvim__get_runtime");
- lua_setfield(lstate, -2, "api");
+ if (!in_script) {
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime);
+ lua_setfield(lstate, -2, "nvim__get_runtime");
+ lua_setfield(lstate, -2, "api");
+ }
lua_setglobal(lstate, "vim");
- nlua_init_packages(lstate);
+ nlua_init_packages(lstate, in_script);
lua_getglobal(lstate, "package");
lua_getfield(lstate, -1, "loaded");
@@ -892,12 +929,13 @@ static void nlua_common_free_all_mem(lua_State *lstate)
if (nlua_track_refs) {
// in case there are leaked luarefs, leak the associated memory
// to get LeakSanitizer stacktraces on exit
- pmap_destroy(handle_T)(&ref_state->ref_markers);
+ map_destroy(int, &ref_state->ref_markers);
}
#endif
lua_close(lstate);
}
+
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -926,10 +964,13 @@ static void nlua_print_event(void **argv)
}
break;
}
- msg(str + start);
+ msg(str + start, 0);
+ if (msg_silent == 0) {
+ msg_didout = true; // Make blank lines work properly
+ }
}
if (len && str[len - 1] == NUL) { // Last was newline
- msg("");
+ msg("", 0);
}
xfree(str);
}
@@ -1076,7 +1117,7 @@ static int nlua_debug(lua_State *lstate)
.v_type = VAR_UNKNOWN,
},
};
- for (;;) {
+ while (true) {
lua_settop(lstate, 0);
typval_T input;
get_user_input(input_args, &input, false, false);
@@ -1088,7 +1129,7 @@ static int nlua_debug(lua_State *lstate)
tv_clear(&input);
return 0;
}
- if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string,
+ if (luaL_loadbuffer(lstate, input.vval.v_string,
strlen(input.vval.v_string), "=(debug command)")) {
nlua_error(lstate, _("E5115: Error while loading debug string: %.*s"));
} else if (nlua_pcall(lstate, 0, 0)) {
@@ -1111,7 +1152,7 @@ static bool viml_func_is_fast(const char *name)
if (fdef) {
return fdef->fast;
}
- // Not a vimL function
+ // Not a Vimscript function
return false;
}
@@ -1121,7 +1162,7 @@ int nlua_call(lua_State *lstate)
size_t name_len;
const char *name = luaL_checklstring(lstate, 1, &name_len);
if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) {
- return luaL_error(lstate, e_luv_api_disabled, "vimL function");
+ return luaL_error(lstate, e_luv_api_disabled, "Vimscript function");
}
int nargs = lua_gettop(lstate) - 1;
@@ -1140,28 +1181,29 @@ int nlua_call(lua_State *lstate)
}
}
- TRY_WRAP({
- // TODO(bfredl): this should be simplified in error handling refactor
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- did_emsg = false;
+ // TODO(bfredl): this should be simplified in error handling refactor
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ did_emsg = false;
- try_start();
- typval_T rettv;
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.fe_firstline = curwin->w_cursor.lnum;
- funcexe.fe_lastline = curwin->w_cursor.lnum;
- funcexe.fe_evaluate = true;
+ typval_T rettv;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_firstline = curwin->w_cursor.lnum;
+ funcexe.fe_lastline = curwin->w_cursor.lnum;
+ funcexe.fe_evaluate = true;
+
+ TRY_WRAP(&err, {
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (TRY_WRAP) to capture abort-causing non-exception errors.
- (void)call_func((char *)name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
- if (!try_end(&err)) {
- nlua_push_typval(lstate, &rettv, false);
- }
- tv_clear(&rettv);
+ (void)call_func(name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
});
+ if (!ERROR_SET(&err)) {
+ nlua_push_typval(lstate, &rettv, false);
+ }
+ tv_clear(&rettv);
+
free_vim_args:
while (i > 0) {
tv_clear(&vim_args[--i]);
@@ -1266,13 +1308,16 @@ LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index)
#ifdef NLUA_TRACK_REFS
if (nlua_track_refs) {
// dummy allocation to make LeakSanitizer track our luarefs
- pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3));
+ pmap_put(int)(&ref_state->ref_markers, ref, xmalloc(3));
}
#endif
}
return ref;
}
+// TODO(lewis6991): Currently cannot be run in __gc metamethods as they are
+// invoked in lua_close() which can be invoked after the ref_markers map is
+// destroyed in nlua_common_free_all_mem.
LuaRef nlua_ref_global(lua_State *lstate, int index)
{
return nlua_ref(lstate, nlua_global_refs, index);
@@ -1286,7 +1331,7 @@ void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref)
#ifdef NLUA_TRACK_REFS
// NB: don't remove entry from map to track double-unref
if (nlua_track_refs) {
- xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref));
+ xfree(pmap_get(int)(&ref_state->ref_markers, ref));
}
#endif
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
@@ -1459,7 +1504,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name)
/// Call a LuaCallable given some typvals
///
-/// Used to call any lua callable passed from Lua into VimL
+/// Used to call any Lua callable passed from Lua into Vimscript.
///
/// @param[in] lstate Lua State
/// @param[in] lua_cb Lua Callable
@@ -1594,24 +1639,25 @@ bool nlua_is_deferred_safe(void)
///
/// Used for :lua.
///
-/// @param eap VimL command being run.
+/// @param eap Vimscript command being run.
void ex_lua(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
size_t len;
char *code = script_get(eap, &len);
- if (eap->skip) {
+ if (eap->skip || code == NULL) {
xfree(code);
return;
}
- // When =expr is used transform it to print(vim.inspect(expr))
- if (code[0] == '=') {
- len += sizeof("vim.pretty_print()") - sizeof("=");
+ // When =expr is used transform it to vim.print(expr)
+ if (eap->cmdidx == CMD_equal || code[0] == '=') {
+ size_t off = (eap->cmdidx == CMD_equal) ? 0 : 1;
+ len += sizeof("vim.print()") - 1 - off;
// code_buf needs to be 1 char larger then len for null byte in the end.
// lua nlua_typval_exec doesn't expect null terminated string so len
// needs to end before null byte.
char *code_buf = xmallocz(len);
- vim_snprintf(code_buf, len + 1, "vim.pretty_print(%s)", code + 1);
+ vim_snprintf(code_buf, len + 1, "vim.print(%s)", code + off);
xfree(code);
code = code_buf;
}
@@ -1625,7 +1671,7 @@ void ex_lua(exarg_T *const eap)
///
/// Used for :luado.
///
-/// @param eap VimL command being run.
+/// @param eap Vimscript command being run.
void ex_luado(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
@@ -1633,7 +1679,7 @@ void ex_luado(exarg_T *const eap)
emsg(_("cannot save undo information"));
return;
}
- const char *const cmd = (const char *)eap->arg;
+ const char *const cmd = eap->arg;
const size_t cmd_len = strlen(cmd);
lua_State *const lstate = global_lstate;
@@ -1674,7 +1720,9 @@ void ex_luado(exarg_T *const eap)
break;
}
lua_pushvalue(lstate, -1);
- const char *old_line = (const char *)ml_get_buf(curbuf, l, false);
+ const char *const old_line = ml_get_buf(curbuf, l);
+ // Get length of old_line here as calling Lua code may free it.
+ const size_t old_line_len = strlen(old_line);
lua_pushstring(lstate, old_line);
lua_pushnumber(lstate, (lua_Number)l);
if (nlua_pcall(lstate, 2, 1)) {
@@ -1682,8 +1730,6 @@ void ex_luado(exarg_T *const eap)
break;
}
if (lua_isstring(lstate, -1)) {
- size_t old_line_len = strlen(old_line);
-
size_t new_line_len;
const char *const new_line = lua_tolstring(lstate, -1, &new_line_len);
char *const new_line_transformed = xmemdupz(new_line, new_line_len);
@@ -1706,11 +1752,11 @@ void ex_luado(exarg_T *const eap)
///
/// Used for :luafile.
///
-/// @param eap VimL command being run.
+/// @param eap Vimscript command being run.
void ex_luafile(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
- nlua_exec_file((const char *)eap->arg);
+ nlua_exec_file(eap->arg);
}
/// Executes Lua code from a file or "-" (stdin).
@@ -1734,13 +1780,12 @@ bool nlua_exec_file(const char *path)
StringBuilder sb = KV_INITIAL_VALUE;
kv_resize(sb, 64);
- ptrdiff_t read_size = -1;
// Read all input from stdin, unless interrupted (ctrl-c).
while (true) {
if (got_int) { // User canceled.
return false;
}
- read_size = file_read(stdin_dup, IObuff, 64);
+ ptrdiff_t read_size = file_read(stdin_dup, IObuff, 64);
if (read_size < 0) { // Error.
return false;
}
@@ -1842,7 +1887,7 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results)
luaL_checktype(lstate, -1, LUA_TFUNCTION);
// [ vim, vim._expand_pat, buf ]
- lua_pushlstring(lstate, (const char *)pat, strlen(pat));
+ lua_pushlstring(lstate, pat, strlen(pat));
if (nlua_pcall(lstate, 1, 2) != 0) {
nlua_error(lstate,
@@ -1961,7 +2006,7 @@ char *nlua_register_table_as_callable(const typval_T *const arg)
void nlua_execute_on_key(int c)
{
char buf[NUMBUFLEN];
- size_t buf_len = special_to_buf(c, mod_mask, false, (char_u *)buf);
+ size_t buf_len = special_to_buf(c, mod_mask, false, buf);
lua_State *const lstate = global_lstate;
@@ -2007,9 +2052,9 @@ void nlua_set_sctx(sctx_T *current)
lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug));
// Files where internal wrappers are defined so we can ignore them
- // like vim.o/opt etc are defined in _meta.lua
+ // like vim.o/opt etc are defined in _options.lua
char *ignorelist[] = {
- "vim/_meta.lua",
+ "vim/_options.lua",
"vim/keymap.lua",
};
int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]);
@@ -2039,10 +2084,15 @@ void nlua_set_sctx(sctx_T *current)
break;
}
char *source_path = fix_fname(info->source + 1);
- get_current_script_id(&source_path, current);
- xfree(source_path);
- current->sc_lnum = info->currentline;
+ int sid = find_script_by_name(source_path);
+ if (sid > 0) {
+ xfree(source_path);
+ } else {
+ new_script_item(source_path, &sid);
+ }
+ current->sc_sid = sid;
current->sc_seq = -1;
+ current->sc_lnum = info->currentline;
cleanup:
xfree(info);
@@ -2069,7 +2119,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
lua_setfield(lstate, -2, "line2");
lua_newtable(lstate); // f-args table
- lua_pushstring(lstate, (const char *)eap->arg);
+ lua_pushstring(lstate, eap->arg);
lua_pushvalue(lstate, -1); // Reference for potential use on f-args
lua_setfield(lstate, -4, "args");
@@ -2252,79 +2302,16 @@ plain:
return str.items;
}
-char *nlua_read_secure(const char *path)
-{
- lua_State *const lstate = global_lstate;
- const int top = lua_gettop(lstate);
-
- lua_getglobal(lstate, "vim");
- lua_getfield(lstate, -1, "secure");
- lua_getfield(lstate, -1, "read");
- lua_pushstring(lstate, path);
- if (nlua_pcall(lstate, 1, 1)) {
- nlua_error(lstate, _("Error executing vim.secure.read: %.*s"));
- lua_settop(lstate, top);
- return NULL;
- }
-
- size_t len = 0;
- const char *contents = lua_tolstring(lstate, -1, &len);
- char *buf = NULL;
- if (contents != NULL) {
- // Add one to include trailing null byte
- buf = xcalloc(len + 1, sizeof(char));
- memcpy(buf, contents, len + 1);
- }
-
- lua_settop(lstate, top);
- return buf;
-}
-
-bool nlua_trust(const char *action, const char *path)
+/// Execute the vim._defaults module to set up default mappings and autocommands
+void nlua_init_defaults(void)
{
- lua_State *const lstate = global_lstate;
- const int top = lua_gettop(lstate);
-
- lua_getglobal(lstate, "vim");
- lua_getfield(lstate, -1, "secure");
- lua_getfield(lstate, -1, "trust");
-
- lua_newtable(lstate);
- lua_pushstring(lstate, "action");
- lua_pushstring(lstate, action);
- lua_settable(lstate, -3);
- if (path == NULL) {
- lua_pushstring(lstate, "bufnr");
- lua_pushnumber(lstate, 0);
- lua_settable(lstate, -3);
- } else {
- lua_pushstring(lstate, "path");
- lua_pushstring(lstate, path);
- lua_settable(lstate, -3);
- }
-
- if (nlua_pcall(lstate, 1, 2)) {
- nlua_error(lstate, _("Error executing vim.secure.trust: %.*s"));
- lua_settop(lstate, top);
- return false;
- }
+ lua_State *const L = global_lstate;
+ assert(L);
- bool success = lua_toboolean(lstate, -2);
- const char *msg = lua_tostring(lstate, -1);
- if (msg != NULL) {
- if (success) {
- if (strcmp(action, "allow") == 0) {
- smsg("Allowed \"%s\" in trust database.", msg);
- } else if (strcmp(action, "deny") == 0) {
- smsg("Denied \"%s\" in trust database.", msg);
- } else if (strcmp(action, "remove") == 0) {
- smsg("Removed \"%s\" from trust database.", msg);
- }
- } else {
- semsg(e_trustfile, msg);
- }
+ lua_getglobal(L, "require");
+ lua_pushstring(L, "vim._defaults");
+ if (nlua_pcall(L, 1, 0)) {
+ os_errmsg(lua_tostring(L, -1));
+ os_errmsg("\n");
}
-
- lua_settop(lstate, top);
- return success;
}
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index c6747833e5..b38faddbb3 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_LUA_EXECUTOR_H
-#define NVIM_LUA_EXECUTOR_H
+#pragma once
#include <lauxlib.h>
#include <lua.h>
@@ -7,13 +6,15 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
-#include "nvim/eval/typval.h"
+#include "nvim/assert_defs.h"
+#include "nvim/cmdexpand_defs.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/func_attr.h"
#include "nvim/lua/converter.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
// Generated by msgpack-gen.lua
@@ -24,7 +25,7 @@ typedef struct {
LuaRef empty_dict_ref;
int ref_count;
#if __has_feature(address_sanitizer)
- PMap(handle_T) ref_markers;
+ PMap(int) ref_markers;
#endif
} nlua_ref_state_t;
@@ -43,7 +44,5 @@ typedef struct {
# include "lua/executor.h.generated.h"
#endif
-EXTERN nlua_ref_state_t *nlua_global_refs INIT(= NULL);
-EXTERN bool nlua_disable_preload INIT(= false);
-
-#endif // NVIM_LUA_EXECUTOR_H
+EXTERN nlua_ref_state_t *nlua_global_refs INIT( = NULL);
+EXTERN bool nlua_disable_preload INIT( = false);
diff --git a/src/nvim/lua/secure.c b/src/nvim/lua/secure.c
new file mode 100644
index 0000000000..65c13f8872
--- /dev/null
+++ b/src/nvim/lua/secure.c
@@ -0,0 +1,119 @@
+#include <lua.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "nvim/charset.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/gettext.h"
+#include "nvim/globals.h"
+#include "nvim/lua/executor.h"
+#include "nvim/lua/secure.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/secure.c.generated.h"
+#endif
+
+char *nlua_read_secure(const char *path)
+{
+ lua_State *const lstate = get_global_lstate();
+ const int top = lua_gettop(lstate);
+
+ lua_getglobal(lstate, "vim");
+ lua_getfield(lstate, -1, "secure");
+ lua_getfield(lstate, -1, "read");
+ lua_pushstring(lstate, path);
+ if (nlua_pcall(lstate, 1, 1)) {
+ nlua_error(lstate, _("Error executing vim.secure.read: %.*s"));
+ lua_settop(lstate, top);
+ return NULL;
+ }
+
+ size_t len = 0;
+ const char *contents = lua_tolstring(lstate, -1, &len);
+ char *buf = NULL;
+ if (contents != NULL) {
+ // Add one to include trailing null byte
+ buf = xcalloc(len + 1, sizeof(char));
+ memcpy(buf, contents, len + 1);
+ }
+
+ lua_settop(lstate, top);
+ return buf;
+}
+
+static bool nlua_trust(const char *action, const char *path)
+{
+ lua_State *const lstate = get_global_lstate();
+ const int top = lua_gettop(lstate);
+
+ lua_getglobal(lstate, "vim");
+ lua_getfield(lstate, -1, "secure");
+ lua_getfield(lstate, -1, "trust");
+
+ lua_newtable(lstate);
+ lua_pushstring(lstate, "action");
+ lua_pushstring(lstate, action);
+ lua_settable(lstate, -3);
+ if (path == NULL) {
+ lua_pushstring(lstate, "bufnr");
+ lua_pushnumber(lstate, 0);
+ lua_settable(lstate, -3);
+ } else {
+ lua_pushstring(lstate, "path");
+ lua_pushstring(lstate, path);
+ lua_settable(lstate, -3);
+ }
+
+ if (nlua_pcall(lstate, 1, 2)) {
+ nlua_error(lstate, _("Error executing vim.secure.trust: %.*s"));
+ lua_settop(lstate, top);
+ return false;
+ }
+
+ bool success = lua_toboolean(lstate, -2);
+ const char *msg = lua_tostring(lstate, -1);
+ if (msg != NULL) {
+ if (success) {
+ if (strcmp(action, "allow") == 0) {
+ smsg(0, "Allowed \"%s\" in trust database.", msg);
+ } else if (strcmp(action, "deny") == 0) {
+ smsg(0, "Denied \"%s\" in trust database.", msg);
+ } else if (strcmp(action, "remove") == 0) {
+ smsg(0, "Removed \"%s\" from trust database.", msg);
+ }
+ } else {
+ semsg(e_trustfile, msg);
+ }
+ }
+
+ lua_settop(lstate, top);
+ return success;
+}
+
+void ex_trust(exarg_T *eap)
+{
+ const char *const p = skiptowhite(eap->arg);
+ char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg));
+ const char *action = "allow";
+ const char *path = skipwhite(p);
+
+ if (strcmp(arg1, "++deny") == 0) {
+ action = "deny";
+ } else if (strcmp(arg1, "++remove") == 0) {
+ action = "remove";
+ } else if (*arg1 != '\0') {
+ semsg(e_invarg2, arg1);
+ goto theend;
+ }
+
+ if (path[0] == '\0') {
+ path = NULL;
+ }
+
+ nlua_trust(action, path);
+
+theend:
+ xfree(arg1);
+}
diff --git a/src/nvim/lua/secure.h b/src/nvim/lua/secure.h
new file mode 100644
index 0000000000..c69c0d4f8f
--- /dev/null
+++ b/src/nvim/lua/secure.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/secure.h.generated.h"
+#endif
diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c
index d510d25e90..c261c5105e 100644
--- a/src/nvim/lua/spell.c
+++ b/src/nvim/lua/spell.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <lauxlib.h>
#include <limits.h>
@@ -8,7 +5,7 @@
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -16,7 +13,6 @@
#include "nvim/lua/spell.h"
#include "nvim/message.h"
#include "nvim/spell.h"
-#include "nvim/types.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/spell.c.generated.h" // IWYU pragma: export
@@ -39,7 +35,7 @@ int nlua_spell_check(lua_State *lstate)
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -51,7 +47,6 @@ int nlua_spell_check(lua_State *lstate)
}
hlf_T attr = HLF_COUNT;
- size_t len = 0;
size_t pos = 0;
int capcol = -1;
int no_res = 0;
@@ -61,7 +56,7 @@ int nlua_spell_check(lua_State *lstate)
while (*str != NUL) {
attr = HLF_COUNT;
- len = spell_check(curwin, (char *)str, &attr, &capcol, false);
+ size_t len = spell_check(curwin, (char *)str, &attr, &capcol, false);
assert(len <= INT_MAX);
if (attr != HLF_COUNT) {
@@ -70,11 +65,11 @@ int nlua_spell_check(lua_State *lstate)
lua_pushlstring(lstate, str, len);
lua_rawseti(lstate, -2, 1);
- result = attr == HLF_SPB ? "bad" :
- attr == HLF_SPR ? "rare" :
- attr == HLF_SPL ? "local" :
- attr == HLF_SPC ? "caps" :
- NULL;
+ result = attr == HLF_SPB
+ ? "bad" : (attr == HLF_SPR
+ ? "rare" : (attr == HLF_SPL
+ ? "local" : (attr == HLF_SPC
+ ? "caps" : NULL)));
assert(result != NULL);
@@ -82,7 +77,7 @@ int nlua_spell_check(lua_State *lstate)
lua_rawseti(lstate, -2, 2);
// +1 for 1-indexing
- lua_pushinteger(lstate, (long)pos + 1);
+ lua_pushinteger(lstate, (lua_Integer)pos + 1);
lua_rawseti(lstate, -2, 3);
lua_rawseti(lstate, -2, ++no_res);
diff --git a/src/nvim/lua/spell.h b/src/nvim/lua/spell.h
index 8f798a5191..6f1b322e5b 100644
--- a/src/nvim/lua/spell.h
+++ b/src/nvim/lua/spell.h
@@ -1,12 +1,7 @@
-#ifndef NVIM_LUA_SPELL_H
-#define NVIM_LUA_SPELL_H
+#pragma once
-#include <lauxlib.h>
-#include <lua.h>
-#include <lualib.h>
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/spell.h.generated.h"
#endif
-
-#endif // NVIM_LUA_SPELL_H
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 6ebca6d97e..33770b2e62 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
@@ -11,30 +8,36 @@
#include <string.h>
#include <sys/types.h>
-#include "auto/config.h"
+#ifdef NVIM_VENDOR_BIT
+# include "bit.h"
+#endif
+
#include "cjson/lua_cjson.h"
#include "mpack/lmpack.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_eval.h"
+#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/lua/base64.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/spell.h"
#include "nvim/lua/stdlib.h"
#include "nvim/lua/xdiff.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/runtime.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/stdlib.c.generated.h"
@@ -78,20 +81,20 @@ static int regex_match_line(lua_State *lstate)
return luaL_error(lstate, "not enough args");
}
- long bufnr = luaL_checkinteger(lstate, 2);
+ handle_T bufnr = (handle_T)luaL_checkinteger(lstate, 2);
linenr_T rownr = (linenr_T)luaL_checkinteger(lstate, 3);
- long start = 0, end = -1;
+ int start = 0, end = -1;
if (narg >= 4) {
- start = luaL_checkinteger(lstate, 4);
+ start = (int)luaL_checkinteger(lstate, 4);
}
if (narg >= 5) {
- end = luaL_checkinteger(lstate, 5);
+ end = (int)luaL_checkinteger(lstate, 5);
if (end < 0) {
return luaL_error(lstate, "invalid end");
}
}
- buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf;
+ buf_T *buf = bufnr ? handle_get_buffer(bufnr) : curbuf;
if (!buf || buf->b_ml.ml_mfp == NULL) {
return luaL_error(lstate, "invalid buffer");
}
@@ -100,7 +103,7 @@ static int regex_match_line(lua_State *lstate)
return luaL_error(lstate, "invalid row");
}
- char *line = ml_get_buf(buf, rownr + 1, false);
+ char *line = ml_get_buf(buf, rownr + 1);
size_t len = strlen(line);
if (start < 0 || (size_t)start > len) {
@@ -178,8 +181,8 @@ int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
size_t codepoints = 0, codeunits = 0;
mb_utflen(s1, (size_t)idx, &codepoints, &codeunits);
- lua_pushinteger(lstate, (long)codepoints);
- lua_pushinteger(lstate, (long)codeunits);
+ lua_pushinteger(lstate, (lua_Integer)codepoints);
+ lua_pushinteger(lstate, (lua_Integer)codeunits);
return 2;
}
@@ -199,7 +202,7 @@ static int nlua_str_utf_pos(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
size_t clen;
for (size_t i = 0; i < s1_len && s1[i] != NUL; i += clen) {
clen = (size_t)utf_ptr2len_len(s1 + i, (int)(s1_len - i));
- lua_pushinteger(lstate, (long)i + 1);
+ lua_pushinteger(lstate, (lua_Integer)i + 1);
lua_rawseti(lstate, -2, (int)idx);
idx++;
}
@@ -218,11 +221,11 @@ static int nlua_str_utf_start(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
size_t s1_len;
const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
- long offset = luaL_checkinteger(lstate, 2);
+ ptrdiff_t offset = luaL_checkinteger(lstate, 2);
if (offset < 0 || offset > (intptr_t)s1_len) {
return luaL_error(lstate, "index out of range");
}
- int head_offset = utf_cp_head_off((char_u *)s1, (char_u *)s1 + offset - 1);
+ int head_offset = -utf_cp_head_off(s1, s1 + offset - 1);
lua_pushinteger(lstate, head_offset);
return 1;
}
@@ -238,7 +241,7 @@ static int nlua_str_utf_end(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
size_t s1_len;
const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
- long offset = luaL_checkinteger(lstate, 2);
+ ptrdiff_t offset = luaL_checkinteger(lstate, 2);
if (offset < 0 || offset > (intptr_t)s1_len) {
return luaL_error(lstate, "index out of range");
}
@@ -271,7 +274,7 @@ int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
return luaL_error(lstate, "index out of range");
}
- lua_pushinteger(lstate, (long)byteidx);
+ lua_pushinteger(lstate, (lua_Integer)byteidx);
return 1;
}
@@ -282,10 +285,8 @@ int nlua_regex(lua_State *lstate)
const char *text = luaL_checkstring(lstate, 1);
regprog_T *prog = NULL;
- TRY_WRAP({
- try_start();
- prog = vim_regcomp((char *)text, RE_AUTO | RE_MAGIC | RE_STRICT);
- try_end(&err);
+ TRY_WRAP(&err, {
+ prog = vim_regcomp(text, RE_AUTO | RE_MAGIC | RE_STRICT);
});
if (ERROR_SET(&err)) {
@@ -356,6 +357,9 @@ int nlua_setvar(lua_State *lstate)
Error err = ERROR_INIT;
dictitem_T *di = dict_check_writable(dict, key, del, &err);
if (ERROR_SET(&err)) {
+ nlua_push_errstr(lstate, "%s", err.msg);
+ api_clear_error(&err);
+ lua_error(lstate);
return 0;
}
@@ -391,6 +395,15 @@ int nlua_setvar(lua_State *lstate)
di = tv_dict_item_alloc_len(key.data, key.size);
tv_dict_add(dict, di);
} else {
+ bool type_error = false;
+ if (dict == &vimvardict
+ && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
+ tv_clear(&tv);
+ if (type_error) {
+ return luaL_error(lstate, "Setting v:%s to value with wrong type", key.data);
+ }
+ return 0;
+ }
if (watched) {
tv_copy(&di->di_tv, &oldtv);
}
@@ -452,7 +465,7 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
int ret = 0;
assert(s1[s1_len] == NUL);
assert(s2[s2_len] == NUL);
- do {
+ while (true) {
nul1 = memchr(s1, NUL, s1_len);
nul2 = memchr(s2, NUL, s2_len);
ret = STRICMP(s1, s2);
@@ -476,7 +489,7 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
} else {
break;
}
- } while (true);
+ }
lua_pop(lstate, 2);
lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0)));
return 1;
@@ -501,7 +514,7 @@ static int nlua_iconv(lua_State *lstate)
const char *str = lua_tolstring(lstate, 1, &str_len);
char *from = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 2, NULL)));
- char *to = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL)));
+ char *to = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL)));
vimconv_T vimconv;
vimconv.vc_type = CONV_NONE;
@@ -524,6 +537,31 @@ static int nlua_iconv(lua_State *lstate)
return 1;
}
+// Like 'zx' but don't call newFoldLevel()
+static int nlua_foldupdate(lua_State *lstate)
+{
+ curwin->w_foldinvalid = true; // recompute folds
+ foldOpenCursor();
+
+ return 0;
+}
+
+// Access to internal functions. For use in runtime/
+static void nlua_state_add_internal(lua_State *const lstate)
+{
+ // _getvar
+ lua_pushcfunction(lstate, &nlua_getvar);
+ lua_setfield(lstate, -2, "_getvar");
+
+ // _setvar
+ lua_pushcfunction(lstate, &nlua_setvar);
+ lua_setfield(lstate, -2, "_setvar");
+
+ // _updatefolds
+ lua_pushcfunction(lstate, &nlua_foldupdate);
+ lua_setfield(lstate, -2, "_foldupdate");
+}
+
void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
{
if (!is_thread) {
@@ -558,14 +596,6 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
lua_setfield(lstate, -2, "__index"); // [meta]
lua_pop(lstate, 1); // don't use metatable now
- // _getvar
- lua_pushcfunction(lstate, &nlua_getvar);
- lua_setfield(lstate, -2, "_getvar");
-
- // _setvar
- lua_pushcfunction(lstate, &nlua_setvar);
- lua_setfield(lstate, -2, "_setvar");
-
// vim.spell
luaopen_spell(lstate);
lua_setfield(lstate, -2, "spell");
@@ -574,6 +604,12 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
// depends on p_ambw, p_emoji
lua_pushcfunction(lstate, &nlua_iconv);
lua_setfield(lstate, -2, "iconv");
+
+ // vim.base64
+ luaopen_base64(lstate);
+ lua_setfield(lstate, -2, "base64");
+
+ nlua_state_add_internal(lstate);
}
// vim.mpack
@@ -589,6 +625,19 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
lua_setfield(lstate, -2, "mpack");
lua_pop(lstate, 3);
+ // vim.lpeg
+ int luaopen_lpeg(lua_State *);
+ luaopen_lpeg(lstate);
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, -4, "lpeg");
+
+ // package.loaded.lpeg = vim.lpeg
+ lua_getglobal(lstate, "package");
+ lua_getfield(lstate, -1, "loaded");
+ lua_pushvalue(lstate, -3);
+ lua_setfield(lstate, -2, "lpeg");
+ lua_pop(lstate, 4);
+
// vim.diff
lua_pushcfunction(lstate, &nlua_xdl_diff);
lua_setfield(lstate, -2, "diff");
@@ -596,6 +645,13 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
// vim.json
lua_cjson_new(lstate);
lua_setfield(lstate, -2, "json");
+
+#ifdef NVIM_VENDOR_BIT
+ // if building with puc lua, use internal fallback for require'bit'
+ int top = lua_gettop(lstate);
+ luaopen_bit(lstate);
+ lua_settop(lstate, top);
+#endif
}
/// like luaL_error, but allow cleanup
diff --git a/src/nvim/lua/stdlib.h b/src/nvim/lua/stdlib.h
index 17aec6714d..26e96055ae 100644
--- a/src/nvim/lua/stdlib.h
+++ b/src/nvim/lua/stdlib.h
@@ -1,10 +1,7 @@
-#ifndef NVIM_LUA_STDLIB_H
-#define NVIM_LUA_STDLIB_H
+#pragma once
-#include <lua.h>
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/stdlib.h.generated.h"
#endif
-
-#endif // NVIM_LUA_STDLIB_H
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 56f4daed1a..008b3f2e95 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -1,11 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// lua bindings for tree-sitter.
// NB: this file mostly contains a generic lua interface for tree-sitter
// trees and nodes, and could be broken out as a reusable lua package
#include <assert.h>
+#include <ctype.h>
#include <lauxlib.h>
#include <limits.h>
#include <lua.h>
@@ -14,6 +12,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <tree_sitter/api.h>
#include <uv.h>
#include "klib/kvec.h"
@@ -21,14 +20,13 @@
#include "nvim/buffer_defs.h"
#include "nvim/globals.h"
#include "nvim/lua/treesitter.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "tree_sitter/api.h"
+#include "nvim/types_defs.h"
#define TS_META_PARSER "treesitter_parser"
#define TS_META_TREE "treesitter_tree"
@@ -43,6 +41,17 @@ typedef struct {
int max_match_id;
} TSLua_cursor;
+typedef struct {
+ LuaRef cb;
+ lua_State *lstate;
+ bool lex;
+ bool parse;
+} TSLuaLoggerOpts;
+
+typedef struct {
+ TSTree *tree;
+} TSLuaTree;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/treesitter.c.generated.h"
#endif
@@ -51,8 +60,13 @@ static struct luaL_Reg parser_meta[] = {
{ "__gc", parser_gc },
{ "__tostring", parser_tostring },
{ "parse", parser_parse },
+ { "reset", parser_reset },
{ "set_included_ranges", parser_set_ranges },
{ "included_ranges", parser_get_ranges },
+ { "set_timeout", parser_set_timeout },
+ { "timeout", parser_get_timeout },
+ { "_set_logger", parser_set_logger },
+ { "_logger", parser_get_logger },
{ NULL, NULL }
};
@@ -61,6 +75,7 @@ static struct luaL_Reg tree_meta[] = {
{ "__tostring", tree_tostring },
{ "root", tree_root },
{ "edit", tree_edit },
+ { "included_ranges", tree_get_ranges },
{ "copy", tree_copy },
{ NULL, NULL }
};
@@ -78,6 +93,8 @@ static struct luaL_Reg node_meta[] = {
{ "field", node_field },
{ "named", node_named },
{ "missing", node_missing },
+ { "extra", node_extra },
+ { "has_changes", node_has_changes },
{ "has_error", node_has_error },
{ "sexpr", node_sexpr },
{ "child_count", node_child_count },
@@ -95,7 +112,9 @@ static struct luaL_Reg node_meta[] = {
{ "prev_named_sibling", node_prev_named_sibling },
{ "named_children", node_named_children },
{ "root", node_root },
+ { "tree", node_tree },
{ "byte_length", node_byte_length },
+ { "equal", node_equal },
{ NULL, NULL }
};
@@ -132,9 +151,9 @@ static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
lua_pop(L, 1); // [] (don't use it now)
}
-/// init the tslua library
+/// Init the tslua library.
///
-/// all global state is stored in the regirstry of the lua_State
+/// All global state is stored in the registry of the lua_State.
void tslua_init(lua_State *L)
{
// type metatables
@@ -145,15 +164,13 @@ void tslua_init(lua_State *L)
build_meta(L, TS_META_QUERYCURSOR, querycursor_meta);
build_meta(L, TS_META_TREECURSOR, treecursor_meta);
-#ifdef NVIM_TS_HAS_SET_ALLOCATOR
ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree);
-#endif
}
int tslua_has_language(lua_State *L)
{
const char *lang_name = luaL_checkstring(L, 1);
- lua_pushboolean(L, pmap_has(cstr_t)(&langs, lang_name));
+ lua_pushboolean(L, map_has(cstr_t, &langs, lang_name));
return 1;
}
@@ -170,7 +187,7 @@ int tslua_add_language(lua_State *L)
symbol_name = luaL_checkstring(L, 3);
}
- if (pmap_has(cstr_t)(&langs, lang_name)) {
+ if (map_has(cstr_t, &langs, lang_name)) {
lua_pushboolean(L, true);
return 1;
}
@@ -223,11 +240,11 @@ int tslua_add_language(lua_State *L)
int tslua_remove_lang(lua_State *L)
{
const char *lang_name = luaL_checkstring(L, 1);
- bool present = pmap_has(cstr_t)(&langs, lang_name);
+ bool present = map_has(cstr_t, &langs, lang_name);
if (present) {
- char *key = (char *)pmap_key(cstr_t)(&langs, lang_name);
- pmap_del(cstr_t)(&langs, lang_name);
- xfree(key);
+ cstr_t key;
+ pmap_del(cstr_t)(&langs, lang_name, &key);
+ xfree((void *)key);
}
lua_pushboolean(L, present);
return 1;
@@ -309,6 +326,17 @@ static TSParser **parser_check(lua_State *L, uint16_t index)
return luaL_checkudata(L, index, TS_META_PARSER);
}
+static void logger_gc(TSLogger logger)
+{
+ if (!logger.log) {
+ return;
+ }
+
+ TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload;
+ luaL_unref(opts->lstate, LUA_REGISTRYINDEX, opts->cb);
+ xfree(opts);
+}
+
static int parser_gc(lua_State *L)
{
TSParser **p = parser_check(L, 1);
@@ -316,6 +344,7 @@ static int parser_gc(lua_State *L)
return 0;
}
+ logger_gc(ts_parser_logger(*p));
ts_parser_delete(*p);
return 0;
}
@@ -329,7 +358,7 @@ static int parser_tostring(lua_State *L)
static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position,
uint32_t *bytes_read)
{
- buf_T *bp = payload;
+ buf_T *bp = payload;
#define BUFSIZE 256
static char buf[BUFSIZE];
@@ -337,7 +366,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position
*bytes_read = 0;
return "";
}
- char *line = ml_get_buf(bp, (linenr_T)position.row + 1, false);
+ char *line = ml_get_buf(bp, (linenr_T)position.row + 1);
size_t len = strlen(line);
if (position.column > len) {
*bytes_read = 0;
@@ -359,19 +388,29 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position
#undef BUFSIZE
}
-static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length)
+static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length,
+ bool include_bytes)
{
lua_createtable(L, (int)length, 0);
for (size_t i = 0; i < length; i++) {
- lua_createtable(L, 4, 0);
+ lua_createtable(L, include_bytes ? 6 : 4, 0);
+ int j = 1;
lua_pushinteger(L, ranges[i].start_point.row);
- lua_rawseti(L, -2, 1);
+ lua_rawseti(L, -2, j++);
lua_pushinteger(L, ranges[i].start_point.column);
- lua_rawseti(L, -2, 2);
+ lua_rawseti(L, -2, j++);
+ if (include_bytes) {
+ lua_pushinteger(L, ranges[i].start_byte);
+ lua_rawseti(L, -2, j++);
+ }
lua_pushinteger(L, ranges[i].end_point.row);
- lua_rawseti(L, -2, 3);
+ lua_rawseti(L, -2, j++);
lua_pushinteger(L, ranges[i].end_point.column);
- lua_rawseti(L, -2, 4);
+ lua_rawseti(L, -2, j++);
+ if (include_bytes) {
+ lua_pushinteger(L, ranges[i].end_byte);
+ lua_rawseti(L, -2, j++);
+ }
lua_rawseti(L, -2, (int)(i + 1));
}
@@ -386,14 +425,14 @@ static int parser_parse(lua_State *L)
TSTree *old_tree = NULL;
if (!lua_isnil(L, 2)) {
- TSTree **tmp = tree_check(L, 2);
- old_tree = tmp ? *tmp : NULL;
+ TSLuaTree *ud = tree_check(L, 2);
+ old_tree = ud ? ud->tree : NULL;
}
TSTree *new_tree = NULL;
size_t len;
const char *str;
- long bufnr;
+ handle_T bufnr;
buf_T *buf;
TSInput input;
@@ -406,13 +445,13 @@ static int parser_parse(lua_State *L)
break;
case LUA_TNUMBER:
- bufnr = lua_tointeger(L, 3);
- buf = handle_get_buffer((handle_T)bufnr);
+ bufnr = (handle_T)lua_tointeger(L, 3);
+ buf = handle_get_buffer(bufnr);
if (!buf) {
#define BUFSIZE 256
char ebuf[BUFSIZE] = { 0 };
- vim_snprintf(ebuf, BUFSIZE, "invalid buffer handle: %ld", bufnr);
+ vim_snprintf(ebuf, BUFSIZE, "invalid buffer handle: %d", bufnr);
return luaL_argerror(L, 3, ebuf);
#undef BUFSIZE
}
@@ -426,34 +465,46 @@ static int parser_parse(lua_State *L)
return luaL_argerror(L, 3, "expected either string or buffer handle");
}
+ bool include_bytes = (lua_gettop(L) >= 4) && lua_toboolean(L, 4);
+
// Sometimes parsing fails (timeout, or wrong parser ABI)
// In those case, just return an error.
if (!new_tree) {
return luaL_error(L, "An error occurred when parsing.");
}
- // The new tree will be pushed to the stack, without copy, ownership is now to
- // the lua GC.
- // Old tree is still owned by the lua GC.
+ // The new tree will be pushed to the stack, without copy, ownership is now to the lua GC.
+ // Old tree is owned by lua GC since before
uint32_t n_ranges = 0;
- TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) : NULL;
+ TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) : NULL;
- push_tree(L, new_tree, false); // [tree]
+ push_tree(L, new_tree); // [tree]
- push_ranges(L, changed, n_ranges); // [tree, ranges]
+ push_ranges(L, changed, n_ranges, include_bytes); // [tree, ranges]
xfree(changed);
return 2;
}
+static int parser_reset(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (p && *p) {
+ ts_parser_reset(*p);
+ }
+
+ return 0;
+}
+
static int tree_copy(lua_State *L)
{
- TSTree **tree = tree_check(L, 1);
- if (!(*tree)) {
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
return 0;
}
- push_tree(L, *tree, true); // [tree]
+ TSTree *copy = ts_tree_copy(ud->tree);
+ push_tree(L, copy); // [tree]
return 1;
}
@@ -465,8 +516,8 @@ static int tree_edit(lua_State *L)
return lua_error(L);
}
- TSTree **tree = tree_check(L, 1);
- if (!(*tree)) {
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
return 0;
}
@@ -480,11 +531,29 @@ static int tree_edit(lua_State *L)
TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
start_point, old_end_point, new_end_point };
- ts_tree_edit(*tree, &edit);
+ ts_tree_edit(ud->tree, &edit);
return 0;
}
+static int tree_get_ranges(lua_State *L)
+{
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
+ return 0;
+ }
+
+ bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
+
+ uint32_t len;
+ TSRange *ranges = ts_tree_included_ranges(ud->tree, &len);
+
+ push_ranges(L, ranges, len, include_bytes);
+
+ xfree(ranges);
+ return 1;
+}
+
// Use the top of the stack (without popping it) to create a TSRange, it can be
// either a lua table or a TSNode
static void range_from_lua(lua_State *L, TSRange *range)
@@ -590,58 +659,159 @@ static int parser_get_ranges(lua_State *L)
return 0;
}
+ bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
+
uint32_t len;
const TSRange *ranges = ts_parser_included_ranges(*p, &len);
- push_ranges(L, ranges, len);
+ push_ranges(L, ranges, len, include_bytes);
+ return 1;
+}
+
+static int parser_set_timeout(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ if (lua_gettop(L) < 2) {
+ luaL_error(L, "integer expected");
+ }
+
+ uint32_t timeout = (uint32_t)luaL_checkinteger(L, 2);
+ ts_parser_set_timeout_micros(*p, timeout);
+ return 0;
+}
+
+static int parser_get_timeout(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ lua_pushinteger(L, (lua_Integer)ts_parser_timeout_micros(*p));
+ return 1;
+}
+
+static void logger_cb(void *payload, TSLogType logtype, const char *s)
+{
+ TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)payload;
+ if ((!opts->lex && logtype == TSLogTypeLex)
+ || (!opts->parse && logtype == TSLogTypeParse)) {
+ return;
+ }
+
+ lua_State *lstate = opts->lstate;
+
+ lua_rawgeti(lstate, LUA_REGISTRYINDEX, opts->cb);
+ lua_pushstring(lstate, logtype == TSLogTypeParse ? "parse" : "lex");
+ lua_pushstring(lstate, s);
+ if (lua_pcall(lstate, 2, 0, 0)) {
+ luaL_error(lstate, "Error executing treesitter logger callback");
+ }
+}
+
+static int parser_set_logger(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ if (!lua_isboolean(L, 2)) {
+ return luaL_argerror(L, 2, "boolean expected");
+ }
+
+ if (!lua_isboolean(L, 3)) {
+ return luaL_argerror(L, 3, "boolean expected");
+ }
+
+ if (!lua_isfunction(L, 4)) {
+ return luaL_argerror(L, 4, "function expected");
+ }
+
+ TSLuaLoggerOpts *opts = xmalloc(sizeof(TSLuaLoggerOpts));
+ lua_pushvalue(L, 4);
+ LuaRef ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ *opts = (TSLuaLoggerOpts){
+ .lex = lua_toboolean(L, 2),
+ .parse = lua_toboolean(L, 3),
+ .cb = ref,
+ .lstate = L
+ };
+
+ TSLogger logger = {
+ .payload = (void *)opts,
+ .log = logger_cb
+ };
+
+ ts_parser_set_logger(*p, logger);
+ return 0;
+}
+
+static int parser_get_logger(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ TSLogger logger = ts_parser_logger(*p);
+ if (logger.log) {
+ TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload;
+ lua_rawgeti(L, LUA_REGISTRYINDEX, opts->cb);
+ } else {
+ lua_pushnil(L);
+ }
+
return 1;
}
// Tree methods
-/// push tree interface on lua stack.
+/// Push tree interface on to the lua stack.
///
-/// This makes a copy of the tree, so ownership of the argument is unaffected.
-void push_tree(lua_State *L, TSTree *tree, bool do_copy)
+/// The tree is not copied. Ownership of the tree is transferred from C to
+/// Lua. If needed use ts_tree_copy() in the caller
+static void push_tree(lua_State *L, TSTree *tree)
{
if (tree == NULL) {
lua_pushnil(L);
return;
}
- TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata]
- if (do_copy) {
- *ud = ts_tree_copy(tree);
- } else {
- *ud = tree;
- }
+ TSLuaTree *ud = lua_newuserdata(L, sizeof(TSLuaTree)); // [udata]
+
+ ud->tree = tree;
lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREE); // [udata, meta]
lua_setmetatable(L, -2); // [udata]
- // table used for node wrappers to keep a reference to tree wrapper
- // NB: in lua 5.3 the uservalue for the node could just be the tree, but
- // in lua 5.1 the uservalue (fenv) must be a table.
+ // To prevent the tree from being garbage collected, create a reference to it
+ // in the fenv which will be passed to userdata nodes of the tree.
+ // Note: environments (fenvs) associated with userdata have no meaning in Lua
+ // and are only used to associate a table.
lua_createtable(L, 1, 0); // [udata, reftable]
lua_pushvalue(L, -2); // [udata, reftable, udata]
lua_rawseti(L, -2, 1); // [udata, reftable]
lua_setfenv(L, -2); // [udata]
}
-static TSTree **tree_check(lua_State *L, int index)
+static TSLuaTree *tree_check(lua_State *L, int index)
{
- TSTree **ud = luaL_checkudata(L, index, TS_META_TREE);
+ TSLuaTree *ud = luaL_checkudata(L, index, TS_META_TREE);
return ud;
}
static int tree_gc(lua_State *L)
{
- TSTree **tree = tree_check(L, 1);
- if (!tree) {
- return 0;
+ TSLuaTree *ud = tree_check(L, 1);
+ if (ud) {
+ ts_tree_delete(ud->tree);
}
-
- ts_tree_delete(*tree);
return 0;
}
@@ -653,20 +823,20 @@ static int tree_tostring(lua_State *L)
static int tree_root(lua_State *L)
{
- TSTree **tree = tree_check(L, 1);
- if (!tree) {
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
return 0;
}
- TSNode root = ts_tree_root_node(*tree);
+ TSNode root = ts_tree_root_node(ud->tree);
push_node(L, root, 1);
return 1;
}
// Node methods
-/// push node interface on lua stack
+/// Push node interface on to the Lua stack
///
-/// top of stack must either be the tree this node belongs to or another node
+/// Top of stack must either be the tree this node belongs to or another node
/// of the same tree! This value is not popped. Can only be called inside a
/// cfunction with the tslua environment.
static void push_node(lua_State *L, TSNode node, int uindex)
@@ -680,6 +850,8 @@ static void push_node(lua_State *L, TSNode node, int uindex)
*ud = node;
lua_getfield(L, LUA_REGISTRYINDEX, TS_META_NODE); // [udata, meta]
lua_setmetatable(L, -2); // [udata]
+
+ // Copy the fenv which contains the nodes tree.
lua_getfenv(L, uindex); // [udata, reftable]
lua_setfenv(L, -2); // [udata]
}
@@ -740,12 +912,26 @@ static int node_range(lua_State *L)
if (!node_check(L, 1, &node)) {
return 0;
}
+
+ bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
+
TSPoint start = ts_node_start_point(node);
TSPoint end = ts_node_end_point(node);
- lua_pushnumber(L, start.row);
- lua_pushnumber(L, start.column);
- lua_pushnumber(L, end.row);
- lua_pushnumber(L, end.column);
+
+ if (include_bytes) {
+ lua_pushinteger(L, start.row);
+ lua_pushinteger(L, start.column);
+ lua_pushinteger(L, ts_node_start_byte(node));
+ lua_pushinteger(L, end.row);
+ lua_pushinteger(L, end.column);
+ lua_pushinteger(L, ts_node_end_byte(node));
+ return 6;
+ }
+
+ lua_pushinteger(L, start.row);
+ lua_pushinteger(L, start.column);
+ lua_pushinteger(L, end.row);
+ lua_pushinteger(L, end.column);
return 4;
}
@@ -757,9 +943,9 @@ static int node_start(lua_State *L)
}
TSPoint start = ts_node_start_point(node);
uint32_t start_byte = ts_node_start_byte(node);
- lua_pushnumber(L, start.row);
- lua_pushnumber(L, start.column);
- lua_pushnumber(L, start_byte);
+ lua_pushinteger(L, start.row);
+ lua_pushinteger(L, start.column);
+ lua_pushinteger(L, start_byte);
return 3;
}
@@ -771,9 +957,9 @@ static int node_end(lua_State *L)
}
TSPoint end = ts_node_end_point(node);
uint32_t end_byte = ts_node_end_byte(node);
- lua_pushnumber(L, end.row);
- lua_pushnumber(L, end.column);
- lua_pushnumber(L, end_byte);
+ lua_pushinteger(L, end.row);
+ lua_pushinteger(L, end.column);
+ lua_pushinteger(L, end_byte);
return 3;
}
@@ -784,7 +970,7 @@ static int node_child_count(lua_State *L)
return 0;
}
uint32_t count = ts_node_child_count(node);
- lua_pushnumber(L, count);
+ lua_pushinteger(L, count);
return 1;
}
@@ -795,7 +981,7 @@ static int node_named_child_count(lua_State *L)
return 0;
}
uint32_t count = ts_node_named_child_count(node);
- lua_pushnumber(L, count);
+ lua_pushinteger(L, count);
return 1;
}
@@ -816,7 +1002,7 @@ static int node_symbol(lua_State *L)
return 0;
}
TSSymbol symbol = ts_node_symbol(node);
- lua_pushnumber(L, symbol);
+ lua_pushinteger(L, symbol);
return 1;
}
@@ -882,6 +1068,26 @@ static int node_missing(lua_State *L)
return 1;
}
+static int node_extra(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_is_extra(node));
+ return 1;
+}
+
+static int node_has_changes(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_has_changes(node));
+ return 1;
+}
+
static int node_has_error(lua_State *L)
{
TSNode node;
@@ -898,8 +1104,8 @@ static int node_child(lua_State *L)
if (!node_check(L, 1, &node)) {
return 0;
}
- long num = lua_tointeger(L, 2);
- TSNode child = ts_node_child(node, (uint32_t)num);
+ uint32_t num = (uint32_t)lua_tointeger(L, 2);
+ TSNode child = ts_node_child(node, num);
push_node(L, child, 1);
return 1;
@@ -911,8 +1117,8 @@ static int node_named_child(lua_State *L)
if (!node_check(L, 1, &node)) {
return 0;
}
- long num = lua_tointeger(L, 2);
- TSNode child = ts_node_named_child(node, (uint32_t)num);
+ uint32_t num = (uint32_t)lua_tointeger(L, 2);
+ TSNode child = ts_node_named_child(node, num);
push_node(L, child, 1);
return 1;
@@ -1108,6 +1314,19 @@ static int node_root(lua_State *L)
return 1;
}
+static int node_tree(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+
+ lua_getfenv(L, 1); // [udata, reftable]
+ lua_rawgeti(L, -1, 1); // [udata, reftable, tree_udata]
+
+ return 1;
+}
+
static int node_byte_length(lua_State *L)
{
TSNode node;
@@ -1118,7 +1337,23 @@ static int node_byte_length(lua_State *L)
uint32_t start_byte = ts_node_start_byte(node);
uint32_t end_byte = ts_node_end_byte(node);
- lua_pushnumber(L, end_byte - start_byte);
+ lua_pushinteger(L, end_byte - start_byte);
+ return 1;
+}
+
+static int node_equal(lua_State *L)
+{
+ TSNode node1;
+ if (!node_check(L, 1, &node1)) {
+ return 0;
+ }
+
+ TSNode node2;
+ if (!node_check(L, 2, &node2)) {
+ return luaL_error(L, "TSNode expected");
+ }
+
+ lua_pushboolean(L, ts_node_eq(node1, node2));
return 1;
}
@@ -1213,11 +1448,12 @@ static int node_rawquery(lua_State *L)
} else {
cursor = ts_query_cursor_new();
}
- // TODO(clason): API introduced after tree-sitter release 0.19.5
- // remove guard when minimum ts version is bumped to 0.19.6+
-#ifdef NVIM_TS_HAS_SET_MATCH_LIMIT
- ts_query_cursor_set_match_limit(cursor, 64);
+
+#ifdef NVIM_TS_HAS_SET_MAX_START_DEPTH
+ // reset the start depth
+ ts_query_cursor_set_max_start_depth(cursor, UINT32_MAX);
#endif
+ ts_query_cursor_set_match_limit(cursor, 256);
ts_query_cursor_exec(cursor, query, node);
bool captures = lua_toboolean(L, 3);
@@ -1228,6 +1464,29 @@ static int node_rawquery(lua_State *L)
ts_query_cursor_set_point_range(cursor, (TSPoint){ start, 0 }, (TSPoint){ end, 0 });
}
+ if (lua_gettop(L) >= 6 && !lua_isnil(L, 6)) {
+ if (!lua_istable(L, 6)) {
+ return luaL_error(L, "table expected");
+ }
+ lua_pushnil(L);
+ // stack: [dict, ..., nil]
+ while (lua_next(L, 6)) {
+ // stack: [dict, ..., key, value]
+ if (lua_type(L, -2) == LUA_TSTRING) {
+ char *k = (char *)lua_tostring(L, -2);
+ if (strequal("max_start_depth", k)) {
+ // TODO(lewis6991): remove ifdef when min TS version is 0.20.9
+#ifdef NVIM_TS_HAS_SET_MAX_START_DEPTH
+ uint32_t max_start_depth = (uint32_t)lua_tointeger(L, -1);
+ ts_query_cursor_set_max_start_depth(cursor, max_start_depth);
+#endif
+ }
+ }
+ lua_pop(L, 1); // pop the value; lua_next will pop the key.
+ // stack: [dict, ..., key]
+ }
+ }
+
TSLua_cursor *ud = lua_newuserdata(L, sizeof(*ud)); // [udata]
ud->cursor = cursor;
ud->predicated_match = -1;
@@ -1281,8 +1540,9 @@ int tslua_parse_query(lua_State *L)
TSQuery *query = ts_query_new(lang, src, (uint32_t)len, &error_offset, &error_type);
if (!query) {
- return luaL_error(L, "query: %s at position %d for language %s",
- query_err_string(error_type), (int)error_offset, lang_name);
+ char err_msg[IOSIZE];
+ query_err_string(src, (int)error_offset, error_type, err_msg, sizeof(err_msg));
+ return luaL_error(L, "%s", err_msg);
}
TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata]
@@ -1292,24 +1552,79 @@ int tslua_parse_query(lua_State *L)
return 1;
}
-static const char *query_err_string(TSQueryError err)
+static const char *query_err_to_string(TSQueryError error_type)
{
- switch (err) {
+ switch (error_type) {
case TSQueryErrorSyntax:
- return "invalid syntax";
+ return "Invalid syntax:\n";
case TSQueryErrorNodeType:
- return "invalid node type";
+ return "Invalid node type ";
case TSQueryErrorField:
- return "invalid field";
+ return "Invalid field name ";
case TSQueryErrorCapture:
- return "invalid capture";
+ return "Invalid capture name ";
case TSQueryErrorStructure:
- return "invalid structure";
+ return "Impossible pattern:\n";
default:
return "error";
}
}
+static void query_err_string(const char *src, int error_offset, TSQueryError error_type, char *err,
+ size_t errlen)
+{
+ int line_start = 0;
+ int row = 0;
+ const char *error_line = NULL;
+ int error_line_len = 0;
+
+ const char *end_str;
+ do {
+ const char *src_tmp = src + line_start;
+ end_str = strchr(src_tmp, '\n');
+ int line_length = end_str != NULL ? (int)(end_str - src_tmp) : (int)strlen(src_tmp);
+ int line_end = line_start + line_length;
+ if (line_end > error_offset) {
+ error_line = src_tmp;
+ error_line_len = line_length;
+ break;
+ }
+ line_start = line_end + 1;
+ row++;
+ } while (end_str != NULL);
+
+ int column = error_offset - line_start;
+
+ const char *type_msg = query_err_to_string(error_type);
+ snprintf(err, errlen, "Query error at %d:%d. %s", row + 1, column + 1, type_msg);
+ size_t offset = strlen(err);
+ errlen = errlen - offset;
+ err = err + offset;
+
+ // Error types that report names
+ if (error_type == TSQueryErrorNodeType
+ || error_type == TSQueryErrorField
+ || error_type == TSQueryErrorCapture) {
+ const char *suffix = src + error_offset;
+ int suffix_len = 0;
+ char c = suffix[suffix_len];
+ while (isalnum(c) || c == '_' || c == '-' || c == '.') {
+ c = suffix[++suffix_len];
+ }
+ snprintf(err, errlen, "\"%.*s\":\n", suffix_len, suffix);
+ offset = strlen(err);
+ errlen = errlen - offset;
+ err = err + offset;
+ }
+
+ if (!error_line) {
+ snprintf(err, errlen, "Unexpected EOF\n");
+ return;
+ }
+
+ snprintf(err, errlen, "%.*s\n%*s^\n", error_line_len, error_line, column, "");
+}
+
static TSQuery *query_check(lua_State *L, int index)
{
TSQuery **ud = luaL_checkudata(L, index, TS_META_QUERY);
@@ -1367,7 +1682,7 @@ static int query_inspect(lua_State *L)
&strlen);
lua_pushlstring(L, str, strlen); // [retval, patterns, pat, pred, item]
} else if (step[k].type == TSQueryPredicateStepTypeCapture) {
- lua_pushnumber(L, step[k].value_id + 1); // [..., pat, pred, item]
+ lua_pushinteger(L, step[k].value_id + 1); // [..., pat, pred, item]
} else {
abort();
}
diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h
index b69fb9dfae..4ef9a10602 100644
--- a/src/nvim/lua/treesitter.h
+++ b/src/nvim/lua/treesitter.h
@@ -1,14 +1,7 @@
-#ifndef NVIM_LUA_TREESITTER_H
-#define NVIM_LUA_TREESITTER_H
+#pragma once
-#include <lauxlib.h>
-#include <lua.h>
-#include <lualib.h>
-
-#include "tree_sitter/api.h"
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/treesitter.h.generated.h"
#endif
-
-#endif // NVIM_LUA_TREESITTER_H
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index 857b159af5..16c3aa5e11 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -1,9 +1,7 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <lauxlib.h>
#include <lua.h>
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
#include "luaconf.h"
@@ -13,9 +11,9 @@
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/lua/xdiff.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
#include "xdiff/xdiff.h"
#define COMPARED_BUFFER0 (1 << 0)
@@ -32,7 +30,7 @@ typedef struct {
Error *err;
mmfile_t *ma;
mmfile_t *mb;
- bool linematch;
+ int64_t linematch;
bool iwhite;
} hunkpriv_t;
@@ -63,25 +61,25 @@ static void lua_pushhunk(lua_State *lstate, long start_a, long count_a, long sta
lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1);
}
-static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, long start_a,
- long count_a, long start_b, long count_b, bool iwhite)
+static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, int start_a,
+ int count_a, int start_b, int count_b, bool iwhite)
{
// get the pointer to char of the start of the diff to pass it to linematch algorithm
const char *diff_begin[2] = { ma->ptr, mb->ptr };
- int diff_length[2] = { (int)count_a, (int)count_b };
+ int diff_length[2] = { count_a, count_b };
- fastforward_buf_to_lnum(&diff_begin[0], start_a + 1);
- fastforward_buf_to_lnum(&diff_begin[1], start_b + 1);
+ fastforward_buf_to_lnum(&diff_begin[0], (linenr_T)start_a + 1);
+ fastforward_buf_to_lnum(&diff_begin[1], (linenr_T)start_b + 1);
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite);
- long lnuma = start_a, lnumb = start_b;
+ int lnuma = start_a, lnumb = start_b;
- long hunkstarta = lnuma;
- long hunkstartb = lnumb;
- long hunkcounta = 0;
- long hunkcountb = 0;
+ int hunkstarta = lnuma;
+ int hunkstartb = lnumb;
+ int hunkcounta = 0;
+ int hunkcountb = 0;
for (size_t i = 0; i < decisions_length; i++) {
if (i && (decisions[i - 1] != decisions[i])) {
lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb);
@@ -109,8 +107,8 @@ static int write_string(void *priv, mmbuffer_t *mb, int nbuf)
{
luaL_Buffer *buf = (luaL_Buffer *)priv;
for (int i = 0; i < nbuf; i++) {
- const long size = mb[i].size;
- for (long total = 0; total < size; total += LUAL_BUFFERSIZE) {
+ const int size = mb[i].size;
+ for (int total = 0; total < size; total += LUAL_BUFFERSIZE) {
const int tocopy = MIN((int)(size - total), LUAL_BUFFERSIZE);
char *p = luaL_prepbuffer(buf);
if (!p) {
@@ -124,11 +122,11 @@ static int write_string(void *priv, mmbuffer_t *mb, int nbuf)
}
// hunk_func callback used when opts.hunk_lines = true
-static int hunk_locations_cb(long start_a, long count_a, long start_b, long count_b, void *cb_data)
+static int hunk_locations_cb(int start_a, int count_a, int start_b, int count_b, void *cb_data)
{
hunkpriv_t *priv = (hunkpriv_t *)cb_data;
lua_State *lstate = priv->lstate;
- if (priv->linematch) {
+ if (priv->linematch > 0 && count_a + count_b <= priv->linematch) {
get_linematch_results(lstate, priv->ma, priv->mb, start_a, count_a, start_b, count_b,
priv->iwhite);
} else {
@@ -139,7 +137,7 @@ static int hunk_locations_cb(long start_a, long count_a, long start_b, long coun
}
// hunk_func callback used when opts.on_hunk is given
-static int call_on_hunk_cb(long start_a, long count_a, long start_b, long count_b, void *cb_data)
+static int call_on_hunk_cb(int start_a, int count_a, int start_b, int count_b, void *cb_data)
{
// Mimic extra offsets done by xdiff, see:
// src/xdiff/xemit.c:284
@@ -193,11 +191,11 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char *
{
if (actType != expType) {
const char *type_str =
- expType == kObjectTypeString ? "string" :
- expType == kObjectTypeInteger ? "integer" :
- expType == kObjectTypeBoolean ? "boolean" :
- expType == kObjectTypeLuaRef ? "function" :
- "NA";
+ expType == kObjectTypeString
+ ? "string" : (expType == kObjectTypeInteger
+ ? "integer" : (expType == kObjectTypeBoolean
+ ? "boolean" : (expType == kObjectTypeLuaRef
+ ? "function" : "NA")));
api_set_error(err, kErrorTypeValidation, "%s is not a %s", name,
type_str);
@@ -208,7 +206,7 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char *
}
static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params,
- bool *linematch, Error *err)
+ int64_t *linematch, Error *err)
{
const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err);
@@ -257,16 +255,20 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg,
if (check_xdiff_opt(v->type, kObjectTypeInteger, "ctxlen", err)) {
goto exit_1;
}
- cfg->ctxlen = v->data.integer;
+ cfg->ctxlen = (long)v->data.integer;
} else if (strequal("interhunkctxlen", k.data)) {
if (check_xdiff_opt(v->type, kObjectTypeInteger, "interhunkctxlen",
err)) {
goto exit_1;
}
- cfg->interhunkctxlen = v->data.integer;
+ cfg->interhunkctxlen = (long)v->data.integer;
} else if (strequal("linematch", k.data)) {
- *linematch = api_object_to_bool(*v, "linematch", false, err);
- if (ERROR_SET(err)) {
+ if (v->type == kObjectTypeBoolean) {
+ *linematch = v->data.boolean ? INT64_MAX : 0;
+ } else if (v->type == kObjectTypeInteger) {
+ *linematch = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "linematch must be a boolean or integer");
goto exit_1;
}
} else {
@@ -330,7 +332,7 @@ int nlua_xdl_diff(lua_State *lstate)
xdemitconf_t cfg;
xpparam_t params;
xdemitcb_t ecb;
- bool linematch = false;
+ int64_t linematch = 0;
CLEAR_FIELD(cfg);
CLEAR_FIELD(params);
@@ -362,18 +364,18 @@ int nlua_xdl_diff(lua_State *lstate)
cfg.hunk_func = call_on_hunk_cb;
priv = (hunkpriv_t) {
.lstate = lstate,
- .err = &err,
+ .err = &err,
};
ecb.priv = &priv;
break;
case kNluaXdiffModeLocations:
cfg.hunk_func = hunk_locations_cb;
priv = (hunkpriv_t) {
- .lstate = lstate,
- .ma = &ma,
- .mb = &mb,
+ .lstate = lstate,
+ .ma = &ma,
+ .mb = &mb,
.linematch = linematch,
- .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0
+ .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0
};
ecb.priv = &priv;
lua_createtable(lstate, 0, 0);
diff --git a/src/nvim/lua/xdiff.h b/src/nvim/lua/xdiff.h
index b172d2f922..2ea74a79e8 100644
--- a/src/nvim/lua/xdiff.h
+++ b/src/nvim/lua/xdiff.h
@@ -1,12 +1,7 @@
-#ifndef NVIM_LUA_XDIFF_H
-#define NVIM_LUA_XDIFF_H
+#pragma once
-#include <lauxlib.h>
-#include <lua.h>
-#include <lualib.h>
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/xdiff.h.generated.h"
#endif
-
-#endif // NVIM_LUA_XDIFF_H
diff --git a/src/nvim/macros.h b/src/nvim/macros_defs.h
index 242e3c381a..a7af2f91c3 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MACROS_H
-#define NVIM_MACROS_H
+#pragma once
#include "auto/config.h"
@@ -61,8 +60,6 @@
/// Don't apply 'langmap' if the character comes from the Stuff buffer or from a
/// mapping and the langnoremap option was set.
/// The do-while is just to ignore a ';' after the macro.
-///
-/// -V:LANGMAP_ADJUST:560
#define LANGMAP_ADJUST(c, condition) \
do { \
if (*p_langmap \
@@ -82,16 +79,7 @@
#define READBIN "rb"
#define APPENDBIN "ab"
-// mch_open_rw(): invoke os_open() with third argument for user R/W.
-#if defined(UNIX) // open in rw------- mode
-# define MCH_OPEN_RW(n, f) os_open((n), (f), (mode_t)0600)
-#elif defined(MSWIN)
-# define MCH_OPEN_RW(n, f) os_open((n), (f), S_IREAD | S_IWRITE)
-#else
-# define MCH_OPEN_RW(n, f) os_open((n), (f), 0)
-#endif
-
-#define REPLACE_NORMAL(s) (((s) & REPLACE_FLAG) && !((s) & VREPLACE_FLAG))
+#define REPLACE_NORMAL(s) (((s)& REPLACE_FLAG) && !((s)& VREPLACE_FLAG))
// MB_PTR_ADV(): advance a pointer to the next character, taking care of
// multi-byte characters if needed. Skip over composing chars.
@@ -120,8 +108,6 @@
/// error. A mechanism to detect many (though not all) of those errors at
/// compile time is implemented. It works by the second division producing
/// a division by zero in those cases (-Wdiv-by-zero in GCC).
-///
-/// -V:ARRAY_SIZE:1063
#define ARRAY_SIZE(arr) \
((sizeof(arr)/sizeof((arr)[0])) \
/ ((size_t)(!(sizeof(arr) % sizeof((arr)[0])))))
@@ -163,17 +149,13 @@
# define FALLTHROUGH
#endif
-// -V:STRUCT_CAST:641
-
-/// Change type of structure pointers: cast `struct a *` to `struct b *`
-///
-/// Used to silence PVS errors.
-///
-/// @param Type Structure to cast to.
-/// @param obj Object to cast.
-///
-/// @return ((Type *)obj).
-#define STRUCT_CAST(Type, obj) ((Type *)(obj))
+#if defined(__clang__) || defined(__GNUC__)
+# define UNREACHABLE __builtin_unreachable()
+#elif defined(_MSVC_VER)
+# define UNREACHABLE __assume(false)
+#else
+# define UNREACHABLE
+#endif
// Type of uv_buf_t.len is platform-dependent.
// Related: https://github.com/libuv/libuv/pull/1236
@@ -228,5 +210,3 @@
#endif
#define EMPTY_POS(a) ((a).lnum == 0 && (a).col == 0 && (a).coladd == 0)
-
-#endif // NVIM_MACROS_H
diff --git a/src/nvim/main.c b/src/nvim/main.c
index bbe877356d..6585bd1df7 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1,7 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-#define EXTERN
+// Make sure extern symbols are exported on Windows
+#ifdef WIN32
+# define EXTERN __declspec(dllexport)
+#else
+# define EXTERN
+#endif
#include <assert.h>
#include <limits.h>
#include <msgpack/pack.h>
@@ -10,10 +12,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#ifdef ENABLE_ASAN_UBSAN
+# include <sanitizer/asan_interface.h>
+# include <sanitizer/ubsan_interface.h>
+#endif
-#include "auto/config.h"
+#include "auto/config.h" // IWYU pragma: keep
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/ui.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -24,14 +34,17 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
+#include "nvim/event/process.h"
#include "nvim/event/stream.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -41,60 +54,52 @@
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/locale.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/lua/secure.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/msgpack_rpc/server.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/fileio.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
+#include "nvim/os/signal.h"
#include "nvim/os/stdpaths_defs.h"
-#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
#include "nvim/shada.h"
-#include "nvim/sign.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
#include "nvim/ui_compositor.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
#endif
-#include "nvim/api/extmark.h"
-#include "nvim/api/private/defs.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/api/ui.h"
-#include "nvim/event/loop.h"
-#include "nvim/event/process.h"
-#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/msgpack_rpc/server.h"
-#include "nvim/os/signal.h"
// values for "window_layout"
enum {
@@ -167,10 +172,9 @@ bool event_teardown(void)
/// Performs early initialization.
///
-/// Needed for unit tests. Must be called after `time_init()`.
+/// Needed for unit tests.
void early_init(mparm_T *paramp)
{
- env_init();
estack_init();
cmdline_init();
eval_init(); // init global variables
@@ -182,19 +186,24 @@ void early_init(mparm_T *paramp)
#ifdef MSWIN
OSVERSIONINFO ovi;
ovi.dwOSVersionInfoSize = sizeof(ovi);
+ // Disable warning about GetVersionExA being deprecated. There doesn't seem to be a convenient
+ // replacement that doesn't add a ton of extra code as of writing this.
+# ifdef _MSC_VER
+# pragma warning(suppress : 4996)
+ GetVersionEx(&ovi);
+# else
GetVersionEx(&ovi);
+# endif
snprintf(windowsVersion, sizeof(windowsVersion), "%d.%d",
(int)ovi.dwMajorVersion, (int)ovi.dwMinorVersion);
#endif
TIME_MSG("early init");
-#if defined(HAVE_LOCALE_H)
// Setup to use the current locale (for ctype() and many other things).
// NOTE: Translated messages with encodings other than latin1 will not
// work until set_init_1() has been called!
init_locale();
-#endif
// tabpage local options (p_ch) must be set before allocating first tabpage.
set_init_tablocal();
@@ -214,8 +223,6 @@ void early_init(mparm_T *paramp)
TIME_MSG("inits 1");
set_lang_var(); // set v:lang and v:ctype
-
- init_signs();
}
#ifdef MAKE_LIB
@@ -239,20 +246,28 @@ int main(int argc, char **argv)
argv0 = argv[0];
+ if (!appname_is_valid()) {
+ os_errmsg("$NVIM_APPNAME must be a name or relative path.\n");
+ exit(1);
+ }
+
+ if (argc > 1 && STRICMP(argv[1], "-ll") == 0) {
+ if (argc == 2) {
+ print_mainerr(err_arg_missing, argv[1]);
+ exit(1);
+ }
+ nlua_run_script(argv, argc, 3);
+ }
+
char *fname = NULL; // file name from command line
mparm_T params; // various parameters passed between
// main() and other functions.
char *cwd = NULL; // current working dir on startup
- time_init();
// Many variables are in `params` so that we can pass them around easily.
// `argc` and `argv` are also copied, so that they can be changed.
init_params(&params, argc, argv);
- // Since os_open is called during the init_startuptime, we need to call
- // fs_init before it.
- fs_init();
-
init_startuptime(&params);
// Need to find "--clean" before actually parsing arguments.
@@ -286,6 +301,16 @@ int main(int argc, char **argv)
}
}
+ if (GARGCOUNT > 0) {
+ fname = get_fname(&params, cwd);
+ }
+
+ // Recovery mode without a file name: List swap files.
+ // In this case, no UI is needed.
+ if (recoverymode && fname == NULL) {
+ headless_mode = true;
+ }
+
#ifdef MSWIN
// on windows we use CONIN special file, thus we don't know this yet.
bool has_term = true;
@@ -312,21 +337,11 @@ int main(int argc, char **argv)
uint64_t rv = ui_client_start_server(params.argc, params.argv);
if (!rv) {
os_errmsg("Failed to start Nvim server!\n");
- getout(1);
+ os_exit(1);
}
ui_client_channel_id = rv;
}
- if (GARGCOUNT > 0) {
- fname = get_fname(&params, cwd);
- }
-
- // Recovery mode without a file name: List swap files.
- // In this case, no UI is needed.
- if (recoverymode && fname == NULL) {
- headless_mode = true;
- }
-
TIME_MSG("expanding arguments");
if (params.diff_mode && params.window_count == -1) {
@@ -335,7 +350,7 @@ int main(int argc, char **argv)
// Don't redraw until much later.
RedrawingDisabled++;
- setbuf(stdout, NULL);
+ setbuf(stdout, NULL); // NOLINT(bugprone-unsafe-functions)
full_screen = !silent_mode;
@@ -348,7 +363,7 @@ int main(int argc, char **argv)
}
assert(p_ch >= 0 && Rows >= p_ch && Rows - p_ch <= INT_MAX);
- cmdline_row = (int)(Rows - p_ch);
+ cmdline_row = Rows - (int)p_ch;
msg_row = cmdline_row;
default_grid_alloc(); // allocate screen buffers
set_init_2(headless_mode);
@@ -372,13 +387,15 @@ int main(int argc, char **argv)
if (ui_client_channel_id) {
ui_client_run(remote_ui); // NORETURN
}
+ assert(!ui_client_channel_id && !use_builtin_ui);
// Wait for UIs to set up Nvim or show early messages
// and prompts (--cmd, swapfile dialog, …).
bool use_remote_ui = (embedded_mode && !headless_mode);
+ bool listen_and_embed = params.listen_addr != NULL;
if (use_remote_ui) {
TIME_MSG("waiting for UI");
- remote_ui_wait_for_attach();
+ remote_ui_wait_for_attach(!listen_and_embed);
TIME_MSG("done waiting for UI");
firstwin->w_prev_height = firstwin->w_height; // may have changed
}
@@ -396,19 +413,9 @@ int main(int argc, char **argv)
open_script_files(&params);
- // Default mappings (incl. menus)
- Error err = ERROR_INIT;
- Object o = NLUA_EXEC_STATIC("return vim._init_default_mappings()",
- (Array)ARRAY_DICT_INIT, &err);
- assert(!ERROR_SET(&err));
- api_clear_error(&err);
- assert(o.type == kObjectTypeNil);
- api_free_object(o);
-
- TIME_MSG("init default mappings");
+ nlua_init_defaults();
- init_default_autocmds();
- TIME_MSG("init default autocommands");
+ TIME_MSG("init default mappings & autocommands");
bool vimrc_none = strequal(params.use_vimrc, "NONE");
@@ -432,8 +439,7 @@ int main(int argc, char **argv)
// If using the runtime (-u is not NONE), enable syntax & filetype plugins.
if (!vimrc_none || params.clean) {
- // Sources filetype.lua and filetype.vim unless the user explicitly disabled it with :filetype
- // off.
+ // Sources filetype.lua unless the user explicitly disabled it with :filetype off.
filetype_maybe_enable();
// Sources syntax/syntax.vim. We do this *after* the user startup scripts so that users can
// disable syntax highlighting with `:syntax off` if they wish.
@@ -449,7 +455,7 @@ int main(int argc, char **argv)
// Recovery mode without a file name: List swap files.
// Uses the 'dir' option, therefore it must be after the initializations.
if (recoverymode && fname == NULL) {
- recover_names(NULL, true, 0, NULL);
+ recover_names(NULL, true, NULL, 0, NULL);
os_exit(0);
}
@@ -572,16 +578,16 @@ int main(int argc, char **argv)
// 'autochdir' has been postponed.
do_autochdir();
- set_vim_var_nr(VV_VIM_DID_ENTER, 1L);
+ set_vim_var_nr(VV_VIM_DID_ENTER, 1);
apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf);
TIME_MSG("VimEnter autocommands");
- if (use_remote_ui || use_builtin_ui) {
- do_autocmd_uienter(use_remote_ui ? CHAN_STDIO : 0, true);
+ if (use_remote_ui) {
+ do_autocmd_uienter_all();
TIME_MSG("UIEnter autocommands");
}
#ifdef MSWIN
- if (use_builtin_ui) {
+ if (use_remote_ui) {
os_icon_init();
}
os_title_save();
@@ -597,7 +603,7 @@ int main(int argc, char **argv)
// scrollbind, sync the scrollbind now.
if (curwin->w_p_diff && curwin->w_p_scb) {
update_topline(curwin);
- check_scrollbind((linenr_T)0, 0L);
+ check_scrollbind(0, 0);
TIME_MSG("diff scrollbinding");
}
@@ -613,8 +619,14 @@ int main(int argc, char **argv)
}
if (params.luaf != NULL) {
+ // Like "--cmd", "+", "-c" and "-S", don't truncate messages.
+ msg_scroll = true;
bool lua_ok = nlua_exec_file(params.luaf);
TIME_MSG("executing Lua -l script");
+ if (msg_didout) {
+ msg_putchar('\n');
+ msg_didout = false;
+ }
getout(lua_ok ? 0 : 1);
}
@@ -637,6 +649,9 @@ void os_exit(int r)
if (ui_client_channel_id) {
ui_client_stop();
+ if (r == 0) {
+ r = ui_client_exit_status;
+ }
} else {
ui_flush();
ui_call_stop();
@@ -663,6 +678,7 @@ void os_exit(int r)
void getout(int exitval)
FUNC_ATTR_NORETURN
{
+ assert(!ui_client_channel_id);
exiting = true;
// On error during Ex mode, exit with a non-zero code.
@@ -673,8 +689,8 @@ void getout(int exitval)
set_vim_var_nr(VV_EXITING, exitval);
- // Position the cursor on the last screen line, below all the text
- ui_cursor_goto(Rows - 1, 0);
+ // Invoked all deferred functions in the function stack.
+ invoke_all_defer();
// Optionally print hashtable efficiency.
hash_debug_results();
@@ -686,7 +702,7 @@ void getout(int exitval)
for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) {
next_tp = tp->tp_next;
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
- if (wp->w_buffer == NULL) {
+ if (wp->w_buffer == NULL || !buf_valid(wp->w_buffer)) {
// Autocmd must have close the buffer already, skip.
continue;
}
@@ -761,9 +777,6 @@ void getout(int exitval)
wait_return(false);
}
- // Position the cursor again, the autocommands may have moved it
- ui_cursor_goto(Rows - 1, 0);
-
// Apply 'titleold'.
if (p_title && *p_titleold != NUL) {
ui_call_set_title(cstr_as_string(p_titleold));
@@ -782,10 +795,11 @@ void getout(int exitval)
os_exit(exitval);
}
-/// Preserve files, print contents of `IObuff`, and exit 1.
+/// Preserve files, print contents of `errmsg`, and exit 1.
+/// @param errmsg If NULL, this function will not print anything.
///
/// May be called from deadly_signal().
-void preserve_exit(void)
+void preserve_exit(const char *errmsg)
FUNC_ATTR_NORETURN
{
// 'true' when we are sure to exit, e.g., after a deadly signal
@@ -803,16 +817,26 @@ void preserve_exit(void)
really_exiting = true;
// Ignore SIGHUP while we are already exiting. #9274
signal_reject_deadly();
- os_errmsg(IObuff);
- os_errmsg("\n");
- ui_flush();
+
+ if (ui_client_channel_id) {
+ // For TUI: exit alternate screen so that the error messages can be seen.
+ ui_client_stop();
+ }
+ if (errmsg != NULL) {
+ os_errmsg(errmsg);
+ os_errmsg("\n");
+ }
+ if (ui_client_channel_id) {
+ os_exit(1);
+ }
ml_close_notmod(); // close all not-modified buffers
FOR_ALL_BUFFERS(buf) {
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
- os_errmsg("Vim: preserving files...\r\n");
- ui_flush();
+ if (errmsg != NULL) {
+ os_errmsg("Vim: preserving files...\r\n");
+ }
ml_sync_all(false, false, true); // preserve all swap files
break;
}
@@ -820,7 +844,9 @@ void preserve_exit(void)
ml_close_all(false); // close all memfiles, without deleting
- os_errmsg("Vim: Finished.\r\n");
+ if (errmsg != NULL) {
+ os_errmsg("Vim: Finished.\r\n");
+ }
getout(1);
}
@@ -840,7 +866,7 @@ void preserve_exit(void)
static int get_number_arg(const char *p, int *idx, int def)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (ascii_isdigit(p[*idx])) { // -V522
+ if (ascii_isdigit(p[*idx])) {
def = atoi(&(p[*idx]));
while (ascii_isdigit(p[*idx])) {
*idx = *idx + 1;
@@ -888,6 +914,11 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
os_errmsg(connect_error);
os_errmsg("\n");
os_exit(1);
+ } else if (strequal(server_addr, os_getenv("NVIM"))) {
+ os_errmsg("Cannot attach UI of :terminal child to its parent. ");
+ os_errmsg("(Unset $NVIM to skip this check)");
+ os_errmsg("\n");
+ os_exit(1);
}
ui_client_channel_id = chan;
@@ -895,9 +926,8 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
}
Array args = ARRAY_DICT_INIT;
- String arg_s;
for (int t_argc = remote_args; t_argc < argc; t_argc++) {
- arg_s = cstr_to_string(argv[t_argc]);
+ String arg_s = cstr_to_string(argv[t_argc]);
ADD(args, STRING_OBJ(arg_s));
}
@@ -927,7 +957,7 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
TriState tabbed = kNone;
for (size_t i = 0; i < rvobj.data.dictionary.size; i++) {
- if (strcmp(rvobj.data.dictionary.items[i].key.data, "errmsg") == 0) {
+ if (strequal(rvobj.data.dictionary.items[i].key.data, "errmsg")) {
if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) {
os_errmsg("vim._cs_remote returned an unexpected type for 'errmsg'\n");
os_exit(2);
@@ -935,13 +965,19 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
os_errmsg(rvobj.data.dictionary.items[i].value.data.string.data);
os_errmsg("\n");
os_exit(2);
- } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "tabbed") == 0) {
+ } else if (strequal(rvobj.data.dictionary.items[i].key.data, "result")) {
+ if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) {
+ os_errmsg("vim._cs_remote returned an unexpected type for 'result'\n");
+ os_exit(2);
+ }
+ os_msg(rvobj.data.dictionary.items[i].value.data.string.data);
+ } else if (strequal(rvobj.data.dictionary.items[i].key.data, "tabbed")) {
if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) {
os_errmsg("vim._cs_remote returned an unexpected type for 'tabbed'\n");
os_exit(2);
}
tabbed = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse;
- } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "should_exit") == 0) {
+ } else if (strequal(rvobj.data.dictionary.items[i].key.data, "should_exit")) {
if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) {
os_errmsg("vim._cs_remote returned an unexpected type for 'should_exit'\n");
os_exit(2);
@@ -984,7 +1020,7 @@ static void command_line_scan(mparm_T *parmp)
int argv_idx; // index in argv[n][]
bool had_minmin = false; // found "--" argument
int want_argument; // option argument with argument
- long n;
+ int n;
argc--;
argv++;
@@ -1035,6 +1071,10 @@ static void command_line_scan(mparm_T *parmp)
version();
os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {
+#ifdef MSWIN
+ // set stdout to binary to avoid crlf in --api-info output
+ _setmode(STDOUT_FILENO, _O_BINARY);
+#endif
FileDescriptor fp;
const int fof_ret = file_open_fd(&fp, STDOUT_FILENO,
kFileWriteOnly);
@@ -1082,7 +1122,7 @@ static void command_line_scan(mparm_T *parmp)
} else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) {
parmp->use_vimrc = "NONE";
parmp->clean = true;
- set_option_value_give_err("shadafile", 0L, "NONE", 0);
+ set_option_value_give_err("shadafile", STATIC_CSTR_AS_OPTVAL("NONE"), 0);
} else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) {
nlua_disable_preload = true;
} else {
@@ -1096,7 +1136,7 @@ static void command_line_scan(mparm_T *parmp)
}
break;
case 'A': // "-A" start in Arabic mode.
- set_option_value_give_err("arabic", 1L, NULL, 0);
+ set_option_value_give_err("arabic", BOOLEAN_OPTVAL(true), 0);
break;
case 'b': // "-b" binary mode.
// Needs to be effective before expanding file names, because
@@ -1125,9 +1165,9 @@ static void command_line_scan(mparm_T *parmp)
case 'h': // "-h" give help message
usage();
os_exit(0);
- case 'H': // "-H" start in Hebrew mode: rl + hkmap set.
- p_hkmap = true;
- set_option_value_give_err("rl", 1L, NULL, 0);
+ case 'H': // "-H" start in Hebrew mode: rl + keymap=hebrew set.
+ set_option_value_give_err("keymap", STATIC_CSTR_AS_OPTVAL("hebrew"), 0);
+ set_option_value_give_err("rl", BOOLEAN_OPTVAL(true), 0);
break;
case 'M': // "-M" no changes or writing of files
reset_modifiable();
@@ -1207,7 +1247,7 @@ static void command_line_scan(mparm_T *parmp)
// default is 10: a little bit verbose
p_verbose = get_number_arg(argv[0], &argv_idx, 10);
if (argv[0][argv_idx] != NUL) {
- set_option_value_give_err("verbosefile", 0L, argv[0] + argv_idx, 0);
+ set_option_value_give_err("verbosefile", CSTR_AS_OPTVAL(argv[0] + argv_idx), 0);
argv_idx = (int)strlen(argv[0]);
}
break;
@@ -1215,7 +1255,7 @@ static void command_line_scan(mparm_T *parmp)
// "-w {scriptout}" write to script
if (ascii_isdigit((argv[0])[argv_idx])) {
n = get_number_arg(argv[0], &argv_idx, 10);
- set_option_value_give_err("window", n, NULL, 0);
+ set_option_value_give_err("window", NUMBER_OPTVAL((OptInt)n), 0);
break;
}
want_argument = true;
@@ -1311,7 +1351,7 @@ static void command_line_scan(mparm_T *parmp)
break;
case 'i': // "-i {shada}" use for shada
- set_option_value_give_err("shadafile", 0L, argv[0], 0);
+ set_option_value_give_err("shadafile", CSTR_AS_OPTVAL(argv[0]), 0);
break;
case 'l': // "-l" Lua script: args after "-l".
@@ -1321,11 +1361,11 @@ static void command_line_scan(mparm_T *parmp)
parmp->no_swap_file = true;
parmp->use_vimrc = parmp->use_vimrc ? parmp->use_vimrc : "NONE";
if (p_shadafile == NULL || *p_shadafile == NUL) {
- set_option_value_give_err("shadafile", 0L, "NONE", 0);
+ set_option_value_give_err("shadafile", STATIC_CSTR_AS_OPTVAL("NONE"), 0);
}
parmp->luaf = argv[0];
argc--;
- if (argc > 0) { // Lua args after "-l <file>".
+ if (argc >= 0) { // Lua args after "-l <file>".
parmp->lua_arg0 = parmp->argc - argc;
argc = 0;
}
@@ -1357,7 +1397,7 @@ scripterror:
if (ascii_isdigit(*(argv[0]))) {
argv_idx = 0;
n = get_number_arg(argv[0], &argv_idx, 10);
- set_option_value_give_err("window", n, NULL, 0);
+ set_option_value_give_err("window", NUMBER_OPTVAL((OptInt)n), 0);
argv_idx = -1;
break;
}
@@ -1448,7 +1488,7 @@ static void init_startuptime(mparm_T *paramp)
{
for (int i = 1; i < paramp->argc - 1; i++) {
if (STRICMP(paramp->argv[i], "--startuptime") == 0) {
- time_fd = os_fopen(paramp->argv[i + 1], "a");
+ time_fd = fopen(paramp->argv[i + 1], "a");
time_start("--- NVIM STARTING ---");
break;
}
@@ -1532,6 +1572,7 @@ static void handle_tag(char *tagname)
// If the user doesn't want to edit the file then we quit here.
if (swap_exists_did_quit) {
+ ui_call_error_exit(1);
getout(1);
}
}
@@ -1570,7 +1611,7 @@ static void open_script_files(mparm_T *parmp)
scriptin[0] = file_open_new(&error, parmp->scriptin,
kFileReadOnly|kFileNonBlocking, 0);
if (scriptin[0] == NULL) {
- vim_snprintf((char *)IObuff, IOSIZE,
+ vim_snprintf(IObuff, IOSIZE,
_("Cannot open for reading: \"%s\": %s\n"),
parmp->scriptin, os_strerror(error));
os_errmsg(IObuff);
@@ -1595,9 +1636,6 @@ static void open_script_files(mparm_T *parmp)
// Also does recovery if "recoverymode" set.
static void create_windows(mparm_T *parmp)
{
- int dorewind;
- int done = 0;
-
// Create the number of windows that was requested.
if (parmp->window_count == -1) { // was not set
parmp->window_count = 1;
@@ -1633,6 +1671,7 @@ static void create_windows(mparm_T *parmp)
}
do_modelines(0); // do modelines
} else {
+ int done = 0;
// Open a buffer for windows that don't have one yet.
// Commands in the vimrc might have loaded a file or split the window.
// Watch out for autocommands that delete a window.
@@ -1640,7 +1679,7 @@ static void create_windows(mparm_T *parmp)
// Don't execute Win/Buf Enter/Leave autocommands here
autocmd_no_enter++;
autocmd_no_leave++;
- dorewind = true;
+ int dorewind = true;
while (done++ < 1000) {
if (dorewind) {
if (parmp->window_layout == WIN_TABS) {
@@ -1677,6 +1716,7 @@ static void create_windows(mparm_T *parmp)
if (got_int || only_one_window()) {
// abort selected or quit and only one window
did_emsg = false; // avoid hit-enter prompt
+ ui_call_error_exit(1);
getout(1);
}
// We can't close the window, it would disturb what
@@ -1712,7 +1752,6 @@ static void create_windows(mparm_T *parmp)
static void edit_buffers(mparm_T *parmp, char *cwd)
{
int arg_idx; // index in argument list
- int i;
bool advance = true;
win_T *win;
char *p_shm_save = NULL;
@@ -1728,7 +1767,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
}
arg_idx = 1;
- for (i = 1; i < parmp->window_count; i++) {
+ for (int i = 1; i < parmp->window_count; i++) {
if (cwd != NULL) {
os_chdir(cwd);
}
@@ -1754,7 +1793,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
p_shm_save = xstrdup(p_shm);
snprintf(buf, sizeof(buf), "F%s", p_shm);
- set_option_value_give_err("shm", 0L, buf, 0);
+ set_option_value_give_err("shm", CSTR_AS_OPTVAL(buf), 0);
}
} else {
if (curwin->w_next == NULL) { // just checking
@@ -1780,6 +1819,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
if (got_int || only_one_window()) {
// abort selected and only one window
did_emsg = false; // avoid hit-enter prompt
+ ui_call_error_exit(1);
getout(1);
}
win_close(curwin, true, false);
@@ -1798,7 +1838,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
}
if (p_shm_save != NULL) {
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
+ set_option_value_give_err("shm", CSTR_AS_OPTVAL(p_shm_save), 0);
xfree(p_shm_save);
}
@@ -1831,7 +1871,6 @@ static void exe_pre_commands(mparm_T *parmp)
{
char **cmds = parmp->pre_commands;
int cnt = parmp->n_pre_commands;
- int i;
if (cnt <= 0) {
return;
@@ -1840,7 +1879,7 @@ static void exe_pre_commands(mparm_T *parmp)
curwin->w_cursor.lnum = 0; // just in case..
estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0);
current_sctx.sc_sid = SID_CMDARG;
- for (i = 0; i < cnt; i++) {
+ for (int i = 0; i < cnt; i++) {
do_cmdline_cmd(cmds[i]);
}
estack_pop();
@@ -1851,8 +1890,6 @@ static void exe_pre_commands(mparm_T *parmp)
// Execute "+", "-c" and "-S" arguments.
static void exe_commands(mparm_T *parmp)
{
- int i;
-
// We start commands on line 0, make "vim +/pat file" match a
// pattern on line 1. But don't move the cursor when an autocommand
// with g`" was used.
@@ -1863,7 +1900,7 @@ static void exe_commands(mparm_T *parmp)
estack_push(ETYPE_ARGS, "command line", 0);
current_sctx.sc_sid = SID_CARG;
current_sctx.sc_seq = 0;
- for (i = 0; i < parmp->n_commands; i++) {
+ for (int i = 0; i < parmp->n_commands; i++) {
do_cmdline_cmd(parmp->commands[i]);
if (parmp->cmds_tofree[i]) {
xfree(parmp->commands[i]);
@@ -1915,7 +1952,7 @@ static void do_system_initialization(void)
dir_len += 1;
}
memcpy(vimrc + dir_len, path_tail, sizeof(path_tail));
- if (do_source(vimrc, false, DOSO_NONE) != FAIL) {
+ if (do_source(vimrc, false, DOSO_NONE, NULL) != FAIL) {
xfree(vimrc);
xfree(config_dirs);
return;
@@ -1927,7 +1964,7 @@ static void do_system_initialization(void)
#ifdef SYS_VIMRC_FILE
// Get system wide defaults, if the file name is defined.
- (void)do_source(SYS_VIMRC_FILE, false, DOSO_NONE);
+ (void)do_source(SYS_VIMRC_FILE, false, DOSO_NONE, NULL);
#endif
}
@@ -1956,7 +1993,7 @@ static bool do_user_initialization(void)
// init.lua
if (os_path_exists(init_lua_path)
- && do_source(init_lua_path, true, DOSO_VIMRC)) {
+ && do_source(init_lua_path, true, DOSO_VIMRC, NULL)) {
if (os_path_exists(user_vimrc)) {
semsg(_("E5422: Conflicting configs: \"%s\" \"%s\""), init_lua_path,
user_vimrc);
@@ -1970,7 +2007,7 @@ static bool do_user_initialization(void)
xfree(init_lua_path);
// init.vim
- if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) {
+ if (do_source(user_vimrc, true, DOSO_VIMRC, NULL) != FAIL) {
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare(VIMRC_FILE, user_vimrc, false, true) != kEqualFiles);
@@ -1996,7 +2033,7 @@ static bool do_user_initialization(void)
memmove(vimrc, dir, dir_len);
vimrc[dir_len] = PATHSEP;
memmove(vimrc + dir_len + 1, path_tail, sizeof(path_tail));
- if (do_source(vimrc, true, DOSO_VIMRC) != FAIL) {
+ if (do_source(vimrc, true, DOSO_VIMRC, NULL) != FAIL) {
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare(VIMRC_FILE, vimrc, false, true) != kEqualFiles);
@@ -2062,7 +2099,7 @@ static void source_startup_scripts(const mparm_T *const parmp)
|| strequal(parmp->use_vimrc, "NORC")) {
// Do nothing.
} else {
- if (do_source(parmp->use_vimrc, false, DOSO_NONE) != OK) {
+ if (do_source(parmp->use_vimrc, false, DOSO_NONE, NULL) != OK) {
semsg(_("E282: Cannot read from \"%s\""), parmp->use_vimrc);
}
}
@@ -2095,7 +2132,7 @@ static int execute_env(char *env)
current_sctx.sc_sid = SID_ENV;
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = 0;
- do_cmdline_cmd((char *)initstr);
+ do_cmdline_cmd(initstr);
estack_pop();
current_sctx = save_current_sctx;
@@ -2111,6 +2148,12 @@ static int execute_env(char *env)
static void mainerr(const char *errstr, const char *str)
FUNC_ATTR_NORETURN
{
+ print_mainerr(errstr, str);
+ os_exit(1);
+}
+
+static void print_mainerr(const char *errstr, const char *str)
+{
char *prgname = path_tail(argv0);
signal_stop(); // kill us with CTRL-C here, if you like
@@ -2120,14 +2163,12 @@ static void mainerr(const char *errstr, const char *str)
os_errmsg(_(errstr));
if (str != NULL) {
os_errmsg(": \"");
- os_errmsg((char *)str);
+ os_errmsg(str);
os_errmsg("\"");
}
os_errmsg(_("\nMore info with \""));
os_errmsg(prgname);
os_errmsg(" -h\"\n");
-
- os_exit(1);
}
/// Prints version information for "nvim -v" or "nvim --version".
@@ -2147,43 +2188,33 @@ static void usage(void)
signal_stop(); // kill us with CTRL-C here, if you like
os_msg(_("Usage:\n"));
- os_msg(_(" nvim [options] [file ...] Edit file(s)\n"));
- os_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n"));
- os_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n"));
+ os_msg(_(" nvim [options] [file ...]\n"));
os_msg(_("\nOptions:\n"));
- os_msg(_(" -- Only file names after this\n"));
- os_msg(_(" + Start at end of file\n"));
os_msg(_(" --cmd <cmd> Execute <cmd> before any config\n"));
os_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"));
os_msg(_(" -l <script> [args...] Execute Lua <script> (with optional args)\n"));
+ os_msg(_(" -S <session> Source <session> after loading the first file\n"));
+ os_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
+ os_msg(_(" -u <config> Use this config file\n"));
os_msg("\n");
- os_msg(_(" -b Binary mode\n"));
os_msg(_(" -d Diff mode\n"));
- os_msg(_(" -e, -E Ex mode\n"));
os_msg(_(" -es, -Es Silent (batch) mode\n"));
os_msg(_(" -h, --help Print this help message\n"));
os_msg(_(" -i <shada> Use this shada file\n"));
- os_msg(_(" -m Modifications (writing files) not allowed\n"));
- os_msg(_(" -M Modifications in text not allowed\n"));
os_msg(_(" -n No swap file, use memory only\n"));
os_msg(_(" -o[N] Open N windows (default: one per file)\n"));
os_msg(_(" -O[N] Open N vertical windows (default: one per file)\n"));
os_msg(_(" -p[N] Open N tab pages (default: one per file)\n"));
- os_msg(_(" -r, -L List swap files\n"));
- os_msg(_(" -r <file> Recover edit state for this file\n"));
- os_msg(_(" -R Read-only mode\n"));
- os_msg(_(" -S <session> Source <session> after loading the first file\n"));
- os_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
- os_msg(_(" -u <config> Use this config file\n"));
+ os_msg(_(" -R Read-only (view) mode\n"));
os_msg(_(" -v, --version Print version information\n"));
os_msg(_(" -V[N][file] Verbose [level][file]\n"));
os_msg("\n");
+ os_msg(_(" -- Only file names after this\n"));
os_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n"));
os_msg(_(" --clean \"Factory defaults\" (skip user config and plugins, shada)\n"));
os_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n"));
os_msg(_(" --headless Don't start a user interface\n"));
os_msg(_(" --listen <address> Serve RPC API from this address\n"));
- os_msg(_(" --noplugin Don't load plugins\n"));
os_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n"));
os_msg(_(" --server <address> Specify RPC server to send commands to\n"));
os_msg(_(" --startuptime <file> Write startup timing messages to <file>\n"));
@@ -2196,7 +2227,20 @@ static void usage(void)
static void check_swap_exists_action(void)
{
if (swap_exists_action == SEA_QUIT) {
+ ui_call_error_exit(1);
getout(1);
}
handle_swap_exists(NULL);
}
+
+#ifdef ENABLE_ASAN_UBSAN
+const char *__ubsan_default_options(void)
+{
+ return "print_stacktrace=1";
+}
+
+const char *__asan_default_options(void)
+{
+ return "handle_abort=1,handle_sigill=1";
+}
+#endif
diff --git a/src/nvim/main.h b/src/nvim/main.h
index 2d54837872..6aeb62712a 100644
--- a/src/nvim/main.h
+++ b/src/nvim/main.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MAIN_H
-#define NVIM_MAIN_H
+#pragma once
#include <stdbool.h>
@@ -51,4 +50,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "main.h.generated.h"
#endif
-#endif // NVIM_MAIN_H
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 191a459863..be6bf58daa 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -1,197 +1,193 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
+// map.c: Hash maps and sets
//
-// map.c: khash.h wrapper
+// parts of the implementation derived from khash.h as part of klib (MIT license)
//
// NOTE: Callers must manage memory (allocate) for keys and values.
-// khash.h does not make its own copy of the key or value.
-//
+// Map and Set does not make its own copy of the key or value.
#include <stdbool.h>
-#include <stdlib.h>
#include <string.h>
#include "auto/config.h"
-#include "klib/khash.h"
-#include "nvim/gettext.h"
-#include "nvim/map.h"
#include "nvim/map_defs.h"
#include "nvim/memory.h"
-#define cstr_t_hash kh_str_hash_func
-#define cstr_t_eq kh_str_hash_equal
-#define uint64_t_hash kh_int64_hash_func
-#define uint64_t_eq kh_int64_hash_equal
-#define uint32_t_hash kh_int_hash_func
-#define uint32_t_eq kh_int_hash_equal
-#define int_hash kh_int_hash_func
-#define int_eq kh_int_hash_equal
-#define handle_T_hash kh_int_hash_func
-#define handle_T_eq kh_int_hash_equal
-#define KittyKey_hash kh_int_hash_func
-#define KittyKey_eq kh_int_hash_equal
+#define equal_simple(x, y) ((x) == (y))
+
+#define hash_uint64_t(key) (uint32_t)((key) >> 33^(key)^(key) << 11)
+#define equal_uint64_t equal_simple
+#define hash_uint32_t(x) (x)
+#define equal_uint32_t equal_simple
+#define hash_int(x) hash_uint32_t((uint32_t)(x))
+#define equal_int equal_simple
+#define hash_int64_t(key) hash_uint64_t((uint64_t)key)
+#define equal_int64_t equal_simple
#if defined(ARCH_64)
-# define ptr_t_hash(key) uint64_t_hash((uint64_t)(key))
-# define ptr_t_eq(a, b) uint64_t_eq((uint64_t)(a), (uint64_t)(b))
+# define hash_ptr_t(key) hash_uint64_t((uint64_t)(key))
+# define equal_ptr_t(a, b) equal_uint64_t((uint64_t)(a), (uint64_t)(b))
#elif defined(ARCH_32)
-# define ptr_t_hash(key) uint32_t_hash((uint32_t)(key))
-# define ptr_t_eq(a, b) uint32_t_eq((uint32_t)(a), (uint32_t)(b))
+# define hash_ptr_t(key) hash_uint32_t((uint32_t)(key))
+# define equal_ptr_t(a, b) equal_uint32_t((uint32_t)(a), (uint32_t)(b))
#endif
-#define INITIALIZER(T, U) T##_##U##_initializer
-#define INITIALIZER_DECLARE(T, U, ...) const U INITIALIZER(T, U) = __VA_ARGS__
-#define DEFAULT_INITIALIZER { 0 }
-#define SSIZE_INITIALIZER { -1 }
-
-#define MAP_IMPL(T, U, ...) \
- INITIALIZER_DECLARE(T, U, __VA_ARGS__); \
- __KHASH_IMPL(T##_##U##_map, , T, U, 1, T##_hash, T##_eq) \
- void map_##T##_##U##_destroy(Map(T, U) *map) \
- { \
- kh_dealloc(T##_##U##_map, &map->table); \
- } \
- U map_##T##_##U##_get(Map(T, U) *map, T key) \
- { \
- khiter_t k; \
- if ((k = kh_get(T##_##U##_map, &map->table, key)) == kh_end(&map->table)) { \
- return INITIALIZER(T, U); \
- } \
- return kh_val(&map->table, k); \
- } \
- bool map_##T##_##U##_has(Map(T, U) *map, T key) \
- { \
- return kh_get(T##_##U##_map, &map->table, key) != kh_end(&map->table); \
- } \
- T map_##T##_##U##_key(Map(T, U) *map, T key) \
- { \
- khiter_t k; \
- if ((k = kh_get(T##_##U##_map, &map->table, key)) == kh_end(&map->table)) { \
- abort(); /* Caller must check map_has(). */ \
- } \
- return kh_key(&map->table, k); \
- } \
- U map_##T##_##U##_put(Map(T, U) *map, T key, U value) \
- { \
- int ret; \
- U rv = INITIALIZER(T, U); \
- khiter_t k = kh_put(T##_##U##_map, &map->table, key, &ret); \
- if (!ret) { \
- rv = kh_val(&map->table, k); \
- } \
- kh_val(&map->table, k) = value; \
- return rv; \
- } \
- U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put) \
- { \
- int ret; \
- khiter_t k; \
- if (put) { \
- k = kh_put(T##_##U##_map, &map->table, key, &ret); \
- if (ret) { \
- kh_val(&map->table, k) = INITIALIZER(T, U); \
- } \
- } else { \
- k = kh_get(T##_##U##_map, &map->table, key); \
- if (k == kh_end(&map->table)) { \
- return NULL; \
- } \
- } \
- return &kh_val(&map->table, k); \
- } \
- U map_##T##_##U##_del(Map(T, U) *map, T key) \
- { \
- U rv = INITIALIZER(T, U); \
- khiter_t k; \
- if ((k = kh_get(T##_##U##_map, &map->table, key)) != kh_end(&map->table)) { \
- rv = kh_val(&map->table, k); \
- kh_del(T##_##U##_map, &map->table, k); \
- } \
- return rv; \
- } \
- void map_##T##_##U##_clear(Map(T, U) *map) \
- { \
- kh_clear(T##_##U##_map, &map->table); \
- }
-
-static inline khint_t String_hash(String s)
+static inline uint32_t hash_cstr_t(const char *s)
{
- khint_t h = 0;
- for (size_t i = 0; i < s.size && s.data[i]; i++) {
- h = (h << 5) - h + (uint8_t)s.data[i];
+ uint32_t h = 0;
+ for (size_t i = 0; s[i]; i++) {
+ h = (h << 5) - h + (uint8_t)s[i];
}
return h;
}
-static inline bool String_eq(String a, String b)
-{
- if (a.size != b.size) {
- return false;
- }
- return memcmp(a.data, b.data, a.size) == 0;
-}
+#define equal_cstr_t strequal
-static inline khint_t HlEntry_hash(HlEntry ae)
+static inline uint32_t hash_HlEntry(HlEntry ae)
{
const uint8_t *data = (const uint8_t *)&ae;
- khint_t h = 0;
+ uint32_t h = 0;
for (size_t i = 0; i < sizeof(ae); i++) {
h = (h << 5) - h + data[i];
}
return h;
}
-static inline bool HlEntry_eq(HlEntry ae1, HlEntry ae2)
+static inline bool equal_HlEntry(HlEntry ae1, HlEntry ae2)
{
return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
}
-static inline khint_t ColorKey_hash(ColorKey ae)
+static inline uint32_t hash_ColorKey(ColorKey ae)
{
const uint8_t *data = (const uint8_t *)&ae;
- khint_t h = 0;
+ uint32_t h = 0;
for (size_t i = 0; i < sizeof(ae); i++) {
h = (h << 5) - h + data[i];
}
return h;
}
-static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2)
+static inline bool equal_ColorKey(ColorKey ae1, ColorKey ae2)
{
return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
}
-MAP_IMPL(int, int, DEFAULT_INITIALIZER)
-MAP_IMPL(int, cstr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(cstr_t, int, DEFAULT_INITIALIZER)
-MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint32_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
-MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER)
-MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
-MAP_IMPL(String, handle_T, 0)
-MAP_IMPL(String, int, DEFAULT_INITIALIZER)
-MAP_IMPL(int, String, DEFAULT_INITIALIZER)
-
-MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)
-
-MAP_IMPL(KittyKey, cstr_t, DEFAULT_INITIALIZER)
+// TODO(bfredl): this could be _less_ for the h->hash part as this is now small (4 bytes per value)
+#define UPPER_FILL 0.77
+
+#define roundup32(x) (--(x), (x) |= (x)>>1, (x) |= (x)>>2, (x) |= (x)>>4, (x) |= (x)>>8, \
+ (x) |= (x)>>16, ++(x))
+
+// h->hash must either be NULL or an already valid pointer
+void mh_realloc(MapHash *h, uint32_t n_min_buckets)
+{
+ xfree(h->hash);
+ uint32_t n_buckets = n_min_buckets < 16 ? 16 : n_min_buckets;
+ roundup32(n_buckets);
+ // sets all buckets to EMPTY
+ h->hash = xcalloc(n_buckets, sizeof *h->hash);
+ h->n_occupied = h->size = 0;
+ h->n_buckets = n_buckets;
+ h->upper_bound = (uint32_t)(h->n_buckets * UPPER_FILL + 0.5);
+}
+
+void mh_clear(MapHash *h)
+{
+ if (h->hash) {
+ memset(h->hash, 0, h->n_buckets * sizeof(*h->hash));
+ h->size = h->n_occupied = 0;
+ h->n_keys = 0;
+ }
+}
+
+#define KEY_NAME(x) x##int
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, int)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, String)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##ptr_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##cstr_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, int)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##String
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, int)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##uint32_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, uint32_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##uint64_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, ssize_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, uint64_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##int64_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, int64_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##HlEntry
+#include "nvim/map_key_impl.c.h"
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##ColorKey
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ColorItem)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
/// Deletes a key:value pair from a string:pointer map, and frees the
/// storage of both key and value.
///
void pmap_del2(PMap(cstr_t) *map, const char *key)
{
- if (pmap_has(cstr_t)(map, key)) {
- void *k = (void *)pmap_key(cstr_t)(map, key);
- void *v = pmap_get(cstr_t)(map, key);
- pmap_del(cstr_t)(map, key);
- xfree(k);
- xfree(v);
- }
+ cstr_t key_alloc = NULL;
+ ptr_t val = pmap_del(cstr_t)(map, key, &key_alloc);
+ xfree((void *)key_alloc);
+ xfree(val);
}
diff --git a/src/nvim/map.h b/src/nvim/map.h
deleted file mode 100644
index 92f0b32255..0000000000
--- a/src/nvim/map.h
+++ /dev/null
@@ -1,96 +0,0 @@
-#ifndef NVIM_MAP_H
-#define NVIM_MAP_H
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-
-#include "klib/khash.h"
-#include "nvim/api/private/defs.h"
-#include "nvim/extmark_defs.h"
-#include "nvim/gettext.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/map_defs.h"
-#include "nvim/tui/input_defs.h"
-#include "nvim/types.h"
-#include "nvim/ui_client.h"
-
-#if defined(__NetBSD__)
-# undef uint64_t
-# define uint64_t uint64_t
-#endif
-
-#define MAP_DECLS(T, U) \
- KHASH_DECLARE(T##_##U##_map, T, U) \
- typedef struct { \
- khash_t(T##_##U##_map) table; \
- } Map(T, U); \
- Map(T, U) *map_##T##_##U##_new(void); \
- void map_##T##_##U##_free(Map(T, U) *map); \
- void map_##T##_##U##_destroy(Map(T, U) *map); \
- U map_##T##_##U##_get(Map(T, U) *map, T key); \
- bool map_##T##_##U##_has(Map(T, U) *map, T key); \
- T map_##T##_##U##_key(Map(T, U) *map, T key); \
- U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \
- U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put); \
- U map_##T##_##U##_del(Map(T, U) *map, T key); \
- void map_##T##_##U##_clear(Map(T, U) *map);
-
-//
-// NOTE: Keys AND values must be allocated! khash.h does not make a copy.
-//
-MAP_DECLS(int, int)
-MAP_DECLS(int, cstr_t)
-MAP_DECLS(cstr_t, ptr_t)
-MAP_DECLS(cstr_t, int)
-MAP_DECLS(ptr_t, ptr_t)
-MAP_DECLS(uint32_t, ptr_t)
-MAP_DECLS(uint64_t, ptr_t)
-MAP_DECLS(uint64_t, ssize_t)
-MAP_DECLS(uint64_t, uint64_t)
-MAP_DECLS(uint32_t, uint32_t)
-
-MAP_DECLS(handle_T, ptr_t)
-MAP_DECLS(HlEntry, int)
-MAP_DECLS(String, handle_T)
-MAP_DECLS(String, int)
-MAP_DECLS(int, String)
-
-MAP_DECLS(ColorKey, ColorItem)
-
-MAP_DECLS(KittyKey, cstr_t)
-
-#define MAP_INIT { { 0, 0, 0, 0, NULL, NULL, NULL } }
-#define map_init(k, v, map) do { *(map) = (Map(k, v)) MAP_INIT; } while (false)
-
-#define map_destroy(T, U) map_##T##_##U##_destroy
-#define map_get(T, U) map_##T##_##U##_get
-#define map_has(T, U) map_##T##_##U##_has
-#define map_key(T, U) map_##T##_##U##_key
-#define map_put(T, U) map_##T##_##U##_put
-#define map_ref(T, U) map_##T##_##U##_ref
-#define map_del(T, U) map_##T##_##U##_del
-#define map_clear(T, U) map_##T##_##U##_clear
-
-#define map_size(map) ((map)->table.size)
-
-#define pmap_destroy(T) map_destroy(T, ptr_t)
-#define pmap_get(T) map_get(T, ptr_t)
-#define pmap_has(T) map_has(T, ptr_t)
-#define pmap_key(T) map_key(T, ptr_t)
-#define pmap_put(T) map_put(T, ptr_t)
-#define pmap_ref(T) map_ref(T, ptr_t)
-/// @see pmap_del2
-#define pmap_del(T) map_del(T, ptr_t)
-#define pmap_clear(T) map_clear(T, ptr_t)
-#define pmap_init(k, map) map_init(k, ptr_t, map)
-
-#define map_foreach(map, key, value, block) \
- kh_foreach(&(map)->table, key, value, block)
-
-#define map_foreach_value(map, value, block) \
- kh_foreach_value(&(map)->table, value, block)
-
-void pmap_del2(PMap(cstr_t) *map, const char *key);
-
-#endif // NVIM_MAP_H
diff --git a/src/nvim/map_defs.h b/src/nvim/map_defs.h
index 61afedbe50..147c03327a 100644
--- a/src/nvim/map_defs.h
+++ b/src/nvim/map_defs.h
@@ -1,12 +1,226 @@
-#ifndef NVIM_MAP_DEFS_H
-#define NVIM_MAP_DEFS_H
+#pragma once
-#include "klib/khash.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/assert_defs.h"
+#include "nvim/highlight_defs.h"
+#include "nvim/types_defs.h"
+
+#if defined(__NetBSD__)
+# undef uint64_t
+# define uint64_t uint64_t
+#endif
typedef const char *cstr_t;
typedef void *ptr_t;
-#define Map(T, U) Map_##T##_##U
+// when used as a key, String doesn't need to be NUL terminated,
+// and can also contain embedded NUL:s as part of the data.
+static inline uint32_t hash_String(String s)
+{
+ uint32_t h = 0;
+ for (size_t i = 0; i < s.size; i++) {
+ h = (h << 5) - h + (uint8_t)s.data[i];
+ }
+ return h;
+}
+
+static inline bool equal_String(String a, String b)
+{
+ if (a.size != b.size) {
+ return false;
+ }
+ return memcmp(a.data, b.data, a.size) == 0;
+}
+
+#define Set(type) Set_##type
+#define Map(T, U) Map_##T##U
#define PMap(T) Map(T, ptr_t)
-#endif // NVIM_MAP_DEFS_H
+static const int value_init_int = 0;
+static const ptr_t value_init_ptr_t = NULL;
+static const ssize_t value_init_ssize_t = -1;
+static const uint32_t value_init_uint32_t = 0;
+static const uint64_t value_init_uint64_t = 0;
+static const int64_t value_init_int64_t = 0;
+static const String value_init_String = STRING_INIT;
+static const ColorItem value_init_ColorItem = COLOR_ITEM_INITIALIZER;
+
+// layer 0: type non-specific code
+
+typedef struct {
+ uint32_t n_buckets;
+ uint32_t size;
+ uint32_t n_occupied;
+ uint32_t upper_bound;
+ uint32_t n_keys; // this is almost always "size", but keys[] could contain ded items..
+ uint32_t keys_capacity;
+ uint32_t *hash;
+} MapHash;
+
+#define MAPHASH_INIT { 0, 0, 0, 0, 0, 0, NULL }
+#define SET_INIT { MAPHASH_INIT, NULL }
+#define MAP_INIT { SET_INIT, NULL }
+
+#define MH_TOMBSTONE UINT32_MAX
+
+#define mh_is_empty(h, i) ((h)->hash[i] == 0)
+#define mh_is_del(h, i) ((h)->hash[i] == MH_TOMBSTONE)
+#define mh_is_either(h, i) ((uint32_t)((h)->hash[i] + 1U) <= 1U)
+
+typedef enum {
+ kMHExisting = 0,
+ kMHNewKeyDidFit,
+ kMHNewKeyRealloc,
+} MHPutStatus;
+
+void mh_clear(MapHash *h);
+void mh_realloc(MapHash *h, uint32_t n_min_buckets);
+
+// layer 1: key type specific defs
+// This is all need for sets.
+
+#define MH_DECLS(T, K, K_query) \
+ typedef struct { \
+ MapHash h; \
+ K *keys; \
+ } Set(T); \
+ \
+ uint32_t mh_find_bucket_##T(Set(T) *set, K_query key, bool put); \
+ uint32_t mh_get_##T(Set(T) *set, K_query key); \
+ void mh_rehash_##T(Set(T) *set); \
+ uint32_t mh_put_##T(Set(T) *set, K_query key, MHPutStatus *new); \
+
+#define KEY_DECLS(T) \
+ MH_DECLS(T, T, T) \
+ uint32_t mh_delete_##T(Set(T) *set, T *key); \
+ static inline bool set_put_##T(Set(T) *set, T key, T **key_alloc) { \
+ MHPutStatus status; \
+ uint32_t k = mh_put_##T(set, key, &status); \
+ if (key_alloc) { \
+ *key_alloc = &set->keys[k]; \
+ } \
+ return status != kMHExisting; \
+ } \
+ static inline T set_del_##T(Set(T) *set, T key) \
+ { \
+ mh_delete_##T(set, &key); \
+ return key; \
+ } \
+ static inline bool set_has_##T(Set(T) *set, T key) { \
+ return mh_get_##T(set, key) != MH_TOMBSTONE; \
+ } \
+
+// layer 2: key+value specific defs
+// now we finally get Maps
+
+#define MAP_DECLS(T, U) \
+ typedef struct { \
+ Set(T) set; \
+ U *values; \
+ } Map(T, U); \
+ static inline U map_get_##T##U(Map(T, U) *map, T key) \
+ { \
+ uint32_t k = mh_get_##T(&map->set, key); \
+ return k == MH_TOMBSTONE ? value_init_##U : map->values[k]; \
+ } \
+ U *map_ref_##T##U(Map(T, U) *map, T key, T **key_alloc); \
+ U *map_put_ref_##T##U(Map(T, U) *map, T key, T **key_alloc, bool *new_item); \
+ static inline void map_put_##T##U(Map(T, U) *map, T key, U value) \
+ { \
+ U *val = map_put_ref_##T##U(map, key, NULL, NULL); \
+ *val = value; \
+ } \
+ U map_del_##T##U(Map(T, U) *map, T key, T *key_alloc); \
+
+// NOTE: Keys AND values must be allocated! Map and Set does not make a copy.
+
+#define quasiquote(x, y) x##y
+
+MH_DECLS(glyph, char, String)
+KEY_DECLS(int)
+KEY_DECLS(cstr_t)
+KEY_DECLS(ptr_t)
+KEY_DECLS(uint64_t)
+KEY_DECLS(int64_t)
+KEY_DECLS(uint32_t)
+KEY_DECLS(String)
+KEY_DECLS(HlEntry)
+KEY_DECLS(ColorKey)
+
+MAP_DECLS(int, int)
+MAP_DECLS(int, ptr_t)
+MAP_DECLS(cstr_t, ptr_t)
+MAP_DECLS(cstr_t, int)
+MAP_DECLS(ptr_t, ptr_t)
+MAP_DECLS(uint32_t, ptr_t)
+MAP_DECLS(uint64_t, ptr_t)
+MAP_DECLS(uint64_t, ssize_t)
+MAP_DECLS(uint64_t, uint64_t)
+MAP_DECLS(int64_t, int64_t)
+MAP_DECLS(int64_t, ptr_t)
+MAP_DECLS(uint32_t, uint32_t)
+MAP_DECLS(String, int)
+MAP_DECLS(int, String)
+MAP_DECLS(ColorKey, ColorItem)
+
+#define set_has(T, set, key) set_has_##T(set, key)
+#define set_put(T, set, key) set_put_##T(set, key, NULL)
+#define set_put_ref(T, set, key, key_alloc) set_put_##T(set, key, key_alloc)
+#define set_put_idx(T, set, key, status) mh_put_##T(set, key, status)
+#define set_del(T, set, key) set_del_##T(set, key)
+#define set_destroy(T, set) (xfree((set)->keys), xfree((set)->h.hash))
+#define set_clear(T, set) mh_clear(&(set)->h)
+#define set_size(set) ((set)->h.size)
+
+#define map_get(T, U) map_get_##T##U
+#define map_has(T, map, key) set_has(T, &(map)->set, key)
+#define map_put(T, U) map_put_##T##U
+#define map_ref(T, U) map_ref_##T##U
+#define map_put_ref(T, U) map_put_ref_##T##U
+#define map_del(T, U) map_del_##T##U
+#define map_destroy(T, map) (set_destroy(T, &(map)->set), xfree((map)->values))
+#define map_clear(T, map) set_clear(T, &(map)->set)
+#define map_size(map) set_size(&(map)->set)
+
+#define pmap_get(T) map_get(T, ptr_t)
+#define pmap_put(T) map_put(T, ptr_t)
+#define pmap_ref(T) map_ref(T, ptr_t)
+#define pmap_put_ref(T) map_put_ref(T, ptr_t)
+/// @see pmap_del2
+#define pmap_del(T) map_del(T, ptr_t)
+
+#define set_foreach(set, key, block) \
+ { \
+ uint32_t __i; \
+ for (__i = 0; __i < (set)->h.n_keys; __i++) { \
+ (key) = (set)->keys[__i]; \
+ block; \
+ } \
+ }
+
+#define map_foreach_key(map, key, block) set_foreach(&(map)->set, key, block)
+
+#define map_foreach(map, key, value, code) \
+ { \
+ uint32_t __i; \
+ for (__i = 0; __i < (map)->set.h.n_keys; __i++) { \
+ (key) = (map)->set.keys[__i]; \
+ (value) = (map)->values[__i]; \
+ code; \
+ } \
+ }
+
+#define map_foreach_value(map, value, code) \
+ { \
+ uint32_t __i; \
+ for (__i = 0; __i < (map)->set.h.n_keys; __i++) { \
+ (value) = (map)->values[__i]; \
+ code; \
+ } \
+ }
+
+void pmap_del2(PMap(cstr_t) *map, const char *key);
diff --git a/src/nvim/map_glyph_cache.c b/src/nvim/map_glyph_cache.c
new file mode 100644
index 0000000000..5efa87b960
--- /dev/null
+++ b/src/nvim/map_glyph_cache.c
@@ -0,0 +1,109 @@
+// Specialized version of Set() where interned strings is stored in a compact,
+// NUL-separated char array.
+// `String key` lookup keys don't need to be NULL terminated, but they
+// must not contain embedded NUL:s. When reading a key from set->keys, they
+// are always NUL terminated, though. Thus, it is enough to store an index into
+// this array, and use strlen(), to retrieve an interned key.
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
+#include "nvim/memory.h"
+
+uint32_t mh_find_bucket_glyph(Set(glyph) *set, String key, bool put)
+{
+ MapHash *h = &set->h;
+ uint32_t step = 0;
+ uint32_t mask = h->n_buckets - 1;
+ uint32_t k = hash_String(key);
+ uint32_t i = k & mask;
+ uint32_t last = i;
+ uint32_t site = put ? last : MH_TOMBSTONE;
+ while (!mh_is_empty(h, i)) {
+ if (mh_is_del(h, i)) {
+ if (site == last) {
+ site = i;
+ }
+ } else if (equal_String(cstr_as_string(&set->keys[h->hash[i] - 1]), key)) {
+ return i;
+ }
+ i = (i + (++step)) & mask;
+ if (i == last) {
+ abort();
+ }
+ }
+ if (site == last) {
+ site = i;
+ }
+ return site;
+}
+
+/// @return index into set->keys if found, MH_TOMBSTONE otherwise
+uint32_t mh_get_glyph(Set(glyph) *set, String key)
+{
+ if (set->h.n_buckets == 0) {
+ return MH_TOMBSTONE;
+ }
+ uint32_t idx = mh_find_bucket_glyph(set, key, false);
+ return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE;
+}
+
+void mh_rehash_glyph(Set(glyph) *set)
+{
+ // assume the format of set->keys, i e NUL terminated strings
+ for (uint32_t k = 0; k < set->h.n_keys; k += (uint32_t)strlen(&set->keys[k]) + 1) {
+ uint32_t idx = mh_find_bucket_glyph(set, cstr_as_string(&set->keys[k]), true);
+ // there must be tombstones when we do a rehash
+ if (!mh_is_empty((&set->h), idx)) {
+ abort();
+ }
+ set->h.hash[idx] = k + 1;
+ }
+ set->h.n_occupied = set->h.size = set->h.n_keys;
+}
+
+uint32_t mh_put_glyph(Set(glyph) *set, String key, MHPutStatus *new)
+{
+ MapHash *h = &set->h;
+ // Might rehash ahead of time if "key" already existed. But it was
+ // going to happen soon anyway.
+ if (h->n_occupied >= h->upper_bound) {
+ mh_realloc(h, h->n_buckets + 1);
+ mh_rehash_glyph(set);
+ }
+
+ uint32_t idx = mh_find_bucket_glyph(set, key, true);
+
+ if (mh_is_either(h, idx)) {
+ h->size++;
+ h->n_occupied++;
+
+ uint32_t size = (uint32_t)key.size + 1; // NUL takes space
+ uint32_t pos = h->n_keys;
+ h->n_keys += size;
+ if (h->n_keys > h->keys_capacity) {
+ h->keys_capacity = MAX(h->keys_capacity * 2, 64);
+ set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(char));
+ *new = kMHNewKeyRealloc;
+ } else {
+ *new = kMHNewKeyDidFit;
+ }
+ memcpy(&set->keys[pos], key.data, key.size);
+ set->keys[pos + key.size] = NUL;
+ h->hash[idx] = pos + 1;
+ return pos;
+ } else {
+ *new = kMHExisting;
+ uint32_t pos = h->hash[idx] - 1;
+ assert(equal_String(cstr_as_string(&set->keys[pos]), key));
+ return pos;
+ }
+}
diff --git a/src/nvim/map_key_impl.c.h b/src/nvim/map_key_impl.c.h
new file mode 100644
index 0000000000..5568c049ab
--- /dev/null
+++ b/src/nvim/map_key_impl.c.h
@@ -0,0 +1,159 @@
+#include "nvim/map_defs.h"
+#include "nvim/memory.h"
+
+#ifndef KEY_NAME
+// Don't error out. it is nice to type-check the file in isolation, in clangd or otherwise
+# define KEY_NAME(x) x##int
+# define hash_int(x) ((uint32_t)x)
+# define equal_int(x, y) ((x) == (y))
+#endif
+
+#define SET_TYPE KEY_NAME(Set_)
+#define KEY_TYPE KEY_NAME()
+
+/// find bucket to get or put "key"
+///
+/// set->h.hash assumed already allocated!
+///
+/// @return bucket index, or MH_TOMBSTONE if not found and `put` was false
+/// mh_is_either(hash[rv]) : not found, but this is the place to put
+/// otherwise: hash[rv]-1 is index into key/value arrays
+uint32_t KEY_NAME(mh_find_bucket_)(SET_TYPE *set, KEY_TYPE key, bool put)
+{
+ MapHash *h = &set->h;
+ uint32_t step = 0;
+ uint32_t mask = h->n_buckets - 1;
+ uint32_t k = KEY_NAME(hash_)(key);
+ uint32_t i = k & mask;
+ uint32_t last = i;
+ uint32_t site = put ? last : MH_TOMBSTONE;
+ while (!mh_is_empty(h, i)) {
+ if (mh_is_del(h, i)) {
+ if (site == last) {
+ site = i;
+ }
+ } else if (KEY_NAME(equal_)(set->keys[h->hash[i] - 1], key)) {
+ return i;
+ }
+ i = (i + (++step)) & mask;
+ if (i == last) {
+ abort();
+ }
+ }
+ if (site == last) {
+ site = i;
+ }
+ return site;
+}
+
+/// @return index into set->keys if found, MH_TOMBSTONE otherwise
+uint32_t KEY_NAME(mh_get_)(SET_TYPE *set, KEY_TYPE key)
+{
+ if (set->h.n_buckets == 0) {
+ return MH_TOMBSTONE;
+ }
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, key, false);
+ return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE;
+}
+
+/// Rebuild hash from keys[] array
+///
+/// set->h.hash must be allocated and empty before&alling!
+void KEY_NAME(mh_rehash_)(SET_TYPE *set)
+{
+ for (uint32_t k = 0; k < set->h.n_keys; k++) {
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, set->keys[k], true);
+ // there must be tombstones when we do a rehash
+ if (!mh_is_empty((&set->h), idx)) {
+ abort();
+ }
+ set->h.hash[idx] = k + 1;
+ }
+ set->h.n_occupied = set->h.size = set->h.n_keys;
+}
+
+/// Put a key. Return the existing item if found
+///
+/// Allocates/resizes the hash table and/or keys[] table if needed.
+///
+/// @param[out] new mandatory. Reveals if an existing key was found. In addition,
+/// if new item, indicates if keys[] was resized.
+///
+/// @return keys index
+uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MHPutStatus *new)
+{
+ MapHash *h = &set->h;
+ // Might rehash ahead of time if "key" already existed. But it was
+ // going to happen soon anyway.
+ if (h->n_occupied >= h->upper_bound) {
+ // If we likely were to resize soon, do it now to avoid extra rehash
+ // TODO(bfredl): we never shrink. but maybe that's fine
+ if (h->size >= h->upper_bound * 0.9) {
+ mh_realloc(h, h->n_buckets + 1);
+ } else {
+ // Just a lot of tombstones from deleted items, start all over again
+ memset(h->hash, 0, h->n_buckets * sizeof(*h->hash));
+ h->size = h->n_occupied = 0;
+ }
+ KEY_NAME(mh_rehash_)(set);
+ }
+
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, key, true);
+
+ if (mh_is_either(h, idx)) {
+ h->size++;
+ if (mh_is_empty(h, idx)) {
+ h->n_occupied++;
+ }
+
+ uint32_t pos = h->n_keys++;
+ if (pos >= h->keys_capacity) {
+ h->keys_capacity = MAX(h->keys_capacity * 2, 8);
+ set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(KEY_TYPE));
+ *new = kMHNewKeyRealloc;
+ } else {
+ *new = kMHNewKeyDidFit;
+ }
+ set->keys[pos] = key;
+ h->hash[idx] = pos + 1;
+ return pos;
+ } else {
+ *new = kMHExisting;
+ uint32_t pos = h->hash[idx] - 1;
+ if (!KEY_NAME(equal_)(set->keys[pos], key)) {
+ abort();
+ }
+ return pos;
+ }
+}
+
+/// Deletes `*key` if found, do nothing otherwise
+///
+/// @param[in, out] key modified to the value contained in the set
+/// @return the index the item used to have in keys[]
+/// MH_TOMBSTONE if key was not found
+uint32_t KEY_NAME(mh_delete_)(SET_TYPE *set, KEY_TYPE *key)
+{
+ if (set->h.size == 0) {
+ return MH_TOMBSTONE;
+ }
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, *key, false);
+ if (idx != MH_TOMBSTONE) {
+ uint32_t k = set->h.hash[idx] - 1;
+ set->h.hash[idx] = MH_TOMBSTONE;
+
+ uint32_t last = --set->h.n_keys;
+ *key = set->keys[k];
+ set->h.size--;
+ if (last != k) {
+ uint32_t idx2 = KEY_NAME(mh_find_bucket_)(set, set->keys[last], false);
+ if (set->h.hash[idx2] != last + 1) {
+ abort();
+ }
+ set->h.hash[idx2] = k + 1;
+ set->keys[k] = set->keys[last];
+ }
+ return k;
+ }
+ return MH_TOMBSTONE;
+}
diff --git a/src/nvim/map_value_impl.c.h b/src/nvim/map_value_impl.c.h
new file mode 100644
index 0000000000..d93856c25d
--- /dev/null
+++ b/src/nvim/map_value_impl.c.h
@@ -0,0 +1,64 @@
+#include "nvim/assert_defs.h"
+#include "nvim/map_defs.h"
+
+#if !defined(KEY_NAME) || !defined(VAL_NAME)
+// Don't error out. it is nice to type-check the file in isolation, in clangd or otherwise
+# define KEY_NAME(x) x##int
+# define VAL_NAME(x) quasiquote(x, ptr_t)
+#endif
+
+#define MAP_NAME(x) VAL_NAME(KEY_NAME(x))
+#define MAP_TYPE MAP_NAME(Map_)
+#define KEY_TYPE KEY_NAME()
+#define VALUE_TYPE VAL_NAME()
+#define INITIALIZER VAL_NAME(value_init_)
+
+VALUE_TYPE *MAP_NAME(map_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc)
+{
+ uint32_t k = KEY_NAME(mh_get_)(&map->set, key);
+ if (k == MH_TOMBSTONE) {
+ return NULL;
+ }
+ if (key_alloc) {
+ *key_alloc = &map->set.keys[k];
+ }
+ return &map->values[k];
+}
+
+VALUE_TYPE *MAP_NAME(map_put_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc,
+ bool *new_item)
+{
+ MHPutStatus status;
+ uint32_t k = KEY_NAME(mh_put_)(&map->set, key, &status);
+ if (status != kMHExisting) {
+ if (status == kMHNewKeyRealloc) {
+ map->values = xrealloc(map->values, map->set.h.keys_capacity * sizeof(VALUE_TYPE));
+ }
+ map->values[k] = INITIALIZER;
+ }
+ if (new_item) {
+ *new_item = (status != kMHExisting);
+ }
+ if (key_alloc) {
+ *key_alloc = &map->set.keys[k];
+ }
+ return &map->values[k];
+}
+
+VALUE_TYPE MAP_NAME(map_del_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE *key_alloc)
+{
+ VALUE_TYPE rv = INITIALIZER;
+ uint32_t k = KEY_NAME(mh_delete_)(&map->set, &key);
+ if (k == MH_TOMBSTONE) {
+ return rv;
+ }
+
+ if (key_alloc) {
+ *key_alloc = key;
+ }
+ rv = map->values[k];
+ if (k != map->set.h.n_keys) {
+ map->values[k] = map->values[map->set.h.n_keys];
+ }
+ return rv;
+}
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index 831d1299a8..17593a9121 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -1,48 +1,54 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// mapping.c: Code for mappings and abbreviations.
#include <assert.h>
-#include <inttypes.h>
#include <lauxlib.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_session.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
+#include "nvim/regexp_defs.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/ui.h"
+#include "nvim/vim_defs.h"
/// List used for abbreviations.
static mapblock_T *first_abbr = NULL; // first entry in abbrlist
@@ -61,10 +67,65 @@ static mapblock_T *(maphash[MAX_MAPHASH]) = { 0 };
(MODE_NORMAL | MODE_VISUAL | MODE_SELECT | \
MODE_OP_PENDING | MODE_TERMINAL)) ? (c1) : ((c1) ^ 0x80))
+/// All possible |:map-arguments| usable in a |:map| command.
+///
+/// The <special> argument has no effect on mappings and is excluded from this
+/// struct declaration. |:noremap| is included, since it behaves like a map
+/// argument when used in a mapping.
+///
+/// @see mapblock_T
+struct map_arguments {
+ bool buffer;
+ bool expr;
+ bool noremap;
+ bool nowait;
+ bool script;
+ bool silent;
+ bool unique;
+ bool replace_keycodes;
+
+ /// The {lhs} of the mapping.
+ ///
+ /// vim limits this to MAXMAPLEN characters, allowing us to use a static
+ /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
+ /// that {lhs} was too long and truncated.
+ char lhs[MAXMAPLEN + 1];
+ size_t lhs_len;
+
+ /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0.
+ char alt_lhs[MAXMAPLEN + 1];
+ size_t alt_lhs_len;
+
+ char *rhs; /// The {rhs} of the mapping.
+ size_t rhs_len;
+ LuaRef rhs_lua; /// lua function as {rhs}
+ bool rhs_is_noop; /// True when the {rhs} should be <Nop>.
+
+ char *orig_rhs; /// The original text of the {rhs}.
+ size_t orig_rhs_len;
+ char *desc; /// map description
+};
+typedef struct map_arguments MapArguments;
+#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, false, \
+ { 0 }, 0, { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mapping.c.generated.h"
#endif
+static const char e_global_abbreviation_already_exists_for_str[]
+ = N_("E224: Global abbreviation already exists for %s");
+static const char e_global_mapping_already_exists_for_str[]
+ = N_("E225: Global mapping already exists for %s");
+static const char e_abbreviation_already_exists_for_str[]
+ = N_("E226: Abbreviation already exists for %s");
+static const char e_mapping_already_exists_for_str[]
+ = N_("E227: Mapping already exists for %s");
+static const char e_entries_missing_in_mapset_dict_argument[]
+ = N_("E460: Entries missing in mapset() dict argument");
+static const char e_illegal_map_mode_string_str[]
+ = N_("E1276: Illegal map mode string: '%s'");
+
/// Get the start of the hashed map list for "state" and first character "c".
mapblock_T *get_maphash_list(int state, int c)
{
@@ -95,9 +156,7 @@ mapblock_T *get_maphash(int index, buf_T *buf)
/// "mpp" is a pointer to the m_next field of the PREVIOUS entry!
static void mapblock_free(mapblock_T **mpp)
{
- mapblock_T *mp;
-
- mp = *mpp;
+ mapblock_T *mp = *mpp;
xfree(mp->m_keys);
if (!mp->m_simplified) {
NLUA_CLEAR_REF(mp->m_luaref);
@@ -159,26 +218,23 @@ static char *map_mode_to_chars(int mode)
/// @param local true for buffer-local map
static void showmap(mapblock_T *mp, bool local)
{
- size_t len = 1;
-
if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)
&& (mp->m_desc == NULL || message_filtered(mp->m_desc))) {
return;
}
- if (msg_didout || msg_silent != 0) {
+ // When ext_messages is active, msg_didout is never set.
+ if (msg_didout || msg_silent != 0 || ui_has(kUIMessages)) {
msg_putchar('\n');
if (got_int) { // 'q' typed at MORE prompt
return;
}
}
- {
- char *const mapchars = map_mode_to_chars(mp->m_mode);
- msg_puts(mapchars);
- len = strlen(mapchars);
- xfree(mapchars);
- }
+ char *const mapchars = map_mode_to_chars(mp->m_mode);
+ msg_puts(mapchars);
+ size_t len = strlen(mapchars);
+ xfree(mapchars);
while (++len <= 3) {
msg_putchar(' ');
@@ -268,16 +324,16 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs
bool did_simplify = false;
const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
char *bufarg = lhs_buf;
- char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags, &did_simplify,
- cpo_flags);
+ char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, 0,
+ flags, &did_simplify, cpo_flags);
if (replaced == NULL) {
return false;
}
mapargs->lhs_len = strlen(replaced);
xstrlcpy(mapargs->lhs, replaced, sizeof(mapargs->lhs));
if (did_simplify) {
- replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags | REPTERM_NO_SIMPLIFY,
- NULL, cpo_flags);
+ replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, 0,
+ flags | REPTERM_NO_SIMPLIFY, NULL, cpo_flags);
if (replaced == NULL) {
return false;
}
@@ -287,14 +343,15 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs
mapargs->alt_lhs_len = 0;
}
- set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, cpo_flags, mapargs);
+ set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, 0, cpo_flags, mapargs);
return true;
}
/// @see set_maparg_lhs_rhs
static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len,
- const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs)
+ const LuaRef rhs_lua, const scid_T sid, const int cpo_flags,
+ MapArguments *const mapargs)
{
mapargs->rhs_lua = rhs_lua;
@@ -308,8 +365,8 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len
mapargs->rhs_is_noop = true;
} else {
char *rhs_buf = NULL;
- char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
- cpo_flags);
+ char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, sid,
+ REPTERM_DO_LT, NULL, cpo_flags);
mapargs->rhs_len = strlen(replaced);
// NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty
// (e.g. a single ^V, see :h map-empty-rhs)
@@ -323,7 +380,7 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len
mapargs->orig_rhs_len = 0;
// stores <lua>ref_no<cr> in map_str
mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL,
- (char_u)KS_EXTRA, KE_LUA, rhs_lua);
+ KS_EXTRA, KE_LUA, rhs_lua);
mapargs->rhs = xstrdup(tmp_buf);
}
}
@@ -422,7 +479,7 @@ static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapa
// {lhs_end} is a pointer to the "terminating whitespace" after {lhs}.
// Use that to initialize {rhs_start}.
- const char *rhs_start = skipwhite((char *)lhs_end);
+ const char *rhs_start = skipwhite(lhs_end);
// Given {lhs} might be larger than MAXMAPLEN before replace_termcodes
// (e.g. "<Space>" is longer than ' '), so first copy into a buffer.
@@ -449,7 +506,7 @@ static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapa
/// @param args "rhs", "rhs_lua", "orig_rhs", "expr", "silent", "nowait", "replace_keycodes" and
/// and "desc" fields are used.
/// "rhs", "rhs_lua", "orig_rhs" fields are cleared if "simplified" is false.
-/// @param sid -1 to use current_sctx
+/// @param sid 0 to use current_sctx
static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, const char *keys,
MapArguments *args, int noremap, int mode, bool is_abbr, scid_T sid,
linenr_T lnum, bool simplified)
@@ -482,7 +539,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
mp->m_simplified = simplified;
mp->m_expr = args->expr;
mp->m_replace_keycodes = args->replace_keycodes;
- if (sid >= 0) {
+ if (sid != 0) {
mp->m_script_ctx.sc_sid = sid;
mp->m_script_ctx.sc_lnum = lnum;
} else {
@@ -518,33 +575,16 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
/// @param buf Target Buffer
static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf)
{
- mapblock_T *mp, **mpp;
- const char *p;
- int n;
int retval = 0;
- mapblock_T **abbr_table;
- mapblock_T **map_table;
- int noremap;
- map_table = maphash;
- abbr_table = &first_abbr;
+ // If <buffer> was given, we'll be searching through the buffer's
+ // mappings/abbreviations, not the globals.
+ mapblock_T **map_table = args->buffer ? buf->b_maphash : maphash;
+ mapblock_T **abbr_table = args->buffer ? &buf->b_first_abbr : &first_abbr;
// For ":noremap" don't remap, otherwise do remap.
- if (maptype == MAPTYPE_NOREMAP) {
- noremap = REMAP_NONE;
- } else {
- noremap = REMAP_YES;
- }
-
- if (args->buffer) {
- // If <buffer> was given, we'll be searching through the buffer's
- // mappings/abbreviations, not the globals.
- map_table = buf->b_maphash;
- abbr_table = &buf->b_first_abbr;
- }
- if (args->script) {
- noremap = REMAP_SCRIPT;
- }
+ int noremap = args->script ? REMAP_SCRIPT
+ : maptype == MAPTYPE_NOREMAP ? REMAP_NONE : REMAP_YES;
const bool has_lhs = (args->lhs[0] != NUL);
const bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop;
@@ -594,15 +634,15 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
const int first = vim_iswordp(lhs);
int last = first;
- p = (char *)lhs + utfc_ptr2len((char *)lhs);
- n = 1;
- while (p < (char *)lhs + len) {
+ const char *p = lhs + utfc_ptr2len(lhs);
+ int n = 1;
+ while (p < lhs + len) {
n++; // nr of (multi-byte) chars
last = vim_iswordp(p); // type of last char
if (same == -1 && last != first) {
same = n - 1; // count of same char type
}
- p += utfc_ptr2len((char *)p);
+ p += utfc_ptr2len(p);
}
if (last && n > 2 && same >= 0 && same < n - 1) {
retval = 1;
@@ -631,6 +671,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
&& maptype != MAPTYPE_UNMAP) {
// need to loop over all global hash lists
for (int hash = 0; hash < 256 && !got_int; hash++) {
+ mapblock_T *mp;
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
@@ -645,10 +686,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
&& mp->m_keylen == len
&& strncmp(mp->m_keys, lhs, (size_t)len) == 0) {
if (is_abbrev) {
- semsg(_("E224: global abbreviation already exists for %s"),
- mp->m_keys);
+ semsg(_(e_global_abbreviation_already_exists_for_str), mp->m_keys);
} else {
- semsg(_("E225: global mapping already exists for %s"), mp->m_keys);
+ semsg(_(e_global_mapping_already_exists_for_str), mp->m_keys);
}
retval = 5;
goto theend;
@@ -661,6 +701,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
if (map_table != buf->b_maphash && !has_rhs && maptype != MAPTYPE_UNMAP) {
// need to loop over all global hash lists
for (int hash = 0; hash < 256 && !got_int; hash++) {
+ mapblock_T *mp;
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
@@ -676,7 +717,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
showmap(mp, true);
did_local = true;
} else {
- n = mp->m_keylen;
+ int n = mp->m_keylen;
if (strncmp(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
showmap(mp, true);
did_local = true;
@@ -706,8 +747,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
hash_end = 256;
}
for (int hash = hash_start; hash < hash_end && !got_int; hash++) {
- mpp = is_abbrev ? abbr_table : &(map_table[hash]);
- for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
+ mapblock_T **mpp = is_abbrev ? abbr_table : &(map_table[hash]);
+ for (mapblock_T *mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
if ((mp->m_mode & mode) == 0) {
// skip entries with wrong mode
mpp = &(mp->m_next);
@@ -719,6 +760,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
did_it = true;
}
} else { // do we have a match?
+ int n;
+ const char *p;
if (round) { // second round: Try unmap "rhs" string
n = (int)strlen(mp->m_str);
p = mp->m_str;
@@ -733,8 +776,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
// we ignore trailing space when matching with
// the "lhs", since an abbreviation can't have
// trailing space.
- if (n != len && (!is_abbrev || round || n > len
- || *skipwhite((char *)lhs + n) != NUL)) {
+ if (n != len && (!is_abbrev || round || n > len || *skipwhite(lhs + n) != NUL)) {
mpp = &(mp->m_next);
continue;
}
@@ -762,9 +804,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
break;
} else if (args->unique) {
if (is_abbrev) {
- semsg(_("E226: abbreviation already exists for %s"), p);
+ semsg(_(e_abbreviation_already_exists_for_str), p);
} else {
- semsg(_("E227: mapping already exists for %s"), p);
+ semsg(_(e_mapping_already_exists_for_str), p);
}
retval = 5;
goto theend;
@@ -844,9 +886,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
// print entries
if (!did_it && !did_local) {
if (is_abbrev) {
- msg(_("No abbreviation found"));
+ msg(_("No abbreviation found"), 0);
} else {
- msg(_("No mapping found"));
+ msg(_("No mapping found"), 0);
}
}
goto theend; // listing finished
@@ -857,9 +899,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
}
// Get here when adding a new entry to the maphash[] list or abbrlist.
- map_add(buf, map_table, abbr_table, (char *)lhs, args, noremap, mode, is_abbrev,
- -1, // sid
- 0, // lnum
+ map_add(buf, map_table, abbr_table, lhs, args, noremap, mode, is_abbrev,
+ 0, // sid
+ 0, // lnum
keyround1_simplified);
}
@@ -942,12 +984,10 @@ free_and_return:
/// Get the mapping mode from the command name.
static int get_map_mode(char **cmdp, bool forceit)
{
- char *p;
- int modec;
int mode;
- p = *cmdp;
- modec = (uint8_t)(*p++);
+ char *p = *cmdp;
+ int modec = (uint8_t)(*p++);
if (modec == 'i') {
mode = MODE_INSERT; // :imap
} else if (modec == 'l') {
@@ -984,16 +1024,13 @@ static int get_map_mode(char **cmdp, bool forceit)
/// This function used to be called map_clear().
static void do_mapclear(char *cmdp, char *arg, int forceit, int abbr)
{
- int mode;
- int local;
-
- local = (strcmp(arg, "<buffer>") == 0);
+ bool local = strcmp(arg, "<buffer>") == 0;
if (!local && *arg != NUL) {
emsg(_(e_invarg));
return;
}
- mode = get_map_mode(&cmdp, forceit);
+ int mode = get_map_mode(&cmdp, forceit);
map_clear_mode(curbuf, mode, local, abbr);
}
@@ -1005,11 +1042,8 @@ static void do_mapclear(char *cmdp, char *arg, int forceit, int abbr)
/// @param abbr true for abbreviations
void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr)
{
- mapblock_T *mp, **mpp;
- int hash;
- int new_hash;
-
- for (hash = 0; hash < 256; hash++) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T **mpp;
if (abbr) {
if (hash > 0) { // there is only one abbrlist
break;
@@ -1027,7 +1061,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr)
}
}
while (*mpp != NULL) {
- mp = *mpp;
+ mapblock_T *mp = *mpp;
if (mp->m_mode & mode) {
mp->m_mode &= ~mode;
if (mp->m_mode == 0) { // entry can be deleted
@@ -1035,7 +1069,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr)
continue;
}
// May need to put this entry into another hash list.
- new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]);
+ int new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]);
if (!abbr && new_hash != hash) {
*mpp = mp->m_next;
if (local) {
@@ -1067,12 +1101,10 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
int mode = 0;
- int retval;
char *buf = NULL;
- const char *const rhs = replace_termcodes(str, strlen(str),
- &buf, REPTERM_DO_LT,
- NULL, CPO_TO_CPO_FLAGS);
+ const char *const rhs = replace_termcodes(str, strlen(str), &buf, 0,
+ REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
#define MAPMODE(mode, modechars, chr, modeflags) \
do { \
@@ -1090,7 +1122,7 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
MAPMODE(mode, modechars, 'c', MODE_CMDLINE);
#undef MAPMODE
- retval = map_to_exists_mode(rhs, mode, abbr);
+ int retval = map_to_exists_mode(rhs, mode, abbr);
xfree(buf);
return retval;
@@ -1108,13 +1140,12 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
/// @return true if there is at least one mapping with given parameters.
int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
{
- mapblock_T *mp;
- int hash;
bool exp_buffer = false;
// Do it twice: once for global maps and once for local maps.
- for (;;) {
- for (hash = 0; hash < 256; hash++) {
+ while (true) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
if (abbr) {
if (hash > 0) { // There is only one abbr list.
break;
@@ -1162,12 +1193,13 @@ static bool expand_buffer = false;
/// @param cpo_flags Value of various flags present in &cpo
///
/// @return NULL when there is a problem.
-static char_u *translate_mapping(char_u *str, int cpo_flags)
+static char *translate_mapping(char *str_in, int cpo_flags)
{
+ uint8_t *str = (uint8_t *)str_in;
garray_T ga;
ga_init(&ga, 1, 40);
- bool cpo_bslash = !(cpo_flags&FLAG_CPO_BSLASH);
+ bool cpo_bslash = cpo_flags & FLAG_CPO_BSLASH;
for (; *str; str++) {
int c = *str;
@@ -1188,13 +1220,13 @@ static char_u *translate_mapping(char_u *str, int cpo_flags)
str += 2;
}
if (IS_SPECIAL(c) || modifiers) { // special key
- ga_concat(&ga, (char *)get_special_key_name(c, modifiers));
+ ga_concat(&ga, get_special_key_name(c, modifiers));
continue; // for (str)
}
}
if (c == ' ' || c == '\t' || c == Ctrl_J || c == Ctrl_V
- || (c == '\\' && !cpo_bslash)) {
+ || c == '<' || (c == '\\' && !cpo_bslash)) {
ga_append(&ga, cpo_bslash ? Ctrl_V : '\\');
}
@@ -1203,7 +1235,7 @@ static char_u *translate_mapping(char_u *str, int cpo_flags)
}
}
ga_append(&ga, NUL);
- return (char_u *)(ga.ga_data);
+ return (char *)ga.ga_data;
}
/// Work out what to complete when doing command line completion of mapping
@@ -1212,8 +1244,8 @@ static char_u *translate_mapping(char_u *str, int cpo_flags)
/// @param forceit true if '!' given
/// @param isabbrev true if abbreviation
/// @param isunmap true if unmap/unabbrev command
-char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit, bool isabbrev,
- bool isunmap, cmdidx_T cmdidx)
+char *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit, bool isabbrev,
+ bool isunmap, cmdidx_T cmdidx)
{
if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) {
xp->xp_context = EXPAND_NOTHING;
@@ -1229,7 +1261,7 @@ char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit,
expand_isabbrev = isabbrev;
xp->xp_context = EXPAND_MAPPINGS;
expand_buffer = false;
- for (;;) {
+ while (true) {
if (strncmp(arg, "<buffer>", 8) == 0) {
expand_buffer = true;
arg = skipwhite(arg + 8);
@@ -1308,7 +1340,7 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
bool match;
int score = 0;
if (!fuzzy) {
- match = vim_regexec(regmatch, p, (colnr_T)0);
+ match = vim_regexec(regmatch, p, 0);
} else {
score = fuzzy_match_str(p, pat);
match = (score != 0);
@@ -1342,11 +1374,11 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
mp = maphash[hash];
}
for (; mp; mp = mp->m_next) {
- if (!(mp->m_mode & expand_mapmodes)) {
+ if (mp->m_simplified || !(mp->m_mode & expand_mapmodes)) {
continue;
}
- char *p = (char *)translate_mapping((char_u *)mp->m_keys, CPO_TO_CPO_FLAGS);
+ char *p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS);
if (p == NULL) {
continue;
}
@@ -1354,7 +1386,7 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
bool match;
int score = 0;
if (!fuzzy) {
- match = vim_regexec(regmatch, p, (colnr_T)0);
+ match = vim_regexec(regmatch, p, 0);
} else {
score = fuzzy_match_str(p, pat);
match = (score != 0);
@@ -1434,15 +1466,8 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
// Return true if there is an abbreviation, false if not.
bool check_abbr(int c, char *ptr, int col, int mincol)
{
- int len;
- int scol; // starting column of the abbr.
- int j;
- char *s;
- char_u tb[MB_MAXBYTES + 4];
- mapblock_T *mp;
- mapblock_T *mp2;
+ uint8_t tb[MB_MAXBYTES + 4];
int clen = 0; // length in characters
- bool is_id = true;
if (typebuf.tb_no_abbr_cnt) { // abbrev. are not recursive
return false;
@@ -1461,7 +1486,10 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
return false;
}
+ int scol; // starting column of the abbr.
+
{
+ bool is_id = true;
bool vim_abbr;
char *p = mb_prevptr(ptr, ptr + col);
if (!vim_iswordp(p)) {
@@ -1489,24 +1517,24 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
}
if (scol < col) { // there is a word in front of the cursor
ptr += scol;
- len = col - scol;
- mp = curbuf->b_first_abbr;
- mp2 = first_abbr;
+ int len = col - scol;
+ mapblock_T *mp = curbuf->b_first_abbr;
+ mapblock_T *mp2 = first_abbr;
if (mp == NULL) {
mp = mp2;
mp2 = NULL;
}
for (; mp;
- mp->m_next == NULL ? (mp = mp2, mp2 = NULL) :
- (mp = mp->m_next)) {
+ mp->m_next == NULL ? (mp = mp2, mp2 = NULL)
+ : (mp = mp->m_next)) {
int qlen = mp->m_keylen;
char *q = mp->m_keys;
int match;
- if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) {
+ if (strchr(mp->m_keys, K_SPECIAL) != NULL) {
// Might have K_SPECIAL escaped mp->m_keys.
q = xstrdup(mp->m_keys);
- vim_unescape_ks((char_u *)q);
+ vim_unescape_ks(q);
qlen = (int)strlen(q);
}
// find entries with right mode and keys
@@ -1532,13 +1560,13 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
//
// Character CTRL-] is treated specially - it completes the
// abbreviation, but is not inserted into the input stream.
- j = 0;
+ int j = 0;
if (c != Ctrl_RSB) {
// special key code, split up
if (IS_SPECIAL(c) || c == K_SPECIAL) {
tb[j++] = K_SPECIAL;
- tb[j++] = (char_u)K_SECOND(c);
- tb[j++] = (char_u)K_THIRD(c);
+ tb[j++] = (uint8_t)K_SECOND(c);
+ tb[j++] = (uint8_t)K_THIRD(c);
} else {
if (c < ABBR_OFF && (c < ' ' || c > '~')) {
tb[j++] = Ctrl_V; // special char needs CTRL-V
@@ -1568,6 +1596,7 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
const bool silent = mp->m_silent;
const bool expr = mp->m_expr;
+ char *s;
if (expr) {
s = eval_map_expr(mp, c);
} else {
@@ -1609,15 +1638,14 @@ char *eval_map_expr(mapblock_T *mp, int c)
// typeahead.
if (mp->m_luaref == LUA_NOREF) {
expr = xstrdup(mp->m_str);
- vim_unescape_ks((char_u *)expr);
+ vim_unescape_ks(expr);
}
const bool replace_keycodes = mp->m_replace_keycodes;
// Forbid changing text or using ":normal" to avoid most of the bad side
// effects. Also restore the cursor position.
- textlock++;
- ex_normal_lock++;
+ expr_map_lock++;
set_vim_var_char(c); // set v:char to the typed character
const pos_T save_cursor = curwin->w_cursor;
const int save_msg_col = msg_col;
@@ -1627,7 +1655,7 @@ char *eval_map_expr(mapblock_T *mp, int c)
Array args = ARRAY_DICT_INIT;
Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err);
if (ret.type == kObjectTypeString) {
- p = xstrndup(ret.data.string.data, ret.data.string.size);
+ p = string_to_cstr(ret.data.string);
}
api_free_object(ret);
if (err.type != kErrorTypeNone) {
@@ -1635,11 +1663,10 @@ char *eval_map_expr(mapblock_T *mp, int c)
api_clear_error(&err);
}
} else {
- p = eval_to_string(expr, NULL, false);
+ p = eval_to_string(expr, false);
xfree(expr);
}
- textlock--;
- ex_normal_lock--;
+ expr_map_lock--;
curwin->w_cursor = save_cursor;
msg_col = save_msg_col;
msg_row = save_msg_row;
@@ -1651,7 +1678,7 @@ char *eval_map_expr(mapblock_T *mp, int c)
char *res = NULL;
if (replace_keycodes) {
- replace_termcodes(p, strlen(p), &res, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(p, strlen(p), &res, 0, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
} else {
// Escape K_SPECIAL in the result to be able to use the string as typeahead.
res = vim_strsave_escape_ks(p);
@@ -1667,18 +1694,13 @@ char *eval_map_expr(mapblock_T *mp, int c)
/// @param buf buffer for local mappings or NULL
int makemap(FILE *fd, buf_T *buf)
{
- mapblock_T *mp;
- char c1, c2, c3;
- char *p;
- char *cmd;
- int abbr;
- int hash;
bool did_cpo = false;
// Do the loop twice: Once for mappings, once for abbreviations.
// Then loop over all map hash lists.
- for (abbr = 0; abbr < 2; abbr++) {
- for (hash = 0; hash < 256; hash++) {
+ for (int abbr = 0; abbr < 2; abbr++) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
if (abbr) {
if (hash > 0) { // there is only one abbr list
break;
@@ -1707,6 +1729,7 @@ int makemap(FILE *fd, buf_T *buf)
if (mp->m_luaref != LUA_NOREF) {
continue;
}
+ char *p;
for (p = mp->m_str; *p != NUL; p++) {
if ((uint8_t)p[0] == K_SPECIAL && (uint8_t)p[1] == KS_EXTRA
&& p[2] == KE_SNR) {
@@ -1720,14 +1743,10 @@ int makemap(FILE *fd, buf_T *buf)
// It's possible to create a mapping and then ":unmap" certain
// modes. We recreate this here by mapping the individual
// modes, which requires up to three of them.
- c1 = NUL;
- c2 = NUL;
- c3 = NUL;
- if (abbr) {
- cmd = "abbr";
- } else {
- cmd = "map";
- }
+ char c1 = NUL;
+ char c2 = NUL;
+ char c3 = NUL;
+ char *cmd = abbr ? "abbr" : "map";
switch (mp->m_mode) {
case MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING:
break;
@@ -1814,8 +1833,7 @@ int makemap(FILE *fd, buf_T *buf)
did_cpo = true;
} else {
const char specials[] = { (char)(uint8_t)K_SPECIAL, NL, NUL };
- if (strpbrk((const char *)mp->m_str, specials) != NULL
- || strpbrk((const char *)mp->m_keys, specials) != NULL) {
+ if (strpbrk(mp->m_str, specials) != NULL || strpbrk(mp->m_keys, specials) != NULL) {
did_cpo = true;
}
}
@@ -1881,8 +1899,7 @@ int makemap(FILE *fd, buf_T *buf)
// return FAIL for failure, OK otherwise
int put_escstr(FILE *fd, char *strstart, int what)
{
- char_u *str = (char_u *)strstart;
- int c;
+ uint8_t *str = (uint8_t *)strstart;
// :map xx <Nop>
if (*str == NUL && what == 1) {
@@ -1906,7 +1923,7 @@ int put_escstr(FILE *fd, char *strstart, int what)
continue;
}
- c = *str;
+ int c = *str;
// Special key codes have to be translated to be able to make sense
// when they are read back.
if (c == K_SPECIAL && what != 2) {
@@ -1921,7 +1938,7 @@ int put_escstr(FILE *fd, char *strstart, int what)
str += 2;
}
if (IS_SPECIAL(c) || modifiers) { // special key
- if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) {
+ if (fputs(get_special_key_name(c, modifiers), fd) < 0) {
return FAIL;
}
continue;
@@ -1958,7 +1975,7 @@ int put_escstr(FILE *fd, char *strstart, int what)
}
} else if (c < ' ' || c > '~' || c == '|'
|| (what == 0 && c == ' ')
- || (what == 1 && str == (char_u *)strstart && c == ' ')
+ || (what == 1 && str == (uint8_t *)strstart && c == ' ')
|| (what != 2 && c == '<')) {
if (putc(Ctrl_V, fd) < 0) {
return FAIL;
@@ -1983,14 +2000,13 @@ int put_escstr(FILE *fd, char *strstart, int what)
char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr,
int *local_ptr, int *rhs_lua)
{
- int len, minlen;
- mapblock_T *mp;
*rhs_lua = LUA_NOREF;
- len = (int)strlen(keys);
+ int len = (int)strlen(keys);
for (int local = 1; local >= 0; local--) {
// loop over all hash lists
for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
if (abbr) {
if (hash > 0) { // there is only one list.
break;
@@ -2015,7 +2031,7 @@ char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock
s += 3;
keylen -= 3;
}
- minlen = keylen < len ? keylen : len;
+ int minlen = keylen < len ? keylen : len;
if (strncmp(s, keys, (size_t)minlen) == 0) {
if (mp_ptr != NULL) {
*mp_ptr = mp;
@@ -2050,28 +2066,25 @@ void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
- if (map_to_exists(name, mode, abbr)) {
- rettv->vval.v_number = true;
- } else {
- rettv->vval.v_number = false;
- }
+ rettv->vval.v_number = map_to_exists(name, mode, abbr);
}
/// Fill a Dictionary with all applicable maparg() like dictionaries
///
/// @param mp The maphash that contains the mapping information
/// @param buffer_value The "buffer" value
+/// @param abbr True if abbreviation
/// @param compatible True for compatible with old maparg() dict
///
/// @return A Dictionary.
static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhsrawalt,
- const long buffer_value, const bool compatible)
+ const int buffer_value, const bool abbr, const bool compatible)
FUNC_ATTR_NONNULL_ARG(1)
{
Dictionary dict = ARRAY_DICT_INIT;
char *const lhs = str2special_save(mp->m_keys, compatible, !compatible);
char *const mapmode = map_mode_to_chars(mp->m_mode);
- varnumber_T noremap_value;
+ int noremap_value;
if (compatible) {
// Keep old compatible behavior
@@ -2091,26 +2104,29 @@ static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhs
: cstr_as_string(str2special_save(mp->m_str, false, true))));
}
if (mp->m_desc != NULL) {
- PUT(dict, "desc", STRING_OBJ(cstr_to_string(mp->m_desc)));
+ PUT(dict, "desc", CSTR_TO_OBJ(mp->m_desc));
}
- PUT(dict, "lhs", STRING_OBJ(cstr_as_string(lhs)));
- PUT(dict, "lhsraw", STRING_OBJ(cstr_to_string((const char *)mp->m_keys)));
+ PUT(dict, "lhs", CSTR_AS_OBJ(lhs));
+ PUT(dict, "lhsraw", CSTR_TO_OBJ(mp->m_keys));
if (lhsrawalt != NULL) {
// Also add the value for the simplified entry.
- PUT(dict, "lhsrawalt", STRING_OBJ(cstr_to_string(lhsrawalt)));
+ PUT(dict, "lhsrawalt", CSTR_TO_OBJ(lhsrawalt));
}
PUT(dict, "noremap", INTEGER_OBJ(noremap_value));
PUT(dict, "script", INTEGER_OBJ(mp->m_noremap == REMAP_SCRIPT ? 1 : 0));
PUT(dict, "expr", INTEGER_OBJ(mp->m_expr ? 1 : 0));
PUT(dict, "silent", INTEGER_OBJ(mp->m_silent ? 1 : 0));
- PUT(dict, "sid", INTEGER_OBJ((varnumber_T)mp->m_script_ctx.sc_sid));
- PUT(dict, "lnum", INTEGER_OBJ((varnumber_T)mp->m_script_ctx.sc_lnum));
- PUT(dict, "buffer", INTEGER_OBJ((varnumber_T)buffer_value));
+ PUT(dict, "sid", INTEGER_OBJ(mp->m_script_ctx.sc_sid));
+ PUT(dict, "scriptversion", INTEGER_OBJ(1));
+ PUT(dict, "lnum", INTEGER_OBJ(mp->m_script_ctx.sc_lnum));
+ PUT(dict, "buffer", INTEGER_OBJ(buffer_value));
PUT(dict, "nowait", INTEGER_OBJ(mp->m_nowait ? 1 : 0));
if (mp->m_replace_keycodes) {
PUT(dict, "replace_keycodes", INTEGER_OBJ(1));
}
- PUT(dict, "mode", STRING_OBJ(cstr_as_string(mapmode)));
+ PUT(dict, "mode", CSTR_AS_OBJ(mapmode));
+ PUT(dict, "abbr", INTEGER_OBJ(abbr ? 1 : 0));
+ PUT(dict, "mode_bits", INTEGER_OBJ(mp->m_mode));
return dict;
}
@@ -2152,8 +2168,8 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
const int mode = get_map_mode((char **)&which, 0);
- char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, flags, &did_simplify,
- CPO_TO_CPO_FLAGS);
+ char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, 0,
+ flags, &did_simplify, CPO_TO_CPO_FLAGS);
mapblock_T *mp = NULL;
int buffer_local;
LuaRef rhs_lua;
@@ -2162,10 +2178,8 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
if (did_simplify) {
// When the lhs is being simplified the not-simplified keys are
// preferred for printing, like in do_map().
- (void)replace_termcodes(keys,
- strlen(keys),
- &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL,
- CPO_TO_CPO_FLAGS);
+ (void)replace_termcodes(keys, strlen(keys), &alt_keys_buf, 0,
+ flags | REPTERM_NO_SIMPLIFY, NULL, CPO_TO_CPO_FLAGS);
rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
}
@@ -2185,7 +2199,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
Dictionary dict = mapblock_fill_dict(mp,
did_simplify ? keys_simplified : NULL,
- buffer_local, true);
+ buffer_local, abbr, true);
(void)object_to_vim(DICTIONARY_OBJ(dict), rettv, NULL);
api_free_dictionary(dict);
} else {
@@ -2198,22 +2212,99 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
xfree(alt_keys_buf);
}
+/// Get the mapping mode from the mode string.
+/// It may contain multiple characters, eg "nox", or "!", or ' '
+/// Return 0 if there is an error.
+static int get_map_mode_string(const char *const mode_string, const bool abbr)
+{
+ const char *p = mode_string;
+ const int MASK_V = MODE_VISUAL | MODE_SELECT;
+ const int MASK_MAP = MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING;
+ const int MASK_BANG = MODE_INSERT | MODE_CMDLINE;
+
+ if (*p == NUL) {
+ p = " "; // compatibility
+ }
+ int mode = 0;
+ int modec;
+ while ((modec = (uint8_t)(*p++))) {
+ int tmode;
+ switch (modec) {
+ case 'i':
+ tmode = MODE_INSERT; break;
+ case 'l':
+ tmode = MODE_LANGMAP; break;
+ case 'c':
+ tmode = MODE_CMDLINE; break;
+ case 'n':
+ tmode = MODE_NORMAL; break;
+ case 'x':
+ tmode = MODE_VISUAL; break;
+ case 's':
+ tmode = MODE_SELECT; break;
+ case 'o':
+ tmode = MODE_OP_PENDING; break;
+ case 't':
+ tmode = MODE_TERMINAL; break;
+ case 'v':
+ tmode = MASK_V; break;
+ case '!':
+ tmode = MASK_BANG; break;
+ case ' ':
+ tmode = MASK_MAP; break;
+ default:
+ return 0; // error, unknown mode character
+ }
+ mode |= tmode;
+ }
+ if ((abbr && (mode & ~MASK_BANG) != 0)
+ || (!abbr && (mode & (mode - 1)) != 0 // more than one bit set
+ && (
+ // false if multiple bits set in mode and mode is fully
+ // contained in one mask
+ !(((mode & MASK_BANG) != 0 && (mode & ~MASK_BANG) == 0)
+ || ((mode & MASK_MAP) != 0 && (mode & ~MASK_MAP) == 0))))) {
+ return 0;
+ }
+
+ return mode;
+}
+
/// "mapset()" function
void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ const char *which;
char buf[NUMBUFLEN];
- const char *which = tv_get_string_buf_chk(&argvars[0], buf);
- if (which == NULL) {
- return;
+ int is_abbr;
+ dict_T *d;
+
+ // If first arg is a dict, then that's the only arg permitted.
+ const bool dict_only = argvars[0].v_type == VAR_DICT;
+
+ if (dict_only) {
+ d = argvars[0].vval.v_dict;
+ which = tv_dict_get_string(d, "mode", false);
+ is_abbr = (int)tv_dict_get_bool(d, "abbr", -1);
+ if (which == NULL || is_abbr < 0) {
+ emsg(_(e_entries_missing_in_mapset_dict_argument));
+ return;
+ }
+ } else {
+ which = tv_get_string_buf_chk(&argvars[0], buf);
+ if (which == NULL) {
+ return;
+ }
+ is_abbr = (int)tv_get_bool(&argvars[1]);
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
+ return;
+ }
+ d = argvars[2].vval.v_dict;
}
- const int mode = get_map_mode((char **)&which, 0);
- const bool is_abbr = tv_get_number(&argvars[1]) != 0;
-
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ const int mode = get_map_mode_string(which, is_abbr);
+ if (mode == 0) {
+ semsg(_(e_illegal_map_mode_string_str), which);
return;
}
- dict_T *d = argvars[2].vval.v_dict;
// Get the values in the same order as above in get_maparg().
char *lhs = tv_dict_get_string(d, "lhs", false);
@@ -2232,7 +2323,7 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
api_free_object(callback_obj);
}
if (lhs == NULL || lhsraw == NULL || orig_rhs == NULL) {
- emsg(_("E460: entries missing in mapset() dict argument"));
+ emsg(_(e_entries_missing_in_mapset_dict_argument));
api_free_luaref(rhs_lua);
return;
}
@@ -2248,12 +2339,13 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
.replace_keycodes = tv_dict_get_number(d, "replace_keycodes") != 0,
.desc = tv_dict_get_string(d, "desc", false),
};
- set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, CPO_TO_CPO_FLAGS, &args);
scid_T sid = (scid_T)tv_dict_get_number(d, "sid");
linenr_T lnum = (linenr_T)tv_dict_get_number(d, "lnum");
bool buffer = tv_dict_get_number(d, "buffer") != 0;
// mode from the dict is not used
+ set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, sid, CPO_TO_CPO_FLAGS, &args);
+
mapblock_T **map_table = buffer ? curbuf->b_maphash : maphash;
mapblock_T **abbr_table = buffer ? &curbuf->b_first_abbr : &first_abbr;
@@ -2273,6 +2365,59 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
sid, lnum, false);
}
+/// "maplist()" function
+void f_maplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
+ const bool abbr = argvars[0].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[0]);
+
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ // Do it twice: once for global maps and once for local maps.
+ for (int buffer_local = 0; buffer_local <= 1; buffer_local++) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
+ if (abbr) {
+ if (hash > 0) { // there is only one abbr list
+ break;
+ }
+ if (buffer_local) {
+ mp = curbuf->b_first_abbr;
+ } else {
+ mp = first_abbr;
+ }
+ } else if (buffer_local) {
+ mp = curbuf->b_maphash[hash];
+ } else {
+ mp = maphash[hash];
+ }
+ for (; mp; mp = mp->m_next) {
+ if (mp->m_simplified) {
+ continue;
+ }
+
+ char *keys_buf = NULL;
+ bool did_simplify = false;
+
+ char *lhs = str2special_save(mp->m_keys, true, false);
+ (void)replace_termcodes(lhs, strlen(lhs), &keys_buf, 0, flags, &did_simplify,
+ CPO_TO_CPO_FLAGS);
+ xfree(lhs);
+
+ Dictionary dict = mapblock_fill_dict(mp,
+ did_simplify ? keys_buf : NULL,
+ buffer_local, abbr, true);
+ typval_T d = TV_INITIAL_VALUE;
+ (void)object_to_vim(DICTIONARY_OBJ(dict), &d, NULL);
+ assert(d.v_type == VAR_DICT);
+ tv_list_append_dict(rettv->vval.v_list, d.vval.v_dict);
+ api_free_dictionary(dict);
+ xfree(keys_buf);
+ }
+ }
+ }
+}
+
/// "maparg()" function
void f_maparg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2328,13 +2473,13 @@ static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE;
static void langmap_set_entry(int from, int to)
{
langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data);
- unsigned int a = 0;
+ unsigned a = 0;
assert(langmap_mapga.ga_len >= 0);
- unsigned int b = (unsigned int)langmap_mapga.ga_len;
+ unsigned b = (unsigned)langmap_mapga.ga_len;
// Do a binary search for an existing entry.
while (a != b) {
- unsigned int i = (a + b) / 2;
+ unsigned i = (a + b) / 2;
int d = entries[i].from - from;
if (d == 0) {
@@ -2353,7 +2498,7 @@ static void langmap_set_entry(int from, int to)
// insert new entry at position "a"
entries = (langmap_entry_T *)(langmap_mapga.ga_data) + a;
memmove(entries + 1, entries,
- ((unsigned int)langmap_mapga.ga_len - a) * sizeof(langmap_entry_T));
+ ((unsigned)langmap_mapga.ga_len - a) * sizeof(langmap_entry_T));
langmap_mapga.ga_len++;
entries[0].from = from;
entries[0].to = to;
@@ -2385,23 +2530,20 @@ int langmap_adjust_mb(int c)
void langmap_init(void)
{
for (int i = 0; i < 256; i++) {
- langmap_mapchar[i] = (char_u)i; // we init with a one-to-one map
+ langmap_mapchar[i] = (uint8_t)i; // we init with a one-to-one map
}
ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8);
}
/// Called when langmap option is set; the language map can be
/// changed at any time!
-void langmap_set(void)
+const char *did_set_langmap(optset_T *args)
{
- char *p;
- char *p2;
- int from, to;
-
- ga_clear(&langmap_mapga); // clear the previous map first
- langmap_init(); // back to one-to-one map
+ ga_clear(&langmap_mapga); // clear the previous map first
+ langmap_init(); // back to one-to-one map
- for (p = p_langmap; p[0] != NUL;) {
+ for (char *p = p_langmap; p[0] != NUL;) {
+ char *p2;
for (p2 = p; p2[0] != NUL && p2[0] != ',' && p2[0] != ';';
MB_PTR_ADV(p2)) {
if (p2[0] == '\\' && p2[1] != NUL) {
@@ -2421,8 +2563,8 @@ void langmap_set(void)
if (p[0] == '\\' && p[1] != NUL) {
p++;
}
- from = utf_ptr2char(p);
- to = NUL;
+ int from = utf_ptr2char(p);
+ int to = NUL;
if (p2 == NULL) {
MB_PTR_ADV(p);
if (p[0] != ',') {
@@ -2440,16 +2582,17 @@ void langmap_set(void)
}
}
if (to == NUL) {
- semsg(_("E357: 'langmap': Matching character missing for %s"),
- transchar(from));
- return;
+ snprintf(args->os_errbuf, args->os_errbuflen,
+ _("E357: 'langmap': Matching character missing for %s"),
+ transchar(from));
+ return args->os_errbuf;
}
if (from >= 256) {
langmap_set_entry(from, to);
} else {
assert(to <= UCHAR_MAX);
- langmap_mapchar[from & 255] = (char_u)to;
+ langmap_mapchar[from & 255] = (uint8_t)to;
}
// Advance to next pair
@@ -2460,8 +2603,10 @@ void langmap_set(void)
p = p2;
if (p[0] != NUL) {
if (p[0] != ',') {
- semsg(_("E358: 'langmap': Extra characters after semicolon: %s"), p);
- return;
+ snprintf(args->os_errbuf, args->os_errbuflen,
+ _("E358: 'langmap': Extra characters after semicolon: %s"),
+ p);
+ return args->os_errbuf;
}
p++;
}
@@ -2470,16 +2615,17 @@ void langmap_set(void)
}
}
}
+
+ return NULL;
}
static void do_exmap(exarg_T *eap, int isabbrev)
{
- int mode;
char *cmdp = eap->cmd;
- mode = get_map_mode(&cmdp, eap->forceit || isabbrev);
+ int mode = get_map_mode(&cmdp, eap->forceit || isabbrev);
switch (do_map((*cmdp == 'n') ? MAPTYPE_NOREMAP
- : (*cmdp == 'u') ? MAPTYPE_UNMAP : MAPTYPE_MAP,
+ : (*cmdp == 'u') ? MAPTYPE_UNMAP : MAPTYPE_MAP,
eap->arg, mode, isabbrev)) {
case 1:
emsg(_(e_invarg));
@@ -2502,7 +2648,7 @@ void ex_map(exarg_T *eap)
// If we are in a secure mode we print the mappings for security reasons.
if (secure) {
secure = 2;
- msg_outtrans(eap->cmd);
+ msg_outtrans(eap->cmd, 0);
msg_putchar('\n');
}
do_exmap(eap, false);
@@ -2549,26 +2695,22 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
const sctx_T save_current_sctx = api_set_sctx(channel_id);
- if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) {
- lua_funcref = opts->callback.data.luaref;
- opts->callback.data.luaref = LUA_NOREF;
- }
MapArguments parsed_args = MAP_ARGUMENTS_INIT;
if (opts) {
-#define KEY_TO_BOOL(name) \
- parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \
- if (ERROR_SET(err)) { \
- goto fail_and_free; \
- }
-
- KEY_TO_BOOL(nowait);
- KEY_TO_BOOL(noremap);
- KEY_TO_BOOL(silent);
- KEY_TO_BOOL(script);
- KEY_TO_BOOL(expr);
- KEY_TO_BOOL(unique);
- KEY_TO_BOOL(replace_keycodes);
-#undef KEY_TO_BOOL
+ parsed_args.nowait = opts->nowait;
+ parsed_args.noremap = opts->noremap;
+ parsed_args.silent = opts->silent;
+ parsed_args.script = opts->script;
+ parsed_args.expr = opts->expr;
+ parsed_args.unique = opts->unique;
+ parsed_args.replace_keycodes = opts->replace_keycodes;
+ if (HAS_KEY(opts, keymap, callback)) {
+ lua_funcref = opts->callback;
+ opts->callback = LUA_NOREF;
+ }
+ if (HAS_KEY(opts, keymap, desc)) {
+ parsed_args.desc = string_to_cstr(opts->desc);
+ }
}
parsed_args.buffer = !global;
@@ -2584,32 +2726,26 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
goto fail_and_free;
}
- if (opts != NULL && opts->desc.type == kObjectTypeString) {
- parsed_args.desc = string_to_cstr(opts->desc.data.string);
- } else {
- parsed_args.desc = NULL;
- }
if (parsed_args.lhs_len > MAXMAPLEN || parsed_args.alt_lhs_len > MAXMAPLEN) {
api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
goto fail_and_free;
}
- if (mode.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data);
- goto fail_and_free;
+ char *p = mode.size > 0 ? mode.data : "m";
+ bool forceit = *p == '!';
+ // integer value of the mapping mode, to be passed to do_map()
+ int mode_val = get_map_mode(&p, forceit);
+ if (forceit) {
+ assert(p == mode.data);
+ p++;
}
- int mode_val; // integer value of the mapping mode, to be passed to do_map()
- char *p = (mode.size) ? mode.data : "m";
- if (strncmp(p, "!", 2) == 0) {
- mode_val = get_map_mode(&p, true); // mapmode-ic
- } else {
- mode_val = get_map_mode(&p, false);
- if (mode_val == (MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING) && mode.size > 0) {
- // get_map_mode() treats unrecognized mode shortnames as ":map".
- // This is an error unless the given shortname was empty string "".
- api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", p);
- goto fail_and_free;
- }
+ bool is_abbrev = (mode_val & (MODE_INSERT | MODE_CMDLINE)) != 0 && *p == 'a';
+ if (is_abbrev) {
+ p++;
+ }
+ if (mode.size > 0 && (size_t)(p - mode.data) != mode.size) {
+ api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", mode.data);
+ goto fail_and_free;
}
if (parsed_args.lhs_len == 0) {
@@ -2645,14 +2781,14 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
maptype_val = MAPTYPE_NOREMAP;
}
- switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
+ switch (buf_do_map(maptype_val, &parsed_args, mode_val, is_abbrev, target_buf)) {
case 0:
break;
case 1:
- api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
+ api_set_error(err, kErrorTypeException, e_invarg, 0);
goto fail_and_free;
case 2:
- api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
+ api_set_error(err, kErrorTypeException, e_nomap, 0);
goto fail_and_free;
case 5:
api_set_error(err, kErrorTypeException,
@@ -2687,7 +2823,7 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
int int_mode = get_map_mode(&p, 0);
// Determine the desired buffer value
- long buffer_value = (buf == NULL) ? 0 : buf->handle;
+ int buffer_value = (buf == NULL) ? 0 : buf->handle;
for (int i = 0; i < MAX_MAPHASH; i++) {
for (const mapblock_T *current_maphash = get_maphash(i, buf);
@@ -2699,7 +2835,8 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
// Check for correct mode
if (int_mode & current_maphash->m_mode) {
ADD(mappings,
- DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL, buffer_value, false)));
+ DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL,
+ buffer_value, false, false)));
}
}
}
diff --git a/src/nvim/mapping.h b/src/nvim/mapping.h
index 58e28810bc..ffe7ab4290 100644
--- a/src/nvim/mapping.h
+++ b/src/nvim/mapping.h
@@ -1,63 +1,25 @@
-#ifndef NVIM_MAPPING_H
-#define NVIM_MAPPING_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-#include "lauxlib.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-
-/// All possible |:map-arguments| usable in a |:map| command.
-///
-/// The <special> argument has no effect on mappings and is excluded from this
-/// struct declaration. |:noremap| is included, since it behaves like a map
-/// argument when used in a mapping.
-///
-/// @see mapblock_T
-struct map_arguments {
- bool buffer;
- bool expr;
- bool noremap;
- bool nowait;
- bool script;
- bool silent;
- bool unique;
- bool replace_keycodes;
-
- /// The {lhs} of the mapping.
- ///
- /// vim limits this to MAXMAPLEN characters, allowing us to use a static
- /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
- /// that {lhs} was too long and truncated.
- char lhs[MAXMAPLEN + 1];
- size_t lhs_len;
-
- /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0.
- char alt_lhs[MAXMAPLEN + 1];
- size_t alt_lhs_len;
-
- char *rhs; /// The {rhs} of the mapping.
- size_t rhs_len;
- LuaRef rhs_lua; /// lua function as {rhs}
- bool rhs_is_noop; /// True when the {rhs} should be <Nop>.
-
- char *orig_rhs; /// The original text of the {rhs}.
- size_t orig_rhs_len;
- char *desc; /// map description
+#pragma once
+
+#include <stdint.h> // IWYU pragma: keep
+#include <stdio.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/mapping_defs.h" // IWYU pragma: export
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/regexp_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+
+/// Used for the first argument of do_map()
+enum {
+ MAPTYPE_MAP = 0,
+ MAPTYPE_UNMAP = 1,
+ MAPTYPE_NOREMAP = 2,
};
-typedef struct map_arguments MapArguments;
-#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, false, \
- { 0 }, 0, { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
-
-// Used for the first argument of do_map()
-#define MAPTYPE_MAP 0
-#define MAPTYPE_UNMAP 1
-#define MAPTYPE_NOREMAP 2
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mapping.h.generated.h"
#endif
-#endif // NVIM_MAPPING_H
diff --git a/src/nvim/mapping_defs.h b/src/nvim/mapping_defs.h
new file mode 100644
index 0000000000..6691c5ac3b
--- /dev/null
+++ b/src/nvim/mapping_defs.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "nvim/eval/typval_defs.h"
+#include "nvim/types_defs.h"
+
+/// Maximum length of key sequence to be mapped.
+enum { MAXMAPLEN = 50, };
+
+/// Structure used for mappings and abbreviations.
+typedef struct mapblock mapblock_T;
+struct mapblock {
+ mapblock_T *m_next; ///< next mapblock in list
+ char *m_keys; ///< mapped from, lhs
+ char *m_str; ///< mapped to, rhs
+ char *m_orig_str; ///< rhs as entered by the user
+ LuaRef m_luaref; ///< lua function reference as rhs
+ int m_keylen; ///< strlen(m_keys)
+ int m_mode; ///< valid mode
+ int m_simplified; ///< m_keys was simplified
+ int m_noremap; ///< if non-zero no re-mapping for m_str
+ char m_silent; ///< <silent> used, don't echo commands
+ char m_nowait; ///< <nowait> used
+ char m_expr; ///< <expr> used, m_str is an expression
+ sctx_T m_script_ctx; ///< SCTX where map was defined
+ char *m_desc; ///< description of mapping
+ bool m_replace_keycodes; ///< replace keycodes in result of expression
+};
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index f1a1f25e6c..5839cf7a2e 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// mark.c: functions for setting marks and jumping to them
#include <assert.h>
@@ -9,7 +6,7 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
@@ -17,14 +14,13 @@
#include "nvim/diff.h"
#include "nvim/edit.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/extmark_defs.h"
#include "nvim/fold.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -32,16 +28,15 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/quickfix.h"
-#include "nvim/sign.h"
#include "nvim/strings.h"
#include "nvim/textobject.h"
-#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// This file contains routines to maintain and manipulate marks.
@@ -51,9 +46,6 @@
// There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing
// shada).
-/// Global marks (marks with file number or name)
-static xfmark_T namedfm[NGLOBALMARKS];
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.c.generated.h"
#endif
@@ -80,11 +72,12 @@ void free_xfmark(xfmark_T fm)
}
/// Free and clear fmark_T item
-void clear_fmark(fmark_T *fm)
+void clear_fmark(fmark_T *const fm, const Timestamp timestamp)
FUNC_ATTR_NONNULL_ALL
{
free_fmark(*fm);
- CLEAR_POINTER(fm);
+ *fm = (fmark_T)INIT_FMARK;
+ fm->timestamp = timestamp;
}
// Set named mark "c" to position "pos".
@@ -239,7 +232,7 @@ fmark_T *get_jumplist(win_T *win, int count)
return NULL;
}
- for (;;) {
+ while (true) {
if (win->w_jumplistidx + count < 0
|| win->w_jumplistidx + count >= win->w_jumplistlen) {
return NULL;
@@ -445,11 +438,11 @@ fmark_T *mark_get_motion(buf_T *buf, win_T *win, int name)
listcmd_busy = true; // avoid that '' is changed
if (name == '{' || name == '}') { // to previous/next paragraph
oparg_T oa;
- if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1L, NUL, false)) {
+ if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1, NUL, false)) {
mark = pos_to_mark(buf, NULL, win->w_cursor);
}
} else if (name == '(' || name == ')') { // to previous/next sentence
- if (findsent(name == ')' ? FORWARD : BACKWARD, 1L)) {
+ if (findsent(name == ')' ? FORWARD : BACKWARD, 1)) {
mark = pos_to_mark(buf, NULL, win->w_cursor);
}
}
@@ -521,11 +514,10 @@ fmark_T *pos_to_mark(buf_T *buf, fmark_T *fmp, pos_T pos)
/// @return whether the buffer was switched or not.
static MarkMoveRes switch_to_mark_buf(fmark_T *fm, bool pcmark_on_switch)
{
- bool res;
if (fm->fnum != curbuf->b_fnum) {
// Switch to another file.
int getfile_flag = pcmark_on_switch ? GETF_SETMARK : 0;
- res = buflist_getfile(fm->fnum, (linenr_T)1, getfile_flag, false) == OK;
+ bool res = buflist_getfile(fm->fnum, fm->mark.lnum, getfile_flag, false) == OK;
return res == true ? kMarkSwitchedBuf : kMarkMoveFailed;
}
return 0;
@@ -542,7 +534,11 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags)
{
static fmark_T fm_copy = INIT_FMARK;
MarkMoveRes res = kMarkMoveSuccess;
- if (!mark_check(fm)) {
+ const char *errormsg = NULL;
+ if (!mark_check(fm, &errormsg)) {
+ if (errormsg != NULL) {
+ emsg(errormsg);
+ }
res = kMarkMoveFailed;
goto end;
}
@@ -558,7 +554,10 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags)
goto end;
}
// Check line count now that the **destination buffer is loaded**.
- if (!mark_check_line_bounds(curbuf, fm)) {
+ if (!mark_check_line_bounds(curbuf, fm, &errormsg)) {
+ if (errormsg != NULL) {
+ emsg(errormsg);
+ }
res |= kMarkMoveFailed;
goto end;
}
@@ -617,11 +616,8 @@ fmarkv_T mark_view_make(linenr_T topline, pos_T pos)
/// @return next mark or NULL if no mark is found.
fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line)
{
- int i;
fmark_T *result = NULL;
- pos_T pos;
-
- pos = *startpos;
+ pos_T pos = *startpos;
if (dir == BACKWARD && begin_line) {
pos.col = 0;
@@ -629,7 +625,7 @@ fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line)
pos.col = MAXCOL;
}
- for (i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
if (curbuf->b_namedm[i].mark.lnum > 0) {
if (dir == FORWARD) {
if ((result == NULL || lt(curbuf->b_namedm[i].mark, result->mark))
@@ -677,7 +673,7 @@ static void fname2fnum(xfmark_T *fm)
char *p = path_shorten_fname(NameBuff, IObuff);
// buflist_new() will call fmarks_check_names()
- (void)buflist_new(NameBuff, p, (linenr_T)1, 0);
+ (void)buflist_new(NameBuff, p, 1, 0);
}
// Check all file marks for a name that matches the file name in buf.
@@ -686,18 +682,17 @@ static void fname2fnum(xfmark_T *fm)
void fmarks_check_names(buf_T *buf)
{
char *name = buf->b_ffname;
- int i;
if (buf->b_ffname == NULL) {
return;
}
- for (i = 0; i < NGLOBALMARKS; i++) {
+ for (int i = 0; i < NGLOBALMARKS; i++) {
fmarks_check_one(&namedfm[i], name, buf);
}
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- for (i = 0; i < wp->w_jumplistlen; i++) {
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
fmarks_check_one(&wp->w_jumplist[i], name, buf);
}
}
@@ -715,43 +710,45 @@ static void fmarks_check_one(xfmark_T *fm, char *name, buf_T *buf)
/// Check the position in @a fm is valid.
///
-/// Emit error message and return accordingly.
-///
/// Checks for:
/// - NULL raising unknown mark error.
/// - Line number <= 0 raising mark not set.
/// - Line number > buffer line count, raising invalid mark.
+///
/// @param fm[in] File mark to check.
+/// @param errormsg[out] Error message, if any.
///
/// @return true if the mark passes all the above checks, else false.
-bool mark_check(fmark_T *fm)
+bool mark_check(fmark_T *fm, const char **errormsg)
{
if (fm == NULL) {
- emsg(_(e_umark));
+ *errormsg = _(e_umark);
return false;
} else if (fm->mark.lnum <= 0) {
// In both cases it's an error but only raise when equals to 0
if (fm->mark.lnum == 0) {
- emsg(_(e_marknotset));
+ *errormsg = _(e_marknotset);
}
return false;
}
// Only check for valid line number if the buffer is loaded.
- if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm)) {
+ if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm, errormsg)) {
return false;
}
return true;
}
/// Check if a mark line number is greater than the buffer line count, and set e_markinval.
+///
/// @note Should be done after the buffer is loaded into memory.
/// @param buf Buffer where the mark is set.
/// @param fm Mark to check.
+/// @param errormsg[out] Error message, if any.
/// @return true if below line count else false.
-bool mark_check_line_bounds(buf_T *buf, fmark_T *fm)
+bool mark_check_line_bounds(buf_T *buf, fmark_T *fm, const char **errormsg)
{
if (buf != NULL && fm->mark.lnum > buf->b_ml.ml_line_count) {
- emsg(_(e_markinval));
+ *errormsg = _(e_markinval);
return false;
}
return true;
@@ -762,20 +759,20 @@ bool mark_check_line_bounds(buf_T *buf, fmark_T *fm)
/// Used mainly when trashing the entire buffer during ":e" type commands.
///
/// @param[out] buf Buffer to clear marks in.
-void clrallmarks(buf_T *const buf)
+void clrallmarks(buf_T *const buf, const Timestamp timestamp)
FUNC_ATTR_NONNULL_ALL
{
for (size_t i = 0; i < NMARKS; i++) {
- clear_fmark(&buf->b_namedm[i]);
+ clear_fmark(&buf->b_namedm[i], timestamp);
}
- clear_fmark(&buf->b_last_cursor);
+ clear_fmark(&buf->b_last_cursor, timestamp);
buf->b_last_cursor.mark.lnum = 1;
- clear_fmark(&buf->b_last_insert);
- clear_fmark(&buf->b_last_change);
+ clear_fmark(&buf->b_last_insert, timestamp);
+ clear_fmark(&buf->b_last_change, timestamp);
buf->b_op_start.lnum = 0; // start/end op mark cleared
buf->b_op_end.lnum = 0;
for (int i = 0; i < buf->b_changelistlen; i++) {
- clear_fmark(&buf->b_changelist[i]);
+ clear_fmark(&buf->b_changelist[i], timestamp);
}
buf->b_changelistlen = 0;
}
@@ -795,18 +792,17 @@ char *fm_getname(fmark_T *fmark, int lead_len)
/// The returned string has been allocated.
static char *mark_line(pos_T *mp, int lead_len)
{
- char *s, *p;
- int len;
+ char *p;
if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) {
return xstrdup("-invalid-");
}
assert(Columns >= 0);
// Allow for up to 5 bytes per character.
- s = xstrnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5);
+ char *s = xstrnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5);
// Truncate the line to fit it in the window
- len = 0;
+ int len = 0;
for (p = s; *p != NUL; MB_PTR_ADV(p)) {
len += ptr2cells(p);
if (len >= Columns - lead_len) {
@@ -821,19 +817,18 @@ static char *mark_line(pos_T *mp, int lead_len)
void ex_marks(exarg_T *eap)
{
char *arg = eap->arg;
- int i;
char *name;
- pos_T *posp, *startp, *endp;
+ pos_T *posp;
if (arg != NULL && *arg == NUL) {
arg = NULL;
}
show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true);
- for (i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true);
}
- for (i = 0; i < NGLOBALMARKS; i++) {
+ for (int i = 0; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum != 0) {
name = fm_getname(&namedfm[i].fmark, 15);
} else {
@@ -855,8 +850,8 @@ void ex_marks(exarg_T *eap)
show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true);
// Show the marks as where they will jump to.
- startp = &curbuf->b_visual.vi_start;
- endp = &curbuf->b_visual.vi_end;
+ pos_T *startp = &curbuf->b_visual.vi_start;
+ pos_T *endp = &curbuf->b_visual.vi_end;
if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) {
posp = startp;
} else {
@@ -880,7 +875,7 @@ static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int curren
did_title = false;
} else {
if (arg == NULL) {
- msg(_("No marks set"));
+ msg(_("No marks set"), 0);
} else {
semsg(_("E283: No marks matching \"%s\""), arg);
}
@@ -902,9 +897,9 @@ static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int curren
msg_putchar('\n');
if (!got_int) {
snprintf(IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col);
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
if (name != NULL) {
- msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0);
+ msg_outtrans(name, current ? HL_ATTR(HLF_D) : 0);
}
}
}
@@ -917,33 +912,30 @@ static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int curren
// ":delmarks[!] [marks]"
void ex_delmarks(exarg_T *eap)
{
- char *p;
int from, to;
- int i;
- int lower;
- int digit;
int n;
if (*eap->arg == NUL && eap->forceit) {
// clear all marks
- clrallmarks(curbuf);
+ clrallmarks(curbuf, os_time());
} else if (eap->forceit) {
emsg(_(e_invarg));
} else if (*eap->arg == NUL) {
emsg(_(e_argreq));
} else {
// clear specified marks only
- for (p = eap->arg; *p != NUL; p++) {
- lower = ASCII_ISLOWER(*p);
- digit = ascii_isdigit(*p);
+ const Timestamp timestamp = os_time();
+ for (char *p = eap->arg; *p != NUL; p++) {
+ int lower = ASCII_ISLOWER(*p);
+ int digit = ascii_isdigit(*p);
if (lower || digit || ASCII_ISUPPER(*p)) {
if (p[1] == '-') {
// clear range of marks
from = (uint8_t)(*p);
to = (uint8_t)p[2];
if (!(lower ? ASCII_ISLOWER(p[2])
- : (digit ? ascii_isdigit(p[2])
- : ASCII_ISUPPER(p[2])))
+ : (digit ? ascii_isdigit(p[2])
+ : ASCII_ISUPPER(p[2])))
|| to < from) {
semsg(_(e_invarg2), p);
return;
@@ -954,9 +946,10 @@ void ex_delmarks(exarg_T *eap)
from = to = (uint8_t)(*p);
}
- for (i = from; i <= to; i++) {
+ for (int i = from; i <= to; i++) {
if (lower) {
curbuf->b_namedm[i - 'a'].mark.lnum = 0;
+ curbuf->b_namedm[i - 'a'].timestamp = timestamp;
} else {
if (digit) {
n = i - '0' + NMARKS;
@@ -965,25 +958,29 @@ void ex_delmarks(exarg_T *eap)
}
namedfm[n].fmark.mark.lnum = 0;
namedfm[n].fmark.fnum = 0;
+ namedfm[n].fmark.timestamp = timestamp;
XFREE_CLEAR(namedfm[n].fname);
}
}
} else {
switch (*p) {
case '"':
- CLEAR_FMARK(&curbuf->b_last_cursor); break;
+ clear_fmark(&curbuf->b_last_cursor, timestamp);
+ break;
case '^':
- CLEAR_FMARK(&curbuf->b_last_insert); break;
+ clear_fmark(&curbuf->b_last_insert, timestamp);
+ break;
case '.':
- CLEAR_FMARK(&curbuf->b_last_change); break;
+ clear_fmark(&curbuf->b_last_change, timestamp);
+ break;
case '[':
- curbuf->b_op_start.lnum = 0; break;
+ curbuf->b_op_start.lnum = 0; break;
case ']':
- curbuf->b_op_end.lnum = 0; break;
+ curbuf->b_op_end.lnum = 0; break;
case '<':
curbuf->b_visual.vi_start.lnum = 0; break;
case '>':
- curbuf->b_visual.vi_end.lnum = 0; break;
+ curbuf->b_visual.vi_end.lnum = 0; break;
case ' ':
break;
default:
@@ -998,15 +995,12 @@ void ex_delmarks(exarg_T *eap)
// print the jumplist
void ex_jumps(exarg_T *eap)
{
- int i;
- char *name;
-
cleanup_jumplist(curwin, true);
// Highlight title
msg_puts_title(_("\n jump line col file/text"));
- for (i = 0; i < curwin->w_jumplistlen && !got_int; i++) {
+ for (int i = 0; i < curwin->w_jumplistlen && !got_int; i++) {
if (curwin->w_jumplist[i].fmark.mark.lnum != 0) {
- name = fm_getname(&curwin->w_jumplist[i].fmark, 16);
+ char *name = fm_getname(&curwin->w_jumplist[i].fmark, 16);
// Make sure to output the current indicator, even when on an wiped
// out buffer. ":filter" may still skip it.
@@ -1028,10 +1022,9 @@ void ex_jumps(exarg_T *eap)
i == curwin->w_jumplistidx ? '>' : ' ',
i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx : curwin->w_jumplistidx - i,
curwin->w_jumplist[i].fmark.mark.lnum, curwin->w_jumplist[i].fmark.mark.col);
- msg_outtrans(IObuff);
- msg_outtrans_attr(name,
- curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum
- ? HL_ATTR(HLF_D) : 0);
+ msg_outtrans(IObuff, 0);
+ msg_outtrans(name,
+ curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0);
xfree(name);
os_breakcheck();
}
@@ -1051,26 +1044,24 @@ void ex_clearjumps(exarg_T *eap)
// print the changelist
void ex_changes(exarg_T *eap)
{
- int i;
- char *name;
-
// Highlight title
msg_puts_title(_("\nchange line col text"));
- for (i = 0; i < curbuf->b_changelistlen && !got_int; i++) {
+ for (int i = 0; i < curbuf->b_changelistlen && !got_int; i++) {
if (curbuf->b_changelist[i].mark.lnum != 0) {
msg_putchar('\n');
if (got_int) {
break;
}
- snprintf(IObuff, IOSIZE, "%c %3d %5ld %4d ",
+ snprintf(IObuff, IOSIZE, "%c %3d %5" PRIdLINENR " %4d ",
i == curwin->w_changelistidx ? '>' : ' ',
- i > curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i,
- (long)curbuf->b_changelist[i].mark.lnum,
+ i >
+ curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i,
+ curbuf->b_changelist[i].mark.lnum,
curbuf->b_changelist[i].mark.col);
- msg_outtrans(IObuff);
- name = mark_line(&curbuf->b_changelist[i].mark, 17);
- msg_outtrans_attr(name, HL_ATTR(HLF_D));
+ msg_outtrans(IObuff, 0);
+ char *name = mark_line(&curbuf->b_changelist[i].mark, 17);
+ msg_outtrans(name, HL_ATTR(HLF_D));
xfree(name);
os_breakcheck();
}
@@ -1109,19 +1100,19 @@ void ex_changes(exarg_T *eap)
} \
}
-// Adjust marks between line1 and line2 (inclusive) to move 'amount' lines.
+// Adjust marks between "line1" and "line2" (inclusive) to move "amount" lines.
// Must be called before changed_*(), appended_lines() or deleted_lines().
// May be called before or after changing the text.
-// When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks
-// within this range are made invalid.
-// If 'amount_after' is non-zero adjust marks after line2.
+// When deleting lines "line1" to "line2", use an "amount" of MAXLNUM: The
+// marks within this range are made invalid.
+// If "amount_after" is non-zero adjust marks after "line2".
// Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2);
// Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0);
// or: mark_adjust(56, 55, MAXLNUM, 2);
void mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after,
ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, true, op);
+ mark_adjust_buf(curbuf, line1, line2, amount, amount_after, true, false, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@@ -1132,84 +1123,83 @@ void mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amoun
void mark_adjust_nofold(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after,
ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, false, op);
+ mark_adjust_buf(curbuf, line1, line2, amount, amount_after, false, false, op);
}
-static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount,
- linenr_T amount_after, bool adjust_folds, ExtmarkOp op)
+void mark_adjust_buf(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount,
+ linenr_T amount_after, bool adjust_folds, bool by_api, ExtmarkOp op)
{
- int i;
- int fnum = curbuf->b_fnum;
+ int fnum = buf->b_fnum;
linenr_T *lp;
static pos_T initpos = { 1, 0, 0 };
- if (line2 < line1 && amount_after == 0L) { // nothing to do
+ if (line2 < line1 && amount_after == 0) { // nothing to do
return;
}
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// named marks, lower case and upper case
- for (i = 0; i < NMARKS; i++) {
- ONE_ADJUST(&(curbuf->b_namedm[i].mark.lnum));
+ for (int i = 0; i < NMARKS; i++) {
+ ONE_ADJUST(&(buf->b_namedm[i].mark.lnum));
if (namedfm[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum));
}
}
- for (i = NMARKS; i < NGLOBALMARKS; i++) {
+ for (int i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum));
}
}
// last Insert position
- ONE_ADJUST(&(curbuf->b_last_insert.mark.lnum));
+ ONE_ADJUST(&(buf->b_last_insert.mark.lnum));
// last change position
- ONE_ADJUST(&(curbuf->b_last_change.mark.lnum));
+ ONE_ADJUST(&(buf->b_last_change.mark.lnum));
// last cursor position, if it was set
- if (!equalpos(curbuf->b_last_cursor.mark, initpos)) {
- ONE_ADJUST(&(curbuf->b_last_cursor.mark.lnum));
+ if (!equalpos(buf->b_last_cursor.mark, initpos)) {
+ ONE_ADJUST(&(buf->b_last_cursor.mark.lnum));
}
// list of change positions
- for (i = 0; i < curbuf->b_changelistlen; i++) {
- ONE_ADJUST_NODEL(&(curbuf->b_changelist[i].mark.lnum));
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ ONE_ADJUST_NODEL(&(buf->b_changelist[i].mark.lnum));
}
// Visual area
- ONE_ADJUST_NODEL(&(curbuf->b_visual.vi_start.lnum));
- ONE_ADJUST_NODEL(&(curbuf->b_visual.vi_end.lnum));
+ ONE_ADJUST_NODEL(&(buf->b_visual.vi_start.lnum));
+ ONE_ADJUST_NODEL(&(buf->b_visual.vi_end.lnum));
// quickfix marks
- if (!qf_mark_adjust(NULL, line1, line2, amount, amount_after)) {
- curbuf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY;
+ if (!qf_mark_adjust(buf, NULL, line1, line2, amount, amount_after)) {
+ buf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY;
}
// location lists
bool found_one = false;
FOR_ALL_TAB_WINDOWS(tab, win) {
- found_one |= qf_mark_adjust(win, line1, line2, amount, amount_after);
+ found_one |= qf_mark_adjust(buf, win, line1, line2, amount, amount_after);
}
if (!found_one) {
- curbuf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY;
+ buf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY;
}
-
- sign_mark_adjust(line1, line2, amount, amount_after);
}
if (op != kExtmarkNOOP) {
- extmark_adjust(curbuf, line1, line2, amount, amount_after, op);
+ extmark_adjust(buf, line1, line2, amount, amount_after, op);
}
- // previous context mark
- ONE_ADJUST(&(curwin->w_pcmark.lnum));
+ if (curwin->w_buffer == buf) {
+ // previous context mark
+ ONE_ADJUST(&(curwin->w_pcmark.lnum));
- // previous pcmark
- ONE_ADJUST(&(curwin->w_prev_pcmark.lnum));
+ // previous pcpmark
+ ONE_ADJUST(&(curwin->w_prev_pcmark.lnum));
- // saved cursor for formatting
- if (saved_cursor.lnum != 0) {
- ONE_ADJUST_NODEL(&(saved_cursor.lnum));
+ // saved cursor for formatting
+ if (saved_cursor.lnum != 0) {
+ ONE_ADJUST_NODEL(&(saved_cursor.lnum));
+ }
}
// Adjust items in all windows related to the current buffer.
@@ -1217,17 +1207,17 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// Marks in the jumplist. When deleting lines, this may create
// duplicate marks in the jumplist, they will be removed later.
- for (i = 0; i < win->w_jumplistlen; i++) {
+ for (int i = 0; i < win->w_jumplistlen; i++) {
if (win->w_jumplist[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(win->w_jumplist[i].fmark.mark.lnum));
}
}
}
- if (win->w_buffer == curbuf) {
+ if (win->w_buffer == buf) {
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// marks in the tag stack
- for (i = 0; i < win->w_tagstacklen; i++) {
+ for (int i = 0; i < win->w_tagstacklen; i++) {
if (win->w_tagstack[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(win->w_tagstack[i].fmark.mark.lnum));
}
@@ -1242,22 +1232,29 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
// topline and cursor position for windows with the same buffer
// other than the current window
- if (win != curwin) {
+ if (win != curwin || by_api) {
if (win->w_topline >= line1 && win->w_topline <= line2) {
if (amount == MAXLNUM) { // topline is deleted
if (line1 <= 1) {
win->w_topline = 1;
} else {
- win->w_topline = line1 - 1;
+ // api: if the deleted region was replaced with new contents, display that
+ win->w_topline = (by_api && amount_after > line1 - line2 - 1) ? line1 : line1 - 1;
}
- } else { // keep topline on the same line
+ } else if (win->w_topline > line1) {
+ // keep topline on the same line, unless inserting just
+ // above it (we probably want to see that line then)
win->w_topline += amount;
}
win->w_topfill = 0;
- } else if (amount_after && win->w_topline > line2) {
+ // api: display new line if inserted right at topline
+ // TODO(bfredl): maybe always?
+ } else if (amount_after && win->w_topline > line2 + (by_api ? 1 : 0)) {
win->w_topline += amount_after;
win->w_topfill = 0;
}
+ }
+ if (win != curwin && !by_api) {
if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2) {
if (amount == MAXLNUM) { // line with cursor is deleted
if (line1 <= 1) {
@@ -1281,7 +1278,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
}
// adjust diffs
- diff_mark_adjust(line1, line2, amount, amount_after);
+ diff_mark_adjust(buf, line1, line2, amount, amount_after);
}
// This code is used often, needs to be fast.
@@ -1306,24 +1303,23 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
// position.
// "spaces_removed" is the number of spaces that were removed, matters when the
// cursor is inside them.
-void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long col_amount,
+void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, colnr_T col_amount,
int spaces_removed)
{
- int i;
int fnum = curbuf->b_fnum;
pos_T *posp;
- if ((col_amount == 0L && lnum_amount == 0L) || (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
+ if ((col_amount == 0 && lnum_amount == 0) || (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
return; // nothing to do
}
// named marks, lower case and upper case
- for (i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
COL_ADJUST(&(curbuf->b_namedm[i].mark));
if (namedfm[i].fmark.fnum == fnum) {
COL_ADJUST(&(namedfm[i].fmark.mark));
}
}
- for (i = NMARKS; i < NGLOBALMARKS; i++) {
+ for (int i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum) {
COL_ADJUST(&(namedfm[i].fmark.mark));
}
@@ -1336,7 +1332,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c
COL_ADJUST(&(curbuf->b_last_change.mark));
// list of change positions
- for (i = 0; i < curbuf->b_changelistlen; i++) {
+ for (int i = 0; i < curbuf->b_changelistlen; i++) {
COL_ADJUST(&(curbuf->b_changelist[i].mark));
}
@@ -1356,7 +1352,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c
// Adjust items in all windows related to the current buffer.
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
// marks in the jumplist
- for (i = 0; i < win->w_jumplistlen; i++) {
+ for (int i = 0; i < win->w_jumplistlen; i++) {
if (win->w_jumplist[i].fmark.fnum == fnum) {
COL_ADJUST(&(win->w_jumplist[i].fmark.mark));
}
@@ -1364,7 +1360,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c
if (win->w_buffer == curbuf) {
// marks in the tag stack
- for (i = 0; i < win->w_tagstacklen; i++) {
+ for (int i = 0; i < win->w_tagstacklen; i++) {
if (win->w_tagstack[i].fmark.fnum == fnum) {
COL_ADJUST(&(win->w_tagstack[i].fmark.mark));
}
@@ -1457,9 +1453,7 @@ void cleanup_jumplist(win_T *wp, bool loadfiles)
// Copy the jumplist from window "from" to window "to".
void copy_jumplist(win_T *from, win_T *to)
{
- int i;
-
- for (i = 0; i < from->w_jumplistlen; i++) {
+ for (int i = 0; i < from->w_jumplistlen; i++) {
to->w_jumplist[i] = from->w_jumplist[i];
if (from->w_jumplist[i].fname != NULL) {
to->w_jumplist[i].fname = xstrdup(from->w_jumplist[i].fname);
@@ -1523,9 +1517,9 @@ const void *mark_global_iter(const void *const iter, char *const name, xfmark_T
return NULL;
}
size_t iter_off = (size_t)(iter_mark - &(namedfm[0]));
- *name = (char)(iter_off < NMARKS ?
- 'A' + (char)iter_off :
- '0' + (char)(iter_off - NMARKS));
+ *name = (char)(iter_off < NMARKS
+ ? 'A' + (char)iter_off
+ : '0' + (char)(iter_off - NMARKS));
*fm = *iter_mark;
while ((size_t)(++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) {
if (iter_mark->fmark.mark.lnum) {
@@ -1587,11 +1581,15 @@ const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, cha
FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
{
*name = NUL;
- char mark_name = (char)(iter == NULL ? NUL :
- iter == &(buf->b_last_cursor) ? '"' :
- iter == &(buf->b_last_insert) ? '^' :
- iter == &(buf->b_last_change) ? '.' :
- 'a' + (char)((const fmark_T *)iter - &(buf->b_namedm[0])));
+ char mark_name = (char)(iter == NULL
+ ? NUL
+ : (iter == &(buf->b_last_cursor)
+ ? '"'
+ : (iter == &(buf->b_last_insert)
+ ? '^'
+ : (iter == &(buf->b_last_change)
+ ? '.'
+ : 'a' + (const fmark_T *)iter - &(buf->b_namedm[0])))));
const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name);
while (iter_mark != NULL && iter_mark->mark.lnum == 0) {
iter_mark = next_buffer_mark(buf, &mark_name);
@@ -1671,9 +1669,7 @@ bool mark_set_local(const char name, buf_T *const buf, const fmark_T fm, const b
// Free items in the jumplist of window "wp".
void free_jumplist(win_T *wp)
{
- int i;
-
- for (i = 0; i < wp->w_jumplistlen; i++) {
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
free_xfmark(wp->w_jumplist[i]);
}
wp->w_jumplistlen = 0;
@@ -1710,7 +1706,7 @@ void mark_mb_adjustpos(buf_T *buf, pos_T *lp)
FUNC_ATTR_NONNULL_ALL
{
if (lp->col > 0 || lp->coladd > 1) {
- const char *const p = ml_get_buf(buf, lp->lnum, false);
+ const char *const p = ml_get_buf(buf, lp->lnum);
if (*p == NUL || (int)strlen(p) < lp->col) {
lp->col = 0;
} else {
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index af0abba864..3237ae541e 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -1,19 +1,18 @@
-#ifndef NVIM_MARK_H
-#define NVIM_MARK_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark_defs.h"
#include "nvim/func_attr.h"
-#include "nvim/macros.h"
-#include "nvim/mark_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark_defs.h" // IWYU pragma: export
#include "nvim/memory.h"
#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
/// Set fmark using given value
#define SET_FMARK(fmarkp_, mark_, fnum_, view_) \
@@ -34,10 +33,6 @@
SET_FMARK(fmarkp___, mark_, fnum_, view_); \
} while (0)
-/// Clear given fmark
-#define CLEAR_FMARK(fmarkp_) \
- RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0, ((fmarkv_T) { 0 }))
-
/// Set given extended mark (regular mark + file name)
#define SET_XFMARK(xfmarkp_, mark_, fnum_, view_, fname_) \
do { \
@@ -125,4 +120,3 @@ static inline void clearpos(pos_T *a)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.h.generated.h"
#endif
-#endif // NVIM_MARK_H
diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h
index f9df0028db..67532d030f 100644
--- a/src/nvim/mark_defs.h
+++ b/src/nvim/mark_defs.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_MARK_DEFS_H
-#define NVIM_MARK_DEFS_H
+#pragma once
-#include "nvim/eval/typval.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/os/time_defs.h"
+#include "nvim/pos_defs.h"
// marks: positions in a file
// (a normal mark is a lnum/col pair, the same as a file position)
@@ -86,4 +85,5 @@ typedef struct xfilemark {
#define INIT_XFMARK { INIT_FMARK, NULL }
-#endif // NVIM_MARK_DEFS_H
+/// Global marks (marks with file number or name)
+EXTERN xfmark_T namedfm[NGLOBALMARKS] INIT( = { 0 });
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 2036ddd21d..b555b3b4ae 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Tree data structure for storing marks at (row, col) positions and updating
// them to arbitrary text changes. Derivative work of kbtree in klib, whose
// copyright notice is reproduced below. Also inspired by the design of the
@@ -14,8 +11,6 @@
// Use marktree_itr_current and marktree_itr_next/prev to read marks in a loop.
// marktree_del_itr deletes the current mark of the iterator and implicitly
// moves the iterator to the next mark.
-//
-// Work is ongoing to fully support ranges (mark pairs).
// Copyright notice for kbtree (included in heavily modified form):
//
@@ -48,29 +43,41 @@
// at the repo root.
#include <assert.h>
+#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "klib/kvec.h"
#include "nvim/garray.h"
#include "nvim/marktree.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+// only for debug functions
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/garray_defs.h"
+#include "nvim/macros_defs.h"
#define T MT_BRANCH_FACTOR
-#define ILEN (sizeof(mtnode_t) + (2 * T) * sizeof(void *))
+#define ILEN (sizeof(MTNode) + (2 * T) * sizeof(void *))
#define ID_INCR (((uint64_t)1) << 2)
-#define rawkey(itr) ((itr)->node->key[(itr)->i])
+#define rawkey(itr) ((itr)->x->key[(itr)->i])
-static bool pos_leq(mtpos_t a, mtpos_t b)
+static bool pos_leq(MTPos a, MTPos b)
{
return a.row < b.row || (a.row == b.row && a.col <= b.col);
}
-static void relative(mtpos_t base, mtpos_t *val)
+static bool pos_less(MTPos a, MTPos b)
+{
+ return !pos_leq(b, a);
+}
+
+static void relative(MTPos base, MTPos *val)
{
assert(pos_leq(base, *val));
if (val->row == base.row) {
@@ -81,7 +88,7 @@ static void relative(mtpos_t base, mtpos_t *val)
}
}
-static void unrelative(mtpos_t base, mtpos_t *val)
+static void unrelative(MTPos base, MTPos *val)
{
if (val->row == 0) {
val->row = base.row;
@@ -91,7 +98,7 @@ static void unrelative(mtpos_t base, mtpos_t *val)
}
}
-static void compose(mtpos_t *base, mtpos_t val)
+static void compose(MTPos *base, MTPos val)
{
if (val.row == 0) {
base->col += val.col;
@@ -101,12 +108,21 @@ static void compose(mtpos_t *base, mtpos_t val)
}
}
+// Used by `marktree_splice`. Need to keep track of marks which moved
+// in order to repair intersections.
+typedef struct {
+ uint64_t id;
+ MTNode *old, *new;
+ int old_i, new_i;
+} Damage;
+typedef kvec_withinit_t(Damage, 8) DamageList;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "marktree.c.generated.h"
#endif
#define mt_generic_cmp(a, b) (((b) < (a)) - ((a) < (b)))
-static int key_cmp(mtkey_t a, mtkey_t b)
+static int key_cmp(MTKey a, MTKey b)
{
int cmp = mt_generic_cmp(a.pos.row, b.pos.row);
if (cmp != 0) {
@@ -116,18 +132,25 @@ static int key_cmp(mtkey_t a, mtkey_t b)
if (cmp != 0) {
return cmp;
}
- // NB: keeping the events at the same pos sorted by id is actually not
- // necessary only make sure that START is before END etc.
- return mt_generic_cmp(a.flags, b.flags);
+
+ // TODO(bfredl): MT_FLAG_REAL could go away if we fix marktree_getp_aux for real
+ const uint16_t cmp_mask = MT_FLAG_RIGHT_GRAVITY | MT_FLAG_END | MT_FLAG_REAL | MT_FLAG_LAST;
+ return mt_generic_cmp(a.flags & cmp_mask, b.flags & cmp_mask);
}
-static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
+/// @return position of k if it exists in the node, otherwise the position
+/// it should be inserted, which ranges from 0 to x->n _inclusively_
+/// @param match (optional) set to TRUE if match (pos, gravity) was found
+static inline int marktree_getp_aux(const MTNode *x, MTKey k, bool *match)
{
- int tr, *rr, begin = 0, end = x->n;
+ bool dummy_match;
+ bool *m = match ? match : &dummy_match;
+
+ int begin = 0, end = x->n;
if (x->n == 0) {
+ *m = false;
return -1;
}
- rr = r? r : &tr;
while (begin < end) {
int mid = (begin + end) >> 1;
if (key_cmp(x->key[mid], k) < 0) {
@@ -137,47 +160,84 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
}
}
if (begin == x->n) {
- *rr = 1; return x->n - 1;
+ *m = false;
+ return x->n - 1;
}
- if ((*rr = key_cmp(k, x->key[begin])) < 0) {
+ if (!(*m = (key_cmp(k, x->key[begin]) == 0))) {
begin--;
}
return begin;
}
-static inline void refkey(MarkTree *b, mtnode_t *x, int i)
+static inline void refkey(MarkTree *b, MTNode *x, int i)
{
pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x);
}
+static MTNode *id2node(MarkTree *b, uint64_t id)
+{
+ return pmap_get(uint64_t)(b->id2node, id);
+}
+
// put functions
// x must be an internal node, which is not full
// x->ptr[i] should be a full node, i e x->ptr[i]->n == 2*T-1
-static inline void split_node(MarkTree *b, mtnode_t *x, const int i)
+static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
{
- mtnode_t *y = x->ptr[i];
- mtnode_t *z;
- z = (mtnode_t *)xcalloc(1, y->level ? ILEN : sizeof(mtnode_t));
- b->n_nodes++;
+ MTNode *y = x->ptr[i];
+ MTNode *z = marktree_alloc_node(b, y->level);
z->level = y->level;
z->n = T - 1;
- memcpy(z->key, &y->key[T], sizeof(mtkey_t) * (T - 1));
+
+ // tricky: we might split a node in between inserting the start node and the end
+ // node of the same pair. Then we must not intersect this id yet (done later
+ // in marktree_intersect_pair).
+ uint64_t last_start = mt_end(next) ? mt_lookup_id(next.ns, next.id, false) : MARKTREE_END_FLAG;
+
+ // no alloc in the common case (less than 4 intersects)
+ kvi_copy(z->intersect, y->intersect);
+
+ if (!y->level) {
+ uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index
+ for (int j = 0; j < T; j++) {
+ MTKey k = y->key[j];
+ uint64_t pi_end = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, true), true);
+ if (mt_start(k) && pi_end > pi && mt_lookup_key(k) != last_start) {
+ intersect_node(b, z, mt_lookup_id(k.ns, k.id, false));
+ }
+ }
+
+ // note: y->key[T-1] is moved up and thus checked for both
+ for (int j = T - 1; j < (T * 2) - 1; j++) {
+ MTKey k = y->key[j];
+ uint64_t pi_start = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, false), true);
+ if (mt_end(k) && pi_start > 0 && pi_start < pi) {
+ intersect_node(b, y, mt_lookup_id(k.ns, k.id, false));
+ }
+ }
+ }
+
+ memcpy(z->key, &y->key[T], sizeof(MTKey) * (T - 1));
for (int j = 0; j < T - 1; j++) {
refkey(b, z, j);
}
if (y->level) {
- memcpy(z->ptr, &y->ptr[T], sizeof(mtnode_t *) * T);
+ memcpy(z->ptr, &y->ptr[T], sizeof(MTNode *) * T);
for (int j = 0; j < T; j++) {
z->ptr[j]->parent = z;
+ z->ptr[j]->p_idx = (int16_t)j;
}
}
y->n = T - 1;
memmove(&x->ptr[i + 2], &x->ptr[i + 1],
- sizeof(mtnode_t *) * (size_t)(x->n - i));
+ sizeof(MTNode *) * (size_t)(x->n - i));
x->ptr[i + 1] = z;
z->parent = x; // == y->parent
- memmove(&x->key[i + 1], &x->key[i], sizeof(mtkey_t) * (size_t)(x->n - i));
+ for (int j = i + 1; j < x->n + 2; j++) {
+ x->ptr[j]->p_idx = (int16_t)j;
+ }
+ memmove(&x->key[i + 1], &x->key[i], sizeof(MTKey) * (size_t)(x->n - i));
// move key to internal layer:
x->key[i] = y->key[T - 1];
@@ -190,25 +250,32 @@ static inline void split_node(MarkTree *b, mtnode_t *x, const int i)
if (i > 0) {
unrelative(x->key[i - 1].pos, &x->key[i].pos);
}
+
+ if (y->level) {
+ bubble_up(y);
+ bubble_up(z);
+ } else {
+ // code above goose here
+ }
}
// x must not be a full node (even if there might be internal space)
-static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
+static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k)
{
- int i;
+ // TODO(bfredl): ugh, make sure this is the _last_ valid (pos, gravity) position,
+ // to minimize movement
+ int i = marktree_getp_aux(x, k, NULL) + 1;
if (x->level == 0) {
- i = marktree_getp_aux(x, k, 0);
- if (i != x->n - 1) {
- memmove(&x->key[i + 2], &x->key[i + 1],
- (size_t)(x->n - i - 1) * sizeof(mtkey_t));
+ if (i != x->n) {
+ memmove(&x->key[i + 1], &x->key[i],
+ (size_t)(x->n - i) * sizeof(MTKey));
}
- x->key[i + 1] = k;
- refkey(b, x, i + 1);
+ x->key[i] = k;
+ refkey(b, x, i);
x->n++;
} else {
- i = marktree_getp_aux(x, k, 0) + 1;
if (x->ptr[i]->n == 2 * T - 1) {
- split_node(b, x, i);
+ split_node(b, x, i, k);
if (key_cmp(k, x->key[i]) > 0) {
i++;
}
@@ -220,9 +287,9 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
}
}
-void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right)
+void marktree_put(MarkTree *b, MTKey key, int end_row, int end_col, bool end_right)
{
- assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK));
+ assert(!(key.flags & ~(MT_FLAG_EXTERNAL_MASK | MT_FLAG_RIGHT_GRAVITY)));
if (end_row >= 0) {
key.flags |= MT_FLAG_PAIRED;
}
@@ -230,32 +297,150 @@ void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_r
marktree_put_key(b, key);
if (end_row >= 0) {
- mtkey_t end_key = key;
+ MTKey end_key = key;
end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY)
|(uint16_t)MT_FLAG_END
|(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0));
- end_key.pos = (mtpos_t){ end_row, end_col };
+ end_key.pos = (MTPos){ end_row, end_col };
marktree_put_key(b, end_key);
+ MarkTreeIter itr[1] = { 0 }, end_itr[1] = { 0 };
+ marktree_lookup(b, mt_lookup_key(key), itr);
+ marktree_lookup(b, mt_lookup_key(end_key), end_itr);
+
+ marktree_intersect_pair(b, mt_lookup_key(key), itr, end_itr, false);
+ }
+}
+
+// this is currently not used very often, but if it was it should use binary search
+static bool intersection_has(Intersection *x, uint64_t id)
+{
+ for (size_t i = 0; i < kv_size(*x); i++) {
+ if (kv_A(*x, i) == id) {
+ return true;
+ } else if (kv_A(*x, i) >= id) {
+ return false;
+ }
}
+ return false;
}
-void marktree_put_key(MarkTree *b, mtkey_t k)
+static void intersect_node(MarkTree *b, MTNode *x, uint64_t id)
+{
+ assert(!(id & MARKTREE_END_FLAG));
+ kvi_pushp(x->intersect);
+ // optimized for the common case: new key is always in the end
+ for (ssize_t i = (ssize_t)kv_size(x->intersect) - 1; i >= 0; i--) {
+ if (i > 0 && kv_A(x->intersect, i - 1) > id) {
+ kv_A(x->intersect, i) = kv_A(x->intersect, i - 1);
+ } else {
+ kv_A(x->intersect, i) = id;
+ break;
+ }
+ }
+}
+
+static void unintersect_node(MarkTree *b, MTNode *x, uint64_t id, bool strict)
+{
+ assert(!(id & MARKTREE_END_FLAG));
+ bool seen = false;
+ size_t i;
+ for (i = 0; i < kv_size(x->intersect); i++) {
+ if (kv_A(x->intersect, i) < id) {
+ continue;
+ } else if (kv_A(x->intersect, i) == id) {
+ seen = true;
+ break;
+ } else { // (kv_A(x->intersect, i) > id)
+ break;
+ }
+ }
+ if (strict) {
+ assert(seen);
+ }
+
+ if (seen) {
+ if (i < kv_size(x->intersect) - 1) {
+ memmove(&kv_A(x->intersect, i), &kv_A(x->intersect, i + 1), (kv_size(x->intersect) - i - 1) *
+ sizeof(kv_A(x->intersect, i)));
+ }
+ kv_size(x->intersect)--;
+ }
+}
+
+/// @param itr mutated
+/// @param end_itr not mutated
+void marktree_intersect_pair(MarkTree *b, uint64_t id, MarkTreeIter *itr, MarkTreeIter *end_itr,
+ bool delete)
+{
+ int lvl = 0, maxlvl = MIN(itr->lvl, end_itr->lvl);
+#define iat(itr, l, q) ((l == itr->lvl) ? itr->i + q : itr->s[l].i)
+ for (; lvl < maxlvl; lvl++) {
+ if (itr->s[lvl].i > end_itr->s[lvl].i) {
+ return; // empty range
+ } else if (itr->s[lvl].i < end_itr->s[lvl].i) {
+ break; // work to do
+ }
+ }
+ if (lvl == maxlvl && iat(itr, lvl, 1) > iat(end_itr, lvl, 0)) {
+ return; // empty range
+ }
+
+ while (itr->x) {
+ bool skip = false;
+ if (itr->x == end_itr->x) {
+ if (itr->x->level == 0 || itr->i >= end_itr->i) {
+ break;
+ } else {
+ skip = true;
+ }
+ } else if (itr->lvl > lvl) {
+ skip = true;
+ } else {
+ if (iat(itr, lvl, 1) < iat(end_itr, lvl, 1)) {
+ skip = true;
+ } else {
+ lvl++;
+ }
+ }
+
+ if (skip) {
+ if (itr->x->level) {
+ MTNode *x = itr->x->ptr[itr->i + 1];
+ if (delete) {
+ unintersect_node(b, x, id, true);
+ } else {
+ intersect_node(b, x, id);
+ }
+ }
+ }
+ marktree_itr_next_skip(b, itr, skip, true, NULL);
+ }
+#undef iat
+}
+
+static MTNode *marktree_alloc_node(MarkTree *b, bool internal)
+{
+ MTNode *x = xcalloc(1, internal ? ILEN : sizeof(MTNode));
+ kvi_init(x->intersect);
+ b->n_nodes++;
+ return x;
+}
+
+void marktree_put_key(MarkTree *b, MTKey k)
{
k.flags |= MT_FLAG_REAL; // let's be real.
if (!b->root) {
- b->root = (mtnode_t *)xcalloc(1, ILEN);
- b->n_nodes++;
+ b->root = marktree_alloc_node(b, true);
}
- mtnode_t *r, *s;
b->n_keys++;
- r = b->root;
+ MTNode *r = b->root;
if (r->n == 2 * T - 1) {
- b->n_nodes++;
- s = (mtnode_t *)xcalloc(1, ILEN);
+ MTNode *s = marktree_alloc_node(b, true);
b->root = s; s->level = r->level + 1; s->n = 0;
s->ptr[0] = r;
r->parent = s;
- split_node(b, s, 0);
+ r->p_idx = 0;
+ split_node(b, s, 0, k);
r = s;
}
marktree_putp_aux(b, r, k);
@@ -279,26 +464,41 @@ void marktree_put_key(MarkTree *b, mtkey_t k)
/// 6. If 4 went all the way to the root node. The root node
/// might have ended up with size 0. Delete it then.
///
-/// NB: ideally keeps the iterator valid. Like point to the key after this
-/// if present.
+/// The iterator remains valid, and now points at the key _after_ the deleted
+/// one.
///
/// @param rev should be true if we plan to iterate _backwards_ and delete
/// stuff before this key. Most of the time this is false (the
/// recommended strategy is to always iterate forward)
-void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
+uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
{
int adjustment = 0;
- mtnode_t *cur = itr->node;
+ MTNode *cur = itr->x;
int curi = itr->i;
uint64_t id = mt_lookup_key(cur->key[curi]);
- // fprintf(stderr, "\nDELET %lu\n", id);
- if (itr->node->level) {
+ MTKey raw = rawkey(itr);
+ uint64_t other = 0;
+ if (mt_paired(raw) && !(raw.flags & MT_FLAG_ORPHANED)) {
+ other = mt_lookup_key_side(raw, !mt_end(raw));
+
+ MarkTreeIter other_itr[1];
+ marktree_lookup(b, other, other_itr);
+ rawkey(other_itr).flags |= MT_FLAG_ORPHANED;
+ // Remove intersect markers. NB: must match exactly!
+ if (mt_start(raw)) {
+ MarkTreeIter this_itr[1] = { *itr }; // mutated copy
+ marktree_intersect_pair(b, id, this_itr, other_itr, true);
+ } else {
+ marktree_intersect_pair(b, other, other_itr, itr, true);
+ }
+ }
+
+ if (itr->x->level) {
if (rev) {
abort();
} else {
- // fprintf(stderr, "INTERNAL %d\n", cur->level);
// steal previous node
marktree_itr_prev(b, itr);
adjustment = -1;
@@ -306,41 +506,72 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
}
// 3.
- mtnode_t *x = itr->node;
+ MTNode *x = itr->x;
assert(x->level == 0);
- mtkey_t intkey = x->key[itr->i];
+ MTKey intkey = x->key[itr->i];
if (x->n > itr->i + 1) {
memmove(&x->key[itr->i], &x->key[itr->i + 1],
- sizeof(mtkey_t) * (size_t)(x->n - itr->i - 1));
+ sizeof(MTKey) * (size_t)(x->n - itr->i - 1));
}
x->n--;
+ b->n_keys--;
+ pmap_del(uint64_t)(b->id2node, id, NULL);
+
// 4.
// if (adjustment == 1) {
// abort();
// }
if (adjustment == -1) {
int ilvl = itr->lvl - 1;
- const mtnode_t *lnode = x;
+ MTNode *lnode = x;
+ uint64_t start_id = 0;
+ bool did_bubble = false;
+ if (mt_end(intkey)) {
+ start_id = mt_lookup_key_side(intkey, false);
+ }
do {
- const mtnode_t *const p = lnode->parent;
+ MTNode *p = lnode->parent;
if (ilvl < 0) {
abort();
}
- const int i = itr->s[ilvl].i;
+ int i = itr->s[ilvl].i;
assert(p->ptr[i] == lnode);
if (i > 0) {
unrelative(p->key[i - 1].pos, &intkey.pos);
}
+
+ if (p != cur && start_id) {
+ if (intersection_has(&p->ptr[0]->intersect, start_id)) {
+ // if not the first time, we need to undo the addition in the
+ // previous step (`intersect_node` just below)
+ int last = (lnode != x) ? 1 : 0;
+ for (int k = 0; k < p->n + last; k++) { // one less as p->ptr[n] is the last
+ unintersect_node(b, p->ptr[k], start_id, true);
+ }
+ intersect_node(b, p, start_id);
+ did_bubble = true;
+ }
+ }
+
lnode = p;
ilvl--;
} while (lnode != cur);
- mtkey_t deleted = cur->key[curi];
+ MTKey deleted = cur->key[curi];
cur->key[curi] = intkey;
refkey(b, cur, curi);
+ // if `did_bubble` then we already added `start_id` to some parent
+ if (mt_end(cur->key[curi]) && !did_bubble) {
+ uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index
+ uint64_t pi_start = pseudo_index_for_id(b, start_id, true);
+ if (pi_start > 0 && pi_start < pi) {
+ intersect_node(b, x, start_id);
+ }
+ }
+
relative(intkey.pos, &deleted.pos);
- mtnode_t *y = cur->ptr[curi + 1];
+ MTNode *y = cur->ptr[curi + 1];
if (deleted.pos.row || deleted.pos.col) {
while (y) {
for (int k = 0; k < y->n; k++) {
@@ -352,46 +583,48 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
itr->i--;
}
- b->n_keys--;
- pmap_del(uint64_t)(b->id2node, id);
-
// 5.
bool itr_dirty = false;
int rlvl = itr->lvl - 1;
int *lasti = &itr->i;
+ MTPos ppos = itr->pos;
while (x != b->root) {
assert(rlvl >= 0);
- mtnode_t *p = x->parent;
+ MTNode *p = x->parent;
if (x->n >= T - 1) {
// we are done, if this node is fine the rest of the tree will be
break;
}
int pi = itr->s[rlvl].i;
assert(p->ptr[pi] == x);
+ if (pi > 0) {
+ ppos.row -= p->key[pi - 1].pos.row;
+ ppos.col = itr->s[rlvl].oldcol;
+ }
+ // ppos is now the pos of p
+
if (pi > 0 && p->ptr[pi - 1]->n > T - 1) {
*lasti += 1;
itr_dirty = true;
// steal one key from the left neighbour
- pivot_right(b, p, pi - 1);
+ pivot_right(b, ppos, p, pi - 1);
break;
} else if (pi < p->n && p->ptr[pi + 1]->n > T - 1) {
// steal one key from right neighbour
- pivot_left(b, p, pi);
+ pivot_left(b, ppos, p, pi);
break;
} else if (pi > 0) {
- // fprintf(stderr, "LEFT ");
assert(p->ptr[pi - 1]->n == T - 1);
// merge with left neighbour
*lasti += T;
x = merge_node(b, p, pi - 1);
if (lasti == &itr->i) {
// TRICKY: we merged the node the iterator was on
- itr->node = x;
+ itr->x = x;
}
itr->s[rlvl].i--;
itr_dirty = true;
} else {
- // fprintf(stderr, "RIGHT ");
assert(pi < p->n && p->ptr[pi + 1]->n == T - 1);
merge_node(b, p, pi);
// no iter adjustment needed
@@ -408,18 +641,18 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
itr->lvl--;
}
if (b->root->level) {
- mtnode_t *oldroot = b->root;
+ MTNode *oldroot = b->root;
b->root = b->root->ptr[0];
b->root->parent = NULL;
- xfree(oldroot);
+ marktree_free_node(b, oldroot);
} else {
// no items, nothing for iterator to point to
// not strictly needed, should handle delete right-most mark anyway
- itr->node = NULL;
+ itr->x = NULL;
}
}
- if (itr->node && itr_dirty) {
+ if (itr->x && itr_dirty) {
marktree_itr_fix_pos(b, itr);
}
@@ -435,18 +668,241 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
marktree_itr_next(b, itr);
marktree_itr_next(b, itr);
} else {
- if (itr->node && itr->i >= itr->node->n) {
+ if (itr->x && itr->i >= itr->x->n) {
// we deleted the last key of a leaf node
// go to the inner key after that.
- assert(itr->node->level == 0);
+ assert(itr->x->level == 0);
marktree_itr_next(b, itr);
}
}
+
+ return other;
}
-static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i)
+/// similar to intersect_common but modify x and y in place to retain
+/// only the items which are NOT in common
+static void intersect_merge(Intersection *restrict m, Intersection *restrict x,
+ Intersection *restrict y)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
+ size_t xi = 0, yi = 0;
+ size_t xn = 0, yn = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ // TODO(bfredl): kvi_pushp is actually quite complex, break out kvi_resize() to a function?
+ kvi_push(*m, kv_A(*x, xi));
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ kv_A(*x, xn++) = kv_A(*x, xi++);
+ } else {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ }
+ }
+
+ if (xi < kv_size(*x)) {
+ memmove(&kv_A(*x, xn), &kv_A(*x, xi), sizeof(kv_A(*x, xn)) * (kv_size(*x) - xi));
+ xn += kv_size(*x) - xi;
+ }
+ if (yi < kv_size(*y)) {
+ memmove(&kv_A(*y, yn), &kv_A(*y, yi), sizeof(kv_A(*y, yn)) * (kv_size(*y) - yi));
+ yn += kv_size(*y) - yi;
+ }
+
+ kv_size(*x) = xn;
+ kv_size(*y) = yn;
+}
+
+// w used to be a child of x but it is now a child of y, adjust intersections accordingly
+// @param[out] d are intersections which should be added to the old children of y
+static void intersect_mov(Intersection *restrict x, Intersection *restrict y,
+ Intersection *restrict w, Intersection *restrict d)
+{
+ size_t wi = 0, yi = 0;
+ size_t wn = 0, yn = 0;
+ size_t xi = 0;
+ while (wi < kv_size(*w) || xi < kv_size(*x)) {
+ if (wi < kv_size(*w) && (xi >= kv_size(*x) || kv_A(*x, xi) >= kv_A(*w, wi))) {
+ if (xi < kv_size(*x) && kv_A(*x, xi) == kv_A(*w, wi)) {
+ xi++;
+ }
+ // now w < x strictly
+ while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*w, wi)) {
+ kvi_push(*d, kv_A(*y, yi));
+ yi++;
+ }
+ if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*w, wi)) {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ wi++;
+ } else {
+ kv_A(*w, wn++) = kv_A(*w, wi++);
+ }
+ } else {
+ // x < w strictly
+ while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*x, xi)) {
+ kvi_push(*d, kv_A(*y, yi));
+ yi++;
+ }
+ if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*x, xi)) {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ xi++;
+ } else {
+ // add kv_A(x, xi) at kv_A(w, wn), pushing up wi if wi == wn
+ if (wi == wn) {
+ size_t n = kv_size(*w) - wn;
+ kvi_pushp(*w);
+ if (n > 0) {
+ memmove(&kv_A(*w, wn + 1), &kv_A(*w, wn), n * sizeof(kv_A(*w, 0)));
+ }
+ kv_A(*w, wi) = kv_A(*x, xi);
+ wn++;
+ wi++; // no need to consider the added element again
+ } else {
+ assert(wn < wi);
+ kv_A(*w, wn++) = kv_A(*x, xi);
+ }
+ xi++;
+ }
+ }
+ }
+ if (yi < kv_size(*y)) {
+ // move remaining items to d
+ size_t n = kv_size(*y) - yi; // at least one
+ kvi_ensure_more_space(*d, n);
+ memcpy(&kv_A(*d, kv_size(*d)), &kv_A(*y, yi), n * sizeof(kv_A(*d, 0)));
+ kv_size(*d) += n;
+ }
+ kv_size(*w) = wn;
+ kv_size(*y) = yn;
+}
+
+bool intersect_mov_test(const uint64_t *x, size_t nx, const uint64_t *y, size_t ny,
+ const uint64_t *win, size_t nwin, uint64_t *wout, size_t *nwout,
+ uint64_t *dout, size_t *ndout)
+{
+ // x is immutable in the context of intersect_mov. y might shrink, but we
+ // don't care about it (we get it the deleted ones in d)
+ Intersection xi = { .items = (uint64_t *)x, .size = nx };
+ Intersection yi = { .items = (uint64_t *)y, .size = ny };
+
+ Intersection w;
+ kvi_init(w);
+ for (size_t i = 0; i < nwin; i++) {
+ kvi_push(w, win[i]);
+ }
+ Intersection d;
+ kvi_init(d);
+
+ intersect_mov(&xi, &yi, &w, &d);
+
+ if (w.size > *nwout || d.size > *ndout) {
+ return false;
+ }
+
+ memcpy(wout, w.items, sizeof(w.items[0]) * w.size);
+ *nwout = w.size;
+
+ memcpy(dout, d.items, sizeof(d.items[0]) * d.size);
+ *ndout = d.size;
+
+ return true;
+}
+
+/// intersection: i = x & y
+static void intersect_common(Intersection *i, Intersection *x, Intersection *y)
+{
+ size_t xi = 0, yi = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ kvi_push(*i, kv_A(*x, xi));
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ xi++;
+ } else {
+ yi++;
+ }
+ }
+}
+
+// inplace union: x |= y
+static void intersect_add(Intersection *x, Intersection *y)
+{
+ size_t xi = 0, yi = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ xi++;
+ yi++;
+ } else if (kv_A(*y, yi) < kv_A(*x, xi)) {
+ size_t n = kv_size(*x) - xi; // at least one
+ kvi_pushp(*x);
+ memmove(&kv_A(*x, xi + 1), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0)));
+ kv_A(*x, xi) = kv_A(*y, yi);
+ xi++; // newly added element
+ yi++;
+ } else {
+ xi++;
+ }
+ }
+ if (yi < kv_size(*y)) {
+ size_t n = kv_size(*y) - yi; // at least one
+ kvi_ensure_more_space(*x, n);
+ memcpy(&kv_A(*x, kv_size(*x)), &kv_A(*y, yi), n * sizeof(kv_A(*x, 0)));
+ kv_size(*x) += n;
+ }
+}
+
+// inplace asymmetric difference: x &= ~y
+static void intersect_sub(Intersection *restrict x, Intersection *restrict y)
+{
+ size_t xi = 0, yi = 0;
+ size_t xn = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ kv_A(*x, xn++) = kv_A(*x, xi++);
+ } else {
+ yi++;
+ }
+ }
+ if (xi < kv_size(*x)) {
+ size_t n = kv_size(*x) - xi;
+ if (xn < xi) { // otherwise xn == xi
+ memmove(&kv_A(*x, xn), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0)));
+ }
+ xn += n;
+ }
+ kv_size(*x) = xn;
+}
+
+/// x is a node which shrunk, or the half of a split
+///
+/// this means that intervals which previously intersected all the (current)
+/// child nodes, now instead intersects `x` itself.
+static void bubble_up(MTNode *x)
+{
+ Intersection xi;
+ kvi_init(xi);
+ // due to invariants, the largest subset of _all_ subnodes is the intersection
+ // between the first and the last
+ intersect_common(&xi, &x->ptr[0]->intersect, &x->ptr[x->n]->intersect);
+ if (kv_size(xi)) {
+ for (int i = 0; i < x->n + 1; i++) {
+ intersect_sub(&x->ptr[i]->intersect, &xi);
+ }
+ intersect_add(&x->intersect, &xi);
+ }
+ kvi_destroy(xi);
+}
+
+static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
+{
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
+ Intersection m;
+ kvi_init(m);
+
+ intersect_merge(&m, &x->intersect, &y->intersect);
x->key[x->n] = p->key[i];
refkey(b, x, x->n);
@@ -454,35 +910,78 @@ static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i)
relative(p->key[i - 1].pos, &x->key[x->n].pos);
}
- memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(mtkey_t));
+ memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey));
for (int k = 0; k < y->n; k++) {
refkey(b, x, x->n + 1 + k);
unrelative(x->key[x->n].pos, &x->key[x->n + 1 + k].pos);
}
if (x->level) {
- memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *));
- for (int k = 0; k < y->n + 1; k++) {
- x->ptr[x->n + k + 1]->parent = x;
+ // bubble down: ranges that intersected old-x but not old-y or vice versa
+ // must be moved to their respective children
+ memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
+ for (int k = 0; k < x->n + 1; k++) {
+ // TODO(bfredl): dedicated impl for "Z |= Y"
+ for (size_t idx = 0; idx < kv_size(x->intersect); idx++) {
+ intersect_node(b, x->ptr[k], kv_A(x->intersect, idx));
+ }
+ }
+ for (int ky = 0; ky < y->n + 1; ky++) {
+ int k = x->n + ky + 1;
+ // nodes that used to be in y, now the second half of x
+ x->ptr[k]->parent = x;
+ x->ptr[k]->p_idx = (int16_t)k;
+ // TODO(bfredl): dedicated impl for "Z |= X"
+ for (size_t idx = 0; idx < kv_size(y->intersect); idx++) {
+ intersect_node(b, x->ptr[k], kv_A(y->intersect, idx));
+ }
}
}
x->n += y->n + 1;
- memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(mtkey_t));
+ memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(MTKey));
memmove(&p->ptr[i + 1], &p->ptr[i + 2],
- (size_t)(p->n - i - 1) * sizeof(mtkey_t *));
+ (size_t)(p->n - i - 1) * sizeof(MTKey *));
+ for (int j = i + 1; j < p->n; j++) { // note: one has been deleted
+ p->ptr[j]->p_idx = (int16_t)j;
+ }
p->n--;
- xfree(y);
- b->n_nodes--;
+ marktree_free_node(b, y);
+
+ kvi_destroy(x->intersect);
+
+ // move of a kvec_withinit_t, messy!
+ // TODO(bfredl): special case version of intersect_merge(x_out, x_in_m_out, y) to avoid this
+ kvi_move(&x->intersect, &m);
+
return x;
}
+/// @param dest is overwritten (assumed to already been freed/moved)
+/// @param src consumed (don't free or use)
+void kvi_move(Intersection *dest, Intersection *src)
+{
+ dest->size = src->size;
+ dest->capacity = src->capacity;
+ if (src->items == src->init_array) {
+ memcpy(dest->init_array, src->init_array, src->size * sizeof(*src->init_array));
+ dest->items = dest->init_array;
+ } else {
+ dest->items = src->items;
+ }
+}
+
// TODO(bfredl): as a potential "micro" optimization, pivoting should balance
// the two nodes instead of stealing just one key
-static void pivot_right(MarkTree *b, mtnode_t *p, int i)
+// x_pos is the absolute position of the key just before x (or a dummy key strictly less than any
+// key inside x, if x is the first leaf)
+static void pivot_right(MarkTree *b, MTPos p_pos, MTNode *p, const int i)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
- memmove(&y->key[1], y->key, (size_t)y->n * sizeof(mtkey_t));
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
+ memmove(&y->key[1], y->key, (size_t)y->n * sizeof(MTKey));
if (y->level) {
- memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *));
+ memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
+ for (int j = 1; j < y->n + 2; j++) {
+ y->ptr[j]->p_idx = (int16_t)j;
+ }
}
y->key[0] = p->key[i];
refkey(b, y, 0);
@@ -491,6 +990,7 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i)
if (x->level) {
y->ptr[0] = x->ptr[x->n];
y->ptr[0]->parent = y;
+ y->ptr[0]->p_idx = 0;
}
x->n--;
y->n++;
@@ -501,11 +1001,46 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i)
for (int k = 1; k < y->n; k++) {
unrelative(y->key[0].pos, &y->key[k].pos);
}
+
+ // repair intersections of x
+ if (x->level) {
+ // handle y and first new y->ptr[0]
+ Intersection d;
+ kvi_init(d);
+ // y->ptr[0] was moved from x to y
+ // adjust y->ptr[0] for a difference between the parents
+ // in addition, this might cause some intersection of the old y
+ // to bubble down to the old children of y (if y->ptr[0] wasn't intersected)
+ intersect_mov(&x->intersect, &y->intersect, &y->ptr[0]->intersect, &d);
+ if (kv_size(d)) {
+ for (int yi = 1; yi < y->n + 1; yi++) {
+ intersect_add(&y->ptr[yi]->intersect, &d);
+ }
+ }
+ kvi_destroy(d);
+
+ bubble_up(x);
+ } else {
+ // if the last element of x used to be an end node, check if it now covers all of x
+ if (mt_end(p->key[i])) {
+ uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index
+ uint64_t start_id = mt_lookup_key_side(p->key[i], false);
+ uint64_t pi_start = pseudo_index_for_id(b, start_id, true);
+ if (pi_start > 0 && pi_start < pi) {
+ intersect_node(b, x, start_id);
+ }
+ }
+
+ if (mt_start(y->key[0])) {
+ // no need for a check, just delet it if it was there
+ unintersect_node(b, y, mt_lookup_key(y->key[0]), false);
+ }
+ }
}
-static void pivot_left(MarkTree *b, mtnode_t *p, int i)
+static void pivot_left(MarkTree *b, MTPos p_pos, MTNode *p, int i)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
// reverse from how we "always" do it. but pivot_left
// is just the inverse of pivot_right, so reverse it literally.
@@ -524,97 +1059,191 @@ static void pivot_left(MarkTree *b, mtnode_t *p, int i)
if (x->level) {
x->ptr[x->n + 1] = y->ptr[0];
x->ptr[x->n + 1]->parent = x;
+ x->ptr[x->n + 1]->p_idx = (int16_t)(x->n + 1);
}
- memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(mtkey_t));
+ memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(MTKey));
if (y->level) {
- memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(mtnode_t *));
+ memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(MTNode *));
+ for (int j = 0; j < y->n; j++) { // note: last item deleted
+ y->ptr[j]->p_idx = (int16_t)j;
+ }
}
x->n++;
y->n--;
+
+ // repair intersections of x,y
+ if (x->level) {
+ // handle y and first new y->ptr[0]
+ Intersection d;
+ kvi_init(d);
+ // x->ptr[x->n] was moved from y to x
+ // adjust x->ptr[x->n] for a difference between the parents
+ // in addition, this might cause some intersection of the old x
+ // to bubble down to the old children of x (if x->ptr[n] wasn't intersected)
+ intersect_mov(&y->intersect, &x->intersect, &x->ptr[x->n]->intersect, &d);
+ if (kv_size(d)) {
+ for (int xi = 0; xi < x->n; xi++) { // ptr[x->n| deliberately skipped
+ intersect_add(&x->ptr[xi]->intersect, &d);
+ }
+ }
+ kvi_destroy(d);
+
+ bubble_up(y);
+ } else {
+ // if the first element of y used to be an start node, check if it now covers all of y
+ if (mt_start(p->key[i])) {
+ uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index
+
+ uint64_t end_id = mt_lookup_key_side(p->key[i], true);
+ uint64_t pi_end = pseudo_index_for_id(b, end_id, true);
+
+ if (pi_end > pi) {
+ intersect_node(b, y, mt_lookup_key(p->key[i]));
+ }
+ }
+
+ if (mt_end(x->key[x->n - 1])) {
+ // no need for a check, just delet it if it was there
+ unintersect_node(b, x, mt_lookup_key_side(x->key[x->n - 1], false), false);
+ }
+ }
}
/// frees all mem, resets tree to valid empty state
void marktree_clear(MarkTree *b)
{
if (b->root) {
- marktree_free_node(b->root);
+ marktree_free_subtree(b, b->root);
b->root = NULL;
}
- if (b->id2node->table.keys) {
- pmap_destroy(uint64_t)(b->id2node);
- pmap_init(uint64_t, b->id2node);
- }
+ map_destroy(uint64_t, b->id2node);
+ *b->id2node = (PMap(uint64_t)) MAP_INIT;
b->n_keys = 0;
- b->n_nodes = 0;
+ assert(b->n_nodes == 0);
}
-void marktree_free_node(mtnode_t *x)
+void marktree_free_subtree(MarkTree *b, MTNode *x)
{
if (x->level) {
for (int i = 0; i < x->n + 1; i++) {
- marktree_free_node(x->ptr[i]);
+ marktree_free_subtree(b, x->ptr[i]);
}
}
- xfree(x);
+ marktree_free_node(b, x);
}
-/// NB: caller must check not pair!
-void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key)
+static void marktree_free_node(MarkTree *b, MTNode *x)
{
- // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms
- // once we upgrade to a non-broken version of gcc in functionaltest-lua CI
- rawkey(itr).flags = (uint16_t)(rawkey(itr).flags & (uint16_t) ~MT_FLAG_DECOR_MASK);
- rawkey(itr).flags = (uint16_t)(rawkey(itr).flags
- | (uint16_t)(decor_level << MT_FLAG_DECOR_OFFSET)
- | (uint16_t)(key.flags & MT_FLAG_DECOR_MASK));
- rawkey(itr).decor_full = key.decor_full;
- rawkey(itr).hl_id = key.hl_id;
- rawkey(itr).priority = key.priority;
+ kvi_destroy(x->intersect);
+ xfree(x);
+ b->n_nodes--;
}
+/// @param itr iterator is invalid after call
void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col)
{
- mtkey_t key = rawkey(itr);
- // TODO(bfredl): optimize when moving a mark within a leaf without moving it
- // across neighbours!
- marktree_del_itr(b, itr, false);
- key.pos = (mtpos_t){ row, col };
+ MTKey key = rawkey(itr);
+ MTNode *x = itr->x;
+ if (!x->level) {
+ bool internal = false;
+ MTPos newpos = MTPos(row, col);
+ if (x->parent != NULL) {
+ // strictly _after_ key before `x`
+ // (not optimal when x is very first leaf of the entire tree, but that's fine)
+ if (pos_less(itr->pos, newpos)) {
+ relative(itr->pos, &newpos);
+
+ // strictly before the end of x. (this could be made sharper by
+ // finding the internal key just after x, but meh)
+ if (pos_less(newpos, x->key[x->n - 1].pos)) {
+ internal = true;
+ }
+ }
+ } else {
+ // tree is one node. newpos thus is already "relative" itr->pos
+ internal = true;
+ }
+
+ if (internal) {
+ if (key.pos.row == newpos.row && key.pos.col == newpos.col) {
+ return;
+ }
+ key.pos = newpos;
+ bool match;
+ // tricky: could minimize movement in either direction better
+ int new_i = marktree_getp_aux(x, key, &match);
+ if (!match) {
+ new_i++;
+ }
+ if (new_i == itr->i) {
+ x->key[itr->i].pos = newpos;
+ } else if (new_i < itr->i) {
+ memmove(&x->key[new_i + 1], &x->key[new_i], sizeof(MTKey) * (size_t)(itr->i - new_i));
+ x->key[new_i] = key;
+ } else if (new_i > itr->i) {
+ memmove(&x->key[itr->i], &x->key[itr->i + 1], sizeof(MTKey) * (size_t)(new_i - itr->i - 1));
+ x->key[new_i - 1] = key;
+ }
+ return;
+ }
+ }
+ uint64_t other = marktree_del_itr(b, itr, false);
+ key.pos = (MTPos){ row, col };
marktree_put_key(b, key);
- itr->node = NULL; // itr might become invalid by put
+
+ if (other) {
+ marktree_restore_pair(b, key);
+ }
+ itr->x = NULL; // itr might become invalid by put
+}
+
+void marktree_restore_pair(MarkTree *b, MTKey key)
+{
+ MarkTreeIter itr[1];
+ MarkTreeIter end_itr[1];
+ marktree_lookup(b, mt_lookup_key_side(key, false), itr);
+ marktree_lookup(b, mt_lookup_key_side(key, true), end_itr);
+ if (!itr->x || !end_itr->x) {
+ // this could happen if the other end is waiting to be restored later
+ // this function will be called again for the other end.
+ return;
+ }
+ rawkey(itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED;
+ rawkey(end_itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED;
+
+ marktree_intersect_pair(b, mt_lookup_key_side(key, false), itr, end_itr, false);
}
// itr functions
-// TODO(bfredl): static inline?
bool marktree_itr_get(MarkTree *b, int32_t row, int col, MarkTreeIter *itr)
{
- return marktree_itr_get_ext(b, (mtpos_t){ row, col },
- itr, false, false, NULL);
+ return marktree_itr_get_ext(b, MTPos(row, col), itr, false, false, NULL);
}
-bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity,
- mtpos_t *oldbase)
+bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bool gravity,
+ MTPos *oldbase)
{
if (b->n_keys == 0) {
- itr->node = NULL;
+ itr->x = NULL;
return false;
}
- mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
+ MTKey k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
if (last && !gravity) {
k.flags = MT_FLAG_LAST;
}
- itr->pos = (mtpos_t){ 0, 0 };
- itr->node = b->root;
+ itr->pos = (MTPos){ 0, 0 };
+ itr->x = b->root;
itr->lvl = 0;
if (oldbase) {
oldbase[itr->lvl] = itr->pos;
}
while (true) {
- itr->i = marktree_getp_aux(itr->node, k, 0) + 1;
+ itr->i = marktree_getp_aux(itr->x, k, 0) + 1;
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
break;
}
@@ -622,10 +1251,10 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
itr->s[itr->lvl].oldcol = itr->pos.col;
if (itr->i > 0) {
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
- relative(itr->node->key[itr->i - 1].pos, &k.pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
+ relative(itr->x->key[itr->i - 1].pos, &k.pos);
}
- itr->node = itr->node->ptr[itr->i];
+ itr->x = itr->x->ptr[itr->i];
itr->lvl++;
if (oldbase) {
oldbase[itr->lvl] = itr->pos;
@@ -634,7 +1263,7 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
if (last) {
return marktree_itr_prev(b, itr);
- } else if (itr->i >= itr->node->n) {
+ } else if (itr->i >= itr->x->n) {
return marktree_itr_next(b, itr);
}
return true;
@@ -642,19 +1271,20 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr)
{
- itr->node = b->root;
if (b->n_keys == 0) {
+ itr->x = NULL;
return false;
}
+ itr->x = b->root;
itr->i = 0;
itr->lvl = 0;
- itr->pos = (mtpos_t){ 0, 0 };
- while (itr->node->level > 0) {
+ itr->pos = MTPos(0, 0);
+ while (itr->x->level > 0) {
itr->s[itr->lvl].i = 0;
itr->s[itr->lvl].oldcol = 0;
itr->lvl++;
- itr->node = itr->node->ptr[0];
+ itr->x = itr->x->ptr[0];
}
return true;
}
@@ -663,16 +1293,16 @@ bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr)
int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
{
if (b->n_keys == 0) {
- itr->node = NULL;
+ itr->x = NULL;
return false;
}
- itr->pos = (mtpos_t){ 0, 0 };
- itr->node = b->root;
+ itr->pos = MTPos(0, 0);
+ itr->x = b->root;
itr->lvl = 0;
while (true) {
- itr->i = itr->node->n;
+ itr->i = itr->x->n;
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
break;
}
@@ -680,63 +1310,71 @@ int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
itr->s[itr->lvl].oldcol = itr->pos.col;
assert(itr->i > 0);
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
- itr->node = itr->node->ptr[itr->i];
+ itr->x = itr->x->ptr[itr->i];
itr->lvl++;
}
itr->i--;
return true;
}
-// TODO(bfredl): static inline
bool marktree_itr_next(MarkTree *b, MarkTreeIter *itr)
{
- return marktree_itr_next_skip(b, itr, false, NULL);
+ return marktree_itr_next_skip(b, itr, false, false, NULL);
}
-static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mtpos_t oldbase[])
+static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, bool preload,
+ MTPos oldbase[])
{
- if (!itr->node) {
+ if (!itr->x) {
return false;
}
itr->i++;
- if (itr->node->level == 0 || skip) {
- if (itr->i < itr->node->n) {
+ if (itr->x->level == 0 || skip) {
+ if (preload && itr->x->level == 0 && skip) {
+ // skip rest of this leaf node
+ itr->i = itr->x->n;
+ } else if (itr->i < itr->x->n) {
// TODO(bfredl): this is the common case,
// and could be handled by inline wrapper
return true;
}
// we ran out of non-internal keys. Go up until we find an internal key
- while (itr->i >= itr->node->n) {
- itr->node = itr->node->parent;
- if (itr->node == NULL) {
+ while (itr->i >= itr->x->n) {
+ itr->x = itr->x->parent;
+ if (itr->x == NULL) {
return false;
}
itr->lvl--;
itr->i = itr->s[itr->lvl].i;
if (itr->i > 0) {
- itr->pos.row -= itr->node->key[itr->i - 1].pos.row;
+ itr->pos.row -= itr->x->key[itr->i - 1].pos.row;
itr->pos.col = itr->s[itr->lvl].oldcol;
}
}
} else {
// we stood at an "internal" key. Go down to the first non-internal
// key after it.
- while (itr->node->level > 0) {
+ while (itr->x->level > 0) {
// internal key, there is always a child after
if (itr->i > 0) {
itr->s[itr->lvl].oldcol = itr->pos.col;
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
}
if (oldbase && itr->i == 0) {
oldbase[itr->lvl + 1] = oldbase[itr->lvl];
}
itr->s[itr->lvl].i = itr->i;
- assert(itr->node->ptr[itr->i]->parent == itr->node);
- itr->node = itr->node->ptr[itr->i];
- itr->i = 0;
+ assert(itr->x->ptr[itr->i]->parent == itr->x);
itr->lvl++;
+ itr->x = itr->x->ptr[itr->i];
+ if (preload && itr->x->level) {
+ itr->i = -1;
+ break;
+ } else {
+ itr->i = 0;
+ }
}
}
return true;
@@ -744,10 +1382,10 @@ static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mt
bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
{
- if (!itr->node) {
+ if (!itr->x) {
return false;
}
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
itr->i--;
if (itr->i >= 0) {
// TODO(bfredl): this is the common case,
@@ -756,30 +1394,30 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
}
// we ran out of non-internal keys. Go up until we find a non-internal key
while (itr->i < 0) {
- itr->node = itr->node->parent;
- if (itr->node == NULL) {
+ itr->x = itr->x->parent;
+ if (itr->x == NULL) {
return false;
}
itr->lvl--;
itr->i = itr->s[itr->lvl].i - 1;
if (itr->i >= 0) {
- itr->pos.row -= itr->node->key[itr->i].pos.row;
+ itr->pos.row -= itr->x->key[itr->i].pos.row;
itr->pos.col = itr->s[itr->lvl].oldcol;
}
}
} else {
// we stood at an "internal" key. Go down to the last non-internal
// key before it.
- while (itr->node->level > 0) {
+ while (itr->x->level > 0) {
// internal key, there is always a child before
if (itr->i > 0) {
itr->s[itr->lvl].oldcol = itr->pos.col;
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
}
itr->s[itr->lvl].i = itr->i;
- assert(itr->node->ptr[itr->i]->parent == itr->node);
- itr->node = itr->node->ptr[itr->i];
- itr->i = itr->node->n;
+ assert(itr->x->ptr[itr->i]->parent == itr->x);
+ itr->x = itr->x->ptr[itr->i];
+ itr->i = itr->x->n;
itr->lvl++;
}
itr->i--;
@@ -787,33 +1425,22 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
return true;
}
-void marktree_itr_rewind(MarkTree *b, MarkTreeIter *itr)
-{
- if (!itr->node) {
- return;
- }
- if (itr->node->level) {
- marktree_itr_prev(b, itr);
- }
- itr->i = 0;
-}
-
bool marktree_itr_node_done(MarkTreeIter *itr)
{
- return !itr->node || itr->i == itr->node->n - 1;
+ return !itr->x || itr->i == itr->x->n - 1;
}
-mtpos_t marktree_itr_pos(MarkTreeIter *itr)
+MTPos marktree_itr_pos(MarkTreeIter *itr)
{
- mtpos_t pos = rawkey(itr).pos;
+ MTPos pos = rawkey(itr).pos;
unrelative(itr->pos, &pos);
return pos;
}
-mtkey_t marktree_itr_current(MarkTreeIter *itr)
+MTKey marktree_itr_current(MarkTreeIter *itr)
{
- if (itr->node) {
- mtkey_t key = rawkey(itr);
+ if (itr->x) {
+ MTKey key = rawkey(itr);
key.pos = marktree_itr_pos(itr);
return key;
}
@@ -825,47 +1452,193 @@ static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2)
return (&rawkey(itr1) == &rawkey(itr2));
}
-static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2)
+/// Get all marks which overlaps the position (row,col)
+///
+/// After calling this function, use marktree_itr_step_overlap to step through
+/// one overlapping mark at a time, until it returns false
+///
+/// NOTE: It's possible to get all marks which overlaps a region (row,col) to (row_end,col_end)
+/// To do this, first call marktree_itr_get_overlap with the start position and
+/// keep calling marktree_itr_step_overlap until it returns false.
+/// After this, as a second loop, keep calling the marktree_itr_next() until
+/// the iterator is invalid or reaches past (row_end, col_end). In this loop,
+/// consider all "start" marks (and unpaired marks if relevant), but skip over
+/// all "end" marks, using mt_end(mark).
+///
+/// @return false if we already know no marks can be found
+/// even if "true" the first call to marktree_itr_step_overlap
+/// could return false
+bool marktree_itr_get_overlap(MarkTree *b, int row, int col, MarkTreeIter *itr)
+{
+ if (b->n_keys == 0) {
+ itr->x = NULL;
+ return false;
+ }
+
+ itr->x = b->root;
+ itr->i = -1;
+ itr->lvl = 0;
+ itr->pos = MTPos(0, 0);
+ itr->intersect_pos = MTPos(row, col);
+ // intersect_pos but will be adjusted relative itr->x
+ itr->intersect_pos_x = MTPos(row, col);
+ itr->intersect_idx = 0;
+ return true;
+}
+
+/// Step through all overlapping pairs at a position.
+///
+/// This function must only be used with an iterator from |marktree_itr_step_overlap|
+///
+/// @return true if a valid pair was found (returned as `pair`)
+/// When all overlapping mark pairs have been found, false will be returned. `itr`
+/// is then valid as an ordinary iterator at the (row, col) position specified in
+/// marktree_itr_step_overlap
+bool marktree_itr_step_overlap(MarkTree *b, MarkTreeIter *itr, MTPair *pair)
+{
+ // phase one: we start at the root node and step inwards towards itr->intersect_pos
+ // (the position queried in marktree_itr_get_overlap)
+ //
+ // For each node (ancestor node to the node containing the sought position)
+ // we return all intersecting intervals, one at a time
+ while (itr->i == -1) {
+ if (itr->intersect_idx < kv_size(itr->x->intersect)) {
+ uint64_t id = kv_A(itr->x->intersect, itr->intersect_idx++);
+ *pair = mtpair_from(marktree_lookup(b, id, NULL),
+ marktree_lookup(b, id|MARKTREE_END_FLAG, NULL));
+ return true;
+ }
+
+ if (itr->x->level == 0) {
+ itr->s[itr->lvl].i = itr->i = 0;
+ break;
+ }
+
+ MTKey k = { .pos = itr->intersect_pos_x, .flags = 0 };
+ itr->i = marktree_getp_aux(itr->x, k, 0) + 1;
+
+ itr->s[itr->lvl].i = itr->i;
+ itr->s[itr->lvl].oldcol = itr->pos.col;
+
+ if (itr->i > 0) {
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
+ relative(itr->x->key[itr->i - 1].pos, &itr->intersect_pos_x);
+ }
+ itr->x = itr->x->ptr[itr->i];
+ itr->lvl++;
+ itr->i = -1;
+ itr->intersect_idx = 0;
+ }
+
+ // phase two: we now need to handle the node found at itr->intersect_pos
+ // first consider all start nodes in the node before this position.
+ while (itr->i < itr->x->n && pos_less(rawkey(itr).pos, itr->intersect_pos_x)) {
+ MTKey k = itr->x->key[itr->i++];
+ itr->s[itr->lvl].i = itr->i;
+ if (mt_start(k)) {
+ MTKey end = marktree_lookup(b, mt_lookup_id(k.ns, k.id, true), NULL);
+ if (pos_less(end.pos, itr->intersect_pos)) {
+ continue;
+ }
+
+ unrelative(itr->pos, &k.pos);
+ *pair = mtpair_from(k, end);
+ return true; // it's a start!
+ }
+ }
+
+ // phase 2B: We also need to step to the end of this node and consider all end marks, which
+ // might end an interval overlapping itr->intersect_pos
+ while (itr->i < itr->x->n) {
+ MTKey k = itr->x->key[itr->i++];
+ if (mt_end(k)) {
+ uint64_t id = mt_lookup_id(k.ns, k.id, false);
+ if (id2node(b, id) == itr->x) {
+ continue;
+ }
+ unrelative(itr->pos, &k.pos);
+ MTKey start = marktree_lookup(b, id, NULL);
+ if (pos_less(itr->intersect_pos, start.pos)) {
+ continue;
+ }
+ *pair = mtpair_from(start, k);
+ return true; // end of a range which began before us!
+ }
+ }
+
+ // when returning false, get back to the queried position, to ensure the caller
+ // can keep using it as an ordinary iterator at the queried position. The docstring
+ // for marktree_itr_get_overlap explains how this is useful.
+ itr->i = itr->s[itr->lvl].i;
+ assert(itr->i >= 0);
+ if (itr->i >= itr->x->n) {
+ marktree_itr_next(b, itr);
+ }
+
+ // either on or after the intersected position, bail out
+ return false;
+}
+
+static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, DamageList *damage)
{
- mtkey_t key1 = rawkey(itr1);
- mtkey_t key2 = rawkey(itr2);
+ if (itr1->x != itr2->x) {
+ if (mt_paired(rawkey(itr1))) {
+ kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr1)), itr1->x, itr2->x,
+ itr1->i, itr2->i }));
+ }
+ if (mt_paired(rawkey(itr2))) {
+ kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr2)), itr2->x, itr1->x,
+ itr2->i, itr1->i }));
+ }
+ }
+
+ MTKey key1 = rawkey(itr1);
+ MTKey key2 = rawkey(itr2);
rawkey(itr1) = key2;
rawkey(itr1).pos = key1.pos;
rawkey(itr2) = key1;
rawkey(itr2).pos = key2.pos;
+ refkey(b, itr1->x, itr1->i);
+ refkey(b, itr2->x, itr2->i);
+}
+
+static int damage_cmp(const void *s1, const void *s2)
+{
+ Damage *d1 = (Damage *)s1, *d2 = (Damage *)s2;
+ assert(d1->id != d2->id);
+ return d1->id > d2->id ? 1 : -1;
}
bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_extent_line,
int old_extent_col, int new_extent_line, int new_extent_col)
{
- mtpos_t start = { start_line, start_col };
- mtpos_t old_extent = { old_extent_line, old_extent_col };
- mtpos_t new_extent = { new_extent_line, new_extent_col };
+ MTPos start = { start_line, start_col };
+ MTPos old_extent = { old_extent_line, old_extent_col };
+ MTPos new_extent = { new_extent_line, new_extent_col };
bool may_delete = (old_extent.row != 0 || old_extent.col != 0);
bool same_line = old_extent.row == 0 && new_extent.row == 0;
unrelative(start, &old_extent);
unrelative(start, &new_extent);
- MarkTreeIter itr[1] = { 0 };
- MarkTreeIter enditr[1] = { 0 };
+ MarkTreeIter itr[1] = { 0 }, enditr[1] = { 0 };
- mtpos_t oldbase[MT_MAX_DEPTH] = { 0 };
+ MTPos oldbase[MT_MAX_DEPTH] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, oldbase);
- if (!itr->node) {
+ if (!itr->x) {
// den e FÄRDIG
return false;
}
- mtpos_t delta = { new_extent.row - old_extent.row,
- new_extent.col - old_extent.col };
+ MTPos delta = { new_extent.row - old_extent.row,
+ new_extent.col - old_extent.col };
if (may_delete) {
- mtpos_t ipos = marktree_itr_pos(itr);
+ MTPos ipos = marktree_itr_pos(itr);
if (!pos_leq(old_extent, ipos)
|| (old_extent.row == ipos.row && old_extent.col == ipos.col
&& !mt_right(rawkey(itr)))) {
marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL);
- assert(enditr->node);
+ assert(enditr->x);
// "assert" (itr <= enditr)
} else {
may_delete = false;
@@ -874,14 +1647,16 @@ bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_ext
bool past_right = false;
bool moved = false;
+ DamageList damage;
+ kvi_init(damage);
// Follow the general strategy of messing things up and fix them later
// "oldbase" carries the information needed to calculate old position of
// children.
if (may_delete) {
- while (itr->node && !past_right) {
- mtpos_t loc_start = start;
- mtpos_t loc_old = old_extent;
+ while (itr->x && !past_right) {
+ MTPos loc_start = start;
+ MTPos loc_old = old_extent;
relative(itr->pos, &loc_start);
relative(oldbase[itr->lvl], &loc_old);
@@ -899,9 +1674,7 @@ continue_same_node:
marktree_itr_prev(b, enditr);
}
if (!mt_right(rawkey(enditr))) {
- itr_swap(itr, enditr);
- refkey(b, itr->node, itr->i);
- refkey(b, enditr->node, enditr->i);
+ swap_keys(b, itr, enditr, &damage);
} else {
past_right = true; // NOLINT
(void)past_right;
@@ -915,14 +1688,14 @@ continue_same_node:
}
moved = true;
- if (itr->node->level) {
+ if (itr->x->level) {
oldbase[itr->lvl + 1] = rawkey(itr).pos;
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
rawkey(itr).pos = loc_start;
- marktree_itr_next_skip(b, itr, false, oldbase);
+ marktree_itr_next_skip(b, itr, false, false, oldbase);
} else {
rawkey(itr).pos = loc_start;
- if (itr->i < itr->node->n - 1) {
+ if (itr->i < itr->x->n - 1) {
itr->i++;
if (!past_right) {
goto continue_same_node;
@@ -932,10 +1705,10 @@ continue_same_node:
}
}
}
- while (itr->node) {
- mtpos_t loc_new = new_extent;
+ while (itr->x) {
+ MTPos loc_new = new_extent;
relative(itr->pos, &loc_new);
- mtpos_t limit = old_extent;
+ MTPos limit = old_extent;
relative(oldbase[itr->lvl], &limit);
@@ -945,16 +1718,16 @@ past_continue_same_node:
break;
}
- mtpos_t oldpos = rawkey(itr).pos;
+ MTPos oldpos = rawkey(itr).pos;
rawkey(itr).pos = loc_new;
moved = true;
- if (itr->node->level) {
+ if (itr->x->level) {
oldbase[itr->lvl + 1] = oldpos;
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
- marktree_itr_next_skip(b, itr, false, oldbase);
+ marktree_itr_next_skip(b, itr, false, false, oldbase);
} else {
- if (itr->i < itr->node->n - 1) {
+ if (itr->i < itr->x->n - 1) {
itr->i++;
goto past_continue_same_node;
} else {
@@ -964,7 +1737,7 @@ past_continue_same_node:
}
}
- while (itr->node) {
+ while (itr->x) {
unrelative(oldbase[itr->lvl], &rawkey(itr).pos);
int realrow = rawkey(itr).pos.row;
assert(realrow >= old_extent.row);
@@ -972,7 +1745,6 @@ past_continue_same_node:
if (realrow == old_extent.row) {
if (delta.col) {
rawkey(itr).pos.col += delta.col;
- moved = true;
}
} else {
if (same_line) {
@@ -988,22 +1760,79 @@ past_continue_same_node:
if (done) {
break;
}
- marktree_itr_next_skip(b, itr, true, NULL);
+ marktree_itr_next_skip(b, itr, true, false, NULL);
+ }
+
+ if (kv_size(damage)) {
+ // TODO(bfredl): a full sort is not really needed. we just need a "start" node to find
+ // its corresponding "end" node. Set up some dedicated hash for this later (c.f. the
+ // "grow only" variant of khash_t branch)
+ qsort((void *)&kv_A(damage, 0), kv_size(damage), sizeof(kv_A(damage, 0)),
+ damage_cmp);
+
+ for (size_t i = 0; i < kv_size(damage); i++) {
+ Damage d = kv_A(damage, i);
+ assert(i == 0 || d.id > kv_A(damage, i - 1).id);
+ if (!(d.id & MARKTREE_END_FLAG)) { // start
+ if (i + 1 < kv_size(damage) && kv_A(damage, i + 1).id == (d.id | MARKTREE_END_FLAG)) {
+ Damage d2 = kv_A(damage, i + 1);
+
+ // pair
+ marktree_itr_set_node(b, itr, d.old, d.old_i);
+ marktree_itr_set_node(b, enditr, d2.old, d2.old_i);
+ marktree_intersect_pair(b, d.id, itr, enditr, true);
+ marktree_itr_set_node(b, itr, d.new, d.new_i);
+ marktree_itr_set_node(b, enditr, d2.new, d2.new_i);
+ marktree_intersect_pair(b, d.id, itr, enditr, false);
+
+ i++; // consume two items
+ continue;
+ }
+
+ // d is lone start, end didn't move
+ MarkTreeIter endpos[1];
+ marktree_lookup(b, d.id | MARKTREE_END_FLAG, endpos);
+ if (endpos->x) {
+ marktree_itr_set_node(b, itr, d.old, d.old_i);
+ *enditr = *endpos;
+ marktree_intersect_pair(b, d.id, itr, enditr, true);
+ marktree_itr_set_node(b, itr, d.new, d.new_i);
+ *enditr = *endpos;
+ marktree_intersect_pair(b, d.id, itr, enditr, false);
+ }
+ } else {
+ // d is lone end, start didn't move
+ MarkTreeIter startpos[1];
+ uint64_t start_id = d.id & ~MARKTREE_END_FLAG;
+
+ marktree_lookup(b, start_id, startpos);
+ if (startpos->x) {
+ *itr = *startpos;
+ marktree_itr_set_node(b, enditr, d.old, d.old_i);
+ marktree_intersect_pair(b, start_id, itr, enditr, true);
+ *itr = *startpos;
+ marktree_itr_set_node(b, enditr, d.new, d.new_i);
+ marktree_intersect_pair(b, start_id, itr, enditr, false);
+ }
+ }
+ }
}
+ kvi_destroy(damage);
+
return moved;
}
void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int extent_row,
colnr_T extent_col, int new_row, colnr_T new_col)
{
- mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col };
- mtpos_t end = size;
+ MTPos start = { start_row, start_col }, size = { extent_row, extent_col };
+ MTPos end = size;
unrelative(start, &end);
MarkTreeIter itr[1] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, NULL);
- kvec_t(mtkey_t) saved = KV_INITIAL_VALUE;
- while (itr->node) {
- mtkey_t k = marktree_itr_current(itr);
+ kvec_t(MTKey) saved = KV_INITIAL_VALUE;
+ while (itr->x) {
+ MTKey k = marktree_itr_current(itr);
if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col
&& mt_right(k))) {
break;
@@ -1014,57 +1843,101 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
}
marktree_splice(b, start.row, start.col, size.row, size.col, 0, 0);
- mtpos_t new = { new_row, new_col };
+ MTPos new = { new_row, new_col };
marktree_splice(b, new.row, new.col,
0, 0, size.row, size.col);
for (size_t i = 0; i < kv_size(saved); i++) {
- mtkey_t item = kv_A(saved, i);
+ MTKey item = kv_A(saved, i);
unrelative(new, &item.pos);
marktree_put_key(b, item);
+ if (mt_paired(item)) {
+ // other end might be later in `saved`, this will safely bail out then
+ marktree_restore_pair(b, item);
+ }
}
kv_destroy(saved);
}
/// @param itr OPTIONAL. set itr to pos.
-mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
+MTKey marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
{
return marktree_lookup(b, mt_lookup_id(ns, id, end), itr);
}
+static uint64_t pseudo_index(MTNode *x, int i)
+{
+ int off = MT_LOG2_BRANCH * x->level;
+ uint64_t index = 0;
+
+ while (x) {
+ index |= (uint64_t)(i + 1) << off;
+ off += MT_LOG2_BRANCH;
+ i = x->p_idx;
+ x = x->parent;
+ }
+
+ return index;
+}
+
+/// @param itr OPTIONAL. set itr to pos.
+/// if sloppy, two keys at the same _leaf_ node has the same index
+static uint64_t pseudo_index_for_id(MarkTree *b, uint64_t id, bool sloppy)
+{
+ MTNode *n = id2node(b, id);
+ if (n == NULL) {
+ return 0; // a valid pseudo-index is never zero!
+ }
+
+ int i = 0;
+ if (n->level || !sloppy) {
+ for (i = 0; i < n->n; i++) {
+ if (mt_lookup_key(n->key[i]) == id) {
+ break;
+ }
+ }
+ assert(i < n->n);
+ if (n->level) {
+ i += 1; // internal key i comes after ptr[i]
+ }
+ }
+
+ return pseudo_index(n, i);
+}
+
/// @param itr OPTIONAL. set itr to pos.
-mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
+MTKey marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
{
- mtnode_t *n = pmap_get(uint64_t)(b->id2node, id);
+ MTNode *n = id2node(b, id);
if (n == NULL) {
if (itr) {
- itr->node = NULL;
+ itr->x = NULL;
}
return MT_INVALID_KEY;
}
int i = 0;
for (i = 0; i < n->n; i++) {
if (mt_lookup_key(n->key[i]) == id) {
- goto found;
+ return marktree_itr_set_node(b, itr, n, i);
}
}
+
abort();
-found: {}
- mtkey_t key = n->key[i];
+}
+
+MTKey marktree_itr_set_node(MarkTree *b, MarkTreeIter *itr, MTNode *n, int i)
+{
+ MTKey key = n->key[i];
if (itr) {
itr->i = i;
- itr->node = n;
+ itr->x = n;
itr->lvl = b->root->level - n->level;
}
while (n->parent != NULL) {
- mtnode_t *p = n->parent;
- for (i = 0; i < p->n + 1; i++) {
- if (p->ptr[i] == n) {
- goto found_node;
- }
- }
- abort();
-found_node:
+ MTNode *p = n->parent;
+ i = n->p_idx;
+ assert(p->ptr[i] == n);
+
if (itr) {
itr->s[b->root->level - p->level].i = i;
}
@@ -1079,14 +1952,14 @@ found_node:
return key;
}
-mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+MTPos marktree_get_altpos(MarkTree *b, MTKey mark, MarkTreeIter *itr)
{
return marktree_get_alt(b, mark, itr).pos;
}
-mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+MTKey marktree_get_alt(MarkTree *b, MTKey mark, MarkTreeIter *itr)
{
- mtkey_t end = MT_INVALID_KEY;
+ MTKey end = MT_INVALID_KEY;
if (mt_paired(mark)) {
end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr);
}
@@ -1095,8 +1968,8 @@ mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
{
- itr->pos = (mtpos_t){ 0, 0 };
- mtnode_t *x = b->root;
+ itr->pos = (MTPos){ 0, 0 };
+ MTNode *x = b->root;
for (int lvl = 0; lvl < itr->lvl; lvl++) {
itr->s[lvl].oldcol = itr->pos.col;
int i = itr->s[lvl].i;
@@ -1106,23 +1979,36 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
assert(x->level);
x = x->ptr[i];
}
- assert(x == itr->node);
+ assert(x == itr->x);
}
// for unit test
-void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity)
+void marktree_put_test(MarkTree *b, uint32_t ns, uint32_t id, int row, int col, bool right_gravity,
+ int end_row, int end_col, bool end_right)
{
- mtkey_t key = { { row, col }, UINT32_MAX, id, 0,
- mt_flags(right_gravity, 0), 0, NULL };
- marktree_put(b, key, -1, -1, false);
+ uint16_t flags = mt_flags(right_gravity, false, false, false);
+ MTKey key = { { row, col }, ns, id, flags, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } };
+ marktree_put(b, key, end_row, end_col, end_right);
}
// for unit test
-bool mt_right_test(mtkey_t key)
+bool mt_right_test(MTKey key)
{
return mt_right(key);
}
+// for unit test
+void marktree_del_pair_test(MarkTree *b, uint32_t ns, uint32_t id)
+{
+ MarkTreeIter itr[1];
+ marktree_lookup_ns(b, ns, id, false, itr);
+
+ uint64_t other = marktree_del_itr(b, itr, false);
+ assert(other);
+ marktree_lookup(b, other, itr);
+ marktree_del_itr(b, itr, false);
+}
+
void marktree_check(MarkTree *b)
{
#ifndef NDEBUG
@@ -1133,9 +2019,9 @@ void marktree_check(MarkTree *b)
return;
}
- mtpos_t dummy;
+ MTPos dummy;
bool last_right = false;
- size_t nkeys = check_node(b, b->root, &dummy, &last_right);
+ size_t nkeys = marktree_check_node(b, b->root, &dummy, &last_right);
assert(b->n_keys == nkeys);
assert(b->n_keys == map_size(b->id2node));
#else
@@ -1145,7 +2031,7 @@ void marktree_check(MarkTree *b)
}
#ifndef NDEBUG
-static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right)
+size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right)
{
assert(x->n <= 2 * T - 1);
// TODO(bfredl): too strict if checking "in repair" post-delete tree.
@@ -1154,9 +2040,9 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig
for (int i = 0; i < x->n; i++) {
if (x->level) {
- n_keys += check_node(b, x->ptr[i], last, last_right);
+ n_keys += marktree_check_node(b, x->ptr[i], last, last_right);
} else {
- *last = (mtpos_t) { 0, 0 };
+ *last = (MTPos) { 0, 0 };
}
if (i > 0) {
unrelative(x->key[i - 1].pos, last);
@@ -1171,50 +2057,238 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig
}
if (x->level) {
- n_keys += check_node(b, x->ptr[x->n], last, last_right);
+ n_keys += marktree_check_node(b, x->ptr[x->n], last, last_right);
unrelative(x->key[x->n - 1].pos, last);
for (int i = 0; i < x->n + 1; i++) {
assert(x->ptr[i]->parent == x);
+ assert(x->ptr[i]->p_idx == i);
assert(x->ptr[i]->level == x->level - 1);
// PARANOIA: check no double node ref
for (int j = 0; j < i; j++) {
assert(x->ptr[i] != x->ptr[j]);
}
}
- } else {
+ } else if (x->n > 0) {
*last = x->key[x->n - 1].pos;
}
return n_keys;
}
+
+bool marktree_check_intersections(MarkTree *b)
+{
+ if (!b->root) {
+ return true;
+ }
+ PMap(ptr_t) checked = MAP_INIT;
+
+ // 1. move x->intersect to checked[x] and reinit x->intersect
+ mt_recurse_nodes(b->root, &checked);
+
+ // 2. iterate over all marks. for each START mark of a pair,
+ // intersect the nodes between the pair
+ MarkTreeIter itr[1];
+ marktree_itr_first(b, itr);
+ while (true) {
+ MTKey mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0) {
+ break;
+ }
+
+ if (mt_start(mark)) {
+ MarkTreeIter start_itr[1];
+ MarkTreeIter end_itr[1];
+ uint64_t end_id = mt_lookup_id(mark.ns, mark.id, true);
+ MTKey k = marktree_lookup(b, end_id, end_itr);
+ if (k.pos.row >= 0) {
+ *start_itr = *itr;
+ marktree_intersect_pair(b, mt_lookup_key(mark), start_itr, end_itr, false);
+ }
+ }
+
+ marktree_itr_next(b, itr);
+ }
+
+ // 3. for each node check if the recreated intersection
+ // matches the old checked[x] intersection.
+ bool status = mt_recurse_nodes_compare(b->root, &checked);
+
+ uint64_t *val;
+ map_foreach_value(&checked, val, {
+ xfree(val);
+ });
+ map_destroy(ptr_t, &checked);
+
+ return status;
+}
+
+void mt_recurse_nodes(MTNode *x, PMap(ptr_t) *checked)
+{
+ if (kv_size(x->intersect)) {
+ kvi_push(x->intersect, (uint64_t)-1); // sentinel
+ uint64_t *val;
+ if (x->intersect.items == x->intersect.init_array) {
+ val = xmemdup(x->intersect.items, x->intersect.size * sizeof(*x->intersect.items));
+ } else {
+ val = x->intersect.items;
+ }
+ pmap_put(ptr_t)(checked, x, val);
+ kvi_init(x->intersect);
+ }
+
+ if (x->level) {
+ for (int i = 0; i < x->n + 1; i++) {
+ mt_recurse_nodes(x->ptr[i], checked);
+ }
+ }
+}
+
+bool mt_recurse_nodes_compare(MTNode *x, PMap(ptr_t) *checked)
+{
+ uint64_t *ref = pmap_get(ptr_t)(checked, x);
+ if (ref != NULL) {
+ for (size_t i = 0;; i++) {
+ if (ref[i] == (uint64_t)-1) {
+ if (i != kv_size(x->intersect)) {
+ return false;
+ }
+
+ break;
+ } else {
+ if (kv_size(x->intersect) <= i || ref[i] != kv_A(x->intersect, i)) {
+ return false;
+ }
+ }
+ }
+ } else {
+ if (kv_size(x->intersect)) {
+ return false;
+ }
+ }
+
+ if (x->level) {
+ for (int i = 0; i < x->n + 1; i++) {
+ if (!mt_recurse_nodes_compare(x->ptr[i], checked)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
#endif
-char *mt_inspect_rec(MarkTree *b)
+// TODO(bfredl): kv_print
+#define GA_PUT(x) ga_concat(ga, (char *)(x))
+#define GA_PRINT(fmt, ...) snprintf(buf, sizeof(buf), fmt, __VA_ARGS__); \
+ GA_PUT(buf);
+
+String mt_inspect(MarkTree *b, bool keys, bool dot)
{
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
- mtpos_t p = { 0, 0 };
- mt_inspect_node(b, &ga, b->root, p);
- return ga.ga_data;
+ garray_T ga[1];
+ ga_init(ga, (int)sizeof(char), 80);
+ MTPos p = { 0, 0 };
+ if (b->root) {
+ if (dot) {
+ GA_PUT("digraph D {\n\n");
+ mt_inspect_dotfile_node(b, ga, b->root, p, NULL);
+ GA_PUT("\n}");
+ } else {
+ mt_inspect_node(b, ga, keys, b->root, p);
+ }
+ }
+ return ga_take_string(ga);
}
-void mt_inspect_node(MarkTree *b, garray_T *ga, mtnode_t *n, mtpos_t off)
+void mt_inspect_node(MarkTree *b, garray_T *ga, bool keys, MTNode *n, MTPos off)
{
static char buf[1024];
- ga_concat(ga, "[");
+ GA_PUT("[");
+ if (keys && kv_size(n->intersect)) {
+ for (size_t i = 0; i < kv_size(n->intersect); i++) {
+ GA_PUT(i == 0 ? "{" : ";");
+ // GA_PRINT("%"PRIu64, kv_A(n->intersect, i));
+ GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i)));
+ }
+ GA_PUT("},");
+ }
if (n->level) {
- mt_inspect_node(b, ga, n->ptr[0], off);
+ mt_inspect_node(b, ga, keys, n->ptr[0], off);
}
for (int i = 0; i < n->n; i++) {
- mtpos_t p = n->key[i].pos;
+ MTPos p = n->key[i].pos;
unrelative(off, &p);
- snprintf((char *)buf, sizeof(buf), "%d/%d", p.row, p.col);
- ga_concat(ga, buf);
+ GA_PRINT("%d/%d", p.row, p.col);
+ if (keys) {
+ MTKey key = n->key[i];
+ GA_PUT(":");
+ if (mt_start(key)) {
+ GA_PUT("<");
+ }
+ // GA_PRINT("%"PRIu64, mt_lookup_id(key.ns, key.id, false));
+ GA_PRINT("%" PRIu32, key.id);
+ if (mt_end(key)) {
+ GA_PUT(">");
+ }
+ }
if (n->level) {
- mt_inspect_node(b, ga, n->ptr[i + 1], p);
+ mt_inspect_node(b, ga, keys, n->ptr[i + 1], p);
} else {
ga_concat(ga, ",");
}
}
ga_concat(ga, "]");
}
+
+void mt_inspect_dotfile_node(MarkTree *b, garray_T *ga, MTNode *n, MTPos off, char *parent)
+{
+ static char buf[1024];
+ char namebuf[64];
+ if (parent != NULL) {
+ snprintf(namebuf, sizeof namebuf, "%s_%c%d", parent, 'a' + n->level, n->p_idx);
+ } else {
+ snprintf(namebuf, sizeof namebuf, "MTNode");
+ }
+
+ GA_PRINT(" %s[shape=plaintext, label=<\n", namebuf);
+ GA_PUT(" <table border='0' cellborder='1' cellspacing='0'>\n");
+ if (kv_size(n->intersect)) {
+ GA_PUT(" <tr><td>");
+ for (size_t i = 0; i < kv_size(n->intersect); i++) {
+ if (i > 0) {
+ GA_PUT(", ");
+ }
+ GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i)));
+ }
+ GA_PUT("</td></tr>\n");
+ }
+
+ GA_PUT(" <tr><td>");
+ for (int i = 0; i < n->n; i++) {
+ MTKey k = n->key[i];
+ if (i > 0) {
+ GA_PUT(", ");
+ }
+ GA_PRINT("%d", k.id);
+ if (mt_paired(k)) {
+ GA_PUT(mt_end(k) ? "e" : "s");
+ }
+ }
+ GA_PUT("</td></tr>\n");
+ GA_PUT(" </table>\n");
+ GA_PUT(">];\n");
+ if (parent) {
+ GA_PRINT(" %s -> %s\n", parent, namebuf);
+ }
+ if (n->level) {
+ mt_inspect_dotfile_node(b, ga, n->ptr[0], off, namebuf);
+ }
+ for (int i = 0; i < n->n; i++) {
+ MTPos p = n->key[i].pos;
+ unrelative(off, &p);
+ if (n->level) {
+ mt_inspect_dotfile_node(b, ga, n->ptr[i + 1], p, namebuf);
+ }
+ }
+}
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index 5ce4b2cd24..c76359d3f9 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -1,140 +1,209 @@
-#ifndef NVIM_MARKTREE_H
-#define NVIM_MARKTREE_H
+#pragma once
-#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/assert.h"
-#include "nvim/garray.h"
-#include "nvim/map.h"
+#include "klib/kvec.h"
+#include "nvim/decoration_defs.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
#include "nvim/map_defs.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-struct mtnode_s;
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+// only for debug functions:
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#define MT_MAX_DEPTH 20
#define MT_BRANCH_FACTOR 10
+// note max branch is actually 2*MT_BRANCH_FACTOR
+// and strictly this is ceil(log2(2*MT_BRANCH_FACTOR + 1))
+// as we need a pseudo-index for "right before this node"
+#define MT_LOG2_BRANCH 5
typedef struct {
int32_t row;
int32_t col;
-} mtpos_t;
+} MTPos;
+#define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) })
-typedef struct mtnode_s mtnode_t;
-typedef struct {
- int oldcol;
- int i;
-} iterstate_t;
+typedef struct mtnode_s MTNode;
typedef struct {
- mtpos_t pos;
+ MTPos pos;
int lvl;
- mtnode_t *node;
+ MTNode *x;
int i;
- iterstate_t s[MT_MAX_DEPTH];
+ struct {
+ int oldcol;
+ int i;
+ } s[MT_MAX_DEPTH];
+
+ size_t intersect_idx;
+ MTPos intersect_pos;
+ MTPos intersect_pos_x;
} MarkTreeIter;
+#define marktree_itr_valid(itr) ((itr)->x != NULL)
+// access raw key: flags in MT_FLAG_EXTERNAL_MASK and decor_data are safe to modify.
+#define mt_itr_rawkey(itr) ((itr)->x->key[(itr)->i])
+
// Internal storage
//
// NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for
// "space before (row,col)"
typedef struct {
- mtpos_t pos;
+ MTPos pos;
uint32_t ns;
uint32_t id;
- int32_t hl_id;
uint16_t flags;
- uint16_t priority;
- Decoration *decor_full;
-} mtkey_t;
-#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL }
+ DecorInlineData decor_data; // "ext" tag in flags
+} MTKey;
+
+typedef struct {
+ MTKey start;
+ MTPos end_pos;
+ bool end_right_gravity;
+} MTPair;
+
+#define MT_INVALID_KEY (MTKey) { { -1, -1 }, 0, 0, 0, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } }
#define MT_FLAG_REAL (((uint16_t)1) << 0)
#define MT_FLAG_END (((uint16_t)1) << 1)
#define MT_FLAG_PAIRED (((uint16_t)1) << 2)
-#define MT_FLAG_HL_EOL (((uint16_t)1) << 3)
-
-#define DECOR_LEVELS 4
-#define MT_FLAG_DECOR_OFFSET 4
-#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS - 1)) << MT_FLAG_DECOR_OFFSET)
-
-// next flag is (((uint16_t)1) << 6)
+// orphaned: the other side of this paired mark was deleted. this mark must be deleted very soon!
+#define MT_FLAG_ORPHANED (((uint16_t)1) << 3)
+#define MT_FLAG_NO_UNDO (((uint16_t)1) << 4)
+#define MT_FLAG_INVALIDATE (((uint16_t)1) << 5)
+#define MT_FLAG_INVALID (((uint16_t)1) << 6)
+// discriminant for union
+#define MT_FLAG_DECOR_EXT (((uint16_t)1) << 7)
+
+// TODO(bfredl): flags for decorations. These cover the cases where we quickly needs
+// to skip over irrelevant marks internally. When we refactor this more, also make all info
+// for ExtmarkType included here
+#define MT_FLAG_DECOR_HL (((uint16_t)1) << 8)
+#define MT_FLAG_DECOR_SIGNTEXT (((uint16_t)1) << 9)
+// TODO(bfredl): for now this means specifically number_hl, line_hl, cursorline_hl
+// needs to clean up the name.
+#define MT_FLAG_DECOR_SIGNHL (((uint16_t)1) << 10)
+#define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11)
+#define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12)
// These _must_ be last to preserve ordering of marks
#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
#define MT_FLAG_LAST (((uint16_t)1) << 15)
-#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL)
+#define MT_FLAG_DECOR_MASK (MT_FLAG_DECOR_EXT| MT_FLAG_DECOR_HL | MT_FLAG_DECOR_SIGNTEXT \
+ | MT_FLAG_DECOR_SIGNHL | MT_FLAG_DECOR_VIRT_LINES \
+ | MT_FLAG_DECOR_VIRT_TEXT_INLINE)
-#define MARKTREE_END_FLAG (((uint64_t)1) << 63)
+#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO \
+ | MT_FLAG_INVALIDATE | MT_FLAG_INVALID)
+
+// this is defined so that start and end of the same range have adjacent ids
+#define MARKTREE_END_FLAG ((uint64_t)1)
static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda)
{
- return (uint64_t)ns << 32 | id | (enda?MARKTREE_END_FLAG:0);
+ return (uint64_t)ns << 33 | (id <<1) | (enda ? MARKTREE_END_FLAG : 0);
}
-#undef MARKTREE_END_FLAG
-static inline uint64_t mt_lookup_key(mtkey_t key)
+static inline uint64_t mt_lookup_key_side(MTKey key, bool end)
+{
+ return mt_lookup_id(key.ns, key.id, end);
+}
+
+static inline uint64_t mt_lookup_key(MTKey key)
{
return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END);
}
-static inline bool mt_paired(mtkey_t key)
+static inline bool mt_paired(MTKey key)
{
return key.flags & MT_FLAG_PAIRED;
}
-static inline bool mt_end(mtkey_t key)
+static inline bool mt_end(MTKey key)
{
return key.flags & MT_FLAG_END;
}
-static inline bool mt_start(mtkey_t key)
+static inline bool mt_start(MTKey key)
{
return mt_paired(key) && !mt_end(key);
}
-static inline bool mt_right(mtkey_t key)
+static inline bool mt_right(MTKey key)
{
return key.flags & MT_FLAG_RIGHT_GRAVITY;
}
-static inline uint8_t marktree_decor_level(mtkey_t key)
+static inline bool mt_no_undo(MTKey key)
+{
+ return key.flags & MT_FLAG_NO_UNDO;
+}
+
+static inline bool mt_invalidate(MTKey key)
+{
+ return key.flags & MT_FLAG_INVALIDATE;
+}
+
+static inline bool mt_invalid(MTKey key)
{
- return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET);
+ return key.flags & MT_FLAG_INVALID;
}
-static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level)
+static inline bool mt_decor_any(MTKey key)
+{
+ return key.flags & MT_FLAG_DECOR_MASK;
+}
+
+static inline bool mt_decor_sign(MTKey key)
+{
+ return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL);
+}
+
+static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext)
{
- assert(decor_level < DECOR_LEVELS);
return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)
- | (decor_level << MT_FLAG_DECOR_OFFSET));
+ | (no_undo ? MT_FLAG_NO_UNDO : 0)
+ | (invalidate ? MT_FLAG_INVALIDATE : 0)
+ | (decor_ext ? MT_FLAG_DECOR_EXT : 0));
+}
+
+static inline MTPair mtpair_from(MTKey start, MTKey end)
+{
+ return (MTPair){ .start = start, .end_pos = end.pos, .end_right_gravity = mt_right(end) };
}
+static inline DecorInline mt_decor(MTKey key)
+{
+ return (DecorInline){ .ext = key.flags & MT_FLAG_DECOR_EXT, .data = key.decor_data };
+}
+
+typedef kvec_withinit_t(uint64_t, 4) Intersection;
+
struct mtnode_s {
int32_t n;
- int32_t level;
+ int16_t level;
+ int16_t p_idx; // index in parent
+ Intersection intersect;
// TODO(bfredl): we could consider having a only-sometimes-valid
// index into parent for faster "cached" lookup.
- mtnode_t *parent;
- mtkey_t key[2 * MT_BRANCH_FACTOR - 1];
- mtnode_t *ptr[];
+ MTNode *parent;
+ MTKey key[2 * MT_BRANCH_FACTOR - 1];
+ MTNode *ptr[];
};
-// TODO(bfredl): the iterator is pretty much everpresent, make it part of the
-// tree struct itself?
+static inline uint64_t mt_dbg_id(uint64_t id)
+{
+ return (id>>1)&0xffffffff;
+}
+
typedef struct {
- mtnode_t *root;
+ MTNode *root;
size_t n_keys, n_nodes;
- // TODO(bfredl): the pointer to node could be part of the larger
- // Map(uint64_t, ExtmarkItem) essentially;
PMap(uint64_t) id2node[1];
} MarkTree;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "marktree.h.generated.h"
#endif
-
-#endif // NVIM_MARKTREE_H
diff --git a/src/nvim/match.c b/src/nvim/match.c
index 6663dfd7ec..0a7c264d4f 100644
--- a/src/nvim/match.c
+++ b/src/nvim/match.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// match.c: functions for highlighting matches
#include <assert.h>
@@ -9,40 +6,40 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/match.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "match.c.generated.h"
#endif
-static char *e_invalwindow = N_("E957: Invalid window number");
+static const char *e_invalwindow = N_("E957: Invalid window number");
#define SEARCH_HL_PRIORITY 0
@@ -59,9 +56,6 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
list_T *pos_list, const char *const conceal_char)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
- matchitem_T *cur;
- matchitem_T *prev;
- matchitem_T *m;
int hlg_id;
regprog_T *regprog = NULL;
int rtype = UPD_SOME_VALID;
@@ -80,7 +74,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
id = wp->w_next_match_id++;
} else {
// check the given ID is not already in use
- for (cur = wp->w_match_head; cur != NULL; cur = cur->mit_next) {
+ for (matchitem_T *cur = wp->w_match_head; cur != NULL; cur = cur->mit_next) {
if (cur->mit_id == id) {
semsg(_("E801: ID already taken: %" PRId64), (int64_t)id);
return -1;
@@ -98,20 +92,20 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) {
return -1;
}
- if (pat != NULL && (regprog = vim_regcomp((char *)pat, RE_MAGIC)) == NULL) {
+ if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL) {
semsg(_(e_invarg2), pat);
return -1;
}
// Build new match.
- m = xcalloc(1, sizeof(matchitem_T));
+ matchitem_T *m = xcalloc(1, sizeof(matchitem_T));
if (pos_list != NULL) {
m->mit_pos_array = xcalloc((size_t)tv_list_len(pos_list), sizeof(llpos_T));
m->mit_pos_count = tv_list_len(pos_list);
}
m->mit_id = id;
m->mit_priority = prio;
- m->mit_pattern = pat == NULL ? NULL: xstrdup(pat);
+ m->mit_pattern = pat == NULL ? NULL : xstrdup(pat);
m->mit_hlg_id = hlg_id;
m->mit_match.regprog = regprog;
m->mit_match.rmm_ic = false;
@@ -215,8 +209,8 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
// Insert new match. The match list is in ascending order with regard to
// the match priorities.
- cur = wp->w_match_head;
- prev = cur;
+ matchitem_T *cur = wp->w_match_head;
+ matchitem_T *prev = cur;
while (cur != NULL && prio >= cur->mit_priority) {
prev = cur;
cur = cur->mit_next;
@@ -296,10 +290,8 @@ static int match_delete(win_T *wp, int id, bool perr)
/// Delete all matches in the match list of window 'wp'.
void clear_matches(win_T *wp)
{
- matchitem_T *m;
-
while (wp->w_match_head != NULL) {
- m = wp->w_match_head->mit_next;
+ matchitem_T *m = wp->w_match_head->mit_next;
vim_regfree(wp->w_match_head->mit_match.regprog);
xfree(wp->w_match_head->mit_pattern);
xfree(wp->w_match_head->mit_pos_array);
@@ -358,11 +350,10 @@ void init_search_hl(win_T *wp, match_T *search_hl)
static int next_search_hl_pos(match_T *shl, linenr_T lnum, matchitem_T *match, colnr_T mincol)
FUNC_ATTR_NONNULL_ALL
{
- int i;
int found = -1;
shl->lnum = 0;
- for (i = match->mit_pos_cur; i < match->mit_pos_count; i++) {
+ for (int i = match->mit_pos_cur; i < match->mit_pos_count; i++) {
llpos_T *pos = &match->mit_pos_array[i];
if (pos->lnum == 0) {
@@ -388,7 +379,7 @@ static int next_search_hl_pos(match_T *shl, linenr_T lnum, matchitem_T *match, c
match->mit_pos_cur = 0;
if (found >= 0) {
colnr_T start = match->mit_pos_array[found].col == 0
- ? 0: match->mit_pos_array[found].col - 1;
+ ? 0 : match->mit_pos_array[found].col - 1;
colnr_T end = match->mit_pos_array[found].col == 0
? MAXCOL : start + match->mit_pos_array[found].len;
@@ -419,9 +410,8 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
colnr_T mincol, matchitem_T *cur)
FUNC_ATTR_NONNULL_ARG(2)
{
- linenr_T l;
colnr_T matchcol;
- long nmatched = 0;
+ int nmatched = 0;
const int called_emsg_before = called_emsg;
// for :{range}s/pat only highlight inside the range
@@ -435,7 +425,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
// 1. If the "lnum" is below a previous match, start a new search.
// 2. If the previous match includes "mincol", use it.
// 3. Continue after the previous match.
- l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
+ linenr_T l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
if (lnum > l) {
shl->lnum = 0;
} else if (lnum < l || shl->rm.endpos[0].col > mincol) {
@@ -445,7 +435,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
// Repeat searching for a match until one is found that includes "mincol"
// or none is found in this line.
- for (;;) {
+ while (true) {
// Stop searching after passing the time limit.
if (profile_passed_limit(shl->tm)) {
shl->lnum = 0; // no match found in time
@@ -464,7 +454,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
char *ml;
matchcol = shl->rm.startpos[0].col;
- ml = ml_get_buf(shl->buf, lnum, false) + matchcol;
+ ml = ml_get_buf(shl->buf, lnum) + matchcol;
if (*ml == NUL) {
matchcol++;
shl->lnum = 0;
@@ -523,22 +513,19 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
FUNC_ATTR_NONNULL_ALL
{
- matchitem_T *cur; // points to the match list
+ matchitem_T *cur = wp->w_match_head; // points to the match list
match_T *shl; // points to search_hl or a match
- bool shl_flag; // flag to indicate whether search_hl
- // has been processed or not
+ bool shl_flag = false; // flag to indicate whether search_hl has been processed or not
// When using a multi-line pattern, start searching at the top
// of the window or just after a closed fold.
// Do this both for search_hl and the match list.
- cur = wp->w_match_head;
- shl_flag = false;
while (cur != NULL || shl_flag == false) {
if (shl_flag == false) {
shl = search_hl;
shl_flag = true;
} else {
- shl = &cur->mit_hl; // -V595
+ shl = &cur->mit_hl;
}
if (shl->rm.regprog != NULL
&& shl->lnum == 0
@@ -616,7 +603,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **lin
shl = search_hl;
shl_flag = true;
} else {
- shl = &cur->mit_hl; // -V595
+ shl = &cur->mit_hl;
}
shl->startcol = MAXCOL;
shl->endcol = MAXCOL;
@@ -631,7 +618,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **lin
// Need to get the line again, a multi-line regexp may have made it
// invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
+ *line = ml_get_buf(wp->w_buffer, lnum);
if (shl->lnum != 0 && shl->lnum <= lnum) {
if (shl->lnum == lnum) {
@@ -659,7 +646,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **lin
shl->endcol++;
}
}
- if ((long)shl->startcol < mincol) { // match at leftcol
+ if (shl->startcol < mincol) { // match at leftcol
shl->attr_cur = shl->attr;
*search_attr = shl->attr;
*search_attr_from_match = shl != search_hl;
@@ -717,8 +704,8 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T
}
// Highlight the match were the cursor is using the CurSearch
// group.
- if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC))) {
- shl->attr_cur = win_hl_attr(wp, HLF_LC) ? win_hl_attr(wp, HLF_LC) : HL_ATTR(HLF_LC);
+ if (shl == search_hl && shl->has_cursor) {
+ shl->attr_cur = win_hl_attr(wp, HLF_LC);
} else {
shl->attr_cur = shl->attr;
}
@@ -741,7 +728,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T
// Need to get the line again, a multi-line regexp
// may have made it invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
+ *line = ml_get_buf(wp->w_buffer, lnum);
if (shl->lnum == lnum) {
shl->startcol = shl->rm.startpos[0].col;
@@ -809,28 +796,27 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T
return search_attr;
}
-bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
+bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, colnr_T curcol)
{
- long prevcol = curcol;
- matchitem_T *cur; // points to the match list
+ colnr_T prevcol = curcol;
// we're not really at that column when skipping some text
- if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
+ if ((wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
prevcol++;
}
// Highlight a character after the end of the line if the match started
// at the end of the line or when the match continues in the next line
// (match includes the line break).
- if (!search_hl->is_addpos && (prevcol == (long)search_hl->startcol
- || (prevcol > (long)search_hl->startcol
+ if (!search_hl->is_addpos && (prevcol == search_hl->startcol
+ || (prevcol > search_hl->startcol
&& search_hl->endcol == MAXCOL))) {
return true;
}
- cur = wp->w_match_head;
+ matchitem_T *cur = wp->w_match_head; // points to the match list
while (cur != NULL) {
- if (!cur->mit_hl.is_addpos && (prevcol == (long)cur->mit_hl.startcol
- || (prevcol > (long)cur->mit_hl.startcol
+ if (!cur->mit_hl.is_addpos && (prevcol == cur->mit_hl.startcol
+ || (prevcol > cur->mit_hl.startcol
&& cur->mit_hl.endcol == MAXCOL))) {
return true;
}
@@ -842,7 +828,7 @@ bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
/// Get highlighting for the char after the text in "char_attr" from 'hlsearch'
/// or match highlighting.
-void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
+void get_search_match_hl(win_T *wp, match_T *search_hl, colnr_T col, int *char_attr)
{
matchitem_T *cur = wp->w_match_head; // points to the match list
match_T *shl; // points to search_hl or a match
@@ -857,7 +843,7 @@ void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr
} else {
shl = &cur->mit_hl;
}
- if (col - 1 == (long)shl->startcol
+ if (col - 1 == shl->startcol
&& (shl == search_hl || !shl->is_addpos)) {
*char_attr = shl->attr;
}
@@ -906,8 +892,6 @@ void f_clearmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "getmatches()" function
void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- matchitem_T *cur;
- int i;
win_T *win = get_optional_window(argvars, 0);
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -915,12 +899,12 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- cur = win->w_match_head;
+ matchitem_T *cur = win->w_match_head;
while (cur != NULL) {
dict_T *dict = tv_dict_alloc();
if (cur->mit_match.regprog == NULL) {
// match added with matchaddpos()
- for (i = 0; i < cur->mit_pos_count; i++) {
+ for (int i = 0; i < cur->mit_pos_count; i++) {
llpos_T *llpos;
char buf[30]; // use 30 to avoid compiler warning
@@ -939,15 +923,14 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_dict_add_list(dict, buf, (size_t)len, l);
}
} else {
- tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->mit_pattern);
+ tv_dict_add_str(dict, S_LEN("pattern"), cur->mit_pattern);
}
- tv_dict_add_str(dict, S_LEN("group"),
- (const char *)syn_id2name(cur->mit_hlg_id));
+ tv_dict_add_str(dict, S_LEN("group"), syn_id2name(cur->mit_hlg_id));
tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->mit_priority);
tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->mit_id);
if (cur->mit_conceal_char) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
buf[utf_char2bytes(cur->mit_conceal_char, buf)] = NUL;
tv_dict_add_str(dict, S_LEN("conceal"), buf);
@@ -1167,9 +1150,8 @@ void f_matcharg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
matchitem_T *const m = get_match(curwin, id);
if (m != NULL) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)syn_id2name(m->mit_hlg_id), -1);
- tv_list_append_string(rettv->vval.v_list, (const char *)m->mit_pattern, -1);
+ tv_list_append_string(rettv->vval.v_list, syn_id2name(m->mit_hlg_id), -1);
+ tv_list_append_string(rettv->vval.v_list, m->mit_pattern, -1);
} else {
tv_list_append_string(rettv->vval.v_list, NULL, 0);
tv_list_append_string(rettv->vval.v_list, NULL, 0);
@@ -1194,10 +1176,8 @@ void f_matchdelete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// skipping commands to find the next command.
void ex_match(exarg_T *eap)
{
- char *p;
char *g = NULL;
char *end;
- int c;
int id;
if (eap->line2 <= 3) {
@@ -1218,9 +1198,9 @@ void ex_match(exarg_T *eap)
&& (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) {
end = eap->arg + 4;
} else {
- p = skiptowhite(eap->arg);
+ char *p = skiptowhite(eap->arg);
if (!eap->skip) {
- g = xstrnsave(eap->arg, (size_t)(p - eap->arg));
+ g = xmemdupz(eap->arg, (size_t)(p - eap->arg));
}
p = skipwhite(p);
if (*p == NUL) {
@@ -1233,7 +1213,7 @@ void ex_match(exarg_T *eap)
if (!eap->skip) {
if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) {
xfree(g);
- eap->errmsg = ex_errmsg(e_trailing_arg, (const char *)end);
+ eap->errmsg = ex_errmsg(e_trailing_arg, end);
return;
}
if (*end != *p) {
@@ -1242,10 +1222,9 @@ void ex_match(exarg_T *eap)
return;
}
- c = (uint8_t)(*end);
+ int c = (uint8_t)(*end);
*end = NUL;
- match_add(curwin, (const char *)g, (const char *)p + 1, 10, id,
- NULL, NULL);
+ match_add(curwin, g, p + 1, 10, id, NULL, NULL);
xfree(g);
*end = (char)c;
}
diff --git a/src/nvim/match.h b/src/nvim/match.h
index 22a848bfdf..8dcf4fa470 100644
--- a/src/nvim/match.h
+++ b/src/nvim/match.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_MATCH_H
-#define NVIM_MATCH_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "match.h.generated.h"
#endif
-
-#endif // NVIM_MATCH_H
diff --git a/src/nvim/math.c b/src/nvim/math.c
index 31c6b5af69..96ff1bef10 100644
--- a/src/nvim/math.c
+++ b/src/nvim/math.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// uncrustify:off
#include <math.h>
// uncrustify:on
@@ -16,10 +13,9 @@
int xfpclassify(double d)
{
uint64_t m;
- int e;
memcpy(&m, &d, sizeof(m));
- e = 0x7ff & (m >> 52);
+ int e = 0x7ff & (m >> 52);
m = 0xfffffffffffffULL & m;
switch (e) {
diff --git a/src/nvim/math.h b/src/nvim/math.h
index 7969323905..c88ce1e03d 100644
--- a/src/nvim/math.h
+++ b/src/nvim/math.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_MATH_H
-#define NVIM_MATH_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "math.h.generated.h"
#endif
-#endif // NVIM_MATH_H
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 8b50ba719a..f2883cc5c7 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// mbyte.c: Code specifically for handling multi-byte characters.
/// Multibyte extensions partly by Sung-Hoon Baek
///
@@ -29,18 +26,21 @@
#include <ctype.h>
#include <errno.h>
#include <iconv.h>
+#include <locale.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <wchar.h>
+#include <sys/types.h>
#include <wctype.h>
#include "auto/config.h"
#include "nvim/arabic.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/typval.h"
@@ -48,28 +48,23 @@
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/iconv.h"
+#include "nvim/grid.h"
+#include "nvim/iconv_defs.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/mbyte_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/optionstr.h"
#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-
-#ifdef HAVE_LOCALE_H
-# include <locale.h>
-#endif
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
typedef struct {
int rangeStart;
@@ -79,8 +74,8 @@ typedef struct {
} convertStruct;
struct interval {
- long first;
- long last;
+ int first;
+ int last;
};
// uncrustify:off
@@ -90,17 +85,17 @@ struct interval {
#endif
// uncrustify:on
-static char e_list_item_nr_is_not_list[]
+static const char e_list_item_nr_is_not_list[]
= N_("E1109: List item %d is not a List");
-static char e_list_item_nr_does_not_contain_3_numbers[]
+static const char e_list_item_nr_does_not_contain_3_numbers[]
= N_("E1110: List item %d does not contain 3 numbers");
-static char e_list_item_nr_range_invalid[]
+static const char e_list_item_nr_range_invalid[]
= N_("E1111: List item %d range invalid");
-static char e_list_item_nr_cell_width_invalid[]
+static const char e_list_item_nr_cell_width_invalid[]
= N_("E1112: List item %d cell width invalid");
-static char e_overlapping_ranges_for_nr[]
+static const char e_overlapping_ranges_for_nr[]
= N_("E1113: Overlapping ranges for 0x%lx");
-static char e_only_values_of_0x80_and_higher_supported[]
+static const char e_only_values_of_0x80_and_higher_supported[]
= N_("E1114: Only values of 0x80 and higher supported");
// To speed up BYTELEN(); keep a lookup table to quickly get the length in
@@ -370,7 +365,7 @@ static int enc_canon_search(const char *name)
int enc_canon_props(const char *name)
FUNC_ATTR_PURE
{
- int i = enc_canon_search((char *)name);
+ int i = enc_canon_search(name);
if (i >= 0) {
return enc_canon_table[i].prop;
} else if (strncmp(name, "2byte-", 6) == 0) {
@@ -449,18 +444,16 @@ int mb_get_class_tab(const char *p, const uint64_t *const chartab)
static bool intable(const struct interval *table, size_t n_items, int c)
FUNC_ATTR_PURE
{
- int mid, bot, top;
-
// first quick check for Latin1 etc. characters
if (c < table[0].first) {
return false;
}
// binary search in table
- bot = 0;
- top = (int)(n_items - 1);
+ int bot = 0;
+ int top = (int)(n_items - 1);
while (top >= bot) {
- mid = (bot + top) / 2;
+ int mid = (bot + top) / 2;
if (table[mid].last < c) {
bot = mid + 1;
} else if (table[mid].first > c) {
@@ -518,11 +511,9 @@ int utf_char2cells(int c)
/// This doesn't take care of unprintable characters, use ptr2cells() for that.
int utf_ptr2cells(const char *p)
{
- int c;
-
// Need to convert to a character number.
if ((uint8_t)(*p) >= 0x80) {
- c = utf_ptr2char(p);
+ int c = utf_ptr2char(p);
// An illegal byte is displayed as <xx>.
if (utf_ptr2len(p) == 1 || c == NUL) {
return 4;
@@ -540,16 +531,14 @@ int utf_ptr2cells(const char *p)
/// For an empty string or truncated character returns 1.
int utf_ptr2cells_len(const char *p, int size)
{
- int c;
-
// Need to convert to a wide character.
if (size > 0 && (uint8_t)(*p) >= 0x80) {
if (utf_ptr2len_len(p, size) < utf8len_tab[(uint8_t)(*p)]) {
return 1; // truncated
}
- c = utf_ptr2char((char *)p);
+ int c = utf_ptr2char(p);
// An illegal byte is displayed as <xx>.
- if (utf_ptr2len((char *)p) == 1 || c == NUL) {
+ if (utf_ptr2len(p) == 1 || c == NUL) {
return 4;
}
// If the char is ASCII it must be an overlong sequence.
@@ -662,34 +651,32 @@ int utf_ptr2char(const char *const p_in)
//
// If byte sequence is illegal or incomplete, returns -1 and does not advance
// "s".
-static int utf_safe_read_char_adv(const char_u **s, size_t *n)
+static int utf_safe_read_char_adv(const char **s, size_t *n)
{
- int c;
-
if (*n == 0) { // end of buffer
return 0;
}
- uint8_t k = utf8len_tab_zero[**s];
+ uint8_t k = utf8len_tab_zero[(uint8_t)(**s)];
if (k == 1) {
// ASCII character or NUL
(*n)--;
- return *(*s)++;
+ return (uint8_t)(*(*s)++);
}
if (k <= *n) {
// We have a multibyte sequence and it isn't truncated by buffer
// limits so utf_ptr2char() is safe to use. Or the first byte is
// illegal (k=0), and it's also safe to use utf_ptr2char().
- c = utf_ptr2char((char *)(*s));
+ int c = utf_ptr2char(*s);
// On failure, utf_ptr2char() returns the first byte, so here we
// check equality with the first byte. The only non-ASCII character
// which equals the first byte of its own UTF-8 representation is
// U+00C3 (UTF-8: 0xC3 0x83), so need to check that special case too.
// It's safe even if n=1, else we would have k=2 > n.
- if (c != (int)(**s) || (c == 0xC3 && (*s)[1] == 0x83)) {
+ if (c != (int)((uint8_t)(**s)) || (c == 0xC3 && (uint8_t)(*s)[1] == 0x83)) {
// byte sequence was successfully decoded
*s += k;
*n -= k;
@@ -705,9 +692,7 @@ static int utf_safe_read_char_adv(const char_u **s, size_t *n)
// Note: composing characters are skipped!
int mb_ptr2char_adv(const char **const pp)
{
- int c;
-
- c = utf_ptr2char(*pp);
+ int c = utf_ptr2char(*pp);
*pp += utfc_ptr2len(*pp);
return c;
}
@@ -716,9 +701,7 @@ int mb_ptr2char_adv(const char **const pp)
// Note: composing characters are returned as separate characters.
int mb_cptr2char_adv(const char **pp)
{
- int c;
-
- c = utf_ptr2char(*pp);
+ int c = utf_ptr2char(*pp);
*pp += utf_ptr2len(*pp);
return c;
}
@@ -728,92 +711,78 @@ int mb_cptr2char_adv(const char **pp)
/// behaves like a composing character.
bool utf_composinglike(const char *p1, const char *p2)
{
- int c2;
-
- c2 = utf_ptr2char((char *)p2);
+ int c2 = utf_ptr2char(p2);
if (utf_iscomposing(c2)) {
return true;
}
if (!arabic_maycombine(c2)) {
return false;
}
- return arabic_combine(utf_ptr2char((char *)p1), c2);
+ return arabic_combine(utf_ptr2char(p1), c2);
}
-/// Convert a UTF-8 string to a wide character
+/// Get the screen char at the beginning of a string
+///
+/// Caller is expected to check for things like unprintable chars etc
+/// If first char in string is a composing char, prepend a space to display it correctly.
///
-/// Also gets up to #MAX_MCO composing characters.
+/// If "p" starts with an invalid sequence, zero is returned.
///
-/// @param[out] pcc Location where to store composing characters. Must have
-/// space at least for #MAX_MCO + 1 elements.
+/// @param[out] firstc (required) The first codepoint of the screen char,
+/// or the first byte of an invalid sequence
///
-/// @return leading character.
-int utfc_ptr2char(const char *p, int *pcc)
+/// @return the char
+schar_T utfc_ptr2schar(const char *p, int *firstc)
+ FUNC_ATTR_NONNULL_ALL
{
- int i = 0;
-
int c = utf_ptr2char(p);
- int len = utf_ptr2len(p);
+ *firstc = c; // NOT optional, you are gonna need it
+ bool first_compose = utf_iscomposing(c);
+ size_t maxlen = MAX_SCHAR_SIZE - 1 - first_compose;
+ size_t len = (size_t)utfc_ptr2len_len(p, (int)maxlen);
- // Only accept a composing char when the first char isn't illegal.
- if ((len > 1 || (uint8_t)(*p) < 0x80)
- && (uint8_t)p[len] >= 0x80
- && utf_composinglike(p, p + len)) {
- int cc = utf_ptr2char(p + len);
- for (;;) {
- pcc[i++] = cc;
- if (i == MAX_MCO) {
- break;
- }
- len += utf_ptr2len(p + len);
- if ((uint8_t)p[len] < 0x80 || !utf_iscomposing(cc = utf_ptr2char(p + len))) {
- break;
- }
- }
- }
-
- if (i < MAX_MCO) { // last composing char must be 0
- pcc[i] = 0;
+ if (len == 1 && (uint8_t)(*p) >= 0x80) {
+ return 0; // invalid sequence
}
- return c;
+ return schar_from_buf_first(p, len, first_compose);
}
-// Convert a UTF-8 byte string to a wide character. Also get up to MAX_MCO
-// composing characters. Use no more than p[maxlen].
-//
-// @param [out] pcc: composing chars, last one is 0
-int utfc_ptr2char_len(const char *p, int *pcc, int maxlen)
+/// Get the screen char at the beginning of a string with length
+///
+/// Like utfc_ptr2schar but use no more than p[maxlen].
+schar_T utfc_ptr2schar_len(const char *p, int maxlen, int *firstc)
+ FUNC_ATTR_NONNULL_ALL
{
assert(maxlen > 0);
- int i = 0;
+ size_t len = (size_t)utf_ptr2len_len(p, maxlen);
+ if (len > (size_t)maxlen || (len == 1 && (uint8_t)(*p) >= 0x80) || len == 0) {
+ // invalid or truncated sequence
+ *firstc = (uint8_t)(*p);
+ return 0;
+ }
- int len = utf_ptr2len_len(p, maxlen);
- // Is it safe to use utf_ptr2char()?
- bool safe = len > 1 && len <= maxlen;
- int c = safe ? utf_ptr2char(p) : (uint8_t)(*p);
+ int c = utf_ptr2char(p);
+ *firstc = c;
+ bool first_compose = utf_iscomposing(c);
+ maxlen = MIN(maxlen, MAX_SCHAR_SIZE - 1 - first_compose);
+ len = (size_t)utfc_ptr2len_len(p, maxlen);
- // Only accept a composing char when the first char isn't illegal.
- if ((safe || c < 0x80) && len < maxlen && (uint8_t)p[len] >= 0x80) {
- for (; i < MAX_MCO; i++) {
- int len_cc = utf_ptr2len_len(p + len, maxlen - len);
- safe = len_cc > 1 && len_cc <= maxlen - len;
- if (!safe || (pcc[i] = utf_ptr2char(p + len)) < 0x80
- || !(i == 0 ? utf_composinglike(p, p + len) : utf_iscomposing(pcc[i]))) {
- break;
- }
- len += len_cc;
- }
- }
+ return schar_from_buf_first(p, len, first_compose);
+}
- if (i < MAX_MCO) {
- // last composing char must be 0
- pcc[i] = 0;
+/// Caller must ensure there is space for `first_compose`
+static schar_T schar_from_buf_first(const char *buf, size_t len, bool first_compose)
+{
+ if (first_compose) {
+ char cbuf[MAX_SCHAR_SIZE];
+ cbuf[0] = ' ';
+ memcpy(cbuf + 1, buf, len);
+ return schar_from_buf(cbuf, len + 1);
+ } else {
+ return schar_from_buf(buf, len);
}
-
- return c;
-#undef ISCOMPOSING
}
/// Get the length of a UTF-8 byte sequence representing a single codepoint
@@ -854,11 +823,9 @@ int utf_byte2len(int b)
// Never returns zero.
int utf_ptr2len_len(const char *p, int size)
{
- int len;
- int i;
int m;
- len = utf8len_tab[(uint8_t)(*p)];
+ int len = utf8len_tab[(uint8_t)(*p)];
if (len == 1) {
return 1; // NUL, ascii or illegal lead byte
}
@@ -867,7 +834,7 @@ int utf_ptr2len_len(const char *p, int size)
} else {
m = len;
}
- for (i = 1; i < m; i++) {
+ for (int i = 1; i < m; i++) {
if ((p[i] & 0xc0) != 0x80) {
return 1;
}
@@ -898,10 +865,9 @@ int utfc_ptr2len(const char *const p)
return 1;
}
- // Check for composing characters. We can handle only the first six, but
- // skip all of them (otherwise the cursor would get stuck).
+ // Check for composing characters.
int prevlen = 0;
- for (;;) {
+ while (true) {
if ((uint8_t)p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len)) {
return len;
}
@@ -918,9 +884,6 @@ int utfc_ptr2len(const char *const p)
/// Returns 1 for an illegal char or an incomplete byte sequence.
int utfc_ptr2len_len(const char *p, int size)
{
- int len;
- int prevlen;
-
if (size < 1 || *p == NUL) {
return 0;
}
@@ -929,7 +892,7 @@ int utfc_ptr2len_len(const char *p, int size)
}
// Skip over first UTF-8 char, stopping at a NUL byte.
- len = utf_ptr2len_len(p, size);
+ int len = utf_ptr2len_len(p, size);
// Check for illegal byte and incomplete byte sequence.
if ((len == 1 && (uint8_t)p[0] >= 0x80) || len > size) {
@@ -938,17 +901,15 @@ int utfc_ptr2len_len(const char *p, int size)
// Check for composing characters. We can handle only the first six, but
// skip all of them (otherwise the cursor would get stuck).
- prevlen = 0;
+ int prevlen = 0;
while (len < size) {
- int len_next_char;
-
if ((uint8_t)p[len] < 0x80) {
break;
}
// Next character length should not go beyond size to ensure that
// utf_composinglike(...) does not read beyond size.
- len_next_char = utf_ptr2len_len(p + len, size - len);
+ int len_next_char = utf_ptr2len_len(p + len, size - len);
if (len_next_char > size - len) {
break;
}
@@ -1063,9 +1024,9 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
{
// sorted list of non-overlapping intervals
static struct clinterval {
- unsigned int first;
- unsigned int last;
- unsigned int class;
+ unsigned first;
+ unsigned last;
+ unsigned cls;
} classes[] = {
{ 0x037e, 0x037e, 1 }, // Greek question mark
{ 0x0387, 0x0387, 1 }, // Greek ano teleia
@@ -1141,7 +1102,6 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
};
int bot = 0;
int top = ARRAY_SIZE(classes) - 1;
- int mid;
// First quick check for Latin1 characters, use 'iskeyword'.
if (c < 0x100) {
@@ -1161,13 +1121,13 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
// binary search in table
while (top >= bot) {
- mid = (bot + top) / 2;
- if (classes[mid].last < (unsigned int)c) {
+ int mid = (bot + top) / 2;
+ if (classes[mid].last < (unsigned)c) {
bot = mid + 1;
- } else if (classes[mid].first > (unsigned int)c) {
+ } else if (classes[mid].first > (unsigned)c) {
top = mid - 1;
} else {
- return (int)classes[mid].class;
+ return (int)classes[mid].cls;
}
}
@@ -1186,13 +1146,12 @@ bool utf_ambiguous_width(int c)
// the given conversion "table". Uses binary search on "table".
static int utf_convert(int a, const convertStruct *const table, size_t n_items)
{
- size_t start, mid, end; // indices into table
-
- start = 0;
- end = n_items;
+ // indices into table
+ size_t start = 0;
+ size_t end = n_items;
while (start < end) {
// need to search further
- mid = (end + start) / 2;
+ size_t mid = (end + start) / 2;
if (table[mid].rangeEnd < a) {
start = mid + 1;
} else {
@@ -1285,12 +1244,12 @@ bool mb_isalpha(int a)
return mb_islower(a) || mb_isupper(a);
}
-static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2)
+static int utf_strnicmp(const char *s1, const char *s2, size_t n1, size_t n2)
{
- int c1, c2, cdiff;
+ int c1, c2;
char buffer[6];
- for (;;) {
+ while (true) {
c1 = utf_safe_read_char_adv(&s1, &n1);
c2 = utf_safe_read_char_adv(&s2, &n2);
@@ -1302,7 +1261,7 @@ static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2
continue;
}
- cdiff = utf_fold(c1) - utf_fold(c2);
+ int cdiff = utf_fold(c1) - utf_fold(c2);
if (cdiff != 0) {
return cdiff;
}
@@ -1326,15 +1285,15 @@ static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2
// to fold just one character to determine the result of comparison.
if (c1 != -1 && c2 == -1) {
- n1 = (size_t)utf_char2bytes(utf_fold(c1), (char *)buffer);
- s1 = (char_u *)buffer;
+ n1 = (size_t)utf_char2bytes(utf_fold(c1), buffer);
+ s1 = buffer;
} else if (c2 != -1 && c1 == -1) {
- n2 = (size_t)utf_char2bytes(utf_fold(c2), (char *)buffer);
- s2 = (char_u *)buffer;
+ n2 = (size_t)utf_char2bytes(utf_fold(c2), buffer);
+ s2 = buffer;
}
while (n1 > 0 && n2 > 0 && *s1 != NUL && *s2 != NUL) {
- cdiff = (int)(*s1) - (int)(*s2);
+ int cdiff = (int)((uint8_t)(*s1)) - (int)((uint8_t)(*s2));
if (cdiff != 0) {
return cdiff;
}
@@ -1483,11 +1442,11 @@ ssize_t mb_utf_index_to_bytes(const char *s, size_t len, size_t index, bool use_
FUNC_ATTR_NONNULL_ALL
{
size_t count = 0;
- size_t clen, i;
+ size_t clen;
if (index == 0) {
return 0;
}
- for (i = 0; i < len; i += clen) {
+ for (size_t i = 0; i < len; i += clen) {
clen = (size_t)utf_ptr2len_len(s + i, (int)(len - i));
// NB: gets the byte value of invalid sequence bytes.
// we only care whether the char fits in the BMP or not
@@ -1512,7 +1471,7 @@ ssize_t mb_utf_index_to_bytes(const char *s, size_t len, size_t index, bool use_
/// two characters otherwise.
int mb_strnicmp(const char *s1, const char *s2, const size_t nn)
{
- return utf_strnicmp((char_u *)s1, (char_u *)s2, nn, nn);
+ return utf_strnicmp(s1, s2, nn, nn);
}
/// Compare strings case-insensitively
@@ -1536,23 +1495,18 @@ int mb_stricmp(const char *s1, const char *s2)
// 'encoding' has been set to.
void show_utf8(void)
{
- int len;
- int rlen = 0;
- char *line;
- int clen;
- int i;
-
// Get the byte length of the char under the cursor, including composing
// characters.
- line = get_cursor_pos_ptr();
- len = utfc_ptr2len(line);
+ char *line = get_cursor_pos_ptr();
+ int len = utfc_ptr2len(line);
if (len == 0) {
- msg("NUL");
+ msg("NUL", 0);
return;
}
- clen = 0;
- for (i = 0; i < len; i++) {
+ size_t rlen = 0;
+ int clen = 0;
+ for (int i = 0; i < len; i++) {
if (clen == 0) {
// start of (composing) character, get its length
if (i > 0) {
@@ -1561,16 +1515,17 @@ void show_utf8(void)
}
clen = utf_ptr2len(line + i);
}
- sprintf(IObuff + rlen, "%02x ", // NOLINT(runtime/printf)
- (line[i] == NL) ? NUL : (uint8_t)line[i]); // NUL is stored as NL
+ assert(IOSIZE > rlen);
+ snprintf(IObuff + rlen, IOSIZE - rlen, "%02x ",
+ (line[i] == NL) ? NUL : (uint8_t)line[i]); // NUL is stored as NL
clen--;
- rlen += (int)strlen(IObuff + rlen);
+ rlen += strlen(IObuff + rlen);
if (rlen > IOSIZE - 20) {
break;
}
}
- msg(IObuff);
+ msg(IObuff, 0);
}
/// Return offset from "p" to the start of a character, including composing characters.
@@ -1579,9 +1534,6 @@ void show_utf8(void)
/// Returns 0 when already at the first byte of a character.
int utf_head_off(const char *base_in, const char *p_in)
{
- int c;
- int len;
-
if ((uint8_t)(*p_in) < 0x80) { // be quick for ASCII
return 0;
}
@@ -1603,7 +1555,7 @@ int utf_head_off(const char *base_in, const char *p_in)
}
// Check for illegal sequence. Do allow an illegal byte after where we
// started.
- len = utf8len_tab[*q];
+ int len = utf8len_tab[*q];
if (len != (int)(s - q + 1) && len != (int)(p - q + 1)) {
return 0;
}
@@ -1612,7 +1564,7 @@ int utf_head_off(const char *base_in, const char *p_in)
break;
}
- c = utf_ptr2char((char *)q);
+ int c = utf_ptr2char((char *)q);
if (utf_iscomposing(c)) {
continue;
}
@@ -1669,7 +1621,7 @@ bool utf_allow_break_before(int cc)
0x2021, // ‡ double dagger
0x2026, // … horizontal ellipsis
0x2030, // ‰ per mille sign
- 0x2031, // ‱ per then thousand sign
+ 0x2031, // ‱ per the thousand sign
0x203c, // ‼ double exclamation mark
0x2047, // ⇠double question mark
0x2048, // ∠question exclamation mark
@@ -1795,7 +1747,6 @@ int mb_off_next(const char *base, const char *p_in)
{
const uint8_t *p = (uint8_t *)p_in;
int i;
- int j;
if (*p < 0x80) { // be quick for ASCII
return 0;
@@ -1804,6 +1755,7 @@ int mb_off_next(const char *base, const char *p_in)
// Find the next character that isn't 10xx.xxxx
for (i = 0; (p[i] & 0xc0) == 0x80; i++) {}
if (i > 0) {
+ int j;
// Check for illegal sequence.
for (j = 0; p - j > (uint8_t *)base; j++) {
if ((p[-j] & 0xc0) != 0x80) {
@@ -1849,33 +1801,35 @@ int utf_cp_tail_off(const char *base, const char *p_in)
/// Return the offset from "p" to the first byte of the codepoint it points
/// to. Can start anywhere in a stream of bytes.
/// Note: Unlike `utf_head_off`, this counts individual codepoints of composed characters
-/// separately and returns a negative offset.
+/// separately.
///
/// @param[in] base Pointer to start of string
/// @param[in] p Pointer to byte for which to return the offset to the previous codepoint
//
-/// @return 0 if invalid sequence, else offset to previous codepoint
-int utf_cp_head_off(const char_u *base, const char_u *p)
+/// @return 0 if invalid sequence, else number of bytes to previous codepoint
+int utf_cp_head_off(const char *base, const char *p)
{
int i;
- int j;
if (*p == NUL) {
return 0;
}
// Find the first character that is not 10xx.xxxx
- for (i = 0; p - i > base; i--) {
- if ((p[i] & 0xc0) != 0x80) {
+ for (i = 0; p - i >= base; i++) {
+ if (((uint8_t)p[-i] & 0xc0) != 0x80) {
break;
}
}
- // Find the last character that is 10xx.xxxx
- for (j = 0; (p[j + 1] & 0xc0) == 0x80; j++) {}
+ // Find the last character that is 10xx.xxxx (condition terminates on NUL)
+ int j = 1;
+ while (((uint8_t)p[j] & 0xc0) == 0x80) {
+ j++;
+ }
// Check for illegal sequence.
- if (utf8len_tab[p[i]] == 1) {
+ if (utf8len_tab[(uint8_t)p[-i]] != j + i) {
return 0;
}
return i;
@@ -1885,8 +1839,6 @@ int utf_cp_head_off(const char_u *base, const char_u *p)
void utf_find_illegal(void)
{
pos_T pos = curwin->w_cursor;
- char *p;
- int len;
vimconv_T vimconv;
char *tofree = NULL;
@@ -1899,8 +1851,8 @@ void utf_find_illegal(void)
}
curwin->w_cursor.coladd = 0;
- for (;;) {
- p = get_cursor_pos_ptr();
+ while (true) {
+ char *p = get_cursor_pos_ptr();
if (vimconv.vc_type != CONV_NONE) {
xfree(tofree);
tofree = string_convert(&vimconv, p, NULL);
@@ -1913,7 +1865,7 @@ void utf_find_illegal(void)
while (*p != NUL) {
// Illegal means that there are not enough trail bytes (checked by
// utf_ptr2len()) or too many of them (overlong sequence).
- len = utf_ptr2len(p);
+ int len = utf_ptr2len(p);
if ((uint8_t)(*p) >= 0x80 && (len == 1 || utf_char2len(utf_ptr2char(p)) != len)) {
if (vimconv.vc_type == CONV_NONE) {
curwin->w_cursor.col += (colnr_T)(p - get_cursor_pos_ptr());
@@ -1948,16 +1900,16 @@ theend:
/// @return true if string "s" is a valid utf-8 string.
/// When "end" is NULL stop at the first NUL. Otherwise stop at "end".
-bool utf_valid_string(const char_u *s, const char_u *end)
+bool utf_valid_string(const char *s, const char *end)
{
- const char_u *p = s;
+ const uint8_t *p = (uint8_t *)s;
- while (end == NULL ? *p != NUL : p < end) {
+ while (end == NULL ? *p != NUL : p < (uint8_t *)end) {
int l = utf8len_tab_zero[*p];
if (l == 0) {
return false; // invalid lead byte
}
- if (end != NULL && p + l > end) {
+ if (end != NULL && p + l > (uint8_t *)end) {
return false; // incomplete byte sequence
}
p++;
@@ -1988,7 +1940,7 @@ void mb_check_adjust_col(void *win_)
// Column 0 is always valid.
if (oldcol != 0) {
- char *p = ml_get_buf(win->w_buffer, win->w_cursor.lnum, false);
+ char *p = ml_get_buf(win->w_buffer, win->w_cursor.lnum);
colnr_T len = (colnr_T)strlen(p);
// Empty line or invalid column?
@@ -2042,6 +1994,24 @@ int mb_charlen(const char *str)
return count;
}
+int mb_charlen2bytelen(const char *str, int charlen)
+{
+ const char *p = str;
+ int count = 0;
+
+ if (p == NULL) {
+ return 0;
+ }
+
+ for (int i = 0; *p != NUL && i < charlen; i++) {
+ int b = utfc_ptr2len(p);
+ p += b;
+ count += b;
+ }
+
+ return count;
+}
+
/// Like mb_charlen() but for a string with specified length.
int mb_charlen_len(const char *str, int len)
{
@@ -2122,7 +2092,6 @@ char *enc_skip(char *p)
char *enc_canonize(char *enc)
FUNC_ATTR_NONNULL_RET
{
- char *p, *s;
if (strcmp(enc, "default") == 0) {
// Use the default encoding as found by set_init_1().
return xstrdup(fenc_default);
@@ -2131,8 +2100,8 @@ char *enc_canonize(char *enc)
// copy "enc" to allocated memory, with room for two '-'
char *r = xmalloc(strlen(enc) + 3);
// Make it all lower case and replace '_' with '-'.
- p = r;
- for (s = enc; *s != NUL; s++) {
+ char *p = r;
+ for (char *s = enc; *s != NUL; s++) {
if (*s == '_') {
*p++ = '-';
} else {
@@ -2184,9 +2153,7 @@ char *enc_canonize(char *enc)
/// Returns -1 when not found.
static int enc_alias_search(const char *name)
{
- int i;
-
- for (i = 0; enc_alias_table[i].name != NULL; i++) {
+ for (int i = 0; enc_alias_table[i].name != NULL; i++) {
if (strcmp(name, enc_alias_table[i].name) == 0) {
return enc_alias_table[i].canon;
}
@@ -2210,10 +2177,7 @@ char *enc_locale(void)
if (!(s = nl_langinfo(CODESET)) || *s == NUL)
#endif
{
-#if defined(HAVE_LOCALE_H)
- if (!(s = setlocale(LC_CTYPE, NULL)) || *s == NUL)
-#endif
- {
+ if (!(s = setlocale(LC_CTYPE, NULL)) || *s == NUL) {
if ((s = os_getenv("LC_ALL"))) {
if ((s = os_getenv("LC_CTYPE"))) {
s = os_getenv("LANG");
@@ -2269,17 +2233,14 @@ enc_locale_copy_enc:
// (should return iconv_t, but that causes problems with prototypes).
void *my_iconv_open(char *to, char *from)
{
- iconv_t fd;
#define ICONV_TESTLEN 400
char tobuf[ICONV_TESTLEN];
- char *p;
- size_t tolen;
static WorkingStatus iconv_working = kUnknown;
if (iconv_working == kBroken) {
return (void *)-1; // detected a broken iconv() previously
}
- fd = iconv_open(enc_skip(to), enc_skip(from));
+ iconv_t fd = iconv_open(enc_skip(to), enc_skip(from));
if (fd != (iconv_t)-1 && iconv_working == kUnknown) {
// Do a dummy iconv() call to check if it actually works. There is a
@@ -2287,8 +2248,8 @@ void *my_iconv_open(char *to, char *from)
// because it's wide-spread. The symptoms are that after outputting
// the initial shift state the "to" pointer is NULL and conversion
// stops for no apparent reason after about 8160 characters.
- p = tobuf;
- tolen = ICONV_TESTLEN;
+ char *p = tobuf;
+ size_t tolen = ICONV_TESTLEN;
(void)iconv(fd, NULL, NULL, &p, &tolen);
if (p == NULL) {
iconv_working = kBroken;
@@ -2310,24 +2271,19 @@ void *my_iconv_open(char *to, char *from)
static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t slen,
size_t *unconvlenp, size_t *resultlenp)
{
- const char *from;
- size_t fromlen;
char *to;
- size_t tolen;
size_t len = 0;
size_t done = 0;
char *result = NULL;
- char *p;
- int l;
- from = str;
- fromlen = slen;
- for (;;) {
+ const char *from = str;
+ size_t fromlen = slen;
+ while (true) {
if (len == 0 || ICONV_ERRNO == ICONV_E2BIG) {
// Allocate enough room for most conversions. When re-allocating
// increase the buffer size.
len = len + fromlen * 2 + 40;
- p = xmalloc(len);
+ char *p = xmalloc(len);
if (done > 0) {
memmove(p, result, done);
}
@@ -2336,7 +2292,7 @@ static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t sl
}
to = result + done;
- tolen = len - done - 2;
+ size_t tolen = len - done - 2;
// Avoid a warning for systems with a wrong iconv() prototype by
// casting the second argument to void *.
if (iconv(vcp->vc_fd, (void *)&from, &fromlen, &to, &tolen) != SIZE_MAX) {
@@ -2366,7 +2322,7 @@ static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t sl
if (utf_ptr2cells(from) > 1) {
*to++ = '?';
}
- l = utfc_ptr2len_len(from, (int)fromlen);
+ int l = utfc_ptr2len_len(from, (int)fromlen);
from += l;
fromlen -= (size_t)l;
} else if (ICONV_ERRNO != ICONV_E2BIG) {
@@ -2384,6 +2340,34 @@ static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t sl
return result;
}
+/// iconv() function
+void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ vimconv_T vimconv;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const str = tv_get_string(&argvars[0]);
+ char buf1[NUMBUFLEN];
+ char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1)));
+ char buf2[NUMBUFLEN];
+ char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2)));
+ vimconv.vc_type = CONV_NONE;
+ convert_setup(&vimconv, from, to);
+
+ // If the encodings are equal, no conversion needed.
+ if (vimconv.vc_type == CONV_NONE) {
+ rettv->vval.v_string = xstrdup(str);
+ } else {
+ rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL);
+ }
+
+ convert_setup(&vimconv, NULL, NULL);
+ xfree(from);
+ xfree(to);
+}
+
/// Setup "vcp" for conversion from "from" to "to".
/// The names must have been made canonical with enc_canonize().
/// vcp->vc_type must have been initialized to CONV_NONE.
@@ -2402,8 +2386,6 @@ int convert_setup(vimconv_T *vcp, char *from, char *to)
int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, char *to,
bool to_unicode_is_utf8)
{
- int from_prop;
- int to_prop;
int from_is_utf8;
int to_is_utf8;
@@ -2419,8 +2401,8 @@ int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, cha
return OK;
}
- from_prop = enc_canon_props(from);
- to_prop = enc_canon_props(to);
+ int from_prop = enc_canon_props(from);
+ int to_prop = enc_canon_props(to);
if (from_unicode_is_utf8) {
from_is_utf8 = from_prop & ENC_UNICODE;
} else {
@@ -2477,9 +2459,8 @@ char *string_convert(const vimconv_T *const vcp, char *ptr, size_t *lenp)
// set to the number of remaining bytes.
char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, size_t *unconvlenp)
{
- char_u *retval = NULL;
- char_u *d;
- int l;
+ uint8_t *retval = NULL;
+ uint8_t *d;
int c;
size_t len;
@@ -2499,10 +2480,10 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
for (size_t i = 0; i < len; i++) {
c = (uint8_t)ptr[i];
if (c < 0x80) {
- *d++ = (char_u)c;
+ *d++ = (uint8_t)c;
} else {
- *d++ = (char_u)(0xc0 + (char_u)((unsigned)c >> 6));
- *d++ = (char_u)(0x80 + (c & 0x3f));
+ *d++ = (uint8_t)(0xc0 + (uint8_t)((unsigned)c >> 6));
+ *d++ = (uint8_t)(0x80 + (c & 0x3f));
}
}
*d = NUL;
@@ -2547,7 +2528,7 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
retval = xmalloc(len + 1);
d = retval;
for (size_t i = 0; i < len; i++) {
- l = utf_ptr2len_len(ptr + i, (int)(len - i));
+ int l = utf_ptr2len_len(ptr + i, (int)(len - i));
if (l == 0) {
*d++ = NUL;
} else if (l == 1) {
@@ -2597,7 +2578,7 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
}
if (!utf_iscomposing(c)) { // skip composing chars
if (c < 0x100) {
- *d++ = (char_u)c;
+ *d++ = (uint8_t)c;
} else if (vcp->vc_fail) {
xfree(retval);
return NULL;
@@ -2618,7 +2599,7 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
break;
case CONV_ICONV: // conversion with vcp->vc_fd
- retval = (char_u *)iconv_string(vcp, ptr, len, unconvlenp, lenp);
+ retval = (uint8_t *)iconv_string(vcp, ptr, len, unconvlenp, lenp);
break;
}
@@ -2627,8 +2608,8 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
/// Table set by setcellwidths().
typedef struct {
- long first;
- long last;
+ int64_t first;
+ int64_t last;
char width;
} cw_interval_T;
@@ -2753,7 +2734,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const listitem_T *lili = tv_list_first(li_l);
const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number;
if (item > 0 && n1 <= table[item - 1].last) {
- semsg(_(e_overlapping_ranges_for_nr), (long)n1);
+ semsg(_(e_overlapping_ranges_for_nr), (size_t)n1);
xfree((void *)ptrs);
xfree(table);
return;
@@ -2810,3 +2791,14 @@ void f_charclass(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
rettv->vval.v_number = mb_get_class(argvars[0].vval.v_string);
}
+
+/// Function given to ExpandGeneric() to obtain the possible arguments of the
+/// encoding options.
+char *get_encoding_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(enc_canon_table)) {
+ return NULL;
+ }
+
+ return (char *)enc_canon_table[idx].name;
+}
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index 780f33e05b..49c323282d 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -1,15 +1,15 @@
-#ifndef NVIM_MBYTE_H
-#define NVIM_MBYTE_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/func_attr.h"
-#include "nvim/mbyte_defs.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/types.h"
+#include "nvim/mbyte_defs.h" // IWYU pragma: export
+#include "nvim/os/os_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Return byte length of character that starts with byte "b".
// Returns 1 for a single-byte character.
@@ -38,4 +38,3 @@ static inline int mb_strcmp_ic(bool ic, const char *s1, const char *s2)
{
return (ic ? mb_stricmp(s1, s2) : strcmp(s1, s2));
}
-#endif // NVIM_MBYTE_H
diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h
index e913e20f9f..2904047223 100644
--- a/src/nvim/mbyte_defs.h
+++ b/src/nvim/mbyte_defs.h
@@ -1,9 +1,13 @@
-#ifndef NVIM_MBYTE_DEFS_H
-#define NVIM_MBYTE_DEFS_H
+#pragma once
#include <stdbool.h>
-#include "nvim/iconv.h"
+#include "nvim/iconv_defs.h"
+
+/// Maximum number of bytes in a multi-byte character. It can be one 32-bit
+/// character of up to 6 bytes, or one 16-bit character of up to three bytes
+/// plus six following composing characters of three bytes each.
+enum { MB_MAXBYTES = 21, };
/// max length of an unicode char
enum { MB_MAXCHAR = 6, };
@@ -50,5 +54,3 @@ typedef struct {
bool vc_fail; ///< What to do with invalid characters: if true, fail,
///< otherwise use '?'.
} vimconv_T;
-
-#endif // NVIM_MBYTE_DEFS_H
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index 46be9ccea5..d989600d45 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// An abstraction to handle blocks of memory which can be stored in a file.
/// This is the implementation of a sort of virtual memory.
///
@@ -45,24 +42,25 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include <sys/stat.h>
-#include "nvim/assert.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/fileio.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/map_defs.h"
#include "nvim/memfile.h"
#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/vim_defs.h"
#define MEMFILE_PAGE_SIZE 4096 /// default page size
@@ -70,6 +68,8 @@
# include "memfile.c.generated.h"
#endif
+static const char e_block_was_not_locked[] = N_("E293: Block was not locked");
+
/// Open a new or existing memory block file.
///
/// @param fname Name of file to use.
@@ -99,11 +99,9 @@ memfile_T *mf_open(char *fname, int flags)
}
mfp->mf_free_first = NULL; // free list is empty
- mfp->mf_used_first = NULL; // used list is empty
- mfp->mf_used_last = NULL;
- mfp->mf_dirty = false;
- mf_hash_init(&mfp->mf_hash);
- mf_hash_init(&mfp->mf_trans);
+ mfp->mf_dirty = MF_DIRTY_NO;
+ mfp->mf_hash = (PMap(int64_t)) MAP_INIT;
+ mfp->mf_trans = (Map(int64_t, int64_t)) MAP_INIT;
mfp->mf_page_size = MEMFILE_PAGE_SIZE;
// Try to set the page size equal to device's block size. Speeds up I/O a lot.
@@ -124,7 +122,7 @@ memfile_T *mf_open(char *fname, int flags)
// must be rounded up.
if (mfp->mf_fd < 0
|| (flags & (O_TRUNC|O_EXCL))
- || (size = vim_lseek(mfp->mf_fd, 0L, SEEK_END)) <= 0) {
+ || (size = vim_lseek(mfp->mf_fd, 0, SEEK_END)) <= 0) {
// no file or empty file
mfp->mf_blocknr_max = 0;
} else {
@@ -157,7 +155,7 @@ memfile_T *mf_open(char *fname, int flags)
int mf_open_file(memfile_T *mfp, char *fname)
{
if (mf_do_open(mfp, fname, O_RDWR | O_CREAT | O_EXCL)) {
- mfp->mf_dirty = true;
+ mfp->mf_dirty = MF_DIRTY_YES;
return OK;
}
@@ -180,15 +178,15 @@ void mf_close(memfile_T *mfp, bool del_file)
}
// free entries in used list
- for (bhdr_T *hp = mfp->mf_used_first, *nextp; hp != NULL; hp = nextp) {
- nextp = hp->bh_next;
+ bhdr_T *hp;
+ map_foreach_value(&mfp->mf_hash, hp, {
mf_free_bhdr(hp);
- }
+ })
while (mfp->mf_free_first != NULL) { // free entries in free list
xfree(mf_rem_free(mfp));
}
- mf_hash_free(&mfp->mf_hash);
- mf_hash_free_all(&mfp->mf_trans); // free hashtable and its items
+ map_destroy(int64_t, &mfp->mf_hash);
+ map_destroy(int64_t, &mfp->mf_trans); // free hashtable and its items
mf_free_fnames(mfp);
xfree(mfp);
}
@@ -206,7 +204,7 @@ void mf_close_file(buf_T *buf, bool getlines)
if (getlines) {
// get all blocks in memory by accessing all lines (clumsy!)
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- (void)ml_get_buf(buf, lnum, false);
+ (void)ml_get_buf(buf, lnum);
}
}
@@ -267,10 +265,9 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count)
}
}
hp->bh_flags = BH_LOCKED | BH_DIRTY; // new block is always dirty
- mfp->mf_dirty = true;
+ mfp->mf_dirty = MF_DIRTY_YES;
hp->bh_page_count = page_count;
- mf_ins_used(mfp, hp);
- mf_ins_hash(mfp, hp);
+ pmap_put(int64_t)(&mfp->mf_hash, hp->bh_bnum, hp);
// Init the data to all zero, to avoid reading uninitialized data.
// This also avoids that the passwd file ends up in the swap file!
@@ -292,7 +289,7 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count)
}
// see if it is in the cache
- bhdr_T *hp = mf_find_hash(mfp, nr);
+ bhdr_T *hp = pmap_get(int64_t)(&mfp->mf_hash, nr);
if (hp == NULL) { // not in the hash list
if (nr < 0 || nr >= mfp->mf_infile_count) { // can't be in the file
return NULL;
@@ -300,7 +297,12 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count)
// could check here if the block is in the free list
- hp = mf_alloc_bhdr(mfp, page_count);
+ if (page_count > 0) {
+ hp = mf_alloc_bhdr(mfp, page_count);
+ }
+ if (hp == NULL) {
+ return NULL;
+ }
hp->bh_bnum = nr;
hp->bh_flags = 0;
@@ -310,13 +312,11 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count)
return NULL;
}
} else {
- mf_rem_used(mfp, hp); // remove from list, insert in front below
- mf_rem_hash(mfp, hp);
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL);
}
hp->bh_flags |= BH_LOCKED;
- mf_ins_used(mfp, hp); // put in front of used list
- mf_ins_hash(mfp, hp); // put in front of hash list
+ pmap_put(int64_t)(&mfp->mf_hash, hp->bh_bnum, hp); // put in front of hash table
return hp;
}
@@ -330,12 +330,14 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile)
unsigned flags = hp->bh_flags;
if ((flags & BH_LOCKED) == 0) {
- iemsg(_("E293: block was not locked"));
+ iemsg(_(e_block_was_not_locked));
}
flags &= ~BH_LOCKED;
if (dirty) {
flags |= BH_DIRTY;
- mfp->mf_dirty = true;
+ if (mfp->mf_dirty != MF_DIRTY_YES_NOSYNC) {
+ mfp->mf_dirty = MF_DIRTY_YES;
+ }
}
hp->bh_flags = flags;
if (infile) {
@@ -347,8 +349,7 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile)
void mf_free(memfile_T *mfp, bhdr_T *hp)
{
xfree(hp->bh_data); // free data
- mf_rem_hash(mfp, hp); // get *hp out of the hash list
- mf_rem_used(mfp, hp); // get *hp out of the used list
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL); // get *hp out of the hash table
if (hp->bh_bnum < 0) {
xfree(hp); // don't want negative numbers in free list
mfp->mf_neg_count--;
@@ -375,8 +376,9 @@ int mf_sync(memfile_T *mfp, int flags)
{
int got_int_save = got_int;
- if (mfp->mf_fd < 0) { // there is no file, nothing to do
- mfp->mf_dirty = false;
+ if (mfp->mf_fd < 0) {
+ // there is no file, nothing to do
+ mfp->mf_dirty = MF_DIRTY_NO;
return FAIL;
}
@@ -389,7 +391,8 @@ int mf_sync(memfile_T *mfp, int flags)
// fails then we give up.
int status = OK;
bhdr_T *hp;
- for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) {
+ // note, "last" block is typically earlier in the hash list
+ map_foreach_value(&mfp->mf_hash, hp, {
if (((flags & MFS_ALL) || hp->bh_bnum >= 0)
&& (hp->bh_flags & BH_DIRTY)
&& (status == OK || (hp->bh_bnum >= 0
@@ -414,12 +417,12 @@ int mf_sync(memfile_T *mfp, int flags)
break;
}
}
- }
+ })
// If the whole list is flushed, the memfile is not dirty anymore.
// In case of an error, dirty flag is also set, to avoid trying all the time.
if (hp == NULL || status == FAIL) {
- mfp->mf_dirty = false;
+ mfp->mf_dirty = MF_DIRTY_NO;
}
if (flags & MFS_FLUSH) {
@@ -437,59 +440,13 @@ int mf_sync(memfile_T *mfp, int flags)
/// These are blocks that need to be written to a newly created swapfile.
void mf_set_dirty(memfile_T *mfp)
{
- for (bhdr_T *hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) {
+ bhdr_T *hp;
+ map_foreach_value(&mfp->mf_hash, hp, {
if (hp->bh_bnum > 0) {
hp->bh_flags |= BH_DIRTY;
}
- }
- mfp->mf_dirty = true;
-}
-
-/// Insert block in front of memfile's hash list.
-static void mf_ins_hash(memfile_T *mfp, bhdr_T *hp)
-{
- mf_hash_add_item(&mfp->mf_hash, (mf_hashitem_T *)hp);
-}
-
-/// Remove block from memfile's hash list.
-static void mf_rem_hash(memfile_T *mfp, bhdr_T *hp)
-{
- mf_hash_rem_item(&mfp->mf_hash, (mf_hashitem_T *)hp);
-}
-
-/// Lookup block with number "nr" in memfile's hash list.
-static bhdr_T *mf_find_hash(memfile_T *mfp, blocknr_T nr)
-{
- return (bhdr_T *)mf_hash_find(&mfp->mf_hash, nr);
-}
-
-/// Insert block at the front of memfile's used list.
-static void mf_ins_used(memfile_T *mfp, bhdr_T *hp)
-{
- hp->bh_next = mfp->mf_used_first;
- mfp->mf_used_first = hp;
- hp->bh_prev = NULL;
- if (hp->bh_next == NULL) { // list was empty, adjust last pointer
- mfp->mf_used_last = hp;
- } else {
- hp->bh_next->bh_prev = hp;
- }
-}
-
-/// Remove block from memfile's used list.
-static void mf_rem_used(memfile_T *mfp, bhdr_T *hp)
-{
- if (hp->bh_next == NULL) { // last block in used list
- mfp->mf_used_last = hp->bh_prev;
- } else {
- hp->bh_next->bh_prev = hp->bh_prev;
- }
-
- if (hp->bh_prev == NULL) { // first block in used list
- mfp->mf_used_first = hp->bh_next;
- } else {
- hp->bh_prev->bh_next = hp->bh_next;
- }
+ })
+ mfp->mf_dirty = MF_DIRTY_YES;
}
/// Release as many blocks as possible.
@@ -510,17 +467,18 @@ bool mf_release_all(void)
// Flush as many blocks as possible, only if there is a swapfile.
if (mfp->mf_fd >= 0) {
- for (bhdr_T *hp = mfp->mf_used_last; hp != NULL;) {
+ for (int i = 0; i < (int)map_size(&mfp->mf_hash);) {
+ bhdr_T *hp = mfp->mf_hash.values[i];
if (!(hp->bh_flags & BH_LOCKED)
&& (!(hp->bh_flags & BH_DIRTY)
|| mf_write(mfp, hp) != FAIL)) {
- mf_rem_used(mfp, hp);
- mf_rem_hash(mfp, hp);
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL);
mf_free_bhdr(hp);
- hp = mfp->mf_used_last; // restart, list was changed
retval = true;
+ // Rerun with the same value of i. another item will have taken
+ // its place (or it was the last)
} else {
- hp = hp->bh_prev;
+ i++;
}
}
}
@@ -548,7 +506,7 @@ static void mf_free_bhdr(bhdr_T *hp)
/// Insert a block in the free list.
static void mf_ins_free(memfile_T *mfp, bhdr_T *hp)
{
- hp->bh_next = mfp->mf_free_first;
+ hp->bh_data = mfp->mf_free_first;
mfp->mf_free_first = hp;
}
@@ -558,7 +516,7 @@ static void mf_ins_free(memfile_T *mfp, bhdr_T *hp)
static bhdr_T *mf_rem_free(memfile_T *mfp)
{
bhdr_T *hp = mfp->mf_free_first;
- mfp->mf_free_first = hp->bh_next;
+ mfp->mf_free_first = hp->bh_data;
return hp;
}
@@ -602,12 +560,8 @@ static int mf_read(memfile_T *mfp, bhdr_T *hp)
/// - Write error in swap file.
static int mf_write(memfile_T *mfp, bhdr_T *hp)
{
- off_T offset; // offset in the file
- blocknr_T nr; // block nr which is being written
bhdr_T *hp2;
- unsigned page_size; // number of bytes in a page
unsigned page_count; // number of pages written
- unsigned size; // number of bytes written
if (mfp->mf_fd < 0) { // there is no file, can't write
return FAIL;
@@ -619,23 +573,23 @@ static int mf_write(memfile_T *mfp, bhdr_T *hp)
}
}
- page_size = mfp->mf_page_size;
+ unsigned page_size = mfp->mf_page_size; // number of bytes in a page
/// We don't want gaps in the file. Write the blocks in front of *hp
/// to extend the file.
/// If block 'mf_infile_count' is not in the hash list, it has been
/// freed. Fill the space in the file with data from the current block.
- for (;;) {
- nr = hp->bh_bnum;
+ while (true) {
+ blocknr_T nr = hp->bh_bnum; // block nr which is being written
if (nr > mfp->mf_infile_count) { // beyond end of file
nr = mfp->mf_infile_count;
- hp2 = mf_find_hash(mfp, nr); // NULL caught below
+ hp2 = pmap_get(int64_t)(&mfp->mf_hash, nr); // NULL caught below
} else {
hp2 = hp;
}
// TODO(elmart): Check (page_size * nr) within off_T bounds.
- offset = (off_T)(page_size * nr);
+ off_T offset = (off_T)(page_size * nr); // offset in the file
if (vim_lseek(mfp->mf_fd, offset, SEEK_SET) != offset) {
PERROR(_("E296: Seek error in swap file write"));
return FAIL;
@@ -645,7 +599,7 @@ static int mf_write(memfile_T *mfp, bhdr_T *hp)
} else {
page_count = hp2->bh_page_count;
}
- size = page_size * page_count;
+ unsigned size = page_size * page_count; // number of bytes written
void *data = (hp2 == NULL) ? hp->bh_data : hp2->bh_data;
if ((unsigned)write_eintr(mfp->mf_fd, data, size) != size) {
/// Avoid repeating the error message, this mostly happens when the
@@ -682,8 +636,6 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp)
return OK;
}
- mf_blocknr_trans_item_T *np = xmalloc(sizeof(mf_blocknr_trans_item_T));
-
// Get a new number for the block.
// If the first item in the free list has sufficient pages, use its number.
// Otherwise use mf_blocknr_max.
@@ -706,15 +658,13 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp)
mfp->mf_blocknr_max += page_count;
}
- np->nt_old_bnum = hp->bh_bnum; // adjust number
- np->nt_new_bnum = new_bnum;
-
- mf_rem_hash(mfp, hp); // remove from old hash list
+ blocknr_T old_bnum = hp->bh_bnum; // adjust number
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL);
hp->bh_bnum = new_bnum;
- mf_ins_hash(mfp, hp); // insert in new hash list
+ pmap_put(int64_t)(&mfp->mf_hash, new_bnum, hp);
// Insert "np" into "mf_trans" hashtable with key "np->nt_old_bnum".
- mf_hash_add_item(&mfp->mf_trans, (mf_hashitem_T *)np);
+ map_put(int64_t, int64_t)(&mfp->mf_trans, old_bnum, new_bnum);
return OK;
}
@@ -725,20 +675,16 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp)
/// The old number When not found.
blocknr_T mf_trans_del(memfile_T *mfp, blocknr_T old_nr)
{
- mf_blocknr_trans_item_T *np =
- (mf_blocknr_trans_item_T *)mf_hash_find(&mfp->mf_trans, old_nr);
-
- if (np == NULL) { // not found
+ blocknr_T *num = map_ref(int64_t, int64_t)(&mfp->mf_trans, old_nr, false);
+ if (num == NULL) { // not found
return old_nr;
}
mfp->mf_neg_count--;
- blocknr_T new_bnum = np->nt_new_bnum;
+ blocknr_T new_bnum = *num;
// remove entry from the trans list
- mf_hash_rem_item(&mfp->mf_trans, (mf_hashitem_T *)np);
-
- xfree(np);
+ map_del(int64_t, int64_t)(&mfp->mf_trans, old_nr, NULL);
return new_bnum;
}
@@ -802,7 +748,7 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags)
emsg(_("E300: Swap file already exists (symlink attack?)"));
} else {
// try to open the file
- mfp->mf_fd = MCH_OPEN_RW((char *)mfp->mf_fname, flags | O_NOFOLLOW);
+ mfp->mf_fd = os_open(mfp->mf_fname, flags | O_NOFOLLOW, S_IREAD | S_IWRITE);
}
// If the file cannot be opened, use memory only
@@ -815,152 +761,3 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags)
return true;
}
-
-//
-// Implementation of mf_hashtab_T.
-//
-
-/// The number of buckets in the hashtable is increased by a factor of
-/// MHT_GROWTH_FACTOR when the average number of items per bucket
-/// exceeds 2 ^ MHT_LOG_LOAD_FACTOR.
-enum {
- MHT_LOG_LOAD_FACTOR = 6,
- MHT_GROWTH_FACTOR = 2, // must be a power of two
-};
-
-/// Initialize an empty hash table.
-static void mf_hash_init(mf_hashtab_T *mht)
-{
- CLEAR_POINTER(mht);
- mht->mht_buckets = mht->mht_small_buckets;
- mht->mht_mask = MHT_INIT_SIZE - 1;
-}
-
-/// Free the array of a hash table. Does not free the items it contains!
-/// The hash table must not be used again without another mf_hash_init() call.
-static void mf_hash_free(mf_hashtab_T *mht)
-{
- if (mht->mht_buckets != mht->mht_small_buckets) {
- xfree(mht->mht_buckets);
- }
-}
-
-/// Free the array of a hash table and all the items it contains.
-static void mf_hash_free_all(mf_hashtab_T *mht)
-{
- for (size_t idx = 0; idx <= mht->mht_mask; idx++) {
- mf_hashitem_T *next;
- for (mf_hashitem_T *mhi = mht->mht_buckets[idx]; mhi != NULL; mhi = next) {
- next = mhi->mhi_next;
- xfree(mhi);
- }
- }
-
- mf_hash_free(mht);
-}
-
-/// Find by key.
-///
-/// @return A pointer to a mf_hashitem_T or NULL if the item was not found.
-static mf_hashitem_T *mf_hash_find(mf_hashtab_T *mht, blocknr_T key)
-{
- mf_hashitem_T *mhi = mht->mht_buckets[(size_t)key & mht->mht_mask];
- while (mhi != NULL && mhi->mhi_key != key) {
- mhi = mhi->mhi_next;
- }
- return mhi;
-}
-
-/// Add item to hashtable. Item must not be NULL.
-static void mf_hash_add_item(mf_hashtab_T *mht, mf_hashitem_T *mhi)
-{
- size_t idx = (size_t)mhi->mhi_key & mht->mht_mask;
- mhi->mhi_next = mht->mht_buckets[idx];
- mhi->mhi_prev = NULL;
- if (mhi->mhi_next != NULL) {
- mhi->mhi_next->mhi_prev = mhi;
- }
- mht->mht_buckets[idx] = mhi;
-
- mht->mht_count++;
-
- /// Grow hashtable when we have more thank 2^MHT_LOG_LOAD_FACTOR
- /// items per bucket on average.
- if ((mht->mht_count >> MHT_LOG_LOAD_FACTOR) > mht->mht_mask) {
- mf_hash_grow(mht);
- }
-}
-
-/// Remove item from hashtable. Item must be non NULL and within hashtable.
-static void mf_hash_rem_item(mf_hashtab_T *mht, mf_hashitem_T *mhi)
-{
- if (mhi->mhi_prev == NULL) {
- mht->mht_buckets[(size_t)mhi->mhi_key & mht->mht_mask] =
- mhi->mhi_next;
- } else {
- mhi->mhi_prev->mhi_next = mhi->mhi_next;
- }
-
- if (mhi->mhi_next != NULL) {
- mhi->mhi_next->mhi_prev = mhi->mhi_prev;
- }
-
- mht->mht_count--;
-
- // We could shrink the table here, but it typically takes little memory,
- // so why bother?
-}
-
-/// Increase number of buckets in the hashtable by MHT_GROWTH_FACTOR and
-/// rehash items.
-static void mf_hash_grow(mf_hashtab_T *mht)
-{
- size_t size = (mht->mht_mask + 1) * MHT_GROWTH_FACTOR * sizeof(void *);
- mf_hashitem_T **buckets = xcalloc(1, size);
-
- int shift = 0;
- while ((mht->mht_mask >> shift) != 0) {
- shift++;
- }
-
- for (size_t i = 0; i <= mht->mht_mask; i++) {
- /// Traverse the items in the i-th original bucket and move them into
- /// MHT_GROWTH_FACTOR new buckets, preserving their relative order
- /// within each new bucket. Preserving the order is important because
- /// mf_get() tries to keep most recently used items at the front of
- /// each bucket.
- ///
- /// Here we strongly rely on the fact that hashes are computed modulo
- /// a power of two.
-
- mf_hashitem_T *tails[MHT_GROWTH_FACTOR];
- CLEAR_FIELD(tails);
-
- for (mf_hashitem_T *mhi = mht->mht_buckets[i];
- mhi != NULL; mhi = mhi->mhi_next) {
- size_t j = (mhi->mhi_key >> shift) & (MHT_GROWTH_FACTOR - 1);
- if (tails[j] == NULL) {
- buckets[i + (j << shift)] = mhi;
- tails[j] = mhi;
- mhi->mhi_prev = NULL;
- } else {
- tails[j]->mhi_next = mhi;
- mhi->mhi_prev = tails[j];
- tails[j] = mhi;
- }
- }
-
- for (size_t j = 0; j < MHT_GROWTH_FACTOR; j++) {
- if (tails[j] != NULL) {
- tails[j]->mhi_next = NULL;
- }
- }
- }
-
- if (mht->mht_buckets != mht->mht_small_buckets) {
- xfree(mht->mht_buckets);
- }
-
- mht->mht_buckets = buckets;
- mht->mht_mask = (mht->mht_mask + 1) * MHT_GROWTH_FACTOR - 1;
-}
diff --git a/src/nvim/memfile.h b/src/nvim/memfile.h
index 085fa22f12..687ac042a4 100644
--- a/src/nvim/memfile.h
+++ b/src/nvim/memfile.h
@@ -1,16 +1,25 @@
-#ifndef NVIM_MEMFILE_H
-#define NVIM_MEMFILE_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/memfile_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/memfile_defs.h" // IWYU pragma: export
/// flags for mf_sync()
-#define MFS_ALL 1 /// also sync blocks with negative numbers
-#define MFS_STOP 2 /// stop syncing when a character is available
-#define MFS_FLUSH 4 /// flushed file to disk
-#define MFS_ZERO 8 /// only write block 0
+enum {
+ MFS_ALL = 1, ///< also sync blocks with negative numbers
+ MFS_STOP = 2, ///< stop syncing when a character is available
+ MFS_FLUSH = 4, ///< flushed file to disk
+ MFS_ZERO = 8, ///< only write block 0
+};
+
+enum {
+ /// Minimal size for block 0 of a swap file.
+ /// NOTE: This depends on size of struct block0! It's not done with a sizeof(),
+ /// because struct block0 is defined in memline.c (Sorry).
+ /// The maximal block size is arbitrary.
+ MIN_SWAP_PAGE_SIZE = 1048,
+ MAX_SWAP_PAGE_SIZE = 50000,
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memfile.h.generated.h"
#endif
-#endif // NVIM_MEMFILE_H
diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h
index 53152c28f8..db68ecf039 100644
--- a/src/nvim/memfile_defs.h
+++ b/src/nvim/memfile_defs.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_MEMFILE_DEFS_H
-#define NVIM_MEMFILE_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/map_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
/// A block number.
///
@@ -15,93 +15,54 @@
/// with negative numbers are currently in memory only.
typedef int64_t blocknr_T;
-/// A hash item.
-///
-/// Items' keys are block numbers.
-/// Items in the same bucket are organized into a doubly-linked list.
-///
-/// Therefore, items can be arbitrary data structures beginning with pointers
-/// for the list and and a block number key.
-typedef struct mf_hashitem {
- struct mf_hashitem *mhi_next;
- struct mf_hashitem *mhi_prev;
- blocknr_T mhi_key;
-} mf_hashitem_T;
-
-/// Initial size for a hashtable.
-#define MHT_INIT_SIZE 64
-
-/// A chained hashtable with block numbers as keys and arbitrary data structures
-/// as items.
-///
-/// This is an intrusive data structure: we require that items begin with
-/// mf_hashitem_T which contains the key and linked list pointers. List of items
-/// in each bucket is doubly-linked.
-typedef struct mf_hashtab {
- size_t mht_mask; /// mask used to mod hash value to array index
- /// (nr of items in array is 'mht_mask + 1')
- size_t mht_count; /// number of items inserted
- mf_hashitem_T **mht_buckets; /// points to the array of buckets (can be
- /// mht_small_buckets or a newly allocated array
- /// when mht_small_buckets becomes too small)
- mf_hashitem_T *mht_small_buckets[MHT_INIT_SIZE]; /// initial buckets
-} mf_hashtab_T;
-
/// A block header.
///
/// There is a block header for each previously used block in the memfile.
///
/// The block may be linked in the used list OR in the free list.
-/// The used blocks are also kept in hash lists.
///
/// The used list is a doubly linked list, most recently used block first.
/// The blocks in the used list have a block of memory allocated.
-/// The hash lists are used to quickly find a block in the used list.
/// The free list is a single linked list, not sorted.
/// The blocks in the free list have no block of memory allocated and
/// the contents of the block in the file (if any) is irrelevant.
typedef struct bhdr {
- mf_hashitem_T bh_hashitem; /// header for hash table and key
-#define bh_bnum bh_hashitem.mhi_key /// block number, part of bh_hashitem
+ blocknr_T bh_bnum; ///< key used in hash table
- struct bhdr *bh_next; /// next block header in free or used list
- struct bhdr *bh_prev; /// previous block header in used list
- void *bh_data; /// pointer to memory (for used block)
- unsigned bh_page_count; /// number of pages in this block
+ void *bh_data; ///< pointer to memory (for used block)
+ unsigned bh_page_count; ///< number of pages in this block
#define BH_DIRTY 1U
#define BH_LOCKED 2U
- unsigned bh_flags; // BH_DIRTY or BH_LOCKED
+ unsigned bh_flags; ///< BH_DIRTY or BH_LOCKED
} bhdr_T;
-/// A block number translation list item.
-///
-/// When a block with a negative number is flushed to the file, it gets
-/// a positive number. Because the reference to the block is still the negative
-/// number, we remember the translation to the new positive number in the
-/// double linked trans lists. The structure is the same as the hash lists.
-typedef struct mf_blocknr_trans_item {
- mf_hashitem_T nt_hashitem; /// header for hash table and key
-#define nt_old_bnum nt_hashitem.mhi_key /// old, negative, number
- blocknr_T nt_new_bnum; /// new, positive, number
-} mf_blocknr_trans_item_T;
+typedef enum {
+ MF_DIRTY_NO = 0, ///< no dirty blocks
+ MF_DIRTY_YES, ///< there are dirty blocks
+ MF_DIRTY_YES_NOSYNC, ///< there are dirty blocks, do not sync yet
+} mfdirty_T;
/// A memory file.
typedef struct memfile {
- char *mf_fname; /// name of the file
- char *mf_ffname; /// idem, full path
- int mf_fd; /// file descriptor
- bhdr_T *mf_free_first; /// first block header in free list
- bhdr_T *mf_used_first; /// mru block header in used list
- bhdr_T *mf_used_last; /// lru block header in used list
- mf_hashtab_T mf_hash; /// hash lists
- mf_hashtab_T mf_trans; /// trans lists
- blocknr_T mf_blocknr_max; /// highest positive block number + 1
- blocknr_T mf_blocknr_min; /// lowest negative block number - 1
- blocknr_T mf_neg_count; /// number of negative blocks numbers
- blocknr_T mf_infile_count; /// number of pages in the file
- unsigned mf_page_size; /// number of bytes in a page
- bool mf_dirty; /// true if there are dirty blocks
-} memfile_T;
+ char *mf_fname; ///< name of the file
+ char *mf_ffname; ///< idem, full path
+ int mf_fd; ///< file descriptor
+ bhdr_T *mf_free_first; ///< first block header in free list
+
+ /// The used blocks are kept in mf_hash.
+ /// mf_hash are used to quickly find a block in the used list.
+ PMap(int64_t) mf_hash;
-#endif // NVIM_MEMFILE_DEFS_H
+ /// When a block with a negative number is flushed to the file, it gets
+ /// a positive number. Because the reference to the block is still the negative
+ /// number, we remember the translation to the new positive number.
+ Map(int64_t, int64_t) mf_trans;
+
+ blocknr_T mf_blocknr_max; ///< highest positive block number + 1
+ blocknr_T mf_blocknr_min; ///< lowest negative block number - 1
+ blocknr_T mf_neg_count; ///< number of negative blocks numbers
+ blocknr_T mf_infile_count; ///< number of pages in the file
+ unsigned mf_page_size; ///< number of bytes in a page
+ mfdirty_T mf_dirty;
+} memfile_T;
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 10a8195e7a..3c671121b7 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// for debugging
// #define CHECK(c, s) do { if (c) emsg(s); } while (0)
#define CHECK(c, s) do {} while (0)
@@ -39,14 +36,16 @@
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
+#include <sys/types.h>
#include <time.h>
#include <uv.h>
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -55,16 +54,17 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/input.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
@@ -72,31 +72,26 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/process.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/spell.h"
+#include "nvim/statusline.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifndef UNIX // it's in os/unix_defs.h for Unix
# include <time.h>
#endif
-typedef struct block0 ZERO_BL; // contents of the first block
-typedef struct pointer_block PTR_BL; // contents of a pointer block
-typedef struct data_block DATA_BL; // contents of a data block
-typedef struct pointer_entry PTR_EN; // block/line-count pair
-
enum {
DATA_ID = (('d' << 8) + 'a'), // data block id
PTR_ID = (('p' << 8) + 't'), // pointer block id
@@ -105,39 +100,43 @@ enum {
};
// pointer to a block, used in a pointer block
-struct pointer_entry {
+typedef struct {
blocknr_T pe_bnum; // block number
linenr_T pe_line_count; // number of lines in this branch
linenr_T pe_old_lnum; // lnum for this block (for recovery)
int pe_page_count; // number of pages in block pe_bnum
-};
+} PointerEntry;
// A pointer block contains a list of branches in the tree.
-struct pointer_block {
+typedef struct {
uint16_t pb_id; // ID for pointer block: PTR_ID
uint16_t pb_count; // number of pointers in this block
uint16_t pb_count_max; // maximum value for pb_count
- PTR_EN pb_pointer[1]; // list of pointers to blocks (actually longer)
+ PointerEntry pb_pointer[]; // list of pointers to blocks
// followed by empty space until end of page
-};
+} PointerBlock;
+
+// Value for pb_count_max.
+#define PB_COUNT_MAX(mfp) \
+ (uint16_t)((mfp->mf_page_size - offsetof(PointerBlock, pb_pointer)) / sizeof(PointerEntry))
// A data block is a leaf in the tree.
//
// The text of the lines is at the end of the block. The text of the first line
// in the block is put at the end, the text of the second line in front of it,
// etc. Thus the order of the lines is the opposite of the line number.
-struct data_block {
+typedef struct {
uint16_t db_id; // ID for data block: DATA_ID
unsigned db_free; // free space available
unsigned db_txt_start; // byte where text starts
unsigned db_txt_end; // byte just after data block
// linenr_T db_line_count;
long db_line_count; // number of lines in this block
- unsigned db_index[1]; // index for start of line (actually bigger)
+ unsigned db_index[]; // index for start of line
// followed by empty space up to db_txt_start
// followed by the text in the lines until
// end of page
-};
+} DataBlock;
// The low bits of db_index hold the actual index. The topmost bit is
// used for the global command to be able to mark a line.
@@ -149,7 +148,7 @@ struct data_block {
#define DB_INDEX_MASK (~DB_MARKED)
#define INDEX_SIZE (sizeof(unsigned)) // size of one db_index entry
-#define HEADER_SIZE (sizeof(DATA_BL) - INDEX_SIZE) // size of data block header
+#define HEADER_SIZE (offsetof(DataBlock, db_index)) // size of data block header
enum {
B0_FNAME_SIZE_ORG = 900, // what it was in older versions
@@ -162,37 +161,36 @@ enum {
// This won't detect a 64 bit machine that only swaps a byte in the top 32
// bits, but that is crazy anyway.
enum {
- B0_MAGIC_LONG = 0x30313233L,
- B0_MAGIC_INT = 0x20212223L,
- B0_MAGIC_SHORT = 0x10111213L,
+ B0_MAGIC_LONG = 0x30313233,
+ B0_MAGIC_INT = 0x20212223,
+ B0_MAGIC_SHORT = 0x10111213,
B0_MAGIC_CHAR = 0x55,
};
-// Block zero holds all info about the swap file.
-//
-// NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing
-// swap files unusable!
-//
-// If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in vim.h!!
-//
-// This block is built up of single bytes, to make it portable across
-// different machines. b0_magic_* is used to check the byte order and size of
-// variables, because the rest of the swap file is not portable.
-struct block0 {
- char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1.
- char b0_version[10]; // Vim version string
- char_u b0_page_size[4]; // number of bytes per page
- char_u b0_mtime[4]; // last modification time of file
- char_u b0_ino[4]; // inode of b0_fname
- char_u b0_pid[4]; // process id of creator (or 0)
- char_u b0_uname[B0_UNAME_SIZE]; // name of user (uid if no name)
- char_u b0_hname[B0_HNAME_SIZE]; // host name (if it has a name)
- char b0_fname[B0_FNAME_SIZE_ORG]; // name of file being edited
- long b0_magic_long; // check for byte order of long
- int b0_magic_int; // check for byte order of int
- int16_t b0_magic_short; // check for byte order of short
- char_u b0_magic_char; // check for last char
-};
+/// Block zero holds all info about the swapfile. This is the first block in the file.
+///
+/// NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing swapfiles unusable!
+///
+/// If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in memfile.h!!
+///
+/// This block is built up of single bytes, to make it portable across
+/// different machines. b0_magic_* is used to check the byte order and size of
+/// variables, because the rest of the swapfile is not portable.
+typedef struct {
+ char b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1.
+ char b0_version[10]; ///< Vim version string
+ char b0_page_size[4]; ///< number of bytes per page
+ char b0_mtime[4]; ///< last modification time of file
+ char b0_ino[4]; ///< inode of b0_fname
+ char b0_pid[4]; ///< process id of creator (or 0)
+ char b0_uname[B0_UNAME_SIZE]; ///< name of user (uid if no name)
+ char b0_hname[B0_HNAME_SIZE]; ///< host name (if it has a name)
+ char b0_fname[B0_FNAME_SIZE_ORG]; ///< name of file being edited
+ long b0_magic_long; ///< check for byte order of long
+ int b0_magic_int; ///< check for byte order of int
+ int16_t b0_magic_short; ///< check for byte order of short
+ char b0_magic_char; ///< check for last char
+} ZeroBlock;
// Note: b0_dirty and b0_flags are put at the end of the file name. For very
// long file names in older versions of Vim they are invalid.
@@ -209,8 +207,7 @@ struct block0 {
// EOL_MAC + 1.
#define B0_FF_MASK 3
-// Swap file is in directory of edited file. Used to find the file from
-// different mount points.
+// Swapfile is in directory of edited file. Used to find the file from different mount points.
#define B0_SAME_DIR 4
// The 'fileencoding' is at the end of b0_fname[], with a NUL in front of it.
@@ -240,17 +237,48 @@ typedef enum {
UB_SAME_DIR, // update the B0_SAME_DIR flag
} upd_block0_T;
+typedef enum {
+ SEA_CHOICE_NONE = 0,
+ SEA_CHOICE_READONLY = 1,
+ SEA_CHOICE_EDIT = 2,
+ SEA_CHOICE_RECOVER = 3,
+ SEA_CHOICE_DELETE = 4,
+ SEA_CHOICE_QUIT = 5,
+ SEA_CHOICE_ABORT = 6,
+} sea_choice_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memline.c.generated.h"
#endif
+static const char e_ml_get_invalid_lnum_nr[]
+ = N_("E315: ml_get: Invalid lnum: %" PRId64);
+static const char e_ml_get_cannot_find_line_nr_in_buffer_nr_str[]
+ = N_("E316: ml_get: Cannot find line %" PRId64 "in buffer %d %s");
+static const char e_pointer_block_id_wrong[]
+ = N_("E317: Pointer block id wrong");
+static const char e_pointer_block_id_wrong_two[]
+ = N_("E317: Pointer block id wrong 2");
+static const char e_pointer_block_id_wrong_three[]
+ = N_("E317: Pointer block id wrong 3");
+static const char e_pointer_block_id_wrong_four[]
+ = N_("E317: Pointer block id wrong 4");
+static const char e_line_number_out_of_range_nr_past_the_end[]
+ = N_("E322: Line number out of range: %" PRId64 " past the end");
+static const char e_line_count_wrong_in_block_nr[]
+ = N_("E323: Line count wrong in block %" PRId64);
+static const char e_warning_pointer_block_corrupted[]
+ = N_("E1364: Warning: Pointer block corrupted");
+
+#if __has_feature(address_sanitizer)
+# define ML_GET_ALLOC_LINES
+#endif
+
/// Open a new memline for "buf".
///
/// @return FAIL for failure, OK otherwise.
int ml_open(buf_T *buf)
{
- bhdr_T *hp = NULL;
-
// init fields in memline struct
buf->b_ml.ml_stack_size = 0; // no stack yet
buf->b_ml.ml_stack = NULL; // no stack yet
@@ -265,14 +293,14 @@ int ml_open(buf_T *buf)
buf->b_p_swf = false;
}
- // When 'updatecount' is non-zero swap file may be opened later.
+ // When 'updatecount' is non-zero swapfile may be opened later.
if (!buf->terminal && p_uc && buf->b_p_swf) {
buf->b_may_swap = true;
} else {
buf->b_may_swap = false;
}
- // Open the memfile. No swap file is created yet.
+ // Open the memfile. No swapfile is created yet.
memfile_T *mfp = mf_open(NULL, 0);
if (mfp == NULL) {
goto error;
@@ -283,12 +311,12 @@ int ml_open(buf_T *buf)
buf->b_ml.ml_line_count = 1;
// fill block0 struct and write page 0
- hp = mf_new(mfp, false, 1);
+ bhdr_T *hp = mf_new(mfp, false, 1);
if (hp->bh_bnum != 0) {
iemsg(_("E298: Didn't get block nr 0?"));
goto error;
}
- ZERO_BL *b0p = hp->bh_data;
+ ZeroBlock *b0p = hp->bh_data;
b0p->b0_id[0] = BLOCK0_ID0;
b0p->b0_id[1] = BLOCK0_ID1;
@@ -303,32 +331,31 @@ int ml_open(buf_T *buf)
b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0;
b0p->b0_flags = (char)(get_fileformat(buf) + 1);
set_b0_fname(b0p, buf);
- (void)os_get_username((char *)b0p->b0_uname, B0_UNAME_SIZE);
+ (void)os_get_username(b0p->b0_uname, B0_UNAME_SIZE);
b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL;
- os_get_hostname((char *)b0p->b0_hname, B0_HNAME_SIZE);
+ os_get_hostname(b0p->b0_hname, B0_HNAME_SIZE);
b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL;
- long_to_char(os_get_pid(), b0p->b0_pid);
+ long_to_char((long)os_get_pid(), b0p->b0_pid);
}
// Always sync block number 0 to disk, so we can check the file name in
- // the swap file in findswapname(). Don't do this for a help files or
+ // the swapfile in findswapname(). Don't do this for a help files or
// a spell buffer though.
// Only works when there's a swapfile, otherwise it's done when the file
// is created.
mf_put(mfp, hp, true, false);
- if (!buf->b_help && !B_SPELL(buf)) {
+ if (!buf->b_help && !buf->b_spell) {
(void)mf_sync(mfp, 0);
}
// Fill in root pointer block and write page 1.
- if ((hp = ml_new_ptr(mfp)) == NULL) {
- goto error;
- }
+ hp = ml_new_ptr(mfp);
+ assert(hp != NULL);
if (hp->bh_bnum != 1) {
iemsg(_("E298: Didn't get block nr 1?"));
goto error;
}
- PTR_BL *pp = hp->bh_data;
+ PointerBlock *pp = hp->bh_data;
pp->pb_count = 1;
pp->pb_pointer[0].pe_bnum = 2;
pp->pb_pointer[0].pe_page_count = 1;
@@ -343,11 +370,11 @@ int ml_open(buf_T *buf)
goto error;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
dp->db_index[0] = --dp->db_txt_start; // at end of block
dp->db_free -= 1 + (unsigned)INDEX_SIZE;
dp->db_line_count = 1;
- *((char_u *)dp + dp->db_txt_start) = NUL; // empty line
+ *((char *)dp + dp->db_txt_start) = NUL; // empty line
return OK;
@@ -363,17 +390,17 @@ error:
}
/// ml_setname() is called when the file name of "buf" has been changed.
-/// It may rename the swap file.
+/// It may rename the swapfile.
void ml_setname(buf_T *buf)
{
bool success = false;
memfile_T *mfp = buf->b_ml.ml_mfp;
- if (mfp->mf_fd < 0) { // there is no swap file yet
- // When 'updatecount' is 0 and 'noswapfile' there is no swap file.
- // For help files we will make a swap file now.
+ if (mfp->mf_fd < 0) { // there is no swapfile yet
+ // When 'updatecount' is 0 and 'noswapfile' there is no swapfile.
+ // For help files we will make a swapfile now.
if (p_uc != 0 && (cmdmod.cmod_flags & CMOD_NOSWAPFILE) == 0) {
- ml_open_file(buf); // create a swap file
+ ml_open_file(buf); // create a swapfile
}
return;
}
@@ -381,7 +408,7 @@ void ml_setname(buf_T *buf)
// Try all directories in the 'directory' option.
char *dirp = p_dir;
bool found_existing_dir = false;
- for (;;) {
+ while (true) {
if (*dirp == NUL) { // tried all directories, fail
break;
}
@@ -400,13 +427,13 @@ void ml_setname(buf_T *buf)
success = true;
break;
}
- // need to close the swap file before renaming
+ // need to close the swapfile before renaming
if (mfp->mf_fd >= 0) {
close(mfp->mf_fd);
mfp->mf_fd = -1;
}
- // try to rename the swap file
+ // try to rename the swapfile
if (vim_rename(mfp->mf_fname, fname) == 0) {
success = true;
mf_free_fnames(mfp);
@@ -417,10 +444,10 @@ void ml_setname(buf_T *buf)
xfree(fname); // this fname didn't work, try another
}
- if (mfp->mf_fd == -1) { // need to (re)open the swap file
+ if (mfp->mf_fd == -1) { // need to (re)open the swapfile
mfp->mf_fd = os_open(mfp->mf_fname, O_RDWR, 0);
if (mfp->mf_fd < 0) {
- // could not (re)open the swap file, what can we do????
+ // could not (re)open the swapfile, what can we do????
emsg(_("E301: Oops, lost the swap file!!!"));
return;
}
@@ -443,7 +470,7 @@ void ml_open_files(void)
}
}
-/// Open a swap file for an existing memfile, if there is no swap file yet.
+/// Open a swapfile for an existing memfile, if there is no swapfile yet.
/// If we are unable to find a file name, mf_fname will be NULL
/// and the memfile will be in memory only (no recovery possible).
void ml_open_file(buf_T *buf)
@@ -468,11 +495,11 @@ void ml_open_file(buf_T *buf)
// Try all directories in 'directory' option.
char *dirp = p_dir;
bool found_existing_dir = false;
- for (;;) {
+ while (true) {
if (*dirp == NUL) {
break;
}
- // There is a small chance that between choosing the swap file name
+ // There is a small chance that between choosing the swapfile name
// and creating it, another Vim creates the file. In that case the
// creation will fail and we will use another directory.
char *fname = findswapname(buf, &dirp, NULL, &found_existing_dir);
@@ -483,13 +510,15 @@ void ml_open_file(buf_T *buf)
continue;
}
if (mf_open_file(mfp, fname) == OK) { // consumes fname!
+ // don't sync yet in ml_sync_all()
+ mfp->mf_dirty = MF_DIRTY_YES_NOSYNC;
ml_upd_block0(buf, UB_SAME_DIR);
// Flush block zero, so others can read it
if (mf_sync(mfp, MFS_ZERO) == OK) {
// Mark all blocks that should be in the swapfile as dirty.
// Needed for when the 'swapfile' option was reset, so that
- // the swap file was deleted, and then on again.
+ // the swapfile was deleted, and then on again.
mf_set_dirty(mfp);
break;
}
@@ -506,12 +535,12 @@ void ml_open_file(buf_T *buf)
no_wait_return--;
}
- // don't try to open a swap file again
+ // don't try to open a swapfile again
buf->b_may_swap = false;
}
-/// If still need to create a swap file, and starting to edit a not-readonly
-/// file, or reading into an existing buffer, create a swap file now.
+/// If still need to create a swapfile, and starting to edit a not-readonly
+/// file, or reading into an existing buffer, create a swapfile now.
///
/// @param newfile reading file into new buffer
void check_need_swap(bool newfile)
@@ -528,14 +557,15 @@ void check_need_swap(bool newfile)
/// Close memline for buffer 'buf'.
///
-/// @param del_file if true, delete the swap file
+/// @param del_file if true, delete the swapfile
void ml_close(buf_T *buf, int del_file)
{
if (buf->b_ml.ml_mfp == NULL) { // not open
return;
}
mf_close(buf->b_ml.ml_mfp, del_file); // close the .swp file
- if (buf->b_ml.ml_line_lnum != 0 && (buf->b_ml.ml_flags & ML_LINE_DIRTY)) {
+ if (buf->b_ml.ml_line_lnum != 0
+ && (buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED))) {
xfree(buf->b_ml.ml_line_ptr);
}
xfree(buf->b_ml.ml_stack);
@@ -579,20 +609,20 @@ void ml_timestamp(buf_T *buf)
}
/// Checks whether the IDs in b0 are valid.
-static bool ml_check_b0_id(ZERO_BL *b0p)
+static bool ml_check_b0_id(ZeroBlock *b0p)
FUNC_ATTR_NONNULL_ALL
{
return b0p->b0_id[0] == BLOCK0_ID0 && b0p->b0_id[1] == BLOCK0_ID1;
}
/// Checks whether all strings in b0 are valid (i.e. nul-terminated).
-static bool ml_check_b0_strings(ZERO_BL *b0p)
+static bool ml_check_b0_strings(ZeroBlock *b0p)
FUNC_ATTR_NONNULL_ALL
{
return (memchr(b0p->b0_version, NUL, 10)
&& memchr(b0p->b0_uname, NUL, B0_UNAME_SIZE)
&& memchr(b0p->b0_hname, NUL, B0_HNAME_SIZE)
- && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V1086
+ && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT));
}
/// Update the timestamp or the B0_SAME_DIR flag of the .swp file.
@@ -604,7 +634,7 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what)
if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) {
return;
}
- ZERO_BL *b0p = hp->bh_data;
+ ZeroBlock *b0p = hp->bh_data;
if (ml_check_b0_id(b0p) == FAIL) {
iemsg(_("E304: ml_upd_block0(): Didn't get block 0??"));
} else {
@@ -617,10 +647,10 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what)
mf_put(mfp, hp, true, false);
}
-/// Write file name and timestamp into block 0 of a swap file.
+/// Write file name and timestamp into block 0 of a swapfile.
/// Also set buf->b_mtime.
/// Don't use NameBuff[]!!!
-static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
+static void set_b0_fname(ZeroBlock *b0p, buf_T *buf)
{
if (buf->b_ffname == NULL) {
b0p->b0_fname[0] = NUL;
@@ -632,7 +662,7 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
// editing the same file on different machines over a network.
// First replace home dir path with "~/" with home_replace().
// Then insert the user name to get "~user/".
- home_replace(NULL, buf->b_ffname, (char *)b0p->b0_fname,
+ home_replace(NULL, buf->b_ffname, b0p->b0_fname,
B0_FNAME_SIZE_CRYPT, true);
if (b0p->b0_fname[0] == '~') {
// If there is no user name or it is too long, don't use "~/"
@@ -654,8 +684,8 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
buf->b_mtime_read = buf->b_mtime;
buf->b_mtime_read_ns = buf->b_mtime_ns;
} else {
- long_to_char(0L, b0p->b0_mtime);
- long_to_char(0L, b0p->b0_ino);
+ long_to_char(0, b0p->b0_mtime);
+ long_to_char(0, b0p->b0_ino);
buf->b_mtime = 0;
buf->b_mtime_ns = 0;
buf->b_mtime_read = 0;
@@ -669,11 +699,11 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
add_b0_fenc(b0p, curbuf);
}
-/// Update the B0_SAME_DIR flag of the swap file. It's set if the file and the
+/// Update the B0_SAME_DIR flag of the swapfile. It's set if the file and the
/// swapfile for "buf" are in the same directory.
/// This is fail safe: if we are not sure the directories are equal the flag is
/// not set.
-static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf)
+static void set_b0_dir_flag(ZeroBlock *b0p, buf_T *buf)
{
if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) {
b0p->b0_flags |= B0_SAME_DIR;
@@ -683,7 +713,7 @@ static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf)
}
/// When there is room, add the 'fileencoding' to block zero.
-static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
+static void add_b0_fenc(ZeroBlock *b0p, buf_T *buf)
{
const int size = B0_FNAME_SIZE_NOCRYPT;
@@ -691,48 +721,46 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
if ((int)strlen(b0p->b0_fname) + n + 1 > size) {
b0p->b0_flags = (char)(b0p->b0_flags & ~B0_HAS_FENC);
} else {
- memmove((char *)b0p->b0_fname + size - n,
+ memmove(b0p->b0_fname + size - n,
buf->b_p_fenc, (size_t)n);
*(b0p->b0_fname + size - n - 1) = NUL;
b0p->b0_flags |= B0_HAS_FENC;
}
}
-/// Return true if the process with number "b0p->b0_pid" is still running.
-/// "swap_fname" is the name of the swap file, if it's from before a reboot then
-/// the result is false;
-static bool swapfile_process_running(const ZERO_BL *b0p, const char *swap_fname)
+/// Returns the PID of the process that owns the swapfile, if it is running.
+///
+/// @param b0p swapfile data
+/// @param swap_fname Name of the swapfile. If it's from before a reboot, the result is 0.
+///
+/// @return PID, or 0 if process is not running or the swapfile is from before a reboot.
+static int swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname)
{
FileInfo st;
double uptime;
- // If the system rebooted after when the swap file was written then the
+ // If the system rebooted after when the swapfile was written then the
// process can't be running now.
if (os_fileinfo(swap_fname, &st)
&& uv_uptime(&uptime) == 0
&& (Timestamp)st.stat.st_mtim.tv_sec < os_time() - (Timestamp)uptime) {
- return false;
+ return 0;
}
- return os_proc_running((int)char_to_long(b0p->b0_pid));
+ int pid = (int)char_to_long(b0p->b0_pid);
+ return os_proc_running(pid) ? pid : 0;
}
/// Try to recover curbuf from the .swp file.
///
-/// @param checkext if true, check the extension and detect whether it is a
-/// swap file.
+/// @param checkext if true, check the extension and detect whether it is a swapfile.
void ml_recover(bool checkext)
{
buf_T *buf = NULL;
memfile_T *mfp = NULL;
char *fname_used = NULL;
bhdr_T *hp = NULL;
- ZERO_BL *b0p;
- int b0_ff;
char *b0_fenc = NULL;
- PTR_BL *pp;
- DATA_BL *dp;
infoptr_T *ip;
bool directly;
- char *p;
bool serious_error = true;
int orig_file_status = NOTDONE;
@@ -740,8 +768,8 @@ void ml_recover(bool checkext)
int called_from_main = (curbuf->b_ml.ml_mfp == NULL);
int attr = HL_ATTR(HLF_E);
- // If the file name ends in ".s[a-w][a-z]" we assume this is the swap file.
- // Otherwise a search is done to find the swap file(s).
+ // If the file name ends in ".s[a-w][a-z]" we assume this is the swapfile.
+ // Otherwise a search is done to find the swapfile(s).
char *fname = curbuf->b_fname;
if (fname == NULL) { // When there is no file name
fname = "";
@@ -756,18 +784,18 @@ void ml_recover(bool checkext)
} else {
directly = false;
- // count the number of matching swap files
- len = recover_names(fname, false, 0, NULL);
- if (len == 0) { // no swap files found
+ // count the number of matching swapfiles
+ len = recover_names(fname, false, NULL, 0, NULL);
+ if (len == 0) { // no swapfiles found
semsg(_("E305: No swap file found for %s"), fname);
goto theend;
}
int i;
- if (len == 1) { // one swap file found, use it
+ if (len == 1) { // one swapfile found, use it
i = 1;
- } else { // several swap files found, choose
- // list the names of the swap files
- (void)recover_names(fname, true, 0, NULL);
+ } else { // several swapfiles found, choose
+ // list the names of the swapfiles
+ (void)recover_names(fname, true, NULL, 0, NULL);
msg_putchar('\n');
msg_puts(_("Enter number of swap file to use (0 to quit): "));
i = get_number(false, NULL);
@@ -775,8 +803,8 @@ void ml_recover(bool checkext)
goto theend;
}
}
- // get the swap file name that will be used
- (void)recover_names(fname, false, i, &fname_used);
+ // get the swapfile name that will be used
+ (void)recover_names(fname, false, NULL, i, &fname_used);
}
if (fname_used == NULL) {
goto theend; // user chose invalid number.
@@ -786,7 +814,7 @@ void ml_recover(bool checkext)
getout(1);
}
- // Allocate a buffer structure for the swap file that is used for recovery.
+ // Allocate a buffer structure for the swapfile that is used for recovery.
// Only the memline in it is really used.
buf = xmalloc(sizeof(buf_T));
@@ -799,8 +827,8 @@ void ml_recover(bool checkext)
buf->b_ml.ml_locked = NULL; // no locked block
buf->b_ml.ml_flags = 0;
- // open the memfile from the old swap file
- p = xstrdup(fname_used); // save "fname_used" for the message:
+ // open the memfile from the old swapfile
+ char *p = xstrdup(fname_used); // save "fname_used" for the message:
// mf_open() will consume "fname_used"!
mfp = mf_open(fname_used, O_RDONLY);
fname_used = p;
@@ -811,7 +839,7 @@ void ml_recover(bool checkext)
buf->b_ml.ml_mfp = mfp;
// The page size set in mf_open() might be different from the page size
- // used in the swap file, we must get it from block 0. But to read block
+ // used in the swapfile, we must get it from block 0. But to read block
// 0 we need a page size. Use the minimal size for block 0 here, it will
// be set to the real value below.
mfp->mf_page_size = MIN_SWAP_PAGE_SIZE;
@@ -820,16 +848,16 @@ void ml_recover(bool checkext)
if ((hp = mf_get(mfp, 0, 1)) == NULL) {
msg_start();
msg_puts_attr(_("Unable to read block 0 from "), attr | MSG_HIST);
- msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST);
+ msg_outtrans(mfp->mf_fname, attr | MSG_HIST);
msg_puts_attr(_("\nMaybe no changes were made or Vim did not update the swap file."),
attr | MSG_HIST);
msg_end();
goto theend;
}
- b0p = hp->bh_data;
+ ZeroBlock *b0p = hp->bh_data;
if (strncmp(b0p->b0_version, "VIM 3.0", 7) == 0) {
msg_start();
- msg_outtrans_attr(mfp->mf_fname, MSG_HIST);
+ msg_outtrans(mfp->mf_fname, MSG_HIST);
msg_puts_attr(_(" cannot be used with this version of Vim.\n"),
MSG_HIST);
msg_puts_attr(_("Use Vim version 3.0.\n"), MSG_HIST);
@@ -842,13 +870,13 @@ void ml_recover(bool checkext)
}
if (b0_magic_wrong(b0p)) {
msg_start();
- msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST);
+ msg_outtrans(mfp->mf_fname, attr | MSG_HIST);
msg_puts_attr(_(" cannot be used on this computer.\n"),
attr | MSG_HIST);
msg_puts_attr(_("The file was created on "), attr | MSG_HIST);
// avoid going past the end of a corrupted hostname
b0p->b0_fname[0] = NUL;
- msg_puts_attr((char *)b0p->b0_hname, attr | MSG_HIST);
+ msg_puts_attr(b0p->b0_hname, attr | MSG_HIST);
msg_puts_attr(_(",\nor the file has been damaged."), attr | MSG_HIST);
msg_end();
goto theend;
@@ -862,14 +890,14 @@ void ml_recover(bool checkext)
mf_new_page_size(mfp, (unsigned)char_to_long(b0p->b0_page_size));
if (mfp->mf_page_size < previous_page_size) {
msg_start();
- msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST);
+ msg_outtrans(mfp->mf_fname, attr | MSG_HIST);
msg_puts_attr(_(" has been damaged (page size is smaller than minimum value).\n"),
attr | MSG_HIST);
msg_end();
goto theend;
}
off_T size;
- if ((size = vim_lseek(mfp->mf_fd, (off_T)0L, SEEK_END)) <= 0) {
+ if ((size = vim_lseek(mfp->mf_fd, 0, SEEK_END)) <= 0) {
mfp->mf_blocknr_max = 0; // no file or empty file
} else {
mfp->mf_blocknr_max = size / mfp->mf_page_size;
@@ -884,29 +912,29 @@ void ml_recover(bool checkext)
b0p = hp->bh_data;
}
- // If .swp file name given directly, use name from swap file for buffer.
+ // If .swp file name given directly, use name from swapfile for buffer.
if (directly) {
- expand_env((char *)b0p->b0_fname, NameBuff, MAXPATHL);
+ expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
goto theend;
}
}
home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, true);
- smsg(_("Using swap file \"%s\""), NameBuff);
+ smsg(0, _("Using swap file \"%s\""), NameBuff);
if (buf_spname(curbuf) != NULL) {
xstrlcpy(NameBuff, buf_spname(curbuf), MAXPATHL);
} else {
home_replace(NULL, curbuf->b_ffname, NameBuff, MAXPATHL, true);
}
- smsg(_("Original file \"%s\""), NameBuff);
+ smsg(0, _("Original file \"%s\""), NameBuff);
msg_putchar('\n');
- // check date of swap file and original file
+ // check date of swapfile and original file
FileInfo org_file_info;
FileInfo swp_file_info;
- long mtime = char_to_long(b0p->b0_mtime);
+ int mtime = (int)char_to_long(b0p->b0_mtime);
if (curbuf->b_ffname != NULL
&& os_fileinfo(curbuf->b_ffname, &org_file_info)
&& ((os_fileinfo(mfp->mf_fname, &swp_file_info)
@@ -918,11 +946,11 @@ void ml_recover(bool checkext)
ui_flush();
// Get the 'fileformat' and 'fileencoding' from block zero.
- b0_ff = (b0p->b0_flags & B0_FF_MASK);
+ int b0_ff = (b0p->b0_flags & B0_FF_MASK);
if (b0p->b0_flags & B0_HAS_FENC) {
int fnsize = B0_FNAME_SIZE_NOCRYPT;
- for (p = (char *)b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; p--) {}
+ for (p = b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; p--) {}
b0_fenc = xstrnsave(p, (size_t)(b0p->b0_fname + fnsize - p));
}
@@ -932,22 +960,22 @@ void ml_recover(bool checkext)
// Now that we are sure that the file is going to be recovered, clear the
// contents of the current buffer.
while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
// Try reading the original file to obtain the values of 'fileformat',
// 'fileencoding', etc. Ignore errors. The text itself is not used.
if (curbuf->b_ffname != NULL) {
- orig_file_status = readfile(curbuf->b_ffname, NULL, (linenr_T)0,
- (linenr_T)0, (linenr_T)MAXLNUM, NULL, READ_NEW, false);
+ orig_file_status = readfile(curbuf->b_ffname, NULL, 0,
+ 0, MAXLNUM, NULL, READ_NEW, false);
}
- // Use the 'fileformat' and 'fileencoding' as stored in the swap file.
+ // Use the 'fileformat' and 'fileencoding' as stored in the swapfile.
if (b0_ff != 0) {
set_fileformat(b0_ff - 1, OPT_LOCAL);
}
if (b0_fenc != NULL) {
- set_option_value_give_err("fenc", 0L, b0_fenc, OPT_LOCAL);
+ set_option_value_give_err("fenc", CSTR_AS_OPTVAL(b0_fenc), OPT_LOCAL);
xfree(b0_fenc);
}
unchanged(curbuf, true, true);
@@ -957,10 +985,10 @@ void ml_recover(bool checkext)
linenr_T lnum = 0; // append after line 0 in curbuf
linenr_T line_count = 0;
int idx = 0; // start with first index in block 1
- long error = 0;
- buf->b_ml.ml_stack_top = 0; // -V1048
+ int error = 0;
+ buf->b_ml.ml_stack_top = 0;
buf->b_ml.ml_stack = NULL;
- buf->b_ml.ml_stack_size = 0; // -V1048
+ buf->b_ml.ml_stack_size = 0;
bool cannot_open = (curbuf->b_ffname == NULL);
@@ -976,11 +1004,23 @@ void ml_recover(bool checkext)
goto theend;
}
error++;
- ml_append(lnum++, _("???MANY LINES MISSING"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???MANY LINES MISSING"), 0, true);
} else { // there is a block
- pp = hp->bh_data;
+ PointerBlock *pp = hp->bh_data;
if (pp->pb_id == PTR_ID) { // it is a pointer block
+ bool ptr_block_error = false;
+ if (pp->pb_count_max != PB_COUNT_MAX(mfp)) {
+ ptr_block_error = true;
+ pp->pb_count_max = PB_COUNT_MAX(mfp);
+ }
+ if (pp->pb_count > pp->pb_count_max) {
+ ptr_block_error = true;
+ pp->pb_count = pp->pb_count_max;
+ }
+ if (ptr_block_error) {
+ emsg(_(e_warning_pointer_block_corrupted));
+ }
+
// check line count when using pointer block first time
if (idx == 0 && line_count != 0) {
for (int i = 0; i < (int)pp->pb_count; i++) {
@@ -988,14 +1028,12 @@ void ml_recover(bool checkext)
}
if (line_count != 0) {
error++;
- ml_append(lnum++, _("???LINE COUNT WRONG"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???LINE COUNT WRONG"), 0, true);
}
}
if (pp->pb_count == 0) {
- ml_append(lnum++, _("???EMPTY BLOCK"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???EMPTY BLOCK"), 0, true);
error++;
} else if (idx < (int)pp->pb_count) { // go a block deeper
if (pp->pb_pointer[idx].pe_bnum < 0) {
@@ -1014,8 +1052,7 @@ void ml_recover(bool checkext)
}
if (cannot_open) {
error++;
- ml_append(lnum++, _("???LINES MISSING"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???LINES MISSING"), 0, true);
}
idx++; // get same block again for next index
continue;
@@ -1034,7 +1071,7 @@ void ml_recover(bool checkext)
continue;
}
} else { // not a pointer block
- dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
if (dp->db_id != DATA_ID) { // block id wrong
if (bnum == 1) {
semsg(_("E310: Block 1 ID wrong (%s not a .swp file?)"),
@@ -1042,50 +1079,65 @@ void ml_recover(bool checkext)
goto theend;
}
error++;
- ml_append(lnum++, _("???BLOCK MISSING"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???BLOCK MISSING"), 0, true);
} else {
- // it is a data block
- // Append all the lines in this block
+ // It is a data block.
+ // Append all the lines in this block.
bool has_error = false;
- // check length of block
- // if wrong, use length in pointer block
+
+ // Check the length of the block.
+ // If wrong, use the length given in the pointer block.
if (page_count * mfp->mf_page_size != dp->db_txt_end) {
ml_append(lnum++,
_("??? from here until ???END lines" " may be messed up"),
- (colnr_T)0, true);
+ 0, true);
error++;
has_error = true;
dp->db_txt_end = page_count * mfp->mf_page_size;
}
- // make sure there is a NUL at the end of the block
- *((char_u *)dp + dp->db_txt_end - 1) = NUL;
+ // Make sure there is a NUL at the end of the block so we
+ // don't go over the end when copying text.
+ *((char *)dp + dp->db_txt_end - 1) = NUL;
- // check number of lines in block
- // if wrong, use count in data block
+ // Check the number of lines in the block.
+ // If wrong, use the count in the data block.
if (line_count != dp->db_line_count) {
ml_append(lnum++,
_("??? from here until ???END lines"
" may have been inserted/deleted"),
- (colnr_T)0, true);
+ 0, true);
error++;
has_error = true;
}
+ bool did_questions = false;
for (int i = 0; i < dp->db_line_count; i++) {
+ if ((char *)&(dp->db_index[i]) >= (char *)dp + dp->db_txt_start) {
+ // line count must be wrong
+ error++;
+ ml_append(lnum++, _("??? lines may be missing"), 0, true);
+ break;
+ }
+
int txt_start = (dp->db_index[i] & DB_INDEX_MASK);
if (txt_start <= (int)HEADER_SIZE
|| txt_start >= (int)dp->db_txt_end) {
- p = "???";
error++;
+ // avoid lots of lines with "???"
+ if (did_questions) {
+ continue;
+ }
+ did_questions = true;
+ p = "???";
} else {
+ did_questions = false;
p = (char *)dp + txt_start;
}
- ml_append(lnum++, p, (colnr_T)0, true);
+ ml_append(lnum++, p, 0, true);
}
if (has_error) {
- ml_append(lnum++, _("???END"), (colnr_T)0, true);
+ ml_append(lnum++, _("???END"), 0, true);
}
}
}
@@ -1111,7 +1163,7 @@ void ml_recover(bool checkext)
// Recovering an empty file results in two lines and the first line is
// empty. Don't set the modified flag then.
if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) {
- changed_internal();
+ changed_internal(curbuf);
buf_inc_changedtick(curbuf);
}
} else {
@@ -1121,7 +1173,7 @@ void ml_recover(bool checkext)
int i = strcmp(p, ml_get(idx + lnum));
xfree(p);
if (i != 0) {
- changed_internal();
+ changed_internal(curbuf);
buf_inc_changedtick(curbuf);
break;
}
@@ -1142,25 +1194,25 @@ void ml_recover(bool checkext)
emsg(_("E311: Recovery Interrupted"));
} else if (error) {
no_wait_return++;
- msg(">>>>>>>>>>>>>");
+ msg(">>>>>>>>>>>>>", 0);
emsg(_("E312: Errors detected while recovering; look for lines starting with ???"));
no_wait_return--;
- msg(_("See \":help E312\" for more information."));
- msg(">>>>>>>>>>>>>");
+ msg(_("See \":help E312\" for more information."), 0);
+ msg(">>>>>>>>>>>>>", 0);
} else {
if (curbuf->b_changed) {
- msg(_("Recovery completed. You should check if everything is OK."));
+ msg(_("Recovery completed. You should check if everything is OK."), 0);
msg_puts(_("\n(You might want to write out this file under another name\n"));
msg_puts(_("and run diff with the original file to check for changes)"));
} else {
- msg(_("Recovery completed. Buffer contents equals file contents."));
+ msg(_("Recovery completed. Buffer contents equals file contents."), 0);
}
msg_puts(_("\nYou may want to delete the .swp file now."));
if (swapfile_process_running(b0p, fname_used)) {
// Warn there could be an active Vim on the same file, the user may
// want to kill it.
msg_puts(_("\nNote: process STILL RUNNING: "));
- msg_outnum(char_to_long(b0p->b0_pid));
+ msg_outnum((int)char_to_long(b0p->b0_pid));
}
msg_puts("\n\n");
cmdline_row = msg_row;
@@ -1176,7 +1228,7 @@ theend:
}
mf_close(mfp, false); // will also xfree(mfp->mf_fname)
}
- if (buf != NULL) { // may be NULL if swap file not found.
+ if (buf != NULL) { // may be NULL if swapfile not found.
xfree(buf->b_ml.ml_stack);
xfree(buf);
}
@@ -1188,20 +1240,22 @@ theend:
}
}
-/// Find the names of swap files in current directory and the directory given
+/// Find the names of swapfiles in current directory and the directory given
/// with the 'directory' option.
///
/// Used to:
-/// - list the swap files for "vim -r"
-/// - count the number of swap files when recovering
-/// - list the swap files when recovering
-/// - find the name of the n'th swap file when recovering
-///
-/// @param fname base for swap file name
-/// @param list when true, list the swap file names
-/// @param nr when non-zero, return nr'th swap file name
+/// - list the swapfiles for "vim -r"
+/// - count the number of swapfiles when recovering
+/// - list the swapfiles when recovering
+/// - list the swapfiles for swapfilelist()
+/// - find the name of the n'th swapfile when recovering
+///
+/// @param fname base for swapfile name
+/// @param do_list when true, list the swapfile names
+/// @param ret_list when not NULL add file names to it
+/// @param nr when non-zero, return nr'th swapfile name
/// @param fname_out result when "nr" > 0
-int recover_names(char *fname, int list, int nr, char **fname_out)
+int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fname_out)
{
int num_names;
char *(names[6]);
@@ -1216,18 +1270,17 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
if (fname != NULL) {
#ifdef HAVE_READLINK
- // Expand symlink in the file name, because the swap file is created
+ // Expand symlink in the file name, because the swapfile is created
// with the actual file instead of with the symlink.
- if (resolve_symlink(fname, fname_buf) == OK) {
- fname_res = fname_buf;
- } else
-#endif
+ fname_res = (resolve_symlink(fname, fname_buf) == OK) ? fname_buf : fname;
+#else
fname_res = fname;
+#endif
}
- if (list) {
+ if (do_list) {
// use msg() to start the scrolling properly
- msg(_("Swap files found:"));
+ msg(_("Swap files found:"), 0);
msg_putchar('\n');
}
@@ -1285,9 +1338,9 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
num_files = 0;
}
- // When no swap file found, wildcard expansion might have failed (e.g.
+ // When no swapfile found, wildcard expansion might have failed (e.g.
// not able to execute the shell).
- // Try finding a swap file by simply adding ".swp" to the file name.
+ // Try finding a swapfile by simply adding ".swp" to the file name.
if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) {
char *swapname = modname(fname_res, ".swp", true);
if (swapname != NULL) {
@@ -1301,9 +1354,11 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
}
}
- // remove swapfile name of the current buffer, it must be ignored
+ // Remove swapfile name of the current buffer, it must be ignored.
+ // But keep it for swapfilelist().
if (curbuf->b_ml.ml_mfp != NULL
- && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) {
+ && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL
+ && ret_list == NULL) {
for (int i = 0; i < num_files; i++) {
// Do not expand wildcards, on Windows would try to expand
// "%tmp%" in "%tmp%file"
@@ -1328,7 +1383,7 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
*fname_out = xstrdup(files[nr - 1 + num_files - file_count]);
dirp = ""; // stop searching
}
- } else if (list) {
+ } else if (do_list) {
if (dir_name[0] == '.' && dir_name[1] == NUL) {
if (fname == NULL) {
msg_puts(_(" In current directory:\n"));
@@ -1343,10 +1398,10 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
if (num_files) {
for (int i = 0; i < num_files; i++) {
- // print the swap file name
- msg_outnum((long)++file_count);
+ // print the swapfile name
+ msg_outnum(++file_count);
msg_puts(". ");
- msg_puts((const char *)path_tail(files[i]));
+ msg_puts(path_tail(files[i]));
msg_putchar('\n');
(void)swapfile_info(files[i]);
}
@@ -1354,6 +1409,11 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
msg_puts(_(" -- none --\n"));
}
ui_flush();
+ } else if (ret_list != NULL) {
+ for (int i = 0; i < num_files; i++) {
+ char *name = concat_fnames(dir_name, files[i], true);
+ tv_list_append_allocated_string(ret_list, name);
+ }
} else {
file_count += num_files;
}
@@ -1392,15 +1452,16 @@ char *make_percent_swname(const char *dir, const char *name)
return d;
}
-static bool process_still_running;
+// PID of swapfile owner, or zero if not running.
+static int process_running;
-/// This is used by the swapinfo() function.
+/// For Vimscript "swapinfo()".
///
/// @return information found in swapfile "fname" in dictionary "d".
-void get_b0_dict(const char *fname, dict_T *d)
+void swapfile_dict(const char *fname, dict_T *d)
{
int fd;
- struct block0 b0;
+ ZeroBlock b0;
if ((fd = os_open(fname, O_RDONLY, 0)) >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
@@ -1411,14 +1472,14 @@ void get_b0_dict(const char *fname, dict_T *d)
} else {
// We have swap information.
tv_dict_add_str_len(d, S_LEN("version"), b0.b0_version, 10);
- tv_dict_add_str_len(d, S_LEN("user"), (char *)b0.b0_uname,
+ tv_dict_add_str_len(d, S_LEN("user"), b0.b0_uname,
B0_UNAME_SIZE);
- tv_dict_add_str_len(d, S_LEN("host"), (char *)b0.b0_hname,
+ tv_dict_add_str_len(d, S_LEN("host"), b0.b0_hname,
B0_HNAME_SIZE);
- tv_dict_add_str_len(d, S_LEN("fname"), (char *)b0.b0_fname,
+ tv_dict_add_str_len(d, S_LEN("fname"), b0.b0_fname,
B0_FNAME_SIZE_ORG);
- tv_dict_add_nr(d, S_LEN("pid"), char_to_long(b0.b0_pid));
+ tv_dict_add_nr(d, S_LEN("pid"), swapfile_process_running(&b0, fname));
tv_dict_add_nr(d, S_LEN("mtime"), char_to_long(b0.b0_mtime));
tv_dict_add_nr(d, S_LEN("dirty"), b0.b0_dirty ? 1 : 0);
tv_dict_add_nr(d, S_LEN("inode"), char_to_long(b0.b0_ino));
@@ -1432,27 +1493,26 @@ void get_b0_dict(const char *fname, dict_T *d)
}
}
-/// Give information about an existing swap file.
+/// Loads info from swapfile `fname`, and displays it to the user.
///
/// @return timestamp (0 when unknown).
static time_t swapfile_info(char *fname)
{
assert(fname != NULL);
- int fd;
- struct block0 b0;
+ ZeroBlock b0;
time_t x = (time_t)0;
#ifdef UNIX
char uname[B0_UNAME_SIZE];
#endif
- // print the swap file date
+ // print the swapfile date
FileInfo file_info;
if (os_fileinfo(fname, &file_info)) {
#ifdef UNIX
// print name of owner of the file
if (os_get_uname((uv_uid_t)file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) {
msg_puts(_(" owned by: "));
- msg_outtrans(uname);
+ msg_outtrans(uname, 0);
msg_puts(_(" dated: "));
} else {
msg_puts(_(" dated: "));
@@ -1466,7 +1526,7 @@ static time_t swapfile_info(char *fname)
}
// print the original file name
- fd = os_open(fname, O_RDONLY, 0);
+ int fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
if (strncmp(b0.b0_version, "VIM 3.0", 7) == 0) {
@@ -1480,7 +1540,7 @@ static time_t swapfile_info(char *fname)
if (b0.b0_fname[0] == NUL) {
msg_puts(_("[No Name]"));
} else {
- msg_outtrans((char *)b0.b0_fname);
+ msg_outtrans(b0.b0_fname, 0);
}
msg_puts(_("\n modified: "));
@@ -1488,7 +1548,7 @@ static time_t swapfile_info(char *fname)
if (*(b0.b0_uname) != NUL) {
msg_puts(_("\n user name: "));
- msg_outtrans((char *)b0.b0_uname);
+ msg_outtrans(b0.b0_uname, 0);
}
if (*(b0.b0_hname) != NUL) {
@@ -1497,15 +1557,14 @@ static time_t swapfile_info(char *fname)
} else {
msg_puts(_("\n host name: "));
}
- msg_outtrans((char *)b0.b0_hname);
+ msg_outtrans(b0.b0_hname, 0);
}
- if (char_to_long(b0.b0_pid) != 0L) {
+ if (char_to_long(b0.b0_pid) != 0) {
msg_puts(_("\n process ID: "));
- msg_outnum(char_to_long(b0.b0_pid));
- if (swapfile_process_running(&b0, (const char *)fname)) {
+ msg_outnum((int)char_to_long(b0.b0_pid));
+ if ((process_running = swapfile_process_running(&b0, fname))) {
msg_puts(_(" (STILL RUNNING)"));
- process_still_running = true;
}
}
@@ -1525,13 +1584,12 @@ static time_t swapfile_info(char *fname)
return x;
}
-/// @return true if the swap file looks OK and there are no changes, thus it
-/// can be safely deleted.
+/// @return true if the swapfile looks OK and there are no changes, thus it can be safely deleted.
static bool swapfile_unchanged(char *fname)
{
- struct block0 b0;
+ ZeroBlock b0;
- // Swap file must exist.
+ // Swapfile must exist.
if (!os_path_exists(fname)) {
return false;
}
@@ -1573,7 +1631,7 @@ static bool swapfile_unchanged(char *fname)
}
// process must be known and not running.
- if (char_to_long(b0.b0_pid) == 0L || swapfile_process_running(&b0, fname)) {
+ if (char_to_long(b0.b0_pid) == 0 || swapfile_process_running(&b0, fname)) {
ret = false;
}
@@ -1589,7 +1647,7 @@ static int recov_file_names(char **names, char *path, int prepend_dot)
{
int num_names = 0;
- // May also add the file name with a dot prepended, for swap file in same
+ // May also add the file name with a dot prepended, for swapfile in same
// dir as original file.
if (prepend_dot) {
names[num_names] = modname(path, ".sw?", true);
@@ -1599,7 +1657,7 @@ static int recov_file_names(char **names, char *path, int prepend_dot)
num_names++;
}
- // Form the normal swap file name pattern by appending ".sw?".
+ // Form the normal swapfile name pattern by appending ".sw?".
names[num_names] = concat_fnames(path, ".sw?", false);
if (num_names >= 1) { // check if we have the same name twice
char *p = names[num_names - 1];
@@ -1633,7 +1691,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
}
ml_flush_line(buf); // flush buffered line
// flush locked block
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH);
+ (void)ml_find_line(buf, 0, ML_FLUSH);
if (bufIsChanged(buf) && check_file && mf_need_trans(buf->b_ml.ml_mfp)
&& buf->b_ffname != NULL) {
// If the original file does not exist anymore or has been changed
@@ -1648,7 +1706,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
need_check_timestamps = true; // give message later
}
}
- if (buf->b_ml.ml_mfp->mf_dirty) {
+ if (buf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES) {
(void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0)
| (do_fsync && bufIsChanged(buf) ? MFS_FLUSH : 0));
if (check_char && os_char_avail()) { // character available now
@@ -1660,7 +1718,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
/// sync one buffer, including negative blocks
///
-/// after this all the blocks are in the swap file
+/// after this all the blocks are in the swapfile
///
/// Used for the :preserve command and when the original file has been
/// changed or deleted.
@@ -1683,7 +1741,7 @@ void ml_preserve(buf_T *buf, int message, bool do_fsync)
got_int = false;
ml_flush_line(buf); // flush buffered line
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block
+ (void)ml_find_line(buf, 0, ML_FLUSH); // flush locked block
int status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0));
// stack is invalid after mf_sync(.., MFS_ALL)
@@ -1710,7 +1768,7 @@ void ml_preserve(buf_T *buf, int message, bool do_fsync)
CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum");
lnum = buf->b_ml.ml_locked_high + 1;
}
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block
+ (void)ml_find_line(buf, 0, ML_FLUSH); // flush locked block
// sync the updated pointer blocks
if (mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)) == FAIL) {
status = FAIL;
@@ -1722,7 +1780,7 @@ theend:
if (message) {
if (status == OK) {
- msg(_("File preserved"));
+ msg(_("File preserved"), 0);
} else {
emsg(_("E314: Preserve failed"));
}
@@ -1735,20 +1793,40 @@ theend:
// line2 = ml_get(2); // line1 is now invalid!
// Make a copy of the line if necessary.
-/// @return a pointer to a (read-only copy of a) line.
+/// @return a pointer to a (read-only copy of a) line in curbuf.
///
/// On failure an error message is given and IObuff is returned (to avoid
/// having to check for error everywhere).
char *ml_get(linenr_T lnum)
{
- return ml_get_buf(curbuf, lnum, false);
+ return ml_get_buf_impl(curbuf, lnum, false);
+}
+
+/// @return a pointer to a (read-only copy of a) line.
+///
+/// This is the same as ml_get(), but taking in the buffer
+/// as an argument.
+char *ml_get_buf(buf_T *buf, linenr_T lnum)
+{
+ return ml_get_buf_impl(buf, lnum, false);
+}
+
+/// Like `ml_get_buf`, but allow the line to be mutated in place.
+///
+/// This is very limited. Generally ml_replace_buf()
+/// should be used to modify a line.
+///
+/// @return a pointer to a line in the buffer
+char *ml_get_buf_mut(buf_T *buf, linenr_T lnum)
+{
+ return ml_get_buf_impl(buf, lnum, true);
}
/// @return pointer to position "pos".
char *ml_get_pos(const pos_T *pos)
FUNC_ATTR_NONNULL_ALL
{
- return ml_get_buf(curbuf, pos->lnum, false) + pos->col;
+ return ml_get_buf(curbuf, pos->lnum) + pos->col;
}
/// @return codepoint at pos. pos must be either valid or have col set to MAXCOL!
@@ -1765,7 +1843,7 @@ int gchar_pos(pos_T *pos)
/// @param will_change true mark the buffer dirty (chars in the line will be changed)
///
/// @return a pointer to a line in a specific buffer
-char *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change)
+static char *ml_get_buf_impl(buf_T *buf, linenr_T lnum, bool will_change)
FUNC_ATTR_NONNULL_ALL
{
static int recursive = 0;
@@ -1776,11 +1854,10 @@ char *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change)
// Avoid giving this message for a recursive call, may happen when
// the GUI redraws part of the text.
recursive++;
- siemsg(_("E315: ml_get: invalid lnum: %" PRId64), (int64_t)lnum);
+ siemsg(_(e_ml_get_invalid_lnum_nr), (int64_t)lnum);
recursive--;
}
ml_flush_line(buf);
- buf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
errorret:
STRCPY(questions, "???");
buf->b_ml.ml_line_lnum = lnum;
@@ -1812,30 +1889,48 @@ errorret:
recursive++;
get_trans_bufname(buf);
shorten_dir(NameBuff);
- siemsg(_("E316: ml_get: cannot find line %" PRId64 " in buffer %d %s"),
+ siemsg(_(e_ml_get_cannot_find_line_nr_in_buffer_nr_str),
(int64_t)lnum, buf->b_fnum, NameBuff);
recursive--;
}
goto errorret;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
char *ptr = (char *)dp + (dp->db_index[lnum - buf->b_ml.ml_locked_low] & DB_INDEX_MASK);
buf->b_ml.ml_line_ptr = ptr;
buf->b_ml.ml_line_lnum = lnum;
- buf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
+ buf->b_ml.ml_flags &= ~(ML_LINE_DIRTY | ML_ALLOCATED);
}
if (will_change) {
buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);
+#ifdef ML_GET_ALLOC_LINES
+ if (buf->b_ml.ml_flags & ML_ALLOCATED) {
+ // can't make the change in the data block
+ buf->b_ml.ml_flags |= ML_LINE_DIRTY;
+ }
+#endif
ml_add_deleted_len_buf(buf, buf->b_ml.ml_line_ptr, -1);
}
+#ifdef ML_GET_ALLOC_LINES
+ if ((buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) == 0) {
+ // make sure the text is in allocated memory
+ buf->b_ml.ml_line_ptr = xstrdup(buf->b_ml.ml_line_ptr);
+ buf->b_ml.ml_flags |= ML_ALLOCATED;
+ if (will_change) {
+ // can't make the change in the data block
+ buf->b_ml.ml_flags |= ML_LINE_DIRTY;
+ }
+ }
+#endif
return buf->b_ml.ml_line_ptr;
}
/// Check if a line that was just obtained by a call to ml_get
/// is in allocated memory.
+/// This ignores ML_ALLOCATED to get the same behavior as without ML_GET_ALLOC_LINES.
int ml_line_alloced(void)
{
return curbuf->b_ml.ml_flags & ML_LINE_DIRTY;
@@ -1917,7 +2012,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// This also fills the stack with the blocks from the root to the data block
// This also releases any locked block.
bhdr_T *hp;
- if ((hp = ml_find_line(buf, lnum == 0 ? (linenr_T)1 : lnum,
+ if ((hp = ml_find_line(buf, lnum == 0 ? 1 : lnum,
ML_INSERT)) == NULL) {
return FAIL;
}
@@ -1933,7 +2028,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// get line count (number of indexes in current block) before the insertion
int line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low;
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
// If
// - there is not enough room in the current block
@@ -2016,14 +2111,10 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
int lines_moved;
int data_moved = 0; // init to shut up gcc
int total_moved = 0; // init to shut up gcc
- DATA_BL *dp_right, *dp_left;
int stack_idx;
bool in_left;
- int lineadd;
- blocknr_T bnum_left, bnum_right;
linenr_T lnum_left, lnum_right;
- int pb_idx;
- PTR_BL *pp_new;
+ PointerBlock *pp_new;
// We are going to allocate a new data block. Depending on the
// situation it will be put to the left or right of the existing
@@ -2067,10 +2158,10 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
line_count_left = line_count;
line_count_right = 0;
}
- dp_right = hp_right->bh_data;
- dp_left = hp_left->bh_data;
- bnum_left = hp_left->bh_bnum;
- bnum_right = hp_right->bh_bnum;
+ DataBlock *dp_right = hp_right->bh_data;
+ DataBlock *dp_left = hp_left->bh_data;
+ blocknr_T bnum_left = hp_left->bh_bnum;
+ blocknr_T bnum_right = hp_right->bh_bnum;
page_count_left = (int)hp_left->bh_page_count;
page_count_right = (int)hp_right->bh_page_count;
@@ -2149,20 +2240,20 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// flush the old data block
// set ml_locked_lineadd to 0, because the updating of the
// pointer blocks is done below
- lineadd = buf->b_ml.ml_locked_lineadd;
+ int lineadd = buf->b_ml.ml_locked_lineadd;
buf->b_ml.ml_locked_lineadd = 0;
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush data block
+ (void)ml_find_line(buf, 0, ML_FLUSH); // flush data block
// update pointer blocks for the new data block
for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; stack_idx--) {
infoptr_T *ip = &(buf->b_ml.ml_stack[stack_idx]);
- pb_idx = ip->ip_index;
+ int pb_idx = ip->ip_index;
if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) {
return FAIL;
}
- PTR_BL *pp = hp->bh_data; // must be pointer block
+ PointerBlock *pp = hp->bh_data; // must be pointer block
if (pp->pb_id != PTR_ID) {
- iemsg(_("E317: pointer block id wrong 3"));
+ iemsg(_(e_pointer_block_id_wrong_three));
mf_put(mfp, hp, false, false);
return FAIL;
}
@@ -2173,7 +2264,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
if (pb_idx + 1 < (int)pp->pb_count) {
memmove(&pp->pb_pointer[pb_idx + 2],
&pp->pb_pointer[pb_idx + 1],
- (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN));
+ (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PointerEntry));
}
pp->pb_count++;
pp->pb_pointer[pb_idx].pe_line_count = line_count_left;
@@ -2212,7 +2303,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// allocate a new pointer block
// move some of the pointer into the new block
// prepare for updating the parent block
- for (;;) { // do this twice when splitting block 1
+ while (true) { // do this twice when splitting block 1
hp_new = ml_new_ptr(mfp);
if (hp_new == NULL) { // TODO(vim): try to fix tree
return FAIL;
@@ -2246,7 +2337,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
if (total_moved) {
memmove(&pp_new->pb_pointer[0],
&pp->pb_pointer[pb_idx + 1],
- (size_t)(total_moved) * sizeof(PTR_EN));
+ (size_t)(total_moved) * sizeof(PointerEntry));
pp_new->pb_count = (uint16_t)total_moved;
pp->pb_count = (uint16_t)(pp->pb_count - (total_moved - 1));
pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right;
@@ -2297,7 +2388,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
}
// The line was inserted below 'lnum'
- ml_updatechunk(buf, lnum + 1, (long)len, ML_CHNK_ADDLINE);
+ ml_updatechunk(buf, lnum + 1, len, ML_CHNK_ADDLINE);
return OK;
}
@@ -2315,13 +2406,13 @@ void ml_add_deleted_len_buf(buf_T *buf, char *ptr, ssize_t len)
if (len == -1 || len > maxlen) {
len = maxlen;
}
- curbuf->deleted_bytes += (size_t)len + 1;
- curbuf->deleted_bytes2 += (size_t)len + 1;
- if (curbuf->update_need_codepoints) {
- mb_utflen(ptr, (size_t)len, &curbuf->deleted_codepoints,
- &curbuf->deleted_codeunits);
- curbuf->deleted_codepoints++; // NL char
- curbuf->deleted_codeunits++;
+ buf->deleted_bytes += (size_t)len + 1;
+ buf->deleted_bytes2 += (size_t)len + 1;
+ if (buf->update_need_codepoints) {
+ mb_utflen(ptr, (size_t)len, &buf->deleted_codepoints,
+ &buf->deleted_codeunits);
+ buf->deleted_codepoints++; // NL char
+ buf->deleted_codeunits++;
}
}
@@ -2353,22 +2444,21 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy)
return FAIL;
}
- bool readlen = true;
-
if (copy) {
line = xstrdup(line);
}
- if (buf->b_ml.ml_line_lnum != lnum) { // other line buffered
- ml_flush_line(buf); // flush it
- } else if (buf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated
- ml_add_deleted_len_buf(buf, buf->b_ml.ml_line_ptr, -1);
- readlen = false; // already added the length
- xfree(buf->b_ml.ml_line_ptr); // free it
+ if (buf->b_ml.ml_line_lnum != lnum) {
+ // another line is buffered, flush it
+ ml_flush_line(buf);
+ }
+
+ if (kv_size(buf->update_callbacks)) {
+ ml_add_deleted_len_buf(buf, ml_get_buf(buf, lnum), -1);
}
- if (readlen && kv_size(buf->update_callbacks)) {
- ml_add_deleted_len_buf(buf, ml_get_buf(buf, lnum, false), -1);
+ if (buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) {
+ xfree(buf->b_ml.ml_line_ptr); // free allocated line
}
buf->b_ml.ml_line_ptr = line;
@@ -2392,6 +2482,19 @@ int ml_delete(linenr_T lnum, bool message)
return ml_delete_int(curbuf, lnum, message);
}
+/// Delete line `lnum` in buffer
+///
+/// @note The caller of this function should probably also call changed_lines() after this.
+///
+/// @param message Show "--No lines in buffer--" message.
+///
+/// @return FAIL for failure, OK otherwise
+int ml_delete_buf(buf_T *buf, linenr_T lnum, bool message)
+{
+ ml_flush_line(buf);
+ return ml_delete_int(buf, lnum, message);
+}
+
static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
{
if (lnum < 1 || lnum > buf->b_ml.ml_line_count) {
@@ -2408,7 +2511,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
set_keep_msg(_(no_lines_msg), 0);
}
- int i = ml_replace((linenr_T)1, "", true);
+ int i = ml_replace_buf(buf, 1, "", true);
buf->b_ml.ml_flags |= ML_EMPTY;
return i;
@@ -2427,7 +2530,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
return FAIL;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
// compute line count (number of entries in block) before the delete
int count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 2;
int idx = lnum - buf->b_ml.ml_locked_low;
@@ -2435,11 +2538,11 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
buf->b_ml.ml_line_count--;
int line_start = ((dp->db_index[idx]) & DB_INDEX_MASK);
- long line_size;
+ int line_size;
if (idx == 0) { // first line in block, text at the end
- line_size = dp->db_txt_end - (unsigned)line_start;
+ line_size = (int)(dp->db_txt_end - (unsigned)line_start);
} else {
- line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - (unsigned)line_start;
+ line_size = (int)(((dp->db_index[idx - 1]) & DB_INDEX_MASK) - (unsigned)line_start);
}
// Line should always have an NL char internally (represented as NUL),
@@ -2464,9 +2567,9 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) {
return FAIL;
}
- PTR_BL *pp = hp->bh_data; // must be pointer block
+ PointerBlock *pp = hp->bh_data; // must be pointer block
if (pp->pb_id != PTR_ID) {
- iemsg(_("E317: pointer block id wrong 4"));
+ iemsg(_(e_pointer_block_id_wrong_four));
mf_put(mfp, hp, false, false);
return FAIL;
}
@@ -2476,7 +2579,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
} else {
if (count != idx) { // move entries after the deleted one
memmove(&pp->pb_pointer[idx], &pp->pb_pointer[idx + 1],
- (size_t)(count - idx) * sizeof(PTR_EN));
+ (size_t)(count - idx) * sizeof(PointerEntry));
}
mf_put(mfp, hp, true, false);
@@ -2536,7 +2639,7 @@ void ml_setmarked(linenr_T lnum)
if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) {
return; // give error message?
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
dp->db_index[lnum - curbuf->b_ml.ml_locked_low] |= DB_MARKED;
curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
}
@@ -2545,7 +2648,7 @@ void ml_setmarked(linenr_T lnum)
linenr_T ml_firstmarked(void)
{
if (curbuf->b_ml.ml_mfp == NULL) {
- return (linenr_T)0;
+ return 0;
}
// The search starts with lowest_marked line. This is the last line where
@@ -2556,9 +2659,9 @@ linenr_T ml_firstmarked(void)
// block This also releases any locked block.
bhdr_T *hp;
if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) {
- return (linenr_T)0; // give error message?
+ return 0; // give error message?
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
for (int i = lnum - curbuf->b_ml.ml_locked_low;
lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) {
@@ -2571,7 +2674,7 @@ linenr_T ml_firstmarked(void)
}
}
- return (linenr_T)0;
+ return 0;
}
/// clear all DB_MARKED flags
@@ -2590,7 +2693,7 @@ void ml_clearmarked(void)
if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) {
return; // give error message?
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
for (int i = lnum - curbuf->b_ml.ml_locked_low;
lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) {
@@ -2639,7 +2742,7 @@ static void ml_flush_line(buf_T *buf)
if (hp == NULL) {
siemsg(_("E320: Cannot find line %" PRId64), (int64_t)lnum);
} else {
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
int idx = lnum - buf->b_ml.ml_locked_low;
int start = ((dp->db_index[idx]) & DB_INDEX_MASK);
char *old_line = (char *)dp + start;
@@ -2677,7 +2780,7 @@ static void ml_flush_line(buf_T *buf)
memmove(old_line - extra, new_line, (size_t)new_len);
buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);
// The else case is already covered by the insert and delete
- ml_updatechunk(buf, lnum, (long)extra, ML_CHNK_UPDLINE);
+ ml_updatechunk(buf, lnum, extra, ML_CHNK_UPDLINE);
} else {
// Cannot do it in one data block: Delete and append.
// Append first, because ml_delete_int() cannot delete the
@@ -2693,8 +2796,11 @@ static void ml_flush_line(buf_T *buf)
xfree(new_line);
entered = false;
+ } else if (buf->b_ml.ml_flags & ML_ALLOCATED) {
+ xfree(buf->b_ml.ml_line_ptr);
}
+ buf->b_ml.ml_flags &= ~(ML_LINE_DIRTY | ML_ALLOCATED);
buf->b_ml.ml_line_lnum = 0;
buf->b_ml.ml_line_offset = 0;
}
@@ -2704,7 +2810,7 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
{
assert(page_count >= 0);
bhdr_T *hp = mf_new(mfp, negative, (unsigned)page_count);
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
dp->db_id = DATA_ID;
dp->db_txt_start = dp->db_txt_end = (unsigned)page_count * mfp->mf_page_size;
dp->db_free = dp->db_txt_start - (unsigned)HEADER_SIZE;
@@ -2717,10 +2823,10 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
static bhdr_T *ml_new_ptr(memfile_T *mfp)
{
bhdr_T *hp = mf_new(mfp, false, 1);
- PTR_BL *pp = hp->bh_data;
+ PointerBlock *pp = hp->bh_data;
pp->pb_id = PTR_ID;
pp->pb_count = 0;
- pp->pb_count_max = (uint16_t)((mfp->mf_page_size - sizeof(PTR_BL)) / sizeof(PTR_EN) + 1);
+ pp->pb_count_max = PB_COUNT_MAX(mfp);
return hp;
}
@@ -2740,7 +2846,6 @@ static bhdr_T *ml_new_ptr(memfile_T *mfp)
/// @return NULL for failure, pointer to block header otherwise
static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
{
- PTR_BL *pp;
bhdr_T *hp;
int top;
@@ -2805,7 +2910,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
buf->b_ml.ml_stack_top = 0; // start at the root
}
// search downwards in the tree until a data block is found
- for (;;) {
+ while (true) {
if ((hp = mf_get(mfp, bnum, (unsigned)page_count)) == NULL) {
goto error_noblock;
}
@@ -2817,7 +2922,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
high--;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
if (dp->db_id == DATA_ID) { // data block
buf->b_ml.ml_locked = hp;
buf->b_ml.ml_locked_low = low;
@@ -2827,9 +2932,9 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
return hp;
}
- pp = (PTR_BL *)(dp); // must be pointer block
+ PointerBlock *pp = (PointerBlock *)(dp); // must be pointer block
if (pp->pb_id != PTR_ID) {
- iemsg(_("E317: pointer block id wrong"));
+ iemsg(_(e_pointer_block_id_wrong));
goto error_block;
}
@@ -2867,10 +2972,10 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
}
if (idx >= (int)pp->pb_count) { // past the end: something wrong!
if (lnum > buf->b_ml.ml_line_count) {
- siemsg(_("E322: line number out of range: %" PRId64 " past the end"),
+ siemsg(_(e_line_number_out_of_range_nr_past_the_end),
(int64_t)lnum - buf->b_ml.ml_line_count);
} else {
- siemsg(_("E323: line count wrong in block %" PRId64), bnum);
+ siemsg(_(e_line_count_wrong_in_block_nr), bnum);
}
goto error_block;
}
@@ -2937,10 +3042,10 @@ static void ml_lineadd(buf_T *buf, int count)
if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) {
break;
}
- PTR_BL *pp = hp->bh_data; // must be pointer block
+ PointerBlock *pp = hp->bh_data; // must be pointer block
if (pp->pb_id != PTR_ID) {
mf_put(mfp, hp, false, false);
- iemsg(_("E317: pointer block id wrong 2"));
+ iemsg(_(e_pointer_block_id_wrong_two));
break;
}
pp->pb_pointer[ip->ip_index].pe_line_count += count;
@@ -2969,7 +3074,7 @@ int resolve_symlink(const char *fname, char *buf)
// Put the result so far in tmp[], starting with the original name.
xstrlcpy(tmp, fname, MAXPATHL);
- for (;;) {
+ while (true) {
// Limit symlink depth to 100, catch recursive loops.
if (++depth == 100) {
semsg(_("E773: Symlink loop for \"%s\""), fname);
@@ -3017,7 +3122,7 @@ int resolve_symlink(const char *fname, char *buf)
}
#endif
-/// Make swap file name out of the file name and a directory name.
+/// Make swapfile name out of the file name and a directory name.
///
/// @return pointer to allocated memory or NULL.
char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
@@ -3026,9 +3131,9 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
#ifdef HAVE_READLINK
char fname_buf[MAXPATHL];
- // Expand symlink in the file name, so that we put the swap file with the
+ // Expand symlink in the file name, so that we put the swapfile with the
// actual file instead of with the symlink.
- if (resolve_symlink(fname, (char *)fname_buf) == OK) {
+ if (resolve_symlink(fname, fname_buf) == OK) {
fname_res = fname_buf;
}
#endif
@@ -3047,7 +3152,7 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
return r;
}
- // Prepend a '.' to the swap file name for the current directory.
+ // Prepend a '.' to the swapfile name for the current directory.
char *r = modname(fname_res, ".swp",
dir_name[0] == '.' && dir_name[1] == NUL);
if (r == NULL) { // out of memory
@@ -3059,14 +3164,11 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
return s;
}
-/// Get file name to use for swap file or backup file.
-/// Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir'
-/// option "dname".
-/// - If "dname" is ".", return "fname" (swap file in dir of file).
-/// - If "dname" starts with "./", insert "dname" in "fname" (swap file
-/// relative to dir of file).
-/// - Otherwise, prepend "dname" to the tail of "fname" (swap file in specific
-/// dir).
+/// Get file name to use for swapfile or backup file.
+/// Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir' option "dname".
+/// - If "dname" is ".", return "fname" (swapfile in dir of file).
+/// - If "dname" starts with "./", insert "dname" in "fname" (swapfile relative to dir of file).
+/// - Otherwise, prepend "dname" to the tail of "fname" (swapfile in specific dir).
///
/// The return value is an allocated string and can be NULL.
///
@@ -3097,10 +3199,10 @@ char *get_file_in_dir(char *fname, char *dname)
return retval;
}
-/// Print the ATTENTION message: info about an existing swap file.
+/// Print the ATTENTION message: info about an existing swapfile.
///
/// @param buf buffer being edited
-/// @param fname swap file name
+/// @param fname swapfile name
static void attention_message(buf_T *buf, char *fname)
{
assert(buf->b_fname != NULL);
@@ -3112,7 +3214,7 @@ static void attention_message(buf_T *buf, char *fname)
msg_puts("\"\n");
const time_t swap_mtime = swapfile_info(fname);
msg_puts(_("While opening file \""));
- msg_outtrans(buf->b_fname);
+ msg_outtrans(buf->b_fname, 0);
msg_puts("\"\n");
FileInfo file_info;
if (!os_fileinfo(buf->b_fname, &file_info)) {
@@ -3134,10 +3236,10 @@ static void attention_message(buf_T *buf, char *fname)
" Quit, or continue with caution.\n"));
msg_puts(_("(2) An edit session for this file crashed.\n"));
msg_puts(_(" If this is the case, use \":recover\" or \"vim -r "));
- msg_outtrans(buf->b_fname);
+ msg_outtrans(buf->b_fname, 0);
msg_puts(_("\"\n to recover the changes (see \":help recovery\").\n"));
msg_puts(_(" If you did this already, delete the swap file \""));
- msg_outtrans(fname);
+ msg_outtrans(fname, 0);
msg_puts(_("\"\n to avoid this message.\n"));
cmdline_row = msg_row;
no_wait_return--;
@@ -3145,15 +3247,8 @@ static void attention_message(buf_T *buf, char *fname)
/// Trigger the SwapExists autocommands.
///
-/// @return a value for equivalent to do_dialog() (see below):
-/// 0: still need to ask for a choice
-/// 1: open read-only
-/// 2: edit anyway
-/// 3: recover
-/// 4: delete it
-/// 5: quit
-/// 6: abort
-static int do_swapexists(buf_T *buf, char *fname)
+/// @return a value for equivalent to do_dialog().
+static sea_choice_T do_swapexists(buf_T *buf, char *fname)
{
set_vim_var_string(VV_SWAPNAME, fname, -1);
set_vim_var_string(VV_SWAPCHOICE, NULL, -1);
@@ -3168,23 +3263,23 @@ static int do_swapexists(buf_T *buf, char *fname)
switch (*get_vim_var_str(VV_SWAPCHOICE)) {
case 'o':
- return 1;
+ return SEA_CHOICE_READONLY;
case 'e':
- return 2;
+ return SEA_CHOICE_EDIT;
case 'r':
- return 3;
+ return SEA_CHOICE_RECOVER;
case 'd':
- return 4;
+ return SEA_CHOICE_DELETE;
case 'q':
- return 5;
+ return SEA_CHOICE_QUIT;
case 'a':
- return 6;
+ return SEA_CHOICE_ABORT;
}
- return 0;
+ return SEA_CHOICE_NONE;
}
-/// Find out what name to use for the swap file for buffer 'buf'.
+/// Find out what name to use for the swapfile for buffer 'buf'.
///
/// Several names are tried to find one that does not exist. Last directory in
/// option is automatically created.
@@ -3193,24 +3288,23 @@ static int do_swapexists(buf_T *buf, char *fname)
/// not being able to open the swap or undo file.
/// @note May trigger SwapExists autocmd, pointers may change!
///
-/// @param[in] buf Buffer for which swap file names needs to be found.
+/// @param[in] buf Buffer for which swapfile names needs to be found.
/// @param[in,out] dirp Pointer to a list of directories. When out of memory,
/// is set to NULL. Is advanced to the next directory in
/// the list otherwise.
-/// @param[in] old_fname Allowed existing swap file name. Except for this
+/// @param[in] old_fname Allowed existing swapfile name. Except for this
/// case, name of the non-existing file is used.
/// @param[in,out] found_existing_dir If points to true, then new directory
-/// for swap file is not created. At first
+/// for swapfile is not created. At first
/// findswapname() call this argument must
/// point to false. This parameter may only
/// be set to true by this function, it is
/// never set to false.
///
-/// @return [allocated] Name of the swap file.
+/// @return [allocated] Name of the swapfile.
static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_existing_dir)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
- size_t n;
char *buf_fname = buf->b_fname;
// Isolate a directory name from *dirp and put it in dir_name.
@@ -3219,10 +3313,11 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
char *dir_name = xmalloc(dir_len);
(void)copy_option_part(dirp, dir_name, dir_len, ",");
- // we try different names until we find one that does not exist yet
+ // We try different swapfile names until we find one that does not exist yet.
char *fname = makeswapname(buf_fname, buf->b_ffname, buf, dir_name);
- for (;;) {
+ while (true) {
+ size_t n;
if (fname == NULL) { // must be out of memory
break;
}
@@ -3231,7 +3326,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
break;
}
// check if the swapfile already exists
- // Extra security check: When a swap file is a symbolic link, this
+ // Extra security check: When a swapfile is a symbolic link, this
// is most likely a symlink attack.
FileInfo file_info;
bool file_or_link_found = os_fileinfo_link(fname, &file_info);
@@ -3250,37 +3345,36 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
// Give an error message, unless recovering, no file name, we are
// viewing a help file or when the path of the file is different
// (happens when all .swp files are in one directory).
- if (!recoverymode && buf_fname != NULL
- && !buf->b_help && !(buf->b_flags & BF_DUMMY)) {
+ if (!recoverymode && buf_fname != NULL && !buf->b_help && !(buf->b_flags & BF_DUMMY)) {
int fd;
- struct block0 b0;
+ ZeroBlock b0;
int differ = false;
- // Try to read block 0 from the swap file to get the original
- // file name (and inode number).
+ // Try to read block 0 from the swapfile to get the original file name (and inode number).
fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
+ process_running = swapfile_process_running(&b0, fname);
+
// If the swapfile has the same directory as the
// buffer don't compare the directory names, they can
// have a different mountpoint.
if (b0.b0_flags & B0_SAME_DIR) {
if (path_fnamecmp(path_tail(buf->b_ffname),
- path_tail((char *)b0.b0_fname)) != 0
+ path_tail(b0.b0_fname)) != 0
|| !same_directory(fname, buf->b_ffname)) {
// Symlinks may point to the same file even
// when the name differs, need to check the
// inode too.
- expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL);
+ expand_env(b0.b0_fname, NameBuff, MAXPATHL);
if (fnamecmp_ino(buf->b_ffname, NameBuff,
char_to_long(b0.b0_ino))) {
differ = true;
}
}
} else {
- // The name in the swap file may be
- // "~user/path/file". Expand it first.
- expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL);
+ // The name in the swapfile may be "~user/path/file". Expand it first.
+ expand_env(b0.b0_fname, NameBuff, MAXPATHL);
if (fnamecmp_ino(buf->b_ffname, NameBuff,
char_to_long(b0.b0_ino))) {
differ = true;
@@ -3290,18 +3384,18 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
close(fd);
}
- // give the ATTENTION message when there is an old swap file
- // for the current file, and the buffer was not recovered.
+ // Show the ATTENTION message when:
+ // - there is an old swapfile for the current file
+ // - the buffer was not recovered
if (differ == false && !(curbuf->b_flags & BF_RECOVERED)
&& vim_strchr(p_shm, SHM_ATTENTION) == NULL) {
- int choice = 0;
+ sea_choice_T choice = SEA_CHOICE_NONE;
- process_still_running = false;
- // It's safe to delete the swap file if all these are true:
+ // It's safe to delete the swapfile if all these are true:
// - the edited file exists
- // - the swap file has no changes and looks OK
+ // - the swapfile has no changes and looks OK
if (os_path_exists(buf->b_fname) && swapfile_unchanged(fname)) {
- choice = 4;
+ choice = SEA_CHOICE_DELETE;
if (p_verbose > 0) {
verb_msg(_("Found a swap file that is not useful, deleting it"));
}
@@ -3309,14 +3403,20 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
// If there is a SwapExists autocommand and we can handle the
// response, trigger it. It may return 0 to ask the user anyway.
- if (choice == 0
+ if (choice == SEA_CHOICE_NONE
&& swap_exists_action != SEA_NONE
&& has_autocmd(EVENT_SWAPEXISTS, buf_fname, buf)) {
choice = do_swapexists(buf, fname);
}
- if (choice == 0) {
- // Show info about the existing swap file.
+ if (choice == SEA_CHOICE_NONE && swap_exists_action == SEA_READONLY) {
+ // always open readonly.
+ choice = SEA_CHOICE_READONLY;
+ }
+
+ process_running = 0; // Set by attention_message..swapfile_info.
+ if (choice == SEA_CHOICE_NONE) {
+ // Show info about the existing swapfile.
attention_message(buf, fname);
// We don't want a 'q' typed at the more-prompt
@@ -3328,7 +3428,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
flush_buffers(FLUSH_TYPEAHEAD);
}
- if (swap_exists_action != SEA_NONE && choice == 0) {
+ if (swap_exists_action != SEA_NONE && choice == SEA_CHOICE_NONE) {
const char *const sw_msg_1 = _("Swap file \"");
const char *const sw_msg_2 = _("\" already exists!");
@@ -3342,65 +3442,66 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
memcpy(name, sw_msg_1, sw_msg_1_len + 1);
home_replace(NULL, fname, &name[sw_msg_1_len], fname_len, true);
xstrlcat(name, sw_msg_2, name_len);
- choice = do_dialog(VIM_WARNING, _("VIM - ATTENTION"),
- name,
- process_still_running
- ? _("&Open Read-Only\n&Edit anyway\n&Recover"
- "\n&Quit\n&Abort") :
- _("&Open Read-Only\n&Edit anyway\n&Recover"
- "\n&Delete it\n&Quit\n&Abort"),
- 1, NULL, false);
-
- if (process_still_running && choice >= 4) {
- choice++; // Skip missing "Delete it" button.
+ int dialog_result
+ = do_dialog(VIM_WARNING,
+ _("VIM - ATTENTION"),
+ name,
+ process_running
+ ? _("&Open Read-Only\n&Edit anyway\n&Recover\n&Quit\n&Abort")
+ : _("&Open Read-Only\n&Edit anyway\n&Recover\n&Delete it\n&Quit\n&Abort"),
+ 1, NULL, false);
+
+ if (process_running && dialog_result >= 4) {
+ // compensate for missing "Delete it" button
+ dialog_result++;
}
+ choice = (sea_choice_T)dialog_result;
xfree(name);
// pretend screen didn't scroll, need redraw anyway
msg_reset_scroll();
}
- if (choice > 0) {
- switch (choice) {
- case 1:
- buf->b_p_ro = true;
- break;
- case 2:
- break;
- case 3:
- swap_exists_action = SEA_RECOVER;
- break;
- case 4:
- os_remove(fname);
- break;
- case 5:
- swap_exists_action = SEA_QUIT;
- break;
- case 6:
- swap_exists_action = SEA_QUIT;
- got_int = true;
- break;
- }
-
- // If the file was deleted this fname can be used.
- if (!os_path_exists(fname)) {
- break;
- }
- } else {
+ switch (choice) {
+ case SEA_CHOICE_READONLY: // "Open Read-Only"
+ buf->b_p_ro = true;
+ break;
+ case SEA_CHOICE_EDIT: // "Edit anyway"
+ break;
+ case SEA_CHOICE_RECOVER: // "Recover"
+ swap_exists_action = SEA_RECOVER;
+ break;
+ case SEA_CHOICE_DELETE: // "Delete it"
+ os_remove(fname);
+ break;
+ case SEA_CHOICE_QUIT: // "Quit"
+ swap_exists_action = SEA_QUIT;
+ break;
+ case SEA_CHOICE_ABORT: // "Abort"
+ swap_exists_action = SEA_QUIT;
+ got_int = true;
+ break;
+ case SEA_CHOICE_NONE:
msg_puts("\n");
if (msg_silent == 0) {
// call wait_return() later
need_wait_return = true;
}
+ break;
+ }
+
+ // If the swapfile was deleted this `fname` can be used.
+ if (choice != SEA_CHOICE_NONE && !os_path_exists(fname)) {
+ break;
}
}
}
}
- // Change the ".swp" extension to find another file that can be used.
+ // Permute the ".swp" extension to find a unique swapfile name.
// First decrement the last char: ".swo", ".swn", etc.
// If that still isn't enough decrement the last but one char: ".svz"
- // Can happen when editing many "No Name" buffers.
+ // Can happen when many Nvim instances are editing the same file (including "No Name" buffers).
if (fname[n - 1] == 'a') { // ".s?a"
if (fname[n - 2] == 'a') { // ".saa": tried enough, give up
emsg(_("E326: Too many swap files found"));
@@ -3418,7 +3519,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
} else if (!*found_existing_dir && **dirp == NUL) {
int ret;
char *failed_dir;
- if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
+ if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E303: Unable to create directory \"%s\" for swap file, "
"recovery impossible: %s"),
failed_dir, os_strerror(ret));
@@ -3430,7 +3531,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
return fname;
}
-static int b0_magic_wrong(ZERO_BL *b0p)
+static int b0_magic_wrong(ZeroBlock *b0p)
{
return b0p->b0_magic_long != B0_MAGIC_LONG
|| b0p->b0_magic_int != B0_MAGIC_INT
@@ -3438,7 +3539,7 @@ static int b0_magic_wrong(ZERO_BL *b0p)
|| b0p->b0_magic_char != B0_MAGIC_CHAR;
}
-/// Compare current file name with file name from swap file.
+/// Compare current file name with file name from swapfile.
/// Try to use inode numbers when possible.
/// Return non-zero when files are different.
///
@@ -3448,7 +3549,7 @@ static int b0_magic_wrong(ZERO_BL *b0p)
/// because the device number cannot be used over a network.
/// - When a file does not exist yet (editing a new file) there is no inode
/// number.
-/// - The file name in a swap file may not be valid on the current host. The
+/// - The file name in a swapfile may not be valid on the current host. The
/// "~user" form is used whenever possible to avoid this.
///
/// This is getting complicated, let's make a table:
@@ -3462,7 +3563,7 @@ static int b0_magic_wrong(ZERO_BL *b0p)
/// == 0 X OK OK fname_c != fname_s
/// X == 0 OK OK fname_c != fname_s
///
-/// current file doesn't exist, file for swap file exist, file name(s) not
+/// current file doesn't exist, file for swapfile exist, file name(s) not
/// available -> probably different
/// == 0 != 0 FAIL X true
/// == 0 != 0 X FAIL true
@@ -3485,11 +3586,11 @@ static int b0_magic_wrong(ZERO_BL *b0p)
/// without making the block 0 incompatible with 32 bit versions.
///
/// @param fname_c current file name
-/// @param fname_s file name from swap file
+/// @param fname_s file name from swapfile
static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
{
uint64_t ino_c = 0; // ino of current file
- uint64_t ino_s; // ino of file from swap file
+ uint64_t ino_s; // ino of file from swapfile
char buf_c[MAXPATHL]; // full path of fname_c
char buf_s[MAXPATHL]; // full path of fname_s
int retval_c; // flag: buf_c valid
@@ -3501,7 +3602,7 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
}
// First we try to get the inode from the file name, because the inode in
- // the swap file may be outdated. If that fails (e.g. this path is not
+ // the swapfile may be outdated. If that fails (e.g. this path is not
// valid on this machine), use the inode from block 0.
if (os_fileinfo(fname_s, &file_info)) {
ino_s = os_fileinfo_inode(&file_info);
@@ -3515,15 +3616,15 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
// One of the inode numbers is unknown, try a forced vim_FullName() and
// compare the file names.
- retval_c = vim_FullName(fname_c, (char *)buf_c, MAXPATHL, true);
- retval_s = vim_FullName(fname_s, (char *)buf_s, MAXPATHL, true);
+ retval_c = vim_FullName(fname_c, buf_c, MAXPATHL, true);
+ retval_s = vim_FullName(fname_s, buf_s, MAXPATHL, true);
if (retval_c == OK && retval_s == OK) {
return strcmp(buf_c, buf_s) != 0;
}
// Can't compare inodes or file names, guess that the files are different,
// unless both appear not to exist at all, then compare with the file name
- // in the swap file.
+ // in the swapfile.
if (ino_s == 0 && ino_c == 0 && retval_c == FAIL && retval_s == FAIL) {
return strcmp(fname_c, fname_s) != 0;
}
@@ -3532,22 +3633,23 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
/// Move a long integer into a four byte character array.
/// Used for machine independency in block zero.
-static void long_to_char(long n, char_u *s)
+static void long_to_char(long n, char *s_in)
{
- s[0] = (char_u)(n & 0xff);
+ uint8_t *s = (uint8_t *)s_in;
+ s[0] = (uint8_t)(n & 0xff);
n = (unsigned)n >> 8;
- s[1] = (char_u)(n & 0xff);
+ s[1] = (uint8_t)(n & 0xff);
n = (unsigned)n >> 8;
- s[2] = (char_u)(n & 0xff);
+ s[2] = (uint8_t)(n & 0xff);
n = (unsigned)n >> 8;
- s[3] = (char_u)(n & 0xff);
+ s[3] = (uint8_t)(n & 0xff);
}
-static long char_to_long(const char_u *s)
+static long char_to_long(const char *s_in)
{
- long retval;
+ const uint8_t *s = (uint8_t *)s_in;
- retval = s[3];
+ long retval = s[3];
retval <<= 8;
retval |= s[2];
retval <<= 8;
@@ -3558,28 +3660,23 @@ static long char_to_long(const char_u *s)
return retval;
}
-/// Set the flags in the first block of the swap file:
+/// Set the flags in the first block of the swapfile:
/// - file is modified or not: buf->b_changed
/// - 'fileformat'
/// - 'fileencoding'
void ml_setflags(buf_T *buf)
{
- bhdr_T *hp;
- ZERO_BL *b0p;
-
if (!buf->b_ml.ml_mfp) {
return;
}
- for (hp = buf->b_ml.ml_mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) {
- if (hp->bh_bnum == 0) {
- b0p = hp->bh_data;
- b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0;
- b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1));
- add_b0_fenc(b0p, buf);
- hp->bh_flags |= BH_DIRTY;
- mf_sync(buf->b_ml.ml_mfp, MFS_ZERO);
- break;
- }
+ bhdr_T *hp = pmap_get(int64_t)(&buf->b_ml.ml_mfp->mf_hash, 0);
+ if (hp) {
+ ZeroBlock *b0p = hp->bh_data;
+ b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0;
+ b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1));
+ add_b0_fenc(b0p, buf);
+ hp->bh_flags |= BH_DIRTY;
+ mf_sync(buf->b_ml.ml_mfp, MFS_ZERO);
}
}
@@ -3595,7 +3692,7 @@ enum {
/// Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called.
/// ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it
/// ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity.
-static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
+static void ml_updatechunk(buf_T *buf, linenr_T line, int len, int updtype)
{
static buf_T *ml_upd_lastbuf = NULL;
static linenr_T ml_upd_lastline;
@@ -3604,11 +3701,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
linenr_T curline = ml_upd_lastcurline;
int curix = ml_upd_lastcurix;
- long size;
- chunksize_T *curchnk;
- int rest;
bhdr_T *hp;
- DATA_BL *dp;
if (buf->b_ml.ml_usedchunks == -1 || len == 0) {
return;
@@ -3625,8 +3718,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
// First line in empty buffer from ml_flush_line() -- reset
buf->b_ml.ml_usedchunks = 1;
buf->b_ml.ml_chunksize[0].mlcs_numlines = 1;
- buf->b_ml.ml_chunksize[0].mlcs_totalsize =
- (long)strlen(buf->b_ml.ml_line_ptr) + 1;
+ buf->b_ml.ml_chunksize[0].mlcs_totalsize = (int)strlen(buf->b_ml.ml_line_ptr) + 1;
return;
}
@@ -3647,13 +3739,15 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines;
curix++;
}
- curchnk = buf->b_ml.ml_chunksize + curix;
+ chunksize_T *curchnk = buf->b_ml.ml_chunksize + curix;
if (updtype == ML_CHNK_DELLINE) {
len = -len;
}
curchnk->mlcs_totalsize += len;
if (updtype == ML_CHNK_ADDLINE) {
+ int rest;
+ DataBlock *dp;
curchnk->mlcs_numlines++;
// May resize here so we don't have to do it in both cases below
@@ -3664,17 +3758,14 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
}
if (buf->b_ml.ml_chunksize[curix].mlcs_numlines >= MLCS_MAXL) {
- int count; // number of entries in block
- int idx;
int text_end;
- int linecnt;
memmove(buf->b_ml.ml_chunksize + curix + 1,
buf->b_ml.ml_chunksize + curix,
(size_t)(buf->b_ml.ml_usedchunks - curix) * sizeof(chunksize_T));
// Compute length of first half of lines in the split chunk
- size = 0;
- linecnt = 0;
+ int size = 0;
+ int linecnt = 0;
while (curline < buf->b_ml.ml_line_count
&& linecnt < MLCS_MINL) {
if ((hp = ml_find_line(buf, curline, ML_FIND)) == NULL) {
@@ -3682,8 +3773,9 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
return;
}
dp = hp->bh_data;
- count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1;
- idx = curline - buf->b_ml.ml_locked_low;
+ int count
+ = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; // number of entries in block
+ int idx = curline - buf->b_ml.ml_locked_low;
curline = buf->b_ml.ml_locked_high + 1;
if (idx == 0) { // first line in block, text at the end
text_end = (int)dp->db_txt_end;
@@ -3786,19 +3878,11 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
/// @param no_ff ignore 'fileformat' option, always use one byte for NL.
///
/// @return -1 if information is not available
-long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
+int ml_find_line_or_offset(buf_T *buf, linenr_T lnum, int *offp, bool no_ff)
{
- linenr_T curline;
- int curix;
- long size;
bhdr_T *hp;
- DATA_BL *dp;
- int count; // number of entries in block
- int idx;
- int start_idx;
int text_end;
- long offset;
- int len;
+ int offset;
int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS);
int extra = 0;
@@ -3816,12 +3900,17 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
if (lnum == 0 || buf->b_ml.ml_line_lnum < lnum || !no_ff) {
ml_flush_line(curbuf);
} else if (can_cache && buf->b_ml.ml_line_offset > 0) {
- return (long)buf->b_ml.ml_line_offset;
+ return (int)buf->b_ml.ml_line_offset;
}
if (buf->b_ml.ml_usedchunks == -1
|| buf->b_ml.ml_chunksize == NULL
|| lnum < 0) {
+ // memline is currently empty. Although if it is loaded,
+ // it behaves like there is one empty line.
+ if (!ffdos && buf->b_ml.ml_mfp && (lnum == 1 || lnum == 2)) {
+ return lnum - 1;
+ }
return -1;
}
@@ -3835,16 +3924,16 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
}
// Find the last chunk before the one containing our line. Last chunk is
// special because it will never qualify
- curline = 1;
- curix = 0;
- size = 0;
+ linenr_T curline = 1;
+ int curix = 0;
+ int size = 0;
while (curix < buf->b_ml.ml_usedchunks - 1
&& ((lnum != 0
&& lnum >= curline + buf->b_ml.ml_chunksize[curix].mlcs_numlines)
|| (offset != 0
&& offset > size +
buf->b_ml.ml_chunksize[curix].mlcs_totalsize
- + (long)ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
+ + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines;
size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize;
if (offset && ffdos) {
@@ -3858,9 +3947,11 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
|| (hp = ml_find_line(buf, curline, ML_FIND)) == NULL) {
return -1;
}
- dp = hp->bh_data;
- count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1;
- start_idx = idx = curline - buf->b_ml.ml_locked_low;
+ DataBlock *dp = hp->bh_data;
+ int count
+ = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; // number of entries in block
+ int idx;
+ int start_idx = idx = curline - buf->b_ml.ml_locked_low;
if (idx == 0) { // first line in block, text at the end
text_end = (int)dp->db_txt_end;
} else {
@@ -3888,7 +3979,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
idx++;
}
}
- len = text_end - (int)((dp->db_index[idx]) & DB_INDEX_MASK);
+ int len = text_end - (int)((dp->db_index[idx]) & DB_INDEX_MASK);
size += len;
if (offset != 0 && size >= offset) {
if (size + ffdos == offset) {
@@ -3930,16 +4021,16 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
}
/// Goto byte in buffer with offset 'cnt'.
-void goto_byte(long cnt)
+void goto_byte(int cnt)
{
- long boff = cnt;
+ int boff = cnt;
ml_flush_line(curbuf); // cached line may be dirty
setpcmark();
if (boff) {
boff--;
}
- linenr_T lnum = (linenr_T)ml_find_line_or_offset(curbuf, (linenr_T)0, &boff, false);
+ linenr_T lnum = (linenr_T)ml_find_line_or_offset(curbuf, 0, &boff, false);
if (lnum < 1) { // past the end
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
curwin->w_curswant = MAXCOL;
@@ -3968,7 +4059,7 @@ int inc(pos_T *lp)
if (lp->col != MAXCOL) {
const char *const p = ml_get_pos(lp);
if (*p != NUL) { // still within line, move to next char (may be NUL)
- const int l = utfc_ptr2len((char *)p);
+ const int l = utfc_ptr2len(p);
lp->col += l;
return ((p[l] != NUL) ? 0 : 2);
diff --git a/src/nvim/memline.h b/src/nvim/memline.h
index f4190f0210..e70a8e423e 100644
--- a/src/nvim/memline.h
+++ b/src/nvim/memline.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_MEMLINE_H
-#define NVIM_MEMLINE_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/memline_defs.h" // IWYU pragma: export
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memline.h.generated.h"
#endif
-#endif // NVIM_MEMLINE_H
diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h
index 6bb9255909..f95dc7a2e5 100644
--- a/src/nvim/memline_defs.h
+++ b/src/nvim/memline_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MEMLINE_DEFS_H
-#define NVIM_MEMLINE_DEFS_H
+#pragma once
#include "nvim/memfile_defs.h"
@@ -17,7 +16,7 @@ typedef struct info_pointer {
typedef struct ml_chunksize {
int mlcs_numlines;
- long mlcs_totalsize;
+ int mlcs_totalsize;
} chunksize_T;
// Flags when calling ml_updatechunk()
@@ -49,10 +48,11 @@ typedef struct memline {
int ml_stack_top; // current top of ml_stack
int ml_stack_size; // total number of entries in ml_stack
-#define ML_EMPTY 1 // empty buffer
-#define ML_LINE_DIRTY 2 // cached line was changed and allocated
-#define ML_LOCKED_DIRTY 4 // ml_locked was changed
-#define ML_LOCKED_POS 8 // ml_locked needs positive block number
+#define ML_EMPTY 0x01 // empty buffer
+#define ML_LINE_DIRTY 0x02 // cached line was changed and allocated
+#define ML_LOCKED_DIRTY 0x04 // ml_locked was changed
+#define ML_LOCKED_POS 0x08 // ml_locked needs positive block number
+#define ML_ALLOCATED 0x10 // ml_line_ptr is an allocated copy
int ml_flags;
linenr_T ml_line_lnum; // line number of cached line, 0 if not valid
@@ -68,5 +68,3 @@ typedef struct memline {
int ml_numchunks;
int ml_usedchunks;
} memline_T;
-
-#endif // NVIM_MEMLINE_DEFS_H
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 5356300382..df6c81fe0d 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Various routines dealing with allocation and deallocation of memory.
#include <assert.h>
@@ -13,11 +10,13 @@
#include "nvim/api/extmark.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/context.h"
#include "nvim/decoration_provider.h"
+#include "nvim/drawline.h"
#include "nvim/eval.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
@@ -29,10 +28,12 @@
#include "nvim/memfile.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/option_vars.h"
#include "nvim/sign.h"
+#include "nvim/state_defs.h"
#include "nvim/ui.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef UNIT_TESTING
# define malloc(size) mem_malloc(size)
@@ -121,9 +122,7 @@ void *xmalloc(size_t size)
{
void *ret = try_malloc(size);
if (!ret) {
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
return ret;
}
@@ -152,9 +151,7 @@ void *xcalloc(size_t count, size_t size)
try_to_free_memory();
ret = calloc(allocated_count, allocated_size);
if (!ret) {
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
}
return ret;
@@ -174,9 +171,7 @@ void *xrealloc(void *ptr, size_t size)
try_to_free_memory();
ret = realloc(ptr, allocated_size);
if (!ret) {
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
}
return ret;
@@ -194,8 +189,7 @@ void *xmallocz(size_t size)
{
size_t total_size = size + 1;
if (total_size < size) {
- os_errmsg(_("Vim: Data too large to fit into virtual memory space\n"));
- preserve_exit();
+ preserve_exit(_("Vim: Data too large to fit into virtual memory space\n"));
}
void *ret = xmalloc(total_size);
@@ -219,6 +213,18 @@ void *xmemdupz(const void *data, size_t len)
return memcpy(xmallocz(len), data, len);
}
+#ifndef HAVE_STRNLEN
+size_t xstrnlen(const char *s, size_t n)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
+{
+ const char *end = memchr(s, '\0', n);
+ if (end == NULL) {
+ return n;
+ }
+ return (size_t)(end - s);
+}
+#endif
+
/// A version of strchr() that returns a pointer to the terminating NUL if it
/// doesn't find `c`.
///
@@ -502,13 +508,6 @@ bool strequal(const char *a, const char *b)
return (a == NULL && b == NULL) || (a && b && strcmp(a, b) == 0);
}
-/// Case-insensitive `strequal`.
-bool striequal(const char *a, const char *b)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- return (a == NULL && b == NULL) || (a && b && STRICMP(a, b) == 0);
-}
-
// Avoid repeating the error message many times (they take 1 second each).
// Did_outofmem_msg is reset when a character is read.
void do_outofmem_msg(size_t size)
@@ -585,7 +584,9 @@ void alloc_block(Arena *arena)
static size_t arena_align_offset(uint64_t off)
{
+#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double))
return ((off + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1));
+#undef ARENA_ALIGN
}
/// @param arena if NULL, do a global allocation. caller must then free the value!
@@ -663,14 +664,13 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
# include "nvim/cmdhist.h"
# include "nvim/diff.h"
# include "nvim/edit.h"
-# include "nvim/eval/typval.h"
# include "nvim/ex_cmds.h"
# include "nvim/ex_docmd.h"
-# include "nvim/ex_getln.h"
# include "nvim/file_search.h"
# include "nvim/getchar.h"
# include "nvim/grid.h"
# include "nvim/mark.h"
+# include "nvim/msgpack_rpc/channel.h"
# include "nvim/ops.h"
# include "nvim/option.h"
# include "nvim/os/os.h"
@@ -762,11 +762,7 @@ void free_all_mem(void)
p_hi = 0;
init_history();
- qf_free_all(NULL);
- // Free all location lists
- FOR_ALL_TAB_WINDOWS(tab, win) {
- qf_free_all(win);
- }
+ free_quickfix();
// Close all script inputs.
close_all_scripts();
@@ -777,8 +773,6 @@ void free_all_mem(void)
// Free all option values. Must come after closing windows.
free_all_options();
- free_arshape_buf();
-
// Clear registers.
clear_registers();
ResetRedobuff();
@@ -793,7 +787,7 @@ void free_all_mem(void)
first_tabpage = NULL;
// message history
- for (;;) {
+ while (true) {
if (delete_first_msg() == FAIL) {
break;
}
@@ -826,14 +820,15 @@ void free_all_mem(void)
grid_free_all_mem();
clear_hl_tables(false);
- list_free_log();
check_quickfix_busy();
decor_free_all_mem();
+ drawline_free_all_mem();
ui_free_all_mem();
nlua_free_all_mem();
+ rpc_free_all_mem();
// should be last, in case earlier free functions deallocates arenas
arena_free_reuse_blks();
diff --git a/src/nvim/memory.h b/src/nvim/memory.h
index 5b9798dc0d..ffdc4c7366 100644
--- a/src/nvim/memory.h
+++ b/src/nvim/memory.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_MEMORY_H
-#define NVIM_MEMORY_H
+#pragma once
#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <time.h>
+#include <stdint.h> // IWYU pragma: keep
+#include <time.h> // IWYU pragma: keep
-#include "nvim/macros.h"
+#include "auto/config.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory_defs.h" // IWYU pragma: export
/// `malloc()` function signature
typedef void *(*MemMalloc)(size_t);
@@ -39,21 +39,7 @@ extern MemRealloc mem_realloc;
extern bool entered_free_all_mem;
#endif
-EXTERN size_t arena_alloc_count INIT(= 0);
-
-typedef struct consumed_blk {
- struct consumed_blk *prev;
-} *ArenaMem;
-
-#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double))
-
-typedef struct {
- char *cur_blk;
- size_t pos, size;
-} Arena;
-
-// inits an empty arena.
-#define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 }
+EXTERN size_t arena_alloc_count INIT( = 0);
#define kv_fixsize_arena(a, v, s) \
((v).capacity = (s), \
@@ -73,4 +59,16 @@ typedef struct {
(void)(*ptr_); \
} while (0)
-#endif // NVIM_MEMORY_H
+#define CLEAR_FIELD(field) memset(&(field), 0, sizeof(field))
+#define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr)))
+
+#ifndef HAVE_STRNLEN
+# define strnlen xstrnlen // Older versions of SunOS may not have strnlen
+#endif
+
+#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
+
+// Like strcpy() but allows overlapped source and destination.
+#define STRMOVE(d, s) memmove((d), (s), strlen(s) + 1)
+
+#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
diff --git a/src/nvim/memory_defs.h b/src/nvim/memory_defs.h
new file mode 100644
index 0000000000..bde0e54f54
--- /dev/null
+++ b/src/nvim/memory_defs.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <stddef.h>
+
+typedef struct consumed_blk {
+ struct consumed_blk *prev;
+} *ArenaMem;
+
+typedef struct {
+ char *cur_blk;
+ size_t pos, size;
+} Arena;
+
+// inits an empty arena.
+#define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 }
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 2a18b08d8d..3252a73970 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Code for menus. Used for the GUI and 'wildmenu'.
// GUI/Motif support by Robert Webb
@@ -9,37 +6,36 @@
#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/menu_defs.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#define MENUDEPTH 10 // maximum depth of menus
@@ -50,8 +46,8 @@
/// The character for each menu mode
static char *menu_mode_chars[] = { "n", "v", "s", "o", "i", "c", "tl", "t" };
-static char e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
-static char e_nomenu[] = N_("E329: No menu \"%s\"");
+static const char e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
+static const char e_nomenu[] = N_("E329: No menu \"%s\"");
// Return true if "name" is a window toolbar menu name.
static bool menu_is_winbar(const char *const name)
@@ -70,25 +66,22 @@ static vimmenu_T **get_root_menu(const char *const name)
/// @param eap Ex command arguments
void ex_menu(exarg_T *eap)
{
- char *menu_path;
- int modes;
char *map_to; // command mapped to the menu entry
int noremap;
bool silent = false;
int unmenu;
char *map_buf;
- char *arg;
char *p;
int i;
- long pri_tab[MENUDEPTH + 1];
+ int pri_tab[MENUDEPTH + 1];
TriState enable = kNone; // kTrue for "menu enable",
// kFalse for "menu disable
vimmenu_T menuarg;
- modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
- arg = eap->arg;
+ int modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
+ char *arg = eap->arg;
- for (;;) {
+ while (true) {
if (strncmp(arg, "<script>", 8) == 0) {
noremap = REMAP_SCRIPT;
arg = skipwhite(arg + 8);
@@ -131,7 +124,7 @@ void ex_menu(exarg_T *eap)
}
if (ascii_iswhite(*p)) {
for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); i++) {
- pri_tab[i] = getdigits_long(&arg, false, 0);
+ pri_tab[i] = getdigits_int(&arg, false, 0);
if (pri_tab[i] == 0) {
pri_tab[i] = 500;
}
@@ -166,7 +159,7 @@ void ex_menu(exarg_T *eap)
return;
}
- menu_path = arg;
+ char *menu_path = arg;
if (*menu_path == '.') {
semsg(_(e_invarg2), menu_path);
goto theend;
@@ -232,7 +225,7 @@ void ex_menu(exarg_T *eap)
map_buf = NULL; // Menu tips are plain text.
} else {
map_buf = NULL;
- map_to = replace_termcodes(map_to, strlen(map_to), &map_buf,
+ map_to = replace_termcodes(map_to, strlen(map_to), &map_buf, 0,
REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
}
menuarg.modes = modes;
@@ -267,38 +260,28 @@ theend:
/// @param[out] menuarg menu entry
/// @param[] pri_tab priority table
/// @param[in] call_data Right hand side command
-static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const long *const pri_tab,
+static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const int *const pri_tab,
const char *const call_data)
{
- char *path_name;
int modes = menuarg->modes;
vimmenu_T *menu = NULL;
- vimmenu_T *parent;
vimmenu_T **lower_pri;
- char *p;
- char *name;
char *dname;
- char *next_name;
- char c;
- char d;
- int i;
int pri_idx = 0;
int old_modes = 0;
- int amenu;
char *en_name;
- char *map_to = NULL;
// Make a copy so we can stuff around with it, since it could be const
- path_name = xstrdup(menu_path);
+ char *path_name = xstrdup(menu_path);
vimmenu_T **root_menu_ptr = get_root_menu(menu_path);
vimmenu_T **menup = root_menu_ptr;
- parent = NULL;
- name = path_name;
+ vimmenu_T *parent = NULL;
+ char *name = path_name;
while (*name) {
// Get name of this element in the menu hierarchy, and the simplified
// name (without mnemonic and accelerator text).
- next_name = menu_name_skip(name);
- map_to = menutrans_lookup(name, (int)strlen(name));
+ char *next_name = menu_name_skip(name);
+ char *map_to = menutrans_lookup(name, (int)strlen(name));
if (map_to != NULL) {
en_name = name;
name = map_to;
@@ -401,25 +384,25 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const
// Only add system menu items which have not been defined yet.
// First check if this was an ":amenu".
- amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
- (MENU_NORMAL_MODE | MENU_INSERT_MODE));
+ int amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
+ (MENU_NORMAL_MODE | MENU_INSERT_MODE));
if (sys_menu) {
modes &= ~old_modes;
}
if (menu != NULL && modes) {
- p = (call_data == NULL) ? NULL : xstrdup(call_data);
+ char *p = (call_data == NULL) ? NULL : xstrdup(call_data);
// loop over all modes, may add more than one
- for (i = 0; i < MENU_MODES; i++) {
+ for (int i = 0; i < MENU_MODES; i++) {
if (modes & (1 << i)) {
// free any old menu
free_menu_string(menu, i);
// For "amenu", may insert an extra character.
// Don't do this for "<Nop>".
- c = 0;
- d = 0;
+ char c = 0;
+ char d = 0;
if (amenu && call_data != NULL && *call_data != NUL) {
switch (1 << i) {
case MENU_VISUAL_MODE:
@@ -487,13 +470,11 @@ erret:
// Called recursively.
static int menu_enable_recurse(vimmenu_T *menu, char *name, int modes, int enable)
{
- char *p;
-
if (menu == NULL) {
return OK; // Got to bottom of hierarchy
}
// Get name of this element in the menu hierarchy
- p = menu_name_skip(name);
+ char *p = menu_name_skip(name);
// Find the menu
while (menu != NULL) {
@@ -536,14 +517,12 @@ static int menu_enable_recurse(vimmenu_T *menu, char *name, int modes, int enabl
static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
{
vimmenu_T *menu;
- vimmenu_T *child;
- char *p;
if (*menup == NULL) {
return OK; // Got to bottom of hierarchy
}
// Get name of this element in the menu hierarchy
- p = menu_name_skip(name);
+ char *p = menu_name_skip(name);
// Find the menu
while ((menu = *menup) != NULL) {
@@ -560,7 +539,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
}
} else if (*name != NUL) {
if (!silent) {
- emsg(_(e_menuothermode));
+ emsg(_(e_menu_only_exists_in_another_mode));
}
return FAIL;
}
@@ -597,7 +576,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
// Recalculate modes for menu based on the new updated children
menu->modes &= ~modes;
- child = menu->children;
+ vimmenu_T *child = menu->children;
for (; child != NULL; child = child->next) {
menu->modes |= child->modes;
}
@@ -617,20 +596,15 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
// Free the given menu structure and remove it from the linked list.
static void free_menu(vimmenu_T **menup)
{
- int i;
- vimmenu_T *menu;
-
- menu = *menup;
+ vimmenu_T *menu = *menup;
- // Don't change *menup until after calling gui_mch_destroy_menu(). The
- // MacOS code needs the original structure to properly delete the menu.
*menup = menu->next;
xfree(menu->name);
xfree(menu->dname);
xfree(menu->en_name);
xfree(menu->en_dname);
xfree(menu->actext);
- for (i = 0; i < MENU_MODES; i++) {
+ for (int i = 0; i < MENU_MODES; i++) {
free_menu_string(menu, i);
}
xfree(menu);
@@ -640,9 +614,8 @@ static void free_menu(vimmenu_T **menup)
static void free_menu_string(vimmenu_T *menu, int idx)
{
int count = 0;
- int i;
- for (i = 0; i < MENU_MODES; i++) {
+ for (int i = 0; i < MENU_MODES; i++) {
if (menu->strings[i] == menu->strings[idx]) {
count++;
}
@@ -662,15 +635,13 @@ static void free_menu_string(vimmenu_T *menu, int idx)
/// @see menu_get
static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)
{
- dict_T *dict;
-
if (!menu || (menu->modes & modes) == 0x0) {
return NULL;
}
- dict = tv_dict_alloc();
+ dict_T *dict = tv_dict_alloc();
tv_dict_add_str(dict, S_LEN("name"), menu->dname);
- tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority);
+ tv_dict_add_nr(dict, S_LEN("priority"), menu->priority);
tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname));
if (menu->mnemonic) {
@@ -755,11 +726,9 @@ bool menu_get(char *const path_name, int modes, list_T *list)
/// @return menu if \p name is null, found menu or NULL
static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes)
{
- char *p;
-
while (*name) {
// find the end of one dot-separated name and put a NUL at the dot
- p = menu_name_skip(name);
+ char *p = menu_name_skip(name);
while (menu != NULL) {
if (menu_name_equal(name, menu)) {
// Found menu
@@ -767,7 +736,7 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes)
emsg(_(e_notsubmenu));
return NULL;
} else if ((menu->modes & modes) == 0x0) {
- emsg(_(e_menuothermode));
+ emsg(_(e_menu_only_exists_in_another_mode));
return NULL;
} else if (*p == NUL) { // found a full match
return menu;
@@ -814,9 +783,6 @@ static int show_menus(char *const path_name, int modes)
/// Recursively show the mappings associated with the menus under the given one
static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
{
- int i;
- int bit;
-
if (menu != NULL && (menu->modes & modes) == 0x0) {
return;
}
@@ -826,7 +792,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
if (got_int) { // "q" hit for "--more--"
return;
}
- for (i = 0; i < depth; i++) {
+ for (int i = 0; i < depth; i++) {
msg_puts(" ");
}
if (menu->priority) {
@@ -834,17 +800,17 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
msg_puts(" ");
}
// Same highlighting as for directories!?
- msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
+ msg_outtrans(menu->name, HL_ATTR(HLF_D));
}
if (menu != NULL && menu->children == NULL) {
- for (bit = 0; bit < MENU_MODES; bit++) {
+ for (int bit = 0; bit < MENU_MODES; bit++) {
if ((menu->modes & modes & (1 << bit)) != 0) {
msg_putchar('\n');
if (got_int) { // "q" hit for "--more--"
return;
}
- for (i = 0; i < depth + 2; i++) {
+ for (int i = 0; i < depth + 2; i++) {
msg_puts(" ");
}
msg_puts(menu_mode_chars[bit]);
@@ -902,10 +868,8 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for
char *after_dot;
char *p;
char *path_name = NULL;
- char *name;
int unmenu;
vimmenu_T *menu;
- int expand_menus;
xp->xp_context = EXPAND_UNSUCCESSFUL;
@@ -943,7 +907,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for
}
// ":popup" only uses menus, not entries
- expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
+ int expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
expand_emenu = (*cmd == 'e');
if (expand_menus && ascii_iswhite(*p)) {
return NULL; // TODO(vim): check for next command?
@@ -963,7 +927,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for
path_name = xmalloc(path_len);
xstrlcpy(path_name, arg, path_len);
}
- name = path_name;
+ char *name = path_name;
while (name != NULL && *name) {
p = menu_name_skip(name);
while (menu != NULL) {
@@ -1316,17 +1280,16 @@ static char *menu_text(const char *str, int *mnemonic, char **actext)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
FUNC_ATTR_NONNULL_ARG(1)
{
- char *p;
char *text;
// Locate accelerator text, after the first TAB
- p = vim_strchr(str, TAB);
+ char *p = vim_strchr(str, TAB);
if (p != NULL) {
if (actext != NULL) {
*actext = xstrdup(p + 1);
}
assert(p >= str);
- text = xstrnsave(str, (size_t)(p - str));
+ text = xmemdupz(str, (size_t)(p - str));
} else {
text = xstrdup(str);
}
@@ -1352,7 +1315,7 @@ static char *menu_text(const char *str, int *mnemonic, char **actext)
bool menu_is_menubar(const char *const name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- return !menu_is_popup((char *)name)
+ return !menu_is_popup(name)
&& !menu_is_toolbar(name)
&& !menu_is_winbar(name)
&& *name != MNU_HIDDEN_CHAR;
@@ -1459,7 +1422,8 @@ void show_popupmenu(void)
/// Execute "menu". Use by ":emenu" and the window toolbar.
/// @param eap NULL for the window toolbar.
-/// @param mode_idx specify a MENU_INDEX_ value, use -1 to depend on the current state
+/// @param mode_idx specify a MENU_INDEX_ value,
+/// use MENU_INDEX_INVALID to depend on the current state
void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx)
FUNC_ATTR_NONNULL_ARG(2)
{
@@ -1467,7 +1431,7 @@ void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx)
if (idx < 0) {
// Use the Insert mode entry when returning to Insert mode.
- if (((State & MODE_INSERT) || restart_edit) && !current_sctx.sc_sid) {
+ if (((State & MODE_INSERT) || restart_edit) && current_sctx.sc_sid == 0) {
idx = MENU_INDEX_INSERT;
} else if (State & MODE_CMDLINE) {
idx = MENU_INDEX_CMDLINE;
@@ -1621,7 +1585,7 @@ static vimmenu_T *menu_getbyname(char *name_arg)
void ex_emenu(exarg_T *eap)
{
char *arg = eap->arg;
- int mode_idx = -1;
+ int mode_idx = MENU_INDEX_INVALID;
if (arg[0] && ascii_iswhite(arg[1])) {
switch (arg[0]) {
@@ -1723,7 +1687,6 @@ static garray_T menutrans_ga = GA_EMPTY_INIT_VALUE;
void ex_menutranslate(exarg_T *eap)
{
char *arg = eap->arg;
- char *from, *from_noamp, *to;
if (menutrans_ga.ga_itemsize == 0) {
ga_init(&menutrans_ga, (int)sizeof(menutrans_T), 5);
@@ -1737,18 +1700,18 @@ void ex_menutranslate(exarg_T *eap)
del_menutrans_vars();
} else {
// ":menutrans from to": add translation
- from = arg;
+ char *from = arg;
arg = menu_skip_part(arg);
- to = skipwhite(arg);
+ char *to = skipwhite(arg);
*arg = NUL;
arg = menu_skip_part(to);
if (arg == to) {
emsg(_(e_invarg));
} else {
from = xstrdup(from);
- from_noamp = menu_text(from, NULL, NULL);
+ char *from_noamp = menu_text(from, NULL, NULL);
assert(arg >= to);
- to = xstrnsave(to, (size_t)(arg - to));
+ to = xmemdupz(to, (size_t)(arg - to));
menu_translate_tab_and_shift(from);
menu_translate_tab_and_shift(to);
menu_unescape_name(from);
@@ -1778,7 +1741,6 @@ static char *menu_skip_part(char *p)
static char *menutrans_lookup(char *name, int len)
{
menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
- char *dname;
for (int i = 0; i < menutrans_ga.ga_len; i++) {
if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL) {
@@ -1789,7 +1751,7 @@ static char *menutrans_lookup(char *name, int len)
// Now try again while ignoring '&' characters.
char c = name[len];
name[len] = NUL;
- dname = menu_text(name, NULL, NULL);
+ char *dname = menu_text(name, NULL, NULL);
name[len] = c;
for (int i = 0; i < menutrans_ga.ga_len; i++) {
if (STRICMP(dname, tp[i].from_noamp) == 0) {
@@ -1805,9 +1767,7 @@ static char *menutrans_lookup(char *name, int len)
// Unescape the name in the translate dictionary table.
static void menu_unescape_name(char *name)
{
- char *p;
-
- for (p = name; *p && *p != '.'; MB_PTR_ADV(p)) {
+ for (char *p = name; *p && *p != '.'; MB_PTR_ADV(p)) {
if (*p == '\\') {
STRMOVE(p, p + 1);
}
@@ -1859,7 +1819,7 @@ static void menuitem_getinfo(const char *menu_name, const vimmenu_T *menu, int m
if (menu->actext != NULL) {
tv_dict_add_str(dict, S_LEN("accel"), menu->actext);
}
- tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority);
+ tv_dict_add_nr(dict, S_LEN("priority"), menu->priority);
tv_dict_add_str(dict, S_LEN("modes"), get_menu_mode_str(menu->modes));
char buf[NUMBUFLEN];
diff --git a/src/nvim/menu.h b/src/nvim/menu.h
index 32959cf35f..9644386003 100644
--- a/src/nvim/menu.h
+++ b/src/nvim/menu.h
@@ -1,13 +1,10 @@
-#ifndef NVIM_MENU_H
-#define NVIM_MENU_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/menu_defs.h"
-#include "nvim/types.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/menu_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "menu.h.generated.h"
#endif
-#endif // NVIM_MENU_H
diff --git a/src/nvim/menu_defs.h b/src/nvim/menu_defs.h
index 79b267ae49..b870055238 100644
--- a/src/nvim/menu_defs.h
+++ b/src/nvim/menu_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MENU_DEFS_H
-#define NVIM_MENU_DEFS_H
+#pragma once
#include <stdbool.h>
@@ -52,7 +51,7 @@ struct VimMenu {
char *en_dname; ///< NULL when "dname" untranslated
int mnemonic; ///< mnemonic key (after '&')
char *actext; ///< accelerator text (after TAB)
- long priority; ///< Menu order priority
+ int priority; ///< Menu order priority
char *strings[MENU_MODES]; ///< Mapped string for each mode
int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
bool silent[MENU_MODES]; ///< A silent flag for each mode
@@ -60,5 +59,3 @@ struct VimMenu {
vimmenu_T *parent; ///< Parent of menu
vimmenu_T *next; ///< Next item in menu
};
-
-#endif // NVIM_MENU_DEFS_H
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 7f29b19031..3268ff389a 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// message.c: functions for displaying messages on the command line
#include <assert.h>
@@ -11,22 +8,23 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -44,16 +42,20 @@
#include "nvim/mouse.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
-#include "nvim/pos.h"
+#include "nvim/os/time.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// To be able to scroll back at the "more" and "hit-enter" prompts we need to
// store the displayed text and remember where screen lines start.
@@ -64,7 +66,7 @@ struct msgchunk_S {
char sb_eol; // true when line ends after this text
int sb_msg_col; // column in which text starts
int sb_attr; // text attributes
- char sb_text[1]; // text to be displayed, actually longer
+ char sb_text[]; // text to be displayed
};
// Magic chars used in confirm dialog strings
@@ -142,7 +144,7 @@ static int msg_grid_pos_at_flush = 0;
static void ui_ext_msg_set_pos(int row, bool scrolled)
{
- char buf[MAX_MCO + 1];
+ char buf[MB_MAXCHAR + 1];
size_t size = (size_t)utf_char2bytes(curwin->w_p_fcs_chars.msgsep, buf);
buf[size] = '\0';
ui_call_msg_set_pos(msg_grid.handle, row, scrolled,
@@ -182,7 +184,7 @@ void msg_grid_validate(void)
msg_grid.dirty_col = xcalloc((size_t)Rows, sizeof(*msg_grid.dirty_col));
// Tricky: allow resize while pager or ex mode is active
- int pos = MAX(max_rows - msg_scrolled, 0);
+ int pos = (State & MODE_ASKMORE) ? 0 : MAX(max_rows - msg_scrolled, 0);
msg_grid.throttled = false; // don't throttle in 'cmdheight' area
msg_grid_set_pos(pos, msg_scrolled);
ui_comp_put_grid(&msg_grid, pos, 0, msg_grid.rows, msg_grid.cols,
@@ -213,17 +215,8 @@ void msg_grid_validate(void)
}
}
-/// Displays the string 's' on the status line
-/// When terminal not initialized (yet) os_errmsg(..) is used.
-///
-/// @return true if wait_return() not called
-int msg(char *s)
-{
- return msg_attr_keep(s, 0, false, false);
-}
-
/// Like msg() but keep it silent when 'verbosefile' is set.
-int verb_msg(char *s)
+int verb_msg(const char *s)
{
verbose_enter();
int n = msg_attr_keep(s, 0, false, false);
@@ -232,14 +225,18 @@ int verb_msg(char *s)
return n;
}
-int msg_attr(const char *s, const int attr)
+/// Displays the string 's' on the status line
+/// When terminal not initialized (yet) os_errmsg(..) is used.
+///
+/// @return true if wait_return() not called
+int msg(const char *s, const int attr)
FUNC_ATTR_NONNULL_ARG(1)
{
return msg_attr_keep(s, attr, false, false);
}
-/// Similar to msg_outtrans_attr, but support newlines and tabs.
-void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clear)
+/// Similar to msg_outtrans, but support newlines and tabs.
+void msg_multiline(const char *s, int attr, bool check_int, bool *need_clear)
FUNC_ATTR_NONNULL_ALL
{
const char *next_spec = s;
@@ -252,7 +249,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea
if (next_spec != NULL) {
// Printing all char that are before the char found by strpbrk
- msg_outtrans_len_attr(s, (int)(next_spec - s), attr);
+ msg_outtrans_len(s, (int)(next_spec - s), attr);
if (*next_spec != TAB && *need_clear) {
msg_clr_eos();
@@ -266,7 +263,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea
// Print the rest of the message. We know there is no special
// character because strpbrk returned NULL
if (*s != NUL) {
- msg_outtrans_attr(s, attr);
+ msg_outtrans(s, attr);
}
}
@@ -279,8 +276,7 @@ void msg_multiattr(HlMessage hl_msg, const char *kind, bool history)
msg_ext_set_kind(kind);
for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
HlMessageChunk chunk = kv_A(hl_msg, i);
- msg_multiline_attr((const char *)chunk.text.data, chunk.attr,
- true, &need_clear);
+ msg_multiline(chunk.text.data, chunk.attr, true, &need_clear);
}
if (history && kv_size(hl_msg)) {
add_msg_hist_multiattr(NULL, 0, 0, true, hl_msg);
@@ -294,7 +290,6 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
FUNC_ATTR_NONNULL_ALL
{
static int entered = 0;
- int retval;
char *buf = NULL;
if (keep && multiline) {
@@ -306,7 +301,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
// Skip messages not match ":filter pattern".
// Don't filter when there is an error.
- if (!emsg_on_display && message_filtered((char *)s)) {
+ if (!emsg_on_display && message_filtered(s)) {
return true;
}
@@ -334,24 +329,24 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
// Truncate the message if needed.
msg_start();
- buf = msg_strtrunc((char *)s, false);
+ buf = msg_strtrunc(s, false);
if (buf != NULL) {
s = buf;
}
bool need_clear = true;
if (multiline) {
- msg_multiline_attr(s, attr, false, &need_clear);
+ msg_multiline(s, attr, false, &need_clear);
} else {
- msg_outtrans_attr(s, attr);
+ msg_outtrans(s, attr);
}
if (need_clear) {
msg_clr_eos();
}
- retval = msg_end();
+ int retval = msg_end();
- if (keep && retval && vim_strsize((char *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) {
- set_keep_msg((char *)s, 0);
+ if (keep && retval && vim_strsize(s) < (Rows - cmdline_row - 1) * Columns + sc_col) {
+ set_keep_msg(s, 0);
}
need_fileinfo = false;
@@ -366,17 +361,16 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
/// @return an allocated string or NULL when no truncating is done.
///
/// @param force always truncate
-char *msg_strtrunc(char *s, int force)
+char *msg_strtrunc(const char *s, int force)
{
char *buf = NULL;
- int len;
- int room;
// May truncate message to avoid a hit-return prompt
if ((!msg_scroll && !need_wait_return && shortmess(SHM_TRUNCALL)
&& !exmode_active && msg_silent == 0 && !ui_has(kUIMessages))
|| force) {
- len = vim_strsize(s);
+ int room;
+ int len = vim_strsize(s);
if (msg_scrolled != 0) {
// Use all the columns.
room = (Rows - msg_row) * Columns - 1;
@@ -397,10 +391,9 @@ char *msg_strtrunc(char *s, int force)
/// Truncate a string "s" to "buf" with cell width "room".
/// "s" and "buf" may be equal.
-void trunc_string(char *s, char *buf, int room_in, int buflen)
+void trunc_string(const char *s, char *buf, int room_in, int buflen)
{
int room = room_in - 3; // "..." takes 3 chars
- int half;
int len = 0;
int e;
int i;
@@ -416,7 +409,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen)
if (room_in < 3) {
room = 0;
}
- half = room / 2;
+ int half = room / 2;
// First part: Start of the string.
for (e = 0; len < half && e < buflen; e++) {
@@ -441,7 +434,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen)
// Last part: End of the string.
half = i = (int)strlen(s);
- for (;;) {
+ while (true) {
do {
half = half - utf_head_off(s, s + half - 1) - 1;
} while (half > 0 && utf_iscomposing(utf_ptr2char(s + half)));
@@ -478,26 +471,19 @@ void trunc_string(char *s, char *buf, int room_in, int buflen)
buf[e + 3 + len - 1] = NUL;
} else {
// can't fit in the "...", just truncate it
- buf[e - 1] = NUL;
+ buf[buflen - 1] = NUL;
}
}
-// Note: Caller of smsg() and smsg_attr() must check the resulting string is
-// shorter than IOSIZE!!!
-
-int smsg(const char *s, ...)
- FUNC_ATTR_PRINTF(1, 2)
-{
- va_list arglist;
-
- va_start(arglist, s);
- vim_vsnprintf(IObuff, IOSIZE, s, arglist);
- va_end(arglist);
-
- return msg(IObuff);
-}
-
-int smsg_attr(int attr, const char *s, ...)
+/// Shows a printf-style message with attributes.
+///
+/// Note: Caller must check the resulting string is shorter than IOSIZE!!!
+///
+/// @see semsg
+/// @see swmsg
+///
+/// @param s printf-style format message
+int smsg(int attr, const char *s, ...)
FUNC_ATTR_PRINTF(2, 3)
{
va_list arglist;
@@ -505,7 +491,7 @@ int smsg_attr(int attr, const char *s, ...)
va_start(arglist, s);
vim_vsnprintf(IObuff, IOSIZE, s, arglist);
va_end(arglist);
- return msg_attr((const char *)IObuff, attr);
+ return msg(IObuff, attr);
}
int smsg_attr_keep(int attr, const char *s, ...)
@@ -516,7 +502,7 @@ int smsg_attr_keep(int attr, const char *s, ...)
va_start(arglist, s);
vim_vsnprintf(IObuff, IOSIZE, s, arglist);
va_end(arglist);
- return msg_attr_keep((const char *)IObuff, attr, true, false);
+ return msg_attr_keep(IObuff, attr, true, false);
}
// Remember the last sourcing name/lnum used in an error message, so that it
@@ -581,10 +567,10 @@ static char *get_emsg_lnum(void)
if (SOURCING_NAME != NULL
&& (other_sourcing_name() || SOURCING_LNUM != last_sourcing_lnum)
&& SOURCING_LNUM != 0) {
- const char *const p = _("line %4ld:");
+ const char *const p = _("line %4" PRIdLINENR ":");
const size_t buf_len = 20 + strlen(p);
char *const buf = xmalloc(buf_len);
- snprintf(buf, buf_len, p, (long)SOURCING_LNUM);
+ snprintf(buf, buf_len, p, SOURCING_LNUM);
return buf;
}
return NULL;
@@ -607,12 +593,12 @@ void msg_source(int attr)
char *p = get_emsg_source();
if (p != NULL) {
msg_scroll = true; // this will take more than one line
- msg_attr(p, attr);
+ msg(p, attr);
xfree(p);
}
p = get_emsg_lnum();
if (p != NULL) {
- msg_attr(p, HL_ATTR(HLF_N));
+ msg(p, HL_ATTR(HLF_N));
xfree(p);
last_sourcing_lnum = SOURCING_LNUM; // only once for each line
}
@@ -643,9 +629,8 @@ int emsg_not_now(void)
return false;
}
-static bool emsg_multiline(const char *s, bool multiline)
+bool emsg_multiline(const char *s, bool multiline)
{
- int attr;
bool ignore = false;
// Skip this if not giving error messages at the moment.
@@ -666,7 +651,7 @@ static bool emsg_multiline(const char *s, bool multiline)
// be found, the message will be displayed later on.) "ignore" is set
// when the message should be ignored completely (used for the
// interrupt message).
- if (cause_errthrow(s, severe, &ignore)) {
+ if (cause_errthrow(s, multiline, severe, &ignore)) {
if (!ignore) {
did_emsg++;
}
@@ -707,8 +692,8 @@ static bool emsg_multiline(const char *s, bool multiline)
// Log (silent) errors as debug messages.
if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
- DLOG("(:silent) %s (%s (line %ld))",
- s, SOURCING_NAME, (long)SOURCING_LNUM);
+ DLOG("(:silent) %s (%s (line %" PRIdLINENR "))",
+ s, SOURCING_NAME, SOURCING_LNUM);
} else {
DLOG("(:silent) %s", s);
}
@@ -718,7 +703,7 @@ static bool emsg_multiline(const char *s, bool multiline)
// Log editor errors as INFO.
if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
- ILOG("%s (%s (line %ld))", s, SOURCING_NAME, (long)SOURCING_LNUM);
+ ILOG("%s (%s (line %" PRIdLINENR "))", s, SOURCING_NAME, SOURCING_LNUM);
} else {
ILOG("%s", s);
}
@@ -742,7 +727,7 @@ static bool emsg_multiline(const char *s, bool multiline)
}
emsg_on_display = true; // remember there is an error message
- attr = HL_ATTR(HLF_E); // set highlight mode for error messages
+ int attr = HL_ATTR(HLF_E); // set highlight mode for error messages
if (msg_scrolled != 0) {
need_wait_return = true; // needed in case emsg() is called after
} // wait_return() has reset need_wait_return
@@ -774,10 +759,12 @@ bool emsg(const char *s)
void emsg_invreg(int name)
{
- semsg(_("E354: Invalid register name: '%s'"), transchar(name));
+ semsg(_("E354: Invalid register name: '%s'"), transchar_buf(NULL, name));
}
/// Print an error message with unknown number of arguments
+///
+/// @return whether the message was displayed
bool semsg(const char *const fmt, ...)
FUNC_ATTR_PRINTF(1, 2)
{
@@ -864,7 +851,7 @@ void siemsg(const char *s, ...)
}
/// Give an "Internal error" message.
-void internal_error(char *where)
+void internal_error(const char *where)
{
siemsg(_(e_intern2), where);
}
@@ -888,22 +875,38 @@ void msg_schedule_semsg(const char *const fmt, ...)
loop_schedule_deferred(&main_loop, event_create(msg_semsg_event, 1, s));
}
+static void msg_semsg_multiline_event(void **argv)
+{
+ char *s = argv[0];
+ (void)emsg_multiline(s, true);
+ xfree(s);
+}
+
+void msg_schedule_semsg_multiline(const char *const fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vim_vsnprintf(IObuff, IOSIZE, fmt, ap);
+ va_end(ap);
+
+ char *s = xstrdup(IObuff);
+ loop_schedule_deferred(&main_loop, event_create(msg_semsg_multiline_event, 1, s));
+}
+
/// Like msg(), but truncate to a single line if p_shm contains 't', or when
/// "force" is true. This truncates in another way as for normal messages.
/// Careful: The string may be changed by msg_may_trunc()!
///
/// @return a pointer to the printed message, if wait_return() not called.
-char *msg_trunc_attr(char *s, bool force, int attr)
+char *msg_trunc(char *s, bool force, int attr)
{
- int n;
-
// Add message to history before truncating.
add_msg_hist(s, -1, attr, false);
char *ts = msg_may_trunc(force, s);
msg_hist_off = true;
- n = msg_attr(ts, attr);
+ int n = msg(ts, attr);
msg_hist_off = false;
if (n) {
@@ -1009,12 +1012,10 @@ static void add_msg_hist_multiattr(const char *s, int len, int attr, bool multil
/// @return FAIL if there are no messages.
int delete_first_msg(void)
{
- struct msg_hist *p;
-
if (msg_hist_len <= 0) {
return FAIL;
}
- p = first_msg_hist;
+ struct msg_hist *p = first_msg_hist;
first_msg_hist = p->next;
if (first_msg_hist == NULL) { // history is becoming empty
assert(msg_hist_len == 1);
@@ -1028,13 +1029,9 @@ int delete_first_msg(void)
}
/// :messages command implementation
-void ex_messages(void *const eap_p)
+void ex_messages(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- const exarg_T *const eap = (const exarg_T *)eap_p;
- struct msg_hist *p;
- int c = 0;
-
if (strcmp(eap->arg, "clear") == 0) {
int keep = eap->addr_count == 0 ? 0 : eap->line2;
@@ -1049,9 +1046,10 @@ void ex_messages(void *const eap_p)
return;
}
- p = first_msg_hist;
+ struct msg_hist *p = first_msg_hist;
if (eap->addr_count != 0) {
+ int c = 0;
// Count total messages
for (; p != NULL && !got_int; p = p->next) {
c++;
@@ -1072,7 +1070,7 @@ void ex_messages(void *const eap_p)
for (; p != NULL; p = p->next) {
if (kv_size(p->multiattr) || (p->msg && p->msg[0])) {
Array entry = ARRAY_DICT_INIT;
- ADD(entry, STRING_OBJ(cstr_to_string(p->kind)));
+ ADD(entry, CSTR_TO_OBJ(p->kind));
Array content = ARRAY_DICT_INIT;
if (kv_size(p->multiattr)) {
for (uint32_t i = 0; i < kv_size(p->multiattr); i++) {
@@ -1085,7 +1083,7 @@ void ex_messages(void *const eap_p)
} else if (p->msg && p->msg[0]) {
Array content_entry = ARRAY_DICT_INIT;
ADD(content_entry, INTEGER_OBJ(p->attr));
- ADD(content_entry, STRING_OBJ(cstr_to_string((char *)(p->msg))));
+ ADD(content_entry, CSTR_TO_OBJ(p->msg));
ADD(content, ARRAY_OBJ(content_entry));
}
ADD(entry, ARRAY_OBJ(content));
@@ -1130,8 +1128,6 @@ void msg_end_prompt(void)
void wait_return(int redraw)
{
int c;
- int oldState;
- int tmpState;
int had_got_int;
FILE *save_scriptout;
@@ -1165,7 +1161,7 @@ void wait_return(int redraw)
}
redir_off = true; // don't redirect this message
- oldState = State;
+ int oldState = State;
if (quit_more) {
c = CAR; // just pretend CR was hit
quit_more = false;
@@ -1226,9 +1222,7 @@ void wait_return(int redraw)
} else {
msg_didout = false;
c = K_IGNORE;
- msg_col =
- cmdmsg_rl ? Columns - 1 :
- 0;
+ msg_col = 0;
}
if (quit_more) {
c = CAR; // just pretend CR was hit
@@ -1253,6 +1247,7 @@ void wait_return(int redraw)
|| c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSEMOVE);
os_breakcheck();
+
// Avoid that the mouse-up event causes visual mode to start.
if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE
|| c == K_X1MOUSE || c == K_X2MOUSE) {
@@ -1281,7 +1276,7 @@ void wait_return(int redraw)
// If the screen size changed screen_resize() will redraw the screen.
// Otherwise the screen is only redrawn if 'redraw' is set and no ':'
// typed.
- tmpState = State;
+ int tmpState = State;
State = oldState; // restore State before screen_resize()
setmouse();
msg_check();
@@ -1329,7 +1324,7 @@ static void hit_return_msg(void)
}
/// Set "keep_msg" to "s". Free the old value and check for NULL pointer.
-void set_keep_msg(char *s, int attr)
+void set_keep_msg(const char *s, int attr)
{
xfree(keep_msg);
if (s != NULL && msg_silent == 0) {
@@ -1341,9 +1336,17 @@ void set_keep_msg(char *s, int attr)
keep_msg_attr = attr;
}
-void msgmore(long n)
+/// Return true if printing messages should currently be done.
+bool messaging(void)
{
- long pn;
+ // TODO(bfredl): with general support for "async" messages with p_ch,
+ // this should be re-enabled.
+ return !(p_lz && char_avail() && !KeyTyped) && (p_ch > 0 || ui_has(kUIMessages));
+}
+
+void msgmore(int n)
+{
+ int pn;
if (global_busy // no messages now, wait until global is finished
|| !messaging()) { // 'lazyredraw' set, don't do messages now
@@ -1366,17 +1369,17 @@ void msgmore(long n)
if (pn > p_report) {
if (n > 0) {
vim_snprintf(msg_buf, MSG_BUF_LEN,
- NGETTEXT("%ld more line", "%ld more lines", pn),
+ NGETTEXT("%d more line", "%d more lines", pn),
pn);
} else {
vim_snprintf(msg_buf, MSG_BUF_LEN,
- NGETTEXT("%ld line less", "%ld fewer lines", pn),
+ NGETTEXT("%d line less", "%d fewer lines", pn),
pn);
}
if (got_int) {
xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN);
}
- if (msg(msg_buf)) {
+ if (msg(msg_buf, 0)) {
set_keep_msg(msg_buf, 0);
keep_msg_more = true;
}
@@ -1397,7 +1400,11 @@ void msg_ext_set_kind(const char *msg_kind)
/// Prepare for outputting characters in the command line.
void msg_start(void)
{
- int did_return = false;
+ bool did_return = false;
+
+ if (msg_row < cmdline_row) {
+ msg_row = cmdline_row;
+ }
if (!msg_silent) {
XFREE_CLEAR(keep_msg); // don't display old message now
@@ -1421,7 +1428,7 @@ void msg_start(void)
if (!msg_scroll && full_screen) { // overwrite last message
msg_row = cmdline_row;
- msg_col = cmdmsg_rl ? Columns - 1 : 0;
+ msg_col = 0;
} else if (msg_didout || (p_ch == 0 && !ui_has(kUIMessages))) { // start message on next line
msg_putchar('\n');
did_return = true;
@@ -1462,7 +1469,7 @@ void msg_putchar(int c)
void msg_putchar_attr(int c, int attr)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
if (IS_SPECIAL(c)) {
buf[0] = (char)K_SPECIAL;
@@ -1470,33 +1477,33 @@ void msg_putchar_attr(int c, int attr)
buf[2] = (char)K_THIRD(c);
buf[3] = NUL;
} else {
- buf[utf_char2bytes(c, (char *)buf)] = NUL;
+ buf[utf_char2bytes(c, buf)] = NUL;
}
- msg_puts_attr((const char *)buf, attr);
+ msg_puts_attr(buf, attr);
}
-void msg_outnum(long n)
+void msg_outnum(int n)
{
char buf[20];
- snprintf(buf, sizeof(buf), "%ld", n);
+ snprintf(buf, sizeof(buf), "%d", n);
msg_puts(buf);
}
-void msg_home_replace(char *fname)
+void msg_home_replace(const char *fname)
{
msg_home_replace_attr(fname, 0);
}
-void msg_home_replace_hl(char *fname)
+void msg_home_replace_hl(const char *fname)
{
msg_home_replace_attr(fname, HL_ATTR(HLF_D));
}
-static void msg_home_replace_attr(char *fname, int attr)
+static void msg_home_replace_attr(const char *fname, int attr)
{
char *name = home_replace_save(NULL, fname);
- msg_outtrans_attr(name, attr);
+ msg_outtrans(name, attr);
xfree(name);
}
@@ -1505,44 +1512,33 @@ static void msg_home_replace_attr(char *fname, int attr)
/// Use attributes 'attr'.
///
/// @return the number of characters it takes on the screen.
-int msg_outtrans(char *str)
-{
- return msg_outtrans_attr(str, 0);
-}
-
-int msg_outtrans_attr(const char *str, int attr)
-{
- return msg_outtrans_len_attr(str, (int)strlen(str), attr);
-}
-
-int msg_outtrans_len(const char *str, int len)
+int msg_outtrans(const char *str, int attr)
{
- return msg_outtrans_len_attr(str, len, 0);
+ return msg_outtrans_len(str, (int)strlen(str), attr);
}
/// Output one character at "p".
/// Handles multi-byte characters.
///
/// @return pointer to the next character.
-char *msg_outtrans_one(char *p, int attr)
+const char *msg_outtrans_one(const char *p, int attr)
{
int l;
if ((l = utfc_ptr2len(p)) > 1) {
- msg_outtrans_len_attr(p, l, attr);
+ msg_outtrans_len(p, l, attr);
return p + l;
}
- msg_puts_attr((const char *)transchar_byte((uint8_t)(*p)), attr);
+ msg_puts_attr(transchar_byte_buf(NULL, (uint8_t)(*p)), attr);
return p + 1;
}
-int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
+int msg_outtrans_len(const char *msgstr, int len, int attr)
{
int retval = 0;
const char *str = msgstr;
const char *plain_start = msgstr;
char *s;
- int mb_l;
int c;
int save_got_int = got_int;
@@ -1555,17 +1551,18 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
attr &= ~MSG_HIST;
}
- // If the string starts with a composing character first draw a space on
- // which the composing char can be drawn.
- if (utf_iscomposing(utf_ptr2char((char *)msgstr))) {
- msg_puts_attr(" ", attr);
+ // When drawing over the command line no need to clear it later or remove
+ // the mode message.
+ if (msg_row >= cmdline_row && msg_col == 0) {
+ clear_cmdline = false;
+ mode_displayed = false;
}
// Go over the string. Special characters are translated and printed.
// Normal characters are printed several at a time.
while (--len >= 0 && !got_int) {
// Don't include composing chars after the end.
- mb_l = utfc_ptr2len_len(str, len + 1);
+ int mb_l = utfc_ptr2len_len(str, len + 1);
if (mb_l > 1) {
c = utf_ptr2char(str);
if (vim_isprintc(c)) {
@@ -1575,25 +1572,24 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
// Unprintable multi-byte char: print the printable chars so
// far and the translation of the unprintable char.
if (str > plain_start) {
- msg_puts_attr_len(plain_start, str - plain_start, attr);
+ msg_puts_len(plain_start, str - plain_start, attr);
}
plain_start = str + mb_l;
- msg_puts_attr((const char *)transchar(c),
- (attr == 0 ? HL_ATTR(HLF_8) : attr));
+ msg_puts_attr(transchar_buf(NULL, c), attr == 0 ? HL_ATTR(HLF_8) : attr);
retval += char2cells(c);
}
len -= mb_l - 1;
str += mb_l;
} else {
- s = (char *)transchar_byte((uint8_t)(*str));
+ s = transchar_byte_buf(NULL, (uint8_t)(*str));
if (s[1] != NUL) {
// Unprintable char: print the printable chars so far and the
// translation of the unprintable char.
if (str > plain_start) {
- msg_puts_attr_len(plain_start, str - plain_start, attr);
+ msg_puts_len(plain_start, str - plain_start, attr);
}
plain_start = str + 1;
- msg_puts_attr((const char *)s, attr == 0 ? HL_ATTR(HLF_8) : attr);
+ msg_puts_attr(s, attr == 0 ? HL_ATTR(HLF_8) : attr);
retval += (int)strlen(s);
} else {
retval++;
@@ -1604,7 +1600,7 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
if (str > plain_start && !got_int) {
// Print the printable chars at the end.
- msg_puts_attr_len(plain_start, str - plain_start, attr);
+ msg_puts_len(plain_start, str - plain_start, attr);
}
got_int |= save_got_int;
@@ -1612,11 +1608,11 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
return retval;
}
-void msg_make(char *arg)
+void msg_make(const char *arg)
{
int i;
- static char *str = "eeffoc";
- static char *rs = "Plon#dqg#vxjduB";
+ static const char *str = "eeffoc";
+ static const char *rs = "Plon#dqg#vxjduB";
arg = skipwhite(arg);
for (i = 5; *arg && i >= 0; i--) {
@@ -1667,9 +1663,9 @@ int msg_outtrans_special(const char *strstart, bool from, int maxlen)
}
if (text[0] != NUL && text[1] == NUL) {
// single-byte character or illegal byte
- text = (char *)transchar_byte((uint8_t)text[0]);
+ text = transchar_byte_buf(NULL, (uint8_t)text[0]);
}
- const int len = vim_strsize((char *)text);
+ const int len = vim_strsize(text);
if (maxlen > 0 && retval + len >= maxlen) {
break;
}
@@ -1773,7 +1769,7 @@ const char *str2special(const char **const sp, const bool replace_spaces, const
|| c < ' '
|| (replace_spaces && c == ' ')
|| (replace_lt && c == '<')) {
- return (const char *)get_special_key_name(c, modifiers);
+ return get_special_key_name(c, modifiers);
}
buf[0] = (char)c;
buf[1] = NUL;
@@ -1802,20 +1798,20 @@ void str2specialbuf(const char *sp, char *buf, size_t len)
}
/// print line for :print or :list command
-void msg_prt_line(char *s, int list)
+void msg_prt_line(const char *s, int list)
{
int c;
int col = 0;
int n_extra = 0;
int c_extra = 0;
int c_final = 0;
- char *p_extra = NULL; // init to make SASC shut up
+ const char *p_extra = NULL; // init to make SASC shut up
int n;
int attr = 0;
- char *lead = NULL;
+ const char *lead = NULL;
bool in_multispace = false;
int multispace_pos = 0;
- char *trail = NULL;
+ const char *trail = NULL;
int l;
if (curwin->w_p_list) {
@@ -1878,10 +1874,13 @@ void msg_prt_line(char *s, int list)
continue;
} else {
attr = 0;
- c = (unsigned char)(*s++);
- in_multispace = c == ' ' && ((col > 0 && s[-2] == ' ') || *s == ' ');
- if (!in_multispace) {
- multispace_pos = 0;
+ c = (uint8_t)(*s++);
+ if (list) {
+ in_multispace = c == ' ' && (*s == ' '
+ || (col > 0 && s[-2] == ' '));
+ if (!in_multispace) {
+ multispace_pos = 0;
+ }
}
if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) {
// tab amount depends on current column
@@ -1913,7 +1912,7 @@ void msg_prt_line(char *s, int list)
s--;
} else if (c != NUL && (n = byte2cells(c)) > 1) {
n_extra = n - 1;
- p_extra = (char *)transchar_byte(c);
+ p_extra = transchar_byte_buf(NULL, c);
c_extra = NUL;
c_final = NUL;
c = (unsigned char)(*p_extra++);
@@ -1921,7 +1920,7 @@ void msg_prt_line(char *s, int list)
// the same in plain text.
attr = HL_ATTR(HLF_0);
} else if (c == ' ') {
- if (list && lead != NULL && s <= lead && in_multispace
+ if (lead != NULL && s <= lead && in_multispace
&& curwin->w_p_lcs_chars.leadmultispace != NULL) {
c = curwin->w_p_lcs_chars.leadmultispace[multispace_pos++];
if (curwin->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
@@ -1934,7 +1933,7 @@ void msg_prt_line(char *s, int list)
} else if (trail != NULL && s > trail) {
c = curwin->w_p_lcs_chars.trail;
attr = HL_ATTR(HLF_0);
- } else if (list && in_multispace
+ } else if (in_multispace
&& curwin->w_p_lcs_chars.multispace != NULL) {
c = curwin->w_p_lcs_chars.multispace[multispace_pos++];
if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
@@ -1958,40 +1957,6 @@ void msg_prt_line(char *s, int list)
msg_clr_eos();
}
-/// Use grid_puts() to output one multi-byte character.
-///
-/// @return the pointer "s" advanced to the next character.
-static char *screen_puts_mbyte(char *s, int l, int attr)
-{
- int cw;
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
-
- msg_didout = true; // remember that line is not empty
- cw = utf_ptr2cells(s);
- if (cw > 1
- && (cmdmsg_rl ? msg_col <= 1 : msg_col == Columns - 1)) {
- // Doesn't fit, print a highlighted '>' to fill it up.
- msg_screen_putchar('>', HL_ATTR(HLF_AT));
- return s;
- }
-
- grid_puts_len(&msg_grid_adj, s, l, msg_row, msg_col, attr);
- if (cmdmsg_rl) {
- msg_col -= cw;
- if (msg_col == 0) {
- msg_col = Columns;
- msg_row++;
- }
- } else {
- msg_col += cw;
- if (msg_col >= Columns) {
- msg_col = 0;
- msg_row++;
- }
- }
- return s + l;
-}
-
/// Output a string to the screen at position msg_row, msg_col.
/// Update msg_row and msg_col for the next message.
void msg_puts(const char *s)
@@ -2007,29 +1972,23 @@ void msg_puts_title(const char *s)
/// Show a message in such a way that it always fits in the line. Cut out a
/// part in the middle and replace it with "..." when necessary.
/// Does not handle multi-byte characters!
-void msg_outtrans_long_attr(char *longstr, int attr)
-{
- msg_outtrans_long_len_attr(longstr, (int)strlen(longstr), attr);
-}
-
-void msg_outtrans_long_len_attr(char *longstr, int len, int attr)
+void msg_outtrans_long(const char *longstr, int attr)
{
+ int len = (int)strlen(longstr);
int slen = len;
- int room;
-
- room = Columns - msg_col;
+ int room = Columns - msg_col;
if (len > room && room >= 20) {
slen = (room - 3) / 2;
- msg_outtrans_len_attr(longstr, slen, attr);
+ msg_outtrans_len(longstr, slen, attr);
msg_puts_attr("...", HL_ATTR(HLF_8));
}
- msg_outtrans_len_attr(longstr + len - slen, slen, attr);
+ msg_outtrans_len(longstr + len - slen, slen, attr);
}
/// Basic function for writing a message with highlight attributes.
void msg_puts_attr(const char *const s, const int attr)
{
- msg_puts_attr_len(s, -1, attr);
+ msg_puts_len(s, -1, attr);
}
/// Write a message with highlight attributes
@@ -2037,7 +1996,7 @@ void msg_puts_attr(const char *const s, const int attr)
/// @param[in] str NUL-terminated message string.
/// @param[in] len Length of the string or -1.
/// @param[in] attr Highlight attribute.
-void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
+void msg_puts_len(const char *const str, const ptrdiff_t len, int attr)
FUNC_ATTR_NONNULL_ALL
{
assert(len < 0 || memchr(str, 0, (size_t)len) == NULL);
@@ -2113,7 +2072,7 @@ void msg_printf_attr(const int attr, const char *const fmt, ...)
va_end(ap);
msg_scroll = true;
- msg_puts_attr_len(msgbuf, (ptrdiff_t)len, attr);
+ msg_puts_len(msgbuf, (ptrdiff_t)len, attr);
}
static void msg_ext_emit_chunk(void)
@@ -2130,19 +2089,13 @@ static void msg_ext_emit_chunk(void)
ADD(msg_ext_chunks, ARRAY_OBJ(chunk));
}
-/// The display part of msg_puts_attr_len().
+/// The display part of msg_puts_len().
/// May be called recursively to display scroll-back text.
static void msg_puts_display(const char *str, int maxlen, int attr, int recurse)
{
const char *s = str;
- const char *t_s = str; // String from "t_s" to "s" is still todo.
- int t_col = 0; // Screen cells todo, 0 when "t_s" not used.
- int l;
- int cw;
- const char *sb_str = (char *)str;
+ const char *sb_str = str;
int sb_col = msg_col;
- int wrap;
- int did_last_char;
did_wait_return = false;
@@ -2152,197 +2105,175 @@ static void msg_puts_display(const char *str, int maxlen, int attr, int recurse)
msg_ext_last_attr = attr;
}
// Concat pieces with the same highlight
- size_t len = strnlen(str, (size_t)maxlen); // -V781
- ga_concat_len(&msg_ext_last_chunk, (char *)str, len);
+ size_t len = strnlen(str, (size_t)maxlen);
+ ga_concat_len(&msg_ext_last_chunk, str, len);
msg_ext_cur_len += len;
return;
}
+ int print_attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
msg_grid_validate();
cmdline_was_last_drawn = redrawing_cmdline;
- while ((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL) {
- // We are at the end of the screen line when:
- // - When outputting a newline.
- // - When outputting a character in the last column.
- if (!recurse && msg_row >= Rows - 1
- && (*s == '\n' || (cmdmsg_rl
- ? (msg_col <= 1
- || (*s == TAB && msg_col <= 7)
- || (utf_ptr2cells(s) > 1
- && msg_col <= 2))
- : ((*s != '\r' && msg_col + t_col >= Columns - 1)
- || (*s == TAB
- && msg_col + t_col >= ((Columns - 1) & ~7))
- || (utf_ptr2cells(s) > 1
- && msg_col + t_col >= Columns - 2))))) {
- // The screen is scrolled up when at the last row (some terminals
- // scroll automatically, some don't. To avoid problems we scroll
- // ourselves).
- if (t_col > 0) {
- // output postponed text
- t_puts(&t_col, t_s, s, attr);
- }
+ int msg_row_pending = -1;
- // When no more prompt and no more room, truncate here
+ while (true) {
+ if (msg_col >= Columns) {
+ if (p_more && !recurse) {
+ // Store text for scrolling back.
+ store_sb_text(&sb_str, s, attr, &sb_col, true);
+ }
if (msg_no_more && lines_left == 0) {
break;
}
- // Scroll the screen up one line.
- bool has_last_char = ((uint8_t)(*s) >= ' ' && !cmdmsg_rl);
- msg_scroll_up(!has_last_char, false);
+ msg_col = 0;
+ msg_row++;
+ msg_didout = false;
+ }
- msg_row = Rows - 2;
- if (msg_col >= Columns) { // can happen after screen resize
- msg_col = Columns - 1;
- }
+ if (msg_row >= Rows) {
+ msg_row = Rows - 1;
- // Display char in last column before showing more-prompt.
- if (has_last_char) {
- if (maxlen >= 0) {
- // Avoid including composing chars after the end.
- l = utfc_ptr2len_len(s, (int)((str + maxlen) - s));
- } else {
- l = utfc_ptr2len(s);
- }
- s = screen_puts_mbyte((char *)s, l, attr);
- did_last_char = true;
- } else {
- did_last_char = false;
+ // When no more prompt and no more room, truncate here
+ if (msg_no_more && lines_left == 0) {
+ break;
}
- // Tricky: if last cell will be written, delay the throttle until
- // after the first scroll. Otherwise we would need to keep track of it.
- if (has_last_char && msg_do_throttle()) {
- if (!msg_grid.throttled) {
- msg_grid_scroll_discount++;
+ if (!recurse) {
+ if (msg_row_pending >= 0) {
+ msg_line_flush();
+ msg_row_pending = -1;
}
- msg_grid.throttled = true;
- }
-
- if (p_more) {
- // Store text for scrolling back.
- store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true);
- }
- inc_msg_scrolled();
- need_wait_return = true; // may need wait_return() in main()
- redraw_cmdline = true;
- if (cmdline_row > 0 && !exmode_active) {
- cmdline_row--;
- }
+ // Scroll the screen up one line.
+ msg_scroll_up(true, false);
- // If screen is completely filled and 'more' is set then wait
- // for a character.
- if (lines_left > 0) {
- lines_left--;
- }
- if (p_more && lines_left == 0 && State != MODE_HITRETURN
- && !msg_no_more && !exmode_active) {
- if (do_more_prompt(NUL)) {
- s = confirm_msg_tail;
+ inc_msg_scrolled();
+ need_wait_return = true; // may need wait_return() in main()
+ redraw_cmdline = true;
+ if (cmdline_row > 0 && !exmode_active) {
+ cmdline_row--;
}
- if (quit_more) {
- return;
+
+ // If screen is completely filled and 'more' is set then wait
+ // for a character.
+ if (lines_left > 0) {
+ lines_left--;
}
- }
- // When we displayed a char in last column need to check if there
- // is still more.
- if (did_last_char) {
- continue;
+ if (p_more && lines_left == 0 && State != MODE_HITRETURN
+ && !msg_no_more && !exmode_active) {
+ if (do_more_prompt(NUL)) {
+ s = confirm_msg_tail;
+ }
+ if (quit_more) {
+ return;
+ }
+ }
}
}
- wrap = *s == '\n'
- || msg_col + t_col >= Columns
- || (utf_ptr2cells(s) > 1
- && msg_col + t_col >= Columns - 1)
- ;
- if (t_col > 0 && (wrap || *s == '\r' || *s == '\b'
- || *s == '\t' || *s == BELL)) {
- // Output any postponed text.
- t_puts(&t_col, t_s, s, attr);
+ if (!((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL)) {
+ break;
}
- if (wrap && p_more && !recurse) {
- // Store text for scrolling back.
- store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true);
+ if (msg_row != msg_row_pending && ((uint8_t)(*s) >= 0x20 || *s == TAB)) {
+ // TODO(bfredl): this logic is messier that it has to be. What
+ // messages really want is its own private linebuf_char buffer.
+ if (msg_row_pending >= 0) {
+ msg_line_flush();
+ }
+ grid_line_start(&msg_grid_adj, msg_row);
+ msg_row_pending = msg_row;
}
- if (*s == '\n') { // go to next line
- msg_didout = false; // remember that line is empty
- if (cmdmsg_rl) {
- msg_col = Columns - 1;
- } else {
- msg_col = 0;
- }
- if (++msg_row >= Rows) { // safety check
- msg_row = Rows - 1;
- }
- } else if (*s == '\r') { // go to column 0
- msg_col = 0;
- } else if (*s == '\b') { // go to previous char
- if (msg_col) {
- msg_col--;
- }
- } else if (*s == TAB) { // translate Tab into spaces
- do {
- msg_screen_putchar(' ', attr);
- } while (msg_col & 7);
- } else if (*s == BELL) { // beep (from ":sh")
- vim_beep(BO_SH);
- } else if ((uint8_t)(*s) >= 0x20) { // printable char
- cw = utf_ptr2cells(s);
- if (maxlen >= 0) {
- // avoid including composing chars after the end
- l = utfc_ptr2len_len(s, (int)((str + maxlen) - s));
+ if ((uint8_t)(*s) >= 0x20) { // printable char
+ int cw = utf_ptr2cells(s);
+ // avoid including composing chars after the end
+ int l = (maxlen >= 0) ? utfc_ptr2len_len(s, (int)((str + maxlen) - s)) : utfc_ptr2len(s);
+
+ if (cw > 1 && (msg_col == Columns - 1)) {
+ // Doesn't fit, print a highlighted '>' to fill it up.
+ grid_line_puts(msg_col, ">", 1, HL_ATTR(HLF_AT));
+ cw = 1;
} else {
- l = utfc_ptr2len(s);
+ grid_line_puts(msg_col, s, l, print_attr);
+ s += l;
}
- // When drawing from right to left or when a double-wide character
- // doesn't fit, draw a single character here. Otherwise collect
- // characters and draw them all at once later.
- if (cmdmsg_rl || (cw > 1 && msg_col + t_col >= Columns - 1)) {
- if (l > 1) {
- s = screen_puts_mbyte((char *)s, l, attr) - 1;
- } else {
- msg_screen_putchar(*s, attr);
+ msg_didout = true; // remember that line is not empty
+ msg_col += cw;
+ } else {
+ char c = *s++;
+ if (c == '\n') { // go to next line
+ msg_didout = false; // remember that line is empty
+ msg_col = 0;
+ msg_row++;
+ if (p_more && !recurse) {
+ // Store text for scrolling back.
+ store_sb_text(&sb_str, s, attr, &sb_col, true);
}
- } else {
- // postpone this character until later
- if (t_col == 0) {
- t_s = s;
+ } else if (c == '\r') { // go to column 0
+ msg_col = 0;
+ } else if (c == '\b') { // go to previous char
+ if (msg_col) {
+ msg_col--;
}
- t_col += cw;
- s += l - 1;
+ } else if (c == TAB) { // translate Tab into spaces
+ do {
+ grid_line_puts(msg_col, " ", 1, print_attr);
+ msg_col += 1;
+
+ if (msg_col == Columns) {
+ break;
+ }
+ } while (msg_col & 7);
+ } else if (c == BELL) { // beep (from ":sh")
+ vim_beep(BO_SH);
}
}
- s++;
}
- // Output any postponed text.
- if (t_col > 0) {
- t_puts(&t_col, t_s, s, attr);
+ if (msg_row_pending >= 0) {
+ msg_line_flush();
}
+ msg_cursor_goto(msg_row, msg_col);
+
if (p_more && !recurse && !(s == sb_str + 1 && *sb_str == '\n')) {
- store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, false);
+ store_sb_text(&sb_str, s, attr, &sb_col, false);
}
msg_check();
}
+void msg_line_flush(void)
+{
+ if (cmdmsg_rl) {
+ grid_line_mirror();
+ }
+ grid_line_flush_if_valid_row();
+}
+
+void msg_cursor_goto(int row, int col)
+{
+ ScreenGrid *grid = &msg_grid_adj;
+ if (cmdmsg_rl) {
+ col = Columns - 1 - col;
+ }
+ grid_adjust(&grid, &row, &col);
+ ui_grid_cursor_goto(grid->handle, row, col);
+}
+
/// @return true when ":filter pattern" was used and "msg" does not match
/// "pattern".
-bool message_filtered(char *msg)
+bool message_filtered(const char *msg)
{
if (cmdmod.cmod_filter_regmatch.regprog == NULL) {
return false;
}
- bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, (colnr_T)0);
+ bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, 0);
return cmdmod.cmod_filter_force ? match : !match;
}
@@ -2502,7 +2433,7 @@ static sb_clear_T do_clear_sb_text = SB_CLEAR_NONE;
/// @param sb_str start of string
/// @param s just after string
/// @param finish line ends
-static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int finish)
+static void store_sb_text(const char **sb_str, const char *s, int attr, int *sb_col, int finish)
{
msgchunk_T *mp;
@@ -2510,11 +2441,14 @@ static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int fin
|| do_clear_sb_text == SB_CLEAR_CMDLINE_DONE) {
clear_sb_text(do_clear_sb_text == SB_CLEAR_ALL);
msg_sb_eol(); // prevent messages from overlapping
+ if (do_clear_sb_text == SB_CLEAR_CMDLINE_DONE && s > *sb_str && **sb_str == '\n') {
+ (*sb_str)++;
+ }
do_clear_sb_text = SB_CLEAR_NONE;
}
if (s > *sb_str) {
- mp = xmalloc((sizeof(msgchunk_T) + (size_t)(s - *sb_str)));
+ mp = xmalloc(offsetof(msgchunk_T, sb_text) + (size_t)(s - *sb_str) + 1);
mp->sb_eol = (char)finish;
mp->sb_msg_col = *sb_col;
mp->sb_attr = attr;
@@ -2652,15 +2586,11 @@ void msg_sb_eol(void)
static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
{
msgchunk_T *mp = smp;
- char *p;
- for (;;) {
+ while (true) {
msg_row = row;
msg_col = mp->sb_msg_col;
- p = mp->sb_text;
- if (*p == '\n') { // don't display the line break
- p++;
- }
+ char *p = mp->sb_text;
msg_puts_display(p, -1, mp->sb_attr, true);
if (mp->sb_eol || mp->sb_next == NULL) {
break;
@@ -2671,26 +2601,6 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
return mp->sb_next;
}
-/// Output any postponed text for msg_puts_attr_len().
-static void t_puts(int *t_col, const char *t_s, const char *s, int attr)
-{
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
- // Output postponed text.
- msg_didout = true; // Remember that line is not empty.
- grid_puts_len(&msg_grid_adj, (char *)t_s, (int)(s - t_s), msg_row, msg_col, attr);
- msg_col += *t_col;
- *t_col = 0;
- // If the string starts with a composing character don't increment the
- // column position for it.
- if (utf_iscomposing(utf_ptr2char(t_s))) {
- msg_col--;
- }
- if (msg_col >= Columns) {
- msg_col = 0;
- msg_row++;
- }
-}
-
/// @return true when messages should be printed to stdout/stderr:
/// - "batch mode" ("silent mode", -es/-Es)
/// - no UI and not embedded
@@ -2736,18 +2646,10 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
int cw = utf_char2cells(utf_ptr2char(s));
// primitive way to compute the current column
- if (cmdmsg_rl) {
- if (*s == '\r' || *s == '\n') {
- msg_col = Columns - 1;
- } else {
- msg_col -= cw;
- }
+ if (*s == '\r' || *s == '\n') {
+ msg_col = 0;
} else {
- if (*s == '\r' || *s == '\n') {
- msg_col = 0;
- } else {
- msg_col += cw;
- }
+ msg_col += cw;
}
s += len;
}
@@ -2767,11 +2669,9 @@ static int do_more_prompt(int typed_char)
int oldState = State;
int c;
int retval = false;
- int toscroll;
bool to_redraw = false;
msgchunk_T *mp_last = NULL;
msgchunk_T *mp;
- int i;
// If headless mode is enabled and no input is required, this variable
// will be true. However If server mode is enabled, the message "--more--"
@@ -2789,7 +2689,7 @@ static int do_more_prompt(int typed_char)
if (typed_char == 'G') {
// "g<": Find first line on the last page.
mp_last = msg_sb_start(last_msgchunk);
- for (i = 0; i < Rows - 2 && mp_last != NULL
+ for (int i = 0; i < Rows - 2 && mp_last != NULL
&& mp_last->sb_prev != NULL; i++) {
mp_last = msg_sb_start(mp_last->sb_prev);
}
@@ -2800,7 +2700,7 @@ static int do_more_prompt(int typed_char)
if (typed_char == NUL) {
msg_moremsg(false);
}
- for (;;) {
+ while (true) {
// Get a typed character directly from the user.
if (used_typed_char != NUL) {
c = used_typed_char; // was typed at hit-enter prompt
@@ -2809,7 +2709,7 @@ static int do_more_prompt(int typed_char)
c = get_keystroke(resize_events);
}
- toscroll = 0;
+ int toscroll = 0;
switch (c) {
case BS: // scroll one line back
case K_BS:
@@ -2907,13 +2807,13 @@ static int do_more_prompt(int typed_char)
}
// go to start of line at top of the screen
- for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) {
+ for (int i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) {
mp = msg_sb_start(mp->sb_prev);
}
if (mp != NULL && (mp->sb_prev != NULL || to_redraw)) {
// Find line to be displayed at top
- for (i = 0; i > toscroll; i--) {
+ for (int i = 0; i > toscroll; i--) {
if (mp == NULL || mp->sb_prev == NULL) {
break;
}
@@ -2937,7 +2837,7 @@ static int do_more_prompt(int typed_char)
// event fragmentization, not unnecessary scroll events).
grid_fill(&msg_grid_adj, 0, Rows, 0, Columns, ' ', ' ',
HL_ATTR(HLF_MSG));
- for (i = 0; mp != NULL && i < Rows - 1; i++) {
+ for (int i = 0; mp != NULL && i < Rows - 1; i++) {
mp = disp_sb_line(i, mp);
msg_scrolled++;
}
@@ -2996,8 +2896,6 @@ static int do_more_prompt(int typed_char)
if (quit_more) {
msg_row = Rows - 1;
msg_col = 0;
- } else if (cmdmsg_rl) {
- msg_col = Columns - 1;
}
entered = false;
@@ -3038,37 +2936,17 @@ void os_msg(const char *str)
}
#endif // MSWIN
-/// Put a character on the screen at the current message position and advance
-/// to the next position. Only for printable ASCII!
-static void msg_screen_putchar(int c, int attr)
-{
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
- msg_didout = true; // remember that line is not empty
- grid_putchar(&msg_grid_adj, c, msg_row, msg_col, attr);
- if (cmdmsg_rl) {
- if (--msg_col == 0) {
- msg_col = Columns;
- msg_row++;
- }
- } else {
- if (++msg_col >= Columns) {
- msg_col = 0;
- msg_row++;
- }
- }
-}
-
void msg_moremsg(int full)
{
- int attr;
- char *s = _("-- More --");
-
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M));
- grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr);
+ int attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M));
+ grid_line_start(&msg_grid_adj, Rows - 1);
+ int len = grid_line_puts(0, _("-- More --"), -1, attr);
if (full) {
- grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "),
- Rows - 1, vim_strsize(s), attr);
+ len += grid_line_puts(len, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "),
+ -1, attr);
}
+ grid_line_cursor_goto(len);
+ grid_line_flush();
}
/// Repeat the message for the current mode: MODE_ASKMORE, MODE_EXTERNCMD,
@@ -3115,12 +2993,15 @@ void msg_clr_eos_force(void)
return;
}
int msg_startcol = (cmdmsg_rl) ? 0 : msg_col;
- int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : Columns;
+ int msg_endcol = (cmdmsg_rl) ? Columns - msg_col : Columns;
+ // TODO(bfredl): ugly, this state should already been validated at this
+ // point. But msg_clr_eos() is called in a lot of places.
if (msg_grid.chars && msg_row < msg_grid_pos) {
- // TODO(bfredl): ugly, this state should already been validated at this
- // point. But msg_clr_eos() is called in a lot of places.
- msg_row = msg_grid_pos;
+ msg_grid_validate();
+ if (msg_row < msg_grid_pos) {
+ msg_row = msg_grid_pos;
+ }
}
grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol,
@@ -3129,7 +3010,7 @@ void msg_clr_eos_force(void)
' ', ' ', HL_ATTR(HLF_MSG));
redraw_cmdline = true; // overwritten the command line
- if (msg_row < Rows - 1 || msg_col == (cmdmsg_rl ? Columns : 0)) {
+ if (msg_row < Rows - 1 || msg_col == 0) {
clear_cmdline = false; // command line has been cleared
mode_displayed = false; // mode cleared or overwritten
}
@@ -3305,7 +3186,7 @@ static void redir_write(const char *const str, const ptrdiff_t maxlen)
write_reg_contents(redir_reg, s, (ssize_t)len, true);
}
if (redir_vname) {
- var_redir_str((char *)s, (int)maxlen);
+ var_redir_str(s, (int)maxlen);
}
// Write and adjust the current column.
@@ -3414,7 +3295,7 @@ int verbose_open(void)
/// Give a warning message (for searching).
/// Use 'w' highlighting and may repeat the message after redrawing
-void give_warning(char *message, bool hl)
+void give_warning(const char *message, bool hl)
FUNC_ATTR_NONNULL_ARG(1)
{
// Don't do this for ":silent".
@@ -3437,7 +3318,7 @@ void give_warning(char *message, bool hl)
msg_ext_set_kind("wmsg");
}
- if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) {
+ if (msg(message, keep_msg_attr) && msg_scrolled == 0) {
set_keep_msg(message, keep_msg_attr);
}
msg_didout = false; // Overwrite this message.
@@ -3447,9 +3328,22 @@ void give_warning(char *message, bool hl)
no_wait_return--;
}
-void give_warning2(char *const message, char *const a1, bool hl)
+/// Shows a warning, with optional highlighting.
+///
+/// @param hl enable highlighting
+/// @param fmt printf-style format message
+///
+/// @see smsg
+/// @see semsg
+void swmsg(bool hl, const char *const fmt, ...)
+ FUNC_ATTR_PRINTF(2, 3)
{
- vim_snprintf(IObuff, IOSIZE, message, a1);
+ va_list args;
+
+ va_start(args, fmt);
+ vim_vsnprintf(IObuff, IOSIZE, fmt, args);
+ va_end(args);
+
give_warning(IObuff, hl);
}
@@ -3471,14 +3365,8 @@ void msg_advance(int col)
if (col >= Columns) { // not enough room
col = Columns - 1;
}
- if (cmdmsg_rl) {
- while (msg_col > Columns - col) {
- msg_putchar(' ');
- }
- } else {
- while (msg_col < col) {
- msg_putchar(' ');
- }
+ while (msg_col < col) {
+ msg_putchar(' ');
}
}
@@ -3502,12 +3390,11 @@ void msg_advance(int col)
/// @param textfiel IObuff for inputdialog(), NULL otherwise
/// @param ex_cmd when true pressing : accepts default and starts Ex command
/// @returns 0 if cancelled, otherwise the nth button (1-indexed).
-int do_dialog(int type, char *title, char *message, char *buttons, int dfltbutton, char *textfield,
- int ex_cmd)
+int do_dialog(int type, const char *title, const char *message, const char *buttons, int dfltbutton,
+ const char *textfield, int ex_cmd)
{
int retval = 0;
char *hotkeys;
- int c;
int i;
if (silent_mode // No dialogs in silent mode ("ex -s")
@@ -3528,9 +3415,9 @@ int do_dialog(int type, char *title, char *message, char *buttons, int dfltbutto
no_wait_return++;
hotkeys = msg_show_console_dialog(message, buttons, dfltbutton);
- for (;;) {
+ while (true) {
// Get a typed character directly from the user.
- c = get_keystroke(NULL);
+ int c = get_keystroke(NULL);
switch (c) {
case CAR: // User accepts default option
case NL:
@@ -3611,7 +3498,7 @@ static int copy_char(const char *from, char *to, bool lowercase)
/// corresponding button has a hotkey
///
/// @return Pointer to memory allocated for storing hotkeys
-static char *console_dialog_alloc(const char *message, char *buttons, bool has_hotkey[])
+static char *console_dialog_alloc(const char *message, const char *buttons, bool has_hotkey[])
{
int lenhotkey = HOTK_LEN; // count first button
has_hotkey[0] = false;
@@ -3619,7 +3506,7 @@ static char *console_dialog_alloc(const char *message, char *buttons, bool has_h
// Compute the size of memory to allocate.
int len = 0;
int idx = 0;
- char *r = buttons;
+ const char *r = buttons;
while (*r) {
if (*r == DLG_BUTTON_SEP) {
len += 3; // '\n' -> ', '; 'x' -> '(x)'
@@ -3665,7 +3552,7 @@ static char *console_dialog_alloc(const char *message, char *buttons, bool has_h
/// The hotkeys can be multi-byte characters, but without combining chars.
///
/// @return an allocated string with hotkeys.
-static char *msg_show_console_dialog(char *message, char *buttons, int dfltbutton)
+static char *msg_show_console_dialog(const char *message, const char *buttons, int dfltbutton)
FUNC_ATTR_NONNULL_RET
{
bool has_hotkey[HAS_HOTKEY_LEN] = { false };
@@ -3685,7 +3572,7 @@ static char *msg_show_console_dialog(char *message, char *buttons, int dfltbutto
/// @param has_hotkey An element in this array is true if corresponding button
/// has a hotkey
/// @param[out] hotkeys_ptr Pointer to the memory location where hotkeys will be copied
-static void copy_hotkeys_and_msg(const char *message, char *buttons, int default_button_idx,
+static void copy_hotkeys_and_msg(const char *message, const char *buttons, int default_button_idx,
const bool has_hotkey[], char *hotkeys_ptr)
{
*confirm_msg = '\n';
@@ -3708,7 +3595,7 @@ static void copy_hotkeys_and_msg(const char *message, char *buttons, int default
}
int idx = 0;
- char *r = buttons;
+ const char *r = buttons;
while (*r) {
if (*r == DLG_BUTTON_SEP) {
*msgp++ = ',';
@@ -3812,3 +3699,21 @@ int vim_dialog_yesnoallcancel(int type, char *title, char *message, int dflt)
}
return VIM_CANCEL;
}
+
+/// Check if there should be a delay to allow the user to see a message.
+///
+/// Used before clearing or redrawing the screen or the command line.
+void msg_check_for_delay(bool check_msg_scroll)
+{
+ if ((emsg_on_display || (check_msg_scroll && msg_scroll))
+ && !did_wait_return
+ && emsg_silent == 0
+ && !in_assert_fails) {
+ ui_flush();
+ os_delay(1006, true);
+ emsg_on_display = false;
+ if (check_msg_scroll) {
+ msg_scroll = false;
+ }
+ }
+}
diff --git a/src/nvim/message.h b/src/nvim/message.h
index 191d3b8da7..adbb40277b 100644
--- a/src/nvim/message.h
+++ b/src/nvim/message.h
@@ -1,30 +1,37 @@
-#ifndef NVIM_MESSAGE_H
-#define NVIM_MESSAGE_H
+#pragma once
-#include <stdarg.h>
+#include <errno.h>
#include <stdbool.h>
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
+#include <stdio.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
-
-// Types of dialogs passed to do_dialog().
-#define VIM_GENERIC 0
-#define VIM_ERROR 1
-#define VIM_WARNING 2
-#define VIM_INFO 3
-#define VIM_QUESTION 4
-#define VIM_LAST_TYPE 4 // sentinel value
-
-// Return values for functions like vim_dialogyesno()
-#define VIM_YES 2
-#define VIM_NO 3
-#define VIM_CANCEL 4
-#define VIM_ALL 5
-#define VIM_DISCARDALL 6
+#include "nvim/macros_defs.h"
+
+/// Types of dialogs passed to do_dialog().
+enum {
+ VIM_GENERIC = 0,
+ VIM_ERROR = 1,
+ VIM_WARNING = 2,
+ VIM_INFO = 3,
+ VIM_QUESTION = 4,
+ VIM_LAST_TYPE = 4, ///< sentinel value
+};
+
+/// Return values for functions like vim_dialogyesno()
+enum {
+ VIM_YES = 2,
+ VIM_NO = 3,
+ VIM_CANCEL = 4,
+ VIM_ALL = 5,
+ VIM_DISCARDALL = 6,
+};
+
+/// special attribute addition: Put message in history
+enum { MSG_HIST = 0x1000, };
typedef struct {
String text;
@@ -36,8 +43,8 @@ typedef kvec_t(HlMessageChunk) HlMessage;
/// Message history for `:messages`
typedef struct msg_hist {
struct msg_hist *next; ///< Next message.
- char *msg; ///< Message text.
- const char *kind; ///< Message kind (for msg_ext)
+ char *msg; ///< Message text.
+ const char *kind; ///< Message kind (for msg_ext)
int attr; ///< Message highlighting.
bool multiline; ///< Multiline message.
HlMessage multiattr; ///< multiattr message.
@@ -48,27 +55,39 @@ extern MessageHistoryEntry *first_msg_hist;
/// Last message
extern MessageHistoryEntry *last_msg_hist;
-EXTERN bool msg_ext_need_clear INIT(= false);
+EXTERN bool msg_ext_need_clear INIT( = false);
+
+/// allocated grid for messages. Used when display+=msgsep is set, or
+/// ext_multigrid is active. See also the description at msg_scroll_flush()
+EXTERN ScreenGrid msg_grid INIT( = SCREEN_GRID_INIT);
+EXTERN int msg_grid_pos INIT( = 0);
-// allocated grid for messages. Used when display+=msgsep is set, or
-// ext_multigrid is active. See also the description at msg_scroll_flush()
-EXTERN ScreenGrid msg_grid INIT(= SCREEN_GRID_INIT);
-EXTERN int msg_grid_pos INIT(= 0);
+/// "adjusted" message grid. This grid accepts positions relative to
+/// default_grid. Internally it will be translated to a position on msg_grid
+/// relative to the start of the message area, or directly mapped to default_grid
+/// for legacy (display-=msgsep) message scroll behavior.
+/// TODO(bfredl): refactor "internal" message logic, msg_row etc
+/// to use the correct positions already.
+EXTERN ScreenGrid msg_grid_adj INIT( = SCREEN_GRID_INIT);
-// "adjusted" message grid. This grid accepts positions relative to
-// default_grid. Internally it will be translated to a position on msg_grid
-// relative to the start of the message area, or directly mapped to default_grid
-// for legacy (display-=msgsep) message scroll behavior.
-// // TODO(bfredl): refactor "internal" message logic, msg_row etc
-// to use the correct positions already.
-EXTERN ScreenGrid msg_grid_adj INIT(= SCREEN_GRID_INIT);
+/// value of msg_scrolled at latest msg_scroll_flush.
+EXTERN int msg_scrolled_at_flush INIT( = 0);
-// value of msg_scrolled at latest msg_scroll_flush.
-EXTERN int msg_scrolled_at_flush INIT(= 0);
+EXTERN int msg_grid_scroll_discount INIT( = 0);
-EXTERN int msg_grid_scroll_discount INIT(= 0);
+EXTERN int msg_listdo_overwrite INIT( = 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "message.h.generated.h"
#endif
-#endif // NVIM_MESSAGE_H
+
+// Prefer using semsg(), because perror() may send the output to the wrong
+// destination and mess up the screen.
+#define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno))
+
+#ifndef MSWIN
+/// Headless (no UI) error message handler.
+# define os_errmsg(str) fprintf(stderr, "%s", (str))
+/// Headless (no UI) message handler.
+# define os_msg(str) printf("%s", (str))
+#endif
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index 950b025e53..8fe3864424 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -1,31 +1,30 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
+#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
+#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
@@ -33,18 +32,18 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
-#include "nvim/syntax.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -86,15 +85,11 @@ static int get_mouse_class(char *p)
/// Move "pos" back to the start of the word it's in.
static void find_start_of_word(pos_T *pos)
{
- char *line;
- int cclass;
- int col;
-
- line = ml_get(pos->lnum);
- cclass = get_mouse_class(line + pos->col);
+ char *line = ml_get(pos->lnum);
+ int cclass = get_mouse_class(line + pos->col);
while (pos->col > 0) {
- col = pos->col - 1;
+ int col = pos->col - 1;
col -= utf_head_off(line, line + col);
if (get_mouse_class(line + col) != cclass) {
break;
@@ -107,18 +102,14 @@ static void find_start_of_word(pos_T *pos)
/// When 'selection' is "exclusive", the position is just after the word.
static void find_end_of_word(pos_T *pos)
{
- char *line;
- int cclass;
- int col;
-
- line = ml_get(pos->lnum);
+ char *line = ml_get(pos->lnum);
if (*p_sel == 'e' && pos->col > 0) {
pos->col--;
pos->col -= utf_head_off(line, line + pos->col);
}
- cclass = get_mouse_class(line + pos->col);
+ int cclass = get_mouse_class(line + pos->col);
while (line[pos->col] != NUL) {
- col = pos->col + utfc_ptr2len(line + pos->col);
+ int col = pos->col + utfc_ptr2len(line + pos->col);
if (get_mouse_class(line + col) != cclass) {
if (*p_sel == 'e') {
pos->col = col;
@@ -143,6 +134,8 @@ static void move_tab_to_mouse(void)
}
}
+static bool got_click = false; // got a click some time back
+
/// Call click definition function for column "col" in the "click_defs" array for button
/// "which_button".
static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button)
@@ -198,6 +191,8 @@ static void call_click_def_func(StlClickDefinition *click_defs, int col, int whi
typval_T rettv;
(void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv);
tv_clear(&rettv);
+ // Make sure next click does not register as drag when callback absorbs the release event.
+ got_click = false;
}
/// Translate window coordinates to buffer position without any side effects.
@@ -218,27 +213,37 @@ static int get_fpos_of_mouse(pos_T *mpos)
if (wp == NULL) {
return IN_UNKNOWN;
}
+ int winrow = row;
+ int wincol = col;
+
+ // compute the position in the buffer line from the posn on the screen
+ bool below_buffer = mouse_comp_pos(wp, &row, &col, &mpos->lnum);
+
+ if (!below_buffer && *wp->w_p_stc != NUL
+ && (wp->w_p_rl
+ ? wincol >= wp->w_width_inner - win_col_off(wp)
+ : wincol < win_col_off(wp))) {
+ return MOUSE_STATUSCOL;
+ }
// winpos and height may change in win_enter()!
- if (row + wp->w_winbar_height >= wp->w_height) { // In (or below) status line
+ if (winrow >= wp->w_height_inner) { // In (or below) status line
return IN_STATUS_LINE;
}
- if (col >= wp->w_width) { // In vertical separator line
- return IN_SEP_LINE;
- }
- if (wp != curwin) {
- return IN_UNKNOWN;
+ if (winrow == -1 && wp->w_winbar_height != 0) {
+ return MOUSE_WINBAR;
}
- // compute the position in the buffer line from the posn on the screen
- if (mouse_comp_pos(curwin, &row, &col, &mpos->lnum)) {
- return IN_STATUS_LINE; // past bottom
+ if (wincol >= wp->w_width_inner) { // In vertical separator line
+ return IN_SEP_LINE;
}
- mpos->col = vcol2col(wp, mpos->lnum, col);
+ if (wp != curwin || below_buffer) {
+ return IN_UNKNOWN;
+ }
- mpos->coladd = 0;
+ mpos->col = vcol2col(wp, mpos->lnum, col, &mpos->coladd);
return IN_BUFFER;
}
@@ -281,10 +286,8 @@ static int get_fpos_of_mouse(pos_T *mpos)
/// @param fixindent PUT_FIXINDENT if fixing indent necessary
///
/// @return true if start_arrow() should be called for edit mode.
-bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
+bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent)
{
- static bool got_click = false; // got a click some time back
-
int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT
bool is_click; // If false it's a drag or release event
bool is_drag; // If true it's a drag event
@@ -296,20 +299,18 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
bool in_status_line; // mouse in status line
static bool in_tab_line = false; // mouse clicked in tab line
bool in_sep_line; // mouse in vertical separator line
- int c1, c2;
- pos_T save_cursor;
+ int c1;
win_T *old_curwin = curwin;
static pos_T orig_cursor;
colnr_T leftcol, rightcol;
pos_T end_visual;
- long diff;
int old_active = VIsual_active;
int old_mode = VIsual_mode;
int regname;
- save_cursor = curwin->w_cursor;
+ pos_T save_cursor = curwin->w_cursor;
- for (;;) {
+ while (true) {
which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag);
if (is_drag) {
// If the next character is the same mouse event then use that
@@ -367,7 +368,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
stuffnumReadbuff(count);
}
stuffcharReadbuff(Ctrl_T);
- got_click = false; // ignore drag&release now
+ got_click = false; // ignore drag&release now
return false;
}
@@ -449,7 +450,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) {
insert_reg(regname, true);
} else {
- do_put(regname, NULL, BACKWARD, 1L,
+ do_put(regname, NULL, BACKWARD, 1,
(fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND);
// Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r
@@ -538,6 +539,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// shift-left button -> right button
// alt-left button -> alt-right button
if (mouse_model_popup()) {
+ pos_T m_pos;
+ int m_pos_flag = get_fpos_of_mouse(&m_pos);
+ if (m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) {
+ goto popupexit;
+ }
if (which_button == MOUSE_RIGHT
&& !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) {
if (!is_click) {
@@ -547,17 +553,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
}
jump_flags = 0;
if (strcmp(p_mousem, "popup_setpos") == 0) {
- // First set the cursor position before showing the popup
- // menu.
+ // First set the cursor position before showing the popup menu.
if (VIsual_active) {
- pos_T m_pos;
- // set MOUSE_MAY_STOP_VIS if we are outside the
- // selection or the current window (might have false
- // negative here)
- if (mouse_row < curwin->w_winrow
- || mouse_row > (curwin->w_winrow + curwin->w_height)) {
- jump_flags = MOUSE_MAY_STOP_VIS;
- } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) {
+ // set MOUSE_MAY_STOP_VIS if we are outside the selection
+ // or the current window (might have false negative here)
+ if (m_pos_flag != IN_BUFFER) {
jump_flags = MOUSE_MAY_STOP_VIS;
} else {
if (VIsual_mode == 'V') {
@@ -601,6 +601,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
mod_mask &= ~MOD_MASK_SHIFT;
}
}
+popupexit:
if ((State & (MODE_NORMAL | MODE_INSERT))
&& !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) {
@@ -652,7 +653,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
in_sep_line = (jump_flags & IN_SEP_LINE);
if ((in_winbar || in_status_line || in_statuscol) && is_click) {
- // Handle click event on window bar or status lin
+ // Handle click event on window bar, status line or status column
int click_grid = mouse_grid;
int click_row = mouse_row;
int click_col = mouse_col;
@@ -672,6 +673,10 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
click_col = mouse_col;
}
+ if (in_statuscol && wp->w_p_rl) {
+ click_col = wp->w_width_inner - click_col - 1;
+ }
+
if (click_defs != NULL) {
switch (click_defs[click_col].type) {
case kStlClickDisabled:
@@ -680,7 +685,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
call_click_def_func(click_defs, click_col, which_button);
break;
default:
- assert(false && "winbar and statusline only support %@ for clicks");
+ assert(false && "winbar, statusline and statuscolumn only support %@ for clicks");
break;
}
}
@@ -703,9 +708,9 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
&& which_button == MOUSE_LEFT) {
// open or close a fold at this line
if (jump_flags & MOUSE_FOLD_OPEN) {
- openFold(curwin->w_cursor, 1L);
+ openFold(curwin->w_cursor, 1);
} else {
- closeFold(curwin->w_cursor, 1L);
+ closeFold(curwin->w_cursor, 1);
}
// don't move the cursor if still in the same window
if (curwin == old_curwin) {
@@ -726,11 +731,12 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// When dragging the mouse above the window, scroll down.
if (is_drag && mouse_row < 0 && !in_status_line) {
- scroll_redraw(false, 1L);
+ scroll_redraw(false, 1);
mouse_row = 0;
}
if (start_visual.lnum) { // right click in visual mode
+ linenr_T diff;
// When ALT is pressed make Visual mode blockwise.
if (mod_mask & MOD_MASK_ALT) {
VIsual_mode = Ctrl_V;
@@ -800,6 +806,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// Middle mouse click: Put text before cursor.
if (which_button == MOUSE_MIDDLE) {
+ int c2;
if (regname == 0 && eval_has_provider("clipboard")) {
regname = '*';
}
@@ -835,7 +842,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
} else { // location list window
do_cmdline_cmd(".ll");
}
- got_click = false; // ignore drag&release now
+ got_click = false; // ignore drag&release now
} else if ((mod_mask & MOD_MASK_CTRL)
|| (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) {
// Ctrl-Mouse click (or double click in a help window) jumps to the tag
@@ -844,7 +851,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
stuffcharReadbuff(Ctrl_O);
}
stuffcharReadbuff(Ctrl_RSB);
- got_click = false; // ignore drag&release now
+ got_click = false; // ignore drag&release now
} else if ((mod_mask & MOD_MASK_SHIFT)) {
// Shift-Mouse click searches for the next occurrence of the word under
// the mouse pointer
@@ -888,13 +895,13 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// A double click selects a word or a block.
if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) {
pos_T *pos = NULL;
- int gc;
if (is_click) {
// If the character under the cursor (skipping white space) is
// not a word character, try finding a match and select a (),
// {}, [], #if/#endif, etc. block.
end_visual = curwin->w_cursor;
+ int gc;
while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) {
inc(&end_visual);
}
@@ -956,6 +963,146 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
return moved;
}
+void ins_mouse(int c)
+{
+ win_T *old_curwin = curwin;
+
+ undisplay_dollar();
+ pos_T tpos = curwin->w_cursor;
+ if (do_mouse(NULL, c, BACKWARD, 1, 0)) {
+ win_T *new_curwin = curwin;
+
+ if (curwin != old_curwin && win_valid(old_curwin)) {
+ // Mouse took us to another window. We need to go back to the
+ // previous one to stop insert there properly.
+ curwin = old_curwin;
+ curbuf = curwin->w_buffer;
+ if (bt_prompt(curbuf)) {
+ // Restart Insert mode when re-entering the prompt buffer.
+ curbuf->b_prompt_insert = 'A';
+ }
+ }
+ start_arrow(curwin == old_curwin ? &tpos : NULL);
+ if (curwin != new_curwin && win_valid(new_curwin)) {
+ curwin = new_curwin;
+ curbuf = curwin->w_buffer;
+ }
+ set_can_cindent(true);
+ }
+
+ // redraw status lines (in case another window became active)
+ redraw_statuslines();
+}
+
+/// Common mouse wheel scrolling, shared between Insert mode and NV modes.
+/// Default action is to scroll mouse_vert_step lines (or mouse_hor_step columns
+/// depending on the scroll direction) or one page when Shift or Ctrl is used.
+/// Direction is indicated by "cap->arg":
+/// K_MOUSEUP - MSCR_UP
+/// K_MOUSEDOWN - MSCR_DOWN
+/// K_MOUSELEFT - MSCR_LEFT
+/// K_MOUSERIGHT - MSCR_RIGHT
+/// "curwin" may have been changed to the window that should be scrolled and
+/// differ from the window that actually has focus.
+void do_mousescroll(cmdarg_T *cap)
+{
+ bool shift_or_ctrl = mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL);
+
+ if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) {
+ // Vertical scrolling
+ if ((State & MODE_NORMAL) && shift_or_ctrl) {
+ // whole page up or down
+ (void)onepage(cap->arg ? FORWARD : BACKWARD, 1);
+ } else {
+ if (shift_or_ctrl) {
+ // whole page up or down
+ cap->count1 = curwin->w_botline - curwin->w_topline;
+ } else {
+ cap->count1 = (int)p_mousescroll_vert;
+ }
+ if (cap->count1 > 0) {
+ cap->count0 = cap->count1;
+ nv_scroll_line(cap);
+ }
+ }
+ } else {
+ // Horizontal scrolling
+ int step = shift_or_ctrl ? curwin->w_width_inner : (int)p_mousescroll_hor;
+ colnr_T leftcol = curwin->w_leftcol + (cap->arg == MSCR_RIGHT ? -step : +step);
+ if (leftcol < 0) {
+ leftcol = 0;
+ }
+ (void)do_mousescroll_horiz(leftcol);
+ }
+}
+
+/// Implementation for scrolling in Insert mode in direction "dir", which is one
+/// of the MSCR_ values.
+void ins_mousescroll(int dir)
+{
+ cmdarg_T cap;
+ oparg_T oa;
+ CLEAR_FIELD(cap);
+ clear_oparg(&oa);
+ cap.oap = &oa;
+ cap.arg = dir;
+
+ switch (dir) {
+ case MSCR_UP:
+ cap.cmdchar = K_MOUSEUP;
+ break;
+ case MSCR_DOWN:
+ cap.cmdchar = K_MOUSEDOWN;
+ break;
+ case MSCR_LEFT:
+ cap.cmdchar = K_MOUSELEFT;
+ break;
+ case MSCR_RIGHT:
+ cap.cmdchar = K_MOUSERIGHT;
+ break;
+ default:
+ siemsg("Invalid ins_mousescroll() argument: %d", dir);
+ }
+
+ win_T *old_curwin = curwin;
+ if (mouse_row >= 0 && mouse_col >= 0) {
+ // Find the window at the mouse pointer coordinates.
+ // NOTE: Must restore "curwin" to "old_curwin" before returning!
+ int grid = mouse_grid;
+ int row = mouse_row;
+ int col = mouse_col;
+ curwin = mouse_find_win(&grid, &row, &col);
+ if (curwin == NULL) {
+ curwin = old_curwin;
+ return;
+ }
+ curbuf = curwin->w_buffer;
+ }
+
+ if (curwin == old_curwin) {
+ // Don't scroll the current window if the popup menu is visible.
+ if (pum_visible()) {
+ return;
+ }
+
+ undisplay_dollar();
+ }
+
+ pos_T orig_cursor = curwin->w_cursor;
+
+ // Call the common mouse scroll function shared with other modes.
+ do_mousescroll(&cap);
+
+ curwin->w_redr_status = true;
+ curwin = old_curwin;
+ curbuf = curwin->w_buffer;
+
+ if (!equalpos(curwin->w_cursor, orig_cursor)) {
+ start_arrow(&orig_cursor);
+ set_can_cindent(true);
+ }
+}
+
/// Return true if "c" is a mouse key.
bool is_mouse_key(int c)
{
@@ -1036,8 +1183,6 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button)
static int prev_col = -1;
static int did_drag = false; // drag was noticed
- win_T *wp, *old_curwin;
- pos_T old_cursor;
int count;
bool first;
int row = mouse_row;
@@ -1090,34 +1235,28 @@ retnomove:
if (flags & MOUSE_SETPOS) {
goto retnomove; // ugly goto...
}
- old_curwin = curwin;
- old_cursor = curwin->w_cursor;
+ win_T *old_curwin = curwin;
+ pos_T old_cursor = curwin->w_cursor;
if (row < 0 || col < 0) { // check if it makes sense
return IN_UNKNOWN;
}
// find the window where the row is in
- wp = mouse_find_win(&grid, &row, &col);
+ win_T *wp = mouse_find_win(&grid, &row, &col);
if (wp == NULL) {
return IN_UNKNOWN;
}
- on_status_line = (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height)
- ? row + wp->w_winbar_height - wp->w_height + 1 == 1
- : false;
-
- on_winbar = (row == -1)
- ? wp->w_winbar_height != 0
- : false;
-
- on_statuscol = !on_status_line && !on_winbar && col < win_col_off(wp)
- ? *wp->w_p_stc != NUL
- : false;
-
- on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width
- ? col - wp->w_width + 1 == 1
- : false;
+ bool below_window = grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height;
+ on_status_line = below_window && row + wp->w_winbar_height - wp->w_height + 1 == 1;
+ on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width && col - wp->w_width + 1 == 1;
+ on_winbar = row == -1 && wp->w_winbar_height != 0;
+ on_statuscol = !below_window && !on_status_line && !on_sep_line && !on_winbar
+ && *wp->w_p_stc != NUL
+ && (wp->w_p_rl
+ ? col >= wp->w_width_inner - win_col_off(wp)
+ : col < win_col_off(wp));
// The rightmost character of the status line might be a vertical
// separator character if there is no connecting window to the right.
@@ -1150,7 +1289,7 @@ retnomove:
dragwin = NULL;
// winpos and height may change in win_enter()!
- if (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height) {
+ if (below_window) {
// In (or below) status line
status_line_offset = row + wp->w_winbar_height - wp->w_height + 1;
dragwin = wp;
@@ -1306,14 +1445,13 @@ retnomove:
}
first = false;
- if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline)
- && curwin->w_topline == curbuf->b_ml.ml_line_count) {
- break;
- }
-
if (curwin->w_topfill > 0) {
curwin->w_topfill--;
} else {
+ if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline)
+ && curwin->w_topline == curbuf->b_ml.ml_line_count) {
+ break;
+ }
curwin->w_topline++;
curwin->w_topfill = win_get_fill(curwin, curwin->w_topline);
}
@@ -1336,15 +1474,15 @@ retnomove:
}
}
+ colnr_T col_from_screen = -1;
+ int mouse_fold_flags = 0;
+ mouse_check_grid(&col_from_screen, &mouse_fold_flags);
+
// compute the position in the buffer line from the posn on the screen
if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) {
mouse_past_bottom = true;
}
- if (!(flags & MOUSE_RELEASED) && which_button == MOUSE_LEFT) {
- col = mouse_adjust_click(curwin, row, col);
- }
-
// Start Visual mode before coladvance(), for when 'sel' != "old"
if ((flags & MOUSE_MAY_VIS) && !VIsual_active) {
VIsual = old_cursor;
@@ -1359,6 +1497,10 @@ retnomove:
}
}
+ if (col_from_screen >= 0) {
+ col = col_from_screen;
+ }
+
curwin->w_curswant = col;
curwin->w_set_curswant = false; // May still have been true
if (coladvance(col) == FAIL) { // Mouse click beyond end of line
@@ -1376,41 +1518,110 @@ retnomove:
count |= CURSOR_MOVED; // Cursor has moved
}
- count |= mouse_check_fold();
+ count |= mouse_fold_flags;
return count;
}
-// Compute the position in the buffer line from the posn on the screen in
-// window "win".
-// Returns true if the position is below the last line.
+/// Make a horizontal scroll to "leftcol".
+/// @return true if the cursor moved, false otherwise.
+static bool do_mousescroll_horiz(colnr_T leftcol)
+{
+ if (curwin->w_p_wrap) {
+ return false; // no horizontal scrolling when wrapping
+ }
+ if (curwin->w_leftcol == leftcol) {
+ return false; // already there
+ }
+
+ // When the line of the cursor is too short, move the cursor to the
+ // longest visible line.
+ if (!virtual_active()
+ && leftcol > scroll_line_len(curwin->w_cursor.lnum)) {
+ curwin->w_cursor.lnum = find_longest_lnum();
+ curwin->w_cursor.col = 0;
+ }
+
+ return set_leftcol(leftcol);
+}
+
+/// Normal and Visual modes implementation for scrolling in direction
+/// "cap->arg", which is one of the MSCR_ values.
+void nv_mousescroll(cmdarg_T *cap)
+{
+ win_T *const old_curwin = curwin;
+
+ if (mouse_row >= 0 && mouse_col >= 0) {
+ // Find the window at the mouse pointer coordinates.
+ // NOTE: Must restore "curwin" to "old_curwin" before returning!
+ int grid = mouse_grid;
+ int row = mouse_row;
+ int col = mouse_col;
+ curwin = mouse_find_win(&grid, &row, &col);
+ if (curwin == NULL) {
+ curwin = old_curwin;
+ return;
+ }
+ curbuf = curwin->w_buffer;
+ }
+
+ // Call the common mouse scroll function shared with other modes.
+ do_mousescroll(cap);
+
+ if (curwin != old_curwin && curwin->w_p_cul) {
+ redraw_for_cursorline(curwin);
+ }
+ curwin->w_redr_status = true;
+ curwin = old_curwin;
+ curbuf = curwin->w_buffer;
+}
+
+/// Mouse clicks and drags.
+void nv_mouse(cmdarg_T *cap)
+{
+ (void)do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0);
+}
+
+/// Compute the position in the buffer line from the posn on the screen in
+/// window "win".
+/// Returns true if the position is below the last line.
bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
{
int col = *colp;
int row = *rowp;
- linenr_T lnum;
bool retval = false;
- int off;
int count;
if (win->w_p_rl) {
col = win->w_width_inner - 1 - col;
}
- lnum = win->w_topline;
+ linenr_T lnum = win->w_topline;
while (row > 0) {
// Don't include filler lines in "count"
- if (win_may_fill(win)
- && !hasFoldingWin(win, lnum, NULL, NULL, true, NULL)) {
+ if (win_may_fill(win)) {
if (lnum == win->w_topline) {
row -= win->w_topfill;
} else {
row -= win_get_fill(win, lnum);
}
- count = plines_win_nofill(win, lnum, true);
+ count = plines_win_nofill(win, lnum, false);
} else {
- count = plines_win(win, lnum, true);
+ count = plines_win(win, lnum, false);
+ }
+
+ if (win->w_skipcol > 0 && lnum == win->w_topline) {
+ // Adjust for 'smoothscroll' clipping the top screen lines.
+ // A similar formula is used in curs_columns().
+ int width1 = win->w_width_inner - win_col_off(win);
+ int skip_lines = 0;
+ if (win->w_skipcol > width1) {
+ skip_lines = (win->w_skipcol - width1) / (width1 + win_col_off2(win)) + 1;
+ } else if (win->w_skipcol > 0) {
+ skip_lines = 1;
+ }
+ count -= skip_lines;
}
if (count > row) {
@@ -1429,13 +1640,16 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
if (!retval) {
// Compute the column without wrapping.
- off = win_col_off(win) - win_col_off2(win);
+ int off = win_col_off(win) - win_col_off2(win);
if (col < off) {
col = off;
}
col += row * (win->w_width_inner - off);
- // add skip column (for long wrapping line)
- col += win->w_skipcol;
+
+ // Add skip column for the topline.
+ if (lnum == win->w_topline) {
+ col += win->w_skipcol;
+ }
}
if (!win->w_p_wrap) {
@@ -1467,11 +1681,9 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
return NULL;
}
- frame_T *fp;
-
- fp = topframe;
+ frame_T *fp = topframe;
*rowp -= firstwin->w_winrow;
- for (;;) {
+ while (true) {
if (fp->fr_layout == FR_LEAF) {
break;
}
@@ -1535,20 +1747,27 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
}
/// Convert a virtual (screen) column to a character column.
-/// The first column is one.
-colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+/// The first column is zero.
+colnr_T vcol2col(win_T *wp, linenr_T lnum, colnr_T vcol, colnr_T *coladdp)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
{
// try to advance to the specified column
- char *line = ml_get_buf(wp->w_buffer, lnum, false);
+ char *line = ml_get_buf(wp->w_buffer, lnum);
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) {
- cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
+ int size = win_lbr_chartabsize(&cts, NULL);
+ if (cts.cts_vcol + size > vcol) {
+ break;
+ }
+ cts.cts_vcol += size;
MB_PTR_ADV(cts.cts_ptr);
}
clear_chartabsize_arg(&cts);
+ if (coladdp != NULL) {
+ *coladdp = vcol - cts.cts_vcol;
+ }
return (colnr_T)(cts.cts_ptr - line);
}
@@ -1569,15 +1788,13 @@ static void set_mouse_topline(win_T *wp)
orig_topfill = wp->w_topfill;
}
-///
/// Return length of line "lnum" for horizontal scrolling.
-///
static colnr_T scroll_line_len(linenr_T lnum)
{
colnr_T col = 0;
char *line = ml_get(lnum);
if (*line != NUL) {
- for (;;) {
+ while (true) {
int numchar = win_chartabsize(curwin, line, col);
MB_PTR_ADV(line);
if (*line == NUL) { // don't count the last character
@@ -1589,9 +1806,7 @@ static colnr_T scroll_line_len(linenr_T lnum)
return col;
}
-///
/// Find longest visible line number.
-///
static linenr_T find_longest_lnum(void)
{
linenr_T ret = 0;
@@ -1602,17 +1817,17 @@ static linenr_T find_longest_lnum(void)
if (curwin->w_topline <= curwin->w_cursor.lnum
&& curwin->w_botline > curwin->w_cursor.lnum
&& curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) {
- long max = 0;
+ colnr_T max = 0;
// Use maximum of all visible lines. Remember the lnum of the
// longest line, closest to the cursor line. Used when scrolling
// below.
for (linenr_T lnum = curwin->w_topline; lnum < curwin->w_botline; lnum++) {
colnr_T len = scroll_line_len(lnum);
- if (len > (colnr_T)max) {
+ if (len > max) {
max = len;
ret = lnum;
- } else if (len == (colnr_T)max
+ } else if (len == max
&& abs(lnum - curwin->w_cursor.lnum)
< abs(ret - curwin->w_cursor.lnum)) {
ret = lnum;
@@ -1626,194 +1841,116 @@ static linenr_T find_longest_lnum(void)
return ret;
}
-/// Do a horizontal scroll.
-/// @return true if the cursor moved, false otherwise.
-bool mouse_scroll_horiz(int dir)
-{
- if (curwin->w_p_wrap) {
- return false;
- }
-
- int step = (int)p_mousescroll_hor;
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- step = curwin->w_width_inner;
- }
-
- int leftcol = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : +step);
- if (leftcol < 0) {
- leftcol = 0;
- }
-
- if (curwin->w_leftcol == leftcol) {
- return false;
- }
-
- curwin->w_leftcol = (colnr_T)leftcol;
-
- // When the line of the cursor is too short, move the cursor to the
- // longest visible line.
- if (!virtual_active()
- && (colnr_T)leftcol > scroll_line_len(curwin->w_cursor.lnum)) {
- curwin->w_cursor.lnum = find_longest_lnum();
- curwin->w_cursor.col = 0;
- }
-
- return leftcol_changed();
-}
-
-/// Adjusts the clicked column position when 'conceallevel' > 0
-static int mouse_adjust_click(win_T *wp, int row, int col)
+/// Check clicked cell on its grid
+static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
+ FUNC_ATTR_NONNULL_ALL
{
- if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0
- && wp->w_leftcol < curbuf->b_p_smc && conceal_cursor_line(wp))) {
- return col;
- }
-
- // `col` is the position within the current line that is highlighted by the
- // cursor without consideration for concealed characters. The current line is
- // scanned *up to* `col`, nudging it left or right when concealed characters
- // are encountered.
- //
- // win_chartabsize() is used to keep track of the virtual column position
- // relative to the line's bytes. For example: if col == 9 and the line
- // starts with a tab that's 8 columns wide, we would want the cursor to be
- // highlighting the second byte, not the ninth.
-
- linenr_T lnum = wp->w_cursor.lnum;
- char *line = ml_get(lnum);
- char *ptr = line;
- char *ptr_end;
- char *ptr_row_offset = line; // Where we begin adjusting `ptr_end`
-
- // Find the offset where scanning should begin.
- int offset = wp->w_leftcol;
- if (row > 0) {
- offset += row * (wp->w_width_inner - win_col_off(wp) - win_col_off2(wp) -
- wp->w_leftcol + wp->w_skipcol);
- }
-
- int vcol;
-
- if (offset) {
- // Skip everything up to an offset since nvim takes care of displaying the
- // correct portion of the line when horizontally scrolling.
- // When 'wrap' is enabled, only the row (of the wrapped line) needs to be
- // checked for concealed characters.
- vcol = 0;
- while (vcol < offset && *ptr != NUL) {
- vcol += win_chartabsize(curwin, ptr, vcol);
- ptr += utfc_ptr2len(ptr);
- }
-
- ptr_row_offset = ptr;
- }
-
- // Align `ptr_end` with `col`
- vcol = offset;
- ptr_end = ptr_row_offset;
- while (vcol < col && *ptr_end != NUL) {
- vcol += win_chartabsize(curwin, ptr_end, vcol);
- ptr_end += utfc_ptr2len(ptr_end);
- }
-
- int matchid;
- int prev_matchid;
- int nudge = 0;
- int cwidth = 0;
-
- vcol = offset;
-
-#define INCR() nudge++; ptr_end += utfc_ptr2len((char *)ptr_end)
-#define DECR() nudge--; ptr_end -= utfc_ptr2len((char *)ptr_end)
-
- while (ptr < ptr_end && *ptr != NUL) {
- cwidth = win_chartabsize(curwin, ptr, vcol);
- vcol += cwidth;
- if (cwidth > 1 && *ptr == '\t' && nudge > 0) {
- // A tab will "absorb" any previous adjustments.
- cwidth = MIN(cwidth, nudge);
- while (cwidth > 0) {
- DECR();
- cwidth--;
- }
- }
-
- matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
- if (matchid != 0) {
- if (wp->w_p_cole == 3) {
- INCR();
- } else {
- if (!(row > 0 && ptr == ptr_row_offset)
- && (wp->w_p_cole == 1 || (wp->w_p_cole == 2
- && (wp->w_p_lcs_chars.conceal != NUL
- || syn_get_sub_char() != NUL)))) {
- // At least one placeholder character will be displayed.
- DECR();
- }
-
- prev_matchid = matchid;
+ int click_grid = mouse_grid;
+ int click_row = mouse_row;
+ int click_col = mouse_col;
- while (prev_matchid == matchid && *ptr != NUL) {
- INCR();
- ptr += utfc_ptr2len(ptr);
- matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
+ // XXX: this doesn't change click_grid if it is 1, even with multigrid
+ win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col);
+ // Only use vcols[] after the window was redrawn. Mainly matters
+ // for tests, a user would not click before redrawing.
+ if (wp == NULL || wp->w_redr_type != 0) {
+ return;
+ }
+ ScreenGrid *gp = &wp->w_grid;
+ int start_row = 0;
+ int start_col = 0;
+ grid_adjust(&gp, &start_row, &start_col);
+ if (gp->handle != click_grid || gp->chars == NULL) {
+ return;
+ }
+ click_row += start_row;
+ click_col += start_col;
+ if (click_row < 0 || click_row >= gp->rows
+ || click_col < 0 || click_col >= gp->cols) {
+ return;
+ }
+
+ const size_t off = gp->line_offset[click_row] + (size_t)click_col;
+ colnr_T col_from_screen = gp->vcols[off];
+
+ if (col_from_screen == MAXCOL) {
+ // When clicking after end of line, still need to set correct curswant
+ size_t off_l = gp->line_offset[click_row] + (size_t)start_col;
+ if (gp->vcols[off_l] < MAXCOL) {
+ // Binary search to find last char in line
+ size_t off_r = off;
+ while (off_l < off_r) {
+ size_t off_m = (off_l + off_r + 1) / 2;
+ if (gp->vcols[off_m] < MAXCOL) {
+ off_l = off_m;
+ } else {
+ off_r = off_m - 1;
}
-
- continue;
}
+ colnr_T eol_vcol = gp->vcols[off_r];
+ assert(eol_vcol < MAXCOL);
+ if (eol_vcol < 0) {
+ // Empty line or whole line before w_leftcol,
+ // with columns before buffer text
+ eol_vcol = wp->w_leftcol - 1;
+ }
+ *vcolp = eol_vcol + (int)(off - off_r);
+ } else {
+ // Empty line or whole line before w_leftcol
+ *vcolp = click_col - start_col + wp->w_leftcol;
}
-
- ptr += utfc_ptr2len(ptr);
+ } else if (col_from_screen >= 0) {
+ // Use the virtual column from vcols[], it is accurate also after
+ // concealed characters.
+ *vcolp = col_from_screen;
}
- return col + nudge;
+ if (col_from_screen == -2) {
+ *flagsp |= MOUSE_FOLD_OPEN;
+ } else if (col_from_screen == -3) {
+ *flagsp |= MOUSE_FOLD_CLOSE;
+ }
}
-// Check clicked cell is foldcolumn
-int mouse_check_fold(void)
+/// "getmousepos()" function
+void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int click_grid = mouse_grid;
- int click_row = mouse_row;
- int click_col = mouse_col;
- int mouse_char = ' ';
- int max_row = Rows;
- int max_col = Columns;
- int multigrid = ui_has(kUIMultigrid);
-
- win_T *wp;
-
- wp = mouse_find_win(&click_grid, &click_row, &click_col);
- if (wp && multigrid) {
- max_row = wp->w_grid_alloc.rows;
- max_col = wp->w_grid_alloc.cols;
- }
+ int row = mouse_row;
+ int col = mouse_col;
+ int grid = mouse_grid;
+ varnumber_T winid = 0;
+ varnumber_T winrow = 0;
+ varnumber_T wincol = 0;
+ linenr_T lnum = 0;
+ varnumber_T column = 0;
+ colnr_T coladd = 0;
- if (wp && mouse_row >= 0 && mouse_row < max_row
- && mouse_col >= 0 && mouse_col < max_col) {
- ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid;
- int fdc = win_fdccol_count(wp);
- int row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
- int col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
+ tv_dict_alloc_ret(rettv);
+ dict_T *d = rettv->vval.v_dict;
- // Remember the character under the mouse, might be one of foldclose or
- // foldopen fillchars in the fold column.
- if (gp->chars != NULL) {
- mouse_char = utf_ptr2char((char *)gp->chars[gp->line_offset[row]
- + (unsigned)col]);
- }
+ tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1);
+ tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1);
- // Check for position outside of the fold column.
- if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc :
- click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
- mouse_char = ' ';
+ win_T *wp = mouse_find_win(&grid, &row, &col);
+ if (wp != NULL) {
+ int height = wp->w_height + wp->w_hsep_height + wp->w_status_height;
+ // The height is adjusted by 1 when there is a bottom border. This is not
+ // necessary for a top border since `row` starts at -1 in that case.
+ if (row < height + wp->w_border_adj[2]) {
+ winid = wp->handle;
+ winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border
+ wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border
+ if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) {
+ (void)mouse_comp_pos(wp, &row, &col, &lnum);
+ col = vcol2col(wp, lnum, col, &coladd);
+ column = col + 1;
+ }
}
}
-
- if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) {
- return MOUSE_FOLD_OPEN;
- } else if (mouse_char != ' ') {
- return MOUSE_FOLD_CLOSE;
- }
-
- return 0;
+ tv_dict_add_nr(d, S_LEN("winid"), winid);
+ tv_dict_add_nr(d, S_LEN("winrow"), winrow);
+ tv_dict_add_nr(d, S_LEN("wincol"), wincol);
+ tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum);
+ tv_dict_add_nr(d, S_LEN("column"), column);
+ tv_dict_add_nr(d, S_LEN("coladd"), coladd);
}
diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h
index 8b2d7e0acd..928b3e360b 100644
--- a/src/nvim/mouse.h
+++ b/src/nvim/mouse.h
@@ -1,12 +1,10 @@
-#ifndef NVIM_MOUSE_H
-#define NVIM_MOUSE_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/buffer_defs.h"
-#include "nvim/normal.h"
-#include "nvim/vim.h"
-#include "nvim/window.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
/// jump_to_mouse() returns one of first five these values, possibly with
/// some of the other five added.
@@ -56,5 +54,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mouse.h.generated.h"
#endif
-
-#endif // NVIM_MOUSE_H
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 3af26b910e..9ed3978490 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// move.c: Functions for moving the cursor and scrolling text.
//
// There are two ways to move the cursor:
@@ -14,39 +11,42 @@
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
-#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
+#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/sign_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
typedef struct {
linenr_T lnum; // line number
@@ -58,6 +58,37 @@ typedef struct {
# include "move.c.generated.h"
#endif
+/// Get the number of screen lines skipped with "wp->w_skipcol".
+int adjust_plines_for_skipcol(win_T *wp)
+{
+ if (wp->w_skipcol == 0) {
+ return 0;
+ }
+
+ int width = wp->w_width_inner - win_col_off(wp);
+ int w2 = width + win_col_off2(wp);
+ if (wp->w_skipcol >= width && w2 > 0) {
+ return (wp->w_skipcol - width) / w2 + 1;
+ }
+
+ return 0;
+}
+
+/// Return how many lines "lnum" will take on the screen, taking into account
+/// whether it is the first line, whether w_skipcol is non-zero and limiting to
+/// the window height.
+static int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool *foldedp)
+{
+ int n = plines_win_full(wp, lnum, nextp, foldedp, true, false);
+ if (lnum == wp->w_topline) {
+ n -= adjust_plines_for_skipcol(wp);
+ }
+ if (n > wp->w_height_inner) {
+ return wp->w_height_inner;
+ }
+ return n;
+}
+
// Compute wp->w_botline for the current wp->w_topline. Can be called after
// wp->w_topline changed.
static void comp_botline(win_T *wp)
@@ -79,7 +110,7 @@ static void comp_botline(win_T *wp)
for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) {
linenr_T last = lnum;
bool folded;
- int n = plines_win_full(wp, lnum, &last, &folded, true);
+ int n = plines_correct_topline(wp, lnum, &last, &folded);
if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) {
wp->w_cline_row = done;
wp->w_cline_height = n;
@@ -104,18 +135,6 @@ static void comp_botline(win_T *wp)
win_check_anchored_floats(wp);
}
-/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
-/// Also when concealing is on and 'concealcursor' is not active.
-void redraw_for_cursorline(win_T *wp)
- FUNC_ATTR_NONNULL_ALL
-{
- if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
- && (wp->w_p_rnu || win_cursorline_standout(wp))) {
- // win_line() will redraw the number column and cursorline only.
- redraw_later(wp, UPD_VALID);
- }
-}
-
/// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt'
/// contains "screenline" or when the "CurSearch" highlight is in use.
/// Also when concealing is on and 'concealcursor' is active.
@@ -123,7 +142,8 @@ static void redraw_for_cursorcolumn(win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) {
- if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC)) && using_hlsearch())) {
+ if (wp->w_p_cuc
+ || (win_hl_attr(wp, HLF_LC) != win_hl_attr(wp, HLF_L) && using_hlsearch())) {
// When 'cursorcolumn' is set or "CurSearch" is in use
// need to redraw with UPD_SOME_VALID.
redraw_later(wp, UPD_SOME_VALID);
@@ -140,15 +160,63 @@ static void redraw_for_cursorcolumn(win_T *wp)
}
}
+/// Calculates how much the 'listchars' "precedes" or 'smoothscroll' "<<<"
+/// marker overlaps with buffer text for window "wp".
+/// Parameter "extra2" should be the padding on the 2nd line, not the first
+/// line.
+/// Returns the number of columns of overlap with buffer text, excluding the
+/// extra padding on the ledge.
+int sms_marker_overlap(win_T *wp, int extra2)
+{
+ // There is no marker overlap when in showbreak mode, thus no need to
+ // account for it. See grid_put_linebuf().
+ if (*get_showbreak_value(wp) != NUL) {
+ return 0;
+ }
+
+ // Overlap when 'list' and 'listchars' "precedes" are set is 1.
+ if (wp->w_p_list && wp->w_p_lcs_chars.prec) {
+ return 1;
+ }
+
+ return extra2 > 3 ? 0 : 3 - extra2;
+}
+
+/// Calculates the skipcol offset for window "wp" given how many
+/// physical lines we want to scroll down.
+static int skipcol_from_plines(win_T *wp, int plines_off)
+{
+ int width1 = wp->w_width_inner - win_col_off(wp);
+
+ int skipcol = 0;
+ if (plines_off > 0) {
+ skipcol += width1;
+ }
+ if (plines_off > 1) {
+ skipcol += (width1 + win_col_off2(wp)) * (plines_off - 1);
+ }
+ return skipcol;
+}
+
+/// Set wp->w_skipcol to zero and redraw later if needed.
+static void reset_skipcol(win_T *wp)
+{
+ if (wp->w_skipcol != 0) {
+ wp->w_skipcol = 0;
+
+ // Should use the least expensive way that displays all that changed.
+ // UPD_NOT_VALID is too expensive, UPD_REDRAW_TOP does not redraw
+ // enough when the top line gets another screen line.
+ redraw_later(wp, UPD_SOME_VALID);
+ }
+}
+
// Update curwin->w_topline to move the cursor onto the screen.
void update_topline(win_T *wp)
{
- linenr_T old_topline;
- int old_topfill;
- bool check_topline = false;
bool check_botline = false;
- long *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so;
- long save_so = *so_ptr;
+ OptInt *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so;
+ OptInt save_so = *so_ptr;
// Cursor is updated instead when this is true for 'splitkeep'.
if (skip_update_topline) {
@@ -175,11 +243,11 @@ void update_topline(win_T *wp)
*so_ptr = mouse_dragging - 1;
}
- old_topline = wp->w_topline;
- old_topfill = wp->w_topfill;
+ linenr_T old_topline = wp->w_topline;
+ int old_topfill = wp->w_topfill;
// If the buffer is empty, always set topline to 1.
- if (buf_is_empty(curbuf)) { // special case - file is empty
+ if (buf_is_empty(wp->w_buffer)) { // special case - file is empty
if (wp->w_topline != 1) {
redraw_later(wp, UPD_NOT_VALID);
}
@@ -189,9 +257,10 @@ void update_topline(win_T *wp)
wp->w_viewport_invalid = true;
wp->w_scbind_pos = 1;
} else {
+ bool check_topline = false;
// If the cursor is above or near the top of the window, scroll the window
// to show the line the cursor is in, with 'scrolloff' context.
- if (wp->w_topline > 1) {
+ if (wp->w_topline > 1 || wp->w_skipcol > 0) {
// If the cursor is above topline, scrolling is always needed.
// If the cursor is far below topline and there is no folding,
// scrolling down is never needed.
@@ -199,6 +268,16 @@ void update_topline(win_T *wp)
check_topline = true;
} else if (check_top_offset()) {
check_topline = true;
+ } else if (wp->w_skipcol > 0 && wp->w_cursor.lnum == wp->w_topline) {
+ colnr_T vcol;
+
+ // Check that the cursor position is visible. Add columns for
+ // the marker displayed in the top-left if needed.
+ getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL);
+ int overlap = sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp));
+ if (wp->w_skipcol + overlap > vcol) {
+ check_topline = true;
+ }
}
}
// Check if there are more filler lines than allowed.
@@ -211,7 +290,7 @@ void update_topline(win_T *wp)
if (halfheight < 2) {
halfheight = 2;
}
- long n;
+ int64_t n;
if (hasAnyFolding(wp)) {
// Count the number of logical lines between the cursor and
// topline + p_so (approximation of how much will be
@@ -235,7 +314,7 @@ void update_topline(win_T *wp)
// cursor in the middle of the window. Otherwise put the cursor
// near the top of the window.
if (n >= halfheight) {
- scroll_cursor_halfway(false);
+ scroll_cursor_halfway(false, false);
} else {
scroll_cursor_top(scrolljump_value(), false);
check_botline = true;
@@ -261,9 +340,7 @@ void update_topline(win_T *wp)
assert(wp->w_buffer != 0);
if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
if (wp->w_cursor.lnum < wp->w_botline) {
- if (((long)wp->w_cursor.lnum
- >= (long)wp->w_botline - *so_ptr
- || hasAnyFolding(wp))) {
+ if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || hasAnyFolding(wp))) {
lineoff_T loff;
// Cursor is (a few lines) above botline, check if there are
@@ -294,7 +371,7 @@ void update_topline(win_T *wp)
}
}
if (check_botline) {
- long line_count = 0;
+ int line_count = 0;
if (hasAnyFolding(wp)) {
// Count the number of logical lines between the cursor and
// botline - p_so (approximation of how much will be
@@ -309,12 +386,12 @@ void update_topline(win_T *wp)
(void)hasFolding(lnum, &lnum, NULL);
}
} else {
- line_count = wp->w_cursor.lnum - wp->w_botline + 1 + *so_ptr;
+ line_count = wp->w_cursor.lnum - wp->w_botline + 1 + (int)(*so_ptr);
}
if (line_count <= wp->w_height_inner + 1) {
scroll_cursor_bot(scrolljump_value(), false);
} else {
- scroll_cursor_halfway(false);
+ scroll_cursor_halfway(false, false);
}
}
}
@@ -327,12 +404,15 @@ void update_topline(win_T *wp)
if (wp->w_topline != old_topline
|| wp->w_topfill != old_topfill) {
dollar_vcol = -1;
- if (wp->w_skipcol != 0) {
- wp->w_skipcol = 0;
- redraw_later(wp, UPD_NOT_VALID);
- } else {
- redraw_later(wp, UPD_VALID);
+ redraw_later(wp, UPD_VALID);
+
+ // When 'smoothscroll' is not set, should reset w_skipcol.
+ if (!wp->w_p_sms) {
+ reset_skipcol(wp);
+ } else if (wp->w_skipcol != 0) {
+ redraw_later(wp, UPD_SOME_VALID);
}
+
// May need to set w_skipcol when cursor in w_topline.
if (wp->w_cursor.lnum == wp->w_topline) {
validate_cursor();
@@ -347,16 +427,15 @@ void update_topline(win_T *wp)
// When 'scrolljump' is negative use it as a percentage of the window height.
static int scrolljump_value(void)
{
- long result = p_sj >= 0 ? p_sj : (curwin->w_height_inner * -p_sj) / 100;
- assert(result <= INT_MAX);
- return (int)result;
+ int result = p_sj >= 0 ? (int)p_sj : (curwin->w_height_inner * (int)(-p_sj)) / 100;
+ return result;
}
// Return true when there are not 'scrolloff' lines above the cursor for the
// current window.
static bool check_top_offset(void)
{
- long so = get_scrolloff_value(curwin);
+ int so = get_scrolloff_value(curwin);
if (curwin->w_cursor.lnum < curwin->w_topline + so
|| hasAnyFolding(curwin)) {
lineoff_T loff;
@@ -405,7 +484,15 @@ void check_cursor_moved(win_T *wp)
|VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
wp->w_valid_cursor = wp->w_cursor;
wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_valid_skipcol = wp->w_skipcol;
wp->w_viewport_invalid = true;
+ } else if (wp->w_skipcol != wp->w_valid_skipcol) {
+ wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
+ |VALID_CHEIGHT|VALID_CROW
+ |VALID_BOTLINE|VALID_BOTLINE_AP);
+ wp->w_valid_cursor = wp->w_cursor;
+ wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_valid_skipcol = wp->w_skipcol;
} else if (wp->w_cursor.col != wp->w_valid_cursor.col
|| wp->w_leftcol != wp->w_valid_leftcol
|| wp->w_cursor.coladd !=
@@ -454,18 +541,14 @@ void set_topline(win_T *wp, linenr_T lnum)
redraw_later(wp, UPD_VALID);
}
-// Call this function when the length of the cursor line (in screen
-// characters) has changed, and the change is before the cursor.
-// Need to take care of w_botline separately!
-void changed_cline_bef_curs(void)
+/// Call this function when the length of the cursor line (in screen
+/// characters) has changed, and the change is before the cursor.
+/// If the line length changed the number of screen lines might change,
+/// requiring updating w_topline. That may also invalidate w_crow.
+/// Need to take care of w_botline separately!
+void changed_cline_bef_curs(win_T *wp)
{
- curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
- |VALID_CHEIGHT|VALID_TOPLINE);
-}
-
-void changed_cline_bef_curs_win(win_T *wp)
-{
- wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
+ wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW
|VALID_CHEIGHT|VALID_TOPLINE);
}
@@ -484,6 +567,19 @@ void changed_line_abv_curs_win(win_T *wp)
|VALID_CHEIGHT|VALID_TOPLINE);
}
+/// Display of line has changed for "buf", invalidate cursor position and
+/// w_botline.
+void changed_line_display_buf(buf_T *buf)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf) {
+ wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
+ |VALID_CROW|VALID_CHEIGHT
+ |VALID_TOPLINE|VALID_BOTLINE|VALID_BOTLINE_AP);
+ }
+ }
+}
+
// Make sure the value of curwin->w_botline is valid.
void validate_botline(win_T *wp)
{
@@ -492,13 +588,8 @@ void validate_botline(win_T *wp)
}
}
-// Mark curwin->w_botline as invalid (because of some change in the buffer).
-void invalidate_botline(void)
-{
- curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
-}
-
-void invalidate_botline_win(win_T *wp)
+// Mark wp->w_botline as invalid (because of some change in the buffer).
+void invalidate_botline(win_T *wp)
{
wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
}
@@ -512,14 +603,14 @@ void approximate_botline_win(win_T *wp)
int cursor_valid(void)
{
check_cursor_moved(curwin);
- return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) ==
- (VALID_WROW|VALID_WCOL);
+ return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) == (VALID_WROW|VALID_WCOL);
}
// Validate cursor position. Makes sure w_wrow and w_wcol are valid.
// w_topline must be valid, you may need to call update_topline() first!
void validate_cursor(void)
{
+ check_cursor();
check_cursor_moved(curwin);
if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) {
curs_columns(curwin, true);
@@ -555,7 +646,7 @@ static void curs_rows(win_T *wp)
i--; // hold at inserted lines
}
}
- if (valid && (lnum != wp->w_topline || !win_may_fill(wp))) {
+ if (valid && (lnum != wp->w_topline || (wp->w_skipcol == 0 && !win_may_fill(wp)))) {
lnum = wp->w_lines[i].wl_lastlnum + 1;
// Cursor inside folded lines, don't count this row
if (lnum > wp->w_cursor.lnum) {
@@ -565,7 +656,7 @@ static void curs_rows(win_T *wp)
} else {
linenr_T last = lnum;
bool folded;
- int n = plines_win_full(wp, lnum, &last, &folded, false);
+ int n = plines_correct_topline(wp, lnum, &last, &folded);
lnum = last + 1;
if (folded && lnum > wp->w_cursor.lnum) {
break;
@@ -582,7 +673,7 @@ static void curs_rows(win_T *wp)
&& (!wp->w_lines[i].wl_valid
|| wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) {
wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL,
- &wp->w_cline_folded, true);
+ &wp->w_cline_folded, true, true);
} else if (i > wp->w_lines_valid) {
// a line that is too long to fit on the last screen line
wp->w_cline_height = 0;
@@ -629,7 +720,7 @@ void validate_cheight(void)
curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum,
NULL, &curwin->w_cline_folded,
- true);
+ true, true);
curwin->w_valid |= VALID_CHEIGHT;
}
@@ -667,11 +758,10 @@ void validate_cursor_col(void)
// fold column and sign column (these don't move when scrolling horizontally).
int win_col_off(win_T *wp)
{
- return ((wp->w_p_nu || wp->w_p_rnu || (*wp->w_p_stc != NUL)) ?
- (number_width(wp) + (*wp->w_p_stc == NUL)) : 0)
- + (cmdwin_type == 0 || wp != curwin ? 0 : 1)
- + win_fdccol_count(wp)
- + (win_signcol_count(wp) * win_signcol_width(wp));
+ return ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL)
+ ? (number_width(wp) + (*wp->w_p_stc == NUL)) : 0)
+ + ((cmdwin_type == 0 || wp != curwin) ? 0 : 1)
+ + win_fdccol_count(wp) + (win_signcol_count(wp) * SIGN_WIDTH);
}
int curwin_col_off(void)
@@ -680,12 +770,13 @@ int curwin_col_off(void)
}
// Return the difference in column offset for the second screen line of a
-// wrapped line. It's 8 if 'number' or 'relativenumber' is on and 'n' is in
-// 'cpoptions'.
+// wrapped line. It's positive if 'number' or 'relativenumber' is on and 'n'
+// is in 'cpoptions'.
int win_col_off2(win_T *wp)
{
- if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) {
- return number_width(wp) + 1;
+ if ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL)
+ && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) {
+ return number_width(wp) + (*wp->w_p_stc == NUL);
}
return 0;
}
@@ -695,19 +786,14 @@ int curwin_col_off2(void)
return win_col_off2(curwin);
}
-// Compute curwin->w_wcol and curwin->w_virtcol.
-// Also updates curwin->w_wrow and curwin->w_cline_row.
-// Also updates curwin->w_leftcol.
+// Compute wp->w_wcol and wp->w_virtcol.
+// Also updates wp->w_wrow and wp->w_cline_row.
+// Also updates wp->w_leftcol.
// @param may_scroll when true, may scroll horizontally
void curs_columns(win_T *wp, int may_scroll)
{
- int n;
- int width = 0;
colnr_T startcol;
colnr_T endcol;
- colnr_T prev_skipcol;
- long so = get_scrolloff_value(wp);
- long siso = get_sidescrolloff_value(wp);
// First make sure that w_topline is valid (after moving the cursor).
update_topline(wp);
@@ -737,8 +823,11 @@ void curs_columns(win_T *wp, int may_scroll)
// Now compute w_wrow, counting screen lines from w_cline_row.
wp->w_wrow = wp->w_cline_row;
- int textwidth = wp->w_width_inner - extra;
- if (textwidth <= 0) {
+ int n;
+ int width1 = wp->w_width_inner - extra; // text width for first screen line
+ int width2 = 0; // text width for second and later screen line
+ bool did_sub_skipcol = false;
+ if (width1 <= 0) {
// No room for text, put cursor in last char of window.
// If not wrapping, the last non-empty line.
wp->w_wcol = wp->w_width_inner - 1;
@@ -748,23 +837,29 @@ void curs_columns(win_T *wp, int may_scroll)
wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows;
}
} else if (wp->w_p_wrap && wp->w_width_inner != 0) {
- width = textwidth + win_col_off2(wp);
+ width2 = width1 + win_col_off2(wp);
+
+ // skip columns that are not visible
+ if (wp->w_cursor.lnum == wp->w_topline
+ && wp->w_skipcol > 0
+ && wp->w_wcol >= wp->w_skipcol) {
+ // Deduct by multiples of width2. This allows the long line wrapping
+ // formula below to correctly calculate the w_wcol value when wrapping.
+ if (wp->w_skipcol <= width1) {
+ wp->w_wcol -= width2;
+ } else {
+ wp->w_wcol -= width2 * (((wp->w_skipcol - width1) / width2) + 1);
+ }
+
+ did_sub_skipcol = true;
+ }
// long line wrapping, adjust wp->w_wrow
if (wp->w_wcol >= wp->w_width_inner) {
// this same formula is used in validate_cursor_col()
- n = (wp->w_wcol - wp->w_width_inner) / width + 1;
- wp->w_wcol -= n * width;
+ n = (wp->w_wcol - wp->w_width_inner) / width2 + 1;
+ wp->w_wcol -= n * width2;
wp->w_wrow += n;
-
- // When cursor wraps to first char of next line in Insert
- // mode, the 'showbreak' string isn't shown, backup to first
- // column
- char *const sbr = get_showbreak_value(wp);
- if (*sbr && *get_cursor_pos_ptr() == NUL
- && wp->w_wcol == vim_strsize(sbr)) {
- wp->w_wcol = 0;
- }
}
} else if (may_scroll
&& !wp->w_cline_folded) {
@@ -776,18 +871,17 @@ void curs_columns(win_T *wp, int may_scroll)
// If Cursor is right of the screen, scroll leftwards
// If we get closer to the edge than 'sidescrolloff', scroll a little
// extra
- assert(siso <= INT_MAX);
- int off_left = startcol - wp->w_leftcol - (int)siso;
- int off_right =
- endcol - wp->w_leftcol - wp->w_width_inner + (int)siso + 1;
+ int siso = get_sidescrolloff_value(wp);
+ int off_left = startcol - wp->w_leftcol - siso;
+ int off_right = endcol - wp->w_leftcol - wp->w_width_inner + siso + 1;
if (off_left < 0 || off_right > 0) {
- int diff = (off_left < 0) ? -off_left: off_right;
+ int diff = (off_left < 0) ? -off_left : off_right;
// When far off or not enough room on either side, put cursor in
// middle of window.
int new_leftcol;
- if (p_ss == 0 || diff >= textwidth / 2 || off_right >= off_left) {
- new_leftcol = wp->w_wcol - extra - textwidth / 2;
+ if (p_ss == 0 || diff >= width1 / 2 || off_right >= off_left) {
+ new_leftcol = curwin->w_wcol - extra - width1 / 2;
} else {
if (diff < p_ss) {
assert(p_ss <= INT_MAX);
@@ -824,9 +918,9 @@ void curs_columns(win_T *wp, int may_scroll)
wp->w_wrow += win_get_fill(wp, wp->w_cursor.lnum);
}
- prev_skipcol = wp->w_skipcol;
-
int plines = 0;
+ int so = get_scrolloff_value(wp);
+ colnr_T prev_skipcol = wp->w_skipcol;
if ((wp->w_wrow >= wp->w_height_inner
|| ((prev_skipcol > 0
|| wp->w_wrow + so >= wp->w_height_inner)
@@ -834,7 +928,7 @@ void curs_columns(win_T *wp, int may_scroll)
>= wp->w_height_inner))
&& wp->w_height_inner != 0
&& wp->w_cursor.lnum == wp->w_topline
- && width > 0
+ && width2 > 0
&& wp->w_width_inner != 0) {
// Cursor past end of screen. Happens with a single line that does
// not fit on screen. Find a skipcol to show the text around the
@@ -843,7 +937,7 @@ void curs_columns(win_T *wp, int may_scroll)
// 2: Less than "p_so" lines below
// 3: both of them
extra = 0;
- if (wp->w_skipcol + so * width > wp->w_virtcol) {
+ if (wp->w_skipcol + so * width2 > wp->w_virtcol) {
extra = 1;
}
// Compute last display line of the buffer line that we want at the
@@ -854,17 +948,17 @@ void curs_columns(win_T *wp, int may_scroll)
plines--;
if (plines > wp->w_wrow + so) {
assert(so <= INT_MAX);
- n = wp->w_wrow + (int)so;
+ n = wp->w_wrow + so;
} else {
n = plines;
}
- if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width - so) {
+ if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width2 - so) {
extra += 2;
}
- if (extra == 3 || plines <= so * 2) {
+ if (extra == 3 || wp->w_height_inner <= so * 2) {
// not enough room for 'scrolloff', put cursor in the middle
- n = wp->w_virtcol / width;
+ n = wp->w_virtcol / width2;
if (n > wp->w_height_inner / 2) {
n -= wp->w_height_inner / 2;
} else {
@@ -874,51 +968,62 @@ void curs_columns(win_T *wp, int may_scroll)
if (n > plines - wp->w_height_inner + 1) {
n = plines - wp->w_height_inner + 1;
}
- wp->w_skipcol = n * width;
+ if (n > 0) {
+ curwin->w_skipcol = width1 + (n - 1) * width2;
+ } else {
+ curwin->w_skipcol = 0;
+ }
} else if (extra == 1) {
// less than 'scrolloff' lines above, decrease skipcol
assert(so <= INT_MAX);
- extra = (wp->w_skipcol + (int)so * width - wp->w_virtcol
- + width - 1) / width;
+ extra = (wp->w_skipcol + so * width2 - wp->w_virtcol + width2 - 1) / width2;
if (extra > 0) {
- if ((colnr_T)(extra * width) > wp->w_skipcol) {
- extra = wp->w_skipcol / width;
+ if ((colnr_T)(extra * width2) > wp->w_skipcol) {
+ extra = wp->w_skipcol / width2;
}
- wp->w_skipcol -= extra * width;
+ wp->w_skipcol -= extra * width2;
}
} else if (extra == 2) {
// less than 'scrolloff' lines below, increase skipcol
- endcol = (n - wp->w_height_inner + 1) * width;
+ endcol = (n - wp->w_height_inner + 1) * width2;
while (endcol > wp->w_virtcol) {
- endcol -= width;
+ endcol -= width2;
}
if (endcol > wp->w_skipcol) {
wp->w_skipcol = endcol;
}
}
- wp->w_wrow -= wp->w_skipcol / width;
+ // adjust w_wrow for the changed w_skipcol
+ if (did_sub_skipcol) {
+ wp->w_wrow -= (wp->w_skipcol - prev_skipcol) / width2;
+ } else {
+ wp->w_wrow -= wp->w_skipcol / width2;
+ }
+
if (wp->w_wrow >= wp->w_height_inner) {
// small window, make sure cursor is in it
extra = wp->w_wrow - wp->w_height_inner + 1;
- wp->w_skipcol += extra * width;
+ wp->w_skipcol += extra * width2;
wp->w_wrow -= extra;
}
// extra could be either positive or negative
- extra = ((int)prev_skipcol - (int)wp->w_skipcol) / width;
+ extra = (prev_skipcol - wp->w_skipcol) / width2;
win_scroll_lines(wp, 0, extra);
- } else {
+ } else if (!wp->w_p_sms) {
wp->w_skipcol = 0;
}
if (prev_skipcol != wp->w_skipcol) {
- redraw_later(wp, UPD_NOT_VALID);
+ redraw_later(wp, UPD_SOME_VALID);
}
- redraw_for_cursorcolumn(curwin);
+ redraw_for_cursorcolumn(wp);
- // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise
+ // now w_leftcol and w_skipcol are valid, avoid check_cursor_moved()
+ // thinking otherwise
wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_valid_skipcol = wp->w_skipcol;
wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
@@ -935,64 +1040,67 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp,
{
colnr_T scol = 0, ccol = 0, ecol = 0;
int row = 0;
- int rowoff = 0;
colnr_T coloff = 0;
bool visible_row = false;
bool is_folded = false;
- if (pos->lnum >= wp->w_topline && pos->lnum <= wp->w_botline) {
- linenr_T lnum = pos->lnum;
+ linenr_T lnum = pos->lnum;
+ if (lnum >= wp->w_topline && lnum <= wp->w_botline) {
is_folded = hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL);
- row = plines_m_win(wp, wp->w_topline, lnum - 1) + 1;
+ row = plines_m_win(wp, wp->w_topline, lnum - 1, false);
+ // "row" should be the screen line where line "lnum" begins, which can
+ // be negative if "lnum" is "w_topline" and "w_skipcol" is non-zero.
+ row -= adjust_plines_for_skipcol(wp);
// Add filler lines above this buffer line.
- row += win_get_fill(wp, lnum);
+ row += lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum);
visible_row = true;
- } else if (!local || pos->lnum < wp->w_topline) {
+ } else if (!local || lnum < wp->w_topline) {
row = 0;
} else {
- row = wp->w_height_inner;
+ row = wp->w_height_inner - 1;
}
- bool existing_row = (pos->lnum > 0
- && pos->lnum <= wp->w_buffer->b_ml.ml_line_count);
+ bool existing_row = (lnum > 0 && lnum <= wp->w_buffer->b_ml.ml_line_count);
if ((local || visible_row) && existing_row) {
const colnr_T off = win_col_off(wp);
if (is_folded) {
- row += local ? 0 : wp->w_winrow + wp->w_winrow_off;
+ row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1;
coloff = (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1 + off;
} else {
+ assert(lnum == pos->lnum);
getvcol(wp, pos, &scol, &ccol, &ecol);
// similar to what is done in validate_cursor_col()
colnr_T col = scol;
col += off;
- int width = wp->w_width - off + win_col_off2(wp);
+ int width = wp->w_width_inner - off + win_col_off2(wp);
// long line wrapping, adjust row
- if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) {
+ if (wp->w_p_wrap && col >= (colnr_T)wp->w_width_inner && width > 0) {
// use same formula as what is used in curs_columns()
- rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0;
+ int rowoff = visible_row ? ((col - wp->w_width_inner) / width + 1) : 0;
col -= rowoff * width;
+ row += rowoff;
}
col -= wp->w_leftcol;
- if (col >= 0 && col < wp->w_width && row + rowoff <= wp->w_height) {
+ if (col >= 0 && col < wp->w_width_inner && row >= 0 && row < wp->w_height_inner) {
coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1;
- row += local ? 0 : wp->w_winrow + wp->w_winrow_off;
+ row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1;
} else {
// character is left, right or below of the window
scol = ccol = ecol = 0;
if (local) {
coloff = col < 0 ? -1 : wp->w_width_inner + 1;
} else {
- row = rowoff = 0;
+ row = 0;
}
}
}
}
- *rowp = row + rowoff;
+ *rowp = row;
*scolp = scol + coloff;
*ccolp = ccol + coloff;
*ecolp = ecol + coloff;
@@ -1010,8 +1118,8 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
pos_T pos = {
- .lnum = (linenr_T)tv_get_number(&argvars[1]),
- .col = (colnr_T)tv_get_number(&argvars[2]) - 1,
+ .lnum = (linenr_T)tv_get_number(&argvars[1]),
+ .col = (colnr_T)tv_get_number(&argvars[2]) - 1,
.coladd = 0
};
if (pos.lnum > wp->w_buffer->b_ml.ml_line_count) {
@@ -1028,6 +1136,25 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
}
+/// Convert a virtual (screen) column to a character column. The first column
+/// is one. For a multibyte character, the column number of the first byte is
+/// returned.
+static int virtcol2col(win_T *wp, linenr_T lnum, int vcol)
+{
+ int offset = vcol2col(wp, lnum, vcol - 1, NULL);
+ char *line = ml_get_buf(wp->w_buffer, lnum);
+ char *p = line + offset;
+
+ if (*p == NUL) {
+ if (p == line) { // empty line
+ return 0;
+ }
+ // Move to the first byte of the last char.
+ MB_PTR_BACK(line, p);
+ }
+ return (int)(p - line + 1);
+}
+
/// "virtcol2col({winid}, {lnum}, {col})" function
void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -1055,46 +1182,83 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- rettv->vval.v_number = vcol2col(wp, lnum, screencol);
+ rettv->vval.v_number = virtcol2col(wp, lnum, screencol);
}
/// Scroll the current window down by "line_count" logical lines. "CTRL-Y"
///
/// @param line_count number of lines to scroll
/// @param byfold if true, count a closed fold as one line
-bool scrolldown(long line_count, int byfold)
+bool scrolldown(linenr_T line_count, int byfold)
{
int done = 0; // total # of physical lines done
+ int width1 = 0;
+ int width2 = 0;
+ bool do_sms = curwin->w_p_wrap && curwin->w_p_sms;
+
+ if (do_sms) {
+ width1 = curwin->w_width_inner - curwin_col_off();
+ width2 = width1 + curwin_col_off2();
+ }
// Make sure w_topline is at the first of a sequence of folded lines.
(void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
validate_cursor(); // w_wrow needs to be valid
- while (line_count-- > 0) {
+ for (int todo = line_count; todo > 0; todo--) {
if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)
&& curwin->w_topfill < curwin->w_height_inner - 1) {
curwin->w_topfill++;
done++;
} else {
- if (curwin->w_topline == 1) {
+ // break when at the very top
+ if (curwin->w_topline == 1 && (!do_sms || curwin->w_skipcol < width1)) {
break;
}
- curwin->w_topline--;
- curwin->w_topfill = 0;
- // A sequence of folded lines only counts for one logical line
- linenr_T first;
- if (hasFolding(curwin->w_topline, &first, NULL)) {
- done++;
- if (!byfold) {
- line_count -= curwin->w_topline - first - 1;
+ if (do_sms && curwin->w_skipcol >= width1) {
+ // scroll a screen line down
+ if (curwin->w_skipcol >= width1 + width2) {
+ curwin->w_skipcol -= width2;
+ } else {
+ curwin->w_skipcol -= width1;
}
- curwin->w_botline -= curwin->w_topline - first;
- curwin->w_topline = first;
+ redraw_later(curwin, UPD_NOT_VALID);
+ done++;
} else {
- done += plines_win_nofill(curwin, curwin->w_topline, true);
+ // scroll a text line down
+ curwin->w_topline--;
+ curwin->w_skipcol = 0;
+ curwin->w_topfill = 0;
+ // A sequence of folded lines only counts for one logical line
+ linenr_T first;
+ if (hasFolding(curwin->w_topline, &first, NULL)) {
+ done++;
+ if (!byfold) {
+ todo -= curwin->w_topline - first - 1;
+ }
+ curwin->w_botline -= curwin->w_topline - first;
+ curwin->w_topline = first;
+ } else {
+ if (do_sms) {
+ int size = win_linetabsize(curwin, curwin->w_topline,
+ ml_get(curwin->w_topline), MAXCOL);
+ if (size > width1) {
+ curwin->w_skipcol = width1;
+ size -= width1;
+ redraw_later(curwin, UPD_NOT_VALID);
+ }
+ while (size > width2) {
+ curwin->w_skipcol += width2;
+ size -= width2;
+ }
+ done++;
+ } else {
+ done += plines_win_nofill(curwin, curwin->w_topline, true);
+ }
+ }
}
}
curwin->w_botline--; // approximate w_botline
- invalidate_botline();
+ invalidate_botline(curwin);
}
curwin->w_wrow += done; // keep w_wrow updated
curwin->w_cline_row += done; // keep w_cline_row updated
@@ -1135,6 +1299,27 @@ bool scrolldown(long line_count, int byfold)
foldAdjustCursor();
coladvance(curwin->w_curswant);
}
+
+ if (curwin->w_cursor.lnum == curwin->w_topline && do_sms) {
+ int so = get_scrolloff_value(curwin);
+ colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
+
+ // make sure the cursor is in the visible text
+ validate_virtcol();
+ colnr_T col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols;
+ int row = 0;
+ if (col >= width1) {
+ col -= width1;
+ row++;
+ }
+ if (col > width2 && width2 > 0) {
+ row += (int)col / width2;
+ }
+ if (row >= curwin->w_height_inner) {
+ curwin->w_curswant = curwin->w_virtcol - (row - curwin->w_height_inner + 1) * width2;
+ coladvance(curwin->w_curswant);
+ }
+ }
return moved;
}
@@ -1142,35 +1327,76 @@ bool scrolldown(long line_count, int byfold)
///
/// @param line_count number of lines to scroll
/// @param byfold if true, count a closed fold as one line
-bool scrollup(long line_count, int byfold)
+bool scrollup(linenr_T line_count, int byfold)
{
linenr_T topline = curwin->w_topline;
linenr_T botline = curwin->w_botline;
+ int do_sms = curwin->w_p_wrap && curwin->w_p_sms;
- if ((byfold && hasAnyFolding(curwin))
- || win_may_fill(curwin)) {
- // count each sequence of folded lines as one logical line
- linenr_T lnum = curwin->w_topline;
- while (line_count--) {
+ if (do_sms || (byfold && hasAnyFolding(curwin)) || win_may_fill(curwin)) {
+ int width1 = curwin->w_width_inner - curwin_col_off();
+ int width2 = width1 + curwin_col_off2();
+ int size = 0;
+ const colnr_T prev_skipcol = curwin->w_skipcol;
+
+ if (do_sms) {
+ size = linetabsize(curwin, curwin->w_topline);
+ }
+
+ // diff mode: first consume "topfill"
+ // 'smoothscroll': increase "w_skipcol" until it goes over the end of
+ // the line, then advance to the next line.
+ // folding: count each sequence of folded lines as one logical line.
+ for (int todo = line_count; todo > 0; todo--) {
if (curwin->w_topfill > 0) {
curwin->w_topfill--;
} else {
+ linenr_T lnum = curwin->w_topline;
if (byfold) {
+ // for a closed fold: go to the last line in the fold
(void)hasFolding(lnum, NULL, &lnum);
}
- if (lnum >= curbuf->b_ml.ml_line_count) {
- break;
+ if (lnum == curwin->w_topline && do_sms) {
+ // 'smoothscroll': increase "w_skipcol" until it goes over
+ // the end of the line, then advance to the next line.
+ int add = curwin->w_skipcol > 0 ? width2 : width1;
+ curwin->w_skipcol += add;
+ if (curwin->w_skipcol >= size) {
+ if (lnum == curbuf->b_ml.ml_line_count) {
+ // at the last screen line, can't scroll further
+ curwin->w_skipcol -= add;
+ break;
+ }
+ lnum++;
+ }
+ } else {
+ if (lnum >= curbuf->b_ml.ml_line_count) {
+ break;
+ }
+ lnum++;
+ }
+
+ if (lnum > curwin->w_topline) {
+ // approximate w_botline
+ curwin->w_botline += lnum - curwin->w_topline;
+ curwin->w_topline = lnum;
+ curwin->w_topfill = win_get_fill(curwin, lnum);
+ curwin->w_skipcol = 0;
+ if (todo > 1 && do_sms) {
+ size = linetabsize(curwin, curwin->w_topline);
+ }
}
- lnum++;
- curwin->w_topfill = win_get_fill(curwin, lnum);
}
}
- // approximate w_botline
- curwin->w_botline += lnum - curwin->w_topline;
- curwin->w_topline = lnum;
+
+ if (prev_skipcol > 0 || curwin->w_skipcol > 0) {
+ // need to redraw more, because wl_size of the (new) topline may
+ // now be invalid
+ redraw_later(curwin, UPD_NOT_VALID);
+ }
} else {
- curwin->w_topline += (linenr_T)line_count;
- curwin->w_botline += (linenr_T)line_count; // approximate w_botline
+ curwin->w_topline += line_count;
+ curwin->w_botline += line_count; // approximate w_botline
}
if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
@@ -1195,12 +1421,116 @@ bool scrollup(long line_count, int byfold)
coladvance(curwin->w_curswant);
}
- bool moved = topline != curwin->w_topline
- || botline != curwin->w_botline;
+ if (curwin->w_cursor.lnum == curwin->w_topline && do_sms && curwin->w_skipcol > 0) {
+ int col_off = curwin_col_off();
+ int col_off2 = curwin_col_off2();
+
+ int width1 = curwin->w_width_inner - col_off;
+ int width2 = width1 + col_off2;
+ int extra2 = col_off - col_off2;
+ int so = get_scrolloff_value(curwin);
+ colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
+ int space_cols = (curwin->w_height_inner - 1) * width2;
+
+ // If we have non-zero scrolloff, just ignore the marker as we are
+ // going past it anyway.
+ int overlap = scrolloff_cols != 0 ? 0 : sms_marker_overlap(curwin, extra2);
+
+ // Make sure the cursor is in a visible part of the line, taking
+ // 'scrolloff' into account, but using screen lines.
+ // If there are not enough screen lines put the cursor in the middle.
+ if (scrolloff_cols > space_cols / 2) {
+ scrolloff_cols = space_cols / 2;
+ }
+ validate_virtcol();
+ if (curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) {
+ colnr_T col = curwin->w_virtcol;
+
+ if (col < width1) {
+ col += width1;
+ }
+ while (col < curwin->w_skipcol + overlap + scrolloff_cols) {
+ col += width2;
+ }
+ curwin->w_curswant = col;
+ coladvance(curwin->w_curswant);
+
+ // validate_virtcol() marked various things as valid, but after
+ // moving the cursor they need to be recomputed
+ curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
+ }
+ }
+
+ bool moved = topline != curwin->w_topline || botline != curwin->w_botline;
return moved;
}
+/// Called after changing the cursor column: make sure that curwin->w_skipcol is
+/// valid for 'smoothscroll'.
+void adjust_skipcol(void)
+{
+ if (!curwin->w_p_wrap || !curwin->w_p_sms || curwin->w_cursor.lnum != curwin->w_topline) {
+ return;
+ }
+
+ int width1 = curwin->w_width_inner - curwin_col_off();
+ if (width1 <= 0) {
+ return; // no text will be displayed
+ }
+ int width2 = width1 + curwin_col_off2();
+ int so = get_scrolloff_value(curwin);
+ colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
+ bool scrolled = false;
+
+ validate_cheight();
+ if (curwin->w_cline_height == curwin->w_height_inner
+ // w_cline_height may be capped at w_height_inner, check there aren't
+ // actually more lines.
+ && plines_win(curwin, curwin->w_cursor.lnum, false) <= curwin->w_height_inner) {
+ // the line just fits in the window, don't scroll
+ reset_skipcol(curwin);
+ return;
+ }
+
+ validate_virtcol();
+ int overlap = sms_marker_overlap(curwin, curwin_col_off() - curwin_col_off2());
+ while (curwin->w_skipcol > 0
+ && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) {
+ // scroll a screen line down
+ if (curwin->w_skipcol >= width1 + width2) {
+ curwin->w_skipcol -= width2;
+ } else {
+ curwin->w_skipcol -= width1;
+ }
+ scrolled = true;
+ }
+ if (scrolled) {
+ validate_virtcol();
+ redraw_later(curwin, UPD_NOT_VALID);
+ return; // don't scroll in the other direction now
+ }
+ colnr_T col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols;
+ int row = 0;
+ if (col >= width1) {
+ col -= width1;
+ row++;
+ }
+ if (col > width2) {
+ row += (int)col / width2;
+ }
+ if (row >= curwin->w_height_inner) {
+ if (curwin->w_skipcol == 0) {
+ curwin->w_skipcol += width1;
+ row--;
+ }
+ if (row >= curwin->w_height_inner) {
+ curwin->w_skipcol += (row - curwin->w_height_inner) * width2;
+ }
+ redraw_later(curwin, UPD_NOT_VALID);
+ }
+}
+
/// Don't end up with too many filler lines in the window.
///
/// @param down when true scroll down when not enough space
@@ -1317,7 +1647,8 @@ void scrollup_clamp(void)
// a (wrapped) text line. Uses and sets "lp->fill".
// Returns the height of the added line in "lp->height".
// Lines above the first one are incredibly high: MAXCOL.
-static void topline_back(win_T *wp, lineoff_T *lp)
+// When "winheight" is true limit to window height.
+static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight)
{
if (lp->fill < win_get_fill(wp, lp->lnum)) {
// Add a filler line
@@ -1332,11 +1663,16 @@ static void topline_back(win_T *wp, lineoff_T *lp)
// Add a closed fold
lp->height = 1;
} else {
- lp->height = plines_win_nofill(wp, lp->lnum, true);
+ lp->height = plines_win_nofill(wp, lp->lnum, winheight);
}
}
}
+static void topline_back(win_T *wp, lineoff_T *lp)
+{
+ topline_back_winheight(wp, lp, true);
+}
+
// Add one line below "lp->lnum". This can be a filler line, a closed fold or
// a (wrapped) text line. Uses and sets "lp->fill".
// Returns the height of the added line in "lp->height".
@@ -1389,13 +1725,10 @@ static void topline_botline(lineoff_T *lp)
// If "always" is true, always set topline (for "zt").
void scroll_cursor_top(int min_scroll, int always)
{
- int scrolled = 0;
- linenr_T top; // just above displayed lines
- linenr_T bot; // just below displayed lines
linenr_T old_topline = curwin->w_topline;
+ int old_skipcol = curwin->w_skipcol;
linenr_T old_topfill = curwin->w_topfill;
- linenr_T new_topline;
- int off = (int)get_scrolloff_value(curwin);
+ int off = get_scrolloff_value(curwin);
if (mouse_dragging > 0) {
off = mouse_dragging - 1;
@@ -1407,11 +1740,14 @@ void scroll_cursor_top(int min_scroll, int always)
// - moved at least 'scrolljump' lines and
// - at least 'scrolloff' lines above and below the cursor
validate_cheight();
+ int scrolled = 0;
int used = curwin->w_cline_height; // includes filler lines above
if (curwin->w_cursor.lnum < curwin->w_topline) {
scrolled = used;
}
+ linenr_T top; // just above displayed lines
+ linenr_T bot; // just below displayed lines
if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) {
top--;
bot++;
@@ -1419,7 +1755,7 @@ void scroll_cursor_top(int min_scroll, int always)
top = curwin->w_cursor.lnum - 1;
bot = curwin->w_cursor.lnum + 1;
}
- new_topline = top + 1;
+ linenr_T new_topline = top + 1;
// "used" already contains the number of filler lines above, don't add it
// again.
@@ -1432,6 +1768,15 @@ void scroll_cursor_top(int min_scroll, int always)
int i = hasFolding(top, &top, NULL)
? 1 // count one logical line for a sequence of folded lines
: plines_win_nofill(curwin, top, true);
+ if (top < curwin->w_topline) {
+ scrolled += i;
+ }
+
+ // If scrolling is needed, scroll at least 'sj' lines.
+ if ((new_topline >= curwin->w_topline || scrolled > min_scroll) && extra >= off) {
+ break;
+ }
+
used += i;
if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) {
if (hasFolding(bot, NULL, &bot)) {
@@ -1444,15 +1789,6 @@ void scroll_cursor_top(int min_scroll, int always)
if (used > curwin->w_height_inner) {
break;
}
- if (top < curwin->w_topline) {
- scrolled += i;
- }
-
- // If scrolling is needed, scroll at least 'sj' lines.
- if ((new_topline >= curwin->w_topline || scrolled > min_scroll)
- && extra >= off) {
- break;
- }
extra += i;
new_topline = top;
@@ -1464,10 +1800,10 @@ void scroll_cursor_top(int min_scroll, int always)
// This makes sure we get the same position when using "k" and "j"
// in a small window.
if (used > curwin->w_height_inner) {
- scroll_cursor_halfway(false);
+ scroll_cursor_halfway(false, false);
} else {
// If "always" is false, only adjust topline to a lower value, higher
- // value may happen with wrapping lines
+ // value may happen with wrapping lines.
if (new_topline < curwin->w_topline || always) {
curwin->w_topline = new_topline;
}
@@ -1482,7 +1818,18 @@ void scroll_cursor_top(int min_scroll, int always)
}
}
check_topfill(curwin, false);
+ if (curwin->w_topline != old_topline) {
+ reset_skipcol(curwin);
+ } else if (curwin->w_topline == curwin->w_cursor.lnum) {
+ validate_virtcol();
+ if (curwin->w_skipcol >= curwin->w_virtcol) {
+ // TODO(vim): if the line doesn't fit may optimize w_skipcol instead
+ // of making it zero
+ reset_skipcol(curwin);
+ }
+ }
if (curwin->w_topline != old_topline
+ || curwin->w_skipcol != old_skipcol
|| curwin->w_topfill != old_topfill) {
curwin->w_valid &=
~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
@@ -1519,31 +1866,44 @@ void set_empty_rows(win_T *wp, int used)
/// This is messy stuff!!!
void scroll_cursor_bot(int min_scroll, int set_topbot)
{
- int used;
- int scrolled = 0;
- int extra = 0;
lineoff_T loff;
- lineoff_T boff;
- int fill_below_window;
- linenr_T old_topline = curwin->w_topline;
- int old_topfill = curwin->w_topfill;
- linenr_T old_botline = curwin->w_botline;
- int old_valid = curwin->w_valid;
+ linenr_T old_topline = curwin->w_topline;
+ int old_skipcol = curwin->w_skipcol;
+ int old_topfill = curwin->w_topfill;
+ linenr_T old_botline = curwin->w_botline;
+ int old_valid = curwin->w_valid;
int old_empty_rows = curwin->w_empty_rows;
- linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number
- long so = get_scrolloff_value(curwin);
+ linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number
+ int do_sms = curwin->w_p_wrap && curwin->w_p_sms;
if (set_topbot) {
- used = 0;
+ bool set_skipcol = false;
+
+ int used = 0;
curwin->w_botline = cln + 1;
loff.fill = 0;
for (curwin->w_topline = curwin->w_botline;
curwin->w_topline > 1;
curwin->w_topline = loff.lnum) {
loff.lnum = curwin->w_topline;
- topline_back(curwin, &loff);
- if (loff.height == MAXCOL
- || used + loff.height > curwin->w_height_inner) {
+ topline_back_winheight(curwin, &loff, false);
+ if (loff.height == MAXCOL) {
+ break;
+ }
+ if (used + loff.height > curwin->w_height_inner) {
+ if (do_sms) {
+ // 'smoothscroll' and 'wrap' are set. The above line is
+ // too long to show in its entirety, so we show just a part
+ // of it.
+ if (used < curwin->w_height_inner) {
+ int plines_offset = used + loff.height - curwin->w_height_inner;
+ used = curwin->w_height_inner;
+ curwin->w_topfill = loff.fill;
+ curwin->w_topline = loff.lnum;
+ curwin->w_skipcol = skipcol_from_plines(curwin, plines_offset);
+ set_skipcol = true;
+ }
+ }
break;
}
used += loff.height;
@@ -1552,26 +1912,59 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
set_empty_rows(curwin, used);
curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
if (curwin->w_topline != old_topline
- || curwin->w_topfill != old_topfill) {
+ || curwin->w_topfill != old_topfill
+ || set_skipcol
+ || curwin->w_skipcol != 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
+ if (set_skipcol) {
+ redraw_later(curwin, UPD_NOT_VALID);
+ } else {
+ reset_skipcol(curwin);
+ }
}
} else {
validate_botline(curwin);
}
// The lines of the cursor line itself are always used.
- used = plines_win_nofill(curwin, cln, true);
+ int used = plines_win_nofill(curwin, cln, true);
- // If the cursor is below botline, we will at least scroll by the height
- // of the cursor line. Correct for empty lines, which are really part of
- // botline.
+ int scrolled = 0;
+ // If the cursor is on or below botline, we will at least scroll by the
+ // height of the cursor line, which is "used". Correct for empty lines,
+ // which are really part of botline.
if (cln >= curwin->w_botline) {
scrolled = used;
if (cln == curwin->w_botline) {
scrolled -= curwin->w_empty_rows;
}
+ if (do_sms) {
+ // 'smoothscroll' and 'wrap' are set.
+ // Calculate how many screen lines the current top line of window
+ // occupies. If it is occupying more than the entire window, we
+ // need to scroll the additional clipped lines to scroll past the
+ // top line before we can move on to the other lines.
+ int top_plines = plines_win_nofill(curwin, curwin->w_topline, false);
+ int skip_lines = 0;
+ int width1 = curwin->w_width_inner - curwin_col_off();
+ if (width1 > 0) {
+ int width2 = width1 + curwin_col_off2();
+ // similar formula is used in curs_columns()
+ if (curwin->w_skipcol > width1) {
+ skip_lines += (curwin->w_skipcol - width1) / width2 + 1;
+ } else if (curwin->w_skipcol > 0) {
+ skip_lines = 1;
+ }
+
+ top_plines -= skip_lines;
+ if (top_plines > curwin->w_height_inner) {
+ scrolled += (top_plines - curwin->w_height_inner);
+ }
+ }
+ }
}
+ lineoff_T boff;
// Stop counting lines to scroll when
// - hitting start of the file
// - scrolled nothing or at least 'sj' lines
@@ -1583,9 +1976,10 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
}
loff.fill = 0;
boff.fill = 0;
- fill_below_window = win_get_fill(curwin, curwin->w_botline)
- - curwin->w_filler_rows;
+ int fill_below_window = win_get_fill(curwin, curwin->w_botline) - curwin->w_filler_rows;
+ int extra = 0;
+ int so = get_scrolloff_value(curwin);
while (loff.lnum > 1) {
// Stop when scrolled nothing or at least "min_scroll", found "extra"
// context for 'scrolloff' and counted all lines below the window.
@@ -1669,15 +2063,19 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
// Scroll up if the cursor is off the bottom of the screen a bit.
// Otherwise put it at 1/2 of the screen.
if (line_count >= curwin->w_height_inner && line_count > min_scroll) {
- scroll_cursor_halfway(false);
- } else {
- scrollup(line_count, true);
+ scroll_cursor_halfway(false, true);
+ } else if (line_count > 0) {
+ if (do_sms) {
+ scrollup(scrolled, true); // TODO(vim):
+ } else {
+ scrollup(line_count, true);
+ }
}
// If topline didn't change we need to restore w_botline and w_empty_rows
// (we changed them).
// If topline did change, update_screen() will set botline.
- if (curwin->w_topline == old_topline && set_topbot) {
+ if (curwin->w_topline == old_topline && curwin->w_skipcol == old_skipcol && set_topbot) {
curwin->w_botline = old_botline;
curwin->w_empty_rows = old_empty_rows;
curwin->w_valid = old_valid;
@@ -1690,55 +2088,121 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
///
/// @param atend if true, also put the cursor halfway to the end of the file.
///
-void scroll_cursor_halfway(int atend)
+void scroll_cursor_halfway(bool atend, bool prefer_above)
{
- int above = 0;
- int topfill = 0;
- int below = 0;
- lineoff_T loff;
- lineoff_T boff;
linenr_T old_topline = curwin->w_topline;
-
- loff.lnum = boff.lnum = curwin->w_cursor.lnum;
+ lineoff_T loff = { .lnum = curwin->w_cursor.lnum };
+ lineoff_T boff = { .lnum = curwin->w_cursor.lnum };
(void)hasFolding(loff.lnum, &loff.lnum, &boff.lnum);
int used = plines_win_nofill(curwin, loff.lnum, true);
loff.fill = 0;
boff.fill = 0;
linenr_T topline = loff.lnum;
- while (topline > 1) {
- if (below <= above) { // add a line below the cursor first
- if (boff.lnum < curbuf->b_ml.ml_line_count) {
- botline_forw(curwin, &boff);
- used += boff.height;
- if (used > curwin->w_height_inner) {
- break;
- }
- below += boff.height;
- } else {
- below++; // count a "~" line
- if (atend) {
- used++;
- }
- }
+ colnr_T skipcol = 0;
+
+ int want_height;
+ bool do_sms = curwin->w_p_wrap && curwin->w_p_sms;
+ if (do_sms) {
+ // 'smoothscroll' and 'wrap' are set
+ if (atend) {
+ want_height = (curwin->w_height_inner - used) / 2;
+ used = 0;
+ } else {
+ want_height = curwin->w_height_inner;
}
+ }
- if (below > above) { // add a line above the cursor
- topline_back(curwin, &loff);
+ int topfill = 0;
+ while (topline > 1) {
+ // If using smoothscroll, we can precisely scroll to the
+ // exact point where the cursor is halfway down the screen.
+ if (do_sms) {
+ topline_back_winheight(curwin, &loff, false);
if (loff.height == MAXCOL) {
- used = MAXCOL;
- } else {
- used += loff.height;
+ break;
}
- if (used > curwin->w_height_inner) {
+ used += loff.height;
+ if (!atend && boff.lnum < curbuf->b_ml.ml_line_count) {
+ botline_forw(curwin, &boff);
+ used += boff.height;
+ }
+ if (used > want_height) {
+ if (used - loff.height < want_height) {
+ topline = loff.lnum;
+ topfill = loff.fill;
+ skipcol = skipcol_from_plines(curwin, used - want_height);
+ }
break;
}
- above += loff.height;
topline = loff.lnum;
topfill = loff.fill;
+ continue;
+ }
+
+ // If not using smoothscroll, we have to iteratively find how many
+ // lines to scroll down to roughly fit the cursor.
+ // This may not be right in the middle if the lines'
+ // physical height > 1 (e.g. 'wrap' is on).
+
+ // Depending on "prefer_above" we add a line above or below first.
+ // Loop twice to avoid duplicating code.
+ bool done = false;
+ int above = 0;
+ int below = 0;
+ for (int round = 1; round <= 2; round++) {
+ if (prefer_above
+ ? (round == 2 && below < above)
+ : (round == 1 && below <= above)) {
+ // add a line below the cursor
+ if (boff.lnum < curbuf->b_ml.ml_line_count) {
+ botline_forw(curwin, &boff);
+ used += boff.height;
+ if (used > curwin->w_height_inner) {
+ done = true;
+ break;
+ }
+ below += boff.height;
+ } else {
+ below++; // count a "~" line
+ if (atend) {
+ used++;
+ }
+ }
+ }
+
+ if (prefer_above
+ ? (round == 1 && below >= above)
+ : (round == 1 && below > above)) {
+ // add a line above the cursor
+ topline_back(curwin, &loff);
+ if (loff.height == MAXCOL) {
+ used = MAXCOL;
+ } else {
+ used += loff.height;
+ }
+ if (used > curwin->w_height_inner) {
+ done = true;
+ break;
+ }
+ above += loff.height;
+ topline = loff.lnum;
+ topfill = loff.fill;
+ }
+ }
+ if (done) {
+ break;
}
}
- if (!hasFolding(topline, &curwin->w_topline, NULL)) {
+
+ if (!hasFolding(topline, &curwin->w_topline, NULL)
+ && (curwin->w_topline != topline || skipcol != 0 || curwin->w_skipcol != 0)) {
curwin->w_topline = topline;
+ if (skipcol != 0) {
+ curwin->w_skipcol = skipcol;
+ redraw_later(curwin, UPD_NOT_VALID);
+ } else if (do_sms) {
+ reset_skipcol(curwin);
+ }
}
curwin->w_topfill = topfill;
if (old_topline > curwin->w_topline + curwin->w_height_inner) {
@@ -1757,8 +2221,8 @@ void cursor_correct(void)
{
// How many lines we would like to have above/below the cursor depends on
// whether the first/last line of the file is on screen.
- int above_wanted = (int)get_scrolloff_value(curwin);
- int below_wanted = (int)get_scrolloff_value(curwin);
+ int above_wanted = get_scrolloff_value(curwin);
+ int below_wanted = get_scrolloff_value(curwin);
if (mouse_dragging > 0) {
above_wanted = mouse_dragging - 1;
below_wanted = mouse_dragging - 1;
@@ -1789,6 +2253,16 @@ void cursor_correct(void)
return;
}
+ if (curwin->w_p_sms && !curwin->w_p_wrap) {
+ // 'smoothscroll' is active
+ if (curwin->w_cline_height == curwin->w_height_inner) {
+ // The cursor line just fits in the window, don't scroll.
+ reset_skipcol(curwin);
+ return;
+ }
+ // TODO(vim): If the cursor line doesn't fit in the window then only adjust w_skipcol.
+ }
+
// Narrow down the area where the cursor can be put by taking lines from
// the top and the bottom until:
// - the desired context lines are found
@@ -1841,16 +2315,16 @@ void cursor_correct(void)
curwin->w_viewport_invalid = true;
}
-// move screen 'count' pages up or down and update screen
-//
-// return FAIL for failure, OK otherwise
-int onepage(Direction dir, long count)
+/// Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD)
+/// and update the screen.
+///
+/// @return FAIL for failure, OK otherwise.
+int onepage(Direction dir, int count)
{
- long n;
int retval = OK;
lineoff_T loff;
linenr_T old_topline = curwin->w_topline;
- long so = get_scrolloff_value(curwin);
+ int so = get_scrolloff_value(curwin);
if (curbuf->b_ml.ml_line_count == 1) { // nothing to do
beep_flush();
@@ -1945,7 +2419,7 @@ int onepage(Direction dir, long count)
// Find the line just above the new topline to get the right line
// at the bottom of the window.
- n = 0;
+ int n = 0;
while (n <= curwin->w_height_inner && loff.lnum >= 1) {
topline_back(curwin, &loff);
if (loff.height == MAXCOL) {
@@ -1981,11 +2455,11 @@ int onepage(Direction dir, long count)
if (curwin->w_topfill == loff.fill) {
curwin->w_topline--;
curwin->w_topfill = 0;
+ curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
}
comp_botline(curwin);
curwin->w_cursor.lnum = curwin->w_botline - 1;
- curwin->w_valid &=
- ~(VALID_WCOL | VALID_CHEIGHT | VALID_WROW | VALID_CROW);
+ curwin->w_valid &= ~(VALID_WCOL|VALID_CHEIGHT|VALID_WROW|VALID_CROW);
} else {
curwin->w_topline = loff.lnum;
curwin->w_topfill = loff.fill;
@@ -2087,7 +2561,7 @@ static void get_scroll_overlap(lineoff_T *lp, int dir)
// Scroll 'scroll' lines up or down.
void halfpage(bool flag, linenr_T Prenum)
{
- long scrolled = 0;
+ int scrolled = 0;
int i;
if (Prenum) {
@@ -2157,7 +2631,7 @@ void halfpage(bool flag, linenr_T Prenum)
} else {
curwin->w_cursor.lnum += n;
}
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
}
} else {
// scroll the text down
@@ -2210,9 +2684,18 @@ void halfpage(bool flag, linenr_T Prenum)
void do_check_cursorbind(void)
{
- linenr_T line = curwin->w_cursor.lnum;
- colnr_T col = curwin->w_cursor.col;
- colnr_T coladd = curwin->w_cursor.coladd;
+ static win_T *prev_curwin = NULL;
+ static pos_T prev_cursor = { 0, 0, 0 };
+
+ if (curwin == prev_curwin && equalpos(curwin->w_cursor, prev_cursor)) {
+ return;
+ }
+ prev_curwin = curwin;
+ prev_cursor = curwin->w_cursor;
+
+ linenr_T line = curwin->w_cursor.lnum;
+ colnr_T col = curwin->w_cursor.col;
+ colnr_T coladd = curwin->w_cursor.coladd;
colnr_T curswant = curwin->w_curswant;
int set_curswant = curwin->w_set_curswant;
win_T *old_curwin = curwin;
@@ -2221,11 +2704,11 @@ void do_check_cursorbind(void)
int old_VIsual_active = VIsual_active;
// loop through the cursorbound windows
- VIsual_select = VIsual_active = 0;
+ VIsual_select = VIsual_active = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
curwin = wp;
curbuf = curwin->w_buffer;
- // skip original window and windows with 'noscrollbind'
+ // skip original window and windows with 'nocursorbind'
if (curwin != old_curwin && curwin->w_p_crb) {
if (curwin->w_p_diff) {
curwin->w_cursor.lnum =
diff --git a/src/nvim/move.h b/src/nvim/move.h
index dd944e39ee..ab8fb2b386 100644
--- a/src/nvim/move.h
+++ b/src/nvim/move.h
@@ -1,11 +1,12 @@
-#ifndef NVIM_MOVE_H
-#define NVIM_MOVE_H
+#pragma once
#include <stdbool.h>
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "move.h.generated.h"
#endif
-#endif // NVIM_MOVE_H
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index d60e18590f..0fb1ebf931 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <msgpack/object.h>
@@ -10,7 +7,6 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
-#include <uv.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
@@ -24,9 +20,11 @@
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
+#include "nvim/func_attr.h"
+#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/channel.h"
@@ -35,16 +33,82 @@
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/os/input.h"
#include "nvim/rbuffer.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
-#if MIN_LOG_LEVEL > LOGLVL_DBG
+#ifdef NVIM_LOG_DEBUG
+# define REQ "[request] "
+# define RES "[response] "
+# define NOT "[notify] "
+# define ERR "[error] "
+
+// Cannot define array with negative offsets, so this one is needed to be added
+// to MSGPACK_UNPACK_\* values.
+# define MUR_OFF 2
+
+static const char *const msgpack_error_messages[] = {
+ [MSGPACK_UNPACK_EXTRA_BYTES + MUR_OFF] = "extra bytes found",
+ [MSGPACK_UNPACK_CONTINUE + MUR_OFF] = "incomplete string",
+ [MSGPACK_UNPACK_PARSE_ERROR + MUR_OFF] = "parse error",
+ [MSGPACK_UNPACK_NOMEM_ERROR + MUR_OFF] = "not enough memory",
+};
+
+static void log_close(FILE *f)
+{
+ fputc('\n', f);
+ fflush(f);
+ fclose(f);
+ log_unlock();
+}
+
+static void log_server_msg(uint64_t channel_id, msgpack_sbuffer *packed)
+{
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ DLOGN("RPC ->ch %" PRIu64 ": ", channel_id);
+ const msgpack_unpack_return result =
+ msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
+ switch (result) {
+ case MSGPACK_UNPACK_SUCCESS: {
+ uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
+ log_lock();
+ FILE *f = open_log_file();
+ fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
+ msgpack_object_print(f, unpacked.data);
+ log_close(f);
+ msgpack_unpacked_destroy(&unpacked);
+ break;
+ }
+ case MSGPACK_UNPACK_EXTRA_BYTES:
+ case MSGPACK_UNPACK_CONTINUE:
+ case MSGPACK_UNPACK_PARSE_ERROR:
+ case MSGPACK_UNPACK_NOMEM_ERROR: {
+ log_lock();
+ FILE *f = open_log_file();
+ fprintf(f, ERR);
+ fprintf(f, "%s", msgpack_error_messages[result + MUR_OFF]);
+ log_close(f);
+ break;
+ }
+ }
+}
+
+static void log_client_msg(uint64_t channel_id, bool is_request, const char *name)
+{
+ DLOGN("RPC <-ch %" PRIu64 ": ", channel_id);
+ log_lock();
+ FILE *f = open_log_file();
+ fprintf(f, "%s: %s", is_request ? REQ : RES, name);
+ log_close(f);
+}
+
+#else
# define log_client_msg(...)
# define log_server_msg(...)
#endif
-static PMap(cstr_t) event_strings = MAP_INIT;
+static Set(cstr_t) event_strings = SET_INIT;
static msgpack_sbuffer out_buffer;
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -71,7 +135,7 @@ void rpc_start(Channel *channel)
if (channel->streamtype != kChannelStreamInternal) {
Stream *out = channel_outstream(channel);
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
+#ifdef NVIM_LOG_DEBUG
Stream *in = channel_instream(channel);
DLOG("rpc ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id,
(void *)in, (void *)out);
@@ -141,9 +205,15 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem
// Push the frame
ChannelCallFrame frame = { request_id, false, false, NIL, NULL };
kv_push(rpc->call_stack, &frame);
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned);
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned || rpc->closed);
(void)kv_pop(rpc->call_stack);
+ if (rpc->closed) {
+ api_set_error(err, kErrorTypeException, "Invalid channel: %" PRIu64, id);
+ channel_decref(channel);
+ return NIL;
+ }
+
if (frame.errored) {
if (frame.result.type == kObjectTypeString) {
api_set_error(err, kErrorTypeException, "%s",
@@ -188,14 +258,12 @@ void rpc_subscribe(uint64_t id, char *event)
abort();
}
- char *event_string = pmap_get(cstr_t)(&event_strings, event);
-
- if (!event_string) {
- event_string = xstrdup(event);
- pmap_put(cstr_t)(&event_strings, event_string, event_string);
+ const char **key_alloc = NULL;
+ if (set_put_ref(cstr_t, &event_strings, event, &key_alloc)) {
+ *key_alloc = xstrdup(event);
}
- pmap_put(cstr_t)(channel->rpc.subscribed_events, event_string, event_string);
+ set_put(cstr_t, channel->rpc.subscribed_events, *key_alloc);
}
/// Unsubscribes to event broadcasts
@@ -242,26 +310,43 @@ end:
channel_decref(channel);
}
+static ChannelCallFrame *find_call_frame(RpcState *rpc, uint32_t request_id)
+{
+ for (size_t i = 0; i < kv_size(rpc->call_stack); i++) {
+ ChannelCallFrame *frame = kv_Z(rpc->call_stack, i);
+ if (frame->request_id == request_id) {
+ return frame;
+ }
+ }
+ return NULL;
+}
+
static void parse_msgpack(Channel *channel)
{
Unpacker *p = channel->rpc.unpacker;
while (unpacker_advance(p)) {
if (p->type == kMessageTypeRedrawEvent) {
- if (p->grid_line_event) {
- ui_client_event_raw_line(p->grid_line_event);
- } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) {
- p->ui_handler.fn(p->result.data.array);
+ // When exiting, ui_client_stop() has already been called, so don't handle UI events.
+ if (ui_client_channel_id && !exiting) {
+ if (p->grid_line_event) {
+ ui_client_event_raw_line(p->grid_line_event);
+ } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) {
+ p->ui_handler.fn(p->result.data.array);
+ }
}
arena_mem_free(arena_finish(&p->arena));
} else if (p->type == kMessageTypeResponse) {
- ChannelCallFrame *frame = kv_last(channel->rpc.call_stack);
- if (p->request_id != frame->request_id) {
+ ChannelCallFrame *frame = channel->rpc.client_type == kClientTypeMsgpackRpc
+ ? find_call_frame(&channel->rpc, p->request_id)
+ : kv_last(channel->rpc.call_stack);
+ if (frame == NULL || p->request_id != frame->request_id) {
char buf[256];
snprintf(buf, sizeof(buf),
- "ch %" PRIu64 " returned a response with an unknown request "
- "id. Ensure the client is properly synchronized",
- channel->id);
+ "ch %" PRIu64 " (type=%" PRIu32 ") returned a response with an unknown request "
+ "id %" PRIu32 ". Ensure the client is properly synchronized",
+ channel->id, (unsigned)channel->rpc.client_type, p->request_id);
chan_close_with_error(channel, buf, LOGLVL_ERR);
+ return;
}
frame->returned = true;
frame->errored = (p->error.type != kObjectTypeNil);
@@ -486,7 +571,7 @@ static void broadcast_event(const char *name, Array args)
map_foreach_value(&channels, channel, {
if (channel->is_rpc
- && pmap_has(cstr_t)(channel->rpc.subscribed_events, name)) {
+ && set_has(cstr_t, channel->rpc.subscribed_events, name)) {
kv_push(subscribed, channel);
}
});
@@ -514,24 +599,12 @@ end:
static void unsubscribe(Channel *channel, char *event)
{
- char *event_string = pmap_get(cstr_t)(&event_strings, event);
- if (!event_string) {
+ if (!set_has(cstr_t, &event_strings, event)) {
WLOG("RPC: ch %" PRIu64 ": tried to unsubscribe unknown event '%s'",
channel->id, event);
return;
}
- pmap_del(cstr_t)(channel->rpc.subscribed_events, event_string);
-
- map_foreach_value(&channels, channel, {
- if (channel->is_rpc
- && pmap_has(cstr_t)(channel->rpc.subscribed_events, event_string)) {
- return;
- }
- });
-
- // Since the string is no longer used by other channels, release it's memory
- pmap_del(cstr_t)(&event_strings, event_string);
- xfree(event_string);
+ set_del(cstr_t, channel->rpc.subscribed_events, event);
}
/// Mark rpc state as closed, and release its reference to the channel.
@@ -547,6 +620,10 @@ void rpc_close(Channel *channel)
if (channel->streamtype == kChannelStreamStdio
|| (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) {
+ if (channel->streamtype == kChannelStreamStdio) {
+ // Avoid hanging when there are no other UIs and a prompt is triggered on exit.
+ remote_ui_disconnect(channel->id);
+ }
exit_from_channel(0);
}
}
@@ -557,13 +634,7 @@ void rpc_free(Channel *channel)
unpacker_teardown(channel->rpc.unpacker);
xfree(channel->rpc.unpacker);
- // Unsubscribe from all events
- char *event_string;
- map_foreach_value(channel->rpc.subscribed_events, event_string, {
- unsubscribe(channel, event_string);
- });
-
- pmap_destroy(cstr_t)(channel->rpc.subscribed_events);
+ set_destroy(cstr_t, channel->rpc.subscribed_events);
kv_destroy(channel->rpc.call_stack);
api_free_dictionary(channel->rpc.info);
}
@@ -575,7 +646,7 @@ static void chan_close_with_error(Channel *channel, char *msg, int loglevel)
ChannelCallFrame *frame = kv_A(channel->rpc.call_stack, i);
frame->returned = true;
frame->errored = true;
- frame->result = STRING_OBJ(cstr_to_string(msg));
+ frame->result = CSTR_TO_OBJ(msg);
}
channel_close(channel->id, kChannelPartRpc, NULL);
@@ -612,7 +683,7 @@ static WBuffer *serialize_response(uint64_t channel_id, MsgpackRpcRequestHandler
} else {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(err->type));
- ADD(args, STRING_OBJ(cstr_to_string(err->msg)));
+ ADD(args, CSTR_TO_OBJ(err->msg));
msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"),
args, &pac);
api_free_array(args);
@@ -638,6 +709,25 @@ void rpc_set_client_info(uint64_t id, Dictionary info)
api_free_dictionary(chan->rpc.info);
chan->rpc.info = info;
+
+ // Parse "type" on "info" and set "client_type"
+ const char *type = get_client_info(chan, "type");
+ if (type == NULL || strequal(type, "remote")) {
+ chan->rpc.client_type = kClientTypeRemote;
+ } else if (strequal(type, "msgpack-rpc")) {
+ chan->rpc.client_type = kClientTypeMsgpackRpc;
+ } else if (strequal(type, "ui")) {
+ chan->rpc.client_type = kClientTypeUi;
+ } else if (strequal(type, "embedder")) {
+ chan->rpc.client_type = kClientTypeEmbedder;
+ } else if (strequal(type, "host")) {
+ chan->rpc.client_type = kClientTypeHost;
+ } else if (strequal(type, "plugin")) {
+ chan->rpc.client_type = kClientTypePlugin;
+ } else {
+ chan->rpc.client_type = kClientTypeUnknown;
+ }
+
channel_info_changed(chan, false);
}
@@ -646,14 +736,15 @@ Dictionary rpc_client_info(Channel *chan)
return copy_dictionary(chan->rpc.info, NULL);
}
-const char *rpc_client_name(Channel *chan)
+const char *get_client_info(Channel *chan, const char *key)
+ FUNC_ATTR_NONNULL_ALL
{
if (!chan->is_rpc) {
return NULL;
}
Dictionary info = chan->rpc.info;
for (size_t i = 0; i < info.size; i++) {
- if (strequal("name", info.items[i].key.data)
+ if (strequal(key, info.items[i].key.data)
&& info.items[i].value.type == kObjectTypeString) {
return info.items[i].value.data.string.data;
}
@@ -662,69 +753,11 @@ const char *rpc_client_name(Channel *chan)
return NULL;
}
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
-# define REQ "[request] "
-# define RES "[response] "
-# define NOT "[notify] "
-# define ERR "[error] "
-
-// Cannot define array with negative offsets, so this one is needed to be added
-// to MSGPACK_UNPACK_\* values.
-# define MUR_OFF 2
-
-static const char *const msgpack_error_messages[] = {
- [MSGPACK_UNPACK_EXTRA_BYTES + MUR_OFF] = "extra bytes found",
- [MSGPACK_UNPACK_CONTINUE + MUR_OFF] = "incomplete string",
- [MSGPACK_UNPACK_PARSE_ERROR + MUR_OFF] = "parse error",
- [MSGPACK_UNPACK_NOMEM_ERROR + MUR_OFF] = "not enough memory",
-};
-
-static void log_server_msg(uint64_t channel_id, msgpack_sbuffer *packed)
-{
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
- DLOGN("RPC ->ch %" PRIu64 ": ", channel_id);
- const msgpack_unpack_return result =
- msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
- switch (result) {
- case MSGPACK_UNPACK_SUCCESS: {
- uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
- log_lock();
- FILE *f = open_log_file();
- fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
- msgpack_object_print(f, unpacked.data);
- log_close(f);
- msgpack_unpacked_destroy(&unpacked);
- break;
- }
- case MSGPACK_UNPACK_EXTRA_BYTES:
- case MSGPACK_UNPACK_CONTINUE:
- case MSGPACK_UNPACK_PARSE_ERROR:
- case MSGPACK_UNPACK_NOMEM_ERROR: {
- log_lock();
- FILE *f = open_log_file();
- fprintf(f, ERR);
- fprintf(f, "%s", msgpack_error_messages[result + MUR_OFF]);
- log_close(f);
- break;
- }
- }
-}
-
-static void log_client_msg(uint64_t channel_id, bool is_request, const char *name)
+void rpc_free_all_mem(void)
{
- DLOGN("RPC <-ch %" PRIu64 ": ", channel_id);
- log_lock();
- FILE *f = open_log_file();
- fprintf(f, "%s: %s", is_request ? REQ : RES, name);
- log_close(f);
-}
-
-static void log_close(FILE *f)
-{
- fputc('\n', f);
- fflush(f);
- fclose(f);
- log_unlock();
+ cstr_t key;
+ set_foreach(&event_strings, key, {
+ xfree((void *)key);
+ });
+ set_destroy(cstr_t, &event_strings);
}
-#endif
diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h
index ce5806930c..818bee8318 100644
--- a/src/nvim/msgpack_rpc/channel.h
+++ b/src/nvim/msgpack_rpc/channel.h
@@ -1,25 +1,24 @@
-#ifndef NVIM_MSGPACK_RPC_CHANNEL_H
-#define NVIM_MSGPACK_RPC_CHANNEL_H
+#pragma once
-#include <stdbool.h>
-#include <uv.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/channel.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/socket.h"
-#include "nvim/macros.h"
-#include "nvim/vim.h"
+#include "nvim/event/wstream.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory_defs.h" // IWYU pragma: keep
+#include "nvim/msgpack_rpc/channel_defs.h" // IWYU pragma: export
#define METHOD_MAXLEN 512
/// HACK: os/input.c drains this queue immediately before blocking for input.
/// Events on this queue are async-safe, but they need the resolved state
/// of os_inchar(), so they are processed "just-in-time".
-EXTERN MultiQueue *ch_before_blocking_events INIT(= NULL);
+EXTERN MultiQueue *ch_before_blocking_events INIT( = NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/channel.h.generated.h"
#endif
-#endif // NVIM_MSGPACK_RPC_CHANNEL_H
diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h
index 404e68329a..20b8a89afb 100644
--- a/src/nvim/msgpack_rpc/channel_defs.h
+++ b/src/nvim/msgpack_rpc/channel_defs.h
@@ -1,19 +1,29 @@
-#ifndef NVIM_MSGPACK_RPC_CHANNEL_DEFS_H
-#define NVIM_MSGPACK_RPC_CHANNEL_DEFS_H
+#pragma once
#include <msgpack.h>
#include <stdbool.h>
#include <uv.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/event/process.h"
#include "nvim/event/socket.h"
-#include "nvim/vim.h"
+#include "nvim/map_defs.h"
typedef struct Channel Channel;
typedef struct Unpacker Unpacker;
+typedef enum {
+ kClientTypeUnknown = -1,
+ kClientTypeRemote = 0,
+ kClientTypeMsgpackRpc = 5,
+ kClientTypeUi = 1,
+ kClientTypeEmbedder = 2,
+ kClientTypeHost = 3,
+ kClientTypePlugin = 4,
+} ClientType;
+
typedef struct {
uint32_t request_id;
bool returned, errored;
@@ -31,12 +41,11 @@ typedef struct {
} RequestEvent;
typedef struct {
- PMap(cstr_t) subscribed_events[1];
+ Set(cstr_t) subscribed_events[1];
bool closed;
Unpacker *unpacker;
uint32_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack;
Dictionary info;
+ ClientType client_type;
} RpcState;
-
-#endif // NVIM_MSGPACK_RPC_CHANNEL_DEFS_H
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index 5f0f03dd69..1fdfc9e536 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <msgpack/object.h>
#include <msgpack/sbuffer.h>
#include <msgpack/unpack.h>
@@ -12,14 +9,13 @@
#include "klib/kvec.h"
#include "msgpack/pack.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
-#include "nvim/event/wstream.h"
+#include "nvim/assert_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "keysets.generated.h" // IWYU pragma: export
# include "msgpack_rpc/helpers.c.generated.h"
#endif
@@ -84,12 +80,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
*cur.aobj = INTEGER_OBJ((Integer)cur.mobj->via.u64);
}
break;
-#ifdef NVIM_MSGPACK_HAS_FLOAT32
case MSGPACK_OBJECT_FLOAT32:
case MSGPACK_OBJECT_FLOAT64:
-#else
- case MSGPACK_OBJECT_FLOAT:
-#endif
{
STATIC_ASSERT(sizeof(Float) == sizeof(cur.mobj->via.f64),
"Msgpack floating-point size does not match API integer");
@@ -156,12 +148,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
case MSGPACK_OBJECT_BOOLEAN:
case MSGPACK_OBJECT_POSITIVE_INTEGER:
case MSGPACK_OBJECT_NEGATIVE_INTEGER:
-#ifdef NVIM_MSGPACK_HAS_FLOAT32
case MSGPACK_OBJECT_FLOAT32:
case MSGPACK_OBJECT_FLOAT64:
-#else
- case MSGPACK_OBJECT_FLOAT:
-#endif
case MSGPACK_OBJECT_EXT:
case MSGPACK_OBJECT_MAP:
case MSGPACK_OBJECT_ARRAY:
@@ -484,8 +472,8 @@ msgpack_object *msgpack_rpc_method(msgpack_object *req)
{
msgpack_object *obj = req->via.array.ptr
+ (msgpack_rpc_is_notification(req) ? 1 : 2);
- return obj->type == MSGPACK_OBJECT_STR || obj->type == MSGPACK_OBJECT_BIN ?
- obj : NULL;
+ return obj->type == MSGPACK_OBJECT_STR || obj->type == MSGPACK_OBJECT_BIN
+ ? obj : NULL;
}
msgpack_object *msgpack_rpc_args(msgpack_object *req)
diff --git a/src/nvim/msgpack_rpc/helpers.h b/src/nvim/msgpack_rpc/helpers.h
index dab8a16b6b..dd2096f305 100644
--- a/src/nvim/msgpack_rpc/helpers.h
+++ b/src/nvim/msgpack_rpc/helpers.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MSGPACK_RPC_HELPERS_H
-#define NVIM_MSGPACK_RPC_HELPERS_H
+#pragma once
#include <msgpack.h>
#include <stdbool.h>
@@ -19,5 +18,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/helpers.h.generated.h"
#endif
-
-#endif // NVIM_MSGPACK_RPC_HELPERS_H
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index 1d75c208be..f3627eaa61 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
@@ -10,6 +7,7 @@
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/event/socket.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/log.h"
#include "nvim/main.h"
@@ -71,8 +69,8 @@ static void close_socket_watcher(SocketWatcher **watcher)
static void set_vservername(garray_T *srvs)
{
char *default_server = (srvs->ga_len > 0)
- ? ((SocketWatcher **)srvs->ga_data)[0]->addr
- : NULL;
+ ? ((SocketWatcher **)srvs->ga_data)[0]->addr
+ : NULL;
set_vim_var_string(VV_SEND_SERVER, default_server, -1);
}
@@ -91,13 +89,14 @@ char *server_address_new(const char *name)
{
static uint32_t count = 0;
char fmt[ADDRESS_MAX_SIZE];
+ const char *appname = get_appname();
#ifdef MSWIN
int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32,
- name ? name : "nvim", os_get_pid(), count++);
+ name ? name : appname, os_get_pid(), count++);
#else
char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir);
int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32,
- dir, name ? name : "nvim", os_get_pid(), count++);
+ dir, name ? name : appname, os_get_pid(), count++);
xfree(dir);
#endif
if ((size_t)r >= sizeof(fmt)) {
diff --git a/src/nvim/msgpack_rpc/server.h b/src/nvim/msgpack_rpc/server.h
index 5446e40e0b..71b578a14b 100644
--- a/src/nvim/msgpack_rpc/server.h
+++ b/src/nvim/msgpack_rpc/server.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_MSGPACK_RPC_SERVER_H
-#define NVIM_MSGPACK_RPC_SERVER_H
+#pragma once
-#include <stdio.h>
+#include <stddef.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/server.h.generated.h"
#endif
-#endif // NVIM_MSGPACK_RPC_SERVER_H
diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c
index 44a16beb48..38263381bf 100644
--- a/src/nvim/msgpack_rpc/unpacker.c
+++ b/src/nvim/msgpack_rpc/unpacker.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
@@ -8,8 +5,9 @@
#include "klib/kvec.h"
#include "mpack/conv.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
-#include "nvim/macros.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/grid.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
@@ -87,7 +85,7 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
*result = NIL;
break;
case MPACK_TOKEN_BOOLEAN:
- *result = BOOL(mpack_unpack_boolean(node->tok));
+ *result = BOOLEAN_OBJ(mpack_unpack_boolean(node->tok));
break;
case MPACK_TOKEN_SINT:
*result = INTEGER_OBJ(mpack_unpack_sint(node->tok));
@@ -172,14 +170,12 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
node->data[0].p = result;
break;
}
-
- default:
- abort();
}
}
static void api_parse_exit(mpack_parser_t *parser, mpack_node_t *node)
-{}
+{
+}
void unpacker_init(Unpacker *p)
{
@@ -292,13 +288,13 @@ error:
// objects. For the moment "redraw/grid_line" uses a hand-rolled decoder,
// to avoid a blizzard of small objects for each screen cell.
//
-// <0>[2, "redraw", <10>[{11}["method", <12>[args], <12>[args], ...], <11>[...], ...]]
+// <0>[2, "redraw", <10>[<11>["method", <12>[args], <12>[args], ...], <11>[...], ...]]
//
// Where [args] gets unpacked as an Array. Note: first {11} is not saved as a state.
//
// When method is "grid_line", we furthermore decode a cell at a time like:
//
-// <0>[2, "redraw", <10>[{11}["grid_line", <14>[g, r, c, [<15>[cell], <15>[cell], ...]], ...], <11>[...], ...]]
+// <0>[2, "redraw", <10>[<11>["grid_line", <14>[g, r, c, [<15>[cell], <15>[cell], ...], <16>wrap]], <11>[...], ...]]
//
// where [cell] is [char, repeat, attr], where 'repeat' and 'attr' is optional
@@ -323,7 +319,7 @@ bool unpacker_advance(Unpacker *p)
return false;
}
- if (p->state == 15) {
+ if (p->state == 16) {
// grid_line event already unpacked
goto done;
} else {
@@ -358,10 +354,10 @@ done:
p->state = 0;
return true;
case 13:
- case 15:
+ case 16:
p->ncalls--;
if (p->ncalls > 0) {
- p->state = (p->state == 15) ? 14 : 12;
+ p->state = (p->state == 16) ? 14 : 12;
} else if (p->nevents > 0) {
p->state = 11;
} else {
@@ -382,7 +378,6 @@ bool unpacker_parse_redraw(Unpacker *p)
size_t size = p->read_size;
GridLineEvent *g = p->grid_line_event;
-// -V:NEXT_TYPE:501
#define NEXT_TYPE(tok, typ) \
result = mpack_rtoken(&data, &size, &tok); \
if (result == MPACK_EOF) { \
@@ -394,7 +389,6 @@ bool unpacker_parse_redraw(Unpacker *p)
return false; \
}
-redo:
switch (p->state) {
case 10:
NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
@@ -440,7 +434,7 @@ redo:
case 14:
NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
int eventarrsize = (int)tok.length;
- if (eventarrsize != 4) {
+ if (eventarrsize != 5) {
p->state = -1;
return false;
}
@@ -462,58 +456,64 @@ redo:
FALLTHROUGH;
case 15:
- assert(g->icell < g->ncells);
-
- NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
- int cellarrsize = (int)tok.length;
- if (cellarrsize < 1 || cellarrsize > 3) {
- p->state = -1;
- return false;
- }
+ for (; g->icell != g->ncells; g->icell++) {
+ assert(g->icell < g->ncells);
+
+ NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
+ int cellarrsize = (int)tok.length;
+ if (cellarrsize < 1 || cellarrsize > 3) {
+ p->state = -1;
+ return false;
+ }
- NEXT_TYPE(tok, MPACK_TOKEN_STR);
- if (tok.length > size) {
- return false;
- }
+ NEXT_TYPE(tok, MPACK_TOKEN_STR);
+ if (tok.length > size) {
+ return false;
+ }
- const char *cellbuf = data;
- size_t cellsize = tok.length;
- data += cellsize;
- size -= cellsize;
+ const char *cellbuf = data;
+ size_t cellsize = tok.length;
+ data += cellsize;
+ size -= cellsize;
- if (cellarrsize >= 2) {
- NEXT_TYPE(tok, MPACK_TOKEN_SINT);
- g->cur_attr = (int)tok.data.value.lo;
- }
+ if (cellarrsize >= 2) {
+ NEXT_TYPE(tok, MPACK_TOKEN_SINT);
+ g->cur_attr = (int)tok.data.value.lo;
+ }
- int repeat = 1;
- if (cellarrsize >= 3) {
- NEXT_TYPE(tok, MPACK_TOKEN_UINT);
- repeat = (int)tok.data.value.lo;
- }
+ int repeat = 1;
+ if (cellarrsize >= 3) {
+ NEXT_TYPE(tok, MPACK_TOKEN_UINT);
+ repeat = (int)tok.data.value.lo;
+ }
- g->clear_width = 0;
- if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) {
- g->clear_width = repeat;
- } else {
- for (int r = 0; r < repeat; r++) {
- if (g->coloff >= (int)grid_line_buf_size) {
- p->state = -1;
- return false;
+ g->clear_width = 0;
+ if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) {
+ g->clear_width = repeat;
+ } else {
+ schar_T sc = schar_from_buf(cellbuf, cellsize);
+ for (int r = 0; r < repeat; r++) {
+ if (g->coloff >= (int)grid_line_buf_size) {
+ p->state = -1;
+ return false;
+ }
+ grid_line_buf_char[g->coloff] = sc;
+ grid_line_buf_attr[g->coloff++] = g->cur_attr;
}
- memcpy(grid_line_buf_char[g->coloff], cellbuf, cellsize);
- grid_line_buf_char[g->coloff][cellsize] = NUL;
- grid_line_buf_attr[g->coloff++] = g->cur_attr;
}
+
+ p->read_ptr = data;
+ p->read_size = size;
}
+ p->state = 16;
+ FALLTHROUGH;
- g->icell++;
+ case 16:
+ NEXT_TYPE(tok, MPACK_TOKEN_BOOLEAN);
+ g->wrap = mpack_unpack_boolean(tok);
p->read_ptr = data;
p->read_size = size;
- if (g->icell == g->ncells) {
- return true;
- }
- goto redo;
+ return true;
case 12:
return true;
diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h
index b8b2d38d3b..53af29761e 100644
--- a/src/nvim/msgpack_rpc/unpacker.h
+++ b/src/nvim/msgpack_rpc/unpacker.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MSGPACK_RPC_UNPACKER_H
-#define NVIM_MSGPACK_RPC_UNPACKER_H
+#pragma once
#include <inttypes.h>
#include <stdbool.h>
@@ -11,9 +10,9 @@
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/grid_defs.h"
-#include "nvim/memory.h"
+#include "nvim/memory_defs.h"
#include "nvim/msgpack_rpc/channel_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui_client.h"
struct Unpacker {
@@ -49,5 +48,3 @@ struct Unpacker {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/unpacker.h.generated.h"
#endif
-
-#endif // NVIM_MSGPACK_RPC_UNPACKER_H
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 58a18ca5a8..1f789dc153 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// normal.c: Contains the main routine for processing characters in command
// mode. Communicates closely with the code in ops.c to handle
@@ -17,9 +14,8 @@
#include <string.h>
#include <time.h>
-#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -38,14 +34,15 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -57,12 +54,12 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
@@ -74,10 +71,9 @@
#include "nvim/tag.h"
#include "nvim/textformat.h"
#include "nvim/textobject.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
typedef struct normal_state {
@@ -107,6 +103,10 @@ static int VIsual_mode_orig = NUL; // saved Visual mode
# include "normal.c.generated.h"
#endif
+static const char e_changelist_is_empty[] = N_("E664: Changelist is empty");
+static const char e_cmdline_window_already_open[]
+ = N_("E1292: Command-line window is already open");
+
static inline void normal_state_init(NormalState *s)
{
memset(s, 0, sizeof(NormalState));
@@ -118,7 +118,7 @@ static inline void normal_state_init(NormalState *s)
// n_*(): functions called to handle Normal mode commands.
// v_*(): functions called to handle Visual mode commands.
-static char *e_noident = N_("E349: No identifier under cursor");
+static const char *e_noident = N_("E349: No identifier under cursor");
/// Function to be called for a Normal or Visual mode command.
/// The argument is a cmdarg_T.
@@ -359,11 +359,9 @@ static int nv_max_linear;
/// through the index in nv_cmd_idx[].
static int nv_compare(const void *s1, const void *s2)
{
- int c1, c2;
-
// The commands are sorted on absolute value.
- c1 = nv_cmds[*(const int16_t *)s1].cmd_char;
- c2 = nv_cmds[*(const int16_t *)s2].cmd_char;
+ int c1 = nv_cmds[*(const int16_t *)s1].cmd_char;
+ int c2 = nv_cmds[*(const int16_t *)s2].cmd_char;
if (c1 < 0) {
c1 = -c1;
}
@@ -401,11 +399,6 @@ void init_normal_cmds(void)
/// @return -1 for invalid command.
static int find_command(int cmdchar)
{
- int i;
- int idx;
- int top, bot;
- int c;
-
// A multi-byte character is never a command.
if (cmdchar >= 0x100) {
return -1;
@@ -425,12 +418,12 @@ static int find_command(int cmdchar)
}
// Perform a binary search.
- bot = nv_max_linear + 1;
- top = NV_CMDS_SIZE - 1;
- idx = -1;
+ int bot = nv_max_linear + 1;
+ int top = NV_CMDS_SIZE - 1;
+ int idx = -1;
while (bot <= top) {
- i = (top + bot) / 2;
- c = nv_cmds[nv_cmd_idx[i]].cmd_char;
+ int i = (top + bot) / 2;
+ int c = nv_cmds[nv_cmd_idx[i]].cmd_char;
if (c < 0) {
c = -c;
}
@@ -465,7 +458,7 @@ static bool check_text_locked(oparg_T *oap)
/// If text is locked, "curbuf->b_ro_locked" or "allbuf_lock" is set:
/// Give an error message, possibly beep and return true.
/// "oap" may be NULL.
-static bool check_text_or_curbuf_locked(oparg_T *oap)
+bool check_text_or_curbuf_locked(oparg_T *oap)
{
if (check_text_locked(oap)) {
return true;
@@ -481,6 +474,20 @@ static bool check_text_or_curbuf_locked(oparg_T *oap)
return true;
}
+static oparg_T *current_oap = NULL;
+
+/// Check if an operator was started but not finished yet.
+/// Includes typing a count or a register name.
+bool op_pending(void)
+{
+ return !(current_oap != NULL
+ && !finish_op
+ && current_oap->prev_opcount == 0
+ && current_oap->prev_count0 == 0
+ && current_oap->op_type == OP_NOP
+ && current_oap->regname == NUL);
+}
+
/// Normal state entry point. This is called on:
///
/// - Startup, In this case the function never returns.
@@ -489,15 +496,18 @@ static bool check_text_or_curbuf_locked(oparg_T *oap)
/// for example. Returns when re-entering ex mode(because ex mode recursion is
/// not allowed)
///
-/// This used to be called main_loop on main.c
+/// This used to be called main_loop() on main.c
void normal_enter(bool cmdwin, bool noexmode)
{
NormalState state;
normal_state_init(&state);
+ oparg_T *prev_oap = current_oap;
+ current_oap = &state.oa;
state.cmdwin = cmdwin;
state.noexmode = noexmode;
state.toplevel = (!cmdwin || cmdwin_result == 0) && !noexmode;
state_enter(&state.state);
+ current_oap = prev_oap;
}
static void normal_prepare(NormalState *s)
@@ -671,16 +681,16 @@ static void normal_redraw_mode_message(NormalState *s)
keep_msg = kmsg;
kmsg = xstrdup(keep_msg);
- msg_attr((const char *)kmsg, keep_msg_attr);
+ msg(kmsg, keep_msg_attr);
xfree(kmsg);
}
setcursor();
ui_cursor_shape(); // show different cursor shape
ui_flush();
if (msg_scroll || emsg_on_display) {
- os_delay(1003L, true); // wait at least one second
+ os_delay(1003, true); // wait at least one second
}
- os_delay(3003L, false); // wait up to three seconds
+ os_delay(3003, false); // wait up to three seconds
State = save_State;
msg_scroll = false;
@@ -693,7 +703,6 @@ static void normal_get_additional_char(NormalState *s)
int *cp;
bool repl = false; // get character for replace mode
bool lit = false; // get extra character literally
- bool langmap_active = false; // using :lmap mappings
int lang; // getting a text character
no_mapping++;
@@ -729,6 +738,7 @@ static void normal_get_additional_char(NormalState *s)
// Get a second or third character.
if (cp != NULL) {
+ bool langmap_active = false; // using :lmap mappings
if (repl) {
State = MODE_REPLACE; // pretend Replace mode
ui_cursor_shape(); // show different cursor shape
@@ -771,10 +781,6 @@ static void normal_get_additional_char(NormalState *s)
// adjust chars > 127, except after "tTfFr" commands
LANGMAP_ADJUST(*cp, !lang);
- // adjust Hebrew mapped char
- if (p_hkmap && lang && KeyTyped) {
- *cp = hkmap(*cp);
- }
}
// When the next character is CTRL-\ a following CTRL-N means the
@@ -789,13 +795,13 @@ static void normal_get_additional_char(NormalState *s)
&& s->ca.cmdchar == 'g') {
s->ca.oap->op_type = get_op_type(*cp, NUL);
} else if (*cp == Ctrl_BSL) {
- long towait = (p_ttm >= 0 ? p_ttm : p_tm);
+ int towait = (p_ttm >= 0 ? (int)p_ttm : (int)p_tm);
// There is a busy wait here when typing "f<C-\>" and then
// something different from CTRL-N. Can't be avoided.
- while ((s->c = vpeekc()) <= 0 && towait > 0L) {
- do_sleep(towait > 50L ? 50L : towait);
- towait -= 50L;
+ while ((s->c = vpeekc()) <= 0 && towait > 0) {
+ do_sleep(towait > 50 ? 50 : towait);
+ towait -= 50;
}
if (s->c > 0) {
s->c = plain_vgetc();
@@ -810,25 +816,34 @@ static void normal_get_additional_char(NormalState *s)
}
}
- // When getting a text character and the next character is a
- // multi-byte character, it could be a composing character.
- // However, don't wait for it to arrive. Also, do enable mapping,
- // because if it's put back with vungetc() it's too late to apply
- // mapping.
- no_mapping--;
- while (lang && (s->c = vpeekc()) > 0
- && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
- s->c = plain_vgetc();
- if (!utf_iscomposing(s->c)) {
- vungetc(s->c); // it wasn't, put it back
- break;
- } else if (s->ca.ncharC1 == 0) {
- s->ca.ncharC1 = s->c;
- } else {
- s->ca.ncharC2 = s->c;
+ if (lang) {
+ // When getting a text character and the next character is a
+ // multi-byte character, it could be a composing character.
+ // However, don't wait for it to arrive. Also, do enable mapping,
+ // because if it's put back with vungetc() it's too late to apply
+ // mapping.
+ no_mapping--;
+ while ((s->c = vpeekc()) > 0
+ && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
+ s->c = plain_vgetc();
+ if (!utf_iscomposing(s->c)) {
+ vungetc(s->c); // it wasn't, put it back
+ break;
+ } else if (s->ca.ncharC1 == 0) {
+ s->ca.ncharC1 = s->c;
+ } else {
+ s->ca.ncharC2 = s->c;
+ }
}
+ no_mapping++;
+ // Vim may be in a different mode when the user types the next key,
+ // but when replaying a recording the next key is already in the
+ // typeahead buffer, so record a <Nop> before that to prevent the
+ // vpeekc() above from applying wrong mappings when replaying.
+ no_u_sync++;
+ gotchars_nop();
+ no_u_sync--;
}
- no_mapping++;
}
no_mapping--;
allow_keys--;
@@ -874,8 +889,8 @@ static bool normal_get_command_count(NormalState *s)
if (s->c == K_DEL || s->c == K_KDEL) {
s->ca.count0 /= 10;
del_from_showcmd(4); // delete the digit and ~@%
- } else if (s->ca.count0 > 99999999L) {
- s->ca.count0 = 999999999L;
+ } else if (s->ca.count0 > 99999999) {
+ s->ca.count0 = 999999999;
} else {
s->ca.count0 = s->ca.count0 * 10 + (s->c - '0');
}
@@ -963,13 +978,16 @@ normal_end:
set_reg_var(get_default_register_name());
}
- // Reset finish_op, in case it was set
- s->c = finish_op;
- finish_op = false;
- may_trigger_modechanged();
+ const bool prev_finish_op = finish_op;
+ if (s->oa.op_type == OP_NOP) {
+ // Reset finish_op, in case it was set
+ finish_op = false;
+ may_trigger_modechanged();
+ }
// Redraw the cursor with another shape, if we were in Operator-pending
// mode or did a replace command.
- if (s->c || s->ca.cmdchar == 'r') {
+ if (prev_finish_op || s->ca.cmdchar == 'r'
+ || (s->ca.cmdchar == 'g' && s->ca.nchar == 'r')) {
ui_cursor_shape(); // may show different cursor shape
}
@@ -1010,7 +1028,7 @@ normal_end:
restart_VIsual_select = 0;
}
if (restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0) {
- (void)edit(restart_edit, false, 1L);
+ (void)edit(restart_edit, false, 1);
}
}
@@ -1088,8 +1106,8 @@ static int normal_execute(VimState *state, int key)
// If you give a count before AND after the operator, they are
// multiplied.
if (s->ca.count0) {
- if (s->ca.opcount >= 999999999L / s->ca.count0) {
- s->ca.count0 = 999999999L;
+ if (s->ca.opcount >= 999999999 / s->ca.count0) {
+ s->ca.count0 = 999999999;
} else {
s->ca.count0 *= s->ca.opcount;
}
@@ -1168,7 +1186,7 @@ static int normal_execute(VimState *state, int key)
State = MODE_NORMAL;
- if (s->ca.nchar == ESC) {
+ if (s->ca.nchar == ESC || s->ca.extra_char == ESC) {
clearop(&s->oa);
s->command_finished = true;
goto finish;
@@ -1179,7 +1197,7 @@ static int normal_execute(VimState *state, int key)
msg_col = 0;
}
- s->old_pos = curwin->w_cursor; // remember where cursor was
+ s->old_pos = curwin->w_cursor; // remember where the cursor was
// When 'keymodel' contains "startsel" some keys start Select/Visual
// mode.
@@ -1260,9 +1278,11 @@ static void normal_check_cursor_moved(NormalState *s)
{
// Trigger CursorMoved if the cursor moved.
if (!finish_op && has_event(EVENT_CURSORMOVED)
- && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)) {
+ && (last_cursormoved_win != curwin
+ || !equalpos(last_cursormoved, curwin->w_cursor))) {
apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf);
- curwin->w_last_cursormoved = curwin->w_cursor;
+ last_cursormoved_win = curwin;
+ last_cursormoved = curwin->w_cursor;
}
}
@@ -1286,6 +1306,13 @@ static void normal_check_buffer_modified(NormalState *s)
}
}
+/// If nothing is pending and we are going to wait for the user to
+/// type a character, trigger SafeState.
+static void normal_check_safe_state(NormalState *s)
+{
+ may_trigger_safestate(!op_pending() && restart_edit == 0);
+}
+
static void normal_check_folds(NormalState *s)
{
// Include a closed fold completely in the Visual area.
@@ -1311,16 +1338,20 @@ static void normal_redraw(NormalState *s)
update_topline(curwin);
validate_cursor();
+ show_cursor_info_later(false);
+
if (VIsual_active) {
redraw_curbuf_later(UPD_INVERTED); // update inverted part
- update_screen();
- } else if (must_redraw) {
- update_screen();
- } else if (redraw_cmdline || clear_cmdline || redraw_mode) {
- showmode();
}
- redraw_statuslines();
+ if (must_redraw) {
+ update_screen();
+ } else {
+ redraw_statuslines();
+ if (redraw_cmdline || clear_cmdline || redraw_mode) {
+ showmode();
+ }
+ }
if (need_maketitle) {
maketitle();
@@ -1338,7 +1369,7 @@ static void normal_redraw(NormalState *s)
// check for duplicates. Never put this message in
// history.
msg_hist_off = true;
- msg_attr((const char *)p, keep_msg_attr);
+ msg(p, keep_msg_attr);
msg_hist_off = false;
xfree(p);
}
@@ -1353,7 +1384,6 @@ static void normal_redraw(NormalState *s)
did_emsg = false;
msg_didany = false; // reset lines_left in msg_start()
may_clear_sb_text(); // clear scroll-back text on next msg
- show_cursor_info(false);
setcursor();
}
@@ -1375,11 +1405,14 @@ static int normal_check(VimState *state)
}
quit_more = false;
+ state_no_longer_safe(NULL);
+
// If skip redraw is set (for ":" in wait_return()), don't redraw now.
// If there is nothing in the stuff_buffer or do_redraw is true,
// update cursor and redraw.
if (skip_redraw || exmode_active) {
skip_redraw = false;
+ setcursor();
} else if (do_redraw || stuff_empty()) {
// Ensure curwin->w_topline and curwin->w_leftcol are up to date
// before triggering a WinScrolled autocommand.
@@ -1390,6 +1423,7 @@ static int normal_check(VimState *state)
normal_check_text_changed(s);
normal_check_window_scrolled(s);
normal_check_buffer_modified(s);
+ normal_check_safe_state(s);
// Updating diffs from changed() does not always work properly,
// esp. updating folds. Do an update just before redrawing if
@@ -1402,7 +1436,7 @@ static int normal_check(VimState *state)
// Scroll-binding for diff mode may have been postponed until
// here. Avoids doing it for every change.
if (diff_need_scrollbind) {
- check_scrollbind((linenr_T)0, 0L);
+ check_scrollbind(0, 0);
diff_need_scrollbind = false;
}
@@ -1455,7 +1489,7 @@ static int normal_check(VimState *state)
/// Set v:prevcount only when "set_prevcount" is true.
static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount)
{
- long count = cap->count0;
+ int64_t count = cap->count0;
// multiply with cap->opcount the same way as above
if (cap->opcount != 0) {
@@ -1597,7 +1631,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text
// if i == 0: try to find an identifier
// if i == 1: try to find any non-white text
- char *ptr = ml_get_buf(wp->w_buffer, lnum, false);
+ char *ptr = ml_get_buf(wp->w_buffer, lnum);
for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; i++) {
// 1. skip to start of identifier/text
col = startcol;
@@ -1671,7 +1705,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text
col = 0;
// Search for point of changing multibyte character class.
this_class = mb_get_class(ptr);
- while (ptr[col] != NUL // -V781
+ while (ptr[col] != NUL
&& ((i == 0
? mb_get_class(ptr + col) == this_class
: mb_get_class(ptr + col) != 0)
@@ -1694,13 +1728,13 @@ static void prep_redo_cmd(cmdarg_T *cap)
/// Prepare for redo of any command.
/// Note that only the last argument can be a multi-byte char.
-void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5)
+void prep_redo(int regname, int num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5)
{
- prep_redo_num2(regname, num, cmd1, cmd2, 0L, cmd3, cmd4, cmd5);
+ prep_redo_num2(regname, num, cmd1, cmd2, 0, cmd3, cmd4, cmd5);
}
/// Prepare for redo of any command with extra count after "cmd2".
-void prep_redo_num2(int regname, long num1, int cmd1, int cmd2, long num2, int cmd3, int cmd4,
+void prep_redo_num2(int regname, int num1, int cmd1, int cmd2, int num2, int cmd3, int cmd4,
int cmd5)
{
ResetRedobuff();
@@ -1731,9 +1765,9 @@ void prep_redo_num2(int regname, long num1, int cmd1, int cmd2, long num2, int c
}
}
-/// check for operator active and clear it
+/// Check for operator active and clear it.
///
-/// @return true if operator was active
+/// Beep and return true if an operator was active.
static bool checkclearop(oparg_T *oap)
{
if (oap->op_type == OP_NOP) {
@@ -1745,7 +1779,7 @@ static bool checkclearop(oparg_T *oap)
/// Check for operator or Visual active. Clear active operator.
///
-/// @return true if operator or Visual was active.
+/// Beep and return true if an operator or Visual was active.
static bool checkclearopq(oparg_T *oap)
{
if (oap->op_type == OP_NOP && !VIsual_active) {
@@ -1815,7 +1849,7 @@ void clear_showcmd(void)
if (VIsual_active && !char_avail()) {
int cursor_bot = lt(VIsual, curwin->w_cursor);
- long lines;
+ int lines;
colnr_T leftcol, rightcol;
linenr_T top, bot;
@@ -1837,8 +1871,8 @@ void clear_showcmd(void)
char *const saved_w_sbr = curwin->w_p_sbr;
// Make 'sbr' empty for a moment to get the correct size.
- p_sbr = empty_option;
- curwin->w_p_sbr = empty_option;
+ p_sbr = empty_string_option;
+ curwin->w_p_sbr = empty_string_option;
getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol);
p_sbr = saved_sbr;
curwin->w_p_sbr = saved_w_sbr;
@@ -1848,7 +1882,6 @@ void clear_showcmd(void)
snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64, (int64_t)lines);
} else {
char *s, *e;
- int l;
int bytes = 0;
int chars = 0;
@@ -1860,7 +1893,7 @@ void clear_showcmd(void)
e = ml_get_pos(&VIsual);
}
while ((*p_sel != 'e') ? s <= e : s < e) {
- l = utfc_ptr2len(s);
+ int l = utfc_ptr2len(s);
if (l == 0) {
bytes++;
chars++;
@@ -1957,13 +1990,11 @@ void add_to_showcmd_c(int c)
/// Delete 'len' characters from the end of the shown command.
static void del_from_showcmd(int len)
{
- int old_len;
-
if (!p_sc) {
return;
}
- old_len = (int)strlen(showcmd_buf);
+ int old_len = (int)strlen(showcmd_buf);
if (len > old_len) {
len = old_len;
}
@@ -2000,13 +2031,21 @@ static void display_showcmd(void)
showcmd_is_clear = (len == 0);
if (*p_sloc == 's') {
- win_redr_status(curwin);
- setcursor(); // put cursor back where it belongs
+ if (showcmd_is_clear) {
+ curwin->w_redr_status = true;
+ } else {
+ win_redr_status(curwin);
+ setcursor(); // put cursor back where it belongs
+ }
return;
}
if (*p_sloc == 't') {
- draw_tabline();
- setcursor(); // put cursor back where it belongs
+ if (showcmd_is_clear) {
+ redraw_tabline = true;
+ } else {
+ draw_tabline();
+ setcursor(); // put cursor back where it belongs
+ }
return;
}
// 'showcmdloc' is "last" or empty
@@ -2020,7 +2059,7 @@ static void display_showcmd(void)
if (len > 0) {
// placeholder for future highlight support
ADD_C(chunk, INTEGER_OBJ(0));
- ADD_C(chunk, STRING_OBJ(cstr_as_string(showcmd_buf)));
+ ADD_C(chunk, CSTR_AS_OBJ(showcmd_buf));
ADD_C(content, ARRAY_OBJ(chunk));
}
ui_call_msg_showcmd(content);
@@ -2029,18 +2068,16 @@ static void display_showcmd(void)
msg_grid_validate();
int showcmd_row = Rows - 1;
- grid_puts_line_start(&msg_grid_adj, showcmd_row);
+ grid_line_start(&msg_grid_adj, showcmd_row);
if (!showcmd_is_clear) {
- grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col,
- HL_ATTR(HLF_MSG));
+ grid_line_puts(sc_col, showcmd_buf, -1, HL_ATTR(HLF_MSG));
}
// clear the rest of an old message by outputting up to SHOWCMD_COLS spaces
- grid_puts(&msg_grid_adj, (char *)" " + len, showcmd_row,
- sc_col + len, HL_ATTR(HLF_MSG));
+ grid_line_puts(sc_col + len, (char *)" " + len, -1, HL_ATTR(HLF_MSG));
- grid_puts_line_flush(false);
+ grid_line_flush();
}
/// When "check" is false, prepare for commands that scroll the window.
@@ -2069,8 +2106,7 @@ void do_check_scrollbind(bool check)
&& (curwin->w_topline != old_topline
|| curwin->w_topfill != old_topfill
|| curwin->w_leftcol != old_leftcol)) {
- check_scrollbind(curwin->w_topline - old_topline,
- (long)(curwin->w_leftcol - old_leftcol));
+ check_scrollbind(curwin->w_topline - old_topline, curwin->w_leftcol - old_leftcol);
}
} else if (vim_strchr(p_sbo, 'j')) { // jump flag set in 'scrollopt'
// When switching between windows, make sure that the relative
@@ -2081,7 +2117,7 @@ void do_check_scrollbind(bool check)
// resync is performed, some of the other 'scrollbind' windows may
// need to jump so that the current window's relative position is
// visible on-screen.
- check_scrollbind(curwin->w_topline - (linenr_T)curwin->w_scbind_pos, 0L);
+ check_scrollbind(curwin->w_topline - (linenr_T)curwin->w_scbind_pos, 0);
}
curwin->w_scbind_pos = curwin->w_topline;
}
@@ -2096,22 +2132,20 @@ void do_check_scrollbind(bool check)
/// Synchronize any windows that have "scrollbind" set, based on the
/// number of rows by which the current window has changed
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
-void check_scrollbind(linenr_T topline_diff, long leftcol_diff)
+void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
{
- bool want_ver;
- bool want_hor;
win_T *old_curwin = curwin;
buf_T *old_curbuf = curbuf;
int old_VIsual_select = VIsual_select;
int old_VIsual_active = VIsual_active;
colnr_T tgt_leftcol = curwin->w_leftcol;
- long topline;
- long y;
+ linenr_T topline;
+ linenr_T y;
// check 'scrollopt' string for vertical and horizontal scroll options
- want_ver = (vim_strchr(p_sbo, 'v') && topline_diff != 0);
+ bool want_ver = (vim_strchr(p_sbo, 'v') && topline_diff != 0);
want_ver |= old_curwin->w_p_diff;
- want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || topline_diff != 0));
+ bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || topline_diff != 0));
// loop through the scrollbound windows and scroll accordingly
VIsual_select = VIsual_active = 0;
@@ -2129,7 +2163,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff)
diff_set_topline(old_curwin, curwin);
} else {
curwin->w_scbind_pos += topline_diff;
- topline = curwin->w_scbind_pos;
+ topline = (linenr_T)curwin->w_scbind_pos;
if (topline > curbuf->b_ml.ml_line_count) {
topline = curbuf->b_ml.ml_line_count;
}
@@ -2151,9 +2185,8 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff)
}
// do the horizontal scroll
- if (want_hor && curwin->w_leftcol != tgt_leftcol) {
- curwin->w_leftcol = tgt_leftcol;
- leftcol_changed();
+ if (want_hor) {
+ (void)set_leftcol(tgt_leftcol);
}
}
@@ -2175,7 +2208,8 @@ static void nv_ignore(cmdarg_T *cap)
/// Command character that doesn't do anything, but unlike nv_ignore() does
/// start edit(). Used for "startinsert" executed while starting up.
static void nv_nop(cmdarg_T *cap)
-{}
+{
+}
/// Command character doesn't exist.
static void nv_error(cmdarg_T *cap)
@@ -2199,7 +2233,7 @@ static void nv_addsub(cmdarg_T *cap)
} else if (!VIsual_active && cap->oap->op_type == OP_NOP) {
prep_redo_cmd(cap);
cap->oap->op_type = cap->cmdchar == Ctrl_A ? OP_NR_ADD : OP_NR_SUB;
- op_addsub(cap->oap, (linenr_T)cap->count1, cap->arg);
+ op_addsub(cap->oap, cap->count1, cap->arg);
cap->oap->op_type = OP_NOP;
} else if (VIsual_active) {
nv_operator(cap);
@@ -2218,9 +2252,9 @@ static void nv_page(cmdarg_T *cap)
if (mod_mask & MOD_MASK_CTRL) {
// <C-PageUp>: tab page back; <C-PageDown>: tab page forward
if (cap->arg == BACKWARD) {
- goto_tabpage(-(int)cap->count1);
+ goto_tabpage(-cap->count1);
} else {
- goto_tabpage((int)cap->count0);
+ goto_tabpage(cap->count0);
}
} else {
(void)onepage(cap->arg, cap->count1);
@@ -2292,34 +2326,30 @@ static bool is_ident(const char *line, int offset)
/// @return fail when not found.
bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_arg)
{
- char *pat;
- pos_T old_pos;
pos_T par_pos;
pos_T found_pos;
bool t;
- bool save_p_ws;
- bool save_p_scs;
bool retval = true;
bool incll;
int searchflags = flags_arg;
- pat = xmalloc(len + 7);
+ size_t patlen = len + 7;
+ char *pat = xmalloc(patlen);
// Put "\V" before the pattern to avoid that the special meaning of "."
// and "~" causes trouble.
- assert(len <= INT_MAX);
- sprintf(pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", // NOLINT(runtime/printf)
- (int)len, ptr);
- old_pos = curwin->w_cursor;
- save_p_ws = p_ws;
- save_p_scs = p_scs;
+ assert(patlen <= INT_MAX);
+ snprintf(pat, patlen, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", (int)len, ptr);
+ pos_T old_pos = curwin->w_cursor;
+ bool save_p_ws = p_ws;
+ bool save_p_scs = p_scs;
p_ws = false; // don't wrap around end of file now
p_scs = false; // don't switch ignorecase off now
// With "gD" go to line 1.
// With "gd" Search back for the start of the current function, then go
// back until a blank line. If this fails go to line 1.
- if (!locally || !findpar(&incll, BACKWARD, 1L, '{', false)) {
+ if (!locally || !findpar(&incll, BACKWARD, 1, '{', false)) {
setpcmark(); // Set in findpar() otherwise
curwin->w_cursor.lnum = 1;
par_pos = curwin->w_cursor;
@@ -2334,9 +2364,9 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
// Search forward for the identifier, ignore comment lines.
clearpos(&found_pos);
- for (;;) {
+ while (true) {
t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD,
- pat, 1L, searchflags, RE_LAST, NULL);
+ pat, 1, searchflags, RE_LAST, NULL);
if (curwin->w_cursor.lnum >= old_pos.lnum) {
t = false; // match after start is failure too
}
@@ -2422,12 +2452,11 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
/// 'dist' must be positive.
///
/// @return true if able to move cursor, false otherwise.
-static bool nv_screengo(oparg_T *oap, int dir, long dist)
+static bool nv_screengo(oparg_T *oap, int dir, int dist)
{
- int linelen = linetabsize(get_cursor_line_ptr());
+ int linelen = linetabsize(curwin, curwin->w_cursor.lnum);
bool retval = true;
bool atend = false;
- int n;
int col_off1; // margin offset for first screen line
int col_off2; // margin offset for wrapped screen line
int width1; // text width for first screen line
@@ -2446,6 +2475,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
}
if (curwin->w_width_inner != 0) {
+ int n;
// Instead of sticking at the last character of the buffer line we
// try to stick in the last column of the screen.
if (curwin->w_curswant == MAXCOL) {
@@ -2482,20 +2512,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
curwin->w_curswant -= width2;
} else {
// to previous line
-
- // Move to the start of a closed fold. Don't do that when
- // 'foldopen' contains "all": it will open in a moment.
- if (!(fdo_flags & FDO_ALL)) {
- (void)hasFolding(curwin->w_cursor.lnum,
- &curwin->w_cursor.lnum, NULL);
- }
- if (curwin->w_cursor.lnum == 1) {
+ if (curwin->w_cursor.lnum <= 1) {
retval = false;
break;
}
- curwin->w_cursor.lnum--;
+ cursor_up_inner(curwin, 1);
- linelen = linetabsize(get_cursor_line_ptr());
+ linelen = linetabsize(curwin, curwin->w_cursor.lnum);
if (linelen > width1) {
int w = (((linelen - width1 - 1) / width2) + 1) * width2;
assert(curwin->w_curswant <= INT_MAX - w);
@@ -2514,16 +2537,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
curwin->w_curswant += width2;
} else {
// to next line
-
- // Move to the end of a closed fold.
- (void)hasFolding(curwin->w_cursor.lnum, NULL,
- &curwin->w_cursor.lnum);
- if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
+ if (curwin->w_cursor.lnum >= curwin->w_buffer->b_ml.ml_line_count) {
retval = false;
break;
}
- curwin->w_cursor.lnum++;
+ cursor_down_inner(curwin, 1);
curwin->w_curswant %= width2;
+
// Check if the cursor has moved below the number display
// when width1 < width2 (with cpoptions+=n). Subtract width2
// to get a negative value for w_curswant, which will get
@@ -2531,7 +2551,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
if (curwin->w_curswant >= width1) {
curwin->w_curswant -= width2;
}
- linelen = linetabsize(get_cursor_line_ptr());
+ linelen = linetabsize(curwin, curwin->w_cursor.lnum);
}
}
}
@@ -2572,63 +2592,14 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
if (atend) {
curwin->w_curswant = MAXCOL; // stick in the last column
}
- return retval;
-}
-
-/// Mouse scroll wheel: Default action is to scroll three lines, or one page
-/// when Shift or Ctrl is used.
-/// K_MOUSEUP (cap->arg == 1) or K_MOUSEDOWN (cap->arg == 0) or
-/// K_MOUSELEFT (cap->arg == -1) or K_MOUSERIGHT (cap->arg == -2)
-static void nv_mousescroll(cmdarg_T *cap)
-{
- win_T *old_curwin = curwin;
-
- if (mouse_row >= 0 && mouse_col >= 0) {
- int grid, row, col;
-
- grid = mouse_grid;
- row = mouse_row;
- col = mouse_col;
-
- // find the window at the pointer coordinates
- win_T *wp = mouse_find_win(&grid, &row, &col);
- if (wp == NULL) {
- return;
- }
- curwin = wp;
- curbuf = curwin->w_buffer;
- }
+ adjust_skipcol();
- if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) {
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L);
- } else if (p_mousescroll_vert > 0) {
- cap->count1 = p_mousescroll_vert;
- cap->count0 = p_mousescroll_vert;
- nv_scroll_line(cap);
- }
- } else {
- mouse_scroll_horiz(cap->arg);
- }
- if (curwin != old_curwin && curwin->w_p_cul) {
- redraw_for_cursorline(curwin);
- }
-
- curwin->w_redr_status = true;
-
- curwin = old_curwin;
- curbuf = curwin->w_buffer;
-}
-
-/// Mouse clicks and drags.
-static void nv_mouse(cmdarg_T *cap)
-{
- (void)do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0);
+ return retval;
}
/// Handle CTRL-E and CTRL-Y commands: scroll a line up or down.
/// cap->arg must be true for CTRL-E.
-static void nv_scroll_line(cmdarg_T *cap)
+void nv_scroll_line(cmdarg_T *cap)
{
if (!checkclearop(cap->oap)) {
scroll_redraw(cap->arg, cap->count1);
@@ -2636,17 +2607,18 @@ static void nv_scroll_line(cmdarg_T *cap)
}
/// Scroll "count" lines up or down, and redraw.
-void scroll_redraw(int up, long count)
+void scroll_redraw(int up, linenr_T count)
{
linenr_T prev_topline = curwin->w_topline;
+ int prev_skipcol = curwin->w_skipcol;
int prev_topfill = curwin->w_topfill;
linenr_T prev_lnum = curwin->w_cursor.lnum;
- bool moved = up ?
- scrollup(count, true) :
- scrolldown(count, true);
+ bool moved = up
+ ? scrollup(count, true)
+ : scrolldown(count, true);
- if (get_scrolloff_value(curwin)) {
+ if (get_scrolloff_value(curwin) > 0) {
// Adjust the cursor position for 'scrolloff'. Mark w_topline as
// valid, otherwise the screen jumps back at the end of the file.
cursor_correct();
@@ -2657,16 +2629,17 @@ void scroll_redraw(int up, long count)
// we get stuck at one position. Don't move the cursor up if the
// first line of the buffer is already on the screen
while (curwin->w_topline == prev_topline
+ && curwin->w_skipcol == prev_skipcol
&& curwin->w_topfill == prev_topfill) {
if (up) {
if (curwin->w_cursor.lnum > prev_lnum
- || cursor_down(1L, false) == false) {
+ || cursor_down(1, false) == false) {
break;
}
} else {
if (curwin->w_cursor.lnum < prev_lnum
- || prev_topline == 1L
- || cursor_up(1L, false) == false) {
+ || prev_topline == 1
+ || cursor_up(1, false) == false) {
break;
}
}
@@ -2696,9 +2669,9 @@ static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg)
if (checkclearop(cap->oap)) {
return false;
}
- long n = nchar - '0';
+ int n = nchar - '0';
- for (;;) {
+ while (true) {
no_mapping++;
allow_keys++; // no mapping for nchar, but allow key codes
nchar = plain_vgetc();
@@ -2710,9 +2683,13 @@ static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg)
if (nchar == K_DEL || nchar == K_KDEL) {
n /= 10;
} else if (ascii_isdigit(nchar)) {
+ if (n > INT_MAX / 10) {
+ clearopbeep(cap->oap);
+ break;
+ }
n = n * 10 + (nchar - '0');
} else if (nchar == CAR) {
- win_setheight((int)n);
+ win_setheight(n);
break;
} else if (nchar == 'l'
|| nchar == 'h'
@@ -2784,7 +2761,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar)
assert(len <= INT_MAX);
spell_add_word(ptr, (int)len,
nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD,
- (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1,
+ (nchar == 'G' || nchar == 'W') ? 0 : cap->count1,
undo);
return OK;
@@ -2793,13 +2770,12 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar)
/// Commands that start with "z".
static void nv_zet(cmdarg_T *cap)
{
- int n;
colnr_T col;
int nchar = cap->nchar;
- long old_fdl = curwin->w_p_fdl;
+ int old_fdl = (int)curwin->w_p_fdl;
int old_fen = curwin->w_p_fen;
- int siso = (int)get_sidescrolloff_value(curwin);
+ int siso = get_sidescrolloff_value(curwin);
if (ascii_isdigit(nchar) && !nv_z_get_count(cap, &nchar)) {
return;
@@ -2824,7 +2800,7 @@ static void nv_zet(cmdarg_T *cap)
if (cap->count0 > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
} else {
- curwin->w_cursor.lnum = (linenr_T)cap->count0;
+ curwin->w_cursor.lnum = cap->count0;
}
check_cursor_col();
}
@@ -2860,7 +2836,7 @@ static void nv_zet(cmdarg_T *cap)
FALLTHROUGH;
case 'z':
- scroll_cursor_halfway(true);
+ scroll_cursor_halfway(true, false);
redraw_later(curwin, UPD_VALID);
set_fraction(curwin);
break;
@@ -2897,27 +2873,21 @@ static void nv_zet(cmdarg_T *cap)
case 'h':
case K_LEFT:
if (!curwin->w_p_wrap) {
- if ((colnr_T)cap->count1 > curwin->w_leftcol) {
- curwin->w_leftcol = 0;
- } else {
- curwin->w_leftcol -= (colnr_T)cap->count1;
- }
- leftcol_changed();
+ (void)set_leftcol((colnr_T)cap->count1 > curwin->w_leftcol
+ ? 0 : curwin->w_leftcol - (colnr_T)cap->count1);
}
break;
- // "zL" - scroll screen left half-page
+ // "zL" - scroll window left half-page
case 'L':
cap->count1 *= curwin->w_width_inner / 2;
FALLTHROUGH;
- // "zl" - scroll screen to the left
+ // "zl" - scroll window to the left if not wrapping
case 'l':
case K_RIGHT:
if (!curwin->w_p_wrap) {
- // scroll the window left
- curwin->w_leftcol += (colnr_T)cap->count1;
- leftcol_changed();
+ (void)set_leftcol(curwin->w_leftcol + (colnr_T)cap->count1);
}
break;
@@ -2949,7 +2919,7 @@ static void nv_zet(cmdarg_T *cap)
} else {
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
}
- n = curwin->w_width_inner - curwin_col_off();
+ int n = curwin->w_width_inner - curwin_col_off();
if (col + siso < n) {
col = 0;
} else {
@@ -3011,7 +2981,7 @@ static void nv_zet(cmdarg_T *cap)
clearFolding(curwin);
changed_window_setting();
} else if (foldmethodIsMarker(curwin)) {
- deleteFold(curwin, (linenr_T)1, curbuf->b_ml.ml_line_count, true, false);
+ deleteFold(curwin, 1, curbuf->b_ml.ml_line_count, true, false);
} else {
emsg(_("E352: Cannot erase folds with current 'foldmethod'"));
}
@@ -3165,7 +3135,7 @@ static void nv_zet(cmdarg_T *cap)
case '=': // "z=": suggestions for a badly spelled word
if (!checkclearop(cap->oap)) {
- spell_suggest((int)cap->count0);
+ spell_suggest(cap->count0);
}
break;
@@ -3230,7 +3200,7 @@ static void nv_colon(cmdarg_T *cap)
stuffcharReadbuff('.');
if (cap->count0 > 1) {
stuffReadbuff(",.+");
- stuffnumReadbuff(cap->count0 - 1L);
+ stuffnumReadbuff(cap->count0 - 1);
}
}
@@ -3240,7 +3210,7 @@ static void nv_colon(cmdarg_T *cap)
}
if (is_lua) {
- cmd_result = map_execute_lua();
+ cmd_result = map_execute_lua(true);
} else {
// get a command line and execute it
cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
@@ -3269,7 +3239,7 @@ static void nv_ctrlg(cmdarg_T *cap)
showmode();
} else if (!checkclearop(cap->oap)) {
// print full name if count given or :cd used
- fileinfo((int)cap->count0, false, true);
+ fileinfo(cap->count0, false, true);
}
}
@@ -3319,7 +3289,7 @@ static void nv_ctrlo(cmdarg_T *cap)
static void nv_hat(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap)) {
- (void)buflist_getfile((int)cap->count0, (linenr_T)0,
+ (void)buflist_getfile(cap->count0, 0,
GETF_SETMARK|GETF_ALT, false);
}
}
@@ -3438,7 +3408,6 @@ static void nv_ident(cmdarg_T *cap)
int cmdchar;
bool g_cmd; // "g" command
bool tag_cmd = false;
- char *aux_ptr;
if (cap->cmdchar == 'g') { // "g*", "g#", "g]" and "gCTRL-]"
cmdchar = cap->nchar;
@@ -3531,7 +3500,7 @@ static void nv_ident(cmdarg_T *cap)
ptr = xstrnsave(ptr, n);
if (kp_ex) {
// Escape the argument properly for an Ex command
- p = vim_strsave_fnameescape((const char *)ptr, VSE_NONE);
+ p = vim_strsave_fnameescape(ptr, VSE_NONE);
} else {
// Escape the argument properly for a shell command
p = vim_strsave_shellescape(ptr, true, true);
@@ -3542,6 +3511,7 @@ static void nv_ident(cmdarg_T *cap)
STRCAT(buf, p);
xfree(p);
} else {
+ char *aux_ptr;
if (cmdchar == '*') {
aux_ptr = (magic_isset() ? "/.*~[^$\\" : "/^$\\");
} else if (cmdchar == '#') {
@@ -3646,17 +3616,15 @@ bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp)
static void nv_tagpop(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap)) {
- do_tag("", DT_POP, (int)cap->count1, false, true);
+ do_tag("", DT_POP, cap->count1, false, true);
}
}
/// Handle scrolling command 'H', 'L' and 'M'.
static void nv_scroll(cmdarg_T *cap)
{
- int used = 0;
- long n;
+ int n;
linenr_T lnum;
- int half;
cap->oap->motion_type = kMTLineWise;
setpcmark();
@@ -3678,28 +3646,29 @@ static void nv_scroll(cmdarg_T *cap)
}
}
} else {
- curwin->w_cursor.lnum -= (linenr_T)cap->count1 - 1;
+ curwin->w_cursor.lnum -= cap->count1 - 1;
}
}
} else {
if (cap->cmdchar == 'M') {
+ int used = 0;
// Don't count filler lines above the window.
used -= win_get_fill(curwin, curwin->w_topline)
- curwin->w_topfill;
validate_botline(curwin); // make sure w_empty_rows is valid
- half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2;
+ int half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2;
for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) {
// Count half the number of filler lines to be "below this
// line" and half to be "above the next line".
- if (n > 0 && used + win_get_fill(curwin, curwin->w_topline + (linenr_T)n) / 2 >= half) {
+ if (n > 0 && used + win_get_fill(curwin, curwin->w_topline + n) / 2 >= half) {
n--;
break;
}
- used += plines_win(curwin, curwin->w_topline + (linenr_T)n, true);
+ used += plines_win(curwin, curwin->w_topline + n, true);
if (used >= half) {
break;
}
- if (hasFolding(curwin->w_topline + (linenr_T)n, NULL, &lnum)) {
+ if (hasFolding(curwin->w_topline + n, NULL, &lnum)) {
n = lnum - curwin->w_topline;
}
}
@@ -3718,7 +3687,7 @@ static void nv_scroll(cmdarg_T *cap)
n = lnum - curwin->w_topline;
}
}
- curwin->w_cursor.lnum = curwin->w_topline + (linenr_T)n;
+ curwin->w_cursor.lnum = curwin->w_topline + n;
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
@@ -3734,7 +3703,7 @@ static void nv_scroll(cmdarg_T *cap)
/// Cursor right commands.
static void nv_right(cmdarg_T *cap)
{
- long n;
+ int n;
if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
// <C-Right> and <S-Right> move a word or WORD right
@@ -3812,7 +3781,7 @@ static void nv_right(cmdarg_T *cap)
/// @return true when operator end should not be adjusted.
static void nv_left(cmdarg_T *cap)
{
- long n;
+ int n;
if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
// <C-Left> and <S-Left> move a word or WORD left
@@ -3921,14 +3890,13 @@ static void nv_down(cmdarg_T *cap)
/// Grab the file name under the cursor and edit it.
static void nv_gotofile(cmdarg_T *cap)
{
- char *ptr;
linenr_T lnum = -1;
if (check_text_or_curbuf_locked(cap->oap)) {
return;
}
- ptr = grab_file_name(cap->count1, &lnum);
+ char *ptr = grab_file_name(cap->count1, &lnum);
if (ptr != NULL) {
// do autowrite if necessary
@@ -3940,7 +3908,7 @@ static void nv_gotofile(cmdarg_T *cap)
buf_hide(curbuf) ? ECMD_HIDE : 0, curwin) == OK
&& cap->nchar == 'F' && lnum >= 0) {
curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_SOL | BL_FIX);
}
xfree(ptr);
@@ -4113,9 +4081,8 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
pos_T new_pos = { 0, 0, 0 };
pos_T *pos = NULL; // init for GCC
pos_T prev_pos;
- long n;
+ int n;
int findc;
- int c;
if (cap->nchar == '*') {
cap->nchar = '/';
@@ -4155,6 +4122,7 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
// Try finding the '{' or '}' we want to be at.
// Also repeat for the given count.
if (cap->nchar == 'm' || cap->nchar == 'M') {
+ int c;
// norm is true for "]M" and "[m"
int norm = ((findc == '{') == (cap->nchar == 'm'));
@@ -4170,7 +4138,7 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
pos = NULL;
}
while (n > 0) {
- for (;;) {
+ while (true) {
if ((findc == '{' ? dec_cursor() : inc_cursor()) < 0) {
// if not found anything, that's an error
if (pos == NULL) {
@@ -4225,13 +4193,12 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
/// cap->arg is BACKWARD for "[" and FORWARD for "]".
static void nv_brackets(cmdarg_T *cap)
{
- pos_T old_pos; // cursor position before command
int flag;
- long n;
+ int n;
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
- old_pos = curwin->w_cursor;
+ pos_T old_pos = curwin->w_cursor; // cursor position before command
curwin->w_cursor.coladd = 0; // TODO(Unknown): don't do this for an error.
// "[f" or "]f" : Edit file under the cursor (same as "gf")
@@ -4252,19 +4219,19 @@ static void nv_brackets(cmdarg_T *cap)
clearop(cap->oap);
} else {
// Make a copy, if the line was changed it will be freed.
- ptr = xstrnsave(ptr, len);
+ ptr = xmemdupz(ptr, len);
find_pattern_in_path(ptr, 0, len, true,
cap->count0 == 0 ? !isupper(cap->nchar) : false,
(((cap->nchar & 0xf) == ('d' & 0xf))
? FIND_DEFINE
: FIND_ANY),
cap->count1,
- (isupper(cap->nchar) ? ACTION_SHOW_ALL :
- islower(cap->nchar) ? ACTION_SHOW :
- ACTION_GOTO),
+ (isupper(cap->nchar) ? ACTION_SHOW_ALL
+ : islower(cap->nchar) ? ACTION_SHOW
+ : ACTION_GOTO),
(cap->cmdchar == ']'
? curwin->w_cursor.lnum + 1
- : (linenr_T)1),
+ : 1),
MAXLNUM);
xfree(ptr);
curwin->w_set_curswant = true;
@@ -4318,7 +4285,7 @@ static void nv_brackets(cmdarg_T *cap)
fm = prev_fm;
}
MarkMove flags = kMarkContext;
- flags |= cap->nchar == '\'' ? kMarkBeginLine: 0;
+ flags |= cap->nchar == '\'' ? kMarkBeginLine : 0;
nv_mark_move_to(cap, flags, fm);
} else if (cap->nchar >= K_RIGHTRELEASE && cap->nchar <= K_LEFTMOUSE) {
// [ or ] followed by a middle mouse click: put selected text with
@@ -4361,7 +4328,6 @@ static void nv_brackets(cmdarg_T *cap)
/// Handle Normal mode "%" command.
static void nv_percent(cmdarg_T *cap)
{
- pos_T *pos;
linenr_T lnum = curwin->w_cursor.lnum;
cap->oap->inclusive = true;
@@ -4377,10 +4343,10 @@ static void nv_percent(cmdarg_T *cap)
// to avoid overflows.
if (curbuf->b_ml.ml_line_count >= 21474836) {
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count + 99)
- / 100 * (linenr_T)cap->count0;
+ / 100 * cap->count0;
} else {
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count *
- (linenr_T)cap->count0 + 99) / 100;
+ cap->count0 + 99) / 100;
}
if (curwin->w_cursor.lnum < 1) {
curwin->w_cursor.lnum = 1;
@@ -4391,6 +4357,7 @@ static void nv_percent(cmdarg_T *cap)
beginline(BL_SOL | BL_FIX);
}
} else { // "%" : go to matching paren
+ pos_T *pos;
cap->oap->motion_type = kMTCharWise;
cap->oap->use_reg_one = true;
if ((pos = findmatch(cap->oap, NUL)) == NULL) {
@@ -4490,14 +4457,13 @@ static void nv_kundo(cmdarg_T *cap)
clearopbeep(cap->oap);
return;
}
- u_undo((int)cap->count1);
+ u_undo(cap->count1);
curwin->w_set_curswant = true;
}
/// Handle the "r" command.
static void nv_replace(cmdarg_T *cap)
{
- char *ptr;
int had_ctrl_v;
if (checkclearop(cap->oap)) {
@@ -4509,7 +4475,7 @@ static void nv_replace(cmdarg_T *cap)
}
// get another character
- if (cap->nchar == Ctrl_V) {
+ if (cap->nchar == Ctrl_V || cap->nchar == Ctrl_Q) {
had_ctrl_v = Ctrl_V;
cap->nchar = get_literal(false);
// Don't redo a multibyte character with CTRL-V.
@@ -4529,7 +4495,7 @@ static void nv_replace(cmdarg_T *cap)
// Visual mode "r"
if (VIsual_active) {
if (got_int) {
- reset_VIsual();
+ got_int = false;
}
if (had_ctrl_v) {
// Use a special (negative) number to make a difference between a
@@ -4560,7 +4526,7 @@ static void nv_replace(cmdarg_T *cap)
}
// Abort if not enough characters to replace.
- ptr = get_cursor_pos_ptr();
+ char *ptr = get_cursor_pos_ptr();
if (strlen(ptr) < (unsigned)cap->count1
|| (mb_charlen(ptr) < cap->count1)) {
clearopbeep(cap->oap);
@@ -4614,7 +4580,7 @@ static void nv_replace(cmdarg_T *cap)
// This is slow, but it handles replacing a single-byte with a
// multi-byte and the other way around. Also handles adding
// composing characters for utf-8.
- for (long n = cap->count1; n > 0; n--) {
+ for (int n = cap->count1; n > 0; n--) {
State = MODE_REPLACE;
if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
int c = ins_copychar(curwin->w_cursor.lnum
@@ -4652,11 +4618,10 @@ static void nv_replace(cmdarg_T *cap)
/// 'O': same, but in block mode exchange left and right corners.
static void v_swap_corners(int cmdchar)
{
- pos_T old_cursor;
colnr_T left, right;
if (cmdchar == 'O' && VIsual_mode == Ctrl_V) {
- old_cursor = curwin->w_cursor;
+ pos_T old_cursor = curwin->w_cursor;
getvcols(curwin, &old_cursor, &VIsual, &left, &right);
curwin->w_cursor.lnum = VIsual.lnum;
coladvance(left);
@@ -4686,7 +4651,7 @@ static void v_swap_corners(int cmdchar)
curwin->w_curswant = left;
}
} else {
- old_cursor = curwin->w_cursor;
+ pos_T old_cursor = curwin->w_cursor;
curwin->w_cursor = VIsual;
VIsual = old_cursor;
curwin->w_set_curswant = true;
@@ -4736,9 +4701,15 @@ static void nv_vreplace(cmdarg_T *cap)
if (!MODIFIABLE(curbuf)) {
emsg(_(e_modifiable));
} else {
- if (cap->extra_char == Ctrl_V) { // get another character
+ if (cap->extra_char == Ctrl_V || cap->extra_char == Ctrl_Q) {
+ // get another character
cap->extra_char = get_literal(false);
}
+ if (cap->extra_char < ' ') {
+ // Prefix a control character with CTRL-V to avoid it being used as
+ // a command.
+ stuffcharReadbuff(Ctrl_V);
+ }
stuffcharReadbuff(cap->extra_char);
stuffcharReadbuff(ESC);
if (virtual_active()) {
@@ -4751,8 +4722,6 @@ static void nv_vreplace(cmdarg_T *cap)
/// Swap case for "~" command, when it does not work like an operator.
static void n_swapchar(cmdarg_T *cap)
{
- long n;
- pos_T startpos;
int did_change = 0;
if (checkclearopq(cap->oap)) {
@@ -4770,8 +4739,8 @@ static void n_swapchar(cmdarg_T *cap)
return;
}
- startpos = curwin->w_cursor;
- for (n = cap->count1; n > 0; n--) {
+ pos_T startpos = curwin->w_cursor;
+ for (int n = cap->count1; n > 0; n--) {
did_change |= swapchar(cap->oap->op_type, &curwin->w_cursor);
inc_cursor();
if (gchar_cursor() == NUL) {
@@ -4783,7 +4752,7 @@ static void n_swapchar(cmdarg_T *cap)
if (u_savesub(curwin->w_cursor.lnum) == false) {
break;
}
- u_clearline();
+ u_clearline(curbuf);
}
} else {
break;
@@ -4794,8 +4763,8 @@ static void n_swapchar(cmdarg_T *cap)
check_cursor();
curwin->w_set_curswant = true;
if (did_change) {
- changed_lines(startpos.lnum, startpos.col, curwin->w_cursor.lnum + 1,
- 0L, true);
+ changed_lines(curbuf, startpos.lnum, startpos.col, curwin->w_cursor.lnum + 1,
+ 0, true);
curbuf->b_op_start = startpos;
curbuf->b_op_end = curwin->w_cursor;
if (curbuf->b_op_end.col > 0) {
@@ -4957,9 +4926,9 @@ static void nv_pcmark(cmdarg_T *cap)
}
if (cap->cmdchar == 'g') {
- fm = get_changelist(curbuf, curwin, (int)cap->count1);
+ fm = get_changelist(curbuf, curwin, cap->count1);
} else {
- fm = get_jumplist(curwin, (int)cap->count1);
+ fm = get_jumplist(curwin, cap->count1);
flags |= KMarkNoContext | kMarkJumpList;
}
// Changelist and jumplist have their own error messages. Therefore avoid
@@ -4969,7 +4938,7 @@ static void nv_pcmark(cmdarg_T *cap)
move_res = nv_mark_move_to(cap, flags, fm);
} else if (cap->cmdchar == 'g') {
if (curbuf->b_changelistlen == 0) {
- emsg(_("E664: changelist is empty"));
+ emsg(_(e_changelist_is_empty));
} else if (cap->count1 < 0) {
emsg(_("E662: At start of changelist"));
} else {
@@ -5051,7 +5020,7 @@ static void nv_visual(cmdarg_T *cap)
// For V and ^V, we multiply the number of lines even if there
// was only one -- webb
if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) {
- curwin->w_cursor.lnum += resel_VIsual_line_count * (linenr_T)cap->count0 - 1;
+ curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1;
check_cursor();
}
VIsual_mode = resel_VIsual_mode;
@@ -5059,7 +5028,7 @@ static void nv_visual(cmdarg_T *cap)
if (resel_VIsual_line_count <= 1) {
update_curswant_force();
assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX);
- curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0;
+ curwin->w_curswant += resel_VIsual_vcol * cap->count0;
if (*p_sel != 'e') {
curwin->w_curswant--;
}
@@ -5072,9 +5041,13 @@ static void nv_visual(cmdarg_T *cap)
curwin->w_curswant = MAXCOL;
coladvance(MAXCOL);
} else if (VIsual_mode == Ctrl_V) {
+ // Update curswant on the original line, that is where "col" is valid.
+ linenr_T lnum = curwin->w_cursor.lnum;
+ curwin->w_cursor.lnum = VIsual.lnum;
update_curswant_force();
assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX);
- curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0 - 1;
+ curwin->w_curswant += resel_VIsual_vcol * cap->count0 - 1;
+ curwin->w_cursor.lnum = lnum;
coladvance(curwin->w_curswant);
} else {
curwin->w_set_curswant = true;
@@ -5268,10 +5241,11 @@ static void nv_g_home_m_cmd(cmdarg_T *cap)
if (flag) {
do {
i = gchar_cursor();
- } while (ascii_iswhite(i) && oneright());
+ } while (ascii_iswhite(i) && oneright() == OK);
curwin->w_valid &= ~VALID_WCOL;
}
curwin->w_set_curswant = true;
+ adjust_skipcol();
}
/// "g_": to the last non-blank character in the line or <count> lines downward.
@@ -5306,6 +5280,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
oparg_T *oap = cap->oap;
int i;
int col_off = curwin_col_off();
+ const bool flag = cap->nchar == K_END || cap->nchar == K_KEND;
oap->motion_type = kMTCharWise;
oap->inclusive = true;
@@ -5344,11 +5319,11 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
coladvance((colnr_T)i);
// if the character doesn't fit move one back
- if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) {
+ if (curwin->w_cursor.col > 0 && utf_ptr2cells(get_cursor_pos_ptr()) > 1) {
colnr_T vcol;
getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
- if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) {
+ if (vcol >= curwin->w_leftcol + curwin->w_width_inner - col_off) {
curwin->w_cursor.col--;
}
}
@@ -5356,6 +5331,12 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
// Make sure we stick in this column.
update_curswant_force();
}
+ if (flag) {
+ do {
+ i = gchar_cursor();
+ } while (ascii_iswhite(i) && oneleft() == OK);
+ curwin->w_valid &= ~VALID_WCOL;
+ }
}
/// "gi": start Insert at the last position.
@@ -5363,7 +5344,7 @@ static void nv_gi_cmd(cmdarg_T *cap)
{
if (curbuf->b_last_insert.mark.lnum != 0) {
curwin->w_cursor = curbuf->b_last_insert.mark;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
int i = (int)strlen(get_cursor_line_ptr());
if (curwin->w_cursor.col > (colnr_T)i) {
if (virtual_active()) {
@@ -5492,7 +5473,7 @@ static void nv_g_cmd(cmdarg_T *cap)
case 'M':
oap->motion_type = kMTCharWise;
oap->inclusive = false;
- i = linetabsize(get_cursor_line_ptr());
+ i = linetabsize(curwin, curwin->w_cursor.lnum);
if (cap->count0 > 0 && cap->count0 <= 100) {
coladvance((colnr_T)(i * cap->count0 / 100));
} else {
@@ -5570,7 +5551,7 @@ static void nv_g_cmd(cmdarg_T *cap)
// "gs": Goto sleep.
case 's':
- do_sleep(cap->count1 * 1000L);
+ do_sleep(cap->count1 * 1000);
break;
// "ga": Display the ascii value of the character under the
@@ -5625,7 +5606,7 @@ static void nv_g_cmd(cmdarg_T *cap)
// "gD": idem, but in the current file.
case 'd':
case 'D':
- nv_gd(oap, cap->nchar, (int)cap->count0);
+ nv_gd(oap, cap->nchar, cap->count0);
break;
// g<*Mouse> : <C-*mouse>
@@ -5681,12 +5662,12 @@ static void nv_g_cmd(cmdarg_T *cap)
case 't':
if (!checkclearop(oap)) {
- goto_tabpage((int)cap->count0);
+ goto_tabpage(cap->count0);
}
break;
case 'T':
if (!checkclearop(oap)) {
- goto_tabpage(-(int)cap->count1);
+ goto_tabpage(-cap->count1);
}
break;
@@ -5726,10 +5707,10 @@ static void n_opencmd(cmdarg_T *cap)
(void)hasFolding(curwin->w_cursor.lnum,
NULL, &curwin->w_cursor.lnum);
}
- if (u_save((linenr_T)(curwin->w_cursor.lnum -
- (cap->cmdchar == 'O' ? 1 : 0)),
- (linenr_T)(curwin->w_cursor.lnum +
- (cap->cmdchar == 'o' ? 1 : 0)))
+ // trigger TextChangedI for the 'o/O' command
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+ if (u_save(curwin->w_cursor.lnum - (cap->cmdchar == 'O' ? 1 : 0),
+ curwin->w_cursor.lnum + (cap->cmdchar == 'o' ? 1 : 0))
&& open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD,
has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0,
0, NULL)) {
@@ -5760,10 +5741,9 @@ static void nv_dot(cmdarg_T *cap)
static void nv_redo_or_register(cmdarg_T *cap)
{
if (VIsual_select && VIsual_active) {
- int reg;
// Get register name
no_mapping++;
- reg = plain_vgetc();
+ int reg = plain_vgetc();
LANGMAP_ADJUST(reg, true);
no_mapping--;
@@ -5780,7 +5760,7 @@ static void nv_redo_or_register(cmdarg_T *cap)
return;
}
- u_redo((int)cap->count1);
+ u_redo(cap->count1);
curwin->w_set_curswant = true;
}
@@ -5823,9 +5803,7 @@ static void nv_tilde(cmdarg_T *cap)
/// The actual work is done by do_pending_operator().
static void nv_operator(cmdarg_T *cap)
{
- int op_type;
-
- op_type = get_op_type(cap->cmdchar, cap->nchar);
+ int op_type = get_op_type(cap->cmdchar, cap->nchar);
if (bt_prompt(curbuf) && op_is_change(op_type)
&& !prompt_curpos_editable()) {
@@ -5872,7 +5850,7 @@ static void set_op_var(int optype)
static void nv_lineop(cmdarg_T *cap)
{
cap->oap->motion_type = kMTLineWise;
- if (cursor_down(cap->count1 - 1L, cap->oap->op_type == OP_NOP) == false) {
+ if (cursor_down(cap->count1 - 1, cap->oap->op_type == OP_NOP) == false) {
clearopbeep(cap->oap);
} else if ((cap->oap->op_type == OP_DELETE
// only with linewise motions
@@ -6042,9 +6020,8 @@ static void adjust_for_sel(cmdarg_T *cap)
/// @return true when backed up to the previous line.
bool unadjust_for_sel(void)
{
- pos_T *pp;
-
if (*p_sel == 'e' && !equalpos(VIsual, curwin->w_cursor)) {
+ pos_T *pp;
if (lt(VIsual, curwin->w_cursor)) {
pp = &curwin->w_cursor;
} else {
@@ -6086,17 +6063,17 @@ static void nv_goto(cmdarg_T *cap)
if (cap->arg) {
lnum = curbuf->b_ml.ml_line_count;
} else {
- lnum = 1L;
+ lnum = 1;
}
cap->oap->motion_type = kMTLineWise;
setpcmark();
// When a count is given, use it instead of the default lnum
if (cap->count0 != 0) {
- lnum = (linenr_T)cap->count0;
+ lnum = cap->count0;
}
- if (lnum < 1L) {
- lnum = 1L;
+ if (lnum < 1) {
+ lnum = 1;
} else if (lnum > curbuf->b_ml.ml_line_count) {
lnum = curbuf->b_ml.ml_line_count;
}
@@ -6132,20 +6109,18 @@ static void nv_normal(cmdarg_T *cap)
/// Don't even beep if we are canceling a command.
static void nv_esc(cmdarg_T *cap)
{
- int no_reason;
-
- no_reason = (cap->oap->op_type == OP_NOP
- && cap->opcount == 0
- && cap->count0 == 0
- && cap->oap->regname == 0);
+ int no_reason = (cap->oap->op_type == OP_NOP
+ && cap->opcount == 0
+ && cap->count0 == 0
+ && cap->oap->regname == 0);
if (cap->arg) { // true for CTRL-C
if (restart_edit == 0 && cmdwin_type == 0 && !VIsual_active && no_reason) {
if (anyBufIsChanged()) {
msg(_("Type :qa! and press <Enter> to abandon all changes"
- " and exit Nvim"));
+ " and exit Nvim"), 0);
} else {
- msg(_("Type :qa and press <Enter> to exit Nvim"));
+ msg(_("Type :qa and press <Enter> to exit Nvim"), 0);
}
}
@@ -6270,6 +6245,11 @@ static void invoke_edit(cmdarg_T *cap, int repl, int cmd, int startln)
// Always reset "restart_edit", this is not a restarted edit.
restart_edit = 0;
+ // Reset Changedtick_i, so that TextChangedI will only be triggered for stuff
+ // from insert mode, for 'o/O' this has already been done in n_opencmd
+ if (cap->cmdchar != 'O' && cap->cmdchar != 'o') {
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+ }
if (edit(cmd, startln, cap->count1)) {
cap->retval |= CA_COMMAND_BUSY;
}
@@ -6284,7 +6264,6 @@ static void nv_object(cmdarg_T *cap)
{
bool flag;
bool include;
- char *mps_save;
if (cap->cmdchar == 'i') {
include = false; // "ix" = inner object: exclude white space
@@ -6292,7 +6271,7 @@ static void nv_object(cmdarg_T *cap)
include = true; // "ax" = an object: include white space
}
// Make sure (), [], {} and <> are in 'matchpairs'
- mps_save = curbuf->b_p_mps;
+ char *mps_save = curbuf->b_p_mps;
curbuf->b_p_mps = "(:),{:},[:],<:>";
switch (cap->nchar) {
@@ -6372,6 +6351,10 @@ static void nv_record(cmdarg_T *cap)
}
if (cap->nchar == ':' || cap->nchar == '/' || cap->nchar == '?') {
+ if (cmdwin_type != 0) {
+ emsg(_(e_cmdline_window_already_open));
+ return;
+ }
stuffcharReadbuff(cap->nchar);
stuffcharReadbuff(K_CMDWIN);
} else {
@@ -6411,7 +6394,7 @@ static void nv_halfpage(cmdarg_T *cap)
&& curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count)) {
clearopbeep(cap->oap);
} else if (!checkclearop(cap->oap)) {
- halfpage(cap->cmdchar == Ctrl_D, (linenr_T)cap->count0);
+ halfpage(cap->cmdchar == Ctrl_D, cap->count0);
}
}
@@ -6456,7 +6439,6 @@ static void nv_put(cmdarg_T *cap)
/// @param fix_indent true for "[p", "[P", "]p" and "]P".
static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
{
- int regname = 0;
yankreg_T *savereg = NULL;
bool empty = false;
bool was_visual = false;
@@ -6482,7 +6464,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
if (fix_indent) {
dir = (cap->cmdchar == ']' && cap->nchar == 'p')
- ? FORWARD : BACKWARD;
+ ? FORWARD : BACKWARD;
flags |= PUT_FIXINDENT;
} else {
dir = (cap->cmdchar == 'P'
@@ -6502,7 +6484,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// Need to save and restore the registers that the delete
// overwrites if the old contents is being put.
was_visual = true;
- regname = cap->oap->regname;
+ int regname = cap->oap->regname;
bool keep_registers = cap->cmdchar == 'P';
// '+' and '*' could be the same selection
bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK);
diff --git a/src/nvim/normal.h b/src/nvim/normal.h
index bed1a40b97..dbe74712fc 100644
--- a/src/nvim/normal.h
+++ b/src/nvim/normal.h
@@ -1,85 +1,20 @@
-#ifndef NVIM_NORMAL_H
-#define NVIM_NORMAL_H
+#pragma once
-#include <stdbool.h>
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/normal_defs.h" // IWYU pragma: export
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/pos.h"
+/// Values for find_ident_under_cursor()
+enum {
+ FIND_IDENT = 1, ///< find identifier (word)
+ FIND_STRING = 2, ///< find any string (WORD)
+ FIND_EVAL = 4, ///< include "->", "[]" and "."
+};
-// Values for find_ident_under_cursor()
-#define FIND_IDENT 1 // find identifier (word)
-#define FIND_STRING 2 // find any string (WORD)
-#define FIND_EVAL 4 // include "->", "[]" and "."
-
-/// Motion types, used for operators and for yank/delete registers.
-///
-/// The three valid numerical values must not be changed, as they
-/// are used in external communication and serialization.
-typedef enum {
- kMTCharWise = 0, ///< character-wise movement/register
- kMTLineWise = 1, ///< line-wise movement/register
- kMTBlockWise = 2, ///< block-wise movement/register
- kMTUnknown = -1, ///< Unknown or invalid motion type
-} MotionType;
-
-// Arguments for operators.
-typedef struct oparg_S {
- int op_type; // current pending operator type
- int regname; // register to use for the operator
- MotionType motion_type; // type of the current cursor motion
- int motion_force; // force motion type: 'v', 'V' or CTRL-V
- bool use_reg_one; // true if delete uses reg 1 even when not
- // linewise
- bool inclusive; // true if char motion is inclusive (only
- // valid when motion_type is kMTCharWise)
- bool end_adjusted; // backuped b_op_end one char (only used by
- // do_format())
- pos_T start; // start of the operator
- pos_T end; // end of the operator
- pos_T cursor_start; // cursor position before motion for "gw"
-
- long line_count; // number of lines from op_start to op_end
- // (inclusive)
- bool empty; // op_start and op_end the same (only used by
- // op_change())
- bool is_VIsual; // operator on Visual area
- colnr_T start_vcol; // start col for block mode operator
- colnr_T end_vcol; // end col for block mode operator
- long prev_opcount; // ca.opcount saved for K_EVENT
- long prev_count0; // ca.count0 saved for K_EVENT
- bool excl_tr_ws; // exclude trailing whitespace for yank of a
- // block
-} oparg_T;
-
-// Arguments for Normal mode commands.
-typedef struct cmdarg_S {
- oparg_T *oap; // Operator arguments
- int prechar; // prefix character (optional, always 'g')
- int cmdchar; // command character
- int nchar; // next command character (optional)
- int ncharC1; // first composing character (optional)
- int ncharC2; // second composing character (optional)
- int extra_char; // yet another character (optional)
- long opcount; // count before an operator
- long count0; // count before command, default 0
- long count1; // count before command, default 1
- int arg; // extra argument from nv_cmds[]
- int retval; // return: CA_* values
- char *searchbuf; // return: pointer to search pattern or NULL
-} cmdarg_T;
-
-// values for retval:
-#define CA_COMMAND_BUSY 1 // skip restarting edit() once
-#define CA_NO_ADJ_OP_END 2 // don't adjust operator end
-
-// columns needed by shown command
-#define SHOWCMD_COLS 10
-// 'showcmd' buffer shared between normal.c and statusline.c
-#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30)
+/// 'showcmd' buffer shared between normal.c and statusline.c
EXTERN char showcmd_buf[SHOWCMD_BUFLEN];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "normal.h.generated.h"
#endif
-#endif // NVIM_NORMAL_H
diff --git a/src/nvim/normal_defs.h b/src/nvim/normal_defs.h
new file mode 100644
index 0000000000..060c1057f9
--- /dev/null
+++ b/src/nvim/normal_defs.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "nvim/pos_defs.h"
+
+/// Motion types, used for operators and for yank/delete registers.
+///
+/// The three valid numerical values must not be changed, as they
+/// are used in external communication and serialization.
+typedef enum {
+ kMTCharWise = 0, ///< character-wise movement/register
+ kMTLineWise = 1, ///< line-wise movement/register
+ kMTBlockWise = 2, ///< block-wise movement/register
+ kMTUnknown = -1, ///< Unknown or invalid motion type
+} MotionType;
+
+/// Arguments for operators.
+typedef struct oparg_S {
+ int op_type; ///< current pending operator type
+ int regname; ///< register to use for the operator
+ MotionType motion_type; ///< type of the current cursor motion
+ int motion_force; ///< force motion type: 'v', 'V' or CTRL-V
+ bool use_reg_one; ///< true if delete uses reg 1 even when not
+ ///< linewise
+ bool inclusive; ///< true if char motion is inclusive (only
+ ///< valid when motion_type is kMTCharWise)
+ bool end_adjusted; ///< backuped b_op_end one char (only used by
+ ///< do_format())
+ pos_T start; ///< start of the operator
+ pos_T end; ///< end of the operator
+ pos_T cursor_start; ///< cursor position before motion for "gw"
+
+ linenr_T line_count; ///< number of lines from op_start to op_end (inclusive)
+ bool empty; ///< op_start and op_end the same (only used by op_change())
+ bool is_VIsual; ///< operator on Visual area
+ colnr_T start_vcol; ///< start col for block mode operator
+ colnr_T end_vcol; ///< end col for block mode operator
+ int prev_opcount; ///< ca.opcount saved for K_EVENT
+ int prev_count0; ///< ca.count0 saved for K_EVENT
+ bool excl_tr_ws; ///< exclude trailing whitespace for yank of a block
+} oparg_T;
+
+/// Arguments for Normal mode commands.
+typedef struct cmdarg_S {
+ oparg_T *oap; ///< Operator arguments
+ int prechar; ///< prefix character (optional, always 'g')
+ int cmdchar; ///< command character
+ int nchar; ///< next command character (optional)
+ int ncharC1; ///< first composing character (optional)
+ int ncharC2; ///< second composing character (optional)
+ int extra_char; ///< yet another character (optional)
+ int opcount; ///< count before an operator
+ int count0; ///< count before command, default 0
+ int count1; ///< count before command, default 1
+ int arg; ///< extra argument from nv_cmds[]
+ int retval; ///< return: CA_* values
+ char *searchbuf; ///< return: pointer to search pattern or NULL
+} cmdarg_T;
+
+/// values for retval:
+enum {
+ CA_COMMAND_BUSY = 1, ///< skip restarting edit() once
+ CA_NO_ADJ_OP_END = 2, ///< don't adjust operator end
+};
+
+/// Replacement for nchar used by nv_replace().
+enum {
+ REPLACE_CR_NCHAR = -1,
+ REPLACE_NL_NCHAR = -2,
+};
+
+/// columns needed by shown command
+enum { SHOWCMD_COLS = 10, };
+enum { SHOWCMD_BUFLEN = SHOWCMD_COLS + 1 + 30, };
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 435ca106ab..3a4e87edf7 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
// op_change, op_yank, do_put, do_join
@@ -12,10 +9,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
@@ -34,11 +32,11 @@
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -49,19 +47,20 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
#include "nvim/textformat.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
@@ -97,13 +96,16 @@ struct block_def {
# include "ops.c.generated.h"
#endif
+static const char e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines[]
+ = N_("E883: Search pattern and expression register may not contain two or more lines");
+
// Flags for third item in "opchars".
#define OPF_LINES 1 // operator always works on lines
#define OPF_CHANGE 2 // operator changes text
-// The names of operators.
-// IMPORTANT: Index must correspond with defines in vim.h!!!
-// The third field indicates whether the operator always works on lines.
+/// The names of operators.
+/// IMPORTANT: Index must correspond with defines in ops.h!!!
+/// The third field indicates whether the operator always works on lines.
static char opchars[][3] = {
{ NUL, NUL, 0 }, // OP_NOP
{ 'd', NUL, OPF_CHANGE }, // OP_DELETE
@@ -213,8 +215,7 @@ int get_extra_op_char(int optype)
/// handle a shift operation
void op_shift(oparg_T *oap, int curs_top, int amount)
{
- long i;
- int first_char;
+ int i;
int block_col = 0;
if (u_save((linenr_T)(oap->start.lnum - 1),
@@ -227,7 +228,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
}
for (i = oap->line_count - 1; i >= 0; i--) {
- first_char = (uint8_t)(*get_cursor_line_ptr());
+ int first_char = (uint8_t)(*get_cursor_line_ptr());
if (first_char == NUL) { // empty line
curwin->w_cursor.col = 0;
} else if (oap->motion_type == kMTBlockWise) {
@@ -280,7 +281,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
}
}
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
}
/// Shift the current line one shiftwidth left (if left != 0) or right
@@ -289,15 +290,13 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
/// @param call_changed_bytes call changed_bytes()
void shift_line(int left, int round, int amount, int call_changed_bytes)
{
- int count;
- int i, j;
- const int sw_val = (int)get_sw_value_indent(curbuf);
+ const int sw_val = get_sw_value_indent(curbuf);
- count = get_indent(); // get current indent
+ int count = get_indent(); // get current indent
if (round) { // round off indent
- i = count / sw_val; // number of 'shiftwidth' rounded down
- j = count % sw_val; // extra spaces
+ int i = count / sw_val; // number of 'shiftwidth' rounded down
+ int j = count % sw_val; // extra spaces
if (j && left) { // first remove extra spaces
amount--;
}
@@ -337,11 +336,10 @@ static void shift_block(oparg_T *oap, int amount)
const int oldstate = State;
char *newp;
const int oldcol = curwin->w_cursor.col;
- const int sw_val = (int)get_sw_value_indent(curbuf);
+ const int sw_val = get_sw_value_indent(curbuf);
const int ts_val = (int)curbuf->b_p_ts;
struct block_def bd;
int incr;
- int i = 0, j = 0;
const int old_p_ri = p_ri;
p_ri = 0; // don't want revins in indent
@@ -392,40 +390,36 @@ static void shift_block(oparg_T *oap, int amount)
bd.start_vcol = cts.cts_vcol;
clear_chartabsize_arg(&cts);
+ int tabs = 0, spaces = 0;
// OK, now total=all the VWS reqd, and textstart points at the 1st
// non-ws char in the block.
if (!curbuf->b_p_et) {
- tabstop_fromto(ws_vcol, ws_vcol + total, ts_val, curbuf->b_p_vts_array, &i, &j);
+ tabstop_fromto(ws_vcol, ws_vcol + total,
+ ts_val, curbuf->b_p_vts_array, &tabs, &spaces);
} else {
- j = total;
+ spaces = total;
}
// if we're splitting a TAB, allow for it
- int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0);
+ const int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0);
bd.textcol -= col_pre;
- const int len = (int)strlen(bd.textstart) + 1;
- int col = bd.textcol + i + j + len;
- assert(col >= 0);
- newp = xmalloc((size_t)col);
- memset(newp, NUL, (size_t)col);
+
+ const size_t new_line_len // the length of the line after the block shift
+ = (size_t)bd.textcol + (size_t)tabs + (size_t)spaces + strlen(bd.textstart);
+ newp = xmalloc(new_line_len + 1);
memmove(newp, oldp, (size_t)bd.textcol);
startcol = bd.textcol;
oldlen = (int)(bd.textstart - old_textstart) + col_pre;
- newlen = i + j;
- memset(newp + bd.textcol, TAB, (size_t)i);
- memset(newp + bd.textcol + i, ' ', (size_t)j);
- // the end
- memmove(newp + bd.textcol + i + j, bd.textstart, (size_t)len);
+ newlen = tabs + spaces;
+ memset(newp + bd.textcol, TAB, (size_t)tabs);
+ memset(newp + bd.textcol + tabs, ' ', (size_t)spaces);
+ // Note that STRMOVE() copies the trailing NUL.
+ STRMOVE(newp + bd.textcol + tabs + spaces, bd.textstart);
} else { // left
- colnr_T destination_col; // column to which text in block will
- // be shifted
char *verbatim_copy_end; // end of the part of the line which is
// copied verbatim
colnr_T verbatim_copy_width; // the (displayed) width of this part
// of line
- size_t fill; // nr of spaces that replace a TAB
- size_t new_line_len; // the length of the line after the
- // block shift
char *non_white = bd.textstart;
// Firstly, let's find the first non-whitespace character that is
@@ -458,10 +452,10 @@ static void shift_block(oparg_T *oap, int amount)
const colnr_T block_space_width = non_white_col - oap->start_vcol;
// We will shift by "total" or "block_space_width", whichever is less.
const colnr_T shift_amount = block_space_width < total
- ? block_space_width
- : total;
+ ? block_space_width
+ : total;
// The column to which we will shift the text.
- destination_col = non_white_col - shift_amount;
+ const colnr_T destination_col = non_white_col - shift_amount;
// Now let's find out how much of the beginning of the line we can
// reuse without modification.
@@ -492,7 +486,8 @@ static void shift_block(oparg_T *oap, int amount)
// part of the line that will be copied, it means we encountered a tab
// character, which we will have to partly replace with spaces.
assert(destination_col - verbatim_copy_width >= 0);
- fill = (size_t)(destination_col - verbatim_copy_width);
+ const size_t fill // nr of spaces that replace a TAB
+ = (size_t)(destination_col - verbatim_copy_width);
assert(verbatim_copy_end - oldp >= 0);
const size_t verbatim_diff = (size_t)(verbatim_copy_end - oldp);
@@ -500,14 +495,16 @@ static void shift_block(oparg_T *oap, int amount)
// - the beginning of the original line up to "verbatim_copy_end",
// - "fill" number of spaces,
// - the rest of the line, pointed to by non_white.
- new_line_len = verbatim_diff + fill + strlen(non_white) + 1;
+ const size_t new_line_len // the length of the line after the block shift
+ = verbatim_diff + fill + strlen(non_white);
- newp = xmalloc(new_line_len);
+ newp = xmalloc(new_line_len + 1);
startcol = (int)verbatim_diff;
oldlen = bd.textcol + (int)(non_white - bd.textstart) - (int)verbatim_diff;
newlen = (int)fill;
memmove(newp, oldp, verbatim_diff);
memset(newp + verbatim_diff, ' ', fill);
+ // Note that STRMOVE() copies the trailing NUL.
STRMOVE(newp + verbatim_diff + fill, non_white);
}
// replace the line
@@ -531,11 +528,10 @@ static void block_insert(oparg_T *oap, char *s, int b_insert, struct block_def *
colnr_T offset; // pointer along new line
size_t s_len = strlen(s);
char *newp, *oldp; // new, old lines
- linenr_T lnum; // loop var
int oldstate = State;
State = MODE_INSERT; // don't want MODE_REPLACE for State
- for (lnum = oap->start.lnum + 1; lnum <= oap->end.lnum; lnum++) {
+ for (linenr_T lnum = oap->start.lnum + 1; lnum <= oap->end.lnum; lnum++) {
block_prep(oap, bdp, lnum, true);
if (bdp->is_short && b_insert) {
continue; // OP_INSERT, line ends before block start
@@ -627,17 +623,15 @@ static void block_insert(oparg_T *oap, char *s, int b_insert, struct block_def *
}
} // for all lnum
- changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L, true);
-
State = oldstate;
+
+ changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
}
/// Handle reindenting a block of lines.
void op_reindent(oparg_T *oap, Indenter how)
{
- long i = 0;
- char *l;
- int amount;
+ int i = 0;
linenr_T first_changed = 0;
linenr_T last_changed = 0;
linenr_T start_lnum = curwin->w_cursor.lnum;
@@ -650,8 +644,9 @@ void op_reindent(oparg_T *oap, Indenter how)
// Save for undo. Do this once for all lines, much faster than doing this
// for each line separately, especially when undoing.
- if (u_savecommon(curbuf, start_lnum - 1, start_lnum + (linenr_T)oap->line_count,
- start_lnum + (linenr_T)oap->line_count, false) == OK) {
+ if (u_savecommon(curbuf, start_lnum - 1, start_lnum + oap->line_count,
+ start_lnum + oap->line_count, false) == OK) {
+ int amount;
for (i = oap->line_count - 1; i >= 0 && !got_int; i--) {
// it's a slow thing to do, so give feedback so there's no worry
// that the computer's just hung.
@@ -659,14 +654,14 @@ void op_reindent(oparg_T *oap, Indenter how)
if (i > 1
&& (i % 50 == 0 || i == oap->line_count - 1)
&& oap->line_count > p_report) {
- smsg(_("%" PRId64 " lines to indent... "), (int64_t)i);
+ smsg(0, _("%" PRId64 " lines to indent... "), (int64_t)i);
}
// Be vi-compatible: For lisp indenting the first line is not
// indented, unless there is only one line.
if (i != oap->line_count - 1 || oap->line_count == 1
|| how != get_lisp_indent) {
- l = skipwhite(get_cursor_line_ptr());
+ char *l = skipwhite(get_cursor_line_ptr());
if (*l == NUL) { // empty or blank line
amount = 0;
} else {
@@ -693,18 +688,16 @@ void op_reindent(oparg_T *oap, Indenter how)
// highlighting was present, need to continue until the last line. When
// there is no change still need to remove the Visual highlighting.
if (last_changed != 0) {
- changed_lines(first_changed, 0,
- oap->is_VIsual ? start_lnum + (linenr_T)oap->line_count :
- last_changed + 1, 0L, true);
+ changed_lines(curbuf, first_changed, 0,
+ oap->is_VIsual ? start_lnum + oap->line_count
+ : last_changed + 1, 0, true);
} else if (oap->is_VIsual) {
redraw_curbuf_later(UPD_INVERTED);
}
if (oap->line_count > p_report) {
i = oap->line_count - (i + 1);
- smsg(NGETTEXT("%" PRId64 " line indented ",
- "%" PRId64 " lines indented ", i),
- (int64_t)i);
+ smsg(0, NGETTEXT("%" PRId64 " line indented ", "%" PRId64 " lines indented ", i), (int64_t)i);
}
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// set '[ and '] marks
@@ -721,9 +714,7 @@ static char *expr_line = NULL;
/// @return '=' when OK, NUL otherwise.
int get_expr_register(void)
{
- char *new_line;
-
- new_line = getcmdline('=', 0L, 0, true);
+ char *new_line = getcmdline('=', 0, 0, true);
if (new_line == NULL) {
return NUL;
}
@@ -748,8 +739,6 @@ void set_expr_line(char *new_line)
/// @return a pointer to allocated memory, or NULL for failure.
char *get_expr_line(void)
{
- char *expr_copy;
- char *rv;
static int nested = 0;
if (expr_line == NULL) {
@@ -758,7 +747,7 @@ char *get_expr_line(void)
// Make a copy of the expression, because evaluating it may cause it to be
// changed.
- expr_copy = xstrdup(expr_line);
+ char *expr_copy = xstrdup(expr_line);
// When we are invoked recursively limit the evaluation to 10 levels.
// Then return the string as-is.
@@ -767,7 +756,7 @@ char *get_expr_line(void)
}
nested++;
- rv = eval_to_string(expr_copy, NULL, true);
+ char *rv = eval_to_string(expr_copy, true);
nested--;
xfree(expr_copy);
return rv;
@@ -855,15 +844,6 @@ static bool is_append_register(int regname)
return ASCII_ISUPPER(regname);
}
-/// @see get_yank_register
-/// @returns true when register should be inserted literally
-/// (selection or clipboard)
-static inline bool is_literal_register(int regname)
- FUNC_ATTR_CONST
-{
- return regname == '*' || regname == '+';
-}
-
/// @return a copy of contents in register `name` for use in do_put. Should be
/// freed by caller.
yankreg_T *copy_register(int name)
@@ -902,9 +882,7 @@ bool yank_register_mline(int regname)
/// @return FAIL for failure, OK otherwise.
int do_record(int c)
{
- char *p;
static int regname;
- yankreg_T *old_y_previous;
int retval;
if (reg_recording == 0) {
@@ -927,11 +905,11 @@ int do_record(int c)
dict_T *dict = get_v_event(&save_v_event);
// The recorded text contents.
- p = (char *)get_recorded();
+ char *p = get_recorded();
if (p != NULL) {
// Remove escaping for K_SPECIAL in multi-byte chars.
- vim_unescape_ks((char_u *)p);
- (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p);
+ vim_unescape_ks(p);
+ (void)tv_dict_add_str(dict, S_LEN("regcontents"), p);
}
// Name of requested register, or empty string for unnamed operation.
@@ -951,14 +929,14 @@ int do_record(int c)
if (p_ch == 0 || ui_has(kUIMessages)) {
showmode();
} else {
- msg("");
+ msg("", 0);
}
if (p == NULL) {
retval = FAIL;
} else {
// We don't want to change the default register here, so save and
// restore the current register name.
- old_y_previous = y_previous;
+ yankreg_T *old_y_previous = y_previous;
retval = stuff_yank(regname, p);
@@ -996,10 +974,12 @@ static int stuff_yank(int regname, char *p)
yankreg_T *reg = get_yank_register(regname, YREG_YANK);
if (is_append_register(regname) && reg->y_array != NULL) {
char **pp = &(reg->y_array[reg->y_size - 1]);
- char *lp = xmalloc(strlen(*pp) + strlen(p) + 1);
- STRCPY(lp, *pp);
- // TODO(philix): use xstpcpy() in stuff_yank()
- STRCAT(lp, p);
+ const size_t ppl = strlen(*pp);
+ const size_t pl = strlen(p);
+ char *lp = xmalloc(ppl + pl + 1);
+ memcpy(lp, *pp, ppl);
+ memcpy(lp + ppl, p, pl);
+ *(lp + ppl + pl) = NUL;
xfree(p);
xfree(*pp);
*pp = lp;
@@ -1037,13 +1017,11 @@ static char *execreg_line_continuation(char **lines, size_t *idx)
garray_T ga;
ga_init(&ga, (int)sizeof(char), 400);
- char *p;
-
// search backwards to find the first line of this command.
// Any line not starting with \ or "\ is the start of the
// command.
while (--i > 0) {
- p = skipwhite(lines[i]);
+ char *p = skipwhite(lines[i]);
if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) {
break;
}
@@ -1053,7 +1031,7 @@ static char *execreg_line_continuation(char **lines, size_t *idx)
// join all the lines
ga_concat(&ga, lines[cmd_start]);
for (size_t j = cmd_start + 1; j <= cmd_end; j++) {
- p = skipwhite(lines[j]);
+ char *p = skipwhite(lines[j]);
if (*p == '\\') {
// Adjust the growsize to the current length to
// speed up concatenating many lines.
@@ -1080,7 +1058,6 @@ static char *execreg_line_continuation(char **lines, size_t *idx)
/// @return FAIL for failure, OK otherwise
int do_execreg(int regname, int colon, int addcr, int silent)
{
- char *p;
int retval = OK;
if (regname == '@') { // repeat previous one
@@ -1109,12 +1086,12 @@ int do_execreg(int regname, int colon, int addcr, int silent)
// don't keep the cmdline containing @:
XFREE_CLEAR(new_last_cmdline);
// Escape all control characters with a CTRL-V
- p = vim_strsave_escaped_ext(last_cmdline,
- "\001\002\003\004\005\006\007"
- "\010\011\012\013\014\015\016\017"
- "\020\021\022\023\024\025\026\027"
- "\030\031\032\033\034\035\036\037",
- Ctrl_V, false);
+ char *p = vim_strsave_escaped_ext(last_cmdline,
+ "\001\002\003\004\005\006\007"
+ "\010\011\012\013\014\015\016\017"
+ "\020\021\022\023\024\025\026\027"
+ "\030\031\032\033\034\035\036\037",
+ Ctrl_V, false);
// When in Visual mode "'<,'>" will be prepended to the command.
// Remove it when it's already there.
if (VIsual_active && strncmp(p, "'<,'>", 5) == 0) {
@@ -1124,14 +1101,14 @@ int do_execreg(int regname, int colon, int addcr, int silent)
}
xfree(p);
} else if (regname == '=') {
- p = get_expr_line();
+ char *p = get_expr_line();
if (p == NULL) {
return FAIL;
}
retval = put_in_typebuf(p, true, colon, silent);
xfree(p);
} else if (regname == '.') { // use last inserted text
- p = get_last_insert_save();
+ char *p = get_last_insert_save();
if (p == NULL) {
emsg(_(e_noinstext));
return FAIL;
@@ -1149,7 +1126,6 @@ int do_execreg(int regname, int colon, int addcr, int silent)
// Insert lines into typeahead buffer, from last one to first one.
put_reedit_in_typebuf(silent);
- char *escaped;
for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included
// insert NL between lines and after last line if type is kMTLineWise
if (reg->y_type == kMTLineWise || i < reg->y_size - 1 || addcr) {
@@ -1162,13 +1138,13 @@ int do_execreg(int regname, int colon, int addcr, int silent)
char *str = reg->y_array[i];
bool free_str = false;
if (colon && i > 0) {
- p = skipwhite(str);
+ char *p = skipwhite(str);
if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) {
str = execreg_line_continuation(reg->y_array, &i);
free_str = true;
}
}
- escaped = vim_strsave_escape_ks(str);
+ char *escaped = vim_strsave_escape_ks(str);
if (free_str) {
xfree(str);
}
@@ -1191,7 +1167,7 @@ int do_execreg(int regname, int colon, int addcr, int silent)
/// used only after other typeahead has been processed.
static void put_reedit_in_typebuf(int silent)
{
- char_u buf[3];
+ uint8_t buf[3];
if (restart_edit == NUL) {
return;
@@ -1202,7 +1178,7 @@ static void put_reedit_in_typebuf(int silent)
buf[1] = 'R';
buf[2] = NUL;
} else {
- buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit);
+ buf[0] = (uint8_t)(restart_edit == 'I' ? 'i' : restart_edit);
buf[1] = NUL;
}
if (ins_typebuf((char *)buf, REMAP_NONE, 0, true, silent) == OK) {
@@ -1274,12 +1250,12 @@ int insert_reg(int regname, bool literally_arg)
char *arg;
if (regname == '.') { // Insert last inserted text.
- retval = stuff_inserted(NUL, 1L, true);
+ retval = stuff_inserted(NUL, 1, true);
} else if (get_spec_reg(regname, &arg, &allocated, true)) {
if (arg == NULL) {
return FAIL;
}
- stuffescaped((const char *)arg, literally);
+ stuffescaped(arg, literally);
if (allocated) {
xfree(arg);
}
@@ -1292,9 +1268,9 @@ int insert_reg(int regname, bool literally_arg)
if (regname == '-') {
AppendCharToRedobuff(Ctrl_R);
AppendCharToRedobuff(regname);
- do_put(regname, NULL, BACKWARD, 1L, PUT_CURSEND);
+ do_put(regname, NULL, BACKWARD, 1, PUT_CURSEND);
} else {
- stuffescaped((const char *)reg->y_array[i], literally);
+ stuffescaped(reg->y_array[i], literally);
}
// Insert a newline between lines and after last line if
// y_type is kMTLineWise.
@@ -1317,8 +1293,6 @@ int insert_reg(int regname, bool literally_arg)
/// @return true if "regname" is a special register,
bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
{
- size_t cnt;
-
*argp = NULL;
*allocated = false;
switch (regname) {
@@ -1366,7 +1340,7 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
return false;
}
*argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0),
- 1L, NULL);
+ 1, NULL);
*allocated = true;
return true;
@@ -1375,10 +1349,10 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
if (!errmsg) {
return false;
}
- cnt = find_ident_under_cursor(argp, (regname == Ctrl_W
- ? (FIND_IDENT|FIND_STRING)
- : FIND_STRING));
- *argp = cnt ? xstrnsave(*argp, cnt) : NULL;
+ size_t cnt = find_ident_under_cursor(argp, (regname == Ctrl_W
+ ? (FIND_IDENT|FIND_STRING)
+ : FIND_STRING));
+ *argp = cnt ? xmemdupz(*argp, cnt) : NULL;
*allocated = true;
return true;
@@ -1387,7 +1361,7 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
return false;
}
- *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false);
+ *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum);
return true;
case '_': // black hole: always empty
@@ -1454,10 +1428,7 @@ static void shift_delete_registers(bool y_append)
/// @return FAIL if undo failed, OK otherwise.
int op_delete(oparg_T *oap)
{
- int n;
linenr_T lnum;
- char *ptr;
- char *newp, *oldp;
struct block_def bd = { 0 };
linenr_T old_lcount = curbuf->b_ml.ml_line_count;
@@ -1490,7 +1461,7 @@ int op_delete(oparg_T *oap)
&& oap->line_count > 1
&& oap->motion_force == NUL
&& oap->op_type == OP_DELETE) {
- ptr = ml_get(oap->end.lnum) + oap->end.col;
+ char *ptr = ml_get(oap->end.lnum) + oap->end.col;
if (*ptr != NUL) {
ptr += oap->inclusive;
}
@@ -1584,12 +1555,12 @@ int op_delete(oparg_T *oap)
curwin->w_cursor.coladd = 0;
}
- // n == number of chars deleted
+ // "n" == number of chars deleted
// If we delete a TAB, it may be replaced by several characters.
// Thus the number of characters may increase!
- n = bd.textlen - bd.startspaces - bd.endspaces;
- oldp = ml_get(lnum);
- newp = xmalloc(strlen(oldp) - (size_t)n + 1);
+ int n = bd.textlen - bd.startspaces - bd.endspaces;
+ char *oldp = ml_get(lnum);
+ char *newp = xmalloc(strlen(oldp) - (size_t)n + 1);
// copy up to deleted part
memmove(newp, oldp, (size_t)bd.textcol);
// insert spaces
@@ -1607,8 +1578,8 @@ int op_delete(oparg_T *oap)
}
check_cursor_col();
- changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
- oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
+ oap->end.lnum + 1, 0, true);
oap->line_count = 0; // no lines deleted
} else if (oap->motion_type == kMTLineWise) {
if (oap->op_type == OP_CHANGE) {
@@ -1642,19 +1613,18 @@ int op_delete(oparg_T *oap)
// leave cursor past last char in line
if (oap->line_count > 1) {
- u_clearline(); // "U" command not possible after "2cc"
+ u_clearline(curbuf); // "U" command not possible after "2cc"
}
} else {
del_lines(oap->line_count, true);
beginline(BL_WHITE | BL_FIX);
- u_clearline(); // "U" command not possible after "dd"
+ u_clearline(curbuf); // "U" command not possible after "dd"
}
} else {
if (virtual_op) {
- int endcol = 0;
-
// For virtualedit: break the tabs that are partly included.
if (gchar_pos(&oap->start) == '\t') {
+ int endcol = 0;
if (u_save_cursor() == FAIL) { // save first line for undo
return FAIL;
}
@@ -1701,7 +1671,7 @@ int op_delete(oparg_T *oap)
display_dollar(oap->end.col - !oap->inclusive);
}
- n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
+ int n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
if (virtual_op) {
// fix up things for virtualedit-delete:
@@ -1732,8 +1702,8 @@ int op_delete(oparg_T *oap)
pos_T curpos;
// save deleted and changed lines for undo
- if (u_save((linenr_T)(curwin->w_cursor.lnum - 1),
- (linenr_T)(curwin->w_cursor.lnum + oap->line_count)) == FAIL) {
+ if (u_save(curwin->w_cursor.lnum - 1,
+ curwin->w_cursor.lnum + oap->line_count) == FAIL) {
return FAIL;
}
@@ -1750,7 +1720,7 @@ int op_delete(oparg_T *oap)
del_lines(oap->line_count - 2, false);
// delete from start of line until op_end
- n = (oap->end.col + 1 - !oap->inclusive);
+ int n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
@@ -1798,7 +1768,7 @@ static void mb_adjust_opend(oparg_T *oap)
static inline void pbyte(pos_T lp, int c)
{
assert(c <= UCHAR_MAX);
- *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char)c;
+ *(ml_get_buf_mut(curbuf, lp.lnum) + lp.col) = (char)c;
if (!curbuf_splice_pending) {
extmark_splice_cols(curbuf, (int)lp.lnum - 1, lp.col, 1, 1, kExtmarkUndo);
}
@@ -1820,10 +1790,7 @@ static void replace_character(int c)
/// Replace a whole area with one character.
static int op_replace(oparg_T *oap, int c)
{
- int n, numc;
- int num_chars;
- char *newp, *oldp;
- colnr_T oldlen;
+ int n;
struct block_def bd;
char *after_p = NULL;
int had_ctrl_v_cr = false;
@@ -1848,6 +1815,11 @@ static int op_replace(oparg_T *oap, int c)
// block mode replace
if (oap->motion_type == kMTBlockWise) {
+ int numc;
+ int num_chars;
+ char *newp;
+ char *oldp;
+ colnr_T oldlen;
bd.is_MAX = (curwin->w_curswant == MAXCOL);
for (; curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) {
curwin->w_cursor.col = 0; // make sure cursor position is valid
@@ -1947,7 +1919,7 @@ static int op_replace(oparg_T *oap, int c)
linenr_T baselnum = curwin->w_cursor.lnum;
if (after_p != NULL) {
ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
- appended_lines_mark(curwin->w_cursor.lnum, 1L);
+ appended_lines_mark(curwin->w_cursor.lnum, 1);
oap->end.lnum++;
xfree(after_p);
}
@@ -2043,7 +2015,7 @@ static int op_replace(oparg_T *oap, int c)
curwin->w_cursor = oap->start;
check_cursor();
- changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0, true);
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// Set "'[" and "']" marks.
@@ -2057,7 +2029,6 @@ static int op_replace(oparg_T *oap, int c)
/// Handle the (non-standard vi) tilde operator. Also for "gu", "gU" and "g?".
void op_tilde(oparg_T *oap)
{
- pos_T pos;
struct block_def bd;
int did_change = false;
@@ -2066,7 +2037,7 @@ void op_tilde(oparg_T *oap)
return;
}
- pos = oap->start;
+ pos_T pos = oap->start;
if (oap->motion_type == kMTBlockWise) { // Visual block mode
for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
int one_change;
@@ -2077,7 +2048,7 @@ void op_tilde(oparg_T *oap)
did_change |= one_change;
}
if (did_change) {
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
}
} else { // not block mode
if (oap->motion_type == kMTLineWise) {
@@ -2095,18 +2066,18 @@ void op_tilde(oparg_T *oap)
did_change = swapchars(oap->op_type, &pos,
oap->end.col - pos.col + 1);
} else {
- for (;;) {
+ while (true) {
did_change |= swapchars(oap->op_type, &pos,
- pos.lnum == oap->end.lnum ? oap->end.col + 1 :
- (int)strlen(ml_get_pos(&pos)));
+ pos.lnum == oap->end.lnum ? oap->end.col + 1
+ : (int)strlen(ml_get_pos(&pos)));
if (ltoreq(oap->end, pos) || inc(&pos) == -1) {
break;
}
}
}
if (did_change) {
- changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1,
- 0L, true);
+ changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1,
+ 0, true);
}
}
@@ -2122,8 +2093,7 @@ void op_tilde(oparg_T *oap)
}
if (oap->line_count > p_report) {
- smsg(NGETTEXT("%" PRId64 " line changed",
- "%" PRId64 " lines changed", oap->line_count),
+ smsg(0, NGETTEXT("%" PRId64 " line changed", "%" PRId64 " lines changed", oap->line_count),
(int64_t)oap->line_count);
}
}
@@ -2216,15 +2186,12 @@ bool swapchar(int op_type, pos_T *pos)
}
/// Insert and append operators for Visual mode.
-void op_insert(oparg_T *oap, long count1)
+void op_insert(oparg_T *oap, int count1)
{
- long ins_len, pre_textlen = 0;
- char *firstline, *ins_text;
- colnr_T ind_pre_col = 0, ind_post_col;
- int ind_pre_vcol = 0, ind_post_vcol = 0;
+ int pre_textlen = 0;
+ colnr_T ind_pre_col = 0;
+ int ind_pre_vcol = 0;
struct block_def bd;
- int i;
- pos_T t1;
// edit() changes this - record it for OP_APPEND
bd.is_MAX = (curwin->w_curswant == MAXCOL);
@@ -2261,12 +2228,12 @@ void op_insert(oparg_T *oap, long count1)
// Get indent information
ind_pre_col = (colnr_T)getwhitecols_curline();
ind_pre_vcol = get_indent();
- firstline = ml_get(oap->start.lnum) + bd.textcol;
+ char *firstline = ml_get(oap->start.lnum) + bd.textcol;
if (oap->op_type == OP_APPEND) {
firstline += bd.textlen;
}
- pre_textlen = (long)strlen(firstline);
+ pre_textlen = (int)strlen(firstline);
}
if (oap->op_type == OP_APPEND) {
@@ -2284,7 +2251,7 @@ void op_insert(oparg_T *oap, long count1)
if (u_save_cursor() == FAIL) {
return;
}
- for (i = 0; i < bd.endspaces; i++) {
+ for (int i = 0; i < bd.endspaces; i++) {
ins_char(' ');
}
bd.textlen += bd.endspaces;
@@ -2301,7 +2268,7 @@ void op_insert(oparg_T *oap, long count1)
}
}
- t1 = oap->start;
+ pos_T t1 = oap->start;
const pos_T start_insert = curwin->w_cursor;
(void)edit(NUL, false, (linenr_T)count1);
@@ -2321,12 +2288,13 @@ void op_insert(oparg_T *oap, long count1)
}
if (oap->motion_type == kMTBlockWise) {
+ int ind_post_vcol = 0;
struct block_def bd2;
bool did_indent = false;
// if indent kicked in, the firstline might have changed
// but only do that, if the indent actually increased
- ind_post_col = (colnr_T)getwhitecols_curline();
+ colnr_T ind_post_col = (colnr_T)getwhitecols_curline();
if (curbuf->b_op_start.col > ind_pre_col && ind_post_col > ind_pre_col) {
bd.textcol += ind_post_col - ind_pre_col;
ind_post_vcol = get_indent();
@@ -2391,7 +2359,7 @@ void op_insert(oparg_T *oap, long count1)
// Subsequent calls to ml_get() flush the firstline data - take a
// copy of the required string.
- firstline = ml_get(oap->start.lnum);
+ char *firstline = ml_get(oap->start.lnum);
const size_t len = strlen(firstline);
colnr_T add = bd.textcol;
colnr_T offset = 0; // offset when cursor was moved in insert mode
@@ -2414,9 +2382,9 @@ void op_insert(oparg_T *oap, long count1)
} else {
firstline += add;
}
- ins_len = (long)strlen(firstline) - pre_textlen - offset;
+ int ins_len = (int)strlen(firstline) - pre_textlen - offset;
if (pre_textlen >= 0 && ins_len > 0) {
- ins_text = xstrnsave(firstline, (size_t)ins_len);
+ char *ins_text = xmemdupz(firstline, (size_t)ins_len);
// block handled here
if (u_save(oap->start.lnum, (linenr_T)(oap->end.lnum + 1)) == OK) {
block_insert(oap, ins_text, (oap->op_type == OP_INSERT), &bd);
@@ -2434,20 +2402,12 @@ void op_insert(oparg_T *oap, long count1)
/// @return true if edit() returns because of a CTRL-O command
int op_change(oparg_T *oap)
{
- colnr_T l;
- int retval;
- long offset;
- linenr_T linenr;
- long ins_len;
- long pre_textlen = 0;
- long pre_indent = 0;
- char *newp;
+ int pre_textlen = 0;
+ int pre_indent = 0;
char *firstline;
- char *ins_text;
- char *oldp;
struct block_def bd;
- l = oap->start.col;
+ colnr_T l = oap->start.col;
if (oap->motion_type == kMTLineWise) {
l = 0;
can_si = may_do_si(); // Like opening a new line, do smart indent
@@ -2477,8 +2437,8 @@ int op_change(oparg_T *oap)
coladvance_force(getviscol());
}
firstline = ml_get(oap->start.lnum);
- pre_textlen = (long)strlen(firstline);
- pre_indent = (long)getwhitecols(firstline);
+ pre_textlen = (int)strlen(firstline);
+ pre_indent = (int)getwhitecols(firstline);
bd.textcol = curwin->w_cursor.col;
}
@@ -2490,7 +2450,7 @@ int op_change(oparg_T *oap)
const bool save_finish_op = finish_op;
finish_op = false;
- retval = edit(NUL, false, (linenr_T)1);
+ int retval = edit(NUL, false, 1);
finish_op = save_finish_op;
@@ -2499,23 +2459,27 @@ int op_change(oparg_T *oap)
// Don't repeat the insert when Insert mode ended with CTRL-C.
if (oap->motion_type == kMTBlockWise
&& oap->start.lnum != oap->end.lnum && !got_int) {
+ int ins_len;
// Auto-indenting may have changed the indent. If the cursor was past
// the indent, exclude that indent change from the inserted text.
firstline = ml_get(oap->start.lnum);
if (bd.textcol > (colnr_T)pre_indent) {
- long new_indent = (long)getwhitecols(firstline);
+ int new_indent = (int)getwhitecols(firstline);
pre_textlen += new_indent - pre_indent;
bd.textcol += (colnr_T)(new_indent - pre_indent);
}
- ins_len = (long)strlen(firstline) - pre_textlen;
+ ins_len = (int)strlen(firstline) - pre_textlen;
if (ins_len > 0) {
+ int offset;
+ char *newp;
+ char *oldp;
// Subsequent calls to ml_get() flush the firstline data - take a
// copy of the inserted text.
- ins_text = xmalloc((size_t)(ins_len + 1));
+ char *ins_text = xmalloc((size_t)ins_len + 1);
xstrlcpy(ins_text, firstline + bd.textcol, (size_t)ins_len + 1);
- for (linenr = oap->start.lnum + 1; linenr <= oap->end.lnum;
+ for (linenr_T linenr = oap->start.lnum + 1; linenr <= oap->end.lnum;
linenr++) {
block_prep(oap, &bd, linenr, true);
if (!bd.is_short || virtual_op) {
@@ -2543,11 +2507,11 @@ int op_change(oparg_T *oap)
STRMOVE(newp + offset, oldp);
ml_replace(linenr, newp, false);
extmark_splice_cols(curbuf, (int)linenr - 1, bd.textcol,
- 0, vpos.coladd + (int)ins_len, kExtmarkUndo);
+ 0, vpos.coladd + ins_len, kExtmarkUndo);
}
}
check_cursor();
- changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
xfree(ins_text);
}
}
@@ -2559,9 +2523,7 @@ int op_change(oparg_T *oap)
#if defined(EXITFREE)
void clear_registers(void)
{
- int i;
-
- for (i = 0; i < NUM_REGISTERS; i++) {
+ for (int i = 0; i < NUM_REGISTERS; i++) {
free_register(&y_regs[i]);
}
}
@@ -2617,14 +2579,9 @@ bool op_yank(oparg_T *oap, bool message)
static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
{
yankreg_T newreg; // new yank register when appending
- char **new_ptr;
- linenr_T lnum; // current line number
- size_t j;
MotionType yank_type = oap->motion_type;
size_t yanklines = (size_t)oap->line_count;
linenr_T yankendlnum = oap->end.lnum;
- char *p;
- char *pnew;
struct block_def bd;
yankreg_T *curr = reg; // copy of current register
@@ -2656,7 +2613,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
reg->timestamp = os_time();
size_t y_idx = 0; // index in y_array[]
- lnum = oap->start.lnum;
+ linenr_T lnum = oap->start.lnum; // current line number
if (yank_type == kMTBlockWise) {
// Visual block mode
@@ -2682,7 +2639,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
colnr_T startcol = 0, endcol = MAXCOL;
int is_oneChar = false;
colnr_T cs, ce;
- p = ml_get(lnum);
+ char *p = ml_get(lnum);
bd.startspaces = 0;
bd.endspaces = 0;
@@ -2692,8 +2649,10 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
getvcol(curwin, &oap->start, &cs, NULL, &ce);
if (ce != cs && oap->start.coladd > 0) {
// Part of a tab selected -- but don't double-count it.
- bd.startspaces = (ce - cs + 1)
- - oap->start.coladd;
+ bd.startspaces = (ce - cs + 1) - oap->start.coladd;
+ if (bd.startspaces < 0) {
+ bd.startspaces = 0;
+ }
startcol++;
}
}
@@ -2743,7 +2702,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
}
if (curr != reg) { // append the new block to the old block
- new_ptr = xmalloc(sizeof(char *) * (curr->y_size + reg->y_size));
+ size_t j;
+ char **new_ptr = xmalloc(sizeof(char *) * (curr->y_size + reg->y_size));
for (j = 0; j < curr->y_size; j++) {
new_ptr[j] = curr->y_array[j];
}
@@ -2759,8 +2719,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
// the new block, unless being Vi compatible.
if (curr->y_type == kMTCharWise
&& vim_strchr(p_cpo, CPO_REGAPPEND) == NULL) {
- pnew = xmalloc(strlen(curr->y_array[curr->y_size - 1])
- + strlen(reg->y_array[0]) + 1);
+ char *pnew = xmalloc(strlen(curr->y_array[curr->y_size - 1])
+ + strlen(reg->y_array[0]) + 1);
STRCPY(pnew, curr->y_array[--j]);
STRCAT(pnew, reg->y_array[0]);
xfree(curr->y_array[j]);
@@ -2797,12 +2757,12 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
update_screen();
}
if (yank_type == kMTBlockWise) {
- smsg(NGETTEXT("block of %" PRId64 " line yanked%s",
- "block of %" PRId64 " lines yanked%s", yanklines),
+ smsg(0, NGETTEXT("block of %" PRId64 " line yanked%s",
+ "block of %" PRId64 " lines yanked%s", yanklines),
(int64_t)yanklines, namebuf);
} else {
- smsg(NGETTEXT("%" PRId64 " line yanked%s",
- "%" PRId64 " lines yanked%s", yanklines),
+ smsg(0, NGETTEXT("%" PRId64 " line yanked%s",
+ "%" PRId64 " lines yanked%s", yanklines),
(int64_t)yanklines, namebuf);
}
}
@@ -2873,7 +2833,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
// The yanked text contents.
list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size);
for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(list, (const char *)reg->y_array[i], -1);
+ tv_list_append_string(list, reg->y_array[i], -1);
}
tv_list_set_lock(list, VAR_FIXED);
(void)tv_dict_add_list(dict, S_LEN("regcontents"), list);
@@ -2918,39 +2878,28 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
/// PUT_LINE force linewise put (":put")
/// PUT_BLOCK_INNER in block mode, do not add trailing spaces
/// @param dir BACKWARD for 'P', FORWARD for 'p'
-void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
+void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
{
- char *ptr;
- char *newp;
- char *oldp;
- int yanklen;
size_t totlen = 0; // init for gcc
linenr_T lnum = 0;
- colnr_T col = 0;
- size_t i; // index in y_array[]
MotionType y_type;
size_t y_size;
- size_t oldlen;
int y_width = 0;
colnr_T vcol = 0;
- int delcount;
int incr = 0;
struct block_def bd;
char **y_array = NULL;
linenr_T nr_lines = 0;
- pos_T new_cursor;
int indent;
int orig_indent = 0; // init for gcc
int indent_diff = 0; // init for gcc
bool first_indent = true;
int lendiff = 0;
- pos_T old_pos;
char *insert_string = NULL;
bool allocated = false;
- long cnt;
const pos_T orig_start = curbuf->b_op_start;
const pos_T orig_end = curbuf->b_op_end;
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
if (flags & PUT_FIXINDENT) {
orig_indent = get_indent();
@@ -2965,8 +2914,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
bool non_linewise_vis = (VIsual_active && VIsual_mode != 'V');
// PUT_LINE has special handling below which means we use 'i' to start.
- char command_start_char = non_linewise_vis ? 'c' :
- (flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i'));
+ char command_start_char = non_linewise_vis
+ ? 'c'
+ : (flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i'));
// To avoid 'autoindent' on linewise puts, create a new line with `:put _`.
if (flags & PUT_LINE) {
@@ -3062,9 +3012,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// For the = register we need to split the string at NL
// characters.
// Loop twice: count the number of lines and save them.
- for (;;) {
+ while (true) {
y_size = 0;
- ptr = insert_string;
+ char *ptr = insert_string;
while (ptr != NULL) {
if (y_array != NULL) {
y_array[y_size] = ptr;
@@ -3122,16 +3072,16 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (dir == FORWARD && *p != NUL) {
MB_PTR_ADV(p);
}
- ptr = xstrdup(p);
- ml_append(curwin->w_cursor.lnum, ptr, (colnr_T)0, false);
+ char *ptr = xstrdup(p);
+ ml_append(curwin->w_cursor.lnum, ptr, 0, false);
xfree(ptr);
- oldp = get_cursor_line_ptr();
+ char *oldp = get_cursor_line_ptr();
p = oldp + curwin->w_cursor.col;
if (dir == FORWARD && *p != NUL) {
MB_PTR_ADV(p);
}
- ptr = xstrnsave(oldp, (size_t)(p - oldp));
+ ptr = xmemdupz(oldp, (size_t)(p - oldp));
ml_replace(curwin->w_cursor.lnum, ptr, false);
nr_lines++;
dir = FORWARD;
@@ -3177,8 +3127,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
// In an empty buffer the empty line is going to be replaced, include
// it in the saved lines.
- if ((buf_is_empty(curbuf) ?
- u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) {
+ if ((buf_is_empty(curbuf)
+ ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) {
goto end;
}
if (dir == FORWARD) {
@@ -3191,12 +3141,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
goto end;
}
- yanklen = (int)strlen(y_array[0]);
+ int yanklen = (int)strlen(y_array[0]);
if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) {
if (gchar_cursor() == TAB) {
int viscol = getviscol();
- long ts = curbuf->b_p_ts;
+ OptInt ts = curbuf->b_p_ts;
// Don't need to insert spaces when "p" on the last position of a
// tab or "P" on the first position.
if (dir == FORWARD
@@ -3212,7 +3162,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
lnum = curwin->w_cursor.lnum;
- col = curwin->w_cursor.col;
+ colnr_T col = curwin->w_cursor.col;
// Block mode
if (y_type == kMTBlockWise) {
@@ -3253,7 +3203,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
curwin->w_cursor.coladd = 0;
bd.textcol = 0;
- for (i = 0; i < y_size; i++) {
+ for (size_t i = 0; i < y_size; i++) {
int spaces = 0;
char shortline;
// can just be 0 or 1, needed for blockwise paste beyond the current
@@ -3263,20 +3213,19 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
bd.startspaces = 0;
bd.endspaces = 0;
vcol = 0;
- delcount = 0;
+ int delcount = 0;
// add a new line
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- if (ml_append(curbuf->b_ml.ml_line_count, "",
- (colnr_T)1, false) == FAIL) {
+ if (ml_append(curbuf->b_ml.ml_line_count, "", 1, false) == FAIL) {
break;
}
nr_lines++;
lines_appended = 1;
}
// get the old line and advance to the position to insert at
- oldp = get_cursor_line_ptr();
- oldlen = strlen(oldp);
+ char *oldp = get_cursor_line_ptr();
+ size_t oldlen = strlen(oldp);
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, oldp, oldp);
@@ -3286,7 +3235,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
cts.cts_vcol += incr;
}
vcol = cts.cts_vcol;
- ptr = cts.cts_ptr;
+ char *ptr = cts.cts_ptr;
bd.textcol = (colnr_T)(ptr - oldp);
clear_chartabsize_arg(&cts);
@@ -3335,8 +3284,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
break;
}
- totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces);
- newp = xmalloc(totlen + oldlen + 1);
+ totlen = (size_t)count * (size_t)(yanklen + spaces) + (size_t)bd.startspaces +
+ (size_t)bd.endspaces;
+ char *newp = xmalloc(totlen + oldlen + 1);
// copy part up to cursor to new line
ptr = newp;
@@ -3348,12 +3298,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
ptr += bd.startspaces;
// insert the new text
- for (long j = 0; j < count; j++) {
+ for (int j = 0; j < count; j++) {
memmove(ptr, y_array[i], (size_t)yanklen);
ptr += yanklen;
// insert block's trailing spaces only if there's text behind
- if ((j < count - 1 || !shortline) && spaces) {
+ if ((j < count - 1 || !shortline) && spaces > 0) {
memset(ptr, ' ', (size_t)spaces);
ptr += spaces;
} else {
@@ -3379,7 +3329,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
- changed_lines(lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size
+ changed_lines(curbuf, lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size
- nr_lines, nr_lines, true);
// Set '[ mark.
@@ -3427,7 +3377,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// Line mode: BACKWARD is the same as FORWARD on the previous line
lnum--;
}
- new_cursor = curwin->w_cursor;
+ pos_T new_cursor = curwin->w_cursor;
// simple case: insert into one line at a time
if (y_type == kMTCharWise && y_size == 1) {
@@ -3461,10 +3411,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// multiplication overflow
emsg(_(e_resulting_text_too_long));
} else {
- totlen = (size_t)(count * yanklen);
+ totlen = (size_t)count * (size_t)yanklen;
do {
- oldp = ml_get(lnum);
- oldlen = strlen(oldp);
+ char *oldp = ml_get(lnum);
+ size_t oldlen = strlen(oldp);
if (lnum > start_lnum) {
pos_T pos = {
.lnum = lnum,
@@ -3479,10 +3429,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
lnum++;
continue;
}
- newp = xmalloc(totlen + oldlen + 1);
+ char *newp = xmalloc(totlen + oldlen + 1);
memmove(newp, oldp, (size_t)col);
- ptr = newp + col;
- for (i = 0; i < (size_t)count; i++) {
+ char *ptr = newp + col;
+ for (size_t i = 0; i < (size_t)count; i++) {
memmove(ptr, y_array[0], (size_t)yanklen);
ptr += yanklen;
}
@@ -3495,7 +3445,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// Place cursor on last putted char.
if (lnum == curwin->w_cursor.lnum) {
// make sure curwin->w_virtcol is updated
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
+ invalidate_botline(curwin);
curwin->w_cursor.col += (colnr_T)(totlen - 1);
}
changed_bytes(lnum, col);
@@ -3523,28 +3474,27 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
} else {
linenr_T new_lnum = new_cursor.lnum;
- size_t len;
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
- for (cnt = 1; cnt <= count; cnt++) {
- i = 0;
+ for (int cnt = 1; cnt <= count; cnt++) {
+ size_t i = 0;
if (y_type == kMTCharWise) {
// Split the current line in two at the insert position.
// First insert y_array[size - 1] in front of second line.
// Then append y_array[0] to first line.
lnum = new_cursor.lnum;
- ptr = ml_get(lnum) + col;
+ char *ptr = ml_get(lnum) + col;
totlen = strlen(y_array[y_size - 1]);
- newp = xmalloc((size_t)(strlen(ptr) + totlen + 1));
+ char *newp = xmalloc((size_t)(strlen(ptr) + totlen + 1));
STRCPY(newp, y_array[y_size - 1]);
STRCAT(newp, ptr);
// insert second line
- ml_append(lnum, newp, (colnr_T)0, false);
+ ml_append(lnum, newp, 0, false);
new_lnum++;
xfree(newp);
- oldp = ml_get(lnum);
+ char *oldp = ml_get(lnum);
newp = xmalloc((size_t)col + (size_t)yanklen + 1);
// copy first part of line
memmove(newp, oldp, (size_t)col);
@@ -3558,7 +3508,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
for (; i < y_size; i++) {
if ((y_type != kMTCharWise || i < y_size - 1)) {
- if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) {
+ if (ml_append(lnum, y_array[i], 0, false) == FAIL) {
goto error;
}
new_lnum++;
@@ -3566,9 +3516,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
lnum++;
nr_lines++;
if (flags & PUT_FIXINDENT) {
- old_pos = curwin->w_cursor;
+ pos_T old_pos = curwin->w_cursor;
curwin->w_cursor.lnum = lnum;
- ptr = ml_get(lnum);
+ char *ptr = ml_get(lnum);
if (cnt == count && i == y_size - 1) {
lendiff = (int)strlen(ptr);
}
@@ -3629,21 +3579,21 @@ error:
ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT))
? kExtmarkUndo : kExtmarkNOOP;
mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
- (linenr_T)MAXLNUM, nr_lines, 0L, kind);
+ (linenr_T)MAXLNUM, nr_lines, 0, kind);
// note changed text for displaying and folding
if (y_type == kMTCharWise) {
- changed_lines(curwin->w_cursor.lnum, col,
+ changed_lines(curbuf, curwin->w_cursor.lnum, col,
curwin->w_cursor.lnum + 1, nr_lines, true);
} else {
- changed_lines(curbuf->b_op_start.lnum, 0,
+ changed_lines(curbuf, curbuf->b_op_start.lnum, 0,
curbuf->b_op_start.lnum, nr_lines, true);
}
// Put the '] mark on the first byte of the last inserted character.
// Correct the length for change in indent.
curbuf->b_op_end.lnum = new_lnum;
- len = strlen(y_array[y_size - 1]);
+ size_t len = strlen(y_array[y_size - 1]);
col = (colnr_T)len - lendiff;
if (col > 1) {
curbuf->b_op_end.col = col - 1;
@@ -3692,6 +3642,15 @@ error:
msgmore(nr_lines);
curwin->w_set_curswant = true;
+ // Make sure the cursor is not after the NUL.
+ int len = (int)strlen(get_cursor_line_ptr());
+ if (curwin->w_cursor.col > len) {
+ if (cur_ve_flags == VE_ALL) {
+ curwin->w_cursor.coladd = curwin->w_cursor.col - len;
+ }
+ curwin->w_cursor.col = len;
+ }
+
end:
if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
curbuf->b_op_start = orig_start;
@@ -3714,7 +3673,7 @@ end:
/// there move it left.
void adjust_cursor_eol(void)
{
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
const bool adj_cursor = (curwin->w_cursor.col > 0
&& gchar_cursor() == NUL
@@ -3773,9 +3732,7 @@ void ex_display(exarg_T *eap)
{
char *p;
yankreg_T *yb;
- int name;
char *arg = eap->arg;
- int clen;
int type;
if (arg != NULL && *arg == NUL) {
@@ -3786,7 +3743,7 @@ void ex_display(exarg_T *eap)
// Highlight title
msg_puts_title(_("\nType Name Content"));
for (int i = -1; i < NUM_REGISTERS && !got_int; i++) {
- name = get_register_name(i);
+ int name = get_register_name(i);
switch (get_reg_type(name, NULL)) {
case kMTLineWise:
type = 'l'; break;
@@ -3841,9 +3798,9 @@ void ex_display(exarg_T *eap)
n -= 2;
}
for (p = yb->y_array[j];
- *p != NUL && (n -= ptr2cells(p)) >= 0; p++) { // -V1019
- clen = utfc_ptr2len(p);
- msg_outtrans_len(p, clen);
+ *p != NUL && (n -= ptr2cells(p)) >= 0; p++) {
+ int clen = utfc_ptr2len(p);
+ msg_outtrans_len(p, clen, 0);
p += clen - 1;
}
}
@@ -3856,7 +3813,7 @@ void ex_display(exarg_T *eap)
}
// display last inserted text
- if ((p = (char *)get_last_insert()) != NULL
+ if ((p = get_last_insert()) != NULL
&& (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int
&& !message_filtered(p)) {
msg_puts("\n c \". ");
@@ -3912,18 +3869,16 @@ void ex_display(exarg_T *eap)
static void dis_msg(const char *p, bool skip_esc)
FUNC_ATTR_NONNULL_ALL
{
- int n;
- int l;
-
- n = Columns - 6;
+ int n = Columns - 6;
while (*p != NUL
&& !(*p == ESC && skip_esc && *(p + 1) == NUL)
&& (n -= ptr2cells(p)) >= 0) {
+ int l;
if ((l = utfc_ptr2len(p)) > 1) {
- msg_outtrans_len(p, l);
+ msg_outtrans_len(p, l, 0);
p += l;
} else {
- msg_outtrans_len(p++, 1);
+ msg_outtrans_len(p++, 1, 0);
}
}
os_breakcheck();
@@ -3943,7 +3898,6 @@ static void dis_msg(const char *p, bool skip_esc)
char *skip_comment(char *line, bool process, bool include_space, bool *is_comment)
{
char *comment_flags = NULL;
- int lead_len;
int leader_offset = get_last_leader_offset(line, &comment_flags);
*is_comment = false;
@@ -3966,7 +3920,7 @@ char *skip_comment(char *line, bool process, bool include_space, bool *is_commen
return line;
}
- lead_len = get_leader_len(line, &comment_flags, false, include_space);
+ int lead_len = get_leader_len(line, &comment_flags, false, include_space);
if (lead_len == 0) {
return line;
@@ -4007,14 +3961,10 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
char *curr = NULL;
char *curr_start = NULL;
char *cend;
- char *newp;
- char *spaces; // number of spaces inserted before a line
int endcurr1 = NUL;
int endcurr2 = NUL;
int currsize = 0; // size of the current line
int sumsize = 0; // size of the long new line
- linenr_T t;
- colnr_T col = 0;
int ret = OK;
int *comments = NULL;
int remove_comments = (use_formatoptions == true)
@@ -4029,15 +3979,16 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// Allocate an array to store the number of spaces inserted before each
// line. We will use it to pre-compute the length of the new line and the
// proper placement of each original line in the new one.
- spaces = xcalloc(count, 1);
+ char *spaces = xcalloc(count, 1); // number of spaces inserted before a line
if (remove_comments) {
comments = xcalloc(count, sizeof(*comments));
}
- // Don't move anything, just compute the final line length
+ // Don't move anything yet, just compute the final line length
// and setup the array of space strings lengths
- for (t = 0; t < (linenr_T)count; t++) {
- curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t));
+ // This loops forward over joined lines.
+ for (linenr_T t = 0; t < (linenr_T)count; t++) {
+ curr_start = ml_get(curwin->w_cursor.lnum + t);
curr = curr_start;
if (t == 0 && setmark && (cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// Set the '[ mark.
@@ -4108,14 +4059,15 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
}
// store the column position before last line
- col = sumsize - currsize - spaces[count - 1];
+ colnr_T col = sumsize - currsize - spaces[count - 1];
// allocate the space for the new line
- newp = xmalloc((size_t)sumsize + 1);
+ char *newp = xmalloc((size_t)sumsize + 1);
cend = newp + sumsize;
*cend = 0;
// Move affected lines to the new long one.
+ // This loops backwards over the joined lines, including the original line.
//
// Move marks from each deleted line to the joined line, adjusting the
// column. This is not Vi compatible, but Vi deletes the marks, thus that
@@ -4123,7 +4075,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
curbuf_splice_pending++;
- for (t = (linenr_T)count - 1;; t--) {
+ for (linenr_T t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
if (spaces[t] > 0) {
@@ -4135,9 +4087,9 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// what is added if it is inside these spaces.
const int spaces_removed = (int)((curr - curr_start) - spaces[t]);
linenr_T lnum = curwin->w_cursor.lnum + t;
- colnr_T mincol = (colnr_T)0;
+ colnr_T mincol = 0;
linenr_T lnum_amount = -t;
- long col_amount = (cend - newp - spaces_removed);
+ colnr_T col_amount = (colnr_T)(cend - newp - spaces_removed);
mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed);
@@ -4166,15 +4118,15 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// Only report the change in the first line here, del_lines() will report
// the deleted line.
- changed_lines(curwin->w_cursor.lnum, currsize,
- curwin->w_cursor.lnum + 1, 0L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, currsize,
+ curwin->w_cursor.lnum + 1, 0, true);
// Delete following lines. To do this we move the cursor there
// briefly, and then move it back. After del_lines() the cursor may
// have moved up (last line deleted), so the current lnum is kept in t.
- t = curwin->w_cursor.lnum;
+ linenr_T t = curwin->w_cursor.lnum;
curwin->w_cursor.lnum++;
- del_lines((long)count - 1, false);
+ del_lines((int)count - 1, false);
curwin->w_cursor.lnum = t;
curbuf_splice_pending--;
curbuf->deleted_bytes2 = 0;
@@ -4236,11 +4188,6 @@ static void restore_lbr(bool lbr_saved)
static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del)
{
int incr = 0;
- char *pend;
- char *pstart;
- char *line;
- char *prev_pstart;
- char *prev_pend;
// Avoid a problem with unwanted linebreaks in block mode.
const bool lbr_saved = reset_lbr();
@@ -4256,8 +4203,8 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
bdp->end_char_vcols = 0;
bdp->start_char_vcols = 0;
- line = ml_get(lnum);
- prev_pstart = line;
+ char *line = ml_get(lnum);
+ char *prev_pstart = line;
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, lnum, bdp->start_vcol, line, line);
@@ -4276,7 +4223,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
MB_PTR_ADV(cts.cts_ptr);
}
bdp->start_vcol = cts.cts_vcol;
- pstart = cts.cts_ptr;
+ char *pstart = cts.cts_ptr;
clear_chartabsize_arg(&cts);
bdp->start_char_vcols = incr;
@@ -4293,7 +4240,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
if (is_del && bdp->startspaces) {
bdp->startspaces = bdp->start_char_vcols - bdp->startspaces;
}
- pend = pstart;
+ char *pend = pstart;
bdp->end_vcol = bdp->start_vcol;
if (bdp->end_vcol > oap->end_vcol) { // it's all in one character
bdp->is_oneChar = true;
@@ -4315,7 +4262,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
}
} else {
init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol, line, pend);
- prev_pend = pend;
+ char *prev_pend = pend;
while (cts.cts_vcol <= oap->end_vcol && *cts.cts_ptr != NUL) {
// Count a tab for what it's worth (if list mode not on)
prev_pend = cts.cts_ptr;
@@ -4367,7 +4314,6 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
/// @param[in] g_cmd Prefixed with `g`.
void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
{
- pos_T pos;
struct block_def bd;
ssize_t change_cnt = 0;
linenr_T amount = Prenum1;
@@ -4378,7 +4324,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
disable_fold_update++;
if (!VIsual_active) {
- pos = curwin->w_cursor;
+ pos_T pos = curwin->w_cursor;
if (u_save_cursor() == FAIL) {
disable_fold_update--;
return;
@@ -4386,10 +4332,9 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
change_cnt = do_addsub(oap->op_type, &pos, 0, amount);
disable_fold_update--;
if (change_cnt) {
- changed_lines(pos.lnum, 0, pos.lnum + 1, 0L, true);
+ changed_lines(curbuf, pos.lnum, 0, pos.lnum + 1, 0, true);
}
} else {
- int one_change;
int length;
pos_T startpos;
@@ -4399,7 +4344,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
return;
}
- pos = oap->start;
+ pos_T pos = oap->start;
for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
if (oap->motion_type == kMTBlockWise) {
// Visual block mode
@@ -4429,7 +4374,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
length = oap->end.col - pos.col + 1;
}
}
- one_change = do_addsub(oap->op_type, &pos, length, amount);
+ int one_change = do_addsub(oap->op_type, &pos, length, amount);
if (one_change) {
// Remember the start position of the first change.
if (change_cnt == 0) {
@@ -4445,7 +4390,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
disable_fold_update--;
if (change_cnt) {
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
}
if (!change_cnt && oap->is_VIsual) {
@@ -4460,8 +4405,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
}
if (change_cnt > p_report) {
- smsg(NGETTEXT("%" PRId64 " lines changed",
- "%" PRId64 " lines changed", change_cnt),
+ smsg(0, NGETTEXT("%" PRId64 " lines changed", "%" PRId64 " lines changed", change_cnt),
(int64_t)change_cnt);
}
}
@@ -4477,17 +4421,11 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
/// @return true if some character was changed.
int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
{
- int col;
char *buf1 = NULL;
char buf2[NUMBUFLEN];
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
uvarnumber_T n;
- uvarnumber_T oldn;
- char *ptr;
- int c;
- int todel;
- int firstdigit;
bool negative = false;
bool was_positive = true;
bool visual = VIsual_active;
@@ -4511,8 +4449,8 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
curwin->w_cursor = *pos;
- ptr = ml_get(pos->lnum);
- col = pos->col;
+ char *ptr = ml_get(pos->lnum);
+ int col = pos->col;
if (*ptr == NUL || col + !!save_coladd >= (int)strlen(ptr)) {
goto theend;
@@ -4605,7 +4543,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
// If a number was found, and saving for undo works, replace the number.
- firstdigit = (uint8_t)ptr[col];
+ int firstdigit = (uint8_t)ptr[col];
if (!ascii_isdigit(firstdigit) && !(do_alpha && ASCII_ISALPHA(firstdigit))) {
beep_flush();
goto theend;
@@ -4658,11 +4596,12 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
: length);
}
+ bool overflow = false;
vim_str2nr(ptr + col, &pre, &length,
0 + (do_bin ? STR2NR_BIN : 0)
+ (do_oct ? STR2NR_OCT : 0)
+ (do_hex ? STR2NR_HEX : 0),
- NULL, &n, maxlen, false);
+ NULL, &n, maxlen, false, &overflow);
// ignore leading '-' for hex, octal and bin numbers
if (pre && negative) {
@@ -4680,10 +4619,12 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
subtract ^= true;
}
- oldn = n;
+ uvarnumber_T oldn = n;
- n = subtract ? n - (uvarnumber_T)Prenum1
- : n + (uvarnumber_T)Prenum1;
+ if (!overflow) { // if number is too big don't add/subtract
+ n = subtract ? n - (uvarnumber_T)Prenum1
+ : n + (uvarnumber_T)Prenum1;
+ }
// handle wraparound for decimal numbers
if (!pre) {
@@ -4707,7 +4648,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
if (do_unsigned && negative) {
if (subtract) {
// sticking at zero.
- n = (uvarnumber_T)0;
+ n = 0;
} else {
// sticking at 2^64 - 1.
n = (uvarnumber_T)(-1);
@@ -4725,8 +4666,8 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
curwin->w_cursor.col = col;
startpos = curwin->w_cursor;
did_change = true;
- todel = length;
- c = gchar_cursor();
+ int todel = length;
+ int c = gchar_cursor();
// Don't include the '-' in the length, only the length of the part
// after it is kept the same.
@@ -4775,7 +4716,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
}
- while (bits > 0) {
+ while (bits > 0 && i < NUMBUFLEN - 1) {
buf2[i++] = ((n >> --bits) & 0x1) ? '1' : '0';
}
@@ -4961,7 +4902,7 @@ void *get_reg_contents(int regname, int flags)
if (flags & kGRegList) {
list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size);
for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(list, (const char *)reg->y_array[i], -1);
+ tv_list_append_string(list, reg->y_array[i], -1);
}
return list;
@@ -5030,7 +4971,7 @@ static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous
/// @see write_reg_contents_ex
void write_reg_contents(int name, const char *str, ssize_t len, int must_append)
{
- write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0L);
+ write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0);
}
void write_reg_contents_lst(int name, char **strings, bool must_append, MotionType yank_type,
@@ -5041,8 +4982,7 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy
if (strings[0] == NULL) {
s = "";
} else if (strings[1] != NULL) {
- emsg(_("E883: search pattern and expression register may not "
- "contain two or more lines"));
+ emsg(_(e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines));
return;
}
write_reg_contents_ex(name, s, -1, must_append, yank_type, block_len);
@@ -5160,7 +5100,7 @@ void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_app
/// @param str string or list of strings to put in register
/// @param len length of the string (Ignored when str_list=true.)
/// @param blocklen width of visual block, or -1 for "I don't know."
-/// @param str_list True if str is `char_u **`.
+/// @param str_list True if str is `char **`.
static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, size_t len,
colnr_T blocklen, bool str_list)
FUNC_ATTR_NONNULL_ALL
@@ -5320,10 +5260,8 @@ static varnumber_T line_count_info(char *line, varnumber_T *wc, varnumber_T *cc,
/// @param dict when not NULL, store the info there instead of showing it.
void cursor_pos_info(dict_T *dict)
{
- char *p;
char buf1[50];
char buf2[40];
- linenr_T lnum;
varnumber_T byte_count = 0;
varnumber_T bom_count = 0;
varnumber_T byte_count_cursor = 0;
@@ -5331,9 +5269,6 @@ void cursor_pos_info(dict_T *dict)
varnumber_T char_count_cursor = 0;
varnumber_T word_count = 0;
varnumber_T word_count_cursor = 0;
- int eol_size;
- varnumber_T last_check = 100000L;
- long line_count_selected = 0;
pos_T min_pos, max_pos;
oparg_T oparg;
struct block_def bd;
@@ -5343,10 +5278,13 @@ void cursor_pos_info(dict_T *dict)
// Compute the length of the file in characters.
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
if (dict == NULL) {
- msg(_(no_lines_msg));
+ msg(_(no_lines_msg), 0);
return;
}
} else {
+ int eol_size;
+ varnumber_T last_check = 100000;
+ int line_count_selected = 0;
if (get_fileformat(curbuf) == EOL_DOS) {
eol_size = 2;
} else {
@@ -5370,8 +5308,8 @@ void cursor_pos_info(dict_T *dict)
char *const saved_w_sbr = curwin->w_p_sbr;
// Make 'sbr' empty for a moment to get the correct size.
- p_sbr = empty_option;
- curwin->w_p_sbr = empty_option;
+ p_sbr = empty_string_option;
+ curwin->w_p_sbr = empty_string_option;
oparg.is_VIsual = true;
oparg.motion_type = kMTBlockWise;
oparg.op_type = OP_NOP;
@@ -5391,21 +5329,21 @@ void cursor_pos_info(dict_T *dict)
line_count_selected = max_pos.lnum - min_pos.lnum + 1;
}
- for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
+ for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
// Check for a CTRL-C every 100000 characters.
if (byte_count > last_check) {
os_breakcheck();
if (got_int) {
return;
}
- last_check = byte_count + 100000L;
+ last_check = byte_count + 100000;
}
// Do extra processing for VIsual mode.
if (l_VIsual_active
&& lnum >= min_pos.lnum && lnum <= max_pos.lnum) {
char *s = NULL;
- long len = 0L;
+ int len = 0;
switch (l_VIsual_mode) {
case Ctrl_V:
@@ -5413,7 +5351,7 @@ void cursor_pos_info(dict_T *dict)
block_prep(&oparg, &bd, lnum, false);
virtual_op = kNone;
s = bd.textstart;
- len = (long)bd.textlen;
+ len = bd.textlen;
break;
case 'V':
s = ml_get(lnum);
@@ -5436,7 +5374,7 @@ void cursor_pos_info(dict_T *dict)
if (lnum == curbuf->b_ml.ml_line_count
&& !curbuf->b_p_eol
&& (curbuf->b_p_bin || !curbuf->b_p_fixeol)
- && (long)strlen(s) < len) {
+ && (int)strlen(s) < len) {
byte_count_cursor -= eol_size;
}
}
@@ -5497,11 +5435,11 @@ void cursor_pos_info(dict_T *dict)
(int64_t)byte_count_cursor, (int64_t)byte_count);
}
} else {
- p = get_cursor_line_ptr();
+ char *p = get_cursor_line_ptr();
validate_virtcol();
col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1,
(int)curwin->w_virtcol + 1);
- col_print((char *)buf2, sizeof(buf2), (int)strlen(p), linetabsize(p));
+ col_print(buf2, sizeof(buf2), (int)strlen(p), linetabsize_str(p));
if (char_count_cursor == byte_count_cursor
&& char_count == byte_count) {
@@ -5538,13 +5476,13 @@ void cursor_pos_info(dict_T *dict)
}
if (dict == NULL) {
// Don't shorten this message, the user asked for it.
- p = p_shm;
+ char *p = p_shm;
p_shm = "";
if (p_ch < 1) {
msg_start();
msg_scroll = true;
}
- msg(IObuff);
+ msg(IObuff, 0);
p_shm = p;
}
}
@@ -5553,7 +5491,7 @@ void cursor_pos_info(dict_T *dict)
// Don't shorten this message, the user asked for it.
tv_dict_add_nr(dict, S_LEN("words"), word_count);
tv_dict_add_nr(dict, S_LEN("chars"), char_count);
- tv_dict_add_nr(dict, S_LEN("bytes"), (varnumber_T)(byte_count + bom_count));
+ tv_dict_add_nr(dict, S_LEN("bytes"), byte_count + bom_count);
STATIC_ASSERT(sizeof("visual") == sizeof("cursor"),
"key_len argument in tv_dict_add_nr is wrong");
@@ -5577,7 +5515,7 @@ static void op_colon(oparg_T *oap)
if (oap->start.lnum == curwin->w_cursor.lnum) {
stuffcharReadbuff('.');
} else {
- stuffnumReadbuff((long)oap->start.lnum);
+ stuffnumReadbuff(oap->start.lnum);
}
// When using !! on a closed fold the range ".!" works best to operate
@@ -5598,7 +5536,7 @@ static void op_colon(oparg_T *oap)
stuffReadbuff(".+");
stuffnumReadbuff(oap->line_count - 1);
} else {
- stuffnumReadbuff((long)oap->end.lnum);
+ stuffnumReadbuff(oap->end.lnum);
}
}
}
@@ -5606,13 +5544,13 @@ static void op_colon(oparg_T *oap)
stuffReadbuff("!");
}
if (oap->op_type == OP_INDENT) {
- stuffReadbuff((const char *)get_equalprg());
+ stuffReadbuff(get_equalprg());
stuffReadbuff("\n");
} else if (oap->op_type == OP_FORMAT) {
if (*curbuf->b_p_fp != NUL) {
- stuffReadbuff((const char *)curbuf->b_p_fp);
+ stuffReadbuff(curbuf->b_p_fp);
} else if (*p_fp != NUL) {
- stuffReadbuff((const char *)p_fp);
+ stuffReadbuff(p_fp);
} else {
stuffReadbuff("fmt");
}
@@ -5626,11 +5564,12 @@ static void op_colon(oparg_T *oap)
static Callback opfunc_cb;
/// Process the 'operatorfunc' option value.
-void set_operatorfunc_option(char **errmsg)
+const char *did_set_operatorfunc(optset_T *args FUNC_ATTR_UNUSED)
{
if (option_set_callback_func(p_opfunc, &opfunc_cb) == FAIL) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
#if defined(EXITFREE)
@@ -5768,31 +5707,34 @@ typedef struct {
int rv_mode; ///< 'v', 'V', or Ctrl-V
linenr_T rv_line_count; ///< number of lines
colnr_T rv_vcol; ///< number of cols or end column
- long rv_count; ///< count for Visual operator
+ int rv_count; ///< count for Visual operator
int rv_arg; ///< extra argument
} redo_VIsual_T;
+static bool is_ex_cmdchar(cmdarg_T *cap)
+{
+ return cap->cmdchar == ':' || cap->cmdchar == K_COMMAND;
+}
+
/// Handle an operator after Visual mode or when the movement is finished.
/// "gui_yank" is true when yanking text for the clipboard.
void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
{
oparg_T *oap = cap->oap;
- pos_T old_cursor;
- bool empty_region_error;
- int restart_edit_save;
int lbr_saved = curwin->w_p_lbr;
// The visual area is remembered for redo
static redo_VIsual_T redo_VIsual = { NUL, 0, 0, 0, 0 };
- bool include_line_break = false;
-
- old_cursor = curwin->w_cursor;
+ pos_T old_cursor = curwin->w_cursor;
// If an operation is pending, handle it...
if ((finish_op
|| VIsual_active)
&& oap->op_type != OP_NOP) {
+ bool empty_region_error;
+ int restart_edit_save;
+ bool include_line_break = false;
// Yank can be redone when 'y' is in 'cpoptions', but not when yanking
// for the clipboard.
const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank;
@@ -5828,7 +5770,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if ((redo_yank || oap->op_type != OP_YANK)
&& ((!VIsual_active || oap->motion_force)
// Also redo Operator-pending Visual mode mappings.
- || ((cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
+ || ((is_ex_cmdchar(cap) || cap->cmdchar == K_LUA)
&& oap->op_type != OP_COLON))
&& cap->cmdchar != 'D'
&& oap->op_type != OP_FOLD
@@ -5848,17 +5790,24 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
AppendToRedobuffLit(cap->searchbuf, -1);
}
AppendToRedobuff(NL_STR);
- } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) {
+ } else if (is_ex_cmdchar(cap)) {
// do_cmdline() has stored the first typed line in
// "repeat_cmdline". When several lines are typed repeating
// won't be possible.
if (repeat_cmdline == NULL) {
ResetRedobuff();
} else {
- AppendToRedobuffLit(repeat_cmdline, -1);
+ if (cap->cmdchar == ':') {
+ AppendToRedobuffLit(repeat_cmdline, -1);
+ } else {
+ AppendToRedobuffSpec(repeat_cmdline);
+ }
AppendToRedobuff(NL_STR);
XFREE_CLEAR(repeat_cmdline);
}
+ } else if (cap->cmdchar == K_LUA) {
+ AppendNumberToRedobuff(repeat_luaref);
+ AppendToRedobuff(NL_STR);
}
}
@@ -5914,8 +5863,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
} else if (VIsual_mode == 'v') {
// If 'selection' is "exclusive", backup one character for
// charwise selections.
- include_line_break =
- unadjust_for_sel();
+ include_line_break = unadjust_for_sel();
}
oap->start = VIsual;
@@ -5994,7 +5942,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
resel_VIsual_vcol = oap->end_vcol;
}
}
- resel_VIsual_line_count = (linenr_T)oap->line_count;
+ resel_VIsual_line_count = oap->line_count;
}
// can't redo yank (unless 'y' is in 'cpoptions') and ":"
@@ -6015,7 +5963,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
prep_redo(oap->regname, cap->count0,
get_op_char(oap->op_type), get_extra_op_char(oap->op_type),
oap->motion_force, cap->cmdchar, cap->nchar);
- } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) {
+ } else if (!is_ex_cmdchar(cap) && cap->cmdchar != K_LUA) {
int opchar = get_op_char(oap->op_type);
int extra_opchar = get_extra_op_char(oap->op_type);
int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL;
@@ -6029,9 +5977,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (opchar == 'g' && extra_opchar == '@') {
// also repeat the count for 'operatorfunc'
- prep_redo_num2(oap->regname, 0L, NUL, 'v', cap->count0, opchar, extra_opchar, nchar);
+ prep_redo_num2(oap->regname, 0, NUL, 'v', cap->count0, opchar, extra_opchar, nchar);
} else {
- prep_redo(oap->regname, 0L, NUL, 'v', opchar, extra_opchar, nchar);
+ prep_redo(oap->regname, 0, NUL, 'v', opchar, extra_opchar, nchar);
}
}
if (!redo_VIsual_busy) {
@@ -6153,7 +6101,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
switch (oap->op_type) {
case OP_LSHIFT:
case OP_RSHIFT:
- op_shift(oap, true, oap->is_VIsual ? (int)cap->count1 : 1);
+ op_shift(oap, true, oap->is_VIsual ? cap->count1 : 1);
auto_format(false, true);
break;
@@ -6221,6 +6169,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
// Restore linebreak, so that when the user edits it looks as before.
restore_lbr(lbr_saved);
+ // trigger TextChangedI
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+
if (op_change(oap)) { // will call edit()
cap->retval |= CA_COMMAND_BUSY;
}
@@ -6252,8 +6203,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
break;
}
op_reindent(oap,
- *curbuf->b_p_inde != NUL ? get_expr_indent :
- get_c_indent);
+ *curbuf->b_p_inde != NUL ? get_expr_indent
+ : get_c_indent);
break;
}
@@ -6319,6 +6270,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
// Restore linebreak, so that when the user edits it looks as before.
restore_lbr(lbr_saved);
+ // trigger TextChangedI
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+
op_insert(oap, cap->count1);
// Reset linebreak, so that formatting works correctly.
@@ -6448,7 +6402,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
clipboard_didwarn = true;
// Do NOT error (emsg()) here--if it interrupts :redir we get into
// a weird state, stuck in "redirect mode".
- msg(MSG_NO_CLIP);
+ msg(MSG_NO_CLIP, 0);
}
// ... else, be silent (don't flood during :while, :redir, etc.).
goto end;
@@ -6471,7 +6425,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
}
if (cb_flags & CB_UNNAMEDPLUS) {
- *name = (cb_flags & CB_UNNAMED && writing) ? '"': '+';
+ *name = (cb_flags & CB_UNNAMED && writing) ? '"' : '+';
target = &y_regs[PLUS_REGISTER];
} else {
*name = '*';
@@ -6640,7 +6594,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) {
goto err;
}
- reg->y_array[tv_idx++] = xstrdupnul((const char *)TV_LIST_ITEM_TV(li)->vval.v_string);
+ reg->y_array[tv_idx++] = xstrdupnul(TV_LIST_ITEM_TV(li)->vval.v_string);
});
if (reg->y_size > 0 && strlen(reg->y_array[reg->y_size - 1]) == 0) {
@@ -6701,7 +6655,7 @@ static void set_clipboard(int name, yankreg_T *reg)
list_T *const lines = tv_list_alloc((ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise));
for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(lines, (const char *)reg->y_array[i], -1);
+ tv_list_append_string(lines, reg->y_array[i], -1);
}
char regtype;
@@ -6723,7 +6677,7 @@ static void set_clipboard(int name, yankreg_T *reg)
list_T *args = tv_list_alloc(3);
tv_list_append_list(args, lines);
- tv_list_append_string(args, &regtype, 1); // -V614
+ tv_list_append_string(args, &regtype, 1);
tv_list_append_string(args, ((char[]) { (char)name }), 1);
(void)eval_call_provider("clipboard", "set", args, true);
@@ -6911,14 +6865,14 @@ bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum, linenr_T end_lnum
if (start_lnum == end_lnum) {
return end_col - start_col;
}
- const char *first = (const char *)ml_get_buf(buf, start_lnum, false);
+ const char *first = ml_get_buf(buf, start_lnum);
bcount_t deleted_bytes = (bcount_t)strlen(first) - start_col + 1;
for (linenr_T i = 1; i <= end_lnum - start_lnum - 1; i++) {
if (start_lnum + i > max_lnum) {
return deleted_bytes;
}
- deleted_bytes += (bcount_t)strlen(ml_get_buf(buf, start_lnum + i, false)) + 1;
+ deleted_bytes += (bcount_t)strlen(ml_get_buf(buf, start_lnum + i)) + 1;
}
if (end_lnum > max_lnum) {
return deleted_bytes;
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 75ea1853a0..67a613cbca 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -1,78 +1,82 @@
-#ifndef NVIM_OPS_H
-#define NVIM_OPS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/ascii.h"
-#include "nvim/eval/typval.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/extmark.h"
-#include "nvim/macros.h"
-#include "nvim/normal.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/extmark_defs.h" // IWYU pragma: keep
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
+#include "nvim/normal_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/os/time_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
typedef int (*Indenter)(void);
-// flags for do_put()
-#define PUT_FIXINDENT 1 // make indent look nice
-#define PUT_CURSEND 2 // leave cursor after end of new text
-#define PUT_CURSLINE 4 // leave cursor on last line of new text
-#define PUT_LINE 8 // put register as lines
-#define PUT_LINE_SPLIT 16 // split line for linewise register
-#define PUT_LINE_FORWARD 32 // put linewise register below Visual sel.
-#define PUT_BLOCK_INNER 64 // in block mode, do not add trailing spaces
+/// flags for do_put()
+enum {
+ PUT_FIXINDENT = 1, ///< make indent look nice
+ PUT_CURSEND = 2, ///< leave cursor after end of new text
+ PUT_CURSLINE = 4, ///< leave cursor on last line of new text
+ PUT_LINE = 8, ///< put register as lines
+ PUT_LINE_SPLIT = 16, ///< split line for linewise register
+ PUT_LINE_FORWARD = 32, ///< put linewise register below Visual sel.
+ PUT_BLOCK_INNER = 64, ///< in block mode, do not add trailing spaces
+};
-// Registers:
-// 0 = register for latest (unnamed) yank
-// 1..9 = registers '1' to '9', for deletes
-// 10..35 = registers 'a' to 'z'
-// 36 = delete register '-'
-// 37 = selection register '*'
-// 38 = clipboard register '+'
-#define DELETION_REGISTER 36
-#define NUM_SAVED_REGISTERS 37
-// The following registers should not be saved in ShaDa file:
-#define STAR_REGISTER 37
-#define PLUS_REGISTER 38
-#define NUM_REGISTERS 39
+/// Registers:
+/// 0 = register for latest (unnamed) yank
+/// 1..9 = registers '1' to '9', for deletes
+/// 10..35 = registers 'a' to 'z'
+/// 36 = delete register '-'
+/// 37 = selection register '*'
+/// 38 = clipboard register '+'
+enum {
+ DELETION_REGISTER = 36,
+ NUM_SAVED_REGISTERS = 37,
+ // The following registers should not be saved in ShaDa file:
+ STAR_REGISTER = 37,
+ PLUS_REGISTER = 38,
+ NUM_REGISTERS = 39,
+};
-// Operator IDs; The order must correspond to opchars[] in ops.c!
-#define OP_NOP 0 // no pending operation
-#define OP_DELETE 1 // "d" delete operator
-#define OP_YANK 2 // "y" yank operator
-#define OP_CHANGE 3 // "c" change operator
-#define OP_LSHIFT 4 // "<" left shift operator
-#define OP_RSHIFT 5 // ">" right shift operator
-#define OP_FILTER 6 // "!" filter operator
-#define OP_TILDE 7 // "g~" switch case operator
-#define OP_INDENT 8 // "=" indent operator
-#define OP_FORMAT 9 // "gq" format operator
-#define OP_COLON 10 // ":" colon operator
-#define OP_UPPER 11 // "gU" make upper case operator
-#define OP_LOWER 12 // "gu" make lower case operator
-#define OP_JOIN 13 // "J" join operator, only for Visual mode
-#define OP_JOIN_NS 14 // "gJ" join operator, only for Visual mode
-#define OP_ROT13 15 // "g?" rot-13 encoding
-#define OP_REPLACE 16 // "r" replace chars, only for Visual mode
-#define OP_INSERT 17 // "I" Insert column, only for Visual mode
-#define OP_APPEND 18 // "A" Append column, only for Visual mode
-#define OP_FOLD 19 // "zf" define a fold
-#define OP_FOLDOPEN 20 // "zo" open folds
-#define OP_FOLDOPENREC 21 // "zO" open folds recursively
-#define OP_FOLDCLOSE 22 // "zc" close folds
-#define OP_FOLDCLOSEREC 23 // "zC" close folds recursively
-#define OP_FOLDDEL 24 // "zd" delete folds
-#define OP_FOLDDELREC 25 // "zD" delete folds recursively
-#define OP_FORMAT2 26 // "gw" format operator, keeps cursor pos
-#define OP_FUNCTION 27 // "g@" call 'operatorfunc'
-#define OP_NR_ADD 28 // "<C-A>" Add to the number or alphabetic
- // character (OP_ADD conflicts with Perl)
-#define OP_NR_SUB 29 // "<C-X>" Subtract from the number or
- // alphabetic character
+/// Operator IDs; The order must correspond to opchars[] in ops.c!
+enum {
+ OP_NOP = 0, ///< no pending operation
+ OP_DELETE = 1, ///< "d" delete operator
+ OP_YANK = 2, ///< "y" yank operator
+ OP_CHANGE = 3, ///< "c" change operator
+ OP_LSHIFT = 4, ///< "<" left shift operator
+ OP_RSHIFT = 5, ///< ">" right shift operator
+ OP_FILTER = 6, ///< "!" filter operator
+ OP_TILDE = 7, ///< "g~" switch case operator
+ OP_INDENT = 8, ///< "=" indent operator
+ OP_FORMAT = 9, ///< "gq" format operator
+ OP_COLON = 10, ///< ":" colon operator
+ OP_UPPER = 11, ///< "gU" make upper case operator
+ OP_LOWER = 12, ///< "gu" make lower case operator
+ OP_JOIN = 13, ///< "J" join operator, only for Visual mode
+ OP_JOIN_NS = 14, ///< "gJ" join operator, only for Visual mode
+ OP_ROT13 = 15, ///< "g?" rot-13 encoding
+ OP_REPLACE = 16, ///< "r" replace chars, only for Visual mode
+ OP_INSERT = 17, ///< "I" Insert column, only for Visual mode
+ OP_APPEND = 18, ///< "A" Append column, only for Visual mode
+ OP_FOLD = 19, ///< "zf" define a fold
+ OP_FOLDOPEN = 20, ///< "zo" open folds
+ OP_FOLDOPENREC = 21, ///< "zO" open folds recursively
+ OP_FOLDCLOSE = 22, ///< "zc" close folds
+ OP_FOLDCLOSEREC = 23, ///< "zC" close folds recursively
+ OP_FOLDDEL = 24, ///< "zd" delete folds
+ OP_FOLDDELREC = 25, ///< "zD" delete folds recursively
+ OP_FORMAT2 = 26, ///< "gw" format operator, keeps cursor pos
+ OP_FUNCTION = 27, ///< "g@" call 'operatorfunc'
+ OP_NR_ADD = 28, ///< "<C-A>" Add to the number or alphabetic character
+ OP_NR_SUB = 29, ///< "<C-X>" Subtract from the number or alphabetic character
+};
/// Flags for get_reg_contents().
enum GRegFlags {
@@ -123,7 +127,17 @@ static inline int op_reg_index(const int regname)
}
}
+/// @see get_yank_register
+/// @return true when register should be inserted literally
+/// (selection or clipboard)
+static inline bool is_literal_register(const int regname)
+ FUNC_ATTR_CONST
+{
+ return regname == '*' || regname == '+';
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
#endif
-#endif // NVIM_OPS_H
+
+EXTERN LuaRef repeat_luaref INIT( = LUA_NOREF); ///< LuaRef for "."
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 01a5c7677f..96d6d8e01e 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// User-settable options. Checklist for adding a new option:
// - Put it in options.lua
// - For a global option: Add a variable for it in option_defs.h.
@@ -13,15 +10,13 @@
// add some code to didset_window_options().
// - For a buffer option, add some code to buf_copy_options().
// - For a buffer string option, add code to check_buf_options().
-// - If it's a numeric option, add any necessary bounds checks to
-// set_num_option().
+// - If it's a numeric option, add any necessary bounds checks to check_num_option_bounds().
// - If it's a list of flags, add some code in do_set(), search for WW_ALL.
// - Add documentation! doc/options.txt, and any other related places.
// - Add an entry in runtime/optwin.vim.
#define IN_OPTION_C
#include <assert.h>
-#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
@@ -30,38 +25,43 @@
#include <string.h>
#include "auto/config.h"
+#include "klib/kvec.h"
+#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor_shape.h"
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_session.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
-#include "nvim/locale.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/lua/executor.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
@@ -74,47 +74,46 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/input.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sign_defs.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
-#ifdef MSWIN
-# include "nvim/os/pty_conpty_win.h"
+
+#ifdef BACKSLASH_IN_FILENAME
+# include "nvim/arglist.h"
#endif
-#include "nvim/api/extmark.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/lua/executor.h"
-#include "nvim/os/input.h"
-#include "nvim/os/lang.h"
-static char e_unknown_option[]
+static const char e_unknown_option[]
= N_("E518: Unknown option");
-static char e_not_allowed_in_modeline[]
+static const char e_not_allowed_in_modeline[]
= N_("E520: Not allowed in a modeline");
-static char e_not_allowed_in_modeline_when_modelineexpr_is_off[]
+static const char e_not_allowed_in_modeline_when_modelineexpr_is_off[]
= N_("E992: Not allowed in a modeline when 'modelineexpr' is off");
-static char e_key_code_not_set[]
+static const char e_key_code_not_set[]
= N_("E846: Key code not set");
-static char e_number_required_after_equal[]
+static const char e_number_required_after_equal[]
= N_("E521: Number required after =");
-static char e_preview_window_already_exists[]
+static const char e_preview_window_already_exists[]
= N_("E590: A preview window already exists");
static char *p_term = NULL;
@@ -123,17 +122,30 @@ static char *p_ttytype = NULL;
// Saved values for when 'bin' is set.
static int p_et_nobin;
static int p_ml_nobin;
-static long p_tw_nobin;
-static long p_wm_nobin;
+static OptInt p_tw_nobin;
+static OptInt p_wm_nobin;
// Saved values for when 'paste' is set.
static int p_ai_nopaste;
static int p_et_nopaste;
-static long p_sts_nopaste;
-static long p_tw_nopaste;
-static long p_wm_nopaste;
+static OptInt p_sts_nopaste;
+static OptInt p_tw_nopaste;
+static OptInt p_wm_nopaste;
static char *p_vsts_nopaste;
+#define OPTION_COUNT ARRAY_SIZE(options)
+
+/// :set boolean option prefix
+typedef enum {
+ PREFIX_NO = 0, ///< "no" prefix
+ PREFIX_NONE, ///< no prefix
+ PREFIX_INV, ///< "inv" prefix
+} set_prefix_T;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "option.c.generated.h"
+#endif
+
// options[] is initialized here.
// The order of the options MUST be alphabetic for ":set all" and findoption().
// All option names MUST start with a lowercase letter (for findoption()).
@@ -145,141 +157,190 @@ static char *p_vsts_nopaste;
# include "options.generated.h"
#endif
-#define OPTION_COUNT ARRAY_SIZE(options)
-
-typedef enum {
- OP_NONE = 0,
- OP_ADDING, ///< "opt+=arg"
- OP_PREPENDING, ///< "opt^=arg"
- OP_REMOVING, ///< "opt-=arg"
-} set_op_T;
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "option.c.generated.h"
-#endif
+static char *(p_bin_dep_opts[]) = {
+ "textwidth", "wrapmargin", "modeline", "expandtab", NULL
+};
+static char *(p_paste_dep_opts[]) = {
+ "autoindent", "expandtab", "ruler", "showmatch", "smarttab",
+ "softtabstop", "textwidth", "wrapmargin", "revins", "varsofttabstop", NULL
+};
void set_init_tablocal(void)
{
// susy baka: cmdheight calls itself OPT_GLOBAL but is really tablocal!
int ch_idx = findoption("cmdheight");
- p_ch = (long)options[ch_idx].def_val;
+ p_ch = (OptInt)(intptr_t)options[ch_idx].def_val;
}
-/// Initialize the options, first part.
-///
-/// Called only once from main(), just after creating the first buffer.
-/// If "clean_arg" is true, Nvim was started with --clean.
-///
-/// NOTE: ELOG() etc calls are not allowed here, as log location depends on
-/// env var expansion which depends on expression evaluation and other
-/// editor state initialized here. Do logging in set_init_2 or later.
-void set_init_1(bool clean_arg)
+/// Initialize the 'shell' option to a default value.
+static void set_init_default_shell(void)
{
- langmap_init();
-
// Find default value for 'shell' option.
// Don't use it if it is empty.
- {
- const char *shell = os_getenv("SHELL");
- if (shell != NULL) {
- if (vim_strchr(shell, ' ') != NULL) {
- const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL
- char *const cmd = xmalloc(len);
- snprintf(cmd, len, "\"%s\"", shell);
- set_string_default("sh", cmd, true);
- } else {
- set_string_default("sh", (char *)shell, false);
- }
+ const char *shell = os_getenv("SHELL");
+ if (shell != NULL) {
+ if (vim_strchr(shell, ' ') != NULL) {
+ const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL
+ char *const cmd = xmalloc(len);
+ snprintf(cmd, len, "\"%s\"", shell);
+ set_string_default("sh", cmd, true);
+ } else {
+ set_string_default("sh", (char *)shell, false);
}
}
+}
- // Set the default for 'backupskip' to include environment variables for
- // temp files.
- {
+/// Set the default for 'backupskip' to include environment variables for
+/// temp files.
+static void set_init_default_backupskip(void)
+{
#ifdef UNIX
- static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" };
+ static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" };
#else
- static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" };
+ static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" };
#endif
- garray_T ga;
- int opt_idx = findoption("backupskip");
+ garray_T ga;
+ int opt_idx = findoption("backupskip");
- ga_init(&ga, 1, 100);
- for (size_t n = 0; n < ARRAY_SIZE(names); n++) {
- bool mustfree = true;
- char *p;
+ ga_init(&ga, 1, 100);
+ for (size_t n = 0; n < ARRAY_SIZE(names); n++) {
+ bool mustfree = true;
+ char *p;
#ifdef UNIX
- if (*names[n] == NUL) {
+ if (*names[n] == NUL) {
# ifdef __APPLE__
- p = "/private/tmp";
+ p = "/private/tmp";
# else
- p = "/tmp";
+ p = "/tmp";
# endif
- mustfree = false;
- } else // NOLINT(readability/braces)
+ mustfree = false;
+ } else // NOLINT(readability/braces)
#endif
- {
- p = vim_getenv(names[n]);
- }
- if (p != NULL && *p != NUL) {
- // First time count the NUL, otherwise count the ','.
- const size_t len = strlen(p) + 3;
- char *item = xmalloc(len);
- xstrlcpy(item, p, len);
- add_pathsep(item);
- xstrlcat(item, "*", len);
- if (find_dup_item(ga.ga_data, item, options[opt_idx].flags)
- == NULL) {
- ga_grow(&ga, (int)len);
- if (!GA_EMPTY(&ga)) {
- STRCAT(ga.ga_data, ",");
- }
- STRCAT(ga.ga_data, p);
- add_pathsep(ga.ga_data);
- STRCAT(ga.ga_data, "*");
- ga.ga_len += (int)len;
+ {
+ p = vim_getenv(names[n]);
+ }
+ if (p != NULL && *p != NUL) {
+ // First time count the NUL, otherwise count the ','.
+ const size_t len = strlen(p) + 3;
+ char *item = xmalloc(len);
+ xstrlcpy(item, p, len);
+ add_pathsep(item);
+ xstrlcat(item, "*", len);
+ if (find_dup_item(ga.ga_data, item, options[opt_idx].flags)
+ == NULL) {
+ ga_grow(&ga, (int)len);
+ if (!GA_EMPTY(&ga)) {
+ STRCAT(ga.ga_data, ",");
}
- xfree(item);
- }
- if (mustfree) {
- xfree(p);
+ STRCAT(ga.ga_data, p);
+ add_pathsep(ga.ga_data);
+ STRCAT(ga.ga_data, "*");
+ ga.ga_len += (int)len;
}
+ xfree(item);
+ }
+ if (mustfree) {
+ xfree(p);
}
- if (ga.ga_data != NULL) {
- set_string_default("bsk", ga.ga_data, true);
+ }
+ if (ga.ga_data != NULL) {
+ set_string_default("bsk", ga.ga_data, true);
+ }
+}
+
+/// Initialize the 'cdpath' option to a default value.
+static void set_init_default_cdpath(void)
+{
+ char *cdpath = vim_getenv("CDPATH");
+ if (cdpath == NULL) {
+ return;
+ }
+
+ char *buf = xmalloc(2 * strlen(cdpath) + 2);
+ buf[0] = ','; // start with ",", current dir first
+ int j = 1;
+ for (int i = 0; cdpath[i] != NUL; i++) {
+ if (vim_ispathlistsep(cdpath[i])) {
+ buf[j++] = ',';
+ } else {
+ if (cdpath[i] == ' ' || cdpath[i] == ',') {
+ buf[j++] = '\\';
+ }
+ buf[j++] = cdpath[i];
}
}
+ buf[j] = NUL;
+ int opt_idx = findoption("cdpath");
+ if (opt_idx >= 0) {
+ options[opt_idx].def_val = buf;
+ options[opt_idx].flags |= P_DEF_ALLOCED;
+ } else {
+ xfree(buf); // cannot happen
+ }
+ xfree(cdpath);
+}
- {
- // Initialize the 'cdpath' option's default value.
- char *cdpath = vim_getenv("CDPATH");
- if (cdpath != NULL) {
- char *buf = xmalloc(2 * strlen(cdpath) + 2);
- {
- buf[0] = ','; // start with ",", current dir first
- int j = 1;
- for (int i = 0; cdpath[i] != NUL; i++) {
- if (vim_ispathlistsep(cdpath[i])) {
- buf[j++] = ',';
- } else {
- if (cdpath[i] == ' ' || cdpath[i] == ',') {
- buf[j++] = '\\';
- }
- buf[j++] = cdpath[i];
- }
- }
- buf[j] = NUL;
- int opt_idx = findoption("cdpath");
- if (opt_idx >= 0) {
- options[opt_idx].def_val = buf;
- options[opt_idx].flags |= P_DEF_ALLOCED;
- } else {
- xfree(buf); // cannot happen
- }
+/// Expand environment variables and things like "~" for the defaults.
+/// If option_expand() returns non-NULL the variable is expanded. This can
+/// only happen for non-indirect options.
+/// Also set the default to the expanded value, so ":set" does not list
+/// them.
+/// Don't set the P_ALLOCED flag, because we don't want to free the
+/// default.
+static void set_init_expand_env(void)
+{
+ for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) {
+ vimoption_T *opt = &options[opt_idx];
+ if (opt->flags & P_NO_DEF_EXP) {
+ continue;
+ }
+ char *p;
+ if ((opt->flags & P_GETTEXT) && opt->var != NULL) {
+ p = _(*(char **)opt->var);
+ } else {
+ p = option_expand(opt_idx, NULL);
+ }
+ if (p != NULL) {
+ p = xstrdup(p);
+ *(char **)opt->var = p;
+ if (opt->flags & P_DEF_ALLOCED) {
+ xfree(opt->def_val);
}
- xfree(cdpath);
+ opt->def_val = p;
+ opt->flags |= P_DEF_ALLOCED;
}
}
+}
+
+/// Initialize the encoding used for "default" in 'fileencodings'.
+static void set_init_fenc_default(void)
+{
+ // enc_locale() will try to find the encoding of the current locale.
+ // This will be used when "default" is used as encoding specifier
+ // in 'fileencodings'.
+ char *p = enc_locale();
+ if (p == NULL) {
+ // Use utf-8 as "default" if locale encoding can't be detected.
+ p = xmemdupz(S_LEN("utf-8"));
+ }
+ fenc_default = p;
+}
+
+/// Initialize the options, first part.
+///
+/// Called only once from main(), just after creating the first buffer.
+/// If "clean_arg" is true, Nvim was started with --clean.
+///
+/// NOTE: ELOG() etc calls are not allowed here, as log location depends on
+/// env var expansion which depends on expression evaluation and other
+/// editor state initialized here. Do logging in set_init_2 or later.
+void set_init_1(bool clean_arg)
+{
+ langmap_init();
+
+ set_init_default_shell();
+ set_init_default_backupskip();
+ set_init_default_cdpath();
char *backupdir = stdpaths_user_state_subpath("backup", 2, true);
const size_t backupdir_len = strlen(backupdir);
@@ -328,33 +389,7 @@ void set_init_1(bool clean_arg)
init_spell_chartab();
// Expand environment variables and things like "~" for the defaults.
- // If option_expand() returns non-NULL the variable is expanded. This can
- // only happen for non-indirect options.
- // Also set the default to the expanded value, so ":set" does not list
- // them.
- // Don't set the P_ALLOCED flag, because we don't want to free the
- // default.
- for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) {
- vimoption_T *opt = &options[opt_idx];
- if (opt->flags & P_NO_DEF_EXP) {
- continue;
- }
- char *p;
- if ((opt->flags & P_GETTEXT) && opt->var != NULL) {
- p = _(*(char **)opt->var);
- } else {
- p = option_expand(opt_idx, NULL);
- }
- if (p != NULL) {
- p = xstrdup(p);
- *(char **)opt->var = p;
- if (opt->flags & P_DEF_ALLOCED) {
- xfree(opt->def_val);
- }
- opt->def_val = p;
- opt->flags |= P_DEF_ALLOCED;
- }
- }
+ set_init_expand_env();
save_file_ff(curbuf); // Buffer is unchanged
@@ -364,22 +399,13 @@ void set_init_1(bool clean_arg)
// NOTE: mlterm's author is being asked to 'set' a variable
// instead of an environment variable due to inheritance.
if (os_env_exists("MLTERM")) {
- set_option_value_give_err("tbidi", 1L, NULL, 0);
+ set_option_value_give_err("tbidi", BOOLEAN_OPTVAL(true), 0);
}
didset_options2();
lang_init();
-
- // enc_locale() will try to find the encoding of the current locale.
- // This will be used when 'default' is used as encoding specifier
- // in 'fileencodings'
- char *p = enc_locale();
- if (p == NULL) {
- // use utf-8 as 'default' if locale encoding can't be detected.
- p = xmemdupz(S_LEN("utf-8"));
- }
- fenc_default = p;
+ set_init_fenc_default();
#ifdef HAVE_WORKING_LIBINTL
// GNU gettext 0.10.37 supports this feature: set the codeset used for
@@ -401,7 +427,7 @@ static void set_option_default(const int opt_idx, int opt_flags)
// pointer to variable for current option
vimoption_T *opt = &options[opt_idx];
- char_u *varp = (char_u *)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
+ void *varp = get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
uint32_t flags = opt->flags;
if (varp != NULL) { // skip hidden option, nothing to do for it
if (flags & P_STRING) {
@@ -420,19 +446,18 @@ static void set_option_default(const int opt_idx, int opt_flags)
if (opt->indir == PV_SCROLL) {
win_comp_scroll(curwin);
} else {
- long def_val = (long)opt->def_val;
- if ((long *)varp == &curwin->w_p_so
- || (long *)varp == &curwin->w_p_siso) {
+ OptInt def_val = (OptInt)(intptr_t)opt->def_val;
+ if ((OptInt *)varp == &curwin->w_p_so
+ || (OptInt *)varp == &curwin->w_p_siso) {
// 'scrolloff' and 'sidescrolloff' local values have a
// different default value than the global default.
- *(long *)varp = -1;
+ *(OptInt *)varp = -1;
} else {
- *(long *)varp = def_val;
+ *(OptInt *)varp = def_val;
}
// May also set global value for local option.
if (both) {
- *(long *)get_varp_scope(opt, OPT_GLOBAL) =
- def_val;
+ *(OptInt *)get_varp_scope(opt, OPT_GLOBAL) = def_val;
}
}
} else { // P_BOOL
@@ -531,11 +556,11 @@ static char *find_dup_item(char *origval, const char *newval, uint32_t flags)
/// Set the Vi-default value of a number option.
/// Used for 'lines' and 'columns'.
-void set_number_default(char *name, long val)
+void set_number_default(char *name, OptInt val)
{
int opt_idx = findoption(name);
if (opt_idx >= 0) {
- options[opt_idx].def_val = (char *)(intptr_t)val;
+ options[opt_idx].def_val = (void *)(intptr_t)val;
}
}
@@ -547,14 +572,14 @@ void free_all_options(void)
if (options[i].indir == PV_NONE) {
// global option: free value and default value.
if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) {
- free_string_option(*(char **)options[i].var);
+ optval_free(optval_from_varp(i, options[i].var));
}
if (options[i].flags & P_DEF_ALLOCED) {
- free_string_option(options[i].def_val);
+ optval_free(optval_from_varp(i, &options[i].def_val));
}
- } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) {
+ } else if (options[i].var != VAR_WIN) {
// buffer-local option: free global value
- clear_string_option((char **)options[i].var);
+ optval_free(optval_from_varp(i, options[i].var));
}
}
free_operatorfunc_option();
@@ -594,16 +619,16 @@ void set_init_3(void)
// set, but only if they have not been set before.
int idx_srr = findoption("srr");
int do_srr = (idx_srr < 0)
- ? false
- : !(options[idx_srr].flags & P_WAS_SET);
+ ? false
+ : !(options[idx_srr].flags & P_WAS_SET);
int idx_sp = findoption("sp");
int do_sp = (idx_sp < 0)
- ? false
- : !(options[idx_sp].flags & P_WAS_SET);
+ ? false
+ : !(options[idx_sp].flags & P_WAS_SET);
size_t len = 0;
char *p = (char *)invocation_path_tail(p_sh, &len);
- p = xstrnsave(p, len);
+ p = xmemdupz(p, len);
{
//
@@ -726,69 +751,187 @@ void ex_set(exarg_T *eap)
(void)do_set(eap->arg, flags);
}
-/// Part of do_set() for string options.
-/// @return FAIL on failure, do not process further options.
-static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, set_op_T op_arg,
- uint32_t flags, char *varp_arg, char *errbuf, size_t errbuflen,
- int *value_checked, char **errmsg)
+/// Get the default value for a string option.
+static char *stropt_get_default_val(int opt_idx, uint64_t flags)
+{
+ char *newval = options[opt_idx].def_val;
+ // expand environment variables and ~ since the default value was
+ // already expanded, only required when an environment variable was set
+ // later
+ if (newval == NULL) {
+ newval = empty_string_option;
+ } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) {
+ char *s = option_expand(opt_idx, newval);
+ if (s == NULL) {
+ s = newval;
+ }
+ newval = xstrdup(s);
+ } else {
+ newval = xstrdup(newval);
+ }
+ return newval;
+}
+
+/// Copy the new string value into allocated memory for the option.
+/// Can't use set_string_option_direct(), because we need to remove the
+/// backslashes.
+static char *stropt_copy_value(char *origval, char **argp, set_op_T op,
+ uint32_t flags FUNC_ATTR_UNUSED)
{
char *arg = *argp;
- set_op_T op = op_arg;
- char *varp = varp_arg;
- char *save_arg = NULL;
- char *s = NULL;
- char_u *origval_l = NULL;
- char_u *origval_g = NULL;
- char whichwrap[80];
- // When using ":set opt=val" for a global option
- // with a local value the local value will be
- // reset, use the global value here.
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0
- && ((int)options[opt_idx].indir & PV_BOTH)) {
- varp = (char *)options[opt_idx].var;
+ // get a bit too much
+ size_t newlen = strlen(arg) + 1;
+ if (op != OP_NONE) {
+ newlen += strlen(origval) + 1;
+ }
+ char *newval = xmalloc(newlen);
+ char *s = newval;
+
+ // Copy the string, skip over escaped chars.
+ // For MS-Windows backslashes before normal file name characters
+ // are not removed, and keep backslash at start, for "\\machine\path",
+ // but do remove it for "\\\\machine\\path".
+ // The reverse is found in escape_option_str_cmdline().
+ while (*arg != NUL && !ascii_iswhite(*arg)) {
+ if (*arg == '\\' && arg[1] != NUL
+#ifdef BACKSLASH_IN_FILENAME
+ && !((flags & P_EXPAND)
+ && vim_isfilec((uint8_t)arg[1])
+ && !ascii_iswhite(arg[1])
+ && (arg[1] != '\\'
+ || (s == newval && arg[2] != '\\')))
+#endif
+ ) {
+ arg++; // remove backslash
+ }
+ int i = utfc_ptr2len(arg);
+ if (i > 1) {
+ // copy multibyte char
+ memmove(s, arg, (size_t)i);
+ arg += i;
+ s += i;
+ } else {
+ *s++ = *arg++;
+ }
}
+ *s = NUL;
- // The old value is kept until we are sure that the new value is valid.
- char *oldval = *(char **)varp;
+ *argp = arg;
+ return newval;
+}
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL);
- origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+/// Expand environment variables and ~ in string option value 'newval'.
+static char *stropt_expand_envvar(int opt_idx, char *origval, char *newval, set_op_T op)
+{
+ char *s = option_expand(opt_idx, newval);
+ if (s == NULL) {
+ return newval;
+ }
- // A global-local string option might have an empty option as value to
- // indicate that the global value should be used.
- if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == (char_u *)empty_option) {
- origval_l = origval_g;
- }
+ xfree(newval);
+ uint32_t newlen = (unsigned)strlen(s) + 1;
+ if (op != OP_NONE) {
+ newlen += (unsigned)strlen(origval) + 1;
}
+ newval = xmalloc(newlen);
+ STRCPY(newval, s);
- char *origval;
- // When setting the local value of a global option, the old value may be
- // the global value.
- if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) {
- origval = *(char **)get_varp(&options[opt_idx]);
+ return newval;
+}
+
+/// Concatenate the original and new values of a string option, adding a "," if
+/// needed.
+static void stropt_concat_with_comma(char *origval, char *newval, set_op_T op, uint32_t flags)
+{
+ int len = 0;
+ int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL);
+ if (op == OP_ADDING) {
+ len = (int)strlen(origval);
+ // Strip a trailing comma, would get 2.
+ if (comma && len > 1
+ && (flags & P_ONECOMMA) == P_ONECOMMA
+ && origval[len - 1] == ','
+ && origval[len - 2] != '\\') {
+ len--;
+ }
+ memmove(newval + len + comma, newval, strlen(newval) + 1);
+ memmove(newval, origval, (size_t)len);
} else {
- origval = oldval;
+ len = (int)strlen(newval);
+ STRMOVE(newval + len + comma, origval);
}
+ if (comma) {
+ newval[len] = ',';
+ }
+}
- char *newval;
- if (nextchar == '&') { // set to default val
- newval = options[opt_idx].def_val;
- // expand environment variables and ~ since the default value was
- // already expanded, only required when an environment variable was set
- // later
- if (newval == NULL) {
- newval = empty_option;
- } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) {
- s = option_expand(opt_idx, newval);
- if (s == NULL) {
- s = newval;
+/// Remove a value from a string option. Copy string option value in "origval"
+/// to "newval" and then remove the string "strval" of length "len".
+static void stropt_remove_val(char *origval, char *newval, uint32_t flags, char *strval, int len)
+{
+ // Remove newval[] from origval[]. (Note: "len" has been set above
+ // and is used here).
+ STRCPY(newval, origval);
+ if (*strval) {
+ // may need to remove a comma
+ if (flags & P_COMMA) {
+ if (strval == origval) {
+ // include comma after string
+ if (strval[len] == ',') {
+ len++;
+ }
+ } else {
+ // include comma before string
+ strval--;
+ len++;
+ }
+ }
+ STRMOVE(newval + (strval - origval), strval + len);
+ }
+}
+
+/// Remove flags that appear twice in the string option value 'newval'.
+static void stropt_remove_dupflags(char *newval, uint32_t flags)
+{
+ char *s = newval;
+ // Remove flags that appear twice.
+ for (s = newval; *s;) {
+ // if options have P_FLAGLIST and P_ONECOMMA such as 'whichwrap'
+ if (flags & P_ONECOMMA) {
+ if (*s != ',' && *(s + 1) == ','
+ && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) {
+ // Remove the duplicated value and the next comma.
+ STRMOVE(s, s + 2);
+ continue;
}
- newval = xstrdup(s);
} else {
- newval = xstrdup(newval);
+ if ((!(flags & P_COMMA) || *s != ',')
+ && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) {
+ STRMOVE(s, s + 1);
+ continue;
+ }
}
+ s++;
+ }
+}
+
+/// Get the string value specified for a ":set" command. The following set
+/// options are supported:
+/// set {opt}&
+/// set {opt}<
+/// set {opt}={val}
+/// set {opt}:{val}
+static char *stropt_get_newval(int nextchar, int opt_idx, char **argp, void *varp, char *origval,
+ set_op_T *op_arg, uint32_t flags)
+{
+ char *arg = *argp;
+ set_op_T op = *op_arg;
+ char *save_arg = NULL;
+ char *newval;
+ char *s = NULL;
+ if (nextchar == '&') { // set to default val
+ newval = stropt_get_default_val(opt_idx, flags);
} else if (nextchar == '<') { // set to global val
newval = xstrdup(*(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL));
} else {
@@ -796,123 +939,18 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar,
// Set 'keywordprg' to ":help" if an empty
// value was passed to :set by the user.
- if (varp == (char *)&p_kp && (*arg == NUL || *arg == ' ')) {
+ if (varp == &p_kp && (*arg == NUL || *arg == ' ')) {
save_arg = arg;
arg = ":help";
- } else if (varp == (char *)&p_bs && ascii_isdigit(**(char_u **)varp)) {
- // Convert 'backspace' number to string, for
- // adding, prepending and removing string.
- int i = getdigits_int((char **)varp, true, 0);
- switch (i) {
- case 0:
- *(char **)varp = empty_option;
- break;
- case 1:
- *(char_u **)varp = (char_u *)xstrdup("indent,eol");
- break;
- case 2:
- *(char_u **)varp = (char_u *)xstrdup("indent,eol,start");
- break;
- case 3:
- *(char_u **)varp = (char_u *)xstrdup("indent,eol,nostop");
- break;
- }
- xfree(oldval);
- if (origval == oldval) {
- origval = *(char **)varp;
- }
- if (origval_l == (char_u *)oldval) {
- origval_l = *(char_u **)varp;
- }
- if (origval_g == (char_u *)oldval) {
- origval_g = *(char_u **)varp;
- }
- oldval = *(char **)varp;
- } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) {
- // Convert 'whichwrap' number to string, for backwards compatibility
- // with Vim 3.0.
- *whichwrap = NUL;
- int i = getdigits_int(&arg, true, 0);
- if (i & 1) {
- xstrlcat(whichwrap, "b,", sizeof(whichwrap));
- }
- if (i & 2) {
- xstrlcat(whichwrap, "s,", sizeof(whichwrap));
- }
- if (i & 4) {
- xstrlcat(whichwrap, "h,l,", sizeof(whichwrap));
- }
- if (i & 8) {
- xstrlcat(whichwrap, "<,>,", sizeof(whichwrap));
- }
- if (i & 16) {
- xstrlcat(whichwrap, "[,],", sizeof(whichwrap));
- }
- if (*whichwrap != NUL) { // remove trailing ,
- whichwrap[strlen(whichwrap) - 1] = NUL;
- }
- save_arg = arg;
- arg = whichwrap;
- } else if (*arg == '>' && (varp == (char *)&p_dir || varp == (char *)&p_bdir)) {
- // Remove '>' before 'dir' and 'bdir', for backwards compatibility with
- // version 3.0
- arg++;
}
// Copy the new string into allocated memory.
- // Can't use set_string_option_direct(), because we need to remove the
- // backslashes.
-
- // get a bit too much
- size_t newlen = strlen(arg) + 1;
- if (op != OP_NONE) {
- newlen += strlen(origval) + 1;
- }
- newval = xmalloc(newlen);
- s = newval;
-
- // Copy the string, skip over escaped chars.
- // For MS-Windows backslashes before normal file name characters
- // are not removed, and keep backslash at start, for "\\machine\path",
- // but do remove it for "\\\\machine\\path".
- // The reverse is found in ExpandOldSetting().
- while (*arg != NUL && !ascii_iswhite(*arg)) {
- if (*arg == '\\' && arg[1] != NUL
-#ifdef BACKSLASH_IN_FILENAME
- && !((flags & P_EXPAND)
- && vim_isfilec((uint8_t)arg[1])
- && !ascii_iswhite(arg[1])
- && (arg[1] != '\\'
- || (s == newval && arg[2] != '\\')))
-#endif
- ) {
- arg++; // remove backslash
- }
- int i = utfc_ptr2len(arg);
- if (i > 1) {
- // copy multibyte char
- memmove(s, arg, (size_t)i);
- arg += i;
- s += i;
- } else {
- *s++ = *arg++;
- }
- }
- *s = NUL;
+ newval = stropt_copy_value(origval, &arg, op, flags);
// Expand environment variables and ~.
// Don't do it when adding without inserting a comma.
if (op == OP_NONE || (flags & P_COMMA)) {
- s = option_expand(opt_idx, newval);
- if (s != NULL) {
- xfree(newval);
- newlen = (unsigned)strlen(s) + 1;
- if (op != OP_NONE) {
- newlen += (unsigned)strlen(origval) + 1;
- }
- newval = xmalloc(newlen);
- STRCPY(newval, s);
- }
+ newval = stropt_expand_envvar(opt_idx, origval, newval, op);
}
// locate newval[] in origval[] when removing it
@@ -936,130 +974,439 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar,
// concatenate the two strings; add a ',' if needed
if (op == OP_ADDING || op == OP_PREPENDING) {
- int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL);
- if (op == OP_ADDING) {
- len = (int)strlen(origval);
- // Strip a trailing comma, would get 2.
- if (comma && len > 1
- && (flags & P_ONECOMMA) == P_ONECOMMA
- && origval[len - 1] == ','
- && origval[len - 2] != '\\') {
- len--;
- }
- memmove(newval + len + comma, newval, strlen(newval) + 1);
- memmove(newval, origval, (size_t)len);
- } else {
- len = (int)strlen(newval);
- STRMOVE(newval + len + comma, origval);
+ stropt_concat_with_comma(origval, newval, op, flags);
+ } else if (op == OP_REMOVING) {
+ // Remove newval[] from origval[]. (Note: "len" has been set above
+ // and is used here).
+ stropt_remove_val(origval, newval, flags, s, len);
+ }
+
+ if (flags & P_FLAGLIST) {
+ // Remove flags that appear twice.
+ stropt_remove_dupflags(newval, flags);
+ }
+ }
+
+ if (save_arg != NULL) {
+ arg = save_arg; // arg was temporarily changed, restore it
+ }
+ *argp = arg;
+ *op_arg = op;
+
+ return newval;
+}
+
+static set_op_T get_op(const char *arg)
+{
+ set_op_T op = OP_NONE;
+ if (*arg != NUL && *(arg + 1) == '=') {
+ if (*arg == '+') {
+ op = OP_ADDING; // "+="
+ } else if (*arg == '^') {
+ op = OP_PREPENDING; // "^="
+ } else if (*arg == '-') {
+ op = OP_REMOVING; // "-="
+ }
+ }
+ return op;
+}
+
+static set_prefix_T get_option_prefix(char **argp)
+{
+ if (strncmp(*argp, "no", 2) == 0) {
+ *argp += 2;
+ return PREFIX_NO;
+ } else if (strncmp(*argp, "inv", 3) == 0) {
+ *argp += 3;
+ return PREFIX_INV;
+ }
+
+ return PREFIX_NONE;
+}
+
+/// @param[in] arg Pointer to start option name
+/// @param[out] opt_idxp Option index in options[] table.
+/// @param[out] keyp
+/// @param[out] len Length of option name
+/// @return FAIL if an error is detected, OK otherwise
+static int parse_option_name(char *arg, int *keyp, int *lenp, int *opt_idxp)
+{
+ // find end of name
+ int key = 0;
+ int len;
+ int opt_idx;
+
+ if (*arg == '<') {
+ opt_idx = -1;
+ // look out for <t_>;>
+ if (arg[1] == 't' && arg[2] == '_' && arg[3] && arg[4]) {
+ len = 5;
+ } else {
+ len = 1;
+ while (arg[len] != NUL && arg[len] != '>') {
+ len++;
}
- if (comma) {
- newval[len] = ',';
+ }
+ if (arg[len] != '>') {
+ return FAIL;
+ }
+ if (arg[1] == 't' && arg[2] == '_') { // could be term code
+ opt_idx = findoption_len(arg + 1, (size_t)(len - 1));
+ }
+ len++;
+ if (opt_idx == -1) {
+ key = find_key_option(arg + 1, true);
+ }
+ } else {
+ // The two characters after "t_" may not be alphanumeric.
+ if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3]) {
+ len = 4;
+ } else {
+ len = 0;
+ while (ASCII_ISALNUM(arg[len]) || arg[len] == '_') {
+ len++;
}
}
+ opt_idx = findoption_len(arg, (size_t)len);
+ if (opt_idx == -1) {
+ key = find_key_option(arg, false);
+ }
+ }
- // Remove newval[] from origval[]. (Note: "len" has been set above and
- // is used here).
- if (op == OP_REMOVING) {
- STRCPY(newval, origval);
- if (*s) {
- // may need to remove a comma
- if (flags & P_COMMA) {
- if (s == origval) {
- // include comma after string
- if (s[len] == ',') {
- len++;
- }
- } else {
- // include comma before string
- s--;
- len++;
- }
- }
- STRMOVE(newval + (s - origval), s + len);
+ *keyp = key;
+ *lenp = len;
+ *opt_idxp = opt_idx;
+
+ return OK;
+}
+
+static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t flags,
+ set_prefix_T prefix, const char **errmsg)
+{
+ // Only bools can have a prefix of 'inv' or 'no'
+ if (!(flags & P_BOOL) && prefix != PREFIX_NONE) {
+ *errmsg = e_invarg;
+ return FAIL;
+ }
+
+ // Skip all options that are not window-local (used when showing
+ // an already loaded buffer in a window).
+ if ((opt_flags & OPT_WINONLY)
+ && (opt_idx < 0 || options[opt_idx].var != VAR_WIN)) {
+ return FAIL;
+ }
+
+ // Skip all options that are window-local (used for :vimgrep).
+ if ((opt_flags & OPT_NOWIN) && opt_idx >= 0
+ && options[opt_idx].var == VAR_WIN) {
+ return FAIL;
+ }
+
+ // Disallow changing some options from modelines.
+ if (opt_flags & OPT_MODELINE) {
+ if (flags & (P_SECURE | P_NO_ML)) {
+ *errmsg = e_not_allowed_in_modeline;
+ return FAIL;
+ }
+ if ((flags & P_MLE) && !p_mle) {
+ *errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off;
+ return FAIL;
+ }
+ // In diff mode some options are overruled. This avoids that
+ // 'foldmethod' becomes "marker" instead of "diff" and that
+ // "wrap" gets set.
+ if (win->w_p_diff
+ && opt_idx >= 0 // shut up coverity warning
+ && (options[opt_idx].indir == PV_FDM
+ || options[opt_idx].indir == PV_WRAP)) {
+ return FAIL;
+ }
+ }
+
+ // Disallow changing some options in the sandbox
+ if (sandbox != 0 && (flags & P_SECURE)) {
+ *errmsg = e_sandbox;
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// Get new option value from argp. Allocated OptVal must be freed by caller.
+static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix, char **argp,
+ int nextchar, set_op_T op, uint32_t flags, void *varp, char *errbuf,
+ const size_t errbuflen, const char **errmsg)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ assert(varp != NULL);
+
+ vimoption_T *opt = &options[opt_idx];
+ char *arg = *argp;
+ // When setting the local value of a global option, the old value may be the global value.
+ const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL);
+ OptVal oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp);
+ OptVal newval = NIL_OPTVAL;
+
+ switch (oldval.type) {
+ case kOptValTypeNil:
+ abort();
+ case kOptValTypeBoolean: {
+ TriState newval_bool;
+
+ // ":set opt!": invert
+ // ":set opt&": reset to default value
+ // ":set opt<": reset to global value
+ if (nextchar == '!') {
+ switch (oldval.data.boolean) {
+ case kNone:
+ newval_bool = kNone;
+ break;
+ case kTrue:
+ newval_bool = kFalse;
+ break;
+ case kFalse:
+ newval_bool = kTrue;
+ break;
+ }
+ } else if (nextchar == '&') {
+ newval_bool = TRISTATE_FROM_INT((int)(intptr_t)options[opt_idx].def_val);
+ } else if (nextchar == '<') {
+ // For 'autoread', kNone means to use global value.
+ if ((int *)varp == &curbuf->b_p_ar && opt_flags == OPT_LOCAL) {
+ newval_bool = kNone;
+ } else {
+ newval_bool = TRISTATE_FROM_INT(*(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL));
+ }
+ } else {
+ // ":set invopt": invert
+ // ":set opt" or ":set noopt": set or reset
+ if (prefix == PREFIX_INV) {
+ newval_bool = *(int *)varp ^ 1;
+ } else {
+ newval_bool = prefix == PREFIX_NO ? 0 : 1;
}
}
- if (flags & P_FLAGLIST) {
- // Remove flags that appear twice.
- for (s = newval; *s;) {
- // if options have P_FLAGLIST and P_ONECOMMA such as
- // 'whichwrap'
- if (flags & P_ONECOMMA) {
- if (*s != ',' && *(s + 1) == ','
- && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) {
- // Remove the duplicated value and the next comma.
- STRMOVE(s, s + 2);
- continue;
- }
- } else {
- if ((!(flags & P_COMMA) || *s != ',')
- && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) {
- STRMOVE(s, s + 1);
- continue;
- }
- }
- s++;
+ newval = BOOLEAN_OPTVAL(newval_bool);
+ break;
+ }
+ case kOptValTypeNumber: {
+ OptInt oldval_num = oldval.data.number;
+ OptInt newval_num;
+
+ // Different ways to set a number option:
+ // & set to default value
+ // < set to global value
+ // <xx> accept special key codes for 'wildchar'
+ // c accept any non-digit for 'wildchar'
+ // [-]0-9 set number
+ // other error
+ arg++;
+ if (nextchar == '&') {
+ newval_num = (OptInt)(intptr_t)options[opt_idx].def_val;
+ } else if (nextchar == '<') {
+ if ((OptInt *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) {
+ // for 'undolevels' NO_LOCAL_UNDOLEVEL means using the global newval_num
+ newval_num = NO_LOCAL_UNDOLEVEL;
+ } else if (opt_flags == OPT_LOCAL
+ && ((OptInt *)varp == &curwin->w_p_siso || (OptInt *)varp == &curwin->w_p_so)) {
+ // for 'scrolloff'/'sidescrolloff' -1 means using the global newval_num
+ newval_num = -1;
+ } else {
+ newval_num = *(OptInt *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+ }
+ } else if (((OptInt *)varp == &p_wc || (OptInt *)varp == &p_wcm)
+ && (*arg == '<' || *arg == '^'
+ || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1]))
+ && !ascii_isdigit(*arg)))) {
+ newval_num = string_to_key(arg);
+ if (newval_num == 0 && (OptInt *)varp != &p_wcm) {
+ *errmsg = e_invarg;
+ return newval;
}
+ } else if (*arg == '-' || ascii_isdigit(*arg)) {
+ int i;
+ // Allow negative, octal and hex numbers.
+ vim_str2nr(arg, NULL, &i, STR2NR_ALL, &newval_num, NULL, 0, true, NULL);
+ if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) {
+ *errmsg = e_number_required_after_equal;
+ return newval;
+ }
+ } else {
+ *errmsg = e_number_required_after_equal;
+ return newval;
}
- if (save_arg != NULL) {
- arg = save_arg; // arg was temporarily changed, restore it
+ if (op == OP_ADDING) {
+ newval_num = oldval_num + newval_num;
}
+ if (op == OP_PREPENDING) {
+ newval_num = oldval_num * newval_num;
+ }
+ if (op == OP_REMOVING) {
+ newval_num = oldval_num - newval_num;
+ }
+
+ newval = NUMBER_OPTVAL(newval_num);
+ break;
+ }
+ case kOptValTypeString: {
+ char *oldval_str = oldval.data.string.data;
+ // Get the new value for the option
+ char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op, flags);
+ newval = CSTR_AS_OPTVAL(newval_str);
+ break;
+ }
}
- // Set the new value.
- *(char_u **)(varp) = (char_u *)newval;
+ return newval;
+}
- // origval may be freed by did_set_string_option(), make a copy.
- char *saved_origval = (origval != NULL) ? xstrdup(origval) : NULL;
- char *saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : NULL;
- char *saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : NULL;
+static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char *errbuf,
+ size_t errbuflen, const char **errmsg)
+{
+ // 1: nothing, 0: "no", 2: "inv" in front of name
+ set_prefix_T prefix = get_option_prefix(argp);
- // newval (and varp) may become invalid if the buffer is closed by
- // autocommands.
- char *saved_newval = (newval != NULL) ? xstrdup(newval) : NULL;
+ char *arg = *argp;
- {
- uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
- const int secure_saved = secure;
+ // find end of name
+ int key = 0;
+ int len;
+ int opt_idx;
+ if (parse_option_name(arg, &key, &len, &opt_idx) == FAIL) {
+ *errmsg = e_invarg;
+ return;
+ }
+
+ // remember character after option name
+ int afterchar = (uint8_t)arg[len];
- // When an option is set in the sandbox, from a modeline or in secure
- // mode, then deal with side effects in secure mode. Also when the
- // value was set with the P_INSECURE flag and is not completely
- // replaced.
- if ((opt_flags & OPT_MODELINE)
- || sandbox != 0
- || (op != OP_NONE && (*p & P_INSECURE))) {
- secure = 1;
+ // skip white space, allow ":set ai ?"
+ while (ascii_iswhite(arg[len])) {
+ len++;
+ }
+
+ set_op_T op = get_op(arg + len);
+ if (op != OP_NONE) {
+ len++;
+ }
+
+ uint8_t nextchar = (uint8_t)arg[len]; // next non-white char after option name
+
+ if (opt_idx == -1 && key == 0) { // found a mismatch: skip
+ *errmsg = e_unknown_option;
+ return;
+ }
+
+ uint32_t flags; // flags for current option
+ void *varp = NULL; // pointer to variable for current option
+
+ if (opt_idx >= 0) {
+ if (options[opt_idx].var == NULL) { // hidden option: skip
+ // Only give an error message when requesting the value of
+ // a hidden option, ignore setting it.
+ if (vim_strchr("=:!&<", nextchar) == NULL
+ && (!(options[opt_idx].flags & P_BOOL)
+ || nextchar == '?')) {
+ *errmsg = e_unsupportedoption;
+ }
+ return;
}
- // Handle side effects, and set the global value for ":set" on local
- // options. Note: when setting 'syntax' or 'filetype' autocommands may
- // be triggered that can cause havoc.
- *errmsg = did_set_string_option(opt_idx, (char **)varp, oldval,
- errbuf, errbuflen,
- opt_flags, value_checked);
+ flags = options[opt_idx].flags;
+ varp = get_varp_scope(&(options[opt_idx]), opt_flags);
+ } else {
+ flags = P_STRING;
+ }
- secure = secure_saved;
+ if (validate_opt_idx(curwin, opt_idx, opt_flags, flags, prefix, errmsg) == FAIL) {
+ return;
}
- if (*errmsg == NULL) {
- if (!starting) {
- trigger_optionset_string(opt_idx, opt_flags, saved_origval, saved_origval_l,
- saved_origval_g, saved_newval);
+ if (vim_strchr("?=:!&<", nextchar) != NULL) {
+ *argp += len;
+ if (nextchar == '&' && (*argp)[1] == 'v' && (*argp)[2] == 'i') {
+ if ((*argp)[3] == 'm') { // "opt&vim": set to Vim default
+ *argp += 3;
+ } else { // "opt&vi": set to Vi default
+ *argp += 2;
+ }
}
- if (options[opt_idx].flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
- STRING_OBJ(cstr_as_string(saved_newval)));
+ if (vim_strchr("?!&<", nextchar) != NULL
+ && (*argp)[1] != NUL && !ascii_iswhite((*argp)[1])) {
+ *errmsg = e_trailing;
+ return;
}
}
- xfree(saved_origval);
- xfree(saved_origval_l);
- xfree(saved_origval_g);
- xfree(saved_newval);
- *argp = arg;
- return *errmsg == NULL ? OK : FAIL;
+ //
+ // allow '=' and ':' as MS-DOS command.com allows only one
+ // '=' character per "set" command line. grrr. (jw)
+ //
+ if (nextchar == '?'
+ || (prefix == PREFIX_NONE
+ && vim_strchr("=:&<", nextchar) == NULL
+ && !(flags & P_BOOL))) {
+ // print value
+ if (*did_show) {
+ msg_putchar('\n'); // cursor below last one
+ } else {
+ gotocmdline(true); // cursor at status line
+ *did_show = true; // remember that we did a line
+ }
+ if (opt_idx >= 0) {
+ showoneopt(&options[opt_idx], opt_flags);
+ if (p_verbose > 0) {
+ // Mention where the option was last set.
+ if (varp == options[opt_idx].var) {
+ option_last_set_msg(options[opt_idx].last_set);
+ } else if ((int)options[opt_idx].indir & PV_WIN) {
+ option_last_set_msg(curwin->w_p_script_ctx[(int)options[opt_idx].indir & PV_MASK]);
+ } else if ((int)options[opt_idx].indir & PV_BUF) {
+ option_last_set_msg(curbuf->b_p_script_ctx[(int)options[opt_idx].indir & PV_MASK]);
+ }
+ }
+ } else {
+ *errmsg = e_key_code_not_set;
+ return;
+ }
+ if (nextchar != '?' && nextchar != NUL && !ascii_iswhite(afterchar)) {
+ *errmsg = e_trailing;
+ }
+ return;
+ }
+
+ if (flags & P_BOOL) {
+ if (vim_strchr("=:", nextchar) != NULL) {
+ *errmsg = e_invarg;
+ return;
+ }
+
+ if (vim_strchr("!&<", nextchar) == NULL && nextchar != NUL && !ascii_iswhite(afterchar)) {
+ *errmsg = e_trailing;
+ return;
+ }
+ } else {
+ if (vim_strchr("=:&<", nextchar) == NULL) {
+ *errmsg = e_invarg;
+ return;
+ }
+ }
+
+ // Don't try to change hidden option.
+ if (varp == NULL) {
+ return;
+ }
+
+ OptVal newval = get_option_newval(opt_idx, opt_flags, prefix, argp, nextchar, op, flags, varp,
+ errbuf, errbuflen, errmsg);
+
+ if (newval.type == kOptValTypeNil || *errmsg != NULL) {
+ return;
+ }
+
+ *errmsg = set_option(opt_idx, varp, newval, opt_flags, op == OP_NONE, errbuf, errbuflen);
}
/// Parse 'arg' for option settings.
@@ -1079,402 +1426,79 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar,
/// @return FAIL if an error is detected, OK otherwise
int do_set(char *arg, int opt_flags)
{
- int did_show = false; // already showed one value
+ bool did_show = false; // already showed one value
if (*arg == NUL) {
- showoptions(0, opt_flags);
+ showoptions(false, opt_flags);
did_show = true;
- goto theend;
- }
-
- char errbuf[80];
-
- while (*arg != NUL) { // loop to process all options
- char *errmsg = NULL;
- char *startarg = arg; // remember for error message
-
- if (strncmp(arg, "all", 3) == 0 && !ASCII_ISALPHA(arg[3])
- && !(opt_flags & OPT_MODELINE)) {
- // ":set all" show all options.
- // ":set all&" set all options to their default value.
- arg += 3;
- if (*arg == '&') {
- arg++;
- // Only for :set command set global value of local options.
- set_options_default(OPT_FREE | opt_flags);
- didset_options();
- didset_options2();
- ui_refresh_options();
- redraw_all_later(UPD_CLEAR);
- } else {
- showoptions(1, opt_flags);
- did_show = true;
- }
- } else {
- int prefix = 1; // 1: nothing, 0: "no", 2: "inv" in front of name
- if (strncmp(arg, "no", 2) == 0) {
- prefix = 0;
- arg += 2;
- } else if (strncmp(arg, "inv", 3) == 0) {
- prefix = 2;
+ } else {
+ while (*arg != NUL) { // loop to process all options
+ if (strncmp(arg, "all", 3) == 0 && !ASCII_ISALPHA(arg[3])
+ && !(opt_flags & OPT_MODELINE)) {
+ // ":set all" show all options.
+ // ":set all&" set all options to their default value.
arg += 3;
- }
-
- // find end of name
- int key = 0;
- int len;
- int opt_idx;
- if (*arg == '<') {
- opt_idx = -1;
- // look out for <t_>;>
- if (arg[1] == 't' && arg[2] == '_' && arg[3] && arg[4]) {
- len = 5;
+ if (*arg == '&') {
+ arg++;
+ // Only for :set command set global value of local options.
+ set_options_default(OPT_FREE | opt_flags);
+ didset_options();
+ didset_options2();
+ ui_refresh_options();
+ redraw_all_later(UPD_CLEAR);
} else {
- len = 1;
- while (arg[len] != NUL && arg[len] != '>') {
- len++;
- }
- }
- if (arg[len] != '>') {
- errmsg = e_invarg;
- goto skip;
- }
- if (arg[1] == 't' && arg[2] == '_') { // could be term code
- opt_idx = findoption_len((const char *)arg + 1, (size_t)(len - 1));
- }
- len++;
- if (opt_idx == -1) {
- key = find_key_option(arg + 1, true);
+ showoptions(true, opt_flags);
+ did_show = true;
}
} else {
- len = 0;
- // The two characters after "t_" may not be alphanumeric.
- if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3]) {
- len = 4;
- } else {
- while (ASCII_ISALNUM(arg[len]) || arg[len] == '_') {
- len++;
+ char *startarg = arg; // remember for error message
+ const char *errmsg = NULL;
+ char errbuf[80];
+
+ do_one_set_option(opt_flags, &arg, &did_show, errbuf, sizeof(errbuf), &errmsg);
+
+ // Advance to next argument.
+ // - skip until a blank found, taking care of backslashes
+ // - skip blanks
+ // - skip one "=val" argument (for hidden options ":set gfn =xx")
+ for (int i = 0; i < 2; i++) {
+ arg = skiptowhite_esc(arg);
+ arg = skipwhite(arg);
+ if (*arg != '=') {
+ break;
}
}
- opt_idx = findoption_len((const char *)arg, (size_t)len);
- if (opt_idx == -1) {
- key = find_key_option(arg, false);
- }
- }
-
- // remember character after option name
- int afterchar = (uint8_t)arg[len];
-
- // skip white space, allow ":set ai ?"
- while (ascii_iswhite(arg[len])) {
- len++;
- }
-
- set_op_T op = OP_NONE;
- if (arg[len] != NUL && arg[len + 1] == '=') {
- if (arg[len] == '+') {
- op = OP_ADDING; // "+="
- len++;
- } else if (arg[len] == '^') {
- op = OP_PREPENDING; // "^="
- len++;
- } else if (arg[len] == '-') {
- op = OP_REMOVING; // "-="
- len++;
- }
- }
- char_u nextchar = (uint8_t)arg[len]; // next non-white char after option name
- if (opt_idx == -1 && key == 0) { // found a mismatch: skip
- errmsg = e_unknown_option;
- goto skip;
- }
-
- uint32_t flags; // flags for current option
- char *varp = NULL; // pointer to variable for current option
-
- if (opt_idx >= 0) {
- if (options[opt_idx].var == NULL) { // hidden option: skip
- // Only give an error message when requesting the value of
- // a hidden option, ignore setting it.
- if (vim_strchr("=:!&<", (uint8_t)nextchar) == NULL
- && (!(options[opt_idx].flags & P_BOOL)
- || nextchar == '?')) {
- errmsg = e_unsupportedoption;
+ if (errmsg != NULL) {
+ xstrlcpy(IObuff, _(errmsg), IOSIZE);
+ int i = (int)strlen(IObuff) + 2;
+ if (i + (arg - startarg) < IOSIZE) {
+ // append the argument with the error
+ xstrlcat(IObuff, ": ", IOSIZE);
+ assert(arg >= startarg);
+ memmove(IObuff + i, startarg, (size_t)(arg - startarg));
+ IObuff[i + (arg - startarg)] = NUL;
}
- goto skip;
- }
+ // make sure all characters are printable
+ trans_characters(IObuff, IOSIZE);
- flags = options[opt_idx].flags;
- varp = get_varp_scope(&(options[opt_idx]), opt_flags);
- } else {
- flags = P_STRING;
- }
-
- // Skip all options that are not window-local (used when showing
- // an already loaded buffer in a window).
- if ((opt_flags & OPT_WINONLY)
- && (opt_idx < 0 || options[opt_idx].var != VAR_WIN)) {
- goto skip;
- }
+ no_wait_return++; // wait_return() done later
+ emsg(IObuff); // show error highlighted
+ no_wait_return--;
- // Skip all options that are window-local (used for :vimgrep).
- if ((opt_flags & OPT_NOWIN) && opt_idx >= 0
- && options[opt_idx].var == VAR_WIN) {
- goto skip;
- }
-
- // Disallow changing some options from modelines.
- if (opt_flags & OPT_MODELINE) {
- if (flags & (P_SECURE | P_NO_ML)) {
- errmsg = e_not_allowed_in_modeline;
- goto skip;
- }
- if ((flags & P_MLE) && !p_mle) {
- errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off;
- goto skip;
- }
- // In diff mode some options are overruled. This avoids that
- // 'foldmethod' becomes "marker" instead of "diff" and that
- // "wrap" gets set.
- if (curwin->w_p_diff
- && opt_idx >= 0 // shut up coverity warning
- && (options[opt_idx].indir == PV_FDM
- || options[opt_idx].indir == PV_WRAP)) {
- goto skip;
- }
- }
-
- // Disallow changing some options in the sandbox
- if (sandbox != 0 && (flags & P_SECURE)) {
- errmsg = e_sandbox;
- goto skip;
- }
-
- if (vim_strchr("?=:!&<", (uint8_t)nextchar) != NULL) {
- arg += len;
- if (nextchar == '&' && arg[1] == 'v' && arg[2] == 'i') {
- if (arg[3] == 'm') { // "opt&vim": set to Vim default
- arg += 3;
- } else { // "opt&vi": set to Vi default
- arg += 2;
- }
- }
- if (vim_strchr("?!&<", (uint8_t)nextchar) != NULL
- && arg[1] != NUL && !ascii_iswhite(arg[1])) {
- errmsg = e_trailing;
- goto skip;
- }
- }
-
- //
- // allow '=' and ':' as MS-DOS command.com allows only one
- // '=' character per "set" command line. grrr. (jw)
- //
- if (nextchar == '?'
- || (prefix == 1
- && vim_strchr("=:&<", (uint8_t)nextchar) == NULL
- && !(flags & P_BOOL))) {
- // print value
- if (did_show) {
- msg_putchar('\n'); // cursor below last one
- } else {
- gotocmdline(true); // cursor at status line
- did_show = true; // remember that we did a line
- }
- if (opt_idx >= 0) {
- showoneopt(&options[opt_idx], opt_flags);
- if (p_verbose > 0) {
- // Mention where the option was last set.
- if (varp == (char *)options[opt_idx].var) {
- option_last_set_msg(options[opt_idx].last_set);
- } else if ((int)options[opt_idx].indir & PV_WIN) {
- option_last_set_msg(curwin->w_p_script_ctx[
- (int)options[opt_idx].indir & PV_MASK]);
- } else if ((int)options[opt_idx].indir & PV_BUF) {
- option_last_set_msg(curbuf->b_p_script_ctx[
- (int)options[opt_idx].indir & PV_MASK]);
- }
- }
- } else {
- errmsg = e_key_code_not_set;
- goto skip;
- }
- if (nextchar != '?'
- && nextchar != NUL && !ascii_iswhite(afterchar)) {
- errmsg = e_trailing;
- }
- } else {
- int value_checked = false;
- varnumber_T value;
-
- if (flags & P_BOOL) { // boolean
- if (nextchar == '=' || nextchar == ':') {
- errmsg = e_invarg;
- goto skip;
- }
-
- // ":set opt!": invert
- // ":set opt&": reset to default value
- // ":set opt<": reset to global value
- if (nextchar == '!') {
- value = *(int *)(varp) ^ 1;
- } else if (nextchar == '&') {
- value = (int)(intptr_t)options[opt_idx].def_val;
- } else if (nextchar == '<') {
- // For 'autoread' -1 means to use global value.
- if ((int *)varp == &curbuf->b_p_ar
- && opt_flags == OPT_LOCAL) {
- value = -1;
- } else {
- value = *(int *)get_varp_scope(&(options[opt_idx]),
- OPT_GLOBAL);
- }
- } else {
- // ":set invopt": invert
- // ":set opt" or ":set noopt": set or reset
- if (nextchar != NUL && !ascii_iswhite(afterchar)) {
- errmsg = e_trailing;
- goto skip;
- }
- if (prefix == 2) { // inv
- value = *(int *)(varp) ^ 1;
- } else {
- value = prefix;
- }
- }
-
- errmsg = set_bool_option(opt_idx, (char_u *)varp, (int)value, opt_flags);
- } else { // Numeric or string.
- if (vim_strchr("=:&<", (uint8_t)nextchar) == NULL
- || prefix != 1) {
- errmsg = e_invarg;
- goto skip;
- }
-
- if (flags & P_NUM) { // numeric
- // Different ways to set a number option:
- // & set to default value
- // < set to global value
- // <xx> accept special key codes for 'wildchar'
- // c accept any non-digit for 'wildchar'
- // [-]0-9 set number
- // other error
- arg++;
- if (nextchar == '&') {
- value = (long)(intptr_t)options[opt_idx].def_val;
- } else if (nextchar == '<') {
- // For 'undolevels' NO_LOCAL_UNDOLEVEL means to
- // use the global value.
- if ((long *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) {
- value = NO_LOCAL_UNDOLEVEL;
- } else {
- value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
- }
- } else if (((long *)varp == &p_wc
- || (long *)varp == &p_wcm)
- && (*arg == '<'
- || *arg == '^'
- || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1]))
- && !ascii_isdigit(*arg)))) {
- value = string_to_key(arg);
- if (value == 0 && (long *)varp != &p_wcm) {
- errmsg = e_invarg;
- goto skip;
- }
- } else if (*arg == '-' || ascii_isdigit(*arg)) {
- int i;
- // Allow negative, octal and hex numbers.
- vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true);
- if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) {
- errmsg = e_number_required_after_equal;
- goto skip;
- }
- } else {
- errmsg = e_number_required_after_equal;
- goto skip;
- }
-
- if (op == OP_ADDING) {
- value = *(long *)varp + value;
- }
- if (op == OP_PREPENDING) {
- value = *(long *)varp * value;
- }
- if (op == OP_REMOVING) {
- value = *(long *)varp - value;
- }
- errmsg = set_num_option(opt_idx, (char_u *)varp, (long)value,
- errbuf, sizeof(errbuf),
- opt_flags);
- } else if (opt_idx >= 0) { // String.
- if (do_set_string(opt_idx, opt_flags, &arg, nextchar,
- op, flags, varp, errbuf, sizeof(errbuf),
- &value_checked, &errmsg) == FAIL) {
- if (errmsg != NULL) {
- goto skip;
- }
- break;
- }
- } else {
- // key code option(FIXME(tarruda): Show a warning or something
- // similar)
- }
- }
-
- if (opt_idx >= 0) {
- did_set_option(opt_idx, opt_flags, op == OP_NONE, value_checked);
- }
- }
-
-skip:
- // Advance to next argument.
- // - skip until a blank found, taking care of backslashes
- // - skip blanks
- // - skip one "=val" argument (for hidden options ":set gfn =xx")
- for (int i = 0; i < 2; i++) {
- while (*arg != NUL && !ascii_iswhite(*arg)) {
- if (*arg++ == '\\' && *arg != NUL) {
- arg++;
- }
- }
- arg = skipwhite(arg);
- if (*arg != '=') {
- break;
+ return FAIL;
}
}
- }
-
- if (errmsg != NULL) {
- xstrlcpy(IObuff, _(errmsg), IOSIZE);
- int i = (int)strlen(IObuff) + 2;
- if (i + (arg - startarg) < IOSIZE) {
- // append the argument with the error
- STRCAT(IObuff, ": ");
- assert(arg >= startarg);
- memmove(IObuff + i, startarg, (size_t)(arg - startarg));
- IObuff[i + (arg - startarg)] = NUL;
- }
- // make sure all characters are printable
- trans_characters(IObuff, IOSIZE);
-
- no_wait_return++; // wait_return() done later
- emsg(IObuff); // show error highlighted
- no_wait_return--;
- return FAIL;
+ arg = skipwhite(arg);
}
-
- arg = skipwhite(arg);
}
-theend:
if (silent_mode && did_show) {
// After displaying option values in silent mode.
silent_mode = false;
info_message = true; // use os_msg(), not os_errmsg()
msg_putchar('\n');
- ui_flush();
silent_mode = true;
info_message = false; // use os_msg(), not os_errmsg()
}
@@ -1482,29 +1506,6 @@ theend:
return OK;
}
-/// Call this when an option has been given a new value through a user command.
-/// Sets the P_WAS_SET flag and takes care of the P_INSECURE flag.
-///
-/// @param opt_flags possibly with OPT_MODELINE
-/// @param new_value value was replaced completely
-/// @param value_checked value was checked to be safe, no need to set P_INSECURE
-void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked)
-{
- options[opt_idx].flags |= P_WAS_SET;
-
- // When an option is set in the sandbox, from a modeline or in secure mode
- // set the P_INSECURE flag. Otherwise, if a new value is stored reset the
- // flag.
- uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
- if (!value_checked && (secure
- || sandbox != 0
- || (opt_flags & OPT_MODELINE))) {
- *p = *p | P_INSECURE;
- } else if (new_value) {
- *p = *p & ~P_INSECURE;
- }
-}
-
/// Convert a key name or string into a key value.
/// Used for 'wildchar' and 'cedit' options.
int string_to_key(char *arg)
@@ -1579,6 +1580,9 @@ void set_options_bin(int oldval, int newval, int opt_flags)
p_et = p_et_nobin;
}
}
+
+ // Remember where the dependent option were reset
+ didset_options_sctx(opt_flags, p_bin_dep_opts);
}
/// Find the parameter represented by the given character (eg ', :, ", or /),
@@ -1642,8 +1646,8 @@ static char *option_expand(int opt_idx, char *val)
// For 'spellsuggest' expand after "file:".
expand_env_esc(val, NameBuff, MAXPATHL,
(char **)options[opt_idx].var == &p_tags, false,
- (char_u **)options[opt_idx].var == (char_u **)&p_sps ? "file:" :
- NULL);
+ (char **)options[opt_idx].var == &p_sps ? "file:"
+ : NULL);
if (strcmp(NameBuff, val) == 0) { // they are the same
return NULL;
}
@@ -1665,9 +1669,9 @@ static void didset_options(void)
(void)compile_cap_prog(curwin->w_s);
(void)did_set_spell_option(true);
// set cedit_key
- (void)check_cedit();
+ (void)did_set_cedit(NULL);
// initialize the table for 'breakat'.
- fill_breakat_flags();
+ (void)did_set_breakat(NULL);
didset_window_options(curwin, true);
}
@@ -1678,10 +1682,10 @@ static void didset_options2(void)
highlight_changed();
// Parse default for 'fillchars'.
- (void)set_chars_option(curwin, &curwin->w_p_fcs, true);
+ (void)set_fillchars_option(curwin, curwin->w_p_fcs, true);
// Parse default for 'listchars'.
- (void)set_chars_option(curwin, &curwin->w_p_lcs, true);
+ (void)set_listchars_option(curwin, curwin->w_p_lcs, true);
// Parse default for 'wildmode'.
check_opt_wim();
@@ -1720,7 +1724,7 @@ int was_set_insecurely(win_T *const wp, char *opt, int opt_flags)
/// "opt_idx". For some local options a local flags field is used.
/// NOTE: Caller must make sure that "wp" is set to the window from which
/// the option is used.
-static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags)
+uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags)
{
if (opt_flags & OPT_LOCAL) {
assert(wp != NULL);
@@ -1776,7 +1780,7 @@ void check_blending(win_T *wp)
/// Handle setting `winhighlight' in window "wp"
bool parse_winhl_opt(win_T *wp)
{
- const char *p = (const char *)wp->w_p_winhl;
+ const char *p = wp->w_p_winhl;
if (!*p) {
if (wp->w_ns_hl_winhl && wp->w_ns_hl == wp->w_ns_hl_winhl) {
@@ -1807,6 +1811,9 @@ bool parse_winhl_opt(win_T *wp)
char *commap = xstrchrnul(hi, ',');
size_t len = (size_t)(commap - hi);
int hl_id = len ? syn_check_group(hi, len) : -1;
+ if (hl_id == 0) {
+ return false;
+ }
int hl_id_link = nlen ? syn_check_group(p, nlen) : 0;
HlAttrs attrs = HLATTRS_INIT;
@@ -1820,6 +1827,18 @@ bool parse_winhl_opt(win_T *wp)
return true;
}
+/// Get the script context of global option "name".
+sctx_T *get_option_sctx(const char *const name)
+{
+ int idx = findoption(name);
+
+ if (idx >= 0) {
+ return &options[idx].last_set.script_ctx;
+ }
+ siemsg("no such option: %s", name);
+ return NULL;
+}
+
/// Set the script_ctx for an option, taking care of setting the buffer- or
/// window-local value.
void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
@@ -1847,738 +1866,1127 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
curbuf->b_p_script_ctx[indir & PV_MASK] = last_set;
} else if (indir & PV_WIN) {
curwin->w_p_script_ctx[indir & PV_MASK] = last_set;
+ if (both) {
+ // also setting the "all buffers" value
+ curwin->w_allbuf_opt.wo_script_ctx[indir & PV_MASK] = last_set;
+ }
}
}
}
/// Apply the OptionSet autocommand.
-static void apply_optionset_autocmd(int opt_idx, long opt_flags, long oldval, long oldval_g,
- long newval, const char *errmsg)
+static void apply_optionset_autocmd(int opt_idx, int opt_flags, OptVal oldval, OptVal oldval_g,
+ OptVal oldval_l, OptVal newval, const char *errmsg)
{
// Don't do this while starting up, failure or recursively.
if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) {
return;
}
- char buf_old[12], buf_old_global[12], buf_new[12], buf_type[12];
+ char buf_type[7];
+ typval_T oldval_tv = optval_as_tv(oldval);
+ typval_T oldval_g_tv = optval_as_tv(oldval_g);
+ typval_T oldval_l_tv = optval_as_tv(oldval_l);
+ typval_T newval_tv = optval_as_tv(newval);
- vim_snprintf(buf_old, sizeof(buf_old), "%ld", oldval);
- vim_snprintf(buf_old_global, sizeof(buf_old_global), "%ld", oldval_g);
- vim_snprintf(buf_new, sizeof(buf_new), "%ld", newval);
- vim_snprintf(buf_type, sizeof(buf_type), "%s",
- (opt_flags & OPT_LOCAL) ? "local" : "global");
- set_vim_var_string(VV_OPTION_NEW, buf_new, -1);
- set_vim_var_string(VV_OPTION_OLD, buf_old, -1);
+ vim_snprintf(buf_type, sizeof(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_tv(VV_OPTION_NEW, &newval_tv);
+ set_vim_var_tv(VV_OPTION_OLD, &oldval_tv);
set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
if (opt_flags & OPT_LOCAL) {
set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1);
+ set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv);
}
if (opt_flags & OPT_GLOBAL) {
set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1);
+ set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_tv);
}
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
set_vim_var_string(VV_OPTION_COMMAND, "set", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1);
+ set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_l_tv);
+ set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_g_tv);
}
if (opt_flags & OPT_MODELINE) {
set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1);
+ set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv);
}
apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL);
reset_v_option_vars();
}
-/// Set the value of a boolean option, taking care of side effects
-///
-/// @param[in] opt_idx Option index in options[] table.
-/// @param[out] varp Pointer to the option variable.
-/// @param[in] value New value.
-/// @param[in] opt_flags OPT_LOCAL and/or OPT_GLOBAL.
-///
-/// @return NULL on success, error message on error.
-static char *set_bool_option(const int opt_idx, char_u *const varp, const int value,
- const int opt_flags)
+/// Process the updated 'arabic' option value.
+static const char *did_set_arabic(optset_T *args)
{
- int old_value = *(int *)varp;
- int old_global_value = 0;
- char *errmsg = NULL;
+ win_T *win = (win_T *)args->os_win;
+ const char *errmsg = NULL;
+
+ if (win->w_p_arab) {
+ // 'arabic' is set, handle various sub-settings.
+ if (!p_tbidi) {
+ // set rightleft mode
+ if (!win->w_p_rl) {
+ win->w_p_rl = true;
+ changed_window_setting();
+ }
- // Disallow changing some options from secure mode
- if ((secure || sandbox != 0)
- && (options[opt_idx].flags & P_SECURE)) {
- return (char *)e_secure;
+ // Enable Arabic shaping (major part of what Arabic requires)
+ if (!p_arshape) {
+ p_arshape = true;
+ redraw_all_later(UPD_NOT_VALID);
+ }
+ }
+
+ // Arabic requires a utf-8 encoding, inform the user if it's not
+ // set.
+ if (strcmp(p_enc, "utf-8") != 0) {
+ static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'");
+
+ msg_source(HL_ATTR(HLF_W));
+ msg(_(w_arabic), HL_ATTR(HLF_W));
+ set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1);
+ }
+
+ // set 'delcombine'
+ p_deco = true;
+
+ // Force-set the necessary keymap for arabic.
+ errmsg = set_option_value("keymap", STATIC_CSTR_AS_OPTVAL("arabic"), OPT_LOCAL);
+ } else {
+ // 'arabic' is reset, handle various sub-settings.
+ if (!p_tbidi) {
+ // reset rightleft mode
+ if (win->w_p_rl) {
+ win->w_p_rl = false;
+ changed_window_setting();
+ }
+
+ // 'arabicshape' isn't reset, it is a global option and
+ // another window may still need it "on".
+ }
+
+ // 'delcombine' isn't reset, it is a global option and another
+ // window may still want it "on".
+
+ // Revert to the default keymap
+ curbuf->b_p_iminsert = B_IMODE_NONE;
+ curbuf->b_p_imsearch = B_IMODE_USE_INSERT;
}
- // Save the global value before changing anything. This is needed as for
- // a global-only option setting the "local value" in fact sets the global
- // value (since there is only one value).
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- old_global_value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+ return errmsg;
+}
+
+/// Process the updated 'autochdir' option value.
+static const char *did_set_autochdir(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // Change directories when the 'acd' option is set now.
+ do_autochdir();
+ return NULL;
+}
+
+/// Process the updated 'binary' option value.
+static const char *did_set_binary(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+
+ // when 'bin' is set also set some other options
+ set_options_bin((int)args->os_oldval.boolean, buf->b_p_bin, args->os_flags);
+ redraw_titles();
+
+ return NULL;
+}
+
+/// Called when the 'breakat' option changes value.
+static const char *did_set_breakat(optset_T *args FUNC_ATTR_UNUSED)
+{
+ for (int i = 0; i < 256; i++) {
+ breakat_flags[i] = false;
}
- *(int *)varp = value; // set the new value
- // Remember where the option was set.
- set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
+ if (p_breakat != NULL) {
+ for (char *p = p_breakat; *p; p++) {
+ breakat_flags[(uint8_t)(*p)] = true;
+ }
+ }
- // May set global value for local option.
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = value;
- }
-
- // Ensure that options set to p_force_on cannot be disabled.
- if ((int *)varp == &p_force_on && p_force_on == false) {
- p_force_on = true;
- return e_unsupportedoption;
- // Ensure that options set to p_force_off cannot be enabled.
- } else if ((int *)varp == &p_force_off && p_force_off == true) {
- p_force_off = false;
- return (char *)e_unsupportedoption;
- } else if ((int *)varp == &p_lrm) {
- // 'langremap' -> !'langnoremap'
- p_lnr = !p_lrm;
- } else if ((int *)varp == &p_lnr) {
- // 'langnoremap' -> !'langremap'
- p_lrm = !p_lnr;
- } else if ((int *)varp == &curbuf->b_p_udf || (int *)varp == &p_udf) {
- // 'undofile'
- // Only take action when the option was set. When reset we do not
- // delete the undo file, the option may be set again without making
- // any changes in between.
- if (curbuf->b_p_udf || p_udf) {
- char_u hash[UNDO_HASH_SIZE];
-
- FOR_ALL_BUFFERS(bp) {
- // When 'undofile' is set globally: for every buffer, otherwise
- // only for the current buffer: Try to read in the undofile,
- // if one exists, the buffer wasn't changed and the buffer was
- // loaded
- if ((curbuf == bp
- || (opt_flags & OPT_GLOBAL) || opt_flags == 0)
- && !bufIsChanged(bp) && bp->b_ml.ml_mfp != NULL) {
- u_compute_hash(bp, hash);
- u_read_undo(NULL, hash, bp->b_fname);
- }
- }
+ return NULL;
+}
+
+/// Process the updated 'buflisted' option value.
+static const char *did_set_buflisted(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+
+ // when 'buflisted' changes, trigger autocommands
+ if (args->os_oldval.boolean != buf->b_p_bl) {
+ apply_autocmds(buf->b_p_bl ? EVENT_BUFADD : EVENT_BUFDELETE,
+ NULL, NULL, true, buf);
+ }
+ return NULL;
+}
+
+/// Process the new 'cmdheight' option value.
+static const char *did_set_cmdheight(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+
+ if (ui_has(kUIMessages)) {
+ p_ch = 0;
+ }
+ if (p_ch > Rows - min_rows() + 1) {
+ p_ch = Rows - min_rows() + 1;
+ }
+
+ // if p_ch changed value, change the command line height
+ // Only compute the new window layout when startup has been
+ // completed. Otherwise the frame sizes may be wrong.
+ if ((p_ch != old_value
+ || tabline_height() + global_stl_height() + topframe->fr_height != Rows - p_ch)
+ && full_screen) {
+ command_height();
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'diff' option value.
+static const char *did_set_diff(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ // May add or remove the buffer from the list of diff buffers.
+ diff_buf_adjust(win);
+ if (foldmethodIsDiff(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// Process the updated 'endoffile' or 'endofline' or 'fixendofline' or 'bomb'
+/// option value.
+static const char *did_set_eof_eol_fixeol_bomb(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // redraw the window title and tab page text
+ redraw_titles();
+ return NULL;
+}
+
+/// Process the updated 'equalalways' option value.
+static const char *did_set_equalalways(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (p_ea && !args->os_oldval.boolean) {
+ win_equal(win, false, 0);
+ }
+
+ return NULL;
+}
+
+/// Process the new 'foldlevel' option value.
+static const char *did_set_foldlevel(optset_T *args FUNC_ATTR_UNUSED)
+{
+ newFoldLevel();
+ return NULL;
+}
+
+/// Process the new 'foldminlines' option value.
+static const char *did_set_foldminlines(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ foldUpdateAll(win);
+ return NULL;
+}
+
+/// Process the new 'foldnestmax' option value.
+static const char *did_set_foldnestmax(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (foldmethodIsSyntax(win) || foldmethodIsIndent(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// Process the new 'helpheight' option value.
+static const char *did_set_helpheight(optset_T *args)
+{
+ // Change window height NOW
+ if (!ONE_WINDOW) {
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ if (buf->b_help && win->w_height < p_hh) {
+ win_setheight((int)p_hh);
}
- } else if ((int *)varp == &curbuf->b_p_ro) {
- // when 'readonly' is reset globally, also reset readonlymode
- if (!curbuf->b_p_ro && (opt_flags & OPT_LOCAL) == 0) {
- readonlymode = false;
- }
-
- // when 'readonly' is set may give W10 again
- if (curbuf->b_p_ro) {
- curbuf->b_did_warn = false;
- }
-
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_ma) {
- // when 'modifiable' is changed, redraw the window title
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_eof
- || (int *)varp == &curbuf->b_p_eol
- || (int *)varp == &curbuf->b_p_fixeol
- || (int *)varp == &curbuf->b_p_bomb) {
- // redraw the window title and tab page text when 'endoffile', 'endofline',
- // 'fixeol' or 'bomb' is changed
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_bin) {
- // when 'bin' is set also set some other options
- set_options_bin(old_value, curbuf->b_p_bin, opt_flags);
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_bl && old_value != curbuf->b_p_bl) {
- // when 'buflisted' changes, trigger autocommands
- apply_autocmds(curbuf->b_p_bl ? EVENT_BUFADD : EVENT_BUFDELETE,
- NULL, NULL, true, curbuf);
- } else if ((int *)varp == &curbuf->b_p_swf) {
- // when 'swf' is set, create swapfile, when reset remove swapfile
- if (curbuf->b_p_swf && p_uc) {
- ml_open_file(curbuf); // create the swap file
- } else {
- // no need to reset curbuf->b_may_swap, ml_open_file() will check
- // buf->b_p_swf
- mf_close_file(curbuf, true); // remove the swap file
- }
- } else if ((int *)varp == &p_paste) {
- // when 'paste' is set or reset also change other options
- paste_option_changed();
- } else if ((int *)varp == &p_ic && p_hls) {
- // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'hlsearch' option value.
+static const char *did_set_hlsearch(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'hlsearch' is set or reset: reset no_hlsearch
+ set_no_hlsearch(false);
+ return NULL;
+}
+
+/// Process the updated 'ignorecase' option value.
+static const char *did_set_ignorecase(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw
+ if (p_hls) {
redraw_all_later(UPD_SOME_VALID);
- } else if ((int *)varp == &p_hls) {
- // when 'hlsearch' is set or reset: reset no_hlsearch
- set_no_hlsearch(false);
- } else if ((int *)varp == &curwin->w_p_scb) {
- // when 'scrollbind' is set: snapshot the current position to avoid a jump
- // at the end of normal_cmd()
- if (curwin->w_p_scb) {
- do_check_scrollbind(false);
- curwin->w_scbind_pos = curwin->w_topline;
- }
- } else if ((int *)varp == &curwin->w_p_pvw) {
- // There can be only one window with 'previewwindow' set.
- if (curwin->w_p_pvw) {
- FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
- if (win->w_p_pvw && win != curwin) {
- curwin->w_p_pvw = false;
- return e_preview_window_already_exists;
+ }
+ return NULL;
+}
+
+/// Process the new 'iminset' option value.
+static const char *did_set_iminsert(optset_T *args FUNC_ATTR_UNUSED)
+{
+ showmode();
+ // Show/unshow value of 'keymap' in status lines.
+ status_redraw_curbuf();
+
+ return NULL;
+}
+
+/// Process the updated 'langnoremap' option value.
+static const char *did_set_langnoremap(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // 'langnoremap' -> !'langremap'
+ p_lrm = !p_lnr;
+ return NULL;
+}
+
+/// Process the updated 'langremap' option value.
+static const char *did_set_langremap(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // 'langremap' -> !'langnoremap'
+ p_lnr = !p_lrm;
+ return NULL;
+}
+
+/// Process the new 'laststatus' option value.
+static const char *did_set_laststatus(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+ OptInt value = args->os_newval.number;
+
+ // When switching to global statusline, decrease topframe height
+ // Also clear the cmdline to remove the ruler if there is one
+ if (value == 3 && old_value != 3) {
+ frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false);
+ (void)win_comp_pos();
+ clear_cmdline = true;
+ }
+ // When switching from global statusline, increase height of topframe by STATUS_HEIGHT
+ // in order to to re-add the space that was previously taken by the global statusline
+ if (old_value == 3 && value != 3) {
+ frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false);
+ (void)win_comp_pos();
+ }
+
+ last_status(false); // (re)set last window status line.
+ return NULL;
+}
+
+/// Process the updated 'lisp' option value.
+static const char *did_set_lisp(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ // When 'lisp' option changes include/exclude '-' in keyword characters.
+ (void)buf_init_chartab(buf, false); // ignore errors
+ return NULL;
+}
+
+/// Process the updated 'modifiable' option value.
+static const char *did_set_modifiable(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'modifiable' is changed, redraw the window title
+ redraw_titles();
+
+ return NULL;
+}
+
+/// Process the updated 'modified' option value.
+static const char *did_set_modified(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ if (!args->os_newval.boolean) {
+ save_file_ff(buf); // Buffer is unchanged
+ }
+ redraw_titles();
+ modified_was_set = (int)args->os_newval.boolean;
+ return NULL;
+}
+
+/// Process the updated 'number' or 'relativenumber' option value.
+static const char *did_set_number_relativenumber(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (*win->w_p_stc != NUL) {
+ // When 'relativenumber'/'number' is changed and 'statuscolumn' is set, reset width.
+ win->w_nrwidth_line_count = 0;
+ }
+ check_signcolumn(win);
+ return NULL;
+}
+
+/// Process the new 'numberwidth' option value.
+static const char *did_set_numberwidth(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ win->w_nrwidth_line_count = 0; // trigger a redraw
+
+ return NULL;
+}
+
+/// Process the updated 'paste' option value.
+static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED)
+{
+ static int old_p_paste = false;
+ static int save_sm = 0;
+ static int save_sta = 0;
+ static int save_ru = 0;
+ static int save_ri = 0;
+
+ if (p_paste) {
+ // Paste switched from off to on.
+ // Save the current values, so they can be restored later.
+ if (!old_p_paste) {
+ // save options for each buffer
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_p_tw_nopaste = buf->b_p_tw;
+ buf->b_p_wm_nopaste = buf->b_p_wm;
+ buf->b_p_sts_nopaste = buf->b_p_sts;
+ buf->b_p_ai_nopaste = buf->b_p_ai;
+ buf->b_p_et_nopaste = buf->b_p_et;
+ if (buf->b_p_vsts_nopaste) {
+ xfree(buf->b_p_vsts_nopaste);
}
+ buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_string_option
+ ? xstrdup(buf->b_p_vsts)
+ : NULL;
+ }
+
+ // save global options
+ save_sm = p_sm;
+ save_sta = p_sta;
+ save_ru = p_ru;
+ save_ri = p_ri;
+ // save global values for local buffer options
+ p_ai_nopaste = p_ai;
+ p_et_nopaste = p_et;
+ p_sts_nopaste = p_sts;
+ p_tw_nopaste = p_tw;
+ p_wm_nopaste = p_wm;
+ if (p_vsts_nopaste) {
+ xfree(p_vsts_nopaste);
}
+ p_vsts_nopaste = p_vsts && p_vsts != empty_string_option ? xstrdup(p_vsts) : NULL;
}
- } else if (varp == (char_u *)&(curbuf->b_p_lisp)) {
- // When 'lisp' option changes include/exclude '-' in
- // keyword characters.
- (void)buf_init_chartab(curbuf, false); // ignore errors
- } else if ((int *)varp == &p_title) {
- // when 'title' changed, may need to change the title; same for 'icon'
- did_set_title();
- } else if ((int *)varp == &p_icon) {
- did_set_title();
- } else if ((int *)varp == &curbuf->b_changed) {
- if (!value) {
- save_file_ff(curbuf); // Buffer is unchanged
- }
- redraw_titles();
- modified_was_set = value;
-#ifdef BACKSLASH_IN_FILENAME
- } else if ((int *)varp == &p_ssl) {
- if (p_ssl) {
- psepc = '/';
- psepcN = '\\';
- pseps[0] = '/';
- } else {
- psepc = '\\';
- psepcN = '/';
- pseps[0] = '\\';
+ // Always set the option values, also when 'paste' is set when it is
+ // already on.
+ // set options for each buffer
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_p_tw = 0; // textwidth is 0
+ buf->b_p_wm = 0; // wrapmargin is 0
+ buf->b_p_sts = 0; // softtabstop is 0
+ buf->b_p_ai = 0; // no auto-indent
+ buf->b_p_et = 0; // no expandtab
+ if (buf->b_p_vsts) {
+ free_string_option(buf->b_p_vsts);
+ }
+ buf->b_p_vsts = empty_string_option;
+ XFREE_CLEAR(buf->b_p_vsts_array);
}
- // need to adjust the file name arguments and buffer names.
- buflist_slash_adjust();
- alist_slash_adjust();
- scriptnames_slash_adjust();
-#endif
- } else if ((int *)varp == &curwin->w_p_wrap) {
- // If 'wrap' is set, set w_leftcol to zero.
- if (curwin->w_p_wrap) {
- curwin->w_leftcol = 0;
- }
- } else if ((int *)varp == &p_ea) {
- if (p_ea && !old_value) {
- win_equal(curwin, false, 0);
- }
- } else if ((int *)varp == &p_acd) {
- // Change directories when the 'acd' option is set now.
- do_autochdir();
- } else if ((int *)varp == &curwin->w_p_diff) { // 'diff'
- // May add or remove the buffer from the list of diff buffers.
- diff_buf_adjust(curwin);
- if (foldmethodIsDiff(curwin)) {
- foldUpdateAll(curwin);
- }
- } else if ((int *)varp == &curwin->w_p_spell) { // 'spell'
- if (curwin->w_p_spell) {
- errmsg = did_set_spelllang(curwin);
- }
- } else if (((int *)varp == &curwin->w_p_nu || (int *)varp == &curwin->w_p_rnu)
- && *curwin->w_p_stc != NUL) { // '(relative)number' + 'statuscolumn'
- curwin->w_nrwidth_line_count = 0;
- }
-
- if ((int *)varp == &curwin->w_p_arab) {
- if (curwin->w_p_arab) {
- // 'arabic' is set, handle various sub-settings.
- if (!p_tbidi) {
- // set rightleft mode
- if (!curwin->w_p_rl) {
- curwin->w_p_rl = true;
- changed_window_setting();
- }
+ // set global options
+ p_sm = 0; // no showmatch
+ p_sta = 0; // no smarttab
+ if (p_ru) {
+ status_redraw_all(); // redraw to remove the ruler
+ }
+ p_ru = 0; // no ruler
+ p_ri = 0; // no reverse insert
+ // set global values for local buffer options
+ p_tw = 0;
+ p_wm = 0;
+ p_sts = 0;
+ p_ai = 0;
+ p_et = 0;
+ if (p_vsts) {
+ free_string_option(p_vsts);
+ }
+ p_vsts = empty_string_option;
+ } else if (old_p_paste) {
+ // Paste switched from on to off: Restore saved values.
- // Enable Arabic shaping (major part of what Arabic requires)
- if (!p_arshape) {
- p_arshape = true;
- redraw_all_later(UPD_NOT_VALID);
- }
+ // restore options for each buffer
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_p_tw = buf->b_p_tw_nopaste;
+ buf->b_p_wm = buf->b_p_wm_nopaste;
+ buf->b_p_sts = buf->b_p_sts_nopaste;
+ buf->b_p_ai = buf->b_p_ai_nopaste;
+ buf->b_p_et = buf->b_p_et_nopaste;
+ if (buf->b_p_vsts) {
+ free_string_option(buf->b_p_vsts);
}
+ buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_string_option;
+ xfree(buf->b_p_vsts_array);
+ if (buf->b_p_vsts && buf->b_p_vsts != empty_string_option) {
+ (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
+ } else {
+ buf->b_p_vsts_array = NULL;
+ }
+ }
- // Arabic requires a utf-8 encoding, inform the user if it's not
- // set.
- if (strcmp(p_enc, "utf-8") != 0) {
- static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'");
+ // restore global options
+ p_sm = save_sm;
+ p_sta = save_sta;
+ if (p_ru != save_ru) {
+ status_redraw_all(); // redraw to draw the ruler
+ }
+ p_ru = save_ru;
+ p_ri = save_ri;
+ // set global values for local buffer options
+ p_ai = p_ai_nopaste;
+ p_et = p_et_nopaste;
+ p_sts = p_sts_nopaste;
+ p_tw = p_tw_nopaste;
+ p_wm = p_wm_nopaste;
+ if (p_vsts) {
+ free_string_option(p_vsts);
+ }
+ p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_string_option;
+ }
- msg_source(HL_ATTR(HLF_W));
- msg_attr(_(w_arabic), HL_ATTR(HLF_W));
- set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1);
- }
+ old_p_paste = p_paste;
- // set 'delcombine'
- p_deco = true;
+ // Remember where the dependent options were reset
+ didset_options_sctx((OPT_LOCAL | OPT_GLOBAL), p_paste_dep_opts);
- // Force-set the necessary keymap for arabic.
- errmsg = set_option_value("keymap", 0L, "arabic", OPT_LOCAL);
- } else {
- // 'arabic' is reset, handle various sub-settings.
- if (!p_tbidi) {
- // reset rightleft mode
- if (curwin->w_p_rl) {
- curwin->w_p_rl = false;
- changed_window_setting();
- }
+ return NULL;
+}
- // 'arabicshape' isn't reset, it is a global option and
- // another window may still need it "on".
- }
+/// Process the updated 'previewwindow' option value.
+static const char *did_set_previewwindow(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
- // 'delcombine' isn't reset, it is a global option and another
- // window may still want it "on".
+ if (!win->w_p_pvw) {
+ return NULL;
+ }
- // Revert to the default keymap
- curbuf->b_p_iminsert = B_IMODE_NONE;
- curbuf->b_p_imsearch = B_IMODE_USE_INSERT;
+ // There can be only one window with 'previewwindow' set.
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_p_pvw && wp != win) {
+ win->w_p_pvw = false;
+ return e_preview_window_already_exists;
}
}
- // End of handling side effects for bool options.
+ return NULL;
+}
- // after handling side effects, call autocommand
+/// Process the new 'pumblend' option value.
+static const char *did_set_pumblend(optset_T *args FUNC_ATTR_UNUSED)
+{
+ p_pb = MAX(MIN(p_pb, 100), 0);
+ hl_invalidate_blends();
+ pum_grid.blending = (p_pb > 0);
+ if (pum_drawn()) {
+ pum_redraw();
+ }
- options[opt_idx].flags |= P_WAS_SET;
+ return NULL;
+}
- apply_optionset_autocmd(opt_idx, opt_flags,
- (long)(old_value ? true : false),
- (long)(old_global_value ? true : false),
- (long)(value ? true : false), NULL);
+/// Process the updated 'readonly' option value.
+static const char *did_set_readonly(optset_T *args)
+{
+ // when 'readonly' is reset globally, also reset readonlymode
+ if (!curbuf->b_p_ro && (args->os_flags & OPT_LOCAL) == 0) {
+ readonlymode = false;
+ }
- if (options[opt_idx].flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
- BOOLEAN_OBJ(*varp));
+ // when 'readonly' is set may give W10 again
+ if (curbuf->b_p_ro) {
+ curbuf->b_did_warn = false;
}
- comp_col(); // in case 'ruler' or 'showcmd' changed
- if (curwin->w_curswant != MAXCOL
- && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) {
- curwin->w_set_curswant = true;
+ redraw_titles();
+
+ return NULL;
+}
+
+/// Process the new 'scrollback' option value.
+static const char *did_set_scrollback(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ OptInt old_value = args->os_oldval.number;
+ OptInt value = args->os_newval.number;
+
+ if (buf->terminal && value < old_value) {
+ // Force the scrollback to take immediate effect only when decreasing it.
+ on_scrollback_option_changed(buf->terminal);
}
- check_redraw(options[opt_idx].flags);
+ return NULL;
+}
- return errmsg;
+/// Process the updated 'scrollbind' option value.
+static const char *did_set_scrollbind(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+
+ // when 'scrollbind' is set: snapshot the current position to avoid a jump
+ // at the end of normal_cmd()
+ if (!win->w_p_scb) {
+ return NULL;
+ }
+ do_check_scrollbind(false);
+ win->w_scbind_pos = win->w_topline;
+ return NULL;
}
-/// Set the value of a number option, taking care of side effects
-///
-/// @param[in] opt_idx Option index in options[] table.
-/// @param[out] varp Pointer to the option variable.
-/// @param[in] value New value.
-/// @param errbuf Buffer for error messages.
-/// @param[in] errbuflen Length of `errbuf`.
-/// @param[in] opt_flags OPT_LOCAL, OPT_GLOBAL or OPT_MODELINE.
-///
-/// @return NULL on success, error message on error.
-static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, size_t errbuflen,
- int opt_flags)
+#ifdef BACKSLASH_IN_FILENAME
+/// Process the updated 'shellslash' option value.
+static const char *did_set_shellslash(optset_T *args FUNC_ATTR_UNUSED)
{
- char *errmsg = NULL;
- long old_value = *(long *)varp;
- long old_global_value = 0; // only used when setting a local and global option
- long old_Rows = Rows; // remember old Rows
- long *pp = (long *)varp;
+ if (p_ssl) {
+ psepc = '/';
+ psepcN = '\\';
+ pseps[0] = '/';
+ } else {
+ psepc = '\\';
+ psepcN = '/';
+ pseps[0] = '\\';
+ }
- // Disallow changing some options from secure mode.
- if ((secure || sandbox != 0)
- && (options[opt_idx].flags & P_SECURE)) {
- return e_secure;
+ // need to adjust the file name arguments and buffer names.
+ buflist_slash_adjust();
+ alist_slash_adjust();
+ scriptnames_slash_adjust();
+ return NULL;
+}
+#endif
+
+/// Process the new 'shiftwidth' or the 'tabstop' option value.
+static const char *did_set_shiftwidth_tabstop(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ OptInt *pp = (OptInt *)args->os_varp;
+
+ if (foldmethodIsIndent(win)) {
+ foldUpdateAll(win);
+ }
+ // When 'shiftwidth' changes, or it's zero and 'tabstop' changes:
+ // parse 'cinoptions'.
+ if (pp == &buf->b_p_sw || buf->b_p_sw == 0) {
+ parse_cino(buf);
}
- // Save the global value before changing anything. This is needed as for
- // a global-only option setting the "local value" in fact sets the global
- // value (since there is only one value).
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- old_global_value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+ return NULL;
+}
+
+/// Process the new 'showtabline' option value.
+static const char *did_set_showtabline(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // (re)set tab page line
+ win_new_screen_rows(); // recompute window positions and heights
+ return NULL;
+}
+
+/// Process the updated 'smoothscroll' option value.
+static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (!win->w_p_sms) {
+ win->w_skipcol = 0;
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'spell' option value.
+static const char *did_set_spell(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (win->w_p_spell) {
+ return parse_spelllang(win);
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'swapfile' option value.
+static const char *did_set_swapfile(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ // when 'swf' is set, create swapfile, when reset remove swapfile
+ if (buf->b_p_swf && p_uc) {
+ ml_open_file(buf); // create the swap file
+ } else {
+ // no need to reset curbuf->b_may_swap, ml_open_file() will check
+ // buf->b_p_swf
+ mf_close_file(buf, true); // remove the swap file
+ }
+ return NULL;
+}
+
+/// Process the new 'textwidth' option value.
+static const char *did_set_textwidth(optset_T *args FUNC_ATTR_UNUSED)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ check_colorcolumn(wp);
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'title' or the 'icon' option value.
+static const char *did_set_title_icon(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'title' changed, may need to change the title; same for 'icon'
+ did_set_title();
+ return NULL;
+}
+
+/// Process the new 'titlelen' option value.
+static const char *did_set_titlelen(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+
+ // if 'titlelen' has changed, redraw the title
+ if (starting != NO_SCREEN && old_value != p_titlelen) {
+ need_maketitle = true;
}
+ return NULL;
+}
+
+/// Process the updated 'undofile' option value.
+static const char *did_set_undofile(optset_T *args)
+{
+ // Only take action when the option was set.
+ if (!curbuf->b_p_udf && !p_udf) {
+ return NULL;
+ }
+
+ // When reset we do not delete the undo file, the option may be set again
+ // without making any changes in between.
+ uint8_t hash[UNDO_HASH_SIZE];
+
+ FOR_ALL_BUFFERS(bp) {
+ // When 'undofile' is set globally: for every buffer, otherwise
+ // only for the current buffer: Try to read in the undofile,
+ // if one exists, the buffer wasn't changed and the buffer was
+ // loaded
+ if ((curbuf == bp
+ || (args->os_flags & OPT_GLOBAL) || args->os_flags == 0)
+ && !bufIsChanged(bp) && bp->b_ml.ml_mfp != NULL) {
+ u_compute_hash(bp, hash);
+ u_read_undo(NULL, hash, bp->b_fname);
+ }
+ }
+
+ return NULL;
+}
+
+/// Process the new global 'undolevels' option value.
+const char *did_set_global_undolevels(OptInt value, OptInt old_value)
+{
+ // sync undo before 'undolevels' changes
+ // use the old value, otherwise u_sync() may not work properly
+ p_ul = old_value;
+ u_sync(true);
+ p_ul = value;
+ return NULL;
+}
+
+/// Process the new buffer local 'undolevels' option value.
+const char *did_set_buflocal_undolevels(buf_T *buf, OptInt value, OptInt old_value)
+{
+ // use the old value, otherwise u_sync() may not work properly
+ buf->b_p_ul = old_value;
+ u_sync(true);
+ buf->b_p_ul = value;
+ return NULL;
+}
+
+/// Process the new 'undolevels' option value.
+static const char *did_set_undolevels(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ OptInt *pp = (OptInt *)args->os_varp;
+
+ if (pp == &p_ul) { // global 'undolevels'
+ did_set_global_undolevels(args->os_newval.number, args->os_oldval.number);
+ } else if (pp == &curbuf->b_p_ul) { // buffer local 'undolevels'
+ did_set_buflocal_undolevels(buf, args->os_newval.number, args->os_oldval.number);
+ }
+
+ return NULL;
+}
+
+/// Process the new 'updatecount' option value.
+static const char *did_set_updatecount(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+
+ // when 'updatecount' changes from zero to non-zero, open swap files
+ if (p_uc && !old_value) {
+ ml_open_files();
+ }
+
+ return NULL;
+}
+
+/// Process the new 'wildchar' / 'wildcharm' option value.
+static const char *did_set_wildchar(optset_T *args)
+{
+ OptInt c = *(OptInt *)args->os_varp;
+
+ // Don't allow key values that wouldn't work as wildchar.
+ if (c == Ctrl_C || c == '\n' || c == '\r' || c == K_KENTER) {
+ return e_invarg;
+ }
+
+ return NULL;
+}
+
+/// Process the new 'winblend' option value.
+static const char *did_set_winblend(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ OptInt old_value = args->os_oldval.number;
+ OptInt value = args->os_newval.number;
+
+ if (value != old_value) {
+ win->w_p_winbl = MAX(MIN(win->w_p_winbl, 100), 0);
+ win->w_hl_needs_update = true;
+ check_blending(win);
+ }
+
+ return NULL;
+}
+
+/// Process the new 'window' option value.
+static const char *did_set_window(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (p_window < 1) {
+ p_window = Rows - 1;
+ } else if (p_window >= Rows) {
+ p_window = Rows - 1;
+ }
+ return NULL;
+}
+
+/// Process the new 'winheight' value.
+static const char *did_set_winheight(optset_T *args)
+{
+ // Change window height NOW
+ if (!ONE_WINDOW) {
+ win_T *win = (win_T *)args->os_win;
+ if (win->w_height < p_wh) {
+ win_setheight((int)p_wh);
+ }
+ }
+
+ return NULL;
+}
+
+/// Process the new 'winwidth' option value.
+static const char *did_set_winwidth(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+
+ if (!ONE_WINDOW && win->w_width < p_wiw) {
+ win_setwidth((int)p_wiw);
+ }
+ return NULL;
+}
+
+/// Process the updated 'wrap' option value.
+static const char *did_set_wrap(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ // Set w_leftcol or w_skipcol to zero.
+ if (win->w_p_wrap) {
+ win->w_leftcol = 0;
+ } else {
+ win->w_skipcol = 0;
+ }
+
+ return NULL;
+}
+
+// When 'syntax' is set, load the syntax of that name
+static void do_syntax_autocmd(buf_T *buf, bool value_changed)
+{
+ static int syn_recursive = 0;
+
+ syn_recursive++;
+ // Only pass true for "force" when the value changed or not used
+ // recursively, to avoid endless recurrence.
+ apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname,
+ value_changed || syn_recursive == 1, buf);
+ buf->b_flags |= BF_SYN_SET;
+ syn_recursive--;
+}
+
+static void do_spelllang_source(win_T *win)
+{
+ char fname[200];
+ char *q = win->w_s->b_p_spl;
+
+ // Skip the first name if it is "cjk".
+ if (strncmp(q, "cjk,", 4) == 0) {
+ q += 4;
+ }
+
+ // Source the spell/LANG.{vim,lua} in 'runtimepath'.
+ // They could set 'spellcapcheck' depending on the language.
+ // Use the first name in 'spelllang' up to '_region' or
+ // '.encoding'.
+ char *p;
+ for (p = q; *p != NUL; p++) {
+ if (!ASCII_ISALNUM(*p) && *p != '-') {
+ break;
+ }
+ }
+ if (p > q) {
+ vim_snprintf(fname, sizeof(fname), "spell/%.*s.*", (int)(p - q), q);
+ source_runtime_vim_lua(fname, DIP_ALL);
+ }
+}
+
+/// Check the bounds of numeric options.
+static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, char *errbuf,
+ size_t errbuflen, const char *errmsg)
+{
+ int old_Rows = Rows; // remember old Rows
+ // Check the (new) bounds for Rows and Columns here.
+ if (p_lines < min_rows() && full_screen) {
+ if (errbuf != NULL) {
+ vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"), min_rows());
+ errmsg = errbuf;
+ }
+ p_lines = min_rows();
+ }
+ if (p_columns < MIN_COLUMNS && full_screen) {
+ if (errbuf != NULL) {
+ vim_snprintf(errbuf, errbuflen, _("E594: Need at least %d columns"), MIN_COLUMNS);
+ errmsg = errbuf;
+ }
+ p_columns = MIN_COLUMNS;
+ }
+
+ // True max size is defined by check_screensize()
+ p_lines = MIN(p_lines, INT_MAX);
+ p_columns = MIN(p_columns, INT_MAX);
+
+ // If the screen (shell) height has been changed, assume it is the
+ // physical screenheight.
+ if (p_lines != Rows || p_columns != Columns) {
+ // Changing the screen size is not allowed while updating the screen.
+ if (updating_screen) {
+ *pp = old_value;
+ } else if (full_screen) {
+ screen_resize((int)p_columns, (int)p_lines);
+ } else {
+ // TODO(bfredl): is this branch ever needed?
+ // Postpone the resizing; check the size and cmdline position for
+ // messages.
+ Rows = (int)p_lines;
+ Columns = (int)p_columns;
+ check_screensize();
+ int new_row = (int)(Rows - MAX(p_ch, 1));
+ if (cmdline_row > new_row && Rows > p_ch) {
+ assert(p_ch >= 0 && new_row <= INT_MAX);
+ cmdline_row = new_row;
+ }
+ }
+ if (p_window >= Rows || !option_was_set("window")) {
+ p_window = Rows - 1;
+ }
+ }
+
+ if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0))
+ && full_screen) {
+ if (pp == &(curwin->w_p_scr)) {
+ if (curwin->w_p_scr != 0) {
+ errmsg = e_scroll;
+ }
+ win_comp_scroll(curwin);
+ } else if (curwin->w_p_scr <= 0) {
+ // If 'scroll' became invalid because of a side effect silently adjust it.
+ curwin->w_p_scr = 1;
+ } else { // curwin->w_p_scr > curwin->w_height
+ curwin->w_p_scr = curwin->w_height;
+ }
+ }
+ if ((p_sj < -100 || p_sj >= Rows) && full_screen) {
+ if (Rows != old_Rows) { // Rows changed, just adjust p_sj
+ p_sj = Rows / 2;
+ } else {
+ errmsg = e_scroll;
+ p_sj = 1;
+ }
+ }
+
+ return errmsg;
+}
+
+/// Options that need some validation.
+static const char *validate_num_option(const OptInt *pp, OptInt *valuep)
+{
+ OptInt value = *valuep;
+
// Many number options assume their value is in the signed int range.
if (value < INT_MIN || value > INT_MAX) {
return e_invarg;
}
- // Options that need some validation.
if (pp == &p_wh) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (p_wmh > value) {
- errmsg = e_winheight;
+ return e_winheight;
}
} else if (pp == &p_hh) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_wmh) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > p_wh) {
- errmsg = e_winheight;
+ return e_winheight;
}
} else if (pp == &p_wiw) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (p_wmw > value) {
- errmsg = e_winwidth;
+ return e_winwidth;
}
} else if (pp == &p_wmw) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > p_wiw) {
- errmsg = e_winwidth;
+ return e_winwidth;
}
} else if (pp == &p_mco) {
- value = MAX_MCO;
+ *valuep = MAX_MCO;
} else if (pp == &p_titlelen) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_uc) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_ch) {
- int minval = 0;
- if (value < minval) {
- errmsg = e_positive;
+ if (value < 0) {
+ return e_positive;
} else {
p_ch_was_zero = value == 0;
}
} else if (pp == &p_tm) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_hi) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > 10000) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &p_pyx) {
if (value == 0) {
- value = 3;
+ *valuep = 3;
} else if (value != 3) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &p_re) {
if (value < 0 || value > 2) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &p_report) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_so) {
if (value < 0 && full_screen) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_siso) {
if (value < 0 && full_screen) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_cwh) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_ut) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_ss) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &curwin->w_p_fdl || pp == &curwin->w_allbuf_opt.wo_fdl) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &curwin->w_p_cole || pp == &curwin->w_allbuf_opt.wo_cole) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > 3) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > MAX_NUMBERWIDTH) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) {
if (value < 0 || value > B_IMODE_LAST) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) {
if (value < -1 || value > B_IMODE_LAST) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_channel || pp == &p_channel) {
- errmsg = e_invarg;
+ return e_invarg;
} else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) {
if (value < -1 || value > SB_MAX) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_sw || pp == &p_sw) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &curbuf->b_p_ts || pp == &p_ts) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > TABSTOP_MAX) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_tw || pp == &p_tw) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_wd) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
}
- // Don't change the value and return early if validation failed.
- if (errmsg != NULL) {
- return errmsg;
- }
-
- *pp = value;
- // Remember where the option was set.
- set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
-
- // For these options we want to fix some invalid values.
- if (pp == &p_window) {
- if (p_window < 1) {
- p_window = Rows - 1;
- } else if (p_window >= Rows) {
- p_window = Rows - 1;
- }
- } else if (pp == &p_ch) {
- if (ui_has(kUIMessages)) {
- p_ch = 0;
- }
- if (p_ch > Rows - min_rows() + 1) {
- p_ch = Rows - min_rows() + 1;
- }
- }
-
- // Number options that need some action when changed
- if (pp == &p_wh) {
- // 'winheight'
- if (!ONE_WINDOW && curwin->w_height < p_wh) {
- win_setheight((int)p_wh);
- }
- } else if (pp == &p_hh) {
- // 'helpheight'
- if (!ONE_WINDOW && curbuf->b_help && curwin->w_height < p_hh) {
- win_setheight((int)p_hh);
- }
- } else if (pp == &p_wmh) {
- // 'winminheight'
- win_setminheight();
- } else if (pp == &p_wiw) {
- // 'winwidth'
- if (!ONE_WINDOW && curwin->w_width < p_wiw) {
- win_setwidth((int)p_wiw);
- }
- } else if (pp == &p_wmw) {
- // 'winminwidth'
- win_setminwidth();
- } else if (pp == &p_ls) {
- // When switching to global statusline, decrease topframe height
- // Also clear the cmdline to remove the ruler if there is one
- if (value == 3 && old_value != 3) {
- frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false);
- (void)win_comp_pos();
- clear_cmdline = true;
- }
- // When switching from global statusline, increase height of topframe by STATUS_HEIGHT
- // in order to to re-add the space that was previously taken by the global statusline
- if (old_value == 3 && value != 3) {
- frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false);
- (void)win_comp_pos();
- }
-
- last_status(false); // (re)set last window status line.
- } else if (pp == &p_stal) {
- // (re)set tab page line
- win_new_screen_rows(); // recompute window positions and heights
- } else if (pp == &curwin->w_p_fdl) {
- newFoldLevel();
- } else if (pp == &curwin->w_p_fml) {
- foldUpdateAll(curwin);
- } else if (pp == &curwin->w_p_fdn) {
- if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) {
- foldUpdateAll(curwin);
- }
- } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) {
- // 'shiftwidth' or 'tabstop'
- if (foldmethodIsIndent(curwin)) {
- foldUpdateAll(curwin);
- }
- // When 'shiftwidth' changes, or it's zero and 'tabstop' changes:
- // parse 'cinoptions'.
- if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) {
- parse_cino(curbuf);
- }
- } else if (pp == &curbuf->b_p_iminsert) {
- showmode();
- // Show/unshow value of 'keymap' in status lines.
- status_redraw_curbuf();
- } else if (pp == &p_titlelen) {
- // if 'titlelen' has changed, redraw the title
- if (starting != NO_SCREEN && old_value != p_titlelen) {
- need_maketitle = true;
- }
- } else if (pp == &p_ch) {
- // if p_ch changed value, change the command line height
- // Only compute the new window layout when startup has been
- // completed. Otherwise the frame sizes may be wrong.
- if ((p_ch != old_value
- || tabline_height() + global_stl_height() + topframe->fr_height != Rows - p_ch)
- && full_screen) {
- command_height();
- }
- } else if (pp == &p_uc) {
- // when 'updatecount' changes from zero to non-zero, open swap files
- if (p_uc && !old_value) {
- ml_open_files();
- }
- } else if (pp == &p_pb) {
- p_pb = MAX(MIN(p_pb, 100), 0);
- hl_invalidate_blends();
- pum_grid.blending = (p_pb > 0);
- if (pum_drawn()) {
- pum_redraw();
- }
- } else if (pp == &p_ul || pp == &curbuf->b_p_ul) {
- // sync undo before 'undolevels' changes
- // use the old value, otherwise u_sync() may not work properly
- *pp = old_value;
- u_sync(true);
- *pp = value;
- } else if (pp == &curbuf->b_p_tw) {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- check_colorcolumn(wp);
- }
- } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) {
- if (curbuf->terminal && value < old_value) {
- // Force the scrollback to take immediate effect only when decreasing it.
- on_scrollback_option_changed(curbuf->terminal);
- }
- } else if (pp == &curwin->w_p_nuw) {
- curwin->w_nrwidth_line_count = 0;
- } else if (pp == &curwin->w_p_winbl && value != old_value) {
- // 'winblend'
- curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0);
- curwin->w_hl_needs_update = true;
- check_blending(curwin);
- }
-
- // Check the (new) bounds for Rows and Columns here.
- if (p_lines < min_rows() && full_screen) {
- if (errbuf != NULL) {
- vim_snprintf(errbuf, errbuflen,
- _("E593: Need at least %d lines"), min_rows());
- errmsg = errbuf;
- }
- p_lines = min_rows();
- }
- if (p_columns < MIN_COLUMNS && full_screen) {
- if (errbuf != NULL) {
- vim_snprintf(errbuf, errbuflen,
- _("E594: Need at least %d columns"), MIN_COLUMNS);
- errmsg = errbuf;
- }
- p_columns = MIN_COLUMNS;
- }
-
- // True max size is defined by check_screensize()
- p_lines = MIN(p_lines, INT_MAX);
- p_columns = MIN(p_columns, INT_MAX);
-
- // If the screen (shell) height has been changed, assume it is the
- // physical screenheight.
- if (p_lines != Rows || p_columns != Columns) {
- // Changing the screen size is not allowed while updating the screen.
- if (updating_screen) {
- *pp = old_value;
- } else if (full_screen) {
- screen_resize((int)p_columns, (int)p_lines);
- } else {
- // TODO(bfredl): is this branch ever needed?
- // Postpone the resizing; check the size and cmdline position for
- // messages.
- Rows = (int)p_lines;
- Columns = (int)p_columns;
- check_screensize();
- int new_row = (int)(Rows - MAX(p_ch, 1));
- if (cmdline_row > new_row && Rows > p_ch) {
- assert(p_ch >= 0 && new_row <= INT_MAX);
- cmdline_row = new_row;
- }
- }
- if (p_window >= Rows || !option_was_set("window")) {
- p_window = Rows - 1;
- }
- }
-
- if ((curwin->w_p_scr <= 0
- || (curwin->w_p_scr > curwin->w_height
- && curwin->w_height > 0))
- && full_screen) {
- if (pp == &(curwin->w_p_scr)) {
- if (curwin->w_p_scr != 0) {
- errmsg = e_scroll;
- }
- win_comp_scroll(curwin);
- } else if (curwin->w_p_scr <= 0) {
- // If 'scroll' became invalid because of a side effect silently adjust it.
- curwin->w_p_scr = 1;
- } else { // curwin->w_p_scr > curwin->w_height
- curwin->w_p_scr = curwin->w_height;
- }
- }
- if ((p_sj < -100 || p_sj >= Rows) && full_screen) {
- if (Rows != old_Rows) { // Rows changed, just adjust p_sj
- p_sj = Rows / 2;
- } else {
- errmsg = e_scroll;
- p_sj = 1;
- }
- }
-
- // May set global value for local option.
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp;
- }
-
- options[opt_idx].flags |= P_WAS_SET;
-
- apply_optionset_autocmd(opt_idx, opt_flags, old_value, old_global_value,
- value, errmsg);
-
- if (errmsg == NULL && options[opt_idx].flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
- INTEGER_OBJ(*pp));
- }
-
- comp_col(); // in case 'columns' or 'ls' changed
- if (curwin->w_curswant != MAXCOL
- && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) {
- curwin->w_set_curswant = true;
- }
- check_redraw(options[opt_idx].flags);
-
- return errmsg;
+ return NULL;
}
/// Called after an option changed: check if something needs to be redrawn.
@@ -2757,23 +3165,6 @@ bool set_tty_option(const char *name, char *value)
return false;
}
-void set_tty_background(const char *value)
-{
- if (option_was_set("bg") || strequal(p_bg, value)) {
- // background is already set... ignore
- return;
- }
- if (starting) {
- // Wait until after startup, so OptionSet is triggered.
- do_cmdline_cmd((value[0] == 'l')
- ? "autocmd VimEnter * ++once ++nested set bg=light"
- : "autocmd VimEnter * ++once ++nested set bg=dark");
- } else {
- set_option_value_give_err("bg", 0L, value, 0);
- reset_option_was_set("bg");
- }
-}
-
/// Find index for an option
///
/// @param[in] arg Option name.
@@ -2785,283 +3176,702 @@ int findoption(const char *const arg)
return findoption_len(arg, strlen(arg));
}
-/// Gets the value for an option.
+/// Free an allocated OptVal.
+void optval_free(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ break;
+ case kOptValTypeString:
+ // Don't free empty string option
+ if (o.data.string.data != empty_string_option) {
+ api_free_string(o.data.string);
+ }
+ break;
+ }
+}
+
+/// Copy an OptVal.
+OptVal optval_copy(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ return o;
+ case kOptValTypeString:
+ return STRING_OPTVAL(copy_string(o.data.string, NULL));
+ }
+ UNREACHABLE;
+}
+
+/// Check if two option values are equal.
+bool optval_equal(OptVal o1, OptVal o2)
+{
+ if (o1.type != o2.type) {
+ return false;
+ }
+
+ switch (o1.type) {
+ case kOptValTypeNil:
+ return true;
+ case kOptValTypeBoolean:
+ return o1.data.boolean == o2.data.boolean;
+ case kOptValTypeNumber:
+ return o1.data.number == o2.data.number;
+ case kOptValTypeString:
+ return o1.data.string.size == o2.data.string.size
+ && strequal(o1.data.string.data, o2.data.string.data);
+ }
+ UNREACHABLE;
+}
+
+/// Match type of OptVal with the type of the target option. Returns true if the types match and
+/// false otherwise.
+static bool optval_match_type(OptVal o, int opt_idx)
+{
+ assert(opt_idx >= 0);
+ uint32_t flags = options[opt_idx].flags;
+
+ switch (o.type) {
+ case kOptValTypeNil:
+ return false;
+ case kOptValTypeBoolean:
+ return flags & P_BOOL;
+ case kOptValTypeNumber:
+ return flags & P_NUM;
+ case kOptValTypeString:
+ return flags & P_STRING;
+ }
+ UNREACHABLE;
+}
+
+/// Create OptVal from var pointer.
///
-/// @param stringval NULL when only checking existence
-/// @param flagsp set to the option flags (P_xxxx) (if not NULL)
+/// @param opt_idx Option index in options[] table.
+/// @param[out] varp Pointer to option variable.
+OptVal optval_from_varp(int opt_idx, void *varp)
+{
+ // Special case: 'modified' is b_changed, but we also want to consider it set when 'ff' or 'fenc'
+ // changed.
+ if ((int *)varp == &curbuf->b_changed) {
+ return BOOLEAN_OPTVAL(curbufIsChanged());
+ }
+
+ uint32_t flags = options[opt_idx].flags;
+
+ OptValType type = kOptValTypeNil;
+ if (flags & P_BOOL) {
+ type = kOptValTypeBoolean;
+ } else if (flags & P_NUM) {
+ type = kOptValTypeNumber;
+ } else if (flags & P_STRING) {
+ type = kOptValTypeString;
+ } else {
+ abort();
+ }
+
+ switch (type) {
+ case kOptValTypeNil:
+ return NIL_OPTVAL;
+ case kOptValTypeBoolean:
+ return BOOLEAN_OPTVAL(varp == NULL ? false : TRISTATE_FROM_INT(*(int *)varp));
+ case kOptValTypeNumber:
+ return NUMBER_OPTVAL(varp == NULL ? 0 : *(OptInt *)varp);
+ case kOptValTypeString:
+ return STRING_OPTVAL(varp == NULL ? (String)STRING_INIT : cstr_as_string(*(char **)varp));
+ }
+ UNREACHABLE;
+}
+
+/// Set option var pointer value from Optval.
///
-/// @returns:
-/// Number option: gov_number, *numval gets value.
-/// Tottle option: gov_bool, *numval gets value.
-/// String option: gov_string, *stringval gets allocated string.
-/// Hidden Number option: gov_hidden_number.
-/// Hidden Toggle option: gov_hidden_bool.
-/// Hidden String option: gov_hidden_string.
-/// Unknown option: gov_unknown.
-getoption_T get_option_value(const char *name, long *numval, char **stringval, uint32_t *flagsp,
- int scope)
+/// @param opt_idx Option index in options[] table.
+/// @param[out] varp Pointer to option variable.
+/// @param[in] value New option value.
+/// @param free_oldval Free old value.
+static void set_option_varp(int opt_idx, void *varp, OptVal value, bool free_oldval)
+ FUNC_ATTR_NONNULL_ARG(2)
{
- if (get_tty_option(name, stringval)) {
- return gov_string;
+ assert(optval_match_type(value, opt_idx));
+
+ if (free_oldval) {
+ optval_free(optval_from_varp(opt_idx, varp));
}
- int opt_idx = findoption(name);
- if (opt_idx < 0) { // option not in the table
- return gov_unknown;
+ switch (value.type) {
+ case kOptValTypeNil:
+ return;
+ case kOptValTypeBoolean:
+ *(int *)varp = value.data.boolean;
+ return;
+ case kOptValTypeNumber:
+ *(OptInt *)varp = value.data.number;
+ return;
+ case kOptValTypeString:
+ *(char **)varp = value.data.string.data;
+ return;
}
+ UNREACHABLE;
+}
- char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), scope);
+/// Return C-string representation of OptVal. Caller must free the returned C-string.
+static char *optval_to_cstr(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ return xstrdup("");
+ case kOptValTypeBoolean:
+ return xstrdup(o.data.boolean ? "true" : "false");
+ case kOptValTypeNumber: {
+ char *buf = xmalloc(NUMBUFLEN);
+ snprintf(buf, NUMBUFLEN, "%" PRId64, o.data.number);
+ return buf;
+ }
+ case kOptValTypeString: {
+ char *buf = xmalloc(o.data.string.size + 3);
+ snprintf(buf, o.data.string.size + 3, "\"%s\"", o.data.string.data);
+ return buf;
+ }
+ }
+ UNREACHABLE;
+}
- if (flagsp != NULL) {
- // Return the P_xxxx option flags.
- *flagsp = options[opt_idx].flags;
+/// Convert an OptVal to an API Object.
+Object optval_as_object(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ return NIL;
+ case kOptValTypeBoolean:
+ switch (o.data.boolean) {
+ case kFalse:
+ case kTrue:
+ return BOOLEAN_OBJ(o.data.boolean);
+ case kNone:
+ return NIL;
+ }
+ UNREACHABLE;
+ case kOptValTypeNumber:
+ return INTEGER_OBJ(o.data.number);
+ case kOptValTypeString:
+ return STRING_OBJ(o.data.string);
+ }
+ UNREACHABLE;
+}
+
+/// Convert an API Object to an OptVal.
+OptVal object_as_optval(Object o, bool *error)
+{
+ switch (o.type) {
+ case kObjectTypeNil:
+ return NIL_OPTVAL;
+ case kObjectTypeBoolean:
+ return BOOLEAN_OPTVAL(o.data.boolean);
+ case kObjectTypeInteger:
+ return NUMBER_OPTVAL((OptInt)o.data.integer);
+ case kObjectTypeString:
+ return STRING_OPTVAL(o.data.string);
+ default:
+ *error = true;
+ return NIL_OPTVAL;
}
+ UNREACHABLE;
+}
- if (options[opt_idx].flags & P_STRING) {
- if (varp == NULL) { // hidden option
- return gov_hidden_string;
+/// Unset the local value of an option. The exact semantics of this depend on the option.
+/// TODO(famiu): Remove this once we have a dedicated OptVal type for unset local options.
+///
+/// @param opt_idx Option index in options[] table.
+/// @param[in] varp Pointer to option variable.
+///
+/// @return [allocated] Option value equal to the unset value for the option.
+static OptVal optval_unset_local(int opt_idx, void *varp)
+{
+ vimoption_T *opt = &options[opt_idx];
+ // For global-local options, use the unset value of the local value.
+ if (opt->indir & PV_BOTH) {
+ // String global-local options always use an empty string for the unset value.
+ if (opt->flags & P_STRING) {
+ return STATIC_CSTR_TO_OPTVAL("");
}
- if (stringval != NULL) {
- if ((char **)varp == &p_pt) { // 'pastetoggle'
- *stringval = str2special_save(*(char **)(varp), false, false);
- } else {
- *stringval = xstrdup(*(char **)(varp));
- }
+
+ if ((int *)varp == &curbuf->b_p_ar) {
+ return BOOLEAN_OPTVAL(kNone);
+ } else if ((OptInt *)varp == &curwin->w_p_so || (OptInt *)varp == &curwin->w_p_siso) {
+ return NUMBER_OPTVAL(-1);
+ } else if ((OptInt *)varp == &curbuf->b_p_ul) {
+ return NUMBER_OPTVAL(NO_LOCAL_UNDOLEVEL);
+ } else {
+ // This should never happen.
+ abort();
}
- return gov_string;
}
+ // For options that aren't global-local, just set the local value to the global value.
+ return get_option_value(opt->fullname, NULL, OPT_GLOBAL, NULL);
+}
+
+/// Get an allocated string containing a list of valid types for an option.
+/// For options with a singular type, it returns the name of the type. For options with multiple
+/// possible types, it returns a slash separated list of types. For example, if an option can be a
+/// number, boolean or string, the function returns "Number/Boolean/String"
+static char *option_get_valid_types(int opt_idx)
+{
+ uint32_t flags = options[opt_idx].flags;
+ uint32_t type_count = 0;
- if (varp == NULL) { // hidden option
- return (options[opt_idx].flags & P_NUM) ? gov_hidden_number : gov_hidden_bool;
+ StringBuilder str = KV_INITIAL_VALUE;
+ kv_resize(str, 32);
+
+#define OPTION_ADD_TYPE(typename) \
+ do { \
+ if (type_count == 0) { \
+ kv_concat(str, typename); \
+ } else { \
+ kv_printf(str, "/%s", typename); \
+ } \
+ type_count++; \
+ } while (0);
+
+ if (flags & P_NUM) {
+ OPTION_ADD_TYPE("Number");
}
- if (options[opt_idx].flags & P_NUM) {
- *numval = *(long *)varp;
- } else {
- // Special case: 'modified' is b_changed, but we also want to consider
- // it set when 'ff' or 'fenc' changed.
- if ((int *)varp == &curbuf->b_changed) {
- *numval = curbufIsChanged();
- } else {
- *numval = (long)(*(int *)varp);
- }
+ if (flags & P_BOOL) {
+ OPTION_ADD_TYPE("Boolean");
}
- return (options[opt_idx].flags & P_NUM) ? gov_number : gov_bool;
+ if (flags & P_STRING) {
+ OPTION_ADD_TYPE("String");
+ }
+
+ if (type_count == 0) {
+ abort();
+ }
+
+ // Ensure that the string is NUL-terminated.
+ kv_push(str, NUL);
+ return str.items;
+
+#undef OPTION_ADD_TYPE
}
-// Returns the option attributes and its value. Unlike the above function it
-// will return either global value or local value of the option depending on
-// what was requested, but it will never return global value if it was
-// requested to return local one and vice versa. Neither it will return
-// buffer-local value if it was requested to return window-local one.
-//
-// Pretends that option is absent if it is not present in the requested scope
-// (i.e. has no global, window-local or buffer-local value depending on
-// opt_type).
-//
-// Returned flags:
-// 0 hidden or unknown option, also option that does not have requested
-// type (see SREQ_* in option_defs.h)
-// see SOPT_* in option_defs.h for other flags
-//
-// Possible opt_type values: see SREQ_* in option_defs.h
-int get_option_value_strict(char *name, int64_t *numval, char **stringval, int opt_type, void *from)
+/// Gets the value for an option.
+///
+/// @param[in] name Option name.
+/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL).
+/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param[out] hidden Whether option is hidden.
+///
+/// @return [allocated] Option value. Returns NIL_OPTVAL for invalid options.
+OptVal get_option_value(const char *name, uint32_t *flagsp, int scope, bool *hidden)
{
- if (get_tty_option(name, stringval)) {
- return SOPT_STRING | SOPT_GLOBAL;
+ // Make sure that hidden and flagsp are never returned uninitialized
+ if (hidden != NULL) {
+ *hidden = false;
+ }
+ if (flagsp != NULL) {
+ *flagsp = 0;
+ }
+
+ char *str;
+ if (get_tty_option(name, &str)) {
+ return CSTR_AS_OPTVAL(str);
}
- int rv = 0;
int opt_idx = findoption(name);
- if (opt_idx < 0) {
- return 0;
+ if (opt_idx < 0) { // option not in the table
+ return NIL_OPTVAL;
}
- vimoption_T *p = &options[opt_idx];
+ vimoption_T *opt = &options[opt_idx];
+ void *varp = get_varp_scope(opt, scope);
- // Hidden option
- if (p->var == NULL) {
- return 0;
+ if (hidden != NULL) {
+ *hidden = varp == NULL;
}
- if (p->flags & P_BOOL) {
- rv |= SOPT_BOOL;
- } else if (p->flags & P_NUM) {
- rv |= SOPT_NUM;
- } else if (p->flags & P_STRING) {
- rv |= SOPT_STRING;
+ if (flagsp != NULL) {
+ // Return the P_xxxx option flags.
+ *flagsp = opt->flags;
}
- if (p->indir == PV_NONE) {
- if (opt_type == SREQ_GLOBAL) {
- rv |= SOPT_GLOBAL;
- } else {
- return 0; // Did not request global-only option
- }
- } else {
- if (p->indir & PV_BOTH) {
- rv |= SOPT_GLOBAL;
- }
+ return optval_copy(optval_from_varp(opt_idx, varp));
+}
- if (p->indir & PV_WIN) {
- if (opt_type == SREQ_BUF) {
- return 0; // Requested buffer-local, not window-local option
- }
- rv |= SOPT_WIN;
- } else if (p->indir & PV_BUF) {
- if (opt_type == SREQ_WIN) {
- return 0; // Requested window-local, not buffer-local option
- }
- rv |= SOPT_BUF;
+/// Return information for option at 'opt_idx'
+vimoption_T *get_option(int opt_idx)
+{
+ return &options[opt_idx];
+}
+
+/// Check if local value of global-local option is unset for current buffer / window.
+/// Always returns false for options that aren't global-local.
+///
+/// TODO(famiu): Remove this once we have an OptVal type to indicate an unset local value.
+static bool is_option_local_value_unset(vimoption_T *opt, buf_T *buf, win_T *win)
+{
+ // Local value of option that isn't global-local is always considered set.
+ if (!((int)opt->indir & PV_BOTH)) {
+ return false;
+ }
+
+ // Get pointer to local value in varp_local, and a pointer to the currently used value in varp.
+ // If the local value is the one currently being used, that indicates that it's set.
+ // Otherwise it indicates the local value is unset.
+ void *varp = get_varp_from(opt, buf, win);
+ void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, buf, win);
+
+ return varp != varp_local;
+}
+
+/// Handle side-effects of setting an option.
+///
+/// @param opt_idx Index in options[] table. Must be >= 0.
+/// @param[in] varp Option variable pointer, cannot be NULL.
+/// @param old_value Old option value.
+/// @param new_value New option value.
+/// @param opt_flags Option flags.
+/// @param[out] value_checked Value was checked to be safe, no need to set P_INSECURE.
+/// @param value_replaced Value was replaced completely.
+/// @param[out] errbuf Buffer for error message.
+/// @param errbuflen Length of error buffer.
+///
+/// @return NULL on success, an untranslated error message on error.
+static const char *did_set_option(int opt_idx, void *varp, OptVal old_value, OptVal new_value,
+ int opt_flags, bool *value_checked, bool value_replaced,
+ char *errbuf, size_t errbuflen)
+{
+ vimoption_T *opt = &options[opt_idx];
+ const char *errmsg = NULL;
+ bool restore_chartab = false;
+ bool free_oldval = (opt->flags & P_ALLOCED);
+ bool value_changed = false;
+
+ optset_T did_set_cb_args = {
+ .os_varp = varp,
+ .os_idx = opt_idx,
+ .os_flags = opt_flags,
+ .os_oldval = old_value.data,
+ .os_newval = new_value.data,
+ .os_value_checked = false,
+ .os_value_changed = false,
+ .os_restore_chartab = false,
+ .os_errbuf = errbuf,
+ .os_errbuflen = errbuflen,
+ .os_buf = curbuf,
+ .os_win = curwin
+ };
+
+ // Disallow changing immutable options.
+ if (opt->immutable && !optval_equal(old_value, new_value)) {
+ errmsg = e_unsupportedoption;
+ }
+ // Disallow changing some options from secure mode.
+ else if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) {
+ errmsg = e_secure;
+ }
+ // Check for a "normal" directory or file name in some string options.
+ else if (new_value.type == kOptValTypeString
+ && check_illegal_path_names(*(char **)varp, opt->flags)) {
+ errmsg = e_invarg;
+ } else if (opt->opt_did_set_cb != NULL) {
+ // Invoke the option specific callback function to validate and apply the new value.
+ errmsg = opt->opt_did_set_cb(&did_set_cb_args);
+ // The 'filetype' and 'syntax' option callback functions may change the os_value_changed field.
+ value_changed = did_set_cb_args.os_value_changed;
+ // The 'keymap', 'filetype' and 'syntax' option callback functions may change the
+ // os_value_checked field.
+ *value_checked = did_set_cb_args.os_value_checked;
+ // The 'isident', 'iskeyword', 'isprint' and 'isfname' options may change the character table.
+ // On failure, this needs to be restored.
+ restore_chartab = did_set_cb_args.os_restore_chartab;
+ }
+
+ // If an error is detected, restore the previous value and don't do any further processing.
+ if (errmsg != NULL) {
+ set_option_varp(opt_idx, varp, old_value, true);
+ // When resetting some values, need to act on it.
+ if (restore_chartab) {
+ (void)buf_init_chartab(curbuf, true);
}
+
+ // Unset new_value as it is no longer valid.
+ new_value = NIL_OPTVAL; // NOLINT(clang-analyzer-deadcode.DeadStores)
+ return errmsg;
+ }
+
+ // Re-assign the new value as its value may get freed or modified by the option callback.
+ new_value = optval_from_varp(opt_idx, varp);
+
+ // Remember where the option was set.
+ set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
+ // Free options that are in allocated memory.
+ // Use "free_oldval", because recursiveness may change the flags (esp. init_highlight()).
+ if (free_oldval) {
+ optval_free(old_value);
+ }
+ opt->flags |= P_ALLOCED;
+
+ // Check the bound for num options.
+ if (new_value.type == kOptValTypeNumber) {
+ errmsg = check_num_option_bounds((OptInt *)varp, old_value.data.number, errbuf, errbuflen,
+ errmsg);
+ // Re-assign new_value because the new value was modified by the bound check.
+ new_value = optval_from_varp(opt_idx, varp);
+ }
+
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && (opt->indir & PV_BOTH)) {
+ // Global option with local value set to use global value.
+ // Free the local value and clear it.
+ void *varp_local = get_varp_scope(opt, OPT_LOCAL);
+ OptVal local_unset_value = optval_unset_local(opt_idx, varp_local);
+ set_option_varp(opt_idx, varp_local, local_unset_value, true);
+ } else if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
+ // May set global value for local option.
+ void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
+ set_option_varp(opt_idx, varp_global, optval_copy(new_value), true);
+ }
+
+ // Trigger the autocommand only after setting the flags.
+ if (varp == &curbuf->b_p_syn) {
+ do_syntax_autocmd(curbuf, value_changed);
+ } else if (varp == &curbuf->b_p_ft) {
+ // 'filetype' is set, trigger the FileType autocommand
+ // Skip this when called from a modeline
+ // Force autocmd when the filetype was changed
+ if (!(opt_flags & OPT_MODELINE) || value_changed) {
+ do_filetype_autocmd(curbuf, value_changed);
+ }
+ } else if (varp == &curwin->w_s->b_p_spl) {
+ do_spelllang_source(curwin);
+ }
+
+ // In case 'columns' or 'ls' changed.
+ comp_col();
+
+ if (varp == &p_mouse) {
+ setmouse(); // in case 'mouse' changed
+ } else if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) {
+ // Changing Formatlistpattern when briopt includes the list setting:
+ // redraw
+ redraw_all_later(UPD_NOT_VALID);
+ } else if (varp == &p_wbr || varp == &(curwin->w_p_wbr)) {
+ // add / remove window bars for 'winbar'
+ set_winbar(true);
}
- if (stringval == NULL) {
- return rv;
+ if (curwin->w_curswant != MAXCOL && (opt->flags & (P_CURSWANT | P_RALL)) != 0) {
+ curwin->w_set_curswant = true;
}
- char_u *varp = NULL;
+ check_redraw(opt->flags);
- if (opt_type == SREQ_GLOBAL) {
- if (p->var == VAR_WIN) {
- return 0;
+ if (errmsg == NULL) {
+ uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
+ opt->flags |= P_WAS_SET;
+
+ // When an option is set in the sandbox, from a modeline or in secure mode set the P_INSECURE
+ // flag. Otherwise, if a new value is stored reset the flag.
+ if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) {
+ *p |= P_INSECURE;
+ } else if (value_replaced) {
+ *p &= ~P_INSECURE;
}
- varp = p->var;
- } else {
- if (opt_type == SREQ_BUF) {
- // Special case: 'modified' is b_changed, but we also want to
- // consider it set when 'ff' or 'fenc' changed.
- if (p->indir == PV_MOD) {
- *numval = bufIsChanged((buf_T *)from);
- varp = NULL;
- } else {
- buf_T *save_curbuf = curbuf;
-
- // only getting a pointer, no need to use aucmd_prepbuf()
- curbuf = (buf_T *)from;
- curwin->w_buffer = curbuf;
- varp = (char_u *)get_varp_scope(p, OPT_LOCAL);
- curbuf = save_curbuf;
- curwin->w_buffer = curbuf;
- }
- } else if (opt_type == SREQ_WIN) {
- win_T *save_curwin = curwin;
- curwin = (win_T *)from;
- curbuf = curwin->w_buffer;
- varp = (char_u *)get_varp_scope(p, OPT_LOCAL);
- curwin = save_curwin;
- curbuf = curwin->w_buffer;
+ }
+
+ return errmsg;
+}
+
+/// Set the value of an option using an OptVal.
+///
+/// @param opt_idx Index in options[] table. Must be >= 0.
+/// @param[in] varp Option variable pointer, cannot be NULL.
+/// @param value New option value. Might get freed.
+/// @param opt_flags Option flags.
+/// @param value_replaced Value was replaced completely.
+/// @param[out] errbuf Buffer for error message.
+/// @param errbuflen Length of error buffer.
+///
+/// @return NULL on success, an untranslated error message on error.
+static const char *set_option(const int opt_idx, void *varp, OptVal value, int opt_flags,
+ const bool value_replaced, char *errbuf, size_t errbuflen)
+{
+ assert(opt_idx >= 0 && varp != NULL);
+
+ const char *errmsg = NULL;
+ bool value_checked = false;
+
+ vimoption_T *opt = &options[opt_idx];
+
+ static const char *optval_type_names[] = {
+ [kOptValTypeNil] = "Nil",
+ [kOptValTypeBoolean] = "Boolean",
+ [kOptValTypeNumber] = "Number",
+ [kOptValTypeString] = "String"
+ };
+
+ if (value.type == kOptValTypeNil) {
+ // Don't try to unset local value if scope is global.
+ // TODO(famiu): Change this to forbid changing all non-local scopes when the API scope bug is
+ // fixed.
+ if (opt_flags == OPT_GLOBAL) {
+ errmsg = _("Cannot unset global option value");
+ } else {
+ optval_free(value);
+ value = optval_unset_local(opt_idx, varp);
}
+ } else if (!optval_match_type(value, opt_idx)) {
+ char *rep = optval_to_cstr(value);
+ char *valid_types = option_get_valid_types(opt_idx);
+ snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"),
+ opt->fullname, valid_types, optval_type_names[value.type], rep);
+ xfree(rep);
+ xfree(valid_types);
+ errmsg = errbuf;
+ }
- if (varp == p->var) {
- return (rv | SOPT_UNSET);
+ if (errmsg != NULL) {
+ goto err;
+ }
+
+ // When using ":set opt=val" for a global option with a local value the local value will be reset,
+ // use the global value here.
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && ((int)opt->indir & PV_BOTH)) {
+ varp = opt->var;
+ }
+
+ OptVal old_value = optval_from_varp(opt_idx, varp);
+ OptVal old_global_value = NIL_OPTVAL;
+ OptVal old_local_value = NIL_OPTVAL;
+
+ // Save the local and global values before changing anything. This is needed as for a global-only
+ // option setting the "local value" in fact sets the global value (since there is only one value).
+ //
+ // TODO(famiu): This needs to be changed to use the current type of the old value instead of
+ // value.type, when multi-type options are added.
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
+ old_global_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_GLOBAL));
+ old_local_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_LOCAL));
+
+ // If local value of global-local option is unset, use global value as local value.
+ if (is_option_local_value_unset(opt, curbuf, curwin)) {
+ old_local_value = old_global_value;
}
}
- if (varp != NULL) {
- if (p->flags & P_STRING) {
- *stringval = *(char **)(varp);
- } else if (p->flags & P_NUM) {
- *numval = *(long *)varp;
- } else {
- *numval = *(int *)varp;
+ // Value that's actually being used.
+ // For local scope of a global-local option, it is equal to the global value.
+ // In every other case, it is the same as old_value.
+ const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL);
+ OptVal used_old_value = oldval_is_global ? optval_from_varp(opt_idx, get_varp(opt)) : old_value;
+
+ if (value.type == kOptValTypeNumber) {
+ errmsg = validate_num_option((OptInt *)varp, &value.data.number);
+
+ // Don't change the value and return early if validation failed.
+ if (errmsg != NULL) {
+ goto err;
}
}
- return rv;
-}
+ set_option_varp(opt_idx, varp, value, false);
-// Return information for option at 'opt_idx'
-vimoption_T *get_option(int opt_idx)
-{
- return &options[opt_idx];
+ OptVal saved_used_value = optval_copy(used_old_value);
+ OptVal saved_old_global_value = optval_copy(old_global_value);
+ OptVal saved_old_local_value = optval_copy(old_local_value);
+ // New value (and varp) may become invalid if the buffer is closed by autocommands.
+ OptVal saved_new_value = optval_copy(value);
+
+ uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
+ const int secure_saved = secure;
+
+ // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side
+ // effects in secure mode. Also when the value was set with the P_INSECURE flag and is not
+ // completely replaced.
+ if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!value_replaced && (*p & P_INSECURE))) {
+ secure = 1;
+ }
+
+ errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, &value_checked,
+ value_replaced, errbuf, errbuflen);
+
+ secure = secure_saved;
+
+ if (errmsg == NULL) {
+ if (!starting) {
+ apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value,
+ saved_old_local_value, saved_new_value, errmsg);
+ }
+ if (opt->flags & P_UI_OPTION) {
+ // Calculate saved_new_value again as its value might be changed by bound checks.
+ // NOTE: Currently there are no buffer/window local UI options, but if there ever are buffer
+ // or window local UI options added in the future, varp might become invalid if the buffer or
+ // window is closed during an autocommand, and a check would have to be added for it.
+ optval_free(saved_new_value);
+ saved_new_value = optval_copy(optval_from_varp(opt_idx, varp));
+ ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value));
+ }
+ }
+
+ // Free copied values as they are not needed anymore
+ optval_free(saved_used_value);
+ optval_free(saved_old_local_value);
+ optval_free(saved_old_global_value);
+ optval_free(saved_new_value);
+ return errmsg;
+err:
+ optval_free(value);
+ return errmsg;
}
/// Set the value of an option
///
-/// @param[in] name Option name.
-/// @param[in] number New value for the number or boolean option.
-/// @param[in] string New value for string option.
+/// @param[in] name Option name.
+/// @param[in] value Option value. If NIL_OPTVAL, the option value is cleared.
/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
-/// If OPT_CLEAR is set, the value of the option
-/// is cleared (the exact semantics of this depend
-/// on the option).
///
-/// @return NULL on success, an untranslated error message on error.
-char *set_option_value(const char *const name, const long number, const char *const string,
- const int opt_flags)
+/// @return NULL on success, an untranslated error message on error.
+const char *set_option_value(const char *const name, const OptVal value, int opt_flags)
FUNC_ATTR_NONNULL_ARG(1)
{
+ static char errbuf[IOSIZE];
+
if (is_tty_option(name)) {
return NULL; // Fail silently; many old vimrcs set t_xx options.
}
int opt_idx = findoption(name);
if (opt_idx < 0) {
- semsg(_("E355: Unknown option: %s"), name);
- return NULL;
+ snprintf(errbuf, IOSIZE, _(e_unknown_option2), name);
+ return errbuf;
}
uint32_t flags = options[opt_idx].flags;
// Disallow changing some options in the sandbox
if (sandbox > 0 && (flags & P_SECURE)) {
- emsg(_(e_sandbox));
- return NULL;
- }
-
- if (flags & P_STRING) {
- const char *s = string;
- if (s == NULL || opt_flags & OPT_CLEAR) {
- s = "";
- }
- return set_string_option(opt_idx, s, opt_flags);
+ return _(e_sandbox);
}
- char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags);
+ void *varp = get_varp_scope(&(options[opt_idx]), opt_flags);
if (varp == NULL) {
// hidden option is not changed
return NULL;
}
- if (number == 0 && string != NULL) {
- int idx;
+ const char *errmsg = NULL;
- // Either we are given a string or we are setting option
- // to zero.
- for (idx = 0; string[idx] == '0'; idx++) {}
- if (string[idx] != NUL || idx == 0) {
- // There's another character after zeros or the string
- // is empty. In both cases, we are trying to set a
- // num option using a string.
- semsg(_("E521: Number required: &%s = '%s'"),
- name, string);
- return NULL; // do nothing as we hit an error
- }
- }
- long numval = number;
- if (opt_flags & OPT_CLEAR) {
- if ((int *)varp == &curbuf->b_p_ar) {
- numval = -1;
- } else if ((long *)varp == &curbuf->b_p_ul) {
- numval = NO_LOCAL_UNDOLEVEL;
- } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) {
- numval = -1;
- } else {
- char *s = NULL;
- (void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL);
- }
- }
- if (flags & P_NUM) {
- return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags);
- }
- return set_bool_option(opt_idx, varp, (int)numval, opt_flags);
+ errmsg = set_option(opt_idx, varp, optval_copy(value), opt_flags, true, errbuf, sizeof(errbuf));
+
+ return errmsg;
}
/// Call set_option_value() and when an error is returned report it.
///
/// @param opt_flags OPT_LOCAL or 0 (both)
-void set_option_value_give_err(const char *name, long number, const char *string, int opt_flags)
+void set_option_value_give_err(const char *name, OptVal value, int opt_flags)
{
- char *errmsg = set_option_value(name, number, string, opt_flags);
+ const char *errmsg = set_option_value(name, value, opt_flags);
if (errmsg != NULL) {
emsg(_(errmsg));
@@ -3074,14 +3884,6 @@ bool is_option_allocated(const char *name)
return idx >= 0 && (options[idx].flags & P_ALLOCED);
}
-/// Return true if "name" is a string option.
-/// Returns false if option "name" does not exist.
-bool is_string_option(const char *name)
-{
- int idx = findoption(name);
- return idx >= 0 && (options[idx].flags & P_STRING);
-}
-
// Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number.
// When "has_lt" is true there is a '<' before "*arg_arg".
// Returns 0 when the key is not recognized.
@@ -3111,11 +3913,11 @@ static int find_key_option(const char *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' == false: show changed options
+/// if 'all' == true: show all normal options
///
/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL
-static void showoptions(int all, int opt_flags)
+static void showoptions(bool all, int opt_flags)
{
#define INC 20
#define GAP 3
@@ -3144,16 +3946,15 @@ static void showoptions(int all, int opt_flags)
continue;
}
- char_u *varp = NULL;
+ void *varp = NULL;
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) {
if (p->indir != PV_NONE) {
- varp = (char_u *)get_varp_scope(p, opt_flags);
+ varp = get_varp_scope(p, opt_flags);
}
} else {
varp = get_varp(p);
}
- if (varp != NULL
- && (all == 1 || (all == 0 && !optval_default(p, varp)))) {
+ if (varp != NULL && (all || !optval_default(p, varp))) {
int len;
if (opt_flags & OPT_ONECOLUMN) {
len = Columns;
@@ -3204,13 +4005,13 @@ static void showoptions(int all, int opt_flags)
}
/// Return true if option "p" has its default value.
-static int optval_default(vimoption_T *p, const char_u *varp)
+static int optval_default(vimoption_T *p, const void *varp)
{
if (varp == NULL) {
return true; // hidden option is always at default
}
if (p->flags & P_NUM) {
- return *(long *)varp == (long)(intptr_t)p->def_val;
+ return *(OptInt *)varp == (OptInt)(intptr_t)p->def_val;
}
if (p->flags & P_BOOL) {
return *(int *)varp == (int)(intptr_t)p->def_val;
@@ -3233,10 +4034,10 @@ void ui_refresh_options(void)
if (flags & P_BOOL) {
value = BOOLEAN_OBJ(*(int *)varp);
} else if (flags & P_NUM) {
- value = INTEGER_OBJ(*(long *)varp);
+ value = INTEGER_OBJ(*(OptInt *)varp);
} else if (flags & P_STRING) {
// cstr_as_string handles NULL string
- value = STRING_OBJ(cstr_as_string(*(char **)varp));
+ value = CSTR_AS_OBJ(*(char **)varp);
}
ui_call_option_set(name, value);
}
@@ -3256,7 +4057,7 @@ static void showoneopt(vimoption_T *p, int opt_flags)
silent_mode = false;
info_message = true; // use os_msg(), not os_errmsg()
- char_u *varp = (char_u *)get_varp_scope(p, opt_flags);
+ void *varp = get_varp_scope(p, opt_flags);
// for 'modified' we also need to check if 'ff' or 'fenc' changed.
if ((p->flags & P_BOOL) && ((int *)varp == &curbuf->b_changed
@@ -3272,7 +4073,7 @@ static void showoneopt(vimoption_T *p, int opt_flags)
msg_putchar('=');
// put value string in NameBuff
option_value2string(p, opt_flags);
- msg_outtrans(NameBuff);
+ msg_outtrans(NameBuff, 0);
}
silent_mode = save_silent;
@@ -3323,23 +4124,23 @@ int makeset(FILE *fd, int opt_flags, int local_only)
continue;
}
- char *varp = get_varp_scope(p, opt_flags); // currently used value
+ void *varp = get_varp_scope(p, opt_flags); // currently used value
// Hidden options are never written.
if (!varp) {
continue;
}
// Global values are only written when not at the default value.
- if ((opt_flags & OPT_GLOBAL) && optval_default(p, (char_u *)varp)) {
+ if ((opt_flags & OPT_GLOBAL) && optval_default(p, varp)) {
continue;
}
if ((opt_flags & OPT_SKIPRTP)
- && (p->var == (char_u *)&p_rtp || p->var == (char_u *)&p_pp)) {
+ && (p->var == &p_rtp || p->var == &p_pp)) {
continue;
}
int round = 2;
- char_u *varp_local = NULL; // fresh value
+ void *varp_local = NULL; // fresh value
if (p->indir != PV_NONE) {
if (p->var == VAR_WIN) {
// skip window-local option when only doing globals
@@ -3349,11 +4150,11 @@ int makeset(FILE *fd, int opt_flags, int local_only)
// When fresh value of window-local option is not at the
// default, need to write it too.
if (!(opt_flags & OPT_GLOBAL) && !local_only) {
- char_u *varp_fresh = (char_u *)get_varp_scope(p, OPT_GLOBAL); // local value
+ void *varp_fresh = get_varp_scope(p, OPT_GLOBAL); // local value
if (!optval_default(p, varp_fresh)) {
round = 1;
- varp_local = (char_u *)varp;
- varp = (char *)varp_fresh;
+ varp_local = varp;
+ varp = varp_fresh;
}
}
}
@@ -3361,7 +4162,7 @@ int makeset(FILE *fd, int opt_flags, int local_only)
// Round 1: fresh value for window-local options.
// Round 2: other values
- for (; round <= 2; varp = (char *)varp_local, round++) {
+ for (; round <= 2; varp = varp_local, round++) {
char *cmd;
if (round == 1 || (opt_flags & OPT_GLOBAL)) {
cmd = "set";
@@ -3374,7 +4175,7 @@ int makeset(FILE *fd, int opt_flags, int local_only)
return FAIL;
}
} else if (p->flags & P_NUM) {
- if (put_setnum(fd, cmd, p->fullname, (long *)varp) == FAIL) {
+ if (put_setnum(fd, cmd, p->fullname, (OptInt *)varp) == FAIL) {
return FAIL;
}
} else { // P_STRING
@@ -3384,7 +4185,7 @@ int makeset(FILE *fd, int opt_flags, int local_only)
// already right, avoids reloading the syntax file.
if (p->indir == PV_SYN || p->indir == PV_FT) {
if (fprintf(fd, "if &%s != '%s'", p->fullname,
- *(char_u **)(varp)) < 0
+ *(char **)(varp)) < 0
|| put_eol(fd) < 0) {
return FAIL;
}
@@ -3431,20 +4232,10 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_
}
char *buf = NULL;
- char_u *part = NULL;
+ char *part = NULL;
if (*valuep != NULL) {
- // Output 'pastetoggle' as key names. For other
- // options some characters have to be escaped with
- // CTRL-V or backslash
- if (valuep == &p_pt) {
- char_u *s = (char_u *)(*valuep);
- while (*s != NUL) {
- if (put_escstr(fd, (char *)str2special((const char **)&s, false, false), 2) == FAIL) {
- return FAIL;
- }
- }
- } else if ((flags & P_EXPAND) != 0) {
+ if ((flags & P_EXPAND) != 0) {
size_t size = (size_t)strlen(*valuep) + 1;
// replace home directory in the whole option value into "buf"
@@ -3469,8 +4260,8 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_
if (fprintf(fd, "%s %s+=", cmd, name) < 0) {
goto fail;
}
- (void)copy_option_part(&p, (char *)part, size, ",");
- if (put_escstr(fd, (char *)part, 2) == FAIL || put_eol(fd) == FAIL) {
+ (void)copy_option_part(&p, part, size, ",");
+ if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) {
goto fail;
}
}
@@ -3497,15 +4288,15 @@ fail:
return FAIL;
}
-static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep)
+static int put_setnum(FILE *fd, char *cmd, char *name, OptInt *valuep)
{
if (fprintf(fd, "%s %s=", cmd, name) < 0) {
return FAIL;
}
- long wc;
- if (wc_use_keyname((char_u *)valuep, &wc)) {
+ OptInt wc;
+ if (wc_use_keyname(valuep, &wc)) {
// print 'wildchar' and 'wildcharm' as a key name
- if (fputs((char *)get_special_key_name((int)wc, 0), fd) < 0) {
+ if (fputs(get_special_key_name((int)wc, 0), fd) < 0) {
return FAIL;
}
} else if (fprintf(fd, "%" PRId64, (int64_t)(*valuep)) < 0) {
@@ -3529,196 +4320,94 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value)
return OK;
}
-// Unset local option value, similar to ":set opt<".
-void unset_global_local_option(char *name, void *from)
-{
- vimoption_T *p;
- buf_T *buf = (buf_T *)from;
-
- int opt_idx = findoption(name);
- if (opt_idx < 0) {
- semsg(_("E355: Unknown option: %s"), name);
- return;
- }
- p = &(options[opt_idx]);
-
- switch ((int)p->indir) {
- // global option with local value: use local value if it's been set
- case PV_EP:
- clear_string_option(&buf->b_p_ep);
- break;
- case PV_KP:
- clear_string_option(&buf->b_p_kp);
- break;
- case PV_PATH:
- clear_string_option(&buf->b_p_path);
- break;
- case PV_AR:
- buf->b_p_ar = -1;
- break;
- case PV_BKC:
- clear_string_option(&buf->b_p_bkc);
- buf->b_bkc_flags = 0;
- break;
- case PV_TAGS:
- clear_string_option(&buf->b_p_tags);
- break;
- case PV_TC:
- clear_string_option(&buf->b_p_tc);
- buf->b_tc_flags = 0;
- break;
- case PV_SISO:
- curwin->w_p_siso = -1;
- break;
- case PV_SO:
- curwin->w_p_so = -1;
- break;
- case PV_DEF:
- clear_string_option(&buf->b_p_def);
- break;
- case PV_INC:
- clear_string_option(&buf->b_p_inc);
- break;
- case PV_DICT:
- clear_string_option(&buf->b_p_dict);
- break;
- case PV_TSR:
- clear_string_option(&buf->b_p_tsr);
- break;
- case PV_TSRFU:
- clear_string_option(&buf->b_p_tsrfu);
- break;
- case PV_FP:
- clear_string_option(&buf->b_p_fp);
- break;
- case PV_EFM:
- clear_string_option(&buf->b_p_efm);
- break;
- case PV_GP:
- clear_string_option(&buf->b_p_gp);
- break;
- case PV_MP:
- clear_string_option(&buf->b_p_mp);
- break;
- case PV_SBR:
- clear_string_option(&((win_T *)from)->w_p_sbr);
- break;
- case PV_STL:
- clear_string_option(&((win_T *)from)->w_p_stl);
- break;
- case PV_WBR:
- clear_string_option(&((win_T *)from)->w_p_wbr);
- break;
- case PV_UL:
- buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
- break;
- case PV_LW:
- clear_string_option(&buf->b_p_lw);
- break;
- case PV_MENC:
- clear_string_option(&buf->b_p_menc);
- break;
- case PV_LCS:
- clear_string_option(&((win_T *)from)->w_p_lcs);
- set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true);
- redraw_later((win_T *)from, UPD_NOT_VALID);
- break;
- case PV_FCS:
- clear_string_option(&((win_T *)from)->w_p_fcs);
- set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true);
- redraw_later((win_T *)from, UPD_NOT_VALID);
- break;
- case PV_VE:
- clear_string_option(&((win_T *)from)->w_p_ve);
- ((win_T *)from)->w_ve_flags = 0;
- break;
- case PV_STC:
- clear_string_option(&((win_T *)from)->w_p_stc);
- break;
- }
-}
-
-char *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win)
+void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win)
{
if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) {
if (p->var == VAR_WIN) {
return GLOBAL_WO(get_varp_from(p, buf, win));
}
- return (char *)p->var;
+ return p->var;
}
if ((scope & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) {
switch ((int)p->indir) {
case PV_FP:
- return (char *)&(buf->b_p_fp);
+ return &(buf->b_p_fp);
case PV_EFM:
- return (char *)&(buf->b_p_efm);
+ return &(buf->b_p_efm);
case PV_GP:
- return (char *)&(buf->b_p_gp);
+ return &(buf->b_p_gp);
case PV_MP:
- return (char *)&(buf->b_p_mp);
+ return &(buf->b_p_mp);
case PV_EP:
- return (char *)&(buf->b_p_ep);
+ return &(buf->b_p_ep);
case PV_KP:
- return (char *)&(buf->b_p_kp);
+ return &(buf->b_p_kp);
case PV_PATH:
- return (char *)&(buf->b_p_path);
+ return &(buf->b_p_path);
case PV_AR:
- return (char *)&(buf->b_p_ar);
+ return &(buf->b_p_ar);
case PV_TAGS:
- return (char *)&(buf->b_p_tags);
+ return &(buf->b_p_tags);
case PV_TC:
- return (char *)&(buf->b_p_tc);
+ return &(buf->b_p_tc);
case PV_SISO:
- return (char *)&(win->w_p_siso);
+ return &(win->w_p_siso);
case PV_SO:
- return (char *)&(win->w_p_so);
+ return &(win->w_p_so);
case PV_DEF:
- return (char *)&(buf->b_p_def);
+ return &(buf->b_p_def);
case PV_INC:
- return (char *)&(buf->b_p_inc);
+ return &(buf->b_p_inc);
case PV_DICT:
- return (char *)&(buf->b_p_dict);
+ return &(buf->b_p_dict);
case PV_TSR:
- return (char *)&(buf->b_p_tsr);
+ return &(buf->b_p_tsr);
case PV_TSRFU:
- return (char *)&(buf->b_p_tsrfu);
+ return &(buf->b_p_tsrfu);
case PV_TFU:
- return (char *)&(buf->b_p_tfu);
+ return &(buf->b_p_tfu);
case PV_SBR:
- return (char *)&(win->w_p_sbr);
+ return &(win->w_p_sbr);
case PV_STL:
- return (char *)&(win->w_p_stl);
+ return &(win->w_p_stl);
case PV_WBR:
- return (char *)&(win->w_p_wbr);
+ return &(win->w_p_wbr);
case PV_UL:
- return (char *)&(buf->b_p_ul);
+ return &(buf->b_p_ul);
case PV_LW:
- return (char *)&(buf->b_p_lw);
+ return &(buf->b_p_lw);
case PV_BKC:
- return (char *)&(buf->b_p_bkc);
+ return &(buf->b_p_bkc);
case PV_MENC:
- return (char *)&(buf->b_p_menc);
+ return &(buf->b_p_menc);
case PV_FCS:
- return (char *)&(win->w_p_fcs);
+ return &(win->w_p_fcs);
case PV_LCS:
- return (char *)&(win->w_p_lcs);
+ return &(win->w_p_lcs);
case PV_VE:
- return (char *)&(win->w_p_ve);
+ return &(win->w_p_ve);
}
return NULL; // "cannot happen"
}
- return (char *)get_varp_from(p, buf, win);
+ return get_varp_from(p, buf, win);
}
/// Get pointer to option variable, depending on local or global scope.
///
/// @param scope can be OPT_LOCAL, OPT_GLOBAL or a combination.
-char *get_varp_scope(vimoption_T *p, int scope)
+void *get_varp_scope(vimoption_T *p, int scope)
{
return get_varp_scope_from(p, scope, curbuf, curwin);
}
-static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
+/// Get pointer to option variable at 'opt_idx', depending on local or global
+/// scope.
+void *get_option_varp_scope_from(int opt_idx, int scope, buf_T *buf, win_T *win)
+{
+ return get_varp_scope_from(&(options[opt_idx]), scope, buf, win);
+}
+
+void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
{
// hidden option, always return NULL
if (p->var == NULL) {
@@ -3731,309 +4420,284 @@ static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
// global option with local value: use local value if it's been set
case PV_EP:
- return *buf->b_p_ep != NUL
- ? (char_u *)&buf->b_p_ep : p->var;
+ return *buf->b_p_ep != NUL ? &buf->b_p_ep : p->var;
case PV_KP:
- return *buf->b_p_kp != NUL
- ? (char_u *)&buf->b_p_kp : p->var;
+ return *buf->b_p_kp != NUL ? &buf->b_p_kp : p->var;
case PV_PATH:
- return *buf->b_p_path != NUL
- ? (char_u *)&(buf->b_p_path) : p->var;
+ return *buf->b_p_path != NUL ? &(buf->b_p_path) : p->var;
case PV_AR:
- return buf->b_p_ar >= 0
- ? (char_u *)&(buf->b_p_ar) : p->var;
+ return buf->b_p_ar >= 0 ? &(buf->b_p_ar) : p->var;
case PV_TAGS:
- return *buf->b_p_tags != NUL
- ? (char_u *)&(buf->b_p_tags) : p->var;
+ return *buf->b_p_tags != NUL ? &(buf->b_p_tags) : p->var;
case PV_TC:
- return *buf->b_p_tc != NUL
- ? (char_u *)&(buf->b_p_tc) : p->var;
+ return *buf->b_p_tc != NUL ? &(buf->b_p_tc) : p->var;
case PV_SISO:
- return win->w_p_siso >= 0
- ? (char_u *)&(win->w_p_siso) : p->var;
+ return win->w_p_siso >= 0 ? &(win->w_p_siso) : p->var;
case PV_SO:
- return win->w_p_so >= 0
- ? (char_u *)&(win->w_p_so) : p->var;
+ return win->w_p_so >= 0 ? &(win->w_p_so) : p->var;
case PV_BKC:
- return *buf->b_p_bkc != NUL
- ? (char_u *)&(buf->b_p_bkc) : p->var;
+ return *buf->b_p_bkc != NUL ? &(buf->b_p_bkc) : p->var;
case PV_DEF:
- return *buf->b_p_def != NUL
- ? (char_u *)&(buf->b_p_def) : p->var;
+ return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var;
case PV_INC:
- return *buf->b_p_inc != NUL
- ? (char_u *)&(buf->b_p_inc) : p->var;
+ return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var;
case PV_DICT:
- return *buf->b_p_dict != NUL
- ? (char_u *)&(buf->b_p_dict) : p->var;
+ return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var;
case PV_TSR:
- return *buf->b_p_tsr != NUL
- ? (char_u *)&(buf->b_p_tsr) : p->var;
+ return *buf->b_p_tsr != NUL ? &(buf->b_p_tsr) : p->var;
case PV_TSRFU:
- return *buf->b_p_tsrfu != NUL
- ? (char_u *)&(buf->b_p_tsrfu) : p->var;
+ return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var;
case PV_FP:
- return *buf->b_p_fp != NUL
- ? (char_u *)&(buf->b_p_fp) : p->var;
+ return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var;
case PV_EFM:
- return *buf->b_p_efm != NUL
- ? (char_u *)&(buf->b_p_efm) : p->var;
+ return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var;
case PV_GP:
- return *buf->b_p_gp != NUL
- ? (char_u *)&(buf->b_p_gp) : p->var;
+ return *buf->b_p_gp != NUL ? &(buf->b_p_gp) : p->var;
case PV_MP:
- return *buf->b_p_mp != NUL
- ? (char_u *)&(buf->b_p_mp) : p->var;
+ return *buf->b_p_mp != NUL ? &(buf->b_p_mp) : p->var;
case PV_SBR:
- return *win->w_p_sbr != NUL
- ? (char_u *)&(win->w_p_sbr) : p->var;
+ return *win->w_p_sbr != NUL ? &(win->w_p_sbr) : p->var;
case PV_STL:
- return *win->w_p_stl != NUL
- ? (char_u *)&(win->w_p_stl) : p->var;
+ return *win->w_p_stl != NUL ? &(win->w_p_stl) : p->var;
case PV_WBR:
- return *win->w_p_wbr != NUL
- ? (char_u *)&(win->w_p_wbr) : p->var;
+ return *win->w_p_wbr != NUL ? &(win->w_p_wbr) : p->var;
case PV_UL:
- return buf->b_p_ul != NO_LOCAL_UNDOLEVEL
- ? (char_u *)&(buf->b_p_ul) : p->var;
+ return buf->b_p_ul != NO_LOCAL_UNDOLEVEL ? &(buf->b_p_ul) : p->var;
case PV_LW:
- return *buf->b_p_lw != NUL
- ? (char_u *)&(buf->b_p_lw) : p->var;
+ return *buf->b_p_lw != NUL ? &(buf->b_p_lw) : p->var;
case PV_MENC:
- return *buf->b_p_menc != NUL
- ? (char_u *)&(buf->b_p_menc) : p->var;
+ return *buf->b_p_menc != NUL ? &(buf->b_p_menc) : p->var;
case PV_FCS:
- return *win->w_p_fcs != NUL
- ? (char_u *)&(win->w_p_fcs) : p->var;
+ return *win->w_p_fcs != NUL ? &(win->w_p_fcs) : p->var;
case PV_LCS:
- return *win->w_p_lcs != NUL
- ? (char_u *)&(win->w_p_lcs) : p->var;
+ return *win->w_p_lcs != NUL ? &(win->w_p_lcs) : p->var;
case PV_VE:
- return *win->w_p_ve != NUL
- ? (char_u *)&win->w_p_ve : p->var;
+ return *win->w_p_ve != NUL ? &win->w_p_ve : p->var;
case PV_ARAB:
- return (char_u *)&(win->w_p_arab);
+ return &(win->w_p_arab);
case PV_LIST:
- return (char_u *)&(win->w_p_list);
+ return &(win->w_p_list);
case PV_SPELL:
- return (char_u *)&(win->w_p_spell);
+ return &(win->w_p_spell);
case PV_CUC:
- return (char_u *)&(win->w_p_cuc);
+ return &(win->w_p_cuc);
case PV_CUL:
- return (char_u *)&(win->w_p_cul);
+ return &(win->w_p_cul);
case PV_CULOPT:
- return (char_u *)&(win->w_p_culopt);
+ return &(win->w_p_culopt);
case PV_CC:
- return (char_u *)&(win->w_p_cc);
+ return &(win->w_p_cc);
case PV_DIFF:
- return (char_u *)&(win->w_p_diff);
+ return &(win->w_p_diff);
case PV_FDC:
- return (char_u *)&(win->w_p_fdc);
+ return &(win->w_p_fdc);
case PV_FEN:
- return (char_u *)&(win->w_p_fen);
+ return &(win->w_p_fen);
case PV_FDI:
- return (char_u *)&(win->w_p_fdi);
+ return &(win->w_p_fdi);
case PV_FDL:
- return (char_u *)&(win->w_p_fdl);
+ return &(win->w_p_fdl);
case PV_FDM:
- return (char_u *)&(win->w_p_fdm);
+ return &(win->w_p_fdm);
case PV_FML:
- return (char_u *)&(win->w_p_fml);
+ return &(win->w_p_fml);
case PV_FDN:
- return (char_u *)&(win->w_p_fdn);
+ return &(win->w_p_fdn);
case PV_FDE:
- return (char_u *)&(win->w_p_fde);
+ return &(win->w_p_fde);
case PV_FDT:
- return (char_u *)&(win->w_p_fdt);
+ return &(win->w_p_fdt);
case PV_FMR:
- return (char_u *)&(win->w_p_fmr);
+ return &(win->w_p_fmr);
case PV_NU:
- return (char_u *)&(win->w_p_nu);
+ return &(win->w_p_nu);
case PV_RNU:
- return (char_u *)&(win->w_p_rnu);
+ return &(win->w_p_rnu);
case PV_NUW:
- return (char_u *)&(win->w_p_nuw);
+ return &(win->w_p_nuw);
case PV_WFH:
- return (char_u *)&(win->w_p_wfh);
+ return &(win->w_p_wfh);
case PV_WFW:
- return (char_u *)&(win->w_p_wfw);
+ return &(win->w_p_wfw);
case PV_PVW:
- return (char_u *)&(win->w_p_pvw);
+ return &(win->w_p_pvw);
case PV_RL:
- return (char_u *)&(win->w_p_rl);
+ return &(win->w_p_rl);
case PV_RLC:
- return (char_u *)&(win->w_p_rlc);
+ return &(win->w_p_rlc);
case PV_SCROLL:
- return (char_u *)&(win->w_p_scr);
+ return &(win->w_p_scr);
+ case PV_SMS:
+ return &(win->w_p_sms);
case PV_WRAP:
- return (char_u *)&(win->w_p_wrap);
+ return &(win->w_p_wrap);
case PV_LBR:
- return (char_u *)&(win->w_p_lbr);
+ return &(win->w_p_lbr);
case PV_BRI:
- return (char_u *)&(win->w_p_bri);
+ return &(win->w_p_bri);
case PV_BRIOPT:
- return (char_u *)&(win->w_p_briopt);
+ return &(win->w_p_briopt);
case PV_SCBIND:
- return (char_u *)&(win->w_p_scb);
+ return &(win->w_p_scb);
case PV_CRBIND:
- return (char_u *)&(win->w_p_crb);
+ return &(win->w_p_crb);
case PV_COCU:
- return (char_u *)&(win->w_p_cocu);
+ return &(win->w_p_cocu);
case PV_COLE:
- return (char_u *)&(win->w_p_cole);
+ return &(win->w_p_cole);
case PV_AI:
- return (char_u *)&(buf->b_p_ai);
+ return &(buf->b_p_ai);
case PV_BIN:
- return (char_u *)&(buf->b_p_bin);
+ return &(buf->b_p_bin);
case PV_BOMB:
- return (char_u *)&(buf->b_p_bomb);
+ return &(buf->b_p_bomb);
case PV_BH:
- return (char_u *)&(buf->b_p_bh);
+ return &(buf->b_p_bh);
case PV_BT:
- return (char_u *)&(buf->b_p_bt);
+ return &(buf->b_p_bt);
case PV_BL:
- return (char_u *)&(buf->b_p_bl);
+ return &(buf->b_p_bl);
case PV_CHANNEL:
- return (char_u *)&(buf->b_p_channel);
+ return &(buf->b_p_channel);
case PV_CI:
- return (char_u *)&(buf->b_p_ci);
+ return &(buf->b_p_ci);
case PV_CIN:
- return (char_u *)&(buf->b_p_cin);
+ return &(buf->b_p_cin);
case PV_CINK:
- return (char_u *)&(buf->b_p_cink);
+ return &(buf->b_p_cink);
case PV_CINO:
- return (char_u *)&(buf->b_p_cino);
+ return &(buf->b_p_cino);
case PV_CINSD:
- return (char_u *)&(buf->b_p_cinsd);
+ return &(buf->b_p_cinsd);
case PV_CINW:
- return (char_u *)&(buf->b_p_cinw);
+ return &(buf->b_p_cinw);
case PV_COM:
- return (char_u *)&(buf->b_p_com);
+ return &(buf->b_p_com);
case PV_CMS:
- return (char_u *)&(buf->b_p_cms);
+ return &(buf->b_p_cms);
case PV_CPT:
- return (char_u *)&(buf->b_p_cpt);
+ return &(buf->b_p_cpt);
#ifdef BACKSLASH_IN_FILENAME
case PV_CSL:
- return (char_u *)&(buf->b_p_csl);
+ return &(buf->b_p_csl);
#endif
case PV_CFU:
- return (char_u *)&(buf->b_p_cfu);
+ return &(buf->b_p_cfu);
case PV_OFU:
- return (char_u *)&(buf->b_p_ofu);
+ return &(buf->b_p_ofu);
case PV_EOF:
- return (char_u *)&(buf->b_p_eof);
+ return &(buf->b_p_eof);
case PV_EOL:
- return (char_u *)&(buf->b_p_eol);
+ return &(buf->b_p_eol);
case PV_FIXEOL:
- return (char_u *)&(buf->b_p_fixeol);
+ return &(buf->b_p_fixeol);
case PV_ET:
- return (char_u *)&(buf->b_p_et);
+ return &(buf->b_p_et);
case PV_FENC:
- return (char_u *)&(buf->b_p_fenc);
+ return &(buf->b_p_fenc);
case PV_FF:
- return (char_u *)&(buf->b_p_ff);
+ return &(buf->b_p_ff);
case PV_FT:
- return (char_u *)&(buf->b_p_ft);
+ return &(buf->b_p_ft);
case PV_FO:
- return (char_u *)&(buf->b_p_fo);
+ return &(buf->b_p_fo);
case PV_FLP:
- return (char_u *)&(buf->b_p_flp);
+ return &(buf->b_p_flp);
case PV_IMI:
- return (char_u *)&(buf->b_p_iminsert);
+ return &(buf->b_p_iminsert);
case PV_IMS:
- return (char_u *)&(buf->b_p_imsearch);
+ return &(buf->b_p_imsearch);
case PV_INF:
- return (char_u *)&(buf->b_p_inf);
+ return &(buf->b_p_inf);
case PV_ISK:
- return (char_u *)&(buf->b_p_isk);
+ return &(buf->b_p_isk);
case PV_INEX:
- return (char_u *)&(buf->b_p_inex);
+ return &(buf->b_p_inex);
case PV_INDE:
- return (char_u *)&(buf->b_p_inde);
+ return &(buf->b_p_inde);
case PV_INDK:
- return (char_u *)&(buf->b_p_indk);
+ return &(buf->b_p_indk);
case PV_FEX:
- return (char_u *)&(buf->b_p_fex);
+ return &(buf->b_p_fex);
case PV_LISP:
- return (char_u *)&(buf->b_p_lisp);
+ return &(buf->b_p_lisp);
case PV_LOP:
- return (char_u *)&(buf->b_p_lop);
+ return &(buf->b_p_lop);
case PV_ML:
- return (char_u *)&(buf->b_p_ml);
+ return &(buf->b_p_ml);
case PV_MPS:
- return (char_u *)&(buf->b_p_mps);
+ return &(buf->b_p_mps);
case PV_MA:
- return (char_u *)&(buf->b_p_ma);
+ return &(buf->b_p_ma);
case PV_MOD:
- return (char_u *)&(buf->b_changed);
+ return &(buf->b_changed);
case PV_NF:
- return (char_u *)&(buf->b_p_nf);
+ return &(buf->b_p_nf);
case PV_PI:
- return (char_u *)&(buf->b_p_pi);
+ return &(buf->b_p_pi);
case PV_QE:
- return (char_u *)&(buf->b_p_qe);
+ return &(buf->b_p_qe);
case PV_RO:
- return (char_u *)&(buf->b_p_ro);
+ return &(buf->b_p_ro);
case PV_SCBK:
- return (char_u *)&(buf->b_p_scbk);
+ return &(buf->b_p_scbk);
case PV_SI:
- return (char_u *)&(buf->b_p_si);
+ return &(buf->b_p_si);
case PV_STS:
- return (char_u *)&(buf->b_p_sts);
+ return &(buf->b_p_sts);
case PV_SUA:
- return (char_u *)&(buf->b_p_sua);
+ return &(buf->b_p_sua);
case PV_SWF:
- return (char_u *)&(buf->b_p_swf);
+ return &(buf->b_p_swf);
case PV_SMC:
- return (char_u *)&(buf->b_p_smc);
+ return &(buf->b_p_smc);
case PV_SYN:
- return (char_u *)&(buf->b_p_syn);
+ return &(buf->b_p_syn);
case PV_SPC:
- return (char_u *)&(win->w_s->b_p_spc);
+ return &(win->w_s->b_p_spc);
case PV_SPF:
- return (char_u *)&(win->w_s->b_p_spf);
+ return &(win->w_s->b_p_spf);
case PV_SPL:
- return (char_u *)&(win->w_s->b_p_spl);
+ return &(win->w_s->b_p_spl);
case PV_SPO:
- return (char_u *)&(win->w_s->b_p_spo);
+ return &(win->w_s->b_p_spo);
case PV_SW:
- return (char_u *)&(buf->b_p_sw);
+ return &(buf->b_p_sw);
case PV_TFU:
- return (char_u *)&(buf->b_p_tfu);
+ return &(buf->b_p_tfu);
case PV_TS:
- return (char_u *)&(buf->b_p_ts);
+ return &(buf->b_p_ts);
case PV_TW:
- return (char_u *)&(buf->b_p_tw);
+ return &(buf->b_p_tw);
case PV_UDF:
- return (char_u *)&(buf->b_p_udf);
+ return &(buf->b_p_udf);
case PV_WM:
- return (char_u *)&(buf->b_p_wm);
+ return &(buf->b_p_wm);
case PV_VSTS:
- return (char_u *)&(buf->b_p_vsts);
+ return &(buf->b_p_vsts);
case PV_VTS:
- return (char_u *)&(buf->b_p_vts);
+ return &(buf->b_p_vts);
case PV_KMAP:
- return (char_u *)&(buf->b_p_keymap);
+ return &(buf->b_p_keymap);
case PV_SCL:
- return (char_u *)&(win->w_p_scl);
+ return &(win->w_p_scl);
case PV_WINHL:
- return (char_u *)&(win->w_p_winhl);
+ return &(win->w_p_winhl);
case PV_WINBL:
- return (char_u *)&(win->w_p_winbl);
+ return &(win->w_p_winbl);
case PV_STC:
- return (char_u *)&(win->w_p_stc);
+ return &(win->w_p_stc);
default:
iemsg(_("E356: get_varp ERROR"));
}
// always return a valid pointer to avoid a crash!
- return (char_u *)&(buf->b_p_wm);
+ return &(buf->b_p_wm);
}
/// Get pointer to option variable.
-static inline char_u *get_varp(vimoption_T *p)
+static inline void *get_varp(vimoption_T *p)
{
return get_varp_from(p, curbuf, curwin);
}
@@ -4058,8 +4722,8 @@ void win_copy_options(win_T *wp_from, win_T *wp_to)
static char *copy_option_val(const char *val)
{
- if (val == empty_option) {
- return empty_option; // no need to allocate memory
+ if (val == empty_string_option) {
+ return empty_string_option; // no need to allocate memory
}
return xstrdup(val);
}
@@ -4079,7 +4743,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_ve = copy_option_val(from->wo_ve);
to->wo_ve_flags = from->wo_ve_flags;
to->wo_nuw = from->wo_nuw;
- to->wo_rl = from->wo_rl;
+ to->wo_rl = from->wo_rl;
to->wo_rlc = copy_option_val(from->wo_rlc);
to->wo_sbr = copy_option_val(from->wo_sbr);
to->wo_stl = copy_option_val(from->wo_stl);
@@ -4091,8 +4755,11 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_briopt = copy_option_val(from->wo_briopt);
to->wo_scb = from->wo_scb;
to->wo_scb_save = from->wo_scb_save;
+ to->wo_sms = from->wo_sms;
to->wo_crb = from->wo_crb;
to->wo_crb_save = from->wo_crb_save;
+ to->wo_siso = from->wo_siso;
+ to->wo_so = from->wo_so;
to->wo_spell = from->wo_spell;
to->wo_cuc = from->wo_cuc;
to->wo_cul = from->wo_cul;
@@ -4103,7 +4770,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_cocu = copy_option_val(from->wo_cocu);
to->wo_cole = from->wo_cole;
to->wo_fdc = copy_option_val(from->wo_fdc);
- to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_option;
+ to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_string_option;
to->wo_fen = from->wo_fen;
to->wo_fen_save = from->wo_fen_save;
to->wo_fdi = copy_option_val(from->wo_fdi);
@@ -4111,7 +4778,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_fdl = from->wo_fdl;
to->wo_fdl_save = from->wo_fdl_save;
to->wo_fdm = copy_option_val(from->wo_fdm);
- to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_option;
+ to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_string_option;
to->wo_fdn = from->wo_fdn;
to->wo_fde = copy_option_val(from->wo_fde);
to->wo_fdt = copy_option_val(from->wo_fdt);
@@ -4133,7 +4800,7 @@ void check_win_options(win_T *win)
check_winopt(&win->w_allbuf_opt);
}
-/// Check for NULL pointers in a winopt_T and replace them with empty_option.
+/// Check for NULL pointers in a winopt_T and replace them with empty_string_option.
static void check_winopt(winopt_T *wop)
{
check_string_option(&wop->wo_fdc);
@@ -4192,11 +4859,12 @@ void didset_window_options(win_T *wp, bool valid_cursor)
check_colorcolumn(wp);
briopt_check(wp);
fill_culopt_flags(NULL, wp);
- set_chars_option(wp, &wp->w_p_fcs, true);
- set_chars_option(wp, &wp->w_p_lcs, true);
+ set_fillchars_option(wp, wp->w_p_fcs, true);
+ set_listchars_option(wp, wp->w_p_lcs, true);
parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
check_blending(wp);
set_winbar_win(wp, false, valid_cursor);
+ check_signcolumn(wp);
wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
@@ -4286,8 +4954,8 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_ff = xstrdup(p_ff);
break;
}
- buf->b_p_bh = empty_option;
- buf->b_p_bt = empty_option;
+ buf->b_p_bh = empty_string_option;
+ buf->b_p_bt = empty_string_option;
} else {
free_buf_options(buf, false);
}
@@ -4348,7 +5016,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_vsts = xstrdup(p_vsts);
COPY_OPT_SCTX(buf, BV_VSTS);
- if (p_vsts && p_vsts != empty_option) {
+ if (p_vsts && p_vsts != empty_string_option) {
(void)tabstop_set(p_vsts, &buf->b_p_vsts_array);
} else {
buf->b_p_vsts_array = NULL;
@@ -4384,7 +5052,7 @@ void buf_copy_options(buf_T *buf, int flags)
COPY_OPT_SCTX(buf, BV_LOP);
// Don't copy 'filetype', it must be detected
- buf->b_p_ft = empty_option;
+ buf->b_p_ft = empty_string_option;
buf->b_p_pi = p_pi;
COPY_OPT_SCTX(buf, BV_PI);
buf->b_p_cinw = xstrdup(p_cinw);
@@ -4392,10 +5060,10 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_lisp = p_lisp;
COPY_OPT_SCTX(buf, BV_LISP);
// Don't copy 'syntax', it must be set
- buf->b_p_syn = empty_option;
+ buf->b_p_syn = empty_string_option;
buf->b_p_smc = p_smc;
COPY_OPT_SCTX(buf, BV_SMC);
- buf->b_s.b_syn_isk = empty_option;
+ buf->b_s.b_syn_isk = empty_string_option;
buf->b_s.b_p_spc = xstrdup(p_spc);
COPY_OPT_SCTX(buf, BV_SPC);
(void)compile_cap_prog(&buf->b_s);
@@ -4409,7 +5077,7 @@ void buf_copy_options(buf_T *buf, int flags)
COPY_OPT_SCTX(buf, BV_INDE);
buf->b_p_indk = xstrdup(p_indk);
COPY_OPT_SCTX(buf, BV_INDK);
- buf->b_p_fp = empty_option;
+ buf->b_p_fp = empty_string_option;
buf->b_p_fex = xstrdup(p_fex);
COPY_OPT_SCTX(buf, BV_FEX);
buf->b_p_sua = xstrdup(p_sua);
@@ -4428,30 +5096,30 @@ void buf_copy_options(buf_T *buf, int flags)
// are not copied, start using the global value
buf->b_p_ar = -1;
buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
- buf->b_p_bkc = empty_option;
+ buf->b_p_bkc = empty_string_option;
buf->b_bkc_flags = 0;
- buf->b_p_gp = empty_option;
- buf->b_p_mp = empty_option;
- buf->b_p_efm = empty_option;
- buf->b_p_ep = empty_option;
- buf->b_p_kp = empty_option;
- buf->b_p_path = empty_option;
- buf->b_p_tags = empty_option;
- buf->b_p_tc = empty_option;
+ buf->b_p_gp = empty_string_option;
+ buf->b_p_mp = empty_string_option;
+ buf->b_p_efm = empty_string_option;
+ buf->b_p_ep = empty_string_option;
+ buf->b_p_kp = empty_string_option;
+ buf->b_p_path = empty_string_option;
+ buf->b_p_tags = empty_string_option;
+ buf->b_p_tc = empty_string_option;
buf->b_tc_flags = 0;
- buf->b_p_def = empty_option;
- buf->b_p_inc = empty_option;
+ buf->b_p_def = empty_string_option;
+ buf->b_p_inc = empty_string_option;
buf->b_p_inex = xstrdup(p_inex);
COPY_OPT_SCTX(buf, BV_INEX);
- buf->b_p_dict = empty_option;
- buf->b_p_tsr = empty_option;
- buf->b_p_tsrfu = empty_option;
+ buf->b_p_dict = empty_string_option;
+ buf->b_p_tsr = empty_string_option;
+ buf->b_p_tsrfu = empty_string_option;
buf->b_p_qe = xstrdup(p_qe);
COPY_OPT_SCTX(buf, BV_QE);
buf->b_p_udf = p_udf;
COPY_OPT_SCTX(buf, BV_UDF);
- buf->b_p_lw = empty_option;
- buf->b_p_menc = empty_option;
+ buf->b_p_lw = empty_string_option;
+ buf->b_p_menc = empty_string_option;
// Don't copy the options set by ex_help(), use the saved values,
// when going from a help buffer to a non-help buffer.
@@ -4459,7 +5127,7 @@ void buf_copy_options(buf_T *buf, int flags)
// or to a help buffer.
if (dont_do_help) {
buf->b_p_isk = save_p_isk;
- if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
+ if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) {
(void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
@@ -4472,7 +5140,7 @@ void buf_copy_options(buf_T *buf, int flags)
COPY_OPT_SCTX(buf, BV_TS);
buf->b_p_vts = xstrdup(p_vts);
COPY_OPT_SCTX(buf, BV_VTS);
- if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
+ if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) {
(void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
@@ -4525,8 +5193,10 @@ void set_imsearch_global(buf_T *buf)
}
static int expand_option_idx = -1;
-static char_u expand_option_name[5] = { 't', '_', NUL, NUL, NUL };
+static int expand_option_start_col = 0;
+static char expand_option_name[5] = { 't', '_', NUL, NUL, NUL };
static int expand_option_flags = 0;
+static bool expand_option_append = false;
/// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL
void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
@@ -4560,10 +5230,11 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
}
if (strncmp(p, "no", 2) == 0) {
xp->xp_context = EXPAND_BOOL_SETTINGS;
+ xp->xp_prefix = XP_PREFIX_NO;
p += 2;
- }
- if (strncmp(p, "inv", 3) == 0) {
+ } else if (strncmp(p, "inv", 3) == 0) {
xp->xp_context = EXPAND_BOOL_SETTINGS;
+ xp->xp_prefix = XP_PREFIX_INV;
p += 3;
}
xp->xp_pattern = p;
@@ -4580,15 +5251,15 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
return;
}
}
- int key = get_special_key_code((char_u *)arg + 1);
+ int key = get_special_key_code(arg + 1);
if (key == 0) { // unknown name
xp->xp_context = EXPAND_NOTHING;
return;
}
nextchar = *++p;
is_term_option = true;
- expand_option_name[2] = (char_u)KEY2TERMCAP0(key);
- expand_option_name[3] = KEY2TERMCAP1(key);
+ expand_option_name[2] = (char)(uint8_t)KEY2TERMCAP0(key);
+ expand_option_name[3] = (char)(uint8_t)KEY2TERMCAP1(key);
} else {
if (p[0] == 't' && p[1] == '_') {
p += 2;
@@ -4600,8 +5271,8 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
}
nextchar = *++p;
is_term_option = true;
- expand_option_name[2] = (char_u)p[-2];
- expand_option_name[3] = (char_u)p[-1];
+ expand_option_name[2] = p[-2];
+ expand_option_name[3] = p[-1];
} else {
// Allow * wildcard.
while (ASCII_ISALNUM(*p) || *p == '_' || *p == '*') {
@@ -4611,7 +5282,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
return;
}
nextchar = *p;
- opt_idx = findoption_len((const char *)arg, (size_t)(p - arg));
+ opt_idx = findoption_len(arg, (size_t)(p - arg));
if (opt_idx == -1 || options[opt_idx].var == NULL) {
xp->xp_context = EXPAND_NOTHING;
return;
@@ -4624,7 +5295,15 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
}
}
// handle "-=" and "+="
+ expand_option_append = false;
+ bool expand_option_subtract = false;
if ((nextchar == '-' || nextchar == '+' || nextchar == '^') && p[1] == '=') {
+ if (nextchar == '-') {
+ expand_option_subtract = true;
+ }
+ if (nextchar == '+' || nextchar == '^') {
+ expand_option_append = true;
+ }
p++;
nextchar = '=';
}
@@ -4633,25 +5312,53 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
xp->xp_context = EXPAND_UNSUCCESSFUL;
return;
}
- if (p[1] == NUL) {
+
+ // Below are for handling expanding a specific option's value after the '=' or ':'
+
+ if (is_term_option) {
+ expand_option_idx = -1;
+ } else {
+ expand_option_idx = opt_idx;
+ }
+
+ xp->xp_pattern = p + 1;
+ expand_option_start_col = (int)(p + 1 - xp->xp_line);
+
+ // Certain options currently have special case handling to reuse the
+ // expansion logic with other commands.
+ if (options[opt_idx].var == &p_syn) {
+ xp->xp_context = EXPAND_OWNSYNTAX;
+ return;
+ }
+ if (options[opt_idx].var == &p_ft) {
+ xp->xp_context = EXPAND_FILETYPE;
+ return;
+ }
+
+ // Now pick. If the option has a custom expander, use that. Otherwise, just
+ // fill with the existing option value.
+ if (expand_option_subtract) {
+ xp->xp_context = EXPAND_SETTING_SUBTRACT;
+ return;
+ } else if (expand_option_idx >= 0
+ && options[expand_option_idx].opt_expand_cb != NULL) {
+ xp->xp_context = EXPAND_STRING_SETTING;
+ } else if (*xp->xp_pattern == NUL) {
xp->xp_context = EXPAND_OLD_SETTING;
- if (is_term_option) {
- expand_option_idx = -1;
- } else {
- expand_option_idx = opt_idx;
- }
- xp->xp_pattern = p + 1;
return;
+ } else {
+ xp->xp_context = EXPAND_NOTHING;
}
- xp->xp_context = EXPAND_NOTHING;
+
if (is_term_option || (flags & P_NUM)) {
return;
}
- xp->xp_pattern = p + 1;
+ // Only string options below
+ // Options that have P_EXPAND are considered to all use file/dir expansion.
if (flags & P_EXPAND) {
- p = (char *)options[opt_idx].var;
+ p = options[opt_idx].var;
if (p == (char *)&p_bdir
|| p == (char *)&p_dir
|| p == (char *)&p_path
@@ -4665,8 +5372,6 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
} else {
xp->xp_backslash = XP_BS_ONE;
}
- } else if (p == (char *)&p_ft) {
- xp->xp_context = EXPAND_FILETYPE;
} else {
xp->xp_context = EXPAND_FILES;
// for 'tags' need three backslashes for a space
@@ -4676,29 +5381,54 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
xp->xp_backslash = XP_BS_ONE;
}
}
+ if (flags & P_COMMA) {
+ xp->xp_backslash |= XP_BS_COMMA;
+ }
}
- // For an option that is a list of file names, find the start of the
- // last file name.
- for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) {
- // count number of backslashes before ' ' or ','
- if (*p == ' ' || *p == ',') {
- char *s = p;
- while (s > xp->xp_pattern && *(s - 1) == '\\') {
- s--;
- }
- if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3))
- || (*p == ',' && (flags & P_COMMA) && ((p - s) & 1) == 0)) {
- xp->xp_pattern = p + 1;
- break;
+ // For an option that is a list of file names, or comma/colon-separated
+ // values, split it by the delimiter and find the start of the current
+ // pattern, while accounting for backslash-escaped space/commas/colons.
+ // Triple-backslashed escaped file names (e.g. 'path') can also be
+ // delimited by space.
+ if ((flags & P_EXPAND) || (flags & P_COMMA) || (flags & P_COLON)) {
+ for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) {
+ // count number of backslashes before ' ' or ','
+ if (*p == ' ' || *p == ',' || (*p == ':' && (flags & P_COLON))) {
+ char *s = p;
+ while (s > xp->xp_pattern && *(s - 1) == '\\') {
+ s--;
+ }
+ if ((*p == ' ' && ((xp->xp_backslash & XP_BS_THREE) && (p - s) < 3))
+#if defined(BACKSLASH_IN_FILENAME)
+ || (*p == ',' && (flags & P_COMMA) && (p - s) < 1)
+#else
+ || (*p == ',' && (flags & P_COMMA) && (p - s) < 2)
+#endif
+ || (*p == ':' && (flags & P_COLON))) {
+ xp->xp_pattern = p + 1;
+ break;
+ }
}
}
+ }
- // for 'spellsuggest' start at "file:"
- if (options[opt_idx].var == (char_u *)&p_sps
- && strncmp(p, "file:", 5) == 0) {
- xp->xp_pattern = p + 5;
- break;
+ // An option that is a list of single-character flags should always start
+ // at the end as we don't complete words.
+ if (flags & P_FLAGLIST) {
+ xp->xp_pattern = arg + strlen(arg);
+ }
+
+ // Some options can either be using file/dir expansions, or custom value
+ // expansion depending on what the user typed. Unfortunately we have to
+ // manually handle it here to make sure we have the correct xp_context set.
+ // for 'spellsuggest' start at "file:"
+ if (options[opt_idx].var == &p_sps) {
+ if (strncmp(xp->xp_pattern, "file:", 5) == 0) {
+ xp->xp_pattern += 5;
+ return;
+ } else if (options[expand_option_idx].opt_expand_cb != NULL) {
+ xp->xp_context = EXPAND_STRING_SETTING;
}
}
}
@@ -4722,7 +5452,7 @@ static bool match_str(char *const str, regmatch_T *const regmatch, char **const
const char *const fuzzystr, fuzmatch_str_T *const fuzmatch)
{
if (!fuzzy) {
- if (vim_regexec(regmatch, str, (colnr_T)0)) {
+ if (vim_regexec(regmatch, str, 0)) {
if (!test_only) {
matches[idx] = xstrdup(str);
}
@@ -4757,10 +5487,9 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM
// loop == 0: count the number of matching options
// loop == 1: copy the matching options into allocated memory
for (int loop = 0; loop <= 1; loop++) {
- int match;
regmatch->rm_ic = ic;
if (xp->xp_context != EXPAND_BOOL_SETTINGS) {
- for (match = 0; match < (int)ARRAY_SIZE(names);
+ for (int match = 0; match < (int)ARRAY_SIZE(names);
match++) {
if (match_str(names[match], regmatch, *matches,
count, (loop == 0), fuzzy, fuzzystr, fuzmatch)) {
@@ -4791,7 +5520,7 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM
count++;
}
} else if (!fuzzy && options[opt_idx].shortname != NULL
- && vim_regexec(regmatch, options[opt_idx].shortname, (colnr_T)0)) {
+ && vim_regexec(regmatch, options[opt_idx].shortname, 0)) {
// Compare against the abbreviated option name (for regular
// expression match). Fuzzy matching (previous if) already
// matches against both the expanded and abbreviated names.
@@ -4824,16 +5553,42 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM
return OK;
}
-void ExpandOldSetting(int *num_file, char ***file)
+/// Escape an option value that can be used on the command-line with :set.
+/// Caller needs to free the returned string, unless NULL is returned.
+static char *escape_option_str_cmdline(char *var)
+{
+ // A backslash is required before some characters. This is the reverse of
+ // what happens in do_set().
+ char *buf = vim_strsave_escaped(var, escape_chars);
+
+#ifdef BACKSLASH_IN_FILENAME
+ // For MS-Windows et al. we don't double backslashes at the start and
+ // before a file name character.
+ // The reverse is found at stropt_copy_value().
+ for (var = buf; *var != NUL; MB_PTR_ADV(var)) {
+ if (var[0] == '\\' && var[1] == '\\'
+ && expand_option_idx >= 0
+ && (options[expand_option_idx].flags & P_EXPAND)
+ && vim_isfilec((uint8_t)var[2])
+ && (var[2] != '\\' || (var == buf && var[4] != '\\'))) {
+ STRMOVE(var, var + 1);
+ }
+ }
+#endif
+ return buf;
+}
+
+/// Expansion handler for :set= when we just want to fill in with the existing value.
+int ExpandOldSetting(int *numMatches, char ***matches)
{
char *var = NULL;
- *num_file = 0;
- *file = xmalloc(sizeof(char_u *));
+ *numMatches = 0;
+ *matches = xmalloc(sizeof(char *));
// For a terminal key code expand_option_idx is < 0.
if (expand_option_idx < 0) {
- expand_option_idx = findoption((const char *)expand_option_name);
+ expand_option_idx = findoption(expand_option_name);
}
if (expand_option_idx >= 0) {
@@ -4844,26 +5599,149 @@ void ExpandOldSetting(int *num_file, char ***file)
var = "";
}
- // A backslash is required before some characters. This is the reverse of
- // what happens in do_set().
- char *buf = vim_strsave_escaped(var, escape_chars);
+ char *buf = escape_option_str_cmdline(var);
-#ifdef BACKSLASH_IN_FILENAME
- // For MS-Windows et al. we don't double backslashes at the start and
- // before a file name character.
- for (var = buf; *var != NUL; MB_PTR_ADV(var)) {
- if (var[0] == '\\' && var[1] == '\\'
- && expand_option_idx >= 0
- && (options[expand_option_idx].flags & P_EXPAND)
- && vim_isfilec((uint8_t)var[2])
- && (var[2] != '\\' || (var == buf && var[4] != '\\'))) {
- STRMOVE(var, var + 1);
+ (*matches)[0] = buf;
+ *numMatches = 1;
+ return OK;
+}
+
+/// Expansion handler for :set=/:set+= when the option has a custom expansion handler.
+int ExpandStringSetting(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches)
+{
+ if (expand_option_idx < 0
+ || options[expand_option_idx].opt_expand_cb == NULL) {
+ // Not supposed to reach this. This function is only for options with
+ // custom expansion callbacks.
+ return FAIL;
+ }
+
+ optexpand_T args = {
+ .oe_varp = get_varp_scope(&options[expand_option_idx], expand_option_flags),
+ .oe_append = expand_option_append,
+ .oe_regmatch = regmatch,
+ .oe_xp = xp,
+ .oe_set_arg = xp->xp_line + expand_option_start_col,
+ };
+ args.oe_include_orig_val = !expand_option_append && (*args.oe_set_arg == NUL);
+
+ // Retrieve the existing value, but escape it as a reverse of setting it.
+ // We technically only need to do this when oe_append or
+ // oe_include_orig_val is true.
+ option_value2string(&options[expand_option_idx], expand_option_flags);
+ char *var = NameBuff;
+ char *buf = escape_option_str_cmdline(var);
+ args.oe_opt_value = buf;
+
+ int num_ret = options[expand_option_idx].opt_expand_cb(&args, numMatches, matches);
+
+ xfree(buf);
+ return num_ret;
+}
+
+/// Expansion handler for :set-=
+int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches)
+{
+ if (expand_option_idx < 0) {
+ // term option
+ return ExpandOldSetting(numMatches, matches);
+ }
+
+ char *option_val = *(char **)get_option_varp_scope_from(expand_option_idx,
+ expand_option_flags,
+ curbuf, curwin);
+
+ uint32_t option_flags = options[expand_option_idx].flags;
+
+ if (option_flags & P_NUM) {
+ return ExpandOldSetting(numMatches, matches);
+ } else if (option_flags & P_COMMA) {
+ // Split the option by comma, then present each option to the user if
+ // it matches the pattern.
+ // This condition needs to go first, because 'whichwrap' has both
+ // P_COMMA and P_FLAGLIST.
+
+ if (*option_val == NUL) {
+ return FAIL;
}
+
+ // Make a copy as we need to inject null characters destructively.
+ char *option_copy = xstrdup(option_val);
+ char *next_val = option_copy;
+
+ garray_T ga;
+ ga_init(&ga, sizeof(char *), 10);
+
+ do {
+ char *item = next_val;
+ char *comma = vim_strchr(next_val, ',');
+ while (comma != NULL && comma != next_val && *(comma - 1) == '\\') {
+ // "\," is interpreted as a literal comma rather than option
+ // separator when reading options in copy_option_part(). Skip
+ // it.
+ comma = vim_strchr(comma + 1, ',');
+ }
+ if (comma != NULL) {
+ *comma = NUL; // null-terminate this value, required by later functions
+ next_val = comma + 1;
+ } else {
+ next_val = NULL;
+ }
+
+ if (*item == NUL) {
+ // empty value, don't add to list
+ continue;
+ }
+
+ if (!vim_regexec(regmatch, item, 0)) {
+ continue;
+ }
+
+ char *buf = escape_option_str_cmdline(item);
+ GA_APPEND(char *, &ga, buf);
+ } while (next_val != NULL);
+
+ xfree(option_copy);
+
+ *matches = ga.ga_data;
+ *numMatches = ga.ga_len;
+ return OK;
+ } else if (option_flags & P_FLAGLIST) {
+ // Only present the flags that are set on the option as the other flags
+ // are not meaningful to do set-= on.
+
+ if (*xp->xp_pattern != NUL) {
+ // Don't suggest anything if cmdline is non-empty. Vim's set-=
+ // behavior requires consecutive strings and it's usually
+ // unintuitive to users if they try to subtract multiple flags at
+ // once.
+ return FAIL;
+ }
+
+ size_t num_flags = strlen(option_val);
+ if (num_flags == 0) {
+ return FAIL;
+ }
+
+ *matches = xmalloc(sizeof(char *) * (num_flags + 1));
+
+ int count = 0;
+
+ (*matches)[count++] = xstrdup(option_val);
+
+ if (num_flags > 1) {
+ // If more than one flags, split the flags up and expose each
+ // character as individual choice.
+ for (char *flag = option_val; *flag != NUL; flag++) {
+ (*matches)[count++] = xmemdupz(flag, 1);
+ }
+ }
+
+ *numMatches = count;
+ return OK;
}
-#endif
- *file[0] = buf;
- *num_file = 1;
+ return ExpandOldSetting(numMatches, matches);
}
/// Get the value for the numeric or string option///opp in a nice format into
@@ -4872,30 +5750,27 @@ void ExpandOldSetting(int *num_file, char ***file)
/// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL
static void option_value2string(vimoption_T *opp, int scope)
{
- char *varp = get_varp_scope(opp, scope);
+ void *varp = get_varp_scope(opp, scope);
if (opp->flags & P_NUM) {
- long wc = 0;
+ OptInt wc = 0;
- if (wc_use_keyname((char_u *)varp, &wc)) {
- xstrlcpy(NameBuff, (char *)get_special_key_name((int)wc, 0), sizeof(NameBuff));
+ if (wc_use_keyname(varp, &wc)) {
+ xstrlcpy(NameBuff, get_special_key_name((int)wc, 0), sizeof(NameBuff));
} else if (wc != 0) {
xstrlcpy(NameBuff, transchar((int)wc), sizeof(NameBuff));
} else {
snprintf(NameBuff,
sizeof(NameBuff),
"%" PRId64,
- (int64_t)(*(long *)varp));
+ (int64_t)(*(OptInt *)varp));
}
} else { // P_STRING
varp = *(char **)(varp);
if (varp == NULL) { // Just in case.
NameBuff[0] = NUL;
} else if (opp->flags & P_EXPAND) {
- home_replace(NULL, varp, (char *)NameBuff, MAXPATHL, false);
- // Translate 'pastetoggle' into special key names.
- } else if ((char **)opp->var == &p_pt) {
- str2specialbuf((const char *)p_pt, NameBuff, MAXPATHL);
+ home_replace(NULL, varp, NameBuff, MAXPATHL, false);
} else {
xstrlcpy(NameBuff, varp, MAXPATHL);
}
@@ -4905,10 +5780,10 @@ static void option_value2string(vimoption_T *opp, int scope)
/// Return true if "varp" points to 'wildchar' or 'wildcharm' and it can be
/// printed as a keyname.
/// "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'.
-static int wc_use_keyname(const char_u *varp, long *wcp)
+static int wc_use_keyname(const void *varp, OptInt *wcp)
{
- if (((long *)varp == &p_wc) || ((long *)varp == &p_wcm)) {
- *wcp = *(long *)varp;
+ if (((OptInt *)varp == &p_wc) || ((OptInt *)varp == &p_wcm)) {
+ *wcp = *(OptInt *)varp;
if (IS_SPECIAL(*wcp) || find_special_key_in_table((int)(*wcp)) >= 0) {
return true;
}
@@ -4926,133 +5801,6 @@ bool shortmess(int x)
&& vim_strchr(SHM_ALL_ABBREVIATIONS, x) != NULL)));
}
-/// paste_option_changed() - Called after p_paste was set or reset.
-static void paste_option_changed(void)
-{
- static int old_p_paste = false;
- static int save_sm = 0;
- static int save_sta = 0;
- static int save_ru = 0;
- static int save_ri = 0;
- static int save_hkmap = 0;
-
- if (p_paste) {
- // Paste switched from off to on.
- // Save the current values, so they can be restored later.
- if (!old_p_paste) {
- // save options for each buffer
- FOR_ALL_BUFFERS(buf) {
- buf->b_p_tw_nopaste = buf->b_p_tw;
- buf->b_p_wm_nopaste = buf->b_p_wm;
- buf->b_p_sts_nopaste = buf->b_p_sts;
- buf->b_p_ai_nopaste = buf->b_p_ai;
- buf->b_p_et_nopaste = buf->b_p_et;
- if (buf->b_p_vsts_nopaste) {
- xfree(buf->b_p_vsts_nopaste);
- }
- buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option
- ? xstrdup(buf->b_p_vsts)
- : NULL;
- }
-
- // save global options
- save_sm = p_sm;
- save_sta = p_sta;
- save_ru = p_ru;
- save_ri = p_ri;
- save_hkmap = p_hkmap;
- // save global values for local buffer options
- p_ai_nopaste = p_ai;
- p_et_nopaste = p_et;
- p_sts_nopaste = p_sts;
- p_tw_nopaste = p_tw;
- p_wm_nopaste = p_wm;
- if (p_vsts_nopaste) {
- xfree(p_vsts_nopaste);
- }
- p_vsts_nopaste = p_vsts && p_vsts != empty_option ? xstrdup(p_vsts) : NULL;
- }
-
- // Always set the option values, also when 'paste' is set when it is
- // already on.
- // set options for each buffer
- FOR_ALL_BUFFERS(buf) {
- buf->b_p_tw = 0; // textwidth is 0
- buf->b_p_wm = 0; // wrapmargin is 0
- buf->b_p_sts = 0; // softtabstop is 0
- buf->b_p_ai = 0; // no auto-indent
- buf->b_p_et = 0; // no expandtab
- if (buf->b_p_vsts) {
- free_string_option(buf->b_p_vsts);
- }
- buf->b_p_vsts = empty_option;
- XFREE_CLEAR(buf->b_p_vsts_array);
- }
-
- // set global options
- p_sm = 0; // no showmatch
- p_sta = 0; // no smarttab
- if (p_ru) {
- status_redraw_all(); // redraw to remove the ruler
- }
- p_ru = 0; // no ruler
- p_ri = 0; // no reverse insert
- p_hkmap = 0; // no Hebrew keyboard
- // set global values for local buffer options
- p_tw = 0;
- p_wm = 0;
- p_sts = 0;
- p_ai = 0;
- if (p_vsts) {
- free_string_option(p_vsts);
- }
- p_vsts = empty_option;
- } else if (old_p_paste) {
- // Paste switched from on to off: Restore saved values.
-
- // restore options for each buffer
- FOR_ALL_BUFFERS(buf) {
- buf->b_p_tw = buf->b_p_tw_nopaste;
- buf->b_p_wm = buf->b_p_wm_nopaste;
- buf->b_p_sts = buf->b_p_sts_nopaste;
- buf->b_p_ai = buf->b_p_ai_nopaste;
- buf->b_p_et = buf->b_p_et_nopaste;
- if (buf->b_p_vsts) {
- free_string_option(buf->b_p_vsts);
- }
- buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_option;
- xfree(buf->b_p_vsts_array);
- if (buf->b_p_vsts && buf->b_p_vsts != empty_option) {
- (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
- } else {
- buf->b_p_vsts_array = NULL;
- }
- }
-
- // restore global options
- p_sm = save_sm;
- p_sta = save_sta;
- if (p_ru != save_ru) {
- status_redraw_all(); // redraw to draw the ruler
- }
- p_ru = save_ru;
- p_ri = save_ri;
- p_hkmap = save_hkmap;
- // set global values for local buffer options
- p_ai = p_ai_nopaste;
- p_et = p_et_nopaste;
- p_sts = p_sts_nopaste;
- p_tw = p_tw_nopaste;
- p_wm = p_wm_nopaste;
- if (p_vsts) {
- free_string_option(p_vsts);
- }
- p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_option;
- }
-
- old_p_paste = p_paste;
-}
-
/// vimrc_found() - Called when a vimrc or "VIMINIT" has been found.
///
/// Set the values for options that didn't get set yet to the defaults.
@@ -5105,25 +5853,11 @@ void reset_option_was_set(const char *name)
options[idx].flags &= ~P_WAS_SET;
}
-/// fill_breakat_flags() -- called when 'breakat' changes value.
-void fill_breakat_flags(void)
-{
- for (int i = 0; i < 256; i++) {
- breakat_flags[i] = false;
- }
-
- if (p_breakat != NULL) {
- for (char_u *p = (char_u *)p_breakat; *p; p++) {
- breakat_flags[*p] = true;
- }
- }
-}
-
/// fill_culopt_flags() -- called when 'culopt' changes value
int fill_culopt_flags(char *val, win_T *wp)
{
char *p;
- char_u culopt_flags_new = 0;
+ uint8_t culopt_flags_new = 0;
if (val == NULL) {
p = wp->w_p_culopt;
@@ -5131,6 +5865,7 @@ int fill_culopt_flags(char *val, win_T *wp)
p = val;
}
while (*p != NUL) {
+ // Note: Keep this in sync with p_culopt_values.
if (strncmp(p, "line", 4) == 0) {
p += 4;
culopt_flags_new |= CULOPT_LINE;
@@ -5191,7 +5926,7 @@ int option_set_callback_func(char *optval, Callback *optcb)
|| (strncmp(optval, "function(", 9) == 0)
|| (strncmp(optval, "funcref(", 8) == 0)) {
// Lambda expression or a funcref
- tv = eval_expr(optval);
+ tv = eval_expr(optval, NULL);
if (tv == NULL) {
return FAIL;
}
@@ -5214,6 +5949,20 @@ int option_set_callback_func(char *optval, Callback *optcb)
return OK;
}
+static void didset_options_sctx(int opt_flags, char **buf)
+{
+ for (int i = 0;; i++) {
+ if (buf[i] == NULL) {
+ break;
+ }
+
+ int idx = findoption(buf[i]);
+ if (idx >= 0) {
+ set_option_sctx_idx(idx, opt_flags, current_sctx);
+ }
+ }
+}
+
/// Check if backspacing over something is allowed.
/// @param what BS_INDENT, BS_EOL, BS_START, or BS_NOSTOP
bool can_bs(int what)
@@ -5221,23 +5970,20 @@ bool can_bs(int what)
if (what == BS_START && bt_prompt(curbuf)) {
return false;
}
- switch (*p_bs) {
- case '3':
- return true;
- case '2':
+
+ // support for number values was removed but we keep '2' since it is used in
+ // legacy tests
+ if (*p_bs == '2') {
return what != BS_NOSTOP;
- case '1':
- return what != BS_START;
- case '0':
- return false;
}
+
return vim_strchr(p_bs, what) != NULL;
}
/// Get the local or global value of 'backupcopy'.
///
/// @param buf The buffer.
-unsigned int get_bkc_value(buf_T *buf)
+unsigned get_bkc_value(buf_T *buf)
{
return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags;
}
@@ -5254,7 +6000,7 @@ char *get_flp_value(buf_T *buf)
}
/// Get the local or global value of the 'virtualedit' flags.
-unsigned int get_ve_flags(void)
+unsigned get_ve_flags(void)
{
return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU);
}
@@ -5270,7 +6016,7 @@ char *get_showbreak_value(win_T *const win)
return p_sbr;
}
if (strcmp(win->w_p_sbr, "NONE") == 0) {
- return empty_option;
+ return empty_string_option;
}
return win->w_p_sbr;
}
@@ -5428,53 +6174,12 @@ bool fish_like_shell(void)
/// buffer signs and on user configuration.
int win_signcol_count(win_T *wp)
{
- return win_signcol_configured(wp, NULL);
-}
-
-/// Return the number of requested sign columns, based on user / configuration.
-int win_signcol_configured(win_T *wp, int *is_fixed)
-{
- const char *scl = (const char *)wp->w_p_scl;
-
- if (is_fixed) {
- *is_fixed = 1;
- }
-
- // Note: It checks "no" or "number" in 'signcolumn' option
- if (*scl == 'n'
- && (*(scl + 1) == 'o' || (*(scl + 1) == 'u'
- && (wp->w_p_nu || wp->w_p_rnu)))) {
+ if (wp->w_minscwidth <= SCL_NO) {
return 0;
}
- // yes or yes
- if (!strncmp(scl, "yes:", 4)) {
- // Fixed amount of columns
- return scl[4] - '0';
- }
- if (*scl == 'y') {
- return 1;
- }
-
- if (is_fixed) {
- // auto or auto:<NUM>
- *is_fixed = 0;
- }
-
- int minimum = 0, maximum = 1;
-
- if (!strncmp(scl, "auto:", 5)) {
- // Variable depending on a configuration
- maximum = scl[5] - '0';
- // auto:<NUM>-<NUM>
- if (strlen(scl) == 8 && *(scl + 6) == '-') {
- minimum = maximum;
- maximum = scl[7] - '0';
- }
- }
-
- int needed_signcols = buf_signcols(wp->w_buffer, maximum);
- int ret = MAX(minimum, MIN(maximum, needed_signcols));
+ int needed_signcols = buf_signcols(wp->w_buffer, wp->w_maxscwidth);
+ int ret = MAX(wp->w_minscwidth, MIN(wp->w_maxscwidth, needed_signcols));
assert(ret <= SIGN_SHOW_MAX);
return ret;
}
@@ -5490,7 +6195,7 @@ dict_T *get_winbuf_options(const int bufopt)
if ((bufopt && (opt->indir & PV_BUF))
|| (!bufopt && (opt->indir & PV_WIN))) {
- char_u *varp = get_varp(opt);
+ void *varp = get_varp(opt);
if (varp != NULL) {
if (opt->flags & P_STRING) {
@@ -5498,7 +6203,7 @@ dict_T *get_winbuf_options(const int bufopt)
*(const char **)varp);
} else if (opt->flags & P_NUM) {
tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname),
- *(long *)varp);
+ *(OptInt *)varp);
} else {
tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *(int *)varp);
}
@@ -5511,43 +6216,43 @@ dict_T *get_winbuf_options(const int bufopt)
/// Return the effective 'scrolloff' value for the current window, using the
/// global value when appropriate.
-long get_scrolloff_value(win_T *wp)
+int get_scrolloff_value(win_T *wp)
{
// Disallow scrolloff in terminal-mode. #11915
if (State & MODE_TERMINAL) {
return 0;
}
- return wp->w_p_so < 0 ? p_so : wp->w_p_so;
+ return (int)(wp->w_p_so < 0 ? p_so : wp->w_p_so);
}
/// Return the effective 'sidescrolloff' value for the current window, using the
/// global value when appropriate.
-long get_sidescrolloff_value(win_T *wp)
+int get_sidescrolloff_value(win_T *wp)
{
- return wp->w_p_siso < 0 ? p_siso : wp->w_p_siso;
+ return (int)(wp->w_p_siso < 0 ? p_siso : wp->w_p_siso);
}
-Dictionary get_vimoption(String name, Error *err)
+Dictionary get_vimoption(String name, int scope, buf_T *buf, win_T *win, Error *err)
{
- int opt_idx = findoption_len((const char *)name.data, name.size);
- if (opt_idx < 0) {
- api_set_error(err, kErrorTypeValidation, "no such option: '%s'", name.data);
+ int opt_idx = findoption_len(name.data, name.size);
+ VALIDATE_S(opt_idx >= 0, "option (not found)", name.data, {
return (Dictionary)ARRAY_DICT_INIT;
- }
- return vimoption2dict(&options[opt_idx]);
+ });
+
+ return vimoption2dict(&options[opt_idx], scope, buf, win);
}
Dictionary get_all_vimoptions(void)
{
Dictionary retval = ARRAY_DICT_INIT;
for (size_t i = 0; options[i].fullname != NULL; i++) {
- Dictionary opt_dict = vimoption2dict(&options[i]);
+ Dictionary opt_dict = vimoption2dict(&options[i], OPT_GLOBAL, curbuf, curwin);
PUT(retval, options[i].fullname, DICTIONARY_OBJ(opt_dict));
}
return retval;
}
-static Dictionary vimoption2dict(vimoption_T *opt)
+static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win)
{
Dictionary dict = ARRAY_DICT_INIT;
@@ -5566,35 +6271,51 @@ static Dictionary vimoption2dict(vimoption_T *opt)
PUT(dict, "scope", CSTR_TO_OBJ(scope));
// welcome to the jungle
- PUT(dict, "global_local", BOOL(opt->indir & PV_BOTH));
- PUT(dict, "commalist", BOOL(opt->flags & P_COMMA));
- PUT(dict, "flaglist", BOOL(opt->flags & P_FLAGLIST));
+ PUT(dict, "global_local", BOOLEAN_OBJ(opt->indir & PV_BOTH));
+ PUT(dict, "commalist", BOOLEAN_OBJ(opt->flags & P_COMMA));
+ PUT(dict, "flaglist", BOOLEAN_OBJ(opt->flags & P_FLAGLIST));
- PUT(dict, "was_set", BOOL(opt->flags & P_WAS_SET));
+ PUT(dict, "was_set", BOOLEAN_OBJ(opt->flags & P_WAS_SET));
+
+ LastSet last_set = { .channel_id = 0 };
+ if (req_scope == OPT_GLOBAL) {
+ last_set = opt->last_set;
+ } else {
+ // Scope is either OPT_LOCAL or a fallback mode was requested.
+ if (opt->indir & PV_BUF) {
+ last_set = buf->b_p_script_ctx[opt->indir & PV_MASK];
+ }
+ if (opt->indir & PV_WIN) {
+ last_set = win->w_p_script_ctx[opt->indir & PV_MASK];
+ }
+ if (req_scope != OPT_LOCAL && last_set.script_ctx.sc_sid == 0) {
+ last_set = opt->last_set;
+ }
+ }
- PUT(dict, "last_set_sid", INTEGER_OBJ(opt->last_set.script_ctx.sc_sid));
- PUT(dict, "last_set_linenr", INTEGER_OBJ(opt->last_set.script_ctx.sc_lnum));
- PUT(dict, "last_set_chan", INTEGER_OBJ((int64_t)opt->last_set.channel_id));
+ PUT(dict, "last_set_sid", INTEGER_OBJ(last_set.script_ctx.sc_sid));
+ PUT(dict, "last_set_linenr", INTEGER_OBJ(last_set.script_ctx.sc_lnum));
+ PUT(dict, "last_set_chan", INTEGER_OBJ((int64_t)last_set.channel_id));
const char *type;
Object def;
// TODO(bfredl): do you even nocp?
- char_u *def_val = (char_u *)opt->def_val;
+ char *def_val = opt->def_val;
if (opt->flags & P_STRING) {
type = "string";
- def = CSTR_TO_OBJ(def_val ? (char *)def_val : "");
+ def = CSTR_TO_OBJ(def_val ? def_val : "");
} else if (opt->flags & P_NUM) {
type = "number";
def = INTEGER_OBJ((Integer)(intptr_t)def_val);
} else if (opt->flags & P_BOOL) {
type = "boolean";
- def = BOOL((intptr_t)def_val);
+ def = BOOLEAN_OBJ((intptr_t)def_val);
} else {
type = ""; def = NIL;
}
PUT(dict, "type", CSTR_TO_OBJ(type));
PUT(dict, "default", def);
- PUT(dict, "allows_duplicates", BOOL(!(opt->flags & P_NODUP)));
+ PUT(dict, "allows_duplicates", BOOLEAN_OBJ(!(opt->flags & P_NODUP)));
return dict;
}
diff --git a/src/nvim/option.h b/src/nvim/option.h
index 636257bfe8..ebf8e0417d 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -1,27 +1,113 @@
-#ifndef NVIM_OPTION_H
-#define NVIM_OPTION_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
+#include <stdint.h>
+#include <stdio.h> // IWYU pragma: keep
-/// Returned by get_option_value().
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/api/private/helpers.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
+
+/// The options that are local to a window or buffer have "indir" set to one of
+/// these values. Special values:
+/// PV_NONE: global option.
+/// PV_WIN is added: window-local option
+/// PV_BUF is added: buffer-local option
+/// PV_BOTH is added: global option which also has a local value.
+enum {
+ PV_BOTH = 0x1000,
+ PV_WIN = 0x2000,
+ PV_BUF = 0x4000,
+ PV_MASK = 0x0fff,
+};
+#define OPT_WIN(x) (idopt_T)(PV_WIN + (int)(x))
+#define OPT_BUF(x) (idopt_T)(PV_BUF + (int)(x))
+#define OPT_BOTH(x) (idopt_T)(PV_BOTH + (int)(x))
+
+/// WV_ and BV_ values get typecasted to this for the "indir" field
typedef enum {
- gov_unknown,
- gov_bool,
- gov_number,
- gov_string,
- gov_hidden_bool,
- gov_hidden_number,
- gov_hidden_string,
-} getoption_T;
-
-// flags for buf_copy_options()
-#define BCO_ENTER 1 // going to enter the buffer
-#define BCO_ALWAYS 2 // always copy the options
-#define BCO_NOHELP 4 // don't touch the help related options
-
-#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn'
+ PV_NONE = 0,
+ PV_MAXVAL = 0xffff, ///< to avoid warnings for value out of range
+} idopt_T;
+
+// Options local to a window have a value local to a buffer and global to all
+// buffers. Indicate this by setting "var" to VAR_WIN.
+#define VAR_WIN ((char *)-1)
+
+typedef struct vimoption {
+ char *fullname; ///< full option name
+ char *shortname; ///< permissible abbreviation
+ uint32_t flags; ///< see above
+ void *var; ///< global option: pointer to variable;
+ ///< window-local option: VAR_WIN;
+ ///< buffer-local option: global value
+ idopt_T indir; ///< global option: PV_NONE;
+ ///< local option: indirect option index
+ ///< callback function to invoke after an option is modified to validate and
+ ///< apply the new value.
+ bool immutable; ///< option value cannot be changed from the default value.
+
+ /// callback function to invoke after an option is modified to validate and
+ /// apply the new value.
+ opt_did_set_cb_T opt_did_set_cb;
+
+ /// callback function to invoke when expanding possible values on the
+ /// cmdline. Only useful for string options.
+ opt_expand_cb_T opt_expand_cb;
+
+ // TODO(famiu): Use OptVal for def_val.
+ void *def_val; ///< default values for variable (neovim!!)
+ LastSet last_set; ///< script in which the option was last set
+} vimoption_T;
+
+/// flags for buf_copy_options()
+enum {
+ BCO_ENTER = 1, ///< going to enter the buffer
+ BCO_ALWAYS = 2, ///< always copy the options
+ BCO_NOHELP = 4, ///< don't touch the help related options
+};
+
+/// Flags for option-setting functions
+///
+/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global
+/// values, get local value.
+typedef enum {
+ // TODO(famiu): See if `OPT_FREE` is really necessary and remove it if not.
+ OPT_FREE = 0x01, ///< Free old value if it was allocated.
+ OPT_GLOBAL = 0x02, ///< Use global value.
+ OPT_LOCAL = 0x04, ///< Use local value.
+ OPT_MODELINE = 0x08, ///< Option in modeline.
+ OPT_WINONLY = 0x10, ///< Only set window-local options.
+ OPT_NOWIN = 0x20, ///< Don’t set window-local options.
+ OPT_ONECOLUMN = 0x40, ///< list options one per line
+ OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option
+ OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions'
+} OptionFlags;
+
+/// Return value from get_option_value_strict
+enum {
+ SOPT_BOOL = 0x01, ///< Boolean option
+ SOPT_NUM = 0x02, ///< Number option
+ SOPT_STRING = 0x04, ///< String option
+ SOPT_GLOBAL = 0x08, ///< Option has global value
+ SOPT_WIN = 0x10, ///< Option has window-local value
+ SOPT_BUF = 0x20, ///< Option has buffer-local value
+};
+
+// OptVal helper macros.
+#define NIL_OPTVAL ((OptVal) { .type = kOptValTypeNil })
+#define BOOLEAN_OPTVAL(b) ((OptVal) { .type = kOptValTypeBoolean, .data.boolean = b })
+#define NUMBER_OPTVAL(n) ((OptVal) { .type = kOptValTypeNumber, .data.number = n })
+#define STRING_OPTVAL(s) ((OptVal) { .type = kOptValTypeString, .data.string = s })
+
+#define CSTR_AS_OPTVAL(s) STRING_OPTVAL(cstr_as_string(s))
+#define CSTR_TO_OPTVAL(s) STRING_OPTVAL(cstr_to_string(s))
+#define STATIC_CSTR_AS_OPTVAL(s) STRING_OPTVAL(STATIC_CSTR_AS_STRING(s))
+#define STATIC_CSTR_TO_OPTVAL(s) STRING_OPTVAL(STATIC_CSTR_TO_STRING(s))
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "option.h.generated.h"
#endif
-#endif // NVIM_OPTION_H
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index d190fc5999..b2e8081a08 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -1,1024 +1,123 @@
-#ifndef NVIM_OPTION_DEFS_H
-#define NVIM_OPTION_DEFS_H
+#pragma once
-#include "nvim/eval/typval.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include <stdbool.h>
+#include <stddef.h>
-// option_defs.h: definition of global variables for settable options
+#include "nvim/api/private/defs.h"
+#include "nvim/cmdexpand_defs.h"
+#include "nvim/regexp_defs.h"
+#include "nvim/types_defs.h"
-// Flags
-#define P_BOOL 0x01U ///< the option is boolean
-#define P_NUM 0x02U ///< the option is numeric
-#define P_STRING 0x04U ///< the option is a string
-#define P_ALLOCED 0x08U ///< the string option is in allocated memory,
- ///< must use free_string_option() when
- ///< assigning new value. Not set if default is
- ///< the same.
-#define P_EXPAND 0x10U ///< environment expansion. NOTE: P_EXPAND can
- ///< never be used for local or hidden options
-#define P_NODEFAULT 0x40U ///< don't set to default value
-#define P_DEF_ALLOCED 0x80U ///< default value is in allocated memory, must
- ///< use free() when assigning new value
-#define P_WAS_SET 0x100U ///< option has been set/reset
-#define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output
-
-// when option changed, what to display:
-#define P_UI_OPTION 0x400U ///< send option to remote UI
-#define P_RTABL 0x800U ///< redraw tabline
-#define P_RSTAT 0x1000U ///< redraw status lines
-#define P_RWIN 0x2000U ///< redraw current window and recompute text
-#define P_RBUF 0x4000U ///< redraw current buffer and recompute text
-#define P_RALL 0x6000U ///< redraw all windows
-#define P_RCLR 0x7000U ///< clear and redraw all
-
-#define P_COMMA 0x8000U ///< comma separated list
-#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive
- ///< commas
-#define P_NODUP 0x20000U ///< don't allow duplicate strings
-#define P_FLAGLIST 0x40000U ///< list of single-char flags
-
-#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode
-#define P_GETTEXT 0x100000U ///< expand default value with _()
-#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc
-#define P_NFNAME 0x400000U ///< only normal file name chars allowed
-#define P_INSECURE 0x800000U ///< option was set from a modeline
-#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option
- ///< has side effects)
-#define P_NO_ML 0x2000000U ///< not allowed in modeline
-#define P_CURSWANT 0x4000000U ///< update curswant required; not needed
- ///< when there is a redraw flag
-#define P_NDNAME 0x8000000U ///< only normal dir name chars allowed
-#define P_RWINONLY 0x10000000U ///< only redraw current window
-#define P_MLE 0x20000000U ///< under control of 'modelineexpr'
-#define P_FUNC 0x40000000U ///< accept a function reference or a lambda
-
-#define P_NO_DEF_EXP 0x80000000U ///< Do not expand default value.
-
-/// Flags for option-setting functions
-///
-/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global
-/// values, get local value.
+/// Option value type
typedef enum {
- OPT_FREE = 0x01, ///< Free old value if it was allocated.
- OPT_GLOBAL = 0x02, ///< Use global value.
- OPT_LOCAL = 0x04, ///< Use local value.
- OPT_MODELINE = 0x08, ///< Option in modeline.
- OPT_WINONLY = 0x10, ///< Only set window-local options.
- OPT_NOWIN = 0x20, ///< Don’t set window-local options.
- OPT_ONECOLUMN = 0x40, ///< list options one per line
- OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option
- OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions'
- OPT_CLEAR = 0x200, ///< Clear local value of an option.
-} OptionFlags;
-
-// Return value from get_option_value_strict
-#define SOPT_BOOL 0x01 // Boolean option
-#define SOPT_NUM 0x02 // Number option
-#define SOPT_STRING 0x04 // String option
-#define SOPT_GLOBAL 0x08 // Option has global value
-#define SOPT_WIN 0x10 // Option has window-local value
-#define SOPT_BUF 0x20 // Option has buffer-local value
-#define SOPT_UNSET 0x40 // Option does not have local value set
-
-// Option types for various functions in option.c
-#define SREQ_GLOBAL 0 // Request global option value
-#define SREQ_WIN 1 // Request window-local option value
-#define SREQ_BUF 2 // Request buffer-local option value
-
-#define HIGHLIGHT_INIT \
- "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \
- "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \
- "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \
- "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \
- "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \
- "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \
- "X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \
- "q:QuickFixLine,0:Whitespace,I:NormalNC"
-
-// Default values for 'errorformat'.
-// The "%f|%l| %m" one is used for when the contents of the quickfix window is
-// written to a file.
-#ifdef MSWIN
-# define DFLT_EFM \
- "%f(%l) \\=: %t%*\\D%n: %m,%*[^\"]\"%f\"%*\\D%l: %m,%f(%l) \\=: %m,%*[^ ] %f %l: %m,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,%f|%l| %m"
-#else
-# define DFLT_EFM \
- "%*[^\"]\"%f\"%*\\D%l: %m,\"%f\"%*\\D%l: %m,%-G%f:%l: (Each undeclared identifier is reported only once,%-G%f:%l: for each function it appears in.),%-GIn file included from %f:%l:%c:,%-GIn file included from %f:%l:%c\\,,%-GIn file included from %f:%l:%c,%-GIn file included from %f:%l,%-G%*[ ]from %f:%l:%c,%-G%*[ ]from %f:%l:,%-G%*[ ]from %f:%l\\,,%-G%*[ ]from %f:%l,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,\"%f\"\\, line %l%*\\D%c%*[^ ] %m,%D%*\\a[%*\\d]: Entering directory %*[`']%f',%X%*\\a[%*\\d]: Leaving directory %*[`']%f',%D%*\\a: Entering directory %*[`']%f',%X%*\\a: Leaving directory %*[`']%f',%DMaking %*\\a in %f,%f|%l| %m"
-#endif
-
-#define DFLT_GREPFORMAT "%f:%l:%m,%f:%l%m,%f %l%m"
-
-// default values for b_p_ff 'fileformat' and p_ffs 'fileformats'
-#define FF_DOS "dos"
-#define FF_MAC "mac"
-#define FF_UNIX "unix"
-
-#ifdef USE_CRNL
-# define DFLT_FF "dos"
-# define DFLT_FFS_VIM "dos,unix"
-# define DFLT_FFS_VI "dos,unix" // also autodetect in compatible mode
-#else
-# define DFLT_FF "unix"
-# define DFLT_FFS_VIM "unix,dos"
-# define DFLT_FFS_VI ""
-#endif
-
-// Possible values for 'encoding'
-#define ENC_UCSBOM "ucs-bom" // check for BOM at start of file
-
-// default value for 'encoding'
-#define ENC_DFLT "utf-8"
-
-// end-of-line style
-#define EOL_UNKNOWN (-1) // not defined yet
-#define EOL_UNIX 0 // NL
-#define EOL_DOS 1 // CR NL
-#define EOL_MAC 2 // CR
-
-// Formatting options for p_fo 'formatoptions'
-#define FO_WRAP 't'
-#define FO_WRAP_COMS 'c'
-#define FO_RET_COMS 'r'
-#define FO_OPEN_COMS 'o'
-#define FO_NO_OPEN_COMS '/'
-#define FO_Q_COMS 'q'
-#define FO_Q_NUMBER 'n'
-#define FO_Q_SECOND '2'
-#define FO_INS_VI 'v'
-#define FO_INS_LONG 'l'
-#define FO_INS_BLANK 'b'
-#define FO_MBYTE_BREAK 'm' // break before/after multi-byte char
-#define FO_MBYTE_JOIN 'M' // no space before/after multi-byte char
-#define FO_MBYTE_JOIN2 'B' // no space between multi-byte chars
-#define FO_ONE_LETTER '1'
-#define FO_WHITE_PAR 'w' // trailing white space continues paragr.
-#define FO_AUTO 'a' // automatic formatting
-#define FO_RIGOROUS_TW ']' // respect textwidth rigorously
-#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines
-#define FO_PERIOD_ABBR 'p' // don't break a single space after a period
-
-#define DFLT_FO_VI "vt"
-#define DFLT_FO_VIM "tcqj"
-#define FO_ALL "tcro/q2vlb1mMBn,aw]jp" // for do_set()
-
-// characters for the p_cpo option:
-#define CPO_ALTREAD 'a' // ":read" sets alternate file name
-#define CPO_ALTWRITE 'A' // ":write" sets alternate file name
-#define CPO_BAR 'b' // "\|" ends a mapping
-#define CPO_BSLASH 'B' // backslash in mapping is not special
-#define CPO_SEARCH 'c'
-#define CPO_CONCAT 'C' // Don't concatenate sourced lines
-#define CPO_DOTTAG 'd' // "./tags" in 'tags' is in current dir
-#define CPO_DIGRAPH 'D' // No digraph after "r", "f", etc.
-#define CPO_EXECBUF 'e'
-#define CPO_EMPTYREGION 'E' // operating on empty region is an error
-#define CPO_FNAMER 'f' // set file name for ":r file"
-#define CPO_FNAMEW 'F' // set file name for ":w file"
-#define CPO_INTMOD 'i' // interrupt a read makes buffer modified
-#define CPO_INDENT 'I' // remove auto-indent more often
-#define CPO_ENDOFSENT 'J' // need two spaces to detect end of sentence
-#define CPO_KOFFSET 'K' // don't wait for key code in mappings
-#define CPO_LITERAL 'l' // take char after backslash in [] literal
-#define CPO_LISTWM 'L' // 'list' changes wrapmargin
-#define CPO_SHOWMATCH 'm'
-#define CPO_MATCHBSL 'M' // "%" ignores use of backslashes
-#define CPO_NUMCOL 'n' // 'number' column also used for text
-#define CPO_LINEOFF 'o'
-#define CPO_OVERNEW 'O' // silently overwrite new file
-#define CPO_LISP 'p' // 'lisp' indenting
-#define CPO_FNAMEAPP 'P' // set file name for ":w >>file"
-#define CPO_JOINCOL 'q' // with "3J" use column after first join
-#define CPO_REDO 'r'
-#define CPO_REMMARK 'R' // remove marks when filtering
-#define CPO_BUFOPT 's'
-#define CPO_BUFOPTGLOB 'S'
-#define CPO_TAGPAT 't' // tag pattern is used for "n"
-#define CPO_UNDO 'u' // "u" undoes itself
-#define CPO_BACKSPACE 'v' // "v" keep deleted text
-#define CPO_FWRITE 'W' // "w!" doesn't overwrite readonly files
-#define CPO_ESC 'x'
-#define CPO_REPLCNT 'X' // "R" with a count only deletes chars once
-#define CPO_YANK 'y'
-#define CPO_KEEPRO 'Z' // don't reset 'readonly' on ":w!"
-#define CPO_DOLLAR '$'
-#define CPO_FILTER '!'
-#define CPO_MATCH '%'
-#define CPO_PLUS '+' // ":write file" resets 'modified'
-#define CPO_REGAPPEND '>' // insert NL when appending to a register
-#define CPO_SCOLON ';' // using "," and ";" will skip over char if
- // cursor would not move
-#define CPO_CHANGEW '_' // "cw" special-case
-// default values for Vim and Vi
-#define CPO_VIM "aABceFs_"
-#define CPO_VI "aAbBcCdDeEfFiIJKlLmMnoOpPqrRsStuvWxXyZ$!%+>;_"
-
-// characters for p_ww option:
-#define WW_ALL "bshl<>[],~"
-
-// characters for p_mouse option:
-#define MOUSE_NORMAL 'n' // use mouse in Normal mode
-#define MOUSE_VISUAL 'v' // use mouse in Visual/Select mode
-#define MOUSE_INSERT 'i' // use mouse in Insert mode
-#define MOUSE_COMMAND 'c' // use mouse in Command-line mode
-#define MOUSE_HELP 'h' // use mouse in help buffers
-#define MOUSE_RETURN 'r' // use mouse for hit-return message
-#define MOUSE_A "nvich" // used for 'a' flag
-#define MOUSE_ALL "anvichr" // all possible characters
-#define MOUSE_NONE ' ' // don't use Visual selection
-#define MOUSE_NONEF 'x' // forced modeless selection
-
-// default vertical and horizontal mouse scroll values.
-// Note: This should be in sync with the default mousescroll option.
-#define MOUSESCROLL_VERT_DFLT 3
-#define MOUSESCROLL_HOR_DFLT 6
-
-#define COCU_ALL "nvic" // flags for 'concealcursor'
-
-/// characters for p_shm option:
-enum {
- SHM_RO = 'r', ///< Readonly.
- SHM_MOD = 'm', ///< Modified.
- SHM_FILE = 'f', ///< (file 1 of 2)
- SHM_LAST = 'i', ///< Last line incomplete.
- SHM_TEXT = 'x', ///< tx instead of textmode.
- SHM_LINES = 'l', ///< "L" instead of "lines".
- SHM_NEW = 'n', ///< "[New]" instead of "[New file]".
- SHM_WRI = 'w', ///< "[w]" instead of "written".
- SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS.
- SHM_WRITE = 'W', ///< Don't use "written" at all.
- SHM_TRUNC = 't', ///< Truncate file messages.
- SHM_TRUNCALL = 'T', ///< Truncate all messages.
- SHM_OVER = 'o', ///< Overwrite file messages.
- SHM_OVERALL = 'O', ///< Overwrite more messages.
- SHM_SEARCH = 's', ///< No search hit bottom messages.
- SHM_ATTENTION = 'A', ///< No ATTENTION messages.
- SHM_INTRO = 'I', ///< Intro messages.
- SHM_COMPLETIONMENU = 'c', ///< Completion menu messages.
- SHM_COMPLETIONSCAN = 'C', ///< Completion scanning messages.
- SHM_RECORDING = 'q', ///< Short recording message.
- SHM_FILEINFO = 'F', ///< No file info messages.
- SHM_SEARCHCOUNT = 'S', ///< Search stats: '[1/10]'
-};
-/// Represented by 'a' flag.
-#define SHM_ALL_ABBREVIATIONS ((char[]) { \
- SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \
- 0 })
-
-// characters for p_go:
-#define GO_ASEL 'a' // autoselect
-#define GO_ASELML 'A' // autoselect modeless selection
-#define GO_BOT 'b' // use bottom scrollbar
-#define GO_CONDIALOG 'c' // use console dialog
-#define GO_DARKTHEME 'd' // use dark theme variant
-#define GO_TABLINE 'e' // may show tabline
-#define GO_FORG 'f' // start GUI in foreground
-#define GO_GREY 'g' // use grey menu items
-#define GO_HORSCROLL 'h' // flexible horizontal scrolling
-#define GO_ICON 'i' // use Vim icon
-#define GO_LEFT 'l' // use left scrollbar
-#define GO_VLEFT 'L' // left scrollbar with vert split
-#define GO_MENUS 'm' // use menu bar
-#define GO_NOSYSMENU 'M' // don't source system menu
-#define GO_POINTER 'p' // pointer enter/leave callbacks
-#define GO_ASELPLUS 'P' // autoselectPlus
-#define GO_RIGHT 'r' // use right scrollbar
-#define GO_VRIGHT 'R' // right scrollbar with vert split
-#define GO_TOOLBAR 'T' // add toolbar
-#define GO_FOOTER 'F' // add footer
-#define GO_VERTICAL 'v' // arrange dialog buttons vertically
-#define GO_KEEPWINSIZE 'k' // keep GUI window size
-#define GO_ALL "aAbcdefFghilmMprTvk" // all possible flags for 'go'
-
-// flags for 'comments' option
-#define COM_NEST 'n' // comments strings nest
-#define COM_BLANK 'b' // needs blank after string
-#define COM_START 's' // start of comment
-#define COM_MIDDLE 'm' // middle of comment
-#define COM_END 'e' // end of comment
-#define COM_AUTO_END 'x' // last char of end closes comment
-#define COM_FIRST 'f' // first line comment only
-#define COM_LEFT 'l' // left adjusted
-#define COM_RIGHT 'r' // right adjusted
-#define COM_NOBACK 'O' // don't use for "O" command
-#define COM_ALL "nbsmexflrO" // all flags for 'comments' option
-#define COM_MAX_LEN 50 // maximum length of a part
-
-/// 'statusline' option flags
-enum {
- STL_FILEPATH = 'f', ///< Path of file in buffer.
- STL_FULLPATH = 'F', ///< Full path of file in buffer.
- STL_FILENAME = 't', ///< Last part (tail) of file path.
- STL_COLUMN = 'c', ///< Column og cursor.
- STL_VIRTCOL = 'v', ///< Virtual column.
- STL_VIRTCOL_ALT = 'V', ///< - with 'if different' display.
- STL_LINE = 'l', ///< Line number of cursor.
- STL_NUMLINES = 'L', ///< Number of lines in buffer.
- STL_BUFNO = 'n', ///< Current buffer number.
- STL_KEYMAP = 'k', ///< 'keymap' when active.
- STL_OFFSET = 'o', ///< Offset of character under cursor.
- STL_OFFSET_X = 'O', ///< - in hexadecimal.
- STL_BYTEVAL = 'b', ///< Byte value of character.
- STL_BYTEVAL_X = 'B', ///< - in hexadecimal.
- STL_ROFLAG = 'r', ///< Readonly flag.
- STL_ROFLAG_ALT = 'R', ///< - other display.
- STL_HELPFLAG = 'h', ///< Window is showing a help file.
- STL_HELPFLAG_ALT = 'H', ///< - other display.
- STL_FILETYPE = 'y', ///< 'filetype'.
- STL_FILETYPE_ALT = 'Y', ///< - other display.
- STL_PREVIEWFLAG = 'w', ///< Window is showing the preview buf.
- STL_PREVIEWFLAG_ALT = 'W', ///< - other display.
- STL_MODIFIED = 'm', ///< Modified flag.
- STL_MODIFIED_ALT = 'M', ///< - other display.
- STL_QUICKFIX = 'q', ///< Quickfix window description.
- STL_PERCENTAGE = 'p', ///< Percentage through file.
- STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%.
- STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y).
- STL_PAGENUM = 'N', ///< Page number (when printing).
- STL_SHOWCMD = 'S', ///< 'showcmd' buffer
- STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn'
- STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn'
- STL_VIM_EXPR = '{', ///< Start of expression to substitute.
- STL_SEPARATE = '=', ///< Separation between alignment sections.
- STL_TRUNCMARK = '<', ///< Truncation mark if line is too long.
- STL_USER_HL = '*', ///< Highlight from (User)1..9 or 0.
- STL_HIGHLIGHT = '#', ///< Highlight name.
- STL_TABPAGENR = 'T', ///< Tab page label nr.
- STL_TABCLOSENR = 'X', ///< Tab page close nr.
- STL_CLICK_FUNC = '@', ///< Click region start.
-};
-/// C string containing all 'statusline' option flags
-#define STL_ALL ((char[]) { \
- STL_FILEPATH, STL_FULLPATH, STL_FILENAME, STL_COLUMN, STL_VIRTCOL, \
- STL_VIRTCOL_ALT, STL_LINE, STL_NUMLINES, STL_BUFNO, STL_KEYMAP, STL_OFFSET, \
- STL_OFFSET_X, STL_BYTEVAL, STL_BYTEVAL_X, STL_ROFLAG, STL_ROFLAG_ALT, \
- STL_HELPFLAG, STL_HELPFLAG_ALT, STL_FILETYPE, STL_FILETYPE_ALT, \
- STL_PREVIEWFLAG, STL_PREVIEWFLAG_ALT, STL_MODIFIED, STL_MODIFIED_ALT, \
- STL_QUICKFIX, STL_PERCENTAGE, STL_ALTPERCENT, STL_ARGLISTSTAT, STL_PAGENUM, \
- STL_SHOWCMD, STL_FOLDCOL, STL_SIGNCOL, STL_VIM_EXPR, STL_SEPARATE, \
- STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, \
- STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \
- 0, })
-
-// flags used for parsed 'wildmode'
-#define WIM_FULL 0x01
-#define WIM_LONGEST 0x02
-#define WIM_LIST 0x04
-#define WIM_BUFLASTUSED 0x08
-
-// arguments for can_bs()
-// each defined char should be unique over all values
-// except for BS_START, that intentionally also matches BS_NOSTOP
-// because BS_NOSTOP behaves exactly the same except it
-// does not stop at the start of the insert point
-#define BS_INDENT 'i' // "Indent"
-#define BS_EOL 'l' // "eoL"
-#define BS_START 's' // "Start"
-#define BS_NOSTOP 'p' // "nostoP
-
-// flags for the 'culopt' option
-#define CULOPT_LINE 0x01 // Highlight complete line
-#define CULOPT_SCRLINE 0x02 // Highlight screen line
-#define CULOPT_NBR 0x04 // Highlight Number column
-
-#define LISPWORD_VALUE \
- "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object"
-
-// The following are actual variables for the options
-
-EXTERN long p_aleph; // 'aleph'
-EXTERN char *p_ambw; ///< 'ambiwidth'
-EXTERN int p_acd; ///< 'autochdir'
-EXTERN int p_ai; ///< 'autoindent'
-EXTERN int p_bin; ///< 'binary'
-EXTERN int p_bomb; ///< 'bomb'
-EXTERN int p_bl; ///< 'buflisted'
-EXTERN int p_cin; ///< 'cindent'
-EXTERN long p_channel; ///< 'channel'
-EXTERN char *p_cink; ///< 'cinkeys'
-EXTERN char *p_cinsd; ///< 'cinscopedecls'
-EXTERN char *p_cinw; ///< 'cinwords'
-EXTERN char *p_cfu; ///< 'completefunc'
-EXTERN char *p_ofu; ///< 'omnifunc'
-EXTERN char *p_tsrfu; ///< 'thesaurusfunc'
-EXTERN int p_ci; ///< 'copyindent'
-EXTERN int p_ar; // 'autoread'
-EXTERN int p_aw; // 'autowrite'
-EXTERN int p_awa; // 'autowriteall'
-EXTERN char *p_bs; // 'backspace'
-EXTERN char *p_bg; // 'background'
-EXTERN int p_bk; // 'backup'
-EXTERN char *p_bkc; // 'backupcopy'
-EXTERN unsigned int bkc_flags; ///< flags from 'backupcopy'
-#define BKC_YES 0x001
-#define BKC_AUTO 0x002
-#define BKC_NO 0x004
-#define BKC_BREAKSYMLINK 0x008
-#define BKC_BREAKHARDLINK 0x010
-EXTERN char *p_bdir; // 'backupdir'
-EXTERN char *p_bex; // 'backupext'
-EXTERN char *p_bo; // 'belloff'
-EXTERN char breakat_flags[256]; // which characters are in 'breakat'
-EXTERN unsigned bo_flags;
-
-// values for the 'belloff' option
-#define BO_ALL 0x0001
-#define BO_BS 0x0002
-#define BO_CRSR 0x0004
-#define BO_COMPL 0x0008
-#define BO_COPY 0x0010
-#define BO_CTRLG 0x0020
-#define BO_ERROR 0x0040
-#define BO_ESC 0x0080
-#define BO_EX 0x0100
-#define BO_HANGUL 0x0200
-#define BO_IM 0x0400
-#define BO_LANG 0x0800
-#define BO_MESS 0x1000
-#define BO_MATCH 0x2000
-#define BO_OPER 0x4000
-#define BO_REG 0x8000
-#define BO_SH 0x10000
-#define BO_SPELL 0x20000
-#define BO_WILD 0x40000
-
-EXTERN char *p_bsk; // 'backupskip'
-EXTERN char *p_breakat; // 'breakat'
-EXTERN char *p_bh; ///< 'bufhidden'
-EXTERN char *p_bt; ///< 'buftype'
-EXTERN char *p_cmp; // 'casemap'
-EXTERN unsigned cmp_flags;
-#define CMP_INTERNAL 0x001
-#define CMP_KEEPASCII 0x002
-EXTERN char *p_enc; // 'encoding'
-EXTERN int p_deco; // 'delcombine'
-EXTERN char *p_ccv; // 'charconvert'
-EXTERN char *p_cino; ///< 'cinoptions'
-EXTERN char *p_cedit; // 'cedit'
-EXTERN char *p_cb; // 'clipboard'
-EXTERN unsigned cb_flags;
-#define CB_UNNAMED 0x001
-#define CB_UNNAMEDPLUS 0x002
-#define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
-EXTERN long p_cwh; // 'cmdwinheight'
-EXTERN long p_ch; // 'cmdheight'
-EXTERN char *p_cms; ///< 'commentstring'
-EXTERN char *p_cpt; ///< 'complete'
-EXTERN long p_columns; // 'columns'
-EXTERN int p_confirm; // 'confirm'
-EXTERN char *p_cot; // 'completeopt'
-#ifdef BACKSLASH_IN_FILENAME
-EXTERN char *p_csl; // 'completeslash'
-#endif
-EXTERN long p_pb; // 'pumblend'
-EXTERN long p_ph; // 'pumheight'
-EXTERN long p_pw; // 'pumwidth'
-EXTERN char *p_com; ///< 'comments'
-EXTERN char *p_cpo; // 'cpoptions'
-EXTERN char *p_debug; // 'debug'
-EXTERN char *p_def; // 'define'
-EXTERN char *p_inc;
-EXTERN char *p_dip; // 'diffopt'
-EXTERN char *p_dex; // 'diffexpr'
-EXTERN char *p_dict; // 'dictionary'
-EXTERN int p_dg; // 'digraph'
-EXTERN char *p_dir; // 'directory'
-EXTERN char *p_dy; // 'display'
-EXTERN unsigned dy_flags;
-#define DY_LASTLINE 0x001
-#define DY_TRUNCATE 0x002
-#define DY_UHEX 0x004
-// legacy flag, not used
-#define DY_MSGSEP 0x008
-EXTERN int p_ed; // 'edcompatible'
-EXTERN char *p_ead; // 'eadirection'
-EXTERN int p_emoji; // 'emoji'
-EXTERN int p_ea; // 'equalalways'
-EXTERN char *p_ep; // 'equalprg'
-EXTERN int p_eb; // 'errorbells'
-EXTERN char *p_ef; // 'errorfile'
-EXTERN char *p_efm; // 'errorformat'
-EXTERN char *p_gefm; // 'grepformat'
-EXTERN char *p_gp; // 'grepprg'
-EXTERN int p_eof; ///< 'endoffile'
-EXTERN int p_eol; ///< 'endofline'
-EXTERN char *p_ei; // 'eventignore'
-EXTERN int p_et; ///< 'expandtab'
-EXTERN int p_exrc; // 'exrc'
-EXTERN char *p_fenc; ///< 'fileencoding'
-EXTERN char *p_fencs; // 'fileencodings'
-EXTERN char *p_ff; ///< 'fileformat'
-EXTERN char *p_ffs; // 'fileformats'
-EXTERN int p_fic; // 'fileignorecase'
-EXTERN char *p_ft; ///< 'filetype'
-EXTERN char *p_fcs; ///< 'fillchar'
-EXTERN int p_fixeol; ///< 'fixendofline'
-EXTERN char *p_fcl; // 'foldclose'
-EXTERN long p_fdls; // 'foldlevelstart'
-EXTERN char *p_fdo; // 'foldopen'
-EXTERN unsigned fdo_flags;
-#define FDO_ALL 0x001
-#define FDO_BLOCK 0x002
-#define FDO_HOR 0x004
-#define FDO_MARK 0x008
-#define FDO_PERCENT 0x010
-#define FDO_QUICKFIX 0x020
-#define FDO_SEARCH 0x040
-#define FDO_TAG 0x080
-#define FDO_INSERT 0x100
-#define FDO_UNDO 0x200
-#define FDO_JUMP 0x400
-EXTERN char *p_fex; ///< 'formatexpr'
-EXTERN char *p_flp; ///< 'formatlistpat'
-EXTERN char *p_fo; ///< 'formatoptions'
-EXTERN char *p_fp; // 'formatprg'
-EXTERN int p_fs; // 'fsync'
-EXTERN int p_gd; // 'gdefault'
-EXTERN char *p_guicursor; // 'guicursor'
-EXTERN char *p_guifont; // 'guifont'
-EXTERN char *p_guifontwide; // 'guifontwide'
-EXTERN char *p_hf; // 'helpfile'
-EXTERN long p_hh; // 'helpheight'
-EXTERN char *p_hlg; // 'helplang'
-EXTERN int p_hid; // 'hidden'
-EXTERN char *p_hl; // 'highlight'
-EXTERN int p_hls; // 'hlsearch'
-EXTERN long p_hi; // 'history'
-EXTERN int p_hkmap; // 'hkmap'
-EXTERN int p_hkmapp; // 'hkmapp'
-EXTERN int p_arshape; // 'arabicshape'
-EXTERN int p_icon; // 'icon'
-EXTERN char *p_iconstring; // 'iconstring'
-EXTERN int p_ic; // 'ignorecase'
-EXTERN long p_iminsert; ///< 'iminsert'
-EXTERN long p_imsearch; ///< 'imsearch'
-EXTERN int p_inf; ///< 'infercase'
-EXTERN char *p_inex; ///< 'includeexpr'
-EXTERN int p_is; // 'incsearch'
-EXTERN char *p_inde; ///< 'indentexpr'
-EXTERN char *p_indk; ///< 'indentkeys'
-EXTERN char *p_icm; // 'inccommand'
-EXTERN char *p_isf; // 'isfname'
-EXTERN char *p_isi; // 'isident'
-EXTERN char *p_isk; ///< 'iskeyword'
-EXTERN char *p_isp; // 'isprint'
-EXTERN int p_js; // 'joinspaces'
-EXTERN char *p_jop; // 'jumpooptions'
-EXTERN unsigned jop_flags;
-#define JOP_STACK 0x01
-#define JOP_VIEW 0x02
-EXTERN char *p_keymap; ///< 'keymap'
-EXTERN char *p_kp; // 'keywordprg'
-EXTERN char *p_km; // 'keymodel'
-EXTERN char *p_langmap; // 'langmap'
-EXTERN int p_lnr; // 'langnoremap'
-EXTERN int p_lrm; // 'langremap'
-EXTERN char *p_lm; // 'langmenu'
-EXTERN long p_lines; // 'lines'
-EXTERN long p_linespace; // 'linespace'
-EXTERN int p_lisp; ///< 'lisp'
-EXTERN char *p_lop; ///< 'lispoptions'
-EXTERN char *p_lispwords; // 'lispwords'
-EXTERN long p_ls; // 'laststatus'
-EXTERN long p_stal; // 'showtabline'
-EXTERN char *p_lcs; // 'listchars'
-
-EXTERN int p_lz; // 'lazyredraw'
-EXTERN int p_lpl; // 'loadplugins'
-EXTERN int p_magic; // 'magic'
-EXTERN char *p_menc; // 'makeencoding'
-EXTERN char *p_mef; // 'makeef'
-EXTERN char *p_mp; // 'makeprg'
-EXTERN char *p_mps; ///< 'matchpairs'
-EXTERN long p_mat; // 'matchtime'
-EXTERN long p_mco; // 'maxcombine'
-EXTERN long p_mfd; // 'maxfuncdepth'
-EXTERN long p_mmd; // 'maxmapdepth'
-EXTERN long p_mmp; // 'maxmempattern'
-EXTERN long p_mis; // 'menuitems'
-EXTERN char *p_msm; // 'mkspellmem'
-EXTERN int p_ml; ///< 'modeline'
-EXTERN int p_mle; // 'modelineexpr'
-EXTERN long p_mls; // 'modelines'
-EXTERN int p_ma; ///< 'modifiable'
-EXTERN int p_mod; ///< 'modified'
-EXTERN char *p_mouse; // 'mouse'
-EXTERN char *p_mousem; // 'mousemodel'
-EXTERN int p_mousemev; ///< 'mousemoveevent'
-EXTERN int p_mousef; // 'mousefocus'
-EXTERN char *p_mousescroll; // 'mousescroll'
-EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT);
-EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT);
-EXTERN long p_mouset; // 'mousetime'
-EXTERN int p_more; // 'more'
-EXTERN char *p_nf; ///< 'nrformats'
-EXTERN char *p_opfunc; // 'operatorfunc'
-EXTERN char *p_para; // 'paragraphs'
-EXTERN int p_paste; // 'paste'
-EXTERN char *p_pt; // 'pastetoggle'
-EXTERN char *p_pex; // 'patchexpr'
-EXTERN char *p_pm; // 'patchmode'
-EXTERN char *p_path; // 'path'
-EXTERN char *p_cdpath; // 'cdpath'
-EXTERN int p_pi; ///< 'preserveindent'
-EXTERN long p_pyx; // 'pyxversion'
-EXTERN char *p_qe; ///< 'quoteescape'
-EXTERN int p_ro; ///< 'readonly'
-EXTERN char *p_rdb; // 'redrawdebug'
-EXTERN unsigned rdb_flags;
-#define RDB_COMPOSITOR 0x001
-#define RDB_NOTHROTTLE 0x002
-#define RDB_INVALID 0x004
-#define RDB_NODELTA 0x008
-
-EXTERN long p_rdt; // 'redrawtime'
-EXTERN long p_re; // 'regexpengine'
-EXTERN long p_report; // 'report'
-EXTERN long p_pvh; // 'previewheight'
-EXTERN int p_ari; // 'allowrevins'
-EXTERN int p_ri; // 'revins'
-EXTERN int p_ru; // 'ruler'
-EXTERN char *p_ruf; // 'rulerformat'
-EXTERN char *p_pp; // 'packpath'
-EXTERN char *p_qftf; // 'quickfixtextfunc'
-EXTERN char *p_rtp; // 'runtimepath'
-EXTERN long p_scbk; // 'scrollback'
-EXTERN long p_sj; // 'scrolljump'
-EXTERN long p_so; // 'scrolloff'
-EXTERN char *p_sbo; // 'scrollopt'
-EXTERN char *p_sections; // 'sections'
-EXTERN int p_secure; // 'secure'
-EXTERN char *p_sel; // 'selection'
-EXTERN char *p_slm; // 'selectmode'
-EXTERN char *p_ssop; // 'sessionoptions'
-EXTERN unsigned ssop_flags;
-
-#define SSOP_BUFFERS 0x001
-#define SSOP_WINPOS 0x002
-#define SSOP_RESIZE 0x004
-#define SSOP_WINSIZE 0x008
-#define SSOP_LOCALOPTIONS 0x010
-#define SSOP_OPTIONS 0x020
-#define SSOP_HELP 0x040
-#define SSOP_BLANK 0x080
-#define SSOP_GLOBALS 0x100
-#define SSOP_SLASH 0x200 // Deprecated, always set.
-#define SSOP_UNIX 0x400 // Deprecated, always set.
-#define SSOP_SESDIR 0x800
-#define SSOP_CURDIR 0x1000
-#define SSOP_FOLDS 0x2000
-#define SSOP_CURSOR 0x4000
-#define SSOP_TABPAGES 0x8000
-#define SSOP_TERMINAL 0x10000
-#define SSOP_SKIP_RTP 0x20000
-
-EXTERN char *p_sh; // 'shell'
-EXTERN char *p_shcf; // 'shellcmdflag'
-EXTERN char *p_sp; // 'shellpipe'
-EXTERN char *p_shq; // 'shellquote'
-EXTERN char *p_sxq; // 'shellxquote'
-EXTERN char *p_sxe; // 'shellxescape'
-EXTERN char *p_srr; // 'shellredir'
-EXTERN int p_stmp; // 'shelltemp'
-#ifdef BACKSLASH_IN_FILENAME
-EXTERN int p_ssl; // 'shellslash'
-#endif
-EXTERN char *p_stl; // 'statusline'
-EXTERN char *p_wbr; // 'winbar'
-EXTERN int p_sr; // 'shiftround'
-EXTERN long p_sw; ///< 'shiftwidth'
-EXTERN char *p_shm; // 'shortmess'
-EXTERN char *p_sbr; // 'showbreak'
-EXTERN int p_sc; // 'showcmd'
-EXTERN char *p_sloc; // 'showcmdloc'
-EXTERN int p_sft; // 'showfulltag'
-EXTERN int p_sm; // 'showmatch'
-EXTERN int p_smd; // 'showmode'
-EXTERN long p_ss; // 'sidescroll'
-EXTERN long p_siso; // 'sidescrolloff'
-EXTERN int p_scs; // 'smartcase'
-EXTERN int p_si; ///< 'smartindent'
-EXTERN int p_sta; // 'smarttab'
-EXTERN long p_sts; ///< 'softtabstop'
-EXTERN int p_sb; // 'splitbelow'
-EXTERN char *p_sua; ///< 'suffixesadd'
-EXTERN int p_swf; ///< 'swapfile'
-EXTERN long p_smc; ///< 'synmaxcol'
-EXTERN long p_tpm; // 'tabpagemax'
-EXTERN char *p_tal; // 'tabline'
-EXTERN char *p_tpf; // 'termpastefilter'
-EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter'
-#define TPF_BS 0x001
-#define TPF_HT 0x002
-#define TPF_FF 0x004
-#define TPF_ESC 0x008
-#define TPF_DEL 0x010
-#define TPF_C0 0x020
-#define TPF_C1 0x040
-EXTERN char *p_tfu; ///< 'tagfunc'
-EXTERN char *p_spc; ///< 'spellcapcheck'
-EXTERN char *p_spf; ///< 'spellfile'
-EXTERN char *p_spk; ///< 'splitkeep'
-EXTERN char *p_spl; ///< 'spelllang'
-EXTERN char *p_spo; // 'spelloptions'
-EXTERN unsigned int spo_flags;
-EXTERN char *p_sps; // 'spellsuggest'
-EXTERN int p_spr; // 'splitright'
-EXTERN int p_sol; // 'startofline'
-EXTERN char *p_su; // 'suffixes'
-EXTERN char *p_swb; // 'switchbuf'
-EXTERN unsigned swb_flags;
-// Keep in sync with p_swb_values in optionstr.c
-#define SWB_USEOPEN 0x001
-#define SWB_USETAB 0x002
-#define SWB_SPLIT 0x004
-#define SWB_NEWTAB 0x008
-#define SWB_VSPLIT 0x010
-#define SWB_USELAST 0x020
-EXTERN char *p_syn; ///< 'syntax'
-EXTERN long p_ts; ///< 'tabstop'
-EXTERN int p_tbs; ///< 'tagbsearch'
-EXTERN char *p_tc; ///< 'tagcase'
-EXTERN unsigned tc_flags; ///< flags from 'tagcase'
-#define TC_FOLLOWIC 0x01
-#define TC_IGNORE 0x02
-#define TC_MATCH 0x04
-#define TC_FOLLOWSCS 0x08
-#define TC_SMART 0x10
-EXTERN long p_tl; ///< 'taglength'
-EXTERN int p_tr; ///< 'tagrelative'
-EXTERN char *p_tags; ///< 'tags'
-EXTERN int p_tgst; ///< 'tagstack'
-EXTERN int p_tbidi; ///< 'termbidi'
-EXTERN long p_tw; ///< 'textwidth'
-EXTERN int p_to; ///< 'tildeop'
-EXTERN int p_timeout; ///< 'timeout'
-EXTERN long p_tm; ///< 'timeoutlen'
-EXTERN int p_title; ///< 'title'
-EXTERN long p_titlelen; ///< 'titlelen'
-EXTERN char *p_titleold; ///< 'titleold'
-EXTERN char *p_titlestring; ///< 'titlestring'
-EXTERN char *p_tsr; ///< 'thesaurus'
-EXTERN int p_tgc; ///< 'termguicolors'
-EXTERN int p_ttimeout; ///< 'ttimeout'
-EXTERN long p_ttm; ///< 'ttimeoutlen'
-EXTERN char *p_udir; ///< 'undodir'
-EXTERN int p_udf; ///< 'undofile'
-EXTERN long p_ul; ///< 'undolevels'
-EXTERN long p_ur; ///< 'undoreload'
-EXTERN long p_uc; ///< 'updatecount'
-EXTERN long p_ut; ///< 'updatetime'
-EXTERN char *p_shada; ///< 'shada'
-EXTERN char *p_shadafile; ///< 'shadafile'
-EXTERN char *p_vsts; ///< 'varsofttabstop'
-EXTERN char *p_vts; ///< 'vartabstop'
-EXTERN char *p_vdir; ///< 'viewdir'
-EXTERN char *p_vop; ///< 'viewoptions'
-EXTERN unsigned vop_flags; ///< uses SSOP_ flags
-EXTERN int p_vb; ///< 'visualbell'
-EXTERN char *p_ve; ///< 'virtualedit'
-EXTERN unsigned ve_flags;
-#define VE_BLOCK 5U // includes "all"
-#define VE_INSERT 6U // includes "all"
-#define VE_ALL 4U
-#define VE_ONEMORE 8U
-#define VE_NONE 16U // "none"
-#define VE_NONEU 32U // "NONE"
-EXTERN long p_verbose; // 'verbose'
-#ifdef IN_OPTION_C
-char *p_vfile = ""; // used before options are initialized
-#else
-extern char *p_vfile; // 'verbosefile'
-#endif
-EXTERN int p_warn; // 'warn'
-EXTERN char *p_wop; // 'wildoptions'
-EXTERN unsigned wop_flags;
-#define WOP_TAGFILE 0x01
-#define WOP_PUM 0x02
-#define WOP_FUZZY 0x04
-EXTERN long p_window; // 'window'
-EXTERN char *p_wak; // 'winaltkeys'
-EXTERN char *p_wig; // 'wildignore'
-EXTERN char *p_ww; // 'whichwrap'
-EXTERN long p_wc; // 'wildchar'
-EXTERN long p_wcm; // 'wildcharm'
-EXTERN int p_wic; // 'wildignorecase'
-EXTERN char *p_wim; // 'wildmode'
-EXTERN int p_wmnu; // 'wildmenu'
-EXTERN long p_wh; // 'winheight'
-EXTERN long p_wmh; // 'winminheight'
-EXTERN long p_wmw; // 'winminwidth'
-EXTERN long p_wiw; // 'winwidth'
-EXTERN long p_wm; ///< 'wrapmargin'
-EXTERN int p_ws; // 'wrapscan'
-EXTERN int p_write; // 'write'
-EXTERN int p_wa; // 'writeany'
-EXTERN int p_wb; // 'writebackup'
-EXTERN long p_wd; // 'writedelay'
-EXTERN int p_cdh; // 'cdhome'
-
-EXTERN int p_force_on; ///< options that cannot be turned off.
-EXTERN int p_force_off; ///< options that cannot be turned on.
-
-//
-// "indir" values for buffer-local options.
-// These need to be defined globally, so that the BV_COUNT can be used with
-// b_p_scriptID[].
-//
-enum {
- BV_AI = 0,
- BV_AR,
- BV_BH,
- BV_BKC,
- BV_BT,
- BV_EFM,
- BV_GP,
- BV_MP,
- BV_BIN,
- BV_BL,
- BV_BOMB,
- BV_CHANNEL,
- BV_CI,
- BV_CIN,
- BV_CINK,
- BV_CINO,
- BV_CINW,
- BV_CINSD,
- BV_CM,
- BV_CMS,
- BV_COM,
- BV_CPT,
- BV_DICT,
- BV_TSR,
- BV_CSL,
- BV_CFU,
- BV_DEF,
- BV_INC,
- BV_EOF,
- BV_EOL,
- BV_FIXEOL,
- BV_EP,
- BV_ET,
- BV_FENC,
- BV_FP,
- BV_BEXPR,
- BV_FEX,
- BV_FF,
- BV_FLP,
- BV_FO,
- BV_FT,
- BV_IMI,
- BV_IMS,
- BV_INDE,
- BV_INDK,
- BV_INEX,
- BV_INF,
- BV_ISK,
- BV_KMAP,
- BV_KP,
- BV_LISP,
- BV_LOP,
- BV_LW,
- BV_MENC,
- BV_MA,
- BV_ML,
- BV_MOD,
- BV_MPS,
- BV_NF,
- BV_OFU,
- BV_PATH,
- BV_PI,
- BV_QE,
- BV_RO,
- BV_SCBK,
- BV_SI,
- BV_SMC,
- BV_SYN,
- BV_SPC,
- BV_SPF,
- BV_SPL,
- BV_SPO,
- BV_STS,
- BV_SUA,
- BV_SW,
- BV_SWF,
- BV_TFU,
- BV_TSRFU,
- BV_TAGS,
- BV_TC,
- BV_TS,
- BV_TW,
- BV_TX,
- BV_UDF,
- BV_UL,
- BV_WM,
- BV_VSTS,
- BV_VTS,
- BV_COUNT, // must be the last one
-};
-
-// "indir" values for window-local options.
-// These need to be defined globally, so that the WV_COUNT can be used in the
-// window structure.
-enum {
- WV_LIST = 0,
- WV_ARAB,
- WV_COCU,
- WV_COLE,
- WV_CRBIND,
- WV_BRI,
- WV_BRIOPT,
- WV_DIFF,
- WV_FDC,
- WV_FEN,
- WV_FDI,
- WV_FDL,
- WV_FDM,
- WV_FML,
- WV_FDN,
- WV_FDE,
- WV_FDT,
- WV_FMR,
- WV_LBR,
- WV_NU,
- WV_RNU,
- WV_VE,
- WV_NUW,
- WV_PVW,
- WV_RL,
- WV_RLC,
- WV_SCBIND,
- WV_SCROLL,
- WV_SISO,
- WV_SO,
- WV_SPELL,
- WV_CUC,
- WV_CUL,
- WV_CULOPT,
- WV_CC,
- WV_SBR,
- WV_STC,
- WV_STL,
- WV_WFH,
- WV_WFW,
- WV_WRAP,
- WV_SCL,
- WV_WINHL,
- WV_LCS,
- WV_FCS,
- WV_WINBL,
- WV_WBR,
- WV_COUNT, // must be the last one
-};
-
-// Value for b_p_ul indicating the global value must be used.
-#define NO_LOCAL_UNDOLEVEL (-123456)
-
-#define SB_MAX 100000 // Maximum 'scrollback' value.
-
-#define TABSTOP_MAX 9999
-
-/// Stores an identifier of a script or channel that last set an option.
+ kOptValTypeNil = 0,
+ kOptValTypeBoolean,
+ kOptValTypeNumber,
+ kOptValTypeString,
+} OptValType;
+
+typedef union {
+ // boolean options are actually tri-states because they have a third "None" value.
+ TriState boolean;
+ OptInt number;
+ String string;
+} OptValData;
+
+/// Option value
typedef struct {
- sctx_T script_ctx; /// script context where the option was last set
- uint64_t channel_id; /// Only used when script_id is SID_API_CLIENT.
-} LastSet;
+ OptValType type;
+ OptValData data;
+} OptVal;
-// WV_ and BV_ values get typecasted to this for the "indir" field
+/// :set operator types
typedef enum {
- PV_NONE = 0,
- PV_MAXVAL = 0xffff, // to avoid warnings for value out of range
-} idopt_T;
-
-typedef struct vimoption {
- char *fullname; // full option name
- char *shortname; // permissible abbreviation
- uint32_t flags; // see below
- char_u *var; // global option: pointer to variable;
- // window-local option: VAR_WIN;
- // buffer-local option: global value
- idopt_T indir; // global option: PV_NONE;
- // local option: indirect option index
- char *def_val; // default values for variable (neovim!!)
- LastSet last_set; // script in which the option was last set
-} vimoption_T;
-
-// The options that are local to a window or buffer have "indir" set to one of
-// these values. Special values:
-// PV_NONE: global option.
-// PV_WIN is added: window-local option
-// PV_BUF is added: buffer-local option
-// PV_BOTH is added: global option which also has a local value.
-#define PV_BOTH 0x1000
-#define PV_WIN 0x2000
-#define PV_BUF 0x4000
-#define PV_MASK 0x0fff
-#define OPT_WIN(x) (idopt_T)(PV_WIN + (int)(x))
-#define OPT_BUF(x) (idopt_T)(PV_BUF + (int)(x))
-#define OPT_BOTH(x) (idopt_T)(PV_BOTH + (int)(x))
+ OP_NONE = 0,
+ OP_ADDING, ///< "opt+=arg"
+ OP_PREPENDING, ///< "opt^=arg"
+ OP_REMOVING, ///< "opt-=arg"
+} set_op_T;
+
+/// Argument for the callback function (opt_did_set_cb_T) invoked after an
+/// option value is modified.
+typedef struct {
+ /// Pointer to the option variable. The variable can be an OptInt (numeric
+ /// option), an int (boolean option) or a char pointer (string option).
+ void *os_varp;
+ int os_idx;
+ int os_flags;
+
+ /// Old value of the option.
+ OptValData os_oldval;
+ /// New value of the option.
+ OptValData os_newval;
+
+ /// Option value was checked to be safe, no need to set P_INSECURE
+ /// Used for the 'keymap', 'filetype' and 'syntax' options.
+ bool os_value_checked;
+ /// Option value changed. Used for the 'filetype' and 'syntax' options.
+ bool os_value_changed;
+
+ /// Used by the 'isident', 'iskeyword', 'isprint' and 'isfname' options.
+ /// Set to true if the character table is modified when processing the
+ /// option and need to be restored because of a failure.
+ bool os_restore_chartab;
+
+ /// If the value specified for an option is not valid and the error message
+ /// is parameterized, then the "os_errbuf" buffer is used to store the error
+ /// message (when it is not NULL).
+ char *os_errbuf;
+ size_t os_errbuflen;
+
+ void *os_win;
+ void *os_buf;
+} optset_T;
+
+/// Type for the callback function that is invoked after an option value is
+/// changed to validate and apply the new value.
+///
+/// Returns NULL if the option value is valid and successfully applied.
+/// Otherwise returns an error message.
+typedef const char *(*opt_did_set_cb_T)(optset_T *args);
-// Options local to a window have a value local to a buffer and global to all
-// buffers. Indicate this by setting "var" to VAR_WIN.
-#define VAR_WIN ((char_u *)-1)
+/// Argument for the callback function (opt_expand_cb_T) invoked after a string
+/// option value is expanded for cmdline completion.
+typedef struct {
+ /// Pointer to the option variable. It's always a string.
+ char *oe_varp;
+ /// The original option value, escaped.
+ char *oe_opt_value;
+
+ /// true if using set+= instead of set=
+ bool oe_append;
+ /// true if we would like to add the original option value as the first choice.
+ bool oe_include_orig_val;
+
+ /// Regex from the cmdline, for matching potential options against.
+ regmatch_T *oe_regmatch;
+ /// The expansion context.
+ expand_T *oe_xp;
+
+ /// The full argument passed to :set. For example, if the user inputs
+ /// ":set dip=icase,algorithm:my<Tab>", oe_xp->xp_pattern will only have
+ /// "my", but oe_set_arg will contain the whole "icase,algorithm:my".
+ char *oe_set_arg;
+} optexpand_T;
+
+/// Type for the callback function that is invoked when expanding possible
+/// string option values during cmdline completion.
+///
+/// Strings in returned matches will be managed and freed by caller.
+///
+/// Returns OK if the expansion succeeded (numMatches and matches have to be
+/// set). Otherwise returns FAIL.
+///
+/// Note: If returned FAIL or *numMatches is 0, *matches will NOT be freed by
+/// caller.
+typedef int (*opt_expand_cb_T)(optexpand_T *args, int *numMatches, char ***matches);
-#endif // NVIM_OPTION_DEFS_H
+/// Requested option scopes for various functions in option.c
+typedef enum {
+ kOptReqGlobal = 0, ///< Request global option value
+ kOptReqWin = 1, ///< Request window-local option value
+ kOptReqBuf = 2, ///< Request buffer-local option value
+} OptReqScope;
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
new file mode 100644
index 0000000000..b0e9ff9434
--- /dev/null
+++ b/src/nvim/option_vars.h
@@ -0,0 +1,948 @@
+#pragma once
+
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
+
+// option_vars.h: definition of global variables for settable options
+
+// Option Flags
+#define P_BOOL 0x01U ///< the option is boolean
+#define P_NUM 0x02U ///< the option is numeric
+#define P_STRING 0x04U ///< the option is a string
+#define P_ALLOCED 0x08U ///< the string option is in allocated memory,
+ ///< must use free_string_option() when
+ ///< assigning new value. Not set if default is
+ ///< the same.
+#define P_EXPAND 0x10U ///< environment expansion. NOTE: P_EXPAND can
+ ///< never be used for local or hidden options
+#define P_NO_DEF_EXP 0x20U ///< do not expand default value
+#define P_NODEFAULT 0x40U ///< don't set to default value
+#define P_DEF_ALLOCED 0x80U ///< default value is in allocated memory, must
+ ///< use free() when assigning new value
+#define P_WAS_SET 0x100U ///< option has been set/reset
+#define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output
+
+// when option changed, what to display:
+#define P_UI_OPTION 0x400U ///< send option to remote UI
+#define P_RTABL 0x800U ///< redraw tabline
+#define P_RSTAT 0x1000U ///< redraw status lines
+#define P_RWIN 0x2000U ///< redraw current window and recompute text
+#define P_RBUF 0x4000U ///< redraw current buffer and recompute text
+#define P_RALL 0x6000U ///< redraw all windows
+#define P_RCLR 0x7000U ///< clear and redraw all
+
+#define P_COMMA 0x8000U ///< comma separated list
+#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive
+ ///< commas
+#define P_NODUP 0x20000U ///< don't allow duplicate strings
+#define P_FLAGLIST 0x40000U ///< list of single-char flags
+
+#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode
+#define P_GETTEXT 0x100000U ///< expand default value with _()
+#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc
+#define P_NFNAME 0x400000U ///< only normal file name chars allowed
+#define P_INSECURE 0x800000U ///< option was set from a modeline
+#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option
+ ///< has side effects)
+#define P_NO_ML 0x2000000U ///< not allowed in modeline
+#define P_CURSWANT 0x4000000U ///< update curswant required; not needed
+ ///< when there is a redraw flag
+#define P_NDNAME 0x8000000U ///< only normal dir name chars allowed
+#define P_RWINONLY 0x10000000U ///< only redraw current window
+#define P_MLE 0x20000000U ///< under control of 'modelineexpr'
+#define P_FUNC 0x40000000U ///< accept a function reference or a lambda
+#define P_COLON 0x80000000U ///< values use colons to create sublists
+// Warning: Currently we have used all 32 bits for option flags, and adding more
+// flags will overflow it. Adding another flag will need to change how
+// it's stored first.
+
+#define HIGHLIGHT_INIT \
+ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \
+ "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \
+ "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \
+ "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \
+ "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \
+ "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel," \
+ "[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb," \
+ "*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \
+ "q:QuickFixLine,0:Whitespace,I:NormalNC"
+
+// Default values for 'errorformat'.
+// The "%f|%l| %m" one is used for when the contents of the quickfix window is
+// written to a file.
+#ifdef MSWIN
+# define DFLT_EFM \
+ "%f(%l): %t%*\\D%n: %m,%f(%l\\,%c): %t%*\\D%n: %m,%f(%l) \\=: %t%*\\D%n: %m,%*[^\"]\"%f\"%*\\D%l: %m,%f(%l) \\=: %m,%*[^ ] %f %l: %m,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,%f|%l| %m"
+#else
+# define DFLT_EFM \
+ "%*[^\"]\"%f\"%*\\D%l: %m,\"%f\"%*\\D%l: %m,%-Gg%\\?make[%*\\d]: *** [%f:%l:%m,%-Gg%\\?make: *** [%f:%l:%m,%-G%f:%l: (Each undeclared identifier is reported only once,%-G%f:%l: for each function it appears in.),%-GIn file included from %f:%l:%c:,%-GIn file included from %f:%l:%c\\,,%-GIn file included from %f:%l:%c,%-GIn file included from %f:%l,%-G%*[ ]from %f:%l:%c,%-G%*[ ]from %f:%l:,%-G%*[ ]from %f:%l\\,,%-G%*[ ]from %f:%l,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,\"%f\"\\, line %l%*\\D%c%*[^ ] %m,%D%*\\a[%*\\d]: Entering directory %*[`']%f',%X%*\\a[%*\\d]: Leaving directory %*[`']%f',%D%*\\a: Entering directory %*[`']%f',%X%*\\a: Leaving directory %*[`']%f',%DMaking %*\\a in %f,%f|%l| %m"
+#endif
+
+#define DFLT_GREPFORMAT "%f:%l:%m,%f:%l%m,%f %l%m"
+
+// default values for b_p_ff 'fileformat' and p_ffs 'fileformats'
+#define FF_DOS "dos"
+#define FF_MAC "mac"
+#define FF_UNIX "unix"
+
+#ifdef USE_CRNL
+# define DFLT_FF "dos"
+# define DFLT_FFS_VIM "dos,unix"
+# define DFLT_FFS_VI "dos,unix" // also autodetect in compatible mode
+#else
+# define DFLT_FF "unix"
+# define DFLT_FFS_VIM "unix,dos"
+# define DFLT_FFS_VI ""
+#endif
+
+// Possible values for 'encoding'
+#define ENC_UCSBOM "ucs-bom" // check for BOM at start of file
+
+// default value for 'encoding'
+#define ENC_DFLT "utf-8"
+
+// end-of-line style
+#define EOL_UNKNOWN (-1) // not defined yet
+#define EOL_UNIX 0 // NL
+#define EOL_DOS 1 // CR NL
+#define EOL_MAC 2 // CR
+
+// Formatting options for p_fo 'formatoptions'
+#define FO_WRAP 't'
+#define FO_WRAP_COMS 'c'
+#define FO_RET_COMS 'r'
+#define FO_OPEN_COMS 'o'
+#define FO_NO_OPEN_COMS '/'
+#define FO_Q_COMS 'q'
+#define FO_Q_NUMBER 'n'
+#define FO_Q_SECOND '2'
+#define FO_INS_VI 'v'
+#define FO_INS_LONG 'l'
+#define FO_INS_BLANK 'b'
+#define FO_MBYTE_BREAK 'm' // break before/after multi-byte char
+#define FO_MBYTE_JOIN 'M' // no space before/after multi-byte char
+#define FO_MBYTE_JOIN2 'B' // no space between multi-byte chars
+#define FO_ONE_LETTER '1'
+#define FO_WHITE_PAR 'w' // trailing white space continues paragr.
+#define FO_AUTO 'a' // automatic formatting
+#define FO_RIGOROUS_TW ']' // respect textwidth rigorously
+#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines
+#define FO_PERIOD_ABBR 'p' // don't break a single space after a period
+
+#define DFLT_FO_VI "vt"
+#define DFLT_FO_VIM "tcqj"
+#define FO_ALL "tcro/q2vlb1mMBn,aw]jp" // for do_set()
+
+// characters for the p_cpo option:
+#define CPO_ALTREAD 'a' // ":read" sets alternate file name
+#define CPO_ALTWRITE 'A' // ":write" sets alternate file name
+#define CPO_BAR 'b' // "\|" ends a mapping
+#define CPO_BSLASH 'B' // backslash in mapping is not special
+#define CPO_SEARCH 'c'
+#define CPO_CONCAT 'C' // Don't concatenate sourced lines
+#define CPO_DOTTAG 'd' // "./tags" in 'tags' is in current dir
+#define CPO_DIGRAPH 'D' // No digraph after "r", "f", etc.
+#define CPO_EXECBUF 'e'
+#define CPO_EMPTYREGION 'E' // operating on empty region is an error
+#define CPO_FNAMER 'f' // set file name for ":r file"
+#define CPO_FNAMEW 'F' // set file name for ":w file"
+#define CPO_INTMOD 'i' // interrupt a read makes buffer modified
+#define CPO_INDENT 'I' // remove auto-indent more often
+#define CPO_ENDOFSENT 'J' // need two spaces to detect end of sentence
+#define CPO_KOFFSET 'K' // don't wait for key code in mappings
+#define CPO_LITERAL 'l' // take char after backslash in [] literal
+#define CPO_LISTWM 'L' // 'list' changes wrapmargin
+#define CPO_SHOWMATCH 'm'
+#define CPO_MATCHBSL 'M' // "%" ignores use of backslashes
+#define CPO_NUMCOL 'n' // 'number' column also used for text
+#define CPO_LINEOFF 'o'
+#define CPO_OVERNEW 'O' // silently overwrite new file
+#define CPO_LISP 'p' // 'lisp' indenting
+#define CPO_FNAMEAPP 'P' // set file name for ":w >>file"
+#define CPO_JOINCOL 'q' // with "3J" use column after first join
+#define CPO_REDO 'r'
+#define CPO_REMMARK 'R' // remove marks when filtering
+#define CPO_BUFOPT 's'
+#define CPO_BUFOPTGLOB 'S'
+#define CPO_TAGPAT 't' // tag pattern is used for "n"
+#define CPO_UNDO 'u' // "u" undoes itself
+#define CPO_BACKSPACE 'v' // "v" keep deleted text
+#define CPO_FWRITE 'W' // "w!" doesn't overwrite readonly files
+#define CPO_ESC 'x'
+#define CPO_REPLCNT 'X' // "R" with a count only deletes chars once
+#define CPO_YANK 'y'
+#define CPO_KEEPRO 'Z' // don't reset 'readonly' on ":w!"
+#define CPO_DOLLAR '$'
+#define CPO_FILTER '!'
+#define CPO_MATCH '%'
+#define CPO_PLUS '+' // ":write file" resets 'modified'
+#define CPO_REGAPPEND '>' // insert NL when appending to a register
+#define CPO_SCOLON ';' // using "," and ";" will skip over char if
+ // cursor would not move
+#define CPO_CHANGEW '_' // "cw" special-case
+// default values for Vim and Vi
+#define CPO_VIM "aABceFs_"
+#define CPO_VI "aAbBcCdDeEfFiIJKlLmMnoOpPqrRsStuvWxXyZ$!%+>;_"
+
+// characters for p_ww option:
+#define WW_ALL "bshl<>[]~"
+
+// characters for p_mouse option:
+#define MOUSE_NORMAL 'n' // use mouse in Normal mode
+#define MOUSE_VISUAL 'v' // use mouse in Visual/Select mode
+#define MOUSE_INSERT 'i' // use mouse in Insert mode
+#define MOUSE_COMMAND 'c' // use mouse in Command-line mode
+#define MOUSE_HELP 'h' // use mouse in help buffers
+#define MOUSE_RETURN 'r' // use mouse for hit-return message
+#define MOUSE_A "nvich" // used for 'a' flag
+#define MOUSE_ALL "anvichr" // all possible characters
+#define MOUSE_NONE ' ' // don't use Visual selection
+#define MOUSE_NONEF 'x' // forced modeless selection
+
+// default vertical and horizontal mouse scroll values.
+// Note: This should be in sync with the default mousescroll option.
+#define MOUSESCROLL_VERT_DFLT 3
+#define MOUSESCROLL_HOR_DFLT 6
+
+#define COCU_ALL "nvic" // flags for 'concealcursor'
+
+/// characters for p_shm option:
+enum {
+ SHM_RO = 'r', ///< Readonly.
+ SHM_MOD = 'm', ///< Modified.
+ SHM_LINES = 'l', ///< "L" instead of "lines".
+ SHM_WRI = 'w', ///< "[w]" instead of "written".
+ SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS.
+ SHM_WRITE = 'W', ///< Don't use "written" at all.
+ SHM_TRUNC = 't', ///< Truncate file messages.
+ SHM_TRUNCALL = 'T', ///< Truncate all messages.
+ SHM_OVER = 'o', ///< Overwrite file messages.
+ SHM_OVERALL = 'O', ///< Overwrite more messages.
+ SHM_SEARCH = 's', ///< No search hit bottom messages.
+ SHM_ATTENTION = 'A', ///< No ATTENTION messages.
+ SHM_INTRO = 'I', ///< Intro messages.
+ SHM_COMPLETIONMENU = 'c', ///< Completion menu messages.
+ SHM_COMPLETIONSCAN = 'C', ///< Completion scanning messages.
+ SHM_RECORDING = 'q', ///< Short recording message.
+ SHM_FILEINFO = 'F', ///< No file info messages.
+ SHM_SEARCHCOUNT = 'S', ///< No search stats: '[1/10]'
+};
+/// Represented by 'a' flag.
+#define SHM_ALL_ABBREVIATIONS ((char[]) { \
+ SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, \
+ 0 })
+
+// characters for p_go:
+#define GO_ASEL 'a' // autoselect
+#define GO_ASELML 'A' // autoselect modeless selection
+#define GO_BOT 'b' // use bottom scrollbar
+#define GO_CONDIALOG 'c' // use console dialog
+#define GO_DARKTHEME 'd' // use dark theme variant
+#define GO_TABLINE 'e' // may show tabline
+#define GO_FORG 'f' // start GUI in foreground
+#define GO_GREY 'g' // use grey menu items
+#define GO_HORSCROLL 'h' // flexible horizontal scrolling
+#define GO_ICON 'i' // use Vim icon
+#define GO_LEFT 'l' // use left scrollbar
+#define GO_VLEFT 'L' // left scrollbar with vert split
+#define GO_MENUS 'm' // use menu bar
+#define GO_NOSYSMENU 'M' // don't source system menu
+#define GO_POINTER 'p' // pointer enter/leave callbacks
+#define GO_ASELPLUS 'P' // autoselectPlus
+#define GO_RIGHT 'r' // use right scrollbar
+#define GO_VRIGHT 'R' // right scrollbar with vert split
+#define GO_TOOLBAR 'T' // add toolbar
+#define GO_FOOTER 'F' // add footer
+#define GO_VERTICAL 'v' // arrange dialog buttons vertically
+#define GO_KEEPWINSIZE 'k' // keep GUI window size
+#define GO_ALL "!aAbcdefFghilLmMpPrRtTvk" // all possible flags for 'go'
+
+// flags for 'comments' option
+#define COM_NEST 'n' // comments strings nest
+#define COM_BLANK 'b' // needs blank after string
+#define COM_START 's' // start of comment
+#define COM_MIDDLE 'm' // middle of comment
+#define COM_END 'e' // end of comment
+#define COM_AUTO_END 'x' // last char of end closes comment
+#define COM_FIRST 'f' // first line comment only
+#define COM_LEFT 'l' // left adjusted
+#define COM_RIGHT 'r' // right adjusted
+#define COM_NOBACK 'O' // don't use for "O" command
+#define COM_ALL "nbsmexflrO" // all flags for 'comments' option
+#define COM_MAX_LEN 50 // maximum length of a part
+
+/// 'statusline' option flags
+enum {
+ STL_FILEPATH = 'f', ///< Path of file in buffer.
+ STL_FULLPATH = 'F', ///< Full path of file in buffer.
+ STL_FILENAME = 't', ///< Last part (tail) of file path.
+ STL_COLUMN = 'c', ///< Column og cursor.
+ STL_VIRTCOL = 'v', ///< Virtual column.
+ STL_VIRTCOL_ALT = 'V', ///< - with 'if different' display.
+ STL_LINE = 'l', ///< Line number of cursor.
+ STL_NUMLINES = 'L', ///< Number of lines in buffer.
+ STL_BUFNO = 'n', ///< Current buffer number.
+ STL_KEYMAP = 'k', ///< 'keymap' when active.
+ STL_OFFSET = 'o', ///< Offset of character under cursor.
+ STL_OFFSET_X = 'O', ///< - in hexadecimal.
+ STL_BYTEVAL = 'b', ///< Byte value of character.
+ STL_BYTEVAL_X = 'B', ///< - in hexadecimal.
+ STL_ROFLAG = 'r', ///< Readonly flag.
+ STL_ROFLAG_ALT = 'R', ///< - other display.
+ STL_HELPFLAG = 'h', ///< Window is showing a help file.
+ STL_HELPFLAG_ALT = 'H', ///< - other display.
+ STL_FILETYPE = 'y', ///< 'filetype'.
+ STL_FILETYPE_ALT = 'Y', ///< - other display.
+ STL_PREVIEWFLAG = 'w', ///< Window is showing the preview buf.
+ STL_PREVIEWFLAG_ALT = 'W', ///< - other display.
+ STL_MODIFIED = 'm', ///< Modified flag.
+ STL_MODIFIED_ALT = 'M', ///< - other display.
+ STL_QUICKFIX = 'q', ///< Quickfix window description.
+ STL_PERCENTAGE = 'p', ///< Percentage through file.
+ STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%.
+ STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y).
+ STL_PAGENUM = 'N', ///< Page number (when printing).
+ STL_SHOWCMD = 'S', ///< 'showcmd' buffer
+ STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn'
+ STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn'
+ STL_VIM_EXPR = '{', ///< Start of expression to substitute.
+ STL_SEPARATE = '=', ///< Separation between alignment sections.
+ STL_TRUNCMARK = '<', ///< Truncation mark if line is too long.
+ STL_USER_HL = '*', ///< Highlight from (User)1..9 or 0.
+ STL_HIGHLIGHT = '#', ///< Highlight name.
+ STL_TABPAGENR = 'T', ///< Tab page label nr.
+ STL_TABCLOSENR = 'X', ///< Tab page close nr.
+ STL_CLICK_FUNC = '@', ///< Click region start.
+};
+/// C string containing all 'statusline' option flags
+#define STL_ALL ((char[]) { \
+ STL_FILEPATH, STL_FULLPATH, STL_FILENAME, STL_COLUMN, STL_VIRTCOL, \
+ STL_VIRTCOL_ALT, STL_LINE, STL_NUMLINES, STL_BUFNO, STL_KEYMAP, STL_OFFSET, \
+ STL_OFFSET_X, STL_BYTEVAL, STL_BYTEVAL_X, STL_ROFLAG, STL_ROFLAG_ALT, \
+ STL_HELPFLAG, STL_HELPFLAG_ALT, STL_FILETYPE, STL_FILETYPE_ALT, \
+ STL_PREVIEWFLAG, STL_PREVIEWFLAG_ALT, STL_MODIFIED, STL_MODIFIED_ALT, \
+ STL_QUICKFIX, STL_PERCENTAGE, STL_ALTPERCENT, STL_ARGLISTSTAT, STL_PAGENUM, \
+ STL_SHOWCMD, STL_FOLDCOL, STL_SIGNCOL, STL_VIM_EXPR, STL_SEPARATE, \
+ STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, \
+ STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \
+ 0, })
+
+// flags used for parsed 'wildmode'
+#define WIM_FULL 0x01
+#define WIM_LONGEST 0x02
+#define WIM_LIST 0x04
+#define WIM_BUFLASTUSED 0x08
+
+// arguments for can_bs()
+// each defined char should be unique over all values
+// except for BS_START, that intentionally also matches BS_NOSTOP
+// because BS_NOSTOP behaves exactly the same except it
+// does not stop at the start of the insert point
+#define BS_INDENT 'i' // "Indent"
+#define BS_EOL 'l' // "eoL"
+#define BS_START 's' // "Start"
+#define BS_NOSTOP 'p' // "nostoP
+
+// flags for the 'culopt' option
+#define CULOPT_LINE 0x01 // Highlight complete line
+#define CULOPT_SCRLINE 0x02 // Highlight screen line
+#define CULOPT_NBR 0x04 // Highlight Number column
+
+#define LISPWORD_VALUE \
+ "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object"
+
+// The following are actual variables for the options
+
+EXTERN char *p_ambw; ///< 'ambiwidth'
+EXTERN int p_acd; ///< 'autochdir'
+EXTERN int p_ai; ///< 'autoindent'
+EXTERN int p_bin; ///< 'binary'
+EXTERN int p_bomb; ///< 'bomb'
+EXTERN int p_bl; ///< 'buflisted'
+EXTERN int p_cin; ///< 'cindent'
+EXTERN OptInt p_channel; ///< 'channel'
+EXTERN char *p_cink; ///< 'cinkeys'
+EXTERN char *p_cinsd; ///< 'cinscopedecls'
+EXTERN char *p_cinw; ///< 'cinwords'
+EXTERN char *p_cfu; ///< 'completefunc'
+EXTERN char *p_ofu; ///< 'omnifunc'
+EXTERN char *p_tsrfu; ///< 'thesaurusfunc'
+EXTERN int p_ci; ///< 'copyindent'
+EXTERN int p_ar; ///< 'autoread'
+EXTERN int p_aw; ///< 'autowrite'
+EXTERN int p_awa; ///< 'autowriteall'
+EXTERN char *p_bs; ///< 'backspace'
+EXTERN char *p_bg; ///< 'background'
+EXTERN int p_bk; ///< 'backup'
+EXTERN char *p_bkc; ///< 'backupcopy'
+EXTERN unsigned bkc_flags; ///< flags from 'backupcopy'
+#define BKC_YES 0x001
+#define BKC_AUTO 0x002
+#define BKC_NO 0x004
+#define BKC_BREAKSYMLINK 0x008
+#define BKC_BREAKHARDLINK 0x010
+EXTERN char *p_bdir; ///< 'backupdir'
+EXTERN char *p_bex; ///< 'backupext'
+EXTERN char *p_bo; ///< 'belloff'
+EXTERN char breakat_flags[256]; ///< which characters are in 'breakat'
+EXTERN unsigned bo_flags;
+
+// values for the 'belloff' option
+#define BO_ALL 0x0001
+#define BO_BS 0x0002
+#define BO_CRSR 0x0004
+#define BO_COMPL 0x0008
+#define BO_COPY 0x0010
+#define BO_CTRLG 0x0020
+#define BO_ERROR 0x0040
+#define BO_ESC 0x0080
+#define BO_EX 0x0100
+#define BO_HANGUL 0x0200
+#define BO_IM 0x0400
+#define BO_LANG 0x0800
+#define BO_MESS 0x1000
+#define BO_MATCH 0x2000
+#define BO_OPER 0x4000
+#define BO_REG 0x8000
+#define BO_SH 0x10000
+#define BO_SPELL 0x20000
+#define BO_WILD 0x40000
+
+EXTERN char *p_bsk; ///< 'backupskip'
+EXTERN char *p_breakat; ///< 'breakat'
+EXTERN char *p_bh; ///< 'bufhidden'
+EXTERN char *p_bt; ///< 'buftype'
+EXTERN char *p_cmp; ///< 'casemap'
+EXTERN unsigned cmp_flags;
+#define CMP_INTERNAL 0x001
+#define CMP_KEEPASCII 0x002
+EXTERN char *p_enc; ///< 'encoding'
+EXTERN int p_deco; ///< 'delcombine'
+EXTERN char *p_ccv; ///< 'charconvert'
+EXTERN char *p_cino; ///< 'cinoptions'
+EXTERN char *p_cedit; ///< 'cedit'
+EXTERN char *p_cb; ///< 'clipboard'
+EXTERN unsigned cb_flags;
+#define CB_UNNAMED 0x001
+#define CB_UNNAMEDPLUS 0x002
+#define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
+EXTERN OptInt p_cwh; ///< 'cmdwinheight'
+EXTERN OptInt p_ch; ///< 'cmdheight'
+EXTERN char *p_cms; ///< 'commentstring'
+EXTERN char *p_cpt; ///< 'complete'
+EXTERN OptInt p_columns; ///< 'columns'
+EXTERN int p_confirm; ///< 'confirm'
+EXTERN char *p_cot; ///< 'completeopt'
+#ifdef BACKSLASH_IN_FILENAME
+EXTERN char *p_csl; ///< 'completeslash'
+#endif
+EXTERN OptInt p_pb; ///< 'pumblend'
+EXTERN OptInt p_ph; ///< 'pumheight'
+EXTERN OptInt p_pw; ///< 'pumwidth'
+EXTERN char *p_com; ///< 'comments'
+EXTERN char *p_cpo; ///< 'cpoptions'
+EXTERN char *p_debug; ///< 'debug'
+EXTERN char *p_def; ///< 'define'
+EXTERN char *p_inc;
+EXTERN char *p_dip; ///< 'diffopt'
+EXTERN char *p_dex; ///< 'diffexpr'
+EXTERN char *p_dict; ///< 'dictionary'
+EXTERN int p_dg; ///< 'digraph'
+EXTERN char *p_dir; ///< 'directory'
+EXTERN char *p_dy; ///< 'display'
+EXTERN unsigned dy_flags;
+#define DY_LASTLINE 0x001
+#define DY_TRUNCATE 0x002
+#define DY_UHEX 0x004
+// legacy flag, not used
+#define DY_MSGSEP 0x008
+EXTERN char *p_ead; ///< 'eadirection'
+EXTERN int p_emoji; ///< 'emoji'
+EXTERN int p_ea; ///< 'equalalways'
+EXTERN char *p_ep; ///< 'equalprg'
+EXTERN int p_eb; ///< 'errorbells'
+EXTERN char *p_ef; ///< 'errorfile'
+EXTERN char *p_efm; ///< 'errorformat'
+EXTERN char *p_gefm; ///< 'grepformat'
+EXTERN char *p_gp; ///< 'grepprg'
+EXTERN int p_eof; ///< 'endoffile'
+EXTERN int p_eol; ///< 'endofline'
+EXTERN char *p_ei; ///< 'eventignore'
+EXTERN int p_et; ///< 'expandtab'
+EXTERN int p_exrc; ///< 'exrc'
+EXTERN char *p_fenc; ///< 'fileencoding'
+EXTERN char *p_fencs; ///< 'fileencodings'
+EXTERN char *p_ff; ///< 'fileformat'
+EXTERN char *p_ffs; ///< 'fileformats'
+EXTERN int p_fic; ///< 'fileignorecase'
+EXTERN char *p_ft; ///< 'filetype'
+EXTERN char *p_fcs; ///< 'fillchar'
+EXTERN int p_fixeol; ///< 'fixendofline'
+EXTERN char *p_fcl; ///< 'foldclose'
+EXTERN OptInt p_fdls; ///< 'foldlevelstart'
+EXTERN char *p_fdo; ///< 'foldopen'
+EXTERN unsigned fdo_flags;
+#define FDO_ALL 0x001
+#define FDO_BLOCK 0x002
+#define FDO_HOR 0x004
+#define FDO_MARK 0x008
+#define FDO_PERCENT 0x010
+#define FDO_QUICKFIX 0x020
+#define FDO_SEARCH 0x040
+#define FDO_TAG 0x080
+#define FDO_INSERT 0x100
+#define FDO_UNDO 0x200
+#define FDO_JUMP 0x400
+EXTERN char *p_fex; ///< 'formatexpr'
+EXTERN char *p_flp; ///< 'formatlistpat'
+EXTERN char *p_fo; ///< 'formatoptions'
+EXTERN char *p_fp; ///< 'formatprg'
+EXTERN int p_fs; ///< 'fsync'
+EXTERN int p_gd; ///< 'gdefault'
+EXTERN char *p_guicursor; ///< 'guicursor'
+EXTERN char *p_guifont; ///< 'guifont'
+EXTERN char *p_guifontwide; ///< 'guifontwide'
+EXTERN char *p_hf; ///< 'helpfile'
+EXTERN OptInt p_hh; ///< 'helpheight'
+EXTERN char *p_hlg; ///< 'helplang'
+EXTERN int p_hid; ///< 'hidden'
+EXTERN char *p_hl; ///< 'highlight'
+EXTERN int p_hls; ///< 'hlsearch'
+EXTERN OptInt p_hi; ///< 'history'
+EXTERN int p_arshape; ///< 'arabicshape'
+EXTERN int p_icon; ///< 'icon'
+EXTERN char *p_iconstring; ///< 'iconstring'
+EXTERN int p_ic; ///< 'ignorecase'
+EXTERN OptInt p_iminsert; ///< 'iminsert'
+EXTERN OptInt p_imsearch; ///< 'imsearch'
+EXTERN int p_inf; ///< 'infercase'
+EXTERN char *p_inex; ///< 'includeexpr'
+EXTERN int p_is; ///< 'incsearch'
+EXTERN char *p_inde; ///< 'indentexpr'
+EXTERN char *p_indk; ///< 'indentkeys'
+EXTERN char *p_icm; ///< 'inccommand'
+EXTERN char *p_isf; ///< 'isfname'
+EXTERN char *p_isi; ///< 'isident'
+EXTERN char *p_isk; ///< 'iskeyword'
+EXTERN char *p_isp; ///< 'isprint'
+EXTERN int p_js; ///< 'joinspaces'
+EXTERN char *p_jop; ///< 'jumpooptions'
+EXTERN unsigned jop_flags;
+#define JOP_STACK 0x01
+#define JOP_VIEW 0x02
+EXTERN char *p_keymap; ///< 'keymap'
+EXTERN char *p_kp; ///< 'keywordprg'
+EXTERN char *p_km; ///< 'keymodel'
+EXTERN char *p_langmap; ///< 'langmap'
+EXTERN int p_lnr; ///< 'langnoremap'
+EXTERN int p_lrm; ///< 'langremap'
+EXTERN char *p_lm; ///< 'langmenu'
+EXTERN OptInt p_lines; ///< 'lines'
+EXTERN OptInt p_linespace; ///< 'linespace'
+EXTERN int p_lisp; ///< 'lisp'
+EXTERN char *p_lop; ///< 'lispoptions'
+EXTERN char *p_lispwords; ///< 'lispwords'
+EXTERN OptInt p_ls; ///< 'laststatus'
+EXTERN OptInt p_stal; ///< 'showtabline'
+EXTERN char *p_lcs; ///< 'listchars'
+
+EXTERN int p_lz; ///< 'lazyredraw'
+EXTERN int p_lpl; ///< 'loadplugins'
+EXTERN int p_magic; ///< 'magic'
+EXTERN char *p_menc; ///< 'makeencoding'
+EXTERN char *p_mef; ///< 'makeef'
+EXTERN char *p_mp; ///< 'makeprg'
+EXTERN char *p_mps; ///< 'matchpairs'
+EXTERN OptInt p_mat; ///< 'matchtime'
+EXTERN OptInt p_mco; ///< 'maxcombine'
+#define MAX_MCO 6 // fixed value for 'maxcombine'
+EXTERN OptInt p_mfd; ///< 'maxfuncdepth'
+EXTERN OptInt p_mmd; ///< 'maxmapdepth'
+EXTERN OptInt p_mmp; ///< 'maxmempattern'
+EXTERN OptInt p_mis; ///< 'menuitems'
+EXTERN char *p_msm; ///< 'mkspellmem'
+EXTERN int p_ml; ///< 'modeline'
+EXTERN int p_mle; ///< 'modelineexpr'
+EXTERN OptInt p_mls; ///< 'modelines'
+EXTERN int p_ma; ///< 'modifiable'
+EXTERN int p_mod; ///< 'modified'
+EXTERN char *p_mouse; ///< 'mouse'
+EXTERN char *p_mousem; ///< 'mousemodel'
+EXTERN int p_mousemev; ///< 'mousemoveevent'
+EXTERN int p_mousef; ///< 'mousefocus'
+EXTERN int p_mh; ///< 'mousehide'
+EXTERN char *p_mousescroll; ///< 'mousescroll'
+EXTERN OptInt p_mousescroll_vert INIT( = MOUSESCROLL_VERT_DFLT);
+EXTERN OptInt p_mousescroll_hor INIT( = MOUSESCROLL_HOR_DFLT);
+EXTERN OptInt p_mouset; ///< 'mousetime'
+EXTERN int p_more; ///< 'more'
+EXTERN char *p_nf; ///< 'nrformats'
+EXTERN char *p_opfunc; ///< 'operatorfunc'
+EXTERN char *p_para; ///< 'paragraphs'
+EXTERN int p_paste; ///< 'paste'
+EXTERN char *p_pex; ///< 'patchexpr'
+EXTERN char *p_pm; ///< 'patchmode'
+EXTERN char *p_path; ///< 'path'
+EXTERN char *p_cdpath; ///< 'cdpath'
+EXTERN int p_pi; ///< 'preserveindent'
+EXTERN OptInt p_pyx; ///< 'pyxversion'
+EXTERN char *p_qe; ///< 'quoteescape'
+EXTERN int p_ro; ///< 'readonly'
+EXTERN char *p_rdb; ///< 'redrawdebug'
+EXTERN unsigned rdb_flags;
+#define RDB_COMPOSITOR 0x001
+#define RDB_NOTHROTTLE 0x002
+#define RDB_INVALID 0x004
+#define RDB_NODELTA 0x008
+#define RDB_LINE 0x010
+#define RDB_FLUSH 0x020
+#define RDB_INTERSECT 0x040
+
+EXTERN OptInt p_rdt; ///< 'redrawtime'
+EXTERN OptInt p_re; ///< 'regexpengine'
+EXTERN OptInt p_report; ///< 'report'
+EXTERN OptInt p_pvh; ///< 'previewheight'
+EXTERN int p_ari; ///< 'allowrevins'
+EXTERN int p_ri; ///< 'revins'
+EXTERN int p_ru; ///< 'ruler'
+EXTERN char *p_ruf; ///< 'rulerformat'
+EXTERN char *p_pp; ///< 'packpath'
+EXTERN char *p_qftf; ///< 'quickfixtextfunc'
+EXTERN char *p_rtp; ///< 'runtimepath'
+EXTERN OptInt p_scbk; ///< 'scrollback'
+EXTERN OptInt p_sj; ///< 'scrolljump'
+EXTERN OptInt p_so; ///< 'scrolloff'
+EXTERN char *p_sbo; ///< 'scrollopt'
+EXTERN char *p_sections; ///< 'sections'
+EXTERN int p_secure; ///< 'secure'
+EXTERN char *p_sel; ///< 'selection'
+EXTERN char *p_slm; ///< 'selectmode'
+EXTERN char *p_ssop; ///< 'sessionoptions'
+EXTERN unsigned ssop_flags;
+
+#define SSOP_BUFFERS 0x001
+#define SSOP_WINPOS 0x002
+#define SSOP_RESIZE 0x004
+#define SSOP_WINSIZE 0x008
+#define SSOP_LOCALOPTIONS 0x010
+#define SSOP_OPTIONS 0x020
+#define SSOP_HELP 0x040
+#define SSOP_BLANK 0x080
+#define SSOP_GLOBALS 0x100
+#define SSOP_SLASH 0x200 // Deprecated, always set.
+#define SSOP_UNIX 0x400 // Deprecated, always set.
+#define SSOP_SESDIR 0x800
+#define SSOP_CURDIR 0x1000
+#define SSOP_FOLDS 0x2000
+#define SSOP_CURSOR 0x4000
+#define SSOP_TABPAGES 0x8000
+#define SSOP_TERMINAL 0x10000
+#define SSOP_SKIP_RTP 0x20000
+
+EXTERN char *p_sh; ///< 'shell'
+EXTERN char *p_shcf; ///< 'shellcmdflag'
+EXTERN char *p_sp; ///< 'shellpipe'
+EXTERN char *p_shq; ///< 'shellquote'
+EXTERN char *p_sxq; ///< 'shellxquote'
+EXTERN char *p_sxe; ///< 'shellxescape'
+EXTERN char *p_srr; ///< 'shellredir'
+EXTERN int p_stmp; ///< 'shelltemp'
+#ifdef BACKSLASH_IN_FILENAME
+EXTERN int p_ssl; ///< 'shellslash'
+#endif
+EXTERN char *p_stl; ///< 'statusline'
+EXTERN char *p_wbr; ///< 'winbar'
+EXTERN int p_sr; ///< 'shiftround'
+EXTERN OptInt p_sw; ///< 'shiftwidth'
+EXTERN char *p_shm; ///< 'shortmess'
+EXTERN char *p_sbr; ///< 'showbreak'
+EXTERN int p_sc; ///< 'showcmd'
+EXTERN char *p_sloc; ///< 'showcmdloc'
+EXTERN int p_sft; ///< 'showfulltag'
+EXTERN int p_sm; ///< 'showmatch'
+EXTERN int p_smd; ///< 'showmode'
+EXTERN OptInt p_ss; ///< 'sidescroll'
+EXTERN OptInt p_siso; ///< 'sidescrolloff'
+EXTERN int p_scs; ///< 'smartcase'
+EXTERN int p_si; ///< 'smartindent'
+EXTERN int p_sta; ///< 'smarttab'
+EXTERN OptInt p_sts; ///< 'softtabstop'
+EXTERN int p_sb; ///< 'splitbelow'
+EXTERN char *p_sua; ///< 'suffixesadd'
+EXTERN int p_swf; ///< 'swapfile'
+EXTERN OptInt p_smc; ///< 'synmaxcol'
+EXTERN OptInt p_tpm; ///< 'tabpagemax'
+EXTERN char *p_tal; ///< 'tabline'
+EXTERN char *p_tpf; ///< 'termpastefilter'
+EXTERN unsigned tpf_flags; ///< flags from 'termpastefilter'
+#define TPF_BS 0x001
+#define TPF_HT 0x002
+#define TPF_FF 0x004
+#define TPF_ESC 0x008
+#define TPF_DEL 0x010
+#define TPF_C0 0x020
+#define TPF_C1 0x040
+EXTERN char *p_tfu; ///< 'tagfunc'
+EXTERN char *p_spc; ///< 'spellcapcheck'
+EXTERN char *p_spf; ///< 'spellfile'
+EXTERN char *p_spk; ///< 'splitkeep'
+EXTERN char *p_spl; ///< 'spelllang'
+EXTERN char *p_spo; ///< 'spelloptions'
+EXTERN unsigned spo_flags;
+EXTERN char *p_sps; ///< 'spellsuggest'
+EXTERN int p_spr; ///< 'splitright'
+EXTERN int p_sol; ///< 'startofline'
+EXTERN char *p_su; ///< 'suffixes'
+EXTERN char *p_swb; ///< 'switchbuf'
+EXTERN unsigned swb_flags;
+// Keep in sync with p_swb_values in optionstr.c
+#define SWB_USEOPEN 0x001
+#define SWB_USETAB 0x002
+#define SWB_SPLIT 0x004
+#define SWB_NEWTAB 0x008
+#define SWB_VSPLIT 0x010
+#define SWB_USELAST 0x020
+EXTERN char *p_syn; ///< 'syntax'
+EXTERN OptInt p_ts; ///< 'tabstop'
+EXTERN int p_tbs; ///< 'tagbsearch'
+EXTERN char *p_tc; ///< 'tagcase'
+EXTERN unsigned tc_flags; ///< flags from 'tagcase'
+#define TC_FOLLOWIC 0x01
+#define TC_IGNORE 0x02
+#define TC_MATCH 0x04
+#define TC_FOLLOWSCS 0x08
+#define TC_SMART 0x10
+EXTERN OptInt p_tl; ///< 'taglength'
+EXTERN int p_tr; ///< 'tagrelative'
+EXTERN char *p_tags; ///< 'tags'
+EXTERN int p_tgst; ///< 'tagstack'
+EXTERN int p_tbidi; ///< 'termbidi'
+EXTERN OptInt p_tw; ///< 'textwidth'
+EXTERN int p_to; ///< 'tildeop'
+EXTERN int p_timeout; ///< 'timeout'
+EXTERN OptInt p_tm; ///< 'timeoutlen'
+EXTERN int p_title; ///< 'title'
+EXTERN OptInt p_titlelen; ///< 'titlelen'
+EXTERN char *p_titleold; ///< 'titleold'
+EXTERN char *p_titlestring; ///< 'titlestring'
+EXTERN char *p_tsr; ///< 'thesaurus'
+EXTERN int p_tgc; ///< 'termguicolors'
+EXTERN int p_ttimeout; ///< 'ttimeout'
+EXTERN OptInt p_ttm; ///< 'ttimeoutlen'
+EXTERN char *p_udir; ///< 'undodir'
+EXTERN int p_udf; ///< 'undofile'
+EXTERN OptInt p_ul; ///< 'undolevels'
+EXTERN OptInt p_ur; ///< 'undoreload'
+EXTERN OptInt p_uc; ///< 'updatecount'
+EXTERN OptInt p_ut; ///< 'updatetime'
+EXTERN char *p_shada; ///< 'shada'
+EXTERN char *p_shadafile; ///< 'shadafile'
+EXTERN int p_termsync; ///< 'termsync'
+EXTERN char *p_vsts; ///< 'varsofttabstop'
+EXTERN char *p_vts; ///< 'vartabstop'
+EXTERN char *p_vdir; ///< 'viewdir'
+EXTERN char *p_vop; ///< 'viewoptions'
+EXTERN unsigned vop_flags; ///< uses SSOP_ flags
+EXTERN int p_vb; ///< 'visualbell'
+EXTERN char *p_ve; ///< 'virtualedit'
+EXTERN unsigned ve_flags;
+#define VE_BLOCK 5U // includes "all"
+#define VE_INSERT 6U // includes "all"
+#define VE_ALL 4U
+#define VE_ONEMORE 8U
+#define VE_NONE 16U // "none"
+#define VE_NONEU 32U // "NONE"
+EXTERN OptInt p_verbose; ///< 'verbose'
+#ifdef IN_OPTION_C
+char *p_vfile = ""; ///< used before options are initialized
+#else
+extern char *p_vfile; ///< 'verbosefile'
+#endif
+EXTERN int p_warn; ///< 'warn'
+EXTERN char *p_wop; ///< 'wildoptions'
+EXTERN unsigned wop_flags;
+#define WOP_FUZZY 0x01
+#define WOP_TAGFILE 0x02
+#define WOP_PUM 0x04
+EXTERN OptInt p_window; ///< 'window'
+EXTERN char *p_wak; ///< 'winaltkeys'
+EXTERN char *p_wig; ///< 'wildignore'
+EXTERN char *p_ww; ///< 'whichwrap'
+EXTERN OptInt p_wc; ///< 'wildchar'
+EXTERN OptInt p_wcm; ///< 'wildcharm'
+EXTERN int p_wic; ///< 'wildignorecase'
+EXTERN char *p_wim; ///< 'wildmode'
+EXTERN int p_wmnu; ///< 'wildmenu'
+EXTERN OptInt p_wh; ///< 'winheight'
+EXTERN OptInt p_wmh; ///< 'winminheight'
+EXTERN OptInt p_wmw; ///< 'winminwidth'
+EXTERN OptInt p_wiw; ///< 'winwidth'
+EXTERN OptInt p_wm; ///< 'wrapmargin'
+EXTERN int p_ws; ///< 'wrapscan'
+EXTERN int p_write; ///< 'write'
+EXTERN int p_wa; ///< 'writeany'
+EXTERN int p_wb; ///< 'writebackup'
+EXTERN OptInt p_wd; ///< 'writedelay'
+EXTERN int p_cdh; ///< 'cdhome'
+
+/// "indir" values for buffer-local options.
+/// These need to be defined globally, so that the BV_COUNT can be used with
+/// b_p_script_stx[].
+enum {
+ BV_AI = 0,
+ BV_AR,
+ BV_BH,
+ BV_BKC,
+ BV_BT,
+ BV_EFM,
+ BV_GP,
+ BV_MP,
+ BV_BIN,
+ BV_BL,
+ BV_BOMB,
+ BV_CHANNEL,
+ BV_CI,
+ BV_CIN,
+ BV_CINK,
+ BV_CINO,
+ BV_CINW,
+ BV_CINSD,
+ BV_CM,
+ BV_CMS,
+ BV_COM,
+ BV_CPT,
+ BV_DICT,
+ BV_TSR,
+ BV_CSL,
+ BV_CFU,
+ BV_DEF,
+ BV_INC,
+ BV_EOF,
+ BV_EOL,
+ BV_FIXEOL,
+ BV_EP,
+ BV_ET,
+ BV_FENC,
+ BV_FP,
+ BV_BEXPR,
+ BV_FEX,
+ BV_FF,
+ BV_FLP,
+ BV_FO,
+ BV_FT,
+ BV_IMI,
+ BV_IMS,
+ BV_INDE,
+ BV_INDK,
+ BV_INEX,
+ BV_INF,
+ BV_ISK,
+ BV_KMAP,
+ BV_KP,
+ BV_LISP,
+ BV_LOP,
+ BV_LW,
+ BV_MENC,
+ BV_MA,
+ BV_ML,
+ BV_MOD,
+ BV_MPS,
+ BV_NF,
+ BV_OFU,
+ BV_PATH,
+ BV_PI,
+ BV_QE,
+ BV_RO,
+ BV_SCBK,
+ BV_SI,
+ BV_SMC,
+ BV_SYN,
+ BV_SPC,
+ BV_SPF,
+ BV_SPL,
+ BV_SPO,
+ BV_STS,
+ BV_SUA,
+ BV_SW,
+ BV_SWF,
+ BV_TFU,
+ BV_TSRFU,
+ BV_TAGS,
+ BV_TC,
+ BV_TS,
+ BV_TW,
+ BV_TX,
+ BV_UDF,
+ BV_UL,
+ BV_WM,
+ BV_VSTS,
+ BV_VTS,
+ BV_COUNT, // must be the last one
+};
+
+/// "indir" values for window-local options.
+/// These need to be defined globally, so that the WV_COUNT can be used in the
+/// window structure.
+enum {
+ WV_LIST = 0,
+ WV_ARAB,
+ WV_COCU,
+ WV_COLE,
+ WV_CRBIND,
+ WV_BRI,
+ WV_BRIOPT,
+ WV_DIFF,
+ WV_FDC,
+ WV_FEN,
+ WV_FDI,
+ WV_FDL,
+ WV_FDM,
+ WV_FML,
+ WV_FDN,
+ WV_FDE,
+ WV_FDT,
+ WV_FMR,
+ WV_LBR,
+ WV_NU,
+ WV_RNU,
+ WV_VE,
+ WV_NUW,
+ WV_PVW,
+ WV_RL,
+ WV_RLC,
+ WV_SCBIND,
+ WV_SCROLL,
+ WV_SMS,
+ WV_SISO,
+ WV_SO,
+ WV_SPELL,
+ WV_CUC,
+ WV_CUL,
+ WV_CULOPT,
+ WV_CC,
+ WV_SBR,
+ WV_STC,
+ WV_STL,
+ WV_WFH,
+ WV_WFW,
+ WV_WRAP,
+ WV_SCL,
+ WV_WINHL,
+ WV_LCS,
+ WV_FCS,
+ WV_WINBL,
+ WV_WBR,
+ WV_COUNT, // must be the last one
+};
+
+// Value for b_p_ul indicating the global value must be used.
+#define NO_LOCAL_UNDOLEVEL (-123456)
+
+#define SB_MAX 100000 // Maximum 'scrollback' value.
+
+#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn'
+
+#define TABSTOP_MAX 9999
+
+#define SCL_NO -1 // 'signcolumn' set to "no"
+#define SCL_NUM -2 // 'signcolumn' set to "number"
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 387ccd0888..daaf73d241 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1,2905 +1,10093 @@
--- {
--- {
--- full_name='aleph', abbreviation='al',
--- short_desc="ASCII code of the letter Aleph (Hebrew)",
--- varname='p_aleph', pv_name=nil,
--- type='number', list=nil, scope={'global'},
--- deny_duplicates=nil,
--- enable_if=nil,
--- defaults={condition=nil, if_true=224, if_false=nil},
--- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil,
--- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil,
--- modelineexpr=nil,
--- func=nil,
--- expand=nil, nodefault=nil, no_mkrc=nil,
--- alloced=nil,
--- save_pv_indir=nil,
--- redraw={'curswant'},
--- }
--- }
--- types: bool, number, string
--- lists: (nil), comma, onecomma, flags, flagscomma
--- scopes: global, buffer, window
--- redraw options: statuslines, tabline, current_window, current_window_only,
--- current_buffer, all_windows, curswant
--- defaults: {condition=#if condition, if_true=default, if_false=default}
--- #if condition:
--- string: #ifdef string
--- !string: #ifndef string
--- {string, string}: #if defined(string) && defined(string)
--- {!string, !string}: #if !defined(string) && !defined(string)
-local cstr = function(s)
+--- @class vim.option_meta
+--- @field full_name string
+--- @field desc? string
+--- @field abbreviation? string
+--- @field short_desc? string|fun(): string
+--- @field varname? string
+--- @field pv_name? string
+--- @field type 'bool'|'number'|'string'
+--- @field immutable? boolean
+--- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma'
+--- @field scope vim.option_scope[]
+--- @field deny_duplicates? boolean
+--- @field enable_if? string|false
+--- @field defaults? vim.option_defaults
+--- @field secure? true
+--- @field noglob? true
+--- @field normal_fname_chars? true
+--- @field pri_mkrc? true
+--- @field deny_in_modelines? true
+--- @field normal_dname_chars? true
+--- @field modelineexpr? true
+--- @field func? true
+--- @field expand? string|true
+--- @field nodefault? true
+--- @field no_mkrc? true
+--- @field alloced? true
+--- @field redraw? vim.option_redraw[]
+--- @field cb? string
+--- @field expand_cb? string
+--- @field tags? string[]
+
+--- @class vim.option_defaults
+--- @field condition? string
+--- string: #ifdef string
+--- !string: #ifndef string
+--- @field if_true integer|boolean|string|fun(): string
+--- @field if_false? integer|boolean|string
+--- @field doc? string Default to show in options.txt
+--- @field meta? integer|boolean|string Default to use in Lua meta files
+
+--- @alias vim.option_scope 'global'|'buffer'|'window'
+
+--- @alias vim.option_redraw
+--- |'statuslines'
+--- |'tabline'
+--- |'current_window'
+--- |'current_window_only'
+--- |'current_buffer'
+--- |'all_windows'
+--- |'curswant'
+--- |'ui_option'
+
+--- @param s string
+--- @return string
+local function cstr(s)
return '"' .. s:gsub('["\\]', '\\%0'):gsub('\t', '\\t') .. '"'
end
-local macros=function(s)
+
+--- @param s string
+--- @return fun(): string
+local function macros(s)
return function()
return s
end
end
-local imacros=function(s)
+
+--- @param s string
+--- @return fun(): string
+local function imacros(s)
return function()
return '(intptr_t)' .. s
end
end
-local N_=function(s) -- luacheck: ignore 211 (currently unused)
+
+--- @param s string
+--- @return fun(): string
+local function N_(s) -- luacheck: ignore 211 (currently unused)
return function()
return 'N_(' .. cstr(s) .. ')'
end
end
--- used for 'cinkeys' and 'indentkeys'
-local indentkeys_default = '0{,0},0),0],:,0#,!^F,o,O,e';
+
+-- luacheck: ignore 621
return {
- cstr=cstr,
- options={
- {
- full_name='aleph', abbreviation='al',
- short_desc=N_("ASCII code of the letter Aleph (Hebrew)"),
- type='number', scope={'global'},
- redraw={'curswant'},
- varname='p_aleph',
- defaults={if_true=224}
- },
- {
- full_name='arabic', abbreviation='arab',
- short_desc=N_("Arabic as a default second language"),
- type='bool', scope={'window'},
- redraw={'curswant'},
- defaults={if_true=false}
- },
- {
- full_name='arabicshape', abbreviation='arshape',
- short_desc=N_("do shaping for Arabic characters"),
- type='bool', scope={'global'},
- redraw={'all_windows', 'ui_option'},
+ cstr = cstr,
+ --- @type vim.option_meta[]
+ options = {
+ {
+ abbreviation = 'al',
+ defaults = { if_true = 224 },
+ full_name = 'aleph',
+ scope = { 'global' },
+ short_desc = N_('ASCII code of the letter Aleph (Hebrew)'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'arab',
+ cb = 'did_set_arabic',
+ defaults = { if_true = false },
+ desc = [=[
+ This option can be set to start editing Arabic text.
+ Setting this option will:
+ - Set the 'rightleft' option, unless 'termbidi' is set.
+ - Set the 'arabicshape' option, unless 'termbidi' is set.
+ - Set the 'keymap' option to "arabic"; in Insert mode CTRL-^ toggles
+ between typing English and Arabic key mapping.
+ - Set the 'delcombine' option
- varname='p_arshape',
- defaults={if_true=true}
- },
- {
- full_name='allowrevins', abbreviation='ari',
- short_desc=N_("allow CTRL-_ in Insert and Command-line mode"),
- type='bool', scope={'global'},
- varname='p_ari',
- defaults={if_true=false}
- },
- {
- full_name='ambiwidth', abbreviation='ambw',
- short_desc=N_("what to do with Unicode chars of ambiguous width"),
- type='string', scope={'global'},
- redraw={'all_windows', 'ui_option'},
- varname='p_ambw',
- defaults={if_true="single"}
- },
- {
- full_name='autochdir', abbreviation='acd',
- short_desc=N_("change directory to the file in the current window"),
- type='bool', scope={'global'},
- varname='p_acd',
- defaults={if_true=false}
- },
- {
- full_name='autoindent', abbreviation='ai',
- short_desc=N_("take indent for new line from previous line"),
- type='bool', scope={'buffer'},
- varname='p_ai',
- defaults={if_true=true}
- },
- {
- full_name='autoread', abbreviation='ar',
- short_desc=N_("autom. read file when changed outside of Vim"),
- type='bool', scope={'global', 'buffer'},
- varname='p_ar',
- defaults={if_true=true}
- },
- {
- full_name='autowrite', abbreviation='aw',
- short_desc=N_("automatically write file if changed"),
- type='bool', scope={'global'},
- varname='p_aw',
- defaults={if_true=false}
- },
- {
- full_name='autowriteall', abbreviation='awa',
- short_desc=N_("as 'autowrite', but works with more commands"),
- type='bool', scope={'global'},
- varname='p_awa',
- defaults={if_true=false}
- },
- {
- full_name='background', abbreviation='bg',
- short_desc=N_("\"dark\" or \"light\", used for highlight colors"),
- type='string', scope={'global'},
- varname='p_bg',
- defaults={if_true="dark"}
- },
- {
- full_name='backspace', abbreviation='bs',
- short_desc=N_("how backspace works at start of line"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_bs',
- defaults={if_true="indent,eol,start"}
- },
- {
- full_name='backup', abbreviation='bk',
- short_desc=N_("keep backup file after overwriting a file"),
- type='bool', scope={'global'},
- varname='p_bk',
- defaults={if_true=false}
- },
- {
- full_name='backupcopy', abbreviation='bkc',
- short_desc=N_("make backup as a copy, don't rename the file"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- varname='p_bkc',
- defaults={
- condition='UNIX',
- if_true="auto",
- if_false="auto"
+ Resetting this option will:
+ - Reset the 'rightleft' option.
+ - Disable the use of 'keymap' (without changing its value).
+ Note that 'arabicshape' and 'delcombine' are not reset (it is a global
+ option).
+ Also see |arabic.txt|.
+ ]=],
+ full_name = 'arabic',
+ redraw = { 'curswant' },
+ scope = { 'window' },
+ short_desc = N_('Arabic as a default second language'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'arshape',
+ defaults = { if_true = true },
+ desc = [=[
+ When on and 'termbidi' is off, the required visual character
+ corrections that need to take place for displaying the Arabic language
+ take effect. Shaping, in essence, gets enabled; the term is a broad
+ one which encompasses:
+ a) the changing/morphing of characters based on their location
+ within a word (initial, medial, final and stand-alone).
+ b) the enabling of the ability to compose characters
+ c) the enabling of the required combining of some characters
+ When disabled the display shows each character's true stand-alone
+ form.
+ Arabic is a complex language which requires other settings, for
+ further details see |arabic.txt|.
+ ]=],
+ full_name = 'arabicshape',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('do shaping for Arabic characters'),
+ type = 'bool',
+ varname = 'p_arshape',
+ },
+ {
+ abbreviation = 'ari',
+ defaults = { if_true = false },
+ desc = [=[
+ Allow CTRL-_ in Insert and Command-line mode. This is default off, to
+ avoid that users that accidentally type CTRL-_ instead of SHIFT-_ get
+ into reverse Insert mode, and don't know how to get out. See
+ 'revins'.
+ ]=],
+ full_name = 'allowrevins',
+ scope = { 'global' },
+ short_desc = N_('allow CTRL-_ in Insert and Command-line mode'),
+ type = 'bool',
+ varname = 'p_ari',
+ },
+ {
+ abbreviation = 'ambw',
+ cb = 'did_set_ambiwidth',
+ defaults = { if_true = 'single' },
+ desc = [=[
+ Tells Vim what to do with characters with East Asian Width Class
+ Ambiguous (such as Euro, Registered Sign, Copyright Sign, Greek
+ letters, Cyrillic letters).
+
+ There are currently two possible values:
+ "single": Use the same width as characters in US-ASCII. This is
+ expected by most users.
+ "double": Use twice the width of ASCII characters.
+ *E834* *E835*
+ The value "double" cannot be used if 'listchars' or 'fillchars'
+ contains a character that would be double width. These errors may
+ also be given when calling setcellwidths().
+
+ The values are overruled for characters specified with
+ |setcellwidths()|.
+
+ There are a number of CJK fonts for which the width of glyphs for
+ those characters are solely based on how many octets they take in
+ legacy/traditional CJK encodings. In those encodings, Euro,
+ Registered sign, Greek/Cyrillic letters are represented by two octets,
+ therefore those fonts have "wide" glyphs for them. This is also
+ true of some line drawing characters used to make tables in text
+ file. Therefore, when a CJK font is used for GUI Vim or
+ Vim is running inside a terminal (emulators) that uses a CJK font
+ (or Vim is run inside an xterm invoked with "-cjkwidth" option.),
+ this option should be set to "double" to match the width perceived
+ by Vim with the width of glyphs in the font. Perhaps it also has
+ to be set to "double" under CJK MS-Windows when the system locale is
+ set to one of CJK locales. See Unicode Standard Annex #11
+ (https://www.unicode.org/reports/tr11).
+ ]=],
+ expand_cb = 'expand_set_ambiwidth',
+ full_name = 'ambiwidth',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('what to do with Unicode chars of ambiguous width'),
+ type = 'string',
+ varname = 'p_ambw',
+ },
+ {
+ abbreviation = 'acd',
+ cb = 'did_set_autochdir',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, Vim will change the current working directory whenever you
+ open a file, switch buffers, delete a buffer or open/close a window.
+ It will change to the directory containing the file which was opened
+ or selected. When a buffer has no name it also has no directory, thus
+ the current directory won't change when navigating to it.
+ Note: When this option is on some plugins may not work.
+ ]=],
+ full_name = 'autochdir',
+ scope = { 'global' },
+ short_desc = N_('change directory to the file in the current window'),
+ type = 'bool',
+ varname = 'p_acd',
+ },
+ {
+ abbreviation = 'ai',
+ defaults = { if_true = true },
+ desc = [=[
+ Copy indent from current line when starting a new line (typing <CR>
+ in Insert mode or when using the "o" or "O" command). If you do not
+ type anything on the new line except <BS> or CTRL-D and then type
+ <Esc>, CTRL-O or <CR>, the indent is deleted again. Moving the cursor
+ to another line has the same effect, unless the 'I' flag is included
+ in 'cpoptions'.
+ When autoindent is on, formatting (with the "gq" command or when you
+ reach 'textwidth' in Insert mode) uses the indentation of the first
+ line.
+ When 'smartindent' or 'cindent' is on the indent is changed in
+ a different way.
+ ]=],
+ full_name = 'autoindent',
+ scope = { 'buffer' },
+ short_desc = N_('take indent for new line from previous line'),
+ type = 'bool',
+ varname = 'p_ai',
+ },
+ {
+ abbreviation = 'ar',
+ defaults = { if_true = true },
+ desc = [=[
+ When a file has been detected to have been changed outside of Vim and
+ it has not been changed inside of Vim, automatically read it again.
+ When the file has been deleted this is not done, so you have the text
+ from before it was deleted. When it appears again then it is read.
+ |timestamp|
+ If this option has a local value, use this command to switch back to
+ using the global value: >
+ :set autoread<
+ <
+ ]=],
+ full_name = 'autoread',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('autom. read file when changed outside of Vim'),
+ type = 'bool',
+ varname = 'p_ar',
+ },
+ {
+ abbreviation = 'aw',
+ defaults = { if_true = false },
+ desc = [=[
+ Write the contents of the file, if it has been modified, on each
+ `:next`, `:rewind`, `:last`, `:first`, `:previous`, `:stop`,
+ `:suspend`, `:tag`, `:!`, `:make`, CTRL-] and CTRL-^ command; and when
+ a `:buffer`, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one
+ to another file.
+ A buffer is not written if it becomes hidden, e.g. when 'bufhidden' is
+ set to "hide" and `:next` is used.
+ Note that for some commands the 'autowrite' option is not used, see
+ 'autowriteall' for that.
+ Some buffers will not be written, specifically when 'buftype' is
+ "nowrite", "nofile", "terminal" or "prompt".
+ USE WITH CARE: If you make temporary changes to a buffer that you
+ don't want to be saved this option may cause it to be saved anyway.
+ Renaming the buffer with ":file {name}" may help avoid this.
+ ]=],
+ full_name = 'autowrite',
+ scope = { 'global' },
+ short_desc = N_('automatically write file if changed'),
+ type = 'bool',
+ varname = 'p_aw',
+ },
+ {
+ abbreviation = 'awa',
+ defaults = { if_true = false },
+ desc = [=[
+ Like 'autowrite', but also used for commands ":edit", ":enew", ":quit",
+ ":qall", ":exit", ":xit", ":recover" and closing the Vim window.
+ Setting this option also implies that Vim behaves like 'autowrite' has
+ been set.
+ ]=],
+ full_name = 'autowriteall',
+ scope = { 'global' },
+ short_desc = N_("as 'autowrite', but works with more commands"),
+ type = 'bool',
+ varname = 'p_awa',
+ },
+ {
+ abbreviation = 'bg',
+ cb = 'did_set_background',
+ defaults = { if_true = 'dark' },
+ desc = [=[
+ When set to "dark" or "light", adjusts the default color groups for
+ that background type. The |TUI| or other UI sets this on startup
+ (triggering |OptionSet|) if it can detect the background color.
+
+ This option does NOT change the background color, it tells Nvim what
+ the "inherited" (terminal/GUI) background looks like.
+ See |:hi-normal| if you want to set the background color explicitly.
+ *g:colors_name*
+ When a color scheme is loaded (the "g:colors_name" variable is set)
+ setting 'background' will cause the color scheme to be reloaded. If
+ the color scheme adjusts to the value of 'background' this will work.
+ However, if the color scheme sets 'background' itself the effect may
+ be undone. First delete the "g:colors_name" variable when needed.
+
+ Normally this option would be set in the vimrc file. Possibly
+ depending on the terminal name. Example: >
+ :if $TERM ==# "xterm"
+ : set background=dark
+ :endif
+ < When this option is set, the default settings for the highlight groups
+ will change. To use other settings, place ":highlight" commands AFTER
+ the setting of the 'background' option.
+ This option is also used in the "$VIMRUNTIME/syntax/syntax.vim" file
+ to select the colors for syntax highlighting. After changing this
+ option, you must load syntax.vim again to see the result. This can be
+ done with ":syntax on".
+ ]=],
+ expand_cb = 'expand_set_background',
+ full_name = 'background',
+ scope = { 'global' },
+ short_desc = N_('"dark" or "light", used for highlight colors'),
+ type = 'string',
+ varname = 'p_bg',
+ },
+ {
+ abbreviation = 'bs',
+ cb = 'did_set_backspace',
+ defaults = { if_true = 'indent,eol,start' },
+ deny_duplicates = true,
+ desc = [=[
+ Influences the working of <BS>, <Del>, CTRL-W and CTRL-U in Insert
+ mode. This is a list of items, separated by commas. Each item allows
+ a way to backspace over something:
+ value effect ~
+ indent allow backspacing over autoindent
+ eol allow backspacing over line breaks (join lines)
+ start allow backspacing over the start of insert; CTRL-W and CTRL-U
+ stop once at the start of insert.
+ nostop like start, except CTRL-W and CTRL-U do not stop at the start of
+ insert.
+
+ When the value is empty, Vi compatible backspacing is used, none of
+ the ways mentioned for the items above are possible.
+ ]=],
+ expand_cb = 'expand_set_backspace',
+ full_name = 'backspace',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('how backspace works at start of line'),
+ type = 'string',
+ varname = 'p_bs',
+ },
+ {
+ abbreviation = 'bk',
+ defaults = { if_true = false },
+ desc = [=[
+ Make a backup before overwriting a file. Leave it around after the
+ file has been successfully written. If you do not want to keep the
+ backup file, but you do want a backup while the file is being
+ written, reset this option and set the 'writebackup' option (this is
+ the default). If you do not want a backup file at all reset both
+ options (use this if your file system is almost full). See the
+ |backup-table| for more explanations.
+ When the 'backupskip' pattern matches, a backup is not made anyway.
+ When 'patchmode' is set, the backup may be renamed to become the
+ oldest version of a file.
+ ]=],
+ full_name = 'backup',
+ scope = { 'global' },
+ short_desc = N_('keep backup file after overwriting a file'),
+ type = 'bool',
+ varname = 'p_bk',
+ },
+ {
+ abbreviation = 'bkc',
+ cb = 'did_set_backupcopy',
+ defaults = { condition = 'UNIX', if_false = 'auto', if_true = 'auto' },
+ deny_duplicates = true,
+ desc = [=[
+ When writing a file and a backup is made, this option tells how it's
+ done. This is a comma-separated list of words.
+
+ The main values are:
+ "yes" make a copy of the file and overwrite the original one
+ "no" rename the file and write a new one
+ "auto" one of the previous, what works best
+
+ Extra values that can be combined with the ones above are:
+ "breaksymlink" always break symlinks when writing
+ "breakhardlink" always break hardlinks when writing
+
+ Making a copy and overwriting the original file:
+ - Takes extra time to copy the file.
+ + When the file has special attributes, is a (hard/symbolic) link or
+ has a resource fork, all this is preserved.
+ - When the file is a link the backup will have the name of the link,
+ not of the real file.
+
+ Renaming the file and writing a new one:
+ + It's fast.
+ - Sometimes not all attributes of the file can be copied to the new
+ file.
+ - When the file is a link the new file will not be a link.
+
+ The "auto" value is the middle way: When Vim sees that renaming the
+ file is possible without side effects (the attributes can be passed on
+ and the file is not a link) that is used. When problems are expected,
+ a copy will be made.
+
+ The "breaksymlink" and "breakhardlink" values can be used in
+ combination with any of "yes", "no" and "auto". When included, they
+ force Vim to always break either symbolic or hard links by doing
+ exactly what the "no" option does, renaming the original file to
+ become the backup and writing a new file in its place. This can be
+ useful for example in source trees where all the files are symbolic or
+ hard links and any changes should stay in the local source tree, not
+ be propagated back to the original source.
+ *crontab*
+ One situation where "no" and "auto" will cause problems: A program
+ that opens a file, invokes Vim to edit that file, and then tests if
+ the open file was changed (through the file descriptor) will check the
+ backup file instead of the newly created file. "crontab -e" is an
+ example.
+
+ When a copy is made, the original file is truncated and then filled
+ with the new text. This means that protection bits, owner and
+ symbolic links of the original file are unmodified. The backup file,
+ however, is a new file, owned by the user who edited the file. The
+ group of the backup is set to the group of the original file. If this
+ fails, the protection bits for the group are made the same as for
+ others.
+
+ When the file is renamed, this is the other way around: The backup has
+ the same attributes of the original file, and the newly written file
+ is owned by the current user. When the file was a (hard/symbolic)
+ link, the new file will not! That's why the "auto" value doesn't
+ rename when the file is a link. The owner and group of the newly
+ written file will be set to the same ones as the original file, but
+ the system may refuse to do this. In that case the "auto" value will
+ again not rename the file.
+ ]=],
+ expand_cb = 'expand_set_backupcopy',
+ full_name = 'backupcopy',
+ list = 'onecomma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_("make backup as a copy, don't rename the file"),
+ type = 'string',
+ varname = 'p_bkc',
+ },
+ {
+ abbreviation = 'bdir',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of directories for the backup file, separated with commas.
+ - The backup file will be created in the first directory in the list
+ where this is possible. If none of the directories exist Nvim will
+ attempt to create the last directory in the list.
+ - Empty means that no backup file will be created ('patchmode' is
+ impossible!). Writing may fail because of this.
+ - A directory "." means to put the backup file in the same directory
+ as the edited file.
+ - A directory starting with "./" (or ".\" for MS-Windows) means to put
+ the backup file relative to where the edited file is. The leading
+ "." is replaced with the path name of the edited file.
+ ("." inside a directory name has no special meaning).
+ - Spaces after the comma are ignored, other spaces are considered part
+ of the directory name. To have a space at the start of a directory
+ name, precede it with a backslash.
+ - To include a comma in a directory name precede it with a backslash.
+ - A directory name may end in an '/'.
+ - For Unix and Win32, if a directory ends in two path separators "//",
+ the swap file name will be built from the complete path to the file
+ with all path separators changed to percent '%' signs. This will
+ ensure file name uniqueness in the backup directory.
+ On Win32, it is also possible to end with "\\". However, When a
+ separating comma is following, you must use "//", since "\\" will
+ include the comma in the file name. Therefore it is recommended to
+ use '//', instead of '\\'.
+ - Environment variables are expanded |:set_env|.
+ - Careful with '\' characters, type one before a space, type two to
+ get one in the option (see |option-backslash|), for example: >
+ :set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+ <
+ See also 'backup' and 'writebackup' options.
+ If you want to hide your backup files on Unix, consider this value: >
+ :set backupdir=./.backup,~/.backup,.,/tmp
+ < You must create a ".backup" directory in each directory and in your
+ home directory for this to work properly.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ directories from the list. This avoids problems when a future version
+ uses another default.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'backupdir',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories for the backup file'),
+ type = 'string',
+ varname = 'p_bdir',
+ },
+ {
+ abbreviation = 'bex',
+ cb = 'did_set_backupext_or_patchmode',
+ defaults = { if_true = '~' },
+ desc = [=[
+ String which is appended to a file name to make the name of the
+ backup file. The default is quite unusual, because this avoids
+ accidentally overwriting existing files with a backup file. You might
+ prefer using ".bak", but make sure that you don't have files with
+ ".bak" that you want to keep.
+ Only normal file name characters can be used; `/\*?[|<>` are illegal.
+
+ If you like to keep a lot of backups, you could use a BufWritePre
+ autocommand to change 'backupext' just before writing the file to
+ include a timestamp. >
+ :au BufWritePre * let &bex = '-' .. strftime("%Y%b%d%X") .. '~'
+ < Use 'backupdir' to put the backup in a different directory.
+ ]=],
+ full_name = 'backupext',
+ normal_fname_chars = true,
+ scope = { 'global' },
+ short_desc = N_('extension used for the backup file'),
+ tags = { 'E589' },
+ type = 'string',
+ varname = 'p_bex',
+ },
+ {
+ abbreviation = 'bsk',
+ defaults = {
+ if_true = '',
+ doc = [["$TMPDIR/*,$TMP/*,$TEMP/*"
+ Unix: "/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*"
+ Mac: "/private/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*"]],
+ meta = '/tmp/*',
},
- },
- {
- full_name='backupdir', abbreviation='bdir',
- short_desc=N_("list of directories for the backup file"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_bdir',
- defaults={if_true=''}
- },
- {
- full_name='backupext', abbreviation='bex',
- short_desc=N_("extension used for the backup file"),
- type='string', scope={'global'},
- normal_fname_chars=true,
- varname='p_bex',
- defaults={if_true="~"}
- },
- {
- full_name='backupskip', abbreviation='bsk',
- short_desc=N_("no backup for files that match these patterns"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_bsk',
- defaults={if_true=""}
- },
- {
- full_name='belloff', abbreviation='bo',
- short_desc=N_("do not ring the bell for these reasons"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- varname='p_bo',
- defaults={if_true="all"}
- },
- {
- full_name='binary', abbreviation='bin',
- short_desc=N_("read/write/edit file in binary mode"),
- type='bool', scope={'buffer'},
- redraw={'statuslines'},
- varname='p_bin',
- defaults={if_true=false}
- },
- {
- full_name='bomb',
- short_desc=N_("a Byte Order Mark to the file"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_bomb',
- defaults={if_true=false}
- },
- {
- full_name='breakat', abbreviation='brk',
- short_desc=N_("characters that may cause a line break"),
- type='string', list='flags', scope={'global'},
- redraw={'all_windows'},
- varname='p_breakat',
- defaults={if_true=" \t!@*-+;:,./?"}
- },
- {
- full_name='breakindent', abbreviation='bri',
- short_desc=N_("wrapped line repeats indent"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='breakindentopt', abbreviation='briopt',
- short_desc=N_("settings for 'breakindent'"),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_buffer'},
- defaults={if_true=""},
- },
- {
- full_name='browsedir', abbreviation='bsdir',
- short_desc=N_("which directory to start browsing in"),
- type='string', scope={'global'},
- enable_if=false,
- },
- {
- full_name='bufhidden', abbreviation='bh',
- short_desc=N_("what to do when buffer is no longer in window"),
- type='string', scope={'buffer'},
- noglob=true,
- alloced=true,
- varname='p_bh',
- defaults={if_true=""}
- },
- {
- full_name='buflisted', abbreviation='bl',
- short_desc=N_("whether the buffer shows up in the buffer list"),
- type='bool', scope={'buffer'},
- noglob=true,
- varname='p_bl',
- defaults={if_true=1}
- },
- {
- full_name='buftype', abbreviation='bt',
- short_desc=N_("special type of buffer"),
- type='string', scope={'buffer'},
- noglob=true,
- alloced=true,
- varname='p_bt',
- defaults={if_true=""}
- },
- {
- full_name='casemap', abbreviation='cmp',
- short_desc=N_("specifies how case of letters is changed"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_cmp',
- defaults={if_true="internal,keepascii"}
- },
- {
- full_name='cdhome', abbreviation='cdh',
- short_desc=N_(":cd without argument goes to the home directory"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_cdh',
- defaults={if_true=false}
- },
- {
- full_name='cdpath', abbreviation='cd',
- short_desc=N_("list of directories searched with \":cd\""),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- expand=true,
- secure=true,
- varname='p_cdpath',
- defaults={if_true=",,"}
- },
- {
- full_name='cedit',
- short_desc=N_("used to open the command-line window"),
- type='string', scope={'global'},
- varname='p_cedit',
- defaults={if_true=macros('CTRL_F_STR')}
- },
- {
- full_name='channel',
- short_desc=N_("Channel connected to the buffer"),
- type='number', scope={'buffer'},
- no_mkrc=true,
- nodefault=true,
- varname='p_channel',
- defaults={if_true=0}
- },
- {
- full_name='charconvert', abbreviation='ccv',
- short_desc=N_("expression for character encoding conversion"),
- type='string', scope={'global'},
- secure=true,
- varname='p_ccv',
- defaults={if_true=""}
- },
- {
- full_name='cindent', abbreviation='cin',
- short_desc=N_("do C program indenting"),
- type='bool', scope={'buffer'},
- varname='p_cin',
- defaults={if_true=false}
- },
- {
- full_name='cinkeys', abbreviation='cink',
- short_desc=N_("keys that trigger indent when 'cindent' is set"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cink',
- defaults={if_true=indentkeys_default}
- },
- {
- full_name='cinoptions', abbreviation='cino',
- short_desc=N_("how to do indenting when 'cindent' is set"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cino',
- defaults={if_true=""}
- },
- {
- full_name='cinwords', abbreviation='cinw',
- short_desc=N_("words where 'si' and 'cin' add an indent"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cinw',
- defaults={if_true="if,else,while,do,for,switch"}
- },
- {
- full_name='cinscopedecls', abbreviation='cinsd',
- short_desc=N_("words that are recognized by 'cino-g'"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cinsd',
- defaults={if_true="public,protected,private"}
- },
- {
- full_name='clipboard', abbreviation='cb',
- short_desc=N_("use the clipboard as the unnamed register"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_cb',
- defaults={if_true=""}
- },
- {
- full_name='cmdheight', abbreviation='ch',
- short_desc=N_("number of lines to use for the command-line"),
- type='number', scope={'global'},
- redraw={'all_windows'},
- varname='p_ch',
- defaults={if_true=1}
- },
- {
- full_name='cmdwinheight', abbreviation='cwh',
- short_desc=N_("height of the command-line window"),
- type='number', scope={'global'},
- varname='p_cwh',
- defaults={if_true=7}
- },
- {
- full_name='colorcolumn', abbreviation='cc',
- short_desc=N_("columns to highlight"),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- redraw={'current_window'},
- defaults={if_true=""}
- },
- {
- full_name='columns', abbreviation='co',
- short_desc=N_("number of columns in the display"),
- type='number', scope={'global'},
- no_mkrc=true,
- varname='p_columns',
- defaults={if_true=macros('DFLT_COLS')}
- },
- {
- full_name='comments', abbreviation='com',
- short_desc=N_("patterns that can start a comment line"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- redraw={'curswant'},
- varname='p_com',
- defaults={if_true="s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-"}
- },
- {
- full_name='commentstring', abbreviation='cms',
- short_desc=N_("template for comments; used for fold marker"),
- type='string', scope={'buffer'},
- alloced=true,
- redraw={'curswant'},
- varname='p_cms',
- defaults={if_true="/*%s*/"}
- },
- {
- full_name='compatible', abbreviation='cp',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- -- pri_mkrc isn't needed here, optval_default()
- -- always returns TRUE for 'compatible'
- defaults={if_true=false}
- },
- {
- full_name='complete', abbreviation='cpt',
- short_desc=N_("specify how Insert mode completion works"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cpt',
- defaults={if_true=".,w,b,u,t"}
- },
- {
- full_name='concealcursor', abbreviation='cocu',
- short_desc=N_("whether concealable text is hidden in cursor line"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true=""}
- },
- {
- full_name='conceallevel', abbreviation='cole',
- short_desc=N_("whether concealable text is shown or hidden"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=0}
- },
- {
- full_name='completefunc', abbreviation='cfu',
- short_desc=N_("function to be used for Insert mode completion"),
- type='string', scope={'buffer'},
- secure=true,
- alloced=true,
- func=true,
- varname='p_cfu',
- defaults={if_true=""}
- },
- {
- full_name='completeopt', abbreviation='cot',
- short_desc=N_("options for Insert mode completion"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_cot',
- defaults={if_true="menu,preview"}
- },
- {
- full_name='completeslash', abbreviation='csl',
- type='string', scope={'buffer'},
- varname='p_csl',
- enable_if='BACKSLASH_IN_FILENAME',
- defaults={if_true=""}
- },
- {
- full_name='confirm', abbreviation='cf',
- short_desc=N_("ask what to do about unsaved/read-only files"),
- type='bool', scope={'global'},
- varname='p_confirm',
- defaults={if_true=false}
- },
- {
- full_name='copyindent', abbreviation='ci',
- short_desc=N_("make 'autoindent' use existing indent structure"),
- type='bool', scope={'buffer'},
- varname='p_ci',
- defaults={if_true=false}
- },
- {
- full_name='cpoptions', abbreviation='cpo',
- short_desc=N_("flags for Vi-compatible behavior"),
- type='string', list='flags', scope={'global'},
- redraw={'all_windows'},
- varname='p_cpo',
- defaults={if_true=macros('CPO_VIM')}
- },
- {
- full_name='cursorbind', abbreviation='crb',
- short_desc=N_("move cursor in window as it moves in other windows"),
- type='bool', scope={'window'},
- pv_name='p_crbind',
- defaults={if_true=false}
- },
- {
- full_name='cursorcolumn', abbreviation='cuc',
- short_desc=N_("highlight the screen column of the cursor"),
- type='bool', scope={'window'},
- redraw={'current_window_only'},
- defaults={if_true=false}
- },
- {
- full_name='cursorline', abbreviation='cul',
- short_desc=N_("highlight the screen line of the cursor"),
- type='bool', scope={'window'},
- redraw={'current_window_only'},
- defaults={if_true=false}
- },
- {
- full_name='cursorlineopt', abbreviation='culopt',
- short_desc=N_("settings for 'cursorline'"),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- redraw={'current_window_only'},
- defaults={if_true="both"}
- },
- {
- full_name='debug',
- short_desc=N_("to \"msg\" to see all error messages"),
- type='string', scope={'global'},
- varname='p_debug',
- defaults={if_true=""}
- },
- {
- full_name='define', abbreviation='def',
- short_desc=N_("pattern to be used to find a macro definition"),
- type='string', scope={'global', 'buffer'},
- alloced=true,
- redraw={'curswant'},
- varname='p_def',
- defaults={if_true="^\\s*#\\s*define"}
- },
- {
- full_name='delcombine', abbreviation='deco',
- short_desc=N_("delete combining characters on their own"),
- type='bool', scope={'global'},
- varname='p_deco',
- defaults={if_true=false}
- },
- {
- full_name='dictionary', abbreviation='dict',
- short_desc=N_("list of file names used for keyword completion"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- normal_dname_chars=true,
- expand=true,
- varname='p_dict',
- defaults={if_true=""}
- },
- {
- full_name='diff',
- short_desc=N_("diff mode for the current window"),
- type='bool', scope={'window'},
- noglob=true,
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='diffexpr', abbreviation='dex',
- short_desc=N_("expression used to obtain a diff file"),
- type='string', scope={'global'},
- secure=true,
- redraw={'curswant'},
- varname='p_dex',
- defaults={if_true=""}
- },
- {
- full_name='diffopt', abbreviation='dip',
- short_desc=N_("options for using diff mode"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- varname='p_dip',
- defaults={if_true="internal,filler,closeoff"}
- },
- {
- full_name='digraph', abbreviation='dg',
- short_desc=N_("enable the entering of digraphs in Insert mode"),
- type='bool', scope={'global'},
- varname='p_dg',
- defaults={if_true=false}
- },
- {
- full_name='directory', abbreviation='dir',
- short_desc=N_("list of directory names for the swap file"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_dir',
- defaults={if_true=''}
- },
- {
- full_name='display', abbreviation='dy',
- short_desc=N_("list of flags for how to display text"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'all_windows'},
- varname='p_dy',
- defaults={if_true="lastline"}
- },
- {
- full_name='eadirection', abbreviation='ead',
- short_desc=N_("in which direction 'equalalways' works"),
- type='string', scope={'global'},
- varname='p_ead',
- defaults={if_true="both"}
- },
- {
- full_name='edcompatible', abbreviation='ed',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- defaults={if_true=false}
- },
- {
- full_name='emoji', abbreviation='emo',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- redraw={'all_windows', 'ui_option'},
- varname='p_emoji',
- defaults={if_true=true}
- },
- {
- full_name='encoding', abbreviation='enc',
- short_desc=N_("encoding used internally"),
- type='string', scope={'global'},
- deny_in_modelines=true,
- varname='p_enc',
- defaults={if_true=macros('ENC_DFLT')}
- },
- {
- full_name='endoffile', abbreviation='eof',
- short_desc=N_("write CTRL-Z for last line in file"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_eof',
- defaults={if_true=false}
- },
- {
- full_name='endofline', abbreviation='eol',
- short_desc=N_("write <EOL> for last line in file"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_eol',
- defaults={if_true=true}
- },
- {
- full_name='equalalways', abbreviation='ea',
- short_desc=N_("windows are automatically made the same size"),
- type='bool', scope={'global'},
- varname='p_ea',
- defaults={if_true=true}
- },
- {
- full_name='equalprg', abbreviation='ep',
- short_desc=N_("external program to use for \"=\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_ep',
- defaults={if_true=""}
- },
- {
- full_name='errorbells', abbreviation='eb',
- short_desc=N_("ring the bell for error messages"),
- type='bool', scope={'global'},
- varname='p_eb',
- defaults={if_true=false}
- },
- {
- full_name='errorfile', abbreviation='ef',
- short_desc=N_("name of the errorfile for the QuickFix mode"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_ef',
- defaults={if_true=macros('DFLT_ERRORFILE')}
- },
- {
- full_name='errorformat', abbreviation='efm',
- short_desc=N_("description of the lines in the error file"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- varname='p_efm',
- defaults={if_true=macros('DFLT_EFM')}
- },
- {
- full_name='eventignore', abbreviation='ei',
- short_desc=N_("autocommand events that are ignored"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_ei',
- defaults={if_true=""}
- },
- {
- full_name='expandtab', abbreviation='et',
- short_desc=N_("use spaces when <Tab> is inserted"),
- type='bool', scope={'buffer'},
- varname='p_et',
- defaults={if_true=false}
- },
- {
- full_name='exrc', abbreviation='ex',
- short_desc=N_("read .nvimrc and .exrc in the current directory"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_exrc',
- defaults={if_true=false}
- },
- {
- full_name='fileencoding', abbreviation='fenc',
- short_desc=N_("file encoding for multi-byte text"),
- type='string', scope={'buffer'},
- no_mkrc=true,
- alloced=true,
- redraw={'statuslines', 'current_buffer'},
- varname='p_fenc',
- defaults={if_true=""}
- },
- {
- full_name='fileencodings', abbreviation='fencs',
- short_desc=N_("automatically detected character encodings"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_fencs',
- defaults={if_true="ucs-bom,utf-8,default,latin1"}
- },
- {
- full_name='fileformat', abbreviation='ff',
- short_desc=N_("file format used for file I/O"),
- type='string', scope={'buffer'},
- no_mkrc=true,
- alloced=true,
- redraw={'curswant', 'statuslines'},
- varname='p_ff',
- defaults={if_true=macros('DFLT_FF')}
- },
- {
- full_name='fileformats', abbreviation='ffs',
- short_desc=N_("automatically detected values for 'fileformat'"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_ffs',
- defaults={if_true=macros('DFLT_FFS_VIM')}
- },
- {
- full_name='fileignorecase', abbreviation='fic',
- short_desc=N_("ignore case when using file names"),
- type='bool', scope={'global'},
- varname='p_fic',
- defaults={
- condition='CASE_INSENSITIVE_FILENAME',
- if_true=true,
- if_false=false,
- }
- },
- {
- full_name='filetype', abbreviation='ft',
- short_desc=N_("type of file, used for autocommands"),
- type='string', scope={'buffer'},
- noglob=true,
- normal_fname_chars=true,
- alloced=true,
- expand=true,
- varname='p_ft',
- defaults={if_true=""}
- },
- {
- full_name='fillchars', abbreviation='fcs',
- short_desc=N_("characters to use for displaying special items"),
- type='string', list='onecomma', scope={'global', 'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- varname='p_fcs',
- defaults={if_true=''}
- },
- {
- full_name='fixendofline', abbreviation='fixeol',
- short_desc=N_("make sure last line in file has <EOL>"),
- type='bool', scope={'buffer'},
- redraw={'statuslines'},
- varname='p_fixeol',
- defaults={if_true=true}
- },
- {
- full_name='foldclose', abbreviation='fcl',
- short_desc=N_("close a fold when the cursor leaves it"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'current_window'},
- varname='p_fcl',
- defaults={if_true=""}
- },
- {
- full_name='foldcolumn', abbreviation='fdc',
- short_desc=N_("width of the column used to indicate folds"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="0"}
- },
- {
- full_name='foldenable', abbreviation='fen',
- short_desc=N_("set to display all folds open"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=true}
- },
- {
- full_name='foldexpr', abbreviation='fde',
- short_desc=N_("expression used when 'foldmethod' is \"expr\""),
- type='string', scope={'window'},
- modelineexpr=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="0"}
- },
- {
- full_name='foldignore', abbreviation='fdi',
- short_desc=N_("ignore lines when 'foldmethod' is \"indent\""),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="#"}
- },
- {
- full_name='foldlevel', abbreviation='fdl',
- short_desc=N_("close folds with a level higher than this"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=0}
- },
- {
- full_name='foldlevelstart', abbreviation='fdls',
- short_desc=N_("'foldlevel' when starting to edit a file"),
- type='number', scope={'global'},
- redraw={'curswant'},
- varname='p_fdls',
- defaults={if_true=-1}
- },
- {
- full_name='foldmarker', abbreviation='fmr',
- short_desc=N_("markers used when 'foldmethod' is \"marker\""),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="{{{,}}}"}
- },
- {
- full_name='foldmethod', abbreviation='fdm',
- short_desc=N_("folding type"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="manual"}
- },
- {
- full_name='foldminlines', abbreviation='fml',
- short_desc=N_("minimum number of lines for a fold to be closed"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=1}
- },
- {
- full_name='foldnestmax', abbreviation='fdn',
- short_desc=N_("maximum fold depth"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=20}
- },
- {
- full_name='foldopen', abbreviation='fdo',
- short_desc=N_("for which commands a fold will be opened"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'curswant'},
- varname='p_fdo',
- defaults={if_true="block,hor,mark,percent,quickfix,search,tag,undo"}
- },
- {
- full_name='foldtext', abbreviation='fdt',
- short_desc=N_("expression used to display for a closed fold"),
- type='string', scope={'window'},
- modelineexpr=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="foldtext()"}
- },
- {
- full_name='formatexpr', abbreviation='fex',
- short_desc=N_("expression used with \"gq\" command"),
- type='string', scope={'buffer'},
- modelineexpr=true,
- alloced=true,
- varname='p_fex',
- defaults={if_true=""}
- },
- {
- full_name='formatoptions', abbreviation='fo',
- short_desc=N_("how automatic formatting is to be done"),
- type='string', list='flags', scope={'buffer'},
- alloced=true,
- varname='p_fo',
- defaults={if_true=macros('DFLT_FO_VIM')}
- },
- {
- full_name='formatlistpat', abbreviation='flp',
- short_desc=N_("pattern used to recognize a list header"),
- type='string', scope={'buffer'},
- alloced=true,
- varname='p_flp',
- defaults={if_true="^\\s*\\d\\+[\\]:.)}\\t ]\\s*"}
- },
- {
- full_name='formatprg', abbreviation='fp',
- short_desc=N_("name of external program used with \"gq\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_fp',
- defaults={if_true=""}
- },
- {
- full_name='fsync', abbreviation='fs',
- short_desc=N_("whether to invoke fsync() after file write"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_fs',
- defaults={if_true=false}
- },
- {
- full_name='gdefault', abbreviation='gd',
- short_desc=N_("the \":substitute\" flag 'g' is default on"),
- type='bool', scope={'global'},
- varname='p_gd',
- defaults={if_true=false}
- },
- {
- full_name='grepformat', abbreviation='gfm',
- short_desc=N_("format of 'grepprg' output"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_gefm',
- defaults={if_true=macros('DFLT_GREPFORMAT')}
- },
- {
- full_name='grepprg', abbreviation='gp',
- short_desc=N_("program to use for \":grep\""),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_gp',
- defaults={
- condition='MSWIN',
- -- Add an extra file name so that grep will always
- -- insert a file name in the match line. */
- if_true="findstr /n $* nul",
- if_false="grep -n $* /dev/null"
- }
- },
- {
- full_name='guicursor', abbreviation='gcr',
- short_desc=N_("GUI: settings for cursor shape and blinking"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_guicursor',
- defaults={if_true="n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20"}
- },
- {
- full_name='guifont', abbreviation='gfn',
- short_desc=N_("GUI: Name(s) of font(s) to be used"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_guifont',
- redraw={'ui_option'},
- defaults={if_true=""}
- },
- {
- full_name='guifontwide', abbreviation='gfw',
- short_desc=N_("list of font names for double-wide characters"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'ui_option'},
- varname='p_guifontwide',
- defaults={if_true=""}
- },
- {
- full_name='guioptions', abbreviation='go',
- short_desc=N_("GUI: Which components and options are used"),
- type='string', list='flags', scope={'global'},
- enable_if=false,
- },
- {
- full_name='guitablabel', abbreviation='gtl',
- short_desc=N_("GUI: custom label for a tab page"),
- type='string', scope={'global'},
- modelineexpr=true,
- redraw={'current_window'},
- enable_if=false,
- },
- {
- full_name='guitabtooltip', abbreviation='gtt',
- short_desc=N_("GUI: custom tooltip for a tab page"),
- type='string', scope={'global'},
- redraw={'current_window'},
- enable_if=false,
- },
- {
- full_name='helpfile', abbreviation='hf',
- short_desc=N_("full path name of the main help file"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_hf',
- defaults={if_true=macros('DFLT_HELPFILE')}
- },
- {
- full_name='helpheight', abbreviation='hh',
- short_desc=N_("minimum height of a new help window"),
- type='number', scope={'global'},
- varname='p_hh',
- defaults={if_true=20}
- },
- {
- full_name='helplang', abbreviation='hlg',
- short_desc=N_("preferred help languages"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_hlg',
- defaults={if_true=""}
- },
- {
- full_name='hidden', abbreviation='hid',
- short_desc=N_("don't unload buffer when it is |abandon|ed"),
- type='bool', scope={'global'},
- varname='p_hid',
- defaults={if_true=true}
- },
- {
- full_name='highlight', abbreviation='hl',
- short_desc=N_("sets highlighting mode for various occasions"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_hl',
- defaults={if_true=macros('HIGHLIGHT_INIT')}
- },
- {
- full_name='history', abbreviation='hi',
- short_desc=N_("number of command-lines that are remembered"),
- type='number', scope={'global'},
- varname='p_hi',
- defaults={if_true=10000}
- },
- {
- full_name='hkmap', abbreviation='hk',
- short_desc=N_("Hebrew keyboard mapping"),
- type='bool', scope={'global'},
- varname='p_hkmap',
- defaults={if_true=false}
- },
- {
- full_name='hkmapp', abbreviation='hkp',
- short_desc=N_("phonetic Hebrew keyboard mapping"),
- type='bool', scope={'global'},
- varname='p_hkmapp',
- defaults={if_true=false}
- },
- {
- full_name='hlsearch', abbreviation='hls',
- short_desc=N_("highlight matches with last search pattern"),
- type='bool', scope={'global'},
- redraw={'all_windows'},
- varname='p_hls',
- defaults={if_true=true}
- },
- {
- full_name='icon',
- short_desc=N_("Vim set the text of the window icon"),
- type='bool', scope={'global'},
- varname='p_icon',
- defaults={if_true=false}
- },
- {
- full_name='iconstring',
- short_desc=N_("to use for the Vim icon text"),
- type='string', scope={'global'},
- modelineexpr=true,
- varname='p_iconstring',
- defaults={if_true=""}
- },
- {
- full_name='ignorecase', abbreviation='ic',
- short_desc=N_("ignore case in search patterns"),
- type='bool', scope={'global'},
- varname='p_ic',
- defaults={if_true=false}
- },
- {
- full_name='imcmdline', abbreviation='imc',
- short_desc=N_("use IM when starting to edit a command line"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=false}
- },
- {
- full_name='imdisable', abbreviation='imd',
- short_desc=N_("do not use the IM in any mode"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=false}
- },
- {
- full_name='iminsert', abbreviation='imi',
- short_desc=N_("use :lmap or IM in Insert mode"),
- type='number', scope={'buffer'},
- varname='p_iminsert', pv_name='p_imi',
- defaults={
- if_true=macros('B_IMODE_NONE'),
- }
- },
- {
- full_name='imsearch', abbreviation='ims',
- short_desc=N_("use :lmap or IM when typing a search pattern"),
- type='number', scope={'buffer'},
- varname='p_imsearch', pv_name='p_ims',
- defaults={
- if_true=macros('B_IMODE_USE_INSERT'),
- }
- },
- {
- full_name='inccommand', abbreviation='icm',
- short_desc=N_("Live preview of substitution"),
- type='string', scope={'global'},
- varname='p_icm',
- defaults={if_true="nosplit"}
- },
- {
- full_name='include', abbreviation='inc',
- short_desc=N_("pattern to be used to find an include file"),
- type='string', scope={'global', 'buffer'},
- alloced=true,
- varname='p_inc',
- defaults={if_true="^\\s*#\\s*include"}
- },
- {
- full_name='includeexpr', abbreviation='inex',
- short_desc=N_("expression used to process an include line"),
- type='string', scope={'buffer'},
- modelineexpr=true,
- alloced=true,
- varname='p_inex',
- defaults={if_true=""}
- },
- {
- full_name='incsearch', abbreviation='is',
- short_desc=N_("highlight match while typing search pattern"),
- type='bool', scope={'global'},
- varname='p_is',
- defaults={if_true=true}
- },
- {
- full_name='indentexpr', abbreviation='inde',
- short_desc=N_("expression used to obtain the indent of a line"),
- type='string', scope={'buffer'},
- modelineexpr=true,
- alloced=true,
- varname='p_inde',
- defaults={if_true=""}
- },
- {
- full_name='indentkeys', abbreviation='indk',
- short_desc=N_("keys that trigger indenting with 'indentexpr'"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_indk',
- defaults={if_true=indentkeys_default}
- },
- {
- full_name='infercase', abbreviation='inf',
- short_desc=N_("adjust case of match for keyword completion"),
- type='bool', scope={'buffer'},
- varname='p_inf',
- defaults={if_true=false}
- },
- {
- full_name='insertmode', abbreviation='im',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- defaults={if_true=false}
- },
- {
- full_name='isfname', abbreviation='isf',
- short_desc=N_("characters included in file names and pathnames"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- varname='p_isf',
- defaults={
- condition='BACKSLASH_IN_FILENAME',
- -- Excluded are: & and ^ are special in cmd.exe
- -- ( and ) are used in text separating fnames */
- if_true="@,48-57,/,\\,.,-,_,+,,,#,$,%,{,},[,],:,@-@,!,~,=",
- if_false="@,48-57,/,.,-,_,+,,,#,$,%,~,="
- }
- },
- {
- full_name='isident', abbreviation='isi',
- short_desc=N_("characters included in identifiers"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- varname='p_isi',
- defaults={
- condition='MSWIN',
- if_true="@,48-57,_,128-167,224-235",
- if_false="@,48-57,_,192-255"
- }
- },
- {
- full_name='iskeyword', abbreviation='isk',
- short_desc=N_("characters included in keywords"),
- type='string', list='comma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_isk',
- defaults={if_true="@,48-57,_,192-255"}
- },
- {
- full_name='isprint', abbreviation='isp',
- short_desc=N_("printable characters"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- redraw={'all_windows'},
- varname='p_isp',
- defaults={if_true="@,161-255"
- }
- },
- {
- full_name='joinspaces', abbreviation='js',
- short_desc=N_("two spaces after a period with a join command"),
- type='bool', scope={'global'},
- varname='p_js',
- defaults={if_true=false}
- },
- {
- full_name='jumpoptions', abbreviation='jop',
- short_desc=N_("Controls the behavior of the jumplist"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_jop',
- defaults={if_true=''}
- },
- {
- full_name='keymap', abbreviation='kmp',
- short_desc=N_("name of a keyboard mapping"),
- type='string', scope={'buffer'},
- normal_fname_chars=true,
- pri_mkrc=true,
- alloced=true,
- redraw={'statuslines', 'current_buffer'},
- varname='p_keymap', pv_name='p_kmap',
- defaults={if_true=""}
- },
- {
- full_name='keymodel', abbreviation='km',
- short_desc=N_("enable starting/stopping selection with keys"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_km',
- defaults={if_true=""}
- },
- {
- full_name='keywordprg', abbreviation='kp',
- short_desc=N_("program to use for the \"K\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_kp',
- defaults={
- if_true=":Man",
- }
- },
- {
- full_name='langmap', abbreviation='lmap',
- short_desc=N_("alphabetic characters for other language mode"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- varname='p_langmap',
- defaults={if_true=""}
- },
- {
- full_name='langmenu', abbreviation='lm',
- short_desc=N_("language to be used for the menus"),
- type='string', scope={'global'},
- normal_fname_chars=true,
- varname='p_lm',
- defaults={if_true=""}
- },
- {
- full_name='langnoremap', abbreviation='lnr',
- short_desc=N_("do not apply 'langmap' to mapped characters"),
- type='bool', scope={'global'},
- varname='p_lnr',
- defaults={if_true=true}
- },
- {
- full_name='langremap', abbreviation='lrm',
- short_desc=N_('No description'),
- type='bool', scope={'global'},
- varname='p_lrm',
- defaults={if_true=false}
- },
- {
- full_name='laststatus', abbreviation='ls',
- short_desc=N_("tells when last window has status lines"),
- type='number', scope={'global'},
- redraw={'all_windows'},
- varname='p_ls',
- defaults={if_true=2}
- },
- {
- full_name='lazyredraw', abbreviation='lz',
- short_desc=N_("don't redraw while executing macros"),
- type='bool', scope={'global'},
- varname='p_lz',
- defaults={if_true=false}
- },
- {
- full_name='linebreak', abbreviation='lbr',
- short_desc=N_("wrap long lines at a blank"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='lines',
- short_desc=N_("of lines in the display"),
- type='number', scope={'global'},
- no_mkrc=true,
- varname='p_lines',
- defaults={if_true=macros('DFLT_ROWS')}
- },
- {
- full_name='linespace', abbreviation='lsp',
- short_desc=N_("number of pixel lines to use between characters"),
- type='number', scope={'global'},
- redraw={'ui_option'},
- varname='p_linespace',
- defaults={if_true=0}
- },
- {
- full_name='lisp',
- short_desc=N_("indenting for Lisp"),
- type='bool', scope={'buffer'},
- varname='p_lisp',
- defaults={if_true=false}
- },
- {
- full_name='lispoptions', abbreviation='lop',
- short_desc=N_("options for lisp indenting"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- varname='p_lop', pv_name='p_lop',
- defaults={if_true=''}
- },
- {
- full_name='lispwords', abbreviation='lw',
- short_desc=N_("words that change how lisp indenting works"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- varname='p_lispwords', pv_name='p_lw',
- defaults={if_true=macros('LISPWORD_VALUE')}
- },
- {
- full_name='list',
- short_desc=N_("<Tab> and <EOL>"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='listchars', abbreviation='lcs',
- short_desc=N_("characters for displaying in list mode"),
- type='string', list='onecomma', scope={'global', 'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- varname='p_lcs',
- defaults={if_true="tab:> ,trail:-,nbsp:+"}
- },
- {
- full_name='loadplugins', abbreviation='lpl',
- short_desc=N_("load plugin scripts when starting up"),
- type='bool', scope={'global'},
- varname='p_lpl',
- defaults={if_true=true}
- },
- {
- full_name='magic',
- short_desc=N_("special characters in search patterns"),
- type='bool', scope={'global'},
- varname='p_magic',
- defaults={if_true=true}
- },
- {
- full_name='makeef', abbreviation='mef',
- short_desc=N_("name of the errorfile for \":make\""),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_mef',
- defaults={if_true=""}
- },
- {
- full_name='makeencoding', abbreviation='menc',
- short_desc=N_("Converts the output of external commands"),
- type='string', scope={'global', 'buffer'},
- varname='p_menc',
- defaults={if_true=""}
- },
- {
- full_name='makeprg', abbreviation='mp',
- short_desc=N_("program to use for the \":make\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_mp',
- defaults={if_true="make"}
- },
- {
- full_name='matchpairs', abbreviation='mps',
- short_desc=N_("pairs of characters that \"%\" can match"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_mps',
- defaults={if_true="(:),{:},[:]"}
- },
- {
- full_name='matchtime', abbreviation='mat',
- short_desc=N_("tenths of a second to show matching paren"),
- type='number', scope={'global'},
- varname='p_mat',
- defaults={if_true=5}
- },
- {
- full_name='maxcombine', abbreviation='mco',
- short_desc=N_("maximum nr of combining characters displayed"),
- type='number', scope={'global'},
- varname='p_mco',
- defaults={if_true=6}
- },
- {
- full_name='maxfuncdepth', abbreviation='mfd',
- short_desc=N_("maximum recursive depth for user functions"),
- type='number', scope={'global'},
- varname='p_mfd',
- defaults={if_true=100}
- },
- {
- full_name='maxmapdepth', abbreviation='mmd',
- short_desc=N_("maximum recursive depth for mapping"),
- type='number', scope={'global'},
- varname='p_mmd',
- defaults={if_true=1000}
- },
- {
- full_name='maxmempattern', abbreviation='mmp',
- short_desc=N_("maximum memory (in Kbyte) used for pattern search"),
- type='number', scope={'global'},
- varname='p_mmp',
- defaults={if_true=1000}
- },
- {
- full_name='menuitems', abbreviation='mis',
- short_desc=N_("maximum number of items in a menu"),
- type='number', scope={'global'},
- varname='p_mis',
- defaults={if_true=25}
- },
- {
- full_name='mkspellmem', abbreviation='msm',
- short_desc=N_("memory used before |:mkspell| compresses the tree"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_msm',
- defaults={if_true="460000,2000,500"}
- },
- {
- full_name='modeline', abbreviation='ml',
- short_desc=N_("recognize modelines at start or end of file"),
- type='bool', scope={'buffer'},
- varname='p_ml',
- defaults={if_true=true}
- },
- {
- full_name='modelineexpr', abbreviation='mle',
- short_desc=N_("allow some options to be set in modeline"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_mle',
- defaults={if_true=false}
- },
- {
- full_name='modelines', abbreviation='mls',
- short_desc=N_("number of lines checked for modelines"),
- type='number', scope={'global'},
- varname='p_mls',
- defaults={if_true=5}
- },
- {
- full_name='modifiable', abbreviation='ma',
- short_desc=N_("changes to the text are not possible"),
- type='bool', scope={'buffer'},
- noglob=true,
- varname='p_ma',
- defaults={if_true=true}
- },
- {
- full_name='modified', abbreviation='mod',
- short_desc=N_("buffer has been modified"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_mod',
- defaults={if_true=false}
- },
- {
- full_name='more',
- short_desc=N_("listings when the whole screen is filled"),
- type='bool', scope={'global'},
- varname='p_more',
- defaults={if_true=true}
- },
- {
- full_name='mouse',
- short_desc=N_("the use of mouse clicks"),
- type='string', list='flags', scope={'global'},
- varname='p_mouse',
- defaults={if_true="nvi"}
- },
- {
- full_name='mousefocus', abbreviation='mousef',
- short_desc=N_("keyboard focus follows the mouse"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_mousef',
- defaults={if_true=false}
- },
- {
- full_name='mousehide', abbreviation='mh',
- short_desc=N_("hide mouse pointer while typing"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=true}
- },
- {
- full_name='mousemodel', abbreviation='mousem',
- short_desc=N_("changes meaning of mouse buttons"),
- type='string', scope={'global'},
- varname='p_mousem',
- defaults={if_true="popup_setpos"}
- },
- {
- full_name='mousemoveevent', abbreviation='mousemev',
- short_desc=N_("deliver mouse move events to input queue"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_mousemev',
- defaults={if_true=false}
- },
- {
- full_name='mousescroll',
- short_desc=N_("amount to scroll by when scrolling with a mouse"),
- type='string', list='comma', scope={'global'},
- vi_def=true,
- varname='p_mousescroll',
- defaults={if_true="ver:3,hor:6"}
- },
- {
- full_name='mouseshape', abbreviation='mouses',
- short_desc=N_("shape of the mouse pointer in different modes"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- enable_if=false,
- },
- {
- full_name='mousetime', abbreviation='mouset',
- short_desc=N_("max time between mouse double-click"),
- type='number', scope={'global'},
- varname='p_mouset',
- defaults={if_true=500}
- },
- {
- full_name='nrformats', abbreviation='nf',
- short_desc=N_("number formats recognized for CTRL-A command"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_nf',
- defaults={if_true="bin,hex"}
- },
- {
- full_name='number', abbreviation='nu',
- short_desc=N_("print the line number in front of each line"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='numberwidth', abbreviation='nuw',
- short_desc=N_("number of columns used for the line number"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=4}
- },
- {
- full_name='omnifunc', abbreviation='ofu',
- short_desc=N_("function for filetype-specific completion"),
- type='string', scope={'buffer'},
- secure=true,
- alloced=true,
- func=true,
- varname='p_ofu',
- defaults={if_true=""}
- },
- {
- full_name='opendevice', abbreviation='odev',
- short_desc=N_("allow reading/writing devices on MS-Windows"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=false}
- },
- {
- full_name='operatorfunc', abbreviation='opfunc',
- short_desc=N_("function to be called for |g@| operator"),
- type='string', scope={'global'},
- secure=true,
- func=true,
- varname='p_opfunc',
- defaults={if_true=""}
- },
- {
- full_name='packpath', abbreviation='pp',
- short_desc=N_("list of directories used for packages"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_pp',
- defaults={if_true=''}
- },
- {
- full_name='paragraphs', abbreviation='para',
- short_desc=N_("nroff macros that separate paragraphs"),
- type='string', scope={'global'},
- varname='p_para',
- defaults={if_true="IPLPPPQPP TPHPLIPpLpItpplpipbp"}
- },
- {
- full_name='paste',
- short_desc=N_("pasting text"),
- type='bool', scope={'global'},
- pri_mkrc=true,
- varname='p_paste',
- defaults={if_true=false}
- },
- {
- full_name='pastetoggle', abbreviation='pt',
- short_desc=N_("key code that causes 'paste' to toggle"),
- type='string', scope={'global'},
- varname='p_pt',
- defaults={if_true=""}
- },
- {
- full_name='patchexpr', abbreviation='pex',
- short_desc=N_("expression used to patch a file"),
- type='string', scope={'global'},
- secure=true,
- varname='p_pex',
- defaults={if_true=""}
- },
- {
- full_name='patchmode', abbreviation='pm',
- short_desc=N_("keep the oldest version of a file"),
- type='string', scope={'global'},
- normal_fname_chars=true,
- varname='p_pm',
- defaults={if_true=""}
- },
- {
- full_name='path', abbreviation='pa',
- short_desc=N_("list of directories searched with \"gf\" et.al."),
- type='string', list='comma', scope={'global', 'buffer'},
- deny_duplicates=true,
- expand=true,
- varname='p_path',
- defaults={if_true=".,/usr/include,,"}
- },
- {
- full_name='preserveindent', abbreviation='pi',
- short_desc=N_("preserve the indent structure when reindenting"),
- type='bool', scope={'buffer'},
- varname='p_pi',
- defaults={if_true=false}
- },
- {
- full_name='previewheight', abbreviation='pvh',
- short_desc=N_("height of the preview window"),
- type='number', scope={'global'},
- varname='p_pvh',
- defaults={if_true=12}
- },
- {
- full_name='previewwindow', abbreviation='pvw',
- short_desc=N_("identifies the preview window"),
- type='bool', scope={'window'},
- noglob=true,
- redraw={'statuslines'},
- defaults={if_true=false}
- },
- {
- full_name='prompt',
- short_desc=N_("enable prompt in Ex mode"),
- type='bool', scope={'global'},
- varname='p_force_on',
- defaults={if_true=true}
- },
- {
- full_name='pumblend', abbreviation='pb',
- short_desc=N_("Controls transparency level of popup menu"),
- type='number', scope={'global'},
- redraw={'ui_option'},
- varname='p_pb',
- defaults={if_true=0}
- },
- {
- full_name='pumheight', abbreviation='ph',
- short_desc=N_("maximum height of the popup menu"),
- type='number', scope={'global'},
- varname='p_ph',
- defaults={if_true=0}
- },
- {
- full_name='pumwidth', abbreviation='pw',
- short_desc=N_("minimum width of the popup menu"),
- type='number', scope={'global'},
- varname='p_pw',
- defaults={if_true=15}
- },
- {
- full_name='pyxversion', abbreviation='pyx',
- short_desc=N_("selects default python version to use"),
- type='number', scope={'global'},
- secure=true,
- varname='p_pyx',
- defaults={if_true=3}
- },
- {
- full_name='quickfixtextfunc', abbreviation='qftf',
- short_desc=N_("customize the quickfix window"),
- type='string', scope={'global'},
- secure=true,
- func=true,
- varname='p_qftf',
- defaults={if_true=""}
- },
- {
- full_name='quoteescape', abbreviation='qe',
- short_desc=N_("escape characters used in a string"),
- type='string', scope={'buffer'},
- alloced=true,
- varname='p_qe',
- defaults={if_true="\\"}
- },
- {
- full_name='readonly', abbreviation='ro',
- short_desc=N_("disallow writing the buffer"),
- type='bool', scope={'buffer'},
- noglob=true,
- redraw={'statuslines'},
- varname='p_ro',
- defaults={if_true=false}
- },
- {
- full_name='redrawdebug', abbreviation='rdb',
- short_desc=N_("Changes the way redrawing works (debug)"),
- type='string', list='onecomma', scope={'global'},
- varname='p_rdb',
- defaults={if_true=''}
- },
- {
- full_name='redrawtime', abbreviation='rdt',
- short_desc=N_("timeout for 'hlsearch' and |:match| highlighting"),
- type='number', scope={'global'},
- varname='p_rdt',
- defaults={if_true=2000}
- },
- {
- full_name='regexpengine', abbreviation='re',
- short_desc=N_("default regexp engine to use"),
- type='number', scope={'global'},
- varname='p_re',
- defaults={if_true=0}
- },
- {
- full_name='relativenumber', abbreviation='rnu',
- short_desc=N_("show relative line number in front of each line"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='remap',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_on',
- defaults={if_true=true}
- },
- {
- full_name='report',
- short_desc=N_("for reporting nr. of lines changed"),
- type='number', scope={'global'},
- varname='p_report',
- defaults={if_true=2}
- },
- {
- full_name='revins', abbreviation='ri',
- short_desc=N_("inserting characters will work backwards"),
- type='bool', scope={'global'},
- varname='p_ri',
- defaults={if_true=false}
- },
- {
- full_name='rightleft', abbreviation='rl',
- short_desc=N_("window is right-to-left oriented"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='rightleftcmd', abbreviation='rlc',
- short_desc=N_("commands for which editing works right-to-left"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="search"}
- },
- {
- full_name='ruler', abbreviation='ru',
- short_desc=N_("show cursor line and column in the status line"),
- type='bool', scope={'global'},
- redraw={'statuslines'},
- varname='p_ru',
- defaults={if_true=true}
- },
- {
- full_name='rulerformat', abbreviation='ruf',
- short_desc=N_("custom format for the ruler"),
- type='string', scope={'global'},
- alloced=true,
- modelineexpr=true,
- redraw={'statuslines'},
- varname='p_ruf',
- defaults={if_true=""}
- },
- {
- full_name='runtimepath', abbreviation='rtp',
- short_desc=N_("list of directories used for runtime files"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_rtp',
- defaults={if_true=''}
- },
- {
- full_name='scroll', abbreviation='scr',
- short_desc=N_("lines to scroll with CTRL-U and CTRL-D"),
- type='number', scope={'window'},
- no_mkrc=true,
- pv_name='p_scroll',
- defaults={if_true=0}
- },
- {
- full_name='scrollback', abbreviation='scbk',
- short_desc=N_("lines to scroll with CTRL-U and CTRL-D"),
- type='number', scope={'buffer'},
- varname='p_scbk',
- redraw={'current_buffer'},
- defaults={if_true=-1}
- },
- {
- full_name='scrollbind', abbreviation='scb',
- short_desc=N_("scroll in window as other windows scroll"),
- type='bool', scope={'window'},
- pv_name='p_scbind',
- defaults={if_true=false}
- },
- {
- full_name='scrolljump', abbreviation='sj',
- short_desc=N_("minimum number of lines to scroll"),
- type='number', scope={'global'},
- varname='p_sj',
- defaults={if_true=1}
- },
- {
- full_name='scrolloff', abbreviation='so',
- short_desc=N_("minimum nr. of lines above and below cursor"),
- type='number', scope={'global', 'window'},
- varname='p_so',
- defaults={if_true=0}
- },
- {
- full_name='scrollopt', abbreviation='sbo',
- short_desc=N_("how 'scrollbind' should behave"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_sbo',
- defaults={if_true="ver,jump"}
- },
- {
- full_name='sections', abbreviation='sect',
- short_desc=N_("nroff macros that separate sections"),
- type='string', scope={'global'},
- varname='p_sections',
- defaults={if_true="SHNHH HUnhsh"}
- },
- {
- full_name='secure',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_secure',
- defaults={if_true=false}
- },
- {
- full_name='selection', abbreviation='sel',
- short_desc=N_("what type of selection to use"),
- type='string', scope={'global'},
- varname='p_sel',
- defaults={if_true="inclusive"}
- },
- {
- full_name='selectmode', abbreviation='slm',
- short_desc=N_("when to use Select mode instead of Visual mode"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_slm',
- defaults={if_true=""}
- },
- {
- full_name='sessionoptions', abbreviation='ssop',
- short_desc=N_("options for |:mksession|"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_ssop',
- defaults={if_true="blank,buffers,curdir,folds,help,tabpages,winsize,terminal"}
- },
- {
- full_name='shada', abbreviation='sd',
- short_desc=N_("use .shada file upon startup and exiting"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- varname='p_shada',
- defaults={if_true="!,'100,<50,s10,h"}
- },
- {
- full_name='shadafile', abbreviation='sdf',
- short_desc=N_("overrides the filename used for shada"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_shadafile',
- defaults={if_true=""}
- },
- {
- full_name='shell', abbreviation='sh',
- short_desc=N_("name of shell to use for external commands"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_sh',
- defaults={
- condition='MSWIN',
- if_true="cmd.exe",
- if_false="sh"
- }
- },
- {
- full_name='shellcmdflag', abbreviation='shcf',
- short_desc=N_("flag to shell to execute one command"),
- type='string', scope={'global'},
- secure=true,
- varname='p_shcf',
- defaults={
- condition='MSWIN',
- if_true="/s /c",
- if_false="-c"
- }
- },
- {
- full_name='shellpipe', abbreviation='sp',
- short_desc=N_("string to put output of \":make\" in error file"),
- type='string', scope={'global'},
- secure=true,
- varname='p_sp',
- defaults={
- condition='MSWIN',
- if_true=">%s 2>&1",
- if_false="| tee",
- }
- },
- {
- full_name='shellquote', abbreviation='shq',
- short_desc=N_("quote character(s) for around shell command"),
- type='string', scope={'global'},
- secure=true,
- varname='p_shq',
- defaults={if_true=""}
- },
- {
- full_name='shellredir', abbreviation='srr',
- short_desc=N_("string to put output of filter in a temp file"),
- type='string', scope={'global'},
- secure=true,
- varname='p_srr',
- defaults={
- condition='MSWIN',
- if_true=">%s 2>&1",
- if_false=">"
- }
- },
- {
- full_name='shellslash', abbreviation='ssl',
- short_desc=N_("use forward slash for shell file names"),
- type='bool', scope={'global'},
- varname='p_ssl',
- enable_if='BACKSLASH_IN_FILENAME',
- defaults={if_true=false}
- },
- {
- full_name='shelltemp', abbreviation='stmp',
- short_desc=N_("whether to use a temp file for shell commands"),
- type='bool', scope={'global'},
- varname='p_stmp',
- defaults={if_true=true}
- },
- {
- full_name='shellxquote', abbreviation='sxq',
- short_desc=N_("like 'shellquote', but include redirection"),
- type='string', scope={'global'},
- secure=true,
- varname='p_sxq',
- defaults={
- condition='MSWIN',
- if_true="\"",
- if_false="",
- }
- },
- {
- full_name='shellxescape', abbreviation='sxe',
- short_desc=N_("characters to escape when 'shellxquote' is ("),
- type='string', scope={'global'},
- secure=true,
- varname='p_sxe',
- defaults={if_true=""}
- },
- {
- full_name='shiftround', abbreviation='sr',
- short_desc=N_("round indent to multiple of shiftwidth"),
- type='bool', scope={'global'},
- varname='p_sr',
- defaults={if_true=false}
- },
- {
- full_name='shiftwidth', abbreviation='sw',
- short_desc=N_("number of spaces to use for (auto)indent step"),
- type='number', scope={'buffer'},
- varname='p_sw',
- defaults={if_true=8}
- },
- {
- full_name='shortmess', abbreviation='shm',
- short_desc=N_("list of flags, reduce length of messages"),
- type='string', list='flags', scope={'global'},
- varname='p_shm',
- defaults={if_true="filnxtToOF"}
- },
- {
- full_name='showbreak', abbreviation='sbr',
- short_desc=N_("string to use at the start of wrapped lines"),
- type='string', scope={'global', 'window'},
- redraw={'all_windows'},
- varname='p_sbr',
- defaults={if_true=""}
- },
- {
- full_name='showcmd', abbreviation='sc',
- short_desc=N_("show (partial) command in status line"),
- type='bool', scope={'global'},
- varname='p_sc',
- defaults={if_true=true}
- },
- {
- full_name='showcmdloc', abbreviation='sloc',
- short_desc=N_("change location of partial command"),
- type='string', scope={'global'},
- varname='p_sloc',
- defaults={if_true="last"}
- },
- {
- full_name='showfulltag', abbreviation='sft',
- short_desc=N_("show full tag pattern when completing tag"),
- type='bool', scope={'global'},
- varname='p_sft',
- defaults={if_true=false}
- },
- {
- full_name='showmatch', abbreviation='sm',
- short_desc=N_("briefly jump to matching bracket if insert one"),
- type='bool', scope={'global'},
- varname='p_sm',
- defaults={if_true=false}
- },
- {
- full_name='showmode', abbreviation='smd',
- short_desc=N_("message on status line to show current mode"),
- type='bool', scope={'global'},
- varname='p_smd',
- defaults={if_true=true}
- },
- {
- full_name='showtabline', abbreviation='stal',
- short_desc=N_("tells when the tab pages line is displayed"),
- type='number', scope={'global'},
- redraw={'all_windows', 'ui_option'},
- varname='p_stal',
- defaults={if_true=1}
- },
- {
- full_name='sidescroll', abbreviation='ss',
- short_desc=N_("minimum number of columns to scroll horizontal"),
- type='number', scope={'global'},
- varname='p_ss',
- defaults={if_true=1}
- },
- {
- full_name='sidescrolloff', abbreviation='siso',
- short_desc=N_("min. nr. of columns to left and right of cursor"),
- type='number', scope={'global', 'window'},
- varname='p_siso',
- defaults={if_true=0}
- },
- {
- full_name='signcolumn', abbreviation='scl',
- short_desc=N_("when to display the sign column"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="auto"}
- },
- {
- full_name='smartcase', abbreviation='scs',
- short_desc=N_("no ignore case when pattern has uppercase"),
- type='bool', scope={'global'},
- varname='p_scs',
- defaults={if_true=false}
- },
- {
- full_name='smartindent', abbreviation='si',
- short_desc=N_("smart autoindenting for C programs"),
- type='bool', scope={'buffer'},
- varname='p_si',
- defaults={if_true=false}
- },
- {
- full_name='smarttab', abbreviation='sta',
- short_desc=N_("use 'shiftwidth' when inserting <Tab>"),
- type='bool', scope={'global'},
- varname='p_sta',
- defaults={if_true=true}
- },
- {
- full_name='softtabstop', abbreviation='sts',
- short_desc=N_("number of spaces that <Tab> uses while editing"),
- type='number', scope={'buffer'},
- varname='p_sts',
- defaults={if_true=0}
- },
- {
- full_name='spell',
- short_desc=N_("spell checking"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='spellcapcheck', abbreviation='spc',
- short_desc=N_("pattern to locate end of a sentence"),
- type='string', scope={'buffer'},
- alloced=true,
- redraw={'current_buffer'},
- varname='p_spc',
- defaults={if_true="[.?!]\\_[\\])'\" ]\\+"}
- },
- {
- full_name='spellfile', abbreviation='spf',
- short_desc=N_("files where |zg| and |zw| store words"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- secure=true,
- alloced=true,
- expand=true,
- varname='p_spf',
- defaults={if_true=""}
- },
- {
- full_name='spelllang', abbreviation='spl',
- short_desc=N_("language(s) to do spell checking for"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- expand=true,
- redraw={'current_buffer'},
- varname='p_spl',
- defaults={if_true="en"}
- },
- {
- full_name='spellsuggest', abbreviation='sps',
- short_desc=N_("method(s) used to suggest spelling corrections"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_sps',
- defaults={if_true="best"}
- },
- {
- full_name='spelloptions', abbreviation='spo',
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_spo',
- redraw={'current_buffer'},
- defaults={if_true=""}
- },
- {
- full_name='splitbelow', abbreviation='sb',
- short_desc=N_("new window from split is below the current one"),
- type='bool', scope={'global'},
- varname='p_sb',
- defaults={if_true=false}
- },
- {
- full_name='splitkeep', abbreviation='spk',
- short_desc=N_("determines scroll behavior for split windows"),
- type='string', scope={'global'},
- varname='p_spk',
- defaults={if_true='cursor'}
- },
- {
- full_name='splitright', abbreviation='spr',
- short_desc=N_("new window is put right of the current one"),
- type='bool', scope={'global'},
- varname='p_spr',
- defaults={if_true=false}
- },
- {
- full_name='startofline', abbreviation='sol',
- short_desc=N_("commands move cursor to first non-blank in line"),
- type='bool', scope={'global'},
- vim=false,
- varname='p_sol',
- defaults={if_true=false}
- },
- {
- full_name='statuscolumn', abbreviation='stc',
- short_desc=N_("custom format for the status column"),
- type='string', scope={'window'},
- redraw={'current_window'},
- secure=true,
- alloced=true,
- defaults={if_true=""}
- },
- {
- full_name='statusline', abbreviation='stl',
- short_desc=N_("custom format for the status line"),
- type='string', scope={'global', 'window'},
- alloced=true,
- modelineexpr=true,
- redraw={'statuslines'},
- varname='p_stl',
- defaults={if_true=""}
- },
- {
- full_name='suffixes', abbreviation='su',
- short_desc=N_("suffixes that are ignored with multiple match"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_su',
- defaults={if_true=".bak,~,.o,.h,.info,.swp,.obj"}
- },
- {
- full_name='suffixesadd', abbreviation='sua',
- short_desc=N_("suffixes added when searching for a file"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_sua',
- defaults={if_true=""}
- },
- {
- full_name='swapfile', abbreviation='swf',
- short_desc=N_("whether to use a swapfile for a buffer"),
- type='bool', scope={'buffer'},
- redraw={'statuslines'},
- varname='p_swf',
- defaults={if_true=true}
- },
- {
- full_name='switchbuf', abbreviation='swb',
- short_desc=N_("sets behavior when switching to another buffer"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_swb',
- defaults={if_true="uselast"}
- },
- {
- full_name='synmaxcol', abbreviation='smc',
- short_desc=N_("maximum column to find syntax items"),
- type='number', scope={'buffer'},
- redraw={'current_buffer'},
- varname='p_smc',
- defaults={if_true=3000}
- },
- {
- full_name='syntax', abbreviation='syn',
- short_desc=N_("syntax to be loaded for current buffer"),
- type='string', scope={'buffer'},
- noglob=true,
- normal_fname_chars=true,
- alloced=true,
- varname='p_syn',
- defaults={if_true=""}
- },
- {
- full_name='tagfunc', abbreviation='tfu',
- short_desc=N_("function used to perform tag searches"),
- type='string', scope={'buffer'},
- secure=true,
- func=true,
- varname='p_tfu',
- defaults={if_true=""}
- },
- {
- full_name='tabline', abbreviation='tal',
- short_desc=N_("custom format for the console tab pages line"),
- type='string', scope={'global'},
- modelineexpr=true,
- redraw={'tabline'},
- varname='p_tal',
- defaults={if_true=""}
- },
- {
- full_name='tabpagemax', abbreviation='tpm',
- short_desc=N_("maximum number of tab pages for |-p| and \"tab all\""),
- type='number', scope={'global'},
- varname='p_tpm',
- defaults={if_true=50}
- },
- {
- full_name='tabstop', abbreviation='ts',
- short_desc=N_("number of spaces that <Tab> in file uses"),
- type='number', scope={'buffer'},
- redraw={'current_buffer'},
- varname='p_ts',
- defaults={if_true=8}
- },
- {
- full_name='tagbsearch', abbreviation='tbs',
- short_desc=N_("use binary searching in tags files"),
- type='bool', scope={'global'},
- varname='p_tbs',
- defaults={if_true=true}
- },
- {
- full_name='tagcase', abbreviation='tc',
- short_desc=N_("how to handle case when searching in tags files"),
- type='string', scope={'global', 'buffer'},
- varname='p_tc',
- defaults={if_true="followic"}
- },
- {
- full_name='taglength', abbreviation='tl',
- short_desc=N_("number of significant characters for a tag"),
- type='number', scope={'global'},
- varname='p_tl',
- defaults={if_true=0}
- },
- {
- full_name='tagrelative', abbreviation='tr',
- short_desc=N_("file names in tag file are relative"),
- type='bool', scope={'global'},
- varname='p_tr',
- defaults={if_true=true}
- },
- {
- full_name='tags', abbreviation='tag',
- short_desc=N_("list of file names used by the tag command"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- expand=true,
- varname='p_tags',
- defaults={if_true="./tags;,tags"}
- },
- {
- full_name='tagstack', abbreviation='tgst',
- short_desc=N_("push tags onto the tag stack"),
- type='bool', scope={'global'},
- varname='p_tgst',
- defaults={if_true=true}
- },
- {
- full_name='termbidi', abbreviation='tbidi',
- short_desc=N_("terminal takes care of bi-directionality"),
- type='bool', scope={'global'},
- varname='p_tbidi',
- defaults={if_true=false}
- },
- {
- full_name='termencoding', abbreviation='tenc',
- short_desc=N_("Terminal encoding"),
- type='string', scope={'global'},
- defaults={if_true=""}
- },
- {
- full_name='termguicolors', abbreviation='tgc',
- short_desc=N_("Terminal true color support"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_tgc',
- defaults={if_true=false}
- },
- {
- full_name='termpastefilter', abbreviation='tpf',
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_tpf',
- defaults={if_true="BS,HT,ESC,DEL"}
- },
- {
- full_name='terse',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- defaults={if_true=false}
- },
- {
- full_name='textwidth', abbreviation='tw',
- short_desc=N_("maximum width of text that is being inserted"),
- type='number', scope={'buffer'},
- redraw={'current_buffer'},
- varname='p_tw',
- defaults={if_true=0}
- },
- {
- full_name='thesaurus', abbreviation='tsr',
- short_desc=N_("list of thesaurus files for keyword completion"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- normal_dname_chars=true,
- expand=true,
- varname='p_tsr',
- defaults={if_true=""}
- },
- {
- full_name='thesaurusfunc', abbreviation='tsrfu',
- short_desc=N_("function used for thesaurus completion"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- alloced=true,
- func=true,
- varname='p_tsrfu',
- defaults={if_true=""}
- },
- {
- full_name='tildeop', abbreviation='top',
- short_desc=N_("tilde command \"~\" behaves like an operator"),
- type='bool', scope={'global'},
- varname='p_to',
- defaults={if_true=false}
- },
- {
- full_name='timeout', abbreviation='to',
- short_desc=N_("time out on mappings and key codes"),
- type='bool', scope={'global'},
- varname='p_timeout',
- defaults={if_true=true}
- },
- {
- full_name='timeoutlen', abbreviation='tm',
- short_desc=N_("time out time in milliseconds"),
- type='number', scope={'global'},
- varname='p_tm',
- defaults={if_true=1000}
- },
- {
- full_name='title',
- short_desc=N_("Vim set the title of the window"),
- type='bool', scope={'global'},
- varname='p_title',
- defaults={if_true=false}
- },
- {
- full_name='titlelen',
- short_desc=N_("of 'columns' used for window title"),
- type='number', scope={'global'},
- varname='p_titlelen',
- defaults={if_true=85}
- },
- {
- full_name='titleold',
- short_desc=N_("title, restored when exiting"),
- type='string', scope={'global'},
- secure=true,
- no_mkrc=true,
- varname='p_titleold',
- defaults={if_true=""}
- },
- {
- full_name='titlestring',
- short_desc=N_("to use for the Vim window title"),
- type='string', scope={'global'},
- modelineexpr=true,
- varname='p_titlestring',
- defaults={if_true=""}
- },
- {
- full_name='ttimeout',
- short_desc=N_("out on mappings"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_ttimeout',
- defaults={if_true=true}
- },
- {
- full_name='ttimeoutlen', abbreviation='ttm',
- short_desc=N_("time out time for key codes in milliseconds"),
- type='number', scope={'global'},
- redraw={'ui_option'},
- varname='p_ttm',
- defaults={if_true=50}
- },
- {
- full_name='ttyfast', abbreviation='tf',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- no_mkrc=true,
- varname='p_force_on',
- defaults={if_true=true}
- },
- {
- full_name='undodir', abbreviation='udir',
- short_desc=N_("where to store undo files"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_udir',
- defaults={if_true=''}
- },
- {
- full_name='undofile', abbreviation='udf',
- short_desc=N_("save undo information in a file"),
- type='bool', scope={'buffer'},
- varname='p_udf',
- defaults={if_true=false}
- },
- {
- full_name='undolevels', abbreviation='ul',
- short_desc=N_("maximum number of changes that can be undone"),
- type='number', scope={'global', 'buffer'},
- varname='p_ul',
- defaults={if_true=1000}
- },
- {
- full_name='undoreload', abbreviation='ur',
- short_desc=N_("max nr of lines to save for undo on a buffer reload"),
- type='number', scope={'global'},
- varname='p_ur',
- defaults={if_true=10000}
- },
- {
- full_name='updatecount', abbreviation='uc',
- short_desc=N_("after this many characters flush swap file"),
- type='number', scope={'global'},
- varname='p_uc',
- defaults={if_true=200}
- },
- {
- full_name='updatetime', abbreviation='ut',
- short_desc=N_("after this many milliseconds flush swap file"),
- type='number', scope={'global'},
- varname='p_ut',
- defaults={if_true=4000}
- },
- {
- full_name='varsofttabstop', abbreviation='vsts',
- short_desc=N_("list of numbers of spaces that <Tab> uses while editing"),
- type='string', list='comma', scope={'buffer'},
- varname='p_vsts',
- defaults={if_true=""}
- },
- {
- full_name='vartabstop', abbreviation='vts',
- short_desc=N_("list of numbers of spaces that <Tab> in file uses"),
- type='string', list='comma', scope={'buffer'},
- varname='p_vts',
- redraw={'current_buffer'},
- defaults={if_true=""}
- },
- {
- full_name='verbose', abbreviation='vbs',
- short_desc=N_("give informative messages"),
- type='number', scope={'global'},
- varname='p_verbose', redraw={'ui_option'},
- defaults={if_true=0}
- },
- {
- full_name='verbosefile', abbreviation='vfile',
- short_desc=N_("file to write messages in"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_vfile',
- defaults={if_true=""}
- },
- {
- full_name='viewdir', abbreviation='vdir',
- short_desc=N_("directory where to store files with :mkview"),
- type='string', scope={'global'},
- secure=true,
- expand='nodefault',
- varname='p_vdir',
- defaults={if_true=''}
- },
- {
- full_name='viewoptions', abbreviation='vop',
- short_desc=N_("specifies what to save for :mkview"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_vop',
- defaults={if_true="folds,cursor,curdir"}
- },
- {
- -- Alias for "shada".
- full_name='viminfo', abbreviation='vi',
- short_desc=N_("Alias for shada"),
- type='string', scope={'global'}, nodefault=true,
- },
- {
- -- Alias for "shadafile".
- full_name='viminfofile', abbreviation='vif',
- short_desc=N_("Alias for shadafile instead"),
- type='string', scope={'global'}, nodefault=true,
- },
- {
- full_name='virtualedit', abbreviation='ve',
- short_desc=N_("when to use virtual editing"),
- type='string', list='onecomma', scope={'global', 'window'},
- deny_duplicates=true,
- redraw={'curswant'},
- varname='p_ve',
- defaults={if_true=""}
- },
- {
- full_name='visualbell', abbreviation='vb',
- short_desc=N_("use visual bell instead of beeping"),
- type='bool', scope={'global'},
- varname='p_vb',
- defaults={if_true=false}
- },
- {
- full_name='warn',
- short_desc=N_("for shell command when buffer was changed"),
- type='bool', scope={'global'},
- varname='p_warn',
- defaults={if_true=true}
- },
- {
- full_name='whichwrap', abbreviation='ww',
- short_desc=N_("allow specified keys to cross line boundaries"),
- type='string', list='flagscomma', scope={'global'},
- varname='p_ww',
- defaults={if_true="b,s"}
- },
- {
- full_name='wildchar', abbreviation='wc',
- short_desc=N_("command-line character for wildcard expansion"),
- type='number', scope={'global'},
- varname='p_wc',
- defaults={if_true=imacros('TAB')}
- },
- {
- full_name='wildcharm', abbreviation='wcm',
- short_desc=N_("like 'wildchar' but also works when mapped"),
- type='number', scope={'global'},
- varname='p_wcm',
- defaults={if_true=0}
- },
- {
- full_name='wildignore', abbreviation='wig',
- short_desc=N_("files matching these patterns are not completed"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_wig',
- defaults={if_true=""}
- },
- {
- full_name='wildignorecase', abbreviation='wic',
- short_desc=N_("ignore case when completing file names"),
- type='bool', scope={'global'},
- varname='p_wic',
- defaults={if_true=false}
- },
- {
- full_name='wildmenu', abbreviation='wmnu',
- short_desc=N_("use menu for command line completion"),
- type='bool', scope={'global'},
- varname='p_wmnu',
- defaults={if_true=true}
- },
- {
- full_name='wildmode', abbreviation='wim',
- short_desc=N_("mode for 'wildchar' command-line expansion"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=false,
- varname='p_wim',
- defaults={if_true="full"}
- },
- {
- full_name='wildoptions', abbreviation='wop',
- short_desc=N_("specifies how command line completion is done"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_wop',
- defaults={if_true='pum,tagfile'}
- },
- {
- full_name='winaltkeys', abbreviation='wak',
- short_desc=N_("when the windows system handles ALT keys"),
- type='string', scope={'global'},
- varname='p_wak',
- defaults={if_true="menu"}
- },
- {
- full_name='winbar', abbreviation='wbr',
- short_desc=N_("custom format for the window bar"),
- type='string', scope={'global', 'window'},
- alloced=true,
- modelineexpr=true,
- redraw={'statuslines'},
- varname='p_wbr',
- defaults={if_true=""}
- },
- {
- full_name='winblend', abbreviation='winbl',
- short_desc=N_("Controls transparency level for floating windows"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=0}
- },
- {
- full_name='winhighlight', abbreviation='winhl',
- short_desc=N_("Setup window-local highlights");
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true=""}
- },
- {
- full_name='window', abbreviation='wi',
- short_desc=N_("nr of lines to scroll for CTRL-F and CTRL-B"),
- type='number', scope={'global'},
- varname='p_window',
- defaults={if_true=0}
- },
- {
- full_name='winheight', abbreviation='wh',
- short_desc=N_("minimum number of lines for the current window"),
- type='number', scope={'global'},
- varname='p_wh',
- defaults={if_true=1}
- },
- {
- full_name='winfixheight', abbreviation='wfh',
- short_desc=N_("keep window height when opening/closing windows"),
- type='bool', scope={'window'},
- redraw={'statuslines'},
- defaults={if_true=false}
- },
- {
- full_name='winfixwidth', abbreviation='wfw',
- short_desc=N_("keep window width when opening/closing windows"),
- type='bool', scope={'window'},
- redraw={'statuslines'},
- defaults={if_true=false}
- },
- {
- full_name='winminheight', abbreviation='wmh',
- short_desc=N_("minimum number of lines for any window"),
- type='number', scope={'global'},
- varname='p_wmh',
- defaults={if_true=1}
- },
- {
- full_name='winminwidth', abbreviation='wmw',
- short_desc=N_("minimal number of columns for any window"),
- type='number', scope={'global'},
- varname='p_wmw',
- defaults={if_true=1}
- },
- {
- full_name='winwidth', abbreviation='wiw',
- short_desc=N_("minimal number of columns for current window"),
- type='number', scope={'global'},
- varname='p_wiw',
- defaults={if_true=20}
- },
- {
- full_name='wrap',
- short_desc=N_("lines wrap and continue on the next line"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=true}
- },
- {
- full_name='wrapmargin', abbreviation='wm',
- short_desc=N_("chars from the right where wrapping starts"),
- type='number', scope={'buffer'},
- varname='p_wm',
- defaults={if_true=0}
- },
- {
- full_name='wrapscan', abbreviation='ws',
- short_desc=N_("searches wrap around the end of the file"),
- type='bool', scope={'global'},
- varname='p_ws',
- defaults={if_true=true}
- },
- {
- full_name='write',
- short_desc=N_("to a file is allowed"),
- type='bool', scope={'global'},
- varname='p_write',
- defaults={if_true=true}
- },
- {
- full_name='writeany', abbreviation='wa',
- short_desc=N_("write to file with no need for \"!\" override"),
- type='bool', scope={'global'},
- varname='p_wa',
- defaults={if_true=false}
- },
- {
- full_name='writebackup', abbreviation='wb',
- short_desc=N_("make a backup before overwriting a file"),
- type='bool', scope={'global'},
- varname='p_wb',
- defaults={if_true=true}
- },
- {
- full_name='writedelay', abbreviation='wd',
- short_desc=N_("delay this many msec for each char (for debug)"),
- type='number', scope={'global'},
- varname='p_wd',
- defaults={if_true=0}
- },
- }
+ deny_duplicates = true,
+ desc = [=[
+ A list of file patterns. When one of the patterns matches with the
+ name of the file which is written, no backup file is created. Both
+ the specified file name and the full path name of the file are used.
+ The pattern is used like with |:autocmd|, see |autocmd-pattern|.
+ Watch out for special characters, see |option-backslash|.
+ When $TMPDIR, $TMP or $TEMP is not defined, it is not used for the
+ default value. "/tmp/*" is only used for Unix.
+
+ WARNING: Not having a backup file means that when Vim fails to write
+ your buffer correctly and then, for whatever reason, Vim exits, you
+ lose both the original file and what you were writing. Only disable
+ backups if you don't care about losing the file.
+
+ Note that environment variables are not expanded. If you want to use
+ $HOME you must expand it explicitly, e.g.: >vim
+ :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*'
+
+ < Note that the default also makes sure that "crontab -e" works (when a
+ backup would be made by renaming the original file crontab won't see
+ the newly created file). Also see 'backupcopy' and |crontab|.
+ ]=],
+ full_name = 'backupskip',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('no backup for files that match these patterns'),
+ type = 'string',
+ varname = 'p_bsk',
+ },
+ {
+ abbreviation = 'bo',
+ cb = 'did_set_belloff',
+ defaults = { if_true = 'all' },
+ deny_duplicates = true,
+ desc = [=[
+ Specifies for which events the bell will not be rung. It is a comma-
+ separated list of items. For each item that is present, the bell
+ will be silenced. This is most useful to specify specific events in
+ insert mode to be silenced.
+
+ item meaning when present ~
+ all All events.
+ backspace When hitting <BS> or <Del> and deleting results in an
+ error.
+ cursor Fail to move around using the cursor keys or
+ <PageUp>/<PageDown> in |Insert-mode|.
+ complete Error occurred when using |i_CTRL-X_CTRL-K| or
+ |i_CTRL-X_CTRL-T|.
+ copy Cannot copy char from insert mode using |i_CTRL-Y| or
+ |i_CTRL-E|.
+ ctrlg Unknown Char after <C-G> in Insert mode.
+ error Other Error occurred (e.g. try to join last line)
+ (mostly used in |Normal-mode| or |Cmdline-mode|).
+ esc hitting <Esc> in |Normal-mode|.
+ hangul Ignored.
+ lang Calling the beep module for Lua/Mzscheme/TCL.
+ mess No output available for |g<|.
+ showmatch Error occurred for 'showmatch' function.
+ operator Empty region error |cpo-E|.
+ register Unknown register after <C-R> in |Insert-mode|.
+ shell Bell from shell output |:!|.
+ spell Error happened on spell suggest.
+ wildmode More matches in |cmdline-completion| available
+ (depends on the 'wildmode' setting).
+
+ This is most useful to fine tune when in Insert mode the bell should
+ be rung. For Normal mode and Ex commands, the bell is often rung to
+ indicate that an error occurred. It can be silenced by adding the
+ "error" keyword.
+ ]=],
+ expand_cb = 'expand_set_belloff',
+ full_name = 'belloff',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('do not ring the bell for these reasons'),
+ type = 'string',
+ varname = 'p_bo',
+ },
+ {
+ abbreviation = 'bin',
+ cb = 'did_set_binary',
+ defaults = { if_true = false },
+ desc = [=[
+ This option should be set before editing a binary file. You can also
+ use the |-b| Vim argument. When this option is switched on a few
+ options will be changed (also when it already was on):
+ 'textwidth' will be set to 0
+ 'wrapmargin' will be set to 0
+ 'modeline' will be off
+ 'expandtab' will be off
+ Also, 'fileformat' and 'fileformats' options will not be used, the
+ file is read and written like 'fileformat' was "unix" (a single <NL>
+ separates lines).
+ The 'fileencoding' and 'fileencodings' options will not be used, the
+ file is read without conversion.
+ NOTE: When you start editing a(nother) file while the 'bin' option is
+ on, settings from autocommands may change the settings again (e.g.,
+ 'textwidth'), causing trouble when editing. You might want to set
+ 'bin' again when the file has been loaded.
+ The previous values of these options are remembered and restored when
+ 'bin' is switched from on to off. Each buffer has its own set of
+ saved option values.
+ To edit a file with 'binary' set you can use the |++bin| argument.
+ This avoids you have to do ":set bin", which would have effect for all
+ files you edit.
+ When writing a file the <EOL> for the last line is only written if
+ there was one in the original file (normally Vim appends an <EOL> to
+ the last line if there is none; this would make the file longer). See
+ the 'endofline' option.
+ ]=],
+ full_name = 'binary',
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('read/write/edit file in binary mode'),
+ type = 'bool',
+ varname = 'p_bin',
+ },
+ {
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = false },
+ desc = [=[
+ When writing a file and the following conditions are met, a BOM (Byte
+ Order Mark) is prepended to the file:
+ - this option is on
+ - the 'binary' option is off
+ - 'fileencoding' is "utf-8", "ucs-2", "ucs-4" or one of the little/big
+ endian variants.
+ Some applications use the BOM to recognize the encoding of the file.
+ Often used for UCS-2 files on MS-Windows. For other applications it
+ causes trouble, for example: "cat file1 file2" makes the BOM of file2
+ appear halfway through the resulting file. Gcc doesn't accept a BOM.
+ When Vim reads a file and 'fileencodings' starts with "ucs-bom", a
+ check for the presence of the BOM is done and 'bomb' set accordingly.
+ Unless 'binary' is set, it is removed from the first line, so that you
+ don't see it when editing. When you don't change the options, the BOM
+ will be restored when writing the file.
+ ]=],
+ full_name = 'bomb',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('a Byte Order Mark to the file'),
+ type = 'bool',
+ varname = 'p_bomb',
+ },
+ {
+ abbreviation = 'brk',
+ cb = 'did_set_breakat',
+ defaults = {
+ if_true = ' \t!@*-+;:,./?',
+ doc = '" ^I!@*-+;:,./?"',
+ },
+ desc = [=[
+ This option lets you choose which characters might cause a line
+ break if 'linebreak' is on. Only works for ASCII characters.
+ ]=],
+ full_name = 'breakat',
+ list = 'flags',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('characters that may cause a line break'),
+ type = 'string',
+ varname = 'p_breakat',
+ },
+ {
+ abbreviation = 'bri',
+ defaults = { if_true = false },
+ desc = [=[
+ Every wrapped line will continue visually indented (same amount of
+ space as the beginning of that line), thus preserving horizontal blocks
+ of text.
+ ]=],
+ full_name = 'breakindent',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('wrapped line repeats indent'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'briopt',
+ alloced = true,
+ cb = 'did_set_breakindentopt',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Settings for 'breakindent'. It can consist of the following optional
+ items and must be separated by a comma:
+ min:{n} Minimum text width that will be kept after
+ applying 'breakindent', even if the resulting
+ text should normally be narrower. This prevents
+ text indented almost to the right window border
+ occupying lot of vertical space when broken.
+ (default: 20)
+ shift:{n} After applying 'breakindent', the wrapped line's
+ beginning will be shifted by the given number of
+ characters. It permits dynamic French paragraph
+ indentation (negative) or emphasizing the line
+ continuation (positive).
+ (default: 0)
+ sbr Display the 'showbreak' value before applying the
+ additional indent.
+ (default: off)
+ list:{n} Adds an additional indent for lines that match a
+ numbered or bulleted list (using the
+ 'formatlistpat' setting).
+ list:-1 Uses the length of a match with 'formatlistpat'
+ for indentation.
+ (default: 0)
+ column:{n} Indent at column {n}. Will overrule the other
+ sub-options. Note: an additional indent may be
+ added for the 'showbreak' setting.
+ (default: off)
+ ]=],
+ expand_cb = 'expand_set_breakindentopt',
+ full_name = 'breakindentopt',
+ list = 'onecomma',
+ redraw = { 'current_buffer' },
+ scope = { 'window' },
+ short_desc = N_("settings for 'breakindent'"),
+ type = 'string',
+ },
+ {
+ abbreviation = 'bsdir',
+ defaults = {
+ if_true = '',
+ doc = '"last"',
+ },
+ desc = [=[
+ Which directory to use for the file browser:
+ last Use same directory as with last file browser, where a
+ file was opened or saved.
+ buffer Use the directory of the related buffer.
+ current Use the current directory.
+ {path} Use the specified directory
+ ]=],
+ enable_if = false,
+ full_name = 'browsedir',
+ scope = { 'global' },
+ short_desc = N_('which directory to start browsing in'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'bh',
+ alloced = true,
+ cb = 'did_set_bufhidden',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies what happens when a buffer is no longer
+ displayed in a window:
+ <empty> follow the global 'hidden' option
+ hide hide the buffer (don't unload it), even if 'hidden' is
+ not set
+ unload unload the buffer, even if 'hidden' is set; the
+ |:hide| command will also unload the buffer
+ delete delete the buffer from the buffer list, even if
+ 'hidden' is set; the |:hide| command will also delete
+ the buffer, making it behave like |:bdelete|
+ wipe wipe the buffer from the buffer list, even if
+ 'hidden' is set; the |:hide| command will also wipe
+ out the buffer, making it behave like |:bwipeout|
+
+ CAREFUL: when "unload", "delete" or "wipe" is used changes in a buffer
+ are lost without a warning. Also, these values may break autocommands
+ that switch between buffers temporarily.
+ This option is used together with 'buftype' and 'swapfile' to specify
+ special kinds of buffers. See |special-buffers|.
+ ]=],
+ expand_cb = 'expand_set_bufhidden',
+ full_name = 'bufhidden',
+ noglob = true,
+ scope = { 'buffer' },
+ short_desc = N_('what to do when buffer is no longer in window'),
+ type = 'string',
+ varname = 'p_bh',
+ },
+ {
+ abbreviation = 'bl',
+ cb = 'did_set_buflisted',
+ defaults = { if_true = true },
+ desc = [=[
+ When this option is set, the buffer shows up in the buffer list. If
+ it is reset it is not used for ":bnext", "ls", the Buffers menu, etc.
+ This option is reset by Vim for buffers that are only used to remember
+ a file name or marks. Vim sets it when starting to edit a buffer.
+ But not when moving to a buffer with ":buffer".
+ ]=],
+ full_name = 'buflisted',
+ noglob = true,
+ scope = { 'buffer' },
+ short_desc = N_('whether the buffer shows up in the buffer list'),
+ tags = { 'E85' },
+ type = 'bool',
+ varname = 'p_bl',
+ },
+ {
+ abbreviation = 'bt',
+ alloced = true,
+ cb = 'did_set_buftype',
+ defaults = { if_true = '' },
+ desc = [=[
+ The value of this option specifies the type of a buffer:
+ <empty> normal buffer
+ acwrite buffer will always be written with |BufWriteCmd|s
+ help help buffer (do not set this manually)
+ nofile buffer is not related to a file, will not be written
+ nowrite buffer will not be written
+ quickfix list of errors |:cwindow| or locations |:lwindow|
+ terminal |terminal-emulator| buffer
+ prompt buffer where only the last line can be edited, meant
+ to be used by a plugin, see |prompt-buffer|
+
+ This option is used together with 'bufhidden' and 'swapfile' to
+ specify special kinds of buffers. See |special-buffers|.
+ Also see |win_gettype()|, which returns the type of the window.
+
+ Be careful with changing this option, it can have many side effects!
+ One such effect is that Vim will not check the timestamp of the file,
+ if the file is changed by another program this will not be noticed.
+
+ A "quickfix" buffer is only used for the error list and the location
+ list. This value is set by the |:cwindow| and |:lwindow| commands and
+ you are not supposed to change it.
+
+ "nofile" and "nowrite" buffers are similar:
+ both: The buffer is not to be written to disk, ":w" doesn't
+ work (":w filename" does work though).
+ both: The buffer is never considered to be |'modified'|.
+ There is no warning when the changes will be lost, for
+ example when you quit Vim.
+ both: A swap file is only created when using too much memory
+ (when 'swapfile' has been reset there is never a swap
+ file).
+ nofile only: The buffer name is fixed, it is not handled like a
+ file name. It is not modified in response to a |:cd|
+ command.
+ both: When using ":e bufname" and already editing "bufname"
+ the buffer is made empty and autocommands are
+ triggered as usual for |:edit|.
+ *E676*
+ "acwrite" implies that the buffer name is not related to a file, like
+ "nofile", but it will be written. Thus, in contrast to "nofile" and
+ "nowrite", ":w" does work and a modified buffer can't be abandoned
+ without saving. For writing there must be matching |BufWriteCmd|,
+ |FileWriteCmd| or |FileAppendCmd| autocommands.
+ ]=],
+ expand_cb = 'expand_set_buftype',
+ full_name = 'buftype',
+ noglob = true,
+ scope = { 'buffer' },
+ tags = { 'E382' },
+ short_desc = N_('special type of buffer'),
+ type = 'string',
+ varname = 'p_bt',
+ },
+ {
+ abbreviation = 'cmp',
+ cb = 'did_set_casemap',
+ defaults = { if_true = 'internal,keepascii' },
+ deny_duplicates = true,
+ desc = [=[
+ Specifies details about changing the case of letters. It may contain
+ these words, separated by a comma:
+ internal Use internal case mapping functions, the current
+ locale does not change the case mapping. When
+ "internal" is omitted, the towupper() and towlower()
+ system library functions are used when available.
+ keepascii For the ASCII characters (0x00 to 0x7f) use the US
+ case mapping, the current locale is not effective.
+ This probably only matters for Turkish.
+ ]=],
+ expand_cb = 'expand_set_casemap',
+ full_name = 'casemap',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('specifies how case of letters is changed'),
+ type = 'string',
+ varname = 'p_cmp',
+ },
+ {
+ abbreviation = 'cdh',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, |:cd|, |:tcd| and |:lcd| without an argument changes the
+ current working directory to the |$HOME| directory like in Unix.
+ When off, those commands just print the current directory name.
+ On Unix this option has no effect.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'cdhome',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_(':cd without argument goes to the home directory'),
+ type = 'bool',
+ varname = 'p_cdh',
+ },
+ {
+ abbreviation = 'cd',
+ defaults = {
+ if_true = ',,',
+ doc = 'equivalent to $CDPATH or ",,"',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ This is a list of directories which will be searched when using the
+ |:cd|, |:tcd| and |:lcd| commands, provided that the directory being
+ searched for has a relative path, not an absolute part starting with
+ "/", "./" or "../", the 'cdpath' option is not used then.
+ The 'cdpath' option's value has the same form and semantics as
+ |'path'|. Also see |file-searching|.
+ The default value is taken from $CDPATH, with a "," prepended to look
+ in the current directory first.
+ If the default value taken from $CDPATH is not what you want, include
+ a modified version of the following command in your vimrc file to
+ override it: >
+ :let &cdpath = ',' .. substitute(substitute($CDPATH, '[, ]', '\\\0', 'g'), ':', ',', 'g')
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ (parts of 'cdpath' can be passed to the shell to expand file names).
+ ]=],
+ expand = true,
+ full_name = 'cdpath',
+ list = 'comma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories searched with ":cd"'),
+ tags = { 'E344', 'E346' },
+ type = 'string',
+ varname = 'p_cdpath',
+ },
+ {
+ cb = 'did_set_cedit',
+ defaults = {
+ if_true = macros('CTRL_F_STR'),
+ doc = 'CTRL-F',
+ },
+ desc = [=[
+ The key used in Command-line Mode to open the command-line window.
+ Only non-printable keys are allowed.
+ The key can be specified as a single character, but it is difficult to
+ type. The preferred way is to use the <> notation. Examples: >
+ :exe "set cedit=\\<C-Y>"
+ :exe "set cedit=\\<Esc>"
+ < |Nvi| also has this option, but it only uses the first character.
+ See |cmdwin|.
+ ]=],
+ full_name = 'cedit',
+ scope = { 'global' },
+ short_desc = N_('used to open the command-line window'),
+ type = 'string',
+ varname = 'p_cedit',
+ },
+ {
+ defaults = { if_true = 0 },
+ desc = [=[
+ |channel| connected to the buffer, or 0 if no channel is connected.
+ In a |:terminal| buffer this is the terminal channel.
+ Read-only.
+ ]=],
+ full_name = 'channel',
+ no_mkrc = true,
+ nodefault = true,
+ scope = { 'buffer' },
+ short_desc = N_('Channel connected to the buffer'),
+ type = 'number',
+ varname = 'p_channel',
+ },
+ {
+ abbreviation = 'ccv',
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ An expression that is used for character encoding conversion. It is
+ evaluated when a file that is to be read or has been written has a
+ different encoding from what is desired.
+ 'charconvert' is not used when the internal iconv() function is
+ supported and is able to do the conversion. Using iconv() is
+ preferred, because it is much faster.
+ 'charconvert' is not used when reading stdin |--|, because there is no
+ file to convert from. You will have to save the text in a file first.
+ The expression must return zero, false or an empty string for success,
+ non-zero or true for failure.
+ See |encoding-names| for possible encoding names.
+ Additionally, names given in 'fileencodings' and 'fileencoding' are
+ used.
+ Conversion between "latin1", "unicode", "ucs-2", "ucs-4" and "utf-8"
+ is done internally by Vim, 'charconvert' is not used for this.
+ Also used for Unicode conversion.
+ Example: >
+ set charconvert=CharConvert()
+ fun CharConvert()
+ system("recode "
+ \ .. v:charconvert_from .. ".." .. v:charconvert_to
+ \ .. " <" .. v:fname_in .. " >" .. v:fname_out)
+ return v:shell_error
+ endfun
+ < The related Vim variables are:
+ v:charconvert_from name of the current encoding
+ v:charconvert_to name of the desired encoding
+ v:fname_in name of the input file
+ v:fname_out name of the output file
+ Note that v:fname_in and v:fname_out will never be the same.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'charconvert',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('expression for character encoding conversion'),
+ type = 'string',
+ tags = { 'E202', 'E214', 'E513' },
+ varname = 'p_ccv',
+ },
+ {
+ abbreviation = 'cin',
+ defaults = { if_true = false },
+ desc = [=[
+ Enables automatic C program indenting. See 'cinkeys' to set the keys
+ that trigger reindenting in insert mode and 'cinoptions' to set your
+ preferred indent style.
+ If 'indentexpr' is not empty, it overrules 'cindent'.
+ If 'lisp' is not on and both 'indentexpr' and 'equalprg' are empty,
+ the "=" operator indents using this algorithm rather than calling an
+ external program.
+ See |C-indenting|.
+ When you don't like the way 'cindent' works, try the 'smartindent'
+ option or 'indentexpr'.
+ ]=],
+ full_name = 'cindent',
+ scope = { 'buffer' },
+ short_desc = N_('do C program indenting'),
+ type = 'bool',
+ varname = 'p_cin',
+ },
+ {
+ abbreviation = 'cink',
+ alloced = true,
+ defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of keys that, when typed in Insert mode, cause reindenting of
+ the current line. Only used if 'cindent' is on and 'indentexpr' is
+ empty.
+ For the format of this option see |cinkeys-format|.
+ See |C-indenting|.
+ ]=],
+ full_name = 'cinkeys',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("keys that trigger indent when 'cindent' is set"),
+ type = 'string',
+ varname = 'p_cink',
+ },
+ {
+ abbreviation = 'cino',
+ alloced = true,
+ cb = 'did_set_cinoptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ The 'cinoptions' affect the way 'cindent' reindents lines in a C
+ program. See |cinoptions-values| for the values of this option, and
+ |C-indenting| for info on C indenting in general.
+ ]=],
+ full_name = 'cinoptions',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("how to do indenting when 'cindent' is set"),
+ type = 'string',
+ varname = 'p_cino',
+ },
+ {
+ abbreviation = 'cinw',
+ alloced = true,
+ defaults = { if_true = 'if,else,while,do,for,switch' },
+ deny_duplicates = true,
+ desc = [=[
+ These keywords start an extra indent in the next line when
+ 'smartindent' or 'cindent' is set. For 'cindent' this is only done at
+ an appropriate place (inside {}).
+ Note that 'ignorecase' isn't used for 'cinwords'. If case doesn't
+ matter, include the keyword both the uppercase and lowercase:
+ "if,If,IF".
+ ]=],
+ full_name = 'cinwords',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("words where 'si' and 'cin' add an indent"),
+ type = 'string',
+ varname = 'p_cinw',
+ },
+ {
+ abbreviation = 'cinsd',
+ alloced = true,
+ defaults = { if_true = 'public,protected,private' },
+ deny_duplicates = true,
+ desc = [=[
+ Keywords that are interpreted as a C++ scope declaration by |cino-g|.
+ Useful e.g. for working with the Qt framework that defines additional
+ scope declarations "signals", "public slots" and "private slots": >
+ set cinscopedecls+=signals,public\ slots,private\ slots
+ <
+ ]=],
+ full_name = 'cinscopedecls',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("words that are recognized by 'cino-g'"),
+ type = 'string',
+ varname = 'p_cinsd',
+ },
+ {
+ abbreviation = 'cb',
+ cb = 'did_set_clipboard',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option is a list of comma-separated names.
+ These names are recognized:
+
+ *clipboard-unnamed*
+ unnamed When included, Vim will use the clipboard register "*"
+ for all yank, delete, change and put operations which
+ would normally go to the unnamed register. When a
+ register is explicitly specified, it will always be
+ used regardless of whether "unnamed" is in 'clipboard'
+ or not. The clipboard register can always be
+ explicitly accessed using the "* notation. Also see
+ |clipboard|.
+
+ *clipboard-unnamedplus*
+ unnamedplus A variant of the "unnamed" flag which uses the
+ clipboard register "+" (|quoteplus|) instead of
+ register "*" for all yank, delete, change and put
+ operations which would normally go to the unnamed
+ register. When "unnamed" is also included to the
+ option, yank and delete operations (but not put)
+ will additionally copy the text into register
+ "*". See |clipboard|.
+ ]=],
+ deny_duplicates = true,
+ expand_cb = 'expand_set_clipboard',
+ full_name = 'clipboard',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('use the clipboard as the unnamed register'),
+ type = 'string',
+ varname = 'p_cb',
+ },
+ {
+ abbreviation = 'ch',
+ cb = 'did_set_cmdheight',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Number of screen lines to use for the command-line. Helps avoiding
+ |hit-enter| prompts.
+ The value of this option is stored with the tab page, so that each tab
+ page can have a different value.
+
+ When 'cmdheight' is zero, there is no command-line unless it is being
+ used. The command-line will cover the last line of the screen when
+ shown.
+
+ WARNING: `cmdheight=0` is considered experimental. Expect some
+ unwanted behaviour. Some 'shortmess' flags and similar
+ mechanism might fail to take effect, causing unwanted hit-enter
+ prompts. Some informative messages, both from Nvim itself and
+ plugins, will not be displayed.
+ ]=],
+ full_name = 'cmdheight',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('number of lines to use for the command-line'),
+ type = 'number',
+ varname = 'p_ch',
+ },
+ {
+ abbreviation = 'cwh',
+ defaults = { if_true = 7 },
+ desc = [=[
+ Number of screen lines to use for the command-line window. |cmdwin|
+ ]=],
+ full_name = 'cmdwinheight',
+ scope = { 'global' },
+ short_desc = N_('height of the command-line window'),
+ type = 'number',
+ varname = 'p_cwh',
+ },
+ {
+ abbreviation = 'cc',
+ cb = 'did_set_colorcolumn',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ 'colorcolumn' is a comma-separated list of screen columns that are
+ highlighted with ColorColumn |hl-ColorColumn|. Useful to align
+ text. Will make screen redrawing slower.
+ The screen column can be an absolute number, or a number preceded with
+ '+' or '-', which is added to or subtracted from 'textwidth'. >
+
+ :set cc=+1 " highlight column after 'textwidth'
+ :set cc=+1,+2,+3 " highlight three columns after 'textwidth'
+ :hi ColorColumn ctermbg=lightgrey guibg=lightgrey
+ <
+ When 'textwidth' is zero then the items with '-' and '+' are not used.
+ A maximum of 256 columns are highlighted.
+ ]=],
+ full_name = 'colorcolumn',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('columns to highlight'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'co',
+ defaults = {
+ if_true = macros('DFLT_COLS'),
+ doc = '80 or terminal width',
+ },
+ desc = [=[
+ Number of columns of the screen. Normally this is set by the terminal
+ initialization and does not have to be set by hand.
+ When Vim is running in the GUI or in a resizable window, setting this
+ option will cause the window size to be changed. When you only want
+ to use the size for the GUI, put the command in your |ginit.vim| file.
+ When you set this option and Vim is unable to change the physical
+ number of columns of the display, the display may be messed up. For
+ the GUI it is always possible and Vim limits the number of columns to
+ what fits on the screen. You can use this command to get the widest
+ window possible: >
+ :set columns=9999
+ < Minimum value is 12, maximum value is 10000.
+ ]=],
+ full_name = 'columns',
+ no_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('number of columns in the display'),
+ tags = { 'E594' },
+ type = 'number',
+ varname = 'p_columns',
+ },
+ {
+ abbreviation = 'com',
+ alloced = true,
+ cb = 'did_set_comments',
+ defaults = { if_true = 's1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-,fb:•' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of strings that can start a comment line. See
+ |format-comments|. See |option-backslash| about using backslashes to
+ insert a space.
+ ]=],
+ full_name = 'comments',
+ list = 'onecomma',
+ redraw = { 'curswant' },
+ scope = { 'buffer' },
+ short_desc = N_('patterns that can start a comment line'),
+ tags = { 'E524', 'E525' },
+ type = 'string',
+ varname = 'p_com',
+ },
+ {
+ abbreviation = 'cms',
+ alloced = true,
+ cb = 'did_set_commentstring',
+ defaults = { if_true = '' },
+ desc = [=[
+ A template for a comment. The "%s" in the value is replaced with the
+ comment text. For example, C uses "/*%s*/". Currently only used to
+ add markers for folding, see |fold-marker|.
+ ]=],
+ full_name = 'commentstring',
+ redraw = { 'curswant' },
+ scope = { 'buffer' },
+ short_desc = N_('template for comments; used for fold marker'),
+ tags = { 'E537' },
+ type = 'string',
+ varname = 'p_cms',
+ },
+ {
+ abbreviation = 'cp',
+ defaults = { if_true = false },
+ full_name = 'compatible',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'cpt',
+ alloced = true,
+ cb = 'did_set_complete',
+ defaults = { if_true = '.,w,b,u,t' },
+ deny_duplicates = true,
+ desc = [=[
+ This option specifies how keyword completion |ins-completion| works
+ when CTRL-P or CTRL-N are used. It is also used for whole-line
+ completion |i_CTRL-X_CTRL-L|. It indicates the type of completion
+ and the places to scan. It is a comma-separated list of flags:
+ . scan the current buffer ('wrapscan' is ignored)
+ w scan buffers from other windows
+ b scan other loaded buffers that are in the buffer list
+ u scan the unloaded buffers that are in the buffer list
+ U scan the buffers that are not in the buffer list
+ k scan the files given with the 'dictionary' option
+ kspell use the currently active spell checking |spell|
+ k{dict} scan the file {dict}. Several "k" flags can be given,
+ patterns are valid too. For example: >
+ :set cpt=k/usr/dict/*,k~/spanish
+ < s scan the files given with the 'thesaurus' option
+ s{tsr} scan the file {tsr}. Several "s" flags can be given, patterns
+ are valid too.
+ i scan current and included files
+ d scan current and included files for defined name or macro
+ |i_CTRL-X_CTRL-D|
+ ] tag completion
+ t same as "]"
+ f scan the buffer names (as opposed to buffer contents)
+
+ Unloaded buffers are not loaded, thus their autocmds |:autocmd| are
+ not executed, this may lead to unexpected completions from some files
+ (gzipped files for example). Unloaded buffers are not scanned for
+ whole-line completion.
+
+ As you can see, CTRL-N and CTRL-P can be used to do any 'iskeyword'-
+ based expansion (e.g., dictionary |i_CTRL-X_CTRL-K|, included patterns
+ |i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]| and normal expansions).
+ ]=],
+ expand_cb = 'expand_set_complete',
+ full_name = 'complete',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('specify how Insert mode completion works'),
+ tags = { 'E535' },
+ type = 'string',
+ varname = 'p_cpt',
+ },
+ {
+ abbreviation = 'cocu',
+ alloced = true,
+ cb = 'did_set_concealcursor',
+ defaults = { if_true = '' },
+ desc = [=[
+ Sets the modes in which text in the cursor line can also be concealed.
+ When the current mode is listed then concealing happens just like in
+ other lines.
+ n Normal mode
+ v Visual mode
+ i Insert mode
+ c Command line editing, for 'incsearch'
+
+ 'v' applies to all lines in the Visual area, not only the cursor.
+ A useful value is "nc". This is used in help files. So long as you
+ are moving around text is concealed, but when starting to insert text
+ or selecting a Visual area the concealed text is displayed, so that
+ you can see what you are doing.
+ Keep in mind that the cursor position is not always where it's
+ displayed. E.g., when moving vertically it may change column.
+ ]=],
+ expand_cb = 'expand_set_concealcursor',
+ full_name = 'concealcursor',
+ list = 'flags',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('whether concealable text is hidden in cursor line'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'cole',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Determine how text with the "conceal" syntax attribute |:syn-conceal|
+ is shown:
+
+ Value Effect ~
+ 0 Text is shown normally
+ 1 Each block of concealed text is replaced with one
+ character. If the syntax item does not have a custom
+ replacement character defined (see |:syn-cchar|) the
+ character defined in 'listchars' is used.
+ It is highlighted with the "Conceal" highlight group.
+ 2 Concealed text is completely hidden unless it has a
+ custom replacement character defined (see
+ |:syn-cchar|).
+ 3 Concealed text is completely hidden.
+
+ Note: in the cursor line concealed text is not hidden, so that you can
+ edit and copy the text. This can be changed with the 'concealcursor'
+ option.
+ ]=],
+ full_name = 'conceallevel',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('whether concealable text is shown or hidden'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'cfu',
+ alloced = true,
+ cb = 'did_set_completefunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used for Insert mode completion
+ with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U|
+ See |complete-functions| for an explanation of how the function is
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'completefunc',
+ func = true,
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('function to be used for Insert mode completion'),
+ type = 'string',
+ varname = 'p_cfu',
+ },
+ {
+ abbreviation = 'cot',
+ cb = 'did_set_completeopt',
+ defaults = { if_true = 'menu,preview' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of options for Insert mode completion
+ |ins-completion|. The supported values are:
+
+ menu Use a popup menu to show the possible completions. The
+ menu is only shown when there is more than one match and
+ sufficient colors are available. |ins-completion-menu|
+
+ menuone Use the popup menu also when there is only one match.
+ Useful when there is additional information about the
+ match, e.g., what file it comes from.
+
+ longest Only insert the longest common text of the matches. If
+ the menu is displayed you can use CTRL-L to add more
+ characters. Whether case is ignored depends on the kind
+ of completion. For buffer text the 'ignorecase' option is
+ used.
+
+ preview Show extra information about the currently selected
+ completion in the preview window. Only works in
+ combination with "menu" or "menuone".
+
+ noinsert Do not insert any text for a match until the user selects
+ a match from the menu. Only works in combination with
+ "menu" or "menuone". No effect if "longest" is present.
+
+ noselect Do not select a match in the menu, force the user to
+ select one from the menu. Only works in combination with
+ "menu" or "menuone".
+ ]=],
+ expand_cb = 'expand_set_completeopt',
+ full_name = 'completeopt',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('options for Insert mode completion'),
+ type = 'string',
+ varname = 'p_cot',
+ },
+ {
+ abbreviation = 'csl',
+ cb = 'did_set_completeslash',
+ defaults = { if_true = '' },
+ desc = [=[
+ only for MS-Windows
+ When this option is set it overrules 'shellslash' for completion:
+ - When this option is set to "slash", a forward slash is used for path
+ completion in insert mode. This is useful when editing HTML tag, or
+ Makefile with 'noshellslash' on MS-Windows.
+ - When this option is set to "backslash", backslash is used. This is
+ useful when editing a batch file with 'shellslash' set on MS-Windows.
+ - When this option is empty, same character is used as for
+ 'shellslash'.
+ For Insert mode completion the buffer-local value is used. For
+ command line completion the global value is used.
+ ]=],
+ enable_if = 'BACKSLASH_IN_FILENAME',
+ expand_cb = 'expand_set_completeslash',
+ full_name = 'completeslash',
+ scope = { 'buffer' },
+ type = 'string',
+ varname = 'p_csl',
+ },
+ {
+ abbreviation = 'cf',
+ defaults = { if_true = false },
+ desc = [=[
+ When 'confirm' is on, certain operations that would normally
+ fail because of unsaved changes to a buffer, e.g. ":q" and ":e",
+ instead raise a dialog asking if you wish to save the current
+ file(s). You can still use a ! to unconditionally |abandon| a buffer.
+ If 'confirm' is off you can still activate confirmation for one
+ command only (this is most useful in mappings) with the |:confirm|
+ command.
+ Also see the |confirm()| function and the 'v' flag in 'guioptions'.
+ ]=],
+ full_name = 'confirm',
+ scope = { 'global' },
+ short_desc = N_('ask what to do about unsaved/read-only files'),
+ type = 'bool',
+ varname = 'p_confirm',
+ },
+ {
+ abbreviation = 'ci',
+ defaults = { if_true = false },
+ desc = [=[
+ Copy the structure of the existing lines indent when autoindenting a
+ new line. Normally the new indent is reconstructed by a series of
+ tabs followed by spaces as required (unless |'expandtab'| is enabled,
+ in which case only spaces are used). Enabling this option makes the
+ new line copy whatever characters were used for indenting on the
+ existing line. 'expandtab' has no effect on these characters, a Tab
+ remains a Tab. If the new indent is greater than on the existing
+ line, the remaining space is filled in the normal manner.
+ See 'preserveindent'.
+ ]=],
+ full_name = 'copyindent',
+ scope = { 'buffer' },
+ short_desc = N_("make 'autoindent' use existing indent structure"),
+ type = 'bool',
+ varname = 'p_ci',
+ },
+ {
+ abbreviation = 'cpo',
+ cb = 'did_set_cpoptions',
+ defaults = { if_true = macros('CPO_VIM') },
+ desc = [=[
+ A sequence of single character flags. When a character is present
+ this indicates Vi-compatible behavior. This is used for things where
+ not being Vi-compatible is mostly or sometimes preferred.
+ 'cpoptions' stands for "compatible-options".
+ Commas can be added for readability.
+ To avoid problems with flags that are added in the future, use the
+ "+=" and "-=" feature of ":set" |add-option-flags|.
+
+ contains behavior ~
+ *cpo-a*
+ a When included, a ":read" command with a file name
+ argument will set the alternate file name for the
+ current window.
+ *cpo-A*
+ A When included, a ":write" command with a file name
+ argument will set the alternate file name for the
+ current window.
+ *cpo-b*
+ b "\|" in a ":map" command is recognized as the end of
+ the map command. The '\' is included in the mapping,
+ the text after the '|' is interpreted as the next
+ command. Use a CTRL-V instead of a backslash to
+ include the '|' in the mapping. Applies to all
+ mapping, abbreviation, menu and autocmd commands.
+ See also |map_bar|.
+ *cpo-B*
+ B A backslash has no special meaning in mappings,
+ abbreviations, user commands and the "to" part of the
+ menu commands. Remove this flag to be able to use a
+ backslash like a CTRL-V. For example, the command
+ ":map X \\<Esc>" results in X being mapped to:
+ 'B' included: "\^[" (^[ is a real <Esc>)
+ 'B' excluded: "<Esc>" (5 characters)
+ *cpo-c*
+ c Searching continues at the end of any match at the
+ cursor position, but not further than the start of the
+ next line. When not present searching continues
+ one character from the cursor position. With 'c'
+ "abababababab" only gets three matches when repeating
+ "/abab", without 'c' there are five matches.
+ *cpo-C*
+ C Do not concatenate sourced lines that start with a
+ backslash. See |line-continuation|.
+ *cpo-d*
+ d Using "./" in the 'tags' option doesn't mean to use
+ the tags file relative to the current file, but the
+ tags file in the current directory.
+ *cpo-D*
+ D Can't use CTRL-K to enter a digraph after Normal mode
+ commands with a character argument, like |r|, |f| and
+ |t|.
+ *cpo-e*
+ e When executing a register with ":@r", always add a
+ <CR> to the last line, also when the register is not
+ linewise. If this flag is not present, the register
+ is not linewise and the last line does not end in a
+ <CR>, then the last line is put on the command-line
+ and can be edited before hitting <CR>.
+ *cpo-E*
+ E It is an error when using "y", "d", "c", "g~", "gu" or
+ "gU" on an Empty region. The operators only work when
+ at least one character is to be operated on. Example:
+ This makes "y0" fail in the first column.
+ *cpo-f*
+ f When included, a ":read" command with a file name
+ argument will set the file name for the current buffer,
+ if the current buffer doesn't have a file name yet.
+ *cpo-F*
+ F When included, a ":write" command with a file name
+ argument will set the file name for the current
+ buffer, if the current buffer doesn't have a file name
+ yet. Also see |cpo-P|.
+ *cpo-i*
+ i When included, interrupting the reading of a file will
+ leave it modified.
+ *cpo-I*
+ I When moving the cursor up or down just after inserting
+ indent for 'autoindent', do not delete the indent.
+ *cpo-J*
+ J A |sentence| has to be followed by two spaces after
+ the '.', '!' or '?'. A <Tab> is not recognized as
+ white space.
+ *cpo-K*
+ K Don't wait for a key code to complete when it is
+ halfway through a mapping. This breaks mapping
+ <F1><F1> when only part of the second <F1> has been
+ read. It enables cancelling the mapping by typing
+ <F1><Esc>.
+ *cpo-l*
+ l Backslash in a [] range in a search pattern is taken
+ literally, only "\]", "\^", "\-" and "\\" are special.
+ See |/[]|
+ 'l' included: "/[ \t]" finds <Space>, '\' and 't'
+ 'l' excluded: "/[ \t]" finds <Space> and <Tab>
+ *cpo-L*
+ L When the 'list' option is set, 'wrapmargin',
+ 'textwidth', 'softtabstop' and Virtual Replace mode
+ (see |gR|) count a <Tab> as two characters, instead of
+ the normal behavior of a <Tab>.
+ *cpo-m*
+ m When included, a showmatch will always wait half a
+ second. When not included, a showmatch will wait half
+ a second or until a character is typed. |'showmatch'|
+ *cpo-M*
+ M When excluded, "%" matching will take backslashes into
+ account. Thus in "( \( )" and "\( ( \)" the outer
+ parenthesis match. When included "%" ignores
+ backslashes, which is Vi compatible.
+ *cpo-n*
+ n When included, the column used for 'number' and
+ 'relativenumber' will also be used for text of wrapped
+ lines.
+ *cpo-o*
+ o Line offset to search command is not remembered for
+ next search.
+ *cpo-O*
+ O Don't complain if a file is being overwritten, even
+ when it didn't exist when editing it. This is a
+ protection against a file unexpectedly created by
+ someone else. Vi didn't complain about this.
+ *cpo-p*
+ p Vi compatible Lisp indenting. When not present, a
+ slightly better algorithm is used.
+ *cpo-P*
+ P When included, a ":write" command that appends to a
+ file will set the file name for the current buffer, if
+ the current buffer doesn't have a file name yet and
+ the 'F' flag is also included |cpo-F|.
+ *cpo-q*
+ q When joining multiple lines leave the cursor at the
+ position where it would be when joining two lines.
+ *cpo-r*
+ r Redo ("." command) uses "/" to repeat a search
+ command, instead of the actually used search string.
+ *cpo-R*
+ R Remove marks from filtered lines. Without this flag
+ marks are kept like |:keepmarks| was used.
+ *cpo-s*
+ s Set buffer options when entering the buffer for the
+ first time. This is like it is in Vim version 3.0.
+ And it is the default. If not present the options are
+ set when the buffer is created.
+ *cpo-S*
+ S Set buffer options always when entering a buffer
+ (except 'readonly', 'fileformat', 'filetype' and
+ 'syntax'). This is the (most) Vi compatible setting.
+ The options are set to the values in the current
+ buffer. When you change an option and go to another
+ buffer, the value is copied. Effectively makes the
+ buffer options global to all buffers.
+
+ 's' 'S' copy buffer options
+ no no when buffer created
+ yes no when buffer first entered (default)
+ X yes each time when buffer entered (vi comp.)
+ *cpo-t*
+ t Search pattern for the tag command is remembered for
+ "n" command. Otherwise Vim only puts the pattern in
+ the history for search pattern, but doesn't change the
+ last used search pattern.
+ *cpo-u*
+ u Undo is Vi compatible. See |undo-two-ways|.
+ *cpo-v*
+ v Backspaced characters remain visible on the screen in
+ Insert mode. Without this flag the characters are
+ erased from the screen right away. With this flag the
+ screen newly typed text overwrites backspaced
+ characters.
+ *cpo-W*
+ W Don't overwrite a readonly file. When omitted, ":w!"
+ overwrites a readonly file, if possible.
+ *cpo-x*
+ x <Esc> on the command-line executes the command-line.
+ The default in Vim is to abandon the command-line,
+ because <Esc> normally aborts a command. |c_<Esc>|
+ *cpo-X*
+ X When using a count with "R" the replaced text is
+ deleted only once. Also when repeating "R" with "."
+ and a count.
+ *cpo-y*
+ y A yank command can be redone with ".". Think twice if
+ you really want to use this, it may break some
+ plugins, since most people expect "." to only repeat a
+ change.
+ *cpo-Z*
+ Z When using "w!" while the 'readonly' option is set,
+ don't reset 'readonly'.
+ *cpo-!*
+ ! When redoing a filter command, use the last used
+ external command, whatever it was. Otherwise the last
+ used -filter- command is used.
+ *cpo-$*
+ $ When making a change to one line, don't redisplay the
+ line, but put a '$' at the end of the changed text.
+ The changed text will be overwritten when you type the
+ new text. The line is redisplayed if you type any
+ command that moves the cursor from the insertion
+ point.
+ *cpo-%*
+ % Vi-compatible matching is done for the "%" command.
+ Does not recognize "#if", "#endif", etc.
+ Does not recognize "/*" and "*/".
+ Parens inside single and double quotes are also
+ counted, causing a string that contains a paren to
+ disturb the matching. For example, in a line like
+ "if (strcmp("foo(", s))" the first paren does not
+ match the last one. When this flag is not included,
+ parens inside single and double quotes are treated
+ specially. When matching a paren outside of quotes,
+ everything inside quotes is ignored. When matching a
+ paren inside quotes, it will find the matching one (if
+ there is one). This works very well for C programs.
+ This flag is also used for other features, such as
+ C-indenting.
+ *cpo-+*
+ + When included, a ":write file" command will reset the
+ 'modified' flag of the buffer, even though the buffer
+ itself may still be different from its file.
+ *cpo->*
+ > When appending to a register, put a line break before
+ the appended text.
+ *cpo-;*
+ ; When using |,| or |;| to repeat the last |t| search
+ and the cursor is right in front of the searched
+ character, the cursor won't move. When not included,
+ the cursor would skip over it and jump to the
+ following occurrence.
+ *cpo-_*
+ _ When using |cw| on a word, do not include the
+ whitespace following the word in the motion.
+ ]=],
+ expand_cb = 'expand_set_cpoptions',
+ full_name = 'cpoptions',
+ list = 'flags',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('flags for Vi-compatible behavior'),
+ tags = { 'cpo' },
+ type = 'string',
+ varname = 'p_cpo',
+ },
+ {
+ abbreviation = 'crb',
+ defaults = { if_true = false },
+ desc = [=[
+ When this option is set, as the cursor in the current
+ window moves other cursorbound windows (windows that also have
+ this option set) move their cursors to the corresponding line and
+ column. This option is useful for viewing the
+ differences between two versions of a file (see 'diff'); in diff mode,
+ inserted and deleted lines (though not characters within a line) are
+ taken into account.
+ ]=],
+ full_name = 'cursorbind',
+ pv_name = 'p_crbind',
+ scope = { 'window' },
+ short_desc = N_('move cursor in window as it moves in other windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'cuc',
+ defaults = { if_true = false },
+ desc = [=[
+ Highlight the screen column of the cursor with CursorColumn
+ |hl-CursorColumn|. Useful to align text. Will make screen redrawing
+ slower.
+ If you only want the highlighting in the current window you can use
+ these autocommands: >
+ au WinLeave * set nocursorline nocursorcolumn
+ au WinEnter * set cursorline cursorcolumn
+ <
+ ]=],
+ full_name = 'cursorcolumn',
+ redraw = { 'current_window_only' },
+ scope = { 'window' },
+ short_desc = N_('highlight the screen column of the cursor'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'cul',
+ defaults = { if_true = false },
+ desc = [=[
+ Highlight the text line of the cursor with CursorLine |hl-CursorLine|.
+ Useful to easily spot the cursor. Will make screen redrawing slower.
+ When Visual mode is active the highlighting isn't used to make it
+ easier to see the selected text.
+ ]=],
+ full_name = 'cursorline',
+ redraw = { 'current_window_only' },
+ scope = { 'window' },
+ short_desc = N_('highlight the screen line of the cursor'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'culopt',
+ cb = 'did_set_cursorlineopt',
+ defaults = { if_true = 'both' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of settings for how 'cursorline' is displayed.
+ Valid values:
+ "line" Highlight the text line of the cursor with
+ CursorLine |hl-CursorLine|.
+ "screenline" Highlight only the screen line of the cursor with
+ CursorLine |hl-CursorLine|.
+ "number" Highlight the line number of the cursor with
+ CursorLineNr |hl-CursorLineNr|.
+
+ Special value:
+ "both" Alias for the values "line,number".
+
+ "line" and "screenline" cannot be used together.
+ ]=],
+ expand_cb = 'expand_set_cursorlineopt',
+ full_name = 'cursorlineopt',
+ list = 'onecomma',
+ redraw = { 'current_window_only' },
+ scope = { 'window' },
+ short_desc = N_("settings for 'cursorline'"),
+ type = 'string',
+ },
+ {
+ cb = 'did_set_debug',
+ defaults = { if_true = '' },
+ desc = [=[
+ These values can be used:
+ msg Error messages that would otherwise be omitted will be given
+ anyway.
+ throw Error messages that would otherwise be omitted will be given
+ anyway and also throw an exception and set |v:errmsg|.
+ beep A message will be given when otherwise only a beep would be
+ produced.
+ The values can be combined, separated by a comma.
+ "msg" and "throw" are useful for debugging 'foldexpr', 'formatexpr' or
+ 'indentexpr'.
+ ]=],
+ expand_cb = 'expand_set_debug',
+ full_name = 'debug',
+ scope = { 'global' },
+ short_desc = N_('to "msg" to see all error messages'),
+ type = 'string',
+ varname = 'p_debug',
+ },
+ {
+ abbreviation = 'def',
+ alloced = true,
+ defaults = { if_true = '' },
+ desc = [=[
+ Pattern to be used to find a macro definition. It is a search
+ pattern, just like for the "/" command. This option is used for the
+ commands like "[i" and "[d" |include-search|. The 'isident' option is
+ used to recognize the defined name after the match: >
+ {match with 'define'}{non-ID chars}{defined name}{non-ID char}
+ < See |option-backslash| about inserting backslashes to include a space
+ or backslash.
+ For C++ this value would be useful, to include const type declarations: >
+ ^\(#\s*define\|[a-z]*\s*const\s*[a-z]*\)
+ < You can also use "\ze" just before the name and continue the pattern
+ to check what is following. E.g. for Javascript, if a function is
+ defined with `func_name = function(args)`: >
+ ^\s*\ze\i\+\s*=\s*function(
+ < If the function is defined with `func_name : function() {...`: >
+ ^\s*\ze\i\+\s*[:]\s*(*function\s*(
+ < When using the ":set" command, you need to double the backslashes!
+ To avoid that use `:let` with a single quote string: >
+ let &l:define = '^\s*\ze\k\+\s*=\s*function('
+ <
+ ]=],
+ full_name = 'define',
+ redraw = { 'curswant' },
+ scope = { 'global', 'buffer' },
+ short_desc = N_('pattern to be used to find a macro definition'),
+ type = 'string',
+ varname = 'p_def',
+ },
+ {
+ abbreviation = 'deco',
+ defaults = { if_true = false },
+ desc = [=[
+ If editing Unicode and this option is set, backspace and Normal mode
+ "x" delete each combining character on its own. When it is off (the
+ default) the character along with its combining characters are
+ deleted.
+ Note: When 'delcombine' is set "xx" may work differently from "2x"!
+
+ This is useful for Arabic, Hebrew and many other languages where one
+ may have combining characters overtop of base characters, and want
+ to remove only the combining ones.
+ ]=],
+ full_name = 'delcombine',
+ scope = { 'global' },
+ short_desc = N_('delete combining characters on their own'),
+ type = 'bool',
+ varname = 'p_deco',
+ },
+ {
+ abbreviation = 'dict',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of file names, separated by commas, that are used to lookup words
+ for keyword completion commands |i_CTRL-X_CTRL-K|. Each file should
+ contain a list of words. This can be one word per line, or several
+ words per line, separated by non-keyword characters (white space is
+ preferred). Maximum line length is 510 bytes.
+
+ When this option is empty or an entry "spell" is present, and spell
+ checking is enabled, words in the word lists for the currently active
+ 'spelllang' are used. See |spell|.
+
+ To include a comma in a file name precede it with a backslash. Spaces
+ after a comma are ignored, otherwise spaces are included in the file
+ name. See |option-backslash| about using backslashes.
+ This has nothing to do with the |Dictionary| variable type.
+ Where to find a list of words?
+ - BSD/macOS include the "/usr/share/dict/words" file.
+ - Try "apt install spell" to get the "/usr/share/dict/words" file on
+ apt-managed systems (Debian/Ubuntu).
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ directories from the list. This avoids problems when a future version
+ uses another default.
+ Backticks cannot be used in this option for security reasons.
+ ]=],
+ expand = true,
+ full_name = 'dictionary',
+ list = 'onecomma',
+ normal_dname_chars = true,
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of file names used for keyword completion'),
+ type = 'string',
+ varname = 'p_dict',
+ },
+ {
+ cb = 'did_set_diff',
+ defaults = { if_true = false },
+ desc = [=[
+ Join the current window in the group of windows that shows differences
+ between files. See |diff-mode|.
+ ]=],
+ full_name = 'diff',
+ noglob = true,
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('diff mode for the current window'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'dex',
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to obtain a diff file (either ed-style
+ or unified-style) from two versions of a file. See |diff-diffexpr|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'diffexpr',
+ redraw = { 'curswant' },
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('expression used to obtain a diff file'),
+ type = 'string',
+ varname = 'p_dex',
+ },
+ {
+ abbreviation = 'dip',
+ alloced = true,
+ cb = 'did_set_diffopt',
+ defaults = { if_true = 'internal,filler,closeoff' },
+ deny_duplicates = true,
+ desc = [=[
+ Option settings for diff mode. It can consist of the following items.
+ All are optional. Items must be separated by a comma.
+
+ filler Show filler lines, to keep the text
+ synchronized with a window that has inserted
+ lines at the same position. Mostly useful
+ when windows are side-by-side and 'scrollbind'
+ is set.
+
+ context:{n} Use a context of {n} lines between a change
+ and a fold that contains unchanged lines.
+ When omitted a context of six lines is used.
+ When using zero the context is actually one,
+ since folds require a line in between, also
+ for a deleted line. Set it to a very large
+ value (999999) to disable folding completely.
+ See |fold-diff|.
+
+ iblank Ignore changes where lines are all blank. Adds
+ the "-B" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly.
+ NOTE: the diff windows will get out of sync,
+ because no differences between blank lines are
+ taken into account.
+
+ icase Ignore changes in case of text. "a" and "A"
+ are considered the same. Adds the "-i" flag
+ to the "diff" command if 'diffexpr' is empty.
+
+ iwhite Ignore changes in amount of white space. Adds
+ the "-b" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly. It should ignore adding trailing
+ white space, but not leading white space.
+
+ iwhiteall Ignore all white space changes. Adds
+ the "-w" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly.
+
+ iwhiteeol Ignore white space changes at end of line.
+ Adds the "-Z" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly.
+
+ horizontal Start diff mode with horizontal splits (unless
+ explicitly specified otherwise).
+
+ vertical Start diff mode with vertical splits (unless
+ explicitly specified otherwise).
+
+ closeoff When a window is closed where 'diff' is set
+ and there is only one window remaining in the
+ same tab page with 'diff' set, execute
+ `:diffoff` in that window. This undoes a
+ `:diffsplit` command.
+
+ hiddenoff Do not use diff mode for a buffer when it
+ becomes hidden.
+
+ foldcolumn:{n} Set the 'foldcolumn' option to {n} when
+ starting diff mode. Without this 2 is used.
+
+ followwrap Follow the 'wrap' option and leave as it is.
+
+ internal Use the internal diff library. This is
+ ignored when 'diffexpr' is set. *E960*
+ When running out of memory when writing a
+ buffer this item will be ignored for diffs
+ involving that buffer. Set the 'verbose'
+ option to see when this happens.
+
+ indent-heuristic
+ Use the indent heuristic for the internal
+ diff library.
+
+ linematch:{n} Enable a second stage diff on each generated
+ hunk in order to align lines. When the total
+ number of lines in a hunk exceeds {n}, the
+ second stage diff will not be performed as
+ very large hunks can cause noticeable lag. A
+ recommended setting is "linematch:60", as this
+ will enable alignment for a 2 buffer diff with
+ hunks of up to 30 lines each, or a 3 buffer
+ diff with hunks of up to 20 lines each.
+
+ algorithm:{text} Use the specified diff algorithm with the
+ internal diff engine. Currently supported
+ algorithms are:
+ myers the default algorithm
+ minimal spend extra time to generate the
+ smallest possible diff
+ patience patience diff algorithm
+ histogram histogram diff algorithm
+
+ Examples: >
+ :set diffopt=internal,filler,context:4
+ :set diffopt=
+ :set diffopt=internal,filler,foldcolumn:3
+ :set diffopt-=internal " do NOT use the internal diff parser
+ <
+ ]=],
+ expand_cb = 'expand_set_diffopt',
+ full_name = 'diffopt',
+ list = 'onecommacolon',
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('options for using diff mode'),
+ type = 'string',
+ varname = 'p_dip',
+ },
+ {
+ abbreviation = 'dg',
+ defaults = { if_true = false },
+ desc = [=[
+ Enable the entering of digraphs in Insert mode with {char1} <BS>
+ {char2}. See |digraphs|.
+ ]=],
+ full_name = 'digraph',
+ scope = { 'global' },
+ short_desc = N_('enable the entering of digraphs in Insert mode'),
+ type = 'bool',
+ varname = 'p_dg',
+ },
+ {
+ abbreviation = 'dir',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of directory names for the swap file, separated with commas.
+
+ Possible items:
+ - The swap file will be created in the first directory where this is
+ possible. If it is not possible in any directory, but last
+ directory listed in the option does not exist, it is created.
+ - Empty means that no swap file will be used (recovery is
+ impossible!) and no |E303| error will be given.
+ - A directory "." means to put the swap file in the same directory as
+ the edited file. On Unix, a dot is prepended to the file name, so
+ it doesn't show in a directory listing. On MS-Windows the "hidden"
+ attribute is set and a dot prepended if possible.
+ - A directory starting with "./" (or ".\" for MS-Windows) means to put
+ the swap file relative to where the edited file is. The leading "."
+ is replaced with the path name of the edited file.
+ - For Unix and Win32, if a directory ends in two path separators "//",
+ the swap file name will be built from the complete path to the file
+ with all path separators replaced by percent '%' signs (including
+ the colon following the drive letter on Win32). This will ensure
+ file name uniqueness in the preserve directory.
+ On Win32, it is also possible to end with "\\". However, When a
+ separating comma is following, you must use "//", since "\\" will
+ include the comma in the file name. Therefore it is recommended to
+ use '//', instead of '\\'.
+ - Spaces after the comma are ignored, other spaces are considered part
+ of the directory name. To have a space at the start of a directory
+ name, precede it with a backslash.
+ - To include a comma in a directory name precede it with a backslash.
+ - A directory name may end in an ':' or '/'.
+ - Environment variables are expanded |:set_env|.
+ - Careful with '\' characters, type one before a space, type two to
+ get one in the option (see |option-backslash|), for example: >
+ :set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+ <
+ Editing the same file twice will result in a warning. Using "/tmp" on
+ is discouraged: if the system crashes you lose the swap file. And
+ others on the computer may be able to see the files.
+ Use |:set+=| and |:set-=| when adding or removing directories from the
+ list, this avoids problems if the Nvim default is changed.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'directory',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directory names for the swap file'),
+ type = 'string',
+ varname = 'p_dir',
+ },
+ {
+ abbreviation = 'dy',
+ cb = 'did_set_display',
+ defaults = { if_true = 'lastline' },
+ deny_duplicates = true,
+ desc = [=[
+ Change the way text is displayed. This is a comma-separated list of
+ flags:
+ lastline When included, as much as possible of the last line
+ in a window will be displayed. "@@@" is put in the
+ last columns of the last screen line to indicate the
+ rest of the line is not displayed.
+ truncate Like "lastline", but "@@@" is displayed in the first
+ column of the last screen line. Overrules "lastline".
+ uhex Show unprintable characters hexadecimal as <xx>
+ instead of using ^C and ~C.
+ msgsep Obsolete flag. Allowed but takes no effect. |msgsep|
+
+ When neither "lastline" nor "truncate" is included, a last line that
+ doesn't fit is replaced with "@" lines.
+
+ The "@" character can be changed by setting the "lastline" item in
+ 'fillchars'. The character is highlighted with |hl-NonText|.
+ ]=],
+ expand_cb = 'expand_set_display',
+ full_name = 'display',
+ list = 'onecomma',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('list of flags for how to display text'),
+ type = 'string',
+ varname = 'p_dy',
+ },
+ {
+ abbreviation = 'ead',
+ cb = 'did_set_eadirection',
+ defaults = { if_true = 'both' },
+ desc = [=[
+ Tells when the 'equalalways' option applies:
+ ver vertically, width of windows is not affected
+ hor horizontally, height of windows is not affected
+ both width and height of windows is affected
+ ]=],
+ expand_cb = 'expand_set_eadirection',
+ full_name = 'eadirection',
+ scope = { 'global' },
+ short_desc = N_("in which direction 'equalalways' works"),
+ type = 'string',
+ varname = 'p_ead',
+ },
+ {
+ abbreviation = 'ed',
+ defaults = { if_true = false },
+ full_name = 'edcompatible',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'emo',
+ cb = 'did_set_ambiwidth',
+ defaults = { if_true = true },
+ desc = [=[
+ When on all Unicode emoji characters are considered to be full width.
+ This excludes "text emoji" characters, which are normally displayed as
+ single width. Unfortunately there is no good specification for this
+ and it has been determined on trial-and-error basis. Use the
+ |setcellwidths()| function to change the behavior.
+ ]=],
+ full_name = 'emoji',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ varname = 'p_emoji',
+ },
+ {
+ abbreviation = 'enc',
+ cb = 'did_set_encoding',
+ defaults = { if_true = macros('ENC_DFLT') },
+ deny_in_modelines = true,
+ desc = [=[
+ String-encoding used internally and for |RPC| communication.
+ Always UTF-8.
+
+ See 'fileencoding' to control file-content encoding.
+ ]=],
+ full_name = 'encoding',
+ scope = { 'global' },
+ short_desc = N_('encoding used internally'),
+ type = 'string',
+ varname = 'p_enc',
+ },
+ {
+ abbreviation = 'eof',
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = false },
+ desc = [=[
+ Indicates that a CTRL-Z character was found at the end of the file
+ when reading it. Normally only happens when 'fileformat' is "dos".
+ When writing a file and this option is off and the 'binary' option
+ is on, or 'fixeol' option is off, no CTRL-Z will be written at the
+ end of the file.
+ See |eol-and-eof| for example settings.
+ ]=],
+ full_name = 'endoffile',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('write CTRL-Z for last line in file'),
+ type = 'bool',
+ varname = 'p_eof',
+ },
+ {
+ abbreviation = 'eol',
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = true },
+ desc = [=[
+ When writing a file and this option is off and the 'binary' option
+ is on, or 'fixeol' option is off, no <EOL> will be written for the
+ last line in the file. This option is automatically set or reset when
+ starting to edit a new file, depending on whether file has an <EOL>
+ for the last line in the file. Normally you don't have to set or
+ reset this option.
+ When 'binary' is off and 'fixeol' is on the value is not used when
+ writing the file. When 'binary' is on or 'fixeol' is off it is used
+ to remember the presence of a <EOL> for the last line in the file, so
+ that when you write the file the situation from the original file can
+ be kept. But you can change it if you want to.
+ See |eol-and-eof| for example settings.
+ ]=],
+ full_name = 'endofline',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('write <EOL> for last line in file'),
+ type = 'bool',
+ varname = 'p_eol',
+ },
+ {
+ abbreviation = 'ea',
+ cb = 'did_set_equalalways',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, all the windows are automatically made the same size after
+ splitting or closing a window. This also happens the moment the
+ option is switched on. When off, splitting a window will reduce the
+ size of the current window and leave the other windows the same. When
+ closing a window the extra lines are given to the window next to it
+ (depending on 'splitbelow' and 'splitright').
+ When mixing vertically and horizontally split windows, a minimal size
+ is computed and some windows may be larger if there is room. The
+ 'eadirection' option tells in which direction the size is affected.
+ Changing the height and width of a window can be avoided by setting
+ 'winfixheight' and 'winfixwidth', respectively.
+ If a window size is specified when creating a new window sizes are
+ currently not equalized (it's complicated, but may be implemented in
+ the future).
+ ]=],
+ full_name = 'equalalways',
+ scope = { 'global' },
+ short_desc = N_('windows are automatically made the same size'),
+ type = 'bool',
+ varname = 'p_ea',
+ },
+ {
+ abbreviation = 'ep',
+ defaults = { if_true = '' },
+ desc = [=[
+ External program to use for "=" command. When this option is empty
+ the internal formatting functions are used; either 'lisp', 'cindent'
+ or 'indentexpr'.
+ Environment variables are expanded |:set_env|. See |option-backslash|
+ about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'equalprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('external program to use for "=" command'),
+ type = 'string',
+ varname = 'p_ep',
+ },
+ {
+ abbreviation = 'eb',
+ defaults = { if_true = false },
+ desc = [=[
+ Ring the bell (beep or screen flash) for error messages. This only
+ makes a difference for error messages, the bell will be used always
+ for a lot of errors without a message (e.g., hitting <Esc> in Normal
+ mode). See 'visualbell' to make the bell behave like a screen flash
+ or do nothing. See 'belloff' to finetune when to ring the bell.
+ ]=],
+ full_name = 'errorbells',
+ scope = { 'global' },
+ short_desc = N_('ring the bell for error messages'),
+ type = 'bool',
+ varname = 'p_eb',
+ },
+ {
+ abbreviation = 'ef',
+ defaults = { if_true = macros('DFLT_ERRORFILE') },
+ desc = [=[
+ Name of the errorfile for the QuickFix mode (see |:cf|).
+ When the "-q" command-line argument is used, 'errorfile' is set to the
+ following argument. See |-q|.
+ NOT used for the ":make" command. See 'makeef' for that.
+ Environment variables are expanded |:set_env|.
+ See |option-backslash| about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'errorfile',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('name of the errorfile for the QuickFix mode'),
+ type = 'string',
+ varname = 'p_ef',
+ },
+ {
+ abbreviation = 'efm',
+ defaults = {
+ if_true = macros('DFLT_EFM'),
+ doc = 'is very long',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Scanf-like description of the format for the lines in the error file
+ (see |errorformat|).
+ ]=],
+ full_name = 'errorformat',
+ list = 'onecomma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('description of the lines in the error file'),
+ type = 'string',
+ varname = 'p_efm',
+ },
+ {
+ abbreviation = 'ei',
+ cb = 'did_set_eventignore',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of autocommand event names, which are to be ignored.
+ When set to "all" or when "all" is one of the items, all autocommand
+ events are ignored, autocommands will not be executed.
+ Otherwise this is a comma-separated list of event names. Example: >
+ :set ei=WinEnter,WinLeave
+ <
+ ]=],
+ expand_cb = 'expand_set_eventignore',
+ full_name = 'eventignore',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('autocommand events that are ignored'),
+ type = 'string',
+ varname = 'p_ei',
+ },
+ {
+ abbreviation = 'et',
+ defaults = { if_true = false },
+ desc = [=[
+ In Insert mode: Use the appropriate number of spaces to insert a
+ <Tab>. Spaces are used in indents with the '>' and '<' commands and
+ when 'autoindent' is on. To insert a real tab when 'expandtab' is
+ on, use CTRL-V<Tab>. See also |:retab| and |ins-expandtab|.
+ ]=],
+ full_name = 'expandtab',
+ scope = { 'buffer' },
+ short_desc = N_('use spaces when <Tab> is inserted'),
+ type = 'bool',
+ varname = 'p_et',
+ },
+ {
+ abbreviation = 'ex',
+ defaults = { if_true = false },
+ desc = [=[
+ Automatically execute .nvim.lua, .nvimrc, and .exrc files in the
+ current directory, if the file is in the |trust| list. Use |:trust| to
+ manage trusted files. See also |vim.secure.read()|.
+
+ Compare 'exrc' to |editorconfig|:
+ - 'exrc' can execute any code; editorconfig only specifies settings.
+ - 'exrc' is Nvim-specific; editorconfig works in other editors.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'exrc',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('read .nvimrc and .exrc in the current directory'),
+ type = 'bool',
+ varname = 'p_exrc',
+ },
+ {
+ abbreviation = 'fenc',
+ alloced = true,
+ cb = 'did_set_encoding',
+ defaults = { if_true = '' },
+ desc = [=[
+ File-content encoding for the current buffer. Conversion is done with
+ iconv() or as specified with 'charconvert'.
+
+ When 'fileencoding' is not UTF-8, conversion will be done when
+ writing the file. For reading see below.
+ When 'fileencoding' is empty, the file will be saved with UTF-8
+ encoding (no conversion when reading or writing a file).
+
+ WARNING: Conversion to a non-Unicode encoding can cause loss of
+ information!
+
+ See |encoding-names| for the possible values. Additionally, values may be
+ specified that can be handled by the converter, see
+ |mbyte-conversion|.
+
+ When reading a file 'fileencoding' will be set from 'fileencodings'.
+ To read a file in a certain encoding it won't work by setting
+ 'fileencoding', use the |++enc| argument. One exception: when
+ 'fileencodings' is empty the value of 'fileencoding' is used.
+ For a new file the global value of 'fileencoding' is used.
+
+ Prepending "8bit-" and "2byte-" has no meaning here, they are ignored.
+ When the option is set, the value is converted to lowercase. Thus
+ you can set it with uppercase values too. '_' characters are
+ replaced with '-'. If a name is recognized from the list at
+ |encoding-names|, it is replaced by the standard name. For example
+ "ISO8859-2" becomes "iso-8859-2".
+
+ When this option is set, after starting to edit a file, the 'modified'
+ option is set, because the file would be different when written.
+
+ Keep in mind that changing 'fenc' from a modeline happens
+ AFTER the text has been read, thus it applies to when the file will be
+ written. If you do set 'fenc' in a modeline, you might want to set
+ 'nomodified' to avoid not being able to ":q".
+
+ This option cannot be changed when 'modifiable' is off.
+ ]=],
+ expand_cb = 'expand_set_encoding',
+ full_name = 'fileencoding',
+ no_mkrc = true,
+ redraw = { 'statuslines', 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('file encoding for multi-byte text'),
+ tags = { 'E213' },
+ type = 'string',
+ varname = 'p_fenc',
+ },
+ {
+ abbreviation = 'fencs',
+ defaults = { if_true = 'ucs-bom,utf-8,default,latin1' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a list of character encodings considered when starting to edit
+ an existing file. When a file is read, Vim tries to use the first
+ mentioned character encoding. If an error is detected, the next one
+ in the list is tried. When an encoding is found that works,
+ 'fileencoding' is set to it. If all fail, 'fileencoding' is set to
+ an empty string, which means that UTF-8 is used.
+ WARNING: Conversion can cause loss of information! You can use
+ the |++bad| argument to specify what is done with characters
+ that can't be converted.
+ For an empty file or a file with only ASCII characters most encodings
+ will work and the first entry of 'fileencodings' will be used (except
+ "ucs-bom", which requires the BOM to be present). If you prefer
+ another encoding use an BufReadPost autocommand event to test if your
+ preferred encoding is to be used. Example: >
+ au BufReadPost * if search('\S', 'w') == 0 |
+ \ set fenc=iso-2022-jp | endif
+ < This sets 'fileencoding' to "iso-2022-jp" if the file does not contain
+ non-blank characters.
+ When the |++enc| argument is used then the value of 'fileencodings' is
+ not used.
+ Note that 'fileencodings' is not used for a new file, the global value
+ of 'fileencoding' is used instead. You can set it with: >
+ :setglobal fenc=iso-8859-2
+ < This means that a non-existing file may get a different encoding than
+ an empty file.
+ The special value "ucs-bom" can be used to check for a Unicode BOM
+ (Byte Order Mark) at the start of the file. It must not be preceded
+ by "utf-8" or another Unicode encoding for this to work properly.
+ An entry for an 8-bit encoding (e.g., "latin1") should be the last,
+ because Vim cannot detect an error, thus the encoding is always
+ accepted.
+ The special value "default" can be used for the encoding from the
+ environment. It is useful when your environment uses a non-latin1
+ encoding, such as Russian.
+ When a file contains an illegal UTF-8 byte sequence it won't be
+ recognized as "utf-8". You can use the |8g8| command to find the
+ illegal byte sequence.
+ WRONG VALUES: WHAT'S WRONG:
+ latin1,utf-8 "latin1" will always be used
+ utf-8,ucs-bom,latin1 BOM won't be recognized in an utf-8
+ file
+ cp1250,latin1 "cp1250" will always be used
+ If 'fileencodings' is empty, 'fileencoding' is not modified.
+ See 'fileencoding' for the possible values.
+ Setting this option does not have an effect until the next time a file
+ is read.
+ ]=],
+ expand_cb = 'expand_set_encoding',
+ full_name = 'fileencodings',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('automatically detected character encodings'),
+ type = 'string',
+ varname = 'p_fencs',
+ },
+ {
+ abbreviation = 'ff',
+ alloced = true,
+ cb = 'did_set_fileformat',
+ defaults = {
+ if_true = macros('DFLT_FF'),
+ doc = 'Windows: "dos", Unix: "unix"',
+ },
+ desc = [=[
+ This gives the <EOL> of the current buffer, which is used for
+ reading/writing the buffer from/to a file:
+ dos <CR><NL>
+ unix <NL>
+ mac <CR>
+ When "dos" is used, CTRL-Z at the end of a file is ignored.
+ See |file-formats| and |file-read|.
+ For the character encoding of the file see 'fileencoding'.
+ When 'binary' is set, the value of 'fileformat' is ignored, file I/O
+ works like it was set to "unix".
+ This option is set automatically when starting to edit a file and
+ 'fileformats' is not empty and 'binary' is off.
+ When this option is set, after starting to edit a file, the 'modified'
+ option is set, because the file would be different when written.
+ This option cannot be changed when 'modifiable' is off.
+ ]=],
+ expand_cb = 'expand_set_fileformat',
+ full_name = 'fileformat',
+ no_mkrc = true,
+ redraw = { 'curswant', 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('file format used for file I/O'),
+ type = 'string',
+ varname = 'p_ff',
+ },
+ {
+ abbreviation = 'ffs',
+ cb = 'did_set_fileformats',
+ defaults = {
+ if_true = macros('DFLT_FFS_VIM'),
+ doc = 'Windows: "dos,unix", Unix: "unix,dos"',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ This gives the end-of-line (<EOL>) formats that will be tried when
+ starting to edit a new buffer and when reading a file into an existing
+ buffer:
+ - When empty, the format defined with 'fileformat' will be used
+ always. It is not set automatically.
+ - When set to one name, that format will be used whenever a new buffer
+ is opened. 'fileformat' is set accordingly for that buffer. The
+ 'fileformats' name will be used when a file is read into an existing
+ buffer, no matter what 'fileformat' for that buffer is set to.
+ - When more than one name is present, separated by commas, automatic
+ <EOL> detection will be done when reading a file. When starting to
+ edit a file, a check is done for the <EOL>:
+ 1. If all lines end in <CR><NL>, and 'fileformats' includes "dos",
+ 'fileformat' is set to "dos".
+ 2. If a <NL> is found and 'fileformats' includes "unix", 'fileformat'
+ is set to "unix". Note that when a <NL> is found without a
+ preceding <CR>, "unix" is preferred over "dos".
+ 3. If 'fileformat' has not yet been set, and if a <CR> is found, and
+ if 'fileformats' includes "mac", 'fileformat' is set to "mac".
+ This means that "mac" is only chosen when:
+ "unix" is not present or no <NL> is found in the file, and
+ "dos" is not present or no <CR><NL> is found in the file.
+ Except: if "unix" was chosen, but there is a <CR> before
+ the first <NL>, and there appear to be more <CR>s than <NL>s in
+ the first few lines, "mac" is used.
+ 4. If 'fileformat' is still not set, the first name from
+ 'fileformats' is used.
+ When reading a file into an existing buffer, the same is done, but
+ this happens like 'fileformat' has been set appropriately for that
+ file only, the option is not changed.
+ When 'binary' is set, the value of 'fileformats' is not used.
+
+ When Vim starts up with an empty buffer the first item is used. You
+ can overrule this by setting 'fileformat' in your .vimrc.
+
+ For systems with a Dos-like <EOL> (<CR><NL>), when reading files that
+ are ":source"ed and for vimrc files, automatic <EOL> detection may be
+ done:
+ - When 'fileformats' is empty, there is no automatic detection. Dos
+ format will be used.
+ - When 'fileformats' is set to one or more names, automatic detection
+ is done. This is based on the first <NL> in the file: If there is a
+ <CR> in front of it, Dos format is used, otherwise Unix format is
+ used.
+ Also see |file-formats|.
+ ]=],
+ expand_cb = 'expand_set_fileformat',
+ full_name = 'fileformats',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_("automatically detected values for 'fileformat'"),
+ type = 'string',
+ varname = 'p_ffs',
+ },
+ {
+ abbreviation = 'fic',
+ defaults = {
+ condition = 'CASE_INSENSITIVE_FILENAME',
+ if_false = false,
+ if_true = true,
+ doc = [[on for systems where case in file
+ names is normally ignored]],
+ },
+ desc = [=[
+ When set case is ignored when using file names and directories.
+ See 'wildignorecase' for only ignoring case when doing completion.
+ ]=],
+ full_name = 'fileignorecase',
+ scope = { 'global' },
+ short_desc = N_('ignore case when using file names'),
+ type = 'bool',
+ varname = 'p_fic',
+ },
+ {
+ abbreviation = 'ft',
+ alloced = true,
+ cb = 'did_set_filetype_or_syntax',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is set, the FileType autocommand event is triggered.
+ All autocommands that match with the value of this option will be
+ executed. Thus the value of 'filetype' is used in place of the file
+ name.
+ Otherwise this option does not always reflect the current file type.
+ This option is normally set when the file type is detected. To enable
+ this use the ":filetype on" command. |:filetype|
+ Setting this option to a different value is most useful in a modeline,
+ for a file for which the file type is not automatically recognized.
+ Example, for in an IDL file: >
+ /* vim: set filetype=idl : */
+ < |FileType| |filetypes|
+ When a dot appears in the value then this separates two filetype
+ names. Example: >
+ /* vim: set filetype=c.doxygen : */
+ < This will use the "c" filetype first, then the "doxygen" filetype.
+ This works both for filetype plugins and for syntax files. More than
+ one dot may appear.
+ This option is not copied to another buffer, independent of the 's' or
+ 'S' flag in 'cpoptions'.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'filetype',
+ noglob = true,
+ normal_fname_chars = true,
+ scope = { 'buffer' },
+ short_desc = N_('type of file, used for autocommands'),
+ type = 'string',
+ varname = 'p_ft',
+ },
+ {
+ abbreviation = 'fcs',
+ alloced = true,
+ cb = 'did_set_chars_option',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Characters to fill the statuslines, vertical separators and special
+ lines in the window.
+ It is a comma-separated list of items. Each item has a name, a colon
+ and the value of that item:
+
+ item default Used for ~
+ stl ' ' statusline of the current window
+ stlnc ' ' statusline of the non-current windows
+ wbr ' ' window bar
+ horiz '─' or '-' horizontal separators |:split|
+ horizup 'â”´' or '-' upwards facing horizontal separator
+ horizdown '┬' or '-' downwards facing horizontal separator
+ vert '│' or '|' vertical separators |:vsplit|
+ vertleft '┤' or '|' left facing vertical separator
+ vertright '├' or '|' right facing vertical separator
+ verthoriz '┼' or '+' overlapping vertical and horizontal
+ separator
+ fold '·' or '-' filling 'foldtext'
+ foldopen '-' mark the beginning of a fold
+ foldclose '+' show a closed fold
+ foldsep '│' or '|' open fold middle marker
+ diff '-' deleted lines of the 'diff' option
+ msgsep ' ' message separator 'display'
+ eob '~' empty lines at the end of a buffer
+ lastline '@' 'display' contains lastline/truncate
+
+ Any one that is omitted will fall back to the default.
+
+ Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and
+ "verthoriz" are only used when 'laststatus' is 3, since only vertical
+ window separators are used otherwise.
+
+ If 'ambiwidth' is "double" then "horiz", "horizup", "horizdown",
+ "vert", "vertleft", "vertright", "verthoriz", "foldsep" and "fold"
+ default to single-byte alternatives.
+
+ Example: >
+ :set fillchars=stl:\ ,stlnc:\ ,vert:│,fold:·,diff:-
+ <
+ For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items
+ single-byte and multibyte characters are supported. But double-width
+ characters are not supported.
+
+ The highlighting used for these items:
+ item highlight group ~
+ stl StatusLine |hl-StatusLine|
+ stlnc StatusLineNC |hl-StatusLineNC|
+ wbr WinBar |hl-WinBar| or |hl-WinBarNC|
+ horiz WinSeparator |hl-WinSeparator|
+ horizup WinSeparator |hl-WinSeparator|
+ horizdown WinSeparator |hl-WinSeparator|
+ vert WinSeparator |hl-WinSeparator|
+ vertleft WinSeparator |hl-WinSeparator|
+ vertright WinSeparator |hl-WinSeparator|
+ verthoriz WinSeparator |hl-WinSeparator|
+ fold Folded |hl-Folded|
+ diff DiffDelete |hl-DiffDelete|
+ eob EndOfBuffer |hl-EndOfBuffer|
+ lastline NonText |hl-NonText|
+ ]=],
+ expand_cb = 'expand_set_chars_option',
+ full_name = 'fillchars',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'global', 'window' },
+ short_desc = N_('characters to use for displaying special items'),
+ type = 'string',
+ varname = 'p_fcs',
+ },
+ {
+ abbreviation = 'fixeol',
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = true },
+ desc = [=[
+ When writing a file and this option is on, <EOL> at the end of file
+ will be restored if missing. Turn this option off if you want to
+ preserve the situation from the original file.
+ When the 'binary' option is set the value of this option doesn't
+ matter.
+ See the 'endofline' option.
+ See |eol-and-eof| for example settings.
+ ]=],
+ full_name = 'fixendofline',
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('make sure last line in file has <EOL>'),
+ type = 'bool',
+ varname = 'p_fixeol',
+ },
+ {
+ abbreviation = 'fcl',
+ cb = 'did_set_foldclose',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ When set to "all", a fold is closed when the cursor isn't in it and
+ its level is higher than 'foldlevel'. Useful if you want folds to
+ automatically close when moving out of them.
+ ]=],
+ expand_cb = 'expand_set_foldclose',
+ full_name = 'foldclose',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('close a fold when the cursor leaves it'),
+ type = 'string',
+ varname = 'p_fcl',
+ },
+ {
+ abbreviation = 'fdc',
+ alloced = true,
+ cb = 'did_set_foldcolumn',
+ defaults = { if_true = '0' },
+ desc = [=[
+ When and how to draw the foldcolumn. Valid values are:
+ "auto": resize to the minimum amount of folds to display.
+ "auto:[1-9]": resize to accommodate multiple folds up to the
+ selected level
+ "0": to disable foldcolumn
+ "[1-9]": to display a fixed number of columns
+ See |folding|.
+ ]=],
+ expand_cb = 'expand_set_foldcolumn',
+ full_name = 'foldcolumn',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('width of the column used to indicate folds'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fen',
+ defaults = { if_true = true },
+ desc = [=[
+ When off, all folds are open. This option can be used to quickly
+ switch between showing all text unfolded and viewing the text with
+ folds (including manually opened or closed folds). It can be toggled
+ with the |zi| command. The 'foldcolumn' will remain blank when
+ 'foldenable' is off.
+ This option is set by commands that create a new fold or close a fold.
+ See |folding|.
+ ]=],
+ full_name = 'foldenable',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('set to display all folds open'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'fde',
+ alloced = true,
+ cb = 'did_set_foldexpr',
+ defaults = { if_true = '0' },
+ desc = [=[
+ The expression used for when 'foldmethod' is "expr". It is evaluated
+ for each line to obtain its fold level. The context is set to the
+ script where 'foldexpr' was set, script-local items can be accessed.
+ See |fold-expr| for the usage.
+
+ The expression will be evaluated in the |sandbox| if set from a
+ modeline, see |sandbox-option|.
+ This option can't be set from a |modeline| when the 'diff' option is
+ on or the 'modelineexpr' option is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'foldexpr' |textlock|.
+ ]=],
+ full_name = 'foldexpr',
+ modelineexpr = true,
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('expression used when \'foldmethod\' is "expr"'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fdi',
+ alloced = true,
+ cb = 'did_set_foldignore',
+ defaults = { if_true = '#' },
+ desc = [=[
+ Used only when 'foldmethod' is "indent". Lines starting with
+ characters in 'foldignore' will get their fold level from surrounding
+ lines. White space is skipped before checking for this character.
+ The default "#" works well for C programs. See |fold-indent|.
+ ]=],
+ full_name = 'foldignore',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('ignore lines when \'foldmethod\' is "indent"'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fdl',
+ cb = 'did_set_foldlevel',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Sets the fold level: Folds with a higher level will be closed.
+ Setting this option to zero will close all folds. Higher numbers will
+ close fewer folds.
+ This option is set by commands like |zm|, |zM| and |zR|.
+ See |fold-foldlevel|.
+ ]=],
+ full_name = 'foldlevel',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('close folds with a level higher than this'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'fdls',
+ defaults = { if_true = -1 },
+ desc = [=[
+ Sets 'foldlevel' when starting to edit another buffer in a window.
+ Useful to always start editing with all folds closed (value zero),
+ some folds closed (one) or no folds closed (99).
+ This is done before reading any modeline, thus a setting in a modeline
+ overrules this option. Starting to edit a file for |diff-mode| also
+ ignores this option and closes all folds.
+ It is also done before BufReadPre autocommands, to allow an autocmd to
+ overrule the 'foldlevel' value for specific files.
+ When the value is negative, it is not used.
+ ]=],
+ full_name = 'foldlevelstart',
+ redraw = { 'curswant' },
+ scope = { 'global' },
+ short_desc = N_("'foldlevel' when starting to edit a file"),
+ type = 'number',
+ varname = 'p_fdls',
+ },
+ {
+ abbreviation = 'fmr',
+ alloced = true,
+ cb = 'did_set_foldmarker',
+ defaults = { if_true = '{{{,}}}' },
+ deny_duplicates = true,
+ desc = [=[
+ The start and end marker used when 'foldmethod' is "marker". There
+ must be one comma, which separates the start and end marker. The
+ marker is a literal string (a regular expression would be too slow).
+ See |fold-marker|.
+ ]=],
+ full_name = 'foldmarker',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('markers used when \'foldmethod\' is "marker"'),
+ tags = { 'E536' },
+ type = 'string',
+ },
+ {
+ abbreviation = 'fdm',
+ alloced = true,
+ cb = 'did_set_foldmethod',
+ defaults = { if_true = 'manual' },
+ desc = [=[
+ The kind of folding used for the current window. Possible values:
+ |fold-manual| manual Folds are created manually.
+ |fold-indent| indent Lines with equal indent form a fold.
+ |fold-expr| expr 'foldexpr' gives the fold level of a line.
+ |fold-marker| marker Markers are used to specify folds.
+ |fold-syntax| syntax Syntax highlighting items specify folds.
+ |fold-diff| diff Fold text that is not changed.
+ ]=],
+ expand_cb = 'expand_set_foldmethod',
+ full_name = 'foldmethod',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('folding type'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fml',
+ cb = 'did_set_foldminlines',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Sets the number of screen lines above which a fold can be displayed
+ closed. Also for manually closed folds. With the default value of
+ one a fold can only be closed if it takes up two or more screen lines.
+ Set to zero to be able to close folds of just one screen line.
+ Note that this only has an effect on what is displayed. After using
+ "zc" to close a fold, which is displayed open because it's smaller
+ than 'foldminlines', a following "zc" may close a containing fold.
+ ]=],
+ full_name = 'foldminlines',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('minimum number of lines for a fold to be closed'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'fdn',
+ cb = 'did_set_foldnestmax',
+ defaults = { if_true = 20 },
+ desc = [=[
+ Sets the maximum nesting of folds for the "indent" and "syntax"
+ methods. This avoids that too many folds will be created. Using more
+ than 20 doesn't work, because the internal limit is 20.
+ ]=],
+ full_name = 'foldnestmax',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('maximum fold depth'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'fdo',
+ cb = 'did_set_foldopen',
+ defaults = { if_true = 'block,hor,mark,percent,quickfix,search,tag,undo' },
+ deny_duplicates = true,
+ desc = [=[
+ Specifies for which type of commands folds will be opened, if the
+ command moves the cursor into a closed fold. It is a comma-separated
+ list of items.
+ NOTE: When the command is part of a mapping this option is not used.
+ Add the |zv| command to the mapping to get the same effect.
+ (rationale: the mapping may want to control opening folds itself)
+
+ item commands ~
+ all any
+ block (, {, [[, [{, etc.
+ hor horizontal movements: "l", "w", "fx", etc.
+ insert any command in Insert mode
+ jump far jumps: "G", "gg", etc.
+ mark jumping to a mark: "'m", CTRL-O, etc.
+ percent "%"
+ quickfix ":cn", ":crew", ":make", etc.
+ search search for a pattern: "/", "n", "*", "gd", etc.
+ (not for a search pattern in a ":" command)
+ Also for |[s| and |]s|.
+ tag jumping to a tag: ":ta", CTRL-T, etc.
+ undo undo or redo: "u" and CTRL-R
+ When a movement command is used for an operator (e.g., "dl" or "y%")
+ this option is not used. This means the operator will include the
+ whole closed fold.
+ Note that vertical movements are not here, because it would make it
+ very difficult to move onto a closed fold.
+ In insert mode the folds containing the cursor will always be open
+ when text is inserted.
+ To close folds you can re-apply 'foldlevel' with the |zx| command or
+ set the 'foldclose' option to "all".
+ ]=],
+ expand_cb = 'expand_set_foldopen',
+ full_name = 'foldopen',
+ list = 'onecomma',
+ redraw = { 'curswant' },
+ scope = { 'global' },
+ short_desc = N_('for which commands a fold will be opened'),
+ type = 'string',
+ varname = 'p_fdo',
+ },
+ {
+ abbreviation = 'fdt',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = 'foldtext()' },
+ desc = [=[
+ An expression which is used to specify the text displayed for a closed
+ fold. The context is set to the script where 'foldexpr' was set,
+ script-local items can be accessed. See |fold-foldtext| for the
+ usage.
+
+ The expression will be evaluated in the |sandbox| if set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'foldtext' |textlock|.
+ ]=],
+ full_name = 'foldtext',
+ modelineexpr = true,
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('expression used to display for a closed fold'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fex',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to format a range of lines for the |gq|
+ operator or automatic formatting (see 'formatoptions'). When this
+ option is empty 'formatprg' is used.
+
+ The |v:lnum| variable holds the first line to be formatted.
+ The |v:count| variable holds the number of lines to be formatted.
+ The |v:char| variable holds the character that is going to be
+ inserted if the expression is being evaluated due to
+ automatic formatting. This can be empty. Don't insert
+ it yet!
+
+ Example: >
+ :set formatexpr=mylang#Format()
+ < This will invoke the mylang#Format() function in the
+ autoload/mylang.vim file in 'runtimepath'. |autoload|
+
+ The expression is also evaluated when 'textwidth' is set and adding
+ text beyond that limit. This happens under the same conditions as
+ when internal formatting is used. Make sure the cursor is kept in the
+ same spot relative to the text then! The |mode()| function will
+ return "i" or "R" in this situation.
+
+ When the expression evaluates to non-zero Vim will fall back to using
+ the internal format mechanism.
+
+ If the expression starts with s: or |<SID>|, then it is replaced with
+ the script ID (|local-function|). Example: >
+ set formatexpr=s:MyFormatExpr()
+ set formatexpr=<SID>SomeFormatExpr()
+ < Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|. That stops the option from working,
+ since changing the buffer text is not allowed.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+ NOTE: This option is set to "" when 'compatible' is set.
+ ]=],
+ full_name = 'formatexpr',
+ modelineexpr = true,
+ scope = { 'buffer' },
+ short_desc = N_('expression used with "gq" command'),
+ type = 'string',
+ varname = 'p_fex',
+ },
+ {
+ abbreviation = 'fo',
+ alloced = true,
+ cb = 'did_set_formatoptions',
+ defaults = { if_true = macros('DFLT_FO_VIM') },
+ desc = [=[
+ This is a sequence of letters which describes how automatic
+ formatting is to be done.
+ See |fo-table| for possible values and |gq| for how to format text.
+ Commas can be inserted for readability.
+ To avoid problems with flags that are added in the future, use the
+ "+=" and "-=" feature of ":set" |add-option-flags|.
+ ]=],
+ expand_cb = 'expand_set_formatoptions',
+ full_name = 'formatoptions',
+ list = 'flags',
+ scope = { 'buffer' },
+ short_desc = N_('how automatic formatting is to be done'),
+ type = 'string',
+ varname = 'p_fo',
+ },
+ {
+ abbreviation = 'flp',
+ alloced = true,
+ defaults = { if_true = '^\\s*\\d\\+[\\]:.)}\\t ]\\s*' },
+ desc = [=[
+ A pattern that is used to recognize a list header. This is used for
+ the "n" flag in 'formatoptions'.
+ The pattern must match exactly the text that will be the indent for
+ the line below it. You can use |/\ze| to mark the end of the match
+ while still checking more characters. There must be a character
+ following the pattern, when it matches the whole line it is handled
+ like there is no match.
+ The default recognizes a number, followed by an optional punctuation
+ character and white space.
+ ]=],
+ full_name = 'formatlistpat',
+ scope = { 'buffer' },
+ short_desc = N_('pattern used to recognize a list header'),
+ type = 'string',
+ varname = 'p_flp',
+ },
+ {
+ abbreviation = 'fp',
+ defaults = { if_true = '' },
+ desc = [=[
+ The name of an external program that will be used to format the lines
+ selected with the |gq| operator. The program must take the input on
+ stdin and produce the output on stdout. The Unix program "fmt" is
+ such a program.
+ If the 'formatexpr' option is not empty it will be used instead.
+ Otherwise, if 'formatprg' option is an empty string, the internal
+ format function will be used |C-indenting|.
+ Environment variables are expanded |:set_env|. See |option-backslash|
+ about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'formatprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('name of external program used with "gq" command'),
+ type = 'string',
+ varname = 'p_fp',
+ },
+ {
+ abbreviation = 'fs',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, the OS function fsync() will be called after saving a file
+ (|:write|, |writefile()|, …), |swap-file|, |undo-persistence| and |shada-file|.
+ This flushes the file to disk, ensuring that it is safely written.
+ Slow on some systems: writing buffers, quitting Nvim, and other
+ operations may sometimes take a few seconds.
+
+ Files are ALWAYS flushed ('fsync' is ignored) when:
+ - |CursorHold| event is triggered
+ - |:preserve| is called
+ - system signals low battery life
+ - Nvim exits abnormally
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'fsync',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('whether to invoke fsync() after file write'),
+ type = 'bool',
+ varname = 'p_fs',
+ },
+ {
+ abbreviation = 'gd',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, the ":substitute" flag 'g' is default on. This means that
+ all matches in a line are substituted instead of one. When a 'g' flag
+ is given to a ":substitute" command, this will toggle the substitution
+ of all or one match. See |complex-change|.
+
+ command 'gdefault' on 'gdefault' off ~
+ :s/// subst. all subst. one
+ :s///g subst. one subst. all
+ :s///gg subst. all subst. one
+
+ DEPRECATED: Setting this option may break plugins that are not aware
+ of this option. Also, many users get confused that adding the /g flag
+ has the opposite effect of that it normally does.
+ ]=],
+ full_name = 'gdefault',
+ scope = { 'global' },
+ short_desc = N_('the ":substitute" flag \'g\' is default on'),
+ type = 'bool',
+ varname = 'p_gd',
+ },
+ {
+ abbreviation = 'gfm',
+ defaults = { if_true = macros('DFLT_GREPFORMAT') },
+ deny_duplicates = true,
+ desc = [=[
+ Format to recognize for the ":grep" command output.
+ This is a scanf-like string that uses the same format as the
+ 'errorformat' option: see |errorformat|.
+ ]=],
+ full_name = 'grepformat',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_("format of 'grepprg' output"),
+ type = 'string',
+ varname = 'p_gefm',
+ },
+ {
+ abbreviation = 'gp',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = 'grep -n $* /dev/null',
+ if_true = 'findstr /n $* nul',
+ doc = [["grep -n ",
+ Unix: "grep -n $* /dev/null"]],
+ },
+ desc = [=[
+ Program to use for the |:grep| command. This option may contain '%'
+ and '#' characters, which are expanded like when used in a command-
+ line. The placeholder "$*" is allowed to specify where the arguments
+ will be included. Environment variables are expanded |:set_env|. See
+ |option-backslash| about including spaces and backslashes.
+ When your "grep" accepts the "-H" argument, use this to make ":grep"
+ also work well with a single file: >
+ :set grepprg=grep\ -nH
+ < Special value: When 'grepprg' is set to "internal" the |:grep| command
+ works like |:vimgrep|, |:lgrep| like |:lvimgrep|, |:grepadd| like
+ |:vimgrepadd| and |:lgrepadd| like |:lvimgrepadd|.
+ See also the section |:make_makeprg|, since most of the comments there
+ apply equally to 'grepprg'.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'grepprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('program to use for ":grep"'),
+ type = 'string',
+ varname = 'p_gp',
+ },
+ {
+ abbreviation = 'gcr',
+ cb = 'did_set_guicursor',
+ defaults = { if_true = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20' },
+ deny_duplicates = true,
+ desc = [=[
+ Configures the cursor style for each mode. Works in the GUI and many
+ terminals. See |tui-cursor-shape|.
+
+ To disable cursor-styling, reset the option: >
+ :set guicursor=
+
+ < To enable mode shapes, "Cursor" highlight, and blinking: >
+ :set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50
+ \,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor
+ \,sm:block-blinkwait175-blinkoff150-blinkon175
+
+ < The option is a comma-separated list of parts. Each part consists of a
+ mode-list and an argument-list:
+ mode-list:argument-list,mode-list:argument-list,..
+ The mode-list is a dash separated list of these modes:
+ n Normal mode
+ v Visual mode
+ ve Visual mode with 'selection' "exclusive" (same as 'v',
+ if not specified)
+ o Operator-pending mode
+ i Insert mode
+ r Replace mode
+ c Command-line Normal (append) mode
+ ci Command-line Insert mode
+ cr Command-line Replace mode
+ sm showmatch in Insert mode
+ a all modes
+ The argument-list is a dash separated list of these arguments:
+ hor{N} horizontal bar, {N} percent of the character height
+ ver{N} vertical bar, {N} percent of the character width
+ block block cursor, fills the whole character
+ - Only one of the above three should be present.
+ - Default is "block" for each mode.
+ blinkwait{N} *cursor-blinking*
+ blinkon{N}
+ blinkoff{N}
+ blink times for cursor: blinkwait is the delay before
+ the cursor starts blinking, blinkon is the time that
+ the cursor is shown and blinkoff is the time that the
+ cursor is not shown. Times are in msec. When one of
+ the numbers is zero, there is no blinking. E.g.: >
+ :set guicursor=n:blinkon0
+ < - Default is "blinkon0" for each mode.
+ {group-name}
+ Highlight group that decides the color and font of the
+ cursor.
+ In the |TUI|:
+ - |inverse|/reverse and no group-name are interpreted
+ as "host-terminal default cursor colors" which
+ typically means "inverted bg and fg colors".
+ - |ctermfg| and |guifg| are ignored.
+ {group-name}/{group-name}
+ Two highlight group names, the first is used when
+ no language mappings are used, the other when they
+ are. |language-mapping|
+
+ Examples of parts:
+ n-c-v:block-nCursor In Normal, Command-line and Visual mode, use a
+ block cursor with colors from the "nCursor"
+ highlight group
+ n-v-c-sm:block,i-ci-ve:ver25-Cursor,r-cr-o:hor20
+ In Normal et al. modes, use a block cursor
+ with the default colors defined by the host
+ terminal. In Insert-like modes, use
+ a vertical bar cursor with colors from
+ "Cursor" highlight group. In Replace-like
+ modes, use an underline cursor with
+ default colors.
+ i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150
+ In Insert and Command-line Insert mode, use a
+ 30% vertical bar cursor with colors from the
+ "iCursor" highlight group. Blink a bit
+ faster.
+
+ The 'a' mode is different. It will set the given argument-list for
+ all modes. It does not reset anything to defaults. This can be used
+ to do a common setting for all modes. For example, to switch off
+ blinking: "a:blinkon0"
+
+ Examples of cursor highlighting: >
+ :highlight Cursor gui=reverse guifg=NONE guibg=NONE
+ :highlight Cursor gui=NONE guifg=bg guibg=fg
+ <
+ ]=],
+ full_name = 'guicursor',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('GUI: settings for cursor shape and blinking'),
+ tags = { 'E545', 'E546', 'E548', 'E549' },
+ type = 'string',
+ varname = 'p_guicursor',
+ },
+ {
+ abbreviation = 'gfn',
+ defaults = { if_true = '' },
+ desc = [=[
+ This is a list of fonts which will be used for the GUI version of Vim.
+ In its simplest form the value is just one font name. When
+ the font cannot be found you will get an error message. To try other
+ font names a list can be specified, font names separated with commas.
+ The first valid font is used.
+
+ Spaces after a comma are ignored. To include a comma in a font name
+ precede it with a backslash. Setting an option requires an extra
+ backslash before a space and a backslash. See also
+ |option-backslash|. For example: >
+ :set guifont=Screen15,\ 7x13,font\\,with\\,commas
+ < will make Vim try to use the font "Screen15" first, and if it fails it
+ will try to use "7x13" and then "font,with,commas" instead.
+
+ If none of the fonts can be loaded, Vim will keep the current setting.
+ If an empty font list is given, Vim will try using other resource
+ settings (for X, it will use the Vim.font resource), and finally it
+ will try some builtin default which should always be there ("7x13" in
+ the case of X). The font names given should be "normal" fonts. Vim
+ will try to find the related bold and italic fonts.
+
+ For Win32 and Mac OS: >
+ :set guifont=*
+ < will bring up a font requester, where you can pick the font you want.
+
+ The font name depends on the GUI used.
+
+ For Mac OSX you can use something like this: >
+ :set guifont=Monaco:h10
+ < *E236*
+ Note that the fonts must be mono-spaced (all characters have the same
+ width).
+
+ To preview a font on X11, you might be able to use the "xfontsel"
+ program. The "xlsfonts" program gives a list of all available fonts.
+
+ For the Win32 GUI *E244* *E245*
+ - takes these options in the font name:
+ hXX - height is XX (points, can be floating-point)
+ wXX - width is XX (points, can be floating-point)
+ b - bold
+ i - italic
+ u - underline
+ s - strikeout
+ cXX - character set XX. Valid charsets are: ANSI, ARABIC,
+ BALTIC, CHINESEBIG5, DEFAULT, EASTEUROPE, GB2312, GREEK,
+ HANGEUL, HEBREW, JOHAB, MAC, OEM, RUSSIAN, SHIFTJIS,
+ SYMBOL, THAI, TURKISH, VIETNAMESE ANSI and BALTIC.
+ Normally you would use "cDEFAULT".
+
+ Use a ':' to separate the options.
+ - A '_' can be used in the place of a space, so you don't need to use
+ backslashes to escape the spaces.
+ - Examples: >
+ :set guifont=courier_new:h12:w5:b:cRUSSIAN
+ :set guifont=Andale_Mono:h7.5:w4.5
+ <
+ ]=],
+ deny_duplicates = true,
+ full_name = 'guifont',
+ list = 'onecomma',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('GUI: Name(s) of font(s) to be used'),
+ tags = { 'E235', 'E596' },
+ type = 'string',
+ varname = 'p_guifont',
+ },
+ {
+ abbreviation = 'gfw',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of fonts to be used for double-width characters.
+ The first font that can be loaded is used.
+ Note: The size of these fonts must be exactly twice as wide as the one
+ specified with 'guifont' and the same height.
+
+ When 'guifont' has a valid font and 'guifontwide' is empty Vim will
+ attempt to set 'guifontwide' to a matching double-width font.
+ ]=],
+ full_name = 'guifontwide',
+ list = 'onecomma',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('list of font names for double-wide characters'),
+ tags = { 'E231', 'E533', 'E534' },
+ type = 'string',
+ varname = 'p_guifontwide',
+ },
+ {
+ abbreviation = 'go',
+ defaults = {
+ if_true = '',
+ doc = '"egmrLT" (MS-Windows)',
+ },
+ desc = [=[
+ This option only has an effect in the GUI version of Vim. It is a
+ sequence of letters which describes what components and options of the
+ GUI should be used.
+ To avoid problems with flags that are added in the future, use the
+ "+=" and "-=" feature of ":set" |add-option-flags|.
+
+ Valid letters are as follows:
+ *guioptions_a* *'go-a'*
+ 'a' Autoselect: If present, then whenever VISUAL mode is started,
+ or the Visual area extended, Vim tries to become the owner of
+ the windowing system's global selection. This means that the
+ Visually highlighted text is available for pasting into other
+ applications as well as into Vim itself. When the Visual mode
+ ends, possibly due to an operation on the text, or when an
+ application wants to paste the selection, the highlighted text
+ is automatically yanked into the "* selection register.
+ Thus the selection is still available for pasting into other
+ applications after the VISUAL mode has ended.
+ If not present, then Vim won't become the owner of the
+ windowing system's global selection unless explicitly told to
+ by a yank or delete operation for the "* register.
+ The same applies to the modeless selection.
+ *'go-P'*
+ 'P' Like autoselect but using the "+ register instead of the "*
+ register.
+ *'go-A'*
+ 'A' Autoselect for the modeless selection. Like 'a', but only
+ applies to the modeless selection.
+
+ 'guioptions' autoselect Visual autoselect modeless ~
+ "" - -
+ "a" yes yes
+ "A" - yes
+ "aA" yes yes
+
+ *'go-c'*
+ 'c' Use console dialogs instead of popup dialogs for simple
+ choices.
+ *'go-d'*
+ 'd' Use dark theme variant if available.
+ *'go-e'*
+ 'e' Add tab pages when indicated with 'showtabline'.
+ 'guitablabel' can be used to change the text in the labels.
+ When 'e' is missing a non-GUI tab pages line may be used.
+ The GUI tabs are only supported on some systems, currently
+ Mac OS/X and MS-Windows.
+ *'go-i'*
+ 'i' Use a Vim icon.
+ *'go-m'*
+ 'm' Menu bar is present.
+ *'go-M'*
+ 'M' The system menu "$VIMRUNTIME/menu.vim" is not sourced. Note
+ that this flag must be added in the vimrc file, before
+ switching on syntax or filetype recognition (when the |gvimrc|
+ file is sourced the system menu has already been loaded; the
+ `:syntax on` and `:filetype on` commands load the menu too).
+ *'go-g'*
+ 'g' Grey menu items: Make menu items that are not active grey. If
+ 'g' is not included inactive menu items are not shown at all.
+ *'go-T'*
+ 'T' Include Toolbar. Currently only in Win32 GUI.
+ *'go-r'*
+ 'r' Right-hand scrollbar is always present.
+ *'go-R'*
+ 'R' Right-hand scrollbar is present when there is a vertically
+ split window.
+ *'go-l'*
+ 'l' Left-hand scrollbar is always present.
+ *'go-L'*
+ 'L' Left-hand scrollbar is present when there is a vertically
+ split window.
+ *'go-b'*
+ 'b' Bottom (horizontal) scrollbar is present. Its size depends on
+ the longest visible line, or on the cursor line if the 'h'
+ flag is included. |gui-horiz-scroll|
+ *'go-h'*
+ 'h' Limit horizontal scrollbar size to the length of the cursor
+ line. Reduces computations. |gui-horiz-scroll|
+
+ And yes, you may even have scrollbars on the left AND the right if
+ you really want to :-). See |gui-scrollbars| for more information.
+
+ *'go-v'*
+ 'v' Use a vertical button layout for dialogs. When not included,
+ a horizontal layout is preferred, but when it doesn't fit a
+ vertical layout is used anyway. Not supported in GTK 3.
+ *'go-p'*
+ 'p' Use Pointer callbacks for X11 GUI. This is required for some
+ window managers. If the cursor is not blinking or hollow at
+ the right moment, try adding this flag. This must be done
+ before starting the GUI. Set it in your |gvimrc|. Adding or
+ removing it after the GUI has started has no effect.
+ *'go-k'*
+ 'k' Keep the GUI window size when adding/removing a scrollbar, or
+ toolbar, tabline, etc. Instead, the behavior is similar to
+ when the window is maximized and will adjust 'lines' and
+ 'columns' to fit to the window. Without the 'k' flag Vim will
+ try to keep 'lines' and 'columns' the same when adding and
+ removing GUI components.
+ ]=],
+ enable_if = false,
+ full_name = 'guioptions',
+ list = 'flags',
+ scope = { 'global' },
+ short_desc = N_('GUI: Which components and options are used'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'gtl',
+ desc = [=[
+ When non-empty describes the text to use in a label of the GUI tab
+ pages line. When empty and when the result is empty Vim will use a
+ default label. See |setting-guitablabel| for more info.
+
+ The format of this option is like that of 'statusline'.
+ 'guitabtooltip' is used for the tooltip, see below.
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ Only used when the GUI tab pages line is displayed. 'e' must be
+ present in 'guioptions'. For the non-GUI tab pages line 'tabline' is
+ used.
+ ]=],
+ enable_if = false,
+ full_name = 'guitablabel',
+ modelineexpr = true,
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('GUI: custom label for a tab page'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'gtt',
+ desc = [=[
+ When non-empty describes the text to use in a tooltip for the GUI tab
+ pages line. When empty Vim will use a default tooltip.
+ This option is otherwise just like 'guitablabel' above.
+ You can include a line break. Simplest method is to use |:let|: >
+ :let &guitabtooltip = "line one\nline two"
+ <
+ ]=],
+ enable_if = false,
+ full_name = 'guitabtooltip',
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('GUI: custom tooltip for a tab page'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'hf',
+ cb = 'did_set_helpfile',
+ defaults = {
+ if_true = macros('DFLT_HELPFILE'),
+ doc = [[(MS-Windows) "$VIMRUNTIME\doc\help.txt"
+ (others) "$VIMRUNTIME/doc/help.txt"]],
+ },
+ desc = [=[
+ Name of the main help file. All distributed help files should be
+ placed together in one directory. Additionally, all "doc" directories
+ in 'runtimepath' will be used.
+ Environment variables are expanded |:set_env|. For example:
+ "$VIMRUNTIME/doc/help.txt". If $VIMRUNTIME is not set, $VIM is also
+ tried. Also see |$VIMRUNTIME| and |option-backslash| about including
+ spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'helpfile',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('full path name of the main help file'),
+ type = 'string',
+ varname = 'p_hf',
+ },
+ {
+ abbreviation = 'hh',
+ cb = 'did_set_helpheight',
+ defaults = { if_true = 20 },
+ desc = [=[
+ Minimal initial height of the help window when it is opened with the
+ ":help" command. The initial height of the help window is half of the
+ current window, or (when the 'ea' option is on) the same as other
+ windows. When the height is less than 'helpheight', the height is
+ set to 'helpheight'. Set to zero to disable.
+ ]=],
+ full_name = 'helpheight',
+ scope = { 'global' },
+ short_desc = N_('minimum height of a new help window'),
+ type = 'number',
+ varname = 'p_hh',
+ },
+ {
+ abbreviation = 'hlg',
+ cb = 'did_set_helplang',
+ defaults = {
+ if_true = '',
+ doc = 'messages language or empty',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of languages. Vim will use the first language
+ for which the desired help can be found. The English help will always
+ be used as a last resort. You can add "en" to prefer English over
+ another language, but that will only find tags that exist in that
+ language and not in the English help.
+ Example: >
+ :set helplang=de,it
+ < This will first search German, then Italian and finally English help
+ files.
+ When using |CTRL-]| and ":help!" in a non-English help file Vim will
+ try to find the tag in the current language before using this option.
+ See |help-translated|.
+ ]=],
+ full_name = 'helplang',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('preferred help languages'),
+ type = 'string',
+ varname = 'p_hlg',
+ },
+ {
+ abbreviation = 'hid',
+ defaults = { if_true = true },
+ desc = [=[
+ When off a buffer is unloaded (including loss of undo information)
+ when it is |abandon|ed. When on a buffer becomes hidden when it is
+ |abandon|ed. A buffer displayed in another window does not become
+ hidden, of course.
+
+ Commands that move through the buffer list sometimes hide a buffer
+ although the 'hidden' option is off when these three are true:
+ - the buffer is modified
+ - 'autowrite' is off or writing is not possible
+ - the '!' flag was used
+ Also see |windows|.
+
+ To hide a specific buffer use the 'bufhidden' option.
+ 'hidden' is set for one command with ":hide {command}" |:hide|.
+ ]=],
+ full_name = 'hidden',
+ scope = { 'global' },
+ short_desc = N_("don't unload buffer when it is |abandon|ed"),
+ type = 'bool',
+ varname = 'p_hid',
+ },
+ {
+ abbreviation = 'hl',
+ cb = 'did_set_highlight',
+ defaults = { if_true = macros('HIGHLIGHT_INIT') },
+ deny_duplicates = true,
+ full_name = 'highlight',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('sets highlighting mode for various occasions'),
+ type = 'string',
+ varname = 'p_hl',
+ },
+ {
+ abbreviation = 'hi',
+ defaults = { if_true = 10000 },
+ desc = [=[
+ A history of ":" commands, and a history of previous search patterns
+ is remembered. This option decides how many entries may be stored in
+ each of these histories (see |cmdline-editing|).
+ The maximum value is 10000.
+ ]=],
+ full_name = 'history',
+ scope = { 'global' },
+ short_desc = N_('number of command-lines that are remembered'),
+ type = 'number',
+ varname = 'p_hi',
+ },
+ {
+ abbreviation = 'hk',
+ defaults = { if_true = false },
+ full_name = 'hkmap',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'hkp',
+ defaults = { if_true = false },
+ full_name = 'hkmapp',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'hls',
+ cb = 'did_set_hlsearch',
+ defaults = { if_true = true },
+ desc = [=[
+ When there is a previous search pattern, highlight all its matches.
+ The |hl-Search| highlight group determines the highlighting for all
+ matches not under the cursor while the |hl-CurSearch| highlight group
+ (if defined) determines the highlighting for the match under the
+ cursor. If |hl-CurSearch| is not defined, then |hl-Search| is used for
+ both. Note that only the matching text is highlighted, any offsets
+ are not applied.
+ See also: 'incsearch' and |:match|.
+ When you get bored looking at the highlighted matches, you can turn it
+ off with |:nohlsearch|. This does not change the option value, as
+ soon as you use a search command, the highlighting comes back.
+ 'redrawtime' specifies the maximum time spent on finding matches.
+ When the search pattern can match an end-of-line, Vim will try to
+ highlight all of the matched text. However, this depends on where the
+ search starts. This will be the first line in the window or the first
+ line below a closed fold. A match in a previous line which is not
+ drawn may not continue in a newly drawn line.
+ You can specify whether the highlight status is restored on startup
+ with the 'h' flag in 'shada' |shada-h|.
+ ]=],
+ full_name = 'hlsearch',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('highlight matches with last search pattern'),
+ type = 'bool',
+ varname = 'p_hls',
+ },
+ {
+ cb = 'did_set_title_icon',
+ defaults = {
+ if_true = false,
+ doc = 'off, on when title can be restored',
+ },
+ desc = [=[
+ When on, the icon text of the window will be set to the value of
+ 'iconstring' (if it is not empty), or to the name of the file
+ currently being edited. Only the last part of the name is used.
+ Overridden by the 'iconstring' option.
+ Only works if the terminal supports setting window icons.
+ ]=],
+ full_name = 'icon',
+ scope = { 'global' },
+ short_desc = N_('Vim set the text of the window icon'),
+ type = 'bool',
+ varname = 'p_icon',
+ },
+ {
+ cb = 'did_set_iconstring',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is not empty, it will be used for the icon text of
+ the window. This happens only when the 'icon' option is on.
+ Only works if the terminal supports setting window icon text
+ When this option contains printf-style '%' items, they will be
+ expanded according to the rules used for 'statusline'. See
+ 'titlestring' for example settings.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+ ]=],
+ full_name = 'iconstring',
+ modelineexpr = true,
+ scope = { 'global' },
+ short_desc = N_('to use for the Vim icon text'),
+ type = 'string',
+ varname = 'p_iconstring',
+ },
+ {
+ abbreviation = 'ic',
+ cb = 'did_set_ignorecase',
+ defaults = { if_true = false },
+ desc = [=[
+ Ignore case in search patterns, |cmdline-completion|, when
+ searching in the tags file, and |expr-==|.
+ Also see 'smartcase' and 'tagcase'.
+ Can be overruled by using "\c" or "\C" in the pattern, see
+ |/ignorecase|.
+ ]=],
+ full_name = 'ignorecase',
+ scope = { 'global' },
+ short_desc = N_('ignore case in search patterns'),
+ type = 'bool',
+ varname = 'p_ic',
+ },
+ {
+ abbreviation = 'imc',
+ defaults = { if_true = false },
+ desc = [=[
+ When set the Input Method is always on when starting to edit a command
+ line, unless entering a search pattern (see 'imsearch' for that).
+ Setting this option is useful when your input method allows entering
+ English characters directly, e.g., when it's used to type accented
+ characters with dead keys.
+ ]=],
+ enable_if = false,
+ full_name = 'imcmdline',
+ scope = { 'global' },
+ short_desc = N_('use IM when starting to edit a command line'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'imd',
+ defaults = {
+ if_true = false,
+ doc = 'off, on for some systems (SGI)',
+ },
+ desc = [=[
+ When set the Input Method is never used. This is useful to disable
+ the IM when it doesn't work properly.
+ Currently this option is on by default for SGI/IRIX machines. This
+ may change in later releases.
+ ]=],
+ enable_if = false,
+ full_name = 'imdisable',
+ scope = { 'global' },
+ short_desc = N_('do not use the IM in any mode'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'imi',
+ cb = 'did_set_iminsert',
+ defaults = { if_true = macros('B_IMODE_NONE') },
+ desc = [=[
+ Specifies whether :lmap or an Input Method (IM) is to be used in
+ Insert mode. Valid values:
+ 0 :lmap is off and IM is off
+ 1 :lmap is ON and IM is off
+ 2 :lmap is off and IM is ON
+ To always reset the option to zero when leaving Insert mode with <Esc>
+ this can be used: >
+ :inoremap <ESC> <ESC>:set iminsert=0<CR>
+ < This makes :lmap and IM turn off automatically when leaving Insert
+ mode.
+ Note that this option changes when using CTRL-^ in Insert mode
+ |i_CTRL-^|.
+ The value is set to 1 when setting 'keymap' to a valid keymap name.
+ It is also used for the argument of commands like "r" and "f".
+ ]=],
+ full_name = 'iminsert',
+ pv_name = 'p_imi',
+ scope = { 'buffer' },
+ short_desc = N_('use :lmap or IM in Insert mode'),
+ type = 'number',
+ varname = 'p_iminsert',
+ },
+ {
+ abbreviation = 'ims',
+ defaults = { if_true = macros('B_IMODE_USE_INSERT') },
+ desc = [=[
+ Specifies whether :lmap or an Input Method (IM) is to be used when
+ entering a search pattern. Valid values:
+ -1 the value of 'iminsert' is used, makes it look like
+ 'iminsert' is also used when typing a search pattern
+ 0 :lmap is off and IM is off
+ 1 :lmap is ON and IM is off
+ 2 :lmap is off and IM is ON
+ Note that this option changes when using CTRL-^ in Command-line mode
+ |c_CTRL-^|.
+ The value is set to 1 when it is not -1 and setting the 'keymap'
+ option to a valid keymap name.
+ ]=],
+ full_name = 'imsearch',
+ pv_name = 'p_ims',
+ scope = { 'buffer' },
+ short_desc = N_('use :lmap or IM when typing a search pattern'),
+ type = 'number',
+ varname = 'p_imsearch',
+ },
+ {
+ abbreviation = 'icm',
+ cb = 'did_set_inccommand',
+ defaults = { if_true = 'nosplit' },
+ desc = [=[
+ When nonempty, shows the effects of |:substitute|, |:smagic|,
+ |:snomagic| and user commands with the |:command-preview| flag as you
+ type.
+
+ Possible values:
+ nosplit Shows the effects of a command incrementally in the
+ buffer.
+ split Like "nosplit", but also shows partial off-screen
+ results in a preview window.
+
+ If the preview for built-in commands is too slow (exceeds
+ 'redrawtime') then 'inccommand' is automatically disabled until
+ |Command-line-mode| is done.
+ ]=],
+ expand_cb = 'expand_set_inccommand',
+ full_name = 'inccommand',
+ scope = { 'global' },
+ short_desc = N_('Live preview of substitution'),
+ type = 'string',
+ varname = 'p_icm',
+ },
+ {
+ abbreviation = 'inc',
+ alloced = true,
+ defaults = { if_true = '' },
+ desc = [=[
+ Pattern to be used to find an include command. It is a search
+ pattern, just like for the "/" command (See |pattern|). This option
+ is used for the commands "[i", "]I", "[d", etc.
+ Normally the 'isfname' option is used to recognize the file name that
+ comes after the matched pattern. But if "\zs" appears in the pattern
+ then the text matched from "\zs" to the end, or until "\ze" if it
+ appears, is used as the file name. Use this to include characters
+ that are not in 'isfname', such as a space. You can then use
+ 'includeexpr' to process the matched text.
+ See |option-backslash| about including spaces and backslashes.
+ ]=],
+ full_name = 'include',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('pattern to be used to find an include file'),
+ type = 'string',
+ varname = 'p_inc',
+ },
+ {
+ abbreviation = 'inex',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression to be used to transform the string found with the 'include'
+ option to a file name. Mostly useful to change "." to "/" for Java: >
+ :setlocal includeexpr=substitute(v:fname,'\\.','/','g')
+ < The "v:fname" variable will be set to the file name that was detected.
+ Note the double backslash: the `:set` command first halves them, then
+ one remains in the value, where "\." matches a dot literally. For
+ simple character replacements `tr()` avoids the need for escaping: >
+ :setlocal includeexpr=tr(v:fname,'.','/')
+ <
+ Also used for the |gf| command if an unmodified file name can't be
+ found. Allows doing "gf" on the name after an 'include' statement.
+ Also used for |<cfile>|.
+
+ If the expression starts with s: or |<SID>|, then it is replaced with
+ the script ID (|local-function|). Example: >
+ setlocal includeexpr=s:MyIncludeExpr(v:fname)
+ setlocal includeexpr=<SID>SomeIncludeExpr(v:fname)
+ < Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'includeexpr' |textlock|.
+ ]=],
+ full_name = 'includeexpr',
+ modelineexpr = true,
+ scope = { 'buffer' },
+ short_desc = N_('expression used to process an include line'),
+ type = 'string',
+ varname = 'p_inex',
+ },
+ {
+ abbreviation = 'is',
+ defaults = { if_true = true },
+ desc = [=[
+ While typing a search command, show where the pattern, as it was typed
+ so far, matches. The matched string is highlighted. If the pattern
+ is invalid or not found, nothing is shown. The screen will be updated
+ often, this is only useful on fast terminals.
+ Note that the match will be shown, but the cursor will return to its
+ original position when no match is found and when pressing <Esc>. You
+ still need to finish the search command with <Enter> to move the
+ cursor to the match.
+ You can use the CTRL-G and CTRL-T keys to move to the next and
+ previous match. |c_CTRL-G| |c_CTRL-T|
+ Vim only searches for about half a second. With a complicated
+ pattern and/or a lot of text the match may not be found. This is to
+ avoid that Vim hangs while you are typing the pattern.
+ The |hl-IncSearch| highlight group determines the highlighting.
+ When 'hlsearch' is on, all matched strings are highlighted too while
+ typing a search command. See also: 'hlsearch'.
+ If you don't want to turn 'hlsearch' on, but want to highlight all
+ matches while searching, you can turn on and off 'hlsearch' with
+ autocmd. Example: >
+ augroup vimrc-incsearch-highlight
+ autocmd!
+ autocmd CmdlineEnter /,\? :set hlsearch
+ autocmd CmdlineLeave /,\? :set nohlsearch
+ augroup END
+ <
+ CTRL-L can be used to add one character from after the current match
+ to the command line. If 'ignorecase' and 'smartcase' are set and the
+ command line has no uppercase characters, the added character is
+ converted to lowercase.
+ CTRL-R CTRL-W can be used to add the word at the end of the current
+ match, excluding the characters that were already typed.
+ ]=],
+ full_name = 'incsearch',
+ scope = { 'global' },
+ short_desc = N_('highlight match while typing search pattern'),
+ type = 'bool',
+ varname = 'p_is',
+ },
+ {
+ abbreviation = 'inde',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to obtain the proper indent for a line.
+ It is used when a new line is created, for the |=| operator and
+ in Insert mode as specified with the 'indentkeys' option.
+ When this option is not empty, it overrules the 'cindent' and
+ 'smartindent' indenting. When 'lisp' is set, this option is
+ is only used when 'lispoptions' contains "expr:1".
+ The expression is evaluated with |v:lnum| set to the line number for
+ which the indent is to be computed. The cursor is also in this line
+ when the expression is evaluated (but it may be moved around).
+
+ If the expression starts with s: or |<SID>|, then it is replaced with
+ the script ID (|local-function|). Example: >
+ set indentexpr=s:MyIndentExpr()
+ set indentexpr=<SID>SomeIndentExpr()
+ < Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
+ The expression must return the number of spaces worth of indent. It
+ can return "-1" to keep the current indent (this means 'autoindent' is
+ used for the indent).
+ Functions useful for computing the indent are |indent()|, |cindent()|
+ and |lispindent()|.
+ The evaluation of the expression must not have side effects! It must
+ not change the text, jump to another window, etc. Afterwards the
+ cursor position is always restored, thus the cursor may be moved.
+ Normally this option would be set to call a function: >
+ :set indentexpr=GetMyIndent()
+ < Error messages will be suppressed, unless the 'debug' option contains
+ "msg".
+ See |indent-expression|.
+
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'indentexpr' |textlock|.
+ ]=],
+ full_name = 'indentexpr',
+ modelineexpr = true,
+ scope = { 'buffer' },
+ short_desc = N_('expression used to obtain the indent of a line'),
+ type = 'string',
+ varname = 'p_inde',
+ },
+ {
+ abbreviation = 'indk',
+ alloced = true,
+ defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of keys that, when typed in Insert mode, cause reindenting of
+ the current line. Only happens if 'indentexpr' isn't empty.
+ The format is identical to 'cinkeys', see |indentkeys-format|.
+ See |C-indenting| and |indent-expression|.
+ ]=],
+ full_name = 'indentkeys',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("keys that trigger indenting with 'indentexpr'"),
+ type = 'string',
+ varname = 'p_indk',
+ },
+ {
+ abbreviation = 'inf',
+ defaults = { if_true = false },
+ desc = [=[
+ When doing keyword completion in insert mode |ins-completion|, and
+ 'ignorecase' is also on, the case of the match is adjusted depending
+ on the typed text. If the typed text contains a lowercase letter
+ where the match has an upper case letter, the completed part is made
+ lowercase. If the typed text has no lowercase letters and the match
+ has a lowercase letter where the typed text has an uppercase letter,
+ and there is a letter before it, the completed part is made uppercase.
+ With 'noinfercase' the match is used as-is.
+ ]=],
+ full_name = 'infercase',
+ scope = { 'buffer' },
+ short_desc = N_('adjust case of match for keyword completion'),
+ type = 'bool',
+ varname = 'p_inf',
+ },
+ {
+ abbreviation = 'im',
+ defaults = { if_true = false },
+ full_name = 'insertmode',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'isf',
+ cb = 'did_set_isopt',
+ defaults = {
+ condition = 'BACKSLASH_IN_FILENAME',
+ if_false = '@,48-57,/,.,-,_,+,,,#,$,%,~,=',
+ if_true = '@,48-57,/,\\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,=',
+ doc = [[for Windows:
+ "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,="
+ otherwise: "@,48-57,/,.,-,_,+,,,#,$,%,~,="]],
+ },
+ deny_duplicates = true,
+ desc = [=[
+ The characters specified by this option are included in file names and
+ path names. Filenames are used for commands like "gf", "[i" and in
+ the tags file. It is also used for "\f" in a |pattern|.
+ Multi-byte characters 256 and above are always included, only the
+ characters up to 255 are specified with this option.
+ For UTF-8 the characters 0xa0 to 0xff are included as well.
+ Think twice before adding white space to this option. Although a
+ space may appear inside a file name, the effect will be that Vim
+ doesn't know where a file name starts or ends when doing completion.
+ It most likely works better without a space in 'isfname'.
+
+ Note that on systems using a backslash as path separator, Vim tries to
+ do its best to make it work as you would expect. That is a bit
+ tricky, since Vi originally used the backslash to escape special
+ characters. Vim will not remove a backslash in front of a normal file
+ name character on these systems, but it will on Unix and alikes. The
+ '&' and '^' are not included by default, because these are special for
+ cmd.exe.
+
+ The format of this option is a list of parts, separated with commas.
+ Each part can be a single character number or a range. A range is two
+ character numbers with '-' in between. A character number can be a
+ decimal number between 0 and 255 or the ASCII character itself (does
+ not work for digits). Example:
+ "_,-,128-140,#-43" (include '_' and '-' and the range
+ 128 to 140 and '#' to 43)
+ If a part starts with '^', the following character number or range
+ will be excluded from the option. The option is interpreted from left
+ to right. Put the excluded character after the range where it is
+ included. To include '^' itself use it as the last character of the
+ option or the end of a range. Example:
+ "^a-z,#,^" (exclude 'a' to 'z', include '#' and '^')
+ If the character is '@', all characters where isalpha() returns TRUE
+ are included. Normally these are the characters a to z and A to Z,
+ plus accented characters. To include '@' itself use "@-@". Examples:
+ "@,^a-z" All alphabetic characters, excluding lower
+ case ASCII letters.
+ "a-z,A-Z,@-@" All letters plus the '@' character.
+ A comma can be included by using it where a character number is
+ expected. Example:
+ "48-57,,,_" Digits, comma and underscore.
+ A comma can be excluded by prepending a '^'. Example:
+ " -~,^,,9" All characters from space to '~', excluding
+ comma, plus <Tab>.
+ See |option-backslash| about including spaces and backslashes.
+ ]=],
+ full_name = 'isfname',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('characters included in file names and pathnames'),
+ type = 'string',
+ varname = 'p_isf',
+ },
+ {
+ abbreviation = 'isi',
+ cb = 'did_set_isopt',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '@,48-57,_,192-255',
+ if_true = '@,48-57,_,128-167,224-235',
+ doc = [[for Windows:
+ "@,48-57,_,128-167,224-235"
+ otherwise: "@,48-57,_,192-255"]],
+ },
+ deny_duplicates = true,
+ desc = [=[
+ The characters given by this option are included in identifiers.
+ Identifiers are used in recognizing environment variables and after a
+ match of the 'define' option. It is also used for "\i" in a
+ |pattern|. See 'isfname' for a description of the format of this
+ option. For '@' only characters up to 255 are used.
+ Careful: If you change this option, it might break expanding
+ environment variables. E.g., when '/' is included and Vim tries to
+ expand "$HOME/.local/state/nvim/shada/main.shada". Maybe you should
+ change 'iskeyword' instead.
+ ]=],
+ full_name = 'isident',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('characters included in identifiers'),
+ type = 'string',
+ varname = 'p_isi',
+ },
+ {
+ abbreviation = 'isk',
+ alloced = true,
+ cb = 'did_set_isopt',
+ defaults = { if_true = '@,48-57,_,192-255' },
+ deny_duplicates = true,
+ desc = [=[
+ Keywords are used in searching and recognizing with many commands:
+ "w", "*", "[i", etc. It is also used for "\k" in a |pattern|. See
+ 'isfname' for a description of the format of this option. For '@'
+ characters above 255 check the "word" character class (any character
+ that is not white space or punctuation).
+ For C programs you could use "a-z,A-Z,48-57,_,.,-,>".
+ For a help file it is set to all non-blank printable characters except
+ "*", '"' and '|' (so that CTRL-] on a command finds the help for that
+ command).
+ When the 'lisp' option is on the '-' character is always included.
+ This option also influences syntax highlighting, unless the syntax
+ uses |:syn-iskeyword|.
+ ]=],
+ full_name = 'iskeyword',
+ list = 'comma',
+ scope = { 'buffer' },
+ short_desc = N_('characters included in keywords'),
+ type = 'string',
+ varname = 'p_isk',
+ },
+ {
+ abbreviation = 'isp',
+ cb = 'did_set_isopt',
+ defaults = { if_true = '@,161-255' },
+ deny_duplicates = true,
+ desc = [=[
+ The characters given by this option are displayed directly on the
+ screen. It is also used for "\p" in a |pattern|. The characters from
+ space (ASCII 32) to '~' (ASCII 126) are always displayed directly,
+ even when they are not included in 'isprint' or excluded. See
+ 'isfname' for a description of the format of this option.
+
+ Non-printable characters are displayed with two characters:
+ 0 - 31 "^@" - "^_"
+ 32 - 126 always single characters
+ 127 "^?"
+ 128 - 159 "~@" - "~_"
+ 160 - 254 "| " - "|~"
+ 255 "~?"
+ Illegal bytes from 128 to 255 (invalid UTF-8) are
+ displayed as <xx>, with the hexadecimal value of the byte.
+ When 'display' contains "uhex" all unprintable characters are
+ displayed as <xx>.
+ The SpecialKey highlighting will be used for unprintable characters.
+ |hl-SpecialKey|
+
+ Multi-byte characters 256 and above are always included, only the
+ characters up to 255 are specified with this option. When a character
+ is printable but it is not available in the current font, a
+ replacement character will be shown.
+ Unprintable and zero-width Unicode characters are displayed as <xxxx>.
+ There is no option to specify these characters.
+ ]=],
+ full_name = 'isprint',
+ list = 'comma',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('printable characters'),
+ type = 'string',
+ varname = 'p_isp',
+ },
+ {
+ abbreviation = 'js',
+ defaults = { if_true = false },
+ desc = [=[
+ Insert two spaces after a '.', '?' and '!' with a join command.
+ Otherwise only one space is inserted.
+ ]=],
+ full_name = 'joinspaces',
+ scope = { 'global' },
+ short_desc = N_('two spaces after a period with a join command'),
+ type = 'bool',
+ varname = 'p_js',
+ },
+ {
+ abbreviation = 'jop',
+ cb = 'did_set_jumpoptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of words that change the behavior of the |jumplist|.
+ stack Make the jumplist behave like the tagstack.
+ Relative location of entries in the jumplist is
+ preserved at the cost of discarding subsequent entries
+ when navigating backwards in the jumplist and then
+ jumping to a location. |jumplist-stack|
+
+ view When moving through the jumplist, |changelist|,
+ |alternate-file| or using |mark-motions| try to
+ restore the |mark-view| in which the action occurred.
+ ]=],
+ expand_cb = 'expand_set_jumpoptions',
+ full_name = 'jumpoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('Controls the behavior of the jumplist'),
+ type = 'string',
+ varname = 'p_jop',
+ },
+ {
+ abbreviation = 'kmp',
+ alloced = true,
+ cb = 'did_set_keymap',
+ defaults = { if_true = '' },
+ desc = [=[
+ Name of a keyboard mapping. See |mbyte-keymap|.
+ Setting this option to a valid keymap name has the side effect of
+ setting 'iminsert' to one, so that the keymap becomes effective.
+ 'imsearch' is also set to one, unless it was -1
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'keymap',
+ normal_fname_chars = true,
+ pri_mkrc = true,
+ pv_name = 'p_kmap',
+ redraw = { 'statuslines', 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('name of a keyboard mapping'),
+ type = 'string',
+ varname = 'p_keymap',
+ },
+ {
+ abbreviation = 'km',
+ cb = 'did_set_keymodel',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of comma-separated words, which enable special things that keys
+ can do. These values can be used:
+ startsel Using a shifted special key starts selection (either
+ Select mode or Visual mode, depending on "key" being
+ present in 'selectmode').
+ stopsel Using a not-shifted special key stops selection.
+ Special keys in this context are the cursor keys, <End>, <Home>,
+ <PageUp> and <PageDown>.
+ ]=],
+ expand_cb = 'expand_set_keymodel',
+ full_name = 'keymodel',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('enable starting/stopping selection with keys'),
+ type = 'string',
+ varname = 'p_km',
+ },
+ {
+ abbreviation = 'kp',
+ defaults = {
+ if_true = ':Man',
+ doc = '":Man", Windows: ":help"',
+ },
+ desc = [=[
+ Program to use for the |K| command. Environment variables are
+ expanded |:set_env|. ":help" may be used to access the Vim internal
+ help. (Note that previously setting the global option to the empty
+ value did this, which is now deprecated.)
+ When the first character is ":", the command is invoked as a Vim
+ Ex command prefixed with [count].
+ When "man" or "man -s" is used, Vim will automatically translate
+ a [count] for the "K" command to a section number.
+ See |option-backslash| about including spaces and backslashes.
+ Example: >
+ :set keywordprg=man\ -s
+ :set keywordprg=:Man
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'keywordprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('program to use for the "K" command'),
+ type = 'string',
+ varname = 'p_kp',
+ },
+ {
+ abbreviation = 'lmap',
+ cb = 'did_set_langmap',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ This option allows switching your keyboard into a special language
+ mode. When you are typing text in Insert mode the characters are
+ inserted directly. When in Normal mode the 'langmap' option takes
+ care of translating these special characters to the original meaning
+ of the key. This means you don't have to change the keyboard mode to
+ be able to execute Normal mode commands.
+ This is the opposite of the 'keymap' option, where characters are
+ mapped in Insert mode.
+ Also consider setting 'langremap' to off, to prevent 'langmap' from
+ applying to characters resulting from a mapping.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+
+ Example (for Greek, in UTF-8): *greek* >
+ :set langmap=ΑA,Î’B,ΨC,ΔD,ΕE,ΦF,ΓG,ΗH,ΙI,ΞJ,ΚK,ΛL,ΜM,ÎN,ΟO,ΠP,QQ,ΡR,ΣS,ΤT,ΘU,ΩV,WW,ΧX,Î¥Y,ΖZ,αa,βb,ψc,δd,εe,φf,γg,ηh,ιi,ξj,κk,λl,μm,νn,οo,Ï€p,qq,Ïr,σs,Ï„t,θu,ωv,Ï‚w,χx,Ï…y,ζz
+ < Example (exchanges meaning of z and y for commands): >
+ :set langmap=zy,yz,ZY,YZ
+ <
+ The 'langmap' option is a list of parts, separated with commas. Each
+ part can be in one of two forms:
+ 1. A list of pairs. Each pair is a "from" character immediately
+ followed by the "to" character. Examples: "aA", "aAbBcC".
+ 2. A list of "from" characters, a semi-colon and a list of "to"
+ characters. Example: "abc;ABC"
+ Example: "aA,fgh;FGH,cCdDeE"
+ Special characters need to be preceded with a backslash. These are
+ ";", ',', '"', '|' and backslash itself.
+
+ This will allow you to activate vim actions without having to switch
+ back and forth between the languages. Your language characters will
+ be understood as normal vim English characters (according to the
+ langmap mappings) in the following cases:
+ o Normal/Visual mode (commands, buffer/register names, user mappings)
+ o Insert/Replace Mode: Register names after CTRL-R
+ o Insert/Replace Mode: Mappings
+ Characters entered in Command-line mode will NOT be affected by
+ this option. Note that this option can be changed at any time
+ allowing to switch between mappings for different languages/encodings.
+ Use a mapping to avoid having to type it each time!
+ ]=],
+ full_name = 'langmap',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('alphabetic characters for other language mode'),
+ tags = { 'E357', 'E358' },
+ type = 'string',
+ varname = 'p_langmap',
+ },
+ {
+ abbreviation = 'lm',
+ defaults = { if_true = '' },
+ desc = [=[
+ Language to use for menu translation. Tells which file is loaded
+ from the "lang" directory in 'runtimepath': >
+ "lang/menu_" .. &langmenu .. ".vim"
+ < (without the spaces). For example, to always use the Dutch menus, no
+ matter what $LANG is set to: >
+ :set langmenu=nl_NL.ISO_8859-1
+ < When 'langmenu' is empty, |v:lang| is used.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ If your $LANG is set to a non-English language but you do want to use
+ the English menus: >
+ :set langmenu=none
+ < This option must be set before loading menus, switching on filetype
+ detection or syntax highlighting. Once the menus are defined setting
+ this option has no effect. But you could do this: >
+ :source $VIMRUNTIME/delmenu.vim
+ :set langmenu=de_DE.ISO_8859-1
+ :source $VIMRUNTIME/menu.vim
+ < Warning: This deletes all menus that you defined yourself!
+ ]=],
+ full_name = 'langmenu',
+ normal_fname_chars = true,
+ scope = { 'global' },
+ short_desc = N_('language to be used for the menus'),
+ type = 'string',
+ varname = 'p_lm',
+ },
+ {
+ abbreviation = 'lnr',
+ cb = 'did_set_langnoremap',
+ defaults = { if_true = true },
+ full_name = 'langnoremap',
+ scope = { 'global' },
+ short_desc = N_("do not apply 'langmap' to mapped characters"),
+ type = 'bool',
+ varname = 'p_lnr',
+ },
+ {
+ abbreviation = 'lrm',
+ cb = 'did_set_langremap',
+ defaults = { if_true = false },
+ desc = [=[
+ When off, setting 'langmap' does not apply to characters resulting from
+ a mapping. If setting 'langmap' disables some of your mappings, make
+ sure this option is off.
+ ]=],
+ full_name = 'langremap',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ varname = 'p_lrm',
+ },
+ {
+ abbreviation = 'ls',
+ cb = 'did_set_laststatus',
+ defaults = { if_true = 2 },
+ desc = [=[
+ The value of this option influences when the last window will have a
+ status line:
+ 0: never
+ 1: only if there are at least two windows
+ 2: always
+ 3: always and ONLY the last window
+ The screen looks nicer with a status line if you have several
+ windows, but it takes another screen line. |status-line|
+ ]=],
+ full_name = 'laststatus',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('tells when last window has status lines'),
+ type = 'number',
+ varname = 'p_ls',
+ },
+ {
+ abbreviation = 'lz',
+ defaults = { if_true = false },
+ desc = [=[
+ When this option is set, the screen will not be redrawn while
+ executing macros, registers and other commands that have not been
+ typed. Also, updating the window title is postponed. To force an
+ update use |:redraw|.
+ This may occasionally cause display errors. It is only meant to be set
+ temporarily when performing an operation where redrawing may cause
+ flickering or cause a slow down.
+ ]=],
+ full_name = 'lazyredraw',
+ scope = { 'global' },
+ short_desc = N_("don't redraw while executing macros"),
+ type = 'bool',
+ varname = 'p_lz',
+ },
+ {
+ abbreviation = 'lbr',
+ defaults = { if_true = false },
+ desc = [=[
+ If on, Vim will wrap long lines at a character in 'breakat' rather
+ than at the last character that fits on the screen. Unlike
+ 'wrapmargin' and 'textwidth', this does not insert <EOL>s in the file,
+ it only affects the way the file is displayed, not its contents.
+ If 'breakindent' is set, line is visually indented. Then, the value
+ of 'showbreak' is used to put in front of wrapped lines. This option
+ is not used when the 'wrap' option is off.
+ Note that <Tab> characters after an <EOL> are mostly not displayed
+ with the right amount of white space.
+ ]=],
+ full_name = 'linebreak',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('wrap long lines at a blank'),
+ type = 'bool',
+ },
+ {
+ defaults = {
+ if_true = macros('DFLT_ROWS'),
+ doc = '24 or terminal height',
+ },
+ desc = [=[
+ Number of lines of the Vim window.
+ Normally you don't need to set this. It is done automatically by the
+ terminal initialization code.
+ When Vim is running in the GUI or in a resizable window, setting this
+ option will cause the window size to be changed. When you only want
+ to use the size for the GUI, put the command in your |gvimrc| file.
+ Vim limits the number of lines to what fits on the screen. You can
+ use this command to get the tallest window possible: >
+ :set lines=999
+ < Minimum value is 2, maximum value is 1000.
+ ]=],
+ full_name = 'lines',
+ no_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('of lines in the display'),
+ tags = { 'E593' },
+ type = 'number',
+ varname = 'p_lines',
+ },
+ {
+ abbreviation = 'lsp',
+ defaults = { if_true = 0 },
+ desc = [=[
+ only in the GUI
+ Number of pixel lines inserted between characters. Useful if the font
+ uses the full character cell height, making lines touch each other.
+ When non-zero there is room for underlining.
+ With some fonts there can be too much room between lines (to have
+ space for ascents and descents). Then it makes sense to set
+ 'linespace' to a negative value. This may cause display problems
+ though!
+ ]=],
+ full_name = 'linespace',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('number of pixel lines to use between characters'),
+ type = 'number',
+ varname = 'p_linespace',
+ },
+ {
+ cb = 'did_set_lisp',
+ defaults = { if_true = false },
+ desc = [=[
+ Lisp mode: When <Enter> is typed in insert mode set the indent for
+ the next line to Lisp standards (well, sort of). Also happens with
+ "cc" or "S". 'autoindent' must also be on for this to work. The 'p'
+ flag in 'cpoptions' changes the method of indenting: Vi compatible or
+ better. Also see 'lispwords'.
+ The '-' character is included in keyword characters. Redefines the
+ "=" operator to use this same indentation algorithm rather than
+ calling an external program if 'equalprg' is empty.
+ ]=],
+ full_name = 'lisp',
+ scope = { 'buffer' },
+ short_desc = N_('indenting for Lisp'),
+ type = 'bool',
+ varname = 'p_lisp',
+ },
+ {
+ abbreviation = 'lop',
+ cb = 'did_set_lispoptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of items that influence the Lisp indenting when
+ enabled with the |'lisp'| option. Currently only one item is
+ supported:
+ expr:1 use 'indentexpr' for Lisp indenting when it is set
+ expr:0 do not use 'indentexpr' for Lisp indenting (default)
+ Note that when using 'indentexpr' the `=` operator indents all the
+ lines, otherwise the first line is not indented (Vi-compatible).
+ ]=],
+ expand_cb = 'expand_set_lispoptions',
+ full_name = 'lispoptions',
+ list = 'onecomma',
+ pv_name = 'p_lop',
+ scope = { 'buffer' },
+ short_desc = N_('options for lisp indenting'),
+ type = 'string',
+ varname = 'p_lop',
+ },
+ {
+ abbreviation = 'lw',
+ defaults = {
+ if_true = macros('LISPWORD_VALUE'),
+ doc = 'is very long',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of words that influence the Lisp indenting when
+ enabled with the |'lisp'| option.
+ ]=],
+ full_name = 'lispwords',
+ list = 'onecomma',
+ pv_name = 'p_lw',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('words that change how lisp indenting works'),
+ type = 'string',
+ varname = 'p_lispwords',
+ },
+ {
+ defaults = { if_true = false },
+ desc = [=[
+ List mode: By default, show tabs as ">", trailing spaces as "-", and
+ non-breakable space characters as "+". Useful to see the difference
+ between tabs and spaces and for trailing blanks. Further changed by
+ the 'listchars' option.
+
+ The cursor is displayed at the start of the space a Tab character
+ occupies, not at the end as usual in Normal mode. To get this cursor
+ position while displaying Tabs with spaces, use: >
+ :set list lcs=tab:\ \
+ <
+ Note that list mode will also affect formatting (set with 'textwidth'
+ or 'wrapmargin') when 'cpoptions' includes 'L'. See 'listchars' for
+ changing the way tabs are displayed.
+ ]=],
+ full_name = 'list',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('<Tab> and <EOL>'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'lcs',
+ alloced = true,
+ cb = 'did_set_chars_option',
+ defaults = { if_true = 'tab:> ,trail:-,nbsp:+' },
+ deny_duplicates = true,
+ desc = [=[
+ Strings to use in 'list' mode and for the |:list| command. It is a
+ comma-separated list of string settings.
+
+ *lcs-eol*
+ eol:c Character to show at the end of each line. When
+ omitted, there is no extra character at the end of the
+ line.
+ *lcs-tab*
+ tab:xy[z] Two or three characters to be used to show a tab.
+ The third character is optional.
+
+ tab:xy The 'x' is always used, then 'y' as many times as will
+ fit. Thus "tab:>-" displays: >
+ >
+ >-
+ >--
+ etc.
+ <
+ tab:xyz The 'z' is always used, then 'x' is prepended, and
+ then 'y' is used as many times as will fit. Thus
+ "tab:<->" displays: >
+ >
+ <>
+ <->
+ <-->
+ etc.
+ <
+ When "tab:" is omitted, a tab is shown as ^I.
+ *lcs-space*
+ space:c Character to show for a space. When omitted, spaces
+ are left blank.
+ *lcs-multispace*
+ multispace:c...
+ One or more characters to use cyclically to show for
+ multiple consecutive spaces. Overrides the "space"
+ setting, except for single spaces. When omitted, the
+ "space" setting is used. For example,
+ `:set listchars=multispace:---+` shows ten consecutive
+ spaces as: >
+ ---+---+--
+ <
+ *lcs-lead*
+ lead:c Character to show for leading spaces. When omitted,
+ leading spaces are blank. Overrides the "space" and
+ "multispace" settings for leading spaces. You can
+ combine it with "tab:", for example: >
+ :set listchars+=tab:>-,lead:.
+ <
+ *lcs-leadmultispace*
+ leadmultispace:c...
+ Like the |lcs-multispace| value, but for leading
+ spaces only. Also overrides |lcs-lead| for leading
+ multiple spaces.
+ `:set listchars=leadmultispace:---+` shows ten
+ consecutive leading spaces as: >
+ ---+---+--XXX
+ <
+ Where "XXX" denotes the first non-blank characters in
+ the line.
+ *lcs-trail*
+ trail:c Character to show for trailing spaces. When omitted,
+ trailing spaces are blank. Overrides the "space" and
+ "multispace" settings for trailing spaces.
+ *lcs-extends*
+ extends:c Character to show in the last column, when 'wrap' is
+ off and the line continues beyond the right of the
+ screen.
+ *lcs-precedes*
+ precedes:c Character to show in the first visible column of the
+ physical line, when there is text preceding the
+ character visible in the first column.
+ *lcs-conceal*
+ conceal:c Character to show in place of concealed text, when
+ 'conceallevel' is set to 1. A space when omitted.
+ *lcs-nbsp*
+ nbsp:c Character to show for a non-breakable space character
+ (0xA0 (160 decimal) and U+202F). Left blank when
+ omitted.
+
+ The characters ':' and ',' should not be used. UTF-8 characters can
+ be used. All characters must be single width.
+
+ Each character can be specified as hex: >
+ set listchars=eol:\\x24
+ set listchars=eol:\\u21b5
+ set listchars=eol:\\U000021b5
+ < Note that a double backslash is used. The number of hex characters
+ must be exactly 2 for \\x, 4 for \\u and 8 for \\U.
+
+ Examples: >
+ :set lcs=tab:>-,trail:-
+ :set lcs=tab:>-,eol:<,nbsp:%
+ :set lcs=extends:>,precedes:<
+ < |hl-NonText| highlighting will be used for "eol", "extends" and
+ "precedes". |hl-Whitespace| for "nbsp", "space", "tab", "multispace",
+ "lead" and "trail".
+ ]=],
+ expand_cb = 'expand_set_chars_option',
+ full_name = 'listchars',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'global', 'window' },
+ short_desc = N_('characters for displaying in list mode'),
+ type = 'string',
+ varname = 'p_lcs',
+ },
+ {
+ abbreviation = 'lpl',
+ defaults = { if_true = true },
+ desc = [=[
+ When on the plugin scripts are loaded when starting up |load-plugins|.
+ This option can be reset in your |vimrc| file to disable the loading
+ of plugins.
+ Note that using the "-u NONE" and "--noplugin" command line arguments
+ reset this option. |-u| |--noplugin|
+ ]=],
+ full_name = 'loadplugins',
+ scope = { 'global' },
+ short_desc = N_('load plugin scripts when starting up'),
+ type = 'bool',
+ varname = 'p_lpl',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ Changes the special characters that can be used in search patterns.
+ See |pattern|.
+ WARNING: Switching this option off most likely breaks plugins! That
+ is because many patterns assume it's on and will fail when it's off.
+ Only switch it off when working with old Vi scripts. In any other
+ situation write patterns that work when 'magic' is on. Include "\M"
+ when you want to |/\M|.
+ ]=],
+ full_name = 'magic',
+ scope = { 'global' },
+ short_desc = N_('special characters in search patterns'),
+ type = 'bool',
+ varname = 'p_magic',
+ },
+ {
+ abbreviation = 'mef',
+ defaults = { if_true = '' },
+ desc = [=[
+ Name of the errorfile for the |:make| command (see |:make_makeprg|)
+ and the |:grep| command.
+ When it is empty, an internally generated temp file will be used.
+ When "##" is included, it is replaced by a number to make the name
+ unique. This makes sure that the ":make" command doesn't overwrite an
+ existing file.
+ NOT used for the ":cf" command. See 'errorfile' for that.
+ Environment variables are expanded |:set_env|.
+ See |option-backslash| about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'makeef',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('name of the errorfile for ":make"'),
+ type = 'string',
+ varname = 'p_mef',
+ },
+ {
+ abbreviation = 'menc',
+ cb = 'did_set_encoding',
+ defaults = { if_true = '' },
+ desc = [=[
+ Encoding used for reading the output of external commands. When empty,
+ encoding is not converted.
+ This is used for `:make`, `:lmake`, `:grep`, `:lgrep`, `:grepadd`,
+ `:lgrepadd`, `:cfile`, `:cgetfile`, `:caddfile`, `:lfile`, `:lgetfile`,
+ and `:laddfile`.
+
+ This would be mostly useful when you use MS-Windows. If iconv is
+ enabled, setting 'makeencoding' to "char" has the same effect as
+ setting to the system locale encoding. Example: >
+ :set makeencoding=char " system locale is used
+ <
+ ]=],
+ expand_cb = 'expand_set_encoding',
+ full_name = 'makeencoding',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('Converts the output of external commands'),
+ type = 'string',
+ varname = 'p_menc',
+ },
+ {
+ abbreviation = 'mp',
+ defaults = { if_true = 'make' },
+ desc = [=[
+ Program to use for the ":make" command. See |:make_makeprg|.
+ This option may contain '%' and '#' characters (see |:_%| and |:_#|),
+ which are expanded to the current and alternate file name. Use |::S|
+ to escape file names in case they contain special characters.
+ Environment variables are expanded |:set_env|. See |option-backslash|
+ about including spaces and backslashes.
+ Note that a '|' must be escaped twice: once for ":set" and once for
+ the interpretation of a command. When you use a filter called
+ "myfilter" do it like this: >
+ :set makeprg=gmake\ \\\|\ myfilter
+ < The placeholder "$*" can be given (even multiple times) to specify
+ where the arguments will be included, for example: >
+ :set makeprg=latex\ \\\\nonstopmode\ \\\\input\\{$*}
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'makeprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('program to use for the ":make" command'),
+ type = 'string',
+ varname = 'p_mp',
+ },
+ {
+ abbreviation = 'mps',
+ alloced = true,
+ cb = 'did_set_matchpairs',
+ defaults = { if_true = '(:),{:},[:]' },
+ deny_duplicates = true,
+ desc = [=[
+ Characters that form pairs. The |%| command jumps from one to the
+ other.
+ Only character pairs are allowed that are different, thus you cannot
+ jump between two double quotes.
+ The characters must be separated by a colon.
+ The pairs must be separated by a comma. Example for including '<' and
+ '>' (for HTML): >
+ :set mps+=<:>
+
+ < A more exotic example, to jump between the '=' and ';' in an
+ assignment, useful for languages like C and Java: >
+ :au FileType c,cpp,java set mps+==:;
+
+ < For a more advanced way of using "%", see the matchit.vim plugin in
+ the $VIMRUNTIME/plugin directory. |add-local-help|
+ ]=],
+ full_name = 'matchpairs',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('pairs of characters that "%" can match'),
+ type = 'string',
+ varname = 'p_mps',
+ },
+ {
+ abbreviation = 'mat',
+ defaults = { if_true = 5 },
+ desc = [=[
+ Tenths of a second to show the matching paren, when 'showmatch' is
+ set. Note that this is not in milliseconds, like other options that
+ set a time. This is to be compatible with Nvi.
+ ]=],
+ full_name = 'matchtime',
+ scope = { 'global' },
+ short_desc = N_('tenths of a second to show matching paren'),
+ type = 'number',
+ varname = 'p_mat',
+ },
+ {
+ abbreviation = 'mco',
+ defaults = { if_true = 6 },
+ full_name = 'maxcombine',
+ scope = { 'global' },
+ short_desc = N_('maximum nr of combining characters displayed'),
+ type = 'number',
+ varname = 'p_mco',
+ },
+ {
+ abbreviation = 'mfd',
+ defaults = { if_true = 100 },
+ desc = [=[
+ Maximum depth of function calls for user functions. This normally
+ catches endless recursion. When using a recursive function with
+ more depth, set 'maxfuncdepth' to a bigger number. But this will use
+ more memory, there is the danger of failing when memory is exhausted.
+ Increasing this limit above 200 also changes the maximum for Ex
+ command recursion, see |E169|.
+ See also |:function|.
+ ]=],
+ full_name = 'maxfuncdepth',
+ scope = { 'global' },
+ short_desc = N_('maximum recursive depth for user functions'),
+ type = 'number',
+ varname = 'p_mfd',
+ },
+ {
+ abbreviation = 'mmd',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Maximum number of times a mapping is done without resulting in a
+ character to be used. This normally catches endless mappings, like
+ ":map x y" with ":map y x". It still does not catch ":map g wg",
+ because the 'w' is used before the next mapping is done. See also
+ |key-mapping|.
+ ]=],
+ full_name = 'maxmapdepth',
+ scope = { 'global' },
+ short_desc = N_('maximum recursive depth for mapping'),
+ tags = { 'E223' },
+ type = 'number',
+ varname = 'p_mmd',
+ },
+ {
+ abbreviation = 'mmp',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Maximum amount of memory (in Kbyte) to use for pattern matching.
+ The maximum value is about 2000000. Use this to work without a limit.
+ *E363*
+ When Vim runs into the limit it gives an error message and mostly
+ behaves like CTRL-C was typed.
+ Running into the limit often means that the pattern is very
+ inefficient or too complex. This may already happen with the pattern
+ "\(.\)*" on a very long line. ".*" works much better.
+ Might also happen on redraw, when syntax rules try to match a complex
+ text structure.
+ Vim may run out of memory before hitting the 'maxmempattern' limit, in
+ which case you get an "Out of memory" error instead.
+ ]=],
+ full_name = 'maxmempattern',
+ scope = { 'global' },
+ short_desc = N_('maximum memory (in Kbyte) used for pattern search'),
+ type = 'number',
+ varname = 'p_mmp',
+ },
+ {
+ abbreviation = 'mis',
+ defaults = { if_true = 25 },
+ desc = [=[
+ Maximum number of items to use in a menu. Used for menus that are
+ generated from a list of items, e.g., the Buffers menu. Changing this
+ option has no direct effect, the menu must be refreshed first.
+ ]=],
+ full_name = 'menuitems',
+ scope = { 'global' },
+ short_desc = N_('maximum number of items in a menu'),
+ type = 'number',
+ varname = 'p_mis',
+ },
+ {
+ abbreviation = 'msm',
+ cb = 'did_set_mkspellmem',
+ defaults = { if_true = '460000,2000,500' },
+ desc = [=[
+ Parameters for |:mkspell|. This tunes when to start compressing the
+ word tree. Compression can be slow when there are many words, but
+ it's needed to avoid running out of memory. The amount of memory used
+ per word depends very much on how similar the words are, that's why
+ this tuning is complicated.
+
+ There are three numbers, separated by commas: >
+ {start},{inc},{added}
+ <
+ For most languages the uncompressed word tree fits in memory. {start}
+ gives the amount of memory in Kbyte that can be used before any
+ compression is done. It should be a bit smaller than the amount of
+ memory that is available to Vim.
+
+ When going over the {start} limit the {inc} number specifies the
+ amount of memory in Kbyte that can be allocated before another
+ compression is done. A low number means compression is done after
+ less words are added, which is slow. A high number means more memory
+ will be allocated.
+
+ After doing compression, {added} times 1024 words can be added before
+ the {inc} limit is ignored and compression is done when any extra
+ amount of memory is needed. A low number means there is a smaller
+ chance of hitting the {inc} limit, less memory is used but it's
+ slower.
+
+ The languages for which these numbers are important are Italian and
+ Hungarian. The default works for when you have about 512 Mbyte. If
+ you have 1 Gbyte you could use: >
+ :set mkspellmem=900000,3000,800
+ < If you have less than 512 Mbyte |:mkspell| may fail for some
+ languages, no matter what you set 'mkspellmem' to.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'mkspellmem',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('memory used before |:mkspell| compresses the tree'),
+ type = 'string',
+ varname = 'p_msm',
+ },
+ {
+ abbreviation = 'ml',
+ defaults = {
+ if_true = true,
+ doc = 'on (off for root)',
+ },
+ desc = [=[
+ If 'modeline' is on 'modelines' gives the number of lines that is
+ checked for set commands. If 'modeline' is off or 'modelines' is zero
+ no lines are checked. See |modeline|.
+ ]=],
+ full_name = 'modeline',
+ scope = { 'buffer' },
+ short_desc = N_('recognize modelines at start or end of file'),
+ type = 'bool',
+ varname = 'p_ml',
+ },
+ {
+ abbreviation = 'mle',
+ defaults = { if_true = false },
+ desc = [=[
+ When on allow some options that are an expression to be set in the
+ modeline. Check the option for whether it is affected by
+ 'modelineexpr'. Also see |modeline|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'modelineexpr',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('allow some options to be set in modeline'),
+ type = 'bool',
+ varname = 'p_mle',
+ },
+ {
+ abbreviation = 'mls',
+ defaults = { if_true = 5 },
+ desc = [=[
+ If 'modeline' is on 'modelines' gives the number of lines that is
+ checked for set commands. If 'modeline' is off or 'modelines' is zero
+ no lines are checked. See |modeline|.
+
+ ]=],
+ full_name = 'modelines',
+ scope = { 'global' },
+ short_desc = N_('number of lines checked for modelines'),
+ type = 'number',
+ varname = 'p_mls',
+ },
+ {
+ abbreviation = 'ma',
+ cb = 'did_set_modifiable',
+ defaults = { if_true = true },
+ desc = [=[
+ When off the buffer contents cannot be changed. The 'fileformat' and
+ 'fileencoding' options also can't be changed.
+ Can be reset on startup with the |-M| command line argument.
+ ]=],
+ full_name = 'modifiable',
+ noglob = true,
+ scope = { 'buffer' },
+ short_desc = N_('changes to the text are not possible'),
+ tags = { 'E21' },
+ type = 'bool',
+ varname = 'p_ma',
+ },
+ {
+ abbreviation = 'mod',
+ cb = 'did_set_modified',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, the buffer is considered to be modified. This option is set
+ when:
+ 1. A change was made to the text since it was last written. Using the
+ |undo| command to go back to the original text will reset the
+ option. But undoing changes that were made before writing the
+ buffer will set the option again, since the text is different from
+ when it was written.
+ 2. 'fileformat' or 'fileencoding' is different from its original
+ value. The original value is set when the buffer is read or
+ written. A ":set nomodified" command also resets the original
+ values to the current values and the 'modified' option will be
+ reset.
+ Similarly for 'eol' and 'bomb'.
+ This option is not set when a change is made to the buffer as the
+ result of a BufNewFile, BufRead/BufReadPost, BufWritePost,
+ FileAppendPost or VimLeave autocommand event. See |gzip-example| for
+ an explanation.
+ When 'buftype' is "nowrite" or "nofile" this option may be set, but
+ will be ignored.
+ Note that the text may actually be the same, e.g. 'modified' is set
+ when using "rA" on an "A".
+ ]=],
+ full_name = 'modified',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('buffer has been modified'),
+ type = 'bool',
+ varname = 'p_mod',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ When on, listings pause when the whole screen is filled. You will get
+ the |more-prompt|. When this option is off there are no pauses, the
+ listing continues until finished.
+ ]=],
+ full_name = 'more',
+ scope = { 'global' },
+ short_desc = N_('listings when the whole screen is filled'),
+ type = 'bool',
+ varname = 'p_more',
+ },
+ {
+ cb = 'did_set_mouse',
+ defaults = { if_true = 'nvi' },
+ desc = [=[
+ Enables mouse support. For example, to enable the mouse in Normal mode
+ and Visual mode: >
+ :set mouse=nv
+ <
+ To temporarily disable mouse support, hold the shift key while using
+ the mouse.
+
+ Mouse support can be enabled for different modes:
+ n Normal mode
+ v Visual mode
+ i Insert mode
+ c Command-line mode
+ h all previous modes when editing a help file
+ a all previous modes
+ r for |hit-enter| and |more-prompt| prompt
+
+ Left-click anywhere in a text buffer to place the cursor there. This
+ works with operators too, e.g. type |d| then left-click to delete text
+ from the current cursor position to the position where you clicked.
+
+ Drag the |status-line| or vertical separator of a window to resize it.
+
+ If enabled for "v" (Visual mode) then double-click selects word-wise,
+ triple-click makes it line-wise, and quadruple-click makes it
+ rectangular block-wise.
+
+ For scrolling with a mouse wheel see |scroll-mouse-wheel|.
+
+ Note: When enabling the mouse in a terminal, copy/paste will use the
+ "* register if possible. See also 'clipboard'.
+
+ Related options:
+ 'mousefocus' window focus follows mouse pointer
+ 'mousemodel' what mouse button does which action
+ 'mousehide' hide mouse pointer while typing text
+ 'selectmode' whether to start Select mode or Visual mode
+ ]=],
+ expand_cb = 'expand_set_mouse',
+ full_name = 'mouse',
+ list = 'flags',
+ scope = { 'global' },
+ short_desc = N_('the use of mouse clicks'),
+ type = 'string',
+ varname = 'p_mouse',
+ },
+ {
+ abbreviation = 'mousef',
+ defaults = { if_true = false },
+ desc = [=[
+ The window that the mouse pointer is on is automatically activated.
+ When changing the window layout or window focus in another way, the
+ mouse pointer is moved to the window with keyboard focus. Off is the
+ default because it makes using the pull down menus a little goofy, as
+ a pointer transit may activate a window unintentionally.
+ ]=],
+ full_name = 'mousefocus',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('keyboard focus follows the mouse'),
+ type = 'bool',
+ varname = 'p_mousef',
+ },
+ {
+ abbreviation = 'mh',
+ defaults = { if_true = true },
+ desc = [=[
+ only in the GUI
+ When on, the mouse pointer is hidden when characters are typed.
+ The mouse pointer is restored when the mouse is moved.
+ ]=],
+ enable_if = false,
+ full_name = 'mousehide',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('hide mouse pointer while typing'),
+ type = 'bool',
+ varname = 'p_mh',
+ },
+ {
+ abbreviation = 'mousem',
+ cb = 'did_set_mousemodel',
+ defaults = { if_true = 'popup_setpos' },
+ desc = [=[
+ Sets the model to use for the mouse. The name mostly specifies what
+ the right mouse button is used for:
+ extend Right mouse button extends a selection. This works
+ like in an xterm.
+ popup Right mouse button pops up a menu. The shifted left
+ mouse button extends a selection. This works like
+ with Microsoft Windows.
+ popup_setpos Like "popup", but the cursor will be moved to the
+ position where the mouse was clicked, and thus the
+ selected operation will act upon the clicked object.
+ If clicking inside a selection, that selection will
+ be acted upon, i.e. no cursor move. This implies of
+ course, that right clicking outside a selection will
+ end Visual mode.
+ Overview of what button does what for each model:
+ mouse extend popup(_setpos) ~
+ left click place cursor place cursor
+ left drag start selection start selection
+ shift-left search word extend selection
+ right click extend selection popup menu (place cursor)
+ right drag extend selection -
+ middle click paste paste
+
+ In the "popup" model the right mouse button produces a pop-up menu.
+ Nvim creates a default |popup-menu| but you can redefine it.
+
+ Note that you can further refine the meaning of buttons with mappings.
+ See |mouse-overview|. But mappings are NOT used for modeless selection.
+
+ Example: >
+ :map <S-LeftMouse> <RightMouse>
+ :map <S-LeftDrag> <RightDrag>
+ :map <S-LeftRelease> <RightRelease>
+ :map <2-S-LeftMouse> <2-RightMouse>
+ :map <2-S-LeftDrag> <2-RightDrag>
+ :map <2-S-LeftRelease> <2-RightRelease>
+ :map <3-S-LeftMouse> <3-RightMouse>
+ :map <3-S-LeftDrag> <3-RightDrag>
+ :map <3-S-LeftRelease> <3-RightRelease>
+ :map <4-S-LeftMouse> <4-RightMouse>
+ :map <4-S-LeftDrag> <4-RightDrag>
+ :map <4-S-LeftRelease> <4-RightRelease>
+ <
+ Mouse commands requiring the CTRL modifier can be simulated by typing
+ the "g" key before using the mouse:
+ "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click)
+ "g<RightMouse>" is "<C-RightMouse> ("CTRL-T")
+ ]=],
+ expand_cb = 'expand_set_mousemodel',
+ full_name = 'mousemodel',
+ scope = { 'global' },
+ short_desc = N_('changes meaning of mouse buttons'),
+ type = 'string',
+ varname = 'p_mousem',
+ },
+ {
+ abbreviation = 'mousemev',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, mouse move events are delivered to the input queue and are
+ available for mapping. The default, off, avoids the mouse movement
+ overhead except when needed.
+ Warning: Setting this option can make pending mappings to be aborted
+ when the mouse is moved.
+ ]=],
+ full_name = 'mousemoveevent',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('deliver mouse move events to input queue'),
+ type = 'bool',
+ varname = 'p_mousemev',
+ },
+ {
+ cb = 'did_set_mousescroll',
+ defaults = { if_true = 'ver:3,hor:6' },
+ desc = [=[
+ This option controls the number of lines / columns to scroll by when
+ scrolling with a mouse wheel (|scroll-mouse-wheel|). The option is
+ a comma-separated list. Each part consists of a direction and a count
+ as follows:
+ direction:count,direction:count
+ Direction is one of either "hor" or "ver". "hor" controls horizontal
+ scrolling and "ver" controls vertical scrolling. Count sets the amount
+ to scroll by for the given direction, it should be a non negative
+ integer. Each direction should be set at most once. If a direction
+ is omitted, a default value is used (6 for horizontal scrolling and 3
+ for vertical scrolling). You can disable mouse scrolling by using
+ a count of 0.
+
+ Example: >
+ :set mousescroll=ver:5,hor:2
+ < Will make Nvim scroll 5 lines at a time when scrolling vertically, and
+ scroll 2 columns at a time when scrolling horizontally.
+ ]=],
+ expand_cb = 'expand_set_mousescroll',
+ full_name = 'mousescroll',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('amount to scroll by when scrolling with a mouse'),
+ tags = { 'E5080' },
+ type = 'string',
+ varname = 'p_mousescroll',
+ vi_def = true,
+ },
+ {
+ abbreviation = 'mouses',
+ deny_duplicates = true,
+ defaults = {
+ if_true = '',
+ doc = [["i:beam,r:beam,s:updown,sd:cross,
+ m:no,ml:up-arrow,v:rightup-arrow"]],
+ },
+ desc = [=[
+ This option tells Vim what the mouse pointer should look like in
+ different modes. The option is a comma-separated list of parts, much
+ like used for 'guicursor'. Each part consist of a mode/location-list
+ and an argument-list:
+ mode-list:shape,mode-list:shape,..
+ The mode-list is a dash separated list of these modes/locations:
+ In a normal window: ~
+ n Normal mode
+ v Visual mode
+ ve Visual mode with 'selection' "exclusive" (same as 'v',
+ if not specified)
+ o Operator-pending mode
+ i Insert mode
+ r Replace mode
+
+ Others: ~
+ c appending to the command-line
+ ci inserting in the command-line
+ cr replacing in the command-line
+ m at the 'Hit ENTER' or 'More' prompts
+ ml idem, but cursor in the last line
+ e any mode, pointer below last window
+ s any mode, pointer on a status line
+ sd any mode, while dragging a status line
+ vs any mode, pointer on a vertical separator line
+ vd any mode, while dragging a vertical separator line
+ a everywhere
+
+ The shape is one of the following:
+ avail name looks like ~
+ w x arrow Normal mouse pointer
+ w x blank no pointer at all (use with care!)
+ w x beam I-beam
+ w x updown up-down sizing arrows
+ w x leftright left-right sizing arrows
+ w x busy The system's usual busy pointer
+ w x no The system's usual "no input" pointer
+ x udsizing indicates up-down resizing
+ x lrsizing indicates left-right resizing
+ x crosshair like a big thin +
+ x hand1 black hand
+ x hand2 white hand
+ x pencil what you write with
+ x question big ?
+ x rightup-arrow arrow pointing right-up
+ w x up-arrow arrow pointing up
+ x <number> any X11 pointer number (see X11/cursorfont.h)
+
+ The "avail" column contains a 'w' if the shape is available for Win32,
+ x for X11.
+ Any modes not specified or shapes not available use the normal mouse
+ pointer.
+
+ Example: >
+ :set mouseshape=s:udsizing,m:no
+ < will make the mouse turn to a sizing arrow over the status lines and
+ indicate no input when the hit-enter prompt is displayed (since
+ clicking the mouse has no effect in this state.)
+ ]=],
+ enable_if = false,
+ full_name = 'mouseshape',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('shape of the mouse pointer in different modes'),
+ tags = { 'E547' },
+ type = 'string',
+ },
+ {
+ abbreviation = 'mouset',
+ defaults = { if_true = 500 },
+ desc = [=[
+ Defines the maximum time in msec between two mouse clicks for the
+ second click to be recognized as a multi click.
+ ]=],
+ full_name = 'mousetime',
+ scope = { 'global' },
+ short_desc = N_('max time between mouse double-click'),
+ type = 'number',
+ varname = 'p_mouset',
+ },
+ {
+ abbreviation = 'nf',
+ alloced = true,
+ cb = 'did_set_nrformats',
+ defaults = { if_true = 'bin,hex' },
+ deny_duplicates = true,
+ desc = [=[
+ This defines what bases Vim will consider for numbers when using the
+ CTRL-A and CTRL-X commands for adding to and subtracting from a number
+ respectively; see |CTRL-A| for more info on these commands.
+ alpha If included, single alphabetical characters will be
+ incremented or decremented. This is useful for a list with a
+ letter index a), b), etc. *octal-nrformats*
+ octal If included, numbers that start with a zero will be considered
+ to be octal. Example: Using CTRL-A on "007" results in "010".
+ hex If included, numbers starting with "0x" or "0X" will be
+ considered to be hexadecimal. Example: Using CTRL-X on
+ "0x100" results in "0x0ff".
+ bin If included, numbers starting with "0b" or "0B" will be
+ considered to be binary. Example: Using CTRL-X on
+ "0b1000" subtracts one, resulting in "0b0111".
+ unsigned If included, numbers are recognized as unsigned. Thus a
+ leading dash or negative sign won't be considered as part of
+ the number. Examples:
+ Using CTRL-X on "2020" in "9-2020" results in "9-2019"
+ (without "unsigned" it would become "9-2021").
+ Using CTRL-A on "2020" in "9-2020" results in "9-2021"
+ (without "unsigned" it would become "9-2019").
+ Using CTRL-X on "0" or CTRL-A on "18446744073709551615"
+ (2^64 - 1) has no effect, overflow is prevented.
+ Numbers which simply begin with a digit in the range 1-9 are always
+ considered decimal. This also happens for numbers that are not
+ recognized as octal or hex.
+ ]=],
+ expand_cb = 'expand_set_nrformats',
+ full_name = 'nrformats',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('number formats recognized for CTRL-A command'),
+ type = 'string',
+ varname = 'p_nf',
+ },
+ {
+ abbreviation = 'nu',
+ cb = 'did_set_number_relativenumber',
+ defaults = { if_true = false },
+ desc = [=[
+ Print the line number in front of each line. When the 'n' option is
+ excluded from 'cpoptions' a wrapped line will not use the column of
+ line numbers.
+ Use the 'numberwidth' option to adjust the room for the line number.
+ When a long, wrapped line doesn't start with the first character, '-'
+ characters are put before the number.
+ For highlighting see |hl-LineNr|, |hl-CursorLineNr|, and the
+ |:sign-define| "numhl" argument.
+ *number_relativenumber*
+ The 'relativenumber' option changes the displayed number to be
+ relative to the cursor. Together with 'number' there are these
+ four combinations (cursor in line 3):
+
+ 'nonu' 'nu' 'nonu' 'nu'
+ 'nornu' 'nornu' 'rnu' 'rnu'
+ >
+ |apple | 1 apple | 2 apple | 2 apple
+ |pear | 2 pear | 1 pear | 1 pear
+ |nobody | 3 nobody | 0 nobody |3 nobody
+ |there | 4 there | 1 there | 1 there
+ <
+ ]=],
+ full_name = 'number',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('print the line number in front of each line'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'nuw',
+ cb = 'did_set_numberwidth',
+ defaults = { if_true = 4 },
+ desc = [=[
+ Minimal number of columns to use for the line number. Only relevant
+ when the 'number' or 'relativenumber' option is set or printing lines
+ with a line number. Since one space is always between the number and
+ the text, there is one less character for the number itself.
+ The value is the minimum width. A bigger width is used when needed to
+ fit the highest line number in the buffer respectively the number of
+ rows in the window, depending on whether 'number' or 'relativenumber'
+ is set. Thus with the Vim default of 4 there is room for a line number
+ up to 999. When the buffer has 1000 lines five columns will be used.
+ The minimum value is 1, the maximum value is 20.
+ ]=],
+ full_name = 'numberwidth',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('number of columns used for the line number'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'ofu',
+ alloced = true,
+ cb = 'did_set_omnifunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used for Insert mode omni
+ completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O|
+ See |complete-functions| for an explanation of how the function is
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
+ This option is usually set by a filetype plugin:
+ |:filetype-plugin-on|
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'omnifunc',
+ func = true,
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('function for filetype-specific completion'),
+ type = 'string',
+ varname = 'p_ofu',
+ },
+ {
+ abbreviation = 'odev',
+ defaults = { if_true = false },
+ desc = [=[
+ only for Windows
+ Enable reading and writing from devices. This may get Vim stuck on a
+ device that can be opened but doesn't actually do the I/O. Therefore
+ it is off by default.
+ Note that on Windows editing "aux.h", "lpt1.txt" and the like also
+ result in editing a device.
+ ]=],
+ enable_if = false,
+ full_name = 'opendevice',
+ scope = { 'global' },
+ short_desc = N_('allow reading/writing devices on MS-Windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'opfunc',
+ cb = 'did_set_operatorfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be called by the |g@| operator.
+ See |:map-operator| for more info and an example. The value can be
+ the name of a function, a |lambda| or a |Funcref|. See
+ |option-value-function| for more information.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'operatorfunc',
+ func = true,
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('function to be called for |g@| operator'),
+ type = 'string',
+ varname = 'p_opfunc',
+ },
+ {
+ abbreviation = 'pp',
+ cb = 'did_set_runtimepackpath',
+ defaults = {
+ if_true = '',
+ doc = "see 'runtimepath'",
+ meta = '...',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Directories used to find packages.
+ See |packages| and |packages-runtimepath|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'packpath',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories used for packages'),
+ type = 'string',
+ varname = 'p_pp',
+ },
+ {
+ abbreviation = 'para',
+ defaults = { if_true = 'IPLPPPQPP TPHPLIPpLpItpplpipbp' },
+ desc = [=[
+ Specifies the nroff macros that separate paragraphs. These are pairs
+ of two letters (see |object-motions|).
+ ]=],
+ full_name = 'paragraphs',
+ scope = { 'global' },
+ short_desc = N_('nroff macros that separate paragraphs'),
+ type = 'string',
+ varname = 'p_para',
+ },
+ {
+ cb = 'did_set_paste',
+ defaults = { if_true = false },
+ full_name = 'paste',
+ pri_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('pasting text'),
+ type = 'bool',
+ varname = 'p_paste',
+ },
+ {
+ abbreviation = 'pt',
+ defaults = { if_true = '' },
+ full_name = 'pastetoggle',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'pex',
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to apply a patch to a file and generate
+ the resulting new version of the file. See |diff-patchexpr|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'patchexpr',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('expression used to patch a file'),
+ type = 'string',
+ varname = 'p_pex',
+ },
+ {
+ abbreviation = 'pm',
+ cb = 'did_set_backupext_or_patchmode',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty the oldest version of a file is kept. This can be used
+ to keep the original version of a file if you are changing files in a
+ source distribution. Only the first time that a file is written a
+ copy of the original file will be kept. The name of the copy is the
+ name of the original file with the string in the 'patchmode' option
+ appended. This option should start with a dot. Use a string like
+ ".orig" or ".org". 'backupdir' must not be empty for this to work
+ (Detail: The backup file is renamed to the patchmode file after the
+ new file has been successfully written, that's why it must be possible
+ to write a backup file). If there was no file to be backed up, an
+ empty file is created.
+ When the 'backupskip' pattern matches, a patchmode file is not made.
+ Using 'patchmode' for compressed files appends the extension at the
+ end (e.g., "file.gz.orig"), thus the resulting name isn't always
+ recognized as a compressed file.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'patchmode',
+ normal_fname_chars = true,
+ scope = { 'global' },
+ short_desc = N_('keep the oldest version of a file'),
+ tags = { 'E205', 'E206' },
+ type = 'string',
+ varname = 'p_pm',
+ },
+ {
+ abbreviation = 'pa',
+ defaults = { if_true = '.,,' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a list of directories which will be searched when using the
+ |gf|, [f, ]f, ^Wf, |:find|, |:sfind|, |:tabfind| and other commands,
+ provided that the file being searched for has a relative path (not
+ starting with "/", "./" or "../"). The directories in the 'path'
+ option may be relative or absolute.
+ - Use commas to separate directory names: >
+ :set path=.,/usr/local/include,/usr/include
+ < - Spaces can also be used to separate directory names. To have a
+ space in a directory name, precede it with an extra backslash, and
+ escape the space: >
+ :set path=.,/dir/with\\\ space
+ < - To include a comma in a directory name precede it with an extra
+ backslash: >
+ :set path=.,/dir/with\\,comma
+ < - To search relative to the directory of the current file, use: >
+ :set path=.
+ < - To search in the current directory use an empty string between two
+ commas: >
+ :set path=,,
+ < - A directory name may end in a ':' or '/'.
+ - Environment variables are expanded |:set_env|.
+ - When using |netrw.vim| URLs can be used. For example, adding
+ "https://www.vim.org" will make ":find index.html" work.
+ - Search upwards and downwards in a directory tree using "*", "**" and
+ ";". See |file-searching| for info and syntax.
+ - Careful with '\' characters, type two to get one in the option: >
+ :set path=.,c:\\include
+ < Or just use '/' instead: >
+ :set path=.,c:/include
+ < Don't forget "." or files won't even be found in the same directory as
+ the file!
+ The maximum length is limited. How much depends on the system, mostly
+ it is something like 256 or 1024 characters.
+ You can check if all the include files are found, using the value of
+ 'path', see |:checkpath|.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ directories from the list. This avoids problems when a future version
+ uses another default. To remove the current directory use: >
+ :set path-=
+ < To add the current directory use: >
+ :set path+=
+ < To use an environment variable, you probably need to replace the
+ separator. Here is an example to append $INCL, in which directory
+ names are separated with a semi-colon: >
+ :let &path = &path .. "," .. substitute($INCL, ';', ',', 'g')
+ < Replace the ';' with a ':' or whatever separator is used. Note that
+ this doesn't work when $INCL contains a comma or white space.
+ ]=],
+ expand = true,
+ full_name = 'path',
+ list = 'comma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of directories searched with "gf" et.al.'),
+ tags = { 'E343', 'E345', 'E347', 'E854' },
+ type = 'string',
+ varname = 'p_path',
+ },
+ {
+ abbreviation = 'pi',
+ defaults = { if_true = false },
+ desc = [=[
+ When changing the indent of the current line, preserve as much of the
+ indent structure as possible. Normally the indent is replaced by a
+ series of tabs followed by spaces as required (unless |'expandtab'| is
+ enabled, in which case only spaces are used). Enabling this option
+ means the indent will preserve as many existing characters as possible
+ for indenting, and only add additional tabs or spaces as required.
+ 'expandtab' does not apply to the preserved white space, a Tab remains
+ a Tab.
+ NOTE: When using ">>" multiple times the resulting indent is a mix of
+ tabs and spaces. You might not like this.
+ Also see 'copyindent'.
+ Use |:retab| to clean up white space.
+ ]=],
+ full_name = 'preserveindent',
+ scope = { 'buffer' },
+ short_desc = N_('preserve the indent structure when reindenting'),
+ type = 'bool',
+ varname = 'p_pi',
+ },
+ {
+ abbreviation = 'pvh',
+ defaults = { if_true = 12 },
+ desc = [=[
+ Default height for a preview window. Used for |:ptag| and associated
+ commands. Used for |CTRL-W_}| when no count is given.
+ ]=],
+ full_name = 'previewheight',
+ scope = { 'global' },
+ short_desc = N_('height of the preview window'),
+ type = 'number',
+ varname = 'p_pvh',
+ },
+ {
+ abbreviation = 'pvw',
+ cb = 'did_set_previewwindow',
+ defaults = { if_true = false },
+ desc = [=[
+ Identifies the preview window. Only one window can have this option
+ set. It's normally not set directly, but by using one of the commands
+ |:ptag|, |:pedit|, etc.
+ ]=],
+ full_name = 'previewwindow',
+ noglob = true,
+ redraw = { 'statuslines' },
+ scope = { 'window' },
+ short_desc = N_('identifies the preview window'),
+ tags = { 'E590' },
+ type = 'bool',
+ },
+ {
+ defaults = { if_true = true },
+ full_name = 'prompt',
+ scope = { 'global' },
+ short_desc = N_('enable prompt in Ex mode'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'pb',
+ cb = 'did_set_pumblend',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Enables pseudo-transparency for the |popup-menu|. Valid values are in
+ the range of 0 for fully opaque popupmenu (disabled) to 100 for fully
+ transparent background. Values between 0-30 are typically most useful.
+
+ It is possible to override the level for individual highlights within
+ the popupmenu using |highlight-blend|. For instance, to enable
+ transparency but force the current selected element to be fully opaque: >
+
+ :set pumblend=15
+ :hi PmenuSel blend=0
+ <
+ UI-dependent. Works best with RGB colors. 'termguicolors'
+ ]=],
+ full_name = 'pumblend',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('Controls transparency level of popup menu'),
+ type = 'number',
+ varname = 'p_pb',
+ },
+ {
+ abbreviation = 'ph',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Maximum number of items to show in the popup menu
+ (|ins-completion-menu|). Zero means "use available screen space".
+ ]=],
+ full_name = 'pumheight',
+ scope = { 'global' },
+ short_desc = N_('maximum height of the popup menu'),
+ type = 'number',
+ varname = 'p_ph',
+ },
+ {
+ abbreviation = 'pw',
+ defaults = { if_true = 15 },
+ desc = [=[
+ Minimum width for the popup menu (|ins-completion-menu|). If the
+ cursor column + 'pumwidth' exceeds screen width, the popup menu is
+ nudged to fit on the screen.
+ ]=],
+ full_name = 'pumwidth',
+ scope = { 'global' },
+ short_desc = N_('minimum width of the popup menu'),
+ type = 'number',
+ varname = 'p_pw',
+ },
+ {
+ abbreviation = 'pyx',
+ defaults = { if_true = 3 },
+ desc = [=[
+ Specifies the python version used for pyx* functions and commands
+ |python_x|. As only Python 3 is supported, this always has the value
+ `3`. Setting any other value is an error.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'pyxversion',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('selects default python version to use'),
+ type = 'number',
+ varname = 'p_pyx',
+ },
+ {
+ abbreviation = 'qftf',
+ cb = 'did_set_quickfixtextfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used to get the text to display
+ in the quickfix and location list windows. This can be used to
+ customize the information displayed in the quickfix or location window
+ for each entry in the corresponding quickfix or location list. See
+ |quickfix-window-function| for an explanation of how to write the
+ function and an example. The value can be the name of a function, a
+ |lambda| or a |Funcref|. See |option-value-function| for more
+ information.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'quickfixtextfunc',
+ func = true,
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('customize the quickfix window'),
+ type = 'string',
+ varname = 'p_qftf',
+ },
+ {
+ abbreviation = 'qe',
+ alloced = true,
+ defaults = { if_true = '\\' },
+ desc = [=[
+ The characters that are used to escape quotes in a string. Used for
+ objects like a', a" and a` |a'|.
+ When one of the characters in this option is found inside a string,
+ the following character will be skipped. The default value makes the
+ text "foo\"bar\\" considered to be one string.
+ ]=],
+ full_name = 'quoteescape',
+ scope = { 'buffer' },
+ short_desc = N_('escape characters used in a string'),
+ type = 'string',
+ varname = 'p_qe',
+ },
+ {
+ abbreviation = 'ro',
+ cb = 'did_set_readonly',
+ defaults = { if_true = false },
+ desc = [=[
+ If on, writes fail unless you use a '!'. Protects you from
+ accidentally overwriting a file. Default on when Vim is started
+ in read-only mode ("vim -R") or when the executable is called "view".
+ When using ":w!" the 'readonly' option is reset for the current
+ buffer, unless the 'Z' flag is in 'cpoptions'.
+ When using the ":view" command the 'readonly' option is set for the
+ newly edited buffer.
+ See 'modifiable' for disallowing changes to the buffer.
+ ]=],
+ full_name = 'readonly',
+ noglob = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('disallow writing the buffer'),
+ type = 'bool',
+ varname = 'p_ro',
+ },
+ {
+ abbreviation = 'rdb',
+ cb = 'did_set_redrawdebug',
+ defaults = { if_true = '' },
+ desc = [=[
+ Flags to change the way redrawing works, for debugging purposes.
+ Most useful with 'writedelay' set to some reasonable value.
+ Supports the following flags:
+ compositor Indicate each redraw event handled by the compositor
+ by briefly flashing the redrawn regions in colors
+ indicating the redraw type. These are the highlight
+ groups used (and their default colors):
+ RedrawDebugNormal gui=reverse normal redraw passed through
+ RedrawDebugClear guibg=Yellow clear event passed through
+ RedrawDebugComposed guibg=Green redraw event modified by the
+ compositor (due to
+ overlapping grids, etc)
+ RedrawDebugRecompose guibg=Red redraw generated by the
+ compositor itself, due to a
+ grid being moved or deleted.
+ line introduce a delay after each line drawn on the screen.
+ When using the TUI or another single-grid UI, "compositor"
+ gives more information and should be preferred (every
+ line is processed as a separate event by the compositor)
+ flush introduce a delay after each "flush" event.
+ nothrottle Turn off throttling of the message grid. This is an
+ optimization that joins many small scrolls to one
+ larger scroll when drawing the message area (with
+ 'display' msgsep flag active).
+ invalid Enable stricter checking (abort) of inconsistencies
+ of the internal screen state. This is mostly
+ useful when running nvim inside a debugger (and
+ the test suite).
+ nodelta Send all internally redrawn cells to the UI, even if
+ they are unchanged from the already displayed state.
+ ]=],
+ full_name = 'redrawdebug',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('Changes the way redrawing works (debug)'),
+ type = 'string',
+ varname = 'p_rdb',
+ },
+ {
+ abbreviation = 'rdt',
+ defaults = { if_true = 2000 },
+ desc = [=[
+ Time in milliseconds for redrawing the display. Applies to
+ 'hlsearch', 'inccommand', |:match| highlighting and syntax
+ highlighting.
+ When redrawing takes more than this many milliseconds no further
+ matches will be highlighted.
+ For syntax highlighting the time applies per window. When over the
+ limit syntax highlighting is disabled until |CTRL-L| is used.
+ This is used to avoid that Vim hangs when using a very complicated
+ pattern.
+ ]=],
+ full_name = 'redrawtime',
+ scope = { 'global' },
+ short_desc = N_("timeout for 'hlsearch' and |:match| highlighting"),
+ type = 'number',
+ varname = 'p_rdt',
+ },
+ {
+ abbreviation = 're',
+ defaults = { if_true = 0 },
+ desc = [=[
+ This selects the default regexp engine. |two-engines|
+ The possible values are:
+ 0 automatic selection
+ 1 old engine
+ 2 NFA engine
+ Note that when using the NFA engine and the pattern contains something
+ that is not supported the pattern will not match. This is only useful
+ for debugging the regexp engine.
+ Using automatic selection enables Vim to switch the engine, if the
+ default engine becomes too costly. E.g., when the NFA engine uses too
+ many states. This should prevent Vim from hanging on a combination of
+ a complex pattern with long text.
+ ]=],
+ full_name = 'regexpengine',
+ scope = { 'global' },
+ short_desc = N_('default regexp engine to use'),
+ type = 'number',
+ varname = 'p_re',
+ },
+ {
+ abbreviation = 'rnu',
+ cb = 'did_set_number_relativenumber',
+ defaults = { if_true = false },
+ desc = [=[
+ Show the line number relative to the line with the cursor in front of
+ each line. Relative line numbers help you use the |count| you can
+ precede some vertical motion commands (e.g. j k + -) with, without
+ having to calculate it yourself. Especially useful in combination with
+ other commands (e.g. y d c < > gq gw =).
+ When the 'n' option is excluded from 'cpoptions' a wrapped
+ line will not use the column of line numbers.
+ The 'numberwidth' option can be used to set the room used for the line
+ number.
+ When a long, wrapped line doesn't start with the first character, '-'
+ characters are put before the number.
+ See |hl-LineNr| and |hl-CursorLineNr| for the highlighting used for
+ the number.
+
+ The number in front of the cursor line also depends on the value of
+ 'number', see |number_relativenumber| for all combinations of the two
+ options.
+ ]=],
+ full_name = 'relativenumber',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('show relative line number in front of each line'),
+ type = 'bool',
+ },
+ {
+ defaults = { if_true = true },
+ full_name = 'remap',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ defaults = { if_true = 2 },
+ desc = [=[
+ Threshold for reporting number of lines changed. When the number of
+ changed lines is more than 'report' a message will be given for most
+ ":" commands. If you want it always, set 'report' to 0.
+ For the ":substitute" command the number of substitutions is used
+ instead of the number of lines.
+ ]=],
+ full_name = 'report',
+ scope = { 'global' },
+ short_desc = N_('for reporting nr. of lines changed'),
+ type = 'number',
+ varname = 'p_report',
+ },
+ {
+ abbreviation = 'ri',
+ defaults = { if_true = false },
+ desc = [=[
+ Inserting characters in Insert mode will work backwards. See "typing
+ backwards" |ins-reverse|. This option can be toggled with the CTRL-_
+ command in Insert mode, when 'allowrevins' is set.
+ ]=],
+ full_name = 'revins',
+ scope = { 'global' },
+ short_desc = N_('inserting characters will work backwards'),
+ type = 'bool',
+ varname = 'p_ri',
+ },
+ {
+ abbreviation = 'rl',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, display orientation becomes right-to-left, i.e., characters
+ that are stored in the file appear from the right to the left.
+ Using this option, it is possible to edit files for languages that
+ are written from the right to the left such as Hebrew and Arabic.
+ This option is per window, so it is possible to edit mixed files
+ simultaneously, or to view the same file in both ways (this is
+ useful whenever you have a mixed text file with both right-to-left
+ and left-to-right strings so that both sets are displayed properly
+ in different windows). Also see |rileft.txt|.
+ ]=],
+ full_name = 'rightleft',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('window is right-to-left oriented'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'rlc',
+ alloced = true,
+ cb = 'did_set_rightleftcmd',
+ defaults = { if_true = 'search' },
+ desc = [=[
+ Each word in this option enables the command line editing to work in
+ right-to-left mode for a group of commands:
+
+ search "/" and "?" commands
+
+ This is useful for languages such as Hebrew, Arabic and Farsi.
+ The 'rightleft' option must be set for 'rightleftcmd' to take effect.
+ ]=],
+ expand_cb = 'expand_set_rightleftcmd',
+ full_name = 'rightleftcmd',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('commands for which editing works right-to-left'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'ru',
+ defaults = { if_true = true },
+ desc = [=[
+ Show the line and column number of the cursor position, separated by a
+ comma. When there is room, the relative position of the displayed
+ text in the file is shown on the far right:
+ Top first line is visible
+ Bot last line is visible
+ All first and last line are visible
+ 45% relative position in the file
+ If 'rulerformat' is set, it will determine the contents of the ruler.
+ Each window has its own ruler. If a window has a status line, the
+ ruler is shown there. If a window doesn't have a status line and
+ 'cmdheight' is zero, the ruler is not shown. Otherwise it is shown in
+ the last line of the screen. If the statusline is given by
+ 'statusline' (i.e. not empty), this option takes precedence over
+ 'ruler' and 'rulerformat'.
+ If the number of characters displayed is different from the number of
+ bytes in the text (e.g., for a TAB or a multibyte character), both
+ the text column (byte number) and the screen column are shown,
+ separated with a dash.
+ For an empty line "0-1" is shown.
+ For an empty buffer the line number will also be zero: "0,0-1".
+ If you don't want to see the ruler all the time but want to know where
+ you are, use "g CTRL-G" |g_CTRL-G|.
+ ]=],
+ full_name = 'ruler',
+ redraw = { 'statuslines' },
+ scope = { 'global' },
+ short_desc = N_('show cursor line and column in the status line'),
+ type = 'bool',
+ varname = 'p_ru',
+ },
+ {
+ abbreviation = 'ruf',
+ alloced = true,
+ cb = 'did_set_rulerformat',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is not empty, it determines the content of the ruler
+ string, as displayed for the 'ruler' option.
+ The format of this option is like that of 'statusline'.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ The default ruler width is 17 characters. To make the ruler 15
+ characters wide, put "%15(" at the start and "%)" at the end.
+ Example: >
+ :set rulerformat=%15(%c%V\ %p%%%)
+ <
+ ]=],
+ full_name = 'rulerformat',
+ modelineexpr = true,
+ redraw = { 'statuslines' },
+ scope = { 'global' },
+ short_desc = N_('custom format for the ruler'),
+ type = 'string',
+ varname = 'p_ruf',
+ },
+ {
+ abbreviation = 'rtp',
+ cb = 'did_set_runtimepackpath',
+ defaults = {
+ if_true = '',
+ doc = [["$XDG_CONFIG_HOME/nvim,
+ $XDG_CONFIG_DIRS[1]/nvim,
+ $XDG_CONFIG_DIRS[2]/nvim,
+ …
+ $XDG_DATA_HOME/nvim[-data]/site,
+ $XDG_DATA_DIRS[1]/nvim/site,
+ $XDG_DATA_DIRS[2]/nvim/site,
+ …
+ $VIMRUNTIME,
+ …
+ $XDG_DATA_DIRS[2]/nvim/site/after,
+ $XDG_DATA_DIRS[1]/nvim/site/after,
+ $XDG_DATA_HOME/nvim[-data]/site/after,
+ …
+ $XDG_CONFIG_DIRS[2]/nvim/after,
+ $XDG_CONFIG_DIRS[1]/nvim/after,
+ $XDG_CONFIG_HOME/nvim/after"]],
+ meta = '...',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ List of directories to be searched for these runtime files:
+ filetype.lua filetypes |new-filetype|
+ autoload/ automatically loaded scripts |autoload-functions|
+ colors/ color scheme files |:colorscheme|
+ compiler/ compiler files |:compiler|
+ doc/ documentation |write-local-help|
+ ftplugin/ filetype plugins |write-filetype-plugin|
+ indent/ indent scripts |indent-expression|
+ keymap/ key mapping files |mbyte-keymap|
+ lang/ menu translations |:menutrans|
+ lua/ |Lua| plugins
+ menu.vim GUI menus |menu.vim|
+ pack/ packages |:packadd|
+ parser/ |treesitter| syntax parsers
+ plugin/ plugin scripts |write-plugin|
+ queries/ |treesitter| queries
+ rplugin/ |remote-plugin| scripts
+ spell/ spell checking files |spell|
+ syntax/ syntax files |mysyntaxfile|
+ tutor/ tutorial files |:Tutor|
+
+ And any other file searched for with the |:runtime| command.
+
+ Defaults are setup to search these locations:
+ 1. Your home directory, for personal preferences.
+ Given by `stdpath("config")`. |$XDG_CONFIG_HOME|
+ 2. Directories which must contain configuration files according to
+ |xdg| ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains
+ preferences from system administrator.
+ 3. Data home directory, for plugins installed by user.
+ Given by `stdpath("data")/site`. |$XDG_DATA_HOME|
+ 4. nvim/site subdirectories for each directory in $XDG_DATA_DIRS.
+ This is for plugins which were installed by system administrator,
+ but are not part of the Nvim distribution. XDG_DATA_DIRS defaults
+ to /usr/local/share/:/usr/share/, so system administrators are
+ expected to install site plugins to /usr/share/nvim/site.
+ 5. Session state directory, for state data such as swap, backupdir,
+ viewdir, undodir, etc.
+ Given by `stdpath("state")`. |$XDG_STATE_HOME|
+ 6. $VIMRUNTIME, for files distributed with Nvim.
+ *after-directory*
+ 7, 8, 9, 10. In after/ subdirectories of 1, 2, 3 and 4, with reverse
+ ordering. This is for preferences to overrule or add to the
+ distributed defaults or system-wide settings (rarely needed).
+
+ *packages-runtimepath*
+ "start" packages will also be searched (|runtime-search-path|) for
+ runtime files after these, though such packages are not explicitly
+ reported in &runtimepath. But "opt" packages are explicitly added to
+ &runtimepath by |:packadd|.
+
+ Note that, unlike 'path', no wildcards like "**" are allowed. Normal
+ wildcards are allowed, but can significantly slow down searching for
+ runtime files. For speed, use as few items as possible and avoid
+ wildcards.
+ See |:runtime|.
+ Example: >
+ :set runtimepath=~/vimruntime,/mygroup/vim,$VIMRUNTIME
+ < This will use the directory "~/vimruntime" first (containing your
+ personal Nvim runtime files), then "/mygroup/vim", and finally
+ "$VIMRUNTIME" (the default runtime files).
+ You can put a directory before $VIMRUNTIME to find files which replace
+ distributed runtime files. You can put a directory after $VIMRUNTIME
+ to find files which add to distributed runtime files.
+
+ With |--clean| the home directory entries are not included.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'runtimepath',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories used for runtime files'),
+ tags = { 'vimfiles' },
+ type = 'string',
+ varname = 'p_rtp',
+ },
+ {
+ abbreviation = 'scr',
+ defaults = {
+ if_true = 0,
+ doc = 'half the window height',
+ },
+ desc = [=[
+ Number of lines to scroll with CTRL-U and CTRL-D commands. Will be
+ set to half the number of lines in the window when the window size
+ changes. This may happen when enabling the |status-line| or
+ 'tabline' option after setting the 'scroll' option.
+ If you give a count to the CTRL-U or CTRL-D command it will
+ be used as the new value for 'scroll'. Reset to half the window
+ height with ":set scroll=0".
+ ]=],
+ full_name = 'scroll',
+ no_mkrc = true,
+ pv_name = 'p_scroll',
+ scope = { 'window' },
+ short_desc = N_('lines to scroll with CTRL-U and CTRL-D'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'sms',
+ cb = 'did_set_smoothscroll',
+ defaults = { if_true = false },
+ desc = [=[
+ Scrolling works with screen lines. When 'wrap' is set and the first
+ line in the window wraps part of it may not be visible, as if it is
+ above the window. "<<<" is displayed at the start of the first line,
+ highlighted with |hl-NonText|.
+ You may also want to add "lastline" to the 'display' option to show as
+ much of the last line as possible.
+ NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y
+ and scrolling with the mouse.
+ ]=],
+ full_name = 'smoothscroll',
+ pv_name = 'p_sms',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_("scroll by screen lines when 'wrap' is set"),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'scbk',
+ cb = 'did_set_scrollback',
+ defaults = {
+ if_true = -1,
+ doc = '10000',
+ },
+ desc = [=[
+ Maximum number of lines kept beyond the visible screen. Lines at the
+ top are deleted if new lines exceed this limit.
+ Minimum is 1, maximum is 100000.
+ Only in |terminal| buffers.
+
+ Note: Lines that are not visible and kept in scrollback are not
+ reflown when the terminal buffer is resized horizontally.
+ ]=],
+ full_name = 'scrollback',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('lines to scroll with CTRL-U and CTRL-D'),
+ type = 'number',
+ varname = 'p_scbk',
+ },
+ {
+ abbreviation = 'scb',
+ cb = 'did_set_scrollbind',
+ defaults = { if_true = false },
+ desc = [=[
+ See also |scroll-binding|. When this option is set, scrolling the
+ current window also scrolls other scrollbind windows (windows that
+ also have this option set). This option is useful for viewing the
+ differences between two versions of a file, see 'diff'.
+ See |'scrollopt'| for options that determine how this option should be
+ interpreted.
+ This option is mostly reset when splitting a window to edit another
+ file. This means that ":split | edit file" results in two windows
+ with scroll-binding, but ":split file" does not.
+ ]=],
+ full_name = 'scrollbind',
+ pv_name = 'p_scbind',
+ scope = { 'window' },
+ short_desc = N_('scroll in window as other windows scroll'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'sj',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Minimal number of lines to scroll when the cursor gets off the
+ screen (e.g., with "j"). Not used for scroll commands (e.g., CTRL-E,
+ CTRL-D). Useful if your terminal scrolls very slowly.
+ When set to a negative number from -1 to -100 this is used as the
+ percentage of the window height. Thus -50 scrolls half the window
+ height.
+ ]=],
+ full_name = 'scrolljump',
+ scope = { 'global' },
+ short_desc = N_('minimum number of lines to scroll'),
+ type = 'number',
+ varname = 'p_sj',
+ },
+ {
+ abbreviation = 'so',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Minimal number of screen lines to keep above and below the cursor.
+ This will make some context visible around where you are working. If
+ you set it to a very large value (999) the cursor line will always be
+ in the middle of the window (except at the start or end of the file or
+ when long lines wrap).
+ After using the local value, go back the global value with one of
+ these two: >
+ setlocal scrolloff<
+ setlocal scrolloff=-1
+ < For scrolling horizontally see 'sidescrolloff'.
+ ]=],
+ full_name = 'scrolloff',
+ scope = { 'global', 'window' },
+ short_desc = N_('minimum nr. of lines above and below cursor'),
+ type = 'number',
+ varname = 'p_so',
+ },
+ {
+ abbreviation = 'sbo',
+ cb = 'did_set_scrollopt',
+ defaults = { if_true = 'ver,jump' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a comma-separated list of words that specifies how
+ 'scrollbind' windows should behave. 'sbo' stands for ScrollBind
+ Options.
+ The following words are available:
+ ver Bind vertical scrolling for 'scrollbind' windows
+ hor Bind horizontal scrolling for 'scrollbind' windows
+ jump Applies to the offset between two windows for vertical
+ scrolling. This offset is the difference in the first
+ displayed line of the bound windows. When moving
+ around in a window, another 'scrollbind' window may
+ reach a position before the start or after the end of
+ the buffer. The offset is not changed though, when
+ moving back the 'scrollbind' window will try to scroll
+ to the desired position when possible.
+ When now making that window the current one, two
+ things can be done with the relative offset:
+ 1. When "jump" is not included, the relative offset is
+ adjusted for the scroll position in the new current
+ window. When going back to the other window, the
+ new relative offset will be used.
+ 2. When "jump" is included, the other windows are
+ scrolled to keep the same relative offset. When
+ going back to the other window, it still uses the
+ same relative offset.
+ Also see |scroll-binding|.
+ When 'diff' mode is active there always is vertical scroll binding,
+ even when "ver" isn't there.
+ ]=],
+ expand_cb = 'expand_set_scrollopt',
+ full_name = 'scrollopt',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_("how 'scrollbind' should behave"),
+ type = 'string',
+ varname = 'p_sbo',
+ },
+ {
+ abbreviation = 'sect',
+ defaults = { if_true = 'SHNHH HUnhsh' },
+ desc = [=[
+ Specifies the nroff macros that separate sections. These are pairs of
+ two letters (See |object-motions|). The default makes a section start
+ at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh".
+ ]=],
+ full_name = 'sections',
+ scope = { 'global' },
+ short_desc = N_('nroff macros that separate sections'),
+ type = 'string',
+ varname = 'p_sections',
+ },
+ {
+ defaults = { if_true = false },
+ full_name = 'secure',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('No description'),
+ type = 'bool',
+ varname = 'p_secure',
+ },
+ {
+ abbreviation = 'sel',
+ cb = 'did_set_selection',
+ defaults = { if_true = 'inclusive' },
+ desc = [=[
+ This option defines the behavior of the selection. It is only used
+ in Visual and Select mode.
+ Possible values:
+ value past line inclusive ~
+ old no yes
+ inclusive yes yes
+ exclusive yes no
+ "past line" means that the cursor is allowed to be positioned one
+ character past the line.
+ "inclusive" means that the last character of the selection is included
+ in an operation. For example, when "x" is used to delete the
+ selection.
+ When "old" is used and 'virtualedit' allows the cursor to move past
+ the end of line the line break still isn't included.
+ Note that when "exclusive" is used and selecting from the end
+ backwards, you cannot include the last character of a line, when
+ starting in Normal mode and 'virtualedit' empty.
+ ]=],
+ expand_cb = 'expand_set_selection',
+ full_name = 'selection',
+ scope = { 'global' },
+ short_desc = N_('what type of selection to use'),
+ type = 'string',
+ varname = 'p_sel',
+ },
+ {
+ abbreviation = 'slm',
+ cb = 'did_set_selectmode',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a comma-separated list of words, which specifies when to start
+ Select mode instead of Visual mode, when a selection is started.
+ Possible values:
+ mouse when using the mouse
+ key when using shifted special keys
+ cmd when using "v", "V" or CTRL-V
+ See |Select-mode|.
+ ]=],
+ expand_cb = 'expand_set_selectmode',
+ full_name = 'selectmode',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('when to use Select mode instead of Visual mode'),
+ type = 'string',
+ varname = 'p_slm',
+ },
+ {
+ abbreviation = 'ssop',
+ cb = 'did_set_sessionoptions',
+ defaults = { if_true = 'blank,buffers,curdir,folds,help,tabpages,winsize,terminal' },
+ deny_duplicates = true,
+ desc = [=[
+ Changes the effect of the |:mksession| command. It is a comma-
+ separated list of words. Each word enables saving and restoring
+ something:
+ word save and restore ~
+ blank empty windows
+ buffers hidden and unloaded buffers, not just those in windows
+ curdir the current directory
+ folds manually created folds, opened/closed folds and local
+ fold options
+ globals global variables that start with an uppercase letter
+ and contain at least one lowercase letter. Only
+ String and Number types are stored.
+ help the help window
+ localoptions options and mappings local to a window or buffer (not
+ global values for local options)
+ options all options and mappings (also global values for local
+ options)
+ skiprtp exclude 'runtimepath' and 'packpath' from the options
+ resize size of the Vim window: 'lines' and 'columns'
+ sesdir the directory in which the session file is located
+ will become the current directory (useful with
+ projects accessed over a network from different
+ systems)
+ tabpages all tab pages; without this only the current tab page
+ is restored, so that you can make a session for each
+ tab page separately
+ terminal include terminal windows where the command can be
+ restored
+ winpos position of the whole Vim window
+ winsize window sizes
+ slash |deprecated| Always enabled. Uses "/" in filenames.
+ unix |deprecated| Always enabled. Uses "\n" line endings.
+
+ Don't include both "curdir" and "sesdir". When neither is included
+ filenames are stored as absolute paths.
+ If you leave out "options" many things won't work well after restoring
+ the session.
+ ]=],
+ expand_cb = 'expand_set_sessionoptions',
+ full_name = 'sessionoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('options for |:mksession|'),
+ type = 'string',
+ varname = 'p_ssop',
+ },
+ {
+ abbreviation = 'sd',
+ cb = 'did_set_shada',
+ defaults = {
+ if_true = "!,'100,<50,s10,h",
+ doc = [[for
+ Win32: !,'100,<50,s10,h,rA:,rB:
+ others: !,'100,<50,s10,h]],
+ },
+ deny_duplicates = true,
+ desc = [=[
+ When non-empty, the shada file is read upon startup and written
+ when exiting Vim (see |shada-file|). The string should be a comma-
+ separated list of parameters, each consisting of a single character
+ identifying the particular parameter, followed by a number or string
+ which specifies the value of that parameter. If a particular
+ character is left out, then the default value is used for that
+ parameter. The following is a list of the identifying characters and
+ the effect of their value.
+ CHAR VALUE ~
+ *shada-!*
+ ! When included, save and restore global variables that start
+ with an uppercase letter, and don't contain a lowercase
+ letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis"
+ and "_K_L_M" are not. Nested List and Dict items may not be
+ read back correctly, you end up with an empty item.
+ *shada-quote*
+ " Maximum number of lines saved for each register. Old name of
+ the '<' item, with the disadvantage that you need to put a
+ backslash before the ", otherwise it will be recognized as the
+ start of a comment!
+ *shada-%*
+ % When included, save and restore the buffer list. If Vim is
+ started with a file name argument, the buffer list is not
+ restored. If Vim is started without a file name argument, the
+ buffer list is restored from the shada file. Quickfix
+ ('buftype'), unlisted ('buflisted'), unnamed and buffers on
+ removable media (|shada-r|) are not saved.
+ When followed by a number, the number specifies the maximum
+ number of buffers that are stored. Without a number all
+ buffers are stored.
+ *shada-'*
+ ' Maximum number of previously edited files for which the marks
+ are remembered. This parameter must always be included when
+ 'shada' is non-empty.
+ Including this item also means that the |jumplist| and the
+ |changelist| are stored in the shada file.
+ *shada-/*
+ / Maximum number of items in the search pattern history to be
+ saved. If non-zero, then the previous search and substitute
+ patterns are also saved. When not included, the value of
+ 'history' is used.
+ *shada-:*
+ : Maximum number of items in the command-line history to be
+ saved. When not included, the value of 'history' is used.
+ *shada-<*
+ \< Maximum number of lines saved for each register. If zero then
+ registers are not saved. When not included, all lines are
+ saved. '"' is the old name for this item.
+ Also see the 's' item below: limit specified in KiB.
+ *shada-@*
+ @ Maximum number of items in the input-line history to be
+ saved. When not included, the value of 'history' is used.
+ *shada-c*
+ c Dummy option, kept for compatibility reasons. Has no actual
+ effect: ShaDa always uses UTF-8 and 'encoding' value is fixed
+ to UTF-8 as well.
+ *shada-f*
+ f Whether file marks need to be stored. If zero, file marks ('0
+ to '9, 'A to 'Z) are not stored. When not present or when
+ non-zero, they are all stored. '0 is used for the current
+ cursor position (when exiting or when doing |:wshada|).
+ *shada-h*
+ h Disable the effect of 'hlsearch' when loading the shada
+ file. When not included, it depends on whether ":nohlsearch"
+ has been used since the last search command.
+ *shada-n*
+ n Name of the shada file. The name must immediately follow
+ the 'n'. Must be at the end of the option! If the
+ 'shadafile' option is set, that file name overrides the one
+ given here with 'shada'. Environment variables are
+ expanded when opening the file, not when setting the option.
+ *shada-r*
+ r Removable media. The argument is a string (up to the next
+ ','). This parameter can be given several times. Each
+ specifies the start of a path for which no marks will be
+ stored. This is to avoid removable media. For Windows you
+ could use "ra:,rb:". You can also use it for temp files,
+ e.g., for Unix: "r/tmp". Case is ignored.
+ *shada-s*
+ s Maximum size of an item contents in KiB. If zero then nothing
+ is saved. Unlike Vim this applies to all items, except for
+ the buffer list and header. Full item size is off by three
+ unsigned integers: with `s10` maximum item size may be 1 byte
+ (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
+ integer) + 3 bytes (item size: up to 16-bit integer because
+ 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
+ contents size) = 10253 bytes.
+
+ Example: >
+ :set shada='50,<1000,s100,:0,n~/nvim/shada
+ <
+ '50 Marks will be remembered for the last 50 files you
+ edited.
+ <1000 Contents of registers (up to 1000 lines each) will be
+ remembered.
+ s100 Items with contents occupying more then 100 KiB are
+ skipped.
+ :0 Command-line history will not be saved.
+ n~/nvim/shada The name of the file to use is "~/nvim/shada".
+ no / Since '/' is not specified, the default will be used,
+ that is, save all of the search history, and also the
+ previous search and substitute patterns.
+ no % The buffer list will not be saved nor read back.
+ no h 'hlsearch' highlighting will be restored.
+
+ When setting 'shada' from an empty value you can use |:rshada| to
+ load the contents of the file, this is not done automatically.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shada',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('use .shada file upon startup and exiting'),
+ tags = { 'E526', 'E527', 'E528' },
+ type = 'string',
+ varname = 'p_shada',
+ },
+ {
+ abbreviation = 'sdf',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ When non-empty, overrides the file name used for |shada| (viminfo).
+ When equal to "NONE" no shada file will be read or written.
+ This option can be set with the |-i| command line flag. The |--clean|
+ command line flag sets it to "NONE".
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'shadafile',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('overrides the filename used for shada'),
+ type = 'string',
+ varname = 'p_shadafile',
+ },
+ {
+ abbreviation = 'sh',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = 'sh',
+ if_true = 'cmd.exe',
+ doc = '$SHELL or "sh", Win32: "cmd.exe"',
+ meta = 'sh',
+ },
+ desc = [=[
+ Name of the shell to use for ! and :! commands. When changing the
+ value also check these options: 'shellpipe', 'shellslash'
+ 'shellredir', 'shellquote', 'shellxquote' and 'shellcmdflag'.
+ It is allowed to give an argument to the command, e.g. "csh -f".
+ See |option-backslash| about including spaces and backslashes.
+ Environment variables are expanded |:set_env|.
+
+ If the name of the shell contains a space, you need to enclose it in
+ quotes. Example with quotes: >
+ :set shell=\"c:\program\ files\unix\sh.exe\"\ -f
+ < Note the backslash before each quote (to avoid starting a comment) and
+ each space (to avoid ending the option value), so better use |:let-&|
+ like this: >
+ :let &shell='"C:\Program Files\unix\sh.exe" -f'
+ < Also note that the "-f" is not inside the quotes, because it is not
+ part of the command name.
+ *shell-unquoting*
+ Rules regarding quotes:
+ 1. Option is split on space and tab characters that are not inside
+ quotes: "abc def" runs shell named "abc" with additional argument
+ "def", '"abc def"' runs shell named "abc def" with no additional
+ arguments (here and below: additional means “additional to
+ 'shellcmdflag'â€).
+ 2. Quotes in option may be present in any position and any number:
+ '"abc"', '"a"bc', 'a"b"c', 'ab"c"' and '"a"b"c"' are all equivalent
+ to just "abc".
+ 3. Inside quotes backslash preceding backslash means one backslash.
+ Backslash preceding quote means one quote. Backslash preceding
+ anything else means backslash and next character literally:
+ '"a\\b"' is the same as "a\b", '"a\\"b"' runs shell named literally
+ 'a"b', '"a\b"' is the same as "a\b" again.
+ 4. Outside of quotes backslash always means itself, it cannot be used
+ to escape quote: 'a\"b"' is the same as "a\b".
+ Note that such processing is done after |:set| did its own round of
+ unescaping, so to keep yourself sane use |:let-&| like shown above.
+ *shell-powershell*
+ To use PowerShell: >
+ let &shell = executable('pwsh') ? 'pwsh' : 'powershell'
+ let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;'
+ let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
+ let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode'
+ set shellquote= shellxquote=
+
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'shell',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('name of shell to use for external commands'),
+ tags = { 'E91' },
+ type = 'string',
+ varname = 'p_sh',
+ },
+ {
+ abbreviation = 'shcf',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '-c',
+ if_true = '/s /c',
+ doc = '"-c"; Windows: "/s /c"',
+ },
+ desc = [=[
+ Flag passed to the shell to execute "!" and ":!" commands; e.g.,
+ `bash.exe -c ls` or `cmd.exe /s /c "dir"`. For MS-Windows, the
+ default is set according to the value of 'shell', to reduce the need
+ to set this option by the user.
+ On Unix it can have more than one flag. Each white space separated
+ part is passed as an argument to the shell command.
+ See |option-backslash| about including spaces and backslashes.
+ See |shell-unquoting| which talks about separating this option into
+ multiple arguments.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellcmdflag',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('flag to shell to execute one command'),
+ type = 'string',
+ varname = 'p_shcf',
+ },
+ {
+ abbreviation = 'sp',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '| tee',
+ if_true = '2>&1| tee',
+ doc = '">", "| tee", "|& tee" or "2>&1| tee"',
+ },
+ desc = [=[
+ String to be used to put the output of the ":make" command in the
+ error file. See also |:make_makeprg|. See |option-backslash| about
+ including spaces and backslashes.
+ The name of the temporary file can be represented by "%s" if necessary
+ (the file name is appended automatically if no %s appears in the value
+ of this option).
+ For MS-Windows the default is "2>&1| tee". The stdout and stderr are
+ saved in a file and echoed to the screen.
+ For Unix the default is "| tee". The stdout of the compiler is saved
+ in a file and echoed to the screen. If the 'shell' option is "csh" or
+ "tcsh" after initializations, the default becomes "|& tee". If the
+ 'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
+ "bash", "fish", "ash" or "dash" the default becomes "2>&1| tee". This
+ means that stderr is also included. Before using the 'shell' option a
+ path is removed, thus "/bin/sh" uses "sh".
+ The initialization of this option is done after reading the vimrc
+ and the other initializations, so that when the 'shell' option is set
+ there, the 'shellpipe' option changes automatically, unless it was
+ explicitly set before.
+ When 'shellpipe' is set to an empty string, no redirection of the
+ ":make" output will be done. This is useful if you use a 'makeprg'
+ that writes to 'makeef' by itself. If you want no piping, but do
+ want to include the 'makeef', set 'shellpipe' to a single space.
+ Don't forget to precede the space with a backslash: ":set sp=\ ".
+ In the future pipes may be used for filtering and this option will
+ become obsolete (at least for Unix).
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellpipe',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('string to put output of ":make" in error file'),
+ type = 'string',
+ varname = 'p_sp',
+ },
+ {
+ abbreviation = 'shq',
+ defaults = {
+ if_true = '',
+ doc = [[""; Windows, when 'shell'
+ contains "sh" somewhere: "\""]],
+ },
+ desc = [=[
+ Quoting character(s), put around the command passed to the shell, for
+ the "!" and ":!" commands. The redirection is kept outside of the
+ quoting. See 'shellxquote' to include the redirection. It's
+ probably not useful to set both options.
+ This is an empty string by default. Only known to be useful for
+ third-party shells on Windows systems, such as the MKS Korn Shell
+ or bash, where it should be "\"". The default is adjusted according
+ the value of 'shell', to reduce the need to set this option by the
+ user.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellquote',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('quote character(s) for around shell command'),
+ type = 'string',
+ varname = 'p_shq',
+ },
+ {
+ abbreviation = 'srr',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '>',
+ if_true = '>%s 2>&1',
+ doc = '">", ">&" or ">%s 2>&1"',
+ },
+ desc = [=[
+ String to be used to put the output of a filter command in a temporary
+ file. See also |:!|. See |option-backslash| about including spaces
+ and backslashes.
+ The name of the temporary file can be represented by "%s" if necessary
+ (the file name is appended automatically if no %s appears in the value
+ of this option).
+ The default is ">". For Unix, if the 'shell' option is "csh" or
+ "tcsh" during initializations, the default becomes ">&". If the
+ 'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
+ "bash" or "fish", the default becomes ">%s 2>&1". This means that
+ stderr is also included. For Win32, the Unix checks are done and
+ additionally "cmd" is checked for, which makes the default ">%s 2>&1".
+ Also, the same names with ".exe" appended are checked for.
+ The initialization of this option is done after reading the vimrc
+ and the other initializations, so that when the 'shell' option is set
+ there, the 'shellredir' option changes automatically unless it was
+ explicitly set before.
+ In the future pipes may be used for filtering and this option will
+ become obsolete (at least for Unix).
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellredir',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('string to put output of filter in a temp file'),
+ type = 'string',
+ varname = 'p_srr',
+ },
+ {
+ abbreviation = 'ssl',
+ cb = 'did_set_shellslash',
+ defaults = { if_true = false },
+ desc = [=[
+ only for MS-Windows
+ When set, a forward slash is used when expanding file names. This is
+ useful when a Unix-like shell is used instead of cmd.exe. Backward
+ slashes can still be typed, but they are changed to forward slashes by
+ Vim.
+ Note that setting or resetting this option has no effect for some
+ existing file names, thus this option needs to be set before opening
+ any file for best results. This might change in the future.
+ 'shellslash' only works when a backslash can be used as a path
+ separator. To test if this is so use: >
+ if exists('+shellslash')
+ < Also see 'completeslash'.
+ ]=],
+ enable_if = 'BACKSLASH_IN_FILENAME',
+ full_name = 'shellslash',
+ scope = { 'global' },
+ short_desc = N_('use forward slash for shell file names'),
+ type = 'bool',
+ varname = 'p_ssl',
+ },
+ {
+ abbreviation = 'stmp',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, use temp files for shell commands. When off use a pipe.
+ When using a pipe is not possible temp files are used anyway.
+ The advantage of using a pipe is that nobody can read the temp file
+ and the 'shell' command does not need to support redirection.
+ The advantage of using a temp file is that the file type and encoding
+ can be detected.
+ The |FilterReadPre|, |FilterReadPost| and |FilterWritePre|,
+ |FilterWritePost| autocommands event are not triggered when
+ 'shelltemp' is off.
+ |system()| does not respect this option, it always uses pipes.
+ ]=],
+ full_name = 'shelltemp',
+ scope = { 'global' },
+ short_desc = N_('whether to use a temp file for shell commands'),
+ type = 'bool',
+ varname = 'p_stmp',
+ },
+ {
+ abbreviation = 'sxq',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '',
+ if_true = '"',
+ doc = '"", Windows: "\\""',
+ },
+ desc = [=[
+ Quoting character(s), put around the command passed to the shell, for
+ the "!" and ":!" commands. Includes the redirection. See
+ 'shellquote' to exclude the redirection. It's probably not useful
+ to set both options.
+ When the value is '(' then ')' is appended. When the value is '"('
+ then ')"' is appended.
+ When the value is '(' then also see 'shellxescape'.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellxquote',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_("like 'shellquote', but include redirection"),
+ type = 'string',
+ varname = 'p_sxq',
+ },
+ {
+ abbreviation = 'sxe',
+ defaults = { if_true = '' },
+ desc = [=[
+ When 'shellxquote' is set to "(" then the characters listed in this
+ option will be escaped with a '^' character. This makes it possible
+ to execute most external commands with cmd.exe.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellxescape',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_("characters to escape when 'shellxquote' is ("),
+ type = 'string',
+ varname = 'p_sxe',
+ },
+ {
+ abbreviation = 'sr',
+ defaults = { if_true = false },
+ desc = [=[
+ Round indent to multiple of 'shiftwidth'. Applies to > and <
+ commands. CTRL-T and CTRL-D in Insert mode always round the indent to
+ a multiple of 'shiftwidth' (this is Vi compatible).
+ ]=],
+ full_name = 'shiftround',
+ scope = { 'global' },
+ short_desc = N_('round indent to multiple of shiftwidth'),
+ type = 'bool',
+ varname = 'p_sr',
+ },
+ {
+ abbreviation = 'sw',
+ cb = 'did_set_shiftwidth_tabstop',
+ defaults = { if_true = 8 },
+ desc = [=[
+ Number of spaces to use for each step of (auto)indent. Used for
+ |'cindent'|, |>>|, |<<|, etc.
+ When zero the 'tabstop' value will be used. Use the |shiftwidth()|
+ function to get the effective shiftwidth value.
+ ]=],
+ full_name = 'shiftwidth',
+ scope = { 'buffer' },
+ short_desc = N_('number of spaces to use for (auto)indent step'),
+ type = 'number',
+ varname = 'p_sw',
+ },
+ {
+ abbreviation = 'shm',
+ cb = 'did_set_shortmess',
+ defaults = { if_true = 'ltToOCF' },
+ desc = [=[
+ This option helps to avoid all the |hit-enter| prompts caused by file
+ messages, for example with CTRL-G, and to avoid some other messages.
+ It is a list of flags:
+ flag meaning when present ~
+ l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l*
+ m use "[+]" instead of "[Modified]" *shm-m*
+ r use "[RO]" instead of "[readonly]" *shm-r*
+ w use "[w]" instead of "written" for file write message *shm-w*
+ and "[a]" instead of "appended" for ':w >> file' command
+ a all of the above abbreviations *shm-a*
+
+ o overwrite message for writing a file with subsequent *shm-o*
+ message for reading a file (useful for ":wn" or when
+ 'autowrite' on)
+ O message for reading a file overwrites any previous *shm-O*
+ message; also for quickfix message (e.g., ":cn")
+ s don't give "search hit BOTTOM, continuing at TOP" or *shm-s*
+ "search hit TOP, continuing at BOTTOM" messages; when using
+ the search count do not show "W" after the count message (see
+ S below)
+ t truncate file message at the start if it is too long *shm-t*
+ to fit on the command-line, "<" will appear in the left most
+ column; ignored in Ex mode
+ T truncate other messages in the middle if they are too *shm-T*
+ long to fit on the command line; "..." will appear in the
+ middle; ignored in Ex mode
+ W don't give "written" or "[w]" when writing a file *shm-W*
+ A don't give the "ATTENTION" message when an existing *shm-A*
+ swap file is found
+ I don't give the intro message when starting Vim, *shm-I*
+ see |:intro|
+ c don't give |ins-completion-menu| messages; for *shm-c*
+ example, "-- XXX completion (YYY)", "match 1 of 2", "The only
+ match", "Pattern not found", "Back at original", etc.
+ C don't give messages while scanning for ins-completion *shm-C*
+ items, for instance "scanning tags"
+ q use "recording" instead of "recording @a" *shm-q*
+ F don't give the file info when editing a file, like *shm-F*
+ `:silent` was used for the command
+ S do not show search count message when searching, e.g. *shm-S*
+ "[1/5]"
+
+ This gives you the opportunity to avoid that a change between buffers
+ requires you to hit <Enter>, but still gives as useful a message as
+ possible for the space available. To get the whole message that you
+ would have got with 'shm' empty, use ":file!"
+ Useful values:
+ shm= No abbreviation of message.
+ shm=a Abbreviation, but no loss of information.
+ shm=at Abbreviation, and truncate message when necessary.
+ ]=],
+ expand_cb = 'expand_set_shortmess',
+ full_name = 'shortmess',
+ list = 'flags',
+ scope = { 'global' },
+ short_desc = N_('list of flags, reduce length of messages'),
+ tags = { 'E1336' },
+ type = 'string',
+ varname = 'p_shm',
+ },
+ {
+ abbreviation = 'sbr',
+ cb = 'did_set_showbreak',
+ defaults = { if_true = '' },
+ desc = [=[
+ String to put at the start of lines that have been wrapped. Useful
+ values are "> " or "+++ ": >
+ :let &showbreak = "> "
+ :let &showbreak = '+++ '
+ < Only printable single-cell characters are allowed, excluding <Tab> and
+ comma (in a future version the comma might be used to separate the
+ part that is shown at the end and at the start of a line).
+ The |hl-NonText| highlight group determines the highlighting.
+ Note that tabs after the showbreak will be displayed differently.
+ If you want the 'showbreak' to appear in between line numbers, add the
+ "n" flag to 'cpoptions'.
+ A window-local value overrules a global value. If the global value is
+ set and you want no value in the current window use NONE: >
+ :setlocal showbreak=NONE
+ <
+ ]=],
+ full_name = 'showbreak',
+ redraw = { 'all_windows' },
+ scope = { 'global', 'window' },
+ short_desc = N_('string to use at the start of wrapped lines'),
+ tags = { 'E595' },
+ type = 'string',
+ varname = 'p_sbr',
+ },
+ {
+ abbreviation = 'sc',
+ defaults = { if_true = true },
+ desc = [=[
+ Show (partial) command in the last line of the screen. Set this
+ option off if your terminal is slow.
+ In Visual mode the size of the selected area is shown:
+ - When selecting characters within a line, the number of characters.
+ If the number of bytes is different it is also displayed: "2-6"
+ means two characters and six bytes.
+ - When selecting more than one line, the number of lines.
+ - When selecting a block, the size in screen characters:
+ {lines}x{columns}.
+ This information can be displayed in an alternative location using the
+ 'showcmdloc' option, useful when 'cmdheight' is 0.
+ ]=],
+ full_name = 'showcmd',
+ scope = { 'global' },
+ short_desc = N_('show (partial) command in status line'),
+ type = 'bool',
+ varname = 'p_sc',
+ },
+ {
+ abbreviation = 'sloc',
+ cb = 'did_set_showcmdloc',
+ defaults = { if_true = 'last' },
+ desc = [=[
+ This option can be used to display the (partially) entered command in
+ another location. Possible values are:
+ last Last line of the screen (default).
+ statusline Status line of the current window.
+ tabline First line of the screen if 'showtabline' is enabled.
+ Setting this option to "statusline" or "tabline" means that these will
+ be redrawn whenever the command changes, which can be on every key
+ pressed.
+ The %S 'statusline' item can be used in 'statusline' or 'tabline' to
+ place the text. Without a custom 'statusline' or 'tabline' it will be
+ displayed in a convenient location.
+ ]=],
+ expand_cb = 'expand_set_showcmdloc',
+ full_name = 'showcmdloc',
+ scope = { 'global' },
+ short_desc = N_('change location of partial command'),
+ type = 'string',
+ varname = 'p_sloc',
+ },
+ {
+ abbreviation = 'sft',
+ defaults = { if_true = false },
+ desc = [=[
+ When completing a word in insert mode (see |ins-completion|) from the
+ tags file, show both the tag name and a tidied-up form of the search
+ pattern (if there is one) as possible matches. Thus, if you have
+ matched a C function, you can see a template for what arguments are
+ required (coding style permitting).
+ Note that this doesn't work well together with having "longest" in
+ 'completeopt', because the completion from the search pattern may not
+ match the typed text.
+ ]=],
+ full_name = 'showfulltag',
+ scope = { 'global' },
+ short_desc = N_('show full tag pattern when completing tag'),
+ type = 'bool',
+ varname = 'p_sft',
+ },
+ {
+ abbreviation = 'sm',
+ defaults = { if_true = false },
+ desc = [=[
+ When a bracket is inserted, briefly jump to the matching one. The
+ jump is only done if the match can be seen on the screen. The time to
+ show the match can be set with 'matchtime'.
+ A Beep is given if there is no match (no matter if the match can be
+ seen or not).
+ When the 'm' flag is not included in 'cpoptions', typing a character
+ will immediately move the cursor back to where it belongs.
+ See the "sm" field in 'guicursor' for setting the cursor shape and
+ blinking when showing the match.
+ The 'matchpairs' option can be used to specify the characters to show
+ matches for. 'rightleft' and 'revins' are used to look for opposite
+ matches.
+ Also see the matchparen plugin for highlighting the match when moving
+ around |pi_paren.txt|.
+ Note: Use of the short form is rated PG.
+ ]=],
+ full_name = 'showmatch',
+ scope = { 'global' },
+ short_desc = N_('briefly jump to matching bracket if insert one'),
+ type = 'bool',
+ varname = 'p_sm',
+ },
+ {
+ abbreviation = 'smd',
+ defaults = { if_true = true },
+ desc = [=[
+ If in Insert, Replace or Visual mode put a message on the last line.
+ The |hl-ModeMsg| highlight group determines the highlighting.
+ The option has no effect when 'cmdheight' is zero.
+ ]=],
+ full_name = 'showmode',
+ scope = { 'global' },
+ short_desc = N_('message on status line to show current mode'),
+ type = 'bool',
+ varname = 'p_smd',
+ },
+ {
+ abbreviation = 'stal',
+ cb = 'did_set_showtabline',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The value of this option specifies when the line with tab page labels
+ will be displayed:
+ 0: never
+ 1: only if there are at least two tab pages
+ 2: always
+ This is both for the GUI and non-GUI implementation of the tab pages
+ line.
+ See |tab-page| for more information about tab pages.
+ ]=],
+ full_name = 'showtabline',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('tells when the tab pages line is displayed'),
+ type = 'number',
+ varname = 'p_stal',
+ },
+ {
+ abbreviation = 'ss',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The minimal number of columns to scroll horizontally. Used only when
+ the 'wrap' option is off and the cursor is moved off of the screen.
+ When it is zero the cursor will be put in the middle of the screen.
+ When using a slow terminal set it to a large number or 0. Not used
+ for "zh" and "zl" commands.
+ ]=],
+ full_name = 'sidescroll',
+ scope = { 'global' },
+ short_desc = N_('minimum number of columns to scroll horizontal'),
+ type = 'number',
+ varname = 'p_ss',
+ },
+ {
+ abbreviation = 'siso',
+ defaults = { if_true = 0 },
+ desc = [=[
+ The minimal number of screen columns to keep to the left and to the
+ right of the cursor if 'nowrap' is set. Setting this option to a
+ value greater than 0 while having |'sidescroll'| also at a non-zero
+ value makes some context visible in the line you are scrolling in
+ horizontally (except at beginning of the line). Setting this option
+ to a large value (like 999) has the effect of keeping the cursor
+ horizontally centered in the window, as long as one does not come too
+ close to the beginning of the line.
+ After using the local value, go back the global value with one of
+ these two: >
+ setlocal sidescrolloff<
+ setlocal sidescrolloff=-1
+ <
+ Example: Try this together with 'sidescroll' and 'listchars' as
+ in the following example to never allow the cursor to move
+ onto the "extends" character: >
+
+ :set nowrap sidescroll=1 listchars=extends:>,precedes:<
+ :set sidescrolloff=1
+ <
+ ]=],
+ full_name = 'sidescrolloff',
+ scope = { 'global', 'window' },
+ short_desc = N_('min. nr. of columns to left and right of cursor'),
+ type = 'number',
+ varname = 'p_siso',
+ },
+ {
+ abbreviation = 'scl',
+ alloced = true,
+ cb = 'did_set_signcolumn',
+ defaults = { if_true = 'auto' },
+ desc = [=[
+ When and how to draw the signcolumn. Valid values are:
+ "auto" only when there is a sign to display
+ "auto:[1-9]" resize to accommodate multiple signs up to the
+ given number (maximum 9), e.g. "auto:4"
+ "auto:[1-8]-[2-9]"
+ resize to accommodate multiple signs up to the
+ given maximum number (maximum 9) while keeping
+ at least the given minimum (maximum 8) fixed
+ space. The minimum number should always be less
+ than the maximum number, e.g. "auto:2-5"
+ "no" never
+ "yes" always
+ "yes:[1-9]" always, with fixed space for signs up to the given
+ number (maximum 9), e.g. "yes:3"
+ "number" display signs in the 'number' column. If the number
+ column is not present, then behaves like "auto".
+ ]=],
+ expand_cb = 'expand_set_signcolumn',
+ full_name = 'signcolumn',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('when to display the sign column'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'scs',
+ defaults = { if_true = false },
+ desc = [=[
+ Override the 'ignorecase' option if the search pattern contains upper
+ case characters. Only used when the search pattern is typed and
+ 'ignorecase' option is on. Used for the commands "/", "?", "n", "N",
+ ":g" and ":s". Not used for "*", "#", "gd", tag search, etc. After
+ "*" and "#" you can make 'smartcase' used by doing a "/" command,
+ recalling the search pattern from history and hitting <Enter>.
+ ]=],
+ full_name = 'smartcase',
+ scope = { 'global' },
+ short_desc = N_('no ignore case when pattern has uppercase'),
+ type = 'bool',
+ varname = 'p_scs',
+ },
+ {
+ abbreviation = 'si',
+ defaults = { if_true = false },
+ desc = [=[
+ Do smart autoindenting when starting a new line. Works for C-like
+ programs, but can also be used for other languages. 'cindent' does
+ something like this, works better in most cases, but is more strict,
+ see |C-indenting|. When 'cindent' is on or 'indentexpr' is set,
+ setting 'si' has no effect. 'indentexpr' is a more advanced
+ alternative.
+ Normally 'autoindent' should also be on when using 'smartindent'.
+ An indent is automatically inserted:
+ - After a line ending in "{".
+ - After a line starting with a keyword from 'cinwords'.
+ - Before a line starting with "}" (only with the "O" command).
+ When typing '}' as the first character in a new line, that line is
+ given the same indent as the matching "{".
+ When typing '#' as the first character in a new line, the indent for
+ that line is removed, the '#' is put in the first column. The indent
+ is restored for the next line. If you don't want this, use this
+ mapping: ":inoremap # X^H#", where ^H is entered with CTRL-V CTRL-H.
+ When using the ">>" command, lines starting with '#' are not shifted
+ right.
+ ]=],
+ full_name = 'smartindent',
+ scope = { 'buffer' },
+ short_desc = N_('smart autoindenting for C programs'),
+ type = 'bool',
+ varname = 'p_si',
+ },
+ {
+ abbreviation = 'sta',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, a <Tab> in front of a line inserts blanks according to
+ 'shiftwidth'. 'tabstop' or 'softtabstop' is used in other places. A
+ <BS> will delete a 'shiftwidth' worth of space at the start of the
+ line.
+ When off, a <Tab> always inserts blanks according to 'tabstop' or
+ 'softtabstop'. 'shiftwidth' is only used for shifting text left or
+ right |shift-left-right|.
+ What gets inserted (a <Tab> or spaces) depends on the 'expandtab'
+ option. Also see |ins-expandtab|. When 'expandtab' is not set, the
+ number of spaces is minimized by using <Tab>s.
+ ]=],
+ full_name = 'smarttab',
+ scope = { 'global' },
+ short_desc = N_("use 'shiftwidth' when inserting <Tab>"),
+ type = 'bool',
+ varname = 'p_sta',
+ },
+ {
+ abbreviation = 'sts',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Number of spaces that a <Tab> counts for while performing editing
+ operations, like inserting a <Tab> or using <BS>. It "feels" like
+ <Tab>s are being inserted, while in fact a mix of spaces and <Tab>s is
+ used. This is useful to keep the 'ts' setting at its standard value
+ of 8, while being able to edit like it is set to 'sts'. However,
+ commands like "x" still work on the actual characters.
+ When 'sts' is zero, this feature is off.
+ When 'sts' is negative, the value of 'shiftwidth' is used.
+ See also |ins-expandtab|. When 'expandtab' is not set, the number of
+ spaces is minimized by using <Tab>s.
+ The 'L' flag in 'cpoptions' changes how tabs are used when 'list' is
+ set.
+
+ The value of 'softtabstop' will be ignored if |'varsofttabstop'| is set
+ to anything other than an empty string.
+ ]=],
+ full_name = 'softtabstop',
+ scope = { 'buffer' },
+ short_desc = N_('number of spaces that <Tab> uses while editing'),
+ type = 'number',
+ varname = 'p_sts',
+ },
+ {
+ cb = 'did_set_spell',
+ defaults = { if_true = false },
+ desc = [=[
+ When on spell checking will be done. See |spell|.
+ The languages are specified with 'spelllang'.
+ ]=],
+ full_name = 'spell',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('spell checking'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'spc',
+ alloced = true,
+ cb = 'did_set_spellcapcheck',
+ defaults = { if_true = '[.?!]\\_[\\])\'"\\t ]\\+' },
+ desc = [=[
+ Pattern to locate the end of a sentence. The following word will be
+ checked to start with a capital letter. If not then it is highlighted
+ with SpellCap |hl-SpellCap| (unless the word is also badly spelled).
+ When this check is not wanted make this option empty.
+ Only used when 'spell' is set.
+ Be careful with special characters, see |option-backslash| about
+ including spaces and backslashes.
+ To set this option automatically depending on the language, see
+ |set-spc-auto|.
+ ]=],
+ full_name = 'spellcapcheck',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('pattern to locate end of a sentence'),
+ type = 'string',
+ varname = 'p_spc',
+ },
+ {
+ abbreviation = 'spf',
+ alloced = true,
+ cb = 'did_set_spellfile',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Name of the word list file where words are added for the |zg| and |zw|
+ commands. It must end in ".{encoding}.add". You need to include the
+ path, otherwise the file is placed in the current directory.
+ The path may include characters from 'isfname', space, comma and '@'.
+ *E765*
+ It may also be a comma-separated list of names. A count before the
+ |zg| and |zw| commands can be used to access each. This allows using
+ a personal word list file and a project word list file.
+ When a word is added while this option is empty Vim will set it for
+ you: Using the first directory in 'runtimepath' that is writable. If
+ there is no "spell" directory yet it will be created. For the file
+ name the first language name that appears in 'spelllang' is used,
+ ignoring the region.
+ The resulting ".spl" file will be used for spell checking, it does not
+ have to appear in 'spelllang'.
+ Normally one file is used for all regions, but you can add the region
+ name if you want to. However, it will then only be used when
+ 'spellfile' is set to it, for entries in 'spelllang' only files
+ without region name will be found.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'spellfile',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('files where |zg| and |zw| store words'),
+ type = 'string',
+ varname = 'p_spf',
+ },
+ {
+ abbreviation = 'spl',
+ alloced = true,
+ cb = 'did_set_spelllang',
+ defaults = { if_true = 'en' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of word list names. When the 'spell' option is
+ on spellchecking will be done for these languages. Example: >
+ set spelllang=en_us,nl,medical
+ < This means US English, Dutch and medical words are recognized. Words
+ that are not recognized will be highlighted.
+ The word list name must consist of alphanumeric characters, a dash or
+ an underscore. It should not include a comma or dot. Using a dash is
+ recommended to separate the two letter language name from a
+ specification. Thus "en-rare" is used for rare English words.
+ A region name must come last and have the form "_xx", where "xx" is
+ the two-letter, lower case region name. You can use more than one
+ region by listing them: "en_us,en_ca" supports both US and Canadian
+ English, but not words specific for Australia, New Zealand or Great
+ Britain. (Note: currently en_au and en_nz dictionaries are older than
+ en_ca, en_gb and en_us).
+ If the name "cjk" is included East Asian characters are excluded from
+ spell checking. This is useful when editing text that also has Asian
+ words.
+ Note that the "medical" dictionary does not exist, it is just an
+ example of a longer name.
+ *E757*
+ As a special case the name of a .spl file can be given as-is. The
+ first "_xx" in the name is removed and used as the region name
+ (_xx is an underscore, two letters and followed by a non-letter).
+ This is mainly for testing purposes. You must make sure the correct
+ encoding is used, Vim doesn't check it.
+ How the related spell files are found is explained here: |spell-load|.
+
+ If the |spellfile.vim| plugin is active and you use a language name
+ for which Vim cannot find the .spl file in 'runtimepath' the plugin
+ will ask you if you want to download the file.
+
+ After this option has been set successfully, Vim will source the files
+ "spell/LANG.vim" in 'runtimepath'. "LANG" is the value of 'spelllang'
+ up to the first character that is not an ASCII letter or number and
+ not a dash. Also see |set-spc-auto|.
+ ]=],
+ expand = true,
+ full_name = 'spelllang',
+ list = 'onecomma',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('language(s) to do spell checking for'),
+ type = 'string',
+ varname = 'p_spl',
+ },
+ {
+ abbreviation = 'sps',
+ cb = 'did_set_spellsuggest',
+ defaults = { if_true = 'best' },
+ deny_duplicates = true,
+ desc = [=[
+ Methods used for spelling suggestions. Both for the |z=| command and
+ the |spellsuggest()| function. This is a comma-separated list of
+ items:
+
+ best Internal method that works best for English. Finds
+ changes like "fast" and uses a bit of sound-a-like
+ scoring to improve the ordering.
+
+ double Internal method that uses two methods and mixes the
+ results. The first method is "fast", the other method
+ computes how much the suggestion sounds like the bad
+ word. That only works when the language specifies
+ sound folding. Can be slow and doesn't always give
+ better results.
+
+ fast Internal method that only checks for simple changes:
+ character inserts/deletes/swaps. Works well for
+ simple typing mistakes.
+
+ {number} The maximum number of suggestions listed for |z=|.
+ Not used for |spellsuggest()|. The number of
+ suggestions is never more than the value of 'lines'
+ minus two.
+
+ timeout:{millisec} Limit the time searching for suggestions to
+ {millisec} milli seconds. Applies to the following
+ methods. When omitted the limit is 5000. When
+ negative there is no limit.
+
+ file:{filename} Read file {filename}, which must have two columns,
+ separated by a slash. The first column contains the
+ bad word, the second column the suggested good word.
+ Example:
+ theribal/terrible ~
+ Use this for common mistakes that do not appear at the
+ top of the suggestion list with the internal methods.
+ Lines without a slash are ignored, use this for
+ comments.
+ The word in the second column must be correct,
+ otherwise it will not be used. Add the word to an
+ ".add" file if it is currently flagged as a spelling
+ mistake.
+ The file is used for all languages.
+
+ expr:{expr} Evaluate expression {expr}. Use a function to avoid
+ trouble with spaces. |v:val| holds the badly spelled
+ word. The expression must evaluate to a List of
+ Lists, each with a suggestion and a score.
+ Example:
+ [['the', 33], ['that', 44]] ~
+ Set 'verbose' and use |z=| to see the scores that the
+ internal methods use. A lower score is better.
+ This may invoke |spellsuggest()| if you temporarily
+ set 'spellsuggest' to exclude the "expr:" part.
+ Errors are silently ignored, unless you set the
+ 'verbose' option to a non-zero value.
+
+ Only one of "best", "double" or "fast" may be used. The others may
+ appear several times in any order. Example: >
+ :set sps=file:~/.config/nvim/sugg,best,expr:MySuggest()
+ <
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ expand_cb = 'expand_set_spellsuggest',
+ full_name = 'spellsuggest',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('method(s) used to suggest spelling corrections'),
+ type = 'string',
+ varname = 'p_sps',
+ },
+ {
+ abbreviation = 'spo',
+ cb = 'did_set_spelloptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of options for spell checking:
+ camel When a word is CamelCased, assume "Cased" is a
+ separate word: every upper-case character in a word
+ that comes after a lower case character indicates the
+ start of a new word.
+ noplainbuffer Only spellcheck a buffer when 'syntax' is enabled,
+ or when extmarks are set within the buffer. Only
+ designated regions of the buffer are spellchecked in
+ this case.
+ ]=],
+ expand_cb = 'expand_set_spelloptions',
+ full_name = 'spelloptions',
+ list = 'onecomma',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ secure = true,
+ type = 'string',
+ varname = 'p_spo',
+ },
+ {
+ abbreviation = 'sb',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, splitting a window will put the new window below the current
+ one. |:split|
+ ]=],
+ full_name = 'splitbelow',
+ scope = { 'global' },
+ short_desc = N_('new window from split is below the current one'),
+ type = 'bool',
+ varname = 'p_sb',
+ },
+ {
+ abbreviation = 'spk',
+ cb = 'did_set_splitkeep',
+ defaults = { if_true = 'cursor' },
+ desc = [=[
+ The value of this option determines the scroll behavior when opening,
+ closing or resizing horizontal splits.
+
+ Possible values are:
+ cursor Keep the same relative cursor position.
+ screen Keep the text on the same screen line.
+ topline Keep the topline the same.
+
+ For the "screen" and "topline" values, the cursor position will be
+ changed when necessary. In this case, the jumplist will be populated
+ with the previous cursor position. For "screen", the text cannot always
+ be kept on the same screen line when 'wrap' is enabled.
+ ]=],
+ expand_cb = 'expand_set_splitkeep',
+ full_name = 'splitkeep',
+ scope = { 'global' },
+ short_desc = N_('determines scroll behavior for split windows'),
+ type = 'string',
+ varname = 'p_spk',
+ },
+ {
+ abbreviation = 'spr',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, splitting a window will put the new window right of the
+ current one. |:vsplit|
+ ]=],
+ full_name = 'splitright',
+ scope = { 'global' },
+ short_desc = N_('new window is put right of the current one'),
+ type = 'bool',
+ varname = 'p_spr',
+ },
+ {
+ abbreviation = 'sol',
+ defaults = { if_true = false },
+ desc = [=[
+ When "on" the commands listed below move the cursor to the first
+ non-blank of the line. When off the cursor is kept in the same column
+ (if possible). This applies to the commands:
+ - CTRL-D, CTRL-U, CTRL-B, CTRL-F, "G", "H", "M", "L", "gg"
+ - "d", "<<" and ">>" with a linewise operator
+ - "%" with a count
+ - buffer changing commands (CTRL-^, :bnext, :bNext, etc.)
+ - Ex commands that only have a line number, e.g., ":25" or ":+".
+ In case of buffer changing commands the cursor is placed at the column
+ where it was the last time the buffer was edited.
+ ]=],
+ full_name = 'startofline',
+ scope = { 'global' },
+ short_desc = N_('commands move cursor to first non-blank in line'),
+ type = 'bool',
+ varname = 'p_sol',
+ vim = false,
+ },
+ {
+ abbreviation = 'stc',
+ alloced = true,
+ cb = 'did_set_statuscolumn',
+ defaults = { if_true = '' },
+ desc = [=[
+ EXPERIMENTAL
+ When non-empty, this option determines the content of the area to the
+ side of a window, normally containing the fold, sign and number columns.
+ The format of this option is like that of 'statusline'.
+
+ Some of the items from the 'statusline' format are different for
+ 'statuscolumn':
+
+ %l line number of currently drawn line
+ %r relative line number of currently drawn line
+ %s sign column for currently drawn line
+ %C fold column for currently drawn line
+
+ NOTE: To draw the sign and fold columns, their items must be included in
+ 'statuscolumn'. Even when they are not included, the status column width
+ will adapt to the 'signcolumn' and 'foldcolumn' width.
+
+ The |v:lnum| variable holds the line number to be drawn.
+ The |v:relnum| variable holds the relative line number to be drawn.
+ The |v:virtnum| variable is negative when drawing virtual lines, zero
+ when drawing the actual buffer line, and positive when
+ drawing the wrapped part of a buffer line.
+
+ NOTE: The %@ click execute function item is supported as well but the
+ specified function will be the same for each row in the same column.
+ It cannot be switched out through a dynamic 'statuscolumn' format, the
+ handler should be written with this in mind.
+
+ Examples: >vim
+ " Relative number with bar separator and click handlers:
+ :set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T
+
+ " Right aligned relative cursor line number:
+ :let &stc='%=%{v:relnum?v:relnum:v:lnum} '
+
+ " Line numbers in hexadecimal for non wrapped part of lines:
+ :let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} '
+
+ " Human readable line numbers with thousands separator:
+ :let &stc='%{substitute(v:lnum,"\\d\\zs\\ze\\'
+ . '%(\\d\\d\\d\\)\\+$",",","g")}'
+
+ " Both relative and absolute line numbers with different
+ " highlighting for odd and even relative numbers:
+ :let &stc='%#NonText#%{&nu?v:lnum:""}' .
+ '%=%{&rnu&&(v:lnum%2)?"\ ".v:relnum:""}' .
+ '%#LineNr#%{&rnu&&!(v:lnum%2)?"\ ".v:relnum:""}'
+
+ < WARNING: this expression is evaluated for each screen line so defining
+ an expensive expression can negatively affect render performance.
+ ]=],
+ full_name = 'statuscolumn',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ secure = true,
+ short_desc = N_('custom format for the status column'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'stl',
+ alloced = true,
+ cb = 'did_set_statusline',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty, this option determines the content of the status line.
+ Also see |status-line|.
+
+ The option consists of printf style '%' items interspersed with
+ normal text. Each status line item is of the form:
+ %-0{minwid}.{maxwid}{item}
+ All fields except the {item} are optional. A single percent sign can
+ be given as "%%".
+
+ When the option starts with "%!" then it is used as an expression,
+ evaluated and the result is used as the option value. Example: >
+ :set statusline=%!MyStatusLine()
+ < The *g:statusline_winid* variable will be set to the |window-ID| of the
+ window that the status line belongs to.
+ The result can contain %{} items that will be evaluated too.
+ Note that the "%!" expression is evaluated in the context of the
+ current window and buffer, while %{} items are evaluated in the
+ context of the window that the statusline belongs to.
+
+ When there is error while evaluating the option then it will be made
+ empty to avoid further errors. Otherwise screen updating would loop.
+ When the result contains unprintable characters the result is
+ unpredictable.
+
+ Note that the only effect of 'ruler' when this option is set (and
+ 'laststatus' is 2 or 3) is controlling the output of |CTRL-G|.
+
+ field meaning ~
+ - Left justify the item. The default is right justified
+ when minwid is larger than the length of the item.
+ 0 Leading zeroes in numeric items. Overridden by "-".
+ minwid Minimum width of the item, padding as set by "-" & "0".
+ Value must be 50 or less.
+ maxwid Maximum width of the item. Truncation occurs with a "<"
+ on the left for text items. Numeric items will be
+ shifted down to maxwid-2 digits followed by ">"number
+ where number is the amount of missing digits, much like
+ an exponential notation.
+ item A one letter code as described below.
+
+ Following is a description of the possible statusline items. The
+ second character in "item" is the type:
+ N for number
+ S for string
+ F for flags as described below
+ - not applicable
+
+ item meaning ~
+ f S Path to the file in the buffer, as typed or relative to current
+ directory.
+ F S Full path to the file in the buffer.
+ t S File name (tail) of file in the buffer.
+ m F Modified flag, text is "[+]"; "[-]" if 'modifiable' is off.
+ M F Modified flag, text is ",+" or ",-".
+ r F Readonly flag, text is "[RO]".
+ R F Readonly flag, text is ",RO".
+ h F Help buffer flag, text is "[help]".
+ H F Help buffer flag, text is ",HLP".
+ w F Preview window flag, text is "[Preview]".
+ W F Preview window flag, text is ",PRV".
+ y F Type of file in the buffer, e.g., "[vim]". See 'filetype'.
+ Y F Type of file in the buffer, e.g., ",VIM". See 'filetype'.
+ q S "[Quickfix List]", "[Location List]" or empty.
+ k S Value of "b:keymap_name" or 'keymap' when |:lmap| mappings are
+ being used: "<keymap>"
+ n N Buffer number.
+ b N Value of character under cursor.
+ B N As above, in hexadecimal.
+ o N Byte number in file of byte under cursor, first byte is 1.
+ Mnemonic: Offset from start of file (with one added)
+ O N As above, in hexadecimal.
+ l N Line number.
+ L N Number of lines in buffer.
+ c N Column number (byte index).
+ v N Virtual column number (screen column).
+ V N Virtual column number as -{num}. Not displayed if equal to 'c'.
+ p N Percentage through file in lines as in |CTRL-G|.
+ P S Percentage through file of displayed window. This is like the
+ percentage described for 'ruler'. Always 3 in length, unless
+ translated.
+ S S 'showcmd' content, see 'showcmdloc'.
+ a S Argument list status as in default title. ({current} of {max})
+ Empty if the argument file count is zero or one.
+ { NF Evaluate expression between "%{" and "}" and substitute result.
+ Note that there is no "%" before the closing "}". The
+ expression cannot contain a "}" character, call a function to
+ work around that. See |stl-%{| below.
+ `{%` - This is almost same as "{" except the result of the expression is
+ re-evaluated as a statusline format string. Thus if the
+ return value of expr contains "%" items they will get expanded.
+ The expression can contain the "}" character, the end of
+ expression is denoted by "%}".
+ For example: >
+ func! Stl_filename() abort
+ return "%t"
+ endfunc
+ < `stl=%{Stl_filename()}` results in `"%t"`
+ `stl=%{%Stl_filename()%}` results in `"Name of current file"`
+ %} - End of "{%" expression
+ ( - Start of item group. Can be used for setting the width and
+ alignment of a section. Must be followed by %) somewhere.
+ ) - End of item group. No width fields allowed.
+ T N For 'tabline': start of tab page N label. Use %T or %X to end
+ the label. Clicking this label with left mouse button switches
+ to the specified tab page.
+ X N For 'tabline': start of close tab N label. Use %X or %T to end
+ the label, e.g.: %3Xclose%X. Use %999X for a "close current
+ tab" label. Clicking this label with left mouse button closes
+ specified tab page.
+ @ N Start of execute function label. Use %X or %T to
+ end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this
+ label runs specified function: in the example when clicking once
+ using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l',
+ ' ')" expression will be run. Function receives the
+ following arguments in order:
+ 1. minwid field value or zero if no N was specified
+ 2. number of mouse clicks to detect multiple clicks
+ 3. mouse button used: "l", "r" or "m" for left, right or middle
+ button respectively; one should not rely on third argument
+ being only "l", "r" or "m": any other non-empty string value
+ that contains only ASCII lower case letters may be expected
+ for other mouse buttons
+ 4. modifiers pressed: string which contains "s" if shift
+ modifier was pressed, "c" for control, "a" for alt and "m"
+ for meta; currently if modifier is not pressed string
+ contains space instead, but one should not rely on presence
+ of spaces or specific order of modifiers: use |stridx()| to
+ test whether some modifier is present; string is guaranteed
+ to contain only ASCII letters and spaces, one letter per
+ modifier; "?" modifier may also be present, but its presence
+ is a bug that denotes that new mouse button recognition was
+ added without modifying code that reacts on mouse clicks on
+ this label.
+ Use |getmousepos()|.winid in the specified function to get the
+ corresponding window id of the clicked item.
+ \< - Where to truncate line if too long. Default is at the start.
+ No width fields allowed.
+ = - Separation point between alignment sections. Each section will
+ be separated by an equal number of spaces. With one %= what
+ comes after it will be right-aligned. With two %= there is a
+ middle part, with white space left and right of it.
+ No width fields allowed.
+ # - Set highlight group. The name must follow and then a # again.
+ Thus use %#HLname# for highlight group HLname. The same
+ highlighting is used, also for the statusline of non-current
+ windows.
+ * - Set highlight group to User{N}, where {N} is taken from the
+ minwid field, e.g. %1*. Restore normal highlight with %* or %0*.
+ The difference between User{N} and StatusLine will be applied to
+ StatusLineNC for the statusline of non-current windows.
+ The number N must be between 1 and 9. See |hl-User1..9|
+
+ When displaying a flag, Vim removes the leading comma, if any, when
+ that flag comes right after plaintext. This will make a nice display
+ when flags are used like in the examples below.
+
+ When all items in a group becomes an empty string (i.e. flags that are
+ not set) and a minwid is not set for the group, the whole group will
+ become empty. This will make a group like the following disappear
+ completely from the statusline when none of the flags are set. >
+ :set statusline=...%(\ [%M%R%H]%)...
+ < Beware that an expression is evaluated each and every time the status
+ line is displayed.
+ *stl-%{* *g:actual_curbuf* *g:actual_curwin*
+ While evaluating %{} the current buffer and current window will be set
+ temporarily to that of the window (and buffer) whose statusline is
+ currently being drawn. The expression will evaluate in this context.
+ The variable "g:actual_curbuf" is set to the `bufnr()` number of the
+ real current buffer and "g:actual_curwin" to the |window-ID| of the
+ real current window. These values are strings.
+
+ The 'statusline' option will be evaluated in the |sandbox| if set from
+ a modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'statusline' |textlock|.
+
+ If the statusline is not updated when you want it (e.g., after setting
+ a variable that's used in an expression), you can force an update by
+ using `:redrawstatus`.
+
+ A result of all digits is regarded a number for display purposes.
+ Otherwise the result is taken as flag text and applied to the rules
+ described above.
+
+ Watch out for errors in expressions. They may render Vim unusable!
+ If you are stuck, hold down ':' or 'Q' to get a prompt, then quit and
+ edit your vimrc or whatever with "vim --clean" to get it right.
+
+ Examples:
+ Emulate standard status line with 'ruler' set >
+ :set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
+ < Similar, but add ASCII value of char under the cursor (like "ga") >
+ :set statusline=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P
+ < Display byte count and byte value, modified flag in red. >
+ :set statusline=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b'
+ :hi User1 term=inverse,bold cterm=inverse,bold ctermfg=red
+ < Display a ,GZ flag if a compressed file is loaded >
+ :set statusline=...%r%{VarExists('b:gzflag','\ [GZ]')}%h...
+ < In the |:autocmd|'s: >
+ :let b:gzflag = 1
+ < And: >
+ :unlet b:gzflag
+ < And define this function: >
+ :function VarExists(var, val)
+ : if exists(a:var) | return a:val | else | return '' | endif
+ :endfunction
+ <
+ ]=],
+ full_name = 'statusline',
+ modelineexpr = true,
+ redraw = { 'statuslines' },
+ scope = { 'global', 'window' },
+ short_desc = N_('custom format for the status line'),
+ tags = { 'E540', 'E542' },
+ type = 'string',
+ varname = 'p_stl',
+ },
+ {
+ abbreviation = 'su',
+ defaults = { if_true = '.bak,~,.o,.h,.info,.swp,.obj' },
+ deny_duplicates = true,
+ desc = [=[
+ Files with these suffixes get a lower priority when multiple files
+ match a wildcard. See |suffixes|. Commas can be used to separate the
+ suffixes. Spaces after the comma are ignored. A dot is also seen as
+ the start of a suffix. To avoid a dot or comma being recognized as a
+ separator, precede it with a backslash (see |option-backslash| about
+ including spaces and backslashes).
+ See 'wildignore' for completely ignoring files.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ suffixes from the list. This avoids problems when a future version
+ uses another default.
+ ]=],
+ full_name = 'suffixes',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('suffixes that are ignored with multiple match'),
+ type = 'string',
+ varname = 'p_su',
+ },
+ {
+ abbreviation = 'sua',
+ alloced = true,
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of suffixes, which are used when searching for a
+ file for the "gf", "[I", etc. commands. Example: >
+ :set suffixesadd=.java
+ <
+ ]=],
+ full_name = 'suffixesadd',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('suffixes added when searching for a file'),
+ type = 'string',
+ varname = 'p_sua',
+ },
+ {
+ abbreviation = 'swf',
+ cb = 'did_set_swapfile',
+ defaults = { if_true = true },
+ desc = [=[
+ Use a swapfile for the buffer. This option can be reset when a
+ swapfile is not wanted for a specific buffer. For example, with
+ confidential information that even root must not be able to access.
+ Careful: All text will be in memory:
+ - Don't use this for big files.
+ - Recovery will be impossible!
+ A swapfile will only be present when |'updatecount'| is non-zero and
+ 'swapfile' is set.
+ When 'swapfile' is reset, the swap file for the current buffer is
+ immediately deleted. When 'swapfile' is set, and 'updatecount' is
+ non-zero, a swap file is immediately created.
+ Also see |swap-file|.
+ If you want to open a new buffer without creating a swap file for it,
+ use the |:noswapfile| modifier.
+ See 'directory' for where the swap file is created.
+
+ This option is used together with 'bufhidden' and 'buftype' to
+ specify special kinds of buffers. See |special-buffers|.
+ ]=],
+ full_name = 'swapfile',
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('whether to use a swapfile for a buffer'),
+ type = 'bool',
+ varname = 'p_swf',
+ },
+ {
+ abbreviation = 'swb',
+ cb = 'did_set_switchbuf',
+ defaults = { if_true = 'uselast' },
+ deny_duplicates = true,
+ desc = [=[
+ This option controls the behavior when switching between buffers.
+ This option is checked, when
+ - jumping to errors with the |quickfix| commands (|:cc|, |:cn|, |:cp|,
+ etc.).
+ - jumping to a tag using the |:stag| command.
+ - opening a file using the |CTRL-W_f| or |CTRL-W_F| command.
+ - jumping to a buffer using a buffer split command (e.g. |:sbuffer|,
+ |:sbnext|, or |:sbrewind|).
+ Possible values (comma-separated list):
+ useopen If included, jump to the first open window in the
+ current tab page that contains the specified buffer
+ (if there is one). Otherwise: Do not examine other
+ windows.
+ usetab Like "useopen", but also consider windows in other tab
+ pages.
+ split If included, split the current window before loading
+ a buffer for a |quickfix| command that display errors.
+ Otherwise: do not split, use current window (when used
+ in the quickfix window: the previously used window or
+ split if there is no other window).
+ vsplit Just like "split" but split vertically.
+ newtab Like "split", but open a new tab page. Overrules
+ "split" when both are present.
+ uselast If included, jump to the previously used window when
+ jumping to errors with |quickfix| commands.
+ ]=],
+ expand_cb = 'expand_set_switchbuf',
+ full_name = 'switchbuf',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('sets behavior when switching to another buffer'),
+ type = 'string',
+ varname = 'p_swb',
+ },
+ {
+ abbreviation = 'smc',
+ defaults = { if_true = 3000 },
+ desc = [=[
+ Maximum column in which to search for syntax items. In long lines the
+ text after this column is not highlighted and following lines may not
+ be highlighted correctly, because the syntax state is cleared.
+ This helps to avoid very slow redrawing for an XML file that is one
+ long line.
+ Set to zero to remove the limit.
+ ]=],
+ full_name = 'synmaxcol',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('maximum column to find syntax items'),
+ type = 'number',
+ varname = 'p_smc',
+ },
+ {
+ abbreviation = 'syn',
+ alloced = true,
+ cb = 'did_set_filetype_or_syntax',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is set, the syntax with this name is loaded, unless
+ syntax highlighting has been switched off with ":syntax off".
+ Otherwise this option does not always reflect the current syntax (the
+ b:current_syntax variable does).
+ This option is most useful in a modeline, for a file which syntax is
+ not automatically recognized. Example, in an IDL file: >
+ /* vim: set syntax=idl : */
+ < When a dot appears in the value then this separates two filetype
+ names. Example: >
+ /* vim: set syntax=c.doxygen : */
+ < This will use the "c" syntax first, then the "doxygen" syntax.
+ Note that the second one must be prepared to be loaded as an addition,
+ otherwise it will be skipped. More than one dot may appear.
+ To switch off syntax highlighting for the current file, use: >
+ :set syntax=OFF
+ < To switch syntax highlighting on according to the current value of the
+ 'filetype' option: >
+ :set syntax=ON
+ < What actually happens when setting the 'syntax' option is that the
+ Syntax autocommand event is triggered with the value as argument.
+ This option is not copied to another buffer, independent of the 's' or
+ 'S' flag in 'cpoptions'.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'syntax',
+ noglob = true,
+ normal_fname_chars = true,
+ scope = { 'buffer' },
+ short_desc = N_('syntax to be loaded for current buffer'),
+ type = 'string',
+ varname = 'p_syn',
+ },
+ {
+ abbreviation = 'tfu',
+ cb = 'did_set_tagfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used to perform tag searches.
+ The function gets the tag pattern and should return a List of matching
+ tags. See |tag-function| for an explanation of how to write the
+ function and an example. The value can be the name of a function, a
+ |lambda| or a |Funcref|. See |option-value-function| for more
+ information.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'tagfunc',
+ func = true,
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('function used to perform tag searches'),
+ type = 'string',
+ varname = 'p_tfu',
+ },
+ {
+ abbreviation = 'tal',
+ cb = 'did_set_tabline',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty, this option determines the content of the tab pages
+ line at the top of the Vim window. When empty Vim will use a default
+ tab pages line. See |setting-tabline| for more info.
+
+ The tab pages line only appears as specified with the 'showtabline'
+ option and only when there is no GUI tab line. When 'e' is in
+ 'guioptions' and the GUI supports a tab line 'guitablabel' is used
+ instead. Note that the two tab pages lines are very different.
+
+ The value is evaluated like with 'statusline'. You can use
+ |tabpagenr()|, |tabpagewinnr()| and |tabpagebuflist()| to figure out
+ the text to be displayed. Use "%1T" for the first label, "%2T" for
+ the second one, etc. Use "%X" items for closing labels.
+
+ When changing something that is used in 'tabline' that does not
+ trigger it to be updated, use |:redrawtabline|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ Keep in mind that only one of the tab pages is the current one, others
+ are invisible and you can't jump to their windows.
+ ]=],
+ full_name = 'tabline',
+ modelineexpr = true,
+ redraw = { 'tabline' },
+ scope = { 'global' },
+ short_desc = N_('custom format for the console tab pages line'),
+ type = 'string',
+ varname = 'p_tal',
+ },
+ {
+ abbreviation = 'tpm',
+ defaults = { if_true = 50 },
+ desc = [=[
+ Maximum number of tab pages to be opened by the |-p| command line
+ argument or the ":tab all" command. |tabpage|
+ ]=],
+ full_name = 'tabpagemax',
+ scope = { 'global' },
+ short_desc = N_('maximum number of tab pages for |-p| and "tab all"'),
+ type = 'number',
+ varname = 'p_tpm',
+ },
+ {
+ abbreviation = 'ts',
+ cb = 'did_set_shiftwidth_tabstop',
+ defaults = { if_true = 8 },
+ desc = [=[
+ Number of spaces that a <Tab> in the file counts for. Also see
+ the |:retab| command, and the 'softtabstop' option.
+
+ Note: Setting 'tabstop' to any other value than 8 can make your file
+ appear wrong in many places.
+ The value must be more than 0 and less than 10000.
+
+ There are four main ways to use tabs in Vim:
+ 1. Always keep 'tabstop' at 8, set 'softtabstop' and 'shiftwidth' to 4
+ (or 3 or whatever you prefer) and use 'noexpandtab'. Then Vim
+ will use a mix of tabs and spaces, but typing <Tab> and <BS> will
+ behave like a tab appears every 4 (or 3) characters.
+ This is the recommended way, the file will look the same with other
+ tools and when listing it in a terminal.
+ 2. Set 'softtabstop' and 'shiftwidth' to whatever you prefer and use
+ 'expandtab'. This way you will always insert spaces. The
+ formatting will never be messed up when 'tabstop' is changed (leave
+ it at 8 just in case). The file will be a bit larger.
+ You do need to check if no Tabs exist in the file. You can get rid
+ of them by first setting 'expandtab' and using `%retab!`, making
+ sure the value of 'tabstop' is set correctly.
+ 3. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use
+ 'expandtab'. This way you will always insert spaces. The
+ formatting will never be messed up when 'tabstop' is changed.
+ You do need to check if no Tabs exist in the file, just like in the
+ item just above.
+ 4. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use a
+ |modeline| to set these values when editing the file again. Only
+ works when using Vim to edit the file, other tools assume a tabstop
+ is worth 8 spaces.
+ 5. Always set 'tabstop' and 'shiftwidth' to the same value, and
+ 'noexpandtab'. This should then work (for initial indents only)
+ for any tabstop setting that people use. It might be nice to have
+ tabs after the first non-blank inserted as spaces if you do this
+ though. Otherwise aligned comments will be wrong when 'tabstop' is
+ changed.
+
+ The value of 'tabstop' will be ignored if |'vartabstop'| is set to
+ anything other than an empty string.
+ ]=],
+ full_name = 'tabstop',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('number of spaces that <Tab> in file uses'),
+ type = 'number',
+ varname = 'p_ts',
+ },
+ {
+ abbreviation = 'tbs',
+ defaults = { if_true = true },
+ desc = [=[
+ When searching for a tag (e.g., for the |:ta| command), Vim can either
+ use a binary search or a linear search in a tags file. Binary
+ searching makes searching for a tag a LOT faster, but a linear search
+ will find more tags if the tags file wasn't properly sorted.
+ Vim normally assumes that your tags files are sorted, or indicate that
+ they are not sorted. Only when this is not the case does the
+ 'tagbsearch' option need to be switched off.
+
+ When 'tagbsearch' is on, binary searching is first used in the tags
+ files. In certain situations, Vim will do a linear search instead for
+ certain files, or retry all files with a linear search. When
+ 'tagbsearch' is off, only a linear search is done.
+
+ Linear searching is done anyway, for one file, when Vim finds a line
+ at the start of the file indicating that it's not sorted: >
+ !_TAG_FILE_SORTED 0 /some comment/
+ < [The whitespace before and after the '0' must be a single <Tab>]
+
+ When a binary search was done and no match was found in any of the
+ files listed in 'tags', and case is ignored or a pattern is used
+ instead of a normal tag name, a retry is done with a linear search.
+ Tags in unsorted tags files, and matches with different case will only
+ be found in the retry.
+
+ If a tag file indicates that it is case-fold sorted, the second,
+ linear search can be avoided when case is ignored. Use a value of '2'
+ in the "!_TAG_FILE_SORTED" line for this. A tag file can be case-fold
+ sorted with the -f switch to "sort" in most unices, as in the command:
+ "sort -f -o tags tags". For Universal ctags and Exuberant ctags
+ version 5.x or higher (at least 5.5) the --sort=foldcase switch can be
+ used for this as well. Note that case must be folded to uppercase for
+ this to work.
+
+ By default, tag searches are case-sensitive. Case is ignored when
+ 'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is
+ "ignore".
+ Also when 'tagcase' is "followscs" and 'smartcase' is set, or
+ 'tagcase' is "smart", and the pattern contains only lowercase
+ characters.
+
+ When 'tagbsearch' is off, tags searching is slower when a full match
+ exists, but faster when no full match exists. Tags in unsorted tags
+ files may only be found with 'tagbsearch' off.
+ When the tags file is not sorted, or sorted in a wrong way (not on
+ ASCII byte value), 'tagbsearch' should be off, or the line given above
+ must be included in the tags file.
+ This option doesn't affect commands that find all matching tags (e.g.,
+ command-line completion and ":help").
+ ]=],
+ full_name = 'tagbsearch',
+ scope = { 'global' },
+ short_desc = N_('use binary searching in tags files'),
+ type = 'bool',
+ varname = 'p_tbs',
+ },
+ {
+ abbreviation = 'tc',
+ cb = 'did_set_tagcase',
+ defaults = { if_true = 'followic' },
+ desc = [=[
+ This option specifies how case is handled when searching the tags
+ file:
+ followic Follow the 'ignorecase' option
+ followscs Follow the 'smartcase' and 'ignorecase' options
+ ignore Ignore case
+ match Match case
+ smart Ignore case unless an upper case letter is used
+ ]=],
+ expand_cb = 'expand_set_tagcase',
+ full_name = 'tagcase',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('how to handle case when searching in tags files'),
+ type = 'string',
+ varname = 'p_tc',
+ },
+ {
+ abbreviation = 'tl',
+ defaults = { if_true = 0 },
+ desc = [=[
+ If non-zero, tags are significant up to this number of characters.
+ ]=],
+ full_name = 'taglength',
+ scope = { 'global' },
+ short_desc = N_('number of significant characters for a tag'),
+ type = 'number',
+ varname = 'p_tl',
+ },
+ {
+ abbreviation = 'tr',
+ defaults = { if_true = true },
+ desc = [=[
+ If on and using a tags file in another directory, file names in that
+ tags file are relative to the directory where the tags file is.
+ ]=],
+ full_name = 'tagrelative',
+ scope = { 'global' },
+ short_desc = N_('file names in tag file are relative'),
+ type = 'bool',
+ varname = 'p_tr',
+ },
+ {
+ abbreviation = 'tag',
+ defaults = { if_true = './tags;,tags' },
+ deny_duplicates = true,
+ desc = [=[
+ Filenames for the tag command, separated by spaces or commas. To
+ include a space or comma in a file name, precede it with backslashes
+ (see |option-backslash| about including spaces/commas and backslashes).
+ When a file name starts with "./", the '.' is replaced with the path
+ of the current file. But only when the 'd' flag is not included in
+ 'cpoptions'. Environment variables are expanded |:set_env|. Also see
+ |tags-option|.
+ "*", "**" and other wildcards can be used to search for tags files in
+ a directory tree. See |file-searching|. E.g., "/lib/**/tags" will
+ find all files named "tags" below "/lib". The filename itself cannot
+ contain wildcards, it is used as-is. E.g., "/lib/**/tags?" will find
+ files called "tags?".
+ The |tagfiles()| function can be used to get a list of the file names
+ actually used.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ file names from the list. This avoids problems when a future version
+ uses another default.
+ ]=],
+ expand = true,
+ full_name = 'tags',
+ list = 'onecomma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of file names used by the tag command'),
+ tags = { 'E433' },
+ type = 'string',
+ varname = 'p_tags',
+ },
+ {
+ abbreviation = 'tgst',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, the |tagstack| is used normally. When off, a ":tag" or
+ ":tselect" command with an argument will not push the tag onto the
+ tagstack. A following ":tag" without an argument, a ":pop" command or
+ any other command that uses the tagstack will use the unmodified
+ tagstack, but does change the pointer to the active entry.
+ Resetting this option is useful when using a ":tag" command in a
+ mapping which should not change the tagstack.
+ ]=],
+ full_name = 'tagstack',
+ scope = { 'global' },
+ short_desc = N_('push tags onto the tag stack'),
+ type = 'bool',
+ varname = 'p_tgst',
+ },
+ {
+ abbreviation = 'tbidi',
+ defaults = { if_true = false },
+ desc = [=[
+ The terminal is in charge of Bi-directionality of text (as specified
+ by Unicode). The terminal is also expected to do the required shaping
+ that some languages (such as Arabic) require.
+ Setting this option implies that 'rightleft' will not be set when
+ 'arabic' is set and the value of 'arabicshape' will be ignored.
+ Note that setting 'termbidi' has the immediate effect that
+ 'arabicshape' is ignored, but 'rightleft' isn't changed automatically.
+ For further details see |arabic.txt|.
+ ]=],
+ full_name = 'termbidi',
+ scope = { 'global' },
+ short_desc = N_('terminal takes care of bi-directionality'),
+ type = 'bool',
+ varname = 'p_tbidi',
+ },
+ {
+ abbreviation = 'tenc',
+ defaults = { if_true = '' },
+ full_name = 'termencoding',
+ scope = { 'global' },
+ short_desc = N_('Terminal encoding'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'tgc',
+ defaults = { if_true = false },
+ desc = [=[
+ Enables 24-bit RGB color in the |TUI|. Uses "gui" |:highlight|
+ attributes instead of "cterm" attributes. |guifg|
+ Requires an ISO-8613-3 compatible terminal.
+ ]=],
+ full_name = 'termguicolors',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('Terminal true color support'),
+ type = 'bool',
+ varname = 'p_tgc',
+ },
+ {
+ abbreviation = 'tpf',
+ cb = 'did_set_termpastefilter',
+ defaults = { if_true = 'BS,HT,ESC,DEL' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of options for specifying control characters
+ to be removed from the text pasted into the terminal window. The
+ supported values are:
+
+ BS Backspace
+
+ HT TAB
+
+ FF Form feed
+
+ ESC Escape
+
+ DEL DEL
+
+ C0 Other control characters, excluding Line feed and
+ Carriage return < ' '
+
+ C1 Control characters 0x80...0x9F
+ ]=],
+ expand_cb = 'expand_set_termpastefilter',
+ full_name = 'termpastefilter',
+ list = 'onecomma',
+ scope = { 'global' },
+ type = 'string',
+ varname = 'p_tpf',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ If the host terminal supports it, buffer all screen updates
+ made during a redraw cycle so that each screen is displayed in
+ the terminal all at once. This can prevent tearing or flickering
+ when the terminal updates faster than Nvim can redraw.
+ ]=],
+ full_name = 'termsync',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('synchronize redraw output with the host terminal'),
+ type = 'bool',
+ varname = 'p_termsync',
+ },
+ {
+ defaults = { if_true = false },
+ full_name = 'terse',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'tw',
+ cb = 'did_set_textwidth',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Maximum width of text that is being inserted. A longer line will be
+ broken after white space to get this width. A zero value disables
+ this.
+ When 'textwidth' is zero, 'wrapmargin' may be used. See also
+ 'formatoptions' and |ins-textwidth|.
+ When 'formatexpr' is set it will be used to break the line.
+ ]=],
+ full_name = 'textwidth',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('maximum width of text that is being inserted'),
+ type = 'number',
+ varname = 'p_tw',
+ },
+ {
+ abbreviation = 'tsr',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of file names, separated by commas, that are used to lookup words
+ for thesaurus completion commands |i_CTRL-X_CTRL-T|. See
+ |compl-thesaurus|.
+
+ This option is not used if 'thesaurusfunc' is set, either for the
+ buffer or globally.
+
+ To include a comma in a file name precede it with a backslash. Spaces
+ after a comma are ignored, otherwise spaces are included in the file
+ name. See |option-backslash| about using backslashes. The use of
+ |:set+=| and |:set-=| is preferred when adding or removing directories
+ from the list. This avoids problems when a future version uses
+ another default. Backticks cannot be used in this option for security
+ reasons.
+ ]=],
+ expand = true,
+ full_name = 'thesaurus',
+ list = 'onecomma',
+ normal_dname_chars = true,
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of thesaurus files for keyword completion'),
+ type = 'string',
+ varname = 'p_tsr',
+ },
+ {
+ abbreviation = 'tsrfu',
+ alloced = true,
+ cb = 'did_set_thesaurusfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used for thesaurus completion
+ with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|.
+ The value can be the name of a function, a |lambda| or a |Funcref|.
+ See |option-value-function| for more information.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'thesaurusfunc',
+ func = true,
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('function used for thesaurus completion'),
+ type = 'string',
+ varname = 'p_tsrfu',
+ },
+ {
+ abbreviation = 'top',
+ defaults = { if_true = false },
+ desc = [=[
+ When on: The tilde command "~" behaves like an operator.
+ ]=],
+ full_name = 'tildeop',
+ scope = { 'global' },
+ short_desc = N_('tilde command "~" behaves like an operator'),
+ type = 'bool',
+ varname = 'p_to',
+ },
+ {
+ abbreviation = 'to',
+ defaults = { if_true = true },
+ desc = [=[
+ This option and 'timeoutlen' determine the behavior when part of a
+ mapped key sequence has been received. For example, if <c-f> is
+ pressed and 'timeout' is set, Nvim will wait 'timeoutlen' milliseconds
+ for any key that can follow <c-f> in a mapping.
+ ]=],
+ full_name = 'timeout',
+ scope = { 'global' },
+ short_desc = N_('time out on mappings and key codes'),
+ type = 'bool',
+ varname = 'p_timeout',
+ },
+ {
+ abbreviation = 'tm',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Time in milliseconds to wait for a mapped sequence to complete.
+ ]=],
+ full_name = 'timeoutlen',
+ scope = { 'global' },
+ short_desc = N_('time out time in milliseconds'),
+ type = 'number',
+ varname = 'p_tm',
+ },
+ {
+ cb = 'did_set_title_icon',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, the title of the window will be set to the value of
+ 'titlestring' (if it is not empty), or to:
+ filename [+=-] (path) - NVIM
+ Where:
+ filename the name of the file being edited
+ - indicates the file cannot be modified, 'ma' off
+ + indicates the file was modified
+ = indicates the file is read-only
+ =+ indicates the file is read-only and modified
+ (path) is the path of the file being edited
+ - NVIM the server name |v:servername| or "NVIM"
+ ]=],
+ full_name = 'title',
+ scope = { 'global' },
+ short_desc = N_('Vim set the title of the window'),
+ type = 'bool',
+ varname = 'p_title',
+ },
+ {
+ cb = 'did_set_titlelen',
+ defaults = { if_true = 85 },
+ desc = [=[
+ Gives the percentage of 'columns' to use for the length of the window
+ title. When the title is longer, only the end of the path name is
+ shown. A '<' character before the path name is used to indicate this.
+ Using a percentage makes this adapt to the width of the window. But
+ it won't work perfectly, because the actual number of characters
+ available also depends on the font used and other things in the title
+ bar. When 'titlelen' is zero the full path is used. Otherwise,
+ values from 1 to 30000 percent can be used.
+ 'titlelen' is also used for the 'titlestring' option.
+ ]=],
+ full_name = 'titlelen',
+ scope = { 'global' },
+ short_desc = N_("of 'columns' used for window title"),
+ type = 'number',
+ varname = 'p_titlelen',
+ },
+ {
+ defaults = { if_true = '' },
+ desc = [=[
+ If not empty, this option will be used to set the window title when
+ exiting. Only if 'title' is enabled.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'titleold',
+ no_mkrc = true,
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('title, restored when exiting'),
+ type = 'string',
+ varname = 'p_titleold',
+ },
+ {
+ cb = 'did_set_titlestring',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is not empty, it will be used for the title of the
+ window. This happens only when the 'title' option is on.
+
+ When this option contains printf-style '%' items, they will be
+ expanded according to the rules used for 'statusline'.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ Example: >
+ :auto BufEnter * let &titlestring = hostname() .. "/" .. expand("%:p")
+ :set title titlestring=%<%F%=%l/%L-%P titlelen=70
+ < The value of 'titlelen' is used to align items in the middle or right
+ of the available space.
+ Some people prefer to have the file name first: >
+ :set titlestring=%t%(\ %M%)%(\ (%{expand(\"%:~:.:h\")})%)%(\ %a%)
+ < Note the use of "%{ }" and an expression to get the path of the file,
+ without the file name. The "%( %)" constructs are used to add a
+ separating space only when needed.
+ NOTE: Use of special characters in 'titlestring' may cause the display
+ to be garbled (e.g., when it contains a CR or NL character).
+ ]=],
+ full_name = 'titlestring',
+ modelineexpr = true,
+ scope = { 'global' },
+ short_desc = N_('to use for the Vim window title'),
+ type = 'string',
+ varname = 'p_titlestring',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ This option and 'ttimeoutlen' determine the behavior when part of a
+ key code sequence has been received by the |TUI|.
+
+ For example if <Esc> (the \x1b byte) is received and 'ttimeout' is
+ set, Nvim waits 'ttimeoutlen' milliseconds for the terminal to
+ complete a key code sequence. If no input arrives before the timeout,
+ a single <Esc> is assumed. Many TUI cursor key codes start with <Esc>.
+
+ On very slow systems this may fail, causing cursor keys not to work
+ sometimes. If you discover this problem you can ":set ttimeoutlen=9999".
+ Nvim will wait for the next character to arrive after an <Esc>.
+ ]=],
+ full_name = 'ttimeout',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('out on mappings'),
+ type = 'bool',
+ varname = 'p_ttimeout',
+ },
+ {
+ abbreviation = 'ttm',
+ defaults = { if_true = 50 },
+ desc = [=[
+ Time in milliseconds to wait for a key code sequence to complete. Also
+ used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has
+ been typed.
+ ]=],
+ full_name = 'ttimeoutlen',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('time out time for key codes in milliseconds'),
+ type = 'number',
+ varname = 'p_ttm',
+ },
+ {
+ abbreviation = 'tf',
+ defaults = { if_true = true },
+ full_name = 'ttyfast',
+ no_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'udir',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of directory names for undo files, separated with commas.
+ See 'backupdir' for details of the format.
+ "." means using the directory of the file. The undo file name for
+ "file.txt" is ".file.txt.un~".
+ For other directories the file name is the full path of the edited
+ file, with path separators replaced with "%".
+ When writing: The first directory that exists is used. "." always
+ works, no directories after "." will be used for writing. If none of
+ the directories exist Nvim will attempt to create the last directory in
+ the list.
+ When reading all entries are tried to find an undo file. The first
+ undo file that exists is used. When it cannot be read an error is
+ given, no further entry is used.
+ See |undo-persistence|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+
+ Note that unlike 'directory' and 'backupdir', 'undodir' always acts as
+ though the trailing slashes are present (see 'backupdir' for what this
+ means).
+ ]=],
+ expand = 'nodefault',
+ full_name = 'undodir',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('where to store undo files'),
+ tags = { 'E5003' },
+ type = 'string',
+ varname = 'p_udir',
+ },
+ {
+ abbreviation = 'udf',
+ cb = 'did_set_undofile',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, Vim automatically saves undo history to an undo file when
+ writing a buffer to a file, and restores undo history from the same
+ file on buffer read.
+ The directory where the undo file is stored is specified by 'undodir'.
+ For more information about this feature see |undo-persistence|.
+ The undo file is not read when 'undoreload' causes the buffer from
+ before a reload to be saved for undo.
+ When 'undofile' is turned off the undo file is NOT deleted.
+ ]=],
+ full_name = 'undofile',
+ scope = { 'buffer' },
+ short_desc = N_('save undo information in a file'),
+ type = 'bool',
+ varname = 'p_udf',
+ },
+ {
+ abbreviation = 'ul',
+ cb = 'did_set_undolevels',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Maximum number of changes that can be undone. Since undo information
+ is kept in memory, higher numbers will cause more memory to be used.
+ Nevertheless, a single change can already use a large amount of memory.
+ Set to 0 for Vi compatibility: One level of undo and "u" undoes
+ itself: >
+ set ul=0
+ < But you can also get Vi compatibility by including the 'u' flag in
+ 'cpoptions', and still be able to use CTRL-R to repeat undo.
+ Also see |undo-two-ways|.
+ Set to -1 for no undo at all. You might want to do this only for the
+ current buffer: >
+ setlocal ul=-1
+ < This helps when you run out of memory for a single change.
+
+ The local value is set to -123456 when the global value is to be used.
+
+ Also see |clear-undo|.
+ ]=],
+ full_name = 'undolevels',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('maximum number of changes that can be undone'),
+ type = 'number',
+ varname = 'p_ul',
+ },
+ {
+ abbreviation = 'ur',
+ defaults = { if_true = 10000 },
+ desc = [=[
+ Save the whole buffer for undo when reloading it. This applies to the
+ ":e!" command and reloading for when the buffer changed outside of
+ Vim. |FileChangedShell|
+ The save only happens when this option is negative or when the number
+ of lines is smaller than the value of this option.
+ Set this option to zero to disable undo for a reload.
+
+ When saving undo for a reload, any undo file is not read.
+
+ Note that this causes the whole buffer to be stored in memory. Set
+ this option to a lower value if you run out of memory.
+ ]=],
+ full_name = 'undoreload',
+ scope = { 'global' },
+ short_desc = N_('max nr of lines to save for undo on a buffer reload'),
+ type = 'number',
+ varname = 'p_ur',
+ },
+ {
+ abbreviation = 'uc',
+ cb = 'did_set_updatecount',
+ defaults = { if_true = 200 },
+ desc = [=[
+ After typing this many characters the swap file will be written to
+ disk. When zero, no swap file will be created at all (see chapter on
+ recovery |crash-recovery|). 'updatecount' is set to zero by starting
+ Vim with the "-n" option, see |startup|. When editing in readonly
+ mode this option will be initialized to 10000.
+ The swapfile can be disabled per buffer with |'swapfile'|.
+ When 'updatecount' is set from zero to non-zero, swap files are
+ created for all buffers that have 'swapfile' set. When 'updatecount'
+ is set to zero, existing swap files are not deleted.
+ This option has no meaning in buffers where |'buftype'| is "nofile"
+ or "nowrite".
+ ]=],
+ full_name = 'updatecount',
+ scope = { 'global' },
+ short_desc = N_('after this many characters flush swap file'),
+ type = 'number',
+ varname = 'p_uc',
+ },
+ {
+ abbreviation = 'ut',
+ defaults = { if_true = 4000 },
+ desc = [=[
+ If this many milliseconds nothing is typed the swap file will be
+ written to disk (see |crash-recovery|). Also used for the
+ |CursorHold| autocommand event.
+ ]=],
+ full_name = 'updatetime',
+ scope = { 'global' },
+ short_desc = N_('after this many milliseconds flush swap file'),
+ type = 'number',
+ varname = 'p_ut',
+ },
+ {
+ abbreviation = 'vsts',
+ cb = 'did_set_varsofttabstop',
+ defaults = { if_true = '' },
+ desc = [=[
+ A list of the number of spaces that a <Tab> counts for while editing,
+ such as inserting a <Tab> or using <BS>. It "feels" like variable-
+ width <Tab>s are being inserted, while in fact a mixture of spaces
+ and <Tab>s is used. Tab widths are separated with commas, with the
+ final value applying to all subsequent tabs.
+
+ For example, when editing assembly language files where statements
+ start in the 9th column and comments in the 41st, it may be useful
+ to use the following: >
+ :set varsofttabstop=8,32,8
+ < This will set soft tabstops with 8 and 8 + 32 spaces, and 8 more
+ for every column thereafter.
+
+ Note that the value of |'softtabstop'| will be ignored while
+ 'varsofttabstop' is set.
+ ]=],
+ full_name = 'varsofttabstop',
+ list = 'comma',
+ scope = { 'buffer' },
+ short_desc = N_('list of numbers of spaces that <Tab> uses while editing'),
+ type = 'string',
+ varname = 'p_vsts',
+ },
+ {
+ abbreviation = 'vts',
+ cb = 'did_set_vartabstop',
+ defaults = { if_true = '' },
+ desc = [=[
+ A list of the number of spaces that a <Tab> in the file counts for,
+ separated by commas. Each value corresponds to one tab, with the
+ final value applying to all subsequent tabs. For example: >
+ :set vartabstop=4,20,10,8
+ < This will make the first tab 4 spaces wide, the second 20 spaces,
+ the third 10 spaces, and all following tabs 8 spaces.
+
+ Note that the value of |'tabstop'| will be ignored while 'vartabstop'
+ is set.
+ ]=],
+ full_name = 'vartabstop',
+ list = 'comma',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('list of numbers of spaces that <Tab> in file uses'),
+ type = 'string',
+ varname = 'p_vts',
+ },
+ {
+ abbreviation = 'vbs',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Sets the verbosity level. Also set by |-V| and |:verbose|.
+
+ Tracing of options in Lua scripts is activated at level 1; Lua scripts
+ are not traced with verbose=0, for performance.
+
+ If greater than or equal to a given level, Nvim produces the following
+ messages:
+
+ Level Messages ~
+ ----------------------------------------------------------------------
+ 1 Lua assignments to options, mappings, etc.
+ 2 When a file is ":source"'ed, or |shada| file is read or written.
+ 3 UI info, terminal capabilities.
+ 4 Shell commands.
+ 5 Every searched tags file and include file.
+ 8 Files for which a group of autocommands is executed.
+ 9 Executed autocommands.
+ 11 Finding items in a path.
+ 12 Vimscript function calls.
+ 13 When an exception is thrown, caught, finished, or discarded.
+ 14 Anything pending in a ":finally" clause.
+ 15 Ex commands from a script (truncated at 200 characters).
+ 16 Ex commands.
+
+ If 'verbosefile' is set then the verbose messages are not displayed.
+ ]=],
+ full_name = 'verbose',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('give informative messages'),
+ type = 'number',
+ varname = 'p_verbose',
+ },
+ {
+ abbreviation = 'vfile',
+ cb = 'did_set_verbosefile',
+ defaults = { if_true = '' },
+ desc = [=[
+ When not empty all messages are written in a file with this name.
+ When the file exists messages are appended.
+ Writing to the file ends when Vim exits or when 'verbosefile' is made
+ empty. Writes are buffered, thus may not show up for some time.
+ Setting 'verbosefile' to a new value is like making it empty first.
+ The difference with |:redir| is that verbose messages are not
+ displayed when 'verbosefile' is set.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'verbosefile',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('file to write messages in'),
+ type = 'string',
+ varname = 'p_vfile',
+ },
+ {
+ abbreviation = 'vdir',
+ defaults = { if_true = '' },
+ desc = [=[
+ Name of the directory where to store files for |:mkview|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'viewdir',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('directory where to store files with :mkview'),
+ type = 'string',
+ varname = 'p_vdir',
+ },
+ {
+ abbreviation = 'vop',
+ cb = 'did_set_viewoptions',
+ defaults = { if_true = 'folds,cursor,curdir' },
+ deny_duplicates = true,
+ desc = [=[
+ Changes the effect of the |:mkview| command. It is a comma-separated
+ list of words. Each word enables saving and restoring something:
+ word save and restore ~
+ cursor cursor position in file and in window
+ curdir local current directory, if set with |:lcd|
+ folds manually created folds, opened/closed folds and local
+ fold options
+ options options and mappings local to a window or buffer (not
+ global values for local options)
+ localoptions same as "options"
+ slash |deprecated| Always enabled. Uses "/" in filenames.
+ unix |deprecated| Always enabled. Uses "\n" line endings.
+ ]=],
+ expand_cb = 'expand_set_sessionoptions',
+ full_name = 'viewoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('specifies what to save for :mkview'),
+ type = 'string',
+ varname = 'p_vop',
+ },
+ {
+ abbreviation = 'vi',
+ full_name = 'viminfo',
+ nodefault = true,
+ scope = { 'global' },
+ short_desc = N_('Alias for shada'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'vif',
+ full_name = 'viminfofile',
+ nodefault = true,
+ scope = { 'global' },
+ short_desc = N_('Alias for shadafile instead'),
+ type = 'string',
+ },
+ {
+ abbreviation = 've',
+ cb = 'did_set_virtualedit',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of these words:
+ block Allow virtual editing in Visual block mode.
+ insert Allow virtual editing in Insert mode.
+ all Allow virtual editing in all modes.
+ onemore Allow the cursor to move just past the end of the line
+ none When used as the local value, do not allow virtual
+ editing even when the global value is set. When used
+ as the global value, "none" is the same as "".
+ NONE Alternative spelling of "none".
+
+ Virtual editing means that the cursor can be positioned where there is
+ no actual character. This can be halfway into a tab or beyond the end
+ of the line. Useful for selecting a rectangle in Visual mode and
+ editing a table.
+ "onemore" is not the same, it will only allow moving the cursor just
+ after the last character of the line. This makes some commands more
+ consistent. Previously the cursor was always past the end of the line
+ if the line was empty. But it is far from Vi compatible. It may also
+ break some plugins or Vim scripts. For example because |l| can move
+ the cursor after the last character. Use with care!
+ Using the `$` command will move to the last character in the line, not
+ past it. This may actually move the cursor to the left!
+ The `g$` command will move to the end of the screen line.
+ It doesn't make sense to combine "all" with "onemore", but you will
+ not get a warning for it.
+ When combined with other words, "none" is ignored.
+ ]=],
+ expand_cb = 'expand_set_virtualedit',
+ full_name = 'virtualedit',
+ list = 'onecomma',
+ redraw = { 'curswant' },
+ scope = { 'global', 'window' },
+ short_desc = N_('when to use virtual editing'),
+ type = 'string',
+ varname = 'p_ve',
+ },
+ {
+ abbreviation = 'vb',
+ defaults = { if_true = false },
+ desc = [=[
+ Use visual bell instead of beeping. Also see 'errorbells'.
+ ]=],
+ full_name = 'visualbell',
+ scope = { 'global' },
+ short_desc = N_('use visual bell instead of beeping'),
+ type = 'bool',
+ varname = 'p_vb',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ Give a warning message when a shell command is used while the buffer
+ has been changed.
+ ]=],
+ full_name = 'warn',
+ scope = { 'global' },
+ short_desc = N_('for shell command when buffer was changed'),
+ type = 'bool',
+ varname = 'p_warn',
+ },
+ {
+ abbreviation = 'ww',
+ cb = 'did_set_whichwrap',
+ defaults = { if_true = 'b,s' },
+ desc = [=[
+ Allow specified keys that move the cursor left/right to move to the
+ previous/next line when the cursor is on the first/last character in
+ the line. Concatenate characters to allow this for these keys:
+ char key mode ~
+ b <BS> Normal and Visual
+ s <Space> Normal and Visual
+ h "h" Normal and Visual (not recommended)
+ l "l" Normal and Visual (not recommended)
+ < <Left> Normal and Visual
+ > <Right> Normal and Visual
+ ~ "~" Normal
+ [ <Left> Insert and Replace
+ ] <Right> Insert and Replace
+ For example: >
+ :set ww=<,>,[,]
+ < allows wrap only when cursor keys are used.
+ When the movement keys are used in combination with a delete or change
+ operator, the <EOL> also counts for a character. This makes "3h"
+ different from "3dh" when the cursor crosses the end of a line. This
+ is also true for "x" and "X", because they do the same as "dl" and
+ "dh". If you use this, you may also want to use the mapping
+ ":map <BS> X" to make backspace delete the character in front of the
+ cursor.
+ When 'l' is included and it is used after an operator at the end of a
+ line (not an empty line) then it will not move to the next line. This
+ makes "dl", "cl", "yl" etc. work normally.
+ ]=],
+ expand_cb = 'expand_set_whichwrap',
+ full_name = 'whichwrap',
+ list = 'flagscomma',
+ scope = { 'global' },
+ short_desc = N_('allow specified keys to cross line boundaries'),
+ type = 'string',
+ varname = 'p_ww',
+ },
+ {
+ abbreviation = 'wc',
+ cb = 'did_set_wildchar',
+ defaults = {
+ if_true = imacros('TAB'),
+ doc = '<Tab>',
+ },
+ desc = [=[
+ Character you have to type to start wildcard expansion in the
+ command-line, as specified with 'wildmode'.
+ More info here: |cmdline-completion|.
+ The character is not recognized when used inside a macro. See
+ 'wildcharm' for that.
+ Some keys will not work, such as CTRL-C, <CR> and Enter.
+ <Esc> can be used, but hitting it twice in a row will still exit
+ command-line as a failsafe measure.
+ Although 'wc' is a number option, you can set it to a special key: >
+ :set wc=<Tab>
+ <
+ ]=],
+ full_name = 'wildchar',
+ scope = { 'global' },
+ short_desc = N_('command-line character for wildcard expansion'),
+ type = 'number',
+ varname = 'p_wc',
+ },
+ {
+ abbreviation = 'wcm',
+ cb = 'did_set_wildchar',
+ defaults = { if_true = 0 },
+ desc = [=[
+ 'wildcharm' works exactly like 'wildchar', except that it is
+ recognized when used inside a macro. You can find "spare" command-line
+ keys suitable for this option by looking at |ex-edit-index|. Normally
+ you'll never actually type 'wildcharm', just use it in mappings that
+ automatically invoke completion mode, e.g.: >
+ :set wcm=<C-Z>
+ :cnoremap ss so $vim/sessions/*.vim<C-Z>
+ < Then after typing :ss you can use CTRL-P & CTRL-N.
+ ]=],
+ full_name = 'wildcharm',
+ scope = { 'global' },
+ short_desc = N_("like 'wildchar' but also works when mapped"),
+ type = 'number',
+ varname = 'p_wcm',
+ },
+ {
+ abbreviation = 'wig',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of file patterns. A file that matches with one of these
+ patterns is ignored when expanding |wildcards|, completing file or
+ directory names, and influences the result of |expand()|, |glob()| and
+ |globpath()| unless a flag is passed to disable this.
+ The pattern is used like with |:autocmd|, see |autocmd-pattern|.
+ Also see 'suffixes'.
+ Example: >
+ :set wildignore=*.o,*.obj
+ < The use of |:set+=| and |:set-=| is preferred when adding or removing
+ a pattern from the list. This avoids problems when a future version
+ uses another default.
+ ]=],
+ full_name = 'wildignore',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('files matching these patterns are not completed'),
+ type = 'string',
+ varname = 'p_wig',
+ },
+ {
+ abbreviation = 'wic',
+ defaults = { if_true = false },
+ desc = [=[
+ When set case is ignored when completing file names and directories.
+ Has no effect when 'fileignorecase' is set.
+ Does not apply when the shell is used to expand wildcards, which
+ happens when there are special characters.
+ ]=],
+ full_name = 'wildignorecase',
+ scope = { 'global' },
+ short_desc = N_('ignore case when completing file names'),
+ type = 'bool',
+ varname = 'p_wic',
+ },
+ {
+ abbreviation = 'wmnu',
+ defaults = { if_true = true },
+ desc = [=[
+ When 'wildmenu' is on, command-line completion operates in an enhanced
+ mode. On pressing 'wildchar' (usually <Tab>) to invoke completion,
+ the possible matches are shown.
+ When 'wildoptions' contains "pum", then the completion matches are
+ shown in a popup menu. Otherwise they are displayed just above the
+ command line, with the first match highlighted (overwriting the status
+ line, if there is one).
+ Keys that show the previous/next match, such as <Tab> or
+ CTRL-P/CTRL-N, cause the highlight to move to the appropriate match.
+ 'wildmode' must specify "full": "longest" and "list" do not start
+ 'wildmenu' mode. You can check the current mode with |wildmenumode()|.
+ The menu is cancelled when a key is hit that is not used for selecting
+ a completion.
+
+ While the menu is active these keys have special meanings:
+ CTRL-P - go to the previous entry
+ CTRL-N - go to the next entry
+ <Left> <Right> - select previous/next match (like CTRL-P/CTRL-N)
+ <PageUp> - select a match several entries back
+ <PageDown> - select a match several entries further
+ <Up> - in filename/menu name completion: move up into
+ parent directory or parent menu.
+ <Down> - in filename/menu name completion: move into a
+ subdirectory or submenu.
+ <CR> - in menu completion, when the cursor is just after a
+ dot: move into a submenu.
+ CTRL-E - end completion, go back to what was there before
+ selecting a match.
+ CTRL-Y - accept the currently selected match and stop
+ completion.
+
+ If you want <Left> and <Right> to move the cursor instead of selecting
+ a different match, use this: >
+ :cnoremap <Left> <Space><BS><Left>
+ :cnoremap <Right> <Space><BS><Right>
+ <
+ |hl-WildMenu| highlights the current match.
+ ]=],
+ full_name = 'wildmenu',
+ scope = { 'global' },
+ short_desc = N_('use menu for command line completion'),
+ type = 'bool',
+ varname = 'p_wmnu',
+ },
+ {
+ abbreviation = 'wim',
+ cb = 'did_set_wildmode',
+ defaults = { if_true = 'full' },
+ deny_duplicates = false,
+ desc = [=[
+ Completion mode that is used for the character specified with
+ 'wildchar'. It is a comma-separated list of up to four parts. Each
+ part specifies what to do for each consecutive use of 'wildchar'. The
+ first part specifies the behavior for the first use of 'wildchar',
+ The second part for the second use, etc.
+
+ Each part consists of a colon separated list consisting of the
+ following possible values:
+ "" Complete only the first match.
+ "full" Complete the next full match. After the last match,
+ the original string is used and then the first match
+ again. Will also start 'wildmenu' if it is enabled.
+ "longest" Complete till longest common string. If this doesn't
+ result in a longer string, use the next part.
+ "list" When more than one match, list all matches.
+ "lastused" When completing buffer names and more than one buffer
+ matches, sort buffers by time last used (other than
+ the current buffer).
+ When there is only a single match, it is fully completed in all cases.
+
+ Examples of useful colon-separated values:
+ "longest:full" Like "longest", but also start 'wildmenu' if it is
+ enabled. Will not complete to the next full match.
+ "list:full" When more than one match, list all matches and
+ complete first match.
+ "list:longest" When more than one match, list all matches and
+ complete till longest common string.
+ "list:lastused" When more than one buffer matches, list all matches
+ and sort buffers by time last used (other than the
+ current buffer).
+
+ Examples: >
+ :set wildmode=full
+ < Complete first full match, next match, etc. (the default) >
+ :set wildmode=longest,full
+ < Complete longest common string, then each full match >
+ :set wildmode=list:full
+ < List all matches and complete each full match >
+ :set wildmode=list,full
+ < List all matches without completing, then each full match >
+ :set wildmode=longest,list
+ < Complete longest common string, then list alternatives.
+ More info here: |cmdline-completion|.
+ ]=],
+ expand_cb = 'expand_set_wildmode',
+ full_name = 'wildmode',
+ list = 'onecommacolon',
+ scope = { 'global' },
+ short_desc = N_("mode for 'wildchar' command-line expansion"),
+ type = 'string',
+ varname = 'p_wim',
+ },
+ {
+ abbreviation = 'wop',
+ cb = 'did_set_wildoptions',
+ defaults = { if_true = 'pum,tagfile' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of words that change how |cmdline-completion| is done.
+ The following values are supported:
+ fuzzy Use |fuzzy-matching| to find completion matches. When
+ this value is specified, wildcard expansion will not
+ be used for completion. The matches will be sorted by
+ the "best match" rather than alphabetically sorted.
+ This will find more matches than the wildcard
+ expansion. Currently fuzzy matching based completion
+ is not supported for file and directory names and
+ instead wildcard expansion is used.
+ pum Display the completion matches using the popup menu
+ in the same style as the |ins-completion-menu|.
+ tagfile When using CTRL-D to list matching tags, the kind of
+ tag and the file of the tag is listed. Only one match
+ is displayed per line. Often used tag kinds are:
+ d #define
+ f function
+ ]=],
+ expand_cb = 'expand_set_wildoptions',
+ full_name = 'wildoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('specifies how command line completion is done'),
+ type = 'string',
+ varname = 'p_wop',
+ },
+ {
+ abbreviation = 'wak',
+ cb = 'did_set_winaltkeys',
+ defaults = { if_true = 'menu' },
+ desc = [=[
+ only used in Win32
+ Some GUI versions allow the access to menu entries by using the ALT
+ key in combination with a character that appears underlined in the
+ menu. This conflicts with the use of the ALT key for mappings and
+ entering special characters. This option tells what to do:
+ no Don't use ALT keys for menus. ALT key combinations can be
+ mapped, but there is no automatic handling.
+ yes ALT key handling is done by the windowing system. ALT key
+ combinations cannot be mapped.
+ menu Using ALT in combination with a character that is a menu
+ shortcut key, will be handled by the windowing system. Other
+ keys can be mapped.
+ If the menu is disabled by excluding 'm' from 'guioptions', the ALT
+ key is never used for the menu.
+ This option is not used for <F10>; on Win32.
+ ]=],
+ expand_cb = 'expand_set_winaltkeys',
+ full_name = 'winaltkeys',
+ scope = { 'global' },
+ short_desc = N_('when the windows system handles ALT keys'),
+ type = 'string',
+ varname = 'p_wak',
+ },
+ {
+ abbreviation = 'wbr',
+ alloced = true,
+ cb = 'did_set_winbar',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty, this option enables the window bar and determines its
+ contents. The window bar is a bar that's shown at the top of every
+ window with it enabled. The value of 'winbar' is evaluated like with
+ 'statusline'.
+
+ When changing something that is used in 'winbar' that does not trigger
+ it to be updated, use |:redrawstatus|.
+
+ Floating windows do not use the global value of 'winbar'. The
+ window-local value of 'winbar' must be set for a floating window to
+ have a window bar.
+
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+ ]=],
+ full_name = 'winbar',
+ modelineexpr = true,
+ redraw = { 'statuslines' },
+ scope = { 'global', 'window' },
+ short_desc = N_('custom format for the window bar'),
+ type = 'string',
+ varname = 'p_wbr',
+ },
+ {
+ abbreviation = 'winbl',
+ cb = 'did_set_winblend',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Enables pseudo-transparency for a floating window. Valid values are in
+ the range of 0 for fully opaque window (disabled) to 100 for fully
+ transparent background. Values between 0-30 are typically most useful.
+
+ UI-dependent. Works best with RGB colors. 'termguicolors'
+ ]=],
+ full_name = 'winblend',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('Controls transparency level for floating windows'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'winhl',
+ alloced = true,
+ cb = 'did_set_winhighlight',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Window-local highlights. Comma-delimited list of highlight
+ |group-name| pairs "{hl-from}:{hl-to},..." where each {hl-from} is
+ a |highlight-groups| item to be overridden by {hl-to} group in
+ the window.
+
+ Note: highlight namespaces take precedence over 'winhighlight'.
+ See |nvim_win_set_hl_ns()| and |nvim_set_hl()|.
+
+ Highlights of vertical separators are determined by the window to the
+ left of the separator. The 'tabline' highlight of a tabpage is
+ decided by the last-focused window of the tabpage. Highlights of
+ the popupmenu are determined by the current window. Highlights in the
+ message area cannot be overridden.
+
+ Example: show a different color for non-current windows: >
+ set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC
+ <
+ ]=],
+ expand_cb = 'expand_set_winhighlight',
+ full_name = 'winhighlight',
+ list = 'onecommacolon',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('Setup window-local highlights'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'wi',
+ cb = 'did_set_window',
+ defaults = {
+ if_true = 0,
+ doc = 'screen height - 1',
+ },
+ desc = [=[
+ Window height used for |CTRL-F| and |CTRL-B| when there is only one
+ window and the value is smaller than 'lines' minus one. The screen
+ will scroll 'window' minus two lines, with a minimum of one.
+ When 'window' is equal to 'lines' minus one CTRL-F and CTRL-B scroll
+ in a much smarter way, taking care of wrapping lines.
+ When resizing the Vim window, the value is smaller than 1 or more than
+ or equal to 'lines' it will be set to 'lines' minus 1.
+ Note: Do not confuse this with the height of the Vim window, use
+ 'lines' for that.
+ ]=],
+ full_name = 'window',
+ scope = { 'global' },
+ short_desc = N_('nr of lines to scroll for CTRL-F and CTRL-B'),
+ type = 'number',
+ varname = 'p_window',
+ },
+ {
+ abbreviation = 'wh',
+ cb = 'did_set_winheight',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Minimal number of lines for the current window. This is not a hard
+ minimum, Vim will use fewer lines if there is not enough room. If the
+ focus goes to a window that is smaller, its size is increased, at the
+ cost of the height of other windows.
+ Set 'winheight' to a small number for normal editing.
+ Set it to 999 to make the current window fill most of the screen.
+ Other windows will be only 'winminheight' high. This has the drawback
+ that ":all" will create only two windows. To avoid "vim -o 1 2 3 4"
+ to create only two windows, set the option after startup is done,
+ using the |VimEnter| event: >
+ au VimEnter * set winheight=999
+ < Minimum value is 1.
+ The height is not adjusted after one of the commands that change the
+ height of the current window.
+ 'winheight' applies to the current window. Use 'winminheight' to set
+ the minimal height for other windows.
+ ]=],
+ full_name = 'winheight',
+ scope = { 'global' },
+ short_desc = N_('minimum number of lines for the current window'),
+ tags = { 'E591' },
+ type = 'number',
+ varname = 'p_wh',
+ },
+ {
+ abbreviation = 'wfh',
+ defaults = { if_true = false },
+ desc = [=[
+ Keep the window height when windows are opened or closed and
+ 'equalalways' is set. Also for |CTRL-W_=|. Set by default for the
+ |preview-window| and |quickfix-window|.
+ The height may be changed anyway when running out of room.
+ ]=],
+ full_name = 'winfixheight',
+ redraw = { 'statuslines' },
+ scope = { 'window' },
+ short_desc = N_('keep window height when opening/closing windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'wfw',
+ defaults = { if_true = false },
+ desc = [=[
+ Keep the window width when windows are opened or closed and
+ 'equalalways' is set. Also for |CTRL-W_=|.
+ The width may be changed anyway when running out of room.
+ ]=],
+ full_name = 'winfixwidth',
+ redraw = { 'statuslines' },
+ scope = { 'window' },
+ short_desc = N_('keep window width when opening/closing windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'wmh',
+ cb = 'did_set_winminheight',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The minimal height of a window, when it's not the current window.
+ This is a hard minimum, windows will never become smaller.
+ When set to zero, windows may be "squashed" to zero lines (i.e. just a
+ status bar) if necessary. They will return to at least one line when
+ they become active (since the cursor has to have somewhere to go.)
+ Use 'winheight' to set the minimal height of the current window.
+ This option is only checked when making a window smaller. Don't use a
+ large number, it will cause errors when opening more than a few
+ windows. A value of 0 to 3 is reasonable.
+ ]=],
+ full_name = 'winminheight',
+ scope = { 'global' },
+ short_desc = N_('minimum number of lines for any window'),
+ type = 'number',
+ varname = 'p_wmh',
+ },
+ {
+ abbreviation = 'wmw',
+ cb = 'did_set_winminwidth',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The minimal width of a window, when it's not the current window.
+ This is a hard minimum, windows will never become smaller.
+ When set to zero, windows may be "squashed" to zero columns (i.e. just
+ a vertical separator) if necessary. They will return to at least one
+ line when they become active (since the cursor has to have somewhere
+ to go.)
+ Use 'winwidth' to set the minimal width of the current window.
+ This option is only checked when making a window smaller. Don't use a
+ large number, it will cause errors when opening more than a few
+ windows. A value of 0 to 12 is reasonable.
+ ]=],
+ full_name = 'winminwidth',
+ scope = { 'global' },
+ short_desc = N_('minimal number of columns for any window'),
+ type = 'number',
+ varname = 'p_wmw',
+ },
+ {
+ abbreviation = 'wiw',
+ cb = 'did_set_winwidth',
+ defaults = { if_true = 20 },
+ desc = [=[
+ Minimal number of columns for the current window. This is not a hard
+ minimum, Vim will use fewer columns if there is not enough room. If
+ the current window is smaller, its size is increased, at the cost of
+ the width of other windows. Set it to 999 to make the current window
+ always fill the screen. Set it to a small number for normal editing.
+ The width is not adjusted after one of the commands to change the
+ width of the current window.
+ 'winwidth' applies to the current window. Use 'winminwidth' to set
+ the minimal width for other windows.
+ ]=],
+ full_name = 'winwidth',
+ scope = { 'global' },
+ short_desc = N_('minimal number of columns for current window'),
+ tags = { 'E592' },
+ type = 'number',
+ varname = 'p_wiw',
+ },
+ {
+ cb = 'did_set_wrap',
+ defaults = { if_true = true },
+ desc = [=[
+ This option changes how text is displayed. It doesn't change the text
+ in the buffer, see 'textwidth' for that.
+ When on, lines longer than the width of the window will wrap and
+ displaying continues on the next line. When off lines will not wrap
+ and only part of long lines will be displayed. When the cursor is
+ moved to a part that is not shown, the screen will scroll
+ horizontally.
+ The line will be broken in the middle of a word if necessary. See
+ 'linebreak' to get the break at a word boundary.
+ To make scrolling horizontally a bit more useful, try this: >
+ :set sidescroll=5
+ :set listchars+=precedes:<,extends:>
+ < See 'sidescroll', 'listchars' and |wrap-off|.
+ This option can't be set from a |modeline| when the 'diff' option is
+ on.
+ ]=],
+ full_name = 'wrap',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('lines wrap and continue on the next line'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'wm',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Number of characters from the right window border where wrapping
+ starts. When typing text beyond this limit, an <EOL> will be inserted
+ and inserting continues on the next line.
+ Options that add a margin, such as 'number' and 'foldcolumn', cause
+ the text width to be further reduced.
+ When 'textwidth' is non-zero, this option is not used.
+ See also 'formatoptions' and |ins-textwidth|.
+ ]=],
+ full_name = 'wrapmargin',
+ scope = { 'buffer' },
+ short_desc = N_('chars from the right where wrapping starts'),
+ type = 'number',
+ varname = 'p_wm',
+ },
+ {
+ abbreviation = 'ws',
+ defaults = { if_true = true },
+ desc = [=[
+ Searches wrap around the end of the file. Also applies to |]s| and
+ |[s|, searching for spelling mistakes.
+ ]=],
+ full_name = 'wrapscan',
+ scope = { 'global' },
+ short_desc = N_('searches wrap around the end of the file'),
+ tags = { 'E384', 'E385' },
+ type = 'bool',
+ varname = 'p_ws',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ Allows writing files. When not set, writing a file is not allowed.
+ Can be used for a view-only mode, where modifications to the text are
+ still allowed. Can be reset with the |-m| or |-M| command line
+ argument. Filtering text is still possible, even though this requires
+ writing a temporary file.
+ ]=],
+ full_name = 'write',
+ scope = { 'global' },
+ short_desc = N_('to a file is allowed'),
+ type = 'bool',
+ varname = 'p_write',
+ },
+ {
+ abbreviation = 'wa',
+ defaults = { if_true = false },
+ desc = [=[
+ Allows writing to any file with no need for "!" override.
+ ]=],
+ full_name = 'writeany',
+ scope = { 'global' },
+ short_desc = N_('write to file with no need for "!" override'),
+ type = 'bool',
+ varname = 'p_wa',
+ },
+ {
+ abbreviation = 'wb',
+ defaults = { if_true = true },
+ desc = [=[
+ Make a backup before overwriting a file. The backup is removed after
+ the file was successfully written, unless the 'backup' option is
+ also on.
+ WARNING: Switching this option off means that when Vim fails to write
+ your buffer correctly and then, for whatever reason, Vim exits, you
+ lose both the original file and what you were writing. Only reset
+ this option if your file system is almost full and it makes the write
+ fail (and make sure not to exit Vim until the write was successful).
+ See |backup-table| for another explanation.
+ When the 'backupskip' pattern matches, a backup is not made anyway.
+ Depending on 'backupcopy' the backup is a new file or the original
+ file renamed (and a new file is written).
+ ]=],
+ full_name = 'writebackup',
+ scope = { 'global' },
+ short_desc = N_('make a backup before overwriting a file'),
+ type = 'bool',
+ varname = 'p_wb',
+ },
+ {
+ abbreviation = 'wd',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Only takes effect together with 'redrawdebug'.
+ The number of milliseconds to wait after each line or each flush
+ ]=],
+ full_name = 'writedelay',
+ scope = { 'global' },
+ short_desc = N_('delay this many msec for each char (for debug)'),
+ type = 'number',
+ varname = 'p_wd',
+ },
+ },
}
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index a5a708600f..281ec86171 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -1,71 +1,65 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
-#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_getln.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
-#include "nvim/keycodes.h"
-#include "nvim/macros.h"
-#include "nvim/mapping.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/mouse.h"
#include "nvim/move.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/os.h"
-#include "nvim/pos.h"
-#include "nvim/quickfix.h"
-#include "nvim/runtime.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
+#include "nvim/regexp.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
-#include "nvim/tag.h"
-#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "optionstr.c.generated.h"
#endif
-static char e_unclosed_expression_sequence[]
+static const char e_unclosed_expression_sequence[]
= N_("E540: Unclosed expression sequence");
-static char e_unbalanced_groups[]
- = N_("E542: unbalanced groups");
-static char e_backupext_and_patchmode_are_equal[]
+static const char e_comma_required[]
+ = N_("E536: Comma required");
+static const char e_unbalanced_groups[]
+ = N_("E542: Unbalanced groups");
+static const char e_backupext_and_patchmode_are_equal[]
= N_("E589: 'backupext' and 'patchmode' are equal");
-static char e_showbreak_contains_unprintable_or_wide_character[]
+static const char e_showbreak_contains_unprintable_or_wide_character[]
= N_("E595: 'showbreak' contains unprintable or wide character");
static char *(p_ambw_values[]) = { "single", "double", NULL };
@@ -74,12 +68,25 @@ static char *(p_bkc_values[]) = { "yes", "auto", "no", "breaksymlink", "breakhar
static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", "copy", "ctrlg", "error",
"esc", "ex", "hangul", "lang", "mess", "showmatch", "operator",
"register", "shell", "spell", "wildmode", NULL };
+// Note: Keep this in sync with briopt_check()
+static char *(p_briopt_values[]) = { "shift:", "min:", "sbr", "list:", "column:", NULL };
+// Note: Keep this in sync with diffopt_changed()
+static char *(p_dip_values[]) = { "filler", "context:", "iblank", "icase",
+ "iwhite", "iwhiteall", "iwhiteeol", "horizontal", "vertical",
+ "closeoff", "hiddenoff", "foldcolumn:", "followwrap", "internal",
+ "indent-heuristic", "linematch:", "algorithm:", NULL };
+static char *(p_dip_algorithm_values[]) = { "myers", "minimal", "patience", "histogram", NULL };
static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", NULL };
static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL };
+static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL };
static char *(p_cmp_values[]) = { "internal", "keepascii", NULL };
+// Note: Keep this in sync with fill_culopt_flags()
+static char *(p_culopt_values[]) = { "line", "screenline", "number", "both", NULL };
static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", NULL };
static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", "quickfix", "search",
"tag", "insert", "undo", "jump", NULL };
+// Note: Keep this in sync with spell_check_sps()
+static char *(p_sps_values[]) = { "best", "fast", "double", "expr:", "file:", "timeout:", NULL };
/// Also used for 'viewoptions'! Keep in sync with SSOP_ flags.
static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "localoptions",
"options", "help", "blank", "globals", "slash", "unix", "sesdir",
@@ -91,7 +98,9 @@ static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vspli
static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL };
static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL };
static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL };
-static char *(p_wop_values[]) = { "tagfile", "pum", "fuzzy", NULL };
+// Note: Keep this in sync with check_opt_wim()
+static char *(p_wim_values[]) = { "full", "longest", "list", "lastused", NULL };
+static char *(p_wop_values[]) = { "fuzzy", "tagfile", "pum", NULL };
static char *(p_wak_values[]) = { "yes", "menu", "no", NULL };
static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL };
static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL };
@@ -120,20 +129,21 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto
static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5",
"auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4",
"5", "6", "7", "8", "9", NULL };
-static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL };
static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL };
static char *(p_icm_values[]) = { "nosplit", "split", NULL };
static char *(p_jop_values[]) = { "stack", "view", NULL };
static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL };
-static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", NULL };
+static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line",
+ "flush", NULL };
static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL };
/// All possible flags for 'shm'.
-static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW,
+/// the literal chars before 0 are removed flags. these are safely ignored
+static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES,
SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL,
SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO,
SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO,
- SHM_SEARCHCOUNT, 0, };
+ SHM_SEARCHCOUNT, 'n', 'f', 'x', 'i', 0, };
/// After setting various option values: recompute variables that depend on
/// option values.
@@ -146,61 +156,17 @@ void didset_string_options(void)
(void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true);
(void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true);
(void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true);
+ (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
(void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true);
(void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false);
(void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true);
(void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
(void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true);
(void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
- (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
(void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true);
}
-/// Trigger the OptionSet autocommand.
-/// "opt_idx" is the index of the option being set.
-/// "opt_flags" can be OPT_LOCAL etc.
-/// "oldval" the old value
-/// "oldval_l" the old local value (only non-NULL if global and local value are set)
-/// "oldval_g" the old global value (only non-NULL if global and local value are set)
-/// "newval" the new value
-void trigger_optionset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l,
- char *oldval_g, char *newval)
-{
- // Don't do this recursively.
- if (oldval == NULL || newval == NULL
- || *get_vim_var_str(VV_OPTION_TYPE) != NUL) {
- return;
- }
-
- char buf_type[7];
-
- vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",
- (opt_flags & OPT_LOCAL) ? "local" : "global");
- set_vim_var_string(VV_OPTION_OLD, oldval, -1);
- set_vim_var_string(VV_OPTION_NEW, newval, -1);
- set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
- if (opt_flags & OPT_LOCAL) {
- set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1);
- }
- if (opt_flags & OPT_GLOBAL) {
- set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1);
- }
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- set_vim_var_string(VV_OPTION_COMMAND, "set", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1);
- }
- if (opt_flags & OPT_MODELINE) {
- set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1);
- }
- apply_autocmds(EVENT_OPTIONSET, get_option(opt_idx)->fullname, NULL, false, NULL);
- reset_v_option_vars();
-}
-
-static char *illegal_char(char *errbuf, size_t errbuflen, int c)
+char *illegal_char(char *errbuf, size_t errbuflen, int c)
{
if (errbuf == NULL) {
return "";
@@ -270,29 +236,28 @@ void check_buf_options(buf_T *buf)
}
/// Free the string allocated for an option.
-/// Checks for the string being empty_option. This may happen if we're out of
-/// memory, xstrdup() returned NULL, which was replaced by empty_option by
-/// check_options().
+/// Checks for the string being empty_string_option. This may happen if we're out of memory,
+/// xstrdup() returned NULL, which was replaced by empty_string_option by check_options().
/// Does NOT check for P_ALLOCED flag!
void free_string_option(char *p)
{
- if (p != empty_option) {
+ if (p != empty_string_option) {
xfree(p);
}
}
void clear_string_option(char **pp)
{
- if (*pp != empty_option) {
+ if (*pp != empty_string_option) {
xfree(*pp);
}
- *pp = empty_option;
+ *pp = empty_string_option;
}
void check_string_option(char **pp)
{
if (*pp == NULL) {
- *pp = empty_option;
+ *pp = empty_string_option;
}
}
@@ -324,12 +289,12 @@ static void set_string_option_global(vimoption_T *opt, char **varp)
/// "set_sid" is SID_NONE don't set the scriptID. Otherwise set the scriptID to
/// "set_sid".
///
-/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL
+/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL.
+///
+/// TODO(famiu): Remove this and its win/buf variants.
void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags,
int set_sid)
{
- char *s;
- char **varp;
int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0;
int idx = opt_idx;
@@ -348,11 +313,11 @@ void set_string_option_direct(const char *name, int opt_idx, const char *val, in
return;
}
- assert((void *)opt->var != (void *)&p_shada);
+ assert(opt->var != &p_shada);
- s = xstrdup(val);
+ char *s = xstrdup(val);
{
- varp = (char **)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
+ char **varp = (char **)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
if ((opt_flags & OPT_FREE) && (opt->flags & P_ALLOCED)) {
free_string_option(*varp);
}
@@ -369,7 +334,7 @@ void set_string_option_direct(const char *name, int opt_idx, const char *val, in
// make the local value empty, so that the global value is used.
if ((opt->indir & PV_BOTH) && both) {
free_string_option(*varp);
- *varp = empty_option;
+ *varp = empty_string_option;
}
if (set_sid != SID_NONE) {
sctx_T script_ctx;
@@ -402,69 +367,18 @@ void set_string_option_direct_in_win(win_T *wp, const char *name, int opt_idx, c
unblock_autocmds();
}
-/// Set a string option to a new value, handling the effects
-///
-/// @param[in] opt_idx Option to set.
-/// @param[in] value New value.
-/// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or
-/// #OPT_GLOBAL.
-///
-/// @return NULL on success, an untranslated error message on error.
-char *set_string_option(const int opt_idx, const char *const value, const int opt_flags)
- FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT
+/// Like set_string_option_direct(), but for a buffer-local option in "buf".
+/// Blocks autocommands to avoid the old curwin becoming invalid.
+void set_string_option_direct_in_buf(buf_T *buf, const char *name, int opt_idx, const char *val,
+ int opt_flags, int set_sid)
{
- vimoption_T *opt = get_option(opt_idx);
-
- if (opt->var == NULL) { // don't set hidden option
- return NULL;
- }
-
- char *const s = xstrdup(value);
- char **const varp
- = (char **)get_varp_scope(opt, ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0
- ? ((opt->indir & PV_BOTH) ? OPT_GLOBAL : OPT_LOCAL)
- : opt_flags));
- char *const oldval = *varp;
- char *oldval_l = NULL;
- char *oldval_g = NULL;
-
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- oldval_l = *(char **)get_varp_scope(opt, OPT_LOCAL);
- oldval_g = *(char **)get_varp_scope(opt, OPT_GLOBAL);
- }
-
- *varp = s;
-
- char *const saved_oldval = xstrdup(oldval);
- char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0;
- char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0;
- char *const saved_newval = xstrdup(s);
-
- int value_checked = false;
- char *const errmsg = did_set_string_option(opt_idx, varp, oldval,
- NULL, 0,
- opt_flags, &value_checked);
- if (errmsg == NULL) {
- did_set_option(opt_idx, opt_flags, true, value_checked);
- }
-
- // call autocommand after handling side effects
- if (errmsg == NULL) {
- if (!starting) {
- trigger_optionset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g,
- saved_newval);
- }
- if (opt->flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(opt->fullname),
- STRING_OBJ(cstr_as_string(saved_newval)));
- }
- }
- xfree(saved_oldval);
- xfree(saved_oldval_l);
- xfree(saved_oldval_g);
- xfree(saved_newval);
+ buf_T *save_curbuf = curbuf;
- return errmsg;
+ block_autocmds();
+ curbuf = buf;
+ set_string_option_direct(name, opt_idx, val, opt_flags, set_sid);
+ curbuf = save_curbuf;
+ unblock_autocmds();
}
/// Return true if "val" is a valid 'filetype' name.
@@ -475,102 +389,58 @@ static bool valid_filetype(const char *val)
return valid_name(val, ".-_");
}
-/// Handle setting 'mousescroll'.
-/// @return error message, NULL if it's OK.
-static char *check_mousescroll(char *string)
-{
- long vertical = -1;
- long horizontal = -1;
-
- for (;;) {
- char *end = vim_strchr(string, ',');
- size_t length = end ? (size_t)(end - string) : strlen(string);
-
- // Both "ver:" and "hor:" are 4 bytes long.
- // They should be followed by at least one digit.
- if (length <= 4) {
- return e_invarg;
- }
-
- long *direction;
-
- if (memcmp(string, "ver:", 4) == 0) {
- direction = &vertical;
- } else if (memcmp(string, "hor:", 4) == 0) {
- direction = &horizontal;
- } else {
- return e_invarg;
- }
-
- // If the direction has already been set, this is a duplicate.
- if (*direction != -1) {
- return e_invarg;
- }
-
- // Verify that only digits follow the colon.
- for (size_t i = 4; i < length; i++) {
- if (!ascii_isdigit(string[i])) {
- return N_("E548: digit expected");
- }
- }
-
- string += 4;
- *direction = getdigits_int(&string, false, -1);
-
- // Num options are generally kept within the signed int range.
- // We know this number won't be negative because we've already checked for
- // a minus sign. We'll allow 0 as a means of disabling mouse scrolling.
- if (*direction == -1) {
- return e_invarg;
- }
-
- if (!end) {
- break;
- }
-
- string = end + 1;
- }
-
- // If a direction wasn't set, fallback to the default value.
- p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical;
- p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal;
-
- return NULL;
-}
-
-/// Handle setting 'signcolumn' for value 'val'
+/// Handle setting 'signcolumn' for value 'val'. Store minimum and maximum width.
///
/// @return OK when the value is valid, FAIL otherwise
-static int check_signcolumn(char *val)
+int check_signcolumn(win_T *wp)
{
+ char *val = wp->w_p_scl;
if (*val == NUL) {
return FAIL;
}
- // check for basic match
+
if (check_opt_strings(val, p_scl_values, false) == OK) {
+ if (!strncmp(val, "no", 2)) { // no
+ wp->w_minscwidth = wp->w_maxscwidth = SCL_NO;
+ } else if (!strncmp(val, "nu", 2) && (wp->w_p_nu || wp->w_p_rnu)) { // number
+ wp->w_minscwidth = wp->w_maxscwidth = SCL_NUM;
+ } else if (!strncmp(val, "yes:", 4)) { // yes:<NUM>
+ wp->w_minscwidth = wp->w_maxscwidth = val[4] - '0';
+ } else if (*val == 'y') { // yes
+ wp->w_minscwidth = wp->w_maxscwidth = 1;
+ } else if (!strncmp(val, "auto:", 5)) { // auto:<NUM>
+ wp->w_minscwidth = 0;
+ wp->w_maxscwidth = val[5] - '0';
+ } else { // auto
+ wp->w_minscwidth = 0;
+ wp->w_maxscwidth = 1;
+ }
return OK;
}
- // check for 'auto:<NUMBER>-<NUMBER>'
- if (strlen(val) == 8
- && !strncmp(val, "auto:", 5)
- && ascii_isdigit(val[5])
- && val[6] == '-'
- && ascii_isdigit(val[7])) {
- int min = val[5] - '0';
- int max = val[7] - '0';
- if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) {
- return FAIL;
- }
- return OK;
+ if (strncmp(val, "auto:", 5) != 0
+ || strlen(val) != 8
+ || !ascii_isdigit(val[5])
+ || val[6] != '-'
+ || !ascii_isdigit(val[7])) {
+ return FAIL;
}
- return FAIL;
+ // auto:<NUM>-<NUM>
+ int min = val[5] - '0';
+ int max = val[7] - '0';
+ if (min < 1 || max < 2 || min > 8 || min >= max) {
+ return FAIL;
+ }
+
+ wp->w_minscwidth = min;
+ wp->w_maxscwidth = max;
+ return OK;
}
/// Check validity of options with the 'statusline' format.
/// Return an untranslated error message or NULL.
-char *check_stl_option(char *s)
+const char *check_stl_option(char *s)
{
int groupdepth = 0;
static char errbuf[80];
@@ -615,7 +485,7 @@ char *check_stl_option(char *s)
continue;
}
if (vim_strchr(STL_ALL, (uint8_t)(*s)) == NULL) {
- return illegal_char(errbuf, sizeof(errbuf), *s);
+ return illegal_char(errbuf, sizeof(errbuf), (uint8_t)(*s));
}
if (*s == '{') {
bool reevaluate = (*++s == '%');
@@ -638,23 +508,237 @@ char *check_stl_option(char *s)
return NULL;
}
-static int shada_idx = -1;
-
-static bool check_illegal_path_names(char *val, uint32_t flags)
+/// Check for a "normal" directory or file name in some options. Disallow a
+/// path separator (slash and/or backslash), wildcards and characters that are
+/// often illegal in a file name. Be more permissive if "secure" is off.
+bool check_illegal_path_names(char *val, uint32_t flags)
{
- // Disallow a path separator (slash and/or backslash), wildcards and
- // characters that are often illegal in a file name. Be more permissive
- // if "secure" is off.
return (((flags & P_NFNAME)
&& strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL)
|| ((flags & P_NDNAME)
&& strpbrk(val, "*?[|;&<>\r\n") != NULL));
}
-static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **errmsg)
+/// An option that accepts a list of flags is changed.
+/// e.g. 'viewoptions', 'switchbuf', 'casemap', etc.
+static const char *did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list)
+{
+ if (opt_strings_flags(val, values, flagp, list) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+/// An option that accepts a list of string values is changed.
+/// e.g. 'nrformats', 'scrollopt', 'wildoptions', etc.
+static const char *did_set_opt_strings(char *val, char **values, bool list)
+{
+ return did_set_opt_flags(val, values, NULL, list);
+}
+
+/// An option which is a list of flags is set. Valid values are in "flags".
+static const char *did_set_option_listflag(char *val, char *flags, char *errbuf, size_t errbuflen)
+{
+ for (char *s = val; *s; s++) {
+ if (vim_strchr(flags, (uint8_t)(*s)) == NULL) {
+ return illegal_char(errbuf, errbuflen, (uint8_t)(*s));
+ }
+ }
+
+ return NULL;
+}
+
+/// Expand an option that accepts a list of string values.
+static int expand_set_opt_string(optexpand_T *args, char **values, size_t numValues,
+ int *numMatches, char ***matches)
+{
+ regmatch_T *regmatch = args->oe_regmatch;
+ bool include_orig_val = args->oe_include_orig_val;
+ char *option_val = args->oe_opt_value;
+
+ // Assume numValues is small since they are fixed enums, so just allocate
+ // upfront instead of needing two passes to calculate output size.
+ *matches = xmalloc(sizeof(char *) * (numValues + 1));
+
+ int count = 0;
+
+ if (include_orig_val && *option_val != NUL) {
+ (*matches)[count++] = xstrdup(option_val);
+ }
+
+ for (char **val = values; *val != NULL; val++) {
+ if (include_orig_val && *option_val != NUL) {
+ if (strcmp(*val, option_val) == 0) {
+ continue;
+ }
+ }
+ if (vim_regexec(regmatch, *val, 0)) {
+ (*matches)[count++] = xstrdup(*val);
+ }
+ }
+ if (count == 0) {
+ XFREE_CLEAR(*matches);
+ return FAIL;
+ }
+ *numMatches = count;
+ return OK;
+}
+
+static char *set_opt_callback_orig_option = NULL;
+static char *((*set_opt_callback_func)(expand_T *, int));
+
+/// Callback used by expand_set_opt_generic to also include the original value.
+static char *expand_set_opt_callback(expand_T *xp, int idx)
+{
+ if (idx == 0) {
+ if (set_opt_callback_orig_option != NULL) {
+ return set_opt_callback_orig_option;
+ } else {
+ return ""; // empty strings are ignored
+ }
+ }
+ return set_opt_callback_func(xp, idx - 1);
+}
+
+/// Expand an option with a callback that iterates through a list of possible names.
+static int expand_set_opt_generic(optexpand_T *args, CompleteListItemGetter func, int *numMatches,
+ char ***matches)
+{
+ set_opt_callback_orig_option = args->oe_include_orig_val ? args->oe_opt_value : NULL;
+ set_opt_callback_func = func;
+
+ // not using fuzzy as currently EXPAND_STRING_SETTING doesn't use it
+ ExpandGeneric("", args->oe_xp, args->oe_regmatch, matches, numMatches,
+ expand_set_opt_callback, false);
+
+ set_opt_callback_orig_option = NULL;
+ set_opt_callback_func = NULL;
+ return OK;
+}
+
+/// Expand an option which is a list of flags.
+static int expand_set_opt_listflag(optexpand_T *args, char *flags, int *numMatches, char ***matches)
+{
+ char *option_val = args->oe_opt_value;
+ char *cmdline_val = args->oe_set_arg;
+ bool append = args->oe_append;
+ bool include_orig_val = args->oe_include_orig_val && (*option_val != NUL);
+
+ size_t num_flags = strlen(flags);
+
+ // Assume we only have small number of flags, so just allocate max size.
+ *matches = xmalloc(sizeof(char *) * (num_flags + 1));
+
+ int count = 0;
+
+ if (include_orig_val) {
+ (*matches)[count++] = xstrdup(option_val);
+ }
+
+ for (char *flag = flags; *flag != NUL; flag++) {
+ if (append && vim_strchr(option_val, *flag) != NULL) {
+ continue;
+ }
+
+ if (vim_strchr(cmdline_val, *flag) == NULL) {
+ if (include_orig_val && option_val[1] == NUL && *flag == option_val[0]) {
+ // This value is already used as the first choice as it's the
+ // existing flag. Just skip it to avoid duplicate.
+ continue;
+ }
+ (*matches)[count++] = xmemdupz(flag, 1);
+ }
+ }
+
+ if (count == 0) {
+ XFREE_CLEAR(*matches);
+ return FAIL;
+ }
+ *numMatches = count;
+ return OK;
+}
+
+/// The 'ambiwidth' option is changed.
+const char *did_set_ambiwidth(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) {
+ return e_invarg;
+ }
+ return check_chars_options();
+}
+
+int expand_set_ambiwidth(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_ambw_values,
+ ARRAY_SIZE(p_ambw_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'background' option is changed.
+const char *did_set_background(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_opt_strings(p_bg, p_bg_values, false) != OK) {
+ return e_invarg;
+ }
+
+ int dark = (*p_bg == 'd');
+
+ init_highlight(false, false);
+
+ if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) {
+ // The color scheme must have set 'background' back to another
+ // value, that's not what we want here. Disable the color
+ // scheme and set the colors again.
+ do_unlet(S_LEN("g:colors_name"), true);
+ free_string_option(p_bg);
+ p_bg = xstrdup((dark ? "dark" : "light"));
+ check_string_option(&p_bg);
+ init_highlight(false, false);
+ }
+ return NULL;
+}
+
+int expand_set_background(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bg_values,
+ ARRAY_SIZE(p_bg_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'backspace' option is changed.
+const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (ascii_isdigit(*p_bs)) {
+ if (*p_bs != '2') {
+ return e_invarg;
+ }
+ } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bs_values,
+ ARRAY_SIZE(p_bs_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'backupcopy' option is changed.
+const char *did_set_backupcopy(optset_T *args)
{
+ buf_T *buf = (buf_T *)args->os_buf;
+ const char *oldval = args->os_oldval.string.data;
+ int opt_flags = args->os_flags;
char *bkc = p_bkc;
- unsigned int *flags = &bkc_flags;
+ unsigned *flags = &bkc_flags;
if (opt_flags & OPT_LOCAL) {
bkc = buf->b_p_bkc;
@@ -666,7 +750,7 @@ static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **e
*flags = 0;
} else {
if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) {
- *errmsg = e_invarg;
+ return e_invarg;
}
if (((*flags & BKC_AUTO) != 0)
@@ -674,171 +758,526 @@ static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **e
+ ((*flags & BKC_NO) != 0) != 1) {
// Must have exactly one of "auto", "yes" and "no".
(void)opt_strings_flags(oldval, p_bkc_values, flags, true);
- *errmsg = e_invarg;
+ return e_invarg;
}
}
+
+ return NULL;
}
-static void did_set_backupext_or_patchmode(char **errmsg)
+int expand_set_backupcopy(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bkc_values,
+ ARRAY_SIZE(p_bkc_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'backupext' or the 'patchmode' option is changed.
+const char *did_set_backupext_or_patchmode(optset_T *args FUNC_ATTR_UNUSED)
{
if (strcmp(*p_bex == '.' ? p_bex + 1 : p_bex,
*p_pm == '.' ? p_pm + 1 : p_pm) == 0) {
- *errmsg = e_backupext_and_patchmode_are_equal;
+ return e_backupext_and_patchmode_are_equal;
}
+
+ return NULL;
}
-static void did_set_breakindentopt(win_T *win, char **errmsg)
+/// The 'belloff' option is changed.
+const char *did_set_belloff(optset_T *args FUNC_ATTR_UNUSED)
{
+ return did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true);
+}
+
+int expand_set_belloff(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bo_values,
+ ARRAY_SIZE(p_bo_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'breakindentopt' option is changed.
+const char *did_set_breakindentopt(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
if (briopt_check(win) == FAIL) {
- *errmsg = e_invarg;
+ return e_invarg;
}
// list setting requires a redraw
if (win == curwin && win->w_briopt_list) {
redraw_all_later(UPD_NOT_VALID);
}
+
+ return NULL;
}
-static void did_set_isopt(buf_T *buf, bool *did_chartab, char **errmsg)
+int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matches)
{
- // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[]
- // If the new option is invalid, use old value. 'lisp' option: refill
- // g_chartab[] for '-' char
- if (buf_init_chartab(buf, true) == FAIL) {
- *did_chartab = true; // need to restore it below
- *errmsg = e_invarg; // error in value
+ return expand_set_opt_string(args,
+ p_briopt_values,
+ ARRAY_SIZE(p_briopt_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'bufhidden' option is changed.
+const char *did_set_bufhidden(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ return did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false);
+}
+
+int expand_set_bufhidden(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bufhidden_values,
+ ARRAY_SIZE(p_bufhidden_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'buftype' option is changed.
+const char *did_set_buftype(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ // When 'buftype' is set, check for valid value.
+ if ((buf->terminal && buf->b_p_bt[0] != 't')
+ || (!buf->terminal && buf->b_p_bt[0] == 't')
+ || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) {
+ return e_invarg;
}
+ if (win->w_status_height || global_stl_height()) {
+ win->w_redr_status = true;
+ redraw_later(win, UPD_VALID);
+ }
+ buf->b_help = (buf->b_p_bt[0] == 'h');
+ redraw_titles();
+ return NULL;
}
-static void did_set_helpfile(void)
+int expand_set_buftype(optexpand_T *args, int *numMatches, char ***matches)
{
- // May compute new values for $VIM and $VIMRUNTIME
- if (didset_vim) {
- vim_unsetenv_ext("VIM");
+ return expand_set_opt_string(args,
+ p_buftype_values,
+ ARRAY_SIZE(p_buftype_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'casemap' option is changed.
+const char *did_set_casemap(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true);
+}
+
+int expand_set_casemap(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_cmp_values,
+ ARRAY_SIZE(p_cmp_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The global 'listchars' or 'fillchars' option is changed.
+static const char *did_set_global_listfillchars(win_T *win, char *val, bool opt_lcs, int opt_flags)
+{
+ const char *errmsg = NULL;
+ char **local_ptr = opt_lcs ? &win->w_p_lcs : &win->w_p_fcs;
+
+ // only apply the global value to "win" when it does not have a
+ // local value
+ if (opt_lcs) {
+ errmsg = set_listchars_option(win, val, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL));
+ } else {
+ errmsg = set_fillchars_option(win, val, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL));
}
- if (didset_vimruntime) {
- vim_unsetenv_ext("VIMRUNTIME");
+ if (errmsg != NULL) {
+ return errmsg;
+ }
+
+ // If the current window is set to use the global
+ // 'listchars'/'fillchars' value, clear the window-local value.
+ if (!(opt_flags & OPT_GLOBAL)) {
+ clear_string_option(local_ptr);
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ // If the current window has a local value need to apply it
+ // again, it was changed when setting the global value.
+ // If no error was returned above, we don't expect an error
+ // here, so ignore the return value.
+ if (opt_lcs) {
+ if (*wp->w_p_lcs == NUL) {
+ (void)set_listchars_option(wp, wp->w_p_lcs, true);
+ }
+ } else {
+ if (*wp->w_p_fcs == NUL) {
+ (void)set_fillchars_option(wp, wp->w_p_fcs, true);
+ }
+ }
}
+
+ redraw_all_later(UPD_NOT_VALID);
+
+ return NULL;
}
-static void did_set_cursorlineopt(win_T *win, char **varp, char **errmsg)
+/// The 'fillchars' option or the 'listchars' option is changed.
+const char *did_set_chars_option(optset_T *args)
{
- if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) {
- *errmsg = e_invarg;
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ const char *errmsg = NULL;
+
+ if (varp == &p_lcs // global 'listchars'
+ || varp == &p_fcs) { // global 'fillchars'
+ errmsg = did_set_global_listfillchars(win, *varp, varp == &p_lcs, args->os_flags);
+ } else if (varp == &win->w_p_lcs) { // local 'listchars'
+ errmsg = set_listchars_option(win, *varp, true);
+ } else if (varp == &win->w_p_fcs) { // local 'fillchars'
+ errmsg = set_fillchars_option(win, *varp, true);
}
+
+ return errmsg;
}
-static void did_set_helplang(char **errmsg)
+/// Expand 'fillchars' or 'listchars' option value.
+int expand_set_chars_option(optexpand_T *args, int *numMatches, char ***matches)
{
- // Check for "", "ab", "ab,cd", etc.
- for (char *s = p_hlg; *s != NUL; s += 3) {
- if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) {
- *errmsg = e_invarg;
- break;
+ char **varp = (char **)args->oe_varp;
+ bool is_lcs = (varp == &p_lcs || varp == &curwin->w_p_lcs);
+ return expand_set_opt_generic(args,
+ is_lcs ? get_listchars_name : get_fillchars_name,
+ numMatches,
+ matches);
+}
+
+/// The 'cinoptions' option is changed.
+const char *did_set_cinoptions(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // TODO(vim): recognize errors
+ parse_cino(curbuf);
+
+ return NULL;
+}
+
+/// The 'clipboard' option is changed.
+const char *did_set_clipboard(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true);
+}
+
+int expand_set_clipboard(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_cb_values,
+ ARRAY_SIZE(p_cb_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'colorcolumn' option is changed.
+const char *did_set_colorcolumn(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ return check_colorcolumn(win);
+}
+
+/// The 'comments' option is changed.
+const char *did_set_comments(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+ char *errmsg = NULL;
+ for (char *s = *varp; *s;) {
+ while (*s && *s != ':') {
+ if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL
+ && !ascii_isdigit(*s) && *s != '-') {
+ errmsg = illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s));
+ break;
+ }
+ s++;
}
- if (s[2] == NUL) {
+ if (*s++ == NUL) {
+ errmsg = N_("E524: Missing colon");
+ } else if (*s == ',' || *s == NUL) {
+ errmsg = N_("E525: Zero length string");
+ }
+ if (errmsg != NULL) {
break;
}
+ while (*s && *s != ',') {
+ if (*s == '\\' && s[1] != NUL) {
+ s++;
+ }
+ s++;
+ }
+ s = skip_to_option_part(s);
}
+ return errmsg;
}
-static void did_set_highlight(char **varp, char **errmsg)
+/// The 'commentstring' option is changed.
+const char *did_set_commentstring(optset_T *args)
{
- if (strcmp(*varp, HIGHLIGHT_INIT) != 0) {
- *errmsg = e_unsupportedoption;
+ char **varp = (char **)args->os_varp;
+
+ if (**varp != NUL && strstr(*varp, "%s") == NULL) {
+ return N_("E537: 'commentstring' must be empty or contain %s");
}
+ return NULL;
}
-static void did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list, char **errmsg)
+/// The 'complete' option is changed.
+const char *did_set_complete(optset_T *args)
{
- if (opt_strings_flags(val, values, flagp, list) != OK) {
- *errmsg = e_invarg;
+ char **varp = (char **)args->os_varp;
+
+ // check if it is a valid value for 'complete' -- Acevedo
+ for (char *s = *varp; *s;) {
+ while (*s == ',' || *s == ' ') {
+ s++;
+ }
+ if (!*s) {
+ break;
+ }
+ if (vim_strchr(".wbuksid]tUf", (uint8_t)(*s)) == NULL) {
+ return illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s));
+ }
+ if (*++s != NUL && *s != ',' && *s != ' ') {
+ if (s[-1] == 'k' || s[-1] == 's') {
+ // skip optional filename after 'k' and 's'
+ while (*s && *s != ',' && *s != ' ') {
+ if (*s == '\\' && s[1] != NUL) {
+ s++;
+ }
+ s++;
+ }
+ } else {
+ if (args->os_errbuf != NULL) {
+ vim_snprintf(args->os_errbuf, args->os_errbuflen,
+ _("E535: Illegal character after <%c>"),
+ *--s);
+ return args->os_errbuf;
+ }
+ return "";
+ }
+ }
}
+ return NULL;
}
-static void did_set_opt_strings(char *val, char **values, bool list, char **errmsg)
+int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches)
{
- did_set_opt_flags(val, values, NULL, list, errmsg);
+ static char *(p_cpt_values[]) = {
+ ".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U", "f", NULL
+ };
+ return expand_set_opt_string(args,
+ p_cpt_values,
+ ARRAY_SIZE(p_cpt_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_sessionoptions(char *oldval, char **errmsg)
+/// The 'completeopt' option is changed.
+const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
{
- if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) {
- *errmsg = e_invarg;
- }
- if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) {
- // Don't allow both "sesdir" and "curdir".
- (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true);
- *errmsg = e_invarg;
+ if (check_opt_strings(p_cot, p_cot_values, true) != OK) {
+ return e_invarg;
}
+ completeopt_was_set();
+ return NULL;
}
-static void did_set_ambiwidth(char **errmsg)
+int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches)
{
- if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) {
- *errmsg = e_invarg;
- } else {
- *errmsg = check_chars_options();
- }
+ return expand_set_opt_string(args,
+ p_cot_values,
+ ARRAY_SIZE(p_cot_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_background(char **errmsg)
+#ifdef BACKSLASH_IN_FILENAME
+/// The 'completeslash' option is changed.
+const char *did_set_completeslash(optset_T *args)
{
- if (check_opt_strings(p_bg, p_bg_values, false) != OK) {
- *errmsg = e_invarg;
- return;
+ buf_T *buf = (buf_T *)args->os_buf;
+ if (check_opt_strings(p_csl, p_csl_values, false) != OK
+ || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) {
+ return e_invarg;
}
+ return NULL;
+}
- int dark = (*p_bg == 'd');
+int expand_set_completeslash(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_csl_values,
+ ARRAY_SIZE(p_csl_values) - 1,
+ numMatches,
+ matches);
+}
+#endif
- init_highlight(false, false);
+/// The 'concealcursor' option is changed.
+const char *did_set_concealcursor(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
- if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) {
- // The color scheme must have set 'background' back to another
- // value, that's not what we want here. Disable the color
- // scheme and set the colors again.
- do_unlet(S_LEN("g:colors_name"), true);
- free_string_option(p_bg);
- p_bg = xstrdup((dark ? "dark" : "light"));
- check_string_option(&p_bg);
- init_highlight(false, false);
+ return did_set_option_listflag(*varp, COCU_ALL, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_concealcursor(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, COCU_ALL, numMatches, matches);
+}
+
+/// The 'cpoptions' option is changed.
+const char *did_set_cpoptions(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, CPO_VI, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_cpoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, CPO_VI, numMatches, matches);
+}
+
+/// The 'cursorlineopt' option is changed.
+const char *did_set_cursorlineopt(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+
+ // This could be changed to use opt_strings_flags() instead.
+ if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) {
+ return e_invarg;
}
+
+ return NULL;
}
-static void did_set_wildmode(char **errmsg)
+int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches)
{
- if (check_opt_wim() == FAIL) {
- *errmsg = e_invarg;
+ return expand_set_opt_string(args,
+ p_culopt_values,
+ ARRAY_SIZE(p_culopt_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'debug' option is changed.
+const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_debug, p_debug_values, false);
+}
+
+int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_debug_values,
+ ARRAY_SIZE(p_debug_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'diffopt' option is changed.
+const char *did_set_diffopt(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (diffopt_changed() == FAIL) {
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_winaltkeys(char **errmsg)
+int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches)
{
- if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) {
- *errmsg = e_invarg;
+ expand_T *xp = args->oe_xp;
+
+ if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern - 1) == ':') {
+ // Within "algorithm:", we have a subgroup of possible options.
+ const size_t algo_len = strlen("algorithm:");
+ if (xp->xp_pattern - args->oe_set_arg >= (int)algo_len
+ && strncmp(xp->xp_pattern - algo_len, "algorithm:", algo_len) == 0) {
+ return expand_set_opt_string(args,
+ p_dip_algorithm_values,
+ ARRAY_SIZE(p_dip_algorithm_values) - 1,
+ numMatches,
+ matches);
+ }
+ return FAIL;
}
+
+ return expand_set_opt_string(args,
+ p_dip_values,
+ ARRAY_SIZE(p_dip_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_eventignore(char **errmsg)
+/// The 'display' option is changed.
+const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED)
{
- if (check_ei() == FAIL) {
- *errmsg = e_invarg;
+ if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) {
+ return e_invarg;
}
+ (void)init_chartab();
+ msg_grid_validate();
+ return NULL;
+}
+
+int expand_set_display(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_dy_values,
+ ARRAY_SIZE(p_dy_values) - 1,
+ numMatches,
+ matches);
}
-// 'encoding', 'fileencoding' and 'makeencoding'
-static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flags, char **errmsg)
+/// The 'eadirection' option is changed.
+const char *did_set_eadirection(optset_T *args FUNC_ATTR_UNUSED)
{
+ return did_set_opt_strings(p_ead, p_ead_values, false);
+}
+
+int expand_set_eadirection(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_ead_values,
+ ARRAY_SIZE(p_ead_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// One of the 'encoding', 'fileencoding' or 'makeencoding'
+/// options is changed.
+const char *did_set_encoding(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
+ int opt_flags = args->os_flags;
+ // Get the global option to compare with, otherwise we would have to check
+ // two values for all local options.
+ char **gvarp = (char **)get_option_varp_scope_from(args->os_idx, OPT_GLOBAL, buf, NULL);
+
if (gvarp == &p_fenc) {
if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) {
- *errmsg = e_modifiable;
- return;
+ return e_modifiable;
}
if (vim_strchr(*varp, ',') != NULL) {
// No comma allowed in 'fileencoding'; catches confusing it
// with 'fileencodings'.
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
// May show a "+" in the title now.
@@ -854,19 +1293,347 @@ static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flag
if (varp == &p_enc) {
// only encoding=utf-8 allowed
if (strcmp(p_enc, "utf-8") != 0) {
- *errmsg = e_unsupportedoption;
- return;
+ return e_unsupportedoption;
}
spell_reload();
}
+ return NULL;
+}
+
+int expand_set_encoding(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_generic(args, get_encoding_name, numMatches, matches);
+}
+
+/// The 'eventignore' option is changed.
+const char *did_set_eventignore(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_ei() == FAIL) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+static char *get_eventignore_name(expand_T *xp, int idx)
+{
+ // 'eventignore' allows special keyword "all" in addition to
+ // all event names.
+ if (idx == 0) {
+ return "all";
+ }
+ return get_event_name_no_group(xp, idx - 1);
+}
+
+int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_generic(args, get_eventignore_name, numMatches, matches);
+}
+
+/// The 'fileformat' option is changed.
+const char *did_set_fileformat(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
+ const char *oldval = args->os_oldval.string.data;
+ int opt_flags = args->os_flags;
+ if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) {
+ return e_modifiable;
+ } else if (check_opt_strings(*varp, p_ff_values, false) != OK) {
+ return e_invarg;
+ }
+ redraw_titles();
+ // update flag in swap file
+ ml_setflags(buf);
+ // Redraw needed when switching to/from "mac": a CR in the text
+ // will be displayed differently.
+ if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') {
+ redraw_buf_later(buf, UPD_NOT_VALID);
+ }
+ return NULL;
+}
+
+int expand_set_fileformat(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_ff_values,
+ ARRAY_SIZE(p_ff_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// Function given to ExpandGeneric() to obtain the possible arguments of the
+/// fileformat options.
+char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(p_ff_values)) {
+ return NULL;
+ }
+
+ return p_ff_values[idx];
+}
+
+/// The 'fileformats' option is changed.
+const char *did_set_fileformats(optset_T *args)
+{
+ return did_set_opt_strings(p_ffs, p_ff_values, true);
}
-static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_checked,
- char **errmsg)
+/// The 'filetype' or the 'syntax' option is changed.
+const char *did_set_filetype_or_syntax(optset_T *args)
{
+ char **varp = (char **)args->os_varp;
+
if (!valid_filetype(*varp)) {
- *errmsg = e_invarg;
- return;
+ return e_invarg;
+ }
+
+ args->os_value_changed = strcmp(args->os_oldval.string.data, *varp) != 0;
+
+ // Since we check the value, there is no need to set P_INSECURE,
+ // even when the value comes from a modeline.
+ args->os_value_checked = true;
+
+ return NULL;
+}
+
+/// The 'foldclose' option is changed.
+const char *did_set_foldclose(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_fcl, p_fcl_values, true);
+}
+
+int expand_set_foldclose(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fcl_values,
+ ARRAY_SIZE(p_fcl_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'foldcolumn' option is changed.
+const char *did_set_foldcolumn(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+ if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fdc_values,
+ ARRAY_SIZE(p_fdc_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'foldexpr' option is changed.
+const char *did_set_foldexpr(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ (void)did_set_optexpr(args);
+ if (foldmethodIsExpr(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// The 'foldignore' option is changed.
+const char *did_set_foldignore(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (foldmethodIsIndent(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// The 'foldmarker' option is changed.
+const char *did_set_foldmarker(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ char *p = vim_strchr(*varp, ',');
+
+ if (p == NULL) {
+ return e_comma_required;
+ }
+
+ if (p == *varp || p[1] == NUL) {
+ return e_invarg;
+ }
+
+ if (foldmethodIsMarker(win)) {
+ foldUpdateAll(win);
+ }
+
+ return NULL;
+}
+
+/// The 'foldmethod' option is changed.
+const char *did_set_foldmethod(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ if (check_opt_strings(*varp, p_fdm_values, false) != OK
+ || *win->w_p_fdm == NUL) {
+ return e_invarg;
+ }
+ foldUpdateAll(win);
+ if (foldmethodIsDiff(win)) {
+ newFoldLevel();
+ }
+ return NULL;
+}
+
+int expand_set_foldmethod(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fdm_values,
+ ARRAY_SIZE(p_fdm_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'foldopen' option is changed.
+const char *did_set_foldopen(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true);
+}
+
+int expand_set_foldopen(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fdo_values,
+ ARRAY_SIZE(p_fdo_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'formatoptions' option is changed.
+const char *did_set_formatoptions(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, FO_ALL, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_formatoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, FO_ALL, numMatches, matches);
+}
+
+/// The 'guicursor' option is changed.
+const char *did_set_guicursor(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return parse_shape_opt(SHAPE_CURSOR);
+}
+
+/// The 'helpfile' option is changed.
+const char *did_set_helpfile(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // May compute new values for $VIM and $VIMRUNTIME
+ if (didset_vim) {
+ vim_unsetenv_ext("VIM");
+ }
+ if (didset_vimruntime) {
+ vim_unsetenv_ext("VIMRUNTIME");
+ }
+ return NULL;
+}
+
+/// The 'helplang' option is changed.
+const char *did_set_helplang(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // Check for "", "ab", "ab,cd", etc.
+ for (char *s = p_hlg; *s != NUL; s += 3) {
+ if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) {
+ return e_invarg;
+ }
+ if (s[2] == NUL) {
+ break;
+ }
+ }
+ return NULL;
+}
+
+/// The 'highlight' option is changed.
+const char *did_set_highlight(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ if (strcmp(*varp, HIGHLIGHT_INIT) != 0) {
+ return e_unsupportedoption;
+ }
+ return NULL;
+}
+
+/// The 'iconstring' option is changed.
+const char *did_set_iconstring(optset_T *args)
+{
+ return did_set_titleiconstring(args, STL_IN_ICON);
+}
+
+/// The 'inccommand' option is changed.
+const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (cmdpreview) {
+ return e_invarg;
+ }
+ return did_set_opt_strings(p_icm, p_icm_values, false);
+}
+
+int expand_set_inccommand(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_icm_values,
+ ARRAY_SIZE(p_icm_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'isident' or the 'iskeyword' or the 'isprint' or the 'isfname' option is
+/// changed.
+const char *did_set_isopt(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[]
+ // If the new option is invalid, use old value.
+ // 'lisp' option: refill g_chartab[] for '-' char
+ if (buf_init_chartab(buf, true) == FAIL) {
+ args->os_restore_chartab = true; // need to restore it below
+ return e_invarg; // error in value
+ }
+ return NULL;
+}
+
+/// The 'jumpoptions' option is changed.
+const char *did_set_jumpoptions(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true);
+}
+
+int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_jop_values,
+ ARRAY_SIZE(p_jop_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'keymap' option has changed.
+const char *did_set_keymap(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
+ int opt_flags = args->os_flags;
+
+ if (!valid_filetype(*varp)) {
+ return e_invarg;
}
int secure_save = secure;
@@ -876,15 +1643,15 @@ static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_ch
secure = 0;
// load or unload key mapping tables
- *errmsg = keymap_init();
+ const char *errmsg = keymap_init();
secure = secure_save;
// Since we check the value, there is no need to set P_INSECURE,
// even when the value comes from a modeline.
- *value_checked = true;
+ args->os_value_checked = true;
- if (*errmsg == NULL) {
+ if (errmsg == NULL) {
if (*buf->b_p_keymap != NUL) {
// Installed a new keymap, switch on using it.
buf->b_p_iminsert = B_IMODE_LMAP;
@@ -906,29 +1673,56 @@ static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_ch
}
status_redraw_buf(buf);
}
+
+ return errmsg;
}
-static void did_set_fileformat(buf_T *buf, char **varp, const char *oldval, int opt_flags,
- char **errmsg)
+/// The 'keymodel' option is changed.
+const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED)
{
- if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) {
- *errmsg = e_modifiable;
- } else if (check_opt_strings(*varp, p_ff_values, false) != OK) {
- *errmsg = e_invarg;
- } else {
- redraw_titles();
- // update flag in swap file
- ml_setflags(buf);
- // Redraw needed when switching to/from "mac": a CR in the text
- // will be displayed differently.
- if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') {
- redraw_buf_later(buf, UPD_NOT_VALID);
- }
+ if (check_opt_strings(p_km, p_km_values, true) != OK) {
+ return e_invarg;
+ }
+ km_stopsel = (vim_strchr(p_km, 'o') != NULL);
+ km_startsel = (vim_strchr(p_km, 'a') != NULL);
+ return NULL;
+}
+
+int expand_set_keymodel(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_km_values,
+ ARRAY_SIZE(p_km_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'lispoptions' option is changed.
+const char *did_set_lispoptions(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) {
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_matchpairs(char **varp, char **errmsg)
+int expand_set_lispoptions(optexpand_T *args, int *numMatches, char ***matches)
{
+ static char *(p_lop_values[]) = { "expr:0", "expr:1", NULL };
+ return expand_set_opt_string(args,
+ p_lop_values,
+ ARRAY_SIZE(p_lop_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'matchpairs' option is changed.
+const char *did_set_matchpairs(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
for (char *p = *varp; *p != NUL; p++) {
int x2 = -1;
int x3 = -1;
@@ -942,95 +1736,276 @@ static void did_set_matchpairs(char **varp, char **errmsg)
p += utfc_ptr2len(p);
}
if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) {
- *errmsg = e_invarg;
- break;
+ return e_invarg;
}
if (*p == NUL) {
break;
}
}
+ return NULL;
}
-static void did_set_comments(char **varp, char *errbuf, size_t errbuflen, char **errmsg)
+/// The 'mkspellmem' option is changed.
+const char *did_set_mkspellmem(optset_T *args FUNC_ATTR_UNUSED)
{
- for (char *s = *varp; *s;) {
- while (*s && *s != ':') {
- if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL
- && !ascii_isdigit(*s) && *s != '-') {
- *errmsg = illegal_char(errbuf, errbuflen, *s);
- break;
- }
- s++;
+ if (spell_check_msm() != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+/// The 'mouse' option is changed.
+const char *did_set_mouse(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, MOUSE_ALL, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_mouse(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, MOUSE_ALL, numMatches, matches);
+}
+
+/// The 'mousemodel' option is changed.
+const char *did_set_mousemodel(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_mousem, p_mousem_values, false);
+}
+
+int expand_set_mousemodel(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_mousem_values,
+ ARRAY_SIZE(p_mousem_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// Handle setting 'mousescroll'.
+/// @return error message, NULL if it's OK.
+const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED)
+{
+ OptInt vertical = -1;
+ OptInt horizontal = -1;
+
+ char *string = p_mousescroll;
+
+ while (true) {
+ char *end = vim_strchr(string, ',');
+ size_t length = end ? (size_t)(end - string) : strlen(string);
+
+ // Both "ver:" and "hor:" are 4 bytes long.
+ // They should be followed by at least one digit.
+ if (length <= 4) {
+ return e_invarg;
}
- if (*s++ == NUL) {
- *errmsg = N_("E524: Missing colon");
- } else if (*s == ',' || *s == NUL) {
- *errmsg = N_("E525: Zero length string");
+
+ OptInt *direction;
+
+ if (memcmp(string, "ver:", 4) == 0) {
+ direction = &vertical;
+ } else if (memcmp(string, "hor:", 4) == 0) {
+ direction = &horizontal;
+ } else {
+ return e_invarg;
}
- if (*errmsg != NULL) {
- break;
+
+ // If the direction has already been set, this is a duplicate.
+ if (*direction != -1) {
+ return e_invarg;
}
- while (*s && *s != ',') {
- if (*s == '\\' && s[1] != NUL) {
- s++;
+
+ // Verify that only digits follow the colon.
+ for (size_t i = 4; i < length; i++) {
+ if (!ascii_isdigit(string[i])) {
+ return N_("E5080: Digit expected");
}
- s++;
}
- s = skip_to_option_part(s);
+
+ string += 4;
+ *direction = getdigits_int(&string, false, -1);
+
+ // Num options are generally kept within the signed int range.
+ // We know this number won't be negative because we've already checked for
+ // a minus sign. We'll allow 0 as a means of disabling mouse scrolling.
+ if (*direction == -1) {
+ return e_invarg;
+ }
+
+ if (!end) {
+ break;
+ }
+
+ string = end + 1;
}
+
+ // If a direction wasn't set, fallback to the default value.
+ p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical;
+ p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal;
+
+ return NULL;
}
-static void did_set_global_listfillchars(win_T *win, char **varp, int opt_flags, char **errmsg)
+int expand_set_mousescroll(optexpand_T *args, int *numMatches, char ***matches)
{
- char **local_ptr = varp == &p_lcs ? &win->w_p_lcs : &win->w_p_fcs;
- // only apply the global value to "win" when it does not have a local value
- *errmsg = set_chars_option(win, varp, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL));
- if (*errmsg == NULL) {
- // If the current window is set to use the global
- // 'listchars'/'fillchars' value, clear the window-local value.
- if (!(opt_flags & OPT_GLOBAL)) {
- clear_string_option(local_ptr);
- }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- // If the current window has a local value need to apply it
- // again, it was changed when setting the global value.
- // If no error was returned above, we don't expect an error
- // here, so ignore the return value.
- local_ptr = varp == &p_lcs ? &wp->w_p_lcs : &wp->w_p_fcs;
- if (**local_ptr == NUL) {
- (void)set_chars_option(wp, local_ptr, true);
- }
- }
- redraw_all_later(UPD_NOT_VALID);
+ static char *(p_mousescroll_values[]) = { "hor:", "ver:", NULL };
+ return expand_set_opt_string(args,
+ p_mousescroll_values,
+ ARRAY_SIZE(p_mousescroll_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'nrformats' option is changed.
+const char *did_set_nrformats(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_opt_strings(*varp, p_nf_values, true);
+}
+
+int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_nf_values,
+ ARRAY_SIZE(p_nf_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext',
+/// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'.
+const char *did_set_optexpr(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ // If the option value starts with <SID> or s:, then replace that with
+ // the script identifier.
+ char *name = get_scriptlocal_funcname(*varp);
+ if (name != NULL) {
+ free_string_option(*varp);
+ *varp = name;
}
+ return NULL;
}
-static void did_set_verbosefile(char **errmsg)
+/// The 'redrawdebug' option is changed.
+const char *did_set_redrawdebug(optset_T *args FUNC_ATTR_UNUSED)
{
- verbose_stop();
- if (*p_vfile != NUL && verbose_open() == FAIL) {
- *errmsg = e_invarg;
+ return did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true);
+}
+
+/// The 'rightleftcmd' option is changed.
+const char *did_set_rightleftcmd(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ // Currently only "search" is a supported value.
+ if (**varp != NUL && strcmp(*varp, "search") != 0) {
+ return e_invarg;
+ }
+
+ return NULL;
+}
+
+int expand_set_rightleftcmd(optexpand_T *args, int *numMatches, char ***matches)
+{
+ static char *(p_rlc_values[]) = { "search", NULL };
+ return expand_set_opt_string(args,
+ p_rlc_values,
+ ARRAY_SIZE(p_rlc_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'rulerformat' option is changed.
+const char *did_set_rulerformat(optset_T *args)
+{
+ return did_set_statustabline_rulerformat(args, true, false);
+}
+
+/// The 'scrollopt' option is changed.
+const char *did_set_scrollopt(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_sbo, p_scbopt_values, true);
+}
+
+int expand_set_scrollopt(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_scbopt_values,
+ ARRAY_SIZE(p_scbopt_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'selection' option is changed.
+const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+int expand_set_selection(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_sel_values,
+ ARRAY_SIZE(p_sel_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'selectmode' option is changed.
+const char *did_set_selectmode(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_slm, p_slm_values, true);
+}
+
+int expand_set_selectmode(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_slm_values,
+ ARRAY_SIZE(p_slm_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'sessionoptions' option is changed.
+const char *did_set_sessionoptions(optset_T *args)
+{
+ if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) {
+ return e_invarg;
}
+ if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) {
+ // Don't allow both "sesdir" and "curdir".
+ const char *oldval = args->os_oldval.string.data;
+ (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true);
+ return e_invarg;
+ }
+ return NULL;
}
-static void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, char *errbuf,
- size_t errbuflen, char **errmsg)
+int expand_set_sessionoptions(optexpand_T *args, int *numMatches, char ***matches)
{
- // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo
- // option.
- *opt_idx = (((*opt)->fullname[0] == 'v')
- ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx)
- : *opt_idx);
- *opt = get_option(*opt_idx);
- // Update free_oldval now that we have the opt_idx for 'shada', otherwise
- // there would be a disconnect between the check for P_ALLOCED at the start
- // of the function and the set of P_ALLOCED at the end of the function.
- *free_oldval = ((*opt)->flags & P_ALLOCED);
+ return expand_set_opt_string(args,
+ p_ssop_values,
+ ARRAY_SIZE(p_ssop_values) - 1,
+ numMatches,
+ matches);
+}
+
+const char *did_set_shada(optset_T *args)
+{
+ char *errbuf = args->os_errbuf;
+ size_t errbuflen = args->os_errbuflen;
+
for (char *s = p_shada; *s;) {
// Check it's a valid character
if (vim_strchr("!\"%'/:<@cfhnrs", (uint8_t)(*s)) == NULL) {
- *errmsg = illegal_char(errbuf, errbuflen, *s);
- break;
+ return illegal_char(errbuf, errbuflen, (uint8_t)(*s));
}
if (*s == 'n') { // name is always last one
break;
@@ -1049,280 +2024,261 @@ static void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, ch
vim_snprintf(errbuf, errbuflen,
_("E526: Missing number after <%s>"),
transchar_byte((uint8_t)(*(s - 1))));
- *errmsg = errbuf;
+ return errbuf;
} else {
- *errmsg = "";
+ return "";
}
- break;
}
}
if (*s == ',') {
s++;
} else if (*s) {
if (errbuf != NULL) {
- *errmsg = N_("E527: Missing comma");
+ return N_("E527: Missing comma");
} else {
- *errmsg = "";
+ return "";
}
- break;
}
}
- if (*p_shada && *errmsg == NULL && get_shada_parameter('\'') < 0) {
- *errmsg = N_("E528: Must specify a ' value");
+ if (*p_shada && get_shada_parameter('\'') < 0) {
+ return N_("E528: Must specify a ' value");
}
+ return NULL;
+}
+
+/// The 'shortmess' option is changed.
+const char *did_set_shortmess(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, SHM_ALL, args->os_errbuf, args->os_errbuflen);
}
-static void did_set_showbreak(char **varp, char **errmsg)
+int expand_set_shortmess(optexpand_T *args, int *numMatches, char ***matches)
{
+ return expand_set_opt_listflag(args, SHM_ALL, numMatches, matches);
+}
+
+/// The 'showbreak' option is changed.
+const char *did_set_showbreak(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
for (char *s = *varp; *s;) {
if (ptr2cells(s) != 1) {
- *errmsg = e_showbreak_contains_unprintable_or_wide_character;
+ return e_showbreak_contains_unprintable_or_wide_character;
}
MB_PTR_ADV(s);
}
+ return NULL;
}
-static void did_set_titleiconstring(char **varp)
+/// The 'showcmdloc' option is changed.
+const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED)
{
- // 'titlestring' and 'iconstring'
- int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON;
+ return did_set_opt_strings(p_sloc, p_sloc_values, true);
+}
- // NULL => statusline syntax
- if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) {
- stl_syntax |= flagval;
- } else {
- stl_syntax &= ~flagval;
- }
- did_set_title();
+int expand_set_showcmdloc(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_sloc_values,
+ ARRAY_SIZE(p_sloc_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_selection(char **errmsg)
+/// The 'signcolumn' option is changed.
+const char *did_set_signcolumn(optset_T *args)
{
- if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) {
- *errmsg = e_invarg;
+ win_T *win = (win_T *)args->os_win;
+ const char *oldval = args->os_oldval.string.data;
+ if (check_signcolumn(win) != OK) {
+ return e_invarg;
}
+ // When changing the 'signcolumn' to or from 'number', recompute the
+ // width of the number column if 'number' or 'relativenumber' is set.
+ if ((*oldval == 'n' && *(oldval + 1) == 'u') || win->w_minscwidth == SCL_NUM) {
+ win->w_nrwidth_line_count = 0;
+ }
+ return NULL;
}
-static void did_set_keymodel(char **errmsg)
+int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches)
{
- if (check_opt_strings(p_km, p_km_values, true) != OK) {
- *errmsg = e_invarg;
- return;
- }
- km_stopsel = (vim_strchr(p_km, 'o') != NULL);
- km_startsel = (vim_strchr(p_km, 'a') != NULL);
+ return expand_set_opt_string(args,
+ p_scl_values,
+ ARRAY_SIZE(p_scl_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_display(char **errmsg)
+/// The 'spellcapcheck' option is changed.
+const char *did_set_spellcapcheck(optset_T *args)
{
- if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) {
- *errmsg = e_invarg;
- return;
- }
- (void)init_chartab();
- msg_grid_validate();
+ win_T *win = (win_T *)args->os_win;
+ // When 'spellcapcheck' is set compile the regexp program.
+ return compile_cap_prog(win->w_s);
}
-static void did_set_spellfile(char **varp, char **errmsg)
+/// The 'spellfile' option is changed.
+const char *did_set_spellfile(optset_T *args)
{
+ char **varp = (char **)args->os_varp;
+
// When there is a window for this buffer in which 'spell'
// is set load the wordlists.
-
if ((!valid_spellfile(*varp))) {
- *errmsg = e_invarg;
- } else {
- *errmsg = did_set_spell_option(true);
+ return e_invarg;
}
+ return did_set_spell_option(true);
}
-static void did_set_spell(char **varp, char **errmsg)
+/// The 'spelllang' option is changed.
+const char *did_set_spelllang(optset_T *args)
{
+ char **varp = (char **)args->os_varp;
+
// When there is a window for this buffer in which 'spell'
// is set load the wordlists.
if (!valid_spelllang(*varp)) {
- *errmsg = e_invarg;
- } else {
- *errmsg = did_set_spell_option(false);
+ return e_invarg;
}
+ return did_set_spell_option(false);
}
-static void did_set_spellcapcheck(win_T *win, char **errmsg)
-{
- // When 'spellcapcheck' is set compile the regexp program.
- *errmsg = compile_cap_prog(win->w_s);
-}
-
-static void did_set_spelloptions(win_T *win, char **errmsg)
+/// The 'spelloptions' option is changed.
+const char *did_set_spelloptions(optset_T *args)
{
+ win_T *win = (win_T *)args->os_win;
if (opt_strings_flags(win->w_s->b_p_spo, p_spo_values, &(win->w_s->b_p_spo_flags),
true) != OK) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
+}
+
+int expand_set_spelloptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_spo_values,
+ ARRAY_SIZE(p_spo_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_spellsuggest(char **errmsg)
+/// The 'spellsuggest' option is changed.
+const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED)
{
if (spell_check_sps() != OK) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_mkspellmem(char **errmsg)
+int expand_set_spellsuggest(optexpand_T *args, int *numMatches, char ***matches)
{
- if (spell_check_msm() != OK) {
- *errmsg = e_invarg;
- }
+ return expand_set_opt_string(args,
+ p_sps_values,
+ ARRAY_SIZE(p_sps_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_buftype(buf_T *buf, win_T *win, char **errmsg)
+/// The 'splitkeep' option is changed.
+const char *did_set_splitkeep(optset_T *args FUNC_ATTR_UNUSED)
{
- // When 'buftype' is set, check for valid value.
- if ((buf->terminal && buf->b_p_bt[0] != 't')
- || (!buf->terminal && buf->b_p_bt[0] == 't')
- || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) {
- *errmsg = e_invarg;
- } else {
- if (win->w_status_height || global_stl_height()) {
- win->w_redr_status = true;
- redraw_later(win, UPD_VALID);
- }
- buf->b_help = (buf->b_p_bt[0] == 'h');
- redraw_titles();
- }
+ return did_set_opt_strings(p_spk, p_spk_values, false);
}
-// 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn'
-static void did_set_statusline(win_T *win, char **varp, char **gvarp, char **errmsg)
+int expand_set_splitkeep(optexpand_T *args, int *numMatches, char ***matches)
{
- if (varp == &p_ruf) { // reset ru_wid first
+ return expand_set_opt_string(args,
+ p_spk_values,
+ ARRAY_SIZE(p_spk_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'statuscolumn' option is changed.
+const char *did_set_statuscolumn(optset_T *args)
+{
+ return did_set_statustabline_rulerformat(args, false, true);
+}
+
+/// The 'statusline' option is changed.
+const char *did_set_statusline(optset_T *args)
+{
+ return did_set_statustabline_rulerformat(args, false, false);
+}
+
+/// The 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' option is changed.
+///
+/// @param rulerformat true if the 'rulerformat' option is changed
+/// @param statuscolumn true if the 'statuscolumn' option is changed
+static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerformat,
+ bool statuscolumn)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ if (rulerformat) { // reset ru_wid first
ru_wid = 0;
- } else if (varp == &win->w_p_stc) {
+ } else if (statuscolumn) {
+ // reset 'statuscolumn' width
win->w_nrwidth_line_count = 0;
}
+ const char *errmsg = NULL;
char *s = *varp;
- if (varp == &p_ruf && *s == '%') {
+ if (rulerformat && *s == '%') {
// set ru_wid if 'ruf' starts with "%99("
if (*++s == '-') { // ignore a '-'
s++;
}
int wid = getdigits_int(&s, true, 0);
- if (wid && *s == '(' && (*errmsg = check_stl_option(p_ruf)) == NULL) {
+ if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) {
ru_wid = wid;
} else {
- *errmsg = check_stl_option(p_ruf);
+ errmsg = check_stl_option(p_ruf);
}
- } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') {
+ } else if (rulerformat || s[0] != '%' || s[1] != '!') {
// check 'statusline', 'winbar', 'tabline' or 'statuscolumn'
// only if it doesn't start with "%!"
- *errmsg = check_stl_option(s);
+ errmsg = check_stl_option(s);
}
- if (varp == &p_ruf && *errmsg == NULL) {
+ if (rulerformat && errmsg == NULL) {
comp_col();
}
- // add / remove window bars for 'winbar'
- if (gvarp == &p_wbr) {
- set_winbar(true);
- }
-}
-
-static void did_set_complete(char **varp, char *errbuf, size_t errbuflen, char **errmsg)
-{
- // check if it is a valid value for 'complete' -- Acevedo
- for (char *s = *varp; *s;) {
- while (*s == ',' || *s == ' ') {
- s++;
- }
- if (!*s) {
- break;
- }
- if (vim_strchr(".wbuksid]tU", (uint8_t)(*s)) == NULL) {
- *errmsg = illegal_char(errbuf, errbuflen, *s);
- break;
- }
- if (*++s != NUL && *s != ',' && *s != ' ') {
- if (s[-1] == 'k' || s[-1] == 's') {
- // skip optional filename after 'k' and 's'
- while (*s && *s != ',' && *s != ' ') {
- if (*s == '\\' && s[1] != NUL) {
- s++;
- }
- s++;
- }
- } else {
- if (errbuf != NULL) {
- vim_snprintf(errbuf, errbuflen,
- _("E535: Illegal character after <%c>"),
- *--s);
- *errmsg = errbuf;
- } else {
- *errmsg = "";
- }
- break;
- }
- }
- }
-}
-
-static void did_set_completeopt(char **errmsg)
-{
- if (check_opt_strings(p_cot, p_cot_values, true) != OK) {
- *errmsg = e_invarg;
- } else {
- completeopt_was_set();
- }
+ return errmsg;
}
-static void did_set_signcolumn(win_T *win, char **varp, const char *oldval, char **errmsg)
+/// The 'switchbuf' option is changed.
+const char *did_set_switchbuf(optset_T *args FUNC_ATTR_UNUSED)
{
- if (check_signcolumn(*varp) != OK) {
- *errmsg = e_invarg;
- }
- // When changing the 'signcolumn' to or from 'number', recompute the
- // width of the number column if 'number' or 'relativenumber' is set.
- if (((*oldval == 'n' && *(oldval + 1) == 'u')
- || (*win->w_p_scl == 'n' && *(win->w_p_scl + 1) == 'u'))
- && (win->w_p_nu || win->w_p_rnu)) {
- win->w_nrwidth_line_count = 0;
- }
+ return did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true);
}
-static void did_set_foldcolumn(char **varp, char **errmsg)
+int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches)
{
- if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) {
- *errmsg = e_invarg;
- }
+ return expand_set_opt_string(args,
+ p_swb_values,
+ ARRAY_SIZE(p_swb_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_pastetoggle(void)
+/// The 'tabline' option is changed.
+const char *did_set_tabline(optset_T *args)
{
- // 'pastetoggle': translate key codes like in a mapping
- if (*p_pt) {
- char *p = NULL;
- (void)replace_termcodes(p_pt,
- strlen(p_pt),
- &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL,
- CPO_TO_CPO_FLAGS);
- if (p != NULL) {
- free_string_option(p_pt);
- p_pt = p;
- }
- }
+ return did_set_statustabline_rulerformat(args, false, false);
}
-static void did_set_backspace(char **errmsg)
+/// The 'tagcase' option is changed.
+const char *did_set_tagcase(optset_T *args)
{
- if (ascii_isdigit(*p_bs)) {
- if (*p_bs > '3' || p_bs[1] != NUL) {
- *errmsg = e_invarg;
- }
- } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) {
- *errmsg = e_invarg;
- }
-}
+ buf_T *buf = (buf_T *)args->os_buf;
+ int opt_flags = args->os_flags;
-static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg)
-{
- unsigned int *flags;
+ unsigned *flags;
char *p;
if (opt_flags & OPT_LOCAL) {
@@ -1338,115 +2294,66 @@ static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg)
*flags = 0;
} else if (*p == NUL
|| opt_strings_flags(p, p_tc_values, flags, false) != OK) {
- *errmsg = e_invarg;
- }
-}
-
-static void did_set_diffopt(char **errmsg)
-{
- if (diffopt_changed() == FAIL) {
- *errmsg = e_invarg;
- }
-}
-
-static void did_set_foldmethod(win_T *win, char **varp, char **errmsg)
-{
- if (check_opt_strings(*varp, p_fdm_values, false) != OK
- || *win->w_p_fdm == NUL) {
- *errmsg = e_invarg;
- } else {
- foldUpdateAll(win);
- if (foldmethodIsDiff(win)) {
- newFoldLevel();
- }
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_foldmarker(win_T *win, char **varp, char **errmsg)
+int expand_set_tagcase(optexpand_T *args, int *numMatches, char ***matches)
{
- char *p = vim_strchr(*varp, ',');
- if (p == NULL) {
- *errmsg = N_("E536: comma required");
- } else if (p == *varp || p[1] == NUL) {
- *errmsg = e_invarg;
- } else if (foldmethodIsMarker(win)) {
- foldUpdateAll(win);
- }
+ return expand_set_opt_string(args,
+ p_tc_values,
+ ARRAY_SIZE(p_tc_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_commentstring(char **varp, char **errmsg)
+/// The 'termpastefilter' option is changed.
+const char *did_set_termpastefilter(optset_T *args FUNC_ATTR_UNUSED)
{
- if (**varp != NUL && strstr(*varp, "%s") == NULL) {
- *errmsg = N_("E537: 'commentstring' must be empty or contain %s");
- }
+ return did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true);
}
-static void did_set_foldignore(win_T *win)
+int expand_set_termpastefilter(optexpand_T *args, int *numMatches, char ***matches)
{
- if (foldmethodIsIndent(win)) {
- foldUpdateAll(win);
- }
+ return expand_set_opt_string(args,
+ p_tpf_values,
+ ARRAY_SIZE(p_tpf_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_virtualedit(win_T *win, int opt_flags, char *oldval, char **errmsg)
+/// The 'titlestring' or the 'iconstring' option is changed.
+static const char *did_set_titleiconstring(optset_T *args, int flagval)
{
- char *ve = p_ve;
- unsigned int *flags = &ve_flags;
-
- if (opt_flags & OPT_LOCAL) {
- ve = win->w_p_ve;
- flags = &win->w_ve_flags;
- }
+ char **varp = (char **)args->os_varp;
- if ((opt_flags & OPT_LOCAL) && *ve == NUL) {
- // make the local value empty: use the global value
- *flags = 0;
+ // NULL => statusline syntax
+ if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) {
+ stl_syntax |= flagval;
} else {
- if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) {
- *errmsg = e_invarg;
- } else if (strcmp(p_ve, oldval) != 0) {
- // Recompute cursor position in case the new 've' setting
- // changes something.
- validate_virtcol_win(win);
- coladvance(win->w_virtcol);
- }
+ stl_syntax &= ~flagval;
}
-}
+ did_set_title();
-static void did_set_lispoptions(char **varp, char **errmsg)
-{
- if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) {
- *errmsg = e_invarg;
- }
+ return NULL;
}
-static void did_set_filetype_or_syntax(char **varp, char *oldval, int *value_checked,
- bool *value_changed, char **errmsg)
+/// The 'titlestring' option is changed.
+const char *did_set_titlestring(optset_T *args)
{
- if (!valid_filetype(*varp)) {
- *errmsg = e_invarg;
- return;
- }
-
- *value_changed = strcmp(oldval, *varp) != 0;
-
- // Since we check the value, there is no need to set P_INSECURE,
- // even when the value comes from a modeline.
- *value_checked = true;
+ return did_set_titleiconstring(args, STL_IN_TITLE);
}
-static void did_set_winhl(win_T *win, char **errmsg)
+/// The 'varsofttabstop' option is changed.
+const char *did_set_varsofttabstop(optset_T *args)
{
- if (!parse_winhl_opt(win)) {
- *errmsg = e_invarg;
- }
-}
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
-static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg)
-{
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
XFREE_CLEAR(buf->b_p_vsts_array);
- return;
+ return NULL;
}
for (char *cp = *varp; *cp; cp++) {
@@ -1456,23 +2363,28 @@ static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg)
if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
continue;
}
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
- long *oldarray = buf->b_p_vsts_array;
+ colnr_T *oldarray = buf->b_p_vsts_array;
if (tabstop_set(*varp, &(buf->b_p_vsts_array))) {
xfree(oldarray);
} else {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errmsg)
+/// The 'varstabstop' option is changed.
+const char *did_set_vartabstop(optset_T *args)
{
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
XFREE_CLEAR(buf->b_p_vts_array);
- return;
+ return NULL;
}
for (char *cp = *varp; *cp; cp++) {
@@ -1482,441 +2394,161 @@ static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errms
if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
continue;
}
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
- long *oldarray = buf->b_p_vts_array;
+ colnr_T *oldarray = buf->b_p_vts_array;
if (tabstop_set(*varp, &(buf->b_p_vts_array))) {
xfree(oldarray);
if (foldmethodIsIndent(win)) {
foldUpdateAll(win);
}
} else {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_optexpr(win_T *win, char **p_opt, char **varp, char **gvarp)
+/// The 'verbosefile' option is changed.
+const char *did_set_verbosefile(optset_T *args)
{
- char *name = get_scriptlocal_funcname(*p_opt);
- if (name != NULL) {
- free_string_option(*p_opt);
- *p_opt = name;
+ verbose_stop();
+ if (*p_vfile != NUL && verbose_open() == FAIL) {
+ return (char *)e_invarg;
}
+ return NULL;
}
-// handle option that is a list of flags.
-static void did_set_option_listflag(char **varp, char *flags, char *errbuf, size_t errbuflen,
- char **errmsg)
+/// The 'viewoptions' option is changed.
+const char *did_set_viewoptions(optset_T *args FUNC_ATTR_UNUSED)
{
- for (char *s = *varp; *s; s++) {
- if (vim_strchr(flags, *s) == NULL) {
- *errmsg = illegal_char(errbuf, errbuflen, *s);
- break;
- }
- }
+ return did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true);
}
-// When 'syntax' is set, load the syntax of that name
-static void do_syntax_autocmd(buf_T *buf, bool value_changed)
+/// The 'virtualedit' option is changed.
+const char *did_set_virtualedit(optset_T *args)
{
- static int syn_recursive = 0;
+ win_T *win = (win_T *)args->os_win;
- syn_recursive++;
- // Only pass true for "force" when the value changed or not used
- // recursively, to avoid endless recurrence.
- apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname,
- value_changed || syn_recursive == 1, buf);
- buf->b_flags |= BF_SYN_SET;
- syn_recursive--;
-}
-
-static void do_filetype_autocmd(buf_T *buf, char **varp, int opt_flags, bool value_changed)
-{
- // 'filetype' is set, trigger the FileType autocommand
- // Skip this when called from a modeline and the filetype was
- // already set to this value.
- if (!(opt_flags & OPT_MODELINE) || value_changed) {
- static int ft_recursive = 0;
- int secure_save = secure;
+ char *ve = p_ve;
+ unsigned *flags = &ve_flags;
- // Reset the secure flag, since the value of 'filetype' has
- // been checked to be safe.
- secure = 0;
+ if (args->os_flags & OPT_LOCAL) {
+ ve = win->w_p_ve;
+ flags = &win->w_ve_flags;
+ }
- ft_recursive++;
- did_filetype = true;
- // Only pass true for "force" when the value changed or not
- // used recursively, to avoid endless recurrence.
- apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname,
- value_changed || ft_recursive == 1, buf);
- ft_recursive--;
- // Just in case the old "buf" is now invalid
- if (varp != &(buf->b_p_ft)) {
- varp = NULL;
+ if ((args->os_flags & OPT_LOCAL) && *ve == NUL) {
+ // make the local value empty: use the global value
+ *flags = 0;
+ } else {
+ if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) {
+ return e_invarg;
+ } else if (strcmp(ve, args->os_oldval.string.data) != 0) {
+ // Recompute cursor position in case the new 've' setting
+ // changes something.
+ validate_virtcol_win(win);
+ // XXX: this only works when win == curwin
+ coladvance(win->w_virtcol);
}
- secure = secure_save;
}
+ return NULL;
}
-static void did_set_spelllang_source(win_T *win)
+int expand_set_virtualedit(optexpand_T *args, int *numMatches, char ***matches)
{
- char fname[200];
- char *q = win->w_s->b_p_spl;
+ return expand_set_opt_string(args,
+ p_ve_values,
+ ARRAY_SIZE(p_ve_values) - 1,
+ numMatches,
+ matches);
+}
- // Skip the first name if it is "cjk".
- if (strncmp(q, "cjk,", 4) == 0) {
- q += 4;
- }
+/// The 'whichwrap' option is changed.
+const char *did_set_whichwrap(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
- // Source the spell/LANG.vim in 'runtimepath'.
- // They could set 'spellcapcheck' depending on the language.
- // Use the first name in 'spelllang' up to '_region' or
- // '.encoding'.
- char *p;
- for (p = q; *p != NUL; p++) {
- if (!ASCII_ISALNUM(*p) && *p != '-') {
- break;
- }
- }
- if (p > q) {
- vim_snprintf(fname, sizeof(fname), "spell/%.*s.vim", (int)(p - q), q);
- source_runtime(fname, DIP_ALL);
- }
+ // Add ',' to the list flags because 'whichwrap' is a flag
+ // list that is comma-separated.
+ return did_set_option_listflag(*varp, WW_ALL ",", args->os_errbuf, args->os_errbuflen);
}
-/// Handle string options that need some action to perform when changed.
-/// The new value must be allocated.
-///
-/// @param opt_idx index in options[] table
-/// @param varp pointer to the option variable
-/// @param oldval previous value of the option
-/// @param errbuf buffer for errors, or NULL
-/// @param errbuflen length of errors buffer
-/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL
-/// @param value_checked value was checked to be safe, no need to set P_INSECURE
-///
-/// @return NULL for success, or an untranslated error message for an error
-static char *did_set_string_option_for(buf_T *buf, win_T *win, int opt_idx, char **varp,
- char *oldval, char *errbuf, size_t errbuflen, int opt_flags,
- int *value_checked)
+int expand_set_whichwrap(optexpand_T *args, int *numMatches, char ***matches)
{
- char *errmsg = NULL;
- bool did_chartab = false;
- vimoption_T *opt = get_option(opt_idx);
- bool free_oldval = (opt->flags & P_ALLOCED);
- bool value_changed = false;
+ return expand_set_opt_listflag(args, WW_ALL, numMatches, matches);
+}
- // Get the global option to compare with, otherwise we would have to check
- // two values for all local options.
- char **gvarp = (char **)get_varp_scope(opt, OPT_GLOBAL);
-
- // Disallow changing some options from secure mode
- if ((secure || sandbox != 0)
- && (opt->flags & P_SECURE)) {
- errmsg = e_secure;
- } else if (check_illegal_path_names(*varp, opt->flags)) {
- // Check for a "normal" directory or file name in some options.
- errmsg = e_invarg;
- } else if (gvarp == &p_bkc) { // 'backupcopy'
- did_set_backupcopy(buf, oldval, opt_flags, &errmsg);
- } else if (varp == &p_bex || varp == &p_pm) { // 'backupext' and 'patchmode'
- did_set_backupext_or_patchmode(&errmsg);
- } else if (varp == &win->w_p_briopt) { // 'breakindentopt'
- did_set_breakindentopt(win, &errmsg);
- } else if (varp == &p_isi
- || varp == &buf->b_p_isk
- || varp == &p_isp
- || varp == &p_isf) {
- // 'isident', 'iskeyword', 'isprint or 'isfname' option
- did_set_isopt(buf, &did_chartab, &errmsg);
- } else if (varp == &p_hf) { // 'helpfile'
- did_set_helpfile();
- } else if (varp == &p_rtp || varp == &p_pp) { // 'runtimepath' 'packpath'
- runtime_search_path_invalidate();
- } else if (varp == &win->w_p_culopt
- || gvarp == &win->w_allbuf_opt.wo_culopt) { // 'cursorlineopt'
- did_set_cursorlineopt(win, varp, &errmsg);
- } else if (varp == &win->w_p_cc) { // 'colorcolumn'
- errmsg = check_colorcolumn(win);
- } else if (varp == &p_hlg) { // 'helplang'
- did_set_helplang(&errmsg);
- } else if (varp == &p_hl) { // 'highlight'
- did_set_highlight(varp, &errmsg);
- } else if (varp == &p_jop) { // 'jumpoptions'
- did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true, &errmsg);
- } else if (gvarp == &p_nf) { // 'nrformats'
- did_set_opt_strings(*varp, p_nf_values, true, &errmsg);
- } else if (varp == &p_ssop) { // 'sessionoptions'
- did_set_sessionoptions(oldval, &errmsg);
- } else if (varp == &p_vop) { // 'viewoptions'
- did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true, &errmsg);
- } else if (varp == &p_rdb) { // 'redrawdebug'
- did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true, &errmsg);
- } else if (varp == &p_sbo) { // 'scrollopt'
- did_set_opt_strings(p_sbo, p_scbopt_values, true, &errmsg);
- } else if (varp == &p_ambw || (int *)varp == &p_emoji) { // 'ambiwidth'
- did_set_ambiwidth(&errmsg);
- } else if (varp == &p_bg) { // 'background'
- did_set_background(&errmsg);
- } else if (varp == &p_wim) { // 'wildmode'
- did_set_wildmode(&errmsg);
- } else if (varp == &p_wop) { // 'wildoptions'
- did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true, &errmsg);
- } else if (varp == &p_wak) { // 'winaltkeys'
- did_set_winaltkeys(&errmsg);
- } else if (varp == &p_ei) { // 'eventignore'
- did_set_eventignore(&errmsg);
- } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) {
- // 'encoding', 'fileencoding' and 'makeencoding'
- did_set_encoding(buf, varp, gvarp, opt_flags, &errmsg);
- } else if (varp == &buf->b_p_keymap) {
- did_set_keymap(buf, varp, opt_flags, value_checked, &errmsg);
- } else if (gvarp == &p_ff) { // 'fileformat'
- did_set_fileformat(buf, varp, oldval, opt_flags, &errmsg);
- } else if (varp == &p_ffs) { // 'fileformats'
- did_set_opt_strings(p_ffs, p_ff_values, true, &errmsg);
- } else if (gvarp == &p_mps) { // 'matchpairs'
- did_set_matchpairs(varp, &errmsg);
- } else if (gvarp == &p_com) { // 'comments'
- did_set_comments(varp, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_lcs || varp == &p_fcs) { // global 'listchars' or 'fillchars'
- did_set_global_listfillchars(win, varp, opt_flags, &errmsg);
- } else if (varp == &win->w_p_lcs) { // local 'listchars'
- errmsg = set_chars_option(win, varp, true);
- } else if (varp == &win->w_p_fcs) { // local 'fillchars'
- errmsg = set_chars_option(win, varp, true);
- } else if (varp == &p_cedit) { // 'cedit'
- errmsg = check_cedit();
- } else if (varp == &p_vfile) { // 'verbosefile'
- did_set_verbosefile(&errmsg);
- } else if (varp == &p_shada) { // 'shada'
- did_set_shada(&opt, &opt_idx, &free_oldval, errbuf, errbuflen, &errmsg);
- } else if (gvarp == &p_sbr) { // 'showbreak'
- did_set_showbreak(varp, &errmsg);
- } else if (varp == &p_guicursor) { // 'guicursor'
- errmsg = parse_shape_opt(SHAPE_CURSOR);
- } else if (varp == &p_langmap) { // 'langmap'
- langmap_set();
- } else if (varp == &p_breakat) { // 'breakat'
- fill_breakat_flags();
- } else if (varp == &p_titlestring || varp == &p_iconstring) {
- // 'titlestring' and 'iconstring'
- did_set_titleiconstring(varp);
- } else if (varp == &p_sel) { // 'selection'
- did_set_selection(&errmsg);
- } else if (varp == &p_slm) { // 'selectmode'
- did_set_opt_strings(p_slm, p_slm_values, true, &errmsg);
- } else if (varp == &p_km) { // 'keymodel'
- did_set_keymodel(&errmsg);
- } else if (varp == &p_mousem) { // 'mousemodel'
- did_set_opt_strings(p_mousem, p_mousem_values, false, &errmsg);
- } else if (varp == &p_mousescroll) { // 'mousescroll'
- errmsg = check_mousescroll(p_mousescroll);
- } else if (varp == &p_swb) { // 'switchbuf'
- did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true, &errmsg);
- } else if (varp == &p_spk) { // 'splitkeep'
- did_set_opt_strings(p_spk, p_spk_values, false, &errmsg);
- } else if (varp == &p_debug) { // 'debug'
- did_set_opt_strings(p_debug, p_debug_values, true, &errmsg);
- } else if (varp == &p_dy) { // 'display'
- did_set_display(&errmsg);
- } else if (varp == &p_ead) { // 'eadirection'
- did_set_opt_strings(p_ead, p_ead_values, false, &errmsg);
- } else if (varp == &p_cb) { // 'clipboard'
- did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true, &errmsg);
- } else if (varp == &win->w_s->b_p_spf) {
- did_set_spellfile(varp, &errmsg);
- } else if (varp == &win->w_s->b_p_spl) { // 'spell'
- did_set_spell(varp, &errmsg);
- } else if (varp == &win->w_s->b_p_spc) {
- did_set_spellcapcheck(win, &errmsg);
- } else if (varp == &win->w_s->b_p_spo) { // 'spelloptions'
- did_set_spelloptions(win, &errmsg);
- } else if (varp == &p_sps) { // 'spellsuggest'
- did_set_spellsuggest(&errmsg);
- } else if (varp == &p_msm) { // 'mkspellmem'
- did_set_mkspellmem(&errmsg);
- } else if (gvarp == &p_bh) {
- did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false, &errmsg);
- } else if (gvarp == &p_bt) { // 'buftype'
- did_set_buftype(buf, win, &errmsg);
- } else if (gvarp == &p_stl || gvarp == &p_wbr || varp == &p_tal
- || varp == &p_ruf || varp == &win->w_p_stc) {
- // 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn'
- did_set_statusline(win, varp, gvarp, &errmsg);
- } else if (gvarp == &p_cpt) { // 'complete'
- did_set_complete(varp, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_cot) { // 'completeopt'
- did_set_completeopt(&errmsg);
-#ifdef BACKSLASH_IN_FILENAME
- } else if (gvarp == &p_csl) { // 'completeslash'
- if (check_opt_strings(p_csl, p_csl_values, false) != OK
- || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) {
- errmsg = e_invarg;
- }
-#endif
- } else if (varp == &win->w_p_scl) { // 'signcolumn'
- did_set_signcolumn(win, varp, oldval, &errmsg);
- } else if (varp == &p_sloc) { // 'showcmdloc'
- did_set_opt_strings(*varp, p_sloc_values, false, &errmsg);
- } else if (varp == &win->w_p_fdc
- || varp == &win->w_allbuf_opt.wo_fdc) {
- // 'foldcolumn'
- did_set_foldcolumn(varp, &errmsg);
- } else if (varp == &p_pt) { // 'pastetoggle'
- did_set_pastetoggle();
- } else if (varp == &p_bs) { // 'backspace'
- did_set_backspace(&errmsg);
- } else if (varp == &p_bo) {
- did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true, &errmsg);
- } else if (gvarp == &p_tc) { // 'tagcase'
- did_set_tagcase(buf, opt_flags, &errmsg);
- } else if (varp == &p_cmp) { // 'casemap'
- did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true, &errmsg);
- } else if (varp == &p_dip) { // 'diffopt'
- did_set_diffopt(&errmsg);
- } else if (gvarp == &win->w_allbuf_opt.wo_fdm) { // 'foldmethod'
- did_set_foldmethod(win, varp, &errmsg);
- } else if (gvarp == &win->w_allbuf_opt.wo_fmr) { // 'foldmarker'
- did_set_foldmarker(win, varp, &errmsg);
- } else if (gvarp == &p_cms) { // 'commentstring'
- did_set_commentstring(varp, &errmsg);
- } else if (varp == &p_fdo) { // 'foldopen'
- did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true, &errmsg);
- } else if (varp == &p_fcl) { // 'foldclose'
- did_set_opt_strings(*varp, p_fcl_values, true, &errmsg);
- } else if (gvarp == &win->w_allbuf_opt.wo_fdi) { // 'foldignore'
- did_set_foldignore(win);
- } else if (gvarp == &p_ve) { // 'virtualedit'
- did_set_virtualedit(win, opt_flags, oldval, &errmsg);
- } else if (gvarp == &p_cino) { // 'cinoptions'
- // TODO(vim): recognize errors
- parse_cino(buf);
- } else if (gvarp == &p_lop) { // 'lispoptions'
- did_set_lispoptions(varp, &errmsg);
- } else if (varp == &p_icm) { // 'inccommand'
- did_set_opt_strings(*varp, p_icm_values, false, &errmsg);
- } else if (gvarp == &p_ft || gvarp == &p_syn) {
- did_set_filetype_or_syntax(varp, oldval, value_checked, &value_changed, &errmsg);
- } else if (varp == &win->w_p_winhl) {
- did_set_winhl(win, &errmsg);
- } else if (varp == &p_tpf) {
- did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true, &errmsg);
- } else if (varp == &buf->b_p_vsts) { // 'varsofttabstop'
- did_set_varsoftabstop(buf, varp, &errmsg);
- } else if (varp == &buf->b_p_vts) { // 'vartabstop'
- did_set_vartabstop(buf, win, varp, &errmsg);
- } else if (varp == &p_dex) { // 'diffexpr'
- did_set_optexpr(win, &p_dex, varp, gvarp);
- } else if (varp == &win->w_p_fde) { // 'foldexpr'
- did_set_optexpr(win, &win->w_p_fde, varp, gvarp);
- if (foldmethodIsExpr(win)) {
- foldUpdateAll(win);
- }
- } else if (varp == &win->w_p_fdt) { // 'foldtext'
- did_set_optexpr(win, &win->w_p_fdt, varp, gvarp);
- } else if (varp == &p_pex) { // 'patchexpr'
- did_set_optexpr(win, &p_pex, varp, gvarp);
- } else if (gvarp == &p_fex) { // 'formatexpr'
- did_set_optexpr(win, &buf->b_p_fex, varp, gvarp);
- } else if (gvarp == &p_inex) { // 'includeexpr'
- did_set_optexpr(win, &buf->b_p_inex, varp, gvarp);
- } else if (gvarp == &p_inde) { // 'indentexpr'
- did_set_optexpr(win, &buf->b_p_inde, varp, gvarp);
- } else if (gvarp == &p_cfu) { // 'completefunc'
- set_completefunc_option(&errmsg);
- } else if (gvarp == &p_ofu) { // 'omnifunc'
- set_omnifunc_option(buf, &errmsg);
- } else if (gvarp == &p_tsrfu) { // 'thesaurusfunc'
- set_thesaurusfunc_option(&errmsg);
- } else if (varp == &p_opfunc) { // 'operatorfunc'
- set_operatorfunc_option(&errmsg);
- } else if (varp == &p_qftf) { // 'quickfixtextfunc'
- qf_process_qftf_option(&errmsg);
- } else if (gvarp == &p_tfu) { // 'tagfunc'
- set_tagfunc_option(&errmsg);
- } else if (varp == &p_ww) { // 'whichwrap'
- did_set_option_listflag(varp, WW_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_shm) { // 'shortmess'
- did_set_option_listflag(varp, SHM_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_cpo) { // 'cpoptions'
- did_set_option_listflag(varp, CPO_VI, errbuf, errbuflen, &errmsg);
- } else if (varp == &buf->b_p_fo) { // 'formatoptions'
- did_set_option_listflag(varp, FO_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &win->w_p_cocu) { // 'concealcursor'
- did_set_option_listflag(varp, COCU_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_mouse) { // 'mouse'
- did_set_option_listflag(varp, MOUSE_ALL, errbuf, errbuflen, &errmsg);
- } else if (gvarp == &p_flp) {
- if (win->w_briopt_list) {
- // Changing Formatlistpattern when briopt includes the list setting:
- // redraw
- redraw_all_later(UPD_NOT_VALID);
- }
- }
-
- // If error detected, restore the previous value.
- if (errmsg != NULL) {
- free_string_option(*varp);
- *varp = oldval;
- // When resetting some values, need to act on it.
- if (did_chartab) {
- (void)buf_init_chartab(buf, true);
- }
- } else {
- // Remember where the option was set.
- set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
- // Free string options that are in allocated memory.
- // Use "free_oldval", because recursiveness may change the flags under
- // our fingers (esp. init_highlight()).
- if (free_oldval) {
- free_string_option(oldval);
- }
- opt->flags |= P_ALLOCED;
+/// The 'wildmode' option is changed.
+const char *did_set_wildmode(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_opt_wim() == FAIL) {
+ return e_invarg;
+ }
+ return NULL;
+}
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0
- && (opt->indir & PV_BOTH)) {
- // global option with local value set to use global value; free
- // the local value and make it empty
- char *p = get_varp_scope(opt, OPT_LOCAL);
- free_string_option(*(char **)p);
- *(char **)p = empty_option;
- } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) {
- // May set global value for local option.
- set_string_option_global(opt, varp);
- }
+int expand_set_wildmode(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_wim_values,
+ ARRAY_SIZE(p_wim_values) - 1,
+ numMatches,
+ matches);
+}
- // Trigger the autocommand only after setting the flags.
- if (varp == &buf->b_p_syn) {
- do_syntax_autocmd(buf, value_changed);
- } else if (varp == &buf->b_p_ft) {
- do_filetype_autocmd(buf, varp, opt_flags, value_changed);
- } else if (varp == &win->w_s->b_p_spl) {
- did_set_spelllang_source(win);
- }
- }
+/// The 'wildoptions' option is changed.
+const char *did_set_wildoptions(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true);
+}
- if (varp == &p_mouse) {
- setmouse(); // in case 'mouse' changed
- }
+int expand_set_wildoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_wop_values,
+ ARRAY_SIZE(p_wop_values) - 1,
+ numMatches,
+ matches);
+}
- if (win->w_curswant != MAXCOL
- && (opt->flags & (P_CURSWANT | P_RALL)) != 0) {
- win->w_set_curswant = true;
+/// The 'winaltkeys' option is changed.
+const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) {
+ return e_invarg;
}
+ return NULL;
+}
- check_redraw_for(buf, win, opt->flags);
+int expand_set_winaltkeys(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_wak_values,
+ ARRAY_SIZE(p_wak_values) - 1,
+ numMatches,
+ matches);
+}
- return errmsg;
+/// The 'winbar' option is changed.
+const char *did_set_winbar(optset_T *args)
+{
+ return did_set_statustabline_rulerformat(args, false, false);
+}
+
+/// The 'winhighlight' option is changed.
+const char *did_set_winhighlight(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (!parse_winhl_opt(win)) {
+ return e_invarg;
+ }
+ return NULL;
}
-char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf, size_t errbuflen,
- int opt_flags, int *value_checked)
+int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches)
{
- return did_set_string_option_for(curbuf, curwin, opt_idx, varp, oldval, errbuf, errbuflen,
- opt_flags, value_checked);
+ return expand_set_opt_generic(args, get_highlight_name, numMatches, matches);
}
/// Check an option that can be a range of string values.
@@ -1937,12 +2569,12 @@ static int check_opt_strings(char *val, char **values, int list)
/// @param list when true: accept a list of values
///
/// @return OK for correct value, FAIL otherwise. Empty is always OK.
-static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool list)
+static int opt_strings_flags(const char *val, char **values, unsigned *flagp, bool list)
{
- unsigned int new_flags = 0;
+ unsigned new_flags = 0;
while (*val) {
- for (unsigned int i = 0;; i++) {
+ for (unsigned i = 0;; i++) {
if (values[i] == NULL) { // val not found in values[]
return FAIL;
}
@@ -1951,7 +2583,7 @@ static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool lis
if (strncmp(values[i], val, len) == 0
&& ((list && val[len] == ',') || val[len] == NUL)) {
val += len + (val[len] == ',');
- assert(i < sizeof(1U) * 8);
+ assert(i < sizeof(new_flags) * 8);
new_flags |= (1U << i);
break; // check next item in val list
}
@@ -1969,3 +2601,335 @@ int check_ff_value(char *p)
{
return check_opt_strings(p, p_ff_values, false);
}
+
+static const char e_conflicts_with_value_of_listchars[]
+ = N_("E834: Conflicts with value of 'listchars'");
+static const char e_conflicts_with_value_of_fillchars[]
+ = N_("E835: Conflicts with value of 'fillchars'");
+
+/// Calls mb_cptr2char_adv(p) and returns the character.
+/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used.
+/// Returns 0 for invalid hex or invalid UTF-8 byte.
+static int get_encoded_char_adv(const char **p)
+{
+ const char *s = *p;
+
+ if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) {
+ int64_t num = 0;
+ for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) {
+ *p += 2;
+ int n = hexhex2nr(*p);
+ if (n < 0) {
+ return 0;
+ }
+ num = num * 256 + n;
+ }
+ *p += 2;
+ return (int)num;
+ }
+
+ // TODO(bfredl): use schar_T representation and utfc_ptr2len
+ int clen = utf_ptr2len(s);
+ int c = mb_cptr2char_adv(p);
+ if (clen == 1 && c > 127) { // Invalid UTF-8 byte
+ return 0;
+ }
+ return c;
+}
+
+struct chars_tab {
+ int *cp; ///< char value
+ const char *name; ///< char id
+ int def; ///< default value
+ int fallback; ///< default value when "def" isn't single-width
+};
+
+static fcs_chars_T fcs_chars;
+static const struct chars_tab fcs_tab[] = {
+ { &fcs_chars.stl, "stl", ' ', NUL },
+ { &fcs_chars.stlnc, "stlnc", ' ', NUL },
+ { &fcs_chars.wbr, "wbr", ' ', NUL },
+ { &fcs_chars.horiz, "horiz", 0x2500, '-' }, // ─
+ { &fcs_chars.horizup, "horizup", 0x2534, '-' }, // â”´
+ { &fcs_chars.horizdown, "horizdown", 0x252c, '-' }, // ┬
+ { &fcs_chars.vert, "vert", 0x2502, '|' }, // │
+ { &fcs_chars.vertleft, "vertleft", 0x2524, '|' }, // ┤
+ { &fcs_chars.vertright, "vertright", 0x251c, '|' }, // ├
+ { &fcs_chars.verthoriz, "verthoriz", 0x253c, '+' }, // ┼
+ { &fcs_chars.fold, "fold", 0x00b7, '-' }, // ·
+ { &fcs_chars.foldopen, "foldopen", '-', NUL },
+ { &fcs_chars.foldclosed, "foldclose", '+', NUL },
+ { &fcs_chars.foldsep, "foldsep", 0x2502, '|' }, // │
+ { &fcs_chars.diff, "diff", '-', NUL },
+ { &fcs_chars.msgsep, "msgsep", ' ', NUL },
+ { &fcs_chars.eob, "eob", '~', NUL },
+ { &fcs_chars.lastline, "lastline", '@', NUL },
+};
+
+static lcs_chars_T lcs_chars;
+static const struct chars_tab lcs_tab[] = {
+ { &lcs_chars.eol, "eol", NUL, NUL },
+ { &lcs_chars.ext, "extends", NUL, NUL },
+ { &lcs_chars.nbsp, "nbsp", NUL, NUL },
+ { &lcs_chars.prec, "precedes", NUL, NUL },
+ { &lcs_chars.space, "space", NUL, NUL },
+ { &lcs_chars.tab2, "tab", NUL, NUL },
+ { &lcs_chars.lead, "lead", NUL, NUL },
+ { &lcs_chars.trail, "trail", NUL, NUL },
+ { &lcs_chars.conceal, "conceal", NUL, NUL },
+ { NULL, "multispace", NUL, NUL },
+ { NULL, "leadmultispace", NUL, NUL },
+};
+
+/// Handle setting 'listchars' or 'fillchars'.
+/// Assume monocell characters
+///
+/// @param value points to either the global or the window-local value.
+/// @param is_listchars is true for "listchars" and false for "fillchars".
+/// @param apply if false, do not store the flags, only check for errors.
+/// @return error message, NULL if it's OK.
+static const char *set_chars_option(win_T *wp, const char *value, const bool is_listchars,
+ const bool apply)
+{
+ const char *last_multispace = NULL; // Last occurrence of "multispace:"
+ const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:"
+ int multispace_len = 0; // Length of lcs-multispace string
+ int lead_multispace_len = 0; // Length of lcs-leadmultispace string
+
+ const struct chars_tab *tab;
+ int entries;
+ if (is_listchars) {
+ tab = lcs_tab;
+ entries = ARRAY_SIZE(lcs_tab);
+ if (wp->w_p_lcs[0] == NUL) {
+ value = p_lcs; // local value is empty, use the global value
+ }
+ } else {
+ tab = fcs_tab;
+ entries = ARRAY_SIZE(fcs_tab);
+ if (wp->w_p_fcs[0] == NUL) {
+ value = p_fcs; // local value is empty, use the global value
+ }
+ }
+
+ // first round: check for valid value, second round: assign values
+ for (int round = 0; round <= (apply ? 1 : 0); round++) {
+ if (round > 0) {
+ // After checking that the value is valid: set defaults
+ for (int i = 0; i < entries; i++) {
+ if (tab[i].cp != NULL) {
+ // XXX: Characters taking 2 columns is forbidden (TUI limitation?).
+ // Set old defaults in this case.
+ *(tab[i].cp) = char2cells(tab[i].def) == 1 ? tab[i].def : tab[i].fallback;
+ }
+ }
+
+ if (is_listchars) {
+ lcs_chars.tab1 = NUL;
+ lcs_chars.tab3 = NUL;
+
+ if (multispace_len > 0) {
+ lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int));
+ lcs_chars.multispace[multispace_len] = NUL;
+ } else {
+ lcs_chars.multispace = NULL;
+ }
+
+ if (lead_multispace_len > 0) {
+ lcs_chars.leadmultispace = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int));
+ lcs_chars.leadmultispace[lead_multispace_len] = NUL;
+ } else {
+ lcs_chars.leadmultispace = NULL;
+ }
+ }
+ }
+
+ const char *p = value;
+ while (*p) {
+ int i;
+ for (i = 0; i < entries; i++) {
+ const size_t len = strlen(tab[i].name);
+ if (!(strncmp(p, tab[i].name, len) == 0
+ && p[len] == ':'
+ && p[len + 1] != NUL)) {
+ continue;
+ }
+
+ if (is_listchars && strcmp(tab[i].name, "multispace") == 0) {
+ const char *s = p + len + 1;
+ if (round == 0) {
+ // Get length of lcs-multispace string in the first round
+ last_multispace = p;
+ multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ multispace_len++;
+ }
+ if (multispace_len == 0) {
+ // lcs-multispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (p == last_multispace) {
+ lcs_chars.multispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
+ break;
+ }
+
+ if (is_listchars && strcmp(tab[i].name, "leadmultispace") == 0) {
+ const char *s = p + len + 1;
+ if (round == 0) {
+ // get length of lcs-leadmultispace string in first round
+ last_lmultispace = p;
+ lead_multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ lead_multispace_len++;
+ }
+ if (lead_multispace_len == 0) {
+ // lcs-leadmultispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (p == last_lmultispace) {
+ lcs_chars.leadmultispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
+ break;
+ }
+
+ const char *s = p + len + 1;
+ int c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ int c2 = 0, c3 = 0;
+ if (tab[i].cp == &lcs_chars.tab2) {
+ if (*s == NUL) {
+ return e_invarg;
+ }
+ c2 = get_encoded_char_adv(&s);
+ if (c2 == 0 || char2cells(c2) > 1) {
+ return e_invarg;
+ }
+ if (!(*s == ',' || *s == NUL)) {
+ c3 = get_encoded_char_adv(&s);
+ if (c3 == 0 || char2cells(c3) > 1) {
+ return e_invarg;
+ }
+ }
+ }
+
+ if (*s == ',' || *s == NUL) {
+ if (round > 0) {
+ if (tab[i].cp == &lcs_chars.tab2) {
+ lcs_chars.tab1 = c1;
+ lcs_chars.tab2 = c2;
+ lcs_chars.tab3 = c3;
+ } else if (tab[i].cp != NULL) {
+ *(tab[i].cp) = c1;
+ }
+ }
+ p = s;
+ break;
+ }
+ }
+
+ if (i == entries) {
+ return e_invarg;
+ }
+
+ if (*p == ',') {
+ p++;
+ }
+ }
+ }
+
+ if (apply) {
+ if (is_listchars) {
+ xfree(wp->w_p_lcs_chars.multispace);
+ xfree(wp->w_p_lcs_chars.leadmultispace);
+ wp->w_p_lcs_chars = lcs_chars;
+ } else {
+ wp->w_p_fcs_chars = fcs_chars;
+ }
+ }
+
+ return NULL; // no error
+}
+
+/// Handle the new value of 'fillchars'.
+const char *set_fillchars_option(win_T *wp, char *val, bool apply)
+{
+ return set_chars_option(wp, val, false, apply);
+}
+
+/// Handle the new value of 'listchars'.
+const char *set_listchars_option(win_T *wp, char *val, bool apply)
+{
+ return set_chars_option(wp, val, true, apply);
+}
+
+/// Function given to ExpandGeneric() to obtain possible arguments of the
+/// 'fillchars' option.
+char *get_fillchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(fcs_tab)) {
+ return NULL;
+ }
+
+ return (char *)fcs_tab[idx].name;
+}
+
+/// Function given to ExpandGeneric() to obtain possible arguments of the
+/// 'listchars' option.
+char *get_listchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(lcs_tab)) {
+ return NULL;
+ }
+
+ return (char *)lcs_tab[idx].name;
+}
+
+/// Check all global and local values of 'listchars' and 'fillchars'.
+/// May set different defaults in case character widths change.
+///
+/// @return an untranslated error message if any of them is invalid, NULL otherwise.
+const char *check_chars_options(void)
+{
+ if (set_listchars_option(curwin, p_lcs, false) != NULL) {
+ return e_conflicts_with_value_of_listchars;
+ }
+ if (set_fillchars_option(curwin, p_fcs, false) != NULL) {
+ return e_conflicts_with_value_of_fillchars;
+ }
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (set_listchars_option(wp, wp->w_p_lcs, true) != NULL) {
+ return e_conflicts_with_value_of_listchars;
+ }
+ if (set_fillchars_option(wp, wp->w_p_fcs, true) != NULL) {
+ return e_conflicts_with_value_of_fillchars;
+ }
+ }
+ return NULL;
+}
diff --git a/src/nvim/optionstr.h b/src/nvim/optionstr.h
index 3520cc2061..b317e55b7e 100644
--- a/src/nvim/optionstr.h
+++ b/src/nvim/optionstr.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_OPTIONSTR_H
-#define NVIM_OPTIONSTR_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/option_defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "optionstr.h.generated.h"
#endif
-#endif // NVIM_OPTIONSTR_H
diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c
index 519cef7876..1a8d847f79 100644
--- a/src/nvim/os/dl.c
+++ b/src/nvim/os/dl.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// Functions for using external native libraries
#include <stdbool.h>
@@ -75,7 +72,7 @@ bool os_libcall(const char *libname, const char *funcname, const char *argv, int
// assume that ptr values of NULL, 1 or -1 are illegal
*str_out = (res && (intptr_t)res != 1 && (intptr_t)res != -1)
- ? xstrdup(res) : NULL;
+ ? xstrdup(res) : NULL;
} else {
str_int_fn sfn = (str_int_fn)fn;
int_int_fn ifn = (int_int_fn)fn;
diff --git a/src/nvim/os/dl.h b/src/nvim/os/dl.h
index 302e4e6678..0787c7fe46 100644
--- a/src/nvim/os/dl.h
+++ b/src/nvim/os/dl.h
@@ -1,10 +1,5 @@
-#ifndef NVIM_OS_DL_H
-#define NVIM_OS_DL_H
-
-#include <stdbool.h>
-#include <stdint.h>
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/dl.h.generated.h"
#endif
-#endif // NVIM_OS_DL_H
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 0611de14aa..8620c79069 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Environment inspection
#include <assert.h>
@@ -12,29 +9,33 @@
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/eval.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef MSWIN
-# include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+# include "nvim/mbyte.h"
+#endif
+
+#ifdef BACKSLASH_IN_FILENAME
+# include "nvim/fileio.h"
#endif
#ifdef HAVE__NSGETENVIRON
@@ -48,22 +49,6 @@
// Because `uv_os_getenv` requires allocating, we must manage a map to maintain
// the behavior of `os_getenv`.
static PMap(cstr_t) envmap = MAP_INIT;
-static uv_mutex_t mutex;
-
-void env_init(void)
-{
- uv_mutex_init(&mutex);
-}
-
-void os_env_var_lock(void)
-{
- uv_mutex_lock(&mutex);
-}
-
-void os_env_var_unlock(void)
-{
- uv_mutex_unlock(&mutex);
-}
/// Like getenv(), but returns NULL if the variable is empty.
/// @see os_env_exists
@@ -75,9 +60,8 @@ const char *os_getenv(const char *name)
if (name[0] == '\0') {
return NULL;
}
- uv_mutex_lock(&mutex);
int r = 0;
- if (pmap_has(cstr_t)(&envmap, name)
+ if (map_has(cstr_t, &envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(&envmap, name))) {
if (e[0] != '\0') {
// Found non-empty cached env var.
@@ -101,8 +85,6 @@ const char *os_getenv(const char *name)
}
pmap_put(cstr_t)(&envmap, xstrdup(name), e);
end:
- // Must do this before ELOG, log.c may call os_setenv.
- uv_mutex_unlock(&mutex);
if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
@@ -154,7 +136,6 @@ int os_setenv(const char *name, const char *value, int overwrite)
return 0;
}
#endif
- uv_mutex_lock(&mutex);
int r;
#ifdef MSWIN
// libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s().
@@ -169,8 +150,6 @@ int os_setenv(const char *name, const char *value, int overwrite)
// Destroy the old map item. Do this AFTER uv_os_setenv(), because `value`
// could be a previous os_getenv() result.
pmap_del2(&envmap, name);
- // Must do this before ELOG, log.c may call os_setenv.
- uv_mutex_unlock(&mutex);
if (r != 0) {
ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
@@ -184,11 +163,8 @@ int os_unsetenv(const char *name)
if (name[0] == '\0') {
return -1;
}
- uv_mutex_lock(&mutex);
pmap_del2(&envmap, name);
int r = uv_os_unsetenv(name);
- // Must do this before ELOG, log.c may call os_setenv.
- uv_mutex_unlock(&mutex);
if (r != 0) {
ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
@@ -315,12 +291,11 @@ char *os_getenvname_at_index(size_t index)
// Some Windows env vars start with =, so skip over that to find the
// separator between name/value
- const char * const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0),
- '=');
+ const char *const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0), '=');
assert(end != NULL);
ptrdiff_t len = end - utf8_str;
assert(len > 0);
- name = xstrndup(utf8_str, (size_t)len);
+ name = xmemdupz(utf8_str, (size_t)len);
xfree(utf8_str);
break;
}
@@ -351,7 +326,7 @@ char *os_getenvname_at_index(size_t index)
assert(end != NULL);
ptrdiff_t len = end - str;
assert(len > 0);
- return xstrndup(str, (size_t)len);
+ return xmemdupz(str, (size_t)len);
#endif
}
@@ -490,7 +465,7 @@ void init_homedir(void)
// links. Don't do it when we can't return.
if (os_dirname(os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) {
if (!os_chdir(var) && os_dirname(IObuff, IOSIZE) == OK) {
- var = (char *)IObuff;
+ var = IObuff;
}
if (os_chdir(os_buf) != 0) {
emsg(_(e_prev_dir));
@@ -515,10 +490,8 @@ static char *os_homedir(void)
{
homedir_buf[0] = NUL;
size_t homedir_size = MAXPATHL;
- uv_mutex_lock(&mutex);
// http://docs.libuv.org/en/v1.x/misc.html#c.uv_os_homedir
int ret_value = uv_os_homedir(homedir_buf, &homedir_size);
- uv_mutex_unlock(&mutex);
if (ret_value == 0 && homedir_size < MAXPATHL) {
return homedir_buf;
}
@@ -599,7 +572,7 @@ void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool es
if (src[0] == '`' && src[1] == '=') {
var = src;
src += 2;
- (void)skip_expr(&src);
+ (void)skip_expr(&src, NULL);
if (*src == '`') {
src++;
}
@@ -841,7 +814,7 @@ const void *vim_env_iter(const char delim, const char *const val, const void *co
const char **const dir, size_t *const len)
FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
- const char *varval = (const char *)iter;
+ const char *varval = iter;
if (varval == NULL) {
varval = val;
}
@@ -872,7 +845,7 @@ const void *vim_env_iter_rev(const char delim, const char *const val, const void
const char **const dir, size_t *const len)
FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
- const char *varend = (const char *)iter;
+ const char *varend = iter;
if (varend == NULL) {
varend = val + strlen(val) - 1;
}
@@ -956,10 +929,8 @@ char *vim_getenv(const char *name)
// Find runtime path relative to the nvim binary: ../share/nvim/runtime
if (vim_path == NULL) {
vim_get_prefix_from_exepath(exe_name);
- if (append_path(exe_name,
- "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
- MAXPATHL) == OK) {
- vim_path = exe_name; // -V507
+ if (append_path(exe_name, "share/nvim/runtime/", MAXPATHL) == OK) {
+ vim_path = exe_name;
}
}
@@ -985,7 +956,7 @@ char *vim_getenv(const char *name)
// check that the result is a directory name
assert(vim_path_end >= vim_path);
- vim_path = xstrndup(vim_path, (size_t)(vim_path_end - vim_path));
+ vim_path = xmemdupz(vim_path, (size_t)(vim_path_end - vim_path));
if (!os_isdir(vim_path)) {
xfree(vim_path);
@@ -1056,7 +1027,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
}
if (buf != NULL && buf->b_help) {
- const size_t dlen = xstrlcpy(dst, path_tail((char *)src), dstlen);
+ const size_t dlen = xstrlcpy(dst, path_tail(src), dstlen);
return MIN(dlen, dstlen - 1);
}
@@ -1094,7 +1065,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
}
if (!one) {
- src = skipwhite((char *)src);
+ src = skipwhite(src);
}
char *dst_p = dst;
while (*src && dstlen > 0) {
@@ -1107,7 +1078,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
// er's home directory)).
char *p = homedir;
size_t len = dirlen;
- for (;;) {
+ while (true) {
if (len
&& path_fnamencmp(src, p, len) == 0
&& (vim_ispathsep(src[len])
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index 5af39555c9..79d6ac08e7 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file fileio.c
///
/// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with
@@ -18,14 +15,22 @@
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/os.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os_defs.h"
#include "nvim/rbuffer.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
+
+#ifdef MSWIN
+# include "nvim/os/os_win_console.h"
+#endif
+
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fileio.c.generated.h"
@@ -49,7 +54,6 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f
{
int os_open_flags = 0;
TriState wr = kNone;
- // -V:FLAG:501
#define FLAG(flags, flag, fcntl_flags, wrval, cond) \
do { \
if (flags & flag) { \
@@ -278,9 +282,10 @@ static char writebuf[kRWBufferSize];
///
/// @param[in,out] rv RBuffer instance used.
/// @param[in,out] fp File to work with.
-static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp)
+static void file_rb_write_full_cb(RBuffer *const rv, void *const fp_in)
FUNC_ATTR_NONNULL_ALL
{
+ FileDescriptor *const fp = fp_in;
assert(fp->wr);
assert(rv->data == (void *)fp);
if (rbuffer_size(rv) == 0) {
diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h
index 5e47bbf921..72e7984c8a 100644
--- a/src/nvim/os/fileio.h
+++ b/src/nvim/os/fileio.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_FILEIO_H
-#define NVIM_OS_FILEIO_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -75,4 +74,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fileio.h.generated.h"
#endif
-#endif // NVIM_OS_FILEIO_H
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 302faa8140..8f939c3b40 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// fs.c -- filesystem access
#include <assert.h>
#include <errno.h>
@@ -12,79 +9,75 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
+#include <uv.h>
+
+#ifdef MSWIN
+# include <shlobj.h>
+#endif
#include "auto/config.h"
-#include "nvim/gettext.h"
-#include "nvim/globals.h"
-#include "nvim/log.h"
-#include "nvim/macros.h"
-#include "nvim/option_defs.h"
-#include "nvim/os/fs_defs.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/func_attr.h"
+#include "nvim/os/fs.h"
-#ifdef HAVE_SYS_UIO_H
-# include <sys/uio.h>
+#if defined(HAVE_ACL)
+# ifdef HAVE_SYS_ACL_H
+# include <sys/acl.h>
+# endif
+# ifdef HAVE_SYS_ACCESS_H
+# include <sys/access.h>
+# endif
#endif
-#include <uv.h>
+#ifdef HAVE_XATTR
+# include <sys/xattr.h>
+#endif
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/gettext.h"
+#include "nvim/globals.h"
+#include "nvim/log.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
-struct iovec;
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
#ifdef MSWIN
-# include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+# include "nvim/mbyte.h"
+# include "nvim/option.h"
+# include "nvim/strings.h"
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fs.c.generated.h"
#endif
+#ifdef HAVE_XATTR
+static const char e_xattr_erange[]
+ = N_("E1506: Buffer too small to copy xattr value or key");
+static const char e_xattr_e2big[]
+ = N_("E1508: Size of the extended attribute value is larger than the maximum size allowed");
+static const char e_xattr_other[]
+ = N_("E1509: Error occurred when reading or writing extended attribute");
+#endif
+
#define RUN_UV_FS_FUNC(ret, func, ...) \
do { \
- bool did_try_to_free = false; \
-uv_call_start: {} \
uv_fs_t req; \
- fs_loop_lock(); \
- ret = func(&fs_loop, &req, __VA_ARGS__); \
+ ret = func(NULL, &req, __VA_ARGS__); \
uv_fs_req_cleanup(&req); \
- fs_loop_unlock(); \
- if (ret == UV_ENOMEM && !did_try_to_free) { \
- try_to_free_memory(); \
- did_try_to_free = true; \
- goto uv_call_start; \
- } \
} while (0)
// Many fs functions from libuv return that value on success.
static const int kLibuvSuccess = 0;
-static uv_loop_t fs_loop;
-static uv_mutex_t fs_loop_mutex;
-
-// Initialize the fs module
-void fs_init(void)
-{
- uv_loop_init(&fs_loop);
- uv_mutex_init_recursive(&fs_loop_mutex);
-}
-
-/// TODO(bfredl): some of these operations should
-/// be possible to do the private libuv loop of the
-/// thread, instead of contending the global fs loop
-void fs_loop_lock(void)
-{
- uv_mutex_lock(&fs_loop_mutex);
-}
-
-void fs_loop_unlock(void)
-{
- uv_mutex_unlock(&fs_loop_mutex);
-}
/// Changes the current directory to `path`.
///
@@ -94,7 +87,7 @@ int os_chdir(const char *path)
{
if (p_verbose >= 5) {
verbose_enter();
- smsg("chdir(%s)", path);
+ smsg(0, "chdir(%s)", path);
verbose_leave();
}
return uv_chdir(path);
@@ -123,12 +116,9 @@ bool os_isrealdir(const char *name)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- fs_loop_lock();
- if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) {
- fs_loop_unlock();
+ if (uv_fs_lstat(NULL, &request, name, NULL) != kLibuvSuccess) {
return false;
}
- fs_loop_unlock();
if (S_ISLNK(request.statbuf.st_mode)) {
return false;
}
@@ -146,11 +136,7 @@ bool os_isdir(const char *name)
return false;
}
- if (!S_ISDIR(mode)) {
- return false;
- }
-
- return true;
+ return S_ISDIR(mode);
}
/// Check what `name` is:
@@ -377,7 +363,7 @@ static bool is_executable_in_path(const char *name, char **abspath)
// is an executable file.
char *p = path;
bool rv = false;
- for (;;) {
+ while (true) {
char *e = xstrchrnul(p, ENV_SEPCHAR);
// Combine the $PATH segment with `name`.
@@ -571,7 +557,6 @@ ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, const
return 0;
}
size_t read_bytes = 0;
- bool did_try_to_free = false;
while (read_bytes != size) {
assert(size >= read_bytes);
const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes,
@@ -586,10 +571,6 @@ ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, const
break;
} else if (error == UV_EINTR || error == UV_EAGAIN) {
continue;
- } else if (error == UV_ENOMEM && !did_try_to_free) {
- try_to_free_memory();
- did_try_to_free = true;
- continue;
} else {
return (ptrdiff_t)error;
}
@@ -623,7 +604,6 @@ ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, size_t
{
*ret_eof = false;
size_t read_bytes = 0;
- bool did_try_to_free = false;
size_t toread = 0;
for (size_t i = 0; i < iov_size; i++) {
// Overflow, trying to read too much data
@@ -655,10 +635,6 @@ ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, size_t
break;
} else if (error == UV_EINTR || error == UV_EAGAIN) {
continue;
- } else if (error == UV_ENOMEM && !did_try_to_free) {
- try_to_free_memory();
- did_try_to_free = true;
- continue;
} else {
return (ptrdiff_t)error;
}
@@ -747,9 +723,7 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
return UV_EINVAL;
}
uv_fs_t request;
- fs_loop_lock();
- int result = uv_fs_stat(&fs_loop, &request, name, NULL);
- fs_loop_unlock();
+ int result = uv_fs_stat(NULL, &request, name, NULL);
if (result == kLibuvSuccess) {
*statbuf = request.statbuf;
}
@@ -781,13 +755,84 @@ int os_setperm(const char *const name, int perm)
return (r == kLibuvSuccess ? OK : FAIL);
}
-#if defined(HAVE_ACL)
-# ifdef HAVE_SYS_ACL_H
-# include <sys/acl.h>
-# endif
-# ifdef HAVE_SYS_ACCESS_H
-# include <sys/access.h>
-# endif
+#ifdef HAVE_XATTR
+/// Copy extended attributes from_file to to_file
+void os_copy_xattr(const char *from_file, const char *to_file)
+{
+ if (from_file == NULL) {
+ return;
+ }
+
+ // get the length of the extended attributes
+ ssize_t size = listxattr((char *)from_file, NULL, 0);
+ // not supported or no attributes to copy
+ if (errno == ENOTSUP || size <= 0) {
+ return;
+ }
+ char *xattr_buf = xmalloc((size_t)size);
+ size = listxattr(from_file, xattr_buf, (size_t)size);
+ ssize_t tsize = size;
+
+ errno = 0;
+
+ ssize_t max_vallen = 0;
+ char *val = NULL;
+ const char *errmsg = NULL;
+
+ for (int round = 0; round < 2; round++) {
+ char *key = xattr_buf;
+ if (round == 1) {
+ size = tsize;
+ }
+
+ while (size > 0) {
+ ssize_t vallen = getxattr(from_file, key, val, round ? (size_t)max_vallen : 0);
+ // only set the attribute in the second round
+ if (vallen >= 0 && round
+ && setxattr(to_file, key, val, (size_t)vallen, 0) == 0) {
+ //
+ } else if (errno) {
+ switch (errno) {
+ case E2BIG:
+ errmsg = e_xattr_e2big;
+ goto error_exit;
+ case ENOTSUP:
+ case EACCES:
+ case EPERM:
+ break;
+ case ERANGE:
+ errmsg = e_xattr_erange;
+ goto error_exit;
+ default:
+ errmsg = e_xattr_other;
+ goto error_exit;
+ }
+ }
+
+ if (round == 0 && vallen > max_vallen) {
+ max_vallen = vallen;
+ }
+
+ // add one for terminating null
+ ssize_t keylen = (ssize_t)strlen(key) + 1;
+ size -= keylen;
+ key += keylen;
+ }
+ if (round) {
+ break;
+ }
+
+ val = xmalloc((size_t)max_vallen + 1);
+ }
+error_exit:
+ xfree(xattr_buf);
+ xfree(val);
+
+ if (errmsg != NULL) {
+ emsg(_(errmsg));
+ }
+}
+#endif
// Return a pointer to the ACL of file "fname" in allocated memory.
// Return NULL if the ACL is not available for whatever reason.
@@ -811,7 +856,6 @@ void os_free_acl(vim_acl_T aclent)
return;
}
}
-#endif
#ifdef UNIX
/// Checks if the current user owns a file.
@@ -942,10 +986,13 @@ int os_mkdir(const char *path, int32_t mode)
/// the name of the directory which os_mkdir_recurse
/// failed to create. I.e. it will contain dir or any
/// of the higher level directories.
+/// @param[out] created Set to the full name of the first created directory.
+/// It will be NULL until that happens.
///
/// @return `0` for success, libuv error code for failure.
-int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir,
+ char **const created)
+ FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
// Get end of directory name in "dir".
// We're done when it's "/" or "c:/".
@@ -980,6 +1027,8 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di
if ((ret = os_mkdir(curdir, mode)) != 0) {
*failed_dir = curdir;
return ret;
+ } else if (created != NULL && *created == NULL) {
+ *created = FullName_save(curdir, false);
}
}
xfree(curdir);
@@ -1007,7 +1056,7 @@ int os_file_mkdir(char *fname, int32_t mode)
*tail = NUL;
int r;
char *failed_dir;
- if (((r = os_mkdir_recurse(fname, mode, &failed_dir)) < 0)) {
+ if (((r = os_mkdir_recurse(fname, mode, &failed_dir, NULL)) < 0)) {
semsg(_(e_mkdir), failed_dir, os_strerror(r));
xfree(failed_dir);
}
@@ -1019,18 +1068,16 @@ int os_file_mkdir(char *fname, int32_t mode)
/// Create a unique temporary directory.
///
-/// @param[in] template Template of the path to the directory with XXXXXX
-/// which would be replaced by random chars.
+/// @param[in] templ Template of the path to the directory with XXXXXX
+/// which would be replaced by random chars.
/// @param[out] path Path to created directory for success, undefined for
/// failure.
/// @return `0` for success, non-zero for failure.
-int os_mkdtemp(const char *template, char *path)
+int os_mkdtemp(const char *templ, char *path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- fs_loop_lock();
- int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL);
- fs_loop_unlock();
+ int result = uv_fs_mkdtemp(NULL, &request, templ, NULL);
if (result == kLibuvSuccess) {
xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN);
}
@@ -1057,9 +1104,7 @@ int os_rmdir(const char *path)
bool os_scandir(Directory *dir, const char *path)
FUNC_ATTR_NONNULL_ALL
{
- fs_loop_lock();
- int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL);
- fs_loop_unlock();
+ int r = uv_fs_scandir(NULL, &dir->request, path, 0, NULL);
if (r < 0) {
os_closedir(dir);
}
@@ -1120,9 +1165,7 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info)
return false;
}
uv_fs_t request;
- fs_loop_lock();
- bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess;
- fs_loop_unlock();
+ bool ok = uv_fs_lstat(NULL, &request, path, NULL) == kLibuvSuccess;
if (ok) {
file_info->stat = request.statbuf;
}
@@ -1140,8 +1183,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
{
uv_fs_t request;
CLEAR_POINTER(file_info);
- fs_loop_lock();
- bool ok = uv_fs_fstat(&fs_loop,
+ bool ok = uv_fs_fstat(NULL,
&request,
file_descriptor,
NULL) == kLibuvSuccess;
@@ -1149,7 +1191,6 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
file_info->stat = request.statbuf;
}
uv_fs_req_cleanup(&request);
- fs_loop_unlock();
return ok;
}
@@ -1266,8 +1307,7 @@ char *os_realpath(const char *name, char *buf)
FUNC_ATTR_NONNULL_ARG(1)
{
uv_fs_t request;
- fs_loop_lock();
- int result = uv_fs_realpath(&fs_loop, &request, name, NULL);
+ int result = uv_fs_realpath(NULL, &request, name, NULL);
if (result == kLibuvSuccess) {
if (buf == NULL) {
buf = xmallocz(MAXPATHL);
@@ -1275,13 +1315,10 @@ char *os_realpath(const char *name, char *buf)
xstrlcpy(buf, request.ptr, MAXPATHL + 1);
}
uv_fs_req_cleanup(&request);
- fs_loop_unlock();
return result == kLibuvSuccess ? buf : NULL;
}
#ifdef MSWIN
-# include <shlobj.h>
-
/// When "fname" is the name of a shortcut (*.lnk) resolve the file it points
/// to and return that name in allocated memory.
/// Otherwise NULL is returned.
diff --git a/src/nvim/os/fs.h b/src/nvim/os/fs.h
index 75c24b8db2..56dd657f70 100644
--- a/src/nvim/os/fs.h
+++ b/src/nvim/os/fs.h
@@ -1,10 +1,13 @@
-#ifndef NVIM_OS_FS_H
-#define NVIM_OS_FS_H
+#pragma once
-#include "nvim/os/fs_defs.h"
-#include "nvim/types.h"
+#include <stddef.h> // IWYU pragma: keep
+#include <stdint.h> // IWYU pragma: keep
+#include <stdio.h> // IWYU pragma: keep
+#include <uv.h> // IWYU pragma: keep
+
+#include "nvim/os/fs_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fs.h.generated.h"
#endif
-#endif // NVIM_OS_FS_H
diff --git a/src/nvim/os/fs_defs.h b/src/nvim/os/fs_defs.h
index f4929b12b1..e5355ddefd 100644
--- a/src/nvim/os/fs_defs.h
+++ b/src/nvim/os/fs_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_FS_DEFS_H
-#define NVIM_OS_FS_DEFS_H
+#pragma once
#include <uv.h>
@@ -26,5 +25,3 @@ typedef struct {
#define NODE_WRITABLE 1 // something we can write to (character
// device, fifo, socket, ..)
#define NODE_OTHER 2 // non-writable thing (e.g., block device)
-
-#endif // NVIM_OS_FS_DEFS_H
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 759b3cf83c..f3bd1c7ed9 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
@@ -9,28 +6,28 @@
#include <uv.h>
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
+#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/profile.h"
#include "nvim/rbuffer.h"
#include "nvim/state.h"
-#include "nvim/vim.h"
#define READ_BUFFER_SIZE 0xfff
#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4)
@@ -242,8 +239,8 @@ bool os_isatty(int fd)
size_t input_enqueue(String keys)
{
- char *ptr = keys.data;
- char *end = ptr + keys.size;
+ const char *ptr = keys.data;
+ const char *end = ptr + keys.size;
while (rbuffer_space(input_buffer) >= 19 && ptr < end) {
// A "<x>" form occupies at least 1 characters, and produces up
@@ -253,9 +250,8 @@ size_t input_enqueue(String keys)
// K_SPECIAL(0x80).
uint8_t buf[19] = { 0 };
// Do not simplify the keys here. Simplification will be done later.
- unsigned int new_size
- = trans_special((const char **)&ptr, (size_t)(end - ptr), (char *)buf, FSK_KEYCODE, true,
- NULL);
+ unsigned new_size
+ = trans_special(&ptr, (size_t)(end - ptr), (char *)buf, FSK_KEYCODE, true, NULL);
if (new_size) {
new_size = handle_mouse_event(&ptr, buf, new_size);
@@ -264,7 +260,7 @@ size_t input_enqueue(String keys)
}
if (*ptr == '<') {
- char *old_ptr = ptr;
+ const char *old_ptr = ptr;
// Invalid or incomplete key sequence, skip until the next '>' or *end.
do {
ptr++;
@@ -346,7 +342,7 @@ static uint8_t check_multiclick(int code, int grid, int row, int col)
// Mouse event handling code(Extract row/col if available and detect multiple
// clicks)
-static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, unsigned int bufsize)
+static unsigned handle_mouse_event(const char **ptr, uint8_t *buf, unsigned bufsize)
{
int mouse_code = 0;
int type = 0;
@@ -441,7 +437,7 @@ bool input_blocking(void)
// This is a replacement for the old `WaitForChar` function in os_unix.c
static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
{
- if (input_ready(events)) {
+ if (os_input_ready(events)) {
return kInputAvail;
}
@@ -457,24 +453,19 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL,
events && !multiqueue_empty(events));
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms,
- input_ready(events) || input_eof);
+ os_input_ready(events) || input_eof);
blocking = false;
if (do_profiling == PROF_YES && ms) {
prof_inchar_exit();
}
- if (input_ready(events)) {
+ if (os_input_ready(events)) {
return kInputAvail;
}
return input_eof ? kInputEof : kInputNone;
}
-void input_done(void)
-{
- input_eof = true;
-}
-
bool input_available(void)
{
return rbuffer_size(input_buffer) != 0;
@@ -483,7 +474,7 @@ bool input_available(void)
static void input_read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, bool at_eof)
{
if (at_eof) {
- input_done();
+ input_eof = true;
}
assert(rbuffer_space(input_buffer) >= rbuffer_size(buf));
@@ -535,8 +526,8 @@ static int push_event_key(uint8_t *buf, int maxlen)
return buf_idx;
}
-// Check if there's pending input
-static bool input_ready(MultiQueue *events)
+/// Check if there's pending input already in typebuf or `events`
+bool os_input_ready(MultiQueue *events)
{
return (typebuf_was_filled // API call filled typeahead
|| rbuffer_size(input_buffer) // Input buffer filled
@@ -550,8 +541,7 @@ static void read_error_exit(void)
if (silent_mode) { // Normal way to exit for "nvim -es".
getout(0);
}
- STRCPY(IObuff, _("Vim: Error reading input, exiting...\n"));
- preserve_exit();
+ preserve_exit(_("Vim: Error reading input, exiting...\n"));
}
static bool pending_events(MultiQueue *events)
diff --git a/src/nvim/os/input.h b/src/nvim/os/input.h
index 6f25efdc7b..4b104b0b50 100644
--- a/src/nvim/os/input.h
+++ b/src/nvim/os/input.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_OS_INPUT_H
-#define NVIM_OS_INPUT_H
+#pragma once
#include <stdbool.h>
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/event/multiqueue.h"
+#include "nvim/macros_defs.h"
-EXTERN bool used_stdin INIT(= false);
+EXTERN bool used_stdin INIT( = false);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/input.h.generated.h"
#endif
-#endif // NVIM_OS_INPUT_H
diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c
index 57c82bba86..17d179a56a 100644
--- a/src/nvim/os/lang.c
+++ b/src/nvim/os/lang.c
@@ -1,22 +1,341 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#ifdef __APPLE__
# define Boolean CFBoolean // Avoid conflict with API's Boolean
# define FileInfo CSFileInfo // Avoid conflict with API's Fileinfo
# include <CoreServices/CoreServices.h>
+
# undef Boolean
# undef FileInfo
+#endif
-# include "auto/config.h"
-# ifdef HAVE_LOCALE_H
-# include <locale.h>
-# endif
-# include "nvim/os/os.h"
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "auto/config.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
+#include "nvim/eval.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/garray.h"
+#include "nvim/gettext.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+#include "nvim/option.h"
+#include "nvim/os/lang.h"
+#include "nvim/os/os.h"
+#include "nvim/os/shell.h"
+#include "nvim/path.h"
+#include "nvim/profile.h"
+#include "nvim/vim_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/lang.c.generated.h"
#endif
-#include "nvim/os/lang.h"
+static char *get_locale_val(int what)
+{
+ // Obtain the locale value from the libraries.
+ char *loc = setlocale(what, NULL);
+
+ return loc;
+}
+
+/// @return true when "lang" starts with a valid language name.
+/// Rejects NULL, empty string, "C", "C.UTF-8" and others.
+static bool is_valid_mess_lang(const char *lang)
+{
+ return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]);
+}
+
+/// Obtain the current messages language. Used to set the default for
+/// 'helplang'. May return NULL or an empty string.
+char *get_mess_lang(void)
+{
+ char *p;
+
+#if defined(LC_MESSAGES)
+ p = get_locale_val(LC_MESSAGES);
+#else
+ // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG
+ // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME
+ // and LC_MONETARY may be set differently for a Japanese working in the
+ // US.
+ p = get_locale_val(LC_COLLATE);
+#endif
+ return is_valid_mess_lang(p) ? p : NULL;
+}
+
+/// Get the language used for messages from the environment.
+///
+/// This uses LC_MESSAGES when available, which it is for most systems we build for
+/// except for windows. Then fallback to get the value from the environment
+/// ourselves, and use LC_CTYPE as a last resort.
+static char *get_mess_env(void)
+{
+#ifdef LC_MESSAGES
+ return get_locale_val(LC_MESSAGES);
+#else
+ char *p = (char *)os_getenv("LC_ALL");
+ if (p == NULL) {
+ p = (char *)os_getenv("LC_MESSAGES");
+ if (p == NULL) {
+ p = (char *)os_getenv("LANG");
+ if (p != NULL && ascii_isdigit(*p)) {
+ p = NULL; // ignore something like "1043"
+ }
+ if (p == NULL) {
+ p = get_locale_val(LC_CTYPE);
+ }
+ }
+ }
+ return p;
+#endif
+}
+
+/// Set the "v:lang" variable according to the current locale setting.
+/// Also do "v:lc_time"and "v:ctype".
+void set_lang_var(void)
+{
+ const char *loc;
+
+ loc = get_locale_val(LC_CTYPE);
+ set_vim_var_string(VV_CTYPE, loc, -1);
+
+ loc = get_mess_env();
+ set_vim_var_string(VV_LANG, loc, -1);
+
+ loc = get_locale_val(LC_TIME);
+ set_vim_var_string(VV_LC_TIME, loc, -1);
+
+ loc = get_locale_val(LC_COLLATE);
+ set_vim_var_string(VV_COLLATE, loc, -1);
+}
+
+/// Setup to use the current locale (for ctype() and many other things).
+void init_locale(void)
+{
+ setlocale(LC_ALL, "");
+
+#ifdef LC_NUMERIC
+ // Make sure strtod() uses a decimal point, not a comma.
+ setlocale(LC_NUMERIC, "C");
+#endif
+
+ char localepath[MAXPATHL] = { 0 };
+ snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH));
+ char *tail = path_tail_with_sep(localepath);
+ *tail = NUL;
+ tail = path_tail(localepath);
+ xstrlcpy(tail, "share/locale",
+ sizeof(localepath) - (size_t)(tail - localepath));
+ bindtextdomain(PROJECT_NAME, localepath);
+ textdomain(PROJECT_NAME);
+ TIME_MSG("locale set");
+}
+
+/// ":language": Set the language (locale).
+///
+/// @param eap
+void ex_language(exarg_T *eap)
+{
+ char *loc;
+ char *p;
+ char *name;
+ int what = LC_ALL;
+ char *whatstr = "";
+#ifdef LC_MESSAGES
+# define VIM_LC_MESSAGES LC_MESSAGES
+#else
+# define VIM_LC_MESSAGES 6789
+#endif
+
+ name = eap->arg;
+
+ // Check for "messages {name}", "ctype {name}" or "time {name}" argument.
+ // Allow abbreviation, but require at least 3 characters to avoid
+ // confusion with a two letter language name "me" or "ct".
+ p = skiptowhite(eap->arg);
+ if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) {
+ if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) {
+ what = VIM_LC_MESSAGES;
+ name = skipwhite(p);
+ whatstr = "messages ";
+ } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) {
+ what = LC_CTYPE;
+ name = skipwhite(p);
+ whatstr = "ctype ";
+ } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) {
+ what = LC_TIME;
+ name = skipwhite(p);
+ whatstr = "time ";
+ } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) {
+ what = LC_COLLATE;
+ name = skipwhite(p);
+ whatstr = "collate ";
+ }
+ }
+
+ if (*name == NUL) {
+ if (what == VIM_LC_MESSAGES) {
+ p = get_mess_env();
+ } else {
+ p = setlocale(what, NULL);
+ }
+ if (p == NULL || *p == NUL) {
+ p = "Unknown";
+ }
+ smsg(0, _("Current %slanguage: \"%s\""), whatstr, p);
+ } else {
+#ifndef LC_MESSAGES
+ if (what == VIM_LC_MESSAGES) {
+ loc = "";
+ } else {
+#endif
+ loc = setlocale(what, name);
+#ifdef LC_NUMERIC
+ // Make sure strtod() uses a decimal point, not a comma.
+ setlocale(LC_NUMERIC, "C");
+#endif
+#ifndef LC_MESSAGES
+ }
+#endif
+ if (loc == NULL) {
+ semsg(_("E197: Cannot set language to \"%s\""), name);
+ } else {
+#ifdef HAVE_NL_MSG_CAT_CNTR
+ // Need to do this for GNU gettext, otherwise cached translations
+ // will be used again.
+ extern int _nl_msg_cat_cntr; // NOLINT(bugprone-reserved-identifier)
+
+ _nl_msg_cat_cntr++;
+#endif
+ // Reset $LC_ALL, otherwise it would overrule everything.
+ os_setenv("LC_ALL", "", 1);
+
+ if (what != LC_TIME && what != LC_COLLATE) {
+ // Tell gettext() what to translate to. It apparently doesn't
+ // use the currently effective locale.
+ if (what == LC_ALL) {
+ os_setenv("LANG", name, 1);
+
+ // Clear $LANGUAGE because GNU gettext uses it.
+ os_setenv("LANGUAGE", "", 1);
+ }
+ if (what != LC_CTYPE) {
+ os_setenv("LC_MESSAGES", name, 1);
+ set_helplang_default(name);
+ }
+ }
+
+ // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
+ set_lang_var();
+ maketitle();
+ }
+ }
+}
+
+static char **locales = NULL; // Array of all available locales
+
+#ifndef MSWIN
+static bool did_init_locales = false;
+
+/// @return an array of strings for all available locales + NULL for the
+/// last element or,
+/// NULL in case of error.
+static char **find_locales(void)
+{
+ garray_T locales_ga;
+ char *loc;
+ char *saveptr = NULL;
+
+ // Find all available locales by running command "locale -a". If this
+ // doesn't work we won't have completion.
+ char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL);
+ if (locale_a == NULL) {
+ return NULL;
+ }
+ ga_init(&locales_ga, sizeof(char *), 20);
+
+ // Transform locale_a string where each locale is separated by "\n"
+ // into an array of locale strings.
+ loc = os_strtok(locale_a, "\n", &saveptr);
+
+ while (loc != NULL) {
+ loc = xstrdup(loc);
+ GA_APPEND(char *, &locales_ga, loc);
+ loc = os_strtok(NULL, "\n", &saveptr);
+ }
+ xfree(locale_a);
+ // Guarantee that .ga_data is NULL terminated
+ ga_grow(&locales_ga, 1);
+ ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL;
+ return locales_ga.ga_data;
+}
+#endif
+
+/// Lazy initialization of all available locales.
+static void init_locales(void)
+{
+#ifndef MSWIN
+ if (did_init_locales) {
+ return;
+ }
+
+ did_init_locales = true;
+ locales = find_locales();
+#endif
+}
+
+#if defined(EXITFREE)
+void free_locales(void)
+{
+ if (locales == NULL) {
+ return;
+ }
+
+ for (int i = 0; locales[i] != NULL; i++) {
+ xfree(locales[i]);
+ }
+ XFREE_CLEAR(locales);
+}
+#endif
+
+/// Function given to ExpandGeneric() to obtain the possible arguments of the
+/// ":language" command.
+char *get_lang_arg(expand_T *xp, int idx)
+{
+ if (idx == 0) {
+ return "messages";
+ }
+ if (idx == 1) {
+ return "ctype";
+ }
+ if (idx == 2) {
+ return "time";
+ }
+ if (idx == 3) {
+ return "collate";
+ }
+
+ init_locales();
+ if (locales == NULL) {
+ return NULL;
+ }
+ return locales[idx - 4];
+}
+
+/// Function given to ExpandGeneric() to obtain the available locales.
+char *get_locales(expand_T *xp, int idx)
+{
+ init_locales();
+ if (locales == NULL) {
+ return NULL;
+ }
+ return locales[idx];
+}
void lang_init(void)
{
diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h
index f60e064f57..4e7bf82195 100644
--- a/src/nvim/os/lang.h
+++ b/src/nvim/os/lang.h
@@ -1,7 +1,9 @@
-#ifndef NVIM_OS_LANG_H
-#define NVIM_OS_LANG_H
+#pragma once
+
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/lang.h.generated.h"
#endif
-#endif // NVIM_OS_LANG_H
diff --git a/src/nvim/os/mem.c b/src/nvim/os/mem.c
index 0b7e8065ef..3e6264c691 100644
--- a/src/nvim/os/mem.c
+++ b/src/nvim/os/mem.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// Functions for accessing system memory information.
#include <stdint.h>
diff --git a/src/nvim/os/nvim.manifest b/src/nvim/os/nvim.manifest
index 8878822a5d..571b7f4580 100644
--- a/src/nvim/os/nvim.manifest
+++ b/src/nvim/os/nvim.manifest
@@ -17,4 +17,9 @@
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
</application>
</compatibility>
+ <asmv3:application>
+ <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
+ <activeCodePage>UTF-8</activeCodePage>
+ </asmv3:windowsSettings>
+ </asmv3:application>
</assembly>
diff --git a/src/nvim/os/os.h b/src/nvim/os/os.h
index a7496130cc..302d84d066 100644
--- a/src/nvim/os/os.h
+++ b/src/nvim/os/os.h
@@ -1,22 +1,33 @@
-#ifndef NVIM_OS_OS_H
-#define NVIM_OS_OS_H
+#pragma once
-#include <stdbool.h>
-#include <uv.h>
+#include <stddef.h> // IWYU pragma: keep
+#include <stdint.h> // IWYU pragma: keep
+#include <uv.h> // IWYU pragma: keep
-#include "nvim/os/fs_defs.h"
-#include "nvim/os/stdpaths_defs.h"
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/os/os_defs.h" // IWYU pragma: export
+#include "nvim/os/stdpaths_defs.h" // IWYU pragma: keep
+
+#define HAVE_PATHDEF
+
+// Some file names are stored in pathdef.c, which is generated from the
+// Makefile to make their value depend on the Makefile.
+#ifdef HAVE_PATHDEF
+extern char *default_vim_dir;
+extern char *default_vimruntime_dir;
+extern char *default_lib_dir;
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
+// IWYU pragma: begin_exports
# include "os/env.h.generated.h"
-# include "os/fs.h.generated.h"
# include "os/mem.h.generated.h"
# include "os/stdpaths.h.generated.h"
# include "os/users.h.generated.h"
+// IWYU pragma: end_exports
#endif
#define ENV_LOGFILE "NVIM_LOG_FILE"
#define ENV_NVIM "NVIM"
-
-#endif // NVIM_OS_OS_H
diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h
index a30e16eeba..12de55a227 100644
--- a/src/nvim/os/os_defs.h
+++ b/src/nvim/os/os_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_OS_DEFS_H
-#define NVIM_OS_OS_DEFS_H
+#pragma once
#include <ctype.h>
#include <stdio.h>
@@ -7,12 +6,26 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include "auto/config.h"
+
+// Note: Some systems need both string.h and strings.h (Savage).
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h> // IWYU pragma: export
+#endif
+
#ifdef MSWIN
# include "nvim/os/win_defs.h"
#else
# include "nvim/os/unix_defs.h"
#endif
+#ifdef BACKSLASH_IN_FILENAME
+# define BACKSLASH_IN_FILENAME_BOOL true
+#else
+# define BACKSLASH_IN_FILENAME_BOOL false
+#endif
+
#if !defined(NAME_MAX) && defined(_XOPEN_NAME_MAX)
# define NAME_MAX _XOPEN_NAME_MAX
#endif
@@ -30,12 +43,7 @@
// Command-processing buffer. Use large buffers for all platforms.
#define CMDBUFFSIZE 1024
-// Note: Some systems need both string.h and strings.h (Savage). However,
-// some systems can't handle both, only use string.h in that case.
-#include <string.h>
-#if defined(HAVE_STRINGS_H) && !defined(NO_STRINGS_WITH_STRING_H)
-# include <strings.h>
-#endif
+#define ROOT_UID 0
/// Converts libuv error (negative int) to error description string.
#define os_strerror uv_strerror
@@ -52,52 +60,56 @@
// stat macros
#ifndef S_ISDIR
# ifdef S_IFDIR
-# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+# define S_ISDIR(m) (((m)& S_IFMT) == S_IFDIR)
# else
# define S_ISDIR(m) 0
# endif
#endif
#ifndef S_ISREG
# ifdef S_IFREG
-# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+# define S_ISREG(m) (((m)& S_IFMT) == S_IFREG)
# else
# define S_ISREG(m) 0
# endif
#endif
#ifndef S_ISBLK
# ifdef S_IFBLK
-# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+# define S_ISBLK(m) (((m)& S_IFMT) == S_IFBLK)
# else
# define S_ISBLK(m) 0
# endif
#endif
#ifndef S_ISSOCK
# ifdef S_IFSOCK
-# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+# define S_ISSOCK(m) (((m)& S_IFMT) == S_IFSOCK)
# else
# define S_ISSOCK(m) 0
# endif
#endif
#ifndef S_ISFIFO
# ifdef S_IFIFO
-# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+# define S_ISFIFO(m) (((m)& S_IFMT) == S_IFIFO)
# else
# define S_ISFIFO(m) 0
# endif
#endif
#ifndef S_ISCHR
# ifdef S_IFCHR
-# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+# define S_ISCHR(m) (((m)& S_IFMT) == S_IFCHR)
# else
# define S_ISCHR(m) 0
# endif
#endif
#ifndef S_ISLNK
# ifdef S_IFLNK
-# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+# define S_ISLNK(m) (((m)& S_IFMT) == S_IFLNK)
# else
# define S_ISLNK(m) 0
# endif
#endif
-#endif // NVIM_OS_OS_DEFS_H
+// BSD is supposed to cover FreeBSD and similar systems.
+#if (defined(BSD) || defined(__FreeBSD_kernel__)) \
+ && (defined(S_ISCHR) || defined(S_IFCHR))
+# define OPEN_CHR_FILES
+#endif
diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c
index 006e27d28f..816e81e997 100644
--- a/src/nvim/os/os_win_console.c
+++ b/src/nvim/os/os_win_console.c
@@ -1,10 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+#include <string.h>
+#include "nvim/globals.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/os_win_console.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/os_win_console.c.generated.h"
@@ -83,7 +83,7 @@ void os_icon_init(void)
const char *vimruntime = os_getenv("VIMRUNTIME");
if (vimruntime != NULL) {
- snprintf(NameBuff, MAXPATHL, "%s" _PATHSEPSTR "neovim.ico", vimruntime);
+ snprintf(NameBuff, MAXPATHL, "%s/neovim.ico", vimruntime);
if (!os_path_exists(NameBuff)) {
WLOG("neovim.ico not found: %s", NameBuff);
} else {
diff --git a/src/nvim/os/os_win_console.h b/src/nvim/os/os_win_console.h
index 7b5800afa8..098267312a 100644
--- a/src/nvim/os/os_win_console.h
+++ b/src/nvim/os/os_win_console.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_OS_WIN_CONSOLE_H
-#define NVIM_OS_OS_WIN_CONSOLE_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/os_win_console.h.generated.h"
@@ -8,5 +7,3 @@
#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
# define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
#endif
-
-#endif // NVIM_OS_OS_WIN_CONSOLE_H
diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c
index f4d95e141b..d9ec3a7a8a 100644
--- a/src/nvim/os/process.c
+++ b/src/nvim/os/process.c
@@ -1,29 +1,21 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// OS process functions
///
/// psutil is a good reference for cross-platform syscall voodoo:
/// https://github.com/giampaolo/psutil/tree/master/psutil/arch
+// IWYU pragma: no_include <sys/param.h>
+
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
-#include <stdio.h>
#include <uv.h>
-#include "nvim/log.h"
-#include "nvim/memory.h"
-#include "nvim/os/process.h"
-
#ifdef MSWIN
# include <tlhelp32.h>
-
-# include "nvim/api/private/helpers.h"
#endif
-#if defined(__FreeBSD__) // XXX: OpenBSD ?
+#if defined(__FreeBSD__)
# include <string.h>
# include <sys/types.h>
# include <sys/user.h>
@@ -34,8 +26,21 @@
#endif
#if defined(__APPLE__) || defined(BSD)
-# include <pwd.h>
# include <sys/sysctl.h>
+
+# include "nvim/macros_defs.h"
+#endif
+
+#if defined(__linux__)
+# include <stdio.h>
+#endif
+
+#include "nvim/log.h"
+#include "nvim/memory.h"
+#include "nvim/os/process.h"
+
+#ifdef MSWIN
+# include "nvim/api/private/helpers.h"
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -72,7 +77,7 @@ static bool os_proc_tree_kill_rec(HANDLE process, int sig)
}
theend:
- return (bool)TerminateProcess(process, (unsigned int)sig);
+ return (bool)TerminateProcess(process, (unsigned)sig);
}
/// Kills process `pid` and its descendants recursively.
bool os_proc_tree_kill(int pid, int sig)
@@ -254,7 +259,7 @@ Dictionary os_proc_info(int pid)
if (pe.th32ProcessID == (DWORD)pid) {
PUT(pinfo, "pid", INTEGER_OBJ(pid));
PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID));
- PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile)));
+ PUT(pinfo, "name", CSTR_TO_OBJ(pe.szExeFile));
}
return pinfo;
diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h
index faa4762cf1..3b116b4bad 100644
--- a/src/nvim/os/process.h
+++ b/src/nvim/os/process.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_OS_PROCESS_H
-#define NVIM_OS_PROCESS_H
+#pragma once
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#ifdef MSWIN
+# include "nvim/api/private/defs.h" // IWYU pragma: keep
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/process.h.generated.h"
#endif
-
-#endif // NVIM_OS_PROCESS_H
diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c
index 43c89f8865..53169c0ef8 100644
--- a/src/nvim/os/pty_conpty_win.c
+++ b/src/nvim/os/pty_conpty_win.c
@@ -1,11 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <uv.h>
#include "nvim/os/os.h"
#include "nvim/os/pty_conpty_win.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifndef EXTENDED_STARTUPINFO_PRESENT
# define EXTENDED_STARTUPINFO_PRESENT 0x00080000
@@ -14,9 +11,9 @@
# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
#endif
-HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
-HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
-void (WINAPI *pClosePseudoConsole)(HPCON);
+HRESULT(WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
+HRESULT(WINAPI *pResizePseudoConsole)(HPCON, COORD);
+void(WINAPI *pClosePseudoConsole)(HPCON);
bool os_has_conpty_working(void)
{
diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h
index 0c25a5970e..aa04cd1e84 100644
--- a/src/nvim/os/pty_conpty_win.h
+++ b/src/nvim/os/pty_conpty_win.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_PTY_CONPTY_WIN_H
-#define NVIM_OS_PTY_CONPTY_WIN_H
+#pragma once
#include "klib/kvec.h"
#include "nvim/os/input.h"
@@ -8,10 +7,10 @@
# define HPCON VOID *
#endif
-extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens)
+extern HRESULT(WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens)
(COORD, HANDLE, HANDLE, DWORD, HPCON *);
-extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
-extern void (WINAPI *pClosePseudoConsole)(HPCON);
+extern HRESULT(WINAPI *pResizePseudoConsole)(HPCON, COORD);
+extern void(WINAPI *pClosePseudoConsole)(HPCON);
typedef struct conpty {
HPCON pty;
@@ -21,5 +20,3 @@ typedef struct conpty {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_conpty_win.h.generated.h"
#endif
-
-#endif // NVIM_OS_PTY_CONPTY_WIN_H
diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h
index 07d346be22..2c7a5f66bd 100644
--- a/src/nvim/os/pty_process.h
+++ b/src/nvim/os/pty_process.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_OS_PTY_PROCESS_H
-#define NVIM_OS_PTY_PROCESS_H
+#pragma once
#ifdef MSWIN
# include "nvim/os/pty_process_win.h"
#else
# include "nvim/os/pty_process_unix.h"
#endif
-#endif // NVIM_OS_PTY_PROCESS_H
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index 2413f0339b..f801646967 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Some of the code came from pangoterm and libuv
#include <assert.h>
@@ -11,6 +8,7 @@
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
+#include <uv.h>
// forkpty is not in POSIX, so headers are platform-specific
#if defined(__FreeBSD__) || defined(__DragonFly__)
@@ -31,16 +29,15 @@
# include <crt_externs.h>
#endif
-#include <uv.h>
-
#include "auto/config.h"
#include "klib/klist.h"
#include "nvim/eval/typval.h"
#include "nvim/event/loop.h"
#include "nvim/event/process.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/os/os.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/pty_process_unix.h"
@@ -286,7 +283,7 @@ static void init_child(PtyProcess *ptyproc)
return;
}
- char *prog = ptyproc->process.argv[0];
+ const char *prog = process_get_exepath(proc);
assert(proc->env);
environ = tv_dict_to_env(proc->env);
@@ -336,21 +333,21 @@ static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
termios->c_lflag |= ECHOKE;
#endif
- termios->c_cc[VINTR] = 0x1f & 'C';
- termios->c_cc[VQUIT] = 0x1f & '\\';
- termios->c_cc[VERASE] = 0x7f;
- termios->c_cc[VKILL] = 0x1f & 'U';
- termios->c_cc[VEOF] = 0x1f & 'D';
- termios->c_cc[VEOL] = _POSIX_VDISABLE;
- termios->c_cc[VEOL2] = _POSIX_VDISABLE;
- termios->c_cc[VSTART] = 0x1f & 'Q';
- termios->c_cc[VSTOP] = 0x1f & 'S';
- termios->c_cc[VSUSP] = 0x1f & 'Z';
+ termios->c_cc[VINTR] = 0x1f & 'C';
+ termios->c_cc[VQUIT] = 0x1f & '\\';
+ termios->c_cc[VERASE] = 0x7f;
+ termios->c_cc[VKILL] = 0x1f & 'U';
+ termios->c_cc[VEOF] = 0x1f & 'D';
+ termios->c_cc[VEOL] = _POSIX_VDISABLE;
+ termios->c_cc[VEOL2] = _POSIX_VDISABLE;
+ termios->c_cc[VSTART] = 0x1f & 'Q';
+ termios->c_cc[VSTOP] = 0x1f & 'S';
+ termios->c_cc[VSUSP] = 0x1f & 'Z';
termios->c_cc[VREPRINT] = 0x1f & 'R';
- termios->c_cc[VWERASE] = 0x1f & 'W';
- termios->c_cc[VLNEXT] = 0x1f & 'V';
- termios->c_cc[VMIN] = 1;
- termios->c_cc[VTIME] = 0;
+ termios->c_cc[VWERASE] = 0x1f & 'W';
+ termios->c_cc[VLNEXT] = 0x1f & 'V';
+ termios->c_cc[VMIN] = 1;
+ termios->c_cc[VTIME] = 0;
}
static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h
index 0cc68cf3e9..92cc582832 100644
--- a/src/nvim/os/pty_process_unix.h
+++ b/src/nvim/os/pty_process_unix.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_OS_PTY_PROCESS_UNIX_H
-#define NVIM_OS_PTY_PROCESS_UNIX_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/pty_process.h"
#include <stdint.h>
#include <sys/ioctl.h>
@@ -27,5 +27,3 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_unix.h.generated.h"
#endif
-
-#endif // NVIM_OS_PTY_PROCESS_UNIX_H
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index 6233a90638..ca2dce36ea 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
-#include "nvim/ascii.h"
-#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+#include "nvim/ascii_defs.h"
+#include "nvim/eval/typval.h"
+#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
#include "nvim/os/pty_conpty_win.h"
@@ -172,11 +170,13 @@ void pty_process_close(PtyProcess *ptyproc)
void pty_process_close_master(PtyProcess *ptyproc)
FUNC_ATTR_NONNULL_ALL
-{}
+{
+}
void pty_process_teardown(Loop *loop)
FUNC_ATTR_NONNULL_ALL
-{}
+{
+}
static void pty_process_connect_cb(uv_connect_t *req, int status)
FUNC_ATTR_NONNULL_ALL
@@ -253,9 +253,9 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
QUEUE_FOREACH(q, &args_q, {
ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
+ QUEUE_REMOVE(q);
xfree(arg_node->arg);
xfree(arg_node);
- QUEUE_REMOVE(q);
if (!QUEUE_EMPTY(&args_q)) {
xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
index ed7d765ac7..26cf387e54 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_process_win.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_OS_PTY_PROCESS_WIN_H
-#define NVIM_OS_PTY_PROCESS_WIN_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/pty_process.h"
#include <uv.h>
@@ -37,5 +37,3 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_win.h.generated.h"
#endif
-
-#endif // NVIM_OS_PTY_PROCESS_WIN_H
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index f1e2c5440f..191be784e8 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
@@ -10,8 +7,7 @@
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
@@ -24,29 +20,30 @@
#include "nvim/event/wstream.h"
#include "nvim/ex_cmds.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/rbuffer.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 }
#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
@@ -134,6 +131,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh
#define STYLE_PRINT 3 // use "print -N", for zsh
#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly
+#define STYLE_GLOBSTAR 5 // use extended shell glob for bash (this uses extended
+ // globbing functionality with globstar, needs bash > 4)
int shell_style = STYLE_ECHO;
int check_spaces;
static bool did_find_nul = false;
@@ -141,10 +140,13 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// vimglob() function to define for Posix shell
static char *sh_vimglob_func =
"vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
+ // vimglob() function with globstar setting enabled, only for bash >= 4.X
+ static char *sh_globstar_opt =
+ "[[ ${BASH_VERSINFO[0]} -ge 4 ]] && shopt -s globstar; ";
bool is_fish_shell =
#if defined(UNIX)
- strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
+ strncmp(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
#else
false;
#endif
@@ -190,6 +192,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// If we use *zsh, "print -N" will work better than "glob".
// STYLE_VIMGLOB: NL separated
// If we use *sh*, we define "vimglob()".
+ // STYLE_GLOBSTAR: NL separated
+ // If we use *bash*, we define "vimglob() and enable globstar option".
// STYLE_ECHO: space separated.
// A shell we don't know, stay safe and use "echo".
if (num_pat == 1 && *pat[0] == '`'
@@ -203,9 +207,12 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
shell_style = STYLE_PRINT;
}
}
- if (shell_style == STYLE_ECHO
- && strstr(path_tail(p_sh), "sh") != NULL) {
- shell_style = STYLE_VIMGLOB;
+ if (shell_style == STYLE_ECHO) {
+ if (strstr(path_tail(p_sh), "bash") != NULL) {
+ shell_style = STYLE_GLOBSTAR;
+ } else if (strstr(path_tail(p_sh), "sh") != NULL) {
+ shell_style = STYLE_VIMGLOB;
+ }
}
// Compute the length of the command. We need 2 extra bytes: for the
@@ -214,6 +221,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
len = strlen(tempname) + 29;
if (shell_style == STYLE_VIMGLOB) {
len += strlen(sh_vimglob_func);
+ } else if (shell_style == STYLE_GLOBSTAR) {
+ len += strlen(sh_vimglob_func) + strlen(sh_globstar_opt);
}
for (i = 0; i < num_pat; i++) {
@@ -281,6 +290,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
STRCAT(command, "print -N >");
} else if (shell_style == STYLE_VIMGLOB) {
STRCAT(command, sh_vimglob_func);
+ } else if (shell_style == STYLE_GLOBSTAR) {
+ STRCAT(command, sh_globstar_opt);
+ STRCAT(command, sh_vimglob_func);
} else {
STRCAT(command, "echo >");
}
@@ -352,7 +364,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// When running in the background, give it some time to create the temp
// file, but don't wait for it to finish.
if (ampersand) {
- os_delay(10L, true);
+ os_delay(10, true);
}
xfree(command);
@@ -364,7 +376,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
if (!(flags & EW_SILENT)) {
msg_putchar('\n'); // clear bottom line quickly
cmdline_row = Rows - 1; // continue on last line
- msg(_(e_wildexpand));
+ msg(_(e_wildexpand), 0);
msg_start(); // don't overwrite this message
}
@@ -381,13 +393,13 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
if (fd == NULL) {
// Something went wrong, perhaps a file name with a special char.
if (!(flags & EW_SILENT)) {
- msg(_(e_wildexpand));
+ msg(_(e_wildexpand), 0);
msg_start(); // don't overwrite this message
}
xfree(tempname);
goto notfound;
}
- int fseek_res = fseek(fd, 0L, SEEK_END);
+ int fseek_res = fseek(fd, 0, SEEK_END);
if (fseek_res < 0) {
xfree(tempname);
fclose(fd);
@@ -399,11 +411,11 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
fclose(fd);
return FAIL;
}
-#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T
- assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int)
+#if 8 > SIZEOF_SIZE_T
+ assert(templen <= SIZE_MAX); // NOLINT(runtime/int)
#endif
len = (size_t)templen;
- fseek(fd, 0L, SEEK_SET);
+ fseek(fd, 0, SEEK_SET);
buffer = xmalloc(len + 1);
// fread() doesn't terminate buffer with NUL;
// appropriate termination (not always NUL) is done below.
@@ -430,7 +442,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
p = skipwhite(p); // skip to next entry
}
// file names are separated with NL
- } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) {
+ } else if (shell_style == STYLE_BT
+ || shell_style == STYLE_VIMGLOB
+ || shell_style == STYLE_GLOBSTAR) {
buffer[len] = NUL; // make sure the buffer ends in NUL
p = buffer;
for (i = 0; *p != NUL; i++) { // count number of entries
@@ -496,7 +510,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
(*file)[i] = p;
// Space or NL separates
if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
- || shell_style == STYLE_VIMGLOB) {
+ || shell_style == STYLE_VIMGLOB || shell_style == STYLE_GLOBSTAR) {
while (!(shell_style == STYLE_ECHO && *p == ' ')
&& *p != '\n' && *p != NUL) {
p++;
@@ -715,7 +729,7 @@ int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg)
if (p_verbose > 3) {
verbose_enter();
- smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd);
+ smsg(0, _("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd);
msg_putchar('\n');
verbose_leave();
}
@@ -786,9 +800,9 @@ char *get_cmd_output(char *cmd, char *infile, ShellOpts flags, size_t *ret_len)
goto done;
}
- fseek(fd, 0L, SEEK_END);
+ fseek(fd, 0, SEEK_END);
size_t len = (size_t)ftell(fd); // get size of temp file
- fseek(fd, 0L, SEEK_SET);
+ fseek(fd, 0, SEEK_SET);
buffer = xmalloc(len + 1);
size_t i = fread(buffer, 1, len, fd);
@@ -876,9 +890,9 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
// Failed, probably 'shell' is not executable.
if (!silent) {
msg_puts(_("\nshell failed to start: "));
- msg_outtrans((char *)os_strerror(status));
+ msg_outtrans(os_strerror(status), 0);
msg_puts(": ");
- msg_outtrans(prog);
+ msg_outtrans(prog, 0);
msg_putchar('\n');
}
multiqueue_free(events);
@@ -1006,9 +1020,9 @@ static void system_data_cb(Stream *stream, RBuffer *buf, size_t count, void *dat
/// Returns the previous decision if size=0.
static bool out_data_decide_throttle(size_t size)
{
- static uint64_t started = 0; // Start time of the current throttle.
- static size_t received = 0; // Bytes observed since last throttle.
- static size_t visit = 0; // "Pulse" count of the current throttle.
+ static uint64_t started = 0; // Start time of the current throttle.
+ static size_t received = 0; // Bytes observed since last throttle.
+ static size_t visit = 0; // "Pulse" count of the current throttle.
static char pulse_msg[] = { ' ', ' ', ' ', '\0' };
if (!size) {
@@ -1026,7 +1040,7 @@ static bool out_data_decide_throttle(size_t size)
started = os_hrtime();
} else {
uint64_t since = os_hrtime() - started;
- if (since < (visit * 0.1L * NS_1_SECOND)) {
+ if (since < (visit * (NS_1_SECOND / 10))) {
return true;
}
if (since > (3 * NS_1_SECOND)) {
@@ -1088,7 +1102,7 @@ static void out_data_ring(char *output, size_t size)
last_skipped_len = MAX_CHUNK_SIZE;
} else if (size > 0) {
// Length of the old data that can be kept.
- size_t keep_len = MIN(last_skipped_len, MAX_CHUNK_SIZE - size);
+ size_t keep_len = MIN(last_skipped_len, MAX_CHUNK_SIZE - size);
size_t keep_start = last_skipped_len - keep_len;
// Shift the kept part of the old data to the start.
if (keep_start) {
@@ -1127,7 +1141,7 @@ static void out_data_append_to_screen(char *output, size_t *count, bool eof)
goto end;
}
- (void)msg_outtrans_len_attr(p, i, 0);
+ (void)msg_outtrans_len(p, i, 0);
p += i;
}
}
@@ -1180,7 +1194,7 @@ static size_t tokenize(const char *const str, char **const argv)
}
argc++;
- p = (const char *)skipwhite((p + len));
+ p = skipwhite((p + len));
}
return argc;
@@ -1221,12 +1235,12 @@ static size_t word_length(const char *str)
/// before we finish writing.
static void read_input(DynamicBuffer *buf)
{
- size_t written = 0, l = 0, len = 0;
+ size_t written = 0, len = 0;
linenr_T lnum = curbuf->b_op_start.lnum;
char *lp = ml_get(lnum);
- for (;;) {
- l = strlen(lp + written);
+ while (true) {
+ size_t l = strlen(lp + written);
if (l == 0) {
len = 0;
} else if (lp[written] == NL) {
diff --git a/src/nvim/os/shell.h b/src/nvim/os/shell.h
index 48503f2601..82c83543af 100644
--- a/src/nvim/os/shell.h
+++ b/src/nvim/os/shell.h
@@ -1,9 +1,6 @@
-#ifndef NVIM_OS_SHELL_H
-#define NVIM_OS_SHELL_H
+#pragma once
-#include <stdio.h>
-
-#include "nvim/types.h"
+#include <stddef.h> // IWYU pragma: keep
// Flags for os_call_shell() second argument
typedef enum {
@@ -19,4 +16,3 @@ typedef enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/shell.h.generated.h"
#endif
-#endif // NVIM_OS_SHELL_H
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index b8daaabba2..c920cb655e 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -1,23 +1,24 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
+
#ifndef MSWIN
-# include <signal.h> // for sigset_t
+# include <signal.h>
#endif
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/eval.h"
#include "nvim/event/signal.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
-#include "nvim/memline.h"
#include "nvim/os/signal.h"
+#ifdef SIGPWR
+# include "nvim/memline.h"
+#endif
+
static SignalWatcher spipe, shup, squit, sterm, susr1, swinch;
#ifdef SIGPWR
static SignalWatcher spwr;
@@ -172,11 +173,10 @@ static void deadly_signal(int signum)
ILOG("got signal %d (%s)", signum, signal_name(signum));
- snprintf(IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n",
- signal_name(signum));
+ snprintf(IObuff, IOSIZE, "Vim: Caught deadly signal '%s'\r\n", signal_name(signum));
// Preserve files and exit.
- preserve_exit();
+ preserve_exit(IObuff);
}
static void on_signal(SignalWatcher *handle, int signum, void *data)
diff --git a/src/nvim/os/signal.h b/src/nvim/os/signal.h
index 5d8cc6f661..83a0a9c91b 100644
--- a/src/nvim/os/signal.h
+++ b/src/nvim/os/signal.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_OS_SIGNAL_H
-#define NVIM_OS_SIGNAL_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/signal.h.generated.h"
#endif
-#endif // NVIM_OS_SIGNAL_H
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index 6b07b6ef70..7691aa5122 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -1,11 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
+#include <assert.h>
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
+#include "nvim/globals.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
@@ -57,6 +57,39 @@ static const char *const xdg_defaults[] = {
#endif
};
+/// Get the value of $NVIM_APPNAME or "nvim" if not set.
+///
+/// @return $NVIM_APPNAME value
+const char *get_appname(void)
+{
+ const char *env_val = os_getenv("NVIM_APPNAME");
+ if (env_val == NULL || *env_val == '\0') {
+ env_val = "nvim";
+ }
+ return env_val;
+}
+
+/// Ensure that APPNAME is valid. Must be a name or relative path.
+bool appname_is_valid(void)
+{
+ const char *appname = get_appname();
+ if (path_is_absolute(appname)
+ // TODO(justinmk): on Windows, path_is_absolute says "/" is NOT absolute. Should it?
+ || strequal(appname, "/")
+ || strequal(appname, "\\")
+ || strequal(appname, ".")
+ || strequal(appname, "..")
+#ifdef BACKSLASH_IN_FILENAME
+ || strstr(appname, "\\..") != NULL
+ || strstr(appname, "..\\") != NULL
+#endif
+ || strstr(appname, "/..") != NULL
+ || strstr(appname, "../") != NULL) {
+ return false;
+ }
+ return true;
+}
+
/// Return XDG variable value
///
/// @param[in] idx XDG variable to use.
@@ -92,7 +125,7 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
ret = "/tmp/";
}
size_t len = strlen(ret);
- ret = xstrndup(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash.
+ ret = xmemdupz(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash.
}
return ret;
@@ -100,25 +133,28 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
/// Return Nvim-specific XDG directory subpath.
///
-/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
+/// Windows: Uses "…/$NVIM_APPNAME-data" for kXDGDataHome to avoid storing
/// configuration and data files in the same path. #4403
///
/// @param[in] idx XDG directory to use.
///
-/// @return [allocated] "{xdg_directory}/nvim"
+/// @return [allocated] "{xdg_directory}/$NVIM_APPNAME"
char *get_xdg_home(const XDGVarType idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
char *dir = stdpaths_get_xdg_var(idx);
+ const char *appname = get_appname();
+ size_t appname_len = strlen(appname);
+ assert(appname_len < (IOSIZE - sizeof("-data")));
+
if (dir) {
+ xstrlcpy(IObuff, appname, appname_len + 1);
#if defined(MSWIN)
- dir = concat_fnames_realloc(dir,
- ((idx == kXDGDataHome
- || idx == kXDGStateHome) ? "nvim-data" : "nvim"),
- true);
-#else
- dir = concat_fnames_realloc(dir, "nvim", true);
+ if (idx == kXDGDataHome || idx == kXDGStateHome) {
+ xstrlcat(IObuff, "-data", IOSIZE);
+ }
#endif
+ dir = concat_fnames_realloc(dir, IObuff, true);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(dir);
@@ -131,7 +167,7 @@ char *get_xdg_home(const XDGVarType idx)
///
/// @param[in] fname New component of the path.
///
-/// @return [allocated] `$XDG_CACHE_HOME/nvim/{fname}`
+/// @return [allocated] `$XDG_CACHE_HOME/$NVIM_APPNAME/{fname}`
char *stdpaths_user_cache_subpath(const char *fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -142,7 +178,7 @@ char *stdpaths_user_cache_subpath(const char *fname)
///
/// @param[in] fname New component of the path.
///
-/// @return [allocated] `$XDG_CONFIG_HOME/nvim/{fname}`
+/// @return [allocated] `$XDG_CONFIG_HOME/$NVIM_APPNAME/{fname}`
char *stdpaths_user_conf_subpath(const char *fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -153,7 +189,7 @@ char *stdpaths_user_conf_subpath(const char *fname)
///
/// @param[in] fname New component of the path.
///
-/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`
+/// @return [allocated] `$XDG_DATA_HOME/$NVIM_APPNAME/{fname}`
char *stdpaths_user_data_subpath(const char *fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -166,7 +202,7 @@ char *stdpaths_user_data_subpath(const char *fname)
/// @param[in] trailing_pathseps Amount of trailing path separators to add.
/// @param[in] escape_commas If true, all commas will be escaped.
///
-/// @return [allocated] `$XDG_STATE_HOME/nvim/{fname}`.
+/// @return [allocated] `$XDG_STATE_HOME/$NVIM_APPNAME/{fname}`.
char *stdpaths_user_state_subpath(const char *fname, const size_t trailing_pathseps,
const bool escape_commas)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
diff --git a/src/nvim/os/stdpaths_defs.h b/src/nvim/os/stdpaths_defs.h
index f94c511fe7..985688390d 100644
--- a/src/nvim/os/stdpaths_defs.h
+++ b/src/nvim/os/stdpaths_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_STDPATHS_DEFS_H
-#define NVIM_OS_STDPATHS_DEFS_H
+#pragma once
/// List of possible XDG variables
typedef enum {
@@ -12,5 +11,3 @@ typedef enum {
kXDGConfigDirs, ///< XDG_CONFIG_DIRS
kXDGDataDirs, ///< XDG_DATA_DIRS
} XDGVarType;
-
-#endif // NVIM_OS_STDPATHS_DEFS_H
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 873302a27d..49b43af6c0 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -1,10 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
-#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -12,32 +8,20 @@
#include "auto/config.h"
#include "nvim/event/loop.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
-struct tm;
-
-static uv_mutex_t delay_mutex;
-static uv_cond_t delay_cond;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.c.generated.h" // IWYU pragma: export
#endif
-/// Initializes the time module
-void time_init(void)
-{
- uv_mutex_init(&delay_mutex);
- uv_cond_init(&delay_cond);
-}
-
/// Gets a high-resolution (nanosecond), monotonically-increasing time relative
/// to an arbitrary time in the past.
///
@@ -73,55 +57,28 @@ uint64_t os_now(void)
void os_delay(uint64_t ms, bool ignoreinput)
{
DLOG("%" PRIu64 " ms", ms);
- if (ignoreinput) {
- if (ms > INT_MAX) {
- ms = INT_MAX;
- }
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int);
- } else {
- os_microdelay(ms * 1000U, ignoreinput);
+ if (ms > INT_MAX) {
+ ms = INT_MAX;
}
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms,
+ ignoreinput ? got_int : os_input_ready(NULL));
}
-/// Sleeps for `us` microseconds.
+/// Sleeps for `ms` milliseconds without checking for events or interrupts.
+///
+/// This blocks even "fast" events which is quite disruptive. This should only
+/// be used in debug code. Prefer os_delay() and decide if the delay should be
+/// interrupted by input or only a CTRL-C.
///
/// @see uv_sleep() (libuv v1.34.0)
///
/// @param us Number of microseconds to sleep.
-/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
-/// If false, waiting is aborted on any input.
-void os_microdelay(uint64_t us, bool ignoreinput)
+void os_sleep(uint64_t ms)
{
- uint64_t elapsed = 0U;
- uint64_t base = uv_hrtime();
- // Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
- const uint64_t ns = (us < UINT64_MAX / 1000U) ? us * 1000U : UINT64_MAX;
-
- uv_mutex_lock(&delay_mutex);
-
- while (elapsed < ns) {
- // If ignoring input, we simply wait the full delay.
- // Else we check for input in ~100ms intervals.
- const uint64_t ns_delta = ignoreinput
- ? ns - elapsed
- : MIN(ns - elapsed, 100000000U); // 100ms
-
- const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
- if (0 != rv && UV_ETIMEDOUT != rv) {
- abort();
- break;
- } // Else: Timeout proceeded normally.
-
- if (!ignoreinput && os_char_avail()) {
- break;
- }
-
- const uint64_t now = uv_hrtime();
- elapsed += now - base;
- base = now;
+ if (ms > UINT_MAX) {
+ ms = UINT_MAX;
}
-
- uv_mutex_unlock(&delay_mutex);
+ uv_sleep((unsigned)ms);
}
// Cache of the current timezone name as retrieved from TZ, or an empty string
diff --git a/src/nvim/os/time.h b/src/nvim/os/time.h
index 1b6c667dbb..2748ba6953 100644
--- a/src/nvim/os/time.h
+++ b/src/nvim/os/time.h
@@ -1,13 +1,10 @@
-#ifndef NVIM_OS_TIME_H
-#define NVIM_OS_TIME_H
+#pragma once
-#include <stdbool.h>
-#include <stdint.h>
-#include <time.h>
+#include <stddef.h> // IWYU pragma: keep
+#include <time.h> // IWYU pragma: keep
-typedef uint64_t Timestamp;
+#include "nvim/os/time_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.h.generated.h"
#endif
-#endif // NVIM_OS_TIME_H
diff --git a/src/nvim/os/time_defs.h b/src/nvim/os/time_defs.h
new file mode 100644
index 0000000000..9b71a6764d
--- /dev/null
+++ b/src/nvim/os/time_defs.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <stdint.h>
+typedef uint64_t Timestamp;
diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c
index b5124bd83a..e683b9383f 100644
--- a/src/nvim/os/tty.c
+++ b/src/nvim/os/tty.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// Terminal/console utils
//
diff --git a/src/nvim/os/tty.h b/src/nvim/os/tty.h
index d771e63768..a24d875c05 100644
--- a/src/nvim/os/tty.h
+++ b/src/nvim/os/tty.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_OS_TTY_H
-#define NVIM_OS_TTY_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/tty.h.generated.h"
+# include "os/tty.h.generated.h" // IWYU pragma: export
#endif
-#endif // NVIM_OS_TTY_H
diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h
index 8d002fc5e9..d2bec7b361 100644
--- a/src/nvim/os/unix_defs.h
+++ b/src/nvim/os/unix_defs.h
@@ -1,14 +1,18 @@
-#ifndef NVIM_OS_UNIX_DEFS_H
-#define NVIM_OS_UNIX_DEFS_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/os_defs.h"
+// IWYU pragma: begin_exports
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <pthread.h>
#include <sys/param.h>
+#include <sys/socket.h>
#include <unistd.h>
#if defined(HAVE_TERMIOS_H)
# include <termios.h>
#endif
-
-// POSIX.1-2008 says that NAME_MAX should be in here
-#include <limits.h>
+// IWYU pragma: end_exports
#define TEMP_DIR_NAMES { "$TMPDIR", "/tmp", ".", "~" }
#define TEMP_FILE_PATH_MAXLEN 256
@@ -21,5 +25,3 @@
// Character that separates entries in $PATH.
#define ENV_SEPCHAR ':'
#define ENV_SEPSTR ":"
-
-#endif // NVIM_OS_UNIX_DEFS_H
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
index ef2986246b..ae0994a73f 100644
--- a/src/nvim/os/users.c
+++ b/src/nvim/os/users.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// users.c -- operating system user information
#include <stdbool.h>
@@ -9,17 +6,20 @@
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/garray.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-#ifdef HAVE_PWD_H
+#include "nvim/vim_defs.h"
+#ifdef HAVE_PWD_FUNCS
# include <pwd.h>
#endif
#ifdef MSWIN
# include <lm.h>
+
+# include "nvim/mbyte.h"
+# include "nvim/message.h"
#endif
// All user names (for ~user completion as done by shell).
@@ -30,7 +30,7 @@ static garray_T ga_users = GA_EMPTY_INIT_VALUE;
static void add_user(garray_T *users, char *user, bool need_copy)
{
char *user_copy = (user != NULL && need_copy)
- ? xstrdup(user) : user;
+ ? xstrdup(user) : user;
if (user_copy == NULL || *user_copy == NUL) {
if (need_copy) {
@@ -50,7 +50,7 @@ int os_get_usernames(garray_T *users)
}
ga_init(users, sizeof(char *), 20);
-#if defined(HAVE_GETPWENT) && defined(HAVE_PWD_H)
+#ifdef HAVE_PWD_FUNCS
{
struct passwd *pw;
@@ -81,7 +81,7 @@ int os_get_usernames(garray_T *users)
}
}
#endif
-#if defined(HAVE_GETPWNAM)
+#ifdef HAVE_PWD_FUNCS
{
const char *user_env = os_getenv("USER");
@@ -141,7 +141,7 @@ int os_get_username(char *s, size_t len)
/// @return OK if a username was found, else FAIL.
int os_get_uname(uv_uid_t uid, char *s, size_t len)
{
-#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID)
+#ifdef HAVE_PWD_FUNCS
struct passwd *pw;
if ((pw = getpwuid(uid)) != NULL // NOLINT(runtime/threadsafe_fn)
@@ -159,7 +159,7 @@ int os_get_uname(uv_uid_t uid, char *s, size_t len)
/// Caller must free() the returned string.
char *os_get_userdir(const char *name)
{
-#if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H)
+#ifdef HAVE_PWD_FUNCS
if (name == NULL || *name == NUL) {
return NULL;
}
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index 4f8a242a51..852059f78b 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_OS_WIN_DEFS_H
-#define NVIM_OS_WIN_DEFS_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/os_defs.h"
#ifndef MSWIN
# error Header must be included only when compiling for Windows.
@@ -86,5 +86,3 @@ typedef int mode_t;
#ifndef STDERR_FILENO
# define STDERR_FILENO 2
#endif
-
-#endif // NVIM_OS_WIN_DEFS_H
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 513f366a27..c7212c7ade 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1,8 +1,4 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <ctype.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@@ -11,32 +7,33 @@
#include <string.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/eval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
enum {
@@ -620,7 +617,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
static int stardepth = 0; // depth for "**" expansion
// Expanding "**" may take a long time, check for CTRL-C.
- if (stardepth > 0) {
+ if (stardepth > 0 && !(flags & EW_NOBREAK)) {
os_breakcheck();
if (got_int) {
return 0;
@@ -641,7 +638,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
while (*path_end != NUL) {
// May ignore a wildcard that has a backslash before it; it will
// be removed by rem_backslash() or file_pat_to_reg_pat() below.
- if (path_end >= path + wildoff && rem_backslash((char *)path_end)) {
+ if (path_end >= path + wildoff && rem_backslash(path_end)) {
*p++ = *path_end++;
} else if (vim_ispathsep_nocolon(*path_end)) {
if (e != NULL) {
@@ -649,14 +646,16 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
}
s = p + 1;
} else if (path_end >= path + wildoff
+#ifdef MSWIN
+ && vim_strchr("*?[~", (uint8_t)(*path_end)) != NULL
+#else
&& (vim_strchr("*?[{~$", (uint8_t)(*path_end)) != NULL
-#ifndef MSWIN
- || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char((char *)path_end)))
+ || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char(path_end))))
#endif
- )) { // NOLINT(whitespace/parens)
+ ) { // NOLINT(whitespace/parens)
e = p;
}
- len = (size_t)(utfc_ptr2len((char *)path_end));
+ len = (size_t)(utfc_ptr2len(path_end));
memcpy(p, path_end, len);
p += len;
path_end += len;
@@ -701,7 +700,8 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
if (flags & (EW_NOERROR | EW_NOTWILD)) {
emsg_silent++;
}
- regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
+ bool nobreak = (flags & EW_NOBREAK);
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC | (nobreak ? RE_NOBREAK : 0));
if (flags & (EW_NOERROR | EW_NOTWILD)) {
emsg_silent--;
}
@@ -727,14 +727,14 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
char *dirpath = (*buf == NUL ? "." : buf);
if (os_file_is_readable(dirpath) && os_scandir(&dir, dirpath)) {
// Find all matching entries.
- char *name;
+ const char *name;
scandir_next_with_dots(NULL); // initialize
- while (!got_int && (name = (char *)scandir_next_with_dots(&dir)) != NULL) {
+ while (!got_int && (name = scandir_next_with_dots(&dir)) != NULL) {
if ((name[0] != '.'
|| starts_with_dot
|| ((flags & EW_DODOT)
&& name[1] != NUL
- && (name[1] != '.' || name[2] != NUL))) // -V557
+ && (name[1] != '.' || name[2] != NUL)))
&& ((regmatch.regprog != NULL && vim_regexec(&regmatch, name, 0))
|| ((flags & EW_NOTWILD)
&& path_fnamencmp(path + (s - buf), name, (size_t)(e - s)) == 0))) {
@@ -810,7 +810,7 @@ static int find_previous_pathsep(char *path, char **psep)
}
/// Returns true if "maybe_unique" is unique wrt other_paths in "gap".
-/// "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]".
+/// "maybe_unique" is the end portion of "((char **)gap->ga_data)[i]".
static bool is_unique(char *maybe_unique, garray_T *gap, int i)
{
char **other_paths = gap->ga_data;
@@ -879,7 +879,7 @@ static void expand_path_option(char *curdir, garray_T *gap)
}
STRMOVE(buf + len + 1, buf);
STRCPY(buf, curdir);
- buf[len] = (char_u)PATHSEP;
+ buf[len] = PATHSEP;
simplify_filename(buf);
}
@@ -973,7 +973,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
for (int i = 0; i < gap->ga_len && !got_int; i++) {
char *path = fnames[i];
int is_in_curdir;
- char *dir_end = (char *)gettail_dir((const char *)path);
+ const char *dir_end = gettail_dir(path);
char *pathsep_p;
char *path_cutoff;
@@ -993,7 +993,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
if (pattern[0] == '*' && pattern[1] == '*'
&& vim_ispathsep_nocolon(pattern[2])
&& path_cutoff != NULL
- && vim_regexec(&regmatch, path_cutoff, (colnr_T)0)
+ && vim_regexec(&regmatch, path_cutoff, 0)
&& is_unique(path_cutoff, gap, i)) {
sort_again = true;
memmove(path, path_cutoff, strlen(path_cutoff) + 1);
@@ -1002,7 +1002,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
// unique path. We start at the end of the path. */
pathsep_p = path + len - 1;
while (find_previous_pathsep(path, &pathsep_p)) {
- if (vim_regexec(&regmatch, pathsep_p + 1, (colnr_T)0)
+ if (vim_regexec(&regmatch, pathsep_p + 1, 0)
&& is_unique(pathsep_p + 1, gap, i)
&& path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) {
sort_again = true;
@@ -1138,7 +1138,7 @@ static int expand_in_path(garray_T *const gap, char *const pattern, const int fl
if (flags & EW_ADDSLASH) {
glob_flags |= WILD_ADD_SLASH;
}
- globpath(paths, pattern, gap, glob_flags);
+ globpath(paths, pattern, gap, glob_flags, false);
xfree(paths);
return gap->ga_len;
@@ -1162,7 +1162,7 @@ static bool has_env_var(char *p)
// Return true if "p" contains a special wildcard character, one that Vim
// cannot expand, requires using a shell.
-static bool has_special_wildchar(char *p)
+static bool has_special_wildchar(char *p, int flags)
{
for (; *p; MB_PTR_ADV(p)) {
// Disallow line break characters.
@@ -1173,6 +1173,10 @@ static bool has_special_wildchar(char *p)
if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') {
p++;
} else if (vim_strchr(SPECIAL_WILDCHAR, (uint8_t)(*p)) != NULL) {
+ // Need a shell for curly braces only when including non-existing files.
+ if (*p == '{' && !(flags & EW_NOTFOUND)) {
+ continue;
+ }
// A { must be followed by a matching }.
if (*p == '{' && vim_strchr(p, '}') == NULL) {
continue;
@@ -1232,7 +1236,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i
// avoids starting the shell for each argument separately.
// For `=expr` do use the internal function.
for (int i = 0; i < num_pat; i++) {
- if (has_special_wildchar(pat[i])
+ if (has_special_wildchar(pat[i], flags)
&& !(vim_backtick(pat[i]) && pat[i][1] == '=')) {
return os_expand_wildcards(num_pat, pat, num_file, file, flags);
}
@@ -1368,10 +1372,10 @@ static int expand_backtick(garray_T *gap, char *pat, int flags)
int cnt = 0;
// Create the command: lop off the backticks.
- char *cmd = xstrnsave(pat + 1, strlen(pat) - 2);
+ char *cmd = xmemdupz(pat + 1, strlen(pat) - 2);
if (*cmd == '=') { // `={expr}`: Expand expression
- buffer = eval_to_string(cmd + 1, &p, true);
+ buffer = eval_to_string(cmd + 1, true);
} else {
buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL);
}
@@ -1498,11 +1502,10 @@ void addfile(garray_T *gap, char *f, int flags)
void simplify_filename(char *filename)
{
int components = 0;
- char *p, *tail, *start;
bool stripping_disabled = false;
bool relative = true;
- p = filename;
+ char *p = filename;
#ifdef BACKSLASH_IN_FILENAME
if (p[0] != NUL && p[1] == ':') { // skip "x:"
p += 2;
@@ -1515,7 +1518,7 @@ void simplify_filename(char *filename)
p++;
} while (vim_ispathsep(*p));
}
- start = p; // remember start after "c:/" or "/" or "///"
+ char *start = p; // remember start after "c:/" or "/" or "///"
do {
// At this point "p" is pointing to the char following a single "/"
@@ -1531,7 +1534,7 @@ void simplify_filename(char *filename)
// and there is no trailing path separator, either strip "/." if
// we are after "start", or strip "." if we are at the beginning
// of an absolute path name.
- tail = p + 1;
+ char *tail = p + 1;
if (p[1] != NUL) {
while (vim_ispathsep(*tail)) {
MB_PTR_ADV(tail);
@@ -1544,21 +1547,20 @@ void simplify_filename(char *filename)
} else if (p[0] == '.' && p[1] == '.'
&& (vim_ispathsep(p[2]) || p[2] == NUL)) {
// Skip to after ".." or "../" or "..///".
- tail = p + 2;
+ char *tail = p + 2;
while (vim_ispathsep(*tail)) {
MB_PTR_ADV(tail);
}
if (components > 0) { // strip one preceding component
bool do_strip = false;
- char saved_char;
// Don't strip for an erroneous file name.
if (!stripping_disabled) {
// If the preceding component does not exist in the file
// system, we strip it. On Unix, we don't accept a symbolic
// link that refers to a non-existent file.
- saved_char = p[-1];
+ char saved_char = p[-1];
p[-1] = NUL;
FileInfo file_info;
if (!os_fileinfo_link(filename, &file_info)) {
@@ -1661,10 +1663,15 @@ void simplify_filename(char *filename)
static char *eval_includeexpr(const char *const ptr, const size_t len)
{
+ const sctx_T save_sctx = current_sctx;
set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len);
- char *res = eval_to_string_safe(curbuf->b_p_inex, NULL,
+ current_sctx = curbuf->b_p_script_ctx[BV_INEX].script_ctx;
+
+ char *res = eval_to_string_safe(curbuf->b_p_inex,
was_set_insecurely(curwin, "includeexpr", OPT_LOCAL));
+
set_vim_var_string(VV_FNAME, NULL, 0);
+ current_sctx = save_sctx;
return res;
}
@@ -1690,8 +1697,11 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha
}
if (options & FNAME_EXP) {
- file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, true,
- rel_fname);
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+
+ file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
+ true, rel_fname, &file_to_find, &search_ctx);
// If the file could not be found in a normal way, try applying
// 'includeexpr' (unless done already).
@@ -1702,7 +1712,7 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha
ptr = tofree;
len = strlen(ptr);
file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
- true, rel_fname);
+ true, rel_fname, &file_to_find, &search_ctx);
}
}
if (file_name == NULL && (options & FNAME_MESS)) {
@@ -1716,9 +1726,12 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha
// appears several times in the path.
while (file_name != NULL && --count > 0) {
xfree(file_name);
- file_name =
- find_file_in_path(ptr, len, options, false, rel_fname);
+ file_name = find_file_in_path(ptr, len, options, false, rel_fname,
+ &file_to_find, &search_ctx);
}
+
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
} else {
file_name = xstrnsave(ptr, len);
}
@@ -1794,11 +1807,11 @@ bool path_with_extension(const char *path, const char *extension)
if (!last_dot) {
return false;
}
- return strcmp(last_dot + 1, extension) == 0;
+ return mb_strcmp_ic((bool)p_fic, last_dot + 1, extension) == 0;
}
/// Return true if "name" is a full (absolute) path name or URL.
-bool vim_isAbsName(char *name)
+bool vim_isAbsName(const char *name)
{
return path_with_url(name) != 0 || path_is_absolute(name);
}
@@ -1859,7 +1872,7 @@ char *fix_fname(const char *fname)
#ifdef UNIX
return FullName_save(fname, true);
#else
- if (!vim_isAbsName((char *)fname)
+ if (!vim_isAbsName(fname)
|| strstr(fname, "..") != NULL
|| strstr(fname, "//") != NULL
# ifdef BACKSLASH_IN_FILENAME
@@ -1911,8 +1924,8 @@ void path_fix_case(char *name)
return;
}
- char *entry;
- while ((entry = (char *)os_scandir_next(&dir))) {
+ const char *entry;
+ while ((entry = os_scandir_next(&dir))) {
// Only accept names that differ in case and are the same byte
// length. TODO: accept different length name.
if (STRICMP(tail, entry) == 0 && strlen(tail) == strlen(entry)) {
@@ -1923,7 +1936,7 @@ void path_fix_case(char *name)
xstrlcpy(newname + (tail - name), entry,
(size_t)(MAXPATHL - (tail - name) + 1));
FileInfo file_info_new;
- if (os_fileinfo_link((char *)newname, &file_info_new)
+ if (os_fileinfo_link(newname, &file_info_new)
&& os_fileinfo_id_equal(&file_info, &file_info_new)) {
STRCPY(tail, entry);
break;
@@ -1956,11 +1969,11 @@ bool same_directory(char *f1, char *f2)
return false;
}
- (void)vim_FullName(f1, (char *)ffname, MAXPATHL, false);
+ (void)vim_FullName(f1, ffname, MAXPATHL, false);
t1 = path_tail_with_sep(ffname);
t2 = path_tail_with_sep(f2);
return t1 - ffname == t2 - f2
- && pathcmp((char *)ffname, f2, (int)(t1 - ffname)) == 0;
+ && pathcmp(ffname, f2, (int)(t1 - ffname)) == 0;
}
// Compare path "p[]" to "q[]".
@@ -1969,12 +1982,11 @@ bool same_directory(char *f1, char *f2)
int pathcmp(const char *p, const char *q, int maxlen)
{
int i, j;
- int c1, c2;
const char *s = NULL;
for (i = 0, j = 0; maxlen < 0 || (i < maxlen && j < maxlen);) {
- c1 = utf_ptr2char(p + i);
- c2 = utf_ptr2char(q + j);
+ int c1 = utf_ptr2char(p + i);
+ int c2 = utf_ptr2char(q + j);
// End of "p": check if "q" also ends or just has a slash.
if (c1 == NUL) {
@@ -2016,12 +2028,12 @@ int pathcmp(const char *p, const char *q, int maxlen)
return 0;
}
- c1 = utf_ptr2char(s + i);
- c2 = utf_ptr2char(s + i + utfc_ptr2len(s + i));
+ int c1 = utf_ptr2char(s + i);
+ int c2 = utf_ptr2char(s + i + utfc_ptr2len(s + i));
// ignore a trailing slash, but not "//" or ":/"
if (c2 == NUL
&& i > 0
- && !after_pathsep((char *)s, (char *)s + i)
+ && !after_pathsep(s, s + i)
#ifdef BACKSLASH_IN_FILENAME
&& (c1 == '/' || c1 == '\\')
#else
@@ -2075,17 +2087,17 @@ char *path_shorten_fname(char *full_path, char *dir_name)
assert(dir_name != NULL);
size_t len = strlen(dir_name);
- // If dir_name is a path head, full_path can always be made relative.
- if (len == (size_t)path_head_length() && is_path_head(dir_name)) {
- return full_path + len;
- }
-
// If full_path and dir_name do not match, it's impossible to make one
// relative to the other.
if (path_fnamencmp(dir_name, full_path, len) != 0) {
return NULL;
}
+ // If dir_name is a path head, full_path can always be made relative.
+ if (len == (size_t)path_head_length() && is_path_head(dir_name)) {
+ return full_path + len;
+ }
+
char *p = full_path + len;
// If *p is not pointing to a path separator, this means that full_path's
@@ -2117,7 +2129,7 @@ int expand_wildcards_eval(char **pat, int *num_file, char ***file, int flags)
int ret = FAIL;
char *eval_pat = NULL;
char *exp_pat = *pat;
- char *ignored_msg;
+ const char *ignored_msg;
size_t usedlen;
const bool is_cur_alt_file = *exp_pat == '%' || *exp_pat == '#';
bool star_follows = false;
@@ -2172,12 +2184,7 @@ int expand_wildcards_eval(char **pat, int *num_file, char ***file, int flags)
/// NULL or points to "".
int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int flags)
{
- int retval;
- int i, j;
- char *p;
- int non_suf_match; // number without matching suffix
-
- retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags);
+ int retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags);
// When keeping all matches, return here
if ((flags & EW_KEEPALL) || retval == FAIL) {
@@ -2186,18 +2193,16 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int
// Remove names that match 'wildignore'.
if (*p_wig) {
- char *ffname;
-
// check all files in (*files)[]
assert(*num_files == 0 || *files != NULL);
- for (i = 0; i < *num_files; i++) {
- ffname = FullName_save((*files)[i], false);
+ for (int i = 0; i < *num_files; i++) {
+ char *ffname = FullName_save((*files)[i], false);
assert((*files)[i] != NULL);
assert(ffname != NULL);
if (match_file_list(p_wig, (*files)[i], ffname)) {
// remove this matching file from the list
xfree((*files)[i]);
- for (j = i; j + 1 < *num_files; j++) {
+ for (int j = i; j + 1 < *num_files; j++) {
(*files)[j] = (*files)[j + 1];
}
(*num_files)--;
@@ -2211,12 +2216,12 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int
// Skip when interrupted, the result probably won't be used.
assert(*num_files == 0 || *files != NULL);
if (*num_files > 1 && !got_int) {
- non_suf_match = 0;
- for (i = 0; i < *num_files; i++) {
+ int non_suf_match = 0; // number without matching suffix
+ for (int i = 0; i < *num_files; i++) {
if (!match_suffix((*files)[i])) {
// Move the name without matching suffix to the front of the list.
- p = (*files)[i];
- for (j = i; j > non_suf_match; j--) {
+ char *p = (*files)[i];
+ for (int j = i; j > non_suf_match; j--) {
(*files)[j] = (*files)[j - 1];
}
(*files)[non_suf_match++] = p;
@@ -2354,11 +2359,11 @@ int append_path(char *path, const char *to_append, size_t max_len)
/// @return FAIL for failure, OK for success.
static int path_to_absolute(const char *fname, char *buf, size_t len, int force)
{
- char *p;
+ const char *p;
*buf = NUL;
char *relative_directory = xmalloc(len);
- char *end_of_path = (char *)fname;
+ const char *end_of_path = fname;
// expand it if forced or not an absolute path
if (force || !path_is_absolute(fname)) {
@@ -2375,7 +2380,6 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force)
end_of_path = p + 1;
} else {
relative_directory[0] = NUL;
- end_of_path = (char *)fname;
}
if (FAIL == path_full_dir_name(relative_directory, buf, len)) {
diff --git a/src/nvim/path.h b/src/nvim/path.h
index c8d192dffe..89f939dd02 100644
--- a/src/nvim/path.h
+++ b/src/nvim/path.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_PATH_H
-#define NVIM_PATH_H
+#pragma once
+
+#include <stddef.h> // IWYU pragma: keep
#include "nvim/func_attr.h"
-#include "nvim/garray.h"
-#include "nvim/types.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Flags for expand_wildcards()
#define EW_DIR 0x01 // include directory names
@@ -26,6 +27,7 @@
#define EW_DODOT 0x4000 // also files starting with a dot
#define EW_EMPTYOK 0x8000 // no matches is not an error
#define EW_NOTENV 0x10000 // do not expand environment variables
+#define EW_NOBREAK 0x20000 // do not invoke breakcheck
/// Return value for the comparison of two files. Also @see path_full_compare.
typedef enum file_comparison {
@@ -39,4 +41,3 @@ typedef enum file_comparison {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "path.h.generated.h"
#endif
-#endif // NVIM_PATH_H
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index 5469d94800..6e9f92c193 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -1,223 +1,34 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// plines.c: calculate the vertical and horizontal size of text in a window
-#include <assert.h>
-#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/state.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "plines.c.generated.h"
#endif
-/// Functions calculating vertical size of text when displayed inside a window.
-/// Calls horizontal size functions defined below.
-
-/// Return the number of window lines occupied by buffer line "lnum".
-/// Includes any filler lines.
-///
-/// @param winheight when true limit to window height
-int plines_win(win_T *wp, linenr_T lnum, bool winheight)
-{
- // Check for filler lines above this buffer line. When folded the result
- // is one line anyway.
- return plines_win_nofill(wp, lnum, winheight) + win_get_fill(wp, lnum);
-}
-
-/// Return the number of filler lines above "lnum".
-///
-/// @param wp
-/// @param lnum
-///
-/// @return Number of filler lines above lnum
-int win_get_fill(win_T *wp, linenr_T lnum)
-{
- int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone);
-
- // be quick when there are no filler lines
- if (diffopt_filler()) {
- int n = diff_check(wp, lnum);
-
- if (n > 0) {
- return virt_lines + n;
- }
- }
- return virt_lines;
-}
-
-bool win_may_fill(win_T *wp)
-{
- return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks;
-}
-
-/// Return the number of window lines occupied by buffer line "lnum".
-/// Does not include filler lines.
-///
-/// @param winheight when true limit to window height
-int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight)
-{
- if (!wp->w_p_wrap) {
- return 1;
- }
-
- if (wp->w_width_inner == 0) {
- return 1;
- }
-
- // Folded lines are handled just like an empty line.
- if (lineFolded(wp, lnum)) {
- return 1;
- }
-
- const int lines = plines_win_nofold(wp, lnum);
- if (winheight && lines > wp->w_height_inner) {
- return wp->w_height_inner;
- }
- return lines;
-}
-
-/// @Return number of window lines physical line "lnum" will occupy in window
-/// "wp". Does not care about folding, 'wrap' or 'diff'.
-int plines_win_nofold(win_T *wp, linenr_T lnum)
-{
- char *s;
- unsigned int col;
- int width;
-
- s = ml_get_buf(wp->w_buffer, lnum, false);
- if (*s == NUL) { // empty line
- return 1;
- }
- col = win_linetabsize(wp, lnum, s, MAXCOL);
-
- // If list mode is on, then the '$' at the end of the line may take up one
- // extra column.
- if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) {
- col += 1;
- }
-
- // Add column offset for 'number', 'relativenumber' and 'foldcolumn'.
- width = wp->w_width_inner - win_col_off(wp);
- if (width <= 0 || col > 32000) {
- return 32000; // bigger than the number of screen columns
- }
- if (col <= (unsigned int)width) {
- return 1;
- }
- col -= (unsigned int)width;
- width += win_col_off2(wp);
- assert(col <= INT_MAX && (int)col < INT_MAX - (width - 1));
- return ((int)col + (width - 1)) / width + 1;
-}
-
-/// Like plines_win(), but only reports the number of physical screen lines
-/// used from the start of the line to the given column number.
-int plines_win_col(win_T *wp, linenr_T lnum, long column)
-{
- // Check for filler lines above this buffer line. When folded the result
- // is one line anyway.
- int lines = win_get_fill(wp, lnum);
-
- if (!wp->w_p_wrap) {
- return lines + 1;
- }
-
- if (wp->w_width_inner == 0) {
- return lines + 1;
- }
-
- char *line = ml_get_buf(wp->w_buffer, lnum, false);
-
- colnr_T col = 0;
- chartabsize_T cts;
-
- init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
- while (*cts.cts_ptr != NUL && --column >= 0) {
- cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
- MB_PTR_ADV(cts.cts_ptr);
- }
-
- // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not
- // in MODE_INSERT state, then col must be adjusted so that it represents the
- // last screen position of the TAB. This only fixes an error when the TAB
- // wraps from one screen line to the next (when 'columns' is not a multiple
- // of 'ts') -- webb.
- col = cts.cts_vcol;
- if (*cts.cts_ptr == TAB && (State & MODE_NORMAL)
- && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
- col += win_lbr_chartabsize(&cts, NULL) - 1;
- }
- clear_chartabsize_arg(&cts);
-
- // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
- int width = wp->w_width_inner - win_col_off(wp);
- if (width <= 0) {
- return 9999;
- }
-
- lines += 1;
- if (col > width) {
- lines += (col - width) / (width + win_col_off2(wp)) + 1;
- }
- return lines;
-}
-
-/// Get the number of screen lines lnum takes up. This takes care of
-/// both folds and topfill, and limits to the current window height.
-///
-/// @param[in] wp window line is in
-/// @param[in] lnum line number
-/// @param[out] nextp if not NULL, the line after a fold
-/// @param[out] foldedp if not NULL, whether lnum is on a fold
-/// @param[in] cache whether to use the window's cache for folds
-///
-/// @return the total number of screen lines
-int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp,
- const bool cache)
-{
- bool folded = hasFoldingWin(wp, lnum, NULL, nextp, cache, NULL);
- if (foldedp) {
- *foldedp = folded;
- }
- if (folded) {
- return 1;
- } else if (lnum == wp->w_topline) {
- return plines_win_nofill(wp, lnum, true) + wp->w_topfill;
- }
- return plines_win(wp, lnum, true);
-}
-
-int plines_m_win(win_T *wp, linenr_T first, linenr_T last)
-{
- int count = 0;
-
- while (first <= last) {
- linenr_T next = first;
- count += plines_win_full(wp, first, &next, NULL, false);
- first = next + 1;
- }
- return count;
-}
-
/// Functions calculating horizontal size of text, when displayed in a window.
/// Return the number of characters 'c' will take on the screen, taking
@@ -243,12 +54,12 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col)
/// @param s
///
/// @return Number of characters the string will take on the screen.
-int linetabsize(char *s)
+int linetabsize_str(char *s)
{
return linetabsize_col(0, s);
}
-/// Like linetabsize(), but "s" starts at column "startcol".
+/// Like linetabsize_str(), but "s" starts at column "startcol".
///
/// @param startcol
/// @param s
@@ -265,50 +76,76 @@ int linetabsize_col(int startcol, char *s)
return cts.cts_vcol;
}
-/// Like linetabsize(), but for a given window instead of the current one.
+/// Like linetabsize_str(), but for a given window instead of the current one.
///
/// @param wp
/// @param line
/// @param len
///
/// @return Number of characters the string will take on the screen.
-unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
+int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
{
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
- for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < line + len);
- MB_PTR_ADV(cts.cts_ptr)) {
- cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
- }
+ win_linetabsize_cts(&cts, len);
clear_chartabsize_arg(&cts);
- return (unsigned int)cts.cts_vcol;
+ return cts.cts_vcol;
+}
+
+/// Return the number of cells line "lnum" of window "wp" will take on the
+/// screen, taking into account the size of a tab and inline virtual text.
+int linetabsize(win_T *wp, linenr_T lnum)
+{
+ return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL);
+}
+
+void win_linetabsize_cts(chartabsize_T *cts, colnr_T len)
+{
+ for (; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len);
+ MB_PTR_ADV(cts->cts_ptr)) {
+ cts->cts_vcol += win_lbr_chartabsize(cts, NULL);
+ }
+ // check for inline virtual text after the end of the line
+ if (len == MAXCOL && cts->cts_has_virt_text && *cts->cts_ptr == NUL) {
+ (void)win_lbr_chartabsize(cts, NULL);
+ cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right;
+ }
}
/// Prepare the structure passed to chartabsize functions.
///
/// "line" is the start of the line, "ptr" is the first relevant character.
-/// When "lnum" is zero do not use text properties that insert text.
-void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum FUNC_ATTR_UNUSED,
- colnr_T col, char *line, char *ptr)
+/// When "lnum" is zero do not use inline virtual text.
+void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char *line,
+ char *ptr)
{
cts->cts_win = wp;
cts->cts_vcol = col;
cts->cts_line = line;
cts->cts_ptr = ptr;
- cts->cts_cur_text_width = 0;
- // TODO(bfredl): actually lookup inline virtual text here
+ cts->cts_max_head_vcol = 0;
+ cts->cts_cur_text_width_left = 0;
+ cts->cts_cur_text_width_right = 0;
cts->cts_has_virt_text = false;
+ cts->cts_row = lnum - 1;
+
+ if (cts->cts_row >= 0 && wp->w_buffer->b_virt_text_inline > 0) {
+ marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter);
+ MTKey mark = marktree_itr_current(cts->cts_iter);
+ if (mark.pos.row == cts->cts_row) {
+ cts->cts_has_virt_text = true;
+ }
+ }
}
/// Free any allocated item in "cts".
void clear_chartabsize_arg(chartabsize_T *cts)
-{}
+{
+}
/// like win_chartabsize(), but also check for line breaks on the screen
///
-/// @param line
-/// @param s
-/// @param col
+/// @param cts
///
/// @return The number of characters taken up on the screen.
int lbr_chartabsize(chartabsize_T *cts)
@@ -325,47 +162,39 @@ int lbr_chartabsize(chartabsize_T *cts)
/// Call lbr_chartabsize() and advance the pointer.
///
-/// @param line
-/// @param s
-/// @param col
+/// @param cts
///
/// @return The number of characters take up on the screen.
int lbr_chartabsize_adv(chartabsize_T *cts)
{
- int retval;
-
- retval = lbr_chartabsize(cts);
+ int retval = lbr_chartabsize(cts);
MB_PTR_ADV(cts->cts_ptr);
return retval;
}
+/// Get the number of characters taken up on the screen indicated by "cts".
+/// "cts->cts_cur_text_width_left" and "cts->cts_cur_text_width_right" are set
+/// to the extra size for inline virtual text.
/// This function is used very often, keep it fast!!!!
///
-/// If "headp" not NULL, set *headp to the size of what we for 'showbreak'
-/// string at start of line. Warning: *headp is only set if it's a non-zero
-/// value, init to 0 before calling.
+/// If "headp" not NULL, set "*headp" to the size of 'showbreak'/'breakindent'
+/// included in the return value.
+/// When "cts->cts_max_head_vcol" is positive, only count in "*headp" the size
+/// of 'showbreak'/'breakindent' before "cts->cts_max_head_vcol".
+/// When "cts->cts_max_head_vcol" is negative, only count in "*headp" the size
+/// of 'showbreak'/'breakindent' before where cursor should be placed.
///
-/// @param cts
-/// @param headp
-///
-/// @return The number of characters taken up on the screen.
+/// Warning: "*headp" may not be set if it's 0, init to 0 before calling.
int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
{
win_T *wp = cts->cts_win;
char *line = cts->cts_line; // start of the line
char *s = cts->cts_ptr;
colnr_T vcol = cts->cts_vcol;
-
- colnr_T col2;
- colnr_T col_adj = 0; // vcol + screen size of tab
- colnr_T colmax;
- int added;
int mb_added = 0;
- int numberextra;
- char *ps;
- int n;
- cts->cts_cur_text_width = 0;
+ cts->cts_cur_text_width_left = 0;
+ cts->cts_cur_text_width_right = 0;
// No 'linebreak', 'showbreak' and 'breakindent': return quickly.
if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL
@@ -376,134 +205,186 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
return win_chartabsize(wp, s, vcol);
}
- // First get normal size, without 'linebreak' or virtual text
+ bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL;
+
+ // First get normal size, without 'linebreak' or inline virtual text
int size = win_chartabsize(wp, s, vcol);
+ if (*s == NUL && !has_lcs_eol) {
+ size = 0; // NUL is not displayed
+ }
+ bool is_doublewidth = size == 2 && MB_BYTE2LEN((uint8_t)(*s)) > 1;
+
if (cts->cts_has_virt_text) {
- // TODO(bfredl): inline virtual text
+ int tab_size = size;
+ int col = (int)(s - line);
+ while (true) {
+ MTKey mark = marktree_itr_current(cts->cts_iter);
+ if (mark.pos.row != cts->cts_row || mark.pos.col > col) {
+ break;
+ } else if (mark.pos.col == col) {
+ if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) {
+ DecorInline decor = mt_decor(mark);
+ DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL;
+ while (vt) {
+ if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) {
+ if (mt_right(mark)) {
+ cts->cts_cur_text_width_right += vt->width;
+ } else {
+ cts->cts_cur_text_width_left += vt->width;
+ }
+ size += vt->width;
+ if (*s == TAB) {
+ // tab size changes because of the inserted text
+ size -= tab_size;
+ tab_size = win_chartabsize(wp, s, vcol + size);
+ size += tab_size;
+ }
+ }
+ vt = vt->next;
+ }
+ }
+ }
+ marktree_itr_next(wp->w_buffer->b_marktree, cts->cts_iter);
+ }
+ }
+
+ if (is_doublewidth && wp->w_p_wrap && in_win_border(wp, vcol + size - 2)) {
+ // Count the ">" in the last column.
+ size++;
+ mb_added = 1;
+ }
+
+ // May have to add something for 'breakindent' and/or 'showbreak'
+ // string at the start of a screen line.
+ int head = mb_added;
+ char *const sbr = get_showbreak_value(wp);
+ // When "size" is 0, no new screen line is started.
+ if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) {
+ int col_off_prev = win_col_off(wp);
+ int width2 = wp->w_width_inner - col_off_prev + win_col_off2(wp);
+ colnr_T wcol = vcol + col_off_prev;
+ colnr_T max_head_vcol = cts->cts_max_head_vcol;
+ int added = 0;
+
+ // cells taken by 'showbreak'/'breakindent' before current char
+ int head_prev = 0;
+ if (wcol >= wp->w_width_inner) {
+ wcol -= wp->w_width_inner;
+ col_off_prev = wp->w_width_inner - width2;
+ if (wcol >= width2 && width2 > 0) {
+ wcol %= width2;
+ }
+ if (*sbr != NUL) {
+ head_prev += vim_strsize(sbr);
+ }
+ if (wp->w_p_bri) {
+ head_prev += get_breakindent_win(wp, line);
+ }
+ if (wcol < head_prev) {
+ head_prev -= wcol;
+ wcol += head_prev;
+ added += head_prev;
+ if (max_head_vcol <= 0 || vcol < max_head_vcol) {
+ head += head_prev;
+ }
+ } else {
+ head_prev = 0;
+ }
+ wcol += col_off_prev;
+ }
+
+ if (wcol + size > wp->w_width) {
+ // cells taken by 'showbreak'/'breakindent' halfway current char
+ int head_mid = 0;
+ if (*sbr != NUL) {
+ head_mid += vim_strsize(sbr);
+ }
+ if (wp->w_p_bri) {
+ head_mid += get_breakindent_win(wp, line);
+ }
+ if (head_mid > 0 && wcol + size > wp->w_width_inner) {
+ // Calculate effective window width.
+ int prev_rem = wp->w_width_inner - wcol;
+ int width = width2 - head_mid;
+
+ if (width <= 0) {
+ width = 1;
+ }
+ // Divide "size - prev_rem" by "width", rounding up.
+ int cnt = (size - prev_rem + width - 1) / width;
+ added += cnt * head_mid;
+
+ if (max_head_vcol == 0 || vcol + size + added < max_head_vcol) {
+ head += cnt * head_mid;
+ } else if (max_head_vcol > vcol + head_prev + prev_rem) {
+ head += (max_head_vcol - (vcol + head_prev + prev_rem)
+ + width2 - 1) / width2 * head_mid;
+ } else if (max_head_vcol < 0) {
+ int off = virt_text_cursor_off(cts, *s == NUL);
+ if (off >= prev_rem) {
+ if (size > off) {
+ head += (1 + (off - prev_rem) / width) * head_mid;
+ } else {
+ head += (off - prev_rem + width - 1) / width * head_mid;
+ }
+ }
+ }
+ }
+ }
+
+ size += added;
}
- int c = (uint8_t)(*s);
- if (*s == TAB) {
- col_adj = size - 1;
+ if (headp != NULL) {
+ *headp = head;
}
+ colnr_T vcol_start = 0; // start from where to consider linebreak
// If 'linebreak' set check at a blank before a non-blank if the line
// needs a break here
- if (wp->w_p_lbr
- && vim_isbreak(c)
+ if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width_inner != 0) {
+ char *t = cts->cts_line;
+ while (vim_isbreak((uint8_t)t[0])) {
+ t++;
+ }
+ vcol_start = (colnr_T)(t - cts->cts_line);
+ }
+ if (wp->w_p_lbr && vcol_start <= vcol
+ && vim_isbreak((uint8_t)s[0])
&& !vim_isbreak((uint8_t)s[1])
&& wp->w_p_wrap
- && (wp->w_width_inner != 0)) {
+ && wp->w_width_inner != 0) {
// Count all characters from first non-blank after a blank up to next
// non-blank after a blank.
- numberextra = win_col_off(wp);
- col2 = vcol;
- colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj);
-
+ int numberextra = win_col_off(wp);
+ colnr_T col_adj = size - 1;
+ colnr_T colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj);
if (vcol >= colmax) {
colmax += col_adj;
- n = colmax + win_col_off2(wp);
-
+ int n = colmax + win_col_off2(wp);
if (n > 0) {
colmax += (((vcol - colmax) / n) + 1) * n - col_adj;
}
}
- for (;;) {
- ps = s;
+ colnr_T vcol2 = vcol;
+ while (true) {
+ char *ps = s;
MB_PTR_ADV(s);
- c = (uint8_t)(*s);
-
+ int c = (uint8_t)(*s);
if (!(c != NUL
- && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((uint8_t)(*ps))))) {
+ && (vim_isbreak(c) || vcol2 == vcol || !vim_isbreak((uint8_t)(*ps))))) {
break;
}
- col2 += win_chartabsize(wp, s, col2);
-
- if (col2 >= colmax) { // doesn't fit
+ vcol2 += win_chartabsize(wp, s, vcol2);
+ if (vcol2 >= colmax) { // doesn't fit
size = colmax - vcol + col_adj;
break;
}
}
- } else if ((size == 2)
- && (MB_BYTE2LEN((uint8_t)(*s)) > 1)
- && wp->w_p_wrap
- && in_win_border(wp, vcol)) {
- // Count the ">" in the last column.
- size++;
- mb_added = 1;
- }
-
- // May have to add something for 'breakindent' and/or 'showbreak'
- // string at start of line.
- // Set *headp to the size of what we add.
- // Do not use 'showbreak' at the NUL after the text.
- added = 0;
- char *const sbr = c == NUL ? empty_option : get_showbreak_value(wp);
- if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) {
- colnr_T sbrlen = 0;
- int numberwidth = win_col_off(wp);
-
- numberextra = numberwidth;
- vcol += numberextra + mb_added;
-
- if (vcol >= (colnr_T)wp->w_width_inner) {
- vcol -= wp->w_width_inner;
- numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp));
- if (vcol >= numberextra && numberextra > 0) {
- vcol %= numberextra;
- }
- if (*sbr != NUL) {
- sbrlen = (colnr_T)mb_charlen(sbr);
- if (vcol >= sbrlen) {
- vcol -= sbrlen;
- }
- }
- if (vcol >= numberextra && numberextra > 0) {
- vcol %= numberextra;
- } else if (vcol > 0 && numberextra > 0) {
- vcol += numberwidth - win_col_off2(wp);
- }
-
- numberwidth -= win_col_off2(wp);
- }
-
- if (vcol == 0 || (vcol + size + sbrlen > (colnr_T)wp->w_width_inner)) {
- if (*sbr != NUL) {
- if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) {
- // Calculate effective window width.
- int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth;
- int prev_width = vcol ? ((colnr_T)wp->w_width_inner - (sbrlen + vcol))
- : 0;
-
- if (width <= 0) {
- width = 1;
- }
- added += ((size - prev_width) / width) * vim_strsize(sbr);
- if ((size - prev_width) % width) {
- // Wrapped, add another length of 'sbr'.
- added += vim_strsize(sbr);
- }
- } else {
- added += vim_strsize(sbr);
- }
- }
-
- if (wp->w_p_bri) {
- added += get_breakindent_win(wp, line);
- }
-
- size += added;
- if (vcol != 0) {
- added = 0;
- }
- }
}
- if (headp != NULL) {
- *headp = added + mb_added;
- }
return size;
}
@@ -511,9 +392,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
/// 'wrap' is on. This means we need to check for a double-byte character that
/// doesn't fit at the end of the screen line.
///
-/// @param wp
-/// @param s
-/// @param col
+/// @param cts
/// @param headp
///
/// @return The number of characters take up on the screen.
@@ -522,14 +401,13 @@ static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp)
win_T *wp = cts->cts_win;
char *s = cts->cts_ptr;
colnr_T col = cts->cts_vcol;
- int n;
if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
return tabstop_padding(col,
wp->w_buffer->b_p_ts,
wp->w_buffer->b_p_vts_array);
}
- n = ptr2cells(s);
+ int n = ptr2cells(s);
// Add one cell for a double-width character in the last column of the
// window, displayed with a ">".
@@ -541,3 +419,569 @@ static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp)
}
return n;
}
+
+/// Check that virtual column "vcol" is in the rightmost column of window "wp".
+///
+/// @param wp window
+/// @param vcol column number
+static bool in_win_border(win_T *wp, colnr_T vcol)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (wp->w_width_inner == 0) {
+ // there is no border
+ return false;
+ }
+ int width1 = wp->w_width_inner - win_col_off(wp); // width of first line (after line number)
+
+ if ((int)vcol < width1 - 1) {
+ return false;
+ }
+
+ if ((int)vcol == width1 - 1) {
+ return true;
+ }
+ int width2 = width1 + win_col_off2(wp); // width of further lines
+
+ if (width2 <= 0) {
+ return false;
+ }
+ return (vcol - width1) % width2 == width2 - 1;
+}
+
+/// Get how many virtual columns inline virtual text should offset the cursor.
+///
+/// @param cts should contain information stored by win_lbr_chartabsize()
+/// about widths of left and right gravity virtual text
+/// @param on_NUL whether this is the end of the line
+static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL)
+{
+ int off = 0;
+ if (!on_NUL || !(State & MODE_NORMAL)) {
+ off += cts->cts_cur_text_width_left;
+ }
+ if (!on_NUL && (State & MODE_NORMAL)) {
+ off += cts->cts_cur_text_width_right;
+ }
+ return off;
+}
+
+/// Get virtual column number of pos.
+/// start: on the first position of this character (TAB, ctrl)
+/// cursor: where the cursor is on this character (first char, except for TAB)
+/// end: on the last position of this character (TAB, ctrl)
+///
+/// This is used very often, keep it fast!
+///
+/// @param wp
+/// @param pos
+/// @param start
+/// @param cursor
+/// @param end
+void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
+{
+ char *ptr; // points to current char
+ char *posptr; // points to char at pos->col
+ int incr;
+ int head;
+ colnr_T *vts = wp->w_buffer->b_p_vts_array;
+ int ts = (int)wp->w_buffer->b_p_ts;
+
+ colnr_T vcol = 0;
+ char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line
+
+ if (pos->col == MAXCOL) {
+ // continue until the NUL
+ posptr = NULL;
+ } else {
+ // In a few cases the position can be beyond the end of the line.
+ for (colnr_T i = 0; i < pos->col; i++) {
+ if (ptr[i] == NUL) {
+ pos->col = i;
+ break;
+ }
+ }
+ posptr = ptr + pos->col;
+ posptr -= utf_head_off(line, posptr);
+ }
+
+ chartabsize_T cts;
+ bool on_NUL = false;
+ init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line);
+ cts.cts_max_head_vcol = -1;
+
+ // This function is used very often, do some speed optimizations.
+ // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set
+ // and there are no virtual text use a simple loop.
+ // Also use this when 'list' is set but tabs take their normal size.
+ if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL))
+ && !wp->w_p_lbr
+ && *get_showbreak_value(wp) == NUL
+ && !wp->w_p_bri
+ && !cts.cts_has_virt_text) {
+ while (true) {
+ head = 0;
+ int c = (uint8_t)(*ptr);
+
+ // make sure we don't go past the end of the line
+ if (c == NUL) {
+ // NUL at end of line only takes one column
+ incr = 1;
+ break;
+ }
+
+ // A tab gets expanded, depending on the current column
+ if (c == TAB) {
+ incr = tabstop_padding(vcol, ts, vts);
+ } else {
+ // For utf-8, if the byte is >= 0x80, need to look at
+ // further bytes to find the cell width.
+ if (c >= 0x80) {
+ incr = utf_ptr2cells(ptr);
+ } else {
+ incr = byte2cells(c);
+ }
+
+ // If a double-cell char doesn't fit at the end of a line
+ // it wraps to the next line, it's like this char is three
+ // cells wide.
+ if ((incr == 2)
+ && wp->w_p_wrap
+ && (MB_BYTE2LEN((uint8_t)(*ptr)) > 1)
+ && in_win_border(wp, vcol)) {
+ incr++;
+ head = 1;
+ }
+ }
+
+ if ((posptr != NULL) && (ptr >= posptr)) {
+ // character at pos->col
+ break;
+ }
+
+ vcol += incr;
+ MB_PTR_ADV(ptr);
+ }
+ } else {
+ while (true) {
+ // A tab gets expanded, depending on the current column
+ // Other things also take up space.
+ head = 0;
+ incr = win_lbr_chartabsize(&cts, &head);
+
+ // make sure we don't go past the end of the line
+ if (*cts.cts_ptr == NUL) {
+ // NUL at end of line only takes one column, unless there is virtual text
+ incr = MAX(1, cts.cts_cur_text_width_left + cts.cts_cur_text_width_right);
+ on_NUL = true;
+ break;
+ }
+
+ if ((posptr != NULL) && (cts.cts_ptr >= posptr)) {
+ // character at pos->col
+ break;
+ }
+
+ cts.cts_vcol += incr;
+ MB_PTR_ADV(cts.cts_ptr);
+ }
+ vcol = cts.cts_vcol;
+ ptr = cts.cts_ptr;
+ }
+ clear_chartabsize_arg(&cts);
+
+ if (start != NULL) {
+ *start = vcol + head;
+ }
+
+ if (end != NULL) {
+ *end = vcol + incr - 1;
+ }
+
+ if (cursor != NULL) {
+ if ((*ptr == TAB)
+ && (State & MODE_NORMAL)
+ && !wp->w_p_list
+ && !virtual_active()
+ && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) {
+ // cursor at end
+ *cursor = vcol + incr - 1;
+ } else {
+ vcol += virt_text_cursor_off(&cts, on_NUL);
+ // cursor at start
+ *cursor = vcol + head;
+ }
+ }
+}
+
+/// Get virtual cursor column in the current window, pretending 'list' is off.
+///
+/// @param posp
+///
+/// @retujrn The virtual cursor column.
+colnr_T getvcol_nolist(pos_T *posp)
+{
+ int list_save = curwin->w_p_list;
+ colnr_T vcol;
+
+ curwin->w_p_list = false;
+ if (posp->coladd) {
+ getvvcol(curwin, posp, NULL, &vcol, NULL);
+ } else {
+ getvcol(curwin, posp, NULL, &vcol, NULL);
+ }
+ curwin->w_p_list = list_save;
+ return vcol;
+}
+
+/// Get virtual column in virtual mode.
+///
+/// @param wp
+/// @param pos
+/// @param start
+/// @param cursor
+/// @param end
+void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
+{
+ colnr_T col;
+
+ if (virtual_active()) {
+ // For virtual mode, only want one value
+ getvcol(wp, pos, &col, NULL, NULL);
+
+ colnr_T coladd = pos->coladd;
+ colnr_T endadd = 0;
+
+ // Cannot put the cursor on part of a wide character.
+ char *ptr = ml_get_buf(wp->w_buffer, pos->lnum);
+
+ if (pos->col < (colnr_T)strlen(ptr)) {
+ int c = utf_ptr2char(ptr + pos->col);
+ if ((c != TAB) && vim_isprintc(c)) {
+ endadd = (colnr_T)(char2cells(c) - 1);
+ if (coladd > endadd) {
+ // past end of line
+ endadd = 0;
+ } else {
+ coladd = 0;
+ }
+ }
+ }
+ col += coladd;
+
+ if (start != NULL) {
+ *start = col;
+ }
+
+ if (cursor != NULL) {
+ *cursor = col;
+ }
+
+ if (end != NULL) {
+ *end = col + endadd;
+ }
+ } else {
+ getvcol(wp, pos, start, cursor, end);
+ }
+}
+
+/// Get the leftmost and rightmost virtual column of pos1 and pos2.
+/// Used for Visual block mode.
+///
+/// @param wp
+/// @param pos1
+/// @param pos2
+/// @param left
+/// @param right
+void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right)
+{
+ colnr_T from1;
+ colnr_T from2;
+ colnr_T to1;
+ colnr_T to2;
+
+ if (lt(*pos1, *pos2)) {
+ getvvcol(wp, pos1, &from1, NULL, &to1);
+ getvvcol(wp, pos2, &from2, NULL, &to2);
+ } else {
+ getvvcol(wp, pos2, &from1, NULL, &to1);
+ getvvcol(wp, pos1, &from2, NULL, &to2);
+ }
+
+ if (from2 < from1) {
+ *left = from2;
+ } else {
+ *left = from1;
+ }
+
+ if (to2 > to1) {
+ if ((*p_sel == 'e') && (from2 - 1 >= to1)) {
+ *right = from2 - 1;
+ } else {
+ *right = to2;
+ }
+ } else {
+ *right = to1;
+ }
+}
+
+/// Functions calculating vertical size of text when displayed inside a window.
+/// Calls horizontal size functions defined above.
+
+/// Check if there may be filler lines anywhere in window "wp".
+bool win_may_fill(win_T *wp)
+{
+ return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks;
+}
+
+/// Return the number of filler lines above "lnum".
+///
+/// @param wp
+/// @param lnum
+///
+/// @return Number of filler lines above lnum
+int win_get_fill(win_T *wp, linenr_T lnum)
+{
+ int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone);
+
+ // be quick when there are no filler lines
+ if (diffopt_filler()) {
+ int n = diff_check(wp, lnum);
+
+ if (n > 0) {
+ return virt_lines + n;
+ }
+ }
+ return virt_lines;
+}
+
+/// Return the number of window lines occupied by buffer line "lnum".
+/// Includes any filler lines.
+///
+/// @param limit_winheight when true limit to window height
+int plines_win(win_T *wp, linenr_T lnum, bool limit_winheight)
+{
+ // Check for filler lines above this buffer line.
+ return plines_win_nofill(wp, lnum, limit_winheight) + win_get_fill(wp, lnum);
+}
+
+/// Return the number of window lines occupied by buffer line "lnum".
+/// Does not include filler lines.
+///
+/// @param limit_winheight when true limit to window height
+int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight)
+{
+ if (!wp->w_p_wrap) {
+ return 1;
+ }
+
+ if (wp->w_width_inner == 0) {
+ return 1;
+ }
+
+ // Folded lines are handled just like an empty line.
+ if (lineFolded(wp, lnum)) {
+ return 1;
+ }
+
+ const int lines = plines_win_nofold(wp, lnum);
+ if (limit_winheight && lines > wp->w_height_inner) {
+ return wp->w_height_inner;
+ }
+ return lines;
+}
+
+/// Get number of window lines physical line "lnum" will occupy in window "wp".
+/// Does not care about folding, 'wrap' or filler lines.
+int plines_win_nofold(win_T *wp, linenr_T lnum)
+{
+ char *s = ml_get_buf(wp->w_buffer, lnum);
+ chartabsize_T cts;
+ init_chartabsize_arg(&cts, wp, lnum, 0, s, s);
+ if (*s == NUL && !cts.cts_has_virt_text) {
+ return 1; // be quick for an empty line
+ }
+ win_linetabsize_cts(&cts, (colnr_T)MAXCOL);
+ clear_chartabsize_arg(&cts);
+ int64_t col = cts.cts_vcol;
+
+ // If list mode is on, then the '$' at the end of the line may take up one
+ // extra column.
+ if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) {
+ col += 1;
+ }
+
+ // Add column offset for 'number', 'relativenumber' and 'foldcolumn'.
+ int width = wp->w_width_inner - win_col_off(wp);
+ if (width <= 0) {
+ return 32000; // bigger than the number of screen lines
+ }
+ if (col <= width) {
+ return 1;
+ }
+ col -= width;
+ width += win_col_off2(wp);
+ const int64_t lines = (col + (width - 1)) / width + 1;
+ return (lines > 0 && lines <= INT_MAX) ? (int)lines : INT_MAX;
+}
+
+/// Like plines_win(), but only reports the number of physical screen lines
+/// used from the start of the line to the given column number.
+int plines_win_col(win_T *wp, linenr_T lnum, long column)
+{
+ // Check for filler lines above this buffer line.
+ int lines = win_get_fill(wp, lnum);
+
+ if (!wp->w_p_wrap) {
+ return lines + 1;
+ }
+
+ if (wp->w_width_inner == 0) {
+ return lines + 1;
+ }
+
+ char *line = ml_get_buf(wp->w_buffer, lnum);
+
+ colnr_T col = 0;
+ chartabsize_T cts;
+
+ init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
+ while (*cts.cts_ptr != NUL && --column >= 0) {
+ cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
+ MB_PTR_ADV(cts.cts_ptr);
+ }
+
+ // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not
+ // in MODE_INSERT state, then col must be adjusted so that it represents the
+ // last screen position of the TAB. This only fixes an error when the TAB
+ // wraps from one screen line to the next (when 'columns' is not a multiple
+ // of 'ts') -- webb.
+ col = cts.cts_vcol;
+ if (*cts.cts_ptr == TAB && (State & MODE_NORMAL)
+ && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
+ col += win_lbr_chartabsize(&cts, NULL) - 1;
+ }
+ clear_chartabsize_arg(&cts);
+
+ // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
+ int width = wp->w_width_inner - win_col_off(wp);
+ if (width <= 0) {
+ return 9999;
+ }
+
+ lines += 1;
+ if (col > width) {
+ lines += (col - width) / (width + win_col_off2(wp)) + 1;
+ }
+ return lines;
+}
+
+/// Get the number of screen lines buffer line "lnum" will take in window "wp".
+/// This takes care of both folds and topfill.
+///
+/// XXX: Because of topfill, this only makes sense when lnum >= wp->w_topline.
+///
+/// @param[in] wp window the line is in
+/// @param[in] lnum line number
+/// @param[out] nextp if not NULL, the line after a fold
+/// @param[out] foldedp if not NULL, whether lnum is on a fold
+/// @param[in] cache whether to use the window's cache for folds
+/// @param[in] limit_winheight when true limit to window height
+///
+/// @return the total number of screen lines
+int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp,
+ const bool cache, const bool limit_winheight)
+{
+ bool folded = hasFoldingWin(wp, lnum, &lnum, nextp, cache, NULL);
+ if (foldedp != NULL) {
+ *foldedp = folded;
+ }
+ return ((folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) +
+ (lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum)));
+}
+
+/// Get the number of screen lines a range of buffer lines will take in window "wp".
+/// This takes care of both folds and topfill.
+///
+/// XXX: Because of topfill, this only makes sense when first >= wp->w_topline.
+///
+/// @param first first line number
+/// @param last last line number
+/// @param limit_winheight when true limit each line to window height
+///
+/// @see win_text_height
+int plines_m_win(win_T *wp, linenr_T first, linenr_T last, bool limit_winheight)
+{
+ int count = 0;
+
+ while (first <= last) {
+ linenr_T next = first;
+ count += plines_win_full(wp, first, &next, NULL, false, limit_winheight);
+ first = next + 1;
+ }
+ return count;
+}
+
+/// Get the number of screen lines a range of text will take in window "wp".
+///
+/// @param[in] start_lnum Starting line number, 1-based inclusive.
+/// @param[in] start_vcol >= 0: Starting virtual column index on "start_lnum",
+/// 0-based inclusive, rounded down to full screen lines.
+/// < 0: Count a full "start_lnum", including filler lines above.
+/// @param[in] end_lnum Ending line number, 1-based inclusive.
+/// @param[in] end_vcol >= 0: Ending virtual column index on "end_lnum",
+/// 0-based exclusive, rounded up to full screen lines.
+/// < 0: Count a full "end_lnum", not including filler lines below.
+/// @param[out] fill If not NULL, set to the number of filler lines in the range.
+int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_t start_vcol,
+ const linenr_T end_lnum, const int64_t end_vcol, int64_t *const fill)
+{
+ int width1 = 0;
+ int width2 = 0;
+ if (start_vcol >= 0 || end_vcol >= 0) {
+ width1 = wp->w_width_inner - win_col_off(wp);
+ width2 = width1 + win_col_off2(wp);
+ width1 = MAX(width1, 0);
+ width2 = MAX(width2, 0);
+ }
+
+ int64_t height_sum_fill = 0;
+ int64_t height_cur_nofill = 0;
+ int64_t height_sum_nofill = 0;
+ linenr_T lnum = start_lnum;
+
+ if (start_vcol >= 0) {
+ linenr_T lnum_next = lnum;
+ const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL);
+ height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false);
+ height_sum_nofill += height_cur_nofill;
+ const int64_t row_off = (start_vcol < width1 || width2 <= 0)
+ ? 0
+ : 1 + (start_vcol - width1) / width2;
+ height_sum_nofill -= MIN(row_off, height_cur_nofill);
+ lnum = lnum_next + 1;
+ }
+
+ while (lnum <= end_lnum) {
+ linenr_T lnum_next = lnum;
+ const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL);
+ height_sum_fill += win_get_fill(wp, lnum);
+ height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false);
+ height_sum_nofill += height_cur_nofill;
+ lnum = lnum_next + 1;
+ }
+
+ if (end_vcol >= 0) {
+ height_sum_nofill -= height_cur_nofill;
+ const int64_t row_off = end_vcol == 0
+ ? 0
+ : (end_vcol <= width1 || width2 <= 0)
+ ? 1
+ : 1 + (end_vcol - width1 + width2 - 1) / width2;
+ height_sum_nofill += MIN(row_off, height_cur_nofill);
+ }
+
+ if (fill != NULL) {
+ *fill = height_sum_fill;
+ }
+ return height_sum_fill + height_sum_nofill;
+}
diff --git a/src/nvim/plines.h b/src/nvim/plines.h
index 808f6d284e..6aede88c8b 100644
--- a/src/nvim/plines.h
+++ b/src/nvim/plines.h
@@ -1,25 +1,28 @@
-#ifndef NVIM_PLINES_H
-#define NVIM_PLINES_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/vim.h"
+#include "nvim/marktree.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-// Argument for lbr_chartabsize().
+/// Argument for lbr_chartabsize().
typedef struct {
win_T *cts_win;
- char *cts_line; // start of the line
- char *cts_ptr; // current position in line
+ char *cts_line; ///< start of the line
+ char *cts_ptr; ///< current position in line
+ int cts_row;
- bool cts_has_virt_text; // true if if a property inserts text
- int cts_cur_text_width; // width of current inserted text
- // TODO(bfredl): iterator in to the marktree for scanning virt text
+ bool cts_has_virt_text; ///< true if if there is inline virtual text
+ int cts_cur_text_width_left; ///< width of virtual text left of cursor
+ int cts_cur_text_width_right; ///< width of virtual text right of cursor
+ MarkTreeIter cts_iter[1];
- int cts_vcol; // virtual column at current position
+ int cts_vcol; ///< virtual column at current position
+ int cts_max_head_vcol; ///< see win_lbr_chartabsize()
} chartabsize_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "plines.h.generated.h"
#endif
-#endif // NVIM_PLINES_H
diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt
index 1db21880bb..68e572911c 100644
--- a/src/nvim/po/CMakeLists.txt
+++ b/src/nvim/po/CMakeLists.txt
@@ -1,9 +1,14 @@
find_package(Gettext REQUIRED)
find_program(XGETTEXT_PRG xgettext)
find_program(ICONV_PRG iconv)
-
-option(LANGUAGES "Localizations to build")
-if(NOT LANGUAGES)
+mark_as_advanced(
+ GETTEXT_MSGFMT_EXECUTABLE
+ GETTEXT_MSGMERGE_EXECUTABLE
+ ICONV_PRG
+ XGETTEXT_PRG)
+
+option(ENABLE_LANGUAGES "Localizations to build" ON)
+if(ENABLE_LANGUAGES)
set(LANGUAGES
af
ca
@@ -90,7 +95,6 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
COMMENT "Checking ${name}.po"
VERBATIM
DEPENDS ${poFile})
- set_target_properties(check-po-${name} PROPERTIES FOLDER po/check)
endmacro()
macro(BuildPoIconvGenericWithCharset
@@ -100,12 +104,12 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
add_custom_target(update-po-${lang}
COMMAND ${CMAKE_COMMAND}
- -DICONV_PRG=${ICONV_PRG}
- -DINPUT_FILE=${inputFile}
- -DOUTPUT_FILE=${outputFile}
- -DINPUT_ENC=${inputEnc}
- -DOUTPUT_ENC=${outputEnc}
- -DOUTPUT_CHARSET=${outputCharSet}
+ -D ICONV_PRG=${ICONV_PRG}
+ -D INPUT_FILE=${inputFile}
+ -D OUTPUT_FILE=${outputFile}
+ -D INPUT_ENC=${inputEnc}
+ -D OUTPUT_ENC=${outputEnc}
+ -D OUTPUT_CHARSET=${outputCharSet}
-P ${PROJECT_SOURCE_DIR}/cmake/ConvertPo.cmake
COMMENT "Updating ${outputName}.po"
DEPENDS ${inputFile})
@@ -177,9 +181,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
BuildMo(${LANGUAGE})
endforeach()
- set_target_properties(${UPDATE_PO_TARGETS} PROPERTIES FOLDER po/update)
add_custom_target(translations ALL DEPENDS ${LANGUAGE_MO_FILES})
add_custom_target(update-po DEPENDS ${UPDATE_PO_TARGETS})
- set_target_properties(translations update-po PROPERTIES FOLDER po)
endif()
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index 7705ba8577..e67cb8c149 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -6,6 +6,9 @@
if 1 " Only execute this if the eval feature is available.
+" using line continuation
+set cpo&vim
+
let filename = "check-" . expand("%:t:r") . ".log"
exe 'redir! > ' . filename
@@ -30,8 +33,15 @@ func! GetMline()
" remove '%' used for plural forms.
let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
+ " remove duplicate positional format arguments
+ let idline2 = ""
+ while idline2 != idline
+ let idline2 = idline
+ let idline = substitute(idline, '%\([1-9][0-9]*\)\$\([-+ #''.*]*[0-9]*l\=[dsuxXpoc%]\)\(.*\)%\1$\([-+ #''.*]*\)\(l\=[dsuxXpoc%]\)', '%\1$\2\3\4', 'g')
+ endwhile
+
" remove everything but % items.
- return substitute(idline, '[^%]*\(%[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
+ return substitute(idline, '[^%]*\(%([1-9][0-9]*\$)\=[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
endfunc
" This only works when 'wrapscan' is not set.
@@ -62,12 +72,18 @@ while 1
if getline(line('.') - 1) !~ "no-c-format"
" go over the "msgid" and "msgid_plural" lines
let prevfromline = 'foobar'
+ let plural = 0
while 1
+ if getline('.') =~ 'msgid_plural'
+ let plural += 1
+ endif
let fromline = GetMline()
if prevfromline != 'foobar' && prevfromline != fromline
+ \ && (plural != 1
+ \ || count(prevfromline, '%') + 1 != count(fromline, '%'))
echomsg 'Mismatching % in line ' . (line('.') - 1)
echomsg 'msgid: ' . prevfromline
- echomsg 'msgid ' . fromline
+ echomsg 'msgid: ' . fromline
if error == 0
let error = line('.')
endif
@@ -89,6 +105,7 @@ while 1
while getline('.') =~ '^msgstr'
let toline = GetMline()
if fromline != toline
+ \ && (plural == 0 || count(fromline, '%') != count(toline, '%') + 1)
echomsg 'Mismatching % in line ' . (line('.') - 1)
echomsg 'msgid: ' . fromline
echomsg 'msgstr: ' . toline
diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po
index c55918abc9..0345d3e243 100644
--- a/src/nvim/po/da.po
+++ b/src/nvim/po/da.po
@@ -3787,7 +3787,7 @@ msgstr "linje %4ld:"
msgid "E354: Invalid register name: '%s'"
msgstr "E354: Ugyldigt registernavn: '%s'"
-msgid "Messages maintainer: Bram Moolenaar <Bram@vim.org>"
+msgid "Messages maintainer: The Vim Project"
msgstr "Oversætter: scootergrisen"
msgid "Interrupt: "
diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po
index aaa39d6041..f619c4c408 100644
--- a/src/nvim/po/eo.po
+++ b/src/nvim/po/eo.po
@@ -3657,7 +3657,7 @@ msgstr "linio %4ld:"
msgid "E354: Invalid register name: '%s'"
msgstr "E354: Nevalida nomo de reÄistro: '%s'"
-msgid "Messages maintainer: Bram Moolenaar <Bram@vim.org>"
+msgid "Messages maintainer: The Vim Project"
msgstr "Flegado de mesaÄoj: Dominique PELLÉ <dominique.pelle@gmail.com>"
msgid "Interrupt: "
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index c8db9a64b5..ab6acf685f 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -5540,7 +5540,7 @@ msgstr "tekijät Bram Moolenaar et al."
#~ msgstr "kirjoita :help iccf<Enter> lisätietoa varten "
#, fuzzy
-#~ msgid "type :CheckHealth<Enter> to optimize Nvim"
+#~ msgid "type :checkhealth<Enter> to optimize Nvim"
#~ msgstr "kirjoita :help iccf<Enter> lisätietoa varten "
msgid "type :q<Enter> to exit "
diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po
index f4fce68ac5..d9058326d5 100644
--- a/src/nvim/po/fr.po
+++ b/src/nvim/po/fr.po
@@ -3375,7 +3375,7 @@ msgid "E354: Invalid register name: '%s'"
msgstr "E354: Nom de registre invalide : '%s'"
# DB - todo : mettre à jour ?
-msgid "Messages maintainer: Bram Moolenaar <Bram@vim.org>"
+msgid "Messages maintainer: The Vim Project"
msgstr "Maintenance des messages : Dominique Pellé <dominique.pelle@gmail.com>"
msgid "Interrupt: "
diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po
index eb7879efc4..f3c55fe9ab 100644
--- a/src/nvim/po/tr.po
+++ b/src/nvim/po/tr.po
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Neovim Turkish Localization Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-09-30 20:46+0300\n"
-"PO-Revision-Date: 2022-07-22 23:00+0300\n"
+"POT-Creation-Date: 2023-06-14 17:27+0300\n"
+"PO-Revision-Date: 2023-06-14 23:00+0300\n"
"Last-Translator: Emir SARI <emir_sari@icloud.com>\n"
"Language-Team: Turkish <https://github.com/bitigchi/neovim>\n"
"Language: tr\n"
@@ -18,6 +18,92 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+msgid "(local to window)"
+msgstr "(pencereye yerel)"
+
+msgid "(local to buffer)"
+msgstr "(arabelleÄŸe yerel)"
+
+msgid "(global or local to buffer)"
+msgstr "(arabelleÄŸe global veya yerel)"
+
+msgid ""
+"\" Each \"set\" line shows the current value of an option (on the left)."
+msgstr ""
+"Her bir \"set\" satırı, bir seçeneğin geçerli değerini gösterir (solda)."
+
+msgid "\" Hit <Enter> on a \"set\" line to execute it."
+msgstr "Bir satırı yürütmek için \"set\" üzerinde <Enter>'a basın."
+
+msgid "\" A boolean option will be toggled."
+msgstr "\" Bir Boole değeri açılır/kapatılır."
+
+msgid "pattern for an include-file line"
+msgstr "bir include dosyası satırı için dizgi"
+
+msgid "use binary searching in tags files"
+msgstr "etiket dosyalarında ikili arama kullan"
+
+msgid "long lines wrap"
+msgstr "uzun satırları kaydırılır"
+
+msgid "show the line number for each line"
+msgstr "her bir satır için satır numarasını göster"
+
+msgid "name of syntax highlighting used"
+msgstr "kullanılan sözdizim vurgulamanın adı"
+
+msgid "default height for the preview window"
+msgstr "önizleme penceresinin öntanımlı yüksekliği"
+
+msgid "identifies the preview window"
+msgstr "önizleme penceresini tanımlar"
+
+msgid "terminal"
+msgstr "uçbirim"
+
+msgid "file to write messages in"
+msgstr "iletilerin içine yazılacağı dosya"
+
+msgid "list of directories for undo files"
+msgstr "geri al dosyaları için dizinler listesi"
+
+msgid "list of pairs that match for the \"%\" command"
+msgstr "\"%\" komutu için eşleşen eşler listesi"
+
+msgid "folding"
+msgstr "kıvırma"
+
+msgid "mapping"
+msgstr "eÅŸlemleme"
+
+msgid "reading and writing files"
+msgstr "dosyaları okuma ve yazma"
+
+msgid "automatically write a file when leaving a modified buffer"
+msgstr "değiştirilmiş bir arabellekten ayrılırken kendiliğinden bir dosya yaz"
+
+msgid "the swap file"
+msgstr "takas dosyası"
+
+msgid "use a swap file for this buffer"
+msgstr "bu arabellek için bir takas dosyası kullan"
+
+msgid "specifies how command line completion works"
+msgstr "komut satırı tamamlamasının nasıl çalıştığını belirtir"
+
+msgid "executing external commands"
+msgstr "dış komutları yürütme"
+
+msgid "specifies the characters in a file name"
+msgstr "bir dosya adındaki karakterleri belirtir"
+
+msgid "E249: Window layout changed unexpectedly"
+msgstr "E249: Pencere yerleşimi beklenmedik bir biçimde değişti"
+
+msgid "E1156: Cannot change the argument list recursively"
+msgstr "E1156: Argüman listesi özyineli olarak değiştirilemiyor"
+
msgid "E163: There is only one file to edit"
msgstr "E163: Düzenlenecek yalnızca bir dosya var"
@@ -30,8 +116,8 @@ msgstr "E165: Son dosyadan öteye gidilemez"
msgid "E610: No argument to delete"
msgstr "E610: Silinecek bir argüman yok"
-msgid "E249: window layout changed unexpectedly"
-msgstr "E249: Pencere yerleşimi beklenmedik bir biçimde değişti"
+msgid "E218: Autocommand nesting too deep"
+msgstr "E218: Çok fazla iç içe geçmiş otokomut"
msgid "--Deleted--"
msgstr "--Silindi--"
@@ -68,9 +154,6 @@ msgstr "E217: Otokomutlar TÜM olaylar için çalıştırılamıyor"
msgid "No matching autocommands: %s"
msgstr "EÅŸleÅŸen otokomut yok: %s"
-msgid "E218: autocommand nesting too deep"
-msgstr "E218: Çok fazla iç içe geçmiş otokomut"
-
#, c-format
msgid "%s Autocommands for \"%s\""
msgstr "\"%s\" için %s otokomutlar"
@@ -98,8 +181,9 @@ msgstr "E216: Böyle bir grup veya olay yok: %s"
msgid "E855: Autocommands caused command to abort"
msgstr "E855: Otokomutlar komutun durmasına neden oldu"
-msgid "E937: Attempt to delete a buffer that is in use"
-msgstr "E937: Kullanımda olan bir arabellek silinmeye çalışılıyor"
+#, c-format
+msgid "E937: Attempt to delete a buffer that is in use: %s"
+msgstr "E937: Kullanımda olan bir arabellek silinmeye çalışılıyor: %s"
msgid "E82: Cannot allocate any buffer, exiting..."
msgstr "E82: Arabellek ayrılamadı, çıkılıyor..."
@@ -116,6 +200,24 @@ msgstr "E516: Hiçbir arabellek silinmedi"
msgid "E517: No buffers were wiped out"
msgstr "E517: Hiçbir arabellek yok edilmedi"
+#, c-format
+msgid "%d buffer unloaded"
+msgid_plural "%d buffers unloaded"
+msgstr[0] "%d arabellek bellekten kaldırıldı"
+msgstr[1] "%d arabellek bellekten kaldırıldı"
+
+#, c-format
+msgid "%d buffer deleted"
+msgid_plural "%d buffers deleted"
+msgstr[0] "%d arabellek silindi"
+msgstr[1] "%d arabellek silindi"
+
+#, c-format
+msgid "%d buffer wiped out"
+msgid_plural "%d buffers wiped out"
+msgstr[0] "%d arabellek yok edildi"
+msgstr[1] "%d arabellek yok edildi"
+
msgid "E90: Cannot unload last buffer"
msgstr "E90: Son arabellek bellekten kaldırılamıyor"
@@ -193,8 +295,14 @@ msgid "[readonly]"
msgstr "[saltokunur]"
#, c-format
+msgid "%<PRId64> line --%d%%--"
+msgid_plural "%<PRId64> lines --%d%%--"
+msgstr[0] "%<PRId64>. satır --%%%d--"
+msgstr[1] "%<PRId64>. satır --%%%d--"
+
+#, c-format
msgid "line %<PRId64> of %<PRId64> --%d%%-- col "
-msgstr "satır %<PRId64>/%<PRId64> --%d%%-- sütun "
+msgstr "satır %<PRId64>/%<PRId64> --%%%d-- sütun "
msgid "[No Name]"
msgstr "[Adsız]"
@@ -211,6 +319,26 @@ msgstr "Son"
msgid "Top"
msgstr "BaÅŸ"
+#, c-format
+msgid "%d%%"
+msgstr "%%%d"
+
+#, c-format
+msgid " (%d of %d)"
+msgstr " (%d/%d)"
+
+#, c-format
+msgid " ((%d) of %d)"
+msgstr " ((%d)/%d)"
+
+#, c-format
+msgid " (file %d of %d)"
+msgstr " (dosya %d/%d)"
+
+#, c-format
+msgid " (file (%d) of %d)"
+msgstr " (dosya (%d)/%d)"
+
msgid "E382: Cannot write, 'buftype' option is set"
msgstr "E382: Yazılamıyor, 'buftype' seçeneği ayarlanmamış"
@@ -226,6 +354,116 @@ msgstr "[Konum Listesi]"
msgid "[Quickfix List]"
msgstr "[Hızlı Düzelt Listesi]"
+msgid "E206: Patchmode: can't touch empty original file"
+msgstr "E206: Yama kipi: Özgün boş dosyaya dokunulamıyor"
+
+msgid "E513: Write error, conversion failed (make 'fenc' empty to override)"
+msgstr ""
+"E513: Yazma hatası, dönüştürme başarısız (geçersiz kılmak için 'fenc'i boş "
+"bırak)"
+
+msgid "E513: Write error, conversion failed in line %"
+msgstr "E513: Yazma hatası, şu satırda dönüştürme başarısız: %"
+
+msgid "E514: Write error (file system full?)"
+msgstr "E514: Yazma hatası (dosya sistemi dolu mu?)"
+
+#, c-format
+msgid "E676: No matching autocommands for buftype=%s buffer"
+msgstr "E676: buftype=%s arabelleği için eşleşen otokomut yok"
+
+msgid "WARNING: The file has been changed since reading it!!!"
+msgstr "UYARI: Bu dosya açıldıktan sonra başkası tarafından değiştirilmiş!!!"
+
+msgid "Do you really want to write to it"
+msgstr "Yine de yazmak istiyor musunuz?"
+
+msgid "E203: Autocommands deleted or unloaded buffer to be written"
+msgstr "E203: Otokomutlar arabelleği silmiş veya yazılması için kaldırmışlar"
+
+msgid "E204: Autocommand changed number of lines in unexpected way"
+msgstr "E204: Otokomut satır sayısını beklenmedik biçimde değiştirdi"
+
+msgid "is a directory"
+msgstr "bir dizin"
+
+msgid "is not a file or writable device"
+msgstr "bir dosya veya yazılabilir aygıt değil"
+
+msgid "is read-only (add ! to override)"
+msgstr "saltokunur (geçersiz kılmak için ! ekleyin)"
+
+#, c-format
+msgid "E303: Unable to create directory \"%s\" for backup file: %s"
+msgstr "E303: Yedek dosyası için \"%s\" dizini oluşturulamadı: %s"
+
+msgid "E509: Cannot create backup file (add ! to override)"
+msgstr "E509: Yedek dosyası oluşturulamıyor (geçersiz kılmak için ! ekleyin)"
+
+msgid "E510: Can't make backup file (add ! to override)"
+msgstr "E510: Yedek dosyası yapılamıyor (geçersiz kılmak için ! ekleyin)"
+
+msgid "E214: Can't find temp file for writing"
+msgstr "E214: Yazma için geçici dosya bulunamıyor"
+
+msgid "E213: Cannot convert (add ! to write without conversion)"
+msgstr "E213: Dönüştürülemiyor (dönüştürmeden yazmak için ! ekleyin)"
+
+msgid "E166: Can't open linked file for writing"
+msgstr "E166: Bağlı dosya yazma için açılamıyor"
+
+#, c-format
+msgid "E212: Can't open file for writing: %s"
+msgstr "E212: Dosya yazma için açılamıyor: %s"
+
+#, c-format
+msgid "E512: Close failed: %s"
+msgstr "E512: Kapatma başarısız oldu: %s"
+
+msgid " CONVERSION ERROR"
+msgstr " DÖNÜŞTÜRME HATASI"
+
+#, c-format
+msgid " in line %<PRId64>;"
+msgstr " %<PRId64>. satırda;"
+
+msgid "[NOT converted]"
+msgstr "[dönüştürülmedi]"
+
+msgid "[converted]"
+msgstr "[dönüştürüldü]"
+
+msgid "[Device]"
+msgstr "[Aygıt]"
+
+msgid " [a]"
+msgstr " [i]"
+
+msgid " appended"
+msgstr " iliÅŸtirildi"
+
+msgid " [w]"
+msgstr " [y]"
+
+msgid " written"
+msgstr " yazıldı"
+
+msgid "E205: Patchmode: can't save original file"
+msgstr "E205: Yama kipi: Orijinal dosya kaydedilemiyor"
+
+msgid "E207: Can't delete backup file"
+msgstr "E207: Yedek dosyası silinemiyor"
+
+msgid ""
+"\n"
+"WARNING: Original file may be lost or damaged\n"
+msgstr ""
+"\n"
+"UYARI: Orijinal dosya kaybolmuş veya hasar görmüş olabilir\n"
+
+msgid "don't quit the editor until the file is successfully written!"
+msgstr "dosya başarılı bir biçimde yazılana kadar düzenleyiciden çıkmayın!"
+
msgid "W10: Warning: Changing a readonly file"
msgstr "W10: Uyarı: Saltokunur bir dosya değiştiriliyor"
@@ -253,15 +491,15 @@ msgstr "'history' seçeneği sıfır"
msgid "E474: Failed to convert list to msgpack string buffer"
msgstr "E474: Liste, msgpack dizi arabelleğine dönüştürülemedi"
+msgid "E548: Digit expected"
+msgstr "E548: Basamak bekleniyordu"
+
msgid "E545: Missing colon"
msgstr "E545: İki nokta eksik"
msgid "E546: Illegal mode"
msgstr "E546: İzin verilmeyen kip"
-msgid "E548: digit expected"
-msgstr "E548: Basamak bekleniyordu"
-
msgid "E549: Illegal percentage"
msgstr "E549: İzin verilmeyen yüzde"
@@ -460,11 +698,72 @@ msgstr "E105: :loadkeymap kaynak alınmayan bir dosyada kullanılıyor"
msgid "E791: Empty keymap entry"
msgstr "E791: Boş düğme eşlem girdisi"
+msgid " TERMINAL"
+msgstr " UÇBİRİM"
+
+msgid " VREPLACE"
+msgstr " SANAL DEĞİŞTİR"
+
+msgid " REPLACE"
+msgstr " DEĞİŞTİR"
+
+msgid " REVERSE"
+msgstr " GERİ AL"
+
+msgid " INSERT"
+msgstr " EKLE"
+
+msgid " (terminal)"
+msgstr " (uçbirim)"
+
+msgid " (insert)"
+msgstr " (ekle)"
+
+msgid " (replace)"
+msgstr " (deÄŸiÅŸtir)"
+
+msgid " (vreplace)"
+msgstr " (sanal deÄŸiÅŸtir)"
+
+msgid " Arabic"
+msgstr " Arapça"
+
+msgid " (paste)"
+msgstr " (yapıştır)"
+
+msgid " VISUAL"
+msgstr " GÖRSEL"
+
+msgid " VISUAL LINE"
+msgstr " GÖRSEL SATIR"
+
+msgid " VISUAL BLOCK"
+msgstr " GÖRSEL BLOK"
+
+msgid " SELECT"
+msgstr " SEÇ"
+
+msgid " SELECT LINE"
+msgstr " SATIR SEÇ"
+
+msgid " SELECT BLOCK"
+msgstr " BLOK SEÇ"
+
+msgid "recording"
+msgstr "kaydediliyor"
+
msgid "E111: Missing ']'"
msgstr "E111: ']' eksik"
-msgid "E719: Cannot use [:] with a Dictionary"
-msgstr "E719: [:], bir Sözlük ile kullanılamaz"
+#, c-format
+msgid "E697: Missing end of List ']': %s"
+msgstr "E697: Liste sonunda ']' eksik: %s"
+
+msgid "E719: Cannot slice a Dictionary"
+msgstr "E719: Bir sözlük dilimlenemiyor"
+
+msgid "E909: Cannot index a special variable"
+msgstr "E909: Özel bir değişken dizinlenemiyor"
msgid "E274: No white space allowed before parenthesis"
msgstr "E274: Ayraçtan önce boşluğa izin verilmiyor"
@@ -473,9 +772,26 @@ msgstr "E274: Ayraçtan önce boşluğa izin verilmiyor"
msgid "E80: Error while writing: %s"
msgstr "E80: Yazma sırasında hata: %s"
+msgid "E695: Cannot index a Funcref"
+msgstr "E695: Bir Funcref dizinlenemiyor"
+
+msgid "E698: Variable nested too deep for making a copy"
+msgstr "E698: Değişken kopyalama için çok iç içe geçmiş"
+
msgid "E1098: String, List or Blob required"
msgstr "E1098: Dizi, Liste veya İkili Nesne gerekiyor"
+#, c-format
+msgid "E1169: Expression too recursive: %s"
+msgstr "E1169: Komut çok özyineli: %s"
+
+#, c-format
+msgid "E1203: Dot can only be used on a dictionary: %s"
+msgstr "E1203: İki nokta yalnızca bir sözlük üzerinde kullanılabilir: %s"
+
+msgid "E1192: Empty function name"
+msgstr "E1192: İşlev adı boş"
+
msgid ""
"E5700: Expression from 'spellsuggest' must yield lists with exactly two "
"values"
@@ -500,18 +816,9 @@ msgstr "E713: . sonrası boş anahtar kullanılamaz"
msgid "E709: [:] requires a List or Blob value"
msgstr "E709: [:] bir liste veya ikili geniÅŸ nesne deÄŸeri gerektirir"
-msgid "E972: Blob value does not have the right number of bytes"
-msgstr "E972: İkili geniş nesne değeri doğru bayt sayısına sahip değil"
-
msgid "E996: Cannot lock a range"
msgstr "E996: Erim kilitlenemiyor"
-msgid "E710: List value has more items than target"
-msgstr "E710: Liste değeri hedeften daha fazla ögeye sahip"
-
-msgid "E711: List value has not enough items"
-msgstr "E711: Liste değeri yeterli ögeye sahip değil"
-
msgid "E996: Cannot lock a list or dict"
msgstr "E996: Bir liste veya sözlük kilitlenemiyor"
@@ -524,22 +831,12 @@ msgstr "E109: '?' sonrası ':' eksik"
msgid "E804: Cannot use '%' with Float"
msgstr "E804: Bir kayan noktalı değer ile '%' kullanılamaz"
-msgid "E973: Blob literal should have an even number of hex characters"
-msgstr ""
-"E973: İkili geniş nesne hazır bilgisi çift onalt. karakterlere iye olmalıdır"
-
msgid "E110: Missing ')'"
msgstr "E110: ')' eksik"
msgid "E260: Missing name after ->"
msgstr "E260: -> sonrası ad eksik"
-msgid "E695: Cannot index a Funcref"
-msgstr "E695: Bir Funcref dizinlenemiyor"
-
-msgid "E909: Cannot index a special variable"
-msgstr "E909: Özel bir değişken dizinlenemiyor"
-
#, c-format
msgid "E112: Option name missing: %s"
msgstr "E112: Seçenek adı eksik: %s"
@@ -548,6 +845,10 @@ msgstr "E112: Seçenek adı eksik: %s"
msgid "E113: Unknown option: %s"
msgstr "E113: Bilinmeyen seçenek: %s"
+msgid "E973: Blob literal should have an even number of hex characters"
+msgstr ""
+"E973: İkili geniş nesne hazır bilgisi çift onalt. karakterlere iye olmalıdır"
+
#, c-format
msgid "E114: Missing quote: %s"
msgstr "E114: Tırnak eksik: %s"
@@ -560,10 +861,6 @@ msgstr "E115: Tırnak eksik: %s"
msgid "E696: Missing comma in List: %s"
msgstr "E696: Listede virgül eksik: %s"
-#, c-format
-msgid "E697: Missing end of List ']': %s"
-msgstr "E697: Liste sonunda ']' eksik: %s"
-
msgid "Not enough memory to set references, garbage collection aborted!"
msgstr "Referansları ayarlamak için yetersiz bellek, atık toplama durduruldu"
@@ -593,9 +890,6 @@ msgstr "filter() argümanı"
msgid "E700: Unknown function: %s"
msgstr "E700: Bilinmeyen iÅŸlev: %s"
-msgid "E922: expected a dict"
-msgstr "E922: Bir sözlük bekleniyordu"
-
msgid "E923: Second argument of function() must be a list or a dict"
msgstr "E923: function() ikinci argümanı bir liste veya sözlük olmalıdır"
@@ -609,9 +903,6 @@ msgstr "Komut çalıştırılıyor: \"%s\""
msgid "E921: Invalid callback argument"
msgstr "E921: Geçersiz geri çağırma argümanı"
-msgid "E698: variable nested too deep for making a copy"
-msgstr "E698: Değişken kopyalama için çok iç içe geçmiş"
-
msgid ""
"\n"
"\tLast set from "
@@ -899,7 +1190,7 @@ msgid "E5005: Unable to dump %s: container references itself in %s"
msgstr "E5005: %s dökülemiyor: Kapsayıcı, %s içinde özüne başvuruyor"
#, c-format
-msgid "E684: list index out of range: %<PRId64>"
+msgid "E684: List index out of range: %<PRId64>"
msgstr "E684: Liste indeksi erim dışında: %<PRId64>"
#, c-format
@@ -910,9 +1201,16 @@ msgid "E957: Invalid window number"
msgstr "E957: Geçersiz pencere numarası"
#, c-format
+msgid "E935: Invalid submatch number: %d"
+msgstr "E935: Geçersiz alteşleşme numarası: %d"
+
+#, c-format
msgid "E998: Reduce of an empty %s with no initial value"
msgstr "E998: Başlangıç değeri olmayan boş bir %s için reduce() yapılamıyor"
+msgid "E1132: Missing function argument"
+msgstr "E1132: İşlev argümanı eksik"
+
#, c-format
msgid "Error converting the call result: %s"
msgstr "Çağrı sonuçlarını dönüştürürken hata: %s"
@@ -946,6 +1244,9 @@ msgstr "flatten() argümanı"
msgid "extend() argument"
msgstr "extend() argümanı"
+msgid "extendnew() argument"
+msgstr "extendnew() argümanı"
+
msgid "E5000: Cannot find tab number."
msgstr "E5000: Sekme numarası bulunamıyor."
@@ -1022,10 +1323,6 @@ msgstr "E6100: \"%s\", geçerli bir stdpath değil"
msgid "(Invalid)"
msgstr "(Geçersiz)"
-#, c-format
-msgid "E935: invalid submatch number: %d"
-msgstr "E935: Geçersiz alteşleşme numarası: %d"
-
msgid "Can only call this function in an unmodified buffer"
msgstr "Bu işlev yalnızca değiştirilmemiş bir arabellekte çağrılabilir"
@@ -1047,29 +1344,73 @@ msgstr "E482: %s dosyası yazma için açılamıyor: %s"
msgid "E80: Error when closing file %s: %s"
msgstr "E80: %s dosyası kapatılırken hata: %s"
+msgid "E743: Variable nested too deep for (un)lock"
+msgstr "E743: Değişken kilitlenemez/kilidi açılamaz, çok iç içe geçmiş"
+
+msgid "E908: Using an invalid value as a String"
+msgstr "E908: Bir dizi olarak geçersiz bir değer kullanılıyor"
+
#, c-format
msgid "E1174: String required for argument %d"
msgstr "E1174: %d argümanı için dizi gerekiyor"
#, c-format
-msgid "E1142: Non-empty string required for argument %d"
-msgstr "E1142: %d argümanı için boş olmayan dizi gerekiyor"
+msgid "E1175: Non-empty string required for argument %d"
+msgstr "E1175: %d argümanı için boş olmayan dizi gerekiyor"
+
+#, c-format
+msgid "E1206: Dictionary required for argument %d"
+msgstr "E1206: %d argümanı için sözlük gerekiyor"
#, c-format
msgid "E1210: Number required for argument %d"
msgstr "E1210: %d argümanı için sayı gerekiyor"
#, c-format
-msgid "E5142: Failed to open file %s: %s"
-msgstr "E5142: %s dosyası açılamadı: %s"
+msgid "E1211: List required for argument %d"
+msgstr "E1211: %d argümanı için liste gerekiyor"
+
+#, c-format
+msgid "E1212: Bool required for argument %d"
+msgstr "E1212: %d argümanı için Boole gerekiyor"
+
+#, c-format
+msgid "E1219: Float or Number required for argument %d"
+msgstr "E1219: %d argümanı için sayı veya kayan noktalı değer gerekiyor"
#, c-format
-msgid "E5143: Failed to write to file %s: %s"
-msgstr "E5143: %s dosyası yazılamadı: %s"
+msgid "E1220: String or Number required for argument %d"
+msgstr "E1220: %d argümanı için sayı veya dizi gerekiyor"
#, c-format
-msgid "E5144: Failed to close file %s: %s"
-msgstr "E5144: %s dosyası kapatılamadı: %s"
+msgid "E1222: String or List required for argument %d"
+msgstr "E1222: %d argümanı için dizi veya liste gerekiyor"
+
+#, c-format
+msgid "E1226: List or Blob required for argument %d"
+msgstr "E1226: %d argümanı için liste veya ikili nesne gerekiyor"
+
+#, c-format
+msgid "E1238: Blob required for argument %d"
+msgstr "E1238: %d argümanı için ikili nesne gerekiyor"
+
+#, c-format
+msgid "E1239: Invalid value for blob: %d"
+msgstr "E1239: İkili nesne için geçersiz değer: %d"
+
+#, c-format
+msgid "E1256: String or function required for argument %d"
+msgstr "E1256: %d argümanı için dizi veya işlev gerekiyor"
+
+#, c-format
+msgid "E1297: Non-NULL Dictionary required for argument %d"
+msgstr "E1297: %d argümanı için boş olmayan sözlük gerekiyor"
+
+msgid "E710: List value has more items than target"
+msgstr "E710: Liste değeri hedeften daha fazla ögeye sahip"
+
+msgid "E711: List value has not enough items"
+msgstr "E711: Liste değeri yeterli ögeye sahip değil"
msgid "sort() argument"
msgstr "sort() argümanı"
@@ -1090,8 +1431,8 @@ msgstr "E6000: Argüman bir işlev veya işlev adı değil"
msgid "E737: Key already exists: %s"
msgstr "E737: Anahtar hâlihazırda var: %s"
-msgid "E743: variable nested too deep for (un)lock"
-msgstr "E743: Değişken kilitlenemez/kilidi açılamaz, çok iç içe geçmiş"
+msgid "E972: Blob value does not have the right number of bytes"
+msgstr "E972: İkili geniş nesne değeri doğru bayt sayısına sahip değil"
#, c-format
msgid "E741: Value is locked: %.*s"
@@ -1140,18 +1481,15 @@ msgstr "E974: Bir Sayı olarak ikili geniş nesne kullanılıyor"
msgid "E685: using an invalid value as a Number"
msgstr "E685: Bir sayı olarak geçersiz bir değer kullanılıyor"
-msgid "E730: using List as a String"
+msgid "E730: Using a List as a String"
msgstr "E730: Bir dizi olarak liste kullanılıyor"
-msgid "E731: using Dictionary as a String"
+msgid "E731: Using a Dictionary as a String"
msgstr "E731: Bir dizi olarak sözlük kullanılıyor"
-msgid "E976: using Blob as a String"
+msgid "E976: Using a Blob as a String"
msgstr "E976: Bir dizi olarak ikili geniş nesne kullanılıyor"
-msgid "E908: using an invalid value as a String"
-msgstr "E908: Bir dizi olarak geçersiz bir değer kullanılıyor"
-
msgid "E891: Using a Funcref as a Float"
msgstr "E891: Bir kayan noktalı değer olarak bir Funcref kullanılıyor"
@@ -1178,6 +1516,10 @@ msgid "E808: Number or Float required"
msgstr "E808: Sayı veya kayan noktalı değer gerekiyor"
#, c-format
+msgid "E117: Unknown function: %s"
+msgstr "E117: Bilinmeyen iÅŸlev: %s"
+
+#, c-format
msgid "E122: Function %s already exists, add ! to replace it"
msgstr "E122: %s işlevi hâlihazırda mevcut, değiştirmek için ! ekleyin"
@@ -1191,6 +1533,23 @@ msgstr "E718: Funcref gerekiyor"
msgid "E130: Unknown function: %s"
msgstr "E130: Bilinmeyen iÅŸlev: %s"
+msgid "E454: Function list was modified"
+msgstr "E454: İşlev istesi değiştirildi"
+
+msgid "E1058: Function nesting too deep"
+msgstr "E1058: Pek çok iç içe geçmiş işlev"
+
+#, c-format
+msgid "E1068: No white space allowed before '%s': %s"
+msgstr "E1068: '%s' öncesi boşluğa izin verilmiyor: %s"
+
+#, c-format
+msgid "E1145: Missing heredoc end marker: %s"
+msgstr "E1145: heredoc bitiÅŸ imleyicisi eksik: '%s'"
+
+msgid "E1300: Cannot use a partial with dictionary for :defer"
+msgstr "E1300: :defer için sözlükle bir kısımsal kullanılamıyor"
+
#, c-format
msgid "E125: Illegal argument: %s"
msgstr "E125: İzin verilmeyen argüman: %s"
@@ -1203,6 +1562,10 @@ msgid "E989: Non-default argument follows default argument"
msgstr "E989: Öntanımlı olmayan argüman öntanımlı argümandan sonra"
#, c-format
+msgid "E451: Expected }: %s"
+msgstr "E451: } bekleniyordu: %s"
+
+#, c-format
msgid "E740: Too many arguments for function %s"
msgstr "E740: %s işlevi için pek fazla argüman"
@@ -1237,10 +1600,6 @@ msgid "E699: Too many arguments"
msgstr "E699: Çok fazla argüman"
#, c-format
-msgid "E117: Unknown function: %s"
-msgstr "E117: Bilinmeyen iÅŸlev: %s"
-
-#, c-format
msgid "E276: Cannot use function as a method: %s"
msgstr "E276: İşlev bir yöntem olarak kullanılamaz: %s"
@@ -1249,10 +1608,6 @@ msgid "E933: Function was deleted: %s"
msgstr "E933: İşlev silinmiş: %s"
#, c-format
-msgid "E119: Not enough arguments for function: %s"
-msgstr "E119: Şu işlev için yetersiz sayıda argüman: %s"
-
-#, c-format
msgid "E120: Using <SID> not in a script context: %s"
msgstr "E120: <SID> bir betik bağlamında kullanılmıyor: %s"
@@ -1293,9 +1648,6 @@ msgstr "E126: :endfunction eksik"
msgid "W22: Text found after :endfunction: %s"
msgstr "W22: :endfunction sonrası metin bulundu: %s"
-msgid "E1058: function nesting too deep"
-msgstr "E1058: Pek çok iç içe geçmiş işlev"
-
#, c-format
msgid "E707: Function name conflicts with variable: %s"
msgstr "E707: İşlev adı şu değişken ile çakışıyor: %s"
@@ -1326,8 +1678,12 @@ msgstr "E18: :let içinde beklenmedik karakter"
msgid "E940: Cannot lock or unlock variable %s"
msgstr "E940: Değişken %s kilitlenemiyor veya açılamıyor"
-msgid "E991: cannot use =<< here"
-msgstr "E991: Burada =<< kullanılamaz"
+#, c-format
+msgid "E963: Setting %s to value with wrong type"
+msgstr "E963: %s yanlış türe sahip değere ayarlanıyor"
+
+msgid "E991: Cannot use =<< here"
+msgstr "E991: Burada =<< kullanılamıyor"
msgid "E221: Marker cannot start with lower case letter"
msgstr "E221: İmleyici küçük harfle başlayamaz"
@@ -1366,14 +1722,14 @@ msgid "E108: No such variable: \"%s\""
msgstr "E108: Böyle bir değişken yok: \"%s\""
#, c-format
-msgid "E963: setting %s to value with wrong type"
-msgstr "E963: %s yanlış türe sahip değere ayarlanıyor"
-
-#, c-format
msgid "E794: Cannot set variable in the sandbox: \"%.*s\""
msgstr "E794: Kum havuzunda deÄŸiÅŸken ayarlanamaz: \"%.*s\""
#, c-format
+msgid "E1122: Variable is locked: %*s"
+msgstr "E1122: DeÄŸer kilitli: %*s"
+
+#, c-format
msgid "E795: Cannot delete variable %.*s"
msgstr "E795: %.*s deÄŸiÅŸkeni silinemiyor"
@@ -1385,6 +1741,14 @@ msgstr "E704: Funcref değişkeni BÜYÜK harf ile başlamalıdır: %s"
msgid "E705: Variable name conflicts with existing function: %s"
msgstr "E705: Değişken adı mevcut işlevle çakışıyor: %s"
+#, c-format
+msgid "E521: Number required: &%s = '%s'"
+msgstr "E521: Sayı gerekiyor: &%s = '%s'"
+
+msgid "E1308: Cannot resize a window in another tab page"
+msgstr ""
+"E1308: Başka bir sekme sayfasında bir pencere yeniden boyutlandırılamıyor"
+
msgid "tcp address must be host:port"
msgstr "tcp adresi makine:kapı olmalı"
@@ -1394,6 +1758,9 @@ msgstr "makine veya kapı bulunamadı"
msgid "connection refused"
msgstr "bağlantı reddedildi"
+msgid "E144: Non-numeric argument to :z"
+msgstr "E144: :z için sayısal olmayan argüman"
+
#, c-format
msgid "<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"
msgstr "<%s>%s%s %d, Onalt. %02x, Sek. %03o, Digr %s"
@@ -1422,6 +1789,12 @@ msgid "E134: Cannot move a range of lines into itself"
msgstr "E134: Satırlardan oluşan erim kendi içine taşınamaz"
#, c-format
+msgid "%<PRId64> line moved"
+msgid_plural "%<PRId64> lines moved"
+msgstr[0] "%<PRId64> satır taşındı"
+msgstr[1] "%<PRId64> satır taşındı"
+
+#, c-format
msgid "E482: Can't create file %s"
msgstr "E482: %s dosyası oluşturulamıyor"
@@ -1435,6 +1808,10 @@ msgstr "E135: *Süzgeç* otokomutları şu anki arabelleği değiştirmemelidir"
msgid "[No write since last change]\n"
msgstr "[Son değişiklikten sonra yazılmadı]\n"
+#, c-format
+msgid "E503: \"%s\" is not a file or writable device"
+msgstr "E503: \"%s\", bir dosya veya yazılabilir aygıt değil"
+
msgid "Write partial file?"
msgstr "Dosyanın bir kısmı yazılsın mı?"
@@ -1486,9 +1863,6 @@ msgstr "E505: \"%s\" saltokunur (geçersiz kılmak için ! ekleyin)"
msgid "E143: Autocommands unexpectedly deleted new buffer %s"
msgstr "E143: yeni %s arabelleğini otokomutlar beklenmedik bir biçimde sildi"
-msgid "E144: non-numeric argument to :z"
-msgstr "E144: :z için sayısal olmayan argüman"
-
msgid "E146: Regular expressions can't be delimited by letters"
msgstr "E146: Düzenli ifadeler harflerle sınırlandırılamaz"
@@ -1499,6 +1873,30 @@ msgstr "%s ile deÄŸiÅŸtir (y/n/a/q/l/^E/^Y)?"
msgid "(Interrupted) "
msgstr "(Yarıda kesildi) "
+#, c-format
+msgid "%<PRId64> match on %<PRId64> line"
+msgid_plural "%<PRId64> matches on %<PRId64> line"
+msgstr[0] "%<PRId64> eşleşme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> eşleşme, %<PRId64> satırda"
+
+#, c-format
+msgid "%<PRId64> substitution on %<PRId64> line"
+msgid_plural "%<PRId64> substitutions on %<PRId64> line"
+msgstr[0] "%<PRId64> değiştirme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> değiştirme, %<PRId64> satırda"
+
+#, c-format
+msgid "%<PRId64> match on %<PRId64> lines"
+msgid_plural "%<PRId64> matches on %<PRId64> lines"
+msgstr[0] "%<PRId64> eşleşme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> eşleşme, %<PRId64> satırda"
+
+#, c-format
+msgid "%<PRId64> substitution on %<PRId64> lines"
+msgid_plural "%<PRId64> substitutions on %<PRId64> lines"
+msgstr[0] "%<PRId64> değiştirme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> değiştirme, %<PRId64> satırda"
+
msgid "E147: Cannot do :global recursive with a range"
msgstr "E147: :global özyineli olarak bir erim ile yapılamaz"
@@ -1517,6 +1915,10 @@ msgid "No old files"
msgstr "Eski dosya yok"
#, c-format
+msgid "E666: Compiler not supported: %s"
+msgstr "E666: Derleyici desteklenmiyor: %s"
+
+#, c-format
msgid "Save changes to \"%s\"?"
msgstr "DeÄŸiÅŸiklikler ÅŸuraya kaydedilsin mi: \"%s\"?"
@@ -1535,21 +1937,33 @@ msgstr "E162: \"%s\" arabelleği son değişiklikten sonra yazılmadı"
msgid "Warning: Entered other buffer unexpectedly (check autocommands)"
msgstr "Uyarı: Diğer arabelleğe aniden girildi (otokomutları denetleyin)"
-#, c-format
-msgid "E666: compiler not supported: %s"
-msgstr "E666: Derleyici desteklenmiyor: %s"
-
msgid "E464: Ambiguous use of user-defined command"
msgstr "E464: Kullanıcı tanımlı komutun belirsiz kullanımı"
+msgid "E489: No call stack to substitute for \"<stack>\""
+msgstr "E489: \"<yığın>\" yerine koymak için çağrı yığını yok"
+
msgid "E492: Not an editor command"
msgstr "E492: Bir düzenleyici komutu değil"
-msgid "E498: no :source file name to substitute for \"<sfile>\""
+msgid "E495: No autocommand file name to substitute for \"<afile>\""
+msgstr "E495: \"<odosyası>\" yerine koymak için otokomut dosya adı yok"
+
+msgid "E496: No autocommand buffer number to substitute for \"<abuf>\""
+msgstr ""
+"E496: \"<oarabelleği>\" yerine koymak için otokomut arabellek numarası yok"
+
+msgid "E497: No autocommand match name to substitute for \"<amatch>\""
+msgstr "E497: \"<oeşi>\" yerine koymak için otokomut eşleşme adı yok"
+
+msgid "E498: No :source file name to substitute for \"<sfile>\""
msgstr "E498: \"<kdosyası>\" yerine koymak için :source dosya adı yok"
-msgid "E489: no call stack to substitute for \"<stack>\""
-msgstr "E489: \"<yığın>\" yerine koymak için çağrı yığını yok"
+msgid "E842: No line number to use for \"<slnum>\""
+msgstr "E842: \"<slnum>\" kullanımı için satır numarası yok"
+
+msgid "E961: No line number to use for \"<sflnum>\""
+msgstr "E961: \"<sflnum>\" kullanımı için satır numarası yok"
msgid "E1274: No script file name to substitute for \"<script>\""
msgstr "E1274: \"<betik>\" yerine koymak için betik dosyası adı yok"
@@ -1567,16 +1981,16 @@ msgstr "Çalıştırılıyor: %s"
msgid "line %"
msgstr "satır %"
-#, c-format
-msgid "E605: Exception not caught: %s"
-msgstr "E605: Kural dışı durum yakalanmadı: %s"
-
msgid "End of sourced file"
msgstr "Kaynak alınan dosyanın sonu"
msgid "End of function"
msgstr "İşlevin sonu"
+#, c-format
+msgid "E605: Exception not caught: %s"
+msgstr "E605: Kural dışı durum yakalanmadı: %s"
+
msgid ""
"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"
msgstr ""
@@ -1599,6 +2013,18 @@ msgid "E319: The command is not available in this version"
msgstr "E319: Üzgünüm, komut bu sürümde mevcut değil"
#, c-format
+msgid "%d more file to edit. Quit anyway?"
+msgid_plural "%d more files to edit. Quit anyway?"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "E173: %<PRId64> more file to edit"
+msgid_plural "E173: %<PRId64> more files to edit"
+msgstr[0] "E173: Düzenlenecek %<PRId64> dosya daha var"
+msgstr[1] "E173: Düzenlenecek %<PRId64> dosya daha var"
+
+#, c-format
msgid "E185: Cannot find color scheme '%s'"
msgstr "E185: '%s' renk düzeni bulunamadı"
@@ -1647,22 +2073,6 @@ msgstr "E192: :normal'in özyineli kullanımı çok derinde"
msgid "E194: No alternate file name to substitute for '#'"
msgstr "E194: '#' yerine koymak için başka dosya adı yok"
-msgid "E495: no autocommand file name to substitute for \"<afile>\""
-msgstr "E495: \"<odosyası>\" yerine koymak için otokomut dosya adı yok"
-
-msgid "E496: no autocommand buffer number to substitute for \"<abuf>\""
-msgstr ""
-"E496: \"<oarabelleği>\" yerine koymak için otokomut arabellek numarası yok"
-
-msgid "E497: no autocommand match name to substitute for \"<amatch>\""
-msgstr "E497: \"<oeşi>\" yerine koymak için otokomut eşleşme adı yok"
-
-msgid "E842: no line number to use for \"<slnum>\""
-msgstr "E842: \"<slnum>\" kullanımı için satır numarası yok"
-
-msgid "E961: no line number to use for \"<sflnum>\""
-msgstr "E961: \"<sflnum>\" kullanımı için satır numarası yok"
-
#, no-c-format
msgid "E499: Empty file name for '%' or '#', only works with \":p:h\""
msgstr "E499: '%' veya '#' için boş dosya adı, yalnızca \":p:h\" ile çalışır"
@@ -1673,6 +2083,12 @@ msgstr "E500: Boş bir satır olarak değer biçer"
msgid "Untitled"
msgstr "Adsız"
+msgid "E583: Multiple :else"
+msgstr "E583: Birden çok :else"
+
+msgid "E607: Multiple :finally"
+msgstr "E607: Birden çok :finally"
+
msgid "E608: Cannot :throw exceptions with 'Vim' prefix"
msgstr "E608: 'Vim' öneki ile kural dışı durumlar :throw edilemez"
@@ -1681,14 +2097,14 @@ msgid "Exception thrown: %s"
msgstr "Kural dışı durum verdi: %s"
#, c-format
-msgid "Exception finished: %s"
-msgstr "Kural dışı durum bitti: %s"
-
-#, c-format
msgid "Exception discarded: %s"
msgstr "Kural dışı durum kenara atıldı: %s"
#, c-format
+msgid "Exception finished: %s"
+msgstr "Kural dışı durum bitti: %s"
+
+#, c-format
msgid "%s, line %<PRId64>"
msgstr "%s, %<PRId64>. satır"
@@ -1732,9 +2148,6 @@ msgstr "E581: :if olmadan :else"
msgid "E582: :elseif without :if"
msgstr "E582: :if olmadan :elseif"
-msgid "E583: multiple :else"
-msgstr "E583: Birden fazla :else"
-
msgid "E584: :elseif after :else"
msgstr "E584: :else sonrası :elseif"
@@ -1765,15 +2178,9 @@ msgstr "E604: :finally sonrası :catch"
msgid "E606: :finally without :try"
msgstr "E606: :try olmadan :finally"
-msgid "E607: multiple :finally"
-msgstr "E607: Birden fazla :finally"
-
msgid "E602: :endtry without :try"
msgstr "E602: :try olmadan :endtry"
-msgid "E193: :endfunction not inside a function"
-msgstr "E193: :endfunction, bir işlev içinde değil"
-
msgid "E811: Not allowed to change buffer information now"
msgstr "E811: Åžu anda arabellek bilgisi deÄŸiÅŸtirilemez"
@@ -1814,8 +2221,8 @@ msgstr "[Komut Satırı]"
msgid "E199: Active window or buffer deleted"
msgstr "E199: Etkin pencere veya arabellek silinmiÅŸ"
-msgid "E854: path too long for completion"
-msgstr "E854: Yol tamamlama için çok uzun"
+msgid "E854: Path too long for completion"
+msgstr "E854: Yol tamamlama için pek uzun"
#, c-format
msgid ""
@@ -1844,13 +2251,6 @@ msgstr "E347: Başka bir \"%s\" dosyası yol içinde bulunamadı"
msgid "E812: Autocommands changed buffer or buffer name"
msgstr "E812: Otokomutlar arabelleği veya arabellek adını değiştirdi"
-#, c-format
-msgid "E676: No matching autocommands for buftype=%s buffer"
-msgstr "E676: buftype=%s arabelleği için eşleşen otokomut yok"
-
-msgid "is a directory"
-msgstr "bir dizin"
-
msgid "Illegal file name"
msgstr "İzin verilmeyen dosya adı"
@@ -1890,12 +2290,6 @@ msgstr "[Eksik CR]"
msgid "[long lines split]"
msgstr "[uzun satırlar bölünmüş]"
-msgid "[NOT converted]"
-msgstr "[dönüştürülmedi]"
-
-msgid "[converted]"
-msgstr "[dönüştürüldü]"
-
#, c-format
msgid "[CONVERSION ERROR in line %<PRId64>]"
msgstr "[%<PRId64>. satırda DÖNÜŞTÜRME HATASI]"
@@ -1922,100 +2316,6 @@ msgstr "[Yeni Dosya]"
msgid "[New]"
msgstr "[Yeni]"
-msgid "E203: Autocommands deleted or unloaded buffer to be written"
-msgstr "E203: Otokomutlar arabelleği silmiş veya yazılması için kaldırmışlar"
-
-msgid "E204: Autocommand changed number of lines in unexpected way"
-msgstr "E204: Otokomut satır sayısını beklenmedik biçimde değiştirdi"
-
-msgid "is not a file or writable device"
-msgstr "bir dosya veya yazılabilir aygıt değil"
-
-msgid "is read-only (add ! to override)"
-msgstr "saltokunur (geçersiz kılmak için ! ekleyin)"
-
-#, c-format
-msgid "E303: Unable to create directory \"%s\" for backup file: %s"
-msgstr "E303: Yedek dosyası için \"%s\" dizini oluşturulamadı: %s"
-
-msgid "E506: Can't write to backup file (add ! to override)"
-msgstr "E506: Yedek dosyasına yazılamıyor (geçersiz kılmak için ! ekleyin)"
-
-msgid "E509: Cannot create backup file (add ! to override)"
-msgstr "E509: Yedek dosyası oluşturulamıyor (geçersiz kılmak için ! ekleyin)"
-
-msgid "E510: Can't make backup file (add ! to override)"
-msgstr "E510: Yedek dosyası yapılamıyor (geçersiz kılmak için ! ekleyin)"
-
-msgid "E214: Can't find temp file for writing"
-msgstr "E214: Yazma için geçici dosya bulunamıyor"
-
-msgid "E213: Cannot convert (add ! to write without conversion)"
-msgstr "E213: Dönüştürülemiyor (dönüştürmeden yazmak için ! ekleyin)"
-
-msgid "E166: Can't open linked file for writing"
-msgstr "E166: Bağlı dosya yazma için açılamıyor"
-
-#, c-format
-msgid "E212: Can't open file for writing: %s"
-msgstr "E212: Dosya yazma için açılamıyor: %s"
-
-#, c-format
-msgid "E512: Close failed: %s"
-msgstr "E512: Kapatma başarısız oldu: %s"
-
-msgid "E513: write error, conversion failed (make 'fenc' empty to override)"
-msgstr ""
-"E513: Yazma hatası, dönüştürme başarısız (geçersiz kılmak için 'fenc'i boş "
-"bırakın)"
-
-msgid "E513: write error, conversion failed in line %"
-msgstr "E513: Yazma hatası, şu satırda dönüştürme başarısız: %"
-
-msgid "E514: write error (file system full?)"
-msgstr "E514: Yazma hatası (dosya sistemi dolu mu?)"
-
-msgid " CONVERSION ERROR"
-msgstr " DÖNÜŞTÜRME HATASI"
-
-#, c-format
-msgid " in line %<PRId64>;"
-msgstr " %<PRId64>. satırda;"
-
-msgid "[Device]"
-msgstr "[Aygıt]"
-
-msgid " [a]"
-msgstr " [i]"
-
-msgid " appended"
-msgstr " iliÅŸtirildi"
-
-msgid " [w]"
-msgstr " [y]"
-
-msgid " written"
-msgstr " yazıldı"
-
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: Yama kipi: Orijinal dosya kaydedilemiyor"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: Yama kipi: Orijinal boş dosyaya dokunulamıyor"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: Yedek dosyası silinemiyor"
-
-msgid ""
-"\n"
-"WARNING: Original file may be lost or damaged\n"
-msgstr ""
-"\n"
-"UYARI: Orijinal dosya kaybolmuş veya hasar görmüş olabilir\n"
-
-msgid "don't quit the editor until the file is successfully written!"
-msgstr "dosya başarılı bir biçimde yazılana kadar düzenleyiciden çıkmayın!"
-
msgid "[dos format]"
msgstr "[dos biçimi]"
@@ -2034,18 +2334,24 @@ msgstr "[unix biçimi]"
msgid "[unix]"
msgstr "[unix]"
+#, c-format
+msgid "%<PRId64> line, "
+msgid_plural "%<PRId64> lines, "
+msgstr[0] "%<PRId64> satır, "
+msgstr[1] "%<PRId64> satır, "
+
+#, c-format
+msgid "%<PRId64> byte"
+msgid_plural "%<PRId64> bytes"
+msgstr[0] "%<PRId64> bayt"
+msgstr[1] "%<PRId64> bayt"
+
msgid "[Incomplete last line]"
msgstr "[Tamamlanmamış son satır]"
msgid "[noeol]"
msgstr "[satır sonu yok]"
-msgid "WARNING: The file has been changed since reading it!!!"
-msgstr "UYARI: Bu dosya açıldıktan sonra başkası tarafından değiştirilmiş!!!"
-
-msgid "Do you really want to write to it"
-msgstr "Yine de yazmak istiyor musunuz?"
-
#, c-format
msgid "E208: Error writing to \"%s\""
msgstr "E208: Şuraya yazılamadı: \"%s\""
@@ -2128,12 +2434,30 @@ msgstr "E350: Şu anki 'foldmethod' ile kıvırma oluşturulamıyor"
msgid "E351: Cannot delete fold with current 'foldmethod'"
msgstr "E351: Şu anki 'foldmethod' ile kıvırma silinemiyor"
-msgid "E222: Add to read buffer"
-msgstr "E222: Okunan arabelleÄŸe ekle"
+#, c-format
+msgid "+--%3ld line folded"
+msgid_plural "+--%3ld lines folded "
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "+-%s%3ld line: "
+msgid_plural "+-%s%3ld lines: "
+msgstr[0] "+-%s%3ld satır: "
+msgstr[1] "+-%s%3ld satır: "
-msgid "E223: recursive mapping"
+msgid "E223: Recursive mapping"
msgstr "E223: Özyineli eşlemleme"
+msgid "E1255: <Cmd> mapping must end with <CR>"
+msgstr "E1255: <Cmd> eÅŸlemlemesi <CR> ile bitmelidir"
+
+msgid "E1136: <Cmd> mapping must end with <CR> before second <Cmd>"
+msgstr "E1136: <Cmd> eşlemlemesi ikinci <Cmd>'den önce <CR> ile bitmelidir"
+
+msgid "E222: Add to read buffer"
+msgstr "E222: Okunan arabelleÄŸe ekle"
+
msgid "--No lines in buffer--"
msgstr "--Arabellek içinde satır yok--"
@@ -2155,9 +2479,9 @@ msgstr "E10: \\ sonrasında /, ? veya & gelmeli"
msgid "E11: Invalid in command-line window; <CR> executes, CTRL-C quits"
msgstr "E11: Komut satırı penceresinde geçersiz; <CR> çalıştırır, CTRL-C çıkar"
-msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search"
+msgid "E12: Command not allowed in secure mode in current dir or tag search"
msgstr ""
-"E12: Geçerli dizin veya etiket aramasında exrc veya vimrc'den komutlara izin "
+"E12: Geçerli dizin veya etiket aramasında güvenli kipteyken komuta izin "
"verilmiyor"
msgid "E169: Command too recursive"
@@ -2217,8 +2541,8 @@ msgid "E983: Duplicate argument: %s"
msgstr "E983: Yinelenen argüman: %s"
#, c-format
-msgid "E15: Invalid expression: %s"
-msgstr "E15: Geçersiz ifade: %s"
+msgid "E15: Invalid expression: \"%s\""
+msgstr "E15: Geçersiz ifade: \"%s\""
msgid "E16: Invalid range"
msgstr "E16: Geçersiz erim"
@@ -2426,6 +2750,10 @@ msgid "E118: Too many arguments for function: %s"
msgstr "E118: İşlev için pek fazla argüman: %s"
#, c-format
+msgid "E119: Not enough arguments for function: %s"
+msgstr "E119: Şu işlev için yetersiz sayıda argüman: %s"
+
+#, c-format
msgid "E716: Key not present in Dictionary: \"%s\""
msgstr "E716: Anahtar sözlükte mevcut değil: \"%s\""
@@ -2470,9 +2798,6 @@ msgstr "E255: İşaret verisinde okunamadı!"
msgid "E72: Close error on swap file"
msgstr "E72: Takas dosyasında kapatma hatası"
-msgid "E73: tag stack empty"
-msgstr "E73: Etiket yığını boş"
-
msgid "E74: Command too complex"
msgstr "E74: Komut çok karmaşık"
@@ -2517,16 +2842,17 @@ msgstr "E81: <SID> bir betik bağlamında kullanılmıyor"
msgid "E107: Missing parentheses: %s"
msgstr "E107: Ayraç eksik: %s"
-msgid "E363: pattern uses more memory than 'maxmempattern'"
-msgstr "E363: Dizgi 'maxmempattern' ögesinden daha fazla bellek kullanıyor"
-
-msgid "E749: empty buffer"
+msgid "E749: Empty buffer"
msgstr "E749: BoÅŸ arabellek"
#, c-format
msgid "E86: Buffer %<PRId64> does not exist"
msgstr "E86: Arabellek %<PRId64> mevcut deÄŸil"
+#, c-format
+msgid "E193: %s not inside a function"
+msgstr "E193: %s bir işlev içinde değil"
+
msgid "E682: Invalid search pattern or delimiter"
msgstr "E682: Geçersiz arama dizgisi veya sınırlandırıcısı"
@@ -2563,21 +2889,19 @@ msgstr "E519: Özellik desteklenmiyor"
msgid "E856: Filename too long"
msgstr "E856: Dosya adı pek uzun"
-msgid "E806: using Float as a String"
-msgstr "E806: Kayan Noktalı Değer, bir Dizi yerine kullanılıyor"
+msgid "E806: Using a Float as a String"
+msgstr "E806: Bir dizi olarak kayan noktalı değer kullanılıyor"
msgid "E788: Not allowed to edit another buffer now"
msgstr "E788: Şu anda başka bir arabellek düzenlenemez"
#, c-format
-msgid "E5500: autocmd has thrown an exception: %s"
-msgstr "E5500: Otokomut, bir istisna attı: %s"
+msgid "E1023: Using a Number as a Bool: %d"
+msgstr "E1023: Bir Boole olarak bir sayı kullanılıyor: %d"
-msgid "E5520: <Cmd> mapping must end with <CR>"
-msgstr "E5520: <Cmd> eÅŸlemlemesi <CR> ile bitmelidir"
-
-msgid "E5521: <Cmd> mapping must end with <CR> before second <Cmd>"
-msgstr "E5521: <Cmd> eşlemlemesi ikinci <Cmd>'den önce <CR> ile bitmelidir"
+#, c-format
+msgid "E1085: Not a callable type: %s"
+msgstr "E1085: Çağrılabilir bir tür değil: %s"
#, c-format
msgid "E5555: API call: %s"
@@ -2593,9 +2917,6 @@ msgstr "E5601: Pencere kapatılamıyor, yalnızca yüzen pencere açık kalır"
msgid "E5602: Cannot exchange or rotate float"
msgstr "E5602: Kayan noktalı değer değiştirilemiyor veya döndürülemiyor"
-msgid "E1142: Non-empty string required"
-msgstr "E1142: BoÅŸ olmayan dizi gerekiyor"
-
msgid "E1155: Cannot define autocommands for ALL events"
msgstr "E1155: Otokomutlar TÜM olaylar için tanımlanamıyor"
@@ -2611,11 +2932,31 @@ msgstr "E5248: Grup adında geçersiz karakter"
msgid "E1249: Highlight group name too long"
msgstr "E1249: Vurgulama grubu adı pek uzun"
+#, c-format
+msgid "E966: Invalid line number: %ld"
+msgstr "E966: Geçersiz satır numarası: %ld"
+
+#, c-format
+msgid "E1278: Stray '}' without a matching '{': %s"
+msgstr "E1278: Eşleşen bir '{' olmadan başıboş '}': %s"
+
+#, c-format
+msgid "E1279: Missing '}': %s"
+msgstr "E1279: '}' eksik: %s"
+
msgid "E5767: Cannot use :undo! to redo or move to a different undo branch"
msgstr ""
"E5767: Yinelemek veya başka bir geri al dalına taşımak için :undo! "
"kullanılamaz"
+#, c-format
+msgid "E5570: Cannot update trust file: %s"
+msgstr "E5570: Güven dosyası güncellenemiyor: %s"
+
+#, c-format
+msgid "E355: Unknown option: %s"
+msgstr "E355: Bilinmeyen seçenek: %s"
+
msgid "search hit TOP, continuing at BOTTOM"
msgstr "Arama dosyanın BAŞINI geçti, dosyanın SONUNDAN sürüyor"
@@ -2625,6 +2966,10 @@ msgstr "Arama dosyanın SONUNU geçti, dosyanın BAŞINDAN sürüyor"
msgid " line "
msgstr " satır "
+#, c-format
+msgid "E685: Internal error: hash_add(): duplicate key \"%s\""
+msgstr "E685: İçsel hata: hash_add(): Yinelenmiş anahtar \"%s\""
+
msgid "E478: Don't panic!"
msgstr "E478: Panik yok!"
@@ -2668,9 +3013,24 @@ msgid "E424: Too many different highlighting attributes in use"
msgstr "E424: Çok fazla değişik vurgulama kuralları kullanılıyor"
#, c-format
-msgid "E411: highlight group not found: %s"
+msgid "E411: Highlight group not found: %s"
msgstr "E411: Vurgulama grubu bulunamadı: %s"
+msgid "E414: Group has settings, highlight link ignored"
+msgstr "E414: Grup ayarları var, vurgulama bağlantısı yok sayıldı"
+
+#, c-format
+msgid "E415: Unexpected equal sign: %s"
+msgstr "E415: Beklenmedik eÅŸittir imi: %s"
+
+#, c-format
+msgid "E416: Missing equal sign: %s"
+msgstr "E416: Eksik eÅŸittir imi: %s"
+
+#, c-format
+msgid "E417: Missing argument: %s"
+msgstr "E417: Argüman eksik: %s"
+
#, c-format
msgid "E412: Not enough arguments: \":highlight link %s\""
msgstr "E412: Yetersiz sayıda argüman: \":highlight link %s\""
@@ -2679,25 +3039,10 @@ msgstr "E412: Yetersiz sayıda argüman: \":highlight link %s\""
msgid "E413: Too many arguments: \":highlight link %s\""
msgstr "E413: Çok fazla argüman: \":highlight link %s\""
-msgid "E414: group has settings, highlight link ignored"
-msgstr "E414: Grup ayarları mevcut, vurgulama bağlantısı yok sayıldı"
-
-#, c-format
-msgid "E415: unexpected equal sign: %s"
-msgstr "E415: Beklenmedik eÅŸittir imi: %s"
-
msgid "E423: Illegal argument"
msgstr "E423: İzin verilmeyen argüman"
#, c-format
-msgid "E416: missing equal sign: %s"
-msgstr "E416: Eksik eÅŸittir imi: %s"
-
-#, c-format
-msgid "E417: missing argument: %s"
-msgstr "E417: Argüman eksik: %s"
-
-#, c-format
msgid "E418: Illegal value: %s"
msgstr "E418: İzin verilmeyen değer: %s"
@@ -2721,159 +3066,6 @@ msgstr "E669: Grup adında yazdırılamayan karakter"
msgid "E849: Too many highlight and syntax groups"
msgstr "E849: Çok fazla vurgulama ve sözdizim grupları"
-msgid "Add a new database"
-msgstr "Yeni bir veritabanı ekle"
-
-msgid "Query for a pattern"
-msgstr "Bir dizgiyi sorgula"
-
-msgid "Show this message"
-msgstr "Bu iletiyi göster"
-
-msgid "Kill a connection"
-msgstr "Bir bağlantıyı kes"
-
-msgid "Reinit all connections"
-msgstr "Tüm bağlantıları yeniden kur"
-
-msgid "Show connections"
-msgstr "Bağlantıları göster"
-
-#, c-format
-msgid "E560: Usage: cs[cope] %s"
-msgstr "E560: Kullanım: cs[cope] %s"
-
-msgid "This cscope command does not support splitting the window.\n"
-msgstr "Bu cscope komutu pencereyi bölmeyi desteklemiyor.\n"
-
-msgid "E562: Usage: cstag <ident>"
-msgstr "E562: Kullanım: cstag <ad>"
-
-msgid "E257: cstag: tag not found"
-msgstr "E257: cstag: Etiket bulunamadı"
-
-#, c-format
-msgid "E563: stat(%s) error: %d"
-msgstr "E563: stat(%s) hatası: %d"
-
-#, c-format
-msgid "E564: %s is not a directory or a valid cscope database"
-msgstr "E564: %s, bir dizin veya geçerli bir cscope veritabanı değil"
-
-#, c-format
-msgid "Added cscope database %s"
-msgstr "cscope veritabanı %s eklendi"
-
-#, c-format
-msgid "E262: error reading cscope connection %<PRIu64>"
-msgstr "E262: cscope bağlantısı %<PRIu64> okunurken hata"
-
-msgid "E561: unknown cscope search type"
-msgstr "E561: Bilinmeyen cscope arama türü"
-
-msgid "E566: Could not create cscope pipes"
-msgstr "E566: cscope veri yolları oluşturulamadı"
-
-msgid "E622: Could not fork for cscope"
-msgstr "E622: cscope için çatal oluşturulamadı"
-
-msgid "cs_create_connection setpgid failed"
-msgstr "cs_create_connection: setpgid başarısız oldu"
-
-msgid "cs_create_connection exec failed"
-msgstr "cs_create_connection: exec başarısız oldu"
-
-msgid "cs_create_connection: fdopen for to_fp failed"
-msgstr "cs_create_connection: to_fp için fdopen başarısız oldu"
-
-msgid "cs_create_connection: fdopen for fr_fp failed"
-msgstr "cs_create_connection: fr_fp için fdopen başarısız oldu"
-
-msgid "E623: Could not spawn cscope process"
-msgstr "E623: cscope süreci ortaya çıkarılamadı"
-
-msgid "E567: no cscope connections"
-msgstr "E567: cscope bağlantıları yok"
-
-#, c-format
-msgid "E469: invalid cscopequickfix flag %c for %c"
-msgstr "E469: Geçersiz cscopequickfix bayrağı %c, %c için"
-
-#, c-format
-msgid "E259: no matches found for cscope query %s of %s"
-msgstr "E259: cscope sorgusu %s/%s için eşleşme bulunamadı"
-
-msgid "cscope commands:\n"
-msgstr "cscope komutları:\n"
-
-#, c-format
-msgid "%-5s: %s%*s (Usage: %s)"
-msgstr "%-5s: %s%*s (Kullanım: %s)"
-
-msgid ""
-"\n"
-" a: Find assignments to this symbol\n"
-" c: Find functions calling this function\n"
-" d: Find functions called by this function\n"
-" e: Find this egrep pattern\n"
-" f: Find this file\n"
-" g: Find this definition\n"
-" i: Find files #including this file\n"
-" s: Find this C symbol\n"
-" t: Find this text string\n"
-msgstr ""
-"\n"
-" a: Bu sembole yapılan atamaları bul\n"
-" c: Bu işlevi çağıran işlevleri bul\n"
-" d: bu işlev tarafından çağrılan işlevleri bul\n"
-" e: Bu egrep dizgisini bul\n"
-" f: Bu dosyayı bul\n"
-" g: Bu tanımı bul\n"
-" i: Bu dosyayı içeren (#including) dosyaları bul\n"
-" s: Bu \"C\" sembolünü bul\n"
-" t: Bu metin dizisini bul\n"
-
-msgid "E568: duplicate cscope database not added"
-msgstr "E568: Yinelenen cscope veritabanı eklenmemiş"
-
-#, c-format
-msgid "E261: cscope connection %s not found"
-msgstr "E261: %s cscope bağlantısı bulunamadı"
-
-#, c-format
-msgid "cscope connection %s closed"
-msgstr "%s cscope bağlantısı bitirildi"
-
-msgid "E570: fatal error in cs_manage_matches"
-msgstr "E570: cs_manage_matches içinde onulmaz hata"
-
-#, c-format
-msgid "Cscope tag: %s"
-msgstr "cscope etiketi: %s"
-
-msgid ""
-"\n"
-" # line"
-msgstr ""
-"\n"
-" # satır"
-
-msgid "filename / context / line\n"
-msgstr "dosya adı / bağlam / satır\n"
-
-#, c-format
-msgid "E609: Cscope error: %s"
-msgstr "E609: cscope hatası: %s"
-
-msgid "All cscope databases reset"
-msgstr "Tüm cscope veritabanları sıfırlandı"
-
-msgid "no cscope connections\n"
-msgstr "cscope bağlantısı yok\n"
-
-msgid " # pid database name prepend path\n"
-msgstr " # pid veritabanı adı başlangıç yolu\n"
-
msgid "Type number and <Enter> or click with the mouse (q or empty cancels): "
msgstr ""
"Sayı girip <Enter>'a veya fare düğmesine basın (q veya boş iptal eder): "
@@ -2982,14 +3174,6 @@ msgid "match %d"
msgstr "eÅŸleÅŸme %d"
#, c-format
-msgid "Current %slanguage: \"%s\""
-msgstr "Åžu anki %sdil: \"%s\""
-
-#, c-format
-msgid "E197: Cannot set language to \"%s\""
-msgstr "E197: \"%s\" diline ayarlanamıyor"
-
-#, c-format
msgid "E1502: Lua failed to grow stack to %i"
msgstr "E1502: Lua, yığını %i olarak büyütemedi"
@@ -3080,6 +3264,14 @@ msgstr "vim.on_key Lua geri çağrısı çalıştırılırken hata: %.*s"
msgid "Error executing Lua callback: %.*s"
msgstr "Lua geri çağrısı çalıştırılırken hata: %.*s"
+#, c-format
+msgid "Error executing vim.secure.read: %.*s"
+msgstr "vim.secure.read yürütülürken hata: %.*s"
+
+#, c-format
+msgid "Error executing vim.secure.trust: %.*s"
+msgstr "vim.secure.trust yürütülürken hata: %.*s"
+
msgid "Argument missing after"
msgstr "Şundan sonra argüman eksik:"
@@ -3103,6 +3295,9 @@ msgstr "E5421: stdin açılamadı: %s"
msgid "Attempt to open script file again: \"%s %s\"\n"
msgstr "Betik dosyası yeniden açılmaya çalışılıyor: \"%s %s\"\n"
+msgid "--embed conflicts with -es/-Es/-l"
+msgstr "--embed, -es/-Es/-l ile çakışıyor"
+
#, c-format
msgid "Cannot open for reading: \"%s\": %s\n"
msgstr "Okuma için açılamıyor: \"%s\": %s\n"
@@ -3110,9 +3305,6 @@ msgstr "Okuma için açılamıyor: \"%s\": %s\n"
msgid "Cannot open for script output: \""
msgstr "Betik çıktısı için açılamıyor: \""
-msgid "--embed conflicts with -es/-Es"
-msgstr "--embed, -es/-Es ile çakışıyor"
-
msgid "pre-vimrc command line"
msgstr "vimrc uygulanma öncesi komut satırı"
@@ -3165,6 +3357,7 @@ msgid " +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"
msgstr ""
"+<komut>, -c <komut> Yapılandırma ve ilk dosya sonrası <komut> çalıştır\n"
+
msgid " -b Binary mode\n"
msgstr "-b İkili kip\n"
@@ -3270,21 +3463,24 @@ msgstr ""
"Tüm seçenekler için \":help startup-options\" yazın.\n"
#, c-format
-msgid "E224: global abbreviation already exists for %s"
+msgid "E224: Global abbreviation already exists for %s"
msgstr "E224: %s için global kısaltma hâlihazırda var"
#, c-format
-msgid "E225: global mapping already exists for %s"
+msgid "E225: Global mapping already exists for %s"
msgstr "E225: %s için global eşlemleme hâlihazırda var"
#, c-format
-msgid "E226: abbreviation already exists for %s"
+msgid "E226: Abbreviation already exists for %s"
msgstr "E226: %s için kısaltma hâlihazırda var"
#, c-format
-msgid "E227: mapping already exists for %s"
+msgid "E227: Mapping already exists for %s"
msgstr "E227: %s için eşlemleme hâlihazırda var"
+msgid "E460: Entries missing in mapset() dict argument"
+msgstr "E460: mapset() sözlük argümanında eksik girdiler"
+
msgid "No abbreviation found"
msgstr "Kısaltma bulunamadı"
@@ -3294,9 +3490,6 @@ msgstr "Eşlemleme bulunamadı"
msgid "E228: makemap: Illegal mode"
msgstr "E228: makemap: İzin verilmeyen kip"
-msgid "E460: entries missing in mapset() dict argument"
-msgstr "E460: mapset() sözlük argümanında girdiler eksik"
-
#, c-format
msgid "E357: 'langmap': Matching character missing for %s"
msgstr "E357: 'langmap': %s için eşleşen karakter eksik"
@@ -3393,10 +3586,10 @@ msgstr "E1112: Liste ögesi %d hücre genişliği geçersiz"
msgid "E1113: Overlapping ranges for 0x%lx"
msgstr "E1113: 0x%lx için üst üste binen erimler"
-msgid "E1114: Only values of 0x100 and higher supported"
+msgid "E1114: Only values of 0x80 and higher supported"
msgstr "E1114: Yalnızca 0x100 ve üstü değerler desteklenir"
-msgid "E293: block was not locked"
+msgid "E293: Block was not locked"
msgstr "E293: Blok kilitlenmemiÅŸti"
msgid "E294: Seek error in swap file read"
@@ -3414,6 +3607,37 @@ msgstr "E297: Takas dosyasında yazma hatası"
msgid "E300: Swap file already exists (symlink attack?)"
msgstr "E300: Takas dosyası hâlihazırda var (sembol bağı saldırısı?)"
+#, c-format
+msgid "E315: ml_get: Invalid lnum: %<PRId64>"
+msgstr "E315: ml_get: Geçersiz satır numarası: %<PRId64>"
+
+#, c-format
+msgid "E316: ml_get: Cannot find line %<PRId64>in buffer %d %s"
+msgstr "E316: ml_get: Şu arabellekte %<PRId64>. satır bulunamıyor: %d %s"
+
+msgid "E317: Pointer block id wrong"
+msgstr "E317: İşaretçi blok kimliği yanlış"
+
+msgid "E317: Pointer block id wrong 2"
+msgstr "E317: İşaretçi blok kimliği yanlış 2"
+
+msgid "E317: Pointer block id wrong 3"
+msgstr "E317: İşaretçi blok kimliği yanlış 3"
+
+msgid "E317: Pointer block id wrong 4"
+msgstr "E317: İşaretçi blok kimliği yanlış 4"
+
+#, c-format
+msgid "E322: Line number out of range: %<PRId64> past the end"
+msgstr "E322: Satır numarası erimin dışında: %<PRId64> en sonuncuyu geçmiş"
+
+#, c-format
+msgid "E323: Line count wrong in block %<PRId64>"
+msgstr "E323: %<PRId64>. blokta satır sayısı yanlış"
+
+msgid "E1364: Warning: Pointer block corrupted"
+msgstr "E1364: Uyarı: İşaretçi bloku hasar görmüş"
+
msgid "E298: Didn't get block nr 0?"
msgstr "E298: 0 numaralı blok alınmadı mı?"
@@ -3524,6 +3748,7 @@ msgid "??? from here until ???END lines may have been inserted/deleted"
msgstr ""
"??? konumundan ???SON konumuna kadar satırlar eklenmiş/silinmiş olabilir"
+
msgid "???END"
msgstr "???SON"
@@ -3556,11 +3781,17 @@ msgstr "Kurtarma tamamlandı. Arabellek içeriği dosya içeriğine eşit."
msgid ""
"\n"
-"You may want to delete the .swp file now.\n"
+"You may want to delete the .swp file now."
+msgstr ""
"\n"
+"Bu .swp dosyasını silmeniz iyi olur."
+
+msgid ""
+"\n"
+"Note: process STILL RUNNING: "
msgstr ""
"\n"
-"Bu .swp dosyasını silmeniz iyi olur.\n"
+"Not: Süreç HÂLÂ ÇALIŞIYOR: "
msgid "Swap files found:"
msgstr "Takas dosyası bulundu:"
@@ -3660,26 +3891,12 @@ msgstr "Dosya korundu"
msgid "E314: Preserve failed"
msgstr "E314: Koruma başarısız oldu"
-#, c-format
-msgid "E315: ml_get: invalid lnum: %<PRId64>"
-msgstr "E315: ml_get: Geçersiz satır numarası: %<PRId64>"
-
-#, c-format
-msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s"
-msgstr "E316: ml_get: %<PRId64>. satır %d %s arabelleğinde bulunamıyor"
-
-msgid "E317: pointer block id wrong 3"
-msgstr "E317: Blok 3 gösterge kimliği yanlış"
-
msgid "stack_idx should be 0"
msgstr "stack_idx 0 olmalı"
msgid "E318: Updated too many blocks?"
msgstr "E318: Çok fazla blok mu güncellendi?"
-msgid "E317: pointer block id wrong 4"
-msgstr "E317: Blok 4 gösterge kimliği yanlış"
-
msgid "deleted block 1?"
msgstr "Blok 1 mi silindi?"
@@ -3687,26 +3904,12 @@ msgstr "Blok 1 mi silindi?"
msgid "E320: Cannot find line %<PRId64>"
msgstr "E320: %<PRId64>. satır bulunamıyor"
-msgid "E317: pointer block id wrong"
-msgstr "E317: Gösterge blok kimliği yanlış"
-
msgid "pe_line_count is zero"
msgstr "pe_line_count sıfır"
-#, c-format
-msgid "E322: line number out of range: %<PRId64> past the end"
-msgstr "E322: Satır numarası erimin dışında: %<PRId64> en sonuncuyu geçmiş"
-
-#, c-format
-msgid "E323: line count wrong in block %<PRId64>"
-msgstr "E323: %<PRId64>. blokta satır sayısı yanlış"
-
msgid "Stack size increases"
msgstr "Yığın boyutu artıyor"
-msgid "E317: pointer block id wrong 2"
-msgstr "E317: Blok 2 gösterge kimliği yanlış"
-
#, c-format
msgid "E773: Symlink loop for \"%s\""
msgstr "E773: \"%s\" için sembol bağı döngüsü"
@@ -3882,6 +4085,18 @@ msgstr "Yarıda kes: "
msgid "Press ENTER or type command to continue"
msgstr "Sürdürmek için ENTER'a basın veya komut girin"
+#, c-format
+msgid "%ld more line"
+msgid_plural "%ld more lines"
+msgstr[0] "%ld daha çok satır"
+msgstr[1] "%ld daha çok satır"
+
+#, c-format
+msgid "%ld line less"
+msgid_plural "%ld fewer lines"
+msgstr[0] "%ld daha az satır"
+msgstr[1] "%ld daha az satır"
+
msgid " (Interrupted)"
msgstr " (Yarıda kesildi)"
@@ -3927,6 +4142,12 @@ msgstr ""
"&Tümünü At\n"
"İ&ptal"
+msgid "E664: Changelist is empty"
+msgstr "E664: DeÄŸiÅŸiklik listesi boÅŸ"
+
+msgid "E1292: Command-line window is already open"
+msgstr "E1292: Komut satırı penceresi halihazırda açık"
+
msgid "E349: No identifier under cursor"
msgstr "E349: İmleç altında tanımlayıcı yok"
@@ -3936,9 +4157,6 @@ msgstr "E348: İmleç altında dizi yok"
msgid "E352: Cannot erase folds with current 'foldmethod'"
msgstr "E352: Şu anki 'foldmethod' ile kıvırmalar silinemiyor"
-msgid "E664: changelist is empty"
-msgstr "E664: DeÄŸiÅŸiklik listesi boÅŸ"
-
msgid "E662: At start of changelist"
msgstr "E662: Değişiklik listesinin başında"
@@ -3952,20 +4170,62 @@ msgstr ""
msgid "Type :qa and press <Enter> to exit Nvim"
msgstr "Nvim'den çıkmak için :qa yazıp <Enter>'a basın"
+msgid ""
+"E883: Search pattern and expression register may not contain two or more "
+"lines"
+msgstr ""
+"E883: Arama dizgisi ve ifade yazmacı iki veya daha fazla satır içeremez"
+
+#, c-format
+msgid "%<PRId64> line %sed %d time"
+msgid_plural "%<PRId64> line %sed %d times"
+msgstr[0] "%<PRId64> satır, %s %d kez"
+msgstr[1] "%<PRId64> satır, %s %d kez"
+
+#, c-format
+msgid "%<PRId64> lines %sed %d time"
+msgid_plural "%<PRId64> lines %sed %d times"
+msgstr[0] "%<PRId64> satır, %s %d kez"
+msgstr[1] "%<PRId64> satır, %s %d kez"
+
#, c-format
msgid "%<PRId64> lines to indent... "
msgstr "girintilenecek %<PRId64> satır kaldı... "
+#, c-format
+msgid "%<PRId64> line indented "
+msgid_plural "%<PRId64> lines indented "
+msgstr[0] "%<PRId64> satır girintilendi"
+msgstr[1] "%<PRId64> satır girintilendi"
+
msgid "E748: No previously used register"
msgstr "E748: Daha önce kullanılan bir yazmaç yok"
#, c-format
+msgid "%<PRId64> line changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "%<PRId64> satır değiştirildi"
+msgstr[1] "%<PRId64> satır değiştirildi"
+
+#, c-format
msgid " into \"%c"
msgstr " \"%c"
#, c-format
+msgid "block of %<PRId64> line yanked%s"
+msgid_plural "block of %<PRId64> lines yanked%s"
+msgstr[0] "%<PRId64> satırlık blok şuraya kopyalandı:%s"
+msgstr[1] "%<PRId64> satırlık blok şuraya kopyalandı:%s"
+
+#, c-format
+msgid "%<PRId64> line yanked%s"
+msgid_plural "%<PRId64> lines yanked%s"
+msgstr[0] "%<PRId64> satır şuraya kopyalandı:%s"
+msgstr[1] "%<PRId64> satır şuraya kopyalandı:%s"
+
+#, c-format
msgid "E353: Nothing in register %s"
-msgstr "E353: Yazmaç %s boş"
+msgstr "E353: %s yazmacında bir şey yok"
msgid ""
"\n"
@@ -3974,11 +4234,11 @@ msgstr ""
"\n"
"Tür Ad İçerik"
-msgid ""
-"E883: search pattern and expression register may not contain two or more "
-"lines"
-msgstr ""
-"E883: Arama dizgisi ve ifade yazmacı iki veya daha fazla satır içeremez"
+#, c-format
+msgid "%<PRId64> lines changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "%<PRId64> satır değiştirildi"
+msgstr[1] "%<PRId64> satır değiştirildi"
#, c-format
msgid "%<PRId64> Cols; "
@@ -4053,12 +4313,8 @@ msgid "E594: Need at least %d columns"
msgstr "E594: En azından %d sütun gerekli"
#, c-format
-msgid "E355: Unknown option: %s"
-msgstr "E355: Bilinmeyen seçenek: %s"
-
-#, c-format
-msgid "E521: Number required: &%s = '%s'"
-msgstr "E521: Sayı gerekiyor: &%s = '%s'"
+msgid "Invalid value for option '%s': expected %s, got %s %s"
+msgstr "'%s' seçeneği için geçersiz değer: %s bekleniyordu, %s %s alındı"
msgid ""
"\n"
@@ -4087,7 +4343,10 @@ msgstr "E356: get_varp HATASI"
msgid "E540: Unclosed expression sequence"
msgstr "E540: Kapatılmamış ifade sıralaması"
-msgid "E542: unbalanced groups"
+msgid "E536: Comma required"
+msgstr "E536: Virgül gerekiyor"
+
+msgid "E542: Unbalanced groups"
msgstr "E542: DengelenmemiÅŸ gruplar"
msgid "E589: 'backupext' and 'patchmode' are equal"
@@ -4096,6 +4355,9 @@ msgstr "E589: 'backupext' ve 'patchmode' birbirine eÅŸit"
msgid "E595: 'showbreak' contains unprintable or wide character"
msgstr "E595: 'showbreak' yazdırılamaz veya geniş karakter içeriyor"
+msgid "E1336: Internal error: shortmess too long"
+msgstr "E1336: İçsel hata: Kısalık pek uzun"
+
#, c-format
msgid "E539: Illegal character <%s>"
msgstr "E539: İzin verilmeyen karakter <%s>"
@@ -4104,6 +4366,9 @@ msgstr "E539: İzin verilmeyen karakter <%s>"
msgid "For option %s"
msgstr "%s seçeneği için"
+msgid "E5080: Digit expected"
+msgstr "E5080: Basamak bekleniyordu"
+
msgid "E524: Missing colon"
msgstr "E524: İki nokta eksik"
@@ -4124,13 +4389,16 @@ msgstr "E528: Bir ' deÄŸeri belirtmeli"
msgid "E535: Illegal character after <%c>"
msgstr "E535: <%c> sonrası izin verilmeyen karakter"
-msgid "E536: comma required"
-msgstr "E536: Virgül gerekiyor"
-
#, c-format
msgid "E537: 'commentstring' must be empty or contain %s"
msgstr "E537: 'commentstring' boş olmalı veya %s içermeli"
+msgid "E834: Conflicts with value of 'listchars'"
+msgstr "E834: 'listchars' değeriyle çakışmalar var"
+
+msgid "E835: Conflicts with value of 'fillchars'"
+msgstr "E835: 'fillchars' değeriyle çakışmalar var"
+
#, c-format
msgid "dlerror = \"%s\""
msgstr "dlerror = \"%s\""
@@ -4142,6 +4410,14 @@ msgstr "E5420: Dosyaya yazılamadı: %s"
msgid "Vim: Error reading input, exiting...\n"
msgstr "Vim: Girdi okunurken hata, çıkılıyor...\n"
+#, c-format
+msgid "Current %slanguage: \"%s\""
+msgstr "Åžu anki %sdil: \"%s\""
+
+#, c-format
+msgid "E197: Cannot set language to \"%s\""
+msgstr "E197: \"%s\" diline ayarlanamıyor"
+
msgid ""
"\n"
"shell returned "
@@ -4160,6 +4436,7 @@ msgstr ""
msgid "E5677: Error writing input to shell-command: %s"
msgstr "E5677: Girdi, kabuk komutuna yazılırken hata: %s"
+#, no-c-format
msgid "%a %b %d %H:%M:%S %Y"
msgstr "%a %b %d %H:%M:%S %Y"
@@ -4252,10 +4529,24 @@ msgid "E927: Invalid action: '%s'"
msgstr "E927: Geçersiz eylem: '%s'"
#, c-format
-msgid "E369: invalid item in %s%%[]"
+msgid "E59: Invalid character after %s@"
+msgstr "E59: %s@ sonrası geçersiz karakter"
+
+msgid "E63: Invalid use of \\_"
+msgstr "E63: Geçersiz \\_ kullanımı"
+
+msgid "E363: Pattern uses more memory than 'maxmempattern'"
+msgstr "E363: Dizgi, 'maxmempattern' ögesinden daha çok bellek kullanıyor"
+
+#, c-format
+msgid "E369: Invalid item in %s%%[]"
msgstr "E369: %s%%[] içinde geçersiz öge"
#, c-format
+msgid "E654: Missing delimiter after search pattern: %s"
+msgstr "E654: Arama dizgisi sonrası sınırlandırıcı eksik: %s"
+
+#, c-format
msgid "E769: Missing ] after %s["
msgstr "E769: %s[ sonrası ] eksik"
@@ -4298,6 +4589,14 @@ msgstr "E956: Dizgi özyineli olarak kullanılamaz"
msgid "E1204: No Number allowed after .: '\\%%%c'"
msgstr "E1204: . sonrası Sayıya izin verilmiyor: '\\%%%c'"
+#, c-format
+msgid "E1273: (NFA regexp) missing value in '\\%%%c'"
+msgstr "E1273: (BSO düzenli ifadesi) '\\%%%c' içinde değer eksik"
+
+#, c-format
+msgid "E1281: Atom '\\%%#=%c' must be at the start of the pattern"
+msgstr "E1281: '\\%%#=%c' atomu dizginin başında olmalı"
+
msgid "E1290: substitute nesting too deep"
msgstr "E1290: Değiştirme iç içe geçmesi pek derin"
@@ -4404,81 +4703,18 @@ msgstr "E167: :scriptencoding kaynak alınmış bir dosyanın dışında kullanÄ
msgid "E168: :finish used outside of a sourced file"
msgstr "E168: :finish kaynak alınmış bir dosyanın dışında kullanıldı"
-msgid "E834: Conflicts with value of 'listchars'"
-msgstr "E834: 'listchars' değeriyle çakışmalar var"
-
-msgid "E835: Conflicts with value of 'fillchars'"
-msgstr "E835: 'fillchars' değeriyle çakışmalar var"
-
-msgid " TERMINAL"
-msgstr " UÇBİRİM"
-
-msgid " VREPLACE"
-msgstr " SANAL DEĞİŞTİR"
-
-msgid " REPLACE"
-msgstr " DEĞİŞTİR"
-
-msgid " REVERSE"
-msgstr " GERİ AL"
-
-msgid " INSERT"
-msgstr " EKLE"
-
-msgid " (terminal)"
-msgstr " (uçbirim)"
-
-msgid " (insert)"
-msgstr " (ekle)"
-
-msgid " (replace)"
-msgstr " (deÄŸiÅŸtir)"
-
-msgid " (vreplace)"
-msgstr " (sanal deÄŸiÅŸtir)"
-
-msgid " Hebrew"
-msgstr " İbranca"
-
-msgid " Arabic"
-msgstr " Arapça"
-
-msgid " (paste)"
-msgstr " (yapıştır)"
-
-msgid " VISUAL"
-msgstr " GÖRSEL"
-
-msgid " VISUAL LINE"
-msgstr " GÖRSEL SATIR"
-
-msgid " VISUAL BLOCK"
-msgstr " GÖRSEL BLOK"
-
-msgid " SELECT"
-msgstr " SEÇ"
-
-msgid " SELECT LINE"
-msgstr " SATIR SEÇ"
-
-msgid " SELECT BLOCK"
-msgstr " BLOK SEÇ"
-
-msgid "recording"
-msgstr "kaydediliyor"
-
#, c-format
-msgid "E383: Invalid search string: %s"
-msgstr "E383: Geçersiz arama dizisi: %s"
-
-#, c-format
-msgid "E384: search hit TOP without match for: %s"
+msgid "E384: Search hit TOP without match for: %s"
msgstr "E384: Arama dosyanın BAŞINA vardı, %s bulunamadı"
#, c-format
-msgid "E385: search hit BOTTOM without match for: %s"
+msgid "E385: Search hit BOTTOM without match for: %s"
msgstr "E385: Arama dosyanın SONUNA vardı, %s bulunamadı"
+#, c-format
+msgid "E383: Invalid search string: %s"
+msgstr "E383: Geçersiz arama dizisi: %s"
+
msgid "E386: Expected '?' or '/' after ';'"
msgstr "E386: ';' sonrasında '?' veya '/' bekleniyordu"
@@ -4629,14 +4865,14 @@ msgid "Writing ShaDa file \"%s\""
msgstr "Paylaşılan veri dosyası \"%s\" yazılıyor"
#, c-format
-msgid "Failed setting uid and gid for file %s: %s"
-msgstr "%s dosyası için uid ve gid ayarlanamadı: %s"
-
-#, c-format
msgid "E137: ShaDa file is not writable: %s"
msgstr "E137: Paylaşılan veri dosyası yazılabilir değil: %s"
#, c-format
+msgid "Failed setting uid and gid for file %s: %s"
+msgstr "%s dosyası için uid ve gid ayarlanamadı: %s"
+
+#, c-format
msgid "Can't rename ShaDa file from %s to %s!"
msgstr "Paylaşılan veri dosyası %s -> %s olarak yeniden adlandırılamıyor!"
@@ -4809,6 +5045,13 @@ msgstr "E753: Bulunamadı: %s"
msgid "E758: Truncated spell file"
msgstr "E758: Kırpılmış yazım dosyası"
+#, c-format
+msgid "E782: Error while reading .sug file: %s"
+msgstr "E782: .sug dosyasını okurken hata: %s"
+
+msgid "E783: Duplicate char in MAP entry"
+msgstr "E783: MAP girdisinde yinelenen karakter"
+
msgid "E1280: Illegal character in word"
msgstr "E1280: Sözcükte izin verilmeyen karakter"
@@ -4860,10 +5103,6 @@ msgid "E781: .sug file doesn't match .spl file: %s"
msgstr "E781: .sug dosyası .spl dosyasına eşleşmiyor: %s"
#, c-format
-msgid "E782: error while reading .sug file: %s"
-msgstr "E782: .sug dosyasını okurken hata: %s"
-
-#, c-format
msgid "Reading affix file %s..."
msgstr "%s ekler dosyası okunuyor..."
@@ -5126,9 +5365,6 @@ msgstr "'%.*s' sözcüğü %s dosyasına eklendi"
msgid "E763: Word characters differ between spell files"
msgstr "E763: Sözcük karakterleri yazım dosyaları arasında ayrım gösteriyor"
-msgid "E783: duplicate char in MAP entry"
-msgstr "E783: MAP girdisinde yinelenen karakter"
-
msgid "Sorry, no suggestions"
msgstr "Üzgünüm, şu an için bir önerim yok"
@@ -5163,6 +5399,16 @@ msgstr "E767: printf() için pek fazla argüman"
msgid "E390: Illegal argument: %s"
msgstr "E390: İzin verilmeyen argüman: %s"
+msgid "E395: Contains argument not accepted here"
+msgstr "E395: Burada kabul edilmeyen bir argüman içeriyor"
+
+msgid "E844: Invalid cchar value"
+msgstr "E844: Geçersiz cchar değeri"
+
+#, c-format
+msgid "E890: Trailing char after ']': %s]%s"
+msgstr "E890: ']' sonrası fazladan karakter: %s]%s"
+
msgid "No Syntax items defined for this buffer"
msgstr "Bu arabellek için sözdizim ögeleri tanımlanmamış"
@@ -5231,12 +5477,6 @@ msgstr "; eÅŸleÅŸme "
msgid " line breaks"
msgstr " satır sonu"
-msgid "E395: contains argument not accepted here"
-msgstr "E395: Burada kabul edilmeyen bir argüman içeriyor"
-
-msgid "E844: invalid cchar value"
-msgstr "E844: Geçersiz cchar değeri"
-
msgid "E393: group[t]here not accepted here"
msgstr "E393: group[t]here burada kabul edilmez"
@@ -5255,10 +5495,6 @@ msgid "E789: Missing ']': %s"
msgstr "E789: ']' eksik: %s"
#, c-format
-msgid "E890: trailing char after ']': %s]%s"
-msgstr "E890: ']' sonrası fazladan karakter: %s]%s"
-
-#, c-format
msgid "E398: Missing '=': %s"
msgstr "E398: '=' eksik: %s"
@@ -5316,25 +5552,29 @@ msgid ""
msgstr ""
" TOPLAM SAYI EŞ EN YAVAŞ ORTALAMA AD DİZGİ"
-msgid "E555: at bottom of tag stack"
+msgid "E73: Tag stack empty"
+msgstr "E73: Etiket yığını boş"
+
+#, c-format
+msgid "E426: Tag not found: %s"
+msgstr "E426: Etiket bulunamadı: %s"
+
+msgid "E555: At bottom of tag stack"
msgstr "E555: Etiket yığınının en dibinde"
-msgid "E556: at top of tag stack"
+msgid "E556: At top of tag stack"
msgstr "E556: Etiket yığınının en tepesinde"
-msgid "E986: cannot modify the tag stack within tagfunc"
+msgid "E986: Cannot modify the tag stack within tagfunc"
msgstr "E986: Etiket yığını tagfunc dahilinde değiştirilemiyor"
-msgid "E987: invalid return value from tagfunc"
+msgid "E987: Invalid return value from tagfunc"
msgstr "E987: Etiket işlevinden geçersiz dönüş değeri"
+
msgid "E425: Cannot go before first matching tag"
msgstr "E425: İlk eşleşen etiketten önceye gidilemiyor"
-#, c-format
-msgid "E426: tag not found: %s"
-msgstr "E426: Etiket bulunamadı: %s"
-
msgid "E427: There is only one matching tag"
msgstr "E427: Eşleşen yalnızca bir etiket var"
@@ -5373,10 +5613,6 @@ msgstr ""
" # ETİKETE SATIRDAN dosya/metin içinde"
#, c-format
-msgid "Searching tags file %s"
-msgstr "Etiket dosyası %s aranıyor"
-
-#, c-format
msgid "E431: Format error in tags file \"%s\""
msgstr "E431: Etiket dosyası \"%s\" içinde biçim hatası"
@@ -5385,6 +5621,10 @@ msgid "Before byte %<PRId64>"
msgstr "%<PRId64> baytından önce"
#, c-format
+msgid "Searching tags file %s"
+msgstr "Etiket dosyası %s aranıyor"
+
+#, c-format
msgid "E432: Tags file not sorted: %s"
msgstr "E432: Etiket dosyası sıralanmadı: %s"
@@ -5401,9 +5641,29 @@ msgstr "E435: Etiket bulunamadı, tahmin ediliyor!"
msgid "Duplicate field name: %s"
msgstr "Yinelenen alan adı: %s"
+msgid ""
+"E856: \"assert_fails()\" second argument must be a string or a list with one "
+"or two strings"
+msgstr ""
+"E856: \"assert_fails()\" ikinci argümanı bir dizi veya bir veya iki dizili "
+"bir liste olmalı"
+
+msgid "E1116: \"assert_fails()\" fifth argument must be a string"
+msgstr "E1116: \"assert_fails()\" beşinci argümanı bir dizi olmalı"
+
msgid "Beep!"
msgstr "Bip!"
+msgid "E439: Undo list corrupt"
+msgstr "E439: Geri al listesi hasarlı"
+
+msgid "E440: Undo line missing"
+msgstr "E440: Geri al satırı eksik"
+
+#, c-format
+msgid "E829: Write error in undo file: %s"
+msgstr "E829: Geri al dosyasında yazma hatası: %s"
+
msgid "E881: Line count changed unexpectedly"
msgstr "E881: Satır sayısı beklenmedik bir biçimde değişti"
@@ -5438,10 +5698,6 @@ msgid "Writing undo file: %s"
msgstr "Geri al dosyası yazılıyor: %s"
#, c-format
-msgid "E829: write error in undo file: %s"
-msgstr "E829: Geri al dosyasında yazma hatası: %s"
-
-#, c-format
msgid "Not reading undo file, owner differs: %s"
msgstr "Geri al dosyası okunmayacak, sahibi farklı: %s"
@@ -5509,6 +5765,12 @@ msgstr "ÅŸundan sonra:"
msgid "before"
msgstr "şundan önce:"
+#, c-format
+msgid "%<PRId64> second ago"
+msgid_plural "%<PRId64> seconds ago"
+msgstr[0] "%<PRId64> saniye önce"
+msgstr[1] "%<PRId64> saniye önce"
+
msgid "Nothing to undo"
msgstr "Geri alınacak bir şey yok"
@@ -5518,19 +5780,17 @@ msgstr "kaydedildiÄŸinde numara deÄŸiÅŸir"
msgid "E790: undojoin is not allowed after undo"
msgstr "E790: Geri al sonrasında geri almalar birleştirilemez"
-msgid "E439: undo list corrupt"
-msgstr "E439: Geri al listesi hasarlı"
-
-msgid "E440: undo line missing"
-msgstr "E440: Geri al satırı eksik"
-
-msgid "E1208: -complete used without allowing arguments"
-msgstr "E1208: -complete, argümanlara izin vermeden kullanıldı"
+#, c-format
+msgid "E179: Argument required for %s"
+msgstr "E179: %s için argüman gerekiyor"
#, c-format
msgid "E184: No such user-defined command: %s"
msgstr "E184: Böyle bir kullanıcı tanımlı komut yok: %s"
+msgid "E1208: -complete used without allowing arguments"
+msgstr "E1208: -complete, argümanlara izin vermeden kullanıldı"
+
#, c-format
msgid "E1237: No such user-defined command in current buffer: %s"
msgstr "E1237: Geçerli arabellekte böyle bir kullanıcı tanımlı komut yok: %s"
@@ -5571,12 +5831,6 @@ msgstr "E177: Sayım iki defa belirtilemez"
msgid "E178: Invalid default value for count"
msgstr "E178: Sayım için geçersiz öntanımlı değer"
-msgid "E179: argument required for -complete"
-msgstr "E179: -complete için argüman gerekiyor"
-
-msgid "E179: argument required for -addr"
-msgstr "E179: -addr için argüman gerekiyor"
-
#, c-format
msgid "E181: Invalid attribute: %s"
msgstr "E181: Geçersiz öznitelik: %s"
@@ -5594,25 +5848,6 @@ msgstr "E183: Kullanıcı tanımlı komutlar BÜYÜK harfle başlamalıdır"
msgid "E841: Reserved name, cannot be used for user defined command"
msgstr "E841: Ayrılmış ad, kullanıcı tanımlı komut için kullanılamaz"
-msgid ""
-"\n"
-"\n"
-"Features: "
-msgstr ""
-"\n"
-"\n"
-"Özellikler: "
-
-msgid ""
-"\n"
-"Compiled "
-msgstr ""
-"\n"
-"Derleyen:"
-
-msgid "by "
-msgstr " "
-
msgid " system vimrc file: \""
msgstr " sistem vimrc dosyası: \""
@@ -5637,6 +5872,10 @@ msgstr "çıkmak için :q<Enter> "
msgid "type :help<Enter> for help "
msgstr "yardım için :help<Enter> "
+#, c-format
+msgid "type :help news<Enter> to see changes in v%s.%s"
+msgstr "v%s.%s deÄŸiÅŸiklikleri :help news<Enter>"
+
msgid "Help poor children in Uganda!"
msgstr "Uganda'daki yoksul çocuklara yardım edin!"
@@ -5655,9 +5894,6 @@ msgstr "bilgi için :help sponsor<Enter> "
msgid "type :help register<Enter> for information "
msgstr "bilgi için :help register<Enter> "
-msgid "menu Help->Sponsor/Register for information "
-msgstr "bilgi için Yardım -> Sponsorluk/Kayıt"
-
#, c-format
msgid "E15: Invalid control character present in input: %.*s"
msgstr "E15: Girdide geçersiz denetim karakteri var: %.*s"
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 06f845f113..83898cda12 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -12,18 +12,1261 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: vim 7.4\n"
+"Project-Id-Version: Neovim Ukrainian\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-04-13 10:28+0300\n"
-"PO-Revision-Date: 2020-08-23 20:19+0300\n"
+"POT-Creation-Date: 2023-08-26 20:36+0300\n"
+"PO-Revision-Date: 2023-08-26 21:00+0300\n"
"Last-Translator: Ðнатолій Сахнік <sakhnik@gmail.com>\n"
"Language-Team: Ukrainian\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n"
-"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+msgid "(local to window)"
+msgstr "(тільки у вікні)"
+
+msgid "(local to buffer)"
+msgstr "(тільки в буфері)"
+
+msgid "(global or local to buffer)"
+msgstr "(вÑюди або тільки в буфері)"
+
+msgid ""
+"\" Each \"set\" line shows the current value of an option (on the left)."
+msgstr "\" Кожен Ñ€Ñдок «set» показує теперішнє Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¾Ð¿Ñ†Ñ–Ñ— (ліворуч)."
+
+msgid "\" Hit <Enter> on a \"set\" line to execute it."
+msgstr "\" ÐатиÑніть <Enter> у Ñ€Ñдку «set» щоб виконати."
+
+msgid "\" A boolean option will be toggled."
+msgstr "\" Опцію-перемикач буде перемкнено."
+
+msgid ""
+"\" For other options you can edit the value before hitting "
+"<Enter>."
+msgstr ""
+"\" Ð”Ð»Ñ Ñ–Ð½ÑˆÐ¸Ñ… опцій можна відредагувати Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´ натиÑненнÑм "
+"на <Enter>."
+
+msgid "\" Hit <Enter> on a help line to open a help window on this option."
+msgstr ""
+"\" ÐатиÑніть <Enter> на Ñ€Ñдку 'допомога' щоб відкрити вікно з допомогою про "
+"цю опцію."
+
+msgid "\" Hit <Enter> on an index line to jump there."
+msgstr "\" ÐатиÑніÑть <Enter> на Ñ€Ñдку вказівника щоб туди переÑтрибнути."
+
+msgid "\" Hit <Space> on a \"set\" line to refresh it."
+msgstr "\" ÐатиÑніть <Пробіл> на Ñ€Ñдку «set» Ð´Ð»Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ."
+
+msgid "important"
+msgstr "важливо"
+
+msgid "behave very Vi compatible (not advisable)"
+msgstr "поводитиÑÑ Ð´ÑƒÐ¶Ðµ ÑуміÑно з Vi (не рекомендовано)"
+
+msgid "list of flags to specify Vi compatibility"
+msgstr "ÑпиÑок прапорців щоб задати ÑуміÑніÑть із Vi"
+
+msgid "paste mode, insert typed text literally"
+msgstr "режим вклеюваннÑ, вÑтавити набраний текÑÑ‚ буквально"
+
+msgid "list of directories used for runtime files and plugins"
+msgstr "ÑпиÑок директорій з файлами чаÑу Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ñ– плагінами"
+
+msgid "list of directories used for plugin packages"
+msgstr "ÑпиÑок директорій з пакунками плагінів"
+
+msgid "name of the main help file"
+msgstr "назва головного файлу допомоги"
+
+msgid "moving around, searching and patterns"
+msgstr "переміщеннÑ, пошук Ñ– шаблони"
+
+msgid "list of flags specifying which commands wrap to another line"
+msgstr "ÑпиÑок прапорців, Ñкі задають, котрі команди переноÑÑть на новий Ñ€Ñдок"
+
+msgid ""
+"many jump commands move the cursor to the first non-blank\n"
+"character of a line"
+msgstr ""
+"багато команд Ñтрибків переміщують курÑор до першого непорожнього\n"
+"Ñимволу Ñ€Ñдка"
+
+msgid "nroff macro names that separate paragraphs"
+msgstr "назви макроÑів nroff, Ñкі розділÑють параграфи"
+
+msgid "nroff macro names that separate sections"
+msgstr "назви макроÑів nroff, що розділÑють розділи"
+
+msgid "list of directory names used for file searching"
+msgstr "ÑпиÑок назв директорій Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ файлів"
+
+msgid ":cd without argument goes to the home directory"
+msgstr ":cd без аргументу переходить до домашньої директорії"
+
+msgid "list of directory names used for :cd"
+msgstr "ÑпиÑок назв директорій Ð´Ð»Ñ :cd"
+
+msgid "change to directory of file in buffer"
+msgstr "перейти до директорії файлу в буфері"
+
+msgid "search commands wrap around the end of the buffer"
+msgstr "команди пошуку починають Ñпочатку піÑÐ»Ñ ÐºÑ–Ð½Ñ†Ñ Ð±ÑƒÑ„ÐµÑ€Ð°"
+
+msgid "show match for partly typed search command"
+msgstr "показувати збіг чаÑтково набраної команди пошуку"
+
+msgid "change the way backslashes are used in search patterns"
+msgstr "змінити ÑпоÑіб вжитку \\ у шаблонах пошуку"
+
+msgid "select the default regexp engine used"
+msgstr "вибрати оÑновний рушій регулÑрних виразів"
+
+msgid "ignore case when using a search pattern"
+msgstr "нехтувати регіÑтром у шаблоні пошуку"
+
+msgid "override 'ignorecase' when pattern has upper case characters"
+msgstr "відкинути 'ignorecase', коли шаблон має Ñимволи верхнього регіÑтру"
+
+msgid "what method to use for changing case of letters"
+msgstr "Ñкий метод викориÑтати Ð´Ð»Ñ Ð·Ð¼Ñ–Ð½Ð¸ регіÑтру букв"
+
+msgid "maximum amount of memory in Kbyte used for pattern matching"
+msgstr ""
+"макÑимальний обÑÑг пам’Ñті в Кб Ð´Ð»Ñ ÑпівÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð· регулÑрними вирахами"
+
+msgid "pattern for a macro definition line"
+msgstr "шаблон Ñ€Ñдка Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¼Ð°ÐºÑ€Ð¾Ñу"
+
+msgid "pattern for an include-file line"
+msgstr "шаблон Ñ€Ñдка Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ"
+
+msgid "expression used to transform an include line to a file name"
+msgstr "вираз Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ€Ñдка Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ñƒ назву файлу"
+
+msgid "tags"
+msgstr "мітки"
+
+msgid "use binary searching in tags files"
+msgstr "заÑтоÑовувати двійковий пошук у файлі міток"
+
+msgid "number of significant characters in a tag name or zero"
+msgstr "кількіÑть значущих Ñимволів у назві мітки або нуль"
+
+msgid "list of file names to search for tags"
+msgstr "ÑпиÑок назв файлів Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ міток"
+
+msgid ""
+"how to handle case when searching in tags files:\n"
+"\"followic\" to follow 'ignorecase', \"ignore\" or \"match\""
+msgstr ""
+"Ñк обходитиÑÑŒ із регіÑтром при пошуку в файлах міток:\n"
+"«followic» так Ñамо, Ñк у 'ignorecase', «ignore» або «match»"
+
+msgid "file names in a tags file are relative to the tags file"
+msgstr "назви файлів у файлі міток задані відноÑно файлу міток"
+
+msgid "a :tag command will use the tagstack"
+msgstr "команда :tag викориÑтовуватиме Ñтек міток"
+
+msgid "when completing tags in Insert mode show more info"
+msgstr "при доповненні міток в режимі Insert показувати більше інформації"
+
+msgid "a function to be used to perform tag searches"
+msgstr "функціÑ, Ñку заÑтоÑовувати при пошуку міток"
+
+msgid "displaying text"
+msgstr "Ð¿Ð¾ÐºÐ°Ð·ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑту"
+
+msgid "number of lines to scroll for CTRL-U and CTRL-D"
+msgstr "кількіÑть Ñ€Ñдків Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸ CTRL-U Ñ– CTRL-D"
+
+msgid "scroll by screen line"
+msgstr "прокручувати по Ñ€Ñдках на екрані"
+
+msgid "number of screen lines to show around the cursor"
+msgstr "кількіÑть екранних Ñ€Ñдків, Ñкі показувати навколо курÑору"
+
+msgid "long lines wrap"
+msgstr "перенеÑÐµÐ½Ð½Ñ Ð´Ð¾Ð²Ð³Ð¸Ñ… Ñ€Ñдків"
+
+msgid "wrap long lines at a character in 'breakat'"
+msgstr "переноÑити довгі Ñ€Ñдки по Ñимволах у 'breakat'"
+
+msgid "preserve indentation in wrapped text"
+msgstr "зберегти відÑтупи у перенеÑеному текÑті"
+
+msgid "adjust breakindent behaviour"
+msgstr "підлаштувати поведінку breakindent"
+
+msgid "which characters might cause a line break"
+msgstr "Ñкі Ñимволи можуть Ñпричинити Ñ€Ð¾Ð·Ð±Ð¸Ñ‚Ñ‚Ñ Ñ€Ñдка"
+
+msgid "string to put before wrapped screen lines"
+msgstr "Ñимволи, Ñкі розміÑтити перед перенеÑеними екранними Ñ€Ñдками"
+
+msgid "minimal number of columns to scroll horizontally"
+msgstr "найменша кількіÑть Ñтовпців горизонтального прокручуваннÑ"
+
+msgid "minimal number of columns to keep left and right of the cursor"
+msgstr ""
+"найменша кількіÑть Ñтовпців, Ñкі показувати ліворуч Ñ– праворуч від курÑору"
+
+msgid ""
+"include \"lastline\" to show the last line even if it doesn't fit\n"
+"include \"uhex\" to show unprintable characters as a hex number"
+msgstr ""
+"додайте «lastline» щоб показати оÑтанній Ñ€Ñдок навіть Ñкщо він не влазить\n"
+"додайте «uhex» щоб показати недруковні Ñимволи Ñк шіÑтнадцÑткові чиÑла"
+
+msgid "characters to use for the status line, folds and filler lines"
+msgstr "Ñимволи Ð´Ð»Ñ Ñ€Ñдка Ñтану, згорток Ñ– заповнювачів"
+
+msgid "number of lines used for the command-line"
+msgstr "кількіÑть Ñ€Ñдків Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ð³Ð¾ Ñ€Ñдка"
+
+msgid "width of the display"
+msgstr "ширина диÑплею"
+
+msgid "number of lines in the display"
+msgstr "кількіÑть Ñ€Ñдків у диÑплеї"
+
+msgid "number of lines to scroll for CTRL-F and CTRL-B"
+msgstr "кількіÑть Ñ€Ñдків Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð· допомогою CTRL-F Ñ– CTRL-B"
+
+msgid "don't redraw while executing macros"
+msgstr "не перемальовувати при виконанні макроÑів"
+
+msgid "timeout for 'hlsearch' and :match highlighting in msec"
+msgstr "тайм-аут (мÑ) Ð´Ð»Ñ Ð¿Ñ–Ð´ÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ 'hlsearch' Ñ– :match"
+
+msgid ""
+"delay in msec for each char written to the display\n"
+"(for debugging)"
+msgstr ""
+"затримка (мÑ) Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ Ñимволу, що запиÑуєтьÑÑ Ñƒ диÑплей\n"
+"(Ð´Ð»Ñ Ð½Ð°Ð»Ð°Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ)"
+
+msgid "show <Tab> as ^I and end-of-line as $"
+msgstr "показувати <Tab> Ñк ^I Ñ– кінець Ñ€Ñдка Ñк $"
+
+msgid "list of strings used for list mode"
+msgstr "ÑпиÑок Ñлів, Ñкі вживаютьÑÑ Ð² режимі list"
+
+msgid "show the line number for each line"
+msgstr "показувати номери Ñ€Ñдків"
+
+msgid "show the relative line number for each line"
+msgstr "показувати відноÑні номери Ñ€Ñдків"
+
+msgid "number of columns to use for the line number"
+msgstr "кількіÑть Ñтовпців Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ номерів Ñ€Ñдків"
+
+msgid "controls whether concealable text is hidden"
+msgstr "контролює Ð¿Ñ€Ð¸Ñ…Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð¼Ð°Ñкованого текÑту"
+
+msgid "modes in which text in the cursor line can be concealed"
+msgstr "режими, в Ñких текÑÑ‚ в Ñ€Ñдку з курÑором маÑкуєтьÑÑ"
+
+msgid "syntax, highlighting and spelling"
+msgstr "ÑинтакÑиÑ, підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñ– орфографіÑ"
+
+msgid "\"dark\" or \"light\"; the background color brightness"
+msgstr "«dark» або «light»; ÑÑкравіÑть кольору фону"
+
+msgid "type of file; triggers the FileType event when set"
+msgstr "тип файлу; запуÑкає подію FileType при вÑтановленні"
+
+msgid "name of syntax highlighting used"
+msgstr "назва Ñхеми підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ ÑинтакÑиÑу"
+
+msgid "maximum column to look for syntax items"
+msgstr "крайній Ñтовпець Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ елементів ÑинтакÑиÑу"
+
+msgid "which highlighting to use for various occasions"
+msgstr "Ñке підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°ÑтоÑовувати у різних випадках"
+
+msgid "highlight all matches for the last used search pattern"
+msgstr "підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ ÑƒÑÑ–Ñ… збігів попереднього шаблону пошуку"
+
+msgid "use GUI colors for the terminal"
+msgstr "викориÑтовувати кольори GUI у терміналі"
+
+msgid "highlight the screen column of the cursor"
+msgstr "підÑвічувати Ñтовпець з курÑором"
+
+msgid "highlight the screen line of the cursor"
+msgstr "підÑвічувати екранний Ñ€Ñдок із курÑором"
+
+msgid "specifies which area 'cursorline' highlights"
+msgstr "визначає, Ñку ділÑнку підÑвічує 'cursorline'"
+
+msgid "columns to highlight"
+msgstr "підÑвічувані Ñтовпці"
+
+msgid "highlight spelling mistakes"
+msgstr "підÑвічувати помилки орфографії"
+
+msgid "list of accepted languages"
+msgstr "ÑпиÑок мов перевірки орфографії"
+
+msgid "file that \"zg\" adds good words to"
+msgstr "файл, у Ñкий команда «zg» додає хороші Ñлова"
+
+msgid "pattern to locate the end of a sentence"
+msgstr "шаблон Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ ÐºÑ–Ð½Ñ†Ñ Ñ€ÐµÑ‡ÐµÐ½Ð½Ñ"
+
+msgid "flags to change how spell checking works"
+msgstr "прапорці Ð´Ð»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ орфографії"
+
+msgid "methods used to suggest corrections"
+msgstr "метод підказки виправлень"
+
+msgid "amount of memory used by :mkspell before compressing"
+msgstr "обÑÑг пам’Ñті Ð´Ð»Ñ :mkspell перед ÑтиÑненнÑм"
+
+msgid "multiple windows"
+msgstr "багато вікон"
+
+msgid "0, 1, 2 or 3; when to use a status line for the last window"
+msgstr "0, 1, 2 або 2; коли показувати Ñ€Ñдок Ñтану в оÑтанньому вікні"
+
+msgid "custom format for the status column"
+msgstr "влаÑний формат ÑÑ‚Ð¾Ð²Ð¿Ñ†Ñ Ñтану"
+
+msgid "alternate format to be used for a status line"
+msgstr "змінний формат Ñ€Ñдка Ñтану"
+
+msgid "make all windows the same size when adding/removing windows"
+msgstr "зробити вÑÑ– вікна однакового розміру при додаванні/закритті вікон"
+
+msgid "in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\""
+msgstr "в Ñкому напрÑмку працює 'equalalways': «ver», «hor» або «both»"
+
+msgid "minimal number of lines used for the current window"
+msgstr "найменша кількіÑть Ñ€Ñдків у активному вікні"
+
+msgid "minimal number of lines used for any window"
+msgstr "найменша кількіÑть Ñ€Ñдків у будь-Ñкому вікні"
+
+msgid "keep the height of the window"
+msgstr "витримувати виÑоту вікна"
+
+msgid "keep the width of the window"
+msgstr "витримувати ширину вікна"
+
+msgid "minimal number of columns used for the current window"
+msgstr "найменша кількіÑть Ñтовпців активного вікна"
+
+msgid "minimal number of columns used for any window"
+msgstr "найменша кількіÑть Ñтовпців будь-Ñкого вікна"
+
+msgid "initial height of the help window"
+msgstr "початкова виÑота вікна допомоги"
+
+msgid "default height for the preview window"
+msgstr "початкова виÑота вікна попереднього переглÑду"
+
+msgid "identifies the preview window"
+msgstr "ідентифікує вікно попереднього переглÑду"
+
+msgid "don't unload a buffer when no longer shown in a window"
+msgstr "не вивантажувати буфер, Ñкий більше не показуєтьÑÑ Ñƒ вікні"
+
+msgid ""
+"\"useopen\" and/or \"split\"; which window to use when jumping\n"
+"to a buffer"
+msgstr ""
+"«useopen» Ñ–/або «split»; Ñке вікно викориÑтовувати при Ñтрибках\n"
+"до буферу"
+
+msgid "a new window is put below the current one"
+msgstr "нове вікно розміщуєтьÑÑ Ð¿Ñ–Ð´ активним"
+
+msgid "determines scroll behavior for split windows"
+msgstr "визначає поведінку Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñƒ розщеплених вікнах"
+
+msgid "a new window is put right of the current one"
+msgstr "нове вікно розміщуєтьÑÑ Ð¿Ñ€Ð°Ð²Ð¾Ñ€ÑƒÑ‡ від активного"
+
+msgid "this window scrolls together with other bound windows"
+msgstr "це вікно прокручуєтьÑÑ Ñ€Ð°Ð·Ð¾Ð¼ із іншими пов’Ñзаними вікнами"
+
+msgid "\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'"
+msgstr "«ver», «hor» Ñ–/або «jump»; ÑпиÑок опцій Ð´Ð»Ñ 'scrollbind'"
+
+msgid "this window's cursor moves together with other bound windows"
+msgstr "курÑор цього вікна рухаєтьÑÑ Ñ€Ð°Ð·Ð¾Ð¼ із іншими пов’Ñзаними вікнами"
+
+msgid "size of a terminal window"
+msgstr "розмір термінального вікна"
+
+msgid "key that precedes Vim commands in a terminal window"
+msgstr "клавіша перед командами Vim у термінальному вікні"
+
+msgid "multiple tab pages"
+msgstr "Ñторінки вкладок"
+
+msgid "0, 1 or 2; when to use a tab pages line"
+msgstr "0, 1 або 2; коли викориÑтовувати Ñ€Ñдок Ñторінок вкладок"
+
+msgid "maximum number of tab pages to open for -p and \"tab all\""
+msgstr "найбільша кількіÑть Ñторінок вкладок при -p Ñ– «tab all»"
+
+msgid "custom tab pages line"
+msgstr "влаÑний Ñ€Ñдок Ñторінок вкладок"
+
+msgid "custom tab page label for the GUI"
+msgstr "влаÑний Ñрлик Ñторінок вкладок в GUI"
+
+msgid "custom tab page tooltip for the GUI"
+msgstr "влаÑна Ñпливаюча підказка Ñторінок вкладок в GUI"
+
+msgid "terminal"
+msgstr "термінал"
+
+msgid "minimal number of lines to scroll at a time"
+msgstr "найменша кількіÑть Ñ€Ñдків Ð¿Ñ€Ð¾Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð·Ð° раз"
+
+msgid "specifies what the cursor looks like in different modes"
+msgstr "визначає, Ñкий має виглÑд курÑор у різних режимах"
+
+msgid "show info in the window title"
+msgstr "показувати інформацію у заголовку вікна"
+
+msgid "percentage of 'columns' used for the window title"
+msgstr "відÑоток 'columns' Ð´Ð»Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÑƒ вікна"
+
+msgid "when not empty, string to be used for the window title"
+msgstr "коли не порожнÑ, текÑтовий Ñ€Ñдок Ð´Ð»Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÑƒ вікна"
+
+msgid "string to restore the title to when exiting Vim"
+msgstr "текÑтовий Ñ€Ñдок щоб відновити заголовок при виході з Vim"
+
+msgid "set the text of the icon for this window"
+msgstr "вÑтановити текÑÑ‚ образка Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ вікна"
+
+msgid "when not empty, text for the icon of this window"
+msgstr "коли не порожнÑ, текÑÑ‚ Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð·ÐºÐ° цього вікна"
+
+msgid "using the mouse"
+msgstr "викориÑÑ‚Ð°Ð½Ð½Ñ Ð¼Ð¸ÑˆÑ–"
+
+msgid "list of flags for using the mouse"
+msgstr "ÑпиÑок прапорців Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð¼Ð¸ÑˆÑ–"
+
+msgid "the window with the mouse pointer becomes the current one"
+msgstr "вікно із вказівником миші активуєтьÑÑ"
+
+msgid "hide the mouse pointer while typing"
+msgstr "ховати вказівник миші при набиранні текÑту"
+
+msgid ""
+"\"extend\", \"popup\" or \"popup_setpos\"; what the right\n"
+"mouse button is used for"
+msgstr ""
+"«extend», «popup» або «popup_setpos»; Ð´Ð»Ñ Ñ‡Ð¾Ð³Ð¾ викориÑтовуєтьÑÑ\n"
+"права клавіша миші"
+
+msgid "maximum time in msec to recognize a double-click"
+msgstr "найбільший Ñ‡Ð°Ñ (мÑ) щоб розпізнати подвійне натиÑканнÑ"
+
+msgid "what the mouse pointer looks like in different modes"
+msgstr "виглÑд вказівника миші в різних режимах"
+
+msgid "GUI"
+msgstr "GUI"
+
+msgid "list of font names to be used in the GUI"
+msgstr "ÑпиÑок назв шрифтів у GUI"
+
+msgid "pair of fonts to be used, for multibyte editing"
+msgstr "пари шрифтів Ð´Ð»Ñ Ð±Ð°Ð³Ð°Ñ‚Ð¾Ð±Ð°Ð¹Ñ‚Ð½Ð¾Ð³Ð¾ редагуваннÑ"
+
+msgid "list of font names to be used for double-wide characters"
+msgstr "ÑпиÑок назв шрифтів Ð´Ð»Ñ Ñимволів подвійної ширини"
+
+msgid "list of flags that specify how the GUI works"
+msgstr "ÑпиÑок прапорців, що визначають поведінку GUI"
+
+msgid "\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar"
+msgstr "«icons», «text» Ñ–/або «tooltips»; Ñк показувати панель інÑтрументів"
+
+msgid "size of toolbar icons"
+msgstr "розмір образків панелі інÑтрументів"
+
+msgid ""
+"\"last\", \"buffer\" or \"current\": which directory used for the file "
+"browser"
+msgstr ""
+"«last», «buffer» або «current»: Ñку директорію викориÑтовувати Ð´Ð»Ñ Ð¾Ð³Ð»Ñдача "
+"файлів"
+
+msgid "language to be used for the menus"
+msgstr "мова меню"
+
+msgid "maximum number of items in one menu"
+msgstr "найбільша кількіÑть елементів у одному меню"
+
+msgid "\"no\", \"yes\" or \"menu\"; how to use the ALT key"
+msgstr "«no», «yes» або «menu»; Ñк викориÑтовувати клавішу ALT"
+
+msgid "number of pixel lines to use between characters"
+msgstr "кількіÑть Ñ€Ñдків пікÑелів між Ñимволами"
+
+msgid "delay in milliseconds before a balloon may pop up"
+msgstr "затримка (мÑ) перед показом Ñпливаючої підказки"
+
+msgid "use balloon evaluation in the GUI"
+msgstr "активувати balloon evaluation в GUI"
+
+msgid "use balloon evaluation in the terminal"
+msgstr "активувати balloon evaluation в терміналі"
+
+msgid "expression to show in balloon eval"
+msgstr "показуваний вираз у balloon eval"
+
+msgid "messages and info"
+msgstr "Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¹ інформаціÑ"
+
+msgid "add 's' flag in 'shortmess' (don't show search message)"
+msgstr "додати прапорець 's' у 'shortmess' (не показувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ)"
+
+msgid "list of flags to make messages shorter"
+msgstr "ÑпиÑок прапорців щоб зробити Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ñ€Ð¾Ñ‚ÑˆÐ¸Ð¼Ð¸"
+
+msgid "show (partial) command keys in location given by 'showcmdloc'"
+msgstr "показувати клавіші (чаÑтини) команди у міÑці, заданому 'showcmdloc'"
+
+msgid "location where to show the (partial) command keys for 'showcmd'"
+msgstr "міÑце, де показувати клавіші (чаÑтини) команди Ð´Ð»Ñ 'showcmd'"
+
+msgid "display the current mode in the status line"
+msgstr "показувати актуальний режим у Ñ€Ñдку Ñтану"
+
+msgid "show cursor position below each window"
+msgstr "показувати Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ñ ÐºÑƒÑ€Ñору під кожним вікном"
+
+msgid "alternate format to be used for the ruler"
+msgstr "змінний формат лінійки"
+
+msgid "threshold for reporting number of changed lines"
+msgstr "поріг доповіді кількоÑті змінених Ñ€Ñдків"
+
+msgid "the higher the more messages are given"
+msgstr "чим більше, тим більше буде видано повідомлень"
+
+msgid "file to write messages in"
+msgstr "файл, у Ñкий запиÑувати повідомленнÑ"
+
+msgid "pause listings when the screen is full"
+msgstr "Ñпинити виведеннÑ, коли екран заповнено"
+
+msgid "start a dialog when a command fails"
+msgstr "почати діалог, коли команда зазнає невдачі"
+
+msgid "ring the bell for error messages"
+msgstr "подати звук при повідомленнÑÑ… про помилки"
+
+msgid "use a visual bell instead of beeping"
+msgstr "Ð±Ð»Ð¸Ð¼Ð°Ð½Ð½Ñ Ð·Ð°Ð¼Ñ–Ñть звукового Ñигналу"
+
+msgid "do not ring the bell for these reasons"
+msgstr "не подавати Ñигнал у таких випадках"
+
+msgid "list of preferred languages for finding help"
+msgstr "ÑпиÑок бажаних мов при пошуку допомоги"
+
+msgid "selecting text"
+msgstr "Ð²Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ Ñ‚ÐµÐºÑту"
+
+msgid "\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves"
+msgstr "«old», «inclusive» або «exclusive»; Ñк поводитьÑÑ Ð²Ð¸Ð±Ñ–Ñ€ текÑту"
+
+msgid ""
+"\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode\n"
+"instead of Visual mode"
+msgstr ""
+"«mouse», «key» і/або «cmd»; коли починати режим Select\n"
+"заміÑть Visual"
+
+msgid ""
+"\"unnamed\" to use the * register like unnamed register\n"
+"\"autoselect\" to always put selected text on the clipboard"
+msgstr ""
+"«unnamed» щоб викориÑтовувати регіÑтр * Ñк безіменний регіÑтр\n"
+"«autoselect» щоб завжди поміщати вибраний текÑÑ‚ у буфер обміну"
+
+msgid "\"startsel\" and/or \"stopsel\"; what special keys can do"
+msgstr "«startsel» Ñ–/або «stopsel»; що можуть робити Ñпеціальні клавіші"
+
+msgid "editing text"
+msgstr "Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÑту"
+
+msgid "maximum number of changes that can be undone"
+msgstr "найбільша кількіÑть змін, Ñкі можна повернути"
+
+msgid "automatically save and restore undo history"
+msgstr "автоматично зберігати Ñ– відновлювати Ñ–Ñторію змін"
+
+msgid "list of directories for undo files"
+msgstr "ÑпиÑок директорій Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð² Ñ–Ñторії"
+
+msgid "maximum number lines to save for undo on a buffer reload"
+msgstr ""
+"найбільша кількіÑть Ñ€Ñдків, Ñкі зберігати Ð´Ð»Ñ Ñ–Ñторії змін при перечитуванні "
+"буферу"
+
+msgid "changes have been made and not written to a file"
+msgstr "виконано зміни Ñ– не запиÑано у файл"
+
+msgid "buffer is not to be written"
+msgstr "буфер не Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
+
+msgid "changes to the text are possible"
+msgstr "зміни у текÑті можливі"
+
+msgid "line length above which to break a line"
+msgstr "довжина Ñ€Ñдка, понад Ñкою Ñ€Ñдок розбивати"
+
+msgid "margin from the right in which to break a line"
+msgstr "відÑтуп з правого краю, по Ñкому розбивати Ñ€Ñдок"
+
+msgid "specifies what <BS>, CTRL-W, etc. can do in Insert mode"
+msgstr "визначає, що <BS>, CTRL-W тощо можуть робити у режимі Insert"
+
+msgid "definition of what comment lines look like"
+msgstr "Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‚Ð¾Ð³Ð¾, Ñкий виглÑд мають Ñ€Ñдки коментарів"
+
+msgid "list of flags that tell how automatic formatting works"
+msgstr "ÑпиÑок прапорців, Ñкі визначають, Ñк працює автоматичне форматуваннÑ"
+
+msgid "pattern to recognize a numbered list"
+msgstr "шаблон Ñ€Ð¾Ð·Ð¿Ñ–Ð·Ð½Ð°Ð²Ð°Ð½Ð½Ñ Ð½ÑƒÐ¼ÐµÑ€Ð¾Ð²Ð°Ð½Ð¾Ð³Ð¾ ÑпиÑку"
+
+msgid "expression used for \"gq\" to format lines"
+msgstr "вираз, Ñкий викориÑтати Ð´Ð»Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ñдків при «gq»"
+
+msgid "specifies how Insert mode completion works for CTRL-N and CTRL-P"
+msgstr ""
+"визначає, Ñк працює Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ñ‚ÐµÐºÑту в режимі Insert при CTRL-N Ñ– CTRL-P"
+
+msgid "whether to use a popup menu for Insert mode completion"
+msgstr "чи викориÑтовувати Ñпливаюче меню при доповненні в режимі Insert"
+
+msgid "maximum height of the popup menu"
+msgstr "найбільша виÑота Ñпливаючого меню"
+
+msgid "minimum width of the popup menu"
+msgstr "найменша ширина Ñпливаючого меню"
+
+msgid "user defined function for Insert mode completion"
+msgstr "кориÑтувацька Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð² режимі Insert"
+
+msgid "function for filetype-specific Insert mode completion"
+msgstr "Ñпецифічна до типу файлу Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð² режимі Insert"
+
+msgid "list of dictionary files for keyword completion"
+msgstr "ÑпиÑок Ñловникових файлів Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð¾Ð²Ð¸Ñ… Ñлів"
+
+msgid "list of thesaurus files for keyword completion"
+msgstr "ÑпиÑок файлів тезауруÑу Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð¾Ð²Ð¸Ñ… Ñлів"
+
+msgid "function used for thesaurus completion"
+msgstr "Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð¿Ð¾ тезауруÑу"
+
+msgid "adjust case of a keyword completion match"
+msgstr "поправлÑти регіÑтр букв при доповненні ключових Ñлів"
+
+msgid "enable entering digraphs with c1 <BS> c2"
+msgstr "увімкнути Ð²Ð²ÐµÐ´ÐµÐ½Ð½Ñ Ð´Ð¸Ð³Ñ€Ð°Ñ„Ñ–Ð² з допомогою c1 <BS> c2"
+
+msgid "the \"~\" command behaves like an operator"
+msgstr "команда «~» поводитьÑÑ Ñк оператор"
+
+msgid "function called for the \"g@\" operator"
+msgstr "функціÑ, що викликаєтьÑÑ Ð¿Ñ€Ð¸ операторові «g@»"
+
+msgid "when inserting a bracket, briefly jump to its match"
+msgstr ""
+"коли вÑтавлÑєтьÑÑ Ð´ÑƒÐ¶ÐºÐ°, на короткий Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÑтрибнути до Ñ—Ñ— відповідника"
+
+msgid "tenth of a second to show a match for 'showmatch'"
+msgstr "деÑÑті чаÑтини Ñекунди щоб показати збіг у 'showmatch'"
+
+msgid "list of pairs that match for the \"%\" command"
+msgstr "ÑпиÑок пар, що розпізнаютьÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¾ÑŽ «%»"
+
+msgid "use two spaces after '.' when joining a line"
+msgstr "викориÑтовувати два пропуÑки піÑÐ»Ñ '.' при Ñполученні Ñ€Ñдків"
+
+msgid ""
+"\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\n"
+"recognized for CTRL-A and CTRL-X commands"
+msgstr ""
+"«alpha», «octal», «hex», «bin» Ñ–/чи «unsigned»; формати чиÑел,\n"
+"Ñкі розпізнаютьÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°Ð¼Ð¸ CTRL-A Ñ– CTRL-X"
+
+msgid "tabs and indenting"
+msgstr "табулÑÑ†Ñ–Ñ Ñ– відÑтупи"
+
+msgid "number of spaces a <Tab> in the text stands for"
+msgstr "кількіÑть пробілів, Ñкій дорівнює в текÑті один <Tab>"
+
+msgid "number of spaces used for each step of (auto)indent"
+msgstr "кількіÑть пробілів у кожному кроці (авто)відÑтупу"
+
+msgid "list of number of spaces a tab counts for"
+msgstr "ÑпиÑок кількоÑті пробілів на кожному рівні табулÑції"
+
+msgid "list of number of spaces a soft tabsstop counts for"
+msgstr "ÑпиÑок кількоÑті пробілів на кожному рівні гнучкої табулÑції"
+
+msgid "a <Tab> in an indent inserts 'shiftwidth' spaces"
+msgstr "<Tab> у відÑтупі вÑтавлÑÑ” 'shiftwidth' пробілів"
+
+msgid "if non-zero, number of spaces to insert for a <Tab>"
+msgstr "Ñкщо не нуль, кількіÑть пробілів, Ñкі потрібно вÑтавити при <Tab>"
+
+msgid "round to 'shiftwidth' for \"<<\" and \">>\""
+msgstr "округлити до 'shiftwidth' при «<<» і «>>»"
+
+msgid "expand <Tab> to spaces in Insert mode"
+msgstr "замінити <Tab> пробілами в режимі Insert"
+
+msgid "automatically set the indent of a new line"
+msgstr "автоматично вÑтановити відÑтуп нового Ñ€Ñдка"
+
+msgid "do clever autoindenting"
+msgstr "інтелектуальний автоматичний відÑтуп"
+
+msgid "enable specific indenting for C code"
+msgstr "увімкнути Ñпецифічні відÑтупи в коді С"
+
+msgid "options for C-indenting"
+msgstr "опції відÑтупу С"
+
+msgid "keys that trigger C-indenting in Insert mode"
+msgstr "клавіші, що ÑпричинÑють відÑтупи С в режимі Insert"
+
+msgid "list of words that cause more C-indent"
+msgstr "ÑпиÑок Ñлів, Ñкі ÑпричинÑють більше відÑтупу С"
+
+msgid "list of scope declaration names used by cino-g"
+msgstr "ÑпиÑок назв оголошень облаÑті видимоÑті, що вживаютьÑÑ Ð² cino-g"
+
+msgid "expression used to obtain the indent of a line"
+msgstr "вираз Ð´Ð»Ñ Ð¾Ð±Ñ‡Ð¸ÑÐ»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ñтупу Ñ€Ñдка"
+
+msgid "keys that trigger indenting with 'indentexpr' in Insert mode"
+msgstr ""
+"клавіші, що запуÑкають Ð¿Ñ–Ð´Ð±Ð¸Ð²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ñтупів з допомогою 'indentexpr' у "
+"режимі Insert"
+
+msgid "copy whitespace for indenting from previous line"
+msgstr "Ñкопіювати пропуÑки з попереднього Ñ€Ñдка при відÑтупах"
+
+msgid "preserve kind of whitespace when changing indent"
+msgstr "зберегти вид пропуÑку при зміні відÑтупу"
+
+msgid "enable lisp mode"
+msgstr "увімкнути режим lisp"
+
+msgid "words that change how lisp indenting works"
+msgstr "Ñлова, Ñкі змінюють, Ñк працює Ð¿Ñ–Ð´Ð±Ð¸Ð²Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ñтупів у lisp"
+
+msgid "options for Lisp indenting"
+msgstr "опції відÑтупу Lisp"
+
+msgid "folding"
+msgstr "згортаннÑ"
+
+msgid "unset to display all folds open"
+msgstr "Ñкиньте щоб відкрити вÑÑ– згортки"
+
+msgid "folds with a level higher than this number will be closed"
+msgstr "згортки вищого рівнÑ, ніж це чиÑло, будуть закриті"
+
+msgid "value for 'foldlevel' when starting to edit a file"
+msgstr "Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ 'foldlevel' на початку Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ"
+
+msgid "width of the column used to indicate folds"
+msgstr "ширина колонки, що позначає згортки"
+
+msgid "expression used to display the text of a closed fold"
+msgstr "вираз, що викориÑтовуєтьÑÑ Ð´Ð»Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‚ÐµÐºÑту закритої згортки"
+
+msgid "set to \"all\" to close a fold when the cursor leaves it"
+msgstr "вÑтановити у «all» щоб закривати згортку, коли курÑор з неї виходить"
+
+msgid "specifies for which commands a fold will be opened"
+msgstr "зазначає, при Ñкій команді згортка відкриєтьÑÑ"
+
+msgid "minimum number of screen lines for a fold to be closed"
+msgstr "найменша кількіÑть екранних Ñ€Ñдків щоб закрити згортку"
+
+msgid "template for comments; used to put the marker in"
+msgstr "шаблон коментарів; вживаєтьÑÑ Ñ‰Ð¾Ð± вÑтавити маркер згорток"
+
+msgid ""
+"folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n"
+"\"syntax\" or \"diff\""
+msgstr ""
+"тип згортаннÑ: «manual», «indent», «expr», «marker»,\n"
+"«syntax» або «diff»"
+
+msgid "expression used when 'foldmethod' is \"expr\""
+msgstr "вираз Ð´Ð»Ñ 'foldmethod' «expr»"
+
+msgid "used to ignore lines when 'foldmethod' is \"indent\""
+msgstr "викориÑтовуєтьÑÑ Ñ‰Ð¾Ð± ігнорувати Ñ€Ñдки, коли 'foldmethod' «indent»"
+
+msgid "markers used when 'foldmethod' is \"marker\""
+msgstr "позначки при 'foldmethod' «marker»"
+
+msgid "maximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\""
+msgstr "найбільша глибина згорток при 'foldmethod' «indent» або «syntax»"
+
+msgid "diff mode"
+msgstr "режим порівнÑннÑ"
+
+msgid "use diff mode for the current window"
+msgstr "увімкнути режим порівнÑÐ½Ð½Ñ Ñƒ цьому вікні"
+
+msgid "options for using diff mode"
+msgstr "опції у режимі порівнÑннÑ"
+
+msgid "expression used to obtain a diff file"
+msgstr "вираз Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ порівнÑннÑ"
+
+msgid "expression used to patch a file"
+msgstr "вираз Ð´Ð»Ñ Ð»Ð°Ñ‚Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ"
+
+msgid "mapping"
+msgstr "заміни"
+
+msgid "maximum depth of mapping"
+msgstr "найбільша глибина заміни клавіш"
+
+msgid "allow timing out halfway into a mapping"
+msgstr "дозволити ÑÐ¿Ð»Ð¸Ð²Ð°Ð½Ð½Ñ Ñ‡Ð°Ñу поÑеред заміни клавіш"
+
+msgid "allow timing out halfway into a key code"
+msgstr "дозволити ÑÐ¿Ð»Ð¸Ð²Ð°Ð½Ð½Ñ Ñ‡Ð°Ñу поÑеред ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ»Ð°Ð²Ñ–Ñˆ"
+
+msgid "time in msec for 'timeout'"
+msgstr "Ñ‡Ð°Ñ (мÑ) Ð´Ð»Ñ 'timeout'"
+
+msgid "time in msec for 'ttimeout'"
+msgstr "Ñ‡Ð°Ñ (мÑ) Ð´Ð»Ñ 'ttimeout'"
+
+msgid "reading and writing files"
+msgstr "Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ– Ð·Ð°Ð¿Ð¸Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð²"
+
+msgid "enable using settings from modelines when reading a file"
+msgstr ""
+"увімкнути викориÑÑ‚Ð°Ð½Ð½Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½ÑŒ із Ñ€Ñдка режиму (modeline) при чинні файлу"
+
+msgid "allow setting expression options from a modeline"
+msgstr "дозволити вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð¿Ñ†Ñ–Ð¹-виразів з Ñ€Ñдка режиму (modeline)"
+
+msgid "number of lines to check for modelines"
+msgstr "кількіÑть Ñ€Ñдків пошуку Ñ€Ñдків режиму (modeline)"
+
+msgid "binary file editing"
+msgstr "Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð²Ñ–Ð¹ÐºÐ¾Ð²Ð¸Ñ… файлів"
+
+msgid "last line in the file has an end-of-line"
+msgstr "оÑтанній Ñ€Ñдок файлу має Ñимвол ÐºÑ–Ð½Ñ†Ñ Ñ€Ñдка"
+
+msgid "last line in the file followed by CTRL-Z"
+msgstr "оÑтанній Ñ€Ñдок файлу закінчуєтьÑÑ CTRL-Z"
+
+msgid "fixes missing end-of-line at end of text file"
+msgstr "виправлÑÑ” Ñимвол ÐºÑ–Ð½Ñ†Ñ Ñ€Ñдку, Ñкого бракує наприкінці файлу"
+
+msgid "prepend a Byte Order Mark to the file"
+msgstr "додати на початку файлу Ñимвол порÑдку байтів"
+
+msgid "end-of-line format: \"dos\", \"unix\" or \"mac\""
+msgstr "формат ÐºÑ–Ð½Ñ†Ñ Ñ€Ñдка: «dos», «unix» або «mac»"
+
+msgid "list of file formats to look for when editing a file"
+msgstr "ÑпиÑок форматів файлів Ñк підказка при редагуванні файлу"
+
+msgid "writing files is allowed"
+msgstr "запиÑÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð² дозволено"
+
+msgid "write a backup file before overwriting a file"
+msgstr "запиÑати резервний копію перед перезапиÑуваннÑм файлу"
+
+msgid "keep a backup after overwriting a file"
+msgstr "зберегти резервний копію піÑÐ»Ñ Ð¿ÐµÑ€ÐµÐ·Ð°Ð¿Ð¸Ñу файлу"
+
+msgid "patterns that specify for which files a backup is not made"
+msgstr "шаблони, Ñкі зазначають, Ð´Ð»Ñ Ñких файлів не робитьÑÑ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ñƒ копію"
+
+msgid "whether to make the backup as a copy or rename the existing file"
+msgstr ""
+"Ñтворити резервну копію шлÑхом ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ Ñ‡Ð¸ Ð¿ÐµÑ€ÐµÐ¹Ð¼ÐµÐ½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ñнуючого файла"
+
+msgid "list of directories to put backup files in"
+msgstr "ÑпиÑок директорій, в Ñких розміщувати файли резервних копій"
+
+msgid "file name extension for the backup file"
+msgstr "Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Ð½Ð°Ð·Ð² файлів резервних копій"
+
+msgid "automatically write a file when leaving a modified buffer"
+msgstr "автоматично запиÑувати файл при покиданні модифікованого буфера"
+
+msgid "as 'autowrite', but works with more commands"
+msgstr "Ñк 'autowrite', але працює з більшою кількіÑтю команд"
+
+msgid "always write without asking for confirmation"
+msgstr "завжди запиÑувати без підтвердженнÑ"
+
+msgid "automatically read a file when it was modified outside of Vim"
+msgstr "автоматично перечитувати файл, коли його було модифіковано поза Vim"
+
+msgid "keep oldest version of a file; specifies file name extension"
+msgstr "зберігати найÑтарішу верÑÑ–ÑŽ файлу; задає Ñ€Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Ð½Ð°Ð·Ð²Ð¸ файлу"
+
+msgid "forcibly sync the file to disk after writing it"
+msgstr "примуÑово Ñинхронізувати файл на диÑк піÑÐ»Ñ Ð·Ð°Ð¿Ð¸Ñу"
+
+msgid "the swap file"
+msgstr "файл обміну"
+
+msgid "list of directories for the swap file"
+msgstr "ÑпиÑок директорій Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ñƒ обміну"
+
+msgid "use a swap file for this buffer"
+msgstr "викориÑтовувати файл обміну Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ буферу"
+
+msgid "number of characters typed to cause a swap file update"
+msgstr "кількіÑть набраних Ñимволів щоб Ñпричинити Ð¿Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ обміну"
+
+msgid "time in msec after which the swap file will be updated"
+msgstr "Ñ‡Ð°Ñ (мÑ), піÑÐ»Ñ Ñкого файл обміну буде поновлено"
+
+msgid "command line editing"
+msgstr "Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ñдка команд"
+
+msgid "how many command lines are remembered"
+msgstr "Ñкільки Ñ€Ñдків команд запам’Ñтати"
+
+msgid "key that triggers command-line expansion"
+msgstr "клавіша, Ñка запуÑкає Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñ€Ñдка команд"
+
+msgid "like 'wildchar' but can also be used in a mapping"
+msgstr "ніби 'wildchar', але також можна вжити у заміні клавіш"
+
+msgid "specifies how command line completion works"
+msgstr "визначає, Ñк працює Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð² Ñ€Ñдку команд"
+
+msgid "empty or \"tagfile\" to list file name of matching tags"
+msgstr "порожнє чи «tagfile» щоб отримати ÑпиÑок файлів Ð´Ð»Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð½Ð¸Ñ… міток"
+
+msgid "list of file name extensions that have a lower priority"
+msgstr "ÑпиÑок розширень назв файлів, Ñкі мають нижчий пріоритет"
+
+msgid "list of file name extensions added when searching for a file"
+msgstr "ÑпиÑок розширень назв файлів, що додаютьÑÑ Ð¿Ñ€Ð¸ пошуку файлу"
+
+msgid "list of patterns to ignore files for file name completion"
+msgstr "ÑпиÑок шаблонів ігнорованих файлів при доповненні назв файлів"
+
+msgid "ignore case when using file names"
+msgstr "не зважати на регіÑтр Ñимволів у назвах файлів"
+
+msgid "ignore case when completing file names"
+msgstr "не зважати на регіÑтр Ñимволів при доповненні назв файлів"
+
+msgid "command-line completion shows a list of matches"
+msgstr "Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ñ€Ñдка команд показує ÑпиÑок збігів"
+
+msgid "key used to open the command-line window"
+msgstr "клавіша Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–ÐºÐ½Ð° команд"
+
+msgid "height of the command-line window"
+msgstr "виÑота вікна команд"
+
+msgid "executing external commands"
+msgstr "Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… команд"
+
+msgid "name of the shell program used for external commands"
+msgstr "назва програми оболонки Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ–Ñ… команд"
+
+msgid "character(s) to enclose a shell command in"
+msgstr "Ñимвол(и), Ñкими обрамити команду оболонки"
+
+msgid "like 'shellquote' but include the redirection"
+msgstr "Ñк 'shellquote', але включає перенаправленнÑ"
+
+msgid "characters to escape when 'shellxquote' is ("
+msgstr "Ñимволи, Ñкі потрібно захиÑтити, коли 'shellxquote' ("
+
+msgid "argument for 'shell' to execute a command"
+msgstr "аргумент 'shell' щоб виконати команду"
+
+msgid "used to redirect command output to a file"
+msgstr "вживаєтьÑÑ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð¸Ð²Ð¾Ð´Ñƒ в файл"
+
+msgid "use a temp file for shell commands instead of using a pipe"
+msgstr "викориÑтовувати тимчаÑовий файл Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ оболонки заміÑть конвеєра"
+
+msgid "program used for \"=\" command"
+msgstr "програма Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ «=»"
+
+msgid "program used to format lines with \"gq\" command"
+msgstr "програма Ð´Ð»Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ñдків командою «gq»"
+
+msgid "program used for the \"K\" command"
+msgstr "програма Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ «K»"
+
+msgid "warn when using a shell command and a buffer has changes"
+msgstr "попереджувати при запуÑку команди оболонки, коли буфер має зміни"
+
+msgid "running make and jumping to errors (quickfix)"
+msgstr "Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ make Ñ– перехід до помилок (quickfix)"
+
+msgid "name of the file that contains error messages"
+msgstr "назва файлу з повідомленнÑми про помилки"
+
+msgid "list of formats for error messages"
+msgstr "ÑпиÑок форматів повідомлень про помилки"
+
+msgid "program used for the \":make\" command"
+msgstr "програма команди «:make»"
+
+msgid "string used to put the output of \":make\" in the error file"
+msgstr "Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð¸Ð²Ð¾Ð´Ñƒ програми \":make\" у файл помилок"
+
+msgid "name of the errorfile for the 'makeprg' command"
+msgstr "назва файлу помилок Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ 'makeprg' "
+
+msgid "program used for the \":grep\" command"
+msgstr "програма Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ «:grep»"
+
+msgid "list of formats for output of 'grepprg'"
+msgstr "ÑпиÑок форматів виводу програми 'grepprg'"
+
+msgid "encoding of the \":make\" and \":grep\" output"
+msgstr "ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ð²Ð¾Ð´Ñƒ «:make» Ñ– «:grep»"
+
+msgid "system specific"
+msgstr "ÑтоÑовно ÑиÑтеми"
+
+msgid "use forward slashes in file names; for Unix-like shells"
+msgstr ""
+"викориÑтовувати прÑмі коÑÑ– лінії у назвах файлів; Ð´Ð»Ñ Unix-подібних оболонок"
+
+msgid "specifies slash/backslash used for completion"
+msgstr "визначає прÑму чи зворотну коÑу вживати у доповненні"
+
+msgid "language specific"
+msgstr "ÑтоÑовно мови"
+
+msgid "specifies the characters in a file name"
+msgstr "визначає Ñимволи у назві файлу"
+
+msgid "specifies the characters in an identifier"
+msgstr "визначає Ñимволи у ідентифікаторі"
+
+msgid "specifies the characters in a keyword"
+msgstr "визначає Ñимволи у ключовому Ñлові"
+
+msgid "specifies printable characters"
+msgstr "визначає друковні Ñимволи"
+
+msgid "specifies escape characters in a string"
+msgstr "визначає escape-Ñимволи у текÑтовому Ñ€Ñдку"
+
+msgid "display the buffer right-to-left"
+msgstr "показати буфер Ñправа наліво"
+
+msgid "when to edit the command-line right-to-left"
+msgstr "коли редагувати Ñ€Ñдок команд Ñправа наліво"
+
+msgid "insert characters backwards"
+msgstr "вÑтавлÑти Ñимволи в зворотному порÑдку"
+
+msgid "allow CTRL-_ in Insert and Command-line mode to toggle 'revins'"
+msgstr "дозволити CTRL-_ у режимі Insert і Command-line перемикати 'revins'"
+
+msgid "the ASCII code for the first letter of the Hebrew alphabet"
+msgstr "код ASCII першої літери алфавіту івриту"
+
+msgid "use Hebrew keyboard mapping"
+msgstr "викориÑтовувати розкладку клавіатури Ð´Ð»Ñ Ñ–Ð²Ñ€Ð¸Ñ‚Ñƒ"
+
+msgid "use phonetic Hebrew keyboard mapping"
+msgstr "викориÑтовувати фонетичний набір Ð´Ð»Ñ Ñ–Ð²Ñ€Ð¸Ñ‚Ñƒ"
+
+msgid "prepare for editing Arabic text"
+msgstr "підготувати Ð´Ð»Ñ Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ñ€Ð°Ð±Ñького текÑту"
+
+msgid "perform shaping of Arabic characters"
+msgstr "виконувати Ñ„Ð¾Ñ€Ð¼ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ñ€Ð°Ð±Ñьких Ñимволів"
+
+msgid "terminal will perform bidi handling"
+msgstr "термінал виконуватиме обробку двоÑтороннього текÑту"
+
+msgid "name of a keyboard mapping"
+msgstr "назва розкладки клавіатури"
+
+msgid "list of characters that are translated in Normal mode"
+msgstr "ÑпиÑок транÑльованих у режимі Normal Ñимволів"
+
+msgid "apply 'langmap' to mapped characters"
+msgstr "заÑтоÑувати 'langmap' до замінюваних Ñимволів"
+
+msgid "when set never use IM; overrules following IM options"
+msgstr ""
+"коли вÑтановлено, не викориÑтовувати IM (метод введеннÑ); переÑилює подальші "
+"опції"
+
+msgid "in Insert mode: 1: use :lmap; 2: use IM; 0: neither"
+msgstr ""
+"у режимі Insert: 1: викориÑтовувати :lmap; 2: викориÑтовувати IM; 0: нічого"
+
+msgid "entering a search pattern: 1: use :lmap; 2: use IM; 0: neither"
+msgstr ""
+"Ð²Ð²ÐµÐ´ÐµÐ½Ð½Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ñƒ пошуку: 1: викориÑтовувати :lmap; 2: викориÑтовувати IM; 0: "
+"нічого"
+
+msgid "when set always use IM when starting to edit a command line"
+msgstr ""
+"коли вÑтановлено, завжди викориÑтовувати IM на початку Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð² Ñ€Ñдку "
+"команд"
+
+msgid "function to obtain IME status"
+msgstr "Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ñтану IME"
+
+msgid "function to enable/disable IME"
+msgstr "Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð½Ñ/Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ IME"
+
+msgid "multi-byte characters"
+msgstr "багатобайтні Ñимволи"
+
+msgid "character encoding used in Nvim: \"utf-8\""
+msgstr "ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ñимволів у Nvim: «utf-8»"
+
+msgid "character encoding for the current file"
+msgstr "ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ñимволів у цьому файлі"
+
+msgid "automatically detected character encodings"
+msgstr "автоматично визначати ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ñимволів"
+
+msgid "expression used for character encoding conversion"
+msgstr "вираз Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÐºÐ¾Ð´ÑƒÐ²Ð°Ð½Ð½Ñ Ñимволів"
+
+msgid "delete combining (composing) characters on their own"
+msgstr "видалÑти комбінаційні (Ñкладальні) Ñимволи окремо"
+
+msgid "maximum number of combining (composing) characters displayed"
+msgstr "найбільша кількіÑть зображуваних комбінаційних (Ñкладальних) Ñимволів"
+
+msgid "key that activates the X input method"
+msgstr "клавіша, що активує метод Ð²Ð²ÐµÐ´ÐµÐ½Ð½Ñ X"
+
+msgid "width of ambiguous width characters"
+msgstr "ширина Ñимволів неоднозначної ширини"
+
+msgid "emoji characters are full width"
+msgstr "Ñимволи емодзі повної ширини"
+
+msgid "various"
+msgstr "різне"
+
+msgid ""
+"when to use virtual editing: \"block\", \"insert\", \"all\"\n"
+"and/or \"onemore\""
+msgstr ""
+"коли викориÑтовувати віртуальне редагуваннÑ: «block», «insert», «all»\n"
+"і/або «onemore»"
+
+msgid "list of autocommand events which are to be ignored"
+msgstr "ÑпиÑок подій автокоманд, Ñкі ігнорувати"
+
+msgid "load plugin scripts when starting up"
+msgstr "завантажувати Ñкрипти плагінів при запуÑку"
+
+msgid "enable reading .vimrc/.exrc/.gvimrc in the current directory"
+msgstr "увімкнути Ð¿Ñ–Ð´Ñ‡Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ .vimrc/.exrc/.gvimrc у активній директорії"
+
+msgid "safer working with script files in the current directory"
+msgstr "безпечніша робота із файлами Ñкриптів у активній директорії"
+
+msgid "use the 'g' flag for \":substitute\""
+msgstr "вживати прапорець 'g' у «:substitute»"
+
+msgid "allow reading/writing devices"
+msgstr "дозволити читаннÑ/запиÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ñтроїв"
+
+msgid "maximum depth of function calls"
+msgstr "найбільша глибина виклику функцій"
+
+msgid "list of words that specifies what to put in a session file"
+msgstr "ÑпиÑок Ñлів, що визначає, що вÑтановлювати у файлі ÑеанÑу"
+
+msgid "list of words that specifies what to save for :mkview"
+msgstr "ÑпиÑок Ñлів, що визначає, що зберігати у :mkview"
+
+msgid "directory where to store files with :mkview"
+msgstr "директоріÑ, в Ñкій зберігати файли при :mkview"
+
+msgid "list that specifies what to write in the ShaDa file"
+msgstr "ÑпиÑок, що визначає, що запиÑувати у файлі ShaDa"
+
+msgid "what happens with a buffer when it's no longer in a window"
+msgstr "що ÑтаєтьÑÑ Ð· буфером, коли він більше не у вікні"
+
+msgid "empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer"
+msgstr "порожній, «nofile», «nowrite», «quickfix» тощо: тип буфера"
+
+msgid "whether the buffer shows up in the buffer list"
+msgstr "чи показуєтьÑÑ Ð±ÑƒÑ„ÐµÑ€ у ÑпиÑку буферів"
+
+msgid "set to \"msg\" to see all error messages"
+msgstr "вÑтановити у «msg» щоб бачити вÑÑ– Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ помилки"
+
+msgid "whether to show the signcolumn"
+msgstr "чи показувати signcolumn"
+
+msgid "name of the MzScheme dynamic library"
+msgstr "назва динамічної бібліотеки MzScheme"
+
+msgid "name of the MzScheme GC dynamic library"
+msgstr "назва динамічної бібліотеки MzScheme GC"
+
+msgid "whether to use Python 2 or 3"
+msgstr "викориÑтовувати Python 2 чи 3"
+
+msgid "E249: Window layout changed unexpectedly"
+msgstr "E249: Ð Ð¾Ð·Ð¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð²Ñ–ÐºÐ½Ð° неÑподівано змінилаÑÑ"
+
+msgid "E1156: Cannot change the argument list recursively"
+msgstr "E1156: Ðеможливо рекурÑивно змінити ÑпиÑок аргументів"
+
+msgid "E163: There is only one file to edit"
+msgstr "E163: РедагуєтьÑÑ Ð»Ð¸ÑˆÐµ один файл"
+
+msgid "E164: Cannot go before first file"
+msgstr "E164: Це вже найперший файл"
+
+msgid "E165: Cannot go beyond last file"
+msgstr "E165: Це вже оÑтанній файл"
+
+msgid "E610: No argument to delete"
+msgstr "E610: Ðемає аргументів Ð´Ð»Ñ Ð·Ð½Ð¸Ñ‰ÐµÐ½Ð½Ñ"
+
+msgid "E218: Autocommand nesting too deep"
+msgstr "E218: Забагато вкладених автокоманд"
msgid "--Deleted--"
msgstr "--Знищено--"
@@ -40,7 +1283,7 @@ msgid "E936: Cannot delete the current group"
msgstr "E936: Ðе вдалоÑÑ Ð·Ð½Ð¸Ñ‰Ð¸Ñ‚Ð¸ цю групу"
msgid "W19: Deleting augroup that is still in use"
-msgstr "W19: Ð—Ð½Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð³Ñ€ÑƒÐ¿Ð¸, Ñка вÑе ще викориÑтовуєтьÑÑ"
+msgstr "W19: ЗнищуєтьÑÑ Ð°Ð²Ñ‚Ð¾Ð³Ñ€ÑƒÐ¿Ð° вÑе ще у вжитку"
msgid ""
"\n"
@@ -56,11 +1299,9 @@ msgstr "E680: <буфер=%d>: некоректний номер буфера "
msgid "E217: Can't execute autocommands for ALL events"
msgstr "E217: Ðе можу виконувати автокоманди Ð´Ð»Ñ Ð£Ð¡Ð†Ð¥ подій"
-msgid "No matching autocommands"
-msgstr "Ðемає відповідних автокоманд"
-
-msgid "E218: autocommand nesting too deep"
-msgstr "E218: Забагато вкладених автокоманд"
+#, c-format
+msgid "No matching autocommands: %s"
+msgstr "Ðемає відповідних автокоманд: %s"
#, c-format
msgid "%s Autocommands for \"%s\""
@@ -86,24 +1327,19 @@ msgstr "E216: Ðемає такої події: %s"
msgid "E216: No such group or event: %s"
msgstr "E216: Ðемає такої групи чи події: %s"
-msgid "[Location List]"
-msgstr "[СпиÑок міÑць]"
-
-msgid "[Quickfix List]"
-msgstr "[СпиÑок виправлень]"
-
msgid "E855: Autocommands caused command to abort"
msgstr "E855: Ðвтокоманди призвели до ÑкаÑÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸"
+#, c-format
+msgid "E937: Attempt to delete a buffer that is in use: %s"
+msgstr "E937: Спроба видалити буфер, що викориÑтовуєтьÑÑ: %s"
+
msgid "E82: Cannot allocate any buffer, exiting..."
msgstr "E82: Ðемає можливоÑті розміÑтити хоч один буфер, Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸..."
msgid "E83: Cannot allocate buffer, using other one..."
msgstr "E83: Ðемає можливоÑті розміÑтити буфер, буде викориÑтано інший..."
-msgid "E937: Attempt to delete a buffer that is in use"
-msgstr "E937: Спроба Ð·Ð½Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð±ÑƒÑ„ÐµÑ€Ñƒ, Ñкий викориÑтовуєтьÑÑ"
-
msgid "E515: No buffers were unloaded"
msgstr "E515: Жоден з буферів не був вивантажений"
@@ -113,6 +1349,27 @@ msgstr "E516: Жоден з буферів не знищено"
msgid "E517: No buffers were wiped out"
msgstr "E517: Жоден з буферів не витерто"
+#, c-format
+msgid "%d buffer unloaded"
+msgid_plural "%d buffers unloaded"
+msgstr[0] "%d буфер вивантажено"
+msgstr[1] "%d буфери вивантажено"
+msgstr[2] "%d буферів вивантажено"
+
+#, c-format
+msgid "%d buffer deleted"
+msgid_plural "%d buffers deleted"
+msgstr[0] "%d буфер знищено"
+msgstr[1] "%d буфери знищено"
+msgstr[2] "%d буферів знищено"
+
+#, c-format
+msgid "%d buffer wiped out"
+msgid_plural "%d buffers wiped out"
+msgstr[0] "%d буфер Ñтерто"
+msgstr[1] "%d буфери Ñтерто"
+msgstr[2] "%d буферів Ñтерто"
+
msgid "E90: Cannot unload last buffer"
msgstr "E90: Ðе можу вивантажити оÑтанній буфер"
@@ -138,16 +1395,16 @@ msgid "E89: %s will be killed (add ! to override)"
msgstr "E89: «%s» буде вбито (! щоб не зважати)"
msgid "E948: Job still running (add ! to end the job)"
-msgstr "E948: Задача вÑе ще виконуєтьÑÑ (! щоб закінчити)"
+msgstr "E948: Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ñе ще виконуєтьÑÑ (! щоб закінчити)"
msgid "E37: No write since last change (add ! to override)"
msgstr "E37: Зміни не було запиÑано (! щоб не зважати)"
msgid "E948: Job still running"
-msgstr "E948: Задача вÑе ще виконуєтьÑÑ"
+msgstr "E948: Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ñе ще виконуєтьÑÑ"
msgid "E37: No write since last change"
-msgstr "E37: Ðе запиÑано піÑÐ»Ñ Ð¾Ñтанніх змін"
+msgstr "E37: Ðе запиÑано найновіші зміни"
msgid "W14: Warning: List of file names overflow"
msgstr "W14: Обережно: СпиÑок назв файлів переповнено"
@@ -187,6 +1444,13 @@ msgid "[readonly]"
msgstr "[лише читати]"
#, c-format
+msgid "%<PRId64> line --%d%%--"
+msgid_plural "%<PRId64> lines --%d%%--"
+msgstr[0] "%<PRId64> Ñ€Ñдок --%d%%--"
+msgstr[1] "%<PRId64> Ñ€Ñдки --%d%%--"
+msgstr[2] "%<PRId64> Ñ€Ñдків --%d%%--"
+
+#, c-format
msgid "line %<PRId64> of %<PRId64> --%d%%-- col "
msgstr "Ñ€Ñдок %<PRId64> з %<PRId64> --%d%%-- колонка "
@@ -196,12 +1460,6 @@ msgstr "[Без назви]"
msgid "help"
msgstr "допомога"
-msgid "[Help]"
-msgstr "[Допомога]"
-
-msgid "[Preview]"
-msgstr "[ПереглÑд]"
-
msgid "All"
msgstr "УÑе"
@@ -211,6 +1469,26 @@ msgstr "Знизу"
msgid "Top"
msgstr "Вгорі"
+#, c-format
+msgid "%d%%"
+msgstr "%d%%"
+
+#, c-format
+msgid " (%d of %d)"
+msgstr " (%d з %d)"
+
+#, c-format
+msgid " ((%d) of %d)"
+msgstr " ((%d) з %d)"
+
+#, c-format
+msgid " (file %d of %d)"
+msgstr " (файл %d з %d)"
+
+#, c-format
+msgid " (file (%d) of %d)"
+msgstr " (файл (%d) з %d)"
+
msgid "E382: Cannot write, 'buftype' option is set"
msgstr "E382: Ðе можу запиÑати, вказана Ð¾Ð¿Ñ†Ñ–Ñ 'buftype'"
@@ -220,6 +1498,120 @@ msgstr "[Запит]"
msgid "[Scratch]"
msgstr "[З нулÑ]"
+msgid "[Location List]"
+msgstr "[СпиÑок міÑць]"
+
+msgid "[Quickfix List]"
+msgstr "[СпиÑок виправлень]"
+
+msgid "E206: Patchmode: can't touch empty original file"
+msgstr "E206: ЛатаннÑ: не вдалоÑÑ Ñтворити оригінал"
+
+msgid "E513: Write error, conversion failed (make 'fenc' empty to override)"
+msgstr "E513: Помилка запиÑу, Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ (Ñкиньте 'fenc')"
+
+msgid "E513: Write error, conversion failed in line %"
+msgstr "E513: Помилка запиÑу, Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ðµ вдалаÑÑ Ñƒ Ñ€Ñдку %"
+
+msgid "E514: Write error (file system full?)"
+msgstr "E514: Помилка запиÑу (ÑкінчилоÑÑŒ вільне міÑце?)"
+
+#, c-format
+msgid "E676: No matching autocommands for buftype=%s buffer"
+msgstr "E676: Ðемає відповідних автокоманд Ð´Ð»Ñ Ð±ÑƒÑ„ÐµÑ€Ð° buftype=%s"
+
+msgid "WARNING: The file has been changed since reading it!!!"
+msgstr "ЗÐСТЕРЕЖЕÐÐЯ: Файл змінивÑÑ Ð· чаÑу оÑтаннього читаннÑ!!!"
+
+msgid "Do you really want to write to it"
+msgstr "Ви Ñправді хочете його перепиÑати??"
+
+msgid "E203: Autocommands deleted or unloaded buffer to be written"
+msgstr "E203: Ðвтокоманда знищила або вивантажила буфер, що мав бути запиÑаний"
+
+msgid "E204: Autocommand changed number of lines in unexpected way"
+msgstr "E204: Ðвтокоманда неÑподіваним чином змінила кількіÑть Ñ€Ñдків"
+
+msgid "is a directory"
+msgstr "каталог"
+
+msgid "is not a file or writable device"
+msgstr "Ðе придатний Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
+
+msgid "is read-only (add ! to override)"
+msgstr "лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ (! щоб не зважати)"
+
+#, c-format
+msgid "E303: Unable to create directory \"%s\" for backup file: %s"
+msgstr "E303: Ðе вдалоÑÑ Ñтворити каталог «%s» Ð´Ð»Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð³Ð¾ файлу: %s"
+
+msgid "E509: Cannot create backup file (add ! to override)"
+msgstr "E509: Ðе вдалоÑÑ Ñтворити резервну копію (! щоб не зважати)"
+
+msgid "E510: Can't make backup file (add ! to override)"
+msgstr "E510: Ðе вдалоÑÑ Ð·Ñ€Ð¾Ð±Ð¸Ñ‚Ð¸ резервну копію (! щоб не зважати)"
+
+msgid "E214: Can't find temp file for writing"
+msgstr "E214: Ðе вдалоÑÑ Ð¿Ñ–Ð´ÑˆÑƒÐºÐ°Ñ‚Ð¸ тимчаÑовий файл Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
+
+msgid "E213: Cannot convert (add ! to write without conversion)"
+msgstr "E213: Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚Ð¸ (! щоб запиÑати без конвертації)"
+
+msgid "E166: Can't open linked file for writing"
+msgstr "E166: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу зв'Ñзаний файл"
+
+#, c-format
+msgid "E212: Can't open file for writing: %s"
+msgstr "E212: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ файл Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу: %s"
+
+#, c-format
+msgid "E512: Close failed: %s"
+msgstr "E512: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸: %s"
+
+msgid " CONVERSION ERROR"
+msgstr " ПОМИЛКРКОÐВЕРТÐЦІЇ"
+
+#, c-format
+msgid " in line %<PRId64>;"
+msgstr " у Ñ€Ñдку %<PRId64>;"
+
+msgid "[NOT converted]"
+msgstr "[ÐЕ конвертовано]"
+
+msgid "[converted]"
+msgstr "[конвертовано]"
+
+msgid "[Device]"
+msgstr "[ПриÑтрій]"
+
+msgid " [a]"
+msgstr "[д]"
+
+msgid " appended"
+msgstr " допиÑаний"
+
+msgid " [w]"
+msgstr "[з]"
+
+msgid " written"
+msgstr " запиÑаний"
+
+msgid "E205: Patchmode: can't save original file"
+msgstr "E205: ЛатаннÑ: не вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ оригінал"
+
+msgid "E207: Can't delete backup file"
+msgstr "E207: Ðе вдалоÑÑ Ð·Ð½Ð¸Ñ‰Ð¸Ñ‚Ð¸ резервний файл"
+
+msgid ""
+"\n"
+"WARNING: Original file may be lost or damaged\n"
+msgstr ""
+"\n"
+"ЗÐСТЕРЕЖЕÐÐЯ: Оригінал, мабуть, втрачений чи пошкоджений\n"
+
+msgid "don't quit the editor until the file is successfully written!"
+msgstr "Ðе виходьте з редактора, доки файл не запиÑано!"
+
msgid "W10: Warning: Changing a readonly file"
msgstr "W10: ЗаÑтереженнÑ: ЗмінюєтьÑÑ Ñ„Ð°Ð¹Ð» призначений лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ"
@@ -235,18 +1627,27 @@ msgstr "Ðеможливо надіÑлати дані у закритий поÑ
msgid "Can't send raw data to rpc channel"
msgstr "Ðеможливо надіÑлати дані у канал завданнÑ"
+msgid "tagname"
+msgstr "назва теґу"
+
+msgid " kind file\n"
+msgstr " тип файлу\n"
+
+msgid "'history' option is zero"
+msgstr "ÐžÐ¿Ñ†Ñ–Ñ 'history' порожнÑ"
+
msgid "E474: Failed to convert list to msgpack string buffer"
msgstr "E474: Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚Ð¸ ÑпиÑок у текÑтовий буфер msgpack"
+msgid "E548: Digit expected"
+msgstr "E548: ОчікуєтьÑÑ Ñ†Ð¸Ñ„Ñ€Ð°"
+
msgid "E545: Missing colon"
msgstr "E545: Пропущено двокрапку"
msgid "E546: Illegal mode"
msgstr "E546: Ðеправильний режим"
-msgid "E548: digit expected"
-msgstr "E548: Потрібна цифра"
-
msgid "E549: Illegal percentage"
msgstr "E549: Ðеправильний відÑоток"
@@ -270,11 +1671,11 @@ msgid "cmd: %s"
msgstr "команда: %s"
msgid "frame is zero"
-msgstr "кадр нульовий"
+msgstr "кадр Ñтеку нульовий"
#, c-format
msgid "frame at highest level: %d"
-msgstr "кадр на найвищому рівні: %d"
+msgstr "кадр Ñтеку на найвищому рівні: %d"
#, c-format
msgid "Breakpoint in \"%s%s\" line %<PRId64>"
@@ -301,7 +1702,7 @@ msgstr "E96: Ðе можна порівнювати понад %<PRId64> буфÐ
#, c-format
msgid "Not enough memory to use internal diff for buffer \"%s\""
-msgstr "ÐедоÑтатньо пам’Ñті Ð´Ð»Ñ Ð²Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½ÑŒÐ¾Ð³Ð¾ порівнÑÐ½Ð½Ñ Ñƒ буфері \"%s\""
+msgstr "Бракує пам’Ñті Ð´Ð»Ñ Ð²Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½ÑŒÐ¾Ð³Ð¾ алгоритму порівнÑÐ½Ð½Ñ Ð´Ð»Ñ Ð±ÑƒÑ„ÐµÑ€Ð° «%s»"
msgid "E810: Cannot read or write temp files"
msgstr "E810: Ðе можна читати чи запиÑувати тимчаÑові файли"
@@ -310,7 +1711,7 @@ msgid "E97: Cannot create diffs"
msgstr "E97: Ðе вдалоÑÑ Ñтворити порівнÑннÑ"
msgid "E960: Problem creating the internal diff"
-msgstr "E960: Ðе вдалоÑÑ Ñтворити внутрішнє порівнÑннÑ"
+msgstr "E960: Ðе вдалоÑÑ Ð¿Ð¾Ñ€Ñ–Ð²Ð½Ñти внутрішнім алгоритмом"
msgid "E816: Cannot read patch output"
msgstr "E816: Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ результат patch"
@@ -354,7 +1755,8 @@ msgstr "E1215: Диграф має бути одним Ñимволом: %s"
msgid ""
"E1216: digraph_setlist() argument must be a list of lists with two items"
msgstr ""
-"E1216: аргумент digraph_setlist() має бути ÑпиÑком ÑпиÑків з двох елементів"
+"E1216: Ðргумент digraph_setlist() повинен бути ÑпиÑком ÑпиÑків із двох "
+"елементів"
msgid "E104: Escape not allowed in digraph"
msgstr "E104: У диграфах не може міÑтитиÑÑ escape"
@@ -363,7 +1765,7 @@ msgid "Custom"
msgstr "ВлаÑне"
msgid "Latin supplement"
-msgstr "Ð›Ð°Ñ‚Ð¸Ð½Ð¸Ñ†Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ"
+msgstr "Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð»Ð°Ñ‚Ð¸Ð½Ð¸Ñ†Ñ–"
msgid "Greek and Coptic"
msgstr "Грецька Ñ– коптÑька"
@@ -405,7 +1807,7 @@ msgid "Mathematical operators"
msgstr "Математичні оператори"
msgid "Technical"
-msgstr "Технічне"
+msgstr "Технічні"
msgid "Box drawing"
msgstr "ÐœÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ñмокутників"
@@ -420,19 +1822,19 @@ msgid "Symbols"
msgstr "Символи"
msgid "Dingbats"
-msgstr "Дурниці"
+msgstr "ДрукарÑькі орнаменти"
msgid "CJK symbols and punctuation"
msgstr "Символи Ñ– Ð¿ÑƒÐ½ÐºÑ‚ÑƒÐ°Ñ†Ñ–Ñ CJK"
msgid "Hiragana"
-msgstr "Хірагана"
+msgstr "Хіраґана"
msgid "Katakana"
msgstr "Катакана"
msgid "Bopomofo"
-msgstr "Бопомофо"
+msgstr "Чжуїнь"
msgid "E544: Keymap file not found"
msgstr "E544: Ðе знайдено файл розкладки"
@@ -443,139 +1845,103 @@ msgstr "E105: :loadkeymap викориÑтано не у файлі команд
msgid "E791: Empty keymap entry"
msgstr "E791: Елемент розкладки порожній"
-msgid " Keyword completion (^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð¾Ð²Ð¸Ñ… Ñлів (^N^P)"
-
-msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
-msgstr " Режим ^X (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
-
-msgid " Whole line completion (^L^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÑƒÑього Ñ€Ñдка (^L^N^P)"
-
-msgid " File name completion (^F^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð½Ð°Ð·Ð²Ð¸ файлу (^F^N^P)"
+msgid " TERMINAL"
+msgstr " ТЕРМІÐÐЛ"
-msgid " Tag completion (^]^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð· міток (^]^N^P)"
+msgid " VREPLACE"
+msgstr " ВІРТ ЗÐМІÐÐ"
-msgid " Path pattern completion (^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÑˆÐ»Ñху за зразком (^N^P)"
+msgid " REPLACE"
+msgstr " ЗÐМІÐÐ"
-msgid " Definition completion (^D^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ (^D^N^P)"
+msgid " REVERSE"
+msgstr " ÐÐВИВОРІТ"
-msgid " Dictionary completion (^K^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð·Ñ– Ñловника (^K^N^P)"
+msgid " INSERT"
+msgstr " ВСТÐВИТИ"
-msgid " Thesaurus completion (^T^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð· тезауруÑу (^T^N^P)"
+msgid " (terminal)"
+msgstr " (термінал)"
-msgid " Command-line completion (^V^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ (^V^N^P)"
+msgid " (insert)"
+msgstr " (вÑтавити)"
-msgid " User defined completion (^U^N^P)"
-msgstr " КориÑтувацьке Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ (^U^N^P)"
+msgid " (replace)"
+msgstr " (замінити)"
-msgid " Omni completion (^O^N^P)"
-msgstr " Кмітливе Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ (^O^N^P)"
+msgid " (vreplace)"
+msgstr " (вірт заміна)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Орфографічна підказка (s^N^P)"
+msgid " Arabic"
+msgstr " ÐрабÑька"
-msgid " Keyword Local completion (^N^P)"
-msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð¼Ñ–Ñцевих ключових Ñлів (^N^P)"
+msgid " (paste)"
+msgstr " (клей)"
-msgid "Hit end of paragraph"
-msgstr "ТрапивÑÑ ÐºÑ–Ð½ÐµÑ†ÑŒ параграфа"
+msgid " VISUAL"
+msgstr " ВИБІР"
-msgid "E839: Completion function changed window"
-msgstr "E839: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð¸Ð»Ð° вікно"
+msgid " VISUAL LINE"
+msgstr " ВИБІР РЯДКІВ"
-msgid "E840: Completion function deleted text"
-msgstr "E840: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð·Ð½Ð¸Ñ‰Ð¸Ð»Ð° текÑÑ‚"
+msgid " VISUAL BLOCK"
+msgstr " ВИБІР БЛОКУ"
-msgid "'dictionary' option is empty"
-msgstr "ÐžÐ¿Ñ†Ñ–Ñ 'dictionary' порожнÑ"
+msgid " SELECT"
+msgstr " ВИДІЛЕÐÐЯ"
-msgid "'thesaurus' option is empty"
-msgstr "ÐžÐ¿Ñ†Ñ–Ñ 'thesaurus' порожнÑ"
+msgid " SELECT LINE"
+msgstr " ВИДІЛЕÐÐЯ РЯДКІВ"
-#, c-format
-msgid "Scanning dictionary: %s"
-msgstr "СкануєтьÑÑ Ñловник: %s"
+msgid " SELECT BLOCK"
+msgstr " ВИДІЛЕÐÐЯ БЛОКУ"
-msgid " (insert) Scroll (^E/^Y)"
-msgstr " (вÑтавити) Прогорнути (^E/^Y)"
+msgid "recording"
+msgstr "йде запиÑ"
-msgid " (replace) Scroll (^E/^Y)"
-msgstr " (заміна) Прогорнути (^E/^Y)"
+msgid "E111: Missing ']'"
+msgstr "E111: Бракує ']'"
#, c-format
-msgid "Scanning: %s"
-msgstr "Пошук у: %s"
-
-msgid "Scanning tags."
-msgstr "Пошук Ñеред теґів."
-
-msgid "match in file"
-msgstr "збіг у файлі"
-
-msgid " Adding"
-msgstr " ДодаєтьÑÑ"
-
-msgid "-- Searching..."
-msgstr "-- Пошук..."
-
-msgid "Back at original"
-msgstr "Початковий варіант"
+msgid "E697: Missing end of List ']': %s"
+msgstr "E697: Ðемає кінцівки ÑпиÑку ']': %s"
-msgid "Word from other line"
-msgstr "Слово з іншого Ñ€Ñдка"
+msgid "E719: Cannot slice a Dictionary"
+msgstr "E719: Ðе можна зробити зріз Ñловника"
-msgid "The only match"
-msgstr "Єдиний збіг"
+msgid "E909: Cannot index a special variable"
+msgstr "E909: Ðе можна індекÑувати Ñпеціальну змінну"
-#, c-format
-msgid "match %d of %d"
-msgstr "збіг %d з %d"
+msgid "E274: No white space allowed before parenthesis"
+msgstr "E274: Перед дужкою не дозволено пробіл"
#, c-format
-msgid "match %d"
-msgstr "збіг %d"
-
-msgid "E18: Unexpected characters in :let"
-msgstr "E18: Ðеочікувані Ñимволи у :let"
-
-msgid "E111: Missing ']'"
-msgstr "E111: Бракує ']'"
-
-msgid "E719: Cannot use [:] with a Dictionary"
-msgstr "E719: Ðе можна викориÑтати [:] зі Ñловником"
+msgid "E80: Error while writing: %s"
+msgstr "E80: Помилка під Ñ‡Ð°Ñ Ð·Ð°Ð¿Ð¸Ñу: %s"
-#, c-format
-msgid "E461: Illegal variable name: %s"
-msgstr "E461: ÐеприпуÑтима назва змінної: %s"
+msgid "E695: Cannot index a Funcref"
+msgstr "E695: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð½Ðµ має індекÑації"
-msgid "E995: Cannot modify existing variable"
-msgstr "E995: Ðеможливо змінити наÑвну змінну"
+msgid "E698: Variable nested too deep for making a copy"
+msgstr "E698: Змінна вкладена занадто глибоко щоб зробити її копію"
-msgid "E274: No white space allowed before parenthesis"
-msgstr "E274: Перед дужками не має бути пробілу"
+msgid "E1098: String, List or Blob required"
+msgstr "E1098: Потрібен String, List чи Blob"
#, c-format
-msgid "E940: Cannot lock or unlock variable %s"
-msgstr "E940: Ðеможливо заблокувати чи розблокувати змінну %s"
+msgid "E1169: Expression too recursive: %s"
+msgstr "E1169: Вираз занадто рекурÑивний: %s"
#, c-format
-msgid "E80: Error while writing: %s"
-msgstr "E80: Помилка під Ñ‡Ð°Ñ Ð·Ð°Ð¿Ð¸Ñу: %s"
+msgid "E1203: Dot can only be used on a dictionary: %s"
+msgstr "E1203: Крапку можна вжити тільки із Ñловником: %s"
-msgid "E1098: String, List or Blob required"
-msgstr "E1098: Потрібен String, List чи Blob"
+msgid "E1192: Empty function name"
+msgstr "E1192: ÐŸÐ¾Ñ€Ð¾Ð¶Ð½Ñ Ð½Ð°Ð·Ð²Ð° функції"
#, c-format
-msgid "E734: Wrong variable type for %s="
-msgstr "E734: Ðеправильний тип змінної Ð´Ð»Ñ %s="
+msgid "E1250: Argument of %s must be a List, String, Dictionary or Blob"
+msgstr "E1250: Ðргумент у %s має бути List, String, Dictionary або Blob"
msgid ""
"E5700: Expression from 'spellsuggest' must yield lists with exactly two "
@@ -583,41 +1949,6 @@ msgid ""
msgstr ""
"E5700: Вираз із 'spellsuggest' має повертати ÑпиÑок із рівно двома елементами"
-msgid "E991: cannot use =<< here"
-msgstr "E991: Тут не можна викориÑтати =<<"
-
-msgid "E221: Marker cannot start with lower case letter"
-msgstr "E221: Позначка не може починатиÑÑ Ñ–Ð· малої літери"
-
-msgid "E172: Missing marker"
-msgstr "E172: Бракує позначки"
-
-#, c-format
-msgid "E990: Missing end marker '%s'"
-msgstr "E990: Бракує позначки ÐºÑ–Ð½Ñ†Ñ Â«%s»"
-
-msgid "E687: Less targets than List items"
-msgstr "E687: Цілей менше, ніж елементів ÑпиÑку"
-
-msgid "E688: More targets than List items"
-msgstr "E688: Цілей більше, ніж елементів ÑпиÑку"
-
-msgid "E452: Double ; in list of variables"
-msgstr "E452: Друга ; у ÑпиÑку змінних"
-
-#, c-format
-msgid "E738: Can't list variables for %s"
-msgstr "E738: Ðе можна перерахувати змінні у %s"
-
-msgid "E996: Cannot lock an environment variable"
-msgstr "E996: Ðеможливо заблокувати змінну оточеннÑ"
-
-msgid "E996: Cannot lock an option"
-msgstr "E996: Ðеможливо заблокувати опцію"
-
-msgid "E996: Cannot lock a register"
-msgstr "E996: Ðеможливо заблокувати регіÑтр"
-
#, c-format
msgid "E121: Undefined variable: %.*s"
msgstr "E121: Ðевизначена змінна: %.*s"
@@ -632,50 +1963,28 @@ msgid "E713: Cannot use empty key after ."
msgstr "E713: Ðеможливо вжити порожній ключ піÑÐ»Ñ ."
msgid "E709: [:] requires a List or Blob value"
-msgstr "E709: [:] вимагає List чи Blob"
-
-msgid "E972: Blob value does not have the right number of bytes"
-msgstr "E972: неправильна кількіÑть байтів у значенні Blob"
+msgstr "E709: [:] потребує List чи Blob"
msgid "E996: Cannot lock a range"
msgstr "E996: Ðеможливо заблокувати діапазон"
-msgid "E710: List value has more items than target"
-msgstr "E710: СпиÑок має більше елементів, ніж ціль"
-
-msgid "E711: List value has not enough items"
-msgstr "E711: СпиÑок має недоÑтатньо елементів"
-
msgid "E996: Cannot lock a list or dict"
msgstr "E996: Ðеможливо заблокувати ÑпиÑок чи Ñловник"
msgid "E690: Missing \"in\" after :for"
msgstr "E690: Пропущено «in» піÑÐ»Ñ :for"
-#, c-format
-msgid "E108: No such variable: \"%s\""
-msgstr "E108: Змінної немає: «%s»"
-
msgid "E109: Missing ':' after '?'"
msgstr "E109: Бракує ':' піÑÐ»Ñ '?'"
msgid "E804: Cannot use '%' with Float"
msgstr "E804: Ðе можна виконати '%' над Float"
-msgid "E973: Blob literal should have an even number of hex characters"
-msgstr "E973: Ð—Ð°Ð¿Ð¸Ñ Blob повинен мати парну кількіÑть шіÑтнадцÑткових Ñимволів"
-
msgid "E110: Missing ')'"
msgstr "E110: Пропущено ')'"
msgid "E260: Missing name after ->"
-msgstr "E260: ПіÑÐ»Ñ -> бракує імені"
-
-msgid "E695: Cannot index a Funcref"
-msgstr "E695: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð½Ðµ має індекÑації"
-
-msgid "E909: Cannot index a special variable"
-msgstr "E909: Спеціальна змінна не має індекÑації"
+msgstr "E260: ПіÑÐ»Ñ -> бракує назви"
#, c-format
msgid "E112: Option name missing: %s"
@@ -685,6 +1994,9 @@ msgstr "E112: Бракує назви опції: %s"
msgid "E113: Unknown option: %s"
msgstr "E113: Ðевідома опціÑ: %s"
+msgid "E973: Blob literal should have an even number of hex characters"
+msgstr "E973: Ð—Ð°Ð¿Ð¸Ñ Blob повинен мати парну кількіÑть шіÑтнадцÑткових Ñимволів"
+
#, c-format
msgid "E114: Missing quote: %s"
msgstr "E114: Бракує лапки: %s"
@@ -697,10 +2009,6 @@ msgstr "E115: Бракує лапки: %s"
msgid "E696: Missing comma in List: %s"
msgstr "E696: Бракує коми у ÑпиÑку: %s"
-#, c-format
-msgid "E697: Missing end of List ']': %s"
-msgstr "E697: Ðемає кінцівки ÑпиÑку ']': %s"
-
msgid "Not enough memory to set references, garbage collection aborted!"
msgstr ""
"ÐедоÑтатньо пам’Ñті Ð´Ð»Ñ Ð²ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ñилань, Ð·Ð±Ð¸Ñ€Ð°Ð½Ð½Ñ ÑÐ¼Ñ–Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð¿Ð¸Ð½ÐµÐ½Ð¾!"
@@ -724,6 +2032,9 @@ msgstr "E723: Ðемає кінцівки Ñловника '}': %s"
msgid "map() argument"
msgstr "аргумент map()"
+msgid "mapnew() argument"
+msgstr "аргумент mapnew()"
+
msgid "filter() argument"
msgstr "аргумент filter()"
@@ -731,9 +2042,6 @@ msgstr "аргумент filter()"
msgid "E700: Unknown function: %s"
msgstr "E700: Ðевідома функціÑ: %s"
-msgid "E922: expected a dict"
-msgstr "E922: очікуєтьÑÑ Ñловник"
-
msgid "E923: Second argument of function() must be a list or a dict"
msgstr "E923: Другий аргумент function() має бути ÑпиÑком чи Ñловником"
@@ -747,29 +2055,6 @@ msgstr "ВиконуєтьÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°: «%s»"
msgid "E921: Invalid callback argument"
msgstr "E921: Ðекоректний аргумент функції зворотнього виклику"
-#, c-format
-msgid "E963: setting %s to value with wrong type"
-msgstr "E963: вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ %s до Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· неправильним типом"
-
-#, c-format
-msgid "E794: Cannot set variable in the sandbox: \"%.*s\""
-msgstr "E794: Ðе можна вÑтановити змінну у піÑочниці: «%.*s»"
-
-#, c-format
-msgid "E795: Cannot delete variable %.*s"
-msgstr "E795: Ðе можна знищити змінну %.*s"
-
-#, c-format
-msgid "E704: Funcref variable name must start with a capital: %s"
-msgstr "E704: Ðазва змінної Funcref має починатиÑÑ Ð· великої літери: %s"
-
-#, c-format
-msgid "E705: Variable name conflicts with existing function: %s"
-msgstr "E705: Ðазва змінної Ñпівпадає з наÑвною функцією: %s"
-
-msgid "E698: variable nested too deep for making a copy"
-msgstr "E698: Змінна вкладена занадто глибоко щоб зробити її копію"
-
msgid ""
"\n"
"\tLast set from "
@@ -788,7 +2073,7 @@ msgid "E5009: Invalid 'runtimepath'"
msgstr "E5009: Ðекоректний 'runtimepath'"
msgid "E977: Can only compare Blob with Blob"
-msgstr "E977: Блоб можна порівнÑти тільки із блобом"
+msgstr "E977: Blob можна порівнÑти тільки з Blob"
msgid "E691: Can only compare List with List"
msgstr "E691: СпиÑок можна порівнÑти тільки зі ÑпиÑком"
@@ -854,12 +2139,6 @@ msgstr ""
"%.*s"
#, c-format
-msgid "+-%s%3ld line: "
-msgid_plural "+-%s%3ld lines: "
-msgstr[0] "+-%s%3ld Ñ€Ñдок: "
-msgstr[1] "+-%s%3ld Ñ€Ñдків: "
-
-#, c-format
msgid "E474: Expected string end: %.*s"
msgstr "E474: ОчікувавÑÑ ÐºÑ–Ð½ÐµÑ†ÑŒ Ñ€Ñдка: %.*s"
@@ -1062,20 +2341,31 @@ msgid "E5005: Unable to dump %s: container references itself in %s"
msgstr "E5005: Ðеможливо злити %s: контейнер поÑилаєтьÑÑ Ð½Ð° Ñамого Ñебе у %s"
#, c-format
-msgid "E684: list index out of range: %<PRId64>"
+msgid "E684: List index out of range: %<PRId64>"
msgstr "E684: Ð†Ð½Ð´ÐµÐºÑ ÑпиÑку поза межами: %<PRId64>"
#, c-format
msgid "E899: Argument of %s must be a List or Blob"
-msgstr "E899: Ðргумент у %s має бути ÑпиÑком чи блобом"
+msgstr "E899: Ðргумент у %s має бути List або Blob"
msgid "E957: Invalid window number"
msgstr "E957: Ðекоректний номер вікна"
#, c-format
+msgid "E706: Argument of %s must be a List, String or Dictionary"
+msgstr "E706: Ðргумент у %s має бути List, String чи Dictionary"
+
+#, c-format
+msgid "E935: Invalid submatch number: %d"
+msgstr "E935: неправильний номер групи збігу: %d"
+
+#, c-format
msgid "E998: Reduce of an empty %s with no initial value"
msgstr "E998: Ð¡ÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ€Ð¾Ð¶Ð½ÑŒÐ¾Ð³Ð¾ %s без початкового значеннÑ"
+msgid "E1132: Missing function argument"
+msgstr "E1132: Бракує аргументу функції"
+
#, c-format
msgid "Error converting the call result: %s"
msgstr "Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚Ð¸ результат виклику: %s"
@@ -1091,9 +2381,6 @@ msgstr "E158: Ðекоректна назва буфера: %s"
msgid "Invalid channel stream \"%s\""
msgstr "Ðекоректний потік Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Â«%s»"
-msgid "E785: complete() can only be used in Insert mode"
-msgstr "E785: complete() можна вживати тільки в режимі вÑтавлÑннÑ"
-
msgid "&Ok"
msgstr "&O:Гаразд"
@@ -1112,6 +2399,9 @@ msgstr "аргумент flatten()"
msgid "extend() argument"
msgstr "аргумент extend()"
+msgid "extendnew() argument"
+msgstr "аргумент extendnew()"
+
msgid "E5000: Cannot find tab number."
msgstr "E5000: Ðе можна знайти номер вкладки."
@@ -1174,10 +2464,6 @@ msgid "E5010: List item %d of the second argument is not a string"
msgstr "E5010: Елемент ÑпиÑку %d другого аргументу не текÑÑ‚"
#, c-format
-msgid "E927: Invalid action: '%s'"
-msgstr "E927: Ðеправильна діÑ: «%s»"
-
-#, c-format
msgid "E962: Invalid action: '%s'"
msgstr "E962: Ðеправильна діÑ: «%s»"
@@ -1185,18 +2471,6 @@ msgstr "E962: Ðеправильна діÑ: «%s»"
msgid "connection failed: %s"
msgstr "Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вдалоÑÑ: %s"
-msgid "sort() argument"
-msgstr "аргумент sort()"
-
-msgid "uniq() argument"
-msgstr "аргумент uniq()"
-
-msgid "E702: Sort compare function failed"
-msgstr "E702: Помилка у функції порівнÑннÑ"
-
-msgid "E882: Uniq compare function failed"
-msgstr "E882: Помилка у функції порівнÑÐ½Ð½Ñ uniq"
-
#, c-format
msgid "E6100: \"%s\" is not a valid stdpath"
msgstr "E6100: \"%s\" — некоректний stdpath"
@@ -1204,15 +2478,11 @@ msgstr "E6100: \"%s\" — некоректний stdpath"
msgid "(Invalid)"
msgstr "(Ðеможливо)"
-#, c-format
-msgid "E935: invalid submatch number: %d"
-msgstr "E935: неправильний номер групи ÑпівпадіннÑ: %d"
-
msgid "Can only call this function in an unmodified buffer"
msgstr "Цю функцію можна викликати тільки у незміненому буфері"
msgid "writefile() first argument must be a List or a Blob"
-msgstr "перший аргумент writefile() має бути List або Blob"
+msgstr "Перший аргумент writefile() повинен бути List чи Blob"
#, c-format
msgid "E5060: Unknown flag: %s"
@@ -1229,17 +2499,89 @@ msgstr "E482: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ файл %s Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ
msgid "E80: Error when closing file %s: %s"
msgstr "E80: Помилка при закритті файлу %s: %s"
+msgid "E743: Variable nested too deep for (un)lock"
+msgstr "E743: Змінна має забагато вкладень щоб бути за-/відкритою."
+
+msgid "E908: Using an invalid value as a String"
+msgstr "E908: Ðекоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¶Ð¸Ñ‚Ð¾ Ñк String"
+
+#, c-format
+msgid "E1174: String required for argument %d"
+msgstr "E1174: У аргументі %d потрібен String"
+
+#, c-format
+msgid "E1175: Non-empty string required for argument %d"
+msgstr "E1175: У аргументі %d потрібен непорожній текÑтовий Ñ€Ñдок"
+
+#, c-format
+msgid "E1206: Dictionary required for argument %d"
+msgstr "E1206: Потрібен Dictionary в аргументі %d"
+
+#, c-format
+msgid "E1210: Number required for argument %d"
+msgstr "E1210: Потрібне чиÑло у аргументі %d"
+
+#, c-format
+msgid "E1211: List required for argument %d"
+msgstr "E1211: Потрібен ÑпиÑок в аргументі %d"
+
+#, c-format
+msgid "E1212: Bool required for argument %d"
+msgstr "E1212: Потрібен Bool в аргументі %d"
+
+#, c-format
+msgid "E1219: Float or Number required for argument %d"
+msgstr "E1219: Потрібен Float або Number в аргументі %d"
+
+#, c-format
+msgid "E1220: String or Number required for argument %d"
+msgstr "E1220: Потрібен String або Number в аргументі %d"
+
+#, c-format
+msgid "E1222: String or List required for argument %d"
+msgstr "E1222: Потрібен String або List в аргументі %d"
+
#, c-format
-msgid "E5142: Failed to open file %s: %s"
-msgstr "E5142: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ файл %s: %s"
+msgid "E1226: List or Blob required for argument %d"
+msgstr "E1226: Потрібен List або Blob в аргументі %d"
#, c-format
-msgid "E5143: Failed to write to file %s: %s"
-msgstr "E5143: Ðе вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати у файл %s: %s"
+msgid "E1238: Blob required for argument %d"
+msgstr "E1238: У аргументі %d потрібен Blob"
#, c-format
-msgid "E5144: Failed to close file %s: %s"
-msgstr "E5144: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ файл %s: %s"
+msgid "E1239: Invalid value for blob: %d"
+msgstr "E1239: Ðекоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ blob: %d"
+
+#, c-format
+msgid "E1252: String, List or Blob required for argument %d"
+msgstr "E1252: String, List або Blob потрібен в аргументі %d"
+
+#, c-format
+msgid "E1256: String or function required for argument %d"
+msgstr "E1256: Потрібен текÑтовий Ñ€Ñдок або Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð² аргументі %d"
+
+#, c-format
+msgid "E1297: Non-NULL Dictionary required for argument %d"
+msgstr "E1297: Ð”Ð»Ñ Ð°Ñ€Ð³ÑƒÐ¼ÐµÐ½Ñ‚Ñƒ %d потрібен не-NULL Ñловник"
+
+msgid "E710: List value has more items than target"
+msgstr "E710: СпиÑок має більше елементів, ніж ціль"
+
+msgid "E711: List value has not enough items"
+msgstr "E711: СпиÑок має недоÑтатньо елементів"
+
+msgid "E702: Sort compare function failed"
+msgstr "E702: Помилка у функції порівнÑннÑ"
+
+msgid "E882: Uniq compare function failed"
+msgstr "E882: Помилка у функції порівнÑÐ½Ð½Ñ uniq"
+
+msgid "sort() argument"
+msgstr "аргумент sort()"
+
+msgid "uniq() argument"
+msgstr "аргумент uniq()"
msgid "E6000: Argument is not a function or function name"
msgstr "E6000: Ðргумент не Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ñ‡Ð¸ назва функції"
@@ -1248,8 +2590,8 @@ msgstr "E6000: Ðргумент не Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ñ‡Ð¸ назва функці
msgid "E737: Key already exists: %s"
msgstr "E737: Ключ вже Ñ–Ñнує: %s"
-msgid "E743: variable nested too deep for (un)lock"
-msgstr "E743: Змінна має забагато вкладень щоб бути за-/відкритою."
+msgid "E972: Blob value does not have the right number of bytes"
+msgstr "E972: Ðеправильна кількіÑть байтів у значенні Blob"
#, c-format
msgid "E741: Value is locked: %.*s"
@@ -1298,18 +2640,15 @@ msgstr "E974: Blob вжито Ñк Number"
msgid "E685: using an invalid value as a Number"
msgstr "E685: некоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¶Ð¸Ñ‚Ð¾ Ñк Number"
-msgid "E730: using List as a String"
+msgid "E730: Using a List as a String"
msgstr "E730: List вжито Ñк String"
-msgid "E731: using Dictionary as a String"
+msgid "E731: Using a Dictionary as a String"
msgstr "E731: Dictionary вжито Ñк String"
-msgid "E976: using Blob as a String"
+msgid "E976: Using a Blob as a String"
msgstr "E976: Blob вжито Ñк String"
-msgid "E908: using an invalid value as a String"
-msgstr "E908: некоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¶Ð¸Ñ‚Ð¾ Ñк String"
-
msgid "E891: Using a Funcref as a Float"
msgstr "E891: Funcref вжито Ñк Float"
@@ -1323,10 +2662,10 @@ msgid "E894: Using a Dictionary as a Float"
msgstr "E894: Dictionary вжито Ñк Float"
msgid "E362: Using a boolean value as a Float"
-msgstr "E362: ВикориÑтано логічне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñк Float"
+msgstr "E362: Логічне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¶Ð¸Ñ‚Ð¾ Ñк Float"
msgid "E907: Using a special value as a Float"
-msgstr "E907: ВикориÑтано Ñпеціальне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñк Float"
+msgstr "E907: Спеціальне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¶Ð¸Ñ‚Ð¾ Ñк Float"
msgid "E975: Using a Blob as a Float"
msgstr "E975: Blob вжито Ñк Float"
@@ -1335,6 +2674,10 @@ msgid "E808: Number or Float required"
msgstr "E808: Треба вказати Number чи Float"
#, c-format
+msgid "E117: Unknown function: %s"
+msgstr "E117: Ðевідома функціÑ: %s"
+
+#, c-format
msgid "E122: Function %s already exists, add ! to replace it"
msgstr "E122: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ %s уже Ñ–Ñнує, ! щоб замінити"
@@ -1348,6 +2691,23 @@ msgstr "E718: Треба поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° функцію"
msgid "E130: Unknown function: %s"
msgstr "E130: Ðевідома функціÑ: %s"
+msgid "E454: Function list was modified"
+msgstr "E454: СпиÑок функцій змінивÑÑ"
+
+msgid "E1058: Function nesting too deep"
+msgstr "E1058: Забагато вкладених функцій"
+
+#, c-format
+msgid "E1068: No white space allowed before '%s': %s"
+msgstr "E1068: Пробіл не дозволено перед «%s»: %s"
+
+#, c-format
+msgid "E1145: Missing heredoc end marker: %s"
+msgstr "E1145: Бракує кінцевої позначки heredoc: %s"
+
+msgid "E1300: Cannot use a partial with dictionary for :defer"
+msgstr "E1300: Ðеможливо вжити чаÑткову функцію зі Ñловником у :defer"
+
#, c-format
msgid "E125: Illegal argument: %s"
msgstr "E125: Ðедозволений аргумент: %s"
@@ -1357,7 +2717,13 @@ msgid "E853: Duplicate argument name: %s"
msgstr "E853: Ðазва аргументу повторюєтьÑÑ: %s"
msgid "E989: Non-default argument follows default argument"
-msgstr "E989: Ðргумент без домовленого Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ñ–ÑÐ»Ñ Ð°Ñ€Ð³ÑƒÐ¼ÐµÐ½Ñ‚Ñƒ з домовленим значеннÑм"
+msgstr ""
+"E989: Ðргумент без домовленого Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ñ–ÑÐ»Ñ Ð°Ñ€Ð³ÑƒÐ¼ÐµÐ½Ñ‚Ñƒ з домовленим "
+"значеннÑм"
+
+#, c-format
+msgid "E451: Expected }: %s"
+msgstr "E451: ОчікуєтьÑÑ }: %s"
#, c-format
msgid "E740: Too many arguments for function %s"
@@ -1394,20 +2760,12 @@ msgid "E699: Too many arguments"
msgstr "E699: Забагато аргументів"
#, c-format
-msgid "E117: Unknown function: %s"
-msgstr "E117: Ðевідома функціÑ: %s"
-
-#, c-format
msgid "E276: Cannot use function as a method: %s"
msgstr "E276: Ðе можна вжити функцію Ñк метод: %s"
#, c-format
msgid "E933: Function was deleted: %s"
-msgstr "E933: Функцію було видалено: %s"
-
-#, c-format
-msgid "E119: Not enough arguments for function: %s"
-msgstr "E119: Замало аргументів Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ— %s"
+msgstr "E933: Функцію видалено: %s"
#, c-format
msgid "E120: Using <SID> not in a script context: %s"
@@ -1422,11 +2780,11 @@ msgstr "E129: Ðе вказано назву функції"
#, c-format
msgid "E128: Function name must start with a capital or \"s:\": %s"
-msgstr "E128: Ðазва функції має починатиÑÑ Ð· великої літери або \"s:\": %s"
+msgstr "E128: Ðазва функції має починатиÑÑ Ð· великої літери або «s:»: %s"
#, c-format
msgid "E884: Function name cannot contain a colon: %s"
-msgstr "E884: Ðазва функції не може міÑтити двокрапку: %s"
+msgstr "E884: Ðазва функції не може мати двокрапку: %s"
#, c-format
msgid "E123: Undefined function: %s"
@@ -1441,7 +2799,7 @@ msgstr "E862: Тут не можна викориÑтати g:"
#, c-format
msgid "E932: Closure function should not be at top level: %s"
-msgstr "E932: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð·Ð°Ð¼Ð¸ÐºÐ°Ð½Ð½Ñ Ð½Ðµ може бути на верхньому рівні: %s"
+msgstr "E932: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð·Ð°Ð¼Ð¸ÐºÐ°Ð½Ð½Ñ Ð½Ðµ повинна бути на верхньому рівні: %s"
msgid "E126: Missing :endfunction"
msgstr "E126: Бракує :endfunction"
@@ -1473,6 +2831,83 @@ msgstr "Ðе вдалоÑÑ Ð·Ð½Ð¸Ñ‰Ð¸Ñ‚Ð¸ функцію %s: Вона викоÑ
msgid "E133: :return not inside a function"
msgstr "E133: :return поза межами функції"
+msgid "E18: Unexpected characters in :let"
+msgstr "E18: Ðеочікувані Ñимволи у :let"
+
+#, c-format
+msgid "E940: Cannot lock or unlock variable %s"
+msgstr "E940: Ðеможливо заблокувати чи розблокувати змінну %s"
+
+#, c-format
+msgid "E963: Setting %s to value with wrong type"
+msgstr "E963: Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· неправильним типом у %s"
+
+msgid "E991: Cannot use =<< here"
+msgstr "E991: Тут не можна заÑтоÑувати =<<"
+
+msgid "E221: Marker cannot start with lower case letter"
+msgstr "E221: Позначка не може починатиÑÑ Ñ–Ð· малої літери"
+
+msgid "E172: Missing marker"
+msgstr "E172: Бракує позначки"
+
+#, c-format
+msgid "E990: Missing end marker '%s'"
+msgstr "E990: Бракує позначки ÐºÑ–Ð½Ñ†Ñ Â«%s»"
+
+msgid "E687: Less targets than List items"
+msgstr "E687: Цілей менше, ніж елементів ÑпиÑку"
+
+msgid "E688: More targets than List items"
+msgstr "E688: Цілей більше, ніж елементів ÑпиÑку"
+
+msgid "E452: Double ; in list of variables"
+msgstr "E452: Друга ; у ÑпиÑку змінних"
+
+#, c-format
+msgid "E738: Can't list variables for %s"
+msgstr "E738: Ðе можна перерахувати змінні у %s"
+
+msgid "E996: Cannot lock an environment variable"
+msgstr "E996: Ðеможливо заблокувати змінну оточеннÑ"
+
+msgid "E996: Cannot lock an option"
+msgstr "E996: Ðеможливо заблокувати опцію"
+
+msgid "E996: Cannot lock a register"
+msgstr "E996: Ðеможливо заблокувати регіÑтр"
+
+#, c-format
+msgid "E108: No such variable: \"%s\""
+msgstr "E108: Змінної немає: «%s»"
+
+#, c-format
+msgid "E794: Cannot set variable in the sandbox: \"%.*s\""
+msgstr "E794: Ðе можна вÑтановити змінну у піÑочниці: «%.*s»"
+
+#, c-format
+msgid "E1122: Variable is locked: %*s"
+msgstr "E1122: Змінна захищена: %*s"
+
+#, c-format
+msgid "E795: Cannot delete variable %.*s"
+msgstr "E795: Ðе можна знищити змінну %.*s"
+
+#, c-format
+msgid "E704: Funcref variable name must start with a capital: %s"
+msgstr "E704: Ðазва змінної Funcref має починатиÑÑ Ð· великої літери: %s"
+
+#, c-format
+msgid "E705: Variable name conflicts with existing function: %s"
+msgstr "E705: Ðазва змінної Ñпівпадає з наÑвною функцією: %s"
+
+#, c-format
+msgid "E521: Number required: &%s = '%s'"
+msgstr "E521: Потрібно вказати Number: &%s = '%s'"
+
+msgid "E1308: Cannot resize a window in another tab page"
+msgstr "E1308: Ðеможливо змінити розмір вікна у іншій вкладці"
+
msgid "tcp address must be host:port"
msgstr "адреÑа tcp має бути вузол:порт"
@@ -1482,9 +2917,12 @@ msgstr "не вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ вузол чи порт"
msgid "connection refused"
msgstr "з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð²Ñ–Ð´Ð¼Ð¾Ð²Ð»ÐµÐ½Ð¾"
+msgid "E144: Non-numeric argument to :z"
+msgstr "E144: Ðе чиÑловий аргумент у :z"
+
#, c-format
msgid "<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"
-msgstr "<%s>%s%s %d, шіÑÑ‚ %02x, Ð²Ñ–Ñ %03o, дигр %s"
+msgstr "<%s>%s%s %d, шіÑÑ‚ %02x, Ð²Ñ–Ñ %03o, дигр %s"
#, c-format
msgid "<%s>%s%s %d, Hex %02x, Octal %03o"
@@ -1510,6 +2948,13 @@ msgid "E134: Cannot move a range of lines into itself"
msgstr "E134: Ðеможливо переміÑтити діапазон Ñ€Ñдків Ñам у Ñебе"
#, c-format
+msgid "%<PRId64> line moved"
+msgid_plural "%<PRId64> lines moved"
+msgstr[0] "%<PRId64> Ñ€Ñдок переміщено"
+msgstr[1] "%<PRId64> Ñ€Ñдки переміщено"
+msgstr[2] "%<PRId64> Ñ€Ñдків переміщено"
+
+#, c-format
msgid "E482: Can't create file %s"
msgstr "E482: Ðе вдалоÑÑ Ñтворити файл %s"
@@ -1523,6 +2968,10 @@ msgstr "E135: Ðвтокоманди *Filter* не повинні змінюва
msgid "[No write since last change]\n"
msgstr "[Зміни не запиÑано]\n"
+#, c-format
+msgid "E503: \"%s\" is not a file or writable device"
+msgstr "E503: «%s» не файл або не приÑтрій Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
+
msgid "Write partial file?"
msgstr "ЗапиÑати чаÑтину файлу?"
@@ -1574,9 +3023,6 @@ msgstr "E505: «%s» тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ (! щоб не зважÐ
msgid "E143: Autocommands unexpectedly deleted new buffer %s"
msgstr "E143: Ðвтокоманди неÑподівано знищили новий буфер %s"
-msgid "E144: non-numeric argument to :z"
-msgstr "E144: нечиÑловий аргумент Ð´Ð»Ñ :z"
-
msgid "E146: Regular expressions can't be delimited by letters"
msgstr "E146: РегулÑрні вирази не можна розділÑти літерами"
@@ -1587,64 +3033,54 @@ msgstr "Замінити на %s (y/n/a/q/l/^E/^Y)?"
msgid "(Interrupted) "
msgstr "(Перервано) "
-msgid "E147: Cannot do :global recursive with a range"
-msgstr "E147: :global не можна рекурÑивно з діапазоном"
-
-msgid "E148: Regular expression missing from global"
-msgstr "E148: У global бракує зразка"
-
-#, c-format
-msgid "Pattern found in every line: %s"
-msgstr "Зразок знайдено у кожному Ñ€Ñдку: %s"
-
-#, c-format
-msgid "Pattern not found: %s"
-msgstr "Зразок не знайдено: %s"
-
-msgid "E478: Don't panic!"
-msgstr "E478: Без паніки!"
-
-#, c-format
-msgid "E661: Sorry, no '%s' help for %s"
-msgstr "E661: Вибачте, немає допомоги '%s' Ð´Ð»Ñ %s"
-
#, c-format
-msgid "E149: Sorry, no help for %s"
-msgstr "E149: Вибачте, немає допомоги Ð´Ð»Ñ %s"
+msgid "%<PRId64> match on %<PRId64> line"
+msgid_plural "%<PRId64> matches on %<PRId64> line"
+msgstr[0] "%<PRId64> збіг у %<PRId64> Ñ€Ñд."
+msgstr[1] "%<PRId64> збіги у %<PRId64> Ñ€Ñд."
+msgstr[2] "%<PRId64> збігів у %<PRId64> Ñ€Ñд."
#, c-format
-msgid "Sorry, help file \"%s\" not found"
-msgstr "Вибачте, файл допомоги «%s» не знайдено"
+msgid "%<PRId64> substitution on %<PRId64> line"
+msgid_plural "%<PRId64> substitutions on %<PRId64> line"
+msgstr[0] "%<PRId64> заміна у %<PRId64> Ñ€Ñд."
+msgstr[1] "%<PRId64> заміни у %<PRId64> Ñ€Ñд."
+msgstr[2] "%<PRId64> замін у %<PRId64> Ñ€Ñд."
#, c-format
-msgid "E151: No match: %s"
-msgstr "E151: Жодного збігу: %s"
+msgid "%<PRId64> match on %<PRId64> lines"
+msgid_plural "%<PRId64> matches on %<PRId64> lines"
+msgstr[0] "%<PRId64> збіг у %<PRId64> Ñ€Ñд."
+msgstr[1] "%<PRId64> збіги у %<PRId64> Ñ€Ñд."
+msgstr[2] "%<PRId64> збігів у %<PRId64> Ñ€Ñд."
#, c-format
-msgid "E152: Cannot open %s for writing"
-msgstr "E152: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ %s Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
+msgid "%<PRId64> substitution on %<PRId64> lines"
+msgid_plural "%<PRId64> substitutions on %<PRId64> lines"
+msgstr[0] "%<PRId64> заміна у %<PRId64> Ñ€Ñд."
+msgstr[1] "%<PRId64> заміни у %<PRId64> Ñ€Ñд."
+msgstr[2] "%<PRId64> замін у %<PRId64> Ñ€Ñд."
-#, c-format
-msgid "E153: Unable to open %s for reading"
-msgstr "E153: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ %s Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ"
+msgid "E147: Cannot do :global recursive with a range"
+msgstr "E147: :global не можна заÑтоÑовувати рекурÑивно з діапазоном"
-#, c-format
-msgid "E670: Mix of help file encodings within a language: %s"
-msgstr "E670: Мішанина кодувань файлу допомоги Ð´Ð»Ñ Ð¼Ð¾Ð²Ð¸ %s"
+msgid "E148: Regular expression missing from global"
+msgstr "E148: У global бракує зразка"
#, c-format
-msgid "E154: Duplicate tag \"%s\" in file %s/%s"
-msgstr "E154: ÐŸÐ¾Ð²Ñ‚Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚ÐºÐ¸ «%s» у файлі %s/%s"
+msgid "Pattern found in every line: %s"
+msgstr "Зразок знайдено у кожному Ñ€Ñдку: %s"
#, c-format
-msgid "E150: Not a directory: %s"
-msgstr "E150: Ðе Ñ” каталогом: %s"
+msgid "Pattern not found: %s"
+msgstr "Зразок не знайдено: %s"
msgid "No old files"
msgstr "Жодного Ñтарого файлу"
-msgid "E750: First use \":profile start {fname}\""
-msgstr "E750: Спочатку зробіть «:profile start {файл}»"
+#, c-format
+msgid "E666: Compiler not supported: %s"
+msgstr "E666: КомпілÑтор не підтримуєтьÑÑ: %s"
#, c-format
msgid "Save changes to \"%s\"?"
@@ -1656,7 +3092,7 @@ msgstr "Закрити «%s»?"
#, c-format
msgid "E947: Job still running in buffer \"%s\""
-msgstr "E947: Задача вÑе ще запущена у буфері «%s»"
+msgstr "E947: Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ ще виконуєтьÑÑ Ñƒ буфері «%s»"
#, c-format
msgid "E162: No write since last change for buffer \"%s\""
@@ -1666,102 +3102,35 @@ msgid "Warning: Entered other buffer unexpectedly (check autocommands)"
msgstr ""
"Обережно: ÐеÑподівано опинилиÑÑ Ñƒ іншому буфері (перевірте автокоманди)"
-msgid "E163: There is only one file to edit"
-msgstr "E163: РедагуєтьÑÑ Ð»Ð¸ÑˆÐµ один файл"
-
-msgid "E164: Cannot go before first file"
-msgstr "E164: Це вже найперший файл"
-
-msgid "E165: Cannot go beyond last file"
-msgstr "E165: Це вже оÑтанній файл"
-
-msgid "E610: No argument to delete"
-msgstr "E610: Ðемає аргументів Ð´Ð»Ñ Ð·Ð½Ð¸Ñ‰ÐµÐ½Ð½Ñ"
-
-#, c-format
-msgid "E666: compiler not supported: %s"
-msgstr "E666: КомпілÑтор не підтримуєтьÑÑ: %s"
-
-#, c-format
-msgid "Cannot source a directory: \"%s\""
-msgstr "Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ каталог: «%s»"
-
-#, c-format
-msgid "could not source \"%s\""
-msgstr "Ðе вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ «%s»"
-
-#, c-format
-msgid "line %<PRId64>: could not source \"%s\""
-msgstr "Ñ€Ñдок %<PRId64>: не вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ «%s»"
-
-#, c-format
-msgid "sourcing \"%s\""
-msgstr "виконуєтьÑÑ Â«%s»"
-
-#, c-format
-msgid "line %<PRId64>: sourcing \"%s\""
-msgstr "Ñ€Ñдок %<PRId64>: виконуєтьÑÑ Â«%s»"
-
-#, c-format
-msgid "finished sourcing %s"
-msgstr "закінчено Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ %s"
-
-msgid "modeline"
-msgstr "modeline"
-
-msgid "--cmd argument"
-msgstr "--cmd аргумент"
-
-msgid "-c argument"
-msgstr "-c аргумент"
-
-msgid "environment variable"
-msgstr "змінна оточеннÑ"
-
-msgid "error handler"
-msgstr "обробник помилки"
-
-msgid "changed window size"
-msgstr "змінено розмір вікна"
-
-msgid "Lua"
-msgstr "Lua"
-
-#, c-format
-msgid "API client (channel id %<PRIu64>)"
-msgstr "Клієнт API (канал «%<PRIu64>»)"
+msgid "E464: Ambiguous use of user-defined command"
+msgstr "E464: Ðеоднозначний вжиток команди кориÑтувача"
-msgid "anonymous :source"
-msgstr "анонімний :source"
+msgid "E489: No call stack to substitute for \"<stack>\""
+msgstr "E489: Ðемає Ñтеку викликів Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<stack>»"
-#, c-format
-msgid "anonymous :source (script id %d)"
-msgstr "анонімний :source (ід. Ñкрипта %d)"
+msgid "E492: Not an editor command"
+msgstr "E492: Це не команда редактора"
-msgid "W15: Warning: Wrong line separator, ^M may be missing"
-msgstr "W15: ЗаÑтереженнÑ: Ðеправильний роздільник Ñ€Ñдків, можливо, бракує ^M"
+msgid "E495: No autocommand file name to substitute for \"<afile>\""
+msgstr "E495: Ðемає назви файлу автокоманди Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<afile>»"
-msgid "E167: :scriptencoding used outside of a sourced file"
-msgstr "E167: :scriptencoding викориÑтано поза виконуваним файлом"
+msgid "E496: No autocommand buffer number to substitute for \"<abuf>\""
+msgstr "E496: Ðемає номера буфера автокоманди Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<abuf>»"
-msgid "E168: :finish used outside of a sourced file"
-msgstr "E168: :finish викориÑтано поза виконуваним файлом"
+msgid "E497: No autocommand match name to substitute for \"<amatch>\""
+msgstr "E497: Ðемає назви збігу автокоманди Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<amatch>»"
-#, c-format
-msgid "Current %slanguage: \"%s\""
-msgstr "Мова (%s): «%s»"
+msgid "E498: No :source file name to substitute for \"<sfile>\""
+msgstr "E498: Ðемає назви файлу :source Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<sfile>»"
-#, c-format
-msgid "E197: Cannot set language to \"%s\""
-msgstr "E197: Ðе вдалоÑÑ Ð²Ñтановити мову «%s»"
+msgid "E842: No line number to use for \"<slnum>\""
+msgstr "E842: Ðемає номера Ñ€Ñдка, щоб викориÑтати з «<sfile>»"
-#, c-format
-msgid "E184: No such user-defined command: %s"
-msgstr "E184: Команду кориÑтувача не знайдено: %s"
+msgid "E961: No line number to use for \"<sflnum>\""
+msgstr "E961: Ðемає номера Ñ€Ñдка, щоб викориÑтати з «<sflnum>»"
-#, c-format
-msgid "E1237: No such user-defined command in current buffer: %s"
-msgstr "E1237: Ðемає такої команди кориÑтувача у цьому буфері: %s"
+msgid "E1274: No script file name to substitute for \"<script>\""
+msgstr "E1274: Ðемає назви файлу Ñкрипту Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<script>»"
msgid "Entering Ex mode. Type \"visual\" to go to Normal mode."
msgstr "Режим Ex. Ð”Ð»Ñ Ð¿Ð¾Ð²ÐµÑ€Ð½ÐµÐ½Ð½Ñ Ð´Ð¾ нормального режиму виконайте «visual»"
@@ -1776,24 +3145,21 @@ msgstr "ВиконуєтьÑÑ: %s"
msgid "line %"
msgstr "Ñ€Ñдок %"
-msgid "E169: Command too recursive"
-msgstr "E169: Команда занадто рекурÑивна"
-
-#, c-format
-msgid "E605: Exception not caught: %s"
-msgstr "E605: ВинÑткова ÑÐ¸Ñ‚ÑƒÐ°Ñ†Ñ–Ñ Ð½Ðµ оброблена: %s"
-
msgid "End of sourced file"
msgstr "Кінець виконуваного файлу"
msgid "End of function"
msgstr "Кінець функції"
-msgid "E464: Ambiguous use of user-defined command"
-msgstr "E464: Ðеоднозначний вжиток команди кориÑтувача"
+#, c-format
+msgid "E605: Exception not caught: %s"
+msgstr "E605: ВинÑткова ÑÐ¸Ñ‚ÑƒÐ°Ñ†Ñ–Ñ Ð½Ðµ оброблена: %s"
-msgid "E492: Not an editor command"
-msgstr "E492: Це не команда редактора"
+msgid ""
+"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"
+msgstr ""
+"Ð’ÐУТРІШÐЄ: Ðе можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи "
+"ADDR_QUICKFIX"
msgid "E493: Backwards range given"
msgstr "E493: Інтервал задано навиворіт"
@@ -1804,12 +3170,6 @@ msgstr "Інтервал задано навиворіт, щоб помінÑÑ‚Ð
msgid "E494: Use w or w>>"
msgstr "E494: Спробуйте w або w>>"
-msgid ""
-"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"
-msgstr ""
-"Ð’ÐУТРІШÐЄ: Ðе можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи "
-"ADDR_QUICKFIX"
-
msgid "E943: Command table needs to be updated, run 'make'"
msgstr "E943: Потрібно поновити таблицю команд, запуÑтіть 'make'"
@@ -1817,67 +3177,18 @@ msgid "E319: The command is not available in this version"
msgstr "E319: Вибачте, цієї команди немає у цій верÑÑ–Ñ—"
#, c-format
-msgid "E174: Command already exists: add ! to replace it: %s"
-msgstr "E174: Команда вже Ñ–Ñнує, ! щоб замінити Ñ—Ñ—: %s"
-
-msgid ""
-"\n"
-" Name Args Address Complete Definition"
-msgstr ""
-"\n"
-" Ðазва Ðрг. ÐдреÑа Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð’Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ"
-
-msgid "No user-defined commands found"
-msgstr "Ðе знайдено команд кориÑтувача"
-
-msgid "E175: No attribute specified"
-msgstr "E175: Ðе вказано атрибутів"
-
-msgid "E176: Invalid number of arguments"
-msgstr "E176: Ðеправильна кількіÑть аргументів"
-
-msgid "E177: Count cannot be specified twice"
-msgstr "E177: Лічильник не може бути вказано двічі"
-
-msgid "E178: Invalid default value for count"
-msgstr "E178: Ðеправильне початкове Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð»Ñ–Ñ‡Ð¸Ð»ÑŒÐ½Ð¸ÐºÐ°"
-
-msgid "E179: argument required for -complete"
-msgstr "E179: Ð´Ð»Ñ -complete потрібний аргумент"
-
-msgid "E179: argument required for -addr"
-msgstr "E179: Ð´Ð»Ñ -addr потрібний аргумент"
+msgid "%d more file to edit. Quit anyway?"
+msgid_plural "%d more files to edit. Quit anyway?"
+msgstr[0] "Ще редагувати %d файл. Вийти вÑе одно?"
+msgstr[1] "Ще редагувати %d файли. Вийти вÑе одно?"
+msgstr[2] "Ще редагувати %d файлів. Вийти вÑе одно?"
#, c-format
-msgid "E181: Invalid attribute: %s"
-msgstr "E181: Ðеправильний атрибут: %s"
-
-msgid "E1208: -complete used without -nargs"
-msgstr "E1208: -complete вжито без without -nargs"
-
-msgid "E182: Invalid command name"
-msgstr "E182: Ðеправильна назва команди"
-
-msgid "E183: User defined commands must start with an uppercase letter"
-msgstr "E183: Команди кориÑтувача повинні починатиÑÑ Ð· великої літери"
-
-msgid "E841: Reserved name, cannot be used for user defined command"
-msgstr ""
-"E841: Зарезервована назва, не можна викориÑтати Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувацької команди"
-
-#, c-format
-msgid "E180: Invalid address type value: %s"
-msgstr "E180: Ðеправильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‚Ð¸Ð¿Ñƒ адреÑи: %s"
-
-#, c-format
-msgid "E180: Invalid complete value: %s"
-msgstr "E180: Ðеправильне доповненнÑ: %s"
-
-msgid "E468: Completion argument only allowed for custom completion"
-msgstr "E468: Ðргумент дозволений тільки Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувацького доповненнÑ"
-
-msgid "E467: Custom completion requires a function argument"
-msgstr "E467: КориÑтувацьке Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð²Ð¸Ð¼Ð°Ð³Ð°Ñ” аргумент-функцію"
+msgid "E173: %<PRId64> more file to edit"
+msgid_plural "E173: %<PRId64> more files to edit"
+msgstr[0] "E173: Ще редагувати %<PRId64> файл"
+msgstr[1] "E173: Ще редагувати %<PRId64> файли"
+msgstr[2] "E173: Ще редагувати %<PRId64> файлів"
#, c-format
msgid "E185: Cannot find color scheme '%s'"
@@ -1928,24 +3239,6 @@ msgstr "E192: Забагато вкладених :normal"
msgid "E194: No alternate file name to substitute for '#'"
msgstr "E194: Ðемає назви вторинного файлу Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ '#'"
-msgid "E495: no autocommand file name to substitute for \"<afile>\""
-msgstr "E495: Ðемає назви файлу автокоманди Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<afile>»"
-
-msgid "E496: no autocommand buffer number to substitute for \"<abuf>\""
-msgstr "E496: Ðемає номера буфера автокоманди Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<abuf>»"
-
-msgid "E497: no autocommand match name to substitute for \"<amatch>\""
-msgstr "E497: Ðемає назви збігу автокоманди Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<amatch>»"
-
-msgid "E498: no :source file name to substitute for \"<sfile>\""
-msgstr "E498: Ðемає назви файлу :source Ð´Ð»Ñ Ð·Ð°Ð¼Ñ–Ð½Ð¸ «<sfile>»"
-
-msgid "E842: no line number to use for \"<slnum>\""
-msgstr "E842: немає номера Ñ€Ñдка, щоб викориÑтати з «<sfile>»"
-
-msgid "E961: no line number to use for \"<sflnum>\""
-msgstr "E961: немає номера Ñ€Ñдка, щоб викориÑтати з «<sflnum>»"
-
#, no-c-format
msgid "E499: Empty file name for '%' or '#', only works with \":p:h\""
msgstr "E499: Ðазва файлу Ð´Ð»Ñ '%' чи '#' порожнÑ, працює лише з «:p:h»"
@@ -1956,6 +3249,12 @@ msgstr "E500: Результат — порожній Ñ€Ñдок"
msgid "Untitled"
msgstr "Ðеназваний"
+msgid "E583: Multiple :else"
+msgstr "E583: Ðе одне :else"
+
+msgid "E607: Multiple :finally"
+msgstr "E607: Ðе одне :finally"
+
msgid "E608: Cannot :throw exceptions with 'Vim' prefix"
msgstr "E608: Ðе можна викидати (:throw) винÑтки з префікÑом 'Vim'"
@@ -1964,14 +3263,14 @@ msgid "Exception thrown: %s"
msgstr "ВинÑткова ÑитуаціÑ: %s"
#, c-format
-msgid "Exception finished: %s"
-msgstr "ВинÑток закінчено: %s"
-
-#, c-format
msgid "Exception discarded: %s"
msgstr "ВинÑток Ñкинуто: %s"
#, c-format
+msgid "Exception finished: %s"
+msgstr "ВинÑток закінчено: %s"
+
+#, c-format
msgid "%s, line %<PRId64>"
msgstr "%s, Ñ€Ñдок %<PRId64>"
@@ -2015,9 +3314,6 @@ msgstr "E581: :else без :if"
msgid "E582: :elseif without :if"
msgstr "E582: :elseif без :if"
-msgid "E583: multiple :else"
-msgstr "E583: Ðе одне :else"
-
msgid "E584: :elseif after :else"
msgstr "E584: :elseif піÑÐ»Ñ :else"
@@ -2048,18 +3344,9 @@ msgstr "E604: :catch піÑÐ»Ñ :finally"
msgid "E606: :finally without :try"
msgstr "E606: :finally без :try"
-msgid "E607: multiple :finally"
-msgstr "E607: Ðе одне :finally"
-
msgid "E602: :endtry without :try"
msgstr "E602: :entry без :try"
-msgid "E193: :endfunction not inside a function"
-msgstr "E193: :endfunction поза межами функції"
-
-msgid "E788: Not allowed to edit another buffer now"
-msgstr "E788: Зараз не можна редагувати інший буфер"
-
msgid "E811: Not allowed to change buffer information now"
msgstr "E811: Зараз не можна змінювати інформацію буфера"
@@ -2094,23 +3381,14 @@ msgstr "E5404: Кінець шматка %i %"
msgid "E5406: Chunk %i end %"
msgstr "E5406: Кінець шматка %i %"
-msgid "tagname"
-msgstr "назва теґу"
-
-msgid " kind file\n"
-msgstr " тип файлу\n"
-
-msgid "'history' option is zero"
-msgstr "ÐžÐ¿Ñ†Ñ–Ñ 'history' порожнÑ"
-
msgid "[Command Line]"
msgstr "[РÑдок Команд]"
msgid "E199: Active window or buffer deleted"
msgstr "E199: Ðктивне вікно або буфер було знищено"
-msgid "E854: path too long for completion"
-msgstr "E854: шлÑÑ… занадто довгий Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ"
+msgid "E854: Path too long for completion"
+msgstr "E854: ШлÑÑ… задовгий Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ"
#, c-format
msgid ""
@@ -2139,9 +3417,6 @@ msgstr "E347: У шлÑху пошуку більше немає файлів «
msgid "E812: Autocommands changed buffer or buffer name"
msgstr "E812: Ðвтокоманди змінили буфер чи його назву"
-msgid "is a directory"
-msgstr "каталог"
-
msgid "Illegal file name"
msgstr "Ðедозволена назва файлу"
@@ -2170,7 +3445,7 @@ msgid "[fifo]"
msgstr "[канал]"
msgid "[socket]"
-msgstr "[Ñокет]"
+msgstr "[розетка]"
msgid "[character special]"
msgstr "[Ñпец. Ñимвольний]"
@@ -2181,12 +3456,6 @@ msgstr "[Бракує CR]"
msgid "[long lines split]"
msgstr "[довгі Ñ€Ñдки розбито]"
-msgid "[NOT converted]"
-msgstr "[ÐЕ конвертовано]"
-
-msgid "[converted]"
-msgstr "[конвертовано]"
-
#, c-format
msgid "[CONVERSION ERROR in line %<PRId64>]"
msgstr "[ПОМИЛКРКОÐВЕРТÐЦІЇ у Ñ€Ñдку %<PRId64>]"
@@ -2213,101 +3482,6 @@ msgstr "[Ðовий файл]"
msgid "[New]"
msgstr "[Ðовий]"
-msgid "E676: No matching autocommands for acwrite buffer"
-msgstr "E676: Ðемає відповідних автокоманд"
-
-msgid "E203: Autocommands deleted or unloaded buffer to be written"
-msgstr "E203: Ðвтокоманда знищила або вивантажила буфер, що мав бути запиÑаний"
-
-msgid "E204: Autocommand changed number of lines in unexpected way"
-msgstr "E204: Ðвтокоманда неÑподіваним чином змінила кількіÑть Ñ€Ñдків"
-
-msgid "is not a file or writable device"
-msgstr "Ðе придатний Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
-
-msgid "is read-only (add ! to override)"
-msgstr "лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ (! щоб не зважати)"
-
-#, c-format
-msgid "E303: Unable to create directory \"%s\" for backup file: %s"
-msgstr "E303: Ðе вдалоÑÑ Ñтворити каталог «%s» Ð´Ð»Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð³Ð¾ файлу: %s"
-
-msgid "E506: Can't write to backup file (add ! to override)"
-msgstr "E506: Ðе вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати резервний файл (! щоб не зважати)"
-
-msgid "E509: Cannot create backup file (add ! to override)"
-msgstr "E509: Ðе вдалоÑÑ Ñтворити резервну копію (! щоб не зважати)"
-
-msgid "E510: Can't make backup file (add ! to override)"
-msgstr "E510: Ðе вдалоÑÑ Ð·Ñ€Ð¾Ð±Ð¸Ñ‚Ð¸ резервну копію (! щоб не зважати)"
-
-msgid "E214: Can't find temp file for writing"
-msgstr "E214: Ðе вдалоÑÑ Ð¿Ñ–Ð´ÑˆÑƒÐºÐ°Ñ‚Ð¸ тимчаÑовий файл Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
-
-msgid "E213: Cannot convert (add ! to write without conversion)"
-msgstr "E213: Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚Ð¸ (! щоб запиÑати без конвертації)"
-
-msgid "E166: Can't open linked file for writing"
-msgstr "E166: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу зв'Ñзаний файл"
-
-#, c-format
-msgid "E212: Can't open file for writing: %s"
-msgstr "E212: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ файл Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу: %s"
-
-#, c-format
-msgid "E512: Close failed: %s"
-msgstr "E512: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸: %s"
-
-msgid "E513: write error, conversion failed (make 'fenc' empty to override)"
-msgstr "E513: Помилка запиÑу, ÐºÐ¾Ð½Ð²ÐµÑ€Ñ‚Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ (Ñкиньте 'fenc')"
-
-msgid "E513: write error, conversion failed in line %"
-msgstr "E513: Помилка запиÑу, Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ðµ вдалаÑÑ Ñƒ Ñ€Ñдку %"
-
-msgid "E514: write error (file system full?)"
-msgstr "E514: Помилка запиÑу (ÑкінчилоÑÑŒ вільне міÑце?)"
-
-msgid " CONVERSION ERROR"
-msgstr " ПОМИЛКРКОÐВЕРТÐЦІЇ"
-
-#, c-format
-msgid " in line %<PRId64>;"
-msgstr " у Ñ€Ñдку %<PRId64>;"
-
-msgid "[Device]"
-msgstr "[ПриÑтрій]"
-
-msgid " [a]"
-msgstr "[д]"
-
-msgid " appended"
-msgstr " допиÑаний"
-
-msgid " [w]"
-msgstr "[з]"
-
-msgid " written"
-msgstr " запиÑаний"
-
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: ЛатаннÑ: не вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ оригінал"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: ЛатаннÑ: не вдалоÑÑ Ñтворити оригінал"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: Ðе вдалоÑÑ Ð·Ð½Ð¸Ñ‰Ð¸Ñ‚Ð¸ резервний файл"
-
-msgid ""
-"\n"
-"WARNING: Original file may be lost or damaged\n"
-msgstr ""
-"\n"
-"ЗÐСТЕРЕЖЕÐÐЯ: Оригінал, мабуть, втрачений чи пошкоджений\n"
-
-msgid "don't quit the editor until the file is successfully written!"
-msgstr "Ðе виходьте з редактора, доки файл не запиÑано!"
-
msgid "[dos format]"
msgstr "[формат dos]"
@@ -2326,18 +3500,26 @@ msgstr "[формат unix]"
msgid "[unix]"
msgstr "[unix]"
+#, c-format
+msgid "%<PRId64> line, "
+msgid_plural "%<PRId64> lines, "
+msgstr[0] "%<PRId64> Ñ€Ñдок, "
+msgstr[1] "%<PRId64> Ñ€Ñдки, "
+msgstr[2] "%<PRId64> Ñ€Ñдків, "
+
+#, c-format
+msgid "%<PRId64> byte"
+msgid_plural "%<PRId64> bytes"
+msgstr[0] "%<PRId64> байт"
+msgstr[1] "%<PRId64> байти"
+msgstr[2] "%<PRId64> байтів"
+
msgid "[Incomplete last line]"
msgstr "[Ðеповний оÑтанній Ñ€Ñдок]"
msgid "[noeol]"
msgstr "[noeol]"
-msgid "WARNING: The file has been changed since reading it!!!"
-msgstr "ЗÐСТЕРЕЖЕÐÐЯ: Файл змінивÑÑ Ð· чаÑу оÑтаннього читаннÑ!!!"
-
-msgid "Do you really want to write to it"
-msgstr "Ви Ñправді хочете його перепиÑати??"
-
#, c-format
msgid "E208: Error writing to \"%s\""
msgstr "E208: Помилка запиÑу у «%s»"
@@ -2419,36 +3601,31 @@ msgstr "E350: Ðе вдалоÑÑ Ñтворити згортку методом
msgid "E351: Cannot delete fold with current 'foldmethod'"
msgstr "E351: Ðе вдалоÑÑ Ð·Ð½Ð¸Ñ‰Ð¸Ñ‚Ð¸ згортку методом 'foldmethod'"
-msgid "E222: Add to read buffer"
-msgstr "E222: Додати до буфера читаннÑ"
-
-msgid "E223: recursive mapping"
-msgstr "E223: Заміна клавіш рекурÑивна"
-
-#, c-format
-msgid "E224: global abbreviation already exists for %s"
-msgstr "E224: Загальне ÑÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ %s вже Ñ–Ñнує"
-
#, c-format
-msgid "E225: global mapping already exists for %s"
-msgstr "E225: Глобальна заміна клавіш Ð´Ð»Ñ %s вже Ñ–Ñнує"
+msgid "+--%3ld line folded"
+msgid_plural "+--%3ld lines folded "
+msgstr[0] "+--%3ld Ñ€Ñдок згорнуто "
+msgstr[1] "+--%3ld Ñ€Ñдки згорнуто "
+msgstr[2] "+--%3ld Ñ€Ñдків згорнуто "
#, c-format
-msgid "E226: abbreviation already exists for %s"
-msgstr "E226: Вже Ñ” ÑÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ %s"
+msgid "+-%s%3ld line: "
+msgid_plural "+-%s%3ld lines: "
+msgstr[0] "+-%s%3ld Ñ€Ñдок: "
+msgstr[1] "+-%s%3ld Ñ€Ñдки: "
+msgstr[2] "+-%s%3ld Ñ€Ñдків: "
-#, c-format
-msgid "E227: mapping already exists for %s"
-msgstr "E227: Вже Ñ” заміна клавіш Ð´Ð»Ñ %s"
+msgid "E223: Recursive mapping"
+msgstr "E223: Заміна клавіш рекурÑивна"
-msgid "No abbreviation found"
-msgstr "Ð¡ÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдено"
+msgid "E1255: <Cmd> mapping must end with <CR>"
+msgstr "E1255: Заміна клавіш <Cmd> має закінчуватиÑÑ <CR>"
-msgid "No mapping found"
-msgstr "Заміни клавіш не знайдено"
+msgid "E1136: <Cmd> mapping must end with <CR> before second <Cmd>"
+msgstr "E1136: Заміна клавіш <Cmd> має завершитиÑÑ <CR> перед другою <Cmd>"
-msgid "E228: makemap: Illegal mode"
-msgstr "E228: makemap: ÐеприпуÑтимий режим"
+msgid "E222: Add to read buffer"
+msgstr "E222: Додати до буфера читаннÑ"
msgid "--No lines in buffer--"
msgstr "--Жодного Ñ€Ñдка--"
@@ -2471,9 +3648,12 @@ msgstr "E10: За \\ має йти /, ? або &"
msgid "E11: Invalid in command-line window; <CR> executes, CTRL-C quits"
msgstr "E11: ÐеприпуÑтимо у вікні команд, <CR> виконує, CTRL-C виходить"
-msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search"
+msgid "E12: Command not allowed in secure mode in current dir or tag search"
msgstr ""
-"E12: Команда не дозволена у exrc/vimrc у пошуку поточного каталогу чи мітки"
+"E12: Команда не дозволена у exrc/vimrc у поточному пошуку каталогу чи мітки"
+
+msgid "E169: Command too recursive"
+msgstr "E169: Команда занадто рекурÑивна"
msgid "E171: Missing :endif"
msgstr "E171: Бракує :endif"
@@ -2529,8 +3709,8 @@ msgid "E983: Duplicate argument: %s"
msgstr "E983: Ðргумент повторюєтьÑÑ: %s"
#, c-format
-msgid "E15: Invalid expression: %s"
-msgstr "E15: Ðеправильний вираз: %s"
+msgid "E15: Invalid expression: \"%s\""
+msgstr "E15: Ðеправильний вираз: «%s»"
msgid "E16: Invalid range"
msgstr "E16: Ðеправильні межі"
@@ -2706,6 +3886,17 @@ msgid "E45: 'readonly' option is set (add ! to override)"
msgstr "E45: Ð’Ñтановлено опцію 'readonly' (! щоб не зважати)"
#, c-format
+msgid "E734: Wrong variable type for %s="
+msgstr "E734: Ðеправильний тип змінної Ð´Ð»Ñ %s="
+
+#, c-format
+msgid "E461: Illegal variable name: %s"
+msgstr "E461: ÐеприпуÑтима назва змінної: %s"
+
+msgid "E995: Cannot modify existing variable"
+msgstr "E995: Ðеможливо змінити наÑвну змінну"
+
+#, c-format
msgid "E46: Cannot change read-only variable \"%.*s\""
msgstr "E46: Змінна тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ: «%.*s»"
@@ -2727,11 +3918,15 @@ msgid "E118: Too many arguments for function: %s"
msgstr "E118: Забагато аргументів Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ—: %s"
#, c-format
+msgid "E119: Not enough arguments for function: %s"
+msgstr "E119: Замало аргументів Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ— %s"
+
+#, c-format
msgid "E716: Key not present in Dictionary: \"%s\""
msgstr "E716: Ðемає такого ключа у Ñловнику: «%s»"
msgid "E714: List required"
-msgstr "E714: Потрібен ÑпиÑок"
+msgstr "E714: Потрібен List"
msgid "E897: List or Blob required"
msgstr "E897: Потрібен List або Blob"
@@ -2748,11 +3943,14 @@ msgid "E47: Error while reading errorfile"
msgstr "E47: Помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ помилок"
msgid "E48: Not allowed in sandbox"
-msgstr "E48: Ðа дозволено у піÑочниці"
+msgstr "E48: Ðе дозволено у піÑочниці"
msgid "E523: Not allowed here"
msgstr "E523: Ðе дозволено тут"
+msgid "E565: Not allowed to change text or change window"
+msgstr "E565: Ðе дозволено змінювати текÑÑ‚ чи вікно"
+
msgid "E359: Screen mode setting not supported"
msgstr "E359: Режим екрану не підтримуєтьÑÑ"
@@ -2768,9 +3966,6 @@ msgstr "E255: Ðе можна зчитати дані напиÑу!"
msgid "E72: Close error on swap file"
msgstr "E72: Помилка під Ñ‡Ð°Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ„Ð°Ð¹Ð»Ñƒ обміну"
-msgid "E73: tag stack empty"
-msgstr "E73: Стек міток порожній"
-
msgid "E74: Command too complex"
msgstr "E74: Занадто Ñкладна команда"
@@ -2815,16 +4010,17 @@ msgstr "E81: <SID> викориÑтовуєтьÑÑ Ð½Ðµ в контекÑті Ñ
msgid "E107: Missing parentheses: %s"
msgstr "E107: Пропущено дужки: %s"
-msgid "E363: pattern uses more memory than 'maxmempattern'"
-msgstr "E363: Зразок викориÑтовує більше, ніж 'maxmempattern', пам'Ñті"
-
-msgid "E749: empty buffer"
+msgid "E749: Empty buffer"
msgstr "E749: Порожній буфер"
#, c-format
msgid "E86: Buffer %<PRId64> does not exist"
msgstr "E86: Буфера %<PRId64> немає"
+#, c-format
+msgid "E193: %s not inside a function"
+msgstr "E193: %s поза межами функції"
+
msgid "E682: Invalid search pattern or delimiter"
msgstr "E682: Ðекоректний зразок Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ чи роздільник"
@@ -2845,6 +4041,9 @@ msgstr "E919: Каталог не знайдено у '%s': «%s»"
msgid "E952: Autocommand caused recursive behavior"
msgstr "E952: Ðвтокоманди призвели до рекурÑÑ–Ñ—"
+msgid "E328: Menu only exists in another mode"
+msgstr "E328: Меню може бути тільки в іншому режимі"
+
msgid "E813: Cannot close autocmd window"
msgstr "E813: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ вікно autocmd"
@@ -2858,22 +4057,19 @@ msgstr "E519: ÐžÐ¿Ñ†Ñ–Ñ Ð½Ðµ підтримуєтьÑÑ"
msgid "E856: Filename too long"
msgstr "E856: Задовга назва файлу"
-msgid "E806: using Float as a String"
+msgid "E806: Using a Float as a String"
msgstr "E806: Float вжито Ñк String"
-#, c-format
-msgid "E5500: autocmd has thrown an exception: %s"
-msgstr "E5500: автокоманда викинула винÑткову Ñитуацію: %s"
-
-msgid "E5520: <Cmd> mapping must end with <CR>"
-msgstr "E5520: Заміна клавіш <Cmd> має закінчуватиÑÑ <CR>"
+msgid "E788: Not allowed to edit another buffer now"
+msgstr "E788: Зараз не можна редагувати інший буфер"
-msgid "E5521: <Cmd> mapping must end with <CR> before second <Cmd>"
-msgstr "E5521: Заміна клавіш <Cmd> має закінчуватиÑÑ <CR> перед другою <Cmd>"
+#, c-format
+msgid "E1023: Using a Number as a Bool: %d"
+msgstr "E1023: Вжито Number Ñк Bool: %d"
#, c-format
-msgid "E5522: <Cmd> mapping must not include %s key"
-msgstr "E5522: Заміна клавіш <Cmd> не може міÑтити ключ %s"
+msgid "E1085: Not a callable type: %s"
+msgstr "E1085: Це не Ñхожий на функцію тип: %s"
#, c-format
msgid "E5555: API call: %s"
@@ -2889,21 +4085,46 @@ msgstr "E5601: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ вікно, залишилоÑ
msgid "E5602: Cannot exchange or rotate float"
msgstr "E5602: Ðе можна обмінÑти чи покрутити плавуче вікно"
-msgid "E1142: Non-empty string required"
-msgstr "E1142: Потрібен непорожній String"
-
msgid "E1155: Cannot define autocommands for ALL events"
-msgstr "E1155: Ðе можу визначити автокоманди Ð´Ð»Ñ Ð£Ð¡Ð†Ð¥ подій"
+msgstr "E1155: Ðеможливо визначити автокоманди Ð´Ð»Ñ Ð£Ð¡Ð†Ð¥ подій"
msgid "E1240: Resulting text too long"
msgstr "E1240: ТекÑÑ‚ результату задовгий"
msgid "E1247: Line number out of range"
-msgstr "E1247: Ðомер Ñ€Ñдка вийшов поза межами"
+msgstr "E1247: Ðомер Ñ€Ñдка поза межами"
+
+msgid "E5248: Invalid character in group name"
+msgstr "E5248: Ðекоректний Ñимвол у назві групи"
msgid "E1249: Highlight group name too long"
msgstr "E1249: Ðазва групи підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð´Ð¾Ð²Ð³Ð°"
+#, c-format
+msgid "E966: Invalid line number: %ld"
+msgstr "E966: Ðекоректний номер Ñ€Ñдка: %ld"
+
+#, c-format
+msgid "E1278: Stray '}' without a matching '{': %s"
+msgstr "E1278: Заблудла '}' без відповідної '{': %s"
+
+#, c-format
+msgid "E1279: Missing '}': %s"
+msgstr "E1279: Пропущено '}': %s"
+
+msgid "E5767: Cannot use :undo! to redo or move to a different undo branch"
+msgstr ""
+"E5767: Ðеможливо заÑтоÑувати :undo! щоб повторити або перейти до іншої гілки "
+"Ñ–Ñторії змін"
+
+#, c-format
+msgid "E5570: Cannot update trust file: %s"
+msgstr "E5570: Ðеможливо поновити файл довіри: %s"
+
+#, c-format
+msgid "E355: Unknown option: %s"
+msgstr "E355: Ðевідома опціÑ: %s"
+
msgid "search hit TOP, continuing at BOTTOM"
msgstr "Пошук дійшов до ПОЧÐТКУ, продовжуєтьÑÑ Ð· КІÐЦЯ"
@@ -2913,38 +4134,83 @@ msgstr "Пошук дійшов до КІÐЦЯ, продовжуєтьÑÑ Ð· Ð
msgid " line "
msgstr " Ñ€Ñдок "
+#, c-format
+msgid "E685: Internal error: hash_add(): duplicate key \"%s\""
+msgstr "E685: Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°: hash_add(): повторний ключ «%s»"
-msgid "E424: Too many different highlighting attributes in use"
-msgstr "E424: ВикориÑтано забагато різних атрибутів кольору"
+msgid "E478: Don't panic!"
+msgstr "E478: Без паніки!"
#, c-format
-msgid "E411: highlight group not found: %s"
-msgstr "E411: Групу підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ðµ знайдено: %s"
+msgid "E661: Sorry, no '%s' help for %s"
+msgstr "E661: Вибачте, немає допомоги '%s' Ð´Ð»Ñ %s"
#, c-format
-msgid "E412: Not enough arguments: \":highlight link %s\""
-msgstr "E412: ÐедоÑтатньо аргументів: «:highlight link %s»"
+msgid "E149: Sorry, no help for %s"
+msgstr "E149: Вибачте, немає допомоги Ð´Ð»Ñ %s"
#, c-format
-msgid "E413: Too many arguments: \":highlight link %s\""
-msgstr "E413: Забагато аргументів: «:highlight link %s»"
+msgid "Sorry, help file \"%s\" not found"
+msgstr "Вибачте, файл допомоги «%s» не знайдено"
-msgid "E414: group has settings, highlight link ignored"
+#, c-format
+msgid "E151: No match: %s"
+msgstr "E151: Жодного збігу: %s"
+
+#, c-format
+msgid "E152: Cannot open %s for writing"
+msgstr "E152: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ %s Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу"
+
+#, c-format
+msgid "E153: Unable to open %s for reading"
+msgstr "E153: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ %s Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ"
+
+#, c-format
+msgid "E670: Mix of help file encodings within a language: %s"
+msgstr "E670: Мішанина кодувань файлу допомоги Ð´Ð»Ñ Ð¼Ð¾Ð²Ð¸ %s"
+
+#, c-format
+msgid "E154: Duplicate tag \"%s\" in file %s/%s"
+msgstr "E154: ÐŸÐ¾Ð²Ñ‚Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚ÐºÐ¸ «%s» у файлі %s/%s"
+
+#, c-format
+msgid "E150: Not a directory: %s"
+msgstr "E150: Ðе Ñ” каталогом: %s"
+
+msgid "E424: Too many different highlighting attributes in use"
+msgstr "E424: ВикориÑтано забагато різних атрибутів кольору"
+
+#, c-format
+msgid "E411: Highlight group not found: %s"
+msgstr "E411: Групу підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ðµ знайдено: %s"
+
+msgid "E414: Group has settings, highlight link ignored"
msgstr "E414: Грума має settings, highlight link проігноровано"
#, c-format
-msgid "E415: unexpected equal sign: %s"
+msgid "E415: Unexpected equal sign: %s"
msgstr "E415: ÐеÑподіваний знак рівноÑті: %s"
#, c-format
-msgid "E416: missing equal sign: %s"
+msgid "E416: Missing equal sign: %s"
msgstr "E416: Пропущено знак рівноÑті: %s"
#, c-format
-msgid "E417: missing argument: %s"
+msgid "E417: Missing argument: %s"
msgstr "E417: Пропущено аргумент: %s"
#, c-format
+msgid "E412: Not enough arguments: \":highlight link %s\""
+msgstr "E412: ÐедоÑтатньо аргументів: «:highlight link %s»"
+
+#, c-format
+msgid "E413: Too many arguments: \":highlight link %s\""
+msgstr "E413: Забагато аргументів: «:highlight link %s»"
+
+msgid "E423: Illegal argument"
+msgstr "E423: ÐеприпуÑтимий аргумент"
+
+#, c-format
msgid "E418: Illegal value: %s"
msgstr "E418: Ðеправильне значеннÑ: %s"
@@ -2965,171 +4231,114 @@ msgstr "E423: Ðеправильний аргумент: %s"
msgid "E669: Unprintable character in group name"
msgstr "E669: Ðедруковний Ñимвол у назві групи"
-msgid "E5248: Invalid character in group name"
-msgstr "E5248: Ðекоректний Ñимвол у назві групи"
-
msgid "E849: Too many highlight and syntax groups"
msgstr "E849: Забагато груп підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñ– ÑинтакÑиÑу"
-msgid "Add a new database"
-msgstr "Додати нову базу даних"
+msgid "Type number and <Enter> or click with the mouse (q or empty cancels): "
+msgstr "Ðаберіть чиÑло й <Enter> чи клацніть мишкою (q чи порожнє ÑкаÑовує): "
-msgid "Query for a pattern"
-msgstr "Запит за зразком"
+msgid "Type number and <Enter> (q or empty cancels): "
+msgstr "Ðаберіть чиÑло й <Enter> (q чи порожнє ÑкаÑовує): "
-msgid "Show this message"
-msgstr "Показати це повідомленнÑ"
+msgid " Keyword completion (^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð¾Ð²Ð¸Ñ… Ñлів (^N^P)"
-msgid "Kill a connection"
-msgstr "Знищити з'єднаннÑ"
+msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
+msgstr " Режим ^X (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
-msgid "Reinit all connections"
-msgstr "ПерезапуÑтити уÑÑ– з'єднаннÑ"
+msgid " Whole line completion (^L^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÑƒÑього Ñ€Ñдка (^L^N^P)"
-msgid "Show connections"
-msgstr "Показати з'єднаннÑ"
+msgid " File name completion (^F^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð½Ð°Ð·Ð²Ð¸ файлу (^F^N^P)"
-#, c-format
-msgid "E560: Usage: cs[cope] %s"
-msgstr "E560: ВикориÑтаннÑ: cs[cope] %s"
+msgid " Tag completion (^]^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð· міток (^]^N^P)"
-msgid "This cscope command does not support splitting the window.\n"
-msgstr "Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° cscope не вміє ділити вікно.\n"
+msgid " Path pattern completion (^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÑˆÐ»Ñху за зразком (^N^P)"
-msgid "E562: Usage: cstag <ident>"
-msgstr "E562: ВикориÑтаннÑ: cstag <ідентиф-ор>"
+msgid " Definition completion (^D^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ (^D^N^P)"
-msgid "E257: cstag: tag not found"
-msgstr "E257: cstag: мітку не знайдено"
+msgid " Dictionary completion (^K^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð·Ñ– Ñловника (^K^N^P)"
-#, c-format
-msgid "E563: stat(%s) error: %d"
-msgstr "E563: stat(%s) помилка: %d"
+msgid " Thesaurus completion (^T^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð· тезауруÑу (^T^N^P)"
-#, c-format
-msgid "E564: %s is not a directory or a valid cscope database"
-msgstr "E564: %s не є ні каталогом, ні базою даних cscope"
+msgid " Command-line completion (^V^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ (^V^N^P)"
-#, c-format
-msgid "Added cscope database %s"
-msgstr "Додано базу даних cscope %s"
+msgid " User defined completion (^U^N^P)"
+msgstr " КориÑтувацьке Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ (^U^N^P)"
-#, c-format
-msgid "E262: error reading cscope connection %<PRIu64>"
-msgstr "E262: помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð·Ñ– з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ cscope %<PRIu64>"
+msgid " Omni completion (^O^N^P)"
+msgstr " Кмітливе Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ (^O^N^P)"
-msgid "E561: unknown cscope search type"
-msgstr "E561: Ðевідомий тип пошуку cscope"
+msgid " Spelling suggestion (s^N^P)"
+msgstr " Орфографічна підказка (s^N^P)"
-msgid "E566: Could not create cscope pipes"
-msgstr "E566: Ðе вдалоÑÑ Ñтворити канали до cscope"
+msgid " Keyword Local completion (^N^P)"
+msgstr " Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð¼Ñ–Ñцевих ключових Ñлів (^N^P)"
-msgid "E622: Could not fork for cscope"
-msgstr "E622: Ðе вдалоÑÑ Ñ€Ð¾Ð·Ð´Ñ–Ð»Ð¸Ñ‚Ð¸ Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð´Ð»Ñ cscope"
+msgid "Hit end of paragraph"
+msgstr "ТрапивÑÑ ÐºÑ–Ð½ÐµÑ†ÑŒ параграфа"
-msgid "cs_create_connection setpgid failed"
-msgstr "cs_create_connection: помилка setpgid"
+msgid "E840: Completion function deleted text"
+msgstr "E840: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð·Ð½Ð¸Ñ‰Ð¸Ð»Ð° текÑÑ‚"
-msgid "cs_create_connection exec failed"
-msgstr "cs_create_connection: помилка під Ñ‡Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ"
+msgid "'dictionary' option is empty"
+msgstr "ÐžÐ¿Ñ†Ñ–Ñ 'dictionary' порожнÑ"
-msgid "cs_create_connection: fdopen for to_fp failed"
-msgstr "cs_create_connection: fdopen Ð´Ð»Ñ to_fp не вдавÑÑ"
+msgid "'thesaurus' option is empty"
+msgstr "ÐžÐ¿Ñ†Ñ–Ñ 'thesaurus' порожнÑ"
-msgid "cs_create_connection: fdopen for fr_fp failed"
-msgstr "cs_create_connection: fdopen Ð´Ð»Ñ fr_fp не вдавÑÑ"
+#, c-format
+msgid "Scanning dictionary: %s"
+msgstr "СкануєтьÑÑ Ñловник: %s"
-msgid "E623: Could not spawn cscope process"
-msgstr "E623: Ðе вдалоÑÑ Ñтворити Ð¿Ñ€Ð¾Ñ†ÐµÑ cscope"
+msgid " (insert) Scroll (^E/^Y)"
+msgstr " (вÑтавити) Прогорнути (^E/^Y)"
-msgid "E567: no cscope connections"
-msgstr "E567: жодного з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ–Ð· cscope"
+msgid " (replace) Scroll (^E/^Y)"
+msgstr " (заміна) Прогорнути (^E/^Y)"
-#, c-format
-msgid "E469: invalid cscopequickfix flag %c for %c"
-msgstr "E469: Ðекоректний прапорець cscopequickfix %c Ð´Ð»Ñ %c"
+msgid "E785: complete() can only be used in Insert mode"
+msgstr "E785: complete() можна вживати тільки в режимі вÑтавлÑннÑ"
#, c-format
-msgid "E259: no matches found for cscope query %s of %s"
-msgstr "E259: Ð”Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ cscope %s з %s нічого не знайдено"
+msgid "Scanning: %s"
+msgstr "Пошук у: %s"
-msgid "cscope commands:\n"
-msgstr "Команди cscope:\n"
+msgid "Scanning tags."
+msgstr "Пошук Ñеред міток."
-#, c-format
-msgid "%-5s: %s%*s (Usage: %s)"
-msgstr "%-5s: %s%*s (ВикориÑтаннÑ: %s)"
+msgid "match in file"
+msgstr "збіг у файлі"
-msgid ""
-"\n"
-" a: Find assignments to this symbol\n"
-" c: Find functions calling this function\n"
-" d: Find functions called by this function\n"
-" e: Find this egrep pattern\n"
-" f: Find this file\n"
-" g: Find this definition\n"
-" i: Find files #including this file\n"
-" s: Find this C symbol\n"
-" t: Find this text string\n"
-msgstr ""
-"\n"
-" a: Знайти приÑÐ²Ð¾Ñ”Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñимволу\n"
-" a: Знайти приÑÐ²Ð¾Ñ”Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñимволу\n"
-" c: Знайти функції, що викликають цю функцію\n"
-" d: Знайти функції, що викликаютьÑÑ Ñ†Ñ–Ñ”ÑŽ функцією\n"
-" e: Знайти цей шаблон egrep\n"
-" f: Знайти цей файл\n"
-" g: Знайти це визначеннÑ\n"
-" i: Знайти файли, Ñкі включають в Ñебе цей файл\n"
-" s: Знайти цей Ñимвол C\n"
-" t: Знайти цей текÑÑ‚\n"
+msgid " Adding"
+msgstr " ДодаєтьÑÑ"
-msgid "E568: duplicate cscope database not added"
-msgstr "E568: Повторна база даних cscope не додана"
+msgid "-- Searching..."
+msgstr "-- Пошук..."
-#, c-format
-msgid "E261: cscope connection %s not found"
-msgstr "E261: З'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· cscope %s не знайдено"
+msgid "Back at original"
+msgstr "Початковий варіант"
-#, c-format
-msgid "cscope connection %s closed"
-msgstr "З'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· cscope %s закінчено"
+msgid "Word from other line"
+msgstr "Слово з іншого Ñ€Ñдка"
-msgid "E570: fatal error in cs_manage_matches"
-msgstr "E570: Фатальна помилка в cs_manage_matches"
+msgid "The only match"
+msgstr "Єдиний збіг"
#, c-format
-msgid "Cscope tag: %s"
-msgstr "Мітка cscope: %s"
-
-msgid ""
-"\n"
-" # line"
-msgstr ""
-"\n"
-" # Ñ€Ñдок"
-
-msgid "filename / context / line\n"
-msgstr "файл / контекÑÑ‚ / Ñ€Ñдок\n"
+msgid "match %d of %d"
+msgstr "збіг %d з %d"
#, c-format
-msgid "E609: Cscope error: %s"
-msgstr "E609: Помилка cscope: %s"
-
-msgid "All cscope databases reset"
-msgstr "УÑÑ– бази даних cscope перезавантажено"
-
-msgid "no cscope connections\n"
-msgstr "Жодного з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· cscope\n"
-
-msgid " # pid database name prepend path\n"
-msgstr " # pid назва бази даних шлÑÑ…\n"
-
-msgid "Type number and <Enter> or click with the mouse (q or empty cancels): "
-msgstr "Ðаберіть чиÑло й <Enter> чи клацніть мишкою (q чи порожнє ÑкаÑовує): "
-
-msgid "Type number and <Enter> (q or empty cancels): "
-msgstr "Ðаберіть чиÑло й <Enter> (q чи порожнє ÑкаÑовує): "
+msgid "match %d"
+msgstr "збіг %d"
#, c-format
msgid "E1502: Lua failed to grow stack to %i"
@@ -3188,7 +4397,7 @@ msgid "Error executing lua callback: %.*s"
msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¾Ð±Ñ€Ð¾Ð±Ð½Ð¸ÐºÐ° lua: %.*s"
msgid "cannot save undo information"
-msgstr "Ðе вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати інформацію поверненнÑ"
+msgstr "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ Ñ–Ñторію змін"
#, c-format
msgid "E5109: Error loading lua: %.*s"
@@ -3222,6 +4431,14 @@ msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¾Ð±Ñ€Ð¾Ð±Ð½Ð¸ÐºÐ° Lua vim.on_key: %.*
msgid "Error executing Lua callback: %.*s"
msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¾Ð±Ñ€Ð¾Ð±Ð½Ð¸ÐºÐ° Lua: %.*s"
+#, c-format
+msgid "Error executing vim.secure.read: %.*s"
+msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ vim.secure.read: %.*s"
+
+#, c-format
+msgid "Error executing vim.secure.trust: %.*s"
+msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ vim.secure.trust: %.*s"
+
msgid "Argument missing after"
msgstr "Пропущено аргумент піÑлÑ"
@@ -3245,6 +4462,9 @@ msgstr "E5421: Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ stdin: %s"
msgid "Attempt to open script file again: \"%s %s\"\n"
msgstr "Спроба повторно відкрити Ñкрипт знову: \"%s %s\"\n"
+msgid "--embed conflicts with -es/-Es/-l"
+msgstr "--embed конфліктує з -es/-Es/-l"
+
#, c-format
msgid "Cannot open for reading: \"%s\": %s\n"
msgstr "Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ: \"%s\": %s\n"
@@ -3252,9 +4472,6 @@ msgstr "Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ: \"%s\": %s\n
msgid "Cannot open for script output: \""
msgstr "Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ Ñк вихідний файл: \""
-msgid "--embed conflicts with -es/-Es"
-msgstr "--embed конфліктує з -es/-Es"
-
msgid "pre-vimrc command line"
msgstr "команди перед vimrc"
@@ -3276,14 +4493,8 @@ msgstr ""
msgid "Usage:\n"
msgstr "Вжиток:\n"
-msgid " nvim [options] [file ...] Edit file(s)\n"
-msgstr " nvim [опції] [файл ...] Редагувати файли\n"
-
-msgid " nvim [options] -t <tag> Edit file where tag is defined\n"
-msgstr " nvim [опції] -t <мітка> Редагувати файл, де визначено мітку\n"
-
-msgid " nvim [options] -q [errorfile] Edit file with first error\n"
-msgstr " nvim [опції] -q [ф.помилки] Редагувати файл з першою помилкою\n"
+msgid " nvim [options] [file ...]\n"
+msgstr " nvim [опції] [файл ...]\n"
msgid ""
"\n"
@@ -3292,12 +4503,6 @@ msgstr ""
"\n"
"Опції:\n"
-msgid " -- Only file names after this\n"
-msgstr " -- Лише назви файлів піÑÐ»Ñ Ñ†ÑŒÐ¾Ð³Ð¾\n"
-
-msgid " + Start at end of file\n"
-msgstr " + Розпочати в кінці файлу\n"
-
msgid " --cmd <cmd> Execute <cmd> before any config\n"
msgstr ""
" --cmd <команда> Виконати <команду> перед будь-Ñкою конфігурацією\n"
@@ -3306,15 +4511,23 @@ msgid " +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"
msgstr ""
" +<cmd>, -c <команда> Виконати <команду> піÑÐ»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ файлу\n"
-msgid " -b Binary mode\n"
-msgstr " -b Двійковий режим\n"
+msgid " -l <script> [args...] Execute Lua <script> (with optional args)\n"
+msgstr " -l <Ñкрипт> [args...] Виконати <Ñкрипт> Lua (з арг-тами, Ñкщо Ñ”)\n"
+
+msgid " -S <session> Source <session> after loading the first file\n"
+msgstr ""
+" -S <ÑеанÑ> Виконати <ÑеанÑ> піÑÐ»Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ завантаженого файлу\n"
+
+msgid " -s <scriptin> Read Normal mode commands from <scriptin>\n"
+msgstr ""
+" -s <Ñкрипт> Зчитати команди нормального режиму з файлу <Ñкрипт>\n"
+
+msgid " -u <config> Use this config file\n"
+msgstr " -u <config> Вжити цей файл конфігурації\n"
msgid " -d Diff mode\n"
msgstr " -d Режим порівнÑннÑ\n"
-msgid " -e, -E Ex mode\n"
-msgstr " -e, -E Режим Ex\n"
-
msgid " -es, -Es Silent (batch) mode\n"
msgstr " -es, -Es Мовчазний (пакетний) режим\n"
@@ -3324,12 +4537,6 @@ msgstr " -h, --help Ðадрукувати це повідомлен
msgid " -i <shada> Use this shada file\n"
msgstr " -i <shada> Вжити цей файл shada\n"
-msgid " -m Modifications (writing files) not allowed\n"
-msgstr " -m Зміни (Ð·Ð°Ð¿Ð¸Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð²) не дозволено\n"
-
-msgid " -M Modifications in text not allowed\n"
-msgstr " -M Зміни в текÑті файлів не дозволено\n"
-
msgid " -n No swap file, use memory only\n"
msgstr ""
" -n Ðе викориÑтовувати файл обміну, тримати уÑе в "
@@ -3348,37 +4555,30 @@ msgid " -p[N] Open N tab pages (default: one per file)\n"
msgstr ""
" -p[N] Відкрити N вкладок (Ñтандартно: одна на файл)\n"
-msgid " -r, -L List swap files\n"
-msgstr " -r, -L Показати файли обміну\n"
-
-msgid " -r <file> Recover edit state for this file\n"
-msgstr " -r <файл> Відновити Ñтан Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ файлу\n"
-
-msgid " -R Read-only mode\n"
+msgid " -R Read-only (view) mode\n"
msgstr " -R Режим тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ\n"
-msgid " -S <session> Source <session> after loading the first file\n"
-msgstr ""
-" -S <ÑеанÑ> Виконати <ÑеанÑ> піÑÐ»Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ завантаженого файлу\n"
-
-msgid " -s <scriptin> Read Normal mode commands from <scriptin>\n"
-msgstr ""
-" -s <Ñкрипт> Зчитати команди нормального режиму з файлу <Ñкрипт>\n"
-
-msgid " -u <config> Use this config file\n"
-msgstr " -u <config> Вжити цей файл конфігурації\n"
-
msgid " -v, --version Print version information\n"
msgstr " -v, --version Ðадрукувати інформацію про верÑÑ–ÑŽ програми\n"
msgid " -V[N][file] Verbose [level][file]\n"
msgstr " -V[N][файл] Більше повідомлень [рівень][файл]\n"
+msgid " -- Only file names after this\n"
+msgstr " -- Лише назви файлів піÑÐ»Ñ Ñ†ÑŒÐ¾Ð³Ð¾\n"
+
msgid " --api-info Write msgpack-encoded API metadata to stdout\n"
msgstr ""
" --api-info ЗапиÑати метадані API, Ñеріалізовані у msgpack, у "
"stdout\n"
+msgid ""
+" --clean \"Factory defaults\" (skip user config and plugins, "
+"shada)\n"
+msgstr ""
+" --clean «з нулÑ» (пропуÑтити конфігурацію кориÑтувача Ñ– "
+"плагіни, shada)\n"
+
msgid " --embed Use stdin/stdout as a msgpack-rpc channel\n"
msgstr ""
" --embed ВикориÑтати stdin/stdout, Ñк канал msgpack-rpc\n"
@@ -3389,9 +4589,6 @@ msgstr " --headless Ðе запуÑкати Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐºÐ¾Ñ
msgid " --listen <address> Serve RPC API from this address\n"
msgstr " --listen <адреÑа> ОбÑлуговувати RPC API за цією адреÑою\n"
-msgid " --noplugin Don't load plugins\n"
-msgstr " --noplugin Ðе завантажувати доповненнÑ\n"
-
msgid " --remote[-subcommand] Execute commands remotely on a server\n"
msgstr " --remote[-subcommand] Виконати команди на віддаленому Ñервері\n"
@@ -3408,6 +4605,42 @@ msgstr ""
"\n"
"Див \":help startup-options\" щоб побачити вÑÑ– опції.\n"
+#, c-format
+msgid "E224: Global abbreviation already exists for %s"
+msgstr "E224: Загальне ÑÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ %s вже Ñ–Ñнує"
+
+#, c-format
+msgid "E225: Global mapping already exists for %s"
+msgstr "E225: Загальна заміна клавіш Ð´Ð»Ñ %s вже Ñ–Ñнує"
+
+#, c-format
+msgid "E226: Abbreviation already exists for %s"
+msgstr "E226: Вже Ñ” ÑÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð´Ð»Ñ %s"
+
+#, c-format
+msgid "E227: Mapping already exists for %s"
+msgstr "E227: Вже Ñ” заміна клавіш Ð´Ð»Ñ %s"
+
+msgid "E460: Entries missing in mapset() dict argument"
+msgstr "E460: Бракує запиÑів у Ñловниковому аргументі mapset()"
+
+msgid "No abbreviation found"
+msgstr "Ð¡ÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдено"
+
+msgid "No mapping found"
+msgstr "Заміни клавіш не знайдено"
+
+msgid "E228: makemap: Illegal mode"
+msgstr "E228: makemap: ÐеприпуÑтимий режим"
+
+#, c-format
+msgid "E357: 'langmap': Matching character missing for %s"
+msgstr "E357: 'langmap': Ð”Ð»Ñ Ñимволу %s немає пари"
+
+#, c-format
+msgid "E358: 'langmap': Extra characters after semicolon: %s"
+msgstr "E358: 'langmap': Зайві Ñимволи піÑÐ»Ñ `;': %s"
+
msgid "No marks set"
msgstr "Ðе вÑтановлено жодної помітки"
@@ -3462,11 +4695,11 @@ msgstr "E803: ID не знайдено: %<PRId64>"
#, c-format
msgid "E474: List item %d is either not a dictionary or an empty one"
-msgstr "E474: Елемент ÑпиÑку %d або не Ñловник або порожній"
+msgstr "E474: Елемент %d ÑпиÑку або не Ñловник або порожній"
#, c-format
msgid "E474: List item %d is missing one of the required keys"
-msgstr "E474: Елемент ÑпиÑку %d немає одного з обов’Ñзкових ключів"
+msgstr "E474: Елемент %d ÑпиÑку немає одного з обов’Ñзкових ключів"
#, c-format
msgid "E798: ID is reserved for \":match\": %<PRId64>"
@@ -3476,7 +4709,30 @@ msgstr "E798: ID зарезервовано Ð´Ð»Ñ \":match\": %<PRId64>"
msgid "E798: ID is reserved for \"match\": %<PRId64>"
msgstr "E798: ID зарезервовано Ð´Ð»Ñ \"match\": %<PRId64>"
-msgid "E293: block was not locked"
+#, c-format
+msgid "E1109: List item %d is not a List"
+msgstr "E1109: Елемент %d ÑпиÑку не List"
+
+#, c-format
+msgid "E1110: List item %d does not contain 3 numbers"
+msgstr "E1110: Елемент %d ÑпиÑку не міÑтить 3 чиÑла"
+
+#, c-format
+msgid "E1111: List item %d range invalid"
+msgstr "E1111: Діапазон елемента %d ÑпиÑку некоректний"
+
+#, c-format
+msgid "E1112: List item %d cell width invalid"
+msgstr "E1112: Ширина комірки елемента %d ÑпиÑку некоректна"
+
+#, c-format
+msgid "E1113: Overlapping ranges for 0x%lx"
+msgstr "E1113: ÐÐ°ÐºÐ»Ð°Ð´Ð°Ð½Ð½Ñ Ð´Ñ–Ð°Ð¿Ð°Ð·Ð¾Ð½Ñ–Ð² у 0x%lx"
+
+msgid "E1114: Only values of 0x80 and higher supported"
+msgstr "E1114: ПідтримуютьÑÑ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ 0x80 Ñ– вище"
+
+msgid "E293: Block was not locked"
msgstr "E293: Блок не було зафікÑовано"
msgid "E294: Seek error in swap file read"
@@ -3494,6 +4750,37 @@ msgstr "E297: Помилка запиÑу файлу обміну"
msgid "E300: Swap file already exists (symlink attack?)"
msgstr "E300: Файл обміну вже Ñ–Ñнує (атака Ñимвольним поÑиланнÑм?)"
+#, c-format
+msgid "E315: ml_get: Invalid lnum: %<PRId64>"
+msgstr "E315: ml_get: Ðеправильний lnum: %<PRId64>"
+
+#, c-format
+msgid "E316: ml_get: Cannot find line %<PRId64>in buffer %d %s"
+msgstr "E316: ml_get: Ðе знайшов Ñ€Ñдок %<PRId64> у буфері %d %s"
+
+msgid "E317: Pointer block id wrong"
+msgstr "E317: Блок вказівника помилковий"
+
+msgid "E317: Pointer block id wrong 2"
+msgstr "E317: Блок вказівника помилковий 2"
+
+msgid "E317: Pointer block id wrong 3"
+msgstr "E317: Блок вказівника помилковий 3"
+
+msgid "E317: Pointer block id wrong 4"
+msgstr "E317: Блок вказівника помилковий 4"
+
+#, c-format
+msgid "E322: Line number out of range: %<PRId64> past the end"
+msgstr "E322: Ðомер Ñ€Ñдка поза межами: %<PRId64> за кінцем"
+
+#, c-format
+msgid "E323: Line count wrong in block %<PRId64>"
+msgstr "E323: Ðеправильна кількіÑть Ñ€Ñдків у блоці %<PRId64>"
+
+msgid "E1364: Warning: Pointer block corrupted"
+msgstr "E1364: ПопередженнÑ: Пошкоджено блок вказівника"
+
msgid "E298: Didn't get block nr 0?"
msgstr "E298: Ðемає блоку 0?"
@@ -3603,6 +4890,9 @@ msgstr "??? звідÑи Ñ– до `??? КІÐЕЦЬ' Ñ€Ñдки, можливо,
msgid "??? from here until ???END lines may have been inserted/deleted"
msgstr "??? звідÑи Ñ– до `??? КІÐЕЦЬ' Ñ€Ñдки, можливо, були додані/знищені"
+msgid "??? lines may be missing"
+msgstr "??? Ñ€Ñдків може бракувати"
+
msgid "???END"
msgstr "??? КІÐЕЦЬ"
@@ -3636,12 +4926,17 @@ msgstr "Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð¾. ВміÑÑ‚ буфера ÑпÑ
msgid ""
"\n"
-"You may want to delete the .swp file now.\n"
-"\n"
+"You may want to delete the .swp file now."
msgstr ""
"\n"
-"Можливо, тепер ви хочете знищити файл обміну .swp.\n"
+"Тепер ви можете знищити файл обміну .swp."
+
+msgid ""
"\n"
+"Note: process STILL RUNNING: "
+msgstr ""
+"\n"
+"Примітка: Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð©Ð• ВИКОÐУЄТЬСЯ: "
msgid "Swap files found:"
msgstr "Знайдено файли обміну:"
@@ -3741,26 +5036,12 @@ msgstr "Файл збережено"
msgid "E314: Preserve failed"
msgstr "E314: Ð—Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ"
-#, c-format
-msgid "E315: ml_get: invalid lnum: %<PRId64>"
-msgstr "E315: ml_get: неправильний lnum: %<PRId64>"
-
-#, c-format
-msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s"
-msgstr "E316: ml_get: не знайшов Ñ€Ñдок %<PRId64> у буфері %d %s"
-
-msgid "E317: pointer block id wrong 3"
-msgstr "E317: Вказівник блоку помилковий 3"
-
msgid "stack_idx should be 0"
msgstr "stack_idx має бути рівним 0"
msgid "E318: Updated too many blocks?"
msgstr "E318: Поновлено забагато блоків?"
-msgid "E317: pointer block id wrong 4"
-msgstr "E317: Вказівник блоку помилковий 4"
-
msgid "deleted block 1?"
msgstr "блок 1 знищено?"
@@ -3768,26 +5049,12 @@ msgstr "блок 1 знищено?"
msgid "E320: Cannot find line %<PRId64>"
msgstr "E320: Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ Ñ€Ñдок %<PRId64>"
-msgid "E317: pointer block id wrong"
-msgstr "E317: Вказівник блоку помилковий"
-
msgid "pe_line_count is zero"
msgstr "pe_line_count дорівнює 0"
-#, c-format
-msgid "E322: line number out of range: %<PRId64> past the end"
-msgstr "E322: Ðомер Ñ€Ñдка вийшов за межі: %<PRId64> за кінцем"
-
-#, c-format
-msgid "E323: line count wrong in block %<PRId64>"
-msgstr "E323: КількіÑть Ñ€Ñдків у блоці %<PRId64>"
-
msgid "Stack size increases"
msgstr "Розмір Ñтеку збільшуєтьÑÑ"
-msgid "E317: pointer block id wrong 2"
-msgstr "E317: Вказівник блоку помилковий 2"
-
#, c-format
msgid "E773: Symlink loop for \"%s\""
msgstr "E773: Циклічні Ñимвольні поÑÐ¸Ð»Ð°Ð½Ð½Ñ Â«%s»"
@@ -3846,7 +5113,7 @@ msgstr ""
" щоб позбутиÑÑ Ñ†ÑŒÐ¾Ð³Ð¾ повідомленнÑ.\n"
msgid "Found a swap file that is not useful, deleting it"
-msgstr "Знайдено файл обміну, Ñким не можна ÑкориÑтатиÑÑ, знищеннÑ"
+msgstr "Знайдено файл обміну, Ñким не можна ÑкориÑтатиÑÑ, знищуємо"
msgid "Swap file \""
msgstr "Файл обміну «"
@@ -3906,9 +5173,6 @@ msgstr "E342: Забракло пам'Ñті! (потрібно було %<PRIu
msgid "E327: Part of menu-item path is not sub-menu"
msgstr "E327: ЧаÑтина шлÑху до елемента меню не Ñ” підменю"
-msgid "E328: Menu only exists in another mode"
-msgstr "E328: Меню може бути тільки в іншому режимі"
-
#, c-format
msgid "E329: No menu \"%s\""
msgstr "E329: Ðемає меню «%s»"
@@ -3943,6 +5207,12 @@ msgstr "E333: ШлÑÑ… повинен веÑти до елемента меню"
msgid "E334: Menu not found: %s"
msgstr "E334: Меню не знайдено: %s"
+msgid "E336: Menu path must lead to a sub-menu"
+msgstr "E336: ШлÑÑ… до меню повинен веÑти до підменю"
+
+msgid "E337: Menu not found - check menu names"
+msgstr "E337: Меню не знайдено — перевірте назви меню"
+
#, c-format
msgid "Error detected while processing %s:"
msgstr "ВиÑвлено помилку під Ñ‡Ð°Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ %s:"
@@ -3961,6 +5231,20 @@ msgstr "Перервано: "
msgid "Press ENTER or type command to continue"
msgstr "ÐатиÑніть ENTER або введіть команду Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð²Ð¶ÐµÐ½Ð½Ñ"
+#, c-format
+msgid "%ld more line"
+msgid_plural "%ld more lines"
+msgstr[0] "ще %ld Ñ€Ñдок"
+msgstr[1] "ще %ld Ñ€Ñдки"
+msgstr[2] "ще %ld Ñ€Ñдків"
+
+#, c-format
+msgid "%ld line less"
+msgid_plural "%ld fewer lines"
+msgstr[0] "на %ld Ñ€Ñдок менше"
+msgstr[1] "на %ld Ñ€Ñдки менше"
+msgstr[2] "на %ld Ñ€Ñдків менше"
+
msgid " (Interrupted)"
msgstr " (Перервано)"
@@ -4006,6 +5290,12 @@ msgstr ""
"&D:Жодного\n"
"&C:СкаÑувати"
+msgid "E664: Changelist is empty"
+msgstr "E664: СпиÑок змін порожній"
+
+msgid "E1292: Command-line window is already open"
+msgstr "E1292: Вікно командного Ñ€Ñдка вже відкрите"
+
msgid "E349: No identifier under cursor"
msgstr "E349: Ðемає ідентифікатора над курÑором"
@@ -4015,9 +5305,6 @@ msgstr "E348: Ðемає Ñ€Ñдка на курÑорі"
msgid "E352: Cannot erase folds with current 'foldmethod'"
msgstr "E352: Ðе вдалоÑÑ Ð·Ð½Ð¸Ñ‰Ð¸Ñ‚Ð¸ згортки поточним методом 'foldmethod'"
-msgid "E664: changelist is empty"
-msgstr "E664: СпиÑок змін порожній"
-
msgid "E662: At start of changelist"
msgstr "E662: Початок ÑпиÑку змін"
@@ -4031,18 +5318,66 @@ msgstr ""
msgid "Type :qa and press <Enter> to exit Nvim"
msgstr "Введіть :qa Ñ– натиÑніÑть <Enter> щоб вийти з Nvim"
+msgid ""
+"E883: Search pattern and expression register may not contain two or more "
+"lines"
+msgstr ""
+"E883: Шаблон пошуку Ñ– регіÑтр виразу не можуть міÑтити два чи більше Ñ€Ñдків"
+
+#, c-format
+msgid "%<PRId64> line %sed %d time"
+msgid_plural "%<PRId64> line %sed %d times"
+msgstr[0] "ЗÑунуто %2$s %3$d раз Ñ€Ñдків: %1$<PRId64>"
+msgstr[1] "ЗÑунуто %2$s %3$d рази Ñ€Ñдків: %1$<PRId64>"
+msgstr[2] "ЗÑунуто %2$s %3$d разів Ñ€Ñдків: %1$<PRId64>"
+
+#, c-format
+msgid "%<PRId64> lines %sed %d time"
+msgid_plural "%<PRId64> lines %sed %d times"
+msgstr[0] "ЗÑунуто %2$s %3$d раз Ñ€Ñдків: %1$<PRId64>"
+msgstr[1] "ЗÑунуто %2$s %3$d рази Ñ€Ñдків: %1$<PRId64>"
+msgstr[2] "ЗÑунуто %2$s %3$d разів Ñ€Ñдків: %1$<PRId64>"
+
#, c-format
msgid "%<PRId64> lines to indent... "
-msgstr "ЗалишилоÑÑ Ð²Ð¸Ñ€Ñ–Ð²Ð½Ñти %<PRId64> Ñ€Ñдків..."
+msgstr "ЗалишилоÑÑ Ð¿Ñ–Ð´Ð±Ð¸Ñ‚Ð¸ відÑтупи %<PRId64> Ñ€Ñдків..."
+
+#, c-format
+msgid "%<PRId64> line indented "
+msgid_plural "%<PRId64> lines indented "
+msgstr[0] "ВідÑтуп %<PRId64> Ñ€Ñдка "
+msgstr[1] "ВідÑтуп %<PRId64> Ñ€Ñдків "
+msgstr[2] "ВідÑтуп %<PRId64> Ñ€Ñдків "
msgid "E748: No previously used register"
msgstr "E748: РегіÑтри перед цим не вживалиÑÑŒ"
#, c-format
+msgid "%<PRId64> line changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "Змінено %<PRId64> Ñ€Ñдок"
+msgstr[1] "Змінено %<PRId64> Ñ€Ñдки"
+msgstr[2] "Змінено %<PRId64> Ñ€Ñдків"
+
+#, c-format
msgid " into \"%c"
msgstr " у \"%c"
#, c-format
+msgid "block of %<PRId64> line yanked%s"
+msgid_plural "block of %<PRId64> lines yanked%s"
+msgstr[0] "блок з %<PRId64> Ñ€Ñдка виÑмикнуто%s"
+msgstr[1] "блок з %<PRId64> Ñ€Ñдків виÑмикнуто%s"
+msgstr[2] "блок з %<PRId64> Ñ€Ñдків виÑмикнуто%s"
+
+#, c-format
+msgid "%<PRId64> line yanked%s"
+msgid_plural "%<PRId64> lines yanked%s"
+msgstr[0] "%<PRId64> Ñ€Ñдок виÑмикнуто%s"
+msgstr[1] "%<PRId64> Ñ€Ñдки виÑмикнуто%s"
+msgstr[2] "%<PRId64> Ñ€Ñдків виÑмикнуто%s"
+
+#, c-format
msgid "E353: Nothing in register %s"
msgstr "E353: У регіÑтрі %s нічого немає"
@@ -4053,11 +5388,12 @@ msgstr ""
"\n"
"Тип Ðаз. ВміÑÑ‚ "
-msgid ""
-"E883: search pattern and expression register may not contain two or more "
-"lines"
-msgstr ""
-"E883: шаблон пошуку Ñ– реєÑтр виразу не можуть міÑтити два чи більше Ñ€Ñдків"
+#, c-format
+msgid "%<PRId64> lines changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "%<PRId64> Ñ€Ñдок змінено"
+msgstr[1] "%<PRId64> Ñ€Ñдки змінено"
+msgstr[2] "%<PRId64> Ñ€Ñдків змінено"
#, c-format
msgid "%<PRId64> Cols; "
@@ -4109,7 +5445,7 @@ msgid "E520: Not allowed in a modeline"
msgstr "E520: Ðе дозволено у modeline"
msgid "E992: Not allowed in a modeline when 'modelineexpr' is off"
-msgstr "E992: Ðе дозволено у modeline, коли вимкнено 'modelineexpr'"
+msgstr "E992: Ðе дозволено у modeline, коли 'modelineexpr' вимкнено"
msgid "E846: Key code not set"
msgstr "E846: Код ключа не вÑтановлено"
@@ -4117,59 +5453,6 @@ msgstr "E846: Код ключа не вÑтановлено"
msgid "E521: Number required after ="
msgstr "E521: ПіÑÐ»Ñ = потрібно вказати чиÑло"
-#, c-format
-msgid "E539: Illegal character <%s>"
-msgstr "E539: Ðедозволений Ñимвол <%s>"
-
-#, c-format
-msgid "For option %s"
-msgstr "Ð”Ð»Ñ Ð¾Ð¿Ñ†Ñ–Ñ— %s"
-
-msgid "E589: 'backupext' and 'patchmode' are equal"
-msgstr "E589: Опції 'backupext' і 'patchmode' однакові"
-
-msgid "E834: Conflicts with value of 'listchars'"
-msgstr "E834: Конфліктує із значеннÑм 'listchars'"
-
-msgid "E835: Conflicts with value of 'fillchars'"
-msgstr "E835: Конфліктує із значеннÑм 'fillchars'"
-
-msgid "E524: Missing colon"
-msgstr "E524: Бракує двокрапки"
-
-msgid "E525: Zero length string"
-msgstr "E525: РÑдок порожній"
-
-#, c-format
-msgid "E526: Missing number after <%s>"
-msgstr "E526: ПіÑÐ»Ñ <%s> бракує чиÑла"
-
-msgid "E527: Missing comma"
-msgstr "E527: Бракує коми"
-
-msgid "E528: Must specify a ' value"
-msgstr "E528: Потрібно вказати Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ '"
-
-msgid "E595: 'showbreak' contains unprintable or wide character"
-msgstr "E595: 'showbreak' міÑтить недруковні або розширені Ñимволи"
-
-#, c-format
-msgid "E535: Illegal character after <%c>"
-msgstr "E535: Ðедозволений Ñимвол піÑÐ»Ñ <%c>"
-
-msgid "E536: comma required"
-msgstr "E536: Потрібна кома"
-
-#, c-format
-msgid "E537: 'commentstring' must be empty or contain %s"
-msgstr "E537: 'commentstring' має бути порожньою чи міÑтити %s"
-
-msgid "E540: Unclosed expression sequence"
-msgstr "E540: ПоÑлідовніÑть виразів не завершено"
-
-msgid "E542: unbalanced groups"
-msgstr "E542: Групи не збаланÑовано"
-
msgid "E590: A preview window already exists"
msgstr "E590: Вікно переглÑду вже Ñ–Ñнує"
@@ -4186,12 +5469,8 @@ msgid "E594: Need at least %d columns"
msgstr "E594: Потрібно щонайменше %d Ñтовпців"
#, c-format
-msgid "E355: Unknown option: %s"
-msgstr "E355: Ðевідома опціÑ: %s"
-
-#, c-format
-msgid "E521: Number required: &%s = '%s'"
-msgstr "E521: Потрібно вказати Number: &%s = '%s'"
+msgid "Invalid value for option '%s': expected %s, got %s %s"
+msgstr "Ðекоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¾Ð¿Ñ†Ñ–Ñ— '%s': очікуєтьÑÑ %s, отримано %s %s"
msgid ""
"\n"
@@ -4217,13 +5496,64 @@ msgstr ""
msgid "E356: get_varp ERROR"
msgstr "E356: Помилка get_varp"
+msgid "E540: Unclosed expression sequence"
+msgstr "E540: ПоÑлідовніÑть виразів не завершено"
+
+msgid "E536: Comma required"
+msgstr "E536: Потрібна кома"
+
+msgid "E542: Unbalanced groups"
+msgstr "E542: Групи не збаланÑовано"
+
+msgid "E589: 'backupext' and 'patchmode' are equal"
+msgstr "E589: Опції 'backupext' і 'patchmode' однакові"
+
+msgid "E595: 'showbreak' contains unprintable or wide character"
+msgstr "E595: 'showbreak' міÑтить недруковні або розширені Ñимволи"
+
+msgid "E1336: Internal error: shortmess too long"
+msgstr "E1336: Ð’Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°: занадто довгий shortmess"
+
#, c-format
-msgid "E357: 'langmap': Matching character missing for %s"
-msgstr "E357: 'langmap': Ð”Ð»Ñ Ñимволу %s немає пари"
+msgid "E539: Illegal character <%s>"
+msgstr "E539: Ðедозволений Ñимвол <%s>"
#, c-format
-msgid "E358: 'langmap': Extra characters after semicolon: %s"
-msgstr "E358: 'langmap': Зайві Ñимволи піÑÐ»Ñ `;': %s"
+msgid "For option %s"
+msgstr "Ð”Ð»Ñ Ð¾Ð¿Ñ†Ñ–Ñ— %s"
+
+msgid "E5080: Digit expected"
+msgstr "E5080: ОчікуєтьÑÑ Ñ†Ð¸Ñ„Ñ€Ð°"
+
+msgid "E524: Missing colon"
+msgstr "E524: Бракує двокрапки"
+
+msgid "E525: Zero length string"
+msgstr "E525: РÑдок порожній"
+
+#, c-format
+msgid "E526: Missing number after <%s>"
+msgstr "E526: ПіÑÐ»Ñ <%s> бракує чиÑла"
+
+msgid "E527: Missing comma"
+msgstr "E527: Бракує коми"
+
+msgid "E528: Must specify a ' value"
+msgstr "E528: Потрібно вказати Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ '"
+
+#, c-format
+msgid "E535: Illegal character after <%c>"
+msgstr "E535: Ðедозволений Ñимвол піÑÐ»Ñ <%c>"
+
+#, c-format
+msgid "E537: 'commentstring' must be empty or contain %s"
+msgstr "E537: 'commentstring' має бути порожньою чи міÑтити %s"
+
+msgid "E834: Conflicts with value of 'listchars'"
+msgstr "E834: Конфліктує із значеннÑм 'listchars'"
+
+msgid "E835: Conflicts with value of 'fillchars'"
+msgstr "E835: Конфліктує із значеннÑм 'fillchars'"
#, c-format
msgid "dlerror = \"%s\""
@@ -4236,6 +5566,14 @@ msgstr "E5420: Ðе вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати у файл: %s"
msgid "Vim: Error reading input, exiting...\n"
msgstr "Vim: Помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð²Ð²Ð¾Ð´Ñƒ, робота завершуєтьÑÑ...\n"
+#, c-format
+msgid "Current %slanguage: \"%s\""
+msgstr "Мова (%s): «%s»"
+
+#, c-format
+msgid "E197: Cannot set language to \"%s\""
+msgstr "E197: Ðе вдалоÑÑ Ð²Ñтановити мову «%s»"
+
msgid ""
"\n"
"shell returned "
@@ -4254,6 +5592,7 @@ msgstr ""
msgid "E5677: Error writing input to shell-command: %s"
msgstr "E5677: Ðе вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати на вхід команди оболонки: %s"
+#, no-c-format
msgid "%a %b %d %H:%M:%S %Y"
msgstr "%H:%M:%S %a, %d %B %Y р."
@@ -4261,11 +5600,14 @@ msgstr "%H:%M:%S %a, %d %B %Y р."
msgid "E447: Can't find file \"%s\" in path"
msgstr "E447: Файл «%s» не знайдено у шлÑху пошуку"
+msgid "E750: First use \":profile start {fname}\""
+msgstr "E750: Спочатку зробіть «:profile start {файл}»"
+
msgid "E553: No more items"
msgstr "E553: Ðемає більше елементів"
msgid "E925: Current quickfix list was changed"
-msgstr "E925: Поточний ÑпиÑок quickfix змінивÑÑ"
+msgstr "E925: СпиÑок quickfix змінивÑÑ"
msgid "E926: Current location list was changed"
msgstr "E926: Поточний ÑпиÑок міÑць змінивÑÑ"
@@ -4330,7 +5672,7 @@ msgid "Cannot open file \"%s\""
msgstr "Ðе вдалоÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ файл «%s»"
msgid "cannot have both a list and a \"what\" argument"
-msgstr "не можна задавати одночаÑно ÑпиÑок Ñ– аргумент «що»"
+msgstr "неможливо задати Ñ– ÑпиÑок Ñ– аргумент «що»"
msgid "E681: Buffer is not loaded"
msgstr "E681: Буфер не завантажено"
@@ -4339,10 +5681,28 @@ msgid "E777: String or List expected"
msgstr "E777: ОчікуєтьÑÑ String чи List"
#, c-format
-msgid "E369: invalid item in %s%%[]"
+msgid "E927: Invalid action: '%s'"
+msgstr "E927: Ðеправильна діÑ: «%s»"
+
+#, c-format
+msgid "E59: Invalid character after %s@"
+msgstr "E59: Ðекоректний Ñимвол піÑÐ»Ñ %s@"
+
+msgid "E63: Invalid use of \\_"
+msgstr "E63: Ðекоректно вжито \\_"
+
+msgid "E363: Pattern uses more memory than 'maxmempattern'"
+msgstr "E363: Зразок викориÑтовує більше пам’Ñті, ніж 'maxmempattern'"
+
+#, c-format
+msgid "E369: Invalid item in %s%%[]"
msgstr "E369: Ðекоректний елемент у %s%%[]"
#, c-format
+msgid "E654: Missing delimiter after search pattern: %s"
+msgstr "E654: Бракує роздільника піÑÐ»Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ñƒ пошуку: %s"
+
+#, c-format
msgid "E769: Missing ] after %s["
msgstr "E769: Бракує ] піÑÐ»Ñ %s["
@@ -4379,13 +5739,24 @@ msgid "E70: Empty %s%%[]"
msgstr "E70: %s%%[] порожній"
msgid "E956: Cannot use pattern recursively"
-msgstr "E956: Ðе можна рекурÑивно викориÑтати шаблон"
+msgstr "E956: Ðе можна заÑтоÑувати шаблон рекурÑивно"
#, c-format
msgid "E1204: No Number allowed after .: '\\%%%c'"
msgstr "E1204: Number не можна піÑÐ»Ñ .: '\\%%%c'"
#, c-format
+msgid "E1273: (NFA regexp) missing value in '\\%%%c'"
+msgstr "E1273: (NFA regexp) бракує Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñƒ '\\%%%c'"
+
+#, c-format
+msgid "E1281: Atom '\\%%#=%c' must be at the start of the pattern"
+msgstr "E1281: Ðтом '\\%%#=%c' має бути на початку шаблону"
+
+msgid "E1290: substitute nesting too deep"
+msgstr "E1290: Забагато вкладених замін"
+
+#, c-format
msgid "E554: Syntax error in %s{...}"
msgstr "E554: СинтакÑична помилка в %s{...}"
@@ -4404,6 +5775,10 @@ msgid "Switching to backtracking RE engine for pattern: "
msgstr "ÐŸÐµÑ€ÐµÐ¼Ð¸ÐºÐ°Ð½Ð½Ñ Ð´Ð¾ проÑтого Ñ€ÑƒÑˆÑ–Ñ Ñ€ÐµÐ³ÑƒÐ»Ñрних виразів: "
#, c-format
+msgid "Searching for \"%s\" under \"%s\" in \"%s\""
+msgstr "Пошук «%s» за «%s» в «%s»"
+
+#, c-format
msgid "Searching for \"%s\" in \"%s\""
msgstr "Пошук «%s» в «%s»"
@@ -4423,71 +5798,82 @@ msgstr "Пошук «%s» в шлÑху виконаннÑ"
msgid "not found in runtime path: \"%s\""
msgstr "не знайдено в шлÑху виконаннÑ: «%s»"
-msgid " TERMINAL"
-msgstr " ТЕРМІÐÐЛ"
+#, c-format
+msgid "Cannot source a directory: \"%s\""
+msgstr "Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ каталог: «%s»"
-msgid " VREPLACE"
-msgstr " ВІРТ ЗÐМІÐÐ"
+#, c-format
+msgid "could not source \"%s\""
+msgstr "Ðе вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ «%s»"
-msgid " REPLACE"
-msgstr " ЗÐМІÐÐ"
+#, c-format
+msgid "line %<PRId64>: could not source \"%s\""
+msgstr "Ñ€Ñдок %<PRId64>: не вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ «%s»"
-msgid " REVERSE"
-msgstr " ÐÐВИВОРІТ"
+#, c-format
+msgid "sourcing \"%s\""
+msgstr "виконуєтьÑÑ Â«%s»"
-msgid " INSERT"
-msgstr " ВСТÐВИТИ"
+#, c-format
+msgid "line %<PRId64>: sourcing \"%s\""
+msgstr "Ñ€Ñдок %<PRId64>: виконуєтьÑÑ Â«%s»"
-msgid " (insert)"
-msgstr " (вÑтавити)"
+#, c-format
+msgid "finished sourcing %s"
+msgstr "закінчено Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ %s"
-msgid " (replace)"
-msgstr " (заміна)"
+msgid "modeline"
+msgstr "modeline"
-msgid " (vreplace)"
-msgstr " (вірт заміна)"
+msgid "--cmd argument"
+msgstr "--cmd аргумент"
-msgid " Hebrew"
-msgstr " Іврит"
+msgid "-c argument"
+msgstr "-c аргумент"
-msgid " Arabic"
-msgstr " ÐрабÑька"
+msgid "environment variable"
+msgstr "змінна оточеннÑ"
-msgid " (paste)"
-msgstr " (клей)"
+msgid "error handler"
+msgstr "обробник помилки"
-msgid " VISUAL"
-msgstr " ВИБІР"
+msgid "changed window size"
+msgstr "змінено розмір вікна"
-msgid " VISUAL LINE"
-msgstr " ВИБІР РЯДКІВ"
+msgid "Lua"
+msgstr "Lua"
-msgid " VISUAL BLOCK"
-msgstr " ВИБІР БЛОКУ"
+#, c-format
+msgid "API client (channel id %<PRIu64>)"
+msgstr "Клієнт API (канал «%<PRIu64>»)"
-msgid " SELECT"
-msgstr " ВИДІЛЕÐÐЯ"
+msgid "anonymous :source"
+msgstr "анонімний :source"
-msgid " SELECT LINE"
-msgstr " ВИДІЛЕÐÐЯ РЯДКІВ"
+#, c-format
+msgid "anonymous :source (script id %d)"
+msgstr "анонімний :source (ід. Ñкрипта %d)"
-msgid " SELECT BLOCK"
-msgstr " ВИДІЛЕÐÐЯ БЛОКУ"
+msgid "W15: Warning: Wrong line separator, ^M may be missing"
+msgstr "W15: ЗаÑтереженнÑ: Ðеправильний роздільник Ñ€Ñдків, можливо, бракує ^M"
-msgid "recording"
-msgstr "йде запиÑ"
+msgid "E167: :scriptencoding used outside of a sourced file"
+msgstr "E167: :scriptencoding викориÑтано поза виконуваним файлом"
+
+msgid "E168: :finish used outside of a sourced file"
+msgstr "E168: :finish викориÑтано поза виконуваним файлом"
#, c-format
-msgid "E383: Invalid search string: %s"
-msgstr "E383: Ðеправильний зразок Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ: %s"
+msgid "E384: Search hit TOP without match for: %s"
+msgstr "E384: Пошук дійшов до ПОЧÐТКУ без збігів з: %s"
#, c-format
-msgid "E384: search hit TOP without match for: %s"
-msgstr "E384: Пошук дійшов до ПОЧÐТКУ без збігів з %s"
+msgid "E385: Search hit BOTTOM without match for: %s"
+msgstr "E385: Пошук дійшов до КІÐЦЯ без збігів з: %s"
#, c-format
-msgid "E385: search hit BOTTOM without match for: %s"
-msgstr "E385: Пошук дійшов до КІÐЦЯ без збігів з %s"
+msgid "E383: Invalid search string: %s"
+msgstr "E383: Ðеправильний зразок Ð´Ð»Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ: %s"
msgid "E386: Expected '?' or '/' after ';'"
msgstr "E386: ПіÑÐ»Ñ `;' має бути `?' або `/'"
@@ -4638,14 +6024,14 @@ msgid "Writing ShaDa file \"%s\""
msgstr "ЗапиÑуєтьÑÑ Ñ„Ð°Ð¹Ð» ShaDa «%s»"
#, c-format
-msgid "Failed setting uid and gid for file %s: %s"
-msgstr "Ðе вдалоÑÑ Ð²Ñтановити uid Ñ– gid Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ñƒ %s: %s"
-
-#, c-format
msgid "E137: ShaDa file is not writable: %s"
msgstr "E137: Ðе дозволено Ð·Ð°Ð¿Ð¸Ñ Ñƒ файл ShaDa: %s"
#, c-format
+msgid "Failed setting uid and gid for file %s: %s"
+msgstr "Ðе вдалоÑÑ Ð²Ñтановити uid Ñ– gid Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ñƒ %s: %s"
+
+#, c-format
msgid "Can't rename ShaDa file from %s to %s!"
msgstr "Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ¹Ð¼ÐµÐ½ÑƒÐ²Ð°Ñ‚Ð¸ файл ShaDa з %s у %s!"
@@ -4803,21 +6189,6 @@ msgstr "E797: Ðвтокоманда SpellFileMissing знищила буфер"
msgid "Warning: region %s not supported"
msgstr "ЗаÑтереженнÑ: регіон %s не підтримуєтьÑÑ"
-msgid "Sorry, no suggestions"
-msgstr "Пробачте, немає пропозицій"
-
-#, c-format
-msgid "Sorry, only %<PRId64> suggestions"
-msgstr "Пробачте, тільки %<PRId64> пропозицій"
-
-#, c-format
-msgid "Change \"%.*s\" to:"
-msgstr "Замінити «%.*s» на:"
-
-#, c-format
-msgid " < \"%.*s\""
-msgstr " < «%.*s»"
-
msgid "E752: No previous spell replacement"
msgstr "E752: Ðемає попередньої заміни"
@@ -4829,6 +6200,16 @@ msgid "E758: Truncated spell file"
msgstr "E758: Обірваний файл орфографії"
#, c-format
+msgid "E782: Error while reading .sug file: %s"
+msgstr "E782: Помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ .sug: %s"
+
+msgid "E783: Duplicate char in MAP entry"
+msgstr "E783: Повторено Ñимвол у елементі MAP"
+
+msgid "E1280: Illegal character in word"
+msgstr "E1280: Ðедозволений Ñимвол у Ñлові"
+
+#, c-format
msgid "Trailing text in %s line %d: %s"
msgstr "Зайвий текÑÑ‚ у %s у Ñ€Ñдку %d: %s"
@@ -4876,10 +6257,6 @@ msgid "E781: .sug file doesn't match .spl file: %s"
msgstr "E781: Файл .sug не відповідає файлу .spl: %s"
#, c-format
-msgid "E782: error while reading .sug file: %s"
-msgstr "E782: Помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ .sug: %s"
-
-#, c-format
msgid "Reading affix file %s..."
msgstr "ЧитаєтьÑÑ Ñ„Ð°Ð¹Ð» афікÑів %s..."
@@ -4904,7 +6281,7 @@ msgid ""
"Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line "
"%d"
msgstr ""
-"Ð’Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ COMPOUNDFORBIDFLAG піÑÐ»Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚Ñƒ PFX може дати неправильний "
+"Ð’Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ COMPOUNDFORBIDFLAG піÑÐ»Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚Ð° PFX може дати неправильний "
"результат у %s у Ñ€Ñдку %d"
#, c-format
@@ -4912,7 +6289,7 @@ msgid ""
"Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line "
"%d"
msgstr ""
-"Ð’Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ COMPOUNDPERMITFLAG піÑÐ»Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚Ñƒ PFX можу дати неправильний "
+"Ð’Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ COMPOUNDPERMITFLAG піÑÐ»Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚Ð° PFX можу дати неправильний "
"результат у %s у Ñ€Ñдку %d"
#, c-format
@@ -4946,11 +6323,11 @@ msgstr "Подвійний Ð°Ñ„Ñ–ÐºÑ Ñƒ %s у Ñ€Ñдку %d: %s"
#, c-format
msgid ""
-"Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGESTin %s "
+"Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s "
"line %d: %s"
msgstr ""
"ÐÑ„Ñ–ÐºÑ Ñ‚Ð°ÐºÐ¾Ð¶ вживаєтьÑÑ Ð´Ð»Ñ BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/"
-"NOSUGGEST у %s Ñ€Ñдок %d: %s"
+"NOSUGGEST у %s у Ñ€Ñдку %d: %s"
#, c-format
msgid "Expected Y or N in %s line %d: %s"
@@ -5141,8 +6518,96 @@ msgstr "Слово '%.*s' додано до %s"
msgid "E763: Word characters differ between spell files"
msgstr "E763: Символи у Ñлові відрізнÑютьÑÑ Ñƒ файлах орфографії"
-msgid "E783: duplicate char in MAP entry"
-msgstr "E783: Повторено Ñимвол у елементі MAP"
+msgid "Sorry, no suggestions"
+msgstr "Пробачте, немає пропозицій"
+
+#, c-format
+msgid "Sorry, only %<PRId64> suggestions"
+msgstr "Пробачте, тільки %<PRId64> пропозицій"
+
+#, c-format
+msgid "Change \"%.*s\" to:"
+msgstr "Замінити «%.*s» на:"
+
+#, c-format
+msgid " < \"%.*s\""
+msgstr " < «%.*s»"
+
+msgid "[Help]"
+msgstr "[Допомога]"
+
+msgid "[Preview]"
+msgstr "[ПереглÑд]"
+
+#, c-format
+msgid "E1400: Cannot mix positional and non-positional arguments: %s"
+msgstr "E1400: Ðеможливо змішувати позиційні й непозиційні аргументи: %s"
+
+#, c-format
+msgid "E1401: format argument %d unused in $-style format: %s"
+msgstr "E1401: аргумент %d формату не викориÑтано у $-форматуванні: %s"
+
+#, c-format
+msgid ""
+"E1402: Positional argument %d used as field width reused as different type: "
+"%s/%s"
+msgstr ""
+"E1402: Позиційний аргумент %d, що задає ширину полÑ, вжито Ñк інший тип: %s/"
+"%s"
+
+#, c-format
+msgid "E1403: Positional argument %d out of bounds: %s"
+msgstr "E1403: Позиційний аргумент %d поза межами: %s"
+
+#, c-format
+msgid "E1404: Positional argument %d type used inconsistently: %s/%s"
+msgstr "E1404: Тип позиційного аргументу %d вжито нерегулÑрно: %s/%s"
+
+#, c-format
+msgid "E1405: Invalid format specifier: %s"
+msgstr "E1405: Ðекоректний Ñпецифікатор формату: %s"
+
+msgid "unknown"
+msgstr "невідомо"
+
+msgid "int"
+msgstr "int"
+
+msgid "long int"
+msgstr "long int"
+
+msgid "long long int"
+msgstr "long long int"
+
+msgid "signed size_t"
+msgstr "signed size_t"
+
+msgid "unsigned int"
+msgstr "unsigned int"
+
+msgid "unsigned long int"
+msgstr "unsigned long int"
+
+msgid "unsigned long long int"
+msgstr "unsigned long long int"
+
+msgid "size_t"
+msgstr "size_t"
+
+msgid "pointer"
+msgstr "pointer"
+
+msgid "percent"
+msgstr "percent"
+
+msgid "char"
+msgstr "char"
+
+msgid "string"
+msgstr "string"
+
+msgid "float"
+msgstr "float"
msgid "E766: Insufficient arguments for printf()"
msgstr "E766: ÐедоÑтатньо аргументів Ð´Ð»Ñ printf()"
@@ -5157,14 +6622,24 @@ msgstr "E767: Забагато аргументів Ð´Ð»Ñ printf()"
msgid "E390: Illegal argument: %s"
msgstr "E390: Ðеправильний аргумент: %s"
+msgid "E395: Contains argument not accepted here"
+msgstr "E395: МіÑтить неприйнÑтні тут аргументи"
+
+msgid "E844: Invalid cchar value"
+msgstr "E844: Ðекоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ cchar"
+
+#, c-format
+msgid "E890: Trailing char after ']': %s]%s"
+msgstr "E890: Зайвий Ñимвол піÑÐ»Ñ ']': %s]%s"
+
msgid "No Syntax items defined for this buffer"
msgstr "Ð”Ð»Ñ Ð±ÑƒÑ„ÐµÑ€Ð° не визначено елементів ÑинтакÑиÑу"
msgid "'redrawtime' exceeded, syntax highlighting disabled"
-msgstr "'redrawtime' перевищено, підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ ÑинтакÑиÑу вимкнено"
+msgstr "'redrawtime' вичерпано, підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ ÑинтакÑиÑу вимкнено"
msgid "syntax iskeyword not set"
-msgstr "не вÑтановлено ÑинтакÑÐ¸Ñ iskeyword"
+msgstr "ÑинтакÑÐ¸Ñ iskeyword не вÑтановлено"
#, c-format
msgid "E391: No such syntax cluster: %s"
@@ -5225,12 +6700,6 @@ msgstr "; збіг "
msgid " line breaks"
msgstr " розриви Ñ€Ñдків"
-msgid "E395: contains argument not accepted here"
-msgstr "E395: МіÑтить неприйнÑтні тут аргументи"
-
-msgid "E844: invalid cchar value"
-msgstr "E844: Ðекоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ cchar"
-
msgid "E393: group[t]here not accepted here"
msgstr "E393: group[t]here тут неприйнÑтний"
@@ -5249,10 +6718,6 @@ msgid "E789: Missing ']': %s"
msgstr "E789: Пропущено ']': %s"
#, c-format
-msgid "E890: trailing char after ']': %s]%s"
-msgstr "E890: Зайвий Ñимвол піÑÐ»Ñ ']': %s]%s"
-
-#, c-format
msgid "E398: Missing '=': %s"
msgstr "E398: Пропущено `=': %s"
@@ -5311,25 +6776,31 @@ msgid ""
msgstr ""
" ВСЬОГО К-ТЬ СПІВП. ÐÐЙПОВІЛ. СЕРЕДÐ. ÐÐЗВРШÐБЛОÐ"
-msgid "E555: at bottom of tag stack"
-msgstr "E555: Кінець Ñтеку міток"
+msgid "E73: Tag stack empty"
+msgstr "E73: Стек міток порожній"
-msgid "E556: at top of tag stack"
-msgstr "E556: Вершина Ñтеку міток"
+#, c-format
+msgid "E426: Tag not found: %s"
+msgstr "E426: Мітку не знайдено: %s"
-msgid "E986: cannot modify the tag stack within tagfunc"
+msgid "E555: At bottom of tag stack"
+msgstr "E555: Ðа дні Ñтеку міток"
+
+msgid "E556: At top of tag stack"
+msgstr "E556: Ðа вершині Ñтеку міток"
+
+msgid "E986: Cannot modify the tag stack within tagfunc"
msgstr "E986: Ðе можна змінювати Ñтек міток у tagfunc"
-msgid "E987: invalid return value from tagfunc"
-msgstr "E987: Ðекоректне повернене Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· tagfunc"
+msgid "E987: Invalid return value from tagfunc"
+msgstr "E987: Ðекоректне значеннÑ, що повертаєтьÑÑ Ð· tagfunc"
+
+msgid "E1299: Window unexpectedly closed while searching for tags"
+msgstr "E1299: Вікно неÑподівано закрилоÑÑ Ð¿Ñ–Ð´ Ñ‡Ð°Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ міток"
msgid "E425: Cannot go before first matching tag"
msgstr "E425: Це вже найперша відповідна мітка"
-#, c-format
-msgid "E426: tag not found: %s"
-msgstr "E426: Мітку не знайдено: %s"
-
msgid "E427: There is only one matching tag"
msgstr "E427: Лише одна відповідна мітка"
@@ -5368,23 +6839,23 @@ msgstr ""
" # ДО мітки З Ñ€Ñдка у файлі/текÑті"
#, c-format
-msgid "Searching tags file %s"
-msgstr "ШукаєтьÑÑ Ñƒ файлі теґів %s"
-
-#, c-format
msgid "E431: Format error in tags file \"%s\""
-msgstr "E431: Помилка формату у файлі теґів «%s»"
+msgstr "E431: Помилка формату у файлі міток «%s»"
#, c-format
msgid "Before byte %<PRId64>"
msgstr "Перед байтом %<PRId64>"
#, c-format
+msgid "Searching tags file %s"
+msgstr "ШукаєтьÑÑ Ñƒ файлі міток %s"
+
+#, c-format
msgid "E432: Tags file not sorted: %s"
-msgstr "E432: Файл теґів не впорÑдкований: %s"
+msgstr "E432: Файл міток не впорÑдкований: %s"
msgid "E433: No tags file"
-msgstr "E433: Ðемає файлу теґів"
+msgstr "E433: Ðемає файлу міток"
msgid "E434: Can't find tag pattern"
msgstr "E434: Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ зразок мітки"
@@ -5396,9 +6867,35 @@ msgstr "E435: Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ мітку, тільки прип
msgid "Duplicate field name: %s"
msgstr "Ðазва Ð¿Ð¾Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑŽÑ”Ñ‚ÑŒÑÑ: %s"
+msgid ""
+"E856: \"assert_fails()\" second argument must be a string or a list with one "
+"or two strings"
+msgstr ""
+"E856: Другий аргумент «assert_fails()» має бути текÑтовим Ñ€Ñдком чи ÑпиÑок з "
+"одним-двома Ñимвольними Ñ€Ñдками"
+
+msgid "E1115: \"assert_fails()\" fourth argument must be a number"
+msgstr "E1115: Четвертий аргумент «assert_fails()» має бути чиÑлом"
+
+msgid "E1116: \"assert_fails()\" fifth argument must be a string"
+msgstr "E1116: П’Ñтий аргумент «assert_fails()» має бути текÑтовим Ñ€Ñдком"
+
+msgid "E1142: Calling test_garbagecollect_now() while v:testing is not set"
+msgstr "E1142: Виклик test_garbagecollect_now() поки не вÑтановлено v:testing"
+
msgid "Beep!"
msgstr "Дзень!"
+msgid "E439: Undo list corrupt"
+msgstr "E439: ІÑторію змін пошкоджено"
+
+msgid "E440: Undo line missing"
+msgstr "E440: ВідÑутній Ñ€Ñдок в Ñ–Ñторії змін"
+
+#, c-format
+msgid "E829: Write error in undo file: %s"
+msgstr "E829: Помилка запиÑу в файл Ñ–Ñторії: %s"
+
msgid "E881: Line count changed unexpectedly"
msgstr "E881: КількіÑть Ñ€Ñдків неÑподівано змінилаÑÑ"
@@ -5419,7 +6916,7 @@ msgstr "Ðе вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати файл Ñ–Ñторії у жодн
#, c-format
msgid "Will not overwrite with undo file, cannot read: %s"
-msgstr "Will not overwrite with undo file, cannot read: %s"
+msgstr "Ðе можна перезапиÑати з файлу ÑкаÑувань, не можна прочитати: %s"
#, c-format
msgid "Will not overwrite, this is not an undo file: %s"
@@ -5433,10 +6930,6 @@ msgid "Writing undo file: %s"
msgstr "ЗапиÑуєтьÑÑ Ñ„Ð°Ð¹Ð» Ñ–Ñторії: %s"
#, c-format
-msgid "E829: write error in undo file: %s"
-msgstr "E829: Помилка запиÑу у файлі Ñ–Ñторії: %s"
-
-#, c-format
msgid "Not reading undo file, owner differs: %s"
msgstr "Файл Ñ–Ñторії прочитано не буде, влаÑник інший: %s"
@@ -5504,6 +6997,13 @@ msgstr "піÑлÑ"
msgid "before"
msgstr "перед"
+#, c-format
+msgid "%<PRId64> second ago"
+msgid_plural "%<PRId64> seconds ago"
+msgstr[0] "%<PRId64> Ñекунду тому"
+msgstr[1] "%<PRId64> Ñекунди тому"
+msgstr[2] "%<PRId64> Ñекунд тому"
+
msgid "Nothing to undo"
msgstr "Ðемає нічого ÑкаÑовувати"
@@ -5513,30 +7013,74 @@ msgstr "номер зміни Ñ‡Ð°Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð¾"
msgid "E790: undojoin is not allowed after undo"
msgstr "E790: Ðе можна виконати undojoin піÑÐ»Ñ undo"
-msgid "E439: undo list corrupt"
-msgstr "E439: СпиÑок ÑкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÐºÐ¾Ð´Ð¶ÐµÐ½Ð¾"
+#, c-format
+msgid "E179: Argument required for %s"
+msgstr "E179: Потрібен аргумент Ð´Ð»Ñ %s"
+
+#, c-format
+msgid "E184: No such user-defined command: %s"
+msgstr "E184: Команду кориÑтувача не знайдено: %s"
-msgid "E440: undo line missing"
-msgstr "E440: ВідÑутній Ñ€Ñдок ÑкаÑуваннÑ"
+msgid "E1208: -complete used without allowing arguments"
+msgstr "E1208: -complete вжито без дозволених аргументів"
-msgid ""
-"\n"
-"\n"
-"Features: "
-msgstr ""
-"\n"
-"\n"
-"ХарактериÑтики: "
+#, c-format
+msgid "E1237: No such user-defined command in current buffer: %s"
+msgstr "E1237: Ðемає такої команди кориÑтувача у цьому буфері: %s"
msgid ""
"\n"
-"Compiled "
+" Name Args Address Complete Definition"
msgstr ""
"\n"
-"Скомпілював "
+" Ðазва Ðрг. ÐдреÑа Ð”Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð’Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ"
+
+msgid "No user-defined commands found"
+msgstr "Ðе знайдено команд кориÑтувача"
-msgid "by "
-msgstr " "
+#, c-format
+msgid "E180: Invalid address type value: %s"
+msgstr "E180: Ðеправильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‚Ð¸Ð¿Ñƒ адреÑи: %s"
+
+#, c-format
+msgid "E180: Invalid complete value: %s"
+msgstr "E180: Ðеправильне доповненнÑ: %s"
+
+msgid "E468: Completion argument only allowed for custom completion"
+msgstr "E468: Ðргумент дозволений тільки Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувацького доповненнÑ"
+
+msgid "E467: Custom completion requires a function argument"
+msgstr "E467: КориÑтувацьке Ð´Ð¾Ð¿Ð¾Ð²Ð½ÐµÐ½Ð½Ñ Ð²Ð¸Ð¼Ð°Ð³Ð°Ñ” аргумент-функцію"
+
+msgid "E175: No attribute specified"
+msgstr "E175: Ðе вказано атрибутів"
+
+msgid "E176: Invalid number of arguments"
+msgstr "E176: Ðеправильна кількіÑть аргументів"
+
+msgid "E177: Count cannot be specified twice"
+msgstr "E177: Лічильник не може бути вказано двічі"
+
+msgid "E178: Invalid default value for count"
+msgstr "E178: Ðеправильне початкове Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð»Ñ–Ñ‡Ð¸Ð»ÑŒÐ½Ð¸ÐºÐ°"
+
+#, c-format
+msgid "E181: Invalid attribute: %s"
+msgstr "E181: Ðеправильний атрибут: %s"
+
+#, c-format
+msgid "E174: Command already exists: add ! to replace it: %s"
+msgstr "E174: Команда вже Ñ–Ñнує, ! щоб замінити Ñ—Ñ—: %s"
+
+msgid "E182: Invalid command name"
+msgstr "E182: Ðеправильна назва команди"
+
+msgid "E183: User defined commands must start with an uppercase letter"
+msgstr "E183: Команди кориÑтувача повинні починатиÑÑ Ð· великої літери"
+
+msgid "E841: Reserved name, cannot be used for user defined command"
+msgstr ""
+"E841: Зарезервована назва, не можна викориÑтати Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувацької команди"
msgid " system vimrc file: \""
msgstr " ÑиÑтемний vimrc: \""
@@ -5562,6 +7106,10 @@ msgstr ":q<Enter> вихід з Vim "
msgid "type :help<Enter> for help "
msgstr ":help<Enter> щоб отримати допомогу "
+#, c-format
+msgid "type :help news<Enter> to see changes in v%s.%s"
+msgstr ":help news<Enter> щоб побачити зміни у v%s.%s"
+
msgid "Help poor children in Uganda!"
msgstr "Допоможіть Ñиротам з Уганди!"
@@ -5580,9 +7128,6 @@ msgstr ":help sponsor<Enter> подальша Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ "
msgid "type :help register<Enter> for information "
msgstr ":help register<Enter> подальша Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ "
-msgid "menu Help->Sponsor/Register for information "
-msgstr "меню Допомога->СпонÑор/РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð¸Ñ†Ñ– "
-
#, c-format
msgid "E15: Invalid control character present in input: %.*s"
msgstr "E15: Ðекоректні керівні Ñимволи на вході: %.*s"
@@ -5750,17 +7295,23 @@ msgstr "E15: Бракує правої фігурної дужки у лÑмбд
msgid "E109: Missing ':' after '?': %.*s"
msgstr "E109: Бракує ':' піÑÐ»Ñ '?': %.*s"
+msgid "E1159: Cannot split a window when closing the buffer"
+msgstr "E1159: Ðеможливо розщепити вікно при закритті буфера"
+
msgid "Already only one window"
msgstr "Це вже єдине вікно"
msgid "E441: There is no preview window"
msgstr "E441: Ðемає вікна переглÑду"
+msgid "E242: Can't split a window while closing another"
+msgstr "E242: Ðеможливо розщепити вікно, поки закриваєтьÑÑ Ñ–Ð½ÑˆÐµ"
+
msgid "E442: Can't split topleft and botright at the same time"
-msgstr "E442: Ðе вдалоÑÑ Ð¾Ð´Ð½Ð¾Ñ‡Ð°Ñно розбити topleft Ñ– botright"
+msgstr "E442: Ðе вдалоÑÑ Ð¾Ð´Ð½Ð¾Ñ‡Ð°Ñно розщепити topleft Ñ– botright"
msgid "E443: Cannot rotate when another window is split"
-msgstr "E443: Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñтити вікно, заважають інші"
+msgstr "E443: Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñтити по колу, інше вікно розщеплене"
msgid "E444: Cannot close last window"
msgstr "E444: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ оÑтаннє вікно"
@@ -5773,5 +7324,3 @@ msgstr "E445: У іншому вікні є зміни"
msgid "E446: No file name under cursor"
msgstr "E446: Ðемає назви файлу над курÑором"
-
-
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 245ce87865..f009722357 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -1,25 +1,21 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file popupmenu.c
///
/// Popup menu (PUM)
#include <assert.h>
-#include <limits.h>
#include <stdbool.h>
#include <string.h>
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/getchar.h"
+#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@@ -32,13 +28,14 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static pumitem_T *pum_array = NULL; // items of displayed pum
@@ -113,8 +110,6 @@ static void pum_compute_size(void)
void pum_display(pumitem_T *array, int size, int selected, bool array_changed, int cmd_startcol)
{
int context_lines;
- int above_row;
- int below_row;
int redo_count = 0;
int pum_win_row;
int cursor_col;
@@ -134,8 +129,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_is_visible = true;
pum_is_drawn = true;
validate_cursor_col();
- above_row = 0;
- below_row = cmdline_row;
+ int above_row = 0;
+ int below_row = cmdline_row;
// wildoptions=pum
if (State == MODE_CMDLINE) {
@@ -146,7 +141,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// anchor position: the start of the completed word
pum_win_row = curwin->w_wrow;
if (pum_rl) {
- cursor_col = curwin->w_width - curwin->w_wcol - 1;
+ cursor_col = curwin->w_width_inner - curwin->w_wcol - 1;
} else {
cursor_col = curwin->w_wcol;
}
@@ -167,10 +162,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
Array arr = arena_array(&arena, (size_t)size);
for (int i = 0; i < size; i++) {
Array item = arena_array(&arena, 4);
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_text)));
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_kind)));
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_extra)));
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_info)));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_text));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_kind));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_extra));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_info));
ADD_C(arr, ARRAY_OBJ(item));
}
ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col,
@@ -200,6 +195,21 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
}
+ int min_row = 0;
+ int min_col = 0;
+ int max_col = Columns;
+ int win_start_col = curwin->w_wincol;
+ int win_end_col = W_ENDCOL(curwin);
+ if (!(State & MODE_CMDLINE) && ui_has(kUIMultigrid)) {
+ above_row -= curwin->w_winrow;
+ below_row = MAX(below_row - curwin->w_winrow, curwin->w_grid.rows);
+ min_row = -curwin->w_winrow;
+ min_col = -curwin->w_wincol;
+ max_col = MAX(Columns - curwin->w_wincol, curwin->w_grid.cols);
+ win_start_col = 0;
+ win_end_col = curwin->w_grid.cols;
+ }
+
// Figure out the size and position of the pum.
if (size < PUM_DEF_HEIGHT) {
pum_height = size;
@@ -207,7 +217,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_height = PUM_DEF_HEIGHT;
}
- if ((p_ph > 0) && (pum_height > p_ph)) {
+ if (p_ph > 0 && pum_height > p_ph) {
pum_height = (int)p_ph;
}
@@ -230,15 +240,15 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
}
- if (pum_win_row >= size + context_lines) {
+ if (pum_win_row - min_row >= size + context_lines) {
pum_row = pum_win_row - size - context_lines;
pum_height = size;
} else {
- pum_row = 0;
- pum_height = pum_win_row - context_lines;
+ pum_row = min_row;
+ pum_height = pum_win_row - min_row - context_lines;
}
- if ((p_ph > 0) && (pum_height > p_ph)) {
+ if (p_ph > 0 && pum_height > p_ph) {
pum_row += pum_height - (int)p_ph;
pum_height = (int)p_ph;
}
@@ -266,13 +276,13 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_height = size;
}
- if ((p_ph > 0) && (pum_height > p_ph)) {
+ if (p_ph > 0 && pum_height > p_ph) {
pum_height = (int)p_ph;
}
}
// don't display when we only have room for one line
- if ((pum_height < 1) || ((pum_height == 1) && (size > 1))) {
+ if (pum_height < 1 || (pum_height == 1 && size > 1)) {
return;
}
@@ -305,22 +315,23 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
def_width = max_width;
}
- if ((((cursor_col < Columns - p_pw) || (cursor_col < Columns - max_width))
- && !pum_rl)
- || (pum_rl && ((cursor_col > p_pw) || (cursor_col > max_width)))) {
+ if (((cursor_col < max_col - p_pw
+ || cursor_col < max_col - max_width) && !pum_rl)
+ || (pum_rl && (cursor_col - min_col > p_pw
+ || cursor_col - min_col > max_width))) {
// align pum with "cursor_col"
pum_col = cursor_col;
// start with the maximum space available
if (pum_rl) {
- pum_width = pum_col - pum_scrollbar + 1;
+ pum_width = pum_col - min_col - pum_scrollbar + 1;
} else {
- assert(Columns - pum_col - pum_scrollbar >= 0);
- pum_width = Columns - pum_col - pum_scrollbar;
+ assert(max_col - pum_col - pum_scrollbar >= 0);
+ pum_width = max_col - pum_col - pum_scrollbar;
}
- if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1)
- && (pum_width > p_pw)) {
+ if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
+ && pum_width > p_pw) {
// the width is more than needed for the items, make it
// narrower
pum_width = max_width + pum_kind_width + pum_extra_width + 1;
@@ -328,41 +339,42 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
if (pum_width < p_pw) {
pum_width = (int)p_pw;
}
- } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl)
- || (pum_rl && (cursor_col < Columns - p_pw
- || cursor_col < Columns - max_width))) {
+ } else if (((cursor_col - min_col > p_pw
+ || cursor_col - min_col > max_width) && !pum_rl)
+ || (pum_rl && (cursor_col < max_col - p_pw
+ || cursor_col < max_col - max_width))) {
// align pum edge with "cursor_col"
- if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
+ if (pum_rl && win_end_col < max_width + pum_scrollbar + 1) {
pum_col = cursor_col + max_width + pum_scrollbar + 1;
- if (pum_col >= Columns) {
- pum_col = Columns - 1;
+ if (pum_col >= max_col) {
+ pum_col = max_col - 1;
}
} else if (!pum_rl) {
- if (curwin->w_wincol > Columns - max_width - pum_scrollbar
+ if (win_start_col > max_col - max_width - pum_scrollbar
&& max_width <= p_pw) {
// use full width to end of the screen
- pum_col = Columns - max_width - pum_scrollbar;
- if (pum_col < 0) {
- pum_col = 0;
+ pum_col = max_col - max_width - pum_scrollbar;
+ if (pum_col < min_col) {
+ pum_col = min_col;
}
}
}
if (pum_rl) {
- pum_width = pum_col - pum_scrollbar + 1;
+ pum_width = pum_col - min_col - pum_scrollbar + 1;
} else {
- pum_width = Columns - pum_col - pum_scrollbar;
+ pum_width = max_col - pum_col - pum_scrollbar;
}
if (pum_width < p_pw) {
pum_width = (int)p_pw;
if (pum_rl) {
- if (pum_width > pum_col) {
- pum_width = pum_col;
+ if (pum_width > pum_col - min_col) {
+ pum_width = pum_col - min_col;
}
} else {
- if (pum_width >= Columns - pum_col) {
- pum_width = Columns - pum_col - 1;
+ if (pum_width >= max_col - pum_col) {
+ pum_width = max_col - pum_col - 1;
}
}
} else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
@@ -373,26 +385,23 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
}
}
- } else if (Columns < def_width) {
+ } else if (max_col - min_col < def_width) {
// not enough room, will use what we have
if (pum_rl) {
- assert(Columns - 1 >= INT_MIN);
- pum_col = Columns - 1;
+ pum_col = max_col - 1;
} else {
- pum_col = 0;
+ pum_col = min_col;
}
- pum_width = Columns - 1;
+ pum_width = max_col - min_col - 1;
} else {
if (max_width > p_pw) {
// truncate
max_width = (int)p_pw;
}
-
if (pum_rl) {
- pum_col = max_width - 1;
+ pum_col = min_col + max_width - 1;
} else {
- assert(Columns - max_width >= 0);
- pum_col = Columns - max_width;
+ pum_col = max_col - max_width;
}
pum_width = max_width - pum_scrollbar;
}
@@ -400,8 +409,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// Set selected item and redraw. If the window size changed need to redo
// the positioning. Limit this to two times, when there is not much
// room the window size will keep changing.
- } while (pum_set_selected(selected, redo_count) && (++redo_count <= 2));
+ } while (pum_set_selected(selected, redo_count) && ++redo_count <= 2);
+ pum_grid.zindex = (State == MODE_CMDLINE) ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu;
pum_redraw();
}
@@ -409,45 +419,47 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
void pum_redraw(void)
{
int row = 0;
- int grid_col;
- int attr_norm = win_hl_attr(curwin, HLF_PNI);
- int attr_select = win_hl_attr(curwin, HLF_PSI);
int attr_scroll = win_hl_attr(curwin, HLF_PSB);
int attr_thumb = win_hl_attr(curwin, HLF_PST);
- int attr;
- int i;
- int idx;
- char *s;
char *p = NULL;
- int totwidth, width, w;
int thumb_pos = 0;
int thumb_height = 1;
- int round;
int n;
+#define HA(hlf) (win_hl_attr(curwin, (hlf)))
+ // "word" "kind" "extra text"
+ const int attrsNorm[3] = { HA(HLF_PNI), HA(HLF_PNK), HA(HLF_PNX) };
+ const int attrsSel[3] = { HA(HLF_PSI), HA(HLF_PSK), HA(HLF_PSX) };
+#undef HA
+
int grid_width = pum_width;
int col_off = 0;
bool extra_space = false;
if (pum_rl) {
- col_off = pum_width;
- if (pum_col < curwin->w_wincol + curwin->w_width - 1) {
+ col_off = pum_width - 1;
+ assert(!(State & MODE_CMDLINE));
+ int win_end_col = ui_has(kUIMultigrid) ? curwin->w_grid.cols : W_ENDCOL(curwin);
+ if (pum_col < win_end_col - 1) {
+ grid_width += 1;
+ extra_space = true;
+ }
+ } else {
+ int min_col = (!(State & MODE_CMDLINE) && ui_has(kUIMultigrid)) ? -curwin->w_wincol : 0;
+ if (pum_col > min_col) {
grid_width += 1;
+ col_off = 1;
extra_space = true;
}
- } else if (pum_col > 0) {
- grid_width += 1;
- col_off = 1;
- extra_space = true;
}
if (pum_scrollbar > 0) {
grid_width++;
+ if (pum_rl) {
+ col_off++;
+ }
}
grid_assign_handle(&pum_grid);
- pum_grid.zindex = ((State == MODE_CMDLINE)
- ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu);
-
bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col - col_off,
pum_height, grid_width, false, true);
bool invalid_grid = moved || pum_invalid;
@@ -484,42 +496,42 @@ void pum_redraw(void)
/ (pum_size - pum_height);
}
- for (i = 0; i < pum_height; i++) {
- idx = i + pum_first;
- attr = (idx == pum_selected) ? attr_select : attr_norm;
+ for (int i = 0; i < pum_height; i++) {
+ int idx = i + pum_first;
+ const int *const attrs = (idx == pum_selected) ? attrsSel : attrsNorm;
+ int attr = attrs[0]; // start with "word" highlight
- grid_puts_line_start(&pum_grid, row);
+ grid_line_start(&pum_grid, row);
// prepend a space if there is room
if (extra_space) {
if (pum_rl) {
- grid_putchar(&pum_grid, ' ', row, col_off + 1, attr);
+ grid_line_puts(col_off + 1, " ", 1, attr);
} else {
- grid_putchar(&pum_grid, ' ', row, col_off - 1, attr);
+ grid_line_puts(col_off - 1, " ", 1, attr);
}
}
// Display each entry, use two spaces for a Tab.
- // Do this 3 times: For the main text, kind and extra info
- grid_col = col_off;
- totwidth = 0;
-
- for (round = 1; round <= 3; round++) {
- width = 0;
- s = NULL;
+ // Do this 3 times:
+ // 0 - main text
+ // 1 - kind
+ // 2 - extra info
+ int grid_col = col_off;
+ int totwidth = 0;
+
+ for (int round = 0; round < 3; round++) {
+ attr = attrs[round];
+ int width = 0;
+ char *s = NULL;
switch (round) {
+ case 0:
+ p = pum_array[idx].pum_text; break;
case 1:
- p = pum_array[idx].pum_text;
- break;
-
+ p = pum_array[idx].pum_kind; break;
case 2:
- p = pum_array[idx].pum_kind;
- break;
-
- case 3:
- p = pum_array[idx].pum_extra;
- break;
+ p = pum_array[idx].pum_extra; break;
}
if (p != NULL) {
@@ -527,7 +539,7 @@ void pum_redraw(void)
if (s == NULL) {
s = p;
}
- w = ptr2cells(p);
+ int w = ptr2cells(p);
if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) {
// Display the text that fits or comes before a Tab.
@@ -562,13 +574,13 @@ void pum_redraw(void)
size++;
}
}
- grid_puts_len(&pum_grid, rt, (int)strlen(rt), row, grid_col - size + 1, attr);
+ grid_line_puts(grid_col - size + 1, rt, -1, attr);
xfree(rt_start);
xfree(st);
grid_col -= width;
} else {
- // use grid_puts_len() to truncate the text
- grid_puts(&pum_grid, st, row, grid_col, attr);
+ // use grid_line_puts() to truncate the text
+ grid_line_puts(grid_col, st, -1, attr);
xfree(st);
grid_col += width;
}
@@ -579,11 +591,10 @@ void pum_redraw(void)
// Display two spaces for a Tab.
if (pum_rl) {
- grid_puts_len(&pum_grid, " ", 2, row, grid_col - 1,
- attr);
+ grid_line_puts(grid_col - 1, " ", 2, attr);
grid_col -= 2;
} else {
- grid_puts_len(&pum_grid, " ", 2, row, grid_col, attr);
+ grid_line_puts(grid_col, " ", 2, attr);
grid_col += 2;
}
totwidth += 2;
@@ -596,17 +607,17 @@ void pum_redraw(void)
}
}
- if (round > 1) {
+ if (round > 0) {
n = pum_kind_width + 1;
} else {
n = 1;
}
// Stop when there is nothing more to display.
- if ((round == 3)
- || ((round == 2)
- && (pum_array[idx].pum_extra == NULL))
+ if ((round == 2)
|| ((round == 1)
+ && (pum_array[idx].pum_extra == NULL))
+ || ((round == 0)
&& (pum_array[idx].pum_kind == NULL)
&& (pum_array[idx].pum_extra == NULL))
|| (pum_base_width + n >= pum_width)) {
@@ -614,37 +625,31 @@ void pum_redraw(void)
}
if (pum_rl) {
- grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1,
- grid_col + 1, ' ', ' ', attr);
+ grid_line_fill(col_off - pum_base_width - n + 1, grid_col + 1, ' ', attr);
grid_col = col_off - pum_base_width - n + 1;
} else {
- grid_fill(&pum_grid, row, row + 1, grid_col,
- col_off + pum_base_width + n, ' ', ' ', attr);
+ grid_line_fill(grid_col, col_off + pum_base_width + n, ' ', attr);
grid_col = col_off + pum_base_width + n;
}
totwidth = pum_base_width + n;
}
if (pum_rl) {
- grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, grid_col + 1,
- ' ', ' ', attr);
+ grid_line_fill(col_off - pum_width + 1, grid_col + 1, ' ', attr);
} else {
- grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_width, ' ', ' ',
- attr);
+ grid_line_fill(grid_col, col_off + pum_width, ' ', attr);
}
if (pum_scrollbar > 0) {
if (pum_rl) {
- grid_putchar(&pum_grid, ' ', row, col_off - pum_width,
- i >= thumb_pos && i < thumb_pos + thumb_height
- ? attr_thumb : attr_scroll);
+ grid_line_puts(col_off - pum_width, " ", 1,
+ i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll);
} else {
- grid_putchar(&pum_grid, ' ', row, col_off + pum_width,
- i >= thumb_pos && i < thumb_pos + thumb_height
- ? attr_thumb : attr_scroll);
+ grid_line_puts(col_off + pum_width, " ", 1,
+ i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll);
}
}
- grid_puts_line_flush(false);
+ grid_line_flush();
row++;
}
}
@@ -725,7 +730,6 @@ static bool pum_set_selected(int n, int repeat)
&& (vim_strchr(p_cot, 'p') != NULL)) {
win_T *curwin_save = curwin;
tabpage_T *curtab_save = curtab;
- int res = OK;
// Open a preview window. 3 lines by default. Prefer
// 'previewheight' if set and smaller.
@@ -744,6 +748,7 @@ static bool pum_set_selected(int n, int repeat)
g_do_tagpreview = 0;
if (curwin->w_p_pvw) {
+ int res = OK;
if (!resized
&& (curbuf->b_nwindows == 1)
&& (curbuf->b_fname == NULL)
@@ -751,7 +756,7 @@ static bool pum_set_selected(int n, int repeat)
&& (curbuf->b_p_bh[0] == 'w')) {
// Already a "wipeout" buffer, make it empty.
while (!buf_is_empty(curbuf)) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
} else {
// Don't want to sync undo in the current buffer.
@@ -762,20 +767,19 @@ static bool pum_set_selected(int n, int repeat)
if (res == OK) {
// Edit a new, empty buffer. Set options for a "wipeout"
// buffer.
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bl", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL);
- set_option_value_give_err("diff", 0L, NULL, OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bl", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL);
+ set_option_value_give_err("diff", BOOLEAN_OPTVAL(false), OPT_LOCAL);
}
}
if (res == OK) {
- char *p, *e;
linenr_T lnum = 0;
- for (p = pum_array[pum_selected].pum_info; *p != NUL;) {
- e = vim_strchr(p, '\n');
+ for (char *p = pum_array[pum_selected].pum_info; *p != NUL;) {
+ char *e = vim_strchr(p, '\n');
if (e == NULL) {
ml_append(lnum++, p, 0, false);
break;
@@ -971,32 +975,44 @@ void pum_set_event_info(dict_T *dict)
static void pum_position_at_mouse(int min_width)
{
+ int min_row = 0;
+ int max_row = Rows;
+ int max_col = Columns;
+ if (mouse_grid > 1) {
+ win_T *wp = get_win_by_grid_handle(mouse_grid);
+ if (wp != NULL) {
+ min_row = -wp->w_winrow;
+ max_row = MAX(Rows - wp->w_winrow, wp->w_grid.rows);
+ max_col = MAX(Columns - wp->w_wincol, wp->w_grid.cols);
+ }
+ }
pum_anchor_grid = mouse_grid;
- if (Rows - mouse_row > pum_size) {
+ if (max_row - mouse_row > pum_size) {
// Enough space below the mouse row.
pum_above = false;
pum_row = mouse_row + 1;
- if (pum_height > Rows - pum_row) {
- pum_height = Rows - pum_row;
+ if (pum_height > max_row - pum_row) {
+ pum_height = max_row - pum_row;
}
} else {
// Show above the mouse row, reduce height if it does not fit.
pum_above = true;
pum_row = mouse_row - pum_size;
- if (pum_row < 0) {
- pum_height += pum_row;
- pum_row = 0;
+ if (pum_row < min_row) {
+ pum_height += pum_row - min_row;
+ pum_row = min_row;
}
}
- if (Columns - mouse_col >= pum_base_width || Columns - mouse_col > min_width) {
+ if (max_col - mouse_col >= pum_base_width
+ || max_col - mouse_col > min_width) {
// Enough space to show at mouse column.
pum_col = mouse_col;
} else {
// Not enough space, right align with window.
- pum_col = Columns - (pum_base_width > min_width ? min_width : pum_base_width);
+ pum_col = max_col - (pum_base_width > min_width ? min_width : pum_base_width);
}
- pum_width = Columns - pum_col;
+ pum_width = max_col - pum_col;
if (pum_width > pum_base_width + 1) {
pum_width = pum_base_width + 1;
}
@@ -1053,7 +1069,7 @@ void pum_show_popupmenu(vimmenu_T *menu)
// When there are only Terminal mode menus, using "popup Edit" results in
// pum_size being zero.
if (pum_size <= 0) {
- emsg(e_menuothermode);
+ emsg(_(e_menu_only_exists_in_another_mode));
return;
}
@@ -1087,12 +1103,12 @@ void pum_show_popupmenu(vimmenu_T *menu)
ui_call_option_set(STATIC_CSTR_AS_STRING("mousemoveevent"), BOOLEAN_OBJ(true));
}
- for (;;) {
+ while (true) {
pum_is_visible = true;
pum_is_drawn = true;
+ pum_grid.zindex = kZIndexCmdlinePopupMenu; // show above cmdline area #23275
pum_redraw();
setcursor_mayforce(true);
- ui_flush();
int c = vgetc();
@@ -1156,8 +1172,15 @@ void pum_make_popup(const char *path_name, int use_mouse_pos)
if (!use_mouse_pos) {
// Hack: set mouse position at the cursor so that the menu pops up
// around there.
- mouse_row = curwin->w_winrow + curwin->w_wrow;
- mouse_col = curwin->w_wincol + curwin->w_wcol;
+ mouse_row = curwin->w_grid.row_offset + curwin->w_wrow;
+ mouse_col = curwin->w_grid.col_offset + curwin->w_wcol;
+ if (ui_has(kUIMultigrid)) {
+ mouse_grid = curwin->w_grid.target->handle;
+ } else if (curwin->w_grid.target != &default_grid) {
+ mouse_grid = 0;
+ mouse_row += curwin->w_winrow;
+ mouse_col += curwin->w_wincol;
+ }
}
vimmenu_T *menu = menu_find(path_name);
diff --git a/src/nvim/popupmenu.h b/src/nvim/popupmenu.h
index 08b791c509..24a3f8713a 100644
--- a/src/nvim/popupmenu.h
+++ b/src/nvim/popupmenu.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_POPUPMENU_H
-#define NVIM_POPUPMENU_H
+#pragma once
#include <stdbool.h>
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/macros_defs.h"
+#include "nvim/menu_defs.h" // IWYU pragma: keep
/// Used for popup menu items.
typedef struct {
@@ -16,7 +15,7 @@ typedef struct {
char *pum_info; // extra info
} pumitem_T;
-EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT);
+EXTERN ScreenGrid pum_grid INIT( = SCREEN_GRID_INIT);
/// state for pum_ext_select_item.
EXTERN struct {
@@ -29,4 +28,3 @@ EXTERN struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "popupmenu.h.generated.h"
#endif
-#endif // NVIM_POPUPMENU_H
diff --git a/src/nvim/pos.h b/src/nvim/pos_defs.h
index 1b7e6273fd..98a1762a5c 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_POS_H
-#define NVIM_POS_H
+#pragma once
#include <inttypes.h>
@@ -39,5 +38,3 @@ typedef struct {
linenr_T lnum; ///< line number
colnr_T col; ///< column number
} lpos_T;
-
-#endif // NVIM_POS_H
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index fd024f2d38..53ff57dacb 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <math.h>
#include <stdbool.h>
@@ -9,14 +6,16 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -24,14 +23,13 @@
#include "nvim/keycodes.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/runtime.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "profile.c.generated.h"
@@ -232,23 +230,23 @@ void profile_reset(void)
{
// Reset sourced files.
for (int id = 1; id <= script_items.ga_len; id++) {
- scriptitem_T *si = &SCRIPT_ITEM(id);
+ scriptitem_T *si = SCRIPT_ITEM(id);
if (si->sn_prof_on) {
- si->sn_prof_on = false;
- si->sn_pr_force = false;
- si->sn_pr_child = profile_zero();
- si->sn_pr_nest = 0;
- si->sn_pr_count = 0;
- si->sn_pr_total = profile_zero();
- si->sn_pr_self = profile_zero();
- si->sn_pr_start = profile_zero();
- si->sn_pr_children = profile_zero();
+ si->sn_prof_on = false;
+ si->sn_pr_force = false;
+ si->sn_pr_child = profile_zero();
+ si->sn_pr_nest = 0;
+ si->sn_pr_count = 0;
+ si->sn_pr_total = profile_zero();
+ si->sn_pr_self = profile_zero();
+ si->sn_pr_start = profile_zero();
+ si->sn_pr_children = profile_zero();
ga_clear(&si->sn_prl_ga);
- si->sn_prl_start = profile_zero();
+ si->sn_prl_start = profile_zero();
si->sn_prl_children = profile_zero();
- si->sn_prl_wait = profile_zero();
- si->sn_prl_idx = -1;
- si->sn_prl_execed = 0;
+ si->sn_prl_wait = profile_zero();
+ si->sn_prl_idx = -1;
+ si->sn_prl_execed = 0;
}
}
@@ -262,22 +260,22 @@ void profile_reset(void)
todo--;
ufunc_T *uf = HI2UF(hi);
if (uf->uf_prof_initialized) {
- uf->uf_profiling = 0;
- uf->uf_tm_count = 0;
- uf->uf_tm_total = profile_zero();
- uf->uf_tm_self = profile_zero();
- uf->uf_tm_children = profile_zero();
+ uf->uf_profiling = 0;
+ uf->uf_tm_count = 0;
+ uf->uf_tm_total = profile_zero();
+ uf->uf_tm_self = profile_zero();
+ uf->uf_tm_children = profile_zero();
for (int i = 0; i < uf->uf_lines.ga_len; i++) {
uf->uf_tml_count[i] = 0;
uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
}
- uf->uf_tml_start = profile_zero();
+ uf->uf_tml_start = profile_zero();
uf->uf_tml_children = profile_zero();
- uf->uf_tml_wait = profile_zero();
- uf->uf_tml_idx = -1;
- uf->uf_tml_execed = 0;
+ uf->uf_tml_wait = profile_zero();
+ uf->uf_tml_idx = -1;
+ uf->uf_tml_execed = 0;
}
}
}
@@ -290,11 +288,8 @@ void ex_profile(exarg_T *eap)
{
static proftime_T pause_time;
- char *e;
- int len;
-
- e = skiptowhite(eap->arg);
- len = (int)(e - eap->arg);
+ char *e = skiptowhite(eap->arg);
+ int len = (int)(e - eap->arg);
e = skipwhite(e);
if (len == 5 && strncmp(eap->arg, "start", 5) == 0 && *e != NUL) {
@@ -302,13 +297,13 @@ void ex_profile(exarg_T *eap)
profile_fname = expand_env_save_opt(e, true);
do_profiling = PROF_YES;
profile_set_wait(profile_zero());
- set_vim_var_nr(VV_PROFILING, 1L);
+ set_vim_var_nr(VV_PROFILING, 1);
} else if (do_profiling == PROF_NONE) {
emsg(_("E750: First use \":profile start {fname}\""));
} else if (strcmp(eap->arg, "stop") == 0) {
profile_dump();
do_profiling = PROF_NONE;
- set_vim_var_nr(VV_PROFILING, 0L);
+ set_vim_var_nr(VV_PROFILING, 0);
profile_reset();
} else if (strcmp(eap->arg, "pause") == 0) {
if (do_profiling == PROF_YES) {
@@ -407,7 +402,7 @@ bool prof_def_func(void)
FUNC_ATTR_PURE
{
if (current_sctx.sc_sid > 0) {
- return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force;
+ return SCRIPT_ITEM(current_sctx.sc_sid)->sn_pr_force;
}
return false;
}
@@ -438,13 +433,10 @@ static void prof_func_line(FILE *fd, int count, const proftime_T *total, const p
/// @param prefer_self when equal print only self time
static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, bool prefer_self)
{
- int i;
- ufunc_T *fp;
-
fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title);
fprintf(fd, "count total (s) self (s) function\n");
- for (i = 0; i < 20 && i < st_len; i++) {
- fp = sorttab[i];
+ for (int i = 0; i < 20 && i < st_len; i++) {
+ ufunc_T *fp = sorttab[i];
prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self,
prefer_self);
if ((uint8_t)fp->uf_name[0] == K_SPECIAL) {
@@ -513,8 +505,8 @@ void prof_child_enter(proftime_T *tm)
{
funccall_T *fc = get_current_funccal();
- if (fc != NULL && fc->func->uf_profiling) {
- fc->prof_child = profile_start();
+ if (fc != NULL && fc->fc_func->uf_profiling) {
+ fc->fc_prof_child = profile_start();
}
script_prof_save(tm);
@@ -528,14 +520,14 @@ void prof_child_exit(proftime_T *tm)
{
funccall_T *fc = get_current_funccal();
- if (fc != NULL && fc->func->uf_profiling) {
- fc->prof_child = profile_end(fc->prof_child);
+ if (fc != NULL && fc->fc_func->uf_profiling) {
+ fc->fc_prof_child = profile_end(fc->fc_prof_child);
// don't count waiting time
- fc->prof_child = profile_sub_wait(*tm, fc->prof_child);
- fc->func->uf_tm_children =
- profile_add(fc->func->uf_tm_children, fc->prof_child);
- fc->func->uf_tml_children =
- profile_add(fc->func->uf_tml_children, fc->prof_child);
+ fc->fc_prof_child = profile_sub_wait(*tm, fc->fc_prof_child);
+ fc->fc_func->uf_tm_children =
+ profile_add(fc->fc_func->uf_tm_children, fc->fc_prof_child);
+ fc->fc_func->uf_tml_children =
+ profile_add(fc->fc_func->uf_tml_children, fc->fc_prof_child);
}
script_prof_restore(tm);
}
@@ -547,7 +539,7 @@ void prof_child_exit(proftime_T *tm)
void func_line_start(void *cookie)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
if (fp->uf_profiling && SOURCING_LNUM >= 1 && SOURCING_LNUM <= fp->uf_lines.ga_len) {
fp->uf_tml_idx = SOURCING_LNUM - 1;
@@ -566,7 +558,7 @@ void func_line_start(void *cookie)
void func_line_exec(void *cookie)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
fp->uf_tml_execed = true;
@@ -577,7 +569,7 @@ void func_line_exec(void *cookie)
void func_line_end(void *cookie)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
if (fp->uf_tml_execed) {
@@ -598,23 +590,19 @@ void func_line_end(void *cookie)
static void func_dump_profile(FILE *fd)
{
hashtab_T *const functbl = func_tbl_get();
- hashitem_T *hi;
- int todo;
- ufunc_T *fp;
- ufunc_T **sorttab;
int st_len = 0;
- todo = (int)functbl->ht_used;
+ int todo = (int)functbl->ht_used;
if (todo == 0) {
return; // nothing to dump
}
- sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
+ ufunc_T **sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
- for (hi = functbl->ht_array; todo > 0; hi++) {
+ for (hashitem_T *hi = functbl->ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- fp = HI2UF(hi);
+ ufunc_T *fp = HI2UF(hi);
if (fp->uf_prof_initialized) {
sorttab[st_len++] = fp;
@@ -689,10 +677,8 @@ void profile_init(scriptitem_T *si)
/// @param tm place to store wait time
void script_prof_save(proftime_T *tm)
{
- scriptitem_T *si;
-
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && si->sn_pr_nest++ == 0) {
si->sn_pr_child = profile_start();
}
@@ -707,7 +693,7 @@ void script_prof_restore(const proftime_T *tm)
return;
}
- scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && --si->sn_pr_nest == 0) {
si->sn_pr_child = profile_end(si->sn_pr_child);
// don't count wait time
@@ -720,12 +706,10 @@ void script_prof_restore(const proftime_T *tm)
/// Dump the profiling results for all scripts in file "fd".
static void script_dump_profile(FILE *fd)
{
- scriptitem_T *si;
- FILE *sfd;
sn_prl_T *pp;
for (int id = 1; id <= script_items.ga_len; id++) {
- si = &SCRIPT_ITEM(id);
+ scriptitem_T *si = SCRIPT_ITEM(id);
if (si->sn_prof_on) {
fprintf(fd, "SCRIPT %s\n", si->sn_name);
if (si->sn_pr_count == 1) {
@@ -738,7 +722,7 @@ static void script_dump_profile(FILE *fd)
fprintf(fd, "\n");
fprintf(fd, "count total (s) self (s)\n");
- sfd = os_fopen(si->sn_name, "r");
+ FILE *sfd = os_fopen(si->sn_name, "r");
if (sfd == NULL) {
fprintf(fd, "Cannot open file!\n");
} else {
@@ -807,13 +791,10 @@ void profile_dump(void)
/// until later and we need to store the time now.
void script_line_start(void)
{
- scriptitem_T *si;
- sn_prl_T *pp;
-
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
return;
}
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && SOURCING_LNUM >= 1) {
// Grow the array before starting the timer, so that the time spent
// here isn't counted.
@@ -822,7 +803,7 @@ void script_line_start(void)
while (si->sn_prl_ga.ga_len <= si->sn_prl_idx
&& si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) {
// Zero counters for a line that was not used before.
- pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
+ sn_prl_T *pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
pp->snp_count = 0;
pp->sn_prl_total = profile_zero();
pp->sn_prl_self = profile_zero();
@@ -838,12 +819,10 @@ void script_line_start(void)
/// Called when actually executing a function line.
void script_line_exec(void)
{
- scriptitem_T *si;
-
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
return;
}
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && si->sn_prl_idx >= 0) {
si->sn_prl_execed = true;
}
@@ -852,17 +831,14 @@ void script_line_exec(void)
/// Called when done with a function line.
void script_line_end(void)
{
- scriptitem_T *si;
- sn_prl_T *pp;
-
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
return;
}
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && si->sn_prl_idx >= 0
&& si->sn_prl_idx < si->sn_prl_ga.ga_len) {
if (si->sn_prl_execed) {
- pp = &PRL_ITEM(si, si->sn_prl_idx);
+ sn_prl_T *pp = &PRL_ITEM(si, si->sn_prl_idx);
pp->snp_count++;
si->sn_prl_start = profile_end(si->sn_prl_start);
si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start);
diff --git a/src/nvim/profile.h b/src/nvim/profile.h
index 547d11185f..1a1800c279 100644
--- a/src/nvim/profile.h
+++ b/src/nvim/profile.h
@@ -1,11 +1,11 @@
-#ifndef NVIM_PROFILE_H
-#define NVIM_PROFILE_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
#include <time.h>
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/runtime.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/runtime_defs.h" // IWYU pragma: keep
#define TIME_MSG(s) do { \
if (time_fd != NULL) time_msg(s, NULL); \
@@ -14,5 +14,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "profile.h.generated.h"
#endif
-
-#endif // NVIM_PROFILE_H
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 5518fdfa51..4e20eb8925 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// quickfix.c: functions for quickfix mode, using a file with error messages
#include <assert.h>
@@ -14,7 +11,7 @@
#include <time.h>
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -23,7 +20,6 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
@@ -33,34 +29,37 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/search.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
struct dir_stack_T {
@@ -80,14 +79,14 @@ struct qfline_S {
int qf_col; ///< column where the error occurred
int qf_end_col; ///< column when the error has range or zero
int qf_nr; ///< error number
- char *qf_module; ///< module name for this error
- char *qf_pattern; ///< search pattern for the error
- char *qf_text; ///< description of the error
- char qf_viscol; ///< set to true if qf_col and qf_end_col is
- // screen column
- char qf_cleared; ///< set to true if line has been deleted
- char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep
- char qf_valid; ///< valid error message detected
+ char *qf_module; ///< module name for this error
+ char *qf_pattern; ///< search pattern for the error
+ char *qf_text; ///< description of the error
+ char qf_viscol; ///< set to true if qf_col and qf_end_col is screen column
+ char qf_cleared; ///< set to true if line has been deleted
+ char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep
+ typval_T qf_user_data; ///< custom user data associated with this item
+ char qf_valid; ///< valid error message detected
};
// There is a stack of error lists.
@@ -109,18 +108,19 @@ typedef enum {
/// created using setqflist()/setloclist() with a title and/or user context
/// information and entries can be added later using setqflist()/setloclist().
typedef struct qf_list_S {
- unsigned qf_id; ///< Unique identifier for this list
+ unsigned qf_id; ///< Unique identifier for this list
qfltype_T qfl_type;
- qfline_T *qf_start; ///< pointer to the first error
- qfline_T *qf_last; ///< pointer to the last error
- qfline_T *qf_ptr; ///< pointer to the current error
- int qf_count; ///< number of errors (0 means empty list)
- int qf_index; ///< current index in the error list
- int qf_nonevalid; ///< true if not a single valid entry found
- char *qf_title; ///< title derived from the command that created
- ///< the error list or set by setqflist
- typval_T *qf_ctx; ///< context set by setqflist/setloclist
- Callback qf_qftf_cb; ///< 'quickfixtextfunc' callback function
+ qfline_T *qf_start; ///< pointer to the first error
+ qfline_T *qf_last; ///< pointer to the last error
+ qfline_T *qf_ptr; ///< pointer to the current error
+ int qf_count; ///< number of errors (0 means empty list)
+ int qf_index; ///< current index in the error list
+ bool qf_nonevalid; ///< true if not a single valid entry found
+ bool qf_has_user_data; ///< true if at least one item has user_data attached
+ char *qf_title; ///< title derived from the command that created
+ ///< the error list or set by setqflist
+ typval_T *qf_ctx; ///< context set by setqflist/setloclist
+ Callback qf_qftf_cb; ///< 'quickfixtextfunc' callback function
struct dir_stack_T *qf_dir_stack;
char *qf_directory;
@@ -129,7 +129,7 @@ typedef struct qf_list_S {
bool qf_multiline;
bool qf_multiignore;
bool qf_multiscan;
- long qf_changedtick;
+ int qf_changedtick;
} qf_list_T;
/// Quickfix/Location list stack definition
@@ -150,7 +150,7 @@ struct qf_info_S {
static qf_info_T ql_info; // global quickfix list
static unsigned last_qf_id = 0; // Last Used quickfix list id
-#define FMT_PATTERNS 13 // maximum number of % recognized
+#define FMT_PATTERNS 14 // maximum number of % recognized
// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
@@ -215,6 +215,7 @@ typedef struct {
typedef struct {
char *namebuf;
+ int bnr;
char *module;
char *errmsg;
size_t errmsglen;
@@ -226,12 +227,13 @@ typedef struct {
char *pattern;
int enr;
char type;
+ typval_T *user_data;
bool valid;
} qffields_T;
/// :vimgrep command arguments
typedef struct vgr_args_S {
- long tomatch; ///< maximum number of matches to find
+ int tomatch; ///< maximum number of matches to find
char *spat; ///< search pattern
int flags; ///< search modifier
char **fnames; ///< list of files to search
@@ -244,7 +246,13 @@ typedef struct vgr_args_S {
# include "quickfix.c.generated.h"
#endif
-static char *e_no_more_items = N_("E553: No more items");
+static const char *e_no_more_items = N_("E553: No more items");
+static const char *e_current_quickfix_list_was_changed =
+ N_("E925: Current quickfix list was changed");
+static const char *e_current_location_list_was_changed =
+ N_("E926: Current location list was changed");
+
+enum { QF_WINHEIGHT = 10, }; ///< default height for quickfix window
// Quickfix window check helper macro
#define IS_QF_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref == NULL)
@@ -257,10 +265,8 @@ static char *e_no_more_items = N_("E553: No more items");
#define IS_QF_LIST(qfl) ((qfl)->qfl_type == QFLT_QUICKFIX)
#define IS_LL_LIST(qfl) ((qfl)->qfl_type == QFLT_LOCATION)
-//
// Return location list for window 'wp'
// For location list window, return the referenced location list
-//
#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? (wp)->w_llist_ref : (wp)->w_llist)
// Macro to loop through all the items in a quickfix list
@@ -275,10 +281,38 @@ static char *e_no_more_items = N_("E553: No more items");
static char *qf_last_bufname = NULL;
static bufref_T qf_last_bufref = { NULL, 0, 0 };
-static char *e_current_quickfix_list_was_changed =
- N_("E925: Current quickfix list was changed");
-static char *e_current_location_list_was_changed =
- N_("E926: Current location list was changed");
+static garray_T qfga;
+
+/// Get a growarray to buffer text in. Shared between various commands to avoid
+/// many alloc/free calls.
+static garray_T *qfga_get(void)
+{
+ static bool initialized = false;
+
+ if (!initialized) {
+ initialized = true;
+ ga_init(&qfga, 1, 256);
+ }
+
+ // Reset the length to zero. Retain ga_data from previous use to avoid
+ // many alloc/free calls.
+ qfga.ga_len = 0;
+
+ return &qfga;
+}
+
+/// The "qfga" grow array buffer is reused across multiple quickfix commands as
+/// a temporary buffer to reduce the number of alloc/free calls. But if the
+/// buffer size is large, then to avoid holding on to that memory, clear the
+/// grow array. Otherwise just reset the grow array length.
+static void qfga_clear(void)
+{
+ if (qfga.ga_maxlen > 1000) {
+ ga_clear(&qfga);
+ } else {
+ qfga.ga_len = 0;
+ }
+}
// Counter to prevent autocmds from freeing up location lists when they are
// still being used.
@@ -309,7 +343,7 @@ static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T
: ((qfl->qf_currfile != NULL && fields->valid)
? qfl->qf_currfile : NULL),
fields->module,
- 0,
+ fields->bnr,
fields->errmsg,
fields->lnum,
fields->end_lnum,
@@ -319,6 +353,7 @@ static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T
fields->pattern,
fields->enr,
fields->type,
+ fields->user_data,
fields->valid);
}
@@ -342,8 +377,8 @@ int qf_init(win_T *wp, const char *restrict efile, char *restrict errorformat, i
qi = ll_get_or_alloc_list(wp);
}
- return qf_init_ext(qi, qi->qf_curlist, (char *)efile, curbuf, NULL, errorformat,
- newlist, (linenr_T)0, (linenr_T)0, (char *)qf_title, enc);
+ return qf_init_ext(qi, qi->qf_curlist, efile, curbuf, NULL, errorformat,
+ newlist, 0, 0, qf_title, enc);
}
// Maximum number of bytes allowed per line while reading an errorfile.
@@ -355,20 +390,21 @@ static struct fmtpattern {
char *pattern;
} fmt_pat[FMT_PATTERNS] = {
{ 'f', ".\\+" }, // only used when at end
- { 'n', "\\d\\+" }, // 1
- { 'l', "\\d\\+" }, // 2
- { 'e', "\\d\\+" }, // 3
- { 'c', "\\d\\+" }, // 4
- { 'k', "\\d\\+" }, // 5
- { 't', "." }, // 6
-#define FMT_PATTERN_M 7
- { 'm', ".\\+" }, // 7
-#define FMT_PATTERN_R 8
- { 'r', ".*" }, // 8
- { 'p', "[- \t.]*" }, // 9
- { 'v', "\\d\\+" }, // 10
- { 's', ".\\+" }, // 11
- { 'o', ".\\+" } // 12
+ { 'b', "\\d\\+" }, // 1
+ { 'n', "\\d\\+" }, // 2
+ { 'l', "\\d\\+" }, // 3
+ { 'e', "\\d\\+" }, // 4
+ { 'c', "\\d\\+" }, // 5
+ { 'k', "\\d\\+" }, // 6
+ { 't', "." }, // 7
+#define FMT_PATTERN_M 8
+ { 'm', ".\\+" }, // 8
+#define FMT_PATTERN_R 9
+ { 'r', ".*" }, // 9
+ { 'p', "[-\t .]*" }, // 10
+ { 'v', "\\d\\+" }, // 11
+ { 's', ".\\+" }, // 12
+ { 'o', ".\\+" } // 13
};
/// Convert an errorformat pattern to a regular expression pattern.
@@ -499,7 +535,7 @@ static int efm_to_regpat(const char *efm, int len, efm_T *fmt_ptr, char *regpat)
}
}
if (idx < FMT_PATTERNS) {
- ptr = efmpat_to_regpat((char *)efmp, ptr, fmt_ptr, idx, round);
+ ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round);
if (ptr == NULL) {
return FAIL;
}
@@ -726,7 +762,7 @@ static int qf_get_next_buf_line(qfstate_T *state)
if (state->buflnum > state->lnumlast) {
return QF_END_OF_INPUT;
}
- char *p_buf = ml_get_buf(state->buf, state->buflnum, false);
+ char *p_buf = ml_get_buf(state->buf, state->buflnum);
state->buflnum += 1;
size_t len = strlen(p_buf);
@@ -768,7 +804,7 @@ retry:
memcpy(state->growbuf, IObuff, IOSIZE - 1);
size_t growbuflen = state->linelen;
- for (;;) {
+ while (true) {
errno = 0;
if (fgets(state->growbuf + growbuflen,
(int)(state->growbufsiz - growbuflen), state->fd) == NULL) {
@@ -788,7 +824,7 @@ retry:
}
state->growbufsiz = (2 * state->growbufsiz < LINE_MAXLEN)
- ? 2 * state->growbufsiz : LINE_MAXLEN;
+ ? 2 * state->growbufsiz : LINE_MAXLEN;
state->growbuf = xrealloc(state->growbuf, state->growbufsiz);
}
@@ -825,7 +861,7 @@ retry:
state->linebuf = line;
state->growbuf = line;
state->growbufsiz = state->linelen < LINE_MAXLEN
- ? state->linelen : LINE_MAXLEN;
+ ? state->linelen : LINE_MAXLEN;
}
}
}
@@ -1209,7 +1245,7 @@ static char *qf_cmdtitle(char *cmd)
{
static char qftitle_str[IOSIZE];
- snprintf((char *)qftitle_str, IOSIZE, ":%s", cmd);
+ snprintf(qftitle_str, IOSIZE, ":%s", cmd);
return qftitle_str;
}
@@ -1249,6 +1285,7 @@ static void qf_new_list(qf_info_T *qi, const char *qf_title)
qf_store_title(qfl, qf_title);
qfl->qfl_type = qi->qfl_type;
qfl->qf_id = ++last_qf_id;
+ qfl->qf_has_user_data = false;
}
/// Parse the match for filename ('%f') pattern in regmatch.
@@ -1275,6 +1312,21 @@ static int qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, int pre
return QF_OK;
}
+/// Parse the match for buffer number ('%b') pattern in regmatch.
+/// Return the matched value in "fields->bnr".
+static int qf_parse_fmt_b(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ int bnr = (int)atol(rmp->startp[midx]);
+ if (buflist_findnr(bnr) == NULL) {
+ return QF_FAIL;
+ }
+ fields->bnr = bnr;
+ return QF_OK;
+}
+
/// Parse the match for error number ('%n') pattern in regmatch.
/// Return the matched value in "fields->enr".
static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1459,6 +1511,7 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
/// Keep in sync with fmt_pat[].
static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
NULL, // %f
+ qf_parse_fmt_b,
qf_parse_fmt_n,
qf_parse_fmt_l,
qf_parse_fmt_e,
@@ -1531,6 +1584,7 @@ static int qf_parse_get_fields(char *linebuf, size_t linelen, efm_T *fmt_ptr, qf
}
fields->namebuf[0] = NUL;
+ fields->bnr = 0;
fields->module[0] = NUL;
fields->pattern[0] = NUL;
if (!qf_multiscan) {
@@ -1549,7 +1603,7 @@ static int qf_parse_get_fields(char *linebuf, size_t linelen, efm_T *fmt_ptr, qf
// Always ignore case when looking for a matching error.
regmatch.rm_ic = true;
regmatch.regprog = fmt_ptr->prog;
- int r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
+ int r = vim_regexec(&regmatch, linebuf, 0);
fmt_ptr->prog = regmatch.regprog;
int status = QF_FAIL;
if (r) {
@@ -1624,7 +1678,7 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
}
if (*fields->errmsg) {
size_t textlen = strlen(qfprev->qf_text);
- size_t errlen = strlen(fields->errmsg);
+ size_t errlen = strlen(fields->errmsg);
qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2);
qfprev->qf_text[textlen] = '\n';
STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg);
@@ -1804,12 +1858,14 @@ void check_quickfix_busy(void)
/// @param pattern search pattern
/// @param nr error number
/// @param type type character
+/// @param user_data custom user data or NULL
/// @param valid valid entry
///
/// @return QF_OK on success or QF_FAIL on failure.
static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, int bufnum,
char *mesg, linenr_T lnum, linenr_T end_lnum, int col, int end_col,
- char vis_col, char *pattern, int nr, char type, char valid)
+ char vis_col, char *pattern, int nr, char type, typval_T *user_data,
+ char valid)
{
qfline_T *qfp = xmalloc(sizeof(qfline_T));
@@ -1830,6 +1886,12 @@ static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, in
qfp->qf_col = col;
qfp->qf_end_col = end_col;
qfp->qf_viscol = vis_col;
+ if (user_data == NULL || user_data->v_type == VAR_UNKNOWN) {
+ qfp->qf_user_data.v_type = VAR_UNKNOWN;
+ } else {
+ tv_copy(user_data, &qfp->qf_user_data);
+ qfl->qf_has_user_data = true;
+ }
if (pattern == NULL || *pattern == NUL) {
qfp->qf_pattern = NULL;
} else {
@@ -1965,6 +2027,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl)
from_qfp->qf_pattern,
from_qfp->qf_nr,
0,
+ &from_qfp->qf_user_data,
from_qfp->qf_valid) == QF_FAIL) {
return FAIL;
}
@@ -1990,6 +2053,7 @@ static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl)
// Some of the fields are populated by qf_add_entry()
to_qfl->qfl_type = from_qfl->qfl_type;
to_qfl->qf_nonevalid = from_qfl->qf_nonevalid;
+ to_qfl->qf_has_user_data = from_qfl->qf_has_user_data;
to_qfl->qf_count = 0;
to_qfl->qf_index = 0;
to_qfl->qf_start = NULL;
@@ -2018,7 +2082,7 @@ static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl)
// Assign a new ID for the location list
to_qfl->qf_id = ++last_qf_id;
- to_qfl->qf_changedtick = 0L;
+ to_qfl->qf_changedtick = 0;
// When no valid entries are present in the list, qf_ptr points to
// the first item in the list
@@ -2107,7 +2171,7 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname)
xfree(ptr);
} else {
xfree(qf_last_bufname);
- buf = buflist_new(bufname, NULL, (linenr_T)0, BLN_NOOPT);
+ buf = buflist_new(bufname, NULL, 0, BLN_NOOPT);
qf_last_bufname = (bufname == ptr) ? bufname : xstrdup(bufname);
set_bufref(&qf_last_bufref, buf);
}
@@ -2259,7 +2323,7 @@ static char *qf_guess_filepath(qf_list_T *qfl, char *filename)
}
/// Returns true, if a quickfix/location list with the given identifier exists.
-static bool qflist_valid(win_T *wp, unsigned int qf_id)
+static bool qflist_valid(win_T *wp, unsigned qf_id)
{
qf_info_T *qi = &ql_info;
@@ -2353,7 +2417,7 @@ static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr, int dir, int *
{
qfline_T *qf_ptr = qfl->qf_ptr;
int qf_idx = qfl->qf_index;
- char *err = e_no_more_items;
+ const char *err = e_no_more_items;
while (errornr--) {
qfline_T *prev_qf_ptr = qf_ptr;
@@ -2549,7 +2613,7 @@ static int qf_open_new_file_win(qf_info_T *ll_ref)
if (win_split(0, flags) == FAIL) {
return FAIL; // not enough room for window
}
- p_swb = empty_option; // don't split again
+ p_swb = empty_string_option; // don't split again
swb_flags = 0;
RESET_BINDING(curwin);
if (ll_ref != NULL) {
@@ -2609,7 +2673,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
{
win_T *win = curwin;
win_T *altwin = NULL;
- for (;;) {
+ while (true) {
if (win->w_buffer->b_fnum == qf_fnum) {
break;
}
@@ -2709,7 +2773,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
int *opened_window)
{
qf_list_T *qfl = qf_get_curlist(qi);
- long old_changetick = qfl->qf_changedtick;
+ int old_changetick = qfl->qf_changedtick;
int old_qf_curlist = qi->qf_curlist;
qfltype_T qfl_type = qfl->qfl_type;
int retval = OK;
@@ -2722,11 +2786,11 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
no_write_message();
return FAIL;
}
- retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1,
+ retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, 1,
ECMD_HIDE + ECMD_SET_HELP,
prev_winid == curwin->handle ? curwin : NULL);
} else {
- retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
+ retval = buflist_getfile(qf_ptr->qf_fnum, 1,
GETF_SETMARK | GETF_SWITCH, forceit);
}
// If a location list, check whether the associated window is still
@@ -2746,8 +2810,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
return QF_ABORT;
}
- if (old_qf_curlist != qi->qf_curlist // -V560
- || old_changetick != qfl->qf_changedtick // -V560
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
|| !is_qf_entry_present(qfl, qf_ptr)) {
if (qfl_type == QFLT_QUICKFIX) {
emsg(_(e_current_quickfix_list_was_changed));
@@ -2789,7 +2853,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char
// Move the cursor to the first line in the buffer
pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor.lnum = 0;
- if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) {
+ if (!do_search(NULL, '/', '/', qf_pattern, 1, SEARCH_KEEP, NULL)) {
curwin->w_cursor = save_cursor;
}
}
@@ -2799,6 +2863,8 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char
static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf_T *old_curbuf,
linenr_T old_lnum)
{
+ garray_T *const gap = qfga_get();
+
// Update the screen before showing the message, unless the screen
// scrolled up.
if (!msg_scrolled) {
@@ -2807,13 +2873,14 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
update_screen();
}
}
- snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index,
- qf_get_curlist(qi)->qf_count,
- qf_ptr->qf_cleared ? _(" (line deleted)") : "",
- qf_types(qf_ptr->qf_type, qf_ptr->qf_nr));
+ vim_snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index,
+ qf_get_curlist(qi)->qf_count,
+ qf_ptr->qf_cleared ? _(" (line deleted)") : "",
+ qf_types(qf_ptr->qf_type, qf_ptr->qf_nr));
// Add the message, skipping leading whitespace and newlines.
- int len = (int)strlen(IObuff);
- qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len);
+ ga_concat(gap, IObuff);
+ qf_fmt_text(gap, skipwhite(qf_ptr->qf_text));
+ ga_append(gap, NUL);
// Output the message. Overwrite to avoid scrolling when the 'O'
// flag is present in 'shortmess'; But when not jumping, print the
@@ -2825,8 +2892,10 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
msg_scroll = false;
}
msg_ext_set_kind("quickfix");
- msg_attr_keep(IObuff, 0, true, false);
+ msg_attr_keep(gap->ga_data, 0, true, false);
msg_scroll = (int)i;
+
+ qfga_clear();
}
/// Find a usable window for opening a file from the quickfix/location list. If
@@ -2839,7 +2908,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int *opened_window)
{
qf_list_T *qfl = qf_get_curlist(qi);
- long old_changetick = qfl->qf_changedtick;
+ int old_changetick = qfl->qf_changedtick;
int old_qf_curlist = qi->qf_curlist;
qfltype_T qfl_type = qfl->qfl_type;
@@ -2850,7 +2919,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int
}
}
if (old_qf_curlist != qi->qf_curlist
- || old_changetick != qfl->qf_changedtick // -V560
+ || old_changetick != qfl->qf_changedtick
|| !is_qf_entry_present(qfl, qf_ptr)) {
if (qfl_type == QFLT_QUICKFIX) {
emsg(_(e_current_quickfix_list_was_changed));
@@ -3021,7 +3090,7 @@ theend:
qfl->qf_ptr = qf_ptr;
qfl->qf_index = qf_index;
}
- if (p_swb != old_swb && p_swb == empty_option) {
+ if (p_swb != old_swb && p_swb == empty_string_option) {
// Restore old 'switchbuf' value, but not when an autocommand or
// modeline has changed the value.
p_swb = old_swb;
@@ -3081,46 +3150,35 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
}
msg_putchar('\n');
- msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr);
+ msg_outtrans(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr);
if (qfp->qf_lnum != 0) {
msg_puts_attr(":", qfSepAttr);
}
- if (qfp->qf_lnum == 0) {
- IObuff[0] = NUL;
- } else {
- qf_range_text(qfp, IObuff, IOSIZE);
+ garray_T *gap = qfga_get();
+ if (qfp->qf_lnum != 0) {
+ qf_range_text(gap, qfp);
}
- vim_snprintf(IObuff + strlen(IObuff), IOSIZE, "%s", qf_types(qfp->qf_type, qfp->qf_nr));
- msg_puts_attr((const char *)IObuff, qfLineAttr);
+ ga_concat(gap, qf_types(qfp->qf_type, qfp->qf_nr));
+ ga_append(gap, NUL);
+ msg_puts_attr(gap->ga_data, qfLineAttr);
msg_puts_attr(":", qfSepAttr);
if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
- msg_puts((const char *)IObuff);
+ gap = qfga_get();
+ qf_fmt_text(gap, qfp->qf_pattern);
+ ga_append(gap, NUL);
+ msg_puts(gap->ga_data);
msg_puts_attr(":", qfSepAttr);
}
msg_puts(" ");
- char *tbuf = IObuff;
- size_t tbuflen = IOSIZE;
- size_t len = strlen(qfp->qf_text) + 3;
-
- if (len > IOSIZE) {
- tbuf = xmalloc(len);
- tbuflen = len;
- }
-
// Remove newlines and leading whitespace from the text. For an
// unrecognized line keep the indent, the compiler may mark a word
// with ^^^^.
- qf_fmt_text((fname != NULL || qfp->qf_lnum != 0)
- ? skipwhite(qfp->qf_text) : qfp->qf_text,
- tbuf, (int)tbuflen);
- msg_prt_line(tbuf, false);
-
- if (tbuf != IObuff) {
- xfree(tbuf);
- }
+ gap = qfga_get();
+ qf_fmt_text(gap, (fname != NULL || qfp->qf_lnum != 0) ? skipwhite(qfp->qf_text) : qfp->qf_text);
+ ga_append(gap, NUL);
+ msg_prt_line(gap->ga_data, false);
}
// ":clist": list all errors
@@ -3195,51 +3253,53 @@ void qf_list(exarg_T *eap)
}
os_breakcheck();
}
+ qfga_clear();
}
-// Remove newlines and leading whitespace from an error message.
-// Put the result in "buf[bufsize]".
-static void qf_fmt_text(const char *restrict text, char *restrict buf, int bufsize)
+/// Remove newlines and leading whitespace from an error message.
+/// Add the result to the grow array "gap".
+static void qf_fmt_text(garray_T *gap, const char *restrict text)
FUNC_ATTR_NONNULL_ALL
{
- int i;
- const char *p = (char *)text;
-
- for (i = 0; *p != NUL && i < bufsize - 1; i++) {
+ const char *p = text;
+ while (*p != NUL) {
if (*p == '\n') {
- buf[i] = ' ';
+ ga_append(gap, ' ');
while (*++p != NUL) {
if (!ascii_iswhite(*p) && *p != '\n') {
break;
}
}
} else {
- buf[i] = *p++;
+ ga_append(gap, (uint8_t)(*p++));
}
}
- buf[i] = NUL;
}
-// Range information from lnum, col, end_lnum, and end_col.
-// Put the result in "buf[bufsize]".
-static void qf_range_text(const qfline_T *qfp, char *buf, int bufsize)
+/// Add the range information from the lnum, col, end_lnum, and end_col values
+/// of a quickfix entry to the grow array "gap".
+static void qf_range_text(garray_T *gap, const qfline_T *qfp)
{
- vim_snprintf(buf, (size_t)bufsize, "%" PRIdLINENR, qfp->qf_lnum);
- int len = (int)strlen(buf);
+ char *const buf = IObuff;
+ const size_t bufsize = IOSIZE;
+
+ vim_snprintf(buf, bufsize, "%" PRIdLINENR, qfp->qf_lnum);
+ size_t len = strlen(buf);
if (qfp->qf_end_lnum > 0 && qfp->qf_lnum != qfp->qf_end_lnum) {
- vim_snprintf(buf + len, (size_t)(bufsize - len), "-%" PRIdLINENR, qfp->qf_end_lnum);
- len += (int)strlen(buf + len);
+ vim_snprintf(buf + len, bufsize - len, "-%" PRIdLINENR, qfp->qf_end_lnum);
+ len += strlen(buf + len);
}
if (qfp->qf_col > 0) {
- vim_snprintf(buf + len, (size_t)(bufsize - len), " col %d", qfp->qf_col);
- len += (int)strlen(buf + len);
+ vim_snprintf(buf + len, bufsize - len, " col %d", qfp->qf_col);
+ len += strlen(buf + len);
if (qfp->qf_end_col > 0 && qfp->qf_col != qfp->qf_end_col) {
- vim_snprintf(buf + len, (size_t)(bufsize - len), "-%d", qfp->qf_end_col);
- len += (int)strlen(buf + len);
+ vim_snprintf(buf + len, bufsize - len, "-%d", qfp->qf_end_col);
+ len += strlen(buf + len);
}
}
- buf[len] = NUL;
+
+ ga_concat_len(gap, buf, len);
}
/// Display information (list number, list size and the title) about a
@@ -3250,7 +3310,7 @@ static void qf_msg(qf_info_T *qi, int which, char *lead)
int count = qi->qf_lists[which].qf_count;
char buf[IOSIZE];
- vim_snprintf((char *)buf, IOSIZE, _("%serror list %d of %d; %d errors "),
+ vim_snprintf(buf, IOSIZE, _("%serror list %d of %d; %d errors "),
lead,
which + 1,
qi->qf_listcount,
@@ -3266,7 +3326,7 @@ static void qf_msg(qf_info_T *qi, int which, char *lead)
xstrlcat(buf, title, IOSIZE);
}
trunc_string(buf, buf, Columns - 1, IOSIZE);
- msg(buf);
+ msg(buf, 0);
}
/// ":colder [count]": Up in the quickfix stack.
@@ -3325,7 +3385,7 @@ void qf_history(exarg_T *eap)
}
if (qf_stack_empty(qi)) {
- msg(_("No entries"));
+ msg(_("No entries"), 0);
} else {
for (int i = 0; i < qi->qf_listcount; i++) {
qf_msg(qi, i, i == qi->qf_curlist ? "> " : " ");
@@ -3346,6 +3406,7 @@ static void qf_free_items(qf_list_T *qfl)
xfree(qfp->qf_module);
xfree(qfp->qf_text);
xfree(qfp->qf_pattern);
+ tv_clear(&qfp->qf_user_data);
stop = (qfp == qfpnext);
xfree(qfp);
if (stop) {
@@ -3353,9 +3414,10 @@ static void qf_free_items(qf_list_T *qfl)
// to avoid crashing when it's wrong.
// TODO(vim): Avoid qf_count being incorrect.
qfl->qf_count = 1;
+ } else {
+ qfl->qf_start = qfpnext;
}
}
- qfl->qf_start = qfpnext;
qfl->qf_count--;
}
@@ -3387,17 +3449,20 @@ static void qf_free(qf_list_T *qfl)
qfl->qf_ctx = NULL;
callback_free(&qfl->qf_qftf_cb);
qfl->qf_id = 0;
- qfl->qf_changedtick = 0L;
+ qfl->qf_changedtick = 0;
}
-// qf_mark_adjust: adjust marks
-bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
+/// Adjust error list entries for changed line numbers
+///
+/// Note: `buf` is the changed buffer, but `wp` is a potential location list
+/// into that buffer, or NULL to check the quickfix list.
+bool qf_mark_adjust(buf_T *buf, win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
linenr_T amount_after)
{
qf_info_T *qi = &ql_info;
int buf_has_flag = wp == NULL ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
- if (!(curbuf->b_has_qf_entry & buf_has_flag)) {
+ if (!(buf->b_has_qf_entry & buf_has_flag)) {
return false;
}
if (wp != NULL) {
@@ -3414,7 +3479,7 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
qf_list_T *qfl = qf_get_list(qi, idx);
if (!qf_list_empty(qfl)) {
FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
- if (qfp->qf_fnum == curbuf->b_fnum) {
+ if (qfp->qf_fnum == buf->b_fnum) {
found_one = true;
if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) {
if (amount == MAXLNUM) {
@@ -3475,7 +3540,7 @@ static char *qf_types(int c, int nr)
}
static char buf[20];
- snprintf((char *)buf, sizeof(buf), "%s %3d", p, nr);
+ snprintf(buf, sizeof(buf), "%s %3d", p, nr);
return buf;
}
@@ -3581,12 +3646,12 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp
static void qf_set_cwindow_options(void)
{
// switch off 'swapfile'
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "quickfix", OPT_LOCAL);
- set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("quickfix"), OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
RESET_BINDING(curwin);
curwin->w_p_diff = false;
- set_option_value_give_err("fdm", 0L, "manual", OPT_LOCAL);
+ set_option_value_give_err("fdm", STATIC_CSTR_AS_OPTVAL("manual"), OPT_LOCAL);
}
// Open a new quickfix or location list window, load the quickfix buffer and
@@ -3800,13 +3865,11 @@ static bool qf_win_pos_update(qf_info_T *qi, int old_qf_index)
static int is_qf_win(const win_T *win, const qf_info_T *qi)
FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- //
// A window displaying the quickfix buffer will have the w_llist_ref field
// set to NULL.
// A window displaying a location list buffer will have the w_llist_ref
// pointing to the location list.
- //
- if (bt_quickfix(win->w_buffer)) {
+ if (buf_valid(win->w_buffer) && bt_quickfix(win->w_buffer)) {
if ((IS_QF_STACK(qi) && win->w_llist_ref == NULL)
|| (IS_LL_STACK(qi) && win->w_llist_ref == qi)) {
return true;
@@ -3854,11 +3917,12 @@ static buf_T *qf_find_buf(qf_info_T *qi)
}
/// Process the 'quickfixtextfunc' option value.
-void qf_process_qftf_option(char **errmsg)
+const char *did_set_quickfixtextfunc(optset_T *args FUNC_ATTR_UNUSED)
{
if (option_set_callback_func(p_qftf, &qftf_cb) == FAIL) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
/// Update the w:quickfix_title variable in the quickfix/location list window in
@@ -3945,21 +4009,21 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
char *dirname, char *qftf_str, bool first_bufline)
FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5)
{
+ garray_T *gap = qfga_get();
+
// If the 'quickfixtextfunc' function returned a non-empty custom string
// for this entry, then use it.
if (qftf_str != NULL && *qftf_str != NUL) {
- xstrlcpy(IObuff, qftf_str, IOSIZE);
+ ga_concat(gap, qftf_str);
} else {
buf_T *errbuf;
- int len;
if (qfp->qf_module != NULL) {
- xstrlcpy(IObuff, qfp->qf_module, IOSIZE);
- len = (int)strlen(IObuff);
+ ga_concat(gap, qfp->qf_module);
} else if (qfp->qf_fnum != 0
&& (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
&& errbuf->b_fname != NULL) {
if (qfp->qf_type == 1) { // :helpgrep
- xstrlcpy(IObuff, path_tail(errbuf->b_fname), IOSIZE);
+ ga_concat(gap, path_tail(errbuf->b_fname));
} else {
// Shorten the file name if not done already.
// For optimization, do this only for the first entry in a
@@ -3972,48 +4036,38 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
}
shorten_buf_fname(errbuf, dirname, false);
}
- xstrlcpy(IObuff, errbuf->b_fname, IOSIZE);
+ ga_concat(gap, errbuf->b_fname);
}
- len = (int)strlen(IObuff);
- } else {
- len = 0;
}
- if (len < IOSIZE - 1) {
- IObuff[len++] = '|';
- }
- if (qfp->qf_lnum > 0) {
- qf_range_text(qfp, IObuff + len, IOSIZE - len);
- len += (int)strlen(IObuff + len);
- snprintf(IObuff + len, (size_t)(IOSIZE - len), "%s", qf_types(qfp->qf_type,
- qfp->qf_nr));
- len += (int)strlen(IObuff + len);
+ ga_append(gap, '|');
+
+ if (qfp->qf_lnum > 0) {
+ qf_range_text(gap, qfp);
+ ga_concat(gap, qf_types(qfp->qf_type, qfp->qf_nr));
} else if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
- len += (int)strlen(IObuff + len);
- }
- if (len < IOSIZE - 2) {
- IObuff[len++] = '|';
- IObuff[len++] = ' ';
+ qf_fmt_text(gap, qfp->qf_pattern);
}
+ ga_append(gap, '|');
+ ga_append(gap, ' ');
// Remove newlines and leading whitespace from the text.
// For an unrecognized line keep the indent, the compiler may
// mark a word with ^^^^.
- qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
- IObuff + len, IOSIZE - len);
+ qf_fmt_text(gap, gap->ga_len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text);
}
- if (ml_append_buf(buf, lnum, IObuff,
- (colnr_T)strlen(IObuff) + 1, false) == FAIL) {
+ ga_append(gap, NUL);
+ if (ml_append_buf(buf, lnum, gap->ga_data, gap->ga_len, false) == FAIL) {
return FAIL;
}
+
return OK;
}
// Call the 'quickfixtextfunc' function to get the list of lines to display in
// the quickfix window for the entries 'start_idx' to 'end_idx'.
-static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long end_idx)
+static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, int start_idx, int end_idx)
{
Callback *cb = &qftf_cb;
list_T *qftf_list = NULL;
@@ -4078,7 +4132,12 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
// delete all existing lines
while ((curbuf->b_ml.ml_flags & ML_EMPTY) == 0) {
- (void)ml_delete((linenr_T)1, false);
+ // If deletion fails, this loop may run forever, so
+ // signal error and return.
+ if (ml_delete(1, false) == FAIL) {
+ internal_error("qf_fill_buffer()");
+ return;
+ }
}
}
@@ -4104,7 +4163,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
lnum = buf->b_ml.ml_line_count;
}
- list_T *qftf_list = call_qftf_func(qfl, qf_winid, lnum + 1, (long)qfl->qf_count);
+ list_T *qftf_list = call_qftf_func(qfl, qf_winid, lnum + 1, qfl->qf_count);
listitem_T *qftf_li = tv_list_first(qftf_list);
int prev_bufnr = -1;
@@ -4142,6 +4201,8 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
// Delete the empty line which is now at the end
(void)ml_delete(lnum + 1, false);
}
+
+ qfga_clear();
}
// Correct cursor position.
@@ -4152,7 +4213,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
// resembles reading a file into a buffer, it's more logical when using
// autocommands.
curbuf->b_ro_locked++;
- set_option_value_give_err("ft", 0L, "qf", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("qf"), OPT_LOCAL);
curbuf->b_p_ma = false;
keep_filetype = true; // don't detect 'filetype'
@@ -4263,11 +4324,11 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname)
len += strlen(p_sp) + strlen(fname) + 3;
}
char *const cmd = xmalloc(len);
- snprintf(cmd, len, "%s%s%s", p_shq, (char *)makecmd, p_shq);
+ snprintf(cmd, len, "%s%s%s", p_shq, makecmd, p_shq);
// If 'shellpipe' empty: don't redirect to 'errorfile'.
if (*p_sp != NUL) {
- append_redir(cmd, len, p_sp, (char *)fname);
+ append_redir(cmd, len, p_sp, fname);
}
// Display the fully formed command. Output a newline if there's something
@@ -4278,7 +4339,7 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname)
}
msg_start();
msg_puts(":!");
- msg_outtrans(cmd); // show what we are doing
+ msg_outtrans(cmd, 0); // show what we are doing
return cmd;
}
@@ -4391,7 +4452,7 @@ static char *get_mef_name(void)
}
// Keep trying until the name doesn't exist yet.
- for (;;) {
+ while (true) {
if (start == -1) {
start = (int)os_get_pid();
} else {
@@ -5129,9 +5190,9 @@ static void vgr_display_fname(char *fname)
msg_start();
char *p = msg_strtrunc(fname, true);
if (p == NULL) {
- msg_outtrans(fname);
+ msg_outtrans(fname, 0);
} else {
- msg_outtrans(p);
+ msg_outtrans(p, 0);
xfree(p);
}
msg_clr_eos();
@@ -5148,7 +5209,7 @@ static buf_T *vgr_load_dummy_buf(char *fname, char *dirname_start, char *dirname
// indent scripts, a great speed improvement.
char *save_ei = au_event_disable(",Filetype");
- long save_mls = p_mls;
+ OptInt save_mls = p_mls;
p_mls = 0;
// Load file into a buffer, so that 'fileencoding' is detected,
@@ -5187,11 +5248,14 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char *titl
/// Search for a pattern in all the lines in a buffer and add the matching lines
/// to a quickfix list.
static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *spat,
- regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags)
+ regmmatch_T *regmatch, int *tomatch, int duplicate_name, int flags)
FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6)
{
bool found_match = false;
- const size_t pat_len = strlen(spat);
+ size_t pat_len = strlen(spat);
+ if (pat_len > MAX_FUZZY_MATCHES) {
+ pat_len = MAX_FUZZY_MATCHES;
+ }
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
@@ -5206,7 +5270,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
fname,
NULL,
duplicate_name ? 0 : buf->b_fnum,
- ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false),
+ ml_get_buf(buf, regmatch->startpos[0].lnum + lnum),
regmatch->startpos[0].lnum + lnum,
regmatch->endpos[0].lnum + lnum,
regmatch->startpos[0].col + 1,
@@ -5215,6 +5279,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
NULL, // search pattern
0, // nr
0, // type
+ NULL, // user_data
true) // valid
== QF_FAIL) {
got_int = true;
@@ -5228,17 +5293,18 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
break;
}
col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
- if (col > (colnr_T)strlen(ml_get_buf(buf, lnum, false))) {
+ if (col > (colnr_T)strlen(ml_get_buf(buf, lnum))) {
break;
}
}
} else {
- char *const str = ml_get_buf(buf, lnum, false);
+ char *const str = ml_get_buf(buf, lnum);
int score;
uint32_t matches[MAX_FUZZY_MATCHES];
const size_t sz = sizeof(matches) / sizeof(matches[0]);
// Fuzzy string match
+ CLEAR_FIELD(matches);
while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) {
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the
@@ -5257,6 +5323,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
NULL, // search pattern
0, // nr
0, // type
+ NULL, // user_data
true) // valid
== QF_FAIL) {
got_int = true;
@@ -5380,7 +5447,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo
// ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
- time_t seconds = (time_t)0;
+ time_t seconds = 0;
for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) {
char *fname = path_try_shorten_fname(cmd_args->fnames[fi]);
if (time(NULL) > seconds) {
@@ -5413,7 +5480,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo
if (buf == NULL) {
if (!got_int) {
- smsg(_("Cannot open file \"%s\""), fname);
+ smsg(0, _("Cannot open file \"%s\""), fname);
}
} else {
// Try for a match in all lines of the buffer.
@@ -5618,7 +5685,7 @@ static void restore_start_dir(char *dirname_start)
static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resulting_dir)
{
// Allocate a buffer without putting it in the buffer list.
- buf_T *newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ buf_T *newbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
if (newbuf == NULL) {
return NULL;
}
@@ -5650,7 +5717,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin
bufref_T newbuf_to_wipe;
newbuf_to_wipe.br_buf = NULL;
- int readfile_result = readfile(fname, NULL, (linenr_T)0, (linenr_T)0,
+ int readfile_result = readfile(fname, NULL, 0, 0,
(linenr_T)MAXLNUM, NULL,
READ_NEW | READ_DUMMY, false);
newbuf->b_locked--;
@@ -5772,28 +5839,21 @@ static int get_qfline_items(qfline_T *qfp, list_T *list)
buf[0] = qfp->qf_type;
buf[1] = NUL;
if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL
- || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum)
- == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("end_lnum"), (varnumber_T)qfp->qf_end_lnum)
- == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("end_lnum"), (varnumber_T)qfp->qf_end_lnum) == FAIL)
|| (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("end_col"), (varnumber_T)qfp->qf_end_col)
- == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol)
- == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("end_col"), (varnumber_T)qfp->qf_end_col) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol) == FAIL)
|| (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL)
- || (tv_dict_add_str(dict, S_LEN("module"),
- (qfp->qf_module == NULL ? "" : (const char *)qfp->qf_module))
- == FAIL)
- || (tv_dict_add_str(dict, S_LEN("pattern"),
- (qfp->qf_pattern == NULL ? "" : (const char *)qfp->qf_pattern))
+ || (tv_dict_add_str(dict, S_LEN("module"), (qfp->qf_module == NULL ? "" : qfp->qf_module))
== FAIL)
- || (tv_dict_add_str(dict, S_LEN("text"),
- (qfp->qf_text == NULL ? "" : (const char *)qfp->qf_text))
+ || (tv_dict_add_str(dict, S_LEN("pattern"), (qfp->qf_pattern == NULL ? "" : qfp->qf_pattern))
== FAIL)
- || (tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid)
- == FAIL)) {
+ || (tv_dict_add_str(dict, S_LEN("text"), (qfp->qf_text == NULL ? "" : qfp->qf_text)) == FAIL)
+ || (tv_dict_add_str(dict, S_LEN("type"), buf) == FAIL)
+ || (qfp->qf_user_data.v_type != VAR_UNKNOWN
+ && tv_dict_add_tv(dict, S_LEN("user_data"), &qfp->qf_user_data) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) == FAIL)) {
// tv_dict_add* fail only if key already exist, but this is a newly
// allocated dictionary which is thus guaranteed to have no existing keys.
abort();
@@ -5897,7 +5957,7 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL);
if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat,
- true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
+ true, 0, 0, NULL, NULL) > 0) {
(void)get_errorlist(qi, NULL, 0, 0, l);
qf_free(&qi->qf_lists[0]);
}
@@ -6016,8 +6076,7 @@ static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what)
qf_idx = INVALID_QFIDX;
}
}
- } else if (di->di_tv.v_type == VAR_STRING
- && strequal((const char *)di->di_tv.vval.v_string, "$")) {
+ } else if (di->di_tv.v_type == VAR_STRING && strequal(di->di_tv.vval.v_string, "$")) {
// Get the last quickfix list number
qf_idx = qi->qf_listcount - 1;
} else {
@@ -6046,7 +6105,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r
int status = OK;
if (flags & QF_GETLIST_TITLE) {
- status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)"");
+ status = tv_dict_add_str(retdict, S_LEN("title"), "");
}
if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
list_T *l = tv_list_alloc(kListLenMayKnow);
@@ -6059,7 +6118,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r
status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
- status = tv_dict_add_str(retdict, S_LEN("context"), (const char *)"");
+ status = tv_dict_add_str(retdict, S_LEN("context"), "");
}
if ((status == OK) && (flags & QF_GETLIST_ID)) {
status = tv_dict_add_nr(retdict, S_LEN("id"), 0);
@@ -6089,8 +6148,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r
/// Return the quickfix list title as 'title' in retdict
static int qf_getprop_title(qf_list_T *qfl, dict_T *retdict)
{
- return tv_dict_add_str(retdict, S_LEN("title"),
- (const char *)qfl->qf_title);
+ return tv_dict_add_str(retdict, S_LEN("title"), qfl->qf_title);
}
// Returns the identifier of the window used to display files from a location
@@ -6274,8 +6332,7 @@ static int qf_setprop_qftf(qf_list_T *qfl, dictitem_T *di)
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry'
/// to true.
-static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_entry,
- bool *valid_entry)
+static int qf_add_entry_from_dict(qf_list_T *qfl, dict_T *d, bool first_entry, bool *valid_entry)
FUNC_ATTR_NONNULL_ALL
{
static bool did_bufnr_emsg;
@@ -6299,6 +6356,9 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_en
if (text == NULL) {
text = xcalloc(1, 1);
}
+ typval_T user_data = { .v_type = VAR_UNKNOWN };
+ tv_dict_get_tv(d, "user_data", &user_data);
+
bool valid = true;
if ((filename == NULL && bufnum == 0)
|| (lnum == 0 && pattern == NULL)) {
@@ -6335,12 +6395,14 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_en
pattern, // search pattern
nr,
type == NULL ? NUL : *type,
+ &user_data,
valid);
xfree(filename);
xfree(module);
xfree(pattern);
xfree(text);
+ tv_clear(&user_data);
if (valid) {
*valid_entry = true;
@@ -6376,13 +6438,12 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char *title,
continue; // Skip non-dict items.
}
- const dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict;
if (d == NULL) {
continue;
}
- retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list),
- &valid_entry);
+ retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list), &valid_entry);
if (retval == QF_FAIL) {
break;
}
@@ -6439,8 +6500,7 @@ static int qf_setprop_get_qfidx(const qf_info_T *qi, const dict_T *what, int act
} else if (action != ' ') {
*newlist = false; // use the specified list
}
- } else if (di->di_tv.v_type == VAR_STRING
- && strequal((const char *)di->di_tv.vval.v_string, "$")) {
+ } else if (di->di_tv.v_type == VAR_STRING && strequal(di->di_tv.vval.v_string, "$")) {
if (!qf_stack_empty(qi)) {
qf_idx = qi->qf_listcount - 1;
} else if (*newlist) {
@@ -6525,7 +6585,7 @@ static int qf_setprop_items_from_lines(qf_info_T *qi, int qf_idx, const dict_T *
qf_free_items(&qi->qf_lists[qf_idx]);
}
if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat,
- false, (linenr_T)0, (linenr_T)0, NULL, NULL) >= 0) {
+ false, 0, 0, NULL, NULL) >= 0) {
retval = OK;
}
@@ -6721,6 +6781,27 @@ int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what
return retval;
}
+static bool mark_quickfix_user_data(qf_info_T *qi, int copyID)
+{
+ bool abort = false;
+ for (int i = 0; i < LISTCOUNT && !abort; i++) {
+ qf_list_T *qfl = &qi->qf_lists[i];
+ if (!qfl->qf_has_user_data) {
+ continue;
+ }
+ qfline_T *qfp;
+ int j;
+ FOR_ALL_QFL_ITEMS(qfl, qfp, j) {
+ typval_T *user_data = &qfp->qf_user_data;
+ if (user_data != NULL && user_data->v_type != VAR_NUMBER
+ && user_data->v_type != VAR_STRING && user_data->v_type != VAR_FLOAT) {
+ abort = abort || set_ref_in_item(user_data, copyID, NULL, NULL);
+ }
+ }
+ }
+ return abort;
+}
+
/// Mark the quickfix context and callback function as in use for all the lists
/// in a quickfix stack.
static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
@@ -6750,6 +6831,11 @@ bool set_ref_in_quickfix(int copyID)
return abort;
}
+ abort = mark_quickfix_user_data(&ql_info, copyID);
+ if (abort) {
+ return abort;
+ }
+
abort = set_ref_in_callback(&qftf_cb, copyID, NULL, NULL);
if (abort) {
return abort;
@@ -6761,6 +6847,11 @@ bool set_ref_in_quickfix(int copyID)
if (abort) {
return abort;
}
+
+ abort = mark_quickfix_user_data(win->w_llist, copyID);
+ if (abort) {
+ return abort;
+ }
}
if (IS_LL_WINDOW(win) && (win->w_llist_ref->qf_refcount == 1)) {
@@ -6946,18 +7037,18 @@ void ex_cexpr(exarg_T *eap)
// Evaluate the expression. When the result is a string or a list we can
// use it to fill the errorlist.
- typval_T tv;
- if (eval0(eap->arg, &tv, &eap->nextcmd, true) == FAIL) {
+ typval_T *tv = eval_expr(eap->arg, eap);
+ if (tv == NULL) {
return;
}
- if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
- || tv.v_type == VAR_LIST) {
+ if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL)
+ || tv->v_type == VAR_LIST) {
incr_quickfix_busy();
- int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm,
+ int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm,
(eap->cmdidx != CMD_caddexpr
&& eap->cmdidx != CMD_laddexpr),
- (linenr_T)0, (linenr_T)0,
+ 0, 0,
qf_cmdtitle(*eap->cmdlinep), NULL);
if (qf_stack_empty(qi)) {
decr_quickfix_busy();
@@ -6985,7 +7076,7 @@ void ex_cexpr(exarg_T *eap)
emsg(_("E777: String or List expected"));
}
cleanup:
- tv_clear(&tv);
+ tv_free(tv);
}
// Get the location list for ":lhelpgrep"
@@ -7018,7 +7109,7 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch)
while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
char *line = IObuff;
- if (vim_regexec(p_regmatch, line, (colnr_T)0)) {
+ if (vim_regexec(p_regmatch, line, 0)) {
int l = (int)strlen(line);
// remove trailing CR, LF, spaces, etc.
@@ -7041,7 +7132,8 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch)
NULL, // search pattern
0, // nr
1, // type
- true) // valid
+ NULL, // user_data
+ true) // valid
== QF_FAIL) {
got_int = true;
if (line != IObuff) {
@@ -7101,7 +7193,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char
while (*p != NUL && !got_int) {
copy_option_part(&p, NameBuff, MAXPATHL, ",");
- hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, (char *)lang);
+ hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, lang);
}
}
@@ -7130,7 +7222,7 @@ void ex_helpgrep(exarg_T *eap)
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *const save_cpo = p_cpo;
const bool save_cpo_allocated = is_option_allocated("cpo");
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
bool new_qi = false;
if (is_loclist_cmd(eap->cmdidx)) {
@@ -7161,13 +7253,13 @@ void ex_helpgrep(exarg_T *eap)
updated = true;
}
- if (p_cpo == empty_option) {
+ if (p_cpo == empty_string_option) {
p_cpo = save_cpo;
} else {
// Darn, some plugin changed the value. If it's still empty it was
// changed and restored, need to restore in the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
if (save_cpo_allocated) {
free_string_option(save_cpo);
@@ -7214,6 +7306,19 @@ void ex_helpgrep(exarg_T *eap)
}
}
+#if defined(EXITFREE)
+void free_quickfix(void)
+{
+ qf_free_all(NULL);
+ // Free all location lists
+ FOR_ALL_TAB_WINDOWS(tab, win) {
+ qf_free_all(win);
+ }
+
+ ga_clear(&qfga);
+}
+#endif
+
static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv)
{
if (what_arg->v_type == VAR_UNKNOWN) {
@@ -7250,7 +7355,7 @@ void f_getqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_qf_loc_list(true, NULL, &argvars[0], rettv);
}
-/// Create quickfix/location list from VimL values
+/// Create quickfix/location list from Vimscript values
///
/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
/// args argument in which case errors out, including VAR_UNKNOWN parameters.
@@ -7268,7 +7373,7 @@ void f_getqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(2, 3)
{
- static char *e_invact = N_("E927: Invalid action: '%s'");
+ static const char *e_invact = N_("E927: Invalid action: '%s'");
const char *title = NULL;
char action = ' ';
static int recursive = 0;
diff --git a/src/nvim/quickfix.h b/src/nvim/quickfix.h
index 0da43e436c..9c49564d57 100644
--- a/src/nvim/quickfix.h
+++ b/src/nvim/quickfix.h
@@ -1,15 +1,18 @@
-#ifndef NVIM_QUICKFIX_H
-#define NVIM_QUICKFIX_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
-// flags for skip_vimgrep_pat()
-#define VGR_GLOBAL 1
-#define VGR_NOJUMP 2
-#define VGR_FUZZY 4
+/// flags for skip_vimgrep_pat()
+enum {
+ VGR_GLOBAL = 1,
+ VGR_NOJUMP = 2,
+ VGR_FUZZY = 4,
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.h.generated.h"
#endif
-#endif // NVIM_QUICKFIX_H
diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c
index 1088dd3778..f74f68adb6 100644
--- a/src/nvim/rbuffer.c
+++ b/src/nvim/rbuffer.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
-#include "nvim/macros.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/rbuffer.h"
diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h
index 63d5119004..55e9849d3d 100644
--- a/src/nvim/rbuffer.h
+++ b/src/nvim/rbuffer.h
@@ -11,14 +11,11 @@
// stopped(automatic backpressure handling)
//
// Reference: http://en.wikipedia.org/wiki/Circular_buffer
-#ifndef NVIM_RBUFFER_H
-#define NVIM_RBUFFER_H
+#pragma once
#include <stddef.h>
#include <stdint.h>
-struct rbuffer;
-
// Macros that simplify working with the read/write pointers directly by hiding
// ring buffer wrap logic. Some examples:
//
@@ -38,8 +35,6 @@ struct rbuffer;
//
// Note that the rbuffer_{produced,consumed} calls are necessary or these macros
// create infinite loops
-//
-// -V:RBUFFER_UNTIL_EMPTY:1044
#define RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) \
for (size_t rcnt = 0, _r = 1; _r; _r = 0) /* NOLINT(readability/braces) */ \
for (char *rptr = rbuffer_read_ptr(buf, &rcnt); /* NOLINT(readability/braces) */ \
@@ -88,5 +83,3 @@ struct rbuffer {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "rbuffer.h.generated.h"
#endif
-
-#endif // NVIM_RBUFFER_H
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 122f3e2020..3536196a3b 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub()
// By default: do not create debugging logs or files related to regular
@@ -10,38 +7,170 @@
// #define REGEXP_DEBUG
#include <assert.h>
+#include <ctype.h>
#include <inttypes.h>
+#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <string.h>
#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+// Structure used to save the current input state, when it needs to be
+// restored after trying a match. Used by reg_save() and reg_restore().
+// Also stores the length of "backpos".
+typedef struct {
+ union {
+ uint8_t *ptr; // rex.input pointer, for single-line regexp
+ lpos_T pos; // rex.input pos, for multi-line regexp
+ } rs_u;
+ int rs_len;
+} regsave_T;
+
+// struct to save start/end pointer/position in for \(\)
+typedef struct {
+ union {
+ uint8_t *ptr;
+ lpos_T pos;
+ } se_u;
+} save_se_T;
+
+// Values for rs_state in regitem_T.
+typedef enum regstate_E {
+ RS_NOPEN = 0, // NOPEN and NCLOSE
+ RS_MOPEN, // MOPEN + [0-9]
+ RS_MCLOSE, // MCLOSE + [0-9]
+ RS_ZOPEN, // ZOPEN + [0-9]
+ RS_ZCLOSE, // ZCLOSE + [0-9]
+ RS_BRANCH, // BRANCH
+ RS_BRCPLX_MORE, // BRACE_COMPLEX and trying one more match
+ RS_BRCPLX_LONG, // BRACE_COMPLEX and trying longest match
+ RS_BRCPLX_SHORT, // BRACE_COMPLEX and trying shortest match
+ RS_NOMATCH, // NOMATCH
+ RS_BEHIND1, // BEHIND / NOBEHIND matching rest
+ RS_BEHIND2, // BEHIND / NOBEHIND matching behind part
+ RS_STAR_LONG, // STAR/PLUS/BRACE_SIMPLE longest match
+ RS_STAR_SHORT, // STAR/PLUS/BRACE_SIMPLE shortest match
+} regstate_T;
+
+// When there are alternatives a regstate_T is put on the regstack to remember
+// what we are doing.
+// Before it may be another type of item, depending on rs_state, to remember
+// more things.
+typedef struct regitem_S {
+ regstate_T rs_state; // what we are doing, one of RS_ above
+ int16_t rs_no; // submatch nr or BEHIND/NOBEHIND
+ uint8_t *rs_scan; // current node in program
+ union {
+ save_se_T sesave;
+ regsave_T regsave;
+ } rs_un; // room for saving rex.input
+} regitem_T;
+
+// used for BEHIND and NOBEHIND matching
+typedef struct regbehind_S {
+ regsave_T save_after;
+ regsave_T save_behind;
+ int save_need_clear_subexpr;
+ save_se_T save_start[NSUBEXP];
+ save_se_T save_end[NSUBEXP];
+} regbehind_T;
+
+// Since the out pointers in the list are always
+// uninitialized, we use the pointers themselves
+// as storage for the Ptrlists.
+typedef union Ptrlist Ptrlist;
+union Ptrlist {
+ Ptrlist *next;
+ nfa_state_T *s;
+};
+
+struct Frag {
+ nfa_state_T *start;
+ Ptrlist *out;
+};
+typedef struct Frag Frag_T;
+
+typedef struct {
+ int in_use; ///< number of subexpr with useful info
+
+ // When REG_MULTI is true list.multi is used, otherwise list.line.
+ union {
+ struct multipos {
+ linenr_T start_lnum;
+ linenr_T end_lnum;
+ colnr_T start_col;
+ colnr_T end_col;
+ } multi[NSUBEXP];
+ struct linepos {
+ uint8_t *start;
+ uint8_t *end;
+ } line[NSUBEXP];
+ } list;
+ colnr_T orig_start_col; // list.multi[0].start_col without \zs
+} regsub_T;
+
+typedef struct {
+ regsub_T norm; // \( .. \) matches
+ regsub_T synt; // \z( .. \) matches
+} regsubs_T;
+
+// nfa_pim_T stores a Postponed Invisible Match.
+typedef struct nfa_pim_S nfa_pim_T;
+struct nfa_pim_S {
+ int result; // NFA_PIM_*, see below
+ nfa_state_T *state; // the invisible match start state
+ regsubs_T subs; // submatch info, only party used
+ union {
+ lpos_T pos;
+ uint8_t *ptr;
+ } end; // where the match must end
+};
+
+// nfa_thread_T contains execution information of a NFA state
+typedef struct {
+ nfa_state_T *state;
+ int count;
+ nfa_pim_T pim; // if pim.result != NFA_PIM_UNUSED: postponed
+ // invisible match
+ regsubs_T subs; // submatch info, only party used
+} nfa_thread_T;
+
+// nfa_list_T contains the alternative NFA execution states.
+typedef struct {
+ nfa_thread_T *t; ///< allocated array of states
+ int n; ///< nr of states currently in "t"
+ int len; ///< max nr of states in "t"
+ int id; ///< ID of the list
+ int has_pim; ///< true when any state has a PIM
+} nfa_list_T;
#ifdef REGEXP_DEBUG
// show/save debugging data when BT engine is used
@@ -59,11 +188,7 @@
#define un_Magic(x) ((x) + 256)
#define is_Magic(x) ((x) < 0)
-// We should define ftpr as a pointer to a function returning a pointer to
-// a function returning a pointer to a function ...
-// This is impossible, so we declare a pointer to a function returning a
-// pointer to a function returning void. This should work for all compilers.
-typedef void (*(*fptr_T)(int *, int))(void);
+typedef void (*fptr_T)(int *, int);
static int no_Magic(int x)
{
@@ -97,31 +222,41 @@ static int toggle_Magic(int x)
#define EMSG2_RET_NULL(m, c) \
return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL)
#define EMSG3_RET_NULL(m, c, a) \
- return (semsg((const char *)(m), (c) ? "" : "\\", (a)), rc_did_emsg = true, (void *)NULL)
+ return (semsg((m), (c) ? "" : "\\", (a)), rc_did_emsg = true, (void *)NULL)
#define EMSG2_RET_FAIL(m, c) \
return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL)
-#define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_("E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL)
-
-#define MAX_LIMIT (32767L << 16L)
-
-static char e_missingbracket[] = N_("E769: Missing ] after %s[");
-static char e_reverse_range[] = N_("E944: Reverse range in character class");
-static char e_large_class[] = N_("E945: Range too large in character class");
-static char e_unmatchedpp[] = N_("E53: Unmatched %s%%(");
-static char e_unmatchedp[] = N_("E54: Unmatched %s(");
-static char e_unmatchedpar[] = N_("E55: Unmatched %s)");
-static char e_z_not_allowed[] = N_("E66: \\z( not allowed here");
-static char e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here");
-static char e_missing_sb[] = N_("E69: Missing ] after %s%%[");
-static char e_empty_sb[] = N_("E70: Empty %s%%[]");
-static char e_recursive[] = N_("E956: Cannot use pattern recursively");
-static char e_regexp_number_after_dot_pos_search_chr[]
+#define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_(e_invalid_item_in_str_brackets), reg_magic == MAGIC_ALL)
+
+#define MAX_LIMIT (32767 << 16)
+
+static const char e_invalid_character_after_str_at[]
+ = N_("E59: Invalid character after %s@");
+static const char e_invalid_use_of_underscore[]
+ = N_("E63: Invalid use of \\_");
+static const char e_pattern_uses_more_memory_than_maxmempattern[]
+ = N_("E363: Pattern uses more memory than 'maxmempattern'");
+static const char e_invalid_item_in_str_brackets[]
+ = N_("E369: Invalid item in %s%%[]");
+static const char e_missing_delimiter_after_search_pattern_str[]
+ = N_("E654: Missing delimiter after search pattern: %s");
+static const char e_missingbracket[] = N_("E769: Missing ] after %s[");
+static const char e_reverse_range[] = N_("E944: Reverse range in character class");
+static const char e_large_class[] = N_("E945: Range too large in character class");
+static const char e_unmatchedpp[] = N_("E53: Unmatched %s%%(");
+static const char e_unmatchedp[] = N_("E54: Unmatched %s(");
+static const char e_unmatchedpar[] = N_("E55: Unmatched %s)");
+static const char e_z_not_allowed[] = N_("E66: \\z( not allowed here");
+static const char e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here");
+static const char e_missing_sb[] = N_("E69: Missing ] after %s%%[");
+static const char e_empty_sb[] = N_("E70: Empty %s%%[]");
+static const char e_recursive[] = N_("E956: Cannot use pattern recursively");
+static const char e_regexp_number_after_dot_pos_search_chr[]
= N_("E1204: No Number allowed after .: '\\%%%c'");
-static char e_nfa_regexp_missing_value_in_chr[]
+static const char e_nfa_regexp_missing_value_in_chr[]
= N_("E1273: (NFA regexp) missing value in '\\%%%c'");
-static char e_atom_engine_must_be_at_start_of_pattern[]
+static const char e_atom_engine_must_be_at_start_of_pattern[]
= N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern");
-static char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep");
+static const char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep");
#define NOT_MULTI 0
#define MULTI_ONE 1
@@ -491,7 +626,7 @@ char *skip_regexp_err(char *startp, int delim, int magic)
char *p = skip_regexp(startp, delim, magic);
if (*p != delim) {
- semsg(_("E654: missing delimiter after search pattern: %s"), startp);
+ semsg(_(e_missing_delimiter_after_search_pattern_str), startp);
return NULL;
}
return p;
@@ -869,7 +1004,7 @@ static int64_t getoctchrs(void)
int c;
int i;
- for (i = 0; i < 3 && nr < 040; i++) { // -V536
+ for (i = 0; i < 3 && nr < 040; i++) {
c = (uint8_t)regparse[0];
if (c < '0' || c > '7') {
break;
@@ -889,11 +1024,11 @@ static int64_t getoctchrs(void)
// If the first character is '-', then the range is reversed.
// Should end with 'end'. If minval is missing, zero is default, if maxval is
// missing, a very big number is the default.
-static int read_limits(long *minval, long *maxval)
+static int read_limits(int *minval, int *maxval)
{
int reverse = false;
char *first_char;
- long tmp;
+ int tmp;
if (*regparse == '-') {
// Starts with '-', so reverse the range later.
@@ -901,10 +1036,10 @@ static int read_limits(long *minval, long *maxval)
reverse = true;
}
first_char = regparse;
- *minval = getdigits_long(&regparse, false, 0);
+ *minval = getdigits_int(&regparse, false, 0);
if (*regparse == ',') { // There is a comma.
if (ascii_isdigit(*++regparse)) {
- *maxval = getdigits_long(&regparse, false, MAX_LIMIT);
+ *maxval = getdigits_int(&regparse, false, MAX_LIMIT);
} else {
*maxval = MAX_LIMIT;
}
@@ -989,6 +1124,8 @@ typedef struct {
// flag in the regexp. Defaults to false, always.
bool reg_icombine;
+ bool reg_nobreak;
+
// Copy of "rmm_maxcol": maximum column to search for a match. Zero when
// there is no maximum.
colnr_T reg_maxcol;
@@ -1011,6 +1148,13 @@ typedef struct {
static regexec_T rex;
static bool rex_in_use = false;
+static void reg_breakcheck(void)
+{
+ if (!rex.reg_nobreak) {
+ fast_breakcheck();
+ }
+}
+
// Return true if character 'c' is included in 'iskeyword' option for
// "reg_buf" buffer.
static bool reg_iswordc(int c)
@@ -1030,7 +1174,7 @@ static char *reg_getline(linenr_T lnum)
// Must have matched the "\n" in the last line.
return "";
}
- return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false);
+ return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum);
}
static uint8_t *reg_startzp[NSUBEXP]; // Workspace to mark beginning
@@ -1150,9 +1294,7 @@ static bool reg_match_visual(void)
rex.line = (uint8_t *)reg_getline(rex.lnum);
rex.input = rex.line + col;
- unsigned int cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
- assert(cols_u <= MAXCOL);
- colnr_T cols = (colnr_T)cols_u;
+ colnr_T cols = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
if (cols < start || cols > end - (*p_sel == 'e')) {
return false;
}
@@ -1221,7 +1363,7 @@ static void reg_nextline(void)
{
rex.line = (uint8_t *)reg_getline(++rex.lnum);
rex.input = rex.line;
- fast_breakcheck();
+ reg_breakcheck();
}
// Check whether a backreference matches.
@@ -1239,7 +1381,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
if (bytelen != NULL) {
*bytelen = 0;
}
- for (;;) {
+ while (true) {
// Since getting one line may invalidate the other, need to make copy.
// Slow!
if (rex.line != reg_tofree) {
@@ -1393,7 +1535,7 @@ static int cstrncmp(char *s1, char *s2, int *n)
// if it failed and it's utf8 and we want to combineignore:
if (result != 0 && rex.reg_icombine) {
- char *str1, *str2;
+ const char *str1, *str2;
int c1, c2, c11, c12;
int junk;
@@ -1403,8 +1545,8 @@ static int cstrncmp(char *s1, char *s2, int *n)
str2 = s2;
c1 = c2 = 0;
while ((int)(str1 - s1) < *n) {
- c1 = mb_ptr2char_adv((const char **)&str1);
- c2 = mb_ptr2char_adv((const char **)&str2);
+ c1 = mb_ptr2char_adv(&str1);
+ c2 = mb_ptr2char_adv(&str2);
// decompose the character if necessary, into 'base' characters
// because I don't care about Arabic, I will hard-code the Hebrew
@@ -1475,34 +1617,14 @@ static inline char *cstrchr(const char *const s, const int c)
// regsub stuff //
////////////////////////////////////////////////////////////////
-// This stuff below really confuses cc on an SGI -- webb
-
-static fptr_T do_upper(int *d, int c)
-{
- *d = mb_toupper(c);
-
- return (fptr_T)NULL;
-}
-
-static fptr_T do_Upper(int *d, int c)
+static void do_upper(int *d, int c)
{
*d = mb_toupper(c);
-
- return (fptr_T)do_Upper;
-}
-
-static fptr_T do_lower(int *d, int c)
-{
- *d = mb_tolower(c);
-
- return (fptr_T)NULL;
}
-static fptr_T do_Lower(int *d, int c)
+static void do_lower(int *d, int c)
{
*d = mb_tolower(c);
-
- return (fptr_T)do_Lower;
}
/// regtilde(): Replace tildes in the pattern by the old pattern.
@@ -1518,41 +1640,46 @@ static fptr_T do_Lower(int *d, int c)
char *regtilde(char *source, int magic, bool preview)
{
char *newsub = source;
- char *tmpsub;
- char *p;
- int len;
- int prevlen;
- for (p = newsub; *p; p++) {
+ for (char *p = newsub; *p; p++) {
if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) {
if (reg_prev_sub != NULL) {
// length = len(newsub) - 1 + len(prev_sub) + 1
- prevlen = (int)strlen(reg_prev_sub);
- tmpsub = xmalloc(strlen(newsub) + (size_t)prevlen);
+ // Avoid making the text longer than MAXCOL, it will cause
+ // trouble at some point.
+ size_t prevsublen = strlen(reg_prev_sub);
+ size_t newsublen = strlen(newsub);
+ if (prevsublen > MAXCOL || newsublen > MAXCOL
+ || newsublen + prevsublen > MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
+
+ char *tmpsub = xmalloc(newsublen + prevsublen);
// copy prefix
- len = (int)(p - newsub); // not including ~
- memmove(tmpsub, newsub, (size_t)len);
+ size_t prefixlen = (size_t)(p - newsub); // not including ~
+ memmove(tmpsub, newsub, prefixlen);
// interpret tilde
- memmove(tmpsub + len, reg_prev_sub, (size_t)prevlen);
+ memmove(tmpsub + prefixlen, reg_prev_sub, prevsublen);
// copy postfix
if (!magic) {
- p++; // back off backslash
+ p++; // back off backslash
}
- STRCPY(tmpsub + len + prevlen, p + 1);
+ STRCPY(tmpsub + prefixlen + prevsublen, p + 1);
- if (newsub != source) { // already allocated newsub
+ if (newsub != source) { // allocated newsub before
xfree(newsub);
}
newsub = tmpsub;
- p = newsub + len + prevlen;
+ p = newsub + prefixlen + prevsublen;
} else if (magic) {
- STRMOVE(p, p + 1); // remove '~'
+ STRMOVE(p, p + 1); // remove '~'
} else {
- STRMOVE(p, p + 2); // remove '\~'
+ STRMOVE(p, p + 2); // remove '\~'
}
p--;
} else {
- if (*p == '\\' && p[1]) { // skip escaped characters
+ if (*p == '\\' && p[1]) { // skip escaped characters
p++;
}
p += utfc_ptr2len(p) - 1;
@@ -1746,9 +1873,10 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
// "flags & REGSUB_COPY" == 0 to the call with
// "flags & REGSUB_COPY" != 0.
if (copy) {
- if (eval_result[nested] != NULL) {
+ size_t reslen = eval_result[nested] != NULL ? strlen(eval_result[nested]) : 0;
+ if (eval_result[nested] != NULL && reslen < (size_t)destlen) {
STRCPY(dest, eval_result[nested]);
- dst += strlen(eval_result[nested]);
+ dst += reslen;
XFREE_CLEAR(eval_result[nested]);
}
} else {
@@ -1812,7 +1940,7 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
}
tv_clear(&rettv);
} else {
- eval_result[nested] = eval_to_string(source + 2, NULL, true);
+ eval_result[nested] = eval_to_string(source + 2, true);
}
nesting--;
@@ -1866,16 +1994,16 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
} else if (vim_strchr("uUlLeE", (uint8_t)(*src))) {
switch (*src++) {
case 'u':
- func_one = (fptr_T)do_upper;
+ func_one = do_upper;
continue;
case 'U':
- func_all = (fptr_T)do_Upper;
+ func_all = do_upper;
continue;
case 'l':
- func_one = (fptr_T)do_lower;
+ func_one = do_lower;
continue;
case 'L':
- func_all = (fptr_T)do_Lower;
+ func_all = do_lower;
continue;
case 'e':
case 'E':
@@ -1934,11 +2062,13 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
} else {
c = utf_ptr2char(src - 1);
}
+
// Write to buffer, if copy is set.
if (func_one != NULL) {
- func_one = (fptr_T)(func_one(&cc, c));
+ func_one(&cc, c);
+ func_one = NULL;
} else if (func_all != NULL) {
- func_all = (fptr_T)(func_all(&cc, c));
+ func_all(&cc, c);
} else {
// just copy
cc = c;
@@ -1994,7 +2124,7 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
}
}
if (s != NULL) {
- for (;;) {
+ while (true) {
if (len == 0) {
if (REG_MULTI) {
if (rex.reg_mmatch->endpos[no].lnum == clnum) {
@@ -2041,11 +2171,10 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
c = utf_ptr2char(s);
if (func_one != (fptr_T)NULL) {
- // Turbo C complains without the typecast
- func_one = (fptr_T)(func_one(&cc, c));
+ func_one(&cc, c);
+ func_one = NULL;
} else if (func_all != (fptr_T)NULL) {
- // Turbo C complains without the typecast
- func_all = (fptr_T)(func_all(&cc, c));
+ func_all(&cc, c);
} else { // just copy
cc = c;
}
@@ -2175,7 +2304,7 @@ char *reg_submatch(int no)
}
len += rsm.sm_mmatch->endpos[no].col;
if (round == 2) {
- retval[len] = NUL; // -V595
+ retval[len] = NUL;
}
len++;
}
@@ -2237,12 +2366,12 @@ list_T *reg_submatch_list(int no)
tv_list_append_string(list, s, ecol);
}
} else {
- s = (const char *)rsm.sm_match->startp[no];
+ s = rsm.sm_match->startp[no];
if (s == NULL || rsm.sm_match->endp[no] == NULL) {
return NULL;
}
list = tv_list_alloc(1);
- tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s);
+ tv_list_append_string(list, s, rsm.sm_match->endp[no] - s);
}
tv_list_ref(list);
@@ -2265,14 +2394,13151 @@ static void init_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_
rex.reg_line_lbr = false;
rex.reg_ic = rmp->rmm_ic;
rex.reg_icombine = false;
+ rex.reg_nobreak = rmp->regprog->re_flags & RE_NOBREAK;
rex.reg_maxcol = rmp->rmm_maxcol;
}
-// XXX Do not allow headers generator to catch definitions from regexp_nfa.c
-#ifndef DO_NOT_DEFINE_EMPTY_ATTRIBUTES
-# include "nvim/regexp_bt.c"
-# include "nvim/regexp_nfa.c"
+// regexp_bt.c {{{1
+
+// Backtracking regular expression implementation.
+//
+// NOTICE:
+//
+// This is NOT the original regular expression code as written by Henry
+// Spencer. This code has been modified specifically for use with the VIM
+// editor, and should not be used separately from Vim. If you want a good
+// regular expression library, get the original code. The copyright notice
+// that follows is from the original.
+//
+// END NOTICE
+//
+// Copyright (c) 1986 by University of Toronto.
+// Written by Henry Spencer. Not derived from licensed software.
+//
+// Permission is granted to anyone to use this software for any
+// purpose on any computer system, and to redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The author is not responsible for the consequences of use of
+// this software, no matter how awful, even if they arise
+// from defects in it.
+//
+// 2. The origin of this software must not be misrepresented, either
+// by explicit claim or by omission.
+//
+// 3. Altered versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// Beware that some of this code is subtly aware of the way operator
+// precedence is structured in regular expressions. Serious changes in
+// regular-expression syntax might require a total rethink.
+//
+// Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert
+// Webb, Ciaran McCreesh and Bram Moolenaar.
+// Named character class support added by Walter Briscoe (1998 Jul 01)
+
+// The "internal use only" fields in regexp_defs.h are present to pass info from
+// compile to execute that permits the execute phase to run lots faster on
+// simple cases. They are:
+//
+// regstart char that must begin a match; NUL if none obvious; Can be a
+// multi-byte character.
+// reganch is the match anchored (at beginning-of-line only)?
+// regmust string (pointer into program) that match must include, or NULL
+// regmlen length of regmust string
+// regflags RF_ values or'ed together
+//
+// Regstart and reganch permit very fast decisions on suitable starting points
+// for a match, cutting down the work a lot. Regmust permits fast rejection
+// of lines that cannot possibly match. The regmust tests are costly enough
+// that vim_regcomp() supplies a regmust only if the r.e. contains something
+// potentially expensive (at present, the only such thing detected is * or +
+// at the start of the r.e., which can involve a lot of backup). Regmlen is
+// supplied because the test in vim_regexec() needs it and vim_regcomp() is
+// computing it anyway.
+
+// Structure for regexp "program". This is essentially a linear encoding
+// of a nondeterministic finite-state machine (aka syntax charts or
+// "railroad normal form" in parsing technology). Each node is an opcode
+// plus a "next" pointer, possibly plus an operand. "Next" pointers of
+// all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next"
+// pointer with a BRANCH on both ends of it is connecting two alternatives.
+// (Here we have one of the subtle syntax dependencies: an individual BRANCH
+// (as opposed to a collection of them) is never concatenated with anything
+// because of operator precedence). The "next" pointer of a BRACES_COMPLEX
+// node points to the node after the stuff to be repeated.
+// The operand of some types of node is a literal string; for others, it is a
+// node leading into a sub-FSM. In particular, the operand of a BRANCH node
+// is the first node of the branch.
+// (NB this is *not* a tree structure: the tail of the branch connects to the
+// thing following the set of BRANCHes.)
+//
+// pattern is coded like:
+//
+// +-----------------+
+// | V
+// <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END
+// | ^ | ^
+// +------+ +----------+
+//
+//
+// +------------------+
+// V |
+// <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END
+// | | ^ ^
+// | +---------------+ |
+// +---------------------------------------------+
+//
+//
+// +----------------------+
+// V |
+// <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END
+// | | ^ ^
+// | +-----------+ |
+// +--------------------------------------------------+
+//
+//
+// +-------------------------+
+// V |
+// <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END
+// | | ^
+// | +----------------+
+// +-----------------------------------------------+
+//
+//
+// <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END
+// | | ^ ^
+// | +----------------+ |
+// +--------------------------------+
+//
+// +---------+
+// | V
+// \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END
+// | | | | ^ ^
+// | | | +-----+ |
+// | | +----------------+ |
+// | +---------------------------+ |
+// +------------------------------------------------------+
+//
+// They all start with a BRANCH for "\|" alternatives, even when there is only
+// one alternative.
+
+// The opcodes are:
+
+// definition number opnd? meaning
+#define END 0 // End of program or NOMATCH operand.
+#define BOL 1 // Match "" at beginning of line.
+#define EOL 2 // Match "" at end of line.
+#define BRANCH 3 // node Match this alternative, or the
+ // next...
+#define BACK 4 // Match "", "next" ptr points backward.
+#define EXACTLY 5 // str Match this string.
+#define NOTHING 6 // Match empty string.
+#define STAR 7 // node Match this (simple) thing 0 or more
+ // times.
+#define PLUS 8 // node Match this (simple) thing 1 or more
+ // times.
+#define MATCH 9 // node match the operand zero-width
+#define NOMATCH 10 // node check for no match with operand
+#define BEHIND 11 // node look behind for a match with operand
+#define NOBEHIND 12 // node look behind for no match with operand
+#define SUBPAT 13 // node match the operand here
+#define BRACE_SIMPLE 14 // node Match this (simple) thing between m and
+ // n times (\{m,n\}).
+#define BOW 15 // Match "" after [^a-zA-Z0-9_]
+#define EOW 16 // Match "" at [^a-zA-Z0-9_]
+#define BRACE_LIMITS 17 // nr nr define the min & max for BRACE_SIMPLE
+ // and BRACE_COMPLEX.
+#define NEWL 18 // Match line-break
+#define BHPOS 19 // End position for BEHIND or NOBEHIND
+
+// character classes: 20-48 normal, 50-78 include a line-break
+#define ADD_NL 30
+#define FIRST_NL ANY + ADD_NL
+#define ANY 20 // Match any one character.
+#define ANYOF 21 // str Match any character in this string.
+#define ANYBUT 22 // str Match any character not in this
+ // string.
+#define IDENT 23 // Match identifier char
+#define SIDENT 24 // Match identifier char but no digit
+#define KWORD 25 // Match keyword char
+#define SKWORD 26 // Match word char but no digit
+#define FNAME 27 // Match file name char
+#define SFNAME 28 // Match file name char but no digit
+#define PRINT 29 // Match printable char
+#define SPRINT 30 // Match printable char but no digit
+#define WHITE 31 // Match whitespace char
+#define NWHITE 32 // Match non-whitespace char
+#define DIGIT 33 // Match digit char
+#define NDIGIT 34 // Match non-digit char
+#define HEX 35 // Match hex char
+#define NHEX 36 // Match non-hex char
+#define OCTAL 37 // Match octal char
+#define NOCTAL 38 // Match non-octal char
+#define WORD 39 // Match word char
+#define NWORD 40 // Match non-word char
+#define HEAD 41 // Match head char
+#define NHEAD 42 // Match non-head char
+#define ALPHA 43 // Match alpha char
+#define NALPHA 44 // Match non-alpha char
+#define LOWER 45 // Match lowercase char
+#define NLOWER 46 // Match non-lowercase char
+#define UPPER 47 // Match uppercase char
+#define NUPPER 48 // Match non-uppercase char
+#define LAST_NL NUPPER + ADD_NL
+#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL)
+
+#define MOPEN 80 // -89 Mark this point in input as start of
+ // \( … \) subexpr. MOPEN + 0 marks start of
+ // match.
+#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks
+ // end of match.
+#define BACKREF 100 // -109 node Match same string again \1-\9.
+
+#define ZOPEN 110 // -119 Mark this point in input as start of
+ // \z( … \) subexpr.
+#define ZCLOSE 120 // -129 Analogous to ZOPEN.
+#define ZREF 130 // -139 node Match external submatch \z1-\z9
+
+#define BRACE_COMPLEX 140 // -149 node Match nodes between m & n times
+
+#define NOPEN 150 // Mark this point in input as start of
+ // \%( subexpr.
+#define NCLOSE 151 // Analogous to NOPEN.
+
+#define MULTIBYTECODE 200 // mbc Match one multi-byte character
+#define RE_BOF 201 // Match "" at beginning of file.
+#define RE_EOF 202 // Match "" at end of file.
+#define CURSOR 203 // Match location of cursor.
+
+#define RE_LNUM 204 // nr cmp Match line number
+#define RE_COL 205 // nr cmp Match column number
+#define RE_VCOL 206 // nr cmp Match virtual column number
+
+#define RE_MARK 207 // mark cmp Match mark position
+#define RE_VISUAL 208 // Match Visual area
+#define RE_COMPOSING 209 // any composing characters
+
+// Flags to be passed up and down.
+#define HASWIDTH 0x1 // Known never to match null string.
+#define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand.
+#define SPSTART 0x4 // Starts with * or +.
+#define HASNL 0x8 // Contains some \n.
+#define HASLOOKBH 0x10 // Contains "\@<=" or "\@<!".
+#define WORST 0 // Worst case.
+
+static int prevchr_len; ///< byte length of previous char
+static int num_complex_braces; ///< Complex \{...} count
+static uint8_t *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
+static int64_t regsize; ///< Code size.
+static int reg_toolong; ///< true when offset out of range
+static uint8_t had_endbrace[NSUBEXP]; ///< flags, true if end of () found
+static int64_t brace_min[10]; ///< Minimums for complex brace repeats
+static int64_t brace_max[10]; ///< Maximums for complex brace repeats
+static int brace_count[10]; ///< Current counts for complex brace repeats
+static int one_exactly = false; ///< only do one char for EXACTLY
+
+// When making changes to classchars also change nfa_classcodes.
+static uint8_t *classchars = (uint8_t *)".iIkKfFpPsSdDxXoOwWhHaAlLuU";
+static int classcodes[] = {
+ ANY, IDENT, SIDENT, KWORD, SKWORD,
+ FNAME, SFNAME, PRINT, SPRINT,
+ WHITE, NWHITE, DIGIT, NDIGIT,
+ HEX, NHEX, OCTAL, NOCTAL,
+ WORD, NWORD, HEAD, NHEAD,
+ ALPHA, NALPHA, LOWER, NLOWER,
+ UPPER, NUPPER
+};
+
+// When regcode is set to this value, code is not emitted and size is computed
+// instead.
+#define JUST_CALC_SIZE ((uint8_t *)-1)
+
+// used for STAR, PLUS and BRACE_SIMPLE matching
+typedef struct regstar_S {
+ int nextb; // next byte
+ int nextb_ic; // next byte reverse case
+ int64_t count;
+ int64_t minval;
+ int64_t maxval;
+} regstar_T;
+
+// used to store input position when a BACK was encountered, so that we now if
+// we made any progress since the last time.
+typedef struct backpos_S {
+ uint8_t *bp_scan; // "scan" where BACK was encountered
+ regsave_T bp_pos; // last input position
+} backpos_T;
+
+// "regstack" and "backpos" are used by regmatch(). They are kept over calls
+// to avoid invoking malloc() and free() often.
+// "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T
+// or regbehind_T.
+// "backpos_T" is a table with backpos_T for BACK
+static garray_T regstack = GA_EMPTY_INIT_VALUE;
+static garray_T backpos = GA_EMPTY_INIT_VALUE;
+
+static regsave_T behind_pos;
+
+// Both for regstack and backpos tables we use the following strategy of
+// allocation (to reduce malloc/free calls):
+// - Initial size is fairly small.
+// - When needed, the tables are grown bigger (8 times at first, double after
+// that).
+// - After executing the match we free the memory only if the array has grown.
+// Thus the memory is kept allocated when it's at the initial size.
+// This makes it fast while not keeping a lot of memory allocated.
+// A three times speed increase was observed when using many simple patterns.
+#define REGSTACK_INITIAL 2048
+#define BACKPOS_INITIAL 64
+
+// Opcode notes:
+//
+// BRANCH The set of branches constituting a single choice are hooked
+// together with their "next" pointers, since precedence prevents
+// anything being concatenated to any individual branch. The
+// "next" pointer of the last BRANCH in a choice points to the
+// thing following the whole choice. This is also where the
+// final "next" pointer of each individual branch points; each
+// branch starts with the operand node of a BRANCH node.
+//
+// BACK Normal "next" pointers all implicitly point forward; BACK
+// exists to make loop structures possible.
+//
+// STAR,PLUS '=', and complex '*' and '+', are implemented as circular
+// BRANCH structures using BACK. Simple cases (one character
+// per match) are implemented with STAR and PLUS for speed
+// and to minimize recursive plunges.
+//
+// BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX
+// node, and defines the min and max limits to be used for that
+// node.
+//
+// MOPEN,MCLOSE ...are numbered at compile time.
+// ZOPEN,ZCLOSE ...ditto
+///
+//
+//
+// A node is one char of opcode followed by two chars of "next" pointer.
+// "Next" pointers are stored as two 8-bit bytes, high order first. The
+// value is a positive offset from the opcode of the node containing it.
+// An operand, if any, simply follows the node. (Note that much of the
+// code generation knows about this implicit relationship.)
+//
+// Using two bytes for the "next" pointer is vast overkill for most things,
+// but allows patterns to get big without disasters.
+#define OP(p) ((int)(*(p)))
+#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377))
+#define OPERAND(p) ((p) + 3)
+// Obtain an operand that was stored as four bytes, MSB first.
+#define OPERAND_MIN(p) (((int64_t)(p)[3] << 24) + ((int64_t)(p)[4] << 16) \
+ + ((int64_t)(p)[5] << 8) + (int64_t)(p)[6])
+// Obtain a second operand stored as four bytes.
+#define OPERAND_MAX(p) OPERAND_MIN((p) + 4)
+// Obtain a second single-byte operand stored after a four bytes operand.
+#define OPERAND_CMP(p) (p)[7]
+
+static uint8_t *reg(int paren, int *flagp);
+
+#ifdef BT_REGEXP_DUMP
+static void regdump(uint8_t *, bt_regprog_T *);
+#endif
+
+#ifdef REGEXP_DEBUG
+static uint8_t *regprop(uint8_t *);
+
+static int regnarrate = 0;
+#endif
+
+// Setup to parse the regexp. Used once to get the length and once to do it.
+static void regcomp_start(uint8_t *expr, int re_flags) // see vim_regcomp()
+{
+ initchr((char *)expr);
+ if (re_flags & RE_MAGIC) {
+ reg_magic = MAGIC_ON;
+ } else {
+ reg_magic = MAGIC_OFF;
+ }
+ reg_string = (re_flags & RE_STRING);
+ reg_strict = (re_flags & RE_STRICT);
+ get_cpo_flags();
+
+ num_complex_braces = 0;
+ regnpar = 1;
+ CLEAR_FIELD(had_endbrace);
+ regnzpar = 1;
+ re_has_z = 0;
+ regsize = 0L;
+ reg_toolong = false;
+ regflags = 0;
+ had_eol = false;
+}
+
+// Return true if MULTIBYTECODE should be used instead of EXACTLY for
+// character "c".
+static bool use_multibytecode(int c)
+{
+ return utf_char2len(c) > 1
+ && (re_multi_type(peekchr()) != NOT_MULTI
+ || utf_iscomposing(c));
+}
+
+// Emit (if appropriate) a byte of code
+static void regc(int b)
+{
+ if (regcode == JUST_CALC_SIZE) {
+ regsize++;
+ } else {
+ *regcode++ = (uint8_t)b;
+ }
+}
+
+// Emit (if appropriate) a multi-byte character of code
+static void regmbc(int c)
+{
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += utf_char2len(c);
+ } else {
+ regcode += utf_char2bytes(c, (char *)regcode);
+ }
+}
+
+// Produce the bytes for equivalence class "c".
+// Currently only handles latin1, latin9 and utf-8.
+// NOTE: When changing this function, also change nfa_emit_equi_class()
+static void reg_equi_class(int c)
+{
+ {
+ switch (c) {
+ // Do not use '\300' style, it results in a negative number.
+ case 'A':
+ case 0xc0:
+ case 0xc1:
+ case 0xc2:
+ case 0xc3:
+ case 0xc4:
+ case 0xc5:
+ case 0x100:
+ case 0x102:
+ case 0x104:
+ case 0x1cd:
+ case 0x1de:
+ case 0x1e0:
+ case 0x1fa:
+ case 0x202:
+ case 0x226:
+ case 0x23a:
+ case 0x1e00:
+ case 0x1ea0:
+ case 0x1ea2:
+ case 0x1ea4:
+ case 0x1ea6:
+ case 0x1ea8:
+ case 0x1eaa:
+ case 0x1eac:
+ case 0x1eae:
+ case 0x1eb0:
+ case 0x1eb2:
+ case 0x1eb4:
+ case 0x1eb6:
+ regmbc('A'); regmbc(0xc0); regmbc(0xc1); regmbc(0xc2);
+ regmbc(0xc3); regmbc(0xc4); regmbc(0xc5);
+ regmbc(0x100); regmbc(0x102); regmbc(0x104);
+ regmbc(0x1cd); regmbc(0x1de); regmbc(0x1e0);
+ regmbc(0x1fa); regmbc(0x202); regmbc(0x226);
+ regmbc(0x23a); regmbc(0x1e00); regmbc(0x1ea0);
+ regmbc(0x1ea2); regmbc(0x1ea4); regmbc(0x1ea6);
+ regmbc(0x1ea8); regmbc(0x1eaa); regmbc(0x1eac);
+ regmbc(0x1eae); regmbc(0x1eb0); regmbc(0x1eb2);
+ regmbc(0x1eb4); regmbc(0x1eb6);
+ return;
+ case 'B':
+ case 0x181:
+ case 0x243:
+ case 0x1e02:
+ case 0x1e04:
+ case 0x1e06:
+ regmbc('B');
+ regmbc(0x181); regmbc(0x243); regmbc(0x1e02);
+ regmbc(0x1e04); regmbc(0x1e06);
+ return;
+ case 'C':
+ case 0xc7:
+ case 0x106:
+ case 0x108:
+ case 0x10a:
+ case 0x10c:
+ case 0x187:
+ case 0x23b:
+ case 0x1e08:
+ case 0xa792:
+ regmbc('C'); regmbc(0xc7);
+ regmbc(0x106); regmbc(0x108); regmbc(0x10a);
+ regmbc(0x10c); regmbc(0x187); regmbc(0x23b);
+ regmbc(0x1e08); regmbc(0xa792);
+ return;
+ case 'D':
+ case 0x10e:
+ case 0x110:
+ case 0x18a:
+ case 0x1e0a:
+ case 0x1e0c:
+ case 0x1e0e:
+ case 0x1e10:
+ case 0x1e12:
+ regmbc('D'); regmbc(0x10e); regmbc(0x110);
+ regmbc(0x18a); regmbc(0x1e0a); regmbc(0x1e0c);
+ regmbc(0x1e0e); regmbc(0x1e10); regmbc(0x1e12);
+ return;
+ case 'E':
+ case 0xc8:
+ case 0xc9:
+ case 0xca:
+ case 0xcb:
+ case 0x112:
+ case 0x114:
+ case 0x116:
+ case 0x118:
+ case 0x11a:
+ case 0x204:
+ case 0x206:
+ case 0x228:
+ case 0x246:
+ case 0x1e14:
+ case 0x1e16:
+ case 0x1e18:
+ case 0x1e1a:
+ case 0x1e1c:
+ case 0x1eb8:
+ case 0x1eba:
+ case 0x1ebc:
+ case 0x1ebe:
+ case 0x1ec0:
+ case 0x1ec2:
+ case 0x1ec4:
+ case 0x1ec6:
+ regmbc('E'); regmbc(0xc8); regmbc(0xc9);
+ regmbc(0xca); regmbc(0xcb); regmbc(0x112);
+ regmbc(0x114); regmbc(0x116); regmbc(0x118);
+ regmbc(0x11a); regmbc(0x204); regmbc(0x206);
+ regmbc(0x228); regmbc(0x246); regmbc(0x1e14);
+ regmbc(0x1e16); regmbc(0x1e18); regmbc(0x1e1a);
+ regmbc(0x1e1c); regmbc(0x1eb8); regmbc(0x1eba);
+ regmbc(0x1ebc); regmbc(0x1ebe); regmbc(0x1ec0);
+ regmbc(0x1ec2); regmbc(0x1ec4); regmbc(0x1ec6);
+ return;
+ case 'F':
+ case 0x191:
+ case 0x1e1e:
+ case 0xa798:
+ regmbc('F'); regmbc(0x191); regmbc(0x1e1e);
+ regmbc(0xa798);
+ return;
+ case 'G':
+ case 0x11c:
+ case 0x11e:
+ case 0x120:
+ case 0x122:
+ case 0x193:
+ case 0x1e4:
+ case 0x1e6:
+ case 0x1f4:
+ case 0x1e20:
+ case 0xa7a0:
+ regmbc('G'); regmbc(0x11c); regmbc(0x11e);
+ regmbc(0x120); regmbc(0x122); regmbc(0x193);
+ regmbc(0x1e4); regmbc(0x1e6); regmbc(0x1f4);
+ regmbc(0x1e20); regmbc(0xa7a0);
+ return;
+ case 'H':
+ case 0x124:
+ case 0x126:
+ case 0x21e:
+ case 0x1e22:
+ case 0x1e24:
+ case 0x1e26:
+ case 0x1e28:
+ case 0x1e2a:
+ case 0x2c67:
+ regmbc('H'); regmbc(0x124); regmbc(0x126);
+ regmbc(0x21e); regmbc(0x1e22); regmbc(0x1e24);
+ regmbc(0x1e26); regmbc(0x1e28); regmbc(0x1e2a);
+ regmbc(0x2c67);
+ return;
+ case 'I':
+ case 0xcc:
+ case 0xcd:
+ case 0xce:
+ case 0xcf:
+ case 0x128:
+ case 0x12a:
+ case 0x12c:
+ case 0x12e:
+ case 0x130:
+ case 0x197:
+ case 0x1cf:
+ case 0x208:
+ case 0x20a:
+ case 0x1e2c:
+ case 0x1e2e:
+ case 0x1ec8:
+ case 0x1eca:
+ regmbc('I'); regmbc(0xcc); regmbc(0xcd);
+ regmbc(0xce); regmbc(0xcf); regmbc(0x128);
+ regmbc(0x12a); regmbc(0x12c); regmbc(0x12e);
+ regmbc(0x130); regmbc(0x197); regmbc(0x1cf);
+ regmbc(0x208); regmbc(0x20a); regmbc(0x1e2c);
+ regmbc(0x1e2e); regmbc(0x1ec8); regmbc(0x1eca);
+ return;
+ case 'J':
+ case 0x134:
+ case 0x248:
+ regmbc('J'); regmbc(0x134); regmbc(0x248);
+ return;
+ case 'K':
+ case 0x136:
+ case 0x198:
+ case 0x1e8:
+ case 0x1e30:
+ case 0x1e32:
+ case 0x1e34:
+ case 0x2c69:
+ case 0xa740:
+ regmbc('K'); regmbc(0x136); regmbc(0x198);
+ regmbc(0x1e8); regmbc(0x1e30); regmbc(0x1e32);
+ regmbc(0x1e34); regmbc(0x2c69); regmbc(0xa740);
+ return;
+ case 'L':
+ case 0x139:
+ case 0x13b:
+ case 0x13d:
+ case 0x13f:
+ case 0x141:
+ case 0x23d:
+ case 0x1e36:
+ case 0x1e38:
+ case 0x1e3a:
+ case 0x1e3c:
+ case 0x2c60:
+ regmbc('L'); regmbc(0x139); regmbc(0x13b);
+ regmbc(0x13d); regmbc(0x13f); regmbc(0x141);
+ regmbc(0x23d); regmbc(0x1e36); regmbc(0x1e38);
+ regmbc(0x1e3a); regmbc(0x1e3c); regmbc(0x2c60);
+ return;
+ case 'M':
+ case 0x1e3e:
+ case 0x1e40:
+ case 0x1e42:
+ regmbc('M'); regmbc(0x1e3e); regmbc(0x1e40);
+ regmbc(0x1e42);
+ return;
+ case 'N':
+ case 0xd1:
+ case 0x143:
+ case 0x145:
+ case 0x147:
+ case 0x1f8:
+ case 0x1e44:
+ case 0x1e46:
+ case 0x1e48:
+ case 0x1e4a:
+ case 0xa7a4:
+ regmbc('N'); regmbc(0xd1);
+ regmbc(0x143); regmbc(0x145); regmbc(0x147);
+ regmbc(0x1f8); regmbc(0x1e44); regmbc(0x1e46);
+ regmbc(0x1e48); regmbc(0x1e4a); regmbc(0xa7a4);
+ return;
+ case 'O':
+ case 0xd2:
+ case 0xd3:
+ case 0xd4:
+ case 0xd5:
+ case 0xd6:
+ case 0xd8:
+ case 0x14c:
+ case 0x14e:
+ case 0x150:
+ case 0x19f:
+ case 0x1a0:
+ case 0x1d1:
+ case 0x1ea:
+ case 0x1ec:
+ case 0x1fe:
+ case 0x20c:
+ case 0x20e:
+ case 0x22a:
+ case 0x22c:
+ case 0x22e:
+ case 0x230:
+ case 0x1e4c:
+ case 0x1e4e:
+ case 0x1e50:
+ case 0x1e52:
+ case 0x1ecc:
+ case 0x1ece:
+ case 0x1ed0:
+ case 0x1ed2:
+ case 0x1ed4:
+ case 0x1ed6:
+ case 0x1ed8:
+ case 0x1eda:
+ case 0x1edc:
+ case 0x1ede:
+ case 0x1ee0:
+ case 0x1ee2:
+ regmbc('O'); regmbc(0xd2); regmbc(0xd3); regmbc(0xd4);
+ regmbc(0xd5); regmbc(0xd6); regmbc(0xd8);
+ regmbc(0x14c); regmbc(0x14e); regmbc(0x150);
+ regmbc(0x19f); regmbc(0x1a0); regmbc(0x1d1);
+ regmbc(0x1ea); regmbc(0x1ec); regmbc(0x1fe);
+ regmbc(0x20c); regmbc(0x20e); regmbc(0x22a);
+ regmbc(0x22c); regmbc(0x22e); regmbc(0x230);
+ regmbc(0x1e4c); regmbc(0x1e4e); regmbc(0x1e50);
+ regmbc(0x1e52); regmbc(0x1ecc); regmbc(0x1ece);
+ regmbc(0x1ed0); regmbc(0x1ed2); regmbc(0x1ed4);
+ regmbc(0x1ed6); regmbc(0x1ed8); regmbc(0x1eda);
+ regmbc(0x1edc); regmbc(0x1ede); regmbc(0x1ee0);
+ regmbc(0x1ee2);
+ return;
+ case 'P':
+ case 0x1a4:
+ case 0x1e54:
+ case 0x1e56:
+ case 0x2c63:
+ regmbc('P'); regmbc(0x1a4); regmbc(0x1e54);
+ regmbc(0x1e56); regmbc(0x2c63);
+ return;
+ case 'Q':
+ case 0x24a:
+ regmbc('Q'); regmbc(0x24a);
+ return;
+ case 'R':
+ case 0x154:
+ case 0x156:
+ case 0x158:
+ case 0x210:
+ case 0x212:
+ case 0x24c:
+ case 0x1e58:
+ case 0x1e5a:
+ case 0x1e5c:
+ case 0x1e5e:
+ case 0x2c64:
+ case 0xa7a6:
+ regmbc('R'); regmbc(0x154); regmbc(0x156);
+ regmbc(0x210); regmbc(0x212); regmbc(0x158);
+ regmbc(0x24c); regmbc(0x1e58); regmbc(0x1e5a);
+ regmbc(0x1e5c); regmbc(0x1e5e); regmbc(0x2c64);
+ regmbc(0xa7a6);
+ return;
+ case 'S':
+ case 0x15a:
+ case 0x15c:
+ case 0x15e:
+ case 0x160:
+ case 0x218:
+ case 0x1e60:
+ case 0x1e62:
+ case 0x1e64:
+ case 0x1e66:
+ case 0x1e68:
+ case 0x2c7e:
+ case 0xa7a8:
+ regmbc('S'); regmbc(0x15a); regmbc(0x15c);
+ regmbc(0x15e); regmbc(0x160); regmbc(0x218);
+ regmbc(0x1e60); regmbc(0x1e62); regmbc(0x1e64);
+ regmbc(0x1e66); regmbc(0x1e68); regmbc(0x2c7e);
+ regmbc(0xa7a8);
+ return;
+ case 'T':
+ case 0x162:
+ case 0x164:
+ case 0x166:
+ case 0x1ac:
+ case 0x1ae:
+ case 0x21a:
+ case 0x23e:
+ case 0x1e6a:
+ case 0x1e6c:
+ case 0x1e6e:
+ case 0x1e70:
+ regmbc('T'); regmbc(0x162); regmbc(0x164);
+ regmbc(0x166); regmbc(0x1ac); regmbc(0x23e);
+ regmbc(0x1ae); regmbc(0x21a); regmbc(0x1e6a);
+ regmbc(0x1e6c); regmbc(0x1e6e); regmbc(0x1e70);
+ return;
+ case 'U':
+ case 0xd9:
+ case 0xda:
+ case 0xdb:
+ case 0xdc:
+ case 0x168:
+ case 0x16a:
+ case 0x16c:
+ case 0x16e:
+ case 0x170:
+ case 0x172:
+ case 0x1af:
+ case 0x1d3:
+ case 0x1d5:
+ case 0x1d7:
+ case 0x1d9:
+ case 0x1db:
+ case 0x214:
+ case 0x216:
+ case 0x244:
+ case 0x1e72:
+ case 0x1e74:
+ case 0x1e76:
+ case 0x1e78:
+ case 0x1e7a:
+ case 0x1ee4:
+ case 0x1ee6:
+ case 0x1ee8:
+ case 0x1eea:
+ case 0x1eec:
+ case 0x1eee:
+ case 0x1ef0:
+ regmbc('U'); regmbc(0xd9); regmbc(0xda);
+ regmbc(0xdb); regmbc(0xdc); regmbc(0x168);
+ regmbc(0x16a); regmbc(0x16c); regmbc(0x16e);
+ regmbc(0x170); regmbc(0x172); regmbc(0x1af);
+ regmbc(0x1d3); regmbc(0x1d5); regmbc(0x1d7);
+ regmbc(0x1d9); regmbc(0x1db); regmbc(0x214);
+ regmbc(0x216); regmbc(0x244); regmbc(0x1e72);
+ regmbc(0x1e74); regmbc(0x1e76); regmbc(0x1e78);
+ regmbc(0x1e7a); regmbc(0x1ee4); regmbc(0x1ee6);
+ regmbc(0x1ee8); regmbc(0x1eea); regmbc(0x1eec);
+ regmbc(0x1eee); regmbc(0x1ef0);
+ return;
+ case 'V':
+ case 0x1b2:
+ case 0x1e7c:
+ case 0x1e7e:
+ regmbc('V'); regmbc(0x1b2); regmbc(0x1e7c);
+ regmbc(0x1e7e);
+ return;
+ case 'W':
+ case 0x174:
+ case 0x1e80:
+ case 0x1e82:
+ case 0x1e84:
+ case 0x1e86:
+ case 0x1e88:
+ regmbc('W'); regmbc(0x174); regmbc(0x1e80);
+ regmbc(0x1e82); regmbc(0x1e84); regmbc(0x1e86);
+ regmbc(0x1e88);
+ return;
+ case 'X':
+ case 0x1e8a:
+ case 0x1e8c:
+ regmbc('X'); regmbc(0x1e8a); regmbc(0x1e8c);
+ return;
+ case 'Y':
+ case 0xdd:
+ case 0x176:
+ case 0x178:
+ case 0x1b3:
+ case 0x232:
+ case 0x24e:
+ case 0x1e8e:
+ case 0x1ef2:
+ case 0x1ef6:
+ case 0x1ef4:
+ case 0x1ef8:
+ regmbc('Y'); regmbc(0xdd); regmbc(0x176);
+ regmbc(0x178); regmbc(0x1b3); regmbc(0x232);
+ regmbc(0x24e); regmbc(0x1e8e); regmbc(0x1ef2);
+ regmbc(0x1ef4); regmbc(0x1ef6); regmbc(0x1ef8);
+ return;
+ case 'Z':
+ case 0x179:
+ case 0x17b:
+ case 0x17d:
+ case 0x1b5:
+ case 0x1e90:
+ case 0x1e92:
+ case 0x1e94:
+ case 0x2c6b:
+ regmbc('Z'); regmbc(0x179); regmbc(0x17b);
+ regmbc(0x17d); regmbc(0x1b5); regmbc(0x1e90);
+ regmbc(0x1e92); regmbc(0x1e94); regmbc(0x2c6b);
+ return;
+ case 'a':
+ case 0xe0:
+ case 0xe1:
+ case 0xe2:
+ case 0xe3:
+ case 0xe4:
+ case 0xe5:
+ case 0x101:
+ case 0x103:
+ case 0x105:
+ case 0x1ce:
+ case 0x1df:
+ case 0x1e1:
+ case 0x1fb:
+ case 0x201:
+ case 0x203:
+ case 0x227:
+ case 0x1d8f:
+ case 0x1e01:
+ case 0x1e9a:
+ case 0x1ea1:
+ case 0x1ea3:
+ case 0x1ea5:
+ case 0x1ea7:
+ case 0x1ea9:
+ case 0x1eab:
+ case 0x1ead:
+ case 0x1eaf:
+ case 0x1eb1:
+ case 0x1eb3:
+ case 0x1eb5:
+ case 0x1eb7:
+ case 0x2c65:
+ regmbc('a'); regmbc(0xe0); regmbc(0xe1);
+ regmbc(0xe2); regmbc(0xe3); regmbc(0xe4);
+ regmbc(0xe5); regmbc(0x101); regmbc(0x103);
+ regmbc(0x105); regmbc(0x1ce); regmbc(0x1df);
+ regmbc(0x1e1); regmbc(0x1fb); regmbc(0x201);
+ regmbc(0x203); regmbc(0x227); regmbc(0x1d8f);
+ regmbc(0x1e01); regmbc(0x1e9a); regmbc(0x1ea1);
+ regmbc(0x1ea3); regmbc(0x1ea5); regmbc(0x1ea7);
+ regmbc(0x1ea9); regmbc(0x1eab); regmbc(0x1ead);
+ regmbc(0x1eaf); regmbc(0x1eb1); regmbc(0x1eb3);
+ regmbc(0x1eb5); regmbc(0x1eb7); regmbc(0x2c65);
+ return;
+ case 'b':
+ case 0x180:
+ case 0x253:
+ case 0x1d6c:
+ case 0x1d80:
+ case 0x1e03:
+ case 0x1e05:
+ case 0x1e07:
+ regmbc('b');
+ regmbc(0x180); regmbc(0x253); regmbc(0x1d6c);
+ regmbc(0x1d80); regmbc(0x1e03); regmbc(0x1e05);
+ regmbc(0x1e07);
+ return;
+ case 'c':
+ case 0xe7:
+ case 0x107:
+ case 0x109:
+ case 0x10b:
+ case 0x10d:
+ case 0x188:
+ case 0x23c:
+ case 0x1e09:
+ case 0xa793:
+ case 0xa794:
+ regmbc('c'); regmbc(0xe7); regmbc(0x107);
+ regmbc(0x109); regmbc(0x10b); regmbc(0x10d);
+ regmbc(0x188); regmbc(0x23c); regmbc(0x1e09);
+ regmbc(0xa793); regmbc(0xa794);
+ return;
+ case 'd':
+ case 0x10f:
+ case 0x111:
+ case 0x257:
+ case 0x1d6d:
+ case 0x1d81:
+ case 0x1d91:
+ case 0x1e0b:
+ case 0x1e0d:
+ case 0x1e0f:
+ case 0x1e11:
+ case 0x1e13:
+ regmbc('d'); regmbc(0x10f); regmbc(0x111);
+ regmbc(0x257); regmbc(0x1d6d); regmbc(0x1d81);
+ regmbc(0x1d91); regmbc(0x1e0b); regmbc(0x1e0d);
+ regmbc(0x1e0f); regmbc(0x1e11); regmbc(0x1e13);
+ return;
+ case 'e':
+ case 0xe8:
+ case 0xe9:
+ case 0xea:
+ case 0xeb:
+ case 0x113:
+ case 0x115:
+ case 0x117:
+ case 0x119:
+ case 0x11b:
+ case 0x205:
+ case 0x207:
+ case 0x229:
+ case 0x247:
+ case 0x1d92:
+ case 0x1e15:
+ case 0x1e17:
+ case 0x1e19:
+ case 0x1e1b:
+ case 0x1eb9:
+ case 0x1ebb:
+ case 0x1e1d:
+ case 0x1ebd:
+ case 0x1ebf:
+ case 0x1ec1:
+ case 0x1ec3:
+ case 0x1ec5:
+ case 0x1ec7:
+ regmbc('e'); regmbc(0xe8); regmbc(0xe9);
+ regmbc(0xea); regmbc(0xeb); regmbc(0x113);
+ regmbc(0x115); regmbc(0x117); regmbc(0x119);
+ regmbc(0x11b); regmbc(0x205); regmbc(0x207);
+ regmbc(0x229); regmbc(0x247); regmbc(0x1d92);
+ regmbc(0x1e15); regmbc(0x1e17); regmbc(0x1e19);
+ regmbc(0x1e1b); regmbc(0x1e1d); regmbc(0x1eb9);
+ regmbc(0x1ebb); regmbc(0x1ebd); regmbc(0x1ebf);
+ regmbc(0x1ec1); regmbc(0x1ec3); regmbc(0x1ec5);
+ regmbc(0x1ec7);
+ return;
+ case 'f':
+ case 0x192:
+ case 0x1d6e:
+ case 0x1d82:
+ case 0x1e1f:
+ case 0xa799:
+ regmbc('f'); regmbc(0x192); regmbc(0x1d6e);
+ regmbc(0x1d82); regmbc(0x1e1f); regmbc(0xa799);
+ return;
+ case 'g':
+ case 0x11d:
+ case 0x11f:
+ case 0x121:
+ case 0x123:
+ case 0x1e5:
+ case 0x1e7:
+ case 0x260:
+ case 0x1f5:
+ case 0x1d83:
+ case 0x1e21:
+ case 0xa7a1:
+ regmbc('g'); regmbc(0x11d); regmbc(0x11f);
+ regmbc(0x121); regmbc(0x123); regmbc(0x1e5);
+ regmbc(0x1e7); regmbc(0x1f5); regmbc(0x260);
+ regmbc(0x1d83); regmbc(0x1e21); regmbc(0xa7a1);
+ return;
+ case 'h':
+ case 0x125:
+ case 0x127:
+ case 0x21f:
+ case 0x1e23:
+ case 0x1e25:
+ case 0x1e27:
+ case 0x1e29:
+ case 0x1e2b:
+ case 0x1e96:
+ case 0x2c68:
+ case 0xa795:
+ regmbc('h'); regmbc(0x125); regmbc(0x127);
+ regmbc(0x21f); regmbc(0x1e23); regmbc(0x1e25);
+ regmbc(0x1e27); regmbc(0x1e29); regmbc(0x1e2b);
+ regmbc(0x1e96); regmbc(0x2c68); regmbc(0xa795);
+ return;
+ case 'i':
+ case 0xec:
+ case 0xed:
+ case 0xee:
+ case 0xef:
+ case 0x129:
+ case 0x12b:
+ case 0x12d:
+ case 0x12f:
+ case 0x1d0:
+ case 0x209:
+ case 0x20b:
+ case 0x268:
+ case 0x1d96:
+ case 0x1e2d:
+ case 0x1e2f:
+ case 0x1ec9:
+ case 0x1ecb:
+ regmbc('i'); regmbc(0xec); regmbc(0xed);
+ regmbc(0xee); regmbc(0xef); regmbc(0x129);
+ regmbc(0x12b); regmbc(0x12d); regmbc(0x12f);
+ regmbc(0x1d0); regmbc(0x209); regmbc(0x20b);
+ regmbc(0x268); regmbc(0x1d96); regmbc(0x1e2d);
+ regmbc(0x1e2f); regmbc(0x1ec9); regmbc(0x1ecb);
+ return;
+ case 'j':
+ case 0x135:
+ case 0x1f0:
+ case 0x249:
+ regmbc('j'); regmbc(0x135); regmbc(0x1f0);
+ regmbc(0x249);
+ return;
+ case 'k':
+ case 0x137:
+ case 0x199:
+ case 0x1e9:
+ case 0x1d84:
+ case 0x1e31:
+ case 0x1e33:
+ case 0x1e35:
+ case 0x2c6a:
+ case 0xa741:
+ regmbc('k'); regmbc(0x137); regmbc(0x199);
+ regmbc(0x1e9); regmbc(0x1d84); regmbc(0x1e31);
+ regmbc(0x1e33); regmbc(0x1e35); regmbc(0x2c6a);
+ regmbc(0xa741);
+ return;
+ case 'l':
+ case 0x13a:
+ case 0x13c:
+ case 0x13e:
+ case 0x140:
+ case 0x142:
+ case 0x19a:
+ case 0x1e37:
+ case 0x1e39:
+ case 0x1e3b:
+ case 0x1e3d:
+ case 0x2c61:
+ regmbc('l'); regmbc(0x13a); regmbc(0x13c);
+ regmbc(0x13e); regmbc(0x140); regmbc(0x142);
+ regmbc(0x19a); regmbc(0x1e37); regmbc(0x1e39);
+ regmbc(0x1e3b); regmbc(0x1e3d); regmbc(0x2c61);
+ return;
+ case 'm':
+ case 0x1d6f:
+ case 0x1e3f:
+ case 0x1e41:
+ case 0x1e43:
+ regmbc('m'); regmbc(0x1d6f); regmbc(0x1e3f);
+ regmbc(0x1e41); regmbc(0x1e43);
+ return;
+ case 'n':
+ case 0xf1:
+ case 0x144:
+ case 0x146:
+ case 0x148:
+ case 0x149:
+ case 0x1f9:
+ case 0x1d70:
+ case 0x1d87:
+ case 0x1e45:
+ case 0x1e47:
+ case 0x1e49:
+ case 0x1e4b:
+ case 0xa7a5:
+ regmbc('n'); regmbc(0xf1); regmbc(0x144);
+ regmbc(0x146); regmbc(0x148); regmbc(0x149);
+ regmbc(0x1f9); regmbc(0x1d70); regmbc(0x1d87);
+ regmbc(0x1e45); regmbc(0x1e47); regmbc(0x1e49);
+ regmbc(0x1e4b); regmbc(0xa7a5);
+ return;
+ case 'o':
+ case 0xf2:
+ case 0xf3:
+ case 0xf4:
+ case 0xf5:
+ case 0xf6:
+ case 0xf8:
+ case 0x14d:
+ case 0x14f:
+ case 0x151:
+ case 0x1a1:
+ case 0x1d2:
+ case 0x1eb:
+ case 0x1ed:
+ case 0x1ff:
+ case 0x20d:
+ case 0x20f:
+ case 0x22b:
+ case 0x22d:
+ case 0x22f:
+ case 0x231:
+ case 0x275:
+ case 0x1e4d:
+ case 0x1e4f:
+ case 0x1e51:
+ case 0x1e53:
+ case 0x1ecd:
+ case 0x1ecf:
+ case 0x1ed1:
+ case 0x1ed3:
+ case 0x1ed5:
+ case 0x1ed7:
+ case 0x1ed9:
+ case 0x1edb:
+ case 0x1edd:
+ case 0x1edf:
+ case 0x1ee1:
+ case 0x1ee3:
+ regmbc('o'); regmbc(0xf2); regmbc(0xf3);
+ regmbc(0xf4); regmbc(0xf5); regmbc(0xf6);
+ regmbc(0xf8); regmbc(0x14d); regmbc(0x14f);
+ regmbc(0x151); regmbc(0x1a1); regmbc(0x1d2);
+ regmbc(0x1eb); regmbc(0x1ed); regmbc(0x1ff);
+ regmbc(0x20d); regmbc(0x20f); regmbc(0x22b);
+ regmbc(0x22d); regmbc(0x22f); regmbc(0x231);
+ regmbc(0x275); regmbc(0x1e4d); regmbc(0x1e4f);
+ regmbc(0x1e51); regmbc(0x1e53); regmbc(0x1ecd);
+ regmbc(0x1ecf); regmbc(0x1ed1); regmbc(0x1ed3);
+ regmbc(0x1ed5); regmbc(0x1ed7); regmbc(0x1ed9);
+ regmbc(0x1edb); regmbc(0x1edd); regmbc(0x1edf);
+ regmbc(0x1ee1); regmbc(0x1ee3);
+ return;
+ case 'p':
+ case 0x1a5:
+ case 0x1d71:
+ case 0x1d88:
+ case 0x1d7d:
+ case 0x1e55:
+ case 0x1e57:
+ regmbc('p'); regmbc(0x1a5); regmbc(0x1d71);
+ regmbc(0x1d7d); regmbc(0x1d88); regmbc(0x1e55);
+ regmbc(0x1e57);
+ return;
+ case 'q':
+ case 0x24b:
+ case 0x2a0:
+ regmbc('q'); regmbc(0x24b); regmbc(0x2a0);
+ return;
+ case 'r':
+ case 0x155:
+ case 0x157:
+ case 0x159:
+ case 0x211:
+ case 0x213:
+ case 0x24d:
+ case 0x27d:
+ case 0x1d72:
+ case 0x1d73:
+ case 0x1d89:
+ case 0x1e59:
+ case 0x1e5b:
+ case 0x1e5d:
+ case 0x1e5f:
+ case 0xa7a7:
+ regmbc('r'); regmbc(0x155); regmbc(0x157);
+ regmbc(0x159); regmbc(0x211); regmbc(0x213);
+ regmbc(0x24d); regmbc(0x1d72); regmbc(0x1d73);
+ regmbc(0x1d89); regmbc(0x1e59); regmbc(0x27d);
+ regmbc(0x1e5b); regmbc(0x1e5d); regmbc(0x1e5f);
+ regmbc(0xa7a7);
+ return;
+ case 's':
+ case 0x15b:
+ case 0x15d:
+ case 0x15f:
+ case 0x161:
+ case 0x1e61:
+ case 0x219:
+ case 0x23f:
+ case 0x1d74:
+ case 0x1d8a:
+ case 0x1e63:
+ case 0x1e65:
+ case 0x1e67:
+ case 0x1e69:
+ case 0xa7a9:
+ regmbc('s'); regmbc(0x15b); regmbc(0x15d);
+ regmbc(0x15f); regmbc(0x161); regmbc(0x23f);
+ regmbc(0x219); regmbc(0x1d74); regmbc(0x1d8a);
+ regmbc(0x1e61); regmbc(0x1e63); regmbc(0x1e65);
+ regmbc(0x1e67); regmbc(0x1e69); regmbc(0xa7a9);
+ return;
+ case 't':
+ case 0x163:
+ case 0x165:
+ case 0x167:
+ case 0x1ab:
+ case 0x1ad:
+ case 0x21b:
+ case 0x288:
+ case 0x1d75:
+ case 0x1e6b:
+ case 0x1e6d:
+ case 0x1e6f:
+ case 0x1e71:
+ case 0x1e97:
+ case 0x2c66:
+ regmbc('t'); regmbc(0x163); regmbc(0x165);
+ regmbc(0x167); regmbc(0x1ab); regmbc(0x21b);
+ regmbc(0x1ad); regmbc(0x288); regmbc(0x1d75);
+ regmbc(0x1e6b); regmbc(0x1e6d); regmbc(0x1e6f);
+ regmbc(0x1e71); regmbc(0x1e97); regmbc(0x2c66);
+ return;
+ case 'u':
+ case 0xf9:
+ case 0xfa:
+ case 0xfb:
+ case 0xfc:
+ case 0x169:
+ case 0x16b:
+ case 0x16d:
+ case 0x16f:
+ case 0x171:
+ case 0x173:
+ case 0x1b0:
+ case 0x1d4:
+ case 0x1d6:
+ case 0x1d8:
+ case 0x1da:
+ case 0x1dc:
+ case 0x215:
+ case 0x217:
+ case 0x289:
+ case 0x1e73:
+ case 0x1d7e:
+ case 0x1d99:
+ case 0x1e75:
+ case 0x1e77:
+ case 0x1e79:
+ case 0x1e7b:
+ case 0x1ee5:
+ case 0x1ee7:
+ case 0x1ee9:
+ case 0x1eeb:
+ case 0x1eed:
+ case 0x1eef:
+ case 0x1ef1:
+ regmbc('u'); regmbc(0xf9); regmbc(0xfa);
+ regmbc(0xfb); regmbc(0xfc); regmbc(0x169);
+ regmbc(0x16b); regmbc(0x16d); regmbc(0x16f);
+ regmbc(0x171); regmbc(0x173); regmbc(0x1d6);
+ regmbc(0x1d8); regmbc(0x1da); regmbc(0x1dc);
+ regmbc(0x215); regmbc(0x217); regmbc(0x1b0);
+ regmbc(0x1d4); regmbc(0x289); regmbc(0x1d7e);
+ regmbc(0x1d99); regmbc(0x1e73); regmbc(0x1e75);
+ regmbc(0x1e77); regmbc(0x1e79); regmbc(0x1e7b);
+ regmbc(0x1ee5); regmbc(0x1ee7); regmbc(0x1ee9);
+ regmbc(0x1eeb); regmbc(0x1eed); regmbc(0x1eef);
+ regmbc(0x1ef1);
+ return;
+ case 'v':
+ case 0x28b:
+ case 0x1d8c:
+ case 0x1e7d:
+ case 0x1e7f:
+ regmbc('v'); regmbc(0x28b); regmbc(0x1d8c);
+ regmbc(0x1e7d); regmbc(0x1e7f);
+ return;
+ case 'w':
+ case 0x175:
+ case 0x1e81:
+ case 0x1e83:
+ case 0x1e85:
+ case 0x1e87:
+ case 0x1e89:
+ case 0x1e98:
+ regmbc('w'); regmbc(0x175); regmbc(0x1e81);
+ regmbc(0x1e83); regmbc(0x1e85); regmbc(0x1e87);
+ regmbc(0x1e89); regmbc(0x1e98);
+ return;
+ case 'x':
+ case 0x1e8b:
+ case 0x1e8d:
+ regmbc('x'); regmbc(0x1e8b); regmbc(0x1e8d);
+ return;
+ case 'y':
+ case 0xfd:
+ case 0xff:
+ case 0x177:
+ case 0x1b4:
+ case 0x233:
+ case 0x24f:
+ case 0x1e8f:
+ case 0x1e99:
+ case 0x1ef3:
+ case 0x1ef5:
+ case 0x1ef7:
+ case 0x1ef9:
+ regmbc('y'); regmbc(0xfd); regmbc(0xff);
+ regmbc(0x177); regmbc(0x1b4); regmbc(0x233);
+ regmbc(0x24f); regmbc(0x1e8f); regmbc(0x1e99);
+ regmbc(0x1ef3); regmbc(0x1ef5); regmbc(0x1ef7);
+ regmbc(0x1ef9);
+ return;
+ case 'z':
+ case 0x17a:
+ case 0x17c:
+ case 0x17e:
+ case 0x1b6:
+ case 0x1d76:
+ case 0x1d8e:
+ case 0x1e91:
+ case 0x1e93:
+ case 0x1e95:
+ case 0x2c6c:
+ regmbc('z'); regmbc(0x17a); regmbc(0x17c);
+ regmbc(0x17e); regmbc(0x1b6); regmbc(0x1d76);
+ regmbc(0x1d8e); regmbc(0x1e91); regmbc(0x1e93);
+ regmbc(0x1e95); regmbc(0x2c6c);
+ return;
+ }
+ }
+ regmbc(c);
+}
+
+// Emit a node.
+// Return pointer to generated code.
+static uint8_t *regnode(int op)
+{
+ uint8_t *ret;
+
+ ret = regcode;
+ if (ret == JUST_CALC_SIZE) {
+ regsize += 3;
+ } else {
+ *regcode++ = (uint8_t)op;
+ *regcode++ = NUL; // Null "next" pointer.
+ *regcode++ = NUL;
+ }
+ return ret;
+}
+
+// Write a four bytes number at "p" and return pointer to the next char.
+static uint8_t *re_put_uint32(uint8_t *p, uint32_t val)
+{
+ *p++ = (uint8_t)((val >> 24) & 0377);
+ *p++ = (uint8_t)((val >> 16) & 0377);
+ *p++ = (uint8_t)((val >> 8) & 0377);
+ *p++ = (uint8_t)(val & 0377);
+ return p;
+}
+
+// regnext - dig the "next" pointer out of a node
+// Returns NULL when calculating size, when there is no next item and when
+// there is an error.
+static uint8_t *regnext(uint8_t *p)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int offset;
+
+ if (p == JUST_CALC_SIZE || reg_toolong) {
+ return NULL;
+ }
+
+ offset = NEXT(p);
+ if (offset == 0) {
+ return NULL;
+ }
+
+ if (OP(p) == BACK) {
+ return p - offset;
+ } else {
+ return p + offset;
+ }
+}
+
+// Set the next-pointer at the end of a node chain.
+static void regtail(uint8_t *p, const uint8_t *val)
+{
+ int offset;
+
+ if (p == JUST_CALC_SIZE) {
+ return;
+ }
+
+ // Find last node.
+ uint8_t *scan = p;
+ while (true) {
+ uint8_t *temp = regnext(scan);
+ if (temp == NULL) {
+ break;
+ }
+ scan = temp;
+ }
+
+ if (OP(scan) == BACK) {
+ offset = (int)(scan - val);
+ } else {
+ offset = (int)(val - scan);
+ }
+ // When the offset uses more than 16 bits it can no longer fit in the two
+ // bytes available. Use a global flag to avoid having to check return
+ // values in too many places.
+ if (offset > 0xffff) {
+ reg_toolong = true;
+ } else {
+ *(scan + 1) = (uint8_t)(((unsigned)offset >> 8) & 0377);
+ *(scan + 2) = (uint8_t)(offset & 0377);
+ }
+}
+
+// Like regtail, on item after a BRANCH; nop if none.
+static void regoptail(uint8_t *p, uint8_t *val)
+{
+ // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless"
+ if (p == NULL || p == JUST_CALC_SIZE
+ || (OP(p) != BRANCH
+ && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) {
+ return;
+ }
+ regtail(OPERAND(p), val);
+}
+
+// Insert an operator in front of already-emitted operand
+//
+// Means relocating the operand.
+static void reginsert(int op, uint8_t *opnd)
+{
+ uint8_t *src;
+ uint8_t *dst;
+ uint8_t *place;
+
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += 3;
+ return;
+ }
+ src = regcode;
+ regcode += 3;
+ dst = regcode;
+ while (src > opnd) {
+ *--dst = *--src;
+ }
+
+ place = opnd; // Op node, where operand used to be.
+ *place++ = (uint8_t)op;
+ *place++ = NUL;
+ *place = NUL;
+}
+
+// Insert an operator in front of already-emitted operand.
+// Add a number to the operator.
+static void reginsert_nr(int op, int64_t val, uint8_t *opnd)
+{
+ uint8_t *src;
+ uint8_t *dst;
+ uint8_t *place;
+
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += 7;
+ return;
+ }
+ src = regcode;
+ regcode += 7;
+ dst = regcode;
+ while (src > opnd) {
+ *--dst = *--src;
+ }
+
+ place = opnd; // Op node, where operand used to be.
+ *place++ = (uint8_t)op;
+ *place++ = NUL;
+ *place++ = NUL;
+ assert(val >= 0 && (uintmax_t)val <= UINT32_MAX);
+ re_put_uint32(place, (uint32_t)val);
+}
+
+// Insert an operator in front of already-emitted operand.
+// The operator has the given limit values as operands. Also set next pointer.
+//
+// Means relocating the operand.
+static void reginsert_limits(int op, int64_t minval, int64_t maxval, uint8_t *opnd)
+{
+ uint8_t *src;
+ uint8_t *dst;
+ uint8_t *place;
+
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += 11;
+ return;
+ }
+ src = regcode;
+ regcode += 11;
+ dst = regcode;
+ while (src > opnd) {
+ *--dst = *--src;
+ }
+
+ place = opnd; // Op node, where operand used to be.
+ *place++ = (uint8_t)op;
+ *place++ = NUL;
+ *place++ = NUL;
+ assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX);
+ place = re_put_uint32(place, (uint32_t)minval);
+ assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX);
+ place = re_put_uint32(place, (uint32_t)maxval);
+ regtail(opnd, place);
+}
+
+/// Return true if the back reference is legal. We must have seen the close
+/// brace.
+/// TODO(vim): Should also check that we don't refer to something repeated
+/// (+*=): what instance of the repetition should we match?
+static int seen_endbrace(int refnum)
+{
+ if (!had_endbrace[refnum]) {
+ uint8_t *p;
+
+ // Trick: check if "@<=" or "@<!" follows, in which case
+ // the \1 can appear before the referenced match.
+ for (p = (uint8_t *)regparse; *p != NUL; p++) {
+ if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) {
+ break;
+ }
+ }
+
+ if (*p == NUL) {
+ emsg(_("E65: Illegal back reference"));
+ rc_did_emsg = true;
+ return false;
+ }
+ }
+ return true;
+}
+
+// Parse the lowest level.
+//
+// Optimization: gobbles an entire sequence of ordinary characters so that
+// it can turn them into a single node, which is smaller to store and
+// faster to run. Don't do this when one_exactly is set.
+static uint8_t *regatom(int *flagp)
+{
+ uint8_t *ret;
+ int flags;
+ int c;
+ uint8_t *p;
+ int extra = 0;
+ int save_prev_at_start = prev_at_start;
+
+ *flagp = WORST; // Tentatively.
+
+ c = getchr();
+ switch (c) {
+ case Magic('^'):
+ ret = regnode(BOL);
+ break;
+
+ case Magic('$'):
+ ret = regnode(EOL);
+ had_eol = true;
+ break;
+
+ case Magic('<'):
+ ret = regnode(BOW);
+ break;
+
+ case Magic('>'):
+ ret = regnode(EOW);
+ break;
+
+ case Magic('_'):
+ c = no_Magic(getchr());
+ if (c == '^') { // "\_^" is start-of-line
+ ret = regnode(BOL);
+ break;
+ }
+ if (c == '$') { // "\_$" is end-of-line
+ ret = regnode(EOL);
+ had_eol = true;
+ break;
+ }
+
+ extra = ADD_NL;
+ *flagp |= HASNL;
+
+ // "\_[" is character range plus newline
+ if (c == '[') {
+ goto collection;
+ }
+
+ // "\_x" is character class plus newline
+ FALLTHROUGH;
+
+ // Character classes.
+ case Magic('.'):
+ case Magic('i'):
+ case Magic('I'):
+ case Magic('k'):
+ case Magic('K'):
+ case Magic('f'):
+ case Magic('F'):
+ case Magic('p'):
+ case Magic('P'):
+ case Magic('s'):
+ case Magic('S'):
+ case Magic('d'):
+ case Magic('D'):
+ case Magic('x'):
+ case Magic('X'):
+ case Magic('o'):
+ case Magic('O'):
+ case Magic('w'):
+ case Magic('W'):
+ case Magic('h'):
+ case Magic('H'):
+ case Magic('a'):
+ case Magic('A'):
+ case Magic('l'):
+ case Magic('L'):
+ case Magic('u'):
+ case Magic('U'):
+ p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
+ if (p == NULL) {
+ EMSG_RET_NULL(_(e_invalid_use_of_underscore));
+ }
+ // When '.' is followed by a composing char ignore the dot, so that
+ // the composing char is matched here.
+ if (c == Magic('.') && utf_iscomposing(peekchr())) {
+ c = getchr();
+ goto do_multibyte;
+ }
+ ret = regnode(classcodes[p - classchars] + extra);
+ *flagp |= HASWIDTH | SIMPLE;
+ break;
+
+ case Magic('n'):
+ if (reg_string) {
+ // In a string "\n" matches a newline character.
+ ret = regnode(EXACTLY);
+ regc(NL);
+ regc(NUL);
+ *flagp |= HASWIDTH | SIMPLE;
+ } else {
+ // In buffer text "\n" matches the end of a line.
+ ret = regnode(NEWL);
+ *flagp |= HASWIDTH | HASNL;
+ }
+ break;
+
+ case Magic('('):
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ ret = reg(REG_PAREN, &flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
+ break;
+
+ case NUL:
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier.
+ // NOTREACHED
+
+ case Magic('='):
+ case Magic('?'):
+ case Magic('+'):
+ case Magic('@'):
+ case Magic('{'):
+ case Magic('*'):
+ c = no_Magic(c);
+ EMSG3_RET_NULL(_("E64: %s%c follows nothing"),
+ (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL), c);
+ // NOTREACHED
+
+ case Magic('~'): // previous substitute pattern
+ if (reg_prev_sub != NULL) {
+ uint8_t *lp;
+
+ ret = regnode(EXACTLY);
+ lp = (uint8_t *)reg_prev_sub;
+ while (*lp != NUL) {
+ regc(*lp++);
+ }
+ regc(NUL);
+ if (*reg_prev_sub != NUL) {
+ *flagp |= HASWIDTH;
+ if ((lp - (uint8_t *)reg_prev_sub) == 1) {
+ *flagp |= SIMPLE;
+ }
+ }
+ } else {
+ EMSG_RET_NULL(_(e_nopresub));
+ }
+ break;
+
+ case Magic('1'):
+ case Magic('2'):
+ case Magic('3'):
+ case Magic('4'):
+ case Magic('5'):
+ case Magic('6'):
+ case Magic('7'):
+ case Magic('8'):
+ case Magic('9'): {
+ int refnum;
+
+ refnum = c - Magic('0');
+ if (!seen_endbrace(refnum)) {
+ return NULL;
+ }
+ ret = regnode(BACKREF + refnum);
+ }
+ break;
+
+ case Magic('z'):
+ c = no_Magic(getchr());
+ switch (c) {
+ case '(':
+ if ((reg_do_extmatch & REX_SET) == 0) {
+ EMSG_RET_NULL(_(e_z_not_allowed));
+ }
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ ret = reg(REG_ZPAREN, &flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH);
+ re_has_z = REX_SET;
+ break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if ((reg_do_extmatch & REX_USE) == 0) {
+ EMSG_RET_NULL(_(e_z1_not_allowed));
+ }
+ ret = regnode(ZREF + c - '0');
+ re_has_z = REX_USE;
+ break;
+
+ case 's':
+ ret = regnode(MOPEN + 0);
+ if (!re_mult_next("\\zs")) {
+ return NULL;
+ }
+ break;
+
+ case 'e':
+ ret = regnode(MCLOSE + 0);
+ if (!re_mult_next("\\ze")) {
+ return NULL;
+ }
+ break;
+
+ default:
+ EMSG_RET_NULL(_("E68: Invalid character after \\z"));
+ }
+ break;
+
+ case Magic('%'):
+ c = no_Magic(getchr());
+ switch (c) {
+ // () without a back reference
+ case '(':
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ ret = reg(REG_NPAREN, &flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
+ break;
+
+ // Catch \%^ and \%$ regardless of where they appear in the
+ // pattern -- regardless of whether or not it makes sense.
+ case '^':
+ ret = regnode(RE_BOF);
+ break;
+
+ case '$':
+ ret = regnode(RE_EOF);
+ break;
+
+ case '#':
+ if (regparse[0] == '=' && regparse[1] >= 48 && regparse[1] <= 50) {
+ // misplaced \%#=1
+ semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
+ return FAIL;
+ }
+ ret = regnode(CURSOR);
+ break;
+
+ case 'V':
+ ret = regnode(RE_VISUAL);
+ break;
+
+ case 'C':
+ ret = regnode(RE_COMPOSING);
+ break;
+
+ // \%[abc]: Emit as a list of branches, all ending at the last
+ // branch which matches nothing.
+ case '[':
+ if (one_exactly) { // doesn't nest
+ EMSG_ONE_RET_NULL;
+ }
+ {
+ uint8_t *lastbranch;
+ uint8_t *lastnode = NULL;
+ uint8_t *br;
+
+ ret = NULL;
+ while ((c = getchr()) != ']') {
+ if (c == NUL) {
+ EMSG2_RET_NULL(_(e_missing_sb),
+ reg_magic == MAGIC_ALL);
+ }
+ br = regnode(BRANCH);
+ if (ret == NULL) {
+ ret = br;
+ } else {
+ regtail(lastnode, br);
+ if (reg_toolong) {
+ return NULL;
+ }
+ }
+
+ ungetchr();
+ one_exactly = true;
+ lastnode = regatom(flagp);
+ one_exactly = false;
+ if (lastnode == NULL) {
+ return NULL;
+ }
+ }
+ if (ret == NULL) {
+ EMSG2_RET_NULL(_(e_empty_sb),
+ reg_magic == MAGIC_ALL);
+ }
+ lastbranch = regnode(BRANCH);
+ br = regnode(NOTHING);
+ if (ret != JUST_CALC_SIZE) {
+ regtail(lastnode, br);
+ regtail(lastbranch, br);
+ // connect all branches to the NOTHING
+ // branch at the end
+ for (br = ret; br != lastnode;) {
+ if (OP(br) == BRANCH) {
+ regtail(br, lastbranch);
+ if (reg_toolong) {
+ return NULL;
+ }
+ br = OPERAND(br);
+ } else {
+ br = regnext(br);
+ }
+ }
+ }
+ *flagp &= ~(HASWIDTH | SIMPLE);
+ break;
+ }
+
+ case 'd': // %d123 decimal
+ case 'o': // %o123 octal
+ case 'x': // %xab hex 2
+ case 'u': // %uabcd hex 4
+ case 'U': // %U1234abcd hex 8
+ {
+ int64_t i;
+
+ switch (c) {
+ case 'd':
+ i = getdecchrs(); break;
+ case 'o':
+ i = getoctchrs(); break;
+ case 'x':
+ i = gethexchrs(2); break;
+ case 'u':
+ i = gethexchrs(4); break;
+ case 'U':
+ i = gethexchrs(8); break;
+ default:
+ i = -1; break;
+ }
+
+ if (i < 0 || i > INT_MAX) {
+ EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"),
+ reg_magic == MAGIC_ALL);
+ }
+ if (use_multibytecode((int)i)) {
+ ret = regnode(MULTIBYTECODE);
+ } else {
+ ret = regnode(EXACTLY);
+ }
+ if (i == 0) {
+ regc(0x0a);
+ } else {
+ regmbc((int)i);
+ }
+ regc(NUL);
+ *flagp |= HASWIDTH;
+ break;
+ }
+
+ default:
+ if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') {
+ uint32_t n = 0;
+ int cmp;
+ bool cur = false;
+ bool got_digit = false;
+
+ cmp = c;
+ if (cmp == '<' || cmp == '>') {
+ c = getchr();
+ }
+ if (no_Magic(c) == '.') {
+ cur = true;
+ c = getchr();
+ }
+ while (ascii_isdigit(c)) {
+ got_digit = true;
+ n = n * 10 + (uint32_t)(c - '0');
+ c = getchr();
+ }
+ if (c == '\'' && n == 0) {
+ // "\%'m", "\%<'m" and "\%>'m": Mark
+ c = getchr();
+ ret = regnode(RE_MARK);
+ if (ret == JUST_CALC_SIZE) {
+ regsize += 2;
+ } else {
+ *regcode++ = (uint8_t)c;
+ *regcode++ = (uint8_t)cmp;
+ }
+ break;
+ } else if ((c == 'l' || c == 'c' || c == 'v') && (cur || got_digit)) {
+ if (cur && n) {
+ semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
+ rc_did_emsg = true;
+ return NULL;
+ }
+ if (c == 'l') {
+ if (cur) {
+ n = (uint32_t)curwin->w_cursor.lnum;
+ }
+ ret = regnode(RE_LNUM);
+ if (save_prev_at_start) {
+ at_start = true;
+ }
+ } else if (c == 'c') {
+ if (cur) {
+ n = (uint32_t)curwin->w_cursor.col;
+ n++;
+ }
+ ret = regnode(RE_COL);
+ } else {
+ if (cur) {
+ colnr_T vcol = 0;
+ getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
+ n = (uint32_t)(++vcol);
+ }
+ ret = regnode(RE_VCOL);
+ }
+ if (ret == JUST_CALC_SIZE) {
+ regsize += 5;
+ } else {
+ // put the number and the optional
+ // comparator after the opcode
+ regcode = re_put_uint32(regcode, n);
+ *regcode++ = (uint8_t)cmp;
+ }
+ break;
+ }
+ }
+
+ EMSG2_RET_NULL(_("E71: Invalid character after %s%%"),
+ reg_magic == MAGIC_ALL);
+ }
+ break;
+
+ case Magic('['):
+collection:
+ {
+ uint8_t *lp;
+
+ // If there is no matching ']', we assume the '[' is a normal
+ // character. This makes 'incsearch' and ":help [" work.
+ lp = (uint8_t *)skip_anyof(regparse);
+ if (*lp == ']') { // there is a matching ']'
+ int startc = -1; // > 0 when next '-' is a range
+ int endc;
+
+ // In a character class, different parsing rules apply.
+ // Not even \ is special anymore, nothing is.
+ if (*regparse == '^') { // Complement of range.
+ ret = regnode(ANYBUT + extra);
+ regparse++;
+ } else {
+ ret = regnode(ANYOF + extra);
+ }
+
+ // At the start ']' and '-' mean the literal character.
+ if (*regparse == ']' || *regparse == '-') {
+ startc = (uint8_t)(*regparse);
+ regc(*regparse++);
+ }
+
+ while (*regparse != NUL && *regparse != ']') {
+ if (*regparse == '-') {
+ regparse++;
+ // The '-' is not used for a range at the end and
+ // after or before a '\n'.
+ if (*regparse == ']' || *regparse == NUL
+ || startc == -1
+ || (regparse[0] == '\\' && regparse[1] == 'n')) {
+ regc('-');
+ startc = '-'; // [--x] is a range
+ } else {
+ // Also accept "a-[.z.]"
+ endc = 0;
+ if (*regparse == '[') {
+ endc = get_coll_element(&regparse);
+ }
+ if (endc == 0) {
+ endc = mb_ptr2char_adv((const char **)&regparse);
+ }
+
+ // Handle \o40, \x20 and \u20AC style sequences
+ if (endc == '\\' && !reg_cpo_lit) {
+ endc = coll_get_char();
+ }
+
+ if (startc > endc) {
+ EMSG_RET_NULL(_(e_reverse_range));
+ }
+ if (utf_char2len(startc) > 1
+ || utf_char2len(endc) > 1) {
+ // Limit to a range of 256 chars
+ if (endc > startc + 256) {
+ EMSG_RET_NULL(_(e_large_class));
+ }
+ while (++startc <= endc) {
+ regmbc(startc);
+ }
+ } else {
+ while (++startc <= endc) {
+ regc(startc);
+ }
+ }
+ startc = -1;
+ }
+ }
+ // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
+ // accepts "\t", "\e", etc., but only when the 'l' flag in
+ // 'cpoptions' is not included.
+ else if (*regparse == '\\'
+ && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
+ || (!reg_cpo_lit
+ && vim_strchr(REGEXP_ABBR,
+ (uint8_t)regparse[1]) != NULL))) {
+ regparse++;
+ if (*regparse == 'n') {
+ // '\n' in range: also match NL
+ if (ret != JUST_CALC_SIZE) {
+ // Using \n inside [^] does not change what
+ // matches. "[^\n]" is the same as ".".
+ if (*ret == ANYOF) {
+ *ret = ANYOF + ADD_NL;
+ *flagp |= HASNL;
+ }
+ // else: must have had a \n already
+ }
+ regparse++;
+ startc = -1;
+ } else if (*regparse == 'd'
+ || *regparse == 'o'
+ || *regparse == 'x'
+ || *regparse == 'u'
+ || *regparse == 'U') {
+ startc = coll_get_char();
+ if (startc == 0) {
+ regc(0x0a);
+ } else {
+ regmbc(startc);
+ }
+ } else {
+ startc = backslash_trans(*regparse++);
+ regc(startc);
+ }
+ } else if (*regparse == '[') {
+ int c_class;
+ int cu;
+
+ c_class = get_char_class(&regparse);
+ startc = -1;
+ // Characters assumed to be 8 bits!
+ switch (c_class) {
+ case CLASS_NONE:
+ c_class = get_equi_class(&regparse);
+ if (c_class != 0) {
+ // produce equivalence class
+ reg_equi_class(c_class);
+ } else if ((c_class = get_coll_element(&regparse)) != 0) {
+ // produce a collating element
+ regmbc(c_class);
+ } else {
+ // literal '[', allow [[-x] as a range
+ startc = (uint8_t)(*regparse++);
+ regc(startc);
+ }
+ break;
+ case CLASS_ALNUM:
+ for (cu = 1; cu < 128; cu++) {
+ if (isalnum(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_ALPHA:
+ for (cu = 1; cu < 128; cu++) {
+ if (isalpha(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_BLANK:
+ regc(' ');
+ regc('\t');
+ break;
+ case CLASS_CNTRL:
+ for (cu = 1; cu <= 127; cu++) {
+ if (iscntrl(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_DIGIT:
+ for (cu = 1; cu <= 127; cu++) {
+ if (ascii_isdigit(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_GRAPH:
+ for (cu = 1; cu <= 127; cu++) {
+ if (isgraph(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_LOWER:
+ for (cu = 1; cu <= 255; cu++) {
+ if (mb_islower(cu) && cu != 170 && cu != 186) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_PRINT:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isprintc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_PUNCT:
+ for (cu = 1; cu < 128; cu++) {
+ if (ispunct(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_SPACE:
+ for (cu = 9; cu <= 13; cu++) {
+ regc(cu);
+ }
+ regc(' ');
+ break;
+ case CLASS_UPPER:
+ for (cu = 1; cu <= 255; cu++) {
+ if (mb_isupper(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_XDIGIT:
+ for (cu = 1; cu <= 255; cu++) {
+ if (ascii_isxdigit(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_TAB:
+ regc('\t');
+ break;
+ case CLASS_RETURN:
+ regc('\r');
+ break;
+ case CLASS_BACKSPACE:
+ regc('\b');
+ break;
+ case CLASS_ESCAPE:
+ regc(ESC);
+ break;
+ case CLASS_IDENT:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isIDc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_KEYWORD:
+ for (cu = 1; cu <= 255; cu++) {
+ if (reg_iswordc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_FNAME:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isfilec(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ }
+ } else {
+ // produce a multibyte character, including any
+ // following composing characters.
+ startc = utf_ptr2char(regparse);
+ int len = utfc_ptr2len(regparse);
+ if (utf_char2len(startc) != len) {
+ // composing chars
+ startc = -1;
+ }
+ while (--len >= 0) {
+ regc(*regparse++);
+ }
+ }
+ }
+ regc(NUL);
+ prevchr_len = 1; // last char was the ']'
+ if (*regparse != ']') {
+ EMSG_RET_NULL(_(e_toomsbra)); // Cannot happen?
+ }
+ skipchr(); // let's be friends with the lexer again
+ *flagp |= HASWIDTH | SIMPLE;
+ break;
+ } else if (reg_strict) {
+ EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF);
+ }
+ }
+ FALLTHROUGH;
+
+ default: {
+ int len;
+
+ // A multi-byte character is handled as a separate atom if it's
+ // before a multi and when it's a composing char.
+ if (use_multibytecode(c)) {
+do_multibyte:
+ ret = regnode(MULTIBYTECODE);
+ regmbc(c);
+ *flagp |= HASWIDTH | SIMPLE;
+ break;
+ }
+
+ ret = regnode(EXACTLY);
+
+ // Append characters as long as:
+ // - there is no following multi, we then need the character in
+ // front of it as a single character operand
+ // - not running into a Magic character
+ // - "one_exactly" is not set
+ // But always emit at least one character. Might be a Multi,
+ // e.g., a "[" without matching "]".
+ for (len = 0; c != NUL && (len == 0
+ || (re_multi_type(peekchr()) == NOT_MULTI
+ && !one_exactly
+ && !is_Magic(c))); len++) {
+ c = no_Magic(c);
+ {
+ regmbc(c);
+ {
+ int l;
+
+ // Need to get composing character too.
+ while (true) {
+ l = utf_ptr2len(regparse);
+ if (!utf_composinglike(regparse, regparse + l)) {
+ break;
+ }
+ regmbc(utf_ptr2char(regparse));
+ skipchr();
+ }
+ }
+ }
+ c = getchr();
+ }
+ ungetchr();
+
+ regc(NUL);
+ *flagp |= HASWIDTH;
+ if (len == 1) {
+ *flagp |= SIMPLE;
+ }
+ }
+ break;
+ }
+
+ return ret;
+}
+
+// Parse something followed by possible [*+=].
+//
+// Note that the branching code sequences used for = and the general cases
+// of * and + are somewhat optimized: they use the same NOTHING node as
+// both the endmarker for their branch list and the body of the last branch.
+// It might seem that this node could be dispensed with entirely, but the
+// endmarker role is not redundant.
+static uint8_t *regpiece(int *flagp)
+{
+ uint8_t *ret;
+ int op;
+ uint8_t *next;
+ int flags;
+ int minval;
+ int maxval;
+
+ ret = regatom(&flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ op = peekchr();
+ if (re_multi_type(op) == NOT_MULTI) {
+ *flagp = flags;
+ return ret;
+ }
+ // default flags
+ *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH)));
+
+ skipchr();
+ switch (op) {
+ case Magic('*'):
+ if (flags & SIMPLE) {
+ reginsert(STAR, ret);
+ } else {
+ // Emit x* as (x&|), where & means "self".
+ reginsert(BRANCH, ret); // Either x
+ regoptail(ret, regnode(BACK)); // and loop
+ regoptail(ret, ret); // back
+ regtail(ret, regnode(BRANCH)); // or
+ regtail(ret, regnode(NOTHING)); // null.
+ }
+ break;
+
+ case Magic('+'):
+ if (flags & SIMPLE) {
+ reginsert(PLUS, ret);
+ } else {
+ // Emit x+ as x(&|), where & means "self".
+ next = regnode(BRANCH); // Either
+ regtail(ret, next);
+ regtail(regnode(BACK), ret); // loop back
+ regtail(next, regnode(BRANCH)); // or
+ regtail(ret, regnode(NOTHING)); // null.
+ }
+ *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH)));
+ break;
+
+ case Magic('@'): {
+ int lop = END;
+ int64_t nr = getdecchrs();
+
+ switch (no_Magic(getchr())) {
+ case '=':
+ lop = MATCH; break; // \@=
+ case '!':
+ lop = NOMATCH; break; // \@!
+ case '>':
+ lop = SUBPAT; break; // \@>
+ case '<':
+ switch (no_Magic(getchr())) {
+ case '=':
+ lop = BEHIND; break; // \@<=
+ case '!':
+ lop = NOBEHIND; break; // \@<!
+ }
+ }
+ if (lop == END) {
+ EMSG2_RET_NULL(_(e_invalid_character_after_str_at),
+ reg_magic == MAGIC_ALL);
+ }
+ // Look behind must match with behind_pos.
+ if (lop == BEHIND || lop == NOBEHIND) {
+ regtail(ret, regnode(BHPOS));
+ *flagp |= HASLOOKBH;
+ }
+ regtail(ret, regnode(END)); // operand ends
+ if (lop == BEHIND || lop == NOBEHIND) {
+ if (nr < 0) {
+ nr = 0; // no limit is same as zero limit
+ }
+ reginsert_nr(lop, (uint32_t)nr, ret);
+ } else {
+ reginsert(lop, ret);
+ }
+ break;
+ }
+
+ case Magic('?'):
+ case Magic('='):
+ // Emit x= as (x|)
+ reginsert(BRANCH, ret); // Either x
+ regtail(ret, regnode(BRANCH)); // or
+ next = regnode(NOTHING); // null.
+ regtail(ret, next);
+ regoptail(ret, next);
+ break;
+
+ case Magic('{'):
+ if (!read_limits(&minval, &maxval)) {
+ return NULL;
+ }
+ if (flags & SIMPLE) {
+ reginsert(BRACE_SIMPLE, ret);
+ reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
+ } else {
+ if (num_complex_braces >= 10) {
+ EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"),
+ reg_magic == MAGIC_ALL);
+ }
+ reginsert(BRACE_COMPLEX + num_complex_braces, ret);
+ regoptail(ret, regnode(BACK));
+ regoptail(ret, ret);
+ reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
+ num_complex_braces++;
+ }
+ if (minval > 0 && maxval > 0) {
+ *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH)));
+ }
+ break;
+ }
+ if (re_multi_type(peekchr()) != NOT_MULTI) {
+ // Can't have a multi follow a multi.
+ if (peekchr() == Magic('*')) {
+ EMSG2_RET_NULL(_("E61: Nested %s*"), reg_magic >= MAGIC_ON);
+ }
+ EMSG3_RET_NULL(_("E62: Nested %s%c"), reg_magic == MAGIC_ALL, no_Magic(peekchr()));
+ }
+
+ return ret;
+}
+
+// Parse one alternative of an | or & operator.
+// Implements the concatenation operator.
+static uint8_t *regconcat(int *flagp)
+{
+ uint8_t *first = NULL;
+ uint8_t *chain = NULL;
+ uint8_t *latest;
+ int flags;
+ int cont = true;
+
+ *flagp = WORST; // Tentatively.
+
+ while (cont) {
+ switch (peekchr()) {
+ case NUL:
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ cont = false;
+ break;
+ case Magic('Z'):
+ regflags |= RF_ICOMBINE;
+ skipchr_keepstart();
+ break;
+ case Magic('c'):
+ regflags |= RF_ICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('C'):
+ regflags |= RF_NOICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('v'):
+ reg_magic = MAGIC_ALL;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('m'):
+ reg_magic = MAGIC_ON;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('M'):
+ reg_magic = MAGIC_OFF;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('V'):
+ reg_magic = MAGIC_NONE;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ default:
+ latest = regpiece(&flags);
+ if (latest == NULL || reg_toolong) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH);
+ if (chain == NULL) { // First piece.
+ *flagp |= flags & SPSTART;
+ } else {
+ regtail(chain, latest);
+ }
+ chain = latest;
+ if (first == NULL) {
+ first = latest;
+ }
+ break;
+ }
+ }
+ if (first == NULL) { // Loop ran zero times.
+ first = regnode(NOTHING);
+ }
+ return first;
+}
+
+// Parse one alternative of an | operator.
+// Implements the & operator.
+static uint8_t *regbranch(int *flagp)
+{
+ uint8_t *ret;
+ uint8_t *chain = NULL;
+ uint8_t *latest;
+ int flags;
+
+ *flagp = WORST | HASNL; // Tentatively.
+
+ ret = regnode(BRANCH);
+ while (true) {
+ latest = regconcat(&flags);
+ if (latest == NULL) {
+ return NULL;
+ }
+ // If one of the branches has width, the whole thing has. If one of
+ // the branches anchors at start-of-line, the whole thing does.
+ // If one of the branches uses look-behind, the whole thing does.
+ *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH);
+ // If one of the branches doesn't match a line-break, the whole thing
+ // doesn't.
+ *flagp &= ~HASNL | (flags & HASNL);
+ if (chain != NULL) {
+ regtail(chain, latest);
+ }
+ if (peekchr() != Magic('&')) {
+ break;
+ }
+ skipchr();
+ regtail(latest, regnode(END)); // operand ends
+ if (reg_toolong) {
+ break;
+ }
+ reginsert(MATCH, latest);
+ chain = latest;
+ }
+
+ return ret;
+}
+
+/// Parse regular expression, i.e. main body or parenthesized thing.
+///
+/// Caller must absorb opening parenthesis.
+///
+/// Combining parenthesis handling with the base level of regular expression
+/// is a trifle forced, but the need to tie the tails of the branches to what
+/// follows makes it hard to avoid.
+///
+/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
+static uint8_t *reg(int paren, int *flagp)
+{
+ uint8_t *ret;
+ uint8_t *br;
+ uint8_t *ender;
+ int parno = 0;
+ int flags;
+
+ *flagp = HASWIDTH; // Tentatively.
+
+ if (paren == REG_ZPAREN) {
+ // Make a ZOPEN node.
+ if (regnzpar >= NSUBEXP) {
+ EMSG_RET_NULL(_("E50: Too many \\z("));
+ }
+ parno = regnzpar;
+ regnzpar++;
+ ret = regnode(ZOPEN + parno);
+ } else if (paren == REG_PAREN) {
+ // Make a MOPEN node.
+ if (regnpar >= NSUBEXP) {
+ EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL);
+ }
+ parno = regnpar;
+ regnpar++;
+ ret = regnode(MOPEN + parno);
+ } else if (paren == REG_NPAREN) {
+ // Make a NOPEN node.
+ ret = regnode(NOPEN);
+ } else {
+ ret = NULL;
+ }
+
+ // Pick up the branches, linking them together.
+ br = regbranch(&flags);
+ if (br == NULL) {
+ return NULL;
+ }
+ if (ret != NULL) {
+ regtail(ret, br); // [MZ]OPEN -> first.
+ } else {
+ ret = br;
+ }
+ // If one of the branches can be zero-width, the whole thing can.
+ // If one of the branches has * at start or matches a line-break, the
+ // whole thing can.
+ if (!(flags & HASWIDTH)) {
+ *flagp &= ~HASWIDTH;
+ }
+ *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
+ while (peekchr() == Magic('|')) {
+ skipchr();
+ br = regbranch(&flags);
+ if (br == NULL || reg_toolong) {
+ return NULL;
+ }
+ regtail(ret, br); // BRANCH -> BRANCH.
+ if (!(flags & HASWIDTH)) {
+ *flagp &= ~HASWIDTH;
+ }
+ *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
+ }
+
+ // Make a closing node, and hook it on the end.
+ ender = regnode(paren == REG_ZPAREN ? ZCLOSE + parno
+ : paren == REG_PAREN ? MCLOSE + parno
+ : paren == REG_NPAREN ? NCLOSE : END);
+ regtail(ret, ender);
+
+ // Hook the tails of the branches to the closing node.
+ for (br = ret; br != NULL; br = regnext(br)) {
+ regoptail(br, ender);
+ }
+
+ // Check for proper termination.
+ if (paren != REG_NOPAREN && getchr() != Magic(')')) {
+ if (paren == REG_ZPAREN) {
+ EMSG_RET_NULL(_("E52: Unmatched \\z("));
+ } else if (paren == REG_NPAREN) {
+ EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
+ }
+ } else if (paren == REG_NOPAREN && peekchr() != NUL) {
+ if (curchr == Magic(')')) {
+ EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG_RET_NULL(_(e_trailing)); // "Can't happen".
+ }
+ // NOTREACHED
+ }
+ // Here we set the flag allowing back references to this set of
+ // parentheses.
+ if (paren == REG_PAREN) {
+ had_endbrace[parno] = true; // have seen the close paren
+ }
+ return ret;
+}
+
+// bt_regcomp() - compile a regular expression into internal code for the
+// traditional back track matcher.
+// Returns the program in allocated space. Returns NULL for an error.
+//
+// We can't allocate space until we know how big the compiled form will be,
+// but we can't compile it (and thus know how big it is) until we've got a
+// place to put the code. So we cheat: we compile it twice, once with code
+// generation turned off and size counting turned on, and once "for real".
+// This also means that we don't allocate space until we are sure that the
+// thing really will compile successfully, and we never have to move the
+// code and thus invalidate pointers into it. (Note that it has to be in
+// one piece because free() must be able to free it all.)
+//
+// Whether upper/lower case is to be ignored is decided when executing the
+// program, it does not matter here.
+//
+// Beware that the optimization-preparation code in here knows about some
+// of the structure of the compiled regexp.
+// "re_flags": RE_MAGIC and/or RE_STRING.
+static regprog_T *bt_regcomp(uint8_t *expr, int re_flags)
+{
+ uint8_t *scan;
+ uint8_t *longest;
+ int len;
+ int flags;
+
+ if (expr == NULL) {
+ IEMSG_RET_NULL(_(e_null));
+ }
+
+ init_class_tab();
+
+ // First pass: determine size, legality.
+ regcomp_start(expr, re_flags);
+ regcode = JUST_CALC_SIZE;
+ regc(REGMAGIC);
+ if (reg(REG_NOPAREN, &flags) == NULL) {
+ return NULL;
+ }
+
+ // Allocate space.
+ bt_regprog_T *r = xmalloc(offsetof(bt_regprog_T, program) + (size_t)regsize);
+ r->re_in_use = false;
+
+ // Second pass: emit code.
+ regcomp_start(expr, re_flags);
+ regcode = r->program;
+ regc(REGMAGIC);
+ if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) {
+ xfree(r);
+ if (reg_toolong) {
+ EMSG_RET_NULL(_("E339: Pattern too long"));
+ }
+ return NULL;
+ }
+
+ // Dig out information for optimizations.
+ r->regstart = NUL; // Worst-case defaults.
+ r->reganch = 0;
+ r->regmust = NULL;
+ r->regmlen = 0;
+ r->regflags = regflags;
+ if (flags & HASNL) {
+ r->regflags |= RF_HASNL;
+ }
+ if (flags & HASLOOKBH) {
+ r->regflags |= RF_LOOKBH;
+ }
+ // Remember whether this pattern has any \z specials in it.
+ r->reghasz = (uint8_t)re_has_z;
+ scan = r->program + 1; // First BRANCH.
+ if (OP(regnext(scan)) == END) { // Only one top-level choice.
+ scan = OPERAND(scan);
+
+ // Starting-point info.
+ if (OP(scan) == BOL || OP(scan) == RE_BOF) {
+ r->reganch++;
+ scan = regnext(scan);
+ }
+
+ if (OP(scan) == EXACTLY) {
+ r->regstart = utf_ptr2char((char *)OPERAND(scan));
+ } else if (OP(scan) == BOW
+ || OP(scan) == EOW
+ || OP(scan) == NOTHING
+ || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN
+ || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) {
+ uint8_t *regnext_scan = regnext(scan);
+ if (OP(regnext_scan) == EXACTLY) {
+ r->regstart = utf_ptr2char((char *)OPERAND(regnext_scan));
+ }
+ }
+
+ // If there's something expensive in the r.e., find the longest
+ // literal string that must appear and make it the regmust. Resolve
+ // ties in favor of later strings, since the regstart check works
+ // with the beginning of the r.e. and avoiding duplication
+ // strengthens checking. Not a strong reason, but sufficient in the
+ // absence of others.
+
+ // When the r.e. starts with BOW, it is faster to look for a regmust
+ // first. Used a lot for "#" and "*" commands. (Added by mool).
+ if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW)
+ && !(flags & HASNL)) {
+ longest = NULL;
+ len = 0;
+ for (; scan != NULL; scan = regnext(scan)) {
+ if (OP(scan) == EXACTLY && strlen((char *)OPERAND(scan)) >= (size_t)len) {
+ longest = OPERAND(scan);
+ len = (int)strlen((char *)OPERAND(scan));
+ }
+ }
+ r->regmust = longest;
+ r->regmlen = len;
+ }
+ }
+#ifdef BT_REGEXP_DUMP
+ regdump(expr, r);
+#endif
+ r->engine = &bt_regengine;
+ return (regprog_T *)r;
+}
+
+// Check if during the previous call to vim_regcomp the EOL item "$" has been
+// found. This is messy, but it works fine.
+int vim_regcomp_had_eol(void)
+{
+ return had_eol;
+}
+
+// Get a number after a backslash that is inside [].
+// When nothing is recognized return a backslash.
+static int coll_get_char(void)
+{
+ int64_t nr = -1;
+
+ switch (*regparse++) {
+ case 'd':
+ nr = getdecchrs(); break;
+ case 'o':
+ nr = getoctchrs(); break;
+ case 'x':
+ nr = gethexchrs(2); break;
+ case 'u':
+ nr = gethexchrs(4); break;
+ case 'U':
+ nr = gethexchrs(8); break;
+ }
+ if (nr < 0 || nr > INT_MAX) {
+ // If getting the number fails be backwards compatible: the character
+ // is a backslash.
+ regparse--;
+ nr = '\\';
+ }
+ return (int)nr;
+}
+
+// Free a compiled regexp program, returned by bt_regcomp().
+static void bt_regfree(regprog_T *prog)
+{
+ xfree(prog);
+}
+
+#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input)
+
+// The arguments from BRACE_LIMITS are stored here. They are actually local
+// to regmatch(), but they are here to reduce the amount of stack space used
+// (it can be called recursively many times).
+static int64_t bl_minval;
+static int64_t bl_maxval;
+
+// Save the input line and position in a regsave_T.
+static void reg_save(regsave_T *save, garray_T *gap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (REG_MULTI) {
+ save->rs_u.pos.col = (colnr_T)(rex.input - rex.line);
+ save->rs_u.pos.lnum = rex.lnum;
+ } else {
+ save->rs_u.ptr = rex.input;
+ }
+ save->rs_len = gap->ga_len;
+}
+
+// Restore the input line and position from a regsave_T.
+static void reg_restore(regsave_T *save, garray_T *gap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (REG_MULTI) {
+ if (rex.lnum != save->rs_u.pos.lnum) {
+ // only call reg_getline() when the line number changed to save
+ // a bit of time
+ rex.lnum = save->rs_u.pos.lnum;
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ }
+ rex.input = rex.line + save->rs_u.pos.col;
+ } else {
+ rex.input = save->rs_u.ptr;
+ }
+ gap->ga_len = save->rs_len;
+}
+
+// Return true if current position is equal to saved position.
+static bool reg_save_equal(const regsave_T *save)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (REG_MULTI) {
+ return rex.lnum == save->rs_u.pos.lnum
+ && rex.input == rex.line + save->rs_u.pos.col;
+ }
+ return rex.input == save->rs_u.ptr;
+}
+
+// Save the sub-expressions before attempting a match.
+#define save_se(savep, posp, pp) \
+ REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp))
+
+// After a failed match restore the sub-expressions.
+#define restore_se(savep, posp, pp) { \
+ if (REG_MULTI) /* NOLINT(readability/braces) */ \
+ *(posp) = (savep)->se_u.pos; \
+ else /* NOLINT */ \
+ *(pp) = (savep)->se_u.ptr; }
+
+// Tentatively set the sub-expression start to the current position (after
+// calling regmatch() they will have changed). Need to save the existing
+// values for when there is no match.
+// Use se_save() to use pointer (save_se_multi()) or position (save_se_one()),
+// depending on REG_MULTI.
+static void save_se_multi(save_se_T *savep, lpos_T *posp)
+{
+ savep->se_u.pos = *posp;
+ posp->lnum = rex.lnum;
+ posp->col = (colnr_T)(rex.input - rex.line);
+}
+
+static void save_se_one(save_se_T *savep, uint8_t **pp)
+{
+ savep->se_u.ptr = *pp;
+ *pp = rex.input;
+}
+
+/// regrepeat - repeatedly match something simple, return how many.
+/// Advances rex.input (and rex.lnum) to just after the matched chars.
+///
+/// @param maxcount maximum number of matches allowed
+static int regrepeat(uint8_t *p, int64_t maxcount)
+{
+ int64_t count = 0;
+ uint8_t *opnd;
+ int mask;
+ int testval = 0;
+
+ uint8_t *scan = rex.input; // Make local copy of rex.input for speed.
+ opnd = OPERAND(p);
+ switch (OP(p)) {
+ case ANY:
+ case ANY + ADD_NL:
+ while (count < maxcount) {
+ // Matching anything means we continue until end-of-line (or
+ // end-of-file for ANY + ADD_NL), only limited by maxcount.
+ while (*scan != NUL && count < maxcount) {
+ count++;
+ MB_PTR_ADV(scan);
+ }
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr || count == maxcount) {
+ break;
+ }
+ count++; // count the line-break
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ }
+ break;
+
+ case IDENT:
+ case IDENT + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SIDENT:
+ case SIDENT + ADD_NL:
+ while (count < maxcount) {
+ if (vim_isIDc(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case KWORD:
+ case KWORD + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SKWORD:
+ case SKWORD + ADD_NL:
+ while (count < maxcount) {
+ if (vim_iswordp_buf((char *)scan, rex.reg_buf)
+ && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case FNAME:
+ case FNAME + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SFNAME:
+ case SFNAME + ADD_NL:
+ while (count < maxcount) {
+ if (vim_isfilec(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case PRINT:
+ case PRINT + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SPRINT:
+ case SPRINT + ADD_NL:
+ while (count < maxcount) {
+ if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (vim_isprintc(utf_ptr2char((char *)scan)) == 1
+ && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case WHITE:
+ case WHITE + ADD_NL:
+ testval = mask = RI_WHITE;
+do_class:
+ while (count < maxcount) {
+ int l;
+ if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if ((l = utfc_ptr2len((char *)scan)) > 1) {
+ if (testval != 0) {
+ break;
+ }
+ scan += l;
+ } else if ((class_tab[*scan] & mask) == testval) {
+ scan++;
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case NWHITE:
+ case NWHITE + ADD_NL:
+ mask = RI_WHITE;
+ goto do_class;
+ case DIGIT:
+ case DIGIT + ADD_NL:
+ testval = mask = RI_DIGIT;
+ goto do_class;
+ case NDIGIT:
+ case NDIGIT + ADD_NL:
+ mask = RI_DIGIT;
+ goto do_class;
+ case HEX:
+ case HEX + ADD_NL:
+ testval = mask = RI_HEX;
+ goto do_class;
+ case NHEX:
+ case NHEX + ADD_NL:
+ mask = RI_HEX;
+ goto do_class;
+ case OCTAL:
+ case OCTAL + ADD_NL:
+ testval = mask = RI_OCTAL;
+ goto do_class;
+ case NOCTAL:
+ case NOCTAL + ADD_NL:
+ mask = RI_OCTAL;
+ goto do_class;
+ case WORD:
+ case WORD + ADD_NL:
+ testval = mask = RI_WORD;
+ goto do_class;
+ case NWORD:
+ case NWORD + ADD_NL:
+ mask = RI_WORD;
+ goto do_class;
+ case HEAD:
+ case HEAD + ADD_NL:
+ testval = mask = RI_HEAD;
+ goto do_class;
+ case NHEAD:
+ case NHEAD + ADD_NL:
+ mask = RI_HEAD;
+ goto do_class;
+ case ALPHA:
+ case ALPHA + ADD_NL:
+ testval = mask = RI_ALPHA;
+ goto do_class;
+ case NALPHA:
+ case NALPHA + ADD_NL:
+ mask = RI_ALPHA;
+ goto do_class;
+ case LOWER:
+ case LOWER + ADD_NL:
+ testval = mask = RI_LOWER;
+ goto do_class;
+ case NLOWER:
+ case NLOWER + ADD_NL:
+ mask = RI_LOWER;
+ goto do_class;
+ case UPPER:
+ case UPPER + ADD_NL:
+ testval = mask = RI_UPPER;
+ goto do_class;
+ case NUPPER:
+ case NUPPER + ADD_NL:
+ mask = RI_UPPER;
+ goto do_class;
+
+ case EXACTLY: {
+ int cu, cl;
+
+ // This doesn't do a multi-byte character, because a MULTIBYTECODE
+ // would have been used for it. It does handle single-byte
+ // characters, such as latin1.
+ if (rex.reg_ic) {
+ cu = mb_toupper(*opnd);
+ cl = mb_tolower(*opnd);
+ while (count < maxcount && (*scan == cu || *scan == cl)) {
+ count++;
+ scan++;
+ }
+ } else {
+ cu = *opnd;
+ while (count < maxcount && *scan == cu) {
+ count++;
+ scan++;
+ }
+ }
+ break;
+ }
+
+ case MULTIBYTECODE: {
+ int i, len, cf = 0;
+
+ // Safety check (just in case 'encoding' was changed since
+ // compiling the program).
+ if ((len = utfc_ptr2len((char *)opnd)) > 1) {
+ if (rex.reg_ic) {
+ cf = utf_fold(utf_ptr2char((char *)opnd));
+ }
+ while (count < maxcount && utfc_ptr2len((char *)scan) >= len) {
+ for (i = 0; i < len; i++) {
+ if (opnd[i] != scan[i]) {
+ break;
+ }
+ }
+ if (i < len && (!rex.reg_ic
+ || utf_fold(utf_ptr2char((char *)scan)) != cf)) {
+ break;
+ }
+ scan += len;
+ count++;
+ }
+ }
+ }
+ break;
+
+ case ANYOF:
+ case ANYOF + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+
+ case ANYBUT:
+ case ANYBUT + ADD_NL:
+ while (count < maxcount) {
+ int len;
+ if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else if ((len = utfc_ptr2len((char *)scan)) > 1) {
+ if ((cstrchr((char *)opnd, utf_ptr2char((char *)scan)) == NULL) == testval) {
+ break;
+ }
+ scan += len;
+ } else {
+ if ((cstrchr((char *)opnd, *scan) == NULL) == testval) {
+ break;
+ }
+ scan++;
+ }
+ count++;
+ }
+ break;
+
+ case NEWL:
+ while (count < maxcount
+ && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr
+ && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) {
+ count++;
+ if (rex.reg_line_lbr) {
+ ADVANCE_REGINPUT();
+ } else {
+ reg_nextline();
+ }
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ }
+ break;
+
+ default: // Oh dear. Called inappropriately.
+ iemsg(_(e_re_corr));
+#ifdef REGEXP_DEBUG
+ printf("Called regrepeat with op code %d\n", OP(p));
+#endif
+ break;
+ }
+
+ rex.input = scan;
+
+ return (int)count;
+}
+
+// Push an item onto the regstack.
+// Returns pointer to new item. Returns NULL when out of memory.
+static regitem_T *regstack_push(regstate_T state, uint8_t *scan)
+{
+ regitem_T *rp;
+
+ if ((int64_t)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ return NULL;
+ }
+ ga_grow(&regstack, sizeof(regitem_T));
+
+ rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len);
+ rp->rs_state = state;
+ rp->rs_scan = scan;
+
+ regstack.ga_len += (int)sizeof(regitem_T);
+ return rp;
+}
+
+// Pop an item from the regstack.
+static void regstack_pop(uint8_t **scan)
+{
+ regitem_T *rp;
+
+ rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
+ *scan = rp->rs_scan;
+
+ regstack.ga_len -= (int)sizeof(regitem_T);
+}
+
+// Save the current subexpr to "bp", so that they can be restored
+// later by restore_subexpr().
+static void save_subexpr(regbehind_T *bp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // When "rex.need_clear_subexpr" is set we don't need to save the values, only
+ // remember that this flag needs to be set again when restoring.
+ bp->save_need_clear_subexpr = rex.need_clear_subexpr;
+ if (rex.need_clear_subexpr) {
+ return;
+ }
+
+ for (int i = 0; i < NSUBEXP; i++) {
+ if (REG_MULTI) {
+ bp->save_start[i].se_u.pos = rex.reg_startpos[i];
+ bp->save_end[i].se_u.pos = rex.reg_endpos[i];
+ } else {
+ bp->save_start[i].se_u.ptr = rex.reg_startp[i];
+ bp->save_end[i].se_u.ptr = rex.reg_endp[i];
+ }
+ }
+}
+
+// Restore the subexpr from "bp".
+static void restore_subexpr(regbehind_T *bp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Only need to restore saved values when they are not to be cleared.
+ rex.need_clear_subexpr = bp->save_need_clear_subexpr;
+ if (rex.need_clear_subexpr) {
+ return;
+ }
+
+ for (int i = 0; i < NSUBEXP; i++) {
+ if (REG_MULTI) {
+ rex.reg_startpos[i] = bp->save_start[i].se_u.pos;
+ rex.reg_endpos[i] = bp->save_end[i].se_u.pos;
+ } else {
+ rex.reg_startp[i] = bp->save_start[i].se_u.ptr;
+ rex.reg_endp[i] = bp->save_end[i].se_u.ptr;
+ }
+ }
+}
+/// Main matching routine
+///
+/// Conceptually the strategy is simple: Check to see whether the current node
+/// matches, push an item onto the regstack and loop to see whether the rest
+/// matches, and then act accordingly. In practice we make some effort to
+/// avoid using the regstack, in particular by going through "ordinary" nodes
+/// (that don't need to know whether the rest of the match failed) by a nested
+/// loop.
+///
+/// @param scan Current node.
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return - true when there is a match. Leaves rex.input and rex.lnum
+/// just after the last matched character.
+/// - false when there is no match. Leaves rex.input and rex.lnum in an
+/// undefined state!
+static bool regmatch(uint8_t *scan, const proftime_T *tm, int *timed_out)
+{
+ uint8_t *next; // Next node.
+ int op;
+ int c;
+ regitem_T *rp;
+ int no;
+ int status; // one of the RA_ values:
+ int tm_count = 0;
+
+ // Make "regstack" and "backpos" empty. They are allocated and freed in
+ // bt_regexec_both() to reduce malloc()/free() calls.
+ regstack.ga_len = 0;
+ backpos.ga_len = 0;
+
+ // Repeat until "regstack" is empty.
+ while (true) {
+ // Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q".
+ // Allow interrupting them with CTRL-C.
+ reg_breakcheck();
+
+#ifdef REGEXP_DEBUG
+ if (scan != NULL && regnarrate) {
+ os_errmsg((char *)regprop(scan));
+ os_errmsg("(\n");
+ }
+#endif
+
+ // Repeat for items that can be matched sequentially, without using the
+ // regstack.
+ while (true) {
+ if (got_int || scan == NULL) {
+ status = RA_FAIL;
+ break;
+ }
+ // Check for timeout once in a 100 times to avoid overhead.
+ if (tm != NULL && ++tm_count == 100) {
+ tm_count = 0;
+ if (profile_passed_limit(*tm)) {
+ if (timed_out != NULL) {
+ *timed_out = true;
+ }
+ status = RA_FAIL;
+ break;
+ }
+ }
+ status = RA_CONT;
+
+#ifdef REGEXP_DEBUG
+ if (regnarrate) {
+ os_errmsg((char *)regprop(scan));
+ os_errmsg("...\n");
+ if (re_extmatch_in != NULL) {
+ int i;
+
+ os_errmsg(_("External submatches:\n"));
+ for (i = 0; i < NSUBEXP; i++) {
+ os_errmsg(" \"");
+ if (re_extmatch_in->matches[i] != NULL) {
+ os_errmsg((char *)re_extmatch_in->matches[i]);
+ }
+ os_errmsg("\"\n");
+ }
+ }
+ }
+#endif
+ next = regnext(scan);
+
+ op = OP(scan);
+ // Check for character class with NL added.
+ if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI
+ && *rex.input == NUL && rex.lnum <= rex.reg_maxline) {
+ reg_nextline();
+ } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') {
+ ADVANCE_REGINPUT();
+ } else {
+ if (WITH_NL(op)) {
+ op -= ADD_NL;
+ }
+ c = utf_ptr2char((char *)rex.input);
+ switch (op) {
+ case BOL:
+ if (rex.input != rex.line) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case EOL:
+ if (c != NUL) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_BOF:
+ // We're not at the beginning of the file when below the first
+ // line where we started, not at the start of the line or we
+ // didn't start at the first line of the buffer.
+ if (rex.lnum != 0 || rex.input != rex.line
+ || (REG_MULTI && rex.reg_firstlnum > 1)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_EOF:
+ if (rex.lnum != rex.reg_maxline || c != NUL) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case CURSOR:
+ // Check if the buffer is in a window and compare the
+ // rex.reg_win->w_cursor position to the match position.
+ if (rex.reg_win == NULL
+ || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum)
+ || ((colnr_T)(rex.input - rex.line) !=
+ rex.reg_win->w_cursor.col)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_MARK:
+ // Compare the mark position to the match position.
+ {
+ int mark = OPERAND(scan)[0];
+ int cmp = OPERAND(scan)[1];
+ pos_T *pos;
+ size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
+ fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, mark);
+
+ // Line may have been freed, get it again.
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ rex.input = rex.line + col;
+ }
+
+ if (fm == NULL // mark doesn't exist
+ || fm->mark.lnum <= 0) { // mark isn't set in reg_buf
+ status = RA_NOMATCH;
+ } else {
+ pos = &fm->mark;
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)strlen(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ if (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? (cmp == '<' || cmp == '>')
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? cmp != '>'
+ : cmp != '<'))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? cmp != '>'
+ : cmp != '<')) {
+ status = RA_NOMATCH;
+ }
+ }
+ }
+ break;
+
+ case RE_VISUAL:
+ if (!reg_match_visual()) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_LNUM:
+ assert(rex.lnum + rex.reg_firstlnum >= 0
+ && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX);
+ if (!REG_MULTI
+ || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_COL:
+ assert(rex.input - rex.line + 1 >= 0
+ && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX);
+ if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_VCOL:
+ if (!re_num_cmp((unsigned)win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win,
+ rex.reg_firstlnum + rex.lnum,
+ (char *)rex.line,
+ (colnr_T)(rex.input - rex.line)) + 1,
+ scan)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case BOW: // \<word; rex.input points to w
+ if (c == NUL) { // Can't match at end of line
+ status = RA_NOMATCH;
+ } else {
+ // Get class of current and previous char (if it exists).
+ const int this_class =
+ mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ if (this_class <= 1) {
+ status = RA_NOMATCH; // Not on a word at all.
+ } else if (reg_prev_class() == this_class) {
+ status = RA_NOMATCH; // Previous char is in same word.
+ }
+ }
+ break;
+
+ case EOW: // word\>; rex.input points after d
+ if (rex.input == rex.line) { // Can't match at start of line
+ status = RA_NOMATCH;
+ } else {
+ int this_class, prev_class;
+
+ // Get class of current and previous char (if it exists).
+ this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ prev_class = reg_prev_class();
+ if (this_class == prev_class
+ || prev_class == 0 || prev_class == 1) {
+ status = RA_NOMATCH;
+ }
+ }
+ break; // Matched with EOW
+
+ case ANY:
+ // ANY does not match new lines.
+ if (c == NUL) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case IDENT:
+ if (!vim_isIDc(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SIDENT:
+ if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case KWORD:
+ if (!vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SKWORD:
+ if (ascii_isdigit(*rex.input)
+ || !vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case FNAME:
+ if (!vim_isfilec(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SFNAME:
+ if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case PRINT:
+ if (!vim_isprintc(utf_ptr2char((char *)rex.input))) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SPRINT:
+ if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char((char *)rex.input))) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case WHITE:
+ if (!ascii_iswhite(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NWHITE:
+ if (c == NUL || ascii_iswhite(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case DIGIT:
+ if (!ri_digit(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NDIGIT:
+ if (c == NUL || ri_digit(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case HEX:
+ if (!ri_hex(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NHEX:
+ if (c == NUL || ri_hex(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case OCTAL:
+ if (!ri_octal(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NOCTAL:
+ if (c == NUL || ri_octal(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case WORD:
+ if (!ri_word(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NWORD:
+ if (c == NUL || ri_word(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case HEAD:
+ if (!ri_head(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NHEAD:
+ if (c == NUL || ri_head(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case ALPHA:
+ if (!ri_alpha(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NALPHA:
+ if (c == NUL || ri_alpha(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case LOWER:
+ if (!ri_lower(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NLOWER:
+ if (c == NUL || ri_lower(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case UPPER:
+ if (!ri_upper(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NUPPER:
+ if (c == NUL || ri_upper(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case EXACTLY: {
+ int len;
+ uint8_t *opnd;
+
+ opnd = OPERAND(scan);
+ // Inline the first byte, for speed.
+ if (*opnd != *rex.input
+ && (!rex.reg_ic)) {
+ status = RA_NOMATCH;
+ } else if (*opnd == NUL) {
+ // match empty string always works; happens when "~" is
+ // empty.
+ } else {
+ if (opnd[1] == NUL && !rex.reg_ic) {
+ len = 1; // matched a single byte above
+ } else {
+ // Need to match first byte again for multi-byte.
+ len = (int)strlen((char *)opnd);
+ if (cstrncmp((char *)opnd, (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ }
+ }
+ // Check for following composing character, unless %C
+ // follows (skips over all composing chars).
+ if (status != RA_NOMATCH
+ && utf_composinglike((char *)rex.input, (char *)rex.input + len)
+ && !rex.reg_icombine
+ && OP(next) != RE_COMPOSING) {
+ // raaron: This code makes a composing character get
+ // ignored, which is the correct behavior (sometimes)
+ // for voweled Hebrew texts.
+ status = RA_NOMATCH;
+ }
+ if (status != RA_NOMATCH) {
+ rex.input += len;
+ }
+ }
+ }
+ break;
+
+ case ANYOF:
+ case ANYBUT:
+ if (c == NUL) {
+ status = RA_NOMATCH;
+ } else if ((cstrchr((char *)OPERAND(scan), c) == NULL) == (op == ANYOF)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case MULTIBYTECODE: {
+ int i, len;
+
+ const uint8_t *opnd = OPERAND(scan);
+ // Safety check (just in case 'encoding' was changed since
+ // compiling the program).
+ if ((len = utfc_ptr2len((char *)opnd)) < 2) {
+ status = RA_NOMATCH;
+ break;
+ }
+ const int opndc = utf_ptr2char((char *)opnd);
+ if (utf_iscomposing(opndc)) {
+ // When only a composing char is given match at any
+ // position where that composing char appears.
+ status = RA_NOMATCH;
+ for (i = 0; rex.input[i] != NUL;
+ i += utf_ptr2len((char *)rex.input + i)) {
+ const int inpc = utf_ptr2char((char *)rex.input + i);
+ if (!utf_iscomposing(inpc)) {
+ if (i > 0) {
+ break;
+ }
+ } else if (opndc == inpc) {
+ // Include all following composing chars.
+ len = i + utfc_ptr2len((char *)rex.input + i);
+ status = RA_MATCH;
+ break;
+ }
+ }
+ } else {
+ for (i = 0; i < len; i++) {
+ if (opnd[i] != rex.input[i]) {
+ status = RA_NOMATCH;
+ break;
+ }
+ }
+ }
+ rex.input += len;
+ }
+ break;
+
+ case RE_COMPOSING:
+ // Skip composing characters.
+ while (utf_iscomposing(utf_ptr2char((char *)rex.input))) {
+ MB_CPTR_ADV(rex.input);
+ }
+ break;
+
+ case NOTHING:
+ break;
+
+ case BACK: {
+ int i;
+
+ // When we run into BACK we need to check if we don't keep
+ // looping without matching any input. The second and later
+ // times a BACK is encountered it fails if the input is still
+ // at the same position as the previous time.
+ // The positions are stored in "backpos" and found by the
+ // current value of "scan", the position in the RE program.
+ backpos_T *bp = (backpos_T *)backpos.ga_data;
+ for (i = 0; i < backpos.ga_len; i++) {
+ if (bp[i].bp_scan == scan) {
+ break;
+ }
+ }
+ if (i == backpos.ga_len) {
+ backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos);
+ p->bp_scan = scan;
+ } else if (reg_save_equal(&bp[i].bp_pos)) {
+ // Still at same position as last time, fail.
+ status = RA_NOMATCH;
+ }
+
+ assert(status != RA_FAIL);
+ if (status != RA_NOMATCH) {
+ reg_save(&bp[i].bp_pos, &backpos);
+ }
+ }
+ break;
+
+ case MOPEN + 0: // Match start: \zs
+ case MOPEN + 1: // \(
+ case MOPEN + 2:
+ case MOPEN + 3:
+ case MOPEN + 4:
+ case MOPEN + 5:
+ case MOPEN + 6:
+ case MOPEN + 7:
+ case MOPEN + 8:
+ case MOPEN + 9:
+ no = op - MOPEN;
+ cleanup_subexpr();
+ rp = regstack_push(RS_MOPEN, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &rex.reg_startpos[no],
+ &rex.reg_startp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case NOPEN: // \%(
+ case NCLOSE: // \) after \%(
+ if (regstack_push(RS_NOPEN, scan) == NULL) {
+ status = RA_FAIL;
+ }
+ // We simply continue and handle the result when done.
+ break;
+
+ case ZOPEN + 1:
+ case ZOPEN + 2:
+ case ZOPEN + 3:
+ case ZOPEN + 4:
+ case ZOPEN + 5:
+ case ZOPEN + 6:
+ case ZOPEN + 7:
+ case ZOPEN + 8:
+ case ZOPEN + 9:
+ no = op - ZOPEN;
+ cleanup_zsubexpr();
+ rp = regstack_push(RS_ZOPEN, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &reg_startzpos[no],
+ &reg_startzp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case MCLOSE + 0: // Match end: \ze
+ case MCLOSE + 1: // \)
+ case MCLOSE + 2:
+ case MCLOSE + 3:
+ case MCLOSE + 4:
+ case MCLOSE + 5:
+ case MCLOSE + 6:
+ case MCLOSE + 7:
+ case MCLOSE + 8:
+ case MCLOSE + 9:
+ no = op - MCLOSE;
+ cleanup_subexpr();
+ rp = regstack_push(RS_MCLOSE, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case ZCLOSE + 1: // \) after \z(
+ case ZCLOSE + 2:
+ case ZCLOSE + 3:
+ case ZCLOSE + 4:
+ case ZCLOSE + 5:
+ case ZCLOSE + 6:
+ case ZCLOSE + 7:
+ case ZCLOSE + 8:
+ case ZCLOSE + 9:
+ no = op - ZCLOSE;
+ cleanup_zsubexpr();
+ rp = regstack_push(RS_ZCLOSE, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &reg_endzpos[no],
+ &reg_endzp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case BACKREF + 1:
+ case BACKREF + 2:
+ case BACKREF + 3:
+ case BACKREF + 4:
+ case BACKREF + 5:
+ case BACKREF + 6:
+ case BACKREF + 7:
+ case BACKREF + 8:
+ case BACKREF + 9: {
+ int len;
+
+ no = op - BACKREF;
+ cleanup_subexpr();
+ if (!REG_MULTI) { // Single-line regexp
+ if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) {
+ // Backref was not set: Match an empty string.
+ len = 0;
+ } else {
+ // Compare current input with back-ref in the same line.
+ len = (int)(rex.reg_endp[no] - rex.reg_startp[no]);
+ if (cstrncmp((char *)rex.reg_startp[no], (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ }
+ }
+ } else { // Multi-line regexp
+ if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) {
+ // Backref was not set: Match an empty string.
+ len = 0;
+ } else {
+ if (rex.reg_startpos[no].lnum == rex.lnum
+ && rex.reg_endpos[no].lnum == rex.lnum) {
+ // Compare back-ref within the current line.
+ len = rex.reg_endpos[no].col - rex.reg_startpos[no].col;
+ if (cstrncmp((char *)rex.line + rex.reg_startpos[no].col,
+ (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ }
+ } else {
+ // Messy situation: Need to compare between two lines.
+ int r = match_with_backref(rex.reg_startpos[no].lnum,
+ rex.reg_startpos[no].col,
+ rex.reg_endpos[no].lnum,
+ rex.reg_endpos[no].col,
+ &len);
+ if (r != RA_MATCH) {
+ status = r;
+ }
+ }
+ }
+ }
+
+ // Matched the backref, skip over it.
+ rex.input += len;
+ }
+ break;
+
+ case ZREF + 1:
+ case ZREF + 2:
+ case ZREF + 3:
+ case ZREF + 4:
+ case ZREF + 5:
+ case ZREF + 6:
+ case ZREF + 7:
+ case ZREF + 8:
+ case ZREF + 9:
+ cleanup_zsubexpr();
+ no = op - ZREF;
+ if (re_extmatch_in != NULL
+ && re_extmatch_in->matches[no] != NULL) {
+ int len = (int)strlen((char *)re_extmatch_in->matches[no]);
+ if (cstrncmp((char *)re_extmatch_in->matches[no], (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ } else {
+ rex.input += len;
+ }
+ } else {
+ // Backref was not set: Match an empty string.
+ }
+ break;
+
+ case BRANCH:
+ if (OP(next) != BRANCH) { // No choice.
+ next = OPERAND(scan); // Avoid recursion.
+ } else {
+ rp = regstack_push(RS_BRANCH, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ status = RA_BREAK; // rest is below
+ }
+ }
+ break;
+
+ case BRACE_LIMITS:
+ if (OP(next) == BRACE_SIMPLE) {
+ bl_minval = OPERAND_MIN(scan);
+ bl_maxval = OPERAND_MAX(scan);
+ } else if (OP(next) >= BRACE_COMPLEX
+ && OP(next) < BRACE_COMPLEX + 10) {
+ no = OP(next) - BRACE_COMPLEX;
+ brace_min[no] = OPERAND_MIN(scan);
+ brace_max[no] = OPERAND_MAX(scan);
+ brace_count[no] = 0;
+ } else {
+ internal_error("BRACE_LIMITS");
+ status = RA_FAIL;
+ }
+ break;
+
+ case BRACE_COMPLEX + 0:
+ case BRACE_COMPLEX + 1:
+ case BRACE_COMPLEX + 2:
+ case BRACE_COMPLEX + 3:
+ case BRACE_COMPLEX + 4:
+ case BRACE_COMPLEX + 5:
+ case BRACE_COMPLEX + 6:
+ case BRACE_COMPLEX + 7:
+ case BRACE_COMPLEX + 8:
+ case BRACE_COMPLEX + 9:
+ no = op - BRACE_COMPLEX;
+ brace_count[no]++;
+
+ // If not matched enough times yet, try one more
+ if (brace_count[no] <= (brace_min[no] <= brace_max[no]
+ ? brace_min[no] : brace_max[no])) {
+ rp = regstack_push(RS_BRCPLX_MORE, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ next = OPERAND(scan);
+ // We continue and handle the result when done.
+ }
+ break;
+ }
+
+ // If matched enough times, may try matching some more
+ if (brace_min[no] <= brace_max[no]) {
+ // Range is the normal way around, use longest match
+ if (brace_count[no] <= brace_max[no]) {
+ rp = regstack_push(RS_BRCPLX_LONG, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ next = OPERAND(scan);
+ // We continue and handle the result when done.
+ }
+ }
+ } else {
+ // Range is backwards, use shortest match first
+ if (brace_count[no] <= brace_min[no]) {
+ rp = regstack_push(RS_BRCPLX_SHORT, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ reg_save(&rp->rs_un.regsave, &backpos);
+ // We continue and handle the result when done.
+ }
+ }
+ }
+ break;
+
+ case BRACE_SIMPLE:
+ case STAR:
+ case PLUS: {
+ regstar_T rst;
+
+ // Lookahead to avoid useless match attempts when we know
+ // what character comes next.
+ if (OP(next) == EXACTLY) {
+ rst.nextb = *OPERAND(next);
+ if (rex.reg_ic) {
+ if (mb_isupper(rst.nextb)) {
+ rst.nextb_ic = mb_tolower(rst.nextb);
+ } else {
+ rst.nextb_ic = mb_toupper(rst.nextb);
+ }
+ } else {
+ rst.nextb_ic = rst.nextb;
+ }
+ } else {
+ rst.nextb = NUL;
+ rst.nextb_ic = NUL;
+ }
+ if (op != BRACE_SIMPLE) {
+ rst.minval = (op == STAR) ? 0 : 1;
+ rst.maxval = MAX_LIMIT;
+ } else {
+ rst.minval = bl_minval;
+ rst.maxval = bl_maxval;
+ }
+
+ // When maxval > minval, try matching as much as possible, up
+ // to maxval. When maxval < minval, try matching at least the
+ // minimal number (since the range is backwards, that's also
+ // maxval!).
+ rst.count = regrepeat(OPERAND(scan), rst.maxval);
+ if (got_int) {
+ status = RA_FAIL;
+ break;
+ }
+ if (rst.minval <= rst.maxval
+ ? rst.count >= rst.minval : rst.count >= rst.maxval) {
+ // It could match. Prepare for trying to match what
+ // follows. The code is below. Parameters are stored in
+ // a regstar_T on the regstack.
+ if ((int64_t)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ status = RA_FAIL;
+ } else {
+ ga_grow(&regstack, sizeof(regstar_T));
+ regstack.ga_len += (int)sizeof(regstar_T);
+ rp = regstack_push(rst.minval <= rst.maxval ? RS_STAR_LONG : RS_STAR_SHORT, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ *(((regstar_T *)rp) - 1) = rst;
+ status = RA_BREAK; // skip the restore bits
+ }
+ }
+ } else {
+ status = RA_NOMATCH;
+ }
+ }
+ break;
+
+ case NOMATCH:
+ case MATCH:
+ case SUBPAT:
+ rp = regstack_push(RS_NOMATCH, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)op;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ next = OPERAND(scan);
+ // We continue and handle the result when done.
+ }
+ break;
+
+ case BEHIND:
+ case NOBEHIND:
+ // Need a bit of room to store extra positions.
+ if ((int64_t)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ status = RA_FAIL;
+ } else {
+ ga_grow(&regstack, sizeof(regbehind_T));
+ regstack.ga_len += (int)sizeof(regbehind_T);
+ rp = regstack_push(RS_BEHIND1, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ // Need to save the subexpr to be able to restore them
+ // when there is a match but we don't use it.
+ save_subexpr(((regbehind_T *)rp) - 1);
+
+ rp->rs_no = (int16_t)op;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ // First try if what follows matches. If it does then we
+ // check the behind match by looping.
+ }
+ }
+ break;
+
+ case BHPOS:
+ if (REG_MULTI) {
+ if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line)
+ || behind_pos.rs_u.pos.lnum != rex.lnum) {
+ status = RA_NOMATCH;
+ }
+ } else if (behind_pos.rs_u.ptr != rex.input) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case NEWL:
+ if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) {
+ status = RA_NOMATCH;
+ } else if (rex.reg_line_lbr) {
+ ADVANCE_REGINPUT();
+ } else {
+ reg_nextline();
+ }
+ break;
+
+ case END:
+ status = RA_MATCH; // Success!
+ break;
+
+ default:
+ iemsg(_(e_re_corr));
+#ifdef REGEXP_DEBUG
+ printf("Illegal op code %d\n", op);
+#endif
+ status = RA_FAIL;
+ break;
+ }
+ }
+
+ // If we can't continue sequentially, break the inner loop.
+ if (status != RA_CONT) {
+ break;
+ }
+
+ // Continue in inner loop, advance to next item.
+ scan = next;
+ } // end of inner loop
+
+ // If there is something on the regstack execute the code for the state.
+ // If the state is popped then loop and use the older state.
+ while (!GA_EMPTY(&regstack) && status != RA_FAIL) {
+ rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
+ switch (rp->rs_state) {
+ case RS_NOPEN:
+ // Result is passed on as-is, simply pop the state.
+ regstack_pop(&scan);
+ break;
+
+ case RS_MOPEN:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no],
+ &rex.reg_startp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_ZOPEN:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &reg_startzpos[rp->rs_no],
+ &reg_startzp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_MCLOSE:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no],
+ &rex.reg_endp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_ZCLOSE:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &reg_endzpos[rp->rs_no],
+ &reg_endzp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_BRANCH:
+ if (status == RA_MATCH) {
+ // this branch matched, use it
+ regstack_pop(&scan);
+ } else {
+ if (status != RA_BREAK) {
+ // After a non-matching branch: try next one.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ scan = rp->rs_scan;
+ }
+ if (scan == NULL || OP(scan) != BRANCH) {
+ // no more branches, didn't find a match
+ status = RA_NOMATCH;
+ regstack_pop(&scan);
+ } else {
+ // Prepare to try a branch.
+ rp->rs_scan = regnext(scan);
+ reg_save(&rp->rs_un.regsave, &backpos);
+ scan = OPERAND(scan);
+ }
+ }
+ break;
+
+ case RS_BRCPLX_MORE:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ brace_count[rp->rs_no]--; // decrement match count
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_BRCPLX_LONG:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ // There was no match, but we did find enough matches.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ brace_count[rp->rs_no]--;
+ // continue with the items after "\{}"
+ status = RA_CONT;
+ }
+ regstack_pop(&scan);
+ if (status == RA_CONT) {
+ scan = regnext(scan);
+ }
+ break;
+
+ case RS_BRCPLX_SHORT:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ // There was no match, try to match one more item.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ }
+ regstack_pop(&scan);
+ if (status == RA_NOMATCH) {
+ scan = OPERAND(scan);
+ status = RA_CONT;
+ }
+ break;
+
+ case RS_NOMATCH:
+ // Pop the state. If the operand matches for NOMATCH or
+ // doesn't match for MATCH/SUBPAT, we fail. Otherwise backup,
+ // except for SUBPAT, and continue with the next item.
+ if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) {
+ status = RA_NOMATCH;
+ } else {
+ status = RA_CONT;
+ if (rp->rs_no != SUBPAT) { // zero-width
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ }
+ }
+ regstack_pop(&scan);
+ if (status == RA_CONT) {
+ scan = regnext(scan);
+ }
+ break;
+
+ case RS_BEHIND1:
+ if (status == RA_NOMATCH) {
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regbehind_T);
+ } else {
+ // The stuff after BEHIND/NOBEHIND matches. Now try if
+ // the behind part does (not) match before the current
+ // position in the input. This must be done at every
+ // position in the input and checking if the match ends at
+ // the current position.
+
+ // save the position after the found match for next
+ reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos);
+
+ // Start looking for a match with operand at the current
+ // position. Go back one character until we find the
+ // result, hitting the start of the line or the previous
+ // line (for multi-line matching).
+ // Set behind_pos to where the match should end, BHPOS
+ // will match it. Save the current value.
+ (((regbehind_T *)rp) - 1)->save_behind = behind_pos;
+ behind_pos = rp->rs_un.regsave;
+
+ rp->rs_state = RS_BEHIND2;
+
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ scan = OPERAND(rp->rs_scan) + 4;
+ }
+ break;
+
+ case RS_BEHIND2:
+ // Looping for BEHIND / NOBEHIND match.
+ if (status == RA_MATCH && reg_save_equal(&behind_pos)) {
+ // found a match that ends where "next" started
+ behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
+ if (rp->rs_no == BEHIND) {
+ reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
+ &backpos);
+ } else {
+ // But we didn't want a match. Need to restore the
+ // subexpr, because what follows matched, so they have
+ // been set.
+ status = RA_NOMATCH;
+ restore_subexpr(((regbehind_T *)rp) - 1);
+ }
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regbehind_T);
+ } else {
+ int64_t limit;
+
+ // No match or a match that doesn't end where we want it: Go
+ // back one character. May go to previous line once.
+ no = OK;
+ limit = OPERAND_MIN(rp->rs_scan);
+ if (REG_MULTI) {
+ if (limit > 0
+ && ((rp->rs_un.regsave.rs_u.pos.lnum
+ < behind_pos.rs_u.pos.lnum
+ ? (colnr_T)strlen((char *)rex.line)
+ : behind_pos.rs_u.pos.col)
+ - rp->rs_un.regsave.rs_u.pos.col >= limit)) {
+ no = FAIL;
+ } else if (rp->rs_un.regsave.rs_u.pos.col == 0) {
+ if (rp->rs_un.regsave.rs_u.pos.lnum
+ < behind_pos.rs_u.pos.lnum
+ || reg_getline(--rp->rs_un.regsave.rs_u.pos.lnum)
+ == NULL) {
+ no = FAIL;
+ } else {
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ rp->rs_un.regsave.rs_u.pos.col =
+ (colnr_T)strlen((char *)rex.line);
+ }
+ } else {
+ const uint8_t *const line =
+ (uint8_t *)reg_getline(rp->rs_un.regsave.rs_u.pos.lnum);
+
+ rp->rs_un.regsave.rs_u.pos.col -=
+ utf_head_off((char *)line,
+ (char *)line + rp->rs_un.regsave.rs_u.pos.col - 1)
+ + 1;
+ }
+ } else {
+ if (rp->rs_un.regsave.rs_u.ptr == rex.line) {
+ no = FAIL;
+ } else {
+ MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr);
+ if (limit > 0
+ && (behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > (ptrdiff_t)limit) {
+ no = FAIL;
+ }
+ }
+ }
+ if (no == OK) {
+ // Advanced, prepare for finding match again.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ scan = OPERAND(rp->rs_scan) + 4;
+ if (status == RA_MATCH) {
+ // We did match, so subexpr may have been changed,
+ // need to restore them for the next try.
+ status = RA_NOMATCH;
+ restore_subexpr(((regbehind_T *)rp) - 1);
+ }
+ } else {
+ // Can't advance. For NOBEHIND that's a match.
+ behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
+ if (rp->rs_no == NOBEHIND) {
+ reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
+ &backpos);
+ status = RA_MATCH;
+ } else {
+ // We do want a proper match. Need to restore the
+ // subexpr if we had a match, because they may have
+ // been set.
+ if (status == RA_MATCH) {
+ status = RA_NOMATCH;
+ restore_subexpr(((regbehind_T *)rp) - 1);
+ }
+ }
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regbehind_T);
+ }
+ }
+ break;
+
+ case RS_STAR_LONG:
+ case RS_STAR_SHORT: {
+ regstar_T *rst = ((regstar_T *)rp) - 1;
+
+ if (status == RA_MATCH) {
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regstar_T);
+ break;
+ }
+
+ // Tried once already, restore input pointers.
+ if (status != RA_BREAK) {
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ }
+
+ // Repeat until we found a position where it could match.
+ while (true) {
+ if (status != RA_BREAK) {
+ // Tried first position already, advance.
+ if (rp->rs_state == RS_STAR_LONG) {
+ // Trying for longest match, but couldn't or
+ // didn't match -- back up one char.
+ if (--rst->count < rst->minval) {
+ break;
+ }
+ if (rex.input == rex.line) {
+ // backup to last char of previous line
+ if (rex.lnum == 0) {
+ status = RA_NOMATCH;
+ break;
+ }
+ rex.lnum--;
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ // Just in case regrepeat() didn't count right.
+ if (rex.line == NULL) {
+ break;
+ }
+ rex.input = rex.line + strlen((char *)rex.line);
+ reg_breakcheck();
+ } else {
+ MB_PTR_BACK(rex.line, rex.input);
+ }
+ } else {
+ // Range is backwards, use shortest match first.
+ // Careful: maxval and minval are exchanged!
+ // Couldn't or didn't match: try advancing one
+ // char.
+ if (rst->count == rst->minval
+ || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) {
+ break;
+ }
+ rst->count++;
+ }
+ if (got_int) {
+ break;
+ }
+ } else {
+ status = RA_NOMATCH;
+ }
+
+ // If it could match, try it.
+ if (rst->nextb == NUL || *rex.input == rst->nextb
+ || *rex.input == rst->nextb_ic) {
+ reg_save(&rp->rs_un.regsave, &backpos);
+ scan = regnext(rp->rs_scan);
+ status = RA_CONT;
+ break;
+ }
+ }
+ if (status != RA_CONT) {
+ // Failed.
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regstar_T);
+ status = RA_NOMATCH;
+ }
+ }
+ break;
+ }
+
+ // If we want to continue the inner loop or didn't pop a state
+ // continue matching loop
+ if (status == RA_CONT || rp == (regitem_T *)
+ ((char *)regstack.ga_data + regstack.ga_len) - 1) {
+ break;
+ }
+ }
+
+ // May need to continue with the inner loop, starting at "scan".
+ if (status == RA_CONT) {
+ continue;
+ }
+
+ // If the regstack is empty or something failed we are done.
+ if (GA_EMPTY(&regstack) || status == RA_FAIL) {
+ if (scan == NULL) {
+ // We get here only if there's trouble -- normally "case END" is
+ // the terminating point.
+ iemsg(_(e_re_corr));
+#ifdef REGEXP_DEBUG
+ printf("Premature EOL\n");
+#endif
+ }
+ return status == RA_MATCH;
+ }
+ } // End of loop until the regstack is empty.
+
+ // NOTREACHED
+}
+
+/// Try match of "prog" with at rex.line["col"].
+///
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return 0 for failure, or number of lines contained in the match.
+static int regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
+{
+ rex.input = rex.line + col;
+ rex.need_clear_subexpr = true;
+ // Clear the external match subpointers if necessaey.
+ rex.need_clear_zsubexpr = (prog->reghasz == REX_SET);
+
+ if (regmatch(prog->program + 1, tm, timed_out) == 0) {
+ return 0;
+ }
+
+ cleanup_subexpr();
+ if (REG_MULTI) {
+ if (rex.reg_startpos[0].lnum < 0) {
+ rex.reg_startpos[0].lnum = 0;
+ rex.reg_startpos[0].col = col;
+ }
+ if (rex.reg_endpos[0].lnum < 0) {
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
+ } else {
+ // Use line number of "\ze".
+ rex.lnum = rex.reg_endpos[0].lnum;
+ }
+ } else {
+ if (rex.reg_startp[0] == NULL) {
+ rex.reg_startp[0] = rex.line + col;
+ }
+ if (rex.reg_endp[0] == NULL) {
+ rex.reg_endp[0] = rex.input;
+ }
+ }
+ // Package any found \z(...\) matches for export. Default is none.
+ unref_extmatch(re_extmatch_out);
+ re_extmatch_out = NULL;
+
+ if (prog->reghasz == REX_SET) {
+ int i;
+
+ cleanup_zsubexpr();
+ re_extmatch_out = make_extmatch();
+ for (i = 0; i < NSUBEXP; i++) {
+ if (REG_MULTI) {
+ // Only accept single line matches.
+ if (reg_startzpos[i].lnum >= 0
+ && reg_endzpos[i].lnum == reg_startzpos[i].lnum
+ && reg_endzpos[i].col >= reg_startzpos[i].col) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave(reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col,
+ (size_t)(reg_endzpos[i].col - reg_startzpos[i].col));
+ }
+ } else {
+ if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave((char *)reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i]));
+ }
+ }
+ }
+ }
+ return 1 + rex.lnum;
+}
+
+/// Match a regexp against a string ("line" points to the string) or multiple
+/// lines (if "line" is NULL, use reg_getline()).
+///
+/// @param startcol column to start looking for match
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return 0 for failure, or number of lines contained in the match.
+static int bt_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
+{
+ bt_regprog_T *prog;
+ uint8_t *s;
+ colnr_T col = startcol;
+ int retval = 0;
+
+ // Create "regstack" and "backpos" if they are not allocated yet.
+ // We allocate *_INITIAL amount of bytes first and then set the grow size
+ // to much bigger value to avoid many malloc calls in case of deep regular
+ // expressions.
+ if (regstack.ga_data == NULL) {
+ // Use an item size of 1 byte, since we push different things
+ // onto the regstack.
+ ga_init(&regstack, 1, REGSTACK_INITIAL);
+ ga_grow(&regstack, REGSTACK_INITIAL);
+ ga_set_growsize(&regstack, REGSTACK_INITIAL * 8);
+ }
+
+ if (backpos.ga_data == NULL) {
+ ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL);
+ ga_grow(&backpos, BACKPOS_INITIAL);
+ ga_set_growsize(&backpos, BACKPOS_INITIAL * 8);
+ }
+
+ if (REG_MULTI) {
+ prog = (bt_regprog_T *)rex.reg_mmatch->regprog;
+ line = (uint8_t *)reg_getline(0);
+ rex.reg_startpos = rex.reg_mmatch->startpos;
+ rex.reg_endpos = rex.reg_mmatch->endpos;
+ } else {
+ prog = (bt_regprog_T *)rex.reg_match->regprog;
+ rex.reg_startp = (uint8_t **)rex.reg_match->startp;
+ rex.reg_endp = (uint8_t **)rex.reg_match->endp;
+ }
+
+ // Be paranoid...
+ if (prog == NULL || line == NULL) {
+ iemsg(_(e_null));
+ goto theend;
+ }
+
+ // Check validity of program.
+ if (prog_magic_wrong()) {
+ goto theend;
+ }
+
+ // If the start column is past the maximum column: no need to try.
+ if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
+ goto theend;
+ }
+
+ // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
+ if (prog->regflags & RF_ICASE) {
+ rex.reg_ic = true;
+ } else if (prog->regflags & RF_NOICASE) {
+ rex.reg_ic = false;
+ }
+
+ // If pattern contains "\Z" overrule value of rex.reg_icombine
+ if (prog->regflags & RF_ICOMBINE) {
+ rex.reg_icombine = true;
+ }
+
+ // If there is a "must appear" string, look for it.
+ if (prog->regmust != NULL) {
+ int c = utf_ptr2char((char *)prog->regmust);
+ s = line + col;
+
+ // This is used very often, esp. for ":global". Use two versions of
+ // the loop to avoid overhead of conditions.
+ if (!rex.reg_ic) {
+ while ((s = (uint8_t *)vim_strchr((char *)s, c)) != NULL) {
+ if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
+ break; // Found it.
+ }
+ MB_PTR_ADV(s);
+ }
+ } else {
+ while ((s = (uint8_t *)cstrchr((char *)s, c)) != NULL) {
+ if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
+ break; // Found it.
+ }
+ MB_PTR_ADV(s);
+ }
+ }
+ if (s == NULL) { // Not present.
+ goto theend;
+ }
+ }
+
+ rex.line = line;
+ rex.lnum = 0;
+ reg_toolong = false;
+
+ // Simplest case: Anchored match need be tried only once.
+ if (prog->reganch) {
+ int c = utf_ptr2char((char *)rex.line + col);
+ if (prog->regstart == NUL
+ || prog->regstart == c
+ || (rex.reg_ic
+ && (utf_fold(prog->regstart) == utf_fold(c)
+ || (c < 255 && prog->regstart < 255
+ && mb_tolower(prog->regstart) == mb_tolower(c))))) {
+ retval = regtry(prog, col, tm, timed_out);
+ } else {
+ retval = 0;
+ }
+ } else {
+ int tm_count = 0;
+ // Messy cases: unanchored match.
+ while (!got_int) {
+ if (prog->regstart != NUL) {
+ // Skip until the char we know it must start with.
+ s = (uint8_t *)cstrchr((char *)rex.line + col, prog->regstart);
+ if (s == NULL) {
+ retval = 0;
+ break;
+ }
+ col = (int)(s - rex.line);
+ }
+
+ // Check for maximum column to try.
+ if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
+ retval = 0;
+ break;
+ }
+
+ retval = regtry(prog, col, tm, timed_out);
+ if (retval > 0) {
+ break;
+ }
+
+ // if not currently on the first line, get it again
+ if (rex.lnum != 0) {
+ rex.lnum = 0;
+ rex.line = (uint8_t *)reg_getline(0);
+ }
+ if (rex.line[col] == NUL) {
+ break;
+ }
+ col += utfc_ptr2len((char *)rex.line + col);
+ // Check for timeout once in a twenty times to avoid overhead.
+ if (tm != NULL && ++tm_count == 20) {
+ tm_count = 0;
+ if (profile_passed_limit(*tm)) {
+ if (timed_out != NULL) {
+ *timed_out = true;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+theend:
+ // Free "reg_tofree" when it's a bit big.
+ // Free regstack and backpos if they are bigger than their initial size.
+ if (reg_tofreelen > 400) {
+ XFREE_CLEAR(reg_tofree);
+ }
+ if (regstack.ga_maxlen > REGSTACK_INITIAL) {
+ ga_clear(&regstack);
+ }
+ if (backpos.ga_maxlen > BACKPOS_INITIAL) {
+ ga_clear(&backpos);
+ }
+
+ if (retval > 0) {
+ // Make sure the end is never before the start. Can happen when \zs
+ // and \ze are used.
+ if (REG_MULTI) {
+ const lpos_T *const start = &rex.reg_mmatch->startpos[0];
+ const lpos_T *const end = &rex.reg_mmatch->endpos[0];
+
+ if (end->lnum < start->lnum
+ || (end->lnum == start->lnum && end->col < start->col)) {
+ rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
+ }
+
+ // startpos[0] may be set by "\zs", also return the column where
+ // the whole pattern matched.
+ rex.reg_mmatch->rmm_matchcol = col;
+ } else {
+ if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
+ rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ }
+
+ // startpos[0] may be set by "\zs", also return the column where
+ // the whole pattern matched.
+ rex.reg_match->rm_matchcol = col;
+ }
+ }
+
+ return retval;
+}
+
+/// Match a regexp against a string.
+/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
+///
+/// @param line string to match against
+/// @param col column to start looking for match
+///
+/// @return 0 for failure, number of lines contained in the match otherwise.
+static int bt_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
+{
+ rex.reg_match = rmp;
+ rex.reg_mmatch = NULL;
+ rex.reg_maxline = 0;
+ rex.reg_line_lbr = line_lbr;
+ rex.reg_buf = curbuf;
+ rex.reg_win = NULL;
+ rex.reg_ic = rmp->rm_ic;
+ rex.reg_icombine = false;
+ rex.reg_nobreak = rmp->regprog->re_flags & RE_NOBREAK;
+ rex.reg_maxcol = 0;
+
+ int64_t r = bt_regexec_both(line, col, NULL, NULL);
+ assert(r <= INT_MAX);
+ return (int)r;
+}
+
+/// Matches a regexp against multiple lines.
+/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+///
+/// @param win Window in which to search or NULL
+/// @param buf Buffer in which to search
+/// @param lnum Number of line to start looking for match
+/// @param col Column to start looking for match
+/// @param tm Timeout limit or NULL
+///
+/// @return zero if there is no match and number of lines contained in the match
+/// otherwise.
+static int bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
+ proftime_T *tm, int *timed_out)
+{
+ init_regexec_multi(rmp, win, buf, lnum);
+ return bt_regexec_both(NULL, col, tm, timed_out);
+}
+
+// Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL.
+static int re_num_cmp(uint32_t val, const uint8_t *scan)
+{
+ uint32_t n = (uint32_t)OPERAND_MIN(scan);
+
+ if (OPERAND_CMP(scan) == '>') {
+ return val > n;
+ }
+ if (OPERAND_CMP(scan) == '<') {
+ return val < n;
+ }
+ return val == n;
+}
+
+#ifdef BT_REGEXP_DUMP
+
+// regdump - dump a regexp onto stdout in vaguely comprehensible form
+static void regdump(uint8_t *pattern, bt_regprog_T *r)
+{
+ uint8_t *s;
+ int op = EXACTLY; // Arbitrary non-END op.
+ uint8_t *next;
+ uint8_t *end = NULL;
+ FILE *f;
+
+# ifdef BT_REGEXP_LOG
+ f = fopen("bt_regexp_log.log", "a");
+# else
+ f = stdout;
+# endif
+ if (f == NULL) {
+ return;
+ }
+ fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n",
+ pattern);
+
+ s = r->program + 1;
+ // Loop until we find the END that isn't before a referred next (an END
+ // can also appear in a NOMATCH operand).
+ while (op != END || s <= end) {
+ op = OP(s);
+ fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); // Where, what.
+ next = regnext(s);
+ if (next == NULL) { // Next ptr.
+ fprintf(f, "(0)");
+ } else {
+ fprintf(f, "(%d)", (int)((s - r->program) + (next - s)));
+ }
+ if (end < next) {
+ end = next;
+ }
+ if (op == BRACE_LIMITS) {
+ // Two ints
+ fprintf(f, " minval %" PRId64 ", maxval %" PRId64,
+ (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s));
+ s += 8;
+ } else if (op == BEHIND || op == NOBEHIND) {
+ // one int
+ fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
+ s += 4;
+ } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) {
+ // one int plus comparator
+ fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
+ s += 5;
+ }
+ s += 3;
+ if (op == ANYOF || op == ANYOF + ADD_NL
+ || op == ANYBUT || op == ANYBUT + ADD_NL
+ || op == EXACTLY) {
+ // Literal string, where present.
+ fprintf(f, "\nxxxxxxxxx\n");
+ while (*s != NUL) {
+ fprintf(f, "%c", *s++);
+ }
+ fprintf(f, "\nxxxxxxxxx\n");
+ s++;
+ }
+ fprintf(f, "\r\n");
+ }
+
+ // Header fields of interest.
+ if (r->regstart != NUL) {
+ fprintf(f, "start `%s' 0x%x; ", r->regstart < 256
+ ? (char *)transchar(r->regstart)
+ : "multibyte", r->regstart);
+ }
+ if (r->reganch) {
+ fprintf(f, "anchored; ");
+ }
+ if (r->regmust != NULL) {
+ fprintf(f, "must have \"%s\"", r->regmust);
+ }
+ fprintf(f, "\r\n");
+
+# ifdef BT_REGEXP_LOG
+ fclose(f);
+# endif
+}
+#endif // BT_REGEXP_DUMP
+
+#ifdef REGEXP_DEBUG
+
+// regprop - printable representation of opcode
+static uint8_t *regprop(uint8_t *op)
+{
+ char *p;
+ static char buf[50];
+
+ STRCPY(buf, ":");
+
+ switch ((int)OP(op)) {
+ case BOL:
+ p = "BOL";
+ break;
+ case EOL:
+ p = "EOL";
+ break;
+ case RE_BOF:
+ p = "BOF";
+ break;
+ case RE_EOF:
+ p = "EOF";
+ break;
+ case CURSOR:
+ p = "CURSOR";
+ break;
+ case RE_VISUAL:
+ p = "RE_VISUAL";
+ break;
+ case RE_LNUM:
+ p = "RE_LNUM";
+ break;
+ case RE_MARK:
+ p = "RE_MARK";
+ break;
+ case RE_COL:
+ p = "RE_COL";
+ break;
+ case RE_VCOL:
+ p = "RE_VCOL";
+ break;
+ case BOW:
+ p = "BOW";
+ break;
+ case EOW:
+ p = "EOW";
+ break;
+ case ANY:
+ p = "ANY";
+ break;
+ case ANY + ADD_NL:
+ p = "ANY+NL";
+ break;
+ case ANYOF:
+ p = "ANYOF";
+ break;
+ case ANYOF + ADD_NL:
+ p = "ANYOF+NL";
+ break;
+ case ANYBUT:
+ p = "ANYBUT";
+ break;
+ case ANYBUT + ADD_NL:
+ p = "ANYBUT+NL";
+ break;
+ case IDENT:
+ p = "IDENT";
+ break;
+ case IDENT + ADD_NL:
+ p = "IDENT+NL";
+ break;
+ case SIDENT:
+ p = "SIDENT";
+ break;
+ case SIDENT + ADD_NL:
+ p = "SIDENT+NL";
+ break;
+ case KWORD:
+ p = "KWORD";
+ break;
+ case KWORD + ADD_NL:
+ p = "KWORD+NL";
+ break;
+ case SKWORD:
+ p = "SKWORD";
+ break;
+ case SKWORD + ADD_NL:
+ p = "SKWORD+NL";
+ break;
+ case FNAME:
+ p = "FNAME";
+ break;
+ case FNAME + ADD_NL:
+ p = "FNAME+NL";
+ break;
+ case SFNAME:
+ p = "SFNAME";
+ break;
+ case SFNAME + ADD_NL:
+ p = "SFNAME+NL";
+ break;
+ case PRINT:
+ p = "PRINT";
+ break;
+ case PRINT + ADD_NL:
+ p = "PRINT+NL";
+ break;
+ case SPRINT:
+ p = "SPRINT";
+ break;
+ case SPRINT + ADD_NL:
+ p = "SPRINT+NL";
+ break;
+ case WHITE:
+ p = "WHITE";
+ break;
+ case WHITE + ADD_NL:
+ p = "WHITE+NL";
+ break;
+ case NWHITE:
+ p = "NWHITE";
+ break;
+ case NWHITE + ADD_NL:
+ p = "NWHITE+NL";
+ break;
+ case DIGIT:
+ p = "DIGIT";
+ break;
+ case DIGIT + ADD_NL:
+ p = "DIGIT+NL";
+ break;
+ case NDIGIT:
+ p = "NDIGIT";
+ break;
+ case NDIGIT + ADD_NL:
+ p = "NDIGIT+NL";
+ break;
+ case HEX:
+ p = "HEX";
+ break;
+ case HEX + ADD_NL:
+ p = "HEX+NL";
+ break;
+ case NHEX:
+ p = "NHEX";
+ break;
+ case NHEX + ADD_NL:
+ p = "NHEX+NL";
+ break;
+ case OCTAL:
+ p = "OCTAL";
+ break;
+ case OCTAL + ADD_NL:
+ p = "OCTAL+NL";
+ break;
+ case NOCTAL:
+ p = "NOCTAL";
+ break;
+ case NOCTAL + ADD_NL:
+ p = "NOCTAL+NL";
+ break;
+ case WORD:
+ p = "WORD";
+ break;
+ case WORD + ADD_NL:
+ p = "WORD+NL";
+ break;
+ case NWORD:
+ p = "NWORD";
+ break;
+ case NWORD + ADD_NL:
+ p = "NWORD+NL";
+ break;
+ case HEAD:
+ p = "HEAD";
+ break;
+ case HEAD + ADD_NL:
+ p = "HEAD+NL";
+ break;
+ case NHEAD:
+ p = "NHEAD";
+ break;
+ case NHEAD + ADD_NL:
+ p = "NHEAD+NL";
+ break;
+ case ALPHA:
+ p = "ALPHA";
+ break;
+ case ALPHA + ADD_NL:
+ p = "ALPHA+NL";
+ break;
+ case NALPHA:
+ p = "NALPHA";
+ break;
+ case NALPHA + ADD_NL:
+ p = "NALPHA+NL";
+ break;
+ case LOWER:
+ p = "LOWER";
+ break;
+ case LOWER + ADD_NL:
+ p = "LOWER+NL";
+ break;
+ case NLOWER:
+ p = "NLOWER";
+ break;
+ case NLOWER + ADD_NL:
+ p = "NLOWER+NL";
+ break;
+ case UPPER:
+ p = "UPPER";
+ break;
+ case UPPER + ADD_NL:
+ p = "UPPER+NL";
+ break;
+ case NUPPER:
+ p = "NUPPER";
+ break;
+ case NUPPER + ADD_NL:
+ p = "NUPPER+NL";
+ break;
+ case BRANCH:
+ p = "BRANCH";
+ break;
+ case EXACTLY:
+ p = "EXACTLY";
+ break;
+ case NOTHING:
+ p = "NOTHING";
+ break;
+ case BACK:
+ p = "BACK";
+ break;
+ case END:
+ p = "END";
+ break;
+ case MOPEN + 0:
+ p = "MATCH START";
+ break;
+ case MOPEN + 1:
+ case MOPEN + 2:
+ case MOPEN + 3:
+ case MOPEN + 4:
+ case MOPEN + 5:
+ case MOPEN + 6:
+ case MOPEN + 7:
+ case MOPEN + 8:
+ case MOPEN + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MOPEN%d", OP(op) - MOPEN);
+ p = NULL;
+ break;
+ case MCLOSE + 0:
+ p = "MATCH END";
+ break;
+ case MCLOSE + 1:
+ case MCLOSE + 2:
+ case MCLOSE + 3:
+ case MCLOSE + 4:
+ case MCLOSE + 5:
+ case MCLOSE + 6:
+ case MCLOSE + 7:
+ case MCLOSE + 8:
+ case MCLOSE + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MCLOSE%d", OP(op) - MCLOSE);
+ p = NULL;
+ break;
+ case BACKREF + 1:
+ case BACKREF + 2:
+ case BACKREF + 3:
+ case BACKREF + 4:
+ case BACKREF + 5:
+ case BACKREF + 6:
+ case BACKREF + 7:
+ case BACKREF + 8:
+ case BACKREF + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BACKREF%d", OP(op) - BACKREF);
+ p = NULL;
+ break;
+ case NOPEN:
+ p = "NOPEN";
+ break;
+ case NCLOSE:
+ p = "NCLOSE";
+ break;
+ case ZOPEN + 1:
+ case ZOPEN + 2:
+ case ZOPEN + 3:
+ case ZOPEN + 4:
+ case ZOPEN + 5:
+ case ZOPEN + 6:
+ case ZOPEN + 7:
+ case ZOPEN + 8:
+ case ZOPEN + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZOPEN%d", OP(op) - ZOPEN);
+ p = NULL;
+ break;
+ case ZCLOSE + 1:
+ case ZCLOSE + 2:
+ case ZCLOSE + 3:
+ case ZCLOSE + 4:
+ case ZCLOSE + 5:
+ case ZCLOSE + 6:
+ case ZCLOSE + 7:
+ case ZCLOSE + 8:
+ case ZCLOSE + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZCLOSE%d", OP(op) - ZCLOSE);
+ p = NULL;
+ break;
+ case ZREF + 1:
+ case ZREF + 2:
+ case ZREF + 3:
+ case ZREF + 4:
+ case ZREF + 5:
+ case ZREF + 6:
+ case ZREF + 7:
+ case ZREF + 8:
+ case ZREF + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZREF%d", OP(op) - ZREF);
+ p = NULL;
+ break;
+ case STAR:
+ p = "STAR";
+ break;
+ case PLUS:
+ p = "PLUS";
+ break;
+ case NOMATCH:
+ p = "NOMATCH";
+ break;
+ case MATCH:
+ p = "MATCH";
+ break;
+ case BEHIND:
+ p = "BEHIND";
+ break;
+ case NOBEHIND:
+ p = "NOBEHIND";
+ break;
+ case SUBPAT:
+ p = "SUBPAT";
+ break;
+ case BRACE_LIMITS:
+ p = "BRACE_LIMITS";
+ break;
+ case BRACE_SIMPLE:
+ p = "BRACE_SIMPLE";
+ break;
+ case BRACE_COMPLEX + 0:
+ case BRACE_COMPLEX + 1:
+ case BRACE_COMPLEX + 2:
+ case BRACE_COMPLEX + 3:
+ case BRACE_COMPLEX + 4:
+ case BRACE_COMPLEX + 5:
+ case BRACE_COMPLEX + 6:
+ case BRACE_COMPLEX + 7:
+ case BRACE_COMPLEX + 8:
+ case BRACE_COMPLEX + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BRACE_COMPLEX%d",
+ OP(op) - BRACE_COMPLEX);
+ p = NULL;
+ break;
+ case MULTIBYTECODE:
+ p = "MULTIBYTECODE";
+ break;
+ case NEWL:
+ p = "NEWL";
+ break;
+ default:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "corrupt %d", OP(op));
+ p = NULL;
+ break;
+ }
+ if (p != NULL) {
+ STRCAT(buf, p);
+ }
+ return (uint8_t *)buf;
+}
+#endif // REGEXP_DEBUG
+
+// }}}1
+
+// regexp_nfa.c {{{1
+// NFA regular expression implementation.
+
+// Logging of NFA engine.
+//
+// The NFA engine can write four log files:
+// - Error log: Contains NFA engine's fatal errors.
+// - Dump log: Contains compiled NFA state machine's information.
+// - Run log: Contains information of matching procedure.
+// - Debug log: Contains detailed information of matching procedure. Can be
+// disabled by undefining NFA_REGEXP_DEBUG_LOG.
+// The first one can also be used without debug mode.
+// The last three are enabled when compiled as debug mode and individually
+// disabled by commenting them out.
+// The log files can get quite big!
+// To disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in
+// regexp.c
+#ifdef REGEXP_DEBUG
+# define NFA_REGEXP_ERROR_LOG "nfa_regexp_error.log"
+# define NFA_REGEXP_DUMP_LOG "nfa_regexp_dump.log"
+# define NFA_REGEXP_RUN_LOG "nfa_regexp_run.log"
+# define NFA_REGEXP_DEBUG_LOG "nfa_regexp_debug.log"
+#endif
+
+// Added to NFA_ANY - NFA_NUPPER_IC to include a NL.
+#define NFA_ADD_NL 31
+
+enum {
+ NFA_SPLIT = -1024,
+ NFA_MATCH,
+ NFA_EMPTY, // matches 0-length
+
+ NFA_START_COLL, // [abc] start
+ NFA_END_COLL, // [abc] end
+ NFA_START_NEG_COLL, // [^abc] start
+ NFA_END_NEG_COLL, // [^abc] end (postfix only)
+ NFA_RANGE, // range of the two previous items
+ // (postfix only)
+ NFA_RANGE_MIN, // low end of a range
+ NFA_RANGE_MAX, // high end of a range
+
+ NFA_CONCAT, // concatenate two previous items (postfix
+ // only)
+ NFA_OR, // \| (postfix only)
+ NFA_STAR, // greedy * (postfix only)
+ NFA_STAR_NONGREEDY, // non-greedy * (postfix only)
+ NFA_QUEST, // greedy \? (postfix only)
+ NFA_QUEST_NONGREEDY, // non-greedy \? (postfix only)
+
+ NFA_BOL, // ^ Begin line
+ NFA_EOL, // $ End line
+ NFA_BOW, // \< Begin word
+ NFA_EOW, // \> End word
+ NFA_BOF, // \%^ Begin file
+ NFA_EOF, // \%$ End file
+ NFA_NEWL,
+ NFA_ZSTART, // Used for \zs
+ NFA_ZEND, // Used for \ze
+ NFA_NOPEN, // Start of subexpression marked with \%(
+ NFA_NCLOSE, // End of subexpr. marked with \%( ... \)
+ NFA_START_INVISIBLE,
+ NFA_START_INVISIBLE_FIRST,
+ NFA_START_INVISIBLE_NEG,
+ NFA_START_INVISIBLE_NEG_FIRST,
+ NFA_START_INVISIBLE_BEFORE,
+ NFA_START_INVISIBLE_BEFORE_FIRST,
+ NFA_START_INVISIBLE_BEFORE_NEG,
+ NFA_START_INVISIBLE_BEFORE_NEG_FIRST,
+ NFA_START_PATTERN,
+ NFA_END_INVISIBLE,
+ NFA_END_INVISIBLE_NEG,
+ NFA_END_PATTERN,
+ NFA_COMPOSING, // Next nodes in NFA are part of the
+ // composing multibyte char
+ NFA_END_COMPOSING, // End of a composing char in the NFA
+ NFA_ANY_COMPOSING, // \%C: Any composing characters.
+ NFA_OPT_CHARS, // \%[abc]
+
+ // The following are used only in the postfix form, not in the NFA
+ NFA_PREV_ATOM_NO_WIDTH, // Used for \@=
+ NFA_PREV_ATOM_NO_WIDTH_NEG, // Used for \@!
+ NFA_PREV_ATOM_JUST_BEFORE, // Used for \@<=
+ NFA_PREV_ATOM_JUST_BEFORE_NEG, // Used for \@<!
+ NFA_PREV_ATOM_LIKE_PATTERN, // Used for \@>
+
+ NFA_BACKREF1, // \1
+ NFA_BACKREF2, // \2
+ NFA_BACKREF3, // \3
+ NFA_BACKREF4, // \4
+ NFA_BACKREF5, // \5
+ NFA_BACKREF6, // \6
+ NFA_BACKREF7, // \7
+ NFA_BACKREF8, // \8
+ NFA_BACKREF9, // \9
+ NFA_ZREF1, // \z1
+ NFA_ZREF2, // \z2
+ NFA_ZREF3, // \z3
+ NFA_ZREF4, // \z4
+ NFA_ZREF5, // \z5
+ NFA_ZREF6, // \z6
+ NFA_ZREF7, // \z7
+ NFA_ZREF8, // \z8
+ NFA_ZREF9, // \z9
+ NFA_SKIP, // Skip characters
+
+ NFA_MOPEN,
+ NFA_MOPEN1,
+ NFA_MOPEN2,
+ NFA_MOPEN3,
+ NFA_MOPEN4,
+ NFA_MOPEN5,
+ NFA_MOPEN6,
+ NFA_MOPEN7,
+ NFA_MOPEN8,
+ NFA_MOPEN9,
+
+ NFA_MCLOSE,
+ NFA_MCLOSE1,
+ NFA_MCLOSE2,
+ NFA_MCLOSE3,
+ NFA_MCLOSE4,
+ NFA_MCLOSE5,
+ NFA_MCLOSE6,
+ NFA_MCLOSE7,
+ NFA_MCLOSE8,
+ NFA_MCLOSE9,
+
+ NFA_ZOPEN,
+ NFA_ZOPEN1,
+ NFA_ZOPEN2,
+ NFA_ZOPEN3,
+ NFA_ZOPEN4,
+ NFA_ZOPEN5,
+ NFA_ZOPEN6,
+ NFA_ZOPEN7,
+ NFA_ZOPEN8,
+ NFA_ZOPEN9,
+
+ NFA_ZCLOSE,
+ NFA_ZCLOSE1,
+ NFA_ZCLOSE2,
+ NFA_ZCLOSE3,
+ NFA_ZCLOSE4,
+ NFA_ZCLOSE5,
+ NFA_ZCLOSE6,
+ NFA_ZCLOSE7,
+ NFA_ZCLOSE8,
+ NFA_ZCLOSE9,
+
+ // NFA_FIRST_NL
+ NFA_ANY, // Match any one character.
+ NFA_IDENT, // Match identifier char
+ NFA_SIDENT, // Match identifier char but no digit
+ NFA_KWORD, // Match keyword char
+ NFA_SKWORD, // Match word char but no digit
+ NFA_FNAME, // Match file name char
+ NFA_SFNAME, // Match file name char but no digit
+ NFA_PRINT, // Match printable char
+ NFA_SPRINT, // Match printable char but no digit
+ NFA_WHITE, // Match whitespace char
+ NFA_NWHITE, // Match non-whitespace char
+ NFA_DIGIT, // Match digit char
+ NFA_NDIGIT, // Match non-digit char
+ NFA_HEX, // Match hex char
+ NFA_NHEX, // Match non-hex char
+ NFA_OCTAL, // Match octal char
+ NFA_NOCTAL, // Match non-octal char
+ NFA_WORD, // Match word char
+ NFA_NWORD, // Match non-word char
+ NFA_HEAD, // Match head char
+ NFA_NHEAD, // Match non-head char
+ NFA_ALPHA, // Match alpha char
+ NFA_NALPHA, // Match non-alpha char
+ NFA_LOWER, // Match lowercase char
+ NFA_NLOWER, // Match non-lowercase char
+ NFA_UPPER, // Match uppercase char
+ NFA_NUPPER, // Match non-uppercase char
+ NFA_LOWER_IC, // Match [a-z]
+ NFA_NLOWER_IC, // Match [^a-z]
+ NFA_UPPER_IC, // Match [A-Z]
+ NFA_NUPPER_IC, // Match [^A-Z]
+
+ NFA_FIRST_NL = NFA_ANY + NFA_ADD_NL,
+ NFA_LAST_NL = NFA_NUPPER_IC + NFA_ADD_NL,
+
+ NFA_CURSOR, // Match cursor pos
+ NFA_LNUM, // Match line number
+ NFA_LNUM_GT, // Match > line number
+ NFA_LNUM_LT, // Match < line number
+ NFA_COL, // Match cursor column
+ NFA_COL_GT, // Match > cursor column
+ NFA_COL_LT, // Match < cursor column
+ NFA_VCOL, // Match cursor virtual column
+ NFA_VCOL_GT, // Match > cursor virtual column
+ NFA_VCOL_LT, // Match < cursor virtual column
+ NFA_MARK, // Match mark
+ NFA_MARK_GT, // Match > mark
+ NFA_MARK_LT, // Match < mark
+ NFA_VISUAL, // Match Visual area
+
+ // Character classes [:alnum:] etc
+ NFA_CLASS_ALNUM,
+ NFA_CLASS_ALPHA,
+ NFA_CLASS_BLANK,
+ NFA_CLASS_CNTRL,
+ NFA_CLASS_DIGIT,
+ NFA_CLASS_GRAPH,
+ NFA_CLASS_LOWER,
+ NFA_CLASS_PRINT,
+ NFA_CLASS_PUNCT,
+ NFA_CLASS_SPACE,
+ NFA_CLASS_UPPER,
+ NFA_CLASS_XDIGIT,
+ NFA_CLASS_TAB,
+ NFA_CLASS_RETURN,
+ NFA_CLASS_BACKSPACE,
+ NFA_CLASS_ESCAPE,
+ NFA_CLASS_IDENT,
+ NFA_CLASS_KEYWORD,
+ NFA_CLASS_FNAME,
+};
+
+// Keep in sync with classchars.
+static int nfa_classcodes[] = {
+ NFA_ANY, NFA_IDENT, NFA_SIDENT, NFA_KWORD, NFA_SKWORD,
+ NFA_FNAME, NFA_SFNAME, NFA_PRINT, NFA_SPRINT,
+ NFA_WHITE, NFA_NWHITE, NFA_DIGIT, NFA_NDIGIT,
+ NFA_HEX, NFA_NHEX, NFA_OCTAL, NFA_NOCTAL,
+ NFA_WORD, NFA_NWORD, NFA_HEAD, NFA_NHEAD,
+ NFA_ALPHA, NFA_NALPHA, NFA_LOWER, NFA_NLOWER,
+ NFA_UPPER, NFA_NUPPER
+};
+
+static const char e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely");
+static const char e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c");
+static const char e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64);
+static const char e_value_too_large[] = N_("E951: \\% value too large");
+
+// Variables only used in nfa_regcomp() and descendants.
+static int nfa_re_flags; ///< re_flags passed to nfa_regcomp().
+static int *post_start; ///< holds the postfix form of r.e.
+static int *post_end;
+static int *post_ptr;
+
+// Set when the pattern should use the NFA engine.
+// E.g. [[:upper:]] only allows 8bit characters for BT engine,
+// while NFA engine handles multibyte characters correctly.
+static bool wants_nfa;
+
+static int nstate; ///< Number of states in the NFA. Also used when executing.
+static int istate; ///< Index in the state vector, used in alloc_state()
+
+// If not NULL match must end at this position
+static save_se_T *nfa_endp = NULL;
+
+// 0 for first call to nfa_regmatch(), 1 for recursive call.
+static int nfa_ll_index = 0;
+
+// Helper functions used when doing re2post() ... regatom() parsing
+#define EMIT(c) \
+ do { \
+ if (post_ptr >= post_end) { \
+ realloc_post_list(); \
+ } \
+ *post_ptr++ = c; \
+ } while (0)
+
+/// Initialize internal variables before NFA compilation.
+///
+/// @param re_flags @see vim_regcomp()
+static void nfa_regcomp_start(uint8_t *expr, int re_flags)
+{
+ size_t postfix_size;
+ size_t nstate_max;
+
+ nstate = 0;
+ istate = 0;
+ // A reasonable estimation for maximum size
+ nstate_max = (strlen((char *)expr) + 1) * 25;
+
+ // Some items blow up in size, such as [A-z]. Add more space for that.
+ // When it is still not enough realloc_post_list() will be used.
+ nstate_max += 1000;
+
+ // Size for postfix representation of expr.
+ postfix_size = sizeof(int) * nstate_max;
+
+ post_start = (int *)xmalloc(postfix_size);
+ post_ptr = post_start;
+ post_end = post_start + nstate_max;
+ wants_nfa = false;
+ rex.nfa_has_zend = false;
+ rex.nfa_has_backref = false;
+
+ // shared with BT engine
+ regcomp_start(expr, re_flags);
+}
+
+// Figure out if the NFA state list starts with an anchor, must match at start
+// of the line.
+static int nfa_get_reganch(nfa_state_T *start, int depth)
+{
+ nfa_state_T *p = start;
+
+ if (depth > 4) {
+ return 0;
+ }
+
+ while (p != NULL) {
+ switch (p->c) {
+ case NFA_BOL:
+ case NFA_BOF:
+ return 1; // yes!
+
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ case NFA_CURSOR:
+ case NFA_VISUAL:
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ p = p->out;
+ break;
+
+ case NFA_SPLIT:
+ return nfa_get_reganch(p->out, depth + 1)
+ && nfa_get_reganch(p->out1, depth + 1);
+
+ default:
+ return 0; // noooo
+ }
+ }
+ return 0;
+}
+
+// Figure out if the NFA state list starts with a character which must match
+// at start of the match.
+static int nfa_get_regstart(nfa_state_T *start, int depth)
+{
+ nfa_state_T *p = start;
+
+ if (depth > 4) {
+ return 0;
+ }
+
+ while (p != NULL) {
+ switch (p->c) {
+ // all kinds of zero-width matches
+ case NFA_BOL:
+ case NFA_BOF:
+ case NFA_BOW:
+ case NFA_EOW:
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ case NFA_CURSOR:
+ case NFA_VISUAL:
+ case NFA_LNUM:
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_COL:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_VCOL:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_MARK:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT:
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ p = p->out;
+ break;
+
+ case NFA_SPLIT: {
+ int c1 = nfa_get_regstart(p->out, depth + 1);
+ int c2 = nfa_get_regstart(p->out1, depth + 1);
+
+ if (c1 == c2) {
+ return c1; // yes!
+ }
+ return 0;
+ }
+
+ default:
+ if (p->c > 0) {
+ return p->c; // yes!
+ }
+ return 0;
+ }
+ }
+ return 0;
+}
+
+// Figure out if the NFA state list contains just literal text and nothing
+// else. If so return a string in allocated memory with what must match after
+// regstart. Otherwise return NULL.
+static uint8_t *nfa_get_match_text(nfa_state_T *start)
+{
+ nfa_state_T *p = start;
+ int len = 0;
+ uint8_t *ret;
+ uint8_t *s;
+
+ if (p->c != NFA_MOPEN) {
+ return NULL; // just in case
+ }
+ p = p->out;
+ while (p->c > 0) {
+ len += utf_char2len(p->c);
+ p = p->out;
+ }
+ if (p->c != NFA_MCLOSE || p->out->c != NFA_MATCH) {
+ return NULL;
+ }
+
+ ret = xmalloc((size_t)len);
+ p = start->out->out; // skip first char, it goes into regstart
+ s = ret;
+ while (p->c > 0) {
+ s += utf_char2bytes(p->c, (char *)s);
+ p = p->out;
+ }
+ *s = NUL;
+
+ return ret;
+}
+
+// Allocate more space for post_start. Called when
+// running above the estimated number of states.
+static void realloc_post_list(void)
+{
+ // For weird patterns the number of states can be very high. Increasing by
+ // 50% seems a reasonable compromise between memory use and speed.
+ const size_t new_max = (size_t)(post_end - post_start) * 3 / 2;
+ int *new_start = xrealloc(post_start, new_max * sizeof(int));
+ post_ptr = new_start + (post_ptr - post_start);
+ post_end = new_start + new_max;
+ post_start = new_start;
+}
+
+// Search between "start" and "end" and try to recognize a
+// character class in expanded form. For example [0-9].
+// On success, return the id the character class to be emitted.
+// On failure, return 0 (=FAIL)
+// Start points to the first char of the range, while end should point
+// to the closing brace.
+// Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may
+// need to be interpreted as [a-zA-Z].
+static int nfa_recognize_char_class(uint8_t *start, const uint8_t *end, int extra_newl)
+{
+#define CLASS_not 0x80
+#define CLASS_af 0x40
+#define CLASS_AF 0x20
+#define CLASS_az 0x10
+#define CLASS_AZ 0x08
+#define CLASS_o7 0x04
+#define CLASS_o9 0x02
+#define CLASS_underscore 0x01
+
+ uint8_t *p;
+ int config = 0;
+
+ bool newl = extra_newl == true;
+
+ if (*end != ']') {
+ return FAIL;
+ }
+ p = start;
+ if (*p == '^') {
+ config |= CLASS_not;
+ p++;
+ }
+
+ while (p < end) {
+ if (p + 2 < end && *(p + 1) == '-') {
+ switch (*p) {
+ case '0':
+ if (*(p + 2) == '9') {
+ config |= CLASS_o9;
+ break;
+ } else if (*(p + 2) == '7') {
+ config |= CLASS_o7;
+ break;
+ }
+ return FAIL;
+ case 'a':
+ if (*(p + 2) == 'z') {
+ config |= CLASS_az;
+ break;
+ } else if (*(p + 2) == 'f') {
+ config |= CLASS_af;
+ break;
+ }
+ return FAIL;
+ case 'A':
+ if (*(p + 2) == 'Z') {
+ config |= CLASS_AZ;
+ break;
+ } else if (*(p + 2) == 'F') {
+ config |= CLASS_AF;
+ break;
+ }
+ return FAIL;
+ default:
+ return FAIL;
+ }
+ p += 3;
+ } else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') {
+ newl = true;
+ p += 2;
+ } else if (*p == '_') {
+ config |= CLASS_underscore;
+ p++;
+ } else if (*p == '\n') {
+ newl = true;
+ p++;
+ } else {
+ return FAIL;
+ }
+ } // while (p < end)
+
+ if (p != end) {
+ return FAIL;
+ }
+
+ if (newl == true) {
+ extra_newl = NFA_ADD_NL;
+ }
+
+ switch (config) {
+ case CLASS_o9:
+ return extra_newl + NFA_DIGIT;
+ case CLASS_not | CLASS_o9:
+ return extra_newl + NFA_NDIGIT;
+ case CLASS_af | CLASS_AF | CLASS_o9:
+ return extra_newl + NFA_HEX;
+ case CLASS_not | CLASS_af | CLASS_AF | CLASS_o9:
+ return extra_newl + NFA_NHEX;
+ case CLASS_o7:
+ return extra_newl + NFA_OCTAL;
+ case CLASS_not | CLASS_o7:
+ return extra_newl + NFA_NOCTAL;
+ case CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
+ return extra_newl + NFA_WORD;
+ case CLASS_not | CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
+ return extra_newl + NFA_NWORD;
+ case CLASS_az | CLASS_AZ | CLASS_underscore:
+ return extra_newl + NFA_HEAD;
+ case CLASS_not | CLASS_az | CLASS_AZ | CLASS_underscore:
+ return extra_newl + NFA_NHEAD;
+ case CLASS_az | CLASS_AZ:
+ return extra_newl + NFA_ALPHA;
+ case CLASS_not | CLASS_az | CLASS_AZ:
+ return extra_newl + NFA_NALPHA;
+ case CLASS_az:
+ return extra_newl + NFA_LOWER_IC;
+ case CLASS_not | CLASS_az:
+ return extra_newl + NFA_NLOWER_IC;
+ case CLASS_AZ:
+ return extra_newl + NFA_UPPER_IC;
+ case CLASS_not | CLASS_AZ:
+ return extra_newl + NFA_NUPPER_IC;
+ }
+ return FAIL;
+}
+
+// Produce the bytes for equivalence class "c".
+// Currently only handles latin1, latin9 and utf-8.
+// Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is
+// equivalent to 'a OR b OR c'
+//
+// NOTE! When changing this function, also update reg_equi_class()
+static void nfa_emit_equi_class(int c)
+{
+#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT);
+
+ {
+#define A_grave 0xc0
+#define A_acute 0xc1
+#define A_circumflex 0xc2
+#define A_virguilla 0xc3
+#define A_diaeresis 0xc4
+#define A_ring 0xc5
+#define C_cedilla 0xc7
+#define E_grave 0xc8
+#define E_acute 0xc9
+#define E_circumflex 0xca
+#define E_diaeresis 0xcb
+#define I_grave 0xcc
+#define I_acute 0xcd
+#define I_circumflex 0xce
+#define I_diaeresis 0xcf
+#define N_virguilla 0xd1
+#define O_grave 0xd2
+#define O_acute 0xd3
+#define O_circumflex 0xd4
+#define O_virguilla 0xd5
+#define O_diaeresis 0xd6
+#define O_slash 0xd8
+#define U_grave 0xd9
+#define U_acute 0xda
+#define U_circumflex 0xdb
+#define U_diaeresis 0xdc
+#define Y_acute 0xdd
+#define a_grave 0xe0
+#define a_acute 0xe1
+#define a_circumflex 0xe2
+#define a_virguilla 0xe3
+#define a_diaeresis 0xe4
+#define a_ring 0xe5
+#define c_cedilla 0xe7
+#define e_grave 0xe8
+#define e_acute 0xe9
+#define e_circumflex 0xea
+#define e_diaeresis 0xeb
+#define i_grave 0xec
+#define i_acute 0xed
+#define i_circumflex 0xee
+#define i_diaeresis 0xef
+#define n_virguilla 0xf1
+#define o_grave 0xf2
+#define o_acute 0xf3
+#define o_circumflex 0xf4
+#define o_virguilla 0xf5
+#define o_diaeresis 0xf6
+#define o_slash 0xf8
+#define u_grave 0xf9
+#define u_acute 0xfa
+#define u_circumflex 0xfb
+#define u_diaeresis 0xfc
+#define y_acute 0xfd
+#define y_diaeresis 0xff
+ switch (c) {
+ case 'A':
+ case A_grave:
+ case A_acute:
+ case A_circumflex:
+ case A_virguilla:
+ case A_diaeresis:
+ case A_ring:
+ case 0x100:
+ case 0x102:
+ case 0x104:
+ case 0x1cd:
+ case 0x1de:
+ case 0x1e0:
+ case 0x1fa:
+ case 0x200:
+ case 0x202:
+ case 0x226:
+ case 0x23a:
+ case 0x1e00:
+ case 0x1ea0:
+ case 0x1ea2:
+ case 0x1ea4:
+ case 0x1ea6:
+ case 0x1ea8:
+ case 0x1eaa:
+ case 0x1eac:
+ case 0x1eae:
+ case 0x1eb0:
+ case 0x1eb2:
+ case 0x1eb4:
+ case 0x1eb6:
+ EMIT2('A') EMIT2(A_grave) EMIT2(A_acute) // NOLINT(whitespace/cast)
+ EMIT2(A_circumflex) EMIT2(A_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(A_diaeresis) EMIT2(A_ring) // NOLINT(whitespace/cast)
+ EMIT2(0x100) EMIT2(0x102) EMIT2(0x104)
+ EMIT2(0x1cd) EMIT2(0x1de) EMIT2(0x1e0)
+ EMIT2(0x1fa) EMIT2(0x200) EMIT2(0x202)
+ EMIT2(0x226) EMIT2(0x23a) EMIT2(0x1e00)
+ EMIT2(0x1ea0) EMIT2(0x1ea2) EMIT2(0x1ea4)
+ EMIT2(0x1ea6) EMIT2(0x1ea8) EMIT2(0x1eaa)
+ EMIT2(0x1eac) EMIT2(0x1eae) EMIT2(0x1eb0)
+ EMIT2(0x1eb2) EMIT2(0x1eb6) EMIT2(0x1eb4)
+ return;
+
+ case 'B':
+ case 0x181:
+ case 0x243:
+ case 0x1e02:
+ case 0x1e04:
+ case 0x1e06:
+ EMIT2('B')
+ EMIT2(0x181) EMIT2(0x243) EMIT2(0x1e02)
+ EMIT2(0x1e04) EMIT2(0x1e06)
+ return;
+
+ case 'C':
+ case C_cedilla:
+ case 0x106:
+ case 0x108:
+ case 0x10a:
+ case 0x10c:
+ case 0x187:
+ case 0x23b:
+ case 0x1e08:
+ case 0xa792:
+ EMIT2('C') EMIT2(C_cedilla)
+ EMIT2(0x106) EMIT2(0x108) EMIT2(0x10a)
+ EMIT2(0x10c) EMIT2(0x187) EMIT2(0x23b)
+ EMIT2(0x1e08) EMIT2(0xa792)
+ return;
+
+ case 'D':
+ case 0x10e:
+ case 0x110:
+ case 0x18a:
+ case 0x1e0a:
+ case 0x1e0c:
+ case 0x1e0e:
+ case 0x1e10:
+ case 0x1e12:
+ EMIT2('D') EMIT2(0x10e) EMIT2(0x110) EMIT2(0x18a)
+ EMIT2(0x1e0a) EMIT2(0x1e0c) EMIT2(0x1e0e)
+ EMIT2(0x1e10) EMIT2(0x1e12)
+ return;
+
+ case 'E':
+ case E_grave:
+ case E_acute:
+ case E_circumflex:
+ case E_diaeresis:
+ case 0x112:
+ case 0x114:
+ case 0x116:
+ case 0x118:
+ case 0x11a:
+ case 0x204:
+ case 0x206:
+ case 0x228:
+ case 0x246:
+ case 0x1e14:
+ case 0x1e16:
+ case 0x1e18:
+ case 0x1e1a:
+ case 0x1e1c:
+ case 0x1eb8:
+ case 0x1eba:
+ case 0x1ebc:
+ case 0x1ebe:
+ case 0x1ec0:
+ case 0x1ec2:
+ case 0x1ec4:
+ case 0x1ec6:
+ EMIT2('E') EMIT2(E_grave) EMIT2(E_acute) // NOLINT(whitespace/cast)
+ EMIT2(E_circumflex) EMIT2(E_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x112) EMIT2(0x114) EMIT2(0x116)
+ EMIT2(0x118) EMIT2(0x11a) EMIT2(0x204)
+ EMIT2(0x206) EMIT2(0x228) EMIT2(0x246)
+ EMIT2(0x1e14) EMIT2(0x1e16) EMIT2(0x1e18)
+ EMIT2(0x1e1a) EMIT2(0x1e1c) EMIT2(0x1eb8)
+ EMIT2(0x1eba) EMIT2(0x1ebc) EMIT2(0x1ebe)
+ EMIT2(0x1ec0) EMIT2(0x1ec2) EMIT2(0x1ec4)
+ EMIT2(0x1ec6)
+ return;
+
+ case 'F':
+ case 0x191:
+ case 0x1e1e:
+ case 0xa798:
+ EMIT2('F') EMIT2(0x191) EMIT2(0x1e1e) EMIT2(0xa798)
+ return;
+
+ case 'G':
+ case 0x11c:
+ case 0x11e:
+ case 0x120:
+ case 0x122:
+ case 0x193:
+ case 0x1e4:
+ case 0x1e6:
+ case 0x1f4:
+ case 0x1e20:
+ case 0xa7a0:
+ EMIT2('G') EMIT2(0x11c) EMIT2(0x11e) EMIT2(0x120)
+ EMIT2(0x122) EMIT2(0x193) EMIT2(0x1e4)
+ EMIT2(0x1e6) EMIT2(0x1f4) EMIT2(0x1e20)
+ EMIT2(0xa7a0)
+ return;
+
+ case 'H':
+ case 0x124:
+ case 0x126:
+ case 0x21e:
+ case 0x1e22:
+ case 0x1e24:
+ case 0x1e26:
+ case 0x1e28:
+ case 0x1e2a:
+ case 0x2c67:
+ EMIT2('H') EMIT2(0x124) EMIT2(0x126) EMIT2(0x21e)
+ EMIT2(0x1e22) EMIT2(0x1e24) EMIT2(0x1e26)
+ EMIT2(0x1e28) EMIT2(0x1e2a) EMIT2(0x2c67)
+ return;
+
+ case 'I':
+ case I_grave:
+ case I_acute:
+ case I_circumflex:
+ case I_diaeresis:
+ case 0x128:
+ case 0x12a:
+ case 0x12c:
+ case 0x12e:
+ case 0x130:
+ case 0x197:
+ case 0x1cf:
+ case 0x208:
+ case 0x20a:
+ case 0x1e2c:
+ case 0x1e2e:
+ case 0x1ec8:
+ case 0x1eca:
+ EMIT2('I') EMIT2(I_grave) EMIT2(I_acute) // NOLINT(whitespace/cast)
+ EMIT2(I_circumflex) EMIT2(I_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x128) EMIT2(0x12a) EMIT2(0x12c)
+ EMIT2(0x12e) EMIT2(0x130) EMIT2(0x197)
+ EMIT2(0x1cf) EMIT2(0x208) EMIT2(0x20a)
+ EMIT2(0x1e2c) EMIT2(0x1e2e) EMIT2(0x1ec8)
+ EMIT2(0x1eca)
+ return;
+
+ case 'J':
+ case 0x134:
+ case 0x248:
+ EMIT2('J') EMIT2(0x134) EMIT2(0x248)
+ return;
+
+ case 'K':
+ case 0x136:
+ case 0x198:
+ case 0x1e8:
+ case 0x1e30:
+ case 0x1e32:
+ case 0x1e34:
+ case 0x2c69:
+ case 0xa740:
+ EMIT2('K') EMIT2(0x136) EMIT2(0x198) EMIT2(0x1e8)
+ EMIT2(0x1e30) EMIT2(0x1e32) EMIT2(0x1e34)
+ EMIT2(0x2c69) EMIT2(0xa740)
+ return;
+
+ case 'L':
+ case 0x139:
+ case 0x13b:
+ case 0x13d:
+ case 0x13f:
+ case 0x141:
+ case 0x23d:
+ case 0x1e36:
+ case 0x1e38:
+ case 0x1e3a:
+ case 0x1e3c:
+ case 0x2c60:
+ EMIT2('L') EMIT2(0x139) EMIT2(0x13b)
+ EMIT2(0x13d) EMIT2(0x13f) EMIT2(0x141)
+ EMIT2(0x23d) EMIT2(0x1e36) EMIT2(0x1e38)
+ EMIT2(0x1e3a) EMIT2(0x1e3c) EMIT2(0x2c60)
+ return;
+
+ case 'M':
+ case 0x1e3e:
+ case 0x1e40:
+ case 0x1e42:
+ EMIT2('M') EMIT2(0x1e3e) EMIT2(0x1e40)
+ EMIT2(0x1e42)
+ return;
+
+ case 'N':
+ case N_virguilla:
+ case 0x143:
+ case 0x145:
+ case 0x147:
+ case 0x1f8:
+ case 0x1e44:
+ case 0x1e46:
+ case 0x1e48:
+ case 0x1e4a:
+ case 0xa7a4:
+ EMIT2('N') EMIT2(N_virguilla)
+ EMIT2(0x143) EMIT2(0x145) EMIT2(0x147)
+ EMIT2(0x1f8) EMIT2(0x1e44) EMIT2(0x1e46)
+ EMIT2(0x1e48) EMIT2(0x1e4a) EMIT2(0xa7a4)
+ return;
+
+ case 'O':
+ case O_grave:
+ case O_acute:
+ case O_circumflex:
+ case O_virguilla:
+ case O_diaeresis:
+ case O_slash:
+ case 0x14c:
+ case 0x14e:
+ case 0x150:
+ case 0x19f:
+ case 0x1a0:
+ case 0x1d1:
+ case 0x1ea:
+ case 0x1ec:
+ case 0x1fe:
+ case 0x20c:
+ case 0x20e:
+ case 0x22a:
+ case 0x22c:
+ case 0x22e:
+ case 0x230:
+ case 0x1e4c:
+ case 0x1e4e:
+ case 0x1e50:
+ case 0x1e52:
+ case 0x1ecc:
+ case 0x1ece:
+ case 0x1ed0:
+ case 0x1ed2:
+ case 0x1ed4:
+ case 0x1ed6:
+ case 0x1ed8:
+ case 0x1eda:
+ case 0x1edc:
+ case 0x1ede:
+ case 0x1ee0:
+ case 0x1ee2:
+ EMIT2('O') EMIT2(O_grave) EMIT2(O_acute) // NOLINT(whitespace/cast)
+ EMIT2(O_circumflex) EMIT2(O_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(O_diaeresis) EMIT2(O_slash) // NOLINT(whitespace/cast)
+ EMIT2(0x14c) EMIT2(0x14e) EMIT2(0x150)
+ EMIT2(0x19f) EMIT2(0x1a0) EMIT2(0x1d1)
+ EMIT2(0x1ea) EMIT2(0x1ec) EMIT2(0x1fe)
+ EMIT2(0x20c) EMIT2(0x20e) EMIT2(0x22a)
+ EMIT2(0x22c) EMIT2(0x22e) EMIT2(0x230)
+ EMIT2(0x1e4c) EMIT2(0x1e4e) EMIT2(0x1e50)
+ EMIT2(0x1e52) EMIT2(0x1ecc) EMIT2(0x1ece)
+ EMIT2(0x1ed0) EMIT2(0x1ed2) EMIT2(0x1ed4)
+ EMIT2(0x1ed6) EMIT2(0x1ed8) EMIT2(0x1eda)
+ EMIT2(0x1edc) EMIT2(0x1ede) EMIT2(0x1ee0)
+ EMIT2(0x1ee2)
+ return;
+
+ case 'P':
+ case 0x1a4:
+ case 0x1e54:
+ case 0x1e56:
+ case 0x2c63:
+ EMIT2('P') EMIT2(0x1a4) EMIT2(0x1e54) EMIT2(0x1e56)
+ EMIT2(0x2c63)
+ return;
+
+ case 'Q':
+ case 0x24a:
+ EMIT2('Q') EMIT2(0x24a)
+ return;
+
+ case 'R':
+ case 0x154:
+ case 0x156:
+ case 0x158:
+ case 0x210:
+ case 0x212:
+ case 0x24c:
+ case 0x1e58:
+ case 0x1e5a:
+ case 0x1e5c:
+ case 0x1e5e:
+ case 0x2c64:
+ case 0xa7a6:
+ EMIT2('R') EMIT2(0x154) EMIT2(0x156) EMIT2(0x158)
+ EMIT2(0x210) EMIT2(0x212) EMIT2(0x24c) EMIT2(0x1e58)
+ EMIT2(0x1e5a) EMIT2(0x1e5c) EMIT2(0x1e5e) EMIT2(0x2c64)
+ EMIT2(0xa7a6)
+ return;
+
+ case 'S':
+ case 0x15a:
+ case 0x15c:
+ case 0x15e:
+ case 0x160:
+ case 0x218:
+ case 0x1e60:
+ case 0x1e62:
+ case 0x1e64:
+ case 0x1e66:
+ case 0x1e68:
+ case 0x2c7e:
+ case 0xa7a8:
+ EMIT2('S') EMIT2(0x15a) EMIT2(0x15c) EMIT2(0x15e)
+ EMIT2(0x160) EMIT2(0x218) EMIT2(0x1e60) EMIT2(0x1e62)
+ EMIT2(0x1e64) EMIT2(0x1e66) EMIT2(0x1e68) EMIT2(0x2c7e)
+ EMIT2(0xa7a8)
+ return;
+
+ case 'T':
+ case 0x162:
+ case 0x164:
+ case 0x166:
+ case 0x1ac:
+ case 0x1ae:
+ case 0x21a:
+ case 0x23e:
+ case 0x1e6a:
+ case 0x1e6c:
+ case 0x1e6e:
+ case 0x1e70:
+ EMIT2('T') EMIT2(0x162) EMIT2(0x164) EMIT2(0x166)
+ EMIT2(0x1ac) EMIT2(0x1ae) EMIT2(0x23e) EMIT2(0x21a)
+ EMIT2(0x1e6a) EMIT2(0x1e6c) EMIT2(0x1e6e) EMIT2(0x1e70)
+ return;
+
+ case 'U':
+ case U_grave:
+ case U_acute:
+ case U_diaeresis:
+ case U_circumflex:
+ case 0x168:
+ case 0x16a:
+ case 0x16c:
+ case 0x16e:
+ case 0x170:
+ case 0x172:
+ case 0x1af:
+ case 0x1d3:
+ case 0x1d5:
+ case 0x1d7:
+ case 0x1d9:
+ case 0x1db:
+ case 0x214:
+ case 0x216:
+ case 0x244:
+ case 0x1e72:
+ case 0x1e74:
+ case 0x1e76:
+ case 0x1e78:
+ case 0x1e7a:
+ case 0x1ee4:
+ case 0x1ee6:
+ case 0x1ee8:
+ case 0x1eea:
+ case 0x1eec:
+ case 0x1eee:
+ case 0x1ef0:
+ EMIT2('U') EMIT2(U_grave) EMIT2(U_acute) // NOLINT(whitespace/cast)
+ EMIT2(U_diaeresis) EMIT2(U_circumflex) // NOLINT(whitespace/cast)
+ EMIT2(0x168) EMIT2(0x16a)
+ EMIT2(0x16c) EMIT2(0x16e) EMIT2(0x170)
+ EMIT2(0x172) EMIT2(0x1af) EMIT2(0x1d3)
+ EMIT2(0x1d5) EMIT2(0x1d7) EMIT2(0x1d9)
+ EMIT2(0x1db) EMIT2(0x214) EMIT2(0x216)
+ EMIT2(0x244) EMIT2(0x1e72) EMIT2(0x1e74)
+ EMIT2(0x1e76) EMIT2(0x1e78) EMIT2(0x1e7a)
+ EMIT2(0x1ee4) EMIT2(0x1ee6) EMIT2(0x1ee8)
+ EMIT2(0x1eea) EMIT2(0x1eec) EMIT2(0x1eee)
+ EMIT2(0x1ef0)
+ return;
+
+ case 'V':
+ case 0x1b2:
+ case 0x1e7c:
+ case 0x1e7e:
+ EMIT2('V') EMIT2(0x1b2) EMIT2(0x1e7c) EMIT2(0x1e7e)
+ return;
+
+ case 'W':
+ case 0x174:
+ case 0x1e80:
+ case 0x1e82:
+ case 0x1e84:
+ case 0x1e86:
+ case 0x1e88:
+ EMIT2('W') EMIT2(0x174) EMIT2(0x1e80) EMIT2(0x1e82)
+ EMIT2(0x1e84) EMIT2(0x1e86) EMIT2(0x1e88)
+ return;
+
+ case 'X':
+ case 0x1e8a:
+ case 0x1e8c:
+ EMIT2('X') EMIT2(0x1e8a) EMIT2(0x1e8c)
+ return;
+
+ case 'Y':
+ case Y_acute:
+ case 0x176:
+ case 0x178:
+ case 0x1b3:
+ case 0x232:
+ case 0x24e:
+ case 0x1e8e:
+ case 0x1ef2:
+ case 0x1ef4:
+ case 0x1ef6:
+ case 0x1ef8:
+ EMIT2('Y') EMIT2(Y_acute)
+ EMIT2(0x176) EMIT2(0x178) EMIT2(0x1b3)
+ EMIT2(0x232) EMIT2(0x24e) EMIT2(0x1e8e)
+ EMIT2(0x1ef2) EMIT2(0x1ef4) EMIT2(0x1ef6)
+ EMIT2(0x1ef8)
+ return;
+
+ case 'Z':
+ case 0x179:
+ case 0x17b:
+ case 0x17d:
+ case 0x1b5:
+ case 0x1e90:
+ case 0x1e92:
+ case 0x1e94:
+ case 0x2c6b:
+ EMIT2('Z') EMIT2(0x179) EMIT2(0x17b) EMIT2(0x17d)
+ EMIT2(0x1b5) EMIT2(0x1e90) EMIT2(0x1e92)
+ EMIT2(0x1e94) EMIT2(0x2c6b)
+ return;
+
+ case 'a':
+ case a_grave:
+ case a_acute:
+ case a_circumflex:
+ case a_virguilla:
+ case a_diaeresis:
+ case a_ring:
+ case 0x101:
+ case 0x103:
+ case 0x105:
+ case 0x1ce:
+ case 0x1df:
+ case 0x1e1:
+ case 0x1fb:
+ case 0x201:
+ case 0x203:
+ case 0x227:
+ case 0x1d8f:
+ case 0x1e01:
+ case 0x1e9a:
+ case 0x1ea1:
+ case 0x1ea3:
+ case 0x1ea5:
+ case 0x1ea7:
+ case 0x1ea9:
+ case 0x1eab:
+ case 0x1ead:
+ case 0x1eaf:
+ case 0x1eb1:
+ case 0x1eb3:
+ case 0x1eb5:
+ case 0x1eb7:
+ case 0x2c65:
+ EMIT2('a') EMIT2(a_grave) EMIT2(a_acute) // NOLINT(whitespace/cast)
+ EMIT2(a_circumflex) EMIT2(a_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(a_diaeresis) EMIT2(a_ring) // NOLINT(whitespace/cast)
+ EMIT2(0x101) EMIT2(0x103) EMIT2(0x105)
+ EMIT2(0x1ce) EMIT2(0x1df) EMIT2(0x1e1)
+ EMIT2(0x1fb) EMIT2(0x201) EMIT2(0x203)
+ EMIT2(0x227) EMIT2(0x1d8f) EMIT2(0x1e01)
+ EMIT2(0x1e9a) EMIT2(0x1ea1) EMIT2(0x1ea3)
+ EMIT2(0x1ea5) EMIT2(0x1ea7) EMIT2(0x1ea9)
+ EMIT2(0x1eab) EMIT2(0x1ead) EMIT2(0x1eaf)
+ EMIT2(0x1eb1) EMIT2(0x1eb3) EMIT2(0x1eb5)
+ EMIT2(0x1eb7) EMIT2(0x2c65)
+ return;
+
+ case 'b':
+ case 0x180:
+ case 0x253:
+ case 0x1d6c:
+ case 0x1d80:
+ case 0x1e03:
+ case 0x1e05:
+ case 0x1e07:
+ EMIT2('b') EMIT2(0x180) EMIT2(0x253) EMIT2(0x1d6c)
+ EMIT2(0x1d80) EMIT2(0x1e03) EMIT2(0x1e05) EMIT2(0x1e07)
+ return;
+
+ case 'c':
+ case c_cedilla:
+ case 0x107:
+ case 0x109:
+ case 0x10b:
+ case 0x10d:
+ case 0x188:
+ case 0x23c:
+ case 0x1e09:
+ case 0xa793:
+ case 0xa794:
+ EMIT2('c') EMIT2(c_cedilla)
+ EMIT2(0x107) EMIT2(0x109) EMIT2(0x10b)
+ EMIT2(0x10d) EMIT2(0x188) EMIT2(0x23c)
+ EMIT2(0x1e09) EMIT2(0xa793) EMIT2(0xa794)
+ return;
+
+ case 'd':
+ case 0x10f:
+ case 0x111:
+ case 0x257:
+ case 0x1d6d:
+ case 0x1d81:
+ case 0x1d91:
+ case 0x1e0b:
+ case 0x1e0d:
+ case 0x1e0f:
+ case 0x1e11:
+ case 0x1e13:
+ EMIT2('d') EMIT2(0x10f) EMIT2(0x111)
+ EMIT2(0x257) EMIT2(0x1d6d) EMIT2(0x1d81)
+ EMIT2(0x1d91) EMIT2(0x1e0b) EMIT2(0x1e0d)
+ EMIT2(0x1e0f) EMIT2(0x1e11) EMIT2(0x1e13)
+ return;
+
+ case 'e':
+ case e_grave:
+ case e_acute:
+ case e_circumflex:
+ case e_diaeresis:
+ case 0x113:
+ case 0x115:
+ case 0x117:
+ case 0x119:
+ case 0x11b:
+ case 0x205:
+ case 0x207:
+ case 0x229:
+ case 0x247:
+ case 0x1d92:
+ case 0x1e15:
+ case 0x1e17:
+ case 0x1e19:
+ case 0x1e1b:
+ case 0x1e1d:
+ case 0x1eb9:
+ case 0x1ebb:
+ case 0x1ebd:
+ case 0x1ebf:
+ case 0x1ec1:
+ case 0x1ec3:
+ case 0x1ec5:
+ case 0x1ec7:
+ EMIT2('e') EMIT2(e_grave) EMIT2(e_acute) // NOLINT(whitespace/cast)
+ EMIT2(e_circumflex) EMIT2(e_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x113) EMIT2(0x115)
+ EMIT2(0x117) EMIT2(0x119) EMIT2(0x11b)
+ EMIT2(0x205) EMIT2(0x207) EMIT2(0x229)
+ EMIT2(0x247) EMIT2(0x1d92) EMIT2(0x1e15)
+ EMIT2(0x1e17) EMIT2(0x1e19) EMIT2(0x1e1b)
+ EMIT2(0x1e1d) EMIT2(0x1eb9) EMIT2(0x1ebb)
+ EMIT2(0x1ebd) EMIT2(0x1ebf) EMIT2(0x1ec1)
+ EMIT2(0x1ec3) EMIT2(0x1ec5) EMIT2(0x1ec7)
+ return;
+
+ case 'f':
+ case 0x192:
+ case 0x1d6e:
+ case 0x1d82:
+ case 0x1e1f:
+ case 0xa799:
+ EMIT2('f') EMIT2(0x192) EMIT2(0x1d6e) EMIT2(0x1d82)
+ EMIT2(0x1e1f) EMIT2(0xa799)
+ return;
+
+ case 'g':
+ case 0x11d:
+ case 0x11f:
+ case 0x121:
+ case 0x123:
+ case 0x1e5:
+ case 0x1e7:
+ case 0x1f5:
+ case 0x260:
+ case 0x1d83:
+ case 0x1e21:
+ case 0xa7a1:
+ EMIT2('g') EMIT2(0x11d) EMIT2(0x11f) EMIT2(0x121)
+ EMIT2(0x123) EMIT2(0x1e5) EMIT2(0x1e7)
+ EMIT2(0x1f5) EMIT2(0x260) EMIT2(0x1d83)
+ EMIT2(0x1e21) EMIT2(0xa7a1)
+ return;
+
+ case 'h':
+ case 0x125:
+ case 0x127:
+ case 0x21f:
+ case 0x1e23:
+ case 0x1e25:
+ case 0x1e27:
+ case 0x1e29:
+ case 0x1e2b:
+ case 0x1e96:
+ case 0x2c68:
+ case 0xa795:
+ EMIT2('h') EMIT2(0x125) EMIT2(0x127) EMIT2(0x21f)
+ EMIT2(0x1e23) EMIT2(0x1e25) EMIT2(0x1e27)
+ EMIT2(0x1e29) EMIT2(0x1e2b) EMIT2(0x1e96)
+ EMIT2(0x2c68) EMIT2(0xa795)
+ return;
+
+ case 'i':
+ case i_grave:
+ case i_acute:
+ case i_circumflex:
+ case i_diaeresis:
+ case 0x129:
+ case 0x12b:
+ case 0x12d:
+ case 0x12f:
+ case 0x1d0:
+ case 0x209:
+ case 0x20b:
+ case 0x268:
+ case 0x1d96:
+ case 0x1e2d:
+ case 0x1e2f:
+ case 0x1ec9:
+ case 0x1ecb:
+ EMIT2('i') EMIT2(i_grave) EMIT2(i_acute) // NOLINT(whitespace/cast)
+ EMIT2(i_circumflex) EMIT2(i_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x129) EMIT2(0x12b) EMIT2(0x12d)
+ EMIT2(0x12f) EMIT2(0x1d0) EMIT2(0x209)
+ EMIT2(0x20b) EMIT2(0x268) EMIT2(0x1d96)
+ EMIT2(0x1e2d) EMIT2(0x1e2f) EMIT2(0x1ec9)
+ EMIT2(0x1ecb) EMIT2(0x1ecb)
+ return;
+
+ case 'j':
+ case 0x135:
+ case 0x1f0:
+ case 0x249:
+ EMIT2('j') EMIT2(0x135) EMIT2(0x1f0) EMIT2(0x249)
+ return;
+
+ case 'k':
+ case 0x137:
+ case 0x199:
+ case 0x1e9:
+ case 0x1d84:
+ case 0x1e31:
+ case 0x1e33:
+ case 0x1e35:
+ case 0x2c6a:
+ case 0xa741:
+ EMIT2('k') EMIT2(0x137) EMIT2(0x199) EMIT2(0x1e9)
+ EMIT2(0x1d84) EMIT2(0x1e31) EMIT2(0x1e33)
+ EMIT2(0x1e35) EMIT2(0x2c6a) EMIT2(0xa741)
+ return;
+
+ case 'l':
+ case 0x13a:
+ case 0x13c:
+ case 0x13e:
+ case 0x140:
+ case 0x142:
+ case 0x19a:
+ case 0x1e37:
+ case 0x1e39:
+ case 0x1e3b:
+ case 0x1e3d:
+ case 0x2c61:
+ EMIT2('l') EMIT2(0x13a) EMIT2(0x13c)
+ EMIT2(0x13e) EMIT2(0x140) EMIT2(0x142)
+ EMIT2(0x19a) EMIT2(0x1e37) EMIT2(0x1e39)
+ EMIT2(0x1e3b) EMIT2(0x1e3d) EMIT2(0x2c61)
+ return;
+
+ case 'm':
+ case 0x1d6f:
+ case 0x1e3f:
+ case 0x1e41:
+ case 0x1e43:
+ EMIT2('m') EMIT2(0x1d6f) EMIT2(0x1e3f)
+ EMIT2(0x1e41) EMIT2(0x1e43)
+ return;
+
+ case 'n':
+ case n_virguilla:
+ case 0x144:
+ case 0x146:
+ case 0x148:
+ case 0x149:
+ case 0x1f9:
+ case 0x1d70:
+ case 0x1d87:
+ case 0x1e45:
+ case 0x1e47:
+ case 0x1e49:
+ case 0x1e4b:
+ case 0xa7a5:
+ EMIT2('n') EMIT2(n_virguilla)
+ EMIT2(0x144) EMIT2(0x146) EMIT2(0x148)
+ EMIT2(0x149) EMIT2(0x1f9) EMIT2(0x1d70)
+ EMIT2(0x1d87) EMIT2(0x1e45) EMIT2(0x1e47)
+ EMIT2(0x1e49) EMIT2(0x1e4b) EMIT2(0xa7a5)
+ return;
+
+ case 'o':
+ case o_grave:
+ case o_acute:
+ case o_circumflex:
+ case o_virguilla:
+ case o_diaeresis:
+ case o_slash:
+ case 0x14d:
+ case 0x14f:
+ case 0x151:
+ case 0x1a1:
+ case 0x1d2:
+ case 0x1eb:
+ case 0x1ed:
+ case 0x1ff:
+ case 0x20d:
+ case 0x20f:
+ case 0x22b:
+ case 0x22d:
+ case 0x22f:
+ case 0x231:
+ case 0x275:
+ case 0x1e4d:
+ case 0x1e4f:
+ case 0x1e51:
+ case 0x1e53:
+ case 0x1ecd:
+ case 0x1ecf:
+ case 0x1ed1:
+ case 0x1ed3:
+ case 0x1ed5:
+ case 0x1ed7:
+ case 0x1ed9:
+ case 0x1edb:
+ case 0x1edd:
+ case 0x1edf:
+ case 0x1ee1:
+ case 0x1ee3:
+ EMIT2('o') EMIT2(o_grave) EMIT2(o_acute) // NOLINT(whitespace/cast)
+ EMIT2(o_circumflex) EMIT2(o_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(o_diaeresis) EMIT2(o_slash) // NOLINT(whitespace/cast)
+ EMIT2(0x14d) EMIT2(0x14f) EMIT2(0x151)
+ EMIT2(0x1a1) EMIT2(0x1d2) EMIT2(0x1eb)
+ EMIT2(0x1ed) EMIT2(0x1ff) EMIT2(0x20d)
+ EMIT2(0x20f) EMIT2(0x22b) EMIT2(0x22d)
+ EMIT2(0x22f) EMIT2(0x231) EMIT2(0x275)
+ EMIT2(0x1e4d) EMIT2(0x1e4f) EMIT2(0x1e51)
+ EMIT2(0x1e53) EMIT2(0x1ecd) EMIT2(0x1ecf)
+ EMIT2(0x1ed1) EMIT2(0x1ed3) EMIT2(0x1ed5)
+ EMIT2(0x1ed7) EMIT2(0x1ed9) EMIT2(0x1edb)
+ EMIT2(0x1edd) EMIT2(0x1edf) EMIT2(0x1ee1)
+ EMIT2(0x1ee3)
+ return;
+
+ case 'p':
+ case 0x1a5:
+ case 0x1d71:
+ case 0x1d7d:
+ case 0x1d88:
+ case 0x1e55:
+ case 0x1e57:
+ EMIT2('p') EMIT2(0x1a5) EMIT2(0x1d71) EMIT2(0x1d7d)
+ EMIT2(0x1d88) EMIT2(0x1e55) EMIT2(0x1e57)
+ return;
+
+ case 'q':
+ case 0x24b:
+ case 0x2a0:
+ EMIT2('q') EMIT2(0x24b) EMIT2(0x2a0)
+ return;
+
+ case 'r':
+ case 0x155:
+ case 0x157:
+ case 0x159:
+ case 0x211:
+ case 0x213:
+ case 0x24d:
+ case 0x27d:
+ case 0x1d72:
+ case 0x1d73:
+ case 0x1d89:
+ case 0x1e59:
+ case 0x1e5b:
+ case 0x1e5d:
+ case 0x1e5f:
+ case 0xa7a7:
+ EMIT2('r') EMIT2(0x155) EMIT2(0x157) EMIT2(0x159)
+ EMIT2(0x211) EMIT2(0x213) EMIT2(0x24d) EMIT2(0x27d)
+ EMIT2(0x1d72) EMIT2(0x1d73) EMIT2(0x1d89) EMIT2(0x1e59)
+ EMIT2(0x1e5b) EMIT2(0x1e5d) EMIT2(0x1e5f) EMIT2(0xa7a7)
+ return;
+
+ case 's':
+ case 0x15b:
+ case 0x15d:
+ case 0x15f:
+ case 0x161:
+ case 0x219:
+ case 0x23f:
+ case 0x1d74:
+ case 0x1d8a:
+ case 0x1e61:
+ case 0x1e63:
+ case 0x1e65:
+ case 0x1e67:
+ case 0x1e69:
+ case 0xa7a9:
+ EMIT2('s') EMIT2(0x15b) EMIT2(0x15d) EMIT2(0x15f)
+ EMIT2(0x161) EMIT2(0x219) EMIT2(0x23f) EMIT2(0x1d74)
+ EMIT2(0x1d8a) EMIT2(0x1e61) EMIT2(0x1e63) EMIT2(0x1e65)
+ EMIT2(0x1e67) EMIT2(0x1e69) EMIT2(0xa7a9)
+ return;
+
+ case 't':
+ case 0x163:
+ case 0x165:
+ case 0x167:
+ case 0x1ab:
+ case 0x1ad:
+ case 0x21b:
+ case 0x288:
+ case 0x1d75:
+ case 0x1e6b:
+ case 0x1e6d:
+ case 0x1e6f:
+ case 0x1e71:
+ case 0x1e97:
+ case 0x2c66:
+ EMIT2('t') EMIT2(0x163) EMIT2(0x165) EMIT2(0x167)
+ EMIT2(0x1ab) EMIT2(0x1ad) EMIT2(0x21b) EMIT2(0x288)
+ EMIT2(0x1d75) EMIT2(0x1e6b) EMIT2(0x1e6d) EMIT2(0x1e6f)
+ EMIT2(0x1e71) EMIT2(0x1e97) EMIT2(0x2c66)
+ return;
+
+ case 'u':
+ case u_grave:
+ case u_acute:
+ case u_circumflex:
+ case u_diaeresis:
+ case 0x169:
+ case 0x16b:
+ case 0x16d:
+ case 0x16f:
+ case 0x171:
+ case 0x173:
+ case 0x1b0:
+ case 0x1d4:
+ case 0x1d6:
+ case 0x1d8:
+ case 0x1da:
+ case 0x1dc:
+ case 0x215:
+ case 0x217:
+ case 0x289:
+ case 0x1d7e:
+ case 0x1d99:
+ case 0x1e73:
+ case 0x1e75:
+ case 0x1e77:
+ case 0x1e79:
+ case 0x1e7b:
+ case 0x1ee5:
+ case 0x1ee7:
+ case 0x1ee9:
+ case 0x1eeb:
+ case 0x1eed:
+ case 0x1eef:
+ case 0x1ef1:
+ EMIT2('u') EMIT2(u_grave) EMIT2(u_acute) // NOLINT(whitespace/cast)
+ EMIT2(u_circumflex) EMIT2(u_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x169) EMIT2(0x16b)
+ EMIT2(0x16d) EMIT2(0x16f) EMIT2(0x171)
+ EMIT2(0x173) EMIT2(0x1d6) EMIT2(0x1d8)
+ EMIT2(0x215) EMIT2(0x217) EMIT2(0x1b0)
+ EMIT2(0x1d4) EMIT2(0x1da) EMIT2(0x1dc)
+ EMIT2(0x289) EMIT2(0x1e73) EMIT2(0x1d7e)
+ EMIT2(0x1d99) EMIT2(0x1e75) EMIT2(0x1e77)
+ EMIT2(0x1e79) EMIT2(0x1e7b) EMIT2(0x1ee5)
+ EMIT2(0x1ee7) EMIT2(0x1ee9) EMIT2(0x1eeb)
+ EMIT2(0x1eed) EMIT2(0x1eef) EMIT2(0x1ef1)
+ return;
+
+ case 'v':
+ case 0x28b:
+ case 0x1d8c:
+ case 0x1e7d:
+ case 0x1e7f:
+ EMIT2('v') EMIT2(0x28b) EMIT2(0x1d8c) EMIT2(0x1e7d)
+ EMIT2(0x1e7f)
+ return;
+
+ case 'w':
+ case 0x175:
+ case 0x1e81:
+ case 0x1e83:
+ case 0x1e85:
+ case 0x1e87:
+ case 0x1e89:
+ case 0x1e98:
+ EMIT2('w') EMIT2(0x175) EMIT2(0x1e81) EMIT2(0x1e83)
+ EMIT2(0x1e85) EMIT2(0x1e87) EMIT2(0x1e89) EMIT2(0x1e98)
+ return;
+
+ case 'x':
+ case 0x1e8b:
+ case 0x1e8d:
+ EMIT2('x') EMIT2(0x1e8b) EMIT2(0x1e8d)
+ return;
+
+ case 'y':
+ case y_acute:
+ case y_diaeresis:
+ case 0x177:
+ case 0x1b4:
+ case 0x233:
+ case 0x24f:
+ case 0x1e8f:
+ case 0x1e99:
+ case 0x1ef3:
+ case 0x1ef5:
+ case 0x1ef7:
+ case 0x1ef9:
+ EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f)
+ EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3)
+ EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9)
+ return;
+
+ case 'z':
+ case 0x17a:
+ case 0x17c:
+ case 0x17e:
+ case 0x1b6:
+ case 0x1d76:
+ case 0x1d8e:
+ case 0x1e91:
+ case 0x1e93:
+ case 0x1e95:
+ case 0x2c6c:
+ EMIT2('z') EMIT2(0x17a) EMIT2(0x17c) EMIT2(0x17e)
+ EMIT2(0x1b6) EMIT2(0x1d76) EMIT2(0x1d8e) EMIT2(0x1e91)
+ EMIT2(0x1e93) EMIT2(0x1e95) EMIT2(0x2c6c)
+ return;
+
+ // default: character itself
+ }
+ }
+
+ EMIT2(c);
+#undef EMIT2
+}
+
+// Code to parse regular expression.
+//
+// We try to reuse parsing functions in regexp.c to
+// minimize surprise and keep the syntax consistent.
+
+// Parse the lowest level.
+//
+// An atom can be one of a long list of items. Many atoms match one character
+// in the text. It is often an ordinary character or a character class.
+// Braces can be used to make a pattern into an atom. The "\z(\)" construct
+// is only for syntax highlighting.
+//
+// atom ::= ordinary-atom
+// or \( pattern \)
+// or \%( pattern \)
+// or \z( pattern \)
+static int nfa_regatom(void)
+{
+ int c;
+ int charclass;
+ int equiclass;
+ int collclass;
+ int got_coll_char;
+ uint8_t *p;
+ uint8_t *endp;
+ uint8_t *old_regparse = (uint8_t *)regparse;
+ int extra = 0;
+ int emit_range;
+ int negated;
+ int startc = -1;
+ int save_prev_at_start = prev_at_start;
+
+ c = getchr();
+ switch (c) {
+ case NUL:
+ EMSG_RET_FAIL(_(e_nul_found));
+
+ case Magic('^'):
+ EMIT(NFA_BOL);
+ break;
+
+ case Magic('$'):
+ EMIT(NFA_EOL);
+ had_eol = true;
+ break;
+
+ case Magic('<'):
+ EMIT(NFA_BOW);
+ break;
+
+ case Magic('>'):
+ EMIT(NFA_EOW);
+ break;
+
+ case Magic('_'):
+ c = no_Magic(getchr());
+ if (c == NUL) {
+ EMSG_RET_FAIL(_(e_nul_found));
+ }
+
+ if (c == '^') { // "\_^" is start-of-line
+ EMIT(NFA_BOL);
+ break;
+ }
+ if (c == '$') { // "\_$" is end-of-line
+ EMIT(NFA_EOL);
+ had_eol = true;
+ break;
+ }
+
+ extra = NFA_ADD_NL;
+
+ // "\_[" is collection plus newline
+ if (c == '[') {
+ goto collection;
+ }
+
+ // "\_x" is character class plus newline
+ FALLTHROUGH;
+
+ // Character classes.
+ case Magic('.'):
+ case Magic('i'):
+ case Magic('I'):
+ case Magic('k'):
+ case Magic('K'):
+ case Magic('f'):
+ case Magic('F'):
+ case Magic('p'):
+ case Magic('P'):
+ case Magic('s'):
+ case Magic('S'):
+ case Magic('d'):
+ case Magic('D'):
+ case Magic('x'):
+ case Magic('X'):
+ case Magic('o'):
+ case Magic('O'):
+ case Magic('w'):
+ case Magic('W'):
+ case Magic('h'):
+ case Magic('H'):
+ case Magic('a'):
+ case Magic('A'):
+ case Magic('l'):
+ case Magic('L'):
+ case Magic('u'):
+ case Magic('U'):
+ p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
+ if (p == NULL) {
+ if (extra == NFA_ADD_NL) {
+ semsg(_(e_ill_char_class), (int64_t)c);
+ rc_did_emsg = true;
+ return FAIL;
+ }
+ siemsg("INTERNAL: Unknown character class char: %" PRId64, (int64_t)c);
+ return FAIL;
+ }
+ // When '.' is followed by a composing char ignore the dot, so that
+ // the composing char is matched here.
+ if (c == Magic('.') && utf_iscomposing(peekchr())) {
+ old_regparse = (uint8_t *)regparse;
+ c = getchr();
+ goto nfa_do_multibyte;
+ }
+ EMIT(nfa_classcodes[p - classchars]);
+ if (extra == NFA_ADD_NL) {
+ EMIT(NFA_NEWL);
+ EMIT(NFA_OR);
+ regflags |= RF_HASNL;
+ }
+ break;
+
+ case Magic('n'):
+ if (reg_string) {
+ // In a string "\n" matches a newline character.
+ EMIT(NL);
+ } else {
+ // In buffer text "\n" matches the end of a line.
+ EMIT(NFA_NEWL);
+ regflags |= RF_HASNL;
+ }
+ break;
+
+ case Magic('('):
+ if (nfa_reg(REG_PAREN) == FAIL) {
+ return FAIL; // cascaded error
+ }
+ break;
+
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ semsg(_(e_misplaced), (char)no_Magic(c));
+ return FAIL;
+
+ case Magic('='):
+ case Magic('?'):
+ case Magic('+'):
+ case Magic('@'):
+ case Magic('*'):
+ case Magic('{'):
+ // these should follow an atom, not form an atom
+ semsg(_(e_misplaced), (char)no_Magic(c));
+ return FAIL;
+
+ case Magic('~'): {
+ uint8_t *lp;
+
+ // Previous substitute pattern.
+ // Generated as "\%(pattern\)".
+ if (reg_prev_sub == NULL) {
+ emsg(_(e_nopresub));
+ return FAIL;
+ }
+ for (lp = (uint8_t *)reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) {
+ EMIT(utf_ptr2char((char *)lp));
+ if (lp != (uint8_t *)reg_prev_sub) {
+ EMIT(NFA_CONCAT);
+ }
+ }
+ EMIT(NFA_NOPEN);
+ break;
+ }
+
+ case Magic('1'):
+ case Magic('2'):
+ case Magic('3'):
+ case Magic('4'):
+ case Magic('5'):
+ case Magic('6'):
+ case Magic('7'):
+ case Magic('8'):
+ case Magic('9'): {
+ int refnum = no_Magic(c) - '1';
+
+ if (!seen_endbrace(refnum + 1)) {
+ return FAIL;
+ }
+ EMIT(NFA_BACKREF1 + refnum);
+ rex.nfa_has_backref = true;
+ }
+ break;
+
+ case Magic('z'):
+ c = no_Magic(getchr());
+ switch (c) {
+ case 's':
+ EMIT(NFA_ZSTART);
+ if (!re_mult_next("\\zs")) {
+ return false;
+ }
+ break;
+ case 'e':
+ EMIT(NFA_ZEND);
+ rex.nfa_has_zend = true;
+ if (!re_mult_next("\\zs")) {
+ return false;
+ }
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // \z1...\z9
+ if ((reg_do_extmatch & REX_USE) == 0) {
+ EMSG_RET_FAIL(_(e_z1_not_allowed));
+ }
+ EMIT(NFA_ZREF1 + (no_Magic(c) - '1'));
+ // No need to set rex.nfa_has_backref, the sub-matches don't
+ // change when \z1 .. \z9 matches or not.
+ re_has_z = REX_USE;
+ break;
+ case '(':
+ // \z(
+ if (reg_do_extmatch != REX_SET) {
+ EMSG_RET_FAIL(_(e_z_not_allowed));
+ }
+ if (nfa_reg(REG_ZPAREN) == FAIL) {
+ return FAIL; // cascaded error
+ }
+ re_has_z = REX_SET;
+ break;
+ default:
+ semsg(_("E867: (NFA) Unknown operator '\\z%c'"),
+ no_Magic(c));
+ return FAIL;
+ }
+ break;
+
+ case Magic('%'):
+ c = no_Magic(getchr());
+ switch (c) {
+ // () without a back reference
+ case '(':
+ if (nfa_reg(REG_NPAREN) == FAIL) {
+ return FAIL;
+ }
+ EMIT(NFA_NOPEN);
+ break;
+
+ case 'd': // %d123 decimal
+ case 'o': // %o123 octal
+ case 'x': // %xab hex 2
+ case 'u': // %uabcd hex 4
+ case 'U': // %U1234abcd hex 8
+ {
+ int64_t nr;
+
+ switch (c) {
+ case 'd':
+ nr = getdecchrs(); break;
+ case 'o':
+ nr = getoctchrs(); break;
+ case 'x':
+ nr = gethexchrs(2); break;
+ case 'u':
+ nr = gethexchrs(4); break;
+ case 'U':
+ nr = gethexchrs(8); break;
+ default:
+ nr = -1; break;
+ }
+
+ if (nr < 0 || nr > INT_MAX) {
+ EMSG2_RET_FAIL(_("E678: Invalid character after %s%%[dxouU]"),
+ reg_magic == MAGIC_ALL);
+ }
+ // A NUL is stored in the text as NL
+ // TODO(vim): what if a composing character follows?
+ EMIT(nr == 0 ? 0x0a : (int)nr);
+ }
+ break;
+
+ // Catch \%^ and \%$ regardless of where they appear in the
+ // pattern -- regardless of whether or not it makes sense.
+ case '^':
+ EMIT(NFA_BOF);
+ break;
+
+ case '$':
+ EMIT(NFA_EOF);
+ break;
+
+ case '#':
+ if (regparse[0] == '=' && regparse[1] >= 48
+ && regparse[1] <= 50) {
+ // misplaced \%#=1
+ semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
+ return FAIL;
+ }
+ EMIT(NFA_CURSOR);
+ break;
+
+ case 'V':
+ EMIT(NFA_VISUAL);
+ break;
+
+ case 'C':
+ EMIT(NFA_ANY_COMPOSING);
+ break;
+
+ case '[': {
+ int n;
+
+ // \%[abc]
+ for (n = 0; (c = peekchr()) != ']'; n++) {
+ if (c == NUL) {
+ EMSG2_RET_FAIL(_(e_missing_sb),
+ reg_magic == MAGIC_ALL);
+ }
+ // recursive call!
+ if (nfa_regatom() == FAIL) {
+ return FAIL;
+ }
+ }
+ (void)getchr(); // get the ]
+ if (n == 0) {
+ EMSG2_RET_FAIL(_(e_empty_sb), reg_magic == MAGIC_ALL);
+ }
+ EMIT(NFA_OPT_CHARS);
+ EMIT(n);
+
+ // Emit as "\%(\%[abc]\)" to be able to handle
+ // "\%[abc]*" which would cause the empty string to be
+ // matched an unlimited number of times. NFA_NOPEN is
+ // added only once at a position, while NFA_SPLIT is
+ // added multiple times. This is more efficient than
+ // not allowing NFA_SPLIT multiple times, it is used
+ // a lot.
+ EMIT(NFA_NOPEN);
+ break;
+ }
+
+ default: {
+ int64_t n = 0;
+ const int cmp = c;
+ bool cur = false;
+ bool got_digit = false;
+
+ if (c == '<' || c == '>') {
+ c = getchr();
+ }
+ if (no_Magic(c) == '.') {
+ cur = true;
+ c = getchr();
+ }
+ while (ascii_isdigit(c)) {
+ if (cur) {
+ semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
+ return FAIL;
+ }
+ if (n > (INT32_MAX - (c - '0')) / 10) {
+ // overflow.
+ emsg(_(e_value_too_large));
+ return FAIL;
+ }
+ n = n * 10 + (c - '0');
+ c = getchr();
+ got_digit = true;
+ }
+ if (c == 'l' || c == 'c' || c == 'v') {
+ int32_t limit = INT32_MAX;
+
+ if (!cur && !got_digit) {
+ semsg(_(e_nfa_regexp_missing_value_in_chr), no_Magic(c));
+ return FAIL;
+ }
+ if (c == 'l') {
+ if (cur) {
+ n = curwin->w_cursor.lnum;
+ }
+ // \%{n}l \%{n}<l \%{n}>l
+ EMIT(cmp == '<' ? NFA_LNUM_LT
+ : cmp == '>' ? NFA_LNUM_GT : NFA_LNUM);
+ if (save_prev_at_start) {
+ at_start = true;
+ }
+ } else if (c == 'c') {
+ if (cur) {
+ n = curwin->w_cursor.col;
+ n++;
+ }
+ // \%{n}c \%{n}<c \%{n}>c
+ EMIT(cmp == '<' ? NFA_COL_LT
+ : cmp == '>' ? NFA_COL_GT : NFA_COL);
+ } else {
+ if (cur) {
+ colnr_T vcol = 0;
+ getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
+ n = ++vcol;
+ }
+ // \%{n}v \%{n}<v \%{n}>v
+ EMIT(cmp == '<' ? NFA_VCOL_LT
+ : cmp == '>' ? NFA_VCOL_GT : NFA_VCOL);
+ limit = INT32_MAX / MB_MAXBYTES;
+ }
+ if (n >= limit) {
+ emsg(_(e_value_too_large));
+ return FAIL;
+ }
+ EMIT((int)n);
+ break;
+ } else if (c == '\'' && n == 0) {
+ // \%'m \%<'m \%>'m
+ EMIT(cmp == '<' ? NFA_MARK_LT
+ : cmp == '>' ? NFA_MARK_GT : NFA_MARK);
+ EMIT(getchr());
+ break;
+ }
+ }
+ semsg(_("E867: (NFA) Unknown operator '\\%%%c'"),
+ no_Magic(c));
+ return FAIL;
+ }
+ break;
+
+ case Magic('['):
+collection:
+ // [abc] uses NFA_START_COLL - NFA_END_COLL
+ // [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL
+ // Each character is produced as a regular state, using
+ // NFA_CONCAT to bind them together.
+ // Besides normal characters there can be:
+ // - character classes NFA_CLASS_*
+ // - ranges, two characters followed by NFA_RANGE.
+
+ p = (uint8_t *)regparse;
+ endp = (uint8_t *)skip_anyof((char *)p);
+ if (*endp == ']') {
+ // Try to reverse engineer character classes. For example,
+ // recognize that [0-9] stands for \d and [A-Za-z_] for \h,
+ // and perform the necessary substitutions in the NFA.
+ int result = nfa_recognize_char_class((uint8_t *)regparse, endp, extra == NFA_ADD_NL);
+ if (result != FAIL) {
+ if (result >= NFA_FIRST_NL && result <= NFA_LAST_NL) {
+ EMIT(result - NFA_ADD_NL);
+ EMIT(NFA_NEWL);
+ EMIT(NFA_OR);
+ } else {
+ EMIT(result);
+ }
+ regparse = (char *)endp;
+ MB_PTR_ADV(regparse);
+ return OK;
+ }
+ // Failed to recognize a character class. Use the simple
+ // version that turns [abc] into 'a' OR 'b' OR 'c'
+ negated = false;
+ if (*regparse == '^') { // negated range
+ negated = true;
+ MB_PTR_ADV(regparse);
+ EMIT(NFA_START_NEG_COLL);
+ } else {
+ EMIT(NFA_START_COLL);
+ }
+ if (*regparse == '-') {
+ startc = '-';
+ EMIT(startc);
+ EMIT(NFA_CONCAT);
+ MB_PTR_ADV(regparse);
+ }
+ // Emit the OR branches for each character in the []
+ emit_range = false;
+ while ((uint8_t *)regparse < endp) {
+ int oldstartc = startc;
+ startc = -1;
+ got_coll_char = false;
+ if (*regparse == '[') {
+ // Check for [: :], [= =], [. .]
+ equiclass = collclass = 0;
+ charclass = get_char_class(&regparse);
+ if (charclass == CLASS_NONE) {
+ equiclass = get_equi_class(&regparse);
+ if (equiclass == 0) {
+ collclass = get_coll_element(&regparse);
+ }
+ }
+
+ // Character class like [:alpha:]
+ if (charclass != CLASS_NONE) {
+ switch (charclass) {
+ case CLASS_ALNUM:
+ EMIT(NFA_CLASS_ALNUM);
+ break;
+ case CLASS_ALPHA:
+ EMIT(NFA_CLASS_ALPHA);
+ break;
+ case CLASS_BLANK:
+ EMIT(NFA_CLASS_BLANK);
+ break;
+ case CLASS_CNTRL:
+ EMIT(NFA_CLASS_CNTRL);
+ break;
+ case CLASS_DIGIT:
+ EMIT(NFA_CLASS_DIGIT);
+ break;
+ case CLASS_GRAPH:
+ EMIT(NFA_CLASS_GRAPH);
+ break;
+ case CLASS_LOWER:
+ wants_nfa = true;
+ EMIT(NFA_CLASS_LOWER);
+ break;
+ case CLASS_PRINT:
+ EMIT(NFA_CLASS_PRINT);
+ break;
+ case CLASS_PUNCT:
+ EMIT(NFA_CLASS_PUNCT);
+ break;
+ case CLASS_SPACE:
+ EMIT(NFA_CLASS_SPACE);
+ break;
+ case CLASS_UPPER:
+ wants_nfa = true;
+ EMIT(NFA_CLASS_UPPER);
+ break;
+ case CLASS_XDIGIT:
+ EMIT(NFA_CLASS_XDIGIT);
+ break;
+ case CLASS_TAB:
+ EMIT(NFA_CLASS_TAB);
+ break;
+ case CLASS_RETURN:
+ EMIT(NFA_CLASS_RETURN);
+ break;
+ case CLASS_BACKSPACE:
+ EMIT(NFA_CLASS_BACKSPACE);
+ break;
+ case CLASS_ESCAPE:
+ EMIT(NFA_CLASS_ESCAPE);
+ break;
+ case CLASS_IDENT:
+ EMIT(NFA_CLASS_IDENT);
+ break;
+ case CLASS_KEYWORD:
+ EMIT(NFA_CLASS_KEYWORD);
+ break;
+ case CLASS_FNAME:
+ EMIT(NFA_CLASS_FNAME);
+ break;
+ }
+ EMIT(NFA_CONCAT);
+ continue;
+ }
+ // Try equivalence class [=a=] and the like
+ if (equiclass != 0) {
+ nfa_emit_equi_class(equiclass);
+ continue;
+ }
+ // Try collating class like [. .]
+ if (collclass != 0) {
+ startc = collclass; // allow [.a.]-x as a range
+ // Will emit the proper atom at the end of the
+ // while loop.
+ }
+ }
+ // Try a range like 'a-x' or '\t-z'. Also allows '-' as a
+ // start character.
+ if (*regparse == '-' && oldstartc != -1) {
+ emit_range = true;
+ startc = oldstartc;
+ MB_PTR_ADV(regparse);
+ continue; // reading the end of the range
+ }
+
+ // Now handle simple and escaped characters.
+ // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
+ // accepts "\t", "\e", etc., but only when the 'l' flag in
+ // 'cpoptions' is not included.
+ if (*regparse == '\\'
+ && (uint8_t *)regparse + 1 <= endp
+ && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
+ || (!reg_cpo_lit
+ && vim_strchr(REGEXP_ABBR, (uint8_t)regparse[1])
+ != NULL))) {
+ MB_PTR_ADV(regparse);
+
+ if (*regparse == 'n') {
+ startc = (reg_string || emit_range || regparse[1] == '-')
+ ? NL : NFA_NEWL;
+ } else if (*regparse == 'd'
+ || *regparse == 'o'
+ || *regparse == 'x'
+ || *regparse == 'u'
+ || *regparse == 'U') {
+ // TODO(RE): This needs more testing
+ startc = coll_get_char();
+ got_coll_char = true;
+ MB_PTR_BACK(old_regparse, regparse);
+ } else {
+ // \r,\t,\e,\b
+ startc = backslash_trans(*regparse);
+ }
+ }
+
+ // Normal printable char
+ if (startc == -1) {
+ startc = utf_ptr2char(regparse);
+ }
+
+ // Previous char was '-', so this char is end of range.
+ if (emit_range) {
+ int endc = startc;
+ startc = oldstartc;
+ if (startc > endc) {
+ EMSG_RET_FAIL(_(e_reverse_range));
+ }
+
+ if (endc > startc + 2) {
+ // Emit a range instead of the sequence of
+ // individual characters.
+ if (startc == 0) {
+ // \x00 is translated to \x0a, start at \x01.
+ EMIT(1);
+ } else {
+ post_ptr--; // remove NFA_CONCAT
+ }
+ EMIT(endc);
+ EMIT(NFA_RANGE);
+ EMIT(NFA_CONCAT);
+ } else if (utf_char2len(startc) > 1
+ || utf_char2len(endc) > 1) {
+ // Emit the characters in the range.
+ // "startc" was already emitted, so skip it.
+ for (c = startc + 1; c <= endc; c++) {
+ EMIT(c);
+ EMIT(NFA_CONCAT);
+ }
+ } else {
+ // Emit the range. "startc" was already emitted, so
+ // skip it.
+ for (c = startc + 1; c <= endc; c++) {
+ EMIT(c);
+ EMIT(NFA_CONCAT);
+ }
+ }
+ emit_range = false;
+ startc = -1;
+ } else {
+ // This char (startc) is not part of a range. Just
+ // emit it.
+ // Normally, simply emit startc. But if we get char
+ // code=0 from a collating char, then replace it with
+ // 0x0a.
+ // This is needed to completely mimic the behaviour of
+ // the backtracking engine.
+ if (startc == NFA_NEWL) {
+ // Line break can't be matched as part of the
+ // collection, add an OR below. But not for negated
+ // range.
+ if (!negated) {
+ extra = NFA_ADD_NL;
+ }
+ } else {
+ if (got_coll_char == true && startc == 0) {
+ EMIT(0x0a);
+ } else {
+ EMIT(startc);
+ }
+ EMIT(NFA_CONCAT);
+ }
+ }
+
+ MB_PTR_ADV(regparse);
+ } // while (p < endp)
+
+ MB_PTR_BACK(old_regparse, regparse);
+ if (*regparse == '-') { // if last, '-' is just a char
+ EMIT('-');
+ EMIT(NFA_CONCAT);
+ }
+
+ // skip the trailing ]
+ regparse = (char *)endp;
+ MB_PTR_ADV(regparse);
+
+ // Mark end of the collection.
+ if (negated == true) {
+ EMIT(NFA_END_NEG_COLL);
+ } else {
+ EMIT(NFA_END_COLL);
+ }
+
+ // \_[] also matches \n but it's not negated
+ if (extra == NFA_ADD_NL) {
+ EMIT(reg_string ? NL : NFA_NEWL);
+ EMIT(NFA_OR);
+ }
+
+ return OK;
+ } // if exists closing ]
+
+ if (reg_strict) {
+ EMSG_RET_FAIL(_(e_missingbracket));
+ }
+ FALLTHROUGH;
+
+ default: {
+ int plen;
+
+nfa_do_multibyte:
+ // plen is length of current char with composing chars
+ if (utf_char2len(c) != (plen = utfc_ptr2len((char *)old_regparse))
+ || utf_iscomposing(c)) {
+ int i = 0;
+
+ // A base character plus composing characters, or just one
+ // or more composing characters.
+ // This requires creating a separate atom as if enclosing
+ // the characters in (), where NFA_COMPOSING is the ( and
+ // NFA_END_COMPOSING is the ). Note that right now we are
+ // building the postfix form, not the NFA itself;
+ // a composing char could be: a, b, c, NFA_COMPOSING
+ // where 'b' and 'c' are chars with codes > 256. */
+ while (true) {
+ EMIT(c);
+ if (i > 0) {
+ EMIT(NFA_CONCAT);
+ }
+ if ((i += utf_char2len(c)) >= plen) {
+ break;
+ }
+ c = utf_ptr2char((char *)old_regparse + i);
+ }
+ EMIT(NFA_COMPOSING);
+ regparse = (char *)old_regparse + plen;
+ } else {
+ c = no_Magic(c);
+ EMIT(c);
+ }
+ return OK;
+ }
+ }
+
+ return OK;
+}
+
+// Parse something followed by possible [*+=].
+//
+// A piece is an atom, possibly followed by a multi, an indication of how many
+// times the atom can be matched. Example: "a*" matches any sequence of "a"
+// characters: "", "a", "aa", etc.
+//
+// piece ::= atom
+// or atom multi
+static int nfa_regpiece(void)
+{
+ int i;
+ int op;
+ int ret;
+ int minval, maxval;
+ bool greedy = true; // Braces are prefixed with '-' ?
+ parse_state_T old_state;
+ parse_state_T new_state;
+ int64_t c2;
+ int old_post_pos;
+ int my_post_start;
+ int quest;
+
+ // Save the current parse state, so that we can use it if <atom>{m,n} is
+ // next.
+ save_parse_state(&old_state);
+
+ // store current pos in the postfix form, for \{m,n} involving 0s
+ my_post_start = (int)(post_ptr - post_start);
+
+ ret = nfa_regatom();
+ if (ret == FAIL) {
+ return FAIL; // cascaded error
+ }
+ op = peekchr();
+ if (re_multi_type(op) == NOT_MULTI) {
+ return OK;
+ }
+
+ skipchr();
+ switch (op) {
+ case Magic('*'):
+ EMIT(NFA_STAR);
+ break;
+
+ case Magic('+'):
+ // Trick: Normally, (a*)\+ would match the whole input "aaa". The
+ // first and only submatch would be "aaa". But the backtracking
+ // engine interprets the plus as "try matching one more time", and
+ // a* matches a second time at the end of the input, the empty
+ // string.
+ // The submatch will be the empty string.
+ //
+ // In order to be consistent with the old engine, we replace
+ // <atom>+ with <atom><atom>*
+ restore_parse_state(&old_state);
+ curchr = -1;
+ if (nfa_regatom() == FAIL) {
+ return FAIL;
+ }
+ EMIT(NFA_STAR);
+ EMIT(NFA_CONCAT);
+ skipchr(); // skip the \+
+ break;
+
+ case Magic('@'):
+ c2 = getdecchrs();
+ op = no_Magic(getchr());
+ i = 0;
+ switch (op) {
+ case '=':
+ // \@=
+ i = NFA_PREV_ATOM_NO_WIDTH;
+ break;
+ case '!':
+ // \@!
+ i = NFA_PREV_ATOM_NO_WIDTH_NEG;
+ break;
+ case '<':
+ op = no_Magic(getchr());
+ if (op == '=') {
+ // \@<=
+ i = NFA_PREV_ATOM_JUST_BEFORE;
+ } else if (op == '!') {
+ // \@<!
+ i = NFA_PREV_ATOM_JUST_BEFORE_NEG;
+ }
+ break;
+ case '>':
+ // \@>
+ i = NFA_PREV_ATOM_LIKE_PATTERN;
+ break;
+ }
+ if (i == 0) {
+ semsg(_("E869: (NFA) Unknown operator '\\@%c'"), op);
+ return FAIL;
+ }
+ EMIT(i);
+ if (i == NFA_PREV_ATOM_JUST_BEFORE
+ || i == NFA_PREV_ATOM_JUST_BEFORE_NEG) {
+ EMIT((int)c2);
+ }
+ break;
+
+ case Magic('?'):
+ case Magic('='):
+ EMIT(NFA_QUEST);
+ break;
+
+ case Magic('{'):
+ // a{2,5} will expand to 'aaa?a?a?'
+ // a{-1,3} will expand to 'aa??a??', where ?? is the nongreedy
+ // version of '?'
+ // \v(ab){2,3} will expand to '(ab)(ab)(ab)?', where all the
+ // parenthesis have the same id
+
+ greedy = true;
+ c2 = peekchr();
+ if (c2 == '-' || c2 == Magic('-')) {
+ skipchr();
+ greedy = false;
+ }
+ if (!read_limits(&minval, &maxval)) {
+ EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits"));
+ }
+
+ // <atom>{0,inf}, <atom>{0,} and <atom>{} are equivalent to
+ // <atom>*
+ if (minval == 0 && maxval == MAX_LIMIT) {
+ if (greedy) {
+ // \{}, \{0,}
+ EMIT(NFA_STAR);
+ } else {
+ // \{-}, \{-0,}
+ EMIT(NFA_STAR_NONGREEDY);
+ }
+ break;
+ }
+
+ // Special case: x{0} or x{-0}
+ if (maxval == 0) {
+ // Ignore result of previous call to nfa_regatom()
+ post_ptr = post_start + my_post_start;
+ // NFA_EMPTY is 0-length and works everywhere
+ EMIT(NFA_EMPTY);
+ return OK;
+ }
+
+ // The engine is very inefficient (uses too many states) when the
+ // maximum is much larger than the minimum and when the maximum is
+ // large. However, when maxval is MAX_LIMIT, it is okay, as this
+ // will emit NFA_STAR.
+ // Bail out if we can use the other engine, but only, when the
+ // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
+ // does not work with characters > 8 bit with the BT engine)
+ if ((nfa_re_flags & RE_AUTO)
+ && (maxval > 500 || maxval > minval + 200)
+ && (maxval != MAX_LIMIT && minval < 200)
+ && !wants_nfa) {
+ return FAIL;
+ }
+
+ // Ignore previous call to nfa_regatom()
+ post_ptr = post_start + my_post_start;
+ // Save parse state after the repeated atom and the \{}
+ save_parse_state(&new_state);
+
+ quest = (greedy == true ? NFA_QUEST : NFA_QUEST_NONGREEDY);
+ for (i = 0; i < maxval; i++) {
+ // Goto beginning of the repeated atom
+ restore_parse_state(&old_state);
+ old_post_pos = (int)(post_ptr - post_start);
+ if (nfa_regatom() == FAIL) {
+ return FAIL;
+ }
+ // after "minval" times, atoms are optional
+ if (i + 1 > minval) {
+ if (maxval == MAX_LIMIT) {
+ if (greedy) {
+ EMIT(NFA_STAR);
+ } else {
+ EMIT(NFA_STAR_NONGREEDY);
+ }
+ } else {
+ EMIT(quest);
+ }
+ }
+ if (old_post_pos != my_post_start) {
+ EMIT(NFA_CONCAT);
+ }
+ if (i + 1 > minval && maxval == MAX_LIMIT) {
+ break;
+ }
+ }
+
+ // Go to just after the repeated atom and the \{}
+ restore_parse_state(&new_state);
+ curchr = -1;
+
+ break;
+
+ default:
+ break;
+ } // end switch
+
+ if (re_multi_type(peekchr()) != NOT_MULTI) {
+ // Can't have a multi follow a multi.
+ EMSG_RET_FAIL(_("E871: (NFA regexp) Can't have a multi follow a multi"));
+ }
+
+ return OK;
+}
+
+// Parse one or more pieces, concatenated. It matches a match for the
+// first piece, followed by a match for the second piece, etc. Example:
+// "f[0-9]b", first matches "f", then a digit and then "b".
+//
+// concat ::= piece
+// or piece piece
+// or piece piece piece
+// etc.
+static int nfa_regconcat(void)
+{
+ bool cont = true;
+ bool first = true;
+
+ while (cont) {
+ switch (peekchr()) {
+ case NUL:
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ cont = false;
+ break;
+
+ case Magic('Z'):
+ regflags |= RF_ICOMBINE;
+ skipchr_keepstart();
+ break;
+ case Magic('c'):
+ regflags |= RF_ICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('C'):
+ regflags |= RF_NOICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('v'):
+ reg_magic = MAGIC_ALL;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('m'):
+ reg_magic = MAGIC_ON;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('M'):
+ reg_magic = MAGIC_OFF;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('V'):
+ reg_magic = MAGIC_NONE;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+
+ default:
+ if (nfa_regpiece() == FAIL) {
+ return FAIL;
+ }
+ if (first == false) {
+ EMIT(NFA_CONCAT);
+ } else {
+ first = false;
+ }
+ break;
+ }
+ }
+
+ return OK;
+}
+
+// Parse a branch, one or more concats, separated by "\&". It matches the
+// last concat, but only if all the preceding concats also match at the same
+// position. Examples:
+// "foobeep\&..." matches "foo" in "foobeep".
+// ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob"
+//
+// branch ::= concat
+// or concat \& concat
+// or concat \& concat \& concat
+// etc.
+static int nfa_regbranch(void)
+{
+ int old_post_pos;
+
+ old_post_pos = (int)(post_ptr - post_start);
+
+ // First branch, possibly the only one
+ if (nfa_regconcat() == FAIL) {
+ return FAIL;
+ }
+
+ // Try next concats
+ while (peekchr() == Magic('&')) {
+ skipchr();
+ // if concat is empty do emit a node
+ if (old_post_pos == (int)(post_ptr - post_start)) {
+ EMIT(NFA_EMPTY);
+ }
+ EMIT(NFA_NOPEN);
+ EMIT(NFA_PREV_ATOM_NO_WIDTH);
+ old_post_pos = (int)(post_ptr - post_start);
+ if (nfa_regconcat() == FAIL) {
+ return FAIL;
+ }
+ // if concat is empty do emit a node
+ if (old_post_pos == (int)(post_ptr - post_start)) {
+ EMIT(NFA_EMPTY);
+ }
+ EMIT(NFA_CONCAT);
+ }
+
+ // if a branch is empty, emit one node for it
+ if (old_post_pos == (int)(post_ptr - post_start)) {
+ EMIT(NFA_EMPTY);
+ }
+
+ return OK;
+}
+
+/// Parse a pattern, one or more branches, separated by "\|". It matches
+/// anything that matches one of the branches. Example: "foo\|beep" matches
+/// "foo" and matches "beep". If more than one branch matches, the first one
+/// is used.
+///
+/// pattern ::= branch
+/// or branch \| branch
+/// or branch \| branch \| branch
+/// etc.
+///
+/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
+static int nfa_reg(int paren)
+{
+ int parno = 0;
+
+ if (paren == REG_PAREN) {
+ if (regnpar >= NSUBEXP) { // Too many `('
+ EMSG_RET_FAIL(_("E872: (NFA regexp) Too many '('"));
+ }
+ parno = regnpar++;
+ } else if (paren == REG_ZPAREN) {
+ // Make a ZOPEN node.
+ if (regnzpar >= NSUBEXP) {
+ EMSG_RET_FAIL(_("E879: (NFA regexp) Too many \\z("));
+ }
+ parno = regnzpar++;
+ }
+
+ if (nfa_regbranch() == FAIL) {
+ return FAIL; // cascaded error
+ }
+ while (peekchr() == Magic('|')) {
+ skipchr();
+ if (nfa_regbranch() == FAIL) {
+ return FAIL; // cascaded error
+ }
+ EMIT(NFA_OR);
+ }
+
+ // Check for proper termination.
+ if (paren != REG_NOPAREN && getchr() != Magic(')')) {
+ if (paren == REG_NPAREN) {
+ EMSG2_RET_FAIL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG2_RET_FAIL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
+ }
+ } else if (paren == REG_NOPAREN && peekchr() != NUL) {
+ if (peekchr() == Magic(')')) {
+ EMSG2_RET_FAIL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error"));
+ }
+ }
+ // Here we set the flag allowing back references to this set of
+ // parentheses.
+ if (paren == REG_PAREN) {
+ had_endbrace[parno] = true; // have seen the close paren
+ EMIT(NFA_MOPEN + parno);
+ } else if (paren == REG_ZPAREN) {
+ EMIT(NFA_ZOPEN + parno);
+ }
+
+ return OK;
+}
+
+#ifdef REGEXP_DEBUG
+static uint8_t code[50];
+
+static void nfa_set_code(int c)
+{
+ int addnl = false;
+
+ if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) {
+ addnl = true;
+ c -= NFA_ADD_NL;
+ }
+
+ STRCPY(code, "");
+ switch (c) {
+ case NFA_MATCH:
+ STRCPY(code, "NFA_MATCH "); break;
+ case NFA_SPLIT:
+ STRCPY(code, "NFA_SPLIT "); break;
+ case NFA_CONCAT:
+ STRCPY(code, "NFA_CONCAT "); break;
+ case NFA_NEWL:
+ STRCPY(code, "NFA_NEWL "); break;
+ case NFA_ZSTART:
+ STRCPY(code, "NFA_ZSTART"); break;
+ case NFA_ZEND:
+ STRCPY(code, "NFA_ZEND"); break;
+
+ case NFA_BACKREF1:
+ STRCPY(code, "NFA_BACKREF1"); break;
+ case NFA_BACKREF2:
+ STRCPY(code, "NFA_BACKREF2"); break;
+ case NFA_BACKREF3:
+ STRCPY(code, "NFA_BACKREF3"); break;
+ case NFA_BACKREF4:
+ STRCPY(code, "NFA_BACKREF4"); break;
+ case NFA_BACKREF5:
+ STRCPY(code, "NFA_BACKREF5"); break;
+ case NFA_BACKREF6:
+ STRCPY(code, "NFA_BACKREF6"); break;
+ case NFA_BACKREF7:
+ STRCPY(code, "NFA_BACKREF7"); break;
+ case NFA_BACKREF8:
+ STRCPY(code, "NFA_BACKREF8"); break;
+ case NFA_BACKREF9:
+ STRCPY(code, "NFA_BACKREF9"); break;
+ case NFA_ZREF1:
+ STRCPY(code, "NFA_ZREF1"); break;
+ case NFA_ZREF2:
+ STRCPY(code, "NFA_ZREF2"); break;
+ case NFA_ZREF3:
+ STRCPY(code, "NFA_ZREF3"); break;
+ case NFA_ZREF4:
+ STRCPY(code, "NFA_ZREF4"); break;
+ case NFA_ZREF5:
+ STRCPY(code, "NFA_ZREF5"); break;
+ case NFA_ZREF6:
+ STRCPY(code, "NFA_ZREF6"); break;
+ case NFA_ZREF7:
+ STRCPY(code, "NFA_ZREF7"); break;
+ case NFA_ZREF8:
+ STRCPY(code, "NFA_ZREF8"); break;
+ case NFA_ZREF9:
+ STRCPY(code, "NFA_ZREF9"); break;
+ case NFA_SKIP:
+ STRCPY(code, "NFA_SKIP"); break;
+
+ case NFA_PREV_ATOM_NO_WIDTH:
+ STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH"); break;
+ case NFA_PREV_ATOM_NO_WIDTH_NEG:
+ STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH_NEG"); break;
+ case NFA_PREV_ATOM_JUST_BEFORE:
+ STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE"); break;
+ case NFA_PREV_ATOM_JUST_BEFORE_NEG:
+ STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE_NEG"); break;
+ case NFA_PREV_ATOM_LIKE_PATTERN:
+ STRCPY(code, "NFA_PREV_ATOM_LIKE_PATTERN"); break;
+
+ case NFA_NOPEN:
+ STRCPY(code, "NFA_NOPEN"); break;
+ case NFA_NCLOSE:
+ STRCPY(code, "NFA_NCLOSE"); break;
+ case NFA_START_INVISIBLE:
+ STRCPY(code, "NFA_START_INVISIBLE"); break;
+ case NFA_START_INVISIBLE_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_FIRST"); break;
+ case NFA_START_INVISIBLE_NEG:
+ STRCPY(code, "NFA_START_INVISIBLE_NEG"); break;
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_NEG_FIRST"); break;
+ case NFA_START_INVISIBLE_BEFORE:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE"); break;
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE_FIRST"); break;
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG"); break;
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG_FIRST"); break;
+ case NFA_START_PATTERN:
+ STRCPY(code, "NFA_START_PATTERN"); break;
+ case NFA_END_INVISIBLE:
+ STRCPY(code, "NFA_END_INVISIBLE"); break;
+ case NFA_END_INVISIBLE_NEG:
+ STRCPY(code, "NFA_END_INVISIBLE_NEG"); break;
+ case NFA_END_PATTERN:
+ STRCPY(code, "NFA_END_PATTERN"); break;
+
+ case NFA_COMPOSING:
+ STRCPY(code, "NFA_COMPOSING"); break;
+ case NFA_END_COMPOSING:
+ STRCPY(code, "NFA_END_COMPOSING"); break;
+ case NFA_OPT_CHARS:
+ STRCPY(code, "NFA_OPT_CHARS"); break;
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ STRCPY(code, "NFA_MOPEN(x)");
+ code[10] = c - NFA_MOPEN + '0';
+ break;
+ case NFA_MCLOSE:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ STRCPY(code, "NFA_MCLOSE(x)");
+ code[11] = c - NFA_MCLOSE + '0';
+ break;
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ STRCPY(code, "NFA_ZOPEN(x)");
+ code[10] = c - NFA_ZOPEN + '0';
+ break;
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ STRCPY(code, "NFA_ZCLOSE(x)");
+ code[11] = c - NFA_ZCLOSE + '0';
+ break;
+ case NFA_EOL:
+ STRCPY(code, "NFA_EOL "); break;
+ case NFA_BOL:
+ STRCPY(code, "NFA_BOL "); break;
+ case NFA_EOW:
+ STRCPY(code, "NFA_EOW "); break;
+ case NFA_BOW:
+ STRCPY(code, "NFA_BOW "); break;
+ case NFA_EOF:
+ STRCPY(code, "NFA_EOF "); break;
+ case NFA_BOF:
+ STRCPY(code, "NFA_BOF "); break;
+ case NFA_LNUM:
+ STRCPY(code, "NFA_LNUM "); break;
+ case NFA_LNUM_GT:
+ STRCPY(code, "NFA_LNUM_GT "); break;
+ case NFA_LNUM_LT:
+ STRCPY(code, "NFA_LNUM_LT "); break;
+ case NFA_COL:
+ STRCPY(code, "NFA_COL "); break;
+ case NFA_COL_GT:
+ STRCPY(code, "NFA_COL_GT "); break;
+ case NFA_COL_LT:
+ STRCPY(code, "NFA_COL_LT "); break;
+ case NFA_VCOL:
+ STRCPY(code, "NFA_VCOL "); break;
+ case NFA_VCOL_GT:
+ STRCPY(code, "NFA_VCOL_GT "); break;
+ case NFA_VCOL_LT:
+ STRCPY(code, "NFA_VCOL_LT "); break;
+ case NFA_MARK:
+ STRCPY(code, "NFA_MARK "); break;
+ case NFA_MARK_GT:
+ STRCPY(code, "NFA_MARK_GT "); break;
+ case NFA_MARK_LT:
+ STRCPY(code, "NFA_MARK_LT "); break;
+ case NFA_CURSOR:
+ STRCPY(code, "NFA_CURSOR "); break;
+ case NFA_VISUAL:
+ STRCPY(code, "NFA_VISUAL "); break;
+ case NFA_ANY_COMPOSING:
+ STRCPY(code, "NFA_ANY_COMPOSING "); break;
+
+ case NFA_STAR:
+ STRCPY(code, "NFA_STAR "); break;
+ case NFA_STAR_NONGREEDY:
+ STRCPY(code, "NFA_STAR_NONGREEDY "); break;
+ case NFA_QUEST:
+ STRCPY(code, "NFA_QUEST"); break;
+ case NFA_QUEST_NONGREEDY:
+ STRCPY(code, "NFA_QUEST_NON_GREEDY"); break;
+ case NFA_EMPTY:
+ STRCPY(code, "NFA_EMPTY"); break;
+ case NFA_OR:
+ STRCPY(code, "NFA_OR"); break;
+
+ case NFA_START_COLL:
+ STRCPY(code, "NFA_START_COLL"); break;
+ case NFA_END_COLL:
+ STRCPY(code, "NFA_END_COLL"); break;
+ case NFA_START_NEG_COLL:
+ STRCPY(code, "NFA_START_NEG_COLL"); break;
+ case NFA_END_NEG_COLL:
+ STRCPY(code, "NFA_END_NEG_COLL"); break;
+ case NFA_RANGE:
+ STRCPY(code, "NFA_RANGE"); break;
+ case NFA_RANGE_MIN:
+ STRCPY(code, "NFA_RANGE_MIN"); break;
+ case NFA_RANGE_MAX:
+ STRCPY(code, "NFA_RANGE_MAX"); break;
+
+ case NFA_CLASS_ALNUM:
+ STRCPY(code, "NFA_CLASS_ALNUM"); break;
+ case NFA_CLASS_ALPHA:
+ STRCPY(code, "NFA_CLASS_ALPHA"); break;
+ case NFA_CLASS_BLANK:
+ STRCPY(code, "NFA_CLASS_BLANK"); break;
+ case NFA_CLASS_CNTRL:
+ STRCPY(code, "NFA_CLASS_CNTRL"); break;
+ case NFA_CLASS_DIGIT:
+ STRCPY(code, "NFA_CLASS_DIGIT"); break;
+ case NFA_CLASS_GRAPH:
+ STRCPY(code, "NFA_CLASS_GRAPH"); break;
+ case NFA_CLASS_LOWER:
+ STRCPY(code, "NFA_CLASS_LOWER"); break;
+ case NFA_CLASS_PRINT:
+ STRCPY(code, "NFA_CLASS_PRINT"); break;
+ case NFA_CLASS_PUNCT:
+ STRCPY(code, "NFA_CLASS_PUNCT"); break;
+ case NFA_CLASS_SPACE:
+ STRCPY(code, "NFA_CLASS_SPACE"); break;
+ case NFA_CLASS_UPPER:
+ STRCPY(code, "NFA_CLASS_UPPER"); break;
+ case NFA_CLASS_XDIGIT:
+ STRCPY(code, "NFA_CLASS_XDIGIT"); break;
+ case NFA_CLASS_TAB:
+ STRCPY(code, "NFA_CLASS_TAB"); break;
+ case NFA_CLASS_RETURN:
+ STRCPY(code, "NFA_CLASS_RETURN"); break;
+ case NFA_CLASS_BACKSPACE:
+ STRCPY(code, "NFA_CLASS_BACKSPACE"); break;
+ case NFA_CLASS_ESCAPE:
+ STRCPY(code, "NFA_CLASS_ESCAPE"); break;
+ case NFA_CLASS_IDENT:
+ STRCPY(code, "NFA_CLASS_IDENT"); break;
+ case NFA_CLASS_KEYWORD:
+ STRCPY(code, "NFA_CLASS_KEYWORD"); break;
+ case NFA_CLASS_FNAME:
+ STRCPY(code, "NFA_CLASS_FNAME"); break;
+
+ case NFA_ANY:
+ STRCPY(code, "NFA_ANY"); break;
+ case NFA_IDENT:
+ STRCPY(code, "NFA_IDENT"); break;
+ case NFA_SIDENT:
+ STRCPY(code, "NFA_SIDENT"); break;
+ case NFA_KWORD:
+ STRCPY(code, "NFA_KWORD"); break;
+ case NFA_SKWORD:
+ STRCPY(code, "NFA_SKWORD"); break;
+ case NFA_FNAME:
+ STRCPY(code, "NFA_FNAME"); break;
+ case NFA_SFNAME:
+ STRCPY(code, "NFA_SFNAME"); break;
+ case NFA_PRINT:
+ STRCPY(code, "NFA_PRINT"); break;
+ case NFA_SPRINT:
+ STRCPY(code, "NFA_SPRINT"); break;
+ case NFA_WHITE:
+ STRCPY(code, "NFA_WHITE"); break;
+ case NFA_NWHITE:
+ STRCPY(code, "NFA_NWHITE"); break;
+ case NFA_DIGIT:
+ STRCPY(code, "NFA_DIGIT"); break;
+ case NFA_NDIGIT:
+ STRCPY(code, "NFA_NDIGIT"); break;
+ case NFA_HEX:
+ STRCPY(code, "NFA_HEX"); break;
+ case NFA_NHEX:
+ STRCPY(code, "NFA_NHEX"); break;
+ case NFA_OCTAL:
+ STRCPY(code, "NFA_OCTAL"); break;
+ case NFA_NOCTAL:
+ STRCPY(code, "NFA_NOCTAL"); break;
+ case NFA_WORD:
+ STRCPY(code, "NFA_WORD"); break;
+ case NFA_NWORD:
+ STRCPY(code, "NFA_NWORD"); break;
+ case NFA_HEAD:
+ STRCPY(code, "NFA_HEAD"); break;
+ case NFA_NHEAD:
+ STRCPY(code, "NFA_NHEAD"); break;
+ case NFA_ALPHA:
+ STRCPY(code, "NFA_ALPHA"); break;
+ case NFA_NALPHA:
+ STRCPY(code, "NFA_NALPHA"); break;
+ case NFA_LOWER:
+ STRCPY(code, "NFA_LOWER"); break;
+ case NFA_NLOWER:
+ STRCPY(code, "NFA_NLOWER"); break;
+ case NFA_UPPER:
+ STRCPY(code, "NFA_UPPER"); break;
+ case NFA_NUPPER:
+ STRCPY(code, "NFA_NUPPER"); break;
+ case NFA_LOWER_IC:
+ STRCPY(code, "NFA_LOWER_IC"); break;
+ case NFA_NLOWER_IC:
+ STRCPY(code, "NFA_NLOWER_IC"); break;
+ case NFA_UPPER_IC:
+ STRCPY(code, "NFA_UPPER_IC"); break;
+ case NFA_NUPPER_IC:
+ STRCPY(code, "NFA_NUPPER_IC"); break;
+
+ default:
+ STRCPY(code, "CHAR(x)");
+ code[5] = c;
+ }
+
+ if (addnl == true) {
+ STRCAT(code, " + NEWLINE ");
+ }
+}
+
+static FILE *log_fd;
+static const uint8_t e_log_open_failed[] =
+ N_("Could not open temporary log file for writing, displaying on stderr... ");
+
+// Print the postfix notation of the current regexp.
+static void nfa_postfix_dump(uint8_t *expr, int retval)
+{
+ int *p;
+ FILE *f;
+
+ f = fopen(NFA_REGEXP_DUMP_LOG, "a");
+ if (f == NULL) {
+ return;
+ }
+
+ fprintf(f, "\n-------------------------\n");
+ if (retval == FAIL) {
+ fprintf(f, ">>> NFA engine failed... \n");
+ } else if (retval == OK) {
+ fprintf(f, ">>> NFA engine succeeded !\n");
+ }
+ fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr);
+ for (p = post_start; *p && p < post_ptr; p++) {
+ nfa_set_code(*p);
+ fprintf(f, "%s, ", code);
+ }
+ fprintf(f, "\"\nPostfix notation (int): ");
+ for (p = post_start; *p && p < post_ptr; p++) {
+ fprintf(f, "%d ", *p);
+ }
+ fprintf(f, "\n\n");
+ fclose(f);
+}
+
+// Print the NFA starting with a root node "state".
+static void nfa_print_state(FILE *debugf, nfa_state_T *state)
+{
+ garray_T indent;
+
+ ga_init(&indent, 1, 64);
+ ga_append(&indent, '\0');
+ nfa_print_state2(debugf, state, &indent);
+ ga_clear(&indent);
+}
+
+static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent)
+{
+ uint8_t *p;
+
+ if (state == NULL) {
+ return;
+ }
+
+ fprintf(debugf, "(%2d)", abs(state->id));
+
+ // Output indent
+ p = (uint8_t *)indent->ga_data;
+ if (indent->ga_len >= 3) {
+ int last = indent->ga_len - 3;
+ uint8_t save[2];
+
+ strncpy(save, &p[last], 2); // NOLINT(runtime/printf)
+ memcpy(&p[last], "+-", 2);
+ fprintf(debugf, " %s", p);
+ strncpy(&p[last], save, 2); // NOLINT(runtime/printf)
+ } else {
+ fprintf(debugf, " %s", p);
+ }
+
+ nfa_set_code(state->c);
+ fprintf(debugf, "%s (%d) (id=%d) val=%d\n",
+ code,
+ state->c,
+ abs(state->id),
+ state->val);
+ if (state->id < 0) {
+ return;
+ }
+
+ state->id = abs(state->id) * -1;
+
+ // grow indent for state->out
+ indent->ga_len -= 1;
+ if (state->out1) {
+ ga_concat(indent, (uint8_t *)"| ");
+ } else {
+ ga_concat(indent, (uint8_t *)" ");
+ }
+ ga_append(indent, NUL);
+
+ nfa_print_state2(debugf, state->out, indent);
+
+ // replace last part of indent for state->out1
+ indent->ga_len -= 3;
+ ga_concat(indent, (uint8_t *)" ");
+ ga_append(indent, NUL);
+
+ nfa_print_state2(debugf, state->out1, indent);
+
+ // shrink indent
+ indent->ga_len -= 3;
+ ga_append(indent, NUL);
+}
+
+// Print the NFA state machine.
+static void nfa_dump(nfa_regprog_T *prog)
+{
+ FILE *debugf = fopen(NFA_REGEXP_DUMP_LOG, "a");
+
+ if (debugf == NULL) {
+ return;
+ }
+
+ nfa_print_state(debugf, prog->start);
+
+ if (prog->reganch) {
+ fprintf(debugf, "reganch: %d\n", prog->reganch);
+ }
+ if (prog->regstart != NUL) {
+ fprintf(debugf, "regstart: %c (decimal: %d)\n",
+ prog->regstart, prog->regstart);
+ }
+ if (prog->match_text != NULL) {
+ fprintf(debugf, "match_text: \"%s\"\n", prog->match_text);
+ }
+
+ fclose(debugf);
+}
+#endif // REGEXP_DEBUG
+
+// Parse r.e. @expr and convert it into postfix form.
+// Return the postfix string on success, NULL otherwise.
+static int *re2post(void)
+{
+ if (nfa_reg(REG_NOPAREN) == FAIL) {
+ return NULL;
+ }
+ EMIT(NFA_MOPEN);
+ return post_start;
+}
+
+// NB. Some of the code below is inspired by Russ's.
+
+// Represents an NFA state plus zero or one or two arrows exiting.
+// if c == MATCH, no arrows out; matching state.
+// If c == SPLIT, unlabeled arrows to out and out1 (if != NULL).
+// If c < 256, labeled arrow with character c to out.
+
+static nfa_state_T *state_ptr; // points to nfa_prog->state
+
+// Allocate and initialize nfa_state_T.
+static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1)
+{
+ nfa_state_T *s;
+
+ if (istate >= nstate) {
+ return NULL;
+ }
+
+ s = &state_ptr[istate++];
+
+ s->c = c;
+ s->out = out;
+ s->out1 = out1;
+ s->val = 0;
+
+ s->id = istate;
+ s->lastlist[0] = 0;
+ s->lastlist[1] = 0;
+
+ return s;
+}
+
+// A partially built NFA without the matching state filled in.
+// Frag_T.start points at the start state.
+// Frag_T.out is a list of places that need to be set to the
+// next state for this fragment.
+
+// Initialize a Frag_T struct and return it.
+static Frag_T frag(nfa_state_T *start, Ptrlist *out)
+{
+ Frag_T n;
+
+ n.start = start;
+ n.out = out;
+ return n;
+}
+
+// Create singleton list containing just outp.
+static Ptrlist *list1(nfa_state_T **outp)
+{
+ Ptrlist *l;
+
+ l = (Ptrlist *)outp;
+ l->next = NULL;
+ return l;
+}
+
+// Patch the list of states at out to point to start.
+static void patch(Ptrlist *l, nfa_state_T *s)
+{
+ Ptrlist *next;
+
+ for (; l; l = next) {
+ next = l->next;
+ l->s = s;
+ }
+}
+
+// Join the two lists l1 and l2, returning the combination.
+static Ptrlist *append(Ptrlist *l1, Ptrlist *l2)
+{
+ Ptrlist *oldl1;
+
+ oldl1 = l1;
+ while (l1->next) {
+ l1 = l1->next;
+ }
+ l1->next = l2;
+ return oldl1;
+}
+
+// Stack used for transforming postfix form into NFA.
+static Frag_T empty;
+
+static void st_error(int *postfix, int *end, int *p)
+{
+#ifdef NFA_REGEXP_ERROR_LOG
+ FILE *df;
+ int *p2;
+
+ df = fopen(NFA_REGEXP_ERROR_LOG, "a");
+ if (df) {
+ fprintf(df, "Error popping the stack!\n");
+# ifdef REGEXP_DEBUG
+ fprintf(df, "Current regexp is \"%s\"\n", nfa_regengine.expr);
+# endif
+ fprintf(df, "Postfix form is: ");
+# ifdef REGEXP_DEBUG
+ for (p2 = postfix; p2 < end; p2++) {
+ nfa_set_code(*p2);
+ fprintf(df, "%s, ", code);
+ }
+ nfa_set_code(*p);
+ fprintf(df, "\nCurrent position is: ");
+ for (p2 = postfix; p2 <= p; p2++) {
+ nfa_set_code(*p2);
+ fprintf(df, "%s, ", code);
+ }
+# else
+ for (p2 = postfix; p2 < end; p2++) {
+ fprintf(df, "%d, ", *p2);
+ }
+ fprintf(df, "\nCurrent position is: ");
+ for (p2 = postfix; p2 <= p; p2++) {
+ fprintf(df, "%d, ", *p2);
+ }
+# endif
+ fprintf(df, "\n--------------------------\n");
+ fclose(df);
+ }
+#endif
+ emsg(_("E874: (NFA) Could not pop the stack!"));
+}
+
+// Push an item onto the stack.
+static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end)
+{
+ Frag_T *stackp = *p;
+
+ if (stackp >= stack_end) {
+ return;
+ }
+ *stackp = s;
+ *p = *p + 1;
+}
+
+// Pop an item from the stack.
+static Frag_T st_pop(Frag_T **p, Frag_T *stack)
+{
+ Frag_T *stackp;
+
+ *p = *p - 1;
+ stackp = *p;
+ if (stackp < stack) {
+ return empty;
+ }
+ return **p;
+}
+
+// Estimate the maximum byte length of anything matching "state".
+// When unknown or unlimited return -1.
+static int nfa_max_width(nfa_state_T *startstate, int depth)
+{
+ int l, r;
+ nfa_state_T *state = startstate;
+ int len = 0;
+
+ // detect looping in a NFA_SPLIT
+ if (depth > 4) {
+ return -1;
+ }
+
+ while (state != NULL) {
+ switch (state->c) {
+ case NFA_END_INVISIBLE:
+ case NFA_END_INVISIBLE_NEG:
+ // the end, return what we have
+ return len;
+
+ case NFA_SPLIT:
+ // two alternatives, use the maximum
+ l = nfa_max_width(state->out, depth + 1);
+ r = nfa_max_width(state->out1, depth + 1);
+ if (l < 0 || r < 0) {
+ return -1;
+ }
+ return len + (l > r ? l : r);
+
+ case NFA_ANY:
+ case NFA_START_COLL:
+ case NFA_START_NEG_COLL:
+ // Matches some character, including composing chars.
+ len += MB_MAXBYTES;
+ if (state->c != NFA_ANY) {
+ // Skip over the characters.
+ state = state->out1->out;
+ continue;
+ }
+ break;
+
+ case NFA_DIGIT:
+ case NFA_WHITE:
+ case NFA_HEX:
+ case NFA_OCTAL:
+ // ascii
+ len++;
+ break;
+
+ case NFA_IDENT:
+ case NFA_SIDENT:
+ case NFA_KWORD:
+ case NFA_SKWORD:
+ case NFA_FNAME:
+ case NFA_SFNAME:
+ case NFA_PRINT:
+ case NFA_SPRINT:
+ case NFA_NWHITE:
+ case NFA_NDIGIT:
+ case NFA_NHEX:
+ case NFA_NOCTAL:
+ case NFA_WORD:
+ case NFA_NWORD:
+ case NFA_HEAD:
+ case NFA_NHEAD:
+ case NFA_ALPHA:
+ case NFA_NALPHA:
+ case NFA_LOWER:
+ case NFA_NLOWER:
+ case NFA_UPPER:
+ case NFA_NUPPER:
+ case NFA_LOWER_IC:
+ case NFA_NLOWER_IC:
+ case NFA_UPPER_IC:
+ case NFA_NUPPER_IC:
+ case NFA_ANY_COMPOSING:
+ // possibly non-ascii
+ len += 3;
+ break;
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ // zero-width, out1 points to the END state
+ state = state->out1->out;
+ continue;
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ case NFA_NEWL:
+ case NFA_SKIP:
+ // unknown width
+ return -1;
+
+ case NFA_BOL:
+ case NFA_EOL:
+ case NFA_BOF:
+ case NFA_EOF:
+ case NFA_BOW:
+ case NFA_EOW:
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_MCLOSE:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_NOPEN:
+ case NFA_NCLOSE:
+
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT:
+ case NFA_VISUAL:
+ case NFA_LNUM:
+ case NFA_CURSOR:
+ case NFA_COL:
+ case NFA_VCOL:
+ case NFA_MARK:
+
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ case NFA_OPT_CHARS:
+ case NFA_EMPTY:
+ case NFA_START_PATTERN:
+ case NFA_END_PATTERN:
+ case NFA_COMPOSING:
+ case NFA_END_COMPOSING:
+ // zero-width
+ break;
+
+ default:
+ if (state->c < 0) {
+ // don't know what this is
+ return -1;
+ }
+ // normal character
+ len += utf_char2len(state->c);
+ break;
+ }
+
+ // normal way to continue
+ state = state->out;
+ }
+
+ // unrecognized, "cannot happen"
+ return -1;
+}
+
+// Convert a postfix form into its equivalent NFA.
+// Return the NFA start state on success, NULL otherwise.
+static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
+{
+ int *p;
+ int mopen;
+ int mclose;
+ Frag_T *stack = NULL;
+ Frag_T *stackp = NULL;
+ Frag_T *stack_end = NULL;
+ Frag_T e1;
+ Frag_T e2;
+ Frag_T e;
+ nfa_state_T *s;
+ nfa_state_T *s1;
+ nfa_state_T *matchstate;
+ nfa_state_T *ret = NULL;
+
+ if (postfix == NULL) {
+ return NULL;
+ }
+
+#define PUSH(s) st_push((s), &stackp, stack_end)
+#define POP() st_pop(&stackp, stack); \
+ if (stackp < stack) { \
+ st_error(postfix, end, p); \
+ xfree(stack); \
+ return NULL; \
+ }
+
+ if (nfa_calc_size == false) {
+ // Allocate space for the stack. Max states on the stack: "nstate".
+ stack = xmalloc((size_t)(nstate + 1) * sizeof(Frag_T));
+ stackp = stack;
+ stack_end = stack + (nstate + 1);
+ }
+
+ for (p = postfix; p < end; p++) {
+ switch (*p) {
+ case NFA_CONCAT:
+ // Concatenation.
+ // Pay attention: this operator does not exist in the r.e. itself
+ // (it is implicit, really). It is added when r.e. is translated
+ // to postfix form in re2post().
+ if (nfa_calc_size == true) {
+ // nstate += 0;
+ break;
+ }
+ e2 = POP();
+ e1 = POP();
+ patch(e1.out, e2.start);
+ PUSH(frag(e1.start, e2.out));
+ break;
+
+ case NFA_OR:
+ // Alternation
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e2 = POP();
+ e1 = POP();
+ s = alloc_state(NFA_SPLIT, e1.start, e2.start);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, append(e1.out, e2.out)));
+ break;
+
+ case NFA_STAR:
+ // Zero or more, prefer more
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, e.start, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ patch(e.out, s);
+ PUSH(frag(s, list1(&s->out1)));
+ break;
+
+ case NFA_STAR_NONGREEDY:
+ // Zero or more, prefer zero
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, NULL, e.start);
+ if (s == NULL) {
+ goto theend;
+ }
+ patch(e.out, s);
+ PUSH(frag(s, list1(&s->out)));
+ break;
+
+ case NFA_QUEST:
+ // one or zero atoms=> greedy match
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, e.start, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, append(e.out, list1(&s->out1))));
+ break;
+
+ case NFA_QUEST_NONGREEDY:
+ // zero or one atoms => non-greedy match
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, NULL, e.start);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, append(e.out, list1(&s->out))));
+ break;
+
+ case NFA_END_COLL:
+ case NFA_END_NEG_COLL:
+ // On the stack is the sequence starting with NFA_START_COLL or
+ // NFA_START_NEG_COLL and all possible characters. Patch it to
+ // add the output to the start.
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_END_COLL, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ patch(e.out, s);
+ e.start->out1 = s;
+ PUSH(frag(e.start, list1(&s->out)));
+ break;
+
+ case NFA_RANGE:
+ // Before this are two characters, the low and high end of a
+ // range. Turn them into two states with MIN and MAX.
+ if (nfa_calc_size == true) {
+ // nstate += 0;
+ break;
+ }
+ e2 = POP();
+ e1 = POP();
+ e2.start->val = e2.start->c;
+ e2.start->c = NFA_RANGE_MAX;
+ e1.start->val = e1.start->c;
+ e1.start->c = NFA_RANGE_MIN;
+ patch(e1.out, e2.start);
+ PUSH(frag(e1.start, e2.out));
+ break;
+
+ case NFA_EMPTY:
+ // 0-length, used in a repetition with max/min count of 0
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ s = alloc_state(NFA_EMPTY, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, list1(&s->out)));
+ break;
+
+ case NFA_OPT_CHARS: {
+ int n;
+
+ // \%[abc] implemented as:
+ // NFA_SPLIT
+ // +-CHAR(a)
+ // | +-NFA_SPLIT
+ // | +-CHAR(b)
+ // | | +-NFA_SPLIT
+ // | | +-CHAR(c)
+ // | | | +-next
+ // | | +- next
+ // | +- next
+ // +- next
+ n = *++p; // get number of characters
+ if (nfa_calc_size == true) {
+ nstate += n;
+ break;
+ }
+ s = NULL; // avoid compiler warning
+ e1.out = NULL; // stores list with out1's
+ s1 = NULL; // previous NFA_SPLIT to connect to
+ while (n-- > 0) {
+ e = POP(); // get character
+ s = alloc_state(NFA_SPLIT, e.start, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ if (e1.out == NULL) {
+ e1 = e;
+ }
+ patch(e.out, s1);
+ append(e1.out, list1(&s->out1));
+ s1 = s;
+ }
+ PUSH(frag(s, e1.out));
+ break;
+ }
+
+ case NFA_PREV_ATOM_NO_WIDTH:
+ case NFA_PREV_ATOM_NO_WIDTH_NEG:
+ case NFA_PREV_ATOM_JUST_BEFORE:
+ case NFA_PREV_ATOM_JUST_BEFORE_NEG:
+ case NFA_PREV_ATOM_LIKE_PATTERN: {
+ int before = (*p == NFA_PREV_ATOM_JUST_BEFORE
+ || *p == NFA_PREV_ATOM_JUST_BEFORE_NEG);
+ int pattern = (*p == NFA_PREV_ATOM_LIKE_PATTERN);
+ int start_state;
+ int end_state;
+ int n = 0;
+ nfa_state_T *zend;
+ nfa_state_T *skip;
+
+ switch (*p) {
+ case NFA_PREV_ATOM_NO_WIDTH:
+ start_state = NFA_START_INVISIBLE;
+ end_state = NFA_END_INVISIBLE;
+ break;
+ case NFA_PREV_ATOM_NO_WIDTH_NEG:
+ start_state = NFA_START_INVISIBLE_NEG;
+ end_state = NFA_END_INVISIBLE_NEG;
+ break;
+ case NFA_PREV_ATOM_JUST_BEFORE:
+ start_state = NFA_START_INVISIBLE_BEFORE;
+ end_state = NFA_END_INVISIBLE;
+ break;
+ case NFA_PREV_ATOM_JUST_BEFORE_NEG:
+ start_state = NFA_START_INVISIBLE_BEFORE_NEG;
+ end_state = NFA_END_INVISIBLE_NEG;
+ break;
+ default: // NFA_PREV_ATOM_LIKE_PATTERN:
+ start_state = NFA_START_PATTERN;
+ end_state = NFA_END_PATTERN;
+ break;
+ }
+
+ if (before) {
+ n = *++p; // get the count
+ }
+ // The \@= operator: match the preceding atom with zero width.
+ // The \@! operator: no match for the preceding atom.
+ // The \@<= operator: match for the preceding atom.
+ // The \@<! operator: no match for the preceding atom.
+ // Surrounds the preceding atom with START_INVISIBLE and
+ // END_INVISIBLE, similarly to MOPEN.
+
+ if (nfa_calc_size == true) {
+ nstate += pattern ? 4 : 2;
+ break;
+ }
+ e = POP();
+ s1 = alloc_state(end_state, NULL, NULL);
+ if (s1 == NULL) {
+ goto theend;
+ }
+
+ s = alloc_state(start_state, e.start, s1);
+ if (s == NULL) {
+ goto theend;
+ }
+ if (pattern) {
+ // NFA_ZEND -> NFA_END_PATTERN -> NFA_SKIP -> what follows.
+ skip = alloc_state(NFA_SKIP, NULL, NULL);
+ if (skip == NULL) {
+ goto theend;
+ }
+ zend = alloc_state(NFA_ZEND, s1, NULL);
+ if (zend == NULL) {
+ goto theend;
+ }
+ s1->out = skip;
+ patch(e.out, zend);
+ PUSH(frag(s, list1(&skip->out)));
+ } else {
+ patch(e.out, s1);
+ PUSH(frag(s, list1(&s1->out)));
+ if (before) {
+ if (n <= 0) {
+ // See if we can guess the maximum width, it avoids a
+ // lot of pointless tries.
+ n = nfa_max_width(e.start, 0);
+ }
+ s->val = n; // store the count
+ }
+ }
+ break;
+ }
+
+ case NFA_COMPOSING: // char with composing char
+ FALLTHROUGH;
+
+ case NFA_MOPEN: // \( \) Submatch
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN: // \z( \) Submatch
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_NOPEN: // \%( \) "Invisible Submatch"
+ if (nfa_calc_size == true) {
+ nstate += 2;
+ break;
+ }
+
+ mopen = *p;
+ switch (*p) {
+ case NFA_NOPEN:
+ mclose = NFA_NCLOSE; break;
+ case NFA_ZOPEN:
+ mclose = NFA_ZCLOSE; break;
+ case NFA_ZOPEN1:
+ mclose = NFA_ZCLOSE1; break;
+ case NFA_ZOPEN2:
+ mclose = NFA_ZCLOSE2; break;
+ case NFA_ZOPEN3:
+ mclose = NFA_ZCLOSE3; break;
+ case NFA_ZOPEN4:
+ mclose = NFA_ZCLOSE4; break;
+ case NFA_ZOPEN5:
+ mclose = NFA_ZCLOSE5; break;
+ case NFA_ZOPEN6:
+ mclose = NFA_ZCLOSE6; break;
+ case NFA_ZOPEN7:
+ mclose = NFA_ZCLOSE7; break;
+ case NFA_ZOPEN8:
+ mclose = NFA_ZCLOSE8; break;
+ case NFA_ZOPEN9:
+ mclose = NFA_ZCLOSE9; break;
+ case NFA_COMPOSING:
+ mclose = NFA_END_COMPOSING; break;
+ default:
+ // NFA_MOPEN, NFA_MOPEN1 .. NFA_MOPEN9
+ mclose = *p + NSUBEXP;
+ break;
+ }
+
+ // Allow "NFA_MOPEN" as a valid postfix representation for
+ // the empty regexp "". In this case, the NFA will be
+ // NFA_MOPEN -> NFA_MCLOSE. Note that this also allows
+ // empty groups of parenthesis, and empty mbyte chars
+ if (stackp == stack) {
+ s = alloc_state(mopen, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ s1 = alloc_state(mclose, NULL, NULL);
+ if (s1 == NULL) {
+ goto theend;
+ }
+ patch(list1(&s->out), s1);
+ PUSH(frag(s, list1(&s1->out)));
+ break;
+ }
+
+ // At least one node was emitted before NFA_MOPEN, so
+ // at least one node will be between NFA_MOPEN and NFA_MCLOSE
+ e = POP();
+ s = alloc_state(mopen, e.start, NULL); // `('
+ if (s == NULL) {
+ goto theend;
+ }
+
+ s1 = alloc_state(mclose, NULL, NULL); // `)'
+ if (s1 == NULL) {
+ goto theend;
+ }
+ patch(e.out, s1);
+
+ if (mopen == NFA_COMPOSING) {
+ // COMPOSING->out1 = END_COMPOSING
+ patch(list1(&s->out1), s1);
+ }
+
+ PUSH(frag(s, list1(&s1->out)));
+ break;
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ if (nfa_calc_size == true) {
+ nstate += 2;
+ break;
+ }
+ s = alloc_state(*p, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ s1 = alloc_state(NFA_SKIP, NULL, NULL);
+ if (s1 == NULL) {
+ goto theend;
+ }
+ patch(list1(&s->out), s1);
+ PUSH(frag(s, list1(&s1->out)));
+ break;
+
+ case NFA_LNUM:
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_VCOL:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_COL:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_MARK:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT: {
+ int n = *++p; // lnum, col or mark name
+
+ if (nfa_calc_size == true) {
+ nstate += 1;
+ break;
+ }
+ s = alloc_state(p[-1], NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ s->val = n;
+ PUSH(frag(s, list1(&s->out)));
+ break;
+ }
+
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ default:
+ // Operands
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ s = alloc_state(*p, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, list1(&s->out)));
+ break;
+ } // switch(*p)
+ } // for(p = postfix; *p; ++p)
+
+ if (nfa_calc_size == true) {
+ nstate++;
+ goto theend; // Return value when counting size is ignored anyway
+ }
+
+ e = POP();
+ if (stackp != stack) {
+ xfree(stack);
+ EMSG_RET_NULL(_("E875: (NFA regexp) (While converting from postfix to NFA),"
+ "too many states left on stack"));
+ }
+
+ if (istate >= nstate) {
+ xfree(stack);
+ EMSG_RET_NULL(_("E876: (NFA regexp) "
+ "Not enough space to store the whole NFA "));
+ }
+
+ matchstate = &state_ptr[istate++]; // the match state
+ matchstate->c = NFA_MATCH;
+ matchstate->out = matchstate->out1 = NULL;
+ matchstate->id = 0;
+
+ patch(e.out, matchstate);
+ ret = e.start;
+
+theend:
+ xfree(stack);
+ return ret;
+
+#undef POP1
+#undef PUSH1
+#undef POP2
+#undef PUSH2
+#undef POP
+#undef PUSH
+}
+
+// After building the NFA program, inspect it to add optimization hints.
+static void nfa_postprocess(nfa_regprog_T *prog)
+{
+ int i;
+ int c;
+
+ for (i = 0; i < prog->nstate; i++) {
+ c = prog->state[i].c;
+ if (c == NFA_START_INVISIBLE
+ || c == NFA_START_INVISIBLE_NEG
+ || c == NFA_START_INVISIBLE_BEFORE
+ || c == NFA_START_INVISIBLE_BEFORE_NEG) {
+ int directly;
+
+ // Do it directly when what follows is possibly the end of the
+ // match.
+ if (match_follows(prog->state[i].out1->out, 0)) {
+ directly = true;
+ } else {
+ int ch_invisible = failure_chance(prog->state[i].out, 0);
+ int ch_follows = failure_chance(prog->state[i].out1->out, 0);
+
+ // Postpone when the invisible match is expensive or has a
+ // lower chance of failing.
+ if (c == NFA_START_INVISIBLE_BEFORE
+ || c == NFA_START_INVISIBLE_BEFORE_NEG) {
+ // "before" matches are very expensive when
+ // unbounded, always prefer what follows then,
+ // unless what follows will always match.
+ // Otherwise strongly prefer what follows.
+ if (prog->state[i].val <= 0 && ch_follows > 0) {
+ directly = false;
+ } else {
+ directly = ch_follows * 10 < ch_invisible;
+ }
+ } else {
+ // normal invisible, first do the one with the
+ // highest failure chance
+ directly = ch_follows < ch_invisible;
+ }
+ }
+ if (directly) {
+ // switch to the _FIRST state
+ prog->state[i].c++;
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////
+// NFA execution code.
+/////////////////////////////////////////////////////////////////
+
+// Values for done in nfa_pim_T.
+#define NFA_PIM_UNUSED 0 // pim not used
+#define NFA_PIM_TODO 1 // pim not done yet
+#define NFA_PIM_MATCH 2 // pim executed, matches
+#define NFA_PIM_NOMATCH 3 // pim executed, no match
+
+#ifdef REGEXP_DEBUG
+static void log_subsexpr(regsubs_T *subs)
+{
+ log_subexpr(&subs->norm);
+ if (rex.nfa_has_zsubexpr) {
+ log_subexpr(&subs->synt);
+ }
+}
+
+static void log_subexpr(regsub_T *sub)
+{
+ int j;
+
+ for (j = 0; j < sub->in_use; j++) {
+ if (REG_MULTI) {
+ fprintf(log_fd, "*** group %d, start: c=%d, l=%d, end: c=%d, l=%d\n",
+ j,
+ sub->list.multi[j].start_col,
+ (int)sub->list.multi[j].start_lnum,
+ sub->list.multi[j].end_col,
+ (int)sub->list.multi[j].end_lnum);
+ } else {
+ char *s = (char *)sub->list.line[j].start;
+ char *e = (char *)sub->list.line[j].end;
+
+ fprintf(log_fd, "*** group %d, start: \"%s\", end: \"%s\"\n",
+ j,
+ s == NULL ? "NULL" : s,
+ e == NULL ? "NULL" : e);
+ }
+ }
+}
+
+static char *pim_info(const nfa_pim_T *pim)
+{
+ static char buf[30];
+
+ if (pim == NULL || pim->result == NFA_PIM_UNUSED) {
+ buf[0] = NUL;
+ } else {
+ snprintf(buf, sizeof(buf), " PIM col %d",
+ REG_MULTI
+ ? (int)pim->end.pos.col
+ : (int)(pim->end.ptr - rex.input));
+ }
+ return buf;
+}
+
+#endif
+
+// Used during execution: whether a match has been found.
+static int nfa_match;
+static proftime_T *nfa_time_limit;
+static int *nfa_timed_out;
+static int nfa_time_count;
+
+// Copy postponed invisible match info from "from" to "to".
+static void copy_pim(nfa_pim_T *to, nfa_pim_T *from)
+{
+ to->result = from->result;
+ to->state = from->state;
+ copy_sub(&to->subs.norm, &from->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&to->subs.synt, &from->subs.synt);
+ }
+ to->end = from->end;
+}
+
+static void clear_sub(regsub_T *sub)
+{
+ if (REG_MULTI) {
+ // Use 0xff to set lnum to -1
+ memset(sub->list.multi, 0xff, sizeof(struct multipos) * (size_t)rex.nfa_nsubexpr);
+ } else {
+ memset(sub->list.line, 0, sizeof(struct linepos) * (size_t)rex.nfa_nsubexpr);
+ }
+ sub->in_use = 0;
+}
+
+// Copy the submatches from "from" to "to".
+static void copy_sub(regsub_T *to, regsub_T *from)
+{
+ to->in_use = from->in_use;
+ if (from->in_use <= 0) {
+ return;
+ }
+
+ // Copy the match start and end positions.
+ if (REG_MULTI) {
+ memmove(&to->list.multi[0], &from->list.multi[0],
+ sizeof(struct multipos) * (size_t)from->in_use);
+ to->orig_start_col = from->orig_start_col;
+ } else {
+ memmove(&to->list.line[0], &from->list.line[0],
+ sizeof(struct linepos) * (size_t)from->in_use);
+ }
+}
+
+// Like copy_sub() but exclude the main match.
+static void copy_sub_off(regsub_T *to, regsub_T *from)
+{
+ if (to->in_use < from->in_use) {
+ to->in_use = from->in_use;
+ }
+ if (from->in_use <= 1) {
+ return;
+ }
+
+ // Copy the match start and end positions.
+ if (REG_MULTI) {
+ memmove(&to->list.multi[1], &from->list.multi[1],
+ sizeof(struct multipos) * (size_t)(from->in_use - 1));
+ } else {
+ memmove(&to->list.line[1], &from->list.line[1],
+ sizeof(struct linepos) * (size_t)(from->in_use - 1));
+ }
+}
+
+// Like copy_sub() but only do the end of the main match if \ze is present.
+static void copy_ze_off(regsub_T *to, regsub_T *from)
+{
+ if (!rex.nfa_has_zend) {
+ return;
+ }
+
+ if (REG_MULTI) {
+ if (from->list.multi[0].end_lnum >= 0) {
+ to->list.multi[0].end_lnum = from->list.multi[0].end_lnum;
+ to->list.multi[0].end_col = from->list.multi[0].end_col;
+ }
+ } else {
+ if (from->list.line[0].end != NULL) {
+ to->list.line[0].end = from->list.line[0].end;
+ }
+ }
+}
+
+// Return true if "sub1" and "sub2" have the same start positions.
+// When using back-references also check the end position.
+static bool sub_equal(regsub_T *sub1, regsub_T *sub2)
+{
+ int i;
+ int todo;
+ linenr_T s1;
+ linenr_T s2;
+ uint8_t *sp1;
+ uint8_t *sp2;
+
+ todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use;
+ if (REG_MULTI) {
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
+ s1 = sub1->list.multi[i].start_lnum;
+ } else {
+ s1 = -1;
+ }
+ if (i < sub2->in_use) {
+ s2 = sub2->list.multi[i].start_lnum;
+ } else {
+ s2 = -1;
+ }
+ if (s1 != s2) {
+ return false;
+ }
+ if (s1 != -1 && sub1->list.multi[i].start_col
+ != sub2->list.multi[i].start_col) {
+ return false;
+ }
+ if (rex.nfa_has_backref) {
+ if (i < sub1->in_use) {
+ s1 = sub1->list.multi[i].end_lnum;
+ } else {
+ s1 = -1;
+ }
+ if (i < sub2->in_use) {
+ s2 = sub2->list.multi[i].end_lnum;
+ } else {
+ s2 = -1;
+ }
+ if (s1 != s2) {
+ return false;
+ }
+ if (s1 != -1
+ && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) {
+ return false;
+ }
+ }
+ }
+ } else {
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
+ sp1 = sub1->list.line[i].start;
+ } else {
+ sp1 = NULL;
+ }
+ if (i < sub2->in_use) {
+ sp2 = sub2->list.line[i].start;
+ } else {
+ sp2 = NULL;
+ }
+ if (sp1 != sp2) {
+ return false;
+ }
+ if (rex.nfa_has_backref) {
+ if (i < sub1->in_use) {
+ sp1 = sub1->list.line[i].end;
+ } else {
+ sp1 = NULL;
+ }
+ if (i < sub2->in_use) {
+ sp2 = sub2->list.line[i].end;
+ } else {
+ sp2 = NULL;
+ }
+ if (sp1 != sp2) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+#ifdef REGEXP_DEBUG
+static void open_debug_log(TriState result)
+{
+ log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
+ if (log_fd == NULL) {
+ emsg(_(e_log_open_failed));
+ log_fd = stderr;
+ }
+
+ fprintf(log_fd, "****************************\n");
+ fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n");
+ fprintf(log_fd, "MATCH = %s\n", result == kTrue ? "OK" : result == kNone ? "MAYBE" : "FALSE");
+ fprintf(log_fd, "****************************\n");
+}
+
+static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int lid, nfa_pim_T *pim)
+{
+ int col;
+
+ if (sub->in_use <= 0) {
+ col = -1;
+ } else if (REG_MULTI) {
+ col = sub->list.multi[0].start_col;
+ } else {
+ col = (int)(sub->list.line[0].start - rex.line);
+ }
+ nfa_set_code(state->c);
+ if (log_fd == NULL) {
+ open_debug_log(kNone);
+ }
+ fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n",
+ action, abs(state->id), lid, state->c, code, col,
+ pim_info(pim));
+}
+
+#endif
+
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs pointers to subexpressions
+/// @param pim postponed match or NULL
+///
+/// @return true if the same state is already in list "l" with the same
+/// positions as "subs".
+static bool has_state_with_pos(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
+{
+ for (int i = 0; i < l->n; i++) {
+ nfa_thread_T *thread = &l->t[i];
+ if (thread->state->id == state->id
+ && sub_equal(&thread->subs.norm, &subs->norm)
+ && (!rex.nfa_has_zsubexpr
+ || sub_equal(&thread->subs.synt, &subs->synt))
+ && pim_equal(&thread->pim, pim)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Return true if "one" and "two" are equal. That includes when both are not
+// set.
+static bool pim_equal(const nfa_pim_T *one, const nfa_pim_T *two)
+{
+ const bool one_unused = (one == NULL || one->result == NFA_PIM_UNUSED);
+ const bool two_unused = (two == NULL || two->result == NFA_PIM_UNUSED);
+
+ if (one_unused) {
+ // one is unused: equal when two is also unused
+ return two_unused;
+ }
+ if (two_unused) {
+ // one is used and two is not: not equal
+ return false;
+ }
+ // compare the state id
+ if (one->state->id != two->state->id) {
+ return false;
+ }
+ // compare the position
+ if (REG_MULTI) {
+ return one->end.pos.lnum == two->end.pos.lnum
+ && one->end.pos.col == two->end.pos.col;
+ }
+ return one->end.ptr == two->end.ptr;
+}
+
+// Return true if "state" leads to a NFA_MATCH without advancing the input.
+static bool match_follows(const nfa_state_T *startstate, int depth)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const nfa_state_T *state = startstate;
+
+ // avoid too much recursion
+ if (depth > 10) {
+ return false;
+ }
+ while (state != NULL) {
+ switch (state->c) {
+ case NFA_MATCH:
+ case NFA_MCLOSE:
+ case NFA_END_INVISIBLE:
+ case NFA_END_INVISIBLE_NEG:
+ case NFA_END_PATTERN:
+ return true;
+
+ case NFA_SPLIT:
+ return match_follows(state->out, depth + 1)
+ || match_follows(state->out1, depth + 1);
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_FIRST:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+ case NFA_COMPOSING:
+ // skip ahead to next state
+ state = state->out1->out;
+ continue;
+
+ case NFA_ANY:
+ case NFA_ANY_COMPOSING:
+ case NFA_IDENT:
+ case NFA_SIDENT:
+ case NFA_KWORD:
+ case NFA_SKWORD:
+ case NFA_FNAME:
+ case NFA_SFNAME:
+ case NFA_PRINT:
+ case NFA_SPRINT:
+ case NFA_WHITE:
+ case NFA_NWHITE:
+ case NFA_DIGIT:
+ case NFA_NDIGIT:
+ case NFA_HEX:
+ case NFA_NHEX:
+ case NFA_OCTAL:
+ case NFA_NOCTAL:
+ case NFA_WORD:
+ case NFA_NWORD:
+ case NFA_HEAD:
+ case NFA_NHEAD:
+ case NFA_ALPHA:
+ case NFA_NALPHA:
+ case NFA_LOWER:
+ case NFA_NLOWER:
+ case NFA_UPPER:
+ case NFA_NUPPER:
+ case NFA_LOWER_IC:
+ case NFA_NLOWER_IC:
+ case NFA_UPPER_IC:
+ case NFA_NUPPER_IC:
+ case NFA_START_COLL:
+ case NFA_START_NEG_COLL:
+ case NFA_NEWL:
+ // state will advance input
+ return false;
+
+ default:
+ if (state->c > 0) {
+ // state will advance input
+ return false;
+ }
+ // Others: zero-width or possibly zero-width, might still find
+ // a match at the same position, keep looking.
+ break;
+ }
+ state = state->out;
+ }
+ return false;
+}
+
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs pointers to subexpressions
+///
+/// @return true if "state" is already in list "l".
+static bool state_in_list(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (state->lastlist[nfa_ll_index] == l->id) {
+ if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Offset used for "off" by addstate_here().
+#define ADDSTATE_HERE_OFFSET 10
+
+/// Add "state" and possibly what follows to state list ".".
+///
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs_arg pointers to subexpressions
+/// @param pim postponed look-behind match
+/// @param off_arg byte offset, when -1 go to next line
+///
+/// @return "subs_arg", possibly copied into temp_subs.
+/// NULL when recursiveness is too deep.
+static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_arg, nfa_pim_T *pim,
+ int off_arg)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int subidx;
+ int off = off_arg;
+ int add_here = false;
+ int listindex = 0;
+ int k;
+ int found = false;
+ nfa_thread_T *thread;
+ struct multipos save_multipos;
+ int save_in_use;
+ uint8_t *save_ptr;
+ int i;
+ regsub_T *sub;
+ regsubs_T *subs = subs_arg;
+ static regsubs_T temp_subs;
+#ifdef REGEXP_DEBUG
+ int did_print = false;
+#endif
+ static int depth = 0;
+
+ // This function is called recursively. When the depth is too much we run
+ // out of stack and crash, limit recursiveness here.
+ if (++depth >= 5000 || subs == NULL) {
+ depth--;
+ return NULL;
+ }
+
+ if (off_arg <= -ADDSTATE_HERE_OFFSET) {
+ add_here = true;
+ off = 0;
+ listindex = -(off_arg + ADDSTATE_HERE_OFFSET);
+ }
+
+ switch (state->c) {
+ case NFA_NCLOSE:
+ case NFA_MCLOSE:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_MOPEN:
+ case NFA_ZEND:
+ case NFA_SPLIT:
+ case NFA_EMPTY:
+ // These nodes are not added themselves but their "out" and/or
+ // "out1" may be added below.
+ break;
+
+ case NFA_BOL:
+ case NFA_BOF:
+ // "^" won't match past end-of-line, don't bother trying.
+ // Except when at the end of the line, or when we are going to the
+ // next line for a look-behind match.
+ if (rex.input > rex.line
+ && *rex.input != NUL
+ && (nfa_endp == NULL
+ || !REG_MULTI
+ || rex.lnum == nfa_endp->se_u.pos.lnum)) {
+ goto skip_add;
+ }
+ FALLTHROUGH;
+
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZSTART:
+ // These nodes need to be added so that we can bail out when it
+ // was added to this list before at the same position to avoid an
+ // endless loop for "\(\)*"
+
+ default:
+ if (state->lastlist[nfa_ll_index] == l->id && state->c != NFA_SKIP) {
+ // This state is already in the list, don't add it again,
+ // unless it is an MOPEN that is used for a backreference or
+ // when there is a PIM. For NFA_MATCH check the position,
+ // lower position is preferred.
+ if (!rex.nfa_has_backref && pim == NULL && !l->has_pim
+ && state->c != NFA_MATCH) {
+ // When called from addstate_here() do insert before
+ // existing states.
+ if (add_here) {
+ for (k = 0; k < l->n && k < listindex; k++) {
+ if (l->t[k].state->id == state->id) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!add_here || found) {
+skip_add:
+#ifdef REGEXP_DEBUG
+ nfa_set_code(state->c);
+ fprintf(log_fd,
+ "> Not adding state %d to list %d. char %d: %s pim: %s has_pim: %d found: %d\n",
+ abs(state->id), l->id, state->c, code,
+ pim == NULL ? "NULL" : "yes", l->has_pim, found);
+#endif
+ depth--;
+ return subs;
+ }
+ }
+
+ // Do not add the state again when it exists with the same
+ // positions.
+ if (has_state_with_pos(l, state, subs, pim)) {
+ goto skip_add;
+ }
+ }
+
+ // When there are backreferences or PIMs the number of states may
+ // be (a lot) bigger than anticipated.
+ if (l->n == l->len) {
+ const int newlen = l->len * 3 / 2 + 50;
+ const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
+
+ if ((int64_t)(newsize >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ depth--;
+ return NULL;
+ }
+ if (subs != &temp_subs) {
+ // "subs" may point into the current array, need to make a
+ // copy before it becomes invalid.
+ copy_sub(&temp_subs.norm, &subs->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&temp_subs.synt, &subs->synt);
+ }
+ subs = &temp_subs;
+ }
+
+ nfa_thread_T *const newt = xrealloc(l->t, newsize);
+ l->t = newt;
+ l->len = newlen;
+ }
+
+ // add the state to the list
+ state->lastlist[nfa_ll_index] = l->id;
+ thread = &l->t[l->n++];
+ thread->state = state;
+ if (pim == NULL) {
+ thread->pim.result = NFA_PIM_UNUSED;
+ } else {
+ copy_pim(&thread->pim, pim);
+ l->has_pim = true;
+ }
+ copy_sub(&thread->subs.norm, &subs->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&thread->subs.synt, &subs->synt);
+ }
+#ifdef REGEXP_DEBUG
+ report_state("Adding", &thread->subs.norm, state, l->id, pim);
+ did_print = true;
#endif
+ }
+
+#ifdef REGEXP_DEBUG
+ if (!did_print) {
+ report_state("Processing", &subs->norm, state, l->id, pim);
+ }
+#endif
+ switch (state->c) {
+ case NFA_MATCH:
+ break;
+
+ case NFA_SPLIT:
+ // order matters here
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ subs = addstate(l, state->out1, subs, pim, off_arg);
+ break;
+
+ case NFA_EMPTY:
+ case NFA_NOPEN:
+ case NFA_NCLOSE:
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ break;
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_ZSTART:
+ if (state->c == NFA_ZSTART) {
+ subidx = 0;
+ sub = &subs->norm;
+ } else if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) {
+ subidx = state->c - NFA_ZOPEN;
+ sub = &subs->synt;
+ } else {
+ subidx = state->c - NFA_MOPEN;
+ sub = &subs->norm;
+ }
+
+ // avoid compiler warnings
+ save_ptr = NULL;
+ CLEAR_FIELD(save_multipos);
+
+ // Set the position (with "off" added) in the subexpression. Save
+ // and restore it when it was in use. Otherwise fill any gap.
+ if (REG_MULTI) {
+ if (subidx < sub->in_use) {
+ save_multipos = sub->list.multi[subidx];
+ save_in_use = -1;
+ } else {
+ save_in_use = sub->in_use;
+ for (i = sub->in_use; i < subidx; i++) {
+ sub->list.multi[i].start_lnum = -1;
+ sub->list.multi[i].end_lnum = -1;
+ }
+ sub->in_use = subidx + 1;
+ }
+ if (off == -1) {
+ sub->list.multi[subidx].start_lnum = rex.lnum + 1;
+ sub->list.multi[subidx].start_col = 0;
+ } else {
+ sub->list.multi[subidx].start_lnum = rex.lnum;
+ sub->list.multi[subidx].start_col =
+ (colnr_T)(rex.input - rex.line + off);
+ }
+ sub->list.multi[subidx].end_lnum = -1;
+ } else {
+ if (subidx < sub->in_use) {
+ save_ptr = sub->list.line[subidx].start;
+ save_in_use = -1;
+ } else {
+ save_in_use = sub->in_use;
+ for (i = sub->in_use; i < subidx; i++) {
+ sub->list.line[i].start = NULL;
+ sub->list.line[i].end = NULL;
+ }
+ sub->in_use = subidx + 1;
+ }
+ sub->list.line[subidx].start = rex.input + off;
+ }
+
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ if (subs == NULL) {
+ break;
+ }
+ // "subs" may have changed, need to set "sub" again.
+ if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) {
+ sub = &subs->synt;
+ } else {
+ sub = &subs->norm;
+ }
+
+ if (save_in_use == -1) {
+ if (REG_MULTI) {
+ sub->list.multi[subidx] = save_multipos;
+ } else {
+ sub->list.line[subidx].start = save_ptr;
+ }
+ } else {
+ sub->in_use = save_in_use;
+ }
+ break;
+
+ case NFA_MCLOSE:
+ if (rex.nfa_has_zend
+ && (REG_MULTI
+ ? subs->norm.list.multi[0].end_lnum >= 0
+ : subs->norm.list.line[0].end != NULL)) {
+ // Do not overwrite the position set by \ze.
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ break;
+ }
+ FALLTHROUGH;
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_ZEND:
+ if (state->c == NFA_ZEND) {
+ subidx = 0;
+ sub = &subs->norm;
+ } else if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) {
+ subidx = state->c - NFA_ZCLOSE;
+ sub = &subs->synt;
+ } else {
+ subidx = state->c - NFA_MCLOSE;
+ sub = &subs->norm;
+ }
+
+ // We don't fill in gaps here, there must have been an MOPEN that
+ // has done that.
+ save_in_use = sub->in_use;
+ if (sub->in_use <= subidx) {
+ sub->in_use = subidx + 1;
+ }
+ if (REG_MULTI) {
+ save_multipos = sub->list.multi[subidx];
+ if (off == -1) {
+ sub->list.multi[subidx].end_lnum = rex.lnum + 1;
+ sub->list.multi[subidx].end_col = 0;
+ } else {
+ sub->list.multi[subidx].end_lnum = rex.lnum;
+ sub->list.multi[subidx].end_col =
+ (colnr_T)(rex.input - rex.line + off);
+ }
+ // avoid compiler warnings
+ save_ptr = NULL;
+ } else {
+ save_ptr = sub->list.line[subidx].end;
+ sub->list.line[subidx].end = rex.input + off;
+ // avoid compiler warnings
+ CLEAR_FIELD(save_multipos);
+ }
+
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ if (subs == NULL) {
+ break;
+ }
+ // "subs" may have changed, need to set "sub" again.
+ if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) {
+ sub = &subs->synt;
+ } else {
+ sub = &subs->norm;
+ }
+
+ if (REG_MULTI) {
+ sub->list.multi[subidx] = save_multipos;
+ } else {
+ sub->list.line[subidx].end = save_ptr;
+ }
+ sub->in_use = save_in_use;
+ break;
+ }
+ depth--;
+ return subs;
+}
+
+/// Like addstate(), but the new state(s) are put at position "*ip".
+/// Used for zero-width matches, next state to use is the added one.
+/// This makes sure the order of states to be tried does not change, which
+/// matters for alternatives.
+///
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs pointers to subexpressions
+/// @param pim postponed look-behind match
+static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim,
+ int *ip)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 5) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int tlen = l->n;
+ int count;
+ int listidx = *ip;
+
+ // First add the state(s) at the end, so that we know how many there are.
+ // Pass the listidx as offset (avoids adding another argument to
+ // addstate()).
+ regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET);
+ if (r == NULL) {
+ return NULL;
+ }
+
+ // when "*ip" was at the end of the list, nothing to do
+ if (listidx + 1 == tlen) {
+ return r;
+ }
+
+ // re-order to put the new state at the current position
+ count = l->n - tlen;
+ if (count == 0) {
+ return r; // no state got added
+ }
+ if (count == 1) {
+ // overwrite the current state
+ l->t[listidx] = l->t[l->n - 1];
+ } else if (count > 1) {
+ if (l->n + count - 1 >= l->len) {
+ // not enough space to move the new states, reallocate the list
+ // and move the states to the right position
+ const int newlen = l->len * 3 / 2 + 50;
+ const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
+
+ if ((int64_t)(newsize >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ return NULL;
+ }
+ nfa_thread_T *const newl = xmalloc(newsize);
+ l->len = newlen;
+ memmove(&(newl[0]),
+ &(l->t[0]),
+ sizeof(nfa_thread_T) * (size_t)listidx);
+ memmove(&(newl[listidx]),
+ &(l->t[l->n - count]),
+ sizeof(nfa_thread_T) * (size_t)count);
+ memmove(&(newl[listidx + count]),
+ &(l->t[listidx + 1]),
+ sizeof(nfa_thread_T) * (size_t)(l->n - count - listidx - 1));
+ xfree(l->t);
+ l->t = newl;
+ } else {
+ // make space for new states, then move them from the
+ // end to the current position
+ memmove(&(l->t[listidx + count]),
+ &(l->t[listidx + 1]),
+ sizeof(nfa_thread_T) * (size_t)(l->n - listidx - 1));
+ memmove(&(l->t[listidx]),
+ &(l->t[l->n - 1]),
+ sizeof(nfa_thread_T) * (size_t)count);
+ }
+ }
+ l->n--;
+ *ip = listidx - 1;
+
+ return r;
+}
+
+// Check character class "class" against current character c.
+static int check_char_class(int cls, int c)
+{
+ switch (cls) {
+ case NFA_CLASS_ALNUM:
+ if (c >= 1 && c < 128 && isalnum(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_ALPHA:
+ if (c >= 1 && c < 128 && isalpha(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_BLANK:
+ if (c == ' ' || c == '\t') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_CNTRL:
+ if (c >= 1 && c <= 127 && iscntrl(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_DIGIT:
+ if (ascii_isdigit(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_GRAPH:
+ if (c >= 1 && c <= 127 && isgraph(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_LOWER:
+ if (mb_islower(c) && c != 170 && c != 186) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_PRINT:
+ if (vim_isprintc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_PUNCT:
+ if (c >= 1 && c < 128 && ispunct(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_SPACE:
+ if ((c >= 9 && c <= 13) || (c == ' ')) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_UPPER:
+ if (mb_isupper(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_XDIGIT:
+ if (ascii_isxdigit(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_TAB:
+ if (c == '\t') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_RETURN:
+ if (c == '\r') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_BACKSPACE:
+ if (c == '\b') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_ESCAPE:
+ if (c == ESC) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_IDENT:
+ if (vim_isIDc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_KEYWORD:
+ if (reg_iswordc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_FNAME:
+ if (vim_isfilec(c)) {
+ return OK;
+ }
+ break;
+
+ default:
+ // should not be here :P
+ siemsg(_(e_ill_char_class), (int64_t)cls);
+ return FAIL;
+ }
+ return FAIL;
+}
+
+/// Check for a match with subexpression "subidx".
+///
+/// @param sub pointers to subexpressions
+/// @param bytelen out: length of match in bytes
+///
+/// @return true if it matches.
+static int match_backref(regsub_T *sub, int subidx, int *bytelen)
+{
+ int len;
+
+ if (sub->in_use <= subidx) {
+retempty:
+ // backref was not set, match an empty string
+ *bytelen = 0;
+ return true;
+ }
+
+ if (REG_MULTI) {
+ if (sub->list.multi[subidx].start_lnum < 0
+ || sub->list.multi[subidx].end_lnum < 0) {
+ goto retempty;
+ }
+ if (sub->list.multi[subidx].start_lnum == rex.lnum
+ && sub->list.multi[subidx].end_lnum == rex.lnum) {
+ len = sub->list.multi[subidx].end_col
+ - sub->list.multi[subidx].start_col;
+ if (cstrncmp((char *)rex.line + sub->list.multi[subidx].start_col,
+ (char *)rex.input, &len) == 0) {
+ *bytelen = len;
+ return true;
+ }
+ } else {
+ if (match_with_backref(sub->list.multi[subidx].start_lnum,
+ sub->list.multi[subidx].start_col,
+ sub->list.multi[subidx].end_lnum,
+ sub->list.multi[subidx].end_col,
+ bytelen) == RA_MATCH) {
+ return true;
+ }
+ }
+ } else {
+ if (sub->list.line[subidx].start == NULL
+ || sub->list.line[subidx].end == NULL) {
+ goto retempty;
+ }
+ len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start);
+ if (cstrncmp((char *)sub->list.line[subidx].start, (char *)rex.input, &len) == 0) {
+ *bytelen = len;
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Check for a match with \z subexpression "subidx".
+///
+/// @param bytelen out: length of match in bytes
+///
+/// @return true if it matches.
+static int match_zref(int subidx, int *bytelen)
+{
+ int len;
+
+ cleanup_zsubexpr();
+ if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) {
+ // backref was not set, match an empty string
+ *bytelen = 0;
+ return true;
+ }
+
+ len = (int)strlen((char *)re_extmatch_in->matches[subidx]);
+ if (cstrncmp((char *)re_extmatch_in->matches[subidx], (char *)rex.input, &len) == 0) {
+ *bytelen = len;
+ return true;
+ }
+ return false;
+}
+
+// Save list IDs for all NFA states of "prog" into "list".
+// Also reset the IDs to zero.
+// Only used for the recursive value lastlist[1].
+static void nfa_save_listids(nfa_regprog_T *prog, int *list)
+{
+ int i;
+ nfa_state_T *p;
+
+ // Order in the list is reverse, it's a bit faster that way.
+ p = &prog->state[0];
+ for (i = prog->nstate; --i >= 0;) {
+ list[i] = p->lastlist[1];
+ p->lastlist[1] = 0;
+ p++;
+ }
+}
+
+// Restore list IDs from "list" to all NFA states.
+static void nfa_restore_listids(nfa_regprog_T *prog, const int *list)
+{
+ int i;
+ nfa_state_T *p;
+
+ p = &prog->state[0];
+ for (i = prog->nstate; --i >= 0;) {
+ p->lastlist[1] = list[i];
+ p++;
+ }
+}
+
+static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos)
+{
+ if (op == 1) {
+ return pos > val;
+ }
+ if (op == 2) {
+ return pos < val;
+ }
+ return val == pos;
+}
+
+// Recursively call nfa_regmatch()
+// "pim" is NULL or contains info about a Postponed Invisible Match (start
+// position).
+static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog,
+ regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7)
+{
+ const int save_reginput_col = (int)(rex.input - rex.line);
+ const int save_reglnum = rex.lnum;
+ const int save_nfa_match = nfa_match;
+ const int save_nfa_listid = rex.nfa_listid;
+ save_se_T *const save_nfa_endp = nfa_endp;
+ save_se_T endpos;
+ save_se_T *endposp = NULL;
+ int need_restore = false;
+
+ if (pim != NULL) {
+ // start at the position where the postponed match was
+ if (REG_MULTI) {
+ rex.input = rex.line + pim->end.pos.col;
+ } else {
+ rex.input = pim->end.ptr;
+ }
+ }
+
+ if (state->c == NFA_START_INVISIBLE_BEFORE
+ || state->c == NFA_START_INVISIBLE_BEFORE_FIRST
+ || state->c == NFA_START_INVISIBLE_BEFORE_NEG
+ || state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
+ // The recursive match must end at the current position. When "pim" is
+ // not NULL it specifies the current position.
+ endposp = &endpos;
+ if (REG_MULTI) {
+ if (pim == NULL) {
+ endpos.se_u.pos.col = (int)(rex.input - rex.line);
+ endpos.se_u.pos.lnum = rex.lnum;
+ } else {
+ endpos.se_u.pos = pim->end.pos;
+ }
+ } else {
+ if (pim == NULL) {
+ endpos.se_u.ptr = rex.input;
+ } else {
+ endpos.se_u.ptr = pim->end.ptr;
+ }
+ }
+
+ // Go back the specified number of bytes, or as far as the
+ // start of the previous line, to try matching "\@<=" or
+ // not matching "\@<!". This is very inefficient, limit the number of
+ // bytes if possible.
+ if (state->val <= 0) {
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = (uint8_t *)reg_getline(++rex.lnum);
+ }
+ }
+ rex.input = rex.line;
+ } else {
+ if (REG_MULTI && (int)(rex.input - rex.line) < state->val) {
+ // Not enough bytes in this line, go to end of
+ // previous line.
+ rex.line = (uint8_t *)reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = (uint8_t *)reg_getline(++rex.lnum);
+ rex.input = rex.line;
+ } else {
+ rex.input = rex.line + strlen((char *)rex.line);
+ }
+ }
+ if ((int)(rex.input - rex.line) >= state->val) {
+ rex.input -= state->val;
+ rex.input -= utf_head_off((char *)rex.line, (char *)rex.input);
+ } else {
+ rex.input = rex.line;
+ }
+ }
+ }
+
+#ifdef REGEXP_DEBUG
+ if (log_fd != stderr) {
+ fclose(log_fd);
+ }
+ log_fd = NULL;
+#endif
+ // Have to clear the lastlist field of the NFA nodes, so that
+ // nfa_regmatch() and addstate() can run properly after recursion.
+ if (nfa_ll_index == 1) {
+ // Already calling nfa_regmatch() recursively. Save the lastlist[1]
+ // values and clear them.
+ if (*listids == NULL || *listids_len < prog->nstate) {
+ xfree(*listids);
+ *listids = xmalloc(sizeof(**listids) * (size_t)prog->nstate);
+ *listids_len = prog->nstate;
+ }
+ nfa_save_listids(prog, *listids);
+ need_restore = true;
+ // any value of rex.nfa_listid will do
+ } else {
+ // First recursive nfa_regmatch() call, switch to the second lastlist
+ // entry. Make sure rex.nfa_listid is different from a previous
+ // recursive call, because some states may still have this ID.
+ nfa_ll_index++;
+ if (rex.nfa_listid <= rex.nfa_alt_listid) {
+ rex.nfa_listid = rex.nfa_alt_listid;
+ }
+ }
+
+ // Call nfa_regmatch() to check if the current concat matches at this
+ // position. The concat ends with the node NFA_END_INVISIBLE
+ nfa_endp = endposp;
+ const int result = nfa_regmatch(prog, state->out, submatch, m);
+
+ if (need_restore) {
+ nfa_restore_listids(prog, *listids);
+ } else {
+ nfa_ll_index--;
+ rex.nfa_alt_listid = rex.nfa_listid;
+ }
+
+ // restore position in input text
+ rex.lnum = save_reglnum;
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ }
+ rex.input = rex.line + save_reginput_col;
+ if (result != NFA_TOO_EXPENSIVE) {
+ nfa_match = save_nfa_match;
+ rex.nfa_listid = save_nfa_listid;
+ }
+ nfa_endp = save_nfa_endp;
+
+#ifdef REGEXP_DEBUG
+ open_debug_log(result);
+#endif
+
+ return result;
+}
+
+// Estimate the chance of a match with "state" failing.
+// empty match: 0
+// NFA_ANY: 1
+// specific character: 99
+static int failure_chance(nfa_state_T *state, int depth)
+{
+ int c = state->c;
+ int l, r;
+
+ // detect looping
+ if (depth > 4) {
+ return 1;
+ }
+
+ switch (c) {
+ case NFA_SPLIT:
+ if (state->out->c == NFA_SPLIT || state->out1->c == NFA_SPLIT) {
+ // avoid recursive stuff
+ return 1;
+ }
+ // two alternatives, use the lowest failure chance
+ l = failure_chance(state->out, depth + 1);
+ r = failure_chance(state->out1, depth + 1);
+ return l < r ? l : r;
+
+ case NFA_ANY:
+ // matches anything, unlikely to fail
+ return 1;
+
+ case NFA_MATCH:
+ case NFA_MCLOSE:
+ case NFA_ANY_COMPOSING:
+ // empty match works always
+ return 0;
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_FIRST:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+ case NFA_START_PATTERN:
+ // recursive regmatch is expensive, use low failure chance
+ return 5;
+
+ case NFA_BOL:
+ case NFA_EOL:
+ case NFA_BOF:
+ case NFA_EOF:
+ case NFA_NEWL:
+ return 99;
+
+ case NFA_BOW:
+ case NFA_EOW:
+ return 90;
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_NOPEN:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_NCLOSE:
+ return failure_chance(state->out, depth + 1);
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ // backreferences don't match in many places
+ return 94;
+
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT:
+ case NFA_VISUAL:
+ // before/after positions don't match very often
+ return 85;
+
+ case NFA_LNUM:
+ return 90;
+
+ case NFA_CURSOR:
+ case NFA_COL:
+ case NFA_VCOL:
+ case NFA_MARK:
+ // specific positions rarely match
+ return 98;
+
+ case NFA_COMPOSING:
+ return 95;
+
+ default:
+ if (c > 0) {
+ // character match fails often
+ return 95;
+ }
+ }
+
+ // something else, includes character classes
+ return 50;
+}
+
+// Skip until the char "c" we know a match must start with.
+static int skip_to_start(int c, colnr_T *colp)
+{
+ const uint8_t *const s = (uint8_t *)cstrchr((char *)rex.line + *colp, c);
+ if (s == NULL) {
+ return FAIL;
+ }
+ *colp = (int)(s - rex.line);
+ return OK;
+}
+
+// Check for a match with match_text.
+// Called after skip_to_start() has found regstart.
+// Returns zero for no match, 1 for a match.
+static int find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text)
+{
+#define PTR2LEN(x) utf_ptr2len(x)
+
+ colnr_T col = *startcol;
+ int regstart_len = PTR2LEN((char *)rex.line + col);
+
+ while (true) {
+ bool match = true;
+ uint8_t *s1 = match_text;
+ uint8_t *s2 = rex.line + col + regstart_len; // skip regstart
+ while (*s1) {
+ int c1_len = PTR2LEN((char *)s1);
+ int c1 = utf_ptr2char((char *)s1);
+ int c2_len = PTR2LEN((char *)s2);
+ int c2 = utf_ptr2char((char *)s2);
+
+ if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2)))
+ || c1_len != c2_len) {
+ match = false;
+ break;
+ }
+ s1 += c1_len;
+ s2 += c2_len;
+ }
+ if (match
+ // check that no composing char follows
+ && !utf_iscomposing(utf_ptr2char((char *)s2))) {
+ cleanup_subexpr();
+ if (REG_MULTI) {
+ rex.reg_startpos[0].lnum = rex.lnum;
+ rex.reg_startpos[0].col = col;
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (colnr_T)(s2 - rex.line);
+ } else {
+ rex.reg_startp[0] = rex.line + col;
+ rex.reg_endp[0] = s2;
+ }
+ *startcol = col;
+ return 1L;
+ }
+
+ // Try finding regstart after the current match.
+ col += regstart_len; // skip regstart
+ if (skip_to_start(regstart, &col) == FAIL) {
+ break;
+ }
+ }
+
+ *startcol = col;
+ return 0L;
+
+#undef PTR2LEN
+}
+
+static int nfa_did_time_out(void)
+{
+ if (nfa_time_limit != NULL && profile_passed_limit(*nfa_time_limit)) {
+ if (nfa_timed_out != NULL) {
+ *nfa_timed_out = true;
+ }
+ return true;
+ }
+ return false;
+}
+
+/// Main matching routine.
+///
+/// Run NFA to determine whether it matches rex.input.
+///
+/// When "nfa_endp" is not NULL it is a required end-of-match position.
+///
+/// Return true if there is a match, false if there is no match,
+/// NFA_TOO_EXPENSIVE if we end up with too many states.
+/// When there is a match "submatch" contains the positions.
+///
+/// Note: Caller must ensure that: start != NULL.
+static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *submatch, regsubs_T *m)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4)
+{
+ int result = false;
+ int flag = 0;
+ bool go_to_nextline = false;
+ nfa_thread_T *t;
+ nfa_list_T list[2];
+ int listidx;
+ nfa_list_T *thislist;
+ nfa_list_T *nextlist;
+ int *listids = NULL;
+ int listids_len = 0;
+ nfa_state_T *add_state;
+ bool add_here;
+ int add_count;
+ int add_off = 0;
+ int toplevel = start->c == NFA_MOPEN;
+ regsubs_T *r;
+ // Some patterns may take a long time to match, especially when using
+ // recursive_regmatch(). Allow interrupting them with CTRL-C.
+ reg_breakcheck();
+ if (got_int) {
+ return false;
+ }
+ if (nfa_did_time_out()) {
+ return false;
+ }
+
+#ifdef NFA_REGEXP_DEBUG_LOG
+ FILE *debug = fopen(NFA_REGEXP_DEBUG_LOG, "a");
+
+ if (debug == NULL) {
+ semsg("(NFA) COULD NOT OPEN %s!", NFA_REGEXP_DEBUG_LOG);
+ return false;
+ }
+#endif
+ nfa_match = false;
+
+ // Allocate memory for the lists of nodes.
+ size_t size = (size_t)(prog->nstate + 1) * sizeof(nfa_thread_T);
+ list[0].t = xmalloc(size);
+ list[0].len = prog->nstate + 1;
+ list[1].t = xmalloc(size);
+ list[1].len = prog->nstate + 1;
+
+#ifdef REGEXP_DEBUG
+ log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
+ if (log_fd == NULL) {
+ emsg(_(e_log_open_failed));
+ log_fd = stderr;
+ }
+ fprintf(log_fd, "**********************************\n");
+ nfa_set_code(start->c);
+ fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n",
+ abs(start->id), code);
+ fprintf(log_fd, "**********************************\n");
+#endif
+
+ thislist = &list[0];
+ thislist->n = 0;
+ thislist->has_pim = false;
+ nextlist = &list[1];
+ nextlist->n = 0;
+ nextlist->has_pim = false;
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "(---) STARTSTATE first\n");
+#endif
+ thislist->id = rex.nfa_listid + 1;
+
+ // Inline optimized code for addstate(thislist, start, m, 0) if we know
+ // it's the first MOPEN.
+ if (toplevel) {
+ if (REG_MULTI) {
+ m->norm.list.multi[0].start_lnum = rex.lnum;
+ m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line);
+ m->norm.orig_start_col = m->norm.list.multi[0].start_col;
+ } else {
+ m->norm.list.line[0].start = rex.input;
+ }
+ m->norm.in_use = 1;
+ r = addstate(thislist, start->out, m, NULL, 0);
+ } else {
+ r = addstate(thislist, start, m, NULL, 0);
+ }
+ if (r == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+
+#define ADD_STATE_IF_MATCH(state) \
+ if (result) { \
+ add_state = (state)->out; \
+ add_off = clen; \
+ }
+
+ // Run for each character.
+ while (true) {
+ int curc = utf_ptr2char((char *)rex.input);
+ int clen = utfc_ptr2len((char *)rex.input);
+ if (curc == NUL) {
+ clen = 0;
+ go_to_nextline = false;
+ }
+
+ // swap lists
+ thislist = &list[flag];
+ nextlist = &list[flag ^= 1];
+ nextlist->n = 0; // clear nextlist
+ nextlist->has_pim = false;
+ rex.nfa_listid++;
+ if (prog->re_engine == AUTOMATIC_ENGINE
+ && (rex.nfa_listid >= NFA_MAX_STATES)) {
+ // Too many states, retry with old engine.
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+
+ thislist->id = rex.nfa_listid;
+ nextlist->id = rex.nfa_listid + 1;
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "------------------------------------------\n");
+ fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input);
+ fprintf(log_fd,
+ ">>> Advanced one character... Current char is %c (code %d) \n",
+ curc,
+ (int)curc);
+ fprintf(log_fd, ">>> Thislist has %d states available: ", thislist->n);
+ {
+ int i;
+
+ for (i = 0; i < thislist->n; i++) {
+ fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
+ }
+ }
+ fprintf(log_fd, "\n");
+#endif
+
+#ifdef NFA_REGEXP_DEBUG_LOG
+ fprintf(debug, "\n-------------------\n");
+#endif
+ // If the state lists are empty we can stop.
+ if (thislist->n == 0) {
+ break;
+ }
+
+ // compute nextlist
+ for (listidx = 0; listidx < thislist->n; listidx++) {
+ // If the list gets very long there probably is something wrong.
+ // At least allow interrupting with CTRL-C.
+ reg_breakcheck();
+ if (got_int) {
+ break;
+ }
+ if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
+ nfa_time_count = 0;
+ if (nfa_did_time_out()) {
+ break;
+ }
+ }
+ t = &thislist->t[listidx];
+
+#ifdef NFA_REGEXP_DEBUG_LOG
+ nfa_set_code(t->state->c);
+ fprintf(debug, "%s, ", code);
+#endif
+#ifdef REGEXP_DEBUG
+ {
+ int col;
+
+ if (t->subs.norm.in_use <= 0) {
+ col = -1;
+ } else if (REG_MULTI) {
+ col = t->subs.norm.list.multi[0].start_col;
+ } else {
+ col = (int)(t->subs.norm.list.line[0].start - rex.line);
+ }
+ nfa_set_code(t->state->c);
+ fprintf(log_fd, "(%d) char %d %s (start col %d)%s... \n",
+ abs(t->state->id), (int)t->state->c, code, col,
+ pim_info(&t->pim));
+ }
+#endif
+
+ // Handle the possible codes of the current state.
+ // The most important is NFA_MATCH.
+ add_state = NULL;
+ add_here = false;
+ add_count = 0;
+ switch (t->state->c) {
+ case NFA_MATCH:
+ // If the match is not at the start of the line, ends before a
+ // composing characters and rex.reg_icombine is not set, that
+ // is not really a match.
+ if (!rex.reg_icombine
+ && rex.input != rex.line
+ && utf_iscomposing(curc)) {
+ break;
+ }
+ nfa_match = true;
+ copy_sub(&submatch->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&submatch->synt, &t->subs.synt);
+ }
+#ifdef REGEXP_DEBUG
+ log_subsexpr(&t->subs);
+#endif
+ // Found the left-most longest match, do not look at any other
+ // states at this position. When the list of states is going
+ // to be empty quit without advancing, so that "rex.input" is
+ // correct.
+ if (nextlist->n == 0) {
+ clen = 0;
+ }
+ goto nextchar;
+
+ case NFA_END_INVISIBLE:
+ case NFA_END_INVISIBLE_NEG:
+ case NFA_END_PATTERN:
+ // This is only encountered after a NFA_START_INVISIBLE or
+ // NFA_START_INVISIBLE_BEFORE node.
+ // They surround a zero-width group, used with "\@=", "\&",
+ // "\@!", "\@<=" and "\@<!".
+ // If we got here, it means that the current "invisible" group
+ // finished successfully, so return control to the parent
+ // nfa_regmatch(). For a look-behind match only when it ends
+ // in the position in "nfa_endp".
+ // Submatches are stored in *m, and used in the parent call.
+#ifdef REGEXP_DEBUG
+ if (nfa_endp != NULL) {
+ if (REG_MULTI) {
+ fprintf(log_fd,
+ "Current lnum: %d, endp lnum: %d;"
+ " current col: %d, endp col: %d\n",
+ (int)rex.lnum,
+ (int)nfa_endp->se_u.pos.lnum,
+ (int)(rex.input - rex.line),
+ nfa_endp->se_u.pos.col);
+ } else {
+ fprintf(log_fd, "Current col: %d, endp col: %d\n",
+ (int)(rex.input - rex.line),
+ (int)(nfa_endp->se_u.ptr - rex.input));
+ }
+ }
+#endif
+ // If "nfa_endp" is set it's only a match if it ends at
+ // "nfa_endp"
+ if (nfa_endp != NULL
+ && (REG_MULTI
+ ? (rex.lnum != nfa_endp->se_u.pos.lnum
+ || (int)(rex.input - rex.line) != nfa_endp->se_u.pos.col)
+ : rex.input != nfa_endp->se_u.ptr)) {
+ break;
+ }
+ // do not set submatches for \@!
+ if (t->state->c != NFA_END_INVISIBLE_NEG) {
+ copy_sub(&m->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&m->synt, &t->subs.synt);
+ }
+ }
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "Match found:\n");
+ log_subsexpr(m);
+#endif
+ nfa_match = true;
+ // See comment above at "goto nextchar".
+ if (nextlist->n == 0) {
+ clen = 0;
+ }
+ goto nextchar;
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_FIRST:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "Failure chance invisible: %d, what follows: %d\n",
+ failure_chance(t->state->out, 0),
+ failure_chance(t->state->out1->out, 0));
+#endif
+ // Do it directly if there already is a PIM or when
+ // nfa_postprocess() detected it will work better.
+ if (t->pim.result != NFA_PIM_UNUSED
+ || t->state->c == NFA_START_INVISIBLE_FIRST
+ || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || t->state->c == NFA_START_INVISIBLE_BEFORE_FIRST
+ || t->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
+ int in_use = m->norm.in_use;
+
+ // Copy submatch info for the recursive call, opposite
+ // of what happens on success below.
+ copy_sub_off(&m->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&m->synt, &t->subs.synt);
+ }
+ // First try matching the invisible match, then what
+ // follows.
+ result = recursive_regmatch(t->state, NULL, prog, submatch, m,
+ &listids, &listids_len);
+ if (result == NFA_TOO_EXPENSIVE) {
+ nfa_match = result;
+ goto theend;
+ }
+
+ // for \@! and \@<! it is a match when the result is
+ // false
+ if (result != (t->state->c == NFA_START_INVISIBLE_NEG
+ || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || t->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG
+ || t->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
+ // Copy submatch info from the recursive call
+ copy_sub_off(&t->subs.norm, &m->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&t->subs.synt, &m->synt);
+ }
+ // If the pattern has \ze and it matched in the
+ // sub pattern, use it.
+ copy_ze_off(&t->subs.norm, &m->norm);
+
+ // t->state->out1 is the corresponding
+ // END_INVISIBLE node; Add its out to the current
+ // list (zero-width match).
+ add_here = true;
+ add_state = t->state->out1->out;
+ }
+ m->norm.in_use = in_use;
+ } else {
+ nfa_pim_T pim;
+
+ // First try matching what follows. Only if a match
+ // is found verify the invisible match matches. Add a
+ // nfa_pim_T to the following states, it contains info
+ // about the invisible match.
+ pim.state = t->state;
+ pim.result = NFA_PIM_TODO;
+ pim.subs.norm.in_use = 0;
+ pim.subs.synt.in_use = 0;
+ if (REG_MULTI) {
+ pim.end.pos.col = (int)(rex.input - rex.line);
+ pim.end.pos.lnum = rex.lnum;
+ } else {
+ pim.end.ptr = rex.input;
+ }
+ // t->state->out1 is the corresponding END_INVISIBLE
+ // node; Add its out to the current list (zero-width
+ // match).
+ if (addstate_here(thislist, t->state->out1->out, &t->subs,
+ &pim, &listidx) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ break;
+
+ case NFA_START_PATTERN: {
+ nfa_state_T *skip = NULL;
+#ifdef REGEXP_DEBUG
+ int skip_lid = 0;
+#endif
+
+ // There is no point in trying to match the pattern if the
+ // output state is not going to be added to the list.
+ if (state_in_list(nextlist, t->state->out1->out, &t->subs)) {
+ skip = t->state->out1->out;
+#ifdef REGEXP_DEBUG
+ skip_lid = nextlist->id;
+#endif
+ } else if (state_in_list(nextlist,
+ t->state->out1->out->out, &t->subs)) {
+ skip = t->state->out1->out->out;
+#ifdef REGEXP_DEBUG
+ skip_lid = nextlist->id;
+#endif
+ } else if (state_in_list(thislist,
+ t->state->out1->out->out, &t->subs)) {
+ skip = t->state->out1->out->out;
+#ifdef REGEXP_DEBUG
+ skip_lid = thislist->id;
+#endif
+ }
+ if (skip != NULL) {
+#ifdef REGEXP_DEBUG
+ nfa_set_code(skip->c);
+ fprintf(log_fd,
+ "> Not trying to match pattern, output state %d is already in list %d. char %d: %s\n",
+ abs(skip->id), skip_lid, skip->c, code);
+#endif
+ break;
+ }
+ // Copy submatch info to the recursive call, opposite of what
+ // happens afterwards.
+ copy_sub_off(&m->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&m->synt, &t->subs.synt);
+ }
+
+ // First try matching the pattern.
+ result = recursive_regmatch(t->state, NULL, prog, submatch, m,
+ &listids, &listids_len);
+ if (result == NFA_TOO_EXPENSIVE) {
+ nfa_match = result;
+ goto theend;
+ }
+ if (result) {
+ int bytelen;
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "NFA_START_PATTERN matches:\n");
+ log_subsexpr(m);
+#endif
+ // Copy submatch info from the recursive call
+ copy_sub_off(&t->subs.norm, &m->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&t->subs.synt, &m->synt);
+ }
+ // Now we need to skip over the matched text and then
+ // continue with what follows.
+ if (REG_MULTI) {
+ // TODO(RE): multi-line match
+ bytelen = m->norm.list.multi[0].end_col
+ - (int)(rex.input - rex.line);
+ } else {
+ bytelen = (int)(m->norm.list.line[0].end - rex.input);
+ }
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "NFA_START_PATTERN length: %d\n", bytelen);
+#endif
+ if (bytelen == 0) {
+ // empty match, output of corresponding
+ // NFA_END_PATTERN/NFA_SKIP to be used at current
+ // position
+ add_here = true;
+ add_state = t->state->out1->out->out;
+ } else if (bytelen <= clen) {
+ // match current character, output of corresponding
+ // NFA_END_PATTERN to be used at next position.
+ add_state = t->state->out1->out->out;
+ add_off = clen;
+ } else {
+ // skip over the matched characters, set character
+ // count in NFA_SKIP
+ add_state = t->state->out1->out;
+ add_off = bytelen;
+ add_count = bytelen - clen;
+ }
+ }
+ break;
+ }
+
+ case NFA_BOL:
+ if (rex.input == rex.line) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_EOL:
+ if (curc == NUL) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_BOW:
+ result = true;
+
+ if (curc == NUL) {
+ result = false;
+ } else {
+ int this_class;
+
+ // Get class of current and previous char (if it exists).
+ this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ if (this_class <= 1) {
+ result = false;
+ } else if (reg_prev_class() == this_class) {
+ result = false;
+ }
+ }
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_EOW:
+ result = true;
+ if (rex.input == rex.line) {
+ result = false;
+ } else {
+ int this_class, prev_class;
+
+ // Get class of current and previous char (if it exists).
+ this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ prev_class = reg_prev_class();
+ if (this_class == prev_class
+ || prev_class == 0 || prev_class == 1) {
+ result = false;
+ }
+ }
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_BOF:
+ if (rex.lnum == 0 && rex.input == rex.line
+ && (!REG_MULTI || rex.reg_firstlnum == 1)) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_EOF:
+ if (rex.lnum == rex.reg_maxline && curc == NUL) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_COMPOSING: {
+ int mc = curc;
+ int len = 0;
+ nfa_state_T *end;
+ nfa_state_T *sta;
+ int cchars[MAX_MCO];
+ int ccount = 0;
+ int j;
+
+ sta = t->state->out;
+ len = 0;
+ if (utf_iscomposing(sta->c)) {
+ // Only match composing character(s), ignore base
+ // character. Used for ".{composing}" and "{composing}"
+ // (no preceding character).
+ len += utf_char2len(mc);
+ }
+ if (rex.reg_icombine && len == 0) {
+ // If \Z was present, then ignore composing characters.
+ // When ignoring the base character this always matches.
+ if (sta->c != curc) {
+ result = FAIL;
+ } else {
+ result = OK;
+ }
+ while (sta->c != NFA_END_COMPOSING) {
+ sta = sta->out;
+ }
+ } else if (len > 0 || mc == sta->c) {
+ // Check base character matches first, unless ignored.
+ if (len == 0) {
+ len += utf_char2len(mc);
+ sta = sta->out;
+ }
+
+ // We don't care about the order of composing characters.
+ // Get them into cchars[] first.
+ while (len < clen) {
+ mc = utf_ptr2char((char *)rex.input + len);
+ cchars[ccount++] = mc;
+ len += utf_char2len(mc);
+ if (ccount == MAX_MCO) {
+ break;
+ }
+ }
+
+ // Check that each composing char in the pattern matches a
+ // composing char in the text. We do not check if all
+ // composing chars are matched.
+ result = OK;
+ while (sta->c != NFA_END_COMPOSING) {
+ for (j = 0; j < ccount; j++) {
+ if (cchars[j] == sta->c) {
+ break;
+ }
+ }
+ if (j == ccount) {
+ result = FAIL;
+ break;
+ }
+ sta = sta->out;
+ }
+ } else {
+ result = FAIL;
+ }
+
+ end = t->state->out1; // NFA_END_COMPOSING
+ ADD_STATE_IF_MATCH(end);
+ break;
+ }
+
+ case NFA_NEWL:
+ if (curc == NUL && !rex.reg_line_lbr && REG_MULTI
+ && rex.lnum <= rex.reg_maxline) {
+ go_to_nextline = true;
+ // Pass -1 for the offset, which means taking the position
+ // at the start of the next line.
+ add_state = t->state->out;
+ add_off = -1;
+ } else if (curc == '\n' && rex.reg_line_lbr) {
+ // match \n as if it is an ordinary character
+ add_state = t->state->out;
+ add_off = 1;
+ }
+ break;
+
+ case NFA_START_COLL:
+ case NFA_START_NEG_COLL: {
+ // What follows is a list of characters, until NFA_END_COLL.
+ // One of them must match or none of them must match.
+ nfa_state_T *state;
+ int result_if_matched;
+ int c1, c2;
+
+ // Never match EOL. If it's part of the collection it is added
+ // as a separate state with an OR.
+ if (curc == NUL) {
+ break;
+ }
+
+ state = t->state->out;
+ result_if_matched = (t->state->c == NFA_START_COLL);
+ while (true) {
+ if (state->c == NFA_END_COLL) {
+ result = !result_if_matched;
+ break;
+ }
+ if (state->c == NFA_RANGE_MIN) {
+ c1 = state->val;
+ state = state->out; // advance to NFA_RANGE_MAX
+ c2 = state->val;
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "NFA_RANGE_MIN curc=%d c1=%d c2=%d\n",
+ curc, c1, c2);
+#endif
+ if (curc >= c1 && curc <= c2) {
+ result = result_if_matched;
+ break;
+ }
+ if (rex.reg_ic) {
+ int curc_low = utf_fold(curc);
+ int done = false;
+
+ for (; c1 <= c2; c1++) {
+ if (utf_fold(c1) == curc_low) {
+ result = result_if_matched;
+ done = true;
+ break;
+ }
+ }
+ if (done) {
+ break;
+ }
+ }
+ } else if (state->c < 0 ? check_char_class(state->c, curc)
+ : (curc == state->c
+ || (rex.reg_ic
+ && utf_fold(curc) == utf_fold(state->c)))) {
+ result = result_if_matched;
+ break;
+ }
+ state = state->out;
+ }
+ if (result) {
+ // next state is in out of the NFA_END_COLL, out1 of
+ // START points to the END state
+ add_state = t->state->out1->out;
+ add_off = clen;
+ }
+ break;
+ }
+
+ case NFA_ANY:
+ // Any char except '\0', (end of input) does not match.
+ if (curc > 0) {
+ add_state = t->state->out;
+ add_off = clen;
+ }
+ break;
+
+ case NFA_ANY_COMPOSING:
+ // On a composing character skip over it. Otherwise do
+ // nothing. Always matches.
+ if (utf_iscomposing(curc)) {
+ add_off = clen;
+ } else {
+ add_here = true;
+ add_off = 0;
+ }
+ add_state = t->state->out;
+ break;
+
+ // Character classes like \a for alpha, \d for digit etc.
+ case NFA_IDENT: // \i
+ result = vim_isIDc(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SIDENT: // \I
+ result = !ascii_isdigit(curc) && vim_isIDc(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_KWORD: // \k
+ result = vim_iswordp_buf((char *)rex.input, rex.reg_buf);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SKWORD: // \K
+ result = !ascii_isdigit(curc)
+ && vim_iswordp_buf((char *)rex.input, rex.reg_buf);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_FNAME: // \f
+ result = vim_isfilec(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SFNAME: // \F
+ result = !ascii_isdigit(curc) && vim_isfilec(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_PRINT: // \p
+ result = vim_isprintc(utf_ptr2char((char *)rex.input));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SPRINT: // \P
+ result = !ascii_isdigit(curc) && vim_isprintc(utf_ptr2char((char *)rex.input));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_WHITE: // \s
+ result = ascii_iswhite(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NWHITE: // \S
+ result = curc != NUL && !ascii_iswhite(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_DIGIT: // \d
+ result = ri_digit(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NDIGIT: // \D
+ result = curc != NUL && !ri_digit(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_HEX: // \x
+ result = ri_hex(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NHEX: // \X
+ result = curc != NUL && !ri_hex(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_OCTAL: // \o
+ result = ri_octal(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NOCTAL: // \O
+ result = curc != NUL && !ri_octal(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_WORD: // \w
+ result = ri_word(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NWORD: // \W
+ result = curc != NUL && !ri_word(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_HEAD: // \h
+ result = ri_head(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NHEAD: // \H
+ result = curc != NUL && !ri_head(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_ALPHA: // \a
+ result = ri_alpha(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NALPHA: // \A
+ result = curc != NUL && !ri_alpha(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_LOWER: // \l
+ result = ri_lower(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NLOWER: // \L
+ result = curc != NUL && !ri_lower(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_UPPER: // \u
+ result = ri_upper(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NUPPER: // \U
+ result = curc != NUL && !ri_upper(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_LOWER_IC: // [a-z]
+ result = ri_lower(curc) || (rex.reg_ic && ri_upper(curc));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NLOWER_IC: // [^a-z]
+ result = curc != NUL
+ && !(ri_lower(curc) || (rex.reg_ic && ri_upper(curc)));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_UPPER_IC: // [A-Z]
+ result = ri_upper(curc) || (rex.reg_ic && ri_lower(curc));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NUPPER_IC: // [^A-Z]
+ result = curc != NUL
+ && !(ri_upper(curc) || (rex.reg_ic && ri_lower(curc)));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ // \1 .. \9 \z1 .. \z9
+ {
+ int subidx;
+ int bytelen;
+
+ if (t->state->c <= NFA_BACKREF9) {
+ subidx = t->state->c - NFA_BACKREF1 + 1;
+ result = match_backref(&t->subs.norm, subidx, &bytelen);
+ } else {
+ subidx = t->state->c - NFA_ZREF1 + 1;
+ result = match_zref(subidx, &bytelen);
+ }
+
+ if (result) {
+ if (bytelen == 0) {
+ // empty match always works, output of NFA_SKIP to be
+ // used next
+ add_here = true;
+ add_state = t->state->out->out;
+ } else if (bytelen <= clen) {
+ // match current character, jump ahead to out of
+ // NFA_SKIP
+ add_state = t->state->out->out;
+ add_off = clen;
+ } else {
+ // skip over the matched characters, set character
+ // count in NFA_SKIP
+ add_state = t->state->out;
+ add_off = bytelen;
+ add_count = bytelen - clen;
+ }
+ }
+ break;
+ }
+ case NFA_SKIP:
+ // character of previous matching \1 .. \9 or \@>
+ if (t->count - clen <= 0) {
+ // end of match, go to what follows
+ add_state = t->state->out;
+ add_off = clen;
+ } else {
+ // add state again with decremented count
+ add_state = t->state;
+ add_off = 0;
+ add_count = t->count - clen;
+ }
+ break;
+
+ case NFA_LNUM:
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ assert(t->state->val >= 0
+ && !((rex.reg_firstlnum > 0
+ && rex.lnum > LONG_MAX - rex.reg_firstlnum)
+ || (rex.reg_firstlnum < 0
+ && rex.lnum < LONG_MIN + rex.reg_firstlnum))
+ && rex.lnum + rex.reg_firstlnum >= 0);
+ result = (REG_MULTI
+ && nfa_re_num_cmp((uintmax_t)t->state->val,
+ t->state->c - NFA_LNUM,
+ (uintmax_t)rex.lnum + (uintmax_t)rex.reg_firstlnum));
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_COL:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ assert(t->state->val >= 0
+ && rex.input >= rex.line
+ && (uintmax_t)(rex.input - rex.line) <= UINTMAX_MAX - 1);
+ result = nfa_re_num_cmp((uintmax_t)t->state->val,
+ t->state->c - NFA_COL,
+ (uintmax_t)(rex.input - rex.line + 1));
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_VCOL:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT: {
+ int op = t->state->c - NFA_VCOL;
+ colnr_T col = (colnr_T)(rex.input - rex.line);
+
+ // Bail out quickly when there can't be a match, avoid the overhead of
+ // win_linetabsize() on long lines.
+ if (op != 1 && col > t->state->val * MB_MAXBYTES) {
+ break;
+ }
+
+ result = false;
+ win_T *wp = rex.reg_win == NULL ? curwin : rex.reg_win;
+ if (op == 1 && col - 1 > t->state->val && col > 100) {
+ int64_t ts = (int64_t)wp->w_buffer->b_p_ts;
+
+ // Guess that a character won't use more columns than 'tabstop',
+ // with a minimum of 4.
+ if (ts < 4) {
+ ts = 4;
+ }
+ result = col > t->state->val * ts;
+ }
+ if (!result) {
+ int lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
+ assert(t->state->val >= 0);
+ result = nfa_re_num_cmp((uintmax_t)t->state->val, op, (uintmax_t)lts + 1);
+ }
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ }
+ break;
+
+ case NFA_MARK:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT: {
+ size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
+ fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, t->state->val);
+
+ // Line may have been freed, get it again.
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ rex.input = rex.line + col;
+ }
+
+ // Compare the mark position to the match position, if the mark
+ // exists and mark is set in reg_buf.
+ if (fm != NULL && fm->mark.lnum > 0) {
+ pos_T *pos = &fm->mark;
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)strlen(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ result = pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT);
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ }
+ break;
+ }
+
+ case NFA_CURSOR:
+ result = rex.reg_win != NULL
+ && (rex.lnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum)
+ && ((colnr_T)(rex.input - rex.line) == rex.reg_win->w_cursor.col);
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_VISUAL:
+ result = reg_match_visual();
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZSTART:
+ // These states are only added to be able to bail out when
+ // they are added again, nothing is to be done.
+ break;
+
+ default: // regular character
+ {
+ int c = t->state->c;
+
+#ifdef REGEXP_DEBUG
+ if (c < 0) {
+ siemsg("INTERNAL: Negative state char: %" PRId64, (int64_t)c);
+ }
+#endif
+ result = (c == curc);
+
+ if (!result && rex.reg_ic) {
+ result = utf_fold(c) == utf_fold(curc);
+ }
+
+ // If rex.reg_icombine is not set only skip over the character
+ // itself. When it is set skip over composing characters.
+ if (result && !rex.reg_icombine) {
+ clen = utf_ptr2len((char *)rex.input);
+ }
+
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+ }
+ } // switch (t->state->c)
+
+ if (add_state != NULL) {
+ nfa_pim_T *pim;
+ nfa_pim_T pim_copy;
+
+ if (t->pim.result == NFA_PIM_UNUSED) {
+ pim = NULL;
+ } else {
+ pim = &t->pim;
+ }
+
+ // Handle the postponed invisible match if the match might end
+ // without advancing and before the end of the line.
+ if (pim != NULL && (clen == 0 || match_follows(add_state, 0))) {
+ if (pim->result == NFA_PIM_TODO) {
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "\n");
+ fprintf(log_fd, "==================================\n");
+ fprintf(log_fd, "Postponed recursive nfa_regmatch()\n");
+ fprintf(log_fd, "\n");
+#endif
+ result = recursive_regmatch(pim->state, pim, prog, submatch, m,
+ &listids, &listids_len);
+ pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH;
+ // for \@! and \@<! it is a match when the result is
+ // false
+ if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
+ || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
+ // Copy submatch info from the recursive call
+ copy_sub_off(&pim->subs.norm, &m->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&pim->subs.synt, &m->synt);
+ }
+ }
+ } else {
+ result = (pim->result == NFA_PIM_MATCH);
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "\n");
+ fprintf(log_fd,
+ "Using previous recursive nfa_regmatch() result, result == %d\n",
+ pim->result);
+ fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "false");
+ fprintf(log_fd, "\n");
+#endif
+ }
+
+ // for \@! and \@<! it is a match when result is false
+ if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
+ || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
+ // Copy submatch info from the recursive call
+ copy_sub_off(&t->subs.norm, &pim->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&t->subs.synt, &pim->subs.synt);
+ }
+ } else {
+ // look-behind match failed, don't add the state
+ continue;
+ }
+
+ // Postponed invisible match was handled, don't add it to
+ // following states.
+ pim = NULL;
+ }
+
+ // If "pim" points into l->t it will become invalid when
+ // adding the state causes the list to be reallocated. Make a
+ // local copy to avoid that.
+ if (pim == &t->pim) {
+ copy_pim(&pim_copy, pim);
+ pim = &pim_copy;
+ }
+
+ if (add_here) {
+ r = addstate_here(thislist, add_state, &t->subs, pim, &listidx);
+ } else {
+ r = addstate(nextlist, add_state, &t->subs, pim, add_off);
+ if (add_count > 0) {
+ nextlist->t[nextlist->n - 1].count = add_count;
+ }
+ }
+ if (r == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ } // for (thislist = thislist; thislist->state; thislist++)
+
+ // Look for the start of a match in the current position by adding the
+ // start state to the list of states.
+ // The first found match is the leftmost one, thus the order of states
+ // matters!
+ // Do not add the start state in recursive calls of nfa_regmatch(),
+ // because recursive calls should only start in the first position.
+ // Unless "nfa_endp" is not NULL, then we match the end position.
+ // Also don't start a match past the first line.
+ if (!nfa_match
+ && ((toplevel
+ && rex.lnum == 0
+ && clen != 0
+ && (rex.reg_maxcol == 0
+ || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol))
+ || (nfa_endp != NULL
+ && (REG_MULTI
+ ? (rex.lnum < nfa_endp->se_u.pos.lnum
+ || (rex.lnum == nfa_endp->se_u.pos.lnum
+ && (int)(rex.input - rex.line)
+ < nfa_endp->se_u.pos.col))
+ : rex.input < nfa_endp->se_u.ptr)))) {
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "(---) STARTSTATE\n");
+#endif
+ // Inline optimized code for addstate() if we know the state is
+ // the first MOPEN.
+ if (toplevel) {
+ int add = true;
+
+ if (prog->regstart != NUL && clen != 0) {
+ if (nextlist->n == 0) {
+ colnr_T col = (colnr_T)(rex.input - rex.line) + clen;
+
+ // Nextlist is empty, we can skip ahead to the
+ // character that must appear at the start.
+ if (skip_to_start(prog->regstart, &col) == FAIL) {
+ break;
+ }
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, " Skipping ahead %d bytes to regstart\n",
+ col - ((colnr_T)(rex.input - rex.line) + clen));
+#endif
+ rex.input = rex.line + col - clen;
+ } else {
+ // Checking if the required start character matches is
+ // cheaper than adding a state that won't match.
+ const int c = utf_ptr2char((char *)rex.input + clen);
+ if (c != prog->regstart
+ && (!rex.reg_ic
+ || utf_fold(c) != utf_fold(prog->regstart))) {
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd,
+ " Skipping start state, regstart does not match\n");
+#endif
+ add = false;
+ }
+ }
+ }
+
+ if (add) {
+ if (REG_MULTI) {
+ m->norm.list.multi[0].start_col =
+ (colnr_T)(rex.input - rex.line) + clen;
+ m->norm.orig_start_col =
+ m->norm.list.multi[0].start_col;
+ } else {
+ m->norm.list.line[0].start = rex.input + clen;
+ }
+ if (addstate(nextlist, start->out, m, NULL, clen) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ } else {
+ if (addstate(nextlist, start, m, NULL, clen) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ }
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, ">>> Thislist had %d states available: ", thislist->n);
+ {
+ int i;
+
+ for (i = 0; i < thislist->n; i++) {
+ fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
+ }
+ }
+ fprintf(log_fd, "\n");
+#endif
+
+nextchar:
+ // Advance to the next character, or advance to the next line, or
+ // finish.
+ if (clen != 0) {
+ rex.input += clen;
+ } else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI
+ && rex.lnum < nfa_endp->se_u.pos.lnum)) {
+ reg_nextline();
+ } else {
+ break;
+ }
+
+ // Allow interrupting with CTRL-C.
+ reg_breakcheck();
+ if (got_int) {
+ break;
+ }
+ // Check for timeout once every twenty times to avoid overhead.
+ if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
+ nfa_time_count = 0;
+ if (nfa_did_time_out()) {
+ break;
+ }
+ }
+ }
+
+#ifdef REGEXP_DEBUG
+ if (log_fd != stderr) {
+ fclose(log_fd);
+ }
+ log_fd = NULL;
+#endif
+
+theend:
+ // Free memory
+ xfree(list[0].t);
+ xfree(list[1].t);
+ xfree(listids);
+#undef ADD_STATE_IF_MATCH
+#ifdef NFA_REGEXP_DEBUG_LOG
+ fclose(debug);
+#endif
+
+ return nfa_match;
+}
+
+/// Try match of "prog" with at rex.line["col"].
+///
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return <= 0 for failure, number of lines contained in the match otherwise.
+static int nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
+{
+ int i;
+ regsubs_T subs, m;
+ nfa_state_T *start = prog->start;
+#ifdef REGEXP_DEBUG
+ FILE *f;
+#endif
+
+ rex.input = rex.line + col;
+ nfa_time_limit = tm;
+ nfa_timed_out = timed_out;
+ nfa_time_count = 0;
+
+#ifdef REGEXP_DEBUG
+ f = fopen(NFA_REGEXP_RUN_LOG, "a");
+ if (f != NULL) {
+ fprintf(f,
+ "\n\n\t=======================================================\n");
+# ifdef REGEXP_DEBUG
+ fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr);
+# endif
+ fprintf(f, "\tInput text is \"%s\" \n", rex.input);
+ fprintf(f, "\t=======================================================\n\n");
+ nfa_print_state(f, start);
+ fprintf(f, "\n\n");
+ fclose(f);
+ } else {
+ emsg("Could not open temporary log file for writing");
+ }
+#endif
+
+ clear_sub(&subs.norm);
+ clear_sub(&m.norm);
+ clear_sub(&subs.synt);
+ clear_sub(&m.synt);
+
+ int result = nfa_regmatch(prog, start, &subs, &m);
+ if (!result) {
+ return 0;
+ } else if (result == NFA_TOO_EXPENSIVE) {
+ return result;
+ }
+
+ cleanup_subexpr();
+ if (REG_MULTI) {
+ for (i = 0; i < subs.norm.in_use; i++) {
+ rex.reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum;
+ rex.reg_startpos[i].col = subs.norm.list.multi[i].start_col;
+
+ rex.reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum;
+ rex.reg_endpos[i].col = subs.norm.list.multi[i].end_col;
+ }
+ if (rex.reg_mmatch != NULL) {
+ rex.reg_mmatch->rmm_matchcol = subs.norm.orig_start_col;
+ }
+
+ if (rex.reg_startpos[0].lnum < 0) {
+ rex.reg_startpos[0].lnum = 0;
+ rex.reg_startpos[0].col = col;
+ }
+ if (rex.reg_endpos[0].lnum < 0) {
+ // pattern has a \ze but it didn't match, use current end
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
+ } else {
+ // Use line number of "\ze".
+ rex.lnum = rex.reg_endpos[0].lnum;
+ }
+ } else {
+ for (i = 0; i < subs.norm.in_use; i++) {
+ rex.reg_startp[i] = subs.norm.list.line[i].start;
+ rex.reg_endp[i] = subs.norm.list.line[i].end;
+ }
+
+ if (rex.reg_startp[0] == NULL) {
+ rex.reg_startp[0] = rex.line + col;
+ }
+ if (rex.reg_endp[0] == NULL) {
+ rex.reg_endp[0] = rex.input;
+ }
+ }
+
+ // Package any found \z(...\) matches for export. Default is none.
+ unref_extmatch(re_extmatch_out);
+ re_extmatch_out = NULL;
+
+ if (prog->reghasz == REX_SET) {
+ cleanup_zsubexpr();
+ re_extmatch_out = make_extmatch();
+ // Loop over \z1, \z2, etc. There is no \z0.
+ for (i = 1; i < subs.synt.in_use; i++) {
+ if (REG_MULTI) {
+ struct multipos *mpos = &subs.synt.list.multi[i];
+
+ // Only accept single line matches that are valid.
+ if (mpos->start_lnum >= 0
+ && mpos->start_lnum == mpos->end_lnum
+ && mpos->end_col >= mpos->start_col) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave(reg_getline(mpos->start_lnum) + mpos->start_col,
+ (size_t)(mpos->end_col - mpos->start_col));
+ }
+ } else {
+ struct linepos *lpos = &subs.synt.list.line[i];
+
+ if (lpos->start != NULL && lpos->end != NULL) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave((char *)lpos->start, (size_t)(lpos->end - lpos->start));
+ }
+ }
+ }
+ }
+
+ return 1 + rex.lnum;
+}
+
+/// Match a regexp against a string ("line" points to the string) or multiple
+/// lines (if "line" is NULL, use reg_getline()).
+///
+/// @param line String in which to search or NULL
+/// @param startcol Column to start looking for match
+/// @param tm Timeout limit or NULL
+/// @param timed_out Flag set on timeout or NULL
+///
+/// @return <= 0 if there is no match and number of lines contained in the
+/// match otherwise.
+static int nfa_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
+{
+ nfa_regprog_T *prog;
+ int retval = 0;
+ colnr_T col = startcol;
+
+ if (REG_MULTI) {
+ prog = (nfa_regprog_T *)rex.reg_mmatch->regprog;
+ line = (uint8_t *)reg_getline(0); // relative to the cursor
+ rex.reg_startpos = rex.reg_mmatch->startpos;
+ rex.reg_endpos = rex.reg_mmatch->endpos;
+ } else {
+ prog = (nfa_regprog_T *)rex.reg_match->regprog;
+ rex.reg_startp = (uint8_t **)rex.reg_match->startp;
+ rex.reg_endp = (uint8_t **)rex.reg_match->endp;
+ }
+
+ // Be paranoid...
+ if (prog == NULL || line == NULL) {
+ iemsg(_(e_null));
+ goto theend;
+ }
+
+ // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
+ if (prog->regflags & RF_ICASE) {
+ rex.reg_ic = true;
+ } else if (prog->regflags & RF_NOICASE) {
+ rex.reg_ic = false;
+ }
+
+ // If pattern contains "\Z" overrule value of rex.reg_icombine
+ if (prog->regflags & RF_ICOMBINE) {
+ rex.reg_icombine = true;
+ }
+
+ rex.line = line;
+ rex.lnum = 0; // relative to line
+
+ rex.nfa_has_zend = prog->has_zend;
+ rex.nfa_has_backref = prog->has_backref;
+ rex.nfa_nsubexpr = prog->nsubexp;
+ rex.nfa_listid = 1;
+ rex.nfa_alt_listid = 2;
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = prog->pattern;
+#endif
+
+ if (prog->reganch && col > 0) {
+ return 0L;
+ }
+
+ rex.need_clear_subexpr = true;
+ // Clear the external match subpointers if necessary.
+ if (prog->reghasz == REX_SET) {
+ rex.nfa_has_zsubexpr = true;
+ rex.need_clear_zsubexpr = true;
+ } else {
+ rex.nfa_has_zsubexpr = false;
+ rex.need_clear_zsubexpr = false;
+ }
+
+ if (prog->regstart != NUL) {
+ // Skip ahead until a character we know the match must start with.
+ // When there is none there is no match.
+ if (skip_to_start(prog->regstart, &col) == FAIL) {
+ return 0L;
+ }
+
+ // If match_text is set it contains the full text that must match.
+ // Nothing else to try. Doesn't handle combining chars well.
+ if (prog->match_text != NULL && !rex.reg_icombine) {
+ retval = find_match_text(&col, prog->regstart, prog->match_text);
+ if (REG_MULTI) {
+ rex.reg_mmatch->rmm_matchcol = col;
+ } else {
+ rex.reg_match->rm_matchcol = col;
+ }
+ return retval;
+ }
+ }
+
+ // If the start column is past the maximum column: no need to try.
+ if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
+ goto theend;
+ }
+
+ // Set the "nstate" used by nfa_regcomp() to zero to trigger an error when
+ // it's accidentally used during execution.
+ nstate = 0;
+ for (int i = 0; i < prog->nstate; i++) {
+ prog->state[i].id = i;
+ prog->state[i].lastlist[0] = 0;
+ prog->state[i].lastlist[1] = 0;
+ }
+
+ retval = nfa_regtry(prog, col, tm, timed_out);
+
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = NULL;
+#endif
+
+theend:
+ if (retval > 0) {
+ // Make sure the end is never before the start. Can happen when \zs and
+ // \ze are used.
+ if (REG_MULTI) {
+ const lpos_T *const start = &rex.reg_mmatch->startpos[0];
+ const lpos_T *const end = &rex.reg_mmatch->endpos[0];
+
+ if (end->lnum < start->lnum
+ || (end->lnum == start->lnum && end->col < start->col)) {
+ rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
+ }
+ } else {
+ if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
+ rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ }
+
+ // startpos[0] may be set by "\zs", also return the column where
+ // the whole pattern matched.
+ rex.reg_match->rm_matchcol = col;
+ }
+ }
+
+ return retval;
+}
+
+// Compile a regular expression into internal code for the NFA matcher.
+// Returns the program in allocated space. Returns NULL for an error.
+static regprog_T *nfa_regcomp(uint8_t *expr, int re_flags)
+{
+ nfa_regprog_T *prog = NULL;
+ int *postfix;
+
+ if (expr == NULL) {
+ return NULL;
+ }
+
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = expr;
+#endif
+ nfa_re_flags = re_flags;
+
+ init_class_tab();
+
+ nfa_regcomp_start(expr, re_flags);
+
+ // Build postfix form of the regexp. Needed to build the NFA
+ // (and count its size).
+ postfix = re2post();
+ if (postfix == NULL) {
+ goto fail; // Cascaded (syntax?) error
+ }
+
+ // In order to build the NFA, we parse the input regexp twice:
+ // 1. first pass to count size (so we can allocate space)
+ // 2. second to emit code
+#ifdef REGEXP_DEBUG
+ {
+ FILE *f = fopen(NFA_REGEXP_RUN_LOG, "a");
+
+ if (f != NULL) {
+ fprintf(f,
+ "\n*****************************\n\n\n\n\t"
+ "Compiling regexp \"%s\"... hold on !\n",
+ expr);
+ fclose(f);
+ }
+ }
+#endif
+
+ // PASS 1
+ // Count number of NFA states in "nstate". Do not build the NFA.
+ post2nfa(postfix, post_ptr, true);
+
+ // allocate the regprog with space for the compiled regexp
+ size_t prog_size = offsetof(nfa_regprog_T, state) + sizeof(nfa_state_T) * (size_t)nstate;
+ prog = xmalloc(prog_size);
+ state_ptr = prog->state;
+ prog->re_in_use = false;
+
+ // PASS 2
+ // Build the NFA
+ prog->start = post2nfa(postfix, post_ptr, false);
+ if (prog->start == NULL) {
+ goto fail;
+ }
+ prog->regflags = regflags;
+ prog->engine = &nfa_regengine;
+ prog->nstate = nstate;
+ prog->has_zend = rex.nfa_has_zend;
+ prog->has_backref = rex.nfa_has_backref;
+ prog->nsubexp = regnpar;
+
+ nfa_postprocess(prog);
+
+ prog->reganch = nfa_get_reganch(prog->start, 0);
+ prog->regstart = nfa_get_regstart(prog->start, 0);
+ prog->match_text = nfa_get_match_text(prog->start);
+
+#ifdef REGEXP_DEBUG
+ nfa_postfix_dump(expr, OK);
+ nfa_dump(prog);
+#endif
+ // Remember whether this pattern has any \z specials in it.
+ prog->reghasz = re_has_z;
+ prog->pattern = xstrdup((char *)expr);
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = NULL;
+#endif
+
+out:
+ xfree(post_start);
+ post_start = post_ptr = post_end = NULL;
+ state_ptr = NULL;
+ return (regprog_T *)prog;
+
+fail:
+ XFREE_CLEAR(prog);
+#ifdef REGEXP_DEBUG
+ nfa_postfix_dump(expr, FAIL);
+ nfa_regengine.expr = NULL;
+#endif
+ goto out;
+}
+
+// Free a compiled regexp program, returned by nfa_regcomp().
+static void nfa_regfree(regprog_T *prog)
+{
+ if (prog == NULL) {
+ return;
+ }
+
+ xfree(((nfa_regprog_T *)prog)->match_text);
+ xfree(((nfa_regprog_T *)prog)->pattern);
+ xfree(prog);
+}
+
+/// Match a regexp against a string.
+/// "rmp->regprog" is a compiled regexp as returned by nfa_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
+///
+/// @param line string to match against
+/// @param col column to start looking for match
+///
+/// @return <= 0 for failure, number of lines contained in the match otherwise.
+static int nfa_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
+{
+ rex.reg_match = rmp;
+ rex.reg_mmatch = NULL;
+ rex.reg_maxline = 0;
+ rex.reg_line_lbr = line_lbr;
+ rex.reg_buf = curbuf;
+ rex.reg_win = NULL;
+ rex.reg_ic = rmp->rm_ic;
+ rex.reg_icombine = false;
+ rex.reg_nobreak = rmp->regprog->re_flags & RE_NOBREAK;
+ rex.reg_maxcol = 0;
+ return nfa_regexec_both(line, col, NULL, NULL);
+}
+
+/// Matches a regexp against multiple lines.
+/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+///
+/// @param win Window in which to search or NULL
+/// @param buf Buffer in which to search
+/// @param lnum Number of line to start looking for match
+/// @param col Column to start looking for match
+/// @param tm Timeout limit or NULL
+/// @param timed_out Flag set on timeout or NULL
+///
+/// @return <= 0 if there is no match and number of lines contained in the match
+/// otherwise.
+///
+/// @note The body is the same as bt_regexec() except for nfa_regexec_both()
+///
+/// @warning
+/// Match may actually be in another line. e.g.:
+/// when r.e. is \nc, cursor is at 'a' and the text buffer looks like
+///
+/// @par
+///
+/// +-------------------------+
+/// |a |
+/// |b |
+/// |c |
+/// | |
+/// +-------------------------+
+///
+/// @par
+/// then nfa_regexec_multi() returns 3. while the original vim_regexec_multi()
+/// returns 0 and a second call at line 2 will return 2.
+///
+/// @par
+/// FIXME if this behavior is not compatible.
+static int nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
+ proftime_T *tm, int *timed_out)
+{
+ init_regexec_multi(rmp, win, buf, lnum);
+ return nfa_regexec_both(NULL, col, tm, timed_out);
+}
+// }}}1
static regengine_T bt_regengine = {
bt_regcomp,
@@ -2304,10 +15570,10 @@ static uint8_t regname[][30] = {
// Returns the program in allocated memory.
// Use vim_regfree() to free the memory.
// Returns NULL for an error.
-regprog_T *vim_regcomp(char *expr_arg, int re_flags)
+regprog_T *vim_regcomp(const char *expr_arg, int re_flags)
{
regprog_T *prog = NULL;
- char *expr = expr_arg;
+ const char *expr = expr_arg;
regexp_engine = (int)p_re;
@@ -2321,7 +15587,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags)
regexp_engine = expr[4] - '0';
expr += 5;
#ifdef REGEXP_DEBUG
- smsg("New regexp mode selected (%d): %s",
+ smsg(0, "New regexp mode selected (%d): %s",
regexp_engine,
regname[newengine]);
#endif
@@ -2403,7 +15669,7 @@ void free_regexp_stuff(void)
#endif
-static void report_re_switch(char *pat)
+static void report_re_switch(const char *pat)
{
if (p_verbose > 0) {
verbose_enter();
@@ -2425,7 +15691,7 @@ static void report_re_switch(char *pat)
/// @param nl
///
/// @return true if there is a match, false if not.
-static bool vim_regexec_string(regmatch_T *rmp, char *line, colnr_T col, bool nl)
+static bool vim_regexec_string(regmatch_T *rmp, const char *line, colnr_T col, bool nl)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
@@ -2482,7 +15748,7 @@ static bool vim_regexec_string(regmatch_T *rmp, char *line, colnr_T col, bool nl
// Note: "*prog" may be freed and changed.
// Return true if there is a match, false if not.
-bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char *line, colnr_T col)
+bool vim_regexec_prog(regprog_T **prog, bool ignore_case, const char *line, colnr_T col)
{
regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case };
bool r = vim_regexec_string(&regmatch, line, col, false);
@@ -2492,7 +15758,7 @@ bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char *line, colnr_T co
// Note: "rmp->regprog" may be freed and changed.
// Return true if there is a match, false if not.
-bool vim_regexec(regmatch_T *rmp, char *line, colnr_T col)
+bool vim_regexec(regmatch_T *rmp, const char *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, false);
}
@@ -2500,7 +15766,7 @@ bool vim_regexec(regmatch_T *rmp, char *line, colnr_T col)
// Like vim_regexec(), but consider a "\n" in "line" to be a line break.
// Note: "rmp->regprog" may be freed and changed.
// Return true if there is a match, false if not.
-bool vim_regexec_nl(regmatch_T *rmp, char *line, colnr_T col)
+bool vim_regexec_nl(regmatch_T *rmp, const char *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, true);
}
@@ -2519,8 +15785,8 @@ bool vim_regexec_nl(regmatch_T *rmp, char *line, colnr_T col)
///
/// @return zero if there is no match. Return number of lines contained in the
/// match otherwise.
-long vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
- proftime_T *tm, int *timed_out)
+int vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
+ proftime_T *tm, int *timed_out)
FUNC_ATTR_NONNULL_ARG(1)
{
regexec_T rex_save;
@@ -2539,7 +15805,7 @@ long vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum,
}
rex_in_use = true;
- long result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out);
+ int result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out);
rmp->regprog->re_in_use = false;
// NFA engine aborted because it's very slow, use backtracking engine instead.
diff --git a/src/nvim/regexp.h b/src/nvim/regexp.h
index dcc58fa34c..447f4860a9 100644
--- a/src/nvim/regexp.h
+++ b/src/nvim/regexp.h
@@ -1,27 +1,22 @@
-#ifndef NVIM_REGEXP_H
-#define NVIM_REGEXP_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/regexp_defs.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/regexp_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Second argument for vim_regcomp().
#define RE_MAGIC 1 ///< 'magic' option
#define RE_STRING 2 ///< match in string instead of buffer text
#define RE_STRICT 4 ///< don't allow [abc] without ]
#define RE_AUTO 8 ///< automatic engine selection
+#define RE_NOBREAK 16 ///< don't use breakcheck functions
// values for reg_do_extmatch
#define REX_SET 1 ///< to allow \z\(...\),
#define REX_USE 2 ///< to allow \z\1 et al.
#define REX_ALL (REX_SET | REX_USE)
-// regexp.c
-// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "regexp.h.generated.h"
-# include "regexp_bt.h.generated.h"
#endif
-// uncrustify:on
-
-#endif // NVIM_REGEXP_H
diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c
deleted file mode 100644
index 1b32447d77..0000000000
--- a/src/nvim/regexp_bt.c
+++ /dev/null
@@ -1,5661 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// Backtracking regular expression implementation.
-//
-// This file is included in "regexp.c".
-//
-// NOTICE:
-//
-// This is NOT the original regular expression code as written by Henry
-// Spencer. This code has been modified specifically for use with the VIM
-// editor, and should not be used separately from Vim. If you want a good
-// regular expression library, get the original code. The copyright notice
-// that follows is from the original.
-//
-// END NOTICE
-//
-// Copyright (c) 1986 by University of Toronto.
-// Written by Henry Spencer. Not derived from licensed software.
-//
-// Permission is granted to anyone to use this software for any
-// purpose on any computer system, and to redistribute it freely,
-// subject to the following restrictions:
-//
-// 1. The author is not responsible for the consequences of use of
-// this software, no matter how awful, even if they arise
-// from defects in it.
-//
-// 2. The origin of this software must not be misrepresented, either
-// by explicit claim or by omission.
-//
-// 3. Altered versions must be plainly marked as such, and must not
-// be misrepresented as being the original software.
-//
-// Beware that some of this code is subtly aware of the way operator
-// precedence is structured in regular expressions. Serious changes in
-// regular-expression syntax might require a total rethink.
-//
-// Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert
-// Webb, Ciaran McCreesh and Bram Moolenaar.
-// Named character class support added by Walter Briscoe (1998 Jul 01)
-
-// The "internal use only" fields in regexp_defs.h are present to pass info from
-// compile to execute that permits the execute phase to run lots faster on
-// simple cases. They are:
-//
-// regstart char that must begin a match; NUL if none obvious; Can be a
-// multi-byte character.
-// reganch is the match anchored (at beginning-of-line only)?
-// regmust string (pointer into program) that match must include, or NULL
-// regmlen length of regmust string
-// regflags RF_ values or'ed together
-//
-// Regstart and reganch permit very fast decisions on suitable starting points
-// for a match, cutting down the work a lot. Regmust permits fast rejection
-// of lines that cannot possibly match. The regmust tests are costly enough
-// that vim_regcomp() supplies a regmust only if the r.e. contains something
-// potentially expensive (at present, the only such thing detected is * or +
-// at the start of the r.e., which can involve a lot of backup). Regmlen is
-// supplied because the test in vim_regexec() needs it and vim_regcomp() is
-// computing it anyway.
-
-// Structure for regexp "program". This is essentially a linear encoding
-// of a nondeterministic finite-state machine (aka syntax charts or
-// "railroad normal form" in parsing technology). Each node is an opcode
-// plus a "next" pointer, possibly plus an operand. "Next" pointers of
-// all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next"
-// pointer with a BRANCH on both ends of it is connecting two alternatives.
-// (Here we have one of the subtle syntax dependencies: an individual BRANCH
-// (as opposed to a collection of them) is never concatenated with anything
-// because of operator precedence). The "next" pointer of a BRACES_COMPLEX
-// node points to the node after the stuff to be repeated.
-// The operand of some types of node is a literal string; for others, it is a
-// node leading into a sub-FSM. In particular, the operand of a BRANCH node
-// is the first node of the branch.
-// (NB this is *not* a tree structure: the tail of the branch connects to the
-// thing following the set of BRANCHes.)
-//
-// pattern is coded like:
-//
-// +-----------------+
-// | V
-// <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END
-// | ^ | ^
-// +------+ +----------+
-//
-//
-// +------------------+
-// V |
-// <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END
-// | | ^ ^
-// | +---------------+ |
-// +---------------------------------------------+
-//
-//
-// +----------------------+
-// V |
-// <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END
-// | | ^ ^
-// | +-----------+ |
-// +--------------------------------------------------+
-//
-//
-// +-------------------------+
-// V |
-// <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END
-// | | ^
-// | +----------------+
-// +-----------------------------------------------+
-//
-//
-// <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END
-// | | ^ ^
-// | +----------------+ |
-// +--------------------------------+
-//
-// +---------+
-// | V
-// \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END
-// | | | | ^ ^
-// | | | +-----+ |
-// | | +----------------+ |
-// | +---------------------------+ |
-// +------------------------------------------------------+
-//
-// They all start with a BRANCH for "\|" alternatives, even when there is only
-// one alternative.
-
-#include <assert.h>
-#include <inttypes.h>
-#include <stdbool.h>
-#include <string.h>
-
-#include "nvim/garray.h"
-#include "nvim/profile.h"
-#include "nvim/regexp.h"
-
-// The opcodes are:
-
-// definition number opnd? meaning
-#define END 0 // End of program or NOMATCH operand.
-#define BOL 1 // Match "" at beginning of line.
-#define EOL 2 // Match "" at end of line.
-#define BRANCH 3 // node Match this alternative, or the
- // next...
-#define BACK 4 // Match "", "next" ptr points backward.
-#define EXACTLY 5 // str Match this string.
-#define NOTHING 6 // Match empty string.
-#define STAR 7 // node Match this (simple) thing 0 or more
- // times.
-#define PLUS 8 // node Match this (simple) thing 1 or more
- // times.
-#define MATCH 9 // node match the operand zero-width
-#define NOMATCH 10 // node check for no match with operand
-#define BEHIND 11 // node look behind for a match with operand
-#define NOBEHIND 12 // node look behind for no match with operand
-#define SUBPAT 13 // node match the operand here
-#define BRACE_SIMPLE 14 // node Match this (simple) thing between m and
- // n times (\{m,n\}).
-#define BOW 15 // Match "" after [^a-zA-Z0-9_]
-#define EOW 16 // Match "" at [^a-zA-Z0-9_]
-#define BRACE_LIMITS 17 // nr nr define the min & max for BRACE_SIMPLE
- // and BRACE_COMPLEX.
-#define NEWL 18 // Match line-break
-#define BHPOS 19 // End position for BEHIND or NOBEHIND
-
-// character classes: 20-48 normal, 50-78 include a line-break
-#define ADD_NL 30
-#define FIRST_NL ANY + ADD_NL
-#define ANY 20 // Match any one character.
-#define ANYOF 21 // str Match any character in this string.
-#define ANYBUT 22 // str Match any character not in this
- // string.
-#define IDENT 23 // Match identifier char
-#define SIDENT 24 // Match identifier char but no digit
-#define KWORD 25 // Match keyword char
-#define SKWORD 26 // Match word char but no digit
-#define FNAME 27 // Match file name char
-#define SFNAME 28 // Match file name char but no digit
-#define PRINT 29 // Match printable char
-#define SPRINT 30 // Match printable char but no digit
-#define WHITE 31 // Match whitespace char
-#define NWHITE 32 // Match non-whitespace char
-#define DIGIT 33 // Match digit char
-#define NDIGIT 34 // Match non-digit char
-#define HEX 35 // Match hex char
-#define NHEX 36 // Match non-hex char
-#define OCTAL 37 // Match octal char
-#define NOCTAL 38 // Match non-octal char
-#define WORD 39 // Match word char
-#define NWORD 40 // Match non-word char
-#define HEAD 41 // Match head char
-#define NHEAD 42 // Match non-head char
-#define ALPHA 43 // Match alpha char
-#define NALPHA 44 // Match non-alpha char
-#define LOWER 45 // Match lowercase char
-#define NLOWER 46 // Match non-lowercase char
-#define UPPER 47 // Match uppercase char
-#define NUPPER 48 // Match non-uppercase char
-#define LAST_NL NUPPER + ADD_NL
-// -V:WITH_NL:560
-#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL)
-
-#define MOPEN 80 // -89 Mark this point in input as start of
- // \( … \) subexpr. MOPEN + 0 marks start of
- // match.
-#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks
- // end of match.
-#define BACKREF 100 // -109 node Match same string again \1-\9.
-
-#define ZOPEN 110 // -119 Mark this point in input as start of
- // \z( … \) subexpr.
-#define ZCLOSE 120 // -129 Analogous to ZOPEN.
-#define ZREF 130 // -139 node Match external submatch \z1-\z9
-
-#define BRACE_COMPLEX 140 // -149 node Match nodes between m & n times
-
-#define NOPEN 150 // Mark this point in input as start of
- // \%( subexpr.
-#define NCLOSE 151 // Analogous to NOPEN.
-
-#define MULTIBYTECODE 200 // mbc Match one multi-byte character
-#define RE_BOF 201 // Match "" at beginning of file.
-#define RE_EOF 202 // Match "" at end of file.
-#define CURSOR 203 // Match location of cursor.
-
-#define RE_LNUM 204 // nr cmp Match line number
-#define RE_COL 205 // nr cmp Match column number
-#define RE_VCOL 206 // nr cmp Match virtual column number
-
-#define RE_MARK 207 // mark cmp Match mark position
-#define RE_VISUAL 208 // Match Visual area
-#define RE_COMPOSING 209 // any composing characters
-
-// Flags to be passed up and down.
-#define HASWIDTH 0x1 // Known never to match null string.
-#define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand.
-#define SPSTART 0x4 // Starts with * or +.
-#define HASNL 0x8 // Contains some \n.
-#define HASLOOKBH 0x10 // Contains "\@<=" or "\@<!".
-#define WORST 0 // Worst case.
-
-static int prevchr_len; ///< byte length of previous char
-static int num_complex_braces; ///< Complex \{...} count
-static uint8_t *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
-static long regsize; ///< Code size.
-static int reg_toolong; ///< true when offset out of range
-static uint8_t had_endbrace[NSUBEXP]; ///< flags, true if end of () found
-static long brace_min[10]; ///< Minimums for complex brace repeats
-static long brace_max[10]; ///< Maximums for complex brace repeats
-static int brace_count[10]; ///< Current counts for complex brace repeats
-static int one_exactly = false; ///< only do one char for EXACTLY
-
-// When making changes to classchars also change nfa_classcodes.
-static uint8_t *classchars = (uint8_t *)".iIkKfFpPsSdDxXoOwWhHaAlLuU";
-static int classcodes[] = {
- ANY, IDENT, SIDENT, KWORD, SKWORD,
- FNAME, SFNAME, PRINT, SPRINT,
- WHITE, NWHITE, DIGIT, NDIGIT,
- HEX, NHEX, OCTAL, NOCTAL,
- WORD, NWORD, HEAD, NHEAD,
- ALPHA, NALPHA, LOWER, NLOWER,
- UPPER, NUPPER
-};
-
-// When regcode is set to this value, code is not emitted and size is computed
-// instead.
-#define JUST_CALC_SIZE ((uint8_t *)-1)
-
-// Values for rs_state in regitem_T.
-typedef enum regstate_E {
- RS_NOPEN = 0, // NOPEN and NCLOSE
- RS_MOPEN, // MOPEN + [0-9]
- RS_MCLOSE, // MCLOSE + [0-9]
- RS_ZOPEN, // ZOPEN + [0-9]
- RS_ZCLOSE, // ZCLOSE + [0-9]
- RS_BRANCH, // BRANCH
- RS_BRCPLX_MORE, // BRACE_COMPLEX and trying one more match
- RS_BRCPLX_LONG, // BRACE_COMPLEX and trying longest match
- RS_BRCPLX_SHORT, // BRACE_COMPLEX and trying shortest match
- RS_NOMATCH, // NOMATCH
- RS_BEHIND1, // BEHIND / NOBEHIND matching rest
- RS_BEHIND2, // BEHIND / NOBEHIND matching behind part
- RS_STAR_LONG, // STAR/PLUS/BRACE_SIMPLE longest match
- RS_STAR_SHORT, // STAR/PLUS/BRACE_SIMPLE shortest match
-} regstate_T;
-
-// Structure used to save the current input state, when it needs to be
-// restored after trying a match. Used by reg_save() and reg_restore().
-// Also stores the length of "backpos".
-typedef struct {
- union {
- uint8_t *ptr; // rex.input pointer, for single-line regexp
- lpos_T pos; // rex.input pos, for multi-line regexp
- } rs_u;
- int rs_len;
-} regsave_T;
-
-// struct to save start/end pointer/position in for \(\)
-typedef struct {
- union {
- uint8_t *ptr;
- lpos_T pos;
- } se_u;
-} save_se_T;
-
-// used for BEHIND and NOBEHIND matching
-typedef struct regbehind_S {
- regsave_T save_after;
- regsave_T save_behind;
- int save_need_clear_subexpr;
- save_se_T save_start[NSUBEXP];
- save_se_T save_end[NSUBEXP];
-} regbehind_T;
-
-// When there are alternatives a regstate_T is put on the regstack to remember
-// what we are doing.
-// Before it may be another type of item, depending on rs_state, to remember
-// more things.
-typedef struct regitem_S {
- regstate_T rs_state; // what we are doing, one of RS_ above
- int16_t rs_no; // submatch nr or BEHIND/NOBEHIND
- uint8_t *rs_scan; // current node in program
- union {
- save_se_T sesave;
- regsave_T regsave;
- } rs_un; // room for saving rex.input
-} regitem_T;
-
-// used for STAR, PLUS and BRACE_SIMPLE matching
-typedef struct regstar_S {
- int nextb; // next byte
- int nextb_ic; // next byte reverse case
- long count;
- long minval;
- long maxval;
-} regstar_T;
-
-// used to store input position when a BACK was encountered, so that we now if
-// we made any progress since the last time.
-typedef struct backpos_S {
- uint8_t *bp_scan; // "scan" where BACK was encountered
- regsave_T bp_pos; // last input position
-} backpos_T;
-
-// "regstack" and "backpos" are used by regmatch(). They are kept over calls
-// to avoid invoking malloc() and free() often.
-// "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T
-// or regbehind_T.
-// "backpos_T" is a table with backpos_T for BACK
-static garray_T regstack = GA_EMPTY_INIT_VALUE;
-static garray_T backpos = GA_EMPTY_INIT_VALUE;
-
-static regsave_T behind_pos;
-
-// Both for regstack and backpos tables we use the following strategy of
-// allocation (to reduce malloc/free calls):
-// - Initial size is fairly small.
-// - When needed, the tables are grown bigger (8 times at first, double after
-// that).
-// - After executing the match we free the memory only if the array has grown.
-// Thus the memory is kept allocated when it's at the initial size.
-// This makes it fast while not keeping a lot of memory allocated.
-// A three times speed increase was observed when using many simple patterns.
-#define REGSTACK_INITIAL 2048
-#define BACKPOS_INITIAL 64
-
-// Opcode notes:
-//
-// BRANCH The set of branches constituting a single choice are hooked
-// together with their "next" pointers, since precedence prevents
-// anything being concatenated to any individual branch. The
-// "next" pointer of the last BRANCH in a choice points to the
-// thing following the whole choice. This is also where the
-// final "next" pointer of each individual branch points; each
-// branch starts with the operand node of a BRANCH node.
-//
-// BACK Normal "next" pointers all implicitly point forward; BACK
-// exists to make loop structures possible.
-//
-// STAR,PLUS '=', and complex '*' and '+', are implemented as circular
-// BRANCH structures using BACK. Simple cases (one character
-// per match) are implemented with STAR and PLUS for speed
-// and to minimize recursive plunges.
-//
-// BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX
-// node, and defines the min and max limits to be used for that
-// node.
-//
-// MOPEN,MCLOSE ...are numbered at compile time.
-// ZOPEN,ZCLOSE ...ditto
-///
-//
-//
-// A node is one char of opcode followed by two chars of "next" pointer.
-// "Next" pointers are stored as two 8-bit bytes, high order first. The
-// value is a positive offset from the opcode of the node containing it.
-// An operand, if any, simply follows the node. (Note that much of the
-// code generation knows about this implicit relationship.)
-//
-// Using two bytes for the "next" pointer is vast overkill for most things,
-// but allows patterns to get big without disasters.
-#define OP(p) ((int)(*(p)))
-#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377))
-#define OPERAND(p) ((p) + 3)
-// Obtain an operand that was stored as four bytes, MSB first.
-#define OPERAND_MIN(p) (((long)(p)[3] << 24) + ((long)(p)[4] << 16) \
- + ((long)(p)[5] << 8) + (long)(p)[6])
-// Obtain a second operand stored as four bytes.
-#define OPERAND_MAX(p) OPERAND_MIN((p) + 4)
-// Obtain a second single-byte operand stored after a four bytes operand.
-#define OPERAND_CMP(p) (p)[7]
-
-static uint8_t *reg(int paren, int *flagp);
-
-#ifdef BT_REGEXP_DUMP
-static void regdump(uint8_t *, bt_regprog_T *);
-#endif
-
-#ifdef REGEXP_DEBUG
-static uint8_t *regprop(uint8_t *);
-
-static int regnarrate = 0;
-#endif
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "regexp_bt.c.generated.h"
-#endif
-
-// Setup to parse the regexp. Used once to get the length and once to do it.
-static void regcomp_start(uint8_t *expr, int re_flags) // see vim_regcomp()
-{
- initchr((char *)expr);
- if (re_flags & RE_MAGIC) {
- reg_magic = MAGIC_ON;
- } else {
- reg_magic = MAGIC_OFF;
- }
- reg_string = (re_flags & RE_STRING);
- reg_strict = (re_flags & RE_STRICT);
- get_cpo_flags();
-
- num_complex_braces = 0;
- regnpar = 1;
- CLEAR_FIELD(had_endbrace);
- regnzpar = 1;
- re_has_z = 0;
- regsize = 0L;
- reg_toolong = false;
- regflags = 0;
- had_eol = false;
-}
-
-// Return true if MULTIBYTECODE should be used instead of EXACTLY for
-// character "c".
-static bool use_multibytecode(int c)
-{
- return utf_char2len(c) > 1
- && (re_multi_type(peekchr()) != NOT_MULTI
- || utf_iscomposing(c));
-}
-
-// Emit (if appropriate) a byte of code
-static void regc(int b)
-{
- if (regcode == JUST_CALC_SIZE) {
- regsize++;
- } else {
- *regcode++ = (uint8_t)b;
- }
-}
-
-// Emit (if appropriate) a multi-byte character of code
-static void regmbc(int c)
-{
- if (regcode == JUST_CALC_SIZE) {
- regsize += utf_char2len(c);
- } else {
- regcode += utf_char2bytes(c, (char *)regcode);
- }
-}
-
-// Produce the bytes for equivalence class "c".
-// Currently only handles latin1, latin9 and utf-8.
-// NOTE: When changing this function, also change nfa_emit_equi_class()
-static void reg_equi_class(int c)
-{
- {
- switch (c) {
- // Do not use '\300' style, it results in a negative number.
- case 'A':
- case 0xc0:
- case 0xc1:
- case 0xc2:
- case 0xc3:
- case 0xc4:
- case 0xc5:
- case 0x100:
- case 0x102:
- case 0x104:
- case 0x1cd:
- case 0x1de:
- case 0x1e0:
- case 0x1fa:
- case 0x202:
- case 0x226:
- case 0x23a:
- case 0x1e00:
- case 0x1ea0:
- case 0x1ea2:
- case 0x1ea4:
- case 0x1ea6:
- case 0x1ea8:
- case 0x1eaa:
- case 0x1eac:
- case 0x1eae:
- case 0x1eb0:
- case 0x1eb2:
- case 0x1eb4:
- case 0x1eb6:
- regmbc('A'); regmbc(0xc0); regmbc(0xc1); regmbc(0xc2);
- regmbc(0xc3); regmbc(0xc4); regmbc(0xc5);
- regmbc(0x100); regmbc(0x102); regmbc(0x104);
- regmbc(0x1cd); regmbc(0x1de); regmbc(0x1e0);
- regmbc(0x1fa); regmbc(0x202); regmbc(0x226);
- regmbc(0x23a); regmbc(0x1e00); regmbc(0x1ea0);
- regmbc(0x1ea2); regmbc(0x1ea4); regmbc(0x1ea6);
- regmbc(0x1ea8); regmbc(0x1eaa); regmbc(0x1eac);
- regmbc(0x1eae); regmbc(0x1eb0); regmbc(0x1eb2);
- regmbc(0x1eb4); regmbc(0x1eb6);
- return;
- case 'B':
- case 0x181:
- case 0x243:
- case 0x1e02:
- case 0x1e04:
- case 0x1e06:
- regmbc('B');
- regmbc(0x181); regmbc(0x243); regmbc(0x1e02);
- regmbc(0x1e04); regmbc(0x1e06);
- return;
- case 'C':
- case 0xc7:
- case 0x106:
- case 0x108:
- case 0x10a:
- case 0x10c:
- case 0x187:
- case 0x23b:
- case 0x1e08:
- case 0xa792:
- regmbc('C'); regmbc(0xc7);
- regmbc(0x106); regmbc(0x108); regmbc(0x10a);
- regmbc(0x10c); regmbc(0x187); regmbc(0x23b);
- regmbc(0x1e08); regmbc(0xa792);
- return;
- case 'D':
- case 0x10e:
- case 0x110:
- case 0x18a:
- case 0x1e0a:
- case 0x1e0c:
- case 0x1e0e:
- case 0x1e10:
- case 0x1e12:
- regmbc('D'); regmbc(0x10e); regmbc(0x110);
- regmbc(0x18a); regmbc(0x1e0a); regmbc(0x1e0c);
- regmbc(0x1e0e); regmbc(0x1e10); regmbc(0x1e12);
- return;
- case 'E':
- case 0xc8:
- case 0xc9:
- case 0xca:
- case 0xcb:
- case 0x112:
- case 0x114:
- case 0x116:
- case 0x118:
- case 0x11a:
- case 0x204:
- case 0x206:
- case 0x228:
- case 0x246:
- case 0x1e14:
- case 0x1e16:
- case 0x1e18:
- case 0x1e1a:
- case 0x1e1c:
- case 0x1eb8:
- case 0x1eba:
- case 0x1ebc:
- case 0x1ebe:
- case 0x1ec0:
- case 0x1ec2:
- case 0x1ec4:
- case 0x1ec6:
- regmbc('E'); regmbc(0xc8); regmbc(0xc9);
- regmbc(0xca); regmbc(0xcb); regmbc(0x112);
- regmbc(0x114); regmbc(0x116); regmbc(0x118);
- regmbc(0x11a); regmbc(0x204); regmbc(0x206);
- regmbc(0x228); regmbc(0x246); regmbc(0x1e14);
- regmbc(0x1e16); regmbc(0x1e18); regmbc(0x1e1a);
- regmbc(0x1e1c); regmbc(0x1eb8); regmbc(0x1eba);
- regmbc(0x1ebc); regmbc(0x1ebe); regmbc(0x1ec0);
- regmbc(0x1ec2); regmbc(0x1ec4); regmbc(0x1ec6);
- return;
- case 'F':
- case 0x191:
- case 0x1e1e:
- case 0xa798:
- regmbc('F'); regmbc(0x191); regmbc(0x1e1e);
- regmbc(0xa798);
- return;
- case 'G':
- case 0x11c:
- case 0x11e:
- case 0x120:
- case 0x122:
- case 0x193:
- case 0x1e4:
- case 0x1e6:
- case 0x1f4:
- case 0x1e20:
- case 0xa7a0:
- regmbc('G'); regmbc(0x11c); regmbc(0x11e);
- regmbc(0x120); regmbc(0x122); regmbc(0x193);
- regmbc(0x1e4); regmbc(0x1e6); regmbc(0x1f4);
- regmbc(0x1e20); regmbc(0xa7a0);
- return;
- case 'H':
- case 0x124:
- case 0x126:
- case 0x21e:
- case 0x1e22:
- case 0x1e24:
- case 0x1e26:
- case 0x1e28:
- case 0x1e2a:
- case 0x2c67:
- regmbc('H'); regmbc(0x124); regmbc(0x126);
- regmbc(0x21e); regmbc(0x1e22); regmbc(0x1e24);
- regmbc(0x1e26); regmbc(0x1e28); regmbc(0x1e2a);
- regmbc(0x2c67);
- return;
- case 'I':
- case 0xcc:
- case 0xcd:
- case 0xce:
- case 0xcf:
- case 0x128:
- case 0x12a:
- case 0x12c:
- case 0x12e:
- case 0x130:
- case 0x197:
- case 0x1cf:
- case 0x208:
- case 0x20a:
- case 0x1e2c:
- case 0x1e2e:
- case 0x1ec8:
- case 0x1eca:
- regmbc('I'); regmbc(0xcc); regmbc(0xcd);
- regmbc(0xce); regmbc(0xcf); regmbc(0x128);
- regmbc(0x12a); regmbc(0x12c); regmbc(0x12e);
- regmbc(0x130); regmbc(0x197); regmbc(0x1cf);
- regmbc(0x208); regmbc(0x20a); regmbc(0x1e2c);
- regmbc(0x1e2e); regmbc(0x1ec8); regmbc(0x1eca);
- return;
- case 'J':
- case 0x134:
- case 0x248:
- regmbc('J'); regmbc(0x134); regmbc(0x248);
- return;
- case 'K':
- case 0x136:
- case 0x198:
- case 0x1e8:
- case 0x1e30:
- case 0x1e32:
- case 0x1e34:
- case 0x2c69:
- case 0xa740:
- regmbc('K'); regmbc(0x136); regmbc(0x198);
- regmbc(0x1e8); regmbc(0x1e30); regmbc(0x1e32);
- regmbc(0x1e34); regmbc(0x2c69); regmbc(0xa740);
- return;
- case 'L':
- case 0x139:
- case 0x13b:
- case 0x13d:
- case 0x13f:
- case 0x141:
- case 0x23d:
- case 0x1e36:
- case 0x1e38:
- case 0x1e3a:
- case 0x1e3c:
- case 0x2c60:
- regmbc('L'); regmbc(0x139); regmbc(0x13b);
- regmbc(0x13d); regmbc(0x13f); regmbc(0x141);
- regmbc(0x23d); regmbc(0x1e36); regmbc(0x1e38);
- regmbc(0x1e3a); regmbc(0x1e3c); regmbc(0x2c60);
- return;
- case 'M':
- case 0x1e3e:
- case 0x1e40:
- case 0x1e42:
- regmbc('M'); regmbc(0x1e3e); regmbc(0x1e40);
- regmbc(0x1e42);
- return;
- case 'N':
- case 0xd1:
- case 0x143:
- case 0x145:
- case 0x147:
- case 0x1f8:
- case 0x1e44:
- case 0x1e46:
- case 0x1e48:
- case 0x1e4a:
- case 0xa7a4:
- regmbc('N'); regmbc(0xd1);
- regmbc(0x143); regmbc(0x145); regmbc(0x147);
- regmbc(0x1f8); regmbc(0x1e44); regmbc(0x1e46);
- regmbc(0x1e48); regmbc(0x1e4a); regmbc(0xa7a4);
- return;
- case 'O':
- case 0xd2:
- case 0xd3:
- case 0xd4:
- case 0xd5:
- case 0xd6:
- case 0xd8:
- case 0x14c:
- case 0x14e:
- case 0x150:
- case 0x19f:
- case 0x1a0:
- case 0x1d1:
- case 0x1ea:
- case 0x1ec:
- case 0x1fe:
- case 0x20c:
- case 0x20e:
- case 0x22a:
- case 0x22c:
- case 0x22e:
- case 0x230:
- case 0x1e4c:
- case 0x1e4e:
- case 0x1e50:
- case 0x1e52:
- case 0x1ecc:
- case 0x1ece:
- case 0x1ed0:
- case 0x1ed2:
- case 0x1ed4:
- case 0x1ed6:
- case 0x1ed8:
- case 0x1eda:
- case 0x1edc:
- case 0x1ede:
- case 0x1ee0:
- case 0x1ee2:
- regmbc('O'); regmbc(0xd2); regmbc(0xd3); regmbc(0xd4);
- regmbc(0xd5); regmbc(0xd6); regmbc(0xd8);
- regmbc(0x14c); regmbc(0x14e); regmbc(0x150);
- regmbc(0x19f); regmbc(0x1a0); regmbc(0x1d1);
- regmbc(0x1ea); regmbc(0x1ec); regmbc(0x1fe);
- regmbc(0x20c); regmbc(0x20e); regmbc(0x22a);
- regmbc(0x22c); regmbc(0x22e); regmbc(0x230);
- regmbc(0x1e4c); regmbc(0x1e4e); regmbc(0x1e50);
- regmbc(0x1e52); regmbc(0x1ecc); regmbc(0x1ece);
- regmbc(0x1ed0); regmbc(0x1ed2); regmbc(0x1ed4);
- regmbc(0x1ed6); regmbc(0x1ed8); regmbc(0x1eda);
- regmbc(0x1edc); regmbc(0x1ede); regmbc(0x1ee0);
- regmbc(0x1ee2);
- return;
- case 'P':
- case 0x1a4:
- case 0x1e54:
- case 0x1e56:
- case 0x2c63:
- regmbc('P'); regmbc(0x1a4); regmbc(0x1e54);
- regmbc(0x1e56); regmbc(0x2c63);
- return;
- case 'Q':
- case 0x24a:
- regmbc('Q'); regmbc(0x24a);
- return;
- case 'R':
- case 0x154:
- case 0x156:
- case 0x158:
- case 0x210:
- case 0x212:
- case 0x24c:
- case 0x1e58:
- case 0x1e5a:
- case 0x1e5c:
- case 0x1e5e:
- case 0x2c64:
- case 0xa7a6:
- regmbc('R'); regmbc(0x154); regmbc(0x156);
- regmbc(0x210); regmbc(0x212); regmbc(0x158);
- regmbc(0x24c); regmbc(0x1e58); regmbc(0x1e5a);
- regmbc(0x1e5c); regmbc(0x1e5e); regmbc(0x2c64);
- regmbc(0xa7a6);
- return;
- case 'S':
- case 0x15a:
- case 0x15c:
- case 0x15e:
- case 0x160:
- case 0x218:
- case 0x1e60:
- case 0x1e62:
- case 0x1e64:
- case 0x1e66:
- case 0x1e68:
- case 0x2c7e:
- case 0xa7a8:
- regmbc('S'); regmbc(0x15a); regmbc(0x15c);
- regmbc(0x15e); regmbc(0x160); regmbc(0x218);
- regmbc(0x1e60); regmbc(0x1e62); regmbc(0x1e64);
- regmbc(0x1e66); regmbc(0x1e68); regmbc(0x2c7e);
- regmbc(0xa7a8);
- return;
- case 'T':
- case 0x162:
- case 0x164:
- case 0x166:
- case 0x1ac:
- case 0x1ae:
- case 0x21a:
- case 0x23e:
- case 0x1e6a:
- case 0x1e6c:
- case 0x1e6e:
- case 0x1e70:
- regmbc('T'); regmbc(0x162); regmbc(0x164);
- regmbc(0x166); regmbc(0x1ac); regmbc(0x23e);
- regmbc(0x1ae); regmbc(0x21a); regmbc(0x1e6a);
- regmbc(0x1e6c); regmbc(0x1e6e); regmbc(0x1e70);
- return;
- case 'U':
- case 0xd9:
- case 0xda:
- case 0xdb:
- case 0xdc:
- case 0x168:
- case 0x16a:
- case 0x16c:
- case 0x16e:
- case 0x170:
- case 0x172:
- case 0x1af:
- case 0x1d3:
- case 0x1d5:
- case 0x1d7:
- case 0x1d9:
- case 0x1db:
- case 0x214:
- case 0x216:
- case 0x244:
- case 0x1e72:
- case 0x1e74:
- case 0x1e76:
- case 0x1e78:
- case 0x1e7a:
- case 0x1ee4:
- case 0x1ee6:
- case 0x1ee8:
- case 0x1eea:
- case 0x1eec:
- case 0x1eee:
- case 0x1ef0:
- regmbc('U'); regmbc(0xd9); regmbc(0xda);
- regmbc(0xdb); regmbc(0xdc); regmbc(0x168);
- regmbc(0x16a); regmbc(0x16c); regmbc(0x16e);
- regmbc(0x170); regmbc(0x172); regmbc(0x1af);
- regmbc(0x1d3); regmbc(0x1d5); regmbc(0x1d7);
- regmbc(0x1d9); regmbc(0x1db); regmbc(0x214);
- regmbc(0x216); regmbc(0x244); regmbc(0x1e72);
- regmbc(0x1e74); regmbc(0x1e76); regmbc(0x1e78);
- regmbc(0x1e7a); regmbc(0x1ee4); regmbc(0x1ee6);
- regmbc(0x1ee8); regmbc(0x1eea); regmbc(0x1eec);
- regmbc(0x1eee); regmbc(0x1ef0);
- return;
- case 'V':
- case 0x1b2:
- case 0x1e7c:
- case 0x1e7e:
- regmbc('V'); regmbc(0x1b2); regmbc(0x1e7c);
- regmbc(0x1e7e);
- return;
- case 'W':
- case 0x174:
- case 0x1e80:
- case 0x1e82:
- case 0x1e84:
- case 0x1e86:
- case 0x1e88:
- regmbc('W'); regmbc(0x174); regmbc(0x1e80);
- regmbc(0x1e82); regmbc(0x1e84); regmbc(0x1e86);
- regmbc(0x1e88);
- return;
- case 'X':
- case 0x1e8a:
- case 0x1e8c:
- regmbc('X'); regmbc(0x1e8a); regmbc(0x1e8c);
- return;
- case 'Y':
- case 0xdd:
- case 0x176:
- case 0x178:
- case 0x1b3:
- case 0x232:
- case 0x24e:
- case 0x1e8e:
- case 0x1ef2:
- case 0x1ef6:
- case 0x1ef4:
- case 0x1ef8:
- regmbc('Y'); regmbc(0xdd); regmbc(0x176);
- regmbc(0x178); regmbc(0x1b3); regmbc(0x232);
- regmbc(0x24e); regmbc(0x1e8e); regmbc(0x1ef2);
- regmbc(0x1ef4); regmbc(0x1ef6); regmbc(0x1ef8);
- return;
- case 'Z':
- case 0x179:
- case 0x17b:
- case 0x17d:
- case 0x1b5:
- case 0x1e90:
- case 0x1e92:
- case 0x1e94:
- case 0x2c6b:
- regmbc('Z'); regmbc(0x179); regmbc(0x17b);
- regmbc(0x17d); regmbc(0x1b5); regmbc(0x1e90);
- regmbc(0x1e92); regmbc(0x1e94); regmbc(0x2c6b);
- return;
- case 'a':
- case 0xe0:
- case 0xe1:
- case 0xe2:
- case 0xe3:
- case 0xe4:
- case 0xe5:
- case 0x101:
- case 0x103:
- case 0x105:
- case 0x1ce:
- case 0x1df:
- case 0x1e1:
- case 0x1fb:
- case 0x201:
- case 0x203:
- case 0x227:
- case 0x1d8f:
- case 0x1e01:
- case 0x1e9a:
- case 0x1ea1:
- case 0x1ea3:
- case 0x1ea5:
- case 0x1ea7:
- case 0x1ea9:
- case 0x1eab:
- case 0x1ead:
- case 0x1eaf:
- case 0x1eb1:
- case 0x1eb3:
- case 0x1eb5:
- case 0x1eb7:
- case 0x2c65:
- regmbc('a'); regmbc(0xe0); regmbc(0xe1);
- regmbc(0xe2); regmbc(0xe3); regmbc(0xe4);
- regmbc(0xe5); regmbc(0x101); regmbc(0x103);
- regmbc(0x105); regmbc(0x1ce); regmbc(0x1df);
- regmbc(0x1e1); regmbc(0x1fb); regmbc(0x201);
- regmbc(0x203); regmbc(0x227); regmbc(0x1d8f);
- regmbc(0x1e01); regmbc(0x1e9a); regmbc(0x1ea1);
- regmbc(0x1ea3); regmbc(0x1ea5); regmbc(0x1ea7);
- regmbc(0x1ea9); regmbc(0x1eab); regmbc(0x1ead);
- regmbc(0x1eaf); regmbc(0x1eb1); regmbc(0x1eb3);
- regmbc(0x1eb5); regmbc(0x1eb7); regmbc(0x2c65);
- return;
- case 'b':
- case 0x180:
- case 0x253:
- case 0x1d6c:
- case 0x1d80:
- case 0x1e03:
- case 0x1e05:
- case 0x1e07:
- regmbc('b');
- regmbc(0x180); regmbc(0x253); regmbc(0x1d6c);
- regmbc(0x1d80); regmbc(0x1e03); regmbc(0x1e05);
- regmbc(0x1e07);
- return;
- case 'c':
- case 0xe7:
- case 0x107:
- case 0x109:
- case 0x10b:
- case 0x10d:
- case 0x188:
- case 0x23c:
- case 0x1e09:
- case 0xa793:
- case 0xa794:
- regmbc('c'); regmbc(0xe7); regmbc(0x107);
- regmbc(0x109); regmbc(0x10b); regmbc(0x10d);
- regmbc(0x188); regmbc(0x23c); regmbc(0x1e09);
- regmbc(0xa793); regmbc(0xa794);
- return;
- case 'd':
- case 0x10f:
- case 0x111:
- case 0x257:
- case 0x1d6d:
- case 0x1d81:
- case 0x1d91:
- case 0x1e0b:
- case 0x1e0d:
- case 0x1e0f:
- case 0x1e11:
- case 0x1e13:
- regmbc('d'); regmbc(0x10f); regmbc(0x111);
- regmbc(0x257); regmbc(0x1d6d); regmbc(0x1d81);
- regmbc(0x1d91); regmbc(0x1e0b); regmbc(0x1e0d);
- regmbc(0x1e0f); regmbc(0x1e11); regmbc(0x1e13);
- return;
- case 'e':
- case 0xe8:
- case 0xe9:
- case 0xea:
- case 0xeb:
- case 0x113:
- case 0x115:
- case 0x117:
- case 0x119:
- case 0x11b:
- case 0x205:
- case 0x207:
- case 0x229:
- case 0x247:
- case 0x1d92:
- case 0x1e15:
- case 0x1e17:
- case 0x1e19:
- case 0x1e1b:
- case 0x1eb9:
- case 0x1ebb:
- case 0x1e1d:
- case 0x1ebd:
- case 0x1ebf:
- case 0x1ec1:
- case 0x1ec3:
- case 0x1ec5:
- case 0x1ec7:
- regmbc('e'); regmbc(0xe8); regmbc(0xe9);
- regmbc(0xea); regmbc(0xeb); regmbc(0x113);
- regmbc(0x115); regmbc(0x117); regmbc(0x119);
- regmbc(0x11b); regmbc(0x205); regmbc(0x207);
- regmbc(0x229); regmbc(0x247); regmbc(0x1d92);
- regmbc(0x1e15); regmbc(0x1e17); regmbc(0x1e19);
- regmbc(0x1e1b); regmbc(0x1e1d); regmbc(0x1eb9);
- regmbc(0x1ebb); regmbc(0x1ebd); regmbc(0x1ebf);
- regmbc(0x1ec1); regmbc(0x1ec3); regmbc(0x1ec5);
- regmbc(0x1ec7);
- return;
- case 'f':
- case 0x192:
- case 0x1d6e:
- case 0x1d82:
- case 0x1e1f:
- case 0xa799:
- regmbc('f'); regmbc(0x192); regmbc(0x1d6e);
- regmbc(0x1d82); regmbc(0x1e1f); regmbc(0xa799);
- return;
- case 'g':
- case 0x11d:
- case 0x11f:
- case 0x121:
- case 0x123:
- case 0x1e5:
- case 0x1e7:
- case 0x260:
- case 0x1f5:
- case 0x1d83:
- case 0x1e21:
- case 0xa7a1:
- regmbc('g'); regmbc(0x11d); regmbc(0x11f);
- regmbc(0x121); regmbc(0x123); regmbc(0x1e5);
- regmbc(0x1e7); regmbc(0x1f5); regmbc(0x260);
- regmbc(0x1d83); regmbc(0x1e21); regmbc(0xa7a1);
- return;
- case 'h':
- case 0x125:
- case 0x127:
- case 0x21f:
- case 0x1e23:
- case 0x1e25:
- case 0x1e27:
- case 0x1e29:
- case 0x1e2b:
- case 0x1e96:
- case 0x2c68:
- case 0xa795:
- regmbc('h'); regmbc(0x125); regmbc(0x127);
- regmbc(0x21f); regmbc(0x1e23); regmbc(0x1e25);
- regmbc(0x1e27); regmbc(0x1e29); regmbc(0x1e2b);
- regmbc(0x1e96); regmbc(0x2c68); regmbc(0xa795);
- return;
- case 'i':
- case 0xec:
- case 0xed:
- case 0xee:
- case 0xef:
- case 0x129:
- case 0x12b:
- case 0x12d:
- case 0x12f:
- case 0x1d0:
- case 0x209:
- case 0x20b:
- case 0x268:
- case 0x1d96:
- case 0x1e2d:
- case 0x1e2f:
- case 0x1ec9:
- case 0x1ecb:
- regmbc('i'); regmbc(0xec); regmbc(0xed);
- regmbc(0xee); regmbc(0xef); regmbc(0x129);
- regmbc(0x12b); regmbc(0x12d); regmbc(0x12f);
- regmbc(0x1d0); regmbc(0x209); regmbc(0x20b);
- regmbc(0x268); regmbc(0x1d96); regmbc(0x1e2d);
- regmbc(0x1e2f); regmbc(0x1ec9); regmbc(0x1ecb);
- return;
- case 'j':
- case 0x135:
- case 0x1f0:
- case 0x249:
- regmbc('j'); regmbc(0x135); regmbc(0x1f0);
- regmbc(0x249);
- return;
- case 'k':
- case 0x137:
- case 0x199:
- case 0x1e9:
- case 0x1d84:
- case 0x1e31:
- case 0x1e33:
- case 0x1e35:
- case 0x2c6a:
- case 0xa741:
- regmbc('k'); regmbc(0x137); regmbc(0x199);
- regmbc(0x1e9); regmbc(0x1d84); regmbc(0x1e31);
- regmbc(0x1e33); regmbc(0x1e35); regmbc(0x2c6a);
- regmbc(0xa741);
- return;
- case 'l':
- case 0x13a:
- case 0x13c:
- case 0x13e:
- case 0x140:
- case 0x142:
- case 0x19a:
- case 0x1e37:
- case 0x1e39:
- case 0x1e3b:
- case 0x1e3d:
- case 0x2c61:
- regmbc('l'); regmbc(0x13a); regmbc(0x13c);
- regmbc(0x13e); regmbc(0x140); regmbc(0x142);
- regmbc(0x19a); regmbc(0x1e37); regmbc(0x1e39);
- regmbc(0x1e3b); regmbc(0x1e3d); regmbc(0x2c61);
- return;
- case 'm':
- case 0x1d6f:
- case 0x1e3f:
- case 0x1e41:
- case 0x1e43:
- regmbc('m'); regmbc(0x1d6f); regmbc(0x1e3f);
- regmbc(0x1e41); regmbc(0x1e43);
- return;
- case 'n':
- case 0xf1:
- case 0x144:
- case 0x146:
- case 0x148:
- case 0x149:
- case 0x1f9:
- case 0x1d70:
- case 0x1d87:
- case 0x1e45:
- case 0x1e47:
- case 0x1e49:
- case 0x1e4b:
- case 0xa7a5:
- regmbc('n'); regmbc(0xf1); regmbc(0x144);
- regmbc(0x146); regmbc(0x148); regmbc(0x149);
- regmbc(0x1f9); regmbc(0x1d70); regmbc(0x1d87);
- regmbc(0x1e45); regmbc(0x1e47); regmbc(0x1e49);
- regmbc(0x1e4b); regmbc(0xa7a5);
- return;
- case 'o':
- case 0xf2:
- case 0xf3:
- case 0xf4:
- case 0xf5:
- case 0xf6:
- case 0xf8:
- case 0x14d:
- case 0x14f:
- case 0x151:
- case 0x1a1:
- case 0x1d2:
- case 0x1eb:
- case 0x1ed:
- case 0x1ff:
- case 0x20d:
- case 0x20f:
- case 0x22b:
- case 0x22d:
- case 0x22f:
- case 0x231:
- case 0x275:
- case 0x1e4d:
- case 0x1e4f:
- case 0x1e51:
- case 0x1e53:
- case 0x1ecd:
- case 0x1ecf:
- case 0x1ed1:
- case 0x1ed3:
- case 0x1ed5:
- case 0x1ed7:
- case 0x1ed9:
- case 0x1edb:
- case 0x1edd:
- case 0x1edf:
- case 0x1ee1:
- case 0x1ee3:
- regmbc('o'); regmbc(0xf2); regmbc(0xf3);
- regmbc(0xf4); regmbc(0xf5); regmbc(0xf6);
- regmbc(0xf8); regmbc(0x14d); regmbc(0x14f);
- regmbc(0x151); regmbc(0x1a1); regmbc(0x1d2);
- regmbc(0x1eb); regmbc(0x1ed); regmbc(0x1ff);
- regmbc(0x20d); regmbc(0x20f); regmbc(0x22b);
- regmbc(0x22d); regmbc(0x22f); regmbc(0x231);
- regmbc(0x275); regmbc(0x1e4d); regmbc(0x1e4f);
- regmbc(0x1e51); regmbc(0x1e53); regmbc(0x1ecd);
- regmbc(0x1ecf); regmbc(0x1ed1); regmbc(0x1ed3);
- regmbc(0x1ed5); regmbc(0x1ed7); regmbc(0x1ed9);
- regmbc(0x1edb); regmbc(0x1edd); regmbc(0x1edf);
- regmbc(0x1ee1); regmbc(0x1ee3);
- return;
- case 'p':
- case 0x1a5:
- case 0x1d71:
- case 0x1d88:
- case 0x1d7d:
- case 0x1e55:
- case 0x1e57:
- regmbc('p'); regmbc(0x1a5); regmbc(0x1d71);
- regmbc(0x1d7d); regmbc(0x1d88); regmbc(0x1e55);
- regmbc(0x1e57);
- return;
- case 'q':
- case 0x24b:
- case 0x2a0:
- regmbc('q'); regmbc(0x24b); regmbc(0x2a0);
- return;
- case 'r':
- case 0x155:
- case 0x157:
- case 0x159:
- case 0x211:
- case 0x213:
- case 0x24d:
- case 0x27d:
- case 0x1d72:
- case 0x1d73:
- case 0x1d89:
- case 0x1e59:
- case 0x1e5b:
- case 0x1e5d:
- case 0x1e5f:
- case 0xa7a7:
- regmbc('r'); regmbc(0x155); regmbc(0x157);
- regmbc(0x159); regmbc(0x211); regmbc(0x213);
- regmbc(0x24d); regmbc(0x1d72); regmbc(0x1d73);
- regmbc(0x1d89); regmbc(0x1e59); regmbc(0x27d);
- regmbc(0x1e5b); regmbc(0x1e5d); regmbc(0x1e5f);
- regmbc(0xa7a7);
- return;
- case 's':
- case 0x15b:
- case 0x15d:
- case 0x15f:
- case 0x161:
- case 0x1e61:
- case 0x219:
- case 0x23f:
- case 0x1d74:
- case 0x1d8a:
- case 0x1e63:
- case 0x1e65:
- case 0x1e67:
- case 0x1e69:
- case 0xa7a9:
- regmbc('s'); regmbc(0x15b); regmbc(0x15d);
- regmbc(0x15f); regmbc(0x161); regmbc(0x23f);
- regmbc(0x219); regmbc(0x1d74); regmbc(0x1d8a);
- regmbc(0x1e61); regmbc(0x1e63); regmbc(0x1e65);
- regmbc(0x1e67); regmbc(0x1e69); regmbc(0xa7a9);
- return;
- case 't':
- case 0x163:
- case 0x165:
- case 0x167:
- case 0x1ab:
- case 0x1ad:
- case 0x21b:
- case 0x288:
- case 0x1d75:
- case 0x1e6b:
- case 0x1e6d:
- case 0x1e6f:
- case 0x1e71:
- case 0x1e97:
- case 0x2c66:
- regmbc('t'); regmbc(0x163); regmbc(0x165);
- regmbc(0x167); regmbc(0x1ab); regmbc(0x21b);
- regmbc(0x1ad); regmbc(0x288); regmbc(0x1d75);
- regmbc(0x1e6b); regmbc(0x1e6d); regmbc(0x1e6f);
- regmbc(0x1e71); regmbc(0x1e97); regmbc(0x2c66);
- return;
- case 'u':
- case 0xf9:
- case 0xfa:
- case 0xfb:
- case 0xfc:
- case 0x169:
- case 0x16b:
- case 0x16d:
- case 0x16f:
- case 0x171:
- case 0x173:
- case 0x1b0:
- case 0x1d4:
- case 0x1d6:
- case 0x1d8:
- case 0x1da:
- case 0x1dc:
- case 0x215:
- case 0x217:
- case 0x289:
- case 0x1e73:
- case 0x1d7e:
- case 0x1d99:
- case 0x1e75:
- case 0x1e77:
- case 0x1e79:
- case 0x1e7b:
- case 0x1ee5:
- case 0x1ee7:
- case 0x1ee9:
- case 0x1eeb:
- case 0x1eed:
- case 0x1eef:
- case 0x1ef1:
- regmbc('u'); regmbc(0xf9); regmbc(0xfa);
- regmbc(0xfb); regmbc(0xfc); regmbc(0x169);
- regmbc(0x16b); regmbc(0x16d); regmbc(0x16f);
- regmbc(0x171); regmbc(0x173); regmbc(0x1d6);
- regmbc(0x1d8); regmbc(0x1da); regmbc(0x1dc);
- regmbc(0x215); regmbc(0x217); regmbc(0x1b0);
- regmbc(0x1d4); regmbc(0x289); regmbc(0x1d7e);
- regmbc(0x1d99); regmbc(0x1e73); regmbc(0x1e75);
- regmbc(0x1e77); regmbc(0x1e79); regmbc(0x1e7b);
- regmbc(0x1ee5); regmbc(0x1ee7); regmbc(0x1ee9);
- regmbc(0x1eeb); regmbc(0x1eed); regmbc(0x1eef);
- regmbc(0x1ef1);
- return;
- case 'v':
- case 0x28b:
- case 0x1d8c:
- case 0x1e7d:
- case 0x1e7f:
- regmbc('v'); regmbc(0x28b); regmbc(0x1d8c);
- regmbc(0x1e7d); regmbc(0x1e7f);
- return;
- case 'w':
- case 0x175:
- case 0x1e81:
- case 0x1e83:
- case 0x1e85:
- case 0x1e87:
- case 0x1e89:
- case 0x1e98:
- regmbc('w'); regmbc(0x175); regmbc(0x1e81);
- regmbc(0x1e83); regmbc(0x1e85); regmbc(0x1e87);
- regmbc(0x1e89); regmbc(0x1e98);
- return;
- case 'x':
- case 0x1e8b:
- case 0x1e8d:
- regmbc('x'); regmbc(0x1e8b); regmbc(0x1e8d);
- return;
- case 'y':
- case 0xfd:
- case 0xff:
- case 0x177:
- case 0x1b4:
- case 0x233:
- case 0x24f:
- case 0x1e8f:
- case 0x1e99:
- case 0x1ef3:
- case 0x1ef5:
- case 0x1ef7:
- case 0x1ef9:
- regmbc('y'); regmbc(0xfd); regmbc(0xff);
- regmbc(0x177); regmbc(0x1b4); regmbc(0x233);
- regmbc(0x24f); regmbc(0x1e8f); regmbc(0x1e99);
- regmbc(0x1ef3); regmbc(0x1ef5); regmbc(0x1ef7);
- regmbc(0x1ef9);
- return;
- case 'z':
- case 0x17a:
- case 0x17c:
- case 0x17e:
- case 0x1b6:
- case 0x1d76:
- case 0x1d8e:
- case 0x1e91:
- case 0x1e93:
- case 0x1e95:
- case 0x2c6c:
- regmbc('z'); regmbc(0x17a); regmbc(0x17c);
- regmbc(0x17e); regmbc(0x1b6); regmbc(0x1d76);
- regmbc(0x1d8e); regmbc(0x1e91); regmbc(0x1e93);
- regmbc(0x1e95); regmbc(0x2c6c);
- return;
- }
- }
- regmbc(c);
-}
-
-// Emit a node.
-// Return pointer to generated code.
-static uint8_t *regnode(int op)
-{
- uint8_t *ret;
-
- ret = regcode;
- if (ret == JUST_CALC_SIZE) {
- regsize += 3;
- } else {
- *regcode++ = (uint8_t)op;
- *regcode++ = NUL; // Null "next" pointer.
- *regcode++ = NUL;
- }
- return ret;
-}
-
-// Write a four bytes number at "p" and return pointer to the next char.
-static uint8_t *re_put_uint32(uint8_t *p, uint32_t val)
-{
- *p++ = (uint8_t)((val >> 24) & 0377);
- *p++ = (uint8_t)((val >> 16) & 0377);
- *p++ = (uint8_t)((val >> 8) & 0377);
- *p++ = (uint8_t)(val & 0377);
- return p;
-}
-
-// regnext - dig the "next" pointer out of a node
-// Returns NULL when calculating size, when there is no next item and when
-// there is an error.
-static uint8_t *regnext(uint8_t *p)
- FUNC_ATTR_NONNULL_ALL
-{
- int offset;
-
- if (p == JUST_CALC_SIZE || reg_toolong) {
- return NULL;
- }
-
- offset = NEXT(p);
- if (offset == 0) {
- return NULL;
- }
-
- if (OP(p) == BACK) {
- return p - offset;
- } else {
- return p + offset;
- }
-}
-
-// Set the next-pointer at the end of a node chain.
-static void regtail(uint8_t *p, uint8_t *val)
-{
- int offset;
-
- if (p == JUST_CALC_SIZE) {
- return;
- }
-
- // Find last node.
- uint8_t *scan = p;
- for (;;) {
- uint8_t *temp = regnext(scan);
- if (temp == NULL) {
- break;
- }
- scan = temp;
- }
-
- if (OP(scan) == BACK) {
- offset = (int)(scan - val);
- } else {
- offset = (int)(val - scan);
- }
- // When the offset uses more than 16 bits it can no longer fit in the two
- // bytes available. Use a global flag to avoid having to check return
- // values in too many places.
- if (offset > 0xffff) {
- reg_toolong = true;
- } else {
- *(scan + 1) = (uint8_t)(((unsigned)offset >> 8) & 0377);
- *(scan + 2) = (uint8_t)(offset & 0377);
- }
-}
-
-// Like regtail, on item after a BRANCH; nop if none.
-static void regoptail(uint8_t *p, uint8_t *val)
-{
- // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless"
- if (p == NULL || p == JUST_CALC_SIZE
- || (OP(p) != BRANCH
- && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) {
- return;
- }
- regtail(OPERAND(p), val);
-}
-
-// Insert an operator in front of already-emitted operand
-//
-// Means relocating the operand.
-static void reginsert(int op, uint8_t *opnd)
-{
- uint8_t *src;
- uint8_t *dst;
- uint8_t *place;
-
- if (regcode == JUST_CALC_SIZE) {
- regsize += 3;
- return;
- }
- src = regcode;
- regcode += 3;
- dst = regcode;
- while (src > opnd) {
- *--dst = *--src;
- }
-
- place = opnd; // Op node, where operand used to be.
- *place++ = (uint8_t)op;
- *place++ = NUL;
- *place = NUL;
-}
-
-// Insert an operator in front of already-emitted operand.
-// Add a number to the operator.
-static void reginsert_nr(int op, long val, uint8_t *opnd)
-{
- uint8_t *src;
- uint8_t *dst;
- uint8_t *place;
-
- if (regcode == JUST_CALC_SIZE) {
- regsize += 7;
- return;
- }
- src = regcode;
- regcode += 7;
- dst = regcode;
- while (src > opnd) {
- *--dst = *--src;
- }
-
- place = opnd; // Op node, where operand used to be.
- *place++ = (uint8_t)op;
- *place++ = NUL;
- *place++ = NUL;
- assert(val >= 0 && (uintmax_t)val <= UINT32_MAX);
- re_put_uint32(place, (uint32_t)val);
-}
-
-// Insert an operator in front of already-emitted operand.
-// The operator has the given limit values as operands. Also set next pointer.
-//
-// Means relocating the operand.
-static void reginsert_limits(int op, long minval, long maxval, uint8_t *opnd)
-{
- uint8_t *src;
- uint8_t *dst;
- uint8_t *place;
-
- if (regcode == JUST_CALC_SIZE) {
- regsize += 11;
- return;
- }
- src = regcode;
- regcode += 11;
- dst = regcode;
- while (src > opnd) {
- *--dst = *--src;
- }
-
- place = opnd; // Op node, where operand used to be.
- *place++ = (uint8_t)op;
- *place++ = NUL;
- *place++ = NUL;
- assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX);
- place = re_put_uint32(place, (uint32_t)minval);
- assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX);
- place = re_put_uint32(place, (uint32_t)maxval);
- regtail(opnd, place);
-}
-
-/// Return true if the back reference is legal. We must have seen the close
-/// brace.
-/// TODO(vim): Should also check that we don't refer to something repeated
-/// (+*=): what instance of the repetition should we match?
-static int seen_endbrace(int refnum)
-{
- if (!had_endbrace[refnum]) {
- uint8_t *p;
-
- // Trick: check if "@<=" or "@<!" follows, in which case
- // the \1 can appear before the referenced match.
- for (p = (uint8_t *)regparse; *p != NUL; p++) {
- if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) {
- break;
- }
- }
-
- if (*p == NUL) {
- emsg(_("E65: Illegal back reference"));
- rc_did_emsg = true;
- return false;
- }
- }
- return true;
-}
-
-// Parse the lowest level.
-//
-// Optimization: gobbles an entire sequence of ordinary characters so that
-// it can turn them into a single node, which is smaller to store and
-// faster to run. Don't do this when one_exactly is set.
-static uint8_t *regatom(int *flagp)
-{
- uint8_t *ret;
- int flags;
- int c;
- uint8_t *p;
- int extra = 0;
- int save_prev_at_start = prev_at_start;
-
- *flagp = WORST; // Tentatively.
-
- c = getchr();
- switch (c) {
- case Magic('^'):
- ret = regnode(BOL);
- break;
-
- case Magic('$'):
- ret = regnode(EOL);
- had_eol = true;
- break;
-
- case Magic('<'):
- ret = regnode(BOW);
- break;
-
- case Magic('>'):
- ret = regnode(EOW);
- break;
-
- case Magic('_'):
- c = no_Magic(getchr());
- if (c == '^') { // "\_^" is start-of-line
- ret = regnode(BOL);
- break;
- }
- if (c == '$') { // "\_$" is end-of-line
- ret = regnode(EOL);
- had_eol = true;
- break;
- }
-
- extra = ADD_NL;
- *flagp |= HASNL;
-
- // "\_[" is character range plus newline
- if (c == '[') {
- goto collection;
- }
-
- // "\_x" is character class plus newline
- FALLTHROUGH;
-
- // Character classes.
- case Magic('.'):
- case Magic('i'):
- case Magic('I'):
- case Magic('k'):
- case Magic('K'):
- case Magic('f'):
- case Magic('F'):
- case Magic('p'):
- case Magic('P'):
- case Magic('s'):
- case Magic('S'):
- case Magic('d'):
- case Magic('D'):
- case Magic('x'):
- case Magic('X'):
- case Magic('o'):
- case Magic('O'):
- case Magic('w'):
- case Magic('W'):
- case Magic('h'):
- case Magic('H'):
- case Magic('a'):
- case Magic('A'):
- case Magic('l'):
- case Magic('L'):
- case Magic('u'):
- case Magic('U'):
- p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
- if (p == NULL) {
- EMSG_RET_NULL(_("E63: invalid use of \\_"));
- }
- // When '.' is followed by a composing char ignore the dot, so that
- // the composing char is matched here.
- if (c == Magic('.') && utf_iscomposing(peekchr())) {
- c = getchr();
- goto do_multibyte;
- }
- ret = regnode(classcodes[p - classchars] + extra);
- *flagp |= HASWIDTH | SIMPLE;
- break;
-
- case Magic('n'):
- if (reg_string) {
- // In a string "\n" matches a newline character.
- ret = regnode(EXACTLY);
- regc(NL);
- regc(NUL);
- *flagp |= HASWIDTH | SIMPLE;
- } else {
- // In buffer text "\n" matches the end of a line.
- ret = regnode(NEWL);
- *flagp |= HASWIDTH | HASNL;
- }
- break;
-
- case Magic('('):
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- ret = reg(REG_PAREN, &flags);
- if (ret == NULL) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
- break;
-
- case NUL:
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier.
- // NOTREACHED
-
- case Magic('='):
- case Magic('?'):
- case Magic('+'):
- case Magic('@'):
- case Magic('{'):
- case Magic('*'):
- c = no_Magic(c);
- EMSG3_RET_NULL(_("E64: %s%c follows nothing"),
- (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL), c);
- // NOTREACHED
-
- case Magic('~'): // previous substitute pattern
- if (reg_prev_sub != NULL) {
- uint8_t *lp;
-
- ret = regnode(EXACTLY);
- lp = (uint8_t *)reg_prev_sub;
- while (*lp != NUL) {
- regc(*lp++);
- }
- regc(NUL);
- if (*reg_prev_sub != NUL) {
- *flagp |= HASWIDTH;
- if ((lp - (uint8_t *)reg_prev_sub) == 1) {
- *flagp |= SIMPLE;
- }
- }
- } else {
- EMSG_RET_NULL(_(e_nopresub));
- }
- break;
-
- case Magic('1'):
- case Magic('2'):
- case Magic('3'):
- case Magic('4'):
- case Magic('5'):
- case Magic('6'):
- case Magic('7'):
- case Magic('8'):
- case Magic('9'): {
- int refnum;
-
- refnum = c - Magic('0');
- if (!seen_endbrace(refnum)) {
- return NULL;
- }
- ret = regnode(BACKREF + refnum);
- }
- break;
-
- case Magic('z'):
- c = no_Magic(getchr());
- switch (c) {
- case '(':
- if ((reg_do_extmatch & REX_SET) == 0) {
- EMSG_RET_NULL(_(e_z_not_allowed));
- }
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- ret = reg(REG_ZPAREN, &flags);
- if (ret == NULL) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH);
- re_has_z = REX_SET;
- break;
-
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- if ((reg_do_extmatch & REX_USE) == 0) {
- EMSG_RET_NULL(_(e_z1_not_allowed));
- }
- ret = regnode(ZREF + c - '0');
- re_has_z = REX_USE;
- break;
-
- case 's':
- ret = regnode(MOPEN + 0);
- if (!re_mult_next("\\zs")) {
- return NULL;
- }
- break;
-
- case 'e':
- ret = regnode(MCLOSE + 0);
- if (!re_mult_next("\\ze")) {
- return NULL;
- }
- break;
-
- default:
- EMSG_RET_NULL(_("E68: Invalid character after \\z"));
- }
- break;
-
- case Magic('%'):
- c = no_Magic(getchr());
- switch (c) {
- // () without a back reference
- case '(':
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- ret = reg(REG_NPAREN, &flags);
- if (ret == NULL) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
- break;
-
- // Catch \%^ and \%$ regardless of where they appear in the
- // pattern -- regardless of whether or not it makes sense.
- case '^':
- ret = regnode(RE_BOF);
- break;
-
- case '$':
- ret = regnode(RE_EOF);
- break;
-
- case '#':
- if (regparse[0] == '=' && regparse[1] >= 48 && regparse[1] <= 50) {
- // misplaced \%#=1
- semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
- return FAIL;
- }
- ret = regnode(CURSOR);
- break;
-
- case 'V':
- ret = regnode(RE_VISUAL);
- break;
-
- case 'C':
- ret = regnode(RE_COMPOSING);
- break;
-
- // \%[abc]: Emit as a list of branches, all ending at the last
- // branch which matches nothing.
- case '[':
- if (one_exactly) { // doesn't nest
- EMSG_ONE_RET_NULL;
- }
- {
- uint8_t *lastbranch;
- uint8_t *lastnode = NULL;
- uint8_t *br;
-
- ret = NULL;
- while ((c = getchr()) != ']') {
- if (c == NUL) {
- EMSG2_RET_NULL(_(e_missing_sb),
- reg_magic == MAGIC_ALL);
- }
- br = regnode(BRANCH);
- if (ret == NULL) {
- ret = br;
- } else {
- regtail(lastnode, br);
- if (reg_toolong) {
- return NULL;
- }
- }
-
- ungetchr();
- one_exactly = true;
- lastnode = regatom(flagp);
- one_exactly = false;
- if (lastnode == NULL) {
- return NULL;
- }
- }
- if (ret == NULL) {
- EMSG2_RET_NULL(_(e_empty_sb),
- reg_magic == MAGIC_ALL);
- }
- lastbranch = regnode(BRANCH);
- br = regnode(NOTHING);
- if (ret != JUST_CALC_SIZE) {
- regtail(lastnode, br);
- regtail(lastbranch, br);
- // connect all branches to the NOTHING
- // branch at the end
- for (br = ret; br != lastnode;) {
- if (OP(br) == BRANCH) {
- regtail(br, lastbranch);
- if (reg_toolong) {
- return NULL;
- }
- br = OPERAND(br);
- } else {
- br = regnext(br);
- }
- }
- }
- *flagp &= ~(HASWIDTH | SIMPLE);
- break;
- }
-
- case 'd': // %d123 decimal
- case 'o': // %o123 octal
- case 'x': // %xab hex 2
- case 'u': // %uabcd hex 4
- case 'U': // %U1234abcd hex 8
- {
- int64_t i;
-
- switch (c) {
- case 'd':
- i = getdecchrs(); break;
- case 'o':
- i = getoctchrs(); break;
- case 'x':
- i = gethexchrs(2); break;
- case 'u':
- i = gethexchrs(4); break;
- case 'U':
- i = gethexchrs(8); break;
- default:
- i = -1; break;
- }
-
- if (i < 0 || i > INT_MAX) {
- EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"),
- reg_magic == MAGIC_ALL);
- }
- if (use_multibytecode((int)i)) {
- ret = regnode(MULTIBYTECODE);
- } else {
- ret = regnode(EXACTLY);
- }
- if (i == 0) {
- regc(0x0a);
- } else {
- regmbc((int)i);
- }
- regc(NUL);
- *flagp |= HASWIDTH;
- break;
- }
-
- default:
- if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') {
- uint32_t n = 0;
- int cmp;
- bool cur = false;
- bool got_digit = false;
-
- cmp = c;
- if (cmp == '<' || cmp == '>') {
- c = getchr();
- }
- if (no_Magic(c) == '.') {
- cur = true;
- c = getchr();
- }
- while (ascii_isdigit(c)) {
- got_digit = true;
- n = n * 10 + (uint32_t)(c - '0');
- c = getchr();
- }
- if (c == '\'' && n == 0) {
- // "\%'m", "\%<'m" and "\%>'m": Mark
- c = getchr();
- ret = regnode(RE_MARK);
- if (ret == JUST_CALC_SIZE) {
- regsize += 2;
- } else {
- *regcode++ = (uint8_t)c;
- *regcode++ = (uint8_t)cmp;
- }
- break;
- } else if ((c == 'l' || c == 'c' || c == 'v') && (cur || got_digit)) {
- if (cur && n) {
- semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
- rc_did_emsg = true;
- return NULL;
- }
- if (c == 'l') {
- if (cur) {
- n = (uint32_t)curwin->w_cursor.lnum;
- }
- ret = regnode(RE_LNUM);
- if (save_prev_at_start) {
- at_start = true;
- }
- } else if (c == 'c') {
- if (cur) {
- n = (uint32_t)curwin->w_cursor.col;
- n++;
- }
- ret = regnode(RE_COL);
- } else {
- if (cur) {
- colnr_T vcol = 0;
- getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
- n = (uint32_t)(++vcol);
- }
- ret = regnode(RE_VCOL);
- }
- if (ret == JUST_CALC_SIZE) {
- regsize += 5;
- } else {
- // put the number and the optional
- // comparator after the opcode
- regcode = re_put_uint32(regcode, n);
- *regcode++ = (uint8_t)cmp;
- }
- break;
- }
- }
-
- EMSG2_RET_NULL(_("E71: Invalid character after %s%%"),
- reg_magic == MAGIC_ALL);
- }
- break;
-
- case Magic('['):
-collection:
- {
- uint8_t *lp;
-
- // If there is no matching ']', we assume the '[' is a normal
- // character. This makes 'incsearch' and ":help [" work.
- lp = (uint8_t *)skip_anyof(regparse);
- if (*lp == ']') { // there is a matching ']'
- int startc = -1; // > 0 when next '-' is a range
- int endc;
-
- // In a character class, different parsing rules apply.
- // Not even \ is special anymore, nothing is.
- if (*regparse == '^') { // Complement of range.
- ret = regnode(ANYBUT + extra);
- regparse++;
- } else {
- ret = regnode(ANYOF + extra);
- }
-
- // At the start ']' and '-' mean the literal character.
- if (*regparse == ']' || *regparse == '-') {
- startc = *regparse;
- regc(*regparse++);
- }
-
- while (*regparse != NUL && *regparse != ']') {
- if (*regparse == '-') {
- regparse++;
- // The '-' is not used for a range at the end and
- // after or before a '\n'.
- if (*regparse == ']' || *regparse == NUL
- || startc == -1
- || (regparse[0] == '\\' && regparse[1] == 'n')) {
- regc('-');
- startc = '-'; // [--x] is a range
- } else {
- // Also accept "a-[.z.]"
- endc = 0;
- if (*regparse == '[') {
- endc = get_coll_element(&regparse);
- }
- if (endc == 0) {
- endc = mb_ptr2char_adv((const char **)&regparse);
- }
-
- // Handle \o40, \x20 and \u20AC style sequences
- if (endc == '\\' && !reg_cpo_lit) {
- endc = coll_get_char();
- }
-
- if (startc > endc) {
- EMSG_RET_NULL(_(e_reverse_range));
- }
- if (utf_char2len(startc) > 1
- || utf_char2len(endc) > 1) {
- // Limit to a range of 256 chars
- if (endc > startc + 256) {
- EMSG_RET_NULL(_(e_large_class));
- }
- while (++startc <= endc) {
- regmbc(startc);
- }
- } else {
- while (++startc <= endc) {
- regc(startc);
- }
- }
- startc = -1;
- }
- }
- // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
- // accepts "\t", "\e", etc., but only when the 'l' flag in
- // 'cpoptions' is not included.
- else if (*regparse == '\\'
- && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
- || (!reg_cpo_lit
- && vim_strchr(REGEXP_ABBR,
- (uint8_t)regparse[1]) != NULL))) {
- regparse++;
- if (*regparse == 'n') {
- // '\n' in range: also match NL
- if (ret != JUST_CALC_SIZE) {
- // Using \n inside [^] does not change what
- // matches. "[^\n]" is the same as ".".
- if (*ret == ANYOF) {
- *ret = ANYOF + ADD_NL;
- *flagp |= HASNL;
- }
- // else: must have had a \n already
- }
- regparse++;
- startc = -1;
- } else if (*regparse == 'd'
- || *regparse == 'o'
- || *regparse == 'x'
- || *regparse == 'u'
- || *regparse == 'U') {
- startc = coll_get_char();
- if (startc == 0) {
- regc(0x0a);
- } else {
- regmbc(startc);
- }
- } else {
- startc = backslash_trans(*regparse++);
- regc(startc);
- }
- } else if (*regparse == '[') {
- int c_class;
- int cu;
-
- c_class = get_char_class(&regparse);
- startc = -1;
- // Characters assumed to be 8 bits!
- switch (c_class) {
- case CLASS_NONE:
- c_class = get_equi_class(&regparse);
- if (c_class != 0) {
- // produce equivalence class
- reg_equi_class(c_class);
- } else if ((c_class = get_coll_element(&regparse)) != 0) {
- // produce a collating element
- regmbc(c_class);
- } else {
- // literal '[', allow [[-x] as a range
- startc = *regparse++;
- regc(startc);
- }
- break;
- case CLASS_ALNUM:
- for (cu = 1; cu < 128; cu++) {
- if (isalnum(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_ALPHA:
- for (cu = 1; cu < 128; cu++) {
- if (isalpha(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_BLANK:
- regc(' ');
- regc('\t');
- break;
- case CLASS_CNTRL:
- for (cu = 1; cu <= 127; cu++) {
- if (iscntrl(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_DIGIT:
- for (cu = 1; cu <= 127; cu++) {
- if (ascii_isdigit(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_GRAPH:
- for (cu = 1; cu <= 127; cu++) {
- if (isgraph(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_LOWER:
- for (cu = 1; cu <= 255; cu++) {
- if (mb_islower(cu) && cu != 170 && cu != 186) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_PRINT:
- for (cu = 1; cu <= 255; cu++) {
- if (vim_isprintc(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_PUNCT:
- for (cu = 1; cu < 128; cu++) {
- if (ispunct(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_SPACE:
- for (cu = 9; cu <= 13; cu++) {
- regc(cu);
- }
- regc(' ');
- break;
- case CLASS_UPPER:
- for (cu = 1; cu <= 255; cu++) {
- if (mb_isupper(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_XDIGIT:
- for (cu = 1; cu <= 255; cu++) {
- if (ascii_isxdigit(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_TAB:
- regc('\t');
- break;
- case CLASS_RETURN:
- regc('\r');
- break;
- case CLASS_BACKSPACE:
- regc('\b');
- break;
- case CLASS_ESCAPE:
- regc(ESC);
- break;
- case CLASS_IDENT:
- for (cu = 1; cu <= 255; cu++) {
- if (vim_isIDc(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_KEYWORD:
- for (cu = 1; cu <= 255; cu++) {
- if (reg_iswordc(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_FNAME:
- for (cu = 1; cu <= 255; cu++) {
- if (vim_isfilec(cu)) {
- regmbc(cu);
- }
- }
- break;
- }
- } else {
- // produce a multibyte character, including any
- // following composing characters.
- startc = utf_ptr2char((char *)regparse);
- int len = utfc_ptr2len((char *)regparse);
- if (utf_char2len(startc) != len) {
- // composing chars
- startc = -1;
- }
- while (--len >= 0) {
- regc(*regparse++);
- }
- }
- }
- regc(NUL);
- prevchr_len = 1; // last char was the ']'
- if (*regparse != ']') {
- EMSG_RET_NULL(_(e_toomsbra)); // Cannot happen?
- }
- skipchr(); // let's be friends with the lexer again
- *flagp |= HASWIDTH | SIMPLE;
- break;
- } else if (reg_strict) {
- EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF);
- }
- }
- FALLTHROUGH;
-
- default: {
- int len;
-
- // A multi-byte character is handled as a separate atom if it's
- // before a multi and when it's a composing char.
- if (use_multibytecode(c)) {
-do_multibyte:
- ret = regnode(MULTIBYTECODE);
- regmbc(c);
- *flagp |= HASWIDTH | SIMPLE;
- break;
- }
-
- ret = regnode(EXACTLY);
-
- // Append characters as long as:
- // - there is no following multi, we then need the character in
- // front of it as a single character operand
- // - not running into a Magic character
- // - "one_exactly" is not set
- // But always emit at least one character. Might be a Multi,
- // e.g., a "[" without matching "]".
- for (len = 0; c != NUL && (len == 0
- || (re_multi_type(peekchr()) == NOT_MULTI
- && !one_exactly
- && !is_Magic(c))); len++) {
- c = no_Magic(c);
- {
- regmbc(c);
- {
- int l;
-
- // Need to get composing character too.
- for (;;) {
- l = utf_ptr2len((char *)regparse);
- if (!utf_composinglike(regparse, regparse + l)) {
- break;
- }
- regmbc(utf_ptr2char((char *)regparse));
- skipchr();
- }
- }
- }
- c = getchr();
- }
- ungetchr();
-
- regc(NUL);
- *flagp |= HASWIDTH;
- if (len == 1) {
- *flagp |= SIMPLE;
- }
- }
- break;
- }
-
- return ret;
-}
-
-// Parse something followed by possible [*+=].
-//
-// Note that the branching code sequences used for = and the general cases
-// of * and + are somewhat optimized: they use the same NOTHING node as
-// both the endmarker for their branch list and the body of the last branch.
-// It might seem that this node could be dispensed with entirely, but the
-// endmarker role is not redundant.
-static uint8_t *regpiece(int *flagp)
-{
- uint8_t *ret;
- int op;
- uint8_t *next;
- int flags;
- long minval;
- long maxval;
-
- ret = regatom(&flags);
- if (ret == NULL) {
- return NULL;
- }
-
- op = peekchr();
- if (re_multi_type(op) == NOT_MULTI) {
- *flagp = flags;
- return ret;
- }
- // default flags
- *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH)));
-
- skipchr();
- switch (op) {
- case Magic('*'):
- if (flags & SIMPLE) {
- reginsert(STAR, ret);
- } else {
- // Emit x* as (x&|), where & means "self".
- reginsert(BRANCH, ret); // Either x
- regoptail(ret, regnode(BACK)); // and loop
- regoptail(ret, ret); // back
- regtail(ret, regnode(BRANCH)); // or
- regtail(ret, regnode(NOTHING)); // null.
- }
- break;
-
- case Magic('+'):
- if (flags & SIMPLE) {
- reginsert(PLUS, ret);
- } else {
- // Emit x+ as x(&|), where & means "self".
- next = regnode(BRANCH); // Either
- regtail(ret, next);
- regtail(regnode(BACK), ret); // loop back
- regtail(next, regnode(BRANCH)); // or
- regtail(ret, regnode(NOTHING)); // null.
- }
- *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH)));
- break;
-
- case Magic('@'): {
- int lop = END;
- int64_t nr = getdecchrs();
-
- switch (no_Magic(getchr())) {
- case '=':
- lop = MATCH; break; // \@=
- case '!':
- lop = NOMATCH; break; // \@!
- case '>':
- lop = SUBPAT; break; // \@>
- case '<':
- switch (no_Magic(getchr())) {
- case '=':
- lop = BEHIND; break; // \@<=
- case '!':
- lop = NOBEHIND; break; // \@<!
- }
- }
- if (lop == END) {
- EMSG2_RET_NULL(_("E59: invalid character after %s@"),
- reg_magic == MAGIC_ALL);
- }
- // Look behind must match with behind_pos.
- if (lop == BEHIND || lop == NOBEHIND) {
- regtail(ret, regnode(BHPOS));
- *flagp |= HASLOOKBH;
- }
- regtail(ret, regnode(END)); // operand ends
- if (lop == BEHIND || lop == NOBEHIND) {
- if (nr < 0) {
- nr = 0; // no limit is same as zero limit
- }
- reginsert_nr(lop, (uint32_t)nr, ret);
- } else {
- reginsert(lop, ret);
- }
- break;
- }
-
- case Magic('?'):
- case Magic('='):
- // Emit x= as (x|)
- reginsert(BRANCH, ret); // Either x
- regtail(ret, regnode(BRANCH)); // or
- next = regnode(NOTHING); // null.
- regtail(ret, next);
- regoptail(ret, next);
- break;
-
- case Magic('{'):
- if (!read_limits(&minval, &maxval)) {
- return NULL;
- }
- if (flags & SIMPLE) {
- reginsert(BRACE_SIMPLE, ret);
- reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
- } else {
- if (num_complex_braces >= 10) {
- EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"),
- reg_magic == MAGIC_ALL);
- }
- reginsert(BRACE_COMPLEX + num_complex_braces, ret);
- regoptail(ret, regnode(BACK));
- regoptail(ret, ret);
- reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
- num_complex_braces++;
- }
- if (minval > 0 && maxval > 0) {
- *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH)));
- }
- break;
- }
- if (re_multi_type(peekchr()) != NOT_MULTI) {
- // Can't have a multi follow a multi.
- if (peekchr() == Magic('*')) {
- EMSG2_RET_NULL(_("E61: Nested %s*"), reg_magic >= MAGIC_ON);
- }
- EMSG3_RET_NULL(_("E62: Nested %s%c"), reg_magic == MAGIC_ALL, no_Magic(peekchr()));
- }
-
- return ret;
-}
-
-// Parse one alternative of an | or & operator.
-// Implements the concatenation operator.
-static uint8_t *regconcat(int *flagp)
-{
- uint8_t *first = NULL;
- uint8_t *chain = NULL;
- uint8_t *latest;
- int flags;
- int cont = true;
-
- *flagp = WORST; // Tentatively.
-
- while (cont) {
- switch (peekchr()) {
- case NUL:
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- cont = false;
- break;
- case Magic('Z'):
- regflags |= RF_ICOMBINE;
- skipchr_keepstart();
- break;
- case Magic('c'):
- regflags |= RF_ICASE;
- skipchr_keepstart();
- break;
- case Magic('C'):
- regflags |= RF_NOICASE;
- skipchr_keepstart();
- break;
- case Magic('v'):
- reg_magic = MAGIC_ALL;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('m'):
- reg_magic = MAGIC_ON;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('M'):
- reg_magic = MAGIC_OFF;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('V'):
- reg_magic = MAGIC_NONE;
- skipchr_keepstart();
- curchr = -1;
- break;
- default:
- latest = regpiece(&flags);
- if (latest == NULL || reg_toolong) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH);
- if (chain == NULL) { // First piece.
- *flagp |= flags & SPSTART;
- } else {
- regtail(chain, latest);
- }
- chain = latest;
- if (first == NULL) {
- first = latest;
- }
- break;
- }
- }
- if (first == NULL) { // Loop ran zero times.
- first = regnode(NOTHING);
- }
- return first;
-}
-
-// Parse one alternative of an | operator.
-// Implements the & operator.
-static uint8_t *regbranch(int *flagp)
-{
- uint8_t *ret;
- uint8_t *chain = NULL;
- uint8_t *latest;
- int flags;
-
- *flagp = WORST | HASNL; // Tentatively.
-
- ret = regnode(BRANCH);
- for (;;) {
- latest = regconcat(&flags);
- if (latest == NULL) {
- return NULL;
- }
- // If one of the branches has width, the whole thing has. If one of
- // the branches anchors at start-of-line, the whole thing does.
- // If one of the branches uses look-behind, the whole thing does.
- *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH);
- // If one of the branches doesn't match a line-break, the whole thing
- // doesn't.
- *flagp &= ~HASNL | (flags & HASNL);
- if (chain != NULL) {
- regtail(chain, latest);
- }
- if (peekchr() != Magic('&')) {
- break;
- }
- skipchr();
- regtail(latest, regnode(END)); // operand ends
- if (reg_toolong) {
- break;
- }
- reginsert(MATCH, latest);
- chain = latest;
- }
-
- return ret;
-}
-
-/// Parse regular expression, i.e. main body or parenthesized thing.
-///
-/// Caller must absorb opening parenthesis.
-///
-/// Combining parenthesis handling with the base level of regular expression
-/// is a trifle forced, but the need to tie the tails of the branches to what
-/// follows makes it hard to avoid.
-///
-/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
-static uint8_t *reg(int paren, int *flagp)
-{
- uint8_t *ret;
- uint8_t *br;
- uint8_t *ender;
- int parno = 0;
- int flags;
-
- *flagp = HASWIDTH; // Tentatively.
-
- if (paren == REG_ZPAREN) {
- // Make a ZOPEN node.
- if (regnzpar >= NSUBEXP) {
- EMSG_RET_NULL(_("E50: Too many \\z("));
- }
- parno = regnzpar;
- regnzpar++;
- ret = regnode(ZOPEN + parno);
- } else if (paren == REG_PAREN) {
- // Make a MOPEN node.
- if (regnpar >= NSUBEXP) {
- EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL);
- }
- parno = regnpar;
- regnpar++;
- ret = regnode(MOPEN + parno);
- } else if (paren == REG_NPAREN) {
- // Make a NOPEN node.
- ret = regnode(NOPEN);
- } else {
- ret = NULL;
- }
-
- // Pick up the branches, linking them together.
- br = regbranch(&flags);
- if (br == NULL) {
- return NULL;
- }
- if (ret != NULL) {
- regtail(ret, br); // [MZ]OPEN -> first.
- } else {
- ret = br;
- }
- // If one of the branches can be zero-width, the whole thing can.
- // If one of the branches has * at start or matches a line-break, the
- // whole thing can.
- if (!(flags & HASWIDTH)) {
- *flagp &= ~HASWIDTH;
- }
- *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
- while (peekchr() == Magic('|')) {
- skipchr();
- br = regbranch(&flags);
- if (br == NULL || reg_toolong) {
- return NULL;
- }
- regtail(ret, br); // BRANCH -> BRANCH.
- if (!(flags & HASWIDTH)) {
- *flagp &= ~HASWIDTH;
- }
- *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
- }
-
- // Make a closing node, and hook it on the end.
- ender = regnode(paren == REG_ZPAREN ? ZCLOSE + parno :
- paren == REG_PAREN ? MCLOSE + parno :
- paren == REG_NPAREN ? NCLOSE : END);
- regtail(ret, ender);
-
- // Hook the tails of the branches to the closing node.
- for (br = ret; br != NULL; br = regnext(br)) {
- regoptail(br, ender);
- }
-
- // Check for proper termination.
- if (paren != REG_NOPAREN && getchr() != Magic(')')) {
- if (paren == REG_ZPAREN) {
- EMSG_RET_NULL(_("E52: Unmatched \\z("));
- } else if (paren == REG_NPAREN) {
- EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
- } else {
- EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
- }
- } else if (paren == REG_NOPAREN && peekchr() != NUL) {
- if (curchr == Magic(')')) {
- EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
- } else {
- EMSG_RET_NULL(_(e_trailing)); // "Can't happen".
- }
- // NOTREACHED
- }
- // Here we set the flag allowing back references to this set of
- // parentheses.
- if (paren == REG_PAREN) {
- had_endbrace[parno] = true; // have seen the close paren
- }
- return ret;
-}
-
-// bt_regcomp() - compile a regular expression into internal code for the
-// traditional back track matcher.
-// Returns the program in allocated space. Returns NULL for an error.
-//
-// We can't allocate space until we know how big the compiled form will be,
-// but we can't compile it (and thus know how big it is) until we've got a
-// place to put the code. So we cheat: we compile it twice, once with code
-// generation turned off and size counting turned on, and once "for real".
-// This also means that we don't allocate space until we are sure that the
-// thing really will compile successfully, and we never have to move the
-// code and thus invalidate pointers into it. (Note that it has to be in
-// one piece because free() must be able to free it all.)
-//
-// Whether upper/lower case is to be ignored is decided when executing the
-// program, it does not matter here.
-//
-// Beware that the optimization-preparation code in here knows about some
-// of the structure of the compiled regexp.
-// "re_flags": RE_MAGIC and/or RE_STRING.
-static regprog_T *bt_regcomp(uint8_t *expr, int re_flags)
-{
- uint8_t *scan;
- uint8_t *longest;
- int len;
- int flags;
-
- if (expr == NULL) {
- IEMSG_RET_NULL(_(e_null));
- }
-
- init_class_tab();
-
- // First pass: determine size, legality.
- regcomp_start(expr, re_flags);
- regcode = JUST_CALC_SIZE;
- regc(REGMAGIC);
- if (reg(REG_NOPAREN, &flags) == NULL) {
- return NULL;
- }
-
- // Allocate space.
- bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + (size_t)regsize);
- r->re_in_use = false;
-
- // Second pass: emit code.
- regcomp_start(expr, re_flags);
- regcode = r->program;
- regc(REGMAGIC);
- if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) {
- xfree(r);
- if (reg_toolong) {
- EMSG_RET_NULL(_("E339: Pattern too long"));
- }
- return NULL;
- }
-
- // Dig out information for optimizations.
- r->regstart = NUL; // Worst-case defaults.
- r->reganch = 0;
- r->regmust = NULL;
- r->regmlen = 0;
- r->regflags = regflags;
- if (flags & HASNL) {
- r->regflags |= RF_HASNL;
- }
- if (flags & HASLOOKBH) {
- r->regflags |= RF_LOOKBH;
- }
- // Remember whether this pattern has any \z specials in it.
- r->reghasz = (uint8_t)re_has_z;
- scan = r->program + 1; // First BRANCH.
- if (OP(regnext(scan)) == END) { // Only one top-level choice.
- scan = OPERAND(scan);
-
- // Starting-point info.
- if (OP(scan) == BOL || OP(scan) == RE_BOF) {
- r->reganch++;
- scan = regnext(scan);
- }
-
- if (OP(scan) == EXACTLY) {
- r->regstart = utf_ptr2char((char *)OPERAND(scan));
- } else if (OP(scan) == BOW
- || OP(scan) == EOW
- || OP(scan) == NOTHING
- || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN
- || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) {
- uint8_t *regnext_scan = regnext(scan);
- if (OP(regnext_scan) == EXACTLY) {
- r->regstart = utf_ptr2char((char *)OPERAND(regnext_scan));
- }
- }
-
- // If there's something expensive in the r.e., find the longest
- // literal string that must appear and make it the regmust. Resolve
- // ties in favor of later strings, since the regstart check works
- // with the beginning of the r.e. and avoiding duplication
- // strengthens checking. Not a strong reason, but sufficient in the
- // absence of others.
-
- // When the r.e. starts with BOW, it is faster to look for a regmust
- // first. Used a lot for "#" and "*" commands. (Added by mool).
- if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW)
- && !(flags & HASNL)) {
- longest = NULL;
- len = 0;
- for (; scan != NULL; scan = regnext(scan)) {
- if (OP(scan) == EXACTLY && strlen((char *)OPERAND(scan)) >= (size_t)len) {
- longest = OPERAND(scan);
- len = (int)strlen((char *)OPERAND(scan));
- }
- }
- r->regmust = longest;
- r->regmlen = len;
- }
- }
-#ifdef BT_REGEXP_DUMP
- regdump(expr, r);
-#endif
- r->engine = &bt_regengine;
- return (regprog_T *)r;
-}
-
-// Check if during the previous call to vim_regcomp the EOL item "$" has been
-// found. This is messy, but it works fine.
-int vim_regcomp_had_eol(void)
-{
- return had_eol;
-}
-
-// Get a number after a backslash that is inside [].
-// When nothing is recognized return a backslash.
-static int coll_get_char(void)
-{
- int64_t nr = -1;
-
- switch (*regparse++) {
- case 'd':
- nr = getdecchrs(); break;
- case 'o':
- nr = getoctchrs(); break;
- case 'x':
- nr = gethexchrs(2); break;
- case 'u':
- nr = gethexchrs(4); break;
- case 'U':
- nr = gethexchrs(8); break;
- }
- if (nr < 0 || nr > INT_MAX) {
- // If getting the number fails be backwards compatible: the character
- // is a backslash.
- regparse--;
- nr = '\\';
- }
- return (int)nr;
-}
-
-// Free a compiled regexp program, returned by bt_regcomp().
-static void bt_regfree(regprog_T *prog)
-{
- xfree(prog);
-}
-
-#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input)
-
-// The arguments from BRACE_LIMITS are stored here. They are actually local
-// to regmatch(), but they are here to reduce the amount of stack space used
-// (it can be called recursively many times).
-static long bl_minval;
-static long bl_maxval;
-
-// Save the input line and position in a regsave_T.
-static void reg_save(regsave_T *save, garray_T *gap)
- FUNC_ATTR_NONNULL_ALL
-{
- if (REG_MULTI) {
- save->rs_u.pos.col = (colnr_T)(rex.input - rex.line);
- save->rs_u.pos.lnum = rex.lnum;
- } else {
- save->rs_u.ptr = rex.input;
- }
- save->rs_len = gap->ga_len;
-}
-
-// Restore the input line and position from a regsave_T.
-static void reg_restore(regsave_T *save, garray_T *gap)
- FUNC_ATTR_NONNULL_ALL
-{
- if (REG_MULTI) {
- if (rex.lnum != save->rs_u.pos.lnum) {
- // only call reg_getline() when the line number changed to save
- // a bit of time
- rex.lnum = save->rs_u.pos.lnum;
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- }
- rex.input = rex.line + save->rs_u.pos.col;
- } else {
- rex.input = save->rs_u.ptr;
- }
- gap->ga_len = save->rs_len;
-}
-
-// Return true if current position is equal to saved position.
-static bool reg_save_equal(const regsave_T *save)
- FUNC_ATTR_NONNULL_ALL
-{
- if (REG_MULTI) {
- return rex.lnum == save->rs_u.pos.lnum
- && rex.input == rex.line + save->rs_u.pos.col;
- }
- return rex.input == save->rs_u.ptr;
-}
-
-// Save the sub-expressions before attempting a match.
-#define save_se(savep, posp, pp) \
- REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp))
-
-// After a failed match restore the sub-expressions.
-#define restore_se(savep, posp, pp) { \
- if (REG_MULTI) /* NOLINT(readability/braces) */ \
- *(posp) = (savep)->se_u.pos; \
- else /* NOLINT */ \
- *(pp) = (savep)->se_u.ptr; }
-
-// Tentatively set the sub-expression start to the current position (after
-// calling regmatch() they will have changed). Need to save the existing
-// values for when there is no match.
-// Use se_save() to use pointer (save_se_multi()) or position (save_se_one()),
-// depending on REG_MULTI.
-static void save_se_multi(save_se_T *savep, lpos_T *posp)
-{
- savep->se_u.pos = *posp;
- posp->lnum = rex.lnum;
- posp->col = (colnr_T)(rex.input - rex.line);
-}
-
-static void save_se_one(save_se_T *savep, uint8_t **pp)
-{
- savep->se_u.ptr = *pp;
- *pp = rex.input;
-}
-
-/// regrepeat - repeatedly match something simple, return how many.
-/// Advances rex.input (and rex.lnum) to just after the matched chars.
-///
-/// @param maxcount maximum number of matches allowed
-static int regrepeat(uint8_t *p, long maxcount)
-{
- long count = 0;
- uint8_t *opnd;
- int mask;
- int testval = 0;
-
- uint8_t *scan = rex.input; // Make local copy of rex.input for speed.
- opnd = OPERAND(p);
- switch (OP(p)) {
- case ANY:
- case ANY + ADD_NL:
- while (count < maxcount) {
- // Matching anything means we continue until end-of-line (or
- // end-of-file for ANY + ADD_NL), only limited by maxcount.
- while (*scan != NUL && count < maxcount) {
- count++;
- MB_PTR_ADV(scan);
- }
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr || count == maxcount) {
- break;
- }
- count++; // count the line-break
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- }
- break;
-
- case IDENT:
- case IDENT + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SIDENT:
- case SIDENT + ADD_NL:
- while (count < maxcount) {
- if (vim_isIDc(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case KWORD:
- case KWORD + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SKWORD:
- case SKWORD + ADD_NL:
- while (count < maxcount) {
- if (vim_iswordp_buf((char *)scan, rex.reg_buf)
- && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case FNAME:
- case FNAME + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SFNAME:
- case SFNAME + ADD_NL:
- while (count < maxcount) {
- if (vim_isfilec(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case PRINT:
- case PRINT + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SPRINT:
- case SPRINT + ADD_NL:
- while (count < maxcount) {
- if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (vim_isprintc(utf_ptr2char((char *)scan)) == 1
- && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case WHITE:
- case WHITE + ADD_NL:
- testval = mask = RI_WHITE;
-do_class:
- while (count < maxcount) {
- int l;
- if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if ((l = utfc_ptr2len((char *)scan)) > 1) {
- if (testval != 0) {
- break;
- }
- scan += l;
- } else if ((class_tab[*scan] & mask) == testval) {
- scan++;
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case NWHITE:
- case NWHITE + ADD_NL:
- mask = RI_WHITE;
- goto do_class;
- case DIGIT:
- case DIGIT + ADD_NL:
- testval = mask = RI_DIGIT;
- goto do_class;
- case NDIGIT:
- case NDIGIT + ADD_NL:
- mask = RI_DIGIT;
- goto do_class;
- case HEX:
- case HEX + ADD_NL:
- testval = mask = RI_HEX;
- goto do_class;
- case NHEX:
- case NHEX + ADD_NL:
- mask = RI_HEX;
- goto do_class;
- case OCTAL:
- case OCTAL + ADD_NL:
- testval = mask = RI_OCTAL;
- goto do_class;
- case NOCTAL:
- case NOCTAL + ADD_NL:
- mask = RI_OCTAL;
- goto do_class;
- case WORD:
- case WORD + ADD_NL:
- testval = mask = RI_WORD;
- goto do_class;
- case NWORD:
- case NWORD + ADD_NL:
- mask = RI_WORD;
- goto do_class;
- case HEAD:
- case HEAD + ADD_NL:
- testval = mask = RI_HEAD;
- goto do_class;
- case NHEAD:
- case NHEAD + ADD_NL:
- mask = RI_HEAD;
- goto do_class;
- case ALPHA:
- case ALPHA + ADD_NL:
- testval = mask = RI_ALPHA;
- goto do_class;
- case NALPHA:
- case NALPHA + ADD_NL:
- mask = RI_ALPHA;
- goto do_class;
- case LOWER:
- case LOWER + ADD_NL:
- testval = mask = RI_LOWER;
- goto do_class;
- case NLOWER:
- case NLOWER + ADD_NL:
- mask = RI_LOWER;
- goto do_class;
- case UPPER:
- case UPPER + ADD_NL:
- testval = mask = RI_UPPER;
- goto do_class;
- case NUPPER:
- case NUPPER + ADD_NL:
- mask = RI_UPPER;
- goto do_class;
-
- case EXACTLY: {
- int cu, cl;
-
- // This doesn't do a multi-byte character, because a MULTIBYTECODE
- // would have been used for it. It does handle single-byte
- // characters, such as latin1.
- if (rex.reg_ic) {
- cu = mb_toupper(*opnd);
- cl = mb_tolower(*opnd);
- while (count < maxcount && (*scan == cu || *scan == cl)) {
- count++;
- scan++;
- }
- } else {
- cu = *opnd;
- while (count < maxcount && *scan == cu) {
- count++;
- scan++;
- }
- }
- break;
- }
-
- case MULTIBYTECODE: {
- int i, len, cf = 0;
-
- // Safety check (just in case 'encoding' was changed since
- // compiling the program).
- if ((len = utfc_ptr2len((char *)opnd)) > 1) {
- if (rex.reg_ic) {
- cf = utf_fold(utf_ptr2char((char *)opnd));
- }
- while (count < maxcount && utfc_ptr2len((char *)scan) >= len) {
- for (i = 0; i < len; i++) {
- if (opnd[i] != scan[i]) {
- break;
- }
- }
- if (i < len && (!rex.reg_ic
- || utf_fold(utf_ptr2char((char *)scan)) != cf)) {
- break;
- }
- scan += len;
- count++;
- }
- }
- }
- break;
-
- case ANYOF:
- case ANYOF + ADD_NL:
- testval = 1;
- FALLTHROUGH;
-
- case ANYBUT:
- case ANYBUT + ADD_NL:
- while (count < maxcount) {
- int len;
- if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else if ((len = utfc_ptr2len((char *)scan)) > 1) {
- if ((cstrchr((char *)opnd, utf_ptr2char((char *)scan)) == NULL) == testval) {
- break;
- }
- scan += len;
- } else {
- if ((cstrchr((char *)opnd, *scan) == NULL) == testval) {
- break;
- }
- scan++;
- }
- count++;
- }
- break;
-
- case NEWL:
- while (count < maxcount
- && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr
- && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) {
- count++;
- if (rex.reg_line_lbr) {
- ADVANCE_REGINPUT();
- } else {
- reg_nextline();
- }
- scan = rex.input;
- if (got_int) {
- break;
- }
- }
- break;
-
- default: // Oh dear. Called inappropriately.
- iemsg(_(e_re_corr));
-#ifdef REGEXP_DEBUG
- printf("Called regrepeat with op code %d\n", OP(p));
-#endif
- break;
- }
-
- rex.input = scan;
-
- return (int)count;
-}
-
-// Push an item onto the regstack.
-// Returns pointer to new item. Returns NULL when out of memory.
-static regitem_T *regstack_push(regstate_T state, uint8_t *scan)
-{
- regitem_T *rp;
-
- if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- return NULL;
- }
- ga_grow(&regstack, sizeof(regitem_T));
-
- rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len);
- rp->rs_state = state;
- rp->rs_scan = scan;
-
- regstack.ga_len += (int)sizeof(regitem_T);
- return rp;
-}
-
-// Pop an item from the regstack.
-static void regstack_pop(uint8_t **scan)
-{
- regitem_T *rp;
-
- rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
- *scan = rp->rs_scan;
-
- regstack.ga_len -= (int)sizeof(regitem_T);
-}
-
-// Save the current subexpr to "bp", so that they can be restored
-// later by restore_subexpr().
-static void save_subexpr(regbehind_T *bp)
- FUNC_ATTR_NONNULL_ALL
-{
- // When "rex.need_clear_subexpr" is set we don't need to save the values, only
- // remember that this flag needs to be set again when restoring.
- bp->save_need_clear_subexpr = rex.need_clear_subexpr;
- if (rex.need_clear_subexpr) {
- return;
- }
-
- for (int i = 0; i < NSUBEXP; i++) {
- if (REG_MULTI) {
- bp->save_start[i].se_u.pos = rex.reg_startpos[i];
- bp->save_end[i].se_u.pos = rex.reg_endpos[i];
- } else {
- bp->save_start[i].se_u.ptr = rex.reg_startp[i];
- bp->save_end[i].se_u.ptr = rex.reg_endp[i];
- }
- }
-}
-
-// Restore the subexpr from "bp".
-static void restore_subexpr(regbehind_T *bp)
- FUNC_ATTR_NONNULL_ALL
-{
- // Only need to restore saved values when they are not to be cleared.
- rex.need_clear_subexpr = bp->save_need_clear_subexpr;
- if (rex.need_clear_subexpr) {
- return;
- }
-
- for (int i = 0; i < NSUBEXP; i++) {
- if (REG_MULTI) {
- rex.reg_startpos[i] = bp->save_start[i].se_u.pos;
- rex.reg_endpos[i] = bp->save_end[i].se_u.pos;
- } else {
- rex.reg_startp[i] = bp->save_start[i].se_u.ptr;
- rex.reg_endp[i] = bp->save_end[i].se_u.ptr;
- }
- }
-}
-/// Main matching routine
-///
-/// Conceptually the strategy is simple: Check to see whether the current node
-/// matches, push an item onto the regstack and loop to see whether the rest
-/// matches, and then act accordingly. In practice we make some effort to
-/// avoid using the regstack, in particular by going through "ordinary" nodes
-/// (that don't need to know whether the rest of the match failed) by a nested
-/// loop.
-///
-/// @param scan Current node.
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return - true when there is a match. Leaves rex.input and rex.lnum
-/// just after the last matched character.
-/// - false when there is no match. Leaves rex.input and rex.lnum in an
-/// undefined state!
-static bool regmatch(uint8_t *scan, proftime_T *tm, int *timed_out)
-{
- uint8_t *next; // Next node.
- int op;
- int c;
- regitem_T *rp;
- int no;
- int status; // one of the RA_ values:
- int tm_count = 0;
-
- // Make "regstack" and "backpos" empty. They are allocated and freed in
- // bt_regexec_both() to reduce malloc()/free() calls.
- regstack.ga_len = 0;
- backpos.ga_len = 0;
-
- // Repeat until "regstack" is empty.
- for (;;) {
- // Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q".
- // Allow interrupting them with CTRL-C.
- fast_breakcheck();
-
-#ifdef REGEXP_DEBUG
- if (scan != NULL && regnarrate) {
- os_errmsg((char *)regprop(scan));
- os_errmsg("(\n");
- }
-#endif
-
- // Repeat for items that can be matched sequentially, without using the
- // regstack.
- for (;;) {
- if (got_int || scan == NULL) {
- status = RA_FAIL;
- break;
- }
- // Check for timeout once in a 100 times to avoid overhead.
- if (tm != NULL && ++tm_count == 100) {
- tm_count = 0;
- if (profile_passed_limit(*tm)) {
- if (timed_out != NULL) {
- *timed_out = true;
- }
- status = RA_FAIL;
- break;
- }
- }
- status = RA_CONT;
-
-#ifdef REGEXP_DEBUG
- if (regnarrate) {
- os_errmsg((char *)regprop(scan));
- os_errmsg("...\n");
- if (re_extmatch_in != NULL) {
- int i;
-
- os_errmsg(_("External submatches:\n"));
- for (i = 0; i < NSUBEXP; i++) {
- os_errmsg(" \"");
- if (re_extmatch_in->matches[i] != NULL) {
- os_errmsg((char *)re_extmatch_in->matches[i]);
- }
- os_errmsg("\"\n");
- }
- }
- }
-#endif
- next = regnext(scan);
-
- op = OP(scan);
- // Check for character class with NL added.
- if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI
- && *rex.input == NUL && rex.lnum <= rex.reg_maxline) {
- reg_nextline();
- } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') {
- ADVANCE_REGINPUT();
- } else {
- if (WITH_NL(op)) {
- op -= ADD_NL;
- }
- c = utf_ptr2char((char *)rex.input);
- switch (op) {
- case BOL:
- if (rex.input != rex.line) {
- status = RA_NOMATCH;
- }
- break;
-
- case EOL:
- if (c != NUL) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_BOF:
- // We're not at the beginning of the file when below the first
- // line where we started, not at the start of the line or we
- // didn't start at the first line of the buffer.
- if (rex.lnum != 0 || rex.input != rex.line
- || (REG_MULTI && rex.reg_firstlnum > 1)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_EOF:
- if (rex.lnum != rex.reg_maxline || c != NUL) {
- status = RA_NOMATCH;
- }
- break;
-
- case CURSOR:
- // Check if the buffer is in a window and compare the
- // rex.reg_win->w_cursor position to the match position.
- if (rex.reg_win == NULL
- || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum)
- || ((colnr_T)(rex.input - rex.line) !=
- rex.reg_win->w_cursor.col)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_MARK:
- // Compare the mark position to the match position.
- {
- int mark = OPERAND(scan)[0];
- int cmp = OPERAND(scan)[1];
- pos_T *pos;
- size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
- fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, mark);
-
- // Line may have been freed, get it again.
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- rex.input = rex.line + col;
- }
-
- if (fm == NULL // mark doesn't exist
- || fm->mark.lnum <= 0) { // mark isn't set in reg_buf
- status = RA_NOMATCH;
- } else {
- pos = &fm->mark;
- const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
- && pos->col == MAXCOL
- ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum))
- : pos->col;
-
- if (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos_col == (colnr_T)(rex.input - rex.line)
- ? (cmp == '<' || cmp == '>')
- : (pos_col < (colnr_T)(rex.input - rex.line)
- ? cmp != '>'
- : cmp != '<'))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? cmp != '>'
- : cmp != '<')) {
- status = RA_NOMATCH;
- }
- }
- }
- break;
-
- case RE_VISUAL:
- if (!reg_match_visual()) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_LNUM:
- assert(rex.lnum + rex.reg_firstlnum >= 0
- && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX);
- if (!REG_MULTI
- || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_COL:
- assert(rex.input - rex.line + 1 >= 0
- && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX);
- if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_VCOL:
- if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL
- ? curwin : rex.reg_win,
- rex.reg_firstlnum + rex.lnum,
- (char *)rex.line,
- (colnr_T)(rex.input - rex.line)) + 1,
- scan)) {
- status = RA_NOMATCH;
- }
- break;
-
- case BOW: // \<word; rex.input points to w
- if (c == NUL) { // Can't match at end of line
- status = RA_NOMATCH;
- } else {
- // Get class of current and previous char (if it exists).
- const int this_class =
- mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- if (this_class <= 1) {
- status = RA_NOMATCH; // Not on a word at all.
- } else if (reg_prev_class() == this_class) {
- status = RA_NOMATCH; // Previous char is in same word.
- }
- }
- break;
-
- case EOW: // word\>; rex.input points after d
- if (rex.input == rex.line) { // Can't match at start of line
- status = RA_NOMATCH;
- } else {
- int this_class, prev_class;
-
- // Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- prev_class = reg_prev_class();
- if (this_class == prev_class
- || prev_class == 0 || prev_class == 1) {
- status = RA_NOMATCH;
- }
- }
- break; // Matched with EOW
-
- case ANY:
- // ANY does not match new lines.
- if (c == NUL) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case IDENT:
- if (!vim_isIDc(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SIDENT:
- if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case KWORD:
- if (!vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SKWORD:
- if (ascii_isdigit(*rex.input)
- || !vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case FNAME:
- if (!vim_isfilec(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SFNAME:
- if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case PRINT:
- if (!vim_isprintc(utf_ptr2char((char *)rex.input))) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SPRINT:
- if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char((char *)rex.input))) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case WHITE:
- if (!ascii_iswhite(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NWHITE:
- if (c == NUL || ascii_iswhite(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case DIGIT:
- if (!ri_digit(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NDIGIT:
- if (c == NUL || ri_digit(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case HEX:
- if (!ri_hex(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NHEX:
- if (c == NUL || ri_hex(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case OCTAL:
- if (!ri_octal(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NOCTAL:
- if (c == NUL || ri_octal(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case WORD:
- if (!ri_word(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NWORD:
- if (c == NUL || ri_word(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case HEAD:
- if (!ri_head(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NHEAD:
- if (c == NUL || ri_head(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case ALPHA:
- if (!ri_alpha(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NALPHA:
- if (c == NUL || ri_alpha(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case LOWER:
- if (!ri_lower(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NLOWER:
- if (c == NUL || ri_lower(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case UPPER:
- if (!ri_upper(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NUPPER:
- if (c == NUL || ri_upper(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case EXACTLY: {
- int len;
- uint8_t *opnd;
-
- opnd = OPERAND(scan);
- // Inline the first byte, for speed.
- if (*opnd != *rex.input
- && (!rex.reg_ic)) {
- status = RA_NOMATCH;
- } else if (*opnd == NUL) {
- // match empty string always works; happens when "~" is
- // empty.
- } else {
- if (opnd[1] == NUL && !rex.reg_ic) {
- len = 1; // matched a single byte above
- } else {
- // Need to match first byte again for multi-byte.
- len = (int)strlen((char *)opnd);
- if (cstrncmp((char *)opnd, (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- }
- }
- // Check for following composing character, unless %C
- // follows (skips over all composing chars).
- if (status != RA_NOMATCH
- && utf_composinglike((char *)rex.input, (char *)rex.input + len)
- && !rex.reg_icombine
- && OP(next) != RE_COMPOSING) {
- // raaron: This code makes a composing character get
- // ignored, which is the correct behavior (sometimes)
- // for voweled Hebrew texts.
- status = RA_NOMATCH;
- }
- if (status != RA_NOMATCH) {
- rex.input += len;
- }
- }
- }
- break;
-
- case ANYOF:
- case ANYBUT:
- if (c == NUL) {
- status = RA_NOMATCH;
- } else if ((cstrchr((char *)OPERAND(scan), c) == NULL) == (op == ANYOF)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case MULTIBYTECODE: {
- int i, len;
-
- const uint8_t *opnd = OPERAND(scan);
- // Safety check (just in case 'encoding' was changed since
- // compiling the program).
- if ((len = utfc_ptr2len((char *)opnd)) < 2) {
- status = RA_NOMATCH;
- break;
- }
- const int opndc = utf_ptr2char((char *)opnd);
- if (utf_iscomposing(opndc)) {
- // When only a composing char is given match at any
- // position where that composing char appears.
- status = RA_NOMATCH;
- for (i = 0; rex.input[i] != NUL;
- i += utf_ptr2len((char *)rex.input + i)) {
- const int inpc = utf_ptr2char((char *)rex.input + i);
- if (!utf_iscomposing(inpc)) {
- if (i > 0) {
- break;
- }
- } else if (opndc == inpc) {
- // Include all following composing chars.
- len = i + utfc_ptr2len((char *)rex.input + i);
- status = RA_MATCH;
- break;
- }
- }
- } else {
- for (i = 0; i < len; i++) {
- if (opnd[i] != rex.input[i]) {
- status = RA_NOMATCH;
- break;
- }
- }
- }
- rex.input += len;
- }
- break;
-
- case RE_COMPOSING:
- // Skip composing characters.
- while (utf_iscomposing(utf_ptr2char((char *)rex.input))) {
- MB_CPTR_ADV(rex.input);
- }
- break;
-
- case NOTHING:
- break;
-
- case BACK: {
- int i;
-
- // When we run into BACK we need to check if we don't keep
- // looping without matching any input. The second and later
- // times a BACK is encountered it fails if the input is still
- // at the same position as the previous time.
- // The positions are stored in "backpos" and found by the
- // current value of "scan", the position in the RE program.
- backpos_T *bp = (backpos_T *)backpos.ga_data;
- for (i = 0; i < backpos.ga_len; i++) {
- if (bp[i].bp_scan == scan) {
- break;
- }
- }
- if (i == backpos.ga_len) {
- backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos);
- p->bp_scan = scan;
- } else if (reg_save_equal(&bp[i].bp_pos)) {
- // Still at same position as last time, fail.
- status = RA_NOMATCH;
- }
-
- assert(status != RA_FAIL);
- if (status != RA_NOMATCH) {
- reg_save(&bp[i].bp_pos, &backpos);
- }
- }
- break;
-
- case MOPEN + 0: // Match start: \zs
- case MOPEN + 1: // \(
- case MOPEN + 2:
- case MOPEN + 3:
- case MOPEN + 4:
- case MOPEN + 5:
- case MOPEN + 6:
- case MOPEN + 7:
- case MOPEN + 8:
- case MOPEN + 9:
- no = op - MOPEN;
- cleanup_subexpr();
- rp = regstack_push(RS_MOPEN, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &rex.reg_startpos[no],
- &rex.reg_startp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case NOPEN: // \%(
- case NCLOSE: // \) after \%(
- if (regstack_push(RS_NOPEN, scan) == NULL) {
- status = RA_FAIL;
- }
- // We simply continue and handle the result when done.
- break;
-
- case ZOPEN + 1:
- case ZOPEN + 2:
- case ZOPEN + 3:
- case ZOPEN + 4:
- case ZOPEN + 5:
- case ZOPEN + 6:
- case ZOPEN + 7:
- case ZOPEN + 8:
- case ZOPEN + 9:
- no = op - ZOPEN;
- cleanup_zsubexpr();
- rp = regstack_push(RS_ZOPEN, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &reg_startzpos[no],
- &reg_startzp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case MCLOSE + 0: // Match end: \ze
- case MCLOSE + 1: // \)
- case MCLOSE + 2:
- case MCLOSE + 3:
- case MCLOSE + 4:
- case MCLOSE + 5:
- case MCLOSE + 6:
- case MCLOSE + 7:
- case MCLOSE + 8:
- case MCLOSE + 9:
- no = op - MCLOSE;
- cleanup_subexpr();
- rp = regstack_push(RS_MCLOSE, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case ZCLOSE + 1: // \) after \z(
- case ZCLOSE + 2:
- case ZCLOSE + 3:
- case ZCLOSE + 4:
- case ZCLOSE + 5:
- case ZCLOSE + 6:
- case ZCLOSE + 7:
- case ZCLOSE + 8:
- case ZCLOSE + 9:
- no = op - ZCLOSE;
- cleanup_zsubexpr();
- rp = regstack_push(RS_ZCLOSE, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &reg_endzpos[no],
- &reg_endzp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case BACKREF + 1:
- case BACKREF + 2:
- case BACKREF + 3:
- case BACKREF + 4:
- case BACKREF + 5:
- case BACKREF + 6:
- case BACKREF + 7:
- case BACKREF + 8:
- case BACKREF + 9: {
- int len;
-
- no = op - BACKREF;
- cleanup_subexpr();
- if (!REG_MULTI) { // Single-line regexp
- if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) {
- // Backref was not set: Match an empty string.
- len = 0;
- } else {
- // Compare current input with back-ref in the same line.
- len = (int)(rex.reg_endp[no] - rex.reg_startp[no]);
- if (cstrncmp((char *)rex.reg_startp[no], (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- }
- }
- } else { // Multi-line regexp
- if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) {
- // Backref was not set: Match an empty string.
- len = 0;
- } else {
- if (rex.reg_startpos[no].lnum == rex.lnum
- && rex.reg_endpos[no].lnum == rex.lnum) {
- // Compare back-ref within the current line.
- len = rex.reg_endpos[no].col - rex.reg_startpos[no].col;
- if (cstrncmp((char *)rex.line + rex.reg_startpos[no].col,
- (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- }
- } else {
- // Messy situation: Need to compare between two lines.
- int r = match_with_backref(rex.reg_startpos[no].lnum,
- rex.reg_startpos[no].col,
- rex.reg_endpos[no].lnum,
- rex.reg_endpos[no].col,
- &len);
- if (r != RA_MATCH) {
- status = r;
- }
- }
- }
- }
-
- // Matched the backref, skip over it.
- rex.input += len;
- }
- break;
-
- case ZREF + 1:
- case ZREF + 2:
- case ZREF + 3:
- case ZREF + 4:
- case ZREF + 5:
- case ZREF + 6:
- case ZREF + 7:
- case ZREF + 8:
- case ZREF + 9:
- cleanup_zsubexpr();
- no = op - ZREF;
- if (re_extmatch_in != NULL
- && re_extmatch_in->matches[no] != NULL) {
- int len = (int)strlen((char *)re_extmatch_in->matches[no]);
- if (cstrncmp((char *)re_extmatch_in->matches[no], (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- } else {
- rex.input += len;
- }
- } else {
- // Backref was not set: Match an empty string.
- }
- break;
-
- case BRANCH:
- if (OP(next) != BRANCH) { // No choice.
- next = OPERAND(scan); // Avoid recursion.
- } else {
- rp = regstack_push(RS_BRANCH, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- status = RA_BREAK; // rest is below
- }
- }
- break;
-
- case BRACE_LIMITS:
- if (OP(next) == BRACE_SIMPLE) {
- bl_minval = OPERAND_MIN(scan);
- bl_maxval = OPERAND_MAX(scan);
- } else if (OP(next) >= BRACE_COMPLEX
- && OP(next) < BRACE_COMPLEX + 10) {
- no = OP(next) - BRACE_COMPLEX;
- brace_min[no] = OPERAND_MIN(scan);
- brace_max[no] = OPERAND_MAX(scan);
- brace_count[no] = 0;
- } else {
- internal_error("BRACE_LIMITS");
- status = RA_FAIL;
- }
- break;
-
- case BRACE_COMPLEX + 0:
- case BRACE_COMPLEX + 1:
- case BRACE_COMPLEX + 2:
- case BRACE_COMPLEX + 3:
- case BRACE_COMPLEX + 4:
- case BRACE_COMPLEX + 5:
- case BRACE_COMPLEX + 6:
- case BRACE_COMPLEX + 7:
- case BRACE_COMPLEX + 8:
- case BRACE_COMPLEX + 9:
- no = op - BRACE_COMPLEX;
- brace_count[no]++;
-
- // If not matched enough times yet, try one more
- if (brace_count[no] <= (brace_min[no] <= brace_max[no]
- ? brace_min[no] : brace_max[no])) {
- rp = regstack_push(RS_BRCPLX_MORE, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- reg_save(&rp->rs_un.regsave, &backpos);
- next = OPERAND(scan);
- // We continue and handle the result when done.
- }
- break;
- }
-
- // If matched enough times, may try matching some more
- if (brace_min[no] <= brace_max[no]) {
- // Range is the normal way around, use longest match
- if (brace_count[no] <= brace_max[no]) {
- rp = regstack_push(RS_BRCPLX_LONG, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- reg_save(&rp->rs_un.regsave, &backpos);
- next = OPERAND(scan);
- // We continue and handle the result when done.
- }
- }
- } else {
- // Range is backwards, use shortest match first
- if (brace_count[no] <= brace_min[no]) {
- rp = regstack_push(RS_BRCPLX_SHORT, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- reg_save(&rp->rs_un.regsave, &backpos);
- // We continue and handle the result when done.
- }
- }
- }
- break;
-
- case BRACE_SIMPLE:
- case STAR:
- case PLUS: {
- regstar_T rst;
-
- // Lookahead to avoid useless match attempts when we know
- // what character comes next.
- if (OP(next) == EXACTLY) {
- rst.nextb = *OPERAND(next);
- if (rex.reg_ic) {
- if (mb_isupper(rst.nextb)) {
- rst.nextb_ic = mb_tolower(rst.nextb);
- } else {
- rst.nextb_ic = mb_toupper(rst.nextb);
- }
- } else {
- rst.nextb_ic = rst.nextb;
- }
- } else {
- rst.nextb = NUL;
- rst.nextb_ic = NUL;
- }
- if (op != BRACE_SIMPLE) {
- rst.minval = (op == STAR) ? 0 : 1;
- rst.maxval = MAX_LIMIT;
- } else {
- rst.minval = bl_minval;
- rst.maxval = bl_maxval;
- }
-
- // When maxval > minval, try matching as much as possible, up
- // to maxval. When maxval < minval, try matching at least the
- // minimal number (since the range is backwards, that's also
- // maxval!).
- rst.count = regrepeat(OPERAND(scan), rst.maxval);
- if (got_int) {
- status = RA_FAIL;
- break;
- }
- if (rst.minval <= rst.maxval
- ? rst.count >= rst.minval : rst.count >= rst.maxval) {
- // It could match. Prepare for trying to match what
- // follows. The code is below. Parameters are stored in
- // a regstar_T on the regstack.
- if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- status = RA_FAIL;
- } else {
- ga_grow(&regstack, sizeof(regstar_T));
- regstack.ga_len += (int)sizeof(regstar_T);
- rp = regstack_push(rst.minval <= rst.maxval ? RS_STAR_LONG : RS_STAR_SHORT, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- *(((regstar_T *)rp) - 1) = rst;
- status = RA_BREAK; // skip the restore bits
- }
- }
- } else {
- status = RA_NOMATCH;
- }
- }
- break;
-
- case NOMATCH:
- case MATCH:
- case SUBPAT:
- rp = regstack_push(RS_NOMATCH, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)op;
- reg_save(&rp->rs_un.regsave, &backpos);
- next = OPERAND(scan);
- // We continue and handle the result when done.
- }
- break;
-
- case BEHIND:
- case NOBEHIND:
- // Need a bit of room to store extra positions.
- if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- status = RA_FAIL;
- } else {
- ga_grow(&regstack, sizeof(regbehind_T));
- regstack.ga_len += (int)sizeof(regbehind_T);
- rp = regstack_push(RS_BEHIND1, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- // Need to save the subexpr to be able to restore them
- // when there is a match but we don't use it.
- save_subexpr(((regbehind_T *)rp) - 1);
-
- rp->rs_no = (int16_t)op;
- reg_save(&rp->rs_un.regsave, &backpos);
- // First try if what follows matches. If it does then we
- // check the behind match by looping.
- }
- }
- break;
-
- case BHPOS:
- if (REG_MULTI) {
- if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line)
- || behind_pos.rs_u.pos.lnum != rex.lnum) {
- status = RA_NOMATCH;
- }
- } else if (behind_pos.rs_u.ptr != rex.input) {
- status = RA_NOMATCH;
- }
- break;
-
- case NEWL:
- if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) {
- status = RA_NOMATCH;
- } else if (rex.reg_line_lbr) {
- ADVANCE_REGINPUT();
- } else {
- reg_nextline();
- }
- break;
-
- case END:
- status = RA_MATCH; // Success!
- break;
-
- default:
- iemsg(_(e_re_corr));
-#ifdef REGEXP_DEBUG
- printf("Illegal op code %d\n", op);
-#endif
- status = RA_FAIL;
- break;
- }
- }
-
- // If we can't continue sequentially, break the inner loop.
- if (status != RA_CONT) {
- break;
- }
-
- // Continue in inner loop, advance to next item.
- scan = next;
- } // end of inner loop
-
- // If there is something on the regstack execute the code for the state.
- // If the state is popped then loop and use the older state.
- while (!GA_EMPTY(&regstack) && status != RA_FAIL) {
- rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
- switch (rp->rs_state) {
- case RS_NOPEN:
- // Result is passed on as-is, simply pop the state.
- regstack_pop(&scan);
- break;
-
- case RS_MOPEN:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no],
- &rex.reg_startp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_ZOPEN:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &reg_startzpos[rp->rs_no],
- &reg_startzp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_MCLOSE:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no],
- &rex.reg_endp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_ZCLOSE:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &reg_endzpos[rp->rs_no],
- &reg_endzp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_BRANCH:
- if (status == RA_MATCH) {
- // this branch matched, use it
- regstack_pop(&scan);
- } else {
- if (status != RA_BREAK) {
- // After a non-matching branch: try next one.
- reg_restore(&rp->rs_un.regsave, &backpos);
- scan = rp->rs_scan;
- }
- if (scan == NULL || OP(scan) != BRANCH) {
- // no more branches, didn't find a match
- status = RA_NOMATCH;
- regstack_pop(&scan);
- } else {
- // Prepare to try a branch.
- rp->rs_scan = regnext(scan);
- reg_save(&rp->rs_un.regsave, &backpos);
- scan = OPERAND(scan);
- }
- }
- break;
-
- case RS_BRCPLX_MORE:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- reg_restore(&rp->rs_un.regsave, &backpos);
- brace_count[rp->rs_no]--; // decrement match count
- }
- regstack_pop(&scan);
- break;
-
- case RS_BRCPLX_LONG:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- // There was no match, but we did find enough matches.
- reg_restore(&rp->rs_un.regsave, &backpos);
- brace_count[rp->rs_no]--;
- // continue with the items after "\{}"
- status = RA_CONT;
- }
- regstack_pop(&scan);
- if (status == RA_CONT) {
- scan = regnext(scan);
- }
- break;
-
- case RS_BRCPLX_SHORT:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- // There was no match, try to match one more item.
- reg_restore(&rp->rs_un.regsave, &backpos);
- }
- regstack_pop(&scan);
- if (status == RA_NOMATCH) {
- scan = OPERAND(scan);
- status = RA_CONT;
- }
- break;
-
- case RS_NOMATCH:
- // Pop the state. If the operand matches for NOMATCH or
- // doesn't match for MATCH/SUBPAT, we fail. Otherwise backup,
- // except for SUBPAT, and continue with the next item.
- if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) {
- status = RA_NOMATCH;
- } else {
- status = RA_CONT;
- if (rp->rs_no != SUBPAT) { // zero-width
- reg_restore(&rp->rs_un.regsave, &backpos);
- }
- }
- regstack_pop(&scan);
- if (status == RA_CONT) {
- scan = regnext(scan);
- }
- break;
-
- case RS_BEHIND1:
- if (status == RA_NOMATCH) {
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regbehind_T);
- } else {
- // The stuff after BEHIND/NOBEHIND matches. Now try if
- // the behind part does (not) match before the current
- // position in the input. This must be done at every
- // position in the input and checking if the match ends at
- // the current position.
-
- // save the position after the found match for next
- reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos);
-
- // Start looking for a match with operand at the current
- // position. Go back one character until we find the
- // result, hitting the start of the line or the previous
- // line (for multi-line matching).
- // Set behind_pos to where the match should end, BHPOS
- // will match it. Save the current value.
- (((regbehind_T *)rp) - 1)->save_behind = behind_pos;
- behind_pos = rp->rs_un.regsave;
-
- rp->rs_state = RS_BEHIND2;
-
- reg_restore(&rp->rs_un.regsave, &backpos);
- scan = OPERAND(rp->rs_scan) + 4;
- }
- break;
-
- case RS_BEHIND2:
- // Looping for BEHIND / NOBEHIND match.
- if (status == RA_MATCH && reg_save_equal(&behind_pos)) {
- // found a match that ends where "next" started
- behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
- if (rp->rs_no == BEHIND) {
- reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
- &backpos);
- } else {
- // But we didn't want a match. Need to restore the
- // subexpr, because what follows matched, so they have
- // been set.
- status = RA_NOMATCH;
- restore_subexpr(((regbehind_T *)rp) - 1);
- }
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regbehind_T);
- } else {
- long limit;
-
- // No match or a match that doesn't end where we want it: Go
- // back one character. May go to previous line once.
- no = OK;
- limit = OPERAND_MIN(rp->rs_scan);
- if (REG_MULTI) {
- if (limit > 0
- && ((rp->rs_un.regsave.rs_u.pos.lnum
- < behind_pos.rs_u.pos.lnum
- ? (colnr_T)strlen((char *)rex.line)
- : behind_pos.rs_u.pos.col)
- - rp->rs_un.regsave.rs_u.pos.col >= limit)) {
- no = FAIL;
- } else if (rp->rs_un.regsave.rs_u.pos.col == 0) {
- if (rp->rs_un.regsave.rs_u.pos.lnum
- < behind_pos.rs_u.pos.lnum
- || reg_getline(--rp->rs_un.regsave.rs_u.pos.lnum)
- == NULL) {
- no = FAIL;
- } else {
- reg_restore(&rp->rs_un.regsave, &backpos);
- rp->rs_un.regsave.rs_u.pos.col =
- (colnr_T)strlen((char *)rex.line);
- }
- } else {
- const uint8_t *const line =
- (uint8_t *)reg_getline(rp->rs_un.regsave.rs_u.pos.lnum);
-
- rp->rs_un.regsave.rs_u.pos.col -=
- utf_head_off((char *)line,
- (char *)line + rp->rs_un.regsave.rs_u.pos.col - 1)
- + 1;
- }
- } else {
- if (rp->rs_un.regsave.rs_u.ptr == rex.line) {
- no = FAIL;
- } else {
- MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr);
- if (limit > 0
- && (behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > (ptrdiff_t)limit) {
- no = FAIL;
- }
- }
- }
- if (no == OK) {
- // Advanced, prepare for finding match again.
- reg_restore(&rp->rs_un.regsave, &backpos);
- scan = OPERAND(rp->rs_scan) + 4;
- if (status == RA_MATCH) {
- // We did match, so subexpr may have been changed,
- // need to restore them for the next try.
- status = RA_NOMATCH;
- restore_subexpr(((regbehind_T *)rp) - 1);
- }
- } else {
- // Can't advance. For NOBEHIND that's a match.
- behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
- if (rp->rs_no == NOBEHIND) {
- reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
- &backpos);
- status = RA_MATCH;
- } else {
- // We do want a proper match. Need to restore the
- // subexpr if we had a match, because they may have
- // been set.
- if (status == RA_MATCH) {
- status = RA_NOMATCH;
- restore_subexpr(((regbehind_T *)rp) - 1);
- }
- }
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regbehind_T);
- }
- }
- break;
-
- case RS_STAR_LONG:
- case RS_STAR_SHORT: {
- regstar_T *rst = ((regstar_T *)rp) - 1;
-
- if (status == RA_MATCH) {
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regstar_T);
- break;
- }
-
- // Tried once already, restore input pointers.
- if (status != RA_BREAK) {
- reg_restore(&rp->rs_un.regsave, &backpos);
- }
-
- // Repeat until we found a position where it could match.
- for (;;) {
- if (status != RA_BREAK) {
- // Tried first position already, advance.
- if (rp->rs_state == RS_STAR_LONG) {
- // Trying for longest match, but couldn't or
- // didn't match -- back up one char.
- if (--rst->count < rst->minval) {
- break;
- }
- if (rex.input == rex.line) {
- // backup to last char of previous line
- if (rex.lnum == 0) {
- status = RA_NOMATCH;
- break;
- }
- rex.lnum--;
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- // Just in case regrepeat() didn't count right.
- if (rex.line == NULL) {
- break;
- }
- rex.input = rex.line + strlen((char *)rex.line);
- fast_breakcheck();
- } else {
- MB_PTR_BACK(rex.line, rex.input);
- }
- } else {
- // Range is backwards, use shortest match first.
- // Careful: maxval and minval are exchanged!
- // Couldn't or didn't match: try advancing one
- // char.
- if (rst->count == rst->minval
- || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) {
- break;
- }
- rst->count++;
- }
- if (got_int) {
- break;
- }
- } else {
- status = RA_NOMATCH;
- }
-
- // If it could match, try it.
- if (rst->nextb == NUL || *rex.input == rst->nextb
- || *rex.input == rst->nextb_ic) {
- reg_save(&rp->rs_un.regsave, &backpos);
- scan = regnext(rp->rs_scan);
- status = RA_CONT;
- break;
- }
- }
- if (status != RA_CONT) {
- // Failed.
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regstar_T);
- status = RA_NOMATCH;
- }
- }
- break;
- }
-
- // If we want to continue the inner loop or didn't pop a state
- // continue matching loop
- if (status == RA_CONT || rp == (regitem_T *)
- ((char *)regstack.ga_data + regstack.ga_len) - 1) {
- break;
- }
- }
-
- // May need to continue with the inner loop, starting at "scan".
- if (status == RA_CONT) {
- continue;
- }
-
- // If the regstack is empty or something failed we are done.
- if (GA_EMPTY(&regstack) || status == RA_FAIL) {
- if (scan == NULL) {
- // We get here only if there's trouble -- normally "case END" is
- // the terminating point.
- iemsg(_(e_re_corr));
-#ifdef REGEXP_DEBUG
- printf("Premature EOL\n");
-#endif
- }
- return status == RA_MATCH;
- }
- } // End of loop until the regstack is empty.
-
- // NOTREACHED
-}
-
-/// Try match of "prog" with at rex.line["col"].
-///
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return 0 for failure, or number of lines contained in the match.
-static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
-{
- rex.input = rex.line + col;
- rex.need_clear_subexpr = true;
- // Clear the external match subpointers if necessaey.
- rex.need_clear_zsubexpr = (prog->reghasz == REX_SET);
-
- if (regmatch(prog->program + 1, tm, timed_out) == 0) {
- return 0;
- }
-
- cleanup_subexpr();
- if (REG_MULTI) {
- if (rex.reg_startpos[0].lnum < 0) {
- rex.reg_startpos[0].lnum = 0;
- rex.reg_startpos[0].col = col;
- }
- if (rex.reg_endpos[0].lnum < 0) {
- rex.reg_endpos[0].lnum = rex.lnum;
- rex.reg_endpos[0].col = (int)(rex.input - rex.line);
- } else {
- // Use line number of "\ze".
- rex.lnum = rex.reg_endpos[0].lnum;
- }
- } else {
- if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = rex.line + col;
- }
- if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = rex.input;
- }
- }
- // Package any found \z(...\) matches for export. Default is none.
- unref_extmatch(re_extmatch_out);
- re_extmatch_out = NULL;
-
- if (prog->reghasz == REX_SET) {
- int i;
-
- cleanup_zsubexpr();
- re_extmatch_out = make_extmatch();
- for (i = 0; i < NSUBEXP; i++) {
- if (REG_MULTI) {
- // Only accept single line matches.
- if (reg_startzpos[i].lnum >= 0
- && reg_endzpos[i].lnum == reg_startzpos[i].lnum
- && reg_endzpos[i].col >= reg_startzpos[i].col) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col,
- (size_t)(reg_endzpos[i].col - reg_startzpos[i].col));
- }
- } else {
- if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i]));
- }
- }
- }
- }
- return 1 + rex.lnum;
-}
-
-/// Match a regexp against a string ("line" points to the string) or multiple
-/// lines (if "line" is NULL, use reg_getline()).
-///
-/// @param startcol column to start looking for match
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return 0 for failure, or number of lines contained in the match.
-static long bt_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
-{
- bt_regprog_T *prog;
- uint8_t *s;
- colnr_T col = startcol;
- long retval = 0L;
-
- // Create "regstack" and "backpos" if they are not allocated yet.
- // We allocate *_INITIAL amount of bytes first and then set the grow size
- // to much bigger value to avoid many malloc calls in case of deep regular
- // expressions.
- if (regstack.ga_data == NULL) {
- // Use an item size of 1 byte, since we push different things
- // onto the regstack.
- ga_init(&regstack, 1, REGSTACK_INITIAL);
- ga_grow(&regstack, REGSTACK_INITIAL);
- ga_set_growsize(&regstack, REGSTACK_INITIAL * 8);
- }
-
- if (backpos.ga_data == NULL) {
- ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL);
- ga_grow(&backpos, BACKPOS_INITIAL);
- ga_set_growsize(&backpos, BACKPOS_INITIAL * 8);
- }
-
- if (REG_MULTI) {
- prog = (bt_regprog_T *)rex.reg_mmatch->regprog;
- line = (uint8_t *)reg_getline((linenr_T)0);
- rex.reg_startpos = rex.reg_mmatch->startpos;
- rex.reg_endpos = rex.reg_mmatch->endpos;
- } else {
- prog = (bt_regprog_T *)rex.reg_match->regprog;
- rex.reg_startp = (uint8_t **)rex.reg_match->startp;
- rex.reg_endp = (uint8_t **)rex.reg_match->endp;
- }
-
- // Be paranoid...
- if (prog == NULL || line == NULL) {
- iemsg(_(e_null));
- goto theend;
- }
-
- // Check validity of program.
- if (prog_magic_wrong()) {
- goto theend;
- }
-
- // If the start column is past the maximum column: no need to try.
- if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
- goto theend;
- }
-
- // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
- if (prog->regflags & RF_ICASE) {
- rex.reg_ic = true;
- } else if (prog->regflags & RF_NOICASE) {
- rex.reg_ic = false;
- }
-
- // If pattern contains "\Z" overrule value of rex.reg_icombine
- if (prog->regflags & RF_ICOMBINE) {
- rex.reg_icombine = true;
- }
-
- // If there is a "must appear" string, look for it.
- if (prog->regmust != NULL) {
- int c = utf_ptr2char((char *)prog->regmust);
- s = line + col;
-
- // This is used very often, esp. for ":global". Use two versions of
- // the loop to avoid overhead of conditions.
- if (!rex.reg_ic) {
- while ((s = (uint8_t *)vim_strchr((char *)s, c)) != NULL) {
- if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
- break; // Found it.
- }
- MB_PTR_ADV(s);
- }
- } else {
- while ((s = (uint8_t *)cstrchr((char *)s, c)) != NULL) {
- if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
- break; // Found it.
- }
- MB_PTR_ADV(s);
- }
- }
- if (s == NULL) { // Not present.
- goto theend;
- }
- }
-
- rex.line = line;
- rex.lnum = 0;
- reg_toolong = false;
-
- // Simplest case: Anchored match need be tried only once.
- if (prog->reganch) {
- int c = utf_ptr2char((char *)rex.line + col);
- if (prog->regstart == NUL
- || prog->regstart == c
- || (rex.reg_ic
- && (utf_fold(prog->regstart) == utf_fold(c)
- || (c < 255 && prog->regstart < 255
- && mb_tolower(prog->regstart) == mb_tolower(c))))) {
- retval = regtry(prog, col, tm, timed_out);
- } else {
- retval = 0;
- }
- } else {
- int tm_count = 0;
- // Messy cases: unanchored match.
- while (!got_int) {
- if (prog->regstart != NUL) {
- // Skip until the char we know it must start with.
- s = (uint8_t *)cstrchr((char *)rex.line + col, prog->regstart);
- if (s == NULL) {
- retval = 0;
- break;
- }
- col = (int)(s - rex.line);
- }
-
- // Check for maximum column to try.
- if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
- retval = 0;
- break;
- }
-
- retval = regtry(prog, col, tm, timed_out);
- if (retval > 0) {
- break;
- }
-
- // if not currently on the first line, get it again
- if (rex.lnum != 0) {
- rex.lnum = 0;
- rex.line = (uint8_t *)reg_getline((linenr_T)0);
- }
- if (rex.line[col] == NUL) {
- break;
- }
- col += utfc_ptr2len((char *)rex.line + col);
- // Check for timeout once in a twenty times to avoid overhead.
- if (tm != NULL && ++tm_count == 20) {
- tm_count = 0;
- if (profile_passed_limit(*tm)) {
- if (timed_out != NULL) {
- *timed_out = true;
- }
- break;
- }
- }
- }
- }
-
-theend:
- // Free "reg_tofree" when it's a bit big.
- // Free regstack and backpos if they are bigger than their initial size.
- if (reg_tofreelen > 400) {
- XFREE_CLEAR(reg_tofree);
- }
- if (regstack.ga_maxlen > REGSTACK_INITIAL) {
- ga_clear(&regstack);
- }
- if (backpos.ga_maxlen > BACKPOS_INITIAL) {
- ga_clear(&backpos);
- }
-
- if (retval > 0) {
- // Make sure the end is never before the start. Can happen when \zs
- // and \ze are used.
- if (REG_MULTI) {
- const lpos_T *const start = &rex.reg_mmatch->startpos[0];
- const lpos_T *const end = &rex.reg_mmatch->endpos[0];
-
- if (end->lnum < start->lnum
- || (end->lnum == start->lnum && end->col < start->col)) {
- rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
- }
-
- // startpos[0] may be set by "\zs", also return the column where
- // the whole pattern matched.
- rex.reg_mmatch->rmm_matchcol = col;
- } else {
- if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
- rex.reg_match->endp[0] = rex.reg_match->startp[0];
- }
-
- // startpos[0] may be set by "\zs", also return the column where
- // the whole pattern matched.
- rex.reg_match->rm_matchcol = col;
- }
- }
-
- return retval;
-}
-
-/// Match a regexp against a string.
-/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
-///
-/// @param line string to match against
-/// @param col column to start looking for match
-///
-/// @return 0 for failure, number of lines contained in the match otherwise.
-static int bt_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
-{
- rex.reg_match = rmp;
- rex.reg_mmatch = NULL;
- rex.reg_maxline = 0;
- rex.reg_line_lbr = line_lbr;
- rex.reg_buf = curbuf;
- rex.reg_win = NULL;
- rex.reg_ic = rmp->rm_ic;
- rex.reg_icombine = false;
- rex.reg_maxcol = 0;
-
- long r = bt_regexec_both(line, col, NULL, NULL);
- assert(r <= INT_MAX);
- return (int)r;
-}
-
-/// Matches a regexp against multiple lines.
-/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-///
-/// @param win Window in which to search or NULL
-/// @param buf Buffer in which to search
-/// @param lnum Number of line to start looking for match
-/// @param col Column to start looking for match
-/// @param tm Timeout limit or NULL
-///
-/// @return zero if there is no match and number of lines contained in the match
-/// otherwise.
-static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
- proftime_T *tm, int *timed_out)
-{
- init_regexec_multi(rmp, win, buf, lnum);
- return bt_regexec_both(NULL, col, tm, timed_out);
-}
-
-// Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL.
-static int re_num_cmp(uint32_t val, uint8_t *scan)
-{
- uint32_t n = (uint32_t)OPERAND_MIN(scan);
-
- if (OPERAND_CMP(scan) == '>') {
- return val > n;
- }
- if (OPERAND_CMP(scan) == '<') {
- return val < n;
- }
- return val == n;
-}
-
-#ifdef BT_REGEXP_DUMP
-
-// regdump - dump a regexp onto stdout in vaguely comprehensible form
-static void regdump(uint8_t *pattern, bt_regprog_T *r)
-{
- uint8_t *s;
- int op = EXACTLY; // Arbitrary non-END op.
- uint8_t *next;
- uint8_t *end = NULL;
- FILE *f;
-
-# ifdef BT_REGEXP_LOG
- f = fopen("bt_regexp_log.log", "a");
-# else
- f = stdout;
-# endif
- if (f == NULL) {
- return;
- }
- fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n",
- pattern);
-
- s = r->program + 1;
- // Loop until we find the END that isn't before a referred next (an END
- // can also appear in a NOMATCH operand).
- while (op != END || s <= end) {
- op = OP(s);
- fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); // Where, what.
- next = regnext(s);
- if (next == NULL) { // Next ptr.
- fprintf(f, "(0)");
- } else {
- fprintf(f, "(%d)", (int)((s - r->program) + (next - s)));
- }
- if (end < next) {
- end = next;
- }
- if (op == BRACE_LIMITS) {
- // Two ints
- fprintf(f, " minval %" PRId64 ", maxval %" PRId64,
- (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s));
- s += 8;
- } else if (op == BEHIND || op == NOBEHIND) {
- // one int
- fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
- s += 4;
- } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) {
- // one int plus comparator
- fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
- s += 5;
- }
- s += 3;
- if (op == ANYOF || op == ANYOF + ADD_NL
- || op == ANYBUT || op == ANYBUT + ADD_NL
- || op == EXACTLY) {
- // Literal string, where present.
- fprintf(f, "\nxxxxxxxxx\n");
- while (*s != NUL) {
- fprintf(f, "%c", *s++);
- }
- fprintf(f, "\nxxxxxxxxx\n");
- s++;
- }
- fprintf(f, "\r\n");
- }
-
- // Header fields of interest.
- if (r->regstart != NUL) {
- fprintf(f, "start `%s' 0x%x; ", r->regstart < 256
- ? (char *)transchar(r->regstart)
- : "multibyte", r->regstart);
- }
- if (r->reganch) {
- fprintf(f, "anchored; ");
- }
- if (r->regmust != NULL) {
- fprintf(f, "must have \"%s\"", r->regmust);
- }
- fprintf(f, "\r\n");
-
-# ifdef BT_REGEXP_LOG
- fclose(f);
-# endif
-}
-#endif // BT_REGEXP_DUMP
-
-#ifdef REGEXP_DEBUG
-
-// regprop - printable representation of opcode
-static uint8_t *regprop(uint8_t *op)
-{
- char *p;
- static char buf[50];
-
- STRCPY(buf, ":");
-
- switch ((int)OP(op)) {
- case BOL:
- p = "BOL";
- break;
- case EOL:
- p = "EOL";
- break;
- case RE_BOF:
- p = "BOF";
- break;
- case RE_EOF:
- p = "EOF";
- break;
- case CURSOR:
- p = "CURSOR";
- break;
- case RE_VISUAL:
- p = "RE_VISUAL";
- break;
- case RE_LNUM:
- p = "RE_LNUM";
- break;
- case RE_MARK:
- p = "RE_MARK";
- break;
- case RE_COL:
- p = "RE_COL";
- break;
- case RE_VCOL:
- p = "RE_VCOL";
- break;
- case BOW:
- p = "BOW";
- break;
- case EOW:
- p = "EOW";
- break;
- case ANY:
- p = "ANY";
- break;
- case ANY + ADD_NL:
- p = "ANY+NL";
- break;
- case ANYOF:
- p = "ANYOF";
- break;
- case ANYOF + ADD_NL:
- p = "ANYOF+NL";
- break;
- case ANYBUT:
- p = "ANYBUT";
- break;
- case ANYBUT + ADD_NL:
- p = "ANYBUT+NL";
- break;
- case IDENT:
- p = "IDENT";
- break;
- case IDENT + ADD_NL:
- p = "IDENT+NL";
- break;
- case SIDENT:
- p = "SIDENT";
- break;
- case SIDENT + ADD_NL:
- p = "SIDENT+NL";
- break;
- case KWORD:
- p = "KWORD";
- break;
- case KWORD + ADD_NL:
- p = "KWORD+NL";
- break;
- case SKWORD:
- p = "SKWORD";
- break;
- case SKWORD + ADD_NL:
- p = "SKWORD+NL";
- break;
- case FNAME:
- p = "FNAME";
- break;
- case FNAME + ADD_NL:
- p = "FNAME+NL";
- break;
- case SFNAME:
- p = "SFNAME";
- break;
- case SFNAME + ADD_NL:
- p = "SFNAME+NL";
- break;
- case PRINT:
- p = "PRINT";
- break;
- case PRINT + ADD_NL:
- p = "PRINT+NL";
- break;
- case SPRINT:
- p = "SPRINT";
- break;
- case SPRINT + ADD_NL:
- p = "SPRINT+NL";
- break;
- case WHITE:
- p = "WHITE";
- break;
- case WHITE + ADD_NL:
- p = "WHITE+NL";
- break;
- case NWHITE:
- p = "NWHITE";
- break;
- case NWHITE + ADD_NL:
- p = "NWHITE+NL";
- break;
- case DIGIT:
- p = "DIGIT";
- break;
- case DIGIT + ADD_NL:
- p = "DIGIT+NL";
- break;
- case NDIGIT:
- p = "NDIGIT";
- break;
- case NDIGIT + ADD_NL:
- p = "NDIGIT+NL";
- break;
- case HEX:
- p = "HEX";
- break;
- case HEX + ADD_NL:
- p = "HEX+NL";
- break;
- case NHEX:
- p = "NHEX";
- break;
- case NHEX + ADD_NL:
- p = "NHEX+NL";
- break;
- case OCTAL:
- p = "OCTAL";
- break;
- case OCTAL + ADD_NL:
- p = "OCTAL+NL";
- break;
- case NOCTAL:
- p = "NOCTAL";
- break;
- case NOCTAL + ADD_NL:
- p = "NOCTAL+NL";
- break;
- case WORD:
- p = "WORD";
- break;
- case WORD + ADD_NL:
- p = "WORD+NL";
- break;
- case NWORD:
- p = "NWORD";
- break;
- case NWORD + ADD_NL:
- p = "NWORD+NL";
- break;
- case HEAD:
- p = "HEAD";
- break;
- case HEAD + ADD_NL:
- p = "HEAD+NL";
- break;
- case NHEAD:
- p = "NHEAD";
- break;
- case NHEAD + ADD_NL:
- p = "NHEAD+NL";
- break;
- case ALPHA:
- p = "ALPHA";
- break;
- case ALPHA + ADD_NL:
- p = "ALPHA+NL";
- break;
- case NALPHA:
- p = "NALPHA";
- break;
- case NALPHA + ADD_NL:
- p = "NALPHA+NL";
- break;
- case LOWER:
- p = "LOWER";
- break;
- case LOWER + ADD_NL:
- p = "LOWER+NL";
- break;
- case NLOWER:
- p = "NLOWER";
- break;
- case NLOWER + ADD_NL:
- p = "NLOWER+NL";
- break;
- case UPPER:
- p = "UPPER";
- break;
- case UPPER + ADD_NL:
- p = "UPPER+NL";
- break;
- case NUPPER:
- p = "NUPPER";
- break;
- case NUPPER + ADD_NL:
- p = "NUPPER+NL";
- break;
- case BRANCH:
- p = "BRANCH";
- break;
- case EXACTLY:
- p = "EXACTLY";
- break;
- case NOTHING:
- p = "NOTHING";
- break;
- case BACK:
- p = "BACK";
- break;
- case END:
- p = "END";
- break;
- case MOPEN + 0:
- p = "MATCH START";
- break;
- case MOPEN + 1:
- case MOPEN + 2:
- case MOPEN + 3:
- case MOPEN + 4:
- case MOPEN + 5:
- case MOPEN + 6:
- case MOPEN + 7:
- case MOPEN + 8:
- case MOPEN + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MOPEN%d", OP(op) - MOPEN);
- p = NULL;
- break;
- case MCLOSE + 0:
- p = "MATCH END";
- break;
- case MCLOSE + 1:
- case MCLOSE + 2:
- case MCLOSE + 3:
- case MCLOSE + 4:
- case MCLOSE + 5:
- case MCLOSE + 6:
- case MCLOSE + 7:
- case MCLOSE + 8:
- case MCLOSE + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MCLOSE%d", OP(op) - MCLOSE);
- p = NULL;
- break;
- case BACKREF + 1:
- case BACKREF + 2:
- case BACKREF + 3:
- case BACKREF + 4:
- case BACKREF + 5:
- case BACKREF + 6:
- case BACKREF + 7:
- case BACKREF + 8:
- case BACKREF + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BACKREF%d", OP(op) - BACKREF);
- p = NULL;
- break;
- case NOPEN:
- p = "NOPEN";
- break;
- case NCLOSE:
- p = "NCLOSE";
- break;
- case ZOPEN + 1:
- case ZOPEN + 2:
- case ZOPEN + 3:
- case ZOPEN + 4:
- case ZOPEN + 5:
- case ZOPEN + 6:
- case ZOPEN + 7:
- case ZOPEN + 8:
- case ZOPEN + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZOPEN%d", OP(op) - ZOPEN);
- p = NULL;
- break;
- case ZCLOSE + 1:
- case ZCLOSE + 2:
- case ZCLOSE + 3:
- case ZCLOSE + 4:
- case ZCLOSE + 5:
- case ZCLOSE + 6:
- case ZCLOSE + 7:
- case ZCLOSE + 8:
- case ZCLOSE + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZCLOSE%d", OP(op) - ZCLOSE);
- p = NULL;
- break;
- case ZREF + 1:
- case ZREF + 2:
- case ZREF + 3:
- case ZREF + 4:
- case ZREF + 5:
- case ZREF + 6:
- case ZREF + 7:
- case ZREF + 8:
- case ZREF + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZREF%d", OP(op) - ZREF);
- p = NULL;
- break;
- case STAR:
- p = "STAR";
- break;
- case PLUS:
- p = "PLUS";
- break;
- case NOMATCH:
- p = "NOMATCH";
- break;
- case MATCH:
- p = "MATCH";
- break;
- case BEHIND:
- p = "BEHIND";
- break;
- case NOBEHIND:
- p = "NOBEHIND";
- break;
- case SUBPAT:
- p = "SUBPAT";
- break;
- case BRACE_LIMITS:
- p = "BRACE_LIMITS";
- break;
- case BRACE_SIMPLE:
- p = "BRACE_SIMPLE";
- break;
- case BRACE_COMPLEX + 0:
- case BRACE_COMPLEX + 1:
- case BRACE_COMPLEX + 2:
- case BRACE_COMPLEX + 3:
- case BRACE_COMPLEX + 4:
- case BRACE_COMPLEX + 5:
- case BRACE_COMPLEX + 6:
- case BRACE_COMPLEX + 7:
- case BRACE_COMPLEX + 8:
- case BRACE_COMPLEX + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BRACE_COMPLEX%d",
- OP(op) - BRACE_COMPLEX);
- p = NULL;
- break;
- case MULTIBYTECODE:
- p = "MULTIBYTECODE";
- break;
- case NEWL:
- p = "NEWL";
- break;
- default:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "corrupt %d", OP(op));
- p = NULL;
- break;
- }
- if (p != NULL) {
- STRCAT(buf, p);
- }
- return (uint8_t *)buf;
-}
-#endif // REGEXP_DEBUG
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 16bb2db464..079f3b6929 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -7,13 +7,13 @@
//
// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
-#ifndef NVIM_REGEXP_DEFS_H
-#define NVIM_REGEXP_DEFS_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
/// Used for "magic_overruled".
typedef enum {
@@ -33,25 +33,29 @@ typedef enum {
MAGIC_ALL = 4, ///< "\v" very magic
} magic_T;
-// The number of sub-matches is limited to 10.
-// The first one (index 0) is the whole match, referenced with "\0".
-// The second one (index 1) is the first sub-match, referenced with "\1".
-// This goes up to the tenth (index 9), referenced with "\9".
-#define NSUBEXP 10
+/// The number of sub-matches is limited to 10.
+/// The first one (index 0) is the whole match, referenced with "\0".
+/// The second one (index 1) is the first sub-match, referenced with "\1".
+/// This goes up to the tenth (index 9), referenced with "\9".
+enum { NSUBEXP = 10, };
-// In the NFA engine: how many braces are allowed.
-// TODO(RE): Use dynamic memory allocation instead of static, like here
-#define NFA_MAX_BRACES 20
+/// In the NFA engine: how many braces are allowed.
+/// TODO(RE): Use dynamic memory allocation instead of static, like here
+enum { NFA_MAX_BRACES = 20, };
-// In the NFA engine: how many states are allowed.
-#define NFA_MAX_STATES 100000
-#define NFA_TOO_EXPENSIVE (-1)
+/// In the NFA engine: how many states are allowed.
+enum {
+ NFA_MAX_STATES = 100000,
+ NFA_TOO_EXPENSIVE = -1,
+};
-// Which regexp engine to use? Needed for vim_regcomp().
-// Must match with 'regexpengine'.
-#define AUTOMATIC_ENGINE 0
-#define BACKTRACKING_ENGINE 1
-#define NFA_ENGINE 2
+/// Which regexp engine to use? Needed for vim_regcomp().
+/// Must match with 'regexpengine'.
+enum {
+ AUTOMATIC_ENGINE = 0,
+ BACKTRACKING_ENGINE = 1,
+ NFA_ENGINE = 2,
+};
typedef struct regengine regengine_T;
typedef struct regprog regprog_T;
@@ -70,14 +74,14 @@ typedef struct {
colnr_T rmm_matchcol; ///< match start without "\zs"
int rmm_ic;
- colnr_T rmm_maxcol; /// when not zero: maximum column
+ colnr_T rmm_maxcol; ///< when not zero: maximum column
} regmmatch_T;
#include "nvim/buffer_defs.h"
-// Structure returned by vim_regcomp() to pass on to vim_regexec().
-// This is the general structure. For the actual matcher, two specific
-// structures are used. See code below.
+/// Structure returned by vim_regcomp() to pass on to vim_regexec().
+/// This is the general structure. For the actual matcher, two specific
+/// structures are used. See code below.
struct regprog {
regengine_T *engine;
unsigned regflags;
@@ -86,9 +90,9 @@ struct regprog {
bool re_in_use; ///< prog is being executed
};
-// Structure used by the back track matcher.
-// These fields are only to be used in regexp.c!
-// See regexp.c for an explanation.
+/// Structure used by the back track matcher.
+/// These fields are only to be used in regexp.c!
+/// See regexp.c for an explanation.
typedef struct {
// These four members implement regprog_T.
regengine_T *engine;
@@ -98,26 +102,26 @@ typedef struct {
bool re_in_use;
int regstart;
- char_u reganch;
- char_u *regmust;
+ uint8_t reganch;
+ uint8_t *regmust;
int regmlen;
- char_u reghasz;
- char_u program[1]; // actually longer..
+ uint8_t reghasz;
+ uint8_t program[];
} bt_regprog_T;
-// Structure representing a NFA state.
-// An NFA state may have no outgoing edge, when it is a NFA_MATCH state.
+/// Structure representing a NFA state.
+/// An NFA state may have no outgoing edge, when it is a NFA_MATCH state.
typedef struct nfa_state nfa_state_T;
struct nfa_state {
int c;
nfa_state_T *out;
nfa_state_T *out1;
int id;
- int lastlist[2]; // 0: normal, 1: recursive
+ int lastlist[2]; ///< 0: normal, 1: recursive
int val;
};
-// Structure used by the NFA matcher.
+/// Structure used by the NFA matcher.
typedef struct {
// These four members implement regprog_T.
regengine_T *engine;
@@ -126,24 +130,24 @@ typedef struct {
unsigned re_flags;
bool re_in_use;
- nfa_state_T *start; // points into state[]
+ nfa_state_T *start; ///< points into state[]
- int reganch; // pattern starts with ^
- int regstart; // char at start of pattern
- char_u *match_text; // plain text to match with
+ int reganch; ///< pattern starts with ^
+ int regstart; ///< char at start of pattern
+ uint8_t *match_text; ///< plain text to match with
- int has_zend; // pattern contains \ze
- int has_backref; // pattern contains \1 .. \9
+ int has_zend; ///< pattern contains \ze
+ int has_backref; ///< pattern contains \1 .. \9
int reghasz;
char *pattern;
- int nsubexp; // number of ()
+ int nsubexp; ///< number of ()
int nstate;
- nfa_state_T state[1]; // actually longer..
+ nfa_state_T state[];
} nfa_regprog_T;
-// Structure to be used for single-line matching.
-// Sub-match "no" starts at "startp[no]" and ends just before "endp[no]".
-// When there is no match, the pointer is NULL.
+/// Structure to be used for single-line matching.
+/// Sub-match "no" starts at "startp[no]" and ends just before "endp[no]".
+/// When there is no match, the pointer is NULL.
typedef struct {
regprog_T *regprog;
char *startp[NSUBEXP];
@@ -153,29 +157,29 @@ typedef struct {
bool rm_ic;
} regmatch_T;
-// Structure used to store external references: "\z\(\)" to "\z\1".
-// Use a reference count to avoid the need to copy this around. When it goes
-// from 1 to zero the matches need to be freed.
+/// Structure used to store external references: "\z\(\)" to "\z\1".
+/// Use a reference count to avoid the need to copy this around. When it goes
+/// from 1 to zero the matches need to be freed.
struct reg_extmatch {
int16_t refcnt;
- char_u *matches[NSUBEXP];
+ uint8_t *matches[NSUBEXP];
};
struct regengine {
/// bt_regcomp or nfa_regcomp
- regprog_T *(*regcomp)(char_u *, int);
+ regprog_T *(*regcomp)(uint8_t *, int);
/// bt_regfree or nfa_regfree
void (*regfree)(regprog_T *);
/// bt_regexec_nl or nfa_regexec_nl
- int (*regexec_nl)(regmatch_T *, char_u *, colnr_T, bool);
+ int (*regexec_nl)(regmatch_T *, uint8_t *, colnr_T, bool);
/// bt_regexec_mult or nfa_regexec_mult
- long (*regexec_multi)(regmmatch_T *, win_T *, buf_T *, linenr_T, colnr_T, proftime_T *, int *);
- // char_u *expr;
+ int (*regexec_multi)(regmmatch_T *, win_T *, buf_T *, linenr_T, colnr_T, proftime_T *, int *);
+ // uint8_t *expr;
};
-// Flags used by vim_regsub() and vim_regsub_both()
-#define REGSUB_COPY 1
-#define REGSUB_MAGIC 2
-#define REGSUB_BACKSLASH 4
-
-#endif // NVIM_REGEXP_DEFS_H
+/// Flags used by vim_regsub() and vim_regsub_both()
+enum {
+ REGSUB_COPY = 1,
+ REGSUB_MAGIC = 2,
+ REGSUB_BACKSLASH = 4,
+};
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
deleted file mode 100644
index 93b03f0632..0000000000
--- a/src/nvim/regexp_nfa.c
+++ /dev/null
@@ -1,7639 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// NFA regular expression implementation.
-//
-// This file is included in "regexp.c".
-
-#include <assert.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdbool.h>
-
-#include "nvim/ascii.h"
-#include "nvim/garray.h"
-#include "nvim/os/input.h"
-
-// Logging of NFA engine.
-//
-// The NFA engine can write four log files:
-// - Error log: Contains NFA engine's fatal errors.
-// - Dump log: Contains compiled NFA state machine's information.
-// - Run log: Contains information of matching procedure.
-// - Debug log: Contains detailed information of matching procedure. Can be
-// disabled by undefining NFA_REGEXP_DEBUG_LOG.
-// The first one can also be used without debug mode.
-// The last three are enabled when compiled as debug mode and individually
-// disabled by commenting them out.
-// The log files can get quite big!
-// To disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in
-// regexp.c
-#ifdef REGEXP_DEBUG
-# define NFA_REGEXP_ERROR_LOG "nfa_regexp_error.log"
-# define NFA_REGEXP_DUMP_LOG "nfa_regexp_dump.log"
-# define NFA_REGEXP_RUN_LOG "nfa_regexp_run.log"
-# define NFA_REGEXP_DEBUG_LOG "nfa_regexp_debug.log"
-#endif
-
-// Added to NFA_ANY - NFA_NUPPER_IC to include a NL.
-#define NFA_ADD_NL 31
-
-enum {
- NFA_SPLIT = -1024,
- NFA_MATCH,
- NFA_EMPTY, // matches 0-length
-
- NFA_START_COLL, // [abc] start
- NFA_END_COLL, // [abc] end
- NFA_START_NEG_COLL, // [^abc] start
- NFA_END_NEG_COLL, // [^abc] end (postfix only)
- NFA_RANGE, // range of the two previous items
- // (postfix only)
- NFA_RANGE_MIN, // low end of a range
- NFA_RANGE_MAX, // high end of a range
-
- NFA_CONCAT, // concatenate two previous items (postfix
- // only)
- NFA_OR, // \| (postfix only)
- NFA_STAR, // greedy * (postfix only)
- NFA_STAR_NONGREEDY, // non-greedy * (postfix only)
- NFA_QUEST, // greedy \? (postfix only)
- NFA_QUEST_NONGREEDY, // non-greedy \? (postfix only)
-
- NFA_BOL, // ^ Begin line
- NFA_EOL, // $ End line
- NFA_BOW, // \< Begin word
- NFA_EOW, // \> End word
- NFA_BOF, // \%^ Begin file
- NFA_EOF, // \%$ End file
- NFA_NEWL,
- NFA_ZSTART, // Used for \zs
- NFA_ZEND, // Used for \ze
- NFA_NOPEN, // Start of subexpression marked with \%(
- NFA_NCLOSE, // End of subexpr. marked with \%( ... \)
- NFA_START_INVISIBLE,
- NFA_START_INVISIBLE_FIRST,
- NFA_START_INVISIBLE_NEG,
- NFA_START_INVISIBLE_NEG_FIRST,
- NFA_START_INVISIBLE_BEFORE,
- NFA_START_INVISIBLE_BEFORE_FIRST,
- NFA_START_INVISIBLE_BEFORE_NEG,
- NFA_START_INVISIBLE_BEFORE_NEG_FIRST,
- NFA_START_PATTERN,
- NFA_END_INVISIBLE,
- NFA_END_INVISIBLE_NEG,
- NFA_END_PATTERN,
- NFA_COMPOSING, // Next nodes in NFA are part of the
- // composing multibyte char
- NFA_END_COMPOSING, // End of a composing char in the NFA
- NFA_ANY_COMPOSING, // \%C: Any composing characters.
- NFA_OPT_CHARS, // \%[abc]
-
- // The following are used only in the postfix form, not in the NFA
- NFA_PREV_ATOM_NO_WIDTH, // Used for \@=
- NFA_PREV_ATOM_NO_WIDTH_NEG, // Used for \@!
- NFA_PREV_ATOM_JUST_BEFORE, // Used for \@<=
- NFA_PREV_ATOM_JUST_BEFORE_NEG, // Used for \@<!
- NFA_PREV_ATOM_LIKE_PATTERN, // Used for \@>
-
- NFA_BACKREF1, // \1
- NFA_BACKREF2, // \2
- NFA_BACKREF3, // \3
- NFA_BACKREF4, // \4
- NFA_BACKREF5, // \5
- NFA_BACKREF6, // \6
- NFA_BACKREF7, // \7
- NFA_BACKREF8, // \8
- NFA_BACKREF9, // \9
- NFA_ZREF1, // \z1
- NFA_ZREF2, // \z2
- NFA_ZREF3, // \z3
- NFA_ZREF4, // \z4
- NFA_ZREF5, // \z5
- NFA_ZREF6, // \z6
- NFA_ZREF7, // \z7
- NFA_ZREF8, // \z8
- NFA_ZREF9, // \z9
- NFA_SKIP, // Skip characters
-
- NFA_MOPEN,
- NFA_MOPEN1,
- NFA_MOPEN2,
- NFA_MOPEN3,
- NFA_MOPEN4,
- NFA_MOPEN5,
- NFA_MOPEN6,
- NFA_MOPEN7,
- NFA_MOPEN8,
- NFA_MOPEN9,
-
- NFA_MCLOSE,
- NFA_MCLOSE1,
- NFA_MCLOSE2,
- NFA_MCLOSE3,
- NFA_MCLOSE4,
- NFA_MCLOSE5,
- NFA_MCLOSE6,
- NFA_MCLOSE7,
- NFA_MCLOSE8,
- NFA_MCLOSE9,
-
- NFA_ZOPEN,
- NFA_ZOPEN1,
- NFA_ZOPEN2,
- NFA_ZOPEN3,
- NFA_ZOPEN4,
- NFA_ZOPEN5,
- NFA_ZOPEN6,
- NFA_ZOPEN7,
- NFA_ZOPEN8,
- NFA_ZOPEN9,
-
- NFA_ZCLOSE,
- NFA_ZCLOSE1,
- NFA_ZCLOSE2,
- NFA_ZCLOSE3,
- NFA_ZCLOSE4,
- NFA_ZCLOSE5,
- NFA_ZCLOSE6,
- NFA_ZCLOSE7,
- NFA_ZCLOSE8,
- NFA_ZCLOSE9,
-
- // NFA_FIRST_NL
- NFA_ANY, // Match any one character.
- NFA_IDENT, // Match identifier char
- NFA_SIDENT, // Match identifier char but no digit
- NFA_KWORD, // Match keyword char
- NFA_SKWORD, // Match word char but no digit
- NFA_FNAME, // Match file name char
- NFA_SFNAME, // Match file name char but no digit
- NFA_PRINT, // Match printable char
- NFA_SPRINT, // Match printable char but no digit
- NFA_WHITE, // Match whitespace char
- NFA_NWHITE, // Match non-whitespace char
- NFA_DIGIT, // Match digit char
- NFA_NDIGIT, // Match non-digit char
- NFA_HEX, // Match hex char
- NFA_NHEX, // Match non-hex char
- NFA_OCTAL, // Match octal char
- NFA_NOCTAL, // Match non-octal char
- NFA_WORD, // Match word char
- NFA_NWORD, // Match non-word char
- NFA_HEAD, // Match head char
- NFA_NHEAD, // Match non-head char
- NFA_ALPHA, // Match alpha char
- NFA_NALPHA, // Match non-alpha char
- NFA_LOWER, // Match lowercase char
- NFA_NLOWER, // Match non-lowercase char
- NFA_UPPER, // Match uppercase char
- NFA_NUPPER, // Match non-uppercase char
- NFA_LOWER_IC, // Match [a-z]
- NFA_NLOWER_IC, // Match [^a-z]
- NFA_UPPER_IC, // Match [A-Z]
- NFA_NUPPER_IC, // Match [^A-Z]
-
- NFA_FIRST_NL = NFA_ANY + NFA_ADD_NL,
- NFA_LAST_NL = NFA_NUPPER_IC + NFA_ADD_NL,
-
- NFA_CURSOR, // Match cursor pos
- NFA_LNUM, // Match line number
- NFA_LNUM_GT, // Match > line number
- NFA_LNUM_LT, // Match < line number
- NFA_COL, // Match cursor column
- NFA_COL_GT, // Match > cursor column
- NFA_COL_LT, // Match < cursor column
- NFA_VCOL, // Match cursor virtual column
- NFA_VCOL_GT, // Match > cursor virtual column
- NFA_VCOL_LT, // Match < cursor virtual column
- NFA_MARK, // Match mark
- NFA_MARK_GT, // Match > mark
- NFA_MARK_LT, // Match < mark
- NFA_VISUAL, // Match Visual area
-
- // Character classes [:alnum:] etc
- NFA_CLASS_ALNUM,
- NFA_CLASS_ALPHA,
- NFA_CLASS_BLANK,
- NFA_CLASS_CNTRL,
- NFA_CLASS_DIGIT,
- NFA_CLASS_GRAPH,
- NFA_CLASS_LOWER,
- NFA_CLASS_PRINT,
- NFA_CLASS_PUNCT,
- NFA_CLASS_SPACE,
- NFA_CLASS_UPPER,
- NFA_CLASS_XDIGIT,
- NFA_CLASS_TAB,
- NFA_CLASS_RETURN,
- NFA_CLASS_BACKSPACE,
- NFA_CLASS_ESCAPE,
- NFA_CLASS_IDENT,
- NFA_CLASS_KEYWORD,
- NFA_CLASS_FNAME,
-};
-
-// Keep in sync with classchars.
-static int nfa_classcodes[] = {
- NFA_ANY, NFA_IDENT, NFA_SIDENT, NFA_KWORD, NFA_SKWORD,
- NFA_FNAME, NFA_SFNAME, NFA_PRINT, NFA_SPRINT,
- NFA_WHITE, NFA_NWHITE, NFA_DIGIT, NFA_NDIGIT,
- NFA_HEX, NFA_NHEX, NFA_OCTAL, NFA_NOCTAL,
- NFA_WORD, NFA_NWORD, NFA_HEAD, NFA_NHEAD,
- NFA_ALPHA, NFA_NALPHA, NFA_LOWER, NFA_NLOWER,
- NFA_UPPER, NFA_NUPPER
-};
-
-static char e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely");
-static char e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c");
-static char e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64);
-static char e_value_too_large[] = N_("E951: \\% value too large");
-
-// Since the out pointers in the list are always
-// uninitialized, we use the pointers themselves
-// as storage for the Ptrlists.
-typedef union Ptrlist Ptrlist;
-union Ptrlist {
- Ptrlist *next;
- nfa_state_T *s;
-};
-
-struct Frag {
- nfa_state_T *start;
- Ptrlist *out;
-};
-typedef struct Frag Frag_T;
-
-typedef struct {
- int in_use; ///< number of subexpr with useful info
-
- // When REG_MULTI is true list.multi is used, otherwise list.line.
- union {
- struct multipos {
- linenr_T start_lnum;
- linenr_T end_lnum;
- colnr_T start_col;
- colnr_T end_col;
- } multi[NSUBEXP];
- struct linepos {
- uint8_t *start;
- uint8_t *end;
- } line[NSUBEXP];
- } list;
- colnr_T orig_start_col; // list.multi[0].start_col without \zs
-} regsub_T;
-
-typedef struct {
- regsub_T norm; // \( .. \) matches
- regsub_T synt; // \z( .. \) matches
-} regsubs_T;
-
-// nfa_pim_T stores a Postponed Invisible Match.
-typedef struct nfa_pim_S nfa_pim_T;
-struct nfa_pim_S {
- int result; // NFA_PIM_*, see below
- nfa_state_T *state; // the invisible match start state
- regsubs_T subs; // submatch info, only party used
- union {
- lpos_T pos;
- uint8_t *ptr;
- } end; // where the match must end
-};
-
-// nfa_thread_T contains execution information of a NFA state
-typedef struct {
- nfa_state_T *state;
- int count;
- nfa_pim_T pim; // if pim.result != NFA_PIM_UNUSED: postponed
- // invisible match
- regsubs_T subs; // submatch info, only party used
-} nfa_thread_T;
-
-// nfa_list_T contains the alternative NFA execution states.
-typedef struct {
- nfa_thread_T *t; ///< allocated array of states
- int n; ///< nr of states currently in "t"
- int len; ///< max nr of states in "t"
- int id; ///< ID of the list
- int has_pim; ///< true when any state has a PIM
-} nfa_list_T;
-
-// Variables only used in nfa_regcomp() and descendants.
-static int nfa_re_flags; ///< re_flags passed to nfa_regcomp().
-static int *post_start; ///< holds the postfix form of r.e.
-static int *post_end;
-static int *post_ptr;
-
-// Set when the pattern should use the NFA engine.
-// E.g. [[:upper:]] only allows 8bit characters for BT engine,
-// while NFA engine handles multibyte characters correctly.
-static bool wants_nfa;
-
-static int nstate; ///< Number of states in the NFA. Also used when executing.
-static int istate; ///< Index in the state vector, used in alloc_state()
-
-// If not NULL match must end at this position
-static save_se_T *nfa_endp = NULL;
-
-// 0 for first call to nfa_regmatch(), 1 for recursive call.
-static int nfa_ll_index = 0;
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "regexp_nfa.c.generated.h"
-#endif
-
-// Helper functions used when doing re2post() ... regatom() parsing
-#define EMIT(c) \
- do { \
- if (post_ptr >= post_end) { \
- realloc_post_list(); \
- } \
- *post_ptr++ = c; \
- } while (0)
-
-/// Initialize internal variables before NFA compilation.
-///
-/// @param re_flags @see vim_regcomp()
-static void nfa_regcomp_start(uint8_t *expr, int re_flags)
-{
- size_t postfix_size;
- size_t nstate_max;
-
- nstate = 0;
- istate = 0;
- // A reasonable estimation for maximum size
- nstate_max = (strlen((char *)expr) + 1) * 25;
-
- // Some items blow up in size, such as [A-z]. Add more space for that.
- // When it is still not enough realloc_post_list() will be used.
- nstate_max += 1000;
-
- // Size for postfix representation of expr.
- postfix_size = sizeof(int) * nstate_max;
-
- post_start = (int *)xmalloc(postfix_size);
- post_ptr = post_start;
- post_end = post_start + nstate_max;
- wants_nfa = false;
- rex.nfa_has_zend = false;
- rex.nfa_has_backref = false;
-
- // shared with BT engine
- regcomp_start(expr, re_flags);
-}
-
-// Figure out if the NFA state list starts with an anchor, must match at start
-// of the line.
-static int nfa_get_reganch(nfa_state_T *start, int depth)
-{
- nfa_state_T *p = start;
-
- if (depth > 4) {
- return 0;
- }
-
- while (p != NULL) {
- switch (p->c) {
- case NFA_BOL:
- case NFA_BOF:
- return 1; // yes!
-
- case NFA_ZSTART:
- case NFA_ZEND:
- case NFA_CURSOR:
- case NFA_VISUAL:
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_NOPEN:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- p = p->out;
- break;
-
- case NFA_SPLIT:
- return nfa_get_reganch(p->out, depth + 1)
- && nfa_get_reganch(p->out1, depth + 1);
-
- default:
- return 0; // noooo
- }
- }
- return 0;
-}
-
-// Figure out if the NFA state list starts with a character which must match
-// at start of the match.
-static int nfa_get_regstart(nfa_state_T *start, int depth)
-{
- nfa_state_T *p = start;
-
- if (depth > 4) {
- return 0;
- }
-
- while (p != NULL) {
- switch (p->c) {
- // all kinds of zero-width matches
- case NFA_BOL:
- case NFA_BOF:
- case NFA_BOW:
- case NFA_EOW:
- case NFA_ZSTART:
- case NFA_ZEND:
- case NFA_CURSOR:
- case NFA_VISUAL:
- case NFA_LNUM:
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_COL:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_VCOL:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_MARK:
- case NFA_MARK_GT:
- case NFA_MARK_LT:
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_NOPEN:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- p = p->out;
- break;
-
- case NFA_SPLIT: {
- int c1 = nfa_get_regstart(p->out, depth + 1);
- int c2 = nfa_get_regstart(p->out1, depth + 1);
-
- if (c1 == c2) {
- return c1; // yes!
- }
- return 0;
- }
-
- default:
- if (p->c > 0) {
- return p->c; // yes!
- }
- return 0;
- }
- }
- return 0;
-}
-
-// Figure out if the NFA state list contains just literal text and nothing
-// else. If so return a string in allocated memory with what must match after
-// regstart. Otherwise return NULL.
-static uint8_t *nfa_get_match_text(nfa_state_T *start)
-{
- nfa_state_T *p = start;
- int len = 0;
- uint8_t *ret;
- uint8_t *s;
-
- if (p->c != NFA_MOPEN) {
- return NULL; // just in case
- }
- p = p->out;
- while (p->c > 0) {
- len += utf_char2len(p->c);
- p = p->out;
- }
- if (p->c != NFA_MCLOSE || p->out->c != NFA_MATCH) {
- return NULL;
- }
-
- ret = xmalloc((size_t)len);
- p = start->out->out; // skip first char, it goes into regstart
- s = ret;
- while (p->c > 0) {
- s += utf_char2bytes(p->c, (char *)s);
- p = p->out;
- }
- *s = NUL;
-
- return ret;
-}
-
-// Allocate more space for post_start. Called when
-// running above the estimated number of states.
-static void realloc_post_list(void)
-{
- // For weird patterns the number of states can be very high. Increasing by
- // 50% seems a reasonable compromise between memory use and speed.
- const size_t new_max = (size_t)(post_end - post_start) * 3 / 2;
- int *new_start = xrealloc(post_start, new_max * sizeof(int));
- post_ptr = new_start + (post_ptr - post_start);
- post_end = new_start + new_max;
- post_start = new_start;
-}
-
-// Search between "start" and "end" and try to recognize a
-// character class in expanded form. For example [0-9].
-// On success, return the id the character class to be emitted.
-// On failure, return 0 (=FAIL)
-// Start points to the first char of the range, while end should point
-// to the closing brace.
-// Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may
-// need to be interpreted as [a-zA-Z].
-static int nfa_recognize_char_class(uint8_t *start, uint8_t *end, int extra_newl)
-{
-#define CLASS_not 0x80
-#define CLASS_af 0x40
-#define CLASS_AF 0x20
-#define CLASS_az 0x10
-#define CLASS_AZ 0x08
-#define CLASS_o7 0x04
-#define CLASS_o9 0x02
-#define CLASS_underscore 0x01
-
- uint8_t *p;
- int config = 0;
-
- bool newl = extra_newl == true;
-
- if (*end != ']') {
- return FAIL;
- }
- p = start;
- if (*p == '^') {
- config |= CLASS_not;
- p++;
- }
-
- while (p < end) {
- if (p + 2 < end && *(p + 1) == '-') {
- switch (*p) {
- case '0':
- if (*(p + 2) == '9') {
- config |= CLASS_o9;
- break;
- } else if (*(p + 2) == '7') {
- config |= CLASS_o7;
- break;
- }
- return FAIL;
- case 'a':
- if (*(p + 2) == 'z') {
- config |= CLASS_az;
- break;
- } else if (*(p + 2) == 'f') {
- config |= CLASS_af;
- break;
- }
- return FAIL;
- case 'A':
- if (*(p + 2) == 'Z') {
- config |= CLASS_AZ;
- break;
- } else if (*(p + 2) == 'F') {
- config |= CLASS_AF;
- break;
- }
- return FAIL;
- default:
- return FAIL;
- }
- p += 3;
- } else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') {
- newl = true;
- p += 2;
- } else if (*p == '_') {
- config |= CLASS_underscore;
- p++;
- } else if (*p == '\n') {
- newl = true;
- p++;
- } else {
- return FAIL;
- }
- } // while (p < end)
-
- if (p != end) {
- return FAIL;
- }
-
- if (newl == true) {
- extra_newl = NFA_ADD_NL;
- }
-
- switch (config) {
- case CLASS_o9:
- return extra_newl + NFA_DIGIT;
- case CLASS_not | CLASS_o9:
- return extra_newl + NFA_NDIGIT;
- case CLASS_af | CLASS_AF | CLASS_o9:
- return extra_newl + NFA_HEX;
- case CLASS_not | CLASS_af | CLASS_AF | CLASS_o9:
- return extra_newl + NFA_NHEX;
- case CLASS_o7:
- return extra_newl + NFA_OCTAL;
- case CLASS_not | CLASS_o7:
- return extra_newl + NFA_NOCTAL;
- case CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
- return extra_newl + NFA_WORD;
- case CLASS_not | CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
- return extra_newl + NFA_NWORD;
- case CLASS_az | CLASS_AZ | CLASS_underscore:
- return extra_newl + NFA_HEAD;
- case CLASS_not | CLASS_az | CLASS_AZ | CLASS_underscore:
- return extra_newl + NFA_NHEAD;
- case CLASS_az | CLASS_AZ:
- return extra_newl + NFA_ALPHA;
- case CLASS_not | CLASS_az | CLASS_AZ:
- return extra_newl + NFA_NALPHA;
- case CLASS_az:
- return extra_newl + NFA_LOWER_IC;
- case CLASS_not | CLASS_az:
- return extra_newl + NFA_NLOWER_IC;
- case CLASS_AZ:
- return extra_newl + NFA_UPPER_IC;
- case CLASS_not | CLASS_AZ:
- return extra_newl + NFA_NUPPER_IC;
- }
- return FAIL;
-}
-
-// Produce the bytes for equivalence class "c".
-// Currently only handles latin1, latin9 and utf-8.
-// Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is
-// equivalent to 'a OR b OR c'
-//
-// NOTE! When changing this function, also update reg_equi_class()
-static void nfa_emit_equi_class(int c)
-{
-#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT);
-
- {
-#define A_grave 0xc0
-#define A_acute 0xc1
-#define A_circumflex 0xc2
-#define A_virguilla 0xc3
-#define A_diaeresis 0xc4
-#define A_ring 0xc5
-#define C_cedilla 0xc7
-#define E_grave 0xc8
-#define E_acute 0xc9
-#define E_circumflex 0xca
-#define E_diaeresis 0xcb
-#define I_grave 0xcc
-#define I_acute 0xcd
-#define I_circumflex 0xce
-#define I_diaeresis 0xcf
-#define N_virguilla 0xd1
-#define O_grave 0xd2
-#define O_acute 0xd3
-#define O_circumflex 0xd4
-#define O_virguilla 0xd5
-#define O_diaeresis 0xd6
-#define O_slash 0xd8
-#define U_grave 0xd9
-#define U_acute 0xda
-#define U_circumflex 0xdb
-#define U_diaeresis 0xdc
-#define Y_acute 0xdd
-#define a_grave 0xe0
-#define a_acute 0xe1
-#define a_circumflex 0xe2
-#define a_virguilla 0xe3
-#define a_diaeresis 0xe4
-#define a_ring 0xe5
-#define c_cedilla 0xe7
-#define e_grave 0xe8
-#define e_acute 0xe9
-#define e_circumflex 0xea
-#define e_diaeresis 0xeb
-#define i_grave 0xec
-#define i_acute 0xed
-#define i_circumflex 0xee
-#define i_diaeresis 0xef
-#define n_virguilla 0xf1
-#define o_grave 0xf2
-#define o_acute 0xf3
-#define o_circumflex 0xf4
-#define o_virguilla 0xf5
-#define o_diaeresis 0xf6
-#define o_slash 0xf8
-#define u_grave 0xf9
-#define u_acute 0xfa
-#define u_circumflex 0xfb
-#define u_diaeresis 0xfc
-#define y_acute 0xfd
-#define y_diaeresis 0xff
- switch (c) {
- case 'A':
- case A_grave:
- case A_acute:
- case A_circumflex:
- case A_virguilla:
- case A_diaeresis:
- case A_ring:
- case 0x100:
- case 0x102:
- case 0x104:
- case 0x1cd:
- case 0x1de:
- case 0x1e0:
- case 0x1fa:
- case 0x200:
- case 0x202:
- case 0x226:
- case 0x23a:
- case 0x1e00:
- case 0x1ea0:
- case 0x1ea2:
- case 0x1ea4:
- case 0x1ea6:
- case 0x1ea8:
- case 0x1eaa:
- case 0x1eac:
- case 0x1eae:
- case 0x1eb0:
- case 0x1eb2:
- case 0x1eb4:
- case 0x1eb6:
- EMIT2('A') EMIT2(A_grave) EMIT2(A_acute) // NOLINT(whitespace/cast)
- EMIT2(A_circumflex) EMIT2(A_virguilla) // NOLINT(whitespace/cast)
- EMIT2(A_diaeresis) EMIT2(A_ring) // NOLINT(whitespace/cast)
- EMIT2(0x100) EMIT2(0x102) EMIT2(0x104)
- EMIT2(0x1cd) EMIT2(0x1de) EMIT2(0x1e0)
- EMIT2(0x1fa) EMIT2(0x200) EMIT2(0x202)
- EMIT2(0x226) EMIT2(0x23a) EMIT2(0x1e00)
- EMIT2(0x1ea0) EMIT2(0x1ea2) EMIT2(0x1ea4)
- EMIT2(0x1ea6) EMIT2(0x1ea8) EMIT2(0x1eaa)
- EMIT2(0x1eac) EMIT2(0x1eae) EMIT2(0x1eb0)
- EMIT2(0x1eb2) EMIT2(0x1eb6) EMIT2(0x1eb4)
- return;
-
- case 'B':
- case 0x181:
- case 0x243:
- case 0x1e02:
- case 0x1e04:
- case 0x1e06:
- EMIT2('B')
- EMIT2(0x181) EMIT2(0x243) EMIT2(0x1e02)
- EMIT2(0x1e04) EMIT2(0x1e06)
- return;
-
- case 'C':
- case C_cedilla:
- case 0x106:
- case 0x108:
- case 0x10a:
- case 0x10c:
- case 0x187:
- case 0x23b:
- case 0x1e08:
- case 0xa792:
- EMIT2('C') EMIT2(C_cedilla)
- EMIT2(0x106) EMIT2(0x108) EMIT2(0x10a)
- EMIT2(0x10c) EMIT2(0x187) EMIT2(0x23b)
- EMIT2(0x1e08) EMIT2(0xa792)
- return;
-
- case 'D':
- case 0x10e:
- case 0x110:
- case 0x18a:
- case 0x1e0a:
- case 0x1e0c:
- case 0x1e0e:
- case 0x1e10:
- case 0x1e12:
- EMIT2('D') EMIT2(0x10e) EMIT2(0x110) EMIT2(0x18a)
- EMIT2(0x1e0a) EMIT2(0x1e0c) EMIT2(0x1e0e)
- EMIT2(0x1e10) EMIT2(0x1e12)
- return;
-
- case 'E':
- case E_grave:
- case E_acute:
- case E_circumflex:
- case E_diaeresis:
- case 0x112:
- case 0x114:
- case 0x116:
- case 0x118:
- case 0x11a:
- case 0x204:
- case 0x206:
- case 0x228:
- case 0x246:
- case 0x1e14:
- case 0x1e16:
- case 0x1e18:
- case 0x1e1a:
- case 0x1e1c:
- case 0x1eb8:
- case 0x1eba:
- case 0x1ebc:
- case 0x1ebe:
- case 0x1ec0:
- case 0x1ec2:
- case 0x1ec4:
- case 0x1ec6:
- EMIT2('E') EMIT2(E_grave) EMIT2(E_acute) // NOLINT(whitespace/cast)
- EMIT2(E_circumflex) EMIT2(E_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x112) EMIT2(0x114) EMIT2(0x116)
- EMIT2(0x118) EMIT2(0x11a) EMIT2(0x204)
- EMIT2(0x206) EMIT2(0x228) EMIT2(0x246)
- EMIT2(0x1e14) EMIT2(0x1e16) EMIT2(0x1e18)
- EMIT2(0x1e1a) EMIT2(0x1e1c) EMIT2(0x1eb8)
- EMIT2(0x1eba) EMIT2(0x1ebc) EMIT2(0x1ebe)
- EMIT2(0x1ec0) EMIT2(0x1ec2) EMIT2(0x1ec4)
- EMIT2(0x1ec6)
- return;
-
- case 'F':
- case 0x191:
- case 0x1e1e:
- case 0xa798:
- EMIT2('F') EMIT2(0x191) EMIT2(0x1e1e) EMIT2(0xa798)
- return;
-
- case 'G':
- case 0x11c:
- case 0x11e:
- case 0x120:
- case 0x122:
- case 0x193:
- case 0x1e4:
- case 0x1e6:
- case 0x1f4:
- case 0x1e20:
- case 0xa7a0:
- EMIT2('G') EMIT2(0x11c) EMIT2(0x11e) EMIT2(0x120)
- EMIT2(0x122) EMIT2(0x193) EMIT2(0x1e4)
- EMIT2(0x1e6) EMIT2(0x1f4) EMIT2(0x1e20)
- EMIT2(0xa7a0)
- return;
-
- case 'H':
- case 0x124:
- case 0x126:
- case 0x21e:
- case 0x1e22:
- case 0x1e24:
- case 0x1e26:
- case 0x1e28:
- case 0x1e2a:
- case 0x2c67:
- EMIT2('H') EMIT2(0x124) EMIT2(0x126) EMIT2(0x21e)
- EMIT2(0x1e22) EMIT2(0x1e24) EMIT2(0x1e26)
- EMIT2(0x1e28) EMIT2(0x1e2a) EMIT2(0x2c67)
- return;
-
- case 'I':
- case I_grave:
- case I_acute:
- case I_circumflex:
- case I_diaeresis:
- case 0x128:
- case 0x12a:
- case 0x12c:
- case 0x12e:
- case 0x130:
- case 0x197:
- case 0x1cf:
- case 0x208:
- case 0x20a:
- case 0x1e2c:
- case 0x1e2e:
- case 0x1ec8:
- case 0x1eca:
- EMIT2('I') EMIT2(I_grave) EMIT2(I_acute) // NOLINT(whitespace/cast)
- EMIT2(I_circumflex) EMIT2(I_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x128) EMIT2(0x12a) EMIT2(0x12c)
- EMIT2(0x12e) EMIT2(0x130) EMIT2(0x197)
- EMIT2(0x1cf) EMIT2(0x208) EMIT2(0x20a)
- EMIT2(0x1e2c) EMIT2(0x1e2e) EMIT2(0x1ec8)
- EMIT2(0x1eca)
- return;
-
- case 'J':
- case 0x134:
- case 0x248:
- EMIT2('J') EMIT2(0x134) EMIT2(0x248)
- return;
-
- case 'K':
- case 0x136:
- case 0x198:
- case 0x1e8:
- case 0x1e30:
- case 0x1e32:
- case 0x1e34:
- case 0x2c69:
- case 0xa740:
- EMIT2('K') EMIT2(0x136) EMIT2(0x198) EMIT2(0x1e8)
- EMIT2(0x1e30) EMIT2(0x1e32) EMIT2(0x1e34)
- EMIT2(0x2c69) EMIT2(0xa740)
- return;
-
- case 'L':
- case 0x139:
- case 0x13b:
- case 0x13d:
- case 0x13f:
- case 0x141:
- case 0x23d:
- case 0x1e36:
- case 0x1e38:
- case 0x1e3a:
- case 0x1e3c:
- case 0x2c60:
- EMIT2('L') EMIT2(0x139) EMIT2(0x13b)
- EMIT2(0x13d) EMIT2(0x13f) EMIT2(0x141)
- EMIT2(0x23d) EMIT2(0x1e36) EMIT2(0x1e38)
- EMIT2(0x1e3a) EMIT2(0x1e3c) EMIT2(0x2c60)
- return;
-
- case 'M':
- case 0x1e3e:
- case 0x1e40:
- case 0x1e42:
- EMIT2('M') EMIT2(0x1e3e) EMIT2(0x1e40)
- EMIT2(0x1e42)
- return;
-
- case 'N':
- case N_virguilla:
- case 0x143:
- case 0x145:
- case 0x147:
- case 0x1f8:
- case 0x1e44:
- case 0x1e46:
- case 0x1e48:
- case 0x1e4a:
- case 0xa7a4:
- EMIT2('N') EMIT2(N_virguilla)
- EMIT2(0x143) EMIT2(0x145) EMIT2(0x147)
- EMIT2(0x1f8) EMIT2(0x1e44) EMIT2(0x1e46)
- EMIT2(0x1e48) EMIT2(0x1e4a) EMIT2(0xa7a4)
- return;
-
- case 'O':
- case O_grave:
- case O_acute:
- case O_circumflex:
- case O_virguilla:
- case O_diaeresis:
- case O_slash:
- case 0x14c:
- case 0x14e:
- case 0x150:
- case 0x19f:
- case 0x1a0:
- case 0x1d1:
- case 0x1ea:
- case 0x1ec:
- case 0x1fe:
- case 0x20c:
- case 0x20e:
- case 0x22a:
- case 0x22c:
- case 0x22e:
- case 0x230:
- case 0x1e4c:
- case 0x1e4e:
- case 0x1e50:
- case 0x1e52:
- case 0x1ecc:
- case 0x1ece:
- case 0x1ed0:
- case 0x1ed2:
- case 0x1ed4:
- case 0x1ed6:
- case 0x1ed8:
- case 0x1eda:
- case 0x1edc:
- case 0x1ede:
- case 0x1ee0:
- case 0x1ee2:
- EMIT2('O') EMIT2(O_grave) EMIT2(O_acute) // NOLINT(whitespace/cast)
- EMIT2(O_circumflex) EMIT2(O_virguilla) // NOLINT(whitespace/cast)
- EMIT2(O_diaeresis) EMIT2(O_slash) // NOLINT(whitespace/cast)
- EMIT2(0x14c) EMIT2(0x14e) EMIT2(0x150)
- EMIT2(0x19f) EMIT2(0x1a0) EMIT2(0x1d1)
- EMIT2(0x1ea) EMIT2(0x1ec) EMIT2(0x1fe)
- EMIT2(0x20c) EMIT2(0x20e) EMIT2(0x22a)
- EMIT2(0x22c) EMIT2(0x22e) EMIT2(0x230)
- EMIT2(0x1e4c) EMIT2(0x1e4e) EMIT2(0x1e50)
- EMIT2(0x1e52) EMIT2(0x1ecc) EMIT2(0x1ece)
- EMIT2(0x1ed0) EMIT2(0x1ed2) EMIT2(0x1ed4)
- EMIT2(0x1ed6) EMIT2(0x1ed8) EMIT2(0x1eda)
- EMIT2(0x1edc) EMIT2(0x1ede) EMIT2(0x1ee0)
- EMIT2(0x1ee2)
- return;
-
- case 'P':
- case 0x1a4:
- case 0x1e54:
- case 0x1e56:
- case 0x2c63:
- EMIT2('P') EMIT2(0x1a4) EMIT2(0x1e54) EMIT2(0x1e56)
- EMIT2(0x2c63)
- return;
-
- case 'Q':
- case 0x24a:
- EMIT2('Q') EMIT2(0x24a)
- return;
-
- case 'R':
- case 0x154:
- case 0x156:
- case 0x158:
- case 0x210:
- case 0x212:
- case 0x24c:
- case 0x1e58:
- case 0x1e5a:
- case 0x1e5c:
- case 0x1e5e:
- case 0x2c64:
- case 0xa7a6:
- EMIT2('R') EMIT2(0x154) EMIT2(0x156) EMIT2(0x158)
- EMIT2(0x210) EMIT2(0x212) EMIT2(0x24c) EMIT2(0x1e58)
- EMIT2(0x1e5a) EMIT2(0x1e5c) EMIT2(0x1e5e) EMIT2(0x2c64)
- EMIT2(0xa7a6)
- return;
-
- case 'S':
- case 0x15a:
- case 0x15c:
- case 0x15e:
- case 0x160:
- case 0x218:
- case 0x1e60:
- case 0x1e62:
- case 0x1e64:
- case 0x1e66:
- case 0x1e68:
- case 0x2c7e:
- case 0xa7a8:
- EMIT2('S') EMIT2(0x15a) EMIT2(0x15c) EMIT2(0x15e)
- EMIT2(0x160) EMIT2(0x218) EMIT2(0x1e60) EMIT2(0x1e62)
- EMIT2(0x1e64) EMIT2(0x1e66) EMIT2(0x1e68) EMIT2(0x2c7e)
- EMIT2(0xa7a8)
- return;
-
- case 'T':
- case 0x162:
- case 0x164:
- case 0x166:
- case 0x1ac:
- case 0x1ae:
- case 0x21a:
- case 0x23e:
- case 0x1e6a:
- case 0x1e6c:
- case 0x1e6e:
- case 0x1e70:
- EMIT2('T') EMIT2(0x162) EMIT2(0x164) EMIT2(0x166)
- EMIT2(0x1ac) EMIT2(0x1ae) EMIT2(0x23e) EMIT2(0x21a)
- EMIT2(0x1e6a) EMIT2(0x1e6c) EMIT2(0x1e6e) EMIT2(0x1e70)
- return;
-
- case 'U':
- case U_grave:
- case U_acute:
- case U_diaeresis:
- case U_circumflex:
- case 0x168:
- case 0x16a:
- case 0x16c:
- case 0x16e:
- case 0x170:
- case 0x172:
- case 0x1af:
- case 0x1d3:
- case 0x1d5:
- case 0x1d7:
- case 0x1d9:
- case 0x1db:
- case 0x214:
- case 0x216:
- case 0x244:
- case 0x1e72:
- case 0x1e74:
- case 0x1e76:
- case 0x1e78:
- case 0x1e7a:
- case 0x1ee4:
- case 0x1ee6:
- case 0x1ee8:
- case 0x1eea:
- case 0x1eec:
- case 0x1eee:
- case 0x1ef0:
- EMIT2('U') EMIT2(U_grave) EMIT2(U_acute) // NOLINT(whitespace/cast)
- EMIT2(U_diaeresis) EMIT2(U_circumflex) // NOLINT(whitespace/cast)
- EMIT2(0x168) EMIT2(0x16a)
- EMIT2(0x16c) EMIT2(0x16e) EMIT2(0x170)
- EMIT2(0x172) EMIT2(0x1af) EMIT2(0x1d3)
- EMIT2(0x1d5) EMIT2(0x1d7) EMIT2(0x1d9)
- EMIT2(0x1db) EMIT2(0x214) EMIT2(0x216)
- EMIT2(0x244) EMIT2(0x1e72) EMIT2(0x1e74)
- EMIT2(0x1e76) EMIT2(0x1e78) EMIT2(0x1e7a)
- EMIT2(0x1ee4) EMIT2(0x1ee6) EMIT2(0x1ee8)
- EMIT2(0x1eea) EMIT2(0x1eec) EMIT2(0x1eee)
- EMIT2(0x1ef0)
- return;
-
- case 'V':
- case 0x1b2:
- case 0x1e7c:
- case 0x1e7e:
- EMIT2('V') EMIT2(0x1b2) EMIT2(0x1e7c) EMIT2(0x1e7e)
- return;
-
- case 'W':
- case 0x174:
- case 0x1e80:
- case 0x1e82:
- case 0x1e84:
- case 0x1e86:
- case 0x1e88:
- EMIT2('W') EMIT2(0x174) EMIT2(0x1e80) EMIT2(0x1e82)
- EMIT2(0x1e84) EMIT2(0x1e86) EMIT2(0x1e88)
- return;
-
- case 'X':
- case 0x1e8a:
- case 0x1e8c:
- EMIT2('X') EMIT2(0x1e8a) EMIT2(0x1e8c)
- return;
-
- case 'Y':
- case Y_acute:
- case 0x176:
- case 0x178:
- case 0x1b3:
- case 0x232:
- case 0x24e:
- case 0x1e8e:
- case 0x1ef2:
- case 0x1ef4:
- case 0x1ef6:
- case 0x1ef8:
- EMIT2('Y') EMIT2(Y_acute)
- EMIT2(0x176) EMIT2(0x178) EMIT2(0x1b3)
- EMIT2(0x232) EMIT2(0x24e) EMIT2(0x1e8e)
- EMIT2(0x1ef2) EMIT2(0x1ef4) EMIT2(0x1ef6)
- EMIT2(0x1ef8)
- return;
-
- case 'Z':
- case 0x179:
- case 0x17b:
- case 0x17d:
- case 0x1b5:
- case 0x1e90:
- case 0x1e92:
- case 0x1e94:
- case 0x2c6b:
- EMIT2('Z') EMIT2(0x179) EMIT2(0x17b) EMIT2(0x17d)
- EMIT2(0x1b5) EMIT2(0x1e90) EMIT2(0x1e92)
- EMIT2(0x1e94) EMIT2(0x2c6b)
- return;
-
- case 'a':
- case a_grave:
- case a_acute:
- case a_circumflex:
- case a_virguilla:
- case a_diaeresis:
- case a_ring:
- case 0x101:
- case 0x103:
- case 0x105:
- case 0x1ce:
- case 0x1df:
- case 0x1e1:
- case 0x1fb:
- case 0x201:
- case 0x203:
- case 0x227:
- case 0x1d8f:
- case 0x1e01:
- case 0x1e9a:
- case 0x1ea1:
- case 0x1ea3:
- case 0x1ea5:
- case 0x1ea7:
- case 0x1ea9:
- case 0x1eab:
- case 0x1ead:
- case 0x1eaf:
- case 0x1eb1:
- case 0x1eb3:
- case 0x1eb5:
- case 0x1eb7:
- case 0x2c65:
- EMIT2('a') EMIT2(a_grave) EMIT2(a_acute) // NOLINT(whitespace/cast)
- EMIT2(a_circumflex) EMIT2(a_virguilla) // NOLINT(whitespace/cast)
- EMIT2(a_diaeresis) EMIT2(a_ring) // NOLINT(whitespace/cast)
- EMIT2(0x101) EMIT2(0x103) EMIT2(0x105)
- EMIT2(0x1ce) EMIT2(0x1df) EMIT2(0x1e1)
- EMIT2(0x1fb) EMIT2(0x201) EMIT2(0x203)
- EMIT2(0x227) EMIT2(0x1d8f) EMIT2(0x1e01)
- EMIT2(0x1e9a) EMIT2(0x1ea1) EMIT2(0x1ea3)
- EMIT2(0x1ea5) EMIT2(0x1ea7) EMIT2(0x1ea9)
- EMIT2(0x1eab) EMIT2(0x1ead) EMIT2(0x1eaf)
- EMIT2(0x1eb1) EMIT2(0x1eb3) EMIT2(0x1eb5)
- EMIT2(0x1eb7) EMIT2(0x2c65)
- return;
-
- case 'b':
- case 0x180:
- case 0x253:
- case 0x1d6c:
- case 0x1d80:
- case 0x1e03:
- case 0x1e05:
- case 0x1e07:
- EMIT2('b') EMIT2(0x180) EMIT2(0x253) EMIT2(0x1d6c)
- EMIT2(0x1d80) EMIT2(0x1e03) EMIT2(0x1e05) EMIT2(0x1e07)
- return;
-
- case 'c':
- case c_cedilla:
- case 0x107:
- case 0x109:
- case 0x10b:
- case 0x10d:
- case 0x188:
- case 0x23c:
- case 0x1e09:
- case 0xa793:
- case 0xa794:
- EMIT2('c') EMIT2(c_cedilla)
- EMIT2(0x107) EMIT2(0x109) EMIT2(0x10b)
- EMIT2(0x10d) EMIT2(0x188) EMIT2(0x23c)
- EMIT2(0x1e09) EMIT2(0xa793) EMIT2(0xa794)
- return;
-
- case 'd':
- case 0x10f:
- case 0x111:
- case 0x257:
- case 0x1d6d:
- case 0x1d81:
- case 0x1d91:
- case 0x1e0b:
- case 0x1e0d:
- case 0x1e0f:
- case 0x1e11:
- case 0x1e13:
- EMIT2('d') EMIT2(0x10f) EMIT2(0x111)
- EMIT2(0x257) EMIT2(0x1d6d) EMIT2(0x1d81)
- EMIT2(0x1d91) EMIT2(0x1e0b) EMIT2(0x1e0d)
- EMIT2(0x1e0f) EMIT2(0x1e11) EMIT2(0x1e13)
- return;
-
- case 'e':
- case e_grave:
- case e_acute:
- case e_circumflex:
- case e_diaeresis:
- case 0x113:
- case 0x115:
- case 0x117:
- case 0x119:
- case 0x11b:
- case 0x205:
- case 0x207:
- case 0x229:
- case 0x247:
- case 0x1d92:
- case 0x1e15:
- case 0x1e17:
- case 0x1e19:
- case 0x1e1b:
- case 0x1e1d:
- case 0x1eb9:
- case 0x1ebb:
- case 0x1ebd:
- case 0x1ebf:
- case 0x1ec1:
- case 0x1ec3:
- case 0x1ec5:
- case 0x1ec7:
- EMIT2('e') EMIT2(e_grave) EMIT2(e_acute) // NOLINT(whitespace/cast)
- EMIT2(e_circumflex) EMIT2(e_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x113) EMIT2(0x115)
- EMIT2(0x117) EMIT2(0x119) EMIT2(0x11b)
- EMIT2(0x205) EMIT2(0x207) EMIT2(0x229)
- EMIT2(0x247) EMIT2(0x1d92) EMIT2(0x1e15)
- EMIT2(0x1e17) EMIT2(0x1e19) EMIT2(0x1e1b)
- EMIT2(0x1e1d) EMIT2(0x1eb9) EMIT2(0x1ebb)
- EMIT2(0x1ebd) EMIT2(0x1ebf) EMIT2(0x1ec1)
- EMIT2(0x1ec3) EMIT2(0x1ec5) EMIT2(0x1ec7)
- return;
-
- case 'f':
- case 0x192:
- case 0x1d6e:
- case 0x1d82:
- case 0x1e1f:
- case 0xa799:
- EMIT2('f') EMIT2(0x192) EMIT2(0x1d6e) EMIT2(0x1d82)
- EMIT2(0x1e1f) EMIT2(0xa799)
- return;
-
- case 'g':
- case 0x11d:
- case 0x11f:
- case 0x121:
- case 0x123:
- case 0x1e5:
- case 0x1e7:
- case 0x1f5:
- case 0x260:
- case 0x1d83:
- case 0x1e21:
- case 0xa7a1:
- EMIT2('g') EMIT2(0x11d) EMIT2(0x11f) EMIT2(0x121)
- EMIT2(0x123) EMIT2(0x1e5) EMIT2(0x1e7)
- EMIT2(0x1f5) EMIT2(0x260) EMIT2(0x1d83)
- EMIT2(0x1e21) EMIT2(0xa7a1)
- return;
-
- case 'h':
- case 0x125:
- case 0x127:
- case 0x21f:
- case 0x1e23:
- case 0x1e25:
- case 0x1e27:
- case 0x1e29:
- case 0x1e2b:
- case 0x1e96:
- case 0x2c68:
- case 0xa795:
- EMIT2('h') EMIT2(0x125) EMIT2(0x127) EMIT2(0x21f)
- EMIT2(0x1e23) EMIT2(0x1e25) EMIT2(0x1e27)
- EMIT2(0x1e29) EMIT2(0x1e2b) EMIT2(0x1e96)
- EMIT2(0x2c68) EMIT2(0xa795)
- return;
-
- case 'i':
- case i_grave:
- case i_acute:
- case i_circumflex:
- case i_diaeresis:
- case 0x129:
- case 0x12b:
- case 0x12d:
- case 0x12f:
- case 0x1d0:
- case 0x209:
- case 0x20b:
- case 0x268:
- case 0x1d96:
- case 0x1e2d:
- case 0x1e2f:
- case 0x1ec9:
- case 0x1ecb:
- EMIT2('i') EMIT2(i_grave) EMIT2(i_acute) // NOLINT(whitespace/cast)
- EMIT2(i_circumflex) EMIT2(i_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x129) EMIT2(0x12b) EMIT2(0x12d)
- EMIT2(0x12f) EMIT2(0x1d0) EMIT2(0x209)
- EMIT2(0x20b) EMIT2(0x268) EMIT2(0x1d96)
- EMIT2(0x1e2d) EMIT2(0x1e2f) EMIT2(0x1ec9)
- EMIT2(0x1ecb) EMIT2(0x1ecb)
- return;
-
- case 'j':
- case 0x135:
- case 0x1f0:
- case 0x249:
- EMIT2('j') EMIT2(0x135) EMIT2(0x1f0) EMIT2(0x249)
- return;
-
- case 'k':
- case 0x137:
- case 0x199:
- case 0x1e9:
- case 0x1d84:
- case 0x1e31:
- case 0x1e33:
- case 0x1e35:
- case 0x2c6a:
- case 0xa741:
- EMIT2('k') EMIT2(0x137) EMIT2(0x199) EMIT2(0x1e9)
- EMIT2(0x1d84) EMIT2(0x1e31) EMIT2(0x1e33)
- EMIT2(0x1e35) EMIT2(0x2c6a) EMIT2(0xa741)
- return;
-
- case 'l':
- case 0x13a:
- case 0x13c:
- case 0x13e:
- case 0x140:
- case 0x142:
- case 0x19a:
- case 0x1e37:
- case 0x1e39:
- case 0x1e3b:
- case 0x1e3d:
- case 0x2c61:
- EMIT2('l') EMIT2(0x13a) EMIT2(0x13c)
- EMIT2(0x13e) EMIT2(0x140) EMIT2(0x142)
- EMIT2(0x19a) EMIT2(0x1e37) EMIT2(0x1e39)
- EMIT2(0x1e3b) EMIT2(0x1e3d) EMIT2(0x2c61)
- return;
-
- case 'm':
- case 0x1d6f:
- case 0x1e3f:
- case 0x1e41:
- case 0x1e43:
- EMIT2('m') EMIT2(0x1d6f) EMIT2(0x1e3f)
- EMIT2(0x1e41) EMIT2(0x1e43)
- return;
-
- case 'n':
- case n_virguilla:
- case 0x144:
- case 0x146:
- case 0x148:
- case 0x149:
- case 0x1f9:
- case 0x1d70:
- case 0x1d87:
- case 0x1e45:
- case 0x1e47:
- case 0x1e49:
- case 0x1e4b:
- case 0xa7a5:
- EMIT2('n') EMIT2(n_virguilla)
- EMIT2(0x144) EMIT2(0x146) EMIT2(0x148)
- EMIT2(0x149) EMIT2(0x1f9) EMIT2(0x1d70)
- EMIT2(0x1d87) EMIT2(0x1e45) EMIT2(0x1e47)
- EMIT2(0x1e49) EMIT2(0x1e4b) EMIT2(0xa7a5)
- return;
-
- case 'o':
- case o_grave:
- case o_acute:
- case o_circumflex:
- case o_virguilla:
- case o_diaeresis:
- case o_slash:
- case 0x14d:
- case 0x14f:
- case 0x151:
- case 0x1a1:
- case 0x1d2:
- case 0x1eb:
- case 0x1ed:
- case 0x1ff:
- case 0x20d:
- case 0x20f:
- case 0x22b:
- case 0x22d:
- case 0x22f:
- case 0x231:
- case 0x275:
- case 0x1e4d:
- case 0x1e4f:
- case 0x1e51:
- case 0x1e53:
- case 0x1ecd:
- case 0x1ecf:
- case 0x1ed1:
- case 0x1ed3:
- case 0x1ed5:
- case 0x1ed7:
- case 0x1ed9:
- case 0x1edb:
- case 0x1edd:
- case 0x1edf:
- case 0x1ee1:
- case 0x1ee3:
- EMIT2('o') EMIT2(o_grave) EMIT2(o_acute) // NOLINT(whitespace/cast)
- EMIT2(o_circumflex) EMIT2(o_virguilla) // NOLINT(whitespace/cast)
- EMIT2(o_diaeresis) EMIT2(o_slash) // NOLINT(whitespace/cast)
- EMIT2(0x14d) EMIT2(0x14f) EMIT2(0x151)
- EMIT2(0x1a1) EMIT2(0x1d2) EMIT2(0x1eb)
- EMIT2(0x1ed) EMIT2(0x1ff) EMIT2(0x20d)
- EMIT2(0x20f) EMIT2(0x22b) EMIT2(0x22d)
- EMIT2(0x22f) EMIT2(0x231) EMIT2(0x275)
- EMIT2(0x1e4d) EMIT2(0x1e4f) EMIT2(0x1e51)
- EMIT2(0x1e53) EMIT2(0x1ecd) EMIT2(0x1ecf)
- EMIT2(0x1ed1) EMIT2(0x1ed3) EMIT2(0x1ed5)
- EMIT2(0x1ed7) EMIT2(0x1ed9) EMIT2(0x1edb)
- EMIT2(0x1edd) EMIT2(0x1edf) EMIT2(0x1ee1)
- EMIT2(0x1ee3)
- return;
-
- case 'p':
- case 0x1a5:
- case 0x1d71:
- case 0x1d7d:
- case 0x1d88:
- case 0x1e55:
- case 0x1e57:
- EMIT2('p') EMIT2(0x1a5) EMIT2(0x1d71) EMIT2(0x1d7d)
- EMIT2(0x1d88) EMIT2(0x1e55) EMIT2(0x1e57)
- return;
-
- case 'q':
- case 0x24b:
- case 0x2a0:
- EMIT2('q') EMIT2(0x24b) EMIT2(0x2a0)
- return;
-
- case 'r':
- case 0x155:
- case 0x157:
- case 0x159:
- case 0x211:
- case 0x213:
- case 0x24d:
- case 0x27d:
- case 0x1d72:
- case 0x1d73:
- case 0x1d89:
- case 0x1e59:
- case 0x1e5b:
- case 0x1e5d:
- case 0x1e5f:
- case 0xa7a7:
- EMIT2('r') EMIT2(0x155) EMIT2(0x157) EMIT2(0x159)
- EMIT2(0x211) EMIT2(0x213) EMIT2(0x24d) EMIT2(0x27d)
- EMIT2(0x1d72) EMIT2(0x1d73) EMIT2(0x1d89) EMIT2(0x1e59)
- EMIT2(0x1e5b) EMIT2(0x1e5d) EMIT2(0x1e5f) EMIT2(0xa7a7)
- return;
-
- case 's':
- case 0x15b:
- case 0x15d:
- case 0x15f:
- case 0x161:
- case 0x219:
- case 0x23f:
- case 0x1d74:
- case 0x1d8a:
- case 0x1e61:
- case 0x1e63:
- case 0x1e65:
- case 0x1e67:
- case 0x1e69:
- case 0xa7a9:
- EMIT2('s') EMIT2(0x15b) EMIT2(0x15d) EMIT2(0x15f)
- EMIT2(0x161) EMIT2(0x219) EMIT2(0x23f) EMIT2(0x1d74)
- EMIT2(0x1d8a) EMIT2(0x1e61) EMIT2(0x1e63) EMIT2(0x1e65)
- EMIT2(0x1e67) EMIT2(0x1e69) EMIT2(0xa7a9)
- return;
-
- case 't':
- case 0x163:
- case 0x165:
- case 0x167:
- case 0x1ab:
- case 0x1ad:
- case 0x21b:
- case 0x288:
- case 0x1d75:
- case 0x1e6b:
- case 0x1e6d:
- case 0x1e6f:
- case 0x1e71:
- case 0x1e97:
- case 0x2c66:
- EMIT2('t') EMIT2(0x163) EMIT2(0x165) EMIT2(0x167)
- EMIT2(0x1ab) EMIT2(0x1ad) EMIT2(0x21b) EMIT2(0x288)
- EMIT2(0x1d75) EMIT2(0x1e6b) EMIT2(0x1e6d) EMIT2(0x1e6f)
- EMIT2(0x1e71) EMIT2(0x1e97) EMIT2(0x2c66)
- return;
-
- case 'u':
- case u_grave:
- case u_acute:
- case u_circumflex:
- case u_diaeresis:
- case 0x169:
- case 0x16b:
- case 0x16d:
- case 0x16f:
- case 0x171:
- case 0x173:
- case 0x1b0:
- case 0x1d4:
- case 0x1d6:
- case 0x1d8:
- case 0x1da:
- case 0x1dc:
- case 0x215:
- case 0x217:
- case 0x289:
- case 0x1d7e:
- case 0x1d99:
- case 0x1e73:
- case 0x1e75:
- case 0x1e77:
- case 0x1e79:
- case 0x1e7b:
- case 0x1ee5:
- case 0x1ee7:
- case 0x1ee9:
- case 0x1eeb:
- case 0x1eed:
- case 0x1eef:
- case 0x1ef1:
- EMIT2('u') EMIT2(u_grave) EMIT2(u_acute) // NOLINT(whitespace/cast)
- EMIT2(u_circumflex) EMIT2(u_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x169) EMIT2(0x16b)
- EMIT2(0x16d) EMIT2(0x16f) EMIT2(0x171)
- EMIT2(0x173) EMIT2(0x1d6) EMIT2(0x1d8)
- EMIT2(0x215) EMIT2(0x217) EMIT2(0x1b0)
- EMIT2(0x1d4) EMIT2(0x1da) EMIT2(0x1dc)
- EMIT2(0x289) EMIT2(0x1e73) EMIT2(0x1d7e)
- EMIT2(0x1d99) EMIT2(0x1e75) EMIT2(0x1e77)
- EMIT2(0x1e79) EMIT2(0x1e7b) EMIT2(0x1ee5)
- EMIT2(0x1ee7) EMIT2(0x1ee9) EMIT2(0x1eeb)
- EMIT2(0x1eed) EMIT2(0x1eef) EMIT2(0x1ef1)
- return;
-
- case 'v':
- case 0x28b:
- case 0x1d8c:
- case 0x1e7d:
- case 0x1e7f:
- EMIT2('v') EMIT2(0x28b) EMIT2(0x1d8c) EMIT2(0x1e7d)
- EMIT2(0x1e7f)
- return;
-
- case 'w':
- case 0x175:
- case 0x1e81:
- case 0x1e83:
- case 0x1e85:
- case 0x1e87:
- case 0x1e89:
- case 0x1e98:
- EMIT2('w') EMIT2(0x175) EMIT2(0x1e81) EMIT2(0x1e83)
- EMIT2(0x1e85) EMIT2(0x1e87) EMIT2(0x1e89) EMIT2(0x1e98)
- return;
-
- case 'x':
- case 0x1e8b:
- case 0x1e8d:
- EMIT2('x') EMIT2(0x1e8b) EMIT2(0x1e8d)
- return;
-
- case 'y':
- case y_acute:
- case y_diaeresis:
- case 0x177:
- case 0x1b4:
- case 0x233:
- case 0x24f:
- case 0x1e8f:
- case 0x1e99:
- case 0x1ef3:
- case 0x1ef5:
- case 0x1ef7:
- case 0x1ef9:
- EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f)
- EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3)
- EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9)
- return;
-
- case 'z':
- case 0x17a:
- case 0x17c:
- case 0x17e:
- case 0x1b6:
- case 0x1d76:
- case 0x1d8e:
- case 0x1e91:
- case 0x1e93:
- case 0x1e95:
- case 0x2c6c:
- EMIT2('z') EMIT2(0x17a) EMIT2(0x17c) EMIT2(0x17e)
- EMIT2(0x1b6) EMIT2(0x1d76) EMIT2(0x1d8e) EMIT2(0x1e91)
- EMIT2(0x1e93) EMIT2(0x1e95) EMIT2(0x2c6c)
- return;
-
- // default: character itself
- }
- }
-
- EMIT2(c);
-#undef EMIT2
-}
-
-// Code to parse regular expression.
-//
-// We try to reuse parsing functions in regexp.c to
-// minimize surprise and keep the syntax consistent.
-
-// Parse the lowest level.
-//
-// An atom can be one of a long list of items. Many atoms match one character
-// in the text. It is often an ordinary character or a character class.
-// Braces can be used to make a pattern into an atom. The "\z(\)" construct
-// is only for syntax highlighting.
-//
-// atom ::= ordinary-atom
-// or \( pattern \)
-// or \%( pattern \)
-// or \z( pattern \)
-static int nfa_regatom(void)
-{
- int c;
- int charclass;
- int equiclass;
- int collclass;
- int got_coll_char;
- uint8_t *p;
- uint8_t *endp;
- uint8_t *old_regparse = (uint8_t *)regparse;
- int extra = 0;
- int emit_range;
- int negated;
- int startc = -1;
- int save_prev_at_start = prev_at_start;
-
- c = getchr();
- switch (c) {
- case NUL:
- EMSG_RET_FAIL(_(e_nul_found));
-
- case Magic('^'):
- EMIT(NFA_BOL);
- break;
-
- case Magic('$'):
- EMIT(NFA_EOL);
- had_eol = true;
- break;
-
- case Magic('<'):
- EMIT(NFA_BOW);
- break;
-
- case Magic('>'):
- EMIT(NFA_EOW);
- break;
-
- case Magic('_'):
- c = no_Magic(getchr());
- if (c == NUL) {
- EMSG_RET_FAIL(_(e_nul_found));
- }
-
- if (c == '^') { // "\_^" is start-of-line
- EMIT(NFA_BOL);
- break;
- }
- if (c == '$') { // "\_$" is end-of-line
- EMIT(NFA_EOL);
- had_eol = true;
- break;
- }
-
- extra = NFA_ADD_NL;
-
- // "\_[" is collection plus newline
- if (c == '[') {
- goto collection;
- }
-
- // "\_x" is character class plus newline
- FALLTHROUGH;
-
- // Character classes.
- case Magic('.'):
- case Magic('i'):
- case Magic('I'):
- case Magic('k'):
- case Magic('K'):
- case Magic('f'):
- case Magic('F'):
- case Magic('p'):
- case Magic('P'):
- case Magic('s'):
- case Magic('S'):
- case Magic('d'):
- case Magic('D'):
- case Magic('x'):
- case Magic('X'):
- case Magic('o'):
- case Magic('O'):
- case Magic('w'):
- case Magic('W'):
- case Magic('h'):
- case Magic('H'):
- case Magic('a'):
- case Magic('A'):
- case Magic('l'):
- case Magic('L'):
- case Magic('u'):
- case Magic('U'):
- p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
- if (p == NULL) {
- if (extra == NFA_ADD_NL) {
- semsg(_(e_ill_char_class), (int64_t)c);
- rc_did_emsg = true;
- return FAIL;
- }
- siemsg("INTERNAL: Unknown character class char: %" PRId64, (int64_t)c);
- return FAIL;
- }
- // When '.' is followed by a composing char ignore the dot, so that
- // the composing char is matched here.
- if (c == Magic('.') && utf_iscomposing(peekchr())) {
- old_regparse = (uint8_t *)regparse;
- c = getchr();
- goto nfa_do_multibyte;
- }
- EMIT(nfa_classcodes[p - classchars]);
- if (extra == NFA_ADD_NL) {
- EMIT(NFA_NEWL);
- EMIT(NFA_OR);
- regflags |= RF_HASNL;
- }
- break;
-
- case Magic('n'):
- if (reg_string) {
- // In a string "\n" matches a newline character.
- EMIT(NL);
- } else {
- // In buffer text "\n" matches the end of a line.
- EMIT(NFA_NEWL);
- regflags |= RF_HASNL;
- }
- break;
-
- case Magic('('):
- if (nfa_reg(REG_PAREN) == FAIL) {
- return FAIL; // cascaded error
- }
- break;
-
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- semsg(_(e_misplaced), (int64_t)no_Magic(c)); // -V1037
- return FAIL;
-
- case Magic('='):
- case Magic('?'):
- case Magic('+'):
- case Magic('@'):
- case Magic('*'):
- case Magic('{'):
- // these should follow an atom, not form an atom
- semsg(_(e_misplaced), (int64_t)no_Magic(c));
- return FAIL;
-
- case Magic('~'): {
- uint8_t *lp;
-
- // Previous substitute pattern.
- // Generated as "\%(pattern\)".
- if (reg_prev_sub == NULL) {
- emsg(_(e_nopresub));
- return FAIL;
- }
- for (lp = (uint8_t *)reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) {
- EMIT(utf_ptr2char((char *)lp));
- if (lp != (uint8_t *)reg_prev_sub) {
- EMIT(NFA_CONCAT);
- }
- }
- EMIT(NFA_NOPEN);
- break;
- }
-
- case Magic('1'):
- case Magic('2'):
- case Magic('3'):
- case Magic('4'):
- case Magic('5'):
- case Magic('6'):
- case Magic('7'):
- case Magic('8'):
- case Magic('9'): {
- int refnum = no_Magic(c) - '1';
-
- if (!seen_endbrace(refnum + 1)) {
- return FAIL;
- }
- EMIT(NFA_BACKREF1 + refnum);
- rex.nfa_has_backref = true;
- }
- break;
-
- case Magic('z'):
- c = no_Magic(getchr());
- switch (c) {
- case 's':
- EMIT(NFA_ZSTART);
- if (!re_mult_next("\\zs")) {
- return false;
- }
- break;
- case 'e':
- EMIT(NFA_ZEND);
- rex.nfa_has_zend = true;
- if (!re_mult_next("\\zs")) {
- return false;
- }
- break;
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- // \z1...\z9
- if ((reg_do_extmatch & REX_USE) == 0) {
- EMSG_RET_FAIL(_(e_z1_not_allowed));
- }
- EMIT(NFA_ZREF1 + (no_Magic(c) - '1'));
- // No need to set rex.nfa_has_backref, the sub-matches don't
- // change when \z1 .. \z9 matches or not.
- re_has_z = REX_USE;
- break;
- case '(':
- // \z(
- if (reg_do_extmatch != REX_SET) {
- EMSG_RET_FAIL(_(e_z_not_allowed));
- }
- if (nfa_reg(REG_ZPAREN) == FAIL) {
- return FAIL; // cascaded error
- }
- re_has_z = REX_SET;
- break;
- default:
- semsg(_("E867: (NFA) Unknown operator '\\z%c'"),
- no_Magic(c));
- return FAIL;
- }
- break;
-
- case Magic('%'):
- c = no_Magic(getchr());
- switch (c) {
- // () without a back reference
- case '(':
- if (nfa_reg(REG_NPAREN) == FAIL) {
- return FAIL;
- }
- EMIT(NFA_NOPEN);
- break;
-
- case 'd': // %d123 decimal
- case 'o': // %o123 octal
- case 'x': // %xab hex 2
- case 'u': // %uabcd hex 4
- case 'U': // %U1234abcd hex 8
- {
- int64_t nr;
-
- switch (c) {
- case 'd':
- nr = getdecchrs(); break;
- case 'o':
- nr = getoctchrs(); break;
- case 'x':
- nr = gethexchrs(2); break;
- case 'u':
- nr = gethexchrs(4); break;
- case 'U':
- nr = gethexchrs(8); break;
- default:
- nr = -1; break;
- }
-
- if (nr < 0 || nr > INT_MAX) {
- EMSG2_RET_FAIL(_("E678: Invalid character after %s%%[dxouU]"),
- reg_magic == MAGIC_ALL);
- }
- // A NUL is stored in the text as NL
- // TODO(vim): what if a composing character follows?
- EMIT(nr == 0 ? 0x0a : (int)nr);
- }
- break;
-
- // Catch \%^ and \%$ regardless of where they appear in the
- // pattern -- regardless of whether or not it makes sense.
- case '^':
- EMIT(NFA_BOF);
- break;
-
- case '$':
- EMIT(NFA_EOF);
- break;
-
- case '#':
- if (regparse[0] == '=' && regparse[1] >= 48
- && regparse[1] <= 50) {
- // misplaced \%#=1
- semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
- return FAIL;
- }
- EMIT(NFA_CURSOR);
- break;
-
- case 'V':
- EMIT(NFA_VISUAL);
- break;
-
- case 'C':
- EMIT(NFA_ANY_COMPOSING);
- break;
-
- case '[': {
- int n;
-
- // \%[abc]
- for (n = 0; (c = peekchr()) != ']'; n++) {
- if (c == NUL) {
- EMSG2_RET_FAIL(_(e_missing_sb),
- reg_magic == MAGIC_ALL);
- }
- // recursive call!
- if (nfa_regatom() == FAIL) {
- return FAIL;
- }
- }
- (void)getchr(); // get the ]
- if (n == 0) {
- EMSG2_RET_FAIL(_(e_empty_sb), reg_magic == MAGIC_ALL);
- }
- EMIT(NFA_OPT_CHARS);
- EMIT(n);
-
- // Emit as "\%(\%[abc]\)" to be able to handle
- // "\%[abc]*" which would cause the empty string to be
- // matched an unlimited number of times. NFA_NOPEN is
- // added only once at a position, while NFA_SPLIT is
- // added multiple times. This is more efficient than
- // not allowing NFA_SPLIT multiple times, it is used
- // a lot.
- EMIT(NFA_NOPEN);
- break;
- }
-
- default: {
- int64_t n = 0;
- const int cmp = c;
- bool cur = false;
- bool got_digit = false;
-
- if (c == '<' || c == '>') {
- c = getchr();
- }
- if (no_Magic(c) == '.') {
- cur = true;
- c = getchr();
- }
- while (ascii_isdigit(c)) {
- if (cur) {
- semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
- return FAIL;
- }
- if (n > (INT32_MAX - (c - '0')) / 10) {
- // overflow.
- emsg(_(e_value_too_large));
- return FAIL;
- }
- n = n * 10 + (c - '0');
- c = getchr();
- got_digit = true;
- }
- if (c == 'l' || c == 'c' || c == 'v') {
- int32_t limit = INT32_MAX;
-
- if (!cur && !got_digit) {
- semsg(_(e_nfa_regexp_missing_value_in_chr), no_Magic(c));
- return FAIL;
- }
- if (c == 'l') {
- if (cur) {
- n = curwin->w_cursor.lnum;
- }
- // \%{n}l \%{n}<l \%{n}>l
- EMIT(cmp == '<' ? NFA_LNUM_LT :
- cmp == '>' ? NFA_LNUM_GT : NFA_LNUM);
- if (save_prev_at_start) {
- at_start = true;
- }
- } else if (c == 'c') {
- if (cur) {
- n = curwin->w_cursor.col;
- n++;
- }
- // \%{n}c \%{n}<c \%{n}>c
- EMIT(cmp == '<' ? NFA_COL_LT :
- cmp == '>' ? NFA_COL_GT : NFA_COL);
- } else {
- if (cur) {
- colnr_T vcol = 0;
- getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
- n = ++vcol;
- }
- // \%{n}v \%{n}<v \%{n}>v
- EMIT(cmp == '<' ? NFA_VCOL_LT :
- cmp == '>' ? NFA_VCOL_GT : NFA_VCOL);
- limit = INT32_MAX / MB_MAXBYTES;
- }
- if (n >= limit) {
- emsg(_(e_value_too_large));
- return FAIL;
- }
- EMIT((int)n);
- break;
- } else if (c == '\'' && n == 0) {
- // \%'m \%<'m \%>'m
- EMIT(cmp == '<' ? NFA_MARK_LT :
- cmp == '>' ? NFA_MARK_GT : NFA_MARK);
- EMIT(getchr());
- break;
- }
- }
- semsg(_("E867: (NFA) Unknown operator '\\%%%c'"),
- no_Magic(c));
- return FAIL;
- }
- break;
-
- case Magic('['):
-collection:
- // [abc] uses NFA_START_COLL - NFA_END_COLL
- // [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL
- // Each character is produced as a regular state, using
- // NFA_CONCAT to bind them together.
- // Besides normal characters there can be:
- // - character classes NFA_CLASS_*
- // - ranges, two characters followed by NFA_RANGE.
-
- p = (uint8_t *)regparse;
- endp = (uint8_t *)skip_anyof((char *)p);
- if (*endp == ']') {
- // Try to reverse engineer character classes. For example,
- // recognize that [0-9] stands for \d and [A-Za-z_] for \h,
- // and perform the necessary substitutions in the NFA.
- int result = nfa_recognize_char_class((uint8_t *)regparse, endp, extra == NFA_ADD_NL);
- if (result != FAIL) {
- if (result >= NFA_FIRST_NL && result <= NFA_LAST_NL) {
- EMIT(result - NFA_ADD_NL);
- EMIT(NFA_NEWL);
- EMIT(NFA_OR);
- } else {
- EMIT(result);
- }
- regparse = (char *)endp;
- MB_PTR_ADV(regparse);
- return OK;
- }
- // Failed to recognize a character class. Use the simple
- // version that turns [abc] into 'a' OR 'b' OR 'c'
- startc = -1;
- negated = false;
- if (*regparse == '^') { // negated range
- negated = true;
- MB_PTR_ADV(regparse);
- EMIT(NFA_START_NEG_COLL);
- } else {
- EMIT(NFA_START_COLL);
- }
- if (*regparse == '-') {
- startc = '-';
- EMIT(startc);
- EMIT(NFA_CONCAT);
- MB_PTR_ADV(regparse);
- }
- // Emit the OR branches for each character in the []
- emit_range = false;
- while ((uint8_t *)regparse < endp) {
- int oldstartc = startc;
- startc = -1;
- got_coll_char = false;
- if (*regparse == '[') {
- // Check for [: :], [= =], [. .]
- equiclass = collclass = 0;
- charclass = get_char_class(&regparse);
- if (charclass == CLASS_NONE) {
- equiclass = get_equi_class(&regparse);
- if (equiclass == 0) {
- collclass = get_coll_element(&regparse);
- }
- }
-
- // Character class like [:alpha:]
- if (charclass != CLASS_NONE) {
- switch (charclass) {
- case CLASS_ALNUM:
- EMIT(NFA_CLASS_ALNUM);
- break;
- case CLASS_ALPHA:
- EMIT(NFA_CLASS_ALPHA);
- break;
- case CLASS_BLANK:
- EMIT(NFA_CLASS_BLANK);
- break;
- case CLASS_CNTRL:
- EMIT(NFA_CLASS_CNTRL);
- break;
- case CLASS_DIGIT:
- EMIT(NFA_CLASS_DIGIT);
- break;
- case CLASS_GRAPH:
- EMIT(NFA_CLASS_GRAPH);
- break;
- case CLASS_LOWER:
- wants_nfa = true;
- EMIT(NFA_CLASS_LOWER);
- break;
- case CLASS_PRINT:
- EMIT(NFA_CLASS_PRINT);
- break;
- case CLASS_PUNCT:
- EMIT(NFA_CLASS_PUNCT);
- break;
- case CLASS_SPACE:
- EMIT(NFA_CLASS_SPACE);
- break;
- case CLASS_UPPER:
- wants_nfa = true;
- EMIT(NFA_CLASS_UPPER);
- break;
- case CLASS_XDIGIT:
- EMIT(NFA_CLASS_XDIGIT);
- break;
- case CLASS_TAB:
- EMIT(NFA_CLASS_TAB);
- break;
- case CLASS_RETURN:
- EMIT(NFA_CLASS_RETURN);
- break;
- case CLASS_BACKSPACE:
- EMIT(NFA_CLASS_BACKSPACE);
- break;
- case CLASS_ESCAPE:
- EMIT(NFA_CLASS_ESCAPE);
- break;
- case CLASS_IDENT:
- EMIT(NFA_CLASS_IDENT);
- break;
- case CLASS_KEYWORD:
- EMIT(NFA_CLASS_KEYWORD);
- break;
- case CLASS_FNAME:
- EMIT(NFA_CLASS_FNAME);
- break;
- }
- EMIT(NFA_CONCAT);
- continue;
- }
- // Try equivalence class [=a=] and the like
- if (equiclass != 0) {
- nfa_emit_equi_class(equiclass);
- continue;
- }
- // Try collating class like [. .]
- if (collclass != 0) {
- startc = collclass; // allow [.a.]-x as a range
- // Will emit the proper atom at the end of the
- // while loop.
- }
- }
- // Try a range like 'a-x' or '\t-z'. Also allows '-' as a
- // start character.
- if (*regparse == '-' && oldstartc != -1) {
- emit_range = true;
- startc = oldstartc;
- MB_PTR_ADV(regparse);
- continue; // reading the end of the range
- }
-
- // Now handle simple and escaped characters.
- // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
- // accepts "\t", "\e", etc., but only when the 'l' flag in
- // 'cpoptions' is not included.
- if (*regparse == '\\'
- && (uint8_t *)regparse + 1 <= endp
- && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
- || (!reg_cpo_lit
- && vim_strchr(REGEXP_ABBR, (uint8_t)regparse[1])
- != NULL))) {
- MB_PTR_ADV(regparse);
-
- if (*regparse == 'n') {
- startc = (reg_string || emit_range || regparse[1] == '-')
- ? NL : NFA_NEWL;
- } else if (*regparse == 'd'
- || *regparse == 'o'
- || *regparse == 'x'
- || *regparse == 'u'
- || *regparse == 'U') {
- // TODO(RE): This needs more testing
- startc = coll_get_char();
- got_coll_char = true;
- MB_PTR_BACK(old_regparse, regparse);
- } else {
- // \r,\t,\e,\b
- startc = backslash_trans(*regparse);
- }
- }
-
- // Normal printable char
- if (startc == -1) {
- startc = utf_ptr2char((char *)regparse);
- }
-
- // Previous char was '-', so this char is end of range.
- if (emit_range) {
- int endc = startc;
- startc = oldstartc;
- if (startc > endc) {
- EMSG_RET_FAIL(_(e_reverse_range));
- }
-
- if (endc > startc + 2) {
- // Emit a range instead of the sequence of
- // individual characters.
- if (startc == 0) {
- // \x00 is translated to \x0a, start at \x01.
- EMIT(1);
- } else {
- post_ptr--; // remove NFA_CONCAT
- }
- EMIT(endc);
- EMIT(NFA_RANGE);
- EMIT(NFA_CONCAT);
- } else if (utf_char2len(startc) > 1
- || utf_char2len(endc) > 1) {
- // Emit the characters in the range.
- // "startc" was already emitted, so skip it.
- for (c = startc + 1; c <= endc; c++) {
- EMIT(c);
- EMIT(NFA_CONCAT);
- }
- } else {
- // Emit the range. "startc" was already emitted, so
- // skip it.
- for (c = startc + 1; c <= endc; c++) {
- EMIT(c);
- EMIT(NFA_CONCAT);
- }
- }
- emit_range = false;
- startc = -1;
- } else {
- // This char (startc) is not part of a range. Just
- // emit it.
- // Normally, simply emit startc. But if we get char
- // code=0 from a collating char, then replace it with
- // 0x0a.
- // This is needed to completely mimic the behaviour of
- // the backtracking engine.
- if (startc == NFA_NEWL) {
- // Line break can't be matched as part of the
- // collection, add an OR below. But not for negated
- // range.
- if (!negated) {
- extra = NFA_ADD_NL;
- }
- } else {
- if (got_coll_char == true && startc == 0) {
- EMIT(0x0a);
- } else {
- EMIT(startc);
- }
- EMIT(NFA_CONCAT);
- }
- }
-
- MB_PTR_ADV(regparse);
- } // while (p < endp)
-
- MB_PTR_BACK(old_regparse, regparse);
- if (*regparse == '-') { // if last, '-' is just a char
- EMIT('-');
- EMIT(NFA_CONCAT);
- }
-
- // skip the trailing ]
- regparse = (char *)endp;
- MB_PTR_ADV(regparse);
-
- // Mark end of the collection.
- if (negated == true) {
- EMIT(NFA_END_NEG_COLL);
- } else {
- EMIT(NFA_END_COLL);
- }
-
- // \_[] also matches \n but it's not negated
- if (extra == NFA_ADD_NL) {
- EMIT(reg_string ? NL : NFA_NEWL);
- EMIT(NFA_OR);
- }
-
- return OK;
- } // if exists closing ]
-
- if (reg_strict) {
- EMSG_RET_FAIL(_(e_missingbracket));
- }
- FALLTHROUGH;
-
- default: {
- int plen;
-
-nfa_do_multibyte:
- // plen is length of current char with composing chars
- if (utf_char2len(c) != (plen = utfc_ptr2len((char *)old_regparse))
- || utf_iscomposing(c)) {
- int i = 0;
-
- // A base character plus composing characters, or just one
- // or more composing characters.
- // This requires creating a separate atom as if enclosing
- // the characters in (), where NFA_COMPOSING is the ( and
- // NFA_END_COMPOSING is the ). Note that right now we are
- // building the postfix form, not the NFA itself;
- // a composing char could be: a, b, c, NFA_COMPOSING
- // where 'b' and 'c' are chars with codes > 256. */
- for (;;) {
- EMIT(c);
- if (i > 0) {
- EMIT(NFA_CONCAT);
- }
- if ((i += utf_char2len(c)) >= plen) {
- break;
- }
- c = utf_ptr2char((char *)old_regparse + i);
- }
- EMIT(NFA_COMPOSING);
- regparse = (char *)old_regparse + plen;
- } else {
- c = no_Magic(c);
- EMIT(c);
- }
- return OK;
- }
- }
-
- return OK;
-}
-
-// Parse something followed by possible [*+=].
-//
-// A piece is an atom, possibly followed by a multi, an indication of how many
-// times the atom can be matched. Example: "a*" matches any sequence of "a"
-// characters: "", "a", "aa", etc.
-//
-// piece ::= atom
-// or atom multi
-static int nfa_regpiece(void)
-{
- int i;
- int op;
- int ret;
- long minval, maxval;
- bool greedy = true; // Braces are prefixed with '-' ?
- parse_state_T old_state;
- parse_state_T new_state;
- int64_t c2;
- int old_post_pos;
- int my_post_start;
- int quest;
-
- // Save the current parse state, so that we can use it if <atom>{m,n} is
- // next.
- save_parse_state(&old_state);
-
- // store current pos in the postfix form, for \{m,n} involving 0s
- my_post_start = (int)(post_ptr - post_start);
-
- ret = nfa_regatom();
- if (ret == FAIL) {
- return FAIL; // cascaded error
- }
- op = peekchr();
- if (re_multi_type(op) == NOT_MULTI) {
- return OK;
- }
-
- skipchr();
- switch (op) {
- case Magic('*'):
- EMIT(NFA_STAR);
- break;
-
- case Magic('+'):
- // Trick: Normally, (a*)\+ would match the whole input "aaa". The
- // first and only submatch would be "aaa". But the backtracking
- // engine interprets the plus as "try matching one more time", and
- // a* matches a second time at the end of the input, the empty
- // string.
- // The submatch will be the empty string.
- //
- // In order to be consistent with the old engine, we replace
- // <atom>+ with <atom><atom>*
- restore_parse_state(&old_state);
- curchr = -1;
- if (nfa_regatom() == FAIL) {
- return FAIL;
- }
- EMIT(NFA_STAR);
- EMIT(NFA_CONCAT);
- skipchr(); // skip the \+
- break;
-
- case Magic('@'):
- c2 = getdecchrs();
- op = no_Magic(getchr());
- i = 0;
- switch (op) {
- case '=':
- // \@=
- i = NFA_PREV_ATOM_NO_WIDTH;
- break;
- case '!':
- // \@!
- i = NFA_PREV_ATOM_NO_WIDTH_NEG;
- break;
- case '<':
- op = no_Magic(getchr());
- if (op == '=') {
- // \@<=
- i = NFA_PREV_ATOM_JUST_BEFORE;
- } else if (op == '!') {
- // \@<!
- i = NFA_PREV_ATOM_JUST_BEFORE_NEG;
- }
- break;
- case '>':
- // \@>
- i = NFA_PREV_ATOM_LIKE_PATTERN;
- break;
- }
- if (i == 0) {
- semsg(_("E869: (NFA) Unknown operator '\\@%c'"), op);
- return FAIL;
- }
- EMIT(i);
- if (i == NFA_PREV_ATOM_JUST_BEFORE
- || i == NFA_PREV_ATOM_JUST_BEFORE_NEG) {
- EMIT((int)c2);
- }
- break;
-
- case Magic('?'):
- case Magic('='):
- EMIT(NFA_QUEST);
- break;
-
- case Magic('{'):
- // a{2,5} will expand to 'aaa?a?a?'
- // a{-1,3} will expand to 'aa??a??', where ?? is the nongreedy
- // version of '?'
- // \v(ab){2,3} will expand to '(ab)(ab)(ab)?', where all the
- // parenthesis have the same id
-
- greedy = true;
- c2 = peekchr();
- if (c2 == '-' || c2 == Magic('-')) {
- skipchr();
- greedy = false;
- }
- if (!read_limits(&minval, &maxval)) {
- EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits"));
- }
-
- // <atom>{0,inf}, <atom>{0,} and <atom>{} are equivalent to
- // <atom>*
- if (minval == 0 && maxval == MAX_LIMIT) {
- if (greedy) {
- // \{}, \{0,}
- EMIT(NFA_STAR);
- } else {
- // \{-}, \{-0,}
- EMIT(NFA_STAR_NONGREEDY);
- }
- break;
- }
-
- // Special case: x{0} or x{-0}
- if (maxval == 0) {
- // Ignore result of previous call to nfa_regatom()
- post_ptr = post_start + my_post_start;
- // NFA_EMPTY is 0-length and works everywhere
- EMIT(NFA_EMPTY);
- return OK;
- }
-
- // The engine is very inefficient (uses too many states) when the
- // maximum is much larger than the minimum and when the maximum is
- // large. However, when maxval is MAX_LIMIT, it is okay, as this
- // will emit NFA_STAR.
- // Bail out if we can use the other engine, but only, when the
- // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
- // does not work with characters > 8 bit with the BT engine)
- if ((nfa_re_flags & RE_AUTO)
- && (maxval > 500 || maxval > minval + 200)
- && (maxval != MAX_LIMIT && minval < 200)
- && !wants_nfa) {
- return FAIL;
- }
-
- // Ignore previous call to nfa_regatom()
- post_ptr = post_start + my_post_start;
- // Save parse state after the repeated atom and the \{}
- save_parse_state(&new_state);
-
- quest = (greedy == true ? NFA_QUEST : NFA_QUEST_NONGREEDY);
- for (i = 0; i < maxval; i++) {
- // Goto beginning of the repeated atom
- restore_parse_state(&old_state);
- old_post_pos = (int)(post_ptr - post_start);
- if (nfa_regatom() == FAIL) {
- return FAIL;
- }
- // after "minval" times, atoms are optional
- if (i + 1 > minval) {
- if (maxval == MAX_LIMIT) {
- if (greedy) {
- EMIT(NFA_STAR);
- } else {
- EMIT(NFA_STAR_NONGREEDY);
- }
- } else {
- EMIT(quest);
- }
- }
- if (old_post_pos != my_post_start) {
- EMIT(NFA_CONCAT);
- }
- if (i + 1 > minval && maxval == MAX_LIMIT) {
- break;
- }
- }
-
- // Go to just after the repeated atom and the \{}
- restore_parse_state(&new_state);
- curchr = -1;
-
- break;
-
- default:
- break;
- } // end switch
-
- if (re_multi_type(peekchr()) != NOT_MULTI) {
- // Can't have a multi follow a multi.
- EMSG_RET_FAIL(_("E871: (NFA regexp) Can't have a multi follow a multi"));
- }
-
- return OK;
-}
-
-// Parse one or more pieces, concatenated. It matches a match for the
-// first piece, followed by a match for the second piece, etc. Example:
-// "f[0-9]b", first matches "f", then a digit and then "b".
-//
-// concat ::= piece
-// or piece piece
-// or piece piece piece
-// etc.
-static int nfa_regconcat(void)
-{
- bool cont = true;
- bool first = true;
-
- while (cont) {
- switch (peekchr()) {
- case NUL:
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- cont = false;
- break;
-
- case Magic('Z'):
- regflags |= RF_ICOMBINE;
- skipchr_keepstart();
- break;
- case Magic('c'):
- regflags |= RF_ICASE;
- skipchr_keepstart();
- break;
- case Magic('C'):
- regflags |= RF_NOICASE;
- skipchr_keepstart();
- break;
- case Magic('v'):
- reg_magic = MAGIC_ALL;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('m'):
- reg_magic = MAGIC_ON;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('M'):
- reg_magic = MAGIC_OFF;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('V'):
- reg_magic = MAGIC_NONE;
- skipchr_keepstart();
- curchr = -1;
- break;
-
- default:
- if (nfa_regpiece() == FAIL) {
- return FAIL;
- }
- if (first == false) {
- EMIT(NFA_CONCAT);
- } else {
- first = false;
- }
- break;
- }
- }
-
- return OK;
-}
-
-// Parse a branch, one or more concats, separated by "\&". It matches the
-// last concat, but only if all the preceding concats also match at the same
-// position. Examples:
-// "foobeep\&..." matches "foo" in "foobeep".
-// ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob"
-//
-// branch ::= concat
-// or concat \& concat
-// or concat \& concat \& concat
-// etc.
-static int nfa_regbranch(void)
-{
- int old_post_pos;
-
- old_post_pos = (int)(post_ptr - post_start);
-
- // First branch, possibly the only one
- if (nfa_regconcat() == FAIL) {
- return FAIL;
- }
-
- // Try next concats
- while (peekchr() == Magic('&')) {
- skipchr();
- // if concat is empty do emit a node
- if (old_post_pos == (int)(post_ptr - post_start)) {
- EMIT(NFA_EMPTY);
- }
- EMIT(NFA_NOPEN);
- EMIT(NFA_PREV_ATOM_NO_WIDTH);
- old_post_pos = (int)(post_ptr - post_start);
- if (nfa_regconcat() == FAIL) {
- return FAIL;
- }
- // if concat is empty do emit a node
- if (old_post_pos == (int)(post_ptr - post_start)) {
- EMIT(NFA_EMPTY);
- }
- EMIT(NFA_CONCAT);
- }
-
- // if a branch is empty, emit one node for it
- if (old_post_pos == (int)(post_ptr - post_start)) {
- EMIT(NFA_EMPTY);
- }
-
- return OK;
-}
-
-/// Parse a pattern, one or more branches, separated by "\|". It matches
-/// anything that matches one of the branches. Example: "foo\|beep" matches
-/// "foo" and matches "beep". If more than one branch matches, the first one
-/// is used.
-///
-/// pattern ::= branch
-/// or branch \| branch
-/// or branch \| branch \| branch
-/// etc.
-///
-/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
-static int nfa_reg(int paren)
-{
- int parno = 0;
-
- if (paren == REG_PAREN) {
- if (regnpar >= NSUBEXP) { // Too many `('
- EMSG_RET_FAIL(_("E872: (NFA regexp) Too many '('"));
- }
- parno = regnpar++;
- } else if (paren == REG_ZPAREN) {
- // Make a ZOPEN node.
- if (regnzpar >= NSUBEXP) {
- EMSG_RET_FAIL(_("E879: (NFA regexp) Too many \\z("));
- }
- parno = regnzpar++;
- }
-
- if (nfa_regbranch() == FAIL) {
- return FAIL; // cascaded error
- }
- while (peekchr() == Magic('|')) {
- skipchr();
- if (nfa_regbranch() == FAIL) {
- return FAIL; // cascaded error
- }
- EMIT(NFA_OR);
- }
-
- // Check for proper termination.
- if (paren != REG_NOPAREN && getchr() != Magic(')')) {
- if (paren == REG_NPAREN) {
- EMSG2_RET_FAIL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
- } else {
- EMSG2_RET_FAIL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
- }
- } else if (paren == REG_NOPAREN && peekchr() != NUL) {
- if (peekchr() == Magic(')')) {
- EMSG2_RET_FAIL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
- } else {
- EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error"));
- }
- }
- // Here we set the flag allowing back references to this set of
- // parentheses.
- if (paren == REG_PAREN) {
- had_endbrace[parno] = true; // have seen the close paren
- EMIT(NFA_MOPEN + parno);
- } else if (paren == REG_ZPAREN) {
- EMIT(NFA_ZOPEN + parno);
- }
-
- return OK;
-}
-
-#ifdef REGEXP_DEBUG
-static uint8_t code[50];
-
-static void nfa_set_code(int c)
-{
- int addnl = false;
-
- if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) {
- addnl = true;
- c -= NFA_ADD_NL;
- }
-
- STRCPY(code, "");
- switch (c) {
- case NFA_MATCH:
- STRCPY(code, "NFA_MATCH "); break;
- case NFA_SPLIT:
- STRCPY(code, "NFA_SPLIT "); break;
- case NFA_CONCAT:
- STRCPY(code, "NFA_CONCAT "); break;
- case NFA_NEWL:
- STRCPY(code, "NFA_NEWL "); break;
- case NFA_ZSTART:
- STRCPY(code, "NFA_ZSTART"); break;
- case NFA_ZEND:
- STRCPY(code, "NFA_ZEND"); break;
-
- case NFA_BACKREF1:
- STRCPY(code, "NFA_BACKREF1"); break;
- case NFA_BACKREF2:
- STRCPY(code, "NFA_BACKREF2"); break;
- case NFA_BACKREF3:
- STRCPY(code, "NFA_BACKREF3"); break;
- case NFA_BACKREF4:
- STRCPY(code, "NFA_BACKREF4"); break;
- case NFA_BACKREF5:
- STRCPY(code, "NFA_BACKREF5"); break;
- case NFA_BACKREF6:
- STRCPY(code, "NFA_BACKREF6"); break;
- case NFA_BACKREF7:
- STRCPY(code, "NFA_BACKREF7"); break;
- case NFA_BACKREF8:
- STRCPY(code, "NFA_BACKREF8"); break;
- case NFA_BACKREF9:
- STRCPY(code, "NFA_BACKREF9"); break;
- case NFA_ZREF1:
- STRCPY(code, "NFA_ZREF1"); break;
- case NFA_ZREF2:
- STRCPY(code, "NFA_ZREF2"); break;
- case NFA_ZREF3:
- STRCPY(code, "NFA_ZREF3"); break;
- case NFA_ZREF4:
- STRCPY(code, "NFA_ZREF4"); break;
- case NFA_ZREF5:
- STRCPY(code, "NFA_ZREF5"); break;
- case NFA_ZREF6:
- STRCPY(code, "NFA_ZREF6"); break;
- case NFA_ZREF7:
- STRCPY(code, "NFA_ZREF7"); break;
- case NFA_ZREF8:
- STRCPY(code, "NFA_ZREF8"); break;
- case NFA_ZREF9:
- STRCPY(code, "NFA_ZREF9"); break;
- case NFA_SKIP:
- STRCPY(code, "NFA_SKIP"); break;
-
- case NFA_PREV_ATOM_NO_WIDTH:
- STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH"); break;
- case NFA_PREV_ATOM_NO_WIDTH_NEG:
- STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH_NEG"); break;
- case NFA_PREV_ATOM_JUST_BEFORE:
- STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE"); break;
- case NFA_PREV_ATOM_JUST_BEFORE_NEG:
- STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE_NEG"); break;
- case NFA_PREV_ATOM_LIKE_PATTERN:
- STRCPY(code, "NFA_PREV_ATOM_LIKE_PATTERN"); break;
-
- case NFA_NOPEN:
- STRCPY(code, "NFA_NOPEN"); break;
- case NFA_NCLOSE:
- STRCPY(code, "NFA_NCLOSE"); break;
- case NFA_START_INVISIBLE:
- STRCPY(code, "NFA_START_INVISIBLE"); break;
- case NFA_START_INVISIBLE_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_FIRST"); break;
- case NFA_START_INVISIBLE_NEG:
- STRCPY(code, "NFA_START_INVISIBLE_NEG"); break;
- case NFA_START_INVISIBLE_NEG_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_NEG_FIRST"); break;
- case NFA_START_INVISIBLE_BEFORE:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE"); break;
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE_FIRST"); break;
- case NFA_START_INVISIBLE_BEFORE_NEG:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG"); break;
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG_FIRST"); break;
- case NFA_START_PATTERN:
- STRCPY(code, "NFA_START_PATTERN"); break;
- case NFA_END_INVISIBLE:
- STRCPY(code, "NFA_END_INVISIBLE"); break;
- case NFA_END_INVISIBLE_NEG:
- STRCPY(code, "NFA_END_INVISIBLE_NEG"); break;
- case NFA_END_PATTERN:
- STRCPY(code, "NFA_END_PATTERN"); break;
-
- case NFA_COMPOSING:
- STRCPY(code, "NFA_COMPOSING"); break;
- case NFA_END_COMPOSING:
- STRCPY(code, "NFA_END_COMPOSING"); break;
- case NFA_OPT_CHARS:
- STRCPY(code, "NFA_OPT_CHARS"); break;
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- STRCPY(code, "NFA_MOPEN(x)");
- code[10] = c - NFA_MOPEN + '0';
- break;
- case NFA_MCLOSE:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- STRCPY(code, "NFA_MCLOSE(x)");
- code[11] = c - NFA_MCLOSE + '0';
- break;
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- STRCPY(code, "NFA_ZOPEN(x)");
- code[10] = c - NFA_ZOPEN + '0';
- break;
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- STRCPY(code, "NFA_ZCLOSE(x)");
- code[11] = c - NFA_ZCLOSE + '0';
- break;
- case NFA_EOL:
- STRCPY(code, "NFA_EOL "); break;
- case NFA_BOL:
- STRCPY(code, "NFA_BOL "); break;
- case NFA_EOW:
- STRCPY(code, "NFA_EOW "); break;
- case NFA_BOW:
- STRCPY(code, "NFA_BOW "); break;
- case NFA_EOF:
- STRCPY(code, "NFA_EOF "); break;
- case NFA_BOF:
- STRCPY(code, "NFA_BOF "); break;
- case NFA_LNUM:
- STRCPY(code, "NFA_LNUM "); break;
- case NFA_LNUM_GT:
- STRCPY(code, "NFA_LNUM_GT "); break;
- case NFA_LNUM_LT:
- STRCPY(code, "NFA_LNUM_LT "); break;
- case NFA_COL:
- STRCPY(code, "NFA_COL "); break;
- case NFA_COL_GT:
- STRCPY(code, "NFA_COL_GT "); break;
- case NFA_COL_LT:
- STRCPY(code, "NFA_COL_LT "); break;
- case NFA_VCOL:
- STRCPY(code, "NFA_VCOL "); break;
- case NFA_VCOL_GT:
- STRCPY(code, "NFA_VCOL_GT "); break;
- case NFA_VCOL_LT:
- STRCPY(code, "NFA_VCOL_LT "); break;
- case NFA_MARK:
- STRCPY(code, "NFA_MARK "); break;
- case NFA_MARK_GT:
- STRCPY(code, "NFA_MARK_GT "); break;
- case NFA_MARK_LT:
- STRCPY(code, "NFA_MARK_LT "); break;
- case NFA_CURSOR:
- STRCPY(code, "NFA_CURSOR "); break;
- case NFA_VISUAL:
- STRCPY(code, "NFA_VISUAL "); break;
- case NFA_ANY_COMPOSING:
- STRCPY(code, "NFA_ANY_COMPOSING "); break;
-
- case NFA_STAR:
- STRCPY(code, "NFA_STAR "); break;
- case NFA_STAR_NONGREEDY:
- STRCPY(code, "NFA_STAR_NONGREEDY "); break;
- case NFA_QUEST:
- STRCPY(code, "NFA_QUEST"); break;
- case NFA_QUEST_NONGREEDY:
- STRCPY(code, "NFA_QUEST_NON_GREEDY"); break;
- case NFA_EMPTY:
- STRCPY(code, "NFA_EMPTY"); break;
- case NFA_OR:
- STRCPY(code, "NFA_OR"); break;
-
- case NFA_START_COLL:
- STRCPY(code, "NFA_START_COLL"); break;
- case NFA_END_COLL:
- STRCPY(code, "NFA_END_COLL"); break;
- case NFA_START_NEG_COLL:
- STRCPY(code, "NFA_START_NEG_COLL"); break;
- case NFA_END_NEG_COLL:
- STRCPY(code, "NFA_END_NEG_COLL"); break;
- case NFA_RANGE:
- STRCPY(code, "NFA_RANGE"); break;
- case NFA_RANGE_MIN:
- STRCPY(code, "NFA_RANGE_MIN"); break;
- case NFA_RANGE_MAX:
- STRCPY(code, "NFA_RANGE_MAX"); break;
-
- case NFA_CLASS_ALNUM:
- STRCPY(code, "NFA_CLASS_ALNUM"); break;
- case NFA_CLASS_ALPHA:
- STRCPY(code, "NFA_CLASS_ALPHA"); break;
- case NFA_CLASS_BLANK:
- STRCPY(code, "NFA_CLASS_BLANK"); break;
- case NFA_CLASS_CNTRL:
- STRCPY(code, "NFA_CLASS_CNTRL"); break;
- case NFA_CLASS_DIGIT:
- STRCPY(code, "NFA_CLASS_DIGIT"); break;
- case NFA_CLASS_GRAPH:
- STRCPY(code, "NFA_CLASS_GRAPH"); break;
- case NFA_CLASS_LOWER:
- STRCPY(code, "NFA_CLASS_LOWER"); break;
- case NFA_CLASS_PRINT:
- STRCPY(code, "NFA_CLASS_PRINT"); break;
- case NFA_CLASS_PUNCT:
- STRCPY(code, "NFA_CLASS_PUNCT"); break;
- case NFA_CLASS_SPACE:
- STRCPY(code, "NFA_CLASS_SPACE"); break;
- case NFA_CLASS_UPPER:
- STRCPY(code, "NFA_CLASS_UPPER"); break;
- case NFA_CLASS_XDIGIT:
- STRCPY(code, "NFA_CLASS_XDIGIT"); break;
- case NFA_CLASS_TAB:
- STRCPY(code, "NFA_CLASS_TAB"); break;
- case NFA_CLASS_RETURN:
- STRCPY(code, "NFA_CLASS_RETURN"); break;
- case NFA_CLASS_BACKSPACE:
- STRCPY(code, "NFA_CLASS_BACKSPACE"); break;
- case NFA_CLASS_ESCAPE:
- STRCPY(code, "NFA_CLASS_ESCAPE"); break;
- case NFA_CLASS_IDENT:
- STRCPY(code, "NFA_CLASS_IDENT"); break;
- case NFA_CLASS_KEYWORD:
- STRCPY(code, "NFA_CLASS_KEYWORD"); break;
- case NFA_CLASS_FNAME:
- STRCPY(code, "NFA_CLASS_FNAME"); break;
-
- case NFA_ANY:
- STRCPY(code, "NFA_ANY"); break;
- case NFA_IDENT:
- STRCPY(code, "NFA_IDENT"); break;
- case NFA_SIDENT:
- STRCPY(code, "NFA_SIDENT"); break;
- case NFA_KWORD:
- STRCPY(code, "NFA_KWORD"); break;
- case NFA_SKWORD:
- STRCPY(code, "NFA_SKWORD"); break;
- case NFA_FNAME:
- STRCPY(code, "NFA_FNAME"); break;
- case NFA_SFNAME:
- STRCPY(code, "NFA_SFNAME"); break;
- case NFA_PRINT:
- STRCPY(code, "NFA_PRINT"); break;
- case NFA_SPRINT:
- STRCPY(code, "NFA_SPRINT"); break;
- case NFA_WHITE:
- STRCPY(code, "NFA_WHITE"); break;
- case NFA_NWHITE:
- STRCPY(code, "NFA_NWHITE"); break;
- case NFA_DIGIT:
- STRCPY(code, "NFA_DIGIT"); break;
- case NFA_NDIGIT:
- STRCPY(code, "NFA_NDIGIT"); break;
- case NFA_HEX:
- STRCPY(code, "NFA_HEX"); break;
- case NFA_NHEX:
- STRCPY(code, "NFA_NHEX"); break;
- case NFA_OCTAL:
- STRCPY(code, "NFA_OCTAL"); break;
- case NFA_NOCTAL:
- STRCPY(code, "NFA_NOCTAL"); break;
- case NFA_WORD:
- STRCPY(code, "NFA_WORD"); break;
- case NFA_NWORD:
- STRCPY(code, "NFA_NWORD"); break;
- case NFA_HEAD:
- STRCPY(code, "NFA_HEAD"); break;
- case NFA_NHEAD:
- STRCPY(code, "NFA_NHEAD"); break;
- case NFA_ALPHA:
- STRCPY(code, "NFA_ALPHA"); break;
- case NFA_NALPHA:
- STRCPY(code, "NFA_NALPHA"); break;
- case NFA_LOWER:
- STRCPY(code, "NFA_LOWER"); break;
- case NFA_NLOWER:
- STRCPY(code, "NFA_NLOWER"); break;
- case NFA_UPPER:
- STRCPY(code, "NFA_UPPER"); break;
- case NFA_NUPPER:
- STRCPY(code, "NFA_NUPPER"); break;
- case NFA_LOWER_IC:
- STRCPY(code, "NFA_LOWER_IC"); break;
- case NFA_NLOWER_IC:
- STRCPY(code, "NFA_NLOWER_IC"); break;
- case NFA_UPPER_IC:
- STRCPY(code, "NFA_UPPER_IC"); break;
- case NFA_NUPPER_IC:
- STRCPY(code, "NFA_NUPPER_IC"); break;
-
- default:
- STRCPY(code, "CHAR(x)");
- code[5] = c;
- }
-
- if (addnl == true) {
- STRCAT(code, " + NEWLINE ");
- }
-}
-
-static FILE *log_fd;
-static uint8_t e_log_open_failed[] =
- N_("Could not open temporary log file for writing, displaying on stderr... ");
-
-// Print the postfix notation of the current regexp.
-static void nfa_postfix_dump(uint8_t *expr, int retval)
-{
- int *p;
- FILE *f;
-
- f = fopen(NFA_REGEXP_DUMP_LOG, "a");
- if (f == NULL) {
- return;
- }
-
- fprintf(f, "\n-------------------------\n");
- if (retval == FAIL) {
- fprintf(f, ">>> NFA engine failed... \n");
- } else if (retval == OK) {
- fprintf(f, ">>> NFA engine succeeded !\n");
- }
- fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr);
- for (p = post_start; *p && p < post_ptr; p++) {
- nfa_set_code(*p);
- fprintf(f, "%s, ", code);
- }
- fprintf(f, "\"\nPostfix notation (int): ");
- for (p = post_start; *p && p < post_ptr; p++) {
- fprintf(f, "%d ", *p);
- }
- fprintf(f, "\n\n");
- fclose(f);
-}
-
-// Print the NFA starting with a root node "state".
-static void nfa_print_state(FILE *debugf, nfa_state_T *state)
-{
- garray_T indent;
-
- ga_init(&indent, 1, 64);
- ga_append(&indent, '\0');
- nfa_print_state2(debugf, state, &indent);
- ga_clear(&indent);
-}
-
-static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent)
-{
- uint8_t *p;
-
- if (state == NULL) {
- return;
- }
-
- fprintf(debugf, "(%2d)", abs(state->id));
-
- // Output indent
- p = (uint8_t *)indent->ga_data;
- if (indent->ga_len >= 3) {
- int last = indent->ga_len - 3;
- uint8_t save[2];
-
- strncpy(save, &p[last], 2); // NOLINT(runtime/printf)
- memcpy(&p[last], "+-", 2);
- fprintf(debugf, " %s", p);
- strncpy(&p[last], save, 2); // NOLINT(runtime/printf)
- } else {
- fprintf(debugf, " %s", p);
- }
-
- nfa_set_code(state->c);
- fprintf(debugf, "%s (%d) (id=%d) val=%d\n",
- code,
- state->c,
- abs(state->id),
- state->val);
- if (state->id < 0) {
- return;
- }
-
- state->id = abs(state->id) * -1;
-
- // grow indent for state->out
- indent->ga_len -= 1;
- if (state->out1) {
- ga_concat(indent, (uint8_t *)"| ");
- } else {
- ga_concat(indent, (uint8_t *)" ");
- }
- ga_append(indent, NUL);
-
- nfa_print_state2(debugf, state->out, indent);
-
- // replace last part of indent for state->out1
- indent->ga_len -= 3;
- ga_concat(indent, (uint8_t *)" ");
- ga_append(indent, NUL);
-
- nfa_print_state2(debugf, state->out1, indent);
-
- // shrink indent
- indent->ga_len -= 3;
- ga_append(indent, NUL);
-}
-
-// Print the NFA state machine.
-static void nfa_dump(nfa_regprog_T *prog)
-{
- FILE *debugf = fopen(NFA_REGEXP_DUMP_LOG, "a");
-
- if (debugf == NULL) {
- return;
- }
-
- nfa_print_state(debugf, prog->start);
-
- if (prog->reganch) {
- fprintf(debugf, "reganch: %d\n", prog->reganch);
- }
- if (prog->regstart != NUL) {
- fprintf(debugf, "regstart: %c (decimal: %d)\n",
- prog->regstart, prog->regstart);
- }
- if (prog->match_text != NULL) {
- fprintf(debugf, "match_text: \"%s\"\n", prog->match_text);
- }
-
- fclose(debugf);
-}
-#endif // REGEXP_DEBUG
-
-// Parse r.e. @expr and convert it into postfix form.
-// Return the postfix string on success, NULL otherwise.
-static int *re2post(void)
-{
- if (nfa_reg(REG_NOPAREN) == FAIL) {
- return NULL;
- }
- EMIT(NFA_MOPEN);
- return post_start;
-}
-
-// NB. Some of the code below is inspired by Russ's.
-
-// Represents an NFA state plus zero or one or two arrows exiting.
-// if c == MATCH, no arrows out; matching state.
-// If c == SPLIT, unlabeled arrows to out and out1 (if != NULL).
-// If c < 256, labeled arrow with character c to out.
-
-static nfa_state_T *state_ptr; // points to nfa_prog->state
-
-// Allocate and initialize nfa_state_T.
-static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1)
-{
- nfa_state_T *s;
-
- if (istate >= nstate) {
- return NULL;
- }
-
- s = &state_ptr[istate++];
-
- s->c = c;
- s->out = out;
- s->out1 = out1;
- s->val = 0;
-
- s->id = istate;
- s->lastlist[0] = 0;
- s->lastlist[1] = 0;
-
- return s;
-}
-
-// A partially built NFA without the matching state filled in.
-// Frag_T.start points at the start state.
-// Frag_T.out is a list of places that need to be set to the
-// next state for this fragment.
-
-// Initialize a Frag_T struct and return it.
-static Frag_T frag(nfa_state_T *start, Ptrlist *out)
-{
- Frag_T n;
-
- n.start = start;
- n.out = out;
- return n;
-}
-
-// Create singleton list containing just outp.
-static Ptrlist *list1(nfa_state_T **outp)
-{
- Ptrlist *l;
-
- l = (Ptrlist *)outp;
- l->next = NULL;
- return l;
-}
-
-// Patch the list of states at out to point to start.
-static void patch(Ptrlist *l, nfa_state_T *s)
-{
- Ptrlist *next;
-
- for (; l; l = next) {
- next = l->next;
- l->s = s;
- }
-}
-
-// Join the two lists l1 and l2, returning the combination.
-static Ptrlist *append(Ptrlist *l1, Ptrlist *l2)
-{
- Ptrlist *oldl1;
-
- oldl1 = l1;
- while (l1->next) {
- l1 = l1->next;
- }
- l1->next = l2;
- return oldl1;
-}
-
-// Stack used for transforming postfix form into NFA.
-static Frag_T empty;
-
-static void st_error(int *postfix, int *end, int *p)
-{
-#ifdef NFA_REGEXP_ERROR_LOG
- FILE *df;
- int *p2;
-
- df = fopen(NFA_REGEXP_ERROR_LOG, "a");
- if (df) {
- fprintf(df, "Error popping the stack!\n");
-# ifdef REGEXP_DEBUG
- fprintf(df, "Current regexp is \"%s\"\n", nfa_regengine.expr);
-# endif
- fprintf(df, "Postfix form is: ");
-# ifdef REGEXP_DEBUG
- for (p2 = postfix; p2 < end; p2++) {
- nfa_set_code(*p2);
- fprintf(df, "%s, ", code);
- }
- nfa_set_code(*p);
- fprintf(df, "\nCurrent position is: ");
- for (p2 = postfix; p2 <= p; p2++) {
- nfa_set_code(*p2);
- fprintf(df, "%s, ", code);
- }
-# else
- for (p2 = postfix; p2 < end; p2++) {
- fprintf(df, "%d, ", *p2);
- }
- fprintf(df, "\nCurrent position is: ");
- for (p2 = postfix; p2 <= p; p2++) {
- fprintf(df, "%d, ", *p2);
- }
-# endif
- fprintf(df, "\n--------------------------\n");
- fclose(df);
- }
-#endif
- emsg(_("E874: (NFA) Could not pop the stack!"));
-}
-
-// Push an item onto the stack.
-static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end)
-{
- Frag_T *stackp = *p;
-
- if (stackp >= stack_end) {
- return;
- }
- *stackp = s;
- *p = *p + 1;
-}
-
-// Pop an item from the stack.
-static Frag_T st_pop(Frag_T **p, Frag_T *stack)
-{
- Frag_T *stackp;
-
- *p = *p - 1;
- stackp = *p;
- if (stackp < stack) {
- return empty;
- }
- return **p;
-}
-
-// Estimate the maximum byte length of anything matching "state".
-// When unknown or unlimited return -1.
-static int nfa_max_width(nfa_state_T *startstate, int depth)
-{
- int l, r;
- nfa_state_T *state = startstate;
- int len = 0;
-
- // detect looping in a NFA_SPLIT
- if (depth > 4) {
- return -1;
- }
-
- while (state != NULL) {
- switch (state->c) {
- case NFA_END_INVISIBLE:
- case NFA_END_INVISIBLE_NEG:
- // the end, return what we have
- return len;
-
- case NFA_SPLIT:
- // two alternatives, use the maximum
- l = nfa_max_width(state->out, depth + 1);
- r = nfa_max_width(state->out1, depth + 1);
- if (l < 0 || r < 0) {
- return -1;
- }
- return len + (l > r ? l : r);
-
- case NFA_ANY:
- case NFA_START_COLL:
- case NFA_START_NEG_COLL:
- // Matches some character, including composing chars.
- len += MB_MAXBYTES;
- if (state->c != NFA_ANY) {
- // Skip over the characters.
- state = state->out1->out;
- continue;
- }
- break;
-
- case NFA_DIGIT:
- case NFA_WHITE:
- case NFA_HEX:
- case NFA_OCTAL:
- // ascii
- len++;
- break;
-
- case NFA_IDENT:
- case NFA_SIDENT:
- case NFA_KWORD:
- case NFA_SKWORD:
- case NFA_FNAME:
- case NFA_SFNAME:
- case NFA_PRINT:
- case NFA_SPRINT:
- case NFA_NWHITE:
- case NFA_NDIGIT:
- case NFA_NHEX:
- case NFA_NOCTAL:
- case NFA_WORD:
- case NFA_NWORD:
- case NFA_HEAD:
- case NFA_NHEAD:
- case NFA_ALPHA:
- case NFA_NALPHA:
- case NFA_LOWER:
- case NFA_NLOWER:
- case NFA_UPPER:
- case NFA_NUPPER:
- case NFA_LOWER_IC:
- case NFA_NLOWER_IC:
- case NFA_UPPER_IC:
- case NFA_NUPPER_IC:
- case NFA_ANY_COMPOSING:
- // possibly non-ascii
- len += 3;
- break;
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- // zero-width, out1 points to the END state
- state = state->out1->out;
- continue;
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- case NFA_NEWL:
- case NFA_SKIP:
- // unknown width
- return -1;
-
- case NFA_BOL:
- case NFA_EOL:
- case NFA_BOF:
- case NFA_EOF:
- case NFA_BOW:
- case NFA_EOW:
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_MCLOSE:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_NOPEN:
- case NFA_NCLOSE:
-
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_MARK_GT:
- case NFA_MARK_LT:
- case NFA_VISUAL:
- case NFA_LNUM:
- case NFA_CURSOR:
- case NFA_COL:
- case NFA_VCOL:
- case NFA_MARK:
-
- case NFA_ZSTART:
- case NFA_ZEND:
- case NFA_OPT_CHARS:
- case NFA_EMPTY:
- case NFA_START_PATTERN:
- case NFA_END_PATTERN:
- case NFA_COMPOSING:
- case NFA_END_COMPOSING:
- // zero-width
- break;
-
- default:
- if (state->c < 0) {
- // don't know what this is
- return -1;
- }
- // normal character
- len += utf_char2len(state->c);
- break;
- }
-
- // normal way to continue
- state = state->out;
- }
-
- // unrecognized, "cannot happen"
- return -1;
-}
-
-// Convert a postfix form into its equivalent NFA.
-// Return the NFA start state on success, NULL otherwise.
-static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
-{
- int *p;
- int mopen;
- int mclose;
- Frag_T *stack = NULL;
- Frag_T *stackp = NULL;
- Frag_T *stack_end = NULL;
- Frag_T e1;
- Frag_T e2;
- Frag_T e;
- nfa_state_T *s;
- nfa_state_T *s1;
- nfa_state_T *matchstate;
- nfa_state_T *ret = NULL;
-
- if (postfix == NULL) {
- return NULL;
- }
-
-#define PUSH(s) st_push((s), &stackp, stack_end)
-#define POP() st_pop(&stackp, stack); \
- if (stackp < stack) { \
- st_error(postfix, end, p); \
- xfree(stack); \
- return NULL; \
- }
-
- if (nfa_calc_size == false) {
- // Allocate space for the stack. Max states on the stack: "nstate".
- stack = xmalloc((size_t)(nstate + 1) * sizeof(Frag_T));
- stackp = stack;
- stack_end = stack + (nstate + 1);
- }
-
- for (p = postfix; p < end; p++) {
- switch (*p) {
- case NFA_CONCAT:
- // Concatenation.
- // Pay attention: this operator does not exist in the r.e. itself
- // (it is implicit, really). It is added when r.e. is translated
- // to postfix form in re2post().
- if (nfa_calc_size == true) {
- // nstate += 0;
- break;
- }
- e2 = POP();
- e1 = POP();
- patch(e1.out, e2.start);
- PUSH(frag(e1.start, e2.out));
- break;
-
- case NFA_OR:
- // Alternation
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e2 = POP();
- e1 = POP();
- s = alloc_state(NFA_SPLIT, e1.start, e2.start);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, append(e1.out, e2.out)));
- break;
-
- case NFA_STAR:
- // Zero or more, prefer more
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, e.start, NULL);
- if (s == NULL) {
- goto theend;
- }
- patch(e.out, s);
- PUSH(frag(s, list1(&s->out1)));
- break;
-
- case NFA_STAR_NONGREEDY:
- // Zero or more, prefer zero
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, NULL, e.start);
- if (s == NULL) {
- goto theend;
- }
- patch(e.out, s);
- PUSH(frag(s, list1(&s->out)));
- break;
-
- case NFA_QUEST:
- // one or zero atoms=> greedy match
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, e.start, NULL);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, append(e.out, list1(&s->out1))));
- break;
-
- case NFA_QUEST_NONGREEDY:
- // zero or one atoms => non-greedy match
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, NULL, e.start);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, append(e.out, list1(&s->out))));
- break;
-
- case NFA_END_COLL:
- case NFA_END_NEG_COLL:
- // On the stack is the sequence starting with NFA_START_COLL or
- // NFA_START_NEG_COLL and all possible characters. Patch it to
- // add the output to the start.
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_END_COLL, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- patch(e.out, s);
- e.start->out1 = s;
- PUSH(frag(e.start, list1(&s->out)));
- break;
-
- case NFA_RANGE:
- // Before this are two characters, the low and high end of a
- // range. Turn them into two states with MIN and MAX.
- if (nfa_calc_size == true) {
- // nstate += 0;
- break;
- }
- e2 = POP();
- e1 = POP();
- e2.start->val = e2.start->c;
- e2.start->c = NFA_RANGE_MAX;
- e1.start->val = e1.start->c;
- e1.start->c = NFA_RANGE_MIN;
- patch(e1.out, e2.start);
- PUSH(frag(e1.start, e2.out));
- break;
-
- case NFA_EMPTY:
- // 0-length, used in a repetition with max/min count of 0
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- s = alloc_state(NFA_EMPTY, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, list1(&s->out)));
- break;
-
- case NFA_OPT_CHARS: {
- int n;
-
- // \%[abc] implemented as:
- // NFA_SPLIT
- // +-CHAR(a)
- // | +-NFA_SPLIT
- // | +-CHAR(b)
- // | | +-NFA_SPLIT
- // | | +-CHAR(c)
- // | | | +-next
- // | | +- next
- // | +- next
- // +- next
- n = *++p; // get number of characters
- if (nfa_calc_size == true) {
- nstate += n;
- break;
- }
- s = NULL; // avoid compiler warning
- e1.out = NULL; // stores list with out1's
- s1 = NULL; // previous NFA_SPLIT to connect to
- while (n-- > 0) {
- e = POP(); // get character
- s = alloc_state(NFA_SPLIT, e.start, NULL);
- if (s == NULL) {
- goto theend;
- }
- if (e1.out == NULL) {
- e1 = e;
- }
- patch(e.out, s1);
- append(e1.out, list1(&s->out1));
- s1 = s;
- }
- PUSH(frag(s, e1.out));
- break;
- }
-
- case NFA_PREV_ATOM_NO_WIDTH:
- case NFA_PREV_ATOM_NO_WIDTH_NEG:
- case NFA_PREV_ATOM_JUST_BEFORE:
- case NFA_PREV_ATOM_JUST_BEFORE_NEG:
- case NFA_PREV_ATOM_LIKE_PATTERN: {
- int before = (*p == NFA_PREV_ATOM_JUST_BEFORE
- || *p == NFA_PREV_ATOM_JUST_BEFORE_NEG);
- int pattern = (*p == NFA_PREV_ATOM_LIKE_PATTERN);
- int start_state;
- int end_state;
- int n = 0;
- nfa_state_T *zend;
- nfa_state_T *skip;
-
- switch (*p) {
- case NFA_PREV_ATOM_NO_WIDTH:
- start_state = NFA_START_INVISIBLE;
- end_state = NFA_END_INVISIBLE;
- break;
- case NFA_PREV_ATOM_NO_WIDTH_NEG:
- start_state = NFA_START_INVISIBLE_NEG;
- end_state = NFA_END_INVISIBLE_NEG;
- break;
- case NFA_PREV_ATOM_JUST_BEFORE:
- start_state = NFA_START_INVISIBLE_BEFORE;
- end_state = NFA_END_INVISIBLE;
- break;
- case NFA_PREV_ATOM_JUST_BEFORE_NEG:
- start_state = NFA_START_INVISIBLE_BEFORE_NEG;
- end_state = NFA_END_INVISIBLE_NEG;
- break;
- default: // NFA_PREV_ATOM_LIKE_PATTERN:
- start_state = NFA_START_PATTERN;
- end_state = NFA_END_PATTERN;
- break;
- }
-
- if (before) {
- n = *++p; // get the count
- }
- // The \@= operator: match the preceding atom with zero width.
- // The \@! operator: no match for the preceding atom.
- // The \@<= operator: match for the preceding atom.
- // The \@<! operator: no match for the preceding atom.
- // Surrounds the preceding atom with START_INVISIBLE and
- // END_INVISIBLE, similarly to MOPEN.
-
- if (nfa_calc_size == true) {
- nstate += pattern ? 4 : 2;
- break;
- }
- e = POP();
- s1 = alloc_state(end_state, NULL, NULL);
- if (s1 == NULL) {
- goto theend;
- }
-
- s = alloc_state(start_state, e.start, s1);
- if (s == NULL) {
- goto theend;
- }
- if (pattern) {
- // NFA_ZEND -> NFA_END_PATTERN -> NFA_SKIP -> what follows.
- skip = alloc_state(NFA_SKIP, NULL, NULL);
- if (skip == NULL) {
- goto theend;
- }
- zend = alloc_state(NFA_ZEND, s1, NULL);
- if (zend == NULL) {
- goto theend;
- }
- s1->out= skip;
- patch(e.out, zend);
- PUSH(frag(s, list1(&skip->out)));
- } else {
- patch(e.out, s1);
- PUSH(frag(s, list1(&s1->out)));
- if (before) {
- if (n <= 0) {
- // See if we can guess the maximum width, it avoids a
- // lot of pointless tries.
- n = nfa_max_width(e.start, 0);
- }
- s->val = n; // store the count
- }
- }
- break;
- }
-
- case NFA_COMPOSING: // char with composing char
- FALLTHROUGH;
-
- case NFA_MOPEN: // \( \) Submatch
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN: // \z( \) Submatch
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_NOPEN: // \%( \) "Invisible Submatch"
- if (nfa_calc_size == true) {
- nstate += 2;
- break;
- }
-
- mopen = *p;
- switch (*p) {
- case NFA_NOPEN:
- mclose = NFA_NCLOSE; break;
- case NFA_ZOPEN:
- mclose = NFA_ZCLOSE; break;
- case NFA_ZOPEN1:
- mclose = NFA_ZCLOSE1; break;
- case NFA_ZOPEN2:
- mclose = NFA_ZCLOSE2; break;
- case NFA_ZOPEN3:
- mclose = NFA_ZCLOSE3; break;
- case NFA_ZOPEN4:
- mclose = NFA_ZCLOSE4; break;
- case NFA_ZOPEN5:
- mclose = NFA_ZCLOSE5; break;
- case NFA_ZOPEN6:
- mclose = NFA_ZCLOSE6; break;
- case NFA_ZOPEN7:
- mclose = NFA_ZCLOSE7; break;
- case NFA_ZOPEN8:
- mclose = NFA_ZCLOSE8; break;
- case NFA_ZOPEN9:
- mclose = NFA_ZCLOSE9; break;
- case NFA_COMPOSING:
- mclose = NFA_END_COMPOSING; break;
- default:
- // NFA_MOPEN, NFA_MOPEN1 .. NFA_MOPEN9
- mclose = *p + NSUBEXP;
- break;
- }
-
- // Allow "NFA_MOPEN" as a valid postfix representation for
- // the empty regexp "". In this case, the NFA will be
- // NFA_MOPEN -> NFA_MCLOSE. Note that this also allows
- // empty groups of parenthesis, and empty mbyte chars
- if (stackp == stack) {
- s = alloc_state(mopen, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- s1 = alloc_state(mclose, NULL, NULL);
- if (s1 == NULL) {
- goto theend;
- }
- patch(list1(&s->out), s1);
- PUSH(frag(s, list1(&s1->out)));
- break;
- }
-
- // At least one node was emitted before NFA_MOPEN, so
- // at least one node will be between NFA_MOPEN and NFA_MCLOSE
- e = POP();
- s = alloc_state(mopen, e.start, NULL); // `('
- if (s == NULL) {
- goto theend;
- }
-
- s1 = alloc_state(mclose, NULL, NULL); // `)'
- if (s1 == NULL) {
- goto theend;
- }
- patch(e.out, s1);
-
- if (mopen == NFA_COMPOSING) {
- // COMPOSING->out1 = END_COMPOSING
- patch(list1(&s->out1), s1);
- }
-
- PUSH(frag(s, list1(&s1->out)));
- break;
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- if (nfa_calc_size == true) {
- nstate += 2;
- break;
- }
- s = alloc_state(*p, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- s1 = alloc_state(NFA_SKIP, NULL, NULL);
- if (s1 == NULL) {
- goto theend;
- }
- patch(list1(&s->out), s1);
- PUSH(frag(s, list1(&s1->out)));
- break;
-
- case NFA_LNUM:
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_VCOL:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_COL:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_MARK:
- case NFA_MARK_GT:
- case NFA_MARK_LT: {
- int n = *++p; // lnum, col or mark name
-
- if (nfa_calc_size == true) {
- nstate += 1;
- break;
- }
- s = alloc_state(p[-1], NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- s->val = n;
- PUSH(frag(s, list1(&s->out)));
- break;
- }
-
- case NFA_ZSTART:
- case NFA_ZEND:
- default:
- // Operands
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- s = alloc_state(*p, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, list1(&s->out)));
- break;
- } // switch(*p)
- } // for(p = postfix; *p; ++p)
-
- if (nfa_calc_size == true) {
- nstate++;
- goto theend; // Return value when counting size is ignored anyway
- }
-
- e = POP();
- if (stackp != stack) {
- xfree(stack);
- EMSG_RET_NULL(_("E875: (NFA regexp) (While converting from postfix to NFA),"
- "too many states left on stack"));
- }
-
- if (istate >= nstate) {
- xfree(stack);
- EMSG_RET_NULL(_("E876: (NFA regexp) "
- "Not enough space to store the whole NFA "));
- }
-
- matchstate = &state_ptr[istate++]; // the match state
- matchstate->c = NFA_MATCH;
- matchstate->out = matchstate->out1 = NULL;
- matchstate->id = 0;
-
- patch(e.out, matchstate);
- ret = e.start;
-
-theend:
- xfree(stack);
- return ret;
-
-#undef POP1
-#undef PUSH1
-#undef POP2
-#undef PUSH2
-#undef POP
-#undef PUSH
-}
-
-// After building the NFA program, inspect it to add optimization hints.
-static void nfa_postprocess(nfa_regprog_T *prog)
-{
- int i;
- int c;
-
- for (i = 0; i < prog->nstate; i++) {
- c = prog->state[i].c;
- if (c == NFA_START_INVISIBLE
- || c == NFA_START_INVISIBLE_NEG
- || c == NFA_START_INVISIBLE_BEFORE
- || c == NFA_START_INVISIBLE_BEFORE_NEG) {
- int directly;
-
- // Do it directly when what follows is possibly the end of the
- // match.
- if (match_follows(prog->state[i].out1->out, 0)) {
- directly = true;
- } else {
- int ch_invisible = failure_chance(prog->state[i].out, 0);
- int ch_follows = failure_chance(prog->state[i].out1->out, 0);
-
- // Postpone when the invisible match is expensive or has a
- // lower chance of failing.
- if (c == NFA_START_INVISIBLE_BEFORE
- || c == NFA_START_INVISIBLE_BEFORE_NEG) {
- // "before" matches are very expensive when
- // unbounded, always prefer what follows then,
- // unless what follows will always match.
- // Otherwise strongly prefer what follows.
- if (prog->state[i].val <= 0 && ch_follows > 0) {
- directly = false;
- } else {
- directly = ch_follows * 10 < ch_invisible;
- }
- } else {
- // normal invisible, first do the one with the
- // highest failure chance
- directly = ch_follows < ch_invisible;
- }
- }
- if (directly) {
- // switch to the _FIRST state
- prog->state[i].c++;
- }
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////
-// NFA execution code.
-/////////////////////////////////////////////////////////////////
-
-// Values for done in nfa_pim_T.
-#define NFA_PIM_UNUSED 0 // pim not used
-#define NFA_PIM_TODO 1 // pim not done yet
-#define NFA_PIM_MATCH 2 // pim executed, matches
-#define NFA_PIM_NOMATCH 3 // pim executed, no match
-
-#ifdef REGEXP_DEBUG
-static void log_subsexpr(regsubs_T *subs)
-{
- log_subexpr(&subs->norm);
- if (rex.nfa_has_zsubexpr) {
- log_subexpr(&subs->synt);
- }
-}
-
-static void log_subexpr(regsub_T *sub)
-{
- int j;
-
- for (j = 0; j < sub->in_use; j++) {
- if (REG_MULTI) {
- fprintf(log_fd, "*** group %d, start: c=%d, l=%d, end: c=%d, l=%d\n",
- j,
- sub->list.multi[j].start_col,
- (int)sub->list.multi[j].start_lnum,
- sub->list.multi[j].end_col,
- (int)sub->list.multi[j].end_lnum);
- } else {
- char *s = (char *)sub->list.line[j].start;
- char *e = (char *)sub->list.line[j].end;
-
- fprintf(log_fd, "*** group %d, start: \"%s\", end: \"%s\"\n",
- j,
- s == NULL ? "NULL" : s,
- e == NULL ? "NULL" : e);
- }
- }
-}
-
-static char *pim_info(const nfa_pim_T *pim)
-{
- static char buf[30];
-
- if (pim == NULL || pim->result == NFA_PIM_UNUSED) {
- buf[0] = NUL;
- } else {
- snprintf(buf, sizeof(buf), " PIM col %d",
- REG_MULTI
- ? (int)pim->end.pos.col
- : (int)(pim->end.ptr - rex.input));
- }
- return buf;
-}
-
-#endif
-
-// Used during execution: whether a match has been found.
-static int nfa_match;
-static proftime_T *nfa_time_limit;
-static int *nfa_timed_out;
-static int nfa_time_count;
-
-// Copy postponed invisible match info from "from" to "to".
-static void copy_pim(nfa_pim_T *to, nfa_pim_T *from)
-{
- to->result = from->result;
- to->state = from->state;
- copy_sub(&to->subs.norm, &from->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&to->subs.synt, &from->subs.synt);
- }
- to->end = from->end;
-}
-
-static void clear_sub(regsub_T *sub)
-{
- if (REG_MULTI) {
- // Use 0xff to set lnum to -1
- memset(sub->list.multi, 0xff, sizeof(struct multipos) * (size_t)rex.nfa_nsubexpr);
- } else {
- memset(sub->list.line, 0, sizeof(struct linepos) * (size_t)rex.nfa_nsubexpr);
- }
- sub->in_use = 0;
-}
-
-// Copy the submatches from "from" to "to".
-static void copy_sub(regsub_T *to, regsub_T *from)
-{
- to->in_use = from->in_use;
- if (from->in_use <= 0) {
- return;
- }
-
- // Copy the match start and end positions.
- if (REG_MULTI) {
- memmove(&to->list.multi[0], &from->list.multi[0],
- sizeof(struct multipos) * (size_t)from->in_use);
- to->orig_start_col = from->orig_start_col;
- } else {
- memmove(&to->list.line[0], &from->list.line[0],
- sizeof(struct linepos) * (size_t)from->in_use);
- }
-}
-
-// Like copy_sub() but exclude the main match.
-static void copy_sub_off(regsub_T *to, regsub_T *from)
-{
- if (to->in_use < from->in_use) {
- to->in_use = from->in_use;
- }
- if (from->in_use <= 1) {
- return;
- }
-
- // Copy the match start and end positions.
- if (REG_MULTI) {
- memmove(&to->list.multi[1], &from->list.multi[1],
- sizeof(struct multipos) * (size_t)(from->in_use - 1));
- } else {
- memmove(&to->list.line[1], &from->list.line[1],
- sizeof(struct linepos) * (size_t)(from->in_use - 1));
- }
-}
-
-// Like copy_sub() but only do the end of the main match if \ze is present.
-static void copy_ze_off(regsub_T *to, regsub_T *from)
-{
- if (!rex.nfa_has_zend) {
- return;
- }
-
- if (REG_MULTI) {
- if (from->list.multi[0].end_lnum >= 0) {
- to->list.multi[0].end_lnum = from->list.multi[0].end_lnum;
- to->list.multi[0].end_col = from->list.multi[0].end_col;
- }
- } else {
- if (from->list.line[0].end != NULL) {
- to->list.line[0].end = from->list.line[0].end;
- }
- }
-}
-
-// Return true if "sub1" and "sub2" have the same start positions.
-// When using back-references also check the end position.
-static bool sub_equal(regsub_T *sub1, regsub_T *sub2)
-{
- int i;
- int todo;
- linenr_T s1;
- linenr_T s2;
- uint8_t *sp1;
- uint8_t *sp2;
-
- todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use;
- if (REG_MULTI) {
- for (i = 0; i < todo; i++) {
- if (i < sub1->in_use) {
- s1 = sub1->list.multi[i].start_lnum;
- } else {
- s1 = -1;
- }
- if (i < sub2->in_use) {
- s2 = sub2->list.multi[i].start_lnum;
- } else {
- s2 = -1;
- }
- if (s1 != s2) {
- return false;
- }
- if (s1 != -1 && sub1->list.multi[i].start_col
- != sub2->list.multi[i].start_col) {
- return false;
- }
- if (rex.nfa_has_backref) {
- if (i < sub1->in_use) {
- s1 = sub1->list.multi[i].end_lnum;
- } else {
- s1 = -1;
- }
- if (i < sub2->in_use) {
- s2 = sub2->list.multi[i].end_lnum;
- } else {
- s2 = -1;
- }
- if (s1 != s2) {
- return false;
- }
- if (s1 != -1
- && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) {
- return false;
- }
- }
- }
- } else {
- for (i = 0; i < todo; i++) {
- if (i < sub1->in_use) {
- sp1 = sub1->list.line[i].start;
- } else {
- sp1 = NULL;
- }
- if (i < sub2->in_use) {
- sp2 = sub2->list.line[i].start;
- } else {
- sp2 = NULL;
- }
- if (sp1 != sp2) {
- return false;
- }
- if (rex.nfa_has_backref) {
- if (i < sub1->in_use) {
- sp1 = sub1->list.line[i].end;
- } else {
- sp1 = NULL;
- }
- if (i < sub2->in_use) {
- sp2 = sub2->list.line[i].end;
- } else {
- sp2 = NULL;
- }
- if (sp1 != sp2) {
- return false;
- }
- }
- }
- }
-
- return true;
-}
-
-#ifdef REGEXP_DEBUG
-static void open_debug_log(TriState result)
-{
- log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
- if (log_fd == NULL) {
- emsg(_(e_log_open_failed));
- log_fd = stderr;
- }
-
- fprintf(log_fd, "****************************\n");
- fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n");
- fprintf(log_fd, "MATCH = %s\n", result == kTrue ? "OK" : result == kNone ? "MAYBE" : "FALSE");
- fprintf(log_fd, "****************************\n");
-}
-
-static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int lid, nfa_pim_T *pim)
-{
- int col;
-
- if (sub->in_use <= 0) {
- col = -1;
- } else if (REG_MULTI) {
- col = sub->list.multi[0].start_col;
- } else {
- col = (int)(sub->list.line[0].start - rex.line);
- }
- nfa_set_code(state->c);
- if (log_fd == NULL) {
- open_debug_log(kNone);
- }
- fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n",
- action, abs(state->id), lid, state->c, code, col,
- pim_info(pim));
-}
-
-#endif
-
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs pointers to subexpressions
-/// @param pim postponed match or NULL
-///
-/// @return true if the same state is already in list "l" with the same
-/// positions as "subs".
-static bool has_state_with_pos(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim)
- FUNC_ATTR_NONNULL_ARG(1, 2, 3)
-{
- for (int i = 0; i < l->n; i++) {
- nfa_thread_T *thread = &l->t[i];
- if (thread->state->id == state->id
- && sub_equal(&thread->subs.norm, &subs->norm)
- && (!rex.nfa_has_zsubexpr
- || sub_equal(&thread->subs.synt, &subs->synt))
- && pim_equal(&thread->pim, pim)) {
- return true;
- }
- }
- return false;
-}
-
-// Return true if "one" and "two" are equal. That includes when both are not
-// set.
-static bool pim_equal(const nfa_pim_T *one, const nfa_pim_T *two)
-{
- const bool one_unused = (one == NULL || one->result == NFA_PIM_UNUSED);
- const bool two_unused = (two == NULL || two->result == NFA_PIM_UNUSED);
-
- if (one_unused) {
- // one is unused: equal when two is also unused
- return two_unused;
- }
- if (two_unused) {
- // one is used and two is not: not equal
- return false;
- }
- // compare the state id
- if (one->state->id != two->state->id) {
- return false;
- }
- // compare the position
- if (REG_MULTI) {
- return one->end.pos.lnum == two->end.pos.lnum
- && one->end.pos.col == two->end.pos.col;
- }
- return one->end.ptr == two->end.ptr;
-}
-
-// Return true if "state" leads to a NFA_MATCH without advancing the input.
-static bool match_follows(const nfa_state_T *startstate, int depth)
- FUNC_ATTR_NONNULL_ALL
-{
- const nfa_state_T *state = startstate;
-
- // avoid too much recursion
- if (depth > 10) {
- return false;
- }
- while (state != NULL) {
- switch (state->c) {
- case NFA_MATCH:
- case NFA_MCLOSE:
- case NFA_END_INVISIBLE:
- case NFA_END_INVISIBLE_NEG:
- case NFA_END_PATTERN:
- return true;
-
- case NFA_SPLIT:
- return match_follows(state->out, depth + 1)
- || match_follows(state->out1, depth + 1);
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_FIRST:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_NEG_FIRST:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
- case NFA_COMPOSING:
- // skip ahead to next state
- state = state->out1->out;
- continue;
-
- case NFA_ANY:
- case NFA_ANY_COMPOSING:
- case NFA_IDENT:
- case NFA_SIDENT:
- case NFA_KWORD:
- case NFA_SKWORD:
- case NFA_FNAME:
- case NFA_SFNAME:
- case NFA_PRINT:
- case NFA_SPRINT:
- case NFA_WHITE:
- case NFA_NWHITE:
- case NFA_DIGIT:
- case NFA_NDIGIT:
- case NFA_HEX:
- case NFA_NHEX:
- case NFA_OCTAL:
- case NFA_NOCTAL:
- case NFA_WORD:
- case NFA_NWORD:
- case NFA_HEAD:
- case NFA_NHEAD:
- case NFA_ALPHA:
- case NFA_NALPHA:
- case NFA_LOWER:
- case NFA_NLOWER:
- case NFA_UPPER:
- case NFA_NUPPER:
- case NFA_LOWER_IC:
- case NFA_NLOWER_IC:
- case NFA_UPPER_IC:
- case NFA_NUPPER_IC:
- case NFA_START_COLL:
- case NFA_START_NEG_COLL:
- case NFA_NEWL:
- // state will advance input
- return false;
-
- default:
- if (state->c > 0) {
- // state will advance input
- return false;
- }
- // Others: zero-width or possibly zero-width, might still find
- // a match at the same position, keep looking.
- break;
- }
- state = state->out;
- }
- return false;
-}
-
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs pointers to subexpressions
-///
-/// @return true if "state" is already in list "l".
-static bool state_in_list(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs)
- FUNC_ATTR_NONNULL_ALL
-{
- if (state->lastlist[nfa_ll_index] == l->id) {
- if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) {
- return true;
- }
- }
- return false;
-}
-
-// Offset used for "off" by addstate_here().
-#define ADDSTATE_HERE_OFFSET 10
-
-/// Add "state" and possibly what follows to state list ".".
-///
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs_arg pointers to subexpressions
-/// @param pim postponed look-behind match
-/// @param off_arg byte offset, when -1 go to next line
-///
-/// @return "subs_arg", possibly copied into temp_subs.
-/// NULL when recursiveness is too deep.
-static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_arg, nfa_pim_T *pim,
- int off_arg)
- FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
-{
- int subidx;
- int off = off_arg;
- int add_here = false;
- int listindex = 0;
- int k;
- int found = false;
- nfa_thread_T *thread;
- struct multipos save_multipos;
- int save_in_use;
- uint8_t *save_ptr;
- int i;
- regsub_T *sub;
- regsubs_T *subs = subs_arg;
- static regsubs_T temp_subs;
-#ifdef REGEXP_DEBUG
- int did_print = false;
-#endif
- static int depth = 0;
-
- // This function is called recursively. When the depth is too much we run
- // out of stack and crash, limit recursiveness here.
- if (++depth >= 5000 || subs == NULL) {
- depth--;
- return NULL;
- }
-
- if (off_arg <= -ADDSTATE_HERE_OFFSET) {
- add_here = true;
- off = 0;
- listindex = -(off_arg + ADDSTATE_HERE_OFFSET);
- }
-
- switch (state->c) {
- case NFA_NCLOSE:
- case NFA_MCLOSE:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_MOPEN:
- case NFA_ZEND:
- case NFA_SPLIT:
- case NFA_EMPTY:
- // These nodes are not added themselves but their "out" and/or
- // "out1" may be added below.
- break;
-
- case NFA_BOL:
- case NFA_BOF:
- // "^" won't match past end-of-line, don't bother trying.
- // Except when at the end of the line, or when we are going to the
- // next line for a look-behind match.
- if (rex.input > rex.line
- && *rex.input != NUL
- && (nfa_endp == NULL
- || !REG_MULTI
- || rex.lnum == nfa_endp->se_u.pos.lnum)) {
- goto skip_add;
- }
- FALLTHROUGH;
-
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_NOPEN:
- case NFA_ZSTART:
- // These nodes need to be added so that we can bail out when it
- // was added to this list before at the same position to avoid an
- // endless loop for "\(\)*"
-
- default:
- if (state->lastlist[nfa_ll_index] == l->id && state->c != NFA_SKIP) {
- // This state is already in the list, don't add it again,
- // unless it is an MOPEN that is used for a backreference or
- // when there is a PIM. For NFA_MATCH check the position,
- // lower position is preferred.
- if (!rex.nfa_has_backref && pim == NULL && !l->has_pim
- && state->c != NFA_MATCH) {
- // When called from addstate_here() do insert before
- // existing states.
- if (add_here) {
- for (k = 0; k < l->n && k < listindex; k++) {
- if (l->t[k].state->id == state->id) {
- found = true;
- break;
- }
- }
- }
-
- if (!add_here || found) {
-skip_add:
-#ifdef REGEXP_DEBUG
- nfa_set_code(state->c);
- fprintf(log_fd,
- "> Not adding state %d to list %d. char %d: %s pim: %s has_pim: %d found: %d\n",
- abs(state->id), l->id, state->c, code,
- pim == NULL ? "NULL" : "yes", l->has_pim, found);
-#endif
- depth--;
- return subs;
- }
- }
-
- // Do not add the state again when it exists with the same
- // positions.
- if (has_state_with_pos(l, state, subs, pim)) {
- goto skip_add;
- }
- }
-
- // When there are backreferences or PIMs the number of states may
- // be (a lot) bigger than anticipated.
- if (l->n == l->len) {
- const int newlen = l->len * 3 / 2 + 50;
- const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
-
- if ((long)(newsize >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- depth--;
- return NULL;
- }
- if (subs != &temp_subs) {
- // "subs" may point into the current array, need to make a
- // copy before it becomes invalid.
- copy_sub(&temp_subs.norm, &subs->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&temp_subs.synt, &subs->synt);
- }
- subs = &temp_subs;
- }
-
- nfa_thread_T *const newt = xrealloc(l->t, newsize);
- l->t = newt;
- l->len = newlen;
- }
-
- // add the state to the list
- state->lastlist[nfa_ll_index] = l->id;
- thread = &l->t[l->n++];
- thread->state = state;
- if (pim == NULL) {
- thread->pim.result = NFA_PIM_UNUSED;
- } else {
- copy_pim(&thread->pim, pim);
- l->has_pim = true;
- }
- copy_sub(&thread->subs.norm, &subs->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&thread->subs.synt, &subs->synt);
- }
-#ifdef REGEXP_DEBUG
- report_state("Adding", &thread->subs.norm, state, l->id, pim);
- did_print = true;
-#endif
- }
-
-#ifdef REGEXP_DEBUG
- if (!did_print) {
- report_state("Processing", &subs->norm, state, l->id, pim);
- }
-#endif
- switch (state->c) {
- case NFA_MATCH:
- break;
-
- case NFA_SPLIT:
- // order matters here
- subs = addstate(l, state->out, subs, pim, off_arg);
- subs = addstate(l, state->out1, subs, pim, off_arg);
- break;
-
- case NFA_EMPTY:
- case NFA_NOPEN:
- case NFA_NCLOSE:
- subs = addstate(l, state->out, subs, pim, off_arg);
- break;
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_ZSTART:
- if (state->c == NFA_ZSTART) {
- subidx = 0;
- sub = &subs->norm;
- } else if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560
- subidx = state->c - NFA_ZOPEN;
- sub = &subs->synt;
- } else {
- subidx = state->c - NFA_MOPEN;
- sub = &subs->norm;
- }
-
- // avoid compiler warnings
- save_ptr = NULL;
- CLEAR_FIELD(save_multipos);
-
- // Set the position (with "off" added) in the subexpression. Save
- // and restore it when it was in use. Otherwise fill any gap.
- if (REG_MULTI) {
- if (subidx < sub->in_use) {
- save_multipos = sub->list.multi[subidx];
- save_in_use = -1;
- } else {
- save_in_use = sub->in_use;
- for (i = sub->in_use; i < subidx; i++) {
- sub->list.multi[i].start_lnum = -1;
- sub->list.multi[i].end_lnum = -1;
- }
- sub->in_use = subidx + 1;
- }
- if (off == -1) {
- sub->list.multi[subidx].start_lnum = rex.lnum + 1;
- sub->list.multi[subidx].start_col = 0;
- } else {
- sub->list.multi[subidx].start_lnum = rex.lnum;
- sub->list.multi[subidx].start_col =
- (colnr_T)(rex.input - rex.line + off);
- }
- sub->list.multi[subidx].end_lnum = -1;
- } else {
- if (subidx < sub->in_use) {
- save_ptr = sub->list.line[subidx].start;
- save_in_use = -1;
- } else {
- save_in_use = sub->in_use;
- for (i = sub->in_use; i < subidx; i++) {
- sub->list.line[i].start = NULL;
- sub->list.line[i].end = NULL;
- }
- sub->in_use = subidx + 1;
- }
- sub->list.line[subidx].start = rex.input + off;
- }
-
- subs = addstate(l, state->out, subs, pim, off_arg);
- if (subs == NULL) {
- break;
- }
- // "subs" may have changed, need to set "sub" again.
- if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560
- sub = &subs->synt;
- } else {
- sub = &subs->norm;
- }
-
- if (save_in_use == -1) {
- if (REG_MULTI) {
- sub->list.multi[subidx] = save_multipos;
- } else {
- sub->list.line[subidx].start = save_ptr;
- }
- } else {
- sub->in_use = save_in_use;
- }
- break;
-
- case NFA_MCLOSE:
- if (rex.nfa_has_zend
- && (REG_MULTI
- ? subs->norm.list.multi[0].end_lnum >= 0
- : subs->norm.list.line[0].end != NULL)) {
- // Do not overwrite the position set by \ze.
- subs = addstate(l, state->out, subs, pim, off_arg);
- break;
- }
- FALLTHROUGH;
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_ZEND:
- if (state->c == NFA_ZEND) {
- subidx = 0;
- sub = &subs->norm;
- } else if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560
- subidx = state->c - NFA_ZCLOSE;
- sub = &subs->synt;
- } else {
- subidx = state->c - NFA_MCLOSE;
- sub = &subs->norm;
- }
-
- // We don't fill in gaps here, there must have been an MOPEN that
- // has done that.
- save_in_use = sub->in_use;
- if (sub->in_use <= subidx) {
- sub->in_use = subidx + 1;
- }
- if (REG_MULTI) {
- save_multipos = sub->list.multi[subidx];
- if (off == -1) {
- sub->list.multi[subidx].end_lnum = rex.lnum + 1;
- sub->list.multi[subidx].end_col = 0;
- } else {
- sub->list.multi[subidx].end_lnum = rex.lnum;
- sub->list.multi[subidx].end_col =
- (colnr_T)(rex.input - rex.line + off);
- }
- // avoid compiler warnings
- save_ptr = NULL;
- } else {
- save_ptr = sub->list.line[subidx].end;
- sub->list.line[subidx].end = rex.input + off;
- // avoid compiler warnings
- CLEAR_FIELD(save_multipos);
- }
-
- subs = addstate(l, state->out, subs, pim, off_arg);
- if (subs == NULL) {
- break;
- }
- // "subs" may have changed, need to set "sub" again.
- if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560
- sub = &subs->synt;
- } else {
- sub = &subs->norm;
- }
-
- if (REG_MULTI) {
- sub->list.multi[subidx] = save_multipos;
- } else {
- sub->list.line[subidx].end = save_ptr;
- }
- sub->in_use = save_in_use;
- break;
- }
- depth--;
- return subs;
-}
-
-/// Like addstate(), but the new state(s) are put at position "*ip".
-/// Used for zero-width matches, next state to use is the added one.
-/// This makes sure the order of states to be tried does not change, which
-/// matters for alternatives.
-///
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs pointers to subexpressions
-/// @param pim postponed look-behind match
-static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim,
- int *ip)
- FUNC_ATTR_NONNULL_ARG(1, 2, 5) FUNC_ATTR_WARN_UNUSED_RESULT
-{
- int tlen = l->n;
- int count;
- int listidx = *ip;
-
- // First add the state(s) at the end, so that we know how many there are.
- // Pass the listidx as offset (avoids adding another argument to
- // addstate()).
- regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET);
- if (r == NULL) {
- return NULL;
- }
-
- // when "*ip" was at the end of the list, nothing to do
- if (listidx + 1 == tlen) {
- return r;
- }
-
- // re-order to put the new state at the current position
- count = l->n - tlen;
- if (count == 0) {
- return r; // no state got added
- }
- if (count == 1) {
- // overwrite the current state
- l->t[listidx] = l->t[l->n - 1];
- } else if (count > 1) {
- if (l->n + count - 1 >= l->len) {
- // not enough space to move the new states, reallocate the list
- // and move the states to the right position
- const int newlen = l->len * 3 / 2 + 50;
- const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
-
- if ((long)(newsize >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- return NULL;
- }
- nfa_thread_T *const newl = xmalloc(newsize);
- l->len = newlen;
- memmove(&(newl[0]),
- &(l->t[0]),
- sizeof(nfa_thread_T) * (size_t)listidx);
- memmove(&(newl[listidx]),
- &(l->t[l->n - count]),
- sizeof(nfa_thread_T) * (size_t)count);
- memmove(&(newl[listidx + count]),
- &(l->t[listidx + 1]),
- sizeof(nfa_thread_T) * (size_t)(l->n - count - listidx - 1));
- xfree(l->t);
- l->t = newl;
- } else {
- // make space for new states, then move them from the
- // end to the current position
- memmove(&(l->t[listidx + count]),
- &(l->t[listidx + 1]),
- sizeof(nfa_thread_T) * (size_t)(l->n - listidx - 1));
- memmove(&(l->t[listidx]),
- &(l->t[l->n - 1]),
- sizeof(nfa_thread_T) * (size_t)count);
- }
- }
- l->n--;
- *ip = listidx - 1;
-
- return r;
-}
-
-// Check character class "class" against current character c.
-static int check_char_class(int class, int c)
-{
- switch (class) {
- case NFA_CLASS_ALNUM:
- if (c >= 1 && c < 128 && isalnum(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_ALPHA:
- if (c >= 1 && c < 128 && isalpha(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_BLANK:
- if (c == ' ' || c == '\t') {
- return OK;
- }
- break;
- case NFA_CLASS_CNTRL:
- if (c >= 1 && c <= 127 && iscntrl(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_DIGIT:
- if (ascii_isdigit(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_GRAPH:
- if (c >= 1 && c <= 127 && isgraph(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_LOWER:
- if (mb_islower(c) && c != 170 && c != 186) {
- return OK;
- }
- break;
- case NFA_CLASS_PRINT:
- if (vim_isprintc(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_PUNCT:
- if (c >= 1 && c < 128 && ispunct(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_SPACE:
- if ((c >= 9 && c <= 13) || (c == ' ')) {
- return OK;
- }
- break;
- case NFA_CLASS_UPPER:
- if (mb_isupper(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_XDIGIT:
- if (ascii_isxdigit(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_TAB:
- if (c == '\t') {
- return OK;
- }
- break;
- case NFA_CLASS_RETURN:
- if (c == '\r') {
- return OK;
- }
- break;
- case NFA_CLASS_BACKSPACE:
- if (c == '\b') {
- return OK;
- }
- break;
- case NFA_CLASS_ESCAPE:
- if (c == ESC) {
- return OK;
- }
- break;
- case NFA_CLASS_IDENT:
- if (vim_isIDc(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_KEYWORD:
- if (reg_iswordc(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_FNAME:
- if (vim_isfilec(c)) {
- return OK;
- }
- break;
-
- default:
- // should not be here :P
- siemsg(_(e_ill_char_class), (int64_t)class);
- return FAIL;
- }
- return FAIL;
-}
-
-/// Check for a match with subexpression "subidx".
-///
-/// @param sub pointers to subexpressions
-/// @param bytelen out: length of match in bytes
-///
-/// @return true if it matches.
-static int match_backref(regsub_T *sub, int subidx, int *bytelen)
-{
- int len;
-
- if (sub->in_use <= subidx) {
-retempty:
- // backref was not set, match an empty string
- *bytelen = 0;
- return true;
- }
-
- if (REG_MULTI) {
- if (sub->list.multi[subidx].start_lnum < 0
- || sub->list.multi[subidx].end_lnum < 0) {
- goto retempty;
- }
- if (sub->list.multi[subidx].start_lnum == rex.lnum
- && sub->list.multi[subidx].end_lnum == rex.lnum) {
- len = sub->list.multi[subidx].end_col
- - sub->list.multi[subidx].start_col;
- if (cstrncmp((char *)rex.line + sub->list.multi[subidx].start_col,
- (char *)rex.input, &len) == 0) {
- *bytelen = len;
- return true;
- }
- } else {
- if (match_with_backref(sub->list.multi[subidx].start_lnum,
- sub->list.multi[subidx].start_col,
- sub->list.multi[subidx].end_lnum,
- sub->list.multi[subidx].end_col,
- bytelen) == RA_MATCH) {
- return true;
- }
- }
- } else {
- if (sub->list.line[subidx].start == NULL
- || sub->list.line[subidx].end == NULL) {
- goto retempty;
- }
- len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start);
- if (cstrncmp((char *)sub->list.line[subidx].start, (char *)rex.input, &len) == 0) {
- *bytelen = len;
- return true;
- }
- }
- return false;
-}
-
-/// Check for a match with \z subexpression "subidx".
-///
-/// @param bytelen out: length of match in bytes
-///
-/// @return true if it matches.
-static int match_zref(int subidx, int *bytelen)
-{
- int len;
-
- cleanup_zsubexpr();
- if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) {
- // backref was not set, match an empty string
- *bytelen = 0;
- return true;
- }
-
- len = (int)strlen((char *)re_extmatch_in->matches[subidx]);
- if (cstrncmp((char *)re_extmatch_in->matches[subidx], (char *)rex.input, &len) == 0) {
- *bytelen = len;
- return true;
- }
- return false;
-}
-
-// Save list IDs for all NFA states of "prog" into "list".
-// Also reset the IDs to zero.
-// Only used for the recursive value lastlist[1].
-static void nfa_save_listids(nfa_regprog_T *prog, int *list)
-{
- int i;
- nfa_state_T *p;
-
- // Order in the list is reverse, it's a bit faster that way.
- p = &prog->state[0];
- for (i = prog->nstate; --i >= 0;) {
- list[i] = p->lastlist[1];
- p->lastlist[1] = 0;
- p++;
- }
-}
-
-// Restore list IDs from "list" to all NFA states.
-static void nfa_restore_listids(nfa_regprog_T *prog, int *list)
-{
- int i;
- nfa_state_T *p;
-
- p = &prog->state[0];
- for (i = prog->nstate; --i >= 0;) {
- p->lastlist[1] = list[i];
- p++;
- }
-}
-
-static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos)
-{
- if (op == 1) {
- return pos > val;
- }
- if (op == 2) {
- return pos < val;
- }
- return val == pos;
-}
-
-// Recursively call nfa_regmatch()
-// "pim" is NULL or contains info about a Postponed Invisible Match (start
-// position).
-static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog,
- regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len)
- FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7)
-{
- const int save_reginput_col = (int)(rex.input - rex.line);
- const int save_reglnum = rex.lnum;
- const int save_nfa_match = nfa_match;
- const int save_nfa_listid = rex.nfa_listid;
- save_se_T *const save_nfa_endp = nfa_endp;
- save_se_T endpos;
- save_se_T *endposp = NULL;
- int need_restore = false;
-
- if (pim != NULL) {
- // start at the position where the postponed match was
- if (REG_MULTI) {
- rex.input = rex.line + pim->end.pos.col;
- } else {
- rex.input = pim->end.ptr;
- }
- }
-
- if (state->c == NFA_START_INVISIBLE_BEFORE
- || state->c == NFA_START_INVISIBLE_BEFORE_FIRST
- || state->c == NFA_START_INVISIBLE_BEFORE_NEG
- || state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
- // The recursive match must end at the current position. When "pim" is
- // not NULL it specifies the current position.
- endposp = &endpos;
- if (REG_MULTI) {
- if (pim == NULL) {
- endpos.se_u.pos.col = (int)(rex.input - rex.line);
- endpos.se_u.pos.lnum = rex.lnum;
- } else {
- endpos.se_u.pos = pim->end.pos;
- }
- } else {
- if (pim == NULL) {
- endpos.se_u.ptr = rex.input;
- } else {
- endpos.se_u.ptr = pim->end.ptr;
- }
- }
-
- // Go back the specified number of bytes, or as far as the
- // start of the previous line, to try matching "\@<=" or
- // not matching "\@<!". This is very inefficient, limit the number of
- // bytes if possible.
- if (state->val <= 0) {
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(--rex.lnum);
- if (rex.line == NULL) {
- // can't go before the first line
- rex.line = (uint8_t *)reg_getline(++rex.lnum);
- }
- }
- rex.input = rex.line;
- } else {
- if (REG_MULTI && (int)(rex.input - rex.line) < state->val) {
- // Not enough bytes in this line, go to end of
- // previous line.
- rex.line = (uint8_t *)reg_getline(--rex.lnum);
- if (rex.line == NULL) {
- // can't go before the first line
- rex.line = (uint8_t *)reg_getline(++rex.lnum);
- rex.input = rex.line;
- } else {
- rex.input = rex.line + strlen((char *)rex.line);
- }
- }
- if ((int)(rex.input - rex.line) >= state->val) {
- rex.input -= state->val;
- rex.input -= utf_head_off((char *)rex.line, (char *)rex.input);
- } else {
- rex.input = rex.line;
- }
- }
- }
-
-#ifdef REGEXP_DEBUG
- if (log_fd != stderr) {
- fclose(log_fd);
- }
- log_fd = NULL;
-#endif
- // Have to clear the lastlist field of the NFA nodes, so that
- // nfa_regmatch() and addstate() can run properly after recursion.
- if (nfa_ll_index == 1) {
- // Already calling nfa_regmatch() recursively. Save the lastlist[1]
- // values and clear them.
- if (*listids == NULL || *listids_len < prog->nstate) {
- xfree(*listids);
- *listids = xmalloc(sizeof(**listids) * (size_t)prog->nstate);
- *listids_len = prog->nstate;
- }
- nfa_save_listids(prog, *listids);
- need_restore = true;
- // any value of rex.nfa_listid will do
- } else {
- // First recursive nfa_regmatch() call, switch to the second lastlist
- // entry. Make sure rex.nfa_listid is different from a previous
- // recursive call, because some states may still have this ID.
- nfa_ll_index++;
- if (rex.nfa_listid <= rex.nfa_alt_listid) {
- rex.nfa_listid = rex.nfa_alt_listid;
- }
- }
-
- // Call nfa_regmatch() to check if the current concat matches at this
- // position. The concat ends with the node NFA_END_INVISIBLE
- nfa_endp = endposp;
- const int result = nfa_regmatch(prog, state->out, submatch, m);
-
- if (need_restore) {
- nfa_restore_listids(prog, *listids);
- } else {
- nfa_ll_index--;
- rex.nfa_alt_listid = rex.nfa_listid;
- }
-
- // restore position in input text
- rex.lnum = save_reglnum;
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- }
- rex.input = rex.line + save_reginput_col;
- if (result != NFA_TOO_EXPENSIVE) {
- nfa_match = save_nfa_match;
- rex.nfa_listid = save_nfa_listid;
- }
- nfa_endp = save_nfa_endp;
-
-#ifdef REGEXP_DEBUG
- open_debug_log(result);
-#endif
-
- return result;
-}
-
-// Estimate the chance of a match with "state" failing.
-// empty match: 0
-// NFA_ANY: 1
-// specific character: 99
-static int failure_chance(nfa_state_T *state, int depth)
-{
- int c = state->c;
- int l, r;
-
- // detect looping
- if (depth > 4) {
- return 1;
- }
-
- switch (c) {
- case NFA_SPLIT:
- if (state->out->c == NFA_SPLIT || state->out1->c == NFA_SPLIT) {
- // avoid recursive stuff
- return 1;
- }
- // two alternatives, use the lowest failure chance
- l = failure_chance(state->out, depth + 1);
- r = failure_chance(state->out1, depth + 1);
- return l < r ? l : r;
-
- case NFA_ANY:
- // matches anything, unlikely to fail
- return 1;
-
- case NFA_MATCH:
- case NFA_MCLOSE:
- case NFA_ANY_COMPOSING:
- // empty match works always
- return 0;
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_FIRST:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_NEG_FIRST:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
- case NFA_START_PATTERN:
- // recursive regmatch is expensive, use low failure chance
- return 5;
-
- case NFA_BOL:
- case NFA_EOL:
- case NFA_BOF:
- case NFA_EOF:
- case NFA_NEWL:
- return 99;
-
- case NFA_BOW:
- case NFA_EOW:
- return 90;
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_NOPEN:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_NCLOSE:
- return failure_chance(state->out, depth + 1);
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- // backreferences don't match in many places
- return 94;
-
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_MARK_GT:
- case NFA_MARK_LT:
- case NFA_VISUAL:
- // before/after positions don't match very often
- return 85;
-
- case NFA_LNUM:
- return 90;
-
- case NFA_CURSOR:
- case NFA_COL:
- case NFA_VCOL:
- case NFA_MARK:
- // specific positions rarely match
- return 98;
-
- case NFA_COMPOSING:
- return 95;
-
- default:
- if (c > 0) {
- // character match fails often
- return 95;
- }
- }
-
- // something else, includes character classes
- return 50;
-}
-
-// Skip until the char "c" we know a match must start with.
-static int skip_to_start(int c, colnr_T *colp)
-{
- const uint8_t *const s = (uint8_t *)cstrchr((char *)rex.line + *colp, c);
- if (s == NULL) {
- return FAIL;
- }
- *colp = (int)(s - rex.line);
- return OK;
-}
-
-// Check for a match with match_text.
-// Called after skip_to_start() has found regstart.
-// Returns zero for no match, 1 for a match.
-static long find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text)
-{
-#define PTR2LEN(x) utf_ptr2len(x)
-
- colnr_T col = *startcol;
- int regstart_len = PTR2LEN((char *)rex.line + col);
-
- for (;;) {
- bool match = true;
- uint8_t *s1 = match_text;
- uint8_t *s2 = rex.line + col + regstart_len; // skip regstart
- while (*s1) {
- int c1_len = PTR2LEN((char *)s1);
- int c1 = utf_ptr2char((char *)s1);
- int c2_len = PTR2LEN((char *)s2);
- int c2 = utf_ptr2char((char *)s2);
-
- if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2)))
- || c1_len != c2_len) {
- match = false;
- break;
- }
- s1 += c1_len;
- s2 += c2_len;
- }
- if (match
- // check that no composing char follows
- && !utf_iscomposing(utf_ptr2char((char *)s2))) {
- cleanup_subexpr();
- if (REG_MULTI) {
- rex.reg_startpos[0].lnum = rex.lnum;
- rex.reg_startpos[0].col = col;
- rex.reg_endpos[0].lnum = rex.lnum;
- rex.reg_endpos[0].col = (colnr_T)(s2 - rex.line);
- } else {
- rex.reg_startp[0] = rex.line + col;
- rex.reg_endp[0] = s2;
- }
- *startcol = col;
- return 1L;
- }
-
- // Try finding regstart after the current match.
- col += regstart_len; // skip regstart
- if (skip_to_start(regstart, &col) == FAIL) {
- break;
- }
- }
-
- *startcol = col;
- return 0L;
-
-#undef PTR2LEN
-}
-
-static int nfa_did_time_out(void)
-{
- if (nfa_time_limit != NULL && profile_passed_limit(*nfa_time_limit)) {
- if (nfa_timed_out != NULL) {
- *nfa_timed_out = true;
- }
- return true;
- }
- return false;
-}
-
-/// Main matching routine.
-///
-/// Run NFA to determine whether it matches rex.input.
-///
-/// When "nfa_endp" is not NULL it is a required end-of-match position.
-///
-/// Return true if there is a match, false if there is no match,
-/// NFA_TOO_EXPENSIVE if we end up with too many states.
-/// When there is a match "submatch" contains the positions.
-///
-/// Note: Caller must ensure that: start != NULL.
-static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *submatch, regsubs_T *m)
- FUNC_ATTR_NONNULL_ARG(1, 2, 4)
-{
- int result = false;
- int flag = 0;
- bool go_to_nextline = false;
- nfa_thread_T *t;
- nfa_list_T list[2];
- int listidx;
- nfa_list_T *thislist;
- nfa_list_T *nextlist;
- int *listids = NULL;
- int listids_len = 0;
- nfa_state_T *add_state;
- bool add_here;
- int add_count;
- int add_off = 0;
- int toplevel = start->c == NFA_MOPEN;
- regsubs_T *r;
- // Some patterns may take a long time to match, especially when using
- // recursive_regmatch(). Allow interrupting them with CTRL-C.
- fast_breakcheck();
- if (got_int) {
- return false;
- }
- if (nfa_did_time_out()) {
- return false;
- }
-
-#ifdef NFA_REGEXP_DEBUG_LOG
- FILE *debug = fopen(NFA_REGEXP_DEBUG_LOG, "a");
-
- if (debug == NULL) {
- semsg("(NFA) COULD NOT OPEN %s!", NFA_REGEXP_DEBUG_LOG);
- return false;
- }
-#endif
- nfa_match = false;
-
- // Allocate memory for the lists of nodes.
- size_t size = (size_t)(prog->nstate + 1) * sizeof(nfa_thread_T);
- list[0].t = xmalloc(size);
- list[0].len = prog->nstate + 1;
- list[1].t = xmalloc(size);
- list[1].len = prog->nstate + 1;
-
-#ifdef REGEXP_DEBUG
- log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
- if (log_fd == NULL) {
- emsg(_(e_log_open_failed));
- log_fd = stderr;
- }
- fprintf(log_fd, "**********************************\n");
- nfa_set_code(start->c);
- fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n",
- abs(start->id), code);
- fprintf(log_fd, "**********************************\n");
-#endif
-
- thislist = &list[0];
- thislist->n = 0;
- thislist->has_pim = false;
- nextlist = &list[1];
- nextlist->n = 0;
- nextlist->has_pim = false;
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "(---) STARTSTATE first\n");
-#endif
- thislist->id = rex.nfa_listid + 1;
-
- // Inline optimized code for addstate(thislist, start, m, 0) if we know
- // it's the first MOPEN.
- if (toplevel) {
- if (REG_MULTI) {
- m->norm.list.multi[0].start_lnum = rex.lnum;
- m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line);
- m->norm.orig_start_col = m->norm.list.multi[0].start_col;
- } else {
- m->norm.list.line[0].start = rex.input;
- }
- m->norm.in_use = 1;
- r = addstate(thislist, start->out, m, NULL, 0);
- } else {
- r = addstate(thislist, start, m, NULL, 0);
- }
- if (r == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
-
-#define ADD_STATE_IF_MATCH(state) \
- if (result) { \
- add_state = (state)->out; \
- add_off = clen; \
- }
-
- // Run for each character.
- for (;;) {
- int curc = utf_ptr2char((char *)rex.input);
- int clen = utfc_ptr2len((char *)rex.input);
- if (curc == NUL) {
- clen = 0;
- go_to_nextline = false;
- }
-
- // swap lists
- thislist = &list[flag];
- nextlist = &list[flag ^= 1];
- nextlist->n = 0; // clear nextlist
- nextlist->has_pim = false;
- rex.nfa_listid++;
- if (prog->re_engine == AUTOMATIC_ENGINE
- && (rex.nfa_listid >= NFA_MAX_STATES)) {
- // Too many states, retry with old engine.
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
-
- thislist->id = rex.nfa_listid;
- nextlist->id = rex.nfa_listid + 1;
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "------------------------------------------\n");
- fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input);
- fprintf(log_fd,
- ">>> Advanced one character... Current char is %c (code %d) \n",
- curc,
- (int)curc);
- fprintf(log_fd, ">>> Thislist has %d states available: ", thislist->n);
- {
- int i;
-
- for (i = 0; i < thislist->n; i++) {
- fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
- }
- }
- fprintf(log_fd, "\n");
-#endif
-
-#ifdef NFA_REGEXP_DEBUG_LOG
- fprintf(debug, "\n-------------------\n");
-#endif
- // If the state lists are empty we can stop.
- if (thislist->n == 0) {
- break;
- }
-
- // compute nextlist
- for (listidx = 0; listidx < thislist->n; listidx++) {
- // If the list gets very long there probably is something wrong.
- // At least allow interrupting with CTRL-C.
- fast_breakcheck();
- if (got_int) {
- break;
- }
- if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
- nfa_time_count = 0;
- if (nfa_did_time_out()) {
- break;
- }
- }
- t = &thislist->t[listidx];
-
-#ifdef NFA_REGEXP_DEBUG_LOG
- nfa_set_code(t->state->c);
- fprintf(debug, "%s, ", code);
-#endif
-#ifdef REGEXP_DEBUG
- {
- int col;
-
- if (t->subs.norm.in_use <= 0) {
- col = -1;
- } else if (REG_MULTI) {
- col = t->subs.norm.list.multi[0].start_col;
- } else {
- col = (int)(t->subs.norm.list.line[0].start - rex.line);
- }
- nfa_set_code(t->state->c);
- fprintf(log_fd, "(%d) char %d %s (start col %d)%s... \n",
- abs(t->state->id), (int)t->state->c, code, col,
- pim_info(&t->pim));
- }
-#endif
-
- // Handle the possible codes of the current state.
- // The most important is NFA_MATCH.
- add_state = NULL;
- add_here = false;
- add_count = 0;
- switch (t->state->c) {
- case NFA_MATCH:
- // If the match is not at the start of the line, ends before a
- // composing characters and rex.reg_icombine is not set, that
- // is not really a match.
- if (!rex.reg_icombine
- && rex.input != rex.line
- && utf_iscomposing(curc)) {
- break;
- }
- nfa_match = true;
- copy_sub(&submatch->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&submatch->synt, &t->subs.synt);
- }
-#ifdef REGEXP_DEBUG
- log_subsexpr(&t->subs);
-#endif
- // Found the left-most longest match, do not look at any other
- // states at this position. When the list of states is going
- // to be empty quit without advancing, so that "rex.input" is
- // correct.
- if (nextlist->n == 0) {
- clen = 0;
- }
- goto nextchar;
-
- case NFA_END_INVISIBLE:
- case NFA_END_INVISIBLE_NEG:
- case NFA_END_PATTERN:
- // This is only encountered after a NFA_START_INVISIBLE or
- // NFA_START_INVISIBLE_BEFORE node.
- // They surround a zero-width group, used with "\@=", "\&",
- // "\@!", "\@<=" and "\@<!".
- // If we got here, it means that the current "invisible" group
- // finished successfully, so return control to the parent
- // nfa_regmatch(). For a look-behind match only when it ends
- // in the position in "nfa_endp".
- // Submatches are stored in *m, and used in the parent call.
-#ifdef REGEXP_DEBUG
- if (nfa_endp != NULL) {
- if (REG_MULTI) {
- fprintf(log_fd,
- "Current lnum: %d, endp lnum: %d;"
- " current col: %d, endp col: %d\n",
- (int)rex.lnum,
- (int)nfa_endp->se_u.pos.lnum,
- (int)(rex.input - rex.line),
- nfa_endp->se_u.pos.col);
- } else {
- fprintf(log_fd, "Current col: %d, endp col: %d\n",
- (int)(rex.input - rex.line),
- (int)(nfa_endp->se_u.ptr - rex.input));
- }
- }
-#endif
- // If "nfa_endp" is set it's only a match if it ends at
- // "nfa_endp"
- if (nfa_endp != NULL
- && (REG_MULTI
- ? (rex.lnum != nfa_endp->se_u.pos.lnum
- || (int)(rex.input - rex.line) != nfa_endp->se_u.pos.col)
- : rex.input != nfa_endp->se_u.ptr)) {
- break;
- }
- // do not set submatches for \@!
- if (t->state->c != NFA_END_INVISIBLE_NEG) {
- copy_sub(&m->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&m->synt, &t->subs.synt);
- }
- }
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "Match found:\n");
- log_subsexpr(m);
-#endif
- nfa_match = true;
- // See comment above at "goto nextchar".
- if (nextlist->n == 0) {
- clen = 0;
- }
- goto nextchar;
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_FIRST:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_NEG_FIRST:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "Failure chance invisible: %d, what follows: %d\n",
- failure_chance(t->state->out, 0),
- failure_chance(t->state->out1->out, 0));
-#endif
- // Do it directly if there already is a PIM or when
- // nfa_postprocess() detected it will work better.
- if (t->pim.result != NFA_PIM_UNUSED
- || t->state->c == NFA_START_INVISIBLE_FIRST
- || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || t->state->c == NFA_START_INVISIBLE_BEFORE_FIRST
- || t->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
- int in_use = m->norm.in_use;
-
- // Copy submatch info for the recursive call, opposite
- // of what happens on success below.
- copy_sub_off(&m->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&m->synt, &t->subs.synt);
- }
- // First try matching the invisible match, then what
- // follows.
- result = recursive_regmatch(t->state, NULL, prog, submatch, m,
- &listids, &listids_len);
- if (result == NFA_TOO_EXPENSIVE) {
- nfa_match = result;
- goto theend;
- }
-
- // for \@! and \@<! it is a match when the result is
- // false
- if (result != (t->state->c == NFA_START_INVISIBLE_NEG
- || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || t->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG
- || t->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
- // Copy submatch info from the recursive call
- copy_sub_off(&t->subs.norm, &m->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&t->subs.synt, &m->synt);
- }
- // If the pattern has \ze and it matched in the
- // sub pattern, use it.
- copy_ze_off(&t->subs.norm, &m->norm);
-
- // t->state->out1 is the corresponding
- // END_INVISIBLE node; Add its out to the current
- // list (zero-width match).
- add_here = true;
- add_state = t->state->out1->out;
- }
- m->norm.in_use = in_use;
- } else {
- nfa_pim_T pim;
-
- // First try matching what follows. Only if a match
- // is found verify the invisible match matches. Add a
- // nfa_pim_T to the following states, it contains info
- // about the invisible match.
- pim.state = t->state;
- pim.result = NFA_PIM_TODO;
- pim.subs.norm.in_use = 0;
- pim.subs.synt.in_use = 0;
- if (REG_MULTI) {
- pim.end.pos.col = (int)(rex.input - rex.line);
- pim.end.pos.lnum = rex.lnum;
- } else {
- pim.end.ptr = rex.input;
- }
- // t->state->out1 is the corresponding END_INVISIBLE
- // node; Add its out to the current list (zero-width
- // match).
- if (addstate_here(thislist, t->state->out1->out, &t->subs,
- &pim, &listidx) == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- break;
-
- case NFA_START_PATTERN: {
- nfa_state_T *skip = NULL;
-#ifdef REGEXP_DEBUG
- int skip_lid = 0;
-#endif
-
- // There is no point in trying to match the pattern if the
- // output state is not going to be added to the list.
- if (state_in_list(nextlist, t->state->out1->out, &t->subs)) {
- skip = t->state->out1->out;
-#ifdef REGEXP_DEBUG
- skip_lid = nextlist->id;
-#endif
- } else if (state_in_list(nextlist,
- t->state->out1->out->out, &t->subs)) {
- skip = t->state->out1->out->out;
-#ifdef REGEXP_DEBUG
- skip_lid = nextlist->id;
-#endif
- } else if (state_in_list(thislist,
- t->state->out1->out->out, &t->subs)) {
- skip = t->state->out1->out->out;
-#ifdef REGEXP_DEBUG
- skip_lid = thislist->id;
-#endif
- }
- if (skip != NULL) {
-#ifdef REGEXP_DEBUG
- nfa_set_code(skip->c);
- fprintf(log_fd,
- "> Not trying to match pattern, output state %d is already in list %d. char %d: %s\n", // NOLINT(whitespace/line_length)
- abs(skip->id), skip_lid, skip->c, code);
-#endif
- break;
- }
- // Copy submatch info to the recursive call, opposite of what
- // happens afterwards.
- copy_sub_off(&m->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&m->synt, &t->subs.synt);
- }
-
- // First try matching the pattern.
- result = recursive_regmatch(t->state, NULL, prog, submatch, m,
- &listids, &listids_len);
- if (result == NFA_TOO_EXPENSIVE) {
- nfa_match = result;
- goto theend;
- }
- if (result) {
- int bytelen;
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "NFA_START_PATTERN matches:\n");
- log_subsexpr(m);
-#endif
- // Copy submatch info from the recursive call
- copy_sub_off(&t->subs.norm, &m->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&t->subs.synt, &m->synt);
- }
- // Now we need to skip over the matched text and then
- // continue with what follows.
- if (REG_MULTI) {
- // TODO(RE): multi-line match
- bytelen = m->norm.list.multi[0].end_col
- - (int)(rex.input - rex.line);
- } else {
- bytelen = (int)(m->norm.list.line[0].end - rex.input);
- }
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "NFA_START_PATTERN length: %d\n", bytelen);
-#endif
- if (bytelen == 0) {
- // empty match, output of corresponding
- // NFA_END_PATTERN/NFA_SKIP to be used at current
- // position
- add_here = true;
- add_state = t->state->out1->out->out;
- } else if (bytelen <= clen) {
- // match current character, output of corresponding
- // NFA_END_PATTERN to be used at next position.
- add_state = t->state->out1->out->out;
- add_off = clen;
- } else {
- // skip over the matched characters, set character
- // count in NFA_SKIP
- add_state = t->state->out1->out;
- add_off = bytelen;
- add_count = bytelen - clen;
- }
- }
- break;
- }
-
- case NFA_BOL:
- if (rex.input == rex.line) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_EOL:
- if (curc == NUL) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_BOW:
- result = true;
-
- if (curc == NUL) {
- result = false;
- } else {
- int this_class;
-
- // Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- if (this_class <= 1) {
- result = false;
- } else if (reg_prev_class() == this_class) {
- result = false;
- }
- }
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_EOW:
- result = true;
- if (rex.input == rex.line) {
- result = false;
- } else {
- int this_class, prev_class;
-
- // Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- prev_class = reg_prev_class();
- if (this_class == prev_class
- || prev_class == 0 || prev_class == 1) {
- result = false;
- }
- }
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_BOF:
- if (rex.lnum == 0 && rex.input == rex.line
- && (!REG_MULTI || rex.reg_firstlnum == 1)) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_EOF:
- if (rex.lnum == rex.reg_maxline && curc == NUL) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_COMPOSING: {
- int mc = curc;
- int len = 0;
- nfa_state_T *end;
- nfa_state_T *sta;
- int cchars[MAX_MCO];
- int ccount = 0;
- int j;
-
- sta = t->state->out;
- len = 0;
- if (utf_iscomposing(sta->c)) {
- // Only match composing character(s), ignore base
- // character. Used for ".{composing}" and "{composing}"
- // (no preceding character).
- len += utf_char2len(mc);
- }
- if (rex.reg_icombine && len == 0) {
- // If \Z was present, then ignore composing characters.
- // When ignoring the base character this always matches.
- if (sta->c != curc) {
- result = FAIL;
- } else {
- result = OK;
- }
- while (sta->c != NFA_END_COMPOSING) {
- sta = sta->out;
- }
- } else if (len > 0 || mc == sta->c) {
- // Check base character matches first, unless ignored.
- if (len == 0) {
- len += utf_char2len(mc);
- sta = sta->out;
- }
-
- // We don't care about the order of composing characters.
- // Get them into cchars[] first.
- while (len < clen) {
- mc = utf_ptr2char((char *)rex.input + len);
- cchars[ccount++] = mc;
- len += utf_char2len(mc);
- if (ccount == MAX_MCO) {
- break;
- }
- }
-
- // Check that each composing char in the pattern matches a
- // composing char in the text. We do not check if all
- // composing chars are matched.
- result = OK;
- while (sta->c != NFA_END_COMPOSING) {
- for (j = 0; j < ccount; j++) {
- if (cchars[j] == sta->c) {
- break;
- }
- }
- if (j == ccount) {
- result = FAIL;
- break;
- }
- sta = sta->out;
- }
- } else {
- result = FAIL;
- }
-
- end = t->state->out1; // NFA_END_COMPOSING
- ADD_STATE_IF_MATCH(end);
- break;
- }
-
- case NFA_NEWL:
- if (curc == NUL && !rex.reg_line_lbr && REG_MULTI
- && rex.lnum <= rex.reg_maxline) {
- go_to_nextline = true;
- // Pass -1 for the offset, which means taking the position
- // at the start of the next line.
- add_state = t->state->out;
- add_off = -1;
- } else if (curc == '\n' && rex.reg_line_lbr) {
- // match \n as if it is an ordinary character
- add_state = t->state->out;
- add_off = 1;
- }
- break;
-
- case NFA_START_COLL:
- case NFA_START_NEG_COLL: {
- // What follows is a list of characters, until NFA_END_COLL.
- // One of them must match or none of them must match.
- nfa_state_T *state;
- int result_if_matched;
- int c1, c2;
-
- // Never match EOL. If it's part of the collection it is added
- // as a separate state with an OR.
- if (curc == NUL) {
- break;
- }
-
- state = t->state->out;
- result_if_matched = (t->state->c == NFA_START_COLL);
- for (;;) {
- if (state->c == NFA_END_COLL) {
- result = !result_if_matched;
- break;
- }
- if (state->c == NFA_RANGE_MIN) {
- c1 = state->val;
- state = state->out; // advance to NFA_RANGE_MAX
- c2 = state->val;
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "NFA_RANGE_MIN curc=%d c1=%d c2=%d\n",
- curc, c1, c2);
-#endif
- if (curc >= c1 && curc <= c2) {
- result = result_if_matched;
- break;
- }
- if (rex.reg_ic) {
- int curc_low = utf_fold(curc);
- int done = false;
-
- for (; c1 <= c2; c1++) {
- if (utf_fold(c1) == curc_low) {
- result = result_if_matched;
- done = true;
- break;
- }
- }
- if (done) {
- break;
- }
- }
- } else if (state->c < 0 ? check_char_class(state->c, curc)
- : (curc == state->c
- || (rex.reg_ic
- && utf_fold(curc) == utf_fold(state->c)))) {
- result = result_if_matched;
- break;
- }
- state = state->out;
- }
- if (result) {
- // next state is in out of the NFA_END_COLL, out1 of
- // START points to the END state
- add_state = t->state->out1->out;
- add_off = clen;
- }
- break;
- }
-
- case NFA_ANY:
- // Any char except '\0', (end of input) does not match.
- if (curc > 0) {
- add_state = t->state->out;
- add_off = clen;
- }
- break;
-
- case NFA_ANY_COMPOSING:
- // On a composing character skip over it. Otherwise do
- // nothing. Always matches.
- if (utf_iscomposing(curc)) {
- add_off = clen;
- } else {
- add_here = true;
- add_off = 0;
- }
- add_state = t->state->out;
- break;
-
- // Character classes like \a for alpha, \d for digit etc.
- case NFA_IDENT: // \i
- result = vim_isIDc(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SIDENT: // \I
- result = !ascii_isdigit(curc) && vim_isIDc(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_KWORD: // \k
- result = vim_iswordp_buf((char *)rex.input, rex.reg_buf);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SKWORD: // \K
- result = !ascii_isdigit(curc)
- && vim_iswordp_buf((char *)rex.input, rex.reg_buf);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_FNAME: // \f
- result = vim_isfilec(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SFNAME: // \F
- result = !ascii_isdigit(curc) && vim_isfilec(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_PRINT: // \p
- result = vim_isprintc(utf_ptr2char((char *)rex.input));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SPRINT: // \P
- result = !ascii_isdigit(curc) && vim_isprintc(utf_ptr2char((char *)rex.input));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_WHITE: // \s
- result = ascii_iswhite(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NWHITE: // \S
- result = curc != NUL && !ascii_iswhite(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_DIGIT: // \d
- result = ri_digit(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NDIGIT: // \D
- result = curc != NUL && !ri_digit(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_HEX: // \x
- result = ri_hex(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NHEX: // \X
- result = curc != NUL && !ri_hex(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_OCTAL: // \o
- result = ri_octal(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NOCTAL: // \O
- result = curc != NUL && !ri_octal(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_WORD: // \w
- result = ri_word(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NWORD: // \W
- result = curc != NUL && !ri_word(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_HEAD: // \h
- result = ri_head(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NHEAD: // \H
- result = curc != NUL && !ri_head(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_ALPHA: // \a
- result = ri_alpha(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NALPHA: // \A
- result = curc != NUL && !ri_alpha(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_LOWER: // \l
- result = ri_lower(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NLOWER: // \L
- result = curc != NUL && !ri_lower(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_UPPER: // \u
- result = ri_upper(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NUPPER: // \U
- result = curc != NUL && !ri_upper(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_LOWER_IC: // [a-z]
- result = ri_lower(curc) || (rex.reg_ic && ri_upper(curc));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NLOWER_IC: // [^a-z]
- result = curc != NUL
- && !(ri_lower(curc) || (rex.reg_ic && ri_upper(curc)));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_UPPER_IC: // [A-Z]
- result = ri_upper(curc) || (rex.reg_ic && ri_lower(curc));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NUPPER_IC: // [^A-Z]
- result = curc != NUL
- && !(ri_upper(curc) || (rex.reg_ic && ri_lower(curc)));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- // \1 .. \9 \z1 .. \z9
- {
- int subidx;
- int bytelen;
-
- if (t->state->c <= NFA_BACKREF9) {
- subidx = t->state->c - NFA_BACKREF1 + 1;
- result = match_backref(&t->subs.norm, subidx, &bytelen);
- } else {
- subidx = t->state->c - NFA_ZREF1 + 1;
- result = match_zref(subidx, &bytelen);
- }
-
- if (result) {
- if (bytelen == 0) {
- // empty match always works, output of NFA_SKIP to be
- // used next
- add_here = true;
- add_state = t->state->out->out;
- } else if (bytelen <= clen) {
- // match current character, jump ahead to out of
- // NFA_SKIP
- add_state = t->state->out->out;
- add_off = clen;
- } else {
- // skip over the matched characters, set character
- // count in NFA_SKIP
- add_state = t->state->out;
- add_off = bytelen;
- add_count = bytelen - clen;
- }
- }
- break;
- }
- case NFA_SKIP:
- // character of previous matching \1 .. \9 or \@>
- if (t->count - clen <= 0) {
- // end of match, go to what follows
- add_state = t->state->out;
- add_off = clen;
- } else {
- // add state again with decremented count
- add_state = t->state;
- add_off = 0;
- add_count = t->count - clen;
- }
- break;
-
- case NFA_LNUM:
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- assert(t->state->val >= 0
- && !((rex.reg_firstlnum > 0
- && rex.lnum > LONG_MAX - rex.reg_firstlnum)
- || (rex.reg_firstlnum < 0
- && rex.lnum < LONG_MIN + rex.reg_firstlnum))
- && rex.lnum + rex.reg_firstlnum >= 0);
- result = (REG_MULTI
- && nfa_re_num_cmp((uintmax_t)t->state->val,
- t->state->c - NFA_LNUM,
- (uintmax_t)(rex.lnum + rex.reg_firstlnum)));
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_COL:
- case NFA_COL_GT:
- case NFA_COL_LT:
- assert(t->state->val >= 0
- && rex.input >= rex.line
- && (uintmax_t)(rex.input - rex.line) <= UINTMAX_MAX - 1);
- result = nfa_re_num_cmp((uintmax_t)t->state->val,
- t->state->c - NFA_COL,
- (uintmax_t)(rex.input - rex.line + 1));
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_VCOL:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT: {
- int op = t->state->c - NFA_VCOL;
- colnr_T col = (colnr_T)(rex.input - rex.line);
-
- // Bail out quickly when there can't be a match, avoid the overhead of
- // win_linetabsize() on long lines.
- if (op != 1 && col > t->state->val * MB_MAXBYTES) {
- break;
- }
-
- result = false;
- win_T *wp = rex.reg_win == NULL ? curwin : rex.reg_win;
- if (op == 1 && col - 1 > t->state->val && col > 100) {
- long ts = wp->w_buffer->b_p_ts;
-
- // Guess that a character won't use more columns than 'tabstop',
- // with a minimum of 4.
- if (ts < 4) {
- ts = 4;
- }
- result = col > t->state->val * ts;
- }
- if (!result) {
- uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
- assert(t->state->val >= 0);
- result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1);
- }
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- }
- break;
-
- case NFA_MARK:
- case NFA_MARK_GT:
- case NFA_MARK_LT: {
- size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
- fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, t->state->val);
-
- // Line may have been freed, get it again.
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- rex.input = rex.line + col;
- }
-
- // Compare the mark position to the match position, if the mark
- // exists and mark is set in reg_buf.
- if (fm != NULL && fm->mark.lnum > 0) {
- pos_T *pos = &fm->mark;
- const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
- && pos->col == MAXCOL
- ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum))
- : pos->col;
-
- result = pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos_col == (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK
- : (pos_col < (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT);
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- }
- break;
- }
-
- case NFA_CURSOR:
- result = rex.reg_win != NULL
- && (rex.lnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum)
- && ((colnr_T)(rex.input - rex.line) == rex.reg_win->w_cursor.col);
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_VISUAL:
- result = reg_match_visual();
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_NOPEN:
- case NFA_ZSTART:
- // These states are only added to be able to bail out when
- // they are added again, nothing is to be done.
- break;
-
- default: // regular character
- {
- int c = t->state->c;
-
-#ifdef REGEXP_DEBUG
- if (c < 0) {
- siemsg("INTERNAL: Negative state char: %" PRId64, (int64_t)c);
- }
-#endif
- result = (c == curc);
-
- if (!result && rex.reg_ic) {
- result = utf_fold(c) == utf_fold(curc);
- }
-
- // If rex.reg_icombine is not set only skip over the character
- // itself. When it is set skip over composing characters.
- if (result && !rex.reg_icombine) {
- clen = utf_ptr2len((char *)rex.input);
- }
-
- ADD_STATE_IF_MATCH(t->state);
- break;
- }
- } // switch (t->state->c)
-
- if (add_state != NULL) {
- nfa_pim_T *pim;
- nfa_pim_T pim_copy;
-
- if (t->pim.result == NFA_PIM_UNUSED) {
- pim = NULL;
- } else {
- pim = &t->pim;
- }
-
- // Handle the postponed invisible match if the match might end
- // without advancing and before the end of the line.
- if (pim != NULL && (clen == 0 || match_follows(add_state, 0))) {
- if (pim->result == NFA_PIM_TODO) {
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "\n");
- fprintf(log_fd, "==================================\n");
- fprintf(log_fd, "Postponed recursive nfa_regmatch()\n");
- fprintf(log_fd, "\n");
-#endif
- result = recursive_regmatch(pim->state, pim, prog, submatch, m,
- &listids, &listids_len);
- pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH;
- // for \@! and \@<! it is a match when the result is
- // false
- if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
- || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
- // Copy submatch info from the recursive call
- copy_sub_off(&pim->subs.norm, &m->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&pim->subs.synt, &m->synt);
- }
- }
- } else {
- result = (pim->result == NFA_PIM_MATCH);
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "\n");
- fprintf(log_fd,
- "Using previous recursive nfa_regmatch() result, result == %d\n",
- pim->result);
- fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "false");
- fprintf(log_fd, "\n");
-#endif
- }
-
- // for \@! and \@<! it is a match when result is false
- if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
- || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
- // Copy submatch info from the recursive call
- copy_sub_off(&t->subs.norm, &pim->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&t->subs.synt, &pim->subs.synt);
- }
- } else {
- // look-behind match failed, don't add the state
- continue;
- }
-
- // Postponed invisible match was handled, don't add it to
- // following states.
- pim = NULL;
- }
-
- // If "pim" points into l->t it will become invalid when
- // adding the state causes the list to be reallocated. Make a
- // local copy to avoid that.
- if (pim == &t->pim) {
- copy_pim(&pim_copy, pim);
- pim = &pim_copy;
- }
-
- if (add_here) {
- r = addstate_here(thislist, add_state, &t->subs, pim, &listidx);
- } else {
- r = addstate(nextlist, add_state, &t->subs, pim, add_off);
- if (add_count > 0) {
- nextlist->t[nextlist->n - 1].count = add_count;
- }
- }
- if (r == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- } // for (thislist = thislist; thislist->state; thislist++)
-
- // Look for the start of a match in the current position by adding the
- // start state to the list of states.
- // The first found match is the leftmost one, thus the order of states
- // matters!
- // Do not add the start state in recursive calls of nfa_regmatch(),
- // because recursive calls should only start in the first position.
- // Unless "nfa_endp" is not NULL, then we match the end position.
- // Also don't start a match past the first line.
- if (!nfa_match
- && ((toplevel
- && rex.lnum == 0
- && clen != 0
- && (rex.reg_maxcol == 0
- || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol))
- || (nfa_endp != NULL
- && (REG_MULTI
- ? (rex.lnum < nfa_endp->se_u.pos.lnum
- || (rex.lnum == nfa_endp->se_u.pos.lnum
- && (int)(rex.input - rex.line)
- < nfa_endp->se_u.pos.col))
- : rex.input < nfa_endp->se_u.ptr)))) {
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "(---) STARTSTATE\n");
-#endif
- // Inline optimized code for addstate() if we know the state is
- // the first MOPEN.
- if (toplevel) {
- int add = true;
-
- if (prog->regstart != NUL && clen != 0) {
- if (nextlist->n == 0) {
- colnr_T col = (colnr_T)(rex.input - rex.line) + clen;
-
- // Nextlist is empty, we can skip ahead to the
- // character that must appear at the start.
- if (skip_to_start(prog->regstart, &col) == FAIL) {
- break;
- }
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, " Skipping ahead %d bytes to regstart\n",
- col - ((colnr_T)(rex.input - rex.line) + clen));
-#endif
- rex.input = rex.line + col - clen;
- } else {
- // Checking if the required start character matches is
- // cheaper than adding a state that won't match.
- const int c = utf_ptr2char((char *)rex.input + clen);
- if (c != prog->regstart
- && (!rex.reg_ic
- || utf_fold(c) != utf_fold(prog->regstart))) {
-#ifdef REGEXP_DEBUG
- fprintf(log_fd,
- " Skipping start state, regstart does not match\n");
-#endif
- add = false;
- }
- }
- }
-
- if (add) {
- if (REG_MULTI) {
- m->norm.list.multi[0].start_col =
- (colnr_T)(rex.input - rex.line) + clen;
- m->norm.orig_start_col =
- m->norm.list.multi[0].start_col;
- } else {
- m->norm.list.line[0].start = rex.input + clen;
- }
- if (addstate(nextlist, start->out, m, NULL, clen) == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- } else {
- if (addstate(nextlist, start, m, NULL, clen) == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- }
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, ">>> Thislist had %d states available: ", thislist->n);
- {
- int i;
-
- for (i = 0; i < thislist->n; i++) {
- fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
- }
- }
- fprintf(log_fd, "\n");
-#endif
-
-nextchar:
- // Advance to the next character, or advance to the next line, or
- // finish.
- if (clen != 0) {
- rex.input += clen;
- } else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI
- && rex.lnum < nfa_endp->se_u.pos.lnum)) {
- reg_nextline();
- } else {
- break;
- }
-
- // Allow interrupting with CTRL-C.
- line_breakcheck();
- if (got_int) {
- break;
- }
- // Check for timeout once every twenty times to avoid overhead.
- if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
- nfa_time_count = 0;
- if (nfa_did_time_out()) {
- break;
- }
- }
- }
-
-#ifdef REGEXP_DEBUG
- if (log_fd != stderr) {
- fclose(log_fd);
- }
- log_fd = NULL;
-#endif
-
-theend:
- // Free memory
- xfree(list[0].t);
- xfree(list[1].t);
- xfree(listids);
-#undef ADD_STATE_IF_MATCH
-#ifdef NFA_REGEXP_DEBUG_LOG
- fclose(debug);
-#endif
-
- return nfa_match;
-}
-
-/// Try match of "prog" with at rex.line["col"].
-///
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return <= 0 for failure, number of lines contained in the match otherwise.
-static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
-{
- int i;
- regsubs_T subs, m;
- nfa_state_T *start = prog->start;
-#ifdef REGEXP_DEBUG
- FILE *f;
-#endif
-
- rex.input = rex.line + col;
- nfa_time_limit = tm;
- nfa_timed_out = timed_out;
- nfa_time_count = 0;
-
-#ifdef REGEXP_DEBUG
- f = fopen(NFA_REGEXP_RUN_LOG, "a");
- if (f != NULL) {
- fprintf(f,
- "\n\n\t=======================================================\n");
-# ifdef REGEXP_DEBUG
- fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr);
-# endif
- fprintf(f, "\tInput text is \"%s\" \n", rex.input);
- fprintf(f, "\t=======================================================\n\n");
- nfa_print_state(f, start);
- fprintf(f, "\n\n");
- fclose(f);
- } else {
- emsg("Could not open temporary log file for writing");
- }
-#endif
-
- clear_sub(&subs.norm);
- clear_sub(&m.norm);
- clear_sub(&subs.synt);
- clear_sub(&m.synt);
-
- int result = nfa_regmatch(prog, start, &subs, &m);
- if (!result) {
- return 0;
- } else if (result == NFA_TOO_EXPENSIVE) {
- return result;
- }
-
- cleanup_subexpr();
- if (REG_MULTI) {
- for (i = 0; i < subs.norm.in_use; i++) {
- rex.reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum;
- rex.reg_startpos[i].col = subs.norm.list.multi[i].start_col;
-
- rex.reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum;
- rex.reg_endpos[i].col = subs.norm.list.multi[i].end_col;
- }
- if (rex.reg_mmatch != NULL) {
- rex.reg_mmatch->rmm_matchcol = subs.norm.orig_start_col;
- }
-
- if (rex.reg_startpos[0].lnum < 0) {
- rex.reg_startpos[0].lnum = 0;
- rex.reg_startpos[0].col = col;
- }
- if (rex.reg_endpos[0].lnum < 0) {
- // pattern has a \ze but it didn't match, use current end
- rex.reg_endpos[0].lnum = rex.lnum;
- rex.reg_endpos[0].col = (int)(rex.input - rex.line);
- } else {
- // Use line number of "\ze".
- rex.lnum = rex.reg_endpos[0].lnum;
- }
- } else {
- for (i = 0; i < subs.norm.in_use; i++) {
- rex.reg_startp[i] = subs.norm.list.line[i].start;
- rex.reg_endp[i] = subs.norm.list.line[i].end;
- }
-
- if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = rex.line + col;
- }
- if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = rex.input;
- }
- }
-
- // Package any found \z(...\) matches for export. Default is none.
- unref_extmatch(re_extmatch_out);
- re_extmatch_out = NULL;
-
- if (prog->reghasz == REX_SET) {
- cleanup_zsubexpr();
- re_extmatch_out = make_extmatch();
- // Loop over \z1, \z2, etc. There is no \z0.
- for (i = 1; i < subs.synt.in_use; i++) {
- if (REG_MULTI) {
- struct multipos *mpos = &subs.synt.list.multi[i];
-
- // Only accept single line matches that are valid.
- if (mpos->start_lnum >= 0
- && mpos->start_lnum == mpos->end_lnum
- && mpos->end_col >= mpos->start_col) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)reg_getline(mpos->start_lnum) + mpos->start_col,
- (size_t)(mpos->end_col - mpos->start_col));
- }
- } else {
- struct linepos *lpos = &subs.synt.list.line[i];
-
- if (lpos->start != NULL && lpos->end != NULL) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)lpos->start, (size_t)(lpos->end - lpos->start));
- }
- }
- }
- }
-
- return 1 + rex.lnum;
-}
-
-/// Match a regexp against a string ("line" points to the string) or multiple
-/// lines (if "line" is NULL, use reg_getline()).
-///
-/// @param line String in which to search or NULL
-/// @param startcol Column to start looking for match
-/// @param tm Timeout limit or NULL
-/// @param timed_out Flag set on timeout or NULL
-///
-/// @return <= 0 if there is no match and number of lines contained in the
-/// match otherwise.
-static long nfa_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
-{
- nfa_regprog_T *prog;
- long retval = 0L;
- colnr_T col = startcol;
-
- if (REG_MULTI) {
- prog = (nfa_regprog_T *)rex.reg_mmatch->regprog;
- line = (uint8_t *)reg_getline((linenr_T)0); // relative to the cursor
- rex.reg_startpos = rex.reg_mmatch->startpos;
- rex.reg_endpos = rex.reg_mmatch->endpos;
- } else {
- prog = (nfa_regprog_T *)rex.reg_match->regprog;
- rex.reg_startp = (uint8_t **)rex.reg_match->startp;
- rex.reg_endp = (uint8_t **)rex.reg_match->endp;
- }
-
- // Be paranoid...
- if (prog == NULL || line == NULL) {
- iemsg(_(e_null));
- goto theend;
- }
-
- // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
- if (prog->regflags & RF_ICASE) {
- rex.reg_ic = true;
- } else if (prog->regflags & RF_NOICASE) {
- rex.reg_ic = false;
- }
-
- // If pattern contains "\Z" overrule value of rex.reg_icombine
- if (prog->regflags & RF_ICOMBINE) {
- rex.reg_icombine = true;
- }
-
- rex.line = line;
- rex.lnum = 0; // relative to line
-
- rex.nfa_has_zend = prog->has_zend;
- rex.nfa_has_backref = prog->has_backref;
- rex.nfa_nsubexpr = prog->nsubexp;
- rex.nfa_listid = 1;
- rex.nfa_alt_listid = 2;
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = prog->pattern;
-#endif
-
- if (prog->reganch && col > 0) {
- return 0L;
- }
-
- rex.need_clear_subexpr = true;
- // Clear the external match subpointers if necessary.
- if (prog->reghasz == REX_SET) {
- rex.nfa_has_zsubexpr = true;
- rex.need_clear_zsubexpr = true;
- } else {
- rex.nfa_has_zsubexpr = false;
- rex.need_clear_zsubexpr = false;
- }
-
- if (prog->regstart != NUL) {
- // Skip ahead until a character we know the match must start with.
- // When there is none there is no match.
- if (skip_to_start(prog->regstart, &col) == FAIL) {
- return 0L;
- }
-
- // If match_text is set it contains the full text that must match.
- // Nothing else to try. Doesn't handle combining chars well.
- if (prog->match_text != NULL && !rex.reg_icombine) {
- retval = find_match_text(&col, prog->regstart, prog->match_text);
- if (REG_MULTI) {
- rex.reg_mmatch->rmm_matchcol = col;
- } else {
- rex.reg_match->rm_matchcol = col;
- }
- return retval;
- }
- }
-
- // If the start column is past the maximum column: no need to try.
- if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
- goto theend;
- }
-
- // Set the "nstate" used by nfa_regcomp() to zero to trigger an error when
- // it's accidentally used during execution.
- nstate = 0;
- for (int i = 0; i < prog->nstate; i++) {
- prog->state[i].id = i;
- prog->state[i].lastlist[0] = 0;
- prog->state[i].lastlist[1] = 0;
- }
-
- retval = nfa_regtry(prog, col, tm, timed_out);
-
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = NULL;
-#endif
-
-theend:
- if (retval > 0) {
- // Make sure the end is never before the start. Can happen when \zs and
- // \ze are used.
- if (REG_MULTI) {
- const lpos_T *const start = &rex.reg_mmatch->startpos[0];
- const lpos_T *const end = &rex.reg_mmatch->endpos[0];
-
- if (end->lnum < start->lnum
- || (end->lnum == start->lnum && end->col < start->col)) {
- rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
- }
- } else {
- if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
- rex.reg_match->endp[0] = rex.reg_match->startp[0];
- }
-
- // startpos[0] may be set by "\zs", also return the column where
- // the whole pattern matched.
- rex.reg_match->rm_matchcol = col;
- }
- }
-
- return retval;
-}
-
-// Compile a regular expression into internal code for the NFA matcher.
-// Returns the program in allocated space. Returns NULL for an error.
-static regprog_T *nfa_regcomp(uint8_t *expr, int re_flags)
-{
- nfa_regprog_T *prog = NULL;
- int *postfix;
-
- if (expr == NULL) {
- return NULL;
- }
-
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = expr;
-#endif
- nfa_re_flags = re_flags;
-
- init_class_tab();
-
- nfa_regcomp_start(expr, re_flags);
-
- // Build postfix form of the regexp. Needed to build the NFA
- // (and count its size).
- postfix = re2post();
- if (postfix == NULL) {
- goto fail; // Cascaded (syntax?) error
- }
-
- // In order to build the NFA, we parse the input regexp twice:
- // 1. first pass to count size (so we can allocate space)
- // 2. second to emit code
-#ifdef REGEXP_DEBUG
- {
- FILE *f = fopen(NFA_REGEXP_RUN_LOG, "a");
-
- if (f != NULL) {
- fprintf(f,
- "\n*****************************\n\n\n\n\t"
- "Compiling regexp \"%s\"... hold on !\n",
- expr);
- fclose(f);
- }
- }
-#endif
-
- // PASS 1
- // Count number of NFA states in "nstate". Do not build the NFA.
- post2nfa(postfix, post_ptr, true);
-
- // allocate the regprog with space for the compiled regexp
- size_t prog_size = sizeof(nfa_regprog_T) + sizeof(nfa_state_T) * (size_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) {
- goto fail;
- }
- prog->regflags = regflags;
- prog->engine = &nfa_regengine;
- prog->nstate = nstate;
- prog->has_zend = rex.nfa_has_zend;
- prog->has_backref = rex.nfa_has_backref;
- prog->nsubexp = regnpar;
-
- nfa_postprocess(prog);
-
- prog->reganch = nfa_get_reganch(prog->start, 0);
- prog->regstart = nfa_get_regstart(prog->start, 0);
- prog->match_text = nfa_get_match_text(prog->start);
-
-#ifdef REGEXP_DEBUG
- nfa_postfix_dump(expr, OK);
- nfa_dump(prog);
-#endif
- // Remember whether this pattern has any \z specials in it.
- prog->reghasz = re_has_z;
- prog->pattern = xstrdup((char *)expr);
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = NULL;
-#endif
-
-out:
- xfree(post_start);
- post_start = post_ptr = post_end = NULL;
- state_ptr = NULL;
- return (regprog_T *)prog;
-
-fail:
- XFREE_CLEAR(prog);
-#ifdef REGEXP_DEBUG
- nfa_postfix_dump(expr, FAIL);
- nfa_regengine.expr = NULL;
-#endif
- goto out;
-}
-
-// Free a compiled regexp program, returned by nfa_regcomp().
-static void nfa_regfree(regprog_T *prog)
-{
- if (prog == NULL) {
- return;
- }
-
- xfree(((nfa_regprog_T *)prog)->match_text);
- xfree(((nfa_regprog_T *)prog)->pattern);
- xfree(prog);
-}
-
-/// Match a regexp against a string.
-/// "rmp->regprog" is a compiled regexp as returned by nfa_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
-///
-/// @param line string to match against
-/// @param col column to start looking for match
-///
-/// @return <= 0 for failure, number of lines contained in the match otherwise.
-static int nfa_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
-{
- rex.reg_match = rmp;
- rex.reg_mmatch = NULL;
- rex.reg_maxline = 0;
- rex.reg_line_lbr = line_lbr;
- rex.reg_buf = curbuf;
- rex.reg_win = NULL;
- rex.reg_ic = rmp->rm_ic;
- rex.reg_icombine = false;
- rex.reg_maxcol = 0;
- return (int)nfa_regexec_both(line, col, NULL, NULL);
-}
-
-/// Matches a regexp against multiple lines.
-/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-///
-/// @param win Window in which to search or NULL
-/// @param buf Buffer in which to search
-/// @param lnum Number of line to start looking for match
-/// @param col Column to start looking for match
-/// @param tm Timeout limit or NULL
-/// @param timed_out Flag set on timeout or NULL
-///
-/// @return <= 0 if there is no match and number of lines contained in the match
-/// otherwise.
-///
-/// @note The body is the same as bt_regexec() except for nfa_regexec_both()
-///
-/// @warning
-/// Match may actually be in another line. e.g.:
-/// when r.e. is \nc, cursor is at 'a' and the text buffer looks like
-///
-/// @par
-///
-/// +-------------------------+
-/// |a |
-/// |b |
-/// |c |
-/// | |
-/// +-------------------------+
-///
-/// @par
-/// then nfa_regexec_multi() returns 3. while the original vim_regexec_multi()
-/// returns 0 and a second call at line 2 will return 2.
-///
-/// @par
-/// FIXME if this behavior is not compatible.
-static long nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
- proftime_T *tm, int *timed_out)
-{
- init_regexec_multi(rmp, win, buf, lnum);
- return nfa_regexec_both(NULL, col, tm, timed_out);
-}
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index 24500b80b9..38d3942126 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file runtime.c
///
/// Management of runtime files (including packages)
@@ -9,43 +6,58 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <uv.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
+#include "nvim/hashtab.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/path.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
+#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
+#ifdef USE_CRNL
+# include "nvim/highlight.h"
+#endif
/// Structure used to store info for each sourced file.
/// It is shared between do_source() and getsourceline().
@@ -56,7 +68,7 @@ struct source_cookie {
char *nextline; ///< if not NULL: line that was read ahead
linenr_T sourcing_lnum; ///< line number of the source file
int finished; ///< ":finish" used
-#if defined(USE_CRNL)
+#ifdef USE_CRNL
int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS
bool error; ///< true if LF found after CR-LF
#endif
@@ -67,12 +79,26 @@ struct source_cookie {
vimconv_T conv; ///< type of conversion
};
+typedef struct {
+ char *path;
+ bool after;
+ TriState has_lua;
+} SearchPathItem;
+
+typedef kvec_t(SearchPathItem) RuntimeSearchPath;
+typedef kvec_t(char *) CharVec;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "runtime.c.generated.h"
#endif
garray_T exestack = { 0, 0, sizeof(estack_T), 50, NULL };
-garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL };
+garray_T script_items = { 0, 0, sizeof(scriptitem_T *), 20, NULL };
+
+/// The names of packages that once were loaded are remembered.
+static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL };
+
+static int last_current_SID_seq = 0;
/// Initialize the execution stack.
void estack_init(void)
@@ -143,8 +169,8 @@ char *estack_sfile(estack_arg_T which)
? &entry->es_info.ufunc->uf_script_ctx
: &entry->es_info.aucmd->script_ctx);
return def_ctx->sc_sid > 0
- ? xstrdup((SCRIPT_ITEM(def_ctx->sc_sid).sn_name))
- : NULL;
+ ? xstrdup((SCRIPT_ITEM(def_ctx->sc_sid)->sn_name))
+ : NULL;
} else if (entry->es_type == ETYPE_SCRIPT) {
return xstrdup(entry->es_name);
}
@@ -177,8 +203,8 @@ char *estack_sfile(estack_arg_T which)
len += strlen(type_name);
ga_grow(&ga, (int)len);
linenr_T lnum = idx == exestack.ga_len - 1
- ? which == ESTACK_STACK ? SOURCING_LNUM : 0
- : entry->es_lnum;
+ ? which == ESTACK_STACK ? SOURCING_LNUM : 0
+ : entry->es_lnum;
char *dots = idx == exestack.ga_len - 1 ? "" : "..";
if (lnum == 0) {
// For the bottom entry of <sfile>: do not add the line number,
@@ -208,50 +234,132 @@ void runtime_init(void)
uv_mutex_init(&runtime_search_path_mutex);
}
-/// ":runtime [what] {name}"
+/// Get DIP_ flags from the [where] argument of a :runtime command.
+/// "*argp" is advanced to after the [where] argument.
+static int get_runtime_cmd_flags(char **argp, size_t where_len)
+{
+ char *arg = *argp;
+
+ if (where_len == 0) {
+ return 0;
+ }
+
+ if (strncmp(arg, "START", where_len) == 0) {
+ *argp = skipwhite(arg + where_len);
+ return DIP_START + DIP_NORTP;
+ }
+ if (strncmp(arg, "OPT", where_len) == 0) {
+ *argp = skipwhite(arg + where_len);
+ return DIP_OPT + DIP_NORTP;
+ }
+ if (strncmp(arg, "PACK", where_len) == 0) {
+ *argp = skipwhite(arg + where_len);
+ return DIP_START + DIP_OPT + DIP_NORTP;
+ }
+ if (strncmp(arg, "ALL", where_len) == 0) {
+ *argp = skipwhite(arg + where_len);
+ return DIP_START + DIP_OPT;
+ }
+
+ return 0;
+}
+
+/// ":runtime [where] {name}"
void ex_runtime(exarg_T *eap)
{
char *arg = eap->arg;
- char *p = skiptowhite(arg);
- size_t len = (size_t)(p - arg);
int flags = eap->forceit ? DIP_ALL : 0;
+ char *p = skiptowhite(arg);
+ flags += get_runtime_cmd_flags(&arg, (size_t)(p - arg));
+ assert(arg != NULL); // suppress clang false positive
+ source_runtime(arg, flags);
+}
- if (strncmp(arg, "START", len) == 0) {
- flags += DIP_START + DIP_NORTP;
- arg = skipwhite(arg + len);
- } else if (strncmp(arg, "OPT", len) == 0) {
- flags += DIP_OPT + DIP_NORTP;
- arg = skipwhite(arg + len);
- } else if (strncmp(arg, "PACK", len) == 0) {
- flags += DIP_START + DIP_OPT + DIP_NORTP;
- arg = skipwhite(arg + len);
- } else if (strncmp(arg, "ALL", len) == 0) {
- flags += DIP_START + DIP_OPT;
- arg = skipwhite(arg + len);
+static int runtime_expand_flags;
+
+/// Set the completion context for the :runtime command.
+void set_context_in_runtime_cmd(expand_T *xp, const char *arg)
+{
+ char *p = skiptowhite(arg);
+ runtime_expand_flags
+ = *p != NUL ? get_runtime_cmd_flags((char **)&arg, (size_t)(p - arg)) : 0;
+ // Skip to the last argument.
+ while (*(p = skiptowhite_esc(arg)) != NUL) {
+ if (runtime_expand_flags == 0) {
+ // When there are multiple arguments and [where] is not specified,
+ // use an unrelated non-zero flag to avoid expanding [where].
+ runtime_expand_flags = DIP_ALL;
+ }
+ arg = skipwhite(p);
}
+ xp->xp_context = EXPAND_RUNTIME;
+ xp->xp_pattern = (char *)arg;
+}
- source_runtime(arg, flags);
+/// Source all .vim and .lua files in "fnames" with .vim files being sourced first.
+static bool source_callback_vim_lua(int num_fnames, char **fnames, bool all, void *cookie)
+{
+ bool did_one = false;
+
+ for (int i = 0; i < num_fnames; i++) {
+ if (path_with_extension(fnames[i], "vim")) {
+ (void)do_source(fnames[i], false, DOSO_NONE, cookie);
+ did_one = true;
+ if (!all) {
+ return true;
+ }
+ }
+ }
+
+ for (int i = 0; i < num_fnames; i++) {
+ if (path_with_extension(fnames[i], "lua")) {
+ (void)do_source(fnames[i], false, DOSO_NONE, cookie);
+ did_one = true;
+ if (!all) {
+ return true;
+ }
+ }
+ }
+
+ return did_one;
}
-static void source_callback(char *fname, void *cookie)
+/// Source all files in "fnames" with .vim files sourced first, .lua files
+/// sourced second, and any remaining files sourced last.
+static bool source_callback(int num_fnames, char **fnames, bool all, void *cookie)
{
- (void)do_source(fname, false, DOSO_NONE);
+ bool did_one = source_callback_vim_lua(num_fnames, fnames, all, cookie);
+
+ if (!all && did_one) {
+ return true;
+ }
+
+ for (int i = 0; i < num_fnames; i++) {
+ if (!path_with_extension(fnames[i], "vim")
+ && !path_with_extension(fnames[i], "lua")) {
+ (void)do_source(fnames[i], false, DOSO_NONE, cookie);
+ did_one = true;
+ if (!all) {
+ return true;
+ }
+ }
+ }
+
+ return did_one;
}
-/// Find the file "name" in all directories in "path" and invoke
+/// Find the patterns in "name" in all directories in "path" and invoke
/// "callback(fname, cookie)".
-/// "name" can contain wildcards.
+/// "prefix" is prepended to each pattern in "name".
/// When "flags" has DIP_ALL: source all files, otherwise only the first one.
/// When "flags" has DIP_DIR: find directories instead of files.
/// When "flags" has DIP_ERR: give an error message if there is no match.
///
-/// return FAIL when no file could be sourced, OK otherwise.
-int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie)
+/// Return FAIL when no file could be sourced, OK otherwise.
+int do_in_path(const char *path, const char *prefix, char *name, int flags,
+ DoInRuntimepathCB callback, void *cookie)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
- char *tail;
- int num_files;
- char **files;
- int i;
bool did_one = false;
// Make a copy of 'runtimepath'. Invoking the callback may change the
@@ -259,15 +367,22 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
char *rtp_copy = xstrdup(path);
char *buf = xmallocz(MAXPATHL);
{
+ char *tail;
if (p_verbose > 10 && name != NULL) {
verbose_enter();
- smsg(_("Searching for \"%s\" in \"%s\""), name, path);
+ if (*prefix != NUL) {
+ smsg(0, _("Searching for \"%s\" under \"%s\" in \"%s\""), name, prefix, path);
+ } else {
+ smsg(0, _("Searching for \"%s\" in \"%s\""), name, path);
+ }
verbose_leave();
}
+ bool do_all = (flags & DIP_ALL) != 0;
+
// Loop over all entries in 'runtimepath'.
char *rtp = rtp_copy;
- while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) {
+ while (*rtp != NUL && (do_all || !did_one)) {
// Copy the path from 'runtimepath' to buf[].
copy_option_part(&rtp, buf, MAXPATHL, ",");
size_t buflen = strlen(buf);
@@ -283,39 +398,31 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
}
if (name == NULL) {
- (*callback)(buf, cookie);
+ (*callback)(1, &buf, do_all, cookie);
did_one = true;
- } else if (buflen + strlen(name) + 2 < MAXPATHL) {
+ } else if (buflen + 2 + strlen(prefix) + strlen(name) < MAXPATHL) {
add_pathsep(buf);
+ STRCAT(buf, prefix);
tail = buf + strlen(buf);
// Loop over all patterns in "name"
char *np = name;
- while (*np != NUL && ((flags & DIP_ALL) || !did_one)) {
+ while (*np != NUL && (do_all || !did_one)) {
// Append the pattern from "name" to buf[].
assert(MAXPATHL >= (tail - buf));
copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t ");
if (p_verbose > 10) {
verbose_enter();
- smsg(_("Searching for \"%s\""), buf);
+ smsg(0, _("Searching for \"%s\""), buf);
verbose_leave();
}
int ew_flags = ((flags & DIP_DIR) ? EW_DIR : EW_FILE)
- | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0;
-
- // Expand wildcards, invoke the callback for each match.
- if (gen_expand_wildcards(1, &buf, &num_files, &files, ew_flags) == OK) {
- for (i = 0; i < num_files; i++) {
- (*callback)(files[i], cookie);
- did_one = true;
- if (!(flags & DIP_ALL)) {
- break;
- }
- }
- FreeWild(num_files, files);
- }
+ | ((flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0);
+
+ did_one |= gen_expand_wildcards_and_cb(1, &buf, ew_flags, do_all, callback,
+ cookie) == OK;
}
}
}
@@ -329,7 +436,7 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
semsg(_(e_dirnotf), basepath, name);
} else if (p_verbose > 1) {
verbose_enter();
- smsg(_("not found in '%s': \"%s\""), basepath, name);
+ smsg(0, _("not found in '%s': \"%s\""), basepath, name);
verbose_leave();
}
}
@@ -337,7 +444,7 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
return did_one ? OK : FAIL;
}
-RuntimeSearchPath runtime_search_path_get_cached(int *ref)
+static RuntimeSearchPath runtime_search_path_get_cached(int *ref)
FUNC_ATTR_NONNULL_ALL
{
runtime_search_path_validate();
@@ -352,7 +459,7 @@ RuntimeSearchPath runtime_search_path_get_cached(int *ref)
return runtime_search_path;
}
-RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
+static RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
{
RuntimeSearchPath dst = KV_INITIAL_VALUE;
for (size_t j = 0; j < kv_size(src); j++) {
@@ -363,7 +470,7 @@ RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
return dst;
}
-void runtime_search_path_unref(RuntimeSearchPath path, const int *ref)
+static void runtime_search_path_unref(RuntimeSearchPath path, const int *ref)
FUNC_ATTR_NONNULL_ALL
{
if (*ref) {
@@ -383,25 +490,24 @@ void runtime_search_path_unref(RuntimeSearchPath path, const int *ref)
/// When "flags" has DIP_ERR: give an error message if there is no match.
///
/// return FAIL when no file could be sourced, OK otherwise.
-int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie)
+static int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie)
{
char *tail;
- int num_files;
- char **files;
- int i;
bool did_one = false;
char buf[MAXPATHL];
if (p_verbose > 10 && name != NULL) {
verbose_enter();
- smsg(_("Searching for \"%s\" in runtime path"), name);
+ smsg(0, _("Searching for \"%s\" in runtime path"), name);
verbose_leave();
}
int ref;
RuntimeSearchPath path = runtime_search_path_get_cached(&ref);
+ bool do_all = (flags & DIP_ALL) != 0;
+
// Loop over all entries in cached path
for (size_t j = 0; j < kv_size(path); j++) {
SearchPathItem item = kv_A(path, j);
@@ -416,7 +522,7 @@ int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *c
}
if (name == NULL) {
- (*callback)(item.path, cookie);
+ (*callback)(1, &item.path, do_all, cookie);
} else if (buflen + strlen(name) + 2 < MAXPATHL) {
STRCPY(buf, item.path);
add_pathsep(buf);
@@ -424,32 +530,25 @@ int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *c
// Loop over all patterns in "name"
char *np = name;
- while (*np != NUL && ((flags & DIP_ALL) || !did_one)) {
+
+ while (*np != NUL && (do_all || !did_one)) {
// Append the pattern from "name" to buf[].
assert(MAXPATHL >= (tail - buf));
copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t ");
if (p_verbose > 10) {
verbose_enter();
- smsg(_("Searching for \"%s\""), buf);
+ smsg(0, _("Searching for \"%s\""), buf);
verbose_leave();
}
int ew_flags = ((flags & DIP_DIR) ? EW_DIR : EW_FILE)
- | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0;
+ | ((flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0)
+ | EW_NOBREAK;
// Expand wildcards, invoke the callback for each match.
char *(pat[]) = { buf };
- if (gen_expand_wildcards(1, pat, &num_files, &files, ew_flags) == OK) {
- for (i = 0; i < num_files; i++) {
- (*callback)(files[i], cookie);
- did_one = true;
- if (!(flags & DIP_ALL)) {
- break;
- }
- }
- FreeWild(num_files, files);
- }
+ did_one |= gen_expand_wildcards_and_cb(1, pat, ew_flags, do_all, callback, cookie) == OK;
}
}
}
@@ -459,7 +558,7 @@ int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *c
semsg(_(e_dirnotf), "runtime path", name);
} else if (p_verbose > 1) {
verbose_enter();
- smsg(_("not found in runtime path: \"%s\""), name);
+ smsg(0, _("not found in runtime path: \"%s\""), name);
verbose_leave();
}
}
@@ -477,7 +576,7 @@ Array runtime_inspect(void)
for (size_t i = 0; i < kv_size(path); i++) {
SearchPathItem *item = &kv_A(path, i);
Array entry = ARRAY_DICT_INIT;
- ADD(entry, STRING_OBJ(cstr_to_string(item->path)));
+ ADD(entry, CSTR_TO_OBJ(item->path));
ADD(entry, BOOLEAN_OBJ(item->after));
if (item->has_lua != kNone) {
ADD(entry, BOOLEAN_OBJ(item->has_lua == kTrue));
@@ -510,8 +609,8 @@ ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all)
return rv;
}
-ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
- RuntimeSearchPath path, char *buf, size_t buf_len)
+static ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
+ RuntimeSearchPath path, char *buf, size_t buf_len)
{
ArrayOf(String) rv = ARRAY_DICT_INIT;
for (size_t i = 0; i < kv_size(path); i++) {
@@ -533,7 +632,7 @@ ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
item->path, pat_item.data.string.data);
if (size < buf_len) {
if (os_file_is_readable(buf)) {
- ADD(rv, STRING_OBJ(cstr_to_string(buf)));
+ ADD(rv, CSTR_TO_OBJ(buf));
if (!all) {
goto done;
}
@@ -559,73 +658,46 @@ int do_in_path_and_pp(char *path, char *name, int flags, DoInRuntimepathCB callb
int done = FAIL;
if ((flags & DIP_NORTP) == 0) {
- done |= do_in_path(path, (name && !*name) ? NULL : name, flags, callback,
+ done |= do_in_path(path, "", (name && !*name) ? NULL : name, flags, callback,
cookie);
}
if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) {
- char *start_dir = "pack/*/start/*/%s%s"; // NOLINT
- size_t len = strlen(start_dir) + strlen(name) + 6;
- char *s = xmallocz(len); // TODO(bfredl): get rid of random allocations
- char *suffix = (flags & DIP_AFTER) ? "after/" : "";
-
- vim_snprintf(s, len, start_dir, suffix, name);
- done |= do_in_path(p_pp, s, flags & ~DIP_AFTER, callback, cookie);
-
- xfree(s);
+ const char *prefix
+ = (flags & DIP_AFTER) ? "pack/*/start/*/after/" : "pack/*/start/*/"; // NOLINT
+ done |= do_in_path(p_pp, prefix, name, flags & ~DIP_AFTER, callback, cookie);
if (done == FAIL || (flags & DIP_ALL)) {
- start_dir = "start/*/%s%s"; // NOLINT
- len = strlen(start_dir) + strlen(name) + 6;
- s = xmallocz(len);
-
- vim_snprintf(s, len, start_dir, suffix, name);
- done |= do_in_path(p_pp, s, flags & ~DIP_AFTER, callback, cookie);
-
- xfree(s);
+ prefix = (flags & DIP_AFTER) ? "start/*/after/" : "start/*/"; // NOLINT
+ done |= do_in_path(p_pp, prefix, name, flags & ~DIP_AFTER, callback, cookie);
}
}
if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_OPT)) {
- char *opt_dir = "pack/*/opt/*/%s"; // NOLINT
- size_t len = strlen(opt_dir) + strlen(name);
- char *s = xmallocz(len);
-
- vim_snprintf(s, len, opt_dir, name);
- done |= do_in_path(p_pp, s, flags, callback, cookie);
-
- xfree(s);
+ done |= do_in_path(p_pp, "pack/*/opt/*/", name, flags, callback, cookie); // NOLINT
if (done == FAIL || (flags & DIP_ALL)) {
- opt_dir = "opt/*/%s"; // NOLINT
- len = strlen(opt_dir) + strlen(name);
- s = xmallocz(len);
-
- vim_snprintf(s, len, opt_dir, name);
- done |= do_in_path(p_pp, s, flags, callback, cookie);
-
- xfree(s);
+ done |= do_in_path(p_pp, "opt/*/", name, flags, callback, cookie); // NOLINT
}
}
return done;
}
-static void push_path(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used, char *entry,
+static void push_path(RuntimeSearchPath *search_path, Set(String) *rtp_used, char *entry,
bool after)
{
- handle_T h = map_get(String, handle_T)(rtp_used, cstr_as_string(entry));
- if (h == 0) {
- char *allocated = xstrdup(entry);
- map_put(String, handle_T)(rtp_used, cstr_as_string(allocated), 1);
- kv_push(*search_path, ((SearchPathItem){ allocated, after, kNone }));
+ String *key_alloc;
+ if (set_put_ref(String, rtp_used, cstr_as_string(entry), &key_alloc)) {
+ *key_alloc = cstr_to_string(entry);
+ kv_push(*search_path, ((SearchPathItem){ key_alloc->data, after, kNone }));
}
}
-static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used,
- char *entry, bool after)
+static void expand_rtp_entry(RuntimeSearchPath *search_path, Set(String) *rtp_used, char *entry,
+ bool after)
{
- if (map_get(String, handle_T)(rtp_used, cstr_as_string(entry))) {
+ if (set_has(String, rtp_used, cstr_as_string(entry))) {
return;
}
@@ -636,7 +708,7 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_
int num_files;
char **files;
char *(pat[]) = { entry };
- if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR) == OK) {
+ if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR | EW_NOBREAK) == OK) {
for (int i = 0; i < num_files; i++) {
push_path(search_path, rtp_used, files[i], after);
}
@@ -644,7 +716,7 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_
}
}
-static void expand_pack_entry(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used,
+static void expand_pack_entry(RuntimeSearchPath *search_path, Set(String) *rtp_used,
CharVec *after_path, char *pack_entry, size_t pack_entry_len)
{
static char buf[MAXPATHL];
@@ -674,13 +746,11 @@ static bool path_is_after(char *buf, size_t buflen)
&& strcmp(buf + buflen - 5, "after") == 0;
}
-RuntimeSearchPath runtime_search_path_build(void)
+static RuntimeSearchPath runtime_search_path_build(void)
{
kvec_t(String) pack_entries = KV_INITIAL_VALUE;
- // TODO(bfredl): these should just be sets, when Set(String) is do merge to
- // master.
- Map(String, handle_T) pack_used = MAP_INIT;
- Map(String, handle_T) rtp_used = MAP_INIT;
+ Map(String, int) pack_used = MAP_INIT;
+ Set(String) rtp_used = SET_INIT;
RuntimeSearchPath search_path = KV_INITIAL_VALUE;
CharVec after_path = KV_INITIAL_VALUE;
@@ -692,7 +762,7 @@ RuntimeSearchPath runtime_search_path_build(void)
String the_entry = { .data = cur_entry, .size = strlen(buf) };
kv_push(pack_entries, the_entry);
- map_put(String, handle_T)(&pack_used, the_entry, 0);
+ map_put(String, int)(&pack_used, the_entry, 0);
}
char *rtp_entry;
@@ -709,7 +779,7 @@ RuntimeSearchPath runtime_search_path_build(void)
// fact: &rtp entries can contain wild chars
expand_rtp_entry(&search_path, &rtp_used, buf, false);
- handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string(buf), false);
+ handle_T *h = map_ref(String, int)(&pack_used, cstr_as_string(buf), NULL);
if (h) {
(*h)++;
expand_pack_entry(&search_path, &rtp_used, &after_path, buf, buflen);
@@ -718,7 +788,7 @@ RuntimeSearchPath runtime_search_path_build(void)
for (size_t i = 0; i < kv_size(pack_entries); i++) {
String item = kv_A(pack_entries, i);
- handle_T h = map_get(String, handle_T)(&pack_used, item);
+ handle_T h = map_get(String, int)(&pack_used, item);
if (h == 0) {
expand_pack_entry(&search_path, &rtp_used, &after_path, item.data, item.size);
}
@@ -739,18 +809,19 @@ RuntimeSearchPath runtime_search_path_build(void)
// strings are not owned
kv_destroy(pack_entries);
kv_destroy(after_path);
- map_destroy(String, handle_T)(&pack_used);
- map_destroy(String, handle_T)(&rtp_used);
+ map_destroy(String, &pack_used);
+ set_destroy(String, &rtp_used);
return search_path;
}
-void runtime_search_path_invalidate(void)
+const char *did_set_runtimepackpath(optset_T *args)
{
runtime_search_path_valid = false;
+ return NULL;
}
-void runtime_search_path_free(RuntimeSearchPath path)
+static void runtime_search_path_free(RuntimeSearchPath path)
{
for (size_t j = 0; j < kv_size(path); j++) {
SearchPathItem item = kv_A(path, j);
@@ -808,27 +879,46 @@ int source_runtime(char *name, int flags)
return do_in_runtimepath(name, flags, source_callback, NULL);
}
-/// Just like source_runtime(), but use "path" instead of 'runtimepath'.
-int source_in_path(char *path, char *name, int flags)
+/// Just like source_runtime(), but only source vim and lua files
+int source_runtime_vim_lua(char *name, int flags)
+{
+ return do_in_runtimepath(name, flags, source_callback_vim_lua, NULL);
+}
+
+/// Just like source_runtime(), but:
+/// - use "path" instead of 'runtimepath'.
+/// - only source .vim and .lua files
+int source_in_path_vim_lua(char *path, char *name, int flags)
{
- return do_in_path_and_pp(path, name, flags, source_callback, NULL);
+ return do_in_path_and_pp(path, name, flags, source_callback_vim_lua, NULL);
}
-// Expand wildcards in "pat" and invoke do_source()/nlua_exec_file()
-// for each match.
-static void source_all_matches(char *pat)
+/// Expand wildcards in "pats" and invoke callback matches.
+///
+/// @param num_pat is number of input patterns.
+/// @param patx is an array of pointers to input patterns.
+/// @param flags is a combination of EW_* flags used in
+/// expand_wildcards().
+/// @param all invoke callback on all matches or just one
+/// @param callback called for each match.
+/// @param cookie context for callback
+///
+/// @returns OK when some files were found, FAIL otherwise.
+static int gen_expand_wildcards_and_cb(int num_pat, char **pats, int flags, bool all,
+ DoInRuntimepathCB callback, void *cookie)
{
int num_files;
char **files;
- if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) != OK) {
- return;
+ if (gen_expand_wildcards(num_pat, pats, &num_files, &files, flags) != OK) {
+ return FAIL;
}
- for (int i = 0; i < num_files; i++) {
- (void)do_source(files[i], false, DOSO_NONE);
- }
+ (*callback)(num_files, files, all, cookie);
+
FreeWild(num_files, files);
+
+ return OK;
}
/// Add the package directory to 'runtimepath'
@@ -838,7 +928,6 @@ static void source_all_matches(char *pat)
static int add_pack_dir_to_rtp(char *fname, bool is_pack)
{
char *p;
- char *buf = NULL;
char *afterdir = NULL;
int retval = FAIL;
@@ -873,29 +962,16 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
// Find "ffname" in "p_rtp", ignoring '/' vs '\' differences
// Also stop at the first "after" directory
size_t fname_len = strlen(ffname);
- buf = try_malloc(MAXPATHL);
+ char *buf = try_malloc(MAXPATHL);
if (buf == NULL) {
goto theend;
}
const char *insp = NULL;
const char *after_insp = NULL;
- for (const char *entry = (const char *)p_rtp; *entry != NUL;) {
+ for (const char *entry = p_rtp; *entry != NUL;) {
const char *cur_entry = entry;
copy_option_part((char **)&entry, buf, MAXPATHL, ",");
- if (insp == NULL) {
- add_pathsep(buf);
- char *const rtp_ffname = fix_fname(buf);
- if (rtp_ffname == NULL) {
- goto theend;
- }
- bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0;
- xfree(rtp_ffname);
- if (match) {
- // Insert "ffname" after this entry (and comma).
- insp = entry;
- }
- }
if ((p = strstr(buf, "after")) != NULL
&& p > buf
@@ -909,11 +985,25 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
after_insp = cur_entry;
break;
}
+
+ if (insp == NULL) {
+ add_pathsep(buf);
+ char *const rtp_ffname = fix_fname(buf);
+ if (rtp_ffname == NULL) {
+ goto theend;
+ }
+ bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0;
+ xfree(rtp_ffname);
+ if (match) {
+ // Insert "ffname" after this entry (and comma).
+ insp = entry;
+ }
+ }
}
if (insp == NULL) {
// Both "fname" and "after" not found, append at the end.
- insp = (const char *)p_rtp + strlen(p_rtp);
+ insp = p_rtp + strlen(p_rtp);
}
// check if rtp/pack/name/start/name/after exists
@@ -934,7 +1024,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
// We now have 'rtp' parts: {keep}{keep_after}{rest}.
// Create new_rtp, first: {keep},{fname}
- size_t keep = (size_t)(insp - (const char *)p_rtp);
+ size_t keep = (size_t)(insp - p_rtp);
memmove(new_rtp, p_rtp, keep);
size_t new_rtp_len = keep;
if (*insp == NUL) {
@@ -947,7 +1037,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
}
if (afterlen > 0 && after_insp != NULL) {
- size_t keep_after = (size_t)(after_insp - (const char *)p_rtp);
+ size_t keep_after = (size_t)(after_insp - p_rtp);
// Add to new_rtp: {keep},{fname}{keep_after},{afterdir}
memmove(new_rtp + new_rtp_len, p_rtp + keep, keep_after - keep);
@@ -972,7 +1062,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
xstrlcat(new_rtp, afterdir, new_rtp_capacity);
}
- set_option_value_give_err("rtp", 0L, new_rtp, 0);
+ set_option_value_give_err("rtp", CSTR_AS_OPTVAL(new_rtp), 0);
xfree(new_rtp);
retval = OK;
@@ -985,30 +1075,27 @@ theend:
/// Load scripts in "plugin" directory of the package.
/// For opt packages, also load scripts in "ftdetect" (start packages already
-/// load these from filetype.vim)
+/// load these from filetype.lua)
static int load_pack_plugin(bool opt, char *fname)
{
- static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT
+ static const char plugpat[] = "%s/plugin/**/*"; // NOLINT
+ static const char ftpat[] = "%s/ftdetect/*"; // NOLINT
char *const ffname = fix_fname(fname);
- size_t len = strlen(ffname) + strlen(ftpat);
+ size_t len = strlen(ffname) + sizeof(plugpat);
char *pat = xmallocz(len);
- vim_snprintf(pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT
- source_all_matches(pat);
- vim_snprintf(pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT
- source_all_matches(pat);
+ vim_snprintf(pat, len, plugpat, ffname); // NOLINT
+ gen_expand_wildcards_and_cb(1, &pat, EW_FILE, true, source_callback_vim_lua, NULL);
char *cmd = xstrdup("g:did_load_filetypes");
- // If runtime/filetype.vim wasn't loaded yet, the scripts will be
+ // If runtime/filetype.lua wasn't loaded yet, the scripts will be
// found when it loads.
if (opt && eval_to_number(cmd) > 0) {
do_cmdline_cmd("augroup filetypedetect");
vim_snprintf(pat, len, ftpat, ffname);
- source_all_matches(pat);
- vim_snprintf((char *)pat, len, "%s/ftdetect/*.lua", ffname); // NOLINT
- source_all_matches(pat);
+ gen_expand_wildcards_and_cb(1, &pat, EW_FILE, true, source_callback_vim_lua, NULL);
do_cmdline_cmd("augroup END");
}
xfree(cmd);
@@ -1023,48 +1110,68 @@ static int APP_ADD_DIR;
static int APP_LOAD;
static int APP_BOTH;
-static void add_pack_plugin(bool opt, char *fname, void *cookie)
+static void add_pack_plugins(bool opt, int num_fnames, char **fnames, bool all, void *cookie)
{
+ bool did_one = false;
+
if (cookie != &APP_LOAD) {
char *buf = xmalloc(MAXPATHL);
- bool found = false;
-
- const char *p = (const char *)p_rtp;
- while (*p != NUL) {
- copy_option_part((char **)&p, buf, MAXPATHL, ",");
- if (path_fnamecmp(buf, fname) == 0) {
- found = true;
+ for (int i = 0; i < num_fnames; i++) {
+ bool found = false;
+
+ const char *p = p_rtp;
+ while (*p != NUL) {
+ copy_option_part((char **)&p, buf, MAXPATHL, ",");
+ if (path_fnamecmp(buf, fnames[i]) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // directory is not yet in 'runtimepath', add it
+ if (add_pack_dir_to_rtp(fnames[i], false) == FAIL) {
+ xfree(buf);
+ return;
+ }
+ }
+ did_one = true;
+ if (!all) {
break;
}
}
xfree(buf);
- if (!found) {
- // directory is not yet in 'runtimepath', add it
- if (add_pack_dir_to_rtp(fname, false) == FAIL) {
- return;
- }
- }
+ }
+
+ if (!all && did_one) {
+ return;
}
if (cookie != &APP_ADD_DIR) {
- load_pack_plugin(opt, fname);
+ for (int i = 0; i < num_fnames; i++) {
+ load_pack_plugin(opt, fnames[i]);
+ if (!all) {
+ break;
+ }
+ }
}
}
-static void add_start_pack_plugin(char *fname, void *cookie)
+static bool add_start_pack_plugins(int num_fnames, char **fnames, bool all, void *cookie)
{
- add_pack_plugin(false, fname, cookie);
+ add_pack_plugins(false, num_fnames, fnames, all, cookie);
+ return num_fnames > 0;
}
-static void add_opt_pack_plugin(char *fname, void *cookie)
+static bool add_opt_pack_plugins(int num_fnames, char **fnames, bool all, void *cookie)
{
- add_pack_plugin(true, fname, cookie);
+ add_pack_plugins(true, num_fnames, fnames, all, cookie);
+ return num_fnames > 0;
}
/// Add all packages in the "start" directory to 'runtimepath'.
void add_pack_start_dirs(void)
{
- do_in_path(p_pp, NULL, DIP_ALL + DIP_DIR, add_pack_start_dir, NULL);
+ do_in_path(p_pp, "", NULL, DIP_ALL + DIP_DIR, add_pack_start_dir, NULL);
}
static bool pack_has_entries(char *buf)
@@ -1078,30 +1185,38 @@ static bool pack_has_entries(char *buf)
return num_files > 0;
}
-static void add_pack_start_dir(char *fname, void *cookie)
+static bool add_pack_start_dir(int num_fnames, char **fnames, bool all, void *cookie)
{
static char buf[MAXPATHL];
- char *(start_pat[]) = { "/start/*", "/pack/*/start/*" }; // NOLINT
- for (int i = 0; i < 2; i++) {
- if (strlen(fname) + strlen(start_pat[i]) + 1 > MAXPATHL) {
- continue;
+ for (int i = 0; i < num_fnames; i++) {
+ char *(start_pat[]) = { "/start/*", "/pack/*/start/*" }; // NOLINT
+ for (int j = 0; j < 2; j++) {
+ if (strlen(fnames[i]) + strlen(start_pat[j]) + 1 > MAXPATHL) {
+ continue;
+ }
+ xstrlcpy(buf, fnames[i], MAXPATHL);
+ xstrlcat(buf, start_pat[j], sizeof buf);
+ if (pack_has_entries(buf)) {
+ add_pack_dir_to_rtp(buf, true);
+ }
}
- xstrlcpy(buf, fname, MAXPATHL);
- xstrlcat(buf, start_pat[i], sizeof buf);
- if (pack_has_entries(buf)) {
- add_pack_dir_to_rtp(buf, true);
+
+ if (!all) {
+ break;
}
}
+
+ return num_fnames > 1;
}
/// Load plugins from all packages in the "start" directory.
void load_start_packages(void)
{
did_source_packages = true;
- do_in_path(p_pp, "pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT
- add_start_pack_plugin, &APP_LOAD);
- do_in_path(p_pp, "start/*", DIP_ALL + DIP_DIR, // NOLINT
- add_start_pack_plugin, &APP_LOAD);
+ do_in_path(p_pp, "", "pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT
+ add_start_pack_plugins, &APP_LOAD);
+ do_in_path(p_pp, "", "start/*", DIP_ALL + DIP_DIR, // NOLINT
+ add_start_pack_plugins, &APP_LOAD);
}
// ":packloadall"
@@ -1122,17 +1237,16 @@ void load_plugins(void)
{
if (p_lpl) {
char *rtp_copy = p_rtp;
- char *const plugin_pattern_vim = "plugin/**/*.vim"; // NOLINT
- char *const plugin_pattern_lua = "plugin/**/*.lua"; // NOLINT
+ char *const plugin_pattern = "plugin/**/*"; // NOLINT
if (!did_source_packages) {
rtp_copy = xstrdup(p_rtp);
add_pack_start_dirs();
}
- // don't use source_runtime() yet so we can check for :packloadall below
- source_in_path(rtp_copy, plugin_pattern_vim, DIP_ALL | DIP_NOAFTER);
- source_in_path(rtp_copy, plugin_pattern_lua, DIP_ALL | DIP_NOAFTER);
+ // Don't use source_runtime_vim_lua() yet so we can check for :packloadall below.
+ // NB: after calling this "rtp_copy" may have been freed if it wasn't copied.
+ source_in_path_vim_lua(rtp_copy, plugin_pattern, DIP_ALL | DIP_NOAFTER);
TIME_MSG("loading rtp plugins");
// Only source "start" packages if not done already with a :packloadall
@@ -1143,8 +1257,7 @@ void load_plugins(void)
}
TIME_MSG("loading packages");
- source_runtime(plugin_pattern_vim, DIP_ALL | DIP_AFTER);
- source_runtime(plugin_pattern_lua, DIP_ALL | DIP_AFTER);
+ source_runtime_vim_lua(plugin_pattern, DIP_ALL | DIP_AFTER);
TIME_MSG("loading after plugins");
}
}
@@ -1152,7 +1265,7 @@ void load_plugins(void)
/// ":packadd[!] {name}"
void ex_packadd(exarg_T *eap)
{
- static const char *plugpat = "pack/*/%s/%s"; // NOLINT
+ static const char plugpat[] = "pack/*/%s/%s"; // NOLINT
int res = OK;
// Round 1: use "start", round 2: use "opt".
@@ -1162,115 +1275,168 @@ void ex_packadd(exarg_T *eap)
continue;
}
- const size_t len = strlen(plugpat) + strlen(eap->arg) + 5;
+ const size_t len = sizeof(plugpat) + strlen(eap->arg) + 5;
char *pat = xmallocz(len);
vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg);
// The first round don't give a "not found" error, in the second round
// only when nothing was found in the first round.
res =
- do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0),
- round == 1 ? add_start_pack_plugin : add_opt_pack_plugin,
+ do_in_path(p_pp, "", pat,
+ DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0),
+ round == 1 ? add_start_pack_plugins : add_opt_pack_plugins,
eap->forceit ? &APP_ADD_DIR : &APP_BOTH);
xfree(pat);
}
}
-/// Expand color scheme, compiler or filetype names.
-/// Search from 'runtimepath':
-/// 'runtimepath'/{dirnames}/{pat}.(vim|lua)
-/// When "flags" has DIP_START: search also from "start" of 'packpath':
-/// 'packpath'/pack/*/start/*/{dirnames}/{pat}.(vim|lua)
-/// When "flags" has DIP_OPT: search also from "opt" of 'packpath':
-/// 'packpath'/pack/*/opt/*/{dirnames}/{pat}.(vim|lua)
-/// "dirnames" is an array with one or more directory names.
-int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirnames[])
+static void ExpandRTDir_int(char *pat, size_t pat_len, int flags, bool keep_ext, garray_T *gap,
+ char *dirnames[])
{
- *num_file = 0;
- *file = NULL;
- size_t pat_len = strlen(pat);
-
- garray_T ga;
- ga_init(&ga, (int)sizeof(char *), 10);
-
// TODO(bfredl): this is bullshit, expandpath should not reinvent path logic.
for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = strlen(dirnames[i]) + pat_len + 16;
- char *s = xmalloc(size);
- snprintf(s, size, "%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat);
- globpath(p_rtp, s, &ga, 0);
- xfree(s);
- }
+ const size_t buf_len = strlen(dirnames[i]) + pat_len + 31;
+ char *const buf = xmalloc(buf_len);
+ char *const tail = buf + 15;
+ const size_t tail_buflen = buf_len - 15;
+ int glob_flags = 0;
+ bool expand_dirs = false;
+
+ if (*dirnames[i] == NUL) { // empty dir used for :runtime
+ snprintf(tail, tail_buflen, "%s*.{vim,lua}", pat);
+ } else {
+ snprintf(tail, tail_buflen, "%s/%s*.{vim,lua}", dirnames[i], pat);
+ }
- if (flags & DIP_START) {
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = strlen(dirnames[i]) + pat_len + 31;
- char *s = xmalloc(size);
- snprintf(s, size, "pack/*/start/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- xfree(s);
+expand:
+ if ((flags & DIP_NORTP) == 0) {
+ globpath(p_rtp, tail, gap, glob_flags, expand_dirs);
}
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = strlen(dirnames[i]) + pat_len + 31;
- char *s = xmalloc(size);
- snprintf(s, size, "start/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- xfree(s);
+ if (flags & DIP_START) {
+ memcpy(tail - 15, "pack/*/start/*/", 15); // NOLINT
+ globpath(p_pp, tail - 15, gap, glob_flags, expand_dirs);
+ memcpy(tail - 8, "start/*/", 8); // NOLINT
+ globpath(p_pp, tail - 8, gap, glob_flags, expand_dirs);
}
- }
- if (flags & DIP_OPT) {
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = strlen(dirnames[i]) + pat_len + 29;
- char *s = xmalloc(size);
- snprintf(s, size, "pack/*/opt/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- xfree(s);
+ if (flags & DIP_OPT) {
+ memcpy(tail - 13, "pack/*/opt/*/", 13); // NOLINT
+ globpath(p_pp, tail - 13, gap, glob_flags, expand_dirs);
+ memcpy(tail - 6, "opt/*/", 6); // NOLINT
+ globpath(p_pp, tail - 6, gap, glob_flags, expand_dirs);
}
- for (int i = 0; dirnames[i] != NULL; i++) {
- size_t size = strlen(dirnames[i]) + pat_len + 29;
- char *s = xmalloc(size);
- snprintf(s, size, "opt/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
- xfree(s);
+ if (*dirnames[i] == NUL && !expand_dirs) {
+ // expand dir names in another round
+ snprintf(tail, tail_buflen, "%s*", pat);
+ glob_flags = WILD_ADD_SLASH;
+ expand_dirs = true;
+ goto expand;
}
+
+ xfree(buf);
}
- for (int i = 0; i < ga.ga_len; i++) {
- char *match = ((char **)ga.ga_data)[i];
+ int pat_pathsep_cnt = 0;
+ for (size_t i = 0; i < pat_len; i++) {
+ if (vim_ispathsep(pat[i])) {
+ pat_pathsep_cnt++;
+ }
+ }
+
+ for (int i = 0; i < gap->ga_len; i++) {
+ char *match = ((char **)gap->ga_data)[i];
char *s = match;
char *e = s + strlen(s);
- if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0
- || STRNICMP(e - 4, ".lua", 4) == 0)) {
+ if (e - s > 4 && !keep_ext && (STRNICMP(e - 4, ".vim", 4) == 0
+ || STRNICMP(e - 4, ".lua", 4) == 0)) {
e -= 4;
- for (s = e; s > match; MB_PTR_BACK(match, s)) {
- if (vim_ispathsep(*s)) {
- break;
- }
- }
- s++;
*e = NUL;
+ }
+
+ int match_pathsep_cnt = (e > s && e[-1] == '/') ? -1 : 0;
+ for (s = e; s > match; MB_PTR_BACK(match, s)) {
+ if (vim_ispathsep(*s) && ++match_pathsep_cnt > pat_pathsep_cnt) {
+ break;
+ }
+ }
+ s++;
+ if (s != match) {
assert((e - s) + 1 >= 0);
memmove(match, s, (size_t)(e - s) + 1);
}
}
- if (GA_EMPTY(&ga)) {
- return FAIL;
+ if (GA_EMPTY(gap)) {
+ return;
}
// Sort and remove duplicates which can happen when specifying multiple
// directories in dirnames.
- ga_remove_duplicate_strings(&ga);
+ ga_remove_duplicate_strings(gap);
+}
+
+/// Expand color scheme, compiler or filetype names.
+/// Search from 'runtimepath':
+/// 'runtimepath'/{dirnames}/{pat}.{vim,lua}
+/// When "flags" has DIP_START: search also from "start" of 'packpath':
+/// 'packpath'/pack/*/start/*/{dirnames}/{pat}.{vim,lua}
+/// When "flags" has DIP_OPT: search also from "opt" of 'packpath':
+/// 'packpath'/pack/*/opt/*/{dirnames}/{pat}.{vim,lua}
+/// "dirnames" is an array with one or more directory names.
+int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirnames[])
+{
+ *num_file = 0;
+ *file = NULL;
+
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char *), 10);
+
+ ExpandRTDir_int(pat, strlen(pat), flags, false, &ga, dirnames);
+
+ if (GA_EMPTY(&ga)) {
+ return FAIL;
+ }
*file = ga.ga_data;
*num_file = ga.ga_len;
return OK;
}
+/// Handle command line completion for :runtime command.
+int expand_runtime_cmd(char *pat, int *numMatches, char ***matches)
+{
+ *numMatches = 0;
+ *matches = NULL;
+
+ garray_T ga;
+ ga_init(&ga, sizeof(char *), 10);
+
+ const size_t pat_len = strlen(pat);
+ char *dirnames[] = { "", NULL };
+ ExpandRTDir_int(pat, pat_len, runtime_expand_flags, true, &ga, dirnames);
+
+ // Try to complete values for [where] argument when none was found.
+ if (runtime_expand_flags == 0) {
+ char *where_values[] = { "START", "OPT", "PACK", "ALL" };
+ for (size_t i = 0; i < ARRAY_SIZE(where_values); i++) {
+ if (strncmp(pat, where_values[i], pat_len) == 0) {
+ GA_APPEND(char *, &ga, xstrdup(where_values[i]));
+ }
+ }
+ }
+
+ if (GA_EMPTY(&ga)) {
+ return FAIL;
+ }
+
+ *matches = ga.ga_data;
+ *numMatches = ga.ga_len;
+ return OK;
+}
+
/// Expand loadplugin names:
-/// 'packpath'/pack/ * /opt/{pat}
+/// 'packpath'/pack/*/opt/{pat}
int ExpandPackAddDir(char *pat, int *num_file, char ***file)
{
garray_T ga;
@@ -1283,9 +1449,9 @@ int ExpandPackAddDir(char *pat, int *num_file, char ***file)
size_t buflen = pat_len + 26;
char *s = xmalloc(buflen);
snprintf(s, buflen, "pack/*/opt/%s*", pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
+ globpath(p_pp, s, &ga, 0, true);
snprintf(s, buflen, "opt/%s*", pat); // NOLINT
- globpath(p_pp, s, &ga, 0);
+ globpath(p_pp, s, &ga, 0, true);
xfree(s);
for (int i = 0; i < ga.ga_len; i++) {
@@ -1357,7 +1523,6 @@ static inline size_t compute_double_env_sep_len(const char *const val, const siz
return ret;
}
-#define NVIM_SIZE (sizeof("nvim") - 1)
/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one
///
/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in
@@ -1386,6 +1551,8 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch
return dest;
}
const void *iter = NULL;
+ const char *appname = get_appname();
+ const size_t appname_len = strlen(appname);
do {
size_t dir_len;
const char *dir;
@@ -1396,8 +1563,8 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
- memmove(dest, "nvim", NVIM_SIZE);
- dest += NVIM_SIZE;
+ memmove(dest, appname, appname_len);
+ dest += appname_len;
if (suf1 != NULL) {
*dest++ = PATHSEP;
memmove(dest, suf1, len1);
@@ -1451,14 +1618,18 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
+ const char *appname = get_appname();
+ size_t appname_len = strlen(appname);
+ assert(appname_len < (IOSIZE - sizeof("-data")));
+ xstrlcpy(IObuff, appname, appname_len + 1);
#if defined(MSWIN)
- size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE);
- memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size);
- dest += size;
-#else
- memmove(dest, "nvim", NVIM_SIZE);
- dest += NVIM_SIZE;
+ if (type == kXDGDataHome || type == kXDGStateHome) {
+ xstrlcat(IObuff, "-data", IOSIZE);
+ appname_len += 5;
+ }
#endif
+ xstrlcpy(dest, IObuff, appname_len + 1);
+ dest += appname_len;
if (suf1 != NULL) {
*dest++ = PATHSEP;
memmove(dest, suf1, len1);
@@ -1486,7 +1657,7 @@ char *get_lib_dir(void)
// Find library path relative to the nvim binary: ../lib/nvim/
char exe_name[MAXPATHL];
vim_get_prefix_from_exepath(exe_name);
- if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) {
+ if (append_path(exe_name, "lib/nvim", MAXPATHL) == OK) {
return xstrdup(exe_name);
}
return NULL;
@@ -1503,11 +1674,11 @@ char *runtimepath_default(bool clean_arg)
{
size_t rtp_size = 0;
char *const data_home = clean_arg
- ? NULL
- : stdpaths_get_xdg_var(kXDGDataHome);
+ ? NULL
+ : stdpaths_get_xdg_var(kXDGDataHome);
char *const config_home = clean_arg
- ? NULL
- : stdpaths_get_xdg_var(kXDGConfigHome);
+ ? NULL
+ : stdpaths_get_xdg_var(kXDGConfigHome);
char *const vimruntime = vim_getenv("VIMRUNTIME");
char *const libdir = get_lib_dir();
char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs);
@@ -1518,16 +1689,17 @@ char *runtimepath_default(bool clean_arg)
size_t config_len = 0;
size_t vimruntime_len = 0;
size_t libdir_len = 0;
+ const char *appname = get_appname();
+ size_t appname_len = strlen(appname);
if (data_home != NULL) {
data_len = strlen(data_home);
- if (data_len != 0) {
+ size_t nvim_data_size = appname_len;
#if defined(MSWIN)
- size_t nvim_size = (sizeof("nvim-data") - 1);
-#else
- size_t nvim_size = NVIM_SIZE;
+ nvim_data_size += sizeof("-data") - 1; // -1: NULL byte should be ignored
#endif
+ if (data_len != 0) {
rtp_size += ((data_len + memcnt(data_home, ',', data_len)
- + nvim_size + 1 + SITE_SIZE + 1
+ + nvim_data_size + 1 + SITE_SIZE + 1
+ !after_pathsep(data_home, data_home + data_len)) * 2
+ AFTER_SIZE + 1);
}
@@ -1536,7 +1708,7 @@ char *runtimepath_default(bool clean_arg)
config_len = strlen(config_home);
if (config_len != 0) {
rtp_size += ((config_len + memcnt(config_home, ',', config_len)
- + NVIM_SIZE + 1
+ + appname_len + 1
+ !after_pathsep(config_home, config_home + config_len)) * 2
+ AFTER_SIZE + 1);
}
@@ -1554,9 +1726,9 @@ char *runtimepath_default(bool clean_arg)
}
}
rtp_size += compute_double_env_sep_len(data_dirs,
- NVIM_SIZE + 1 + SITE_SIZE + 1,
+ appname_len + 1 + SITE_SIZE + 1,
AFTER_SIZE + 1);
- rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1,
+ rtp_size += compute_double_env_sep_len(config_dirs, appname_len + 1,
AFTER_SIZE + 1);
char *rtp = NULL;
if (rtp_size == 0) {
@@ -1597,7 +1769,6 @@ freeall:
return rtp;
}
-#undef NVIM_SIZE
static void cmd_source(char *fname, exarg_T *eap)
{
@@ -1615,7 +1786,7 @@ static void cmd_source(char *fname, exarg_T *eap)
|| eap->cstack->cs_idx >= 0);
// ":source" read ex commands
- } else if (do_source(fname, false, DOSO_NONE) == FAIL) {
+ } else if (do_source(fname, false, DOSO_NONE, NULL) == FAIL) {
semsg(_(e_notopen), fname);
}
}
@@ -1679,7 +1850,7 @@ static FILE *fopen_noinh_readbin(char *filename)
return fdopen(fd_tmp, READBIN);
}
-/// Concatenate VimL line if it starts with a line continuation into a growarray
+/// Concatenate Vimscript line if it starts with a line continuation into a growarray
/// (excluding the continuation chars and leading whitespace)
///
/// @note Growsize of the growarray may be changed to speed up concatenations!
@@ -1695,7 +1866,7 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize, c
size_t len)
FUNC_ATTR_NONNULL_ALL
{
- const char *const line = skipwhite_len((char *)p, len);
+ const char *const line = skipwhite_len(p, len);
len -= (size_t)(line - p);
// Skip lines starting with '\" ', concat lines starting with '\'
if (len >= 3 && strncmp(line, "\"\\ ", 3) == 0) {
@@ -1743,7 +1914,7 @@ static char *get_str_line(int c, void *cookie, int indent, bool do_concat)
if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - line))) {
break;
}
- eol = (char *)next_eol;
+ eol = next_eol;
}
}
ga_append(&ga, NUL);
@@ -1766,13 +1937,18 @@ scriptitem_T *new_script_item(char *const name, scid_T *const sid_out)
}
ga_grow(&script_items, sid - script_items.ga_len);
while (script_items.ga_len < sid) {
+ scriptitem_T *si = xcalloc(1, sizeof(scriptitem_T));
script_items.ga_len++;
- SCRIPT_ITEM(script_items.ga_len).sn_name = NULL;
- SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false;
+ SCRIPT_ITEM(script_items.ga_len) = si;
+ si->sn_name = NULL;
+
+ // Allocate the local script variables to use for this script.
+ new_script_vars(script_items.ga_len);
+
+ si->sn_prof_on = false;
}
- SCRIPT_ITEM(sid).sn_name = name;
- new_script_vars(sid); // Allocate the local script variables to use for this script.
- return &SCRIPT_ITEM(sid);
+ SCRIPT_ITEM(sid)->sn_name = name;
+ return SCRIPT_ITEM(sid);
}
static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name)
@@ -1784,7 +1960,7 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
if (save_sourcing_name == NULL) {
sname = (char *)traceback_name;
} else {
- snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
+ snprintf(sourcing_name_buf, sizeof(sourcing_name_buf),
"%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name,
save_sourcing_lnum);
sname = sourcing_name_buf;
@@ -1830,13 +2006,11 @@ static void cmd_source_buffer(const exarg_T *const eap)
.buf = ga.ga_data,
.offset = 0,
};
- if (curbuf->b_fname
- && path_with_extension((const char *)curbuf->b_fname, "lua")) {
- nlua_source_using_linegetter(get_str_line, (void *)&cookie,
- ":source (no file)");
+ if (strequal(curbuf->b_p_ft, "lua")
+ || (curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua"))) {
+ nlua_source_using_linegetter(get_str_line, (void *)&cookie, ":source (no file)");
} else {
- source_using_linegetter((void *)&cookie, get_str_line,
- ":source (no file)");
+ source_using_linegetter((void *)&cookie, get_str_line, ":source (no file)");
}
ga_clear(&ga);
}
@@ -1863,13 +2037,14 @@ int do_source_str(const char *cmd, const char *traceback_name)
/// @param fname
/// @param check_other check for .vimrc and _vimrc
/// @param is_vimrc DOSO_ value
+/// @param ret_sid if not NULL and we loaded the script before, don't load it again
///
/// @return FAIL if file could not be opened, OK otherwise
-int do_source(char *fname, int check_other, int is_vimrc)
+///
+/// If a scriptitem_T was found or created "*ret_sid" is set to the SID.
+int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid)
{
struct source_cookie cookie;
- char *p;
- char *fname_exp;
uint8_t *firstline = NULL;
int retval = FAIL;
int save_debug_break_level = debug_break_level;
@@ -1877,17 +2052,26 @@ int do_source(char *fname, int check_other, int is_vimrc)
proftime_T wait_start;
bool trigger_source_post = false;
- p = expand_env_save(fname);
+ char *p = expand_env_save(fname);
if (p == NULL) {
return retval;
}
- fname_exp = fix_fname(p);
+ char *fname_exp = fix_fname(p);
xfree(p);
if (fname_exp == NULL) {
return retval;
}
if (os_isdir(fname_exp)) {
- smsg(_("Cannot source a directory: \"%s\""), fname);
+ smsg(0, _("Cannot source a directory: \"%s\""), fname);
+ goto theend;
+ }
+
+ // See if we loaded this script before.
+ int sid = find_script_by_name(fname_exp);
+ if (sid > 0 && ret_sid != NULL) {
+ // Already loaded and no need to load again, return here.
+ *ret_sid = sid;
+ retval = OK;
goto theend;
}
@@ -1922,9 +2106,9 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (p_verbose > 1) {
verbose_enter();
if (SOURCING_NAME == NULL) {
- smsg(_("could not source \"%s\""), fname);
+ smsg(0, _("could not source \"%s\""), fname);
} else {
- smsg(_("line %" PRId64 ": could not source \"%s\""),
+ smsg(0, _("line %" PRId64 ": could not source \"%s\""),
(int64_t)SOURCING_LNUM, fname);
}
verbose_leave();
@@ -1938,9 +2122,9 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (p_verbose > 1) {
verbose_enter();
if (SOURCING_NAME == NULL) {
- smsg(_("sourcing \"%s\""), fname);
+ smsg(0, _("sourcing \"%s\""), fname);
} else {
- smsg(_("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname);
+ smsg(0, _("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname);
}
verbose_leave();
}
@@ -1963,7 +2147,7 @@ int do_source(char *fname, int check_other, int is_vimrc)
cookie.finished = false;
// Check if this script has a breakpoint.
- cookie.breakpoint = dbg_find_breakpoint(true, fname_exp, (linenr_T)0);
+ cookie.breakpoint = dbg_find_breakpoint(true, fname_exp, 0);
cookie.fname = fname_exp;
cookie.dbg_tick = debug_tick;
@@ -1989,7 +2173,24 @@ int do_source(char *fname, int check_other, int is_vimrc)
save_funccal(&funccalp_entry);
const sctx_T save_current_sctx = current_sctx;
- si = get_current_script_id(&fname_exp, &current_sctx);
+
+ current_sctx.sc_lnum = 0;
+
+ // Always use a new sequence number.
+ current_sctx.sc_seq = ++last_current_SID_seq;
+
+ if (sid > 0) {
+ // loading the same script again
+ si = SCRIPT_ITEM(sid);
+ } else {
+ // It's new, generate a new SID.
+ si = new_script_item(fname_exp, &sid);
+ fname_exp = xstrdup(si->sn_name); // used for autocmd
+ if (ret_sid != NULL) {
+ *ret_sid = sid;
+ }
+ }
+ current_sctx.sc_sid = sid;
// Keep the sourcing name/lnum, for recursive calls.
estack_push(ETYPE_SCRIPT, si->sn_name, 0);
@@ -2011,12 +2212,12 @@ int do_source(char *fname, int check_other, int is_vimrc)
cookie.conv.vc_type = CONV_NONE; // no conversion
- if (path_with_extension((const char *)fname_exp, "lua")) {
+ if (path_with_extension(fname_exp, "lua")) {
const sctx_T current_sctx_backup = current_sctx;
current_sctx.sc_sid = SID_LUA;
current_sctx.sc_lnum = 0;
// Source the file as lua
- nlua_exec_file((const char *)fname_exp);
+ nlua_exec_file(fname_exp);
current_sctx = current_sctx_backup;
} else {
// Read the first line so we can check for a UTF-8 BOM.
@@ -2040,7 +2241,7 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (l_do_profiling == PROF_YES) {
// Get "si" again, "script_items" may have been reallocated.
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on) {
si->sn_pr_start = profile_end(si->sn_pr_start);
si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start);
@@ -2056,9 +2257,9 @@ int do_source(char *fname, int check_other, int is_vimrc)
estack_pop();
if (p_verbose > 1) {
verbose_enter();
- smsg(_("finished sourcing %s"), fname);
+ smsg(0, _("finished sourcing %s"), fname);
if (SOURCING_NAME != NULL) {
- smsg(_("continuing in %s"), SOURCING_NAME);
+ smsg(0, _("continuing in %s"), SOURCING_NAME);
}
verbose_leave();
}
@@ -2099,42 +2300,23 @@ theend:
return retval;
}
-/// Check if fname was sourced before to finds its SID.
-/// If it's new, generate a new SID.
-///
-/// @param[in,out] fnamep pointer to file path of script
-/// @param[out] ret_sctx sctx of this script
-scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx)
+/// Find an already loaded script "name".
+/// If found returns its script ID. If not found returns -1.
+int find_script_by_name(char *name)
{
- static int last_current_SID_seq = 0;
-
- sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq,
- .sc_lnum = 0,
- .sc_sid = 0 };
- scriptitem_T *si = NULL;
-
assert(script_items.ga_len >= 0);
- for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) {
+ for (int sid = script_items.ga_len; sid > 0; sid--) {
// We used to check inode here, but that doesn't work:
// - If a script is edited and written, it may get a different
// inode number, even though to the user it is the same script.
// - If a script is deleted and another script is written, with a
// different name, the inode may be re-used.
- si = &SCRIPT_ITEM(script_sctx.sc_sid);
- if (si->sn_name != NULL && path_fnamecmp(si->sn_name, *fnamep) == 0) {
- // Found it!
- break;
+ scriptitem_T *si = SCRIPT_ITEM(sid);
+ if (si->sn_name != NULL && path_fnamecmp(si->sn_name, name) == 0) {
+ return sid;
}
}
- if (script_sctx.sc_sid == 0) {
- si = new_script_item(*fnamep, &script_sctx.sc_sid);
- *fnamep = xstrdup(si->sn_name);
- }
- if (ret_sctx != NULL) {
- *ret_sctx = script_sctx;
- }
-
- return si;
+ return -1;
}
/// ":scriptnames"
@@ -2146,7 +2328,7 @@ void ex_scriptnames(exarg_T *eap)
emsg(_(e_invarg));
} else {
if (eap->addr_count > 0) {
- eap->arg = SCRIPT_ITEM(eap->line2).sn_name;
+ eap->arg = SCRIPT_ITEM(eap->line2)->sn_name;
} else {
expand_env(eap->arg, NameBuff, MAXPATHL);
eap->arg = NameBuff;
@@ -2157,12 +2339,12 @@ void ex_scriptnames(exarg_T *eap)
}
for (int i = 1; i <= script_items.ga_len && !got_int; i++) {
- if (SCRIPT_ITEM(i).sn_name != NULL) {
- home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true);
+ if (SCRIPT_ITEM(i)->sn_name != NULL) {
+ home_replace(NULL, SCRIPT_ITEM(i)->sn_name, NameBuff, MAXPATHL, true);
vim_snprintf(IObuff, IOSIZE, "%3d: %s", i, NameBuff);
if (!message_filtered(IObuff)) {
msg_putchar('\n');
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
line_breakcheck();
}
}
@@ -2174,8 +2356,8 @@ void ex_scriptnames(exarg_T *eap)
void scriptnames_slash_adjust(void)
{
for (int i = 1; i <= script_items.ga_len; i++) {
- if (SCRIPT_ITEM(i).sn_name != NULL) {
- slash_adjust(SCRIPT_ITEM(i).sn_name);
+ if (SCRIPT_ITEM(i)->sn_name != NULL) {
+ slash_adjust(SCRIPT_ITEM(i)->sn_name);
}
}
}
@@ -2209,7 +2391,7 @@ char *get_scriptname(LastSet last_set, bool *should_free)
case SID_STR:
return _("anonymous :source");
default: {
- char *const sname = SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name;
+ char *const sname = SCRIPT_ITEM(last_set.script_ctx.sc_sid)->sn_name;
if (sname == NULL) {
snprintf(IObuff, IOSIZE, _("anonymous :source (script id %d)"),
last_set.script_ctx.sc_sid);
@@ -2227,17 +2409,126 @@ void free_scriptnames(void)
{
profile_reset();
-# define FREE_SCRIPTNAME(item) xfree((item)->sn_name)
- GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME);
+# define FREE_SCRIPTNAME(item) \
+ do { \
+ scriptitem_T *_si = *(item); \
+ /* the variables themselves are cleared in evalvars_clear() */ \
+ xfree(_si->sn_vars); \
+ xfree(_si->sn_name); \
+ ga_clear(&_si->sn_prl_ga); \
+ xfree(_si); \
+ } while (0) \
+
+ GA_DEEP_CLEAR(&script_items, scriptitem_T *, FREE_SCRIPTNAME);
}
#endif
+void free_autoload_scriptnames(void)
+{
+ ga_clear_strings(&ga_loaded);
+}
+
linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
FUNC_ATTR_PURE
{
return fgetline == getsourceline
- ? ((struct source_cookie *)cookie)->sourcing_lnum
- : SOURCING_LNUM;
+ ? ((struct source_cookie *)cookie)->sourcing_lnum
+ : SOURCING_LNUM;
+}
+
+/// Return a List of script-local functions defined in the script with id "sid".
+static list_T *get_script_local_funcs(scid_T sid)
+{
+ hashtab_T *const functbl = func_tbl_get();
+ list_T *l = tv_list_alloc((ptrdiff_t)functbl->ht_used);
+
+ // Iterate through all the functions in the global function hash table
+ // looking for functions with script ID "sid".
+ HASHTAB_ITER(functbl, hi, {
+ const ufunc_T *const fp = HI2UF(hi);
+ // Add functions with script id == "sid"
+ if (fp->uf_script_ctx.sc_sid == sid) {
+ const char *const name = fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
+ tv_list_append_string(l, name, -1);
+ }
+ });
+
+ return l;
+}
+
+/// "getscriptinfo()" function
+void f_getscriptinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, script_items.ga_len);
+
+ if (tv_check_for_opt_dict_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ list_T *l = rettv->vval.v_list;
+
+ regmatch_T regmatch = {
+ .regprog = NULL,
+ .rm_ic = p_ic,
+ };
+ bool filterpat = false;
+ varnumber_T sid = -1;
+
+ char *pat = NULL;
+ if (argvars[0].v_type == VAR_DICT) {
+ dictitem_T *sid_di = tv_dict_find(argvars[0].vval.v_dict, S_LEN("sid"));
+ if (sid_di != NULL) {
+ bool error = false;
+ sid = tv_get_number_chk(&sid_di->di_tv, &error);
+ if (error) {
+ return;
+ }
+ if (sid <= 0) {
+ semsg(_(e_invargNval), "sid", tv_get_string(&sid_di->di_tv));
+ return;
+ }
+ } else {
+ pat = tv_dict_get_string(argvars[0].vval.v_dict, "name", true);
+ if (pat != NULL) {
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+ }
+ if (regmatch.regprog != NULL) {
+ filterpat = true;
+ }
+ }
+ }
+
+ for (varnumber_T i = sid > 0 ? sid : 1;
+ (i == sid || sid <= 0) && i <= script_items.ga_len; i++) {
+ scriptitem_T *si = SCRIPT_ITEM(i);
+
+ if (si->sn_name == NULL) {
+ continue;
+ }
+
+ if (filterpat && !vim_regexec(&regmatch, si->sn_name, 0)) {
+ continue;
+ }
+
+ dict_T *d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ tv_dict_add_str(d, S_LEN("name"), si->sn_name);
+ tv_dict_add_nr(d, S_LEN("sid"), i);
+ tv_dict_add_nr(d, S_LEN("version"), 1);
+ // Vim9 autoload script (:h vim9-autoload), not applicable to Nvim.
+ tv_dict_add_bool(d, S_LEN("autoload"), false);
+
+ // When a script ID is specified, return information about only the
+ // specified script, and add the script-local variables and functions.
+ if (sid > 0) {
+ dict_T *var_dict = tv_dict_copy(NULL, &si->sn_vars->sv_dict, true, get_copyID());
+ tv_dict_add_dict(d, S_LEN("variables"), var_dict);
+ tv_dict_add_list(d, S_LEN("functions"), get_script_local_funcs((scid_T)sid));
+ }
+ }
+
+ vim_regfree(regmatch.regprog);
+ xfree(pat);
}
/// Get one full line from a sourced file.
@@ -2249,7 +2540,6 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
{
struct source_cookie *sp = (struct source_cookie *)cookie;
char *line;
- char *p;
// If breakpoints have been added/deleted need to check for it.
if (sp->dbg_tick < debug_tick) {
@@ -2279,6 +2569,7 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
// Only concatenate lines starting with a \ when 'cpoptions' doesn't
// contain the 'C' flag.
if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
+ char *p;
// compensate for the one line read-ahead
sp->sourcing_lnum--;
@@ -2306,10 +2597,8 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
}
if (line != NULL && sp->conv.vc_type != CONV_NONE) {
- char *s;
-
// Convert the encoding of the script line.
- s = string_convert(&sp->conv, line, NULL);
+ char *s = string_convert(&sp->conv, line, NULL);
if (s != NULL) {
xfree(line);
line = s;
@@ -2343,7 +2632,7 @@ static char *get_one_sourceline(struct source_cookie *sp)
// Loop until there is a finished line (or end-of-file).
sp->sourcing_lnum++;
- for (;;) {
+ while (true) {
// make room to read at least 120 (more) characters
ga_grow(&ga, 120);
buf = ga.ga_data;
@@ -2471,8 +2760,6 @@ void ex_finish(exarg_T *eap)
/// an extra do_cmdline(). "reanimate" is used in the latter case.
void do_finish(exarg_T *eap, int reanimate)
{
- int idx;
-
if (reanimate) {
((struct source_cookie *)getline_cookie(eap->getline,
eap->cookie))->finished = false;
@@ -2482,7 +2769,7 @@ void do_finish(exarg_T *eap, int reanimate)
// not in its finally clause (which then is to be executed next) is found.
// In this case, make the ":finish" pending for execution at the ":endtry".
// Otherwise, finish normally.
- idx = cleanup_conditionals(eap->cstack, 0, true);
+ int idx = cleanup_conditionals(eap->cstack, 0, true);
if (idx >= 0) {
eap->cstack->cs_pending[idx] = CSTP_FINISH;
report_make_pending(CSTP_FINISH, NULL);
@@ -2500,3 +2787,79 @@ bool source_finished(LineGetter fgetline, void *cookie)
return getline_equal(fgetline, cookie, getsourceline)
&& ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished;
}
+
+/// Return the autoload script name for a function or variable name
+/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
+///
+/// @param[in] name Variable/function name.
+/// @param[in] name_len Name length.
+///
+/// @return [allocated] autoload script name.
+char *autoload_name(const char *const name, const size_t name_len)
+ FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Get the script file name: replace '#' with '/', append ".vim".
+ char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
+ memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
+ memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
+ size_t auchar_idx = 0;
+ for (size_t i = sizeof("autoload/") - 1;
+ i - sizeof("autoload/") + 1 < name_len;
+ i++) {
+ if (scriptname[i] == AUTOLOAD_CHAR) {
+ scriptname[i] = '/';
+ auchar_idx = i;
+ }
+ }
+ memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
+
+ return scriptname;
+}
+
+/// If name has a package name try autoloading the script for it
+///
+/// @param[in] name Variable/function name.
+/// @param[in] name_len Name length.
+/// @param[in] reload If true, load script again when already loaded.
+///
+/// @return true if a package was loaded.
+bool script_autoload(const char *const name, const size_t name_len, const bool reload)
+{
+ // If there is no '#' after name[0] there is no package name.
+ const char *p = memchr(name, AUTOLOAD_CHAR, name_len);
+ if (p == NULL || p == name) {
+ return false;
+ }
+
+ bool ret = false;
+ char *tofree = autoload_name(name, name_len);
+ char *scriptname = tofree;
+
+ // Find the name in the list of previously loaded package names. Skip
+ // "autoload/", it's always the same.
+ int i = 0;
+ for (; i < ga_loaded.ga_len; i++) {
+ if (strcmp(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) {
+ break;
+ }
+ }
+ if (!reload && i < ga_loaded.ga_len) {
+ ret = false; // Was loaded already.
+ } else {
+ // Remember the name if it wasn't loaded already.
+ if (i == ga_loaded.ga_len) {
+ GA_APPEND(char *, &ga_loaded, scriptname);
+ tofree = NULL;
+ }
+
+ // Try loading the package from $VIMRUNTIME/autoload/<name>.vim
+ // Use "ret_sid" to avoid loading the same script again.
+ int ret_sid;
+ if (do_in_runtimepath(scriptname, 0, source_callback, &ret_sid) == OK) {
+ ret = true;
+ }
+ }
+
+ xfree(tofree);
+ return ret;
+}
diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h
index 97063b900c..87e4436618 100644
--- a/src/nvim/runtime.h
+++ b/src/nvim/runtime.h
@@ -1,43 +1,16 @@
-#ifndef NVIM_RUNTIME_H
-#define NVIM_RUNTIME_H
+#pragma once
-#include <stdbool.h>
+#include <stddef.h> // IWYU pragma: keep
-#include "klib/kvec.h"
-#include "nvim/autocmd.h"
-#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/ex_eval_defs.h"
-#include "nvim/garray.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-typedef enum {
- ETYPE_TOP, ///< toplevel
- ETYPE_SCRIPT, ///< sourcing script, use es_info.sctx
- ETYPE_UFUNC, ///< user function, use es_info.ufunc
- ETYPE_AUCMD, ///< autocomand, use es_info.aucmd
- ETYPE_MODELINE, ///< modeline, use es_info.sctx
- ETYPE_EXCEPT, ///< exception, use es_info.exception
- ETYPE_ARGS, ///< command line argument
- ETYPE_ENV, ///< environment variable
- ETYPE_INTERNAL, ///< internal operation
- ETYPE_SPELL, ///< loading spell file
-} etype_T;
-
-/// Entry in the execution stack "exestack".
-typedef struct {
- linenr_T es_lnum; ///< replaces "sourcing_lnum"
- char *es_name; ///< replaces "sourcing_name"
- etype_T es_type;
- union {
- sctx_T *sctx; ///< script and modeline info
- ufunc_T *ufunc; ///< function info
- AutoPatCmd *aucmd; ///< autocommand info
- except_T *except; ///< exception info
- } es_info;
-} estack_T;
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/runtime_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
/// Stack of execution contexts. Each entry is an estack_T.
/// Current context is at ga_len - 1.
@@ -47,67 +20,30 @@ extern garray_T exestack;
/// line number in the message source or zero
#define SOURCING_LNUM (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_lnum)
-/// Argument for estack_sfile().
-typedef enum {
- ESTACK_NONE,
- ESTACK_SFILE,
- ESTACK_STACK,
- ESTACK_SCRIPT,
-} estack_arg_T;
-
-typedef struct scriptitem_S {
- char *sn_name;
- bool sn_prof_on; ///< true when script is/was profiled
- bool sn_pr_force; ///< forceit: profile functions in this script
- proftime_T sn_pr_child; ///< time set when going into first child
- int sn_pr_nest; ///< nesting for sn_pr_child
- // profiling the script as a whole
- int sn_pr_count; ///< nr of times sourced
- proftime_T sn_pr_total; ///< time spent in script + children
- proftime_T sn_pr_self; ///< time spent in script itself
- proftime_T sn_pr_start; ///< time at script start
- proftime_T sn_pr_children; ///< time in children after script start
- // profiling the script per line
- garray_T sn_prl_ga; ///< things stored for every line
- proftime_T sn_prl_start; ///< start time for current line
- proftime_T sn_prl_children; ///< time spent in children for this line
- proftime_T sn_prl_wait; ///< wait start time for current line
- linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
- int sn_prl_execed; ///< line being timed was executed
-} scriptitem_T;
-
/// Growarray to store info about already sourced scripts.
extern garray_T script_items;
-#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1])
+#define SCRIPT_ITEM(id) (((scriptitem_T **)script_items.ga_data)[(id) - 1])
#define SCRIPT_ID_VALID(id) ((id) > 0 && (id) <= script_items.ga_len)
-typedef void (*DoInRuntimepathCB)(char *, void *);
-
-typedef struct {
- char *path;
- bool after;
- TriState has_lua;
-} SearchPathItem;
-
-typedef kvec_t(SearchPathItem) RuntimeSearchPath;
-typedef kvec_t(char *) CharVec;
-
-// last argument for do_source()
-#define DOSO_NONE 0
-#define DOSO_VIMRC 1 // loading vimrc file
-
-// Used for flags in do_in_path()
-#define DIP_ALL 0x01 // all matches, not just the first one
-#define DIP_DIR 0x02 // find directories instead of files
-#define DIP_ERR 0x04 // give an error message when none found
-#define DIP_START 0x08 // also use "start" directory in 'packpath'
-#define DIP_OPT 0x10 // also use "opt" directory in 'packpath'
-#define DIP_NORTP 0x20 // do not use 'runtimepath'
-#define DIP_NOAFTER 0x40 // skip "after" directories
-#define DIP_AFTER 0x80 // only use "after" directories
-#define DIP_DIRFILE 0x200 // find both files and directories
+/// last argument for do_source()
+enum {
+ DOSO_NONE = 0,
+ DOSO_VIMRC = 1, ///< loading vimrc file
+};
+
+/// Used for flags in do_in_path()
+enum {
+ DIP_ALL = 0x01, ///< all matches, not just the first one
+ DIP_DIR = 0x02, ///< find directories instead of files
+ DIP_ERR = 0x04, ///< give an error message when none found
+ DIP_START = 0x08, ///< also use "start" directory in 'packpath'
+ DIP_OPT = 0x10, ///< also use "opt" directory in 'packpath'
+ DIP_NORTP = 0x20, ///< do not use 'runtimepath'
+ DIP_NOAFTER = 0x40, ///< skip "after" directories
+ DIP_AFTER = 0x80, ///< only use "after" directories
+ DIP_DIRFILE = 0x200, ///< find both files and directories
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "runtime.h.generated.h"
#endif
-#endif // NVIM_RUNTIME_H
diff --git a/src/nvim/runtime_defs.h b/src/nvim/runtime_defs.h
new file mode 100644
index 0000000000..169cadfb94
--- /dev/null
+++ b/src/nvim/runtime_defs.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "nvim/autocmd_defs.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_eval_defs.h"
+#include "nvim/garray_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
+
+typedef enum {
+ ETYPE_TOP, ///< toplevel
+ ETYPE_SCRIPT, ///< sourcing script, use es_info.sctx
+ ETYPE_UFUNC, ///< user function, use es_info.ufunc
+ ETYPE_AUCMD, ///< autocomand, use es_info.aucmd
+ ETYPE_MODELINE, ///< modeline, use es_info.sctx
+ ETYPE_EXCEPT, ///< exception, use es_info.exception
+ ETYPE_ARGS, ///< command line argument
+ ETYPE_ENV, ///< environment variable
+ ETYPE_INTERNAL, ///< internal operation
+ ETYPE_SPELL, ///< loading spell file
+} etype_T;
+
+/// Entry in the execution stack "exestack".
+typedef struct {
+ linenr_T es_lnum; ///< replaces "sourcing_lnum"
+ char *es_name; ///< replaces "sourcing_name"
+ etype_T es_type;
+ union {
+ sctx_T *sctx; ///< script and modeline info
+ ufunc_T *ufunc; ///< function info
+ AutoPatCmd *aucmd; ///< autocommand info
+ except_T *except; ///< exception info
+ } es_info;
+} estack_T;
+
+/// Argument for estack_sfile().
+typedef enum {
+ ESTACK_NONE,
+ ESTACK_SFILE,
+ ESTACK_STACK,
+ ESTACK_SCRIPT,
+} estack_arg_T;
+
+/// Holds the hashtab with variables local to each sourced script.
+/// Each item holds a variable (nameless) that points to the dict_T.
+typedef struct {
+ ScopeDictDictItem sv_var;
+ dict_T sv_dict;
+} scriptvar_T;
+
+typedef struct {
+ scriptvar_T *sn_vars; ///< stores s: variables for this script
+
+ char *sn_name;
+ bool sn_prof_on; ///< true when script is/was profiled
+ bool sn_pr_force; ///< forceit: profile functions in this script
+ proftime_T sn_pr_child; ///< time set when going into first child
+ int sn_pr_nest; ///< nesting for sn_pr_child
+ // profiling the script as a whole
+ int sn_pr_count; ///< nr of times sourced
+ proftime_T sn_pr_total; ///< time spent in script + children
+ proftime_T sn_pr_self; ///< time spent in script itself
+ proftime_T sn_pr_start; ///< time at script start
+ proftime_T sn_pr_children; ///< time in children after script start
+ // profiling the script per line
+ garray_T sn_prl_ga; ///< things stored for every line
+ proftime_T sn_prl_start; ///< start time for current line
+ proftime_T sn_prl_children; ///< time spent in children for this line
+ proftime_T sn_prl_wait; ///< wait start time for current line
+ linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
+ int sn_prl_execed; ///< line being timed was executed
+} scriptitem_T;
+
+typedef bool (*DoInRuntimepathCB)(int, char **, bool, void *);
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
deleted file mode 100644
index ebff52cd69..0000000000
--- a/src/nvim/screen.c
+++ /dev/null
@@ -1,1115 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// screen.c: Lower level code for displaying on the screen.
-// grid.c contains some other lower-level code.
-
-// Output to the screen (console, terminal emulator or GUI window) is minimized
-// by remembering what is already on the screen, and only updating the parts
-// that changed.
-
-#include <assert.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "nvim/ascii.h"
-#include "nvim/buffer.h"
-#include "nvim/charset.h"
-#include "nvim/cursor.h"
-#include "nvim/eval.h"
-#include "nvim/fold.h"
-#include "nvim/getchar.h"
-#include "nvim/gettext.h"
-#include "nvim/globals.h"
-#include "nvim/grid.h"
-#include "nvim/grid_defs.h"
-#include "nvim/highlight.h"
-#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
-#include "nvim/memory.h"
-#include "nvim/message.h"
-#include "nvim/move.h"
-#include "nvim/normal.h"
-#include "nvim/option.h"
-#include "nvim/os/os.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
-#include "nvim/profile.h"
-#include "nvim/regexp.h"
-#include "nvim/screen.h"
-#include "nvim/search.h"
-#include "nvim/state.h"
-#include "nvim/statusline.h"
-#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/ui.h"
-#include "nvim/vim.h"
-#include "nvim/window.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "screen.c.generated.h"
-#endif
-
-static char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'");
-static char e_conflicts_with_value_of_fillchars[] = N_("E835: Conflicts with value of 'fillchars'");
-
-/// Return true if the cursor line in window "wp" may be concealed, according
-/// to the 'concealcursor' option.
-bool conceal_cursor_line(const win_T *wp)
- FUNC_ATTR_NONNULL_ALL
-{
- int c;
-
- if (*wp->w_p_cocu == NUL) {
- return false;
- }
- if (get_real_state() & MODE_VISUAL) {
- c = 'v';
- } else if (State & MODE_INSERT) {
- c = 'i';
- } else if (State & MODE_NORMAL) {
- c = 'n';
- } else if (State & MODE_CMDLINE) {
- c = 'c';
- } else {
- return false;
- }
- return vim_strchr(wp->w_p_cocu, c) != NULL;
-}
-
-/// Whether cursorline is drawn in a special way
-///
-/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows.
-bool win_cursorline_standout(const win_T *wp)
- FUNC_ATTR_NONNULL_ALL
-{
- return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
-}
-
-/// Returns width of the signcolumn that should be used for the whole window
-///
-/// @param wp window we want signcolumn width from
-/// @return max width of signcolumn (cell unit)
-///
-/// @note Returns a constant for now but hopefully we can improve neovim so that
-/// the returned value width adapts to the maximum number of marks to draw
-/// for the window
-/// TODO(teto)
-int win_signcol_width(win_T *wp)
-{
- // 2 is vim default value
- return 2;
-}
-
-/// Call grid_fill() with columns adjusted for 'rightleft' if needed.
-/// Return the new offset.
-static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, int endrow,
- int attr)
-{
- int nn = off + width;
-
- if (nn > wp->w_grid.cols) {
- nn = wp->w_grid.cols;
- }
-
- if (wp->w_p_rl) {
- grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - nn, W_ENDCOL(wp) - off,
- c1, c2, attr);
- } else {
- grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr);
- }
-
- return nn;
-}
-
-/// Clear lines near the end of the window and mark the unused lines with "c1".
-/// Use "c2" as filler character.
-/// When "draw_margin" is true, then draw the sign/fold/number columns.
-void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl)
-{
- assert(hl >= 0 && hl < HLF_COUNT);
- int n = 0;
-
- if (draw_margin) {
- // draw the fold column
- int fdc = compute_foldcolumn(wp, 0);
- if (fdc > 0) {
- n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow,
- win_hl_attr(wp, HLF_FC));
- }
- // draw the sign column
- int count = wp->w_scwidth;
- if (count > 0) {
- n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row,
- endrow, win_hl_attr(wp, HLF_SC));
- }
- // draw the number column
- if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) {
- n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow,
- win_hl_attr(wp, HLF_N));
- }
- }
-
- int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl));
-
- if (wp->w_p_rl) {
- grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n,
- c2, c2, attr);
- grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - 1 - n, W_ENDCOL(wp) - n,
- c1, c2, attr);
- } else {
- grid_fill(&wp->w_grid, row, endrow, n, wp->w_grid.cols, c1, c2, attr);
- }
-}
-
-/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
-/// space is available for window "wp", minus "col".
-int compute_foldcolumn(win_T *wp, int col)
-{
- int fdc = win_fdccol_count(wp);
- int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw;
- int wwidth = wp->w_grid.cols;
-
- if (fdc > wwidth - (col + wmw)) {
- fdc = wwidth - (col + wmw);
- }
- return fdc;
-}
-
-/// Fills the foldcolumn at "p" for window "wp".
-/// Only to be called when 'foldcolumn' > 0.
-///
-/// @param[out] p Char array to write into
-/// @param lnum Absolute current line number
-/// @param closed Whether it is in 'foldcolumn' mode
-///
-/// Assume monocell characters
-/// @return number of chars added to \param p
-size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
-{
- int i = 0;
- int level;
- int first_level;
- int fdc = compute_foldcolumn(wp, 0); // available cell width
- size_t char_counter = 0;
- int symbol = 0;
- int len = 0;
- bool closed = foldinfo.fi_lines > 0;
- // Init to all spaces.
- memset(p, ' ', MAX_MCO * (size_t)fdc + 1);
-
- level = foldinfo.fi_level;
-
- // If the column is too narrow, we start at the lowest level that
- // fits and use numbers to indicate the depth.
- first_level = level - fdc - closed + 1;
- if (first_level < 1) {
- first_level = 1;
- }
-
- for (i = 0; i < MIN(fdc, level); i++) {
- if (foldinfo.fi_lnum == lnum
- && first_level + i >= foldinfo.fi_low_level) {
- symbol = wp->w_p_fcs_chars.foldopen;
- } else if (first_level == 1) {
- symbol = wp->w_p_fcs_chars.foldsep;
- } else if (first_level + i <= 9) {
- symbol = '0' + first_level + i;
- } else {
- symbol = '>';
- }
-
- len = utf_char2bytes(symbol, &p[char_counter]);
- char_counter += (size_t)len;
- if (first_level + i >= level) {
- i++;
- break;
- }
- }
-
- if (closed) {
- if (symbol != 0) {
- // rollback previous write
- char_counter -= (size_t)len;
- memset(&p[char_counter], ' ', (size_t)len);
- }
- len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
- char_counter += (size_t)len;
- }
-
- return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc);
-}
-
-/// Mirror text "str" for right-left displaying.
-/// Only works for single-byte characters (e.g., numbers).
-void rl_mirror(char *str)
-{
- char *p1, *p2;
- char t;
-
- for (p1 = str, p2 = str + strlen(str) - 1; p1 < p2; p1++, p2--) {
- t = *p1;
- *p1 = *p2;
- *p2 = t;
- }
-}
-
-/// Only call if (wp->w_vsep_width != 0).
-///
-/// @return true if the status line of window "wp" is connected to the status
-/// line of the window right of it. If not, then it's a vertical separator.
-bool stl_connected(win_T *wp)
-{
- frame_T *fr;
-
- fr = wp->w_frame;
- while (fr->fr_parent != NULL) {
- if (fr->fr_parent->fr_layout == FR_COL) {
- if (fr->fr_next != NULL) {
- break;
- }
- } else {
- if (fr->fr_next != NULL) {
- return true;
- }
- }
- fr = fr->fr_parent;
- }
- return false;
-}
-
-/// Get the value to show for the language mappings, active 'keymap'.
-///
-/// @param fmt format string containing one %s item
-/// @param buf buffer for the result
-/// @param len length of buffer
-bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len)
-{
- char *p;
-
- if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP) {
- return false;
- }
-
- buf_T *old_curbuf = curbuf;
- win_T *old_curwin = curwin;
- char *s;
-
- curbuf = wp->w_buffer;
- curwin = wp;
- STRCPY(buf, "b:keymap_name"); // must be writable
- emsg_skip++;
- s = p = eval_to_string(buf, NULL, false);
- emsg_skip--;
- curbuf = old_curbuf;
- curwin = old_curwin;
- if (p == NULL || *p == NUL) {
- if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) {
- p = wp->w_buffer->b_p_keymap;
- } else {
- p = "lang";
- }
- }
- if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) {
- buf[0] = NUL;
- }
- xfree(s);
- return buf[0] != NUL;
-}
-
-/// Prepare for 'hlsearch' highlighting.
-void start_search_hl(void)
-{
- if (!p_hls || no_hlsearch) {
- return;
- }
-
- end_search_hl(); // just in case it wasn't called before
- last_pat_prog(&screen_search_hl.rm);
- // Set the time limit to 'redrawtime'.
- screen_search_hl.tm = profile_setlimit(p_rdt);
-}
-
-/// Clean up for 'hlsearch' highlighting.
-void end_search_hl(void)
-{
- if (screen_search_hl.rm.regprog == NULL) {
- return;
- }
-
- vim_regfree(screen_search_hl.rm.regprog);
- screen_search_hl.rm.regprog = NULL;
-}
-
-/// Check if there should be a delay. Used before clearing or redrawing the
-/// screen or the command line.
-void check_for_delay(bool check_msg_scroll)
-{
- if ((emsg_on_display || (check_msg_scroll && msg_scroll))
- && !did_wait_return
- && emsg_silent == 0
- && !in_assert_fails) {
- ui_flush();
- os_delay(1006L, true);
- emsg_on_display = false;
- if (check_msg_scroll) {
- msg_scroll = false;
- }
- }
-}
-
-/// Set cursor to its position in the current window.
-void setcursor(void)
-{
- setcursor_mayforce(false);
-}
-
-/// Set cursor to its position in the current window.
-/// @param force when true, also when not redrawing.
-void setcursor_mayforce(bool force)
-{
- if (force || redrawing()) {
- validate_cursor();
-
- ScreenGrid *grid = &curwin->w_grid;
- int row = curwin->w_wrow;
- int col = curwin->w_wcol;
- if (curwin->w_p_rl) {
- // With 'rightleft' set and the cursor on a double-wide character,
- // position it on the leftmost column.
- col = curwin->w_width_inner - curwin->w_wcol
- - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2
- && vim_isprintc(gchar_cursor())) ? 2 : 1);
- }
-
- grid_adjust(&grid, &row, &col);
- ui_grid_cursor_goto(grid->handle, row, col);
- }
-}
-
-/// Scroll `line_count` lines at 'row' in window 'wp'.
-///
-/// Positive `line_count` means scrolling down, so that more space is available
-/// at 'row'. Negative `line_count` implies deleting lines at `row`.
-void win_scroll_lines(win_T *wp, int row, int line_count)
-{
- if (!redrawing() || line_count == 0) {
- return;
- }
-
- // No lines are being moved, just draw over the entire area
- if (row + abs(line_count) >= wp->w_grid.rows) {
- return;
- }
-
- if (line_count < 0) {
- grid_del_lines(&wp->w_grid, row, -line_count,
- wp->w_grid.rows, 0, wp->w_grid.cols);
- } else {
- grid_ins_lines(&wp->w_grid, row, line_count,
- wp->w_grid.rows, 0, wp->w_grid.cols);
- }
-}
-
-/// @return true when postponing displaying the mode message: when not redrawing
-/// or inside a mapping.
-bool skip_showmode(void)
-{
- // Call char_avail() only when we are going to show something, because it
- // takes a bit of time. redrawing() may also call char_avail().
- if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) {
- redraw_mode = true; // show mode later
- return true;
- }
- return false;
-}
-
-/// Show the current mode and ruler.
-///
-/// If clear_cmdline is true, clear the rest of the cmdline.
-/// If clear_cmdline is false there may be a message there that needs to be
-/// cleared only if a mode is shown.
-/// If redraw_mode is true show or clear the mode.
-/// @return the length of the message (0 if no message).
-int showmode(void)
-{
- bool need_clear;
- int length = 0;
- int do_mode;
- int attr;
- int sub_attr;
-
- if (ui_has(kUIMessages) && clear_cmdline) {
- msg_ext_clear(true);
- }
-
- // don't make non-flushed message part of the showmode
- msg_ext_ui_flush();
-
- msg_grid_validate();
-
- do_mode = ((p_smd && msg_silent == 0)
- && ((State & MODE_TERMINAL)
- || (State & MODE_INSERT)
- || restart_edit != NUL
- || VIsual_active));
- if (do_mode || reg_recording != 0) {
- if (skip_showmode()) {
- return 0; // show mode later
- }
-
- bool nwr_save = need_wait_return;
-
- // wait a bit before overwriting an important message
- check_for_delay(false);
-
- // if the cmdline is more than one line high, erase top lines
- need_clear = clear_cmdline;
- if (clear_cmdline && cmdline_row < Rows - 1) {
- msg_clr_cmdline(); // will reset clear_cmdline
- }
-
- // Position on the last line in the window, column 0
- msg_pos_mode();
- attr = HL_ATTR(HLF_CM); // Highlight mode
-
- // When the screen is too narrow to show the entire mode message,
- // avoid scrolling and truncate instead.
- msg_no_more = true;
- int save_lines_left = lines_left;
- lines_left = 0;
-
- if (do_mode) {
- msg_puts_attr("--", attr);
- // CTRL-X in Insert mode
- if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) {
- // These messages can get long, avoid a wrap in a narrow window.
- // Prefer showing edit_submode_extra. With external messages there
- // is no imposed limit.
- if (ui_has(kUIMessages)) {
- length = INT_MAX;
- } else {
- length = (Rows - msg_row) * Columns - 3;
- }
- if (edit_submode_extra != NULL) {
- length -= vim_strsize(edit_submode_extra);
- }
- if (length > 0) {
- if (edit_submode_pre != NULL) {
- length -= vim_strsize(edit_submode_pre);
- }
- if (length - vim_strsize(edit_submode) > 0) {
- if (edit_submode_pre != NULL) {
- msg_puts_attr((const char *)edit_submode_pre, attr);
- }
- msg_puts_attr((const char *)edit_submode, attr);
- }
- if (edit_submode_extra != NULL) {
- msg_puts_attr(" ", attr); // Add a space in between.
- if ((int)edit_submode_highl < HLF_COUNT) {
- sub_attr = win_hl_attr(curwin, (int)edit_submode_highl);
- } else {
- sub_attr = attr;
- }
- msg_puts_attr((const char *)edit_submode_extra, sub_attr);
- }
- }
- } else {
- if (State & MODE_TERMINAL) {
- msg_puts_attr(_(" TERMINAL"), attr);
- } else if (State & VREPLACE_FLAG) {
- msg_puts_attr(_(" VREPLACE"), attr);
- } else if (State & REPLACE_FLAG) {
- msg_puts_attr(_(" REPLACE"), attr);
- } else if (State & MODE_INSERT) {
- if (p_ri) {
- msg_puts_attr(_(" REVERSE"), attr);
- }
- msg_puts_attr(_(" INSERT"), attr);
- } else if (restart_edit == 'I' || restart_edit == 'i'
- || restart_edit == 'a' || restart_edit == 'A') {
- if (curbuf->terminal) {
- msg_puts_attr(_(" (terminal)"), attr);
- } else {
- msg_puts_attr(_(" (insert)"), attr);
- }
- } else if (restart_edit == 'R') {
- msg_puts_attr(_(" (replace)"), attr);
- } else if (restart_edit == 'V') {
- msg_puts_attr(_(" (vreplace)"), attr);
- }
- if (p_hkmap) {
- msg_puts_attr(_(" Hebrew"), attr);
- }
- if (State & MODE_LANGMAP) {
- if (curwin->w_p_arab) {
- msg_puts_attr(_(" Arabic"), attr);
- } else if (get_keymap_str(curwin, " (%s)",
- NameBuff, MAXPATHL)) {
- msg_puts_attr(NameBuff, attr);
- }
- }
- if ((State & MODE_INSERT) && p_paste) {
- msg_puts_attr(_(" (paste)"), attr);
- }
-
- if (VIsual_active) {
- char *p;
-
- // Don't concatenate separate words to avoid translation
- // problems.
- switch ((VIsual_select ? 4 : 0)
- + (VIsual_mode == Ctrl_V) * 2
- + (VIsual_mode == 'V')) {
- case 0:
- p = N_(" VISUAL"); break;
- case 1:
- p = N_(" VISUAL LINE"); break;
- case 2:
- p = N_(" VISUAL BLOCK"); break;
- case 4:
- p = N_(" SELECT"); break;
- case 5:
- p = N_(" SELECT LINE"); break;
- default:
- p = N_(" SELECT BLOCK"); break;
- }
- msg_puts_attr(_(p), attr);
- }
- msg_puts_attr(" --", attr);
- }
-
- need_clear = true;
- }
- if (reg_recording != 0
- && edit_submode == NULL // otherwise it gets too long
- ) {
- recording_mode(attr);
- need_clear = true;
- }
-
- mode_displayed = true;
- if (need_clear || clear_cmdline || redraw_mode) {
- msg_clr_eos();
- }
- msg_didout = false; // overwrite this message
- length = msg_col;
- msg_col = 0;
- msg_no_more = false;
- lines_left = save_lines_left;
- need_wait_return = nwr_save; // never ask for hit-return for this
- } else if (clear_cmdline && msg_silent == 0) {
- // Clear the whole command line. Will reset "clear_cmdline".
- msg_clr_cmdline();
- } else if (redraw_mode) {
- msg_pos_mode();
- msg_clr_eos();
- }
-
- // NB: also handles clearing the showmode if it was empty or disabled
- msg_ext_flush_showmode();
-
- // In Visual mode the size of the selected area must be redrawn.
- if (VIsual_active) {
- clear_showcmd();
- }
-
- // If the last window has no status line and global statusline is disabled,
- // the ruler is after the mode message and must be redrawn
- win_T *last = lastwin_nofloating();
- if (redrawing() && last->w_status_height == 0 && global_stl_height() == 0) {
- win_redr_ruler(last, true);
- }
-
- redraw_cmdline = false;
- redraw_mode = false;
- clear_cmdline = false;
-
- return length;
-}
-
-/// Position for a mode message.
-static void msg_pos_mode(void)
-{
- msg_col = 0;
- msg_row = Rows - 1;
-}
-
-/// Delete mode message. Used when ESC is typed which is expected to end
-/// Insert mode (but Insert mode didn't end yet!).
-/// Caller should check "mode_displayed".
-void unshowmode(bool force)
-{
- // Don't delete it right now, when not redrawing or inside a mapping.
- if (!redrawing() || (!force && char_avail() && !KeyTyped)) {
- redraw_cmdline = true; // delete mode later
- } else {
- clearmode();
- }
-}
-
-// Clear the mode message.
-void clearmode(void)
-{
- const int save_msg_row = msg_row;
- const int save_msg_col = msg_col;
-
- msg_ext_ui_flush();
- msg_pos_mode();
- if (reg_recording != 0) {
- recording_mode(HL_ATTR(HLF_CM));
- }
- msg_clr_eos();
- msg_ext_flush_showmode();
-
- msg_col = save_msg_col;
- msg_row = save_msg_row;
-}
-
-static void recording_mode(int attr)
-{
- msg_puts_attr(_("recording"), attr);
- if (shortmess(SHM_RECORDING)) {
- return;
- }
-
- char s[4];
- snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
- msg_puts_attr(s, attr);
-}
-
-void get_trans_bufname(buf_T *buf)
-{
- if (buf_spname(buf) != NULL) {
- xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL);
- } else {
- home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true);
- }
- trans_characters(NameBuff, MAXPATHL);
-}
-
-/// Get the character to use in a separator between vertically split windows.
-/// Get its attributes in "*attr".
-int fillchar_vsep(win_T *wp, int *attr)
-{
- *attr = win_hl_attr(wp, HLF_C);
- return wp->w_p_fcs_chars.vert;
-}
-
-/// Get the character to use in a separator between horizontally split windows.
-/// Get its attributes in "*attr".
-int fillchar_hsep(win_T *wp, int *attr)
-{
- *attr = win_hl_attr(wp, HLF_C);
- return wp->w_p_fcs_chars.horiz;
-}
-
-/// Return true if redrawing should currently be done.
-bool redrawing(void)
-{
- return !RedrawingDisabled
- && !(p_lz && char_avail() && !KeyTyped && !do_redraw);
-}
-
-/// Return true if printing messages should currently be done.
-bool messaging(void)
-{
- // TODO(bfredl): with general support for "async" messages with p_ch,
- // this should be re-enabled.
- return !(p_lz && char_avail() && !KeyTyped) && (p_ch > 0 || ui_has(kUIMessages));
-}
-
-#define COL_RULER 17 // columns needed by standard ruler
-
-/// Compute columns for ruler and shown command. 'sc_col' is also used to
-/// decide what the maximum length of a message on the status line can be.
-/// If there is a status line for the last window, 'sc_col' is independent
-/// of 'ru_col'.
-void comp_col(void)
-{
- int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW));
-
- sc_col = 0;
- ru_col = 0;
- if (p_ru) {
- ru_col = (ru_wid ? ru_wid : COL_RULER) + 1;
- // no last status line, adjust sc_col
- if (!last_has_status) {
- sc_col = ru_col;
- }
- }
- if (p_sc) {
- sc_col += SHOWCMD_COLS;
- if (!p_ru || last_has_status) { // no need for separating space
- sc_col++;
- }
- }
- assert(sc_col >= 0
- && INT_MIN + sc_col <= Columns);
- sc_col = Columns - sc_col;
- assert(ru_col >= 0
- && INT_MIN + ru_col <= Columns);
- ru_col = Columns - ru_col;
- if (sc_col <= 0) { // screen too narrow, will become a mess
- sc_col = 1;
- }
- if (ru_col <= 0) {
- ru_col = 1;
- }
- set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
-}
-
-/// Return the width of the 'number' and 'relativenumber' column.
-/// Caller may need to check if 'number' or 'relativenumber' is set.
-/// Otherwise it depends on 'numberwidth' and the line count.
-int number_width(win_T *wp)
-{
- int n;
- linenr_T lnum;
-
- if (wp->w_p_rnu && !wp->w_p_nu) {
- // cursor line shows "0"
- lnum = wp->w_height_inner;
- } else {
- // cursor line shows absolute line number
- lnum = wp->w_buffer->b_ml.ml_line_count;
- }
-
- if (lnum == wp->w_nrwidth_line_count) {
- return wp->w_nrwidth_width;
- }
- wp->w_nrwidth_line_count = lnum;
-
- // make best estimate for 'statuscolumn'
- if (*wp->w_p_stc != NUL) {
- char buf[MAXPATHL];
- wp->w_nrwidth_width = 0;
- n = build_statuscol_str(wp, lnum, 0, 0, NUL, buf, NULL, NULL);
- n = MAX(n, (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw);
- wp->w_nrwidth_width = MIN(n, MAX_NUMBERWIDTH);
- return wp->w_nrwidth_width;
- }
-
- n = 0;
- do {
- lnum /= 10;
- n++;
- } while (lnum > 0);
-
- // 'numberwidth' gives the minimal width plus one
- if (n < wp->w_p_nuw - 1) {
- n = (int)wp->w_p_nuw - 1;
- }
-
- // If 'signcolumn' is set to 'number' and there is a sign to display, then
- // the minimal width for the number column is 2.
- if (n < 2 && (wp->w_buffer->b_signlist != NULL)
- && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) {
- n = 2;
- }
-
- wp->w_nrwidth_width = n;
- return n;
-}
-
-/// Calls mb_cptr2char_adv(p) and returns the character.
-/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used.
-/// Returns 0 for invalid hex or invalid UTF-8 byte.
-static int get_encoded_char_adv(const char **p)
-{
- const char *s = *p;
-
- if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) {
- int64_t num = 0;
- for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) {
- *p += 2;
- int n = hexhex2nr(*p);
- if (n < 0) {
- return 0;
- }
- num = num * 256 + n;
- }
- *p += 2;
- return (int)num;
- }
-
- // TODO(bfredl): use schar_T representation and utfc_ptr2len
- int clen = utf_ptr2len(s);
- int c = mb_cptr2char_adv(p);
- if (clen == 1 && c > 127) { // Invalid UTF-8 byte
- return 0;
- }
- return c;
-}
-
-/// Handle setting 'listchars' or 'fillchars'.
-/// Assume monocell characters
-///
-/// @param varp either the global or the window-local value.
-/// @param apply if false, do not store the flags, only check for errors.
-/// @return error message, NULL if it's OK.
-char *set_chars_option(win_T *wp, char **varp, bool apply)
-{
- const char *last_multispace = NULL; // Last occurrence of "multispace:"
- const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:"
- int multispace_len = 0; // Length of lcs-multispace string
- int lead_multispace_len = 0; // Length of lcs-leadmultispace string
- const bool is_listchars = (varp == &p_lcs || varp == &wp->w_p_lcs);
-
- struct chars_tab {
- int *cp; ///< char value
- char *name; ///< char id
- int def; ///< default value
- };
-
- // XXX: Characters taking 2 columns is forbidden (TUI limitation?). Set old defaults in this case.
- struct chars_tab fcs_tab[] = {
- { &wp->w_p_fcs_chars.stl, "stl", ' ' },
- { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' },
- { &wp->w_p_fcs_chars.wbr, "wbr", ' ' },
- { &wp->w_p_fcs_chars.horiz, "horiz", char2cells(0x2500) == 1 ? 0x2500 : '-' }, // ─
- { &wp->w_p_fcs_chars.horizup, "horizup", char2cells(0x2534) == 1 ? 0x2534 : '-' }, // â”´
- { &wp->w_p_fcs_chars.horizdown, "horizdown", char2cells(0x252c) == 1 ? 0x252c : '-' }, // ┬
- { &wp->w_p_fcs_chars.vert, "vert", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │
- { &wp->w_p_fcs_chars.vertleft, "vertleft", char2cells(0x2524) == 1 ? 0x2524 : '|' }, // ┤
- { &wp->w_p_fcs_chars.vertright, "vertright", char2cells(0x251c) == 1 ? 0x251c : '|' }, // ├
- { &wp->w_p_fcs_chars.verthoriz, "verthoriz", char2cells(0x253c) == 1 ? 0x253c : '+' }, // ┼
- { &wp->w_p_fcs_chars.fold, "fold", char2cells(0x00b7) == 1 ? 0x00b7 : '-' }, // ·
- { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' },
- { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' },
- { &wp->w_p_fcs_chars.foldsep, "foldsep", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │
- { &wp->w_p_fcs_chars.diff, "diff", '-' },
- { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' },
- { &wp->w_p_fcs_chars.eob, "eob", '~' },
- { &wp->w_p_fcs_chars.lastline, "lastline", '@' },
- };
-
- struct chars_tab lcs_tab[] = {
- { &wp->w_p_lcs_chars.eol, "eol", NUL },
- { &wp->w_p_lcs_chars.ext, "extends", NUL },
- { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL },
- { &wp->w_p_lcs_chars.prec, "precedes", NUL },
- { &wp->w_p_lcs_chars.space, "space", NUL },
- { &wp->w_p_lcs_chars.tab2, "tab", NUL },
- { &wp->w_p_lcs_chars.lead, "lead", NUL },
- { &wp->w_p_lcs_chars.trail, "trail", NUL },
- { &wp->w_p_lcs_chars.conceal, "conceal", NUL },
- };
-
- struct chars_tab *tab;
- int entries;
- const char *value = *varp;
- if (is_listchars) {
- tab = lcs_tab;
- entries = ARRAY_SIZE(lcs_tab);
- if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) {
- value = p_lcs; // local value is empty, use the global value
- }
- } else {
- tab = fcs_tab;
- entries = ARRAY_SIZE(fcs_tab);
- if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) {
- value = p_fcs; // local value is empty, use the global value
- }
- }
-
- // first round: check for valid value, second round: assign values
- for (int round = 0; round <= (apply ? 1 : 0); round++) {
- if (round > 0) {
- // After checking that the value is valid: set defaults
- for (int i = 0; i < entries; i++) {
- if (tab[i].cp != NULL) {
- *(tab[i].cp) = tab[i].def;
- }
- }
- if (is_listchars) {
- wp->w_p_lcs_chars.tab1 = NUL;
- wp->w_p_lcs_chars.tab3 = NUL;
-
- xfree(wp->w_p_lcs_chars.multispace);
- if (multispace_len > 0) {
- wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int));
- wp->w_p_lcs_chars.multispace[multispace_len] = NUL;
- } else {
- wp->w_p_lcs_chars.multispace = NULL;
- }
-
- xfree(wp->w_p_lcs_chars.leadmultispace);
- if (lead_multispace_len > 0) {
- wp->w_p_lcs_chars.leadmultispace
- = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int));
- wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL;
- } else {
- wp->w_p_lcs_chars.leadmultispace = NULL;
- }
- }
- }
- const char *p = value;
- while (*p) {
- int i;
- for (i = 0; i < entries; i++) {
- const size_t len = strlen(tab[i].name);
- if (strncmp(p, tab[i].name, len) == 0
- && p[len] == ':'
- && p[len + 1] != NUL) {
- const char *s = p + len + 1;
- int c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- int c2 = 0, c3 = 0;
- if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
- if (*s == NUL) {
- return e_invarg;
- }
- c2 = get_encoded_char_adv(&s);
- if (c2 == 0 || char2cells(c2) > 1) {
- return e_invarg;
- }
- if (!(*s == ',' || *s == NUL)) {
- c3 = get_encoded_char_adv(&s);
- if (c3 == 0 || char2cells(c3) > 1) {
- return e_invarg;
- }
- }
- }
- if (*s == ',' || *s == NUL) {
- if (round > 0) {
- if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
- wp->w_p_lcs_chars.tab1 = c1;
- wp->w_p_lcs_chars.tab2 = c2;
- wp->w_p_lcs_chars.tab3 = c3;
- } else if (tab[i].cp != NULL) {
- *(tab[i].cp) = c1;
- }
- }
- p = s;
- break;
- }
- }
- }
-
- if (i == entries) {
- const size_t len = strlen("multispace");
- const size_t len2 = strlen("leadmultispace");
- if (is_listchars
- && strncmp(p, "multispace", len) == 0
- && p[len] == ':'
- && p[len + 1] != NUL) {
- const char *s = p + len + 1;
- if (round == 0) {
- // Get length of lcs-multispace string in the first round
- last_multispace = p;
- multispace_len = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- multispace_len++;
- }
- if (multispace_len == 0) {
- // lcs-multispace cannot be an empty string
- return e_invarg;
- }
- p = s;
- } else {
- int multispace_pos = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (p == last_multispace) {
- wp->w_p_lcs_chars.multispace[multispace_pos++] = c1;
- }
- }
- p = s;
- }
- } else if (is_listchars
- && strncmp(p, "leadmultispace", len2) == 0
- && p[len2] == ':'
- && p[len2 + 1] != NUL) {
- const char *s = p + len2 + 1;
- if (round == 0) {
- // get length of lcs-leadmultispace string in first round
- last_lmultispace = p;
- lead_multispace_len = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- lead_multispace_len++;
- }
- if (lead_multispace_len == 0) {
- // lcs-leadmultispace cannot be an empty string
- return e_invarg;
- }
- p = s;
- } else {
- int multispace_pos = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (p == last_lmultispace) {
- wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1;
- }
- }
- p = s;
- }
- } else {
- return e_invarg;
- }
- }
-
- if (*p == ',') {
- p++;
- }
- }
- }
-
- return NULL; // no error
-}
-
-/// Check all global and local values of 'listchars' and 'fillchars'.
-/// May set different defaults in case character widths change.
-///
-/// @return an untranslated error message if any of them is invalid, NULL otherwise.
-char *check_chars_options(void)
-{
- if (set_chars_option(curwin, &p_lcs, false) != NULL) {
- return e_conflicts_with_value_of_listchars;
- }
- if (set_chars_option(curwin, &p_fcs, false) != NULL) {
- return e_conflicts_with_value_of_fillchars;
- }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) {
- return e_conflicts_with_value_of_listchars;
- }
- if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) {
- return e_conflicts_with_value_of_fillchars;
- }
- }
- return NULL;
-}
-
-/// Check if the new Nvim application "screen" dimensions are valid.
-/// Correct it if it's too small or way too big.
-void check_screensize(void)
-{
- // Limit Rows and Columns to avoid an overflow in Rows * Columns.
- if (Rows < min_rows()) {
- // need room for one window and command line
- Rows = min_rows();
- } else if (Rows > 1000) {
- Rows = 1000;
- }
-
- if (Columns < MIN_COLUMNS) {
- Columns = MIN_COLUMNS;
- } else if (Columns > 10000) {
- Columns = 10000;
- }
-}
diff --git a/src/nvim/screen.h b/src/nvim/screen.h
deleted file mode 100644
index 1d8de8ca21..0000000000
--- a/src/nvim/screen.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef NVIM_SCREEN_H
-#define NVIM_SCREEN_H
-
-#include <stdbool.h>
-
-#include "nvim/buffer_defs.h"
-#include "nvim/fold.h"
-#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-
-EXTERN match_T screen_search_hl; // used for 'hlsearch' highlight matching
-
-#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width)
-#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height)
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "screen.h.generated.h"
-#endif
-#endif // NVIM_SCREEN_H
diff --git a/src/nvim/search.c b/src/nvim/search.c
index b24b6ad27c..642219c1e0 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// search.c: code for normal mode searching commands
#include <assert.h>
@@ -11,7 +8,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -27,13 +24,14 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -43,23 +41,31 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
+#include "nvim/plines.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
+#include "nvim/tag.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.c.generated.h"
#endif
+static const char e_search_hit_top_without_match_for_str[]
+ = N_("E384: Search hit TOP without match for: %s");
+static const char e_search_hit_bottom_without_match_for_str[]
+ = N_("E385: Search hit BOTTOM without match for: %s");
+
// This file contains various searching-related routines. These fall into
// three groups:
// 1. string searches (for /, ?, n, and N)
@@ -88,14 +94,14 @@
static struct spat spats[2] = {
// Last used search pattern
- [0] = { NULL, true, false, 0, { '/', false, false, 0L }, NULL },
+ [0] = { NULL, true, false, 0, { '/', false, false, 0 }, NULL },
// Last used substitute pattern
- [1] = { NULL, true, false, 0, { '/', false, false, 0L }, NULL }
+ [1] = { NULL, true, false, 0, { '/', false, false, 0 }, NULL }
};
static int last_idx = 0; // index in spats[] for RE_LAST
-static char_u lastc[2] = { NUL, NUL }; // last character searched for
+static uint8_t lastc[2] = { NUL, NUL }; // last character searched for
static Direction lastcdir = FORWARD; // last direction of character search
static int last_t_cmd = true; // last search t_cmd
static char lastc_bytes[MB_MAXBYTES + 1];
@@ -136,14 +142,12 @@ typedef struct SearchedFile {
int search_regcomp(char *pat, char **used_pat, int pat_save, int pat_use, int options,
regmmatch_T *regmatch)
{
- int magic;
- int i;
-
rc_did_emsg = false;
- magic = magic_isset();
+ int magic = magic_isset();
// If no pattern given, use a previously defined pattern.
if (pat == NULL || *pat == NUL) {
+ int i;
if (pat_use == RE_LAST) {
i = last_idx;
} else {
@@ -343,13 +347,13 @@ void restore_last_search_pattern(void)
static void save_incsearch_state(void)
{
saved_search_match_endcol = search_match_endcol;
- saved_search_match_lines = search_match_lines;
+ saved_search_match_lines = search_match_lines;
}
static void restore_incsearch_state(void)
{
search_match_endcol = saved_search_match_endcol;
- search_match_lines = saved_search_match_lines;
+ search_match_lines = saved_search_match_lines;
}
char *last_search_pattern(void)
@@ -439,7 +443,7 @@ int last_csearch_until(void)
void set_last_csearch(int c, char *s, int len)
{
- *lastc = (char_u)c;
+ *lastc = (uint8_t)c;
lastc_bytelen = len;
if (len) {
memcpy(lastc_bytes, s, (size_t)len);
@@ -547,7 +551,7 @@ void last_pat_prog(regmmatch_T *regmatch)
/// the index of the first matching
/// subpattern plus one; one if there was none.
int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, char *pat,
- long count, int options, int pat_use, searchit_arg_T *extra_arg)
+ int count, int options, int pat_use, searchit_arg_T *extra_arg)
{
int found;
linenr_T lnum; // no init to shut up Apollo cc
@@ -557,12 +561,10 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
lpos_T endpos;
lpos_T matchpos;
int loop;
- pos_T start_pos;
- int at_first_line;
int extra_col;
int start_char_len;
bool match_ok;
- long nmatched;
+ int nmatched;
int submatch = 0;
bool first_match = true;
const int called_emsg_before = called_emsg;
@@ -596,7 +598,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
&& pos->lnum <= buf->b_ml.ml_line_count
&& pos->col < MAXCOL - 2) {
// Watch out for the "col" being MAXCOL - 2, used in a closed fold.
- ptr = ml_get_buf(buf, pos->lnum, false);
+ ptr = ml_get_buf(buf, pos->lnum);
if ((int)strlen(ptr) <= pos->col) {
start_char_len = 1;
} else {
@@ -611,9 +613,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
extra_col = (options & SEARCH_START) ? start_char_len : 0;
}
- start_pos = *pos; // remember start pos for detecting no match
+ pos_T start_pos = *pos; // remember start pos for detecting no match
found = 0; // default: not found
- at_first_line = true; // default: start in first line
+ int at_first_line = true; // default: start in first line
if (pos->lnum == 0) { // correct lnum for when starting in line 0
pos->lnum = 1;
pos->col = 0;
@@ -667,7 +669,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) {
ptr = "";
} else {
- ptr = ml_get_buf(buf, lnum + matchpos.lnum, false);
+ ptr = ml_get_buf(buf, lnum + matchpos.lnum);
}
// Forward search in the first line: match should be after
@@ -683,9 +685,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
// otherwise "/$" will get stuck on end of line.
while (matchpos.lnum == 0
&& (((options & SEARCH_END) && first_match)
- ? (nmatched == 1
- && (int)endpos.col - 1
- < (int)start_pos.col + extra_col)
+ ? (nmatched == 1
+ && (int)endpos.col - 1
+ < (int)start_pos.col + extra_col)
: ((int)matchpos.col
- (ptr[matchpos.col] == NUL)
< (int)start_pos.col + extra_col))) {
@@ -739,7 +741,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
}
// Need to get the line pointer again, a multi-line search may
// have made it invalid.
- ptr = ml_get_buf(buf, lnum, false);
+ ptr = ml_get_buf(buf, lnum);
}
if (!match_ok) {
continue;
@@ -752,7 +754,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
// When putting the new cursor at the end, compare
// relative to the end of the match.
match_ok = false;
- for (;;) {
+ while (true) {
// Remember a position that is before the start
// position, we use it if it's the last match in
// the line. Always accept a position after
@@ -821,7 +823,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
}
// Need to get the line pointer again, a
// multi-line search may have made it invalid.
- ptr = ml_get_buf(buf, lnum + matchpos.lnum, false);
+ ptr = ml_get_buf(buf, lnum + matchpos.lnum);
}
// If there is only a match after the cursor, skip
@@ -844,12 +846,12 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
if (endpos.col == 0) {
if (pos->lnum > 1) { // just in case
pos->lnum--;
- pos->col = (colnr_T)strlen(ml_get_buf(buf, pos->lnum, false));
+ pos->col = (colnr_T)strlen(ml_get_buf(buf, pos->lnum));
}
} else {
pos->col--;
if (pos->lnum <= buf->b_ml.ml_line_count) {
- ptr = ml_get_buf(buf, pos->lnum, false);
+ ptr = ml_get_buf(buf, pos->lnum);
pos->col -= utf_head_off(ptr, ptr + pos->col);
}
}
@@ -913,19 +915,22 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
|| found || loop) {
break;
}
- //
+
// If 'wrapscan' is set we continue at the other end of the file.
- // If 'shortmess' does not contain 's', we give a message.
+ // If 'shortmess' does not contain 's', we give a message, but
+ // only, if we won't show the search stat later anyhow,
+ // (so SEARCH_COUNT must be absent).
// This message is also remembered in keep_msg for when the screen
// is redrawn. The keep_msg is cleared whenever another message is
// written.
- //
if (dir == BACKWARD) { // start second loop at the other end
lnum = buf->b_ml.ml_line_count;
} else {
lnum = 1;
}
- if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) {
+ if (!shortmess(SHM_SEARCH)
+ && shortmess(SHM_SEARCHCOUNT)
+ && (options & SEARCH_MSG)) {
give_warning(_(dir == BACKWARD ? top_bot_msg : bot_top_msg), true);
}
if (extra_arg != NULL) {
@@ -948,11 +953,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
if (p_ws) {
semsg(_(e_patnotf2), mr_pattern);
} else if (lnum == 0) {
- semsg(_("E384: search hit TOP without match for: %s"),
- mr_pattern);
+ semsg(_(e_search_hit_top_without_match_for_str), mr_pattern);
} else {
- semsg(_("E385: search hit BOTTOM without match for: %s"),
- mr_pattern);
+ semsg(_(e_search_hit_bottom_without_match_for_str), mr_pattern);
}
}
return FAIL;
@@ -961,7 +964,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
// A pattern like "\n\zs" may go past the last line.
if (pos->lnum > buf->b_ml.ml_line_count) {
pos->lnum = buf->b_ml.ml_line_count;
- pos->col = (int)strlen(ml_get_buf(buf, pos->lnum, false));
+ pos->col = (int)strlen(ml_get_buf(buf, pos->lnum));
if (pos->col > 0) {
pos->col--;
}
@@ -977,7 +980,7 @@ void set_search_direction(int cdir)
static void set_vv_searchforward(void)
{
- set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/'));
+ set_vim_var_nr(VV_SEARCHFORWARD, spats[0].off.dir == '/');
}
// Return the number of the first subpat that matched.
@@ -1023,15 +1026,14 @@ static int first_submatch(regmmatch_T *rp)
/// @param sia optional arguments or NULL
///
/// @return 0 for failure, 1 for found, 2 for found and line offset added.
-int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, int options,
+int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, int count, int options,
searchit_arg_T *sia)
{
pos_T pos; // position of the last match
char *searchstr;
- struct soffset old_off;
int retval; // Return value
char *p;
- long c;
+ int64_t c;
char *dircp;
char *strcopy = NULL;
char *ps;
@@ -1047,13 +1049,13 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
// Save the values for when (options & SEARCH_KEEP) is used.
// (there is no "if ()" around this because gcc wants them initialized)
- old_off = spats[0].off;
+ struct soffset old_off = spats[0].off;
pos = curwin->w_cursor; // start searching at the cursor position
// Find out the direction of the search.
if (dirc == 0) {
- dirc = (char_u)spats[0].off.dir;
+ dirc = (uint8_t)spats[0].off.dir;
} else {
spats[0].off.dir = (char)dirc;
set_vv_searchforward();
@@ -1085,7 +1087,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
}
// Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
- for (;;) {
+ while (true) {
bool show_top_bot_msg = false;
searchstr = pat;
@@ -1169,7 +1171,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
// Get the offset, so we know how long it is.
if (!cmd_silent
&& (spats[0].off.line || spats[0].off.end || spats[0].off.off)) {
- p = off_buf; // -V507
+ p = off_buf;
*p++ = (char)dirc;
if (spats[0].off.end) {
*p++ = 'e';
@@ -1262,7 +1264,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
memset(msgbuf + pat_len, ' ', (size_t)(r - msgbuf));
}
}
- msg_outtrans(msgbuf);
+ msg_outtrans(msgbuf, 0);
msg_clr_eos();
msg_check();
@@ -1434,13 +1436,11 @@ end_do_search:
int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat)
{
linenr_T start = 0;
- char *ptr;
- char *p;
if (buf->b_ml.ml_line_count == 0) {
return FAIL;
}
- for (;;) {
+ while (true) {
pos->lnum += dir;
if (pos->lnum < 1) {
if (p_ws) {
@@ -1469,21 +1469,21 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat)
if (start == 0) {
start = pos->lnum;
}
- ptr = ml_get_buf(buf, pos->lnum, false);
- p = skipwhite(ptr);
+ char *ptr = ml_get_buf(buf, pos->lnum);
+ char *p = skipwhite(ptr);
pos->col = (colnr_T)(p - ptr);
// when adding lines the matching line may be empty but it is not
// ignored because we are interested in the next line -- Acevedo
if (compl_status_adding() && !compl_status_sol()) {
- if (mb_strcmp_ic((bool)p_ic, (const char *)p, (const char *)pat) == 0) {
+ if (mb_strcmp_ic((bool)p_ic, p, pat) == 0) {
return OK;
}
} else if (*p != NUL) { // Ignore empty lines.
// Expanding lines or words.
assert(ins_compl_len() >= 0);
if ((p_ic ? mb_strnicmp(p, pat, (size_t)ins_compl_len())
- : strncmp(p, pat, (size_t)ins_compl_len())) == 0) {
+ : strncmp(p, pat, (size_t)ins_compl_len())) == 0) {
return OK;
}
}
@@ -1502,15 +1502,12 @@ int searchc(cmdarg_T *cap, int t_cmd)
{
int c = cap->nchar; // char to search for
int dir = cap->arg; // true for searching forward
- long count = cap->count1; // repeat count
- int col;
- char *p;
- int len;
+ int count = cap->count1; // repeat count
bool stop = true;
if (c != NUL) { // normal search: remember args for repeat
if (!KeyStuffed) { // don't remember when redoing
- *lastc = (char_u)c;
+ *lastc = (uint8_t)c;
set_csearch_direction(dir);
set_csearch_until(t_cmd);
lastc_bytelen = utf_char2bytes(c, lastc_bytes);
@@ -1524,7 +1521,7 @@ int searchc(cmdarg_T *cap, int t_cmd)
}
}
} else { // repeat previous search
- if (*lastc == NUL && lastc_bytelen == 1) {
+ if (*lastc == NUL && lastc_bytelen <= 1) {
return FAIL;
}
if (dir) { // repeat in opposite direction
@@ -1550,12 +1547,12 @@ int searchc(cmdarg_T *cap, int t_cmd)
cap->oap->inclusive = true;
}
- p = get_cursor_line_ptr();
- col = curwin->w_cursor.col;
- len = (int)strlen(p);
+ char *p = get_cursor_line_ptr();
+ int col = curwin->w_cursor.col;
+ int len = (int)strlen(p);
while (count--) {
- for (;;) {
+ while (true) {
if (dir > 0) {
col += utfc_ptr2len(p + col);
if (col >= len) {
@@ -1567,7 +1564,7 @@ int searchc(cmdarg_T *cap, int t_cmd)
}
col -= utf_head_off(p, p + col - 1) + 1;
}
- if (lastc_bytelen == 1) {
+ if (lastc_bytelen <= 1) {
if (p[col] == c && stop) {
break;
}
@@ -1632,7 +1629,7 @@ static bool find_rawstring_end(char *linep, pos_T *startpos, pos_T *endpos)
for (p = linep + startpos->col + 1; *p && *p != '('; p++) {}
size_t delim_len = (size_t)((p - linep) - startpos->col - 1);
- char *delim_copy = xstrnsave(linep + startpos->col + 1, delim_len);
+ char *delim_copy = xmemdupz(linep + startpos->col + 1, delim_len);
bool found = false;
for (lnum = startpos->lnum; lnum <= endpos->lnum; lnum++) {
char *line = ml_get(lnum);
@@ -1821,7 +1818,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
if (linep[pos.col] == NUL && pos.col) {
pos.col--;
}
- for (;;) {
+ while (true) {
initc = utf_ptr2char(linep + pos.col);
if (initc == NUL) {
break;
@@ -1841,11 +1838,11 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
return NULL;
}
} else if (!cpo_bsl) {
- int col, bslcnt = 0;
+ int bslcnt = 0;
// Set "match_escaped" if there are an odd number of
// backslashes.
- for (col = pos.col; check_prevcol(linep, col, '\\', &col);) {
+ for (int col = pos.col; check_prevcol(linep, col, '\\', &col);) {
bslcnt++;
}
match_escaped = (bslcnt & 1);
@@ -2207,10 +2204,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
// quotes when the start is also inside of quotes.
if ((!inquote || start_in_quotes == kTrue)
&& (c == initc || c == findc)) {
- int col, bslcnt = 0;
+ int bslcnt = 0;
if (!cpo_bsl) {
- for (col = pos.col; check_prevcol(linep, col, '\\', &col);) {
+ for (int col = pos.col; check_prevcol(linep, col, '\\', &col);) {
bslcnt++;
}
}
@@ -2244,10 +2241,10 @@ int check_linecomment(const char *line)
const char *p = line; // scan from start
// skip Lispish one-line comments
if (curbuf->b_p_lisp) {
- if (vim_strchr((char *)p, ';') != NULL) { // there may be comments
+ if (vim_strchr(p, ';') != NULL) { // there may be comments
bool in_str = false; // inside of string
- while ((p = strpbrk((char *)p, "\";")) != NULL) {
+ while ((p = strpbrk(p, "\";")) != NULL) {
if (*p == '"') {
if (in_str) {
if (*(p - 1) != '\\') { // skip escaped quote
@@ -2269,7 +2266,7 @@ int check_linecomment(const char *line)
p = NULL;
}
} else {
- while ((p = vim_strchr((char *)p, '/')) != NULL) {
+ while ((p = vim_strchr(p, '/')) != NULL) {
// Accept a double /, unless it's preceded with * and followed by *,
// because * / / * is an end and start of a C comment. Only
// accept the position if it is not inside a string.
@@ -2295,15 +2292,10 @@ int check_linecomment(const char *line)
/// @param c char to show match for
void showmatch(int c)
{
- pos_T *lpos, save_cursor;
- pos_T mpos;
+ pos_T *lpos;
colnr_T vcol;
- long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
- long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
- long save_so;
- long save_siso;
- int save_state;
- colnr_T save_dollar_vcol;
+ OptInt *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ OptInt *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
char *p;
// Only show match for chars in the 'matchpairs' option.
@@ -2345,26 +2337,26 @@ void showmatch(int c)
return;
}
- mpos = *lpos; // save the pos, update_screen() may change it
- save_cursor = curwin->w_cursor;
- save_so = *so;
- save_siso = *siso;
+ pos_T mpos = *lpos; // save the pos, update_screen() may change it
+ pos_T save_cursor = curwin->w_cursor;
+ OptInt save_so = *so;
+ OptInt save_siso = *siso;
// Handle "$" in 'cpo': If the ')' is typed on top of the "$",
// stop displaying the "$".
if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) {
dollar_vcol = -1;
}
curwin->w_virtcol++; // do display ')' just before "$"
- update_screen(); // show the new char first
- save_dollar_vcol = dollar_vcol;
- save_state = State;
+ colnr_T save_dollar_vcol = dollar_vcol;
+ int save_state = State;
State = MODE_SHOWMATCH;
ui_cursor_shape(); // may show different cursor shape
curwin->w_cursor = mpos; // move to matching char
*so = 0; // don't use 'scrolloff' here
*siso = 0; // don't use 'sidescrolloff' here
- show_cursor_info(false);
+ show_cursor_info_later(false);
+ update_screen(); // show the new char
setcursor();
ui_flush();
// Restore dollar_vcol(), because setcursor() may call curs_rows()
@@ -2375,9 +2367,9 @@ void showmatch(int c)
// brief pause, unless 'm' is present in 'cpo' and a character is
// available.
if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) {
- os_delay((uint64_t)p_mat * 100L + 8, true);
+ os_delay((uint64_t)p_mat * 100 + 8, true);
} else if (!char_avail()) {
- os_delay((uint64_t)p_mat * 100L + 9, false);
+ os_delay((uint64_t)p_mat * 100 + 9, false);
}
curwin->w_cursor = save_cursor; // restore cursor position
*so = save_so;
@@ -2390,7 +2382,7 @@ void showmatch(int c)
/// Used while an operator is pending, and in Visual mode.
///
/// @param forward true for forward, false for backward
-int current_search(long count, bool forward)
+int current_search(int count, bool forward)
{
bool old_p_ws = p_ws;
pos_T save_VIsual = VIsual;
@@ -2475,7 +2467,7 @@ int current_search(long count, bool forward)
} else { // try again from end of buffer
// searching backwards, so set pos to last line and col
pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
- pos.col = (colnr_T)strlen(ml_get(curwin->w_buffer->b_ml.ml_line_count));
+ pos.col = (colnr_T)strlen(ml_get(curwin->w_buffer->b_ml.ml_line_count));
}
}
}
@@ -2531,7 +2523,6 @@ int current_search(long count, bool forward)
static int is_zero_width(char *pattern, int move, pos_T *cur, Direction direction)
{
regmmatch_T regmatch;
- int nmatched = 0;
int result = -1;
pos_T pos;
const int called_emsg_before = called_emsg;
@@ -2558,13 +2549,14 @@ static int is_zero_width(char *pattern, int move, pos_T *cur, Direction directio
}
if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1,
SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) {
+ int nmatched = 0;
// Zero-width pattern should match somewhere, then we can check if
// start and end are in the same position.
do {
regmatch.startpos[0].col++;
- nmatched = (int)vim_regexec_multi(&regmatch, curwin, curbuf,
- pos.lnum, regmatch.startpos[0].col,
- NULL, NULL);
+ nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
+ pos.lnum, regmatch.startpos[0].col,
+ NULL, NULL);
if (nmatched != 0) {
break;
}
@@ -2587,16 +2579,14 @@ static int is_zero_width(char *pattern, int move, pos_T *cur, Direction directio
/// return true if line 'lnum' is empty or has white chars only.
int linewhite(linenr_T lnum)
{
- char *p;
-
- p = skipwhite(ml_get(lnum));
+ char *p = skipwhite(ml_get(lnum));
return *p == NUL;
}
/// Add the search count "[3/19]" to "msgbuf".
/// See update_search_stat() for other arguments.
static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool show_top_bot_msg,
- char *msgbuf, bool recompute, int maxcount, long timeout)
+ char *msgbuf, bool recompute, int maxcount, int timeout)
{
searchstat_T stat;
@@ -2644,7 +2634,12 @@ static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool sh
len += 2;
}
- memmove(msgbuf + strlen(msgbuf) - len, t, len);
+ size_t msgbuf_len = strlen(msgbuf);
+ if (len > msgbuf_len) {
+ len = msgbuf_len;
+ }
+ memmove(msgbuf + msgbuf_len - len, t, len);
+
if (dirc == '?' && stat.cur == maxcount + 1) {
stat.cur = -1;
}
@@ -2663,7 +2658,7 @@ static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool sh
// dirc == '/': find the next match
// dirc == '?': find the previous match
static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat,
- bool recompute, int maxcount, long timeout)
+ bool recompute, int maxcount, int timeout)
{
int save_ws = p_ws;
bool wraparound = false;
@@ -2677,7 +2672,6 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst
static int chgtick = 0;
static char *lastpat = NULL;
static buf_T *lbuf = NULL;
- proftime_T start;
CLEAR_POINTER(stat);
@@ -2713,15 +2707,18 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst
lbuf = curbuf;
}
+ // when searching backwards and having jumped to the first occurrence,
+ // cur must remain greater than 1
if (equalpos(lastpos, *cursor_pos) && !wraparound
- && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0)) {
+ && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 1)) {
cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
} else {
+ proftime_T start;
bool done_search = false;
pos_T endpos = { 0, 0, 0 };
p_ws = false;
if (timeout > 0) {
- start = profile_setlimit(timeout);
+ start = profile_setlimit(timeout);
}
while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
@@ -2770,7 +2767,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
pos_T pos = curwin->w_cursor;
char *pattern = NULL;
int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
- long timeout = SEARCH_STAT_DEF_TIMEOUT;
+ int timeout = SEARCH_STAT_DEF_TIMEOUT;
bool recompute = true;
searchstat_T stat;
@@ -2783,29 +2780,27 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (argvars[0].v_type != VAR_UNKNOWN) {
dict_T *dict;
dictitem_T *di;
- listitem_T *li;
bool error = false;
- if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) {
return;
}
dict = argvars[0].vval.v_dict;
- di = tv_dict_find(dict, (const char *)"timeout", -1);
+ di = tv_dict_find(dict, "timeout", -1);
if (di != NULL) {
- timeout = (long)tv_get_number_chk(&di->di_tv, &error);
+ timeout = (int)tv_get_number_chk(&di->di_tv, &error);
if (error) {
return;
}
}
- di = tv_dict_find(dict, (const char *)"maxcount", -1);
+ di = tv_dict_find(dict, "maxcount", -1);
if (di != NULL) {
maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
if (error) {
return;
}
}
- di = tv_dict_find(dict, (const char *)"recompute", -1);
+ di = tv_dict_find(dict, "recompute", -1);
if (di != NULL) {
recompute = tv_get_number_chk(&di->di_tv, &error);
if (error) {
@@ -2819,7 +2814,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
}
- di = tv_dict_find(dict, (const char *)"pos", -1);
+ di = tv_dict_find(dict, "pos", -1);
if (di != NULL) {
if (di->di_tv.v_type != VAR_LIST) {
semsg(_(e_invarg2), "pos");
@@ -2829,21 +2824,21 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
semsg(_(e_invarg2), "List format should be [lnum, col, off]");
return;
}
- li = tv_list_find(di->di_tv.vval.v_list, 0L);
+ listitem_T *li = tv_list_find(di->di_tv.vval.v_list, 0);
if (li != NULL) {
pos.lnum = (linenr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
if (error) {
return;
}
}
- li = tv_list_find(di->di_tv.vval.v_list, 1L);
+ li = tv_list_find(di->di_tv.vval.v_list, 1);
if (li != NULL) {
pos.col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error) - 1;
if (error) {
return;
}
}
- li = tv_list_find(di->di_tv.vval.v_list, 2L);
+ li = tv_list_find(di->di_tv.vval.v_list, 2);
if (li != NULL) {
pos.coladd = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
if (error) {
@@ -3050,8 +3045,8 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
// Loop through fuzpat and str looking for a match
bool first_match = true;
while (*fuzpat != NUL && *str != NUL) {
- const int c1 = utf_ptr2char((char *)fuzpat);
- const int c2 = utf_ptr2char((char *)str);
+ const int c1 = utf_ptr2char(fuzpat);
+ const int c2 = utf_ptr2char(str);
// Found match
if (mb_tolower(c1) == mb_tolower(c2)) {
@@ -3060,6 +3055,10 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
return 0;
}
+ int recursiveScore = 0;
+ uint32_t recursiveMatches[MAX_FUZZY_MATCHES];
+ CLEAR_FIELD(recursiveMatches);
+
// "Copy-on-Write" srcMatches into matches
if (first_match && srcMatches != NULL) {
memcpy(matches, srcMatches, (size_t)nextMatch * sizeof(srcMatches[0]));
@@ -3067,9 +3066,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
}
// Recursive call that "skips" this match
- uint32_t recursiveMatches[MAX_FUZZY_MATCHES];
- int recursiveScore = 0;
- const char *const next_char = (char *)str + utfc_ptr2len((char *)str);
+ const char *const next_char = str + utfc_ptr2len(str);
if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen,
matches, recursiveMatches,
sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch,
@@ -3120,8 +3117,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
/// normalized and varies with pattern.
/// Recursion is limited internally (default=10) to prevent degenerate cases
/// (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
-/// Uses char_u for match indices. Therefore patterns are limited to
-/// MAX_FUZZY_MATCHES characters.
+/// Patterns are limited to MAX_FUZZY_MATCHES characters.
///
/// @return true if "pat_arg" matches "str". Also returns the match score in
/// "outScore" and the matching character positions in "matches".
@@ -3211,10 +3207,10 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
static void fuzzy_match_in_list(list_T *const l, char *const str, const bool matchseq,
const char *const key, Callback *const item_cb,
const bool retmatchpos, list_T *const fmatchlist,
- const long max_matches)
+ const int max_matches)
FUNC_ATTR_NONNULL_ARG(2, 5, 7)
{
- long len = tv_list_len(l);
+ int len = tv_list_len(l);
if (len == 0) {
return;
}
@@ -3223,7 +3219,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
}
fuzzyItem_T *const items = xcalloc((size_t)len, sizeof(fuzzyItem_T));
- long match_count = 0;
+ int match_count = 0;
uint32_t matches[MAX_FUZZY_MATCHES];
// For all the string items in items, get the fuzzy matching score
@@ -3242,7 +3238,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
// For a dict, either use the specified key to lookup the string or
// use the specified callback function to get the string.
if (key != NULL) {
- itemstr = tv_dict_get_string(tv->vval.v_dict, (const char *)key, false);
+ itemstr = tv_dict_get_string(tv->vval.v_dict, key, false);
} else {
typval_T argv[2];
@@ -3272,7 +3268,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
if (retmatchpos) {
items[match_count].lmatchpos = tv_list_alloc(kListLenMayKnow);
int j = 0;
- const char *p = (char *)str;
+ const char *p = str;
while (*p != NUL) {
if (!ascii_iswhite(utf_ptr2char(p)) || matchseq) {
tv_list_append_number(items[match_count].lmatchpos, matches[j]);
@@ -3307,7 +3303,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
}
// Copy the matching strings with a valid score to the return list
- for (long i = 0; i < match_count; i++) {
+ for (int i = 0; i < match_count; i++) {
if (items[i].score == SCORE_NONE) {
break;
}
@@ -3320,7 +3316,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
retlist = TV_LIST_ITEM_TV(li)->vval.v_list;
- for (long i = 0; i < match_count; i++) {
+ for (int i = 0; i < match_count; i++) {
if (items[i].score == SCORE_NONE) {
break;
}
@@ -3331,7 +3327,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
li = tv_list_find(fmatchlist, -1);
assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
retlist = TV_LIST_ITEM_TV(li)->vval.v_list;
- for (long i = 0; i < match_count; i++) {
+ for (int i = 0; i < match_count; i++) {
if (items[i].score == SCORE_NONE) {
break;
}
@@ -3361,10 +3357,9 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
Callback cb = CALLBACK_NONE;
const char *key = NULL;
bool matchseq = false;
- long max_matches = 0;
+ int max_matches = 0;
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
@@ -3389,7 +3384,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
semsg(_(e_invarg2), tv_get_string(&di->di_tv));
return;
}
- max_matches = (long)tv_get_number_chk(&di->di_tv, NULL);
+ max_matches = (int)tv_get_number_chk(&di->di_tv, NULL);
}
if (tv_dict_find(d, "matchseq", -1) != NULL) {
@@ -3553,26 +3548,19 @@ static char *get_line_and_copy(linenr_T lnum, char *buf)
/// @param start_lnum first line to start searching
/// @param end_lnum last line for searching
void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool skip_comments,
- int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum)
+ int type, int count, int action, linenr_T start_lnum, linenr_T end_lnum)
{
SearchedFile *files; // Stack of included files
SearchedFile *bigger; // When we need more space
int max_path_depth = 50;
- long match_count = 1;
+ int match_count = 1;
- char *pat;
char *new_fname;
char *curr_fname = curbuf->b_fname;
char *prev_fname = NULL;
- linenr_T lnum;
- int depth;
int depth_displayed; // For type==CHECK_PATH
- int old_files;
int already_searched;
- char *file_line;
- char *line;
char *p;
- char save_char;
bool define_matched;
regmatch_T regmatch;
regmatch_T incl_regmatch;
@@ -3590,14 +3578,14 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
incl_regmatch.regprog = NULL;
def_regmatch.regprog = NULL;
- file_line = xmalloc(LSIZE);
+ char *file_line = xmalloc(LSIZE);
if (type != CHECK_PATH && type != FIND_DEFINE
// when CONT_SOL is set compare "ptr" with the beginning of the
// line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
&& !compl_status_sol()) {
size_t patlen = len + 5;
- pat = xmalloc(patlen);
+ char *pat = xmalloc(patlen);
assert(len <= INT_MAX);
snprintf(pat, patlen, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr);
// ignore case according to p_ic, p_scs and pat
@@ -3626,23 +3614,23 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
def_regmatch.rm_ic = false; // don't ignore case in define pat.
}
files = xcalloc((size_t)max_path_depth, sizeof(SearchedFile));
- old_files = max_path_depth;
- depth = depth_displayed = -1;
+ int old_files = max_path_depth;
+ int depth = depth_displayed = -1;
- lnum = start_lnum;
+ linenr_T lnum = start_lnum;
if (end_lnum > curbuf->b_ml.ml_line_count) {
end_lnum = curbuf->b_ml.ml_line_count;
}
if (lnum > end_lnum) { // do at least one line
lnum = end_lnum;
}
- line = get_line_and_copy(lnum, file_line);
+ char *line = get_line_and_copy(lnum, file_line);
- for (;;) {
+ while (true) {
if (incl_regmatch.regprog != NULL
- && vim_regexec(&incl_regmatch, line, (colnr_T)0)) {
+ && vim_regexec(&incl_regmatch, line, 0)) {
char *p_fname = (curr_fname == curbuf->b_fname)
- ? curbuf->b_ffname : curr_fname;
+ ? curbuf->b_ffname : curr_fname;
if (inc_opt != NULL && strstr(inc_opt, "\\zs") != NULL) {
// Use text from '\zs' to '\ze' (or end) of 'include'.
@@ -3650,11 +3638,11 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
(size_t)(incl_regmatch.endp[0]
- incl_regmatch.startp[0]),
FNAME_EXP|FNAME_INCL|FNAME_REL,
- 1L, p_fname);
+ 1, p_fname);
} else {
// Use text after match with 'include'.
new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
- FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname,
+ FNAME_EXP|FNAME_INCL|FNAME_REL, 1, p_fname,
NULL);
}
already_searched = false;
@@ -3715,7 +3703,7 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
if (new_fname != NULL) {
// using "new_fname" is more reliable, e.g., when
// 'includeexpr' is set.
- msg_outtrans_attr(new_fname, HL_ATTR(HLF_D));
+ msg_outtrans(new_fname, HL_ATTR(HLF_D));
} else {
// Isolate the file name.
// Include the surrounding "" or <> if present.
@@ -3747,9 +3735,9 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
i++;
}
}
- save_char = p[i];
+ char save_char = p[i];
p[i] = NUL;
- msg_outtrans_attr(p, HL_ATTR(HLF_D));
+ msg_outtrans(p, HL_ATTR(HLF_D));
p[i] = save_char;
}
@@ -3797,14 +3785,14 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
files[depth].lnum = 0;
files[depth].matched = false;
if (action == ACTION_EXPAND) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE,
_("Scanning included file: %s"),
new_fname);
- msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ msg_trunc(IObuff, true, HL_ATTR(HLF_R));
} else if (p_verbose >= 5) {
verbose_enter();
- smsg(_("Searching included file %s"), new_fname);
+ smsg(0, _("Searching included file %s"), new_fname);
verbose_leave();
}
}
@@ -3815,7 +3803,7 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
search_line:
define_matched = false;
if (def_regmatch.regprog != NULL
- && vim_regexec(&def_regmatch, line, (colnr_T)0)) {
+ && vim_regexec(&def_regmatch, line, 0)) {
// Pattern must be first identifier after 'define', so skip
// to that position before checking for match of pattern. Also
// don't let it match beyond the end of this identifier.
@@ -4000,7 +3988,7 @@ search_line:
} else if (action == ACTION_SHOW) {
show_pat_in_path(line, type, did_show, action,
(depth == -1) ? NULL : files[depth].fp,
- (depth == -1) ? &lnum : &files[depth].lnum, 1L);
+ (depth == -1) ? &lnum : &files[depth].lnum, 1);
did_show = true;
} else {
// ":psearch" uses the preview window
@@ -4030,7 +4018,7 @@ search_line:
curwin->w_cursor.lnum = lnum;
check_cursor();
} else {
- if (!GETFILE_SUCCESS(getfile(0, (char *)files[depth].name, NULL, true,
+ if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true,
files[depth].lnum, false))) {
break; // failed to jump to file
}
@@ -4107,7 +4095,7 @@ exit_matched:
}
already = NULL;
}
- // End of big for (;;) loop.
+ // End of big while (true) loop.
// Close any files that are still open.
for (i = 0; i <= depth; i++) {
@@ -4122,9 +4110,9 @@ exit_matched:
if (type == CHECK_PATH) {
if (!did_show) {
if (action != ACTION_SHOW_ALL) {
- msg(_("All included files were found"));
+ msg(_("All included files were found"), 0);
} else {
- msg(_("No included files"));
+ msg(_("No included files"), 0);
}
}
} else if (!found
@@ -4149,11 +4137,9 @@ fpip_end:
}
static void show_pat_in_path(char *line, int type, bool did_show, int action, FILE *fp,
- linenr_T *lnum, long count)
+ linenr_T *lnum, int count)
FUNC_ATTR_NONNULL_ARG(1, 6)
{
- char *p;
-
if (did_show) {
msg_putchar('\n'); // cursor below last one
} else if (!msg_silent) {
@@ -4162,8 +4148,8 @@ static void show_pat_in_path(char *line, int type, bool did_show, int action, FI
if (got_int) { // 'q' typed at "--more--" message
return;
}
- for (;;) {
- p = line + strlen(line) - 1;
+ while (true) {
+ char *p = line + strlen(line) - 1;
if (fp != NULL) {
// We used fgets(), so get rid of newline at end
if (p >= line && *p == '\n') {
@@ -4175,11 +4161,11 @@ static void show_pat_in_path(char *line, int type, bool did_show, int action, FI
*(p + 1) = NUL;
}
if (action == ACTION_SHOW_ALL) {
- snprintf(IObuff, IOSIZE, "%3ld: ", count); // Show match nr.
- msg_puts((const char *)IObuff);
+ snprintf(IObuff, IOSIZE, "%3d: ", count); // Show match nr.
+ msg_puts(IObuff);
snprintf(IObuff, IOSIZE, "%4" PRIdLINENR, *lnum); // Show line nr.
// Highlight line numbers.
- msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N));
+ msg_puts_attr(IObuff, HL_ATTR(HLF_N));
msg_puts(" ");
}
msg_prt_line(line, false);
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 2f140ba840..48ca555e7f 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -1,17 +1,16 @@
-#ifndef NVIM_SEARCH_H
-#define NVIM_SEARCH_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/normal.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/os/time_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/regexp_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
// Values for the find_pattern_in_path() function args 'type' and 'action':
#define FIND_ANY 1
@@ -53,7 +52,7 @@
#define RE_LAST 2 // use last used pattern if "pat" is NULL
// Values for searchcount()
-#define SEARCH_STAT_DEF_TIMEOUT 40L
+#define SEARCH_STAT_DEF_TIMEOUT 40
#define SEARCH_STAT_DEF_MAX_COUNT 99
#define SEARCH_STAT_BUF_LEN 12
@@ -110,4 +109,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
#endif
-#endif // NVIM_SEARCH_H
diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c
index db647f3ecb..d49224a987 100644
--- a/src/nvim/sha256.c
+++ b/src/nvim/sha256.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file sha256.c
///
/// FIPS-180-2 compliant SHA-256 implementation
@@ -18,8 +15,8 @@
#include <stdio.h>
#include <string.h>
+#include "nvim/memory.h"
#include "nvim/sha256.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "sha256.c.generated.h"
@@ -201,14 +198,14 @@ void sha256_update(context_sha256_T *ctx, const uint8_t *input, size_t length)
memcpy(ctx->buffer + left, input, fill);
sha256_process(ctx, ctx->buffer);
length -= fill;
- input += fill;
+ input += fill;
left = 0;
}
while (length >= SHA256_BUFFER_SIZE) {
sha256_process(ctx, input);
length -= SHA256_BUFFER_SIZE;
- input += SHA256_BUFFER_SIZE;
+ input += SHA256_BUFFER_SIZE;
}
if (length) {
@@ -230,7 +227,7 @@ void sha256_finish(context_sha256_T *ctx, uint8_t digest[SHA256_SUM_SIZE])
uint8_t msglen[8];
high = (ctx->total[0] >> 29) | (ctx->total[1] << 3);
- low = (ctx->total[0] << 3);
+ low = (ctx->total[0] << 3);
PUT_UINT32(high, msglen, 0);
PUT_UINT32(low, msglen, 4);
diff --git a/src/nvim/sha256.h b/src/nvim/sha256.h
index eeb4031509..942836a411 100644
--- a/src/nvim/sha256.h
+++ b/src/nvim/sha256.h
@@ -1,11 +1,8 @@
-#ifndef NVIM_SHA256_H
-#define NVIM_SHA256_H
+#pragma once
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
#include <stdint.h>
-#include "nvim/types.h"
-
#define SHA256_BUFFER_SIZE 64
#define SHA256_SUM_SIZE 32
@@ -18,4 +15,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "sha256.h.generated.h"
#endif
-#endif // NVIM_SHA256_H
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 90a01aaf97..be898142f0 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <msgpack/object.h>
@@ -16,25 +13,25 @@
#include <uv.h>
#include "auto/config.h"
-#include "klib/khash.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/cmdhist.h"
#include "nvim/eval.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
@@ -43,38 +40,26 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/search.h"
#include "nvim/shada.h"
#include "nvim/strings.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef HAVE_BE64TOH
-# define _BSD_SOURCE 1
-# define _DEFAULT_SOURCE 1
+# define _BSD_SOURCE 1 // NOLINT(bugprone-reserved-identifier)
+# define _DEFAULT_SOURCE 1 // NOLINT(bugprone-reserved-identifier)
# include ENDIAN_INCLUDE_FILE
#endif
-// Note: when using bufset hash pointers are intentionally casted to uintptr_t
-// and not to khint32_t or khint64_t: this way compiler must give a warning
-// (-Wconversion) when types change.
-#ifdef ARCH_32
-KHASH_SET_INIT_INT(bufset)
-#elif defined(ARCH_64)
-KHASH_SET_INIT_INT64(bufset)
-#else
-# error Not a 64- or 32-bit architecture
-#endif
-KHASH_MAP_INIT_STR(fnamebufs, buf_T *)
-KHASH_SET_INIT_STR(strset)
-
#define SEARCH_KEY_MAGIC "sm"
#define SEARCH_KEY_SMARTCASE "sc"
#define SEARCH_KEY_HAS_LINE_OFFSET "sl"
@@ -305,8 +290,6 @@ typedef struct hm_llist_entry {
struct hm_llist_entry *prev; ///< Pointer to previous entry or NULL.
} HMLListEntry;
-KHASH_MAP_INIT_STR(hmll_entries, HMLListEntry *)
-
/// Sized linked list structure for history merger
typedef struct {
HMLListEntry *entries; ///< Pointer to the start of the allocated array of
@@ -318,9 +301,8 @@ typedef struct {
HMLListEntry *last_free_entry; ///< Last unused element in entries array.
size_t size; ///< Number of allocated entries.
size_t num_entries; ///< Number of entries already used.
- khash_t(hmll_entries) contained_entries; ///< Hash mapping all history entry
- ///< strings to corresponding entry
- ///< pointers.
+ PMap(cstr_t) contained_entries; ///< Map all history entry strings to
+ ///< corresponding entry pointers.
} HMLList;
typedef struct {
@@ -348,8 +330,6 @@ typedef struct {
Timestamp greatest_timestamp; ///< Greatest timestamp among marks.
} FileMarks;
-KHASH_MAP_INIT_STR(file_marks, FileMarks)
-
/// State structure used by shada_write
///
/// Before actually writing most of the data is read to this structure.
@@ -363,8 +343,8 @@ typedef struct {
PossiblyFreedShadaEntry search_pattern; ///< Last search pattern.
PossiblyFreedShadaEntry sub_search_pattern; ///< Last s/ search pattern.
PossiblyFreedShadaEntry replacement; ///< Last s// replacement string.
- khash_t(strset) dumped_variables; ///< Names of already dumped variables.
- khash_t(file_marks) file_marks; ///< All file marks.
+ Set(cstr_t) dumped_variables; ///< Names of already dumped variables.
+ PMap(cstr_t) file_marks; ///< All file marks.
} WriteMergerState;
struct sd_read_def;
@@ -504,7 +484,7 @@ static inline void hmll_init(HMLList *const hmll, const size_t size)
.free_entry = NULL,
.size = size,
.num_entries = 0,
- .contained_entries = KHASH_EMPTY_TABLE(hmll_entries),
+ .contained_entries = MAP_INIT,
};
hmll->last_free_entry = hmll->entries;
}
@@ -535,10 +515,10 @@ static inline void hmll_remove(HMLList *const hmll, HMLListEntry *const hmll_ent
assert(hmll->free_entry == NULL);
hmll->free_entry = hmll_entry;
}
- const khiter_t k = kh_get(hmll_entries, &hmll->contained_entries,
- hmll_entry->data.data.history_item.string);
- assert(k != kh_end(&hmll->contained_entries));
- kh_del(hmll_entries, &hmll->contained_entries, k);
+ ptr_t val = pmap_del(cstr_t)(&hmll->contained_entries,
+ hmll_entry->data.data.history_item.string, NULL);
+ assert(val);
+ (void)val;
if (hmll_entry->next == NULL) {
hmll->last = hmll_entry->prev;
} else {
@@ -586,11 +566,11 @@ static inline void hmll_insert(HMLList *const hmll, HMLListEntry *hmll_entry, co
}
target_entry->data = data;
target_entry->can_free_entry = can_free_entry;
- int kh_ret;
- const khiter_t k = kh_put(hmll_entries, &hmll->contained_entries,
- data.data.history_item.string, &kh_ret);
- if (kh_ret > 0) {
- kh_val(&hmll->contained_entries, k) = target_entry;
+ bool new_item = false;
+ ptr_t *val = pmap_put_ref(cstr_t)(&hmll->contained_entries, data.data.history_item.string,
+ NULL, &new_item);
+ if (new_item) {
+ *val = target_entry;
}
hmll->num_entries++;
target_entry->prev = hmll_entry;
@@ -614,7 +594,7 @@ static inline void hmll_insert(HMLList *const hmll, HMLListEntry *hmll_entry, co
static inline void hmll_dealloc(HMLList *const hmll)
FUNC_ATTR_NONNULL_ALL
{
- kh_dealloc(hmll_entries, &hmll->contained_entries);
+ map_destroy(cstr_t, &hmll->contained_entries);
xfree(hmll->entries);
}
@@ -771,30 +751,6 @@ static void close_file(void *cookie)
}
}
-/// Check whether buffer is in the given set
-///
-/// @param[in] set Set to check within.
-/// @param[in] buf Buffer to find.
-///
-/// @return true or false.
-static inline bool in_bufset(const khash_t(bufset) *const set, const buf_T *buf)
- FUNC_ATTR_PURE
-{
- return kh_get(bufset, set, (uintptr_t)buf) != kh_end(set);
-}
-
-/// Check whether string is in the given set
-///
-/// @param[in] set Set to check within.
-/// @param[in] buf Buffer to find.
-///
-/// @return true or false.
-static inline bool in_strset(const khash_t(strset) *const set, char *str)
- FUNC_ATTR_PURE
-{
- return kh_get(strset, set, str) != kh_end(set);
-}
-
/// Msgpack callback for writing to ShaDaWriteDef*
static int msgpack_sd_writer_write(void *data, const char *buf, size_t len)
{
@@ -837,7 +793,7 @@ static int shada_read_file(const char *const file, const int flags)
if (p_verbose > 1) {
verbose_enter();
- smsg(_("Reading ShaDa file \"%s\"%s%s%s%s"),
+ smsg(0, _("Reading ShaDa file \"%s\"%s%s%s%s"),
fname,
(flags & kShaDaWantInfo) ? _(" info") : "",
(flags & kShaDaWantMarks) ? _(" marks") : "",
@@ -930,10 +886,11 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry,
}
}
HMLList *const hmll = &hms_p->hmll;
- const khiter_t k = kh_get(hmll_entries, &hms_p->hmll.contained_entries,
- entry.data.history_item.string);
- if (k != kh_end(&hmll->contained_entries)) {
- HMLListEntry *const existing_entry = kh_val(&hmll->contained_entries, k);
+ cstr_t *key_alloc = NULL;
+ ptr_t *val = pmap_ref(cstr_t)(&hms_p->hmll.contained_entries, entry.data.history_item.string,
+ &key_alloc);
+ if (val) {
+ HMLListEntry *const existing_entry = *val;
if (entry.timestamp > existing_entry->data.timestamp) {
hmll_remove(hmll, existing_entry);
} else if (!do_iter && entry.timestamp == existing_entry->data.timestamp) {
@@ -944,7 +901,7 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry,
existing_entry->data = entry;
existing_entry->can_free_entry = can_free_entry;
// Previous key was freed above, as part of freeing the ShaDa entry.
- kh_key(&hmll->contained_entries, k) = entry.data.history_item.string;
+ *key_alloc = entry.data.history_item.string;
return;
} else {
return;
@@ -1046,24 +1003,27 @@ static inline void hms_dealloc(HistoryMergerState *const hms_p)
/// @param[in] fname File name to find.
///
/// @return Pointer to the buffer or NULL.
-static buf_T *find_buffer(khash_t(fnamebufs) *const fname_bufs, const char *const fname)
+static buf_T *find_buffer(PMap(cstr_t) *const fname_bufs, const char *const fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- int kh_ret;
- khint_t k = kh_put(fnamebufs, fname_bufs, fname, &kh_ret);
- if (!kh_ret) {
- return kh_val(fname_bufs, k);
+ cstr_t *key_alloc = NULL;
+ bool new_item = false;
+ buf_T **ref = (buf_T **)pmap_put_ref(cstr_t)(fname_bufs, fname, &key_alloc, &new_item);
+ if (new_item) {
+ *key_alloc = xstrdup(fname);
+ } else {
+ return *ref; // item already existed (can be a NULL value)
}
- kh_key(fname_bufs, k) = xstrdup(fname);
+
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL) {
if (path_fnamecmp(fname, buf->b_ffname) == 0) {
- kh_val(fname_bufs, k) = buf;
+ *ref = buf;
return buf;
}
}
}
- kh_val(fname_bufs, k) = NULL;
+ *ref = NULL;
return NULL;
}
@@ -1163,9 +1123,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
}
}
ShadaEntry cur_entry;
- khash_t(bufset) cl_bufs = KHASH_EMPTY_TABLE(bufset);
- khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs);
- khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset);
+ Set(ptr_t) cl_bufs = SET_INIT;
+ PMap(cstr_t) fname_bufs = MAP_INIT;
+ Set(cstr_t) oldfiles_set = SET_INIT;
if (get_old_files && (oldfiles_list == NULL || force)) {
oldfiles_list = tv_list_alloc(kListLenUnknown);
set_vim_var_list(VV_OLDFILES, oldfiles_list);
@@ -1307,6 +1267,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.mark = cur_entry.data.filemark.mark,
.fnum = (buf == NULL ? 0 : buf->b_fnum),
.timestamp = cur_entry.timestamp,
+ .view = INIT_FMARKV,
.additional_data = cur_entry.data.filemark.additional_data,
},
};
@@ -1358,8 +1319,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
break;
case kSDItemChange:
case kSDItemLocalMark: {
- if (get_old_files && !in_strset(&oldfiles_set,
- cur_entry.data.filemark.fname)) {
+ if (get_old_files && !set_has(cstr_t, &oldfiles_set, cur_entry.data.filemark.fname)) {
char *fname = cur_entry.data.filemark.fname;
if (want_marks) {
// Do not bother with allocating memory for the string if already
@@ -1367,8 +1327,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
// want_marks is set because this way it may be used for a mark.
fname = xstrdup(fname);
}
- int kh_ret;
- (void)kh_put(strset, &oldfiles_set, fname, &kh_ret);
+ set_put(cstr_t, &oldfiles_set, fname);
tv_list_append_allocated_string(oldfiles_list, fname);
if (!want_marks) {
// Avoid free because this string was already used.
@@ -1388,6 +1347,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.mark = cur_entry.data.filemark.mark,
.fnum = 0,
.timestamp = cur_entry.timestamp,
+ .view = INIT_FMARKV,
.additional_data = cur_entry.data.filemark.additional_data,
};
if (cur_entry.type == kSDItemLocalMark) {
@@ -1396,8 +1356,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
break;
}
} else {
- int kh_ret;
- (void)kh_put(bufset, &cl_bufs, (uintptr_t)buf, &kh_ret);
+ set_put(ptr_t, &cl_bufs, buf);
#define SDE_TO_FMARK(entry) fm
#define AFTERFREE(entry) (entry).data.filemark.fname = NULL
#define DUMMY_IDX_ADJ(i)
@@ -1435,21 +1394,21 @@ shada_read_main_cycle_end:
hms_dealloc(&hms[i]);
}
}
- if (cl_bufs.n_occupied) {
+ if (cl_bufs.h.n_occupied) {
FOR_ALL_TAB_WINDOWS(tp, wp) {
(void)tp;
- if (in_bufset(&cl_bufs, wp->w_buffer)) {
+ if (set_has(ptr_t, &cl_bufs, wp->w_buffer)) {
wp->w_changelistidx = wp->w_buffer->b_changelistlen;
}
}
}
- kh_dealloc(bufset, &cl_bufs);
+ set_destroy(ptr_t, &cl_bufs);
const char *key;
- kh_foreach_key(&fname_bufs, key, {
- xfree((void *)key);
+ map_foreach_key(&fname_bufs, key, {
+ xfree((char *)key);
})
- kh_dealloc(fnamebufs, &fname_bufs);
- kh_dealloc(strset, &oldfiles_set);
+ map_destroy(cstr_t, &fname_bufs);
+ set_destroy(cstr_t, &oldfiles_set);
}
/// Default shada file location: cached path
@@ -1491,7 +1450,7 @@ static char *shada_filename(const char *file)
// If shell is not performing them then they should be done in main.c
// where arguments are parsed, *not here*.
expand_env((char *)file, &(NameBuff[0]), MAXPATHL);
- file = (const char *)&(NameBuff[0]);
+ file = &(NameBuff[0]);
}
}
return xstrdup(file);
@@ -1544,13 +1503,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
dict_T *const d = (src); \
if (d != NULL) { \
size_t todo = d->dv_hashtab.ht_used; \
- for (const hashitem_T *hi= d->dv_hashtab.ht_array; todo; hi++) { \
+ for (const hashitem_T *hi = d->dv_hashtab.ht_array; todo; hi++) { \
if (!HASHITEM_EMPTY(hi)) { \
todo--; \
dictitem_T *const di = TV_DICT_HI2DI(hi); \
- const size_t key_len = strlen((const char *)hi->hi_key); \
+ const size_t key_len = strlen(hi->hi_key); \
msgpack_pack_str(spacker, key_len); \
- msgpack_pack_str_body(spacker, (const char *)hi->hi_key, key_len); \
+ msgpack_pack_str_body(spacker, hi->hi_key, key_len); \
if (encode_vim_to_msgpack(spacker, &di->di_tv, \
_("additional data of ShaDa " what)) \
== FAIL) { \
@@ -1603,15 +1562,14 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
break;
}
case kSDItemVariable: {
- if (entry.data.global_var.value.v_type == VAR_TYPE_BLOB) {
+ if (entry.data.global_var.value.v_type == VAR_BLOB) {
// Strings and Blobs both pack as msgpack BINs; differentiate them by
// storing an additional VAR_TYPE_BLOB element alongside Blobs
list_T *const list = tv_list_alloc(1);
tv_list_append_number(list, VAR_TYPE_BLOB);
entry.data.global_var.additional_elements = list;
}
- const size_t arr_size = 2 + (size_t)(
- tv_list_len(entry.data.global_var.additional_elements));
+ const size_t arr_size = 2 + (size_t)(tv_list_len(entry.data.global_var.additional_elements));
msgpack_pack_array(spacker, arr_size);
const String varname = cstr_as_string(entry.data.global_var.name);
PACK_BIN(varname);
@@ -1639,24 +1597,24 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
break;
}
case kSDItemSearchPattern: {
- const size_t map_size = (size_t)(
- 1 // Search pattern is always present
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
- + ONE_IF_NOT_DEFAULT(entry,
- search_pattern.is_substitute_pattern)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
- // finally, additional data:
- + (size_t)(
- entry.data.search_pattern.additional_data
- ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used
- : 0));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (
+ 1 // Search pattern is always present
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
+ + ONE_IF_NOT_DEFAULT(entry,
+ search_pattern.is_substitute_pattern)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
+ // finally, additional data:
+ + (
+ entry.data.search_pattern.additional_data
+ ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used
+ : 0));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(SEARCH_KEY_PAT);
PACK_BIN(cstr_as_string(entry.data.search_pattern.pat));
PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic);
@@ -1680,17 +1638,17 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
case kSDItemGlobalMark:
case kSDItemLocalMark:
case kSDItemJump: {
- const size_t map_size = (size_t)(
- 1 // File name
- + ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
- + ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
- + ONE_IF_NOT_DEFAULT(entry, filemark.name)
- // Additional entries, if any:
- + (size_t)(
- entry.data.filemark.additional_data == NULL
- ? 0
- : entry.data.filemark.additional_data->dv_hashtab.ht_used));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (
+ 1 // File name
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.name)
+ // Additional entries, if any:
+ + (
+ entry.data.filemark.additional_data == NULL
+ ? 0
+ : entry.data.filemark.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(KEY_FILE);
PACK_BIN(cstr_as_string(entry.data.filemark.fname));
if (!CHECK_DEFAULT(entry, filemark.mark.lnum)) {
@@ -1713,15 +1671,15 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
break;
}
case kSDItemRegister: {
- const size_t map_size = (size_t)(2 // Register contents and name
- + ONE_IF_NOT_DEFAULT(entry, reg.type)
- + ONE_IF_NOT_DEFAULT(entry, reg.width)
- + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed)
- // Additional entries, if any:
- + (size_t)(entry.data.reg.additional_data == NULL
- ? 0
- : entry.data.reg.additional_data->dv_hashtab.ht_used));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (2 // Register contents and name
+ + ONE_IF_NOT_DEFAULT(entry, reg.type)
+ + ONE_IF_NOT_DEFAULT(entry, reg.width)
+ + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed)
+ // Additional entries, if any:
+ + (entry.data.reg.additional_data == NULL
+ ? 0
+ : entry.data.reg.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(REG_KEY_CONTENTS);
msgpack_pack_array(spacker, entry.data.reg.contents_size);
for (size_t i = 0; i < entry.data.reg.contents_size; i++) {
@@ -1751,20 +1709,20 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
case kSDItemBufferList:
msgpack_pack_array(spacker, entry.data.buffer_list.size);
for (size_t i = 0; i < entry.data.buffer_list.size; i++) {
- const size_t map_size = (size_t)(
- 1 // Buffer name
- + (size_t)(entry.data.buffer_list.buffers[i].pos.lnum
- != default_pos.lnum)
- + (size_t)(entry.data.buffer_list.buffers[i].pos.col
- != default_pos.col)
- // Additional entries, if any:
- + (size_t)(
- entry.data.buffer_list.buffers[i].additional_data
- == NULL
- ? 0
- : (entry.data.buffer_list.buffers[i].additional_data
- ->dv_hashtab.ht_used)));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (
+ 1 // Buffer name
+ + (size_t)(entry.data.buffer_list.buffers[i].pos.lnum
+ != default_pos.lnum)
+ + (size_t)(entry.data.buffer_list.buffers[i].pos.col
+ != default_pos.col)
+ // Additional entries, if any:
+ + (
+ entry.data.buffer_list.buffers[i].additional_data
+ == NULL
+ ? 0
+ : (entry.data.buffer_list.buffers[i].additional_data
+ ->dv_hashtab.ht_used)));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(KEY_FILE);
PACK_BIN(cstr_as_string(entry.data.buffer_list.buffers[i].fname));
if (entry.data.buffer_list.buffers[i].pos.lnum != 1) {
@@ -2152,7 +2110,7 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
break;
}
case kSDItemVariable:
- if (!in_strset(&wms->dumped_variables, entry.data.global_var.name)) {
+ if (!set_has(cstr_t, &wms->dumped_variables, entry.data.global_var.name)) {
ret = shada_pack_entry(packer, entry, 0);
}
shada_free_shada_entry(&entry);
@@ -2199,6 +2157,12 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
shada_free_shada_entry(&entry);
break;
}
+ if (wms->global_marks[idx].data.type == kSDItemMissing) {
+ if (namedfm[idx].fmark.timestamp >= entry.timestamp) {
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ }
COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry);
}
break;
@@ -2208,14 +2172,13 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
shada_free_shada_entry(&entry);
break;
}
- const char *const fname = (const char *)entry.data.filemark.fname;
- khiter_t k;
- int kh_ret;
- k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
- FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
- if (kh_ret > 0) {
- CLEAR_POINTER(filemarks);
+ const char *const fname = entry.data.filemark.fname;
+ cstr_t *key = NULL;
+ ptr_t *val = pmap_put_ref(cstr_t)(&wms->file_marks, fname, &key, NULL);
+ if (*val == NULL) {
+ *val = xcalloc(1, sizeof(FileMarks));
}
+ FileMarks *const filemarks = *val;
if (entry.timestamp > filemarks->greatest_timestamp) {
filemarks->greatest_timestamp = entry.timestamp;
}
@@ -2229,20 +2192,35 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
entry;
} else {
PossiblyFreedShadaEntry *const wms_entry = &filemarks->marks[idx];
+ bool set_wms = true;
if (wms_entry->data.type != kSDItemMissing) {
if (wms_entry->data.timestamp >= entry.timestamp) {
shada_free_shada_entry(&entry);
break;
}
if (wms_entry->can_free_entry) {
- if (kh_key(&wms->file_marks, k)
- == wms_entry->data.data.filemark.fname) {
- kh_key(&wms->file_marks, k) = entry.data.filemark.fname;
+ if (*key == wms_entry->data.data.filemark.fname) {
+ *key = entry.data.filemark.fname;
}
shada_free_shada_entry(&wms_entry->data);
}
+ } else {
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL
+ && path_fnamecmp(entry.data.filemark.fname, buf->b_ffname) == 0) {
+ fmark_T fm;
+ mark_get(buf, curwin, &fm, kMarkBufLocal, (int)entry.data.filemark.name);
+ if (fm.timestamp >= entry.timestamp) {
+ set_wms = false;
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ }
+ }
+ }
+ if (set_wms) {
+ *wms_entry = pfs_entry;
}
- *wms_entry = pfs_entry;
}
} else {
#define AFTERFREE_DUMMY(entry)
@@ -2279,11 +2257,11 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
/// @param[in] removable_bufs Cache of buffers ignored due to their location.
///
/// @return true or false.
-static inline bool ignore_buf(const buf_T *const buf, khash_t(bufset) *const removable_bufs)
+static inline bool ignore_buf(const buf_T *const buf, Set(ptr_t) *const removable_bufs)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \
- || bt_terminal(buf) || in_bufset(removable_bufs, buf));
+ || bt_terminal(buf) || set_has(ptr_t, removable_bufs, (ptr_t)buf));
}
/// Get list of buffers to write to the shada file
@@ -2291,7 +2269,7 @@ static inline bool ignore_buf(const buf_T *const buf, khash_t(bufset) *const rem
/// @param[in] removable_bufs Buffers which are ignored
///
/// @return ShadaEntry List of buffers to save, kSDItemBufferList entry.
-static inline ShadaEntry shada_get_buflist(khash_t(bufset) *const removable_bufs)
+static inline ShadaEntry shada_get_buflist(Set(ptr_t) *const removable_bufs)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
int max_bufs = get_shada_parameter('%');
@@ -2369,8 +2347,8 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse,
: pat.off.line),
.place_cursor_at_end = (
is_substitute_pattern
- ? defaults.data.search_pattern.place_cursor_at_end
- : pat.off.end),
+ ? defaults.data.search_pattern.place_cursor_at_end
+ : pat.off.end),
.offset = (is_substitute_pattern
? defaults.data.search_pattern.offset
: pat.off.off),
@@ -2459,12 +2437,11 @@ static inline void replace_numbered_mark(WriteMergerState *const wms, const size
/// Find buffers ignored due to their location.
///
/// @param[out] removable_bufs Cache of buffers ignored due to their location.
-static inline void find_removable_bufs(khash_t(bufset) *removable_bufs)
+static inline void find_removable_bufs(Set(ptr_t) *removable_bufs)
{
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL && shada_removable(buf->b_ffname)) {
- int kh_ret;
- (void)kh_put(bufset, removable_bufs, (uintptr_t)buf, &kh_ret);
+ set_put(ptr_t, removable_bufs, (ptr_t)buf);
}
}
}
@@ -2516,7 +2493,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
max_reg_lines = get_shada_parameter('"');
}
const bool dump_registers = (max_reg_lines != 0);
- khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ Set(ptr_t) removable_bufs = SET_INIT;
const size_t max_kbyte = (size_t)max_kbyte_i;
const size_t num_marked_files = (size_t)get_shada_parameter('\'');
const bool dump_global_marks = get_shada_parameter('f') != 0;
@@ -2524,9 +2501,9 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
// Initialize history merger
for (HistoryType i = 0; i < HIST_COUNT; i++) {
- long num_saved = get_shada_parameter(hist_type2char(i));
+ int num_saved = get_shada_parameter(hist_type2char(i));
if (num_saved == -1) {
- num_saved = p_hi;
+ num_saved = (int)p_hi;
}
if (num_saved > 0) {
dump_history = true;
@@ -2571,15 +2548,15 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
.capacity = 5,
.items = ((KeyValuePair[]) {
{ STATIC_CSTR_AS_STRING("generator"),
- STRING_OBJ(STATIC_CSTR_AS_STRING("nvim")) },
+ STATIC_CSTR_AS_OBJ("nvim") },
{ STATIC_CSTR_AS_STRING("version"),
- STRING_OBJ(cstr_as_string(longVersion)) },
+ CSTR_AS_OBJ(longVersion) },
{ STATIC_CSTR_AS_STRING("max_kbyte"),
INTEGER_OBJ((Integer)max_kbyte) },
{ STATIC_CSTR_AS_STRING("pid"),
INTEGER_OBJ((Integer)os_get_pid()) },
{ STATIC_CSTR_AS_STRING("encoding"),
- STRING_OBJ(cstr_as_string((char *)p_enc)) },
+ CSTR_AS_OBJ(p_enc) },
}),
}
}
@@ -2660,8 +2637,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
tv_clear(&vartv);
tv_clear(&tgttv);
if (spe_ret == kSDWriteSuccessful) {
- int kh_ret;
- (void)kh_put(strset, &wms->dumped_variables, name, &kh_ret);
+ set_put(cstr_t, &wms->dumped_variables, name);
}
} while (var_iter != NULL);
}
@@ -2714,17 +2690,17 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
const char *fname;
if (fm.fmark.fnum == 0) {
assert(fm.fname != NULL);
- if (shada_removable((const char *)fm.fname)) {
+ if (shada_removable(fm.fname)) {
continue;
}
- fname = (const char *)fm.fname;
+ fname = fm.fname;
} else {
const buf_T *const buf = buflist_findnr(fm.fmark.fnum);
if (buf == NULL || buf->b_ffname == NULL
- || in_bufset(&removable_bufs, buf)) {
+ || set_has(ptr_t, &removable_bufs, (ptr_t)buf)) {
continue;
}
- fname = (const char *)buf->b_ffname;
+ fname = buf->b_ffname;
}
const PossiblyFreedShadaEntry pf_entry = {
.can_free_entry = false,
@@ -2757,18 +2733,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
// Initialize buffers
if (num_marked_files > 0) {
FOR_ALL_BUFFERS(buf) {
- if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) {
+ if (buf->b_ffname == NULL || set_has(ptr_t, &removable_bufs, buf)) {
continue;
}
const void *local_marks_iter = NULL;
- const char *const fname = (const char *)buf->b_ffname;
- khiter_t k;
- int kh_ret;
- k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
- FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
- if (kh_ret > 0) {
- CLEAR_POINTER(filemarks);
+ const char *const fname = buf->b_ffname;
+ ptr_t *val = pmap_put_ref(cstr_t)(&wms->file_marks, fname, NULL, NULL);
+ if (*val == NULL) {
+ *val = xcalloc(1, sizeof(FileMarks));
}
+ FileMarks *const filemarks = *val;
do {
fmark_T fm;
char name = NUL;
@@ -2885,16 +2859,14 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
PACK_WMS_ENTRY(wms->replacement);
#undef PACK_WMS_ENTRY
- const size_t file_markss_size = kh_size(&wms->file_marks);
+ const size_t file_markss_size = map_size(&wms->file_marks);
FileMarks **const all_file_markss =
xmalloc(file_markss_size * sizeof(*all_file_markss));
FileMarks **cur_file_marks = all_file_markss;
- for (khint_t i = kh_begin(&wms->file_marks); i != kh_end(&wms->file_marks);
- i++) {
- if (kh_exist(&wms->file_marks, i)) {
- *cur_file_marks++ = &kh_val(&wms->file_marks, i);
- }
- }
+ ptr_t val;
+ map_foreach_value(&wms->file_marks, val, {
+ *cur_file_marks++ = val;
+ })
qsort((void *)all_file_markss, file_markss_size, sizeof(*all_file_markss),
&compare_file_marks);
const size_t file_markss_to_dump = MIN(num_marked_files, file_markss_size);
@@ -2947,10 +2919,13 @@ shada_write_exit:
hms_dealloc(&wms->hms[i]);
}
}
- kh_dealloc(file_marks, &wms->file_marks);
- kh_dealloc(bufset, &removable_bufs);
+ map_foreach_value(&wms->file_marks, val, {
+ xfree(val);
+ })
+ map_destroy(cstr_t, &wms->file_marks);
+ set_destroy(ptr_t, &removable_bufs);
msgpack_packer_free(packer);
- kh_dealloc(strset, &wms->dumped_variables);
+ set_destroy(cstr_t, &wms->dumped_variables);
xfree(wms);
return ret;
}
@@ -3040,7 +3015,7 @@ shada_write_file_nomerge: {}
if (!os_isdir(fname)) {
int ret;
char *failed_dir;
- if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) {
+ if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir, NULL)) != 0) {
semsg(_(SERR "Failed to create directory %s "
"for writing ShaDa file: %s"),
failed_dir, os_strerror(ret));
@@ -3071,7 +3046,7 @@ shada_write_file_nomerge: {}
if (p_verbose > 1) {
verbose_enter();
- smsg(_("Writing ShaDa file \"%s\""), fname);
+ smsg(0, _("Writing ShaDa file \"%s\""), fname);
verbose_leave();
}
@@ -3166,8 +3141,8 @@ int shada_read_everything(const char *const fname, const bool forceit, const boo
{
return shada_read_file(fname,
kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles
- |(forceit?kShaDaForceit:0)
- |(missing_ok?0:kShaDaMissingError));
+ |(forceit ? kShaDaForceit : 0)
+ |(missing_ok ? 0 : kShaDaMissingError));
}
static void shada_free_shada_entry(ShadaEntry *const entry)
@@ -3361,7 +3336,6 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
#define ID(s) s
#define BINDUP(b) xmemdupz((b).ptr, (b).size)
#define TOINT(s) ((int)(s))
-#define TOLONG(s) ((long)(s))
#define TOCHAR(s) ((char)(s))
#define TOU8(s) ((uint8_t)(s))
#define TOSIZE(s) ((size_t)(s))
@@ -3440,7 +3414,7 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
if (msgpack_to_vim(obj, &adtv) == FAIL \
|| adtv.v_type != VAR_DICT) { \
semsg(_(READERR(name, \
- "cannot be converted to a VimL dictionary")), \
+ "cannot be converted to a Vimscript dictionary")), \
initial_fpos); \
ga_clear(&ad_ga); \
tv_clear(&adtv); \
@@ -3464,7 +3438,7 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
}; \
typval_T aetv; \
if (msgpack_to_vim(obj, &aetv) == FAIL) { \
- semsg(_(READERR(name, "cannot be converted to a VimL list")), \
+ semsg(_(READERR(name, "cannot be converted to a Vimscript list")), \
initial_fpos); \
tv_clear(&aetv); \
goto shada_read_next_item_error; \
@@ -3602,7 +3576,6 @@ shada_read_next_item_start:
ret = spm_ret;
goto shada_read_next_item_error;
}
- ret = kSDReadStatusMalformed;
entry->data = sd_default_values[type_u64].data;
switch ((ShadaEntryType)type_u64) {
case kSDItemHeader:
@@ -3864,7 +3837,7 @@ shada_read_next_item_start:
} else if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
&(entry->data.global_var.value)) == FAIL) {
semsg(_(READERR("variable", "has value that cannot "
- "be converted to the VimL value")), initial_fpos);
+ "be converted to the Vimscript value")), initial_fpos);
goto shada_read_next_item_error;
}
break;
@@ -3921,7 +3894,7 @@ shada_read_next_item_start:
// XXX: Temporarily reassign `i` because the macros depend on it.
const size_t j = i;
{
- for (i = 0; i < unpacked_2.data.via.map.size; i++) { // -V535
+ for (i = 0; i < unpacked_2.data.via.map.size; i++) {
CHECK_KEY_IS_STR(unpacked_2, "buffer list entry")
INTEGER_KEY(unpacked_2, "buffer list entry", KEY_LNUM,
entry->data.buffer_list.buffers[j].pos.lnum)
@@ -3988,7 +3961,6 @@ shada_read_next_item_error:
#undef BINDUP
#undef TOCHAR
#undef TOINT
-#undef TOLONG
#undef TYPED_KEY
#undef INT_KEY
#undef INTEGER_KEY
@@ -4006,12 +3978,11 @@ shada_read_next_item_error:
static bool shada_removable(const char *name)
FUNC_ATTR_WARN_UNUSED_RESULT
{
- char *p;
char part[MAXPATHL + 1];
bool retval = false;
- char *new_name = home_replace_save(NULL, (char *)name);
- for (p = p_shada; *p;) {
+ char *new_name = home_replace_save(NULL, name);
+ for (char *p = p_shada; *p;) {
(void)copy_option_part(&p, part, ARRAY_SIZE(part), ", ");
if (part[0] == 'r') {
home_replace(NULL, part + 1, NameBuff, MAXPATHL, true);
@@ -4034,7 +4005,7 @@ static bool shada_removable(const char *name)
///
/// @return number of jumplist entries
static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps,
- khash_t(bufset) *const removable_bufs)
+ Set(ptr_t) *const removable_bufs)
{
// Initialize jump list
size_t jumps_size = 0;
@@ -4055,7 +4026,7 @@ static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps,
? NULL
: buflist_findnr(fm.fmark.fnum));
if (buf != NULL
- ? in_bufset(removable_bufs, buf)
+ ? set_has(ptr_t, removable_bufs, (ptr_t)buf)
: fm.fmark.fnum != 0) {
continue;
}
@@ -4110,7 +4081,7 @@ void shada_encode_regs(msgpack_sbuffer *const sbuf)
void shada_encode_jumps(msgpack_sbuffer *const sbuf)
FUNC_ATTR_NONNULL_ALL
{
- khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
PossiblyFreedShadaEntry jumps[JUMPLISTSIZE];
size_t jumps_size = shada_init_jumps(jumps, &removable_bufs);
@@ -4129,7 +4100,7 @@ void shada_encode_jumps(msgpack_sbuffer *const sbuf)
void shada_encode_buflist(msgpack_sbuffer *const sbuf)
FUNC_ATTR_NONNULL_ALL
{
- khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
msgpack_packer packer;
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
index 2a945a06bc..d7cac24afc 100644
--- a/src/nvim/shada.h
+++ b/src/nvim/shada.h
@@ -1,7 +1,6 @@
-#ifndef NVIM_SHADA_H
-#define NVIM_SHADA_H
+#pragma once
-#include <msgpack.h>
+#include <msgpack.h> // IWYU pragma: keep
/// Flags for shada_read_file and children
typedef enum {
@@ -15,4 +14,3 @@ typedef enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "shada.h.generated.h"
#endif
-#endif // NVIM_SHADA_H
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index d0c093d93a..d05c708d2c 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -1,69 +1,53 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-//
// sign.c: functions for managing with signs
-//
+#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "klib/kvec.h"
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/hashtab.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
+#include "nvim/marktree.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/option.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/sign.h"
#include "nvim/sign_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
-/// Struct to hold the sign properties.
-typedef struct sign sign_T;
-
-struct sign {
- sign_T *sn_next; // next sign in list
- int sn_typenr; // type number of sign
- char *sn_name; // name of sign
- char *sn_icon; // name of pixmap
- char *sn_text; // text used instead of pixmap
- int sn_line_hl; // highlight ID for line
- int sn_text_hl; // highlight ID for text
- int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set
- int sn_num_hl; // highlight ID for line number
-};
-
-static sign_T *first_sign = NULL;
-static int next_sign_typenr = 1;
-
-static void sign_list_defined(sign_T *sp);
-static void sign_undefine(sign_T *sp, sign_T *sp_prev);
+static PMap(cstr_t) sign_map INIT( = MAP_INIT);
+static kvec_t(Integer) sign_ns INIT( = MAP_INIT);
static char *cmds[] = {
"define",
@@ -82,736 +66,249 @@ static char *cmds[] = {
#define SIGNCMD_LAST 6
};
-static hashtab_T sg_table; // sign group (signgroup_T) hashtable
-static int next_sign_id = 1; // next sign id in the global group
-
-/// Initialize data needed for managing signs
-void init_signs(void)
-{
- hash_init(&sg_table); // sign group hash table
-}
-
-/// A new sign in group 'groupname' is added. If the group is not present,
-/// create it. Otherwise reference the group.
-static signgroup_T *sign_group_ref(const char *groupname)
+// Convert the supplied "group" to a namespace filter
+static int64_t group_get_ns(const char *group)
{
- hash_T hash;
- hashitem_T *hi;
- signgroup_T *group;
-
- hash = hash_hash(groupname);
- hi = hash_lookup(&sg_table, (char *)groupname, strlen(groupname), hash);
- if (HASHITEM_EMPTY(hi)) {
- // new group
- group = xmalloc(sizeof(signgroup_T) + strlen(groupname));
-
- STRCPY(group->sg_name, groupname);
- group->sg_refcount = 1;
- group->sg_next_sign_id = 1;
- hash_add_item(&sg_table, hi, group->sg_name, hash);
- } else {
- // existing group
- group = HI2SG(hi);
- group->sg_refcount++;
+ if (group == NULL) {
+ return 0; // Global namespace
+ } else if (strcmp(group, "*") == 0) {
+ return UINT32_MAX; // All namespaces
}
-
- return group;
+ // Specific or non-existing namespace
+ int ns = map_get(String, int)(&namespace_ids, cstr_as_string((char *)group));
+ return ns ? ns : -1;
}
-/// A sign in group 'groupname' is removed. If all the signs in this group are
-/// removed, then remove the group.
-static void sign_group_unref(char *groupname)
+static const char *sign_get_name(DecorSignHighlight *sh)
{
- hashitem_T *hi = hash_find(&sg_table, groupname);
- if (HASHITEM_EMPTY(hi)) {
- return;
- }
-
- signgroup_T *group = HI2SG(hi);
- group->sg_refcount--;
- if (group->sg_refcount == 0) {
- // All the signs in this group are removed
- hash_remove(&sg_table, hi);
- xfree(group);
- }
-}
-
-/// @return true if 'sign' is in 'group'.
-/// A sign can either be in the global group (sign->group == NULL)
-/// or in a named group. If 'group' is '*', then the sign is part of the group.
-static bool sign_in_group(sign_entry_T *sign, const char *group)
-{
- return ((group != NULL && strcmp(group, "*") == 0)
- || (group == NULL && sign->se_group == NULL)
- || (group != NULL && sign->se_group != NULL
- && strcmp(group, sign->se_group->sg_name) == 0));
-}
-
-/// Get the next free sign identifier in the specified group
-static int sign_group_get_next_signid(buf_T *buf, const char *groupname)
-{
- int id = 1;
- signgroup_T *group = NULL;
- sign_entry_T *sign;
- hashitem_T *hi;
- int found = false;
-
- if (groupname != NULL) {
- hi = hash_find(&sg_table, (char *)groupname);
- if (HASHITEM_EMPTY(hi)) {
- return id;
- }
- group = HI2SG(hi);
- }
-
- // Search for the next usable sign identifier
- while (!found) {
- if (group == NULL) {
- id = next_sign_id++; // global group
- } else {
- id = group->sg_next_sign_id++;
- }
-
- // Check whether this sign is already placed in the buffer
- found = true;
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (id == sign->se_id && sign_in_group(sign, (char *)groupname)) {
- found = false; // sign identifier is in use
- break;
- }
- }
- }
-
- return id;
+ char *name = sh->sign_name;
+ return !name ? "" : map_has(cstr_t, &sign_map, name) ? name : "[Deleted]";
}
-/// Insert a new sign into the signlist for buffer 'buf' between the 'prev' and
-/// 'next' signs.
+/// Create or update a sign extmark.
///
/// @param buf buffer to store sign in
-/// @param prev previous sign entry
-/// @param next next sign entry
/// @param id sign ID
-/// @param group sign group; NULL for global group
+/// @param group sign group
/// @param prio sign priority
/// @param lnum line number which gets the mark
-/// @param typenr typenr of sign we are adding
-/// @param has_text_or_icon sign has text or icon
-static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int id,
- const char *group, int prio, linenr_T lnum, int typenr,
- bool has_text_or_icon)
+/// @param sp sign properties
+static void buf_set_sign(buf_T *buf, uint32_t *id, char *group, int prio, linenr_T lnum, sign_T *sp)
{
- sign_entry_T *newsign = xmalloc(sizeof(sign_entry_T));
- newsign->se_id = id;
- newsign->se_lnum = lnum;
- newsign->se_typenr = typenr;
- newsign->se_has_text_or_icon = has_text_or_icon;
- if (group != NULL) {
- newsign->se_group = sign_group_ref(group);
- } else {
- newsign->se_group = NULL;
- }
- newsign->se_priority = prio;
- newsign->se_next = next;
- newsign->se_prev = prev;
- if (next != NULL) {
- next->se_prev = newsign;
+ if (group && !map_get(String, int)(&namespace_ids, cstr_as_string(group))) {
+ kv_push(sign_ns, nvim_create_namespace(cstr_as_string(group)));
}
- buf_signcols_add_check(buf, newsign);
+ uint32_t ns = group ? (uint32_t)nvim_create_namespace(cstr_as_string(group)) : 0;
+ DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT;
- if (prev == NULL) {
- // When adding first sign need to redraw the windows to create the
- // column for signs.
- if (buf->b_signlist == NULL) {
- redraw_buf_later(buf, UPD_NOT_VALID);
- changed_line_abv_curs();
- }
+ sign.flags |= kSHIsSign;
+ sign.text.ptr = sp->sn_text ? xstrdup(sp->sn_text) : NULL;
+ sign.sign_name = xstrdup(sp->sn_name);
+ sign.hl_id = sp->sn_text_hl;
+ sign.line_hl_id = sp->sn_line_hl;
+ sign.number_hl_id = sp->sn_num_hl;
+ sign.cursorline_hl_id = sp->sn_cul_hl;
+ sign.priority = (DecorPriority)prio;
- // first sign in signlist
- buf->b_signlist = newsign;
- } else {
- prev->se_next = newsign;
- }
+ bool has_hl = (sp->sn_line_hl || sp->sn_num_hl || sp->sn_cul_hl);
+ uint16_t decor_flags = (sp->sn_text ? MT_FLAG_DECOR_SIGNTEXT : 0)
+ | (has_hl ? MT_FLAG_DECOR_SIGNHL : 0);
+
+ DecorInline decor = { .ext = true, .data.ext = { .vt = NULL, .sh_idx = decor_put_sh(sign) } };
+ extmark_set(buf, ns, id, lnum - 1, 0, -1, -1, decor, decor_flags, true,
+ false, true, true, NULL);
}
-/// Insert a new sign sorted by line number and sign priority.
+/// For an existing, placed sign with "id", modify the sign, group or priority.
+/// Returns the line number of the sign, or zero if the sign is not found.
///
/// @param buf buffer to store sign in
-/// @param prev previous sign entry
/// @param id sign ID
-/// @param group sign group; NULL for global group
+/// @param group sign group
/// @param prio sign priority
-/// @param lnum line number which gets the mark
-/// @param typenr typenr of sign we are adding
-/// @param has_text_or_icon sign has text or icon
-static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, const char *group,
- int prio, linenr_T lnum, int typenr, bool has_text_or_icon)
-{
- sign_entry_T *sign;
-
- // keep signs sorted by lnum, priority and id: insert new sign at
- // the proper position in the list for this lnum.
- while (prev != NULL && prev->se_lnum == lnum
- && (prev->se_priority < prio
- || (prev->se_priority == prio && prev->se_id <= id))) {
- prev = prev->se_prev;
- }
- if (prev == NULL) {
- sign = buf->b_signlist;
- } else {
- sign = prev->se_next;
- }
-
- insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon);
-}
-
-/// Lookup a sign by typenr. Returns NULL if sign is not found.
-static sign_T *find_sign_by_typenr(int typenr)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- return sp;
- }
- }
- return NULL;
-}
-
-/// Get the name of a sign by its typenr.
-static char *sign_typenr2name(int typenr)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- return sp->sn_name;
- }
- }
- return _("[Deleted]");
-}
-
-/// Return information about a sign in a Dict
-static dict_T *sign_get_info(sign_entry_T *sign)
-{
- dict_T *d = tv_dict_alloc();
- tv_dict_add_nr(d, S_LEN("id"), sign->se_id);
- tv_dict_add_str(d, S_LEN("group"), ((sign->se_group == NULL)
- ? (char *)""
- : (char *)sign->se_group->sg_name));
- tv_dict_add_nr(d, S_LEN("lnum"), sign->se_lnum);
- tv_dict_add_str(d, S_LEN("name"), sign_typenr2name(sign->se_typenr));
- tv_dict_add_nr(d, S_LEN("priority"), sign->se_priority);
-
- return d;
-}
-
-// Sort the signs placed on the same line as "sign" by priority. Invoked after
-// changing the priority of an already placed sign. Assumes the signs in the
-// buffer are sorted by line number and priority.
-static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign)
- FUNC_ATTR_NONNULL_ALL
+/// @param sp sign pointer
+static linenr_T buf_mod_sign(buf_T *buf, uint32_t *id, char *group, int prio, sign_T *sp)
{
- // If there is only one sign in the buffer or only one sign on the line or
- // the sign is already sorted by priority, then return.
- if ((sign->se_prev == NULL
- || sign->se_prev->se_lnum != sign->se_lnum
- || sign->se_prev->se_priority > sign->se_priority)
- && (sign->se_next == NULL
- || sign->se_next->se_lnum != sign->se_lnum
- || sign->se_next->se_priority < sign->se_priority)) {
- return;
- }
-
- // One or more signs on the same line as 'sign'
- // Find a sign after which 'sign' should be inserted
-
- // First search backward for a sign with higher priority on the same line
- sign_entry_T *p = sign;
- while (p->se_prev != NULL
- && p->se_prev->se_lnum == sign->se_lnum
- && p->se_prev->se_priority <= sign->se_priority) {
- p = p->se_prev;
- }
- if (p == sign) {
- // Sign not found. Search forward for a sign with priority just before
- // 'sign'.
- p = sign->se_next;
- while (p->se_next != NULL
- && p->se_next->se_lnum == sign->se_lnum
- && p->se_next->se_priority > sign->se_priority) {
- p = p->se_next;
- }
+ int64_t ns = group_get_ns(group);
+ if (ns < 0 || (group && ns == 0)) {
+ return 0;
}
- // Remove 'sign' from the list
- if (buf->b_signlist == sign) {
- buf->b_signlist = sign->se_next;
- }
- if (sign->se_prev != NULL) {
- sign->se_prev->se_next = sign->se_next;
- }
- if (sign->se_next != NULL) {
- sign->se_next->se_prev = sign->se_prev;
- }
- sign->se_prev = NULL;
- sign->se_next = NULL;
-
- // Re-insert 'sign' at the right place
- if (p->se_priority <= sign->se_priority) {
- // 'sign' has a higher priority and should be inserted before 'p'
- sign->se_prev = p->se_prev;
- sign->se_next = p;
- p->se_prev = sign;
- if (sign->se_prev != NULL) {
- sign->se_prev->se_next = sign;
- }
- if (buf->b_signlist == p) {
- buf->b_signlist = sign;
- }
- } else {
- // 'sign' has a lower priority and should be inserted after 'p'
- sign->se_prev = p;
- sign->se_next = p->se_next;
- p->se_next = sign;
- if (sign->se_next != NULL) {
- sign->se_next->se_prev = sign;
- }
+ MTKey mark = marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, *id, false, NULL);
+ if (mark.pos.row >= 0) {
+ buf_set_sign(buf, id, group, prio, mark.pos.row + 1, sp);
}
+ return mark.pos.row + 1;
}
-/// Add the sign into the signlist. Find the right spot to do it though.
+/// Find the line number of the sign with the requested id in group 'group'. If
+/// the sign does not exist, return 0 as the line number. This will still let
+/// the correct file get loaded.
///
/// @param buf buffer to store sign in
/// @param id sign ID
-/// @param groupname sign group
-/// @param prio sign priority
-/// @param lnum line number which gets the mark
-/// @param typenr typenr of sign we are adding
-/// @param has_text_or_icon sign has text or icon
-static void buf_addsign(buf_T *buf, int id, const char *groupname, int prio, linenr_T lnum,
- int typenr, bool has_text_or_icon)
-{
- sign_entry_T *sign; // a sign in the signlist
- sign_entry_T *prev; // the previous sign
-
- prev = NULL;
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (lnum == sign->se_lnum && id == sign->se_id
- && sign_in_group(sign, groupname)) {
- // Update an existing sign
- sign->se_typenr = typenr;
- sign->se_priority = prio;
- sign_sort_by_prio_on_line(buf, sign);
- return;
- } else if (lnum < sign->se_lnum) {
- insert_sign_by_lnum_prio(buf,
- prev,
- id,
- groupname,
- prio,
- lnum,
- typenr,
- has_text_or_icon);
- return;
- }
- prev = sign;
- }
-
- insert_sign_by_lnum_prio(buf,
- prev,
- id,
- groupname,
- prio,
- lnum,
- typenr,
- has_text_or_icon);
-}
-
-/// For an existing, placed sign "markId" change the type to "typenr".
-/// Returns the line number of the sign, or zero if the sign is not found.
-///
-/// @param buf buffer to store sign in
-/// @param markId sign ID
/// @param group sign group
-/// @param typenr typenr of sign we are adding
-/// @param prio sign priority
-static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char *group, int typenr,
- int prio)
+static int buf_findsign(buf_T *buf, int id, char *group)
{
- sign_entry_T *sign; // a sign in the signlist
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_id == markId && sign_in_group(sign, (char *)group)) {
- sign->se_typenr = typenr;
- sign->se_priority = prio;
- sign_sort_by_prio_on_line(buf, sign);
- return sign->se_lnum;
- }
+ int64_t ns = group_get_ns(group);
+ if (ns < 0 || (group && ns == 0)) {
+ return 0;
}
-
- return (linenr_T)0;
+ return marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, (uint32_t)id, false, NULL).pos.row + 1;
}
-/// Return the sign attrs which has the attribute specified by 'type'. Returns
-/// NULL if a sign is not found with the specified attribute.
-/// @param type Type of sign to look for
-/// @param sattrs Sign attrs to search through
-/// @param idx if there multiple signs, this index will pick the n-th
-/// out of the most `max_signs` sorted ascending by Id.
-/// @param max_signs the number of signs, with priority for the ones
-/// with the highest Ids.
-/// @return Attrs of the matching sign, or NULL
-SignTextAttrs *sign_get_attr(int idx, SignTextAttrs sattrs[], int max_signs)
+/// qsort() function to sort signs by line number, priority, id and recency.
+int sign_cmp(const void *p1, const void *p2)
{
- SignTextAttrs *matches[SIGN_SHOW_MAX];
- int sattr_matches = 0;
-
- for (int i = 0; i < SIGN_SHOW_MAX; i++) {
- if (sattrs[i].text != NULL) {
- matches[sattr_matches++] = &sattrs[i];
- // attr list is sorted with most important (priority, id), thus we
- // may stop as soon as we have max_signs matches
- if (sattr_matches >= max_signs) {
- break;
- }
- }
- }
+ const MTKey *s1 = (MTKey *)p1;
+ const MTKey *s2 = (MTKey *)p2;
+ int n = s1->pos.row - s2->pos.row;
- if (sattr_matches > idx) {
- return matches[sattr_matches - idx - 1];
+ if (n) {
+ return n;
}
- return NULL;
-}
-
-/// Return the attributes of all the signs placed on line 'lnum' in buffer
-/// 'buf'. Used when refreshing the screen. Returns the number of signs.
-/// @param buf Buffer in which to search
-/// @param lnum Line in which to search
-/// @param sattrs Output array for attrs
-/// @return Number of signs of which attrs were found
-int buf_get_signattrs(buf_T *buf, linenr_T lnum, SignTextAttrs sattrs[], HlPriAttr *num_attrs,
- HlPriAttr *line_attrs, HlPriAttr *cul_attrs)
-{
- sign_entry_T *sign;
-
- int sattr_matches = 0;
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > lnum) {
- // Signs are sorted by line number in the buffer. No need to check
- // for signs after the specified line number 'lnum'.
- break;
- }
+ DecorSignHighlight *sh1 = decor_find_sign(mt_decor(*s1));
+ DecorSignHighlight *sh2 = decor_find_sign(mt_decor(*s2));
+ assert(sh1 && sh2);
- if (sign->se_lnum < lnum) {
- continue;
- }
+ n = sh2->priority - sh1->priority;
- sign_T *sp = find_sign_by_typenr(sign->se_typenr);
- if (sp == NULL) {
- continue;
- }
-
- if (sp->sn_text != NULL && sattr_matches < SIGN_SHOW_MAX) {
- sattrs[sattr_matches++] = (SignTextAttrs) {
- .text = sp->sn_text,
- .hl_attr_id = sp->sn_text_hl == 0 ? 0 : syn_id2attr(sp->sn_text_hl),
- .priority = sign->se_priority
- };
- }
-
- struct { HlPriAttr *dest; int hl; } cattrs[] = {
- { line_attrs, sp->sn_line_hl },
- { num_attrs, sp->sn_num_hl },
- { cul_attrs, sp->sn_cul_hl },
- { NULL, -1 },
- };
- for (int i = 0; cattrs[i].dest; i++) {
- if (cattrs[i].hl != 0 && sign->se_priority >= cattrs[i].dest->priority) {
- *cattrs[i].dest = (HlPriAttr) {
- .attr_id = syn_id2attr(cattrs[i].hl),
- .priority = sign->se_priority
- };
- }
- }
- }
- return sattr_matches;
+ return n ? n : (n = (int)(s2->id - s1->id))
+ ? n : (sh2->sign_add_id - sh1->sign_add_id);
}
-/// Delete sign 'id' in group 'group' from buffer 'buf'.
-/// If 'id' is zero, then delete all the signs in group 'group'. Otherwise
-/// delete only the specified sign.
-/// If 'group' is '*', then delete the sign in all the groups. If 'group' is
-/// NULL, then delete the sign in the global group. Otherwise delete the sign in
-/// the specified group.
+/// Delete the specified sign(s)
///
-/// @param buf buffer sign is stored in
-/// @param atlnum sign at this line, 0 - at any line
-/// @param id sign id
+/// @param buf buffer sign is stored in or NULL for all buffers
/// @param group sign group
-///
-/// @return the line number of the deleted sign. If multiple signs are deleted,
-/// then returns the line number of the last sign deleted.
-static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char *group)
+/// @param id sign id
+/// @param atlnum single sign at this line, specified signs at any line when -1
+static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum)
{
- sign_entry_T **lastp; // pointer to pointer to current sign
- sign_entry_T *sign; // a sign in a b_signlist
- sign_entry_T *next; // the next sign in a b_signlist
- linenr_T lnum; // line number whose sign was deleted
-
- lastp = &buf->b_signlist;
- lnum = 0;
- for (sign = buf->b_signlist; sign != NULL; sign = next) {
- next = sign->se_next;
- if ((id == 0 || sign->se_id == id)
- && (atlnum == 0 || sign->se_lnum == atlnum)
- && sign_in_group(sign, group)) {
- *lastp = next;
- if (next != NULL) {
- next->se_prev = sign->se_prev;
- }
- lnum = sign->se_lnum;
- buf_signcols_del_check(buf, lnum, lnum);
- if (sign->se_group != NULL) {
- sign_group_unref((char *)sign->se_group->sg_name);
- }
- xfree(sign);
- redraw_buf_line_later(buf, lnum, false);
- // Check whether only one sign needs to be deleted
- // If deleting a sign with a specific identifier in a particular
- // group or deleting any sign at a particular line number, delete
- // only one sign.
- if (group == NULL
- || (*group != '*' && id != 0)
- || (*group == '*' && atlnum != 0)) {
- break;
- }
- } else {
- lastp = &sign->se_next;
- }
- }
-
- // When deleting the last sign the cursor position may change, because the
- // sign columns no longer shows. And the 'signcolumn' may be hidden.
- if (buf->b_signlist == NULL) {
- redraw_buf_later(buf, UPD_NOT_VALID);
- changed_line_abv_curs();
+ int64_t ns = group_get_ns(group);
+ if (ns < 0) {
+ return FAIL;
}
- return lnum;
-}
-
-/// Find the line number of the sign with the requested id in group 'group'. If
-/// the sign does not exist, return 0 as the line number. This will still let
-/// the correct file get loaded.
-///
-/// @param buf buffer to store sign in
-/// @param id sign ID
-/// @param group sign group
-static int buf_findsign(buf_T *buf, int id, char *group)
-{
- sign_entry_T *sign; // a sign in the signlist
+ MarkTreeIter itr[1];
+ int row = atlnum > 0 ? atlnum - 1 : 0;
+ kvec_t(MTKey) signs = KV_INITIAL_VALUE;
+ // Store signs at a specific line number to remove one later.
+ if (atlnum > 0) {
+ if (!marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) {
+ return FAIL;
+ }
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_id == id && sign_in_group(sign, group)) {
- return (int)sign->se_lnum;
+ MTPair pair;
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ if ((ns == UINT32_MAX || ns == pair.start.ns) && mt_decor_sign(pair.start)) {
+ kv_push(signs, pair.start);
+ }
}
+ } else {
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
}
- return 0;
-}
-
-/// Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is
-/// not found at the line. If 'groupname' is NULL, searches in the global group.
-///
-/// @param buf buffer whose sign we are searching for
-/// @param lnum line number of sign
-/// @param groupname sign group name
-static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char *groupname)
-{
- sign_entry_T *sign; // a sign in the signlist
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > lnum) {
- // Signs are sorted by line number in the buffer. No need to check
- // for signs after the specified line number 'lnum'.
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (row && mark.pos.row > row) {
break;
}
-
- if (sign->se_lnum == lnum && sign_in_group(sign, groupname)) {
- return sign;
+ if (!mt_end(mark) && mt_decor_sign(mark)
+ && (id == 0 || (int)mark.id == id)
+ && (ns == UINT32_MAX || ns == mark.ns)) {
+ if (atlnum > 0) {
+ kv_push(signs, mark);
+ marktree_itr_next(buf->b_marktree, itr);
+ } else {
+ extmark_del(buf, itr, mark, true);
+ }
+ } else {
+ marktree_itr_next(buf->b_marktree, itr);
}
}
- return NULL;
-}
-
-/// Return the identifier of the sign at line number 'lnum' in buffer 'buf'.
-///
-/// @param buf buffer whose sign we are searching for
-/// @param lnum line number of sign
-/// @param groupname sign group name
-static int buf_findsign_id(buf_T *buf, linenr_T lnum, char *groupname)
-{
- sign_entry_T *sign; // a sign in the signlist
-
- sign = buf_getsign_at_line(buf, lnum, groupname);
- if (sign != NULL) {
- return sign->se_id;
+ // Sort to remove the highest priority sign at a specific line number.
+ if (kv_size(signs)) {
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+ extmark_del_id(buf, kv_A(signs, 0).ns, kv_A(signs, 0).id);
+ kv_destroy(signs);
+ } else if (atlnum > 0) {
+ return FAIL;
}
- return 0;
-}
-
-/// Delete signs in buffer "buf".
-void buf_delete_signs(buf_T *buf, char *group)
-{
- sign_entry_T *sign;
- sign_entry_T **lastp; // pointer to pointer to current sign
- sign_entry_T *next;
-
// When deleting the last sign need to redraw the windows to remove the
// sign column. Not when curwin is NULL (this means we're exiting).
- if (buf->b_signlist != NULL && curwin != NULL) {
+ if (!buf->b_signs_with_text && curwin != NULL) {
changed_line_abv_curs();
}
- lastp = &buf->b_signlist;
- for (sign = buf->b_signlist; sign != NULL; sign = next) {
- next = sign->se_next;
- if (sign_in_group(sign, group)) {
- *lastp = next;
- if (next != NULL) {
- next->se_prev = sign->se_prev;
- }
- if (sign->se_group != NULL) {
- sign_group_unref((char *)sign->se_group->sg_name);
- }
- xfree(sign);
- } else {
- lastp = &sign->se_next;
- }
- }
- buf_signcols_del_check(buf, 1, MAXLNUM);
+ return OK;
}
/// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers.
-static void sign_list_placed(buf_T *rbuf, char *sign_group)
+static void sign_list_placed(buf_T *rbuf, char *group)
{
- buf_T *buf;
- sign_entry_T *sign;
char lbuf[MSG_BUF_LEN];
- char group[MSG_BUF_LEN];
+ char namebuf[MSG_BUF_LEN];
+ char groupbuf[MSG_BUF_LEN];
+ buf_T *buf = rbuf ? rbuf : firstbuf;
+ int64_t ns = group_get_ns(group);
msg_puts_title(_("\n--- Signs ---"));
msg_putchar('\n');
- if (rbuf == NULL) {
- buf = firstbuf;
- } else {
- buf = rbuf;
- }
+
while (buf != NULL && !got_int) {
- if (buf->b_signlist != NULL) {
+ if (buf->b_signs) {
vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname);
msg_puts_attr(lbuf, HL_ATTR(HLF_D));
msg_putchar('\n');
}
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (got_int) {
- break;
- }
- if (!sign_in_group(sign, sign_group)) {
- continue;
- }
- if (sign->se_group != NULL) {
- vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"),
- sign->se_group->sg_name);
- } else {
- group[0] = '\0';
- }
- vim_snprintf(lbuf, MSG_BUF_LEN,
- _(" line=%ld id=%d%s name=%s priority=%d"),
- (long)sign->se_lnum, sign->se_id, group,
- sign_typenr2name(sign->se_typenr), sign->se_priority);
- msg_puts(lbuf);
- msg_putchar('\n');
- }
- if (rbuf != NULL) {
- break;
- }
- buf = buf->b_next;
- }
-}
-
-/// Adjust or delete a placed sign for inserted/deleted lines.
-///
-/// @return the new line number of the sign, or 0 if the sign is in deleted lines.
-static linenr_T sign_adjust_one(const linenr_T se_lnum, linenr_T line1, linenr_T line2,
- linenr_T amount, linenr_T amount_after)
-{
- if (se_lnum < line1) {
- // Ignore changes to lines after the sign
- return se_lnum;
- }
- if (se_lnum > line2) {
- // Lines inserted or deleted before the sign
- return se_lnum + amount_after;
- }
- if (amount == MAXLNUM) { // sign in deleted lines
- return 0;
- }
- return se_lnum + amount;
-}
-/// Adjust placed signs for inserted/deleted lines.
-void sign_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after)
-{
- sign_entry_T *sign; // a sign in a b_signlist
- sign_entry_T *next; // the next sign in a b_signlist
- sign_entry_T *last = NULL; // pointer to pointer to current sign
- sign_entry_T **lastp = NULL; // pointer to pointer to current sign
- linenr_T new_lnum; // new line number to assign to sign
- int is_fixed = 0;
- int signcol = win_signcol_configured(curwin, &is_fixed);
-
- if (amount == MAXLNUM) { // deleting
- buf_signcols_del_check(curbuf, line1, line2);
- }
-
- lastp = &curbuf->b_signlist;
+ if (ns >= 0) {
+ MarkTreeIter itr[1];
+ kvec_t(MTKey) signs = KV_INITIAL_VALUE;
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
- for (sign = curbuf->b_signlist; sign != NULL; sign = next) {
- next = sign->se_next;
-
- new_lnum = sign_adjust_one(sign->se_lnum, line1, line2, amount, amount_after);
- if (new_lnum == 0) { // sign in deleted lines
- if (!is_fixed || signcol >= 2) {
- *lastp = next;
- if (next) {
- next->se_prev = last;
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (!mt_end(mark) && mt_decor_sign(mark)
+ && (ns == UINT32_MAX || ns == mark.ns)) {
+ kv_push(signs, mark);
}
- xfree(sign);
- continue;
+ marktree_itr_next(buf->b_marktree, itr);
}
- } else {
- // If the new sign line number is past the last line in the buffer,
- // then don't adjust the line number. Otherwise, it will always be past
- // the last line and will not be visible.
- if (new_lnum <= curbuf->b_ml.ml_line_count) {
- sign->se_lnum = new_lnum;
+
+ if (kv_size(signs)) {
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+
+ for (size_t i = 0; i < kv_size(signs); i++) {
+ namebuf[0] = '\0';
+ groupbuf[0] = '\0';
+ MTKey mark = kv_A(signs, i);
+
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
+ if (sh->sign_name != NULL) {
+ vim_snprintf(namebuf, MSG_BUF_LEN, _(" name=%s"), sign_get_name(sh));
+ }
+ if (mark.ns != 0) {
+ vim_snprintf(groupbuf, MSG_BUF_LEN, _(" group=%s"), describe_ns((int)mark.ns, ""));
+ }
+ vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%" PRIdLINENR " id=%u%s%s priority=%d"),
+ mark.pos.row + 1, mark.id, groupbuf, namebuf, sh->priority);
+ msg_puts(lbuf);
+ msg_putchar('\n');
+ }
+ kv_destroy(signs);
}
}
- last = sign;
- lastp = &sign->se_next;
- }
-
- new_lnum = sign_adjust_one(curbuf->b_signcols.sentinel, line1, line2, amount, amount_after);
- if (new_lnum != 0) {
- curbuf->b_signcols.sentinel = new_lnum;
+ if (rbuf != NULL) {
+ return;
+ }
+ buf = buf->b_next;
}
}
@@ -835,93 +332,22 @@ static int sign_cmd_idx(char *begin_cmd, char *end_cmd)
return idx;
}
-/// Find a sign by name. Also returns pointer to the previous sign.
-static sign_T *sign_find(const char *name, sign_T **sp_prev)
-{
- sign_T *sp;
-
- if (sp_prev != NULL) {
- *sp_prev = NULL;
- }
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (strcmp(sp->sn_name, name) == 0) {
- break;
- }
- if (sp_prev != NULL) {
- *sp_prev = sp;
- }
- }
-
- return sp;
-}
-
-/// Allocate a new sign
-static sign_T *alloc_new_sign(char *name)
-{
- sign_T *sp;
- sign_T *lp;
- int start = next_sign_typenr;
-
- // Allocate a new sign.
- sp = xcalloc(1, sizeof(sign_T));
-
- // Check that next_sign_typenr is not already being used.
- // This only happens after wrapping around. Hopefully
- // another one got deleted and we can use its number.
- for (lp = first_sign; lp != NULL;) {
- if (lp->sn_typenr == next_sign_typenr) {
- next_sign_typenr++;
- if (next_sign_typenr == MAX_TYPENR) {
- next_sign_typenr = 1;
- }
- if (next_sign_typenr == start) {
- xfree(sp);
- emsg(_("E612: Too many signs defined"));
- return NULL;
- }
- lp = first_sign; // start all over
- continue;
- }
- lp = lp->sn_next;
- }
-
- sp->sn_typenr = next_sign_typenr;
- if (++next_sign_typenr == MAX_TYPENR) {
- next_sign_typenr = 1; // wrap around
- }
-
- sp->sn_name = xstrdup(name);
-
- return sp;
-}
-
-/// Initialize the icon information for a new sign
-static void sign_define_init_icon(sign_T *sp, char *icon)
-{
- xfree(sp->sn_icon);
- sp->sn_icon = xstrdup(icon);
- backslash_halve(sp->sn_icon);
-}
-
-/// Initialize the text for a new sign
-static int sign_define_init_text(sign_T *sp, char *text)
+/// Initialize the "text" for a new sign and store in "sign_text".
+/// "sp" is NULL for signs added through nvim_buf_set_extmark().
+int init_sign_text(sign_T *sp, char **sign_text, char *text)
{
char *s;
- char *endp;
- int cells;
- size_t len;
+ char *endp = text + (int)strlen(text);
- endp = text + (int)strlen(text);
- for (s = text; s + 1 < endp; s++) {
+ for (s = sp ? text : endp; s + 1 < endp; s++) {
if (*s == '\\') {
- // Remove a backslash, so that it is possible
- // to use a space.
+ // Remove a backslash, so that it is possible to use a space.
STRMOVE(s, s + 1);
endp--;
}
}
// Count cells and check for non-printable chars
- cells = 0;
+ int cells = 0;
for (s = text; s < endp; s += utfc_ptr2len(s)) {
if (!vim_isprintc(utf_ptr2char(s))) {
break;
@@ -930,95 +356,69 @@ static int sign_define_init_text(sign_T *sp, char *text)
}
// Currently must be empty, one or two display cells
if (s != endp || cells > 2) {
- semsg(_("E239: Invalid sign text: %s"), text);
+ if (sp != NULL) {
+ semsg(_("E239: Invalid sign text: %s"), text);
+ }
return FAIL;
}
if (cells < 1) {
- sp->sn_text = NULL;
+ if (sp != NULL) {
+ sp->sn_text = NULL;
+ }
return OK;
}
- xfree(sp->sn_text);
- // Allocate one byte more if we need to pad up
- // with a space.
- len = (size_t)(endp - text + ((cells == 1) ? 1 : 0));
- sp->sn_text = xstrnsave(text, len);
+ if (sp != NULL) {
+ xfree(sp->sn_text);
+ }
+ // Allocate one byte more if we need to pad up with a space.
+ size_t len = (size_t)(endp - text + (cells == 1));
+ *sign_text = xstrnsave(text, len);
if (cells == 1) {
- STRCPY(sp->sn_text + len - 1, " ");
+ STRCPY(*sign_text + len - 1, " ");
}
return OK;
}
/// Define a new sign or update an existing sign
-static int sign_define_by_name(char *name, char *icon, char *linehl, char *text, char *texthl,
+static int sign_define_by_name(char *name, char *icon, char *text, char *linehl, char *texthl,
char *culhl, char *numhl)
{
- sign_T *sp_prev;
- sign_T *sp;
+ cstr_t *key;
+ sign_T **sp = (sign_T **)pmap_put_ref(cstr_t)(&sign_map, name, &key, NULL);
- sp = sign_find(name, &sp_prev);
- if (sp == NULL) {
- sp = alloc_new_sign(name);
- if (sp == NULL) {
- return FAIL;
- }
-
- // add the new sign to the list of signs
- if (sp_prev == NULL) {
- first_sign = sp;
- } else {
- sp_prev->sn_next = sp;
- }
+ if (*sp == NULL) {
+ *key = xstrdup(name);
+ *sp = xcalloc(1, sizeof(sign_T));
+ (*sp)->sn_name = (char *)(*key);
} else {
- // Signs may already exist, a redraw is needed in windows with a
- // non-empty sign list.
+ // Signs may already exist, a redraw is needed in windows with a non-empty sign list.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer->b_signlist != NULL) {
+ if (wp->w_buffer->b_signs) {
redraw_buf_later(wp->w_buffer, UPD_NOT_VALID);
}
}
}
- // set values for a defined sign.
+ // Set values for a defined sign.
if (icon != NULL) {
- sign_define_init_icon(sp, icon);
+ /// Initialize the icon information for a new sign
+ xfree((*sp)->sn_icon);
+ (*sp)->sn_icon = xstrdup(icon);
+ backslash_halve((*sp)->sn_icon);
}
- if (text != NULL && (sign_define_init_text(sp, text) == FAIL)) {
+ if (text != NULL && (init_sign_text(*sp, &(*sp)->sn_text, text) == FAIL)) {
return FAIL;
}
- if (linehl != NULL) {
- if (*linehl == NUL) {
- sp->sn_line_hl = 0;
- } else {
- sp->sn_line_hl = syn_check_group(linehl, strlen(linehl));
- }
- }
-
- if (texthl != NULL) {
- if (*texthl == NUL) {
- sp->sn_text_hl = 0;
- } else {
- sp->sn_text_hl = syn_check_group(texthl, strlen(texthl));
- }
- }
-
- if (culhl != NULL) {
- if (*culhl == NUL) {
- sp->sn_cul_hl = 0;
- } else {
- sp->sn_cul_hl = syn_check_group(culhl, strlen(culhl));
- }
- }
-
- if (numhl != NULL) {
- if (*numhl == NUL) {
- sp->sn_num_hl = 0;
- } else {
- sp->sn_num_hl = syn_check_group(numhl, strlen(numhl));
+ char *arg[] = { linehl, texthl, culhl, numhl };
+ int *hl[] = { &(*sp)->sn_line_hl, &(*sp)->sn_text_hl, &(*sp)->sn_cul_hl, &(*sp)->sn_num_hl };
+ for (int i = 0; i < 4; i++) {
+ if (arg[i] != NULL) {
+ *hl[i] = *arg[i] ? syn_check_group(arg[i], strlen(arg[i])) : 0;
}
}
@@ -1028,25 +428,47 @@ static int sign_define_by_name(char *name, char *icon, char *linehl, char *text,
/// Free the sign specified by 'name'.
static int sign_undefine_by_name(const char *name)
{
- sign_T *sp_prev;
- sign_T *sp;
-
- sp = sign_find(name, &sp_prev);
+ sign_T *sp = pmap_del(cstr_t)(&sign_map, name, NULL);
if (sp == NULL) {
semsg(_("E155: Unknown sign: %s"), name);
return FAIL;
}
- sign_undefine(sp, sp_prev);
+ xfree(sp->sn_name);
+ xfree(sp->sn_text);
+ xfree(sp->sn_icon);
+ xfree(sp);
return OK;
}
+/// List one sign.
+static void sign_list_defined(sign_T *sp)
+{
+ smsg(0, "sign %s", sp->sn_name);
+ if (sp->sn_icon != NULL) {
+ msg_puts(" icon=");
+ msg_outtrans(sp->sn_icon, 0);
+ msg_puts(_(" (not supported)"));
+ }
+ if (sp->sn_text != NULL) {
+ msg_puts(" text=");
+ msg_outtrans(sp->sn_text, 0);
+ }
+ static char *arg[] = { " linehl=", " texthl=", " culhl=", " numhl=" };
+ int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl };
+ for (int i = 0; i < 4; i++) {
+ if (hl[i] > 0) {
+ msg_puts(arg[i]);
+ const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false);
+ msg_puts(p ? p : "NONE");
+ }
+ }
+}
+
/// List the signs matching 'name'
static void sign_list_by_name(char *name)
{
- sign_T *sp;
-
- sp = sign_find(name, NULL);
+ sign_T *sp = pmap_get(cstr_t)(&sign_map, name);
if (sp != NULL) {
sign_list_defined(sp);
} else {
@@ -1066,116 +488,97 @@ static void may_force_numberwidth_recompute(buf_T *buf, int unplace)
}
/// Place a sign at the specified file location or update a sign.
-static int sign_place(int *sign_id, const char *sign_group, const char *sign_name, buf_T *buf,
- linenr_T lnum, int prio)
+static int sign_place(uint32_t *id, char *group, char *name, buf_T *buf, linenr_T lnum, int prio)
{
- sign_T *sp;
-
// Check for reserved character '*' in group name
- if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0')) {
+ if (group != NULL && (*group == '*' || *group == '\0')) {
return FAIL;
}
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (strcmp(sp->sn_name, sign_name) == 0) {
- break;
- }
- }
+ sign_T *sp = pmap_get(cstr_t)(&sign_map, name);
if (sp == NULL) {
- semsg(_("E155: Unknown sign: %s"), sign_name);
+ semsg(_("E155: Unknown sign: %s"), name);
return FAIL;
}
- if (*sign_id == 0) {
- *sign_id = sign_group_get_next_signid(buf, (char *)sign_group);
- }
if (lnum > 0) {
- // ":sign place {id} line={lnum} name={name} file={fname}":
- // place a sign
- bool has_text_or_icon = sp->sn_text != NULL || sp->sn_icon != NULL;
- buf_addsign(buf,
- *sign_id,
- (char *)sign_group,
- prio,
- lnum,
- sp->sn_typenr,
- has_text_or_icon);
+ // ":sign place {id} line={lnum} name={name} file={fname}": place a sign
+ buf_set_sign(buf, id, group, prio, lnum, sp);
} else {
// ":sign place {id} file={fname}": change sign type and/or priority
- lnum = buf_change_sign_type(buf, *sign_id, (char *)sign_group, sp->sn_typenr, prio);
+ lnum = buf_mod_sign(buf, id, group, prio, sp);
}
if (lnum > 0) {
- redraw_buf_line_later(buf, lnum, false);
-
// When displaying signs in the 'number' column, if the width of the
// number column is less than 2, then force recomputing the width.
may_force_numberwidth_recompute(buf, false);
} else {
- semsg(_("E885: Not possible to change sign %s"), sign_name);
+ semsg(_("E885: Not possible to change sign %s"), name);
return FAIL;
}
return OK;
}
-/// Unplace the specified sign
-static int sign_unplace(int sign_id, char *sign_group, buf_T *buf, linenr_T atlnum)
+static int sign_unplace_inner(buf_T *buf, int id, char *group, linenr_T atlnum)
{
- if (buf->b_signlist == NULL) { // No signs in the buffer
- return OK;
+ if (!buf->b_signs) { // No signs in the buffer
+ return FAIL;
}
- if (sign_id == 0) {
- // Delete all the signs in the specified buffer
- redraw_buf_later(buf, UPD_NOT_VALID);
- buf_delete_signs(buf, sign_group);
- } else {
- linenr_T lnum;
- // Delete only the specified signs
- lnum = buf_delsign(buf, atlnum, sign_id, sign_group);
- if (lnum == 0) {
+ if (id == 0 || atlnum > 0 || (group != NULL && *group == '*')) {
+ // Delete multiple specified signs
+ if (!buf_delete_signs(buf, group, id, atlnum)) {
+ return FAIL;
+ }
+ } else {
+ // Delete only a single sign
+ int64_t ns = group_get_ns(group);
+ if (ns < 0 || !extmark_del_id(buf, (uint32_t)ns, (uint32_t)id)) {
return FAIL;
}
- redraw_buf_line_later(buf, lnum, false);
}
// When all the signs in a buffer are removed, force recomputing the
// number column width (if enabled) in all the windows displaying the
// buffer if 'signcolumn' is set to 'number' in that window.
- if (buf->b_signlist == NULL) {
+ if (!buf->b_signs_with_text) {
may_force_numberwidth_recompute(buf, true);
}
return OK;
}
-/// Unplace the sign at the current cursor line.
-static void sign_unplace_at_cursor(char *groupname)
+/// Unplace the specified sign for a single or all buffers
+static int sign_unplace(buf_T *buf, int id, char *group, linenr_T atlnum)
{
- int id = -1;
-
- id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname);
- if (id > 0) {
- sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum);
+ if (buf != NULL) {
+ return sign_unplace_inner(buf, id, group, atlnum);
} else {
- emsg(_("E159: Missing sign number"));
+ int retval = OK;
+ FOR_ALL_BUFFERS(cbuf) {
+ if (!sign_unplace_inner(cbuf, id, group, atlnum)) {
+ retval = FAIL;
+ }
+ }
+ return retval;
}
}
/// Jump to a sign.
-static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf)
+static linenr_T sign_jump(int id, char *group, buf_T *buf)
{
- linenr_T lnum;
+ linenr_T lnum = buf_findsign(buf, id, group);
- if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0) {
- semsg(_("E157: Invalid sign ID: %" PRId64), (int64_t)sign_id);
+ if (lnum <= 0) {
+ semsg(_("E157: Invalid sign ID: %" PRId32), id);
return -1;
}
// goto a sign ...
if (buf_jump_open_win(buf) != NULL) { // ... in a current window
curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_WHITE);
} else { // ... not currently in a window
if (buf->b_fname == NULL) {
@@ -1184,8 +587,7 @@ static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf)
}
size_t cmdlen = strlen(buf->b_fname) + 24;
char *cmd = xmallocz(cmdlen);
- snprintf(cmd, cmdlen, "e +%" PRId64 " %s",
- (int64_t)lnum, buf->b_fname);
+ snprintf(cmd, cmdlen, "e +%" PRId64 " %s", (int64_t)lnum, buf->b_fname);
do_cmdline_cmd(cmd);
xfree(cmd);
}
@@ -1196,72 +598,49 @@ static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf)
}
/// ":sign define {name} ..." command
-static void sign_define_cmd(char *sign_name, char *cmdline)
+static void sign_define_cmd(char *name, char *cmdline)
{
- char *arg;
- char *p = cmdline;
char *icon = NULL;
char *text = NULL;
char *linehl = NULL;
char *texthl = NULL;
char *culhl = NULL;
char *numhl = NULL;
- int failed = false;
// set values for a defined sign.
- for (;;) {
- arg = skipwhite(p);
+ while (true) {
+ char *arg = skipwhite(cmdline);
if (*arg == NUL) {
break;
}
- p = skiptowhite_esc(arg);
+ cmdline = skiptowhite_esc(arg);
if (strncmp(arg, "icon=", 5) == 0) {
- arg += 5;
- XFREE_CLEAR(icon);
- icon = xstrnsave(arg, (size_t)(p - arg));
+ icon = arg + 5;
} else if (strncmp(arg, "text=", 5) == 0) {
- arg += 5;
- XFREE_CLEAR(text);
- text = xstrnsave(arg, (size_t)(p - arg));
+ text = arg + 5;
} else if (strncmp(arg, "linehl=", 7) == 0) {
- arg += 7;
- XFREE_CLEAR(linehl);
- linehl = xstrnsave(arg, (size_t)(p - arg));
+ linehl = arg + 7;
} else if (strncmp(arg, "texthl=", 7) == 0) {
- arg += 7;
- XFREE_CLEAR(texthl);
- texthl = xstrnsave(arg, (size_t)(p - arg));
+ texthl = arg + 7;
} else if (strncmp(arg, "culhl=", 6) == 0) {
- arg += 6;
- XFREE_CLEAR(culhl);
- culhl = xstrnsave(arg, (size_t)(p - arg));
+ culhl = arg + 6;
} else if (strncmp(arg, "numhl=", 6) == 0) {
- arg += 6;
- XFREE_CLEAR(numhl);
- numhl = xstrnsave(arg, (size_t)(p - arg));
+ numhl = arg + 6;
} else {
semsg(_(e_invarg2), arg);
- failed = true;
+ return;
+ }
+ if (*cmdline == NUL) {
break;
}
+ *cmdline++ = NUL;
}
- if (!failed) {
- sign_define_by_name(sign_name, icon, linehl, text,
- texthl, culhl, numhl);
- }
-
- xfree(icon);
- xfree(text);
- xfree(linehl);
- xfree(texthl);
- xfree(culhl);
- xfree(numhl);
+ sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl);
}
/// ":sign place" command
-static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group,
- int prio)
+static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *name, int id, char *group, int prio)
{
if (id <= 0) {
// List signs placed in a file/buffer
@@ -1274,74 +653,37 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, c
// :sign place
// :sign place group={group}
// :sign place group=*
- if (lnum >= 0 || sign_name != NULL
- || (group != NULL && *group == '\0')) {
+ if (lnum >= 0 || name != NULL || (group != NULL && *group == '\0')) {
emsg(_(e_invarg));
} else {
sign_list_placed(buf, group);
}
} else {
// Place a new sign
- if (sign_name == NULL || buf == NULL
- || (group != NULL && *group == '\0')) {
+ if (name == NULL || buf == NULL || (group != NULL && *group == '\0')) {
emsg(_(e_invarg));
return;
}
-
- sign_place(&id, group, sign_name, buf, lnum, prio);
+ uint32_t uid = (uint32_t)id;
+ sign_place(&uid, group, name, buf, lnum, prio);
}
}
/// ":sign unplace" command
-static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group)
+static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group)
{
- if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) {
+ if (lnum >= 0 || name != NULL || (group != NULL && *group == '\0')) {
emsg(_(e_invarg));
return;
}
- if (id == -2) {
- if (buf != NULL) {
- // :sign unplace * file={fname}
- // :sign unplace * group={group} file={fname}
- // :sign unplace * group=* file={fname}
- // :sign unplace * buffer={nr}
- // :sign unplace * group={group} buffer={nr}
- // :sign unplace * group=* buffer={nr}
- sign_unplace(0, group, buf, 0);
- } else {
- // :sign unplace *
- // :sign unplace * group={group}
- // :sign unplace * group=*
- FOR_ALL_BUFFERS(cbuf) {
- if (cbuf->b_signlist != NULL) {
- buf_delete_signs(cbuf, group);
- }
- }
- }
- } else {
- if (buf != NULL) {
- // :sign unplace {id} file={fname}
- // :sign unplace {id} group={group} file={fname}
- // :sign unplace {id} group=* file={fname}
- // :sign unplace {id} buffer={nr}
- // :sign unplace {id} group={group} buffer={nr}
- // :sign unplace {id} group=* buffer={nr}
- sign_unplace(id, group, buf, 0);
- } else {
- if (id == -1) {
- // :sign unplace group={group}
- // :sign unplace group=*
- sign_unplace_at_cursor(group);
- } else {
- // :sign unplace {id}
- // :sign unplace {id} group={group}
- // :sign unplace {id} group=*
- FOR_ALL_BUFFERS(cbuf) {
- sign_unplace(id, group, cbuf, 0);
- }
- }
- }
+ if (id == -1) {
+ lnum = curwin->w_cursor.lnum;
+ buf = curwin->w_buffer;
+ }
+
+ if (!sign_unplace(buf, MAX(0, id), group, lnum) && lnum > 0) {
+ emsg(_("E159: Missing sign number"));
}
}
@@ -1350,15 +692,14 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, i
/// :sign jump {id} buffer={nr}
/// :sign jump {id} group={group} file={fname}
/// :sign jump {id} group={group} buffer={nr}
-static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group)
+static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group)
{
- if (sign_name == NULL && group == NULL && id == -1) {
+ if (name == NULL && group == NULL && id == -1) {
emsg(_(e_argreq));
return;
}
- if (buf == NULL || (group != NULL && *group == '\0')
- || lnum >= 0 || sign_name != NULL) {
+ if (buf == NULL || (group != NULL && *group == '\0') || lnum >= 0 || name != NULL) {
// File or buffer is not specified or an empty group is used
// or a line number or a sign name is specified.
emsg(_(e_invarg));
@@ -1372,20 +713,18 @@ static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int
/// ":sign jump" commands.
/// The supported arguments are: line={lnum} name={name} group={group}
/// priority={prio} and file={fname} or buffer={nr}.
-static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid, char **group,
- int *prio, buf_T **buf, linenr_T *lnum)
+static int parse_sign_cmd_args(int cmd, char *arg, char **name, int *id, char **group, int *prio,
+ buf_T **buf, linenr_T *lnum)
{
- char *arg1;
- char *name;
+ char *arg1 = arg;
char *filename = NULL;
int lnum_arg = false;
// first arg could be placed sign id
- arg1 = arg;
if (ascii_isdigit(*arg)) {
- *signid = getdigits_int(&arg, true, 0);
+ *id = getdigits_int(&arg, true, 0);
if (!ascii_iswhite(*arg) && *arg != NUL) {
- *signid = -1;
+ *id = -1;
arg = arg1;
} else {
arg = skipwhite(arg);
@@ -1399,23 +738,23 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid
arg = skiptowhite(arg);
lnum_arg = true;
} else if (strncmp(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) {
- if (*signid != -1) {
+ if (*id != -1) {
emsg(_(e_invarg));
return FAIL;
}
- *signid = -2;
+ *id = -2;
arg = skiptowhite(arg + 1);
} else if (strncmp(arg, "name=", 5) == 0) {
arg += 5;
- name = arg;
+ char *namep = arg;
arg = skiptowhite(arg);
if (*arg != NUL) {
*arg++ = NUL;
}
- while (name[0] == '0' && name[1] != NUL) {
- name++;
+ while (namep[0] == '0' && namep[1] != NUL) {
+ namep++;
}
- *sign_name = name;
+ *name = namep;
} else if (strncmp(arg, "group=", 6) == 0) {
arg += 6;
*group = arg;
@@ -1454,8 +793,7 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid
// If the filename is not supplied for the sign place or the sign jump
// command, then use the current buffer.
- if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg)
- || cmd == SIGNCMD_JUMP)) {
+ if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg) || cmd == SIGNCMD_JUMP)) {
*buf = curwin->w_buffer;
}
return OK;
@@ -1465,13 +803,10 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid
void ex_sign(exarg_T *eap)
{
char *arg = eap->arg;
- char *p;
- int idx;
- sign_T *sp;
// Parse the subcommand.
- p = skiptowhite(arg);
- idx = sign_cmd_idx(arg, p);
+ char *p = skiptowhite(arg);
+ int idx = sign_cmd_idx(arg, p);
if (idx == SIGNCMD_LAST) {
semsg(_("E160: Unknown sign command: %s"), arg);
return;
@@ -1482,14 +817,13 @@ void ex_sign(exarg_T *eap)
// Define, undefine or list signs.
if (idx == SIGNCMD_LIST && *arg == NUL) {
// ":sign list": list all defined signs
- for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next) {
+ sign_T *sp;
+ map_foreach_value(&sign_map, sp, {
sign_list_defined(sp);
- }
+ });
} else if (*arg == NUL) {
emsg(_("E156: Missing sign name"));
} else {
- char *name;
-
// Isolate the sign name. If it's a number skip leading zeroes,
// so that "099" and "99" are the same sign. But keep "0".
p = skiptowhite(arg);
@@ -1499,248 +833,170 @@ void ex_sign(exarg_T *eap)
while (arg[0] == '0' && arg[1] != NUL) {
arg++;
}
- name = xstrdup(arg);
if (idx == SIGNCMD_DEFINE) {
- sign_define_cmd(name, p);
+ sign_define_cmd(arg, p);
} else if (idx == SIGNCMD_LIST) {
// ":sign list {name}"
- sign_list_by_name(name);
+ sign_list_by_name(arg);
} else {
// ":sign undefine {name}"
- sign_undefine_by_name(name);
+ sign_undefine_by_name(arg);
}
- xfree(name);
return;
}
} else {
int id = -1;
linenr_T lnum = -1;
- char *sign_name = NULL;
+ char *name = NULL;
char *group = NULL;
int prio = SIGN_DEF_PRIO;
buf_T *buf = NULL;
// Parse command line arguments
- if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio,
- &buf, &lnum) == FAIL) {
+ if (parse_sign_cmd_args(idx, arg, &name, &id, &group, &prio, &buf, &lnum) == FAIL) {
return;
}
if (idx == SIGNCMD_PLACE) {
- sign_place_cmd(buf, lnum, sign_name, id, group, prio);
+ sign_place_cmd(buf, lnum, name, id, group, prio);
} else if (idx == SIGNCMD_UNPLACE) {
- sign_unplace_cmd(buf, lnum, sign_name, id, group);
+ sign_unplace_cmd(buf, lnum, name, id, group);
} else if (idx == SIGNCMD_JUMP) {
- sign_jump_cmd(buf, lnum, sign_name, id, group);
+ sign_jump_cmd(buf, lnum, name, id, group);
}
}
}
-/// Return information about a specified sign
-static void sign_getinfo(sign_T *sp, dict_T *retdict)
+/// Get dictionary of information for a defined sign "sp"
+static dict_T *sign_get_info_dict(sign_T *sp)
{
- const char *p;
+ dict_T *d = tv_dict_alloc();
+
+ tv_dict_add_str(d, S_LEN("name"), sp->sn_name);
- tv_dict_add_str(retdict, S_LEN("name"), sp->sn_name);
if (sp->sn_icon != NULL) {
- tv_dict_add_str(retdict, S_LEN("icon"), sp->sn_icon);
+ tv_dict_add_str(d, S_LEN("icon"), sp->sn_icon);
}
if (sp->sn_text != NULL) {
- tv_dict_add_str(retdict, S_LEN("text"), sp->sn_text);
- }
- if (sp->sn_line_hl > 0) {
- p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, false);
- if (p == NULL) {
- p = "NONE";
- }
- tv_dict_add_str(retdict, S_LEN("linehl"), p);
- }
- if (sp->sn_text_hl > 0) {
- p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, false);
- if (p == NULL) {
- p = "NONE";
- }
- tv_dict_add_str(retdict, S_LEN("texthl"), p);
+ tv_dict_add_str(d, S_LEN("text"), sp->sn_text);
}
- if (sp->sn_cul_hl > 0) {
- p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false);
- if (p == NULL) {
- p = "NONE";
+ static char *arg[] = { "linehl", "texthl", "culhl", "numhl" };
+ int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl };
+ for (int i = 0; i < 4; i++) {
+ if (hl[i] > 0) {
+ const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false);
+ tv_dict_add_str(d, arg[i], strlen(arg[i]), p ? p : "NONE");
}
- tv_dict_add_str(retdict, S_LEN("culhl"), p);
- }
- if (sp->sn_num_hl > 0) {
- p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false);
- if (p == NULL) {
- p = "NONE";
- }
- tv_dict_add_str(retdict, S_LEN("numhl"), p);
}
+ return d;
}
-/// If 'name' is NULL, return a list of all the defined signs.
-/// Otherwise, return information about the specified sign.
-static void sign_getlist(const char *name, list_T *retlist)
+/// Get dictionary of information for placed sign "mark"
+static dict_T *sign_get_placed_info_dict(MTKey mark)
{
- sign_T *sp = first_sign;
- dict_T *dict;
-
- if (name != NULL) {
- sp = sign_find((char *)name, NULL);
- if (sp == NULL) {
- return;
- }
- }
+ dict_T *d = tv_dict_alloc();
- for (; sp != NULL && !got_int; sp = sp->sn_next) {
- dict = tv_dict_alloc();
- tv_list_append_dict(retlist, dict);
- sign_getinfo(sp, dict);
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
- if (name != NULL) { // handle only the specified sign
- break;
- }
- }
+ tv_dict_add_str(d, S_LEN("name"), sign_get_name(sh));
+ tv_dict_add_nr(d, S_LEN("id"), (int)mark.id);
+ tv_dict_add_str(d, S_LEN("group"), describe_ns((int)mark.ns, ""));
+ tv_dict_add_nr(d, S_LEN("lnum"), mark.pos.row + 1);
+ tv_dict_add_nr(d, S_LEN("priority"), sh->priority);
+ return d;
}
/// Returns information about signs placed in a buffer as list of dicts.
list_T *get_buffer_signs(buf_T *buf)
- FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- sign_entry_T *sign;
- dict_T *d;
list_T *const l = tv_list_alloc(kListLenMayKnow);
+ MarkTreeIter itr[1];
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- d = sign_get_info(sign);
- tv_list_append_dict(l, d);
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (!mt_end(mark) && mt_decor_sign(mark)) {
+ tv_list_append_dict(l, sign_get_placed_info_dict(mark));
+ }
+ marktree_itr_next(buf->b_marktree, itr);
}
+
return l;
}
/// @return information about all the signs placed in a buffer
-static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char *sign_group,
+static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char *group,
list_T *retlist)
{
- dict_T *d;
- list_T *l;
- sign_entry_T *sign;
-
- d = tv_dict_alloc();
+ dict_T *d = tv_dict_alloc();
tv_list_append_dict(retlist, d);
- tv_dict_add_nr(d, S_LEN("bufnr"), (long)buf->b_fnum);
+ tv_dict_add_nr(d, S_LEN("bufnr"), buf->b_fnum);
- l = tv_list_alloc(kListLenMayKnow);
+ list_T *l = tv_list_alloc(kListLenMayKnow);
tv_dict_add_list(d, S_LEN("signs"), l);
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (!sign_in_group(sign, (char *)sign_group)) {
- continue;
+ int64_t ns = group_get_ns(group);
+ if (!buf->b_signs || ns < 0) {
+ return;
+ }
+
+ MarkTreeIter itr[1];
+ kvec_t(MTKey) signs = KV_INITIAL_VALUE;
+ marktree_itr_get(buf->b_marktree, lnum ? lnum - 1 : 0, 0, itr);
+
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (lnum && mark.pos.row >= lnum) {
+ break;
}
- if ((lnum == 0 && sign_id == 0)
- || (sign_id == 0 && lnum == sign->se_lnum)
- || (lnum == 0 && sign_id == sign->se_id)
- || (lnum == sign->se_lnum && sign_id == sign->se_id)) {
- tv_list_append_dict(l, sign_get_info(sign));
+ if (!mt_end(mark)
+ && (ns == UINT32_MAX || ns == mark.ns)
+ && ((lnum == 0 && sign_id == 0)
+ || (sign_id == 0 && lnum == mark.pos.row + 1)
+ || (lnum == 0 && sign_id == (int)mark.id)
+ || (lnum == mark.pos.row + 1 && sign_id == (int)mark.id))) {
+ if (mt_decor_sign(mark)) {
+ kv_push(signs, mark);
+ }
}
+ marktree_itr_next(buf->b_marktree, itr);
+ }
+
+ if (kv_size(signs)) {
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+ for (size_t i = 0; i < kv_size(signs); i++) {
+ tv_list_append_dict(l, sign_get_placed_info_dict(kv_A(signs, i)));
+ }
+ kv_destroy(signs);
}
}
/// Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the
/// sign placed at the line number. If 'lnum' is zero, return all the signs
/// placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers.
-static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char *sign_group,
- list_T *retlist)
+static void sign_get_placed(buf_T *buf, linenr_T lnum, int id, const char *group, list_T *retlist)
{
if (buf != NULL) {
- sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist);
+ sign_get_placed_in_buf(buf, lnum, id, group, retlist);
} else {
FOR_ALL_BUFFERS(cbuf) {
- if (cbuf->b_signlist != NULL) {
- sign_get_placed_in_buf(cbuf, 0, sign_id, sign_group, retlist);
+ if (cbuf->b_signs) {
+ sign_get_placed_in_buf(cbuf, 0, id, group, retlist);
}
}
}
}
-/// List one sign.
-static void sign_list_defined(sign_T *sp)
-{
- smsg("sign %s", sp->sn_name);
- if (sp->sn_icon != NULL) {
- msg_puts(" icon=");
- msg_outtrans(sp->sn_icon);
- msg_puts(_(" (not supported)"));
- }
- if (sp->sn_text != NULL) {
- msg_puts(" text=");
- msg_outtrans(sp->sn_text);
- }
- if (sp->sn_line_hl > 0) {
- msg_puts(" linehl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_line_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
- if (sp->sn_text_hl > 0) {
- msg_puts(" texthl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_text_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
- if (sp->sn_cul_hl > 0) {
- msg_puts(" culhl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_cul_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
- if (sp->sn_num_hl > 0) {
- msg_puts(" numhl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_num_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
-}
-
-/// Undefine a sign and free its memory.
-static void sign_undefine(sign_T *sp, sign_T *sp_prev)
-{
- xfree(sp->sn_name);
- xfree(sp->sn_icon);
- xfree(sp->sn_text);
- if (sp_prev == NULL) {
- first_sign = sp->sn_next;
- } else {
- sp_prev->sn_next = sp->sn_next;
- }
- xfree(sp);
-}
-
-/// Undefine/free all signs.
void free_signs(void)
{
- while (first_sign != NULL) {
- sign_undefine(first_sign, NULL);
- }
+ cstr_t name;
+ map_foreach_key(&sign_map, name, {
+ sign_undefine_by_name(name);
+ });
}
static enum {
@@ -1758,12 +1014,13 @@ static char *get_nth_sign_name(int idx)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
// Complete with name of signs already defined
+ cstr_t name;
int current_idx = 0;
- for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) {
+ map_foreach_key(&sign_map, name, {
if (current_idx++ == idx) {
- return sp->sn_name;
+ return (char *)name;
}
- }
+ });
return NULL;
}
@@ -1771,35 +1028,24 @@ static char *get_nth_sign_name(int idx)
static char *get_nth_sign_group_name(int idx)
{
// Complete with name of sign groups already defined
- int current_idx = 0;
- int todo = (int)sg_table.ht_used;
- for (hashitem_T *hi = sg_table.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- if (current_idx++ == idx) {
- signgroup_T *const group = HI2SG(hi);
- return (char *)group->sg_name;
- }
- }
+ if (idx < (int)kv_size(sign_ns)) {
+ return (char *)describe_ns((NS)kv_A(sign_ns, idx), "");
}
return NULL;
}
-/// Function given to ExpandGeneric() to obtain the sign command
-/// expansion.
+/// Function given to ExpandGeneric() to obtain the sign command expansion.
char *get_sign_name(expand_T *xp, int idx)
{
switch (expand_what) {
case EXP_SUBCMD:
return cmds[idx];
case EXP_DEFINE: {
- char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=",
- NULL };
+ char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", NULL };
return define_arg[idx];
}
case EXP_PLACE: {
- char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=",
- "buffer=", NULL };
+ char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=", "buffer=", NULL };
return place_arg[idx];
}
case EXP_LIST: {
@@ -1822,29 +1068,24 @@ char *get_sign_name(expand_T *xp, int idx)
/// Handle command line completion for :sign command.
void set_context_in_sign_cmd(expand_T *xp, char *arg)
{
- char *end_subcmd;
- char *last;
- int cmd_idx;
- char *begin_subcmd_args;
-
// Default: expand subcommands.
xp->xp_context = EXPAND_SIGN;
expand_what = EXP_SUBCMD;
xp->xp_pattern = arg;
- end_subcmd = skiptowhite(arg);
+ char *end_subcmd = skiptowhite(arg);
if (*end_subcmd == NUL) {
// expand subcmd name
// :sign {subcmd}<CTRL-D>
return;
}
- cmd_idx = sign_cmd_idx(arg, end_subcmd);
+ int cmd_idx = sign_cmd_idx(arg, end_subcmd);
// :sign {subcmd} {subcmd_args}
// |
// begin_subcmd_args
- begin_subcmd_args = skipwhite(end_subcmd);
+ char *begin_subcmd_args = skipwhite(end_subcmd);
// Expand last argument of subcmd.
//
@@ -1853,6 +1094,7 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg)
// p
// Loop until reaching last argument.
+ char *last;
char *p = begin_subcmd_args;
do {
p = skipwhite(p);
@@ -1940,63 +1182,40 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg)
/// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on
/// failure.
-static int sign_define_from_dict(const char *name_arg, dict_T *dict)
+static int sign_define_from_dict(char *name, dict_T *dict)
{
- char *name = NULL;
+ if (name == NULL) {
+ name = tv_dict_get_string(dict, "name", false);
+ if (name == NULL || name[0] == NUL) {
+ return -1;
+ }
+ }
+
char *icon = NULL;
char *linehl = NULL;
char *text = NULL;
char *texthl = NULL;
char *culhl = NULL;
char *numhl = NULL;
- int retval = -1;
- if (name_arg == NULL) {
- if (dict == NULL) {
- return -1;
- }
- name = tv_dict_get_string(dict, "name", true);
- } else {
- name = xstrdup(name_arg);
- }
- if (name == NULL || name[0] == NUL) {
- goto cleanup;
- }
if (dict != NULL) {
- icon = tv_dict_get_string(dict, "icon", true);
- linehl = tv_dict_get_string(dict, "linehl", true);
- text = tv_dict_get_string(dict, "text", true);
- texthl = tv_dict_get_string(dict, "texthl", true);
- culhl = tv_dict_get_string(dict, "culhl", true);
- numhl = tv_dict_get_string(dict, "numhl", true);
- }
-
- if (sign_define_by_name(name, icon, linehl,
- text, texthl, culhl, numhl)
- == OK) {
- retval = 0;
- }
-
-cleanup:
- xfree(name);
- xfree(icon);
- xfree(linehl);
- xfree(text);
- xfree(texthl);
- xfree(culhl);
- xfree(numhl);
-
- return retval;
+ icon = tv_dict_get_string(dict, "icon", false);
+ linehl = tv_dict_get_string(dict, "linehl", false);
+ text = tv_dict_get_string(dict, "text", false);
+ texthl = tv_dict_get_string(dict, "texthl", false);
+ culhl = tv_dict_get_string(dict, "culhl", false);
+ numhl = tv_dict_get_string(dict, "numhl", false);
+ }
+
+ return sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl) - 1;
}
/// Define multiple signs using attributes from list 'l' and store the return
/// values in 'retlist'.
static void sign_define_multiple(list_T *l, list_T *retlist)
{
- int retval;
-
TV_LIST_ITER_CONST(l, li, {
- retval = -1;
+ int retval = -1;
if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
retval = sign_define_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
} else {
@@ -2009,8 +1228,6 @@ static void sign_define_multiple(list_T *l, list_T *retlist)
/// "sign_define()" function
void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *name;
-
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
// Define multiple signs
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -2022,41 +1239,41 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Define a single sign
rettv->vval.v_number = -1;
- name = tv_get_string_chk(&argvars[0]);
+ char *name = (char *)tv_get_string_chk(&argvars[0]);
if (name == NULL) {
return;
}
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_opt_dict_arg(argvars, 1) == FAIL) {
return;
}
- rettv->vval.v_number = sign_define_from_dict(name,
- argvars[1].v_type ==
- VAR_DICT ? argvars[1].vval.v_dict : NULL);
+ dict_T *d = argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL;
+ rettv->vval.v_number = sign_define_from_dict(name, d);
}
/// "sign_getdefined()" function
void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *name = NULL;
-
tv_list_alloc_ret(rettv, 0);
- if (argvars[0].v_type != VAR_UNKNOWN) {
- name = tv_get_string(&argvars[0]);
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ sign_T *sp;
+ map_foreach_value(&sign_map, sp, {
+ tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp));
+ });
+ } else {
+ sign_T *sp = pmap_get(cstr_t)(&sign_map, tv_get_string(&argvars[0]));
+ if (sp != NULL) {
+ tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp));
+ }
}
-
- sign_getlist(name, rettv->vval.v_list);
}
/// "sign_getplaced()" function
void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
buf_T *buf = NULL;
- dict_T *dict;
- dictitem_T *di;
linenr_T lnum = 0;
int sign_id = 0;
const char *group = NULL;
@@ -2072,19 +1289,17 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT
- || ((dict = argvars[1].vval.v_dict) == NULL)) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 1) == FAIL) {
return;
}
+ dictitem_T *di;
+ dict_T *dict = argvars[1].vval.v_dict;
if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
// get signs placed at this line
- lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
+ lnum = tv_get_lnum(&di->di_tv);
+ if (lnum <= 0) {
return;
}
- (void)lnum;
- lnum = tv_get_lnum(&di->di_tv);
}
if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
// get sign placed with this identifier
@@ -2111,103 +1326,81 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "sign_jump()" function
void f_sign_jump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int sign_id;
- char *sign_group = NULL;
- buf_T *buf;
- bool notanum = false;
-
rettv->vval.v_number = -1;
// Sign identifier
- sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
+ bool notanum = false;
+ int id = (int)tv_get_number_chk(&argvars[0], &notanum);
if (notanum) {
return;
}
- if (sign_id <= 0) {
+ if (id <= 0) {
emsg(_(e_invarg));
return;
}
// Sign group
- const char *sign_group_chk = tv_get_string_chk(&argvars[1]);
- if (sign_group_chk == NULL) {
+ char *group = (char *)tv_get_string_chk(&argvars[1]);
+ if (group == NULL) {
return;
}
- if (sign_group_chk[0] == '\0') {
- sign_group = NULL; // global sign group
- } else {
- sign_group = xstrdup(sign_group_chk);
+ if (group[0] == NUL) {
+ group = NULL;
}
// Buffer to place the sign
- buf = get_buf_arg(&argvars[2]);
+ buf_T *buf = get_buf_arg(&argvars[2]);
if (buf == NULL) {
- goto cleanup;
+ return;
}
- rettv->vval.v_number = sign_jump(sign_id, sign_group, buf);
-
-cleanup:
- xfree(sign_group);
+ rettv->vval.v_number = sign_jump(id, group, buf);
}
/// Place a new sign using the values specified in dict 'dict'. Returns the sign
-/// identifier if successfully placed, otherwise returns 0.
+/// identifier if successfully placed, otherwise returns -1.
static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv,
typval_T *buf_tv, dict_T *dict)
{
- int sign_id = 0;
- char *group = NULL;
- char *sign_name = NULL;
- buf_T *buf = NULL;
dictitem_T *di;
- linenr_T lnum = 0;
- int prio = SIGN_DEF_PRIO;
- bool notanum = false;
- int ret_sign_id = -1;
- // sign identifier
+ int id = 0;
+ bool notanum = false;
if (id_tv == NULL) {
di = tv_dict_find(dict, "id", -1);
if (di != NULL) {
id_tv = &di->di_tv;
}
}
- if (id_tv == NULL) {
- sign_id = 0;
- } else {
- sign_id = (int)tv_get_number_chk(id_tv, &notanum);
+ if (id_tv != NULL) {
+ id = (int)tv_get_number_chk(id_tv, &notanum);
if (notanum) {
return -1;
}
- if (sign_id < 0) {
+ if (id < 0) {
emsg(_(e_invarg));
return -1;
}
}
- // sign group
+ char *group = NULL;
if (group_tv == NULL) {
di = tv_dict_find(dict, "group", -1);
if (di != NULL) {
group_tv = &di->di_tv;
}
}
- if (group_tv == NULL) {
- group = NULL; // global group
- } else {
+ if (group_tv != NULL) {
group = (char *)tv_get_string_chk(group_tv);
if (group == NULL) {
- goto cleanup;
+ return -1;
}
- if (group[0] == '\0') { // global sign group
+ if (group[0] == NUL) {
group = NULL;
- } else {
- group = xstrdup(group);
}
}
- // sign name
+ char *name = NULL;
if (name_tv == NULL) {
di = tv_dict_find(dict, "name", -1);
if (di != NULL) {
@@ -2215,14 +1408,13 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n
}
}
if (name_tv == NULL) {
- goto cleanup;
+ return -1;
}
- sign_name = (char *)tv_get_string_chk(name_tv);
- if (sign_name == NULL) {
- goto cleanup;
+ name = (char *)tv_get_string_chk(name_tv);
+ if (name == NULL) {
+ return -1;
}
- // buffer to place the sign
if (buf_tv == NULL) {
di = tv_dict_find(dict, "buffer", -1);
if (di != NULL) {
@@ -2230,40 +1422,38 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n
}
}
if (buf_tv == NULL) {
- goto cleanup;
+ return -1;
}
- buf = get_buf_arg(buf_tv);
+ buf_T *buf = get_buf_arg(buf_tv);
if (buf == NULL) {
- goto cleanup;
+ return -1;
}
- // line number of the sign
+ linenr_T lnum = 0;
di = tv_dict_find(dict, "lnum", -1);
if (di != NULL) {
lnum = tv_get_lnum(&di->di_tv);
if (lnum <= 0) {
emsg(_(e_invarg));
- goto cleanup;
+ return -1;
}
}
- // sign priority
+ int prio = SIGN_DEF_PRIO;
di = tv_dict_find(dict, "priority", -1);
if (di != NULL) {
prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
if (notanum) {
- goto cleanup;
+ return -1;
}
}
- if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK) {
- ret_sign_id = sign_id;
+ uint32_t uid = (uint32_t)id;
+ if (sign_place(&uid, group, name, buf, lnum, prio) == OK) {
+ return (int)uid;
}
-cleanup:
- xfree(group);
-
- return ret_sign_id;
+ return -1;
}
/// "sign_place()" function
@@ -2273,22 +1463,20 @@ void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = -1;
- if (argvars[4].v_type != VAR_UNKNOWN
- && (argvars[4].v_type != VAR_DICT
- || ((dict = argvars[4].vval.v_dict) == NULL))) {
- emsg(_(e_dictreq));
- return;
+ if (argvars[4].v_type != VAR_UNKNOWN) {
+ if (tv_check_for_nonnull_dict_arg(argvars, 4) == FAIL) {
+ return;
+ }
+ dict = argvars[4].vval.v_dict;
}
- rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3],
- dict);
+ rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1],
+ &argvars[2], &argvars[3], dict);
}
/// "sign_placelist()" function. Place multiple signs.
void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int sign_id;
-
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (argvars[0].v_type != VAR_LIST) {
@@ -2298,7 +1486,7 @@ void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Process the List of sign attributes
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- sign_id = -1;
+ int sign_id = -1;
if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
} else {
@@ -2311,13 +1499,10 @@ void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// Undefine multiple signs
static void sign_undefine_multiple(list_T *l, list_T *retlist)
{
- char *name;
- int retval;
-
TV_LIST_ITER_CONST(l, li, {
- retval = -1;
- name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li));
- if (name != NULL && (sign_undefine_by_name((char *)name) == OK)) {
+ int retval = -1;
+ char *name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li));
+ if (name != NULL && (sign_undefine_by_name(name) == OK)) {
retval = 0;
}
tv_list_append_number(retlist, retval);
@@ -2327,8 +1512,6 @@ static void sign_undefine_multiple(list_T *l, list_T *retlist)
/// "sign_undefine()" function
void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *name;
-
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
// Undefine multiple signs
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -2345,7 +1528,7 @@ void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = 0;
} else {
// Free only the specified sign
- name = tv_get_string_chk(&argvars[0]);
+ const char *name = tv_get_string_chk(&argvars[0]);
if (name == NULL) {
return;
}
@@ -2361,57 +1544,31 @@ void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
{
dictitem_T *di;
- int sign_id = 0;
+ int id = 0;
buf_T *buf = NULL;
- char *group = NULL;
- int retval = -1;
-
- // sign group
- if (group_tv != NULL) {
- group = (char *)tv_get_string(group_tv);
- } else {
- group = tv_dict_get_string(dict, "group", false);
- }
- if (group != NULL) {
- if (group[0] == '\0') { // global sign group
- group = NULL;
- } else {
- group = xstrdup(group);
- }
+ char *group = (group_tv != NULL) ? (char *)tv_get_string(group_tv)
+ : tv_dict_get_string(dict, "group", false);
+ if (group != NULL && group[0] == NUL) {
+ group = NULL;
}
if (dict != NULL) {
if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
buf = get_buf_arg(&di->di_tv);
if (buf == NULL) {
- goto cleanup;
+ return -1;
}
}
if (tv_dict_find(dict, "id", -1) != NULL) {
- sign_id = (int)tv_dict_get_number(dict, "id");
- if (sign_id <= 0) {
+ id = (int)tv_dict_get_number(dict, "id");
+ if (id <= 0) {
emsg(_(e_invarg));
- goto cleanup;
+ return -1;
}
}
}
- if (buf == NULL) {
- // Delete the sign in all the buffers
- retval = 0;
- FOR_ALL_BUFFERS(buf2) {
- if (sign_unplace(sign_id, group, buf2, 0) != OK) {
- retval = -1;
- }
- }
- } else if (sign_unplace(sign_id, group, buf, 0) == OK) {
- retval = 0;
- }
-
-cleanup:
- xfree(group);
-
- return retval;
+ return sign_unplace(buf, id, group, 0) - 1;
}
/// "sign_unplace()" function
@@ -2421,16 +1578,12 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_STRING) {
- emsg(_(e_invarg));
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_dict_arg(argvars, 1) == FAIL) {
return;
}
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- return;
- }
dict = argvars[1].vval.v_dict;
}
@@ -2440,8 +1593,6 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "sign_unplacelist()" function
void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int retval;
-
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (argvars[0].v_type != VAR_LIST) {
@@ -2450,7 +1601,7 @@ void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- retval = -1;
+ int retval = -1;
if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
} else {
diff --git a/src/nvim/sign.h b/src/nvim/sign.h
index ba84cd71a4..1c607bc7de 100644
--- a/src/nvim/sign.h
+++ b/src/nvim/sign.h
@@ -1,13 +1,11 @@
-#ifndef NVIM_SIGN_H
-#define NVIM_SIGN_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/sign_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/sign_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "sign.h.generated.h"
#endif
-#endif // NVIM_SIGN_H
diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h
index 16e783aab7..79d21585fc 100644
--- a/src/nvim/sign_defs.h
+++ b/src/nvim/sign_defs.h
@@ -1,54 +1,22 @@
-#ifndef NVIM_SIGN_DEFS_H
-#define NVIM_SIGN_DEFS_H
-
-#include <stdbool.h>
-
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-// signs: line annotations
-
-// Sign group
-typedef struct signgroup_S {
- uint16_t sg_refcount; // number of signs in this group
- int sg_next_sign_id; // next sign id for this group
- char sg_name[1]; // sign group name
-} signgroup_T;
-
-// Macros to get the sign group structure from the group name
-#define SGN_KEY_OFF offsetof(signgroup_T, sg_name)
-#define HI2SG(hi) ((signgroup_T *)((hi)->hi_key - SGN_KEY_OFF))
-
-typedef struct sign_entry sign_entry_T;
-
-struct sign_entry {
- int se_id; // unique identifier for each placed sign
- int se_typenr; // typenr of sign
- int se_priority; // priority for highlighting
- bool se_has_text_or_icon; // has text or icon
- linenr_T se_lnum; // line number which has this sign
- signgroup_T *se_group; // sign group
- sign_entry_T *se_next; // next entry in a list of signs
- sign_entry_T *se_prev; // previous entry -- for easy reordering
-};
+#pragma once
/// Sign attributes. Used by the screen refresh routines.
typedef struct {
char *text;
- int hl_attr_id;
- int priority;
+ int hl_id;
} SignTextAttrs;
-#define SIGN_SHOW_MAX 9
-
-// Default sign priority for highlighting
-#define SIGN_DEF_PRIO 10
-
-// type argument for sign_get_attr()
-typedef enum {
- SIGN_LINEHL,
- SIGN_NUMHL,
- SIGN_TEXT,
-} SignType;
-
-#endif // NVIM_SIGN_DEFS_H
+/// Struct to hold the sign properties.
+typedef struct sign {
+ char *sn_name; // name of sign
+ char *sn_icon; // name of pixmap
+ char *sn_text; // text used instead of pixmap
+ int sn_line_hl; // highlight ID for line
+ int sn_text_hl; // highlight ID for text
+ int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set
+ int sn_num_hl; // highlight ID for line number
+} sign_T;
+
+enum { SIGN_WIDTH = 2, }; ///< Number of display cells for a sign in the signcolumn
+enum { SIGN_SHOW_MAX = 9, }; ///< Maximum number of signs shown on a single line
+enum { SIGN_DEF_PRIO = 10, }; ///< Default sign highlight priority
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 8e18be5bd1..905f5c25b4 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// spell.c: code for spell checking
//
// See spellfile.c for the Vim spell file format.
@@ -64,7 +61,7 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
@@ -76,6 +73,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -83,18 +81,18 @@
#include "nvim/highlight_defs.h"
#include "nvim/insexpand.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
@@ -104,9 +102,9 @@
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
// Result values. Lower number is accepted over higher one.
@@ -150,7 +148,7 @@ typedef struct matchinf_S {
// for when checking a compound word
int mi_compoff; // start of following word offset
- char_u mi_compflags[MAXWLEN]; // flags for compound words used
+ uint8_t mi_compflags[MAXWLEN]; // flags for compound words used
int mi_complen; // nr of compound words used
int mi_compextra; // nr of COMPOUNDROOT words
@@ -184,12 +182,21 @@ int did_set_spelltab;
# include "spell.c.generated.h"
#endif
-// mode values for find_word
-#define FIND_FOLDWORD 0 // find word case-folded
-#define FIND_KEEPWORD 1 // find keep-case word
-#define FIND_PREFIX 2 // find word after prefix
-#define FIND_COMPOUND 3 // find case-folded compound word
-#define FIND_KEEPCOMPOUND 4 // find keep-case compound word
+/// mode values for find_word
+enum {
+ FIND_FOLDWORD = 0, ///< find word case-folded
+ FIND_KEEPWORD = 1, ///< find keep-case word
+ FIND_PREFIX = 2, ///< find word after prefix
+ FIND_COMPOUND = 3, ///< find case-folded compound word
+ FIND_KEEPCOMPOUND = 4, ///< find keep-case compound word
+};
+
+/// type values for get_char_type
+enum {
+ CHAR_OTHER = 0,
+ CHAR_UPPER = 1,
+ CHAR_DIGIT = 2,
+};
char *e_format = N_("E759: Format error in spell file");
@@ -222,7 +229,7 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
size_t wrongcaplen = 0;
bool count_word = docount;
bool use_camel_case = (wp->w_s->b_p_spo_flags & SPO_CAMEL) != 0;
- bool camel_case = false;
+ bool is_camel_case = false;
// A word never starts at a space or a control character. Return quickly
// then, skipping over the character.
@@ -255,24 +262,14 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
mi.mi_word = ptr;
mi.mi_fend = ptr;
if (spell_iswordp(mi.mi_fend, wp)) {
- bool this_upper = false; // init for gcc
-
if (use_camel_case) {
- int c = utf_ptr2char(mi.mi_fend);
- this_upper = SPELL_ISUPPER(c);
+ mi.mi_fend = advance_camelcase_word(ptr, wp, &is_camel_case);
+ } else {
+ do {
+ MB_PTR_ADV(mi.mi_fend);
+ } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp));
}
- do {
- MB_PTR_ADV(mi.mi_fend);
- if (use_camel_case) {
- const bool prev_upper = this_upper;
- int c = utf_ptr2char(mi.mi_fend);
- this_upper = SPELL_ISUPPER(c);
- camel_case = !prev_upper && this_upper;
- }
- } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp)
- && !camel_case);
-
if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) {
// Check word starting with capital letter.
int c = utf_ptr2char(ptr);
@@ -304,7 +301,7 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
MAXWLEN + 1);
mi.mi_fwordlen = (int)strlen(mi.mi_fword);
- if (camel_case && mi.mi_fwordlen > 0) {
+ if (is_camel_case && mi.mi_fwordlen > 0) {
// introduce a fake word end space into the folded word.
mi.mi_fword[mi.mi_fwordlen - 1] = ' ';
}
@@ -380,7 +377,7 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
MB_PTR_ADV(mi.mi_end);
} else if (mi.mi_result == SP_BAD
&& LANGP_ENTRY(wp->w_s->b_langp, 0)->lp_slang->sl_nobreak) {
- char *p, *fp;
+ char *p;
int save_result = mi.mi_result;
// First language in 'spelllang' is NOBREAK. Find first position
@@ -388,8 +385,8 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
mi.mi_lp = LANGP_ENTRY(wp->w_s->b_langp, 0);
if (mi.mi_lp->lp_slang->sl_fidxs != NULL) {
p = mi.mi_word;
- fp = mi.mi_fword;
- for (;;) {
+ char *fp = mi.mi_fword;
+ while (true) {
MB_PTR_ADV(p);
MB_PTR_ADV(fp);
if (p >= mi.mi_end) {
@@ -424,6 +421,66 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
return (size_t)(mi.mi_end - ptr);
}
+/// Determine the type of character "c".
+static int get_char_type(int c)
+{
+ if (ascii_isdigit(c)) {
+ return CHAR_DIGIT;
+ }
+ if (SPELL_ISUPPER(c)) {
+ return CHAR_UPPER;
+ }
+ return CHAR_OTHER;
+}
+
+/// Returns a pointer to the end of the word starting at "str".
+/// Supports camelCase words.
+static char *advance_camelcase_word(char *str, win_T *wp, bool *is_camel_case)
+{
+ char *end = str;
+
+ *is_camel_case = false;
+
+ if (*str == NUL) {
+ return str;
+ }
+
+ int c = utf_ptr2char(end);
+ MB_PTR_ADV(end);
+ // We need at most the types of the type of the last two chars.
+ int last_last_type = -1;
+ int last_type = get_char_type(c);
+
+ while (*end != NUL && spell_iswordp(end, wp)) {
+ c = utf_ptr2char(end);
+ int this_type = get_char_type(c);
+
+ if (last_last_type == CHAR_UPPER && last_type == CHAR_UPPER
+ && this_type == CHAR_OTHER) {
+ // Handle the following cases:
+ // UpperUpperLower
+ *is_camel_case = true;
+ // Back up by one char.
+ MB_PTR_BACK(str, end);
+ break;
+ } else if ((this_type == CHAR_UPPER && last_type == CHAR_OTHER)
+ || (this_type != last_type
+ && (this_type == CHAR_DIGIT || last_type == CHAR_DIGIT))) {
+ // Handle the following cases:
+ // LowerUpper LowerDigit UpperDigit DigitUpper DigitLower
+ *is_camel_case = true;
+ break;
+ }
+
+ last_last_type = last_type;
+ last_type = this_type;
+
+ MB_PTR_ADV(end);
+ }
+
+ return end;
+}
+
// Check if the word at "mip->mi_word" is in the tree.
// When "mode" is FIND_FOLDWORD check in fold-case word tree.
// When "mode" is FIND_KEEPWORD check in keep-case word tree.
@@ -437,14 +494,14 @@ static void find_word(matchinf_T *mip, int mode)
int flen;
char *ptr;
slang_T *slang = mip->mi_lp->lp_slang;
- char_u *byts;
+ uint8_t *byts;
idx_T *idxs;
if (mode == FIND_KEEPWORD || mode == FIND_KEEPCOMPOUND) {
// Check for word with matching case in keep-case tree.
ptr = mip->mi_word;
flen = 9999; // no case folding, always enough bytes
- byts = (char_u *)slang->sl_kbyts;
+ byts = slang->sl_kbyts;
idxs = slang->sl_kidxs;
if (mode == FIND_KEEPCOMPOUND) {
@@ -455,7 +512,7 @@ static void find_word(matchinf_T *mip, int mode)
// Check for case-folded in case-folded tree.
ptr = mip->mi_fword;
flen = mip->mi_fwordlen; // available case-folded bytes
- byts = (char_u *)slang->sl_fbyts;
+ byts = slang->sl_fbyts;
idxs = slang->sl_fidxs;
if (mode == FIND_PREFIX) {
@@ -482,7 +539,7 @@ static void find_word(matchinf_T *mip, int mode)
// - there is a byte that doesn't match,
// - we reach the end of the tree,
// - or we reach the end of the line.
- for (;;) {
+ while (true) {
if (flen <= 0 && *mip->mi_fend != NUL) {
flen = fold_more(mip);
}
@@ -549,7 +606,7 @@ static void find_word(matchinf_T *mip, int mode)
// One space in the good word may stand for several spaces in the
// checked word.
if (c == ' ') {
- for (;;) {
+ while (true) {
if (flen <= 0 && *mip->mi_fend != NUL) {
flen = fold_more(mip);
}
@@ -737,7 +794,7 @@ static void find_word(matchinf_T *mip, int mode)
// If the word ends the sequence of compound flags of the
// words must match with one of the COMPOUNDRULE items and
// the number of syllables must not be too large.
- mip->mi_compflags[mip->mi_complen] = (char_u)((unsigned)flags >> 24);
+ mip->mi_compflags[mip->mi_complen] = (uint8_t)((unsigned)flags >> 24);
mip->mi_compflags[mip->mi_complen + 1] = NUL;
if (word_ends) {
char fword[MAXWLEN] = { 0 };
@@ -795,9 +852,6 @@ static void find_word(matchinf_T *mip, int mode)
mip->mi_compoff = (int)(p - mip->mi_fword);
}
}
-#if 0
- c = mip->mi_compoff;
-#endif
mip->mi_complen++;
if (flags & WF_COMPROOT) {
mip->mi_compextra++;
@@ -823,16 +877,6 @@ static void find_word(matchinf_T *mip, int mode)
// Find following word in keep-case tree.
mip->mi_compoff = wlen;
find_word(mip, FIND_KEEPCOMPOUND);
-
-#if 0 // Disabled, a prefix must not appear halfway through a compound
- // word, unless the COMPOUNDPERMITFLAG is used, in which case it
- // can't be a postponed prefix.
- if (!slang->sl_nobreak || mip->mi_result == SP_BAD) {
- // Check for following word with prefix.
- mip->mi_compoff = c;
- find_prefix(mip, FIND_COMPOUND);
- }
-#endif
}
if (!slang->sl_nobreak) {
@@ -961,10 +1005,10 @@ bool can_compound(slang_T *slang, const char *word, const uint8_t *flags)
// compound rule. This is used to stop trying a compound if the flags
// collected so far can't possibly match any compound rule.
// Caller must check that slang->sl_comprules is not NULL.
-bool match_compoundrule(slang_T *slang, const char_u *compflags)
+bool match_compoundrule(slang_T *slang, const uint8_t *compflags)
{
// loop over all the COMPOUNDRULE entries
- for (char_u *p = (char_u *)slang->sl_comprules; *p != NUL; p++) {
+ for (char *p = (char *)slang->sl_comprules; *p != NUL; p++) {
// loop over the flags in the compound word we have made, match
// them against the current rule entry
for (int i = 0;; i++) {
@@ -982,21 +1026,21 @@ bool match_compoundrule(slang_T *slang, const char_u *compflags)
// compare against all the flags in []
p++;
while (*p != ']' && *p != NUL) {
- if (*p++ == c) {
+ if ((uint8_t)(*p++) == c) {
match = true;
}
}
if (!match) {
break; // none matches
}
- } else if (*p != c) {
+ } else if ((uint8_t)(*p) != c) {
break; // flag of word doesn't match flag in pattern
}
p++;
}
// Skip to the next "/", where the next pattern starts.
- p = (char_u *)vim_strchr((char *)p, '/');
+ p = vim_strchr(p, '/');
if (p == NULL) {
break;
}
@@ -1062,13 +1106,13 @@ static void find_prefix(matchinf_T *mip, int mode)
int wlen = 0;
slang_T *slang = mip->mi_lp->lp_slang;
- char_u *byts = (char_u *)slang->sl_pbyts;
+ uint8_t *byts = slang->sl_pbyts;
if (byts == NULL) {
return; // array is empty
}
// We use the case-folded word here, since prefixes are always
// case-folded.
- char_u *ptr = (char_u *)mip->mi_fword;
+ char *ptr = mip->mi_fword;
int flen = mip->mi_fwordlen; // available case-folded bytes
if (mode == FIND_COMPOUND) {
// Skip over the previously found word(s).
@@ -1081,7 +1125,7 @@ static void find_prefix(matchinf_T *mip, int mode)
// - there is a byte that doesn't match,
// - we reach the end of the tree,
// - or we reach the end of the line.
- for (;;) {
+ while (true) {
if (flen == 0 && *mip->mi_fend != NUL) {
flen = fold_more(mip);
}
@@ -1126,7 +1170,7 @@ static void find_prefix(matchinf_T *mip, int mode)
}
// Perform a binary search in the list of accepted bytes.
- int c = ptr[wlen];
+ int c = (uint8_t)ptr[wlen];
idx_T lo = arridx;
idx_T hi = arridx + len - 1;
while (lo < hi) {
@@ -1189,11 +1233,19 @@ bool spell_valid_case(int wordflags, int treeflags)
|| (wordflags & WF_ONECAP) != 0));
}
-// Returns true if spell checking is not enabled.
+/// Return true if spell checking is enabled for "wp".
+bool spell_check_window(win_T *wp)
+{
+ return wp->w_p_spell
+ && *wp->w_s->b_p_spl != NUL
+ && wp->w_s->b_langp.ga_len > 0
+ && *(char **)(wp->w_s->b_langp.ga_data) != NULL;
+}
+
+/// Return true and give an error if spell checking is not enabled.
bool no_spell_checking(win_T *wp)
{
- if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL
- || GA_EMPTY(&wp->w_s->b_langp)) {
+ if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL || GA_EMPTY(&wp->w_s->b_langp)) {
emsg(_(e_no_spell));
return true;
}
@@ -1203,19 +1255,18 @@ bool no_spell_checking(win_T *wp)
static void decor_spell_nav_start(win_T *wp)
{
decor_state = (DecorState){ 0 };
- decor_redraw_reset(wp->w_buffer, &decor_state);
+ decor_redraw_reset(wp, &decor_state);
}
-static bool decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, int col,
- char **decor_error)
+static TriState decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, int col)
{
if (*decor_lnum != lnum) {
- decor_providers_invoke_spell(wp, lnum - 1, col, lnum - 1, -1, decor_error);
- decor_redraw_line(wp->w_buffer, lnum - 1, &decor_state);
+ decor_providers_invoke_spell(wp, lnum - 1, col, lnum - 1, -1);
+ decor_redraw_line(wp, lnum - 1, &decor_state);
*decor_lnum = lnum;
}
- decor_redraw_col(wp->w_buffer, col, col, false, &decor_state);
- return decor_state.spell == kTrue;
+ decor_redraw_col(wp, col, 0, false, &decor_state);
+ return decor_state.spell;
}
static inline bool can_syn_spell(win_T *wp, linenr_T lnum, int col)
@@ -1269,7 +1320,6 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
linenr_T lnum = wp->w_cursor.lnum;
clearpos(&found_pos);
- char *decor_error = NULL;
// Ephemeral extmarks are currently stored in the global decor_state.
// When looking for spell errors, we need to:
// - temporarily reset decor_state
@@ -1283,7 +1333,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
decor_spell_nav_start(wp);
while (!got_int) {
- char *line = ml_get_buf(wp->w_buffer, lnum, false);
+ char *line = ml_get_buf(wp->w_buffer, lnum);
len = strlen(line);
if (buflen < len + MAXWLEN + 2) {
@@ -1304,23 +1354,23 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
} else if (curline && wp == curwin) {
// For spellbadword(): check if first word needs a capital.
col = (colnr_T)getwhitecols(line);
- if (check_need_cap(lnum, col)) {
+ if (check_need_cap(curwin, lnum, col)) {
capcol = col;
}
// Need to get the line again, may have looked at the previous
// one.
- line = ml_get_buf(wp->w_buffer, lnum, false);
+ line = ml_get_buf(wp->w_buffer, lnum);
}
// Copy the line into "buf" and append the start of the next line if
// possible. Note: this ml_get_buf() may make "line" invalid, check
// for empty line first.
- bool empty_line = *skipwhite((const char *)line) == NUL;
+ bool empty_line = *skipwhite(line) == NUL;
STRCPY(buf, line);
if (lnum < wp->w_buffer->b_ml.ml_line_count) {
spell_cat_line(buf + strlen(buf),
- ml_get_buf(wp->w_buffer, lnum + 1, false),
+ ml_get_buf(wp->w_buffer, lnum + 1),
MAXWLEN);
}
char *p = buf + skip;
@@ -1352,9 +1402,18 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
: p - buf) > wp->w_cursor.col)) {
col = (colnr_T)(p - buf);
- bool can_spell = (!has_syntax && (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0)
- || decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error)
- || (has_syntax && can_syn_spell(wp, lnum, col));
+ bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0;
+ bool can_spell = !no_plain_buffer;
+ switch (decor_spell_nav_col(wp, lnum, &decor_lnum, col)) {
+ case kTrue:
+ can_spell = true; break;
+ case kFalse:
+ can_spell = false; break;
+ case kNone:
+ if (has_syntax) {
+ can_spell = can_syn_spell(wp, lnum, col);
+ }
+ }
if (!can_spell) {
attr = HLF_COUNT;
@@ -1471,7 +1530,6 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
theend:
decor_state_free(&decor_state);
- xfree(decor_error);
decor_state = saved_decor_start;
xfree(buf);
return ret;
@@ -1483,9 +1541,9 @@ theend:
// to skip those bytes if the word was OK.
void spell_cat_line(char *buf, char *line, int maxlen)
{
- char_u *p = (char_u *)skipwhite(line);
+ char *p = skipwhite(line);
while (vim_strchr("*#/\"\t", (uint8_t)(*p)) != NULL) {
- p = (char_u *)skipwhite((char *)p + 1);
+ p = skipwhite(p + 1);
}
if (*p == NUL) {
@@ -1494,10 +1552,10 @@ void spell_cat_line(char *buf, char *line, int maxlen)
// Only worth concatenating if there is something else than spaces to
// concatenate.
- int n = (int)(p - (char_u *)line) + 1;
+ int n = (int)(p - line) + 1;
if (n < maxlen - 1) {
memset(buf, ' ', (size_t)n);
- xstrlcpy(buf + n, (char *)p, (size_t)(maxlen - n));
+ xstrlcpy(buf + n, p, (size_t)(maxlen - n));
}
}
@@ -1553,7 +1611,7 @@ static void spell_load_lang(char *lang)
lang);
do_cmdline_cmd(autocmd_buf);
} else {
- smsg(_("Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\""),
+ smsg(0, _("Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\""),
lang, spell_enc(), lang);
}
} else if (sl.sl_slang != NULL) {
@@ -1700,23 +1758,32 @@ void slang_clear_sug(slang_T *lp)
// Load one spell file and store the info into a slang_T.
// Invoked through do_in_runtimepath().
-static void spell_load_cb(char *fname, void *cookie)
+static bool spell_load_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
spelload_T *slp = (spelload_T *)cookie;
- slang_T *slang = spell_load_file(fname, slp->sl_lang, NULL, false);
- if (slang == NULL) {
- return;
- }
+ for (int i = 0; i < num_fnames; i++) {
+ slang_T *slang = spell_load_file(fnames[i], slp->sl_lang, NULL, false);
+
+ if (slang == NULL) {
+ continue;
+ }
- // When a previously loaded file has NOBREAK also use it for the
- // ".add" files.
- if (slp->sl_nobreak && slang->sl_add) {
- slang->sl_nobreak = true;
- } else if (slang->sl_nobreak) {
- slp->sl_nobreak = true;
+ // When a previously loaded file has NOBREAK also use it for the
+ // ".add" files.
+ if (slp->sl_nobreak && slang->sl_add) {
+ slang->sl_nobreak = true;
+ } else if (slang->sl_nobreak) {
+ slp->sl_nobreak = true;
+ }
+
+ slp->sl_slang = slang;
+
+ if (!all) {
+ break;
+ }
}
- slp->sl_slang = slang;
+ return num_fnames > 0;
}
/// Add a word to the hashtable of common words.
@@ -1743,12 +1810,12 @@ void count_common_word(slang_T *lp, char *word, int len, uint8_t count)
wordcount_T *wc;
hash_T hash = hash_hash(p);
const size_t p_len = strlen(p);
- hashitem_T *hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash);
+ hashitem_T *hi = hash_lookup(&lp->sl_wordcount, p, p_len, hash);
if (HASHITEM_EMPTY(hi)) {
- wc = xmalloc(sizeof(wordcount_T) + p_len);
+ wc = xmalloc(offsetof(wordcount_T, wc_word) + p_len + 1);
memcpy(wc->wc_word, p, p_len + 1);
wc->wc_count = count;
- hash_add_item(&lp->sl_wordcount, hi, (char *)wc->wc_word, hash);
+ hash_add_item(&lp->sl_wordcount, hi, wc->wc_word, hash);
} else {
wc = HI2WC(hi);
wc->wc_count = (uint16_t)(wc->wc_count + count);
@@ -1775,7 +1842,7 @@ bool byte_in_str(uint8_t *str, int n)
int init_syl_tab(slang_T *slang)
{
ga_init(&slang->sl_syl_items, sizeof(syl_item_T), 4);
- char *p = vim_strchr((char *)slang->sl_syllable, '/');
+ char *p = vim_strchr(slang->sl_syllable, '/');
while (p != NULL) {
*p++ = NUL;
if (*p == NUL) { // trailing slash
@@ -1838,7 +1905,7 @@ static int count_syllables(slang_T *slang, const char *word)
// No recognized syllable item, at least a syllable char then?
int c = utf_ptr2char(p);
len = utfc_ptr2len(p);
- if (vim_strchr((char *)slang->sl_syllable, c) == NULL) {
+ if (vim_strchr(slang->sl_syllable, c) == NULL) {
skip = false; // No, search for next syllable
} else if (!skip) {
cnt++; // Yes, count it
@@ -1851,7 +1918,7 @@ static int count_syllables(slang_T *slang, const char *word)
/// Parse 'spelllang' and set w_s->b_langp accordingly.
/// @return NULL if it's OK, an untranslated error message otherwise.
-char *did_set_spelllang(win_T *wp)
+char *parse_spelllang(win_T *wp)
{
garray_T ga;
char *splp;
@@ -1863,14 +1930,12 @@ char *did_set_spelllang(win_T *wp)
int c;
char lang[MAXWLEN + 1];
char spf_name[MAXPATHL];
- int len;
char *p;
int round;
char *spf;
char *use_region = NULL;
bool dont_use_region = false;
bool nobreak = false;
- langp_T *lp, *lp2;
static bool recursive = false;
char *ret_msg = NULL;
char *spl_copy;
@@ -1900,7 +1965,7 @@ char *did_set_spelllang(win_T *wp)
// Get one language name.
copy_option_part(&splp, lang, MAXWLEN, ",");
region = NULL;
- len = (int)strlen(lang);
+ int len = (int)strlen(lang);
if (!valid_spelllang(lang)) {
continue;
@@ -1994,7 +2059,7 @@ char *did_set_spelllang(win_T *wp)
} else {
// This is probably an error. Give a warning and
// accept the words anyway.
- smsg(_("Warning: region %s not supported"),
+ smsg(0, _("Warning: region %s not supported"),
region);
}
} else {
@@ -2107,7 +2172,7 @@ char *did_set_spelllang(win_T *wp)
// REP items. If the language doesn't support it itself use another one
// with the same name. E.g. for "en-math" use "en".
for (int i = 0; i < ga.ga_len; i++) {
- lp = LANGP_ENTRY(ga, i);
+ langp_T *lp = LANGP_ENTRY(ga, i);
// sound folding
if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
@@ -2116,7 +2181,7 @@ char *did_set_spelllang(win_T *wp)
} else {
// find first similar language that does sound folding
for (int j = 0; j < ga.ga_len; j++) {
- lp2 = LANGP_ENTRY(ga, j);
+ langp_T *lp2 = LANGP_ENTRY(ga, j);
if (!GA_EMPTY(&lp2->lp_slang->sl_sal)
&& strncmp(lp->lp_slang->sl_name,
lp2->lp_slang->sl_name, 2) == 0) {
@@ -2133,7 +2198,7 @@ char *did_set_spelllang(win_T *wp)
} else {
// find first similar language that has REP items
for (int j = 0; j < ga.ga_len; j++) {
- lp2 = LANGP_ENTRY(ga, j);
+ langp_T *lp2 = LANGP_ENTRY(ga, j);
if (!GA_EMPTY(&lp2->lp_slang->sl_rep)
&& strncmp(lp->lp_slang->sl_name,
lp2->lp_slang->sl_name, 2) == 0) {
@@ -2167,14 +2232,14 @@ static void use_midword(slang_T *lp, win_T *wp)
return;
}
- for (char *p = (char *)lp->sl_midword; *p != NUL;) {
+ for (char *p = lp->sl_midword; *p != NUL;) {
const int c = utf_ptr2char(p);
const int l = utfc_ptr2len(p);
if (c < 256 && l <= 2) {
wp->w_s->b_spell_ismw[c] = true;
} else if (wp->w_s->b_spell_ismw_mb == NULL) {
// First multi-byte char in "b_spell_ismw_mb".
- wp->w_s->b_spell_ismw_mb = xstrnsave(p, (size_t)l);
+ wp->w_s->b_spell_ismw_mb = xmemdupz(p, (size_t)l);
} else {
// Append multi-byte chars to "b_spell_ismw_mb".
const int n = (int)strlen(wp->w_s->b_spell_ismw_mb);
@@ -2215,10 +2280,10 @@ static int find_region(const char *rp, const char *region)
/// @param[in] end End of word or NULL for NUL delimited string
///
/// @returns Case type of word
-int captype(char *word, const char *end)
+int captype(const char *word, const char *end)
FUNC_ATTR_NONNULL_ARG(1)
{
- char *p;
+ const char *p;
// find first letter
for (p = word; !spell_iswordp_nmw(p, curwin); MB_PTR_ADV(p)) {
@@ -2226,7 +2291,7 @@ int captype(char *word, const char *end)
return 0; // only non-word characters, illegal word
}
}
- int c = mb_ptr2char_adv((const char **)&p);
+ int c = mb_ptr2char_adv(&p);
bool allcap;
bool firstcap = allcap = SPELL_ISUPPER(c);
bool past_second = false; // past second word char
@@ -2309,7 +2374,7 @@ void spell_reload(void)
// window for this buffer in which 'spell' is set.
if (*wp->w_s->b_p_spl != NUL) {
if (wp->w_p_spell) {
- (void)did_set_spelllang(wp);
+ (void)parse_spelllang(wp);
break;
}
}
@@ -2354,8 +2419,8 @@ void clear_spell_chartab(spelltab_T *sp)
CLEAR_FIELD(sp->st_isu);
for (int i = 0; i < 256; i++) {
- sp->st_fold[i] = (char_u)i;
- sp->st_upper[i] = (char_u)i;
+ sp->st_fold[i] = (uint8_t)i;
+ sp->st_upper[i] = (uint8_t)i;
}
// We include digits. A word shouldn't start with a digit, but handling
@@ -2366,11 +2431,11 @@ void clear_spell_chartab(spelltab_T *sp)
for (int i = 'A'; i <= 'Z'; i++) {
sp->st_isw[i] = true;
sp->st_isu[i] = true;
- sp->st_fold[i] = (char_u)(i + 0x20);
+ sp->st_fold[i] = (uint8_t)(i + 0x20);
}
for (int i = 'a'; i <= 'z'; i++) {
sp->st_isw[i] = true;
- sp->st_upper[i] = (char_u)(i - 0x20);
+ sp->st_upper[i] = (uint8_t)(i - 0x20);
}
}
@@ -2391,8 +2456,8 @@ void init_spell_chartab(void)
// The folded/upper-cased value is different between latin1 and
// utf8 for 0xb5, causing E763 for no good reason. Use the latin1
// value for utf-8 to avoid this.
- spelltab.st_fold[i] = (f < 256) ? (char_u)f : (char_u)i;
- spelltab.st_upper[i] = (u < 256) ? (char_u)u : (char_u)i;
+ spelltab.st_fold[i] = (f < 256) ? (uint8_t)f : (uint8_t)i;
+ spelltab.st_upper[i] = (u < 256) ? (uint8_t)u : (uint8_t)i;
}
}
@@ -2480,7 +2545,7 @@ static bool spell_iswordp_w(const int *p, const win_T *wp)
// Uses the character definitions from the .spl file.
// When using a multi-byte 'encoding' the length may change!
// Returns FAIL when something wrong.
-int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen)
+int spell_casefold(const win_T *wp, const char *str, int len, char *buf, int buflen)
FUNC_ATTR_NONNULL_ALL
{
if (len >= buflen) {
@@ -2491,12 +2556,12 @@ int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen)
int outi = 0;
// Fold one character at a time.
- for (char *p = str; p < str + len;) {
+ for (const char *p = str; p < str + len;) {
if (outi + MB_MAXBYTES > buflen) {
buf[outi] = NUL;
return FAIL;
}
- int c = mb_cptr2char_adv((const char **)&p);
+ int c = mb_cptr2char_adv(&p);
// Exception: greek capital sigma 0x03A3 folds to 0x03C3, except
// when it is the last character in a word, then it folds to
@@ -2519,25 +2584,24 @@ int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen)
}
// Check if the word at line "lnum" column "col" is required to start with a
-// capital. This uses 'spellcapcheck' of the current buffer.
-bool check_need_cap(linenr_T lnum, colnr_T col)
+// capital. This uses 'spellcapcheck' of the buffer in window "wp".
+bool check_need_cap(win_T *wp, linenr_T lnum, colnr_T col)
{
- bool need_cap = false;
-
- if (curwin->w_s->b_cap_prog == NULL) {
+ if (wp->w_s->b_cap_prog == NULL) {
return false;
}
- char *line = get_cursor_line_ptr();
+ bool need_cap = false;
+ char *line = col ? ml_get_buf(wp->w_buffer, lnum) : NULL;
char *line_copy = NULL;
colnr_T endcol = 0;
- if (getwhitecols(line) >= (int)col) {
+ if (col == 0 || getwhitecols(line) >= col) {
// At start of line, check if previous line is empty or sentence
// ends there.
if (lnum == 1) {
need_cap = true;
} else {
- line = ml_get(lnum - 1);
+ line = ml_get_buf(wp->w_buffer, lnum - 1);
if (*skipwhite(line) == NUL) {
need_cap = true;
} else {
@@ -2554,13 +2618,13 @@ bool check_need_cap(linenr_T lnum, colnr_T col)
if (endcol > 0) {
// Check if sentence ends before the bad word.
regmatch_T regmatch = {
- .regprog = curwin->w_s->b_cap_prog,
+ .regprog = wp->w_s->b_cap_prog,
.rm_ic = false
};
char *p = line + endcol;
- for (;;) {
+ while (true) {
MB_PTR_BACK(line, p);
- if (p == line || spell_iswordp_nmw(p, curwin)) {
+ if (p == line || spell_iswordp_nmw(p, wp)) {
break;
}
if (vim_regexec(&regmatch, p, 0)
@@ -2569,7 +2633,7 @@ bool check_need_cap(linenr_T lnum, colnr_T col)
break;
}
}
- curwin->w_s->b_cap_prog = regmatch.regprog;
+ wp->w_s->b_cap_prog = regmatch.regprog;
}
xfree(line_copy);
@@ -2588,9 +2652,11 @@ void ex_spellrepall(exarg_T *eap)
emsg(_("E752: No previous spell replacement"));
return;
}
- int addlen = (int)(strlen(repl_to) - strlen(repl_from));
+ const size_t repl_from_len = strlen(repl_from);
+ const size_t repl_to_len = strlen(repl_to);
+ const int addlen = (int)(repl_to_len - repl_from_len);
- size_t frompatlen = strlen(repl_from) + 7;
+ const size_t frompatlen = repl_from_len + 7;
char *frompat = xmalloc(frompatlen);
snprintf(frompat, frompatlen, "\\V\\<%s\\>", repl_from);
p_ws = false;
@@ -2599,7 +2665,7 @@ void ex_spellrepall(exarg_T *eap)
sub_nlines = 0;
curwin->w_cursor.lnum = 0;
while (!got_int) {
- if (do_search(NULL, '/', '/', frompat, 1L, SEARCH_KEEP, NULL) == 0
+ if (do_search(NULL, '/', '/', frompat, 1, SEARCH_KEEP, NULL) == 0
|| u_save_cursor() == FAIL) {
break;
}
@@ -2607,14 +2673,15 @@ void ex_spellrepall(exarg_T *eap)
// Only replace when the right word isn't there yet. This happens
// when changing "etc" to "etc.".
char *line = get_cursor_line_ptr();
- if (addlen <= 0 || strncmp(line + curwin->w_cursor.col,
- repl_to, strlen(repl_to)) != 0) {
+ if (addlen <= 0
+ || strncmp(line + curwin->w_cursor.col, repl_to, repl_to_len) != 0) {
char *p = xmalloc(strlen(line) + (size_t)addlen + 1);
memmove(p, line, (size_t)curwin->w_cursor.col);
STRCPY(p + curwin->w_cursor.col, repl_to);
- STRCAT(p, line + curwin->w_cursor.col + strlen(repl_from));
+ STRCAT(p, line + curwin->w_cursor.col + repl_from_len);
ml_replace(curwin->w_cursor.lnum, p, false);
- changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
+ inserted_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col,
+ (int)repl_from_len, (int)repl_to_len);
if (curwin->w_cursor.lnum != prev_lnum) {
sub_nlines++;
@@ -2622,7 +2689,7 @@ void ex_spellrepall(exarg_T *eap)
}
sub_nsubs++;
}
- curwin->w_cursor.col += (colnr_T)strlen(repl_to);
+ curwin->w_cursor.col += (colnr_T)repl_to_len;
}
p_ws = save_ws;
@@ -2643,10 +2710,10 @@ void ex_spellrepall(exarg_T *eap)
/// @param[in] word source string to copy
/// @param[in,out] wcopy copied string, with case of first letter changed
/// @param[in] upper True to upper case, otherwise lower case
-void onecap_copy(char *word, char *wcopy, bool upper)
+void onecap_copy(const char *word, char *wcopy, bool upper)
{
- char *p = word;
- int c = mb_cptr2char_adv((const char **)&p);
+ const char *p = word;
+ int c = mb_cptr2char_adv(&p);
if (upper) {
c = SPELL_TOUPPER(c);
} else {
@@ -2658,26 +2725,26 @@ void onecap_copy(char *word, char *wcopy, bool upper)
// Make a copy of "word" with all the letters upper cased into
// "wcopy[MAXWLEN]". The result is NUL terminated.
-void allcap_copy(char *word, char *wcopy)
+void allcap_copy(const char *word, char *wcopy)
{
- char_u *d = (char_u *)wcopy;
- for (char *s = word; *s != NUL;) {
- int c = mb_cptr2char_adv((const char **)&s);
+ char *d = wcopy;
+ for (const char *s = word; *s != NUL;) {
+ int c = mb_cptr2char_adv(&s);
if (c == 0xdf) {
c = 'S';
- if (d - (char_u *)wcopy >= MAXWLEN - 1) {
+ if (d - wcopy >= MAXWLEN - 1) {
break;
}
- *d++ = (char_u)c;
+ *d++ = (char)c;
} else {
c = SPELL_TOUPPER(c);
}
- if (d - (char_u *)wcopy >= MAXWLEN - MB_MAXBYTES) {
+ if (d - wcopy >= MAXWLEN - MB_MAXBYTES) {
break;
}
- d += utf_char2bytes(c, (char *)d);
+ d += utf_char2bytes(c, d);
}
*d = NUL;
}
@@ -2777,7 +2844,7 @@ void spell_soundfold(slang_T *slang, char *inword, bool folded, char *res)
// Perform sound folding of "inword" into "res" according to SOFOFROM and
// SOFOTO lines.
-static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res)
+static void spell_soundfold_sofo(slang_T *slang, const char *inword, char *res)
{
int ri = 0;
@@ -2785,8 +2852,8 @@ static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res)
// The sl_sal_first[] table contains the translation for chars up to
// 255, sl_sal the rest.
- for (char *s = inword; *s != NUL;) {
- int c = mb_cptr2char_adv((const char **)&s);
+ for (const char *s = inword; *s != NUL;) {
+ int c = mb_cptr2char_adv(&s);
if (utf_class(c) == 0) {
c = ' ';
} else if (c < 256) {
@@ -2796,7 +2863,7 @@ static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res)
if (ip == NULL) { // empty list, can't match
c = NUL;
} else {
- for (;;) { // find "c" in the list
+ while (true) { // find "c" in the list
if (*ip == 0) { // not found
c = NUL;
break;
@@ -2834,7 +2901,6 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
int j, z;
int reslen;
int k = 0;
- int z0;
int k0;
int n0;
int pri;
@@ -2846,8 +2912,8 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
// Remove accents, if wanted. We actually remove all non-word characters.
// But keep white space.
int wordlen = 0;
- for (const char *s = (char *)inword; *s != NUL;) {
- const char_u *t = (char_u *)s;
+ for (const char *s = inword; *s != NUL;) {
+ const char *t = s;
int c = mb_cptr2char_adv(&s);
if (slang->sl_rem_accents) {
if (utf_class(c) == 0) {
@@ -2858,7 +2924,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
did_white = true;
} else {
did_white = false;
- if (!spell_iswordp_nmw((char *)t, curwin)) {
+ if (!spell_iswordp_nmw(t, curwin)) {
continue;
}
}
@@ -2875,7 +2941,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
while ((c = word[i]) != NUL) {
// Start with the first rule that has the character in the word.
int n = slang->sl_sal_first[c & 0xff];
- z0 = 0;
+ int z0 = 0;
if (n >= 0) {
// Check all rules for the same index byte.
@@ -2915,10 +2981,10 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
k++;
}
- char_u *s = (char_u *)smp[n].sm_rules;
+ char *s = smp[n].sm_rules;
pri = 5; // default priority
- p0 = *s;
+ p0 = (uint8_t)(*s);
k0 = k;
while (*s == '-' && k > 1) {
k--;
@@ -2929,7 +2995,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
if (ascii_isdigit(*s)) {
// determine priority
- pri = *s - '0';
+ pri = (uint8_t)(*s) - '0';
s++;
}
if (*s == '^' && *(s + 1) == '^') {
@@ -2992,7 +3058,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
p0 = 5;
- s = (char_u *)smp[n0].sm_rules;
+ s = smp[n0].sm_rules;
while (*s == '-') {
// "k0" gets NOT reduced because
// "if (k0 == k)"
@@ -3002,7 +3068,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
s++;
}
if (ascii_isdigit(*s)) {
- p0 = *s - '0';
+ p0 = (uint8_t)(*s) - '0';
s++;
}
@@ -3033,8 +3099,8 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
// replace string
ws = smp[n].sm_to_w;
- s = (char_u *)smp[n].sm_rules;
- p0 = (vim_strchr((char *)s, '<') != NULL) ? 1 : 0;
+ s = smp[n].sm_rules;
+ p0 = (vim_strchr(s, '<') != NULL) ? 1 : 0;
if (p0 == 1 && z == 0) {
// rule with '<' is used
if (reslen > 0 && ws != NULL && *ws != NUL
@@ -3077,7 +3143,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
} else {
c = *ws;
}
- if (strstr((char *)s, "^^") != NULL) {
+ if (strstr(s, "^^") != NULL) {
if (c != NUL) {
wres[reslen++] = c;
}
@@ -3130,9 +3196,9 @@ void ex_spellinfo(exarg_T *eap)
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len && !got_int; lpi++) {
langp_T *const lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
msg_puts("file: ");
- msg_puts((const char *)lp->lp_slang->sl_fname);
+ msg_puts(lp->lp_slang->sl_fname);
msg_putchar('\n');
- const char *const p = (const char *)lp->lp_slang->sl_info;
+ const char *const p = lp->lp_slang->sl_info;
if (p != NULL) {
msg_puts(p);
msg_putchar('\n');
@@ -3153,17 +3219,15 @@ void ex_spelldump(exarg_T *eap)
if (no_spell_checking(curwin)) {
return;
}
- char *spl;
- long dummy;
- (void)get_option_value("spl", &dummy, &spl, NULL, OPT_LOCAL);
+ OptVal spl = get_option_value("spl", NULL, OPT_LOCAL, NULL);
// Create a new empty buffer in a new window.
do_cmdline_cmd("new");
// enable spelling locally in the new window
- set_option_value_give_err("spell", true, "", OPT_LOCAL);
- set_option_value_give_err("spl", dummy, spl, OPT_LOCAL);
- xfree(spl);
+ set_option_value_give_err("spell", BOOLEAN_OPTVAL(true), OPT_LOCAL);
+ set_option_value_give_err("spl", spl, OPT_LOCAL);
+ optval_free(spl);
if (!buf_is_empty(curbuf)) {
return;
@@ -3195,7 +3259,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
int curi[MAXWLEN];
char word[MAXWLEN];
int c;
- char *byts;
+ uint8_t *byts;
idx_T *idxs;
linenr_T lnum = 0;
int depth;
@@ -3238,11 +3302,9 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
}
}
- if (do_region && region_names != NULL) {
- if (pat == NULL) {
- vim_snprintf(IObuff, IOSIZE, "/regions=%s", region_names);
- ml_append(lnum++, IObuff, (colnr_T)0, false);
- }
+ if (do_region && region_names != NULL && pat == NULL) {
+ vim_snprintf(IObuff, IOSIZE, "/regions=%s", region_names);
+ ml_append(lnum++, IObuff, 0, false);
} else {
do_region = false;
}
@@ -3257,7 +3319,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
if (pat == NULL) {
vim_snprintf(IObuff, IOSIZE, "# file: %s", slang->sl_fname);
- ml_append(lnum++, IObuff, (colnr_T)0, false);
+ ml_append(lnum++, IObuff, 0, false);
}
// When matching with a pattern and there are no prefixes only use
@@ -3297,7 +3359,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
// Do one more byte at this node.
n = arridx[depth] + curi[depth];
curi[depth]++;
- c = (uint8_t)byts[n];
+ c = byts[n];
if (c == 0 || depth >= MAXWLEN - 1) {
// End of word or reached maximum length, deal with the
// word.
@@ -3426,7 +3488,7 @@ static void dump_word(slang_T *slang, char *word, char *pat, Direction *dir, int
}
}
- ml_append(lnum, p, (colnr_T)0, false);
+ ml_append(lnum, p, 0, false);
} else if (((dumpflags & DUMPFLAG_ICASE)
? mb_strnicmp(p, pat, strlen(pat)) == 0
: strncmp(p, pat, strlen(pat)) == 0)
@@ -3463,7 +3525,7 @@ static linenr_T dump_prefixes(slang_T *slang, char *word, char *pat, Direction *
has_word_up = true;
}
- char_u *byts = (char_u *)slang->sl_pbyts;
+ uint8_t *byts = slang->sl_pbyts;
idx_T *idxs = slang->sl_pidxs;
if (byts != NULL) { // array not is empty
// Loop over all prefixes, building them byte-by-byte in prefix[].
@@ -3585,7 +3647,7 @@ static bool spell_expand_need_cap;
void spell_expand_check_cap(colnr_T col)
{
- spell_expand_need_cap = check_need_cap(curwin->w_cursor.lnum, col);
+ spell_expand_need_cap = check_need_cap(curwin, curwin->w_cursor.lnum, col);
}
// Get list of spelling suggestions.
@@ -3613,16 +3675,16 @@ bool valid_spellfile(const char *val)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
for (const char *s = val; *s != NUL; s++) {
- if (!vim_isfilec((uint8_t)(*s)) && *s != ',' && *s != ' ') {
+ if (!vim_is_fname_char((uint8_t)(*s))) {
return false;
}
}
return true;
}
-char *did_set_spell_option(bool is_spellfile)
+const char *did_set_spell_option(bool is_spellfile)
{
- char *errmsg = NULL;
+ const char *errmsg = NULL;
if (is_spellfile) {
int l = (int)strlen(curwin->w_s->b_p_spf);
@@ -3638,7 +3700,7 @@ char *did_set_spell_option(bool is_spellfile)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == curbuf && wp->w_p_spell) {
- errmsg = did_set_spelllang(wp);
+ errmsg = parse_spelllang(wp);
break;
}
}
@@ -3647,7 +3709,7 @@ char *did_set_spell_option(bool is_spellfile)
/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
/// Return error message when failed, NULL when OK.
-char *compile_cap_prog(synblock_T *synblock)
+const char *compile_cap_prog(synblock_T *synblock)
FUNC_ATTR_NONNULL_ALL
{
regprog_T *rp = synblock->b_cap_prog;
diff --git a/src/nvim/spell.h b/src/nvim/spell.h
index f941f0e5e7..f3977fdaf2 100644
--- a/src/nvim/spell.h
+++ b/src/nvim/spell.h
@@ -1,14 +1,27 @@
-#ifndef NVIM_SPELL_H
-#define NVIM_SPELL_H
+#pragma once
#include <stdbool.h>
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/globals.h"
-#include "nvim/spell_defs.h"
-#include "nvim/vim.h"
+#include "nvim/spell_defs.h" // IWYU pragma: export
+#include "nvim/vim_defs.h"
+
+/// First language that is loaded, start of the linked list of loaded languages.
+extern slang_T *first_lang;
+
+/// file used for "zG" and "zW"
+extern char *int_wordlist;
+
+extern spelltab_T spelltab;
+extern int did_set_spelltab;
+
+extern char *e_format;
+
+// Remember what "z?" replaced.
+extern char *repl_from;
+extern char *repl_to;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spell.h.generated.h"
#endif
-#endif // NVIM_SPELL_H
diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h
index 726af7d698..dfa399750f 100644
--- a/src/nvim/spell_defs.h
+++ b/src/nvim/spell_defs.h
@@ -1,196 +1,201 @@
-#ifndef NVIM_SPELL_DEFS_H
-#define NVIM_SPELL_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
+#include "nvim/hashtab_defs.h"
#include "nvim/regexp_defs.h"
-#include "nvim/types.h"
-#define MAXWLEN 254 // Assume max. word len is this many bytes.
- // Some places assume a word length fits in a
- // byte, thus it can't be above 255.
+/// Assume max. word len is this many bytes.
+/// Some places assume a word length fits in a byte, thus it can't be above 255.
+enum { MAXWLEN = 254, };
-// Number of regions supported.
-#define MAXREGIONS 8
+/// Number of regions supported.
+enum { MAXREGIONS = 8, };
-// Type used for indexes in the word tree need to be at least 4 bytes. If int
-// is 8 bytes we could use something smaller, but what?
+/// Type used for indexes in the word tree need to be at least 4 bytes. If int
+/// is 8 bytes we could use something smaller, but what?
typedef int idx_T;
#define SPL_FNAME_TMPL "%s.%s.spl"
#define SPL_FNAME_ADD ".add."
#define SPL_FNAME_ASCII ".ascii."
-// Flags used for a word. Only the lowest byte can be used, the region byte
-// comes above it.
-#define WF_REGION 0x01 // region byte follows
-#define WF_ONECAP 0x02 // word with one capital (or all capitals)
-#define WF_ALLCAP 0x04 // word must be all capitals
-#define WF_RARE 0x08 // rare word
-#define WF_BANNED 0x10 // bad word
-#define WF_AFX 0x20 // affix ID follows
-#define WF_FIXCAP 0x40 // keep-case word, allcap not allowed
-#define WF_KEEPCAP 0x80 // keep-case word
-
-#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP)
-
-// for <flags2>, shifted up one byte to be used in wn_flags
-#define WF_HAS_AFF 0x0100 // word includes affix
-#define WF_NEEDCOMP 0x0200 // word only valid in compound
-#define WF_NOSUGGEST 0x0400 // word not to be suggested
-#define WF_COMPROOT 0x0800 // already compounded word, COMPOUNDROOT
-#define WF_NOCOMPBEF 0x1000 // no compounding before this word
-#define WF_NOCOMPAFT 0x2000 // no compounding after this word
-
-// flags for <pflags>
-#define WFP_RARE 0x01 // rare prefix
-#define WFP_NC 0x02 // prefix is not combining
-#define WFP_UP 0x04 // to-upper prefix
-#define WFP_COMPPERMIT 0x08 // prefix with COMPOUNDPERMITFLAG
-#define WFP_COMPFORBID 0x10 // prefix with COMPOUNDFORBIDFLAG
-
-// Flags for postponed prefixes in "sl_pidxs". Must be above affixID (one
-// byte) and prefcondnr (two bytes).
-#define WF_RAREPFX (WFP_RARE << 24) // rare postponed prefix
-#define WF_PFX_NC (WFP_NC << 24) // non-combining postponed prefix
-#define WF_PFX_UP (WFP_UP << 24) // to-upper postponed prefix
-#define WF_PFX_COMPPERMIT (WFP_COMPPERMIT << 24) // postponed prefix with
- // COMPOUNDPERMITFLAG
-#define WF_PFX_COMPFORBID (WFP_COMPFORBID << 24) // postponed prefix with
- // COMPOUNDFORBIDFLAG
-
-// flags for <compoptions>
-#define COMP_CHECKDUP 1 // CHECKCOMPOUNDDUP
-#define COMP_CHECKREP 2 // CHECKCOMPOUNDREP
-#define COMP_CHECKCASE 4 // CHECKCOMPOUNDCASE
-#define COMP_CHECKTRIPLE 8 // CHECKCOMPOUNDTRIPLE
-
-// Info from "REP", "REPSAL" and "SAL" entries in ".aff" file used in si_rep,
-// si_repsal, sl_rep, and si_sal. Not for sl_sal!
-// One replacement: from "ft_from" to "ft_to".
+/// Flags used for a word. Only the lowest byte can be used, the region byte
+/// comes above it.
+enum {
+ WF_REGION = 0x01, ///< region byte follows
+ WF_ONECAP = 0x02, ///< word with one capital (or all capitals)
+ WF_ALLCAP = 0x04, ///< word must be all capitals
+ WF_RARE = 0x08, ///< rare word
+ WF_BANNED = 0x10, ///< bad word
+ WF_AFX = 0x20, ///< affix ID follows
+ WF_FIXCAP = 0x40, ///< keep-case word, allcap not allowed
+ WF_KEEPCAP = 0x80, ///< keep-case word
+ WF_CAPMASK = (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP),
+};
+
+/// for <flags2>, shifted up one byte to be used in wn_flags
+enum {
+ WF_HAS_AFF = 0x0100, ///< word includes affix
+ WF_NEEDCOMP = 0x0200, ///< word only valid in compound
+ WF_NOSUGGEST = 0x0400, ///< word not to be suggested
+ WF_COMPROOT = 0x0800, ///< already compounded word, COMPOUNDROOT
+ WF_NOCOMPBEF = 0x1000, ///< no compounding before this word
+ WF_NOCOMPAFT = 0x2000, ///< no compounding after this word
+};
+
+/// flags for <pflags>
+enum {
+ WFP_RARE = 0x01, ///< rare prefix
+ WFP_NC = 0x02, ///< prefix is not combining
+ WFP_UP = 0x04, ///< to-upper prefix
+ WFP_COMPPERMIT = 0x08, ///< prefix with COMPOUNDPERMITFLAG
+ WFP_COMPFORBID = 0x10, ///< prefix with COMPOUNDFORBIDFLAG
+};
+
+/// Flags for postponed prefixes in "sl_pidxs". Must be above affixID (one
+/// byte) and prefcondnr (two bytes).
+enum {
+ WF_RAREPFX = WFP_RARE << 24, ///< rare postponed prefix
+ WF_PFX_NC = WFP_NC << 24, ///< non-combining postponed prefix
+ WF_PFX_UP = WFP_UP << 24, ///< to-upper postponed prefix
+ WF_PFX_COMPPERMIT = WFP_COMPPERMIT << 24, ///< postponed prefix with COMPOUNDPERMITFLAG
+ WF_PFX_COMPFORBID = WFP_COMPFORBID << 24, ///< postponed prefix with COMPOUNDFORBIDFLAG
+};
+
+/// flags for <compoptions>
+enum {
+ COMP_CHECKDUP = 1, ///< CHECKCOMPOUNDDUP
+ COMP_CHECKREP = 2, ///< CHECKCOMPOUNDREP
+ COMP_CHECKCASE = 4, ///< CHECKCOMPOUNDCASE
+ COMP_CHECKTRIPLE = 8, ///< CHECKCOMPOUNDTRIPLE
+};
+
+/// Info from "REP", "REPSAL" and "SAL" entries in ".aff" file used in si_rep,
+/// si_repsal, sl_rep, and si_sal. Not for sl_sal!
+/// One replacement: from "ft_from" to "ft_to".
typedef struct fromto_S {
char *ft_from;
char *ft_to;
} fromto_T;
-// Info from "SAL" entries in ".aff" file used in sl_sal.
-// The info is split for quick processing by spell_soundfold().
-// Note that "sm_oneof" and "sm_rules" point into sm_lead.
+/// Info from "SAL" entries in ".aff" file used in sl_sal.
+/// The info is split for quick processing by spell_soundfold().
+/// Note that "sm_oneof" and "sm_rules" point into sm_lead.
typedef struct salitem_S {
- char *sm_lead; // leading letters
- int sm_leadlen; // length of "sm_lead"
- char_u *sm_oneof; // letters from () or NULL
- char *sm_rules; // rules like ^, $, priority
- char *sm_to; // replacement.
- int *sm_lead_w; // wide character copy of "sm_lead"
- int *sm_oneof_w; // wide character copy of "sm_oneof"
- int *sm_to_w; // wide character copy of "sm_to"
+ char *sm_lead; ///< leading letters
+ int sm_leadlen; ///< length of "sm_lead"
+ char *sm_oneof; ///< letters from () or NULL
+ char *sm_rules; ///< rules like ^, $, priority
+ char *sm_to; ///< replacement.
+ int *sm_lead_w; ///< wide character copy of "sm_lead"
+ int *sm_oneof_w; ///< wide character copy of "sm_oneof"
+ int *sm_to_w; ///< wide character copy of "sm_to"
} salitem_T;
typedef int salfirst_T;
-// Values for SP_*ERROR are negative, positive values are used by
-// read_cnt_string().
-#define SP_TRUNCERROR (-1) // spell file truncated error
-#define SP_FORMERROR (-2) // format error in spell file
-#define SP_OTHERERROR (-3) // other error while reading spell file
-
-// Structure used to store words and other info for one language, loaded from
-// a .spl file.
-// The main access is through the tree in "sl_fbyts/sl_fidxs", storing the
-// case-folded words. "sl_kbyts/sl_kidxs" is for keep-case words.
-//
-// The "byts" array stores the possible bytes in each tree node, preceded by
-// the number of possible bytes, sorted on byte value:
-// <len> <byte1> <byte2> ...
-// The "idxs" array stores the index of the child node corresponding to the
-// byte in "byts".
-// Exception: when the byte is zero, the word may end here and "idxs" holds
-// the flags, region mask and affixID for the word. There may be several
-// zeros in sequence for alternative flag/region/affixID combinations.
+/// Values for SP_*ERROR are negative, positive values are used by
+/// read_cnt_string().
+enum {
+ SP_TRUNCERROR = -1, ///< spell file truncated error
+ SP_FORMERROR = -2, ///< format error in spell file
+ SP_OTHERERROR = -3, ///< other error while reading spell file
+};
+
+/// Structure used to store words and other info for one language, loaded from
+/// a .spl file.
+/// The main access is through the tree in "sl_fbyts/sl_fidxs", storing the
+/// case-folded words. "sl_kbyts/sl_kidxs" is for keep-case words.
+///
+/// The "byts" array stores the possible bytes in each tree node, preceded by
+/// the number of possible bytes, sorted on byte value:
+/// <len> <byte1> <byte2> ...
+/// The "idxs" array stores the index of the child node corresponding to the
+/// byte in "byts".
+/// Exception: when the byte is zero, the word may end here and "idxs" holds
+/// the flags, region mask and affixID for the word. There may be several
+/// zeros in sequence for alternative flag/region/affixID combinations.
typedef struct slang_S slang_T;
struct slang_S {
- slang_T *sl_next; // next language
- char *sl_name; // language name "en", "en.rare", "nl", etc.
- char *sl_fname; // name of .spl file
- bool sl_add; // true if it's a .add file.
-
- char *sl_fbyts; // case-folded word bytes
- long sl_fbyts_len; // length of sl_fbyts
- idx_T *sl_fidxs; // case-folded word indexes
- char *sl_kbyts; // keep-case word bytes
- idx_T *sl_kidxs; // keep-case word indexes
- char *sl_pbyts; // prefix tree word bytes
- idx_T *sl_pidxs; // prefix tree word indexes
-
- char_u *sl_info; // infotext string or NULL
-
+ slang_T *sl_next; ///< next language
+ char *sl_name; ///< language name "en", "en.rare", "nl", etc.
+ char *sl_fname; ///< name of .spl file
+ bool sl_add; ///< true if it's a .add file.
+
+ uint8_t *sl_fbyts; ///< case-folded word bytes
+ int sl_fbyts_len; ///< length of sl_fbyts
+ idx_T *sl_fidxs; ///< case-folded word indexes
+ uint8_t *sl_kbyts; ///< keep-case word bytes
+ idx_T *sl_kidxs; ///< keep-case word indexes
+ uint8_t *sl_pbyts; ///< prefix tree word bytes
+ idx_T *sl_pidxs; ///< prefix tree word indexes
+
+ char *sl_info; ///< infotext string or NULL
+
+ /// table with up to 8 region names plus NUL
char sl_regions[MAXREGIONS * 2 + 1];
- // table with up to 8 region names plus NUL
-
- char_u *sl_midword; // MIDWORD string or NULL
-
- hashtab_T sl_wordcount; // hashtable with word count, wordcount_T
-
- int sl_compmax; // COMPOUNDWORDMAX (default: MAXWLEN)
- int sl_compminlen; // COMPOUNDMIN (default: 0)
- int sl_compsylmax; // COMPOUNDSYLMAX (default: MAXWLEN)
- int sl_compoptions; // COMP_* flags
- garray_T sl_comppat; // CHECKCOMPOUNDPATTERN items
- regprog_T *sl_compprog; // COMPOUNDRULE turned into a regexp progrm
- // (NULL when no compounding)
- uint8_t *sl_comprules; // all COMPOUNDRULE concatenated (or NULL)
- uint8_t *sl_compstartflags; // flags for first compound word
- uint8_t *sl_compallflags; // all flags for compound words
- bool sl_nobreak; // When true: no spaces between words
- char_u *sl_syllable; // SYLLABLE repeatable chars or NULL
- garray_T sl_syl_items; // syllable items
-
- int sl_prefixcnt; // number of items in "sl_prefprog"
- regprog_T **sl_prefprog; // table with regprogs for prefixes
-
- garray_T sl_rep; // list of fromto_T entries from REP lines
- int16_t sl_rep_first[256]; // indexes where byte first appears, -1 if
- // there is none
- garray_T sl_sal; // list of salitem_T entries from SAL lines
- salfirst_T sl_sal_first[256]; // indexes where byte first appears, -1 if
- // there is none
- bool sl_followup; // SAL followup
- bool sl_collapse; // SAL collapse_result
- bool sl_rem_accents; // SAL remove_accents
- bool sl_sofo; // SOFOFROM and SOFOTO instead of SAL items:
- // "sl_sal_first" maps chars
- // "sl_sal" is a list of wide char lists.
- garray_T sl_repsal; // list of fromto_T entries from REPSAL lines
- int16_t sl_repsal_first[256]; // sl_rep_first for REPSAL lines
- bool sl_nosplitsugs; // don't suggest splitting a word
- bool sl_nocompoundsugs; // don't suggest compounding
+
+ char *sl_midword; ///< MIDWORD string or NULL
+
+ hashtab_T sl_wordcount; ///< hashtable with word count, wordcount_T
+
+ int sl_compmax; ///< COMPOUNDWORDMAX (default: MAXWLEN)
+ int sl_compminlen; ///< COMPOUNDMIN (default: 0)
+ int sl_compsylmax; ///< COMPOUNDSYLMAX (default: MAXWLEN)
+ int sl_compoptions; ///< COMP_* flags
+ garray_T sl_comppat; ///< CHECKCOMPOUNDPATTERN items
+ regprog_T *sl_compprog; ///< COMPOUNDRULE turned into a regexp progrm
+ ///< (NULL when no compounding)
+ uint8_t *sl_comprules; ///< all COMPOUNDRULE concatenated (or NULL)
+ uint8_t *sl_compstartflags; ///< flags for first compound word
+ uint8_t *sl_compallflags; ///< all flags for compound words
+ bool sl_nobreak; ///< When true: no spaces between words
+ char *sl_syllable; ///< SYLLABLE repeatable chars or NULL
+ garray_T sl_syl_items; ///< syllable items
+
+ int sl_prefixcnt; ///< number of items in "sl_prefprog"
+ regprog_T **sl_prefprog; ///< table with regprogs for prefixes
+
+ garray_T sl_rep; ///< list of fromto_T entries from REP lines
+ int16_t sl_rep_first[256]; ///< indexes where byte first appears, -1 if there is none
+ garray_T sl_sal; ///< list of salitem_T entries from SAL lines
+ salfirst_T sl_sal_first[256]; ///< indexes where byte first appears, -1 if there is none
+ bool sl_followup; ///< SAL followup
+ bool sl_collapse; ///< SAL collapse_result
+ bool sl_rem_accents; ///< SAL remove_accents
+ bool sl_sofo; ///< SOFOFROM and SOFOTO instead of SAL items:
+ ///< "sl_sal_first" maps chars
+ ///< "sl_sal" is a list of wide char lists.
+ garray_T sl_repsal; ///< list of fromto_T entries from REPSAL lines
+ int16_t sl_repsal_first[256]; ///< sl_rep_first for REPSAL lines
+ bool sl_nosplitsugs; ///< don't suggest splitting a word
+ bool sl_nocompoundsugs; ///< don't suggest compounding
// Info from the .sug file. Loaded on demand.
- time_t sl_sugtime; // timestamp for .sug file
- char *sl_sbyts; // soundfolded word bytes
- idx_T *sl_sidxs; // soundfolded word indexes
- buf_T *sl_sugbuf; // buffer with word number table
- bool sl_sugloaded; // true when .sug file was loaded or failed to
- // load
-
- bool sl_has_map; // true, if there is a MAP line
- hashtab_T sl_map_hash; // MAP for multi-byte chars
- int sl_map_array[256]; // MAP for first 256 chars
- hashtab_T sl_sounddone; // table with soundfolded words that have
- // handled, see add_sound_suggest()
+ time_t sl_sugtime; ///< timestamp for .sug file
+ uint8_t *sl_sbyts; ///< soundfolded word bytes
+ idx_T *sl_sidxs; ///< soundfolded word indexes
+ buf_T *sl_sugbuf; ///< buffer with word number table
+ bool sl_sugloaded; ///< true when .sug file was loaded or failed to load
+
+ bool sl_has_map; ///< true, if there is a MAP line
+ hashtab_T sl_map_hash; ///< MAP for multi-byte chars
+ int sl_map_array[256]; ///< MAP for first 256 chars
+ hashtab_T sl_sounddone; ///< table with soundfolded words that have
+ ///< handled, see add_sound_suggest()
};
-// Structure used in "b_langp", filled from 'spelllang'.
+/// Structure used in "b_langp", filled from 'spelllang'.
typedef struct langp_S {
- slang_T *lp_slang; // info for this language
- slang_T *lp_sallang; // language used for sound folding or NULL
- slang_T *lp_replang; // language used for REP items or NULL
- int lp_region; // bitmask for region or REGION_ALL
+ slang_T *lp_slang; ///< info for this language
+ slang_T *lp_sallang; ///< language used for sound folding or NULL
+ slang_T *lp_replang; ///< language used for REP items or NULL
+ int lp_region; ///< bitmask for region or REGION_ALL
} langp_T;
#define LANGP_ENTRY(ga, i) (((langp_T *)(ga).ga_data) + (i))
@@ -199,15 +204,15 @@ typedef struct langp_S {
#define VIMSUGMAGICL 6
#define VIMSUGVERSION 1
-#define REGION_ALL 0xff // word valid in all regions
+enum { REGION_ALL = 0xff, }; ///< word valid in all regions
-// The tables used for recognizing word characters according to spelling.
-// These are only used for the first 256 characters of 'encoding'.
+/// The tables used for recognizing word characters according to spelling.
+/// These are only used for the first 256 characters of 'encoding'.
typedef struct {
- bool st_isw[256]; // flags: is word char
- bool st_isu[256]; // flags: is uppercase char
- char_u st_fold[256]; // chars: folded case
- char_u st_upper[256]; // chars: upper case
+ bool st_isw[256]; ///< flags: is word char
+ bool st_isu[256]; ///< flags: is uppercase char
+ uint8_t st_fold[256]; ///< chars: folded case
+ uint8_t st_upper[256]; ///< chars: upper case
} spelltab_T;
// Use our own character-case definitions, because the current locale may
@@ -222,19 +227,7 @@ typedef struct {
#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c])
-// First language that is loaded, start of the linked list of loaded
-// languages.
-extern slang_T *first_lang;
-
-// file used for "zG" and "zW"
-extern char *int_wordlist;
-
-extern spelltab_T spelltab;
-extern int did_set_spelltab;
-
-extern char *e_format;
-
-// Values for "what" argument of spell_add_word()
+/// Values for "what" argument of spell_add_word()
typedef enum {
SPELL_ADD_GOOD = 0,
SPELL_ADD_BAD = 1,
@@ -242,16 +235,10 @@ typedef enum {
} SpellAddType;
typedef struct wordcount_S {
- uint16_t wc_count; ///< nr of times word was seen
- char_u wc_word[1]; ///< word, actually longer
+ uint16_t wc_count; ///< nr of times word was seen
+ char wc_word[]; ///< word
} wordcount_T;
#define WC_KEY_OFF offsetof(wordcount_T, wc_word)
#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF))
-#define MAXWORDCOUNT 0xffff
-
-// Remember what "z?" replaced.
-extern char *repl_from;
-extern char *repl_to;
-
-#endif // NVIM_SPELL_DEFS_H
+enum { MAXWORDCOUNT = 0xffff, };
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 44414ca1a5..2607fddc31 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// spellfile.c: code for reading and writing spell files.
//
// See spell.c for information about spell checking.
@@ -231,44 +228,46 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
-#include "auto/config.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/spell.h"
#include "nvim/spell_defs.h"
#include "nvim/spellfile.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Special byte values for <byte>. Some are only used in the tree for
// postponed prefixes, some only in the other trees. This is a bit messy...
@@ -322,11 +321,15 @@ enum {
CF_UPPER = 0x02,
};
-static char *e_spell_trunc = N_("E758: Truncated spell file");
-static char *e_illegal_character_in_word = N_("E1280: Illegal character in word");
-static char *e_afftrailing = N_("Trailing text in %s line %d: %s");
-static char *e_affname = N_("Affix name too long in %s line %d: %s");
-static char *msg_compressing = N_("Compressing word tree...");
+static const char *e_spell_trunc = N_("E758: Truncated spell file");
+static const char e_error_while_reading_sug_file_str[]
+ = N_("E782: Error while reading .sug file: %s");
+static const char e_duplicate_char_in_map_entry[]
+ = N_("E783: Duplicate char in MAP entry");
+static const char *e_illegal_character_in_word = N_("E1280: Illegal character in word");
+static const char *e_afftrailing = N_("Trailing text in %s line %d: %s");
+static const char *e_affname = N_("Affix name too long in %s line %d: %s");
+static const char *msg_compressing = N_("Compressing word tree...");
#define MAXLINELEN 500 // Maximum length in bytes of a line in a .aff
// and .dic file.
@@ -386,7 +389,7 @@ typedef struct affheader_S {
// Flag used in compound items.
typedef struct compitem_S {
- char_u ci_key[AH_KEY_LEN]; // key for hashtab == name of compound
+ char ci_key[AH_KEY_LEN]; // key for hashtab == name of compound
unsigned ci_flag; // affix name as number, uses "af_flagtype"
int ci_newID; // affix ID after renumbering.
} compitem_T;
@@ -404,14 +407,14 @@ typedef struct sblock_S sblock_T;
struct sblock_S {
int sb_used; // nr of bytes already in use
sblock_T *sb_next; // next block in list
- char_u sb_data[1]; // data, actually longer
+ char sb_data[]; // data
};
// A node in the tree.
typedef struct wordnode_S wordnode_T;
struct wordnode_S {
union { // shared to save space
- char_u hashkey[6]; // the hash key, only used while compressing
+ uint8_t hashkey[6]; // the hash key, only used while compressing
int index; // index in written nodes (valid after first
// round)
} wn_u1;
@@ -422,17 +425,17 @@ struct wordnode_S {
wordnode_T *wn_child; // child (next byte in word)
wordnode_T *wn_sibling; // next sibling (alternate byte in word,
// always sorted)
- int wn_refs; // Nr. of references to this node. Only
- // relevant for first node in a list of
- // siblings, in following siblings it is
- // always one.
- char_u wn_byte; // Byte for this node. NUL for word end
+ int wn_refs; // Nr. of references to this node. Only
+ // relevant for first node in a list of
+ // siblings, in following siblings it is
+ // always one.
+ uint8_t wn_byte; // Byte for this node. NUL for word end
// Info for when "wn_byte" is NUL.
// In PREFIXTREE "wn_region" is used for the prefcondnr.
// In the soundfolded word tree "wn_flags" has the MSW of the wordnr and
// "wn_region" the LSW of the wordnr.
- char_u wn_affixID; // supported/required prefix ID or 0
+ uint8_t wn_affixID; // supported/required prefix ID or 0
uint16_t wn_flags; // WF_ flags
int16_t wn_region; // region mask
@@ -448,24 +451,24 @@ struct wordnode_S {
// Info used while reading the spell files.
typedef struct spellinfo_S {
wordnode_T *si_foldroot; // tree with case-folded words
- long si_foldwcount; // nr of words in si_foldroot
+ int si_foldwcount; // nr of words in si_foldroot
wordnode_T *si_keeproot; // tree with keep-case words
- long si_keepwcount; // nr of words in si_keeproot
+ int si_keepwcount; // nr of words in si_keeproot
wordnode_T *si_prefroot; // tree with postponed prefixes
- long si_sugtree; // creating the soundfolding trie
+ int si_sugtree; // creating the soundfolding trie
sblock_T *si_blocks; // memory blocks used
- long si_blocks_cnt; // memory blocks allocated
+ int si_blocks_cnt; // memory blocks allocated
int si_did_emsg; // true when ran out of memory
- long si_compress_cnt; // words to add before lowering
- // compression limit
+ int si_compress_cnt; // words to add before lowering
+ // compression limit
wordnode_T *si_first_free; // List of nodes that have been freed during
// compression, linked by "wn_child" field.
- long si_free_count; // number of nodes in si_first_free
+ int si_free_count; // number of nodes in si_first_free
#ifdef SPELL_PRINTTREE
int si_wordnode_nr; // sequence nr for nodes
#endif
@@ -482,7 +485,7 @@ typedef struct spellinfo_S {
char *si_info; // info text chars or NULL
int si_region_count; // number of regions supported (1 when there
// are no regions)
- char_u si_region_name[MAXREGIONS * 2 + 1];
+ char si_region_name[MAXREGIONS * 2 + 1];
// region names; used only if
// si_region_count > 1)
@@ -508,7 +511,7 @@ typedef struct spellinfo_S {
garray_T si_comppat; // CHECKCOMPOUNDPATTERN items, each stored as
// a string
char *si_compflags; // flags used for compounding
- char_u si_nobreak; // NOBREAK
+ char si_nobreak; // NOBREAK
char *si_syllable; // syllable string
garray_T si_prefcond; // table with conditions for postponed
// prefixes, each stored as a string
@@ -593,29 +596,25 @@ static inline int spell_check_magic_string(FILE *const fd)
/// @return the slang_T the spell file was loaded into. NULL for error.
slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
{
- FILE *fd;
char *p;
- int n;
- int len;
slang_T *lp = NULL;
- int c = 0;
int res;
bool did_estack_push = false;
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
if (!silent) {
semsg(_(e_notopen), fname);
} else if (p_verbose > 2) {
verbose_enter();
- smsg((char *)e_notopen, fname);
+ smsg(0, e_notopen, fname);
verbose_leave();
}
goto endFAIL;
}
if (p_verbose > 2) {
verbose_enter();
- smsg(_("Reading spell file \"%s\""), fname);
+ smsg(0, _("Reading spell file \"%s\""), fname);
verbose_leave();
}
@@ -649,7 +648,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
case 0:
break;
}
- c = getc(fd); // <versionnr>
+ int c = getc(fd); // <versionnr>
if (c < VIMSPELLVERSION) {
emsg(_("E771: Old spell file, needs to be updated"));
goto endFAIL;
@@ -660,13 +659,13 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
// <SECTIONS>: <section> ... <sectionend>
// <section>: <sectionID> <sectionflags> <sectionlen> (section contents)
- for (;;) {
- n = getc(fd); // <sectionID> or <sectionend>
+ while (true) {
+ int n = getc(fd); // <sectionID> or <sectionend>
if (n == SN_END) {
break;
}
c = getc(fd); // <sectionflags>
- len = get4c(fd); // <sectionlen>
+ int len = get4c(fd); // <sectionlen>
if (len < 0) {
goto truncerr;
}
@@ -674,7 +673,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
res = 0;
switch (n) {
case SN_INFO:
- lp->sl_info = READ_STRING(fd, len); // <infotext>
+ lp->sl_info = read_string(fd, (size_t)len); // <infotext>
if (lp->sl_info == NULL) {
goto endFAIL;
}
@@ -689,7 +688,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
break;
case SN_MIDWORD:
- lp->sl_midword = READ_STRING(fd, len); // <midword>
+ lp->sl_midword = read_string(fd, (size_t)len); // <midword>
if (lp->sl_midword == NULL) {
goto endFAIL;
}
@@ -716,7 +715,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
break;
case SN_MAP:
- p = (char *)READ_STRING(fd, len); // <mapstr>
+ p = read_string(fd, (size_t)len); // <mapstr>
if (p == NULL) {
goto endFAIL;
}
@@ -749,7 +748,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
break;
case SN_SYLLABLE:
- lp->sl_syllable = READ_STRING(fd, len); // <syllable>
+ lp->sl_syllable = read_string(fd, (size_t)len); // <syllable>
if (lp->sl_syllable == NULL) {
goto endFAIL;
}
@@ -838,19 +837,16 @@ endOK:
// Fill in the wordcount fields for a trie.
// Returns the total number of words.
-static void tree_count_words(const char_u *byts, idx_T *idxs)
+static void tree_count_words(const uint8_t *byts, idx_T *idxs)
{
- int depth;
idx_T arridx[MAXWLEN];
int curi[MAXWLEN];
- int c;
- idx_T n;
int wordcount[MAXWLEN];
arridx[0] = 0;
curi[0] = 1;
wordcount[0] = 0;
- depth = 0;
+ int depth = 0;
while (depth >= 0 && !got_int) {
if (curi[depth] > byts[arridx[depth]]) {
// Done all bytes at this node, go up one level.
@@ -863,10 +859,10 @@ static void tree_count_words(const char_u *byts, idx_T *idxs)
fast_breakcheck();
} else {
// Do one more byte at this node.
- n = arridx[depth] + curi[depth];
+ idx_T n = arridx[depth] + curi[depth];
curi[depth]++;
- c = byts[n];
+ int c = byts[n];
if (c == 0) {
// End of word, count it.
wordcount[depth]++;
@@ -891,40 +887,31 @@ static void tree_count_words(const char_u *byts, idx_T *idxs)
/// Load the .sug files for languages that have one and weren't loaded yet.
void suggest_load_files(void)
{
- langp_T *lp;
- slang_T *slang;
- char *dotp;
- FILE *fd;
char buf[MAXWLEN];
- int i;
- time_t timestamp;
- int wcount;
- int wordnr;
garray_T ga;
- int c;
// Do this for all languages that support sound folding.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (slang->sl_sugtime != 0 && !slang->sl_sugloaded) {
// Change ".spl" to ".sug" and open the file. When the file isn't
// found silently skip it. Do set "sl_sugloaded" so that we
// don't try again and again.
slang->sl_sugloaded = true;
- dotp = strrchr(slang->sl_fname, '.');
+ char *dotp = strrchr(slang->sl_fname, '.');
if (dotp == NULL || path_fnamecmp(dotp, ".spl") != 0) {
continue;
}
STRCPY(dotp, ".sug");
- fd = os_fopen(slang->sl_fname, "r");
+ FILE *fd = os_fopen(slang->sl_fname, "r");
if (fd == NULL) {
goto nextone;
}
// <SUGHEADER>: <fileID> <versionnr> <timestamp>
- for (i = 0; i < VIMSUGMAGICL; i++) {
+ for (int i = 0; i < VIMSUGMAGICL; i++) {
buf[i] = (char)getc(fd); // <fileID>
}
if (strncmp(buf, VIMSUGMAGIC, VIMSUGMAGICL) != 0) {
@@ -932,7 +919,7 @@ void suggest_load_files(void)
slang->sl_fname);
goto nextone;
}
- c = getc(fd); // <versionnr>
+ int c = getc(fd); // <versionnr>
if (c < VIMSUGVERSION) {
semsg(_("E779: Old .sug file, needs to be updated: %s"),
slang->sl_fname);
@@ -945,7 +932,7 @@ void suggest_load_files(void)
// Check the timestamp, it must be exactly the same as the one in
// the .spl file. Otherwise the word numbers won't match.
- timestamp = get8ctime(fd); // <timestamp>
+ time_t timestamp = get8ctime(fd); // <timestamp>
if (timestamp != slang->sl_sugtime) {
semsg(_("E781: .sug file doesn't match .spl file: %s"),
slang->sl_fname);
@@ -957,7 +944,7 @@ void suggest_load_files(void)
if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs,
false, 0) != 0) {
someerror:
- semsg(_("E782: error while reading .sug file: %s"),
+ semsg(_(e_error_while_reading_sug_file_str),
slang->sl_fname);
slang_clear_sug(slang);
goto nextone;
@@ -971,7 +958,7 @@ someerror:
slang->sl_sugbuf = open_spellbuf();
// <sugwcount>
- wcount = get4c(fd);
+ int wcount = get4c(fd);
if (wcount < 0) {
goto someerror;
}
@@ -979,14 +966,14 @@ someerror:
// Read all the wordnr lists into the buffer, one NUL terminated
// list per line.
ga_init(&ga, 1, 100);
- for (wordnr = 0; wordnr < wcount; wordnr++) {
+ for (int wordnr = 0; wordnr < wcount; wordnr++) {
ga.ga_len = 0;
- for (;;) {
+ while (true) {
c = getc(fd); // <sugline>
if (c < 0) {
goto someerror;
}
- GA_APPEND(char_u, &ga, (char_u)c);
+ GA_APPEND(uint8_t, &ga, (uint8_t)c);
if (c == NUL) {
break;
}
@@ -1000,8 +987,8 @@ someerror:
// Need to put word counts in the word tries, so that we can find
// a word by its number.
- tree_count_words((char_u *)slang->sl_fbyts, slang->sl_fidxs);
- tree_count_words((char_u *)slang->sl_sbyts, slang->sl_sidxs);
+ tree_count_words(slang->sl_fbyts, slang->sl_fidxs);
+ tree_count_words(slang->sl_sbyts, slang->sl_sidxs);
nextone:
if (fd != NULL) {
@@ -1017,10 +1004,9 @@ nextone:
// Returns NULL when the count is zero.
// Sets "*cntp" to SP_*ERROR when there is an error, length of the result
// otherwise.
-static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
+static char *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
{
int cnt = 0;
- char_u *str;
// read the length bytes, MSB first
for (int i = 0; i < cnt_bytes; i++) {
@@ -1036,7 +1022,7 @@ static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
if (cnt == 0) {
return NULL; // nothing to read, return NULL
}
- str = READ_STRING(fd, cnt);
+ char *str = read_string(fd, (size_t)cnt);
if (str == NULL) {
*cntp = SP_OTHERERROR;
}
@@ -1060,18 +1046,16 @@ static int read_region_section(FILE *fd, slang_T *lp, int len)
// Return SP_*ERROR flags.
static int read_charflags_section(FILE *fd)
{
- char *flags;
- char_u *fol;
int flagslen, follen;
// <charflagslen> <charflags>
- flags = (char *)read_cnt_string(fd, 1, &flagslen);
+ char *flags = read_cnt_string(fd, 1, &flagslen);
if (flagslen < 0) {
return flagslen;
}
// <folcharslen> <folchars>
- fol = read_cnt_string(fd, 2, &follen);
+ char *fol = read_cnt_string(fd, 2, &follen);
if (follen < 0) {
xfree(flags);
return follen;
@@ -1079,7 +1063,7 @@ static int read_charflags_section(FILE *fd)
// Set the word-char flags and fill SPELL_ISUPPER() table.
if (flags != NULL && fol != NULL) {
- set_spell_charflags((char_u *)flags, flagslen, (char *)fol);
+ set_spell_charflags(flags, flagslen, fol);
}
xfree(flags);
@@ -1129,10 +1113,9 @@ static int read_prefcond_section(FILE *fd, slang_T *lp)
// Return SP_*ERROR flags.
static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first)
{
- int cnt;
fromto_T *ftp;
- cnt = get2c(fd); // <repcount>
+ int cnt = get2c(fd); // <repcount>
if (cnt < 0) {
return SP_TRUNCERROR;
}
@@ -1143,14 +1126,14 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first)
for (; gap->ga_len < cnt; gap->ga_len++) {
int c;
ftp = &((fromto_T *)gap->ga_data)[gap->ga_len];
- ftp->ft_from = (char *)read_cnt_string(fd, 1, &c);
+ ftp->ft_from = read_cnt_string(fd, 1, &c);
if (c < 0) {
return c;
}
if (c == 0) {
return SP_FORMERROR;
}
- ftp->ft_to = (char *)read_cnt_string(fd, 1, &c);
+ ftp->ft_to = read_cnt_string(fd, 1, &c);
if (c <= 0) {
xfree(ftp->ft_from);
if (c < 0) {
@@ -1177,12 +1160,6 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first)
// Return SP_*ERROR flags.
static int read_sal_section(FILE *fd, slang_T *slang)
{
- int cnt;
- garray_T *gap;
- salitem_T *smp;
- int ccnt;
- char_u *p;
-
slang->sl_sofo = false;
const int flags = getc(fd); // <salflags>
@@ -1196,12 +1173,12 @@ static int read_sal_section(FILE *fd, slang_T *slang)
slang->sl_rem_accents = true;
}
- cnt = get2c(fd); // <salcount>
+ int cnt = get2c(fd); // <salcount>
if (cnt < 0) {
return SP_TRUNCERROR;
}
- gap = &slang->sl_sal;
+ garray_T *gap = &slang->sl_sal;
ga_init(gap, sizeof(salitem_T), 10);
ga_grow(gap, cnt + 1);
@@ -1209,13 +1186,13 @@ static int read_sal_section(FILE *fd, slang_T *slang)
for (; gap->ga_len < cnt; gap->ga_len++) {
int c = NUL;
- smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
- ccnt = getc(fd); // <salfromlen>
+ salitem_T *smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
+ int ccnt = getc(fd); // <salfromlen>
if (ccnt < 0) {
return SP_TRUNCERROR;
}
- p = xmalloc((size_t)ccnt + 2);
- smp->sm_lead = (char *)p;
+ char *p = xmalloc((size_t)ccnt + 2);
+ smp->sm_lead = p;
// Read up to the first special char into sm_lead.
int i = 0;
@@ -1224,9 +1201,9 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (vim_strchr("0123456789(-<^$", c) != NULL) {
break;
}
- *p++ = (char_u)c;
+ *p++ = (char)(uint8_t)c;
}
- smp->sm_leadlen = (int)(p - (char_u *)smp->sm_lead);
+ smp->sm_leadlen = (int)(p - smp->sm_lead);
*p++ = NUL;
// Put (abc) chars in sm_oneof, if any.
@@ -1237,7 +1214,7 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (c == ')') {
break;
}
- *p++ = (char_u)c;
+ *p++ = (char)(uint8_t)c;
}
*p++ = NUL;
if (++i < ccnt) {
@@ -1248,22 +1225,22 @@ static int read_sal_section(FILE *fd, slang_T *slang)
}
// Any following chars go in sm_rules.
- smp->sm_rules = (char *)p;
+ smp->sm_rules = p;
if (i < ccnt) {
// store the char we got while checking for end of sm_lead
- *p++ = (char_u)c;
+ *p++ = (char)(uint8_t)c;
}
i++;
if (i < ccnt) {
SPELL_READ_NONNUL_BYTES( // <salfrom>
- (char *)p, (size_t)(ccnt - i), fd,
+ p, (size_t)(ccnt - i), fd,
xfree(smp->sm_lead));
p += (ccnt - i);
}
*p++ = NUL;
// <saltolen> <salto>
- smp->sm_to = (char *)read_cnt_string(fd, 1, &ccnt);
+ smp->sm_to = read_cnt_string(fd, 1, &ccnt);
if (ccnt < 0) {
xfree(smp->sm_lead);
return ccnt;
@@ -1275,7 +1252,7 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (smp->sm_oneof == NULL) {
smp->sm_oneof_w = NULL;
} else {
- smp->sm_oneof_w = mb_str2wide((char *)smp->sm_oneof);
+ smp->sm_oneof_w = mb_str2wide(smp->sm_oneof);
}
if (smp->sm_to == NULL) {
smp->sm_to_w = NULL;
@@ -1287,15 +1264,15 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (!GA_EMPTY(gap)) {
// Add one extra entry to mark the end with an empty sm_lead. Avoids
// that we need to check the index every time.
- smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
- p = xmalloc(1);
+ salitem_T *smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
+ char *p = xmalloc(1);
p[0] = NUL;
- smp->sm_lead = (char *)p;
+ smp->sm_lead = p;
smp->sm_lead_w = mb_str2wide(smp->sm_lead);
smp->sm_leadlen = 0;
smp->sm_oneof = NULL;
smp->sm_oneof_w = NULL;
- smp->sm_rules = (char *)p;
+ smp->sm_rules = p;
smp->sm_to = NULL;
smp->sm_to_w = NULL;
gap->ga_len++;
@@ -1313,17 +1290,16 @@ static int read_words_section(FILE *fd, slang_T *lp, int len)
{
int done = 0;
int i;
- int c;
- char_u word[MAXWLEN];
+ uint8_t word[MAXWLEN];
while (done < len) {
// Read one word at a time.
for (i = 0;; i++) {
- c = getc(fd);
+ int c = getc(fd);
if (c == EOF) {
return SP_TRUNCERROR;
}
- word[i] = (char_u)c;
+ word[i] = (uint8_t)c;
if (word[i] == NUL) {
break;
}
@@ -1344,19 +1320,18 @@ static int read_words_section(FILE *fd, slang_T *lp, int len)
static int read_sofo_section(FILE *fd, slang_T *slang)
{
int cnt;
- char *from, *to;
int res;
slang->sl_sofo = true;
// <sofofromlen> <sofofrom>
- from = (char *)read_cnt_string(fd, 2, &cnt);
+ char *from = read_cnt_string(fd, 2, &cnt);
if (cnt < 0) {
return cnt;
}
// <sofotolen> <sofoto>
- to = (char *)read_cnt_string(fd, 2, &cnt);
+ char *to = read_cnt_string(fd, 2, &cnt);
if (cnt < 0) {
xfree(from);
return cnt;
@@ -1382,16 +1357,13 @@ static int read_sofo_section(FILE *fd, slang_T *slang)
static int read_compound(FILE *fd, slang_T *slang, int len)
{
int todo = len;
- int c;
- int atstart;
int cnt;
- garray_T *gap;
if (todo < 2) {
return SP_FORMERROR; // need at least two bytes
}
todo--;
- c = getc(fd); // <compmax>
+ int c = getc(fd); // <compmax>
if (c < 2) {
c = MAXWLEN;
}
@@ -1420,7 +1392,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
todo--;
slang->sl_compoptions = c;
- gap = &slang->sl_comppat;
+ garray_T *gap = &slang->sl_comppat;
c = get2c(fd); // <comppatcount>
if (c < 0) {
return SP_TRUNCERROR;
@@ -1429,7 +1401,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
ga_init(gap, sizeof(char *), c);
ga_grow(gap, c);
while (--c >= 0) {
- ((char **)(gap->ga_data))[gap->ga_len++] = (char *)read_cnt_string(fd, 1, &cnt);
+ ((char **)(gap->ga_data))[gap->ga_len++] = read_cnt_string(fd, 1, &cnt);
// <comppatlen> <comppattext>
if (cnt < 0) {
return cnt;
@@ -1465,12 +1437,12 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
uint8_t *crp = xmalloc((size_t)todo + 1);
slang->sl_comprules = crp;
- char_u *pp = (char_u *)pat;
+ char *pp = pat;
*pp++ = '^';
*pp++ = '\\';
*pp++ = '(';
- atstart = 1;
+ int atstart = 1;
while (todo-- > 0) {
c = getc(fd); // <compflags>
if (c == EOF) {
@@ -1481,7 +1453,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
// Add all flags to "sl_compallflags".
if (vim_strchr("?*+[]/", c) == NULL
&& !byte_in_str(slang->sl_compallflags, c)) {
- *ap++ = (char_u)c;
+ *ap++ = (uint8_t)c;
*ap = NUL;
}
@@ -1494,7 +1466,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
atstart = 0;
} else {
if (!byte_in_str(slang->sl_compstartflags, c)) {
- *cp++ = (char_u)c;
+ *cp++ = (uint8_t)c;
*cp = NUL;
}
if (atstart == 1) {
@@ -1509,7 +1481,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
XFREE_CLEAR(slang->sl_comprules);
crp = NULL;
} else {
- *crp++ = (char_u)c;
+ *crp++ = (uint8_t)c;
}
}
@@ -1521,7 +1493,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
if (c == '?' || c == '+' || c == '~') {
*pp++ = '\\'; // "a?" becomes "a\?", "a+" becomes "a\+"
}
- pp += utf_char2bytes(c, (char *)pp);
+ pp += utf_char2bytes(c, pp);
}
}
@@ -1545,10 +1517,10 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
// Set the SOFOFROM and SOFOTO items in language "lp".
// Returns SP_*ERROR flags when there is something wrong.
-static int set_sofo(slang_T *lp, char *from, char *to)
+static int set_sofo(slang_T *lp, const char *from, const char *to)
{
- char *s;
- char *p;
+ const char *s;
+ const char *p;
// Use "sl_sal" as an array with 256 pointers to a list of wide
// characters. The index is the low byte of the character.
@@ -1563,7 +1535,7 @@ static int set_sofo(slang_T *lp, char *from, char *to)
// First count the number of items for each list. Temporarily use
// sl_sal_first[] for this.
for (p = from, s = to; *p != NUL && *s != NUL;) {
- const int c = mb_cptr2char_adv((const char **)&p);
+ const int c = mb_cptr2char_adv(&p);
MB_CPTR_ADV(s);
if (c >= 256) {
lp->sl_sal_first[c & 0xff]++;
@@ -1586,8 +1558,8 @@ static int set_sofo(slang_T *lp, char *from, char *to)
// list.
memset(lp->sl_sal_first, 0, sizeof(salfirst_T) * 256);
for (p = from, s = to; *p != NUL && *s != NUL;) {
- const int c = mb_cptr2char_adv((const char **)&p);
- const int i = mb_cptr2char_adv((const char **)&s);
+ const int c = mb_cptr2char_adv(&p);
+ const int i = mb_cptr2char_adv(&s);
if (c >= 256) {
// Append the from-to chars at the end of the list with
// the low byte.
@@ -1610,21 +1582,18 @@ static int set_sofo(slang_T *lp, char *from, char *to)
// Fill the first-index table for "lp".
static void set_sal_first(slang_T *lp)
{
- salfirst_T *sfirst;
- salitem_T *smp;
- int c;
garray_T *gap = &lp->sl_sal;
- sfirst = lp->sl_sal_first;
+ salfirst_T *sfirst = lp->sl_sal_first;
for (int i = 0; i < 256; i++) {
sfirst[i] = -1;
}
- smp = (salitem_T *)gap->ga_data;
+ salitem_T *smp = (salitem_T *)gap->ga_data;
for (int i = 0; i < gap->ga_len; i++) {
// Use the lowest byte of the first character. For latin1 it's
// the character, for other encodings it should differ for most
// characters.
- c = *smp[i].sm_lead_w & 0xff;
+ int c = *smp[i].sm_lead_w & 0xff;
if (sfirst[c] == -1) {
sfirst[c] = i;
@@ -1656,13 +1625,13 @@ static void set_sal_first(slang_T *lp)
// Turn a multi-byte string into a wide character string.
// Return it in allocated memory.
-static int *mb_str2wide(char *s)
+static int *mb_str2wide(const char *s)
{
int i = 0;
int *res = xmalloc(((size_t)mb_charlen(s) + 1) * sizeof(int));
- for (char *p = s; *p != NUL;) {
- res[i++] = mb_ptr2char_adv((const char **)&p);
+ for (const char *p = s; *p != NUL;) {
+ res[i++] = mb_ptr2char_adv(&p);
}
res[i] = NUL;
@@ -1677,21 +1646,17 @@ static int *mb_str2wide(char *s)
/// @param prefixcnt when "prefixtree" is true: prefix count
///
/// @return zero when OK, SP_ value for an error.
-static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxsp, bool prefixtree,
- int prefixcnt)
+static int spell_read_tree(FILE *fd, uint8_t **bytsp, int *bytsp_len, idx_T **idxsp,
+ bool prefixtree, int prefixcnt)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
- int idx;
- char *bp;
- idx_T *ip;
-
// The tree size was computed when writing the file, so that we can
// allocate it as one long block. <nodecount>
- long len = get4c(fd);
+ int len = get4c(fd);
if (len < 0) {
return SP_TRUNCERROR;
}
- if ((size_t)len >= SIZE_MAX / sizeof(int)) { // -V547
+ if ((size_t)len >= SIZE_MAX / sizeof(int)) {
// Invalid length, multiply with sizeof(int) would overflow.
return SP_FORMERROR;
}
@@ -1700,18 +1665,18 @@ static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxs
}
// Allocate the byte array.
- bp = xmalloc((size_t)len);
+ uint8_t *bp = xmalloc((size_t)len);
*bytsp = bp;
if (bytsp_len != NULL) {
*bytsp_len = len;
}
// Allocate the index array.
- ip = xcalloc((size_t)len, sizeof(*ip));
+ idx_T *ip = xcalloc((size_t)len, sizeof(*ip));
*idxsp = ip;
// Recursively read the tree and store it in the array.
- idx = read_tree_node(fd, (char_u *)bp, ip, (int)len, 0, prefixtree, prefixcnt);
+ int idx = read_tree_node(fd, bp, ip, len, 0, prefixtree, prefixcnt);
if (idx < 0) {
return idx;
}
@@ -1731,18 +1696,13 @@ static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxs
/// @param startidx current index in "byts" and "idxs"
/// @param prefixtree true for reading PREFIXTREE
/// @param maxprefcondnr maximum for <prefcondnr>
-static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx,
+static idx_T read_tree_node(FILE *fd, uint8_t *byts, idx_T *idxs, int maxidx, idx_T startidx,
bool prefixtree, int maxprefcondnr)
{
- int len;
- int i;
- int n;
idx_T idx = startidx;
- int c;
- int c2;
#define SHARED_MASK 0x8000000
- len = getc(fd); // <siblingcount>
+ int len = getc(fd); // <siblingcount>
if (len <= 0) {
return SP_TRUNCERROR;
}
@@ -1750,11 +1710,11 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
if (startidx + len >= maxidx) {
return SP_FORMERROR;
}
- byts[idx++] = (char_u)len;
+ byts[idx++] = (uint8_t)len;
// Read the byte values, flag/region bytes and shared indexes.
- for (i = 1; i <= len; i++) {
- c = getc(fd); // <byte>
+ for (int i = 1; i <= len; i++) {
+ int c = getc(fd); // <byte>
if (c < 0) {
return SP_TRUNCERROR;
}
@@ -1776,7 +1736,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
c |= getc(fd); // <affixID>
- n = get2c(fd); // <prefcondnr>
+ int n = get2c(fd); // <prefcondnr>
if (n >= maxprefcondnr) {
return SP_FORMERROR;
}
@@ -1785,7 +1745,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
// Read flags and optional region and prefix ID. In
// idxs[] the flags go in the low two bytes, region above
// that and prefix ID above the region.
- c2 = c;
+ int c2 = c;
c = getc(fd); // <flags>
if (c2 == BY_FLAGS2) {
c = (getc(fd) << 8) + c; // <flags2>
@@ -1802,7 +1762,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
c = 0;
} else { // c == BY_INDEX
// <nodeidx>
- n = get3c(fd);
+ int n = get3c(fd);
if (n < 0 || n >= maxidx) {
return SP_FORMERROR;
}
@@ -1810,20 +1770,19 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
c = getc(fd); // <xbyte>
}
}
- byts[idx++] = (char_u)c;
+ byts[idx++] = (uint8_t)c;
}
// Recursively read the children for non-shared siblings.
// Skip the end-of-word ones (zero byte value) and the shared ones (and
// remove SHARED_MASK)
- for (i = 1; i <= len; i++) {
+ for (int i = 1; i <= len; i++) {
if (byts[startidx + i] != 0) {
if (idxs[startidx + i] & SHARED_MASK) {
idxs[startidx + i] &= ~SHARED_MASK;
} else {
idxs[startidx + i] = idx;
- idx = read_tree_node(fd, byts, idxs, maxidx, idx,
- prefixtree, maxprefcondnr);
+ idx = read_tree_node(fd, byts, idxs, maxidx, idx, prefixtree, maxprefcondnr);
if (idx < 0) {
break;
}
@@ -1839,10 +1798,9 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
/// @param added_word invoked through "zg"
static void spell_reload_one(char *fname, bool added_word)
{
- slang_T *slang;
bool didit = false;
- for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
+ for (slang_T *slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) {
slang_clear(slang);
if (spell_load_file(fname, NULL, slang, false) == NULL) {
@@ -1857,7 +1815,7 @@ static void spell_reload_one(char *fname, bool added_word)
// When "zg" was used and the file wasn't loaded yet, should redo
// 'spelllang' to load it now.
if (added_word && !didit) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
}
}
@@ -1876,24 +1834,21 @@ static void spell_reload_one(char *fname, bool added_word)
// Tunable parameters for when the tree is compressed. Filled from the
// 'mkspellmem' option.
-static long compress_start = 30000; // memory / SBLOCKSIZE
-static long compress_inc = 100; // memory / SBLOCKSIZE
-static long compress_added = 500000; // word count
+static int compress_start = 30000; // memory / SBLOCKSIZE
+static int compress_inc = 100; // memory / SBLOCKSIZE
+static int compress_added = 500000; // word count
// Check the 'mkspellmem' option. Return FAIL if it's wrong.
// Sets "sps_flags".
int spell_check_msm(void)
{
char *p = p_msm;
- long start = 0;
- long incr = 0;
- long added = 0;
if (!ascii_isdigit(*p)) {
return FAIL;
}
// block count = (value * 1024) / SBLOCKSIZE (but avoid overflow)
- start = (getdigits_long(&p, true, 0) * 10) / (SBLOCKSIZE / 102);
+ int start = (getdigits_int(&p, true, 0) * 10) / (SBLOCKSIZE / 102);
if (*p != ',') {
return FAIL;
}
@@ -1901,7 +1856,7 @@ int spell_check_msm(void)
if (!ascii_isdigit(*p)) {
return FAIL;
}
- incr = (getdigits_long(&p, true, 0) * 102) / (SBLOCKSIZE / 10);
+ int incr = (getdigits_int(&p, true, 0) * 102) / (SBLOCKSIZE / 10);
if (*p != ',') {
return FAIL;
}
@@ -1909,7 +1864,7 @@ int spell_check_msm(void)
if (!ascii_isdigit(*p)) {
return FAIL;
}
- added = getdigits_long(&p, true, 0) * 1024;
+ int added = getdigits_int(&p, true, 0) * 1024;
if (*p != NUL) {
return FAIL;
}
@@ -1957,9 +1912,9 @@ static void spell_print_node(wordnode_T *node, int depth)
PRINTSOME(line1, depth, "(%d)", node->wn_nr, 0);
PRINTSOME(line2, depth, " ", 0, 0);
PRINTSOME(line3, depth, " ", 0, 0);
- msg((char_u *)line1);
- msg((char_u *)line2);
- msg((char_u *)line3);
+ msg(line1, 0);
+ msg(line2, 0);
+ msg(line3, 0);
} else {
node->wn_u1.index = true;
@@ -1983,9 +1938,9 @@ static void spell_print_node(wordnode_T *node, int depth)
}
if (node->wn_byte == NUL) {
- msg((char_u *)line1);
- msg((char_u *)line2);
- msg((char_u *)line3);
+ msg(line1, 0);
+ msg(line2, 0);
+ msg(line3, 0);
}
// do the children
@@ -2022,13 +1977,11 @@ static void spell_print_tree(wordnode_T *root)
// Returns an afffile_T, NULL for complete failure.
static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
{
- FILE *fd;
char rline[MAXLINELEN];
char *line;
char *pc = NULL;
#define MAXITEMCNT 30
char *(items[MAXITEMCNT]);
- int itemcnt;
char *p;
int lnum = 0;
affheader_T *cur_aff = NULL;
@@ -2038,13 +1991,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
char *low = NULL;
char *fol = NULL;
char *upp = NULL;
- int do_rep;
- int do_repsal;
- int do_sal;
- int do_mapline;
bool found_map = false;
hashitem_T *hi;
- int l;
int compminlen = 0; // COMPOUNDMIN value
int compsylmax = 0; // COMPOUNDSYLMAX value
int compoptions = 0; // COMP_ flags
@@ -2057,7 +2005,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
char *sofoto = NULL; // SOFOTO value
// Open the file.
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return NULL;
@@ -2067,16 +2015,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
spell_message(spin, IObuff);
// Only do REP lines when not done in another .aff file already.
- do_rep = GA_EMPTY(&spin->si_rep);
+ int do_rep = GA_EMPTY(&spin->si_rep);
// Only do REPSAL lines when not done in another .aff file already.
- do_repsal = GA_EMPTY(&spin->si_repsal);
+ int do_repsal = GA_EMPTY(&spin->si_repsal);
// Only do SAL lines when not done in another .aff file already.
- do_sal = GA_EMPTY(&spin->si_sal);
+ int do_sal = GA_EMPTY(&spin->si_sal);
// Only do MAP lines when not done in another .aff file already.
- do_mapline = GA_EMPTY(&spin->si_map);
+ int do_mapline = GA_EMPTY(&spin->si_map);
// Allocate and init the afffile_T structure.
afffile_T *aff = getroom(spin, sizeof(*aff), true);
@@ -2099,7 +2047,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (spin->si_conv.vc_type != CONV_NONE) {
pc = string_convert(&spin->si_conv, rline, NULL);
if (pc == NULL) {
- smsg(_("Conversion failure for word in %s line %d: %s"),
+ smsg(0, _("Conversion failure for word in %s line %d: %s"),
fname, lnum, rline);
continue;
}
@@ -2111,7 +2059,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// Split the line up in white separated items. Put a NUL after each
// item.
- itemcnt = 0;
+ int itemcnt = 0;
for (p = line;;) {
while (*p != NUL && (uint8_t)(*p) <= ' ') { // skip white space and CR/NL
p++;
@@ -2143,10 +2091,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (itemcnt > 0) {
if (is_aff_rule(items, itemcnt, "SET", 2) && aff->af_enc == NULL) {
// Setup for conversion from "ENC" to 'encoding'.
- aff->af_enc = enc_canonize((char *)items[1]);
+ aff->af_enc = enc_canonize(items[1]);
if (!spin->si_ascii
&& convert_setup(&spin->si_conv, aff->af_enc, p_enc) == FAIL) {
- smsg(_("Conversion in %s not supported: from %s to %s"),
+ smsg(0, _("Conversion in %s not supported: from %s to %s"),
fname, aff->af_enc, p_enc);
}
spin->si_conv.vc_fail = true;
@@ -2159,7 +2107,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
} else if (strcmp(items[1], "caplong") == 0) {
aff->af_flagtype = AFT_CAPLONG;
} else {
- smsg(_("Invalid value for FLAG in %s line %d: %s"),
+ smsg(0, _("Invalid value for FLAG in %s line %d: %s"),
fname, lnum, items[1]);
}
if (aff->af_rare != 0
@@ -2173,7 +2121,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
|| compflags != NULL
|| aff->af_suff.ht_used > 0
|| aff->af_pref.ht_used > 0) {
- smsg(_("FLAG after using flags in %s line %d: %s"),
+ smsg(0, _("FLAG after using flags in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (spell_info_item(items[0]) && itemcnt > 1) {
@@ -2225,14 +2173,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
&& aff->af_compforbid == 0) {
aff->af_compforbid = affitem2flag(aff->af_flagtype, items[1], fname, lnum);
if (aff->af_pref.ht_used > 0) {
- smsg(_("Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line %d"),
+ smsg(0,
+ _("Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line %d"),
fname, lnum);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDPERMITFLAG", 2)
&& aff->af_comppermit == 0) {
aff->af_comppermit = affitem2flag(aff->af_flagtype, items[1], fname, lnum);
if (aff->af_pref.ht_used > 0) {
- smsg(_("Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line %d"),
+ smsg(0,
+ _("Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line %d"),
fname, lnum);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDFLAG", 2)
@@ -2246,16 +2196,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
} else if (is_aff_rule(items, itemcnt, "COMPOUNDRULES", 2)) {
// We don't use the count, but do check that it's a number and
// not COMPOUNDRULE mistyped.
- if (atoi((char *)items[1]) == 0) {
- smsg(_("Wrong COMPOUNDRULES value in %s line %d: %s"),
+ if (atoi(items[1]) == 0) {
+ smsg(0, _("Wrong COMPOUNDRULES value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDRULE", 2)) {
// Don't use the first rule if it is a number.
- if (compflags != NULL || *skipdigits((char *)items[1]) != NUL) {
+ if (compflags != NULL || *skipdigits(items[1]) != NUL) {
// Concatenate this string to previously defined ones,
// using a slash to separate them.
- l = (int)strlen(items[1]) + 1;
+ int l = (int)strlen(items[1]) + 1;
if (compflags != NULL) {
l += (int)strlen(compflags) + 1;
}
@@ -2269,23 +2219,23 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDWORDMAX", 2)
&& compmax == 0) {
- compmax = atoi((char *)items[1]);
+ compmax = atoi(items[1]);
if (compmax == 0) {
- smsg(_("Wrong COMPOUNDWORDMAX value in %s line %d: %s"),
+ smsg(0, _("Wrong COMPOUNDWORDMAX value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDMIN", 2)
&& compminlen == 0) {
- compminlen = atoi((char *)items[1]);
+ compminlen = atoi(items[1]);
if (compminlen == 0) {
- smsg(_("Wrong COMPOUNDMIN value in %s line %d: %s"),
+ smsg(0, _("Wrong COMPOUNDMIN value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDSYLMAX", 2)
&& compsylmax == 0) {
- compsylmax = atoi((char *)items[1]);
+ compsylmax = atoi(items[1]);
if (compsylmax == 0) {
- smsg(_("Wrong COMPOUNDSYLMAX value in %s line %d: %s"),
+ smsg(0, _("Wrong COMPOUNDSYLMAX value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDDUP", 1)) {
@@ -2297,8 +2247,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDTRIPLE", 1)) {
compoptions |= COMP_CHECKTRIPLE;
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDPATTERN", 2)) {
- if (atoi((char *)items[1]) == 0) {
- smsg(_("Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s"),
+ if (atoi(items[1]) == 0) {
+ smsg(0, _("Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDPATTERN", 3)) {
@@ -2350,15 +2300,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// "S" flag on all but the last block, thus we check for that
// and store it in ah_follows.
xstrlcpy(key, items[1], AH_KEY_LEN);
- hi = hash_find(tp, (char *)key);
+ hi = hash_find(tp, key);
if (!HASHITEM_EMPTY(hi)) {
cur_aff = HI2AH(hi);
if (cur_aff->ah_combine != (*items[2] == 'Y')) {
- smsg(_("Different combining flag in continued affix block in %s line %d: %s"),
+ smsg(0, _("Different combining flag in continued affix block in %s line %d: %s"),
fname, lnum, items[1]);
}
if (!cur_aff->ah_follows) {
- smsg(_("Duplicate affix in %s line %d: %s"),
+ smsg(0, _("Duplicate affix in %s line %d: %s"),
fname, lnum, items[1]);
}
} else {
@@ -2376,9 +2326,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
|| cur_aff->ah_flag == aff->af_nosuggest
|| cur_aff->ah_flag == aff->af_needcomp
|| cur_aff->ah_flag == aff->af_comproot) {
- smsg(_("Affix also used for "
- "BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST "
- "in %s line %d: %s"),
+ smsg(0, _("Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST "
+ "in %s line %d: %s"),
fname, lnum, items[1]);
}
STRCPY(cur_aff->ah_key, items[1]);
@@ -2402,11 +2351,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (itemcnt > lasti
&& !aff->af_ignoreextra
&& *items[lasti] != '#') {
- smsg(_(e_afftrailing), fname, lnum, items[lasti]);
+ smsg(0, _(e_afftrailing), fname, lnum, items[lasti]);
}
if (strcmp(items[2], "Y") != 0 && strcmp(items[2], "N") != 0) {
- smsg(_("Expected Y or N in %s line %d: %s"),
+ smsg(0, _("Expected Y or N in %s line %d: %s"),
fname, lnum, items[2]);
}
@@ -2427,14 +2376,13 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
}
}
- aff_todo = atoi((char *)items[3]);
+ aff_todo = atoi(items[3]);
} else if ((strcmp(items[0], "PFX") == 0
|| strcmp(items[0], "SFX") == 0)
&& aff_todo > 0
&& strcmp(cur_aff->ah_key, items[1]) == 0
&& itemcnt >= 5) {
affentry_T *aff_entry;
- bool upper = false;
int lasti = 5;
// Myspell allows extra text after the item, but that might
@@ -2443,7 +2391,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (itemcnt > lasti && *items[lasti] != '#'
&& (strcmp(items[lasti], "-") != 0
|| itemcnt != lasti + 1)) {
- smsg(_(e_afftrailing), fname, lnum, items[lasti]);
+ smsg(0, _(e_afftrailing), fname, lnum, items[lasti]);
}
// New item for an affix letter.
@@ -2475,14 +2423,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
char buf[MAXLINELEN];
aff_entry->ae_cond = getroom_save(spin, items[4]);
- if (*items[0] == 'P') {
- sprintf(buf, "^%s", items[4]); // NOLINT(runtime/printf)
- } else {
- sprintf(buf, "%s$", items[4]); // NOLINT(runtime/printf)
- }
+ snprintf(buf, sizeof(buf), *items[0] == 'P' ? "^%s" : "%s$", items[4]);
aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING + RE_STRICT);
if (aff_entry->ae_prog == NULL) {
- smsg(_("Broken condition in %s line %d: %s"),
+ smsg(0, _("Broken condition in %s line %d: %s"),
fname, lnum, items[4]);
}
}
@@ -2493,6 +2437,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// COMPOUNDFORBIDFLAG and COMPOUNDPERMITFLAG.
if (*items[0] == 'P' && aff->af_pfxpostpone
&& aff_entry->ae_flags == NULL) {
+ bool upper = false;
// When the chop string is one lower-case letter and
// the add string ends in the upper-case letter we set
// the "upper" flag, clear "ae_chop" and remove the
@@ -2502,10 +2447,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
&& aff_entry->ae_add != NULL
&& aff_entry->ae_chop[utfc_ptr2len(aff_entry->ae_chop)] ==
NUL) {
- int c, c_up;
-
- c = utf_ptr2char(aff_entry->ae_chop);
- c_up = SPELL_TOUPPER(c);
+ int c = utf_ptr2char(aff_entry->ae_chop);
+ int c_up = SPELL_TOUPPER(c);
if (c_up != c
&& (aff_entry->ae_cond == NULL
|| utf_ptr2char(aff_entry->ae_cond) == c)) {
@@ -2524,7 +2467,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
onecap_copy(items[4], buf, true);
aff_entry->ae_cond = getroom_save(spin, buf);
if (aff_entry->ae_cond != NULL) {
- sprintf(buf, "^%s", aff_entry->ae_cond); // NOLINT(runtime/printf)
+ snprintf(buf, MAXLINELEN, "^%s", aff_entry->ae_cond);
vim_regfree(aff_entry->ae_prog);
aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING);
}
@@ -2535,8 +2478,6 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (aff_entry->ae_chop == NULL) {
int idx;
- char_u **pp;
- int n;
// Find a previously used condition.
for (idx = spin->si_prefcond.ga_len - 1; idx >= 0; idx--) {
@@ -2548,9 +2489,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (idx < 0) {
// Not found, add a new condition.
idx = spin->si_prefcond.ga_len;
- pp = GA_APPEND_VIA_PTR(char_u *, &spin->si_prefcond);
- *pp = (aff_entry->ae_cond == NULL) ?
- NULL : (char_u *)getroom_save(spin, aff_entry->ae_cond);
+ char **pp = GA_APPEND_VIA_PTR(char *, &spin->si_prefcond);
+ *pp = (aff_entry->ae_cond == NULL)
+ ? NULL : getroom_save(spin, aff_entry->ae_cond);
}
// Add the prefix to the prefix tree.
@@ -2562,7 +2503,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// PFX_FLAGS is a negative number, so that
// tree_add_word() knows this is the prefix tree.
- n = PFX_FLAGS;
+ int n = PFX_FLAGS;
if (!cur_aff->ah_combine) {
n |= WFP_NC;
}
@@ -2575,7 +2516,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (aff_entry->ae_compforbid) {
n |= WFP_COMPFORBID;
}
- tree_add_word(spin, (char_u *)p, spin->si_prefroot, n,
+ tree_add_word(spin, p, spin->si_prefroot, n,
idx, cur_aff->ah_newID);
did_postpone_prefix = true;
}
@@ -2597,7 +2538,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
|| is_aff_rule(items, itemcnt, "REPSAL", 2)) {
// Ignore REP/REPSAL count
if (!isdigit((uint8_t)(*items[1]))) {
- smsg(_("Expected REP(SAL) count in %s line %d"),
+ smsg(0, _("Expected REP(SAL) count in %s line %d"),
fname, lnum);
}
} else if ((strcmp(items[0], "REP") == 0
@@ -2607,7 +2548,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// Myspell ignores extra arguments, we require it starts with
// # to detect mistakes.
if (itemcnt > 3 && items[3][0] != '#') {
- smsg(_(e_afftrailing), fname, lnum, items[3]);
+ smsg(0, _(e_afftrailing), fname, lnum, items[3]);
}
if (items[0][3] == 'S' ? do_repsal : do_rep) {
// Replace underscore with space (can't include a space
@@ -2632,27 +2573,25 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// First line contains the count.
found_map = true;
if (!isdigit((uint8_t)(*items[1]))) {
- smsg(_("Expected MAP count in %s line %d"),
+ smsg(0, _("Expected MAP count in %s line %d"),
fname, lnum);
}
} else if (do_mapline) {
- int c;
-
// Check that every character appears only once.
for (p = items[1]; *p != NUL;) {
- c = mb_ptr2char_adv((const char **)&p);
+ int c = mb_ptr2char_adv((const char **)&p);
if ((!GA_EMPTY(&spin->si_map)
&& vim_strchr(spin->si_map.ga_data, c)
!= NULL)
|| vim_strchr(p, c) != NULL) {
- smsg(_("Duplicate character in MAP in %s line %d"),
+ smsg(0, _("Duplicate character in MAP in %s line %d"),
fname, lnum);
}
}
// We simply concatenate all the MAP strings, separated by
// slashes.
- ga_concat(&spin->si_map, (char *)items[1]);
+ ga_concat(&spin->si_map, items[1]);
ga_append(&spin->si_map, '/');
}
}
@@ -2671,7 +2610,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// when "to" is "_" it means empty
add_fromto(spin, &spin->si_sal, items[1],
strcmp(items[2], "_") == 0 ? ""
- : items[2]);
+ : items[2]);
}
}
} else if (is_aff_rule(items, itemcnt, "SOFOFROM", 2)
@@ -2681,16 +2620,14 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
&& sofoto == NULL) {
sofoto = getroom_save(spin, items[1]);
} else if (strcmp(items[0], "COMMON") == 0) {
- int i;
-
- for (i = 1; i < itemcnt; i++) {
- if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, (char *)items[i]))) {
+ for (int i = 1; i < itemcnt; i++) {
+ if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, items[i]))) {
p = xstrdup(items[i]);
hash_add(&spin->si_commonwords, p);
}
}
} else {
- smsg(_("Unrecognized or duplicate item in %s line %d: %s"),
+ smsg(0, _("Unrecognized or duplicate item in %s line %d: %s"),
fname, lnum, items[0]);
}
}
@@ -2722,7 +2659,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (compsylmax != 0) {
if (syllable == NULL) {
- smsg("%s", _("COMPOUNDSYLMAX used without SYLLABLE"));
+ smsg(0, "%s", _("COMPOUNDSYLMAX used without SYLLABLE"));
}
aff_check_number(spin->si_compsylmax, compsylmax, "COMPOUNDSYLMAX");
spin->si_compsylmax = compsylmax;
@@ -2740,11 +2677,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// Check that we didn't use too many renumbered flags.
if (spin->si_newcompID < spin->si_newprefID) {
if (spin->si_newcompID == 127 || spin->si_newcompID == 255) {
- msg(_("Too many postponed prefixes"));
+ msg(_("Too many postponed prefixes"), 0);
} else if (spin->si_newprefID == 0 || spin->si_newprefID == 127) {
- msg(_("Too many compound flags"));
+ msg(_("Too many compound flags"), 0);
} else {
- msg(_("Too many postponed prefixes and/or compound flags"));
+ msg(_("Too many postponed prefixes and/or compound flags"), 0);
}
}
@@ -2755,10 +2692,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (sofofrom != NULL || sofoto != NULL) {
if (sofofrom == NULL || sofoto == NULL) {
- smsg(_("Missing SOFO%s line in %s"),
+ smsg(0, _("Missing SOFO%s line in %s"),
sofofrom == NULL ? "FROM" : "TO", fname);
} else if (!GA_EMPTY(&spin->si_sal)) {
- smsg(_("Both SAL and SOFO lines in %s"), fname);
+ smsg(0, _("Both SAL and SOFO lines in %s"), fname);
} else {
aff_check_string(spin->si_sofofr, sofofrom, "SOFOFROM");
aff_check_string(spin->si_sofoto, sofoto, "SOFOTO");
@@ -2790,18 +2727,14 @@ static bool is_aff_rule(char **items, int itemcnt, char *rulename, int mincount)
// ae_flags to ae_comppermit and ae_compforbid.
static void aff_process_flags(afffile_T *affile, affentry_T *entry)
{
- char *p;
- char_u *prevp;
- unsigned flag;
-
if (entry->ae_flags != NULL
&& (affile->af_compforbid != 0 || affile->af_comppermit != 0)) {
- for (p = entry->ae_flags; *p != NUL;) {
- prevp = (char_u *)p;
- flag = get_affitem(affile->af_flagtype, &p);
+ for (char *p = entry->ae_flags; *p != NUL;) {
+ char *prevp = p;
+ unsigned flag = get_affitem(affile->af_flagtype, &p);
if (flag == affile->af_comppermit || flag == affile->af_compforbid) {
- STRMOVE(prevp, (char *)p);
- p = (char *)prevp;
+ STRMOVE(prevp, p);
+ p = prevp;
if (flag == affile->af_comppermit) {
entry->ae_comppermit = true;
} else {
@@ -2833,21 +2766,20 @@ static bool spell_info_item(char *s)
// returns zero for failure.
static unsigned affitem2flag(int flagtype, char *item, char *fname, int lnum)
{
- unsigned res;
char *p = item;
- res = get_affitem(flagtype, &p);
+ unsigned res = get_affitem(flagtype, &p);
if (res == 0) {
if (flagtype == AFT_NUM) {
- smsg(_("Flag is not a number in %s line %d: %s"),
+ smsg(0, _("Flag is not a number in %s line %d: %s"),
fname, lnum, item);
} else {
- smsg(_("Illegal flag in %s line %d: %s"),
+ smsg(0, _("Illegal flag in %s line %d: %s"),
fname, lnum, item);
}
}
if (*p != NUL) {
- smsg(_(e_affname), fname, lnum, item);
+ smsg(0, _(e_affname), fname, lnum, item);
return 0;
}
@@ -2889,44 +2821,38 @@ static unsigned get_affitem(int flagtype, char **pp)
/// they fit in one byte.
static void process_compflags(spellinfo_T *spin, afffile_T *aff, char *compflags)
{
- char *p;
- char *prevp;
- unsigned flag;
compitem_T *ci;
int id;
- int len;
- char_u *tp;
char key[AH_KEY_LEN];
- hashitem_T *hi;
// Make room for the old and the new compflags, concatenated with a / in
// between. Processing it makes it shorter, but we don't know by how
// much, thus allocate the maximum.
- len = (int)strlen(compflags) + 1;
+ int len = (int)strlen(compflags) + 1;
if (spin->si_compflags != NULL) {
len += (int)strlen(spin->si_compflags) + 1;
}
- p = getroom(spin, (size_t)len, false);
+ char *p = getroom(spin, (size_t)len, false);
if (spin->si_compflags != NULL) {
STRCPY(p, spin->si_compflags);
STRCAT(p, "/");
}
spin->si_compflags = p;
- tp = (char_u *)p + strlen(p);
+ uint8_t *tp = (uint8_t *)p + strlen(p);
for (p = compflags; *p != NUL;) {
if (vim_strchr("/?*+[]", (uint8_t)(*p)) != NULL) {
// Copy non-flag characters directly.
- *tp++ = (char_u)(*p++);
+ *tp++ = (uint8_t)(*p++);
} else {
// First get the flag number, also checks validity.
- prevp = p;
- flag = get_affitem(aff->af_flagtype, &p);
+ char *prevp = p;
+ unsigned flag = get_affitem(aff->af_flagtype, &p);
if (flag != 0) {
// Find the flag in the hashtable. If it was used before, use
// the existing ID. Otherwise add a new entry.
xstrlcpy(key, prevp, (size_t)(p - prevp) + 1);
- hi = hash_find(&aff->af_comp, (char *)key);
+ hashitem_T *hi = hash_find(&aff->af_comp, key);
if (!HASHITEM_EMPTY(hi)) {
id = HI2CI(hi)->ci_newID;
} else {
@@ -2940,9 +2866,9 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char *compflags
id = spin->si_newcompID--;
} while (vim_strchr("/?*+[]\\-^", id) != NULL);
ci->ci_newID = id;
- hash_add(&aff->af_comp, (char *)ci->ci_key);
+ hash_add(&aff->af_comp, ci->ci_key);
}
- *tp++ = (char_u)id;
+ *tp++ = (uint8_t)id;
}
if (aff->af_flagtype == AFT_NUM && *p == ',') {
p++;
@@ -2968,17 +2894,14 @@ static void check_renumber(spellinfo_T *spin)
// Returns true if flag "flag" appears in affix list "afflist".
static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag)
{
- char *p;
- unsigned n;
-
switch (flagtype) {
case AFT_CHAR:
return vim_strchr(afflist, (int)flag) != NULL;
case AFT_CAPLONG:
case AFT_LONG:
- for (p = afflist; *p != NUL;) {
- n = (unsigned)mb_ptr2char_adv((const char **)&p);
+ for (char *p = afflist; *p != NUL;) {
+ unsigned n = (unsigned)mb_ptr2char_adv((const char **)&p);
if ((flagtype == AFT_LONG || (n >= 'A' && n <= 'Z'))
&& *p != NUL) {
n = (unsigned)mb_ptr2char_adv((const char **)&p) + (n << 16);
@@ -2990,10 +2913,10 @@ static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag)
break;
case AFT_NUM:
- for (p = afflist; *p != NUL;) {
+ for (char *p = afflist; *p != NUL;) {
int digits = getdigits_int(&p, true, 0);
assert(digits >= 0);
- n = (unsigned int)digits;
+ unsigned n = (unsigned)digits;
if (n == 0) {
n = ZERO_FLAG;
}
@@ -3013,7 +2936,7 @@ static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag)
static void aff_check_number(int spinval, int affval, char *name)
{
if (spinval != 0 && spinval != affval) {
- smsg(_("%s value differs from what is used in another .aff file"),
+ smsg(0, _("%s value differs from what is used in another .aff file"),
name);
}
}
@@ -3022,7 +2945,7 @@ static void aff_check_number(int spinval, int affval, char *name)
static void aff_check_string(char *spinval, char *affval, char *name)
{
if (spinval != NULL && strcmp(spinval, affval) != 0) {
- smsg(_("%s value differs from what is used in another .aff file"),
+ smsg(0, _("%s value differs from what is used in another .aff file"),
name);
}
}
@@ -3059,22 +2982,16 @@ static bool sal_to_bool(char *s)
// Free the structure filled by spell_read_aff().
static void spell_free_aff(afffile_T *aff)
{
- hashtab_T *ht;
- hashitem_T *hi;
- int todo;
- affheader_T *ah;
- affentry_T *ae;
-
xfree(aff->af_enc);
// All this trouble to free the "ae_prog" items...
- for (ht = &aff->af_pref;; ht = &aff->af_suff) {
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0; hi++) {
+ for (hashtab_T *ht = &aff->af_pref;; ht = &aff->af_suff) {
+ int todo = (int)ht->ht_used;
+ for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- ah = HI2AH(hi);
- for (ae = ah->ah_first; ae != NULL; ae = ae->ae_next) {
+ affheader_T *ah = HI2AH(hi);
+ for (affentry_T *ae = ah->ah_first; ae != NULL; ae = ae->ae_next) {
vim_regfree(ae->ae_prog);
}
}
@@ -3095,28 +3012,18 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
{
hashtab_T ht;
char line[MAXLINELEN];
- char_u *p;
- char_u *afflist;
- char_u store_afflist[MAXWLEN];
- int pfxlen;
- bool need_affix;
- char *dw;
- char_u *pc;
- char_u *w;
- int l;
- hash_T hash;
- hashitem_T *hi;
- FILE *fd;
+ char store_afflist[MAXWLEN];
+ char *pc;
+ char *w;
int lnum = 1;
int non_ascii = 0;
int retval = OK;
- char_u message[MAXLINELEN + MAXWLEN];
- int flags;
+ char message[MAXLINELEN + MAXWLEN];
int duplicate = 0;
Timestamp last_msg_time = 0;
// Open the file.
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return FAIL;
@@ -3133,14 +3040,14 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
spin->si_msg_count = 999999;
// Read and ignore the first line: word count.
- if (vim_fgets((char *)line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite((char *)line))) {
+ if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite(line))) {
semsg(_("E760: No word count in %s"), fname);
}
// Read all the lines in the file one by one.
// The words are converted to 'encoding' here, before being added to
// the hashtable.
- while (!vim_fgets((char *)line, MAXLINELEN, fd) && !got_int) {
+ while (!vim_fgets(line, MAXLINELEN, fd) && !got_int) {
line_breakcheck();
lnum++;
if (line[0] == '#' || line[0] == '/') {
@@ -3148,8 +3055,8 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
// Remove CR, LF and white space from the end. White space halfway through
// the word is kept to allow multi-word terms like "et al.".
- l = (int)strlen(line);
- while (l > 0 && line[l - 1] <= ' ') {
+ int l = (int)strlen(line);
+ while (l > 0 && (uint8_t)line[l - 1] <= ' ') {
l--;
}
if (l == 0) {
@@ -3159,24 +3066,24 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
// Convert from "SET" to 'encoding' when needed.
if (spin->si_conv.vc_type != CONV_NONE) {
- pc = (char_u *)string_convert(&spin->si_conv, (char *)line, NULL);
+ pc = string_convert(&spin->si_conv, line, NULL);
if (pc == NULL) {
- smsg(_("Conversion failure for word in %s line %d: %s"),
+ smsg(0, _("Conversion failure for word in %s line %d: %s"),
fname, lnum, line);
continue;
}
w = pc;
} else {
pc = NULL;
- w = (char_u *)line;
+ w = line;
}
// Truncate the word at the "/", set "afflist" to what follows.
// Replace "\/" by "/" and "\\" by "\".
- afflist = NULL;
- for (p = w; *p != NUL; MB_PTR_ADV(p)) {
+ char *afflist = NULL;
+ for (char *p = w; *p != NUL; MB_PTR_ADV(p)) {
if (*p == '\\' && (p[1] == '\\' || p[1] == '/')) {
- STRMOVE(p, (char *)p + 1);
+ STRMOVE(p, p + 1);
} else if (*p == '/') {
*p = NUL;
afflist = p + 1;
@@ -3185,7 +3092,7 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
// Skip non-ASCII words when "spin->si_ascii" is true.
- if (spin->si_ascii && has_non_ascii((char *)w)) {
+ if (spin->si_ascii && has_non_ascii(w)) {
non_ascii++;
xfree(pc);
continue;
@@ -3197,11 +3104,11 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
spin->si_msg_count = 0;
if (os_time() > last_msg_time) {
last_msg_time = os_time();
- vim_snprintf((char *)message, sizeof(message),
- _("line %6d, word %6ld - %s"),
+ vim_snprintf(message, sizeof(message),
+ _("line %6d, word %6d - %s"),
lnum, spin->si_foldwcount + spin->si_keepwcount, w);
msg_start();
- msg_outtrans_long_attr((char *)message, 0);
+ msg_outtrans_long(message, 0);
msg_clr_eos();
msg_didout = false;
msg_col = 0;
@@ -3210,21 +3117,21 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
// Store the word in the hashtable to be able to find duplicates.
- dw = getroom_save(spin, (char *)w);
+ char *dw = getroom_save(spin, w);
if (dw == NULL) {
retval = FAIL;
xfree(pc);
break;
}
- hash = hash_hash(dw);
- hi = hash_lookup(&ht, (const char *)dw, strlen(dw), hash);
+ hash_T hash = hash_hash(dw);
+ hashitem_T *hi = hash_lookup(&ht, dw, strlen(dw), hash);
if (!HASHITEM_EMPTY(hi)) {
if (p_verbose > 0) {
- smsg(_("Duplicate word in %s line %d: %s"),
+ smsg(0, _("Duplicate word in %s line %d: %s"),
fname, lnum, dw);
} else if (duplicate == 0) {
- smsg(_("First duplicate word in %s line %d: %s"),
+ smsg(0, _("First duplicate word in %s line %d: %s"),
fname, lnum, dw);
}
duplicate++;
@@ -3232,51 +3139,51 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
hash_add_item(&ht, hi, dw, hash);
}
- flags = 0;
+ int flags = 0;
store_afflist[0] = NUL;
- pfxlen = 0;
- need_affix = false;
+ int pfxlen = 0;
+ bool need_affix = false;
if (afflist != NULL) {
// Extract flags from the affix list.
- flags |= get_affix_flags(affile, (char *)afflist);
+ flags |= get_affix_flags(affile, afflist);
if (affile->af_needaffix != 0
- && flag_in_afflist(affile->af_flagtype, (char *)afflist,
+ && flag_in_afflist(affile->af_flagtype, afflist,
affile->af_needaffix)) {
need_affix = true;
}
if (affile->af_pfxpostpone) {
// Need to store the list of prefix IDs with the word.
- pfxlen = get_pfxlist(affile, (char *)afflist, store_afflist);
+ pfxlen = get_pfxlist(affile, afflist, store_afflist);
}
if (spin->si_compflags != NULL) {
// Need to store the list of compound flags with the word.
// Concatenate them to the list of prefix IDs.
- get_compflags(affile, (char *)afflist, store_afflist + pfxlen);
+ get_compflags(affile, afflist, store_afflist + pfxlen);
}
}
// Add the word to the word tree(s).
if (store_word(spin, dw, flags, spin->si_region,
- (char *)store_afflist, need_affix) == FAIL) {
+ store_afflist, need_affix) == FAIL) {
retval = FAIL;
}
if (afflist != NULL) {
// Find all matching suffixes and add the resulting words.
// Additionally do matching prefixes that combine.
- if (store_aff_word(spin, dw, (char *)afflist, affile,
+ if (store_aff_word(spin, dw, afflist, affile,
&affile->af_suff, &affile->af_pref,
- CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) {
+ CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) {
retval = FAIL;
}
// Find all matching prefixes and add the resulting words.
- if (store_aff_word(spin, dw, (char *)afflist, affile,
+ if (store_aff_word(spin, dw, afflist, affile,
&affile->af_pref, NULL,
- CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) {
+ CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) {
retval = FAIL;
}
}
@@ -3285,10 +3192,10 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
if (duplicate > 0) {
- smsg(_("%d duplicate word(s) in %s"), duplicate, fname);
+ smsg(0, _("%d duplicate word(s) in %s"), duplicate, fname);
}
if (spin->si_ascii && non_ascii > 0) {
- smsg(_("Ignored %d word(s) with non-ASCII characters in %s"),
+ smsg(0, _("Ignored %d word(s) with non-ASCII characters in %s"),
non_ascii, fname);
}
hash_clear(&ht);
@@ -3338,26 +3245,22 @@ static int get_affix_flags(afffile_T *affile, char *afflist)
// Used for PFXPOSTPONE.
// Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL
// and return the number of affixes.
-static int get_pfxlist(afffile_T *affile, char *afflist, char_u *store_afflist)
+static int get_pfxlist(afffile_T *affile, char *afflist, char *store_afflist)
{
- char *p;
- char *prevp;
int cnt = 0;
- int id;
char key[AH_KEY_LEN];
- hashitem_T *hi;
- for (p = afflist; *p != NUL;) {
- prevp = p;
+ for (char *p = afflist; *p != NUL;) {
+ char *prevp = p;
if (get_affitem(affile->af_flagtype, &p) != 0) {
// A flag is a postponed prefix flag if it appears in "af_pref"
// and its ID is not zero.
xstrlcpy(key, prevp, (size_t)(p - prevp) + 1);
- hi = hash_find(&affile->af_pref, (char *)key);
+ hashitem_T *hi = hash_find(&affile->af_pref, key);
if (!HASHITEM_EMPTY(hi)) {
- id = HI2AH(hi)->ah_newID;
+ int id = HI2AH(hi)->ah_newID;
if (id != 0) {
- store_afflist[cnt++] = (char_u)id;
+ store_afflist[cnt++] = (char)(uint8_t)id;
}
}
}
@@ -3373,22 +3276,19 @@ static int get_pfxlist(afffile_T *affile, char *afflist, char_u *store_afflist)
// Get the list of compound IDs from the affix list "afflist" that are used
// for compound words.
// Puts the flags in "store_afflist[]".
-static void get_compflags(afffile_T *affile, char *afflist, char_u *store_afflist)
+static void get_compflags(afffile_T *affile, char *afflist, char *store_afflist)
{
- char *p;
- char *prevp;
int cnt = 0;
char key[AH_KEY_LEN];
- hashitem_T *hi;
- for (p = afflist; *p != NUL;) {
- prevp = p;
+ for (char *p = afflist; *p != NUL;) {
+ char *prevp = p;
if (get_affitem(affile->af_flagtype, &p) != 0) {
// A flag is a compound flag if it appears in "af_comp".
xstrlcpy(key, prevp, (size_t)(p - prevp) + 1);
- hi = hash_find(&affile->af_comp, (char *)key);
+ hashitem_T *hi = hash_find(&affile->af_comp, key);
if (!HASHITEM_EMPTY(hi)) {
- store_afflist[cnt++] = (char_u)HI2CI(hi)->ci_newID;
+ store_afflist[cnt++] = (char)(uint8_t)HI2CI(hi)->ci_newID;
}
}
if (affile->af_flagtype == AFT_NUM && *p == ',') {
@@ -3418,28 +3318,19 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
hashtab_T *ht, hashtab_T *xht, int condit, int flags, char *pfxlist,
int pfxlen)
{
- int todo;
- hashitem_T *hi;
- affheader_T *ah;
affentry_T *ae;
char newword[MAXWLEN];
int retval = OK;
- int i, j;
- char *p;
- int use_flags;
- char *use_pfxlist;
- int use_pfxlen;
- bool need_affix;
- char_u store_afflist[MAXWLEN];
+ int j;
+ char store_afflist[MAXWLEN];
char pfx_pfxlist[MAXWLEN];
size_t wordlen = strlen(word);
- int use_condit;
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0 && retval == OK; hi++) {
+ int todo = (int)ht->ht_used;
+ for (hashitem_T *hi = ht->ht_array; todo > 0 && retval == OK; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- ah = HI2AH(hi);
+ affheader_T *ah = HI2AH(hi);
// Check that the affix combines, if required, and that the word
// supports this affix.
@@ -3464,7 +3355,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
&& (ae->ae_chop == NULL
|| strlen(ae->ae_chop) < wordlen)
&& (ae->ae_prog == NULL
- || vim_regexec_prog(&ae->ae_prog, false, word, (colnr_T)0))
+ || vim_regexec_prog(&ae->ae_prog, false, word, 0))
&& (((condit & CONDIT_CFIX) == 0)
== ((condit & CONDIT_AFF) == 0
|| ae->ae_flags == NULL
@@ -3478,10 +3369,10 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
} else {
xstrlcpy(newword, ae->ae_add, MAXWLEN);
}
- p = word;
+ char *p = word;
if (ae->ae_chop != NULL) {
// Skip chop string.
- i = mb_charlen(ae->ae_chop);
+ int i = mb_charlen(ae->ae_chop);
for (; i > 0; i--) {
MB_PTR_ADV(p);
}
@@ -3492,8 +3383,8 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
xstrlcpy(newword, word, MAXWLEN);
if (ae->ae_chop != NULL) {
// Remove chop string.
- p = newword + strlen(newword);
- i = mb_charlen(ae->ae_chop);
+ char *p = newword + strlen(newword);
+ int i = mb_charlen(ae->ae_chop);
for (; i > 0; i--) {
MB_PTR_BACK(newword, p);
}
@@ -3504,11 +3395,11 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
}
}
- use_flags = flags;
- use_pfxlist = pfxlist;
- use_pfxlen = pfxlen;
- need_affix = false;
- use_condit = condit | CONDIT_COMB | CONDIT_AFF;
+ int use_flags = flags;
+ char *use_pfxlist = pfxlist;
+ int use_pfxlen = pfxlen;
+ bool need_affix = false;
+ int use_condit = condit | CONDIT_COMB | CONDIT_AFF;
if (ae->ae_flags != NULL) {
// Extract flags from the affix list.
use_flags |= get_affix_flags(affile, ae->ae_flags);
@@ -3539,11 +3430,11 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
} else {
use_pfxlen = 0;
}
- use_pfxlist = (char *)store_afflist;
+ use_pfxlist = store_afflist;
// Combine the prefix IDs. Avoid adding the
// same ID twice.
- for (i = 0; i < pfxlen; i++) {
+ for (int i = 0; i < pfxlen; i++) {
for (j = 0; j < use_pfxlen; j++) {
if (pfxlist[i] == use_pfxlist[j]) {
break;
@@ -3557,7 +3448,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
if (spin->si_compflags != NULL) {
// Get compound IDS from the affix list.
get_compflags(affile, ae->ae_flags,
- (char_u *)use_pfxlist + use_pfxlen);
+ use_pfxlist + use_pfxlen);
} else {
use_pfxlist[use_pfxlen] = NUL;
}
@@ -3565,7 +3456,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
// Combine the list of compound flags.
// Concatenate them to the prefix IDs list.
// Avoid adding the same ID twice.
- for (i = pfxlen; pfxlist[i] != NUL; i++) {
+ for (int i = pfxlen; pfxlist[i] != NUL; i++) {
for (j = use_pfxlen; use_pfxlist[j] != NUL; j++) {
if (pfxlist[i] == use_pfxlist[j]) {
break;
@@ -3624,7 +3515,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
if (store_aff_word(spin, newword, ae->ae_flags,
affile, &affile->af_suff, xht,
use_condit & (xht == NULL
- ? ~0 : ~CONDIT_SUF),
+ ? ~0 : ~CONDIT_SUF),
use_flags, use_pfxlist, pfxlen) == FAIL) {
retval = FAIL;
}
@@ -3660,21 +3551,16 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
// Read a file with a list of words.
static int spell_read_wordfile(spellinfo_T *spin, char *fname)
{
- FILE *fd;
- long lnum = 0;
+ linenr_T lnum = 0;
char rline[MAXLINELEN];
char *line;
- char_u *pc = NULL;
- char_u *p;
- int l;
+ char *pc = NULL;
int retval = OK;
bool did_word = false;
int non_ascii = 0;
- int flags;
- int regionmask;
// Open the file.
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return FAIL;
@@ -3694,7 +3580,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
}
// Remove CR, LF and white space from the end.
- l = (int)strlen(rline);
+ int l = (int)strlen(rline);
while (l > 0 && (uint8_t)rline[l - 1] <= ' ') {
l--;
}
@@ -3706,13 +3592,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
// Convert from "/encoding={encoding}" to 'encoding' when needed.
xfree(pc);
if (spin->si_conv.vc_type != CONV_NONE) {
- pc = (char_u *)string_convert(&spin->si_conv, rline, NULL);
+ pc = string_convert(&spin->si_conv, rline, NULL);
if (pc == NULL) {
- smsg(_("Conversion failure for word in %s line %ld: %s"),
+ smsg(0, _("Conversion failure for word in %s line %" PRIdLINENR ": %s"),
fname, lnum, rline);
continue;
}
- line = (char *)pc;
+ line = pc;
} else {
pc = NULL;
line = rline;
@@ -3722,10 +3608,10 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
line++;
if (strncmp(line, "encoding=", 9) == 0) {
if (spin->si_conv.vc_type != CONV_NONE) {
- smsg(_("Duplicate /encoding= line ignored in %s line %ld: %s"),
+ smsg(0, _("Duplicate /encoding= line ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line - 1);
} else if (did_word) {
- smsg(_("/encoding= line after word ignored in %s line %ld: %s"),
+ smsg(0, _("/encoding= line after word ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line - 1);
} else {
char *enc;
@@ -3735,7 +3621,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
enc = enc_canonize(line);
if (!spin->si_ascii
&& convert_setup(&spin->si_conv, enc, p_enc) == FAIL) {
- smsg(_("Conversion in %s not supported: from %s to %s"),
+ smsg(0, _("Conversion in %s not supported: from %s to %s"),
fname, line, p_enc);
}
xfree(enc);
@@ -3746,12 +3632,12 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
if (strncmp(line, "regions=", 8) == 0) {
if (spin->si_region_count > 1) {
- smsg(_("Duplicate /regions= line ignored in %s line %ld: %s"),
+ smsg(0, _("Duplicate /regions= line ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line);
} else {
line += 8;
if (strlen(line) > MAXREGIONS * 2) {
- smsg(_("Too many regions in %s line %ld: %s"),
+ smsg(0, _("Too many regions in %s line %" PRIdLINENR ": %s"),
fname, lnum, line);
} else {
spin->si_region_count = (int)strlen(line) / 2;
@@ -3764,16 +3650,16 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
continue;
}
- smsg(_("/ line ignored in %s line %ld: %s"),
+ smsg(0, _("/ line ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line - 1);
continue;
}
- flags = 0;
- regionmask = spin->si_region;
+ int flags = 0;
+ int regionmask = spin->si_region;
// Check for flags and region after a slash.
- p = (char_u *)vim_strchr(line, '/');
+ char *p = vim_strchr(line, '/');
if (p != NULL) {
*p++ = NUL;
while (*p != NUL) {
@@ -3783,21 +3669,21 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
flags |= WF_BANNED;
} else if (*p == '?') { // Rare word.
flags |= WF_RARE;
- } else if (ascii_isdigit(*p)) { // region number(s)
+ } else if (ascii_isdigit((uint8_t)(*p))) { // region number(s)
if ((flags & WF_REGION) == 0) { // first one
regionmask = 0;
}
flags |= WF_REGION;
- l = *p - '0';
+ l = (uint8_t)(*p) - '0';
if (l == 0 || l > spin->si_region_count) {
- smsg(_("Invalid region nr in %s line %ld: %s"),
+ smsg(0, _("Invalid region nr in %s line %" PRIdLINENR ": %s"),
fname, lnum, p);
break;
}
regionmask |= 1 << (l - 1);
} else {
- smsg(_("Unrecognized flags in %s line %ld: %s"),
+ smsg(0, _("Unrecognized flags in %s line %" PRIdLINENR ": %s"),
fname, lnum, p);
break;
}
@@ -3842,7 +3728,6 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
static void *getroom(spellinfo_T *spin, size_t len, bool align)
FUNC_ATTR_NONNULL_RET
{
- char_u *p;
sblock_T *bl = spin->si_blocks;
assert(len <= SBLOCKSIZE);
@@ -3855,14 +3740,14 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align)
if (bl == NULL || (size_t)bl->sb_used + len > SBLOCKSIZE) {
// Allocate a block of memory. It is not freed until much later.
- bl = xcalloc(1, (sizeof(sblock_T) + SBLOCKSIZE));
+ bl = xcalloc(1, offsetof(sblock_T, sb_data) + SBLOCKSIZE + 1);
bl->sb_next = spin->si_blocks;
spin->si_blocks = bl;
bl->sb_used = 0;
spin->si_blocks_cnt++;
}
- p = bl->sb_data + bl->sb_used;
+ char *p = bl->sb_data + bl->sb_used;
bl->sb_used += (int)len;
return p;
@@ -3880,10 +3765,8 @@ static char *getroom_save(spellinfo_T *spin, char *s)
// Free the list of allocated sblock_T.
static void free_blocks(sblock_T *bl)
{
- sblock_T *next;
-
while (bl != NULL) {
- next = bl->sb_next;
+ sblock_T *next = bl->sb_next;
xfree(bl);
bl = next;
}
@@ -3901,7 +3784,7 @@ static wordnode_T *wordtree_alloc(spellinfo_T *spin)
/// Control characters and trailing '/' are invalid. Space is OK.
static bool valid_spell_word(const char *word, const char *end)
{
- if (!utf_valid_string((char_u *)word, (char_u *)end)) {
+ if (!utf_valid_string(word, end)) {
return false;
}
for (const char *p = word; *p != NUL && p < end; p += utfc_ptr2len(p)) {
@@ -3929,7 +3812,7 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
{
int len = (int)strlen(word);
int ct = captype(word, word + len);
- char_u foldword[MAXWLEN];
+ char foldword[MAXWLEN];
int res = OK;
// Avoid adding illegal bytes to the word tree.
@@ -3937,8 +3820,8 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
return FAIL;
}
- (void)spell_casefold(curwin, word, len, (char *)foldword, MAXWLEN);
- for (const char_u *p = (char_u *)pfxlist; res == OK; p++) {
+ (void)spell_casefold(curwin, word, len, foldword, MAXWLEN);
+ for (const char *p = pfxlist; res == OK; p++) {
if (!need_affix || (p != NULL && *p != NUL)) {
res = tree_add_word(spin, foldword, spin->si_foldroot, ct | flags,
region, p == NULL ? 0 : *p);
@@ -3950,9 +3833,9 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
spin->si_foldwcount++;
if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) {
- for (const char_u *p = (char_u *)pfxlist; res == OK; p++) {
+ for (const char *p = pfxlist; res == OK; p++) {
if (!need_affix || (p != NULL && *p != NUL)) {
- res = tree_add_word(spin, (char_u *)word, spin->si_keeproot, flags,
+ res = tree_add_word(spin, word, spin->si_keeproot, flags,
region, p == NULL ? 0 : *p);
}
if (p == NULL || *p == NUL) {
@@ -3968,26 +3851,23 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
// When "flags" < 0 we are adding to the prefix tree where "flags" is used for
// "rare" and "region" is the condition nr.
// Returns FAIL when out of memory.
-static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root, int flags,
+static int tree_add_word(spellinfo_T *spin, const char *word, wordnode_T *root, int flags,
int region, int affixID)
{
wordnode_T *node = root;
- wordnode_T *np;
- wordnode_T *copyp, **copyprev;
wordnode_T **prev = NULL;
- int i;
// Add each byte of the word to the tree, including the NUL at the end.
- for (i = 0;; i++) {
+ for (int i = 0;; i++) {
// When there is more than one reference to this node we need to make
// a copy, so that we can modify it. Copy the whole list of siblings
// (we don't optimize for a partly shared list of siblings).
if (node != NULL && node->wn_refs > 1) {
node->wn_refs--;
- copyprev = prev;
- for (copyp = node; copyp != NULL; copyp = copyp->wn_sibling) {
+ wordnode_T **copyprev = prev;
+ for (wordnode_T *copyp = node; copyp != NULL; copyp = copyp->wn_sibling) {
// Allocate a new node and copy the info.
- np = get_wordnode(spin);
+ wordnode_T *np = get_wordnode(spin);
if (np == NULL) {
return FAIL;
}
@@ -4021,7 +3901,7 @@ static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root
// higher byte value. For zero bytes (end of word) the sorting is
// done on flags and then on affixID.
while (node != NULL
- && (node->wn_byte < word[i]
+ && (node->wn_byte < (uint8_t)word[i]
|| (node->wn_byte == NUL
&& (flags < 0
? node->wn_affixID < (unsigned)affixID
@@ -4035,18 +3915,18 @@ static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root
node = *prev;
}
if (node == NULL
- || node->wn_byte != word[i]
+ || node->wn_byte != (uint8_t)word[i]
|| (word[i] == NUL
&& (flags < 0
|| spin->si_sugtree
|| node->wn_flags != (flags & WN_MASK)
|| node->wn_affixID != affixID))) {
// Allocate a new node.
- np = get_wordnode(spin);
+ wordnode_T *np = get_wordnode(spin);
if (np == NULL) {
return FAIL;
}
- np->wn_byte = word[i];
+ np->wn_byte = (uint8_t)word[i];
// If "node" is NULL this is a new child or the end of the sibling
// list: ref count is one. Otherwise use ref count of sibling and
@@ -4068,14 +3948,14 @@ static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root
if (word[i] == NUL) {
node->wn_flags = (uint16_t)flags;
node->wn_region |= (int16_t)region;
- node->wn_affixID = (char_u)affixID;
+ node->wn_affixID = (uint8_t)affixID;
break;
}
prev = &node->wn_child;
node = *prev;
}
#ifdef SPELL_PRINTTREE
- smsg((char_u *)"Added \"%s\"", word);
+ smsg(0, "Added \"%s\"", word);
spell_print_tree(root->wn_sibling);
#endif
@@ -4164,11 +4044,10 @@ static wordnode_T *get_wordnode(spellinfo_T *spin)
static int deref_wordnode(spellinfo_T *spin, wordnode_T *node)
FUNC_ATTR_NONNULL_ALL
{
- wordnode_T *np;
int cnt = 0;
if (--node->wn_refs == 0) {
- for (np = node; np != NULL; np = np->wn_sibling) {
+ for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) {
if (np->wn_child != NULL) {
cnt += deref_wordnode(spin, np->wn_child);
}
@@ -4195,7 +4074,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
FUNC_ATTR_NONNULL_ALL
{
hashtab_T ht;
- long tot = 0;
+ int tot = 0;
long perc;
// Skip the root itself, it's not actually used. The first sibling is the
@@ -4205,7 +4084,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
}
hash_init(&ht);
- const long n = node_compress(spin, root->wn_sibling, &ht, &tot);
+ const int n = node_compress(spin, root->wn_sibling, &ht, &tot);
#ifndef SPELL_PRINTTREE
if (spin->si_verbose || p_verbose > 2)
@@ -4219,7 +4098,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
perc = (tot - n) * 100 / tot;
}
vim_snprintf(IObuff, IOSIZE,
- _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"),
+ _("Compressed %s of %d nodes; %d (%ld%%) remaining"),
name, tot, tot - n, perc);
spell_message(spin, IObuff);
}
@@ -4233,32 +4112,29 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
/// Returns the number of compressed nodes.
///
/// @param tot total count of nodes before compressing, incremented while going through the tree
-static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, long *tot)
+static int node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, int *tot)
FUNC_ATTR_NONNULL_ALL
{
- wordnode_T *np;
wordnode_T *tp;
wordnode_T *child;
- hash_T hash;
- hashitem_T *hi;
- long len = 0;
- unsigned nr, n;
- long compressed = 0;
+ int len = 0;
+ unsigned n;
+ int compressed = 0;
// Go through the list of siblings. Compress each child and then try
// finding an identical child to replace it.
// Note that with "child" we mean not just the node that is pointed to,
// but the whole list of siblings of which the child node is the first.
- for (np = node; np != NULL && !got_int; np = np->wn_sibling) {
+ for (wordnode_T *np = node; np != NULL && !got_int; np = np->wn_sibling) {
len++;
if ((child = np->wn_child) != NULL) {
// Compress the child first. This fills hashkey.
compressed += node_compress(spin, child, ht, tot);
// Try to find an identical child.
- hash = hash_hash((char *)child->wn_u1.hashkey);
- hi = hash_lookup(ht, (const char *)child->wn_u1.hashkey,
- strlen((char *)child->wn_u1.hashkey), hash);
+ hash_T hash = hash_hash((char *)child->wn_u1.hashkey);
+ hashitem_T *hi = hash_lookup(ht, (const char *)child->wn_u1.hashkey,
+ strlen((char *)child->wn_u1.hashkey), hash);
if (!HASHITEM_EMPTY(hi)) {
// There are children we encountered before with a hash value
// identical to the current child. Now check if there is one
@@ -4294,9 +4170,9 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo
// Make a hash key for the node and its siblings, so that we can quickly
// find a lookalike node. This must be done after compressing the sibling
// list, otherwise the hash key would become invalid by the compression.
- node->wn_u1.hashkey[0] = (char_u)len;
- nr = 0;
- for (np = node; np != NULL; np = np->wn_sibling) {
+ node->wn_u1.hashkey[0] = (uint8_t)len;
+ unsigned nr = 0;
+ for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) {
if (np->wn_byte == NUL) {
// end node: use wn_flags, wn_region and wn_affixID
n = (unsigned)(np->wn_flags + (np->wn_region << 8) + (np->wn_affixID << 16));
@@ -4309,13 +4185,13 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo
// Avoid NUL bytes, it terminates the hash key.
n = nr & 0xff;
- node->wn_u1.hashkey[1] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[1] = n == 0 ? 1 : (uint8_t)n;
n = (nr >> 8) & 0xff;
- node->wn_u1.hashkey[2] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[2] = n == 0 ? 1 : (uint8_t)n;
n = (nr >> 16) & 0xff;
- node->wn_u1.hashkey[3] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[3] = n == 0 ? 1 : (uint8_t)n;
n = (nr >> 24) & 0xff;
- node->wn_u1.hashkey[4] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[4] = n == 0 ? 1 : (uint8_t)n;
node->wn_u1.hashkey[5] = NUL;
// Check for CTRL-C pressed now and then.
@@ -4371,7 +4247,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// <HEADER>: <fileID> <versionnr>
// <fileID>
size_t fwv = fwrite(VIMSPELLMAGIC, VIMSPELLMAGICL, 1, fd);
- if (fwv != (size_t)1) {
+ if (fwv != 1) {
// Catch first write error, don't try writing more.
goto theend;
}
@@ -4414,7 +4290,6 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// the table (avoids that it conflicts). File is shorter too.
if (!spin->si_ascii && !spin->si_add) {
char folchars[128 * 8];
- int flags;
putc(SN_CHARFLAGS, fd); // <sectionID>
putc(SNF_REQUIRED, fd); // <sectionflags>
@@ -4422,13 +4297,13 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// Form the <folchars> string first, we need to know its length.
size_t l = 0;
for (size_t i = 128; i < 256; i++) {
- l += (size_t)utf_char2bytes(spelltab.st_fold[i], (char *)folchars + l);
+ l += (size_t)utf_char2bytes(spelltab.st_fold[i], folchars + l);
}
put_bytes(fd, 1 + 128 + 2 + l, 4); // <sectionlen>
fputc(128, fd); // <charflagslen>
for (size_t i = 128; i < 256; i++) {
- flags = 0;
+ int flags = 0;
if (spelltab.st_isw[i]) {
flags |= CF_WORD;
}
@@ -4471,7 +4346,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// round 1: SN_REP section
// round 2: SN_SAL section (unless SN_SOFO is used)
// round 3: SN_REPSAL section
- for (unsigned int round = 1; round <= 3; round++) {
+ for (unsigned round = 1; round <= 3; round++) {
garray_T *gap;
if (round == 1) {
gap = &spin->si_rep;
@@ -4534,7 +4409,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// <rep> : <repfromlen> <repfrom> <reptolen> <repto>
// <sal> : <salfromlen> <salfrom> <saltolen> <salto>
fromto_T *ftp = &((fromto_T *)gap->ga_data)[i];
- for (unsigned int rr = 1; rr <= 2; rr++) {
+ for (unsigned rr = 1; rr <= 2; rr++) {
char *p = rr == 1 ? ftp->ft_from : ftp->ft_to;
l = strlen(p);
assert(l < INT_MAX);
@@ -4571,7 +4446,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// round 1: count the bytes
// round 2: write the bytes
- for (unsigned int round = 1; round <= 2; round++) {
+ for (unsigned round = 1; round <= 2; round++) {
size_t todo;
size_t len = 0;
hashitem_T *hi;
@@ -4694,7 +4569,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// <LWORDTREE> <KWORDTREE> <PREFIXTREE>
spin->si_memtot = 0;
- for (unsigned int round = 1; round <= 3; round++) {
+ for (unsigned round = 1; round <= 3; round++) {
wordnode_T *tree;
if (round == 1) {
tree = spin->si_foldroot->wn_sibling;
@@ -4730,7 +4605,7 @@ theend:
retval = FAIL;
}
- if (fwv != (size_t)1) {
+ if (fwv != 1) {
retval = FAIL;
}
if (retval == FAIL) {
@@ -4745,10 +4620,8 @@ theend:
// space.
static void clear_node(wordnode_T *node)
{
- wordnode_T *np;
-
if (node != NULL) {
- for (np = node; np != NULL; np = np->wn_sibling) {
+ for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) {
np->wn_u1.index = 0;
np->wn_u2.wnode = NULL;
@@ -4906,7 +4779,6 @@ void ex_mkspell(exarg_T *eap)
static void spell_make_sugfile(spellinfo_T *spin, char *wfname)
{
char *fname = NULL;
- int len;
slang_T *slang;
bool free_slang = false;
@@ -4953,7 +4825,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char *wfname)
goto theend;
}
- smsg(_("Number of words after soundfolding: %" PRId64),
+ smsg(0, _("Number of words after soundfolding: %" PRId64),
(int64_t)spin->si_spellbuf->b_ml.ml_line_count);
// Compress the soundfold trie.
@@ -4964,7 +4836,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char *wfname)
// Make the file name by changing ".spl" to ".sug".
fname = xmalloc(MAXPATHL);
xstrlcpy(fname, wfname, MAXPATHL);
- len = (int)strlen(fname);
+ int len = (int)strlen(fname);
fname[len - 2] = 'u';
fname[len - 1] = 'g';
sug_write(spin, fname);
@@ -4981,15 +4853,10 @@ theend:
// Build the soundfold trie for language "slang".
static int sug_filltree(spellinfo_T *spin, slang_T *slang)
{
- char_u *byts;
- idx_T *idxs;
- int depth;
idx_T arridx[MAXWLEN];
int curi[MAXWLEN];
- char_u tword[MAXWLEN];
- char_u tsalword[MAXWLEN];
- int c;
- idx_T n;
+ char tword[MAXWLEN];
+ char tsalword[MAXWLEN];
unsigned words_done = 0;
int wordcount[MAXWLEN];
@@ -5000,15 +4867,18 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
spin->si_sugtree = true;
// Go through the whole case-folded tree, soundfold each word and put it
- // in the trie.
- byts = (char_u *)slang->sl_fbyts;
- idxs = slang->sl_fidxs;
+ // in the trie. Bail out if the tree is empty.
+ uint8_t *byts = slang->sl_fbyts;
+ idx_T *idxs = slang->sl_fidxs;
+ if (byts == NULL || idxs == NULL) {
+ return FAIL;
+ }
arridx[0] = 0;
curi[0] = 1;
wordcount[0] = 0;
- depth = 0;
+ int depth = 0;
while (depth >= 0 && !got_int) {
if (curi[depth] > byts[arridx[depth]]) {
// Done all bytes at this node, go up one level.
@@ -5021,14 +4891,14 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
line_breakcheck();
} else {
// Do one more byte at this node.
- n = arridx[depth] + curi[depth];
+ idx_T n = arridx[depth] + curi[depth];
curi[depth]++;
- c = byts[n];
+ int c = byts[n];
if (c == 0) {
// Sound-fold the word.
tword[depth] = NUL;
- spell_soundfold(slang, (char *)tword, true, (char *)tsalword);
+ spell_soundfold(slang, tword, true, tsalword);
// We use the "flags" field for the MSB of the wordnr,
// "region" for the LSB of the wordnr.
@@ -5053,7 +4923,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
}
} else {
// Normal char, go one level deeper.
- tword[depth++] = (char_u)c;
+ tword[depth++] = (char)(uint8_t)c;
arridx[depth] = idxs[n];
curi[depth] = 1;
wordcount[depth] = 0;
@@ -5061,7 +4931,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
}
}
- smsg(_("Total number of words: %d"), words_done);
+ smsg(0, _("Total number of words: %d"), words_done);
return OK;
}
@@ -5100,31 +4970,27 @@ static int sug_maketable(spellinfo_T *spin)
/// @param gap place to store line of numbers
static int sug_filltable(spellinfo_T *spin, wordnode_T *node, int startwordnr, garray_T *gap)
{
- wordnode_T *p, *np;
int wordnr = startwordnr;
- int nr;
- int prev_nr;
- for (p = node; p != NULL; p = p->wn_sibling) {
+ for (wordnode_T *p = node; p != NULL; p = p->wn_sibling) {
if (p->wn_byte == NUL) {
gap->ga_len = 0;
- prev_nr = 0;
- for (np = p; np != NULL && np->wn_byte == NUL; np = np->wn_sibling) {
+ int prev_nr = 0;
+ for (wordnode_T *np = p; np != NULL && np->wn_byte == NUL; np = np->wn_sibling) {
ga_grow(gap, 10);
- nr = (np->wn_flags << 16) + (np->wn_region & 0xffff);
+ int nr = (np->wn_flags << 16) + (np->wn_region & 0xffff);
// Compute the offset from the previous nr and store the
// offset in a way that it takes a minimum number of bytes.
// It's a bit like utf-8, but without the need to mark
// following bytes.
nr -= prev_nr;
prev_nr += nr;
- gap->ga_len += offset2bytes(nr,
- (char_u *)gap->ga_data + gap->ga_len);
+ gap->ga_len += offset2bytes(nr, (char *)gap->ga_data + gap->ga_len);
}
// add the NUL byte
- ((char_u *)gap->ga_data)[gap->ga_len++] = NUL;
+ ((char *)gap->ga_data)[gap->ga_len++] = NUL;
if (ml_append_buf(spin->si_spellbuf, (linenr_T)wordnr,
gap->ga_data, gap->ga_len, true) == FAIL) {
@@ -5155,39 +5021,38 @@ static int sug_filltable(spellinfo_T *spin, wordnode_T *node, int startwordnr, g
// Convert an offset into a minimal number of bytes.
// Similar to utf_char2byters, but use 8 bits in followup bytes and avoid NUL
// bytes.
-static int offset2bytes(int nr, char_u *buf)
+static int offset2bytes(int nr, char *buf_in)
{
- int rem;
- int b1, b2, b3, b4;
+ uint8_t *buf = (uint8_t *)buf_in;
// Split the number in parts of base 255. We need to avoid NUL bytes.
- b1 = nr % 255 + 1;
- rem = nr / 255;
- b2 = rem % 255 + 1;
+ int b1 = nr % 255 + 1;
+ int rem = nr / 255;
+ int b2 = rem % 255 + 1;
rem = rem / 255;
- b3 = rem % 255 + 1;
- b4 = rem / 255 + 1;
+ int b3 = rem % 255 + 1;
+ int b4 = rem / 255 + 1;
if (b4 > 1 || b3 > 0x1f) { // 4 bytes
- buf[0] = (char_u)(0xe0 + b4);
- buf[1] = (char_u)b3;
- buf[2] = (char_u)b2;
- buf[3] = (char_u)b1;
+ buf[0] = (uint8_t)(0xe0 + b4);
+ buf[1] = (uint8_t)b3;
+ buf[2] = (uint8_t)b2;
+ buf[3] = (uint8_t)b1;
return 4;
}
if (b3 > 1 || b2 > 0x3f) { // 3 bytes
- buf[0] = (char_u)(0xc0 + b3);
- buf[1] = (char_u)b2;
- buf[2] = (char_u)b1;
+ buf[0] = (uint8_t)(0xc0 + b3);
+ buf[1] = (uint8_t)b2;
+ buf[2] = (uint8_t)b1;
return 3;
}
if (b2 > 1 || b1 > 0x7f) { // 2 bytes
- buf[0] = (char_u)(0x80 + b2);
- buf[1] = (char_u)b1;
+ buf[0] = (uint8_t)(0x80 + b2);
+ buf[1] = (uint8_t)b1;
return 2;
}
// 1 byte
- buf[0] = (char_u)b1;
+ buf[0] = (uint8_t)b1;
return 1;
}
@@ -5206,7 +5071,7 @@ static void sug_write(spellinfo_T *spin, char *fname)
spell_message(spin, IObuff);
// <SUGHEADER>: <fileID> <versionnr> <timestamp>
- if (fwrite(VIMSUGMAGIC, VIMSUGMAGICL, (size_t)1, fd) != 1) { // <fileID>
+ if (fwrite(VIMSUGMAGIC, VIMSUGMAGICL, 1, fd) != 1) { // <fileID>
emsg(_(e_write));
goto theend;
}
@@ -5242,7 +5107,7 @@ static void sug_write(spellinfo_T *spin, char *fname)
for (linenr_T lnum = 1; lnum <= wcount; lnum++) {
// <sugline>: <sugnr> ... NUL
- char *line = ml_get_buf(spin->si_spellbuf, lnum, false);
+ char *line = ml_get_buf(spin->si_spellbuf, lnum);
size_t len = strlen(line) + 1;
if (fwrite(line, len, 1, fd) == 0) {
emsg(_(e_write));
@@ -5278,11 +5143,7 @@ theend:
static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool added_word)
{
char *fname = NULL;
- char **innames;
- int incount;
afffile_T *(afile[MAXREGIONS]);
- int i;
- int len;
bool error = false;
spellinfo_T spin;
@@ -5302,13 +5163,13 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
// default: fnames[0] is output file, following are input files
// When "fcount" is 1 there is only one file.
- innames = &fnames[fcount == 1 ? 0 : 1];
- incount = fcount - 1;
+ char **innames = &fnames[fcount == 1 ? 0 : 1];
+ int incount = fcount - 1;
char *wfname = xmalloc(MAXPATHL);
if (fcount >= 1) {
- len = (int)strlen(fnames[0]);
+ int len = (int)strlen(fnames[0]);
if (fcount == 1 && len > 4 && strcmp(fnames[0] + len - 4, ".add") == 0) {
// For ":mkspell path/en.latin1.add" output file is
// "path/en.latin1.add.spl".
@@ -5361,18 +5222,18 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
// Init the aff and dic pointers.
// Get the region names if there are more than 2 arguments.
- for (i = 0; i < incount; i++) {
+ for (int i = 0; i < incount; i++) {
afile[i] = NULL;
if (incount > 1) {
- len = (int)strlen(innames[i]);
+ int len = (int)strlen(innames[i]);
if (strlen(path_tail(innames[i])) < 5
|| innames[i][len - 3] != '_') {
semsg(_("E755: Invalid region in %s"), innames[i]);
goto theend;
}
- spin.si_region_name[i * 2] = (char_u)TOLOWER_ASC(innames[i][len - 2]);
- spin.si_region_name[i * 2 + 1] = (char_u)TOLOWER_ASC(innames[i][len - 1]);
+ spin.si_region_name[i * 2] = (char)(uint8_t)TOLOWER_ASC(innames[i][len - 2]);
+ spin.si_region_name[i * 2 + 1] = (char)(uint8_t)TOLOWER_ASC(innames[i][len - 1]);
}
}
spin.si_region_count = incount;
@@ -5393,7 +5254,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
// Read all the .aff and .dic files.
// Text is converted to 'encoding'.
// Words are stored in the case-folded and keep-case trees.
- for (i = 0; i < incount && !error; i++) {
+ for (int i = 0; i < incount && !error; i++) {
spin.si_conv.vc_type = CONV_NONE;
spin.si_region = 1 << i;
@@ -5424,7 +5285,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
}
if (spin.si_compflags != NULL && spin.si_nobreak) {
- msg(_("Warning: both compounding and NOBREAK specified"));
+ msg(_("Warning: both compounding and NOBREAK specified"), 0);
}
if (!error && !got_int) {
@@ -5464,7 +5325,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
hash_clear_all(&spin.si_commonwords, 0);
// Free the .aff file structures.
- for (i = 0; i < incount; i++) {
+ for (int i = 0; i < incount; i++) {
if (afile[i] != NULL) {
spell_free_aff(afile[i]);
}
@@ -5494,7 +5355,7 @@ static void spell_message(const spellinfo_T *spin, char *str)
if (!spin->si_verbose) {
verbose_enter();
}
- msg(str);
+ msg(str, 0);
ui_flush();
if (!spin->si_verbose) {
verbose_leave();
@@ -5509,8 +5370,9 @@ static void spell_message(const spellinfo_T *spin, char *str)
void ex_spell(exarg_T *eap)
{
spell_add_word(eap->arg, (int)strlen(eap->arg),
- eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD :
- eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD,
+ eap->cmdidx == CMD_spellwrong
+ ? SPELL_ADD_BAD
+ : eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD,
eap->forceit ? 0 : (int)eap->line2,
eap->cmdidx == CMD_spellundo);
}
@@ -5528,8 +5390,6 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
char *fname;
char *fnamebuf = NULL;
char line[MAXWLEN * 2];
- long fpos, fpos_next = 0;
- int i;
char *spf;
if (!valid_spell_word(word, word + len)) {
@@ -5546,6 +5406,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
}
fname = int_wordlist;
} else {
+ int i;
// If 'spellfile' isn't set figure out a good default value.
if (*curwin->w_s->b_p_spf == NUL) {
init_spellfile();
@@ -5585,13 +5446,15 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
}
if (what == SPELL_ADD_BAD || undo) {
+ int fpos_next = 0;
+ int fpos = 0;
// When the word appears as good word we need to remove that one,
// since its flags sort before the one with WF_BANNED.
fd = os_fopen(fname, "r");
if (fd != NULL) {
- while (!vim_fgets((char *)line, MAXWLEN * 2, fd)) {
+ while (!vim_fgets(line, MAXWLEN * 2, fd)) {
fpos = fpos_next;
- fpos_next = ftell(fd);
+ fpos_next = (int)ftell(fd);
if (fpos_next < 0) {
break; // should never happen
}
@@ -5609,7 +5472,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
fputc('#', fd);
if (undo) {
home_replace(NULL, fname, NameBuff, MAXPATHL, true);
- smsg(_("Word '%.*s' removed from %s"), len, word, NameBuff);
+ smsg(0, _("Word '%.*s' removed from %s"), len, word, NameBuff);
}
}
if (fseek(fd, fpos_next, SEEK_SET) != 0) {
@@ -5627,21 +5490,21 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
if (!undo) {
fd = os_fopen(fname, "a");
if (fd == NULL && new_spf) {
- char_u *p;
+ char *p;
// We just initialized the 'spellfile' option and can't open the
// file. We may need to create the "spell" directory first. We
// already checked the runtime directory is writable in
// init_spellfile().
if (!dir_of_file_exists(fname)
- && (p = (char_u *)path_tail_with_sep(fname)) != (char_u *)fname) {
- int c = *p;
+ && (p = path_tail_with_sep(fname)) != fname) {
+ char c = *p;
// The directory doesn't exist. Try creating it and opening
// the file again.
*p = NUL;
os_mkdir(fname, 0755);
- *p = (char_u)c;
+ *p = c;
fd = os_fopen(fname, "a");
}
}
@@ -5659,7 +5522,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
fclose(fd);
home_replace(NULL, fname, NameBuff, MAXPATHL, true);
- smsg(_("Word '%.*s' added to %s"), len, word, NameBuff);
+ smsg(0, _("Word '%.*s' added to %s"), len, word, NameBuff);
}
}
@@ -5680,10 +5543,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
// Initialize 'spellfile' for the current buffer.
static void init_spellfile(void)
{
- char *buf;
int l;
- char *fname;
- char *rtp;
char *lend;
bool aspath = false;
char *lstart = curbuf->b_s.b_p_spl;
@@ -5692,7 +5552,7 @@ static void init_spellfile(void)
return;
}
- buf = xmalloc(MAXPATHL);
+ char *buf = xmalloc(MAXPATHL);
// Find the end of the language name. Exclude the region. If there
// is a path separator remember the start of the tail.
@@ -5706,7 +5566,7 @@ static void init_spellfile(void)
// Loop over all entries in 'runtimepath'. Use the first one where we
// are allowed to write.
- rtp = p_rtp;
+ char *rtp = p_rtp;
while (*rtp != NUL) {
if (aspath) {
// Use directory of an entry with path, e.g., for
@@ -5734,14 +5594,14 @@ static void init_spellfile(void)
"/%.*s", (int)(lend - lstart), lstart);
}
l = (int)strlen(buf);
- fname = LANGP_ENTRY(curwin->w_s->b_langp, 0)
- ->lp_slang->sl_fname;
+ char *fname = LANGP_ENTRY(curwin->w_s->b_langp, 0)
+ ->lp_slang->sl_fname;
vim_snprintf(buf + l, MAXPATHL - (size_t)l, ".%s.add",
((fname != NULL
&& strstr(path_tail(fname), ".ascii.") != NULL)
? "ascii"
- : (const char *)spell_enc()));
- set_option_value_give_err("spellfile", 0L, buf, OPT_LOCAL);
+ : spell_enc()));
+ set_option_value_give_err("spellfile", CSTR_AS_OPTVAL(buf), OPT_LOCAL);
break;
}
aspath = false;
@@ -5753,28 +5613,27 @@ static void init_spellfile(void)
/// Set the spell character tables from strings in the .spl file.
///
/// @param cnt length of "flags"
-static void set_spell_charflags(const char_u *flags, int cnt, char *fol)
+static void set_spell_charflags(const char *flags_in, int cnt, const char *fol)
{
+ const uint8_t *flags = (uint8_t *)flags_in;
// We build the new tables here first, so that we can compare with the
// previous one.
spelltab_T new_st;
- int i;
- char *p = fol;
- int c;
+ const char *p = fol;
clear_spell_chartab(&new_st);
- for (i = 0; i < 128; i++) {
+ for (int i = 0; i < 128; i++) {
if (i < cnt) {
new_st.st_isw[i + 128] = (flags[i] & CF_WORD) != 0;
new_st.st_isu[i + 128] = (flags[i] & CF_UPPER) != 0;
}
if (*p != NUL) {
- c = mb_ptr2char_adv((const char **)&p);
- new_st.st_fold[i + 128] = (char_u)c;
+ int c = mb_ptr2char_adv(&p);
+ new_st.st_fold[i + 128] = (uint8_t)c;
if (i + 128 != c && new_st.st_isu[i + 128] && c < 256) {
- new_st.st_upper[c] = (char_u)(i + 128);
+ new_st.st_upper[c] = (uint8_t)(i + 128);
}
}
}
@@ -5784,11 +5643,9 @@ static void set_spell_charflags(const char_u *flags, int cnt, char *fol)
static int set_spell_finish(spelltab_T *new_st)
{
- int i;
-
if (did_set_spelltab) {
// check that it's the same table
- for (i = 0; i < 256; i++) {
+ for (int i = 0; i < 256; i++) {
if (spelltab.st_isw[i] != new_st->st_isw[i]
|| spelltab.st_isu[i] != new_st->st_isu[i]
|| spelltab.st_fold[i] != new_st->st_fold[i]
@@ -5837,12 +5694,9 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv)
}
// Use map string "map" for languages "lp".
-static void set_map_str(slang_T *lp, char *map)
+static void set_map_str(slang_T *lp, const char *map)
{
- char *p;
int headc = 0;
- int c;
- int i;
if (*map == NUL) {
lp->sl_has_map = false;
@@ -5851,7 +5705,7 @@ static void set_map_str(slang_T *lp, char *map)
lp->sl_has_map = true;
// Init the array and hash tables empty.
- for (i = 0; i < 256; i++) {
+ for (int i = 0; i < 256; i++) {
lp->sl_map_array[i] = 0;
}
hash_init(&lp->sl_map_hash);
@@ -5859,8 +5713,8 @@ static void set_map_str(slang_T *lp, char *map)
// The similar characters are stored separated with slashes:
// "aaa/bbb/ccc/". Fill sl_map_array[c] with the character before c and
// before the same slash. For characters above 255 sl_map_hash is used.
- for (p = map; *p != NUL;) {
- c = mb_cptr2char_adv((const char **)&p);
+ for (const char *p = map; *p != NUL;) {
+ int c = mb_cptr2char_adv(&p);
if (c == '/') {
headc = 0;
} else {
@@ -5883,13 +5737,13 @@ static void set_map_str(slang_T *lp, char *map)
utf_char2bytes(headc, b + cl + 1);
b[cl + 1 + headcl] = NUL;
hash = hash_hash(b);
- hi = hash_lookup(&lp->sl_map_hash, (const char *)b, strlen(b), hash);
+ hi = hash_lookup(&lp->sl_map_hash, b, strlen(b), hash);
if (HASHITEM_EMPTY(hi)) {
hash_add_item(&lp->sl_map_hash, hi, b, hash);
} else {
// This should have been checked when generating the .spl
// file.
- emsg(_("E783: duplicate char in MAP entry"));
+ emsg(_(e_duplicate_char_in_map_entry));
xfree(b);
}
} else {
diff --git a/src/nvim/spellfile.h b/src/nvim/spellfile.h
index 235bc07f61..3e68ddd5a4 100644
--- a/src/nvim/spellfile.h
+++ b/src/nvim/spellfile.h
@@ -1,13 +1,8 @@
-#ifndef NVIM_SPELLFILE_H
-#define NVIM_SPELLFILE_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/spell_defs.h"
-#include "nvim/types.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/spell_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spellfile.h.generated.h"
#endif
-#endif // NVIM_SPELLFILE_H
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
index 22add418a0..d9dd28527e 100644
--- a/src/nvim/spellsuggest.c
+++ b/src/nvim/spellsuggest.c
@@ -1,53 +1,51 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// spellsuggest.c: functions for spelling suggestions
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/highlight_defs.h"
#include "nvim/input.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
-#include "nvim/screen.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Use this to adjust the score after finding suggestions, based on the
// suggested word sounding like the bad word. This is much faster than doing
@@ -74,9 +72,9 @@ typedef struct suginfo_S {
char *su_badptr; ///< start of bad word in line
int su_badlen; ///< length of detected bad word in line
int su_badflags; ///< caps flags for bad word
- char_u su_badword[MAXWLEN]; ///< bad word truncated at su_badlen
+ char su_badword[MAXWLEN]; ///< bad word truncated at su_badlen
char su_fbadword[MAXWLEN]; ///< su_badword case-folded
- char_u su_sal_badword[MAXWLEN]; ///< su_badword soundfolded
+ char su_sal_badword[MAXWLEN]; ///< su_badword soundfolded
hashtab_T su_banned; ///< table with banned words
slang_T *su_sallang; ///< default language for sound folding
} suginfo_T;
@@ -189,28 +187,28 @@ typedef enum {
/// Struct to keep the state at each level in suggest_try_change().
typedef struct trystate_S {
- state_T ts_state; ///< state at this level, STATE_
- int ts_score; ///< score
- idx_T ts_arridx; ///< index in tree array, start of node
- int16_t ts_curi; ///< index in list of child nodes
- char_u ts_fidx; ///< index in fword[], case-folded bad word
- char_u ts_fidxtry; ///< ts_fidx at which bytes may be changed
- char_u ts_twordlen; ///< valid length of tword[]
- char_u ts_prefixdepth; ///< stack depth for end of prefix or
- ///< PFD_PREFIXTREE or PFD_NOPREFIX
- char_u ts_flags; ///< TSF_ flags
- char_u ts_tcharlen; ///< number of bytes in tword character
- char_u ts_tcharidx; ///< current byte index in tword character
- char_u ts_isdiff; ///< DIFF_ values
- char_u ts_fcharstart; ///< index in fword where badword char started
- char_u ts_prewordlen; ///< length of word in "preword[]"
- char_u ts_splitoff; ///< index in "tword" after last split
- char_u ts_splitfidx; ///< "ts_fidx" at word split
- char_u ts_complen; ///< nr of compound words used
- char_u ts_compsplit; ///< index for "compflags" where word was spit
- char_u ts_save_badflags; ///< su_badflags saved here
- char_u ts_delidx; ///< index in fword for char that was deleted,
- ///< valid when "ts_flags" has TSF_DIDDEL
+ state_T ts_state; ///< state at this level, STATE_
+ int ts_score; ///< score
+ idx_T ts_arridx; ///< index in tree array, start of node
+ int16_t ts_curi; ///< index in list of child nodes
+ uint8_t ts_fidx; ///< index in fword[], case-folded bad word
+ uint8_t ts_fidxtry; ///< ts_fidx at which bytes may be changed
+ uint8_t ts_twordlen; ///< valid length of tword[]
+ uint8_t ts_prefixdepth; ///< stack depth for end of prefix or
+ ///< PFD_PREFIXTREE or PFD_NOPREFIX
+ uint8_t ts_flags; ///< TSF_ flags
+ uint8_t ts_tcharlen; ///< number of bytes in tword character
+ uint8_t ts_tcharidx; ///< current byte index in tword character
+ uint8_t ts_isdiff; ///< DIFF_ values
+ uint8_t ts_fcharstart; ///< index in fword where badword char started
+ uint8_t ts_prewordlen; ///< length of word in "preword[]"
+ uint8_t ts_splitoff; ///< index in "tword" after last split
+ uint8_t ts_splitfidx; ///< "ts_fidx" at word split
+ uint8_t ts_complen; ///< nr of compound words used
+ uint8_t ts_compsplit; ///< index for "compflags" where word was spit
+ uint8_t ts_save_badflags; ///< su_badflags saved here
+ uint8_t ts_delidx; ///< index in fword for char that was deleted,
+ ///< valid when "ts_flags" has TSF_DIDDEL
} trystate_T;
// values for ts_isdiff
@@ -234,7 +232,7 @@ enum {
PFD_NOTSPECIAL = 0xfd, // highest value that's not special
};
-static long spell_suggest_timeout = 5000;
+static int spell_suggest_timeout = 5000;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spellsuggest.c.generated.h"
@@ -243,7 +241,7 @@ static long spell_suggest_timeout = 5000;
/// Returns true when the sequence of flags in "compflags" plus "flag" can
/// possibly form a valid compounded word. This also checks the COMPOUNDRULE
/// lines if they don't contain wildcards.
-static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag)
+static bool can_be_compound(trystate_T *sp, slang_T *slang, uint8_t *compflags, int flag)
{
// If the flag doesn't appear in sl_compstartflags or sl_compallflags
// then it can't possibly compound.
@@ -256,7 +254,7 @@ static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, i
// possibly can form a match with COMPOUNDRULE patterns. This only
// makes sense when we have two or more words.
if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) {
- compflags[sp->ts_complen] = (char_u)flag;
+ compflags[sp->ts_complen] = (uint8_t)flag;
compflags[sp->ts_complen + 1] = NUL;
bool v = match_compoundrule(slang, compflags + sp->ts_compsplit);
compflags[sp->ts_complen] = NUL;
@@ -269,18 +267,17 @@ static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, i
/// Adjust the score of common words.
///
/// @param split word was split, less bonus
-static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split)
+static int score_wordcount_adj(slang_T *slang, int score, char *word, bool split)
{
- wordcount_T *wc;
int bonus;
int newscore;
- hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word);
+ hashitem_T *hi = hash_find(&slang->sl_wordcount, word);
if (HASHITEM_EMPTY(hi)) {
return score;
}
- wc = HI2WC(hi);
+ wordcount_T *wc = HI2WC(hi);
if (wc->wc_count < SCORE_THRES2) {
bonus = SCORE_COMMON1;
} else if (wc->wc_count < SCORE_THRES3) {
@@ -302,24 +299,21 @@ static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool spl
/// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a
/// capital. So that make_case_word() can turn WOrd into Word.
/// Add ALLCAP for "WOrD".
-static int badword_captype(char_u *word, char_u *end)
+static int badword_captype(char *word, char *end)
FUNC_ATTR_NONNULL_ALL
{
- int flags = captype((char *)word, (char *)end);
- int c;
- int l, u;
- bool first;
- char_u *p;
+ int flags = captype(word, end);
if (!(flags & WF_KEEPCAP)) {
return flags;
}
// Count the number of UPPER and lower case letters.
- l = u = 0;
- first = false;
- for (p = word; p < end; MB_PTR_ADV(p)) {
- c = utf_ptr2char((char *)p);
+ int l = 0;
+ int u = 0;
+ bool first = false;
+ for (char *p = word; p < end; MB_PTR_ADV(p)) {
+ int c = utf_ptr2char(p);
if (SPELL_ISUPPER(c)) {
u++;
if (p == word) {
@@ -351,13 +345,12 @@ static int badword_captype(char_u *word, char_u *end)
/// "pp" points to the bytes and is advanced over it.
///
/// @return the offset.
-static int bytes2offset(char_u **pp)
+static int bytes2offset(char **pp)
{
- char_u *p = *pp;
+ uint8_t *p = (uint8_t *)(*pp);
int nr;
- int c;
- c = *p++;
+ int c = *p++;
if ((c & 0x80) == 0x00) { // 1 byte
nr = c - 1;
} else if ((c & 0xc0) == 0x80) { // 2 bytes
@@ -374,7 +367,7 @@ static int bytes2offset(char_u **pp)
nr = nr * 255 + (*p++ - 1);
}
- *pp = p;
+ *pp = (char *)p;
return nr;
}
@@ -392,24 +385,22 @@ static int sps_limit = 9999; ///< max nr of suggestions given
/// Sets "sps_flags" and "sps_limit".
int spell_check_sps(void)
{
- char *p;
- char *s;
char buf[MAXPATHL];
- int f;
sps_flags = 0;
sps_limit = 9999;
- for (p = p_sps; *p != NUL;) {
- copy_option_part(&p, (char *)buf, MAXPATHL, ",");
+ for (char *p = p_sps; *p != NUL;) {
+ copy_option_part(&p, buf, MAXPATHL, ",");
- f = 0;
+ int f = 0;
if (ascii_isdigit(*buf)) {
- s = (char *)buf;
+ char *s = buf;
sps_limit = getdigits_int(&s, true, 0);
if (*s != NUL && !ascii_isdigit(*s)) {
f = -1;
}
+ // Note: Keep this in sync with p_sps_values.
} else if (strcmp(buf, "best") == 0) {
f = SPS_BEST;
} else if (strcmp(buf, "fast") == 0) {
@@ -447,15 +438,11 @@ int spell_check_sps(void)
/// When "count" is non-zero use that suggestion.
void spell_suggest(int count)
{
- char *line;
pos_T prev_cursor = curwin->w_cursor;
char wcopy[MAXWLEN + 2];
- char_u *p;
- int c;
suginfo_T sug;
suggest_T *stp;
int mouse_used;
- int need_cap;
int limit;
int selected = count;
int badlen = 0;
@@ -463,7 +450,7 @@ void spell_suggest(int count)
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -488,7 +475,7 @@ void spell_suggest(int count)
badlen++;
end_visual_mode();
// make sure we don't include the NUL at the end of the line
- line = get_cursor_line_ptr();
+ char *line = get_cursor_line_ptr();
if (badlen > (int)strlen(line) - (int)curwin->w_cursor.col) {
badlen = (int)strlen(line) - (int)curwin->w_cursor.col;
}
@@ -498,31 +485,31 @@ void spell_suggest(int count)
// No bad word or it starts after the cursor: use the word under the
// cursor.
curwin->w_cursor = prev_cursor;
- line = get_cursor_line_ptr();
- p = (char_u *)line + curwin->w_cursor.col;
+ char *line = get_cursor_line_ptr();
+ char *p = line + curwin->w_cursor.col;
// Backup to before start of word.
- while (p > (char_u *)line && spell_iswordp_nmw((char *)p, curwin)) {
+ while (p > line && spell_iswordp_nmw(p, curwin)) {
MB_PTR_BACK(line, p);
}
// Forward to start of word.
- while (*p != NUL && !spell_iswordp_nmw((char *)p, curwin)) {
+ while (*p != NUL && !spell_iswordp_nmw(p, curwin)) {
MB_PTR_ADV(p);
}
- if (!spell_iswordp_nmw((char *)p, curwin)) { // No word found.
+ if (!spell_iswordp_nmw(p, curwin)) { // No word found.
beep_flush();
return;
}
- curwin->w_cursor.col = (colnr_T)(p - (char_u *)line);
+ curwin->w_cursor.col = (colnr_T)(p - line);
}
// Get the word and its length.
// Figure out if the word should be capitalised.
- need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col);
+ int need_cap = check_need_cap(curwin, curwin->w_cursor.lnum, curwin->w_cursor.col);
// Make a copy of current line since autocommands may free the line.
- line = xstrdup(get_cursor_line_ptr());
+ char *line = xstrdup(get_cursor_line_ptr());
spell_suggest_timeout = 5000;
// Get the list of suggestions. Limit to 'lines' - 2 or the number in
@@ -532,36 +519,32 @@ void spell_suggest(int count)
} else {
limit = sps_limit;
}
- spell_find_suggest((char_u *)line + curwin->w_cursor.col, badlen, &sug, limit,
+ spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit,
true, need_cap, true);
if (GA_EMPTY(&sug.su_ga)) {
- msg(_("Sorry, no suggestions"));
+ msg(_("Sorry, no suggestions"), 0);
} else if (count > 0) {
if (count > sug.su_ga.ga_len) {
- smsg(_("Sorry, only %" PRId64 " suggestions"),
+ smsg(0, _("Sorry, only %" PRId64 " suggestions"),
(int64_t)sug.su_ga.ga_len);
}
} else {
// When 'rightleft' is set the list is drawn right-left.
cmdmsg_rl = curwin->w_p_rl;
- if (cmdmsg_rl) {
- msg_col = Columns - 1;
- }
// List the suggestions.
msg_start();
msg_row = Rows - 1; // for when 'cmdheight' > 1
lines_left = Rows; // avoid more prompt
- vim_snprintf(IObuff, IOSIZE, _("Change \"%.*s\" to:"),
- sug.su_badlen, sug.su_badptr);
- if (cmdmsg_rl && strncmp(IObuff, "Change", 6) == 0) {
+ char *fmt = _("Change \"%.*s\" to:");
+ if (cmdmsg_rl && strncmp(fmt, "Change", 6) == 0) {
// And now the rabbit from the high hat: Avoid showing the
// untranslated message rightleft.
- vim_snprintf(IObuff, IOSIZE, ":ot \"%.*s\" egnahC",
- sug.su_badlen, sug.su_badptr);
+ fmt = ":ot \"%.*s\" egnahC";
}
- msg_puts((const char *)IObuff);
+ vim_snprintf(IObuff, IOSIZE, fmt, sug.su_badlen, sug.su_badptr);
+ msg_puts(IObuff);
msg_clr_eos();
msg_putchar('\n');
@@ -578,18 +561,18 @@ void spell_suggest(int count)
}
vim_snprintf(IObuff, IOSIZE, "%2d", i + 1);
if (cmdmsg_rl) {
- rl_mirror(IObuff);
+ rl_mirror_ascii(IObuff, NULL);
}
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
vim_snprintf(IObuff, IOSIZE, " \"%s\"", wcopy);
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
// The word may replace more than "su_badlen".
if (sug.su_badlen < stp->st_orglen) {
vim_snprintf(IObuff, IOSIZE, _(" < \"%.*s\""),
stp->st_orglen, sug.su_badptr);
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
}
if (p_verbose > 0) {
@@ -604,10 +587,10 @@ void spell_suggest(int count)
}
if (cmdmsg_rl) {
// Mirror the numbers, but keep the leading space.
- rl_mirror(IObuff + 1);
+ rl_mirror_ascii(IObuff + 1, NULL);
}
msg_advance(30);
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
}
msg_putchar('\n');
}
@@ -650,8 +633,8 @@ void spell_suggest(int count)
}
// Replace the word.
- p = xmalloc(strlen(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1);
- c = (int)(sug.su_badptr - line);
+ char *p = xmalloc(strlen(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1);
+ int c = (int)(sug.su_badptr - line);
memmove(p, line, (size_t)c);
STRCPY(p + c, stp->st_word);
STRCAT(p, sug.su_badptr + stp->st_orglen);
@@ -659,12 +642,12 @@ void spell_suggest(int count)
// For redo we use a change-word command.
ResetRedobuff();
AppendToRedobuff("ciw");
- AppendToRedobuffLit((char *)p + c,
+ AppendToRedobuffLit(p + c,
stp->st_wordlen + sug.su_badlen - stp->st_orglen);
AppendCharToRedobuff(ESC);
// "p" may be freed here
- ml_replace(curwin->w_cursor.lnum, (char *)p, false);
+ ml_replace(curwin->w_cursor.lnum, p, false);
curwin->w_cursor.col = c;
inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen);
@@ -685,23 +668,21 @@ void spell_suggest(int count)
void spell_suggest_list(garray_T *gap, char *word, int maxcount, bool need_cap, bool interactive)
{
suginfo_T sug;
- suggest_T *stp;
- char_u *wcopy;
- spell_find_suggest((char_u *)word, 0, &sug, maxcount, false, need_cap, interactive);
+ spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive);
// Make room in "gap".
- ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1);
+ ga_init(gap, sizeof(char *), sug.su_ga.ga_len + 1);
ga_grow(gap, sug.su_ga.ga_len);
for (int i = 0; i < sug.su_ga.ga_len; i++) {
- stp = &SUG(sug.su_ga, i);
+ suggest_T *stp = &SUG(sug.su_ga, i);
// The suggested word may replace only part of "word", add the not
// replaced part.
- wcopy = xmalloc((size_t)stp->st_wordlen + strlen(sug.su_badptr + stp->st_orglen) + 1);
+ char *wcopy = xmalloc((size_t)stp->st_wordlen + strlen(sug.su_badptr + stp->st_orglen) + 1);
STRCPY(wcopy, stp->st_word);
STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen);
- ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy;
+ ((char **)gap->ga_data)[gap->ga_len++] = wcopy;
}
spell_find_cleanup(&sug);
@@ -716,17 +697,13 @@ void spell_suggest_list(garray_T *gap, char *word, int maxcount, bool need_cap,
/// @param badlen length of bad word or 0 if unknown
/// @param banbadword don't include badword in suggestions
/// @param need_cap word should start with capital
-static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount,
+static void spell_find_suggest(char *badptr, int badlen, suginfo_T *su, int maxcount,
bool banbadword, bool need_cap, bool interactive)
{
hlf_T attr = HLF_COUNT;
char buf[MAXPATHL];
- char *p;
bool do_combine = false;
- char *sps_copy;
static bool expr_busy = false;
- int c;
- langp_T *lp;
bool did_intern = false;
// Set the info in "*su".
@@ -738,7 +715,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
}
hash_init(&su->su_banned);
- su->su_badptr = (char *)badptr;
+ su->su_badptr = badptr;
if (badlen != 0) {
su->su_badlen = badlen;
} else {
@@ -752,7 +729,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
if (su->su_badlen >= MAXWLEN) {
su->su_badlen = MAXWLEN - 1; // just in case
}
- xstrlcpy((char *)su->su_badword, su->su_badptr, (size_t)su->su_badlen + 1);
+ xstrlcpy(su->su_badword, su->su_badptr, (size_t)su->su_badlen + 1);
(void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword,
MAXWLEN);
@@ -762,8 +739,8 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
su->su_fbadword[su->su_badlen] = NUL;
// get caps flags for bad word
- su->su_badflags = badword_captype((char_u *)su->su_badptr,
- (char_u *)su->su_badptr + su->su_badlen);
+ su->su_badflags = badword_captype(su->su_badptr,
+ su->su_badptr + su->su_badlen);
if (need_cap) {
su->su_badflags |= WF_ONECAP;
}
@@ -773,7 +750,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
// using multiple files for one language, it's not that bad when mixing
// languages (e.g., "pl,en").
for (int i = 0; i < curbuf->b_s.b_langp.ga_len; i++) {
- lp = LANGP_ENTRY(curbuf->b_s.b_langp, i);
+ langp_T *lp = LANGP_ENTRY(curbuf->b_s.b_langp, i);
if (lp->lp_sallang != NULL) {
su->su_sallang = lp->lp_sallang;
break;
@@ -783,46 +760,46 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
// Soundfold the bad word with the default sound folding, so that we don't
// have to do this many times.
if (su->su_sallang != NULL) {
- spell_soundfold(su->su_sallang, (char *)su->su_fbadword, true,
- (char *)su->su_sal_badword);
+ spell_soundfold(su->su_sallang, su->su_fbadword, true,
+ su->su_sal_badword);
}
// If the word is not capitalised and spell_check() doesn't consider the
// word to be bad then it might need to be capitalised. Add a suggestion
// for that.
- c = utf_ptr2char(su->su_badptr);
+ int c = utf_ptr2char(su->su_badptr);
if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) {
- make_case_word((char *)su->su_badword, buf, WF_ONECAP);
- add_suggestion(su, &su->su_ga, (char *)buf, su->su_badlen, SCORE_ICASE,
+ make_case_word(su->su_badword, buf, WF_ONECAP);
+ add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE,
0, true, su->su_sallang, false);
}
// Ban the bad word itself. It may appear in another region.
if (banbadword) {
- add_banned(su, (char *)su->su_badword);
+ add_banned(su, su->su_badword);
}
// Make a copy of 'spellsuggest', because the expression may change it.
- sps_copy = xstrdup(p_sps);
+ char *sps_copy = xstrdup(p_sps);
// Loop over the items in 'spellsuggest'.
- for (p = sps_copy; *p != NUL;) {
- copy_option_part(&p, (char *)buf, MAXPATHL, ",");
+ for (char *p = sps_copy; *p != NUL;) {
+ copy_option_part(&p, buf, MAXPATHL, ",");
if (strncmp(buf, "expr:", 5) == 0) {
// Evaluate an expression. Skip this when called recursively,
// when using spellsuggest() in the expression.
if (!expr_busy) {
expr_busy = true;
- spell_suggest_expr(su, (char_u *)buf + 5);
+ spell_suggest_expr(su, buf + 5);
expr_busy = false;
}
} else if (strncmp(buf, "file:", 5) == 0) {
// Use list of suggestions in a file.
- spell_suggest_file(su, (char_u *)buf + 5);
+ spell_suggest_file(su, buf + 5);
} else if (strncmp(buf, "timeout:", 8) == 0) {
// Limit the time searching for suggestions.
- spell_suggest_timeout = atol((char *)buf + 8);
+ spell_suggest_timeout = atoi(buf + 8);
} else if (!did_intern) {
// Use internal method once.
spell_suggest_intern(su, interactive);
@@ -843,21 +820,20 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
}
/// Find suggestions by evaluating expression "expr".
-static void spell_suggest_expr(suginfo_T *su, char_u *expr)
+static void spell_suggest_expr(suginfo_T *su, char *expr)
{
- int score;
const char *p;
// The work is split up in a few parts to avoid having to export
// suginfo_T.
// First evaluate the expression and get the resulting list.
- list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr);
+ list_T *const list = eval_spell_expr(su->su_badword, expr);
if (list != NULL) {
// Loop over the items in the list.
TV_LIST_ITER(list, li, {
if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
// Get the word and the score from the items.
- score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p);
+ int score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p);
if (score >= 0 && score <= su->su_maxscore) {
add_suggestion(su, &su->su_ga, p, su->su_badlen,
score, 0, true, su->su_sallang, false);
@@ -873,43 +849,41 @@ static void spell_suggest_expr(suginfo_T *su, char_u *expr)
}
/// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'.
-static void spell_suggest_file(suginfo_T *su, char_u *fname)
+static void spell_suggest_file(suginfo_T *su, char *fname)
{
- FILE *fd;
- char_u line[MAXWLEN * 2];
- char_u *p;
+ char line[MAXWLEN * 2];
int len;
- char_u cword[MAXWLEN];
+ char cword[MAXWLEN];
// Open the file.
- fd = os_fopen((char *)fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return;
}
// Read it line by line.
- while (!vim_fgets((char *)line, MAXWLEN * 2, fd) && !got_int) {
+ while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) {
line_breakcheck();
- p = (char_u *)vim_strchr((char *)line, '/');
+ char *p = vim_strchr(line, '/');
if (p == NULL) {
continue; // No Tab found, just skip the line.
}
*p++ = NUL;
if (STRICMP(su->su_badword, line) == 0) {
// Match! Isolate the good word, until CR or NL.
- for (len = 0; p[len] >= ' '; len++) {}
+ for (len = 0; (uint8_t)p[len] >= ' '; len++) {}
p[len] = NUL;
// If the suggestion doesn't have specific case duplicate the case
// of the bad word.
- if (captype((char *)p, NULL) == 0) {
- make_case_word((char *)p, (char *)cword, su->su_badflags);
+ if (captype(p, NULL) == 0) {
+ make_case_word(p, cword, su->su_badflags);
p = cword;
}
- add_suggestion(su, &su->su_ga, (char *)p, su->su_badlen,
+ add_suggestion(su, &su->su_ga, p, su->su_badlen,
SCORE_FILE, 0, true, su->su_sallang, false);
}
}
@@ -1013,24 +987,23 @@ static void spell_find_cleanup(suginfo_T *su)
/// Try finding suggestions by recognizing specific situations.
static void suggest_try_special(suginfo_T *su)
{
- char c;
- char_u word[MAXWLEN];
+ char word[MAXWLEN];
// Recognize a word that is repeated: "the the".
- char *p = skiptowhite((char *)su->su_fbadword);
- size_t len = (size_t)(p - (char *)su->su_fbadword);
+ char *p = skiptowhite(su->su_fbadword);
+ size_t len = (size_t)(p - su->su_fbadword);
p = skipwhite(p);
if (strlen(p) == len && strncmp(su->su_fbadword, p, len) == 0) {
// Include badflags: if the badword is onecap or allcap
// use that for the goodword too: "The the" -> "The".
- c = su->su_fbadword[len];
+ char c = su->su_fbadword[len];
su->su_fbadword[len] = NUL;
- make_case_word(su->su_fbadword, (char *)word, su->su_badflags);
+ make_case_word(su->su_fbadword, word, su->su_badflags);
su->su_fbadword[len] = c;
// Give a soundalike score of 0, compute the score as if deleting one
// character.
- add_suggestion(su, &su->su_ga, (char *)word, su->su_badlen,
+ add_suggestion(su, &su->su_ga, word, su->su_badlen,
RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false);
}
}
@@ -1084,16 +1057,13 @@ static void prof_report(char *name)
static void suggest_try_change(suginfo_T *su)
{
char fword[MAXWLEN]; // copy of the bad word, case-folded
- int n;
- char *p;
- langp_T *lp;
// We make a copy of the case-folded bad word, so that we can modify it
// to find matches (esp. REP items). Append some more text, changing
// chars after the bad word may help.
STRCPY(fword, su->su_fbadword);
- n = (int)strlen(fword);
- p = su->su_badptr + su->su_badlen;
+ int n = (int)strlen(fword);
+ char *p = su->su_badptr + su->su_badlen;
(void)spell_casefold(curwin, p, (int)strlen(p), fword + n, MAXWLEN - n);
// Make sure the resulting text is not longer than the original text.
@@ -1103,7 +1073,7 @@ static void suggest_try_change(suginfo_T *su)
}
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
// If reloading a spell file fails it's still in the list but
// everything has been cleared.
@@ -1164,45 +1134,36 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// words and split word. NUL terminated
// when going deeper but not when coming
// back.
- char_u compflags[MAXWLEN]; // compound flags, one for each word
- trystate_T *sp;
- int newscore;
- int score;
- char_u *byts, *fbyts, *pbyts;
+ uint8_t compflags[MAXWLEN]; // compound flags, one for each word
+ uint8_t *byts, *fbyts, *pbyts;
idx_T *idxs, *fidxs, *pidxs;
- int depth;
int c, c2, c3;
int n = 0;
- int flags;
garray_T *gap;
idx_T arridx;
- int len;
- char *p;
- fromto_T *ftp;
- int fl = 0, tl;
+ int fl = 0;
+ int tl;
int repextra = 0; // extra bytes in fword[] from REP item
slang_T *slang = lp->lp_slang;
- int fword_ends;
bool goodword_ends;
#ifdef DEBUG_TRIEWALK
// Stores the name of the change made at each level.
- char_u changename[MAXWLEN][80];
+ uint8_t changename[MAXWLEN][80];
#endif
int breakcheckcount = 1000;
- bool compound_ok;
// Go through the whole case-fold tree, try changes at each node.
// "tword[]" contains the word collected from nodes in the tree.
// "fword[]" the word we are trying to match with (initially the bad
// word).
- depth = 0;
- sp = &stack[0];
- CLEAR_POINTER(sp); // -V1068
+ int depth = 0;
+ trystate_T *sp = &stack[0];
+ CLEAR_POINTER(sp);
sp->ts_curi = 1;
if (soundfold) {
// Going through the soundfold tree.
- byts = fbyts = (char_u *)slang->sl_sbyts;
+ byts = fbyts = slang->sl_sbyts;
idxs = fidxs = slang->sl_sidxs;
pbyts = NULL;
pidxs = NULL;
@@ -1211,9 +1172,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
} else {
// When there are postponed prefixes we need to use these first. At
// the end of the prefix we continue in the case-fold tree.
- fbyts = (char_u *)slang->sl_fbyts;
+ fbyts = slang->sl_fbyts;
fidxs = slang->sl_fidxs;
- pbyts = (char_u *)slang->sl_pbyts;
+ pbyts = slang->sl_pbyts;
pidxs = slang->sl_pidxs;
if (pbyts != NULL) {
byts = pbyts;
@@ -1230,7 +1191,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// The loop may take an indefinite amount of time. Break out after some
// time.
- proftime_T time_limit;
+ proftime_T time_limit = 0;
if (spell_suggest_timeout > 0) {
time_limit = profile_setlimit(spell_suggest_timeout);
}
@@ -1248,7 +1209,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Start of node: Deal with NUL bytes, which means
// tword[] may end here.
arridx = sp->ts_arridx; // current node in the tree
- len = byts[arridx]; // bytes in this node
+ int len = byts[arridx]; // bytes in this node
arridx += sp->ts_curi; // index of current byte
if (sp->ts_prefixdepth == PFD_PREFIXTREE) {
@@ -1260,7 +1221,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
n = (int)sp->ts_state;
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_ENDNUL;
- sp->ts_save_badflags = (char_u)su->su_badflags;
+ sp->ts_save_badflags = (uint8_t)su->su_badflags;
// At end of a prefix or at start of prefixtree: check for
// following word.
@@ -1268,16 +1229,16 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Set su->su_badflags to the caps type at this position.
// Use the caps type until here for the prefix itself.
n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
- flags = badword_captype((char_u *)su->su_badptr, (char_u *)su->su_badptr + n);
- su->su_badflags = badword_captype((char_u *)su->su_badptr + n,
- (char_u *)su->su_badptr + su->su_badlen);
+ int flags = badword_captype(su->su_badptr, su->su_badptr + n);
+ su->su_badflags = badword_captype(su->su_badptr + n,
+ su->su_badptr + su->su_badlen);
#ifdef DEBUG_TRIEWALK
sprintf(changename[depth], "prefix"); // NOLINT(runtime/printf)
#endif
go_deeper(stack, depth, 0);
depth++;
sp = &stack[depth];
- sp->ts_prefixdepth = (char_u)(depth - 1);
+ sp->ts_prefixdepth = (uint8_t)(depth - 1);
byts = fbyts;
idxs = fidxs;
sp->ts_arridx = 0;
@@ -1287,7 +1248,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
tword[sp->ts_twordlen] = NUL;
make_case_word(tword + sp->ts_splitoff,
preword + sp->ts_prewordlen, flags);
- sp->ts_prewordlen = (char_u)strlen(preword);
+ sp->ts_prewordlen = (uint8_t)strlen(preword);
sp->ts_splitoff = sp->ts_twordlen;
}
break;
@@ -1297,24 +1258,24 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Past bytes in node and/or past NUL bytes.
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_ENDNUL;
- sp->ts_save_badflags = (char_u)su->su_badflags;
+ sp->ts_save_badflags = (uint8_t)su->su_badflags;
break;
}
// End of word in tree.
sp->ts_curi++; // eat one NUL byte
- flags = (int)idxs[arridx];
+ int flags = (int)idxs[arridx];
// Skip words with the NOSUGGEST flag.
if (flags & WF_NOSUGGEST) {
break;
}
- fword_ends = (fword[sp->ts_fidx] == NUL
- || (soundfold
- ? ascii_iswhite(fword[sp->ts_fidx])
- : !spell_iswordp(fword + sp->ts_fidx, curwin)));
+ int fword_ends = (fword[sp->ts_fidx] == NUL
+ || (soundfold
+ ? ascii_iswhite(fword[sp->ts_fidx])
+ : !spell_iswordp(fword + sp->ts_fidx, curwin)));
tword[sp->ts_twordlen] = NUL;
if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
@@ -1356,8 +1317,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
goodword_ends = true;
}
- p = NULL;
- compound_ok = true;
+ char *p = NULL;
+ bool compound_ok = true;
if (sp->ts_complen > sp->ts_compsplit) {
if (slang->sl_nobreak) {
// There was a word before this word. When there was no
@@ -1370,12 +1331,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
tword + sp->ts_splitoff,
(size_t)(sp->ts_fidx - sp->ts_splitfidx)) == 0) {
preword[sp->ts_prewordlen] = NUL;
- newscore = score_wordcount_adj(slang, sp->ts_score,
- (char_u *)preword + sp->ts_prewordlen,
- sp->ts_prewordlen > 0);
+ int newscore = score_wordcount_adj(slang, sp->ts_score,
+ preword + sp->ts_prewordlen,
+ sp->ts_prewordlen > 0);
// Add the suggestion if the score isn't too bad.
if (newscore <= su->su_maxscore) {
- add_suggestion(su, &su->su_ga, (char *)preword,
+ add_suggestion(su, &su->su_ga, preword,
sp->ts_splitfidx - repextra,
newscore, 0, false,
lp->lp_sallang, false);
@@ -1400,7 +1361,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
break;
}
- compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
+ compflags[sp->ts_complen] = (uint8_t)((unsigned)flags >> 24);
compflags[sp->ts_complen + 1] = NUL;
xstrlcpy(preword + sp->ts_prewordlen,
tword + sp->ts_splitoff,
@@ -1437,7 +1398,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff);
} else if (flags & WF_KEEPCAP) {
// Must find the word in the keep-case tree.
- find_keepcap_word(slang, (char *)tword + sp->ts_splitoff, preword + sp->ts_prewordlen);
+ find_keepcap_word(slang, tword + sp->ts_splitoff, preword + sp->ts_prewordlen);
} else {
// Include badflags: If the badword is onecap or allcap
// use that for the goodword too. But if the badword is
@@ -1465,8 +1426,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
break;
}
if ((sp->ts_complen == sp->ts_compsplit
- && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen))
- || WAS_BANNED(su, (char *)preword)) {
+ && WAS_BANNED(su, preword + sp->ts_prewordlen))
+ || WAS_BANNED(su, preword)) {
if (slang->sl_compprog == NULL) {
break;
}
@@ -1475,7 +1436,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
}
- newscore = 0;
+ int newscore = 0;
if (!soundfold) { // soundfold words don't have flags
if ((flags & WF_REGION)
&& (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) {
@@ -1502,9 +1463,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
int j;
// print the stack of changes that brought us here
- smsg("------ %s -------", fword);
+ smsg(0, "------ %s -------", fword);
for (j = 0; j < depth; j++) {
- smsg("%s", changename[j]);
+ smsg(0, "%s", changename[j]);
}
}
#endif
@@ -1526,14 +1487,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
// Give a bonus to words seen before.
- score = score_wordcount_adj(slang,
- sp->ts_score + newscore,
- (char_u *)preword + sp->ts_prewordlen,
- sp->ts_prewordlen > 0);
+ int score = score_wordcount_adj(slang,
+ sp->ts_score + newscore,
+ preword + sp->ts_prewordlen,
+ sp->ts_prewordlen > 0);
// Add the suggestion if the score isn't too bad.
if (score <= su->su_maxscore) {
- add_suggestion(su, &su->su_ga, (char *)preword,
+ add_suggestion(su, &su->su_ga, preword,
sp->ts_fidx - repextra,
score, 0, false, lp->lp_sallang, false);
@@ -1546,7 +1507,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
preword + sp->ts_prewordlen,
c == 0 ? WF_ALLCAP : 0);
- add_suggestion(su, &su->su_ga, (char *)preword,
+ add_suggestion(su, &su->su_ga, preword,
sp->ts_fidx - repextra,
score + SCORE_ICASE, 0, false,
lp->lp_sallang, false);
@@ -1598,7 +1559,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
< slang->sl_compmax)
&& (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) {
try_compound = true;
- compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
+ compflags[sp->ts_complen] = (uint8_t)((unsigned)flags >> 24);
compflags[sp->ts_complen + 1] = NUL;
}
@@ -1617,7 +1578,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
sp->ts_curi--; // do the same NUL again
compflags[sp->ts_complen] = NUL;
} else {
- sp->ts_flags &= (char_u) ~TSF_DIDSPLIT;
+ sp->ts_flags &= (uint8_t) ~TSF_DIDSPLIT;
}
if (try_split || try_compound) {
@@ -1647,7 +1608,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Give a bonus to words seen before.
newscore = score_wordcount_adj(slang, newscore,
- (char_u *)preword + sp->ts_prewordlen, true);
+ preword + sp->ts_prewordlen, true);
}
if (TRY_DEEPER(su, stack, depth, newscore)) {
@@ -1662,7 +1623,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
#endif
// Save things to be restored at STATE_SPLITUNDO.
- sp->ts_save_badflags = (char_u)su->su_badflags;
+ sp->ts_save_badflags = (uint8_t)su->su_badflags;
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_SPLITUNDO;
@@ -1673,7 +1634,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
if (!try_compound && !fword_ends) {
STRCAT(preword, " ");
}
- sp->ts_prewordlen = (char_u)strlen(preword);
+ sp->ts_prewordlen = (uint8_t)strlen(preword);
sp->ts_splitoff = sp->ts_twordlen;
sp->ts_splitfidx = sp->ts_fidx;
@@ -1694,12 +1655,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
if (fword_ends) {
// Copy the skipped character to preword.
memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l);
- sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l);
+ sp->ts_prewordlen = (uint8_t)(sp->ts_prewordlen + l);
preword[sp->ts_prewordlen] = NUL;
} else {
sp->ts_score -= SCORE_SPLIT - SCORE_SUBST;
}
- sp->ts_fidx = (char_u)(sp->ts_fidx + l);
+ sp->ts_fidx = (uint8_t)(sp->ts_fidx + l);
}
// When compounding include compound flag in
@@ -1715,8 +1676,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// set su->su_badflags to the caps type at this
// position
n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
- su->su_badflags = badword_captype((char_u *)su->su_badptr + n,
- (char_u *)su->su_badptr + su->su_badlen);
+ su->su_badflags = badword_captype(su->su_badptr + n,
+ su->su_badptr + su->su_badlen);
// Restart at top of the tree.
sp->ts_arridx = 0;
@@ -1824,7 +1785,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// First byte.
sp->ts_tcharidx = 0;
sp->ts_tcharlen = MB_BYTE2LEN(c);
- sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1);
+ sp->ts_fcharstart = (uint8_t)(sp->ts_fidx - 1);
sp->ts_isdiff = (newscore != 0)
? DIFF_YES : DIFF_NONE;
} else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) {
@@ -1837,13 +1798,13 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
if (sp->ts_isdiff == DIFF_YES) {
// Correct ts_fidx for the byte length of the
// character (we didn't check that before).
- sp->ts_fidx = (char_u)(sp->ts_fcharstart
- + utfc_ptr2len(fword + sp->ts_fcharstart));
+ sp->ts_fidx = (uint8_t)(sp->ts_fcharstart
+ + utfc_ptr2len(fword + sp->ts_fcharstart));
// For changing a composing character adjust
// the score from SCORE_SUBST to
// SCORE_SUBCOMP.
- if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen
+ if (utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen
- sp->ts_tcharlen))
&& utf_iscomposing(utf_ptr2char(fword
+ sp->ts_fcharstart))) {
@@ -1851,7 +1812,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
} else if (!soundfold
&& slang->sl_has_map
&& similar_chars(slang,
- utf_ptr2char((char *)tword + sp->ts_twordlen -
+ utf_ptr2char(tword + sp->ts_twordlen -
sp->ts_tcharlen),
utf_ptr2char(fword + sp->ts_fcharstart))) {
// For a similar character adjust score from
@@ -1860,7 +1821,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
} else if (sp->ts_isdiff == DIFF_INSERT
&& sp->ts_twordlen > sp->ts_tcharlen) {
- p = (char *)tword + sp->ts_twordlen - sp->ts_tcharlen;
+ p = tword + sp->ts_twordlen - sp->ts_tcharlen;
c = utf_ptr2char(p);
if (utf_iscomposing(c)) {
// Inserting a composing char doesn't
@@ -1926,7 +1887,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// results.
c = utf_ptr2char(fword + sp->ts_fidx);
stack[depth].ts_fidx =
- (char_u)(stack[depth].ts_fidx + utfc_ptr2len(fword + sp->ts_fidx));
+ (uint8_t)(stack[depth].ts_fidx + utfc_ptr2len(fword + sp->ts_fidx));
if (utf_iscomposing(c)) {
stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
} else if (c == utf_ptr2char(fword + stack[depth].ts_fidx)) {
@@ -1948,7 +1909,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// skip over NUL bytes
n = sp->ts_arridx;
- for (;;) {
+ while (true) {
if (sp->ts_curi > byts[n]) {
// Only NUL bytes at this node, go to next state.
PROF_STORE(sp->ts_state)
@@ -2006,7 +1967,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// There are following bytes for the same character.
// We must find all bytes before trying
// delete/insert/swap/etc.
- sp->ts_tcharlen = (char_u)fl;
+ sp->ts_tcharlen = (uint8_t)fl;
sp->ts_tcharidx = 1;
sp->ts_isdiff = DIFF_INSERT;
}
@@ -2082,7 +2043,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
fl = utf_char2len(c2);
memmove(p, p + n, (size_t)fl);
utf_char2bytes(c, p + fl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + fl);
} else {
// If this swap doesn't work then SWAP3 won't either.
PROF_STORE(sp->ts_state)
@@ -2139,7 +2100,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
memmove(p, p + n + fl, (size_t)tl);
utf_char2bytes(c2, p + tl);
utf_char2bytes(c, p + fl + tl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + fl + tl);
} else {
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_REP_INI;
@@ -2187,7 +2148,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
fl += utf_ptr2len(p + n + fl);
memmove(p, p + n, (size_t)fl);
utf_char2bytes(c, p + fl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + fl);
} else {
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_REP_INI;
@@ -2224,7 +2185,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
tl = utf_ptr2len(p + n);
memmove(p + tl, p, (size_t)n);
utf_char2bytes(c, p);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + tl);
} else {
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_REP_INI;
@@ -2287,7 +2248,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
gap = &lp->lp_replang->sl_rep;
}
while (sp->ts_curi < gap->ga_len) {
- ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
+ fromto_T *ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
if (*ftp->ft_from != *p) {
// past possible matching entries
sp->ts_curi = (int16_t)gap->ga_len;
@@ -2310,11 +2271,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
fl = (int)strlen(ftp->ft_from);
tl = (int)strlen(ftp->ft_to);
if (fl != tl) {
- STRMOVE(p + tl, (char *)p + fl);
+ STRMOVE(p + tl, p + fl);
repextra += tl - fl;
}
memmove(p, ftp->ft_to, (size_t)tl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + tl);
stack[depth].ts_tcharlen = 0;
break;
}
@@ -2335,12 +2296,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
} else {
gap = &lp->lp_replang->sl_rep;
}
- ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
+ fromto_T *ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
fl = (int)strlen(ftp->ft_from);
tl = (int)strlen(ftp->ft_to);
p = fword + sp->ts_fidx;
if (fl != tl) {
- STRMOVE(p + fl, (char *)p + tl);
+ STRMOVE(p + fl, p + tl);
repextra -= tl - fl;
}
memmove(p, ftp->ft_from, (size_t)fl);
@@ -2387,7 +2348,6 @@ static void go_deeper(trystate_T *stack, int depth, int score_add)
static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
{
char uword[MAXWLEN]; // "fword" in upper-case
- int depth;
idx_T tryidx;
// The following arrays are used at each depth in the tree.
@@ -2397,13 +2357,9 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
int uwordidx[MAXWLEN];
int kwordlen[MAXWLEN];
- int flen, ulen;
int l;
- int len;
- int c;
- idx_T lo, hi, m;
- char_u *p;
- char_u *byts = (char_u *)slang->sl_kbyts; // array with bytes of the words
+ char *p;
+ uint8_t *byts = slang->sl_kbyts; // array with bytes of the words
idx_T *idxs = slang->sl_kidxs; // array with indexes
if (byts == NULL) {
@@ -2418,7 +2374,7 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
// Each character needs to be tried both case-folded and upper-case.
// All this gets very complicated if we keep in mind that changing case
// may change the byte length of a multi-byte character...
- depth = 0;
+ int depth = 0;
arridx[0] = 0;
round[0] = 0;
fwordidx[0] = 0;
@@ -2442,24 +2398,24 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
} else {
// round[depth] == 1: Try using the folded-case character.
// round[depth] == 2: Try using the upper-case character.
- flen = utf_ptr2len(fword + fwordidx[depth]);
- ulen = utf_ptr2len((char *)uword + uwordidx[depth]);
+ int flen = utf_ptr2len(fword + fwordidx[depth]);
+ int ulen = utf_ptr2len(uword + uwordidx[depth]);
if (round[depth] == 1) {
- p = (char_u *)fword + fwordidx[depth];
+ p = fword + fwordidx[depth];
l = flen;
} else {
- p = (char_u *)uword + uwordidx[depth];
+ p = uword + uwordidx[depth];
l = ulen;
}
for (tryidx = arridx[depth]; l > 0; l--) {
// Perform a binary search in the list of accepted bytes.
- len = byts[tryidx++];
- c = *p++;
- lo = tryidx;
- hi = tryidx + len - 1;
+ int len = byts[tryidx++];
+ int c = (uint8_t)(*p++);
+ idx_T lo = tryidx;
+ idx_T hi = tryidx + len - 1;
while (lo < hi) {
- m = (lo + hi) / 2;
+ idx_T m = (lo + hi) / 2;
if (byts[m] > c) {
hi = m - 1;
} else if (byts[m] < c) {
@@ -2511,31 +2467,26 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
/// su->su_sga.
static void score_comp_sal(suginfo_T *su)
{
- langp_T *lp;
- char_u badsound[MAXWLEN];
- int i;
- suggest_T *stp;
- suggest_T *sstp;
- int score;
+ char badsound[MAXWLEN];
ga_grow(&su->su_sga, su->su_ga.ga_len);
// Use the sound-folding of the first language that supports it.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
// soundfold the bad word
- spell_soundfold(lp->lp_slang, (char *)su->su_fbadword, true, (char *)badsound);
+ spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound);
- for (i = 0; i < su->su_ga.ga_len; i++) {
- stp = &SUG(su->su_ga, i);
+ for (int i = 0; i < su->su_ga.ga_len; i++) {
+ suggest_T *stp = &SUG(su->su_ga, i);
// Case-fold the suggested word, sound-fold it and compute the
// sound-a-like score.
- score = stp_sal_score(stp, su, lp->lp_slang, badsound);
+ int score = stp_sal_score(stp, su, lp->lp_slang, badsound);
if (score < SCORE_MAXMAX) {
// Add the suggestion.
- sstp = &SUG(su->su_sga, su->su_sga.ga_len);
+ suggest_T *sstp = &SUG(su->su_sga, su->su_sga.ga_len);
sstp->st_word = xstrdup(stp->st_word);
sstp->st_wordlen = stp->st_wordlen;
sstp->st_score = score;
@@ -2554,25 +2505,21 @@ static void score_comp_sal(suginfo_T *su)
static void score_combine(suginfo_T *su)
{
garray_T ga;
- garray_T *gap;
- langp_T *lp;
- suggest_T *stp;
char *p;
char badsound[MAXWLEN];
- int round;
slang_T *slang = NULL;
// Add the alternate score to su_ga.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
// soundfold the bad word
slang = lp->lp_slang;
- spell_soundfold(slang, (char *)su->su_fbadword, true, badsound);
+ spell_soundfold(slang, su->su_fbadword, true, badsound);
for (int i = 0; i < su->su_ga.ga_len; i++) {
- stp = &SUG(su->su_ga, i);
- stp->st_altscore = stp_sal_score(stp, su, slang, (char_u *)badsound);
+ suggest_T *stp = &SUG(su->su_ga, i);
+ stp->st_altscore = stp_sal_score(stp, su, slang, badsound);
if (stp->st_altscore == SCORE_MAXMAX) {
stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4;
} else {
@@ -2592,8 +2539,8 @@ static void score_combine(suginfo_T *su)
// Add the alternate score to su_sga.
for (int i = 0; i < su->su_sga.ga_len; i++) {
- stp = &SUG(su->su_sga, i);
- stp->st_altscore = spell_edit_score(slang, (char_u *)su->su_badword, (char_u *)stp->st_word);
+ suggest_T *stp = &SUG(su->su_sga, i);
+ stp->st_altscore = spell_edit_score(slang, su->su_badword, stp->st_word);
if (stp->st_score == SCORE_MAXMAX) {
stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8;
} else {
@@ -2612,12 +2559,12 @@ static void score_combine(suginfo_T *su)
ga_init(&ga, (int)sizeof(suginfo_T), 1);
ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len);
- stp = &SUG(ga, 0);
+ suggest_T *stp = &SUG(ga, 0);
for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; i++) {
// round 1: get a suggestion from su_ga
// round 2: get a suggestion from su_sga
- for (round = 1; round <= 2; round++) {
- gap = round == 1 ? &su->su_ga : &su->su_sga;
+ for (int round = 1; round <= 2; round++) {
+ garray_T *gap = round == 1 ? &su->su_ga : &su->su_sga;
if (i < gap->ga_len) {
// Don't add a word if it's already there.
p = SUG(*gap, i).st_word;
@@ -2654,23 +2601,21 @@ static void score_combine(suginfo_T *su)
/// badword.
///
/// @param badsound sound-folded badword
-static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound)
+static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char *badsound)
{
- char_u *p;
- char_u *pbad;
- char_u *pgood;
- char_u badsound2[MAXWLEN];
- char_u fword[MAXWLEN];
- char_u goodsound[MAXWLEN];
+ char *pbad;
+ char *pgood;
+ char badsound2[MAXWLEN];
+ char fword[MAXWLEN];
+ char goodsound[MAXWLEN];
char goodword[MAXWLEN];
- int lendiff;
- lendiff = su->su_badlen - stp->st_orglen;
+ int lendiff = su->su_badlen - stp->st_orglen;
if (lendiff >= 0) {
pbad = badsound;
} else {
// soundfold the bad word with more characters following
- (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, (char *)fword, MAXWLEN);
+ (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN);
// When joining two words the sound often changes a lot. E.g., "t he"
// sounds like "t h" while "the" sounds like "@". Avoid that by
@@ -2678,12 +2623,12 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *
// space.
if (ascii_iswhite(su->su_badptr[su->su_badlen])
&& *skiptowhite(stp->st_word) == NUL) {
- for (p = fword; *(p = (char_u *)skiptowhite((char *)p)) != NUL;) {
- STRMOVE(p, (char *)p + 1);
+ for (char *p = fword; *(p = skiptowhite(p)) != NUL;) {
+ STRMOVE(p, p + 1);
}
}
- spell_soundfold(slang, (char *)fword, true, (char *)badsound2);
+ spell_soundfold(slang, fword, true, badsound2);
pbad = badsound2;
}
@@ -2693,39 +2638,36 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *
STRCPY(goodword, stp->st_word);
xstrlcpy(goodword + stp->st_wordlen,
su->su_badptr + su->su_badlen - lendiff, (size_t)lendiff + 1);
- pgood = (char_u *)goodword;
+ pgood = goodword;
} else {
- pgood = (char_u *)stp->st_word;
+ pgood = stp->st_word;
}
// Sound-fold the word and compute the score for the difference.
- spell_soundfold(slang, (char *)pgood, false, (char *)goodsound);
+ spell_soundfold(slang, pgood, false, goodsound);
- return soundalike_score((char *)goodsound, (char *)pbad);
+ return soundalike_score(goodsound, pbad);
}
/// structure used to store soundfolded words that add_sound_suggest() has
/// handled already.
typedef struct {
int16_t sft_score; ///< lowest score used
- char_u sft_word[1]; ///< soundfolded word, actually longer
+ uint8_t sft_word[]; ///< soundfolded word
} sftword_T;
static sftword_T dumsft;
-#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft)))
+#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (uint8_t *)&dumsft)))
#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key)
/// Prepare for calling suggest_try_soundalike().
static void suggest_try_soundalike_prep(void)
{
- langp_T *lp;
- slang_T *slang;
-
// Do this for all languages that support sound folding and for which a
// .sug file has been loaded.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
// prepare the hashtable used by add_sound_suggest()
hash_init(&slang->sl_sounddone);
@@ -2737,18 +2679,16 @@ static void suggest_try_soundalike_prep(void)
/// Note: This doesn't support postponed prefixes.
static void suggest_try_soundalike(suginfo_T *su)
{
- char_u salword[MAXWLEN];
- langp_T *lp;
- slang_T *slang;
+ char salword[MAXWLEN];
// Do this for all languages that support sound folding and for which a
// .sug file has been loaded.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
// soundfold the bad word
- spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)salword);
+ spell_soundfold(slang, su->su_fbadword, true, salword);
// try all kinds of inserts/deletes/swaps/etc.
// TODO(vim): also soundfold the next words, so that we can try joining
@@ -2756,7 +2696,7 @@ static void suggest_try_soundalike(suginfo_T *su)
#ifdef SUGGEST_PROFILE
prof_init();
#endif
- suggest_trie_walk(su, lp, (char *)salword, true);
+ suggest_trie_walk(su, lp, salword, true);
#ifdef SUGGEST_PROFILE
prof_report("soundalike");
#endif
@@ -2767,20 +2707,15 @@ static void suggest_try_soundalike(suginfo_T *su)
/// Finish up after calling suggest_try_soundalike().
static void suggest_try_soundalike_finish(void)
{
- langp_T *lp;
- slang_T *slang;
- int todo;
- hashitem_T *hi;
-
// Do this for all languages that support sound folding and for which a
// .sug file has been loaded.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
// Free the info about handled words.
- todo = (int)slang->sl_sounddone.ht_used;
- for (hi = slang->sl_sounddone.ht_array; todo > 0; hi++) {
+ int todo = (int)slang->sl_sounddone.ht_used;
+ for (hashitem_T *hi = slang->sl_sounddone.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
xfree(HI2SFT(hi));
todo--;
@@ -2801,34 +2736,23 @@ static void suggest_try_soundalike_finish(void)
static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T *lp)
{
slang_T *slang = lp->lp_slang; // language for sound folding
- int sfwordnr;
- char_u *nrline;
- int orgnr;
- char_u theword[MAXWLEN];
+ char theword[MAXWLEN];
int i;
int wlen;
- char_u *byts;
- idx_T *idxs;
- int n;
- int wordcount;
+ uint8_t *byts;
int wc;
int goodscore;
- hash_T hash;
- hashitem_T *hi;
sftword_T *sft;
- int bc, gc;
- int limit;
// It's very well possible that the same soundfold word is found several
// times with different scores. Since the following is quite slow only do
// the words that have a better score than before. Use a hashtable to
// remember the words that have been done.
- hash = hash_hash(goodword);
+ hash_T hash = hash_hash(goodword);
const size_t goodword_len = strlen(goodword);
- hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len,
- hash);
+ hashitem_T *hi = hash_lookup(&slang->sl_sounddone, goodword, goodword_len, hash);
if (HASHITEM_EMPTY(hi)) {
- sft = xmalloc(sizeof(sftword_T) + goodword_len);
+ sft = xmalloc(offsetof(sftword_T, sft_word) + goodword_len + 1);
sft->sft_score = (int16_t)score;
memcpy(sft->sft_word, goodword, goodword_len + 1);
hash_add_item(&slang->sl_sounddone, hi, (char *)sft->sft_word, hash);
@@ -2841,26 +2765,26 @@ static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T
}
// Find the word nr in the soundfold tree.
- sfwordnr = soundfold_find(slang, (char_u *)goodword);
+ int sfwordnr = soundfold_find(slang, goodword);
if (sfwordnr < 0) {
internal_error("add_sound_suggest()");
return;
}
// Go over the list of good words that produce this soundfold word
- nrline = (char_u *)ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false);
- orgnr = 0;
+ char *nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1);
+ int orgnr = 0;
while (*nrline != NUL) {
// The wordnr was stored in a minimal nr of bytes as an offset to the
// previous wordnr.
orgnr += bytes2offset(&nrline);
- byts = (char_u *)slang->sl_fbyts;
- idxs = slang->sl_fidxs;
+ byts = slang->sl_fbyts;
+ idx_T *idxs = slang->sl_fidxs;
// Lookup the word "orgnr" one of the two tries.
- n = 0;
- wordcount = 0;
+ int n = 0;
+ int wordcount = 0;
for (wlen = 0; wlen < MAXWLEN - 3; wlen++) {
i = 1;
if (wordcount == orgnr && byts[n + 1] == NUL) {
@@ -2888,7 +2812,7 @@ static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T
wordcount += wc;
}
- theword[wlen] = byts[n + i];
+ theword[wlen] = (char)byts[n + i];
n = idxs[n + i];
}
badword:
@@ -2896,8 +2820,8 @@ badword:
// Go over the possible flags and regions.
for (; i <= byts[n] && byts[n + i] == NUL; i++) {
- char_u cword[MAXWLEN];
- char_u *p;
+ char cword[MAXWLEN];
+ char *p;
int flags = (int)idxs[n + i];
// Skip words with the NOSUGGEST flag
@@ -2907,13 +2831,13 @@ badword:
if (flags & WF_KEEPCAP) {
// Must find the word in the keep-case tree.
- find_keepcap_word(slang, (char *)theword, (char *)cword);
+ find_keepcap_word(slang, theword, cword);
p = cword;
} else {
flags |= su->su_badflags;
if ((flags & WF_CAPMASK) != 0) {
// Need to fix case according to "flags".
- make_case_word((char *)theword, (char *)cword, flags);
+ make_case_word(theword, cword, flags);
p = cword;
} else {
p = theword;
@@ -2924,7 +2848,7 @@ badword:
if (sps_flags & SPS_DOUBLE) {
// Add the suggestion if the score isn't too bad.
if (score <= su->su_maxscore) {
- add_suggestion(su, &su->su_sga, (char *)p, su->su_badlen,
+ add_suggestion(su, &su->su_sga, p, su->su_badlen,
score, 0, false, slang, false);
}
} else {
@@ -2940,9 +2864,9 @@ badword:
// lower to upper case. Helps for "tath" -> "Kath", which is
// less common than "tath" -> "path". Don't do it when the
// letter is the same, that has already been counted.
- gc = utf_ptr2char((char *)p);
+ int gc = utf_ptr2char(p);
if (SPELL_ISUPPER(gc)) {
- bc = utf_ptr2char((char *)su->su_badword);
+ int bc = utf_ptr2char(su->su_badword);
if (!SPELL_ISUPPER(bc)
&& SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) {
goodscore += SCORE_ICASE / 2;
@@ -2956,12 +2880,11 @@ badword:
// MAXSCORE(), because RESCORE() will change the score.
// If the limit is very high then the iterative method is
// inefficient, using an array is quicker.
- limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
+ int limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
if (limit > SCORE_LIMITMAX) {
- goodscore += spell_edit_score(slang, (char_u *)su->su_badword, p);
+ goodscore += spell_edit_score(slang, su->su_badword, p);
} else {
- goodscore += spell_edit_score_limit(slang, (char_u *)su->su_badword,
- p, limit);
+ goodscore += spell_edit_score_limit(slang, su->su_badword, p, limit);
}
// When going over the limit don't bother to do the rest.
@@ -2972,7 +2895,7 @@ badword:
// Add the suggestion if the score isn't too bad.
goodscore = RESCORE(goodscore, score);
if (goodscore <= su->su_sfmaxscore) {
- add_suggestion(su, &su->su_ga, (char *)p, su->su_badlen,
+ add_suggestion(su, &su->su_ga, p, su->su_badlen,
goodscore, score, true, slang, true);
}
}
@@ -2982,27 +2905,23 @@ badword:
}
/// Find word "word" in fold-case tree for "slang" and return the word number.
-static int soundfold_find(slang_T *slang, char_u *word)
+static int soundfold_find(slang_T *slang, char *word)
{
idx_T arridx = 0;
- int len;
int wlen = 0;
- int c;
- char_u *ptr = word;
- char_u *byts;
- idx_T *idxs;
+ uint8_t *ptr = (uint8_t *)word;
int wordnr = 0;
- byts = (char_u *)slang->sl_sbyts;
- idxs = slang->sl_sidxs;
+ uint8_t *byts = slang->sl_sbyts;
+ idx_T *idxs = slang->sl_sidxs;
- for (;;) {
+ while (true) {
// First byte is the number of possible bytes.
- len = byts[arridx++];
+ int len = byts[arridx++];
// If the first possible byte is a zero the word could end here.
// If the word ends we found the word. If not skip the NUL bytes.
- c = ptr[wlen];
+ int c = ptr[wlen];
if (byts[arridx] == NUL) {
if (c == NUL) {
break;
@@ -3061,12 +2980,11 @@ static int soundfold_find(slang_T *slang, char_u *word)
static bool similar_chars(slang_T *slang, int c1, int c2)
{
int m1, m2;
- char buf[MB_MAXBYTES + 1];
- hashitem_T *hi;
+ char buf[MB_MAXCHAR + 1];
if (c1 >= 256) {
- buf[utf_char2bytes(c1, (char *)buf)] = 0;
- hi = hash_find(&slang->sl_map_hash, buf);
+ buf[utf_char2bytes(c1, buf)] = 0;
+ hashitem_T *hi = hash_find(&slang->sl_map_hash, buf);
if (HASHITEM_EMPTY(hi)) {
m1 = 0;
} else {
@@ -3080,8 +2998,8 @@ static bool similar_chars(slang_T *slang, int c1, int c2)
}
if (c2 >= 256) {
- buf[utf_char2bytes(c2, (char *)buf)] = 0;
- hi = hash_find(&slang->sl_map_hash, buf);
+ buf[utf_char2bytes(c2, buf)] = 0;
+ hashitem_T *hi = hash_find(&slang->sl_map_hash, buf);
if (HASHITEM_EMPTY(hi)) {
m2 = 0;
} else {
@@ -3107,14 +3025,13 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
{
int goodlen; // len of goodword changed
int badlen; // len of bad word changed
- suggest_T *stp;
suggest_T new_sug;
// Minimize "badlen" for consistency. Avoids that changing "the the" to
// "thee the" is added next to changing the first "the" the "thee".
const char *pgood = goodword + strlen(goodword);
char *pbad = su->su_badptr + badlenarg;
- for (;;) {
+ while (true) {
goodlen = (int)(pgood - goodword);
badlen = (int)(pbad - su->su_badptr);
if (goodlen <= 0 || badlen <= 0) {
@@ -3140,7 +3057,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
// Check if the word is already there. Also check the length that is
// being replaced "thes," -> "these" is a different suggestion from
// "thes" -> "these".
- stp = &SUG(*gap, 0);
+ suggest_T *stp = &SUG(*gap, 0);
for (i = gap->ga_len; --i >= 0; stp++) {
if (stp->st_wordlen == goodlen
&& stp->st_orglen == badlen
@@ -3184,8 +3101,8 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
if (i < 0) {
// Add a suggestion.
- stp = GA_APPEND_VIA_PTR(suggest_T, gap);
- stp->st_word = xstrnsave(goodword, (size_t)goodlen);
+ suggest_T *stp = GA_APPEND_VIA_PTR(suggest_T, gap);
+ stp->st_word = xmemdupz(goodword, (size_t)goodlen);
stp->st_wordlen = goodlen;
stp->st_score = score;
stp->st_altscore = altscore;
@@ -3213,22 +3130,18 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
/// @param gap either su_ga or su_sga
static void check_suggestions(suginfo_T *su, garray_T *gap)
{
- suggest_T *stp;
char longword[MAXWLEN + 1];
- int len;
- hlf_T attr;
if (gap->ga_len == 0) {
return;
}
- stp = &SUG(*gap, 0);
+ suggest_T *stp = &SUG(*gap, 0);
for (int i = gap->ga_len - 1; i >= 0; i--) {
// Need to append what follows to check for "the the".
xstrlcpy(longword, stp[i].st_word, MAXWLEN + 1);
- len = stp[i].st_wordlen;
- xstrlcpy(longword + len, su->su_badptr + stp[i].st_orglen,
- (size_t)(MAXWLEN - len + 1));
- attr = HLF_COUNT;
+ int len = stp[i].st_wordlen;
+ xstrlcpy(longword + len, su->su_badptr + stp[i].st_orglen, MAXWLEN + 1 - (size_t)len);
+ hlf_T attr = HLF_COUNT;
(void)spell_check(curwin, longword, &attr, NULL, false);
if (attr != HLF_COUNT) {
// Remove this entry.
@@ -3244,18 +3157,14 @@ static void check_suggestions(suginfo_T *su, garray_T *gap)
/// Add a word to be banned.
static void add_banned(suginfo_T *su, char *word)
{
- char_u *s;
- hash_T hash;
- hashitem_T *hi;
-
- hash = hash_hash(word);
+ hash_T hash = hash_hash(word);
const size_t word_len = strlen(word);
- hi = hash_lookup(&su->su_banned, word, word_len, hash);
+ hashitem_T *hi = hash_lookup(&su->su_banned, word, word_len, hash);
if (!HASHITEM_EMPTY(hi)) { // already present
return;
}
- s = xmemdupz(word, word_len);
- hash_add_item(&su->su_banned, hi, (char *)s, hash);
+ char *s = xmemdupz(word, word_len);
+ hash_add_item(&su->su_banned, hi, s, hash);
}
/// Recompute the score for all suggestions if sound-folding is possible. This
@@ -3273,16 +3182,16 @@ static void rescore_suggestions(suginfo_T *su)
static void rescore_one(suginfo_T *su, suggest_T *stp)
{
slang_T *slang = stp->st_slang;
- char_u sal_badword[MAXWLEN];
- char_u *p;
+ char sal_badword[MAXWLEN];
// Only rescore suggestions that have no sal score yet and do have a
// language.
if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) {
+ char *p;
if (slang == su->su_sallang) {
p = su->su_sal_badword;
} else {
- spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)sal_badword);
+ spell_soundfold(slang, su->su_fbadword, true, sal_badword);
p = sal_badword;
}
@@ -3355,9 +3264,6 @@ static int soundalike_score(char *goodstart, char *badstart)
{
char *goodsound = goodstart;
char *badsound = badstart;
- int goodlen;
- int badlen;
- int n;
char *pl, *ps;
char *pl2, *ps2;
int score = 0;
@@ -3390,12 +3296,12 @@ static int soundalike_score(char *goodstart, char *badstart)
}
}
- goodlen = (int)strlen(goodsound);
- badlen = (int)strlen(badsound);
+ int goodlen = (int)strlen(goodsound);
+ int badlen = (int)strlen(badsound);
// Return quickly if the lengths are too different to be fixed by two
// changes.
- n = goodlen - badlen;
+ int n = goodlen - badlen;
if (n < -2 || n > 2) {
return SCORE_MAXMAX;
}
@@ -3575,13 +3481,8 @@ static int soundalike_score(char *goodstart, char *badstart)
/// The implementation of the algorithm comes from Aspell editdist.cpp,
/// edit_distance(). It has been converted from C++ to C and modified to
/// support multi-byte characters.
-static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u *goodword)
+static int spell_edit_score(slang_T *slang, const char *badword, const char *goodword)
{
- int *cnt;
- int j, i;
- int t;
- int bc, gc;
- int pbc, pgc;
int wbadword[MAXWLEN];
int wgoodword[MAXWLEN];
@@ -3592,12 +3493,12 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
// Get the characters from the multi-byte strings and put them in an
// int array for easy access.
badlen = 0;
- for (const char *p = (char *)badword; *p != NUL;) {
+ for (const char *p = badword; *p != NUL;) {
wbadword[badlen++] = mb_cptr2char_adv(&p);
}
wbadword[badlen++] = 0;
goodlen = 0;
- for (const char *p = (char *)goodword; *p != NUL;) {
+ for (const char *p = goodword; *p != NUL;) {
wgoodword[goodlen++] = mb_cptr2char_adv(&p);
}
wgoodword[goodlen++] = 0;
@@ -3605,18 +3506,18 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
// We use "cnt" as an array: CNT(badword_idx, goodword_idx).
#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)]
- cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1));
+ int *cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1));
CNT(0, 0) = 0;
- for (j = 1; j <= goodlen; j++) {
+ for (int j = 1; j <= goodlen; j++) {
CNT(0, j) = CNT(0, j - 1) + SCORE_INS;
}
- for (i = 1; i <= badlen; i++) {
+ for (int i = 1; i <= badlen; i++) {
CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL;
- for (j = 1; j <= goodlen; j++) {
- bc = wbadword[i - 1];
- gc = wgoodword[j - 1];
+ for (int j = 1; j <= goodlen; j++) {
+ int bc = wbadword[i - 1];
+ int gc = wgoodword[j - 1];
if (bc == gc) {
CNT(i, j) = CNT(i - 1, j - 1);
} else {
@@ -3635,16 +3536,16 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
}
if (i > 1 && j > 1) {
- pbc = wbadword[i - 2];
- pgc = wgoodword[j - 2];
+ int pbc = wbadword[i - 2];
+ int pgc = wgoodword[j - 2];
if (bc == pgc && pbc == gc) {
- t = SCORE_SWAP + CNT(i - 2, j - 2);
+ int t = SCORE_SWAP + CNT(i - 2, j - 2);
if (t < CNT(i, j)) {
CNT(i, j) = t;
}
}
}
- t = SCORE_DEL + CNT(i - 1, j);
+ int t = SCORE_DEL + CNT(i - 1, j);
if (t < CNT(i, j)) {
CNT(i, j) = t;
}
@@ -3656,7 +3557,7 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
}
}
- i = CNT(badlen - 1, goodlen - 1);
+ int i = CNT(badlen - 1, goodlen - 1);
xfree(cnt);
return i;
}
@@ -3673,37 +3574,31 @@ typedef struct {
/// This uses a stack for the edits still to be tried.
/// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support
/// for multi-byte characters.
-static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit)
+static int spell_edit_score_limit(slang_T *slang, char *badword, char *goodword, int limit)
{
return spell_edit_score_limit_w(slang, badword, goodword, limit);
}
/// Multi-byte version of spell_edit_score_limit().
/// Keep it in sync with the above!
-static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const char_u *goodword,
+static int spell_edit_score_limit_w(slang_T *slang, const char *badword, const char *goodword,
int limit)
{
limitscore_T stack[10]; // allow for over 3 * 2 edits
- int stackidx;
- int bi, gi;
- int bi2, gi2;
int bc, gc;
- int score;
int score_off;
- int minscore;
- int round;
int wbadword[MAXWLEN];
int wgoodword[MAXWLEN];
// Get the characters from the multi-byte strings and put them in an
// int array for easy access.
- bi = 0;
- for (const char *p = (char *)badword; *p != NUL;) {
+ int bi = 0;
+ for (const char *p = badword; *p != NUL;) {
wbadword[bi++] = mb_cptr2char_adv(&p);
}
wbadword[bi++] = 0;
- gi = 0;
- for (const char *p = (char *)goodword; *p != NUL;) {
+ int gi = 0;
+ for (const char *p = goodword; *p != NUL;) {
wgoodword[gi++] = mb_cptr2char_adv(&p);
}
wgoodword[gi++] = 0;
@@ -3715,15 +3610,15 @@ static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const
// pushed unto a stack and tried later, some are tried right away. At the
// end of the word the score for one alternative is known. The lowest
// possible score is stored in "minscore".
- stackidx = 0;
+ int stackidx = 0;
bi = 0;
gi = 0;
- score = 0;
- minscore = limit + 1;
+ int score = 0;
+ int minscore = limit + 1;
- for (;;) {
+ while (true) {
// Skip over an equal part, score remains the same.
- for (;;) {
+ while (true) {
bc = wbadword[bi];
gc = wgoodword[gi];
@@ -3759,15 +3654,15 @@ static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const
// that may lead to a lower score than "minscore".
// round 0: try deleting a char from badword
// round 1: try inserting a char in badword
- for (round = 0; round <= 1; round++) {
+ for (int round = 0; round <= 1; round++) {
score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
if (score_off < minscore) {
if (score_off + SCORE_EDIT_MIN >= minscore) {
// Near the limit, rest of the words must match. We
// can check that right now, no need to push an item
// onto the stack.
- bi2 = bi + 1 - round;
- gi2 = gi + round;
+ int bi2 = bi + 1 - round;
+ int gi2 = gi + round;
while (wgoodword[gi2] == wbadword[bi2]) {
if (wgoodword[gi2] == NUL) {
minscore = score_off;
diff --git a/src/nvim/spellsuggest.h b/src/nvim/spellsuggest.h
index 8813a5b3f1..40e51a0f88 100644
--- a/src/nvim/spellsuggest.h
+++ b/src/nvim/spellsuggest.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_SPELLSUGGEST_H
-#define NVIM_SPELLSUGGEST_H
+#pragma once
-#include "nvim/garray.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spellsuggest.h.generated.h"
#endif
-#endif // NVIM_SPELLSUGGEST_H
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 9ba5f81776..900eac0826 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -1,35 +1,29 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
-#include <stddef.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
+#include "nvim/ex_getln.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
+#include "nvim/memory.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
-#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "state.c.generated.h" // IWYU pragma: export
@@ -37,7 +31,7 @@
void state_enter(VimState *s)
{
- for (;;) {
+ while (true) {
int check_result = s->check ? s->check(s) : 1;
if (!check_result) {
@@ -62,6 +56,8 @@ getkey:
if (vpeekc() != NUL || typebuf.tb_len > 0) {
key = safe_vgetc();
} else if (!multiqueue_empty(main_loop.events)) {
+ // No input available and processing events may take time, flush now.
+ ui_flush();
// Event was made available after the last multiqueue_process_events call
key = K_EVENT;
} else {
@@ -71,7 +67,7 @@ getkey:
update_screen();
setcursor(); // put cursor back where it belongs
}
- // Flush screen updates before blocking
+ // Flush screen updates before blocking.
ui_flush();
// Call `os_inchar` directly to block for events or user input without
// consuming anything from `input_buffer`(os/input.c) or calling the
@@ -92,8 +88,9 @@ getkey:
may_sync_undo();
}
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
- log_key(LOGLVL_DBG, key);
+#ifdef NVIM_LOG_DEBUG
+ char *keyname = key == K_EVENT ? "K_EVENT" : get_special_key_name(key, mod_mask);
+ DLOG("input: %s", keyname);
#endif
int execute_result = s->execute(s, key);
@@ -135,7 +132,7 @@ void state_handle_k_event(void)
/// Return true if in the current mode we need to use virtual.
bool virtual_active(void)
{
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
// While an operator is being executed we return "virtual_op", because
// VIsual_active has already been reset, thus we can't check for "block"
@@ -214,6 +211,9 @@ void get_mode(char *buf)
if (exmode_active) {
buf[i++] = 'v';
}
+ if ((State & MODE_CMDLINE) && cmdline_overstrike()) {
+ buf[i++] = 'r';
+ }
} else if (State & MODE_TERMINAL) {
buf[i++] = 't';
} else {
@@ -268,3 +268,50 @@ void may_trigger_modechanged(void)
restore_v_event(v_event, &save_v_event);
}
+
+/// When true in a safe state when starting to wait for a character.
+static bool was_safe = false;
+
+/// Return whether currently it is safe, assuming it was safe before (high level
+/// state didn't change).
+static bool is_safe_now(void)
+{
+ return stuff_empty()
+ && typebuf.tb_len == 0
+ && !using_script()
+ && !global_busy
+ && !debug_mode;
+}
+
+/// Trigger SafeState if currently in s safe state, that is "safe" is TRUE and
+/// there is no typeahead.
+void may_trigger_safestate(bool safe)
+{
+ bool is_safe = safe && is_safe_now();
+
+ if (was_safe != is_safe) {
+ // Only log when the state changes, otherwise it happens at nearly
+ // every key stroke.
+ DLOG(is_safe ? "SafeState: Start triggering" : "SafeState: Stop triggering");
+ }
+ if (is_safe) {
+ apply_autocmds(EVENT_SAFESTATE, NULL, NULL, false, curbuf);
+ }
+ was_safe = is_safe;
+}
+
+/// Something changed which causes the state possibly to be unsafe, e.g. a
+/// character was typed. It will remain unsafe until the next call to
+/// may_trigger_safestate().
+void state_no_longer_safe(const char *reason)
+{
+ if (was_safe && reason != NULL) {
+ DLOG("SafeState reset: %s", reason);
+ }
+ was_safe = false;
+}
+
+bool get_was_safe_state(void)
+{
+ return was_safe;
+}
diff --git a/src/nvim/state.h b/src/nvim/state.h
index 76a38b0dab..8c5957bf9a 100644
--- a/src/nvim/state.h
+++ b/src/nvim/state.h
@@ -1,22 +1,7 @@
-#ifndef NVIM_STATE_H
-#define NVIM_STATE_H
+#pragma once
-#include <stddef.h>
-
-struct vim_state;
-
-typedef struct vim_state VimState;
-
-typedef int (*state_check_callback)(VimState *state);
-typedef int (*state_execute_callback)(VimState *state, int key);
-
-struct vim_state {
- state_check_callback check;
- state_execute_callback execute;
-};
+#include "nvim/state_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "state.h.generated.h"
#endif
-
-#endif // NVIM_STATE_H
diff --git a/src/nvim/state_defs.h b/src/nvim/state_defs.h
new file mode 100644
index 0000000000..0b32412f5a
--- /dev/null
+++ b/src/nvim/state_defs.h
@@ -0,0 +1,45 @@
+#pragma once
+
+typedef struct vim_state VimState;
+
+typedef int (*state_check_callback)(VimState *state);
+typedef int (*state_execute_callback)(VimState *state, int key);
+
+struct vim_state {
+ state_check_callback check;
+ state_execute_callback execute;
+};
+
+/// Values for State
+///
+/// The lower bits up to 0x80 are used to distinguish normal/visual/op_pending
+/// /cmdline/insert/replace/terminal mode. This is used for mapping. If none
+/// of these bits are set, no mapping is done. See the comment above do_map().
+/// The upper bits are used to distinguish between other states and variants of
+/// the base modes.
+enum {
+ MODE_NORMAL = 0x01, ///< Normal mode, command expected
+ MODE_VISUAL = 0x02, ///< Visual mode - use get_real_state()
+ MODE_OP_PENDING = 0x04, ///< Normal mode, operator is pending - use get_real_state()
+ MODE_CMDLINE = 0x08, ///< Editing the command line
+ MODE_INSERT = 0x10, ///< Insert mode, also for Replace mode
+ MODE_LANGMAP = 0x20, ///< Language mapping, can be combined with MODE_INSERT and MODE_CMDLINE
+ MODE_SELECT = 0x40, ///< Select mode, use get_real_state()
+ MODE_TERMINAL = 0x80, ///< Terminal mode
+
+ MAP_ALL_MODES = 0xff, ///< all mode bits used for mapping
+
+ REPLACE_FLAG = 0x100, ///< Replace mode flag
+ MODE_REPLACE = REPLACE_FLAG | MODE_INSERT,
+ VREPLACE_FLAG = 0x200, ///< Virtual-replace mode flag
+ MODE_VREPLACE = REPLACE_FLAG | VREPLACE_FLAG | MODE_INSERT,
+ MODE_LREPLACE = REPLACE_FLAG | MODE_LANGMAP,
+
+ MODE_NORMAL_BUSY = 0x1000 | MODE_NORMAL, ///< Normal mode, busy with a command
+ MODE_HITRETURN = 0x2000 | MODE_NORMAL, ///< waiting for return or command
+ MODE_ASKMORE = 0x3000, ///< Asking if you want --more--
+ MODE_SETWSIZE = 0x4000, ///< window size has changed
+ MODE_EXTERNCMD = 0x5000, ///< executing an external command
+ MODE_SHOWMATCH = 0x6000 | MODE_INSERT, ///< show matching paren
+ MODE_CONFIRM = 0x7000, ///< ":confirm" prompt
+};
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index 6ad1f31143..4dac1b1451 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -1,7 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-//
-
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -12,10 +8,12 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/digraph.h"
+#include "nvim/drawline.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
@@ -25,26 +23,25 @@
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
-#include "nvim/sign_defs.h"
+#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sign.h"
+#include "nvim/state_defs.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
#include "nvim/window.h"
// Determines how deeply nested %{} blocks will be evaluated in statusline.
@@ -62,13 +59,8 @@ typedef enum {
/// If inversion is possible we use it. Else '=' characters are used.
void win_redr_status(win_T *wp)
{
- int row;
- int col;
- char *p;
- int len;
int fillchar;
int attr;
- int this_ru_col;
bool is_stl_global = global_stl_height() > 0;
static bool busy = false;
@@ -97,8 +89,8 @@ void win_redr_status(win_T *wp)
const int stl_width = is_stl_global ? Columns : wp->w_width;
get_trans_bufname(wp->w_buffer);
- p = NameBuff;
- len = (int)strlen(p);
+ char *p = NameBuff;
+ int len = (int)strlen(p);
if ((bt_help(wp->w_buffer)
|| wp->w_p_pvw
@@ -124,7 +116,7 @@ void win_redr_status(win_T *wp)
// len += (int)strlen(p + len); // dead assignment
}
- this_ru_col = ru_col - (Columns - stl_width);
+ int this_ru_col = ru_col - (Columns - stl_width);
if (this_ru_col < (stl_width + 1) / 2) {
this_ru_col = (stl_width + 1) / 2;
}
@@ -132,10 +124,10 @@ void win_redr_status(win_T *wp)
p = "<"; // No room for file name!
len = 1;
} else {
- int clen = 0, i;
+ int i;
// Count total number of display cells.
- clen = (int)mb_string2cells(p);
+ int clen = (int)mb_string2cells(p);
// Find first character that will fit.
// Going from start to end is much faster for DBCS.
@@ -151,29 +143,29 @@ void win_redr_status(win_T *wp)
}
}
- row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
- col = is_stl_global ? 0 : wp->w_wincol;
- grid_puts(&default_grid, p, row, col, attr);
- grid_fill(&default_grid, row, row + 1, len + col,
- this_ru_col + col, fillchar, fillchar, attr);
+ grid_line_start(&default_grid, is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp));
+ int col = is_stl_global ? 0 : wp->w_wincol;
+
+ int width = grid_line_puts(col, p, -1, attr);
+ grid_line_fill(width + col, this_ru_col + col, fillchar, attr);
if (get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL)
&& this_ru_col - len > (int)(strlen(NameBuff) + 1)) {
- grid_puts(&default_grid, NameBuff, row,
- (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr);
+ grid_line_puts((int)((size_t)this_ru_col - strlen(NameBuff) - 1), NameBuff, -1, attr);
}
- win_redr_ruler(wp, true);
+ win_redr_ruler(wp);
// Draw the 'showcmd' information if 'showcmdloc' == "statusline".
if (p_sc && *p_sloc == 's') {
const int sc_width = MIN(10, this_ru_col - len - 2);
if (sc_width > 0) {
- grid_puts_len(&default_grid, showcmd_buf, sc_width, row,
- wp->w_wincol + this_ru_col - sc_width - 1, attr);
+ grid_line_puts(wp->w_wincol + this_ru_col - sc_width - 1, showcmd_buf, sc_width, attr);
}
}
+
+ grid_line_flush();
}
// May need to draw the character below the vertical separator.
@@ -181,13 +173,48 @@ void win_redr_status(win_T *wp)
if (stl_connected(wp)) {
fillchar = fillchar_status(&attr, wp);
} else {
- fillchar = fillchar_vsep(wp, &attr);
+ attr = win_hl_attr(wp, HLF_C);
+ fillchar = wp->w_p_fcs_chars.vert;
}
- grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr);
+ grid_line_start(&default_grid, W_ENDROW(wp));
+ grid_line_put_schar(W_ENDCOL(wp), schar_from_char(fillchar), attr);
+ grid_line_flush();
}
busy = false;
}
+void get_trans_bufname(buf_T *buf)
+{
+ if (buf_spname(buf) != NULL) {
+ xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL);
+ } else {
+ home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true);
+ }
+ trans_characters(NameBuff, MAXPATHL);
+}
+
+/// Only call if (wp->w_vsep_width != 0).
+///
+/// @return true if the status line of window "wp" is connected to the status
+/// line of the window right of it. If not, then it's a vertical separator.
+bool stl_connected(win_T *wp)
+{
+ frame_T *fr = wp->w_frame;
+ while (fr->fr_parent != NULL) {
+ if (fr->fr_parent->fr_layout == FR_COL) {
+ if (fr->fr_next != NULL) {
+ break;
+ }
+ } else {
+ if (fr->fr_next != NULL) {
+ return true;
+ }
+ }
+ fr = fr->fr_parent;
+ }
+ return false;
+}
+
/// Clear status line, window bar or tab page line click definition table
///
/// @param[out] tpcd Table to clear.
@@ -205,7 +232,7 @@ void stl_clear_click_defs(StlClickDefinition *const click_defs, const size_t cli
}
/// Allocate or resize the click definitions array if needed.
-StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, size_t *size)
+StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, int width, size_t *size)
{
if (*size < (size_t)width) {
xfree(cdp);
@@ -216,8 +243,8 @@ StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, si
}
/// Fill the click definitions array if needed.
-void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs, char *buf,
- int width, bool tabline)
+void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs,
+ const char *buf, int width, bool tabline)
{
if (click_defs == NULL) {
return;
@@ -231,6 +258,7 @@ void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_r
};
for (int i = 0; click_recs[i].start != NULL; i++) {
len += vim_strnsize(buf, (int)(click_recs[i].start - buf));
+ assert(len <= width);
if (col < len) {
while (col < len) {
click_defs[col++] = cur_click_def;
@@ -238,7 +266,7 @@ void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_r
} else {
xfree(cur_click_def.func);
}
- buf = (char *)click_recs[i].start;
+ buf = click_recs[i].start;
cur_click_def = click_recs[i].def;
if (!tabline && !(cur_click_def.type == kStlClickDisabled
|| cur_click_def.type == kStlClickFuncRun)) {
@@ -261,23 +289,17 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
{
static bool entered = false;
int attr;
- int curattr;
int row;
int col = 0;
int maxwidth;
- int width;
- int n;
- int len;
int fillchar;
char buf[MAXPATHL];
+ char transbuf[MAXPATHL];
char *stl;
- char *p;
char *opt_name;
int opt_scope = 0;
stl_hlrec_t *hltab;
StlClickRecord *tabtab;
- win_T *ewp;
- int p_crb_save;
bool is_stl_global = global_stl_height() > 0;
ScreenGrid *grid = &default_grid;
@@ -309,7 +331,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
grid_adjust(&grid, &row, &col);
if (row < 0) {
- return;
+ goto theend;
}
fillchar = wp->w_p_fcs_chars.wbr;
@@ -321,7 +343,8 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
} else {
row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
fillchar = fillchar_status(&attr, wp);
- maxwidth = is_stl_global ? Columns : wp->w_width;
+ const bool in_status_line = wp->w_status_height != 0 || is_stl_global;
+ maxwidth = in_status_line && !is_stl_global ? wp->w_width : Columns;
stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
wp->w_status_click_defs = stl_alloc_click_defs(wp->w_status_click_defs, maxwidth,
&wp->w_status_click_defs_size);
@@ -347,8 +370,8 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
if (col < (maxwidth + 1) / 2) {
col = (maxwidth + 1) / 2;
}
- maxwidth = maxwidth - col;
- if (!wp->w_status_height && !is_stl_global) {
+ maxwidth -= col;
+ if (!in_status_line) {
grid = &msg_grid_adj;
row = Rows - 1;
maxwidth--; // writing in last column may cause scrolling
@@ -361,7 +384,9 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0);
}
- col += is_stl_global ? 0 : wp->w_wincol;
+ if (in_status_line && !is_stl_global) {
+ col += wp->w_wincol;
+ }
}
if (maxwidth <= 0) {
@@ -370,41 +395,34 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
// Temporarily reset 'cursorbind', we don't want a side effect from moving
// the cursor away and back.
- ewp = wp == NULL ? curwin : wp;
- p_crb_save = ewp->w_p_crb;
+ win_T *ewp = wp == NULL ? curwin : wp;
+ int p_crb_save = ewp->w_p_crb;
ewp->w_p_crb = false;
// Make a copy, because the statusline may include a function call that
// might change the option value and free the memory.
stl = xstrdup(stl);
- width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope,
- fillchar, maxwidth, &hltab, &tabtab, NULL);
+ build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope,
+ fillchar, maxwidth, &hltab, &tabtab, NULL);
xfree(stl);
ewp->w_p_crb = p_crb_save;
- // Make all characters printable.
- p = transstr(buf, true);
- len = (int)xstrlcpy(buf, p, sizeof(buf));
- len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1;
- xfree(p);
-
- // fill up with "fillchar"
- while (width < maxwidth && len < (int)sizeof(buf) - 1) {
- len += utf_char2bytes(fillchar, buf + len);
- width++;
- }
- buf[len] = NUL;
+ int len = (int)strlen(buf);
+ int start_col = col;
// Draw each snippet with the specified highlighting.
- grid_puts_line_start(grid, row);
+ if (!draw_ruler) {
+ grid_line_start(grid, row);
+ }
- curattr = attr;
- p = buf;
- for (n = 0; hltab[n].start != NULL; n++) {
+ int curattr = attr;
+ char *p = buf;
+ for (int n = 0; hltab[n].start != NULL; n++) {
int textlen = (int)(hltab[n].start - p);
- grid_puts_len(grid, p, textlen, row, col, curattr);
- col += vim_strnsize(p, textlen);
+ // Make all characters printable.
+ size_t tsize = transstr_buf(p, textlen, transbuf, sizeof transbuf, true);
+ col += grid_line_puts(col, transbuf, (int)tsize, curattr);
p = hltab[n].start;
if (hltab[n].userhl == 0) {
@@ -418,9 +436,16 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
}
}
// Make sure to use an empty string instead of p, if p is beyond buf + len.
- grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr);
+ size_t tsize = transstr_buf(p >= buf + len ? "" : p, -1, transbuf, sizeof transbuf, true);
+ col += grid_line_puts(col, transbuf, (int)tsize, curattr);
+ int maxcol = start_col + maxwidth;
- grid_puts_line_flush(false);
+ // fill up with "fillchar"
+ grid_line_fill(col, maxcol, fillchar, curattr);
+
+ if (!draw_ruler) {
+ grid_line_flush();
+ }
// Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking
// in the tab page line, status line or window bar
@@ -453,7 +478,8 @@ void win_redr_winbar(win_T *wp)
entered = false;
}
-void win_redr_ruler(win_T *wp, bool always)
+/// must be called after a grid_line_start() at the intended row
+void win_redr_ruler(win_T *wp)
{
bool is_stl_global = global_stl_height() > 0;
static bool did_show_ext_ruler = false;
@@ -471,7 +497,8 @@ void win_redr_ruler(win_T *wp, bool always)
// Don't draw the ruler while doing insert-completion, it might overwrite
// the (long) mode message.
- if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) {
+ win_T *ruler_win = curwin->w_status_height == 0 ? curwin : lastwin_nofloating();
+ if (wp == ruler_win && ruler_win->w_status_height == 0 && !is_stl_global) {
if (edit_submode != NULL) {
return;
}
@@ -483,138 +510,105 @@ void win_redr_ruler(win_T *wp, bool always)
}
// Check if not in Insert mode and the line is empty (will show "0-1").
- int empty_line = false;
- if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) {
- empty_line = true;
- }
+ int empty_line = (State & MODE_INSERT) == 0
+ && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) == NUL;
- // Only draw the ruler when something changed.
- validate_virtcol_win(wp);
- if (redraw_cmdline
- || always
- || wp->w_cursor.lnum != wp->w_ru_cursor.lnum
- || wp->w_cursor.col != wp->w_ru_cursor.col
- || wp->w_virtcol != wp->w_ru_virtcol
- || wp->w_cursor.coladd != wp->w_ru_cursor.coladd
- || wp->w_topline != wp->w_ru_topline
- || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count
- || wp->w_topfill != wp->w_ru_topfill
- || empty_line != wp->w_ru_empty) {
- int width;
- int row;
- int fillchar;
- int attr;
- int off;
- bool part_of_status = false;
-
- if (wp->w_status_height) {
- row = W_ENDROW(wp);
- fillchar = fillchar_status(&attr, wp);
- off = wp->w_wincol;
- width = wp->w_width;
- part_of_status = true;
- } else if (is_stl_global) {
- row = Rows - (int)p_ch - 1;
- fillchar = fillchar_status(&attr, wp);
- off = 0;
- width = Columns;
- part_of_status = true;
- } else {
- row = Rows - 1;
- fillchar = ' ';
- attr = HL_ATTR(HLF_MSG);
- width = Columns;
- off = 0;
- }
+ int width;
+ int fillchar;
+ int attr;
+ int off;
+ bool part_of_status = false;
- if (!part_of_status && p_ch == 0 && !ui_has(kUIMessages)) {
- return;
- }
+ if (wp->w_status_height) {
+ fillchar = fillchar_status(&attr, wp);
+ off = wp->w_wincol;
+ width = wp->w_width;
+ part_of_status = true;
+ } else if (is_stl_global) {
+ fillchar = fillchar_status(&attr, wp);
+ off = 0;
+ width = Columns;
+ part_of_status = true;
+ } else {
+ fillchar = ' ';
+ attr = HL_ATTR(HLF_MSG);
+ width = Columns;
+ off = 0;
+ }
- // In list mode virtcol needs to be recomputed
- colnr_T virtcol = wp->w_virtcol;
- if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
- wp->w_p_list = false;
- getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
- wp->w_p_list = true;
- }
+ // In list mode virtcol needs to be recomputed
+ colnr_T virtcol = wp->w_virtcol;
+ if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
+ wp->w_p_list = false;
+ getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
+ wp->w_p_list = true;
+ }
#define RULER_BUF_LEN 70
- char buffer[RULER_BUF_LEN];
-
- // Some sprintfs return the length, some return a pointer.
- // To avoid portability problems we use strlen() here.
- vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",",
- (wp->w_buffer->b_ml.ml_flags &
- ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum);
- size_t len = strlen(buffer);
- col_print(buffer + len, RULER_BUF_LEN - len,
- empty_line ? 0 : (int)wp->w_cursor.col + 1,
- (int)virtcol + 1);
-
- // Add a "50%" if there is room for it.
- // On the last line, don't print in the last column (scrolls the
- // screen up on some terminals).
- int i = (int)strlen(buffer);
- get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
- int o = i + vim_strsize(buffer + i + 1);
- if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen
+ char buffer[RULER_BUF_LEN];
+
+ // Some sprintfs return the length, some return a pointer.
+ // To avoid portability problems we use strlen() here.
+ vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",",
+ (wp->w_buffer->b_ml.ml_flags &
+ ML_EMPTY) ? 0 : (int64_t)wp->w_cursor.lnum);
+ size_t len = strlen(buffer);
+ col_print(buffer + len, RULER_BUF_LEN - len,
+ empty_line ? 0 : (int)wp->w_cursor.col + 1,
+ (int)virtcol + 1);
+
+ // Add a "50%" if there is room for it.
+ // On the last line, don't print in the last column (scrolls the
+ // screen up on some terminals).
+ int i = (int)strlen(buffer);
+ get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
+ int o = i + vim_strsize(buffer + i + 1);
+ if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen
+ o++;
+ }
+ int this_ru_col = ru_col - (Columns - width);
+ if (this_ru_col < 0) {
+ this_ru_col = 0;
+ }
+ // Never use more than half the window/screen width, leave the other half
+ // for the filename.
+ if (this_ru_col < (width + 1) / 2) {
+ this_ru_col = (width + 1) / 2;
+ }
+ if (this_ru_col + o < width) {
+ // Need at least 3 chars left for get_rel_pos() + NUL.
+ while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
+ i += utf_char2bytes(fillchar, buffer + i);
o++;
}
- int this_ru_col = ru_col - (Columns - width);
- if (this_ru_col < 0) {
- this_ru_col = 0;
- }
- // Never use more than half the window/screen width, leave the other half
- // for the filename.
- if (this_ru_col < (width + 1) / 2) {
- this_ru_col = (width + 1) / 2;
- }
- if (this_ru_col + o < width) {
- // Need at least 3 chars left for get_rel_pos() + NUL.
- while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
- i += utf_char2bytes(fillchar, buffer + i);
- o++;
- }
- get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
- }
+ get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
+ }
- if (ui_has(kUIMessages) && !part_of_status) {
- MAXSIZE_TEMP_ARRAY(content, 1);
- MAXSIZE_TEMP_ARRAY(chunk, 2);
- ADD_C(chunk, INTEGER_OBJ(attr));
- ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer)));
- ADD_C(content, ARRAY_OBJ(chunk));
- ui_call_msg_ruler(content);
- did_show_ext_ruler = true;
- } else {
- if (did_show_ext_ruler) {
- ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
- did_show_ext_ruler = false;
- }
- // Truncate at window boundary.
- o = 0;
- for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
- o += utf_ptr2cells(buffer + i);
- if (this_ru_col + o > width) {
- buffer[i] = NUL;
- break;
- }
+ if (ui_has(kUIMessages) && !part_of_status) {
+ MAXSIZE_TEMP_ARRAY(content, 1);
+ MAXSIZE_TEMP_ARRAY(chunk, 2);
+ ADD_C(chunk, INTEGER_OBJ(attr));
+ ADD_C(chunk, CSTR_AS_OBJ(buffer));
+ ADD_C(content, ARRAY_OBJ(chunk));
+ ui_call_msg_ruler(content);
+ did_show_ext_ruler = true;
+ } else {
+ if (did_show_ext_ruler) {
+ ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
+ did_show_ext_ruler = false;
+ }
+ // Truncate at window boundary.
+ o = 0;
+ for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
+ o += utf_ptr2cells(buffer + i);
+ if (this_ru_col + o > width) {
+ buffer[i] = NUL;
+ break;
}
-
- ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj;
- grid_puts(grid, buffer, row, this_ru_col + off, attr);
- grid_fill(grid, row, row + 1,
- this_ru_col + off + (int)strlen(buffer), off + width, fillchar,
- fillchar, attr);
}
- wp->w_ru_cursor = wp->w_cursor;
- wp->w_ru_virtcol = wp->w_virtcol;
- wp->w_ru_empty = (char)empty_line;
- wp->w_ru_topline = wp->w_topline;
- wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count;
- wp->w_ru_topfill = wp->w_topfill;
+ int w = grid_line_puts(this_ru_col + off, buffer, -1, attr);
+ grid_line_fill(this_ru_col + off + w, off + width, fillchar, attr);
}
}
@@ -630,18 +624,7 @@ int fillchar_status(int *attr, win_T *wp)
*attr = win_hl_attr(wp, HLF_SNC);
fill = wp->w_p_fcs_chars.stlnc;
}
- // Use fill when there is highlighting, and highlighting of current
- // window differs, or the fillchars differ, or this is not the
- // current window
- if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC)
- || !is_curwin || ONE_WINDOW)
- || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) {
- return fill;
- }
- if (is_curwin) {
- return '^';
- }
- return '=';
+ return fill;
}
/// Redraw the status line according to 'statusline' and take care of any
@@ -710,21 +693,9 @@ static void ui_ext_tabline_update(void)
/// Draw the tab pages line at the top of the Vim window.
void draw_tabline(void)
{
- int tabcount = 0;
- int tabwidth = 0;
- int col = 0;
- int scol = 0;
- int attr;
win_T *wp;
- win_T *cwp;
- int wincount;
- int modified;
- int c;
- int len;
int attr_nosel = HL_ATTR(HLF_TP);
int attr_fill = HL_ATTR(HLF_TPF);
- char *p;
- int room;
int use_sep_chars = (t_colors < 8);
if (default_grid.chars == NULL) {
@@ -749,6 +720,15 @@ void draw_tabline(void)
if (*p_tal != NUL) {
win_redr_custom(NULL, false, false);
} else {
+ int tabcount = 0;
+ int tabwidth = 0;
+ int col = 0;
+ win_T *cwp;
+ int wincount;
+ int c;
+ int len;
+ char *p;
+ grid_line_start(&default_grid, 0);
FOR_ALL_TABS(tp) {
tabcount++;
}
@@ -761,7 +741,7 @@ void draw_tabline(void)
tabwidth = 6;
}
- attr = attr_nosel;
+ int attr = attr_nosel;
tabcount = 0;
FOR_ALL_TABS(tp) {
@@ -769,7 +749,7 @@ void draw_tabline(void)
break;
}
- scol = col;
+ int scol = col;
if (tp == curtab) {
cwp = curwin;
@@ -783,16 +763,16 @@ void draw_tabline(void)
attr = win_hl_attr(cwp, HLF_TPS);
}
if (use_sep_chars && col > 0) {
- grid_putchar(&default_grid, '|', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii('|'), attr);
}
if (tp->tp_topframe != topframe) {
attr = win_hl_attr(cwp, HLF_TP);
}
- grid_putchar(&default_grid, ' ', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii(' '), attr);
- modified = false;
+ int modified = false;
for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) {
if (bufIsChanged(wp->w_buffer)) {
@@ -807,17 +787,17 @@ void draw_tabline(void)
if (col + len >= Columns - 3) {
break;
}
- grid_puts_len(&default_grid, NameBuff, len, 0, col,
- hl_combine_attr(attr, win_hl_attr(cwp, HLF_T)));
+ grid_line_puts(col, NameBuff, len,
+ hl_combine_attr(attr, win_hl_attr(cwp, HLF_T)));
col += len;
}
if (modified) {
- grid_puts_len(&default_grid, "+", 1, 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii('+'), attr);
}
- grid_putchar(&default_grid, ' ', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii(' '), attr);
}
- room = scol - col + tabwidth - 1;
+ int room = scol - col + tabwidth - 1;
if (room > 0) {
// Get buffer name in NameBuff[]
get_trans_bufname(cwp->w_buffer);
@@ -832,10 +812,10 @@ void draw_tabline(void)
len = Columns - col - 1;
}
- grid_puts_len(&default_grid, p, (int)strlen(p), 0, col, attr);
+ grid_line_puts(col, p, -1, attr);
col += len;
}
- grid_putchar(&default_grid, ' ', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii(' '), attr);
// Store the tab page number in tab_page_click_defs[], so that
// jump_to_mouse() knows where each one is.
@@ -854,27 +834,29 @@ void draw_tabline(void)
} else {
c = ' ';
}
- grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill);
+ grid_line_fill(col, Columns, c, attr_fill);
// Draw the 'showcmd' information if 'showcmdloc' == "tabline".
if (p_sc && *p_sloc == 't') {
const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3);
if (sc_width > 0) {
- grid_puts_len(&default_grid, showcmd_buf, sc_width, 0,
- Columns - sc_width - (tabcount > 1) * 2, attr_nosel);
+ grid_line_puts(Columns - sc_width - (tabcount > 1) * 2,
+ showcmd_buf, sc_width, attr_nosel);
}
}
// Put an "X" for closing the current tab if there are several.
if (tabcount > 1) {
- grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel);
+ grid_line_put_schar(Columns - 1, schar_from_ascii('X'), attr_nosel);
tab_page_click_defs[Columns - 1] = (StlClickDefinition) {
.type = kStlClickTabClose,
.tabnr = 999,
.func = NULL,
};
}
+
+ grid_line_flush();
}
// Reset the flag here again, in case evaluating 'tabline' causes it to be
@@ -885,13 +867,12 @@ void draw_tabline(void)
/// Build the 'statuscolumn' string for line "lnum". When "relnum" == -1,
/// the v:lnum and v:relnum variables don't have to be updated.
///
-/// @param hlrec HL attributes (can be NULL)
-/// @param stcp Status column attributes (can be NULL)
/// @return The width of the built status column string for line "lnum"
-int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int fillchar,
- char *buf, stl_hlrec_t **hlrec, statuscol_T *stcp)
+int build_statuscol_str(win_T *wp, linenr_T lnum, linenr_T relnum, statuscol_T *stcp)
{
- bool fillclick = relnum >= 0 && lnum == wp->w_topline;
+ // Only update click definitions once per window per redraw.
+ // Don't update when current width is 0, since it will be redrawn again if not empty.
+ const bool fillclick = relnum >= 0 && stcp->width > 0 && lnum == wp->w_topline;
if (relnum >= 0) {
set_vim_var_nr(VV_LNUM, lnum);
@@ -900,16 +881,15 @@ int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int
StlClickRecord *clickrec;
char *stc = xstrdup(wp->w_p_stc);
- int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, fillchar,
- maxwidth, hlrec, fillclick ? &clickrec : NULL, stcp);
+ int width = build_stl_str_hl(wp, stcp->text, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, ' ',
+ stcp->width, &stcp->hlrec, fillclick ? &clickrec : NULL, stcp);
xfree(stc);
- // Only update click definitions once per window per redraw
if (fillclick) {
stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size);
- wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width,
+ wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, stcp->width,
&wp->w_statuscol_click_defs_size);
- stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false);
+ stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, stcp->text, stcp->width, false);
}
return width;
@@ -971,7 +951,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Allocate one more, because the last element is used to indicate the
// end of the list.
- stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
+ stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1));
stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
@@ -991,7 +971,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
};
set_var(S_LEN("g:statusline_winid"), &tv, false);
- usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
+ usefmt = eval_to_string_safe(fmt + 2, use_sandbox);
if (usefmt == NULL) {
usefmt = fmt;
}
@@ -1012,7 +992,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// Get line & check if empty (cursorpos will show "0-1").
- const char *line_ptr = ml_get_buf(wp->w_buffer, lnum, false);
+ const char *line_ptr = ml_get_buf(wp->w_buffer, lnum);
bool empty_line = (*line_ptr == NUL);
// Get the byte value now, in case we need it below. This is more
@@ -1030,9 +1010,10 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
int groupdepth = 0;
- int evaldepth = 0;
+ int evaldepth = 0;
int curitem = 0;
+ int foldsignitem = -1;
bool prevchar_isflag = true;
bool prevchar_isitem = false;
@@ -1095,7 +1076,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
continue;
}
- // STL_SEPARATE: Separation place between left and right aligned items.
+ // STL_SEPARATE: Separation between items, filled with white space.
if (*fmt_p == STL_SEPARATE) {
fmt_p++;
// Ignored when we are inside of a grouping
@@ -1182,7 +1163,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// { Determine the number of bytes to remove
// Find the first character that should be included.
- long n = 0;
+ int n = 0;
while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) {
group_len -= ptr2cells(t + n);
n += utfc_ptr2len(t + n);
@@ -1307,7 +1288,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (*fmt_p == STL_TABCLOSENR) {
if (minwid == 0) {
// %X ends the close label, go back to the previous tab label nr.
- for (long n = curitem - 1; n >= 0; n--) {
+ for (int n = curitem - 1; n >= 0; n--) {
if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) {
minwid = stl_items[n].minwid;
break;
@@ -1379,6 +1360,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// An invalid item was specified.
// Continue processing on the next character of the format string.
if (vim_strchr(STL_ALL, (uint8_t)(*fmt_p)) == NULL) {
+ if (*fmt_p == NUL) { // can happen with "%0"
+ break;
+ }
fmt_p++;
continue;
}
@@ -1390,7 +1374,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
NumberBase base = kNumBaseDecimal;
bool itemisflag = false;
bool fillable = true;
- long num = -1;
+ int num = -1;
char *str = NULL;
switch (opt) {
case STL_FILEPATH:
@@ -1403,7 +1387,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
xstrlcpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL);
} else {
char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
- : wp->w_buffer->b_fname;
+ : wp->w_buffer->b_fname;
home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true);
}
trans_characters(NameBuff, MAXPATHL);
@@ -1449,8 +1433,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Store the current buffer number as a string variable
vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum);
set_internal_string_var("g:actual_curbuf", buf_tmp);
- vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle);
- set_internal_string_var("g:actual_curwin", (char *)win_tmp);
+ vim_snprintf(win_tmp, sizeof(win_tmp), "%d", curwin->handle);
+ set_internal_string_var("g:actual_curwin", win_tmp);
buf_T *const save_curbuf = curbuf;
win_T *const save_curwin = curwin;
@@ -1463,7 +1447,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// Note: The result stored in `t` is unused.
- str = eval_to_string_safe(out_p, &t, use_sandbox);
+ str = eval_to_string_safe(out_p, use_sandbox);
curwin = save_curwin;
curbuf = save_curbuf;
@@ -1488,7 +1472,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// If the output of the expression needs to be evaluated
// replace the %{} block with the result of evaluation
if (reevaluate && str != NULL && *str != 0
- && strchr((const char *)str, '%') != NULL
+ && strchr(str, '%') != NULL
&& evaldepth < MAX_STL_EVAL_DEPTH) {
size_t parsed_usefmt = (size_t)(block_start - usefmt);
size_t str_length = strlen(str);
@@ -1518,12 +1502,12 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
case STL_LINE:
// Overload %l with v:lnum for 'statuscolumn'
- if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) {
+ if (stcp != NULL) {
if (wp->w_p_nu && !get_vim_var_nr(VV_VIRTNUM)) {
- num = get_vim_var_nr(VV_LNUM);
+ num = (int)get_vim_var_nr(VV_LNUM);
}
} else {
- num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum);
+ num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0 : wp->w_cursor.lnum;
}
break;
@@ -1544,13 +1528,12 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
? 0 : (int)wp->w_cursor.col + 1))) {
break;
}
- num = (long)virtcol;
+ num = virtcol;
break;
}
case STL_PERCENTAGE:
- num = (int)(((long)wp->w_cursor.lnum * 100L) /
- (long)wp->w_buffer->b_ml.ml_line_count);
+ num = ((wp->w_cursor.lnum * 100) / wp->w_buffer->b_ml.ml_line_count);
break;
case STL_ALTPERCENT:
@@ -1578,7 +1561,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Note: The call will only return true if it actually
// appended data to the `buf_tmp` buffer.
- if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) {
+ if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp))) {
str = buf_tmp;
}
break;
@@ -1601,11 +1584,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
base = kNumBaseHexadecimal;
FALLTHROUGH;
case STL_OFFSET: {
- long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL,
- false);
- num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ?
- 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ?
- 0 : (int)wp->w_cursor.col);
+ int l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL,
+ false);
+ num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0
+ ? 0 : l + 1 + ((State & MODE_INSERT) == 0 && empty_line
+ ? 0 : (int)wp->w_cursor.col);
break;
}
case STL_BYTEVAL_X:
@@ -1623,9 +1606,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
case STL_ROFLAG:
case STL_ROFLAG_ALT:
// Overload %r with v:relnum for 'statuscolumn'
- if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) {
+ if (stcp != NULL) {
if (wp->w_p_rnu && !get_vim_var_nr(VV_VIRTNUM)) {
- num = get_vim_var_nr(VV_RELNUM);
+ num = (int)get_vim_var_nr(VV_RELNUM);
}
} else {
itemisflag = true;
@@ -1648,25 +1631,40 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (stcp == NULL) {
break;
}
-
bool fold = opt == STL_FOLDCOL;
- *buf_tmp = NUL;
- for (int i = 0; i <= SIGN_SHOW_MAX; i++) {
- char *p = fold ? stcp->fold_text : stcp->sign_text[i];
- if ((!p || !*p) && *buf_tmp == NUL) {
- break;
+ int width = fold ? (compute_foldcolumn(wp, 0) > 0) : wp->w_scwidth;
+
+ if (width == 0) {
+ break;
+ }
+ foldsignitem = curitem;
+
+ char *p = NULL;
+ if (fold) {
+ size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo,
+ (linenr_T)get_vim_var_nr(VV_LNUM), NULL);
+ stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLF : HLF_FC) + 1);
+ p = out_p;
+ p[n] = NUL;
+ }
+
+ size_t buflen = 0;
+ varnumber_T virtnum = get_vim_var_nr(VV_VIRTNUM);
+ for (int i = 0; i < width; i++) {
+ if (!fold) {
+ SignTextAttrs *sattr = virtnum ? NULL : &stcp->sattrs[i];
+ p = sattr && sattr->text ? sattr->text : " ";
+ stl_items[curitem].minwid = -(sattr && sattr->text
+ ? (stcp->sign_cul_id ? stcp->sign_cul_id : sattr->hl_id)
+ : (stcp->use_cul ? HLF_CLS : HLF_SC) + 1);
}
stl_items[curitem].type = Highlight;
- stl_items[curitem].start = out_p + strlen(buf_tmp);
- stl_items[curitem].minwid = !p || (fold && i) ? 0 : fold ? stcp->fold_attr
- : stcp->sign_attr[i];
+ stl_items[curitem].start = out_p + buflen;
+ xstrlcpy(buf_tmp + buflen, p, TMPLEN - buflen);
+ buflen += strlen(p);
curitem++;
- if (!p || (fold && i)) {
- str = buf_tmp;
- break;
- }
- STRCAT(buf_tmp, p);
}
+ str = buf_tmp;
break;
}
@@ -1773,7 +1771,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// }
- long l = vim_strsize(t);
+ int l = vim_strsize(t);
// If this item is non-empty, record that the last thing
// we put in the output buffer was an item
@@ -1809,6 +1807,13 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
}
minwid = 0;
+ // For a 'statuscolumn' sign or fold item, shift the added items
+ if (foldsignitem >= 0) {
+ ptrdiff_t offset = out_p - stl_items[foldsignitem].start;
+ for (int i = foldsignitem; i < curitem; i++) {
+ stl_items[i].start += offset;
+ }
+ }
} else {
// Note: The negative value denotes a left aligned item.
// Here we switch the minimum width back to a positive value.
@@ -1828,6 +1833,14 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// }
+ // For a 'statuscolumn' sign or fold item, add an item to reset the highlight group
+ if (foldsignitem >= 0) {
+ foldsignitem = -1;
+ stl_items[curitem].type = Highlight;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = 0;
+ }
+
// For left-aligned items, fill any remaining space with the fillchar
for (; l < minwid && out_p < out_end_p; l++) {
MB_CHAR2BYTES(fillchar, out_p);
@@ -1861,8 +1874,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// { Determine how many characters the number will take up when printed
// Note: We have to cast the base because the compiler uses
// unsigned ints for the enum values.
- long num_chars = 1;
- for (long n = num; n >= (int)base; n /= (int)base) {
+ int num_chars = 1;
+ for (int n = num; n >= (int)base; n /= (int)base) {
num_chars++;
}
@@ -1885,11 +1898,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
num_chars += 2;
// How many extra characters there are
- long n = num_chars - maxwid;
+ int n = num_chars - maxwid;
// { Reduce the number by base^n
while (num_chars-- > maxwid) {
- num /= (long)base;
+ num /= (int)base;
}
// }
@@ -1942,8 +1955,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
int width = vim_strsize(out);
// Return truncated width for 'statuscolumn'
- if (stcp != NULL && width > maxwidth) {
- stcp->truncate = width - maxwidth;
+ if (stcp != NULL && width > stcp->width) {
+ stcp->truncate = width - stcp->width;
}
if (maxwidth > 0 && width > maxwidth) {
// Result is too long, must truncate somewhere.
@@ -1977,7 +1990,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// string to find the last character that will fit.
trunc_p = out;
width = 0;
- for (;;) {
+ while (true) {
width += ptr2cells(trunc_p);
if (width >= maxwidth) {
break;
@@ -2009,9 +2022,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Truncate at the truncation point we found
} else {
// { Determine how many bytes to remove
- long trunc_len = 0;
+ int trunc_len = 0;
while (width >= maxwidth) {
- width -= ptr2cells(trunc_p + trunc_len);
+ width -= ptr2cells(trunc_p + trunc_len);
trunc_len += utfc_ptr2len(trunc_p + trunc_len);
}
// }
@@ -2022,17 +2035,6 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Put a `<` to mark where we truncated at
*trunc_p = '<';
-
- if (width + 1 < maxwidth) {
- // Advance the pointer to the end of the string
- trunc_p = trunc_p + strlen(trunc_p);
- }
-
- // Fill up for half a double-wide character.
- while (++width < maxwidth) {
- MB_CHAR2BYTES(fillchar, trunc_p);
- *trunc_p = NUL;
- }
// }
// { Change the start point for items based on
@@ -2040,20 +2042,31 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Note: The offset is one less than the truncation length because
// the truncation marker `<` is not counted.
- long item_offset = trunc_len - 1;
+ int item_offset = trunc_len - 1;
for (int i = item_idx; i < itemcnt; i++) {
// Items starting at or after the end of the truncated section need
// to be moved backwards.
if (stl_items[i].start >= trunc_end_p) {
stl_items[i].start -= item_offset;
+ } else {
// Anything inside the truncated area is set to start
// at the `<` truncation character.
- } else {
stl_items[i].start = trunc_p;
}
}
// }
+
+ if (width + 1 < maxwidth) {
+ // Advance the pointer to the end of the string
+ trunc_p = trunc_p + strlen(trunc_p);
+ }
+
+ // Fill up for half a double-wide character.
+ while (++width < maxwidth) {
+ MB_CHAR2BYTES(fillchar, trunc_p);
+ *trunc_p = NUL;
+ }
}
width = maxwidth;
@@ -2067,8 +2080,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
int num_separators = 0;
for (int i = 0; i < itemcnt; i++) {
if (stl_items[i].type == Separate) {
- // Create an array of the start location for each
- // separator mark.
+ // Create an array of the start location for each separator mark.
stl_separator_locations[num_separators] = i;
num_separators++;
}
@@ -2080,17 +2092,17 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
int final_spaces = (maxwidth - width) -
standard_spaces * (num_separators - 1);
- for (int i = 0; i < num_separators; i++) {
- int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces;
+ for (int l = 0; l < num_separators; l++) {
+ int dislocation = (l == (num_separators - 1)) ? final_spaces : standard_spaces;
dislocation *= utf_char2len(fillchar);
- char *start = stl_items[stl_separator_locations[i]].start;
+ char *start = stl_items[stl_separator_locations[l]].start;
char *seploc = start + dislocation;
STRMOVE(seploc, start);
for (char *s = start; s < seploc;) {
MB_CHAR2BYTES(fillchar, s);
}
- for (int item_idx = stl_separator_locations[i] + 1;
+ for (int item_idx = stl_separator_locations[l] + 1;
item_idx < itemcnt;
item_idx++) {
stl_items[item_idx].start += dislocation;
@@ -2105,7 +2117,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (hltab != NULL) {
*hltab = stl_hltab;
stl_hlrec_t *sp = stl_hltab;
- for (long l = 0; l < itemcnt; l++) {
+ for (int l = 0; l < itemcnt; l++) {
if (stl_items[l].type == Highlight) {
sp->start = stl_items[l].start;
sp->userhl = stl_items[l].minwid;
@@ -2120,7 +2132,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (tabtab != NULL) {
*tabtab = stl_tabtab;
StlClickRecord *cur_tab_rec = stl_tabtab;
- for (long l = 0; l < itemcnt; l++) {
+ for (int l = 0; l < itemcnt; l++) {
if (stl_items[l].type == TabPage) {
cur_tab_rec->start = stl_items[l].start;
if (stl_items[l].minwid == 0) {
diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h
index f7e36f138c..eab7c1aa47 100644
--- a/src/nvim/statusline.h
+++ b/src/nvim/statusline.h
@@ -1,19 +1,16 @@
-#ifndef NVIM_STATUSLINE_H
-#define NVIM_STATUSLINE_H
+#pragma once
#include <stddef.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/statusline_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/statusline_defs.h" // IWYU pragma: export
/// Array defining what should be done when tabline is clicked
-EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL);
+EXTERN StlClickDefinition *tab_page_click_defs INIT( = NULL);
/// Size of the tab_page_click_defs array
-EXTERN size_t tab_page_click_defs_size INIT(= 0);
+EXTERN size_t tab_page_click_defs_size INIT( = 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "statusline.h.generated.h"
#endif
-
-#endif // NVIM_STATUSLINE_H
diff --git a/src/nvim/statusline_defs.h b/src/nvim/statusline_defs.h
index eac9dfd690..c1a54f4f54 100644
--- a/src/nvim/statusline_defs.h
+++ b/src/nvim/statusline_defs.h
@@ -1,9 +1,12 @@
-#ifndef NVIM_STATUSLINE_DEFS_H
-#define NVIM_STATUSLINE_DEFS_H
+#pragma once
+#include <stdbool.h>
#include <stddef.h>
-#include "nvim/macros.h"
+#include "nvim/fold_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/sign_defs.h"
/// Status line click definition
typedef struct {
@@ -23,4 +26,52 @@ typedef struct {
const char *start; ///< Location where region starts.
} StlClickRecord;
-#endif // NVIM_STATUSLINE_DEFS_H
+/// Used for highlighting in the status line.
+typedef struct stl_hlrec stl_hlrec_t;
+struct stl_hlrec {
+ char *start;
+ int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
+};
+
+/// Used for building the status line.
+typedef struct stl_item stl_item_t;
+struct stl_item {
+ // Where the item starts in the status line output buffer
+ char *start;
+ // Function to run for ClickFunc items.
+ char *cmd;
+ // The minimum width of the item
+ int minwid;
+ // The maximum width of the item
+ int maxwid;
+ enum {
+ Normal,
+ Empty,
+ Group,
+ Separate,
+ Highlight,
+ TabPage,
+ ClickFunc,
+ Trunc,
+ } type;
+};
+
+/// Struct to hold info for 'statuscolumn'
+typedef struct statuscol statuscol_T;
+
+struct statuscol {
+ int width; ///< width of the status column
+ int cur_attr; ///< current attributes in text
+ int num_attr; ///< default highlight attr
+ int sign_cul_id; ///< cursorline sign highlight id
+ int truncate; ///< truncated width
+ bool draw; ///< whether to draw the statuscolumn
+ bool use_cul; ///< whether to use cursorline attrs
+ char text[MAXPATHL]; ///< text in status column
+ char *textp; ///< current position in text
+ char *text_end; ///< end of text (the NUL byte)
+ stl_hlrec_t *hlrec; ///< highlight groups
+ stl_hlrec_t *hlrecp; ///< current highlight group
+ foldinfo_T foldinfo; ///< fold information
+ SignTextAttrs *sattrs; ///< sign attributes
+};
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 34b3c38103..a439d11818 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <math.h>
@@ -12,23 +9,56 @@
#include <string.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/charset.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
-#include "nvim/macros.h"
+#include "nvim/globals.h"
+#include "nvim/macros_defs.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/plines.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+static const char e_cannot_mix_positional_and_non_positional_str[]
+ = N_("E1500: Cannot mix positional and non-positional arguments: %s");
+static const char e_fmt_arg_nr_unused_str[]
+ = N_("E1501: format argument %d unused in $-style format: %s");
+static const char e_positional_num_field_spec_reused_str_str[]
+ = N_("E1502: Positional argument %d used as field width reused as different type: %s/%s");
+static const char e_positional_nr_out_of_bounds_str[]
+ = N_("E1503: Positional argument %d out of bounds: %s");
+static const char e_positional_arg_num_type_inconsistent_str_str[]
+ = N_("E1504: Positional argument %d type used inconsistently: %s/%s");
+static const char e_invalid_format_specifier_str[]
+ = N_("E1505: Invalid format specifier: %s");
+static const char e_aptypes_is_null_nr_str[]
+ = "E1507: Internal error: ap_types or ap_types[idx] is NULL: %d: %s";
+
+static const char typename_unknown[] = N_("unknown");
+static const char typename_int[] = N_("int");
+static const char typename_longint[] = N_("long int");
+static const char typename_longlongint[] = N_("long long int");
+static const char typename_signedsizet[] = N_("signed size_t");
+static const char typename_unsignedint[] = N_("unsigned int");
+static const char typename_unsignedlongint[] = N_("unsigned long int");
+static const char typename_unsignedlonglongint[] = N_("unsigned long long int");
+static const char typename_sizet[] = N_("size_t");
+static const char typename_pointer[] = N_("pointer");
+static const char typename_percent[] = N_("percent");
+static const char typename_char[] = N_("char");
+static const char typename_string[] = N_("string");
+static const char typename_float[] = N_("float");
/// Copy up to `len` bytes of `string` into newly allocated memory and
/// terminate with a NUL. The allocated memory always has size `len + 1`, even
@@ -350,18 +380,6 @@ void del_trailing_spaces(char *ptr)
}
}
-#if !defined(HAVE_STRNLEN)
-size_t xstrnlen(const char *s, size_t n)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
-{
- const char *end = memchr(s, '\0', n);
- if (end == NULL) {
- return n;
- }
- return (size_t)(end - s);
-}
-#endif
-
#if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP))
// Compare two strings, ignoring case, using current locale.
// Doesn't work for multi-byte characters.
@@ -371,7 +389,7 @@ int vim_stricmp(const char *s1, const char *s2)
{
int i;
- for (;;) {
+ while (true) {
i = (int)TOLOWER_LOC((uint8_t)(*s1)) - (int)TOLOWER_LOC((uint8_t)(*s2));
if (i != 0) {
return i; // this character different
@@ -411,6 +429,13 @@ int vim_strnicmp(const char *s1, const char *s2, size_t len)
}
#endif
+/// Case-insensitive `strequal`.
+bool striequal(const char *a, const char *b)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (a == NULL && b == NULL) || (a && b && STRICMP(a, b) == 0);
+}
+
/// strchr() version which handles multibyte strings
///
/// @param[in] string String to search in.
@@ -455,10 +480,8 @@ void sort_strings(char **files, int count)
bool has_non_ascii(const char *s)
FUNC_ATTR_PURE
{
- const char *p;
-
if (s != NULL) {
- for (p = s; *p != NUL; p++) {
+ for (const char *p = s; *p != NUL; p++) {
if ((uint8_t)(*p) >= 128) {
return true;
}
@@ -498,13 +521,10 @@ static const char *const e_printf =
/// Get number argument from idxp entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
-/// insufficient entries.
+/// Will give an error message for Vimscript entry with invalid type or for insufficient entries.
///
-/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN
-/// value.
-/// @param[in,out] idxp Index in a list. Will be incremented. Indexing starts
-/// at 1.
+/// @param[in] tvs List of Vimscript values. List is terminated by VAR_UNKNOWN value.
+/// @param[in,out] idxp Index in a list. Will be incremented. Indexing starts at 1.
///
/// @return Number value or 0 in case of error.
static varnumber_T tv_nr(typval_T *tvs, int *idxp)
@@ -528,10 +548,10 @@ static varnumber_T tv_nr(typval_T *tvs, int *idxp)
/// Get string argument from idxp entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
+/// Will give an error message for Vimscript entry with invalid type or for
/// insufficient entries.
///
-/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN
+/// @param[in] tvs List of Vimscript values. List is terminated by VAR_UNKNOWN
/// value.
/// @param[in,out] idxp Index in a list. Will be incremented.
/// @param[out] tofree If the idxp entry in tvs is not a String or a Number,
@@ -562,7 +582,7 @@ static const char *tv_str(typval_T *tvs, int *idxp, char **const tofree)
/// Get pointer argument from the next entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
+/// Will give an error message for Vimscript entry with invalid type or for
/// insufficient entries.
///
/// @param[in] tvs List of typval_T values.
@@ -573,7 +593,7 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
#define OFF(attr) offsetof(union typval_vval_union, attr)
- STATIC_ASSERT(OFF(v_string) == OFF(v_list) // -V568
+ STATIC_ASSERT(OFF(v_string) == OFF(v_list)
&& OFF(v_string) == OFF(v_dict)
&& OFF(v_string) == OFF(v_partial)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list)
@@ -593,11 +613,10 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp)
/// Get float argument from idxp entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
+/// Will give an error message for Vimscript entry with invalid type or for
/// insufficient entries.
///
-/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN
-/// value.
+/// @param[in] tvs List of Vimscript values. List is terminated by VAR_UNKNOWN value.
/// @param[in,out] idxp Index in a list. Will be incremented.
///
/// @return Floating-point value or zero in case of error.
@@ -719,23 +738,608 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap)
return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL);
}
+enum {
+ TYPE_UNKNOWN = -1,
+ TYPE_INT,
+ TYPE_LONGINT,
+ TYPE_LONGLONGINT,
+ TYPE_SIGNEDSIZET,
+ TYPE_UNSIGNEDINT,
+ TYPE_UNSIGNEDLONGINT,
+ TYPE_UNSIGNEDLONGLONGINT,
+ TYPE_SIZET,
+ TYPE_POINTER,
+ TYPE_PERCENT,
+ TYPE_CHAR,
+ TYPE_STRING,
+ TYPE_FLOAT,
+};
+
+/// Types that can be used in a format string
+static int format_typeof(const char *type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // allowed values: \0, h, l, L
+ char length_modifier = '\0';
+
+ // current conversion specifier character
+ char fmt_spec = '\0';
+
+ // parse 'h', 'l', 'll' and 'z' length modifiers
+ if (*type == 'h' || *type == 'l' || *type == 'z') {
+ length_modifier = *type;
+ type++;
+ if (length_modifier == 'l' && *type == 'l') {
+ // double l = long long
+ length_modifier = 'L';
+ type++;
+ }
+ }
+ fmt_spec = *type;
+
+ // common synonyms:
+ switch (fmt_spec) {
+ case 'i':
+ fmt_spec = 'd'; break;
+ case '*':
+ fmt_spec = 'd'; length_modifier = 'h'; break;
+ case 'D':
+ fmt_spec = 'd'; length_modifier = 'l'; break;
+ case 'U':
+ fmt_spec = 'u'; length_modifier = 'l'; break;
+ case 'O':
+ fmt_spec = 'o'; length_modifier = 'l'; break;
+ default:
+ break;
+ }
+
+ // get parameter value, do initial processing
+ switch (fmt_spec) {
+ // '%' and 'c' behave similar to 's' regarding flags and field
+ // widths
+ case '%':
+ return TYPE_PERCENT;
+
+ case 'c':
+ return TYPE_CHAR;
+
+ case 's':
+ case 'S':
+ return TYPE_STRING;
+
+ case 'd':
+ case 'u':
+ case 'b':
+ case 'B':
+ case 'o':
+ case 'x':
+ case 'X':
+ case 'p':
+ // NOTE: the u, b, o, x, X and p conversion specifiers
+ // imply the value is unsigned; d implies a signed
+ // value
+
+ // 0 if numeric argument is zero (or if pointer is
+ // NULL for 'p'), +1 if greater than zero (or nonzero
+ // for unsigned arguments), -1 if negative (unsigned
+ // argument is never negative)
+
+ if (fmt_spec == 'p') {
+ return TYPE_POINTER;
+ } else if (fmt_spec == 'b' || fmt_spec == 'B') {
+ return TYPE_UNSIGNEDLONGLONGINT;
+ } else if (fmt_spec == 'd') {
+ // signed
+ switch (length_modifier) {
+ case '\0':
+ case 'h':
+ // char and short arguments are passed as int.
+ return TYPE_INT;
+ case 'l':
+ return TYPE_LONGINT;
+ case 'L':
+ return TYPE_LONGLONGINT;
+ case 'z':
+ return TYPE_SIGNEDSIZET;
+ }
+ } else {
+ // unsigned
+ switch (length_modifier) {
+ case '\0':
+ case 'h':
+ return TYPE_UNSIGNEDINT;
+ case 'l':
+ return TYPE_UNSIGNEDLONGINT;
+ case 'L':
+ return TYPE_UNSIGNEDLONGLONGINT;
+ case 'z':
+ return TYPE_SIZET;
+ }
+ }
+ break;
+
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ return TYPE_FLOAT;
+ }
+
+ return TYPE_UNKNOWN;
+}
+
+static char *format_typename(const char *type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (format_typeof(type)) {
+ case TYPE_INT:
+ return _(typename_int);
+ case TYPE_LONGINT:
+ return _(typename_longint);
+ case TYPE_LONGLONGINT:
+ return _(typename_longlongint);
+ case TYPE_UNSIGNEDINT:
+ return _(typename_unsignedint);
+ case TYPE_SIGNEDSIZET:
+ return _(typename_signedsizet);
+ case TYPE_UNSIGNEDLONGINT:
+ return _(typename_unsignedlongint);
+ case TYPE_UNSIGNEDLONGLONGINT:
+ return _(typename_unsignedlonglongint);
+ case TYPE_SIZET:
+ return _(typename_sizet);
+ case TYPE_POINTER:
+ return _(typename_pointer);
+ case TYPE_PERCENT:
+ return _(typename_percent);
+ case TYPE_CHAR:
+ return _(typename_char);
+ case TYPE_STRING:
+ return _(typename_string);
+ case TYPE_FLOAT:
+ return _(typename_float);
+ }
+
+ return _(typename_unknown);
+}
+
+static int adjust_types(const char ***ap_types, int arg, int *num_posarg, const char *type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*ap_types == NULL || *num_posarg < arg) {
+ const char **new_types = *ap_types == NULL
+ ? xcalloc(sizeof(const char *), (size_t)arg)
+ : xrealloc(*ap_types, (size_t)arg * sizeof(const char *));
+
+ for (int idx = *num_posarg; idx < arg; idx++) {
+ new_types[idx] = NULL;
+ }
+
+ *ap_types = new_types;
+ *num_posarg = arg;
+ }
+
+ if ((*ap_types)[arg - 1] != NULL) {
+ if ((*ap_types)[arg - 1][0] == '*' || type[0] == '*') {
+ const char *pt = type;
+ if (pt[0] == '*') {
+ pt = (*ap_types)[arg - 1];
+ }
+
+ if (pt[0] != '*') {
+ switch (pt[0]) {
+ case 'd':
+ case 'i':
+ break;
+ default:
+ semsg(_(e_positional_num_field_spec_reused_str_str), arg,
+ format_typename((*ap_types)[arg - 1]), format_typename(type));
+ return FAIL;
+ }
+ }
+ } else {
+ if (format_typeof(type) != format_typeof((*ap_types)[arg - 1])) {
+ semsg(_(e_positional_arg_num_type_inconsistent_str_str), arg,
+ format_typename(type), format_typename((*ap_types)[arg - 1]));
+ return FAIL;
+ }
+ }
+ }
+
+ (*ap_types)[arg - 1] = type;
+
+ return OK;
+}
+
+static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *fmt, typval_T *tvs)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ const char *p = fmt;
+ const char *arg = NULL;
+
+ int any_pos = 0;
+ int any_arg = 0;
+
+#define CHECK_POS_ARG \
+ do { \
+ if (any_pos && any_arg) { \
+ semsg(_(e_cannot_mix_positional_and_non_positional_str), fmt); \
+ goto error; \
+ } \
+ } while (0);
+
+ if (p == NULL) {
+ return OK;
+ }
+
+ while (*p != NUL) {
+ if (*p != '%') {
+ char *q = strchr(p + 1, '%');
+ size_t n = (q == NULL) ? strlen(p) : (size_t)(q - p);
+
+ p += n;
+ } else {
+ // allowed values: \0, h, l, L
+ char length_modifier = '\0';
+
+ // variable for positional arg
+ int pos_arg = -1;
+
+ p++; // skip '%'
+
+ // First check to see if we find a positional
+ // argument specifier
+ const char *ptype = p;
+
+ while (ascii_isdigit(*ptype)) {
+ ptype++;
+ }
+
+ if (*ptype == '$') {
+ if (*p == '0') {
+ // 0 flag at the wrong place
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+
+ // Positional argument
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ pos_arg = (int)uj;
+
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ p++;
+ }
+
+ // parse flags
+ while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
+ || *p == '#' || *p == '\'') {
+ switch (*p) {
+ case '0':
+ break;
+ case '-':
+ break;
+ case '+':
+ break;
+ case ' ': // If both the ' ' and '+' flags appear, the ' '
+ // flag should be ignored
+ break;
+ case '#':
+ break;
+ case '\'':
+ break;
+ }
+ p++;
+ }
+ // If the '0' and '-' flags both appear, the '0' flag should be
+ // ignored.
+
+ // parse field width
+ if (*(arg = p) == '*') {
+ p++;
+
+ if (ascii_isdigit((int)(*p))) {
+ // Positional argument field width
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p != '$') {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ } else {
+ p++;
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ if (adjust_types(ap_types, (int)uj, num_posarg, arg) == FAIL) {
+ goto error;
+ }
+ }
+ } else {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ } else if (ascii_isdigit((int)(*p))) {
+ // size_t could be wider than unsigned int; make sure we treat
+ // argument like common implementations do
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p == '$') {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ }
+
+ // parse precision
+ if (*p == '.') {
+ p++;
+
+ if (*(arg = p) == '*') {
+ p++;
+
+ if (ascii_isdigit((int)(*p))) {
+ // Parse precision
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p == '$') {
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ p++;
+
+ if (adjust_types(ap_types, (int)uj, num_posarg, arg) == FAIL) {
+ goto error;
+ }
+ } else {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ } else {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ } else if (ascii_isdigit((int)(*p))) {
+ // size_t could be wider than unsigned int; make sure we
+ // treat argument like common implementations do
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p == '$') {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ }
+ }
+
+ if (pos_arg != -1) {
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ ptype = p;
+ }
+
+ // parse 'h', 'l', 'll' and 'z' length modifiers
+ if (*p == 'h' || *p == 'l' || *p == 'z') {
+ length_modifier = *p;
+ p++;
+ if (length_modifier == 'l' && *p == 'l') {
+ // double l = long long
+ // length_modifier = 'L';
+ p++;
+ }
+ }
+
+ switch (*p) {
+ // Check for known format specifiers. % is special!
+ case 'i':
+ case '*':
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'D':
+ case 'U':
+ case 'O':
+ case 'x':
+ case 'X':
+ case 'b':
+ case 'B':
+ case 'c':
+ case 's':
+ case 'S':
+ case 'p':
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ if (pos_arg != -1) {
+ if (adjust_types(ap_types, pos_arg, num_posarg, ptype) == FAIL) {
+ goto error;
+ }
+ } else {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ break;
+
+ default:
+ if (pos_arg != -1) {
+ semsg(_(e_cannot_mix_positional_and_non_positional_str), fmt);
+ goto error;
+ }
+ }
+
+ if (*p != NUL) {
+ p++; // step over the just processed conversion specifier
+ }
+ }
+ }
+
+ for (int arg_idx = 0; arg_idx < *num_posarg; arg_idx++) {
+ if ((*ap_types)[arg_idx] == NULL) {
+ semsg(_(e_fmt_arg_nr_unused_str), arg_idx + 1, fmt);
+ goto error;
+ }
+
+ if (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN) {
+ semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt);
+ goto error;
+ }
+ }
+
+ return OK;
+
+error:
+ xfree(*ap_types);
+ *ap_types = NULL;
+ *num_posarg = 0;
+ return FAIL;
+}
+
+static void skip_to_arg(const char **ap_types, va_list ap_start, va_list *ap, int *arg_idx,
+ int *arg_cur, const char *fmt)
+ FUNC_ATTR_NONNULL_ARG(3, 4, 5)
+{
+ int arg_min = 0;
+
+ if (*arg_cur + 1 == *arg_idx) {
+ (*arg_cur)++;
+ (*arg_idx)++;
+ return;
+ }
+
+ if (*arg_cur >= *arg_idx) {
+ // Reset ap to ap_start and skip arg_idx - 1 types
+ va_end(*ap);
+ va_copy(*ap, ap_start);
+ } else {
+ // Skip over any we should skip
+ arg_min = *arg_cur;
+ }
+
+ for (*arg_cur = arg_min; *arg_cur < *arg_idx - 1; (*arg_cur)++) {
+ if (ap_types == NULL || ap_types[*arg_cur] == NULL) {
+ siemsg(e_aptypes_is_null_nr_str, fmt, *arg_cur);
+ return;
+ }
+
+ const char *p = ap_types[*arg_cur];
+
+ int fmt_type = format_typeof(p);
+
+ // get parameter value, do initial processing
+ switch (fmt_type) {
+ case TYPE_PERCENT:
+ case TYPE_UNKNOWN:
+ break;
+
+ case TYPE_CHAR:
+ va_arg(*ap, int);
+ break;
+
+ case TYPE_STRING:
+ va_arg(*ap, const char *);
+ break;
+
+ case TYPE_POINTER:
+ va_arg(*ap, void *);
+ break;
+
+ case TYPE_INT:
+ va_arg(*ap, int);
+ break;
+
+ case TYPE_LONGINT:
+ va_arg(*ap, long);
+ break;
+
+ case TYPE_LONGLONGINT:
+ va_arg(*ap, long long); // NOLINT(runtime/int)
+ break;
+
+ case TYPE_SIGNEDSIZET: // implementation-defined, usually ptrdiff_t
+ va_arg(*ap, ptrdiff_t);
+ break;
+
+ case TYPE_UNSIGNEDINT:
+ va_arg(*ap, unsigned);
+ break;
+
+ case TYPE_UNSIGNEDLONGINT:
+ va_arg(*ap, unsigned long);
+ break;
+
+ case TYPE_UNSIGNEDLONGLONGINT:
+ va_arg(*ap, unsigned long long); // NOLINT(runtime/int)
+ break;
+
+ case TYPE_SIZET:
+ va_arg(*ap, size_t);
+ break;
+
+ case TYPE_FLOAT:
+ va_arg(*ap, double);
+ break;
+ }
+ }
+
+ // Because we know that after we return from this call,
+ // a va_arg() call is made, we can pre-emptively
+ // increment the current argument index.
+ (*arg_cur)++;
+ (*arg_idx)++;
+}
+
/// Write formatted value to the string
///
/// @param[out] str String to write to.
/// @param[in] str_m String length.
/// @param[in] fmt String format.
/// @param[in] ap Values that should be formatted. Ignored if tvs is not NULL.
-/// @param[in] tvs Values that should be formatted, for printf() VimL
+/// @param[in] tvs Values that should be formatted, for printf() Vimscript
/// function. Must be NULL in other cases.
///
/// @return Number of bytes excluding NUL byte that would be written to the
/// string if str_m was greater or equal to the return value.
-int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, typval_T *const tvs)
+int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_start,
+ typval_T *const tvs)
{
size_t str_l = 0;
bool str_avail = str_l < str_m;
const char *p = fmt;
+ int arg_cur = 0;
+ int num_posarg = 0;
int arg_idx = 1;
+ va_list ap;
+ const char **ap_types = NULL;
+
+ if (parse_fmt_types(&ap_types, &num_posarg, fmt, tvs) == FAIL) {
+ return 0;
+ }
+
+ va_copy(ap, ap_start);
if (!p) {
p = "";
@@ -791,8 +1395,31 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// buffer for 's' and 'S' specs
char *tofree = NULL;
+ // variable for positional arg
+ int pos_arg = -1;
+
p++; // skip '%'
+ // First check to see if we find a positional
+ // argument specifier
+ const char *ptype = p;
+
+ while (ascii_isdigit(*ptype)) {
+ ptype++;
+ }
+
+ if (*ptype == '$') {
+ // Positional argument
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ pos_arg = (int)uj;
+
+ p++;
+ }
+
// parse flags
while (true) {
switch (*p) {
@@ -819,7 +1446,25 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// parse field width
if (*p == '*') {
p++;
- const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
+
+ if (ascii_isdigit((int)(*p))) {
+ // Positional argument field width
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ arg_idx = (int)uj;
+
+ p++;
+ }
+
+ const int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
+
if (j >= 0) {
min_field_width = (size_t)j;
} else {
@@ -829,10 +1474,10 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
} else if (ascii_isdigit((int)(*p))) {
// size_t could be wider than unsigned int; make sure we treat
// argument like common implementations do
- unsigned int uj = (unsigned)(*p++ - '0');
+ unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
- uj = 10 * uj + (unsigned int)(*p++ - '0');
+ uj = 10 * uj + (unsigned)(*p++ - '0');
}
min_field_width = uj;
}
@@ -841,24 +1486,43 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
if (*p == '.') {
p++;
precision_specified = 1;
- if (*p == '*') {
- const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
+
+ if (ascii_isdigit((int)(*p))) {
+ // size_t could be wider than unsigned int; make sure we
+ // treat argument like common implementations do
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ precision = uj;
+ } else if (*p == '*') {
p++;
+
+ if (ascii_isdigit((int)(*p))) {
+ // positional argument
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ arg_idx = (int)uj;
+
+ p++;
+ }
+
+ const int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
+
if (j >= 0) {
precision = (size_t)j;
} else {
precision_specified = 0;
precision = 0;
}
- } else if (ascii_isdigit((int)(*p))) {
- // size_t could be wider than unsigned int; make sure we
- // treat argument like common implementations do
- unsigned int uj = (unsigned)(*p++ - '0');
-
- while (ascii_isdigit((int)(*p))) {
- uj = 10 * uj + (unsigned int)(*p++ - '0');
- }
- precision = uj;
}
}
@@ -866,8 +1530,9 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
if (*p == 'h' || *p == 'l' || *p == 'z') {
length_modifier = *p;
p++;
- if (length_modifier == 'l' && *p == 'l') { // ll, encoded as 2
- length_modifier = '2';
+ if (length_modifier == 'l' && *p == 'l') {
+ // double l = long long
+ length_modifier = 'L';
p++;
}
}
@@ -897,10 +1562,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
case 'x':
case 'X':
if (tvs && length_modifier == '\0') {
- length_modifier = '2';
+ length_modifier = 'L';
}
}
+ if (pos_arg != -1) {
+ arg_idx = pos_arg;
+ }
+
// get parameter value, do initial processing
switch (fmt_spec) {
// '%' and 'c' behave similar to 's' regarding flags and field widths
@@ -915,7 +1584,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
break;
case 'c': {
- const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
+ const int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
+
// standard demands unsigned char
uchar_arg = (unsigned char)j;
str_arg = (char *)&uchar_arg;
@@ -924,8 +1598,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
case 's':
case 'S':
- str_arg = tvs ? tv_str(tvs, &arg_idx, &tofree)
- : va_arg(ap, const char *);
+ str_arg = (tvs
+ ? tv_str(tvs, &arg_idx, &tofree)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, const char *)));
+
if (!str_arg) {
str_arg = "[NULL]";
str_arg_l = 6;
@@ -946,10 +1624,10 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
- str_arg);
}
if (fmt_spec == 'S') {
- char *p1;
+ const char *p1;
size_t i;
- for (i = 0, p1 = (char *)str_arg; *p1; p1 += utfc_ptr2len(p1)) {
+ for (i = 0, p1 = str_arg; *p1; p1 += utfc_ptr2len(p1)) {
size_t cell = (size_t)utf_ptr2cells(p1);
if (precision_specified && i + cell > precision) {
break;
@@ -992,7 +1670,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
const void *ptr_arg = NULL;
if (fmt_spec == 'p') {
- ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *);
+ ptr_arg = (tvs
+ ? tv_ptr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, void *)));
+
if (ptr_arg) {
arg_sign = 1;
}
@@ -1000,23 +1683,41 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// signed
switch (length_modifier) {
case '\0':
- arg = (int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int));
+ arg = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
break;
case 'h':
// char and short arguments are passed as int16_t
- arg = (int16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int));
+ arg = (int16_t)
+ (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
break;
case 'l':
- arg = (tvs ? (long)tv_nr(tvs, &arg_idx) : va_arg(ap, long));
+ arg = (tvs
+ ? (long)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, long)));
break;
- case '2':
- arg = (
- tvs
- ? (long long)tv_nr(tvs, &arg_idx) // NOLINT (runtime/int)
- : va_arg(ap, long long)); // NOLINT (runtime/int)
+ case 'L':
+ arg = (tvs
+ ? (long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, long long))); // NOLINT(runtime/int)
break;
- case 'z':
- arg = (tvs ? (ptrdiff_t)tv_nr(tvs, &arg_idx) : va_arg(ap, ptrdiff_t));
+ case 'z': // implementation-defined, usually ptrdiff_t
+ arg = (tvs
+ ? (ptrdiff_t)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, ptrdiff_t)));
break;
}
if (arg > 0) {
@@ -1028,23 +1729,40 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// unsigned
switch (length_modifier) {
case '\0':
- uarg = (unsigned int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int));
+ uarg = (tvs
+ ? (unsigned)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned)));
break;
case 'h':
- uarg = (uint16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int));
+ uarg = (uint16_t)
+ (tvs
+ ? (unsigned)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned)));
break;
case 'l':
- uarg = (tvs ? (unsigned long)tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned long));
+ uarg = (tvs
+ ? (unsigned long)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned long)));
break;
- case '2':
- uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int)
- tvs
- ? ((unsigned long long) // NOLINT (runtime/int)
- tv_nr(tvs, &arg_idx))
- : va_arg(ap, unsigned long long)); // NOLINT (runtime/int)
+ case 'L':
+ uarg = (tvs
+ ? (unsigned long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned long long))); // NOLINT(runtime/int)
break;
case 'z':
- uarg = (tvs ? (size_t)tv_nr(tvs, &arg_idx) : va_arg(ap, size_t));
+ uarg = (tvs
+ ? (size_t)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, size_t)));
break;
}
arg_sign = (uarg != 0);
@@ -1179,7 +1897,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
char format[40];
int remove_trailing_zeroes = false;
- double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double);
+ double f = (tvs
+ ? tv_float(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, double)));
+
double abs_f = f < 0 ? -f : f;
if (fmt_spec == 'g' || fmt_spec == 'G') {
@@ -1234,7 +1957,6 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
assert(str_arg_l < sizeof(tmp));
if (remove_trailing_zeroes) {
- int i;
char *tp;
// using %g or %G: remove superfluous zeroes
@@ -1249,7 +1971,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
STRMOVE(tp + 1, tp + 2);
str_arg_l--;
}
- i = (tp[1] == '-') ? 2 : 1;
+ int i = (tp[1] == '-') ? 2 : 1;
while (tp[i] == '0') {
// change "1.0e07" to "1.0e7"
STRMOVE(tp + i, tp + i + 1);
@@ -1397,10 +2119,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
}
- if (tvs && tvs[arg_idx - 1].v_type != VAR_UNKNOWN) {
+ if (tvs != NULL
+ && tvs[num_posarg != 0 ? num_posarg : arg_idx - 1].v_type != VAR_UNKNOWN) {
emsg(_("E767: Too many arguments to printf()"));
}
+ xfree(ap_types);
+ va_end(ap);
+
// return the number of characters formatted (excluding trailing nul
// character); that is, the number of characters that would have been
// written to the buffer if it were large enough.
@@ -1441,20 +2167,17 @@ int kv_do_printf(StringBuilder *str, const char *fmt, ...)
///
/// @return the allocated string.
char *reverse_text(char *s)
- FUNC_ATTR_NONNULL_RET
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- // Reverse the pattern.
size_t len = strlen(s);
char *rev = xmalloc(len + 1);
- size_t rev_i = len;
- for (size_t s_i = 0; s_i < len; s_i++) {
+ for (size_t s_i = 0, rev_i = len; s_i < len; s_i++) {
const int mb_len = utfc_ptr2len(s + s_i);
rev_i -= (size_t)mb_len;
memmove(rev + rev_i, s + s_i, (size_t)mb_len);
s_i += (size_t)mb_len - 1;
}
rev[len] = NUL;
-
return rev;
}
@@ -1468,7 +2191,7 @@ char *reverse_text(char *s)
/// @return [allocated] Copy of the string.
char *strrep(const char *src, const char *what, const char *rep)
{
- char *pos = (char *)src;
+ const char *pos = src;
size_t whatlen = strlen(what);
// Count occurrences
@@ -1499,3 +2222,761 @@ char *strrep(const char *src, const char *what, const char *rep)
return ret;
}
+
+/// Implementation of "byteidx()" and "byteidxcomp()" functions
+static void byteidx_common(typval_T *argvars, typval_T *rettv, int comp)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T utf16idx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ utf16idx = tv_get_bool_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (utf16idx < 0 || utf16idx > 1) {
+ semsg(_(e_using_number_as_bool_nr), utf16idx);
+ return;
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (comp) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *t = str;
+ for (; idx > 0; idx--) {
+ if (*t == NUL) { // EOL reached.
+ return;
+ }
+ if (utf16idx) {
+ const int clen = ptr2len(t);
+ const int c = (clen > 1) ? utf_ptr2char(t) : *t;
+ if (c > 0xFFFF) {
+ idx--;
+ }
+ }
+ if (idx > 0) {
+ t += ptr2len(t);
+ }
+ }
+ rettv->vval.v_number = (varnumber_T)(t - str);
+}
+
+/// "byteidx()" function
+void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ byteidx_common(argvars, rettv, false);
+}
+
+/// "byteidxcomp()" function
+void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ byteidx_common(argvars, rettv, true);
+}
+
+/// "charidx()" function
+void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_number_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_bool_arg(argvars, 3) == FAIL)) {
+ return;
+ }
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ varnumber_T utf16idx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ utf16idx = tv_get_bool(&argvars[3]);
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (countcc) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *p;
+ int len;
+ for (p = str, len = 0; utf16idx ? idx >= 0 : p <= str + idx; len++) {
+ if (*p == NUL) {
+ // If the index is exactly the number of bytes or utf-16 code units
+ // in the string then return the length of the string in characters.
+ if (utf16idx ? (idx == 0) : (p == (str + idx))) {
+ rettv->vval.v_number = len;
+ }
+ return;
+ }
+ if (utf16idx) {
+ idx--;
+ const int clen = ptr2len(p);
+ const int c = (clen > 1) ? utf_ptr2char(p) : *p;
+ if (c > 0xFFFF) {
+ idx--;
+ }
+ }
+ p += ptr2len(p);
+ }
+
+ rettv->vval.v_number = len > 0 ? len - 1 : 0;
+}
+
+/// "str2list()" function
+void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ const char *p = tv_get_string(&argvars[0]);
+
+ for (; *p != NUL; p += utf_ptr2len(p)) {
+ tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
+ }
+}
+
+/// "str2nr()" function
+void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ int base = 10;
+ int what = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ base = (int)tv_get_number(&argvars[1]);
+ if (base != 2 && base != 8 && base != 10 && base != 16) {
+ emsg(_(e_invarg));
+ return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) {
+ what |= STR2NR_QUOTE;
+ }
+ }
+
+ char *p = skipwhite(tv_get_string(&argvars[0]));
+ bool isneg = (*p == '-');
+ if (*p == '+' || *p == '-') {
+ p = skipwhite(p + 1);
+ }
+ switch (base) {
+ case 2:
+ what |= STR2NR_BIN | STR2NR_FORCE;
+ break;
+ case 8:
+ what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE;
+ break;
+ case 16:
+ what |= STR2NR_HEX | STR2NR_FORCE;
+ break;
+ }
+ varnumber_T n;
+ vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false, NULL);
+ // Text after the number is silently ignored.
+ if (isneg) {
+ rettv->vval.v_number = -n;
+ } else {
+ rettv->vval.v_number = n;
+ }
+}
+
+/// "strgetchar()" function
+void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+ bool error = false;
+ varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+
+ const size_t len = strlen(str);
+ size_t byteidx = 0;
+
+ while (charidx >= 0 && byteidx < len) {
+ if (charidx == 0) {
+ rettv->vval.v_number = utf_ptr2char(str + byteidx);
+ break;
+ }
+ charidx--;
+ byteidx += (size_t)utf_ptr2len(str + byteidx);
+ }
+}
+
+/// "stridx()" function
+void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
+ const char *const haystack_start = haystack;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
+ &error);
+ if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
+ return;
+ }
+ if (start_idx >= 0) {
+ haystack += start_idx;
+ }
+ }
+
+ const char *pos = strstr(haystack, needle);
+ if (pos != NULL) {
+ rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
+ }
+}
+
+/// "string()" function
+void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = encode_tv2string(&argvars[0], NULL);
+}
+
+/// "strlen()" function
+void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
+}
+
+static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc)
+{
+ const char *s = tv_get_string(&argvars[0]);
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char **pp);
+
+ func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
+ while (*s != NUL) {
+ func_mb_ptr2char_adv(&s);
+ len++;
+ }
+ rettv->vval.v_number = len;
+}
+
+/// "strcharlen()" function
+void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ strchar_common(argvars, rettv, true);
+}
+
+/// "strchars()" function
+void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ varnumber_T skipcc = false;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ skipcc = tv_get_bool_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+ if (skipcc < 0 || skipcc > 1) {
+ semsg(_(e_using_number_as_bool_nr), skipcc);
+ return;
+ }
+ }
+
+ strchar_common(argvars, rettv, skipcc);
+}
+
+/// "strutf16len()" function
+void f_strutf16len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[1]);
+ }
+
+ const char *s = tv_get_string(&argvars[0]);
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char **pp);
+
+ func_mb_ptr2char_adv = countcc ? mb_cptr2char_adv : mb_ptr2char_adv;
+ while (*s != NUL) {
+ const int ch = func_mb_ptr2char_adv(&s);
+ if (ch > 0xFFFF) {
+ len++;
+ }
+ len++;
+ }
+ rettv->vval.v_number = len;
+}
+
+/// "strdisplaywidth()" function
+void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+ int col = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ col = (int)tv_get_number(&argvars[1]);
+ }
+
+ rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col);
+}
+
+/// "strwidth()" function
+void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = (varnumber_T)mb_string2cells(s);
+}
+
+/// "strcharpart()" function
+void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ int nbyte = 0;
+ varnumber_T skipcc = false;
+ bool error = false;
+ varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
+ if (!error) {
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ skipcc = tv_get_bool_chk(&argvars[3], &error);
+ if (error) {
+ return;
+ }
+ if (skipcc < 0 || skipcc > 1) {
+ semsg(_(e_using_number_as_bool_nr), skipcc);
+ return;
+ }
+ }
+
+ if (nchar > 0) {
+ while (nchar > 0 && (size_t)nbyte < slen) {
+ if (skipcc) {
+ nbyte += utfc_ptr2len(p + nbyte);
+ } else {
+ nbyte += utf_ptr2len(p + nbyte);
+ }
+ nchar--;
+ }
+ } else {
+ nbyte = (int)nchar;
+ }
+ }
+ int len = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int charlen = (int)tv_get_number(&argvars[2]);
+ while (charlen > 0 && nbyte + len < (int)slen) {
+ int off = nbyte + len;
+
+ if (off < 0) {
+ len += 1;
+ } else {
+ if (skipcc) {
+ len += utfc_ptr2len(p + off);
+ } else {
+ len += utf_ptr2len(p + off);
+ }
+ }
+ charlen--;
+ }
+ } else {
+ len = (int)slen - nbyte; // default: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (nbyte < 0) {
+ len += nbyte;
+ nbyte = 0;
+ } else if ((size_t)nbyte > slen) {
+ nbyte = (int)slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (nbyte + len > (int)slen) {
+ len = (int)slen - nbyte;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmemdupz(p + nbyte, (size_t)len);
+}
+
+/// "strpart()" function
+void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ bool error = false;
+
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ varnumber_T n = tv_get_number_chk(&argvars[1], &error);
+ varnumber_T len;
+ if (error) {
+ len = 0;
+ } else if (argvars[2].v_type != VAR_UNKNOWN) {
+ len = tv_get_number(&argvars[2]);
+ } else {
+ len = (varnumber_T)slen - n; // Default len: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (n < 0) {
+ len += n;
+ n = 0;
+ } else if (n > (varnumber_T)slen) {
+ n = (varnumber_T)slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (n + len > (varnumber_T)slen) {
+ len = (varnumber_T)slen - n;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
+ int off;
+
+ // length in characters
+ for (off = (int)n; off < (int)slen && len > 0; len--) {
+ off += utfc_ptr2len(p + off);
+ }
+ len = off - n;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmemdupz(p + n, (size_t)len);
+}
+
+/// "strridx()" function
+void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
+
+ rettv->vval.v_number = -1;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ const size_t haystack_len = strlen(haystack);
+ ptrdiff_t end_idx;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // Third argument: upper limit for index.
+ end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
+ if (end_idx < 0) {
+ return; // Can never find a match.
+ }
+ } else {
+ end_idx = (ptrdiff_t)haystack_len;
+ }
+
+ const char *lastmatch = NULL;
+ if (*needle == NUL) {
+ // Empty string matches past the end.
+ lastmatch = haystack + end_idx;
+ } else {
+ for (const char *rest = haystack; *rest != NUL; rest++) {
+ rest = strstr(rest, needle);
+ if (rest == NULL || rest > haystack + end_idx) {
+ break;
+ }
+ lastmatch = rest;
+ }
+ }
+
+ if (lastmatch != NULL) {
+ rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
+ }
+}
+
+/// "strtrans()" function
+void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true);
+}
+
+/// "utf16idx()" function
+///
+/// Converts a byte or character offset in a string to the corresponding UTF-16
+/// code unit offset.
+void f_utf16idx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_number_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_bool_arg(argvars, 3) == FAIL)) {
+ return;
+ }
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ varnumber_T charidx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ charidx = tv_get_bool(&argvars[3]);
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (countcc) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *p;
+ int len;
+ int utf16idx = 0;
+ for (p = str, len = 0; charidx ? idx >= 0 : p <= str + idx; len++) {
+ if (*p == NUL) {
+ // If the index is exactly the number of bytes or characters in the
+ // string then return the length of the string in utf-16 code units.
+ if (charidx ? (idx == 0) : (p == (str + idx))) {
+ rettv->vval.v_number = len;
+ }
+ return;
+ }
+ utf16idx = len;
+ const int clen = ptr2len(p);
+ const int c = (clen > 1) ? utf_ptr2char(p) : *p;
+ if (c > 0xFFFF) {
+ len++;
+ }
+ p += ptr2len(p);
+ if (charidx) {
+ idx--;
+ }
+ }
+
+ rettv->vval.v_number = utf16idx;
+}
+
+/// "tolower(string)" function
+void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false);
+}
+
+/// "toupper(string)" function
+void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true);
+}
+
+/// "tr(string, fromstr, tostr)" function
+void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+
+ const char *in_str = tv_get_string(&argvars[0]);
+ const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
+ const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
+
+ // Default return value: empty string.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (fromstr == NULL || tostr == NULL) {
+ return; // Type error; errmsg already given.
+ }
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+
+ // fromstr and tostr have to contain the same number of chars.
+ bool first = true;
+ while (*in_str != NUL) {
+ const char *cpstr = in_str;
+ const int inlen = utfc_ptr2len(in_str);
+ int cplen = inlen;
+ int idx = 0;
+ int fromlen;
+ for (const char *p = fromstr; *p != NUL; p += fromlen) {
+ fromlen = utfc_ptr2len(p);
+ if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) {
+ int tolen;
+ for (p = tostr; *p != NUL; p += tolen) {
+ tolen = utfc_ptr2len(p);
+ if (idx-- == 0) {
+ cplen = tolen;
+ cpstr = p;
+ break;
+ }
+ }
+ if (*p == NUL) { // tostr is shorter than fromstr.
+ goto error;
+ }
+ break;
+ }
+ idx++;
+ }
+
+ if (first && cpstr == in_str) {
+ // Check that fromstr and tostr have the same number of
+ // (multi-byte) characters. Done only once when a character
+ // of in_str doesn't appear in fromstr.
+ first = false;
+ int tolen;
+ for (const char *p = tostr; *p != NUL; p += tolen) {
+ tolen = utfc_ptr2len(p);
+ idx--;
+ }
+ if (idx != 0) {
+ goto error;
+ }
+ }
+
+ ga_grow(&ga, cplen);
+ memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
+ ga.ga_len += cplen;
+
+ in_str += inlen;
+ }
+
+ // add a terminating NUL
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+ return;
+error:
+ semsg(_(e_invarg2), fromstr);
+ ga_clear(&ga);
+}
+
+/// "trim({expr})" function
+void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *head = tv_get_string_buf_chk(&argvars[0], buf1);
+ const char *mask = NULL;
+ const char *prev;
+ const char *p;
+ int dir = 0;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (head == NULL) {
+ return;
+ }
+
+ if (tv_check_for_opt_string_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ if (argvars[1].v_type == VAR_STRING) {
+ mask = tv_get_string_buf_chk(&argvars[1], buf2);
+ if (*mask == NUL) {
+ mask = NULL;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ // leading or trailing characters to trim
+ dir = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (dir < 0 || dir > 2) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
+ }
+
+ if (dir == 0 || dir == 1) {
+ // Trim leading characters
+ while (*head != NUL) {
+ int c1 = utf_ptr2char(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == utf_ptr2char(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ MB_PTR_ADV(head);
+ }
+ }
+
+ const char *tail = head + strlen(head);
+ if (dir == 0 || dir == 2) {
+ // Trim trailing characters
+ for (; tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ int c1 = utf_ptr2char(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == utf_ptr2char(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ }
+ }
+ rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head));
+}
diff --git a/src/nvim/strings.h b/src/nvim/strings.h
index 6ad9daf5bf..d717362f87 100644
--- a/src/nvim/strings.h
+++ b/src/nvim/strings.h
@@ -1,13 +1,14 @@
-#ifndef NVIM_STRINGS_H
-#define NVIM_STRINGS_H
+#pragma once
-#include <stdarg.h>
-#include <stdbool.h>
+#include <stdarg.h> // IWYU pragma: keep
#include <string.h>
+#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/eval/typval.h"
-#include "nvim/types.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/func_attr.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
/// Append string to string and return pointer to the next byte
///
@@ -31,4 +32,23 @@ typedef kvec_t(char) StringBuilder;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "strings.h.generated.h"
#endif
-#endif // NVIM_STRINGS_H
+
+#ifdef HAVE_STRCASECMP
+# define STRICMP(d, s) strcasecmp((char *)(d), (char *)(s))
+#else
+# ifdef HAVE_STRICMP
+# define STRICMP(d, s) stricmp((char *)(d), (char *)(s))
+# else
+# define STRICMP(d, s) vim_stricmp((char *)(d), (char *)(s))
+# endif
+#endif
+
+#ifdef HAVE_STRNCASECMP
+# define STRNICMP(d, s, n) strncasecmp((char *)(d), (char *)(s), (size_t)(n))
+#else
+# ifdef HAVE_STRNICMP
+# define STRNICMP(d, s, n) strnicmp((char *)(d), (char *)(s), (size_t)(n))
+# else
+# define STRNICMP(d, s, n) vim_strnicmp((char *)(d), (char *)(s), (size_t)(n))
+# endif
+#endif
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 05c570e52f..11282ea170 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -1,19 +1,18 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// syntax.c: code for syntax highlighting
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
@@ -21,30 +20,31 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent_c.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
static bool did_syntax_onoff = false;
@@ -58,7 +58,13 @@ static bool did_syntax_onoff = false;
#define SPO_LC_OFF 6 // leading context offset
#define SPO_COUNT 7
-static char e_illegal_arg[] = N_("E390: Illegal argument: %s");
+static const char e_illegal_arg[] = N_("E390: Illegal argument: %s");
+static const char e_contains_argument_not_accepted_here[]
+ = N_("E395: Contains argument not accepted here");
+static const char e_invalid_cchar_value[]
+ = N_("E844: Invalid cchar value");
+static const char e_trailing_char_after_rsb_str_str[]
+ = N_("E890: Trailing char after ']': %s]%s");
// The patterns that are being searched for are stored in a syn_pattern.
// A match item consists of one pattern.
@@ -114,7 +120,7 @@ typedef struct state_item {
int si_end_idx; // group ID for end pattern or zero
int si_ends; // if match ends before si_m_endpos
int si_attr; // attributes in this state
- long si_flags; // HL_HAS_EOL flag in this state, and
+ int si_flags; // HL_HAS_EOL flag in this state, and
// HL_SKIP* for si_next_list
int si_seqnr; // sequence number
int si_cchar; // substitution character for conceal
@@ -231,8 +237,6 @@ static keyentry_T dumkey;
#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char *)&dumkey)))
#define HI2KE(hi) HIKEY2KE((hi)->hi_key)
-// -V:HI2KE:782
-
// To reduce the time spent in keepend(), remember at which level in the state
// stack the first item with "keepend" is present. When "-1", there is no
// "keepend" on the stack.
@@ -257,7 +261,7 @@ static lpos_T next_match_m_endpos; // position for end of next match
static lpos_T next_match_h_startpos; // pos. for highl. start of next match
static lpos_T next_match_h_endpos; // pos. for highl. end of next match
static int next_match_idx; // index of matched item
-static long next_match_flags; // flags for next match
+static int next_match_flags; // flags for next match
static lpos_T next_match_eos_pos; // end of start pattn (start region)
static lpos_T next_match_eoe_pos; // pos. for end of end pattern
static int next_match_end_idx; // ID of group for end pattn or zero
@@ -304,11 +308,10 @@ void syn_set_timeout(proftime_T *tm)
// window.
void syntax_start(win_T *wp, linenr_T lnum)
{
- synstate_T *p;
synstate_T *last_valid = NULL;
synstate_T *last_min_valid = NULL;
- synstate_T *sp, *prev = NULL;
- linenr_T parsed_lnum;
+ synstate_T *sp;
+ synstate_T *prev = NULL;
linenr_T first_stored;
int dist;
static varnumber_T changedtick = 0; // remember the last change ID
@@ -359,7 +362,7 @@ void syntax_start(win_T *wp, linenr_T lnum)
// Only do this if lnum is not before and not to far beyond a saved state.
if (INVALID_STATE(&current_state) && syn_block->b_sst_array != NULL) {
// Find last valid saved state before start_lnum.
- for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
+ for (synstate_T *p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
if (p->sst_lnum > lnum) {
break;
}
@@ -423,7 +426,7 @@ void syntax_start(win_T *wp, linenr_T lnum)
if (sp != NULL
&& sp->sst_lnum == current_lnum
&& syn_stack_equal(sp)) {
- parsed_lnum = current_lnum;
+ linenr_T parsed_lnum = current_lnum;
prev = sp;
while (sp != NULL && sp->sst_change_lnum <= parsed_lnum) {
if (sp->sst_lnum <= lnum) {
@@ -503,7 +506,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
int found_flags = 0;
int found_match_idx = 0;
linenr_T found_current_lnum = 0;
- int found_current_col= 0;
+ int found_current_col = 0;
lpos_T found_m_endpos;
colnr_T prev_current_col;
@@ -619,7 +622,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
for (current_lnum = lnum; current_lnum < end_lnum; current_lnum++) {
syn_start_line();
- for (;;) {
+ while (true) {
had_sync_point = syn_finish_line(true);
// When a sync point has been found, remember where, and
// continue to look for another one, further on in the line.
@@ -721,7 +724,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
static void save_chartab(char *chartab)
{
- if (syn_block->b_syn_isk == empty_option) {
+ if (syn_block->b_syn_isk == empty_string_option) {
return;
}
@@ -731,7 +734,7 @@ static void save_chartab(char *chartab)
static void restore_chartab(char *chartab)
{
- if (syn_win->w_s->b_syn_isk != empty_option) {
+ if (syn_win->w_s->b_syn_isk != empty_string_option) {
memmove(syn_buf->b_chartab, chartab, (size_t)32);
}
}
@@ -750,7 +753,7 @@ static int syn_match_linecont(linenr_T lnum)
regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
regmatch.regprog = syn_block->b_syn_linecont_prog;
- int r = syn_regexec(&regmatch, lnum, (colnr_T)0,
+ int r = syn_regexec(&regmatch, lnum, 0,
IF_SYN_TIME(&syn_block->b_syn_linecont_time));
syn_block->b_syn_linecont_prog = regmatch.regprog;
@@ -874,13 +877,11 @@ static void syn_update_ends(bool startofline)
static void syn_stack_free_block(synblock_T *block)
{
- synstate_T *p;
-
if (block->b_sst_array == NULL) {
return;
}
- for (p = block->b_sst_first; p != NULL; p = p->sst_next) {
+ for (synstate_T *p = block->b_sst_first; p != NULL; p = p->sst_next) {
clear_syn_state(p);
}
XFREE_CLEAR(block->b_sst_array);
@@ -907,9 +908,6 @@ void syn_stack_free_all(synblock_T *block)
// Also used to allocate b_sst_array[] for the first time.
static void syn_stack_alloc(void)
{
- synstate_T *to, *from;
- synstate_T *sstp;
-
int len = syn_buf->b_ml.ml_line_count / SST_DIST + Rows * 2;
if (len < SST_MIN_ENTRIES) {
len = SST_MIN_ENTRIES;
@@ -937,12 +935,12 @@ static void syn_stack_alloc(void)
}
assert(len >= 0);
- sstp = xcalloc((size_t)len, sizeof(synstate_T));
+ synstate_T *sstp = xcalloc((size_t)len, sizeof(synstate_T));
- to = sstp - 1;
+ synstate_T *to = sstp - 1;
if (syn_block->b_sst_array != NULL) {
// Move the states from the old array to the new one.
- for (from = syn_block->b_sst_first; from != NULL;
+ for (synstate_T *from = syn_block->b_sst_first; from != NULL;
from = from->sst_next) {
to++;
*to = *from;
@@ -988,16 +986,13 @@ void syn_stack_apply_changes(buf_T *buf)
static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
{
- synstate_T *p, *prev, *np;
- linenr_T n;
-
- prev = NULL;
- for (p = block->b_sst_first; p != NULL;) {
+ synstate_T *prev = NULL;
+ for (synstate_T *p = block->b_sst_first; p != NULL;) {
if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top) {
- n = p->sst_lnum + buf->b_mod_xlines;
+ linenr_T n = p->sst_lnum + buf->b_mod_xlines;
if (n <= buf->b_mod_bot) {
// this state is inside the changed area, remove it
- np = p->sst_next;
+ synstate_T *np = p->sst_next;
if (prev == NULL) {
block->b_sst_first = np;
} else {
@@ -1034,7 +1029,7 @@ static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
/// @return true if at least one entry was freed.
static bool syn_stack_cleanup(void)
{
- synstate_T *p, *prev;
+ synstate_T *prev;
disptick_T tick;
int dist;
bool retval = false;
@@ -1056,7 +1051,7 @@ static bool syn_stack_cleanup(void)
tick = syn_block->b_sst_lasttick;
bool above = false;
prev = syn_block->b_sst_first;
- for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
+ for (synstate_T *p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
if (prev->sst_lnum + dist > p->sst_lnum) {
if (p->sst_tick > syn_block->b_sst_lasttick) {
if (!above || p->sst_tick < tick) {
@@ -1072,7 +1067,7 @@ static bool syn_stack_cleanup(void)
// Go through the list to make the entries for the oldest tick at an
// interval of several lines.
prev = syn_block->b_sst_first;
- for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
+ for (synstate_T *p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum) {
// Move this entry from used list to free list
prev->sst_next = p->sst_next;
@@ -1098,10 +1093,8 @@ static void syn_stack_free_entry(synblock_T *block, synstate_T *p)
// Returns NULL when there is no entry or the first entry is after "lnum".
static synstate_T *syn_stack_find_entry(linenr_T lnum)
{
- synstate_T *p, *prev;
-
- prev = NULL;
- for (p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next) {
+ synstate_T *prev = NULL;
+ for (synstate_T *p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next) {
if (p->sst_lnum == lnum) {
return p;
}
@@ -1201,7 +1194,7 @@ static synstate_T *store_current_state(void)
}
for (i = 0; i < sp->sst_stacksize; i++) {
bp[i].bs_idx = CUR_STATE(i).si_idx;
- bp[i].bs_flags = (int)CUR_STATE(i).si_flags;
+ bp[i].bs_flags = CUR_STATE(i).si_flags;
bp[i].bs_seqnr = CUR_STATE(i).si_seqnr;
bp[i].bs_cchar = CUR_STATE(i).si_cchar;
bp[i].bs_extmatch = ref_extmatch(CUR_STATE(i).si_extmatch);
@@ -1218,7 +1211,6 @@ static synstate_T *store_current_state(void)
// Copy a state stack from "from" in b_sst_array[] to current_state;
static void load_current_state(synstate_T *from)
{
- int i;
bufstate_T *bp;
clear_current_state();
@@ -1231,7 +1223,7 @@ static void load_current_state(synstate_T *from)
} else {
bp = from->sst_union.sst_stack;
}
- for (i = 0; i < from->sst_stacksize; i++) {
+ for (int i = 0; i < from->sst_stacksize; i++) {
CUR_STATE(i).si_idx = bp[i].bs_idx;
CUR_STATE(i).si_flags = bp[i].bs_flags;
CUR_STATE(i).si_seqnr = bp[i].bs_seqnr;
@@ -1263,7 +1255,6 @@ static void load_current_state(synstate_T *from)
static bool syn_stack_equal(synstate_T *sp)
{
bufstate_T *bp;
- reg_extmatch_T *six, *bsx;
// First a quick check if the stacks have the same size end nextlist.
if (sp->sst_stacksize != current_state.ga_len
@@ -1289,8 +1280,8 @@ static bool syn_stack_equal(synstate_T *sp)
}
// When the extmatch pointers are different, the strings in them can
// still be the same. Check if the extmatch references are equal.
- bsx = bp[i].bs_extmatch;
- six = CUR_STATE(i).si_extmatch;
+ reg_extmatch_T *bsx = bp[i].bs_extmatch;
+ reg_extmatch_T *six = CUR_STATE(i).si_extmatch;
// If one of the extmatch pointers is NULL the states are different.
if (bsx == NULL || six == NULL) {
break;
@@ -1501,10 +1492,11 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
lpos_T eos_pos; // end-of-start match (start region)
lpos_T eoe_pos; // end-of-end pattern
int end_idx; // group ID for end pattern
- stateitem_T *cur_si, *sip = NULL;
+ stateitem_T *cur_si;
+ stateitem_T *sip = NULL;
int startcol;
int endcol;
- long flags;
+ int flags;
int cchar;
int16_t *next_list;
bool found_match; // found usable match
@@ -1877,7 +1869,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
current_attr = sip->si_attr;
current_id = sip->si_id;
current_trans_id = sip->si_trans_id;
- current_flags = (int)sip->si_flags;
+ current_flags = sip->si_flags;
current_seqnr = sip->si_seqnr;
current_sub_char = sip->si_cchar;
break;
@@ -2071,7 +2063,7 @@ static void check_state_ends(void)
int had_extend;
cur_si = &CUR_STATE(current_state.ga_len - 1);
- for (;;) {
+ while (true) {
if (cur_si->si_ends
&& (cur_si->si_m_endpos.lnum < current_lnum
|| (cur_si->si_m_endpos.lnum == current_lnum
@@ -2107,7 +2099,7 @@ static void check_state_ends(void)
// handle next_list, unless at end of line and no "skipnl" or
// "skipempty"
current_next_list = cur_si->si_next_list;
- current_next_flags = (int)cur_si->si_flags;
+ current_next_flags = cur_si->si_flags;
if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
&& syn_getcurline()[current_col] == NUL) {
current_next_list = NULL;
@@ -2355,12 +2347,10 @@ static void pop_current_state(void)
/// @param end_endpos return: end of end pattern match
/// @param end_idx return: group ID for end pat. match, or 0
/// @param start_ext submatches from the start pattern
-static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_endpos,
- long *flagsp, lpos_T *end_endpos, int *end_idx, reg_extmatch_T *start_ext)
+static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_endpos, int *flagsp,
+ lpos_T *end_endpos, int *end_idx, reg_extmatch_T *start_ext)
{
- colnr_T matchcol;
- synpat_T *spp, *spp_skip;
- int start_idx;
+ synpat_T *spp_skip;
int best_idx;
regmmatch_T regmatch;
regmmatch_T best_regmatch; // startpos/endpos of best match
@@ -2377,14 +2367,14 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
// Check for being called with a START pattern.
// Can happen with a match that continues to the next line, because it
// contained a region.
- spp = &(SYN_ITEMS(syn_block)[idx]);
+ synpat_T *spp = &(SYN_ITEMS(syn_block)[idx]);
if (spp->sp_type != SPTYPE_START) {
*hl_endpos = *startpos;
return;
}
// Find the SKIP or first END pattern after the last START pattern.
- for (;;) {
+ while (true) {
spp = &(SYN_ITEMS(syn_block)[idx]);
if (spp->sp_type != SPTYPE_START) {
break;
@@ -2404,14 +2394,14 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
unref_extmatch(re_extmatch_in);
re_extmatch_in = ref_extmatch(start_ext);
- matchcol = startpos->col; // start looking for a match at sstart
- start_idx = idx; // remember the first END pattern.
+ colnr_T matchcol = startpos->col; // start looking for a match at sstart
+ int start_idx = idx; // remember the first END pattern.
best_regmatch.startpos[0].col = 0; // avoid compiler warning
// use syntax iskeyword option
save_chartab(buf_chartab);
- for (;;) {
+ while (true) {
// Find end pattern that matches first after "matchcol".
best_idx = -1;
for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; idx++) {
@@ -2470,7 +2460,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
break;
}
- line = ml_get_buf(syn_buf, startpos->lnum, false);
+ line = ml_get_buf(syn_buf, startpos->lnum);
int line_len = (int)strlen(line);
// take care of an empty match or negative offset
@@ -2606,7 +2596,7 @@ static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp
if (result->lnum > syn_buf->b_ml.ml_line_count) {
col = 0;
} else if (off != 0) {
- base = ml_get_buf(syn_buf, result->lnum, false);
+ base = ml_get_buf(syn_buf, result->lnum);
p = base + col;
if (off > 0) {
while (off-- > 0 && *p != NUL) {
@@ -2648,10 +2638,10 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s
if (result->lnum > syn_buf->b_ml.ml_line_count) {
// a "\n" at the end of the pattern may take us below the last line
result->lnum = syn_buf->b_ml.ml_line_count;
- col = (int)strlen(ml_get_buf(syn_buf, result->lnum, false));
+ col = (int)strlen(ml_get_buf(syn_buf, result->lnum));
}
if (off != 0) {
- base = ml_get_buf(syn_buf, result->lnum, false);
+ base = ml_get_buf(syn_buf, result->lnum);
p = base + col;
if (off > 0) {
while (off-- && *p != NUL) {
@@ -2670,7 +2660,7 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s
/// Get current line in syntax buffer.
static char *syn_getcurline(void)
{
- return ml_get_buf(syn_buf, current_lnum, false);
+ return ml_get_buf(syn_buf, current_lnum);
}
// Call vim_regexec() to find a match with "rmp" in "syn_buf".
@@ -2693,7 +2683,7 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
}
rmp->rmm_maxcol = (colnr_T)syn_buf->b_p_smc;
- long r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, syn_tm, &timed_out);
+ int r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, syn_tm, &timed_out);
if (l_syn_time_on) {
pt = profile_end(pt);
@@ -2708,7 +2698,7 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
}
if (timed_out && !syn_win->w_s->b_syn_slow) {
syn_win->w_s->b_syn_slow = true;
- msg(_("'redrawtime' exceeded, syntax highlighting disabled"));
+ msg(_("'redrawtime' exceeded, syntax highlighting disabled"), 0);
}
if (r > 0) {
@@ -2730,7 +2720,7 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
/// @param cur_si item at the top of the stack
/// @param ccharp conceal substitution char
static int check_keyword_id(char *const line, const int startcol, int *const endcolp,
- long *const flagsp, int16_t **const next_listp,
+ int *const flagsp, int16_t **const next_listp,
stateitem_T *const cur_si, int *const ccharp)
{
// Find first character after the keyword. First character was already
@@ -2811,9 +2801,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_conceal) {
- msg("syntax conceal on");
+ msg("syntax conceal on", 0);
} else {
- msg("syntax conceal off");
+ msg("syntax conceal off", 0);
}
} else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) {
curwin->w_s->b_syn_conceal = true;
@@ -2838,9 +2828,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_ic) {
- msg("syntax case ignore");
+ msg("syntax case ignore", 0);
} else {
- msg("syntax case match");
+ msg("syntax case match", 0);
}
} else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) {
curwin->w_s->b_syn_ic = false;
@@ -2865,9 +2855,9 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing)
if (*arg == NUL) {
switch (curwin->w_s->b_syn_foldlevel) {
case SYNFLD_START:
- msg("syntax foldlevel start"); break;
+ msg("syntax foldlevel start", 0); break;
case SYNFLD_MINIMUM:
- msg("syntax foldlevel minimum"); break;
+ msg("syntax foldlevel minimum", 0); break;
default:
break;
}
@@ -2904,11 +2894,11 @@ static void syn_cmd_spell(exarg_T *eap, int syncing)
next = skiptowhite(arg);
if (*arg == NUL) {
if (curwin->w_s->b_syn_spell == SYNSPL_TOP) {
- msg("syntax spell toplevel");
+ msg("syntax spell toplevel", 0);
} else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) {
- msg("syntax spell notoplevel");
+ msg("syntax spell notoplevel", 0);
} else {
- msg("syntax spell default");
+ msg("syntax spell default", 0);
}
} else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) {
curwin->w_s->b_syn_spell = SYNSPL_TOP;
@@ -2939,11 +2929,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
arg = skipwhite(arg);
if (*arg == NUL) {
msg_puts("\n");
- if (curwin->w_s->b_syn_isk != empty_option) {
+ if (curwin->w_s->b_syn_isk != empty_string_option) {
msg_puts("syntax iskeyword ");
- msg_outtrans(curwin->w_s->b_syn_isk);
+ msg_outtrans(curwin->w_s->b_syn_isk, 0);
} else {
- msg_outtrans(_("syntax iskeyword not set"));
+ msg_outtrans(_("syntax iskeyword not set"), 0);
}
} else {
if (STRNICMP(arg, "clear", 5) == 0) {
@@ -3230,7 +3220,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing)
}
if (!syntax_present(curwin)) {
- msg(_(msg_no_items));
+ msg(_(msg_no_items), 0);
return;
}
@@ -3422,7 +3412,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
msg_putchar(' ');
if (spp->sp_sync_idx >= 0) {
msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s)
- [spp->sp_sync_idx].sp_syn.id - 1));
+ [spp->sp_sync_idx].sp_syn.id - 1), 0);
} else {
msg_puts("NONE");
}
@@ -3435,15 +3425,13 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
(void)syn_list_header(did_header, 0, id, true);
msg_puts_attr("links to", attr);
msg_putchar(' ');
- msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1));
+ msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1), 0);
}
}
static void syn_list_flags(struct name_list *nlist, int flags, int attr)
{
- int i;
-
- for (i = 0; nlist[i].flag != 0; i++) {
+ for (int i = 0; nlist[i].flag != 0; i++) {
if (flags & nlist[i].flag) {
msg_puts_attr(nlist[i].name, attr);
msg_putchar(' ');
@@ -3458,7 +3446,7 @@ static void syn_list_cluster(int id)
// slight hack: roughly duplicate the guts of syn_list_header()
msg_putchar('\n');
- msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
+ msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name, 0);
if (msg_col >= endcol) { // output at least one space
endcol = msg_col + 1;
@@ -3495,9 +3483,9 @@ static void put_id_list(const char *const name, const int16_t *const list, const
int scl_id = *p - SYNID_CLUSTER;
msg_putchar('@');
- msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
+ msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name, 0);
} else {
- msg_outtrans(highlight_group_name(*p - 1));
+ msg_outtrans(highlight_group_name(*p - 1), 0);
}
if (p[1]) {
msg_putchar(',');
@@ -3517,9 +3505,9 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const
msg_puts_attr("matchgroup", attr);
msg_putchar('=');
if (last_matchgroup == 0) {
- msg_outtrans("NONE");
+ msg_outtrans("NONE", 0);
} else {
- msg_outtrans(highlight_group_name(last_matchgroup - 1));
+ msg_outtrans(highlight_group_name(last_matchgroup - 1), 0);
}
msg_putchar(' ');
}
@@ -3536,7 +3524,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const
}
}
msg_putchar(sepchars[i]);
- msg_outtrans(spp->sp_pattern);
+ msg_outtrans(spp->sp_pattern, 0);
msg_putchar(sepchars[i]);
// output any pattern options
@@ -3550,7 +3538,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const
msg_putchar(','); // Separate with commas.
}
msg_puts(spo_name_tab[i]);
- const long n = spp->sp_offsets[i];
+ const int n = spp->sp_offsets[i];
if (i != SPO_LC_OFF) {
if (spp->sp_off_flags & mask) {
msg_putchar('s');
@@ -3646,7 +3634,7 @@ static bool syn_list_keywords(const int id, const hashtab_T *const ht, bool did_
prev_skipempty = (kp->flags & HL_SKIPEMPTY);
}
}
- msg_outtrans((char *)kp->keyword);
+ msg_outtrans(kp->keyword, 0);
}
}
}
@@ -3677,7 +3665,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht)
if (kp_next == NULL) {
hash_remove(ht, hi);
} else {
- hi->hi_key = (char *)KE2HIKEY(kp_next);
+ hi->hi_key = KE2HIKEY(kp_next);
}
} else {
kp_prev->ke_next = kp_next;
@@ -3732,11 +3720,11 @@ static void add_keyword(char *const name, const int id, const int flags,
{
char name_folded[MAXKEYWLEN + 1];
const char *const name_ic = (curwin->w_s->b_syn_ic)
- ? str_foldcase(name, (int)strlen(name), name_folded,
- sizeof(name_folded))
- : name;
+ ? str_foldcase(name, (int)strlen(name), name_folded,
+ sizeof(name_folded))
+ : name;
- keyentry_T *const kp = xmalloc(sizeof(keyentry_T) + strlen(name_ic));
+ keyentry_T *const kp = xmalloc(offsetof(keyentry_T, keyword) + strlen(name_ic) + 1);
STRCPY(kp->keyword, name_ic);
kp->k_syn.id = (int16_t)id;
kp->k_syn.inc_tag = current_syn_inc_tag;
@@ -3750,9 +3738,9 @@ static void add_keyword(char *const name, const int id, const int flags,
const hash_T hash = hash_hash(kp->keyword);
hashtab_T *const ht = (curwin->w_s->b_syn_ic)
- ? &curwin->w_s->b_keywtab_ic
- : &curwin->w_s->b_keywtab;
- hashitem_T *const hi = hash_lookup(ht, (const char *)kp->keyword,
+ ? &curwin->w_s->b_keywtab_ic
+ : &curwin->w_s->b_keywtab;
+ hashitem_T *const hi = hash_lookup(ht, kp->keyword,
strlen(kp->keyword), hash);
// even though it looks like only the kp->keyword member is
@@ -3766,7 +3754,7 @@ static void add_keyword(char *const name, const int id, const int flags,
} else {
// keyword already exists, prepend to list
kp->ke_next = HI2KE(hi);
- hi->hi_key = (char *)KE2HIKEY(kp);
+ hi->hi_key = KE2HIKEY(kp);
}
}
@@ -3803,7 +3791,6 @@ static char *get_group_name(char *arg, char **name_end)
/// Return NULL for any error;
static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, int skip)
{
- char *gname_start, *gname;
int syn_id;
int len = 0;
char *p;
@@ -3841,7 +3828,7 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
opt->flags |= HL_CONCEAL;
}
- for (;;) {
+ while (true) {
// This is used very often when a large number of keywords is defined.
// Need to skip quickly when no option name is found.
// Also avoid tolower(), it's slow.
@@ -3877,7 +3864,7 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
if (flagtab[fidx].argtype == 1) {
if (!opt->has_cont_list) {
- emsg(_("E395: contains argument not accepted here"));
+ emsg(_(e_contains_argument_not_accepted_here));
return NULL;
}
if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) {
@@ -3895,8 +3882,8 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
// cchar=?
*conceal_char = utf_ptr2char(arg + 6);
arg += utfc_ptr2len(arg + 6) - 1;
- if (!vim_isprintc_strict(*conceal_char)) {
- emsg(_("E844: invalid cchar value"));
+ if (!vim_isprintc(*conceal_char)) {
+ emsg(_(e_invalid_cchar_value));
return NULL;
}
arg = skipwhite(arg + 7);
@@ -3910,12 +3897,12 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
emsg(_("E393: group[t]here not accepted here"));
return NULL;
}
- gname_start = arg;
+ char *gname_start = arg;
arg = skiptowhite(arg);
if (gname_start == arg) {
return NULL;
}
- gname = xstrnsave(gname_start, (size_t)(arg - gname_start));
+ char *gname = xstrnsave(gname_start, (size_t)(arg - gname_start));
if (strcmp(gname, "NONE") == 0) {
*opt->sync_idx = NONE_IDX;
} else {
@@ -3976,7 +3963,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
int sgl_id = 1;
char *group_name_end;
char *rest;
- char *errormsg = NULL;
+ const char *errormsg = NULL;
int prev_toplvl_grp;
int prev_syn_inc_tag;
bool source = false;
@@ -4029,7 +4016,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
curwin->w_s->b_syn_topgrp = sgl_id;
if (source
- ? do_source(eap->arg, false, DOSO_NONE) == FAIL
+ ? do_source(eap->arg, false, DOSO_NONE, NULL) == FAIL
: source_runtime(eap->arg, DIP_ALL) == FAIL) {
semsg(_(e_notopen), eap->arg);
}
@@ -4114,8 +4101,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing)
}
if (p[1] == ']') {
if (p[2] != NUL) {
- semsg(_("E890: trailing char after ']': %s]%s"),
- kw, &p[2]);
+ semsg(_(e_trailing_char_after_rsb_str_str), kw, &p[2]);
goto error;
}
kw = p + 1;
@@ -4400,8 +4386,8 @@ static void syn_cmd_region(exarg_T *eap, int syncing)
SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
SYN_ITEMS(curwin->w_s)[idx].sp_type =
- (item == ITEM_START) ? SPTYPE_START :
- (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
+ (item == ITEM_START) ? SPTYPE_START
+ : (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = (int16_t)syn_id;
SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
@@ -4678,7 +4664,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing)
}
scl_id -= SYNID_CLUSTER;
- for (;;) {
+ while (true) {
if (STRNICMP(rest, "add", 3) == 0
&& (ascii_iswhite(rest[3]) || rest[3] == '=')) {
opt_len = 3;
@@ -4736,17 +4722,15 @@ static void init_syn_patterns(void)
/// @return a pointer to the next argument, or NULL in case of an error.
static char *get_syn_pattern(char *arg, synpat_T *ci)
{
- char *end;
int *p;
int idx;
- char *cpo_save;
// need at least three chars
if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL) {
return NULL;
}
- end = skip_regexp(arg + 1, *arg, true);
+ char *end = skip_regexp(arg + 1, *arg, true);
if (*end != *arg) { // end delimiter not found
semsg(_("E401: Pattern delimiter not found: %s"), arg);
return NULL;
@@ -4755,8 +4739,8 @@ static char *get_syn_pattern(char *arg, synpat_T *ci)
ci->sp_pattern = xstrnsave(arg + 1, (size_t)(end - arg) - 1);
// Make 'cpoptions' empty, to avoid the 'l' flag
- cpo_save = p_cpo;
- p_cpo = empty_option;
+ char *cpo_save = p_cpo;
+ p_cpo = empty_string_option;
ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
p_cpo = cpo_save;
@@ -4913,7 +4897,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing)
// Make 'cpoptions' empty, to avoid the 'l' flag
cpo_save = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
curwin->w_s->b_syn_linecont_prog =
vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
p_cpo = cpo_save;
@@ -5047,7 +5031,7 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list,
regmatch.rm_ic = true;
id = 0;
for (int i = highlight_num_groups(); --i >= 0;) {
- if (vim_regexec(&regmatch, highlight_group_name(i), (colnr_T)0)) {
+ if (vim_regexec(&regmatch, highlight_group_name(i), 0)) {
if (round == 2) {
// Got more items than expected; can happen
// when adding items that match:
@@ -5057,7 +5041,7 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list,
xfree(retval);
round = 1;
} else {
- retval[count] = (int16_t)(i + 1); // -V522
+ retval[count] = (int16_t)(i + 1);
}
}
count++;
@@ -5145,7 +5129,6 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
{
int retval;
int16_t *scl_list;
- int16_t item;
int16_t id = ssp->id;
static int depth = 0;
int r;
@@ -5181,7 +5164,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// If the first item is "ALLBUT", return true if "id" is NOT in the
// contains list. We also require that "id" is at the same ":syn include"
// level as the list.
- item = *list;
+ int16_t item = *list;
if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER) {
if (item < SYNID_TOP) {
// ALL or ALLBUT: accept all groups in the same file
@@ -5291,16 +5274,13 @@ void ex_syntax(exarg_T *eap)
void ex_ownsyntax(exarg_T *eap)
{
- char *old_value;
- char *new_value;
-
if (curwin->w_s == &curwin->w_buffer->b_s) {
curwin->w_s = xcalloc(1, sizeof(synblock_T));
hash_init(&curwin->w_s->b_keywtab);
hash_init(&curwin->w_s->b_keywtab_ic);
// TODO(vim): Keep the spell checking as it was.
curwin->w_p_spell = false; // No spell checking
- // make sure option values are "empty_option" instead of NULL
+ // make sure option values are "empty_string_option" instead of NULL
clear_string_option(&curwin->w_s->b_p_spc);
clear_string_option(&curwin->w_s->b_p_spf);
clear_string_option(&curwin->w_s->b_p_spl);
@@ -5309,7 +5289,7 @@ void ex_ownsyntax(exarg_T *eap)
}
// Save value of b:current_syntax.
- old_value = get_var_value("b:current_syntax");
+ char *old_value = get_var_value("b:current_syntax");
if (old_value != NULL) {
old_value = xstrdup(old_value);
}
@@ -5318,7 +5298,7 @@ void ex_ownsyntax(exarg_T *eap)
apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, true, curbuf);
// Move value of b:current_syntax to w:current_syntax.
- new_value = get_var_value("b:current_syntax");
+ char *new_value = get_var_value("b:current_syntax");
if (new_value != NULL) {
set_internal_string_var("w:current_syntax", new_value);
}
@@ -5379,7 +5359,7 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg)
}
// (part of) subcommand already typed
- const char *p = (const char *)skiptowhite(arg);
+ const char *p = skiptowhite(arg);
if (*p == NUL) {
return;
}
@@ -5483,10 +5463,9 @@ int get_syntax_info(int *seqnrp)
int syn_get_concealed_id(win_T *wp, linenr_T lnum, colnr_T col)
{
int seqnr;
- int syntax_flags;
(void)syn_get_id(wp, lnum, col, false, NULL, false);
- syntax_flags = get_syntax_info(&seqnr);
+ int syntax_flags = get_syntax_info(&seqnr);
if (syntax_flags & HL_CONCEAL) {
return seqnr;
@@ -5595,7 +5574,7 @@ static void syntime_clear(void)
synpat_T *spp;
if (!syntax_present(curwin)) {
- msg(_(msg_no_items));
+ msg(_(msg_no_items), 0);
return;
}
for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) {
@@ -5633,7 +5612,7 @@ static int syn_compare_syntime(const void *v1, const void *v2)
static void syntime_report(void)
{
if (!syntax_present(curwin)) {
- msg(_(msg_no_items));
+ msg(_(msg_no_items), 0);
return;
}
@@ -5649,11 +5628,11 @@ static void syntime_report(void)
p = GA_APPEND_VIA_PTR(time_entry_T, &ga);
p->total = spp->sp_time.total;
total_total = profile_add(total_total, spp->sp_time.total);
- p->count = (int)spp->sp_time.count;
- p->match = (int)spp->sp_time.match;
- total_count += (int)spp->sp_time.count;
+ p->count = spp->sp_time.count;
+ p->match = spp->sp_time.match;
+ total_count += spp->sp_time.count;
p->slowest = spp->sp_time.slowest;
- proftime_T tm = profile_divide(spp->sp_time.total, (int)spp->sp_time.count);
+ proftime_T tm = profile_divide(spp->sp_time.total, spp->sp_time.count);
p->average = tm;
p->id = spp->sp_syn.id;
p->pattern = spp->sp_pattern;
@@ -5687,7 +5666,7 @@ static void syntime_report(void)
msg_puts(profile_msg(p->average));
msg_puts(" ");
msg_advance(50);
- msg_outtrans(highlight_group_name(p->id - 1));
+ msg_outtrans(highlight_group_name(p->id - 1), 0);
msg_puts(" ");
msg_advance(69);
@@ -5700,7 +5679,7 @@ static void syntime_report(void)
if (len > (int)strlen(p->pattern)) {
len = (int)strlen(p->pattern);
}
- msg_outtrans_len(p->pattern, len);
+ msg_outtrans_len(p->pattern, len, 0);
msg_puts("\n");
}
ga_clear(&ga);
diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h
index 0a63392a04..4e9c7a27dc 100644
--- a/src/nvim/syntax.h
+++ b/src/nvim/syntax.h
@@ -1,12 +1,13 @@
-#ifndef NVIM_SYNTAX_H
-#define NVIM_SYNTAX_H
+#pragma once
#include <stdbool.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/syntax_defs.h" // IWYU pragma: export
#define HL_CONTAINED 0x01 // not used on toplevel
#define HL_TRANSP 0x02 // has no highlighting
@@ -36,5 +37,3 @@ extern const char *const highlight_init_cmdline[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "syntax.h.generated.h"
#endif
-
-#endif // NVIM_SYNTAX_H
diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h
index 3f3101f7e3..ae4997154e 100644
--- a/src/nvim/syntax_defs.h
+++ b/src/nvim/syntax_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_SYNTAX_DEFS_H
-#define NVIM_SYNTAX_DEFS_H
+#pragma once
#include "nvim/highlight_defs.h"
@@ -30,7 +29,7 @@ struct keyentry {
int16_t *next_list; // ID list for next match (if non-zero)
int flags;
int k_char; // conceal substitute character
- char keyword[1]; // actually longer
+ char keyword[];
};
// Struct used to store one state of the state stack.
@@ -59,5 +58,3 @@ struct syn_state {
linenr_T sst_change_lnum; // when non-zero, change in this line
// may have made the state invalid
};
-
-#endif // NVIM_SYNTAX_DEFS_H
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 197184c181..c6a1a13606 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Code to handle tags and the tag stack
#include <assert.h>
@@ -11,7 +8,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -20,44 +17,45 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/input.h"
#include "nvim/insexpand.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
// Structure to hold pointers to various items in a tag line.
@@ -188,11 +186,19 @@ typedef struct {
# include "tag.c.generated.h"
#endif
-static char *bottommsg = N_("E555: at bottom of tag stack");
-static char *topmsg = N_("E556: at top of tag stack");
-static char *recurmsg = N_("E986: cannot modify the tag stack within tagfunc");
-static char *tfu_inv_ret_msg = N_("E987: invalid return value from tagfunc");
-static char e_window_unexpectedly_close_while_searching_for_tags[]
+static const char e_tag_stack_empty[]
+ = N_("E73: Tag stack empty");
+static const char e_tag_not_found_str[]
+ = N_("E426: Tag not found: %s");
+static const char e_at_bottom_of_tag_stack[]
+ = N_("E555: At bottom of tag stack");
+static const char e_at_top_of_tag_stack[]
+ = N_("E556: At top of tag stack");
+static const char e_cannot_modify_tag_stack_within_tagfunc[]
+ = N_("E986: Cannot modify the tag stack within tagfunc");
+static const char e_invalid_return_value_from_tagfunc[]
+ = N_("E987: Invalid return value from tagfunc");
+static const char e_window_unexpectedly_close_while_searching_for_tags[]
= N_("E1299: Window unexpectedly closed while searching for tags");
static char *tagmatchname = NULL; // name of last used tag
@@ -210,20 +216,23 @@ static Callback tfu_cb; // 'tagfunc' callback function
/// Reads the 'tagfunc' option value and convert that to a callback value.
/// Invoked when the 'tagfunc' option is set. The option value can be a name of
/// a function (string), or function(<name>) or funcref(<name>) or a lambda.
-void set_tagfunc_option(char **errmsg)
+const char *did_set_tagfunc(optset_T *args)
{
+ buf_T *buf = (buf_T *)args->os_buf;
+
callback_free(&tfu_cb);
- callback_free(&curbuf->b_tfu_cb);
+ callback_free(&buf->b_tfu_cb);
- if (*curbuf->b_p_tfu == NUL) {
- return;
+ if (*buf->b_p_tfu == NUL) {
+ return NULL;
}
- if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL) {
- *errmsg = e_invarg;
+ if (option_set_callback_func(buf->b_p_tfu, &tfu_cb) == FAIL) {
+ return e_invarg;
}
- callback_copy(&curbuf->b_tfu_cb, &tfu_cb);
+ callback_copy(&buf->b_tfu_cb, &tfu_cb);
+ return NULL;
}
#if defined(EXITFREE)
@@ -278,10 +287,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
int cur_fnum = curbuf->b_fnum;
int oldtagstackidx = tagstackidx;
int prevtagstackidx = tagstackidx;
- int prev_num_matches;
int new_tag = false;
- int i;
- int ic;
int no_regexp = false;
int error_cur_match = 0;
int save_pos = false;
@@ -301,7 +307,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
static int flags;
if (tfu_in_use) {
- emsg(_(recurmsg));
+ emsg(_(e_cannot_modify_tag_stack_within_tagfunc));
return;
}
@@ -320,7 +326,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
use_tfu = 0;
}
- prev_num_matches = num_matches;
+ int prev_num_matches = num_matches;
free_string_option(nofile_fname);
nofile_fname = NULL;
@@ -329,7 +335,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// Don't add a tag to the tagstack if 'tagstack' has been reset.
assert(tag != NULL);
- if (!p_tgst && *tag != NUL) { // -V522
+ if (!p_tgst && *tag != NUL) {
use_tagstack = false;
new_tag = true;
if (g_do_tagpreview != 0) {
@@ -369,7 +375,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
if (++tagstacklen > TAGSTACKSIZE) {
tagstacklen = TAGSTACKSIZE;
tagstack_clear_entry(&tagstack[0]);
- for (i = 1; i < tagstacklen; i++) {
+ for (int i = 1; i < tagstacklen; i++) {
tagstack[i - 1] = tagstack[i];
}
tagstackidx--;
@@ -385,17 +391,17 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
new_tag = true;
} else {
- if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
- tagstacklen == 0) {
+ if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL
+ : tagstacklen == 0) {
// empty stack
- emsg(_(e_tagstack));
+ emsg(_(e_tag_stack_empty));
goto end_do_tag;
}
if (type == DT_POP) { // go to older position
const bool old_KeyTyped = KeyTyped;
if ((tagstackidx -= count) < 0) {
- emsg(_(bottommsg));
+ emsg(_(e_at_bottom_of_tag_stack));
if (tagstackidx + count == 0) {
// We did [num]^T from the bottom of the stack
tagstackidx = 0;
@@ -405,7 +411,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// way to the bottom now.
tagstackidx = 0;
} else if (tagstackidx >= tagstacklen) { // count == 0?
- emsg(_(topmsg));
+ emsg(_(e_at_top_of_tag_stack));
goto end_do_tag;
}
@@ -454,10 +460,10 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// go to the last one. Don't store the cursor
// position.
tagstackidx = tagstacklen - 1;
- emsg(_(topmsg));
+ emsg(_(e_at_top_of_tag_stack));
save_pos = false;
} else if (tagstackidx < 0) { // must have been count == 0
- emsg(_(bottommsg));
+ emsg(_(e_at_bottom_of_tag_stack));
tagstackidx = 0;
goto end_do_tag;
}
@@ -538,7 +544,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
}
// Repeat searching for tags, when a file has not been found.
- for (;;) {
+ while (true) {
int other_name;
char *name;
@@ -607,19 +613,18 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// to the start. Avoids that the order changes when using
// ":tnext" and jumping to another file.
if (!new_tag && !other_name) {
- int j, k;
int idx = 0;
tagptrs_T tagp, tagp2;
// Find the position of each old match in the new list. Need
// to use parse_match() to find the tag line.
- for (j = 0; j < num_matches; j++) {
+ for (int j = 0; j < num_matches; j++) {
parse_match(matches[j], &tagp);
- for (i = idx; i < new_num_matches; i++) {
+ for (int i = idx; i < new_num_matches; i++) {
parse_match(new_matches[i], &tagp2);
if (strcmp(tagp.tagname, tagp2.tagname) == 0) {
char *p = new_matches[i];
- for (k = i; k > idx; k--) {
+ for (int k = i; k > idx; k--) {
new_matches[k] = new_matches[k - 1];
}
new_matches[idx++] = p;
@@ -635,7 +640,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
if (num_matches <= 0) {
if (verbose) {
- semsg(_("E426: tag not found: %s"), name);
+ semsg(_(e_tag_not_found_str), name);
}
g_do_tagpreview = 0;
} else {
@@ -658,7 +663,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
if (ask_for_selection) {
// Ask to select a tag from the list.
- i = prompt_for_number(NULL);
+ int i = prompt_for_number(NULL);
if (i <= 0 || i > num_matches || got_int) {
// no valid choice: don't change anything
if (use_tagstack) {
@@ -696,7 +701,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
&& tagp2.user_data) {
XFREE_CLEAR(tagstack[tagstackidx].user_data);
tagstack[tagstackidx].user_data =
- xstrnsave(tagp2.user_data, (size_t)(tagp2.user_data_end - tagp2.user_data));
+ xmemdupz(tagp2.user_data, (size_t)(tagp2.user_data_end - tagp2.user_data));
}
tagstackidx++;
@@ -708,10 +713,10 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// Only when going to try the next match, report that the previous
// file didn't exist. Otherwise an emsg() is given below.
if (nofile_fname != NULL && error_cur_match != cur_match) {
- smsg(_("File \"%s\" does not exist"), nofile_fname);
+ smsg(0, _("File \"%s\" does not exist"), nofile_fname);
}
- ic = (matches[cur_match][0] & MT_IC_OFF);
+ int ic = (matches[cur_match][0] & MT_IC_OFF);
if (type != DT_TAG && type != DT_SELECT && type != DT_JUMP
&& (num_matches > 1 || ic)
&& !skip_msg) {
@@ -721,22 +726,18 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
num_matches,
max_num_matches != MAXCOL ? _(" or more") : "");
if (ic) {
- STRCAT(IObuff, _(" Using tag with different case!"));
+ xstrlcat(IObuff, _(" Using tag with different case!"), IOSIZE);
}
if ((num_matches > prev_num_matches || new_tag)
&& num_matches > 1) {
- if (ic) {
- msg_attr((const char *)IObuff, HL_ATTR(HLF_W));
- } else {
- msg(IObuff);
- }
+ msg(IObuff, ic ? HL_ATTR(HLF_W) : 0);
msg_scroll = true; // Don't overwrite this message.
} else {
give_warning(IObuff, ic);
}
if (ic && !msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1007L, true);
+ os_delay(1007, true);
}
}
@@ -745,7 +746,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
set_vim_var_string(VV_SWAPCOMMAND, IObuff, -1);
// Jump to the desired match.
- i = jumpto_tag(matches[cur_match], forceit, true);
+ int i = jumpto_tag(matches[cur_match], forceit, true);
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
@@ -795,17 +796,12 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
{
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
- int i;
- char *p;
- char *command_end;
tagptrs_T tagp;
- int taglen;
- int attr;
// Assume that the first match indicates how long the tags can
// be, and align the file names to that.
parse_match(matches[0], &tagp);
- taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
+ int taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
if (taglen < 18) {
taglen = 18;
}
@@ -821,7 +817,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
taglen_advance(taglen);
msg_puts_attr(_("file\n"), HL_ATTR(HLF_T));
- for (i = 0; i < num_matches && !got_int; i++) {
+ for (int i = 0; i < num_matches && !got_int; i++) {
parse_match(matches[i], &tagp);
if (!new_tag && (
(g_do_tagpreview != 0
@@ -837,21 +833,18 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
mt_names[matches[i][0] & MT_MASK]);
msg_puts(IObuff);
if (tagp.tagkind != NULL) {
- msg_outtrans_len(tagp.tagkind,
- (int)(tagp.tagkind_end - tagp.tagkind));
+ msg_outtrans_len(tagp.tagkind, (int)(tagp.tagkind_end - tagp.tagkind), 0);
}
msg_advance(13);
- msg_outtrans_len_attr(tagp.tagname,
- (int)(tagp.tagname_end - tagp.tagname),
- HL_ATTR(HLF_T));
+ msg_outtrans_len(tagp.tagname, (int)(tagp.tagname_end - tagp.tagname), HL_ATTR(HLF_T));
msg_putchar(' ');
taglen_advance(taglen);
// Find out the actual file name. If it is long, truncate
// it and put "..." in the middle
- p = tag_full_fname(&tagp);
+ const char *p = tag_full_fname(&tagp);
if (p != NULL) {
- msg_outtrans_attr(p, HL_ATTR(HLF_D));
+ msg_outtrans(p, HL_ATTR(HLF_D));
XFREE_CLEAR(p);
}
if (msg_col > 0) {
@@ -863,7 +856,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
msg_advance(15);
// print any extra fields
- command_end = tagp.command_end;
+ const char *command_end = tagp.command_end;
if (command_end != NULL) {
p = command_end + 3;
while (*p && *p != '\r' && *p != '\n') {
@@ -884,7 +877,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
continue;
}
// print all other extra fields
- attr = HL_ATTR(HLF_CM);
+ int attr = HL_ATTR(HLF_CM);
while (*p && *p != '\r' && *p != '\n') {
if (msg_col + ptr2cells(p) >= Columns) {
msg_putchar('\n');
@@ -979,27 +972,20 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
/// window.
static int add_llist_tags(char *tag, int num_matches, char **matches)
{
- list_T *list;
char tag_name[128 + 1];
- char *fname;
- char *cmd;
- int i;
- char *p;
tagptrs_T tagp;
- fname = xmalloc(MAXPATHL + 1);
- cmd = xmalloc(CMDBUFFSIZE + 1);
- list = tv_list_alloc(0);
+ char *fname = xmalloc(MAXPATHL + 1);
+ char *cmd = xmalloc(CMDBUFFSIZE + 1);
+ list_T *list = tv_list_alloc(0);
- for (i = 0; i < num_matches; i++) {
- int len, cmd_len;
- long lnum;
+ for (int i = 0; i < num_matches; i++) {
dict_T *dict;
parse_match(matches[i], &tagp);
// Save the tag name
- len = (int)(tagp.tagname_end - tagp.tagname);
+ int len = (int)(tagp.tagname_end - tagp.tagname);
if (len > 128) {
len = 128;
}
@@ -1007,7 +993,7 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
tag_name[len] = NUL;
// Save the tag file name
- p = tag_full_fname(&tagp);
+ char *p = tag_full_fname(&tagp);
if (p == NULL) {
continue;
}
@@ -1016,18 +1002,16 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
// Get the line number or the search pattern used to locate
// the tag.
- lnum = 0;
+ linenr_T lnum = 0;
if (isdigit((uint8_t)(*tagp.command))) {
// Line number is used to locate the tag
- lnum = atol(tagp.command);
+ lnum = atoi(tagp.command);
} else {
- char *cmd_start, *cmd_end;
-
// Search pattern is used to locate the tag
// Locate the end of the command
- cmd_start = tagp.command;
- cmd_end = tagp.command_end;
+ char *cmd_start = tagp.command;
+ char *cmd_end = tagp.command_end;
if (cmd_end == NULL) {
for (p = tagp.command;
*p && *p != '\r' && *p != '\n'; p++) {}
@@ -1065,7 +1049,7 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
STRCAT(cmd, "\\V");
len += 2;
- cmd_len = (int)(cmd_end - cmd_start + 1);
+ int cmd_len = (int)(cmd_end - cmd_start + 1);
if (cmd_len > (CMDBUFFSIZE - 5)) {
cmd_len = CMDBUFFSIZE - 5;
}
@@ -1088,7 +1072,7 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
tv_list_append_dict(list, dict);
tv_dict_add_str(dict, S_LEN("text"), tag_name);
- tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname);
+ tv_dict_add_str(dict, S_LEN("filename"), fname);
tv_dict_add_nr(dict, S_LEN("lnum"), lnum);
if (lnum == 0) {
tv_dict_add_str(dict, S_LEN("pattern"), cmd);
@@ -1124,17 +1108,15 @@ static void taglen_advance(int l)
// Print the tag stack
void do_tags(exarg_T *eap)
{
- int i;
- char *name;
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
int tagstacklen = curwin->w_tagstacklen;
// Highlight title
msg_puts_title(_("\n # TO tag FROM line in file/text"));
- for (i = 0; i < tagstacklen; i++) {
+ for (int i = 0; i < tagstacklen; i++) {
if (tagstack[i].tagname != NULL) {
- name = fm_getname(&(tagstack[i].fmark), 30);
+ char *name = fm_getname(&(tagstack[i].fmark), 30);
if (name == NULL) { // file name not available
continue;
}
@@ -1146,9 +1128,8 @@ void do_tags(exarg_T *eap)
tagstack[i].cur_match + 1,
tagstack[i].tagname,
tagstack[i].fmark.mark.lnum);
- msg_outtrans(IObuff);
- msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum
- ? HL_ATTR(HLF_D) : 0);
+ msg_outtrans(IObuff, 0);
+ msg_outtrans(name, tagstack[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0);
xfree(name);
}
}
@@ -1162,10 +1143,8 @@ void do_tags(exarg_T *eap)
// Make sure case is folded to uppercase in comparison (like for 'sort -f')
static int tag_strnicmp(char *s1, char *s2, size_t len)
{
- int i;
-
while (len > 0) {
- i = TOUPPER_ASC((uint8_t)(*s1)) - TOUPPER_ASC((uint8_t)(*s2));
+ int i = TOUPPER_ASC((uint8_t)(*s1)) - TOUPPER_ASC((uint8_t)(*s2));
if (i != 0) {
return i; // this character different
}
@@ -1227,10 +1206,7 @@ static void prepare_pats(pat_T *pats, int has_re)
/// @param buf_ffname name of buffer for priority
static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flags, char *buf_ffname)
{
- pos_T save_pos;
- list_T *taglist;
int ntags = 0;
- int result = FAIL;
typval_T args[4];
typval_T rettv;
char flagString[4];
@@ -1256,10 +1232,10 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
// create 'info' dict argument
dict_T *const d = tv_dict_alloc_lock(VAR_FIXED);
if (tag != NULL && tag->user_data != NULL) {
- tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data);
+ tv_dict_add_str(d, S_LEN("user_data"), tag->user_data);
}
if (buf_ffname != NULL) {
- tv_dict_add_str(d, S_LEN("buf_ffname"), (const char *)buf_ffname);
+ tv_dict_add_str(d, S_LEN("buf_ffname"), buf_ffname);
}
d->dv_refcount++;
@@ -1270,12 +1246,12 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
vim_snprintf(flagString, sizeof(flagString),
"%s%s%s",
- g_tag_at_cursor ? "c": "",
- flags & TAG_INS_COMP ? "i": "",
- flags & TAG_REGEXP ? "r": "");
+ g_tag_at_cursor ? "c" : "",
+ flags & TAG_INS_COMP ? "i" : "",
+ flags & TAG_REGEXP ? "r" : "");
- save_pos = curwin->w_cursor;
- result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv);
+ pos_T save_pos = curwin->w_cursor;
+ int result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv);
curwin->w_cursor = save_pos; // restore the cursor position
d->dv_refcount--;
@@ -1288,10 +1264,10 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
}
if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) {
tv_clear(&rettv);
- emsg(_(tfu_inv_ret_msg));
+ emsg(_(e_invalid_return_value_from_tagfunc));
return FAIL;
}
- taglist = rettv.vval.v_list;
+ list_T *taglist = rettv.vval.v_list;
TV_LIST_ITER_CONST(taglist, li, {
char *res_name;
@@ -1302,7 +1278,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
int name_only = flags & TAG_NAMES;
if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) {
- emsg(_(tfu_inv_ret_msg));
+ emsg(_(e_invalid_return_value_from_tagfunc));
break;
}
@@ -1313,7 +1289,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
res_kind = NULL;
TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
- const char *dict_key = (char *)di->di_key;
+ const char *dict_key = di->di_key;
typval_T *tv = &di->di_tv;
if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
@@ -1348,7 +1324,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
}
if (!res_name || !res_fname || !res_cmd) {
- emsg(_(tfu_inv_ret_msg));
+ emsg(_(e_invalid_return_value_from_tagfunc));
break;
}
@@ -1382,7 +1358,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
}
TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
- const char *dict_key = (char *)di->di_key;
+ const char *dict_key = di->di_key;
typval_T *tv = &di->di_tv;
if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
continue;
@@ -1554,11 +1530,10 @@ static int findtags_apply_tfu(findtags_state_T *st, char *pat, char *buf_ffname)
static tags_read_status_T findtags_get_next_line(findtags_state_T *st, tagsearch_info_T *sinfo_p)
{
int eof;
- off_T offset;
// For binary search: compute the next offset to use.
if (st->state == TS_BINARY) {
- offset = sinfo_p->low_offset + ((sinfo_p->high_offset - sinfo_p->low_offset) / 2);
+ off_T offset = sinfo_p->low_offset + ((sinfo_p->high_offset - sinfo_p->low_offset) / 2);
if (offset == sinfo_p->curr_offset) {
return TAGS_READ_EOF; // End the binary search without a match.
} else {
@@ -1569,7 +1544,7 @@ static tags_read_status_T findtags_get_next_line(findtags_state_T *st, tagsearch
sinfo_p->curr_offset -= st->lbuf_size * 2;
if (sinfo_p->curr_offset < 0) {
sinfo_p->curr_offset = 0;
- rewind(st->fp);
+ (void)fseek(st->fp, 0, SEEK_SET);
st->state = TS_STEP_FORWARD;
}
}
@@ -1733,9 +1708,6 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
tagsearch_info_T *sinfo_p)
{
int status;
- int i;
- int cmplen;
- int tagcmp;
// Figure out where the different strings are in this line.
// For "normal" tags: Do a quick check if the tag matches.
@@ -1751,7 +1723,7 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
// Skip this line if the length of the tag is different and
// there is no regexp, or the tag is too short.
- cmplen = (int)(tagpp->tagname_end - tagpp->tagname);
+ int cmplen = (int)(tagpp->tagname_end - tagpp->tagname);
if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength'
cmplen = (int)p_tl;
}
@@ -1762,8 +1734,9 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
}
if (st->state == TS_BINARY) {
+ int tagcmp;
// Simplistic check for unsorted tags file.
- i = (int)tagpp->tagname[0];
+ int i = (uint8_t)tagpp->tagname[0];
if (margs->sortic) {
i = TOUPPER_ASC(tagpp->tagname[0]);
}
@@ -1912,13 +1885,13 @@ static bool findtags_match_tag(findtags_state_T *st, tagptrs_T *tagpp, findtags_
if (!match && st->orgpat->regmatch.regprog != NULL) {
char cc = *tagpp->tagname_end;
*tagpp->tagname_end = NUL;
- match = vim_regexec(&st->orgpat->regmatch, tagpp->tagname, (colnr_T)0);
+ match = vim_regexec(&st->orgpat->regmatch, tagpp->tagname, 0);
if (match) {
margs->matchoff = (int)(st->orgpat->regmatch.startp[0] - tagpp->tagname);
if (st->orgpat->regmatch.rm_ic) {
st->orgpat->regmatch.rm_ic = false;
margs->match_no_ic = vim_regexec(&st->orgpat->regmatch,
- tagpp->tagname, (colnr_T)0);
+ tagpp->tagname, 0);
st->orgpat->regmatch.rm_ic = true;
}
}
@@ -1959,11 +1932,10 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
const bool name_only = (st->flags & TAG_NAMES);
int mtt;
size_t len = 0;
+ size_t mfp_size = 0;
bool is_current; // file name matches
bool is_static; // current tag line is static
char *mfp;
- char *p;
- char *s;
// Decide in which array to store this match.
is_current = test_for_current(tagpp->fname, tagpp->fname_end,
@@ -2002,13 +1974,14 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
// The format is {tagname}@{lang}NUL{heuristic}NUL
*tagpp->tagname_end = NUL;
len = (size_t)(tagpp->tagname_end - tagpp->tagname);
- mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1);
+ mfp_size = sizeof(char) + len + 10 + ML_EXTRA + 1;
+ mfp = xmalloc(mfp_size);
- p = mfp;
+ char *p = mfp;
STRCPY(p, tagpp->tagname);
p[len] = '@';
STRCPY(p + len + 1, st->help_lang);
- snprintf(p + len + 1 + ML_EXTRA, strlen(p) + len + 1 + ML_EXTRA, "%06d",
+ snprintf(p + len + 1 + ML_EXTRA, mfp_size - (len + 1 + ML_EXTRA), "%06d",
help_heuristic(tagpp->tagname,
margs->match_re ? margs->matchoff : 0,
!margs->match_no_ic) + st->help_pri);
@@ -2055,7 +2028,7 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
// Here <mtt> is the "mtt" value plus 1 to avoid NUL.
len = tag_fname_len + strlen(st->lbuf) + 3;
mfp = xmalloc(sizeof(char) + len + 1);
- p = mfp;
+ char *p = mfp;
p[0] = (char)(mtt + 1);
STRCPY(p + 1, st->tag_fname);
#ifdef BACKSLASH_IN_FILENAME
@@ -2064,7 +2037,7 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
slash_adjust(p + 1);
#endif
p[tag_fname_len + 1] = TAG_SEP;
- s = p + 1 + tag_fname_len + 1;
+ char *s = p + 1 + tag_fname_len + 1;
STRCPY(s, st->lbuf);
}
@@ -2077,7 +2050,7 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
// follow after it. E.g. help tags store the priority
// after the NUL.
*hash = hash_hash(mfp);
- hi = hash_lookup(&st->ht_match[mtt], (const char *)mfp, strlen(mfp), *hash);
+ hi = hash_lookup(&st->ht_match[mtt], mfp, strlen(mfp), *hash);
if (HASHITEM_EMPTY(hi)) {
hash_add_item(&st->ht_match[mtt], hi, mfp, *hash);
GA_APPEND(char *, &st->ga_match[mtt], mfp);
@@ -2096,7 +2069,6 @@ static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *m
{
tagptrs_T tagp;
tagsearch_info_T search_info;
- int retval;
hash_T hash = 0;
// This is only to avoid a compiler warning for using search_info
@@ -2104,7 +2076,7 @@ static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *m
CLEAR_FIELD(search_info);
// Read and parse the lines in the file one by one
- for (;;) {
+ while (true) {
// check for CTRL-C typed, more often when jumping around
if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) {
line_breakcheck();
@@ -2128,7 +2100,7 @@ static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *m
goto line_read_in;
}
- retval = (int)findtags_get_next_line(st, &search_info);
+ int retval = (int)findtags_get_next_line(st, &search_info);
if (retval == TAGS_READ_IGNORE) {
continue;
}
@@ -2218,7 +2190,7 @@ static void findtags_in_file(findtags_state_T *st, int flags, char *buf_ffname)
if (p_verbose >= 5) {
verbose_enter();
- smsg(_("Searching tags file %s"), st->tag_fname);
+ smsg(0, _("Searching tags file %s"), st->tag_fname);
verbose_leave();
}
st->did_open = true; // remember that we found at least one file
@@ -2252,10 +2224,6 @@ static int findtags_copy_matches(findtags_state_T *st, char ***matchesp)
{
const bool name_only = (st->flags & TAG_NAMES);
char **matches;
- int mtt;
- int i;
- char *mfp;
- char *p;
if (st->match_count > 0) {
matches = xmalloc((size_t)st->match_count * sizeof(char *));
@@ -2263,9 +2231,9 @@ static int findtags_copy_matches(findtags_state_T *st, char ***matchesp)
matches = NULL;
}
st->match_count = 0;
- for (mtt = 0; mtt < MT_COUNT; mtt++) {
- for (i = 0; i < st->ga_match[mtt].ga_len; i++) {
- mfp = ((char **)(st->ga_match[mtt].ga_data))[i];
+ for (int mtt = 0; mtt < MT_COUNT; mtt++) {
+ for (int i = 0; i < st->ga_match[mtt].ga_len; i++) {
+ char *mfp = ((char **)(st->ga_match[mtt].ga_data))[i];
if (matches == NULL) {
xfree(mfp);
} else {
@@ -2274,7 +2242,7 @@ static int findtags_copy_matches(findtags_state_T *st, char ***matchesp)
*mfp = (char)(*mfp - 1);
// change the TAG_SEP back to NUL
- for (p = mfp + 1; *p != NUL; p++) {
+ for (char *p = mfp + 1; *p != NUL; p++) {
if (*p == TAG_SEP) {
*p = NUL;
}
@@ -2330,11 +2298,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
tagname_T tn; // info for get_tagfname()
int first_file; // trying first tag file
int retval = FAIL; // return value
- int round;
- int save_emsg_off;
-
- int help_save;
int i;
char *saved_pat = NULL; // copy of pat[]
@@ -2344,11 +2308,12 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
int verbose = (flags & TAG_VERBOSE);
int save_p_ic = p_ic;
+ // uncrustify:off
+
// Change the value of 'ignorecase' according to 'tagcase' for the
// duration of this function.
switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags) {
- case TC_FOLLOWIC:
- break;
+ case TC_FOLLOWIC: break;
case TC_IGNORE:
p_ic = true;
break;
@@ -2365,7 +2330,9 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
abort();
}
- help_save = curbuf->b_help;
+ // uncrustify:on
+
+ int help_save = curbuf->b_help;
findtags_state_init(&st, pat, flags, mincount);
@@ -2390,7 +2357,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
st.orgpat->len = (int)p_tl;
}
- save_emsg_off = emsg_off;
+ int save_emsg_off = emsg_off;
emsg_off = true; // don't want error for invalid RE here
prepare_pats(st.orgpat, has_re);
emsg_off = save_emsg_off;
@@ -2425,7 +2392,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
// Only ignore case when TAG_NOIC not used or 'ignorecase' set.
st.orgpat->regmatch.rm_ic = ((p_ic || !noic)
&& (findall || st.orgpat->headlen == 0 || !p_tbs));
- for (round = 1; round <= 2; round++) {
+ for (int round = 1; round <= 2; round++) {
st.linear = (st.orgpat->headlen == 0 || !p_tbs || round == 2);
// Try tag file names from tags option one by one.
@@ -2482,15 +2449,23 @@ static garray_T tag_fnames = GA_EMPTY_INIT_VALUE;
// Callback function for finding all "tags" and "tags-??" files in
// 'runtimepath' doc directories.
-static void found_tagfile_cb(char *fname, void *cookie)
+static bool found_tagfile_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
- char *const tag_fname = xstrdup(fname);
+ for (int i = 0; i < num_fnames; i++) {
+ char *const tag_fname = xstrdup(fnames[i]);
#ifdef BACKSLASH_IN_FILENAME
- slash_adjust(tag_fname);
+ slash_adjust(tag_fname);
#endif
- simplify_filename(tag_fname);
- GA_APPEND(char *, &tag_fnames, tag_fname);
+ simplify_filename(tag_fname);
+ GA_APPEND(char *, &tag_fnames, tag_fname);
+
+ if (!all) {
+ break;
+ }
+ }
+
+ return num_fnames > 0;
}
#if defined(EXITFREE)
@@ -2516,7 +2491,6 @@ void free_tag_stuff(void)
int get_tagfname(tagname_T *tnp, int first, char *buf)
{
char *fname = NULL;
- char *r_ptr;
if (first) {
CLEAR_POINTER(tnp);
@@ -2541,7 +2515,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
}
tnp->tn_hf_idx++;
STRCPY(buf, p_hf);
- STRCPY(path_tail((char *)buf), "tags");
+ STRCPY(path_tail(buf), "tags");
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(buf);
#endif
@@ -2569,7 +2543,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
// There are two states:
// tnp->tn_did_filefind_init == false: setup for next part in 'tags'.
// tnp->tn_did_filefind_init == true: find next file in this part.
- for (;;) {
+ while (true) {
if (tnp->tn_did_filefind_init) {
fname = vim_findfile(tnp->tn_search_ctx);
if (fname != NULL) {
@@ -2591,7 +2565,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
buf[0] = NUL;
(void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,");
- r_ptr = vim_findfile_stopdir(buf);
+ char *r_ptr = vim_findfile_stopdir(buf);
// move the filename one char forward and truncate the
// filepath with a NUL
filename = path_tail(buf);
@@ -2633,11 +2607,9 @@ void tagname_free(tagname_T *tnp)
/// @return FAIL if there is a format error in this line, OK otherwise.
static int parse_tag_line(char *lbuf, tagptrs_T *tagp)
{
- char *p;
-
// Isolate the tagname, from lbuf up to the first white
tagp->tagname = lbuf;
- p = vim_strchr(lbuf, TAB);
+ char *p = vim_strchr(lbuf, TAB);
if (p == NULL) {
return FAIL;
}
@@ -2680,10 +2652,8 @@ static int parse_tag_line(char *lbuf, tagptrs_T *tagp)
// Return false if it is not a static tag.
static bool test_for_static(tagptrs_T *tagp)
{
- char *p;
-
// Check for new style static tag ":...<Tab>file:[<Tab>...]"
- p = tagp->command;
+ char *p = tagp->command;
while ((p = vim_strchr(p, '\t')) != NULL) {
p++;
if (strncmp(p, "file:", 5) == 0) {
@@ -2717,70 +2687,68 @@ static size_t matching_line_len(const char *const lbuf)
/// @return OK or FAIL.
static int parse_match(char *lbuf, tagptrs_T *tagp)
{
- int retval;
- char *p;
- char *pc, *pt;
-
tagp->tag_fname = lbuf + 1;
lbuf += strlen(tagp->tag_fname) + 2;
// Find search pattern and the file name for non-etags.
- retval = parse_tag_line(lbuf, tagp);
+ int retval = parse_tag_line(lbuf, tagp);
tagp->tagkind = NULL;
tagp->user_data = NULL;
tagp->tagline = 0;
tagp->command_end = NULL;
- if (retval == OK) {
- // Try to find a kind field: "kind:<kind>" or just "<kind>"
- p = tagp->command;
- if (find_extra(&p) == OK) {
- tagp->command_end = p;
- if (p > tagp->command && p[-1] == '|') {
- tagp->command_end = p - 1; // drop trailing bar
- }
- p += 2; // skip ";\""
- if (*p++ == TAB) {
- // 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(p + 5);
- }
- if (tagp->tagkind != NULL && tagp->user_data != NULL) {
- break;
- }
+ if (retval != OK) {
+ return retval;
+ }
+
+ // Try to find a kind field: "kind:<kind>" or just "<kind>"
+ char *p = tagp->command;
+ if (find_extra(&p) == OK) {
+ tagp->command_end = p;
+ if (p > tagp->command && p[-1] == '|') {
+ tagp->command_end = p - 1; // drop trailing bar
+ }
+ p += 2; // skip ";\""
+ if (*p++ == TAB) {
+ // 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(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;
- }
- if (pt == NULL) {
- break;
- }
- p = pt;
- MB_PTR_ADV(p);
+ char *pc = vim_strchr(p, ':');
+ char *pt = vim_strchr(p, '\t');
+ if (pc == NULL || (pt != NULL && pc > pt)) {
+ tagp->tagkind = p;
}
+ if (pt == NULL) {
+ break;
+ }
+ p = pt;
+ MB_PTR_ADV(p);
}
}
- if (tagp->tagkind != NULL) {
- for (p = tagp->tagkind;
- *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;
- }
+ }
+ if (tagp->tagkind != NULL) {
+ for (p = tagp->tagkind;
+ *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;
}
@@ -2807,14 +2775,8 @@ static char *tag_full_fname(tagptrs_T *tagp)
/// @return OK for success, NOTAGFILE when file not found, FAIL otherwise.
static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
{
- bool save_p_ws;
- int save_p_scs, save_p_ic;
- linenr_T save_lnum;
- char *str;
- char *pbuf; // search pattern buffer
char *pbuf_end;
char *tofree_fname = NULL;
- char *fname;
tagptrs_T tagp;
int retval = FAIL;
int getfile_result = GETFILE_UNUSED;
@@ -2827,7 +2789,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
char *lbuf = xmalloc(len);
memmove(lbuf, lbuf_arg, len);
- pbuf = xmalloc(LSIZE);
+ char *pbuf = xmalloc(LSIZE); // search pattern buffer
// parse the match line into the tagp structure
if (parse_match(lbuf, &tagp) == FAIL) {
@@ -2837,10 +2799,10 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
// truncate the file name, so it can be used as a string
*tagp.fname_end = NUL;
- fname = tagp.fname;
+ char *fname = tagp.fname;
// copy the command to pbuf[], remove trailing CR/NL
- str = tagp.command;
+ char *str = tagp.command;
for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r';) {
*pbuf_end++ = *str++;
if (pbuf_end - pbuf + 1 >= LSIZE) {
@@ -2899,21 +2861,10 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
buf_T *const existing_buf = buflist_findname_exp(fname);
if (existing_buf != NULL) {
- const win_T *wp = NULL;
-
- if (swb_flags & SWB_USEOPEN) {
- wp = buf_jump_open_win(existing_buf);
- }
-
- // If 'switchbuf' contains "usetab": jump to first window in any tab
- // page containing "existing_buf" if one exists
- if (wp == NULL && (swb_flags & SWB_USETAB)) {
- wp = buf_jump_open_tab(existing_buf);
- }
-
- // We've switched to the buffer, the usual loading of the file must
- // be skipped.
- if (wp != NULL) {
+ // If 'switchbuf' is set jump to the window containing "buf".
+ if (swbuf_goto_win_with_buf(existing_buf) != NULL) {
+ // We've switched to the buffer, the usual loading of the file
+ // must be skipped.
getfile_result = GETFILE_SAME_FILE;
}
}
@@ -2941,7 +2892,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
if (getfile_result == GETFILE_UNUSED) {
// Careful: getfile() may trigger autocommands and call jumpto_tag()
// recursively.
- getfile_result = getfile(0, fname, NULL, true, (linenr_T)0, forceit);
+ getfile_result = getfile(0, fname, NULL, true, 0, forceit);
}
keep_help_flag = false;
@@ -2974,13 +2925,13 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
str = skip_regexp(pbuf + 1, pbuf[0], false) + 1;
}
if (str > pbuf_end - 1) { // search command with nothing following
- save_p_ws = p_ws;
- save_p_ic = p_ic;
- save_p_scs = p_scs;
+ bool save_p_ws = p_ws;
+ int save_p_ic = p_ic;
+ int save_p_scs = p_scs;
p_ws = true; // need 'wrapscan' for backward searches
p_ic = false; // don't ignore case now
p_scs = false;
- save_lnum = curwin->w_cursor.lnum;
+ linenr_T save_lnum = curwin->w_cursor.lnum;
if (tagp.tagline > 0) {
// start search before line from "line:" field
curwin->w_cursor.lnum = tagp.tagline - 1;
@@ -2988,28 +2939,26 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
// start search before first line
curwin->w_cursor.lnum = 0;
}
- if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
- search_options, NULL)) {
+ if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, 1, search_options, NULL)) {
retval = OK;
} else {
int found = 1;
- char cc;
// try again, ignore case now
p_ic = true;
- if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
+ if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, 1,
search_options, NULL)) {
// Failed to find pattern, take a guess: "^func ("
found = 2;
(void)test_for_static(&tagp);
- cc = *tagp.tagname_end;
+ char cc = *tagp.tagname_end;
*tagp.tagname_end = NUL;
snprintf(pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname);
- if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) {
+ if (!do_search(NULL, '/', '/', pbuf, 1, search_options, NULL)) {
// Guess again: "^char * \<func ("
snprintf(pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(",
tagp.tagname);
- if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) {
+ if (!do_search(NULL, '/', '/', pbuf, 1, search_options, NULL)) {
found = 0;
}
}
@@ -3022,17 +2971,17 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
// Only give a message when really guessed, not when 'ic'
// is set and match found while ignoring case.
if (found == 2 || !save_p_ic) {
- msg(_("E435: Couldn't find tag, just guessing!"));
+ msg(_("E435: Couldn't find tag, just guessing!"), 0);
if (!msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1010L, true);
+ os_delay(1010, true);
}
}
retval = OK;
}
}
p_ws = save_p_ws;
- p_ic = save_p_ic; // -V519
+ p_ic = save_p_ic;
p_scs = save_p_scs;
// A search command may have positioned the cursor beyond the end
@@ -3153,10 +3102,10 @@ static char *expand_tag_fname(char *fname, char *const tag_fname, const bool exp
/// file.
static int test_for_current(char *fname, char *fname_end, char *tag_fname, char *buf_ffname)
{
- char c;
int retval = false;
if (buf_ffname != NULL) { // if the buffer has a name
+ char c;
{
c = *fname_end;
*fname_end = NUL;
@@ -3178,7 +3127,7 @@ static int find_extra(char **pp)
char first_char = **pp;
// Repeat for addresses separated with ';'
- for (;;) {
+ while (true) {
if (ascii_isdigit(*str)) {
str = skipdigits(str + 1);
} else if (*str == '/' || *str == '?') {
@@ -3223,14 +3172,12 @@ static void tagstack_clear_entry(taggy_T *item)
/// @param tagnames expand tag names
int expand_tags(int tagnames, char *pat, int *num_file, char ***file)
{
- int i;
int extra_flag;
- char *name_buf;
size_t name_buf_size = 100;
tagptrs_T t_p;
int ret;
- name_buf = xmalloc(name_buf_size);
+ char *name_buf = xmalloc(name_buf_size);
if (tagnames) {
extra_flag = TAG_NAMES;
@@ -3249,7 +3196,7 @@ int expand_tags(int tagnames, char *pat, int *num_file, char ***file)
if (ret == OK && !tagnames) {
// Reorganize the tags for display and matching as strings of:
// "<tagname>\0<kind>\0<filename>\0"
- for (i = 0; i < *num_file; i++) {
+ for (int i = 0; i < *num_file; i++) {
size_t len;
parse_match((*file)[i], &t_p);
@@ -3265,7 +3212,7 @@ int expand_tags(int tagnames, char *pat, int *num_file, char ***file)
memmove(name_buf, t_p.tagname, len);
name_buf[len++] = 0;
name_buf[len++] = (t_p.tagkind != NULL && *t_p.tagkind)
- ? *t_p.tagkind : 'f';
+ ? *t_p.tagkind : 'f';
name_buf[len++] = 0;
memmove((*file)[i] + len, t_p.fname, (size_t)(t_p.fname_end - t_p.fname));
(*file)[i][len + (size_t)(t_p.fname_end - t_p.fname)] = 0;
@@ -3285,13 +3232,12 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
FUNC_ATTR_NONNULL_ARG(1, 2)
{
int len = 0;
- int retval;
// Check that the field name doesn't exist yet.
if (tv_dict_find(dict, field_name, -1) != NULL) {
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Duplicate field name: %s"), field_name);
+ smsg(0, _("Duplicate field name: %s"), field_name);
verbose_leave();
}
return FAIL;
@@ -3311,7 +3257,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
xstrlcpy(buf, start, (size_t)len + 1);
}
buf[len] = NUL;
- retval = tv_dict_add_str(dict, field_name, strlen(field_name), buf);
+ int retval = tv_dict_add_str(dict, field_name, strlen(field_name), buf);
xfree(buf);
return retval;
}
@@ -3320,115 +3266,109 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
/// as a dictionary. Use "buf_fname" for priority, unless NULL.
int get_tags(list_T *list, char *pat, char *buf_fname)
{
- int num_matches, i, ret;
+ int num_matches;
char **matches;
- char *full_fname;
- dict_T *dict;
tagptrs_T tp;
- bool is_static;
- ret = find_tags(pat, &num_matches, &matches,
- TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname);
- if (ret == OK && num_matches > 0) {
- for (i = 0; i < num_matches; i++) {
- int parse_result = parse_match(matches[i], &tp);
+ int ret = find_tags(pat, &num_matches, &matches, TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname);
+ if (ret != OK || num_matches <= 0) {
+ return ret;
+ }
- // Avoid an unused variable warning in release builds.
- (void)parse_result;
- assert(parse_result == OK);
+ for (int i = 0; i < num_matches; i++) {
+ if (parse_match(matches[i], &tp) == FAIL) {
+ xfree(matches[i]);
+ continue;
+ }
- is_static = test_for_static(&tp);
+ bool is_static = test_for_static(&tp);
- // Skip pseudo-tag lines.
- if (strncmp(tp.tagname, "!_TAG_", 6) == 0) {
- xfree(matches[i]);
- continue;
- }
+ // 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);
-
- full_fname = tag_full_fname(&tp);
- if (add_tag_field(dict, "name", tp.tagname, tp.tagname_end) == FAIL
- || add_tag_field(dict, "filename", full_fname, NULL) == FAIL
- || add_tag_field(dict, "cmd", tp.command, tp.command_end) == FAIL
- || add_tag_field(dict, "kind", tp.tagkind,
- tp.tagkind ? tp.tagkind_end : NULL) == FAIL
- || tv_dict_add_nr(dict, S_LEN("static"), is_static) == FAIL) {
- ret = FAIL;
- }
-
- xfree(full_fname);
-
- if (tp.command_end != NULL) {
- for (char *p = tp.command_end + 3;
- *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>"
- p = tp.tagkind_end - 1;
- } else if (strncmp(p, "file:", 5) == 0) {
- // skip "file:" (static tag)
- p += 4;
- } else if (!ascii_iswhite(*p)) {
- char *s, *n;
- int len;
-
- // Add extra field as a dict entry. Fields are
- // separated by Tabs.
- n = p;
- while (*p != NUL && *p >= ' ' && *p < 127 && *p != ':') {
+ dict_T *dict = tv_dict_alloc();
+ tv_list_append_dict(list, dict);
+
+ char *full_fname = tag_full_fname(&tp);
+ if (add_tag_field(dict, "name", tp.tagname, tp.tagname_end) == FAIL
+ || add_tag_field(dict, "filename", full_fname, NULL) == FAIL
+ || add_tag_field(dict, "cmd", tp.command, tp.command_end) == FAIL
+ || add_tag_field(dict, "kind", tp.tagkind,
+ tp.tagkind ? tp.tagkind_end : NULL) == FAIL
+ || tv_dict_add_nr(dict, S_LEN("static"), is_static) == FAIL) {
+ ret = FAIL;
+ }
+
+ xfree(full_fname);
+
+ if (tp.command_end != NULL) {
+ for (char *p = tp.command_end + 3;
+ *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>"
+ p = tp.tagkind_end - 1;
+ } else if (strncmp(p, "file:", 5) == 0) {
+ // skip "file:" (static tag)
+ p += 4;
+ } else if (!ascii_iswhite(*p)) {
+ char *n;
+ int len;
+
+ // Add extra field as a dict entry. Fields are
+ // separated by Tabs.
+ n = p;
+ while (*p != NUL && *p >= ' ' && *p < 127 && *p != ':') {
+ p++;
+ }
+ len = (int)(p - n);
+ if (*p == ':' && len > 0) {
+ char *s = ++p;
+ while (*p != NUL && (uint8_t)(*p) >= ' ') {
p++;
}
- len = (int)(p - n);
- if (*p == ':' && len > 0) {
- s = ++p;
- while (*p != NUL && (uint8_t)(*p) >= ' ') {
- p++;
- }
- n[len] = NUL;
- if (add_tag_field(dict, n, s, p) == FAIL) {
- ret = FAIL;
- }
- n[len] = ':';
- } else {
- // Skip field without colon.
- while (*p != NUL && (uint8_t)(*p) >= ' ') {
- p++;
- }
+ n[len] = NUL;
+ if (add_tag_field(dict, n, s, p) == FAIL) {
+ ret = FAIL;
}
- if (*p == NUL) {
- break;
+ n[len] = ':';
+ } else {
+ // Skip field without colon.
+ while (*p != NUL && (uint8_t)(*p) >= ' ') {
+ p++;
}
}
+ if (*p == NUL) {
+ break;
+ }
}
}
-
- xfree(matches[i]);
}
- xfree(matches);
+
+ xfree(matches[i]);
}
+ xfree(matches);
return ret;
}
// Return information about 'tag' in dict 'retdict'.
static void get_tag_details(taggy_T *tag, dict_T *retdict)
{
- list_T *pos;
- fmark_T *fmark;
-
- tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname);
+ tv_dict_add_str(retdict, S_LEN("tagname"), tag->tagname);
tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1);
tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum);
if (tag->user_data) {
- tv_dict_add_str(retdict, S_LEN("user_data"), (const char *)tag->user_data);
+ tv_dict_add_str(retdict, S_LEN("user_data"), tag->user_data);
}
- pos = tv_list_alloc(4);
+ list_T *pos = tv_list_alloc(4);
tv_dict_add_list(retdict, S_LEN("from"), pos);
- fmark = &tag->fmark;
+ fmark_T *fmark = &tag->fmark;
tv_list_append_number(pos,
(varnumber_T)(fmark->fnum != -1 ? fmark->fnum : 0));
tv_list_append_number(pos, (varnumber_T)fmark->mark.lnum);
@@ -3441,17 +3381,13 @@ static void get_tag_details(taggy_T *tag, dict_T *retdict)
// 'retdict'.
void get_tagstack(win_T *wp, dict_T *retdict)
{
- list_T *l;
- int i;
- dict_T *d;
-
tv_dict_add_nr(retdict, S_LEN("length"), wp->w_tagstacklen);
tv_dict_add_nr(retdict, S_LEN("curidx"), wp->w_tagstackidx + 1);
- l = tv_list_alloc(2);
+ list_T *l = tv_list_alloc(2);
tv_dict_add_list(retdict, S_LEN("items"), l);
- for (i = 0; i < wp->w_tagstacklen; i++) {
- d = tv_dict_alloc();
+ for (int i = 0; i < wp->w_tagstacklen; i++) {
+ dict_T *d = tv_dict_alloc();
tv_list_append_dict(l, d);
get_tag_details(&wp->w_tagstack[i], d);
}
@@ -3508,20 +3444,18 @@ static void tagstack_push_item(win_T *wp, char *tagname, int cur_fnum, int cur_m
/// Add a list of items to the tag stack in the specified window
static void tagstack_push_items(win_T *wp, list_T *l)
{
- listitem_T *li;
dictitem_T *di;
- dict_T *itemdict;
char *tagname;
pos_T mark;
int fnum;
// Add one entry at a time to the tag stack
- for (li = tv_list_first(l); li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ for (listitem_T *li = tv_list_first(l); li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
|| TV_LIST_ITEM_TV(li)->vval.v_dict == NULL) {
continue; // Skip non-dict items
}
- itemdict = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ dict_T *itemdict = TV_LIST_ITEM_TV(li)->vval.v_dict;
// parse 'from' for the cursor position before the tag jump
if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) {
@@ -3572,7 +3506,7 @@ int set_tagstack(win_T *wp, const dict_T *d, int action)
// not allowed to alter the tag stack entries from inside tagfunc
if (tfu_in_use) {
- emsg(_(recurmsg));
+ emsg(_(e_cannot_modify_tag_stack_within_tagfunc));
return FAIL;
}
diff --git a/src/nvim/tag.h b/src/nvim/tag.h
index e08145f727..87e71c8bef 100644
--- a/src/nvim/tag.h
+++ b/src/nvim/tag.h
@@ -1,39 +1,44 @@
-#ifndef NVIM_TAG_H
-#define NVIM_TAG_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
-// Values for do_tag().
-#define DT_TAG 1 // jump to newer position or same tag again
-#define DT_POP 2 // jump to older position
-#define DT_NEXT 3 // jump to next match of same tag
-#define DT_PREV 4 // jump to previous match of same tag
-#define DT_FIRST 5 // jump to first match of same tag
-#define DT_LAST 6 // jump to first match of same tag
-#define DT_SELECT 7 // jump to selection from list
-#define DT_HELP 8 // like DT_TAG, but no wildcards
-#define DT_JUMP 9 // jump to new tag or selection from list
-#define DT_LTAG 11 // tag using location list
-#define DT_FREE 99 // free cached matches
+enum { LSIZE = 512, }; ///< max. size of a line in the tags file
-// flags for find_tags().
-#define TAG_HELP 1 // only search for help tags
-#define TAG_NAMES 2 // only return name of tag
-#define TAG_REGEXP 4 // use tag pattern as regexp
-#define TAG_NOIC 8 // don't always ignore case
-#define TAG_VERBOSE 32 // message verbosity
-#define TAG_INS_COMP 64 // Currently doing insert completion
-#define TAG_KEEP_LANG 128 // keep current language
-#define TAG_NO_TAGFUNC 256 // do not use 'tagfunc'
+/// Values for do_tag().
+enum {
+ DT_TAG = 1, ///< jump to newer position or same tag again
+ DT_POP = 2, ///< jump to older position
+ DT_NEXT = 3, ///< jump to next match of same tag
+ DT_PREV = 4, ///< jump to previous match of same tag
+ DT_FIRST = 5, ///< jump to first match of same tag
+ DT_LAST = 6, ///< jump to first match of same tag
+ DT_SELECT = 7, ///< jump to selection from list
+ DT_HELP = 8, ///< like DT_TAG, but no wildcards
+ DT_JUMP = 9, ///< jump to new tag or selection from list
+ DT_LTAG = 11, ///< tag using location list
+ DT_FREE = 99, ///< free cached matches
+};
-#define TAG_MANY 300 // When finding many tags (for completion),
- // find up to this many tags
+/// flags for find_tags().
+enum {
+ TAG_HELP = 1, ///< only search for help tags
+ TAG_NAMES = 2, ///< only return name of tag
+ TAG_REGEXP = 4, ///< use tag pattern as regexp
+ TAG_NOIC = 8, ///< don't always ignore case
+ TAG_VERBOSE = 32, ///< message verbosity
+ TAG_INS_COMP = 64, ///< Currently doing insert completion
+ TAG_KEEP_LANG = 128, ///< keep current language
+ TAG_NO_TAGFUNC = 256, ///< do not use 'tagfunc'
+ TAG_MANY = 300, ///< When finding many tags (for completion), find up to this many tags
+};
-// Structure used for get_tagfname().
+/// Structure used for get_tagfname().
typedef struct {
- char *tn_tags; // value of 'tags' when starting
- char *tn_np; // current position in tn_tags
+ char *tn_tags; ///< value of 'tags' when starting
+ char *tn_np; ///< current position in tn_tags
int tn_did_filefind_init;
int tn_hf_idx;
void *tn_search_ctx;
@@ -42,4 +47,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tag.h.generated.h"
#endif
-#endif // NVIM_TAG_H
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index a52a34cd66..1527738165 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// VT220/xterm-like terminal emulator.
// Powered by libvterm http://www.leonerd.org.uk/code/libvterm
//
@@ -48,7 +45,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -59,19 +56,18 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/time.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -79,15 +75,16 @@
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/normal.h"
+#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
typedef struct terminal_state {
VimState state;
@@ -161,16 +158,16 @@ struct terminal {
};
static VTermScreenCallbacks vterm_screen_callbacks = {
- .damage = term_damage,
- .moverect = term_moverect,
- .movecursor = term_movecursor,
+ .damage = term_damage,
+ .moverect = term_moverect,
+ .movecursor = term_movecursor,
.settermprop = term_settermprop,
- .bell = term_bell,
+ .bell = term_bell,
.sb_pushline = term_sb_push, // Called before a line goes offscreen.
- .sb_popline = term_sb_pop,
+ .sb_popline = term_sb_pop,
};
-static PMap(ptr_t) invalidated_terminals = MAP_INIT;
+static Set(ptr_t) invalidated_terminals = SET_INIT;
void terminal_init(void)
{
@@ -184,10 +181,10 @@ void terminal_teardown(void)
time_watcher_stop(&refresh_timer);
multiqueue_free(refresh_timer.events);
time_watcher_close(&refresh_timer, NULL);
- pmap_destroy(ptr_t)(&invalidated_terminals);
+ set_destroy(ptr_t, &invalidated_terminals);
// terminal_destroy might be called after terminal_teardown is invoked
// make sure it is in an empty, valid state
- pmap_init(ptr_t, &invalidated_terminals);
+ invalidated_terminals = (Set(ptr_t)) SET_INIT;
}
static void term_output_callback(const char *s, size_t len, void *user_data)
@@ -204,10 +201,11 @@ static void term_output_callback(const char *s, size_t len, void *user_data)
///
/// @param buf Buffer used for presentation of the terminal.
/// @param opts PTY process channel, various terminal properties and callbacks.
-Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
+void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
+ FUNC_ATTR_NONNULL_ALL
{
// Create a new terminal instance and configure it
- Terminal *rv = xcalloc(1, sizeof(Terminal));
+ Terminal *rv = *termpp = xcalloc(1, sizeof(Terminal));
rv->opts = opts;
rv->cursor.visible = true;
// Associate the terminal instance with the new buffer
@@ -221,6 +219,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
// Set up screen
rv->vts = vterm_obtain_screen(rv->vt);
vterm_screen_enable_altscreen(rv->vts, true);
+ vterm_screen_enable_reflow(rv->vts, true);
// delete empty lines at the end of the buffer
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
@@ -235,7 +234,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
aucmd_prepbuf(&aco, buf);
refresh_screen(rv, buf);
- set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("terminal"), OPT_LOCAL);
// Default settings for terminal buffers
buf->b_p_ma = false; // 'nomodifiable'
@@ -243,26 +242,34 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
buf->b_p_scbk = // 'scrollback' (initialize local from global)
(p_scbk < 0) ? 10000 : MAX(1, p_scbk);
buf->b_p_tw = 0; // 'textwidth'
- set_option_value("wrap", false, NULL, OPT_LOCAL);
- set_option_value("list", false, NULL, OPT_LOCAL);
+ set_option_value("wrap", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("list", BOOLEAN_OPTVAL(false), OPT_LOCAL);
if (buf->b_ffname != NULL) {
buf_set_term_title(buf, buf->b_ffname, strlen(buf->b_ffname));
}
RESET_BINDING(curwin);
// Reset cursor in current window.
curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 };
- // Initialize to check if the scrollback buffer has been allocated inside a TermOpen autocmd
+ // Initialize to check if the scrollback buffer has been allocated in a TermOpen autocmd.
rv->sb_buffer = NULL;
// Apply TermOpen autocmds _before_ configuring the scrollback buffer.
apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf);
- // Local 'scrollback' _after_ autocmds.
- buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk;
aucmd_restbuf(&aco);
- // Configure the scrollback buffer.
- rv->sb_size = (size_t)buf->b_p_scbk;
- rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
+ if (*termpp == NULL) {
+ return; // Terminal has already been destroyed.
+ }
+
+ if (rv->sb_buffer == NULL) {
+ // Local 'scrollback' _after_ autocmds.
+ if (buf->b_p_scbk < 1) {
+ buf->b_p_scbk = SB_MAX;
+ }
+ // Configure the scrollback buffer.
+ rv->sb_size = (size_t)buf->b_p_scbk;
+ rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
+ }
// Configure the color palette. Try to get the color from:
//
@@ -290,14 +297,13 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
}
}
}
-
- return rv;
}
/// Closes the Terminal buffer.
///
/// May call terminal_destroy, which sets caller storage to NULL.
void terminal_close(Terminal **termpp, int status)
+ FUNC_ATTR_NONNULL_ALL
{
Terminal *term = *termpp;
if (term->destroy) {
@@ -437,8 +443,8 @@ bool terminal_enter(void)
char *save_w_p_culopt = NULL;
uint8_t save_w_p_culopt_flags = curwin->w_p_culopt_flags;
int save_w_p_cuc = curwin->w_p_cuc;
- long save_w_p_so = curwin->w_p_so;
- long save_w_p_siso = curwin->w_p_siso;
+ OptInt save_w_p_so = curwin->w_p_so;
+ OptInt save_w_p_siso = curwin->w_p_siso;
if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) {
if (strcmp(curwin->w_p_culopt, "number") != 0) {
save_w_p_culopt = curwin->w_p_culopt;
@@ -532,6 +538,7 @@ static int terminal_check(VimState *state)
}
terminal_check_cursor();
+ validate_cursor();
if (must_redraw) {
update_screen();
@@ -576,6 +583,8 @@ static int terminal_execute(VimState *state, int key)
case K_RIGHTRELEASE:
case K_MOUSEDOWN:
case K_MOUSEUP:
+ case K_MOUSELEFT:
+ case K_MOUSERIGHT:
if (send_mouse_event(s->term, key)) {
return 0;
}
@@ -597,7 +606,7 @@ static int terminal_execute(VimState *state, int key)
break;
case K_LUA:
- map_execute_lua();
+ map_execute_lua(false);
break;
case Ctrl_N:
@@ -644,6 +653,7 @@ static int terminal_execute(VimState *state, int key)
/// Frees the given Terminal structure and sets the caller storage to NULL (in the spirit of
/// XFREE_CLEAR).
void terminal_destroy(Terminal **termpp)
+ FUNC_ATTR_NONNULL_ALL
{
Terminal *term = *termpp;
buf_T *buf = handle_get_buffer(term->buf_handle);
@@ -653,12 +663,12 @@ void terminal_destroy(Terminal **termpp)
}
if (!term->refcount) {
- if (pmap_has(ptr_t)(&invalidated_terminals, term)) {
+ if (set_has(ptr_t, &invalidated_terminals, term)) {
// flush any pending changes to the buffer
block_autocmds();
refresh_terminal(term);
unblock_autocmds();
- pmap_del(ptr_t)(&invalidated_terminals, term);
+ set_del(ptr_t, &invalidated_terminals, term);
}
for (size_t i = 0; i < term->sb_current; i++) {
xfree(term->sb_buffer[i]);
@@ -681,7 +691,7 @@ void terminal_send(Terminal *term, char *data, size_t size)
static bool is_filter_char(int c)
{
- unsigned int flag = 0;
+ unsigned flag = 0;
switch (c) {
case 0x08:
flag = TPF_BS;
@@ -711,7 +721,7 @@ static bool is_filter_char(int c)
return !!(tpf_flags & flag);
}
-void terminal_paste(long count, char **y_array, size_t y_size)
+void terminal_paste(int count, char **y_array, size_t y_size)
{
if (y_size == 0) {
return;
@@ -719,12 +729,16 @@ void terminal_paste(long count, char **y_array, size_t y_size)
vterm_keyboard_start_paste(curbuf->terminal->vt);
size_t buff_len = strlen(y_array[0]);
char *buff = xmalloc(buff_len);
- for (int i = 0; i < count; i++) { // -V756
+ for (int i = 0; i < count; i++) {
// feed the lines to the terminal
for (size_t j = 0; j < y_size; j++) {
if (j) {
// terminate the previous line
+#ifdef MSWIN
+ terminal_send(curbuf->terminal, "\r\n", 2);
+#else
terminal_send(curbuf->terminal, "\n", 1);
+#endif
}
size_t len = strlen(y_array[j]);
if (len > buff_len) {
@@ -762,7 +776,7 @@ void terminal_send_key(Terminal *term, int c)
if (key) {
vterm_keyboard_key(term->vt, key, mod);
- } else {
+ } else if (!IS_SPECIAL(c)) {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);
}
}
@@ -836,7 +850,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
| (cell.attrs.italic ? HL_ITALIC : 0)
| (cell.attrs.reverse ? HL_INVERSE : 0)
| get_underline_hl_flag(cell.attrs)
- | (cell.attrs.strike ? HL_STRIKETHROUGH: 0)
+ | (cell.attrs.strike ? HL_STRIKETHROUGH : 0)
| ((fg_indexed && !fg_set) ? HL_FG_INDEXED : 0)
| ((bg_indexed && !bg_set) ? HL_BG_INDEXED : 0);
@@ -893,13 +907,13 @@ static int term_moverect(VTermRect dest, VTermRect src, void *data)
return 1;
}
-static int term_movecursor(VTermPos new, VTermPos old, int visible, void *data)
+static int term_movecursor(VTermPos new_pos, VTermPos old_pos, int visible, void *data)
{
Terminal *term = data;
- term->cursor.row = new.row;
- term->cursor.col = new.col;
- invalidate_terminal(term, old.row, old.row + 1);
- invalidate_terminal(term, new.row, new.row + 1);
+ term->cursor.row = new_pos.row;
+ term->cursor.col = new_pos.col;
+ invalidate_terminal(term, old_pos.row, old_pos.row + 1);
+ invalidate_terminal(term, new_pos.row, new_pos.row + 1);
return 1;
}
@@ -1024,7 +1038,7 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
}
memcpy(sbrow->cells, cells, sizeof(cells[0]) * c);
- pmap_put(ptr_t)(&invalidated_terminals, term, NULL);
+ set_put(ptr_t, &invalidated_terminals, term);
return 1;
}
@@ -1065,7 +1079,7 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
}
xfree(sbrow);
- pmap_put(ptr_t)(&invalidated_terminals, term, NULL);
+ set_put(ptr_t, &invalidated_terminals, term);
return 1;
}
@@ -1091,6 +1105,8 @@ static void convert_modifiers(int key, VTermModifier *statep)
case K_S_DOWN:
case K_S_LEFT:
case K_S_RIGHT:
+ case K_S_HOME:
+ case K_S_END:
case K_S_F1:
case K_S_F2:
case K_S_F3:
@@ -1108,6 +1124,8 @@ static void convert_modifiers(int key, VTermModifier *statep)
case K_C_LEFT:
case K_C_RIGHT:
+ case K_C_HOME:
+ case K_C_END:
*statep |= VTERM_MOD_CTRL;
break;
}
@@ -1154,8 +1172,16 @@ static VTermKey convert_key(int key, VTermModifier *statep)
return VTERM_KEY_INS;
case K_DEL:
return VTERM_KEY_DEL;
+ case K_S_HOME:
+ FALLTHROUGH;
+ case K_C_HOME:
+ FALLTHROUGH;
case K_HOME:
return VTERM_KEY_HOME;
+ case K_S_END:
+ FALLTHROUGH;
+ case K_C_END:
+ FALLTHROUGH;
case K_END:
return VTERM_KEY_END;
case K_PAGEUP:
@@ -1389,13 +1415,14 @@ static void mouse_action(Terminal *term, int button, int row, int col, bool pres
static bool send_mouse_event(Terminal *term, int c)
{
int row = mouse_row, col = mouse_col, grid = mouse_grid;
- int offset;
win_T *mouse_win = mouse_find_win(&grid, &row, &col);
- if (mouse_win == NULL || (offset = win_col_off(mouse_win)) > col) {
+ if (mouse_win == NULL) {
goto end;
}
- if (term->forward_mouse && mouse_win->w_buffer->terminal == term) {
+ int offset;
+ if (term->forward_mouse && mouse_win->w_buffer->terminal == term
+ && col >= (offset = win_col_off(mouse_win))) {
// event in the terminal window and mouse events was enabled by the
// program. translate and forward the event
int button;
@@ -1423,26 +1450,52 @@ static bool send_mouse_event(Terminal *term, int c)
pressed = true; button = 4; break;
case K_MOUSEUP:
pressed = true; button = 5; break;
+ case K_MOUSELEFT:
+ pressed = true; button = 7; break;
+ case K_MOUSERIGHT:
+ pressed = true; button = 6; break;
default:
return false;
}
- mouse_action(term, button, row, col - offset, pressed, 0);
+ VTermModifier mod = VTERM_MOD_NONE;
+ convert_modifiers(c, &mod);
+ mouse_action(term, button, row, col - offset, pressed, mod);
return false;
}
- if (c == K_MOUSEDOWN || c == K_MOUSEUP) {
+ if (c == K_MOUSEUP || c == K_MOUSEDOWN || c == K_MOUSELEFT || c == K_MOUSERIGHT) {
win_T *save_curwin = curwin;
// switch window/buffer to perform the scroll
curwin = mouse_win;
curbuf = curwin->w_buffer;
- int direction = c == K_MOUSEDOWN ? MSCR_DOWN : MSCR_UP;
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- scroll_redraw(direction, curwin->w_botline - curwin->w_topline);
- } else if (p_mousescroll_vert > 0) {
- scroll_redraw(direction, p_mousescroll_vert);
+
+ cmdarg_T cap;
+ oparg_T oa;
+ CLEAR_FIELD(cap);
+ clear_oparg(&oa);
+ cap.oap = &oa;
+
+ switch (cap.cmdchar = c) {
+ case K_MOUSEUP:
+ cap.arg = MSCR_UP;
+ break;
+ case K_MOUSEDOWN:
+ cap.arg = MSCR_DOWN;
+ break;
+ case K_MOUSELEFT:
+ cap.arg = MSCR_LEFT;
+ break;
+ case K_MOUSERIGHT:
+ cap.arg = MSCR_RIGHT;
+ break;
+ default:
+ abort();
}
+ // Call the common mouse scroll function shared with other modes.
+ do_mousescroll(&cap);
+
curwin->w_redr_status = true;
curwin = save_curwin;
curbuf = curwin->w_buffer;
@@ -1452,13 +1505,14 @@ static bool send_mouse_event(Terminal *term, int c)
return mouse_win == curwin;
}
- // ignore left release action if it was not processed above
- // to prevent leaving Terminal mode after entering to it using a mouse
- if (c == K_LEFTRELEASE && mouse_win->w_buffer->terminal == term) {
+end:
+ // Ignore left release action if it was not forwarded to prevent
+ // leaving Terminal mode after entering to it using a mouse.
+ if ((c == K_LEFTRELEASE && mouse_win != NULL && mouse_win->w_buffer->terminal == term)
+ || c == K_MOUSEMOVE) {
return false;
}
-end:
ins_char_typebuf(vgetc_char, vgetc_mod_mask);
return true;
}
@@ -1521,7 +1575,7 @@ static void invalidate_terminal(Terminal *term, int start_row, int end_row)
term->invalid_end = MAX(term->invalid_end, end_row);
}
- pmap_put(ptr_t)(&invalidated_terminals, term, NULL);
+ set_put(ptr_t, &invalidated_terminals, term);
if (!refresh_pending) {
time_watcher_start(&refresh_timer, refresh_timer_cb, REFRESH_DELAY, 0);
refresh_pending = true;
@@ -1539,7 +1593,7 @@ static void refresh_terminal(Terminal *term)
}
return;
}
- long ml_before = buf->b_ml.ml_line_count;
+ linenr_T ml_before = buf->b_ml.ml_line_count;
// refresh_ functions assume the terminal buffer is current
aco_save_T aco;
@@ -1549,7 +1603,7 @@ static void refresh_terminal(Terminal *term)
refresh_screen(term, buf);
aucmd_restbuf(&aco);
- long ml_added = buf->b_ml.ml_line_count - ml_before;
+ int ml_added = buf->b_ml.ml_line_count - ml_before;
adjust_topline(term, buf, ml_added);
}
@@ -1564,10 +1618,10 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data)
void *stub; (void)(stub);
// don't process autocommands while updating terminal buffers
block_autocmds();
- map_foreach(&invalidated_terminals, term, stub, {
+ set_foreach(&invalidated_terminals, term, {
refresh_terminal(term);
});
- pmap_clear(ptr_t)(&invalidated_terminals);
+ set_clear(ptr_t, &invalidated_terminals);
unblock_autocmds();
}
@@ -1704,12 +1758,12 @@ static void refresh_screen(Terminal *term, buf_T *buf)
int change_start = row_to_linenr(term, term->invalid_start);
int change_end = change_start + changed;
- changed_lines(change_start, 0, change_end, added, true);
+ changed_lines(buf, change_start, 0, change_end, added, true);
term->invalid_start = INT_MAX;
term->invalid_end = -1;
}
-static void adjust_topline(Terminal *term, buf_T *buf, long added)
+static void adjust_topline(Terminal *term, buf_T *buf, int added)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == buf) {
diff --git a/src/nvim/terminal.h b/src/nvim/terminal.h
index a83929e224..66cad7ee7a 100644
--- a/src/nvim/terminal.h
+++ b/src/nvim/terminal.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_TERMINAL_H
-#define NVIM_TERMINAL_H
+#pragma once
-#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@@ -10,7 +8,7 @@ typedef void (*terminal_write_cb)(char *buffer, size_t size, void *data);
typedef void (*terminal_resize_cb)(uint16_t width, uint16_t height, void *data);
typedef void (*terminal_close_cb)(void *data);
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
typedef struct {
void *data; // PTY process channel
@@ -23,4 +21,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "terminal.h.generated.h"
#endif
-#endif // NVIM_TERMINAL_H
diff --git a/src/nvim/testdir/Make_all.mak b/src/nvim/testdir/Make_all.mak
deleted file mode 100644
index b917877422..0000000000
--- a/src/nvim/testdir/Make_all.mak
+++ /dev/null
@@ -1 +0,0 @@
-# Tests may depend on the existence of this file.
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
deleted file mode 100644
index 9511b311e6..0000000000
--- a/src/nvim/testdir/Makefile
+++ /dev/null
@@ -1,154 +0,0 @@
-# vim: noet ts=8
-# Makefile to run all tests for Vim
-#
-
-ifeq ($(OS),Windows_NT)
- NVIM_PRG ?= ../../../build/bin/nvim.exe
-else
- NVIM_PRG ?= ../../../build/bin/nvim
-endif
-ROOT := ../../..
-
-export SHELL := sh
-export NVIM_PRG := $(NVIM_PRG)
-export TMPDIR := $(abspath X-test-tmpdir)
-
-ifeq ($(OS),Windows_NT)
- FIXFF = fixff
-else
- FIXFF =
-endif
-
-# Tests using runtest.vim.
-NEW_TESTS_ALOT := test_alot_utf8 test_alot test_alot_latin
-NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT)))
-# Ignored tests.
-# test_largefile: uses too much resources to run on CI.
-NEW_TESTS_IGNORE := \
- test_largefile \
-
-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
- VGDB := --vgdb=yes \
- --vgdb-error=0
-endif
-
-ifdef USE_VALGRIND
- VALGRIND_TOOL := --tool=memcheck \
- --leak-check=yes \
- --track-origins=yes
-# VALGRIND_TOOL := exp-sgcheck
- TOOL := valgrind -q \
- -q \
- $(VALGRIND_TOOL) \
- --suppressions=../../.valgrind.supp \
- --error-exitcode=123 \
- --log-file=valgrind-\%p.$* \
- $(VGDB) \
- --trace-children=yes
-else
- ifdef USE_GDB
- TOOL = gdb --args
- endif
-endif
-
-nongui: nolog $(FIXFF) newtests report
-
-.gdbinit:
- @echo "[OLDTEST-PREP] Setting up .gdbinit"
- @echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit
-
-report:
- $(NVIM_PRG) -u NONE $(NO_INITS) -S summarize.vim messages
- @echo
- @echo 'Test results:'
- @cat test_result.log
- @/bin/sh -c "if test -f test.log; \
- then echo TEST FAILURE; exit 1; \
- else echo ALL DONE; \
- fi"
-
-test1.out: $(NVIM_PRG)
-
-NO_PLUGINS = --noplugin --headless
-# In vim, if the -u command line option is specified, compatible is turned on
-# and viminfo is not read. Unlike vim, neovim reads viminfo and requires the
-# -i command line option.
-NO_INITS = -U NONE -i NONE $(NO_PLUGINS)
-
-# TODO: find a way to avoid changing the distributed files.
-fixff:
- -$(NVIM_PRG) $(NO_INITS) -u unix.vim "+argdo set ff=dos|upd" +q \
- *.in *.ok
- -$(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
-
-# Delete files that may interfere with running tests. This includes some files
-# that may result from working on the tests, not only from running them.
-CLEAN_FILES := *.out \
- *.failed \
- *.res \
- *.rej \
- *.orig \
- *.tlog \
- test.log \
- test_result.log \
- messages \
- $(RM_ON_RUN) \
- $(RM_ON_START) \
- valgrind.* \
- .*.swp \
- .*.swo \
- .gdbinit \
- $(TMPDIR) \
- del
-clean:
- $(RM) -rf $(CLEAN_FILES)
-
-test1.out: .gdbinit test1.in
- @echo "[OLDTEST-PREP] Running test1"
- @rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize
- @mkdir -p $(TMPDIR)
- @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in
- @rm -f wrongtermsize
- @rm -rf X* viminfo
-
-nolog:
- @echo "[OLDTEST-PREP] Removing test.log and messages"
- @rm -f test.log messages
-
-
-# New style of tests uses Vim script with assert calls. These are easier
-# to write and a lot easier to read and debug.
-# Limitation: Only works with the +eval feature.
-RUN_VIMTEST = $(TOOL) $(NVIM_PRG) -u unix.vim
-
-newtests: newtestssilent
- @/bin/sh -c "if test -f messages && grep -q 'FAILED' messages; then \
- cat messages && cat test.log; \
- fi"
-
-newtestssilent: $(NEW_TESTS_RES)
-
-%.res: %.vim .gdbinit
- @echo "[OLDTEST] Running" $*
- @rm -rf $*.failed test.ok $(RM_ON_RUN)
- @mkdir -p $(TMPDIR)
- @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) $(NO_INITS) -u NONE --cmd "set shortmess-=F" -S runtest.vim $*.vim
diff --git a/src/nvim/testdir/README.txt b/src/nvim/testdir/README.txt
deleted file mode 100644
index b8bc52f1e6..0000000000
--- a/src/nvim/testdir/README.txt
+++ /dev/null
@@ -1,121 +0,0 @@
-This directory contains tests for various Vim features.
-For testing an indent script see runtime/indent/testdir/README.txt.
-
-If it makes sense, add a new test method to an already existing file. You may
-want to separate it from other tests with comment lines.
-
-TO ADD A NEW STYLE TEST:
-
-1) Create a test_<subject>.vim file.
-2) Add test_<subject>.res to NEW_TESTS_RES in Make_all.mak in alphabetical
- order.
-3) Also add an entry "test_<subject>" to NEW_TESTS in Make_all.mak.
-4) Use make test_<subject> to run a single test.
-
-At 2), instead of running the test separately, it can be included in
-"test_alot". Do this for quick tests without side effects. The test runs a
-bit faster, because Vim doesn't have to be started, one Vim instance runs many
-tests.
-
-At 4), to run a test in GUI, add "GUI_FLAG=-g" to the make command.
-
-
-What you can use (see test_assert.vim for an example):
-
-- Call assert_equal(), assert_true(), assert_false(), etc.
-
-- Use assert_fails() to check for expected errors.
-
-- Use try/catch to avoid an exception aborts the test.
-
-- Use test_alloc_fail() to have memory allocation fail. This makes it possible
- to check memory allocation failures are handled gracefully. You need to
- change the source code to add an ID to the allocation. Add a new one to
- alloc_id_T, before aid_last.
-
-- Use test_override() to make Vim behave differently, e.g. if char_avail()
- must return FALSE for a while. E.g. to trigger the CursorMovedI autocommand
- event. See test_cursor_func.vim for an example.
-
-- If the bug that is being tested isn't fixed yet, you can throw an exception
- with "Skipped" so that it's clear this still needs work. E.g.: throw
- "Skipped: Bug with <c-e> and popupmenu not fixed yet"
-
-- The following environment variables are recognized and can be set to
- influence the behavior of the test suite (see runtest.vim for details)
-
- - $TEST_MAY_FAIL=Test_channel_one - ignore those failing tests
- - $TEST_FILTER=Test_channel - only run test that match this pattern
- - $TEST_SKIP_PAT=Test_channel - skip tests that match this pattern
- - $TEST_NO_RETRY=yes - do not try to re-run failing tests
- You can also set them in Vim:
- :let $TEST_MAY_FAIL = 'Test_channel_one'
- :let $TEST_FILTER = '_set_mode'
- :let $TEST_SKIP_PAT = 'Test_loop_forever'
- :let $TEST_NO_RETRY = 'yes'
- Use an empty string to revert, e.g.:
- :let $TEST_FILTER = ''
-
-- See the start of runtest.vim for more help.
-
-
-TO ADD A SCREEN DUMP TEST:
-
-Mostly the same as writing a new style test. Additionally, see help on
-"terminal-dumptest". Put the reference dump in "dumps/Test_func_name.dump".
-
-
-OLD STYLE TESTS:
-
-There are a few tests that are used when Vim was built without the +eval
-feature. These cannot use the "assert" functions, therefore they consist of a
-.in file that contains Normal mode commands between STARTTEST and ENDTEST.
-They modify the file and the result gets written in the test.out file. This
-is then compared with the .ok file. If they are equal the test passed. If
-they differ the test failed.
-
-
-RUNNING THE TESTS:
-
-To run a single test from the src directory:
-
- $ make test_<name>
-
-The below commands should be run from the src/testdir directory.
-
-To run a single test:
-
- $ make test_<name>.res
-
-The file 'messages' contains the messages generated by the test script. If a
-test fails, then the test.log file contains the error messages. If all the
-tests are successful, then this file will be an empty file.
-
-- To run a single test function from a test script:
-
- $ ../vim -u NONE -S runtest.vim <test_file>.vim <function_name>
-
-- To execute only specific test functions, add a second argument:
-
- $ ../vim -u NONE -S runtest.vim test_channel.vim open_delay
-
-
-- To run all the tests:
-
- $ make
-
-- To run the test on MS-Windows using the MSVC nmake:
-
- > nmake -f Make_dos.mak
-
-- To run the tests with GUI Vim:
-
- $ make GUI_FLAG=-g
-
- or
-
- $ make VIMPROG=../gvim
-
-- To cleanup the temporary files after running the tests:
-
- $ make clean
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
deleted file mode 100644
index 680a59006b..0000000000
--- a/src/nvim/testdir/check.vim
+++ /dev/null
@@ -1,217 +0,0 @@
-source shared.vim
-source term_util.vim
-
-command -nargs=1 MissingFeature throw 'Skipped: ' .. <args> .. ' feature missing'
-
-" Command to check for the presence of a feature.
-command -nargs=1 CheckFeature call CheckFeature(<f-args>)
-func CheckFeature(name)
- " if !has(a:name, 1)
- " throw 'Checking for non-existent feature ' .. a:name
- " endif
- if !has(a:name)
- MissingFeature a:name
- endif
-endfunc
-
-" Command to check for the absence of a feature.
-command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>)
-func CheckNotFeature(name)
- " if !has(a:name, 1)
- " throw 'Checking for non-existent feature ' .. a:name
- " endif
- if has(a:name)
- throw 'Skipped: ' .. a:name .. ' feature present'
- endif
-endfunc
-
-" Command to check for the presence of a working option.
-command -nargs=1 CheckOption call CheckOption(<f-args>)
-func CheckOption(name)
- if !exists('&' .. a:name)
- throw 'Checking for non-existent option ' .. a:name
- endif
- if !exists('+' .. a:name)
- throw 'Skipped: ' .. a:name .. ' option not supported'
- endif
-endfunc
-
-" Command to check for the presence of a function.
-command -nargs=1 CheckFunction call CheckFunction(<f-args>)
-func CheckFunction(name)
- if !exists('*' .. a:name)
- throw 'Skipped: ' .. a:name .. ' function missing'
- endif
-endfunc
-
-" Command to check for the presence of an Ex command
-command -nargs=1 CheckCommand call CheckCommand(<f-args>)
-func CheckCommand(name)
- if !exists(':' .. a:name)
- throw 'Skipped: ' .. a:name .. ' command not supported'
- endif
-endfunc
-
-" Command to check for the presence of a shell command
-command -nargs=1 CheckExecutable call CheckExecutable(<f-args>)
-func CheckExecutable(name)
- if !executable(a:name)
- throw 'Skipped: ' .. a:name .. ' program not executable'
- endif
-endfunc
-
-" Command to check for the presence of python. Argument should have been
-" obtained with PythonProg()
-func CheckPython(name)
- if a:name == ''
- throw 'Skipped: python command not available'
- endif
-endfunc
-
-" Command to check for running on MS-Windows
-command CheckMSWindows call CheckMSWindows()
-func CheckMSWindows()
- if !has('win32')
- throw 'Skipped: only works on MS-Windows'
- endif
-endfunc
-
-" Command to check for NOT running on MS-Windows
-command CheckNotMSWindows call CheckNotMSWindows()
-func CheckNotMSWindows()
- if has('win32')
- throw 'Skipped: does not work on MS-Windows'
- endif
-endfunc
-
-" Command to check for running on Unix
-command CheckUnix call CheckUnix()
-func CheckUnix()
- if !has('unix')
- throw 'Skipped: only works on Unix'
- endif
-endfunc
-
-" Command to check for running on Linux
-command CheckLinux call CheckLinux()
-func CheckLinux()
- if !has('linux')
- throw 'Skipped: only works on Linux'
- endif
-endfunc
-
-" 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
-
-" Command to check that we can Run Vim in a terminal window
-command CheckRunVimInTerminal call CheckRunVimInTerminal()
-func CheckRunVimInTerminal()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
-endfunc
-
-" Command to check that we can run the GUI
-command CheckCanRunGui call CheckCanRunGui()
-func CheckCanRunGui()
- if !has('gui') || ($DISPLAY == "" && !has('gui_running'))
- throw 'Skipped: cannot start the GUI'
- endif
-endfunc
-
-" Command to Check for an environment variable
-command -nargs=1 CheckEnv call CheckEnv(<f-args>)
-func CheckEnv(name)
- if empty(eval('$' .. a:name))
- throw 'Skipped: Environment variable ' .. a:name .. ' is not set'
- endif
-endfunc
-
-" Command to check that we are using the GUI
-command CheckGui call CheckGui()
-func CheckGui()
- if !has('gui_running')
- throw 'Skipped: only works in the GUI'
- endif
-endfunc
-
-" Command to check that not currently using the GUI
-command CheckNotGui call CheckNotGui()
-func CheckNotGui()
- if has('gui_running')
- throw 'Skipped: only works in the terminal'
- endif
-endfunc
-
-" Command to check that test is not running as root
-command CheckNotRoot call CheckNotRoot()
-func CheckNotRoot()
- if IsRoot()
- throw 'Skipped: cannot run test as root'
- endif
-endfunc
-
-" Command to check that the current language is English
-command CheckEnglish call CheckEnglish()
-func CheckEnglish()
- if v:lang != "C" && v:lang !~ '^[Ee]n'
- throw 'Skipped: only works in English language environment'
- endif
-endfunc
-
-" Command to check for not running under ASAN
-command CheckNotAsan call CheckNotAsan()
-func CheckNotAsan()
- if execute('version') =~# '-fsanitize=[a-z,]*\<address\>'
- throw 'Skipped: does not work with ASAN'
- endif
-endfunc
-
-" Command to check for not running under valgrind
-command CheckNotValgrind call CheckNotValgrind()
-func CheckNotValgrind()
- if RunningWithValgrind()
- throw 'Skipped: does not work well with valgrind'
- endif
-endfunc
-
-" Command to check for X11 based GUI
-command CheckX11BasedGui call CheckX11BasedGui()
-func CheckX11BasedGui()
- if !g:x11_based_gui
- throw 'Skipped: requires X11 based GUI'
- endif
-endfunc
-
-" Command to check for satisfying any of the conditions.
-" e.g. CheckAnyOf Feature:bsd Feature:sun Linux
-command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>)
-func CheckAnyOf(...)
- let excp = []
- for arg in a:000
- try
- exe 'Check' .. substitute(arg, ':', ' ', '')
- return
- catch /^Skipped:/
- let excp += [substitute(v:exception, '^Skipped:\s*', '', '')]
- endtry
- endfor
- throw 'Skipped: ' .. join(excp, '; ')
-endfunc
-
-" Command to check for satisfying all of the conditions.
-" e.g. CheckAllOf Unix Gui Option:ballooneval
-command -nargs=+ CheckAllOf call CheckAllOf(<f-args>)
-func CheckAllOf(...)
- for arg in a:000
- exe 'Check' .. substitute(arg, ':', ' ', '')
- endfor
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/dotest.in b/src/nvim/testdir/dotest.in
deleted file mode 100644
index 2ef4576f4e..0000000000
--- a/src/nvim/testdir/dotest.in
+++ /dev/null
@@ -1,3 +0,0 @@
-:set nomore
-:map dotest /^STARTTEST j:set ff=unix cpo-=A :.,/ENDTEST/-1w! Xdotest :set ff& cpo+=A nj0:so! Xdotest dotest
-dotest
diff --git a/src/nvim/testdir/load.vim b/src/nvim/testdir/load.vim
deleted file mode 100644
index 5697ee7304..0000000000
--- a/src/nvim/testdir/load.vim
+++ /dev/null
@@ -1,32 +0,0 @@
-" Also used by: test/functional/helpers.lua
-
-function! s:load_factor() abort
- let timeout = 200
- let times = []
-
- for _ in range(5)
- let g:val = 0
- let start = reltime()
- call timer_start(timeout, {-> nvim_set_var('val', 1)})
- while 1
- sleep 10m
- if g:val == 1
- let g:waited_in_ms = float2nr(reltimefloat(reltime(start)) * 1000)
- break
- endif
- endwhile
- call insert(times, g:waited_in_ms, 0)
- endfor
-
- let longest = max(times)
- let factor = (longest + 50.0) / timeout
-
- return factor
-endfunction
-
-" Compute load factor only once.
-let g:test_load_factor = s:load_factor()
-
-function! LoadAdjust(num) abort
- return float2nr(ceil(a:num * g:test_load_factor))
-endfunction
diff --git a/src/nvim/testdir/pyxfile/py2_magic.py b/src/nvim/testdir/pyxfile/py2_magic.py
deleted file mode 100644
index 819892fd16..0000000000
--- a/src/nvim/testdir/pyxfile/py2_magic.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# requires python 2.x
-
-import sys
-print(sys.version)
diff --git a/src/nvim/testdir/pyxfile/py2_shebang.py b/src/nvim/testdir/pyxfile/py2_shebang.py
deleted file mode 100644
index 13bfc491ec..0000000000
--- a/src/nvim/testdir/pyxfile/py2_shebang.py
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/python2
-
-import sys
-print(sys.version)
diff --git a/src/nvim/testdir/pyxfile/py3_magic.py b/src/nvim/testdir/pyxfile/py3_magic.py
deleted file mode 100644
index d4b7ee0071..0000000000
--- a/src/nvim/testdir/pyxfile/py3_magic.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# requires python 3.x
-
-import sys
-print(sys.version)
diff --git a/src/nvim/testdir/pyxfile/py3_shebang.py b/src/nvim/testdir/pyxfile/py3_shebang.py
deleted file mode 100644
index ec05808ca4..0000000000
--- a/src/nvim/testdir/pyxfile/py3_shebang.py
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/python3
-
-import sys
-print(sys.version)
diff --git a/src/nvim/testdir/pyxfile/pyx.py b/src/nvim/testdir/pyxfile/pyx.py
deleted file mode 100644
index 261a6512c0..0000000000
--- a/src/nvim/testdir/pyxfile/pyx.py
+++ /dev/null
@@ -1,2 +0,0 @@
-import sys
-print(sys.version)
diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh
deleted file mode 100755
index 322265737a..0000000000
--- a/src/nvim/testdir/runnvim.sh
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/env bash
-
-main() {(
- local separator="================================================================================"
- local oldesttest=
- if test "$1" = "--oldesttest" ; then
- shift
- oldesttest=1
- fi
- local root="$1" ; shift
- local nvim_prg="$1" ; shift
- local test_name="$1" ; shift
-
- local tlog="$test_name.tlog"
-
- export NVIM_TEST_ARGC=$#
- local arg
- local i=0
- # shellcheck disable=SC2034 # (unused "arg", used in "eval").
- for arg ; do
- eval "export NVIM_TEST_ARG$i=\"\$arg\""
- i=$(( i+1 ))
- done
-
- export CI_DIR="$root/ci"
- BUILD_DIR="$(dirname "$nvim_prg")/.."
- export BUILD_DIR
- export FAILED=0
-
- . "$CI_DIR/common/suite.sh"
- . "$CI_DIR/common/test.sh"
-
- export VIMRUNTIME="$root/runtime"
- if ! "$nvim_prg" \
- -u NONE -i NONE \
- --headless \
- --cmd 'set shortmess+=I noswapfile noundofile nomore' \
- -S runnvim.vim \
- "$tlog" > "out-$tlog" 2> "err-$tlog"
- then
- fail "$test_name" "Nvim exited with non-zero code"
- fi
- {
- echo "Stdout of :terminal runner"
- echo "$separator"
- cat "out-$tlog"
- echo "$separator"
- echo "Stderr of :terminal runner"
- echo "$separator"
- cat "err-$tlog"
- echo "$separator"
- } >> "$tlog"
- if test "$oldesttest" = 1 ; then
- if ! diff -q test.out "$test_name.ok" > /dev/null 2>&1 ; then
- if test -f test.out ; then
- fail "$test_name" "Oldest test .out file differs from .ok file"
- {
- echo "Diff between test.out and $test_name.ok"
- echo "$separator"
- diff -a test.out "$test_name.ok"
- echo "$separator"
- } >> "$tlog"
- else
- echo "No output in test.out" >> "$tlog"
- fi
- fi
- fi
- valgrind_check .
- if test -n "$LOG_DIR" ; then
- check_sanitizer "$LOG_DIR"
- fi
- check_core_dumps
- if test "$FAILED" = 1 ; then
- cat "$tlog"
- fi
- rm -f "$tlog"
- 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
-)}
-
-main "$@"
diff --git a/src/nvim/testdir/runnvim.vim b/src/nvim/testdir/runnvim.vim
deleted file mode 100644
index a46e2d3fc0..0000000000
--- a/src/nvim/testdir/runnvim.vim
+++ /dev/null
@@ -1,58 +0,0 @@
-let s:logger = {'d_events': []}
-function s:logger.on_stdout(id, data, event)
- call add(self.d_events, [a:event, a:data])
-endfunction
-let s:logger.on_stderr = s:logger.on_stdout
-function s:logger.on_exit(id, data, event)
- call add(self.d_events, [a:event, ['']])
-endfunction
-
-" Replace non-printable chars by special sequence, or "<%x>".
-let s:escaped_char = {"\n": '\n', "\r": '\r', "\t": '\t'}
-function! s:escape_non_printable(char) abort
- let r = get(s:escaped_char, a:char)
- return r is 0 ? printf('<%x>', char2nr(a:char)) : r
-endfunction
-
-function Main()
- let argc = +$NVIM_TEST_ARGC
- let args = []
- for i in range(argc)
- call add(args, eval("$NVIM_TEST_ARG" . i))
- endfor
- set lines=25
- set columns=80
- enew
- let job = termopen(args, s:logger)
- let results = jobwait([job], 5 * 60 * 1000)
- " TODO(ZyX-I): Get colors
- let screen = getline(1, '$')
- bwipeout! " kills the job always.
- let stringified_events = map(s:logger.d_events,
- \'v:val[0] . ": " . ' .
- \'join(map(v:val[1], '.
- \ '''substitute(v:val, '.
- \ '"\\v\\C(\\p@!.|\\<)", '.
- \ '"\\=s:escape_non_printable(submatch(0))", '.
- \ '"g")''), '.
- \ '''\n'')')
- call setline(1, [
- \ 'Job exited with code ' . results[0],
- \ printf('Screen (%u lines)', len(screen)),
- \ repeat('=', 80),
- \] + screen + [
- \ repeat('=', 80),
- \ printf('Events (%u lines):', len(stringified_events)),
- \ repeat('=', 80),
- \] + stringified_events + [
- \ repeat('=', 80),
- \])
- write
- if results[0] != 0
- cquit
- else
- qall
- endif
-endfunction
-
-call Main()
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
deleted file mode 100644
index 3c5699af73..0000000000
--- a/src/nvim/testdir/runtest.vim
+++ /dev/null
@@ -1,485 +0,0 @@
-" This script is sourced while editing the .vim file with the tests.
-" When the script is successful the .res file will be created.
-" Errors are appended to the test.log file.
-"
-" To execute only specific test functions, add a second argument. It will be
-" matched against the names of the Test_ function. E.g.:
-" ../vim -u NONE -S runtest.vim test_channel.vim open_delay
-" The output can be found in the "messages" file.
-"
-" If the environment variable $TEST_FILTER is set then only test functions
-" matching this pattern are executed. E.g. for sh/bash:
-" export TEST_FILTER=Test_channel
-" For csh:
-" setenv TEST_FILTER Test_channel
-"
-" While working on a test you can make $TEST_NO_RETRY non-empty to not retry:
-" export TEST_NO_RETRY=yes
-"
-" To ignore failure for tests that are known to fail in a certain environment,
-" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for
-" sh/bash:
-" export TEST_MAY_FAIL=Test_channel_one,Test_channel_other
-" The failure report will then not be included in the test.log file and
-" "make test" will not fail.
-"
-" The test script may contain anything, only functions that start with
-" "Test_" are special. These will be invoked and should contain assert
-" functions. See test_assert.vim for an example.
-"
-" It is possible to source other files that contain "Test_" functions. This
-" can speed up testing, since Vim does not need to restart. But be careful
-" that the tests do not interfere with each other.
-"
-" If an error cannot be detected properly with an assert function add the
-" error to the v:errors list:
-" call add(v:errors, 'test foo failed: Cannot find xyz')
-"
-" If preparation for each Test_ function is needed, define a SetUp function.
-" It will be called before each Test_ function.
-"
-" If cleanup after each Test_ function is needed, define a TearDown function.
-" It will be called after each Test_ function.
-"
-" When debugging a test it can be useful to add messages to v:errors:
-" call add(v:errors, "this happened")
-
-
-" Check that the screen size is at least 24 x 80 characters.
-if &lines < 24 || &columns < 80
- let error = 'Screen size too small! Tests require at least 24 lines with 80 characters, got ' .. &lines .. ' lines with ' .. &columns .. ' characters'
- echoerr error
- split test.log
- $put =error
- write
- split messages
- call append(line('$'), '')
- call append(line('$'), 'From ' . expand('%') . ':')
- call append(line('$'), error)
- write
- qa!
-endif
-
-if has('reltime')
- let s:start_time = reltime()
-endif
-
-" Always use forward slashes.
-set shellslash
-
-" Common with all tests on all systems.
-source setup.vim
-
-" For consistency run all tests with 'nocompatible' set.
-" This also enables use of line continuation.
-set nocp viminfo+=nviminfo
-
-" Use utf-8 by default, instead of whatever the system default happens to be.
-" Individual tests can overrule this at the top of the file.
-set encoding=utf-8
-
-" REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for
-" the test_name.vim file itself. Replace it here with a more restrictive one,
-" so we still catch mistakes.
-let s:test_script_fname = expand('%')
-au! SwapExists * call HandleSwapExists()
-func HandleSwapExists()
- if exists('g:ignoreSwapExists')
- return
- endif
- " Ignore finding a swap file for the test script (the user might be
- " editing it and do ":make test_name") and the output file.
- " Report finding another swap file and chose 'q' to avoid getting stuck.
- if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname
- let v:swapchoice = 'e'
- else
- call assert_report('Unexpected swap file: ' .. v:swapname)
- let v:swapchoice = 'q'
- endif
-endfunc
-
-" Avoid stopping at the "hit enter" prompt
-set nomore
-
-" Output all messages in English.
-lang mess C
-
-" Nvim: append runtime from build dir, which contains the generated doc/tags.
-let &runtimepath ..= ',' .. expand($BUILD_DIR) .. '/runtime/'
-
-let s:t_bold = &t_md
-let s:t_normal = &t_me
-if has('win32')
- " avoid prompt that is long or contains a line break
- let $PROMPT = '$P$G'
-endif
-
-if has('mac')
- " In MacOS, when starting a shell in a terminal, a bash deprecation warning
- " message is displayed. This breaks the terminal test. Disable the warning
- " message.
- let $BASH_SILENCE_DEPRECATION_WARNING = 1
-endif
-
-" Prepare for calling test_garbagecollect_now().
-let v:testing = 1
-
-" Support function: get the alloc ID by name.
-func GetAllocId(name)
- exe 'split ' . s:srcdir . '/alloc.h'
- let top = search('typedef enum')
- if top == 0
- call add(v:errors, 'typedef not found in alloc.h')
- endif
- let lnum = search('aid_' . a:name . ',')
- if lnum == 0
- call add(v:errors, 'Alloc ID ' . a:name . ' not defined')
- endif
- close
- return lnum - top - 1
-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
-
- " Avoid a three second wait when a message is about to be overwritten by the
- " mode message.
- set noshowmode
-
- " Some tests wipe out buffers. To be consistent, always wipe out all
- " buffers.
- %bwipe!
-
- " The test may change the current directory. Save and restore the
- " directory after executing the test.
- let save_cwd = getcwd()
-
- " Align Nvim defaults to Vim.
- source setup.vim
-
- if exists("*SetUp")
- try
- call SetUp()
- catch
- call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
- endtry
- endif
-
- au VimLeavePre * call EarlyExit(g:testfunc)
- if a:test =~ 'Test_nocatch_'
- " Function handles errors itself. This avoids skipping commands after the
- " error.
- let g:skipped_reason = ''
- exe 'call ' . a:test
- if g:skipped_reason != ''
- call add(s:messages, ' Skipped')
- call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason)
- endif
- else
- try
- exe 'call ' . a:test
- catch /^\cskipped/
- call add(s:messages, ' Skipped')
- call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', ''))
- catch
- call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
- endtry
- endif
- au! VimLeavePre
-
- " In case 'insertmode' was set and something went wrong, make sure it is
- " reset to avoid trouble with anything else.
- set noinsertmode
-
- if exists("*TearDown")
- try
- call TearDown()
- catch
- call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
- endtry
- endif
-
- " Clear any autocommands and put back the catch-all for SwapExists.
- au!
- au SwapExists * call HandleSwapExists()
-
- " Close any extra tab pages and windows and make the current one not modified.
- while tabpagenr('$') > 1
- let winid = win_getid()
- quit!
- if winid == win_getid()
- echoerr 'Could not quit window'
- break
- endif
- endwhile
-
- while 1
- let wincount = winnr('$')
- if wincount == 1
- break
- endif
- bwipe!
- if wincount == winnr('$')
- " Did not manage to close a window.
- only!
- break
- endif
- endwhile
-
- exe 'cd ' . save_cwd
-
- let message = 'Executed ' . a:test
- if has('reltime')
- let message ..= repeat(' ', 50 - len(message))
- let time = reltime(func_start)
- if has('float') && reltimefloat(time) > 0.1
- let message = s:t_bold .. message
- endif
- let message ..= ' in ' .. reltimestr(time) .. ' seconds'
- if has('float') && reltimefloat(time) > 0.1
- let message ..= s:t_normal
- endif
- endif
- call add(s:messages, message)
- let s:done += 1
-endfunc
-
-func AfterTheTest(func_name)
- if len(v:errors) > 0
- if match(s:may_fail_list, '^' .. a:func_name) >= 0
- let s:fail_expected += 1
- call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':')
- call extend(s:errors_expected, v:errors)
- else
- let s:fail += 1
- call add(s:errors, 'Found errors in ' . g:testfunc . ':')
- call extend(s:errors, v:errors)
- endif
- let v:errors = []
- endif
-endfunc
-
-func EarlyExit(test)
- " It's OK for the test we use to test the quit detection.
- if a:test != 'Test_zz_quit_detected()'
- call add(v:errors, v:errmsg)
- call add(v:errors, 'Test caused Vim to exit: ' . a:test)
- endif
-
- call FinishTesting()
-endfunc
-
-" This function can be called by a test if it wants to abort testing.
-func FinishTesting()
- call AfterTheTest('')
-
- " Don't write viminfo on exit.
- set viminfo=
-
- " Clean up files created by setup.vim
- call delete('XfakeHOME', 'rf')
-
- if s:fail == 0 && s:fail_expected == 0
- " Success, create the .res file so that make knows it's done.
- exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
- write
- endif
-
- if len(s:errors) > 0
- " Append errors to test.log
- split test.log
- call append(line('$'), '')
- call append(line('$'), 'From ' . g:testname . ':')
- call append(line('$'), s:errors)
- write
- endif
-
- if s:done == 0
- if s:filtered > 0
- let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
- else
- let message = 'NO tests executed'
- endif
- else
- if s:filtered > 0
- call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER")
- endif
- let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
- endif
- if s:done > 0 && has('reltime')
- let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
- let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
- let message ..= s:t_normal
- endif
- echo message
- call add(s:messages, message)
- if s:fail > 0
- let message = s:fail . ' FAILED:'
- echo message
- call add(s:messages, message)
- call extend(s:messages, s:errors)
- endif
- if s:fail_expected > 0
- let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
- echo message
- call add(s:messages, message)
- call extend(s:messages, s:errors_expected)
- endif
-
- " Add SKIPPED messages
- call extend(s:messages, s:skipped)
-
- " Append messages to the file "messages"
- split messages
- call append(line('$'), '')
- call append(line('$'), 'From ' . g:testname . ':')
- call append(line('$'), s:messages)
- write
-
- qall!
-endfunc
-
-" Source the test script. First grab the file name, in case the script
-" navigates away. g:testname can be used by the tests.
-let g:testname = expand('%')
-let s:done = 0
-let s:fail = 0
-let s:fail_expected = 0
-let s:errors = []
-let s:errors_expected = []
-let s:messages = []
-let s:skipped = []
-if expand('%') =~ 'test_vimscript.vim'
- " this test has intentional errors, don't use try/catch.
- source %
-else
- try
- source %
- catch /^\cskipped/
- call add(s:messages, ' Skipped')
- call add(s:skipped, 'SKIPPED ' . expand('%') . ': ' . substitute(v:exception, '^\S*\s\+', '', ''))
- catch
- let s:fail += 1
- call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
- endtry
-endif
-
-" Names of flaky tests.
-let s:flaky_tests = [
- \ 'Test_autocmd_SafeState()',
- \ 'Test_cursorhold_insert()',
- \ 'Test_exit_callback_interval()',
- \ 'Test_map_timeout_with_timer_interrupt()',
- \ 'Test_out_cb()',
- \ 'Test_popup_and_window_resize()',
- \ 'Test_quoteplus()',
- \ 'Test_quotestar()',
- \ 'Test_reltime()',
- \ 'Test_state()',
- \ 'Test_term_mouse_double_click_to_create_tab()',
- \ 'Test_term_mouse_multiple_clicks_to_visually_select()',
- \ 'Test_terminal_composing_unicode()',
- \ 'Test_terminal_redir_file()',
- \ 'Test_terminal_tmap()',
- \ 'Test_timer_oneshot()',
- \ 'Test_timer_paused()',
- \ 'Test_timer_repeat_many()',
- \ 'Test_timer_repeat_three()',
- \ 'Test_timer_stop_all_in_callback()',
- \ 'Test_timer_stop_in_callback()',
- \ 'Test_timer_with_partial_callback()',
- \ 'Test_termwinscroll()',
- \ ]
-
-" Locate Test_ functions and execute them.
-redir @q
-silent function /^Test_
-redir END
-let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
-
-" If there is an extra argument filter the function names against it.
-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.
-let s:filtered = 0
-if $TEST_FILTER != ''
- let s:filtered = len(s:tests)
- let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
- let s:filtered -= len(s:tests)
-endif
-
-let s:may_fail_list = []
-if $TEST_MAY_FAIL != ''
- " Split the list at commas and add () to make it match g:testfunc.
- let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'})
-endif
-
-" Execute the tests in alphabetical order.
-for g:testfunc in sort(s:tests)
- " Silence, please!
- set belloff=all
- let prev_error = ''
- let total_errors = []
- let g:run_nr = 1
-
- " A test can set g:test_is_flaky to retry running the test.
- let g:test_is_flaky = 0
-
- call RunTheTest(g:testfunc)
-
- " Repeat a flaky test. Give up when:
- " - $TEST_NO_RETRY is not empty
- " - it fails again with the same message
- " - it fails five times (with a different message)
- if len(v:errors) > 0
- \ && $TEST_NO_RETRY == ''
- \ && (index(s:flaky_tests, g:testfunc) >= 0
- \ || g:test_is_flaky)
- while 1
- call add(s:messages, 'Found errors in ' . g:testfunc . ':')
- call extend(s:messages, v:errors)
-
- call add(total_errors, 'Run ' . g:run_nr . ':')
- call extend(total_errors, v:errors)
-
- if g:run_nr >= 5 || prev_error == v:errors[0]
- call add(total_errors, 'Flaky test failed too often, giving up')
- let v:errors = total_errors
- break
- endif
-
- call add(s:messages, 'Flaky test failed, running it again')
-
- " Flakiness is often caused by the system being very busy. Sleep a
- " couple of seconds to have a higher chance of succeeding the second
- " time.
- sleep 2
-
- let prev_error = v:errors[0]
- let v:errors = []
- let g:run_nr += 1
-
- call RunTheTest(g:testfunc)
-
- if len(v:errors) == 0
- " Test passed on rerun.
- break
- endif
- endwhile
- endif
-
- call AfterTheTest(g:testfunc)
-endfor
-
-call FinishTesting()
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/samples/memfile_test.c b/src/nvim/testdir/samples/memfile_test.c
deleted file mode 100644
index 73f67fb250..0000000000
--- a/src/nvim/testdir/samples/memfile_test.c
+++ /dev/null
@@ -1,148 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// uncrustify:off
-
-/* vi:set ts=8 sts=4 sw=4 noet:
- *
- * VIM - Vi IMproved by Bram Moolenaar
- *
- * Do ":help uganda" in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
- * memfile_test.c: Unittests for memfile.c
- * Mostly by Ivan Krasilnikov.
- */
-
-#undef NDEBUG
-#include <assert.h>
-
-/* Must include main.c because it contains much more than just main() */
-#define NO_VIM_MAIN
-#include "main.c"
-
-/* This file has to be included because the tested functions are static */
-#include "memfile.c"
-
-#define index_to_key(i) ((i) ^ 15167)
-#define TEST_COUNT 50000
-
-/*
- * Test mf_hash_*() functions.
- */
- static void
-test_mf_hash(void)
-{
- mf_hashtab_T ht;
- mf_hashitem_T *item;
- blocknr_T key;
- size_t i;
- size_t num_buckets;
-
- mf_hash_init(&ht);
-
- /* insert some items and check invariants */
- for (i = 0; i < TEST_COUNT; i++)
- {
- assert(ht.mht_count == i);
-
- /* check that number of buckets is a power of 2 */
- num_buckets = ht.mht_mask + 1;
- assert(num_buckets > 0 && (num_buckets & (num_buckets - 1)) == 0);
-
- /* check load factor */
- assert(ht.mht_count <= (num_buckets << MHT_LOG_LOAD_FACTOR));
-
- if (i < (MHT_INIT_SIZE << MHT_LOG_LOAD_FACTOR))
- {
- /* first expansion shouldn't have occurred yet */
- assert(num_buckets == MHT_INIT_SIZE);
- assert(ht.mht_buckets == ht.mht_small_buckets);
- }
- else
- {
- assert(num_buckets > MHT_INIT_SIZE);
- assert(ht.mht_buckets != ht.mht_small_buckets);
- }
-
- key = index_to_key(i);
- assert(mf_hash_find(&ht, key) == NULL);
-
- /* allocate and add new item */
- item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);
- assert(item != NULL);
- item->mhi_key = key;
- mf_hash_add_item(&ht, item);
-
- assert(mf_hash_find(&ht, key) == item);
-
- if (ht.mht_mask + 1 != num_buckets)
- {
- /* hash table was expanded */
- assert(ht.mht_mask + 1 == num_buckets * MHT_GROWTH_FACTOR);
- assert(i + 1 == (num_buckets << MHT_LOG_LOAD_FACTOR));
- }
- }
-
- /* check presence of inserted items */
- for (i = 0; i < TEST_COUNT; i++)
- {
- key = index_to_key(i);
- item = mf_hash_find(&ht, key);
- assert(item != NULL);
- assert(item->mhi_key == key);
- }
-
- /* delete some items */
- for (i = 0; i < TEST_COUNT; i++)
- {
- if (i % 100 < 70)
- {
- key = index_to_key(i);
- item = mf_hash_find(&ht, key);
- assert(item != NULL);
- assert(item->mhi_key == key);
-
- mf_hash_rem_item(&ht, item);
- assert(mf_hash_find(&ht, key) == NULL);
-
- mf_hash_add_item(&ht, item);
- assert(mf_hash_find(&ht, key) == item);
-
- mf_hash_rem_item(&ht, item);
- assert(mf_hash_find(&ht, key) == NULL);
-
- vim_free(item);
- }
- }
-
- /* check again */
- for (i = 0; i < TEST_COUNT; i++)
- {
- key = index_to_key(i);
- item = mf_hash_find(&ht, key);
-
- if (i % 100 < 70)
- {
- assert(item == NULL);
- }
- else
- {
- assert(item != NULL);
- assert(item->mhi_key == key);
- }
- }
-
- /* free hash table and all remaining items */
- mf_hash_free_all(&ht);
-}
-
- int
-main(void)
-{
- test_mf_hash();
- return 0;
-}
diff --git a/src/nvim/testdir/samples/quickfix.txt b/src/nvim/testdir/samples/quickfix.txt
deleted file mode 100644
index 2de3835473..0000000000
--- a/src/nvim/testdir/samples/quickfix.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-samples/quickfix.txt:1:1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-samples/quickfix.txt:2:1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-samples/quickfix.txt:3:1:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-samples/quickfix.txt:4:1:dddddddddd
diff --git a/src/nvim/testdir/samples/re.freeze.txt b/src/nvim/testdir/samples/re.freeze.txt
deleted file mode 100644
index d768c23c5e..0000000000
--- a/src/nvim/testdir/samples/re.freeze.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-:set re=0 or 2
-Search for the pattern: /\s\+\%#\@<!$/
-vim should not freeze.
-
-<td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td>
-
diff --git a/src/nvim/testdir/sautest/autoload/foo.vim b/src/nvim/testdir/sautest/autoload/foo.vim
deleted file mode 100644
index 21d33a0f4d..0000000000
--- a/src/nvim/testdir/sautest/autoload/foo.vim
+++ /dev/null
@@ -1,15 +0,0 @@
-let g:loaded_foo_vim += 1
-
-let foo#bar = {}
-
-func foo#bar.echo()
- let g:called_foo_bar_echo += 1
-endfunc
-
-func foo#addFoo(head)
- return a:head .. 'foo'
-endfunc
-
-func foo#()
- return 'empty'
-endfunc
diff --git a/src/nvim/testdir/sautest/autoload/footest.vim b/src/nvim/testdir/sautest/autoload/footest.vim
deleted file mode 100644
index 1e78963a10..0000000000
--- a/src/nvim/testdir/sautest/autoload/footest.vim
+++ /dev/null
@@ -1,5 +0,0 @@
-" Autoload script used by test_listdict.vim, test_exists.vim and test_let.vim
-let footest#x = 1
-func footest#F()
- return 0
-endfunc
diff --git a/src/nvim/testdir/sautest/autoload/globone.vim b/src/nvim/testdir/sautest/autoload/globone.vim
deleted file mode 100644
index 98c9a10582..0000000000
--- a/src/nvim/testdir/sautest/autoload/globone.vim
+++ /dev/null
@@ -1 +0,0 @@
-" used by Test_globpath()
diff --git a/src/nvim/testdir/sautest/autoload/globtwo.vim b/src/nvim/testdir/sautest/autoload/globtwo.vim
deleted file mode 100644
index 98c9a10582..0000000000
--- a/src/nvim/testdir/sautest/autoload/globtwo.vim
+++ /dev/null
@@ -1 +0,0 @@
-" used by Test_globpath()
diff --git a/src/nvim/testdir/sautest/autoload/sourced.vim b/src/nvim/testdir/sautest/autoload/sourced.vim
deleted file mode 100644
index f69f00cb53..0000000000
--- a/src/nvim/testdir/sautest/autoload/sourced.vim
+++ /dev/null
@@ -1,3 +0,0 @@
-let g:loaded_sourced_vim += 1
-func! sourced#something()
-endfunc
diff --git a/src/nvim/testdir/screendump.vim b/src/nvim/testdir/screendump.vim
deleted file mode 100644
index 8afff1da91..0000000000
--- a/src/nvim/testdir/screendump.vim
+++ /dev/null
@@ -1,2 +0,0 @@
-source shared.vim
-source term_util.vim
diff --git a/src/nvim/testdir/script_util.vim b/src/nvim/testdir/script_util.vim
deleted file mode 100644
index 28d6a621d6..0000000000
--- a/src/nvim/testdir/script_util.vim
+++ /dev/null
@@ -1,69 +0,0 @@
-" Functions shared by the tests for Vim Script
-
-" Commands to track the execution path of a script
-com! XpathINIT let g:Xpath = ''
-com! -nargs=1 -bar Xpath let g:Xpath ..= <args>
-com! XloopINIT let g:Xloop = 1
-com! -nargs=1 -bar Xloop let g:Xpath ..= <args> .. g:Xloop
-com! XloopNEXT let g:Xloop += 1
-
-" MakeScript() - Make a script file from a function. {{{2
-"
-" Create a script that consists of the body of the function a:funcname.
-" Replace any ":return" by a ":finish", any argument variable by a global
-" variable, and every ":call" by a ":source" for the next following argument
-" in the variable argument list. This function is useful if similar tests are
-" to be made for a ":return" from a function call or a ":finish" in a script
-" file.
-func MakeScript(funcname, ...)
- let script = tempname()
- execute "redir! >" . script
- execute "function" a:funcname
- redir END
- execute "edit" script
- " Delete the "function" and the "endfunction" lines. Do not include the
- " word "function" in the pattern since it might be translated if LANG is
- " set. When MakeScript() is being debugged, this deletes also the debugging
- " output of its line 3 and 4.
- exec '1,/.*' . a:funcname . '(.*)/d'
- /^\d*\s*endfunction\>/,$d
- %s/^\d*//e
- %s/return/finish/e
- %s/\<a:\(\h\w*\)/g:\1/ge
- normal gg0
- let cnt = 0
- while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0
- let cnt = cnt + 1
- s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/
- endwhile
- g/^\s*$/d
- write
- bwipeout
- return script
-endfunc
-
-" ExecAsScript - Source a temporary script made from a function. {{{2
-"
-" Make a temporary script file from the function a:funcname, ":source" it, and
-" delete it afterwards. However, if an exception is thrown the file may remain,
-" the caller should call DeleteTheScript() afterwards.
-let s:script_name = ''
-func ExecAsScript(funcname)
- " Make a script from the function passed as argument.
- let s:script_name = MakeScript(a:funcname)
-
- " Source and delete the script.
- exec "source" s:script_name
- call delete(s:script_name)
- let s:script_name = ''
-endfunc
-
-func DeleteTheScript()
- if s:script_name
- call delete(s:script_name)
- let s:script_name = ''
- endif
-endfunc
-
-com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>)
-
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
deleted file mode 100644
index f895287469..0000000000
--- a/src/nvim/testdir/setup.vim
+++ /dev/null
@@ -1,83 +0,0 @@
-if exists('s:did_load')
- " Align Nvim defaults to Vim.
- set backspace=
- set complete=.,w,b,u,t,i
- set directory&
- set directory^=.
- set display=
- set fillchars=vert:\|,foldsep:\|,fold:-
- set formatoptions=tcq
- set fsync
- set laststatus=1
- set listchars=eol:$
- set joinspaces
- set nohidden nosmarttab noautoindent noautoread noruler noshowcmd
- set nohlsearch noincsearch
- set nrformats=bin,octal,hex
- set shortmess=filnxtToOS
- set sidescroll=0
- set tags=./tags,tags
- set undodir&
- set undodir^=.
- set wildoptions=
- set startofline
- set sessionoptions&
- set sessionoptions+=options
- set viewoptions&
- set viewoptions+=options
- set switchbuf=
- " Make "Q" switch to Ex mode.
- " This does not work for all tests.
- nnoremap Q gQ
-endif
-
-" Common preparations for running tests.
-
-" Only load this once.
-if exists('s:did_load')
- finish
-endif
-let s:did_load = 1
-
-" Clear Nvim default mappings and menus.
-mapclear
-mapclear!
-aunmenu *
-tlunmenu *
-
-" roughly equivalent to test_setmouse() in Vim
-func Ntest_setmouse(row, col)
- call nvim_input_mouse('move', '', '', 0, a:row - 1, a:col - 1)
-endfunc
-
-" Prevent Nvim log from writing to stderr.
-let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
-
-
-" Make sure 'runtimepath' and 'packpath' does not include $HOME.
-set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after
-let &packpath = &rtp
-
-" Avoid storing shell history.
-let $HISTFILE = ""
-
-" Use default shell on Windows to avoid segfault, caused by TUI
-if has('win32')
- let $SHELL = ''
- let $TERM = ''
- let &shell = empty($COMSPEC) ? exepath('cmd.exe') : $COMSPEC
- set shellcmdflag=/s/c shellxquote=\" shellredir=>%s\ 2>&1
- let &shellpipe = &shellredir
-endif
-
-" Detect user modules for language providers
-let $PYTHONUSERBASE = $HOME . '/.local'
-if executable('gem')
- let $GEM_PATH = system('gem env gempath')
-endif
-
-" Make sure $HOME does not get read or written.
-let $HOME = expand(getcwd() . '/XfakeHOME')
-if !isdirectory($HOME)
- call mkdir($HOME)
-endif
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
deleted file mode 100644
index 33f6d9a2d0..0000000000
--- a/src/nvim/testdir/shared.vim
+++ /dev/null
@@ -1,389 +0,0 @@
-" Functions shared by several tests.
-
-" Only load this script once.
-if exists('*PythonProg')
- finish
-endif
-
-source view_util.vim
-
-" {Nvim}
-" Filepath captured from output may be truncated, like this:
-" /home/va...estdir/X-test-tmpdir/nvimxbXN4i/10
-" Get last 2 segments, then combine with $TMPDIR.
-func! Fix_truncated_tmpfile(fname)
- " sanity check
- if $TMPDIR ==# ''
- throw '$TMPDIR is empty'
- endif
- let tmpdir_tail = fnamemodify(substitute($TMPDIR, '[\/]\+$', '', 'g'), ':t')
- if tmpdir_tail ==# ''
- throw 'empty tmpdir_tail'
- endif
- if a:fname !~# tmpdir_tail
- throw printf('$TMPDIR (%s) not in fname: %s', tmpdir_tail, a:fname)
- endif
- let last2segments = matchstr(a:fname, '[\/][^\/]\+[\/][^\/]\+$')
- return $TMPDIR.last2segments
-endfunc
-
-" Get the name of the Python executable.
-" Also keeps it in s:python.
-func PythonProg()
- " This test requires the Python command to run the test server.
- " This most likely only works on Unix and Windows.
- if has('unix')
- " We also need the job feature or the pkill command to make sure the server
- " can be stopped.
- if !(executable('python') && (has('job') || executable('pkill')))
- return ''
- endif
- let s:python = 'python'
- elseif has('win32')
- " Use Python Launcher for Windows (py.exe) if available.
- if executable('py.exe')
- let s:python = 'py.exe'
- elseif executable('python.exe')
- let s:python = 'python.exe'
- else
- return ''
- endif
- else
- return ''
- endif
- return s:python
-endfunc
-
-" Run "cmd". Returns the job if using a job.
-func RunCommand(cmd)
- " Running an external command can occasionally be slow or fail.
- let g:test_is_flaky = 1
-
- let job = 0
- if has('job')
- let job = job_start(a:cmd, {"stoponexit": "hup"})
- call job_setoptions(job, {"stoponexit": "kill"})
- elseif has('win32')
- exe 'silent !start cmd /c start "test_channel" ' . a:cmd
- else
- exe 'silent !' . a:cmd . '&'
- endif
- return job
-endfunc
-
-" Read the port number from the Xportnr file.
-func GetPort()
- let l = []
- " with 200 it sometimes failed
- for i in range(400)
- try
- let l = readfile("Xportnr")
- catch
- endtry
- if len(l) >= 1
- break
- endif
- sleep 10m
- endfor
- call delete("Xportnr")
-
- if len(l) == 0
- " Can't make the connection, give up.
- return 0
- endif
- return l[0]
-endfunc
-
-" Run a Python server for "cmd" and call "testfunc".
-" Always kills the server before returning.
-func RunServer(cmd, testfunc, args)
- " The Python program writes the port number in Xportnr.
- call delete("Xportnr")
-
- if len(a:args) == 1
- let arg = ' ' . a:args[0]
- else
- let arg = ''
- endif
- let pycmd = s:python . " " . a:cmd . arg
-
- try
- let g:currentJob = RunCommand(pycmd)
-
- " Wait for up to 2 seconds for the port number to be there.
- let port = GetPort()
- if port == 0
- call assert_false(1, "Can't start " . a:cmd)
- return
- endif
-
- call call(function(a:testfunc), [port])
- catch
- call assert_false(1, 'Caught exception: "' . v:exception . '" in ' . v:throwpoint)
- finally
- call s:kill_server(a:cmd)
- endtry
-endfunc
-
-func s:kill_server(cmd)
- if has('job')
- if exists('g:currentJob')
- call job_stop(g:currentJob)
- unlet g:currentJob
- endif
- elseif has('win32')
- let cmd = substitute(a:cmd, ".py", '', '')
- call system('taskkill /IM ' . s:python . ' /T /F /FI "WINDOWTITLE eq ' . cmd . '"')
- else
- call system("pkill -f " . a:cmd)
- endif
-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.
-"
-" 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 exists('*reltimefloat')
- let start = reltime()
- 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 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
- if exists('*reltimefloat')
- 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.
-" Without +timers it uses simply :sleep.
-func Standby(msec)
- if has('timers') && exists('*reltimefloat')
- let start = reltime()
- let g:_standby_timer = timer_start(a:msec, function('s:feedkeys'))
- call getchar()
- return float2nr(reltimefloat(reltime(start)) * 1000)
- else
- execute 'sleep ' a:msec . 'm'
- return a:msec
- endif
-endfunc
-
-func Resume()
- if exists('g:_standby_timer')
- call timer_stop(g:_standby_timer)
- call s:feedkeys(0)
- unlet g:_standby_timer
- endif
-endfunc
-
-func s:feedkeys(timer)
- call feedkeys('x', 'nt')
-endfunc
-
-" Get $VIMPROG to run the Vim executable.
-" The Makefile writes it as the first line in the "vimcmd" file.
-" Nvim: uses $NVIM_TEST_ARG0.
-func GetVimProg()
- if empty($NVIM_TEST_ARG0)
- " Assume the script was sourced instead of running "make".
- return v:progpath
- endif
- if has('win32')
- return substitute($NVIM_TEST_ARG0, '/', '\\', 'g')
- else
- return $NVIM_TEST_ARG0
- 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(...)
- if a:0 == 0
- let name = 'NONE'
- else
- let name = a:1
- endif
- let cmd = GetVimProg()
- let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '')
- if cmd !~ '-u '. name
- let cmd = cmd . ' -u ' . name
- endif
- let cmd .= ' --headless -i NONE'
- let cmd = substitute(cmd, 'VIMRUNTIME=\S\+', '', '')
-
- " 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
-
-" Return one when it looks like the tests are run with valgrind, which means
-" that everything is much slower.
-func RunningWithValgrind()
- return GetVimCommand() =~ '\<valgrind\>'
-endfunc
-
-" 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', '', '')
-
- " Force using utf-8, Vim may pick up something else from the environment.
- " let cmd ..= ' --cmd "set enc=utf8" '
-
- " Optionally run Vim under valgrind
- " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
-
- return cmd
-endfunc
-
-" Get the command to run Vim, with --clean, and force to run in terminal so it
-" won't start a new GUI.
-func GetVimCommandCleanTerm()
- " Add -v to have gvim run in the terminal (if possible)
- return GetVimCommandClean() .. ' -v '
-endfunc
-
-" Run Vim, using the "vimcmd" file and "-u NORC".
-" "before" is a list of Vim commands to be executed before loading plugins.
-" "after" is a list of Vim commands to be executed after loading plugins.
-" Plugins are not loaded, unless 'loadplugins' is set in "before".
-" Return 1 if Vim could be executed.
-func RunVim(before, after, arguments)
- return RunVimPiped(a:before, a:after, a:arguments, '')
-endfunc
-
-func RunVimPiped(before, after, arguments, pipecmd)
- let cmd = GetVimCommand()
- let args = ''
- if len(a:before) > 0
- call writefile(a:before, 'Xbefore.vim')
- let args .= ' --cmd "so Xbefore.vim"'
- endif
- if len(a:after) > 0
- call writefile(a:after, 'Xafter.vim')
- 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
-
- let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
- " Nvim does not support -Z flag, remove it.
- exe "silent !" . a:pipecmd . cmd . args . ' ' . substitute(a:arguments, '-Z', '', 'g')
-
- if len(a:before) > 0
- call delete('Xbefore.vim')
- endif
- if len(a:after) > 0
- call delete('Xafter.vim')
- endif
- return 1
-endfunc
-
-func IsRoot()
- if !has('unix')
- return v:false
- elseif $USER == 'root' || system('id -un') =~ '\<root\>'
- return v:true
- endif
- return v:false
-endfunc
-
-" Get all messages but drop the maintainer entry.
-func GetMessages()
- redir => result
- redraw | messages
- redir END
- let msg_list = split(result, "\n")
- " if msg_list->len() > 0 && msg_list[0] =~ 'Messages maintainer:'
- " return msg_list[1:]
- " endif
- return msg_list
-endfunc
-
-" Run the list of commands in 'cmds' and look for 'errstr' in exception.
-" Note that assert_fails() cannot be used in some places and this function
-" can be used.
-func AssertException(cmds, errstr)
- let save_exception = ''
- try
- for cmd in a:cmds
- exe cmd
- endfor
- catch
- let save_exception = v:exception
- endtry
- call assert_match(a:errstr, save_exception)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim
deleted file mode 100644
index da5856a2e7..0000000000
--- a/src/nvim/testdir/summarize.vim
+++ /dev/null
@@ -1,62 +0,0 @@
-set cpo&vim
-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)
- elseif a:type ==# 'failed'
- let g:failed += a:match+0
- elseif a:type ==# 'skipped'
- let g:skipped += 1
- call extend(g:skipped_output, ["\t" .. a:match])
- endif
- endfunc
-
- let g:executed = 0
- let g:skipped = 0
- let g:failed = 0
- let g:skipped_output = []
- 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/^SKIPPED \zs.*/\=Count(submatch(0), 'skipped')/egn
- silent %s/^\(\d\+\)\s\+FAILED:/\=Count(submatch(1), 'failed')/egn
-
- call extend(output, ["Skipped:"])
- call extend(output, skipped_output)
-
- call extend(output, [
- \ "",
- \ "-------------------------------",
- \ printf("Executed: %5d Tests", g:executed),
- \ printf(" Skipped: %5d Tests", g:skipped),
- \ printf(" %s: %5d Tests", g:failed == 0 ? 'Failed' : 'FAILED', g:failed),
- \ "",
- \ ])
- if filereadable('test.log')
- " outputs and indents the failed test result
- call extend(output, ["", "Failures: "])
- let failed_output = filter(readfile('test.log'), { v,k -> !empty(k)})
- call extend(output, map(failed_output, { v,k -> "\t".k}))
- " Add a final newline
- call extend(output, [""])
- endif
-
- catch " Catch-all
- finally
- call writefile(output, 'test_result.log') " overwrites an existing file
- endtry
-endif
-
-q!
diff --git a/src/nvim/testdir/term_util.vim b/src/nvim/testdir/term_util.vim
deleted file mode 100644
index 5ff09e25b4..0000000000
--- a/src/nvim/testdir/term_util.vim
+++ /dev/null
@@ -1,13 +0,0 @@
-" 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
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test1.in b/src/nvim/testdir/test1.in
deleted file mode 100644
index 272500cd25..0000000000
--- a/src/nvim/testdir/test1.in
+++ /dev/null
@@ -1,13 +0,0 @@
-First a simple test to check if the test script works.
-
-STARTTEST
-:" If columns or lines are too small, create wrongtermsize.
-:" (Some tests will fail. When columns and/or lines are small)
-:if &lines < 24 || &columns < 80 | sp another | w! wrongtermsize | qa! | endif
-:"
-:" Write a single line to test.out to check if testing works at all.
-:%d
-athis is a test:w! test.out
-:qa!
-ENDTEST
-
diff --git a/src/nvim/testdir/test1.ok b/src/nvim/testdir/test1.ok
deleted file mode 100644
index 90bfcb5106..0000000000
--- a/src/nvim/testdir/test1.ok
+++ /dev/null
@@ -1 +0,0 @@
-this is a test
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
deleted file mode 100644
index a3d240f27e..0000000000
--- a/src/nvim/testdir/test_alot.vim
+++ /dev/null
@@ -1,31 +0,0 @@
-" A series of tests that can run in one Vim invocation.
-" This makes testing go faster, since Vim doesn't need to restart.
-
-source test_backup.vim
-source test_behave.vim
-source test_compiler.vim
-source test_ex_equal.vim
-source test_ex_undo.vim
-source test_ex_z.vim
-source test_ex_mode.vim
-source test_expand.vim
-source test_expand_func.vim
-source test_file_perm.vim
-source test_fnamemodify.vim
-source test_ga.vim
-source test_glob2regpat.vim
-source test_global.vim
-source test_move.vim
-source test_put.vim
-source test_reltime.vim
-source test_scroll_opt.vim
-source test_searchpos.vim
-source test_set.vim
-source test_shift.vim
-source test_sha256.vim
-source test_tabline.vim
-source test_tagcase.vim
-source test_tagfunc.vim
-source test_unlet.vim
-source test_version.vim
-source test_wnext.vim
diff --git a/src/nvim/testdir/test_alot_latin.vim b/src/nvim/testdir/test_alot_latin.vim
deleted file mode 100644
index 23a404cac1..0000000000
--- a/src/nvim/testdir/test_alot_latin.vim
+++ /dev/null
@@ -1,7 +0,0 @@
-" A series of tests that can run in one Vim invocation.
-" This makes testing go faster, since Vim doesn't need to restart.
-
-" These tests use latin1 'encoding'. Setting 'encoding' is in the individual
-" files, so that they can be run by themselves.
-
-source test_regexp_latin.vim
diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim
deleted file mode 100644
index 77f5ede4c8..0000000000
--- a/src/nvim/testdir/test_alot_utf8.vim
+++ /dev/null
@@ -1,14 +0,0 @@
-" A series of tests that can run in one Vim invocation.
-" This makes testing go faster, since Vim doesn't need to restart.
-
-" These tests use utf8 'encoding'. Setting 'encoding' is already done in
-" runtest.vim.
-
-source test_charsearch_utf8.vim
-source test_expr_utf8.vim
-source test_mksession_utf8.vim
-source test_regexp_utf8.vim
-source test_source_utf8.vim
-source test_startup_utf8.vim
-source test_utf8.vim
-source test_utf8_comparisons.vim
diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim
deleted file mode 100644
index 272937387d..0000000000
--- a/src/nvim/testdir/test_arabic.vim
+++ /dev/null
@@ -1,587 +0,0 @@
-" Simplistic testing of Arabic mode.
-" NOTE: This just checks if the code works. If you know Arabic please add
-" functional tests that check the shaping works with real text.
-
-source check.vim
-CheckFeature arabic
-
-source view_util.vim
-
-" Return list of Unicode characters at line lnum.
-" Combining characters are treated as a single item.
-func s:get_chars(lnum)
- call cursor(a:lnum, 1)
- let chars = []
- let numchars = strchars(getline('.'), 1)
- for i in range(1, numchars)
- exe 'norm ' i . '|'
- let c = execute('ascii')
- let c = substitute(c, '\n\?<.\{-}Hex\s*', 'U+', 'g')
- let c = substitute(c, ',\s*Oct\(al\)\=\s\d*\(, Digr ..\)\=', '', 'g')
- call add(chars, c)
- endfor
- return chars
-endfunc
-
-func Test_arabic_toggle()
- set arabic
- call assert_equal(1, &rightleft)
- call assert_equal(1, &arabicshape)
- call assert_equal('arabic', &keymap)
- call assert_equal(1, &delcombine)
-
- set iminsert=1 imsearch=1
- set arabic&
- call assert_equal(0, &rightleft)
- call assert_equal(1, &arabicshape)
- call assert_equal('arabic', &keymap)
- call assert_equal(1, &delcombine)
- call assert_equal(0, &iminsert)
- call assert_equal(-1, &imsearch)
-
- set arabicshape& keymap= delcombine&
-endfunc
-
-func Test_arabic_input()
- new
- set arabic
- " Typing sghl in Arabic insert mode should show the
- " Arabic word 'Salaam' i.e. 'peace', spelled:
- " SEEN, LAM, ALEF, MEEM.
- " See: https://www.mediawiki.org/wiki/VisualEditor/Typing/Right-to-left
- call feedkeys('isghl!', 'tx')
- call assert_match("^ *!\uFEE1\uFEFC\uFEB3$", ScreenLines(1, &columns)[0])
- call assert_equal([
- \ 'U+0633',
- \ 'U+0644 U+0627',
- \ 'U+0645',
- \ 'U+21'], s:get_chars(1))
-
- " Without shaping, it should give individual Arabic letters.
- set noarabicshape
- call assert_match("^ *!\u0645\u0627\u0644\u0633$", ScreenLines(1, &columns)[0])
- call assert_equal([
- \ 'U+0633',
- \ 'U+0644',
- \ 'U+0627',
- \ 'U+0645',
- \ 'U+21'], s:get_chars(1))
-
- set arabic& arabicshape&
- bwipe!
-endfunc
-
-func Test_arabic_toggle_keymap()
- new
- set arabic
- call feedkeys("i12\<C-^>12\<C-^>12", 'tx')
- call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0])
- call assert_equal('١٢12١٢', getline('.'))
- set arabic&
- bwipe!
-endfunc
-
-func Test_delcombine()
- new
- set arabic
- call feedkeys("isghl\<BS>\<BS>", 'tx')
- call assert_match("^ *\uFEDE\uFEB3$", ScreenLines(1, &columns)[0])
- call assert_equal(['U+0633', 'U+0644'], s:get_chars(1))
-
- " Now the same with 'nodelcombine'
- set nodelcombine
- %d
- call feedkeys("isghl\<BS>\<BS>", 'tx')
- call assert_match("^ *\uFEB1$", ScreenLines(1, &columns)[0])
- call assert_equal(['U+0633'], s:get_chars(1))
- set arabic&
- bwipe!
-endfunc
-
-" Values from src/arabic.h (not all used yet)
-let s:a_COMMA = "\u060C"
-let s:a_SEMICOLON = "\u061B"
-let s:a_QUESTION = "\u061F"
-let s:a_HAMZA = "\u0621"
-let s:a_ALEF_MADDA = "\u0622"
-let s:a_ALEF_HAMZA_ABOVE = "\u0623"
-let s:a_WAW_HAMZA = "\u0624"
-let s:a_ALEF_HAMZA_BELOW = "\u0625"
-let s:a_YEH_HAMZA = "\u0626"
-let s:a_ALEF = "\u0627"
-let s:a_BEH = "\u0628"
-let s:a_TEH_MARBUTA = "\u0629"
-let s:a_TEH = "\u062a"
-let s:a_THEH = "\u062b"
-let s:a_JEEM = "\u062c"
-let s:a_HAH = "\u062d"
-let s:a_KHAH = "\u062e"
-let s:a_DAL = "\u062f"
-let s:a_THAL = "\u0630"
-let s:a_REH = "\u0631"
-let s:a_ZAIN = "\u0632"
-let s:a_SEEN = "\u0633"
-let s:a_SHEEN = "\u0634"
-let s:a_SAD = "\u0635"
-let s:a_DAD = "\u0636"
-let s:a_TAH = "\u0637"
-let s:a_ZAH = "\u0638"
-let s:a_AIN = "\u0639"
-let s:a_GHAIN = "\u063a"
-let s:a_TATWEEL = "\u0640"
-let s:a_FEH = "\u0641"
-let s:a_QAF = "\u0642"
-let s:a_KAF = "\u0643"
-let s:a_LAM = "\u0644"
-let s:a_MEEM = "\u0645"
-let s:a_NOON = "\u0646"
-let s:a_HEH = "\u0647"
-let s:a_WAW = "\u0648"
-let s:a_ALEF_MAKSURA = "\u0649"
-let s:a_YEH = "\u064a"
-
-let s:a_FATHATAN = "\u064b"
-let s:a_DAMMATAN = "\u064c"
-let s:a_KASRATAN = "\u064d"
-let s:a_FATHA = "\u064e"
-let s:a_DAMMA = "\u064f"
-let s:a_KASRA = "\u0650"
-let s:a_SHADDA = "\u0651"
-let s:a_SUKUN = "\u0652"
-
-let s:a_MADDA_ABOVE = "\u0653"
-let s:a_HAMZA_ABOVE = "\u0654"
-let s:a_HAMZA_BELOW = "\u0655"
-
-let s:a_ZERO = "\u0660"
-let s:a_ONE = "\u0661"
-let s:a_TWO = "\u0662"
-let s:a_THREE = "\u0663"
-let s:a_FOUR = "\u0664"
-let s:a_FIVE = "\u0665"
-let s:a_SIX = "\u0666"
-let s:a_SEVEN = "\u0667"
-let s:a_EIGHT = "\u0668"
-let s:a_NINE = "\u0669"
-let s:a_PERCENT = "\u066a"
-let s:a_DECIMAL = "\u066b"
-let s:a_THOUSANDS = "\u066c"
-let s:a_STAR = "\u066d"
-let s:a_MINI_ALEF = "\u0670"
-
-let s:a_s_FATHATAN = "\ufe70"
-let s:a_m_TATWEEL_FATHATAN = "\ufe71"
-let s:a_s_DAMMATAN = "\ufe72"
-
-let s:a_s_KASRATAN = "\ufe74"
-
-let s:a_s_FATHA = "\ufe76"
-let s:a_m_FATHA = "\ufe77"
-let s:a_s_DAMMA = "\ufe78"
-let s:a_m_DAMMA = "\ufe79"
-let s:a_s_KASRA = "\ufe7a"
-let s:a_m_KASRA = "\ufe7b"
-let s:a_s_SHADDA = "\ufe7c"
-let s:a_m_SHADDA = "\ufe7d"
-let s:a_s_SUKUN = "\ufe7e"
-let s:a_m_SUKUN = "\ufe7f"
-
-let s:a_s_HAMZA = "\ufe80"
-let s:a_s_ALEF_MADDA = "\ufe81"
-let s:a_f_ALEF_MADDA = "\ufe82"
-let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83"
-let s:a_f_ALEF_HAMZA_ABOVE = "\ufe84"
-let s:a_s_WAW_HAMZA = "\ufe85"
-let s:a_f_WAW_HAMZA = "\ufe86"
-let s:a_s_ALEF_HAMZA_BELOW = "\ufe87"
-let s:a_f_ALEF_HAMZA_BELOW = "\ufe88"
-let s:a_s_YEH_HAMZA = "\ufe89"
-let s:a_f_YEH_HAMZA = "\ufe8a"
-let s:a_i_YEH_HAMZA = "\ufe8b"
-let s:a_m_YEH_HAMZA = "\ufe8c"
-let s:a_s_ALEF = "\ufe8d"
-let s:a_f_ALEF = "\ufe8e"
-let s:a_s_BEH = "\ufe8f"
-let s:a_f_BEH = "\ufe90"
-let s:a_i_BEH = "\ufe91"
-let s:a_m_BEH = "\ufe92"
-let s:a_s_TEH_MARBUTA = "\ufe93"
-let s:a_f_TEH_MARBUTA = "\ufe94"
-let s:a_s_TEH = "\ufe95"
-let s:a_f_TEH = "\ufe96"
-let s:a_i_TEH = "\ufe97"
-let s:a_m_TEH = "\ufe98"
-let s:a_s_THEH = "\ufe99"
-let s:a_f_THEH = "\ufe9a"
-let s:a_i_THEH = "\ufe9b"
-let s:a_m_THEH = "\ufe9c"
-let s:a_s_JEEM = "\ufe9d"
-let s:a_f_JEEM = "\ufe9e"
-let s:a_i_JEEM = "\ufe9f"
-let s:a_m_JEEM = "\ufea0"
-let s:a_s_HAH = "\ufea1"
-let s:a_f_HAH = "\ufea2"
-let s:a_i_HAH = "\ufea3"
-let s:a_m_HAH = "\ufea4"
-let s:a_s_KHAH = "\ufea5"
-let s:a_f_KHAH = "\ufea6"
-let s:a_i_KHAH = "\ufea7"
-let s:a_m_KHAH = "\ufea8"
-let s:a_s_DAL = "\ufea9"
-let s:a_f_DAL = "\ufeaa"
-let s:a_s_THAL = "\ufeab"
-let s:a_f_THAL = "\ufeac"
-let s:a_s_REH = "\ufead"
-let s:a_f_REH = "\ufeae"
-let s:a_s_ZAIN = "\ufeaf"
-let s:a_f_ZAIN = "\ufeb0"
-let s:a_s_SEEN = "\ufeb1"
-let s:a_f_SEEN = "\ufeb2"
-let s:a_i_SEEN = "\ufeb3"
-let s:a_m_SEEN = "\ufeb4"
-let s:a_s_SHEEN = "\ufeb5"
-let s:a_f_SHEEN = "\ufeb6"
-let s:a_i_SHEEN = "\ufeb7"
-let s:a_m_SHEEN = "\ufeb8"
-let s:a_s_SAD = "\ufeb9"
-let s:a_f_SAD = "\ufeba"
-let s:a_i_SAD = "\ufebb"
-let s:a_m_SAD = "\ufebc"
-let s:a_s_DAD = "\ufebd"
-let s:a_f_DAD = "\ufebe"
-let s:a_i_DAD = "\ufebf"
-let s:a_m_DAD = "\ufec0"
-let s:a_s_TAH = "\ufec1"
-let s:a_f_TAH = "\ufec2"
-let s:a_i_TAH = "\ufec3"
-let s:a_m_TAH = "\ufec4"
-let s:a_s_ZAH = "\ufec5"
-let s:a_f_ZAH = "\ufec6"
-let s:a_i_ZAH = "\ufec7"
-let s:a_m_ZAH = "\ufec8"
-let s:a_s_AIN = "\ufec9"
-let s:a_f_AIN = "\ufeca"
-let s:a_i_AIN = "\ufecb"
-let s:a_m_AIN = "\ufecc"
-let s:a_s_GHAIN = "\ufecd"
-let s:a_f_GHAIN = "\ufece"
-let s:a_i_GHAIN = "\ufecf"
-let s:a_m_GHAIN = "\ufed0"
-let s:a_s_FEH = "\ufed1"
-let s:a_f_FEH = "\ufed2"
-let s:a_i_FEH = "\ufed3"
-let s:a_m_FEH = "\ufed4"
-let s:a_s_QAF = "\ufed5"
-let s:a_f_QAF = "\ufed6"
-let s:a_i_QAF = "\ufed7"
-let s:a_m_QAF = "\ufed8"
-let s:a_s_KAF = "\ufed9"
-let s:a_f_KAF = "\ufeda"
-let s:a_i_KAF = "\ufedb"
-let s:a_m_KAF = "\ufedc"
-let s:a_s_LAM = "\ufedd"
-let s:a_f_LAM = "\ufede"
-let s:a_i_LAM = "\ufedf"
-let s:a_m_LAM = "\ufee0"
-let s:a_s_MEEM = "\ufee1"
-let s:a_f_MEEM = "\ufee2"
-let s:a_i_MEEM = "\ufee3"
-let s:a_m_MEEM = "\ufee4"
-let s:a_s_NOON = "\ufee5"
-let s:a_f_NOON = "\ufee6"
-let s:a_i_NOON = "\ufee7"
-let s:a_m_NOON = "\ufee8"
-let s:a_s_HEH = "\ufee9"
-let s:a_f_HEH = "\ufeea"
-let s:a_i_HEH = "\ufeeb"
-let s:a_m_HEH = "\ufeec"
-let s:a_s_WAW = "\ufeed"
-let s:a_f_WAW = "\ufeee"
-let s:a_s_ALEF_MAKSURA = "\ufeef"
-let s:a_f_ALEF_MAKSURA = "\ufef0"
-let s:a_s_YEH = "\ufef1"
-let s:a_f_YEH = "\ufef2"
-let s:a_i_YEH = "\ufef3"
-let s:a_m_YEH = "\ufef4"
-let s:a_s_LAM_ALEF_MADDA_ABOVE = "\ufef5"
-let s:a_f_LAM_ALEF_MADDA_ABOVE = "\ufef6"
-let s:a_s_LAM_ALEF_HAMZA_ABOVE = "\ufef7"
-let s:a_f_LAM_ALEF_HAMZA_ABOVE = "\ufef8"
-let s:a_s_LAM_ALEF_HAMZA_BELOW = "\ufef9"
-let s:a_f_LAM_ALEF_HAMZA_BELOW = "\ufefa"
-let s:a_s_LAM_ALEF = "\ufefb"
-let s:a_f_LAM_ALEF = "\ufefc"
-
-let s:a_BYTE_ORDER_MARK = "\ufeff"
-
-func Test_shape_initial()
- new
- set arabicshape
-
- " Shaping arabic {testchar} non-arabic Tests chg_c_a2i().
- " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result
- for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA],
- \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA],
- \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA],
- \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE],
- \ [s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_s_WAW_HAMZA],
- \ [s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_BELOW],
- \ [s:a_ALEF, s:a_s_GHAIN, s:a_s_ALEF],
- \ [s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_s_TEH_MARBUTA],
- \ [s:a_DAL, s:a_s_GHAIN, s:a_s_DAL],
- \ [s:a_THAL, s:a_s_GHAIN, s:a_s_THAL],
- \ [s:a_REH, s:a_s_GHAIN, s:a_s_REH],
- \ [s:a_ZAIN, s:a_s_GHAIN, s:a_s_ZAIN],
- \ [s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL],
- \ [s:a_WAW, s:a_s_GHAIN, s:a_s_WAW],
- \ [s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_s_ALEF_MAKSURA],
- \ [s:a_BEH, s:a_f_GHAIN, s:a_i_BEH],
- \ [s:a_TEH, s:a_f_GHAIN, s:a_i_TEH],
- \ [s:a_THEH, s:a_f_GHAIN, s:a_i_THEH],
- \ [s:a_JEEM, s:a_f_GHAIN, s:a_i_JEEM],
- \ [s:a_HAH, s:a_f_GHAIN, s:a_i_HAH],
- \ [s:a_KHAH, s:a_f_GHAIN, s:a_i_KHAH],
- \ [s:a_SEEN, s:a_f_GHAIN, s:a_i_SEEN],
- \ [s:a_SHEEN, s:a_f_GHAIN, s:a_i_SHEEN],
- \ [s:a_SAD, s:a_f_GHAIN, s:a_i_SAD],
- \ [s:a_DAD, s:a_f_GHAIN, s:a_i_DAD],
- \ [s:a_TAH, s:a_f_GHAIN, s:a_i_TAH],
- \ [s:a_ZAH, s:a_f_GHAIN, s:a_i_ZAH],
- \ [s:a_AIN, s:a_f_GHAIN, s:a_i_AIN],
- \ [s:a_GHAIN, s:a_f_GHAIN, s:a_i_GHAIN],
- \ [s:a_FEH, s:a_f_GHAIN, s:a_i_FEH],
- \ [s:a_QAF, s:a_f_GHAIN, s:a_i_QAF],
- \ [s:a_KAF, s:a_f_GHAIN, s:a_i_KAF],
- \ [s:a_LAM, s:a_f_GHAIN, s:a_i_LAM],
- \ [s:a_MEEM, s:a_f_GHAIN, s:a_i_MEEM],
- \ [s:a_NOON, s:a_f_GHAIN, s:a_i_NOON],
- \ [s:a_HEH, s:a_f_GHAIN, s:a_i_HEH],
- \ [s:a_YEH, s:a_f_GHAIN, s:a_i_YEH],
- \ ]
- call setline(1, s:a_GHAIN . pair[0] . ' ')
- call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3))
- endfor
-
- set arabicshape&
- bwipe!
-endfunc
-
-func Test_shape_isolated()
- new
- set arabicshape
-
- " Shaping non-arabic {testchar} non-arabic Tests chg_c_a2s().
- " pair[0] = testchar, pair[1] = current-result
- for pair in [[s:a_HAMZA, s:a_s_HAMZA],
- \ [s:a_ALEF_MADDA, s:a_s_ALEF_MADDA],
- \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_ALEF_HAMZA_ABOVE],
- \ [s:a_WAW_HAMZA, s:a_s_WAW_HAMZA],
- \ [s:a_ALEF_HAMZA_BELOW, s:a_s_ALEF_HAMZA_BELOW],
- \ [s:a_YEH_HAMZA, s:a_s_YEH_HAMZA],
- \ [s:a_ALEF, s:a_s_ALEF],
- \ [s:a_TEH_MARBUTA, s:a_s_TEH_MARBUTA],
- \ [s:a_DAL, s:a_s_DAL],
- \ [s:a_THAL, s:a_s_THAL],
- \ [s:a_REH, s:a_s_REH],
- \ [s:a_ZAIN, s:a_s_ZAIN],
- \ [s:a_TATWEEL, s:a_TATWEEL],
- \ [s:a_WAW, s:a_s_WAW],
- \ [s:a_ALEF_MAKSURA, s:a_s_ALEF_MAKSURA],
- \ [s:a_BEH, s:a_s_BEH],
- \ [s:a_TEH, s:a_s_TEH],
- \ [s:a_THEH, s:a_s_THEH],
- \ [s:a_JEEM, s:a_s_JEEM],
- \ [s:a_HAH, s:a_s_HAH],
- \ [s:a_KHAH, s:a_s_KHAH],
- \ [s:a_SEEN, s:a_s_SEEN],
- \ [s:a_SHEEN, s:a_s_SHEEN],
- \ [s:a_SAD, s:a_s_SAD],
- \ [s:a_DAD, s:a_s_DAD],
- \ [s:a_TAH, s:a_s_TAH],
- \ [s:a_ZAH, s:a_s_ZAH],
- \ [s:a_AIN, s:a_s_AIN],
- \ [s:a_GHAIN, s:a_s_GHAIN],
- \ [s:a_FEH, s:a_s_FEH],
- \ [s:a_QAF, s:a_s_QAF],
- \ [s:a_KAF, s:a_s_KAF],
- \ [s:a_LAM, s:a_s_LAM],
- \ [s:a_MEEM, s:a_s_MEEM],
- \ [s:a_NOON, s:a_s_NOON],
- \ [s:a_HEH, s:a_s_HEH],
- \ [s:a_YEH, s:a_s_YEH],
- \ ]
- call setline(1, ' ' . pair[0] . ' ')
- call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3))
- endfor
-
- set arabicshape&
- bwipe!
-endfunc
-
-func Test_shape_iso_to_medial()
- new
- set arabicshape
-
- " Shaping arabic {testchar} arabic Tests chg_c_a2m().
- " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result,
- " pair[3] = previous-result
- for pair in [[s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA, s:a_s_BEH],
- \[s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_f_ALEF_MADDA, s:a_i_BEH],
- \[s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH],
- \[s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_f_WAW_HAMZA, s:a_i_BEH],
- \[s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH],
- \[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_m_YEH_HAMZA, s:a_i_BEH],
- \[s:a_ALEF, s:a_s_GHAIN, s:a_f_ALEF, s:a_i_BEH],
- \[s:a_BEH, s:a_f_GHAIN, s:a_m_BEH, s:a_i_BEH],
- \[s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_f_TEH_MARBUTA, s:a_i_BEH],
- \[s:a_TEH, s:a_f_GHAIN, s:a_m_TEH, s:a_i_BEH],
- \[s:a_THEH, s:a_f_GHAIN, s:a_m_THEH, s:a_i_BEH],
- \[s:a_JEEM, s:a_f_GHAIN, s:a_m_JEEM, s:a_i_BEH],
- \[s:a_HAH, s:a_f_GHAIN, s:a_m_HAH, s:a_i_BEH],
- \[s:a_KHAH, s:a_f_GHAIN, s:a_m_KHAH, s:a_i_BEH],
- \[s:a_DAL, s:a_s_GHAIN, s:a_f_DAL, s:a_i_BEH],
- \[s:a_THAL, s:a_s_GHAIN, s:a_f_THAL, s:a_i_BEH],
- \[s:a_REH, s:a_s_GHAIN, s:a_f_REH, s:a_i_BEH],
- \[s:a_ZAIN, s:a_s_GHAIN, s:a_f_ZAIN, s:a_i_BEH],
- \[s:a_SEEN, s:a_f_GHAIN, s:a_m_SEEN, s:a_i_BEH],
- \[s:a_SHEEN, s:a_f_GHAIN, s:a_m_SHEEN, s:a_i_BEH],
- \[s:a_SAD, s:a_f_GHAIN, s:a_m_SAD, s:a_i_BEH],
- \[s:a_DAD, s:a_f_GHAIN, s:a_m_DAD, s:a_i_BEH],
- \[s:a_TAH, s:a_f_GHAIN, s:a_m_TAH, s:a_i_BEH],
- \[s:a_ZAH, s:a_f_GHAIN, s:a_m_ZAH, s:a_i_BEH],
- \[s:a_AIN, s:a_f_GHAIN, s:a_m_AIN, s:a_i_BEH],
- \[s:a_GHAIN, s:a_f_GHAIN, s:a_m_GHAIN, s:a_i_BEH],
- \[s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL, s:a_i_BEH],
- \[s:a_FEH, s:a_f_GHAIN, s:a_m_FEH, s:a_i_BEH],
- \[s:a_QAF, s:a_f_GHAIN, s:a_m_QAF, s:a_i_BEH],
- \[s:a_KAF, s:a_f_GHAIN, s:a_m_KAF, s:a_i_BEH],
- \[s:a_LAM, s:a_f_GHAIN, s:a_m_LAM, s:a_i_BEH],
- \[s:a_MEEM, s:a_f_GHAIN, s:a_m_MEEM, s:a_i_BEH],
- \[s:a_NOON, s:a_f_GHAIN, s:a_m_NOON, s:a_i_BEH],
- \[s:a_HEH, s:a_f_GHAIN, s:a_m_HEH, s:a_i_BEH],
- \[s:a_WAW, s:a_s_GHAIN, s:a_f_WAW, s:a_i_BEH],
- \[s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_f_ALEF_MAKSURA, s:a_i_BEH],
- \[s:a_YEH, s:a_f_GHAIN, s:a_m_YEH, s:a_i_BEH],
- \ ]
- call setline(1, s:a_GHAIN . pair[0] . s:a_BEH)
- call assert_equal([pair[1] . pair[2] . pair[3]], ScreenLines(1, 3))
- endfor
-
- set arabicshape&
- bwipe!
-endfunc
-
-func Test_shape_final()
- new
- set arabicshape
-
- " Shaping arabic {testchar} arabic Tests chg_c_a2f().
- " pair[0] = testchar, pair[1] = current-result, pair[2] = previous-result
- for pair in [[s:a_HAMZA, s:a_s_HAMZA, s:a_s_BEH],
- \[s:a_ALEF_MADDA, s:a_f_ALEF_MADDA, s:a_i_BEH],
- \[s:a_ALEF_HAMZA_ABOVE, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH],
- \[s:a_WAW_HAMZA, s:a_f_WAW_HAMZA, s:a_i_BEH],
- \[s:a_ALEF_HAMZA_BELOW, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH],
- \[s:a_YEH_HAMZA, s:a_f_YEH_HAMZA, s:a_i_BEH],
- \[s:a_ALEF, s:a_f_ALEF, s:a_i_BEH],
- \[s:a_BEH, s:a_f_BEH, s:a_i_BEH],
- \[s:a_TEH_MARBUTA, s:a_f_TEH_MARBUTA, s:a_i_BEH],
- \[s:a_TEH, s:a_f_TEH, s:a_i_BEH],
- \[s:a_THEH, s:a_f_THEH, s:a_i_BEH],
- \[s:a_JEEM, s:a_f_JEEM, s:a_i_BEH],
- \[s:a_HAH, s:a_f_HAH, s:a_i_BEH],
- \[s:a_KHAH, s:a_f_KHAH, s:a_i_BEH],
- \[s:a_DAL, s:a_f_DAL, s:a_i_BEH],
- \[s:a_THAL, s:a_f_THAL, s:a_i_BEH],
- \[s:a_REH, s:a_f_REH, s:a_i_BEH],
- \[s:a_ZAIN, s:a_f_ZAIN, s:a_i_BEH],
- \[s:a_SEEN, s:a_f_SEEN, s:a_i_BEH],
- \[s:a_SHEEN, s:a_f_SHEEN, s:a_i_BEH],
- \[s:a_SAD, s:a_f_SAD, s:a_i_BEH],
- \[s:a_DAD, s:a_f_DAD, s:a_i_BEH],
- \[s:a_TAH, s:a_f_TAH, s:a_i_BEH],
- \[s:a_ZAH, s:a_f_ZAH, s:a_i_BEH],
- \[s:a_AIN, s:a_f_AIN, s:a_i_BEH],
- \[s:a_GHAIN, s:a_f_GHAIN, s:a_i_BEH],
- \[s:a_TATWEEL, s:a_TATWEEL, s:a_i_BEH],
- \[s:a_FEH, s:a_f_FEH, s:a_i_BEH],
- \[s:a_QAF, s:a_f_QAF, s:a_i_BEH],
- \[s:a_KAF, s:a_f_KAF, s:a_i_BEH],
- \[s:a_LAM, s:a_f_LAM, s:a_i_BEH],
- \[s:a_MEEM, s:a_f_MEEM, s:a_i_BEH],
- \[s:a_NOON, s:a_f_NOON, s:a_i_BEH],
- \[s:a_HEH, s:a_f_HEH, s:a_i_BEH],
- \[s:a_WAW, s:a_f_WAW, s:a_i_BEH],
- \[s:a_ALEF_MAKSURA, s:a_f_ALEF_MAKSURA, s:a_i_BEH],
- \[s:a_YEH, s:a_f_YEH, s:a_i_BEH],
- \ ]
- call setline(1, ' ' . pair[0] . s:a_BEH)
- call assert_equal([' ' . pair[1] . pair[2]], ScreenLines(1, 3))
- endfor
-
- set arabicshape&
- bwipe!
-endfunc
-
-func Test_shape_combination_final()
- new
- set arabicshape
-
- " Shaping arabic {testchar} arabic Tests chg_c_laa2f().
- " pair[0] = testchar, pair[1] = current-result
- for pair in [[s:a_ALEF_MADDA, s:a_f_LAM_ALEF_MADDA_ABOVE],
- \ [s:a_ALEF_HAMZA_ABOVE, s:a_f_LAM_ALEF_HAMZA_ABOVE],
- \ [s:a_ALEF_HAMZA_BELOW, s:a_f_LAM_ALEF_HAMZA_BELOW],
- \ [s:a_ALEF, s:a_f_LAM_ALEF],
- \ ]
- " The test char is a composing char, put on s:a_LAM.
- call setline(1, ' ' . s:a_LAM . pair[0] . s:a_BEH)
- call assert_equal([' ' . pair[1] . s:a_i_BEH], ScreenLines(1, 3))
- endfor
-
- set arabicshape&
- bwipe!
-endfunc
-
-func Test_shape_combination_isolated()
- new
- set arabicshape
-
- " Shaping arabic {testchar} arabic Tests chg_c_laa2i().
- " pair[0] = testchar, pair[1] = current-result
- for pair in [[s:a_ALEF_MADDA, s:a_s_LAM_ALEF_MADDA_ABOVE],
- \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_LAM_ALEF_HAMZA_ABOVE],
- \ [s:a_ALEF_HAMZA_BELOW, s:a_s_LAM_ALEF_HAMZA_BELOW],
- \ [s:a_ALEF, s:a_s_LAM_ALEF],
- \ ]
- " The test char is a composing char, put on s:a_LAM.
- call setline(1, ' ' . s:a_LAM . pair[0] . ' ')
- call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3))
- endfor
-
- set arabicshape&
- bwipe!
-endfunc
-
-" Test for entering arabic character in a search command
-func Test_arabic_chars_in_search_cmd()
- new
- set arabic
- call feedkeys("i\nsghl!\<C-^>vim\<C-^>", 'tx')
- call cursor(1, 1)
- call feedkeys("/^sghl!\<C-^>vim$\<C-^>\<CR>", 'tx')
- call assert_equal([2, 1], [line('.'), col('.')])
-
- " Try searching in left-to-right mode
- set rightleftcmd=
- call cursor(1, 1)
- call feedkeys("/^sghl!\<C-^>vim$\<CR>", 'tx')
- call assert_equal([2, 1], [line('.'), col('.')])
-
- set rightleftcmd&
- set rightleft&
- set arabic&
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
deleted file mode 100644
index fb8b17cd16..0000000000
--- a/src/nvim/testdir/test_arglist.vim
+++ /dev/null
@@ -1,748 +0,0 @@
-" Test argument list commands
-
-source check.vim
-source shared.vim
-source term_util.vim
-
-func Reset_arglist()
- args a | %argd
-endfunc
-
-func Test_argidx()
- args a b c
- last
- call assert_equal(2, argidx())
- %argdelete
- call assert_equal(0, argidx())
- " doing it again doesn't result in an error
- %argdelete
- call assert_equal(0, argidx())
- call assert_fails('2argdelete', 'E16:')
-
- args a b c
- call assert_equal(0, argidx())
- next
- call assert_equal(1, argidx())
- next
- call assert_equal(2, argidx())
- 1argdelete
- call assert_equal(1, argidx())
- 1argdelete
- call assert_equal(0, argidx())
- 1argdelete
- call assert_equal(0, argidx())
-endfunc
-
-func Test_argadd()
- call Reset_arglist()
-
- %argdelete
- argadd a b c
- call assert_equal(0, argidx())
-
- %argdelete
- argadd a
- call assert_equal(0, argidx())
- argadd b c d
- call assert_equal(0, argidx())
-
- call Init_abc()
- argadd x
- call Assert_argc(['a', 'b', 'x', 'c'])
- call assert_equal(1, argidx())
-
- call Init_abc()
- 0argadd x
- call Assert_argc(['x', 'a', 'b', 'c'])
- call assert_equal(2, argidx())
-
- call Init_abc()
- 1argadd x
- call Assert_argc(['a', 'x', 'b', 'c'])
- call assert_equal(2, argidx())
-
- call Init_abc()
- $argadd x
- call Assert_argc(['a', 'b', 'c', 'x'])
- call assert_equal(1, argidx())
-
- call Init_abc()
- $argadd x
- +2argadd y
- call Assert_argc(['a', 'b', 'c', 'x', 'y'])
- call assert_equal(1, argidx())
-
- %argd
- edit d
- arga
- call assert_equal(1, len(argv()))
- call assert_equal('d', get(argv(), 0, ''))
-
- %argd
- edit some\ file
- arga
- call assert_equal(1, len(argv()))
- call assert_equal('some file', get(argv(), 0, ''))
-
- %argd
- new
- arga
- call assert_equal(0, len(argv()))
-
- if has('unix')
- call assert_fails('argadd `Xdoes_not_exist`', 'E479:')
- endif
-endfunc
-
-func Test_argadd_empty_curbuf()
- new
- let curbuf = bufnr('%')
- call writefile(['test', 'Xargadd'], 'Xargadd')
- " must not re-use the current buffer.
- argadd Xargadd
- call assert_equal(curbuf, bufnr('%'))
- call assert_equal('', bufname('%'))
- call assert_equal(1, '$'->line())
- rew
- call assert_notequal(curbuf, '%'->bufnr())
- call assert_equal('Xargadd', '%'->bufname())
- call assert_equal(2, line('$'))
-
- %argd
- bwipe!
-endfunc
-
-func Init_abc()
- args a b c
- next
-endfunc
-
-func Assert_argc(l)
- call assert_equal(len(a:l), argc())
- let i = 0
- while i < len(a:l) && i < argc()
- call assert_equal(a:l[i], argv(i))
- let i += 1
- endwhile
-endfunc
-
-" Test for [count]argument and [count]argdelete commands
-" Ported from the test_argument_count.in test script
-func Test_argument()
- call Reset_arglist()
-
- let save_hidden = &hidden
- set hidden
-
- let g:buffers = []
- augroup TEST
- au BufEnter * call add(buffers, expand('%:t'))
- augroup END
-
- argadd a b c d
- $argu
- $-argu
- -argu
- 1argu
- +2argu
-
- augroup TEST
- au!
- augroup END
-
- call assert_equal(['d', 'c', 'b', 'a', 'c'], g:buffers)
-
- call assert_equal("\na b [c] d ", execute(':args'))
-
- .argd
- call assert_equal(['a', 'b', 'd'], argv())
-
- -argd
- call assert_equal(['a', 'd'], argv())
-
- $argd
- call assert_equal(['a'], argv())
-
- 1arga c
- 1arga b
- $argu
- $arga x
- call assert_equal(['a', 'b', 'c', 'x'], argv())
-
- 0arga y
- call assert_equal(['y', 'a', 'b', 'c', 'x'], argv())
-
- %argd
- call assert_equal([], argv())
-
- arga a b c d e f
- 2,$-argd
- call assert_equal(['a', 'f'], argv())
-
- let &hidden = save_hidden
-
- let save_columns = &columns
- let &columns = 79
- exe 'args ' .. join(range(1, 81))
- call assert_equal(join([
- \ '',
- \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ',
- \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ',
- \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ',
- \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ',
- \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ',
- \ ], "\n"),
- \ execute('args'))
-
- " No trailing newline with one item per row.
- let long_arg = repeat('X', 81)
- exe 'args ' .. long_arg
- call assert_equal("\n[".long_arg.']', execute('args'))
- let &columns = save_columns
-
- " Setting argument list should fail when the current buffer has unsaved
- " changes
- %argd
- enew!
- set modified
- call assert_fails('args x y z', 'E37:')
- args! x y z
- call assert_equal(['x', 'y', 'z'], argv())
- call assert_equal('x', expand('%:t'))
-
- last | enew | argu
- call assert_equal('z', expand('%:t'))
-
- %argdelete
- call assert_fails('argument', 'E163:')
-endfunc
-
-func Test_list_arguments()
- " Clean the argument list
- arga a | %argd
-
- " four args half the screen width makes two lines with two columns
- let aarg = repeat('a', &columns / 2 - 4)
- let barg = repeat('b', &columns / 2 - 4)
- let carg = repeat('c', &columns / 2 - 4)
- let darg = repeat('d', &columns / 2 - 4)
- exe 'argadd ' aarg barg carg darg
-
- redir => result
- args
- redir END
- call assert_match('\[' . aarg . '] \+' . carg . '\n' . barg . ' \+' . darg, trim(result))
-
- " if one arg is longer than half the screen make one column
- exe 'argdel' aarg
- let aarg = repeat('a', &columns / 2 + 2)
- exe '0argadd' aarg
- redir => result
- args
- redir END
- call assert_match(aarg . '\n\[' . barg . ']\n' . carg . '\n' . darg, trim(result))
-
- %argdelete
-endfunc
-
-func Test_args_with_quote()
- " Only on Unix can a file name include a double quote.
- if has('unix')
- args \"foobar
- call assert_equal('"foobar', argv(0))
- %argdelete
- endif
-endfunc
-
-" Test for 0argadd and 0argedit
-" Ported from the test_argument_0count.in test script
-func Test_zero_argadd()
- call Reset_arglist()
-
- arga a b c d
- 2argu
- 0arga added
- call assert_equal(['added', 'a', 'b', 'c', 'd'], argv())
-
- 2argu
- arga third
- call assert_equal(['added', 'a', 'third', 'b', 'c', 'd'], argv())
-
- %argd
- arga a b c d
- 2argu
- 0arge edited
- call assert_equal(['edited', 'a', 'b', 'c', 'd'], argv())
-
- 2argu
- arga third
- call assert_equal(['edited', 'a', 'third', 'b', 'c', 'd'], argv())
-
- 2argu
- argedit file\ with\ spaces another file
- call assert_equal(['edited', 'a', 'file with spaces', 'another', 'file', 'third', 'b', 'c', 'd'], argv())
- call assert_equal('file with spaces', expand('%'))
-endfunc
-
-" Test for argc()
-func Test_argc()
- call Reset_arglist()
- call assert_equal(0, argc())
- argadd a b
- call assert_equal(2, argc())
-endfunc
-
-" Test for arglistid()
-func Test_arglistid()
- call Reset_arglist()
- arga a b
- call assert_equal(0, arglistid())
- split
- arglocal
- call assert_equal(1, arglistid())
- tabnew | tabfirst
- call assert_equal(0, arglistid(2))
- call assert_equal(1, arglistid(1, 1))
- call assert_equal(0, arglistid(2, 1))
- call assert_equal(1, arglistid(1, 2))
- tabonly | only | enew!
- argglobal
- call assert_equal(0, arglistid())
-endfunc
-
-" Tests for argv() and argc()
-func Test_argv()
- call Reset_arglist()
- call assert_equal([], argv())
- call assert_equal("", argv(2))
- call assert_equal(0, argc())
- argadd a b c d
- call assert_equal(4, argc())
- call assert_equal('c', argv(2))
-
- let w1_id = win_getid()
- split
- let w2_id = win_getid()
- arglocal
- args e f g
- tabnew
- let w3_id = win_getid()
- split
- let w4_id = win_getid()
- argglobal
- tabfirst
- call assert_equal(4, argc(w1_id))
- call assert_equal('b', argv(1, w1_id))
- call assert_equal(['a', 'b', 'c', 'd'], argv(-1, w1_id))
-
- call assert_equal(3, argc(w2_id))
- call assert_equal('f', argv(1, w2_id))
- call assert_equal(['e', 'f', 'g'], argv(-1, w2_id))
-
- call assert_equal(3, argc(w3_id))
- call assert_equal('e', argv(0, w3_id))
- call assert_equal(['e', 'f', 'g'], argv(-1, w3_id))
-
- call assert_equal(4, argc(w4_id))
- call assert_equal('c', argv(2, w4_id))
- call assert_equal(['a', 'b', 'c', 'd'], argv(-1, w4_id))
-
- call assert_equal(4, argc(-1))
- call assert_equal(3, argc())
- call assert_equal('d', argv(3, -1))
- call assert_equal(['a', 'b', 'c', 'd'], argv(-1, -1))
- tabonly | only | enew!
- " Negative test cases
- call assert_equal(-1, argc(100))
- call assert_equal('', argv(1, 100))
- call assert_equal([], argv(-1, 100))
- call assert_equal('', argv(10, -1))
-endfunc
-
-" Test for the :argedit command
-func Test_argedit()
- call Reset_arglist()
- argedit a
- call assert_equal(['a'], argv())
- call assert_equal('a', expand('%:t'))
- argedit b
- call assert_equal(['a', 'b'], argv())
- call assert_equal('b', expand('%:t'))
- argedit a
- call assert_equal(['a', 'b', 'a'], argv())
- call assert_equal('a', expand('%:t'))
- " When file name case is ignored, an existing buffer with only case
- " difference is re-used.
- argedit C D
- call assert_equal('C', expand('%:t'))
- call assert_equal(['a', 'b', 'a', 'C', 'D'], argv())
- argedit c
- if has('fname_case')
- call assert_equal(['a', 'b', 'a', 'C', 'c', 'D'], argv())
- else
- call assert_equal(['a', 'b', 'a', 'C', 'C', 'D'], argv())
- endif
- 0argedit x
- if has('fname_case')
- call assert_equal(['x', 'a', 'b', 'a', 'C', 'c', 'D'], argv())
- else
- call assert_equal(['x', 'a', 'b', 'a', 'C', 'C', 'D'], argv())
- endif
- enew! | set modified
- call assert_fails('argedit y', 'E37:')
- argedit! y
- if has('fname_case')
- call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'c', 'D'], argv())
- else
- call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'C', 'D'], argv())
- endif
- %argd
- bwipe! C
- bwipe! D
-
- " :argedit reuses the current buffer if it is empty
- %argd
- " make sure to use a new buffer number for x when it is loaded
- bw! x
- new
- let a = bufnr()
- argedit x
- call assert_equal(a, bufnr())
- call assert_equal('x', bufname())
- %argd
- bw! x
-endfunc
-
-" Test for the :argdedupe command
-func Test_argdedupe()
- call Reset_arglist()
- argdedupe
- call assert_equal([], argv())
-
- args a a a aa b b a b aa
- argdedupe
- call assert_equal(['a', 'aa', 'b'], argv())
-
- args a b c
- argdedupe
- call assert_equal(['a', 'b', 'c'], argv())
-
- args a
- argdedupe
- call assert_equal(['a'], argv())
-
- args a A b B
- argdedupe
- if has('fname_case')
- call assert_equal(['a', 'A', 'b', 'B'], argv())
- else
- call assert_equal(['a', 'b'], argv())
- endif
-
- args a b a c a b
- last
- argdedupe
- next
- call assert_equal('c', expand('%:t'))
-
- args a ./a
- argdedupe
- call assert_equal(['a'], argv())
-
- %argd
-endfunc
-
-" Test for the :argdelete command
-func Test_argdelete()
- call Reset_arglist()
- args aa a aaa b bb
- argdelete a*
- call assert_equal(['b', 'bb'], argv())
- call assert_equal('aa', expand('%:t'))
- last
- argdelete %
- call assert_equal(['b'], argv())
- call assert_fails('argdelete', 'E610:')
- call assert_fails('1,100argdelete', 'E16:')
- call assert_fails('argdel /\)/', 'E55:')
- call assert_fails('1argdel 1', 'E474:')
-
- call Reset_arglist()
- args a b c d
- next
- argdel
- call Assert_argc(['a', 'c', 'd'])
- %argdel
-
- call assert_fails('argdel does_not_exist', 'E480:')
-endfunc
-
-func Test_argdelete_completion()
- args foo bar
-
- call feedkeys(":argdelete \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"argdelete bar foo', @:)
-
- call feedkeys(":argdelete x \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"argdelete x bar foo', @:)
-
- %argd
-endfunc
-
-" Tests for the :next, :prev, :first, :last, :rewind commands
-func Test_argpos()
- call Reset_arglist()
- args a b c d
- last
- call assert_equal(3, argidx())
- call assert_fails('next', 'E165:')
- prev
- call assert_equal(2, argidx())
- Next
- call assert_equal(1, argidx())
- first
- call assert_equal(0, argidx())
- call assert_fails('prev', 'E164:')
- 3next
- call assert_equal(3, argidx())
- rewind
- call assert_equal(0, argidx())
- %argd
-endfunc
-
-" Test for autocommand that redefines the argument list, when doing ":all".
-func Test_arglist_autocmd()
- autocmd BufReadPost Xxx2 next Xxx2 Xxx1
- call writefile(['test file Xxx1'], 'Xxx1')
- call writefile(['test file Xxx2'], 'Xxx2')
- call writefile(['test file Xxx3'], 'Xxx3')
-
- new
- " redefine arglist; go to Xxx1
- next! Xxx1 Xxx2 Xxx3
- " open window for all args; Reading Xxx2 will try to change the arglist and
- " that will fail
- call assert_fails("all", "E1156:")
- call assert_equal('test file Xxx1', getline(1))
- wincmd w
- call assert_equal('test file Xxx2', getline(1))
- wincmd w
- call assert_equal('test file Xxx3', getline(1))
-
- autocmd! BufReadPost Xxx2
- enew! | only
- call delete('Xxx1')
- call delete('Xxx2')
- call delete('Xxx3')
- argdelete Xxx*
- bwipe! Xxx1 Xxx2 Xxx3
-endfunc
-
-func Test_arg_all_expand()
- call writefile(['test file Xxx1'], 'Xx x')
- next notexist Xx\ x runtest.vim
- call assert_equal('notexist Xx\ x runtest.vim', expand('##'))
- call delete('Xx x')
-endfunc
-
-func Test_large_arg()
- " Argument longer or equal to the number of columns used to cause
- " access to invalid memory.
- exe 'argadd ' .repeat('x', &columns)
- args
-endfunc
-
-func Test_argdo()
- next! Xa.c Xb.c Xc.c
- new
- let l = []
- argdo call add(l, expand('%'))
- call assert_equal(['Xa.c', 'Xb.c', 'Xc.c'], l)
- bwipe Xa.c Xb.c Xc.c
-endfunc
-
-" Test for quitting Vim with unedited files in the argument list
-func Test_quit_with_arglist()
- CheckRunVimInTerminal
- let buf = RunVimInTerminal('', {'rows': 6})
- call term_sendkeys(buf, ":set nomore\n")
- call term_sendkeys(buf, ":args a b c\n")
- call term_sendkeys(buf, ":quit\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^E173:', term_getline(buf, 6))})
- call StopVimInTerminal(buf)
-
- " Try :confirm quit with unedited files in arglist
- let buf = RunVimInTerminal('', {'rows': 6})
- call term_sendkeys(buf, ":set nomore\n")
- call term_sendkeys(buf, ":args a b c\n")
- call term_sendkeys(buf, ":confirm quit\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$',
- \ term_getline(buf, 6))})
- call term_sendkeys(buf, "N")
- call term_wait(buf)
- call term_sendkeys(buf, ":confirm quit\n")
- call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$',
- \ term_getline(buf, 6))})
- call term_sendkeys(buf, "Y")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))})
- only!
- " When this test fails, swap files are left behind which breaks subsequent
- " tests
- call delete('.a.swp')
- call delete('.b.swp')
- call delete('.c.swp')
-endfunc
-
-" Test for ":all" not working when in the cmdline window
-func Test_all_not_allowed_from_cmdwin()
- au BufEnter * all
- next x
- " Use try/catch here, somehow assert_fails() doesn't work on MS-Windows
- " console.
- let caught = 'no'
- try
- exe ":norm! 7q?apat\<CR>"
- catch /E11:/
- let caught = 'yes'
- endtry
- call assert_equal('yes', caught)
- au! BufEnter
-endfunc
-
-func Test_clear_arglist_in_all()
- n 0 00 000 0000 00000 000000
- au WinNew 0 n 0
- call assert_fails("all", "E1156")
- au! *
-endfunc
-
-" Test for the :all command
-func Test_all_command()
- %argdelete
-
- " :all command should not close windows with files in the argument list,
- " but can rearrange the windows.
- args Xargnew1 Xargnew2
- %bw!
- edit Xargold1
- split Xargnew1
- let Xargnew1_winid = win_getid()
- split Xargold2
- split Xargnew2
- let Xargnew2_winid = win_getid()
- split Xargold3
- all
- call assert_equal(2, winnr('$'))
- call assert_equal([Xargnew1_winid, Xargnew2_winid],
- \ [win_getid(1), win_getid(2)])
- call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')],
- \ [winbufnr(1), winbufnr(2)])
-
- " :all command should close windows for files which are not in the
- " argument list in the current tab page.
- %bw!
- edit Xargold1
- split Xargold2
- tabedit Xargold3
- split Xargold4
- tabedit Xargold5
- tabfirst
- all
- call assert_equal(3, tabpagenr('$'))
- call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')], tabpagebuflist(1))
- call assert_equal([bufnr('Xargold4'), bufnr('Xargold3')], tabpagebuflist(2))
- call assert_equal([bufnr('Xargold5')], tabpagebuflist(3))
-
- " :tab all command should close windows for files which are not in the
- " argument list across all the tab pages.
- %bw!
- edit Xargold1
- split Xargold2
- tabedit Xargold3
- split Xargold4
- tabedit Xargold5
- tabfirst
- args Xargnew1 Xargnew2
- tab all
- call assert_equal(2, tabpagenr('$'))
- call assert_equal([bufnr('Xargnew1')], tabpagebuflist(1))
- call assert_equal([bufnr('Xargnew2')], tabpagebuflist(2))
-
- " If a count is specified, then :all should open only that many windows.
- %bw!
- args Xargnew1 Xargnew2 Xargnew3 Xargnew4 Xargnew5
- all 3
- call assert_equal(3, winnr('$'))
- call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2'), bufnr('Xargnew3')],
- \ [winbufnr(1), winbufnr(2), winbufnr(3)])
-
- " The :all command should not open more than 'tabpagemax' tab pages.
- " If there are more files, then they should be opened in the last tab page.
- %bw!
- set tabpagemax=3
- tab all
- call assert_equal(3, tabpagenr('$'))
- call assert_equal([bufnr('Xargnew1')], tabpagebuflist(1))
- call assert_equal([bufnr('Xargnew2')], tabpagebuflist(2))
- call assert_equal([bufnr('Xargnew3'), bufnr('Xargnew4'), bufnr('Xargnew5')],
- \ tabpagebuflist(3))
- set tabpagemax&
-
- " Without the 'hidden' option, modified buffers should not be closed.
- args Xargnew1 Xargnew2
- %bw!
- edit Xargtemp1
- call setline(1, 'temp buffer 1')
- split Xargtemp2
- call setline(1, 'temp buffer 2')
- all
- call assert_equal(4, winnr('$'))
- call assert_equal([bufnr('Xargtemp2'), bufnr('Xargtemp1'), bufnr('Xargnew1'),
- \ bufnr('Xargnew2')],
- \ [winbufnr(1), winbufnr(2), winbufnr(3), winbufnr(4)])
-
- " With the 'hidden' option set, both modified and unmodified buffers in
- " closed windows should be hidden.
- set hidden
- all
- call assert_equal(2, winnr('$'))
- call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')],
- \ [winbufnr(1), winbufnr(2)])
- call assert_equal([1, 1, 0, 0], [getbufinfo('Xargtemp1')[0].hidden,
- \ getbufinfo('Xargtemp2')[0].hidden,
- \ getbufinfo('Xargnew1')[0].hidden,
- \ getbufinfo('Xargnew2')[0].hidden])
- set nohidden
-
- " When 'winheight' is set to a large value, :all should open only one
- " window.
- args Xargnew1 Xargnew2 Xargnew3 Xargnew4 Xargnew5
- %bw!
- set winheight=9999
- call assert_fails('all', 'E36:')
- call assert_equal([1, bufnr('Xargnew1')], [winnr('$'), winbufnr(1)])
- set winheight&
-
- " When 'winwidth' is set to a large value, :vert all should open only one
- " window.
- %bw!
- set winwidth=9999
- call assert_fails('vert all', 'E36:')
- call assert_equal([1, bufnr('Xargnew1')], [winnr('$'), winbufnr(1)])
- set winwidth&
-
- " empty argument list tests
- %bw!
- %argdelete
- call assert_equal('', execute('args'))
- all
- call assert_equal(1, winnr('$'))
-
- %argdelete
- %bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
deleted file mode 100644
index 431908e95c..0000000000
--- a/src/nvim/testdir/test_assert.vim
+++ /dev/null
@@ -1,363 +0,0 @@
-" Test that the methods used for testing work.
-
-func Test_assert_false()
- call assert_equal(0, assert_false(0))
- call assert_equal(0, assert_false(v:false))
- call assert_equal(0, v:false->assert_false())
-
- call assert_equal(1, assert_false(123))
- call assert_match("Expected False but got 123", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, 123->assert_false())
- call assert_match("Expected False but got 123", v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_assert_true()
- call assert_equal(0, assert_true(1))
- call assert_equal(0, assert_true(123))
- call assert_equal(0, assert_true(v:true))
- call assert_equal(0, v:true->assert_true())
-
- call assert_equal(1, assert_true(0))
- call assert_match("Expected True but got 0", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, 0->assert_true())
- call assert_match("Expected True but got 0", v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_assert_equal()
- let s = 'foo'
- call assert_equal(0, assert_equal('foo', s))
- let n = 4
- call assert_equal(0, assert_equal(4, n))
- let l = [1, 2, 3]
- call assert_equal(0, 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)
-
- let s = 'foo'
- call assert_equal(1, assert_equal('bar', s))
- call assert_match("Expected 'bar' but got 'foo'", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX')
- call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0])
- call remove(v:errors, 0)
-
- " special characters are escaped
- call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x')
- call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_assert_equal_dict()
- call assert_equal(0, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 1}))
-
- call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 3}))
- call assert_match("Expected {'one': 1} but got {'one': 3} - 1 equal item omitted", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 22, one: 11}))
- call assert_match("Expected {'one': 1, 'two': 2} but got {'one': 11, 'two': 22}", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_equal(#{}, #{two: 2, one: 1}))
- call assert_match("Expected {} but got {'one': 1, 'two': 2}", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_equal(#{two: 2, one: 1}, #{}))
- call assert_match("Expected {'one': 1, 'two': 2} but got {}", v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-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, 'Xone'->assert_equalfile('xyzxyz'))
- call assert_match("E485: Can't read file xyzxyz", v:errors[0])
- call remove(v:errors, 0)
-
- call writefile(goodtext, 'Xtwo')
- call 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_notequal()
- let n = 4
- call assert_equal(0, assert_notequal('foo', n))
- let s = 'foo'
- call assert_equal(0, assert_notequal([1, 2, 3], s))
-
- call assert_equal(1, assert_notequal('foo', s))
- call assert_match("Expected not equal to 'foo'", v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_assert_report()
- call assert_equal(1, assert_report('something is wrong'))
- call assert_match('something is wrong', v:errors[0])
- call remove(v:errors, 0)
- call assert_equal(1, 'also wrong'->assert_report())
- call assert_match('also wrong', v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_assert_exception()
- try
- nocommand
- catch
- call assert_equal(0, assert_exception('E492:'))
- endtry
-
- try
- nocommand
- catch
- call assert_equal(1, assert_exception('E12345:'))
- endtry
- call assert_match("Expected 'E12345:' but got 'Vim:E492: ", v:errors[0])
- call remove(v:errors, 0)
-
- try
- nocommand
- catch
- try
- " illegal argument, get NULL for error
- call assert_equal(1, assert_exception([]))
- catch
- call assert_equal(0, assert_exception('E730:'))
- endtry
- endtry
-
- call assert_equal(1, assert_exception('E492:'))
- call assert_match('v:exception is not set', v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_wrong_error_type()
- let save_verrors = v:errors
- let v:['errors'] = {'foo': 3}
- call assert_equal('yes', 'no')
- let verrors = v:errors
- let v:errors = save_verrors
- call assert_equal(type([]), type(verrors))
-endfunc
-
-func Test_match()
- call assert_equal(0, assert_match('^f.*b.*r$', 'foobar'))
-
- call assert_equal(1, assert_match('bar.*foo', 'foobar'))
- call assert_match("Pattern 'bar.*foo' does not match 'foobar'", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_match('bar.*foo', 'foobar', 'wrong'))
- call assert_match('wrong', v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong'))
- call assert_match('wrong', v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_notmatch()
- call assert_equal(0, assert_notmatch('foo', 'bar'))
- call assert_equal(0, assert_notmatch('^foobar$', 'foobars'))
-
- call assert_equal(1, assert_notmatch('foo', 'foobar'))
- call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, 'foobar'->assert_notmatch('foo'))
- call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_assert_fail_fails()
- call assert_equal(1, assert_fails('xxx', 'E12345'))
- call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_fails('xxx', 'E9876', 'stupid'))
- call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_fails('xxx', ['E9876']))
- call assert_match("Expected \\['E9876'\\] but got 'E492:", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_fails('xxx', ['E492:', 'E9876']))
- call assert_match("Expected \\['E492:', 'E9876'\\] but got 'E492:", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, assert_fails('echo', '', 'echo command'))
- call assert_match("command did not fail: echo command", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(1, 'echo'->assert_fails('', 'echo command'))
- call assert_match("command did not fail: echo command", v:errors[0])
- call remove(v:errors, 0)
-
- try
- call assert_equal(1, assert_fails('xxx', []))
- catch
- let exp = v:exception
- endtry
- call assert_match("E856: assert_fails() second argument", exp)
-
- try
- call assert_equal(1, assert_fails('xxx', ['1', '2', '3']))
- catch
- let exp = v:exception
- endtry
- call assert_match("E856: assert_fails() second argument", exp)
-
- try
- call assert_equal(1, assert_fails('xxx', #{one: 1}))
- catch
- let exp = v:exception
- endtry
- call assert_match("E856: assert_fails() second argument", exp)
-
- try
- call assert_equal(1, assert_fails('xxx', 'E492', '', 'burp'))
- catch
- let exp = v:exception
- endtry
- call assert_match("E1115: assert_fails() fourth argument must be a number", exp)
-
- try
- call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123))
- catch
- let exp = v:exception
- endtry
- call assert_match("E1116: assert_fails() fifth argument must be a string", exp)
-endfunc
-
-func Test_assert_fails_in_try_block()
- try
- call assert_equal(0, assert_fails('throw "error"'))
- endtry
-endfunc
-
-func Test_assert_beeps()
- new
- call assert_equal(0, assert_beeps('normal h'))
-
- call assert_equal(1, assert_beeps('normal 0'))
- call assert_match("command did not beep: normal 0", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(0, 'normal h'->assert_beeps())
- call assert_equal(1, 'normal 0'->assert_beeps())
- call assert_match("command did not beep: normal 0", v:errors[0])
- call remove(v:errors, 0)
-
- bwipe
-endfunc
-
-func Test_assert_inrange()
- call assert_equal(0, assert_inrange(7, 7, 7))
- call assert_equal(0, assert_inrange(5, 7, 5))
- call assert_equal(0, assert_inrange(5, 7, 6))
- call assert_equal(0, assert_inrange(5, 7, 7))
-
- call assert_equal(1, assert_inrange(5, 7, 4))
- call assert_match("Expected range 5 - 7, but got 4", v:errors[0])
- call remove(v:errors, 0)
- call assert_equal(1, assert_inrange(5, 7, 8))
- call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_equal(0, 5->assert_inrange(5, 7))
- call assert_equal(0, 7->assert_inrange(5, 7))
- call assert_equal(1, 8->assert_inrange(5, 7))
- call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
- call remove(v:errors, 0)
-
- call assert_fails('call assert_inrange(1, 1)', 'E119:')
-
- if has('float')
- call assert_equal(0, assert_inrange(7.0, 7, 7))
- call assert_equal(0, assert_inrange(7, 7.0, 7))
- call assert_equal(0, assert_inrange(7, 7, 7.0))
- call assert_equal(0, assert_inrange(5, 7, 5.0))
- call assert_equal(0, assert_inrange(5, 7, 6.0))
- call assert_equal(0, assert_inrange(5, 7, 7.0))
-
- call assert_equal(1, assert_inrange(5, 7, 4.0))
- call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0])
- call remove(v:errors, 0)
- call assert_equal(1, assert_inrange(5, 7, 8.0))
- call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0])
- call remove(v:errors, 0)
- endif
-endfunc
-
-func Test_assert_with_msg()
- call assert_equal('foo', 'bar', 'testing')
- call assert_match("testing: Expected 'foo' but got 'bar'", v:errors[0])
- call remove(v:errors, 0)
-endfunc
-
-func Test_mouse_position()
- let save_mouse = &mouse
- set mouse=a
- new
- call setline(1, ['line one', 'line two'])
- call assert_equal([0, 1, 1, 0], getpos('.'))
- call Ntest_setmouse(1, 5)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 5, 0], getpos('.'))
- call Ntest_setmouse(2, 20)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 2, 8, 0], getpos('.'))
- call Ntest_setmouse(5, 1)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 2, 1, 0], getpos('.'))
- bwipe!
- let &mouse = save_mouse
-endfunc
-
-" Must be last.
-func Test_zz_quit_detected()
- " Verify that if a test function ends Vim the test script detects this.
- quit
-endfunc
diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim
deleted file mode 100644
index a8810047a0..0000000000
--- a/src/nvim/testdir/test_autochdir.vim
+++ /dev/null
@@ -1,130 +0,0 @@
-" Test 'autochdir' behavior
-
-source check.vim
-CheckOption autochdir
-
-func Test_set_filename()
- CheckFunction test_autochdir
- let cwd = getcwd()
- call test_autochdir()
- set acd
-
- let s:li = []
- autocmd DirChanged auto call add(s:li, "autocd")
- autocmd DirChanged auto call add(s:li, expand("<afile>"))
-
- new
- w samples/Xtest
- call assert_equal("Xtest", expand('%'))
- call assert_equal("samples", substitute(getcwd(), '.*/\(\k*\)', '\1', ''))
- call assert_equal(["autocd", getcwd()], s:li)
-
- bwipe!
- au! DirChanged
- set noacd
- call chdir(cwd)
- call delete('samples/Xtest')
-endfunc
-
-func Test_set_filename_other_window()
- CheckFunction test_autochdir
- let cwd = getcwd()
- call test_autochdir()
- call mkdir('Xa')
- call mkdir('Xb')
- call mkdir('Xc')
- try
- args Xa/aaa.txt Xb/bbb.txt
- set acd
- let winid = win_getid()
- snext
- call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', ''))
- call win_execute(winid, 'file ' .. cwd .. '/Xc/ccc.txt')
- call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', ''))
- finally
- set noacd
- call chdir(cwd)
- call delete('Xa', 'rf')
- call delete('Xb', 'rf')
- call delete('Xc', 'rf')
- bwipe! aaa.txt
- bwipe! bbb.txt
- bwipe! ccc.txt
- endtry
-endfunc
-
-func Test_acd_win_execute()
- CheckFunction test_autochdir
- let cwd = getcwd()
- set acd
- call test_autochdir()
-
- call mkdir('Xfile')
- let winid = win_getid()
- new Xfile/file
- call assert_match('testdir.Xfile$', getcwd())
- cd ..
- call assert_match('testdir$', getcwd())
- call win_execute(winid, 'echo')
- call assert_match('testdir$', getcwd())
-
- bwipe!
- set noacd
- call chdir(cwd)
- call delete('Xfile', 'rf')
-endfunc
-
-func Test_verbose_pwd()
- CheckFunction test_autochdir
- let cwd = getcwd()
- call test_autochdir()
-
- edit global.txt
- call assert_match('\[global\].*testdir$', execute('verbose pwd'))
-
- call mkdir('Xautodir')
- split Xautodir/local.txt
- lcd Xautodir
- call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
-
- set acd
- wincmd w
- call assert_match('\[autochdir\].*testdir$', execute('verbose pwd'))
- execute 'tcd' cwd
- call assert_match('\[tabpage\].*testdir$', execute('verbose pwd'))
- execute 'cd' cwd
- call assert_match('\[global\].*testdir$', execute('verbose pwd'))
- execute 'lcd' cwd
- call assert_match('\[window\].*testdir$', execute('verbose pwd'))
- edit
- call assert_match('\[autochdir\].*testdir$', execute('verbose pwd'))
- enew
- wincmd w
- call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd'))
- wincmd w
- call assert_match('\[window\].*testdir$', execute('verbose pwd'))
- wincmd w
- call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd'))
- set noacd
- call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd'))
- wincmd w
- call assert_match('\[window\].*testdir$', execute('verbose pwd'))
- execute 'cd' cwd
- call assert_match('\[global\].*testdir$', execute('verbose pwd'))
- wincmd w
- call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
-
- bwipe!
- call chdir(cwd)
- call delete('Xautodir', 'rf')
-endfunc
-
-func Test_multibyte()
- " using an invalid character should not cause a crash
- set wic
- call assert_fails('tc û¦*', has('win32') ? 'E480:' : 'E344:')
- set nowic
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
deleted file mode 100644
index 83af0f6be0..0000000000
--- a/src/nvim/testdir/test_autocmd.vim
+++ /dev/null
@@ -1,3572 +0,0 @@
-" Tests for autocommands
-
-source shared.vim
-source check.vim
-source term_util.vim
-source screendump.vim
-source load.vim
-
-func s:cleanup_buffers() abort
- for bnr in range(1, bufnr('$'))
- if bufloaded(bnr) && bufnr('%') != bnr
- execute 'bd! ' . bnr
- endif
- endfor
-endfunc
-
-func Test_vim_did_enter()
- call assert_false(v:vim_did_enter)
-
- " This script will never reach the main loop, can't check if v:vim_did_enter
- " becomes one.
-endfunc
-
-" Test for the CursorHold autocmd
-func Test_CursorHold_autocmd()
- CheckRunVimInTerminal
- call writefile(['one', 'two', 'three'], 'Xfile')
- let before =<< trim END
- set updatetime=10
- au CursorHold * call writefile([line('.')], 'Xoutput', 'a')
- END
- call writefile(before, 'Xinit')
- let buf = RunVimInTerminal('-S Xinit Xfile', {})
- call term_sendkeys(buf, "G")
- call term_wait(buf, 20)
- call term_sendkeys(buf, "gg")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal(['1'], readfile('Xoutput')[-1:-1])})
- call term_sendkeys(buf, "j")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal(['1', '2'], readfile('Xoutput')[-2:-1])})
- call term_sendkeys(buf, "j")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal(['1', '2', '3'], readfile('Xoutput')[-3:-1])})
- call StopVimInTerminal(buf)
-
- call delete('Xinit')
- call delete('Xoutput')
- call delete('Xfile')
-endfunc
-
-if has('timers')
-
- func ExitInsertMode(id)
- call feedkeys("\<Esc>")
- endfunc
-
- func Test_cursorhold_insert()
- " Need to move the cursor.
- call feedkeys("ggG", "xt")
-
- let g:triggered = 0
- au CursorHoldI * let g:triggered += 1
- set updatetime=20
- call timer_start(LoadAdjust(200), 'ExitInsertMode')
- call feedkeys('a', 'x!')
- call assert_equal(1, g:triggered)
- unlet g:triggered
- au! CursorHoldI
- set updatetime&
- endfunc
-
- func Test_cursorhold_insert_with_timer_interrupt()
- CheckFeature job
- " Need to move the cursor.
- call feedkeys("ggG", "xt")
-
- " Confirm the timer invoked in exit_cb of the job doesn't disturb
- " CursorHoldI event.
- let g:triggered = 0
- au CursorHoldI * let g:triggered += 1
- set updatetime=500
- call job_start(has('win32') ? 'cmd /c echo:' : 'echo',
- \ {'exit_cb': {-> timer_start(1000, 'ExitInsertMode')}})
- call feedkeys('a', 'x!')
- call assert_equal(1, g:triggered)
- unlet g:triggered
- au! CursorHoldI
- set updatetime&
- endfunc
-
- func Test_cursorhold_insert_ctrl_x()
- let g:triggered = 0
- au CursorHoldI * let g:triggered += 1
- set updatetime=20
- call timer_start(LoadAdjust(100), 'ExitInsertMode')
- " CursorHoldI does not trigger after CTRL-X
- call feedkeys("a\<C-X>", 'x!')
- call assert_equal(0, g:triggered)
- unlet g:triggered
- au! CursorHoldI
- set updatetime&
- endfunc
-
- func Test_OptionSet_modeline()
- CheckFunction test_override
- call test_override('starting', 1)
- au! OptionSet
- augroup set_tabstop
- au OptionSet tabstop call timer_start(1, {-> execute("echo 'Handler called'", "")})
- augroup END
- call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline')
- set modeline
- let v:errmsg = ''
- call assert_fails('split XoptionsetModeline', 'E12:')
- call assert_equal(7, &ts)
- call assert_equal('', v:errmsg)
-
- augroup set_tabstop
- au!
- augroup END
- bwipe!
- set ts&
- call delete('XoptionsetModeline')
- call test_override('starting', 0)
- endfunc
-
-endif "has('timers')
-
-func Test_bufunload()
- augroup test_bufunload_group
- autocmd!
- autocmd BufUnload * call add(s:li, "bufunload")
- autocmd BufDelete * call add(s:li, "bufdelete")
- autocmd BufWipeout * call add(s:li, "bufwipeout")
- augroup END
-
- let s:li = []
- new
- setlocal bufhidden=
- bunload
- call assert_equal(["bufunload", "bufdelete"], s:li)
-
- let s:li = []
- new
- setlocal bufhidden=delete
- bunload
- call assert_equal(["bufunload", "bufdelete"], s:li)
-
- let s:li = []
- new
- setlocal bufhidden=unload
- bwipeout
- call assert_equal(["bufunload", "bufdelete", "bufwipeout"], s:li)
-
- au! test_bufunload_group
- augroup! test_bufunload_group
-endfunc
-
-" SEGV occurs in older versions. (At least 7.4.2005 or older)
-func Test_autocmd_bufunload_with_tabnext()
- tabedit
- tabfirst
-
- augroup test_autocmd_bufunload_with_tabnext_group
- autocmd!
- autocmd BufUnload <buffer> tabnext
- augroup END
-
- quit
- call assert_equal(2, tabpagenr('$'))
-
- autocmd! test_autocmd_bufunload_with_tabnext_group
- augroup! test_autocmd_bufunload_with_tabnext_group
- tablast
- quit
-endfunc
-
-func Test_argdelete_in_next()
- au BufNew,BufEnter,BufLeave,BufWinEnter * argdel
- call assert_fails('next a b', 'E1156:')
- au! BufNew,BufEnter,BufLeave,BufWinEnter *
-endfunc
-
-func Test_autocmd_bufwinleave_with_tabfirst()
- tabedit
- augroup sample
- autocmd!
- autocmd BufWinLeave <buffer> tabfirst
- augroup END
- call setline(1, ['a', 'b', 'c'])
- edit! a.txt
- tabclose
-endfunc
-
-" SEGV occurs in older versions. (At least 7.4.2321 or older)
-func Test_autocmd_bufunload_avoiding_SEGV_01()
- split aa.txt
- let lastbuf = bufnr('$')
-
- augroup test_autocmd_bufunload
- autocmd!
- exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!'
- augroup END
-
- call assert_fails('edit bb.txt', 'E937:')
-
- autocmd! test_autocmd_bufunload
- augroup! test_autocmd_bufunload
- bwipe! aa.txt
- bwipe! bb.txt
-endfunc
-
-" SEGV occurs in older versions. (At least 7.4.2321 or older)
-func Test_autocmd_bufunload_avoiding_SEGV_02()
- setlocal buftype=nowrite
- let lastbuf = bufnr('$')
-
- augroup test_autocmd_bufunload
- autocmd!
- exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!'
- augroup END
-
- normal! i1
- call assert_fails('edit a.txt', 'E517:')
-
- autocmd! test_autocmd_bufunload
- augroup! test_autocmd_bufunload
- bwipe! a.txt
-endfunc
-
-func Test_autocmd_dummy_wipeout()
- " prepare files
- call writefile([''], 'Xdummywipetest1.txt')
- call writefile([''], 'Xdummywipetest2.txt')
- augroup test_bufunload_group
- autocmd!
- autocmd BufUnload * call add(s:li, "bufunload")
- autocmd BufDelete * call add(s:li, "bufdelete")
- autocmd BufWipeout * call add(s:li, "bufwipeout")
- augroup END
-
- let s:li = []
- split Xdummywipetest1.txt
- silent! vimgrep /notmatched/ Xdummywipetest*
- call assert_equal(["bufunload", "bufwipeout"], s:li)
-
- bwipeout
- call delete('Xdummywipetest1.txt')
- call delete('Xdummywipetest2.txt')
- au! test_bufunload_group
- augroup! test_bufunload_group
-endfunc
-
-func Test_win_tab_autocmd()
- let g:record = []
-
- augroup testing
- au WinNew * call add(g:record, 'WinNew')
- au WinClosed * call add(g:record, 'WinClosed')
- au WinEnter * call add(g:record, 'WinEnter')
- au WinLeave * call add(g:record, 'WinLeave')
- au TabNew * call add(g:record, 'TabNew')
- au TabClosed * call add(g:record, 'TabClosed')
- au TabEnter * call add(g:record, 'TabEnter')
- au TabLeave * call add(g:record, 'TabLeave')
- augroup END
-
- split
- tabnew
- close
- close
-
- call assert_equal([
- \ 'WinLeave', 'WinNew', 'WinEnter',
- \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
- \ 'WinLeave', 'TabLeave', 'WinClosed', 'TabClosed', 'WinEnter', 'TabEnter',
- \ 'WinLeave', 'WinClosed', 'WinEnter'
- \ ], g:record)
-
- let g:record = []
- tabnew somefile
- tabnext
- bwipe somefile
-
- call assert_equal([
- \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
- \ 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter',
- \ 'WinClosed', 'TabClosed'
- \ ], g:record)
-
- augroup testing
- au!
- augroup END
- unlet g:record
-endfunc
-
-func Test_WinResized()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set scrolloff=0
- call setline(1, ['111', '222'])
- vnew
- call setline(1, ['aaa', 'bbb'])
- new
- call setline(1, ['foo', 'bar'])
-
- let g:resized = 0
- au WinResized * let g:resized += 1
-
- func WriteResizedEvent()
- call writefile([json_encode(v:event)], 'XresizeEvent')
- endfunc
- au WinResized * call WriteResizedEvent()
- END
- call writefile(lines, 'Xtest_winresized', 'D')
- let buf = RunVimInTerminal('-S Xtest_winresized', {'rows': 10})
-
- " redraw now to avoid a redraw after the :echo command
- call term_sendkeys(buf, ":redraw!\<CR>")
- call TermWait(buf)
-
- call term_sendkeys(buf, ":echo g:resized\<CR>")
- call WaitForAssert({-> assert_match('^0$', term_getline(buf, 10))}, 1000)
-
- " increase window height, two windows will be reported
- call term_sendkeys(buf, "\<C-W>+")
- call TermWait(buf)
- call term_sendkeys(buf, ":echo g:resized\<CR>")
- call WaitForAssert({-> assert_match('^1$', term_getline(buf, 10))}, 1000)
-
- let event = readfile('XresizeEvent')[0]->json_decode()
- call assert_equal({
- \ 'windows': [1002, 1001],
- \ }, event)
-
- " increase window width, three windows will be reported
- call term_sendkeys(buf, "\<C-W>>")
- call TermWait(buf)
- call term_sendkeys(buf, ":echo g:resized\<CR>")
- call WaitForAssert({-> assert_match('^2$', term_getline(buf, 10))}, 1000)
-
- let event = readfile('XresizeEvent')[0]->json_decode()
- call assert_equal({
- \ 'windows': [1002, 1001, 1000],
- \ }, event)
-
- call delete('XresizeEvent')
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_WinScrolled()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set nowrap scrolloff=0
- for ii in range(1, 18)
- call setline(ii, repeat(nr2char(96 + ii), ii * 2))
- endfor
- let win_id = win_getid()
- let g:matched = v:false
- func WriteScrollEvent()
- call writefile([json_encode(v:event)], 'XscrollEvent')
- endfunc
- execute 'au WinScrolled' win_id 'let g:matched = v:true'
- let g:scrolled = 0
- au WinScrolled * let g:scrolled += 1
- au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
- au WinScrolled * let g:afile = str2nr(expand('<afile>'))
- au WinScrolled * call WriteScrollEvent()
- END
- call writefile(lines, 'Xtest_winscrolled', 'D')
- let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6})
-
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000)
-
- " Scroll left/right in Normal mode.
- call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': -1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- " Scroll up/down in Normal mode.
- call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- " Scroll up/down in Insert mode.
- call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>")
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- " Scroll the window horizontally to focus the last letter of the third line
- " containing only six characters. Moving to the previous and shorter lines
- " should trigger another autocommand as Vim has to make them visible.
- call term_sendkeys(buf, "5zl2k")
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': -5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- " Ensure the command was triggered for the specified window ID.
- call term_sendkeys(buf, ":echo g:matched\<CR>")
- call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
-
- " Ensure the expansion of <amatch> and <afile> matches the window ID.
- call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>")
- call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
-
- call delete('XscrollEvent')
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_WinScrolled_mouse()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set nowrap scrolloff=0
- set mouse=a term=xterm ttymouse=sgr mousetime=200 clipboard=
- call setline(1, ['foo']->repeat(32))
- split
- let g:scrolled = 0
- au WinScrolled * let g:scrolled += 1
- END
- call writefile(lines, 'Xtest_winscrolled_mouse', 'D')
- let buf = RunVimInTerminal('-S Xtest_winscrolled_mouse', {'rows': 10})
-
- " With the upper split focused, send a scroll-down event to the unfocused one.
- call test_setmouse(7, 1)
- call term_sendkeys(buf, "\<ScrollWheelDown>")
- call TermWait(buf)
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^1', term_getline(buf, 10))}, 1000)
-
- " Again, but this time while we're in insert mode.
- call term_sendkeys(buf, "i\<ScrollWheelDown>\<Esc>")
- call TermWait(buf)
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^2', term_getline(buf, 10))}, 1000)
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_WinScrolled_close_curwin()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set nowrap scrolloff=0
- call setline(1, ['aaa', 'bbb'])
- vsplit
- au WinScrolled * close
- au VimLeave * call writefile(['123456'], 'Xtestout')
- END
- call writefile(lines, 'Xtest_winscrolled_close_curwin', 'D')
- let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6})
-
- " This was using freed memory
- call term_sendkeys(buf, "\<C-E>")
- call TermWait(buf)
- call StopVimInTerminal(buf)
-
- " check the startup script finished to the end
- call assert_equal(['123456'], readfile('Xtestout'))
- call delete('Xtestout')
-endfunc
-
-func Test_WinScrolled_once_only()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set cmdheight=2
- call setline(1, ['aaa', 'bbb'])
- let trigger_count = 0
- func ShowInfo(id)
- echo g:trigger_count g:winid winlayout()
- endfunc
-
- vsplit
- split
- " use a timer to show the info after a redraw
- au WinScrolled * let trigger_count += 1 | let winid = expand('<amatch>') | call timer_start(100, 'ShowInfo')
- wincmd j
- wincmd l
- END
- call writefile(lines, 'Xtest_winscrolled_once', 'D')
- let buf = RunVimInTerminal('-S Xtest_winscrolled_once', #{rows: 10, cols: 60, statusoff: 2})
-
- call term_sendkeys(buf, "\<C-E>")
- call VerifyScreenDump(buf, 'Test_winscrolled_once_only_1', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Check that WinScrolled is not triggered immediately when defined and there
-" are split windows.
-func Test_WinScrolled_not_when_defined()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- call setline(1, ['aaa', 'bbb'])
- echo 'nothing happened'
- func ShowTriggered(id)
- echo 'triggered'
- endfunc
- END
- call writefile(lines, 'Xtest_winscrolled_not', 'D')
- let buf = RunVimInTerminal('-S Xtest_winscrolled_not', #{rows: 10, cols: 60, statusoff: 2})
- call term_sendkeys(buf, ":split\<CR>")
- call TermWait(buf)
- " use a timer to show the message after redrawing
- call term_sendkeys(buf, ":au WinScrolled * call timer_start(100, 'ShowTriggered')\<CR>")
- call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_1', {})
-
- call term_sendkeys(buf, "\<C-E>")
- call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_2', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_WinScrolled_long_wrapped()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set scrolloff=0
- let height = winheight(0)
- let width = winwidth(0)
- let g:scrolled = 0
- au WinScrolled * let g:scrolled += 1
- call setline(1, repeat('foo', height * width))
- call cursor(1, height * width)
- END
- call writefile(lines, 'Xtest_winscrolled_long_wrapped', 'D')
- let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6})
-
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000)
-
- call term_sendkeys(buf, 'gj')
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^1 ', term_getline(buf, 6))}, 1000)
-
- call term_sendkeys(buf, '0')
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
-
- call term_sendkeys(buf, '$')
- call term_sendkeys(buf, ":echo g:scrolled\<CR>")
- call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000)
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_WinScrolled_diff()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set diffopt+=foldcolumn:0
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
- vnew
- call setline(1, ['d', 'e', 'f', 'g', 'h', 'i'])
- windo diffthis
- func WriteScrollEvent()
- call writefile([json_encode(v:event)], 'XscrollEvent')
- endfunc
- au WinScrolled * call WriteScrollEvent()
- END
- call writefile(lines, 'Xtest_winscrolled_diff', 'D')
- let buf = RunVimInTerminal('-S Xtest_winscrolled_diff', {'rows': 8})
-
- call term_sendkeys(buf, "\<C-E>")
- call WaitForAssert({-> assert_match('^d', term_getline(buf, 3))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -1, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- call term_sendkeys(buf, "2\<C-E>")
- call WaitForAssert({-> assert_match('^f', term_getline(buf, 3))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 2, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -2, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- call term_sendkeys(buf, "\<C-E>")
- call WaitForAssert({-> assert_match('^g', term_getline(buf, 3))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1001': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- call term_sendkeys(buf, "2\<C-Y>")
- call WaitForAssert({-> assert_match('^e', term_getline(buf, 3))}, 1000)
-
- let event = readfile('XscrollEvent')[0]->json_decode()
- call assert_equal({
- \ 'all': {'leftcol': 0, 'topline': 3, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1000': {'leftcol': 0, 'topline': -2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
- \ '1001': {'leftcol': 0, 'topline': -1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0}
- \ }, event)
-
- call StopVimInTerminal(buf)
- call delete('XscrollEvent')
-endfunc
-
-func Test_WinClosed()
- " Test that the pattern is matched against the closed window's ID, and both
- " <amatch> and <afile> are set to it.
- new
- let winid = win_getid()
- let g:matched = v:false
- augroup test-WinClosed
- autocmd!
- execute 'autocmd WinClosed' winid 'let g:matched = v:true'
- autocmd WinClosed * let g:amatch = str2nr(expand('<amatch>'))
- autocmd WinClosed * let g:afile = str2nr(expand('<afile>'))
- augroup END
- close
- call assert_true(g:matched)
- call assert_equal(winid, g:amatch)
- call assert_equal(winid, g:afile)
-
- " Test that WinClosed is non-recursive.
- new
- new
- call assert_equal(3, winnr('$'))
- let g:triggered = 0
- augroup test-WinClosed
- autocmd!
- autocmd WinClosed * let g:triggered += 1
- autocmd WinClosed * 2 wincmd c
- augroup END
- close
- call assert_equal(1, winnr('$'))
- call assert_equal(1, g:triggered)
-
- autocmd! test-WinClosed
- augroup! test-WinClosed
- unlet g:matched
- unlet g:amatch
- unlet g:afile
- unlet g:triggered
-endfunc
-
-func Test_WinClosed_throws()
- vnew
- let bnr = bufnr()
- call assert_equal(1, bufloaded(bnr))
- augroup test-WinClosed
- autocmd WinClosed * throw 'foo'
- augroup END
- try
- close
- catch /.*/
- endtry
- call assert_equal(0, bufloaded(bnr))
-
- autocmd! test-WinClosed
- augroup! test-WinClosed
-endfunc
-
-func Test_WinClosed_throws_with_tabs()
- tabnew
- let bnr = bufnr()
- call assert_equal(1, bufloaded(bnr))
- augroup test-WinClosed
- autocmd WinClosed * throw 'foo'
- augroup END
- try
- close
- catch /.*/
- endtry
- call assert_equal(0, bufloaded(bnr))
-
- autocmd! test-WinClosed
- augroup! test-WinClosed
-endfunc
-
-" This used to trigger WinClosed twice for the same window, and the window's
-" buffer was NULL in the second autocommand.
-func Test_WinClosed_switch_tab()
- edit Xa
- split Xb
- split Xc
- tab split
- new
- augroup test-WinClosed
- autocmd WinClosed * tabprev | bwipe!
- augroup END
- close
- " Check that the tabline has been fully removed
- call assert_equal([1, 1], win_screenpos(0))
-
- autocmd! test-WinClosed
- augroup! test-WinClosed
- %bwipe!
-endfunc
-
-func s:AddAnAutocmd()
- augroup vimBarTest
- au BufReadCmd * echo 'hello'
- augroup END
- call assert_equal(3, len(split(execute('au vimBarTest'), "\n")))
-endfunc
-
-func Test_early_bar()
- " test that a bar is recognized before the {event}
- call s:AddAnAutocmd()
- augroup vimBarTest | au! | let done = 77 | augroup END
- call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
- call assert_equal(77, done)
-
- call s:AddAnAutocmd()
- augroup vimBarTest| au!| let done = 88 | augroup END
- call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
- call assert_equal(88, done)
-
- " test that a bar is recognized after the {event}
- call s:AddAnAutocmd()
- augroup vimBarTest| au!BufReadCmd| let done = 99 | augroup END
- call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
- call assert_equal(99, done)
-
- " test that a bar is recognized after the {group}
- call s:AddAnAutocmd()
- au! vimBarTest|echo 'hello'
- call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
-endfunc
-
-func RemoveGroup()
- autocmd! StartOK
- augroup! StartOK
-endfunc
-
-func Test_augroup_warning()
- augroup TheWarning
- au VimEnter * echo 'entering'
- augroup END
- call assert_match("TheWarning.*VimEnter", execute('au VimEnter'))
- redir => res
- augroup! TheWarning
- redir END
- call assert_match("W19:", res)
- call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
-
- " check "Another" does not take the pace of the deleted entry
- augroup Another
- augroup END
- call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
- augroup! Another
-
- " no warning for postpone aucmd delete
- augroup StartOK
- au VimEnter * call RemoveGroup()
- augroup END
- call assert_match("StartOK.*VimEnter", execute('au VimEnter'))
- redir => res
- doautocmd VimEnter
- redir END
- call assert_notmatch("W19:", res)
- au! VimEnter
-
- call assert_fails('augroup!', 'E471:')
-endfunc
-
-func Test_BufReadCmdHelp()
- " This used to cause access to free memory
- au BufReadCmd * e +h
- help
-
- au! BufReadCmd
-endfunc
-
-func Test_BufReadCmdHelpJump()
- " This used to cause access to free memory
- au BufReadCmd * e +h{
- " } to fix highlighting
- call assert_fails('help', 'E434:')
-
- au! BufReadCmd
-endfunc
-
-" BufReadCmd is triggered for a "nofile" buffer. Check all values.
-func Test_BufReadCmdNofile()
- for val in ['nofile',
- \ 'nowrite',
- \ 'acwrite',
- \ 'quickfix',
- \ 'help',
- "\ 'terminal',
- \ 'prompt',
- "\ 'popup',
- \ ]
- new somefile
- exe 'set buftype=' .. val
- au BufReadCmd somefile call setline(1, 'triggered')
- edit
- call assert_equal('triggered', getline(1))
-
- au! BufReadCmd
- bwipe!
- endfor
-endfunc
-
-func Test_augroup_deleted()
- " This caused a crash before E936 was introduced
- augroup x
- call assert_fails('augroup! x', 'E936:')
- au VimEnter * echo
- augroup end
- augroup! x
- call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
- au! VimEnter
-endfunc
-
-" Tests for autocommands on :close command.
-" This used to be in test13.
-func Test_three_windows()
- " Clean up buffers, because in some cases this function fails.
- call s:cleanup_buffers()
-
- " Write three files and open them, each in a window.
- " Then go to next window, with autocommand that deletes the previous one.
- " Do this twice, writing the file.
- e! Xtestje1
- call setline(1, 'testje1')
- w
- sp Xtestje2
- call setline(1, 'testje2')
- w
- sp Xtestje3
- call setline(1, 'testje3')
- w
- wincmd w
- au WinLeave Xtestje2 bwipe
- wincmd w
- call assert_equal('Xtestje1', expand('%'))
-
- au WinLeave Xtestje1 bwipe Xtestje3
- close
- call assert_equal('Xtestje1', expand('%'))
-
- " Test deleting the buffer on a Unload event. If this goes wrong there
- " will be the ATTENTION prompt.
- e Xtestje1
- au!
- au! BufUnload Xtestje1 bwipe
- call assert_fails('e Xtestje3', 'E937:')
- call assert_equal('Xtestje3', expand('%'))
-
- e Xtestje2
- sp Xtestje1
- call assert_fails('e', 'E937:')
- call assert_equal('Xtestje1', expand('%'))
-
- " Test changing buffers in a BufWipeout autocommand. If this goes wrong
- " there are ml_line errors and/or a Crash.
- au!
- only
- e Xanother
- e Xtestje1
- bwipe Xtestje2
- bwipe Xtestje3
- au BufWipeout Xtestje1 buf Xtestje1
- bwipe
- call assert_equal('Xanother', expand('%'))
-
- only
-
- help
- wincmd w
- 1quit
- call assert_equal('Xanother', expand('%'))
-
- au!
- enew
- call delete('Xtestje1')
- call delete('Xtestje2')
- call delete('Xtestje3')
-endfunc
-
-func Test_BufEnter()
- au! BufEnter
- au Bufenter * let val = val . '+'
- let g:val = ''
- split NewFile
- call assert_equal('+', g:val)
- bwipe!
- call assert_equal('++', g:val)
-
- " Also get BufEnter when editing a directory
- call mkdir('Xdir')
- split Xdir
- call assert_equal('+++', g:val)
-
- " On MS-Windows we can't edit the directory, make sure we wipe the right
- " buffer.
- bwipe! Xdir
- call delete('Xdir', 'd')
- au! BufEnter
-
- " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
- " for historic reasons. Also test other 'buftype' values.
- for val in ['nofile',
- \ 'nowrite',
- \ 'acwrite',
- \ 'quickfix',
- \ 'help',
- "\ 'terminal',
- \ 'prompt',
- "\ 'popup',
- \ ]
- new somefile
- exe 'set buftype=' .. val
- au BufEnter somefile call setline(1, 'some text')
- edit
- call assert_equal('some text', getline(1))
- bwipe!
- au! BufEnter
- endfor
-endfunc
-
-" Closing a window might cause an endless loop
-" E814 for older Vims
-func Test_autocmd_bufwipe_in_SessLoadPost()
- edit Xtest
- tabnew
- file Xsomething
- set noswapfile
- mksession!
-
- 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(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
- let errors = join(readfile('Xerrors'))
- call assert_match('E814', errors)
-
- set swapfile
- for file in ['Session.vim', 'Xvimrc', 'Xerrors']
- call delete(file)
- endfor
-endfunc
-
-" Using :blast and :ball for many events caused a crash, because b_nwindows was
-" not incremented correctly.
-func Test_autocmd_blast_badd()
- let content =<< trim [CODE]
- au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast
- edit foo1
- au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball
- edit foo2
- call writefile(['OK'], 'Xerrors')
- qall
- [CODE]
-
- call writefile(content, 'XblastBall')
- call system(GetVimCommand() .. ' --clean -S XblastBall')
- call assert_match('OK', readfile('Xerrors')->join())
-
- call delete('XblastBall')
- call delete('Xerrors')
-endfunc
-
-" SEGV occurs in older versions.
-func Test_autocmd_bufwipe_in_SessLoadPost2()
- tabnew
- set noswapfile
- mksession!
-
- 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(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
- let errors = join(readfile('Xerrors'))
- " This probably only ever matches on unix.
- call assert_notmatch('Caught deadly signal SEGV', errors)
- call assert_match('SessionLoadPost DONE', errors)
-
- set swapfile
- for file in ['Session.vim', 'Xvimrc', 'Xerrors']
- call delete(file)
- endfor
-endfunc
-
-func Test_empty_doau()
- doau \|
-endfunc
-
-func s:AutoCommandOptionSet(match)
- let template = "Option: <%s>, OldVal: <%s>, OldValLocal: <%s>, OldValGlobal: <%s>, NewVal: <%s>, Scope: <%s>, Command: <%s>\n"
- let item = remove(g:options, 0)
- let expected = printf(template, item[0], item[1], item[2], item[3], item[4], item[5], item[6])
- let actual = printf(template, a:match, v:option_old, v:option_oldlocal, v:option_oldglobal, v:option_new, v:option_type, v:option_command)
- let g:opt = [expected, actual]
- "call assert_equal(expected, actual)
-endfunc
-
-func Test_OptionSet()
- CheckFunction test_override
- CheckOption autochdir
-
- call test_override('starting', 1)
- set nocp
- au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>"))
-
- " 1: Setting number option"
- let g:options = [['number', 0, 0, 0, 1, 'global', 'set']]
- set nu
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 2: Setting local number option"
- let g:options = [['number', 1, 1, '', 0, 'local', 'setlocal']]
- setlocal nonu
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 3: Setting global number option"
- let g:options = [['number', 1, '', 1, 0, 'global', 'setglobal']]
- setglobal nonu
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 4: Setting local autoindent option"
- let g:options = [['autoindent', 0, 0, '', 1, 'local', 'setlocal']]
- setlocal ai
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 5: Setting global autoindent option"
- let g:options = [['autoindent', 0, '', 0, 1, 'global', 'setglobal']]
- setglobal ai
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 6: Setting global autoindent option"
- let g:options = [['autoindent', 1, 1, 1, 0, 'global', 'set']]
- set ai!
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 6a: Setting global autoindent option"
- let g:options = [['autoindent', 1, 1, 0, 0, 'global', 'set']]
- noa setlocal ai
- noa setglobal noai
- set ai!
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " Should not print anything, use :noa
- " 7: don't trigger OptionSet"
- let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']]
- noa set nonu
- call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 8: Setting several global list and number option"
- let g:options = [['list', 0, 0, 0, 1, 'global', 'set'], ['number', 0, 0, 0, 1, 'global', 'set']]
- set list nu
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 9: don't trigger OptionSet"
- let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']]
- noa set nolist nonu
- call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 10: Setting global acd"
- let g:options = [['autochdir', 0, 0, '', 1, 'local', 'setlocal']]
- setlocal acd
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 11: Setting global autoread (also sets local value)"
- let g:options = [['autoread', 0, 0, 0, 1, 'global', 'set']]
- set ar
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 12: Setting local autoread"
- let g:options = [['autoread', 1, 1, '', 1, 'local', 'setlocal']]
- setlocal ar
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 13: Setting global autoread"
- let g:options = [['autoread', 1, '', 1, 0, 'global', 'setglobal']]
- setglobal invar
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 14: Setting option backspace through :let"
- let g:options = [['backspace', '', '', '', 'eol,indent,start', 'global', 'set']]
- let &bs = "eol,indent,start"
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 15: Setting option backspace through setbufvar()"
- let g:options = [['backup', 0, 0, '', 1, 'local', 'setlocal']]
- " try twice, first time, shouldn't trigger because option name is invalid,
- " second time, it should trigger
- let bnum = bufnr('%')
- call assert_fails("call setbufvar(bnum, '&l:bk', 1)", 'E355:')
- " should trigger, use correct option name
- call setbufvar(bnum, '&backup', 1)
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 16: Setting number option using setwinvar"
- let g:options = [['number', 0, 0, '', 1, 'local', 'setlocal']]
- call setwinvar(0, '&number', 1)
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 17: Setting key option, shouldn't trigger"
- let g:options = [['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']]
- setlocal key=blah
- setlocal key=
- call assert_equal([['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 18a: Setting string global option"
- let oldval = &backupext
- let g:options = [['backupext', oldval, oldval, oldval, 'foo', 'global', 'set']]
- set backupext=foo
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 18b: Resetting string global option"
- let g:options = [['backupext', 'foo', 'foo', 'foo', oldval, 'global', 'set']]
- set backupext&
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 18c: Setting global string global option"
- let g:options = [['backupext', oldval, '', oldval, 'bar', 'global', 'setglobal']]
- setglobal backupext=bar
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 18d: Setting local string global option"
- " As this is a global option this sets the global value even though
- " :setlocal is used!
- noa set backupext& " Reset global and local value (without triggering autocmd)
- let g:options = [['backupext', oldval, oldval, '', 'baz', 'local', 'setlocal']]
- setlocal backupext=baz
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 18e: Setting again string global option"
- noa setglobal backupext=ext_global " Reset global and local value (without triggering autocmd)
- noa setlocal backupext=ext_local " Sets the global(!) value!
- let g:options = [['backupext', 'ext_local', 'ext_local', 'ext_local', 'fuu', 'global', 'set']]
- set backupext=fuu
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 19a: Setting string global-local (to buffer) option"
- let oldval = &tags
- let g:options = [['tags', oldval, oldval, oldval, 'tagpath', 'global', 'set']]
- set tags=tagpath
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 19b: Resetting string global-local (to buffer) option"
- let g:options = [['tags', 'tagpath', 'tagpath', 'tagpath', oldval, 'global', 'set']]
- set tags&
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 19c: Setting global string global-local (to buffer) option "
- let g:options = [['tags', oldval, '', oldval, 'tagpath1', 'global', 'setglobal']]
- setglobal tags=tagpath1
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 19d: Setting local string global-local (to buffer) option"
- let g:options = [['tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal']]
- setlocal tags=tagpath2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 19e: Setting again string global-local (to buffer) option"
- " Note: v:option_old is the old global value for global-local string options
- " but the old local value for all other kinds of options.
- noa setglobal tags=tag_global " Reset global and local value (without triggering autocmd)
- noa setlocal tags=tag_local
- let g:options = [['tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set']]
- set tags=tagpath
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 19f: Setting string global-local (to buffer) option to an empty string"
- " Note: v:option_old is the old global value for global-local string options
- " but the old local value for all other kinds of options.
- noa set tags=tag_global " Reset global and local value (without triggering autocmd)
- noa setlocal tags= " empty string
- let g:options = [['tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set']]
- set tags=tagpath
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 20a: Setting string local (to buffer) option"
- let oldval = &spelllang
- let g:options = [['spelllang', oldval, oldval, oldval, 'elvish,klingon', 'global', 'set']]
- set spelllang=elvish,klingon
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 20b: Resetting string local (to buffer) option"
- let g:options = [['spelllang', 'elvish,klingon', 'elvish,klingon', 'elvish,klingon', oldval, 'global', 'set']]
- set spelllang&
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 20c: Setting global string local (to buffer) option"
- let g:options = [['spelllang', oldval, '', oldval, 'elvish', 'global', 'setglobal']]
- setglobal spelllang=elvish
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 20d: Setting local string local (to buffer) option"
- noa set spelllang& " Reset global and local value (without triggering autocmd)
- let g:options = [['spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal']]
- setlocal spelllang=klingon
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 20e: Setting again string local (to buffer) option"
- " Note: v:option_old is the old global value for global-local string options
- " but the old local value for all other kinds of options.
- noa setglobal spelllang=spellglobal " Reset global and local value (without triggering autocmd)
- noa setlocal spelllang=spelllocal
- let g:options = [['spelllang', 'spelllocal', 'spelllocal', 'spellglobal', 'foo', 'global', 'set']]
- set spelllang=foo
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 21a: Setting string global-local (to window) option"
- let oldval = &statusline
- let g:options = [['statusline', oldval, oldval, oldval, 'foo', 'global', 'set']]
- set statusline=foo
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 21b: Resetting string global-local (to window) option"
- " Note: v:option_old is the old global value for global-local string options
- " but the old local value for all other kinds of options.
- let g:options = [['statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set']]
- set statusline&
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 21c: Setting global string global-local (to window) option"
- let g:options = [['statusline', oldval, '', oldval, 'bar', 'global', 'setglobal']]
- setglobal statusline=bar
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 21d: Setting local string global-local (to window) option"
- noa set statusline& " Reset global and local value (without triggering autocmd)
- let g:options = [['statusline', oldval, oldval, '', 'baz', 'local', 'setlocal']]
- setlocal statusline=baz
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 21e: Setting again string global-local (to window) option"
- " Note: v:option_old is the old global value for global-local string options
- " but the old local value for all other kinds of options.
- noa setglobal statusline=bar " Reset global and local value (without triggering autocmd)
- noa setlocal statusline=baz
- let g:options = [['statusline', 'bar', 'baz', 'bar', 'foo', 'global', 'set']]
- set statusline=foo
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 22a: Setting string local (to window) option"
- let oldval = &foldignore
- let g:options = [['foldignore', oldval, oldval, oldval, 'fo', 'global', 'set']]
- set foldignore=fo
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 22b: Resetting string local (to window) option"
- let g:options = [['foldignore', 'fo', 'fo', 'fo', oldval, 'global', 'set']]
- set foldignore&
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 22c: Setting global string local (to window) option"
- let g:options = [['foldignore', oldval, '', oldval, 'bar', 'global', 'setglobal']]
- setglobal foldignore=bar
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 22d: Setting local string local (to window) option"
- noa set foldignore& " Reset global and local value (without triggering autocmd)
- let g:options = [['foldignore', oldval, oldval, '', 'baz', 'local', 'setlocal']]
- setlocal foldignore=baz
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 22e: Setting again string local (to window) option"
- noa setglobal foldignore=glob " Reset global and local value (without triggering autocmd)
- noa setlocal foldignore=loc
- let g:options = [['foldignore', 'loc', 'loc', 'glob', 'fo', 'global', 'set']]
- set foldignore=fo
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 23a: Setting global number global option"
- noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd)
- noa setlocal cmdheight=1 " Sets the global(!) value!
- let g:options = [['cmdheight', '1', '', '1', '2', 'global', 'setglobal']]
- setglobal cmdheight=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 23b: Setting local number global option"
- noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd)
- noa setlocal cmdheight=1 " Sets the global(!) value!
- let g:options = [['cmdheight', '1', '1', '', '2', 'local', 'setlocal']]
- setlocal cmdheight=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 23c: Setting again number global option"
- noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd)
- noa setlocal cmdheight=1 " Sets the global(!) value!
- let g:options = [['cmdheight', '1', '1', '1', '2', 'global', 'set']]
- set cmdheight=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 23d: Setting again number global option"
- noa set cmdheight=8 " Reset global and local value (without triggering autocmd)
- let g:options = [['cmdheight', '8', '8', '8', '2', 'global', 'set']]
- set cmdheight=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 24a: Setting global number global-local (to buffer) option"
- noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd)
- noa setlocal undolevels=1
- let g:options = [['undolevels', '8', '', '8', '2', 'global', 'setglobal']]
- setglobal undolevels=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 24b: Setting local number global-local (to buffer) option"
- noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd)
- noa setlocal undolevels=1
- let g:options = [['undolevels', '1', '1', '', '2', 'local', 'setlocal']]
- setlocal undolevels=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 24c: Setting again number global-local (to buffer) option"
- noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd)
- noa setlocal undolevels=1
- let g:options = [['undolevels', '1', '1', '8', '2', 'global', 'set']]
- set undolevels=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 24d: Setting again global number global-local (to buffer) option"
- noa set undolevels=8 " Reset global and local value (without triggering autocmd)
- let g:options = [['undolevels', '8', '8', '8', '2', 'global', 'set']]
- set undolevels=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 25a: Setting global number local (to buffer) option"
- noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd)
- noa setlocal wrapmargin=1
- let g:options = [['wrapmargin', '8', '', '8', '2', 'global', 'setglobal']]
- setglobal wrapmargin=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 25b: Setting local number local (to buffer) option"
- noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd)
- noa setlocal wrapmargin=1
- let g:options = [['wrapmargin', '1', '1', '', '2', 'local', 'setlocal']]
- setlocal wrapmargin=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 25c: Setting again number local (to buffer) option"
- noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd)
- noa setlocal wrapmargin=1
- let g:options = [['wrapmargin', '1', '1', '8', '2', 'global', 'set']]
- set wrapmargin=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 25d: Setting again global number local (to buffer) option"
- noa set wrapmargin=8 " Reset global and local value (without triggering autocmd)
- let g:options = [['wrapmargin', '8', '8', '8', '2', 'global', 'set']]
- set wrapmargin=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 26: Setting number global-local (to window) option.
- " Such option does currently not exist.
-
-
- " 27a: Setting global number local (to window) option"
- noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd)
- noa setlocal foldcolumn=1
- let g:options = [['foldcolumn', '8', '', '8', '2', 'global', 'setglobal']]
- setglobal foldcolumn=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 27b: Setting local number local (to window) option"
- noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd)
- noa setlocal foldcolumn=1
- let g:options = [['foldcolumn', '1', '1', '', '2', 'local', 'setlocal']]
- setlocal foldcolumn=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 27c: Setting again number local (to window) option"
- noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd)
- noa setlocal foldcolumn=1
- let g:options = [['foldcolumn', '1', '1', '8', '2', 'global', 'set']]
- set foldcolumn=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 27d: Setting again global number local (to window) option"
- noa set foldcolumn=8 " Reset global and local value (without triggering autocmd)
- let g:options = [['foldcolumn', '8', '8', '8', '2', 'global', 'set']]
- set foldcolumn=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 28a: Setting global boolean global option"
- noa setglobal nowrapscan " Reset global and local value (without triggering autocmd)
- noa setlocal wrapscan " Sets the global(!) value!
- let g:options = [['wrapscan', '1', '', '1', '0', 'global', 'setglobal']]
- setglobal nowrapscan
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 28b: Setting local boolean global option"
- noa setglobal nowrapscan " Reset global and local value (without triggering autocmd)
- noa setlocal wrapscan " Sets the global(!) value!
- let g:options = [['wrapscan', '1', '1', '', '0', 'local', 'setlocal']]
- setlocal nowrapscan
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 28c: Setting again boolean global option"
- noa setglobal nowrapscan " Reset global and local value (without triggering autocmd)
- noa setlocal wrapscan " Sets the global(!) value!
- let g:options = [['wrapscan', '1', '1', '1', '0', 'global', 'set']]
- set nowrapscan
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 28d: Setting again global boolean global option"
- noa set nowrapscan " Reset global and local value (without triggering autocmd)
- let g:options = [['wrapscan', '0', '0', '0', '1', 'global', 'set']]
- set wrapscan
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 29a: Setting global boolean global-local (to buffer) option"
- noa setglobal noautoread " Reset global and local value (without triggering autocmd)
- noa setlocal autoread
- let g:options = [['autoread', '0', '', '0', '1', 'global', 'setglobal']]
- setglobal autoread
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 29b: Setting local boolean global-local (to buffer) option"
- noa setglobal noautoread " Reset global and local value (without triggering autocmd)
- noa setlocal autoread
- let g:options = [['autoread', '1', '1', '', '0', 'local', 'setlocal']]
- setlocal noautoread
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 29c: Setting again boolean global-local (to buffer) option"
- noa setglobal noautoread " Reset global and local value (without triggering autocmd)
- noa setlocal autoread
- let g:options = [['autoread', '1', '1', '0', '1', 'global', 'set']]
- set autoread
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 29d: Setting again global boolean global-local (to buffer) option"
- noa set noautoread " Reset global and local value (without triggering autocmd)
- let g:options = [['autoread', '0', '0', '0', '1', 'global', 'set']]
- set autoread
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 30a: Setting global boolean local (to buffer) option"
- noa setglobal nocindent " Reset global and local value (without triggering autocmd)
- noa setlocal cindent
- let g:options = [['cindent', '0', '', '0', '1', 'global', 'setglobal']]
- setglobal cindent
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 30b: Setting local boolean local (to buffer) option"
- noa setglobal nocindent " Reset global and local value (without triggering autocmd)
- noa setlocal cindent
- let g:options = [['cindent', '1', '1', '', '0', 'local', 'setlocal']]
- setlocal nocindent
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 30c: Setting again boolean local (to buffer) option"
- noa setglobal nocindent " Reset global and local value (without triggering autocmd)
- noa setlocal cindent
- let g:options = [['cindent', '1', '1', '0', '1', 'global', 'set']]
- set cindent
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 30d: Setting again global boolean local (to buffer) option"
- noa set nocindent " Reset global and local value (without triggering autocmd)
- let g:options = [['cindent', '0', '0', '0', '1', 'global', 'set']]
- set cindent
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 31: Setting boolean global-local (to window) option
- " Currently no such option exists.
-
-
- " 32a: Setting global boolean local (to window) option"
- noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd)
- noa setlocal cursorcolumn
- let g:options = [['cursorcolumn', '0', '', '0', '1', 'global', 'setglobal']]
- setglobal cursorcolumn
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 32b: Setting local boolean local (to window) option"
- noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd)
- noa setlocal cursorcolumn
- let g:options = [['cursorcolumn', '1', '1', '', '0', 'local', 'setlocal']]
- setlocal nocursorcolumn
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 32c: Setting again boolean local (to window) option"
- noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd)
- noa setlocal cursorcolumn
- let g:options = [['cursorcolumn', '1', '1', '0', '1', 'global', 'set']]
- set cursorcolumn
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
- " 32d: Setting again global boolean local (to window) option"
- noa set nocursorcolumn " Reset global and local value (without triggering autocmd)
- let g:options = [['cursorcolumn', '0', '0', '0', '1', 'global', 'set']]
- set cursorcolumn
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " 33: Test autocommands when an option value is converted internally.
- noa set backspace=1 " Reset global and local value (without triggering autocmd)
- let g:options = [['backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set']]
- set backspace=2
- call assert_equal([], g:options)
- call assert_equal(g:opt[0], g:opt[1])
-
-
- " Cleanup
- au! OptionSet
- " set tags&
- for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'backupext', 'tags', 'spelllang', 'statusline', 'foldignore', 'cmdheight', 'undolevels', 'wrapmargin', 'foldcolumn', 'wrapscan', 'autoread', 'cindent', 'cursorcolumn']
- exe printf(":set %s&vim", opt)
- endfor
- call test_override('starting', 0)
- delfunc! AutoCommandOptionSet
-endfunc
-
-func Test_OptionSet_diffmode()
- CheckFunction test_override
- call test_override('starting', 1)
- " 18: Changing an option when entering diff mode
- new
- au OptionSet diff :let &l:cul = v:option_new
-
- call setline(1, ['buffer 1', 'line2', 'line3', 'line4'])
- call assert_equal(0, &l:cul)
- diffthis
- call assert_equal(1, &l:cul)
-
- vnew
- call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4'])
- call assert_equal(0, &l:cul)
- diffthis
- call assert_equal(1, &l:cul)
-
- diffoff
- call assert_equal(0, &l:cul)
- call assert_equal(1, getwinvar(2, '&l:cul'))
- bw!
-
- call assert_equal(1, &l:cul)
- diffoff!
- call assert_equal(0, &l:cul)
- call assert_equal(0, getwinvar(1, '&l:cul'))
- bw!
-
- " Cleanup
- au! OptionSet
- call test_override('starting', 0)
-endfunc
-
-func Test_OptionSet_diffmode_close()
- CheckFunction test_override
- call test_override('starting', 1)
- " 19: Try to close the current window when entering diff mode
- " should not segfault
- new
- au OptionSet diff close
-
- call setline(1, ['buffer 1', 'line2', 'line3', 'line4'])
- call assert_fails(':diffthis', 'E788')
- call assert_equal(1, &diff)
- vnew
- call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4'])
- call assert_fails(':diffthis', 'E788')
- call assert_equal(1, &diff)
- set diffopt-=closeoff
- bw!
- call assert_fails(':diffoff!', 'E788')
- bw!
-
- " Cleanup
- au! OptionSet
- call test_override('starting', 0)
- "delfunc! AutoCommandOptionSet
-endfunc
-
-" Test for Bufleave autocommand that deletes the buffer we are about to edit.
-func Test_BufleaveWithDelete()
- new | edit Xfile1
-
- augroup test_bufleavewithdelete
- autocmd!
- autocmd BufLeave Xfile1 bwipe Xfile2
- augroup END
-
- call assert_fails('edit Xfile2', 'E143:')
- call assert_equal('Xfile1', bufname('%'))
-
- autocmd! test_bufleavewithdelete BufLeave Xfile1
- augroup! test_bufleavewithdelete
-
- new
- bwipe! Xfile1
-endfunc
-
-" Test for autocommand that changes the buffer list, when doing ":ball".
-func Test_Acmd_BufAll()
- enew!
- %bwipe!
- call writefile(['Test file Xxx1'], 'Xxx1')
- call writefile(['Test file Xxx2'], 'Xxx2')
- call writefile(['Test file Xxx3'], 'Xxx3')
-
- " Add three files to the buffer list
- split Xxx1
- close
- split Xxx2
- close
- split Xxx3
- close
-
- " Wipe the buffer when the buffer is opened
- au BufReadPost Xxx2 bwipe
-
- call append(0, 'Test file Xxx4')
- ball
-
- call assert_equal(2, winnr('$'))
- call assert_equal('Xxx1', bufname(winbufnr(winnr('$'))))
- wincmd t
-
- au! BufReadPost
- %bwipe!
- call delete('Xxx1')
- call delete('Xxx2')
- call delete('Xxx3')
- enew! | only
-endfunc
-
-" Test for autocommand that changes current buffer on BufEnter event.
-" Check if modelines are interpreted for the correct buffer.
-func Test_Acmd_BufEnter()
- %bwipe!
- call writefile(['start of test file Xxx1',
- \ "\<Tab>this is a test",
- \ 'end of test file Xxx1'], 'Xxx1')
- call writefile(['start of test file Xxx2',
- \ 'vim: set noai :',
- \ "\<Tab>this is a test",
- \ 'end of test file Xxx2'], 'Xxx2')
-
- au BufEnter Xxx2 brew
- set ai modeline modelines=3
- edit Xxx1
- " edit Xxx2, autocmd will do :brew
- edit Xxx2
- exe "normal G?this is a\<CR>"
- " Append text with autoindent to this file
- normal othis should be auto-indented
- call assert_equal("\<Tab>this should be auto-indented", getline('.'))
- call assert_equal(3, line('.'))
- " Remove autocmd and edit Xxx2 again
- au! BufEnter Xxx2
- buf! Xxx2
- exe "normal G?this is a\<CR>"
- " append text without autoindent to Xxx
- normal othis should be in column 1
- call assert_equal("this should be in column 1", getline('.'))
- call assert_equal(4, line('.'))
-
- %bwipe!
- call delete('Xxx1')
- call delete('Xxx2')
- set ai&vim modeline&vim modelines&vim
-endfunc
-
-" Test for issue #57
-" do not move cursor on <c-o> when autoindent is set
-func Test_ai_CTRL_O()
- enew!
- set ai
- let save_fo = &fo
- set fo+=r
- exe "normal o# abcdef\<Esc>2hi\<CR>\<C-O>d0\<Esc>"
- exe "normal o# abcdef\<Esc>2hi\<C-O>d0\<Esc>"
- call assert_equal(['# abc', 'def', 'def'], getline(2, 4))
-
- set ai&vim
- let &fo = save_fo
- enew!
-endfunc
-
-" Test for autocommand that deletes the current buffer on BufLeave event.
-" Also test deleting the last buffer, should give a new, empty buffer.
-func Test_BufLeave_Wipe()
- %bwipe!
- let content = ['start of test file Xxx',
- \ 'this is a test',
- \ 'end of test file Xxx']
- call writefile(content, 'Xxx1')
- call writefile(content, 'Xxx2')
-
- au BufLeave Xxx2 bwipe
- edit Xxx1
- split Xxx2
- " delete buffer Xxx2, we should be back to Xxx1
- bwipe
- call assert_equal('Xxx1', bufname('%'))
- call assert_equal(1, winnr('$'))
-
- " Create an alternate buffer
- %write! test.out
- call assert_equal('test.out', bufname('#'))
- " delete alternate buffer
- bwipe test.out
- call assert_equal('Xxx1', bufname('%'))
- call assert_equal('', bufname('#'))
-
- au BufLeave Xxx1 bwipe
- " delete current buffer, get an empty one
- bwipe!
- call assert_equal(1, line('$'))
- call assert_equal('', bufname('%'))
- let g:bufinfo = getbufinfo()
- call assert_equal(1, len(g:bufinfo))
-
- call delete('Xxx1')
- call delete('Xxx2')
- call delete('test.out')
- %bwipe
- au! BufLeave
-
- " check that bufinfo doesn't contain a pointer to freed memory
- call test_garbagecollect_now()
-endfunc
-
-func Test_QuitPre()
- edit Xfoo
- let winid = win_getid(winnr())
- split Xbar
- au! QuitPre * let g:afile = expand('<afile>')
- " Close the other window, <afile> should be correct.
- exe win_id2win(winid) . 'q'
- call assert_equal('Xfoo', g:afile)
-
- unlet g:afile
- bwipe Xfoo
- bwipe Xbar
-endfunc
-
-func Test_Cmdline()
- au! CmdlineChanged : let g:text = getcmdline()
- let g:text = 0
- call feedkeys(":echom 'hello'\<CR>", 'xt')
- call assert_equal("echom 'hello'", g:text)
- au! CmdlineChanged
-
- au! CmdlineChanged : let g:entered = expand('<afile>')
- let g:entered = 0
- call feedkeys(":echom 'hello'\<CR>", 'xt')
- call assert_equal(':', g:entered)
- au! CmdlineChanged
-
- au! CmdlineEnter : let g:entered = expand('<afile>')
- au! CmdlineLeave : let g:left = expand('<afile>')
- let g:entered = 0
- let g:left = 0
- call feedkeys(":echo 'hello'\<CR>", 'xt')
- call assert_equal(':', g:entered)
- call assert_equal(':', g:left)
- au! CmdlineEnter
- au! CmdlineLeave
-
- let save_shellslash = &shellslash
- set noshellslash
- au! CmdlineEnter / let g:entered = expand('<afile>')
- au! CmdlineLeave / let g:left = expand('<afile>')
- let g:entered = 0
- let g:left = 0
- new
- call setline(1, 'hello')
- call feedkeys("/hello\<CR>", 'xt')
- call assert_equal('/', g:entered)
- call assert_equal('/', g:left)
- bwipe!
- au! CmdlineEnter
- au! CmdlineLeave
- let &shellslash = save_shellslash
-endfunc
-
-" Test for BufWritePre autocommand that deletes or unloads the buffer.
-func Test_BufWritePre()
- %bwipe
- au BufWritePre Xxx1 bunload
- au BufWritePre Xxx2 bwipe
-
- call writefile(['start of Xxx1', 'test', 'end of Xxx1'], 'Xxx1')
- call writefile(['start of Xxx2', 'test', 'end of Xxx2'], 'Xxx2')
-
- edit Xtest
- e! Xxx2
- bdel Xtest
- e Xxx1
- " write it, will unload it and give an error msg
- call assert_fails('w', 'E203')
- call assert_equal('Xxx2', bufname('%'))
- edit Xtest
- e! Xxx2
- bwipe Xtest
- " write it, will delete the buffer and give an error msg
- call assert_fails('w', 'E203')
- call assert_equal('Xxx1', bufname('%'))
- au! BufWritePre
- call delete('Xxx1')
- call delete('Xxx2')
-endfunc
-
-" Test for BufUnload autocommand that unloads all the other buffers
-func Test_bufunload_all()
- call writefile(['Test file Xxx1'], 'Xxx1')"
- call writefile(['Test file Xxx2'], 'Xxx2')"
-
- let 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')
- set nohidden
- edit Xxx1
- split Xxx2
- q
- [CODE]
-
- call writefile(content, 'Xtest')
-
- call delete('Xout')
- call system(GetVimCommandClean() .. ' -N --headless -S Xtest')
- call assert_true(filereadable('Xout'))
-
- call delete('Xxx1')
- call delete('Xxx2')
- call delete('Xtest')
- call delete('Xout')
-endfunc
-
-" Some tests for buffer-local autocommands
-func Test_buflocal_autocmd()
- let g:bname = ''
- edit xx
- au BufLeave <buffer> let g:bname = expand("%")
- " here, autocommand for xx should trigger.
- " but autocommand shall not apply to buffer named <buffer>.
- edit somefile
- call assert_equal('xx', g:bname)
- let g:bname = ''
- " here, autocommand shall be auto-deleted
- bwipe xx
- " autocmd should not trigger
- edit xx
- call assert_equal('', g:bname)
- " autocmd should not trigger
- edit somefile
- call assert_equal('', g:bname)
- enew
- unlet g:bname
-endfunc
-
-" Test for "*Cmd" autocommands
-func Test_Cmd_Autocmds()
- call writefile(['start of Xxx', "\tabc2", 'end of Xxx'], 'Xxx')
-
- enew!
- au BufReadCmd XtestA 0r Xxx|$del
- edit XtestA " will read text of Xxd instead
- call assert_equal('start of Xxx', getline(1))
-
- au BufWriteCmd XtestA call append(line("$"), "write")
- write " will append a line to the file
- call assert_equal('write', getline('$'))
- call assert_fails('read XtestA', 'E484') " should not read anything
- call assert_equal('write', getline(4))
-
- " now we have:
- " 1 start of Xxx
- " 2 abc2
- " 3 end of Xxx
- " 4 write
-
- au FileReadCmd XtestB '[r Xxx
- 2r XtestB " will read Xxx below line 2 instead
- call assert_equal('start of Xxx', getline(3))
-
- " now we have:
- " 1 start of Xxx
- " 2 abc2
- " 3 start of Xxx
- " 4 abc2
- " 5 end of Xxx
- " 6 end of Xxx
- " 7 write
-
- au FileWriteCmd XtestC '[,']copy $
- normal 4GA1
- 4,5w XtestC " will copy lines 4 and 5 to the end
- call assert_equal("\tabc21", getline(8))
- call assert_fails('r XtestC', 'E484') " should not read anything
- call assert_equal("end of Xxx", getline(9))
-
- " now we have:
- " 1 start of Xxx
- " 2 abc2
- " 3 start of Xxx
- " 4 abc21
- " 5 end of Xxx
- " 6 end of Xxx
- " 7 write
- " 8 abc21
- " 9 end of Xxx
-
- let g:lines = []
- au FileAppendCmd XtestD call extend(g:lines, getline(line("'["), line("']")))
- w >>XtestD " will add lines to 'lines'
- call assert_equal(9, len(g:lines))
- call assert_fails('$r XtestD', 'E484') " should not read anything
- call assert_equal(9, line('$'))
- call assert_equal('end of Xxx', getline('$'))
-
- au BufReadCmd XtestE 0r Xxx|$del
- sp XtestE " split window with test.out
- call assert_equal('end of Xxx', getline(3))
-
- let g:lines = []
- exe "normal 2Goasdf\<Esc>\<C-W>\<C-W>"
- au BufWriteCmd XtestE call extend(g:lines, getline(0, '$'))
- wall " will write other window to 'lines'
- call assert_equal(4, len(g:lines), g:lines)
- call assert_equal("asdf", g:lines[2])
-
- au! BufReadCmd
- au! BufWriteCmd
- au! FileReadCmd
- au! FileWriteCmd
- au! FileAppendCmd
- %bwipe!
- call delete('Xxx')
- 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 Test_BufWriteCmd()
- autocmd BufWriteCmd Xbufwritecmd let g:written = 1
- new
- file Xbufwritecmd
- set buftype=acwrite
- call mkdir('Xbufwritecmd')
- write
- " BufWriteCmd should be triggered even if a directory has the same name
- call assert_equal(1, g:written)
- call delete('Xbufwritecmd', 'd')
- unlet g:written
- au! BufWriteCmd
- bwipe!
-endfunc
-
-func SetChangeMarks(start, end)
- exe a:start .. 'mark ['
- exe a:end .. 'mark ]'
-endfunc
-
-" Verify the effects of autocmds on '[ and ']
-func Test_change_mark_in_autocmds()
- edit! Xtest
- call feedkeys("ia\<CR>b\<CR>c\<CR>d\<C-g>u\<Esc>", 'xtn')
-
- call SetChangeMarks(2, 3)
- write
- call assert_equal([1, 4], [line("'["), line("']")])
-
- call SetChangeMarks(2, 3)
- au BufWritePre * call assert_equal([1, 4], [line("'["), line("']")])
- write
- au! BufWritePre
-
- if has('unix')
- write XtestFilter
- write >> XtestFilter
-
- call SetChangeMarks(2, 3)
- " Marks are set to the entire range of the write
- au FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")])
- " '[ is adjusted to just before the line that will receive the filtered
- " data
- au FilterReadPre * call assert_equal([4, 4], [line("'["), line("']")])
- " The filtered data is read into the buffer, and the source lines are
- " still present, so the range is after the source lines
- au FilterReadPost * call assert_equal([5, 12], [line("'["), line("']")])
- %!cat XtestFilter
- " After the filtered data is read, the original lines are deleted
- call assert_equal([1, 8], [line("'["), line("']")])
- au! FilterWritePre,FilterReadPre,FilterReadPost
- undo
-
- call SetChangeMarks(1, 4)
- au FilterWritePre * call assert_equal([2, 3], [line("'["), line("']")])
- au FilterReadPre * call assert_equal([3, 3], [line("'["), line("']")])
- au FilterReadPost * call assert_equal([4, 11], [line("'["), line("']")])
- 2,3!cat XtestFilter
- call assert_equal([2, 9], [line("'["), line("']")])
- au! FilterWritePre,FilterReadPre,FilterReadPost
- undo
-
- call delete('XtestFilter')
- endif
-
- call SetChangeMarks(1, 4)
- au FileWritePre * call assert_equal([2, 3], [line("'["), line("']")])
- 2,3write Xtest2
- au! FileWritePre
-
- call SetChangeMarks(2, 3)
- au FileAppendPre * call assert_equal([1, 4], [line("'["), line("']")])
- write >> Xtest2
- au! FileAppendPre
-
- call SetChangeMarks(1, 4)
- au FileAppendPre * call assert_equal([2, 3], [line("'["), line("']")])
- 2,3write >> Xtest2
- au! FileAppendPre
-
- call SetChangeMarks(1, 1)
- au FileReadPre * call assert_equal([3, 1], [line("'["), line("']")])
- au FileReadPost * call assert_equal([4, 11], [line("'["), line("']")])
- 3read Xtest2
- au! FileReadPre,FileReadPost
- undo
-
- call SetChangeMarks(4, 4)
- " When the line is 0, it's adjusted to 1
- au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")])
- au FileReadPost * call assert_equal([1, 8], [line("'["), line("']")])
- 0read Xtest2
- au! FileReadPre,FileReadPost
- undo
-
- call SetChangeMarks(4, 4)
- " When the line is 0, it's adjusted to 1
- au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")])
- au FileReadPost * call assert_equal([2, 9], [line("'["), line("']")])
- 1read Xtest2
- au! FileReadPre,FileReadPost
- undo
-
- bwipe!
- call delete('Xtest')
- call delete('Xtest2')
-endfunc
-
-func Test_Filter_noshelltemp()
- CheckExecutable cat
-
- enew!
- call setline(1, ['a', 'b', 'c', 'd'])
-
- let shelltemp = &shelltemp
- set shelltemp
-
- let g:filter_au = 0
- au FilterWritePre * let g:filter_au += 1
- au FilterReadPre * let g:filter_au += 1
- au FilterReadPost * let g:filter_au += 1
- %!cat
- call assert_equal(3, g:filter_au)
-
- if has('filterpipe')
- set noshelltemp
-
- let g:filter_au = 0
- au FilterWritePre * let g:filter_au += 1
- au FilterReadPre * let g:filter_au += 1
- au FilterReadPost * let g:filter_au += 1
- %!cat
- call assert_equal(0, g:filter_au)
- endif
-
- au! FilterWritePre,FilterReadPre,FilterReadPost
- let &shelltemp = shelltemp
- bwipe!
-endfunc
-
-func Test_TextYankPost()
- enew!
- call setline(1, ['foo'])
-
- let g:event = []
- au TextYankPost * let g:event = copy(v:event)
-
- call assert_equal({}, v:event)
- call assert_fails('let v:event = {}', 'E46:')
- call assert_fails('let v:event.mykey = 0', 'E742:')
-
- norm "ayiw
- call assert_equal(
- \{'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', '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', 'visual': v:true, 'regtype': "\x161"},
- \g:event)
- norm "xciwbar
- call assert_equal(
- \{'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', 'visual': v:false, 'regtype': 'v'},
- \g:event)
-
- call assert_equal({}, v:event)
-
- au! TextYankPost
- unlet g:event
- bwipe!
-endfunc
-
-func Test_autocommand_all_events()
- call assert_fails('au * * bwipe', 'E1155:')
- call assert_fails('au * x bwipe', 'E1155:')
- call assert_fails('au! * x bwipe', 'E1155:')
-endfunc
-
-func Test_autocmd_user()
- au User MyEvent let s:res = [expand("<afile>"), expand("<amatch>")]
- doautocmd User MyEvent
- call assert_equal(['MyEvent', 'MyEvent'], s:res)
- au! User
- unlet s:res
-endfunc
-
-function s:Before_test_dirchanged()
- augroup test_dirchanged
- autocmd!
- augroup END
- let s:li = []
- let s:dir_this = getcwd()
- let s:dir_foo = s:dir_this . '/Xfoo'
- call mkdir(s:dir_foo)
- let s:dir_bar = s:dir_this . '/Xbar'
- call mkdir(s:dir_bar)
-endfunc
-
-function s:After_test_dirchanged()
- call chdir(s:dir_this)
- call delete(s:dir_foo, 'd')
- call delete(s:dir_bar, 'd')
- augroup test_dirchanged
- autocmd!
- augroup END
-endfunc
-
-function Test_dirchanged_global()
- call s:Before_test_dirchanged()
- autocmd test_dirchanged DirChangedPre global call add(s:li, expand("<amatch>") .. " pre cd " .. v:event.directory)
- autocmd test_dirchanged DirChanged global call add(s:li, "cd:")
- autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>"))
- call chdir(s:dir_foo)
- let expected = ["global pre cd " .. s:dir_foo, "cd:", s:dir_foo]
- call assert_equal(expected, s:li)
- call chdir(s:dir_foo)
- call assert_equal(expected, s:li)
- exe 'lcd ' .. fnameescape(s:dir_bar)
- call assert_equal(expected, s:li)
-
- exe 'cd ' .. s:dir_foo
- exe 'cd ' .. s:dir_bar
- autocmd! test_dirchanged DirChanged global let g:result = expand("<afile>")
- cd -
- call assert_equal(s:dir_foo, substitute(g:result, '\\', '/', 'g'))
-
- call s:After_test_dirchanged()
-endfunc
-
-function Test_dirchanged_local()
- call s:Before_test_dirchanged()
- autocmd test_dirchanged DirChanged window call add(s:li, "lcd:")
- autocmd test_dirchanged DirChanged window call add(s:li, expand("<afile>"))
- call chdir(s:dir_foo)
- call assert_equal([], s:li)
- exe 'lcd ' .. fnameescape(s:dir_bar)
- call assert_equal(["lcd:", s:dir_bar], s:li)
- exe 'lcd ' .. fnameescape(s:dir_bar)
- call assert_equal(["lcd:", s:dir_bar], s:li)
- call s:After_test_dirchanged()
-endfunc
-
-function Test_dirchanged_auto()
- CheckFunction test_autochdir
- CheckOption autochdir
- call s:Before_test_dirchanged()
- call test_autochdir()
- autocmd test_dirchanged DirChangedPre auto call add(s:li, "pre cd " .. v:event.directory)
- autocmd test_dirchanged DirChanged auto call add(s:li, "auto:")
- autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>"))
- set acd
- cd ..
- call assert_equal([], s:li)
- exe 'edit ' . s:dir_foo . '/Xfile'
- call assert_equal(s:dir_foo, getcwd())
- let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo]
- call assert_equal(expected, s:li)
- set noacd
- bwipe!
- call s:After_test_dirchanged()
-endfunc
-
-" Test TextChangedI and TextChangedP
-func Test_ChangedP()
- throw 'Skipped: use test/functional/editor/completion_spec.lua'
- new
- call setline(1, ['foo', 'bar', 'foobar'])
- call test_override("char_avail", 1)
- set complete=. completeopt=menuone
-
- func! TextChangedAutocmd(char)
- let g:autocmd .= a:char
- endfunc
-
- au! TextChanged <buffer> :call TextChangedAutocmd('N')
- au! TextChangedI <buffer> :call TextChangedAutocmd('I')
- au! TextChangedP <buffer> :call TextChangedAutocmd('P')
-
- call cursor(3, 1)
- let g:autocmd = ''
- call feedkeys("o\<esc>", 'tnix')
- call assert_equal('I', g:autocmd)
-
- let g:autocmd = ''
- call feedkeys("Sf", 'tnix')
- call assert_equal('II', g:autocmd)
-
- let g:autocmd = ''
- call feedkeys("Sf\<C-N>", 'tnix')
- call assert_equal('IIP', g:autocmd)
-
- let g:autocmd = ''
- call feedkeys("Sf\<C-N>\<C-N>", 'tnix')
- call assert_equal('IIPP', g:autocmd)
-
- let g:autocmd = ''
- call feedkeys("Sf\<C-N>\<C-N>\<C-N>", 'tnix')
- call assert_equal('IIPPP', g:autocmd)
-
- let g:autocmd = ''
- call feedkeys("Sf\<C-N>\<C-N>\<C-N>\<C-N>", 'tnix')
- call assert_equal('IIPPPP', g:autocmd)
-
- call assert_equal(['foo', 'bar', 'foobar', 'foo'], getline(1, '$'))
- " TODO: how should it handle completeopt=noinsert,noselect?
-
- " CleanUp
- call test_override("char_avail", 0)
- au! TextChanged
- au! TextChangedI
- au! TextChangedP
- delfu TextChangedAutocmd
- unlet! g:autocmd
- set complete&vim completeopt&vim
-
- bw!
-endfunc
-
-let g:setline_handled = v:false
-func SetLineOne()
- if !g:setline_handled
- call setline(1, "(x)")
- let g:setline_handled = v:true
- endif
-endfunc
-
-func Test_TextChangedI_with_setline()
- CheckFunction test_override
- new
- call test_override('char_avail', 1)
- autocmd TextChangedI <buffer> call SetLineOne()
- call feedkeys("i(\<CR>\<Esc>", 'tx')
- call assert_equal('(', getline(1))
- call assert_equal('x)', getline(2))
- undo
- call assert_equal('', getline(1))
- call assert_equal('', getline(2))
-
- call test_override('starting', 0)
- bwipe!
-endfunc
-
-func Test_Changed_FirstTime()
- CheckFeature terminal
- CheckNotGui
- " Starting a terminal to run Vim is always considered flaky.
- let g:test_is_flaky = 1
-
- " Prepare file for TextChanged event.
- call writefile([''], 'Xchanged.txt')
- let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3})
- call assert_equal('running', term_getstatus(buf))
- " Wait for the ruler (in the status line) to be shown.
- call WaitForAssert({-> assert_match('\<All$', term_getline(buf, 3))})
- " 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 WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
- call assert_equal([''], readfile('Xchanged.txt'))
-
- " clean up
- call delete('Xchanged.txt')
- bwipe!
-endfunc
-
-func Test_autocmd_nested()
- let g:did_nested = 0
- augroup Testing
- au WinNew * edit somefile
- au BufNew * let g:did_nested = 1
- augroup END
- split
- call assert_equal(0, g:did_nested)
- close
- bwipe! somefile
-
- " old nested argument still works
- augroup Testing
- au!
- au WinNew * nested edit somefile
- au BufNew * let g:did_nested = 1
- augroup END
- split
- call assert_equal(1, g:did_nested)
- close
- bwipe! somefile
-
- " New ++nested argument works
- augroup Testing
- au!
- au WinNew * ++nested edit somefile
- au BufNew * let g:did_nested = 1
- augroup END
- split
- call assert_equal(1, g:did_nested)
- close
- bwipe! somefile
-
- augroup Testing
- au!
- augroup END
-
- call assert_fails('au WinNew * ++nested ++nested echo bad', 'E983:')
- call assert_fails('au WinNew * nested nested echo bad', 'E983:')
-endfunc
-
-func Test_autocmd_nested_cursor_invalid()
- set laststatus=0
- copen
- cclose
- call setline(1, ['foo', 'bar', 'baz'])
- 3
- augroup nested_inv
- autocmd User foo ++nested copen
- autocmd BufAdd * let &laststatus = 2 - &laststatus
- augroup END
- doautocmd User foo
-
- augroup nested_inv
- au!
- augroup END
- set laststatus&
- cclose
- bwipe!
-endfunc
-
-func Test_autocmd_nested_keeps_cursor_pos()
- enew
- call setline(1, 'foo')
- autocmd User foo ++nested normal! $a
- autocmd InsertLeave * :
- doautocmd User foo
- call assert_equal([0, 1, 3, 0], getpos('.'))
-
- bwipe!
-endfunc
-
-func Test_autocmd_nested_switch_window()
- " run this in a separate Vim so that SafeState works
- CheckRunVimInTerminal
-
- let lines =<< trim END
- vim9script
- ['()']->writefile('Xautofile')
- autocmd VimEnter * ++nested edit Xautofile | split
- autocmd BufReadPost * autocmd SafeState * ++once foldclosed('.')
- autocmd WinEnter * matchadd('ErrorMsg', 'pat')
- END
- call writefile(lines, 'Xautoscript')
- let buf = RunVimInTerminal('-S Xautoscript', {'rows': 10})
- call VerifyScreenDump(buf, 'Test_autocmd_nested_switch', {})
-
- call StopVimInTerminal(buf)
- call delete('Xautofile')
- call delete('Xautoscript')
-endfunc
-
-func Test_autocmd_once()
- " Without ++once WinNew triggers twice
- let g:did_split = 0
- augroup Testing
- au WinNew * let g:did_split += 1
- augroup END
- split
- split
- call assert_equal(2, g:did_split)
- call assert_true(exists('#WinNew'))
- close
- close
-
- " With ++once WinNew triggers once
- let g:did_split = 0
- augroup Testing
- au!
- au WinNew * ++once let g:did_split += 1
- augroup END
- split
- split
- call assert_equal(1, g:did_split)
- call assert_false(exists('#WinNew'))
- close
- close
-
- call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
-endfunc
-
-func Test_autocmd_bufreadpre()
- new
- let b:bufreadpre = 1
- call append(0, range(100))
- w! XAutocmdBufReadPre.txt
- autocmd BufReadPre <buffer> :let b:bufreadpre += 1
- norm! 50gg
- sp
- norm! 100gg
- wincmd p
- let g:wsv1 = winsaveview()
- wincmd p
- let g:wsv2 = winsaveview()
- " triggers BufReadPre, should not move the cursor in either window
- " The topline may change one line in a large window.
- edit
- call assert_inrange(g:wsv2.topline - 1, g:wsv2.topline + 1, winsaveview().topline)
- call assert_equal(g:wsv2.lnum, winsaveview().lnum)
- call assert_equal(2, b:bufreadpre)
- wincmd p
- call assert_equal(g:wsv1.topline, winsaveview().topline)
- call assert_equal(g:wsv1.lnum, winsaveview().lnum)
- call assert_equal(2, b:bufreadpre)
- " Now set the cursor position in an BufReadPre autocommand
- " (even though the position will be invalid, this should make Vim reset the
- " cursor position in the other window.
- wincmd p
- 1 " set cpo+=g
- " won't do anything, but try to set the cursor on an invalid lnum
- autocmd BufReadPre <buffer> :norm! 70gg
- " triggers BufReadPre, should not move the cursor in either window
- e
- call assert_equal(1, winsaveview().topline)
- call assert_equal(1, winsaveview().lnum)
- call assert_equal(3, b:bufreadpre)
- wincmd p
- call assert_equal(g:wsv1.topline, winsaveview().topline)
- call assert_equal(g:wsv1.lnum, winsaveview().lnum)
- call assert_equal(3, b:bufreadpre)
- close
- close
- call delete('XAutocmdBufReadPre.txt')
- " set cpo-=g
-endfunc
-
-" FileChangedShell tested in test_filechanged.vim
-
-" Tests for the following autocommands:
-" - FileWritePre writing a compressed file
-" - FileReadPost reading a compressed file
-" - BufNewFile reading a file template
-" - BufReadPre decompressing the file to be read
-" - FilterReadPre substituting characters in the temp file
-" - FilterReadPost substituting characters after filtering
-" - FileReadPre set options for decompression
-" - FileReadPost decompress the file
-func Test_ReadWrite_Autocmds()
- " Run this test only on Unix-like systems and if gzip is available
- if !has('unix') || !executable("gzip")
- return
- endif
-
- " Make $GZIP empty, "-v" would cause trouble.
- let $GZIP = ""
-
- " Use a FileChangedShell autocommand to avoid a prompt for 'Xtestfile.gz'
- " being modified outside of Vim (noticed on Solaris).
- au FileChangedShell * echo 'caught FileChangedShell'
-
- " Test for the FileReadPost, FileWritePre and FileWritePost autocmds
- augroup Test1
- au!
- au FileWritePre *.gz '[,']!gzip
- au FileWritePost *.gz undo
- au FileReadPost *.gz '[,']!gzip -d
- augroup END
-
- new
- set bin
- call append(0, [
- \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
- \ ])
- 1,9write! Xtestfile.gz
- enew! | close
-
- new
- " Read and decompress the testfile
- 0read Xtestfile.gz
- call assert_equal([
- \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
- \ ], getline(1, 9))
- enew! | close
-
- augroup Test1
- au!
- augroup END
-
- " Test for the FileAppendPre and FileAppendPost autocmds
- augroup Test2
- au!
- au BufNewFile *.c read Xtest.c
- au FileAppendPre *.out '[,']s/new/NEW/
- au FileAppendPost *.out !cat Xtest.c >> test.out
- augroup END
-
- call writefile(['/*', ' * Here is a new .c file', ' */'], 'Xtest.c')
- new foo.c " should load Xtest.c
- call assert_equal(['/*', ' * Here is a new .c file', ' */'], getline(2, 4))
- w! >> test.out " append it to the output file
-
- let contents = readfile('test.out')
- call assert_equal(' * Here is a NEW .c file', contents[2])
- call assert_equal(' * Here is a new .c file', contents[5])
-
- call delete('test.out')
- enew! | close
- augroup Test2
- au!
- augroup END
-
- " Test for the BufReadPre and BufReadPost autocmds
- augroup Test3
- au!
- " setup autocommands to decompress before reading and re-compress
- " afterwards
- au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand("<afile>"))
- au BufReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
- au BufReadPost *.gz call rename(expand("<afile>"), expand("<afile>:r"))
- au BufReadPost *.gz exe '!gzip ' . shellescape(expand("<afile>:r"))
- augroup END
-
- e! Xtestfile.gz " Edit compressed file
- call assert_equal([
- \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
- \ ], getline(1, 9))
-
- w! >> test.out " Append it to the output file
-
- augroup Test3
- au!
- augroup END
-
- " Test for the FilterReadPre and FilterReadPost autocmds.
- set shelltemp " need temp files here
- augroup Test4
- au!
- au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t")
- au FilterReadPre *.out exe 'silent !sed s/e/E/ ' . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>"))
- au FilterReadPre *.out exe 'silent !rm ' . shellescape(expand("<afile>")) . '.t'
- au FilterReadPost *.out '[,']s/x/X/g
- augroup END
-
- e! test.out " Edit the output file
- 1,$!cat
- call assert_equal([
- \ 'linE 2 AbcdefghijklmnopqrstuvwXyz',
- \ 'linE 3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
- \ 'linE 4 AbcdefghijklmnopqrstuvwXyz',
- \ 'linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
- \ 'linE 6 AbcdefghijklmnopqrstuvwXyz',
- \ 'linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
- \ 'linE 8 AbcdefghijklmnopqrstuvwXyz',
- \ 'linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
- \ 'linE 10 AbcdefghijklmnopqrstuvwXyz'
- \ ], getline(1, 9))
- call assert_equal([
- \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
- \ ], readfile('test.out'))
-
- augroup Test4
- au!
- augroup END
- set shelltemp&vim
-
- " Test for the FileReadPre and FileReadPost autocmds.
- augroup Test5
- au!
- au FileReadPre *.gz exe 'silent !gzip -d ' . shellescape(expand("<afile>"))
- au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
- au FileReadPost *.gz '[,']s/l/L/
- augroup END
-
- new
- 0r Xtestfile.gz " Read compressed file
- call assert_equal([
- \ 'Line 2 Abcdefghijklmnopqrstuvwxyz',
- \ 'Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'Line 4 Abcdefghijklmnopqrstuvwxyz',
- \ 'Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'Line 6 Abcdefghijklmnopqrstuvwxyz',
- \ 'Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'Line 8 Abcdefghijklmnopqrstuvwxyz',
- \ 'Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'Line 10 Abcdefghijklmnopqrstuvwxyz'
- \ ], getline(1, 9))
- call assert_equal([
- \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
- \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
- \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
- \ ], readfile('Xtestfile.gz'))
-
- augroup Test5
- au!
- augroup END
-
- au! FileChangedShell
- call delete('Xtestfile.gz')
- call delete('Xtest.c')
- call delete('test.out')
-endfunc
-
-func Test_throw_in_BufWritePre()
- new
- call setline(1, ['one', 'two', 'three'])
- call assert_false(filereadable('Xthefile'))
- augroup throwing
- au BufWritePre X* throw 'do not write'
- augroup END
- try
- w Xthefile
- catch
- let caught = 1
- endtry
- call assert_equal(1, caught)
- call assert_false(filereadable('Xthefile'))
-
- bwipe!
- au! throwing
-endfunc
-
-func Test_autocmd_in_try_block()
- call mkdir('Xdir')
- au BufEnter * let g:fname = expand('%')
- try
- edit Xdir/
- endtry
- call assert_match('Xdir', g:fname)
-
- unlet g:fname
- au! BufEnter
- call delete('Xdir', 'rf')
-endfunc
-
-func Test_autocmd_CmdWinEnter()
- CheckRunVimInTerminal
- " There is not cmdwin switch, so
- " test for cmdline_hist
- " (both are available with small builds)
- CheckFeature cmdline_hist
- let lines =<< trim END
- let b:dummy_var = 'This is a dummy'
- autocmd CmdWinEnter * quit
- let winnr = winnr('$')
- END
- let filename = 'XCmdWinEnter'
- call writefile(lines, filename)
- let buf = RunVimInTerminal('-S '.filename, #{rows: 6})
-
- call term_sendkeys(buf, "q:")
- call term_wait(buf)
- call term_sendkeys(buf, ":echo b:dummy_var\<cr>")
- call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 2000)
- call term_sendkeys(buf, ":echo &buftype\<cr>")
- call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000)
- call term_sendkeys(buf, ":echo winnr\<cr>")
- call WaitForAssert({-> assert_match('^1', term_getline(buf, 6))}, 1000)
-
- " clean up
- call StopVimInTerminal(buf)
- call delete(filename)
-endfunc
-
-func Test_autocmd_was_using_freed_memory()
- pedit xx
- n x
- augroup winenter
- au WinEnter * if winnr('$') > 2 | quit | endif
- augroup END
- " Nvim needs large 'winwidth' and 'nowinfixwidth' to crash
- set winwidth=99999 nowinfixwidth
- split
-
- augroup winenter
- au! WinEnter
- augroup END
-
- set winwidth& winfixwidth&
- bwipe xx
- bwipe x
- pclose
-endfunc
-
-func Test_BufWrite_lockmarks()
- edit! Xtest
- call setline(1, ['a', 'b', 'c', 'd'])
-
- " :lockmarks preserves the marks
- call SetChangeMarks(2, 3)
- lockmarks write
- call assert_equal([2, 3], [line("'["), line("']")])
-
- " *WritePre autocmds get the correct line range, but lockmarks preserves the
- " original values for the user
- augroup lockmarks
- au!
- au BufWritePre,FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")])
- au FileWritePre * call assert_equal([3, 4], [line("'["), line("']")])
- augroup END
-
- lockmarks write
- call assert_equal([2, 3], [line("'["), line("']")])
-
- if executable('cat')
- lockmarks %!cat
- call assert_equal([2, 3], [line("'["), line("']")])
- endif
-
- lockmarks 3,4write Xtest2
- call assert_equal([2, 3], [line("'["), line("']")])
-
- au! lockmarks
- augroup! lockmarks
- call delete('Xtest')
- call delete('Xtest2')
-endfunc
-
-func Test_FileType_spell()
- if !isdirectory('/tmp')
- throw "Skipped: requires /tmp directory"
- endif
-
- " this was crashing with an invalid free()
- setglobal spellfile=/tmp/en.utf-8.add
- augroup crash
- autocmd!
- autocmd BufNewFile,BufReadPost crashfile setf somefiletype
- autocmd BufNewFile,BufReadPost crashfile set ft=anotherfiletype
- autocmd FileType anotherfiletype setlocal spell
- augroup END
- func! NoCrash() abort
- edit /tmp/crashfile
- endfunc
- call NoCrash()
-
- au! crash
- setglobal spellfile=
-endfunc
-
-" this was wiping out the current buffer and using freed memory
-func Test_SpellFileMissing_bwipe()
- next 0
- au SpellFileMissing 0 bwipe
- call assert_fails('set spell spelllang=0', 'E937:')
-
- au! SpellFileMissing
- set nospell spelllang=en
- bwipe
-endfunc
-
-" Test closing a window or editing another buffer from a FileChangedRO handler
-" in a readonly buffer
-func Test_FileChangedRO_winclose()
- augroup FileChangedROTest
- au!
- autocmd FileChangedRO * quit
- augroup END
- new
- set readonly
- call assert_fails('normal i', 'E788:')
- close
- augroup! FileChangedROTest
-
- augroup FileChangedROTest
- au!
- autocmd FileChangedRO * edit Xfile
- augroup END
- new
- set readonly
- call assert_fails('normal i', 'E788:')
- close
- augroup! FileChangedROTest
-endfunc
-
-func LogACmd()
- call add(g:logged, line('$'))
-endfunc
-
-func Test_TermChanged()
- throw 'skipped: Nvim does not support TermChanged event'
- CheckNotGui
-
- enew!
- tabnew
- call setline(1, ['a', 'b', 'c', 'd'])
- $
- au TermChanged * call LogACmd()
- let g:logged = []
- let term_save = &term
- set term=xterm
- call assert_equal([1, 4], g:logged)
-
- au! TermChanged
- let &term = term_save
- bwipe!
-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
-
-" Test for passing invalid arguments to autocmd
-func Test_autocmd_invalid_args()
- " Additional character after * for event
- call assert_fails('autocmd *a Xfile set ff=unix', 'E215:')
- augroup Test
- augroup END
- " Invalid autocmd event
- call assert_fails('autocmd Bufabc Xfile set ft=vim', 'E216:')
- " Invalid autocmd event in a autocmd group
- call assert_fails('autocmd Test Bufabc Xfile set ft=vim', 'E216:')
- augroup! Test
- " Execute all autocmds
- call assert_fails('doautocmd * BufEnter', 'E217:')
- call assert_fails('augroup! x1a2b3', 'E367:')
- call assert_fails('autocmd BufNew <buffer=999> pwd', 'E680:')
- call assert_fails('autocmd BufNew \) set ff=unix', 'E55:')
-endfunc
-
-" Test for deep nesting of autocmds
-func Test_autocmd_deep_nesting()
- autocmd BufEnter Xfile doautocmd BufEnter Xfile
- call assert_fails('doautocmd BufEnter Xfile', 'E218:')
- autocmd! BufEnter Xfile
-endfunc
-
-" Tests for SigUSR1 autocmd event, which is only available on posix systems.
-func Test_autocmd_sigusr1()
- CheckUnix
-
- let g:sigusr1_passed = 0
- au Signal SIGUSR1 let g:sigusr1_passed = 1
- call system('kill -s usr1 ' . getpid())
- call WaitForAssert({-> assert_true(g:sigusr1_passed)})
-
- au! Signal
- unlet g:sigusr1_passed
-endfunc
-
-" Test for BufReadPre autocmd deleting the file
-func Test_BufReadPre_delfile()
- augroup TestAuCmd
- au!
- autocmd BufReadPre Xfile call delete('Xfile')
- augroup END
- call writefile([], 'Xfile')
- call assert_fails('new Xfile', 'E200:')
- call assert_equal('Xfile', @%)
- call assert_equal(1, &readonly)
- call delete('Xfile')
- augroup TestAuCmd
- au!
- augroup END
- close!
-endfunc
-
-" Test for BufReadPre autocmd changing the current buffer
-func Test_BufReadPre_changebuf()
- augroup TestAuCmd
- au!
- autocmd BufReadPre Xfile edit Xsomeotherfile
- augroup END
- call writefile([], 'Xfile')
- call assert_fails('new Xfile', 'E201:')
- call assert_equal('Xsomeotherfile', @%)
- call assert_equal(1, &readonly)
- call delete('Xfile')
- augroup TestAuCmd
- au!
- augroup END
- close!
-endfunc
-
-" Test for BufWipeouti autocmd changing the current buffer when reading a file
-" in an empty buffer with 'f' flag in 'cpo'
-func Test_BufDelete_changebuf()
- new
- augroup TestAuCmd
- au!
- autocmd BufWipeout * let bufnr = bufadd('somefile') | exe "b " .. bufnr
- augroup END
- let save_cpo = &cpo
- set cpo+=f
- call assert_fails('r Xfile', ['E812:', 'E484:'])
- call assert_equal('somefile', @%)
- let &cpo = save_cpo
- augroup TestAuCmd
- au!
- augroup END
- close!
-endfunc
-
-" Test for the temporary internal window used to execute autocmds
-func Test_autocmd_window()
- %bw!
- edit one.txt
- tabnew two.txt
- vnew three.txt
- tabnew four.txt
- tabprevious
- let g:blist = []
- augroup aucmd_win_test1
- au!
- au BufEnter * call add(g:blist, [expand('<afile>'),
- \ win_gettype(bufwinnr(expand('<afile>')))])
- augroup END
-
- doautoall BufEnter
- call assert_equal([
- \ ['one.txt', 'autocmd'],
- \ ['two.txt', ''],
- \ ['four.txt', 'autocmd'],
- \ ['three.txt', ''],
- \ ], g:blist)
-
- augroup aucmd_win_test1
- au!
- augroup END
- augroup! aucmd_win_test1
- %bw!
-endfunc
-
-" Test for trying to close the temporary window used for executing an autocmd
-func Test_close_autocmd_window()
- %bw!
- edit one.txt
- tabnew two.txt
- augroup aucmd_win_test2
- au!
- " Nvim makes aucmd_win the last window
- " au BufEnter * if expand('<afile>') == 'one.txt' | 1close | endif
- au BufEnter * if expand('<afile>') == 'one.txt' | close | endif
- augroup END
-
- call assert_fails('doautoall BufEnter', 'E813:')
-
- augroup aucmd_win_test2
- au!
- augroup END
- augroup! aucmd_win_test2
- %bw!
-endfunc
-
-" Test for trying to close the tab that has the temporary window for exeucing
-" an autocmd.
-func Test_close_autocmd_tab()
- edit one.txt
- tabnew two.txt
- augroup aucmd_win_test
- au!
- au BufEnter * if expand('<afile>') == 'one.txt' | tabfirst | tabonly | endif
- augroup END
-
- call assert_fails('doautoall BufEnter', 'E813:')
-
- tabonly
- augroup aucmd_win_test
- au!
- augroup END
- augroup! aucmd_win_test
- %bwipe!
-endfunc
-
-func Test_Visual_doautoall_redraw()
- call setline(1, ['a', 'b'])
- new
- wincmd p
- call feedkeys("G\<C-V>", 'txn')
- autocmd User Explode ++once redraw
- doautoall User Explode
- %bwipe!
-endfunc
-
-" This was using freed memory.
-func Test_BufNew_arglocal()
- arglocal
- au BufNew * arglocal
- call assert_fails('drop xx', 'E1156:')
-
- au! BufNew
-endfunc
-
-func Test_autocmd_closes_window()
- au BufNew,BufWinLeave * e %e
- file yyy
- au BufNew,BufWinLeave * ball
- n xxx
-
- %bwipe
- au! BufNew
- au! BufWinLeave
-endfunc
-
-func Test_autocmd_quit_psearch()
- sn aa bb
- augroup aucmd_win_test
- au!
- au BufEnter,BufLeave,BufNew,WinEnter,WinLeave,WinNew * if winnr('$') > 1 | q | endif
- augroup END
- ps /
-
- augroup aucmd_win_test
- au!
- augroup END
- new
- pclose
-endfunc
-
-" Fuzzer found some strange combination that caused a crash.
-func Test_autocmd_normal_mess()
- " For unknown reason this hangs on MS-Windows
- CheckNotMSWindows
-
- augroup aucmd_normal_test
- au BufLeave,BufWinLeave,BufHidden,BufUnload,BufDelete,BufWipeout * norm 7q/qc
- augroup END
- " Nvim has removed :open
- " call assert_fails('o4', 'E1159')
- call assert_fails('e4', 'E1159')
- silent! H
- call assert_fails('e xx', 'E1159')
- normal G
-
- augroup aucmd_normal_test
- au!
- augroup END
-endfunc
-
-func Test_autocmd_closing_cmdwin()
- " For unknown reason this hangs on MS-Windows
- CheckNotMSWindows
-
- au BufWinLeave * nested q
- call assert_fails("norm 7q?\n", 'E855:')
-
- au! BufWinLeave
- new
- only
-endfunc
-
-func Test_autocmd_vimgrep()
- augroup aucmd_vimgrep
- au QuickfixCmdPre,BufNew,BufReadCmd * sb
- " Nvim makes aucmd_win the last window
- " au QuickfixCmdPre,BufNew,BufReadCmd * q9
- au QuickfixCmdPre,BufNew,BufReadCmd * exe 'q' .. (winnr('$') - (win_gettype(winnr('$')) == 'autocmd'))
- augroup END
- call assert_fails('lv ?a? foo', 'E926:')
-
- augroup aucmd_vimgrep
- au!
- augroup END
-endfunc
-
-func Test_bufwipeout_changes_window()
- " This should not crash, but we don't have any expectations about what
- " happens, changing window in BufWipeout has unpredictable results.
- tabedit
- let g:window_id = win_getid()
- topleft new
- setlocal bufhidden=wipe
- autocmd BufWipeout <buffer> call win_gotoid(g:window_id)
- tabprevious
- +tabclose
-
- unlet g:window_id
- au! BufWipeout
- %bwipe!
-endfunc
-
-func Test_v_event_readonly()
- autocmd CompleteChanged * let v:event.width = 0
- call assert_fails("normal! i\<C-X>\<C-V>", 'E46:')
- au! CompleteChanged
-
- autocmd DirChangedPre * let v:event.directory = ''
- call assert_fails('cd .', 'E46:')
- au! DirChangedPre
-
- autocmd ModeChanged * let v:event.new_mode = ''
- call assert_fails('normal! cc', 'E46:')
- au! ModeChanged
-
- autocmd TextYankPost * let v:event.operator = ''
- call assert_fails('normal! yy', 'E46:')
- au! TextYankPost
-endfunc
-
-" Test for ModeChanged pattern
-func Test_mode_changes()
- let g:index = 0
- let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'n', 'V', 'v', 's', 'n']
- func! TestMode()
- call assert_equal(g:mode_seq[g:index], get(v:event, "old_mode"))
- call assert_equal(g:mode_seq[g:index + 1], get(v:event, "new_mode"))
- call assert_equal(mode(1), get(v:event, "new_mode"))
- let g:index += 1
- endfunc
-
- au ModeChanged * :call TestMode()
- let g:n_to_any = 0
- au ModeChanged n:* let g:n_to_any += 1
- call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdG", 'tnix')
-
- let g:V_to_v = 0
- au ModeChanged V:v let g:V_to_v += 1
- call feedkeys("Vv\<C-G>\<esc>", 'tnix')
- call assert_equal(len(filter(g:mode_seq[1:], {idx, val -> val == 'n'})), g:n_to_any)
- call assert_equal(1, g:V_to_v)
- call assert_equal(len(g:mode_seq) - 1, g:index)
-
- let g:n_to_i = 0
- au ModeChanged n:i let g:n_to_i += 1
- let g:n_to_niI = 0
- au ModeChanged i:niI let g:n_to_niI += 1
- let g:niI_to_i = 0
- au ModeChanged niI:i let g:niI_to_i += 1
- let g:nany_to_i = 0
- au ModeChanged n*:i let g:nany_to_i += 1
- let g:i_to_n = 0
- au ModeChanged i:n let g:i_to_n += 1
- let g:nori_to_any = 0
- au ModeChanged [ni]:* let g:nori_to_any += 1
- let g:i_to_any = 0
- au ModeChanged i:* let g:i_to_any += 1
- let g:index = 0
- let g:mode_seq = ['n', 'i', 'niI', 'i', 'n']
- call feedkeys("a\<C-O>l\<esc>", 'tnix')
- call assert_equal(len(g:mode_seq) - 1, g:index)
- call assert_equal(1, g:n_to_i)
- call assert_equal(1, g:n_to_niI)
- call assert_equal(1, g:niI_to_i)
- call assert_equal(2, g:nany_to_i)
- call assert_equal(1, g:i_to_n)
- call assert_equal(2, g:i_to_any)
- call assert_equal(3, g:nori_to_any)
-
- if has('terminal')
- let g:mode_seq += ['c', 'n', 't', 'nt', 'c', 'nt', 'n']
- call feedkeys(":term\<CR>\<C-W>N:bd!\<CR>", 'tnix')
- call assert_equal(len(g:mode_seq) - 1, g:index)
- call assert_equal(1, g:n_to_i)
- call assert_equal(1, g:n_to_niI)
- call assert_equal(1, g:niI_to_i)
- call assert_equal(2, g:nany_to_i)
- call assert_equal(1, g:i_to_n)
- call assert_equal(2, g:i_to_any)
- call assert_equal(5, g:nori_to_any)
- endif
-
- let g:n_to_c = 0
- au ModeChanged n:c let g:n_to_c += 1
- let g:c_to_n = 0
- au ModeChanged c:n let g:c_to_n += 1
- let g:mode_seq += ['c', 'n', 'c', 'n']
- call feedkeys("q:\<C-C>\<Esc>", 'tnix')
- call assert_equal(len(g:mode_seq) - 1, g:index)
- call assert_equal(2, g:n_to_c)
- call assert_equal(2, g:c_to_n)
- unlet g:n_to_c
- unlet g:c_to_n
-
- let g:n_to_v = 0
- au ModeChanged n:v let g:n_to_v += 1
- let g:v_to_n = 0
- au ModeChanged v:n let g:v_to_n += 1
- let g:mode_seq += ['v', 'n']
- call feedkeys("v\<C-C>", 'tnix')
- call assert_equal(len(g:mode_seq) - 1, g:index)
- call assert_equal(1, g:n_to_v)
- call assert_equal(1, g:v_to_n)
- unlet g:n_to_v
- unlet g:v_to_n
-
- au! ModeChanged
- delfunc TestMode
- unlet! g:mode_seq
- unlet! g:index
- unlet! g:n_to_any
- unlet! g:V_to_v
- unlet! g:n_to_i
- unlet! g:n_to_niI
- unlet! g:niI_to_i
- unlet! g:nany_to_i
- unlet! g:i_to_n
- unlet! g:nori_to_any
- unlet! g:i_to_any
-endfunc
-
-func Test_recursive_ModeChanged()
- au! ModeChanged * norm 0u
- sil! norm 
- au! ModeChanged
-endfunc
-
-func Test_ModeChanged_starts_visual()
- " This was triggering ModeChanged before setting VIsual, causing a crash.
- au! ModeChanged * norm 0u
- sil! norm 
-
- au! ModeChanged
-endfunc
-
-func Test_noname_autocmd()
- augroup test_noname_autocmd_group
- autocmd!
- autocmd BufEnter * call add(s:li, ["BufEnter", expand("<afile>")])
- autocmd BufDelete * call add(s:li, ["BufDelete", expand("<afile>")])
- autocmd BufLeave * call add(s:li, ["BufLeave", expand("<afile>")])
- autocmd BufUnload * call add(s:li, ["BufUnload", expand("<afile>")])
- autocmd BufWipeout * call add(s:li, ["BufWipeout", expand("<afile>")])
- augroup END
-
- let s:li = []
- edit foo
- call assert_equal([['BufUnload', ''], ['BufDelete', ''], ['BufWipeout', ''], ['BufEnter', 'foo']], s:li)
-
- au! test_noname_autocmd_group
- augroup! test_noname_autocmd_group
-endfunc
-
-func Test_autocmd_split_dummy()
- " Autocommand trying to split a window containing a dummy buffer.
- auto BufReadPre * exe "sbuf " .. expand("<abuf>")
- " Avoid the "W11" prompt
- au FileChangedShell * let v:fcs_choice = 'reload'
- func Xautocmd_changelist()
- cal writefile(['Xtestfile2:4:4'], 'Xerr')
- edit Xerr
- lex 'Xtestfile2:4:4'
- endfunc
- call Xautocmd_changelist()
- " Should get E86, but it doesn't always happen (timing?)
- silent! call Xautocmd_changelist()
-
- au! BufReadPre
- au! FileChangedShell
- delfunc Xautocmd_changelist
- bwipe! Xerr
- call delete('Xerr')
-endfunc
-
-" This was crashing because there was only one window to execute autocommands
-" in.
-func Test_autocmd_nested_setbufvar()
- CheckFeature python3
-
- set hidden
- edit Xaaa
- edit Xbbb
- call setline(1, 'bar')
- enew
- au BufWriteCmd Xbbb ++nested call setbufvar('Xaaa', '&ft', 'foo') | bw! Xaaa
- au FileType foo call py3eval('vim.current.buffer.options["cindent"]')
- wall
-
- au! BufWriteCmd
- au! FileType foo
- set nohidden
- call delete('Xaaa')
- call delete('Xbbb')
- %bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_autoload.vim b/src/nvim/testdir/test_autoload.vim
deleted file mode 100644
index e89fe3943b..0000000000
--- a/src/nvim/testdir/test_autoload.vim
+++ /dev/null
@@ -1,25 +0,0 @@
-" Tests for autoload
-
-set runtimepath=./sautest
-
-func Test_autoload_dict_func()
- let g:loaded_foo_vim = 0
- let g:called_foo_bar_echo = 0
- call g:foo#bar.echo()
- call assert_equal(1, g:loaded_foo_vim)
- call assert_equal(1, g:called_foo_bar_echo)
-
- eval 'bar'->g:foo#addFoo()->assert_equal('barfoo')
-
- " empty name works in legacy script
- call assert_equal('empty', foo#())
-endfunc
-
-func Test_source_autoload()
- let g:loaded_sourced_vim = 0
- source sautest/autoload/sourced.vim
- call assert_equal(1, g:loaded_sourced_vim)
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_backspace_opt.vim b/src/nvim/testdir/test_backspace_opt.vim
deleted file mode 100644
index 59e94d2898..0000000000
--- a/src/nvim/testdir/test_backspace_opt.vim
+++ /dev/null
@@ -1,141 +0,0 @@
-" Tests for 'backspace' settings
-
-func Test_backspace_option()
- set backspace=
- call assert_equal('', &backspace)
- set backspace=indent
- call assert_equal('indent', &backspace)
- set backspace=eol
- call assert_equal('eol', &backspace)
- set backspace=start
- call assert_equal('start', &backspace)
- set backspace=nostop
- call assert_equal('nostop', &backspace)
- " Add the value
- set backspace=
- set backspace=indent
- call assert_equal('indent', &backspace)
- set backspace+=eol
- call assert_equal('indent,eol', &backspace)
- set backspace+=start
- call assert_equal('indent,eol,start', &backspace)
- set backspace+=nostop
- call assert_equal('indent,eol,start,nostop', &backspace)
- " Delete the value
- set backspace-=nostop
- call assert_equal('indent,eol,start', &backspace)
- set backspace-=indent
- call assert_equal('eol,start', &backspace)
- set backspace-=start
- call assert_equal('eol', &backspace)
- set backspace-=eol
- call assert_equal('', &backspace)
- " Check the error
- call assert_fails('set backspace=ABC', 'E474:')
- call assert_fails('set backspace+=def', 'E474:')
- " NOTE: Vim doesn't check following error...
- "call assert_fails('set backspace-=ghi', 'E474:')
-
- " Check backwards compatibility with version 5.4 and earlier
- set backspace=0
- call assert_equal('0', &backspace)
- set backspace=1
- call assert_equal('1', &backspace)
- set backspace=2
- call assert_equal('2', &backspace)
- set backspace=3
- call assert_equal('3', &backspace)
- call assert_fails('set backspace=4', 'E474:')
- call assert_fails('set backspace=10', 'E474:')
-
- " Cleared when 'compatible' is set
- " set compatible
- " call assert_equal('', &backspace)
- set nocompatible viminfo+=nviminfo
-endfunc
-
-" Test with backspace set to the non-compatible setting
-func Test_backspace_ctrl_u()
- new
- call append(0, [
- \ "1 this shouldn't be deleted",
- \ "2 this shouldn't be deleted",
- \ "3 this shouldn't be deleted",
- \ "4 this should be deleted",
- \ "5 this shouldn't be deleted",
- \ "6 this shouldn't be deleted",
- \ "7 this shouldn't be deleted",
- \ "8 this shouldn't be deleted (not touched yet)"])
- call cursor(2, 1)
-
- " set compatible
- set backspace=2
-
- exe "normal Avim1\<C-U>\<Esc>\<CR>"
- exe "normal Avim2\<C-G>u\<C-U>\<Esc>\<CR>"
-
- set cpo-=<
- inoremap <c-u> <left><c-u>
- exe "normal Avim3\<*C-U>\<Esc>\<CR>"
- iunmap <c-u>
- exe "normal Avim4\<C-U>\<C-U>\<Esc>\<CR>"
-
- " Test with backspace set to the compatible setting
- set backspace= visualbell
- exe "normal A vim5\<Esc>A\<C-U>\<C-U>\<Esc>\<CR>"
- exe "normal A vim6\<Esc>Azwei\<C-G>u\<C-U>\<Esc>\<CR>"
-
- inoremap <c-u> <left><c-u>
- exe "normal A vim7\<*C-U>\<*C-U>\<Esc>\<CR>"
-
- call assert_equal([
- \ "1 this shouldn't be deleted",
- \ "2 this shouldn't be deleted",
- \ "3 this shouldn't be deleted",
- \ "4 this should be deleted3",
- \ "",
- \ "6 this shouldn't be deleted vim5",
- \ "7 this shouldn't be deleted vim6",
- \ "8 this shouldn't be deleted (not touched yet) vim7",
- \ ""], getline(1, '$'))
-
- " Reset values
- set compatible&vim
- set visualbell&vim
- set backspace&vim
-
- " Test new nostop option
- %d_
- let expected = "foo bar foobar"
- call setline(1, expected)
- call cursor(1, 8)
- exe ":norm! ianotherone\<c-u>"
- call assert_equal(expected, getline(1))
- call cursor(1, 8)
- exe ":norm! ianothertwo\<c-w>"
- call assert_equal(expected, getline(1))
-
- let content = getline(1)
- for value in ['indent,nostop', 'eol,nostop', 'indent,eol,nostop', 'indent,eol,start,nostop']
- exe ":set bs=".. value
- %d _
- call setline(1, content)
- let expected = " foobar"
- call cursor(1, 8)
- exe ":norm! ianotherone\<c-u>"
- call assert_equal(expected, getline(1), 'CTRL-U backspace value: '.. &bs)
- let expected = "foo foobar"
- call setline(1, content)
- call cursor(1, 8)
- exe ":norm! ianothertwo\<c-w>"
- call assert_equal(expected, getline(1), 'CTRL-W backspace value: '.. &bs)
- endfor
-
- " Reset options
- set compatible&vim
- set visualbell&vim
- set backspace&vim
- close!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim
deleted file mode 100644
index d304773da4..0000000000
--- a/src/nvim/testdir/test_backup.vim
+++ /dev/null
@@ -1,89 +0,0 @@
-" Tests for the backup function
-
-source check.vim
-
-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_backup_backupskip()
- set backup backupdir=. backupskip=*.txt
- 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
- call assert_false(filereadable('Xbackup.txt~'))
- 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
-
-" Test for using a non-existing directory as a backup directory
-func Test_non_existing_backupdir()
- throw 'Skipped: Nvim auto-creates backup directory'
- set backupdir=./non_existing_dir backupskip=
- call writefile(['line1'], 'Xfile')
- new Xfile
- call assert_fails('write', 'E510:')
- set backupdir&vim backupskip&vim
- call delete('Xfile')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_behave.vim b/src/nvim/testdir/test_behave.vim
deleted file mode 100644
index c26bfe7ce3..0000000000
--- a/src/nvim/testdir/test_behave.vim
+++ /dev/null
@@ -1,29 +0,0 @@
-" Test the :behave command
-
-func Test_behave()
- behave mswin
- call assert_equal('mouse,key', &selectmode)
- call assert_equal('popup', &mousemodel)
- call assert_equal('startsel,stopsel', &keymodel)
- call assert_equal('exclusive', &selection)
-
- behave xterm
- call assert_equal('', &selectmode)
- call assert_equal('extend', &mousemodel)
- call assert_equal('', &keymodel)
- call assert_equal('inclusive', &selection)
-
- set selection&
- set mousemodel&
- set keymodel&
- set selection&
-endfunc
-
-func Test_behave_completion()
- call feedkeys(":behave \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"behave mswin xterm', @:)
-endfunc
-
-func Test_behave_error()
- call assert_fails('behave x', 'E475:')
-endfunc
diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim
deleted file mode 100644
index 046acb81e1..0000000000
--- a/src/nvim/testdir/test_blob.vim
+++ /dev/null
@@ -1,367 +0,0 @@
-" Tests for the Blob types
-
-func TearDown()
- " Run garbage collection after every test
- call test_garbagecollect_now()
-endfunc
-
-" Tests for Blob type
-
-" Blob creation from constant
-func Test_blob_create()
- let b = 0zDEADBEEF
- call assert_equal(v:t_blob, type(b))
- call assert_equal(4, len(b))
- call assert_equal(0xDE, b[0])
- call assert_equal(0xAD, b[1])
- call assert_equal(0xBE, b[2])
- call assert_equal(0xEF, b[3])
- call assert_fails('let x = b[4]')
-
- call assert_equal(0xDE, get(b, 0))
- call assert_equal(0xEF, get(b, 3))
-
- call assert_fails('let b = 0z1', 'E973:')
- call assert_fails('let b = 0z1x', 'E973:')
- call assert_fails('let b = 0z12345', 'E973:')
-
- call assert_equal(0z, v:_null_blob)
-
- let b = 0z001122.33445566.778899.aabbcc.dd
- call assert_equal(0z00112233445566778899aabbccdd, b)
- call assert_fails('let b = 0z1.1')
- call assert_fails('let b = 0z.')
- call assert_fails('let b = 0z001122.')
- call assert_fails('call get("", 1)', 'E896:')
- call assert_equal(0, len(v:_null_blob))
-endfunc
-
-" assignment to a blob
-func Test_blob_assign()
- let b = 0zDEADBEEF
- let b2 = b[1:2]
- call assert_equal(0zADBE, b2)
-
- let bcopy = b[:]
- call assert_equal(b, bcopy)
- call assert_false(b is bcopy)
-
- let b = 0zDEADBEEF
- let b2 = b
- call assert_true(b is b2)
- let b[:] = 0z11223344
- call assert_equal(0z11223344, b)
- call assert_equal(0z11223344, b2)
- call assert_true(b is b2)
-
- let b = 0zDEADBEEF
- let b[3:] = 0z66
- call assert_equal(0zDEADBE66, b)
- let b[:1] = 0z8899
- call assert_equal(0z8899BE66, b)
-
- call assert_fails('let b[2:3] = 0z112233', 'E972:')
- call assert_fails('let b[2:3] = 0z11', 'E972:')
- call assert_fails('let b[3:2] = 0z', 'E979:')
-
- let b = 0zDEADBEEF
- let b += 0z99
- call assert_equal(0zDEADBEEF99, b)
-
- call assert_fails('let b .= 0z33', 'E734:')
- call assert_fails('let b .= "xx"', 'E734:')
- call assert_fails('let b += "xx"', 'E734:')
- call assert_fails('let b[1:1] .= 0z55', 'E734:')
-
- let l = [0z12]
- let m = deepcopy(l)
- let m[0] = 0z34 " E742 or E741 should not occur.
-endfunc
-
-func Test_blob_get_range()
- let b = 0z0011223344
- call assert_equal(0z2233, b[2:3])
- call assert_equal(0z223344, b[2:-1])
- call assert_equal(0z00, b[0:-5])
- call assert_equal(0z, b[0:-11])
- call assert_equal(0z44, b[-1:])
- call assert_equal(0z0011223344, b[:])
- call assert_equal(0z0011223344, b[:-1])
- call assert_equal(0z, b[5:6])
- call assert_equal(0z0011, b[-10:1])
-endfunc
-
-func Test_blob_get()
- let b = 0z0011223344
- call assert_equal(0x00, get(b, 0))
- call assert_equal(0x22, get(b, 2, 999))
- call assert_equal(0x44, get(b, 4))
- call assert_equal(0x44, get(b, -1))
- call assert_equal(-1, get(b, 5))
- call assert_equal(999, get(b, 5, 999))
- call assert_equal(-1, get(b, -8))
- call assert_equal(999, get(b, -8, 999))
- call assert_equal(10, get(v:_null_blob, 2, 10))
-
- call assert_equal(0x00, b[0])
- call assert_equal(0x22, b[2])
- call assert_equal(0x44, b[4])
- call assert_equal(0x44, b[-1])
- call assert_fails('echo b[5]', 'E979:')
- call assert_fails('echo b[-8]', 'E979:')
-endfunc
-
-func Test_blob_to_string()
- let b = 0z00112233445566778899aabbccdd
- call assert_equal('0z00112233.44556677.8899AABB.CCDD', string(b))
- call assert_equal(b, eval(string(b)))
- call remove(b, 4, -1)
- call assert_equal('0z00112233', string(b))
- call remove(b, 0, 3)
- call assert_equal('0z', string(b))
-endfunc
-
-func Test_blob_compare()
- let b1 = 0z0011
- let b2 = 0z1100
- let b3 = 0z001122
- call assert_true(b1 == b1)
- call assert_false(b1 == b2)
- call assert_false(b1 == b3)
- call assert_true(b1 != b2)
- call assert_true(b1 != b3)
- call assert_true(b1 == 0z0011)
- call assert_fails('echo b1 == 9', 'E977:')
- call assert_fails('echo b1 != 9', 'E977:')
-
- call assert_false(b1 is b2)
- let b2 = b1
- call assert_true(b1 == b2)
- call assert_true(b1 is b2)
- let b2 = copy(b1)
- call assert_true(b1 == b2)
- call assert_false(b1 is b2)
- let b2 = b1[:]
- call assert_true(b1 == b2)
- call assert_false(b1 is b2)
-
- call assert_fails('let x = b1 > b2')
- call assert_fails('let x = b1 < b2')
- call assert_fails('let x = b1 - b2')
- call assert_fails('let x = b1 / b2')
- call assert_fails('let x = b1 * b2')
-endfunc
-
-" test for range assign
-func Test_blob_range_assign()
- let b = 0z00
- let b[1] = 0x11
- let b[2] = 0x22
- call assert_equal(0z001122, b)
- call assert_fails('let b[4] = 0x33', 'E979:')
-endfunc
-
-func Test_blob_for_loop()
- let blob = 0z00010203
- let i = 0
- for byte in blob
- call assert_equal(i, byte)
- let i += 1
- endfor
- call assert_equal(4, i)
-
- let blob = 0z00
- call remove(blob, 0)
- call assert_equal(0, len(blob))
- for byte in blob
- call assert_error('loop over empty blob')
- endfor
-
- let blob = 0z0001020304
- let i = 0
- for byte in blob
- call assert_equal(i, byte)
- if i == 1
- call remove(blob, 0)
- elseif i == 3
- call remove(blob, 3)
- endif
- let i += 1
- endfor
- call assert_equal(5, i)
-endfunc
-
-func Test_blob_concatenate()
- let b = 0z0011
- let b += 0z2233
- call assert_equal(0z00112233, b)
-
- call assert_fails('let b += "a"')
- call assert_fails('let b += 88')
-
- let b = 0zDEAD + 0zBEEF
- call assert_equal(0zDEADBEEF, b)
-endfunc
-
-func Test_blob_add()
- let b = 0z0011
- call add(b, 0x22)
- call assert_equal(0z001122, b)
- call add(b, '51')
- call assert_equal(0z00112233, b)
- call assert_equal(1, add(v:_null_blob, 0x22))
-
- call assert_fails('call add(b, [9])', 'E745:')
- call assert_fails('call add("", 0x01)', 'E897:')
-endfunc
-
-func Test_blob_empty()
- call assert_false(empty(0z001122))
- call assert_true(empty(0z))
- call assert_true(empty(v:_null_blob))
-endfunc
-
-" Test removing items in blob
-func Test_blob_func_remove()
- " Test removing 1 element
- let b = 0zDEADBEEF
- call assert_equal(0xDE, remove(b, 0))
- call assert_equal(0zADBEEF, b)
-
- let b = 0zDEADBEEF
- call assert_equal(0xEF, remove(b, -1))
- call assert_equal(0zDEADBE, b)
-
- let b = 0zDEADBEEF
- call assert_equal(0xAD, remove(b, 1))
- call assert_equal(0zDEBEEF, b)
-
- " Test removing range of element(s)
- let b = 0zDEADBEEF
- call assert_equal(0zBE, remove(b, 2, 2))
- call assert_equal(0zDEADEF, b)
-
- let b = 0zDEADBEEF
- call assert_equal(0zADBE, remove(b, 1, 2))
- call assert_equal(0zDEEF, b)
-
- " Test invalid cases
- let b = 0zDEADBEEF
- call assert_fails("call remove(b, 5)", 'E979:')
- call assert_fails("call remove(b, 1, 5)", 'E979:')
- call assert_fails("call remove(b, 3, 2)", 'E979:')
- call assert_fails("call remove(1, 0)", 'E896:')
- call assert_fails("call remove(b, b)", 'E974:')
- call assert_fails("call remove(b, 1, [])", 'E745:')
- call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:')
-
- " Translated from v8.2.3284
- let b = 0zDEADBEEF
- lockvar b
- call assert_fails('call remove(b, 0)', 'E741:')
- unlockvar b
-endfunc
-
-func Test_blob_read_write()
- let b = 0zDEADBEEF
- call writefile(b, 'Xblob')
- let br = readfile('Xblob', 'B')
- call assert_equal(b, br)
- call delete('Xblob')
-
- " This was crashing when calling readfile() with a directory.
- call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
-endfunc
-
-" filter() item in blob
-func Test_blob_filter()
- call assert_equal(v:_null_blob, filter(v:_null_blob, '0'))
- call assert_equal(0z, filter(0zDEADBEEF, '0'))
- call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE'))
- call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE'))
- call assert_equal(0zDEADBE, filter(0zDEADBEEF, 'v:val != 0xEF'))
- call assert_equal(0zDEADBEEF, filter(0zDEADBEEF, '1'))
- call assert_equal(0z01030103, filter(0z010203010203, 'v:val != 0x02'))
- call assert_equal(0zADEF, filter(0zDEADBEEF, 'v:key % 2'))
-endfunc
-
-" map() item in blob
-func Test_blob_map()
- call assert_equal(0zDFAEBFF0, map(0zDEADBEEF, 'v:val + 1'))
- call assert_equal(0z00010203, map(0zDEADBEEF, 'v:key'))
- call assert_equal(0zDEAEC0F2, map(0zDEADBEEF, 'v:key + v:val'))
-
- call assert_fails("call map(0z00, '[9]')", 'E978:')
-endfunc
-
-func Test_blob_index()
- call assert_equal(2, index(0zDEADBEEF, 0xBE))
- call assert_equal(-1, index(0zDEADBEEF, 0))
- call assert_equal(2, index(0z11111111, 0x11, 2))
- call assert_equal(3, 0z11110111->index(0x11, 2))
- call assert_equal(2, index(0z11111111, 0x11, -2))
- call assert_equal(3, index(0z11110111, 0x11, -2))
- call assert_equal(0, index(0z11110111, 0x11, -10))
- call assert_fails("echo index(0z11110111, 0x11, [])", 'E745:')
- call assert_equal(-1, index(v:_null_blob, 1))
-
- call assert_fails('call index("asdf", 0)', 'E897:')
-endfunc
-
-func Test_blob_insert()
- let b = 0zDEADBEEF
- call insert(b, 0x33)
- call assert_equal(0z33DEADBEEF, b)
-
- let b = 0zDEADBEEF
- call insert(b, 0x33, 2)
- call assert_equal(0zDEAD33BEEF, b)
-
- call assert_fails('call insert(b, -1)', 'E475:')
- call assert_fails('call insert(b, 257)', 'E475:')
- call assert_fails('call insert(b, 0, [9])', 'E745:')
- call assert_fails('call insert(b, 0, -20)', 'E475:')
- call assert_fails('call insert(b, 0, 20)', 'E475:')
- call assert_fails('call insert(b, [])', 'E745:')
- call assert_equal(0, insert(v:_null_blob, 0x33))
-
- " Translated from v8.2.3284
- let b = 0zDEADBEEF
- lockvar b
- call assert_fails('call insert(b, 3)', 'E741:')
- unlockvar b
-endfunc
-
-func Test_blob_reverse()
- call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
- call assert_equal(0zBEADDE, reverse(0zDEADBE))
- call assert_equal(0zADDE, reverse(0zDEAD))
- call assert_equal(0zDE, reverse(0zDE))
- call assert_equal(0z, reverse(v:_null_blob))
-endfunc
-
-func Test_blob_lock()
- let b = 0z112233
- lockvar b
- call assert_fails('let b = 0z44', 'E741:')
- unlockvar b
- let b = 0z44
-endfunc
-
-func Test_blob_sort()
- if has('float')
- call assert_fails('call sort([1.0, 0z11], "f")', 'E975:')
- else
- call assert_fails('call sort(["abc", 0z11], "f")', 'E702:')
- endif
-endfunc
-
-" The following used to cause an out-of-bounds memory access
-func Test_blob2string()
- let v = '0z' .. repeat('01010101.', 444)
- let v ..= '01'
- exe 'let b = ' .. v
- call assert_equal(v, string(b))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim
deleted file mode 100644
index 7b56b1554f..0000000000
--- a/src/nvim/testdir/test_blockedit.vim
+++ /dev/null
@@ -1,132 +0,0 @@
-" Test for block inserting
-"
-
-func Test_blockinsert_indent()
- new
- filetype plugin indent on
- setlocal sw=2 et ft=vim
- call setline(1, ['let a=[', ' ''eins'',', ' ''zwei'',', ' ''drei'']'])
- call cursor(2, 3)
- exe "norm! \<c-v>2jI\\ \<esc>"
- call assert_equal(['let a=[', ' \ ''eins'',', ' \ ''zwei'',', ' \ ''drei'']'],
- \ getline(1,'$'))
- " reset to sane state
- filetype off
- bwipe!
-endfunc
-
-func Test_blockinsert_autoindent()
- new
- let lines =<< trim END
- var d = {
- a: () => 0,
- b: () => 0,
- c: () => 0,
- }
- END
- call setline(1, lines)
- filetype plugin indent on
- setlocal sw=2 et ft=vim
- setlocal indentkeys+=:
- exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>"
- let expected =<< trim END
- var d = {
- a: (): asdf => 0,
- b: (): asdf => 0,
- c: (): asdf => 0,
- }
- END
- call assert_equal(expected, getline(1, 5))
-
- " insert on the next column should do exactly the same
- :%dele
- call setline(1, lines)
- exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>"
- call assert_equal(expected, getline(1, 5))
-
- :%dele
- call setline(1, lines)
- setlocal sw=8 noet
- exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>"
- let expected =<< trim END
- var d = {
- a: (): asdf => 0,
- b: (): asdf => 0,
- c: (): asdf => 0,
- }
- END
- call assert_equal(expected, getline(1, 5))
-
- " insert on the next column should do exactly the same
- :%dele
- call setline(1, lines)
- exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>"
- call assert_equal(expected, getline(1, 5))
-
- filetype off
- bwipe!
-endfunc
-
-func Test_blockinsert_delete()
- new
- let _bs = &bs
- set bs=2
- call setline(1, ['case Arg is ', ' when Name_Async,', ' when Name_Num_Gangs,', 'end if;'])
- exe "norm! ggjVj\<c-v>$o$A\<bs>\<esc>"
- "call feedkeys("Vj\<c-v>$o$A\<bs>\<esc>", 'ti')
- call assert_equal(["case Arg is ", " when Name_Async", " when Name_Num_Gangs,", "end if;"],
- \ getline(1,'$'))
- " reset to sane state
- let &bs = _bs
- bwipe!
-endfunc
-
-func Test_blockappend_eol_cursor()
- new
- " Test 1 Move 1 char left
- call setline(1, ['aaa', 'bbb', 'ccc'])
- exe "norm! gg$\<c-v>2jA\<left>x\<esc>"
- call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
- " Test 2 Move 2 chars left
- sil %d
- call setline(1, ['aaa', 'bbb', 'ccc'])
- exe "norm! gg$\<c-v>2jA\<left>\<left>x\<esc>"
- call assert_equal(['axaa', 'bxbb', 'cxcc'], getline(1, '$'))
- " Test 3 Move 3 chars left (outside of the visual selection)
- sil %d
- call setline(1, ['aaa', 'bbb', 'ccc'])
- exe "norm! ggl$\<c-v>2jA\<left>\<left>\<left>x\<esc>"
- call assert_equal(['xaaa', 'bbb', 'ccc'], getline(1, '$'))
- bw!
-endfunc
-
-func Test_blockappend_eol_cursor2()
- new
- " Test 1 Move 1 char left
- call setline(1, ['aaaaa', 'bbb', 'ccccc'])
- exe "norm! gg\<c-v>$2jA\<left>x\<esc>"
- call assert_equal(['aaaaxa', 'bbbx', 'ccccxc'], getline(1, '$'))
- " Test 2 Move 2 chars left
- sil %d
- call setline(1, ['aaaaa', 'bbb', 'ccccc'])
- exe "norm! gg\<c-v>$2jA\<left>\<left>x\<esc>"
- call assert_equal(['aaaxaa', 'bbbx', 'cccxcc'], getline(1, '$'))
- " Test 3 Move 3 chars left (to the beginning of the visual selection)
- sil %d
- call setline(1, ['aaaaa', 'bbb', 'ccccc'])
- exe "norm! gg\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
- call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
- " Test 4 Move 3 chars left (outside of the visual selection)
- sil %d
- call setline(1, ['aaaaa', 'bbb', 'ccccc'])
- exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
- call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
- " Test 5 Move 4 chars left (outside of the visual selection)
- sil %d
- call setline(1, ['aaaaa', 'bbb', 'ccccc'])
- exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>\<left>x\<esc>"
- call assert_equal(['axaaaa', 'bxbb', 'cxcccc'], getline(1, '$'))
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
deleted file mode 100644
index 2e377aa434..0000000000
--- a/src/nvim/testdir/test_breakindent.vim
+++ /dev/null
@@ -1,1078 +0,0 @@
-" Test for breakindent
-"
-" Note: if you get strange failures when adding new tests, it might be that
-" while the test is run, the breakindent caching gets in its way.
-" It helps to change the tabstop setting and force a redraw (e.g. see
-" Test_breakindent08())
-source check.vim
-CheckOption breakindent
-
-source view_util.vim
-source screendump.vim
-
-let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
-
-func s:screen_lines(lnum, width) abort
- return ScreenLines([a:lnum, a:lnum + 2], a:width)
-endfunc
-
-func s:screen_lines2(lnums, lnume, width) abort
- return ScreenLines([a:lnums, a:lnume], a:width)
-endfunc
-
-func s:compare_lines(expect, actual)
- call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
-endfunc
-
-func s:test_windows(...)
- call NewWindow(10, 20)
- setl ts=4 sw=4 sts=4 breakindent
- put =s:input
- exe get(a:000, 0, '')
-endfunc
-
-func s:close_windows(...)
- call CloseWindow()
- exe get(a:000, 0, '')
-endfunc
-
-func Test_breakindent01()
- " simple breakindent test
- call s:test_windows('setl briopt=min:0')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qrst",
- \ " GHIJ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_breakindent01_vartabs()
- " like 01 but with vartabs feature
- if !has("vartabs")
- return
- endif
- call s:test_windows('setl briopt=min:0 vts=4')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qrst",
- \ " GHIJ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set vts&')
-endfunc
-
-func Test_breakindent02()
- " simple breakindent test with showbreak set
- set sbr=>>
- call s:test_windows('setl briopt=min:0 sbr=')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " >>qr",
- \ " >>EF",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr=')
-endfunc
-
-func Test_breakindent02_vartabs()
- if !has("vartabs")
- return
- endif
- " simple breakindent test with showbreak set
- call s:test_windows('setl briopt=min:0 sbr=>> vts=4')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " >>qr",
- \ " >>EF",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr= vts&')
-endfunc
-
-func Test_breakindent03()
- " simple breakindent test with showbreak set and briopt including sbr
- call s:test_windows('setl briopt=sbr,min:0 sbr=++')
- let lines = s:screen_lines(line('.'),8)
- let expect=[
-\ " abcd",
-\ "++ qrst",
-\ "++ GHIJ",
-\ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr=')
-endfunc
-
-func Test_breakindent03_vartabs()
- " simple breakindent test with showbreak set and briopt including sbr
- if !has("vartabs")
- return
- endif
- call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ "++ qrst",
- \ "++ GHIJ",
- \ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr= vts&')
-endfunc
-
-func Test_breakindent04()
- " breakindent set with min width 18
- set sbr=<<<
- call s:test_windows('setl sbr=NONE briopt=min:18')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qrstuv",
- \ " IJKLMN",
- \ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr=')
- set sbr=
-endfunc
-
-func Test_breakindent04_vartabs()
- " breakindent set with min width 18
- if !has("vartabs")
- return
- endif
- call s:test_windows('setl sbr= briopt=min:18 vts=4')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qrstuv",
- \ " IJKLMN",
- \ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr= vts&')
-endfunc
-
-func Test_breakindent05()
- " breakindent set and shift by 2
- call s:test_windows('setl briopt=shift:2,min:0')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qr",
- \ " EF",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_breakindent05_vartabs()
- " breakindent set and shift by 2
- if !has("vartabs")
- return
- endif
- call s:test_windows('setl briopt=shift:2,min:0 vts=4')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qr",
- \ " EF",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set vts&')
-endfunc
-
-func Test_breakindent06()
- " breakindent set and shift by -1
- call s:test_windows('setl briopt=shift:-1,min:0')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qrstu",
- \ " HIJKL",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_breakindent06_vartabs()
- " breakindent set and shift by -1
- if !has("vartabs")
- return
- endif
- call s:test_windows('setl briopt=shift:-1,min:0 vts=4')
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ " abcd",
- \ " qrstu",
- \ " HIJKL",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set vts&')
-endfunc
-
-func Test_breakindent07()
- " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ab",
- \ "? m",
- \ "? x",
- \ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr= cpo-=n')
-endfunc
-
-func Test_breakindent07_vartabs()
- if !has("vartabs")
- return
- endif
- " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n vts=4')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ab",
- \ "? m",
- \ "? x",
- \ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr= cpo-=n vts&')
-endfunc
-
-func Test_breakindent07a()
- " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ab",
- \ " ? m",
- \ " ? x",
- \ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr=')
-endfunc
-
-func Test_breakindent07a_vartabs()
- if !has("vartabs")
- return
- endif
- " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 vts=4')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ab",
- \ " ? m",
- \ " ? x",
- \ ]
- call s:compare_lines(expect, lines)
- " clean up
- call s:close_windows('set sbr= vts&')
-endfunc
-
-func Test_breakindent08()
- " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4')
- " make sure, cache is invalidated!
- set ts=8
- redraw!
- set ts=4
- redraw!
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ^Iabcd",
- \ "# opq",
- \ "# BCD",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr= cpo-=n')
-endfunc
-
-func Test_breakindent08_vartabs()
- if !has("vartabs")
- return
- endif
- " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4 vts=4')
- " make sure, cache is invalidated!
- set ts=8
- redraw!
- set ts=4
- redraw!
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ^Iabcd",
- \ "# opq",
- \ "# BCD",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr= cpo-=n vts&')
-endfunc
-
-func Test_breakindent08a()
- " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ^Iabcd",
- \ " # opq",
- \ " # BCD",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr=')
-endfunc
-
-func Test_breakindent08a_vartabs()
- if !has("vartabs")
- return
- endif
- " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
- call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list vts=4')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ^Iabcd",
- \ " # opq",
- \ " # BCD",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr= vts&')
-endfunc
-
-func Test_breakindent09()
- " breakindent set and shift by 1, Number and list set sbr=#
- call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ^Iabcd",
- \ " #op",
- \ " #AB",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr=')
-endfunc
-
-func Test_breakindent09_vartabs()
- if !has("vartabs")
- return
- endif
- " breakindent set and shift by 1, Number and list set sbr=#
- call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list vts=4')
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ^Iabcd",
- \ " #op",
- \ " #AB",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr= vts&')
-endfunc
-
-func Test_breakindent10()
- " breakindent set, Number set sbr=~
- call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0')
- " make sure, cache is invalidated!
- set ts=8
- redraw!
- set ts=4
- redraw!
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ab",
- \ "~ mn",
- \ "~ yz",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr= cpo-=n')
-endfunc
-
-func Test_breakindent10_vartabs()
- if !has("vartabs")
- return
- endif
- " breakindent set, Number set sbr=~
- call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0 vts=4')
- " make sure, cache is invalidated!
- set ts=8
- redraw!
- set ts=4
- redraw!
- let lines = s:screen_lines(line('.'),10)
- let expect = [
- \ " 2 ab",
- \ "~ mn",
- \ "~ yz",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set sbr= cpo-=n vts&')
-endfunc
-
-func Test_breakindent11()
- " test strdisplaywidth()
- call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4')
- let text = getline(2)
- let width = strlen(text[1:]) + indent(2) + strlen(&sbr) * 3 " text wraps 3 times
- call assert_equal(width, strdisplaywidth(text))
- call s:close_windows('set sbr=')
- call assert_equal(4, strdisplaywidth("\t", 4))
-endfunc
-
-func Test_breakindent11_vartabs()
- if !has("vartabs")
- return
- endif
- " test strdisplaywidth()
- call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4')
- let text = getline(2)
- let width = strlen(text[1:]) + 2->indent() + strlen(&sbr) * 3 " text wraps 3 times
- call assert_equal(width, text->strdisplaywidth())
- call s:close_windows('set sbr= vts&')
-endfunc
-
-func Test_breakindent12()
- " test breakindent with long indent
- let s:input="\t\t\t\t\t{"
- call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>-')
- let lines = s:screen_lines(2,16)
- let expect = [
- \ " 2 >--->--->--->",
- \ " ---{ ",
- \ "~ ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set nuw=4 listchars=')
-endfunc
-
-func Test_breakindent12_vartabs()
- if !has("vartabs")
- return
- endif
- " test breakindent with long indent
- let s:input = "\t\t\t\t\t{"
- call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>- vts=4')
- let lines = s:screen_lines(2,16)
- let expect = [
- \ " 2 >--->--->--->",
- \ " ---{ ",
- \ "~ ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set nuw=4 listchars= vts&')
-endfunc
-
-func Test_breakindent13()
- let s:input = ""
- call s:test_windows('setl breakindent briopt=min:10 ts=8')
- vert resize 20
- call setline(1, [" a\tb\tc\td\te", " z y x w v"])
- 1
- norm! fbgj"ayl
- 2
- norm! fygj"byl
- call assert_equal('d', @a)
- call assert_equal('w', @b)
- call s:close_windows()
-endfunc
-
-func Test_breakindent13_vartabs()
- if !has("vartabs")
- return
- endif
- let s:input = ""
- call s:test_windows('setl breakindent briopt=min:10 ts=8 vts=8')
- vert resize 20
- call setline(1, [" a\tb\tc\td\te", " z y x w v"])
- 1
- norm! fbgj"ayl
- 2
- norm! fygj"byl
- call assert_equal('d', @a)
- call assert_equal('w', @b)
- call s:close_windows('set vts&')
-endfunc
-
-func Test_breakindent14()
- let s:input = ""
- call s:test_windows('setl breakindent briopt= ts=8')
- vert resize 30
- norm! 3a1234567890
- norm! a abcde
- exec "norm! 0\<C-V>tex"
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ "e ",
- \ "~ ",
- \ "~ ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_breakindent14_vartabs()
- if !has("vartabs")
- return
- endif
- let s:input = ""
- call s:test_windows('setl breakindent briopt= ts=8 vts=8')
- vert resize 30
- norm! 3a1234567890
- norm! a abcde
- exec "norm! 0\<C-V>tex"
- let lines = s:screen_lines(line('.'),8)
- let expect = [
- \ "e ",
- \ "~ ",
- \ "~ ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set vts&')
-endfunc
-
-func Test_breakindent15()
- let s:input = ""
- call s:test_windows('setl breakindent briopt= ts=8 sw=8')
- vert resize 30
- norm! 4a1234567890
- exe "normal! >>\<C-V>3f0x"
- let lines = s:screen_lines(line('.'),20)
- let expect = [
- \ " 1234567890 ",
- \ "~ ",
- \ "~ ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_breakindent15_vartabs()
- if !has("vartabs")
- return
- endif
- let s:input = ""
- call s:test_windows('setl breakindent briopt= ts=8 sw=8 vts=8')
- vert resize 30
- norm! 4a1234567890
- exe "normal! >>\<C-V>3f0x"
- let lines = s:screen_lines(line('.'),20)
- let expect = [
- \ " 1234567890 ",
- \ "~ ",
- \ "~ ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set vts&')
-endfunc
-
-func Test_breakindent16()
- " Check that overlong lines are indented correctly.
- let s:input = ""
- call s:test_windows('setl breakindent briopt=min:0 ts=4')
- call setline(1, "\t".repeat("1234567890", 10))
- resize 6
- norm! 1gg$
- redraw!
- let lines = s:screen_lines(1,10)
- let expect = [
- \ " 789012",
- \ " 345678",
- \ " 901234",
- \ ]
- call s:compare_lines(expect, lines)
- let lines = s:screen_lines(4,10)
- let expect = [
- \ " 567890",
- \ " 123456",
- \ " 7890 ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_breakindent16_vartabs()
- if !has("vartabs")
- return
- endif
- " Check that overlong lines are indented correctly.
- let s:input = ""
- call s:test_windows('setl breakindent briopt=min:0 ts=4 vts=4')
- call setline(1, "\t".repeat("1234567890", 10))
- resize 6
- norm! 1gg$
- redraw!
- let lines = s:screen_lines(1,10)
- let expect = [
- \ " 789012",
- \ " 345678",
- \ " 901234",
- \ ]
- call s:compare_lines(expect, lines)
- let lines = s:screen_lines(4,10)
- let expect = [
- \ " 567890",
- \ " 123456",
- \ " 7890 ",
- \ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('set vts&')
-endfunc
-
-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
- let lines = s:screen_lines(1, 20)
- let expect = [
- \ "aaaaaaaaaaaaaaaaaaaa",
- \ "> aaaaaaaaaaaaaaaaaa",
- \ "> aaaaaaaaaaaaaaaaaa",
- \ ]
- call s:compare_lines(expect, lines)
- redraw!
- " moving the cursor doesn't change the text offset
- norm! l
- redraw!
- let lines = s:screen_lines(1, 20)
- 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
-
-func Test_breakindent20_list()
- call s:test_windows('setl breakindent breakindentopt= linebreak')
- " default:
- call setline(1, [' 1. Congress shall make no law',
- \ ' 2.) Congress shall make no law',
- \ ' 3.] Congress shall make no law'])
- norm! 1gg
- redraw!
- let lines = s:screen_lines2(1, 6, 20)
- let expect = [
- \ " 1. Congress ",
- \ "shall make no law ",
- \ " 2.) Congress ",
- \ "shall make no law ",
- \ " 3.] Congress ",
- \ "shall make no law ",
- \ ]
- call s:compare_lines(expect, lines)
- " set minimum indent
- setl briopt=min:5
- redraw!
- let lines = s:screen_lines2(1, 6, 20)
- let expect = [
- \ " 1. Congress ",
- \ " shall make no law ",
- \ " 2.) Congress ",
- \ " shall make no law ",
- \ " 3.] Congress ",
- \ " shall make no law ",
- \ ]
- call s:compare_lines(expect, lines)
- " set additional handing indent
- setl briopt+=list:4
- redraw!
- let expect = [
- \ " 1. Congress ",
- \ " shall make no ",
- \ " law ",
- \ " 2.) Congress ",
- \ " shall make no ",
- \ " law ",
- \ " 3.] Congress ",
- \ " shall make no ",
- \ " law ",
- \ ]
- let lines = s:screen_lines2(1, 9, 20)
- call s:compare_lines(expect, lines)
-
- " reset linebreak option
- " Note: it indents by one additional
- " space, because of the leading space.
- setl linebreak&vim list listchars=eol:$,space:_
- redraw!
- let expect = [
- \ "__1.__Congress_shall",
- \ " _make_no_law$ ",
- \ "__2.)_Congress_shall",
- \ " _make_no_law$ ",
- \ "__3.]_Congress_shall",
- \ " _make_no_law$ ",
- \ ]
- let lines = s:screen_lines2(1, 6, 20)
- call s:compare_lines(expect, lines)
-
- " check formatlistpat indent
- setl briopt=min:5,list:-1
- setl linebreak list&vim listchars&vim
- let &l:flp = '^\s*\d\+\.\?[\]:)}\t ]\s*'
- redraw!
- let expect = [
- \ " 1. Congress ",
- \ " shall make no ",
- \ " law ",
- \ " 2.) Congress ",
- \ " shall make no ",
- \ " law ",
- \ " 3.] Congress ",
- \ " shall make no ",
- \ " law ",
- \ ]
- let lines = s:screen_lines2(1, 9, 20)
- call s:compare_lines(expect, lines)
- " check formatlistpat indent with different list levels
- let &l:flp = '^\s*\*\+\s\+'
- redraw!
- %delete _
- call setline(1, ['* Congress shall make no law',
- \ '*** Congress shall make no law',
- \ '**** Congress shall make no law'])
- norm! 1gg
- let expect = [
- \ "* Congress shall ",
- \ " make no law ",
- \ "*** Congress shall ",
- \ " make no law ",
- \ "**** Congress shall ",
- \ " make no law ",
- \ ]
- let lines = s:screen_lines2(1, 6, 20)
- call s:compare_lines(expect, lines)
-
- " check formatlistpat indent with different list level
- " showbreak and sbr
- setl briopt=min:5,sbr,list:-1
- setl showbreak=>
- redraw!
- let expect = [
- \ "* Congress shall ",
- \ "> make no law ",
- \ "*** Congress shall ",
- \ "> make no law ",
- \ "**** Congress shall ",
- \ "> make no law ",
- \ ]
- let lines = s:screen_lines2(1, 6, 20)
- call s:compare_lines(expect, lines)
-
- " check formatlistpat indent with different list level
- " showbreak sbr and shift
- setl briopt=min:5,sbr,list:-1,shift:2
- setl showbreak=>
- redraw!
- let expect = [
- \ "* Congress shall ",
- \ "> make no law ",
- \ "*** Congress shall ",
- \ "> make no law ",
- \ "**** Congress shall ",
- \ "> make no law ",
- \ ]
- let lines = s:screen_lines2(1, 6, 20)
- call s:compare_lines(expect, lines)
-
- " check breakindent works if breakindentopt=list:-1
- " for a non list content
- %delete _
- call setline(1, [' Congress shall make no law',
- \ ' Congress shall make no law',
- \ ' Congress shall make no law'])
- norm! 1gg
- setl briopt=min:5,list:-1
- setl showbreak=
- redraw!
- let expect = [
- \ " Congress shall ",
- \ " make no law ",
- \ " Congress shall ",
- \ " make no law ",
- \ " Congress shall ",
- \ " make no law ",
- \ ]
- let lines = s:screen_lines2(1, 6, 20)
- call s:compare_lines(expect, lines)
-
- call s:close_windows('set breakindent& briopt& linebreak& list& listchars& showbreak&')
-endfunc
-
-" The following used to crash Vim. This is fixed by 8.2.3391.
-" This is a regression introduced by 8.2.2903.
-func Test_window_resize_with_linebreak()
- new
- 53vnew
- setl linebreak
- setl showbreak=>>
- setl breakindent
- setl breakindentopt=shift:4
- call setline(1, "\naaaaaaaaa\n\na\naaaaa\n¯aaaaaaaaaa\naaaaaaaaaaaa\naaa\n\"a:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaa\"\naaaaaaaa\n\"a")
- redraw!
- call assert_equal([" >>aa^@\"a: "], ScreenLines(2, 14))
- vertical resize 52
- redraw!
- call assert_equal([" >>aaa^@\"a:"], ScreenLines(2, 14))
- set linebreak& showbreak& breakindent& breakindentopt&
- %bw!
-endfunc
-
-func Test_cursor_position_with_showbreak()
- CheckScreendump
-
- let lines =<< trim END
- vim9script
- &signcolumn = 'yes'
- &showbreak = '+ '
- var leftcol: number = win_getid()->getwininfo()->get(0, {})->get('textoff')
- repeat('x', &columns - leftcol - 1)->setline(1)
- 'second line'->setline(2)
- END
- call writefile(lines, 'XscriptShowbreak')
- let buf = RunVimInTerminal('-S XscriptShowbreak', #{rows: 6})
-
- call term_sendkeys(buf, "AX")
- call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak', {})
-
- call StopVimInTerminal(buf)
- call delete('XscriptShowbreak')
-endfunc
-
-func Test_no_spurious_match()
- let s:input = printf('- y %s y %s', repeat('x', 50), repeat('x', 50))
- call s:test_windows('setl breakindent breakindentopt=list:-1 formatlistpat=^- hls')
- let @/ = '\%>3v[y]'
- redraw!
- call searchcount().total->assert_equal(1)
- " cleanup
- set hls&vim
- let s:input = "\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
- bwipeout!
-endfunc
-
-func Test_no_extra_indent()
- call s:test_windows('setl breakindent breakindentopt=list:-1,min:10')
- %d
- let &l:formatlistpat='^\s*\d\+\.\s\+'
- let text = 'word '
- let len = text->strcharlen()
- let line1 = text->repeat((winwidth(0) / len) * 2)
- let line2 = repeat(' ', 2) .. '1. ' .. line1
- call setline(1, [line2])
- redraw!
- " 1) matches formatlist pattern, so indent
- let expect = [
- \ " 1. word word word ",
- \ " word word word ",
- \ " word word ",
- \ "~ ",
- \ ]
- let lines = s:screen_lines2(1, 4, 20)
- call s:compare_lines(expect, lines)
- " 2) change formatlist pattern
- " -> indent adjusted
- let &l:formatlistpat='^\s*\d\+\.'
- let expect = [
- \ " 1. word word word ",
- \ " word word word ",
- \ " word word ",
- \ "~ ",
- \ ]
- let lines = s:screen_lines2(1, 4, 20)
- " 3) no local formatlist pattern,
- " so use global one -> indent
- let g_flp = &g:flp
- let &g:formatlistpat='^\s*\d\+\.\s\+'
- let &l:formatlistpat=''
- let expect = [
- \ " 1. word word word ",
- \ " word word word ",
- \ " word word ",
- \ "~ ",
- \ ]
- let lines = s:screen_lines2(1, 4, 20)
- call s:compare_lines(expect, lines)
- let &g:flp = g_flp
- let &l:formatlistpat='^\s*\d\+\.'
- " 4) add something in front, no additional indent
- norm! gg0
- exe ":norm! 5iword \<esc>"
- redraw!
- let expect = [
- \ "word word word word ",
- \ "word 1. word word ",
- \ "word word word word ",
- \ "word word ",
- \ "~ ",
- \ ]
- let lines = s:screen_lines2(1, 5, 20)
- call s:compare_lines(expect, lines)
- bwipeout!
-endfunc
-
-func Test_breakindent_column()
- " restore original
- let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
- call s:test_windows('setl breakindent breakindentopt=column:10')
- redraw!
- " 1) default: does not indent, too wide :(
- let expect = [
- \ " ",
- \ " abcdefghijklmnop",
- \ "qrstuvwxyzABCDEFGHIJ",
- \ "KLMNOP "
- \ ]
- let lines = s:screen_lines2(1, 4, 20)
- call s:compare_lines(expect, lines)
- " 2) lower min value, so that breakindent works
- setl breakindentopt+=min:5
- redraw!
- let expect = [
- \ " ",
- \ " abcdefghijklmnop",
- \ " qrstuvwxyz",
- \ " ABCDEFGHIJ",
- \ " KLMNOP "
- \ ]
- let lines = s:screen_lines2(1, 5, 20)
- " 3) set shift option -> no influence
- setl breakindentopt+=shift:5
- redraw!
- let expect = [
- \ " ",
- \ " abcdefghijklmnop",
- \ " qrstuvwxyz",
- \ " ABCDEFGHIJ",
- \ " KLMNOP "
- \ ]
- let lines = s:screen_lines2(1, 5, 20)
- call s:compare_lines(expect, lines)
- " 4) add showbreak value
- setl showbreak=++
- redraw!
- let expect = [
- \ " ",
- \ " abcdefghijklmnop",
- \ " ++qrstuvwx",
- \ " ++yzABCDEF",
- \ " ++GHIJKLMN",
- \ " ++OP "
- \ ]
- let lines = s:screen_lines2(1, 6, 20)
- call s:compare_lines(expect, lines)
- bwipeout!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim
deleted file mode 100644
index 9220cc17b9..0000000000
--- a/src/nvim/testdir/test_buffer.vim
+++ /dev/null
@@ -1,454 +0,0 @@
-" Tests for Vim buffer
-
-source check.vim
-
-" Test for the :bunload command with an offset
-func Test_bunload_with_offset()
- %bwipe!
- call writefile(['B1'], 'b1')
- call writefile(['B2'], 'b2')
- call writefile(['B3'], 'b3')
- call writefile(['B4'], 'b4')
-
- " Load four buffers. Unload the second and third buffers and then
- " execute .+3bunload to unload the last buffer.
- edit b1
- new b2
- new b3
- new b4
-
- bunload b2
- bunload b3
- exe bufwinnr('b1') . 'wincmd w'
- .+3bunload
- call assert_equal(0, getbufinfo('b4')[0].loaded)
- call assert_equal('b1',
- \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
-
- " Load four buffers. Unload the third and fourth buffers. Execute .+3bunload
- " and check whether the second buffer is unloaded.
- ball
- bunload b3
- bunload b4
- exe bufwinnr('b1') . 'wincmd w'
- .+3bunload
- call assert_equal(0, getbufinfo('b2')[0].loaded)
- call assert_equal('b1',
- \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
-
- " Load four buffers. Unload the second and third buffers and from the last
- " buffer execute .-3bunload to unload the first buffer.
- ball
- bunload b2
- bunload b3
- exe bufwinnr('b4') . 'wincmd w'
- .-3bunload
- call assert_equal(0, getbufinfo('b1')[0].loaded)
- call assert_equal('b4',
- \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
-
- " Load four buffers. Unload the first and second buffers. Execute .-3bunload
- " from the last buffer and check whether the third buffer is unloaded.
- ball
- bunload b1
- bunload b2
- exe bufwinnr('b4') . 'wincmd w'
- .-3bunload
- call assert_equal(0, getbufinfo('b3')[0].loaded)
- call assert_equal('b4',
- \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
-
- %bwipe!
- call delete('b1')
- call delete('b2')
- call delete('b3')
- call delete('b4')
-
- call assert_fails('1,4bunload', 'E16:')
- call assert_fails(',100bunload', 'E16:')
-
- call assert_fails('$bunload', 'E90:')
-endfunc
-
-" Test for :buffer, :bnext, :bprevious, :brewind, :blast and :bmodified
-" commands
-func Test_buflist_browse()
- %bwipe!
- call assert_fails('buffer 1000', 'E86:')
-
- call writefile(['foo1', 'foo2', 'foo3', 'foo4'], 'Xfile1')
- call writefile(['bar1', 'bar2', 'bar3', 'bar4'], 'Xfile2')
- call writefile(['baz1', 'baz2', 'baz3', 'baz4'], 'Xfile3')
- edit Xfile1
- let b1 = bufnr()
- edit Xfile2
- let b2 = bufnr()
- edit +/baz4 Xfile3
- let b3 = bufnr()
-
- call assert_fails('buffer ' .. b1 .. ' abc', 'E488:')
- call assert_equal(b3, bufnr())
- call assert_equal(4, line('.'))
- exe 'buffer +/bar2 ' .. b2
- call assert_equal(b2, bufnr())
- call assert_equal(2, line('.'))
- exe 'buffer +/bar1'
- call assert_equal(b2, bufnr())
- call assert_equal(1, line('.'))
-
- brewind +
- call assert_equal(b1, bufnr())
- call assert_equal(4, line('.'))
-
- blast +/baz2
- call assert_equal(b3, bufnr())
- call assert_equal(2, line('.'))
-
- bprevious +/bar4
- call assert_equal(b2, bufnr())
- call assert_equal(4, line('.'))
-
- bnext +/baz3
- call assert_equal(b3, bufnr())
- call assert_equal(3, line('.'))
-
- call assert_fails('bmodified', 'E84:')
- call setbufvar(b2, '&modified', 1)
- exe 'bmodified +/bar3'
- call assert_equal(b2, bufnr())
- call assert_equal(3, line('.'))
-
- " With no listed buffers in the list, :bnext and :bprev should fail
- %bwipe!
- set nobuflisted
- call assert_fails('bnext', 'E85:')
- call assert_fails('bprev', 'E85:')
- set buflisted
-
- call assert_fails('sandbox bnext', 'E48:')
-
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
- %bwipe!
-endfunc
-
-" Test for :bdelete
-func Test_bdelete_cmd()
- %bwipe!
- call assert_fails('bdelete 5', 'E516:')
- call assert_fails('1,1bdelete 1 2', 'E488:')
- call assert_fails('bdelete \)', 'E55:')
-
- " Deleting a unlisted and unloaded buffer
- edit Xfile1
- let bnr = bufnr()
- set nobuflisted
- enew
- call assert_fails('bdelete ' .. bnr, 'E516:')
-
- " Deleting more than one buffer
- new Xbuf1
- new Xbuf2
- exe 'bdel ' .. bufnr('Xbuf2') .. ' ' .. bufnr('Xbuf1')
- call assert_equal(1, winnr('$'))
- call assert_equal(0, getbufinfo('Xbuf1')[0].loaded)
- call assert_equal(0, getbufinfo('Xbuf2')[0].loaded)
-
- " Deleting more than one buffer and an invalid buffer
- new Xbuf1
- new Xbuf2
- let cmd = "exe 'bdel ' .. bufnr('Xbuf2') .. ' xxx ' .. bufnr('Xbuf1')"
- call assert_fails(cmd, 'E94:')
- call assert_equal(2, winnr('$'))
- call assert_equal(1, getbufinfo('Xbuf1')[0].loaded)
- call assert_equal(0, getbufinfo('Xbuf2')[0].loaded)
-
- %bwipe!
-endfunc
-
-func Test_buffer_error()
- new foo1
- new foo2
-
- call assert_fails('buffer foo', 'E93:')
- call assert_fails('buffer bar', 'E94:')
- call assert_fails('buffer 0', 'E939:')
-
- %bwipe
-endfunc
-
-" Test for the status messages displayed when unloading, deleting or wiping
-" out buffers
-func Test_buffer_statusmsg()
- CheckEnglish
- set report=1
- new Xbuf1
- new Xbuf2
- let bnr = bufnr()
- exe "normal 2\<C-G>"
- call assert_match('buf ' .. bnr .. ':', v:statusmsg)
- bunload Xbuf1 Xbuf2
- call assert_equal('2 buffers unloaded', v:statusmsg)
- bdel Xbuf1 Xbuf2
- call assert_equal('2 buffers deleted', v:statusmsg)
- bwipe Xbuf1 Xbuf2
- call assert_equal('2 buffers wiped out', v:statusmsg)
- set report&
-endfunc
-
-" Test for quitting the 'swapfile exists' dialog with the split buffer
-" command.
-func Test_buffer_sbuf_cleanup()
- call writefile([], 'Xfile')
- " first open the file in a buffer
- new Xfile
- let bnr = bufnr()
- close
- " create the swap file
- call writefile([], '.Xfile.swp')
- " Remove the catch-all that runtest.vim adds
- au! SwapExists
- augroup BufTest
- au!
- autocmd SwapExists Xfile let v:swapchoice='q'
- augroup END
- exe 'sbuf ' . bnr
- call assert_equal(1, winnr('$'))
- call assert_equal(0, getbufinfo('Xfile')[0].loaded)
-
- " test for :sball
- sball
- call assert_equal(1, winnr('$'))
- call assert_equal(0, getbufinfo('Xfile')[0].loaded)
-
- %bw!
- set shortmess+=F
- let v:statusmsg = ''
- edit Xfile
- call assert_equal('', v:statusmsg)
- call assert_equal(1, winnr('$'))
- call assert_equal(0, getbufinfo('Xfile')[0].loaded)
- set shortmess&
-
- call delete('Xfile')
- call delete('.Xfile.swp')
- augroup BufTest
- au!
- augroup END
- augroup! BufTest
-endfunc
-
-" Test for deleting a modified buffer with :confirm
-func Test_bdel_with_confirm()
- " requires a UI to be active
- throw 'Skipped: use test/functional/legacy/buffer_spec.lua'
- CheckUnix
- CheckNotGui
- CheckFeature dialog_con
- new
- call setline(1, 'test')
- call assert_fails('bdel', 'E89:')
- call feedkeys('c', 'L')
- confirm bdel
- call assert_equal(2, winnr('$'))
- call assert_equal(1, &modified)
- call feedkeys('n', 'L')
- confirm bdel
- call assert_equal(1, winnr('$'))
-endfunc
-
-" Test for editing another buffer from a modified buffer with :confirm
-func Test_goto_buf_with_confirm()
- " requires a UI to be active
- throw 'Skipped: use test/functional/legacy/buffer_spec.lua'
- CheckUnix
- CheckNotGui
- CheckFeature dialog_con
- new Xfile
- enew
- call setline(1, 'test')
- call assert_fails('b Xfile', 'E37:')
- call feedkeys('c', 'L')
- call assert_fails('confirm b Xfile', 'E37:')
- call assert_equal(1, &modified)
- call assert_equal('', @%)
- call feedkeys('y', 'L')
- call assert_fails('confirm b Xfile', ['', 'E37:'])
- call assert_equal(1, &modified)
- call assert_equal('', @%)
- call feedkeys('n', 'L')
- confirm b Xfile
- call assert_equal('Xfile', @%)
- close!
-endfunc
-
-" Test for splitting buffer with 'switchbuf'
-func Test_buffer_switchbuf()
- new Xfile
- wincmd w
- set switchbuf=useopen
- sbuf Xfile
- call assert_equal(1, winnr())
- call assert_equal(2, winnr('$'))
- set switchbuf=usetab
- tabnew
- sbuf Xfile
- call assert_equal(1, tabpagenr())
- call assert_equal(2, tabpagenr('$'))
- set switchbuf&
- %bw
-endfunc
-
-" Test for BufAdd autocommand wiping out the buffer
-func Test_bufadd_autocmd_bwipe()
- %bw!
- augroup BufAdd_Wipe
- au!
- autocmd BufAdd Xfile %bw!
- augroup END
- edit Xfile
- call assert_equal('', @%)
- call assert_equal(0, bufexists('Xfile'))
- augroup BufAdd_Wipe
- au!
- augroup END
- augroup! BufAdd_Wipe
-endfunc
-
-" Test for trying to load a buffer with text locked
-" <C-\>e in the command line is used to lock the text
-func Test_load_buf_with_text_locked()
- new Xfile1
- edit Xfile2
- let cmd = ":\<C-\>eexecute(\"normal \<C-O>\")\<CR>\<C-C>"
- call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
- %bw!
-endfunc
-
-" Test for using CTRL-^ to edit the alternative file keeping the cursor
-" position with 'nostartofline'. Also test using the 'buf' command.
-func Test_buffer_edit_altfile()
- call writefile(repeat(['one two'], 50), 'Xfile1')
- call writefile(repeat(['five six'], 50), 'Xfile2')
- set nosol
- edit Xfile1
- call cursor(25, 5)
- edit Xfile2
- call cursor(30, 4)
- exe "normal \<C-^>"
- call assert_equal([0, 25, 5, 0], getpos('.'))
- exe "normal \<C-^>"
- call assert_equal([0, 30, 4, 0], getpos('.'))
- buf Xfile1
- call assert_equal([0, 25, 5, 0], getpos('.'))
- buf Xfile2
- call assert_equal([0, 30, 4, 0], getpos('.'))
- set sol&
- call delete('Xfile1')
- call delete('Xfile2')
-endfunc
-
-" Test for running the :sball command with a maximum window count and a
-" modified buffer
-func Test_sball_with_count()
- %bw!
- edit Xfile1
- call setline(1, ['abc'])
- new Xfile2
- new Xfile3
- new Xfile4
- 2sball
- call assert_equal(bufnr('Xfile4'), winbufnr(1))
- call assert_equal(bufnr('Xfile1'), winbufnr(2))
- call assert_equal(0, getbufinfo('Xfile2')[0].loaded)
- call assert_equal(0, getbufinfo('Xfile3')[0].loaded)
- %bw!
-endfunc
-
-func Test_badd_options()
- new SomeNewBuffer
- setlocal numberwidth=3
- wincmd p
- badd +1 SomeNewBuffer
- new SomeNewBuffer
- call assert_equal(3, &numberwidth)
- close
- close
- bwipe! SomeNewBuffer
-endfunc
-
-func Test_balt()
- new SomeNewBuffer
- balt +3 OtherBuffer
- e #
- call assert_equal('OtherBuffer', bufname())
-endfunc
-
-" Test for buffer match URL(scheme) check
-" scheme is alpha and inner hyphen only.
-func Test_buffer_scheme()
- CheckMSWindows
-
- set noshellslash
- %bwipe!
- let bufnames = [
- \ #{id: 'ssb0', name: 'test://xyz/foo/ssb0' , match: 1},
- \ #{id: 'ssb1', name: 'test+abc://xyz/foo/ssb1', match: 0},
- \ #{id: 'ssb2', name: 'test_abc://xyz/foo/ssb2', match: 0},
- \ #{id: 'ssb3', name: 'test-abc://xyz/foo/ssb3', match: 1},
- \ #{id: 'ssb4', name: '-test://xyz/foo/ssb4' , match: 0},
- \ #{id: 'ssb5', name: 'test-://xyz/foo/ssb5' , match: 0},
- \]
- for buf in bufnames
- new `=buf.name`
- if buf.match
- call assert_equal(buf.name, getbufinfo(buf.id)[0].name)
- else
- " slashes will have become backslashes
- call assert_notequal(buf.name, getbufinfo(buf.id)[0].name)
- endif
- bwipe
- endfor
-
- set shellslash&
-endfunc
-
-" this was using a NULL pointer after failing to use the pattern
-func Test_buf_pattern_invalid()
- vsplit 0000000
- silent! buf [0--]\&\zs*\zs*e
- bwipe!
-
- vsplit 00000000000000000000000000
- silent! buf [0--]\&\zs*\zs*e
- bwipe!
-
- " similar case with different code path
- split 0
- edit ÿ
- silent! buf [0--]\&\zs*\zs*0
- bwipe!
-endfunc
-
-" Test for the 'maxmem' and 'maxmemtot' options
-func Test_buffer_maxmem()
- " use 1KB per buffer and 2KB for all the buffers
- " set maxmem=1 maxmemtot=2
- new
- let v:errmsg = ''
- " try opening some files
- edit test_arglist.vim
- call assert_equal('test_arglist.vim', bufname())
- edit test_eval_stuff.vim
- call assert_equal('test_eval_stuff.vim', bufname())
- b test_arglist.vim
- call assert_equal('test_arglist.vim', bufname())
- b test_eval_stuff.vim
- call assert_equal('test_eval_stuff.vim', bufname())
- close
- call assert_equal('', v:errmsg)
- " set maxmem& maxmemtot&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
deleted file mode 100644
index 0468025f55..0000000000
--- a/src/nvim/testdir/test_bufline.vim
+++ /dev/null
@@ -1,296 +0,0 @@
-" Tests for setbufline(), getbufline(), appendbufline(), deletebufline()
-
-source shared.vim
-source screendump.vim
-source check.vim
-
-func Test_setbufline_getbufline()
- " similar to Test_set_get_bufline()
- new
- let b = bufnr('%')
- hide
- call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
- call assert_equal(['foo'], getbufline(b, 1))
- call assert_equal('foo', getbufoneline(b, 1))
- call assert_equal(['bar'], getbufline(b, '$'))
- call assert_equal('bar', getbufoneline(b, '$'))
- call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
- exe "bd!" b
- call assert_equal([], getbufline(b, 1, 2))
-
- split Xtest
- call setline(1, ['a', 'b', 'c'])
- let b = bufnr('%')
- wincmd w
-
- call assert_equal(1, setbufline(b, 5, 'x'))
- call assert_equal(1, setbufline(b, 5, ['x']))
- call assert_equal(1, setbufline(b, 5, []))
- call assert_equal(1, setbufline(b, 5, v:_null_list))
-
- call assert_equal(1, 'x'->setbufline(bufnr('$') + 1, 1))
- call assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1))
- call assert_equal(1, []->setbufline(bufnr('$') + 1, 1))
- call assert_equal(1, v:_null_list->setbufline(bufnr('$') + 1, 1))
-
- call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$'))
-
- call assert_equal(0, setbufline(b, 4, ['d', 'e']))
- call assert_equal(['c'], b->getbufline(3))
- call assert_equal('c', b->getbufoneline(3))
- call assert_equal(['d'], getbufline(b, 4))
- call assert_equal('d', getbufoneline(b, 4))
- call assert_equal(['e'], getbufline(b, 5))
- call assert_equal('e', getbufoneline(b, 5))
- call assert_equal([], getbufline(b, 6))
- call assert_equal([], getbufline(b, 2, 1))
-
- if has('job')
- call setbufline(b, 2, [function('eval'), #{key: 123}, test_null_job()])
- call assert_equal(["function('eval')",
- \ "{'key': 123}",
- \ "no process"],
- \ getbufline(b, 2, 4))
- endif
- exe "bwipe! " . b
-endfunc
-
-func Test_setbufline_getbufline_fold()
- split Xtest
- setlocal foldmethod=expr foldexpr=0
- let b = bufnr('%')
- new
- call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
- call assert_equal(['foo'], getbufline(b, 1))
- call assert_equal(['bar'], getbufline(b, 2))
- call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
- exe "bwipe!" b
- bwipe!
-endfunc
-
-func Test_setbufline_getbufline_fold_tab()
- split Xtest
- setlocal foldmethod=expr foldexpr=0
- let b = bufnr('%')
- tab new
- call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
- call assert_equal(['foo'], getbufline(b, 1))
- call assert_equal(['bar'], getbufline(b, 2))
- call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
- exe "bwipe!" b
- bwipe!
-endfunc
-
-func Test_setline_startup()
- let cmd = GetVimCommand('Xscript')
- if cmd == ''
- return
- endif
- call writefile(['call setline(1, "Hello")', 'silent w Xtest', 'q!'], 'Xscript')
- call system(cmd)
- call assert_equal(['Hello'], readfile('Xtest'))
-
- call delete('Xscript')
- call delete('Xtest')
-endfunc
-
-func Test_appendbufline()
- new
- let b = bufnr('%')
- hide
-
- new
- call setline(1, ['line1', 'line2', 'line3'])
- normal! 2gggg
- call assert_equal(2, line("''"))
-
- call assert_equal(0, appendbufline(b, 0, ['foo', 'bar']))
- call assert_equal(['foo'], getbufline(b, 1))
- call assert_equal(['bar'], getbufline(b, 2))
- call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
- call assert_equal(0, appendbufline(b, 0, 'baz'))
- call assert_equal(['baz', 'foo', 'bar'], getbufline(b, 1, 3))
-
- " appendbufline() in a hidden buffer shouldn't move marks in current window.
- call assert_equal(2, line("''"))
- bwipe!
-
- exe "bd!" b
- call assert_equal([], getbufline(b, 1, 3))
-
- split Xtest
- call setline(1, ['a', 'b', 'c'])
- let b = bufnr('%')
- wincmd w
-
- call assert_equal(1, appendbufline(b, -1, 'x'))
- call assert_equal(1, appendbufline(b, -1, ['x']))
- call assert_equal(1, appendbufline(b, -1, []))
- call assert_equal(1, appendbufline(b, -1, v:_null_list))
-
- call assert_equal(1, appendbufline(b, 4, 'x'))
- call assert_equal(1, appendbufline(b, 4, ['x']))
- call assert_equal(1, appendbufline(b, 4, []))
- call assert_equal(1, appendbufline(b, 4, v:_null_list))
-
- call assert_equal(1, appendbufline(1234, 1, 'x'))
- call assert_equal(1, appendbufline(1234, 1, ['x']))
- call assert_equal(1, appendbufline(1234, 1, []))
- call assert_equal(1, appendbufline(1234, 1, v:_null_list))
-
- call assert_equal(0, appendbufline(b, 1, []))
- call assert_equal(0, appendbufline(b, 1, v:_null_list))
- call assert_equal(1, appendbufline(b, 3, []))
- call assert_equal(1, appendbufline(b, 3, v:_null_list))
-
- call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$'))
-
- call assert_equal(0, appendbufline(b, 3, ['d', 'e']))
- call assert_equal(['c'], getbufline(b, 3))
- call assert_equal(['d'], getbufline(b, 4))
- call assert_equal(['e'], getbufline(b, 5))
- call assert_equal([], getbufline(b, 6))
- exe "bwipe! " . b
-endfunc
-
-func Test_deletebufline()
- new
- let b = bufnr('%')
- call setline(1, ['aaa', 'bbb', 'ccc'])
- hide
-
- new
- call setline(1, ['line1', 'line2', 'line3'])
- normal! 2gggg
- call assert_equal(2, line("''"))
-
- call assert_equal(0, deletebufline(b, 2))
- call assert_equal(['aaa', 'ccc'], getbufline(b, 1, 2))
- call assert_equal(0, deletebufline(b, 2, 8))
- call assert_equal(['aaa'], getbufline(b, 1, 2))
-
- " deletebufline() in a hidden buffer shouldn't move marks in current window.
- call assert_equal(2, line("''"))
- bwipe!
-
- exe "bd!" b
- call assert_equal(1, b->deletebufline(1))
-
- call assert_equal(1, deletebufline(-1, 1))
-
- split Xtest
- call setline(1, ['a', 'b', 'c'])
- call cursor(line('$'), 1)
- let b = bufnr('%')
- wincmd w
- call assert_equal(1, deletebufline(b, 4))
- call assert_equal(0, deletebufline(b, 1))
- call assert_equal(['b', 'c'], getbufline(b, 1, 2))
- exe "bwipe! " . b
-
- edit XbufOne
- let one = bufnr()
- call setline(1, ['a', 'b', 'c'])
- setlocal nomodifiable
- split XbufTwo
- let two = bufnr()
- call assert_fails('call deletebufline(one, 1)', 'E21:')
- call assert_equal(two, bufnr())
- bwipe! XbufTwo
- bwipe! XbufOne
-endfunc
-
-func Test_appendbufline_redraw()
- CheckScreendump
-
- let lines =<< trim END
- new foo
- let winnr = 'foo'->bufwinnr()
- let buf = bufnr('foo')
- wincmd p
- call appendbufline(buf, '$', range(1,200))
- exe winnr .. 'wincmd w'
- norm! G
- wincmd p
- call deletebufline(buf, 1, '$')
- call appendbufline(buf, '$', 'Hello Vim world...')
- END
- call writefile(lines, 'XscriptMatchCommon')
- let buf = RunVimInTerminal('-S XscriptMatchCommon', #{rows: 10})
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_appendbufline_1', {})
-
- call StopVimInTerminal(buf)
- call delete('XscriptMatchCommon')
-endfunc
-
-func Test_setbufline_select_mode()
- new
- call setline(1, ['foo', 'bar'])
- call feedkeys("j^v2l\<C-G>", 'nx')
-
- let bufnr = bufadd('Xdummy')
- call bufload(bufnr)
- call setbufline(bufnr, 1, ['abc'])
-
- call feedkeys("x", 'nx')
- call assert_equal(['foo', 'x'], getline(1, 2))
-
- exe "bwipe! " .. bufnr
- bwipe!
-endfunc
-
-func Test_deletebufline_select_mode()
- new
- call setline(1, ['foo', 'bar'])
- call feedkeys("j^v2l\<C-G>", 'nx')
-
- let bufnr = bufadd('Xdummy')
- call bufload(bufnr)
- call setbufline(bufnr, 1, ['abc', 'def'])
- call deletebufline(bufnr, 1)
-
- call feedkeys("x", 'nx')
- call assert_equal(['foo', 'x'], getline(1, 2))
-
- exe "bwipe! " .. bufnr
- bwipe!
-endfunc
-
-func Test_setbufline_startup_nofile()
- let before =<< trim [CODE]
- set shortmess+=F
- file Xresult
- set buftype=nofile
- call setbufline('', 1, 'success')
- [CODE]
- let after =<< trim [CODE]
- set buftype=
- write
- quit
- [CODE]
-
- if !RunVim(before, after, '--clean')
- return
- endif
- call assert_equal(['success'], readfile('Xresult'))
- call delete('Xresult')
-endfunc
-
-" Test that setbufline(), appendbufline() and deletebufline() should fail and
-" return 1 when "textlock" is active.
-func Test_change_bufline_with_textlock()
- new
- inoremap <buffer> <expr> <F2> setbufline('', 1, '')
- call assert_fails("normal a\<F2>", 'E565:')
- call assert_equal('1', getline(1))
- inoremap <buffer> <expr> <F2> appendbufline('', 1, '')
- call assert_fails("normal a\<F2>", 'E565:')
- call assert_equal('11', getline(1))
- inoremap <buffer> <expr> <F2> deletebufline('', 1)
- call assert_fails("normal a\<F2>", 'E565:')
- call assert_equal('111', getline(1))
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim
deleted file mode 100644
index 326aefb731..0000000000
--- a/src/nvim/testdir/test_bufwintabinfo.vim
+++ /dev/null
@@ -1,187 +0,0 @@
-" Tests for the getbufinfo(), getwininfo() and gettabinfo() functions
-
-function Test_getbufwintabinfo()
- edit Xtestfile1
- edit Xtestfile2
- let buflist = getbufinfo()
- call assert_equal(2, len(buflist))
- call assert_match('Xtestfile1', buflist[0].name)
- call assert_match('Xtestfile2', getbufinfo('Xtestfile2')[0].name)
- call assert_equal([], getbufinfo(2016))
- edit Xtestfile1
- hide edit Xtestfile2
- hide enew
- call assert_equal(3, len(getbufinfo({'bufloaded':1})))
-
- set tabstop&vim
- let b:editor = 'vim'
- let l = getbufinfo('%')
- call assert_equal(bufnr('%'), l[0].bufnr)
- call assert_equal('vim', l[0].variables.editor)
- call assert_notequal(-1, index(l[0].windows, '%'->bufwinid()))
-
- let l = '%'->getbufinfo()
- call assert_equal(bufnr('%'), l[0].bufnr)
-
- " Test for getbufinfo() with 'bufmodified'
- call assert_equal(0, len(getbufinfo({'bufmodified' : 1})))
- call setbufline('Xtestfile1', 1, ["Line1"])
- let l = getbufinfo({'bufmodified' : 1})
- call assert_equal(1, len(l))
- call assert_equal(bufnr('Xtestfile1'), l[0].bufnr)
-
- if has('signs')
- call append(0, ['Linux', 'Windows', 'Mac'])
- sign define Mark text=>> texthl=Search
- exe "sign place 2 line=3 name=Mark buffer=" . bufnr('%')
- let l = getbufinfo('%')
- call assert_equal(2, l[0].signs[0].id)
- call assert_equal(3, l[0].signs[0].lnum)
- call assert_equal('Mark', l[0].signs[0].name)
- sign unplace *
- sign undefine Mark
- enew!
- endif
-
- only
- let w1_id = win_getid()
- setl foldcolumn=3
- new
- let w2_id = win_getid()
- tabnew | let w3_id = win_getid()
- new | let w4_id = win_getid()
- vert new | let w5_id = win_getid()
- eval 'green'->setwinvar(0, 'signal')
- tabfirst
- let winlist = getwininfo()
- call assert_equal(5, len(winlist))
- call assert_equal(winwidth(1), winlist[0].width)
- call assert_equal(1, winlist[0].wincol)
- " tabline adds one row in terminal, not in GUI
- let tablineheight = winlist[0].winrow == 2 ? 1 : 0
- call assert_equal(tablineheight + 1, winlist[0].winrow)
-
- call assert_equal(winbufnr(2), winlist[1].bufnr)
- call assert_equal(winheight(2), winlist[1].height)
- call assert_equal(1, winlist[1].wincol)
- call assert_equal(3, winlist[1].textoff) " foldcolumn
- call assert_equal(tablineheight + winheight(1) + 2, winlist[1].winrow)
-
- call assert_equal(1, winlist[2].winnr)
- call assert_equal(tablineheight + 1, winlist[2].winrow)
- call assert_equal(1, winlist[2].wincol)
-
- call assert_equal(winlist[2].width + 2, winlist[3].wincol)
- call assert_equal(1, winlist[4].wincol)
-
- call assert_equal(1, winlist[0].tabnr)
- call assert_equal(1, winlist[1].tabnr)
- call assert_equal(2, winlist[2].tabnr)
- call assert_equal(2, winlist[3].tabnr)
- call assert_equal(2, winlist[4].tabnr)
-
- call assert_equal('green', winlist[2].variables.signal)
- call assert_equal(w4_id, winlist[3].winid)
- let winfo = w5_id->getwininfo()[0]
- call assert_equal(2, winfo.tabnr)
- call assert_equal([], getwininfo(3))
-
- call settabvar(1, 'space', 'build')
- let tablist = gettabinfo()
- call assert_equal(2, len(tablist))
- call assert_equal(3, len(tablist[1].windows))
- call assert_equal(2, tablist[1].tabnr)
- call assert_equal('build', tablist[0].variables.space)
- call assert_equal(w2_id, tablist[0].windows[0])
- call assert_equal([], 3->gettabinfo())
-
- tabonly | only
-
- lexpr ''
- lopen
- copen
- let winlist = getwininfo()
- call assert_false(winlist[0].quickfix)
- call assert_false(winlist[0].loclist)
- call assert_true(winlist[1].quickfix)
- call assert_true(winlist[1].loclist)
- call assert_true(winlist[2].quickfix)
- call assert_false(winlist[2].loclist)
- wincmd t | only
-endfunction
-
-function Test_get_buf_options()
- let opts = bufnr()->getbufvar('&')
- call assert_equal(v:t_dict, type(opts))
- call assert_equal(8, opts.tabstop)
-endfunc
-
-function Test_get_win_options()
- if has('folding')
- set foldlevel=999
- endif
- set list
- let opts = getwinvar(1, '&')
- call assert_equal(v:t_dict, type(opts))
- call assert_equal(0, opts.linebreak)
- call assert_equal(1, opts.list)
- if has('folding')
- call assert_equal(999, opts.foldlevel)
- endif
- if has('signs')
- call assert_equal('auto', opts.signcolumn)
- endif
-
- let opts = gettabwinvar(1, 1, '&')
- call assert_equal(v:t_dict, type(opts))
- call assert_equal(0, opts.linebreak)
- call assert_equal(1, opts.list)
- if has('signs')
- call assert_equal('auto', opts.signcolumn)
- endif
- set list&
- if has('folding')
- set foldlevel=0
- endif
-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
-
-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
-
-func Test_getwininfo_au()
- enew
- call setline(1, range(1, 16))
-
- let g:info = #{}
- augroup T1
- au!
- au WinEnter * let g:info = getwininfo(win_getid())[0]
- augroup END
-
- 4split
- " Check that calling getwininfo() from WinEnter returns fresh values for
- " topline and botline.
- call assert_equal(1, g:info.topline)
- call assert_equal(4, g:info.botline)
- close
-
- unlet g:info
- augroup! T1
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim
deleted file mode 100644
index 2b37f2c7c0..0000000000
--- a/src/nvim/testdir/test_cd.vim
+++ /dev/null
@@ -1,256 +0,0 @@
-" Test for :cd and chdir()
-
-source shared.vim
-source check.vim
-
-func Test_cd_large_path()
- " This used to crash with a heap write overflow.
- call assert_fails('cd ' . repeat('x', 5000), 'E344:')
-endfunc
-
-func Test_cd_up_and_down()
- let path = getcwd()
- cd ..
- call assert_notequal(path, getcwd())
- exe 'cd ' .. fnameescape(path)
- call assert_equal(path, getcwd())
-endfunc
-
-func Test_cd_no_arg()
- if has('unix')
- " Test that cd without argument goes to $HOME directory on Unix systems.
- let path = getcwd()
- cd
- call assert_equal($HOME, getcwd())
- call assert_notequal(path, getcwd())
- exe 'cd ' .. fnameescape(path)
- call assert_equal(path, getcwd())
- else
- " Test that cd without argument echoes cwd on non-Unix systems.
- call assert_match(getcwd(), execute('cd'))
- endif
-endfunc
-
-func Test_cd_minus()
- " Test the :cd - goes back to the previous directory.
- let path = getcwd()
- cd ..
- let path_dotdot = getcwd()
- call assert_notequal(path, path_dotdot)
- cd -
- call assert_equal(path, getcwd())
- cd -
- call assert_equal(path_dotdot, getcwd())
- cd -
- call assert_equal(path, getcwd())
-
- " Test for :cd - after a failed :cd
- call assert_fails('cd /nonexistent', 'E344:')
- call assert_equal(path, getcwd())
- cd -
- call assert_equal(path_dotdot, getcwd())
- cd -
-
- " Test for :cd - without a previous directory
- let lines =<< trim [SCRIPT]
- call assert_fails('cd -', 'E186:')
- call assert_fails('call chdir("-")', 'E186:')
- call writefile(v:errors, 'Xresult')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -S Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" Test for chdir()
-func Test_chdir_func()
- let topdir = getcwd()
- call mkdir('Xdir/y/z', 'p')
-
- " Create a few tabpages and windows with different directories
- new
- cd Xdir
- tabnew
- tcd y
- below new
- below new
- lcd z
-
- tabfirst
- call assert_match('^\[global\] .*/Xdir$', trim(execute('verbose pwd')))
- call chdir('..')
- call assert_equal('y', fnamemodify(getcwd(1, 2), ':t'))
- call assert_equal('z', fnamemodify(3->getcwd(2), ':t'))
- tabnext | wincmd t
- call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd')))
- eval '..'->chdir()
- call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t'))
- call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t'))
- call assert_equal('z', fnamemodify(getcwd(3, 2), ':t'))
- call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
- 3wincmd w
- call assert_match('^\[window\] .*/z$', trim(execute('verbose pwd')))
- call chdir('..')
- call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t'))
- call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t'))
- call assert_equal('y', fnamemodify(getcwd(3, 2), ':t'))
- call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
-
- " Error case
- call assert_fails("call chdir('dir-abcd')", 'E344:')
- silent! let d = chdir("dir_abcd")
- call assert_equal("", d)
- " Should not crash
- call chdir(d)
-
- only | tabonly
- call chdir(topdir)
- call delete('Xdir', 'rf')
-endfunc
-
-" Test for changing to the previous directory '-'
-func Test_prev_dir()
- let topdir = getcwd()
- call mkdir('Xdir/a/b/c', 'p')
-
- " Create a few tabpages and windows with different directories
- new | only
- tabnew | new
- tabnew
- tabfirst
- cd Xdir
- tabnext | wincmd t
- tcd a
- wincmd w
- lcd b
- tabnext
- tcd a/b/c
-
- " Change to the previous directory twice in all the windows.
- tabfirst
- cd - | cd -
- tabnext | wincmd t
- tcd - | tcd -
- wincmd w
- lcd - | lcd -
- tabnext
- tcd - | tcd -
-
- " Check the directory of all the windows
- tabfirst
- call assert_equal('Xdir', fnamemodify(getcwd(), ':t'))
- tabnext | wincmd t
- call assert_equal('a', fnamemodify(getcwd(), ':t'))
- wincmd w
- call assert_equal('b', fnamemodify(getcwd(), ':t'))
- tabnext
- call assert_equal('c', fnamemodify(getcwd(), ':t'))
-
- " Change to the previous directory using chdir()
- tabfirst
- call chdir("-") | call chdir("-")
- tabnext | wincmd t
- call chdir("-") | call chdir("-")
- wincmd w
- call chdir("-") | call chdir("-")
- tabnext
- call chdir("-") | call chdir("-")
-
- " Check the directory of all the windows
- tabfirst
- call assert_equal('Xdir', fnamemodify(getcwd(), ':t'))
- tabnext | wincmd t
- call assert_equal('a', fnamemodify(getcwd(), ':t'))
- wincmd w
- call assert_equal('b', fnamemodify(getcwd(), ':t'))
- tabnext
- call assert_equal('c', fnamemodify(getcwd(), ':t'))
-
- only | tabonly
- call chdir(topdir)
- call delete('Xdir', 'rf')
-endfunc
-
-func Test_lcd_split()
- let curdir = getcwd()
- lcd ..
- split
- lcd -
- call assert_equal(curdir, getcwd())
- quit!
-endfunc
-
-func Test_cd_from_non_existing_dir()
- CheckNotMSWindows
-
- let saveddir = getcwd()
- call mkdir('Xdeleted_dir')
- cd Xdeleted_dir
- call delete(saveddir .. '/Xdeleted_dir', 'd')
-
- " Expect E187 as the current directory was deleted.
- call assert_fails('pwd', 'E187:')
- call assert_equal('', getcwd())
- cd -
- call assert_equal(saveddir, getcwd())
-endfunc
-
-func Test_cd_completion()
- call mkdir('XComplDir1', 'p')
- call mkdir('XComplDir2', 'p')
- call writefile([], 'XComplFile')
-
- for cmd in ['cd', 'chdir', 'lcd', 'lchdir', 'tcd', 'tchdir']
- call feedkeys(':' .. cmd .. " XCompl\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"' .. cmd .. ' XComplDir1/ XComplDir2/', @:)
- endfor
-
- call delete('XComplDir1', 'd')
- call delete('XComplDir2', 'd')
- call delete('XComplFile')
-endfunc
-
-func Test_cd_unknown_dir()
- call mkdir('Xa')
- cd Xa
- call writefile(['text'], 'Xb.txt')
- edit Xa/Xb.txt
- let first_buf = bufnr()
- cd ..
- edit
- call assert_equal(first_buf, bufnr())
- edit Xa/Xb.txt
- call assert_notequal(first_buf, bufnr())
-
- bwipe!
- exe "bwipe! " .. first_buf
- call delete('Xa', 'rf')
-endfunc
-
-func Test_getcwd_actual_dir()
- CheckFunction test_autochdir
- CheckOption autochdir
-
- let startdir = getcwd()
- call mkdir('Xactual')
- call test_autochdir()
- set autochdir
- edit Xactual/file.txt
- call assert_match('testdir.Xactual$', getcwd())
- lcd ..
- call assert_match('testdir$', getcwd())
- edit
- call assert_match('testdir.Xactual$', getcwd())
- call assert_match('testdir$', getcwd(win_getid()))
-
- set noautochdir
- bwipe!
- call chdir(startdir)
- call delete('Xactual', 'rf')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cdo.vim b/src/nvim/testdir/test_cdo.vim
deleted file mode 100644
index dbed7df4ac..0000000000
--- a/src/nvim/testdir/test_cdo.vim
+++ /dev/null
@@ -1,216 +0,0 @@
-" Tests for the :cdo, :cfdo, :ldo and :lfdo commands
-
-source check.vim
-CheckFeature quickfix
-
-" Create the files used by the tests
-func SetUp()
- call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1')
- call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2')
- call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3')
-endfunc
-
-" Remove the files used by the tests
-func TearDown()
- call delete('Xtestfile1')
- call delete('Xtestfile2')
- call delete('Xtestfile3')
-endfunc
-
-" Returns the current line in '<filename> <linenum>L <column>C' format
-func GetRuler()
- return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C'
-endfunc
-
-" Tests for the :cdo and :ldo commands
-func XdoTests(cchar)
- enew
-
- " Shortcuts for calling the cdo and ldo commands
- let Xdo = a:cchar . 'do'
- let Xgetexpr = a:cchar . 'getexpr'
- let Xprev = a:cchar. 'prev'
- let XdoCmd = Xdo . ' call add(l, GetRuler())'
-
- " Try with an empty list
- let l = []
- exe XdoCmd
- call assert_equal([], l)
-
- " Populate the list and then try
- exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']"
-
- let l = []
- exe XdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- " Run command only on selected error lines
- let l = []
- enew
- exe "2,3" . XdoCmd
- call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- " Boundary condition tests
- let l = []
- enew
- exe "1,1" . XdoCmd
- call assert_equal(['Xtestfile1 1L 3C'], l)
-
- let l = []
- enew
- exe "3" . XdoCmd
- call assert_equal(['Xtestfile3 3L 1C'], l)
-
- " Range test commands
- let l = []
- enew
- exe "%" . XdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- let l = []
- enew
- exe "1,$" . XdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
-
- let l = []
- enew
- exe Xprev
- exe "." . XdoCmd
- call assert_equal(['Xtestfile2 2L 2C'], l)
-
- let l = []
- enew
- exe "+" . XdoCmd
- call assert_equal(['Xtestfile3 3L 1C'], l)
-
- " Invalid error lines test
- let l = []
- enew
- exe "silent! 27" . XdoCmd
- exe "silent! 4,5" . XdoCmd
- call assert_equal([], l)
-
- " Run commands from an unsaved buffer
- let v:errmsg=''
- let l = []
- enew
- setlocal modified
- exe "silent! 2,2" . XdoCmd
- if v:errmsg !~# 'No write since last change'
- call add(v:errors, 'Unsaved file change test failed')
- endif
-
- " If the executed command fails, then the operation should be aborted
- enew!
- let subst_count = 0
- exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1"
- if subst_count != 1 || getline('.') != 'xLine1'
- call add(v:errors, 'Abort command on error test failed')
- endif
-
- let l = []
- exe "2,2" . Xdo . "! call add(l, GetRuler())"
- call assert_equal(['Xtestfile2 2L 2C'], l)
-
- " List with no valid error entries
- let l = []
- edit! +2 Xtestfile1
- exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']"
- exe XdoCmd
- call assert_equal([], l)
- exe "silent! 2" . XdoCmd
- call assert_equal([], l)
- let v:errmsg=''
- exe "%" . XdoCmd
- exe "1,$" . XdoCmd
- exe "." . XdoCmd
- call assert_equal('', v:errmsg)
-
- " List with only one valid entry
- let l = []
- exe Xgetexpr . " ['Xtestfile3:3:1:Line3']"
- exe XdoCmd
- call assert_equal(['Xtestfile3 3L 1C'], l)
-
-endfunc
-
-" Tests for the :cfdo and :lfdo commands
-func XfdoTests(cchar)
- enew
-
- " Shortcuts for calling the cfdo and lfdo commands
- let Xfdo = a:cchar . 'fdo'
- let Xgetexpr = a:cchar . 'getexpr'
- let XfdoCmd = Xfdo . ' call add(l, GetRuler())'
- let Xpfile = a:cchar. 'pfile'
-
- " Clear the quickfix/location list
- exe Xgetexpr . " []"
-
- " Try with an empty list
- let l = []
- exe XfdoCmd
- call assert_equal([], l)
-
- " Populate the list and then try
- exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']"
-
- let l = []
- exe XfdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- " Run command only on selected error lines
- let l = []
- exe "2,3" . XfdoCmd
- call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- " Boundary condition tests
- let l = []
- exe "3" . XfdoCmd
- call assert_equal(['Xtestfile3 2L 3C'], l)
-
- " Range test commands
- let l = []
- exe "%" . XfdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- let l = []
- exe "1,$" . XfdoCmd
- call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
-
- let l = []
- exe Xpfile
- exe "." . XfdoCmd
- call assert_equal(['Xtestfile2 2L 2C'], l)
-
- " List with only one valid entry
- let l = []
- exe Xgetexpr . " ['Xtestfile2:2:5:Line2']"
- exe XfdoCmd
- call assert_equal(['Xtestfile2 2L 5C'], l)
-
-endfunc
-
-" Tests for cdo and cfdo
-func Test_cdo()
- call XdoTests('c')
- call XfdoTests('c')
-endfunc
-
-" Tests for ldo and lfdo
-func Test_ldo()
- call XdoTests('l')
- call XfdoTests('l')
-endfunc
-
-" Test for making 'shm' doesn't interfere with the output.
-func Test_cdo_print()
- enew | only!
- cgetexpr ["Xtestfile1:1:Line1", "Xtestfile2:1:Line1", "Xtestfile3:1:Line1"]
- cdo print
- call assert_equal('Line1', Screenline(&lines))
- call assert_equal('Line1', Screenline(&lines - 3))
- call assert_equal('Line1', Screenline(&lines - 6))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_changedtick.vim b/src/nvim/testdir/test_changedtick.vim
deleted file mode 100644
index c789cdc1bc..0000000000
--- a/src/nvim/testdir/test_changedtick.vim
+++ /dev/null
@@ -1,95 +0,0 @@
-" Tests for b:changedtick
-
-func Test_changedtick_increments()
- new
- " New buffer has an empty line, tick starts at 2.
- let expected = 2
- call assert_equal(expected, b:changedtick)
- call assert_equal(expected, b:['changedtick'])
- call setline(1, 'hello')
- let expected += 1
- call assert_equal(expected, b:changedtick)
- call assert_equal(expected, b:['changedtick'])
- undo
- " Somehow undo counts as two changes.
- let expected += 2
- call assert_equal(expected, b:changedtick)
- call assert_equal(expected, b:['changedtick'])
- bwipe!
-endfunc
-
-func Test_changedtick_dict_entry()
- let d = b:
- call assert_equal(b:changedtick, d['changedtick'])
-endfunc
-
-func Test_changedtick_bdel()
- new
- let bnr = bufnr('%')
- let v = b:changedtick
- bdel
- " Delete counts as a change too.
- call assert_equal(v + 1, getbufvar(bnr, 'changedtick'))
-endfunc
-
-func Test_changedtick_islocked()
- call assert_equal(0, islocked('b:changedtick'))
- let d = b:
- call assert_equal(0, islocked('d.changedtick'))
-endfunc
-
-func Test_changedtick_fixed()
- call assert_fails('let b:changedtick = 4', 'E46:')
- call assert_fails('let b:["changedtick"] = 4', 'E46:')
-
- call assert_fails('lockvar b:changedtick', 'E940:')
- call assert_fails('lockvar b:["changedtick"]', 'E46:')
- call assert_fails('unlockvar b:changedtick', 'E940:')
- call assert_fails('unlockvar b:["changedtick"]', 'E46:')
- call assert_fails('unlet b:changedtick', 'E795:')
- call assert_fails('unlet b:["changedtick"]', 'E46:')
-
- let d = b:
- call assert_fails('lockvar d["changedtick"]', 'E46:')
- call assert_fails('unlockvar d["changedtick"]', 'E46:')
- call assert_fails('unlet d["changedtick"]', 'E46:')
-
-endfunc
-
-func Test_changedtick_not_incremented_with_write()
- new
- let fname = "XChangeTick"
- exe 'w ' .. fname
-
- " :write when the buffer is not changed does not increment changedtick
- let expected = b:changedtick
- w
- call assert_equal(expected, b:changedtick)
-
- " :write when the buffer IS changed DOES increment changedtick
- let expected = b:changedtick + 1
- setlocal modified
- w
- call assert_equal(expected, b:changedtick)
-
- " Two ticks: change + write
- let expected = b:changedtick + 2
- call setline(1, 'hello')
- w
- call assert_equal(expected, b:changedtick)
-
- " Two ticks: start insert + write
- let expected = b:changedtick + 2
- normal! o
- w
- call assert_equal(expected, b:changedtick)
-
- " Three ticks: start insert + change + write
- let expected = b:changedtick + 3
- normal! ochanged
- w
- call assert_equal(expected, b:changedtick)
-
- bwipe
- call delete(fname)
-endfunc
diff --git a/src/nvim/testdir/test_changelist.vim b/src/nvim/testdir/test_changelist.vim
deleted file mode 100644
index 3bb22a89b8..0000000000
--- a/src/nvim/testdir/test_changelist.vim
+++ /dev/null
@@ -1,107 +0,0 @@
-" Tests for the changelist functionality
-
-" When splitting a window the changelist position is wrong.
-" Test the changelist position after splitting a window.
-" Test for the bug fixed by 7.4.386
-func Test_changelist()
- let save_ul = &ul
- enew!
- call append('$', ['1', '2'])
- exe "normal i\<C-G>u"
- exe "normal Gkylpa\<C-G>u"
- set ul=100
- exe "normal Gylpa\<C-G>u"
- set ul=100
- normal gg
- vsplit
- normal g;
- call assert_equal([3, 2], [line('.'), col('.')])
- normal g;
- call assert_equal([2, 2], [line('.'), col('.')])
- call assert_fails('normal g;', 'E662:')
- new
- call assert_fails('normal g;', 'E664:')
- %bwipe!
- let &ul = save_ul
-endfunc
-
-" Moving a split should not change its changelist index.
-func Test_changelist_index_move_split()
- exe "norm! iabc\<C-G>u\ndef\<C-G>u\nghi"
- vsplit
- normal 99g;
- call assert_equal(0, getchangelist('%')[1])
- wincmd L
- call assert_equal(0, getchangelist('%')[1])
-endfunc
-
-" Tests for the getchangelist() function
-func Test_changelist_index()
- edit Xfile1.txt
- exe "normal iabc\<C-G>u\ndef\<C-G>u\nghi"
- call assert_equal(3, getchangelist('%')[1])
- " Move one step back in the changelist.
- normal 2g;
-
- hide edit Xfile2.txt
- exe "normal iabcd\<C-G>u\ndefg\<C-G>u\nghij"
- call assert_equal(3, getchangelist('%')[1])
- " Move to the beginning of the changelist.
- normal 99g;
-
- " Check the changelist indices.
- call assert_equal(0, getchangelist('%')[1])
- call assert_equal(1, getchangelist('#')[1])
-
- bwipe!
- call delete('Xfile1.txt')
- call delete('Xfile2.txt')
-endfunc
-
-func Test_getchangelist()
- bwipe!
- enew
- call assert_equal([], 10->getchangelist())
- call assert_equal([[], 0], getchangelist())
-
- call writefile(['line1', 'line2', 'line3'], 'Xfile1.txt')
- call writefile(['line1', 'line2', 'line3'], 'Xfile2.txt')
-
- edit Xfile1.txt
- let buf_1 = bufnr()
- exe "normal 1Goline\<C-G>u1.1"
- exe "normal 3Goline\<C-G>u2.1"
- exe "normal 5Goline\<C-G>u3.1"
- normal g;
- call assert_equal([[
- \ {'lnum' : 2, 'col' : 4, 'coladd' : 0},
- \ {'lnum' : 4, 'col' : 4, 'coladd' : 0},
- \ {'lnum' : 6, 'col' : 4, 'coladd' : 0}], 2],
- \ getchangelist('%'))
-
- hide edit Xfile2.txt
- let buf_2 = bufnr()
- exe "normal 1GOline\<C-G>u1.0"
- exe "normal 2Goline\<C-G>u2.0"
- call assert_equal([[
- \ {'lnum' : 1, 'col' : 6, 'coladd' : 0},
- \ {'lnum' : 3, 'col' : 6, 'coladd' : 0}], 2],
- \ getchangelist('%'))
- hide enew
-
- call assert_equal([[
- \ {'lnum' : 2, 'col' : 4, 'coladd' : 0},
- \ {'lnum' : 4, 'col' : 4, 'coladd' : 0},
- \ {'lnum' : 6, 'col' : 4, 'coladd' : 0}], 2],
- \ getchangelist(buf_1))
- call assert_equal([[
- \ {'lnum' : 1, 'col' : 6, 'coladd' : 0},
- \ {'lnum' : 3, 'col' : 6, 'coladd' : 0}], 2],
- \ getchangelist(buf_2))
-
- bwipe!
- call delete('Xfile1.txt')
- call delete('Xfile2.txt')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim
deleted file mode 100644
index 54e0a62ce5..0000000000
--- a/src/nvim/testdir/test_charsearch.vim
+++ /dev/null
@@ -1,98 +0,0 @@
-" Test for character search commands - t, T, f, F, ; and ,
-
-func Test_charsearch()
- enew!
- call append(0, ['Xabcdefghijkemnopqretuvwxyz',
- \ 'Yabcdefghijkemnopqretuvwxyz',
- \ 'Zabcdefghijkemnokqretkvwxyz'])
- " check that "fe" and ";" work
- 1
- normal! ylfep;;p,,p
- call assert_equal('XabcdeXfghijkeXmnopqreXtuvwxyz', getline(1))
- " check that save/restore works
- 2
- normal! ylfep
- let csave = getcharsearch()
- normal! fip
- call setcharsearch(csave)
- normal! ;p;p
- call assert_equal('YabcdeYfghiYjkeYmnopqreYtuvwxyz', getline(2))
-
- " check that setcharsearch() changes the settings.
- 3
- normal! ylfep
- eval {'char': 'k'}->setcharsearch()
- normal! ;p
- call setcharsearch({'forward': 0})
- normal! $;p
- call setcharsearch({'until': 1})
- set cpo-=;
- normal! ;;p
- call assert_equal('ZabcdeZfghijkZZemnokqretkZvwxyz', getline(3))
-
- " check that repeating a search before and after a line fails
- normal 3Gfv
- call assert_beeps('normal ;')
- call assert_beeps('normal ,')
-
- " clear the character search
- call setcharsearch({'char' : ''})
- call assert_equal('', getcharsearch().char)
-
- call assert_fails("call setcharsearch([])", 'E715:')
- enew!
-endfunc
-
-" Test for character search in virtual edit mode with <Tab>
-func Test_csearch_virtualedit()
- new
- set virtualedit=all
- call setline(1, "a\tb")
- normal! tb
- call assert_equal([0, 1, 2, 6], getpos('.'))
- set virtualedit&
- bw!
-endfunc
-
-" Test for character search failure in latin1 encoding
-func Test_charsearch_latin1()
- new
- let save_enc = &encoding
- " set encoding=latin1
- call setline(1, 'abcdefghijk')
- call assert_beeps('normal fz')
- call assert_beeps('normal tx')
- call assert_beeps('normal $Fz')
- call assert_beeps('normal $Tx')
- let &encoding = save_enc
- bw!
-endfunc
-
-" Test for using character search to find a multibyte character with composing
-" characters.
-func Test_charsearch_composing_char()
- new
- call setline(1, "one two thq\u0328\u0301r\u0328\u0301ree")
- call feedkeys("fr\u0328\u0301", 'xt')
- call assert_equal([0, 1, 16, 0, 12], getcurpos())
-
- " use character search with a multi-byte character followed by a
- " non-composing character
- call setline(1, "abc deȉf ghi")
- call feedkeys("ggcf\u0209\u0210", 'xt')
- call assert_equal("\u0210f ghi", getline(1))
- bw!
-endfunc
-
-" Test for character search with 'hkmap'
-func Test_charsearch_hkmap()
- new
- set hkmap
- call setline(1, "ùðáâ÷ëòéïçìêöî")
- call feedkeys("fë", 'xt')
- call assert_equal([0, 1, 11, 0, 6], getcurpos())
- set hkmap&
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim
deleted file mode 100644
index 82a807ac5b..0000000000
--- a/src/nvim/testdir/test_charsearch_utf8.vim
+++ /dev/null
@@ -1,19 +0,0 @@
-" Tests for related f{char} and t{char} using utf-8.
-
-" Test for t,f,F,T movement commands
-func Test_search_cmds()
- new!
- call setline(1, "・最åˆã‹ã‚‰æœ€å¾Œã¾ã§æœ€å¼·ã®Vimã¯æœ€é«˜")
- 1
- normal! f最
- call assert_equal([0, 1, 4, 0], getpos('.'))
- normal! ;
- call assert_equal([0, 1, 16, 0], getpos('.'))
- normal! 2;
- call assert_equal([0, 1, 43, 0], getpos('.'))
- normal! ,
- call assert_equal([0, 1, 28, 0], getpos('.'))
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_checkpath.vim b/src/nvim/testdir/test_checkpath.vim
deleted file mode 100644
index f6a3bbce08..0000000000
--- a/src/nvim/testdir/test_checkpath.vim
+++ /dev/null
@@ -1,121 +0,0 @@
-" Tests for the :checkpath command
-
-" Test for 'include' without \zs or \ze
-func Test_checkpath1()
- call mkdir("Xdir1/dir2", "p")
- call writefile(['#include "bar.a"'], 'Xdir1/dir2/foo.a')
- call writefile(['#include "baz.a"'], 'Xdir1/dir2/bar.a')
- call writefile(['#include "foo.a"'], 'Xdir1/dir2/baz.a')
- call writefile(['#include <foo.a>'], 'Xbase.a')
-
- edit Xbase.a
- set path=Xdir1/dir2
- let res = split(execute("checkpath!"), "\n")
- call assert_equal([
- \ '--- Included files in path ---',
- \ 'Xdir1/dir2/foo.a',
- \ 'Xdir1/dir2/foo.a -->',
- \ ' Xdir1/dir2/bar.a',
- \ ' Xdir1/dir2/bar.a -->',
- \ ' Xdir1/dir2/baz.a',
- \ ' Xdir1/dir2/baz.a -->',
- \ ' "foo.a" (Already listed)'], res)
-
- enew
- call delete("./Xbase.a")
- call delete("Xdir1", "rf")
- set path&
-endfunc
-
-func DotsToSlashes()
- return substitute(v:fname, '\.', '/', 'g') . '.b'
-endfunc
-
-" Test for 'include' with \zs and \ze
-func Test_checkpath2()
- call mkdir("Xdir1/dir2", "p")
- call writefile(['%inc /bar/'], 'Xdir1/dir2/foo.b')
- call writefile(['%inc /baz/'], 'Xdir1/dir2/bar.b')
- call writefile(['%inc /foo/'], 'Xdir1/dir2/baz.b')
- call writefile(['%inc /foo/'], 'Xbase.b')
-
- let &include='^\s*%inc\s*/\zs[^/]\+\ze'
- let &includeexpr='DotsToSlashes()'
-
- edit Xbase.b
- set path=Xdir1/dir2
- let res = split(execute("checkpath!"), "\n")
- call assert_equal([
- \ '--- Included files in path ---',
- \ 'Xdir1/dir2/foo.b',
- \ 'Xdir1/dir2/foo.b -->',
- \ ' Xdir1/dir2/bar.b',
- \ ' Xdir1/dir2/bar.b -->',
- \ ' Xdir1/dir2/baz.b',
- \ ' Xdir1/dir2/baz.b -->',
- \ ' foo (Already listed)'], res)
-
- enew
- call delete("./Xbase.b")
- call delete("Xdir1", "rf")
- set path&
- set include&
- set includeexpr&
-endfunc
-
-func StripNewlineChar()
- if v:fname =~ '\n$'
- return v:fname[:-2]
- endif
- return v:fname
-endfunc
-
-" Test for 'include' with \zs and no \ze
-func Test_checkpath3()
- call mkdir("Xdir1/dir2", "p")
- call writefile(['%inc bar.c'], 'Xdir1/dir2/foo.c')
- call writefile(['%inc baz.c'], 'Xdir1/dir2/bar.c')
- call writefile(['%inc foo.c'], 'Xdir1/dir2/baz.c')
- call writefile(['%inc foo.c'], 'Xdir1/dir2/FALSE.c')
- call writefile(['%inc FALSE.c foo.c'], 'Xbase.c')
-
- let &include='^\s*%inc\s*\%([[:upper:]][^[:space:]]*\s\+\)\?\zs\S\+\ze'
- let &includeexpr='StripNewlineChar()'
-
- edit Xbase.c
- set path=Xdir1/dir2
- let res = split(execute("checkpath!"), "\n")
- call assert_equal([
- \ '--- Included files in path ---',
- \ 'Xdir1/dir2/foo.c',
- \ 'Xdir1/dir2/foo.c -->',
- \ ' Xdir1/dir2/bar.c',
- \ ' Xdir1/dir2/bar.c -->',
- \ ' Xdir1/dir2/baz.c',
- \ ' Xdir1/dir2/baz.c -->',
- \ ' foo.c (Already listed)'], res)
-
- enew
- call delete("./Xbase.c")
- call delete("Xdir1", "rf")
- set path&
- set include&
- set includeexpr&
-endfunc
-
-" Test for invalid regex in 'include' and 'define' options
-func Test_checkpath_errors()
- let save_include = &include
- set include=\\%(
- call assert_fails('checkpath', 'E53:')
- let &include = save_include
-
- let save_define = &define
- set define=\\%(
- call assert_fails('dsearch abc', 'E53:')
- let &define = save_define
-
- call assert_fails('psearch \%(', 'E53:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
deleted file mode 100644
index ccc8168c09..0000000000
--- a/src/nvim/testdir/test_cindent.vim
+++ /dev/null
@@ -1,5428 +0,0 @@
-" Test for cinoptions and cindent
-"
-
-func Test_cino_hash()
- " Test that curbuf->b_ind_hash_comment is correctly reset
- new
- setlocal cindent cinoptions=#1
- setlocal cinoptions=
- call setline(1, ["#include <iostream>"])
- call cursor(1, 1)
- norm! o#include
- "call feedkeys("o#include\<esc>", 't')
- call assert_equal(["#include <iostream>", "#include"], getline(1,2))
- bwipe!
-endfunc
-
-func Test_cino_extern_c()
- " Test for cino-E
-
- let without_ind =<< trim [CODE]
- #ifdef __cplusplus
- extern "C" {
- #endif
- int func_a(void);
- #ifdef __cplusplus
- }
- #endif
- [CODE]
-
- 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)
- call feedkeys("gg=G", 'tx')
- call assert_equal(with_ind, getline(1, '$'))
-
- setlocal cinoptions=E-s
- call setline(1, with_ind)
- call feedkeys("gg=G", 'tx')
- call assert_equal(without_ind, getline(1, '$'))
-
- setlocal cinoptions=Es
- let tests = [
- \ ['recognized', ['extern "C" {'], "\t\t;"],
- \ ['recognized', ['extern "C++" {'], "\t\t;"],
- \ ['recognized', ['extern /* com */ "C"{'], "\t\t;"],
- \ ['recognized', ['extern"C"{'], "\t\t;"],
- \ ['recognized', ['extern "C"', '{'], "\t\t;"],
- \ ['not recognized', ['extern {'], "\t;"],
- \ ['not recognized', ['extern /*"C"*/{'], "\t;"],
- \ ['not recognized', ['extern "C" //{'], ";"],
- \ ['not recognized', ['extern "C" /*{*/'], ";"],
- \ ]
-
- for pair in tests
- let lines = pair[1]
- call setline(1, lines)
- call feedkeys(len(lines) . "Go;", 'tx')
- call assert_equal(pair[2], getline(len(lines) + 1), 'Failed for "' . string(lines) . '"')
- endfor
-
- bwipe!
-endfunc
-
-func Test_cindent_rawstring()
- new
- setl cindent
- call feedkeys("i" .
- \ "int main() {\<CR>" .
- \ "R\"(\<CR>" .
- \ ")\";\<CR>" .
- \ "statement;\<Esc>", "x")
- call assert_equal("\tstatement;", getline(line('.')))
- bw!
-endfunc
-
-func Test_cindent_expr()
- new
- func! MyIndentFunction()
- return v:lnum == 1 ? shiftwidth() : 0
- endfunc
- setl expandtab sw=8 indentkeys+=; indentexpr=MyIndentFunction()
- 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')
- let expected =<< [CODE]
- var_a = something();
-b = something();
-[CODE]
- call assert_equal(expected, getline(1, '$'))
-
- %d
- 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')
- let expected =<< [CODE]
- var_a = something();
- b = something()
-[CODE]
- call assert_equal(expected, getline(1, '$'))
- bw!
-endfunc
-
-func Test_cindent_func()
- new
- setlocal cindent
- call setline(1, ['int main(void)', '{', 'return 0;', '}'])
- call assert_equal(-1, cindent(0))
- call assert_equal(&sw, 3->cindent())
- call assert_equal(-1, cindent(line('$')+1))
- bwipe!
-endfunc
-
-func Test_cindent_1()
- new
- setl cindent ts=4 sw=4
- setl cino& sts&
-
- let code =<< trim [CODE]
- /* start of AUTO matically checked vim: set ts=4 : */
- {
- if (test)
- cmd1;
- cmd2;
- }
-
- {
- if (test)
- cmd1;
- else
- cmd2;
- }
-
- {
- if (test)
- {
- cmd1;
- cmd2;
- }
- }
-
- {
- if (test)
- {
- cmd1;
- else
- }
- }
-
- {
- while (this)
- if (test)
- cmd1;
- cmd2;
- }
-
- {
- while (this)
- if (test)
- cmd1;
- else
- cmd2;
- }
-
- {
- if (test)
- {
- cmd;
- }
-
- if (test)
- cmd;
- }
-
- {
- if (test) {
- cmd;
- }
-
- if (test) cmd;
- }
-
- {
- cmd1;
- for (blah)
- while (this)
- if (test)
- cmd2;
- cmd3;
- }
-
- {
- cmd1;
- for (blah)
- while (this)
- if (test)
- cmd2;
- cmd3;
-
- if (test)
- {
- cmd1;
- cmd2;
- cmd3;
- }
- }
-
-
- /* Test for 'cindent' do/while mixed with if/else: */
-
- {
- do
- if (asdf)
- asdfasd;
- while (cond);
-
- do
- if (asdf)
- while (asdf)
- asdf;
- while (asdf);
- }
-
- /* Test for 'cindent' with two ) on a continuation line */
- {
- if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
- aal;sdkjf ( ;asldfkja;sldfk
- al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
- line up here;
- }
-
-
- /* C++ tests: */
-
- // foo() these three lines should remain in column 0
- // {
- // }
-
- /* Test for continuation and unterminated lines: */
- {
- i = 99 + 14325 +
- 21345 +
- 21345 +
- 21345 + ( 21345 +
- 21345) +
- 2345 +
- 1234;
- c = 1;
- }
-
- /*
- testje for indent with empty line
-
- here */
-
- {
- if (testing &&
- not a joke ||
- line up here)
- hay;
- if (testing &&
- (not a joke || testing
- )line up here)
- hay;
- if (testing &&
- (not a joke || testing
- line up here))
- hay;
- }
-
-
- {
- switch (c)
- {
- case xx:
- do
- if (asdf)
- do
- asdfasdf;
- while (asdf);
- else
- asdfasdf;
- while (cond);
- case yy:
- case xx:
- case zz:
- testing;
- }
- }
-
- {
- if (cond) {
- foo;
- }
- else
- {
- bar;
- }
- }
-
- {
- if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
- alsdkfj (asldk;fj
- awith cino=(0 ;lf this one goes to below the paren with ==
- ;laksjfd ;lsakdjf ;alskdf asd)
- asdfasdf;)))
- asdfasdf;
- }
-
- int
- func(a, b)
- int a;
- int c;
- {
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3)
- )
- }
-
- {
- while (asd)
- {
- if (asdf)
- if (test)
- if (that)
- {
- if (asdf)
- do
- cdasd;
- while (as
- df);
- }
- else
- if (asdf)
- asdf;
- else
- asdf;
- asdf;
- }
- }
-
- {
- s = "/*"; b = ';'
- s = "/*"; b = ';';
- a = b;
- }
-
- {
- switch (a)
- {
- case a:
- switch (t)
- {
- case 1:
- cmd;
- break;
- case 2:
- cmd;
- break;
- }
- cmd;
- break;
- case b:
- {
- int i;
- cmd;
- }
- break;
- case c: {
- int i;
- cmd;
- }
- case d: if (cond &&
- test) { /* this line doesn't work right */
- int i;
- cmd;
- }
- break;
- }
- }
-
- {
- if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
- (bp_to->b_p_initialized ||
- (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
- return;
- label :
- asdf = asdf ?
- asdf : asdf;
- asdf = asdf ?
- asdf: asdf;
- }
-
- /* Special Comments : This function has the added complexity (compared */
- /* : to addtolist) of having to check for a detail */
- /* : texture and add that to the list first. */
-
- char *(array[100]) = {
- "testje",
- "foo",
- "bar",
- }
-
- enum soppie
- {
- yes = 0,
- no,
- maybe
- };
-
- typedef enum soppie
- {
- yes = 0,
- no,
- maybe
- };
-
- static enum
- {
- yes = 0,
- no,
- maybe
- } soppie;
-
- public static enum
- {
- yes = 0,
- no,
- maybe
- } soppie;
-
- static private enum
- {
- yes = 0,
- no,
- maybe
- } soppie;
-
- {
- int a,
- b;
- }
-
- {
- struct Type
- {
- int i;
- char *str;
- } var[] =
- {
- 0, "zero",
- 1, "one",
- 2, "two",
- 3, "three"
- };
-
- float matrix[3][3] =
- {
- {
- 0,
- 1,
- 2
- },
- {
- 3,
- 4,
- 5
- },
- {
- 6,
- 7,
- 8
- }
- };
- }
-
- {
- /* blah ( blah */
- /* where does this go? */
-
- /* blah ( blah */
- cmd;
-
- func(arg1,
- /* comment */
- arg2);
- a;
- {
- b;
- {
- c; /* Hey, NOW it indents?! */
- }
- }
-
- {
- func(arg1,
- arg2,
- arg3);
- /* Hey, what am I doing here? Is this coz of the ","? */
- }
- }
-
- main ()
- {
- if (cond)
- {
- a = b;
- }
- if (cond) {
- a = c;
- }
- if (cond)
- a = d;
- return;
- }
-
- {
- case 2: if (asdf &&
- asdfasdf)
- aasdf;
- a = 9;
- case 3: if (asdf)
- aasdf;
- a = 9;
- case 4: x = 1;
- y = 2;
-
- label: if (asdf)
- here;
-
- label: if (asdf &&
- asdfasdf)
- {
- }
-
- label: if (asdf &&
- asdfasdf) {
- there;
- }
-
- label: if (asdf &&
- asdfasdf)
- there;
- }
-
- {
- /*
- hello with ":set comments= cino=c5"
- */
-
- /*
- hello with ":set comments= cino="
- */
- }
-
-
- {
- if (a < b) {
- a = a + 1;
- } else
- a = a + 2;
-
- if (a)
- do {
- testing;
- } while (asdfasdf);
- a = b + 1;
- asdfasdf
- }
-
- {
- for ( int i = 0;
- i < 10; i++ )
- {
- }
- i = 0;
- }
-
- class bob
- {
- int foo() {return 1;}
- int bar;
- }
-
- main()
- {
- while(1)
- if (foo)
- {
- bar;
- }
- else {
- asdf;
- }
- misplacedline;
- }
-
- {
- if (clipboard.state == SELECT_DONE
- && ((row == clipboard.start.lnum
- && col >= clipboard.start.col)
- || row > clipboard.start.lnum))
- }
-
- {
- if (1) {i += 4;}
- where_am_i;
- return 0;
- }
-
- {
- {
- } // sdf(asdf
- if (asdf)
- asd;
- }
-
- {
- label1:
- label2:
- }
-
- {
- int fooRet = foo(pBar1, false /*fKB*/,
- true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
- f() {
- for ( i = 0;
- i < m;
- /* c */ i++ ) {
- a = b;
- }
- }
- }
-
- {
- f1(/*comment*/);
- f2();
- }
-
- {
- do {
- if (foo) {
- } else
- ;
- } while (foo);
- foo(); // was wrong
- }
-
- int x; // no extra indent because of the ;
- void func()
- {
- }
-
- char *tab[] = {"aaa",
- "};", /* }; */ NULL}
- int indented;
- {}
-
- char *a[] = {"aaa", "bbb",
- "ccc", NULL};
- // here
-
- char *tab[] = {"aaa",
- "xx", /* xx */}; /* asdf */
- int not_indented;
-
- {
- do {
- switch (bla)
- {
- case 1: if (foo)
- bar;
- }
- } while (boo);
- wrong;
- }
-
- int foo,
- bar;
- int foo;
-
- #if defined(foo) \
- && defined(bar)
- char * xx = "asdf\
- foo\
- bor";
- int x;
-
- char *foo = "asdf\
- asdf\
- asdf",
- *bar;
-
- void f()
- {
- #if defined(foo) \
- && defined(bar)
- char *foo = "asdf\
- asdf\
- asdf",
- *bar;
- {
- int i;
- char *foo = "asdf\
- asdf\
- asdf",
- *bar;
- }
- #endif
- }
- #endif
-
- int y; // comment
- // comment
-
- // comment
-
- {
- Constructor(int a,
- int b ) : BaseClass(a)
- {
- }
- }
-
- void foo()
- {
- char one,
- two;
- struct bla piet,
- jan;
- enum foo kees,
- jannie;
- static unsigned sdf,
- krap;
- unsigned int piet,
- jan;
- int
- kees,
- jan;
- }
-
- {
- t(int f,
- int d); // )
- d();
- }
-
- Constructor::Constructor(int a,
- int b
- ) :
- BaseClass(a,
- b,
- c),
- mMember(b),
- {
- }
-
- Constructor::Constructor(int a,
- int b ) :
- BaseClass(a)
- {
- }
-
- Constructor::Constructor(int a,
- int b ) /*x*/ : /*x*/ BaseClass(a),
- member(b)
- {
- }
-
- A::A(int a, int b)
- : aa(a),
- bb(b),
- cc(c)
- {
- }
-
- class CAbc :
- public BaseClass1,
- protected BaseClass2
- {
- int Test() { return FALSE; }
- int Test1() { return TRUE; }
-
- CAbc(int a, int b ) :
- BaseClass(a)
- {
- switch(xxx)
- {
- case abc:
- asdf();
- break;
-
- case 999:
- baer();
- break;
- }
- }
-
- public: // <-- this was incorrectly indented before!!
- void testfall();
- protected:
- void testfall();
- };
-
- class CAbc : public BaseClass1,
- protected BaseClass2
- {
- };
-
- static struct
- {
- int a;
- int b;
- } variable[COUNT] =
- {
- {
- 123,
- 456
- },
- {
- 123,
- 456
- }
- };
-
- static struct
- {
- int a;
- int b;
- } variable[COUNT] =
- {
- { 123, 456 },
- { 123, 456 }
- };
-
- void asdf() /* ind_maxparen may cause trouble here */
- {
- if ((0
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1)) break;
- }
-
- foo()
- {
- a = cond ? foo() : asdf
- + asdf;
-
- a = cond ?
- foo() : asdf
- + asdf;
- }
-
- int main(void)
- {
- if (a)
- if (b)
- 2;
- else 3;
- next_line_of_code();
- }
-
- barry()
- {
- Foo::Foo (int one,
- int two)
- : something(4)
- {}
- }
-
- barry()
- {
- Foo::Foo (int one, int two)
- : something(4)
- {}
- }
-
- Constructor::Constructor(int a,
- int b
- ) :
- BaseClass(a,
- b,
- c),
- mMember(b)
- {
- }
- int main ()
- {
- if (lala)
- do
- ++(*lolo);
- while (lili
- && lele);
- lulu;
- }
-
- int main ()
- {
- switch (c)
- {
- case 'c': if (cond)
- {
- }
- }
- }
-
- main()
- {
- (void) MyFancyFuasdfadsfnction(
- argument);
- }
-
- main()
- {
- char foo[] = "/*";
- /* as
- df */
- hello
- }
-
- /* valid namespaces with normal indent */
- namespace
- {
- {
- 111111111111;
- }
- }
- namespace /* test */
- {
- 11111111111111111;
- }
- namespace // test
- {
- 111111111111111111;
- }
- namespace
- {
- 111111111111111111;
- }
- namespace test
- {
- 111111111111111111;
- }
- namespace{
- 111111111111111111;
- }
- namespace test{
- 111111111111111111;
- }
- namespace {
- 111111111111111111;
- }
- namespace test {
- 111111111111111111;
- namespace test2 {
- 22222222222222222;
- }
- }
- inline namespace {
- 111111111111111111;
- }
- inline /* test */ namespace {
- 111111111111111111;
- }
- inline/* test */namespace {
- 111111111111111111;
- }
-
- /* invalid namespaces use block indent */
- namespace test test2 {
- 111111111111111111111;
- }
- namespace11111111111 {
- 111111111111;
- }
- namespace() {
- 1111111111111;
- }
- namespace()
- {
- 111111111111111111;
- }
- namespace test test2
- {
- 1111111111111111111;
- }
- namespace111111111
- {
- 111111111111111111;
- }
- inlinenamespace {
- 111111111111111111;
- }
-
- void getstring() {
- /* Raw strings */
- const char* s = R"(
- test {
- # comment
- field: 123
- }
- )";
- }
-
- void getstring() {
- const char* s = R"foo(
- test {
- # comment
- field: 123
- }
- )foo";
- }
-
- {
- int a[4] = {
- [0] = 0,
- [1] = 1,
- [2] = 2,
- [3] = 3,
- };
- }
-
- {
- a = b[2]
- + 3;
- }
-
- {
- if (1)
- /* aaaaa
- * bbbbb
- */
- a = 1;
- }
-
- void func()
- {
- switch (foo)
- {
- case (bar):
- if (baz())
- quux();
- break;
- case (shmoo):
- if (!bar)
- {
- }
- case (foo1):
- switch (bar)
- {
- case baz:
- baz_f();
- break;
- }
- break;
- default:
- baz();
- baz();
- break;
- }
- }
-
- /* end of AUTO */
- [CODE]
-
- call append(0, code)
- normal gg
- call search('start of AUTO')
- exe "normal =/end of AUTO\<CR>"
-
- let expected =<< trim [CODE]
- /* start of AUTO matically checked vim: set ts=4 : */
- {
- if (test)
- cmd1;
- cmd2;
- }
-
- {
- if (test)
- cmd1;
- else
- cmd2;
- }
-
- {
- if (test)
- {
- cmd1;
- cmd2;
- }
- }
-
- {
- if (test)
- {
- cmd1;
- else
- }
- }
-
- {
- while (this)
- if (test)
- cmd1;
- cmd2;
- }
-
- {
- while (this)
- if (test)
- cmd1;
- else
- cmd2;
- }
-
- {
- if (test)
- {
- cmd;
- }
-
- if (test)
- cmd;
- }
-
- {
- if (test) {
- cmd;
- }
-
- if (test) cmd;
- }
-
- {
- cmd1;
- for (blah)
- while (this)
- if (test)
- cmd2;
- cmd3;
- }
-
- {
- cmd1;
- for (blah)
- while (this)
- if (test)
- cmd2;
- cmd3;
-
- if (test)
- {
- cmd1;
- cmd2;
- cmd3;
- }
- }
-
-
- /* Test for 'cindent' do/while mixed with if/else: */
-
- {
- do
- if (asdf)
- asdfasd;
- while (cond);
-
- do
- if (asdf)
- while (asdf)
- asdf;
- while (asdf);
- }
-
- /* Test for 'cindent' with two ) on a continuation line */
- {
- if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
- aal;sdkjf ( ;asldfkja;sldfk
- al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
- line up here;
- }
-
-
- /* C++ tests: */
-
- // foo() these three lines should remain in column 0
- // {
- // }
-
- /* Test for continuation and unterminated lines: */
- {
- i = 99 + 14325 +
- 21345 +
- 21345 +
- 21345 + ( 21345 +
- 21345) +
- 2345 +
- 1234;
- c = 1;
- }
-
- /*
- testje for indent with empty line
-
- here */
-
- {
- if (testing &&
- not a joke ||
- line up here)
- hay;
- if (testing &&
- (not a joke || testing
- )line up here)
- hay;
- if (testing &&
- (not a joke || testing
- line up here))
- hay;
- }
-
-
- {
- switch (c)
- {
- case xx:
- do
- if (asdf)
- do
- asdfasdf;
- while (asdf);
- else
- asdfasdf;
- while (cond);
- case yy:
- case xx:
- case zz:
- testing;
- }
- }
-
- {
- if (cond) {
- foo;
- }
- else
- {
- bar;
- }
- }
-
- {
- if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
- alsdkfj (asldk;fj
- awith cino=(0 ;lf this one goes to below the paren with ==
- ;laksjfd ;lsakdjf ;alskdf asd)
- asdfasdf;)))
- asdfasdf;
- }
-
- int
- func(a, b)
- int a;
- int c;
- {
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3)
- )
- }
-
- {
- while (asd)
- {
- if (asdf)
- if (test)
- if (that)
- {
- if (asdf)
- do
- cdasd;
- while (as
- df);
- }
- else
- if (asdf)
- asdf;
- else
- asdf;
- asdf;
- }
- }
-
- {
- s = "/*"; b = ';'
- s = "/*"; b = ';';
- a = b;
- }
-
- {
- switch (a)
- {
- case a:
- switch (t)
- {
- case 1:
- cmd;
- break;
- case 2:
- cmd;
- break;
- }
- cmd;
- break;
- case b:
- {
- int i;
- cmd;
- }
- break;
- case c: {
- int i;
- cmd;
- }
- case d: if (cond &&
- test) { /* this line doesn't work right */
- int i;
- cmd;
- }
- break;
- }
- }
-
- {
- if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
- (bp_to->b_p_initialized ||
- (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
- return;
- label :
- asdf = asdf ?
- asdf : asdf;
- asdf = asdf ?
- asdf: asdf;
- }
-
- /* Special Comments : This function has the added complexity (compared */
- /* : to addtolist) of having to check for a detail */
- /* : texture and add that to the list first. */
-
- char *(array[100]) = {
- "testje",
- "foo",
- "bar",
- }
-
- enum soppie
- {
- yes = 0,
- no,
- maybe
- };
-
- typedef enum soppie
- {
- yes = 0,
- no,
- maybe
- };
-
- static enum
- {
- yes = 0,
- no,
- maybe
- } soppie;
-
- public static enum
- {
- yes = 0,
- no,
- maybe
- } soppie;
-
- static private enum
- {
- yes = 0,
- no,
- maybe
- } soppie;
-
- {
- int a,
- b;
- }
-
- {
- struct Type
- {
- int i;
- char *str;
- } var[] =
- {
- 0, "zero",
- 1, "one",
- 2, "two",
- 3, "three"
- };
-
- float matrix[3][3] =
- {
- {
- 0,
- 1,
- 2
- },
- {
- 3,
- 4,
- 5
- },
- {
- 6,
- 7,
- 8
- }
- };
- }
-
- {
- /* blah ( blah */
- /* where does this go? */
-
- /* blah ( blah */
- cmd;
-
- func(arg1,
- /* comment */
- arg2);
- a;
- {
- b;
- {
- c; /* Hey, NOW it indents?! */
- }
- }
-
- {
- func(arg1,
- arg2,
- arg3);
- /* Hey, what am I doing here? Is this coz of the ","? */
- }
- }
-
- main ()
- {
- if (cond)
- {
- a = b;
- }
- if (cond) {
- a = c;
- }
- if (cond)
- a = d;
- return;
- }
-
- {
- case 2: if (asdf &&
- asdfasdf)
- aasdf;
- a = 9;
- case 3: if (asdf)
- aasdf;
- a = 9;
- case 4: x = 1;
- y = 2;
-
- label: if (asdf)
- here;
-
- label: if (asdf &&
- asdfasdf)
- {
- }
-
- label: if (asdf &&
- asdfasdf) {
- there;
- }
-
- label: if (asdf &&
- asdfasdf)
- there;
- }
-
- {
- /*
- hello with ":set comments= cino=c5"
- */
-
- /*
- hello with ":set comments= cino="
- */
- }
-
-
- {
- if (a < b) {
- a = a + 1;
- } else
- a = a + 2;
-
- if (a)
- do {
- testing;
- } while (asdfasdf);
- a = b + 1;
- asdfasdf
- }
-
- {
- for ( int i = 0;
- i < 10; i++ )
- {
- }
- i = 0;
- }
-
- class bob
- {
- int foo() {return 1;}
- int bar;
- }
-
- main()
- {
- while(1)
- if (foo)
- {
- bar;
- }
- else {
- asdf;
- }
- misplacedline;
- }
-
- {
- if (clipboard.state == SELECT_DONE
- && ((row == clipboard.start.lnum
- && col >= clipboard.start.col)
- || row > clipboard.start.lnum))
- }
-
- {
- if (1) {i += 4;}
- where_am_i;
- return 0;
- }
-
- {
- {
- } // sdf(asdf
- if (asdf)
- asd;
- }
-
- {
- label1:
- label2:
- }
-
- {
- int fooRet = foo(pBar1, false /*fKB*/,
- true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
- f() {
- for ( i = 0;
- i < m;
- /* c */ i++ ) {
- a = b;
- }
- }
- }
-
- {
- f1(/*comment*/);
- f2();
- }
-
- {
- do {
- if (foo) {
- } else
- ;
- } while (foo);
- foo(); // was wrong
- }
-
- int x; // no extra indent because of the ;
- void func()
- {
- }
-
- char *tab[] = {"aaa",
- "};", /* }; */ NULL}
- int indented;
- {}
-
- char *a[] = {"aaa", "bbb",
- "ccc", NULL};
- // here
-
- char *tab[] = {"aaa",
- "xx", /* xx */}; /* asdf */
- int not_indented;
-
- {
- do {
- switch (bla)
- {
- case 1: if (foo)
- bar;
- }
- } while (boo);
- wrong;
- }
-
- int foo,
- bar;
- int foo;
-
- #if defined(foo) \
- && defined(bar)
- char * xx = "asdf\
- foo\
- bor";
- int x;
-
- char *foo = "asdf\
- asdf\
- asdf",
- *bar;
-
- void f()
- {
- #if defined(foo) \
- && defined(bar)
- char *foo = "asdf\
- asdf\
- asdf",
- *bar;
- {
- int i;
- char *foo = "asdf\
- asdf\
- asdf",
- *bar;
- }
- #endif
- }
- #endif
-
- int y; // comment
- // comment
-
- // comment
-
- {
- Constructor(int a,
- int b ) : BaseClass(a)
- {
- }
- }
-
- void foo()
- {
- char one,
- two;
- struct bla piet,
- jan;
- enum foo kees,
- jannie;
- static unsigned sdf,
- krap;
- unsigned int piet,
- jan;
- int
- kees,
- jan;
- }
-
- {
- t(int f,
- int d); // )
- d();
- }
-
- Constructor::Constructor(int a,
- int b
- ) :
- BaseClass(a,
- b,
- c),
- mMember(b),
- {
- }
-
- Constructor::Constructor(int a,
- int b ) :
- BaseClass(a)
- {
- }
-
- Constructor::Constructor(int a,
- int b ) /*x*/ : /*x*/ BaseClass(a),
- member(b)
- {
- }
-
- A::A(int a, int b)
- : aa(a),
- bb(b),
- cc(c)
- {
- }
-
- class CAbc :
- public BaseClass1,
- protected BaseClass2
- {
- int Test() { return FALSE; }
- int Test1() { return TRUE; }
-
- CAbc(int a, int b ) :
- BaseClass(a)
- {
- switch(xxx)
- {
- case abc:
- asdf();
- break;
-
- case 999:
- baer();
- break;
- }
- }
-
- public: // <-- this was incorrectly indented before!!
- void testfall();
- protected:
- void testfall();
- };
-
- class CAbc : public BaseClass1,
- protected BaseClass2
- {
- };
-
- static struct
- {
- int a;
- int b;
- } variable[COUNT] =
- {
- {
- 123,
- 456
- },
- {
- 123,
- 456
- }
- };
-
- static struct
- {
- int a;
- int b;
- } variable[COUNT] =
- {
- { 123, 456 },
- { 123, 456 }
- };
-
- void asdf() /* ind_maxparen may cause trouble here */
- {
- if ((0
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1
- && 1)) break;
- }
-
- foo()
- {
- a = cond ? foo() : asdf
- + asdf;
-
- a = cond ?
- foo() : asdf
- + asdf;
- }
-
- int main(void)
- {
- if (a)
- if (b)
- 2;
- else 3;
- next_line_of_code();
- }
-
- barry()
- {
- Foo::Foo (int one,
- int two)
- : something(4)
- {}
- }
-
- barry()
- {
- Foo::Foo (int one, int two)
- : something(4)
- {}
- }
-
- Constructor::Constructor(int a,
- int b
- ) :
- BaseClass(a,
- b,
- c),
- mMember(b)
- {
- }
- int main ()
- {
- if (lala)
- do
- ++(*lolo);
- while (lili
- && lele);
- lulu;
- }
-
- int main ()
- {
- switch (c)
- {
- case 'c': if (cond)
- {
- }
- }
- }
-
- main()
- {
- (void) MyFancyFuasdfadsfnction(
- argument);
- }
-
- main()
- {
- char foo[] = "/*";
- /* as
- df */
- hello
- }
-
- /* valid namespaces with normal indent */
- namespace
- {
- {
- 111111111111;
- }
- }
- namespace /* test */
- {
- 11111111111111111;
- }
- namespace // test
- {
- 111111111111111111;
- }
- namespace
- {
- 111111111111111111;
- }
- namespace test
- {
- 111111111111111111;
- }
- namespace{
- 111111111111111111;
- }
- namespace test{
- 111111111111111111;
- }
- namespace {
- 111111111111111111;
- }
- namespace test {
- 111111111111111111;
- namespace test2 {
- 22222222222222222;
- }
- }
- inline namespace {
- 111111111111111111;
- }
- inline /* test */ namespace {
- 111111111111111111;
- }
- inline/* test */namespace {
- 111111111111111111;
- }
-
- /* invalid namespaces use block indent */
- namespace test test2 {
- 111111111111111111111;
- }
- namespace11111111111 {
- 111111111111;
- }
- namespace() {
- 1111111111111;
- }
- namespace()
- {
- 111111111111111111;
- }
- namespace test test2
- {
- 1111111111111111111;
- }
- namespace111111111
- {
- 111111111111111111;
- }
- inlinenamespace {
- 111111111111111111;
- }
-
- void getstring() {
- /* Raw strings */
- const char* s = R"(
- test {
- # comment
- field: 123
- }
- )";
- }
-
- void getstring() {
- const char* s = R"foo(
- test {
- # comment
- field: 123
- }
- )foo";
- }
-
- {
- int a[4] = {
- [0] = 0,
- [1] = 1,
- [2] = 2,
- [3] = 3,
- };
- }
-
- {
- a = b[2]
- + 3;
- }
-
- {
- if (1)
- /* aaaaa
- * bbbbb
- */
- a = 1;
- }
-
- void func()
- {
- switch (foo)
- {
- case (bar):
- if (baz())
- quux();
- break;
- case (shmoo):
- if (!bar)
- {
- }
- case (foo1):
- switch (bar)
- {
- case baz:
- baz_f();
- break;
- }
- break;
- default:
- baz();
- baz();
- break;
- }
- }
-
- /* end of AUTO */
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_2()
- new
- setl cindent ts=4 sw=4
- setl tw=0 noai fo=croq
- let &wm = &columns - 20
-
- let code =<< trim [CODE]
- {
-
- /* this is
- * a real serious important big
- * comment
- */
- /* insert " about life, the universe, and the rest" after "serious" */
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('serious', 'e')
- normal a about life, the universe, and the rest
-
- let expected =<< trim [CODE]
- {
-
- /* this is
- * a real serious
- * about life, the
- * universe, and the
- * rest important big
- * comment
- */
- /* insert " about life, the universe, and the rest" after "serious" */
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- set wm&
- enew! | close
-endfunc
-
-func Test_cindent_3()
- new
- setl nocindent ts=4 sw=4
-
- let code =<< trim [CODE]
- {
- /*
- * Testing for comments, without 'cin' set
- */
-
- /*
- * what happens here?
- */
-
- /*
- the end of the comment, try inserting a line below */
-
- /* how about
- this one */
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('comments')
- normal joabout life
- call search('happens')
- normal jothere
- call search('below')
- normal oline
- call search('this')
- normal Ohello
-
- let expected =<< trim [CODE]
- {
- /*
- * Testing for comments, without 'cin' set
- */
- about life
-
- /*
- * what happens here?
- */
- there
-
- /*
- the end of the comment, try inserting a line below */
- line
-
- /* how about
- hello
- this one */
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_4()
- new
- setl cindent ts=4 sw=4
-
- let code =<< trim [CODE]
- {
- var = this + that + vec[0] * vec[0]
- + vec[1] * vec[1]
- + vec2[2] * vec[2];
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('vec2')
- normal ==
-
- let expected =<< trim [CODE]
- {
- var = this + that + vec[0] * vec[0]
- + vec[1] * vec[1]
- + vec2[2] * vec[2];
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_5()
- new
- setl cindent ts=4 sw=4
- setl cino=}4
-
- let code =<< trim [CODE]
- {
- asdf asdflkajds f;
- if (tes & ting) {
- asdf asdf asdf ;
- asdfa sdf asdf;
- }
- testing1;
- if (tes & ting)
- {
- asdf asdf asdf ;
- asdfa sdf asdf;
- }
- testing2;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('testing1')
- exe "normal k2==/testing2\<CR>"
- normal k2==
-
- let expected =<< trim [CODE]
- {
- asdf asdflkajds f;
- if (tes & ting) {
- asdf asdf asdf ;
- asdfa sdf asdf;
- }
- testing1;
- if (tes & ting)
- {
- asdf asdf asdf ;
- asdfa sdf asdf;
- }
- testing2;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_6()
- new
- setl cindent ts=4 sw=4
- setl cino=(0,)20
-
- let code =<< trim [CODE]
- main ( int first_par, /*
- * Comment for
- * first par
- */
- int second_par /*
- * Comment for
- * second par
- */
- )
- {
- func( first_par, /*
- * Comment for
- * first par
- */
- second_par /*
- * Comment for
- * second par
- */
- );
-
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('main')
- normal =][
-
- let expected =<< trim [CODE]
- main ( int first_par, /*
- * Comment for
- * first par
- */
- int second_par /*
- * Comment for
- * second par
- */
- )
- {
- func( first_par, /*
- * Comment for
- * first par
- */
- second_par /*
- * Comment for
- * second par
- */
- );
-
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_7()
- new
- setl cindent ts=4 sw=4
- setl cino=es,n0s
-
- let code =<< trim [CODE]
- main(void)
- {
- /* Make sure that cino=X0s is not parsed like cino=Xs. */
- if (cond)
- foo();
- else
- {
- bar();
- }
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('main')
- normal =][
-
- let expected =<< trim [CODE]
- main(void)
- {
- /* Make sure that cino=X0s is not parsed like cino=Xs. */
- if (cond)
- foo();
- else
- {
- bar();
- }
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_8()
- new
- setl cindent ts=4 sw=4
- setl cino=
-
- let code =<< trim [CODE]
-
- {
- do
- {
- if ()
- {
- if ()
- asdf;
- else
- asdf;
- }
- } while ();
- cmd; /* this should go under the } */
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
-
- {
- do
- {
- if ()
- {
- if ()
- asdf;
- else
- asdf;
- }
- } while ();
- cmd; /* this should go under the } */
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_9()
- new
- setl cindent ts=4 sw=4
-
- let code =<< trim [CODE]
-
- void f()
- {
- if ( k() ) {
- l();
-
- } else { /* Start (two words) end */
- m();
- }
-
- n();
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
-
- void f()
- {
- if ( k() ) {
- l();
-
- } else { /* Start (two words) end */
- m();
- }
-
- n();
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_10()
- new
- setl cindent ts=4 sw=4
- setl cino={s,e-s
-
- let code =<< trim [CODE]
-
- void f()
- {
- if ( k() )
- {
- l();
- } else { /* Start (two words) end */
- m();
- }
- n(); /* should be under the if () */
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
-
- void f()
- {
- if ( k() )
- {
- l();
- } else { /* Start (two words) end */
- m();
- }
- n(); /* should be under the if () */
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_11()
- new
- setl cindent ts=4 sw=4
- setl cino={s,fs
-
- let code =<< trim [CODE]
- void bar(void)
- {
- static array[2][2] =
- {
- { 1, 2 },
- { 3, 4 },
- }
-
- while (a)
- {
- foo(&a);
- }
-
- {
- int a;
- {
- a = a + 1;
- }
- }
- b = a;
- }
-
- void func(void)
- {
- a = 1;
- {
- b = 2;
- }
- c = 3;
- d = 4;
- }
- /* foo */
- [CODE]
-
- call append(0, code)
- normal gg
- exe "normal ]]=/ foo\<CR>"
-
- let expected =<< trim [CODE]
- void bar(void)
- {
- static array[2][2] =
- {
- { 1, 2 },
- { 3, 4 },
- }
-
- while (a)
- {
- foo(&a);
- }
-
- {
- int a;
- {
- a = a + 1;
- }
- }
- b = a;
- }
-
- void func(void)
- {
- a = 1;
- {
- b = 2;
- }
- c = 3;
- d = 4;
- }
- /* foo */
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_12()
- new
- setl cindent ts=4 sw=4
- setl cino=
-
- let code =<< trim [CODE]
- a()
- {
- do {
- a = a +
- a;
- } while ( a ); /* add text under this line */
- if ( a )
- a;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('while')
- normal ohere
-
- let expected =<< trim [CODE]
- a()
- {
- do {
- a = a +
- a;
- } while ( a ); /* add text under this line */
- here
- if ( a )
- a;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_13()
- new
- setl cindent ts=4 sw=4
- setl cino= com=
-
- let code =<< trim [CODE]
- a()
- {
- label1:
- /* hmm */
- // comment
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('comment')
- exe "normal olabel2: b();\rlabel3 /* post */:\r/* pre */ label4:\r" .
- \ "f(/*com*/);\rif (/*com*/)\rcmd();"
-
- let expected =<< trim [CODE]
- a()
- {
- label1:
- /* hmm */
- // comment
- label2: b();
- label3 /* post */:
- /* pre */ label4:
- f(/*com*/);
- if (/*com*/)
- cmd();
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_14()
- new
- setl cindent ts=4 sw=4
- setl comments& comments^=s:/*,m:**,ex:*/
-
- let code =<< trim [CODE]
- /*
- * A simple comment
- */
-
- /*
- ** A different comment
- */
- [CODE]
-
- call append(0, code)
- normal gg
- call search('simple')
- normal =5j
-
- let expected =<< trim [CODE]
- /*
- * A simple comment
- */
-
- /*
- ** A different comment
- */
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_15()
- new
- setl cindent ts=4 sw=4
- setl cino=c0
- setl comments& comments-=s1:/* comments^=s0:/*
-
- let code =<< trim [CODE]
- void f()
- {
-
- /*********
- A comment.
- *********/
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
-
- /*********
- A comment.
- *********/
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_16()
- new
- setl cindent ts=4 sw=4
- setl cino=c0,C1
- setl comments& comments-=s1:/* comments^=s0:/*
-
- let code =<< trim [CODE]
- void f()
- {
-
- /*********
- A comment.
- *********/
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
-
- /*********
- A comment.
- *********/
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_17()
- new
- setl cindent ts=4 sw=4
- setl cino=
-
- let code =<< trim [CODE]
- void f()
- {
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_18()
- new
- setl cindent ts=4 sw=4
- setl cino=(s
-
- let code =<< trim [CODE]
- void f()
- {
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_19()
- new
- setl cindent ts=4 sw=4
- set cino=(s,U1
-
- let code =<< trim [CODE]
- void f()
- {
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_20()
- new
- setl cindent ts=4 sw=4
- setl cino=(0
-
- let code =<< trim [CODE]
- void f()
- {
- if ( c1
- && ( c2
- || c3))
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- if ( c1
- && ( c2
- || c3))
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_21()
- new
- setl cindent ts=4 sw=4
- setl cino=(0,w1
-
- let code =<< trim [CODE]
- void f()
- {
- if ( c1
- && ( c2
- || c3))
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- if ( c1
- && ( c2
- || c3))
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_22()
- new
- setl cindent ts=4 sw=4
- setl cino=(s
-
- let code =<< trim [CODE]
- void f()
- {
- c = c1 && (
- c2 ||
- c3
- ) && c4;
- if (
- c1 && c2
- )
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- c = c1 && (
- c2 ||
- c3
- ) && c4;
- if (
- c1 && c2
- )
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_23()
- new
- setl cindent ts=4 sw=4
- setl cino=(s,m1
-
- let code =<< trim [CODE]
- void f()
- {
- c = c1 && (
- c2 ||
- c3
- ) && c4;
- if (
- c1 && c2
- )
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- c = c1 && (
- c2 ||
- c3
- ) && c4;
- if (
- c1 && c2
- )
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_24()
- new
- setl cindent ts=4 sw=4
- setl cino=b1
-
- let code =<< trim [CODE]
- void f()
- {
- switch (x)
- {
- case 1:
- a = b;
- break;
- default:
- a = 0;
- break;
- }
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- switch (x)
- {
- case 1:
- a = b;
- break;
- default:
- a = 0;
- break;
- }
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_25()
- new
- setl cindent ts=4 sw=4
- setl cino=(0,W5
-
- let code =<< trim [CODE]
- void f()
- {
- invokeme(
- argu,
- ment);
- invokeme(
- argu,
- ment
- );
- invokeme(argu,
- ment
- );
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- invokeme(
- argu,
- ment);
- invokeme(
- argu,
- ment
- );
- invokeme(argu,
- ment
- );
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_26()
- new
- setl cindent ts=4 sw=4
- setl cino=/6
-
- let code =<< trim [CODE]
- void f()
- {
- statement;
- // comment 1
- // comment 2
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void f()
- {
- statement;
- // comment 1
- // comment 2
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_27()
- new
- setl cindent ts=4 sw=4
- setl cino=
-
- let code =<< trim [CODE]
- void f()
- {
- statement;
- // comment 1
- // comment 2
- }
- [CODE]
-
- call append(0, code)
- normal gg
- exe "normal ]]/comment 1/+1\<CR>=="
-
- let expected =<< trim [CODE]
- void f()
- {
- statement;
- // comment 1
- // comment 2
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_28()
- new
- setl cindent ts=4 sw=4
- setl cino=g0
-
- let code =<< trim [CODE]
- class CAbc
- {
- int Test() { return FALSE; }
-
- public: // comment
- void testfall();
- protected:
- void testfall();
- };
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- class CAbc
- {
- int Test() { return FALSE; }
-
- public: // comment
- void testfall();
- protected:
- void testfall();
- };
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_29()
- new
- setl cindent ts=4 sw=4
- setl cino=(0,gs,hs
-
- let code =<< trim [CODE]
- class Foo : public Bar
- {
- public:
- virtual void method1(void) = 0;
- virtual void method2(int arg1,
- int arg2,
- int arg3) = 0;
- };
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- class Foo : public Bar
- {
- public:
- virtual void method1(void) = 0;
- virtual void method2(int arg1,
- int arg2,
- int arg3) = 0;
- };
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_30()
- new
- setl cindent ts=4 sw=4
- setl cino=+20
-
- let code =<< [CODE]
- void
-foo()
-{
- if (a)
- {
- } else
- asdf;
-}
-[CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< [CODE]
- void
-foo()
-{
- if (a)
- {
- } else
- asdf;
-}
-
-[CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_31()
- new
- setl cindent ts=4 sw=4
- setl cino=(0,W2s
-
- let code =<< trim [CODE]
-
- {
- averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
- asdasdf,
- func(asdf,
- asdfadsf),
- asdfasdf
- );
-
- /* those are ugly, but consequent */
-
- func()->asd(asdasdf,
- averylongfunctionname(
- abc,
- dec)->averylongfunctionname(
- asdfadsf,
- asdfasdf,
- asdfasdf,
- ),
- func(asdfadf,
- asdfasdf
- ),
- asdasdf
- );
-
- averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
- abc,
- dec)->asdfasdfasdf(
- asdfadsf,
- asdfasdf,
- asdfasdf,
- ),
- func(asdfadf,
- asdfasdf),
- asdasdf
- );
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
-
- {
- averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
- asdasdf,
- func(asdf,
- asdfadsf),
- asdfasdf
- );
-
- /* those are ugly, but consequent */
-
- func()->asd(asdasdf,
- averylongfunctionname(
- abc,
- dec)->averylongfunctionname(
- asdfadsf,
- asdfasdf,
- asdfasdf,
- ),
- func(asdfadf,
- asdfasdf
- ),
- asdasdf
- );
-
- averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
- abc,
- dec)->asdfasdfasdf(
- asdfadsf,
- asdfasdf,
- asdfasdf,
- ),
- func(asdfadf,
- asdfasdf),
- asdasdf
- );
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_32()
- new
- setl cindent ts=4 sw=4
- setl cino=M1
-
- let code =<< trim [CODE]
- int main ()
- {
- if (cond1 &&
- cond2
- )
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- int main ()
- {
- if (cond1 &&
- cond2
- )
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_33()
- new
- setl cindent ts=4 sw=4
- setl cino=(0,ts
-
- let code =<< trim [CODE]
- void func(int a
- #if defined(FOO)
- , int b
- , int c
- #endif
- )
- {
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal 2j=][
-
- let expected =<< trim [CODE]
- void func(int a
- #if defined(FOO)
- , int b
- , int c
- #endif
- )
- {
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_34()
- new
- setl cindent ts=4 sw=4
- setl cino=(0
-
- let code =<< trim [CODE]
-
- void
- func(int a
- #if defined(FOO)
- , int b
- , int c
- #endif
- )
- {
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal =][
-
- let expected =<< trim [CODE]
-
- void
- func(int a
- #if defined(FOO)
- , int b
- , int c
- #endif
- )
- {
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_35()
- new
- setl cindent ts=4 sw=4
- setl cino&
-
- let code =<< trim [CODE]
- void func(void)
- {
- if(x==y)
- if(y==z)
- foo=1;
- else { bar=1;
- baz=2;
- }
- printf("Foo!\n");
- }
-
- void func1(void)
- {
- char* tab[] = {"foo", "bar",
- "baz", "quux",
- "this line used", "to be indented incorrectly"};
- foo();
- }
-
- void func2(void)
- {
- int tab[] =
- {1, 2,
- 3, 4,
- 5, 6};
-
- printf("This line used to be indented incorrectly.\n");
- }
-
- int foo[]
- #ifdef BAR
-
- = { 1, 2, 3,
- 4, 5, 6 }
-
- #endif
- ;
- int baz;
-
- void func3(void)
- {
- int tab[] = {
- 1, 2,
- 3, 4,
- 5, 6};
-
- printf("Don't you dare indent this line incorrectly!\n");
- }
-
- void
- func4(a, b,
- c)
- int a;
- int b;
- int c;
- {
- }
-
- void
- func5(
- int a,
- int b)
- {
- }
-
- void
- func6(
- int a)
- {
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=7][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if(x==y)
- if(y==z)
- foo=1;
- else { bar=1;
- baz=2;
- }
- printf("Foo!\n");
- }
-
- void func1(void)
- {
- char* tab[] = {"foo", "bar",
- "baz", "quux",
- "this line used", "to be indented incorrectly"};
- foo();
- }
-
- void func2(void)
- {
- int tab[] =
- {1, 2,
- 3, 4,
- 5, 6};
-
- printf("This line used to be indented incorrectly.\n");
- }
-
- int foo[]
- #ifdef BAR
-
- = { 1, 2, 3,
- 4, 5, 6 }
-
- #endif
- ;
- int baz;
-
- void func3(void)
- {
- int tab[] = {
- 1, 2,
- 3, 4,
- 5, 6};
-
- printf("Don't you dare indent this line incorrectly!\n");
- }
-
- void
- func4(a, b,
- c)
- int a;
- int b;
- int c;
- {
- }
-
- void
- func5(
- int a,
- int b)
- {
- }
-
- void
- func6(
- int a)
- {
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_36()
- new
- setl cindent ts=4 sw=4
- setl cino&
- setl cino+=l1
-
- let code =<< trim [CODE]
- void func(void)
- {
- int tab[] =
- {
- 1, 2, 3,
- 4, 5, 6};
-
- printf("Indent this line correctly!\n");
-
- switch (foo)
- {
- case bar:
- printf("bar");
- break;
- case baz: {
- printf("baz");
- break;
- }
- case quux:
- printf("But don't break the indentation of this instruction\n");
- break;
- }
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- int tab[] =
- {
- 1, 2, 3,
- 4, 5, 6};
-
- printf("Indent this line correctly!\n");
-
- switch (foo)
- {
- case bar:
- printf("bar");
- break;
- case baz: {
- printf("baz");
- break;
- }
- case quux:
- printf("But don't break the indentation of this instruction\n");
- break;
- }
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_37()
- new
- setl cindent ts=4 sw=4
- setl cino&
-
- let code =<< trim [CODE]
- void func(void)
- {
- cout << "a"
- << "b"
- << ") :"
- << "c";
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- cout << "a"
- << "b"
- << ") :"
- << "c";
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_38()
- new
- setl cindent ts=4 sw=4
- setl com=s1:/*,m:*,ex:*/
-
- let code =<< trim [CODE]
- void func(void)
- {
- /*
- * This is a comment.
- */
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]3jofoo();
-
- let expected =<< trim [CODE]
- void func(void)
- {
- /*
- * This is a comment.
- */
- foo();
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_39()
- new
- setl cindent ts=4 sw=4
- setl cino&
-
- let code =<< trim [CODE]
- void func(void)
- {
- for (int i = 0; i < 10; ++i)
- if (i & 1) {
- foo(1);
- } else
- foo(0);
- baz();
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- for (int i = 0; i < 10; ++i)
- if (i & 1) {
- foo(1);
- } else
- foo(0);
- baz();
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_40()
- new
- setl cindent ts=4 sw=4
- setl cino=k2s,(0
-
- let code =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
-
- if ( c1
- && ( c2
- || c3))
- foo;
- func( c1
- && ( c2
- || c3))
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
-
- if ( c1
- && ( c2
- || c3))
- foo;
- func( c1
- && ( c2
- || c3))
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_41()
- new
- setl cindent ts=4 sw=4
- setl cino=k2s,(s
-
- let code =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
-
- if ( c1
- && ( c2
- || c3))
- foo;
- func( c1
- && ( c2
- || c3))
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
-
- if ( c1
- && ( c2
- || c3))
- foo;
- func( c1
- && ( c2
- || c3))
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_42()
- new
- setl cindent ts=4 sw=4
- setl cino=k2s,(s,U1
-
- let code =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
-
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
-
- c = c1 &&
- (
- c2 ||
- c3
- ) && c4;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_43()
- new
- setl cindent ts=4 sw=4
- setl cino=k2s,(0,W4
-
- let code =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
-
- if ( c1
- && ( c2
- || c3))
- foo;
-
- a_long_line(
- argument,
- argument);
- a_short_line(argument,
- argument);
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
-
- if ( c1
- && ( c2
- || c3))
- foo;
-
- a_long_line(
- argument,
- argument);
- a_short_line(argument,
- argument);
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_44()
- new
- setl cindent ts=4 sw=4
- setl cino=k2s,u2
-
- let code =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_45()
- new
- setl cindent ts=4 sw=4
- setl cino=k2s,(0,w1
-
- let code =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
-
- if ( c1
- && ( c2
- || c3))
- foo;
- func( c1
- && ( c2
- || c3))
- foo;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- if (c123456789
- && (c22345
- || c3))
- printf("foo\n");
-
- if ( c1
- && ( c2
- || c3))
- foo;
- func( c1
- && ( c2
- || c3))
- foo;
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_46()
- new
- setl cindent ts=4 sw=4
- setl cino=k2,(s
-
- let code =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- void func(void)
- {
- if (condition1
- && condition2)
- action();
- function(argument1
- && argument2);
-
- if (c1 && (c2 ||
- c3))
- foo;
- if (c1 &&
- (c2 || c3))
- {
- }
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_47()
- new
- setl cindent ts=4 sw=4
- setl cino=N-s
-
- let code =<< trim [CODE]
- NAMESPACESTART
- /* valid namespaces with normal indent */
- namespace
- {
- {
- 111111111111;
- }
- }
- namespace /* test */
- {
- 11111111111111111;
- }
- namespace // test
- {
- 111111111111111111;
- }
- namespace
- {
- 111111111111111111;
- }
- namespace test
- {
- 111111111111111111;
- }
- namespace test::cpp17
- {
- 111111111111111111;
- }
- namespace ::incorrectcpp17
- {
- 111111111111111111;
- }
- namespace test::incorrectcpp17::
- {
- 111111111111111111;
- }
- namespace test:incorrectcpp17
- {
- 111111111111111111;
- }
- namespace test:::incorrectcpp17
- {
- 111111111111111111;
- }
- namespace{
- 111111111111111111;
- }
- namespace test{
- 111111111111111111;
- }
- namespace {
- 111111111111111111;
- }
- namespace test {
- 111111111111111111;
- namespace test2 {
- 22222222222222222;
- }
- }
- inline namespace {
- 111111111111111111;
- }
- inline /* test */ namespace {
- 111111111111111111;
- }
- inline/* test */namespace {
- 111111111111111111;
- }
-
- /* invalid namespaces use block indent */
- namespace test test2 {
- 111111111111111111111;
- }
- namespace11111111111 {
- 111111111111;
- }
- namespace() {
- 1111111111111;
- }
- namespace()
- {
- 111111111111111111;
- }
- namespace test test2
- {
- 1111111111111111111;
- }
- namespace111111111
- {
- 111111111111111111;
- }
- inlinenamespace {
- 111111111111111111;
- }
- NAMESPACEEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^NAMESPACESTART')
- exe "normal =/^NAMESPACEEND\n"
-
- let expected =<< trim [CODE]
- NAMESPACESTART
- /* valid namespaces with normal indent */
- namespace
- {
- {
- 111111111111;
- }
- }
- namespace /* test */
- {
- 11111111111111111;
- }
- namespace // test
- {
- 111111111111111111;
- }
- namespace
- {
- 111111111111111111;
- }
- namespace test
- {
- 111111111111111111;
- }
- namespace test::cpp17
- {
- 111111111111111111;
- }
- namespace ::incorrectcpp17
- {
- 111111111111111111;
- }
- namespace test::incorrectcpp17::
- {
- 111111111111111111;
- }
- namespace test:incorrectcpp17
- {
- 111111111111111111;
- }
- namespace test:::incorrectcpp17
- {
- 111111111111111111;
- }
- namespace{
- 111111111111111111;
- }
- namespace test{
- 111111111111111111;
- }
- namespace {
- 111111111111111111;
- }
- namespace test {
- 111111111111111111;
- namespace test2 {
- 22222222222222222;
- }
- }
- inline namespace {
- 111111111111111111;
- }
- inline /* test */ namespace {
- 111111111111111111;
- }
- inline/* test */namespace {
- 111111111111111111;
- }
-
- /* invalid namespaces use block indent */
- namespace test test2 {
- 111111111111111111111;
- }
- namespace11111111111 {
- 111111111111;
- }
- namespace() {
- 1111111111111;
- }
- namespace()
- {
- 111111111111111111;
- }
- namespace test test2
- {
- 1111111111111111111;
- }
- namespace111111111
- {
- 111111111111111111;
- }
- inlinenamespace {
- 111111111111111111;
- }
- NAMESPACEEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_48()
- new
- setl cindent ts=4 sw=4
- setl cino=j1,J1
-
- let code =<< trim [CODE]
- JSSTART
- var bar = {
- foo: {
- that: this,
- some: ok,
- },
- "bar":{
- a : 2,
- b: "123abc",
- x: 4,
- "y": 5
- }
- }
- JSEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^JSSTART')
- exe "normal =/^JSEND\n"
-
- let expected =<< trim [CODE]
- JSSTART
- var bar = {
- foo: {
- that: this,
- some: ok,
- },
- "bar":{
- a : 2,
- b: "123abc",
- x: 4,
- "y": 5
- }
- }
- JSEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_49()
- new
- setl cindent ts=4 sw=4
- setl cino=j1,J1
-
- let code =<< trim [CODE]
- JSSTART
- var foo = [
- 1,
- 2,
- 3
- ];
- JSEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^JSSTART')
- exe "normal =/^JSEND\n"
-
- let expected =<< trim [CODE]
- JSSTART
- var foo = [
- 1,
- 2,
- 3
- ];
- JSEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_50()
- new
- setl cindent ts=4 sw=4
- setl cino=j1,J1
-
- let code =<< trim [CODE]
- JSSTART
- function bar() {
- var foo = [
- 1,
- 2,
- 3
- ];
- }
- JSEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^JSSTART')
- exe "normal =/^JSEND\n"
-
- let expected =<< trim [CODE]
- JSSTART
- function bar() {
- var foo = [
- 1,
- 2,
- 3
- ];
- }
- JSEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_51()
- new
- setl cindent ts=4 sw=4
- setl cino=j1,J1
-
- let code =<< trim [CODE]
- JSSTART
- (function($){
-
- if (cond &&
- cond) {
- stmt;
- }
- window.something.left =
- (width - 50 + offset) + "px";
- var class_name='myclass';
-
- function private_method() {
- }
-
- var public_method={
- method: function(options,args){
- private_method();
- }
- }
-
- function init(options) {
-
- $(this).data(class_name+'_public',$.extend({},{
- foo: 'bar',
- bar: 2,
- foobar: [
- 1,
- 2,
- 3
- ],
- callback: function(){
- return true;
- }
- }, options||{}));
- }
-
- $.fn[class_name]=function() {
-
- var _arguments=arguments;
- return this.each(function(){
-
- var options=$(this).data(class_name+'_public');
- if (!options) {
- init.apply(this,_arguments);
-
- } else {
- var method=public_method[_arguments[0]];
-
- if (typeof(method)!='function') {
- console.log(class_name+' has no method "'+_arguments[0]+'"');
- return false;
- }
- _arguments[0]=options;
- method.apply(this,_arguments);
- }
- });
- }
-
- })(jQuery);
- JSEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^JSSTART')
- exe "normal =/^JSEND\n"
-
- let expected =<< trim [CODE]
- JSSTART
- (function($){
-
- if (cond &&
- cond) {
- stmt;
- }
- window.something.left =
- (width - 50 + offset) + "px";
- var class_name='myclass';
-
- function private_method() {
- }
-
- var public_method={
- method: function(options,args){
- private_method();
- }
- }
-
- function init(options) {
-
- $(this).data(class_name+'_public',$.extend({},{
- foo: 'bar',
- bar: 2,
- foobar: [
- 1,
- 2,
- 3
- ],
- callback: function(){
- return true;
- }
- }, options||{}));
- }
-
- $.fn[class_name]=function() {
-
- var _arguments=arguments;
- return this.each(function(){
-
- var options=$(this).data(class_name+'_public');
- if (!options) {
- init.apply(this,_arguments);
-
- } else {
- var method=public_method[_arguments[0]];
-
- if (typeof(method)!='function') {
- console.log(class_name+' has no method "'+_arguments[0]+'"');
- return false;
- }
- _arguments[0]=options;
- method.apply(this,_arguments);
- }
- });
- }
-
- })(jQuery);
- JSEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_52()
- new
- setl cindent ts=4 sw=4
- setl cino=j1,J1
-
- let code =<< trim [CODE]
- JSSTART
- function init(options) {
- $(this).data(class_name+'_public',$.extend({},{
- foo: 'bar',
- bar: 2,
- foobar: [
- 1,
- 2,
- 3
- ],
- callback: function(){
- return true;
- }
- }, options||{}));
- }
- JSEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^JSSTART')
- exe "normal =/^JSEND\n"
-
- let expected =<< trim [CODE]
- JSSTART
- function init(options) {
- $(this).data(class_name+'_public',$.extend({},{
- foo: 'bar',
- bar: 2,
- foobar: [
- 1,
- 2,
- 3
- ],
- callback: function(){
- return true;
- }
- }, options||{}));
- }
- JSEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_53()
- new
- setl cindent ts=4 sw=4
- setl cino=j1,J1
-
- let code =<< trim [CODE]
- JSSTART
- (function($){
- function init(options) {
- $(this).data(class_name+'_public',$.extend({},{
- foo: 'bar',
- bar: 2,
- foobar: [
- 1,
- 2,
- 3
- ],
- callback: function(){
- return true;
- }
- }, options||{}));
- }
- })(jQuery);
- JSEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^JSSTART')
- exe "normal =/^JSEND\n"
-
- let expected =<< trim [CODE]
- JSSTART
- (function($){
- function init(options) {
- $(this).data(class_name+'_public',$.extend({},{
- foo: 'bar',
- bar: 2,
- foobar: [
- 1,
- 2,
- 3
- ],
- callback: function(){
- return true;
- }
- }, options||{}));
- }
- })(jQuery);
- JSEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_54()
- new
- setl cindent ts=4 sw=4
- setl cino=j1,J1,+2
-
- let code =<< trim [CODE]
- JSSTART
- // Results of JavaScript indent
- // 1
- (function(){
- var a = [
- 'a',
- 'b',
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
- }())
-
- // 2
- (function(){
- var a = [
- 0 +
- 5 *
- 9 *
- 'a',
- 'b',
- 0 +
- 5 *
- 9 *
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
- }())
-
- // 3
- (function(){
- var a = [
- 0 +
- // comment 1
- 5 *
- /* comment 2 */
- 9 *
- 'a',
- 'b',
- 0 +
- 5 *
- 9 *
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
- }())
-
- // 4
- {
- var a = [
- 0,
- 1
- ];
- var b;
- var c;
- }
-
- // 5
- {
- var a = [
- [
- 0
- ],
- 2,
- 3
- ];
- }
-
- // 6
- {
- var a = [
- [
- 0,
- 1
- ],
- 2,
- 3
- ];
- }
-
- // 7
- {
- var a = [
- // [
- 0,
- // 1
- // ],
- 2,
- 3
- ];
- }
-
- // 8
- var x = [
- (function(){
- var a,
- b,
- c,
- d,
- e,
- f,
- g,
- h,
- i;
- })
- ];
-
- // 9
- var a = [
- 0 +
- 5 *
- 9 *
- 'a',
- 'b',
- 0 +
- 5 *
- 9 *
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
-
- // 10
- var a,
- b,
- c,
- d,
- e,
- f,
- g,
- h,
- i;
- JSEND
- [CODE]
-
- call append(0, code)
- normal gg
- call search('^JSSTART')
- exe "normal =/^JSEND\n"
-
- let expected =<< trim [CODE]
- JSSTART
- // Results of JavaScript indent
- // 1
- (function(){
- var a = [
- 'a',
- 'b',
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
- }())
-
- // 2
- (function(){
- var a = [
- 0 +
- 5 *
- 9 *
- 'a',
- 'b',
- 0 +
- 5 *
- 9 *
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
- }())
-
- // 3
- (function(){
- var a = [
- 0 +
- // comment 1
- 5 *
- /* comment 2 */
- 9 *
- 'a',
- 'b',
- 0 +
- 5 *
- 9 *
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
- }())
-
- // 4
- {
- var a = [
- 0,
- 1
- ];
- var b;
- var c;
- }
-
- // 5
- {
- var a = [
- [
- 0
- ],
- 2,
- 3
- ];
- }
-
- // 6
- {
- var a = [
- [
- 0,
- 1
- ],
- 2,
- 3
- ];
- }
-
- // 7
- {
- var a = [
- // [
- 0,
- // 1
- // ],
- 2,
- 3
- ];
- }
-
- // 8
- var x = [
- (function(){
- var a,
- b,
- c,
- d,
- e,
- f,
- g,
- h,
- i;
- })
- ];
-
- // 9
- var a = [
- 0 +
- 5 *
- 9 *
- 'a',
- 'b',
- 0 +
- 5 *
- 9 *
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- 'h',
- 'i'
- ];
-
- // 10
- var a,
- b,
- c,
- d,
- e,
- f,
- g,
- h,
- i;
- JSEND
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_55()
- new
- setl cindent ts=4 sw=4
- setl cino&
-
- let code =<< trim [CODE]
- /* start of define */
- {
- }
- #define AAA \
- BBB\
- CCC
-
- #define CNT \
- 1 + \
- 2 + \
- 4
- /* end of define */
- [CODE]
-
- call append(0, code)
- normal gg
- call search('start of define')
- exe "normal =/end of define\n"
-
- let expected =<< trim [CODE]
- /* start of define */
- {
- }
- #define AAA \
- BBB\
- CCC
-
- #define CNT \
- 1 + \
- 2 + \
- 4
- /* end of define */
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_56()
- new
- setl cindent ts=4 sw=4
- setl cino&
-
- let code =<< trim [CODE]
- {
- a = second/*bug*/*line;
- }
- [CODE]
-
- call append(0, code)
- normal gg
- call search('a = second')
- normal ox
-
- let expected =<< trim [CODE]
- {
- a = second/*bug*/*line;
- x
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-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:
- call assert_equal('case x:: // x', getline(1))
- set cindent&
- bwipe!
-endfunc
-
-" Test for changing multiple lines (using c) with cindent
-func Test_cindent_change_multline()
- new
- setlocal cindent
- call setline(1, ['if (a)', '{', ' i = 1;', '}'])
- normal! jc3jm = 2;
- call assert_equal("\tm = 2;", getline(2))
- close!
-endfunc
-
-" This was reading past the end of the line
-func Test_cindent_check_funcdecl()
- new
- sil norm o0('\0=L
- bwipe!
-endfunc
-
-func Test_cindent_scopedecls()
- new
- setl cindent ts=4 sw=4
- setl cino=g0
- setl cinsd+=public\ slots,signals
-
- let code =<< trim [CODE]
- class Foo
- {
- public:
- virtual void foo() = 0;
- public slots:
- void onBar();
- signals:
- void baz();
- private:
- int x;
- };
- [CODE]
-
- call append(0, code)
- normal gg
- normal ]]=][
-
- let expected =<< trim [CODE]
- class Foo
- {
- public:
- virtual void foo() = 0;
- public slots:
- void onBar();
- signals:
- void baz();
- private:
- int x;
- };
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_cindent_pragma()
- new
- setl cindent ts=4 sw=4
- setl cino=Ps
-
- let code =<< trim [CODE]
- {
- #pragma omp parallel
- {
- #pragma omp task
- foo();
- # pragma omp taskwait
- }
- }
- [CODE]
-
- call append(0, code)
- normal gg
- normal =G
-
- let expected =<< trim [CODE]
- {
- #pragma omp parallel
- {
- #pragma omp task
- foo();
- # pragma omp taskwait
- }
- }
-
- [CODE]
-
- call assert_equal(expected, getline(1, '$'))
- enew! | close
-endfunc
-
-func Test_backslash_at_end_of_line()
- new
- exe "norm v>O'\\\<C-m>-"
- exe "norm \<C-q>="
- bwipe!
-endfunc
-
-func Test_find_brace_backwards()
- " this was looking beyond the end of the line
- new
- norm R/*
- norm o0{
- norm o//
- norm V{=
- call assert_equal(['/*', ' 0{', '//'], getline(1, 3))
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cjk_linebreak.vim b/src/nvim/testdir/test_cjk_linebreak.vim
deleted file mode 100644
index dfaa8fa1af..0000000000
--- a/src/nvim/testdir/test_cjk_linebreak.vim
+++ /dev/null
@@ -1,97 +0,0 @@
-scriptencoding utf-8
-
-func Run_cjk_linebreak_after(rigorous)
- set textwidth=12
- for punct in [
- \ '!', '%', ')', ',', ':', ';', '>', '?', ']', '}', '’', 'â€', '†', '‡',
- \ '…', '‰', '‱', '‼', 'â‡', 'âˆ', 'â‰', '℃', '℉', 'ã€', '。', '〉', '》',
- \ 'ã€', 'ã€', '】', '〕', '〗', '〙', '〛', 'ï¼', ')', ',', '.', ':',
- \ 'ï¼›', '?', 'ï¼½', 'ï½']
- call setline('.', '这是一个测试' .. punct.'试试 CJK 行ç¦åˆ™è¡¥ä¸ã€‚')
- normal gqq
- if a:rigorous
- call assert_equal('这是一个测', getline(1))
- else
- call assert_equal('这是一个测试' .. punct, getline(1))
- endif
- %d_
- endfor
-endfunc
-
-func Test_cjk_linebreak_after()
- set formatoptions=croqn2mB1j
- call Run_cjk_linebreak_after(0)
-endfunc
-
-func Test_cjk_linebreak_after_rigorous()
- set formatoptions=croqn2mB1j]
- call Run_cjk_linebreak_after(1)
-endfunc
-
-func Run_cjk_linebreak_before()
- set textwidth=12
- for punct in [
- \ '(', '<', '[', '`', '{', '‘', '“', '〈', '《', '「', '『', 'ã€', '〔',
- \ '〖', '〘', '〚', '(', '[', '{']
- call setline('.', '这是个测试' .. punct.'试试 CJK 行ç¦åˆ™è¡¥ä¸ã€‚')
- normal gqq
- call assert_equal('这是个测试', getline(1))
- %d_
- endfor
-endfunc
-
-func Test_cjk_linebreak_before()
- set formatoptions=croqn2mB1j
- call Run_cjk_linebreak_before()
-endfunc
-
-func Test_cjk_linebreak_before_rigorous()
- set formatoptions=croqn2mB1j]
- call Run_cjk_linebreak_before()
-endfunc
-
-func Run_cjk_linebreak_nobetween(rigorous)
- " …… must not start a line
- call setline('.', '这是个测试……试试 CJK 行ç¦åˆ™è¡¥ä¸ã€‚')
- set textwidth=12 ambiwidth=double
- normal gqq
- if a:rigorous
- call assert_equal('这是个测', getline(1))
- else
- call assert_equal('这是个测试……', getline(1))
- endif
- %d_
-
- call setline('.', '这是一个测试……试试 CJK 行ç¦åˆ™è¡¥ä¸ã€‚')
- set textwidth=12 ambiwidth=double
- normal gqq
- call assert_equal('这是一个测', getline(1))
- %d_
-
- " but —— can
- call setline('.', '这是个测试——试试 CJK 行ç¦åˆ™è¡¥ä¸ã€‚')
- set textwidth=12 ambiwidth=double
- normal gqq
- call assert_equal('这是个测试', getline(1))
-endfunc
-
-func Test_cjk_linebreak_nobetween()
- set formatoptions=croqn2mB1j
- call Run_cjk_linebreak_nobetween(0)
-endfunc
-
-func Test_cjk_linebreak_nobetween_rigorous()
- set formatoptions=croqn2mB1j]
- call Run_cjk_linebreak_nobetween(1)
-endfunc
-
-func Test_cjk_linebreak_join_punct()
- for punct in ['——', '〗', ',', '。', '……']
- call setline(1, '文本文本' .. punct)
- call setline(2, 'English')
- set formatoptions=croqn2mB1j
- normal ggJ
- call assert_equal('文本文本' .. punct.'English', getline(1))
- %d_
- endfor
-endfunc
diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim
deleted file mode 100644
index d8b29f6c42..0000000000
--- a/src/nvim/testdir/test_clientserver.vim
+++ /dev/null
@@ -1,195 +0,0 @@
-" Tests for the +clientserver feature.
-
-source check.vim
-CheckFeature job
-
-if !has('clientserver')
- call assert_fails('call remote_startserver("local")', 'E942:')
-endif
-
-CheckFeature clientserver
-
-source shared.vim
-
-func Check_X11_Connection()
- if has('x11')
- CheckEnv DISPLAY
- try
- call remote_send('xxx', '')
- catch
- if v:exception =~ 'E240:'
- throw 'Skipped: no connection to the X server'
- endif
- " ignore other errors
- endtry
- endif
-endfunc
-
-func Test_client_server()
- let cmd = GetVimCommand()
- if cmd == ''
- return
- endif
- call Check_X11_Connection()
-
- let name = 'XVIMTEST'
- let cmd .= ' --servername ' . name
- let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
- 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 WaitForAssert({-> assert_match(name, serverlist())})
-
- if !has('win32')
- if RunVim([], [], '--serverlist >Xtest_serverlist')
- let lines = readfile('Xtest_serverlist')
- call assert_true(index(lines, 'XVIMTEST') >= 0)
- endif
- call delete('Xtest_serverlist')
- endif
-
- eval name->remote_foreground()
-
- call remote_send(name, ":let testvar = 'yes'\<CR>")
- call WaitFor('remote_expr("' . name . '", "exists(\"testvar\") ? testvar : \"\"", "", 1) == "yes"')
- call assert_equal('yes', remote_expr(name, "testvar", "", 2))
- call assert_fails("let x=remote_expr(name, '2+x')", 'E449:')
- call assert_fails("let x=remote_expr('[], '2+2')", 'E116:')
-
- if has('unix') && has('gui') && !has('gui_running')
- " Running in a terminal and the GUI is available: Tell the server to open
- " the GUI and check that the remote command still works.
- " Need to wait for the GUI to start up, otherwise the send hangs in trying
- " to send to the terminal window.
- if has('gui_motif')
- " For this GUI ignore the 'failed to create input context' error.
- call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>")
- else
- call remote_send(name, ":gui -f\<CR>")
- endif
- " Wait for the server to be up and answering requests.
- " When using valgrind this can be very, very slow.
- sleep 1
- call WaitForAssert({-> assert_match('\d', name->remote_expr("v:version", "", 1))}, 10000)
-
- call remote_send(name, ":let testvar = 'maybe'\<CR>")
- call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))})
- endif
-
- call assert_fails('call remote_send("XXX", ":let testvar = ''yes''\<CR>")', 'E241')
-
- call writefile(['one'], 'Xclientfile')
- let cmd = GetVimProg() .. ' --servername ' .. name .. ' --remote Xclientfile'
- call system(cmd)
- call WaitForAssert({-> assert_equal('Xclientfile', remote_expr(name, "bufname()", "", 2))})
- call WaitForAssert({-> assert_equal('one', remote_expr(name, "getline(1)", "", 2))})
- call writefile(['one', 'two'], 'Xclientfile')
- call system(cmd)
- call WaitForAssert({-> assert_equal('two', remote_expr(name, "getline(2)", "", 2))})
- call delete('Xclientfile')
-
- " Expression evaluated locally.
- if v:servername == ''
- eval 'MYSELF'->remote_startserver()
- " May get MYSELF1 when running the test again.
- call assert_match('MYSELF', v:servername)
- call assert_fails("call remote_startserver('MYSELF')", 'E941:')
- endif
- let g:testvar = 'myself'
- call assert_equal('myself', remote_expr(v:servername, 'testvar'))
- call remote_send(v:servername, ":let g:testvar2 = 75\<CR>")
- call feedkeys('', 'x')
- call assert_equal(75, g:testvar2)
- call assert_fails('let v = remote_expr(v:servername, "/2")', ['E15:.*/2'])
-
- call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid')
- call assert_equal('got it', g:myserverid->remote_read(2))
-
- call remote_send(name, ":eval expand('<client>')->server2client('another')\<CR>", 'g:myserverid')
- let peek_result = 'nothing'
- let r = g:myserverid->remote_peek('peek_result')
- " unpredictable whether the result is already available.
- if r > 0
- call assert_equal('another', peek_result)
- elseif r == 0
- call assert_equal('nothing', peek_result)
- else
- call assert_report('remote_peek() failed')
- endif
- let g:peek_result = 'empty'
- call WaitFor('remote_peek(g:myserverid, "g:peek_result") > 0')
- call assert_equal('another', g:peek_result)
- call assert_equal('another', remote_read(g:myserverid, 2))
-
- if !has('gui_running')
- " In GUI vim, the following tests display a dialog box
-
- let cmd = GetVimProg() .. ' --servername ' .. name
-
- " Run a separate instance to send a command to the server
- call remote_expr(name, 'execute("only")')
- call system(cmd .. ' --remote-send ":new Xfile<CR>"')
- call assert_equal('2', remote_expr(name, 'winnr("$")'))
- call assert_equal('Xfile', remote_expr(name, 'winbufnr(1)->bufname()'))
- call remote_expr(name, 'execute("only")')
-
- " Invoke a remote-expr. On MS-Windows, the returned value has a carriage
- " return.
- let l = system(cmd .. ' --remote-expr "2 + 2"')
- call assert_equal(['4'], split(l, "\n"))
-
- " Edit multiple files using --remote
- call system(cmd .. ' --remote Xfile1 Xfile2 Xfile3')
- call assert_match(".*Xfile1\n.*Xfile2\n.*Xfile3\n", remote_expr(name, 'argv()'))
- eval name->remote_send(":%bw!\<CR>")
-
- " Edit files in separate tab pages
- call system(cmd .. ' --remote-tab Xfile1 Xfile2 Xfile3')
- call WaitForAssert({-> assert_equal('3', remote_expr(name, 'tabpagenr("$")'))})
- call assert_match('.*\<Xfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])'))
- eval name->remote_send(":%bw!\<CR>")
-
- " Edit a file using --remote-wait
- eval name->remote_send(":source $VIMRUNTIME/plugin/rrhelper.vim\<CR>")
- call system(cmd .. ' --remote-wait +enew Xfile1')
- call assert_match('.*\<Xfile1', remote_expr(name, 'bufname("#")'))
- eval name->remote_send(":%bw!\<CR>")
-
- " Edit files using --remote-tab-wait
- call system(cmd .. ' --remote-tabwait +tabonly\|enew Xfile1 Xfile2')
- call assert_equal('1', remote_expr(name, 'tabpagenr("$")'))
- eval name->remote_send(":%bw!\<CR>")
-
- " Error cases
- if v:lang == "C" || v:lang =~ '^[Ee]n'
- let l = split(system(cmd .. ' --remote +pwd'), "\n")
- call assert_equal("Argument missing after: \"+pwd\"", l[1])
- endif
- let l = system(cmd .. ' --remote-expr "abcd"')
- call assert_match('^E449: ', l)
- endif
-
- eval name->remote_send(":%bw!\<CR>")
- eval name->remote_send(":qa!\<CR>")
- try
- call WaitForAssert({-> assert_equal("dead", job_status(job))})
- finally
- if job_status(job) != 'dead'
- call assert_report('Server did not exit')
- call job_stop(job, 'kill')
- endif
- endtry
-
- call assert_fails('call remote_startserver([])', 'E730:')
- call assert_fails("let x = remote_peek([])", 'E730:')
- call assert_fails("let x = remote_read('vim10')",
- \ has('unix') ? ['E573:.*vim10'] : 'E277:')
- call assert_fails("call server2client('abc', 'xyz')",
- \ has('unix') ? ['E573:.*abc'] : 'E258:')
-endfunc
-
-" Uncomment this line to get a debugging log
-" call ch_logfile('channellog', 'w')
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_close_count.vim b/src/nvim/testdir/test_close_count.vim
deleted file mode 100644
index 1f9adba32d..0000000000
--- a/src/nvim/testdir/test_close_count.vim
+++ /dev/null
@@ -1,174 +0,0 @@
-
-" Tests for :[count]close! command
-func Test_close_count()
- enew! | only
-
- let wids = [win_getid()]
- for i in range(5)
- new
- call add(wids, win_getid())
- endfor
-
- 4wincmd w
- close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids)
-
- 1close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids)
-
- $close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[3], wids[1]], ids)
-
- 1wincmd w
- 2close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[1]], ids)
-
- 1wincmd w
- new
- call add(wids, win_getid())
- new
- call add(wids, win_getid())
- 2wincmd w
- -1close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[6], wids[4], wids[1]], ids)
-
- 2wincmd w
- +1close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[6], wids[4]], ids)
-
- only!
-endfunc
-
-" Tests for :[count]hide command
-func Test_hide_count()
- enew! | only
-
- let wids = [win_getid()]
- for i in range(5)
- new
- call add(wids, win_getid())
- endfor
-
- 4wincmd w
- .hide
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids)
-
- 1hide
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids)
-
- $hide
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[3], wids[1]], ids)
-
- 1wincmd w
- 2hide
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[1]], ids)
-
- 1wincmd w
- new
- call add(wids, win_getid())
- new
- call add(wids, win_getid())
- 3wincmd w
- -hide
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[7], wids[4], wids[1]], ids)
-
- 2wincmd w
- +hide
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[7], wids[4]], ids)
-
- only!
-endfunc
-
-" Tests for :[count]close! command with 'hidden'
-func Test_hidden_close_count()
- enew! | only
-
- let wids = [win_getid()]
- for i in range(5)
- new
- call add(wids, win_getid())
- endfor
-
- set hidden
-
- $ hide
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[5], wids[4], wids[3], wids[2], wids[1]], ids)
-
- $-1 close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[5], wids[4], wids[3], wids[1]], ids)
-
- 1wincmd w
- .+close!
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[5], wids[3], wids[1]], ids)
-
- set nohidden
- only!
-endfunc
-
-" Tests for 'CTRL-W c' command to close windows.
-func Test_winclose_command()
- enew! | only
-
- let wids = [win_getid()]
- for i in range(5)
- new
- call add(wids, win_getid())
- endfor
-
- set hidden
-
- 4wincmd w
- exe "normal \<C-W>c"
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids)
-
- exe "normal 1\<C-W>c"
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids)
-
- exe "normal 9\<C-W>c"
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[3], wids[1]], ids)
-
- 1wincmd w
- exe "normal 2\<C-W>c"
- let ids = []
- windo call add(ids, win_getid())
- call assert_equal([wids[4], wids[1]], ids)
-
- set nohidden
- only!
-endfunc
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
deleted file mode 100644
index cf1d56ae38..0000000000
--- a/src/nvim/testdir/test_cmdline.vim
+++ /dev/null
@@ -1,3581 +0,0 @@
-" Tests for editing the command line.
-
-source check.vim
-source screendump.vim
-source view_util.vim
-source shared.vim
-
-func SetUp()
- func SaveLastScreenLine()
- let g:Sline = Screenline(&lines - 1)
- return ''
- endfunc
- cnoremap <expr> <F4> SaveLastScreenLine()
-endfunc
-
-func TearDown()
- delfunc SaveLastScreenLine
- cunmap <F4>
-endfunc
-
-func Test_complete_tab()
- call writefile(['testfile'], 'Xtestfile')
- call feedkeys(":e Xtest\t\r", "tx")
- call assert_equal('testfile', getline(1))
-
- " Pressing <Tab> after '%' completes the current file, also on MS-Windows
- call feedkeys(":e %\t\r", "tx")
- call assert_equal('e Xtestfile', @:)
- call delete('Xtestfile')
-endfunc
-
-func Test_complete_list()
- " We can't see the output, but at least we check the code runs properly.
- call feedkeys(":e test\<C-D>\r", "tx")
- call assert_equal('test', expand('%:t'))
-
- " If a command doesn't support completion, then CTRL-D should be literally
- " used.
- call feedkeys(":chistory \<C-D>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"chistory \<C-D>", @:)
-
- " Test for displaying the tail of the completion matches
- set wildmode=longest,full
- call mkdir('Xtest')
- call writefile([], 'Xtest/a.c')
- call writefile([], 'Xtest/a.h')
- let g:Sline = ''
- call feedkeys(":e Xtest/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal('a.c a.h', g:Sline)
- call assert_equal('"e Xtest/', @:)
- if has('win32')
- " Test for 'completeslash'
- set completeslash=backslash
- call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xtest\', @:)
- call feedkeys(":e Xtest/\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xtest\a.', @:)
- set completeslash=slash
- call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xtest/', @:)
- call feedkeys(":e Xtest\\\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xtest/a.', @:)
- set completeslash&
- endif
-
- " Test for displaying the tail with wildcards
- let g:Sline = ''
- call feedkeys(":e Xtes?/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal('Xtest/a.c Xtest/a.h', g:Sline)
- call assert_equal('"e Xtes?/', @:)
- let g:Sline = ''
- call feedkeys(":e Xtes*/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal('Xtest/a.c Xtest/a.h', g:Sline)
- call assert_equal('"e Xtes*/', @:)
- let g:Sline = ''
- call feedkeys(":e Xtes[/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal(':e Xtes[/', g:Sline)
- call assert_equal('"e Xtes[/', @:)
-
- call delete('Xtest', 'rf')
- set wildmode&
-endfunc
-
-func Test_complete_wildmenu()
- call mkdir('Xdir1/Xdir2', 'p')
- call writefile(['testfile1'], 'Xdir1/Xtestfile1')
- call writefile(['testfile2'], 'Xdir1/Xtestfile2')
- call writefile(['testfile3'], 'Xdir1/Xdir2/Xtestfile3')
- call writefile(['testfile3'], 'Xdir1/Xdir2/Xtestfile4')
- set wildmenu
-
- " Pressing <Tab> completes, and moves to next files when pressing again.
- call feedkeys(":e Xdir1/\<Tab>\<Tab>\<CR>", 'tx')
- call assert_equal('testfile1', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Tab>\<Tab>\<CR>", 'tx')
- call assert_equal('testfile2', getline(1))
-
- " <S-Tab> is like <Tab> but begin with the last match and then go to
- " previous.
- call feedkeys(":e Xdir1/Xtest\<S-Tab>\<CR>", 'tx')
- call assert_equal('testfile2', getline(1))
- call feedkeys(":e Xdir1/Xtest\<S-Tab>\<S-Tab>\<CR>", 'tx')
- call assert_equal('testfile1', getline(1))
-
- " <Left>/<Right> to move to previous/next file.
- call feedkeys(":e Xdir1/\<Tab>\<Right>\<CR>", 'tx')
- call assert_equal('testfile1', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Right>\<Right>\<CR>", 'tx')
- call assert_equal('testfile2', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Right>\<Right>\<Left>\<CR>", 'tx')
- call assert_equal('testfile1', getline(1))
-
- " <Up>/<Down> to go up/down directories.
- call feedkeys(":e Xdir1/\<Tab>\<Down>\<CR>", 'tx')
- call assert_equal('testfile3', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Down>\<Up>\<Right>\<CR>", 'tx')
- call assert_equal('testfile1', getline(1))
-
- " this fails in some Unix GUIs, not sure why
- if !has('unix') || !has('gui_running')
- " <C-J>/<C-K> mappings to go up/down directories when 'wildcharm' is
- " different than 'wildchar'.
- set wildcharm=<C-Z>
- cnoremap <C-J> <Down><C-Z>
- cnoremap <C-K> <Up><C-Z>
- call feedkeys(":e Xdir1/\<Tab>\<C-J>\<CR>", 'tx')
- call assert_equal('testfile3', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<C-J>\<C-K>\<CR>", 'tx')
- call assert_equal('testfile1', getline(1))
- set wildcharm=0
- cunmap <C-J>
- cunmap <C-K>
- endif
-
- " Test for canceling the wild menu by adding a character
- redrawstatus
- call feedkeys(":e Xdir1/\<Tab>x\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xdir1/Xdir2/x', @:)
-
- " Completion using a relative path
- cd Xdir1/Xdir2
- call feedkeys(":e ../\<Tab>\<Right>\<Down>\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"e Xtestfile3 Xtestfile4', @:)
- cd -
-
- " test for wildmenumode()
- cnoremap <expr> <F2> wildmenumode()
- call feedkeys(":cd Xdir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"cd Xdir1/0', @:)
- call feedkeys(":e Xdir1/\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"e Xdir1/Xdir2/1', @:)
- cunmap <F2>
-
- " Test for canceling the wild menu by pressing <PageDown> or <PageUp>.
- " After this pressing <Left> or <Right> should not change the selection.
- call feedkeys(":sign \<Tab>\<PageDown>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sign define', @:)
- call histadd('cmd', 'TestWildMenu')
- call feedkeys(":sign \<Tab>\<PageUp>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"TestWildMenu', @:)
-
- " cleanup
- %bwipe
- call delete('Xdir1', 'rf')
- set nowildmenu
-endfunc
-
-func Test_wildmenu_screendump()
- CheckScreendump
-
- let lines =<< trim [SCRIPT]
- set wildmenu hlsearch
- [SCRIPT]
- call writefile(lines, 'XTest_wildmenu')
-
- let buf = RunVimInTerminal('-S XTest_wildmenu', {'rows': 8})
- call term_sendkeys(buf, ":vim\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_1', {})
-
- call term_sendkeys(buf, "\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_2', {})
-
- call term_sendkeys(buf, "\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_3', {})
-
- call term_sendkeys(buf, "\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_4', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_wildmenu')
-endfunc
-
-func Test_redraw_in_autocmd()
- CheckScreendump
-
- let lines =<< trim END
- set cmdheight=2
- autocmd CmdlineChanged * redraw
- END
- call writefile(lines, 'XTest_redraw', 'D')
-
- let buf = RunVimInTerminal('-S XTest_redraw', {'rows': 8})
- call term_sendkeys(buf, ":for i in range(3)\<CR>")
- call VerifyScreenDump(buf, 'Test_redraw_in_autocmd_1', {})
-
- call term_sendkeys(buf, "let i =")
- call VerifyScreenDump(buf, 'Test_redraw_in_autocmd_2', {})
-
- " clean up
- call term_sendkeys(buf, "\<CR>")
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_redrawstatus_in_autocmd()
- CheckScreendump
-
- let lines =<< trim END
- set laststatus=2
- set statusline=%=:%{getcmdline()}
- autocmd CmdlineChanged * redrawstatus
- END
- call writefile(lines, 'XTest_redrawstatus', 'D')
-
- let buf = RunVimInTerminal('-S XTest_redrawstatus', {'rows': 8})
- " :redrawstatus is postponed if messages have scrolled
- call term_sendkeys(buf, ":echo \"one\\ntwo\\nthree\\nfour\"\<CR>")
- call term_sendkeys(buf, ":foobar")
- call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_1', {})
- " it is not postponed if messages have not scrolled
- call term_sendkeys(buf, "\<Esc>:for in in range(3)")
- call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_2', {})
- " with cmdheight=1 messages have scrolled when typing :endfor
- call term_sendkeys(buf, "\<CR>:endfor")
- call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_3', {})
- call term_sendkeys(buf, "\<CR>:set cmdheight=2\<CR>")
- " with cmdheight=2 messages haven't scrolled when typing :for or :endfor
- call term_sendkeys(buf, ":for in in range(3)")
- call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_4', {})
- call term_sendkeys(buf, "\<CR>:endfor")
- call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_5', {})
-
- " clean up
- call term_sendkeys(buf, "\<CR>")
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_changing_cmdheight()
- CheckScreendump
-
- let lines =<< trim END
- set cmdheight=1 laststatus=2
- func EchoTwo()
- set laststatus=2
- set cmdheight=5
- echo 'foo'
- echo 'bar'
- set cmdheight=1
- endfunc
- END
- call writefile(lines, 'XTest_cmdheight', 'D')
-
- let buf = RunVimInTerminal('-S XTest_cmdheight', {'rows': 8})
- call term_sendkeys(buf, ":resize -3\<CR>")
- call VerifyScreenDump(buf, 'Test_changing_cmdheight_1', {})
-
- " using the space available doesn't change the status line
- call term_sendkeys(buf, ":set cmdheight+=3\<CR>")
- call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {})
-
- " using more space moves the status line up
- call term_sendkeys(buf, ":set cmdheight+=1\<CR>")
- call VerifyScreenDump(buf, 'Test_changing_cmdheight_3', {})
-
- " reducing cmdheight moves status line down
- call term_sendkeys(buf, ":set cmdheight-=2\<CR>")
- call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {})
-
- " reducing window size and then setting cmdheight
- call term_sendkeys(buf, ":resize -1\<CR>")
- call term_sendkeys(buf, ":set cmdheight=1\<CR>")
- call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {})
-
- " setting 'cmdheight' works after outputting two messages
- call term_sendkeys(buf, ":call EchoTwo()\<CR>")
- call VerifyScreenDump(buf, 'Test_changing_cmdheight_6', {})
-
- " clean up
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_cmdheight_tabline()
- CheckScreendump
-
- let buf = RunVimInTerminal('-c "set ls=2" -c "set stal=2" -c "set cmdheight=1"', {'rows': 6})
- call VerifyScreenDump(buf, 'Test_cmdheight_tabline_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_map_completion()
- call feedkeys(":map <unique> <si\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <unique> <silent>', getreg(':'))
- call feedkeys(":map <script> <un\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <script> <unique>', getreg(':'))
- call feedkeys(":map <expr> <sc\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <expr> <script>', getreg(':'))
- call feedkeys(":map <buffer> <e\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <buffer> <expr>', getreg(':'))
- call feedkeys(":map <nowait> <b\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <nowait> <buffer>', getreg(':'))
- call feedkeys(":map <special> <no\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <special> <nowait>', getreg(':'))
- 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
- map <Left> left
- call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <Left>', getreg(':'))
- unmap <Left>
- set cpo-=B
-
- " set cpo+=k
- map <Left> left
- call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"map <Left>', getreg(':'))
- unmap <Left>
- " set cpo-=k
-
- call assert_fails('call feedkeys(":map \\\\%(\<Tab>\<Home>\"\<CR>", "xt")', 'E53:')
-
- unmap <Middle>x
- set cpo&vim
-endfunc
-
-func Test_match_completion()
- hi Aardig ctermfg=green
- " call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt')
- call feedkeys(":match A\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"match Aardig', @:)
- call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"match none', @:)
- call feedkeys(":match | chist\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"match | chistory', @:)
-endfunc
-
-func Test_highlight_completion()
- hi Aardig ctermfg=green
- " call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt')
- call feedkeys(":hi A\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"hi Aardig', getreg(':'))
- " call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt')
- call feedkeys(":hi default A\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"hi default Aardig', getreg(':'))
- call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"hi clear Aardig', getreg(':'))
- call feedkeys(":hi li\<S-Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"hi link', getreg(':'))
- call feedkeys(":hi d\<S-Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"hi default', getreg(':'))
- call feedkeys(":hi c\<S-Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"hi clear', getreg(':'))
-
- " A cleared group does not show up in completions.
- hi Anders ctermfg=green
- call assert_equal(['Aardig', 'Anders'], getcompletion('A', 'highlight'))
- hi clear Aardig
- call assert_equal(['Anders'], getcompletion('A', 'highlight'))
- hi clear Anders
- call assert_equal([], getcompletion('A', 'highlight'))
-endfunc
-
-func Test_getcompletion()
- let groupcount = len(getcompletion('', 'event'))
- call assert_true(groupcount > 0)
- let matchcount = len('File'->getcompletion('event'))
- call assert_true(matchcount > 0)
- call assert_true(groupcount > matchcount)
-
- if has('menu')
- source $VIMRUNTIME/menu.vim
- let matchcount = len(getcompletion('', 'menu'))
- call assert_true(matchcount > 0)
- call assert_equal(['File.'], getcompletion('File', 'menu'))
- call assert_true(matchcount > 0)
- let matchcount = len(getcompletion('File.', 'menu'))
- call assert_true(matchcount > 0)
- source $VIMRUNTIME/delmenu.vim
- endif
-
- let l = getcompletion('v:n', 'var')
- call assert_true(index(l, 'v:null') >= 0)
- let l = getcompletion('v:notexists', 'var')
- call assert_equal([], l)
-
- args a.c b.c
- let l = getcompletion('', 'arglist')
- call assert_equal(['a.c', 'b.c'], l)
- let l = getcompletion('a.', 'buffer')
- call assert_equal(['a.c'], l)
- %argdelete
-
- let l = getcompletion('', 'augroup')
- call assert_true(index(l, 'END') >= 0)
- let l = getcompletion('blahblah', 'augroup')
- call assert_equal([], l)
-
- let l = getcompletion('', 'behave')
- call assert_true(index(l, 'mswin') >= 0)
- let l = getcompletion('not', 'behave')
- call assert_equal([], l)
-
- let l = getcompletion('', 'color')
- call assert_true(index(l, 'default') >= 0)
- let l = getcompletion('dirty', 'color')
- call assert_equal([], l)
-
- let l = getcompletion('', 'command')
- call assert_true(index(l, 'sleep') >= 0)
- let l = getcompletion('awake', 'command')
- call assert_equal([], l)
-
- let l = getcompletion('', 'dir')
- call assert_true(index(l, 'sautest/') >= 0)
- let l = getcompletion('NoMatch', 'dir')
- call assert_equal([], l)
-
- if glob('~/*') !=# ''
- let l = getcompletion('~/', 'dir')
- call assert_true(l[0][0] ==# '~')
- endif
-
- let l = getcompletion('exe', 'expression')
- call assert_true(index(l, 'executable(') >= 0)
- let l = getcompletion('kill', 'expression')
- call assert_equal([], l)
-
- let l = getcompletion('tag', 'function')
- call assert_true(index(l, 'taglist(') >= 0)
- let l = getcompletion('paint', 'function')
- call assert_equal([], l)
-
- let Flambda = {-> 'hello'}
- let l = getcompletion('', 'function')
- let l = filter(l, {i, v -> v =~ 'lambda'})
- call assert_equal(0, len(l))
-
- let l = getcompletion('run', 'file')
- call assert_true(index(l, 'runtest.vim') >= 0)
- let l = getcompletion('walk', 'file')
- call assert_equal([], l)
- set wildignore=*.vim
- let l = getcompletion('run', 'file', 1)
- call assert_true(index(l, 'runtest.vim') < 0)
- set wildignore&
- " Directory name with space character
- call mkdir('Xdir with space')
- call assert_equal(['Xdir with space/'], getcompletion('Xdir\ w', 'shellcmd'))
- call assert_equal(['./Xdir with space/'], getcompletion('./Xdir', 'shellcmd'))
- call delete('Xdir with space', 'd')
-
- let l = getcompletion('ha', 'filetype')
- call assert_true(index(l, 'hamster') >= 0)
- let l = getcompletion('horse', 'filetype')
- call assert_equal([], l)
-
- let l = getcompletion('z', 'syntax')
- call assert_true(index(l, 'zimbu') >= 0)
- let l = getcompletion('emacs', 'syntax')
- call assert_equal([], l)
-
- let l = getcompletion('jikes', 'compiler')
- call assert_true(index(l, 'jikes') >= 0)
- let l = getcompletion('break', 'compiler')
- call assert_equal([], l)
-
- let l = getcompletion('last', 'help')
- call assert_true(index(l, ':tablast') >= 0)
- let l = getcompletion('giveup', 'help')
- call assert_equal([], l)
-
- let l = getcompletion('time', 'option')
- call assert_true(index(l, 'timeoutlen') >= 0)
- let l = getcompletion('space', 'option')
- call assert_equal([], l)
-
- let l = getcompletion('er', 'highlight')
- call assert_true(index(l, 'ErrorMsg') >= 0)
- let l = getcompletion('dark', 'highlight')
- call assert_equal([], l)
-
- let l = getcompletion('', 'messages')
- call assert_true(index(l, 'clear') >= 0)
- let l = getcompletion('not', 'messages')
- call assert_equal([], l)
-
- let l = getcompletion('', 'mapclear')
- call assert_true(index(l, '<buffer>') >= 0)
- let l = getcompletion('not', 'mapclear')
- call assert_equal([], l)
-
- let l = getcompletion('.', 'shellcmd')
- call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"'))
- call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
- let root = has('win32') ? 'C:\\' : '/'
- let l = getcompletion(root, 'shellcmd')
- let expected = map(filter(glob(root . '*', 0, 1),
- \ 'isdirectory(v:val) || executable(v:val)'), 'isdirectory(v:val) ? v:val . ''/'' : v:val')
- call assert_equal(expected, l)
-
- if has('cscope')
- let l = getcompletion('', 'cscope')
- let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show']
- call assert_equal(cmds, l)
- " using cmdline completion must not change the result
- call feedkeys(":cscope find \<c-d>\<c-c>", 'xt')
- let l = getcompletion('', 'cscope')
- call assert_equal(cmds, l)
- let keys = ['a', 'c', 'd', 'e', 'f', 'g', 'i', 's', 't']
- let l = getcompletion('find ', 'cscope')
- call assert_equal(keys, l)
- endif
-
- if has('signs')
- sign define Testing linehl=Comment
- let l = getcompletion('', 'sign')
- let cmds = ['define', 'jump', 'list', 'place', 'undefine', 'unplace']
- call assert_equal(cmds, l)
- " using cmdline completion must not change the result
- call feedkeys(":sign list \<c-d>\<c-c>", 'xt')
- let l = getcompletion('', 'sign')
- call assert_equal(cmds, l)
- let l = getcompletion('list ', 'sign')
- call assert_equal(['Testing'], l)
- let l = getcompletion('de*', 'sign')
- call assert_equal(['define'], l)
- let l = getcompletion('p?', 'sign')
- call assert_equal(['place'], l)
- let l = getcompletion('j.', 'sign')
- call assert_equal(['jump'], l)
- endif
-
- " Command line completion tests
- let l = getcompletion('cd ', 'cmdline')
- call assert_true(index(l, 'samples/') >= 0)
- let l = getcompletion('cd NoMatch', 'cmdline')
- call assert_equal([], l)
- let l = getcompletion('let v:n', 'cmdline')
- call assert_true(index(l, 'v:null') >= 0)
- let l = getcompletion('let v:notexists', 'cmdline')
- call assert_equal([], l)
- let l = getcompletion('call tag', 'cmdline')
- call assert_true(index(l, 'taglist(') >= 0)
- let l = getcompletion('call paint', 'cmdline')
- call assert_equal([], l)
-
- func T(a, c, p)
- let g:cmdline_compl_params = [a:a, a:c, a:p]
- return "oneA\noneB\noneC"
- endfunc
- command -nargs=1 -complete=custom,T MyCmd
- let l = getcompletion('MyCmd ', 'cmdline')
- call assert_equal(['oneA', 'oneB', 'oneC'], l)
- call assert_equal(['', 'MyCmd ', 6], g:cmdline_compl_params)
-
- delcommand MyCmd
- delfunc T
- unlet g:cmdline_compl_params
-
- " For others test if the name is recognized.
- let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user']
- if has('cmdline_hist')
- call add(names, 'history')
- endif
- if has('gettext')
- call add(names, 'locale')
- endif
- if has('profile')
- call add(names, 'syntime')
- endif
-
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", "word\tfile\tcmd"], 'Xtags')
-
- for name in names
- let matchcount = len(getcompletion('', name))
- call assert_true(matchcount >= 0, 'No matches for ' . name)
- endfor
-
- call delete('Xtags')
- set tags&
-
- edit a~b
- enew
- call assert_equal(['a~b'], getcompletion('a~', 'buffer'))
- bw a~b
-
- if has('unix')
- edit Xtest\
- enew
- call assert_equal(['Xtest\'], getcompletion('Xtest\', 'buffer'))
- bw Xtest\
- endif
-
- call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:')
- call assert_fails('call getcompletion("", "burp")', 'E475:')
- call assert_fails('call getcompletion("abc", [])', 'E475:')
-endfunc
-
-" Test for getcompletion() with "fuzzy" in 'wildoptions'
-func Test_getcompletion_wildoptions()
- let save_wildoptions = &wildoptions
- set wildoptions&
- let l = getcompletion('space', 'option')
- call assert_equal([], l)
- let l = getcompletion('ier', 'command')
- call assert_equal([], l)
- set wildoptions=fuzzy
- let l = getcompletion('space', 'option')
- call assert_true(index(l, 'backspace') >= 0)
- let l = getcompletion('ier', 'command')
- call assert_true(index(l, 'compiler') >= 0)
- let &wildoptions = save_wildoptions
-endfunc
-
-func Test_fullcommand()
- let tests = {
- \ '': '',
- \ ':': '',
- \ ':::': '',
- \ ':::5': '',
- \ 'not_a_cmd': '',
- \ 'Check': '',
- \ 'syntax': 'syntax',
- \ ':syntax': 'syntax',
- \ '::::syntax': 'syntax',
- \ 'sy': 'syntax',
- \ 'syn': 'syntax',
- \ 'synt': 'syntax',
- \ ':sy': 'syntax',
- \ '::::sy': 'syntax',
- \ 'match': 'match',
- \ '2match': 'match',
- \ '3match': 'match',
- \ 'aboveleft': 'aboveleft',
- \ 'abo': 'aboveleft',
- \ 's': 'substitute',
- \ '5s': 'substitute',
- \ ':5s': 'substitute',
- \ "'<,'>s": 'substitute',
- \ ":'<,'>s": 'substitute',
- \ 'CheckUni': 'CheckUnix',
- \ 'CheckUnix': 'CheckUnix',
- \ }
-
- for [in, want] in items(tests)
- call assert_equal(want, fullcommand(in))
- endfor
- call assert_equal('', fullcommand(v:_null_string))
-
- call assert_equal('syntax', 'syn'->fullcommand())
-
- command -buffer BufferLocalCommand :
- command GlobalCommand :
- call assert_equal('GlobalCommand', fullcommand('GlobalCom'))
- call assert_equal('BufferLocalCommand', fullcommand('BufferL'))
- delcommand BufferLocalCommand
- delcommand GlobalCommand
-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')
- call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt')
- call assert_equal('find a/b/fileXname', @:)
- bwipe!
- call delete('a', 'rf')
-endfunc
-
-func Test_cmdline_paste()
- let @a = "def"
- call feedkeys(":abc \<C-R>a ghi\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abc def ghi', @:)
-
- new
- call setline(1, 'asdf.x /tmp/some verylongword a;b-c*d ')
-
- call feedkeys(":aaa \<C-R>\<C-W> bbb\<C-B>\"\<CR>", 'tx')
- call assert_equal('"aaa asdf bbb', @:)
-
- call feedkeys("ft:aaa \<C-R>\<C-F> bbb\<C-B>\"\<CR>", 'tx')
- call assert_equal('"aaa /tmp/some bbb', @:)
-
- call feedkeys(":aaa \<C-R>\<C-L> bbb\<C-B>\"\<CR>", 'tx')
- call assert_equal('"aaa '.getline(1).' bbb', @:)
-
- set incsearch
- call feedkeys("fy:aaa veryl\<C-R>\<C-W> bbb\<C-B>\"\<CR>", 'tx')
- call assert_equal('"aaa verylongword bbb', @:)
-
- call feedkeys("f;:aaa \<C-R>\<C-A> bbb\<C-B>\"\<CR>", 'tx')
- call assert_equal('"aaa a;b-c*d bbb', @:)
-
- call feedkeys(":\<C-\>etoupper(getline(1))\<CR>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"ASDF.X /TMP/SOME VERYLONGWORD A;B-C*D ', @:)
- bwipe!
-
- " Error while typing a command used to cause that it was not executed
- " in the end.
- new
- try
- call feedkeys(":file \<C-R>%Xtestfile\<CR>", 'tx')
- catch /^Vim\%((\a\+)\)\=:E32/
- " ignore error E32
- endtry
- call assert_equal("Xtestfile", bufname("%"))
-
- " Try to paste an invalid register using <C-R>
- call feedkeys(":\"one\<C-R>\<C-X>two\<CR>", 'xt')
- call assert_equal('"onetwo', @:)
-
- " Test for pasting register containing CTRL-H using CTRL-R and CTRL-R CTRL-R
- let @a = "xy\<C-H>z"
- call feedkeys(":\"\<C-R>a\<CR>", 'xt')
- call assert_equal('"xz', @:)
- call feedkeys(":\"\<C-R>\<C-R>a\<CR>", 'xt')
- call assert_equal("\"xy\<C-H>z", @:)
- call feedkeys(":\"\<C-R>\<C-O>a\<CR>", 'xt')
- call assert_equal("\"xy\<C-H>z", @:)
-
- " Test for pasting register containing CTRL-V using CTRL-R and CTRL-R CTRL-R
- let @a = "xy\<C-V>z"
- call feedkeys(":\"\<C-R>=@a\<CR>\<cr>", 'xt')
- call assert_equal('"xyz', @:)
- call feedkeys(":\"\<C-R>\<C-R>=@a\<CR>\<cr>", 'xt')
- call assert_equal("\"xy\<C-V>z", @:)
-
- call assert_beeps('call feedkeys(":\<C-R>=\<C-R>=\<Esc>", "xt")')
-
- bwipe!
-endfunc
-
-func Test_cmdline_remove_char()
- let encoding_save = &encoding
-
- " for e in ['utf8', 'latin1']
- for e in ['utf8']
- exe 'set encoding=' . e
-
- call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abc ef', @:, e)
-
- call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abcdef', @:)
-
- call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abc ghi', @:, e)
-
- call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"def', @:, e)
-
- " This was going before the start in latin1.
- call feedkeys(": \<C-W>\<CR>", 'tx')
- endfor
-
- let &encoding = encoding_save
-endfunc
-
-func Test_cmdline_keymap_ctrl_hat()
- if !has('keymap')
- return
- endif
-
- set keymap=esperanto
- call feedkeys(":\"Jxauxdo \<C-^>Jxauxdo \<C-^>Jxauxdo\<CR>", 'tx')
- call assert_equal('"Jxauxdo Ä´aÅ­do Jxauxdo', @:)
- set keymap=
-endfunc
-
-func Test_illegal_address1()
- new
- 2;'(
- 2;')
- quit
-endfunc
-
-func Test_illegal_address2()
- call writefile(['c', 'x', ' x', '.', '1;y'], 'Xtest.vim')
- new
- source Xtest.vim
- " Trigger calling validate_cursor()
- diffsp Xtest.vim
- quit!
- bwipe!
- call delete('Xtest.vim')
-endfunc
-
-func Test_mark_from_line_zero()
- " this was reading past the end of the first (empty) line
- new
- norm oxxxx
- call assert_fails("0;'(", 'E20:')
- bwipe!
-endfunc
-
-func Test_cmdline_complete_wildoptions()
- help
- call feedkeys(":tag /\<c-a>\<c-b>\"\<cr>", 'tx')
- let a = join(sort(split(@:)),' ')
- set wildoptions=tagfile
- call feedkeys(":tag /\<c-a>\<c-b>\"\<cr>", 'tx')
- let b = join(sort(split(@:)),' ')
- call assert_equal(a, b)
- bw!
-endfunc
-
-func Test_cmdline_complete_user_cmd()
- command! -complete=color -nargs=1 Foo :
- call feedkeys(":Foo \<Tab>\<Home>\"\<cr>", 'tx')
- call assert_equal('"Foo blue', @:)
- call feedkeys(":Foo b\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_equal('"Foo blue', @:)
- call feedkeys(":Foo a b\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_equal('"Foo a blue', @:)
- call feedkeys(":Foo b\\\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_equal('"Foo b\', @:)
- call feedkeys(":Foo b\\x\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_equal('"Foo b\x', @:)
- delcommand Foo
-
- redraw
- call assert_equal('~', Screenline(&lines - 1))
- command! FooOne :
- command! FooTwo :
-
- set nowildmenu
- call feedkeys(":Foo\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_equal('"FooOne', @:)
- call assert_equal('~', Screenline(&lines - 1))
-
- call feedkeys(":Foo\<S-Tab>\<Home>\"\<cr>", 'tx')
- call assert_equal('"FooTwo', @:)
- call assert_equal('~', Screenline(&lines - 1))
-
- delcommand FooOne
- delcommand FooTwo
- set wildmenu&
-endfunc
-
-func Test_complete_user_cmd()
- command FooBar echo 'global'
- command -buffer FooBar echo 'local'
- call feedkeys(":Foo\<C-A>\<Home>\"\<CR>", 'tx')
- call assert_equal('"FooBar', @:)
-
- delcommand -buffer FooBar
- delcommand FooBar
-endfunc
-
-func s:ScriptLocalFunction()
- echo 'yes'
-endfunc
-
-func Test_cmdline_complete_user_func()
- call feedkeys(":func Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_match('"func Test_cmdline_complete_user_', @:)
- call feedkeys(":func s:ScriptL\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_match('"func <SNR>\d\+_ScriptLocalFunction', @:)
-
- " g: prefix also works
- call feedkeys(":echo g:Test_cmdline_complete_user_f\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_match('"echo g:Test_cmdline_complete_user_func', @:)
-
- " using g: prefix does not result in just "g:" matches from a lambda
- let Fx = { a -> a }
- call feedkeys(":echo g:\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_match('"echo g:[A-Z]', @:)
-
- " existence of script-local dict function does not break user function name
- " completion
- function s:a_dict_func() dict
- endfunction
- call feedkeys(":call Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx')
- call assert_match('"call Test_cmdline_complete_user_', @:)
- delfunction s:a_dict_func
-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
-
-func Test_cmdline_complete_bang()
- if executable('whoami')
- call feedkeys(":!whoam\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^".*\<whoami\>', @:)
- endif
-endfunc
-
-func Test_cmdline_complete_languages()
- let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '')
- call assert_equal(lang, v:lc_time)
-
- let lang = substitute(execute('language ctype'), '.*"\(.*\)"$', '\1', '')
- call assert_equal(lang, v:ctype)
-
- let lang = substitute(execute('language collate'), '.*"\(.*\)"$', '\1', '')
- call assert_equal(lang, v:collate)
-
- let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '')
- call assert_equal(lang, v:lang)
-
- call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx')
- call assert_match('^"language .*\<collate\>.*\<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 . '\>', @:)
-
- call feedkeys(":language collate \<c-a>\<c-b>\"\<cr>", 'tx')
- call assert_match('^"language .*\<' . lang . '\>', @:)
- endif
-endfunc
-
-func Test_cmdline_complete_env_variable()
- let $X_VIM_TEST_COMPLETE_ENV = 'foo'
- call feedkeys(":edit $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('"edit $X_VIM_TEST_COMPLETE_ENV', @:)
- unlet $X_VIM_TEST_COMPLETE_ENV
-endfunc
-
-func Test_cmdline_complete_expression()
- let g:SomeVar = 'blah'
- for cmd in ['exe', 'echo', 'echon', 'echomsg']
- call feedkeys(":" .. cmd .. " SomeV\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match('"' .. cmd .. ' SomeVar', @:)
- call feedkeys(":" .. cmd .. " foo SomeV\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match('"' .. cmd .. ' foo SomeVar', @:)
- endfor
- unlet g:SomeVar
-endfunc
-
-" Test for various command-line completion
-func Test_cmdline_complete_various()
- " completion for a command starting with a comment
- call feedkeys(": :|\"\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\" :|\"\<C-A>", @:)
-
- " completion for a range followed by a comment
- call feedkeys(":1,2\"\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"1,2\"\<C-A>", @:)
-
- " completion for :k command
- call feedkeys(":ka\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"ka\<C-A>", @:)
-
- " completion for short version of the :s command
- call feedkeys(":sI \<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"sI \<C-A>", @:)
-
- " completion for :write command
- call mkdir('Xdir')
- call writefile(['one'], 'Xdir/Xfile1')
- let save_cwd = getcwd()
- cd Xdir
- call feedkeys(":w >> \<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"w >> Xfile1", @:)
- call chdir(save_cwd)
- call delete('Xdir', 'rf')
-
- " completion for :w ! and :r ! commands
- call feedkeys(":w !invalid_xyz_cmd\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"w !invalid_xyz_cmd", @:)
- call feedkeys(":r !invalid_xyz_cmd\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"r !invalid_xyz_cmd", @:)
-
- " completion for :>> and :<< commands
- call feedkeys(":>>>\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\">>>\<C-A>", @:)
- call feedkeys(":<<<\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"<<<\<C-A>", @:)
-
- " completion for command with +cmd argument
- call feedkeys(":buffer +/pat Xabc\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"buffer +/pat Xabc", @:)
- call feedkeys(":buffer +/pat\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"buffer +/pat\<C-A>", @:)
-
- " completion for a command with a trailing comment
- call feedkeys(":ls \" comment\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"ls \" comment\<C-A>", @:)
-
- " completion for a command with a trailing command
- call feedkeys(":ls | ls\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"ls | ls", @:)
-
- " completion for a command with an CTRL-V escaped argument
- call feedkeys(":ls \<C-V>\<C-V>a\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"ls \<C-V>a\<C-A>", @:)
-
- " completion for a command that doesn't take additional arguments
- call feedkeys(":all abc\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"all abc\<C-A>", @:)
-
- " completion for :wincmd with :horizontal modifier
- call feedkeys(":horizontal wincm\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"horizontal wincmd", @:)
-
- " completion for a command with a command modifier
- call feedkeys(":topleft new\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"topleft new", @:)
-
- " completion for the :match command
- call feedkeys(":match Search /pat/\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"match Search /pat/\<C-A>", @:)
-
- " completion for the :doautocmd command
- call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:)
-
- " completion of autocmd group after comma
- call feedkeys(":doautocmd BufNew,BufEn\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"doautocmd BufNew,BufEnter", @:)
-
- " completion of file name in :doautocmd
- call writefile([], 'Xfile1')
- call writefile([], 'Xfile2')
- call feedkeys(":doautocmd BufEnter Xfi\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"doautocmd BufEnter Xfile1 Xfile2", @:)
- call delete('Xfile1')
- call delete('Xfile2')
-
- " completion for the :augroup command
- augroup XTest.test
- augroup END
- call feedkeys(":augroup X\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"augroup XTest.test", @:)
- call feedkeys(":au X\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"au XTest.test", @:)
- augroup! XTest.test
-
- " completion for the :unlet command
- call feedkeys(":unlet one two\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"unlet one two", @:)
-
- " completion for the :buffer command with curlies
- " FIXME: what should happen on MS-Windows?
- if !has('win32')
- edit \{someFile}
- call feedkeys(":buf someFile\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"buf {someFile}", @:)
- bwipe {someFile}
- endif
-
- " completion for the :bdelete command
- call feedkeys(":bdel a b c\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"bdel a b c", @:)
-
- " completion for the :mapclear command
- call feedkeys(":mapclear \<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"mapclear <buffer>", @:)
-
- " completion for user defined commands with menu names
- menu Test.foo :ls<CR>
- com -nargs=* -complete=menu MyCmd
- call feedkeys(":MyCmd Te\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"MyCmd Test.', @:)
- delcom MyCmd
- unmenu Test
-
- " completion for user defined commands with mappings
- mapclear
- map <F3> :ls<CR>
- com -nargs=* -complete=mapping MyCmd
- call feedkeys(":MyCmd <F\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"MyCmd <F3> <F4>', @:)
- mapclear
- delcom MyCmd
-
- " completion for :set path= with multiple backslashes
- call feedkeys(":set path=a\\\\\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set path=a\\\ b', @:)
-
- " completion for :set dir= with a backslash
- call feedkeys(":set dir=a\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set dir=a\ b', @:)
-
- " completion for the :py3 commands
- call feedkeys(":py3\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"py3 py3do py3file', @:)
-
- " redir @" is not the start of a comment. So complete after that
- call feedkeys(":redir @\" | cwin\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"redir @" | cwindow', @:)
-
- " completion after a backtick
- call feedkeys(":e `a1b2c\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e `a1b2c', @:)
-
- " completion for :language command with an invalid argument
- call feedkeys(":language dummy \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"language dummy \t", @:)
-
- " completion for commands after a :global command
- call feedkeys(":g/a\\xb/clearj\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"g/a\xb/clearjumps', @:)
-
- " completion with ambiguous user defined commands
- com TCmd1 echo 'TCmd1'
- com TCmd2 echo 'TCmd2'
- call feedkeys(":TCmd \t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"TCmd ', @:)
- delcom TCmd1
- delcom TCmd2
-
- " completion after a range followed by a pipe (|) character
- call feedkeys(":1,10 | chist\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"1,10 | chistory', @:)
-
- " completion after a :global command
- call feedkeys(":g/a/chist\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"g/a/chistory', @:)
- call feedkeys(":g/a\\/chist\t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"g/a\\/chist\t", @:)
-
- " use <Esc> as the 'wildchar' for completion
- set wildchar=<Esc>
- call feedkeys(":g/a\\xb/clearj\<Esc>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"g/a\xb/clearjumps', @:)
- " pressing <esc> twice should cancel the command
- call feedkeys(":chist\<Esc>\<Esc>", 'xt')
- call assert_equal('"g/a\xb/clearjumps', @:)
- set wildchar&
-
- if has('unix')
- " should be able to complete a file name that starts with a '~'.
- call writefile([], '~Xtest')
- call feedkeys(":e \\~X\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e \~Xtest', @:)
- call delete('~Xtest')
-
- " should be able to complete a file name that has a '*'
- call writefile([], 'Xx*Yy')
- call feedkeys(":e Xx\*\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xx\*Yy', @:)
- call delete('Xx*Yy')
-
- " use a literal star
- call feedkeys(":e \\*\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e \*', @:)
- endif
-
- call feedkeys(":py3f\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"py3file', @:)
-endfunc
-
-" Test for 'wildignorecase'
-func Test_cmdline_wildignorecase()
- CheckUnix
- call writefile([], 'XTEST')
- set wildignorecase
- call feedkeys(":e xt\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e XTEST', @:)
- call assert_equal(['XTEST'], getcompletion('xt', 'file'))
- let g:Sline = ''
- call feedkeys(":e xt\<C-d>\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e xt', @:)
- call assert_equal('XTEST', g:Sline)
- set wildignorecase&
- call delete('XTEST')
-endfunc
-
-func Test_cmdline_write_alternatefile()
- new
- call setline('.', ['one', 'two'])
- f foo.txt
- new
- f #-A
- call assert_equal('foo.txt-A', expand('%'))
- f #<-B.txt
- call assert_equal('foo-B.txt', expand('%'))
- f %<
- call assert_equal('foo-B', expand('%'))
- new
- call assert_fails('f #<', 'E95')
- bw!
- f foo-B.txt
- f %<-A
- call assert_equal('foo-B-A', expand('%'))
- bw!
- bw!
-endfunc
-
-func Test_cmdline_expand_cur_alt_file()
- enew
- file http://some.com/file.txt
- call feedkeys(":e %\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e http://some.com/file.txt', @:)
- edit another
- call feedkeys(":e #\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e http://some.com/file.txt', @:)
- bwipe
- bwipe http://some.com/file.txt
-endfunc
-
-" using a leading backslash here
-set cpo+=C
-
-func Test_cmdline_search_range()
- new
- call setline(1, ['a', 'b', 'c', 'd'])
- /d
- 1,\/s/b/B/
- call assert_equal('B', getline(2))
-
- /a
- $
- \?,4s/c/C/
- call assert_equal('C', getline(3))
-
- call setline(1, ['a', 'b', 'c', 'd'])
- %s/c/c/
- 1,\&s/b/B/
- call assert_equal('B', getline(2))
-
- let @/ = 'apple'
- call assert_fails('\/print', ['E486:.*apple'])
-
- bwipe!
-endfunc
-
-" Test for the tick mark (') in an excmd range
-func Test_tick_mark_in_range()
- " If only the tick is passed as a range and no command is specified, there
- " should not be an error
- call feedkeys(":'\<CR>", 'xt')
- call assert_equal("'", @:)
- call assert_fails("',print", 'E78:')
-endfunc
-
-" Test for using a line number followed by a search pattern as range
-func Test_lnum_and_pattern_as_range()
- new
- call setline(1, ['foo 1', 'foo 2', 'foo 3'])
- let @" = ''
- 2/foo/yank
- call assert_equal("foo 3\n", @")
- call assert_equal(1, line('.'))
- close!
-endfunc
-
-" Tests for getcmdline(), getcmdpos() and getcmdtype()
-func Check_cmdline(cmdtype)
- call assert_equal('MyCmd a', getcmdline())
- call assert_equal(8, getcmdpos())
- call assert_equal(a:cmdtype, getcmdtype())
- return ''
-endfunc
-
-set cpo&
-
-func Test_getcmdtype()
- call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt")
-
- let cmdtype = ''
- debuggreedy
- call feedkeys(":debug echo 'test'\<CR>", "t")
- call feedkeys("let cmdtype = \<C-R>=string(getcmdtype())\<CR>\<CR>", "t")
- call feedkeys("cont\<CR>", "xt")
- 0debuggreedy
- call assert_equal('>', cmdtype)
-
- call feedkeys("/MyCmd a\<C-R>=Check_cmdline('/')\<CR>\<Esc>", "xt")
- call feedkeys("?MyCmd a\<C-R>=Check_cmdline('?')\<CR>\<Esc>", "xt")
-
- call feedkeys(":call input('Answer?')\<CR>", "t")
- call feedkeys("MyCmd a\<C-R>=Check_cmdline('@')\<CR>\<C-C>", "xt")
-
- call feedkeys(":insert\<CR>MyCmd a\<C-R>=Check_cmdline('-')\<CR>\<Esc>", "xt")
-
- cnoremap <expr> <F6> Check_cmdline('=')
- call feedkeys("a\<C-R>=MyCmd a\<F6>\<Esc>\<Esc>", "xt")
- cunmap <F6>
-
- call assert_equal('', getcmdline())
-endfunc
-
-func Test_getcmdwintype()
- call feedkeys("q/:let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
- call assert_equal('/', a)
-
- call feedkeys("q?:let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
- call assert_equal('?', a)
-
- call feedkeys("q::let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
- call assert_equal(':', a)
-
- call feedkeys(":\<C-F>:let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
- call assert_equal(':', a)
-
- 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
-
-func Test_verbosefile()
- set verbosefile=Xlog
- echomsg 'foo'
- echomsg 'bar'
- set verbosefile=
- let log = readfile('Xlog')
- call assert_match("foo\nbar", join(log, "\n"))
- call delete('Xlog')
- call mkdir('Xdir')
- if !has('win32') " FIXME: no error on Windows, libuv bug?
- call assert_fails('set verbosefile=Xdir', ['E484:.*Xdir', 'E474:'])
- endif
- call delete('Xdir', 'd')
-endfunc
-
-func Test_verbose_option()
- " See test/functional/legacy/cmdline_spec.lua
- CheckScreendump
-
- let lines =<< trim [SCRIPT]
- command DoSomething echo 'hello' |set ts=4 |let v = '123' |echo v
- call feedkeys("\r", 't') " for the hit-enter prompt
- set verbose=20
- [SCRIPT]
- call writefile(lines, 'XTest_verbose')
-
- let buf = RunVimInTerminal('-S XTest_verbose', {'rows': 12})
- call term_wait(buf, 100)
- call term_sendkeys(buf, ":DoSomething\<CR>")
- call VerifyScreenDump(buf, 'Test_verbose_option_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_verbose')
-endfunc
-
-func Test_setcmdpos()
- func InsertTextAtPos(text, pos)
- call assert_equal(0, setcmdpos(a:pos))
- return a:text
- endfunc
-
- " setcmdpos() with position in the middle of the command line.
- call feedkeys(":\"12\<C-R>=InsertTextAtPos('a', 3)\<CR>b\<CR>", 'xt')
- call assert_equal('"1ab2', @:)
-
- call feedkeys(":\"12\<C-R>\<C-R>=InsertTextAtPos('a', 3)\<CR>b\<CR>", 'xt')
- call assert_equal('"1b2a', @:)
-
- " setcmdpos() with position beyond the end of the command line.
- call feedkeys(":\"12\<C-B>\<C-R>=InsertTextAtPos('a', 10)\<CR>b\<CR>", 'xt')
- call assert_equal('"12ab', @:)
-
- " setcmdpos() returns 1 when not editing the command line.
- call assert_equal(1, 3->setcmdpos())
-endfunc
-
-func Test_cmdline_overstrike()
- " Nvim: only utf8 is supported.
- let encodings = ['utf8']
- let encoding_save = &encoding
-
- for e in encodings
- exe 'set encoding=' . e
-
- " Test overstrike in the middle of the command line.
- call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
- call assert_equal('"0ab1cd4', @:, e)
-
- " Test overstrike going beyond end of command line.
- call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cdefgh\<enter>", 'xt')
- call assert_equal('"0ab1cdefgh', @:, e)
-
- " Test toggling insert/overstrike a few times.
- call feedkeys(":\"01234\<home>\<right>ab\<right>\<insert>cd\<right>\<insert>ef\<enter>", 'xt')
- call assert_equal('"ab0cd3ef4', @:, e)
- endfor
-
- " Test overstrike with multi-byte characters.
- call feedkeys(":\"テキストエディタ\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
- call assert_equal('"テabキcdエディタ', @:, e)
-
- let &encoding = encoding_save
-endfunc
-
-func Test_cmdwin_bug()
- let winid = win_getid()
- sp
- try
- call feedkeys("q::call win_gotoid(" .. winid .. ")\<CR>:q\<CR>", 'x!')
- catch /^Vim\%((\a\+)\)\=:E11/
- endtry
- bw!
-endfunc
-
-func Test_cmdwin_restore()
- CheckScreendump
-
- let lines =<< trim [SCRIPT]
- call setline(1, range(30))
- 2split
- [SCRIPT]
- call writefile(lines, 'XTest_restore')
-
- let buf = RunVimInTerminal('-S XTest_restore', {'rows': 12})
- call term_wait(buf, 100)
- call term_sendkeys(buf, "q:")
- call VerifyScreenDump(buf, 'Test_cmdwin_restore_1', {})
-
- " normal restore
- call term_sendkeys(buf, ":q\<CR>")
- call VerifyScreenDump(buf, 'Test_cmdwin_restore_2', {})
-
- " restore after setting 'lines' with one window
- call term_sendkeys(buf, ":close\<CR>")
- call term_sendkeys(buf, "q:")
- call term_sendkeys(buf, ":set lines=18\<CR>")
- call term_sendkeys(buf, ":q\<CR>")
- call VerifyScreenDump(buf, 'Test_cmdwin_restore_3', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_restore')
-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
-
-func Test_cmdwin_feedkeys()
- " This should not generate E488
- call feedkeys("q:\<CR>", 'x')
- " Using feedkeys with q: only should automatically close the cmd window
- call feedkeys('q:', 'xt')
- call assert_equal(1, winnr('$'))
- call assert_equal('', getcmdwintype())
-endfunc
-
-" Tests for the issues fixed in 7.4.441.
-" When 'cedit' is set to Ctrl-C, opening the command window hangs Vim
-func Test_cmdwin_cedit()
- exe "set cedit=\<C-c>"
- normal! :
- call assert_equal(1, winnr('$'))
-
- let g:cmd_wintype = ''
- func CmdWinType()
- let g:cmd_wintype = getcmdwintype()
- let g:wintype = win_gettype()
- return ''
- endfunc
-
- call feedkeys("\<C-c>a\<C-R>=CmdWinType()\<CR>\<CR>")
- echo input('')
- call assert_equal('@', g:cmd_wintype)
- call assert_equal('command', g:wintype)
-
- set cedit&vim
- delfunc CmdWinType
-endfunc
-
-" Test for CmdwinEnter autocmd
-func Test_cmdwin_autocmd()
- CheckFeature cmdwin
-
- augroup CmdWin
- au!
- autocmd BufLeave * if &buftype == '' | update | endif
- autocmd CmdwinEnter * startinsert
- augroup END
-
- call assert_fails('call feedkeys("q:xyz\<CR>", "xt")', 'E492:')
- call assert_equal('xyz', @:)
-
- augroup CmdWin
- au!
- augroup END
- augroup! CmdWin
-endfunc
-
-func Test_cmdlineclear_tabenter()
- " See test/functional/legacy/cmdline_spec.lua
- CheckScreendump
-
- let lines =<< trim [SCRIPT]
- call setline(1, range(30))
- [SCRIPT]
-
- call writefile(lines, 'XtestCmdlineClearTabenter')
- let buf = RunVimInTerminal('-S XtestCmdlineClearTabenter', #{rows: 10})
- call term_wait(buf, 50)
- " in one tab make the command line higher with CTRL-W -
- call term_sendkeys(buf, ":tabnew\<cr>\<C-w>-\<C-w>-gtgt")
- call VerifyScreenDump(buf, 'Test_cmdlineclear_tabenter', {})
-
- call StopVimInTerminal(buf)
- call delete('XtestCmdlineClearTabenter')
-endfunc
-
-" Test for expanding special keywords in cmdline
-func Test_cmdline_expand_special()
- new
- %bwipe!
- call assert_fails('e #', 'E194:')
- call assert_fails('e <afile>', 'E495:')
- call assert_fails('e <abuf>', 'E496:')
- call assert_fails('e <amatch>', 'E497:')
-
- call writefile([], 'Xfile.cpp')
- call writefile([], 'Xfile.java')
- new Xfile.cpp
- call feedkeys(":e %:r\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.cpp Xfile.java', @:)
- close
- call delete('Xfile.cpp')
- call delete('Xfile.java')
-endfunc
-
-func Test_cmdwin_jump_to_win()
- call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:')
- new
- set modified
- call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', ['E37:', 'E162:'])
- close!
- call feedkeys("q/:close\<CR>", "xt")
- call assert_equal(1, winnr('$'))
- call feedkeys("q/:exit\<CR>", "xt")
- call assert_equal(1, winnr('$'))
-
- " opening command window twice should fail
- call assert_beeps('call feedkeys("q:q:\<CR>\<CR>", "xt")')
- call assert_equal(1, winnr('$'))
-endfunc
-
-func Test_cmdwin_tabpage()
- tabedit
- call assert_fails("silent norm q/g :I\<Esc>", 'E11:')
- tabclose!
-endfunc
-
-func Test_cmdwin_interrupted()
- CheckScreendump
-
- " aborting the :smile output caused the cmdline window to use the current
- " buffer.
- let lines =<< trim [SCRIPT]
- au WinNew * smile
- [SCRIPT]
- call writefile(lines, 'XTest_cmdwin')
-
- let buf = RunVimInTerminal('-S XTest_cmdwin', {'rows': 18})
- " open cmdwin
- call term_sendkeys(buf, "q:")
- call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 18))})
- " quit more prompt for :smile command
- call term_sendkeys(buf, "q")
- call WaitForAssert({-> assert_match('^$', term_getline(buf, 18))})
- " execute a simple command
- call term_sendkeys(buf, "aecho 'done'\<CR>")
- call VerifyScreenDump(buf, 'Test_cmdwin_interrupted', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_cmdwin')
-endfunc
-
-" Test for backtick expression in the command line
-func Test_cmd_backtick()
- CheckNotMSWindows " FIXME: see #19297
- %argd
- argadd `=['a', 'b', 'c']`
- call assert_equal(['a', 'b', 'c'], argv())
- %argd
-
- argadd `echo abc def`
- call assert_equal(['abc def'], argv())
- %argd
-endfunc
-
-" Test for the :! command
-func Test_cmd_bang()
- CheckUnix
-
- let lines =<< trim [SCRIPT]
- " Test for no previous command
- call assert_fails('!!', 'E34:')
- set nomore
- " Test for cmdline expansion with :!
- call setline(1, 'foo!')
- silent !echo <cWORD> > Xfile.out
- call assert_equal(['foo!'], readfile('Xfile.out'))
- " Test for using previous command
- silent !echo \! !
- call assert_equal(['! echo foo!'], readfile('Xfile.out'))
- call writefile(v:errors, 'Xresult')
- call delete('Xfile.out')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -S Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-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_cmd_bang_args()
- new
- :.!
- call assert_equal(0, v:shell_error)
-
- " Note that below there is one space char after the '!'. This caused a
- " shell error in the past, see https://github.com/vim/vim/issues/11495.
- :.!
- call assert_equal(0, v:shell_error)
- bwipe!
-
- CheckUnix
- :.!pwd
- call assert_equal(0, v:shell_error)
- :.! pwd
- call assert_equal(0, v:shell_error)
-
- " Note there is one space after 'pwd'.
- :.! pwd
- call assert_equal(0, v:shell_error)
-
- " Note there are two spaces after 'pwd'.
- :.! pwd
- call assert_equal(0, v:shell_error)
- :.!ls ~
- call assert_equal(0, v:shell_error)
-
- " Note there is one space char after '~'.
- :.!ls ~
- call assert_equal(0, v:shell_error)
-
- " Note there are two spaces after '~'.
- :.!ls ~
- call assert_equal(0, v:shell_error)
-
- :.!echo "foo"
- call assert_equal(getline('.'), "foo")
- :.!echo "foo "
- call assert_equal(getline('.'), "foo ")
- :.!echo " foo "
- call assert_equal(getline('.'), " foo ")
- :.!echo " foo "
- call assert_equal(getline('.'), " foo ")
-
- %bwipe!
-endfunc
-
-" Test for using ~ for home directory in cmdline completion matches
-func Test_cmdline_expand_home()
- call mkdir('Xdir')
- call writefile([], 'Xdir/Xfile1')
- call writefile([], 'Xdir/Xfile2')
- cd Xdir
- let save_HOME = $HOME
- let $HOME = getcwd()
- call feedkeys(":e ~/\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e ~/Xfile1 ~/Xfile2', @:)
- let $HOME = save_HOME
- cd ..
- call delete('Xdir', 'rf')
-endfunc
-
-" Test for using CTRL-\ CTRL-G in the command line to go back to normal mode
-" or insert mode (when 'insertmode' is set)
-func Test_cmdline_ctrl_g()
- new
- call setline(1, 'abc')
- call cursor(1, 3)
- " If command line is entered from insert mode, using C-\ C-G should back to
- " insert mode
- call feedkeys("i\<C-O>:\<C-\>\<C-G>xy", 'xt')
- call assert_equal('abxyc', getline(1))
- call assert_equal(4, col('.'))
-
- " If command line is entered in 'insertmode', using C-\ C-G should back to
- " 'insertmode'
- " call feedkeys(":set im\<cr>\<C-L>:\<C-\>\<C-G>12\<C-L>:set noim\<cr>", 'xt')
- " call assert_equal('ab12xyc', getline(1))
- close!
-endfunc
-
-" Test for 'wildmode'
-func Wildmode_tests()
- func T(a, c, p)
- return "oneA\noneB\noneC"
- endfunc
- command -nargs=1 -complete=custom,T MyCmd
-
- set nowildmenu
- set wildmode=full,list
- let g:Sline = ''
- call feedkeys(":MyCmd \t\t\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal('oneA oneB oneC', g:Sline)
- call assert_equal('"MyCmd oneA', @:)
-
- set wildmode=longest,full
- call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"MyCmd one', @:)
- call feedkeys(":MyCmd o\t\t\t\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"MyCmd oneC', @:)
-
- set wildmode=longest
- call feedkeys(":MyCmd one\t\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"MyCmd one', @:)
-
- set wildmode=list:longest
- let g:Sline = ''
- call feedkeys(":MyCmd \t\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal('oneA oneB oneC', g:Sline)
- call assert_equal('"MyCmd one', @:)
-
- set wildmode=""
- call feedkeys(":MyCmd \t\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"MyCmd oneA', @:)
-
- " Test for wildmode=longest with 'fileignorecase' set
- set wildmode=longest
- set fileignorecase
- argadd AAA AAAA AAAAA
- call feedkeys(":buffer a\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"buffer AAA', @:)
- set fileignorecase&
-
- " Test for listing files with wildmode=list
- set wildmode=list
- let g:Sline = ''
- call feedkeys(":b A\t\t\<F4>\<C-B>\"\<CR>", 'xt')
- call assert_equal('AAA AAAA AAAAA', g:Sline)
- call assert_equal('"b A', @:)
-
- " when using longest completion match, matches shorter than the argument
- " should be ignored (happens with :help)
- set wildmode=longest,full
- set wildmenu
- call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"help a', @:)
- " non existing file
- call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e a1b2y3z4', @:)
- set wildmenu&
-
- " Test for longest file name completion with 'fileignorecase'
- " On MS-Windows, file names are case insensitive.
- if has('unix')
- call writefile([], 'XTESTfoo')
- call writefile([], 'Xtestbar')
- set nofileignorecase
- call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e XTESTfoo', @:)
- call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xtestbar', @:)
- set fileignorecase
- call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xtest', @:)
- call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xtest', @:)
- set fileignorecase&
- call delete('XTESTfoo')
- call delete('Xtestbar')
- endif
-
- %argdelete
- delcommand MyCmd
- delfunc T
- set wildmode&
- %bwipe!
-endfunc
-
-func Test_wildmode()
- " Test with utf-8 encoding
- call Wildmode_tests()
-
- " Test with latin1 encoding
- let save_encoding = &encoding
- " set encoding=latin1
- " call Wildmode_tests()
- let &encoding = save_encoding
-endfunc
-
-" Test for interrupting the command-line completion
-func Test_interrupt_compl()
- func F(lead, cmdl, p)
- if a:lead =~ 'tw'
- call interrupt()
- return
- endif
- return "one\ntwo\nthree"
- endfunc
- command -nargs=1 -complete=custom,F Tcmd
-
- set nowildmenu
- set wildmode=full
- let interrupted = 0
- try
- call feedkeys(":Tcmd tw\<Tab>\<C-B>\"\<CR>", 'xt')
- catch /^Vim:Interrupt$/
- let interrupted = 1
- endtry
- call assert_equal(1, interrupted)
-
- let interrupted = 0
- try
- call feedkeys(":Tcmd tw\<C-d>\<C-B>\"\<CR>", 'xt')
- catch /^Vim:Interrupt$/
- let interrupted = 1
- endtry
- call assert_equal(1, interrupted)
-
- delcommand Tcmd
- delfunc F
- set wildmode&
-endfunc
-
-" Test for moving the cursor on the : command line
-func Test_cmdline_edit()
- let str = ":one two\<C-U>"
- let str ..= "one two\<C-W>\<C-W>"
- let str ..= "four\<BS>\<C-H>\<Del>\<kDel>"
- let str ..= "\<Left>five\<Right>"
- let str ..= "\<Home>two "
- let str ..= "\<C-Left>one "
- let str ..= "\<C-Right> three"
- let str ..= "\<End>\<S-Left>four "
- let str ..= "\<S-Right> six"
- let str ..= "\<C-B>\"\<C-E> seven\<CR>"
- call feedkeys(str, 'xt')
- call assert_equal("\"one two three four five six seven", @:)
-endfunc
-
-" Test for moving the cursor on the / command line in 'rightleft' mode
-func Test_cmdline_edit_rightleft()
- CheckFeature rightleft
- set rightleft
- set rightleftcmd=search
- let str = "/one two\<C-U>"
- let str ..= "one two\<C-W>\<C-W>"
- let str ..= "four\<BS>\<C-H>\<Del>\<kDel>"
- let str ..= "\<Right>five\<Left>"
- let str ..= "\<Home>two "
- let str ..= "\<C-Right>one "
- let str ..= "\<C-Left> three"
- let str ..= "\<End>\<S-Right>four "
- let str ..= "\<S-Left> six"
- let str ..= "\<C-B>\"\<C-E> seven\<CR>"
- call assert_fails("call feedkeys(str, 'xt')", 'E486:')
- call assert_equal("\"one two three four five six seven", @/)
- set rightleftcmd&
- set rightleft&
-endfunc
-
-" Test for using <C-\>e in the command line to evaluate an expression
-func Test_cmdline_expr()
- " Evaluate an expression from the beginning of a command line
- call feedkeys(":abc\<C-B>\<C-\>e\"\\\"hello\"\<CR>\<CR>", 'xt')
- call assert_equal('"hello', @:)
-
- " Use an invalid expression for <C-\>e
- call assert_beeps('call feedkeys(":\<C-\>einvalid\<CR>", "tx")')
-
- " Insert literal <CTRL-\> in the command line
- call feedkeys(":\"e \<C-\>\<C-Y>\<CR>", 'xt')
- call assert_equal("\"e \<C-\>\<C-Y>", @:)
-endfunc
-
-" This was making the insert position negative
-func Test_cmdline_expr_register()
- exe "sil! norm! ?\<C-\>e0\<C-R>0\<Esc>?\<C-\>e0\<CR>"
-endfunc
-
-" Test for 'imcmdline' and 'imsearch'
-" This test doesn't actually test the input method functionality.
-func Test_cmdline_inputmethod()
- new
- call setline(1, ['', 'abc', ''])
- set imcmdline
-
- call feedkeys(":\"abc\<CR>", 'xt')
- call assert_equal("\"abc", @:)
- call feedkeys(":\"\<C-^>abc\<C-^>\<CR>", 'xt')
- call assert_equal("\"abc", @:)
- call feedkeys("/abc\<CR>", 'xt')
- call assert_equal([2, 1], [line('.'), col('.')])
- call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt')
- call assert_equal([2, 1], [line('.'), col('.')])
-
- " set imsearch=2
- call cursor(1, 1)
- call feedkeys("/abc\<CR>", 'xt')
- call assert_equal([2, 1], [line('.'), col('.')])
- call cursor(1, 1)
- call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt')
- call assert_equal([2, 1], [line('.'), col('.')])
- set imdisable
- call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt')
- call assert_equal([2, 1], [line('.'), col('.')])
- set imdisable&
- set imsearch&
-
- set imcmdline&
- %bwipe!
-endfunc
-
-" Test for recursively getting multiple command line inputs
-func Test_cmdwin_multi_input()
- call feedkeys(":\<C-R>=input('P: ')\<CR>\"cyan\<CR>\<CR>", 'xt')
- call assert_equal('"cyan', @:)
-endfunc
-
-" Test for using CTRL-_ in the command line with 'allowrevins'
-func Test_cmdline_revins()
- CheckNotMSWindows
- CheckFeature rightleft
- call feedkeys(":\"abc\<c-_>\<cr>", 'xt')
- call assert_equal("\"abc\<c-_>", @:)
- set allowrevins
- call feedkeys(":\"abc\<c-_>xyz\<c-_>\<CR>", 'xt')
- call assert_equal('"abcñèæ', @:)
- set allowrevins&
-endfunc
-
-" Test for typing UTF-8 composing characters in the command line
-func Test_cmdline_composing_chars()
- call feedkeys(":\"\<C-V>u3046\<C-V>u3099\<CR>", 'xt')
- call assert_equal('"ã†ã‚™', @:)
-endfunc
-
-" Test for normal mode commands not supported in the cmd window
-func Test_cmdwin_blocked_commands()
- call assert_fails('call feedkeys("q:\<C-T>\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-]>\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-^>\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:Q\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:Z\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<F1>\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>s\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>v\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>^\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>n\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>z\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>o\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>w\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>j\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>k\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>h\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>l\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>T\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>x\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>r\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>R\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>K\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>}\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>]\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>f\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>d\<CR>", "xt")', 'E11:')
- call assert_fails('call feedkeys("q:\<C-W>g\<CR>", "xt")', 'E11:')
-endfunc
-
-" Close the Cmd-line window in insert mode using CTRL-C
-func Test_cmdwin_insert_mode_close()
- %bw!
- let s = ''
- exe "normal q:a\<C-C>let s='Hello'\<CR>"
- call assert_equal('Hello', s)
- call assert_equal(1, winnr('$'))
-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
-
-func Test_read_shellcmd()
- CheckUnix
- if executable('ls')
- " There should be ls in the $PATH
- call feedkeys(":r! l\<c-a>\<c-b>\"\<cr>", 'tx')
- call assert_match('^"r! .*\<ls\>', @:)
- endif
-
- if executable('rm')
- call feedkeys(":r! ++enc=utf-8 r\<c-a>\<c-b>\"\<cr>", 'tx')
- call assert_notmatch('^"r!.*\<runtest.vim\>', @:)
- call assert_match('^"r!.*\<rm\>', @:)
-
- call feedkeys(":r ++enc=utf-8 !rm\<c-a>\<c-b>\"\<cr>", 'tx')
- call assert_notmatch('^"r.*\<runtest.vim\>', @:)
- call assert_match('^"r ++enc\S\+ !.*\<rm\>', @:)
- endif
-endfunc
-
-" Test for going up and down the directory tree using 'wildmenu'
-func Test_wildmenu_dirstack()
- CheckUnix
- %bw!
- call mkdir('Xdir1/dir2/dir3/dir4', 'p')
- call writefile([], 'Xdir1/file1_1.txt')
- call writefile([], 'Xdir1/file1_2.txt')
- call writefile([], 'Xdir1/dir2/file2_1.txt')
- call writefile([], 'Xdir1/dir2/file2_2.txt')
- call writefile([], 'Xdir1/dir2/dir3/file3_1.txt')
- call writefile([], 'Xdir1/dir2/dir3/file3_2.txt')
- call writefile([], 'Xdir1/dir2/dir3/dir4/file4_1.txt')
- call writefile([], 'Xdir1/dir2/dir3/dir4/file4_2.txt')
- set wildmenu
-
- cd Xdir1/dir2/dir3/dir4
- call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e file4_1.txt', @:)
- call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e ../dir4/', @:)
- call feedkeys(":e \<Tab>\<Up>\<Up>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e ../../dir3/', @:)
- call feedkeys(":e \<Tab>\<Up>\<Up>\<Up>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e ../../../dir2/', @:)
- call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e ../../dir3/dir4/', @:)
- call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e ../../dir3/dir4/file4_1.txt', @:)
- cd -
- call feedkeys(":e Xdir1/\<Tab>\<Down>\<Down>\<Down>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xdir1/dir2/dir3/dir4/file4_1.txt', @:)
-
- call delete('Xdir1', 'rf')
- set wildmenu&
-endfunc
-
-" Test for recalling newer or older cmdline from history with <Up>, <Down>,
-" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <kPageUp>, <kPageDown>, <C-p>, or
-" <C-n>.
-func Test_recalling_cmdline()
- CheckFeature cmdline_hist
-
- let g:cmdlines = []
- cnoremap <Plug>(save-cmdline) <Cmd>let g:cmdlines += [getcmdline()]<CR>
-
- let histories = [
- \ #{name: 'cmd', enter: ':', exit: "\<Esc>"},
- \ #{name: 'search', enter: '/', exit: "\<Esc>"},
- \ #{name: 'expr', enter: ":\<C-r>=", exit: "\<Esc>\<Esc>"},
- \ #{name: 'input', enter: ":call input('')\<CR>", exit: "\<CR>"},
- "\ TODO: {'name': 'debug', ...}
- \]
- let keypairs = [
- \ #{older: "\<Up>", newer: "\<Down>", prefixmatch: v:true},
- \ #{older: "\<S-Up>", newer: "\<S-Down>", prefixmatch: v:false},
- \ #{older: "\<PageUp>", newer: "\<PageDown>", prefixmatch: v:false},
- \ #{older: "\<kPageUp>", newer: "\<kPageDown>", prefixmatch: v:false},
- \ #{older: "\<C-p>", newer: "\<C-n>", prefixmatch: v:false},
- \]
- let prefix = 'vi'
- for h in histories
- call histadd(h.name, 'vim')
- call histadd(h.name, 'virtue')
- call histadd(h.name, 'Virgo')
- call histadd(h.name, 'vogue')
- call histadd(h.name, 'emacs')
- for k in keypairs
- let g:cmdlines = []
- let keyseqs = h.enter
- \ .. prefix
- \ .. repeat(k.older .. "\<Plug>(save-cmdline)", 2)
- \ .. repeat(k.newer .. "\<Plug>(save-cmdline)", 2)
- \ .. h.exit
- call feedkeys(keyseqs, 'xt')
- call histdel(h.name, -1) " delete the history added by feedkeys above
- let expect = k.prefixmatch
- \ ? ['virtue', 'vim', 'virtue', prefix]
- \ : ['emacs', 'vogue', 'emacs', prefix]
- call assert_equal(expect, g:cmdlines)
- endfor
- endfor
-
- unlet g:cmdlines
- cunmap <Plug>(save-cmdline)
-endfunc
-
-func Test_cmd_map_cmdlineChanged()
- let g:log = []
- cnoremap <F1> l<Cmd><CR>s
- augroup test
- autocmd!
- autocmd CmdlineChanged : let g:log += [getcmdline()]
- augroup END
-
- call feedkeys(":\<F1>\<CR>", 'xt')
- call assert_equal(['l', 'ls'], g:log)
-
- let @b = 'b'
- cnoremap <F1> a<C-R>b
- let g:log = []
- call feedkeys(":\<F1>\<CR>", 'xt')
- call assert_equal(['a', 'ab'], g:log)
-
- unlet g:log
- cunmap <F1>
- augroup test
- autocmd!
- augroup END
-endfunc
-
-" Test for the 'suffixes' option
-func Test_suffixes_opt()
- call writefile([], 'Xfile')
- call writefile([], 'Xfile.c')
- call writefile([], 'Xfile.o')
- set suffixes=
- call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile Xfile.c Xfile.o', @:)
- call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.c', @:)
- set suffixes=.c
- call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile Xfile.o Xfile.c', @:)
- call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.o', @:)
- set suffixes=,,
- call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.c Xfile.o Xfile', @:)
- call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.o', @:)
- set suffixes&
- " Test for getcompletion() with different patterns
- call assert_equal(['Xfile', 'Xfile.c', 'Xfile.o'], getcompletion('Xfile', 'file'))
- call assert_equal(['Xfile'], getcompletion('Xfile$', 'file'))
- call delete('Xfile')
- call delete('Xfile.c')
- call delete('Xfile.o')
-endfunc
-
-" Test for using a popup menu for the command line completion matches
-" (wildoptions=pum)
-func Test_wildmenu_pum()
- CheckRunVimInTerminal
-
- let commands =<< trim [CODE]
- set wildmenu
- set wildoptions=pum
- set shm+=I
- set noruler
- set noshowcmd
-
- func CmdCompl(a, b, c)
- return repeat(['aaaa'], 120)
- endfunc
- command -nargs=* -complete=customlist,CmdCompl Tcmd
-
- func MyStatusLine() abort
- return 'status'
- endfunc
- func SetupStatusline()
- set statusline=%!MyStatusLine()
- set laststatus=2
- endfunc
-
- func MyTabLine()
- return 'my tab line'
- endfunc
- func SetupTabline()
- set statusline=
- set tabline=%!MyTabLine()
- set showtabline=2
- endfunc
-
- func DoFeedKeys()
- let &wildcharm = char2nr("\t")
- call feedkeys(":edit $VIMRUNTIME/\<Tab>\<Left>\<C-U>ab\<Tab>")
- endfunc
- [CODE]
- call writefile(commands, 'Xtest')
-
- let buf = RunVimInTerminal('-S Xtest', #{rows: 10})
-
- call term_sendkeys(buf, ":sign \<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_01', {})
-
- " going down the popup menu using <Down>
- call term_sendkeys(buf, "\<Down>\<Down>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_02', {})
-
- " going down the popup menu using <C-N>
- call term_sendkeys(buf, "\<C-N>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_03', {})
-
- " going up the popup menu using <C-P>
- call term_sendkeys(buf, "\<C-P>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_04', {})
-
- " going up the popup menu using <Up>
- call term_sendkeys(buf, "\<Up>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_05', {})
-
- " pressing <C-E> should end completion and go back to the original match
- call term_sendkeys(buf, "\<C-E>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_06', {})
-
- " pressing <C-Y> should select the current match and end completion
- call term_sendkeys(buf, "\<Tab>\<C-P>\<C-P>\<C-Y>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_07', {})
-
- " With 'wildmode' set to 'longest,full', completing a match should display
- " the longest match, the wildmenu should not be displayed.
- call term_sendkeys(buf, ":\<C-U>set wildmode=longest,full\<CR>")
- call TermWait(buf)
- call term_sendkeys(buf, ":sign u\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_08', {})
-
- " pressing <Tab> should display the wildmenu
- call term_sendkeys(buf, "\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {})
-
- " pressing <Tab> second time should select the next entry in the menu
- call term_sendkeys(buf, "\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {})
-
- call term_sendkeys(buf, ":\<C-U>set wildmode=full\<CR>")
- " showing popup menu in different columns in the cmdline
- call term_sendkeys(buf, ":sign define \<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_11', {})
-
- call term_sendkeys(buf, " \<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_12', {})
-
- call term_sendkeys(buf, " \<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_13', {})
-
- " Directory name completion
- call mkdir('Xdir/XdirA/XdirB', 'p')
- call writefile([], 'Xdir/XfileA')
- call writefile([], 'Xdir/XdirA/XfileB')
- call writefile([], 'Xdir/XdirA/XdirB/XfileC')
-
- call term_sendkeys(buf, "\<C-U>e Xdi\<Tab>\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_14', {})
-
- " Pressing <Right> on a directory name should go into that directory
- call term_sendkeys(buf, "\<Right>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_15', {})
-
- " Pressing <Left> on a directory name should go to the parent directory
- call term_sendkeys(buf, "\<Left>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_16', {})
-
- " Pressing <C-A> when the popup menu is displayed should list all the
- " matches but the popup menu should still remain
- call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-A>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_17', {})
-
- " Pressing <C-D> when the popup menu is displayed should remove the popup
- " menu
- call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-D>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_18', {})
-
- " Pressing <S-Tab> should open the popup menu with the last entry selected
- call term_sendkeys(buf, "\<C-U>\<CR>:sign \<S-Tab>\<C-P>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_19', {})
-
- " Pressing <Esc> should close the popup menu and cancel the cmd line
- call term_sendkeys(buf, "\<C-U>\<CR>:sign \<Tab>\<Esc>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_20', {})
-
- " Typing a character when the popup is open, should close the popup
- call term_sendkeys(buf, ":sign \<Tab>x")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_21', {})
-
- " When the popup is open, entering the cmdline window should close the popup
- call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-F>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_22', {})
- call term_sendkeys(buf, ":q\<CR>")
-
- " After the last popup menu item, <C-N> should show the original string
- call term_sendkeys(buf, ":sign u\<Tab>\<C-N>\<C-N>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_23', {})
-
- " Use the popup menu for the command name
- call term_sendkeys(buf, "\<C-U>bu\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_24', {})
-
- " Pressing the left arrow should remove the popup menu
- call term_sendkeys(buf, "\<Left>\<Left>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_25', {})
-
- " Pressing <BS> should remove the popup menu and erase the last character
- call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<BS>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_26', {})
-
- " Pressing <C-W> should remove the popup menu and erase the previous word
- call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-W>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_27', {})
-
- " Pressing <C-U> should remove the popup menu and erase the entire line
- call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-U>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_28', {})
-
- " Using <C-E> to cancel the popup menu and then pressing <Up> should recall
- " the cmdline from history
- call term_sendkeys(buf, "sign xyz\<Esc>:sign \<Tab>\<C-E>\<Up>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {})
-
- " Check "list" still works
- call term_sendkeys(buf, "\<C-U>set wildmode=longest,list\<CR>")
- call term_sendkeys(buf, ":cn\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {})
- call term_sendkeys(buf, "s")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {})
-
- " Tests a directory name contained full-width characters.
- call mkdir('Xdir/ã‚ã„ã†', 'p')
- call writefile([], 'Xdir/ã‚ã„ã†/abc')
- call writefile([], 'Xdir/ã‚ã„ã†/xyz')
- call writefile([], 'Xdir/ã‚ã„ã†/123')
-
- call term_sendkeys(buf, "\<C-U>set wildmode&\<CR>")
- call term_sendkeys(buf, ":\<C-U>e Xdir/ã‚ã„ã†/\<Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_32', {})
-
- " Pressing <C-A> when the popup menu is displayed should list all the
- " matches and pressing a key after that should remove the popup menu
- call term_sendkeys(buf, "\<C-U>set wildmode=full\<CR>")
- call term_sendkeys(buf, ":sign \<Tab>\<C-A>x")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_33', {})
-
- " Pressing <C-A> when the popup menu is displayed should list all the
- " matches and pressing <Left> after that should move the cursor
- call term_sendkeys(buf, "\<C-U>abc\<Esc>")
- call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<Left>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_34', {})
-
- " When <C-A> displays a lot of matches (screen scrolls), all the matches
- " should be displayed correctly on the screen.
- call term_sendkeys(buf, "\<End>\<C-U>Tcmd \<Tab>\<C-A>\<Left>\<Left>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_35', {})
-
- " After using <C-A> to expand all the filename matches, pressing <Up>
- " should not open the popup menu again.
- call term_sendkeys(buf, "\<C-E>\<C-U>:cd Xdir/XdirA\<CR>")
- call term_sendkeys(buf, ":e \<Tab>\<C-A>\<Up>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_36', {})
- call term_sendkeys(buf, "\<C-E>\<C-U>:cd -\<CR>")
-
- " After using <C-A> to expand all the matches, pressing <S-Tab> used to
- " crash Vim
- call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<S-Tab>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_37', {})
-
- " After removing the pum the command line is redrawn
- call term_sendkeys(buf, ":edit foo\<CR>")
- call term_sendkeys(buf, ":edit bar\<CR>")
- call term_sendkeys(buf, ":ls\<CR>")
- call term_sendkeys(buf, ":com\<Tab> ")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_38', {})
- call term_sendkeys(buf, "\<C-U>\<CR>")
-
- " Esc still works to abort the command when 'statusline' is set
- call term_sendkeys(buf, ":call SetupStatusline()\<CR>")
- call term_sendkeys(buf, ":si\<Tab>")
- call term_sendkeys(buf, "\<Esc>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_39', {})
-
- " Esc still works to abort the command when 'tabline' is set
- call term_sendkeys(buf, ":call SetupTabline()\<CR>")
- call term_sendkeys(buf, ":si\<Tab>")
- call term_sendkeys(buf, "\<Esc>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_40', {})
-
- " popup is cleared also when 'lazyredraw' is set
- call term_sendkeys(buf, ":set showtabline=1 laststatus=1 lazyredraw\<CR>")
- call term_sendkeys(buf, ":call DoFeedKeys()\<CR>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_41', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Pressing <PageDown> should scroll the menu downward
- call term_sendkeys(buf, ":sign \<Tab>\<PageDown>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_42', {})
- call term_sendkeys(buf, "\<PageDown>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_43', {})
- call term_sendkeys(buf, "\<PageDown>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_44', {})
- call term_sendkeys(buf, "\<PageDown>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_45', {})
- call term_sendkeys(buf, "\<C-U>sign \<Tab>\<Down>\<Down>\<PageDown>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_46', {})
-
- " Pressing <PageUp> should scroll the menu upward
- call term_sendkeys(buf, "\<C-U>sign \<Tab>\<PageUp>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_47', {})
- call term_sendkeys(buf, "\<PageUp>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_48', {})
- call term_sendkeys(buf, "\<PageUp>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_49', {})
- call term_sendkeys(buf, "\<PageUp>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_50', {})
-
- call term_sendkeys(buf, "\<C-U>\<CR>")
- call StopVimInTerminal(buf)
- call delete('Xtest')
- call delete('Xdir', 'rf')
-endfunc
-
-" Test for wildmenumode() with the cmdline popup menu
-func Test_wildmenumode_with_pum()
- set wildmenu
- set wildoptions=pum
- cnoremap <expr> <F2> wildmenumode()
- call feedkeys(":sign \<Tab>\<F2>\<F2>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"sign define10', @:)
- call feedkeys(":sign \<Tab>\<C-A>\<F2>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"sign define jump list place undefine unplace0', @:)
- call feedkeys(":sign \<Tab>\<C-E>\<F2>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"sign 0', @:)
- call feedkeys(":sign \<Tab>\<C-Y>\<F2>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"sign define0', @:)
- set nowildmenu wildoptions&
- cunmap <F2>
-endfunc
-
-" Test for opening the cmdline completion popup menu from the terminal window.
-" The popup menu should be positioned correctly over the status line of the
-" bottom-most window.
-func Test_wildmenu_pum_from_terminal()
- CheckRunVimInTerminal
- let python = PythonProg()
- call CheckPython(python)
-
- %bw!
- let cmds = ['set wildmenu wildoptions=pum']
- let pcmd = python .. ' -c "import sys; sys.stdout.write(sys.stdin.read())"'
- call add(cmds, "call term_start('" .. pcmd .. "')")
- call writefile(cmds, 'Xtest')
- let buf = RunVimInTerminal('-S Xtest', #{rows: 10})
- call term_sendkeys(buf, "\r\r\r")
- call term_wait(buf)
- call term_sendkeys(buf, "\<C-W>:sign \<Tab>")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_term_01', {})
- call term_wait(buf)
- call StopVimInTerminal(buf)
- call delete('Xtest')
-endfunc
-
-func Test_wildmenu_pum_clear_entries()
- CheckRunVimInTerminal
-
- " This was using freed memory. Run in a terminal to get the pum to update.
- let lines =<< trim END
- set wildoptions=pum
- set wildchar=<C-E>
- END
- call writefile(lines, 'XwildmenuTest', 'D')
- let buf = RunVimInTerminal('-S XwildmenuTest', #{rows: 10})
-
- call term_sendkeys(buf, ":\<C-E>\<C-E>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_clear_entries_1', {})
-
- set wildoptions& wildchar&
-endfunc
-
-" Test for completion after a :substitute command followed by a pipe (|)
-" character
-func Test_cmdline_complete_substitute()
- call feedkeys(":s | \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s | \t", @:)
- call feedkeys(":s/ | \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s/ | \t", @:)
- call feedkeys(":s/one | \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s/one | \t", @:)
- call feedkeys(":s/one/ | \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s/one/ | \t", @:)
- call feedkeys(":s/one/two | \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s/one/two | \t", @:)
- call feedkeys(":s/one/two/ | chist\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"s/one/two/ | chistory', @:)
- call feedkeys(":s/one/two/g \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s/one/two/g \t", @:)
- call feedkeys(":s/one/two/g | chist\t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s/one/two/g | chistory", @:)
- call feedkeys(":s/one/t\\/ | \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"s/one/t\\/ | \t", @:)
- call feedkeys(":s/one/t\"o/ | chist\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"s/one/t"o/ | chistory', @:)
- call feedkeys(":s/one/t|o/ | chist\t\<C-B>\"\<CR>", 'xt')
- call assert_equal('"s/one/t|o/ | chistory', @:)
- call feedkeys(":&\t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"&\t", @:)
-endfunc
-
-" Test for the :dlist command completion
-func Test_cmdline_complete_dlist()
- call feedkeys(":dlist 10 /pat/ a\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"dlist 10 /pat/ a\<C-A>", @:)
- call feedkeys(":dlist 10 /pat/ \t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"dlist 10 /pat/ \t", @:)
- call feedkeys(":dlist 10 /pa\\t/\t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"dlist 10 /pa\\t/\t", @:)
- call feedkeys(":dlist 10 /pat\\\t\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"dlist 10 /pat\\\t", @:)
- call feedkeys(":dlist 10 /pat/ | chist\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"dlist 10 /pat/ | chistory", @:)
-endfunc
-
-" argument list (only for :argdel) fuzzy completion
-func Test_fuzzy_completion_arglist()
- argadd change.py count.py charge.py
- set wildoptions&
- call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"argdel cge', @:)
- set wildoptions=fuzzy
- call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"argdel change.py charge.py', @:)
- %argdelete
- set wildoptions&
-endfunc
-
-" autocmd group name fuzzy completion
-func Test_fuzzy_completion_autocmd()
- set wildoptions&
- augroup MyFuzzyGroup
- augroup END
- call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"augroup mfg', @:)
- call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"augroup MyFuzzyGroup', @:)
- set wildoptions=fuzzy
- call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"augroup MyFuzzyGroup', @:)
- call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"augroup My*p', @:)
- augroup! MyFuzzyGroup
- set wildoptions&
-endfunc
-
-" buffer name fuzzy completion
-func Test_fuzzy_completion_bufname()
- set wildoptions&
- " Use a long name to reduce the risk of matching a random directory name
- edit SomeRandomFileWithLetters.txt
- enew
- call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"b SRFWL', @:)
- call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"b SomeRandomFileWithLetters.txt', @:)
- set wildoptions=fuzzy
- call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"b SomeRandomFileWithLetters.txt', @:)
- call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"b S*FileWithLetters.txt', @:)
- %bw!
- set wildoptions&
-endfunc
-
-" buffer name (full path) fuzzy completion
-func Test_fuzzy_completion_bufname_fullpath()
- CheckUnix
- set wildoptions&
- call mkdir('Xcmd/Xstate/Xfile.js', 'p')
- edit Xcmd/Xstate/Xfile.js
- cd Xcmd/Xstate
- enew
- call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"b CmdStateFile', @:)
- set wildoptions=fuzzy
- call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match('Xcmd/Xstate/Xfile.js$', @:)
- cd -
- call delete('Xcmd', 'rf')
- set wildoptions&
-endfunc
-
-" :behave suboptions fuzzy completion
-func Test_fuzzy_completion_behave()
- set wildoptions&
- call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"behave xm', @:)
- call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"behave xterm', @:)
- set wildoptions=fuzzy
- call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"behave xterm', @:)
- call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"behave xt*m', @:)
- let g:Sline = ''
- call feedkeys(":behave win\<C-D>\<F4>\<C-B>\"\<CR>", 'tx')
- call assert_equal('mswin', g:Sline)
- call assert_equal('"behave win', @:)
- set wildoptions&
-endfunc
-
-" " colorscheme name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_colorscheme()
-" endfunc
-
-" built-in command name fuzzy completion
-func Test_fuzzy_completion_cmdname()
- set wildoptions&
- call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sbwin', @:)
- call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sbrewind', @:)
- set wildoptions=fuzzy
- call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sbrewind', @:)
- call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sbr*d', @:)
- set wildoptions&
-endfunc
-
-" " compiler name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_compiler()
-" endfunc
-
-" :cscope suboptions fuzzy completion
-func Test_fuzzy_completion_cscope()
- CheckFeature cscope
- set wildoptions&
- call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"cscope ret', @:)
- call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"cscope reset', @:)
- set wildoptions=fuzzy
- call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"cscope reset', @:)
- call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"cscope re*t', @:)
- set wildoptions&
-endfunc
-
-" :diffget/:diffput buffer name fuzzy completion
-func Test_fuzzy_completion_diff()
- new SomeBuffer
- diffthis
- new OtherBuffer
- diffthis
- set wildoptions&
- call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffget sbuf', @:)
- call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffput sbuf', @:)
- set wildoptions=fuzzy
- call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffget SomeBuffer', @:)
- call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffput SomeBuffer', @:)
- %bw!
- set wildoptions&
-endfunc
-
-" " directory name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_dirname()
-" endfunc
-
-" environment variable name fuzzy completion
-func Test_fuzzy_completion_env()
- set wildoptions&
- call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"echo $VUT', @:)
- set wildoptions=fuzzy
- call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"echo $VIMRUNTIME', @:)
- set wildoptions&
-endfunc
-
-" autocmd event fuzzy completion
-func Test_fuzzy_completion_autocmd_event()
- set wildoptions&
- call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"autocmd BWout', @:)
- set wildoptions=fuzzy
- call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"autocmd BufWipeout', @:)
- set wildoptions&
-endfunc
-
-" vim expression fuzzy completion
-func Test_fuzzy_completion_expr()
- let g:PerPlaceCount = 10
- set wildoptions&
- call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"let c = ppc', @:)
- set wildoptions=fuzzy
- call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"let c = PerPlaceCount', @:)
- set wildoptions&
-endfunc
-
-" " file name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_filename()
-" endfunc
-
-" " files in path fuzzy completion - NOT supported
-" func Test_fuzzy_completion_filesinpath()
-" endfunc
-
-" " filetype name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_filetype()
-" endfunc
-
-" user defined function name completion
-func Test_fuzzy_completion_userdefined_func()
- set wildoptions&
- call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"call Test_f_u_f', @:)
- set wildoptions=fuzzy
- call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"call Test_fuzzy_completion_userdefined_func()', @:)
- set wildoptions&
-endfunc
-
-" <SNR> functions should be sorted to the end
-func Test_fuzzy_completion_userdefined_snr_func()
- func s:Sendmail()
- endfunc
- func SendSomemail()
- endfunc
- func S1e2n3dmail()
- endfunc
- set wildoptions=fuzzy
- call feedkeys(":call sendmail\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"call SendSomemail() S1e2n3dmail() '
- \ .. expand("<SID>") .. 'Sendmail()', @:)
- set wildoptions&
- delfunc s:Sendmail
- delfunc SendSomemail
- delfunc S1e2n3dmail
-endfunc
-
-" user defined command name completion
-func Test_fuzzy_completion_userdefined_cmd()
- set wildoptions&
- call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"MsFeat', @:)
- set wildoptions=fuzzy
- call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"MissingFeature', @:)
- set wildoptions&
-endfunc
-
-" " :help tag fuzzy completion - NOT supported
-" func Test_fuzzy_completion_helptag()
-" endfunc
-
-" highlight group name fuzzy completion
-func Test_fuzzy_completion_hlgroup()
- set wildoptions&
- call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"highlight SKey', @:)
- call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"highlight SpecialKey', @:)
- set wildoptions=fuzzy
- call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"highlight SpecialKey', @:)
- call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"highlight Sp*Key', @:)
- set wildoptions&
-endfunc
-
-" :history suboptions fuzzy completion
-func Test_fuzzy_completion_history()
- set wildoptions&
- call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"history dg', @:)
- call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"history search', @:)
- set wildoptions=fuzzy
- call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"history debug', @:)
- call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"history se*h', @:)
- set wildoptions&
-endfunc
-
-" :language locale name fuzzy completion
-func Test_fuzzy_completion_lang()
- CheckUnix
- set wildoptions&
- call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"lang psx', @:)
- set wildoptions=fuzzy
- call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"lang POSIX', @:)
- set wildoptions&
-endfunc
-
-" :mapclear buffer argument fuzzy completion
-func Test_fuzzy_completion_mapclear()
- set wildoptions&
- call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"mapclear buf', @:)
- set wildoptions=fuzzy
- call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"mapclear <buffer>', @:)
- set wildoptions&
-endfunc
-
-" map name fuzzy completion
-func Test_fuzzy_completion_mapname()
- " test regex completion works
- set wildoptions=fuzzy
- call feedkeys(":cnoremap <ex\<Tab> <esc> \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"cnoremap <expr> <esc> \<Tab>", @:)
- nmap <plug>MyLongMap :p<CR>
- call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"nmap <Plug>MyLongMap", @:)
- call feedkeys(":nmap MLM \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"nmap MLM \t", @:)
- call feedkeys(":nmap <F2> one two \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"nmap <F2> one two \t", @:)
- " duplicate entries should be removed
- vmap <plug>MyLongMap :<C-U>#<CR>
- call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"nmap <Plug>MyLongMap", @:)
- nunmap <plug>MyLongMap
- vunmap <plug>MyLongMap
- call feedkeys(":nmap ABC\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"nmap ABC\t", @:)
- " results should be sorted by best match
- nmap <Plug>format :
- nmap <Plug>goformat :
- nmap <Plug>TestFOrmat :
- nmap <Plug>fendoff :
- nmap <Plug>state :
- nmap <Plug>FendingOff :
- call feedkeys(":nmap <Plug>fo\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"nmap <Plug>format <Plug>TestFOrmat <Plug>FendingOff <Plug>goformat <Plug>fendoff", @:)
- nunmap <Plug>format
- nunmap <Plug>goformat
- nunmap <Plug>TestFOrmat
- nunmap <Plug>fendoff
- nunmap <Plug>state
- nunmap <Plug>FendingOff
- set wildoptions&
-endfunc
-
-" abbreviation fuzzy completion
-func Test_fuzzy_completion_abbr()
- set wildoptions=fuzzy
- call feedkeys(":iabbr wait\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"iabbr <nowait>", @:)
- iabbr WaitForCompletion WFC
- call feedkeys(":iabbr fcl\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"iabbr WaitForCompletion", @:)
- call feedkeys(":iabbr a1z\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"iabbr a1z\t", @:)
-
- iunabbrev WaitForCompletion
- set wildoptions&
-endfunc
-
-" menu name fuzzy completion
-func Test_fuzzy_completion_menu()
- CheckFeature menu
-
- source $VIMRUNTIME/menu.vim
- set wildoptions&
- call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"menu pup', @:)
- set wildoptions=fuzzy
- call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"menu PopUp.', @:)
-
- set wildoptions&
- source $VIMRUNTIME/delmenu.vim
-endfunc
-
-" :messages suboptions fuzzy completion
-func Test_fuzzy_completion_messages()
- set wildoptions&
- call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"messages clr', @:)
- set wildoptions=fuzzy
- call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"messages clear', @:)
- set wildoptions&
-endfunc
-
-" :set option name fuzzy completion
-func Test_fuzzy_completion_option()
- set wildoptions&
- call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set brkopt', @:)
- set wildoptions=fuzzy
- call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set breakindentopt', @:)
- set wildoptions&
- call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set fixendofline', @:)
- set wildoptions=fuzzy
- call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set fixendofline', @:)
- set wildoptions&
-endfunc
-
-" :set <term_option>
-func Test_fuzzy_completion_term_option()
- throw 'Skipped: Nvim does not support term options'
- set wildoptions&
- call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set t_EC', @:)
- call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set <t_EC>', @:)
- set wildoptions=fuzzy
- call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set t_EC', @:)
- call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set <t_EC>', @:)
- set wildoptions&
-endfunc
-
-" " :packadd directory name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_packadd()
-" endfunc
-
-" " shell command name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_shellcmd()
-" endfunc
-
-" :sign suboptions fuzzy completion
-func Test_fuzzy_completion_sign()
- set wildoptions&
- call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sign ufe', @:)
- set wildoptions=fuzzy
- call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sign undefine', @:)
- set wildoptions&
-endfunc
-
-" :syntax suboptions fuzzy completion
-func Test_fuzzy_completion_syntax_cmd()
- set wildoptions&
- call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syntax kwd', @:)
- set wildoptions=fuzzy
- call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syntax keyword', @:)
- set wildoptions&
-endfunc
-
-" syntax group name fuzzy completion
-func Test_fuzzy_completion_syntax_group()
- set wildoptions&
- call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syntax list mpar', @:)
- set wildoptions=fuzzy
- call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx')
- " Fuzzy match prefers NvimParenthesis over MatchParen
- " call assert_equal('"syntax list MatchParen', @:)
- call assert_equal('"syntax list NvimParenthesis', @:)
- set wildoptions&
-endfunc
-
-" :syntime suboptions fuzzy completion
-func Test_fuzzy_completion_syntime()
- CheckFeature profile
- set wildoptions&
- call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syntime clr', @:)
- set wildoptions=fuzzy
- call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syntime clear', @:)
- set wildoptions&
-endfunc
-
-" " tag name fuzzy completion - NOT supported
-" func Test_fuzzy_completion_tagname()
-" endfunc
-
-" " tag name and file fuzzy completion - NOT supported
-" func Test_fuzzy_completion_tagfile()
-" endfunc
-
-" " user names fuzzy completion - how to test this functionality?
-" func Test_fuzzy_completion_username()
-" endfunc
-
-" user defined variable name fuzzy completion
-func Test_fuzzy_completion_userdefined_var()
- let g:SomeVariable=10
- set wildoptions&
- call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"let SVar', @:)
- set wildoptions=fuzzy
- call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"let SomeVariable', @:)
- set wildoptions&
-endfunc
-
-" Test for sorting the results by the best match
-func Test_fuzzy_completion_cmd_sort_results()
- %bw!
- command T123format :
- command T123goformat :
- command T123TestFOrmat :
- command T123fendoff :
- command T123state :
- command T123FendingOff :
- set wildoptions=fuzzy
- call feedkeys(":T123fo\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"T123format T123TestFOrmat T123FendingOff T123goformat T123fendoff', @:)
- delcommand T123format
- delcommand T123goformat
- delcommand T123TestFOrmat
- delcommand T123fendoff
- delcommand T123state
- delcommand T123FendingOff
- %bw
- set wildoptions&
-endfunc
-
-" Test for fuzzy completion of a command with lower case letters and a number
-func Test_fuzzy_completion_cmd_alnum()
- command Foo2Bar :
- set wildoptions=fuzzy
- call feedkeys(":foo2\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"Foo2Bar', @:)
- call feedkeys(":foo\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"Foo2Bar', @:)
- call feedkeys(":bar\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"Foo2Bar', @:)
- delcommand Foo2Bar
- set wildoptions&
-endfunc
-
-" Test for command completion for a command starting with 'k'
-func Test_fuzzy_completion_cmd_k()
- command KillKillKill :
- set wildoptions&
- call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"killkill\<Tab>", @:)
- set wildoptions=fuzzy
- call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"KillKillKill', @:)
- delcom KillKillKill
- set wildoptions&
-endfunc
-
-" Test for fuzzy completion for user defined custom completion function
-func Test_fuzzy_completion_custom_func()
- func Tcompl(a, c, p)
- return "format\ngoformat\nTestFOrmat\nfendoff\nstate"
- endfunc
- command -nargs=* -complete=custom,Tcompl Fuzzy :
- set wildoptions&
- call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"Fuzzy format", @:)
- call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"Fuzzy xy", @:)
- call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"Fuzzy ttt", @:)
- set wildoptions=fuzzy
- call feedkeys(":Fuzzy \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"Fuzzy format goformat TestFOrmat fendoff state", @:)
- call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"Fuzzy format TestFOrmat goformat fendoff", @:)
- call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"Fuzzy xy", @:)
- call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"Fuzzy TestFOrmat", @:)
- delcom Fuzzy
- set wildoptions&
-endfunc
-
-" Test for :breakadd argument completion
-func Test_cmdline_complete_breakadd()
- call feedkeys(":breakadd \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd expr file func here", @:)
- call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd expr", @:)
- call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd expr", @:)
- call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd here", @:)
- call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd here", @:)
- call feedkeys(":breakadd abc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd abc", @:)
- call assert_equal(['expr', 'file', 'func', 'here'], getcompletion('', 'breakpoint'))
- let l = getcompletion('not', 'breakpoint')
- call assert_equal([], l)
-
- " Test for :breakadd file [lnum] <file>
- call writefile([], 'Xscript')
- call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file Xscript", @:)
- call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file Xscript", @:)
- call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file 20 Xscript", @:)
- call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file 20 Xscript", @:)
- call feedkeys(":breakadd file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file 20x Xsc\t", @:)
- call feedkeys(":breakadd file 20\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file 20\t", @:)
- call feedkeys(":breakadd file 20x\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file 20x\t", @:)
- call feedkeys(":breakadd file Xscript \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file Xscript ", @:)
- call feedkeys(":breakadd file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd file X1B2C3", @:)
- call delete('Xscript')
-
- " Test for :breakadd func [lnum] <function>
- func Xbreak_func()
- endfunc
- call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func Xbreak_func", @:)
- call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func Xbreak_func", @:)
- call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func 20 Xbreak_func", @:)
- call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func 20 Xbreak_func", @:)
- call feedkeys(":breakadd func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func 20x Xbr\t", @:)
- call feedkeys(":breakadd func 20\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func 20\t", @:)
- call feedkeys(":breakadd func 20x\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func 20x\t", @:)
- call feedkeys(":breakadd func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func Xbreak_func ", @:)
- call feedkeys(":breakadd func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd func X1B2C3", @:)
- delfunc Xbreak_func
-
- " Test for :breakadd expr <expression>
- let g:Xtest_var = 10
- call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd expr Xtest_var", @:)
- call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd expr Xtest_var", @:)
- call feedkeys(":breakadd expr Xtest_var \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd expr Xtest_var ", @:)
- call feedkeys(":breakadd expr X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd expr X1B2C3", @:)
- unlet g:Xtest_var
-
- " Test for :breakadd here
- call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd here Xtest", @:)
- call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd here Xtest", @:)
- call feedkeys(":breakadd here \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakadd here ", @:)
-endfunc
-
-" Test for :breakdel argument completion
-func Test_cmdline_complete_breakdel()
- call feedkeys(":breakdel \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file func here", @:)
- call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file", @:)
- call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file", @:)
- call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel here", @:)
- call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel here", @:)
- call feedkeys(":breakdel abc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel abc", @:)
-
- " Test for :breakdel file [lnum] <file>
- call writefile([], 'Xscript')
- call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file Xscript", @:)
- call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file Xscript", @:)
- call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file 20 Xscript", @:)
- call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file 20 Xscript", @:)
- call feedkeys(":breakdel file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file 20x Xsc\t", @:)
- call feedkeys(":breakdel file 20\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file 20\t", @:)
- call feedkeys(":breakdel file 20x\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file 20x\t", @:)
- call feedkeys(":breakdel file Xscript \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file Xscript ", @:)
- call feedkeys(":breakdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel file X1B2C3", @:)
- call delete('Xscript')
-
- " Test for :breakdel func [lnum] <function>
- func Xbreak_func()
- endfunc
- call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func Xbreak_func", @:)
- call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func Xbreak_func", @:)
- call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func 20 Xbreak_func", @:)
- call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func 20 Xbreak_func", @:)
- call feedkeys(":breakdel func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func 20x Xbr\t", @:)
- call feedkeys(":breakdel func 20\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func 20\t", @:)
- call feedkeys(":breakdel func 20x\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func 20x\t", @:)
- call feedkeys(":breakdel func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func Xbreak_func ", @:)
- call feedkeys(":breakdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel func X1B2C3", @:)
- delfunc Xbreak_func
-
- " Test for :breakdel here
- call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel here Xtest", @:)
- call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel here Xtest", @:)
- call feedkeys(":breakdel here \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"breakdel here ", @:)
-endfunc
-
-" Test for :scriptnames argument completion
-func Test_cmdline_complete_scriptnames()
- set wildmenu
- call writefile(['let a = 1'], 'Xa1b2c3.vim')
- source Xa1b2c3.vim
- call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx')
- call assert_match("\"script .*Xa1b2c3.vim$", @:)
- call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx')
- call assert_match("\"script .*Xa1b2c3.vim$", @:)
- call feedkeys(":script b2c3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"script b2c3", @:)
- call feedkeys(":script 2\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match("\"script 2\<Tab>$", @:)
- call feedkeys(":script \<Tab>\<Left>\<Left> \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match("\"script .*Xa1b2c3.vim $", @:)
- call feedkeys(":script \<Tab>\<Left>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"script ", @:)
- call assert_match('Xa1b2c3.vim$', getcompletion('.*Xa1b2.*', 'scriptnames')[0])
- call assert_equal([], getcompletion('Xa1b2', 'scriptnames'))
- new
- call feedkeys(":script \<Tab>\<Left>\<Left>\<CR>", 'tx')
- call assert_equal('Xa1b2c3.vim', fnamemodify(@%, ':t'))
- bw!
- call delete('Xa1b2c3.vim')
- set wildmenu&
-endfunc
-
-" this was going over the end of IObuff
-func Test_report_error_with_composing()
- let caught = 'no'
- try
- exe repeat('0', 987) .. "0\xdd\x80\xdd\x80\xdd\x80\xdd\x80"
- catch /E492:/
- let caught = 'yes'
- endtry
- call assert_equal('yes', caught)
-endfunc
-
-" Test for expanding 2-letter and 3-letter :substitute command arguments.
-" These commands don't accept an argument.
-func Test_cmdline_complete_substitute_short()
- for cmd in ['sc', 'sce', 'scg', 'sci', 'scI', 'scn', 'scp', 'scl',
- \ 'sgc', 'sge', 'sg', 'sgi', 'sgI', 'sgn', 'sgp', 'sgl', 'sgr',
- \ 'sic', 'sie', 'si', 'siI', 'sin', 'sip', 'sir',
- \ 'sIc', 'sIe', 'sIg', 'sIi', 'sI', 'sIn', 'sIp', 'sIl', 'sIr',
- \ 'src', 'srg', 'sri', 'srI', 'srn', 'srp', 'srl', 'sr']
- call feedkeys(':' .. cmd .. " \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"' .. cmd .. " \<Tab>", @:)
- endfor
-endfunc
-
-" Test for :! shell command argument completion
-func Test_cmdline_complete_bang_cmd_argument()
- set wildoptions=fuzzy
- call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"!vim test_cmdline.vim', @:)
- set wildoptions&
- call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"!vim test_cmdline.vim', @:)
-endfunc
-
-func Check_completion()
- call assert_equal('let a', getcmdline())
- call assert_equal(6, getcmdpos())
- call assert_equal(7, getcmdscreenpos())
- call assert_equal('var', getcmdcompltype())
- return ''
-endfunc
-
-func Test_screenpos_and_completion()
- call feedkeys(":let a\<C-R>=Check_completion()\<CR>\<Esc>", "xt")
-endfunc
-
-func Test_recursive_register()
- let @= = ''
- silent! ?e/
- let caught = 'no'
- try
- normal //
- catch /E169:/
- let caught = 'yes'
- endtry
- call assert_equal('yes', caught)
-endfunc
-
-func Test_long_error_message()
- " the error should be truncated, not overrun IObuff
- silent! norm Q00000000000000     000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000                                                                                                                                                                                                                        
-endfunc
-
-func Test_cmdline_redraw_tabline()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set showtabline=2
- autocmd CmdlineEnter * set tabline=foo
- END
- call writefile(lines, 'Xcmdline_redraw_tabline')
- let buf = RunVimInTerminal('-S Xcmdline_redraw_tabline', #{rows: 6})
- call term_sendkeys(buf, ':')
- call WaitForAssert({-> assert_match('^foo', term_getline(buf, 1))})
-
- call StopVimInTerminal(buf)
- call delete('Xcmdline_redraw_tabline')
-endfunc
-
-func Test_wildmenu_pum_disable_while_shown()
- set wildoptions=pum
- set wildmenu
- cnoremap <F2> <Cmd>set nowildmenu<CR>
- call feedkeys(":sign \<Tab>\<F2>\<Esc>", 'tx')
- call assert_equal(0, pumvisible())
- cunmap <F2>
- set wildoptions& wildmenu&
-endfunc
-
-func Test_setcmdline()
- func SetText(text, pos)
- autocmd CmdlineChanged * let g:cmdtype = expand('<afile>')
- call assert_equal(0, setcmdline(a:text))
- call assert_equal(a:text, getcmdline())
- call assert_equal(len(a:text) + 1, getcmdpos())
- call assert_equal(getcmdtype(), g:cmdtype)
- unlet g:cmdtype
- autocmd! CmdlineChanged
-
- call assert_equal(0, setcmdline(a:text, a:pos))
- call assert_equal(a:text, getcmdline())
- call assert_equal(a:pos, getcmdpos())
-
- call assert_fails('call setcmdline("' .. a:text .. '", -1)', 'E487:')
- call assert_fails('call setcmdline({}, 0)', 'E1174:')
- call assert_fails('call setcmdline("' .. a:text .. '", {})', 'E1210:')
-
- return ''
- endfunc
-
- call feedkeys(":\<C-R>=SetText('set rtp?', 2)\<CR>\<CR>", 'xt')
- call assert_equal('set rtp?', @:)
-
- call feedkeys(":let g:str = input('? ')\<CR>", 't')
- call feedkeys("\<C-R>=SetText('foo', 4)\<CR>\<CR>", 'xt')
- call assert_equal('foo', g:str)
- unlet g:str
-
- delfunc SetText
-
- " setcmdline() returns 1 when not editing the command line.
- call assert_equal(1, 'foo'->setcmdline())
-
- " Called in custom function
- func CustomComplete(A, L, P)
- call assert_equal(0, setcmdline("DoCmd "))
- return "January\nFebruary\nMars\n"
- endfunc
-
- com! -nargs=* -complete=custom,CustomComplete DoCmd :
- call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd January February Mars', @:)
- delcom DoCmd
- delfunc CustomComplete
-
- " Called in <expr>
- cnoremap <expr>a setcmdline('let foo=')
- call feedkeys(":a\<CR>", 'tx')
- call assert_equal('let foo=0', @:)
- cunmap a
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim
deleted file mode 100644
index 55b230373f..0000000000
--- a/src/nvim/testdir/test_command_count.vim
+++ /dev/null
@@ -1,196 +0,0 @@
-" Test for user command counts.
-
-func Test_command_count_0()
- let bufnr = bufnr('%')
- set hidden
- set noswapfile
-
- split DoesNotExistEver
- let lastbuf = bufnr('$')
- call setline(1, 'asdf')
- quit!
-
- command! -range -addr=loaded_buffers RangeLoadedBuffers :let lines = [<line1>, <line2>]
- command! -range=% -addr=loaded_buffers RangeLoadedBuffersAll :let lines = [<line1>, <line2>]
- command! -range -addr=buffers RangeBuffers :let lines = [<line1>, <line2>]
- command! -range=% -addr=buffers RangeBuffersAll :let lines = [<line1>, <line2>]
-
- .,$RangeLoadedBuffers
- call assert_equal([bufnr, bufnr], lines)
- %RangeLoadedBuffers
- call assert_equal([bufnr, bufnr], lines)
- RangeLoadedBuffersAll
- call assert_equal([bufnr, bufnr], lines)
- .,$RangeBuffers
- call assert_equal([bufnr, lastbuf], lines)
- %RangeBuffers
- call assert_equal([bufnr, lastbuf], lines)
- RangeBuffersAll
- call assert_equal([bufnr, lastbuf], lines)
-
- delcommand RangeLoadedBuffers
- delcommand RangeLoadedBuffersAll
- delcommand RangeBuffers
- delcommand RangeBuffersAll
-
- set hidden&
- set swapfile&
-endfunc
-
-func Test_command_count_1()
- silent! %argd
- arga a b c d e
- argdo echo "loading buffers"
- argu 3
- command! -range -addr=arguments RangeArguments :let lines = [<line1>, <line2>]
- command! -range=% -addr=arguments RangeArgumentsAll :let lines = [<line1>, <line2>]
- .-,$-RangeArguments
- call assert_equal([2, 4], lines)
- %RangeArguments
- call assert_equal([1, 5], lines)
- RangeArgumentsAll
- call assert_equal([1, 5], lines)
- N
- .RangeArguments
- call assert_equal([2, 2], lines)
- delcommand RangeArguments
- delcommand RangeArgumentsAll
-
- split|split|split|split
- 3wincmd w
- command! -range -addr=windows RangeWindows :let lines = [<line1>, <line2>]
- .,$RangeWindows
- call assert_equal([3, 5], lines)
- %RangeWindows
- call assert_equal([1, 5], lines)
- delcommand RangeWindows
-
- command! -range=% -addr=windows RangeWindowsAll :let lines = [<line1>, <line2>]
- RangeWindowsAll
- call assert_equal([1, 5], lines)
- delcommand RangeWindowsAll
- only
- blast|bd
-
- tabe|tabe|tabe|tabe
- normal 2gt
- command! -range -addr=tabs RangeTabs :let lines = [<line1>, <line2>]
- .,$RangeTabs
- call assert_equal([2, 5], lines)
- %RangeTabs
- call assert_equal([1, 5], lines)
- delcommand RangeTabs
-
- command! -range=% -addr=tabs RangeTabsAll :let lines = [<line1>, <line2>]
- RangeTabsAll
- call assert_equal([1, 5], lines)
- delcommand RangeTabsAll
- 1tabonly
-
- s/\n/\r\r\r\r\r/
- 2ma<
- $-ma>
- command! -range=% RangeLines :let lines = [<line1>, <line2>]
- '<,'>RangeLines
- call assert_equal([2, 5], lines)
- delcommand RangeLines
-
- command! -range=% -buffer LocalRangeLines :let lines = [<line1>, <line2>]
- '<,'>LocalRangeLines
- call assert_equal([2, 5], lines)
- delcommand LocalRangeLines
-endfunc
-
-func Test_command_count_2()
- silent! %argd
- arga a b c d
- call assert_fails('5argu', 'E16:')
-
- $argu
- call assert_equal('d', expand('%:t'))
-
- 1argu
- call assert_equal('a', expand('%:t'))
-
- call assert_fails('300b', 'E16:')
-
- split|split|split|split
- 0close
-
- $wincmd w
- $close
- call assert_equal(3, winnr())
-
- call assert_fails('$+close', 'E16:')
-
- $tabe
- call assert_equal(2, tabpagenr())
-
- call assert_fails('$+tabe', 'E16:')
-
- only!
- e x
- 0tabm
- normal 1gt
- call assert_equal('x', expand('%:t'))
-
- tabonly!
- only!
-endfunc
-
-func Test_command_count_3()
- let bufnr = bufnr('%')
- se nohidden
- e aaa
- let buf_aaa = bufnr('%')
- e bbb
- let buf_bbb = bufnr('%')
- e ccc
- let buf_ccc = bufnr('%')
- exe bufnr . 'buf'
- call assert_equal([1, 1, 1], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)])
- exe buf_bbb . "," . buf_ccc . "bdelete"
- call assert_equal([1, 0, 0], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)])
- exe buf_aaa . "bdelete"
- call assert_equal([0, 0, 0], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)])
-endfunc
-
-func Test_command_count_4()
- %argd
- let bufnr = bufnr('$')
- next aa bb cc dd ee ff
- call assert_equal(bufnr, bufnr('%'))
-
- 3argu
- let args = []
- .,$-argdo call add(args, expand('%'))
- call assert_equal(['cc', 'dd', 'ee'], args)
-
- " create windows to get 5
- split|split|split|split
- 2wincmd w
- let windows = []
- .,$-windo call add(windows, winnr())
- call assert_equal([2, 3, 4], windows)
- only!
-
- exe bufnr . 'buf'
- let bufnr = bufnr('%')
- let buffers = []
- .,$-bufdo call add(buffers, bufnr('%'))
- call assert_equal([bufnr, bufnr + 1, bufnr + 2, bufnr + 3, bufnr + 4], buffers)
-
- exe (bufnr + 3) . 'bdel'
- let buffers = []
- exe (bufnr + 2) . ',' . (bufnr + 5) . "bufdo call add(buffers, bufnr('%'))"
- call assert_equal([bufnr + 2, bufnr + 4, bufnr + 5], buffers)
-
- " create tabpages to get 5
- tabe|tabe|tabe|tabe
- normal! 2gt
- let tabpages = []
- .,$-tabdo call add(tabpages, tabpagenr())
- call assert_equal([2, 3, 4], tabpages)
- tabonly!
- bwipe!
-endfunc
diff --git a/src/nvim/testdir/test_comments.vim b/src/nvim/testdir/test_comments.vim
deleted file mode 100644
index c34b85c42d..0000000000
--- a/src/nvim/testdir/test_comments.vim
+++ /dev/null
@@ -1,277 +0,0 @@
-" Tests for the various flags in the 'comments' option
-
-" Test for the 'n' flag in 'comments'
-func Test_comment_nested()
- new
- setlocal comments=n:> fo+=ro
- exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH"
- exe "normal 5GOE\<C-C>6GoG"
- let expected =<< trim END
- > A
- > B
- > C
- > D
- >>>> E
- >>>> F
- >>>> G
- >>>> H
- END
- call assert_equal(expected, getline(1, '$'))
- close!
-endfunc
-
-" Test for the 'b' flag in 'comments'
-func Test_comment_blank()
- new
- setlocal comments=b:* fo+=ro
- exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD"
- let expected =<< trim END
- A
- *B
- * C
- * D
- * E
- * F
- *G
- H
- END
- call assert_equal(expected, getline(1, '$'))
- close!
-endfunc
-
-" Test for the 'f' flag in 'comments' (only the first line has a comment
-" string)
-func Test_comment_firstline()
- new
- setlocal comments=f:- fo+=ro
- exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>"
- call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$'))
- %d
- setlocal comments=:-
- exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>"
- call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$'))
- close!
-endfunc
-
-" Test for the 's', 'm' and 'e' flags in 'comments'
-" Test for automatically adding comment leaders in insert mode
-func Test_comment_threepiece()
- new
- setlocal expandtab
- call setline(1, ["\t/*"])
- setlocal formatoptions=croql
- call cursor(1, 3)
- call feedkeys("A\<cr>\<cr>/", 'tnix')
- call assert_equal(["\t/*", " *", " */"], getline(1, '$'))
-
- " If a comment ends in a single line, then don't add it in the next line
- %d
- call setline(1, '/* line1 */')
- call feedkeys("A\<CR>next line", 'xt')
- call assert_equal(['/* line1 */', 'next line'], getline(1, '$'))
-
- %d
- " Copy the trailing indentation from the leader comment to a new line
- setlocal autoindent noexpandtab
- call feedkeys("a\t/*\tone\ntwo\n/", 'xt')
- call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$'))
- close!
-endfunc
-
-" Test for the 'r' flag in 'comments' (right align comment)
-func Test_comment_rightalign()
- new
- setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro
- exe "normal i=\<C-C>o\t /***\nD\n/"
- exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG"
- let expected =<< trim END
- =
- A
- /***
- ** B
- ** C
- ** D
- ** E
- ** F
- ******/
- G
- END
- call assert_equal(expected, getline(1, '$'))
- close!
-endfunc
-
-" Test for the 'O' flag in 'comments'
-func Test_comment_O()
- new
- setlocal comments=Ob:* fo+=ro
- exe "normal i* B\nD\<C-C>kOA\<C-C>joC"
- let expected =<< trim END
- A
- * B
- * C
- * D
- END
- call assert_equal(expected, getline(1, '$'))
- close!
-endfunc
-
-" Test for using a multibyte character as a comment leader
-func Test_comment_multibyte_leader()
- 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&
- close!
-endfunc
-
-" Test for a space character in 'comments' setting
-func Test_comment_space()
- new
- setlocal comments=b:\ > fo+=ro
- exe "normal i> B\nD\<C-C>ggOA\<C-C>joC"
- exe "normal Go > F\nH\<C-C>kOE\<C-C>joG"
- let expected =<< trim END
- A
- > B
- C
- D
- > E
- > F
- > G
- > H
- END
- call assert_equal(expected, getline(1, '$'))
- close!
-endfunc
-
-" Test for formatting lines with and without comments
-func Test_comment_format_lines()
- new
- call setline(1, ['one', '/* two */', 'three'])
- normal gggqG
- call assert_equal(['one', '/* two */', 'three'], getline(1, '$'))
- close!
-endfunc
-
-" Test for using 'a' in 'formatoptions' with comments
-func Test_comment_autoformat()
- new
- setlocal formatoptions+=a
- call feedkeys("a- one\n- two\n", 'xt')
- call assert_equal(['- one', '- two', ''], getline(1, '$'))
-
- %d
- call feedkeys("a\none\n", 'xt')
- call assert_equal(['', 'one', ''], getline(1, '$'))
-
- setlocal formatoptions+=aw
- %d
- call feedkeys("aone \ntwo\n", 'xt')
- call assert_equal(['one two', ''], getline(1, '$'))
-
- %d
- call feedkeys("aone\ntwo\n", 'xt')
- call assert_equal(['one', 'two', ''], getline(1, '$'))
-
- close!
-endfunc
-
-" Test for joining lines with comments ('j' flag in 'formatoptions')
-func Test_comment_join_lines_fo_j()
- new
- setlocal fo+=j comments=://
- call setline(1, ['i++; // comment1', ' // comment2'])
- normal J
- call assert_equal('i++; // comment1 comment2', getline(1))
- setlocal fo-=j
- call setline(1, ['i++; // comment1', ' // comment2'])
- normal J
- call assert_equal('i++; // comment1 // comment2', getline(1))
- " Test with nested comments
- setlocal fo+=j comments=n:>,n:)
- call setline(1, ['i++; > ) > ) comment1', ' > ) comment2'])
- normal J
- call assert_equal('i++; > ) > ) comment1 comment2', getline(1))
- close!
-endfunc
-
-" Test for formatting lines where only the first line has a comment.
-func Test_comment_format_firstline_comment()
- new
- setlocal formatoptions=tcq
- call setline(1, ['- one two', 'three'])
- normal gggqG
- call assert_equal(['- one two three'], getline(1, '$'))
-
- %d
- call setline(1, ['- one', '- two'])
- normal gggqG
- call assert_equal(['- one', '- two'], getline(1, '$'))
- close!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_comparators.vim b/src/nvim/testdir/test_comparators.vim
deleted file mode 100644
index 87be006cf2..0000000000
--- a/src/nvim/testdir/test_comparators.vim
+++ /dev/null
@@ -1,9 +0,0 @@
-function Test_Comparators()
- try
- let oldisident=&isident
- set isident+=#
- call assert_equal(1, 1 is#1)
- finally
- let &isident=oldisident
- endtry
-endfunction
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
deleted file mode 100644
index ec7d143030..0000000000
--- a/src/nvim/testdir/test_compiler.vim
+++ /dev/null
@@ -1,78 +0,0 @@
-" Test the :compiler command
-
-source check.vim
-source shared.vim
-
-func Test_compiler()
- CheckExecutable perl
- CheckFeature quickfix
-
- " $LANG changes the output of Perl.
- if $LANG != ''
- 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)
- call assert_fails('let g:current_compiler', 'E121:')
-
- let verbose_efm = execute('verbose set efm')
- call assert_match('Last set from .*[/\\]compiler[/\\]perl.vim ', verbose_efm)
-
- call setline(1, ['#!/usr/bin/perl -w', 'use strict;', 'my $foo=1'])
- w!
- call feedkeys(":make\<CR>\<CR>", 'tx')
- call assert_fails('clist', 'E42:')
-
- call setline(1, ['#!/usr/bin/perl -w', 'use strict;', '$foo=1'])
- w!
- call feedkeys(":make\<CR>\<CR>", 'tx')
- let a=execute('clist')
- 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 GetCompilerNames()
- return glob('$VIMRUNTIME/compiler/*.vim', 0, 1)
- \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})
- \ ->sort()
-endfunc
-
-func Test_compiler_without_arg()
- let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
- let a = split(execute('compiler'))
- let exp = GetCompilerNames()
- call assert_match(runtime .. '/compiler/' .. exp[0] .. '.vim$', a[0])
- call assert_match(runtime .. '/compiler/' .. exp[1] .. '.vim$', a[1])
- call assert_match(runtime .. '/compiler/' .. exp[-1] .. '.vim$', a[-1])
-endfunc
-
-func Test_compiler_completion()
- let clist = GetCompilerNames()->join(' ')
- call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"compiler ' .. clist .. '$', @:)
-
- call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('"compiler pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:)
-
- call feedkeys(":compiler! p\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('"compiler! pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:)
-endfunc
-
-func Test_compiler_error()
- let g:current_compiler = 'abc'
- call assert_fails('compiler doesnotexist', 'E666:')
- call assert_equal('abc', g:current_compiler)
- call assert_fails('compiler! doesnotexist', 'E666:')
- unlet! g:current_compiler
-endfunc
diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim
deleted file mode 100644
index bffc2f49d3..0000000000
--- a/src/nvim/testdir/test_conceal.vim
+++ /dev/null
@@ -1,284 +0,0 @@
-" Tests for 'conceal'.
-
-source check.vim
-CheckFeature conceal
-
-source screendump.vim
-
-func Test_conceal_two_windows()
- CheckScreendump
-
- let code =<< trim [CODE]
- let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"]
- call setline(1, lines)
- syntax match test /|hidden|/ conceal
- set conceallevel=2
- set concealcursor=
- exe "normal /here\r"
- new
- call setline(1, lines)
- call setline(4, "Second window")
- syntax match test /|hidden|/ conceal
- set conceallevel=2
- set concealcursor=nc
- exe "normal /here\r"
- [CODE]
-
- call writefile(code, 'XTest_conceal')
- " Check that cursor line is concealed
- let buf = RunVimInTerminal('-S XTest_conceal', {})
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_01', {})
-
- " Check that with concealed text vertical cursor movement is correct.
- call term_sendkeys(buf, "k")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_02', {})
-
- " Check that with cursor line is not concealed
- call term_sendkeys(buf, "j")
- call term_sendkeys(buf, ":set concealcursor=\r")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_03', {})
-
- " Check that with cursor line is not concealed when moving cursor down
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_04', {})
-
- " Check that with cursor line is not concealed when switching windows
- call term_sendkeys(buf, "\<C-W>\<C-W>")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_05', {})
-
- " Check that with cursor line is only concealed in Normal mode
- call term_sendkeys(buf, ":set concealcursor=n\r")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_06n', {})
- call term_sendkeys(buf, "a")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_06i', {})
- call term_sendkeys(buf, "\<Esc>/e")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_06c', {})
- call term_sendkeys(buf, "\<Esc>v")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_06v', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Check that with cursor line is only concealed in Insert mode
- call term_sendkeys(buf, ":set concealcursor=i\r")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_07n', {})
- call term_sendkeys(buf, "a")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_07i', {})
- call term_sendkeys(buf, "\<Esc>/e")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_07c', {})
- call term_sendkeys(buf, "\<Esc>v")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_07v', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Check that with cursor line is only concealed in Command mode
- call term_sendkeys(buf, ":set concealcursor=c\r")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_08n', {})
- call term_sendkeys(buf, "a")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_08i', {})
- call term_sendkeys(buf, "\<Esc>/e")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_08c', {})
- call term_sendkeys(buf, "\<Esc>v")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_08v', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Check that with cursor line is only concealed in Visual mode
- call term_sendkeys(buf, ":set concealcursor=v\r")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_09n', {})
- call term_sendkeys(buf, "a")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_09i', {})
- call term_sendkeys(buf, "\<Esc>/e")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_09c', {})
- call term_sendkeys(buf, "\<Esc>v")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_09v', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Check moving the cursor while in insert mode.
- call term_sendkeys(buf, ":set concealcursor=\r")
- call term_sendkeys(buf, "a")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_10', {})
- call term_sendkeys(buf, "\<Down>")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_11', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Check the "o" command
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_12', {})
- call term_sendkeys(buf, "o")
- call VerifyScreenDump(buf, 'Test_conceal_two_windows_13', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_conceal')
-endfunc
-
-func Test_conceal_with_cursorline()
- CheckScreendump
-
- " Opens a help window, where 'conceal' is set, switches to the other window
- " where 'cursorline' needs to be updated when the cursor moves.
- let code =<< trim [CODE]
- set cursorline
- normal othis is a test
- new
- call setline(1, ["one", "two", "three", "four", "five"])
- set ft=help
- normal M
- [CODE]
-
- call writefile(code, 'XTest_conceal_cul')
- let buf = RunVimInTerminal('-S XTest_conceal_cul', {})
- call VerifyScreenDump(buf, 'Test_conceal_cul_01', {})
-
- call term_sendkeys(buf, ":wincmd w\r")
- call VerifyScreenDump(buf, 'Test_conceal_cul_02', {})
-
- call term_sendkeys(buf, "k")
- call VerifyScreenDump(buf, 'Test_conceal_cul_03', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_conceal_cul')
-endfunc
-
-func Test_conceal_resize_term()
- CheckScreendump
-
- let code =<< trim [CODE]
- call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed')
- setl cocu=n cole=3
- syn region CommentCodeSpan matchgroup=Comment start=/`/ end=/`/ concealends
- normal fb
- [CODE]
- call writefile(code, 'XTest_conceal_resize')
- let buf = RunVimInTerminal('-S XTest_conceal_resize', {'rows': 6})
- call VerifyScreenDump(buf, 'Test_conceal_resize_01', {})
-
- call win_execute(buf->win_findbuf()[0], 'wincmd +')
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_conceal_resize_02', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_conceal_resize')
-endfunc
-
-" Tests for correct display (cursor column position) with +conceal and
-" tabulators. Need to run this test in a separate Vim instance. Otherwise the
-" screen is not updated (lazy redraw) and the cursor position is wrong.
-func Test_conceal_cursor_pos()
- let code =<< trim [CODE]
- :let l = ['start:', '.concealed. text', "|concealed|\ttext"]
- :let l += ['', "\t.concealed.\ttext", "\t|concealed|\ttext", '']
- :let l += [".a.\t.b.\t.c.\t.d.", "|a|\t|b|\t|c|\t|d|"]
- :call append(0, l)
- :call cursor(1, 1)
- :" Conceal settings.
- :set conceallevel=2
- :set concealcursor=nc
- :syntax match test /|/ conceal
- :" Save current cursor position. Only works in <expr> mode, can't be used
- :" with :normal because it moves the cursor to the command line. Thanks
- :" to ZyX <zyx.vim@gmail.com> for the idea to use an <expr> mapping.
- :let curpos = []
- :nnoremap <expr> GG ":let curpos += ['".screenrow().":".screencol()."']\n"
- :normal ztj
- GGk
- :" We should end up in the same column when running these commands on the
- :" two lines.
- :normal ft
- GGk
- :normal $
- GGk
- :normal 0j
- GGk
- :normal ft
- GGk
- :normal $
- GGk
- :normal 0j0j
- GGk
- :" Same for next test block.
- :normal ft
- GGk
- :normal $
- GGk
- :normal 0j
- GGk
- :normal ft
- GGk
- :normal $
- GGk
- :normal 0j0j
- GGk
- :" And check W with multiple tabs and conceals in a line.
- :normal W
- GGk
- :normal W
- GGk
- :normal W
- GGk
- :normal $
- GGk
- :normal 0j
- GGk
- :normal W
- GGk
- :normal W
- GGk
- :normal W
- GGk
- :normal $
- GGk
- :set lbr
- :normal $
- GGk
- :set list listchars=tab:>-
- :normal 0
- GGk
- :normal W
- GGk
- :normal W
- GGk
- :normal W
- GGk
- :normal $
- GGk
- :call writefile(curpos, 'Xconceal_curpos.out')
- :q!
-
- [CODE]
- call writefile(code, 'XTest_conceal_curpos')
-
- if RunVim([], [], '-s XTest_conceal_curpos')
- call assert_equal([
- \ '2:1', '2:17', '2:20', '3:1', '3:17', '3:20', '5:8', '5:25',
- \ '5:28', '6:8', '6:25', '6:28', '8:1', '8:9', '8:17', '8:25',
- \ '8:27', '9:1', '9:9', '9:17', '9:25', '9:26', '9:26', '9:1',
- \ '9:9', '9:17', '9:25', '9:26'], readfile('Xconceal_curpos.out'))
- endif
-
- call delete('Xconceal_curpos.out')
- call delete('XTest_conceal_curpos')
-endfunc
-
-func Test_conceal_eol()
- new!
- setlocal concealcursor=n conceallevel=1
- call setline(1, ["x", ""])
- call matchaddpos('Conceal', [[2, 1, 1]], 2, -1, {'conceal': 1})
- redraw!
-
- call assert_notequal(screenchar(1, 1), screenchar(2, 2))
- call assert_equal(screenattr(1, 1), screenattr(1, 2))
- call assert_equal(screenattr(1, 2), screenattr(2, 2))
- call assert_equal(screenattr(2, 1), screenattr(2, 2))
-
- set list
- redraw!
-
- call assert_equal(screenattr(1, 1), screenattr(2, 2))
- call assert_notequal(screenattr(1, 1), screenattr(1, 2))
- call assert_notequal(screenattr(1, 2), screenattr(2, 1))
-
- set nolist
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim
deleted file mode 100644
index 4a6c8779dd..0000000000
--- a/src/nvim/testdir/test_const.vim
+++ /dev/null
@@ -1,294 +0,0 @@
-
-" Test for :const
-
-func s:noop()
-endfunc
-
-func Test_define_var_with_lock()
- const i = 1
- const f = 1.1
- const s = 'vim'
- const F = funcref('s:noop')
- const l = [1, 2, 3]
- const d = {'foo': 10}
- if has('channel')
- const j = test_null_job()
- const c = test_null_channel()
- endif
- const b = v:true
- const n = v:null
-
- call assert_true(exists('i'))
- call assert_true(exists('f'))
- call assert_true(exists('s'))
- call assert_true(exists('F'))
- call assert_true(exists('l'))
- call assert_true(exists('d'))
- if has('channel')
- call assert_true(exists('j'))
- call assert_true(exists('c'))
- endif
- call assert_true(exists('b'))
- call assert_true(exists('n'))
-
- call assert_fails('let i = 1', 'E741:')
- call assert_fails('let f = 1.1', 'E741:')
- call assert_fails('let s = "vim"', 'E741:')
- call assert_fails('let F = funcref("s:noop")', 'E741:')
- call assert_fails('let l = [1, 2, 3]', 'E741:')
- call assert_fails('call filter(l, "v:val % 2 == 0")', 'E741:')
- call assert_fails('let d = {"foo": 10}', 'E741:')
- if has('channel')
- call assert_fails('let j = test_null_job()', 'E741:')
- call assert_fails('let c = test_null_channel()', 'E741:')
- endif
- call assert_fails('let b = v:true', 'E741:')
- call assert_fails('let n = v:null', 'E741:')
-
- " Unlet
- unlet i
- unlet f
- unlet s
- unlet F
- unlet l
- unlet d
- if has('channel')
- unlet j
- unlet c
- endif
- unlet b
- unlet n
-endfunc
-
-func Test_define_l_var_with_lock()
- " With l: prefix
- const l:i = 1
- const l:f = 1.1
- const l:s = 'vim'
- const l:F = funcref('s:noop')
- const l:l = [1, 2, 3]
- const l:d = {'foo': 10}
- if has('channel')
- const l:j = test_null_job()
- const l:c = test_null_channel()
- endif
- const l:b = v:true
- const l:n = v:null
-
- call assert_fails('let l:i = 1', 'E741:')
- call assert_fails('let l:f = 1.1', 'E741:')
- call assert_fails('let l:s = "vim"', 'E741:')
- call assert_fails('let l:F = funcref("s:noop")', 'E741:')
- call assert_fails('let l:l = [1, 2, 3]', 'E741:')
- call assert_fails('let l:d = {"foo": 10}', 'E741:')
- if has('channel')
- call assert_fails('let l:j = test_null_job()', 'E741:')
- call assert_fails('let l:c = test_null_channel()', 'E741:')
- endif
- call assert_fails('let l:b = v:true', 'E741:')
- call assert_fails('let l:n = v:null', 'E741:')
-
- " Unlet
- unlet l:i
- unlet l:f
- unlet l:s
- unlet l:F
- unlet l:l
- unlet l:d
- if has('channel')
- unlet l:j
- unlet l:c
- endif
- unlet l:b
- unlet l:n
-endfunc
-
-func Test_define_script_var_with_lock()
- const s:x = 0
- call assert_fails('let s:x = 1', 'E741:')
- unlet s:x
-endfunc
-
-func Test_destructuring_with_lock()
- const [a, b, c] = [1, 1.1, 'vim']
-
- call assert_fails('let a = 1', 'E741:')
- call assert_fails('let b = 1.1', 'E741:')
- call assert_fails('let c = "vim"', 'E741:')
-
- const [d; e] = [1, 1.1, 'vim']
- call assert_fails('let d = 1', 'E741:')
- call assert_fails('let e = [2.2, "a"]', 'E741:')
-endfunc
-
-func Test_cannot_modify_existing_variable()
- let i = 1
- let f = 1.1
- let s = 'vim'
- let F = funcref('s:noop')
- let l = [1, 2, 3]
- let d = {'foo': 10}
- if has('channel')
- let j = test_null_job()
- let c = test_null_channel()
- endif
- let b = v:true
- let n = v:null
-
- call assert_fails('const i = 1', 'E995:')
- call assert_fails('const f = 1.1', 'E995:')
- call assert_fails('const s = "vim"', 'E995:')
- call assert_fails('const F = funcref("s:noop")', 'E995:')
- call assert_fails('const l = [1, 2, 3]', 'E995:')
- call assert_fails('const d = {"foo": 10}', 'E995:')
- if has('channel')
- call assert_fails('const j = test_null_job()', 'E995:')
- call assert_fails('const c = test_null_channel()', 'E995:')
- endif
- call assert_fails('const b = v:true', 'E995:')
- call assert_fails('const n = v:null', 'E995:')
- call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:')
-
- const i2 = 1
- const f2 = 1.1
- const s2 = 'vim'
- const F2 = funcref('s:noop')
- const l2 = [1, 2, 3]
- const d2 = {'foo': 10}
- if has('channel')
- const j2 = test_null_job()
- const c2 = test_null_channel()
- endif
- const b2 = v:true
- const n2 = v:null
-
- call assert_fails('const i2 = 1', 'E995:')
- call assert_fails('const f2 = 1.1', 'E995:')
- call assert_fails('const s2 = "vim"', 'E995:')
- call assert_fails('const F2 = funcref("s:noop")', 'E995:')
- call assert_fails('const l2 = [1, 2, 3]', 'E995:')
- call assert_fails('const d2 = {"foo": 10}', 'E995:')
- if has('channel')
- call assert_fails('const j2 = test_null_job()', 'E995:')
- call assert_fails('const c2 = test_null_channel()', 'E995:')
- endif
- call assert_fails('const b2 = v:true', 'E995:')
- call assert_fails('const n2 = v:null', 'E995:')
- call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
-endfunc
-
-func Test_const_with_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'
-
- let val = [1, 2, 3]
- lockvar 0 val
- let val[0] = 9
- call assert_equal([9, 2, 3], val)
- call add(val, 4)
- call assert_equal([9, 2, 3, 4], val)
- call assert_fails('let val = [4, 5, 6]', 'E1122:')
-endfunc
-
-
-func Test_const_with_index_access()
- let l = [1, 2, 3]
- call assert_fails('const l[0] = 4', 'E996:')
- call assert_fails('const l[0:1] = [1, 2]', 'E996:')
-
- let d = {'aaa': 0}
- call assert_fails("const d['aaa'] = 4", 'E996:')
- call assert_fails("const d.aaa = 4", 'E996:')
-endfunc
-
-func Test_const_with_compound_assign()
- let i = 0
- call assert_fails('const i += 4', 'E995:')
- call assert_fails('const i -= 4', 'E995:')
- call assert_fails('const i *= 4', 'E995:')
- call assert_fails('const i /= 4', 'E995:')
- call assert_fails('const i %= 4', 'E995:')
-
- let s = 'a'
- call assert_fails('const s .= "b"', 'E995:')
-
- let [a, b, c] = [1, 2, 3]
- call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:')
-
- let [d; e] = [1, 2, 3]
- call assert_fails('const [d; e] += [4, 5, 6]', 'E995:')
-endfunc
-
-func Test_const_with_special_variables()
- call assert_fails('const $FOO = "hello"', 'E996:')
- call assert_fails('const @a = "hello"', 'E996:')
- call assert_fails('const &filetype = "vim"', 'E996:')
- call assert_fails('const &l:filetype = "vim"', 'E996:')
- call assert_fails('const &g:encoding = "utf-8"', 'E996:')
-
- call assert_fails('const [a, $CONST_FOO] = [369, "abc"]', 'E996:')
- call assert_equal(369, a)
- call assert_equal(v:null, getenv("CONST_FOO"))
-
- call assert_fails('const [b; $CONST_FOO] = [246, 2, "abc"]', 'E996:')
- call assert_equal(246, b)
- call assert_equal(v:null, getenv("CONST_FOO"))
-endfunc
-
-func Test_const_with_eval_name()
- let s = 'foo'
-
- " eval name with :const should work
- const abc_{s} = 1
- const {s}{s} = 1
-
- let s2 = 'abc_foo'
- call assert_fails('const {s2} = "bar"', 'E995:')
-endfunc
-
-func Test_lock_depth_is_2()
- " Modify list - error when changing item or adding/removing items
- const l = [1, 2, [3, 4]]
- call assert_fails('let l[0] = 42', 'E741:')
- call assert_fails('let l[2][0] = 42', 'E741:')
- call assert_fails('call add(l, 4)', 'E741:')
- call assert_fails('unlet l[1]', 'E741:')
-
- " Modify blob - error when changing
- const b = 0z001122
- call assert_fails('let b[0] = 42', 'E741:')
-
- " Modify dict - error when changing item or adding/removing items
- const d = {'foo': 10}
- call assert_fails("let d['foo'] = 'hello'", 'E741:')
- call assert_fails("let d.foo = 'hello'", 'E741:')
- call assert_fails("let d['bar'] = 'hello'", 'E741:')
- call assert_fails("unlet d['foo']", 'E741:')
-
- " Modifying list or dict item contents is OK.
- let lvar = ['a', 'b']
- let bvar = 0z1122
- const l2 = [0, lvar, bvar]
- let l2[1][0] = 'c'
- let l2[2][1] = 0x33
- call assert_equal([0, ['c', 'b'], 0z1133], l2)
-
- const d2 = #{a: 0, b: lvar, c: 4}
- let d2.b[1] = 'd'
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cpoptions.vim b/src/nvim/testdir/test_cpoptions.vim
deleted file mode 100644
index b9307ab30b..0000000000
--- a/src/nvim/testdir/test_cpoptions.vim
+++ /dev/null
@@ -1,927 +0,0 @@
-" Test for the various 'cpoptions' (cpo) flags
-
-source check.vim
-source shared.vim
-source view_util.vim
-
-" Test for the 'a' flag in 'cpo'. Reading a file should set the alternate
-" file name.
-func Test_cpo_a()
- let save_cpo = &cpo
- call writefile(['one'], 'XfileCpoA')
- " Wipe out all the buffers, so that the alternate file is empty
- edit Xfoo | %bw
- set cpo-=a
- new
- read XfileCpoA
- call assert_equal('', @#)
- %d
- set cpo+=a
- read XfileCpoA
- call assert_equal('XfileCpoA', @#)
- close!
- call delete('XfileCpoA')
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'A' flag in 'cpo'. Writing a file should set the alternate
-" file name.
-func Test_cpo_A()
- let save_cpo = &cpo
- " Wipe out all the buffers, so that the alternate file is empty
- edit Xfoo | %bw
- set cpo-=A
- new Xfile1
- write Xfile2
- call assert_equal('', @#)
- %bw
- call delete('Xfile2')
- new Xfile1
- set cpo+=A
- write Xfile2
- call assert_equal('Xfile2', @#)
- close!
- call delete('Xfile2')
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'b' flag in 'cpo'. "\|" at the end of a map command is
-" recognized as the end of the map.
-func Test_cpo_b()
- let save_cpo = &cpo
- set cpo+=b
- nnoremap <F5> :pwd\<CR>\|let i = 1
- call assert_equal(':pwd\<CR>\', maparg('<F5>'))
- nunmap <F5>
- exe "nnoremap <F5> :pwd\<C-V>|let i = 1"
- call assert_equal(':pwd|let i = 1', maparg('<F5>'))
- nunmap <F5>
- set cpo-=b
- nnoremap <F5> :pwd\<CR>\|let i = 1
- call assert_equal(':pwd\<CR>|let i = 1', maparg('<F5>'))
- let &cpo = save_cpo
- nunmap <F5>
-endfunc
-
-" Test for the 'B' flag in 'cpo'. A backslash in mappings, abbreviations, user
-" commands and menu commands has no special meaning.
-func Test_cpo_B()
- let save_cpo = &cpo
- new
- set cpo-=B
- iabbr <buffer> abc ab\<BS>d
- exe "normal iabc "
- call assert_equal('ab<BS>d ', getline(1))
- %d
- set cpo+=B
- iabbr <buffer> abc ab\<BS>d
- exe "normal iabc "
- call assert_equal('abd ', getline(1))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'c' flag in 'cpo'.
-func Test_cpo_c()
- let save_cpo = &cpo
- set cpo+=c
- new
- call setline(1, ' abababababab')
- exe "normal gg/abab\<CR>"
- call assert_equal(3, searchcount().total)
- set cpo-=c
- exe "normal gg/abab\<CR>"
- call assert_equal(5, searchcount().total)
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'C' flag in 'cpo' (line continuation)
-func Test_cpo_C()
- let save_cpo = &cpo
- call writefile(['let l = [', '\ 1,', '\ 2]'], 'XfileCpoC')
- set cpo-=C
- source XfileCpoC
- call assert_equal([1, 2], g:l)
- set cpo+=C
- call assert_fails('source XfileCpoC', ['E697:', 'E10:'])
- call delete('XfileCpoC')
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'd' flag in 'cpo' (tags relative to the current file)
-func Test_cpo_d()
- let save_cpo = &cpo
- call mkdir('XdirCpoD')
- call writefile(["one\tXfile1\t/^one$/"], 'tags')
- call writefile(["two\tXfile2\t/^two$/"], 'XdirCpoD/tags')
- set tags=./tags
- set cpo-=d
- edit XdirCpoD/Xfile
- call assert_equal('two', taglist('.*')[0].name)
- set cpo+=d
- call assert_equal('one', taglist('.*')[0].name)
- %bw!
- call delete('tags')
- call delete('XdirCpoD', 'rf')
- set tags&
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'D' flag in 'cpo' (digraph after a r, f or t)
-func Test_cpo_D()
- CheckFeature digraphs
- let save_cpo = &cpo
- new
- set cpo-=D
- call setline(1, 'abcdefgh|')
- exe "norm! 1gg0f\<c-k>!!"
- call assert_equal(9, col('.'))
- set cpo+=D
- exe "norm! 1gg0f\<c-k>!!"
- call assert_equal(1, col('.'))
- set cpo-=D
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'e' flag in 'cpo'
-func Test_cpo_e()
- let save_cpo = &cpo
- let @a='let i = 45'
- set cpo+=e
- call feedkeys(":@a\<CR>", 'xt')
- call assert_equal(45, i)
- set cpo-=e
- call feedkeys(":@a\<CR>6\<CR>", 'xt')
- call assert_equal(456, i)
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'E' flag in 'cpo' with yank, change, delete, etc. operators
-func Test_cpo_E()
- new
- call setline(1, '')
- set cpo+=E
- " yank an empty line
- call assert_beeps('normal "ayl')
- " change an empty line
- call assert_beeps('normal lcTa')
- call assert_beeps('normal 0c0')
- " delete an empty line
- call assert_beeps('normal D')
- call assert_beeps('normal dl')
- call assert_equal('', getline(1))
- " change case of an empty line
- call assert_beeps('normal gul')
- call assert_beeps('normal gUl')
- " replace a character
- call assert_beeps('normal vrx')
- " increment and decrement
- call assert_beeps('exe "normal v\<C-A>"')
- call assert_beeps('exe "normal v\<C-X>"')
- set cpo-=E
- close!
-endfunc
-
-" Test for the 'f' flag in 'cpo' (read in an empty buffer sets the file name)
-func Test_cpo_f()
- let save_cpo = &cpo
- new
- set cpo-=f
- read test_cpoptions.vim
- call assert_equal('', @%)
- %d
- set cpo+=f
- read test_cpoptions.vim
- call assert_equal('test_cpoptions.vim', @%)
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'F' flag in 'cpo' (write in an empty buffer sets the file name)
-func Test_cpo_F()
- let save_cpo = &cpo
- new
- set cpo-=F
- write XfileCpoF
- call assert_equal('', @%)
- call delete('XfileCpoF')
- set cpo+=F
- write XfileCpoF
- call assert_equal('XfileCpoF', @%)
- close!
- call delete('XfileCpoF')
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'g' flag in 'cpo' (jump to line 1 when re-editing a file)
-func Test_cpo_g()
- throw 'Skipped: Nvim does not support cpoptions flag "g"'
- let save_cpo = &cpo
- new test_cpoptions.vim
- set cpo-=g
- normal 20G
- edit
- call assert_equal(20, line('.'))
- set cpo+=g
- edit
- call assert_equal(1, line('.'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions')
-func Test_cpo_H()
- throw 'Skipped: Nvim does not support cpoptions flag "H"'
- let save_cpo = &cpo
- new
- set cpo-=H
- call setline(1, ' ')
- normal! Ia
- call assert_equal(' a', getline(1))
- set cpo+=H
- call setline(1, ' ')
- normal! Ia
- call assert_equal(' a ', getline(1))
- close!
- let &cpo = save_cpo
-endfunc
-
-" TODO: Add a test for the 'i' flag in 'cpo'
-" Interrupting the reading of a file will leave it modified.
-
-" Test for the 'I' flag in 'cpo' (deleting autoindent when using arrow keys)
-func Test_cpo_I()
- let save_cpo = &cpo
- new
- setlocal autoindent
- set cpo+=I
- exe "normal i one\<CR>\<Up>"
- call assert_equal(' ', getline(2))
- set cpo-=I
- %d
- exe "normal i one\<CR>\<Up>"
- call assert_equal('', getline(2))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'j' flag in 'cpo' is in the test_join.vim file.
-
-" Test for the 'J' flag in 'cpo' (two spaces after a sentence)
-func Test_cpo_J()
- let save_cpo = &cpo
- new
- set cpo-=J
- call setline(1, 'one. two! three? four."'' five.)]')
- normal 0
- for colnr in [6, 12, 19, 28, 34]
- normal )
- call assert_equal(colnr, col('.'))
- endfor
- for colnr in [28, 19, 12, 6, 1]
- normal (
- call assert_equal(colnr, col('.'))
- endfor
- set cpo+=J
- normal 0
- for colnr in [12, 28, 34]
- normal )
- call assert_equal(colnr, col('.'))
- endfor
- for colnr in [28, 12, 1]
- normal (
- call assert_equal(colnr, col('.'))
- endfor
- close!
- let &cpo = save_cpo
-endfunc
-
-" TODO: Add a test for the 'k' flag in 'cpo'.
-" Disable the recognition of raw key codes in mappings, abbreviations, and the
-" "to" part of menu commands.
-
-" TODO: Add a test for the 'K' flag in 'cpo'.
-" Don't wait for a key code to complete when it is halfway a mapping.
-
-" Test for the 'l' flag in 'cpo' (backslash in a [] range)
-func Test_cpo_l()
- let save_cpo = &cpo
- new
- call setline(1, ['', "a\tc" .. '\t'])
- set cpo-=l
- exe 'normal gg/[\t]' .. "\<CR>"
- call assert_equal([2, 8], [col('.'), virtcol('.')])
- set cpo+=l
- exe 'normal gg/[\t]' .. "\<CR>"
- call assert_equal([4, 10], [col('.'), virtcol('.')])
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions')
-func Test_cpo_L()
- let save_cpo = &cpo
- new
- set cpo-=L
- call setline(1, 'abcdefghijklmnopqr')
- exe "normal 0gR\<Tab>"
- call assert_equal("\<Tab>ijklmnopqr", getline(1))
- set cpo+=L
- set list
- call setline(1, 'abcdefghijklmnopqr')
- exe "normal 0gR\<Tab>"
- call assert_equal("\<Tab>cdefghijklmnopqr", getline(1))
- set nolist
- call setline(1, 'abcdefghijklmnopqr')
- exe "normal 0gR\<Tab>"
- call assert_equal("\<Tab>ijklmnopqr", getline(1))
- close!
- let &cpo = save_cpo
-endfunc
-
-" TODO: Add a test for the 'm' flag in 'cpo'.
-" When included, a showmatch will always wait half a second. When not
-" included, a showmatch will wait half a second or until a character is typed.
-
-" Test for the 'M' flag in 'cpo' (% with escape parenthesis)
-func Test_cpo_M()
- let save_cpo = &cpo
- new
- call setline(1, ['( \( )', '\( ( \)'])
-
- set cpo-=M
- call cursor(1, 1)
- normal %
- call assert_equal(6, col('.'))
- call cursor(1, 4)
- call assert_beeps('normal %')
- call cursor(2, 2)
- normal %
- call assert_equal(7, col('.'))
- call cursor(2, 4)
- call assert_beeps('normal %')
-
- set cpo+=M
- call cursor(1, 4)
- normal %
- call assert_equal(6, col('.'))
- call cursor(1, 1)
- call assert_beeps('normal %')
- call cursor(2, 4)
- normal %
- call assert_equal(7, col('.'))
- call cursor(2, 1)
- call assert_beeps('normal %')
-
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'n' flag in 'cpo' (using number column for wrapped lines)
-func Test_cpo_n()
- let save_cpo = &cpo
- new
- call setline(1, repeat('a', &columns))
- setlocal number
- set cpo-=n
- redraw!
- call assert_equal(' aaaa', Screenline(2))
- set cpo+=n
- redraw!
- call assert_equal('aaaa', Screenline(2))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'o' flag in 'cpo' (line offset to search command)
-func Test_cpo_o()
- let save_cpo = &cpo
- new
- call setline(1, ['', 'one', 'two', 'three', 'one', 'two', 'three'])
- set cpo-=o
- exe "normal /one/+2\<CR>"
- normal n
- call assert_equal(7, line('.'))
- set cpo+=o
- exe "normal /one/+2\<CR>"
- normal n
- call assert_equal(5, line('.'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'O' flag in 'cpo' (overwriting an existing file)
-func Test_cpo_O()
- let save_cpo = &cpo
- new XfileCpoO
- call setline(1, 'one')
- call writefile(['two'], 'XfileCpoO')
- set cpo-=O
- call assert_fails('write', 'E13:')
- set cpo+=O
- write
- call assert_equal(['one'], readfile('XfileCpoO'))
- close!
- call delete('XfileCpoO')
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'p' flag in 'cpo' is in the test_lispwords.vim file.
-
-" Test for the 'P' flag in 'cpo' (appending to a file sets the current file
-" name)
-func Test_cpo_P()
- let save_cpo = &cpo
- call writefile([], 'XfileCpoP')
- new
- call setline(1, 'one')
- set cpo+=F
- set cpo-=P
- write >> XfileCpoP
- call assert_equal('', @%)
- set cpo+=P
- write >> XfileCpoP
- call assert_equal('XfileCpoP', @%)
- close!
- call delete('XfileCpoP')
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'q' flag in 'cpo' (joining multiple lines)
-func Test_cpo_q()
- let save_cpo = &cpo
- new
- call setline(1, ['one', 'two', 'three', 'four', 'five'])
- set cpo-=q
- normal gg4J
- call assert_equal(14, col('.'))
- %d
- call setline(1, ['one', 'two', 'three', 'four', 'five'])
- set cpo+=q
- normal gg4J
- call assert_equal(4, col('.'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'r' flag in 'cpo' (redo command with a search motion)
-func Test_cpo_r()
- let save_cpo = &cpo
- new
- call setline(1, repeat(['one two three four'], 2))
- set cpo-=r
- exe "normal ggc/two\<CR>abc "
- let @/ = 'three'
- normal 2G.
- call assert_equal('abc two three four', getline(2))
- %d
- call setline(1, repeat(['one two three four'], 2))
- set cpo+=r
- exe "normal ggc/two\<CR>abc "
- let @/ = 'three'
- normal 2G.
- call assert_equal('abc three four', getline(2))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'R' flag in 'cpo' (clear marks after a filter command)
-func Test_cpo_R()
- CheckUnix
- let save_cpo = &cpo
- new
- call setline(1, ['three', 'one', 'two'])
- set cpo-=R
- 3mark r
- %!sort
- call assert_equal(3, line("'r"))
- %d
- call setline(1, ['three', 'one', 'two'])
- set cpo+=R
- 3mark r
- %!sort
- call assert_equal(0, line("'r"))
- close!
- let &cpo = save_cpo
-endfunc
-
-" TODO: Add a test for the 's' flag in 'cpo'.
-" Set buffer options when entering the buffer for the first time. If not
-" present the options are set when the buffer is created.
-
-" Test for the 'S' flag in 'cpo' (copying buffer options)
-func Test_cpo_S()
- let save_cpo = &cpo
- new Xfile1
- set noautoindent
- new Xfile2
- set cpo-=S
- set autoindent
- wincmd p
- call assert_equal(0, &autoindent)
- wincmd p
- call assert_equal(1, &autoindent)
- set cpo+=S
- wincmd p
- call assert_equal(1, &autoindent)
- set noautoindent
- wincmd p
- call assert_equal(0, &autoindent)
- wincmd t
- close!
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 't' flag in 'cpo' is in the test_tagjump.vim file.
-
-" Test for the 'u' flag in 'cpo' (Vi-compatible undo)
-func Test_cpo_u()
- let save_cpo = &cpo
- new
- set cpo-=u
- exe "normal iabc\<C-G>udef\<C-G>ughi"
- normal uu
- call assert_equal('abc', getline(1))
- %d
- set cpo+=u
- exe "normal iabc\<C-G>udef\<C-G>ughi"
- normal uu
- call assert_equal('abcdefghi', getline(1))
- close!
- let &cpo = save_cpo
-endfunc
-
-" TODO: Add a test for the 'v' flag in 'cpo'.
-" Backspaced characters remain visible on the screen in Insert mode.
-
-" Test for the 'w' flag in 'cpo' ('cw' on a blank character changes only one
-" character)
-func Test_cpo_w()
- throw 'Skipped: Nvim does not support cpoptions flag "w"'
- let save_cpo = &cpo
- new
- set cpo+=w
- call setline(1, 'here are some words')
- norm! 1gg0elcwZZZ
- call assert_equal('hereZZZ are some words', getline('.'))
- norm! 1gg2elcWYYY
- call assert_equal('hereZZZ areYYY some words', getline('.'))
- set cpo-=w
- call setline(1, 'here are some words')
- norm! 1gg0elcwZZZ
- call assert_equal('hereZZZare some words', getline('.'))
- norm! 1gg2elcWYYY
- call assert_equal('hereZZZare someYYYwords', getline('.'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'W' flag in 'cpo' is in the test_writefile.vim file
-
-" Test for the 'x' flag in 'cpo' (Esc on command-line executes command)
-func Test_cpo_x()
- let save_cpo = &cpo
- set cpo-=x
- let i = 1
- call feedkeys(":let i=10\<Esc>", 'xt')
- call assert_equal(1, i)
- set cpo+=x
- call feedkeys(":let i=10\<Esc>", 'xt')
- call assert_equal(10, i)
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'X' flag in 'cpo' ('R' with a count)
-func Test_cpo_X()
- let save_cpo = &cpo
- new
- call setline(1, 'aaaaaa')
- set cpo-=X
- normal gg4Rx
- call assert_equal('xxxxaa', getline(1))
- normal ggRy
- normal 4.
- call assert_equal('yyyyaa', getline(1))
- call setline(1, 'aaaaaa')
- set cpo+=X
- normal gg4Rx
- call assert_equal('xxxxaaaaa', getline(1))
- normal ggRy
- normal 4.
- call assert_equal('yyyyxxxaaaaa', getline(1))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'y' flag in 'cpo' (repeating a yank command)
-func Test_cpo_y()
- let save_cpo = &cpo
- new
- call setline(1, ['one', 'two'])
- set cpo-=y
- normal ggyy
- normal 2G.
- call assert_equal("one\n", @")
- %d
- call setline(1, ['one', 'two'])
- set cpo+=y
- normal ggyy
- normal 2G.
- call assert_equal("two\n", @")
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the 'Z' flag in 'cpo' (write! resets 'readonly')
-func Test_cpo_Z()
- let save_cpo = &cpo
- call writefile([], 'XfileCpoZ')
- new XfileCpoZ
- setlocal readonly
- set cpo-=Z
- write!
- call assert_equal(0, &readonly)
- set cpo+=Z
- setlocal readonly
- write!
- call assert_equal(1, &readonly)
- close!
- call delete('XfileCpoZ')
- let &cpo = save_cpo
-endfunc
-
-" Test for the '!' flag in 'cpo' is in the test_normal.vim file
-
-" Test for displaying dollar when changing text ('$' flag in 'cpoptions')
-func Test_cpo_dollar()
- throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua'
- new
- let g:Line = ''
- func SaveFirstLine()
- let g:Line = Screenline(1)
- return ''
- endfunc
- inoremap <expr> <buffer> <F2> SaveFirstLine()
- call test_override('redraw_flag', 1)
- set cpo+=$
- call setline(1, 'one two three')
- redraw!
- exe "normal c2w\<F2>vim"
- call assert_equal('one tw$ three', g:Line)
- call assert_equal('vim three', getline(1))
- set cpo-=$
- call test_override('ALL', 0)
- delfunc SaveFirstLine
- %bw!
-endfunc
-
-" Test for the '%' flag in 'cpo' (parenthesis matching inside strings)
-func Test_cpo_percent()
- let save_cpo = &cpo
- new
- call setline(1, ' if (strcmp("ab)cd(", s))')
- set cpo-=%
- normal 8|%
- call assert_equal(28, col('.'))
- normal 15|%
- call assert_equal(27, col('.'))
- normal 27|%
- call assert_equal(15, col('.'))
- call assert_beeps("normal 19|%")
- call assert_beeps("normal 22|%")
- set cpo+=%
- normal 8|%
- call assert_equal(28, col('.'))
- normal 15|%
- call assert_equal(19, col('.'))
- normal 27|%
- call assert_equal(22, col('.'))
- normal 19|%
- call assert_equal(15, col('.'))
- normal 22|%
- call assert_equal(27, col('.'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for cursor movement with '-' in 'cpoptions'
-func Test_cpo_minus()
- throw 'Skipped: Nvim does not support cpoptions flag "-"'
- new
- call setline(1, ['foo', 'bar', 'baz'])
- let save_cpo = &cpo
- set cpo+=-
- call assert_beeps('normal 10j')
- call assert_equal(1, line('.'))
- normal G
- call assert_beeps('normal 10k')
- call assert_equal(3, line('.'))
- call assert_fails(10, 'E16:')
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the '+' flag in 'cpo' ('write file' command resets the 'modified'
-" flag)
-func Test_cpo_plus()
- let save_cpo = &cpo
- call writefile([], 'XfileCpoPlus')
- new XfileCpoPlus
- call setline(1, 'foo')
- write X1
- call assert_equal(1, &modified)
- set cpo+=+
- write X2
- call assert_equal(0, &modified)
- close!
- call delete('XfileCpoPlus')
- call delete('X1')
- call delete('X2')
- let &cpo = save_cpo
-endfunc
-
-" Test for the '*' flag in 'cpo' (':*' is same as ':@')
-func Test_cpo_star()
- throw 'Skipped: Nvim does not support cpoptions flag "*"'
- let save_cpo = &cpo
- let x = 0
- new
- set cpo-=*
- let @a = 'let x += 1'
- call assert_fails('*a', 'E20:')
- set cpo+=*
- *a
- call assert_equal(1, x)
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the '<' flag in 'cpo' is in the test_mapping.vim file
-
-" Test for the '>' flag in 'cpo' (use a new line when appending to a register)
-func Test_cpo_gt()
- let save_cpo = &cpo
- new
- call setline(1, 'one two')
- set cpo-=>
- let @r = ''
- normal gg"Rye
- normal "Rye
- call assert_equal("oneone", @r)
- set cpo+=>
- let @r = ''
- normal gg"Rye
- normal "Rye
- call assert_equal("\none\none", @r)
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the ';' flag in 'cpo'
-" Test for t,f,F,T movement commands and 'cpo-;' setting
-func Test_cpo_semicolon()
- let save_cpo = &cpo
- new
- call append(0, ["aaa two three four", " zzz", "yyy ",
- \ "bbb yee yoo four", "ccc two three four",
- \ "ddd yee yoo four"])
- set cpo-=;
- 1
- normal! 0tt;D
- 2
- normal! 0fz;D
- 3
- normal! $Fy;D
- 4
- normal! $Ty;D
- set cpo+=;
- 5
- normal! 0tt;;D
- 6
- normal! $Ty;;D
-
- call assert_equal('aaa two', getline(1))
- call assert_equal(' z', getline(2))
- call assert_equal('y', getline(3))
- call assert_equal('bbb y', getline(4))
- call assert_equal('ccc', getline(5))
- call assert_equal('ddd yee y', getline(6))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the '#' flag in 'cpo' (count before 'D', 'o' and 'O' operators)
-func Test_cpo_hash()
- throw 'Skipped: Nvim does not support cpoptions flag "#"'
- let save_cpo = &cpo
- new
- set cpo-=#
- call setline(1, ['one', 'two', 'three'])
- normal gg2D
- call assert_equal(['three'], getline(1, '$'))
- normal gg2ofour
- call assert_equal(['three', 'four', 'four'], getline(1, '$'))
- normal gg2Otwo
- call assert_equal(['two', 'two', 'three', 'four', 'four'], getline(1, '$'))
- %d
- set cpo+=#
- call setline(1, ['one', 'two', 'three'])
- normal gg2D
- call assert_equal(['', 'two', 'three'], getline(1, '$'))
- normal gg2oone
- call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
- normal gg2Ozero
- call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the '&' flag in 'cpo'. The swap file is kept when a buffer is still
-" loaded and ':preserve' is used.
-func Test_cpo_ampersand()
- throw 'Skipped: Nvim does not support cpoptions flag "&"'
- call writefile(['one'], 'XfileCpoAmp')
- let after =<< trim [CODE]
- set cpo+=&
- preserve
- qall
- [CODE]
- if RunVim([], after, 'XfileCpoAmp')
- call assert_equal(1, filereadable('.XfileCpoAmp.swp'))
- call delete('.XfileCpoAmp.swp')
- endif
- call delete('XfileCpoAmp')
-endfunc
-
-" Test for the '\' flag in 'cpo' (backslash in a [] range in a search pattern)
-func Test_cpo_backslash()
- throw 'Skipped: Nvim does not support cpoptions flag "\"'
- let save_cpo = &cpo
- new
- call setline(1, ['', " \\-string"])
- set cpo-=\
- exe 'normal gg/[ \-]' .. "\<CR>n"
- call assert_equal(3, col('.'))
- set cpo+=\
- exe 'normal gg/[ \-]' .. "\<CR>n"
- call assert_equal(2, col('.'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the '/' flag in 'cpo' is in the test_substitute.vim file
-
-" Test for the '{' flag in 'cpo' (the "{" and "}" commands stop at a {
-" character at the start of a line)
-func Test_cpo_brace()
- throw 'Skipped: Nvim does not support cpoptions flag "{"'
- let save_cpo = &cpo
- new
- call setline(1, ['', '{', ' int i;', '}', ''])
- set cpo-={
- normal gg}
- call assert_equal(5, line('.'))
- normal G{
- call assert_equal(1, line('.'))
- set cpo+={
- normal gg}
- call assert_equal(2, line('.'))
- normal G{
- call assert_equal(2, line('.'))
- close!
- let &cpo = save_cpo
-endfunc
-
-" Test for the '.' flag in 'cpo' (:cd command fails if the current buffer is
-" modified)
-func Test_cpo_dot()
- throw 'Skipped: Nvim does not support cpoptions flag "."'
- let save_cpo = &cpo
- new Xfoo
- call setline(1, 'foo')
- let save_dir = getcwd()
- set cpo+=.
-
- " :cd should fail when buffer is modified and 'cpo' contains dot.
- call assert_fails('cd ..', 'E747:')
- call assert_equal(save_dir, getcwd())
-
- " :cd with exclamation mark should succeed.
- cd! ..
- call assert_notequal(save_dir, getcwd())
-
- " :cd should succeed when buffer has been written.
- w!
- exe 'cd ' .. fnameescape(save_dir)
- call assert_equal(save_dir, getcwd())
-
- call delete('Xfoo')
- set cpo&
- close!
- let &cpo = save_cpo
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
deleted file mode 100644
index bb8e7cd5c5..0000000000
--- a/src/nvim/testdir/test_cursor_func.vim
+++ /dev/null
@@ -1,481 +0,0 @@
-" Tests for cursor() and other functions that get/set the cursor position
-
-source check.vim
-
-func Test_wrong_arguments()
- call assert_fails('call cursor(1. 3)', 'E474:')
- call assert_fails('call cursor(v:_null_list)', 'E474:')
-endfunc
-
-func Test_move_cursor()
- new
- call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
-
- call cursor([1, 1, 0, 1])
- call assert_equal([1, 1, 0, 1], getcurpos()[1:])
- call cursor([4, 3, 0, 3])
- call assert_equal([4, 3, 0, 3], getcurpos()[1:])
-
- call cursor(2, 2)
- call assert_equal([2, 2, 0, 2], getcurpos()[1:])
- " line number zero keeps the line number
- call cursor(0, 1)
- call assert_equal([2, 1, 0, 1], getcurpos()[1:])
- " col number zero keeps the column
- call cursor(3, 0)
- call assert_equal([3, 1, 0, 1], getcurpos()[1:])
- " below last line goes to last line
- eval [9, 1]->cursor()
- call assert_equal([4, 1, 0, 1], getcurpos()[1:])
- " pass string arguments
- call cursor('3', '3')
- call assert_equal([3, 3, 0, 3], getcurpos()[1:])
-
- call setline(1, ["\<TAB>"])
- call cursor(1, 1, 1)
- call assert_equal([1, 1, 1], getcurpos()[1:3])
-
- call assert_fails('call cursor(-1, -1)', 'E475:')
-
- quit!
-endfunc
-
-" Very short version of what matchparen does.
-function s:Highlight_Matching_Pair()
- let save_cursor = getcurpos()
- eval save_cursor->setpos('.')
-endfunc
-
-func Test_curswant_with_autocommand()
- new
- call setline(1, ['func()', '{', '}', '----'])
- autocmd! CursorMovedI * call s:Highlight_Matching_Pair()
- exe "normal! 3Ga\<Down>X\<Esc>"
- call assert_equal('-X---', getline(4))
- autocmd! CursorMovedI *
- quit!
-endfunc
-
-" Tests for behavior of curswant with cursorcolumn/line
-func Test_curswant_with_cursorcolumn()
- new
- call setline(1, ['01234567', ''])
- exe "normal! ggf6j"
- call assert_equal(6, winsaveview().curswant)
- set cursorcolumn
- call assert_equal(6, winsaveview().curswant)
- quit!
-endfunc
-
-func Test_curswant_with_cursorline()
- new
- call setline(1, ['01234567', ''])
- exe "normal! ggf6j"
- call assert_equal(6, winsaveview().curswant)
- set cursorline
- call assert_equal(6, winsaveview().curswant)
- quit!
-endfunc
-
-func Test_screenpos()
- rightbelow new
- rightbelow 20vsplit
- call setline(1, ["\tsome text", "long wrapping line here", "next line"])
- redraw
- let winid = win_getid()
- let [winrow, wincol] = win_screenpos(winid)
- call assert_equal({'row': winrow,
- \ 'col': wincol + 0,
- \ 'curscol': wincol + 7,
- \ 'endcol': wincol + 7}, winid->screenpos(1, 1))
- call assert_equal({'row': winrow,
- \ 'col': wincol + 13,
- \ 'curscol': wincol + 13,
- \ 'endcol': wincol + 13}, winid->screenpos(1, 7))
- call assert_equal({'row': winrow + 2,
- \ 'col': wincol + 1,
- \ 'curscol': wincol + 1,
- \ 'endcol': wincol + 1}, screenpos(winid, 2, 22))
- setlocal number
- call assert_equal({'row': winrow + 3,
- \ 'col': wincol + 9,
- \ 'curscol': wincol + 9,
- \ 'endcol': wincol + 9}, screenpos(winid, 2, 22))
-
- let wininfo = getwininfo(winid)[0]
- call setline(3, ['x']->repeat(wininfo.height))
- call setline(line('$') + 1, 'x'->repeat(wininfo.width * 3))
- setlocal nonumber display=lastline so=0
- exe "normal G\<C-Y>\<C-Y>"
- redraw
- call assert_equal({'row': winrow + wininfo.height - 1,
- \ 'col': wincol + 7,
- \ 'curscol': wincol + 7,
- \ 'endcol': wincol + 7}, winid->screenpos(line('$'), 8))
- call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0},
- \ winid->screenpos(line('$'), 22))
-
- close
- call assert_equal({}, screenpos(999, 1, 1))
-
- bwipe!
- set display&
-
- call assert_equal(#{col: 1, row: 1, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1))
- " nmenu WinBar.TEST :
- setlocal winbar=TEST
- call assert_equal(#{col: 1, row: 2, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1))
- " nunmenu WinBar.TEST
- setlocal winbar&
-endfunc
-
-func Test_screenpos_fold()
- CheckFeature folding
-
- enew!
- call setline(1, range(10))
- 3,5fold
- redraw
- call assert_equal(2, screenpos(1, 2, 1).row)
- call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 3, 1))
- call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 4, 1))
- call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 5, 1))
- setlocal number
- call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 3, 1))
- call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 4, 1))
- call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 5, 1))
- call assert_equal(4, screenpos(1, 6, 1).row)
- bwipe!
-endfunc
-
-func Test_screenpos_diff()
- CheckFeature diff
-
- enew!
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
- vnew
- call setline(1, ['a', 'b', 'c', 'g', 'h', 'i'])
- windo diffthis
- wincmd w
- call assert_equal(#{col: 3, row: 7, endcol: 3, curscol: 3}, screenpos(0, 4, 1))
-
- windo diffoff
- bwipe!
- 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)
-
- call assert_fails('echo screenpos(0, 2, 1)', 'E966:')
-
- close
- bwipe!
-endfunc
-
-" Save the visual start character position
-func SaveVisualStartCharPos()
- call add(g:VisualStartPos, getcharpos('v'))
- return ''
-endfunc
-
-" Save the current cursor character position in insert mode
-func SaveInsertCurrentCharPos()
- call add(g:InsertCurrentPos, getcharpos('.'))
- return ''
-endfunc
-
-" Test for the getcharpos() function
-func Test_getcharpos()
- call assert_fails('call getcharpos({})', 'E731:')
- call assert_equal([0, 0, 0, 0], getcharpos(0))
- new
- call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678', ' │ x'])
-
- " Test for '.' and '$'
- normal 1G
- call assert_equal([0, 1, 1, 0], getcharpos('.'))
- call assert_equal([0, 5, 1, 0], getcharpos('$'))
- normal 2G6l
- call assert_equal([0, 2, 7, 0], getcharpos('.'))
- normal 3G$
- call assert_equal([0, 3, 1, 0], getcharpos('.'))
- normal 4G$
- call assert_equal([0, 4, 9, 0], getcharpos('.'))
-
- " Test for a mark
- normal 2G7lmmgg
- call assert_equal([0, 2, 8, 0], getcharpos("'m"))
- delmarks m
- call assert_equal([0, 0, 0, 0], getcharpos("'m"))
-
- " Check mark does not move
- normal 5Gfxma
- call assert_equal([0, 5, 5, 0], getcharpos("'a"))
- call assert_equal([0, 5, 5, 0], getcharpos("'a"))
- call assert_equal([0, 5, 5, 0], getcharpos("'a"))
-
- " Test for the visual start column
- vnoremap <expr> <F3> SaveVisualStartCharPos()
- let g:VisualStartPos = []
- exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
- call assert_equal([[0, 2, 7, 0], [0, 2, 10, 0], [0, 2, 5, 0]], g:VisualStartPos)
- call assert_equal([0, 2, 9, 0], getcharpos('v'))
- let g:VisualStartPos = []
- exe "normal 3Gv$\<F3>o\<F3>"
- call assert_equal([[0, 3, 1, 0], [0, 3, 2, 0]], g:VisualStartPos)
- let g:VisualStartPos = []
- exe "normal 1Gv$\<F3>o\<F3>"
- call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos)
- vunmap <F3>
-
- " Test for getting the position in insert mode with the cursor after the
- " last character in a line
- inoremap <expr> <F3> SaveInsertCurrentCharPos()
- let g:InsertCurrentPos = []
- exe "normal 1GA\<F3>"
- exe "normal 2GA\<F3>"
- exe "normal 3GA\<F3>"
- exe "normal 4GA\<F3>"
- exe "normal 2G6li\<F3>"
- call assert_equal([[0, 1, 1, 0], [0, 2, 10, 0], [0, 3, 2, 0], [0, 4, 10, 0],
- \ [0, 2, 7, 0]], g:InsertCurrentPos)
- iunmap <F3>
-
- %bw!
-endfunc
-
-" Test for the setcharpos() function
-func Test_setcharpos()
- call assert_equal(-1, setcharpos('.', v:_null_list))
- new
- call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
- call setcharpos('.', [0, 1, 1, 0])
- call assert_equal([1, 1], [line('.'), col('.')])
- call setcharpos('.', [0, 2, 7, 0])
- call assert_equal([2, 9], [line('.'), col('.')])
- call setcharpos('.', [0, 3, 4, 0])
- call assert_equal([3, 1], [line('.'), col('.')])
- call setcharpos('.', [0, 3, 1, 0])
- call assert_equal([3, 1], [line('.'), col('.')])
- call setcharpos('.', [0, 4, 0, 0])
- call assert_equal([4, 1], [line('.'), col('.')])
- call setcharpos('.', [0, 4, 20, 0])
- call assert_equal([4, 9], [line('.'), col('.')])
-
- " Test for mark
- delmarks m
- call setcharpos("'m", [0, 2, 9, 0])
- normal `m
- call assert_equal([2, 11], [line('.'), col('.')])
- " unload the buffer and try to set the mark
- let bnr = bufnr()
- enew!
- call assert_equal(-1, setcharpos("'m", [bnr, 2, 2, 0]))
-
- %bw!
- call assert_equal(-1, setcharpos('.', [10, 3, 1, 0]))
-endfunc
-
-func SaveVisualStartCharCol()
- call add(g:VisualStartCol, charcol('v'))
- return ''
-endfunc
-
-func SaveInsertCurrentCharCol()
- call add(g:InsertCurrentCol, charcol('.'))
- return ''
-endfunc
-
-" Test for the charcol() function
-func Test_charcol()
- call assert_fails('call charcol({})', 'E1222:')
- call assert_fails('call charcol(".", [])', 'E1210:')
- call assert_fails('call charcol(0)', 'E1222:')
- new
- call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
-
- " Test for '.' and '$'
- normal 1G
- call assert_equal(1, charcol('.'))
- call assert_equal(1, charcol('$'))
- normal 2G6l
- call assert_equal(7, charcol('.'))
- call assert_equal(10, charcol('$'))
- normal 3G$
- call assert_equal(1, charcol('.'))
- call assert_equal(2, charcol('$'))
- normal 4G$
- call assert_equal(9, charcol('.'))
- call assert_equal(10, charcol('$'))
-
- " Test for [lnum, '$']
- call assert_equal(1, charcol([1, '$']))
- call assert_equal(10, charcol([2, '$']))
- call assert_equal(2, charcol([3, '$']))
- call assert_equal(0, charcol([5, '$']))
-
- " Test for a mark
- normal 2G7lmmgg
- call assert_equal(8, charcol("'m"))
- delmarks m
- call assert_equal(0, charcol("'m"))
-
- " Test for the visual start column
- vnoremap <expr> <F3> SaveVisualStartCharCol()
- let g:VisualStartCol = []
- exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
- call assert_equal([7, 10, 5], g:VisualStartCol)
- call assert_equal(9, charcol('v'))
- let g:VisualStartCol = []
- exe "normal 3Gv$\<F3>o\<F3>"
- call assert_equal([1, 2], g:VisualStartCol)
- let g:VisualStartCol = []
- exe "normal 1Gv$\<F3>o\<F3>"
- call assert_equal([1, 1], g:VisualStartCol)
- vunmap <F3>
-
- " Test for getting the column number in insert mode with the cursor after
- " the last character in a line
- inoremap <expr> <F3> SaveInsertCurrentCharCol()
- let g:InsertCurrentCol = []
- exe "normal 1GA\<F3>"
- exe "normal 2GA\<F3>"
- exe "normal 3GA\<F3>"
- exe "normal 4GA\<F3>"
- exe "normal 2G6li\<F3>"
- call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol)
- iunmap <F3>
-
- " Test for getting the column number in another window.
- let winid = win_getid()
- new
- call win_execute(winid, 'normal 1G')
- call assert_equal(1, charcol('.', winid))
- call assert_equal(1, charcol('$', winid))
- call win_execute(winid, 'normal 2G6l')
- call assert_equal(7, charcol('.', winid))
- call assert_equal(10, charcol('$', winid))
-
- " calling from another tab page also works
- tabnew
- call assert_equal(7, charcol('.', winid))
- call assert_equal(10, charcol('$', winid))
- tabclose
-
- " unknown window ID
- call assert_equal(0, charcol('.', 10001))
-
- %bw!
-endfunc
-
-func SaveInsertCursorCharPos()
- call add(g:InsertCursorPos, getcursorcharpos('.'))
- return ''
-endfunc
-
-" Test for getcursorcharpos()
-func Test_getcursorcharpos()
- call assert_equal(getcursorcharpos(), getcursorcharpos(0))
- call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1))
- call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999))
-
- new
- call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
- normal 1G9l
- call assert_equal([0, 1, 1, 0, 1], getcursorcharpos())
- normal 2G9l
- call assert_equal([0, 2, 9, 0, 14], getcursorcharpos())
- normal 3G9l
- call assert_equal([0, 3, 1, 0, 1], getcursorcharpos())
- normal 4G9l
- call assert_equal([0, 4, 9, 0, 9], getcursorcharpos())
-
- " Test for getting the cursor position in insert mode with the cursor after
- " the last character in a line
- inoremap <expr> <F3> SaveInsertCursorCharPos()
- let g:InsertCursorPos = []
- exe "normal 1GA\<F3>"
- exe "normal 2GA\<F3>"
- exe "normal 3GA\<F3>"
- exe "normal 4GA\<F3>"
- exe "normal 2G6li\<F3>"
- call assert_equal([[0, 1, 1, 0, 1], [0, 2, 10, 0, 15], [0, 3, 2, 0, 2],
- \ [0, 4, 10, 0, 10], [0, 2, 7, 0, 12]], g:InsertCursorPos)
- iunmap <F3>
-
- let winid = win_getid()
- normal 2G5l
- wincmd w
- call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid))
- %bw!
-endfunc
-
-" Test for setcursorcharpos()
-func Test_setcursorcharpos()
- call assert_fails('call setcursorcharpos(v:_null_list)', 'E474:')
- call assert_fails('call setcursorcharpos([1])', 'E474:')
- call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:')
- new
- call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
- normal G
- call setcursorcharpos([1, 1])
- call assert_equal([1, 1], [line('.'), col('.')])
-
- call setcursorcharpos([2, 7, 0])
- call assert_equal([2, 9], [line('.'), col('.')])
- call setcursorcharpos([0, 7, 0])
- call assert_equal([2, 9], [line('.'), col('.')])
- call setcursorcharpos(0, 7, 0)
- call assert_equal([2, 9], [line('.'), col('.')])
-
- call setcursorcharpos(3, 4)
- call assert_equal([3, 1], [line('.'), col('.')])
- call setcursorcharpos([3, 1])
- call assert_equal([3, 1], [line('.'), col('.')])
- call setcursorcharpos([4, 0, 0, 0])
- call assert_equal([4, 1], [line('.'), col('.')])
- call setcursorcharpos([4, 20])
- call assert_equal([4, 9], [line('.'), col('.')])
- normal 1G
- call setcursorcharpos([100, 100, 100, 100])
- call assert_equal([4, 9], [line('.'), col('.')])
- normal 1G
- call setcursorcharpos('$', 1)
- call assert_equal([4, 1], [line('.'), col('.')])
-
- %bw!
-endfunc
-
-" Test for virtcol2col()
-func Test_virtcol2col()
- new
- call setline(1, ["a\tb\tc"])
- call assert_equal(1, virtcol2col(0, 1, 1))
- call assert_equal(2, virtcol2col(0, 1, 2))
- call assert_equal(2, virtcol2col(0, 1, 8))
- call assert_equal(3, virtcol2col(0, 1, 9))
- call assert_equal(4, virtcol2col(0, 1, 10))
- call assert_equal(4, virtcol2col(0, 1, 16))
- call assert_equal(5, virtcol2col(0, 1, 17))
- call assert_equal(-1, virtcol2col(10, 1, 1))
- call assert_equal(-1, virtcol2col(0, 10, 1))
- call assert_equal(-1, virtcol2col(0, -1, 1))
- call assert_equal(-1, virtcol2col(0, 1, -1))
- call assert_equal(5, virtcol2col(0, 1, 20))
- call assert_fails('echo virtcol2col("0", 1, 20)', 'E1210:')
- call assert_fails('echo virtcol2col(0, "1", 20)', 'E1210:')
- call assert_fails('echo virtcol2col(0, 1, "1")', 'E1210:')
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cursorline.vim b/src/nvim/testdir/test_cursorline.vim
deleted file mode 100644
index 70f39a8601..0000000000
--- a/src/nvim/testdir/test_cursorline.vim
+++ /dev/null
@@ -1,354 +0,0 @@
-" Test for cursorline and cursorlineopt
-
-source check.vim
-source screendump.vim
-
-func s:screen_attr(lnum) abort
- return map(range(1, 8), 'screenattr(a:lnum, v:val)')
-endfunc
-
-func s:test_windows(h, w) abort
- call NewWindow(a:h, a:w)
-endfunc
-
-func s:close_windows() abort
- call CloseWindow()
-endfunc
-
-func s:new_hi() abort
- redir => save_hi
- silent! hi CursorLineNr
- redir END
- let save_hi = join(split(substitute(save_hi, '\s*xxx\s*', ' ', ''), "\n"), '')
- exe 'hi' save_hi 'ctermbg=0 guibg=Black'
- return save_hi
-endfunc
-
-func Test_cursorline_highlight1()
- let save_hi = s:new_hi()
- try
- call s:test_windows(10, 20)
- call setline(1, repeat(['aaaa'], 10))
- redraw
- let attr01 = s:screen_attr(1)
- call assert_equal(repeat([attr01[0]], 8), attr01)
-
- setl number numberwidth=4
- redraw
- let attr11 = s:screen_attr(1)
- call assert_equal(repeat([attr11[0]], 4), attr11[0:3])
- call assert_equal(repeat([attr11[4]], 4), attr11[4:7])
- call assert_notequal(attr11[0], attr11[4])
-
- setl cursorline
- redraw
- let attr21 = s:screen_attr(1)
- let attr22 = s:screen_attr(2)
- call assert_equal(repeat([attr21[0]], 4), attr21[0:3])
- call assert_equal(repeat([attr21[4]], 4), attr21[4:7])
- call assert_equal(attr11, attr22)
- call assert_notequal(attr22, attr21)
-
- setl nocursorline relativenumber
- redraw
- let attr31 = s:screen_attr(1)
- call assert_equal(attr22[0:3], attr31[0:3])
- call assert_equal(attr11[4:7], attr31[4:7])
-
- call s:close_windows()
- finally
- exe 'hi' save_hi
- endtry
-endfunc
-
-func Test_cursorline_highlight2()
- CheckOption cursorlineopt
-
- let save_hi = s:new_hi()
- try
- call s:test_windows(10, 20)
- call setline(1, repeat(['aaaa'], 10))
- redraw
- let attr0 = s:screen_attr(1)
- call assert_equal(repeat([attr0[0]], 8), attr0)
-
- setl number
- redraw
- let attr1 = s:screen_attr(1)
- call assert_notequal(attr0[0:3], attr1[0:3])
- call assert_equal(attr0[0:3], attr1[4:7])
-
- setl cursorline cursorlineopt=both
- redraw
- let attr2 = s:screen_attr(1)
- call assert_notequal(attr1[0:3], attr2[0:3])
- call assert_notequal(attr1[4:7], attr2[4:7])
-
- setl cursorlineopt=line
- redraw
- let attr3 = s:screen_attr(1)
- call assert_equal(attr1[0:3], attr3[0:3])
- call assert_equal(attr2[4:7], attr3[4:7])
-
- setl cursorlineopt=number
- redraw
- let attr4 = s:screen_attr(1)
- call assert_equal(attr2[0:3], attr4[0:3])
- call assert_equal(attr1[4:7], attr4[4:7])
-
- setl nonumber
- redraw
- let attr5 = s:screen_attr(1)
- call assert_equal(attr0, attr5)
-
- call s:close_windows()
- finally
- exe 'hi' save_hi
- endtry
-endfunc
-
-func Test_cursorline_screenline()
- CheckScreendump
- CheckOption cursorlineopt
-
- let filename='Xcursorline'
- let lines = []
-
- let file_content =<< trim END
- 1 foooooooo ar einsâ€zwei drei vier fünf sechs sieben acht un zehn elf zwöfl dreizehn v ierzehn fünfzehn
- 2 foooooooo bar eins zwei drei vier fünf sechs sieben
- 3 foooooooo bar eins zwei drei vier fünf sechs sieben
- 4 foooooooo bar eins zwei drei vier fünf sechs sieben
- END
- let lines1 =<< trim END1
- set nocp
- set display=lastline
- set cursorlineopt=screenline cursorline nu wrap sbr=>
- hi CursorLineNr ctermfg=blue
- 25vsp
- END1
- let lines2 =<< trim END2
- call cursor(1,1)
- END2
- call extend(lines, lines1)
- call extend(lines, ["call append(0, ".. string(file_content).. ')'])
- call extend(lines, lines2)
- call writefile(lines, filename)
- " basic test
- let buf = RunVimInTerminal('-S '. filename, #{rows: 20})
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_1', {})
- call term_sendkeys(buf, "fagj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_2', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_3', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_4', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_5', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_6', {})
- " test with set list and cursorlineopt containing number
- call term_sendkeys(buf, "gg0")
- call term_sendkeys(buf, ":set list cursorlineopt+=number listchars=space:-\<cr>")
- call VerifyScreenDump(buf, 'Test_'. filename. '_7', {})
- call term_sendkeys(buf, "fagj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_8', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_9', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_10', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_11', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_12', {})
- if exists("+foldcolumn") && exists("+signcolumn") && exists("+breakindent")
- " test with set foldcolumn signcoloumn and breakindent
- call term_sendkeys(buf, "gg0")
- call term_sendkeys(buf, ":set breakindent foldcolumn=2 signcolumn=yes\<cr>")
- call VerifyScreenDump(buf, 'Test_'. filename. '_13', {})
- call term_sendkeys(buf, "fagj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_14', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_15', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_16', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_17', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_18', {})
- call term_sendkeys(buf, ":set breakindent& foldcolumn& signcolumn&\<cr>")
- endif
- " showbreak should not be highlighted with CursorLine when 'number' is off
- call term_sendkeys(buf, "gg0")
- call term_sendkeys(buf, ":set list cursorlineopt=screenline listchars=space:-\<cr>")
- call term_sendkeys(buf, ":set nonumber\<cr>")
- call VerifyScreenDump(buf, 'Test_'. filename. '_19', {})
- call term_sendkeys(buf, "fagj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_20', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_21', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_22', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_23', {})
- call term_sendkeys(buf, "gj")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_'. filename. '_24', {})
- call term_sendkeys(buf, ":set list& cursorlineopt& listchars&\<cr>")
-
- call StopVimInTerminal(buf)
- call delete(filename)
-endfunc
-
-func Test_cursorline_redraw()
- CheckScreendump
- CheckOption cursorlineopt
-
- let textlines =<< END
- When the option is a list of flags, {value} must be
- exactly as they appear in the option. Remove flags
- one by one to avoid problems.
- Also see |:set-args| above.
-
-The {option} arguments to ":set" may be repeated. For example: >
- :set ai nosi sw=3 ts=3
-If you make an error in one of the arguments, an error message will be given
-and the following arguments will be ignored.
-
- *:set-verbose*
-When 'verbose' is non-zero, displaying an option value will also tell where it
-was last set. Example: >
- :verbose set shiftwidth cindent?
-< shiftwidth=4 ~
- Last set from modeline line 1 ~
- cindent ~
- Last set from /usr/local/share/vim/vim60/ftplugin/c.vim line 30 ~
-This is only done when specific option values are requested, not for ":verbose
-set all" or ":verbose set" without an argument.
-When the option was set by hand there is no "Last set" message.
-When the option was set while executing a function, user command or
-END
- call writefile(textlines, 'Xtextfile')
-
- let script =<< trim END
- set cursorline scrolloff=2
- normal 12G
- END
- call writefile(script, 'Xscript')
-
- let buf = RunVimInTerminal('-S Xscript Xtextfile', #{rows: 20, cols: 40})
- call VerifyScreenDump(buf, 'Test_cursorline_redraw_1', {})
- call term_sendkeys(buf, "zt")
- call TermWait(buf)
- call term_sendkeys(buf, "\<C-U>")
- call VerifyScreenDump(buf, 'Test_cursorline_redraw_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xscript')
- call delete('Xtextfile')
-endfunc
-
-func Test_cursorline_callback()
- CheckScreendump
- CheckFeature timers
-
- let lines =<< trim END
- call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
- set cursorline
- call cursor(4, 1)
-
- func Func(timer)
- call cursor(2, 1)
- endfunc
-
- call timer_start(300, 'Func')
- END
- call writefile(lines, 'Xcul_timer')
-
- let buf = RunVimInTerminal('-S Xcul_timer', #{rows: 8})
- call TermWait(buf, 310)
- call VerifyScreenDump(buf, 'Test_cursorline_callback_1', {})
-
- call StopVimInTerminal(buf)
- call delete('Xcul_timer')
-endfunc
-
-func Test_cursorline_screenline_update()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, repeat('xyz ', 30))
- set cursorline cursorlineopt=screenline
- inoremap <F2> <Cmd>call cursor(1, 1)<CR>
- END
- call writefile(lines, 'Xcul_screenline')
-
- let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8})
- call term_sendkeys(buf, "A")
- call VerifyScreenDump(buf, 'Test_cursorline_screenline_1', {})
- call term_sendkeys(buf, "\<F2>")
- call VerifyScreenDump(buf, 'Test_cursorline_screenline_2', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xcul_screenline')
-endfunc
-
-func Test_cursorline_cursorbind_horizontal_scroll()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, 'aa bb cc dd ee ff gg hh ii jj kk ll mm' ..
- \ ' nn oo pp qq rr ss tt uu vv ww xx yy zz')
- set nowrap
- " The following makes the cursor apparent on the screen dump
- set sidescroll=1 cursorcolumn
- " add empty lines, required for cursorcolumn
- call append(1, ['','','',''])
- 20vsp
- windo :set cursorbind
- END
- call writefile(lines, 'Xhor_scroll')
-
- let buf = RunVimInTerminal('-S Xhor_scroll', #{rows: 8})
- call term_sendkeys(buf, "20l")
- call VerifyScreenDump(buf, 'Test_hor_scroll_1', {})
- call term_sendkeys(buf, "10l")
- call VerifyScreenDump(buf, 'Test_hor_scroll_2', {})
- call term_sendkeys(buf, ":windo :set cursorline\<cr>")
- call term_sendkeys(buf, "0")
- call term_sendkeys(buf, "20l")
- call VerifyScreenDump(buf, 'Test_hor_scroll_3', {})
- call term_sendkeys(buf, "10l")
- call VerifyScreenDump(buf, 'Test_hor_scroll_4', {})
- call term_sendkeys(buf, ":windo :set nocursorline nocursorcolumn\<cr>")
- call term_sendkeys(buf, "0")
- call term_sendkeys(buf, "40l")
- call VerifyScreenDump(buf, 'Test_hor_scroll_5', {})
-
- call StopVimInTerminal(buf)
- call delete('Xhor_scroll')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_curswant.vim b/src/nvim/testdir/test_curswant.vim
deleted file mode 100644
index e54cd4b280..0000000000
--- a/src/nvim/testdir/test_curswant.vim
+++ /dev/null
@@ -1,23 +0,0 @@
-" Tests for curswant not changing when setting an option
-
-func Test_curswant()
- new
- call append(0, ['1234567890', '12345'])
-
- normal! ggf8j
- call assert_equal(7, winsaveview().curswant)
- let &tabstop=&tabstop
- call assert_equal(4, winsaveview().curswant)
-
- normal! ggf8j
- call assert_equal(7, winsaveview().curswant)
- let &timeoutlen=&timeoutlen
- call assert_equal(7, winsaveview().curswant)
-
- normal! ggf8j
- call assert_equal(7, winsaveview().curswant)
- let &ttimeoutlen=&ttimeoutlen
- call assert_equal(7, winsaveview().curswant)
-
- enew!
-endfunc
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
deleted file mode 100644
index f5177c8fb2..0000000000
--- a/src/nvim/testdir/test_debugger.vim
+++ /dev/null
@@ -1,1237 +0,0 @@
-" Tests for the Vim script debug commands
-
-source shared.vim
-source screendump.vim
-source check.vim
-
-func CheckCWD()
- " Check that the longer lines don't wrap due to the length of the script name
- " in cwd
- let script_len = len( getcwd() .. '/Xtest1.vim' )
- let longest_line = len( 'Breakpoint in "" line 1' )
- if script_len > ( 75 - longest_line )
- throw 'Skipped: Your CWD has too many characters'
- endif
-endfunc
-command! -nargs=0 -bar CheckCWD call CheckCWD()
-
-" "options" argument can contain:
-" 'msec' - time to wait for a match
-" 'match' - "pattern" to use "lines" as pattern instead of text
-func CheckDbgOutput(buf, lines, options = {})
- " Verify the expected output
- let lnum = 20 - len(a:lines)
- let msec = get(a:options, 'msec', 1000)
- for l in a:lines
- if get(a:options, 'match', 'equal') ==# 'pattern'
- call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, msec)
- else
- call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, msec)
- endif
- let lnum += 1
- endfor
-endfunc
-
-" Run a Vim debugger command
-" If the expected output argument is supplied, then check for it.
-func RunDbgCmd(buf, cmd, ...)
- call term_sendkeys(a:buf, a:cmd . "\r")
- call term_wait(a:buf, 20)
-
- if a:0 != 0
- let options = #{match: 'equal'}
- if a:0 > 1
- call extend(options, a:2)
- endif
- call CheckDbgOutput(a:buf, a:1, options)
- endif
-endfunc
-
-" Debugger tests
-func Test_Debugger()
- CheckRunVimInTerminal
-
- " Create a Vim script with some functions
- 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', {})
-
- " Start the Vim debugger
- call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])
-
- " Create a few stack frames by stepping through functions
- call RunDbgCmd(buf, 'step', ['line 1: let var1 = 1'])
- call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bar(var1) + 9'])
- call RunDbgCmd(buf, 'step', ['line 1: let var1 = 2 + a:var'])
- call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bazz(var1) + 4'])
- call RunDbgCmd(buf, 'step', ['line 1: try'])
- call RunDbgCmd(buf, 'step', ['line 2: let var1 = 3 + a:var'])
- call RunDbgCmd(buf, 'step', ['line 3: let var3 = "another var"'])
-
- " check backtrace
- call RunDbgCmd(buf, 'backtrace', [
- \ ' 2 function Foo[2]',
- \ ' 1 Bar[2]',
- \ '->0 Bazz',
- \ 'line 3: let var3 = "another var"'])
-
- " Check variables in different stack frames
- call RunDbgCmd(buf, 'echo var1', ['6'])
-
- call RunDbgCmd(buf, 'up')
- call RunDbgCmd(buf, 'back', [
- \ ' 2 function Foo[2]',
- \ '->1 Bar[2]',
- \ ' 0 Bazz',
- \ 'line 3: let var3 = "another var"'])
- call RunDbgCmd(buf, 'echo var1', ['3'])
-
- call RunDbgCmd(buf, 'u')
- call RunDbgCmd(buf, 'bt', [
- \ '->2 function Foo[2]',
- \ ' 1 Bar[2]',
- \ ' 0 Bazz',
- \ 'line 3: let var3 = "another var"'])
- call RunDbgCmd(buf, 'echo var1', ['1'])
-
- " Undefined variables
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'frame 2')
- call RunDbgCmd(buf, 'echo var3', [
- \ 'Error detected while processing function Foo[2]..Bar[2]..Bazz:',
- \ 'line 4:',
- \ 'E121: Undefined variable: var3'])
-
- " var3 is defined in this level with some other value
- call RunDbgCmd(buf, 'fr 0')
- call RunDbgCmd(buf, 'echo var3', ['another var'])
-
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, '')
- call RunDbgCmd(buf, '')
- call RunDbgCmd(buf, '')
- call RunDbgCmd(buf, '')
- call RunDbgCmd(buf, 'step', [
- \ 'function Foo[2]..Bar',
- \ 'line 3: End of function'])
- call RunDbgCmd(buf, 'up')
-
- " Undefined var2
- call RunDbgCmd(buf, 'echo var2', [
- \ 'Error detected while processing function Foo[2]..Bar:',
- \ 'line 3:',
- \ 'E121: Undefined variable: var2'])
-
- " Var2 is defined with 10
- call RunDbgCmd(buf, 'down')
- call RunDbgCmd(buf, 'echo var2', ['10'])
-
- " Backtrace movements
- call RunDbgCmd(buf, 'b', [
- \ ' 1 function Foo[2]',
- \ '->0 Bar',
- \ 'line 3: End of function'])
-
- " next command cannot go down, we are on bottom
- call RunDbgCmd(buf, 'down', ['frame is zero'])
- call RunDbgCmd(buf, 'up')
-
- " next command cannot go up, we are on top
- call RunDbgCmd(buf, 'up', ['frame at highest level: 1'])
- call RunDbgCmd(buf, 'where', [
- \ '->1 function Foo[2]',
- \ ' 0 Bar',
- \ 'line 3: End of function'])
-
- " fil is not frame or finish, it is file
- call RunDbgCmd(buf, 'fil', ['"[No Name]" --No lines in buffer--'])
-
- " relative backtrace movement
- call RunDbgCmd(buf, 'fr -1')
- call RunDbgCmd(buf, 'frame', [
- \ ' 1 function Foo[2]',
- \ '->0 Bar',
- \ 'line 3: End of function'])
-
- call RunDbgCmd(buf, 'fr +1')
- call RunDbgCmd(buf, 'fram', [
- \ '->1 function Foo[2]',
- \ ' 0 Bar',
- \ 'line 3: End of function'])
-
- " go beyond limits does not crash
- call RunDbgCmd(buf, 'fr 100', ['frame at highest level: 1'])
- call RunDbgCmd(buf, 'fra', [
- \ '->1 function Foo[2]',
- \ ' 0 Bar',
- \ 'line 3: End of function'])
-
- call RunDbgCmd(buf, 'frame -40', ['frame is zero'])
- call RunDbgCmd(buf, 'fram', [
- \ ' 1 function Foo[2]',
- \ '->0 Bar',
- \ 'line 3: End of function'])
-
- " final result 19
- call RunDbgCmd(buf, 'cont', ['19'])
-
- " breakpoints tests
-
- " Start a debug session, so that reading the last line from the terminal
- " works properly.
- call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])
-
- " No breakpoints
- call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
-
- " Place some breakpoints
- call RunDbgCmd(buf, 'breaka func Bar')
- call RunDbgCmd(buf, 'breaklis', [' 1 func Bar line 1'])
- call RunDbgCmd(buf, 'breakadd func 3 Bazz')
- call RunDbgCmd(buf, 'breaklist', [' 1 func Bar line 1',
- \ ' 2 func Bazz line 3'])
-
- " Check whether the breakpoints are hit
- call RunDbgCmd(buf, 'cont', [
- \ 'Breakpoint in "Bar" line 1',
- \ 'function Foo[2]..Bar',
- \ 'line 1: let var1 = 2 + a:var'])
- call RunDbgCmd(buf, 'cont', [
- \ 'Breakpoint in "Bazz" line 3',
- \ 'function Foo[2]..Bar[2]..Bazz',
- \ 'line 3: let var3 = "another var"'])
-
- " Delete the breakpoints
- call RunDbgCmd(buf, 'breakd 1')
- call RunDbgCmd(buf, 'breakli', [' 2 func Bazz line 3'])
- call RunDbgCmd(buf, 'breakdel func 3 Bazz')
- call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
-
- call RunDbgCmd(buf, 'cont')
-
- " Make sure the breakpoints are removed
- call RunDbgCmd(buf, ':echo Foo()', ['19'])
-
- " Delete a non-existing breakpoint
- call RunDbgCmd(buf, ':breakdel 2', ['E161: Breakpoint not found: 2'])
-
- " Expression breakpoint
- call RunDbgCmd(buf, ':breakadd func 2 Bazz')
- call RunDbgCmd(buf, ':echo Bazz(1)', [
- \ 'Entering Debug mode. Type "cont" to continue.',
- \ 'function Bazz',
- \ 'line 2: let var1 = 3 + a:var'])
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'breaka expr var3')
- call RunDbgCmd(buf, 'breakl', [' 3 func Bazz line 2',
- \ ' 4 expr var3'])
- call RunDbgCmd(buf, 'cont', ['Breakpoint in "Bazz" line 5',
- \ 'Oldval = "''another var''"',
- \ 'Newval = "''value2''"',
- \ 'function Bazz',
- \ 'line 5: catch'])
-
- call RunDbgCmd(buf, 'breakdel *')
- call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
-
- " Check for error cases
- call RunDbgCmd(buf, 'breakadd abcd', [
- \ 'Error detected while processing function Bazz:',
- \ 'line 5:',
- \ 'E475: Invalid argument: abcd'])
- call RunDbgCmd(buf, 'breakadd func', ['E475: Invalid argument: func'])
- call RunDbgCmd(buf, 'breakadd func 2', ['E475: Invalid argument: func 2'])
- call RunDbgCmd(buf, 'breaka func a()', ['E475: Invalid argument: func a()'])
- call RunDbgCmd(buf, 'breakd abcd', ['E475: Invalid argument: abcd'])
- call RunDbgCmd(buf, 'breakd func', ['E475: Invalid argument: func'])
- call RunDbgCmd(buf, 'breakd func a()', ['E475: Invalid argument: func a()'])
- call RunDbgCmd(buf, 'breakd func a', ['E161: Breakpoint not found: func a'])
- call RunDbgCmd(buf, 'breakd expr', ['E475: Invalid argument: expr'])
- call RunDbgCmd(buf, 'breakd expr x', ['E161: Breakpoint not found: expr x'])
-
- " finish the current function
- call RunDbgCmd(buf, 'finish', [
- \ 'function Bazz',
- \ 'line 8: End of function'])
- call RunDbgCmd(buf, 'cont')
-
- " Test for :next
- call RunDbgCmd(buf, ':debug echo Bar(1)')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'next')
- call RunDbgCmd(buf, '', [
- \ 'function Bar',
- \ 'line 3: return var2'])
- call RunDbgCmd(buf, 'c')
-
- " Test for :interrupt
- call RunDbgCmd(buf, ':debug echo Bazz(1)')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'interrupt', [
- \ 'Exception thrown: Vim:Interrupt',
- \ 'function Bazz',
- \ 'line 5: catch'])
- call RunDbgCmd(buf, 'c')
-
- " Test for :quit
- call RunDbgCmd(buf, ':debug echo Foo()')
- call RunDbgCmd(buf, 'breakdel *')
- call RunDbgCmd(buf, 'breakadd func 3 Foo')
- call RunDbgCmd(buf, 'breakadd func 3 Bazz')
- call RunDbgCmd(buf, 'cont', [
- \ 'Breakpoint in "Bazz" line 3',
- \ 'function Foo[2]..Bar[2]..Bazz',
- \ 'line 3: let var3 = "another var"'])
- call RunDbgCmd(buf, 'quit', [
- \ 'Breakpoint in "Foo" line 3',
- \ 'function Foo',
- \ 'line 3: return var2'])
- call RunDbgCmd(buf, 'breakdel *')
- call RunDbgCmd(buf, 'quit')
- call RunDbgCmd(buf, 'enew! | only!')
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_Debugger_breakadd()
- " Tests for :breakadd file and :breakadd here
- " Breakpoints should be set before sourcing the file
- CheckRunVimInTerminal
-
- 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', {})
- call RunDbgCmd(buf, ':breakadd file 2 Xtest.vim')
- call RunDbgCmd(buf, ':4 | breakadd here')
- call RunDbgCmd(buf, ':source Xtest.vim', ['line 2: let var2 = 20'])
- call RunDbgCmd(buf, 'cont', ['line 4: let var4 = 40'])
- call RunDbgCmd(buf, 'cont')
-
- call StopVimInTerminal(buf)
-
- call delete('Xtest.vim')
- %bw!
-
- call assert_fails('breakadd here', 'E32:')
- call assert_fails('breakadd file Xtest.vim /\)/', 'E55:')
-endfunc
-
-func Test_Backtrace_Through_Source()
- CheckRunVimInTerminal
- CheckCWD
- let file1 =<< trim END
- func SourceAnotherFile()
- source Xtest2.vim
- endfunc
-
- func CallAFunction()
- call SourceAnotherFile()
- call File2Function()
- endfunc
-
- func GlobalFunction()
- call CallAFunction()
- endfunc
- END
- call writefile(file1, 'Xtest1.vim')
-
- let file2 =<< trim END
- func DoAThing()
- echo "DoAThing"
- endfunc
-
- func File2Function()
- call DoAThing()
- endfunc
-
- call File2Function()
- END
- call writefile(file2, 'Xtest2.vim')
-
- let buf = RunVimInTerminal('-S Xtest1.vim', {})
-
- call RunDbgCmd(buf,
- \ ':debug call GlobalFunction()',
- \ ['cmd: call GlobalFunction()'])
- call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
-
- call RunDbgCmd(buf, 'backtrace', ['>backtrace',
- \ '->0 function GlobalFunction',
- \ 'line 1: call CallAFunction()'])
-
- call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
- call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
-
- call RunDbgCmd(buf, 'backtrace', ['>backtrace',
- \ ' 2 function GlobalFunction[1]',
- \ ' 1 CallAFunction[1]',
- \ '->0 SourceAnotherFile',
- \ 'line 1: source Xtest2.vim'])
-
- " Step into the 'source' command. Note that we print the full trace all the
- " way though the source command.
- call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()'])
-
- call RunDbgCmd( buf, 'up' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ '->1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'up' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 function GlobalFunction[1]',
- \ '->2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'up' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ '->3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'up', [ 'frame at highest level: 3' ] )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ '->3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 function GlobalFunction[1]',
- \ '->2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ '->1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
-
- " step until we have another meaninfgul trace
- call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
- call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 9: call File2Function()'])
-
- call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
- call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 5 function GlobalFunction[1]',
- \ ' 4 CallAFunction[1]',
- \ ' 3 SourceAnotherFile[1]',
- \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
- \ ' 1 function File2Function[1]',
- \ '->0 DoAThing',
- \ 'line 1: echo "DoAThing"'])
-
- " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
- call RunDbgCmd(buf, 'step', ['line 1: End of function'])
- call RunDbgCmd(buf, 'step', ['line 1: End of function'])
- call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
- call RunDbgCmd(buf, 'step', ['line 1: End of function'])
- call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 1 function GlobalFunction[1]',
- \ '->0 CallAFunction',
- \ 'line 2: call File2Function()'])
-
- call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 2 function GlobalFunction[1]',
- \ ' 1 CallAFunction[2]',
- \ '->0 File2Function',
- \ 'line 1: call DoAThing()'])
-
- call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
-endfunc
-
-func Test_Backtrace_Autocmd()
- CheckRunVimInTerminal
- CheckCWD
- let file1 =<< trim END
- func SourceAnotherFile()
- source Xtest2.vim
- endfunc
-
- func CallAFunction()
- call SourceAnotherFile()
- call File2Function()
- endfunc
-
- func GlobalFunction()
- call CallAFunction()
- endfunc
-
- au User TestGlobalFunction :call GlobalFunction() | echo "Done"
- END
- call writefile(file1, 'Xtest1.vim')
-
- let file2 =<< trim END
- func DoAThing()
- echo "DoAThing"
- endfunc
-
- func File2Function()
- call DoAThing()
- endfunc
-
- call File2Function()
- END
- call writefile(file2, 'Xtest2.vim')
-
- let buf = RunVimInTerminal('-S Xtest1.vim', {})
-
- call RunDbgCmd(buf,
- \ ':debug doautocmd User TestGlobalFunction',
- \ ['cmd: doautocmd User TestGlobalFunction'])
- call RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"'])
-
- " At this point the ontly thing in the stack is the autocommand
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ '->0 User Autocommands for "TestGlobalFunction"',
- \ 'cmd: call GlobalFunction() | echo "Done"'])
-
- " And now we're back into the call stack
- call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 1 User Autocommands for "TestGlobalFunction"',
- \ '->0 function GlobalFunction',
- \ 'line 1: call CallAFunction()'])
-
- call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
- call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
-
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 User Autocommands for "TestGlobalFunction"',
- \ ' 2 function GlobalFunction[1]',
- \ ' 1 CallAFunction[1]',
- \ '->0 SourceAnotherFile',
- \ 'line 1: source Xtest2.vim'])
-
- " Step into the 'source' command. Note that we print the full trace all the
- " way though the source command.
- call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()'])
-
- call RunDbgCmd( buf, 'up' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ '->1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'up' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ '->2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'up' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ '->3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'up' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ '->4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'up', [ 'frame at highest level: 4' ] )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ '->4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ '->3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
-
- call RunDbgCmd( buf, 'down' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ '->2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ '->1 SourceAnotherFile[1]',
- \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down' )
- call RunDbgCmd( buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 1: func DoAThing()' ] )
-
- call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
-
- " step until we have another meaninfgul trace
- call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
- call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 4 User Autocommands for "TestGlobalFunction"',
- \ ' 3 function GlobalFunction[1]',
- \ ' 2 CallAFunction[1]',
- \ ' 1 SourceAnotherFile[1]',
- \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ 'line 9: call File2Function()'])
-
- call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
- call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 6 User Autocommands for "TestGlobalFunction"',
- \ ' 5 function GlobalFunction[1]',
- \ ' 4 CallAFunction[1]',
- \ ' 3 SourceAnotherFile[1]',
- \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
- \ ' 1 function File2Function[1]',
- \ '->0 DoAThing',
- \ 'line 1: echo "DoAThing"'])
-
- " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
- call RunDbgCmd(buf, 'step', ['line 1: End of function'])
- call RunDbgCmd(buf, 'step', ['line 1: End of function'])
- call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
- call RunDbgCmd(buf, 'step', ['line 1: End of function'])
- call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 2 User Autocommands for "TestGlobalFunction"',
- \ ' 1 function GlobalFunction[1]',
- \ '->0 CallAFunction',
- \ 'line 2: call File2Function()'])
-
- call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 User Autocommands for "TestGlobalFunction"',
- \ ' 2 function GlobalFunction[1]',
- \ ' 1 CallAFunction[2]',
- \ '->0 File2Function',
- \ 'line 1: call DoAThing()'])
-
-
- " Now unwind so that we get back to the original autocommand (and the second
- " cmd echo "Done")
- call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 3 User Autocommands for "TestGlobalFunction"',
- \ ' 2 function GlobalFunction[1]',
- \ ' 1 CallAFunction[2]',
- \ '->0 File2Function',
- \ 'line 1: End of function'])
-
- call RunDbgCmd(buf, 'finish', ['line 2: End of function'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 2 User Autocommands for "TestGlobalFunction"',
- \ ' 1 function GlobalFunction[1]',
- \ '->0 CallAFunction',
- \ 'line 2: End of function'])
-
- call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 1 User Autocommands for "TestGlobalFunction"',
- \ '->0 function GlobalFunction',
- \ 'line 1: End of function'])
-
- call RunDbgCmd(buf, 'step', ['cmd: echo "Done"'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ '->0 User Autocommands for "TestGlobalFunction"',
- \ 'cmd: echo "Done"'])
-
- call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
-endfunc
-
-func Test_Backtrace_CmdLine()
- CheckRunVimInTerminal
- CheckCWD
- let file1 =<< trim END
- func SourceAnotherFile()
- source Xtest2.vim
- endfunc
-
- func CallAFunction()
- call SourceAnotherFile()
- call File2Function()
- endfunc
-
- func GlobalFunction()
- call CallAFunction()
- endfunc
-
- au User TestGlobalFunction :call GlobalFunction() | echo "Done"
- END
- call writefile(file1, 'Xtest1.vim')
-
- let file2 =<< trim END
- func DoAThing()
- echo "DoAThing"
- endfunc
-
- func File2Function()
- call DoAThing()
- endfunc
-
- call File2Function()
- END
- call writefile(file2, 'Xtest2.vim')
-
- let buf = RunVimInTerminal(
- \ '-S Xtest1.vim -c "debug call GlobalFunction()"',
- \ {'wait_for_ruler': 0})
-
- " Need to wait for the vim-in-terminal to be ready.
- " With valgrind this can take quite long.
- call CheckDbgOutput(buf, ['command line',
- \ 'cmd: call GlobalFunction()'], #{msec: 5000})
-
- " At this point the ontly thing in the stack is the cmdline
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ '->0 command line',
- \ 'cmd: call GlobalFunction()'])
-
- " And now we're back into the call stack
- call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '>backtrace',
- \ ' 1 command line',
- \ '->0 function GlobalFunction',
- \ 'line 1: call CallAFunction()'])
-
- call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
-endfunc
-
-func Test_Backtrace_DefFunction()
- CheckRunVimInTerminal
- CheckCWD
- let file1 =<< trim END
- vim9script
- import File2Function from './Xtest2.vim'
-
- def SourceAnotherFile()
- source Xtest2.vim
- enddef
-
- def CallAFunction()
- SourceAnotherFile()
- File2Function()
- enddef
-
- def g:GlobalFunction()
- CallAFunction()
- enddef
-
- defcompile
- END
- call writefile(file1, 'Xtest1.vim')
-
- let file2 =<< trim END
- vim9script
-
- def DoAThing(): number
- var a = 100 * 2
- a += 3
- return a
- enddef
-
- export def File2Function()
- DoAThing()
- enddef
-
- defcompile
- File2Function()
- END
- call writefile(file2, 'Xtest2.vim')
-
- let buf = RunVimInTerminal('-S Xtest1.vim', {})
-
- call RunDbgCmd(buf,
- \ ':debug call GlobalFunction()',
- \ ['cmd: call GlobalFunction()'])
-
- " FIXME: Vim9 lines are not debugged!
- call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
-
- " But they do appear in the backtrace
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 2 function GlobalFunction[1]',
- \ '\V 1 <SNR>\.\*_CallAFunction[1]',
- \ '\V->0 <SNR>\.\*_SourceAnotherFile',
- \ '\Vline 1: source Xtest2.vim'],
- \ #{match: 'pattern'})
-
-
- call RunDbgCmd(buf, 'step', ['line 1: vim9script'])
- call RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number'])
- call RunDbgCmd(buf, 'step', ['line 9: export def File2Function()'])
- call RunDbgCmd(buf, 'step', ['line 9: def File2Function()'])
- call RunDbgCmd(buf, 'step', ['line 13: defcompile'])
- call RunDbgCmd(buf, 'step', ['line 14: File2Function()'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 3 function GlobalFunction[1]',
- \ '\V 2 <SNR>\.\*_CallAFunction[1]',
- \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
- \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ '\Vline 14: File2Function()'],
- \ #{match: 'pattern'})
-
- " Don't step into compiled functions...
- call RunDbgCmd(buf, 'step', ['line 15: End of sourced file'])
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 3 function GlobalFunction[1]',
- \ '\V 2 <SNR>\.\*_CallAFunction[1]',
- \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
- \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
- \ '\Vline 15: End of sourced file'],
- \ #{match: 'pattern'})
-
-
- call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
-endfunc
-
-func Test_debug_backtrace_level()
- CheckRunVimInTerminal
- CheckCWD
- let lines =<< trim END
- let s:file1_var = 'file1'
- let g:global_var = 'global'
-
- func s:File1Func( arg )
- let s:file1_var .= a:arg
- let local_var = s:file1_var .. ' test1'
- let g:global_var .= local_var
- source Xtest2.vim
- endfunc
-
- call s:File1Func( 'arg1' )
- END
- call writefile(lines, 'Xtest1.vim')
-
- let lines =<< trim END
- let s:file2_var = 'file2'
-
- func s:File2Func( arg )
- let s:file2_var .= a:arg
- let local_var = s:file2_var .. ' test2'
- let g:global_var .= local_var
- endfunc
-
- call s:File2Func( 'arg2' )
- END
- call writefile(lines, 'Xtest2.vim')
-
- let file1 = getcwd() .. '/Xtest1.vim'
- let file2 = getcwd() .. '/Xtest2.vim'
-
- " set a breakpoint and source file1.vim
- let buf = RunVimInTerminal(
- \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim',
- \ #{wait_for_ruler: 0})
-
- call CheckDbgOutput(buf, [
- \ 'Breakpoint in "' .. file1 .. '" line 1',
- \ 'Entering Debug mode. Type "cont" to continue.',
- \ 'command line..script ' .. file1,
- \ 'line 1: let s:file1_var = ''file1'''
- \ ], #{msec: 5000})
-
- " step through the initial declarations
- call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] )
- call RunDbgCmd(buf, 'step', [ 'line 4: func s:File1Func( arg )' ] )
- call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
- call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
- call RunDbgCmd(buf, 'echo global_var', [ 'global' ] )
-
- " step in to the first function
- call RunDbgCmd(buf, 'step', [ 'line 11: call s:File1Func( ''arg1'' )' ] )
- call RunDbgCmd(buf, 'step', [ 'line 1: let s:file1_var .= a:arg' ] )
- call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
- call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
- call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
- call RunDbgCmd(buf,
- \'echo global_var',
- \[ 'E121: Undefined variable: global_var' ] )
- call RunDbgCmd(buf,
- \'echo local_var',
- \[ 'E121: Undefined variable: local_var' ] )
- call RunDbgCmd(buf,
- \'echo l:local_var',
- \[ 'E121: Undefined variable: l:local_var' ] )
-
- " backtrace up
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 2 command line',
- \ '\V 1 script ' .. file1 .. '[11]',
- \ '\V->0 function <SNR>\.\*_File1Func',
- \ '\Vline 1: let s:file1_var .= a:arg',
- \ ],
- \ #{ match: 'pattern' } )
- call RunDbgCmd(buf, 'up', [ '>up' ] )
-
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 2 command line',
- \ '\V->1 script ' .. file1 .. '[11]',
- \ '\V 0 function <SNR>\.\*_File1Func',
- \ '\Vline 1: let s:file1_var .= a:arg',
- \ ],
- \ #{ match: 'pattern' } )
-
- " Expression evaluation in the script frame (not the function frame)
- " FIXME: Unexpected in this scope (a: should not be visibnle)
- call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
- call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
- call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
- " FIXME: Unexpected in this scope (global should be found)
- call RunDbgCmd(buf,
- \'echo global_var',
- \[ 'E121: Undefined variable: global_var' ] )
- call RunDbgCmd(buf,
- \'echo local_var',
- \[ 'E121: Undefined variable: local_var' ] )
- call RunDbgCmd(buf,
- \'echo l:local_var',
- \[ 'E121: Undefined variable: l:local_var' ] )
-
-
- " step while backtraced jumps to the latest frame
- call RunDbgCmd(buf, 'step', [
- \ 'line 2: let local_var = s:file1_var .. '' test1''' ] )
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 2 command line',
- \ '\V 1 script ' .. file1 .. '[11]',
- \ '\V->0 function <SNR>\.\*_File1Func',
- \ '\Vline 2: let local_var = s:file1_var .. '' test1''',
- \ ],
- \ #{ match: 'pattern' } )
-
- call RunDbgCmd(buf, 'step', [ 'line 3: let g:global_var .= local_var' ] )
- call RunDbgCmd(buf, 'echo local_var', [ 'file1arg1 test1' ] )
- call RunDbgCmd(buf, 'echo l:local_var', [ 'file1arg1 test1' ] )
-
- call RunDbgCmd(buf, 'step', [ 'line 4: source Xtest2.vim' ] )
- call RunDbgCmd(buf, 'step', [ 'line 1: let s:file2_var = ''file2''' ] )
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 3 command line',
- \ '\V 2 script ' .. file1 .. '[11]',
- \ '\V 1 function <SNR>\.\*_File1Func[4]',
- \ '\V->0 script ' .. file2,
- \ '\Vline 1: let s:file2_var = ''file2''',
- \ ],
- \ #{ match: 'pattern' } )
-
- " Expression evaluation in the script frame file2 (not the function frame)
- call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
- call RunDbgCmd(buf,
- \ 'echo s:file1_var',
- \ [ 'E121: Undefined variable: s:file1_var' ] )
- call RunDbgCmd(buf, 'echo g:global_var', [ 'globalfile1arg1 test1' ] )
- call RunDbgCmd(buf, 'echo global_var', [ 'globalfile1arg1 test1' ] )
- call RunDbgCmd(buf,
- \'echo local_var',
- \[ 'E121: Undefined variable: local_var' ] )
- call RunDbgCmd(buf,
- \'echo l:local_var',
- \[ 'E121: Undefined variable: l:local_var' ] )
- call RunDbgCmd(buf,
- \ 'echo s:file2_var',
- \ [ 'E121: Undefined variable: s:file2_var' ] )
-
- call RunDbgCmd(buf, 'step', [ 'line 3: func s:File2Func( arg )' ] )
- call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
-
- " Up the stack to the other script context
- call RunDbgCmd(buf, 'up')
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 3 command line',
- \ '\V 2 script ' .. file1 .. '[11]',
- \ '\V->1 function <SNR>\.\*_File1Func[4]',
- \ '\V 0 script ' .. file2,
- \ '\Vline 3: func s:File2Func( arg )',
- \ ],
- \ #{ match: 'pattern' } )
- " FIXME: Unexpected. Should see the a: and l: dicts from File1Func
- call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
- call RunDbgCmd(buf,
- \ 'echo l:local_var',
- \ [ 'E121: Undefined variable: l:local_var' ] )
-
- call RunDbgCmd(buf, 'up')
- call RunDbgCmd(buf, 'backtrace', [
- \ '\V>backtrace',
- \ '\V 3 command line',
- \ '\V->2 script ' .. file1 .. '[11]',
- \ '\V 1 function <SNR>\.\*_File1Func[4]',
- \ '\V 0 script ' .. file2,
- \ '\Vline 3: func s:File2Func( arg )',
- \ ],
- \ #{ match: 'pattern' } )
-
- " FIXME: Unexpected (wrong script vars are used)
- call RunDbgCmd(buf,
- \ 'echo s:file1_var',
- \ [ 'E121: Undefined variable: s:file1_var' ] )
- call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
-
- call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
-endfunc
-
-" Test for setting a breakpoint on a :endif where the :if condition is false
-" and then quit the script. This should generate an interrupt.
-func Test_breakpt_endif_intr()
- func F()
- let g:Xpath ..= 'a'
- if v:false
- let g:Xpath ..= 'b'
- endif
- invalid_command
- endfunc
-
- let g:Xpath = ''
- breakadd func 4 F
- try
- let caught_intr = 0
- debuggreedy
- call feedkeys(":call F()\<CR>quit\<CR>", "xt")
- catch /^Vim:Interrupt$/
- call assert_match('\.F, line 4', v:throwpoint)
- let caught_intr = 1
- endtry
- 0debuggreedy
- call assert_equal(1, caught_intr)
- call assert_equal('a', g:Xpath)
- breakdel *
- delfunc F
-endfunc
-
-" Test for setting a breakpoint on a :else where the :if condition is false
-" and then quit the script. This should generate an interrupt.
-func Test_breakpt_else_intr()
- func F()
- let g:Xpath ..= 'a'
- if v:false
- let g:Xpath ..= 'b'
- else
- invalid_command
- endif
- invalid_command
- endfunc
-
- let g:Xpath = ''
- breakadd func 4 F
- try
- let caught_intr = 0
- debuggreedy
- call feedkeys(":call F()\<CR>quit\<CR>", "xt")
- catch /^Vim:Interrupt$/
- call assert_match('\.F, line 4', v:throwpoint)
- let caught_intr = 1
- endtry
- 0debuggreedy
- call assert_equal(1, caught_intr)
- call assert_equal('a', g:Xpath)
- breakdel *
- delfunc F
-endfunc
-
-" Test for setting a breakpoint on a :endwhile where the :while condition is
-" false and then quit the script. This should generate an interrupt.
-func Test_breakpt_endwhile_intr()
- func F()
- let g:Xpath ..= 'a'
- while v:false
- let g:Xpath ..= 'b'
- endwhile
- invalid_command
- endfunc
-
- let g:Xpath = ''
- breakadd func 4 F
- try
- let caught_intr = 0
- debuggreedy
- call feedkeys(":call F()\<CR>quit\<CR>", "xt")
- catch /^Vim:Interrupt$/
- call assert_match('\.F, line 4', v:throwpoint)
- let caught_intr = 1
- endtry
- 0debuggreedy
- call assert_equal(1, caught_intr)
- call assert_equal('a', g:Xpath)
- breakdel *
- delfunc F
-endfunc
-
-" Test for setting a breakpoint on a script local function
-func Test_breakpt_scriptlocal_func()
- let g:Xpath = ''
- func s:G()
- let g:Xpath ..= 'a'
- endfunc
-
- let funcname = expand("<SID>") .. "G"
- exe "breakadd func 1 " .. funcname
- debuggreedy
- redir => output
- call feedkeys(":call " .. funcname .. "()\<CR>c\<CR>", "xt")
- redir END
- 0debuggreedy
- call assert_match('Breakpoint in "' .. funcname .. '" line 1', output)
- call assert_equal('a', g:Xpath)
- breakdel *
- exe "delfunc " .. funcname
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_delete.vim b/src/nvim/testdir/test_delete.vim
deleted file mode 100644
index 6b49f153c6..0000000000
--- a/src/nvim/testdir/test_delete.vim
+++ /dev/null
@@ -1,110 +0,0 @@
-" Test for delete().
-
-source check.vim
-
-func Test_file_delete()
- split Xfile
- call setline(1, ['a', 'b'])
- wq
- call assert_equal(['a', 'b'], readfile('Xfile'))
- call assert_equal(0, delete('Xfile'))
- call assert_fails('call readfile("Xfile")', 'E484:')
- call assert_equal(-1, delete('Xfile'))
- bwipe Xfile
-endfunc
-
-func Test_dir_delete()
- call mkdir('Xdir1')
- call assert_true(isdirectory('Xdir1'))
- call assert_equal(0, delete('Xdir1', 'd'))
- call assert_false(isdirectory('Xdir1'))
- call assert_equal(-1, delete('Xdir1', 'd'))
-endfunc
-
-func Test_recursive_delete()
- call mkdir('Xdir1')
- call mkdir('Xdir1/subdir')
- call mkdir('Xdir1/empty')
- split Xdir1/Xfile
- call setline(1, ['a', 'b'])
- w
- w Xdir1/subdir/Xfile
- close
- call assert_true(isdirectory('Xdir1'))
- call assert_equal(['a', 'b'], readfile('Xdir1/Xfile'))
- call assert_true(isdirectory('Xdir1/subdir'))
- call assert_equal(['a', 'b'], readfile('Xdir1/subdir/Xfile'))
- call assert_true('Xdir1/empty'->isdirectory())
- call assert_equal(0, delete('Xdir1', 'rf'))
- call assert_false(isdirectory('Xdir1'))
- call assert_equal(-1, delete('Xdir1', 'd'))
- bwipe Xdir1/Xfile
- bwipe Xdir1/subdir/Xfile
-endfunc
-
-func Test_symlink_delete()
- CheckUnix
- split Xfile
- call setline(1, ['a', 'b'])
- wq
- silent !ln -s Xfile Xlink
- " Delete the link, not the file
- call assert_equal(0, delete('Xlink'))
- call assert_equal(-1, delete('Xlink'))
- call assert_equal(0, delete('Xfile'))
- bwipe Xfile
-endfunc
-
-func Test_symlink_dir_delete()
- CheckUnix
- call mkdir('Xdir1')
- silent !ln -s Xdir1 Xlink
- call assert_true(isdirectory('Xdir1'))
- call assert_true(isdirectory('Xlink'))
- " Delete the link, not the directory
- call assert_equal(0, delete('Xlink'))
- call assert_equal(-1, delete('Xlink'))
- call assert_equal(0, delete('Xdir1', 'd'))
-endfunc
-
-func Test_symlink_recursive_delete()
- CheckUnix
- call mkdir('Xdir3')
- call mkdir('Xdir3/subdir')
- call mkdir('Xdir4')
- split Xdir3/Xfile
- call setline(1, ['a', 'b'])
- w
- w Xdir3/subdir/Xfile
- w Xdir4/Xfile
- close
- silent !ln -s ../Xdir4 Xdir3/Xlink
-
- call assert_true(isdirectory('Xdir3'))
- call assert_equal(['a', 'b'], readfile('Xdir3/Xfile'))
- call assert_true(isdirectory('Xdir3/subdir'))
- call assert_equal(['a', 'b'], readfile('Xdir3/subdir/Xfile'))
- call assert_true(isdirectory('Xdir4'))
- call assert_true(isdirectory('Xdir3/Xlink'))
- call assert_equal(['a', 'b'], readfile('Xdir4/Xfile'))
-
- call assert_equal(0, delete('Xdir3', 'rf'))
- call assert_false(isdirectory('Xdir3'))
- call assert_equal(-1, delete('Xdir3', 'd'))
- " symlink is deleted, not the directory it points to
- call assert_true(isdirectory('Xdir4'))
- call assert_equal(['a', 'b'], readfile('Xdir4/Xfile'))
- call assert_equal(0, delete('Xdir4/Xfile'))
- call assert_equal(0, delete('Xdir4', 'd'))
-
- bwipe Xdir3/Xfile
- bwipe Xdir3/subdir/Xfile
- bwipe Xdir4/Xfile
-endfunc
-
-func Test_delete_errors()
- call assert_fails('call delete('''')', 'E474:')
- call assert_fails('call delete(''foo'', 0)', 'E15:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
deleted file mode 100644
index 0049398776..0000000000
--- a/src/nvim/testdir/test_diffmode.vim
+++ /dev/null
@@ -1,1647 +0,0 @@
-" Tests for diff mode
-
-source shared.vim
-source screendump.vim
-source check.vim
-
-func Test_diff_fold_sync()
- enew!
- let g:update_count = 0
- au DiffUpdated * let g:update_count += 1
-
- let l = range(50)
- call setline(1, l)
- diffthis
- let winone = win_getid()
- new
- let l[25] = 'diff'
- call setline(1, l)
- diffthis
- let wintwo = win_getid()
- " line 15 is inside the closed fold
- call assert_equal(19, foldclosedend(10))
- call win_gotoid(winone)
- call assert_equal(19, foldclosedend(10))
- " open the fold
- normal zv
- call assert_equal(-1, foldclosedend(10))
- " fold in other window must have opened too
- call win_gotoid(wintwo)
- call assert_equal(-1, foldclosedend(10))
-
- " cursor position is in sync
- normal 23G
- call win_gotoid(winone)
- call assert_equal(23, getcurpos()[1])
-
- " depending on how redraw is done DiffUpdated may be triggered once or twice
- call assert_inrange(1, 2, g:update_count)
- au! DiffUpdated
-
- windo diffoff
- close!
- set nomodified
-endfunc
-
-func Test_vert_split()
- set diffopt=filler
- call Common_vert_split()
- set diffopt&
-endfunc
-
-func Test_vert_split_internal()
- set diffopt=internal,filler
- call Common_vert_split()
- set diffopt&
-endfunc
-
-func Common_vert_split()
- " Disable the title to avoid xterm keeping the wrong one.
- set notitle noicon
- new
- let l = ['1 aa', '2 bb', '3 cc', '4 dd', '5 ee']
- call setline(1, l)
- w! Xtest
- normal dd
- $
- put
- normal kkrXoxxx
- w! Xtest2
- file Nop
- normal ggoyyyjjjozzzz
- set foldmethod=marker foldcolumn=4
- call assert_equal(0, &diff)
- call assert_equal('marker', &foldmethod)
- call assert_equal('4', &foldcolumn)
- call assert_equal(0, &scrollbind)
- call assert_equal(0, &cursorbind)
- call assert_equal(1, &wrap)
-
- vert diffsplit Xtest
- vert diffsplit Xtest2
- call assert_equal(1, &diff)
- call assert_equal('diff', &foldmethod)
- call assert_equal('2', &foldcolumn)
- call assert_equal(1, &scrollbind)
- call assert_equal(1, &cursorbind)
- call assert_equal(0, &wrap)
-
- let diff_fdm = &fdm
- let diff_fdc = &fdc
- " repeat entering diff mode here to see if this saves the wrong settings
- diffthis
- " jump to second window for a moment to have filler line appear at start of
- " first window
- wincmd w
- normal gg
- wincmd p
- normal gg
- call assert_equal(2, winline())
- normal j
- call assert_equal(4, winline())
- normal j
- call assert_equal(5, winline())
- normal j
- call assert_equal(6, winline())
- normal j
- call assert_equal(8, winline())
- normal j
- call assert_equal(9, winline())
-
- wincmd w
- normal gg
- call assert_equal(1, winline())
- normal j
- call assert_equal(2, winline())
- normal j
- call assert_equal(4, winline())
- normal j
- call assert_equal(5, winline())
- normal j
- call assert_equal(8, winline())
-
- wincmd w
- normal gg
- call assert_equal(2, winline())
- normal j
- call assert_equal(3, winline())
- normal j
- call assert_equal(4, winline())
- normal j
- call assert_equal(5, winline())
- normal j
- call assert_equal(6, winline())
- normal j
- call assert_equal(7, winline())
- normal j
- call assert_equal(8, winline())
-
- " Test diffoff
- diffoff!
- 1wincmd w
- let &diff = 1
- let &fdm = diff_fdm
- let &fdc = diff_fdc
- 4wincmd w
- diffoff!
- 1wincmd w
- call assert_equal(0, &diff)
- call assert_equal('marker', &foldmethod)
- call assert_equal('4', &foldcolumn)
- call assert_equal(0, &scrollbind)
- call assert_equal(0, &cursorbind)
- call assert_equal(1, &wrap)
-
- wincmd w
- call assert_equal(0, &diff)
- call assert_equal('marker', &foldmethod)
- call assert_equal('4', &foldcolumn)
- call assert_equal(0, &scrollbind)
- call assert_equal(0, &cursorbind)
- call assert_equal(1, &wrap)
-
- wincmd w
- call assert_equal(0, &diff)
- call assert_equal('marker', &foldmethod)
- call assert_equal('4', &foldcolumn)
- call assert_equal(0, &scrollbind)
- call assert_equal(0, &cursorbind)
- call assert_equal(1, &wrap)
-
- call delete('Xtest')
- call delete('Xtest2')
- windo bw!
-endfunc
-
-func Test_filler_lines()
- " Test that diffing shows correct filler lines
- enew!
- put =range(4,10)
- 1d _
- vnew
- put =range(1,10)
- 1d _
- windo diffthis
- wincmd h
- call assert_equal(1, line('w0'))
- unlet! diff_fdm diff_fdc
- windo diffoff
- bwipe!
- enew!
-endfunc
-
-func Test_diffget_diffput()
- enew!
- let l = range(50)
- call setline(1, l)
- call assert_fails('diffget', 'E99:')
- diffthis
- call assert_fails('diffget', 'E100:')
- new
- let l[10] = 'one'
- let l[20] = 'two'
- let l[30] = 'three'
- let l[40] = 'four'
- call setline(1, l)
- diffthis
- call assert_equal('one', getline(11))
- 11diffget
- call assert_equal('10', getline(11))
- 21diffput
- wincmd w
- call assert_equal('two', getline(21))
- normal 31Gdo
- call assert_equal('three', getline(31))
- call assert_equal('40', getline(41))
- normal 41Gdp
- wincmd w
- call assert_equal('40', getline(41))
- new
- diffthis
- call assert_fails('diffget', 'E101:')
-
- windo diffoff
- %bwipe!
-endfunc
-
-" Test putting two changes from one buffer to another
-func Test_diffput_two()
- new a
- let win_a = win_getid()
- call setline(1, range(1, 10))
- diffthis
- new b
- let win_b = win_getid()
- call setline(1, range(1, 10))
- 8del
- 5del
- diffthis
- call win_gotoid(win_a)
- %diffput
- call win_gotoid(win_b)
- call assert_equal(map(range(1, 10), 'string(v:val)'), getline(1, '$'))
- bwipe! a
- bwipe! b
-endfunc
-
-" Test for :diffget/:diffput with a range that is inside a diff chunk
-func Test_diffget_diffput_range()
- call setline(1, range(1, 10))
- new
- call setline(1, range(11, 20))
- windo diffthis
- 3,5diffget
- call assert_equal(['13', '14', '15'], getline(3, 5))
- call setline(1, range(1, 10))
- 4,8diffput
- wincmd p
- call assert_equal(['13', '4', '5', '6', '7', '8', '19'], getline(3, 9))
- %bw!
-endfunc
-
-" Test for :diffget/:diffput with an empty buffer and a non-empty buffer
-func Test_diffget_diffput_empty_buffer()
- %d _
- new
- call setline(1, 'one')
- windo diffthis
- diffget
- call assert_equal(['one'], getline(1, '$'))
- %d _
- diffput
- wincmd p
- call assert_equal([''], getline(1, '$'))
- %bw!
-endfunc
-
-" :diffput and :diffget completes names of buffers which
-" are in diff mode and which are different then current buffer.
-" No completion when the current window is not in diff mode.
-func Test_diffget_diffput_completion()
- e Xdiff1 | diffthis
- botright new Xdiff2
- botright new Xdiff3 | split | diffthis
- botright new Xdiff4 | diffthis
-
- wincmd t
- call assert_equal('Xdiff1', bufname('%'))
- call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffput Xdiff3 Xdiff4', @:)
- call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffget Xdiff3 Xdiff4', @:)
- call assert_equal(['Xdiff3', 'Xdiff4'], getcompletion('', 'diff_buffer'))
-
- " Xdiff2 is not in diff mode, so no completion for :diffput, :diffget
- wincmd j
- call assert_equal('Xdiff2', bufname('%'))
- call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffput ', @:)
- call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffget ', @:)
- call assert_equal([], getcompletion('', 'diff_buffer'))
-
- " Xdiff3 is split in 2 windows, only the top one is in diff mode.
- " So completion of :diffput :diffget only happens in the top window.
- wincmd j
- call assert_equal('Xdiff3', bufname('%'))
- call assert_equal(1, &diff)
- call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffput Xdiff1 Xdiff4', @:)
- call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffget Xdiff1 Xdiff4', @:)
- call assert_equal(['Xdiff1', 'Xdiff4'], getcompletion('', 'diff_buffer'))
-
- wincmd j
- call assert_equal('Xdiff3', bufname('%'))
- call assert_equal(0, &diff)
- call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffput ', @:)
- call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffget ', @:)
- call assert_equal([], getcompletion('', 'diff_buffer'))
-
- wincmd j
- call assert_equal('Xdiff4', bufname('%'))
- call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffput Xdiff1 Xdiff3', @:)
- call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"diffget Xdiff1 Xdiff3', @:)
- call assert_equal(['Xdiff1', 'Xdiff3'], getcompletion('', 'diff_buffer'))
-
- %bwipe
-endfunc
-
-func Test_dp_do_buffer()
- e! one
- let bn1=bufnr('%')
- let l = range(60)
- call setline(1, l)
- diffthis
-
- new two
- let l[10] = 'one'
- let l[20] = 'two'
- let l[30] = 'three'
- let l[40] = 'four'
- let l[50] = 'five'
- call setline(1, l)
- diffthis
-
- " dp and do with invalid buffer number.
- 11
- call assert_fails('norm 99999dp', 'E102:')
- call assert_fails('norm 99999do', 'E102:')
- call assert_fails('diffput non_existing_buffer', 'E94:')
- call assert_fails('diffget non_existing_buffer', 'E94:')
-
- " dp and do with valid buffer number.
- call assert_equal('one', getline('.'))
- exe 'norm ' . bn1 . 'do'
- call assert_equal('10', getline('.'))
- 21
- call assert_equal('two', getline('.'))
- diffget one
- call assert_equal('20', getline('.'))
-
- 31
- exe 'norm ' . bn1 . 'dp'
- 41
- diffput one
- wincmd w
- 31
- call assert_equal('three', getline('.'))
- 41
- call assert_equal('four', getline('.'))
-
- " dp and do with buffer number which is not in diff mode.
- new not_in_diff_mode
- let bn3=bufnr('%')
- wincmd w
- 51
- call assert_fails('exe "norm" . bn3 . "dp"', 'E103:')
- call assert_fails('exe "norm" . bn3 . "do"', 'E103:')
- call assert_fails('diffput not_in_diff_mode', 'E94:')
- call assert_fails('diffget not_in_diff_mode', 'E94:')
-
- windo diffoff
- %bwipe!
-endfunc
-
-func Test_do_lastline()
- e! one
- call setline(1, ['1','2','3','4','5','6'])
- diffthis
-
- new two
- call setline(1, ['2','4','5'])
- diffthis
-
- 1
- norm dp]c
- norm dp]c
- wincmd w
- call assert_equal(4, line('$'))
- norm G
- norm do
- call assert_equal(3, line('$'))
-
- windo diffoff
- %bwipe!
-endfunc
-
-func Test_diffoff()
- enew!
- call setline(1, ['Two', 'Three'])
- redraw
- let normattr = screenattr(1, 1)
- diffthis
- botright vert new
- call setline(1, ['One', '', 'Two', 'Three'])
- diffthis
- redraw
- call assert_notequal(normattr, 1->screenattr(1))
- diffoff!
- redraw
- call assert_equal(normattr, screenattr(1, 1))
- bwipe!
- bwipe!
-endfunc
-
-func Common_icase_test()
- edit one
- call setline(1, ['One', 'Two', 'Three', 'Four', 'Fi#ve'])
- redraw
- let normattr = screenattr(1, 1)
- diffthis
-
- botright vert new two
- call setline(1, ['one', 'TWO', 'Three ', 'Four', 'fI=VE'])
- diffthis
-
- redraw
- call assert_equal(normattr, screenattr(1, 1))
- call assert_equal(normattr, screenattr(2, 1))
- call assert_notequal(normattr, screenattr(3, 1))
- call assert_equal(normattr, screenattr(4, 1))
-
- let dtextattr = screenattr(5, 3)
- call assert_notequal(dtextattr, screenattr(5, 1))
- call assert_notequal(dtextattr, screenattr(5, 5))
-
- diffoff!
- %bwipe!
-endfunc
-
-func Test_diffopt_icase()
- set diffopt=icase,foldcolumn:0
- call Common_icase_test()
- set diffopt&
-endfunc
-
-func Test_diffopt_icase_internal()
- set diffopt=icase,foldcolumn:0,internal
- call Common_icase_test()
- set diffopt&
-endfunc
-
-func Common_iwhite_test()
- edit one
- " Difference in trailing spaces and amount of spaces should be ignored,
- " but not other space differences.
- call setline(1, ["One \t", 'Two', 'Three', 'one two', 'one two', 'Four'])
- redraw
- let normattr = screenattr(1, 1)
- diffthis
-
- botright vert new two
- call setline(1, ["One\t ", "Two\t ", 'Three', 'one two', 'onetwo', ' Four'])
- diffthis
-
- redraw
- call assert_equal(normattr, screenattr(1, 1))
- call assert_equal(normattr, screenattr(2, 1))
- call assert_equal(normattr, screenattr(3, 1))
- call assert_equal(normattr, screenattr(4, 1))
- call assert_notequal(normattr, screenattr(5, 1))
- call assert_notequal(normattr, screenattr(6, 1))
-
- diffoff!
- %bwipe!
-endfunc
-
-func Test_diffopt_iwhite()
- set diffopt=iwhite,foldcolumn:0
- call Common_iwhite_test()
- set diffopt&
-endfunc
-
-func Test_diffopt_iwhite_internal()
- set diffopt=internal,iwhite,foldcolumn:0
- call Common_iwhite_test()
- set diffopt&
-endfunc
-
-func Test_diffopt_context()
- enew!
- call setline(1, ['1', '2', '3', '4', '5', '6', '7'])
- diffthis
- new
- call setline(1, ['1', '2', '3', '4', '5x', '6', '7'])
- diffthis
-
- set diffopt=context:2
- call assert_equal('+-- 2 lines: 1', foldtextresult(1))
- set diffopt=internal,context:2
- call assert_equal('+-- 2 lines: 1', foldtextresult(1))
-
- set diffopt=context:1
- call assert_equal('+-- 3 lines: 1', foldtextresult(1))
- set diffopt=internal,context:1
- call assert_equal('+-- 3 lines: 1', foldtextresult(1))
-
- diffoff!
- %bwipe!
- set diffopt&
-endfunc
-
-func Test_diffopt_horizontal()
- set diffopt=internal,horizontal
- diffsplit
-
- call assert_equal(&columns, winwidth(1))
- call assert_equal(&columns, winwidth(2))
- call assert_equal(&lines, winheight(1) + winheight(2) + 3)
- call assert_inrange(0, 1, winheight(1) - winheight(2))
-
- set diffopt&
- diffoff!
- %bwipe
-endfunc
-
-func Test_diffopt_vertical()
- set diffopt=internal,vertical
- diffsplit
-
- call assert_equal(&lines - 2, winheight(1))
- call assert_equal(&lines - 2, winheight(2))
- call assert_equal(&columns, winwidth(1) + winwidth(2) + 1)
- call assert_inrange(0, 1, winwidth(1) - winwidth(2))
-
- set diffopt&
- diffoff!
- %bwipe
-endfunc
-
-func Test_diffopt_hiddenoff()
- set diffopt=internal,filler,foldcolumn:0,hiddenoff
- e! one
- call setline(1, ['Two', 'Three'])
- redraw
- let normattr = screenattr(1, 1)
- diffthis
- botright vert new two
- call setline(1, ['One', 'Four'])
- diffthis
- redraw
- call assert_notequal(normattr, screenattr(1, 1))
- set hidden
- close
- redraw
- " should not diffing with hidden buffer two while 'hiddenoff' is enabled
- call assert_equal(normattr, screenattr(1, 1))
-
- bwipe!
- bwipe!
- set hidden& diffopt&
-endfunc
-
-func Test_diffoff_hidden()
- set diffopt=internal,filler,foldcolumn:0
- e! one
- call setline(1, ['Two', 'Three'])
- redraw
- let normattr = screenattr(1, 1)
- diffthis
- botright vert new two
- call setline(1, ['One', 'Four'])
- diffthis
- redraw
- call assert_notequal(normattr, screenattr(1, 1))
- set hidden
- close
- redraw
- " diffing with hidden buffer two
- call assert_notequal(normattr, screenattr(1, 1))
- diffoff
- redraw
- call assert_equal(normattr, screenattr(1, 1))
- diffthis
- redraw
- " still diffing with hidden buffer two
- call assert_notequal(normattr, screenattr(1, 1))
- diffoff!
- redraw
- call assert_equal(normattr, screenattr(1, 1))
- diffthis
- redraw
- " no longer diffing with hidden buffer two
- call assert_equal(normattr, screenattr(1, 1))
-
- bwipe!
- bwipe!
- set hidden& diffopt&
-endfunc
-
-func Test_setting_cursor()
- new Xtest1
- put =range(1,90)
- wq
- new Xtest2
- put =range(1,100)
- wq
-
- tabe Xtest2
- $
- diffsp Xtest1
- tabclose
-
- call delete('Xtest1')
- call delete('Xtest2')
-endfunc
-
-func Test_diff_move_to()
- new
- call setline(1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- diffthis
- vnew
- call setline(1, [1, '2x', 3, 4, 4, 5, '6x', 7, '8x', 9, '10x'])
- diffthis
- norm ]c
- call assert_equal(2, line('.'))
- norm 3]c
- call assert_equal(9, line('.'))
- norm 10]c
- call assert_equal(11, line('.'))
- norm [c
- call assert_equal(9, line('.'))
- norm 2[c
- call assert_equal(5, line('.'))
- norm 10[c
- call assert_equal(2, line('.'))
- %bwipe!
-endfunc
-
-func Test_diffexpr()
- CheckExecutable diff
-
- func DiffExpr()
- " Prepend some text to check diff type detection
- call writefile(['warning', ' message'], v:fname_out)
- silent exe '!diff ' . v:fname_in . ' ' . v:fname_new . '>>' . v:fname_out
- endfunc
- set diffexpr=DiffExpr()
- set diffopt=foldcolumn:0
-
- enew!
- call setline(1, ['one', 'two', 'three'])
- redraw
- let normattr = screenattr(1, 1)
- diffthis
-
- botright vert new
- call setline(1, ['one', 'two', 'three.'])
- diffthis
-
- redraw
- call assert_equal(normattr, screenattr(1, 1))
- call assert_equal(normattr, screenattr(2, 1))
- call assert_notequal(normattr, screenattr(3, 1))
- diffoff!
-
- " Try using an non-existing function for 'diffexpr'.
- set diffexpr=NewDiffFunc()
- call assert_fails('windo diffthis', ['E117:', 'E97:'])
- diffoff!
-
- " Using a script-local function
- func s:NewDiffExpr()
- endfunc
- set diffexpr=s:NewDiffExpr()
- call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr)
- set diffexpr=<SID>NewDiffExpr()
- call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr)
-
- %bwipe!
- set diffexpr& diffopt&
- delfunc DiffExpr
- delfunc s:NewDiffExpr
-endfunc
-
-func Test_diffpatch()
- " The patch program on MS-Windows may fail or hang.
- CheckExecutable patch
- CheckUnix
- new
- insert
-***************
-*** 1,3 ****
- 1
-! 2
- 3
---- 1,4 ----
- 1
-! 2x
- 3
-+ 4
-.
- saveas! Xpatch
- bwipe!
- new
- call assert_fails('diffpatch Xpatch', 'E816:')
-
- for name in ['Xpatch', 'Xpatch$HOME', 'Xpa''tch']
- call setline(1, ['1', '2', '3'])
- if name != 'Xpatch'
- call rename('Xpatch', name)
- endif
- exe 'diffpatch ' . escape(name, '$')
- call assert_equal(['1', '2x', '3', '4'], getline(1, '$'))
- if name != 'Xpatch'
- call rename(name, 'Xpatch')
- endif
- bwipe!
- endfor
-
- call delete('Xpatch')
- bwipe!
-endfunc
-
-func Test_diff_too_many_buffers()
- for i in range(1, 8)
- exe "new Xtest" . i
- diffthis
- endfor
- new Xtest9
- call assert_fails('diffthis', 'E96:')
- %bwipe!
-endfunc
-
-func Test_diff_nomodifiable()
- new
- call setline(1, [1, 2, 3, 4])
- setl nomodifiable
- diffthis
- vnew
- call setline(1, ['1x', 2, 3, 3, 4])
- diffthis
- call assert_fails('norm dp', 'E793:')
- setl nomodifiable
- call assert_fails('norm do', 'E21:')
- %bwipe!
-endfunc
-
-func Test_diff_filler()
- new
- call setline(1, [1, 2, 3, 'x', 4])
- diffthis
- vnew
- call setline(1, [1, 2, 'y', 'y', 3, 4])
- diffthis
- redraw
-
- call assert_equal([0, 0, 0, 0, 0, 0, 0, 1, 0], map(range(-1, 7), 'v:val->diff_filler()'))
- wincmd w
- call assert_equal([0, 0, 0, 0, 2, 0, 0, 0], map(range(-1, 6), 'diff_filler(v:val)'))
-
- %bwipe!
-endfunc
-
-func Test_diff_hlID()
- new
- call setline(1, [1, 2, 3])
- diffthis
- vnew
- call setline(1, ['1x', 2, 'x', 3])
- diffthis
- redraw
-
- call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("")
-
- call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange")
- call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText")
- call diff_hlID(2, 1)->synIDattr("name")->assert_equal("")
- call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd")
- eval 4->diff_hlID(1)->synIDattr("name")->assert_equal("")
-
- wincmd w
- call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange")
- call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "")
- call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "")
-
- %bwipe!
-endfunc
-
-func Test_diff_lastline()
- enew!
- only!
- call setline(1, ['This is a ', 'line with five ', 'rows'])
- diffthis
- botright vert new
- call setline(1, ['This is', 'a line with ', 'four rows'])
- diffthis
- 1
- call feedkeys("Je a\<CR>", 'tx')
- call feedkeys("Je a\<CR>", 'tx')
- let w1lines = winline()
- wincmd w
- $
- let w2lines = winline()
- call assert_equal(w2lines, w1lines)
- bwipe!
- bwipe!
-endfunc
-
-func WriteDiffFiles(buf, list1, list2)
- call writefile(a:list1, 'Xfile1')
- call writefile(a:list2, 'Xfile2')
- if a:buf
- call term_sendkeys(a:buf, ":checktime\<CR>")
- endif
-endfunc
-" Verify a screendump with both the internal and external diff.
-func VerifyBoth(buf, dumpfile, extra)
- " trailing : for leaving the cursor on the command line
- for cmd in [":set diffopt=filler" . a:extra . "\<CR>:", ":set diffopt+=internal\<CR>:"]
- call term_sendkeys(a:buf, cmd)
- if VerifyScreenDump(a:buf, a:dumpfile, {}, cmd =~ 'internal' ? 'internal' : 'external')
- break " don't let the next iteration overwrite the "failed" file.
- " don't let the next iteration overwrite the "failed" file.
- return
- endif
- endfor
-
- " also test unified diff
- call term_sendkeys(a:buf, ":call SetupUnified()\<CR>:")
- call term_sendkeys(a:buf, ":redraw!\<CR>:")
- call VerifyScreenDump(a:buf, a:dumpfile, {}, 'unified')
- call term_sendkeys(a:buf, ":call StopUnified()\<CR>:")
-endfunc
-
-" Verify a screendump with the internal diff only.
-func VerifyInternal(buf, dumpfile, extra)
- call term_sendkeys(a:buf, ":diffupdate!\<CR>")
- " trailing : for leaving the cursor on the command line
- call term_sendkeys(a:buf, ":set diffopt=internal,filler" . a:extra . "\<CR>:")
- call TermWait(a:buf)
- call VerifyScreenDump(a:buf, a:dumpfile, {})
-endfunc
-
-func Test_diff_screen()
- CheckScreendump
- CheckFeature menu
-
- let lines =<< trim END
- func UnifiedDiffExpr()
- " Prepend some text to check diff type detection
- call writefile(['warning', ' message'], v:fname_out)
- silent exe '!diff -U0 ' .. v:fname_in .. ' ' .. v:fname_new .. '>>' .. v:fname_out
- endfunc
- func SetupUnified()
- set diffexpr=UnifiedDiffExpr()
- diffupdate
- endfunc
- func StopUnified()
- set diffexpr=
- endfunc
- END
- call writefile(lines, 'XdiffSetup')
-
- " clean up already existing swap files, just in case
- call delete('.Xfile1.swp')
- call delete('.Xfile2.swp')
-
- " Test 1: Add a line in beginning of file 2
- call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- let buf = RunVimInTerminal('-d -S XdiffSetup Xfile1 Xfile2', {})
- " Set autoread mode, so that Vim won't complain once we re-write the test
- " files
- call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
-
- call VerifyBoth(buf, 'Test_diff_01', '')
-
- " Test 2: Add a line in beginning of file 1
- call WriteDiffFiles(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- call VerifyBoth(buf, 'Test_diff_02', '')
-
- " Test 3: Add a line at the end of file 2
- call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
- call VerifyBoth(buf, 'Test_diff_03', '')
-
- " Test 4: Add a line at the end of file 1
- call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- call VerifyBoth(buf, 'Test_diff_04', '')
-
- " Test 5: Add a line in the middle of file 2, remove on at the end of file 1
- call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10])
- call VerifyBoth(buf, 'Test_diff_05', '')
-
- " Test 6: Add a line in the middle of file 1, remove on at the end of file 2
- call WriteDiffFiles(buf, [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
- call VerifyBoth(buf, 'Test_diff_06', '')
-
- " Variants on test 6 with different context settings
- call term_sendkeys(buf, ":set diffopt+=context:2\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_06.2', {})
- call term_sendkeys(buf, ":set diffopt-=context:2\<cr>")
- call term_sendkeys(buf, ":set diffopt+=context:1\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_06.1', {})
- call term_sendkeys(buf, ":set diffopt-=context:1\<cr>")
- call term_sendkeys(buf, ":set diffopt+=context:0\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_06.0', {})
- call term_sendkeys(buf, ":set diffopt-=context:0\<cr>")
-
- " Test 7 - 9: Test normal/patience/histogram diff algorithm
- call WriteDiffFiles(buf, ['#include <stdio.h>', '', '// Frobs foo heartily', 'int frobnitz(int foo)', '{',
- \ ' int i;', ' for(i = 0; i < 10; i++)', ' {', ' printf("Your answer is: ");',
- \ ' printf("%d\n", foo);', ' }', '}', '', 'int fact(int n)', '{', ' if(n > 1)', ' {',
- \ ' return fact(n-1) * n;', ' }', ' return 1;', '}', '', 'int main(int argc, char **argv)',
- \ '{', ' frobnitz(fact(10));', '}'],
- \ ['#include <stdio.h>', '', 'int fib(int n)', '{', ' if(n > 2)', ' {',
- \ ' return fib(n-1) + fib(n-2);', ' }', ' return 1;', '}', '', '// Frobs foo heartily',
- \ 'int frobnitz(int foo)', '{', ' int i;', ' for(i = 0; i < 10; i++)', ' {',
- \ ' printf("%d\n", foo);', ' }', '}', '',
- \ 'int main(int argc, char **argv)', '{', ' frobnitz(fib(10));', '}'])
- call term_sendkeys(buf, ":diffupdate!\<cr>")
- call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_07', {})
-
- call term_sendkeys(buf, ":set diffopt+=algorithm:patience\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_08', {})
-
- call term_sendkeys(buf, ":set diffopt+=algorithm:histogram\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_09', {})
-
- " Test 10-11: normal/indent-heuristic
- call term_sendkeys(buf, ":set diffopt&vim\<cr>")
- call WriteDiffFiles(buf, ['', ' def finalize(values)', '', ' values.each do |v|', ' v.finalize', ' end'],
- \ ['', ' def finalize(values)', '', ' values.each do |v|', ' v.prepare', ' end', '',
- \ ' values.each do |v|', ' v.finalize', ' end'])
- call term_sendkeys(buf, ":diffupdate!\<cr>")
- call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_10', {})
-
- " Leave trailing : at commandline!
- call term_sendkeys(buf, ":set diffopt+=indent-heuristic\<cr>:\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_11', {}, 'one')
- " shouldn't matter, if indent-algorithm comes before or after the algorithm
- call term_sendkeys(buf, ":set diffopt&\<cr>")
- call term_sendkeys(buf, ":set diffopt+=indent-heuristic,algorithm:patience\<cr>:\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_11', {}, 'two')
- call term_sendkeys(buf, ":set diffopt&\<cr>")
- call term_sendkeys(buf, ":set diffopt+=algorithm:patience,indent-heuristic\<cr>:\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_11', {}, 'three')
-
- " Test 12: diff the same file
- call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- call VerifyBoth(buf, 'Test_diff_12', '')
-
- " Test 13: diff an empty file
- call WriteDiffFiles(buf, [], [])
- call VerifyBoth(buf, 'Test_diff_13', '')
-
- " Test 14: test diffopt+=icase
- call WriteDiffFiles(buf, ['a', 'b', 'cd'], ['A', 'b', 'cDe'])
- call VerifyBoth(buf, 'Test_diff_14', " diffopt+=filler diffopt+=icase")
-
- " Test 15-16: test diffopt+=iwhite
- call WriteDiffFiles(buf, ['int main()', '{', ' printf("Hello, World!");', ' return 0;', '}'],
- \ ['int main()', '{', ' if (0)', ' {', ' printf("Hello, World!");', ' return 0;', ' }', '}'])
- call term_sendkeys(buf, ":diffupdate!\<cr>")
- call term_sendkeys(buf, ":set diffopt&vim diffopt+=filler diffopt+=iwhite\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_15', {})
- call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_16', {})
-
- " Test 17: test diffopt+=iblank
- call WriteDiffFiles(buf, ['a', ' ', 'cd', 'ef', 'xxx'], ['a', 'cd', '', 'ef', 'yyy'])
- call VerifyInternal(buf, 'Test_diff_17', " diffopt+=iblank")
-
- " Test 18: test diffopt+=iblank,iwhite / iwhiteall / iwhiteeol
- call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhite")
- call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteall")
- call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteeol")
-
- " Test 19: test diffopt+=iwhiteeol
- call WriteDiffFiles(buf, ['a ', 'x', 'cd', 'ef', 'xx xx', 'foo', 'bar'], ['a', 'x', 'c d', ' ef', 'xx xx', 'foo', '', 'bar'])
- call VerifyInternal(buf, 'Test_diff_19', " diffopt+=iwhiteeol")
-
- " Test 19: test diffopt+=iwhiteall
- call VerifyInternal(buf, 'Test_diff_20', " diffopt+=iwhiteall")
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('XdiffSetup')
-endfunc
-
-func Test_diff_with_scroll_and_change()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, range(1, 15))
- vnew
- call setline(1, range(9, 15))
- windo diffthis
- wincmd h
- exe "normal Gl5\<C-E>"
- END
- call writefile(lines, 'Xtest_scroll_change')
- let buf = RunVimInTerminal('-S Xtest_scroll_change', {})
-
- call VerifyScreenDump(buf, 'Test_diff_scroll_change_01', {})
-
- call term_sendkeys(buf, "ax\<Esc>")
- call VerifyScreenDump(buf, 'Test_diff_scroll_change_02', {})
-
- call term_sendkeys(buf, "\<C-W>lay\<Esc>")
- call VerifyScreenDump(buf, 'Test_diff_scroll_change_03', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_scroll_change')
-endfunc
-
-func Test_diff_with_cursorline()
- CheckScreendump
-
- call writefile([
- \ 'hi CursorLine ctermbg=red ctermfg=white',
- \ 'set cursorline',
- \ 'call setline(1, ["foo","foo","foo","bar"])',
- \ 'vnew',
- \ 'call setline(1, ["bee","foo","foo","baz"])',
- \ 'windo diffthis',
- \ '2wincmd w',
- \ ], 'Xtest_diff_cursorline')
- let buf = RunVimInTerminal('-S Xtest_diff_cursorline', {})
-
- call VerifyScreenDump(buf, 'Test_diff_with_cursorline_01', {})
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_diff_with_cursorline_02', {})
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_diff_with_cursorline_03', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_diff_cursorline')
-endfunc
-
-func Test_diff_with_cursorline_number()
- CheckScreendump
-
- let lines =<< trim END
- hi CursorLine ctermbg=red ctermfg=white
- hi CursorLineNr ctermbg=white ctermfg=black cterm=underline
- set cursorline number
- call setline(1, ["baz", "foo", "foo", "bar"])
- 2
- vnew
- call setline(1, ["foo", "foo", "bar"])
- windo diffthis
- 1wincmd w
- END
- call writefile(lines, 'Xtest_diff_cursorline_number')
- let buf = RunVimInTerminal('-S Xtest_diff_cursorline_number', {})
-
- call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_01', {})
- call term_sendkeys(buf, ":set cursorlineopt=number\r")
- call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_02', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_diff_cursorline_number')
-endfunc
-
-func Test_diff_with_cursorline_breakindent()
- CheckScreendump
-
- call writefile([
- \ 'hi CursorLine ctermbg=red ctermfg=white',
- \ 'set noequalalways wrap diffopt=followwrap cursorline breakindent',
- \ '50vnew',
- \ 'call setline(1, [" "," "," "," "])',
- \ 'exe "norm 20Afoo\<Esc>j20Afoo\<Esc>j20Afoo\<Esc>j20Abar\<Esc>"',
- \ 'vnew',
- \ 'call setline(1, [" "," "," "," "])',
- \ 'exe "norm 20Abee\<Esc>j20Afoo\<Esc>j20Afoo\<Esc>j20Abaz\<Esc>"',
- \ 'windo diffthis',
- \ '2wincmd w',
- \ ], 'Xtest_diff_cursorline_breakindent')
- let buf = RunVimInTerminal('-S Xtest_diff_cursorline_breakindent', {})
-
- call term_sendkeys(buf, "gg0")
- call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_01', {})
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_02', {})
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_03', {})
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_04', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_diff_cursorline_breakindent')
-endfunc
-
-func Test_diff_with_syntax()
- CheckScreendump
-
- let lines =<< trim END
- void doNothing() {
- int x = 0;
- char *s = "hello";
- return 5;
- }
- END
- call writefile(lines, 'Xprogram1.c')
- let lines =<< trim END
- void doSomething() {
- int x = 0;
- char *s = "there";
- return 5;
- }
- END
- call writefile(lines, 'Xprogram2.c')
-
- let lines =<< trim END
- edit Xprogram1.c
- diffsplit Xprogram2.c
- END
- call writefile(lines, 'Xtest_diff_syntax')
- let buf = RunVimInTerminal('-S Xtest_diff_syntax', {})
-
- call VerifyScreenDump(buf, 'Test_diff_syntax_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_diff_syntax')
- call delete('Xprogram1.c')
- call delete('Xprogram2.c')
-endfunc
-
-func Test_diff_of_diff()
- CheckScreendump
- CheckFeature rightleft
-
- call writefile([
- \ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])',
- \ 'vnew',
- \ 'call setline(1, ["aa","bb","cc"])',
- \ 'windo diffthis',
- \ ], 'Xtest_diff_diff')
- let buf = RunVimInTerminal('-S Xtest_diff_diff', {})
-
- call VerifyScreenDump(buf, 'Test_diff_of_diff_01', {})
-
- call term_sendkeys(buf, ":set rightleft\<cr>")
- call VerifyScreenDump(buf, 'Test_diff_of_diff_02', {})
-
- " clean up
- 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_followwrap()
- new
- set diffopt+=followwrap
- set wrap
- diffthis
- call assert_equal(1, &wrap)
- diffoff
- set nowrap
- diffthis
- call assert_equal(0, &wrap)
- diffoff
- set diffopt&
- bwipe!
-endfunc
-
-func Test_diff_maintains_change_mark()
- func DiffMaintainsChangeMark()
- enew!
- call setline(1, ['a', 'b', 'c', 'd'])
- diffthis
- new
- call setline(1, ['a', 'b', 'c', 'e'])
- " Set '[ and '] marks
- 2,3yank
- call assert_equal([2, 3], [line("'["), line("']")])
- " Verify they aren't affected by the implicit diff
- diffthis
- call assert_equal([2, 3], [line("'["), line("']")])
- " Verify they aren't affected by an explicit diff
- diffupdate
- call assert_equal([2, 3], [line("'["), line("']")])
- bwipe!
- bwipe!
- endfunc
-
- set diffopt-=internal
- call DiffMaintainsChangeMark()
- set diffopt+=internal
- call DiffMaintainsChangeMark()
-
- set diffopt&
- delfunc DiffMaintainsChangeMark
-endfunc
-
-" Test for 'patchexpr'
-func Test_patchexpr()
- let g:patch_args = []
- func TPatch()
- call add(g:patch_args, readfile(v:fname_in))
- call add(g:patch_args, readfile(v:fname_diff))
- call writefile(['output file'], v:fname_out)
- endfunc
- set patchexpr=TPatch()
-
- call writefile(['input file'], 'Xinput')
- call writefile(['diff file'], 'Xdiff')
- %bwipe!
- edit Xinput
- diffpatch Xdiff
- call assert_equal('output file', getline(1))
- call assert_equal('Xinput.new', bufname())
- call assert_equal(2, winnr('$'))
- call assert_true(&diff)
-
- " Using a script-local function
- func s:NewPatchExpr()
- endfunc
- set patchexpr=s:NewPatchExpr()
- call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr)
- set patchexpr=<SID>NewPatchExpr()
- call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr)
-
- call delete('Xinput')
- call delete('Xdiff')
- set patchexpr&
- delfunc TPatch
- delfunc s:NewPatchExpr
- %bwipe!
-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_multilineconceal()
- new
- diffthis
-
- new
- call matchadd('Conceal', 'a\nb', 9, -1, {'conceal': 'Y'})
- set cole=2 cocu=n
- call setline(1, ["a", "b"])
- diffthis
- redraw
-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
-
-func Test_diff_filler_cursorcolumn()
- CheckScreendump
-
- let content =<< trim END
- call setline(1, ['aa', 'bb', 'cc'])
- vnew
- call setline(1, ['aa', 'cc'])
- windo diffthis
- wincmd p
- setlocal cursorcolumn foldcolumn=0
- norm! gg0
- redraw!
- END
- call writefile(content, 'Xtest_diff_cuc')
- let buf = RunVimInTerminal('-S Xtest_diff_cuc', {})
-
- call VerifyScreenDump(buf, 'Test_diff_cuc_01', {})
-
- call term_sendkeys(buf, "l")
- call term_sendkeys(buf, "\<C-l>")
- call VerifyScreenDump(buf, 'Test_diff_cuc_02', {})
- call term_sendkeys(buf, "0j")
- call term_sendkeys(buf, "\<C-l>")
- call VerifyScreenDump(buf, 'Test_diff_cuc_03', {})
- call term_sendkeys(buf, "l")
- call term_sendkeys(buf, "\<C-l>")
- call VerifyScreenDump(buf, 'Test_diff_cuc_04', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_diff_cuc')
-endfunc
-
-" Test for adding/removing lines inside diff chunks, between diff chunks
-" and before diff chunks
-func Test_diff_modify_chunks()
- enew!
- let w2_id = win_getid()
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
- new
- let w1_id = win_getid()
- call setline(1, ['a', '2', '3', 'd', 'e', 'f', '7', '8', 'i'])
- windo diffthis
-
- " remove a line between two diff chunks and create a new diff chunk
- call win_gotoid(w2_id)
- 5d
- call win_gotoid(w1_id)
- call diff_hlID(5, 1)->synIDattr('name')->assert_equal('DiffAdd')
-
- " add a line between two diff chunks
- call win_gotoid(w2_id)
- normal! 4Goe
- call win_gotoid(w1_id)
- call diff_hlID(4, 1)->synIDattr('name')->assert_equal('')
- call diff_hlID(5, 1)->synIDattr('name')->assert_equal('')
-
- " remove all the lines in a diff chunk.
- call win_gotoid(w2_id)
- 7,8d
- call win_gotoid(w1_id)
- let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
- call assert_equal(['', 'DiffText', 'DiffText', '', '', '', 'DiffAdd',
- \ 'DiffAdd', ''], hl)
-
- " remove lines from one diff chunk to just before the next diff chunk
- call win_gotoid(w2_id)
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
- 2,6d
- call win_gotoid(w1_id)
- let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
- call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', 'DiffAdd',
- \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl)
-
- " remove lines just before the top of a diff chunk
- call win_gotoid(w2_id)
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
- 5,6d
- call win_gotoid(w1_id)
- let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
- call assert_equal(['', 'DiffText', 'DiffText', '', 'DiffText', 'DiffText',
- \ 'DiffAdd', 'DiffAdd', ''], hl)
-
- " remove line after the end of a diff chunk
- call win_gotoid(w2_id)
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
- 4d
- call win_gotoid(w1_id)
- let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
- call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', '', '', 'DiffText',
- \ 'DiffText', ''], hl)
-
- " remove lines starting from the end of one diff chunk and ending inside
- " another diff chunk
- call win_gotoid(w2_id)
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
- 4,7d
- call win_gotoid(w1_id)
- let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
- call assert_equal(['', 'DiffText', 'DiffText', 'DiffText', 'DiffAdd',
- \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl)
-
- " removing the only remaining diff chunk should make the files equal
- call win_gotoid(w2_id)
- call setline(1, ['a', '2', '3', 'x', 'd', 'e', 'f', 'x', '7', '8', 'i'])
- 8d
- let hl = range(1, 10)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
- call assert_equal(['', '', '', 'DiffAdd', '', '', '', '', '', ''], hl)
- call win_gotoid(w2_id)
- 4d
- call win_gotoid(w1_id)
- let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
- call assert_equal(['', '', '', '', '', '', '', '', ''], hl)
-
- %bw!
-endfunc
-
-func Test_diff_binary()
- CheckScreendump
-
- let content =<< trim END
- call setline(1, ['a', 'b', "c\n", 'd', 'e', 'f', 'g'])
- vnew
- call setline(1, ['A', 'b', 'c', 'd', 'E', 'f', 'g'])
- windo diffthis
- wincmd p
- norm! gg0
- redraw!
- END
- call writefile(content, 'Xtest_diff_bin')
- let buf = RunVimInTerminal('-S Xtest_diff_bin', {})
-
- " Test using internal diff
- call VerifyScreenDump(buf, 'Test_diff_bin_01', {})
-
- " Test using internal diff and case folding
- call term_sendkeys(buf, ":set diffopt+=icase\<cr>")
- call term_sendkeys(buf, "\<C-l>")
- call VerifyScreenDump(buf, 'Test_diff_bin_02', {})
- " Test using external diff
- call term_sendkeys(buf, ":set diffopt=filler\<cr>")
- call term_sendkeys(buf, "\<C-l>")
- call VerifyScreenDump(buf, 'Test_diff_bin_03', {})
- " Test using external diff and case folding
- call term_sendkeys(buf, ":set diffopt=filler,icase\<cr>")
- call term_sendkeys(buf, "\<C-l>")
- call VerifyScreenDump(buf, 'Test_diff_bin_04', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_diff_bin')
- set diffopt&vim
-endfunc
-
-" Test for using the 'zi' command to invert 'foldenable' in diff windows (test
-" for the issue fixed by patch 6.2.317)
-func Test_diff_foldinvert()
- %bw!
- edit Xfile1
- new Xfile2
- new Xfile3
- windo diffthis
- " open a non-diff window
- botright new
- 1wincmd w
- call assert_true(getwinvar(1, '&foldenable'))
- call assert_true(getwinvar(2, '&foldenable'))
- call assert_true(getwinvar(3, '&foldenable'))
- normal zi
- call assert_false(getwinvar(1, '&foldenable'))
- call assert_false(getwinvar(2, '&foldenable'))
- call assert_false(getwinvar(3, '&foldenable'))
- normal zi
- call assert_true(getwinvar(1, '&foldenable'))
- call assert_true(getwinvar(2, '&foldenable'))
- call assert_true(getwinvar(3, '&foldenable'))
-
- " If the current window has 'noscrollbind', then 'zi' should not change
- " 'foldenable' in other windows.
- 1wincmd w
- set noscrollbind
- normal zi
- call assert_false(getwinvar(1, '&foldenable'))
- call assert_true(getwinvar(2, '&foldenable'))
- call assert_true(getwinvar(3, '&foldenable'))
-
- " 'zi' should not change the 'foldenable' for windows with 'noscrollbind'
- 1wincmd w
- set scrollbind
- normal zi
- call setwinvar(2, '&scrollbind', v:false)
- normal zi
- call assert_false(getwinvar(1, '&foldenable'))
- call assert_true(getwinvar(2, '&foldenable'))
- call assert_false(getwinvar(3, '&foldenable'))
-
- %bw!
- set scrollbind&
-endfunc
-
-" This was scrolling for 'cursorbind' but 'scrollbind' is more important
-func Test_diff_scroll()
- CheckScreendump
-
- let left =<< trim END
- line 1
- line 2
- line 3
- line 4
-
- // Common block
- // one
- // containing
- // four lines
-
- // Common block
- // two
- // containing
- // four lines
- END
- call writefile(left, 'Xleft')
- let right =<< trim END
- line 1
- line 2
- line 3
- line 4
-
- Lorem
- ipsum
- dolor
- sit
- amet,
- consectetur
- adipiscing
- elit.
- Etiam
- luctus
- lectus
- sodales,
- dictum
-
- // Common block
- // one
- // containing
- // four lines
-
- Vestibulum
- tincidunt
- aliquet
- nulla.
-
- // Common block
- // two
- // containing
- // four lines
- END
- call writefile(right, 'Xright')
- let buf = RunVimInTerminal('-d Xleft Xright', {'rows': 12})
- call term_sendkeys(buf, "\<C-W>\<C-W>jjjj")
- call VerifyScreenDump(buf, 'Test_diff_scroll_1', {})
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_diff_scroll_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xleft')
- call delete('Xright')
-endfunc
-
-" This was trying to update diffs for a buffer being closed
-func Test_diff_only()
- silent! lfile
- set diff
- lopen
- norm o
- silent! norm o
-
- set nodiff
- %bwipe!
-endfunc
-
-" This was causing invalid diff block values
-" FIXME: somehow this causes a valgrind error when run directly but not when
-" run as a test.
-func Test_diff_manipulations()
- set diff
- split 0
- sil! norm R doobdeuR doobdeuR doobdeu
-
- set nodiff
- %bwipe!
-endfunc
-
-" This was causing the line number in the diff block to go below one.
-" FIXME: somehow this causes a valgrind error when run directly but not when
-" run as a test.
-func Test_diff_put_and_undo()
- set diff
- next 0
- split 00
- sil! norm o0gguudpo0ggJuudp
-
- bwipe!
- bwipe!
- set nodiff
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
deleted file mode 100644
index f08dff8605..0000000000
--- a/src/nvim/testdir/test_digraph.vim
+++ /dev/null
@@ -1,604 +0,0 @@
-" Tests for digraphs
-
-source check.vim
-CheckFeature digraphs
-source term_util.vim
-
-func Put_Dig(chars)
- exe "norm! o\<c-k>".a:chars
-endfu
-
-func Put_Dig_BS(char1, char2)
- exe "norm! o".a:char1."\<bs>".a:char2
-endfu
-
-func Test_digraphs()
- new
- call Put_Dig("00")
- call assert_equal("∞", getline('.'))
- " not a digraph
- call Put_Dig("el")
- call assert_equal("l", getline('.'))
- call Put_Dig("ht")
- call assert_equal("þ", getline('.'))
- " digraph "ab" is the same as "ba"
- call Put_Dig("ab")
- call Put_Dig("ba")
- call assert_equal(["ã°","ã°"], getline(line('.')-1,line('.')))
- " Euro sign
- call Put_Dig("e=")
- call Put_Dig("=e")
- call Put_Dig("Eu")
- call Put_Dig("uE")
- call assert_equal(['е']+repeat(["€"],3), getline(line('.')-3,line('.')))
- " Rouble sign
- call Put_Dig("R=")
- call Put_Dig("=R")
- call Put_Dig("=P")
- call Put_Dig("P=")
- call assert_equal(['Р']+repeat(["₽"],2)+['П'], getline(line('.')-3,line('.')))
- " Not a digraph
- call Put_Dig("a\<bs>")
- call Put_Dig("\<bs>a")
- call assert_equal(["<BS>", "<BS>a"], getline(line('.')-1,line('.')))
- " Grave
- call Put_Dig("a!")
- call Put_Dig("!e")
- call Put_Dig("b!") " not defined
- call assert_equal(["à", "è", "!"], getline(line('.')-2,line('.')))
- " Acute accent
- call Put_Dig("a'")
- call Put_Dig("'e")
- call Put_Dig("b'") " not defined
- call assert_equal(["á", "é", "'"], getline(line('.')-2,line('.')))
- " Cicumflex
- call Put_Dig("a>")
- call Put_Dig(">e")
- call Put_Dig("b>") " not defined
- call assert_equal(['â', 'ê', '>'], getline(line('.')-2,line('.')))
- " Tilde
- call Put_Dig("o~")
- call Put_Dig("~u") " not defined
- call Put_Dig("z~") " not defined
- call assert_equal(['õ', 'u', '~'], getline(line('.')-2,line('.')))
- " Tilde
- call Put_Dig("o?")
- call Put_Dig("?u")
- call Put_Dig("z?") " not defined
- call assert_equal(['õ', 'ũ', '?'], getline(line('.')-2,line('.')))
- " Macron
- call Put_Dig("o-")
- call Put_Dig("-u")
- call Put_Dig("z-") " not defined
- call assert_equal(['Å', 'Å«', '-'], getline(line('.')-2,line('.')))
- " Breve
- call Put_Dig("o(")
- call Put_Dig("(u")
- call Put_Dig("z(") " not defined
- call assert_equal(['Å', 'Å­', '('], getline(line('.')-2,line('.')))
- " Dot above
- call Put_Dig("b.")
- call Put_Dig(".e")
- call Put_Dig("a.") " not defined
- call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
- " Diaeresis
- call Put_Dig("a:")
- call Put_Dig(":u")
- call Put_Dig("b:") " not defined
- call assert_equal(['ä', 'ü', ':'], getline(line('.')-2,line('.')))
- " Cedilla
- call Put_Dig("',")
- call Put_Dig(",C")
- call Put_Dig("b,") " not defined
- call assert_equal(['¸', 'Ç', ','], getline(line('.')-2,line('.')))
- " Underline
- call Put_Dig("B_")
- call Put_Dig("_t")
- call Put_Dig("a_") " not defined
- call assert_equal(['Ḇ', 'ṯ', '_'], getline(line('.')-2,line('.')))
- " Stroke
- call Put_Dig("j/")
- call Put_Dig("/l")
- call Put_Dig("b/") " not defined
- call assert_equal(['/', 'Å‚', '/'], getline(line('.')-2,line('.')))
- " Double acute
- call Put_Dig('O"')
- call Put_Dig('"y')
- call Put_Dig('b"') " not defined
- call assert_equal(['Å', 'ÿ', '"'], getline(line('.')-2,line('.')))
- " Ogonek
- call Put_Dig('u;')
- call Put_Dig(';E')
- call Put_Dig('b;') " not defined
- call assert_equal(['ų', 'Ę', ';'], getline(line('.')-2,line('.')))
- " Caron
- call Put_Dig('u<')
- call Put_Dig('<E')
- call Put_Dig('b<') " not defined
- call assert_equal(['Ç”', 'Äš', '<'], getline(line('.')-2,line('.')))
- " Ring above
- call Put_Dig('u0')
- call Put_Dig('0E') " not defined
- call Put_Dig('b0') " not defined
- call assert_equal(['ů', 'E', '0'], getline(line('.')-2,line('.')))
- " Hook
- call Put_Dig('u2')
- call Put_Dig('2E')
- call Put_Dig('b2') " not defined
- call assert_equal(['ủ', 'Ẻ', '2'], getline(line('.')-2,line('.')))
- " Horn
- call Put_Dig('u9')
- call Put_Dig('9E') " not defined
- call Put_Dig('b9') " not defined
- call assert_equal(['ư', 'E', '9'], getline(line('.')-2,line('.')))
- " Cyrillic
- call Put_Dig('u=')
- call Put_Dig('=b')
- call Put_Dig('=_')
- call assert_equal(['у', 'б', '〓'], getline(line('.')-2,line('.')))
- " Greek
- call Put_Dig('u*')
- call Put_Dig('*b')
- call Put_Dig('*_')
- call assert_equal(['υ', 'β', '々'], getline(line('.')-2,line('.')))
- " Greek/Cyrillic special
- call Put_Dig('u%')
- call Put_Dig('%b') " not defined
- call Put_Dig('%_') " not defined
- call assert_equal(['Ï', 'b', '_'], getline(line('.')-2,line('.')))
- " Arabic
- call Put_Dig('u+')
- call Put_Dig('+b')
- call Put_Dig('+_') " japanese industrial symbol
- call assert_equal(['+', 'ب', '〄'], getline(line('.')-2,line('.')))
- " Hebrew
- call Put_Dig('Q+')
- call Put_Dig('+B')
- call Put_Dig('+X')
- call assert_equal(['ק', 'ב', 'ח'], getline(line('.')-2,line('.')))
- " Latin
- call Put_Dig('a3')
- call Put_Dig('A3')
- call Put_Dig('3X')
- call assert_equal(['Ç£', 'Ç¢', 'X'], getline(line('.')-2,line('.')))
- " Bopomofo
- call Put_Dig('a4')
- call Put_Dig('A4')
- call Put_Dig('4X')
- call assert_equal(['ㄚ', '4', 'X'], getline(line('.')-2,line('.')))
- " Hiragana
- call Put_Dig('a5')
- call Put_Dig('A5')
- call Put_Dig('5X')
- call assert_equal(['ã‚', 'ã', 'X'], getline(line('.')-2,line('.')))
- " Katakana
- call Put_Dig('a6')
- call Put_Dig('A6')
- call Put_Dig('6X')
- call assert_equal(['ã‚¡', 'ã‚¢', 'X'], getline(line('.')-2,line('.')))
- " Superscripts
- call Put_Dig('1S')
- call Put_Dig('2S')
- call Put_Dig('3S')
- call assert_equal(['¹', '²', '³'], getline(line('.')-2,line('.')))
- " Subscripts
- call Put_Dig('1s')
- call Put_Dig('2s')
- call Put_Dig('3s')
- call assert_equal(['â‚', 'â‚‚', '₃'], getline(line('.')-2,line('.')))
- " Eszet (only lowercase)
- call Put_Dig("ss")
- call Put_Dig("SS") " start of string
- call assert_equal(["ß", "˜"], getline(line('.')-1,line('.')))
- " High bit set
- call Put_Dig("a ")
- call Put_Dig(" A")
- call assert_equal(['á', 'Ã'], getline(line('.')-1,line('.')))
- " Escape is not part of a digraph
- call Put_Dig("a\<esc>")
- call Put_Dig("\<esc>A")
- call assert_equal(['', 'A'], getline(line('.')-1,line('.')))
- " define some custom digraphs
- " old: 00 ∞
- " old: el l
- digraph 00 9216
- digraph el 0252
- call Put_Dig("00")
- call Put_Dig("el")
- " Reset digraphs
- digraph 00 8734
- digraph el 108
- call Put_Dig("00")
- call Put_Dig("el")
- call assert_equal(['â€', 'ü', '∞', 'l'], getline(line('.')-3,line('.')))
- call assert_fails('exe "digraph a\<Esc> 100"', 'E104:')
- call assert_fails('exe "digraph \<Esc>a 100"', 'E104:')
- call assert_fails('digraph xy z', 'E39:')
- call assert_fails('digraph x', 'E1214:')
- bw!
-endfunc
-
-func Test_digraphs_option()
- " reset whichwrap option, so that testing <esc><bs>A works,
- " without moving up a line
- set digraph ww=
- new
- call Put_Dig_BS("0","0")
- call assert_equal("∞", getline('.'))
- " not a digraph
- call Put_Dig_BS("e","l")
- call assert_equal("l", getline('.'))
- call Put_Dig_BS("h","t")
- call assert_equal("þ", getline('.'))
- " digraph "ab" is the same as "ba"
- call Put_Dig_BS("a","b")
- call Put_Dig_BS("b","a")
- call assert_equal(["ã°","ã°"], getline(line('.')-1,line('.')))
- " Euro sign
- call Put_Dig_BS("e","=")
- call Put_Dig_BS("=","e")
- call Put_Dig_BS("E","u")
- call Put_Dig_BS("u","E")
- call assert_equal(['е']+repeat(["€"],3), getline(line('.')-3,line('.')))
- " Rouble sign
- call Put_Dig_BS("R","=")
- call Put_Dig_BS("=","R")
- call Put_Dig_BS("=","P")
- call Put_Dig_BS("P","=")
- call assert_equal(['Р']+repeat(["₽"],2)+['П'], getline(line('.')-3,line('.')))
- " Not a digraph: this is different from <c-k>!
- call Put_Dig_BS("a","\<bs>")
- call Put_Dig_BS("\<bs>","a")
- call assert_equal(['','a'], getline(line('.')-1,line('.')))
- " Grave
- call Put_Dig_BS("a","!")
- call Put_Dig_BS("!","e")
- call Put_Dig_BS("b","!") " not defined
- call assert_equal(["à", "è", "!"], getline(line('.')-2,line('.')))
- " Acute accent
- call Put_Dig_BS("a","'")
- call Put_Dig_BS("'","e")
- call Put_Dig_BS("b","'") " not defined
- call assert_equal(["á", "é", "'"], getline(line('.')-2,line('.')))
- " Cicumflex
- call Put_Dig_BS("a",">")
- call Put_Dig_BS(">","e")
- call Put_Dig_BS("b",">") " not defined
- call assert_equal(['â', 'ê', '>'], getline(line('.')-2,line('.')))
- " Tilde
- call Put_Dig_BS("o","~")
- call Put_Dig_BS("~","u") " not defined
- call Put_Dig_BS("z","~") " not defined
- call assert_equal(['õ', 'u', '~'], getline(line('.')-2,line('.')))
- " Tilde
- call Put_Dig_BS("o","?")
- call Put_Dig_BS("?","u")
- call Put_Dig_BS("z","?") " not defined
- call assert_equal(['õ', 'ũ', '?'], getline(line('.')-2,line('.')))
- " Macron
- call Put_Dig_BS("o","-")
- call Put_Dig_BS("-","u")
- call Put_Dig_BS("z","-") " not defined
- call assert_equal(['Å', 'Å«', '-'], getline(line('.')-2,line('.')))
- " Breve
- call Put_Dig_BS("o","(")
- call Put_Dig_BS("(","u")
- call Put_Dig_BS("z","(") " not defined
- call assert_equal(['Å', 'Å­', '('], getline(line('.')-2,line('.')))
- " Dot above
- call Put_Dig_BS("b",".")
- call Put_Dig_BS(".","e")
- call Put_Dig_BS("a",".") " not defined
- call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
- " Diaeresis
- call Put_Dig_BS("a",":")
- call Put_Dig_BS(":","u")
- call Put_Dig_BS("b",":") " not defined
- call assert_equal(['ä', 'ü', ':'], getline(line('.')-2,line('.')))
- " Cedilla
- call Put_Dig_BS("'",",")
- call Put_Dig_BS(",","C")
- call Put_Dig_BS("b",",") " not defined
- call assert_equal(['¸', 'Ç', ','], getline(line('.')-2,line('.')))
- " Underline
- call Put_Dig_BS("B","_")
- call Put_Dig_BS("_","t")
- call Put_Dig_BS("a","_") " not defined
- call assert_equal(['Ḇ', 'ṯ', '_'], getline(line('.')-2,line('.')))
- " Stroke
- call Put_Dig_BS("j","/")
- call Put_Dig_BS("/","l")
- call Put_Dig_BS("b","/") " not defined
- call assert_equal(['/', 'Å‚', '/'], getline(line('.')-2,line('.')))
- " Double acute
- call Put_Dig_BS('O','"')
- call Put_Dig_BS('"','y')
- call Put_Dig_BS('b','"') " not defined
- call assert_equal(['Å', 'ÿ', '"'], getline(line('.')-2,line('.')))
- " Ogonek
- call Put_Dig_BS('u',';')
- call Put_Dig_BS(';','E')
- call Put_Dig_BS('b',';') " not defined
- call assert_equal(['ų', 'Ę', ';'], getline(line('.')-2,line('.')))
- " Caron
- call Put_Dig_BS('u','<')
- call Put_Dig_BS('<','E')
- call Put_Dig_BS('b','<') " not defined
- call assert_equal(['Ç”', 'Äš', '<'], getline(line('.')-2,line('.')))
- " Ring above
- call Put_Dig_BS('u','0')
- call Put_Dig_BS('0','E') " not defined
- call Put_Dig_BS('b','0') " not defined
- call assert_equal(['ů', 'E', '0'], getline(line('.')-2,line('.')))
- " Hook
- call Put_Dig_BS('u','2')
- call Put_Dig_BS('2','E')
- call Put_Dig_BS('b','2') " not defined
- call assert_equal(['ủ', 'Ẻ', '2'], getline(line('.')-2,line('.')))
- " Horn
- call Put_Dig_BS('u','9')
- call Put_Dig_BS('9','E') " not defined
- call Put_Dig_BS('b','9') " not defined
- call assert_equal(['ư', 'E', '9'], getline(line('.')-2,line('.')))
- " Cyrillic
- call Put_Dig_BS('u','=')
- call Put_Dig_BS('=','b')
- call Put_Dig_BS('=','_')
- call assert_equal(['у', 'б', '〓'], getline(line('.')-2,line('.')))
- " Greek
- call Put_Dig_BS('u','*')
- call Put_Dig_BS('*','b')
- call Put_Dig_BS('*','_')
- call assert_equal(['υ', 'β', '々'], getline(line('.')-2,line('.')))
- " Greek/Cyrillic special
- call Put_Dig_BS('u','%')
- call Put_Dig_BS('%','b') " not defined
- call Put_Dig_BS('%','_') " not defined
- call assert_equal(['Ï', 'b', '_'], getline(line('.')-2,line('.')))
- " Arabic
- call Put_Dig_BS('u','+')
- call Put_Dig_BS('+','b')
- call Put_Dig_BS('+','_') " japanese industrial symbol
- call assert_equal(['+', 'ب', '〄'], getline(line('.')-2,line('.')))
- " Hebrew
- call Put_Dig_BS('Q','+')
- call Put_Dig_BS('+','B')
- call Put_Dig_BS('+','X')
- call assert_equal(['ק', 'ב', 'ח'], getline(line('.')-2,line('.')))
- " Latin
- call Put_Dig_BS('a','3')
- call Put_Dig_BS('A','3')
- call Put_Dig_BS('3','X')
- call assert_equal(['Ç£', 'Ç¢', 'X'], getline(line('.')-2,line('.')))
- " Bopomofo
- call Put_Dig_BS('a','4')
- call Put_Dig_BS('A','4')
- call Put_Dig_BS('4','X')
- call assert_equal(['ㄚ', '4', 'X'], getline(line('.')-2,line('.')))
- " Hiragana
- call Put_Dig_BS('a','5')
- call Put_Dig_BS('A','5')
- call Put_Dig_BS('5','X')
- call assert_equal(['ã‚', 'ã', 'X'], getline(line('.')-2,line('.')))
- " Katakana
- call Put_Dig_BS('a','6')
- call Put_Dig_BS('A','6')
- call Put_Dig_BS('6','X')
- call assert_equal(['ã‚¡', 'ã‚¢', 'X'], getline(line('.')-2,line('.')))
- " Superscripts
- call Put_Dig_BS('1','S')
- call Put_Dig_BS('2','S')
- call Put_Dig_BS('3','S')
- call assert_equal(['¹', '²', '³'], getline(line('.')-2,line('.')))
- " Subscripts
- call Put_Dig_BS('1','s')
- call Put_Dig_BS('2','s')
- call Put_Dig_BS('3','s')
- call assert_equal(['â‚', 'â‚‚', '₃'], getline(line('.')-2,line('.')))
- " Eszet (only lowercase)
- call Put_Dig_BS("s","s")
- call Put_Dig_BS("S","S") " start of string
- call assert_equal(["ß", "˜"], getline(line('.')-1,line('.')))
- " High bit set (different from <c-k>)
- call Put_Dig_BS("a"," ")
- call Put_Dig_BS(" ","A")
- call assert_equal([' ', 'A'], getline(line('.')-1,line('.')))
- " Escape is not part of a digraph (different from <c-k>)
- call Put_Dig_BS("a","\<esc>")
- call Put_Dig_BS("\<esc>","A")
- call assert_equal(['', ''], getline(line('.')-1,line('.')))
- " define some custom digraphs
- " old: 00 ∞
- " old: el l
- digraph 00 9216
- digraph el 0252
- call Put_Dig_BS("0","0")
- call Put_Dig_BS("e","l")
- " Reset digraphs
- digraph 00 8734
- digraph el 108
- call Put_Dig_BS("0","0")
- call Put_Dig_BS("e","l")
- call assert_equal(['â€', 'ü', '∞', 'l'], getline(line('.')-3,line('.')))
- set nodigraph ww&vim
- bw!
-endfunc
-
-func Test_digraphs_output()
- new
- let out = execute(':digraph')
- call assert_equal('Eu € 8364', matchstr(out, '\C\<Eu\D*8364\>'))
- call assert_equal('=e € 8364', matchstr(out, '\C=e\D*8364\>'))
- call assert_equal('=R ₽ 8381', matchstr(out, '\C=R\D*8381\>'))
- call assert_equal('=P ₽ 8381', matchstr(out, '\C=P\D*8381\>'))
- call assert_equal('o: ö 246', matchstr(out, '\C\<o:\D*246\>'))
- call assert_equal('v4 ㄪ 12586', matchstr(out, '\C\<v4\D*12586\>'))
- call assert_equal("'0 Ëš 730", matchstr(out, '\C''0\D*730\>'))
- 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
-
-func Test_loadkeymap()
- if !has('keymap')
- return
- endif
- new
- set keymap=czech
- set iminsert=0
- call feedkeys("o|\<c-^>|01234567890|\<esc>", 'tx')
- call assert_equal("|'é+ěšÄřžýáíé'", getline('.'))
- " reset keymap and encoding option
- set keymap=
- bw!
-endfunc
-
-func Test_digraph_cmndline()
- " Create digraph on commandline
- call feedkeys(":\"\<c-k>Eu\<cr>", 'xt')
- call assert_equal('"€', @:)
-
- " Canceling a CTRL-K on the cmdline
- call feedkeys(":\"a\<c-k>\<esc>b\<cr>", 'xt')
- call assert_equal('"ab', @:)
-endfunc
-
-func Test_show_digraph()
- new
- call Put_Dig("e=")
- call assert_equal("\n<е> 1077, Hex 0435, Oct 2065, Digr e=", execute('ascii'))
- bwipe!
-endfunc
-
-func Test_show_digraph_cp1251()
- throw 'skipped: Nvim supports ''utf8'' encoding only'
- 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
-
-" Test for error in a keymap file
-func Test_loadkeymap_error()
- if !has('keymap')
- return
- endif
- call assert_fails('loadkeymap', 'E105:')
- call writefile(['loadkeymap', 'a'], 'Xkeymap')
- call assert_fails('source Xkeymap', 'E791:')
- call delete('Xkeymap')
-endfunc
-
-" Test for the characters displayed on the screen when entering a digraph
-func Test_entering_digraph()
- CheckRunVimInTerminal
- let buf = RunVimInTerminal('', {'rows': 6})
- call term_sendkeys(buf, "i\<C-K>")
- call term_wait(buf)
- call assert_equal('?', term_getline(buf, 1))
- call term_sendkeys(buf, "1")
- call term_wait(buf)
- call assert_equal('1', term_getline(buf, 1))
- call term_sendkeys(buf, "2")
- call term_wait(buf)
- call assert_equal('½', term_getline(buf, 1))
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_digraph_set_function()
- new
- call digraph_set('aa', 'ã‚')
- call Put_Dig('aa')
- call assert_equal('ã‚', getline('$'))
- call digraph_set(' i', 'ã„')
- call Put_Dig(' i')
- call assert_equal('ã„', getline('$'))
- call digraph_set(' ', 'ã†')
- call Put_Dig(' ')
- call assert_equal('ã†', getline('$'))
-
- eval 'aa'->digraph_set('ãˆ')
- call Put_Dig('aa')
- call assert_equal('ãˆ', getline('$'))
-
- call assert_fails('call digraph_set("aaa", "ã‚")', 'E1214: Digraph must be just two characters: aaa')
- call assert_fails('call digraph_set("b", "ã‚")', 'E1214: Digraph must be just two characters: b')
- call assert_fails('call digraph_set("ã‚", "ã‚")', 'E1214: Digraph must be just two characters: ã‚')
- call assert_fails('call digraph_set("aa", "ã‚ã‚")', 'E1215: Digraph must be one character: ã‚ã‚')
- call assert_fails('call digraph_set("aa", "ã‹" .. nr2char(0x3099))', 'E1215: Digraph must be one character: ã‹' .. nr2char(0x3099))
- bwipe!
-endfunc
-
-func Test_digraph_get_function()
- " Built-in digraphs
- call assert_equal('∞', digraph_get('00'))
-
- " User-defined digraphs
- call digraph_set('aa', 'ã‚')
- call digraph_set(' i', 'ã„')
- call digraph_set(' ', 'ã†')
- call assert_equal('ã‚', digraph_get('aa'))
- call assert_equal('ã‚', 'aa'->digraph_get())
- call assert_equal('ã„', digraph_get(' i'))
- call assert_equal('ã†', digraph_get(' '))
- call assert_fails('call digraph_get("aaa")', 'E1214: Digraph must be just two characters: aaa')
- call assert_fails('call digraph_get("b")', 'E1214: Digraph must be just two characters: b')
-endfunc
-
-func Test_digraph_get_function_encode()
- throw 'Skipped: Nvim does not support setting encoding=japan'
- CheckFeature iconv
-
- let testcases = {
- \'00': '∞',
- \'aa': 'ã‚',
- \}
- for [key, ch] in items(testcases)
- call digraph_set(key, ch)
- set encoding=japan
- call assert_equal(iconv(ch, 'utf-8', 'japan'), digraph_get(key))
- set encoding=utf-8
- endfor
-endfunc
-
-func Test_digraph_setlist_function()
- call digraph_setlist([['aa', 'ã'], ['bb', 'ã']])
- call assert_equal('ã', digraph_get('aa'))
- call assert_equal('ã', digraph_get('bb'))
-
- call assert_fails('call digraph_setlist([[]])', 'E1216:')
- call assert_fails('call digraph_setlist([["aa", "b", "cc"]])', '1216:')
- call assert_fails('call digraph_setlist([["ã‚", "ã‚"]])', 'E1214: Digraph must be just two characters: ã‚')
-endfunc
-
-func Test_digraph_getlist_function()
- " Make sure user-defined digraphs are defined
- call digraph_setlist([['aa', 'ã'], ['bb', 'ã']])
-
- for pair in digraph_getlist(1)
- call assert_equal(digraph_get(pair[0]), pair[1])
- endfor
-
- " We don't know how many digraphs are registered before, so check the number
- " of digraphs returned.
- call assert_equal(digraph_getlist()->len(), digraph_getlist(0)->len())
- call assert_notequal((digraph_getlist()->len()), digraph_getlist(1)->len())
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
deleted file mode 100644
index b642f39c9f..0000000000
--- a/src/nvim/testdir/test_display.vim
+++ /dev/null
@@ -1,482 +0,0 @@
-" Test for displaying stuff
-
-" Nvim: `:set term` is not supported.
-" if !has('gui_running') && has('unix')
-" set term=ansi
-" endif
-
-source view_util.vim
-source check.vim
-source screendump.vim
-
-func Test_display_foldcolumn()
- CheckFeature folding
-
- new
- vnew
- vert resize 25
- call assert_equal(25, winwidth(winnr()))
- set isprint=@
-
- 1put='e more noise blah blah‚ more stuff here'
-
- let expect = [
- \ "e more noise blah blah<82",
- \ "> more stuff here "
- \ ]
-
- call cursor(2, 1)
- norm! zt
- let lines = ScreenLines([1,2], winwidth(0))
- call assert_equal(expect, lines)
- set fdc=2
- let lines = ScreenLines([1,2], winwidth(0))
- let expect = [
- \ " e more noise blah blah<",
- \ " 82> more stuff here "
- \ ]
- call assert_equal(expect, lines)
-
- quit!
- quit!
-endfunc
-
-func Test_display_foldtext_mbyte()
- CheckFeature folding
-
- call NewWindow(10, 40)
- call append(0, range(1,20))
- exe "set foldmethod=manual foldtext=foldtext() fillchars=fold:\u2500,vert:\u2502 fdc=2"
- call cursor(2, 1)
- norm! zf13G
- let lines=ScreenLines([1,3], winwidth(0)+1)
- let expect=[
- \ " 1 \u2502",
- \ "+ +-- 12 lines: 2". repeat("\u2500", 23). "\u2502",
- \ " 14 \u2502",
- \ ]
- call assert_equal(expect, lines)
-
- set fillchars=fold:-,vert:\|
- let lines=ScreenLines([1,3], winwidth(0)+1)
- let expect=[
- \ " 1 |",
- \ "+ +-- 12 lines: 2". repeat("-", 23). "|",
- \ " 14 |",
- \ ]
- call assert_equal(expect, lines)
-
- set foldtext& fillchars& foldmethod& fdc&
- bw!
-endfunc
-
-" check that win_ins_lines() and win_del_lines() work when t_cs is empty.
-func Test_scroll_without_region()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, range(1, 20))
- set t_cs=
- set laststatus=2
- END
- call writefile(lines, 'Xtestscroll')
- let buf = RunVimInTerminal('-S Xtestscroll', #{rows: 10})
-
- call VerifyScreenDump(buf, 'Test_scroll_no_region_1', {})
-
- call term_sendkeys(buf, ":3delete\<cr>")
- call VerifyScreenDump(buf, 'Test_scroll_no_region_2', {})
-
- call term_sendkeys(buf, ":4put\<cr>")
- call VerifyScreenDump(buf, 'Test_scroll_no_region_3', {})
-
- call term_sendkeys(buf, ":undo\<cr>")
- call term_sendkeys(buf, ":undo\<cr>")
- call term_sendkeys(buf, ":set laststatus=0\<cr>")
- call VerifyScreenDump(buf, 'Test_scroll_no_region_4', {})
-
- call term_sendkeys(buf, ":3delete\<cr>")
- call VerifyScreenDump(buf, 'Test_scroll_no_region_5', {})
-
- call term_sendkeys(buf, ":4put\<cr>")
- call VerifyScreenDump(buf, 'Test_scroll_no_region_6', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtestscroll')
-endfunc
-
-func Test_display_listchars_precedes()
- set fillchars+=vert:\|
- call NewWindow(10, 10)
- " Need a physical line that wraps over the complete
- " window size
- 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
-
-" Check that win_lines() works correctly with the number_only parameter=TRUE
-" should break early to optimize cost of drawing, but needs to make sure
-" that the number column is correctly highlighted.
-func Test_scroll_CursorLineNr_update()
- CheckScreendump
-
- let lines =<< trim END
- hi CursorLineNr ctermfg=73 ctermbg=236
- set nu rnu cursorline cursorlineopt=number
- exe ":norm! o\<esc>110ia\<esc>"
- END
- let filename = 'Xdrawscreen'
- call writefile(lines, filename)
- let buf = RunVimInTerminal('-S '.filename, #{rows: 5, cols: 50})
- call term_sendkeys(buf, "k")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_winline_rnu', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete(filename)
-endfunc
-
-" check a long file name does not result in the hit-enter prompt
-func Test_edit_long_file_name()
- CheckScreendump
-
- let longName = 'x'->repeat(min([&columns, 255]))
- call writefile([], longName)
- let buf = RunVimInTerminal('-N -u NONE ' .. longName, #{rows: 8})
-
- call VerifyScreenDump(buf, 'Test_long_file_name_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete(longName)
-endfunc
-
-func Test_unprintable_fileformats()
- CheckScreendump
-
- call writefile(["unix\r", "two"], 'Xunix.txt')
- call writefile(["mac\r", "two"], 'Xmac.txt')
- let lines =<< trim END
- edit Xunix.txt
- split Xmac.txt
- edit ++ff=mac
- END
- let filename = 'Xunprintable'
- call writefile(lines, filename)
- let buf = RunVimInTerminal('-S '.filename, #{rows: 9, cols: 50})
- call VerifyScreenDump(buf, 'Test_display_unprintable_01', {})
- call term_sendkeys(buf, "\<C-W>\<C-W>\<C-L>")
- call VerifyScreenDump(buf, 'Test_display_unprintable_02', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xunix.txt')
- call delete('Xmac.txt')
- call delete(filename)
-endfunc
-
-" Test for scrolling that modifies buffer during visual block
-func Test_visual_block_scroll()
- CheckScreendump
-
- let lines =<< trim END
- source $VIMRUNTIME/plugin/matchparen.vim
- set scrolloff=1
- call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
- call cursor(5, 1)
- END
-
- let filename = 'Xvisualblockmodifiedscroll'
- call writefile(lines, filename, 'D')
-
- let buf = RunVimInTerminal('-S '.filename, #{rows: 7})
- call term_sendkeys(buf, "V\<C-D>\<C-D>")
-
- call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Test for clearing paren highlight when switching buffers
-func Test_matchparen_clear_highlight()
- CheckScreendump
-
- let lines =<< trim END
- source $VIMRUNTIME/plugin/matchparen.vim
- set hidden
- call setline(1, ['()'])
- normal 0
-
- func OtherBuffer()
- enew
- exe "normal iaa\<Esc>0"
- endfunc
- END
- call writefile(lines, 'XMatchparenClear', 'D')
- let buf = RunVimInTerminal('-S XMatchparenClear', #{rows: 5})
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
-
- call term_sendkeys(buf, ":call OtherBuffer()\<CR>:\<Esc>")
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
-
- call term_sendkeys(buf, "\<C-^>:\<Esc>")
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
-
- call term_sendkeys(buf, "\<C-^>:\<Esc>")
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_display_scroll_at_topline()
- CheckScreendump
-
- let buf = RunVimInTerminal('', #{cols: 20})
- call term_sendkeys(buf, ":call setline(1, repeat('a', 21))\<CR>")
- call term_wait(buf)
- call term_sendkeys(buf, "O\<Esc>")
- call VerifyScreenDump(buf, 'Test_display_scroll_at_topline', #{rows: 4})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_display_scroll_update_visual()
- CheckScreendump
-
- let lines =<< trim END
- set scrolloff=0
- call setline(1, repeat(['foo'], 10))
- call sign_define('foo', { 'text': '>' })
- call sign_place(1, 'bar', 'foo', bufnr(), { 'lnum': 2 })
- call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 })
- autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif
- END
- call writefile(lines, 'XupdateVisual.vim')
-
- let buf = RunVimInTerminal('-S XupdateVisual.vim', #{rows: 8, cols: 60})
- call term_sendkeys(buf, "VG7kk")
- call VerifyScreenDump(buf, 'Test_display_scroll_update_visual', {})
-
- call StopVimInTerminal(buf)
- call delete('XupdateVisual.vim')
-endfunc
-
-" Test for 'eob' (EndOfBuffer) item in 'fillchars'
-func Test_eob_fillchars()
- " default value (skipped)
- " call assert_match('eob:\~', &fillchars)
- " invalid values
- call assert_fails(':set fillchars=eob:', 'E474:')
- call assert_fails(':set fillchars=eob:xy', 'E474:')
- call assert_fails(':set fillchars=eob:\255', 'E474:')
- call assert_fails(':set fillchars=eob:<ff>', 'E474:')
- call assert_fails(":set fillchars=eob:\x01", 'E474:')
- call assert_fails(':set fillchars=eob:\\x01', 'E474:')
- " default is ~
- new
- redraw
- call assert_equal('~', Screenline(2))
- set fillchars=eob:+
- redraw
- call assert_equal('+', Screenline(2))
- set fillchars=eob:\
- redraw
- call assert_equal(' ', nr2char(screenchar(2, 1)))
- set fillchars&
- close
-endfunc
-
-" Test for 'foldopen', 'foldclose' and 'foldsep' in 'fillchars'
-func Test_fold_fillchars()
- new
- set fdc=2 foldenable foldmethod=manual
- call setline(1, ['one', 'two', 'three', 'four', 'five'])
- 2,4fold
- " First check for the default setting for a closed fold
- let lines = ScreenLines([1, 3], 8)
- let expected = [
- \ ' one ',
- \ '+ +-- 3',
- \ ' five '
- \ ]
- call assert_equal(expected, lines)
- normal 2Gzo
- " check the characters for an open fold
- let lines = ScreenLines([1, 5], 8)
- let expected = [
- \ ' one ',
- \ '- two ',
- \ '| three ',
- \ '| four ',
- \ ' five '
- \ ]
- call assert_equal(expected, lines)
-
- " change the setting
- set fillchars=vert:\|,fold:-,eob:~,foldopen:[,foldclose:],foldsep:-
-
- " check the characters for an open fold
- let lines = ScreenLines([1, 5], 8)
- let expected = [
- \ ' one ',
- \ '[ two ',
- \ '- three ',
- \ '- four ',
- \ ' five '
- \ ]
- call assert_equal(expected, lines)
-
- " check the characters for a closed fold
- normal 2Gzc
- let lines = ScreenLines([1, 3], 8)
- let expected = [
- \ ' one ',
- \ '] +-- 3',
- \ ' five '
- \ ]
- call assert_equal(expected, lines)
-
- %bw!
- set fillchars& fdc& foldmethod& foldenable&
-endfunc
-
-func Test_local_fillchars()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['window 1']->repeat(3))
- setlocal fillchars=stl:1,stlnc:a,vert:=,eob:x
- vnew
- call setline(1, ['window 2']->repeat(3))
- setlocal fillchars=stl:2,stlnc:b,vert:+,eob:y
- new
- wincmd J
- call setline(1, ['window 3']->repeat(3))
- setlocal fillchars=stl:3,stlnc:c,vert:<,eob:z
- vnew
- call setline(1, ['window 4']->repeat(3))
- setlocal fillchars=stl:4,stlnc:d,vert:>,eob:o
- END
- call writefile(lines, 'Xdisplayfillchars')
- let buf = RunVimInTerminal('-S Xdisplayfillchars', #{rows: 12})
- call VerifyScreenDump(buf, 'Test_display_fillchars_1', {})
-
- call term_sendkeys(buf, ":wincmd k\r")
- call VerifyScreenDump(buf, 'Test_display_fillchars_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xdisplayfillchars')
-endfunc
-
-func Test_display_linebreak_breakat()
- new
- vert resize 25
- let _breakat = &breakat
- setl signcolumn=yes linebreak breakat=) showbreak=+\
- call setline(1, repeat('x', winwidth(0) - 2) .. ')abc')
- let lines = ScreenLines([1, 2], 25)
- let expected = [
- \ ' xxxxxxxxxxxxxxxxxxxxxxx',
- \ ' + )abc '
- \ ]
- call assert_equal(expected, lines)
- %bw!
- let &breakat=_breakat
-endfunc
-
-func Run_Test_display_lastline(euro)
- let lines =<< trim END
- call setline(1, ['aaa', 'b'->repeat(200)])
- set display=truncate
-
- vsplit
- 100wincmd <
- END
- if a:euro != ''
- let lines[2] = 'set fillchars=vert:\|,lastline:€'
- endif
- call writefile(lines, 'XdispLastline', 'D')
- let buf = RunVimInTerminal('-S XdispLastline', #{rows: 10})
- call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}1', {})
-
- call term_sendkeys(buf, ":set display=lastline\<CR>")
- call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}2', {})
-
- call term_sendkeys(buf, ":100wincmd >\<CR>")
- call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}3', {})
-
- call term_sendkeys(buf, ":set display=truncate\<CR>")
- call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}4', {})
-
- call term_sendkeys(buf, ":close\<CR>")
- call term_sendkeys(buf, ":3split\<CR>")
- call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}5', {})
-
- call term_sendkeys(buf, ":close\<CR>")
- call term_sendkeys(buf, ":2vsplit\<CR>")
- call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}6', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_display_lastline()
- CheckScreendump
-
- call Run_Test_display_lastline('')
- call Run_Test_display_lastline('euro_')
-
- call assert_fails(':set fillchars=lastline:', 'E474:')
- call assert_fails(':set fillchars=lastline:〇', 'E474:')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
deleted file mode 100644
index fd54f77ccb..0000000000
--- a/src/nvim/testdir/test_edit.vim
+++ /dev/null
@@ -1,2084 +0,0 @@
-" Test for edit functions
-
-if exists("+t_kD")
- let &t_kD="[3;*~"
-endif
-
-source check.vim
-
-" Needed for testing basic rightleft: Test_edit_rightleft
-source view_util.vim
-
-" Needs to come first until the bug in getchar() is
-" fixed: https://groups.google.com/d/msg/vim_dev/fXL9yme4H4c/bOR-U6_bAQAJ
-func Test_edit_00b()
- new
- call setline(1, ['abc '])
- inoreabbr <buffer> h here some more
- call cursor(1, 4)
- " <esc> expands the abbreviation and ends insert mode
- " call feedkeys(":set im\<cr> h\<c-l>:set noim\<cr>", 'tix')
- call feedkeys("i h\<esc>", 'tix')
- call assert_equal(['abc here some more '], getline(1,'$'))
- iunabbr <buffer> h
- bw!
-endfunc
-
-func Test_edit_01()
- " set for Travis CI?
- " set nocp noesckeys
- new
- " 1) empty buffer
- call assert_equal([''], getline(1,'$'))
- " 2) delete in an empty line
- call feedkeys("i\<del>\<esc>", 'tnix')
- call assert_equal([''], getline(1,'$'))
- %d
- " 3) delete one character
- call setline(1, 'a')
- call feedkeys("i\<del>\<esc>", 'tnix')
- call assert_equal([''], getline(1,'$'))
- %d
- " 4) delete a multibyte character
- call setline(1, "\u0401")
- call feedkeys("i\<del>\<esc>", 'tnix')
- call assert_equal([''], getline(1,'$'))
- %d
- " 5.1) delete linebreak with 'bs' option containing eol
- let _bs=&bs
- set bs=eol
- call setline(1, ["abc def", "ghi jkl"])
- call cursor(1, 1)
- call feedkeys("A\<del>\<esc>", 'tnix')
- call assert_equal(['abc defghi jkl'], getline(1, 2))
- %d
- " 5.2) delete linebreak with backspace option w/out eol
- set bs=
- call setline(1, ["abc def", "ghi jkl"])
- call cursor(1, 1)
- call feedkeys("A\<del>\<esc>", 'tnix')
- call assert_equal(["abc def", "ghi jkl"], getline(1, 2))
- let &bs=_bs
- bw!
-endfunc
-
-func Test_edit_02()
- " Change cursor position in InsertCharPre command
- new
- call setline(1, 'abc')
- call cursor(1, 1)
- fu! DoIt(...)
- call cursor(1, 4)
- if len(a:000)
- let v:char=a:1
- endif
- endfu
- au InsertCharPre <buffer> :call DoIt('y')
- call feedkeys("ix\<esc>", 'tnix')
- call assert_equal(['abcy'], getline(1, '$'))
- " Setting <Enter> in InsertCharPre
- au! InsertCharPre <buffer> :call DoIt("\n")
- call setline(1, 'abc')
- call cursor(1, 1)
- call feedkeys("ix\<esc>", 'tnix')
- call assert_equal(['abc', ''], getline(1, '$'))
- %d
- au! InsertCharPre
- " Change cursor position in InsertEnter command
- " 1) when setting v:char, keeps changed cursor position
- au! InsertEnter <buffer> :call DoIt('y')
- call setline(1, 'abc')
- call cursor(1, 1)
- call feedkeys("ix\<esc>", 'tnix')
- call assert_equal(['abxc'], getline(1, '$'))
- " 2) when not setting v:char, restores changed cursor position
- au! InsertEnter <buffer> :call DoIt()
- call setline(1, 'abc')
- call cursor(1, 1)
- call feedkeys("ix\<esc>", 'tnix')
- call assert_equal(['xabc'], getline(1, '$'))
- au! InsertEnter
- delfu DoIt
- bw!
-endfunc
-
-func Test_edit_03()
- " Change cursor after <c-o> command to end of line
- new
- call setline(1, 'abc')
- call cursor(1, 1)
- call feedkeys("i\<c-o>$y\<esc>", 'tnix')
- call assert_equal(['abcy'], getline(1, '$'))
- %d
- call setline(1, 'abc')
- call cursor(1, 1)
- call feedkeys("i\<c-o>80|y\<esc>", 'tnix')
- call assert_equal(['abcy'], getline(1, '$'))
- %d
- call setline(1, 'abc')
- call feedkeys("Ad\<c-o>:s/$/efg/\<cr>hij", 'tnix')
- call assert_equal(['hijabcdefg'], getline(1, '$'))
- bw!
-endfunc
-
-func Test_edit_04()
- " test for :stopinsert
- new
- call setline(1, 'abc')
- call cursor(1, 1)
- call feedkeys("i\<c-o>:stopinsert\<cr>$", 'tnix')
- call feedkeys("aX\<esc>", 'tnix')
- call assert_equal(['abcX'], getline(1, '$'))
- %d
- bw!
-endfunc
-
-func Test_edit_05()
- " test for folds being opened
- new
- call setline(1, ['abcX', 'abcX', 'zzzZ'])
- call cursor(1, 1)
- set foldmethod=manual foldopen+=insert
- " create fold for those two lines
- norm! Vjzf
- call feedkeys("$ay\<esc>", 'tnix')
- call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$'))
- %d
- call setline(1, ['abcX', 'abcX', 'zzzZ'])
- call cursor(1, 1)
- set foldmethod=manual foldopen-=insert
- " create fold for those two lines
- norm! Vjzf
- call feedkeys("$ay\<esc>", 'tnix')
- call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$'))
- %d
- bw!
-endfunc
-
-func Test_edit_06()
- " Test in diff mode
- if !has("diff") || !executable("diff")
- return
- endif
- new
- call setline(1, ['abc', 'xxx', 'yyy'])
- vnew
- call setline(1, ['abc', 'zzz', 'xxx', 'yyy'])
- wincmd p
- diffthis
- wincmd p
- diffthis
- wincmd p
- call cursor(2, 1)
- norm! zt
- call feedkeys("Ozzz\<esc>", 'tnix')
- call assert_equal(['abc', 'zzz', 'xxx', 'yyy'], getline(1,'$'))
- bw!
- bw!
-endfunc
-
-func Test_edit_07()
- " 1) Test with completion <c-l> when popupmenu is visible
- new
- call setline(1, 'J')
-
- func! ListMonths()
- call complete(col('.')-1, ['January', 'February', 'March',
- \ 'April', 'May', 'June', 'July', 'August', 'September',
- \ 'October', 'November', 'December'])
- return ''
- endfunc
- inoremap <buffer> <F5> <C-R>=ListMonths()<CR>
-
- call feedkeys("A\<f5>\<c-p>". repeat("\<down>", 6)."\<c-l>\<down>\<c-l>\<cr>", 'tx')
- call assert_equal(['July'], getline(1,'$'))
- " 1) Test completion when InsertCharPre kicks in
- %d
- call setline(1, 'J')
- fu! DoIt()
- if v:char=='u'
- let v:char='an'
- endif
- endfu
- au InsertCharPre <buffer> :call DoIt()
- call feedkeys("A\<f5>\<c-p>u\<cr>\<c-l>\<cr>", 'tx')
- call assert_equal(["Jan\<c-l>",''], 1->getline('$'))
- %d
- call setline(1, 'J')
- call feedkeys("A\<f5>\<c-p>u\<down>\<c-l>\<cr>", 'tx')
- call assert_equal(["January"], 1->getline('$'))
-
- delfu ListMonths
- delfu DoIt
- iunmap <buffer> <f5>
- bw!
-endfunc
-
-func Test_edit_08()
- throw 'skipped: moved to test/functional/legacy/edit_spec.lua'
- " reset insertmode from i_ctrl-r_=
- let g:bufnr = bufnr('%')
- new
- call setline(1, ['abc'])
- call cursor(1, 4)
- call feedkeys(":set im\<cr>ZZZ\<c-r>=setbufvar(g:bufnr,'&im', 0)\<cr>",'tnix')
- call assert_equal(['abZZZc'], getline(1,'$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
- call assert_false(0, '&im')
- bw!
- unlet g:bufnr
-endfunc
-
-func Test_edit_09()
- " test i_CTRL-\ combinations
- new
- call setline(1, ['abc', 'def', 'ghi'])
- call cursor(1, 1)
- " 1) CTRL-\ CTLR-N
- " call feedkeys(":set im\<cr>\<c-\>\<c-n>ccABC\<c-l>", 'txin')
- call feedkeys("i\<c-\>\<c-n>ccABC\<esc>", 'txin')
- call assert_equal(['ABC', 'def', 'ghi'], getline(1,'$'))
- call setline(1, ['ABC', 'def', 'ghi'])
- " 2) CTRL-\ CTLR-G
- " CTRL-\ CTRL-G goes to Insert mode when 'insertmode' is set, but
- " 'insertmode' is now removed so skip this test
- " call feedkeys("j0\<c-\>\<c-g>ZZZ\<cr>\<esc>", 'txin')
- " call assert_equal(['ABC', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
- " call feedkeys("I\<c-\>\<c-g>YYY\<c-l>", 'txin')
- " call assert_equal(['ABC', 'ZZZ', 'YYYdef', 'ghi'], getline(1,'$'))
- " set noinsertmode
- " 3) CTRL-\ CTRL-O
- call setline(1, ['ABC', 'ZZZ', 'def', 'ghi'])
- call cursor(1, 1)
- call feedkeys("A\<c-o>ix", 'txin')
- call assert_equal(['ABxC', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
- call feedkeys("A\<c-\>\<c-o>ix", 'txin')
- call assert_equal(['ABxCx', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
- " 4) CTRL-\ a (should be inserted literally, not special after <c-\>
- call setline(1, ['ABC', 'ZZZ', 'def', 'ghi'])
- call cursor(1, 1)
- call feedkeys("A\<c-\>a", 'txin')
- call assert_equal(["ABC\<c-\>a", 'ZZZ', 'def', 'ghi'], getline(1, '$'))
- bw!
-endfunc
-
-func Test_edit_11()
- " Test that indenting kicks in
- new
- set cindent
- call setline(1, ['{', '', ''])
- call cursor(2, 1)
- call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
- call cursor(3, 1)
- call feedkeys("\<Insert>/* comment */", 'tnix')
- call assert_equal(['{', "\<tab>int c;", "/* comment */"], getline(1, '$'))
- " added changed cindentkeys slightly
- set cindent cinkeys+=*/
- call setline(1, ['{', '', ''])
- call cursor(2, 1)
- call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
- call cursor(3, 1)
- call feedkeys("i/* comment */", 'tnix')
- call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */"], getline(1, '$'))
- set cindent cinkeys+==end
- call feedkeys("oend\<cr>\<esc>", 'tnix')
- call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */", "\tend", ''], getline(1, '$'))
- set cinkeys-==end
- %d
- " Use indentexpr instead of cindenting
- func! Do_Indent()
- if v:lnum == 3
- return 3*shiftwidth()
- else
- return 2*shiftwidth()
- endif
- endfunc
- setl indentexpr=Do_Indent() indentkeys+=*/
- call setline(1, ['{', '', ''])
- call cursor(2, 1)
- call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
- call cursor(3, 1)
- call feedkeys("i/* comment */", 'tnix')
- call assert_equal(['{', "\<tab>\<tab>int c;", "\<tab>\<tab>\<tab>/* comment */"], getline(1, '$'))
- set cinkeys&vim indentkeys&vim
- set nocindent indentexpr=
- delfu Do_Indent
- bw!
-endfunc
-
-func Test_edit_11_indentexpr()
- " Test that indenting kicks in
- new
- " Use indentexpr instead of cindenting
- func! Do_Indent()
- let pline=prevnonblank(v:lnum)
- if empty(getline(v:lnum))
- if getline(pline) =~ 'if\|then'
- return shiftwidth()
- else
- return 0
- endif
- else
- return 0
- endif
- endfunc
- setl indentexpr=Do_Indent() indentkeys+=0=then,0=fi
- call setline(1, ['if [ $this ]'])
- call cursor(1, 1)
- call feedkeys("othen\<cr>that\<cr>fi", 'tnix')
- call assert_equal(['if [ $this ]', "then", "\<tab>that", "fi"], getline(1, '$'))
- set cinkeys&vim indentkeys&vim
- set nocindent indentexpr=
- delfu Do_Indent
-
- " Using a script-local function
- func s:NewIndentExpr()
- endfunc
- set indentexpr=s:NewIndentExpr()
- call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
- set indentexpr=<SID>NewIndentExpr()
- call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
- set indentexpr&
-
- bw!
-endfunc
-
-" Test changing indent in replace mode
-func Test_edit_12()
- new
- call setline(1, ["\tabc", "\tdef"])
- call cursor(2, 4)
- call feedkeys("R^\<c-d>", 'tnix')
- call assert_equal(["\tabc", "def"], getline(1, '$'))
- call assert_equal([0, 2, 2, 0], '.'->getpos())
- %d
- call setline(1, ["\tabc", "\t\tdef"])
- call cursor(2, 2)
- call feedkeys("R^\<c-d>", 'tnix')
- call assert_equal(["\tabc", "def"], getline(1, '$'))
- call assert_equal([0, 2, 1, 0], getpos('.'))
- %d
- call setline(1, ["\tabc", "\t\tdef"])
- call cursor(2, 2)
- call feedkeys("R\<c-t>", 'tnix')
- call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
- call assert_equal([0, 2, 2, 0], getpos('.'))
- bw!
- 10vnew
- call setline(1, ["\tabc", "\t\tdef"])
- call cursor(2, 2)
- call feedkeys("R\<c-t>", 'tnix')
- call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
- call assert_equal([0, 2, 2, 0], getpos('.'))
- %d
- set sw=4
- call setline(1, ["\tabc", "\t\tdef"])
- call cursor(2, 2)
- call feedkeys("R\<c-t>\<c-t>", 'tnix')
- call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
- call assert_equal([0, 2, 2, 0], getpos('.'))
- %d
- call setline(1, ["\tabc", "\t\tdef"])
- call cursor(2, 2)
- call feedkeys("R\<c-t>\<c-t>", 'tnix')
- call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
- call assert_equal([0, 2, 2, 0], getpos('.'))
- set sw&
-
- " In replace mode, after hitting enter in a line with tab characters,
- " pressing backspace should restore the tab characters.
- %d
- setlocal autoindent backspace=2
- call setline(1, "\tone\t\ttwo")
- exe "normal ggRred\<CR>six" .. repeat("\<BS>", 8)
- call assert_equal(["\tone\t\ttwo"], getline(1, '$'))
- bw!
-endfunc
-
-func Test_edit_13()
- " Test smartindenting
- new
- set smartindent autoindent
- call setline(1, ["\tabc"])
- call feedkeys("A {\<cr>more\<cr>}\<esc>", 'tnix')
- call assert_equal(["\tabc {", "\t\tmore", "\t}"], getline(1, '$'))
- set smartindent& autoindent&
- bwipe!
-
- " Test autoindent removing indent of blank line.
- new
- call setline(1, ' foo bar baz')
- set autoindent
- exe "normal 0eea\<CR>\<CR>\<Esc>"
- call assert_equal(" foo bar", getline(1))
- call assert_equal("", getline(2))
- call assert_equal(" baz", getline(3))
- set autoindent&
-
- " pressing <C-U> to erase line should keep the indent with 'autoindent'
- set backspace=2 autoindent
- %d
- exe "normal i\tone\<CR>three\<C-U>two"
- call assert_equal(["\tone", "\ttwo"], getline(1, '$'))
- set backspace& autoindent&
-
- bwipe!
-endfunc
-
-" Test for autoindent removing indent when insert mode is stopped. Some parts
-" of the code is exercised only when interactive mode is used. So use Vim in a
-" terminal.
-func Test_autoindent_remove_indent()
- CheckRunVimInTerminal
- let buf = RunVimInTerminal('-N Xfile', {'rows': 6, 'cols' : 20})
- call TermWait(buf)
- call term_sendkeys(buf, ":set autoindent\n")
- " leaving insert mode in a new line with indent added by autoindent, should
- " remove the indent.
- call term_sendkeys(buf, "i\<Tab>foo\<CR>\<Esc>")
- " Need to delay for sometime, otherwise the code in getchar.c will not be
- " exercised.
- call TermWait(buf, 50)
- " when a line is wrapped and the cursor is at the start of the second line,
- " leaving insert mode, should move the cursor back to the first line.
- call term_sendkeys(buf, "o" .. repeat('x', 20) .. "\<Esc>")
- " Need to delay for sometime, otherwise the code in getchar.c will not be
- " exercised.
- call TermWait(buf, 50)
- call term_sendkeys(buf, ":w\n")
- call TermWait(buf)
- call StopVimInTerminal(buf)
- call assert_equal(["\tfoo", '', repeat('x', 20)], readfile('Xfile'))
- call delete('Xfile')
-endfunc
-
-func Test_edit_CR()
- " Test for <CR> in insert mode
- " basically only in quickfix mode ist tested, the rest
- " has been taken care of by other tests
- CheckFeature quickfix
- botright new
- call writefile(range(1, 10), 'Xqflist.txt')
- call setqflist([{'filename': 'Xqflist.txt', 'lnum': 2}])
- copen
- set modifiable
- call feedkeys("A\<cr>", 'tnix')
- call assert_equal('Xqflist.txt', bufname(''))
- call assert_equal(2, line('.'))
- cclose
- botright new
- call setloclist(0, [{'filename': 'Xqflist.txt', 'lnum': 10}])
- lopen
- set modifiable
- call feedkeys("A\<cr>", 'tnix')
- call assert_equal('Xqflist.txt', bufname(''))
- call assert_equal(10, line('.'))
- call feedkeys("A\<Enter>", 'tnix')
- call feedkeys("A\<kEnter>", 'tnix')
- call feedkeys("A\n", 'tnix')
- call feedkeys("A\r", 'tnix')
- call assert_equal(map(range(1, 10), 'string(v:val)') + ['', '', '', ''], getline(1, '$'))
- bw!
- lclose
- call delete('Xqflist.txt')
-endfunc
-
-func Test_edit_CTRL_()
- " disabled for Windows builds, why?
- if !has("rightleft") || has("win32")
- return
- endif
- let _encoding=&encoding
- set encoding=utf-8
- " Test for CTRL-_
- new
- call setline(1, ['abc'])
- call cursor(1, 1)
- call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
- call assert_equal(["\<C-_>xyzabc"], getline(1, '$'))
- call assert_false(&revins)
- set ari
- call setline(1, ['abc'])
- call cursor(1, 1)
- call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
- call assert_equal(["æèñabc"], getline(1, '$'))
- call assert_true(&revins)
- call setline(1, ['abc'])
- call cursor(1, 1)
- call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
- call assert_equal(["xyzabc"], getline(1, '$'))
- call assert_false(&revins)
- set noari
- let &encoding=_encoding
- bw!
-endfunc
-
-" needs to come first, to have the @. register empty
-func Test_edit_00a_CTRL_A()
- " Test pressing CTRL-A
- new
- call setline(1, repeat([''], 5))
- call cursor(1, 1)
- try
- call feedkeys("A\<NUL>", 'tnix')
- catch /^Vim\%((\a\+)\)\=:E29/
- call assert_true(1, 'E29 error caught')
- endtry
- call cursor(1, 1)
- call feedkeys("Afoobar \<esc>", 'tnix')
- call cursor(2, 1)
- call feedkeys("A\<c-a>more\<esc>", 'tnix')
- call cursor(3, 1)
- call feedkeys("A\<NUL>and more\<esc>", 'tnix')
- call assert_equal(['foobar ', 'foobar more', 'foobar morend more', '', ''], getline(1, '$'))
- bw!
-endfunc
-
-func Test_edit_CTRL_EY()
- " Ctrl-E/ Ctrl-Y in insert mode completion to scroll
- 10new
- call setline(1, range(1, 100))
- call cursor(30, 1)
- norm! z.
- call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix')
- call assert_equal(30, winsaveview()['topline'])
- call assert_equal([0, 30, 2, 0], getpos('.'))
- call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix')
- call feedkeys("A\<c-x>".repeat("\<c-y>", 10), 'tnix')
- call assert_equal(21, winsaveview()['topline'])
- call assert_equal([0, 30, 2, 0], getpos('.'))
- bw!
-endfunc
-
-func Test_edit_CTRL_G()
- new
- call setline(1, ['foobar', 'foobar', 'foobar'])
- call cursor(2, 4)
- call feedkeys("ioooooooo\<c-g>k\<c-r>.\<esc>", 'tnix')
- call assert_equal(['foooooooooobar', 'foooooooooobar', 'foobar'], getline(1, '$'))
- call assert_equal([0, 1, 11, 0], getpos('.'))
- call feedkeys("i\<c-g>k\<esc>", 'tnix')
- call assert_equal([0, 1, 10, 0], getpos('.'))
- call cursor(2, 4)
- call feedkeys("i\<c-g>jzzzz\<esc>", 'tnix')
- call assert_equal(['foooooooooobar', 'foooooooooobar', 'foozzzzbar'], getline(1, '$'))
- call assert_equal([0, 3, 7, 0], getpos('.'))
- call feedkeys("i\<c-g>j\<esc>", 'tnix')
- call assert_equal([0, 3, 6, 0], getpos('.'))
- bw!
-endfunc
-
-func Test_edit_CTRL_I()
- " Tab in completion mode
- let path=expand("%:p:h")
- new
- call setline(1, [path. "/", ''])
- call feedkeys("Arunt\<c-x>\<c-f>\<tab>\<cr>\<esc>", 'tnix')
- call assert_match('runtest\.vim', getline(1))
- %d
- call writefile(['one', 'two', 'three'], 'Xinclude.txt')
- let include='#include Xinclude.txt'
- call setline(1, [include, ''])
- call cursor(2, 1)
- call feedkeys("A\<c-x>\<tab>\<cr>\<esc>", 'tnix')
- call assert_equal([include, 'one', ''], getline(1, '$'))
- call feedkeys("2ggC\<c-x>\<tab>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal([include, 'two', ''], getline(1, '$'))
- call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal([include, 'three', ''], getline(1, '$'))
- call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal([include, '', ''], getline(1, '$'))
- call delete("Xinclude.txt")
- bw!
-endfunc
-
-func Test_edit_CTRL_K()
- " Test pressing CTRL-K (basically only dictionary completion and digraphs
- " the rest is already covered
- call writefile(['A', 'AA', 'AAA', 'AAAA'], 'Xdictionary.txt')
- set dictionary=Xdictionary.txt
- new
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<cr>\<esc>", 'tnix')
- call assert_equal(['AA', ''], getline(1, '$'))
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal(['AAA'], getline(1, '$'))
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal(['AAAA'], getline(1, '$'))
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal(['A'], getline(1, '$'))
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal(['AA'], getline(1, '$'))
-
- " press an unexpected key after dictionary completion
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<c-]>\<cr>\<esc>", 'tnix')
- call assert_equal(['AA', ''], getline(1, '$'))
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<c-s>\<cr>\<esc>", 'tnix')
- call assert_equal(["AA\<c-s>", ''], getline(1, '$'))
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-k>\<c-f>\<cr>\<esc>", 'tnix')
- call assert_equal(["AA\<c-f>", ''], getline(1, '$'))
-
- set dictionary=
- %d
- call setline(1, 'A')
- call cursor(1, 1)
- let v:testing = 1
- try
- call feedkeys("A\<c-x>\<c-k>\<esc>", 'tnix')
- catch
- " error sleeps 2 seconds, when v:testing is not set
- let v:testing = 0
- endtry
- call delete('Xdictionary.txt')
-
- if exists('*test_override')
- call test_override("char_avail", 1)
- set showcmd
- %d
- call feedkeys("A\<c-k>a:\<esc>", 'tnix')
- call assert_equal(['ä'], getline(1, '$'))
- call test_override("char_avail", 0)
- set noshowcmd
- endif
- bw!
-endfunc
-
-func Test_edit_CTRL_L()
- " Test Ctrl-X Ctrl-L (line completion)
- new
- set complete=.
- call setline(1, ['one', 'two', 'three', '', '', '', ''])
- call cursor(4, 1)
- call feedkeys("A\<c-x>\<c-l>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-n>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-p>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<c-p>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
- set complete=
- call cursor(5, 1)
- call feedkeys("A\<c-x>\<c-l>\<c-p>\<c-n>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 'three', "\<c-l>\<c-p>\<c-n>", '', ''], getline(1, '$'))
- set complete&
- %d
- if has("conceal") && has("syntax") && !has("nvim")
- call setline(1, ['foo', 'bar', 'foobar'])
- call test_override("char_avail", 1)
- set conceallevel=2 concealcursor=n
- syn on
- syn match ErrorMsg "^bar"
- call matchadd("Conceal", 'oo', 10, -1, {'conceal': 'X'})
- func! DoIt()
- let g:change=1
- endfunc
- au! TextChangedI <buffer> :call DoIt()
-
- call cursor(2, 1)
- call assert_false(exists("g:change"))
- call feedkeys("A \<esc>", 'tnix')
- call assert_equal(['foo', 'bar ', 'foobar'], getline(1, '$'))
- call assert_equal(1, g:change)
-
- call test_override("char_avail", 0)
- call clearmatches()
- syn off
- au! TextChangedI
- delfu DoIt
- unlet! g:change
- endif
- bw!
-endfunc
-
-func Test_edit_CTRL_N()
- " Check keyword completion
- " for e in ['latin1', 'utf-8']
- for e in ['utf-8']
- exe 'set encoding=' .. e
- new
- set complete=.
- call setline(1, ['INFER', 'loWER', '', '', ])
- call cursor(3, 1)
- call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix")
- call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$'), e)
- %d
- call setline(1, ['INFER', 'loWER', '', '', ])
- call cursor(3, 1)
- set ignorecase infercase
- call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix")
- call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$'), e)
- set noignorecase noinfercase
- %d
- call setline(1, ['one word', 'two word'])
- exe "normal! Goo\<C-P>\<C-X>\<C-P>"
- call assert_equal('one word', getline(3))
- %d
- set complete&
- bw!
- endfor
-endfunc
-
-func Test_edit_CTRL_O()
- " Check for CTRL-O in insert mode
- new
- inoreabbr <buffer> h here some more
- call setline(1, ['abc', 'def'])
- call cursor(1, 1)
- " Ctrl-O after an abbreviation
- exe "norm A h\<c-o>:set nu\<cr> text"
- call assert_equal(['abc here some more text', 'def'], getline(1, '$'))
- call assert_true(&nu)
- set nonu
- iunabbr <buffer> h
- " Ctrl-O at end of line with 've'=onemore
- call cursor(1, 1)
- call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix')
- call assert_equal([0, 1, 23, 0], g:a)
- call cursor(1, 1)
- set ve=onemore
- call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix')
- call assert_equal([0, 1, 24, 0], g:a)
- set ve=
- unlet! g:a
- bw!
-endfunc
-
-func Test_edit_CTRL_R()
- " Insert Register
- new
- " call test_override("ALL", 1)
- set showcmd
- call feedkeys("AFOOBAR eins zwei\<esc>", 'tnix')
- call feedkeys("O\<c-r>.", 'tnix')
- call feedkeys("O\<c-r>=10*500\<cr>\<esc>", 'tnix')
- call feedkeys("O\<c-r>=getreg('=', 1)\<cr>\<esc>", 'tnix')
- call assert_equal(["getreg('=', 1)", '5000', "FOOBAR eins zwei", "FOOBAR eins zwei"], getline(1, '$'))
- " call test_override("ALL", 0)
- set noshowcmd
- bw!
-endfunc
-
-func Test_edit_CTRL_S()
- " Test pressing CTRL-S (basically only spellfile completion)
- " the rest is already covered
- new
- if !has("spell")
- call setline(1, 'vim')
- call feedkeys("A\<c-x>ss\<cr>\<esc>", 'tnix')
- call assert_equal(['vims', ''], getline(1, '$'))
- bw!
- return
- endif
- call setline(1, 'vim')
- " spell option not yet set
- try
- call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix')
- catch /^Vim\%((\a\+)\)\=:E756/
- call assert_true(1, 'error caught')
- endtry
- call assert_equal(['vim', ''], getline(1, '$'))
- %d
- setl spell spelllang=en
- call setline(1, 'vim')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix')
- call assert_equal(['Vim', ''], getline(1, '$'))
- %d
- call setline(1, 'vim')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-s>\<down>\<cr>\<esc>", 'tnix')
- call assert_equal(['Aim'], getline(1, '$'))
- %d
- call setline(1, 'vim')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix')
- call assert_equal(['vim', ''], getline(1, '$'))
- %d
- " empty buffer
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix')
- call assert_equal(['', ''], getline(1, '$'))
- setl nospell
- bw!
-endfunc
-
-func Test_edit_CTRL_T()
- " Check for CTRL-T and CTRL-X CTRL-T in insert mode
- " 1) increase indent
- new
- call setline(1, "abc")
- call cursor(1, 1)
- call feedkeys("A\<c-t>xyz", 'tnix')
- call assert_equal(["\<tab>abcxyz"], getline(1, '$'))
- " 2) also when paste option is set
- set paste
- call setline(1, "abc")
- call cursor(1, 1)
- call feedkeys("A\<c-t>xyz", 'tnix')
- call assert_equal(["\<tab>abcxyz"], getline(1, '$'))
- set nopaste
- " CTRL-X CTRL-T (thesaurus complete)
- call writefile(['angry furious mad enraged'], 'Xthesaurus')
- set thesaurus=Xthesaurus
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix')
- call assert_equal(['mad', ''], getline(1, '$'))
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['angry', ''], getline(1, '$'))
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['furious', ''], getline(1, '$'))
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['enraged', ''], getline(1, '$'))
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['mad', ''], getline(1, '$'))
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['mad', ''], getline(1, '$'))
- " Using <c-p> <c-n> when 'complete' is empty
- set complete=
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['angry', ''], getline(1, '$'))
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-p>\<cr>\<esc>", 'tnix')
- call assert_equal(['mad', ''], getline(1, '$'))
- set complete&
-
- set thesaurus=
- %d
- call setline(1, 'mad')
- call cursor(1, 1)
- let v:testing = 1
- try
- call feedkeys("A\<c-x>\<c-t>\<esc>", 'tnix')
- catch
- " error sleeps 2 seconds, when v:testing is not set
- let v:testing = 0
- endtry
- call assert_equal(['mad'], getline(1, '$'))
- call delete('Xthesaurus')
- bw!
-endfunc
-
-" Test thesaurus completion with different encodings
-func Test_thesaurus_complete_with_encoding()
- call writefile(['angry furious mad enraged'], 'Xthesaurus')
- set thesaurus=Xthesaurus
- " for e in ['latin1', 'utf-8']
- for e in ['utf-8']
- exe 'set encoding=' .. e
- new
- call setline(1, 'mad')
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix')
- call assert_equal(['mad', ''], getline(1, '$'))
- bw!
- endfor
- set thesaurus=
- call delete('Xthesaurus')
-endfunc
-
-" Test 'thesaurusfunc'
-func MyThesaurus(findstart, base)
- let mythesaurus = [
- \ #{word: "happy",
- \ synonyms: "cheerful,blissful,flying high,looking good,peppy"},
- \ #{word: "kind",
- \ synonyms: "amiable,bleeding-heart,heart in right place"}]
- if a:findstart
- " locate the start of the word
- let line = getline('.')
- let start = col('.') - 1
- while start > 0 && line[start - 1] =~ '\a'
- let start -= 1
- endwhile
- return start
- else
- " find strings matching with "a:base"
- let res = []
- for w in mythesaurus
- if w.word =~ '^' . a:base
- call add(res, w.word)
- call extend(res, split(w.synonyms, ","))
- endif
- endfor
- return res
- endif
-endfunc
-
-func Test_thesaurus_func()
- new
- set thesaurus=notused
- set thesaurusfunc=NotUsed
- setlocal thesaurusfunc=MyThesaurus
- call setline(1, "an ki")
- call cursor(1, 1)
- call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['an amiable', ''], getline(1, '$'))
-
- setlocal thesaurusfunc=NonExistingFunc
- call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')
-
- setlocal thesaurusfunc=
- set thesaurusfunc=NonExistingFunc
- call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')
- %bw!
-
- set thesaurusfunc=
- set thesaurus=
-endfunc
-
-func Test_edit_CTRL_U()
- " Test 'completefunc'
- new
- " -1, -2 and -3 are special return values
- let g:special=0
- fun! CompleteMonths(findstart, base)
- if a:findstart
- " locate the start of the word
- return g:special
- else
- " find months matching with "a:base"
- let res = []
- for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")
- if m =~ '^\c'.a:base
- call add(res, {'word': m, 'abbr': m.' Month', 'icase': 0})
- endif
- endfor
- return {'words': res, 'refresh': 'always'}
- endif
- endfun
- set completefunc=CompleteMonths
- call setline(1, ['', ''])
- call cursor(1, 1)
- call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
- call assert_equal(['X', '', ''], getline(1, '$'))
- %d
- let g:special=-1
- call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
- call assert_equal(['XJan', ''], getline(1, '$'))
- %d
- let g:special=-2
- call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
- call assert_equal(['X', ''], getline(1, '$'))
- %d
- let g:special=-3
- call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
- call assert_equal(['X', ''], getline(1, '$'))
- %d
- let g:special=0
- call feedkeys("AM\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
- call assert_equal(['Mar', ''], getline(1, '$'))
- %d
- call feedkeys("AM\<c-x>\<c-u>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['May', ''], getline(1, '$'))
- %d
- call feedkeys("AM\<c-x>\<c-u>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
- call assert_equal(['M', ''], getline(1, '$'))
- delfu CompleteMonths
- %d
- try
- call feedkeys("A\<c-x>\<c-u>", 'tnix')
- call assert_fails(1, 'unknown completion function')
- catch /^Vim\%((\a\+)\)\=:E117/
- call assert_true(1, 'E117 error caught')
- endtry
- set completefunc=
- bw!
-endfunc
-
-func Test_edit_completefunc_delete()
- func CompleteFunc(findstart, base)
- if a:findstart == 1
- return col('.') - 1
- endif
- normal dd
- return ['a', 'b']
- endfunc
- new
- set completefunc=CompleteFunc
- call setline(1, ['', 'abcd', ''])
- 2d
- call assert_fails("normal 2G$a\<C-X>\<C-U>", 'E565:')
- bwipe!
-endfunc
-
-func Test_edit_CTRL_Z()
- " Ctrl-Z when insertmode is not set inserts it literally
- new
- call setline(1, 'abc')
- call feedkeys("A\<c-z>\<esc>", 'tnix')
- call assert_equal(["abc\<c-z>"], getline(1,'$'))
- bw!
- " TODO: How to Test Ctrl-Z in insert mode, e.g. suspend?
-endfunc
-
-func Test_edit_DROP()
- if !has("dnd")
- return
- endif
- new
- call setline(1, ['abc def ghi'])
- call cursor(1, 1)
- try
- call feedkeys("i\<Drop>\<Esc>", 'tnix')
- call assert_fails(1, 'Invalid register name')
- catch /^Vim\%((\a\+)\)\=:E353/
- call assert_true(1, 'error caught')
- endtry
- bw!
-endfunc
-
-func Test_edit_CTRL_V()
- new
- call setline(1, ['abc'])
- call cursor(2, 1)
-
- " force some redraws
- set showmode showcmd
- " call test_override('char_avail', 1)
-
- call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
- call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
-
- if has("rightleft") && exists("+rl")
- set rl
- call setline(1, ['abc'])
- call cursor(2, 1)
- call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
- call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
- set norl
- endif
-
- set noshowmode showcmd
- " call test_override('char_avail', 0)
-
- " No modifiers should be applied to the char typed using i_CTRL-V_digit.
- call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix')
- call assert_equal('LcL<C-F2>πjπ<M-F3>', getline(2))
-
- if has('osx')
- " A char with a modifier should not be a valid char for i_CTRL-V_digit.
- call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix')
- call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3))
- endif
-
- bw!
-endfunc
-
-func Test_edit_F1()
- " Pressing <f1>
- new
- " call feedkeys(":set im\<cr>\<f1>\<c-l>", 'tnix')
- call feedkeys("i\<f1>\<esc>", 'tnix')
- set noinsertmode
- call assert_equal('help', &buftype)
- bw
- bw
-endfunc
-
-func Test_edit_F21()
- " Pressing <f21>
- " sends a netbeans command
- if has("netbeans_intg")
- new
- " I have no idea what this is supposed to do :)
- call feedkeys("A\<F21>\<F1>\<esc>", 'tnix')
- bw
- endif
-endfunc
-
-func Test_edit_HOME_END()
- " Test Home/End Keys
- new
- set foldopen+=hor
- call setline(1, ['abc', 'def'])
- call cursor(1, 1)
- call feedkeys("AX\<Home>Y\<esc>", 'tnix')
- call cursor(2, 1)
- call feedkeys("iZ\<End>Y\<esc>", 'tnix')
- call assert_equal(['YabcX', 'ZdefY'], getline(1, '$'))
-
- set foldopen-=hor
- bw!
-endfunc
-
-func Test_edit_INS()
- " Test for Pressing <Insert>
- new
- call setline(1, ['abc', 'def'])
- call cursor(1, 1)
- call feedkeys("i\<Insert>ZYX>", 'tnix')
- call assert_equal(['ZYX>', 'def'], getline(1, '$'))
- call setline(1, ['abc', 'def'])
- call cursor(1, 1)
- call feedkeys("i\<Insert>Z\<Insert>YX>", 'tnix')
- call assert_equal(['ZYX>bc', 'def'], getline(1, '$'))
- bw!
-endfunc
-
-func Test_edit_LEFT_RIGHT()
- " Left, Shift-Left, Right, Shift-Right
- new
- call setline(1, ['abc def ghi', 'ABC DEF GHI', 'ZZZ YYY XXX'])
- let _ww=&ww
- set ww=
- call cursor(2, 1)
- call feedkeys("i\<left>\<esc>", 'tnix')
- call assert_equal([0, 2, 1, 0], getpos('.'))
- " Is this a bug, <s-left> does not respect whichwrap option
- call feedkeys("i\<s-left>\<esc>", 'tnix')
- call assert_equal([0, 1, 8, 0], getpos('.'))
- call feedkeys("i". repeat("\<s-left>", 3). "\<esc>", 'tnix')
- call assert_equal([0, 1, 1, 0], getpos('.'))
- call feedkeys("i\<right>\<esc>", 'tnix')
- call assert_equal([0, 1, 1, 0], getpos('.'))
- call feedkeys("i\<right>\<right>\<esc>", 'tnix')
- call assert_equal([0, 1, 2, 0], getpos('.'))
- call feedkeys("A\<right>\<esc>", 'tnix')
- call assert_equal([0, 1, 11, 0], getpos('.'))
- call feedkeys("A\<s-right>\<esc>", 'tnix')
- call assert_equal([0, 2, 1, 0], getpos('.'))
- call feedkeys("i\<s-right>\<esc>", 'tnix')
- call assert_equal([0, 2, 4, 0], getpos('.'))
- call cursor(3, 11)
- call feedkeys("A\<right>\<esc>", 'tnix')
- call feedkeys("A\<s-right>\<esc>", 'tnix')
- call assert_equal([0, 3, 11, 0], getpos('.'))
- call cursor(2, 11)
- " <S-Right> does not respect 'whichwrap' option
- call feedkeys("A\<s-right>\<esc>", 'tnix')
- call assert_equal([0, 3, 1, 0], getpos('.'))
- " Check motion when 'whichwrap' contains cursor keys for insert mode
- set ww+=[,]
- call cursor(2, 1)
- call feedkeys("i\<left>\<esc>", 'tnix')
- call assert_equal([0, 1, 11, 0], getpos('.'))
- call cursor(2, 11)
- call feedkeys("A\<right>\<esc>", 'tnix')
- call assert_equal([0, 3, 1, 0], getpos('.'))
- call cursor(2, 11)
- call feedkeys("A\<s-right>\<esc>", 'tnix')
- call assert_equal([0, 3, 1, 0], getpos('.'))
- let &ww = _ww
- bw!
-endfunc
-
-func Test_edit_MOUSE()
- " This is a simple test, since we not really using the mouse here
- CheckFeature mouse
- 10new
- call setline(1, range(1, 100))
- call cursor(1, 1)
- call assert_equal(1, line('w0'))
- call assert_equal(10, line('w$'))
- set mouse=a
- " One scroll event moves three lines.
- call feedkeys("A\<ScrollWheelDown>\<esc>", 'tnix')
- call assert_equal(4, line('w0'))
- call assert_equal(13, line('w$'))
- " This should move by one page down.
- call feedkeys("A\<S-ScrollWheelDown>\<esc>", 'tnix')
- call assert_equal(14, line('w0'))
- set nostartofline
- " Another page down.
- call feedkeys("A\<C-ScrollWheelDown>\<esc>", 'tnix')
- call assert_equal(24, line('w0'))
-
- call assert_equal([0, 24, 2, 0], getpos('.'))
- call Ntest_setmouse(4, 3)
- call feedkeys("A\<LeftMouse>\<esc>", 'tnix')
- call assert_equal([0, 27, 2, 0], getpos('.'))
- set mousemodel=extend
- call Ntest_setmouse(5, 3)
- call feedkeys("A\<RightMouse>\<esc>\<esc>", 'tnix')
- call assert_equal([0, 28, 2, 0], getpos('.'))
- set mousemodel&
- call cursor(1, 100)
- norm! zt
- " this should move by a screen up, but when the test
- " is run, it moves up to the top of the buffer...
- call feedkeys("A\<ScrollWheelUp>\<esc>", 'tnix')
- call assert_equal([0, 1, 1, 0], getpos('.'))
- call cursor(1, 30)
- norm! zt
- call feedkeys("A\<S-ScrollWheelUp>\<esc>", 'tnix')
- call assert_equal([0, 1, 1, 0], getpos('.'))
- call cursor(1, 30)
- norm! zt
- call feedkeys("A\<C-ScrollWheelUp>\<esc>", 'tnix')
- call assert_equal([0, 1, 1, 0], getpos('.'))
- %d
- call setline(1, repeat(["12345678901234567890"], 100))
- call cursor(2, 1)
- call feedkeys("A\<ScrollWheelRight>\<esc>", 'tnix')
- call assert_equal([0, 2, 20, 0], getpos('.'))
- call feedkeys("A\<ScrollWheelLeft>\<esc>", 'tnix')
- call assert_equal([0, 2, 20, 0], getpos('.'))
- call feedkeys("A\<S-ScrollWheelRight>\<esc>", 'tnix')
- call assert_equal([0, 2, 20, 0], getpos('.'))
- call feedkeys("A\<S-ScrollWheelLeft>\<esc>", 'tnix')
- call assert_equal([0, 2, 20, 0], getpos('.'))
- call feedkeys("A\<C-ScrollWheelRight>\<esc>", 'tnix')
- call assert_equal([0, 2, 20, 0], getpos('.'))
- call feedkeys("A\<C-ScrollWheelLeft>\<esc>", 'tnix')
- call assert_equal([0, 2, 20, 0], getpos('.'))
- set mouse& startofline
- bw!
-endfunc
-
-func Test_edit_PAGEUP_PAGEDOWN()
- 10new
- call setline(1, repeat(['abc def ghi'], 30))
- call cursor(1, 1)
- call feedkeys("i\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 9, 1, 0], getpos('.'))
- call feedkeys("i\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 17, 1, 0], getpos('.'))
- call feedkeys("i\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 25, 1, 0], getpos('.'))
- call feedkeys("i\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 30, 1, 0], getpos('.'))
- call feedkeys("i\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 30, 1, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 29, 1, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 21, 1, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 13, 1, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 5, 1, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 5, 11, 0], getpos('.'))
- " <S-Up> is the same as <PageUp>
- " <S-Down> is the same as <PageDown>
- call cursor(1, 1)
- call feedkeys("i\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 9, 1, 0], getpos('.'))
- call feedkeys("i\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 17, 1, 0], getpos('.'))
- call feedkeys("i\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 25, 1, 0], getpos('.'))
- call feedkeys("i\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 30, 1, 0], getpos('.'))
- call feedkeys("i\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 30, 1, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 29, 1, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 21, 1, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 13, 1, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 5, 1, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 5, 11, 0], getpos('.'))
- set nostartofline
- call cursor(30, 11)
- norm! zt
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 29, 11, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 21, 11, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 13, 11, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 5, 11, 0], getpos('.'))
- call feedkeys("A\<PageUp>\<esc>", 'tnix')
- call assert_equal([0, 5, 11, 0], getpos('.'))
- call cursor(1, 1)
- call feedkeys("A\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 9, 11, 0], getpos('.'))
- call feedkeys("A\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 17, 11, 0], getpos('.'))
- call feedkeys("A\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 25, 11, 0], getpos('.'))
- call feedkeys("A\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 30, 11, 0], getpos('.'))
- call feedkeys("A\<PageDown>\<esc>", 'tnix')
- call assert_equal([0, 30, 11, 0], getpos('.'))
- " <S-Up> is the same as <PageUp>
- " <S-Down> is the same as <PageDown>
- call cursor(30, 11)
- norm! zt
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 29, 11, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 21, 11, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 13, 11, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 5, 11, 0], getpos('.'))
- call feedkeys("A\<S-Up>\<esc>", 'tnix')
- call assert_equal([0, 5, 11, 0], getpos('.'))
- call cursor(1, 1)
- call feedkeys("A\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 9, 11, 0], getpos('.'))
- call feedkeys("A\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 17, 11, 0], getpos('.'))
- call feedkeys("A\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 25, 11, 0], getpos('.'))
- call feedkeys("A\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 30, 11, 0], getpos('.'))
- call feedkeys("A\<S-Down>\<esc>", 'tnix')
- call assert_equal([0, 30, 11, 0], getpos('.'))
- bw!
-endfunc
-
-func Test_edit_forbidden()
- new
- " 1) edit in the sandbox is not allowed
- call setline(1, 'a')
- com! Sandbox :sandbox call feedkeys("i\<del>\<esc>", 'tnix')
- call assert_fails(':Sandbox', 'E48:')
- com! Sandbox :sandbox exe "norm! i\<del>"
- call assert_fails(':Sandbox', 'E48:')
- delcom Sandbox
- call assert_equal(['a'], getline(1,'$'))
-
- " 2) edit with textlock set
- fu! DoIt()
- call feedkeys("i\<del>\<esc>", 'tnix')
- endfu
- au InsertCharPre <buffer> :call DoIt()
- try
- call feedkeys("ix\<esc>", 'tnix')
- call assert_fails(1, 'textlock')
- catch /^Vim\%((\a\+)\)\=:E565/ " catch E565: not allowed here
- endtry
- " TODO: Might be a bug: should x really be inserted here
- call assert_equal(['xa'], getline(1, '$'))
- delfu DoIt
- try
- call feedkeys("ix\<esc>", 'tnix')
- call assert_fails(1, 'unknown function')
- catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function
- endtry
- au! InsertCharPre
-
- " 3) edit when completion is shown
- fun! Complete(findstart, base)
- if a:findstart
- return col('.')
- else
- call feedkeys("i\<del>\<esc>", 'tnix')
- return []
- endif
- endfun
- set completefunc=Complete
- try
- call feedkeys("i\<c-x>\<c-u>\<esc>", 'tnix')
- call assert_fails(1, 'change in complete function')
- catch /^Vim\%((\a\+)\)\=:E565/ " catch E565
- endtry
- delfu Complete
- set completefunc=
-
- if has("rightleft") && exists("+fkmap")
- " 4) 'R' when 'fkmap' and 'revins' is set.
- set revins fkmap
- try
- normal Ri
- call assert_fails(1, "R with 'fkmap' and 'ri' set")
- catch
- finally
- set norevins nofkmap
- endtry
- endif
- bw!
-endfunc
-
-func Test_edit_rightleft()
- " Cursor in rightleft mode moves differently
- CheckFeature rightleft
- call NewWindow(10, 20)
- call setline(1, ['abc', 'def', 'ghi'])
- call cursor(1, 2)
- set rightleft
- " Screen looks as expected
- let lines = ScreenLines([1, 4], winwidth(0))
- let expect = [
- \" cba",
- \" fed",
- \" ihg",
- \" ~"]
- call assert_equal(join(expect, "\n"), join(lines, "\n"))
- " 2) right moves to the left
- call feedkeys("i\<right>\<esc>x", 'txin')
- call assert_equal(['bc', 'def', 'ghi'], getline(1,'$'))
- call cursor(1, 2)
- call feedkeys("i\<s-right>\<esc>", 'txin')
- call cursor(1, 2)
- call feedkeys("i\<c-right>\<esc>", 'txin')
- " Screen looks as expected
- let lines = ScreenLines([1, 4], winwidth(0))
- let expect = [
- \" cb",
- \" fed",
- \" ihg",
- \" ~"]
- call assert_equal(join(expect, "\n"), join(lines, "\n"))
- " 2) left moves to the right
- call setline(1, ['abc', 'def', 'ghi'])
- call cursor(1, 2)
- call feedkeys("i\<left>\<esc>x", 'txin')
- call assert_equal(['ac', 'def', 'ghi'], getline(1,'$'))
- call cursor(1, 2)
- call feedkeys("i\<s-left>\<esc>", 'txin')
- call cursor(1, 2)
- call feedkeys("i\<c-left>\<esc>", 'txin')
- " Screen looks as expected
- let lines = ScreenLines([1, 4], winwidth(0))
- let expect = [
- \" ca",
- \" fed",
- \" ihg",
- \" ~"]
- call assert_equal(join(expect, "\n"), join(lines, "\n"))
- %d _
- " call test_override('redraw_flag', 1)
- " call test_override('char_avail', 1)
- call feedkeys("a\<C-V>x41", "xt")
- redraw!
- call assert_equal(repeat(' ', 19) .. 'A', Screenline(1))
- " call test_override('ALL', 0)
- set norightleft
- bw!
-endfunc
-
-func Test_edit_backtick()
- next a\`b c
- call assert_equal('a`b', expand('%'))
- next
- call assert_equal('c', expand('%'))
- call assert_equal('a\`b c', expand('##'))
-endfunc
-
-func Test_edit_quit()
- edit foo.txt
- split
- new
- call setline(1, 'hello')
- 3wincmd w
- redraw!
- call assert_fails('1q', 'E37:')
- bwipe! foo.txt
- only
-endfunc
-
-func Test_edit_complete_very_long_name()
- if !has('unix')
- " Long directory names only work on Unix.
- return
- endif
-
- let dirname = getcwd() . "/Xdir"
- let longdirname = dirname . repeat('/' . repeat('d', 255), 4)
- try
- call mkdir(longdirname, 'p')
- catch /E739:/
- " Long directory name probably not supported.
- call delete(dirname, 'rf')
- return
- endtry
-
- " Try to get the Vim window position before setting 'columns', so that we can
- " move the window back to where it was.
- let winposx = getwinposx()
- let winposy = getwinposy()
-
- if winposx >= 0 && winposy >= 0 && !has('gui_running')
- " We did get the window position, but xterm may report the wrong numbers.
- " Move the window to the reported position and compute any offset.
- exe 'winpos ' . winposx . ' ' . winposy
- sleep 100m
- let x = getwinposx()
- if x >= 0
- let winposx += winposx - x
- endif
- let y = getwinposy()
- if y >= 0
- let winposy += winposy - y
- endif
- endif
-
- let save_columns = &columns
- " Need at least about 1100 columns to reproduce the problem.
- set columns=2000
- set noswapfile
-
- let longfilename = longdirname . '/' . repeat('a', 255)
- call writefile(['Totum', 'Table'], longfilename)
- new
- exe "next Xfile " . longfilename
- exe "normal iT\<C-N>"
-
- bwipe!
- exe 'bwipe! ' . longfilename
- call delete(dirname, 'rf')
- let &columns = save_columns
- if winposx >= 0 && winposy >= 0
- exe 'winpos ' . winposx . ' ' . winposy
- endif
- set swapfile&
-endfunc
-
-func Test_edit_alt()
- " Keeping the cursor line didn't happen when the first line has indent.
- new
- call setline(1, [' one', 'two', 'three'])
- w XAltFile
- $
- call assert_equal(3, line('.'))
- e Xother
- e #
- call assert_equal(3, line('.'))
-
- bwipe XAltFile
- call delete('XAltFile')
-endfunc
-
-func Test_edit_InsertLeave()
- new
- au InsertLeavePre * let g:did_au_pre = 1
- au InsertLeave * let g:did_au = 1
- let g:did_au_pre = 0
- let g:did_au = 0
- call feedkeys("afoo\<Esc>", 'tx')
- call assert_equal(1, g:did_au_pre)
- call assert_equal(1, g:did_au)
- call assert_equal('foo', getline(1))
-
- let g:did_au_pre = 0
- let g:did_au = 0
- call feedkeys("Sbar\<C-C>", 'tx')
- call assert_equal(1, g:did_au_pre)
- call assert_equal(0, g:did_au)
- call assert_equal('bar', getline(1))
-
- inoremap x xx<Esc>
- let g:did_au_pre = 0
- let g:did_au = 0
- call feedkeys("Saax", 'tx')
- call assert_equal(1, g:did_au_pre)
- call assert_equal(1, g:did_au)
- call assert_equal('aaxx', getline(1))
-
- inoremap x xx<C-C>
- let g:did_au_pre = 0
- let g:did_au = 0
- call feedkeys("Sbbx", 'tx')
- call assert_equal(1, g:did_au_pre)
- call assert_equal(0, g:did_au)
- call assert_equal('bbxx', getline(1))
-
- bwipe!
- au! InsertLeave InsertLeavePre
- 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')
- call delete(undofile('XtestUndo'))
- set undofile&
-endfunc
-
-" Test for inserting characters using CTRL-V followed by a number.
-func Test_edit_special_chars()
- new
-
- let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
-
- exe "normal " . t
- call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2))
-
- close!
-endfunc
-
-func Test_edit_startinsert()
- new
- set backspace+=start
- call setline(1, 'foobar')
- call feedkeys("A\<C-U>\<Esc>", 'xt')
- call assert_equal('', getline(1))
-
- call setline(1, 'foobar')
- call feedkeys(":startinsert!\<CR>\<C-U>\<Esc>", 'xt')
- call assert_equal('', getline(1))
-
- set backspace&
- bwipe!
-endfunc
-
-" Test for :startreplace and :startgreplace
-func Test_edit_startreplace()
- new
- call setline(1, 'abc')
- call feedkeys("l:startreplace\<CR>xyz\e", 'xt')
- call assert_equal('axyz', getline(1))
- call feedkeys("0:startreplace!\<CR>abc\e", 'xt')
- call assert_equal('axyzabc', getline(1))
- call setline(1, "a\tb")
- call feedkeys("0l:startgreplace\<CR>xyz\e", 'xt')
- call assert_equal("axyz\tb", getline(1))
- call feedkeys("0i\<C-R>=execute('startreplace')\<CR>12\e", 'xt')
- call assert_equal("12axyz\tb", getline(1))
- close!
-endfunc
-
-func Test_edit_noesckeys()
- CheckNotGui
- new
-
- " <Left> moves cursor when 'esckeys' is set
- exe "set t_kl=\<Esc>OD"
- " set esckeys
- call feedkeys("axyz\<Esc>ODX", "xt")
- " call assert_equal("xyXz", getline(1))
-
- " <Left> exits Insert mode when 'esckeys' is off
- " set noesckeys
- call setline(1, '')
- call feedkeys("axyz\<Esc>ODX", "xt")
- call assert_equal(["DX", "xyz"], getline(1, 2))
-
- bwipe!
- " set esckeys
-endfunc
-
-" Test for running an invalid ex command in insert mode using CTRL-O
-func Test_edit_ctrl_o_invalid_cmd()
- new
- set showmode showcmd
- " Avoid a sleep of 3 seconds. Zero might have side effects.
- " call test_override('ui_delay', 50)
- let caught_e492 = 0
- try
- call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt")
- catch /E492:/
- let caught_e492 = 1
- endtry
- call assert_equal(1, caught_e492)
- call assert_equal('abc', getline(1))
- set showmode& showcmd&
- " call test_override('ui_delay', 0)
- close!
-endfunc
-
-" Test for editing a file with a very long name
-func Test_edit_illegal_filename()
- CheckEnglish
- new
- redir => msg
- exe 'edit ' . repeat('f', 5000)
- redir END
- call assert_match("Illegal file name$", split(msg, "\n")[0])
- close!
-endfunc
-
-" Test for editing a directory
-func Test_edit_is_a_directory()
- CheckEnglish
- let dirname = getcwd() . "/Xdir"
- call mkdir(dirname, 'p')
-
- new
- redir => msg
- exe 'edit' dirname
- redir END
- call assert_match("is a directory$", split(msg, "\n")[0])
- bwipe!
-
- let dirname .= '/'
-
- new
- redir => msg
- exe 'edit' dirname
- redir END
- call assert_match("is a directory$", split(msg, "\n")[0])
- bwipe!
-
- call delete(dirname, 'rf')
-endfunc
-
-" Test for editing a file using invalid file encoding
-func Test_edit_invalid_encoding()
- CheckEnglish
- call writefile([], 'Xfile')
- redir => msg
- new ++enc=axbyc Xfile
- redir END
- call assert_match('\[NOT converted\]', msg)
- call delete('Xfile')
- close!
-endfunc
-
-" Test for the "charconvert" option
-func Test_edit_charconvert()
- CheckEnglish
- call writefile(['one', 'two'], 'Xfile')
-
- " set 'charconvert' to a non-existing function
- set charconvert=NonExitingFunc()
- new
- let caught_e117 = v:false
- try
- redir => msg
- edit ++enc=axbyc Xfile
- catch /E117:/
- let caught_e117 = v:true
- finally
- redir END
- endtry
- call assert_true(caught_e117)
- call assert_equal(['one', 'two'], getline(1, '$'))
- call assert_match("Conversion with 'charconvert' failed", msg)
- close!
- set charconvert&
-
- " 'charconvert' function doesn't create a output file
- func Cconv1()
- endfunc
- set charconvert=Cconv1()
- new
- redir => msg
- edit ++enc=axbyc Xfile
- redir END
- call assert_equal(['one', 'two'], getline(1, '$'))
- call assert_match("can't read output of 'charconvert'", msg)
- close!
- delfunc Cconv1
- set charconvert&
-
- " 'charconvert' function to convert to upper case
- func Cconv2()
- let data = readfile(v:fname_in)
- call map(data, 'toupper(v:val)')
- call writefile(data, v:fname_out)
- endfunc
- set charconvert=Cconv2()
- new Xfile
- write ++enc=ucase Xfile1
- call assert_equal(['ONE', 'TWO'], readfile('Xfile1'))
- call delete('Xfile1')
- close!
- delfunc Cconv2
- set charconvert&
-
- " 'charconvert' function removes the input file
- func Cconv3()
- call delete(v:fname_in)
- endfunc
- set charconvert=Cconv3()
- new
- call assert_fails('edit ++enc=lcase Xfile', 'E202:')
- call assert_equal([''], getline(1, '$'))
- close!
- delfunc Cconv3
- set charconvert&
-
- call delete('Xfile')
-endfunc
-
-" Test for editing a file without read permission
-func Test_edit_file_no_read_perm()
- CheckUnix
- CheckNotRoot
-
- call writefile(['one', 'two'], 'Xfile')
- call setfperm('Xfile', '-w-------')
- new
- redir => msg
- edit Xfile
- redir END
- call assert_equal(1, &readonly)
- call assert_equal([''], getline(1, '$'))
- call assert_match('\[Permission Denied\]', msg)
- close!
- call delete('Xfile')
-endfunc
-
-" Using :edit without leaving 'insertmode' should not cause Insert mode to be
-" re-entered immediately after <C-L>
-func Test_edit_insertmode_ex_edit()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set insertmode noruler
- inoremap <C-B> <Cmd>edit Xfoo<CR>
- END
- call writefile(lines, 'Xtest_edit_insertmode_ex_edit')
-
- let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6})
- call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, 6))})
- call term_sendkeys(buf, "\<C-B>\<C-L>")
- call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_edit_insertmode_ex_edit')
-endfunc
-
-" Pressing escape in 'insertmode' should beep
-" FIXME: Execute this later, when using valgrind it makes the next test
-" Test_edit_insertmode_ex_edit() fail.
-func Test_z_edit_insertmode_esc_beeps()
- new
- " set insertmode
- " call assert_beeps("call feedkeys(\"one\<Esc>\", 'xt')")
- set insertmode&
- " unsupported "CTRL-G l" command should beep in insert mode.
- call assert_beeps("normal i\<C-G>l")
- bwipe!
-endfunc
-
-" Test for 'hkmap' and 'hkmapp'
-func Test_edit_hkmap()
- CheckFeature rightleft
- if has('win32') && !has('gui')
- " Test fails on the MS-Windows terminal version
- return
- endif
- new
-
- set revins hkmap
- let str = 'abcdefghijklmnopqrstuvwxyz'
- let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- let str ..= '`/'',.;'
- call feedkeys('i' .. str, 'xt')
- let expected = "óõú,.;"
- let expected ..= "ZYXWVUTSRQPONMLKJIHGFEDCBA"
- let expected ..= "æèñ'äåàãø/ôíîöêìçïéòë÷âáðù"
- call assert_equal(expected, getline(1))
-
- %d
- set revins hkmap hkmapp
- let str = 'abcdefghijklmnopqrstuvwxyz'
- let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- call feedkeys('i' .. str, 'xt')
- let expected = "õYXWVUTSRQóOïíLKJIHGFEDêBA"
- let expected ..= "öòXùåèúæø'ôñðîì÷çéäâóǟãëáà"
- call assert_equal(expected, getline(1))
-
- set revins& hkmap& hkmapp&
- close!
-endfunc
-
-" Test for 'allowrevins' and using CTRL-_ in insert mode
-func Test_edit_allowrevins()
- CheckFeature rightleft
- new
- set allowrevins
- call feedkeys("iABC\<C-_>DEF\<C-_>GHI", 'xt')
- call assert_equal('ABCFEDGHI', getline(1))
- set allowrevins&
- close!
-endfunc
-
-" Test for inserting a register in insert mode using CTRL-R
-func Test_edit_insert_reg()
- throw 'Skipped: use test/functional/legacy/edit_spec.lua'
- new
- let g:Line = ''
- func SaveFirstLine()
- let g:Line = Screenline(1)
- return 'r'
- endfunc
- inoremap <expr> <buffer> <F2> SaveFirstLine()
- call test_override('redraw_flag', 1)
- call test_override('char_avail', 1)
- let @r = 'sample'
- call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt")
- call assert_equal('"', g:Line)
- call test_override('ALL', 0)
- close!
-endfunc
-
-" When a character is inserted at the last position of the last line in a
-" window, the window contents should be scrolled one line up. If the top line
-" is part of a fold, then the entire fold should be scrolled up.
-func Test_edit_lastline_scroll()
- new
- let h = winheight(0)
- let lines = ['one', 'two', 'three']
- let lines += repeat(['vim'], h - 4)
- call setline(1, lines)
- call setline(h, repeat('x', winwidth(0) - 1))
- call feedkeys("GAx", 'xt')
- redraw!
- call assert_equal(h - 1, winline())
- call assert_equal(2, line('w0'))
-
- " scroll with a fold
- 1,2fold
- normal gg
- call setline(h + 1, repeat('x', winwidth(0) - 1))
- call feedkeys("GAx", 'xt')
- redraw!
- call assert_equal(h - 1, winline())
- call assert_equal(3, line('w0'))
-
- close!
-endfunc
-
-func Test_edit_browse()
- " in the GUI this opens a file picker, we only test the terminal behavior
- CheckNotGui
-
- " ":browse xxx" checks for the FileExplorer augroup and assumes editing "."
- " works then.
- augroup FileExplorer
- au!
- augroup END
-
- " When the USE_FNAME_CASE is defined this used to cause a crash.
- browse enew
- bwipe!
-
- browse split
- bwipe!
-endfunc
-
-func Test_read_invalid()
- " set encoding=latin1
- " This was not properly checking for going past the end.
- call assert_fails('r`=', 'E484')
- set encoding=utf-8
-endfunc
-
-" Test for the 'revins' option
-func Test_edit_revins()
- CheckFeature rightleft
- new
- set revins
- exe "normal! ione\ttwo three"
- call assert_equal("eerht owt\teno", getline(1))
- call setline(1, "one\ttwo three")
- normal! gg$bi a
- call assert_equal("one\ttwo a three", getline(1))
- exe "normal! $bi\<BS>\<BS>"
- call assert_equal("one\ttwo a ree", getline(1))
- exe "normal! 0wi\<C-W>"
- call assert_equal("one\t a ree", getline(1))
- exe "normal! 0wi\<C-U>"
- call assert_equal("one\t ", getline(1))
- " newline in insert mode starts at the end of the line
- call setline(1, 'one two three')
- exe "normal! wi\nfour"
- call assert_equal(['one two three', 'ruof'], getline(1, '$'))
- set revins&
- bw!
-endfunc
-
-" Test for getting the character of the line below after "p"
-func Test_edit_put_CTRL_E()
- " set encoding=latin1
- new
- let @" = ''
- sil! norm orggRx
- sil! norm pr
- call assert_equal(['r', 'r'], getline(1, 2))
- bwipe!
- set encoding=utf-8
-endfunc
-
-" Test toggling of input method. See :help i_CTRL-^
-func Test_edit_CTRL_hat()
- CheckFeature xim
-
- " FIXME: test fails with Motif GUI.
- " test also fails when running in the GUI.
- CheckFeature gui_gtk
- CheckNotGui
-
- new
-
- call assert_equal(0, &iminsert)
- call feedkeys("i\<C-^>", 'xt')
- call assert_equal(2, &iminsert)
- call feedkeys("i\<C-^>", 'xt')
- call assert_equal(0, &iminsert)
-
- bwipe!
-endfunc
-
-" Weird long file name was going over the end of NameBuff
-func Test_edit_overlong_file_name()
- CheckUnix
-
- file 0000000000000000000000000000
- file %%%%%%%%%%%%%%%%%%%%%%%%%%
- file %%%%%%
- set readonly
- set ls=2
-
- redraw!
- set noreadonly ls&
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim
deleted file mode 100644
index d8344817f5..0000000000
--- a/src/nvim/testdir/test_environ.vim
+++ /dev/null
@@ -1,89 +0,0 @@
-" Test for environment variables.
-
-scriptencoding utf-8
-
-source check.vim
-
-func Test_environ()
- unlet! $TESTENV
- call assert_equal(0, has_key(environ(), 'TESTENV'))
- let $TESTENV = 'foo'
- call assert_equal(1, has_key(environ(), 'TESTENV'))
- let $TESTENV = 'ã“ã‚“ã«ã¡ã‚'
- call assert_equal('ã“ã‚“ã«ã¡ã‚', environ()['TESTENV'])
-endfunc
-
-func Test_getenv()
- unlet! $TESTENV
- call assert_equal(v:null, 'TESTENV'->getenv())
- let $TESTENV = 'foo'
- call assert_equal('foo', getenv('TESTENV'))
-endfunc
-
-func Test_setenv()
- unlet! $TESTENV
- eval 'foo'->setenv('TEST ENV')
- call assert_equal('foo', getenv('TEST ENV'))
- call setenv('TEST ENV', v:null)
- call assert_equal(v:null, getenv('TEST ENV'))
-endfunc
-
-func Test_special_env()
- " The value for $HOME is cached internally by Vim, ensure the value is up to
- " date.
- let orig_ENV = $HOME
-
- let $HOME = 'foo'
- call assert_equal('foo', expand('~'))
- " old $HOME value is kept until a new one is set
- unlet $HOME
- call assert_equal('foo', expand('~'))
-
- call setenv('HOME', 'bar')
- call assert_equal('bar', expand('~'))
- " old $HOME value is kept until a new one is set
- call setenv('HOME', v:null)
- call assert_equal('bar', expand('~'))
-
- let $HOME = orig_ENV
-endfunc
-
-func Test_external_env()
- call setenv('FOO', 'HelloWorld')
- if has('win32')
- let result = system('echo %FOO%')
- else
- let result = system('echo $FOO')
- endif
- let result = substitute(result, '[ \r\n]', '', 'g')
- call assert_equal('HelloWorld', result)
-
- call setenv('FOO', v:null)
- if has('win32')
- let result = system('set | findstr "^FOO="')
- else
- let result = system('env | grep ^FOO=')
- endif
- call assert_equal('', result)
-endfunc
-
-func Test_mac_locale()
- CheckFeature osxdarwin
-
- " If $LANG is not set then the system locale will be used.
- " Run Vim after unsetting all the locale environmental vars, and capture the
- " output of :lang.
- let lang_results = system("unset LANG; unset LC_MESSAGES; " ..
- \ shellescape(v:progpath) ..
- \ " --clean -esX -c 'redir @a' -c 'lang' -c 'put a' -c 'print' -c 'qa!' ")
-
- " Check that:
- " 1. The locale is the form of <locale>.UTF-8.
- " 2. Check that fourth item (LC_NUMERIC) is properly set to "C".
- " Example match: "en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8"
- call assert_match('"\([a-zA-Z_]\+\.UTF-8/\)\{3}C\(/[a-zA-Z_]\+\.UTF-8\)\{2}"',
- \ lang_results,
- \ "Default locale should have UTF-8 encoding set, and LC_NUMERIC set to 'C'")
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_erasebackword.vim b/src/nvim/testdir/test_erasebackword.vim
deleted file mode 100644
index 9522ec2cd6..0000000000
--- a/src/nvim/testdir/test_erasebackword.vim
+++ /dev/null
@@ -1,19 +0,0 @@
-
-func Test_erasebackword()
- enew
-
- exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>"
- call assert_equal(' wwwã“ã‚“ã«ã¡ã‚世界ワールド', getline('.'))
- exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>"
- call assert_equal(' wwwã“ã‚“ã«ã¡ã‚世界', getline('.'))
- exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>"
- call assert_equal(' wwwã“ã‚“ã«ã¡ã‚', getline('.'))
- exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>"
- call assert_equal(' www', getline('.'))
- exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>"
- call assert_equal(' ', getline('.'))
- exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>\<C-W>"
- call assert_equal('', getline('.'))
-
- enew!
-endfunc
diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim
deleted file mode 100644
index 9f53c76a2c..0000000000
--- a/src/nvim/testdir/test_escaped_glob.vim
+++ /dev/null
@@ -1,34 +0,0 @@
-" Test whether glob()/globpath() return correct results with certain escaped
-" characters.
-
-func SetUp()
- " consistent sorting of file names
- set nofileignorecase
-endfunction
-
-function Test_glob()
- if !has('unix')
- " This test fails on Windows because of the special characters in the
- " filenames. Disable the test on non-Unix systems for now.
- return
- endif
-
- " Execute these commands in the sandbox, so that using the shell fails.
- " Setting 'shell' to an invalid name causes a memory leak.
- sandbox call assert_equal("", glob('Xxx\{'))
- sandbox call assert_equal("", 'Xxx\$'->glob())
- w! Xxx\{
- " } to fix highlighting
- w! Xxx\$
- sandbox call assert_equal("Xxx{", glob('Xxx\{'))
- sandbox call assert_equal("Xxx$", glob('Xxx\$'))
- call delete('Xxx{')
- call delete('Xxx$')
-endfunction
-
-function Test_globpath()
- sandbox call assert_equal(expand("sautest/autoload/globone.vim\nsautest/autoload/globtwo.vim"),
- \ globpath('sautest/autoload', 'glob*.vim'))
- sandbox call assert_equal([expand('sautest/autoload/globone.vim'), expand('sautest/autoload/globtwo.vim')],
- \ 'glob*.vim'->globpath('sautest/autoload', 0, 1))
-endfunction
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
deleted file mode 100644
index 46482c34a1..0000000000
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ /dev/null
@@ -1,392 +0,0 @@
-" Tests for various eval things.
-
-source view_util.vim
-source shared.vim
-
-function s:foo() abort
- try
- return [] == 0
- catch
- return 1
- endtry
-endfunction
-
-func Test_catch_return_with_error()
- call assert_equal(1, s:foo())
-endfunc
-
-func Test_nocatch_restore_silent_emsg()
- silent! try
- throw 1
- catch
- endtry
- echoerr 'wrong again'
- call assert_equal('wrong again', ScreenLine(&lines))
-endfunc
-
-func Test_mkdir_p()
- call mkdir('Xmkdir/nested', 'p')
- call assert_true(isdirectory('Xmkdir/nested'))
- try
- " Trying to make existing directories doesn't error
- call mkdir('Xmkdir', 'p')
- call mkdir('Xmkdir/nested', 'p')
- catch /E739:/
- call assert_report('mkdir(..., "p") failed for an existing directory')
- endtry
- " 'p' doesn't suppress real errors
- call writefile([], 'Xfile')
- call assert_fails('call mkdir("Xfile", "p")', 'E739')
- call delete('Xfile')
- call delete('Xmkdir', 'rf')
-endfunc
-
-func Test_line_continuation()
- let array = [5,
- "\ ignore this
- \ 6,
- "\ more to ignore
- "\ more moreto ignore
- \ ]
- "\ and some more
- call assert_equal([5, 6], array)
-endfunc
-
-func Test_E963()
- " These commands used to cause an internal error prior to vim 8.1.0563
- let v_e = v:errors
- let v_o = v:oldfiles
- call assert_fails("let v:errors=''", 'E963:')
- call assert_equal(v_e, v:errors)
- call assert_fails("let v:oldfiles=''", 'E963:')
- call assert_equal(v_o, v:oldfiles)
-endfunc
-
-func Test_for_invalid()
- call assert_fails("for x in 99", 'E1098:')
- call assert_fails("for x in function('winnr')", 'E1098:')
- call assert_fails("for x in {'a': 9}", 'E1098:')
-
- if 0
- /1/5/2/s/\n
- endif
- redraw
-endfunc
-
-func Test_for_over_null_string()
- let save_enc = &enc
- " set enc=iso8859
- let cnt = 0
- for c in v:_null_string
- let cnt += 1
- endfor
- call assert_equal(0, cnt)
-
- let &enc = save_enc
-endfunc
-
-func Test_for_invalid_line_count()
- let lines =<< trim END
- 111111111111111111111111 for line in ['one']
- endfor
- END
- call writefile(lines, 'XinvalidFor')
- " only test that this doesn't crash
- call RunVim([], [], '-u NONE -e -s -S XinvalidFor -c qa')
-
- call delete('XinvalidFor')
-endfunc
-
-func Test_readfile_binary()
- new
- call setline(1, ['one', 'two', 'three'])
- setlocal ff=dos
- silent write XReadfile_bin
- let lines = 'XReadfile_bin'->readfile()
- call assert_equal(['one', 'two', 'three'], lines)
- let lines = readfile('XReadfile_bin', '', 2)
- call assert_equal(['one', 'two'], lines)
- let lines = readfile('XReadfile_bin', 'b')
- call assert_equal(["one\r", "two\r", "three\r", ""], lines)
- let lines = readfile('XReadfile_bin', 'b', 2)
- call assert_equal(["one\r", "two\r"], lines)
-
- bwipe!
- call delete('XReadfile_bin')
-endfunc
-
-func Test_readfile_binary_empty()
- call writefile([], 'Xempty-file')
- " This used to compare uninitialized memory in Vim <= 8.2.4065
- call assert_equal([''], readfile('Xempty-file', 'b'))
- call delete('Xempty-file')
-endfunc
-
-func Test_readfile_bom()
- call writefile(["\ufeffFOO", "FOO\ufeffBAR"], 'XReadfile_bom')
- call assert_equal(['FOO', 'FOOBAR'], readfile('XReadfile_bom'))
- call delete('XReadfile_bom')
-endfunc
-
-func Test_readfile_max()
- call writefile(range(1, 4), 'XReadfile_max')
- call assert_equal(['1', '2'], readfile('XReadfile_max', '', 2))
- call assert_equal(['3', '4'], readfile('XReadfile_max', '', -2))
- call delete('XReadfile_max')
-endfunc
-
-func Test_let_errmsg()
- call assert_fails('let v:errmsg = []', 'E730:')
- let v:errmsg = ''
- call assert_fails('let v:errmsg = []', 'E730:')
- let v:errmsg = ''
-endfunc
-
-func Test_string_concatenation()
- call assert_equal('ab', 'a'.'b')
- call assert_equal('ab', 'a' .'b')
- call assert_equal('ab', 'a'. 'b')
- call assert_equal('ab', 'a' . 'b')
-
- call assert_equal('ab', 'a'..'b')
- call assert_equal('ab', 'a' ..'b')
- call assert_equal('ab', 'a'.. 'b')
- call assert_equal('ab', 'a' .. 'b')
-
- let a = 'a'
- let b = 'b'
- let a .= b
- call assert_equal('ab', a)
-
- let a = 'a'
- let a.=b
- call assert_equal('ab', a)
-
- let a = 'a'
- let a ..= b
- call assert_equal('ab', a)
-
- let a = 'a'
- let a..=b
- call assert_equal('ab', a)
-endfunc
-
-" Test fix for issue #4507
-func Test_skip_after_throw()
- try
- throw 'something'
- let x = wincol() || &ts
- catch /something/
- endtry
-endfunc
-
-" scriptversion 1
-func Test_string_concat_scriptversion1()
- call assert_true(has('vimscript-1'))
- let a = 'a'
- let b = 'b'
-
- echo a . b
- let a .= b
- let vers = 1.2.3
- call assert_equal('123', vers)
-
- if has('float')
- call assert_fails('let f = .5', 'E15:')
- endif
-endfunc
-
-" scriptversion 1
-func Test_vvar_scriptversion1()
- call assert_equal(15, 017)
- call assert_equal(15, 0o17)
- call assert_equal(15, 0O17)
- call assert_equal(18, 018)
- call assert_equal(511, 0o777)
-endfunc
-
-func Test_number_max_min_size()
- " This will fail on systems without 64 bit number support or when not
- " configured correctly.
- call assert_equal(64, v:numbersize)
-
- call assert_true(v:numbermin < -9999999)
- call assert_true(v:numbermax > 9999999)
-endfunc
-
-func Assert_reg(name, type, value, valuestr, expr, exprstr)
- call assert_equal(a:type, getregtype(a:name))
- call assert_equal(a:value, getreg(a:name))
- call assert_equal(a:valuestr, string(getreg(a:name, 0, 1)))
- call assert_equal(a:expr, getreg(a:name, 1))
- call assert_equal(a:exprstr, string(getreg(a:name, 1, 1)))
-endfunc
-
-func Test_let_register()
- let @" = 'abc'
- call Assert_reg('"', 'v', "abc", "['abc']", "abc", "['abc']")
- let @" = "abc\n"
- call Assert_reg('"', 'V', "abc\n", "['abc']", "abc\n", "['abc']")
- let @" = "abc\<C-m>"
- call Assert_reg('"', 'V', "abc\r\n", "['abc\r']", "abc\r\n", "['abc\r']")
- let @= = '"abc"'
- call Assert_reg('=', 'v', "abc", "['abc']", '"abc"', "['\"abc\"']")
-endfunc
-
-func Assert_regput(name, result)
- new
- execute "silent normal! o==\n==\e\"" . a:name . "P"
- call assert_equal(a:result, getline(2, line('$')))
- bwipe!
-endfunc
-
-func Test_setreg_basic()
- call setreg('a', 'abcA', 'c')
- call Assert_reg('a', 'v', "abcA", "['abcA']", "abcA", "['abcA']")
- call Assert_regput('a', ['==', '=abcA='])
-
- call setreg('A', 'abcAc', 'c')
- call Assert_reg('A', 'v', "abcAabcAc", "['abcAabcAc']", "abcAabcAc", "['abcAabcAc']")
- call Assert_regput('a', ['==', '=abcAabcAc='])
-
- call setreg('A', 'abcAl', 'l')
- call Assert_reg('A', 'V', "abcAabcAcabcAl\n", "['abcAabcAcabcAl']", "abcAabcAcabcAl\n", "['abcAabcAcabcAl']")
- call Assert_regput('a', ['==', 'abcAabcAcabcAl', '=='])
-
- call setreg('A', 'abcAc2','c')
- call Assert_reg('A', 'v', "abcAabcAcabcAl\nabcAc2", "['abcAabcAcabcAl', 'abcAc2']", "abcAabcAcabcAl\nabcAc2", "['abcAabcAcabcAl', 'abcAc2']")
- call Assert_regput('a', ['==', '=abcAabcAcabcAl', 'abcAc2='])
-
- call setreg('b', 'abcB', 'v')
- call Assert_reg('b', 'v', "abcB", "['abcB']", "abcB", "['abcB']")
- call Assert_regput('b', ['==', '=abcB='])
-
- call setreg('b', 'abcBc', 'ca')
- call Assert_reg('b', 'v', "abcBabcBc", "['abcBabcBc']", "abcBabcBc", "['abcBabcBc']")
- call Assert_regput('b', ['==', '=abcBabcBc='])
-
- call setreg('b', 'abcBb', 'ba')
- call Assert_reg('b', "\<C-V>5", "abcBabcBcabcBb", "['abcBabcBcabcBb']", "abcBabcBcabcBb", "['abcBabcBcabcBb']")
- call Assert_regput('b', ['==', '=abcBabcBcabcBb='])
-
- call setreg('b', 'abcBc2','ca')
- call Assert_reg('b', "v", "abcBabcBcabcBb\nabcBc2", "['abcBabcBcabcBb', 'abcBc2']", "abcBabcBcabcBb\nabcBc2", "['abcBabcBcabcBb', 'abcBc2']")
- call Assert_regput('b', ['==', '=abcBabcBcabcBb', 'abcBc2='])
-
- call setreg('b', 'abcBb2','b50a')
- call Assert_reg('b', "\<C-V>50", "abcBabcBcabcBb\nabcBc2abcBb2", "['abcBabcBcabcBb', 'abcBc2abcBb2']", "abcBabcBcabcBb\nabcBc2abcBb2", "['abcBabcBcabcBb', 'abcBc2abcBb2']")
- call Assert_regput('b', ['==', '=abcBabcBcabcBb =', ' abcBc2abcBb2'])
-
- call setreg('c', 'abcC', 'l')
- call Assert_reg('c', 'V', "abcC\n", "['abcC']", "abcC\n", "['abcC']")
- call Assert_regput('c', ['==', 'abcC', '=='])
-
- call setreg('C', 'abcCl', 'l')
- call Assert_reg('C', 'V', "abcC\nabcCl\n", "['abcC', 'abcCl']", "abcC\nabcCl\n", "['abcC', 'abcCl']")
- call Assert_regput('c', ['==', 'abcC', 'abcCl', '=='])
-
- call setreg('C', 'abcCc', 'c')
- call Assert_reg('C', 'v', "abcC\nabcCl\nabcCc", "['abcC', 'abcCl', 'abcCc']", "abcC\nabcCl\nabcCc", "['abcC', 'abcCl', 'abcCc']")
- call Assert_regput('c', ['==', '=abcC', 'abcCl', 'abcCc='])
-
- call setreg('d', 'abcD', 'V')
- call Assert_reg('d', 'V', "abcD\n", "['abcD']", "abcD\n", "['abcD']")
- call Assert_regput('d', ['==', 'abcD', '=='])
-
- call setreg('D', 'abcDb', 'b')
- call Assert_reg('d', "\<C-V>5", "abcD\nabcDb", "['abcD', 'abcDb']", "abcD\nabcDb", "['abcD', 'abcDb']")
- call Assert_regput('d', ['==', '=abcD =', ' abcDb'])
-
- call setreg('e', 'abcE', 'b')
- call Assert_reg('e', "\<C-V>4", "abcE", "['abcE']", "abcE", "['abcE']")
- call Assert_regput('e', ['==', '=abcE='])
-
- call setreg('E', 'abcEb', 'b')
- call Assert_reg('E', "\<C-V>5", "abcE\nabcEb", "['abcE', 'abcEb']", "abcE\nabcEb", "['abcE', 'abcEb']")
- call Assert_regput('e', ['==', '=abcE =', ' abcEb'])
-
- call setreg('E', 'abcEl', 'l')
- call Assert_reg('E', "V", "abcE\nabcEb\nabcEl\n", "['abcE', 'abcEb', 'abcEl']", "abcE\nabcEb\nabcEl\n", "['abcE', 'abcEb', 'abcEl']")
- call Assert_regput('e', ['==', 'abcE', 'abcEb', 'abcEl', '=='])
-
- call setreg('f', 'abcF', "\<C-v>")
- call Assert_reg('f', "\<C-V>4", "abcF", "['abcF']", "abcF", "['abcF']")
- call Assert_regput('f', ['==', '=abcF='])
-
- call setreg('F', 'abcFc', 'c')
- call Assert_reg('F', "v", "abcF\nabcFc", "['abcF', 'abcFc']", "abcF\nabcFc", "['abcF', 'abcFc']")
- call Assert_regput('f', ['==', '=abcF', 'abcFc='])
-
- call setreg('g', 'abcG', 'b10')
- call Assert_reg('g', "\<C-V>10", "abcG", "['abcG']", "abcG", "['abcG']")
- call Assert_regput('g', ['==', '=abcG ='])
-
- call setreg('h', 'abcH', "\<C-v>10")
- call Assert_reg('h', "\<C-V>10", "abcH", "['abcH']", "abcH", "['abcH']")
- call Assert_regput('h', ['==', '=abcH ='])
-
- call setreg('I', 'abcI')
- call Assert_reg('I', "v", "abcI", "['abcI']", "abcI", "['abcI']")
- call Assert_regput('I', ['==', '=abcI='])
-
- " Error cases
- call assert_fails('call setreg()', 'E119:')
- call assert_fails('call setreg(1)', 'E119:')
- call assert_fails('call setreg(1, 2, 3, 4)', 'E118:')
- call assert_fails('call setreg([], 2)', 'E730:')
- call assert_fails('call setreg(1, 2, [])', 'E730:')
- call assert_fails('call setreg("/", ["1", "2"])', 'E883:')
- call assert_fails('call setreg("=", ["1", "2"])', 'E883:')
- call assert_fails('call setreg(1, ["", "", [], ""])', 'E730:')
-endfunc
-
-func Test_curly_assignment()
- let s:svar = 'svar'
- let g:gvar = 'gvar'
- let lname = 'gvar'
- let gname = 'gvar'
- let {'s:'.lname} = {'g:'.gname}
- call assert_equal('gvar', s:gvar)
- let s:gvar = ''
- let { 's:'.lname } = { 'g:'.gname }
- call assert_equal('gvar', s:gvar)
- let s:gvar = ''
- let { 's:' . lname } = { 'g:' . gname }
- call assert_equal('gvar', s:gvar)
- let s:gvar = ''
- let { 's:' .. lname } = { 'g:' .. gname }
- call assert_equal('gvar', s:gvar)
-
- unlet s:svar
- unlet s:gvar
- unlet g:gvar
-endfunc
-
-func Test_deep_recursion()
- " this was running out of stack
- call assert_fails("exe 'if ' .. repeat('(', 1002)", 'E1169: Expression too recursive: ((')
-endfunc
-
-" K_SPECIAL in the modified character used be escaped, which causes
-" double-escaping with feedkeys() or as the return value of an <expr> mapping,
-" and doesn't match what getchar() returns,
-func Test_modified_char_no_escape_special()
- nnoremap <M-…> <Cmd>let g:got_m_ellipsis += 1<CR>
- call feedkeys("\<M-…>", 't')
- call assert_equal("\<M-…>", getchar())
- let g:got_m_ellipsis = 0
- call feedkeys("\<M-…>", 'xt')
- call assert_equal(1, g:got_m_ellipsis)
- func Func()
- return "\<M-…>"
- endfunc
- nmap <expr> <F2> Func()
- call feedkeys("\<F2>", 'xt')
- call assert_equal(2, g:got_m_ellipsis)
- delfunc Func
- nunmap <F2>
- unlet g:got_m_ellipsis
- nunmap <M-…>
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ex_equal.vim b/src/nvim/testdir/test_ex_equal.vim
deleted file mode 100644
index 05ad276836..0000000000
--- a/src/nvim/testdir/test_ex_equal.vim
+++ /dev/null
@@ -1,32 +0,0 @@
-" Test Ex := command.
-
-func Test_ex_equal()
- new
- call setline(1, ["foo\tbar", "bar\tfoo"])
-
- let a = execute('=')
- call assert_equal("\n2", a)
-
- let a = execute('=#')
- call assert_equal("\n2\n 1 foo bar", a)
-
- let a = execute('=l')
- call assert_equal("\n2\nfoo^Ibar$", a)
-
- let a = execute('=p')
- call assert_equal("\n2\nfoo bar", a)
-
- let a = execute('=l#')
- call assert_equal("\n2\n 1 foo^Ibar$", a)
-
- let a = execute('=p#')
- call assert_equal("\n2\n 1 foo bar", a)
-
- let a = execute('.=')
- call assert_equal("\n1", a)
-
- call assert_fails('3=', 'E16:')
- call assert_fails('=x', 'E488:')
-
- bwipe!
-endfunc
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
deleted file mode 100644
index 93100732ed..0000000000
--- a/src/nvim/testdir/test_ex_mode.vim
+++ /dev/null
@@ -1,243 +0,0 @@
-" Test editing line in Ex mode (see :help Q and :help gQ).
-
-source check.vim
-source shared.vim
-
-" Helper function to test editing line in Q Ex mode
-func Ex_Q(cmd)
- " Is there a simpler way to test editing Ex line?
- call feedkeys("Q"
- \ .. "let s:test_ex =<< END\<CR>"
- \ .. a:cmd .. "\<CR>"
- \ .. "END\<CR>"
- \ .. "visual\<CR>", 'tx')
- return s:test_ex[0]
-endfunc
-
-" Helper function to test editing line in gQ Ex mode
-func Ex_gQ(cmd)
- call feedkeys("gQ" .. a:cmd .. "\<C-b>\"\<CR>", 'tx')
- let ret = @:[1:] " Remove leading quote.
- call feedkeys("visual\<CR>", 'tx')
- return ret
-endfunc
-
-" Helper function to test editing line with both Q and gQ Ex mode.
-func Ex(cmd)
- return [Ex_Q(a:cmd), Ex_gQ(a:cmd)]
-endfunc
-
-" Test editing line in Ex mode (both Q and gQ)
-func Test_ex_mode()
- throw 'Skipped: Nvim only supports Vim Ex mode'
- let encoding_save = &encoding
- set sw=2
-
- for e in ['utf8', 'latin1']
- exe 'set encoding=' . e
-
- call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
- call assert_equal(["1\<C-u>2", "1\<C-u>2"], Ex("1\<C-v>\<C-u>2"), e)
- call assert_equal(["1\<C-b>2\<C-e>3", '213'], Ex("1\<C-b>2\<C-e>3"), e)
- call assert_equal(['0123', '2013'], Ex("01\<Home>2\<End>3"), e)
- call assert_equal(['0123', '0213'], Ex("01\<Left>2\<Right>3"), e)
- call assert_equal(['01234', '0342'], Ex("012\<Left>\<Left>\<Insert>3\<Insert>4"), e)
- call assert_equal(["foo bar\<C-w>", 'foo '], Ex("foo bar\<C-w>"), e)
- call assert_equal(['foo', 'foo'], Ex("fooba\<Del>\<Del>"), e)
- call assert_equal(["foo\tbar", 'foobar'], Ex("foo\<Tab>bar"), e)
- call assert_equal(["abbrev\t", 'abbreviate'], Ex("abbrev\<Tab>"), e)
- call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>"), e)
- call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>\<C-d>"), e)
- call assert_equal([' foo', ' foo'], Ex(" foo\<C-d>"), e)
- call assert_equal(['foo', ' foo0'], Ex(" foo0\<C-d>"), e)
- call assert_equal(['foo', ' foo^'], Ex(" foo^\<C-d>"), e)
- call assert_equal(['foo', 'foo'],
- \ Ex("\<BS>\<C-H>\<Del>\<kDel>foo"), e)
- " default wildchar <Tab> interferes with this test
- set wildchar=<c-e>
- call assert_equal(["a\tb", "a\tb"], Ex("a\t\t\<C-H>b"), e)
- call assert_equal(["\t mn", "\tm\<C-T>n"], Ex("\tm\<C-T>n"), e)
- set wildchar&
- endfor
-
- set sw&
- let &encoding = encoding_save
-endfunc
-
-" Test substitute confirmation prompt :%s/pat/str/c in Ex mode
-func Test_Ex_substitute()
- CheckRunVimInTerminal
- let buf = RunVimInTerminal('', {'rows': 6})
-
- call term_sendkeys(buf, ":call setline(1, ['foo foo', 'foo foo', 'foo foo'])\<CR>")
- call term_sendkeys(buf, ":set number\<CR>")
- call term_sendkeys(buf, "gQ")
- call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000)
-
- call term_sendkeys(buf, "%s/foo/bar/gc\<CR>")
- call WaitForAssert({-> assert_match(' 1 foo foo', term_getline(buf, 5))},
- \ 1000)
- call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000)
- call term_sendkeys(buf, "N\<CR>")
- call term_wait(buf)
- call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000)
- call term_sendkeys(buf, "n\<CR>")
- call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))},
- \ 1000)
- call term_sendkeys(buf, "y\<CR>")
-
- call term_sendkeys(buf, "q\<CR>")
- call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000)
-
- " Pressing enter in ex mode should print the current line
- call term_sendkeys(buf, "\<CR>")
- call WaitForAssert({-> assert_match(' 3 foo foo',
- \ term_getline(buf, 5))}, 1000)
-
- call term_sendkeys(buf, ":vi\<CR>")
- call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000)
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Test for displaying lines from an empty buffer in Ex mode
-func Test_Ex_emptybuf()
- new
- call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E749:')
- call setline(1, "abc")
- call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E501:')
- call assert_fails('call feedkeys("Q%d\<CR>", "xt")', 'E749:')
- close!
-endfunc
-
-" Test for the :open command
-func Test_open_command()
- throw 'Skipped: Nvim does not have :open'
- new
- call setline(1, ['foo foo', 'foo bar', 'foo baz'])
- call feedkeys("Qopen\<CR>j", 'xt')
- call assert_equal('foo bar', getline('.'))
- call feedkeys("Qopen /bar/\<CR>", 'xt')
- call assert_equal(5, col('.'))
- call assert_fails('call feedkeys("Qopen /baz/\<CR>", "xt")', 'E479:')
- close!
-endfunc
-
-" Test for :g/pat/visual to run vi commands in Ex mode
-" This used to hang Vim before 8.2.0274.
-func Test_Ex_global()
- new
- call setline(1, ['', 'foo', 'bar', 'foo', 'bar', 'foo'])
- call feedkeys("Q\<bs>g/bar/visual\<CR>$rxQ$ryQvisual\<CR>j", "xt")
- call assert_equal('bax', getline(3))
- call assert_equal('bay', getline(5))
- bwipe!
-endfunc
-
-" Test for pressing Ctrl-C in :append inside a loop in Ex mode
-" This used to hang Vim
-func Test_Ex_append_in_loop()
- CheckRunVimInTerminal
- let buf = RunVimInTerminal('', {'rows': 6})
-
- call term_sendkeys(buf, "gQ")
- call term_sendkeys(buf, "for i in range(1)\<CR>")
- call term_sendkeys(buf, "append\<CR>")
- call WaitForAssert({-> assert_match(': append', term_getline(buf, 5))}, 1000)
- call term_sendkeys(buf, "\<C-C>")
- " Wait for input to be flushed
- call term_wait(buf)
- call term_sendkeys(buf, "foo\<CR>")
- call WaitForAssert({-> assert_match('foo', term_getline(buf, 5))}, 1000)
- call term_sendkeys(buf, ".\<CR>")
- call WaitForAssert({-> assert_match('.', term_getline(buf, 5))}, 1000)
- call term_sendkeys(buf, "endfor\<CR>")
- call term_sendkeys(buf, "vi\<CR>")
- call WaitForAssert({-> assert_match('foo', term_getline(buf, 1))}, 1000)
-
- call StopVimInTerminal(buf)
-endfunc
-
-" In Ex-mode, a backslash escapes a newline
-func Test_Ex_escape_enter()
- call feedkeys("gQlet l = \"a\\\<kEnter>b\"\<cr>vi\<cr>", 'xt')
- call assert_equal("a\rb", l)
-endfunc
-
-" Test for :append! command in Ex mode
-func Test_Ex_append()
- throw 'Skipped: Nvim only supports Vim Ex mode'
- new
- call setline(1, "\t abc")
- call feedkeys("Qappend!\npqr\nxyz\n.\nvisual\n", 'xt')
- call assert_equal(["\t abc", "\t pqr", "\t xyz"], getline(1, '$'))
- close!
-endfunc
-
-" In Ex-mode, backslashes at the end of a command should be halved.
-func Test_Ex_echo_backslash()
- throw 'Skipped: Nvim only supports Vim Ex mode'
- " This test works only when the language is English
- CheckEnglish
- let bsl = '\\\\'
- let bsl2 = '\\\'
- call assert_fails('call feedkeys("Qecho " .. bsl .. "\nvisual\n", "xt")',
- \ "E15: Invalid expression: \\\\")
- call assert_fails('call feedkeys("Qecho " .. bsl2 .. "\nm\nvisual\n", "xt")',
- \ "E15: Invalid expression: \\\nm")
-endfunc
-
-func Test_ex_mode_errors()
- " Not allowed to enter ex mode when text is locked
- au InsertCharPre <buffer> normal! gQ<CR>
- let caught_e565 = 0
- try
- call feedkeys("ix\<esc>", 'xt')
- catch /^Vim\%((\a\+)\)\=:E565/ " catch E565
- let caught_e565 = 1
- endtry
- call assert_equal(1, caught_e565)
- au! InsertCharPre
-
- new
- au CmdLineEnter * call ExEnterFunc()
- func ExEnterFunc()
-
- endfunc
- call feedkeys("gQvi\r", 'xt')
-
- au! CmdLineEnter
- delfunc ExEnterFunc
- quit
-endfunc
-
-func Test_ex_mode_count_overflow()
- " The multiplication causes an integer overflow
- CheckNotAsan
-
- " this used to cause a crash
- let lines =<< trim END
- call feedkeys("\<Esc>gQ\<CR>")
- v9|9silent! vi|333333233333y32333333%O
- call writefile(['done'], 'Xdidexmode')
- qall!
- END
- call writefile(lines, 'Xexmodescript')
- call assert_equal(1, RunVim([], [], '-e -s -S Xexmodescript -c qa'))
- call assert_equal(['done'], readfile('Xdidexmode'))
-
- call delete('Xdidexmode')
- call delete('Xexmodescript')
-endfunc
-
-func Test_ex_mode_large_indent()
- new
- set ts=500 ai
- call setline(1, "\t")
- exe "normal gQi\<CR>."
- set ts=8 noai
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ex_undo.vim b/src/nvim/testdir/test_ex_undo.vim
deleted file mode 100644
index 44feb3680a..0000000000
--- a/src/nvim/testdir/test_ex_undo.vim
+++ /dev/null
@@ -1,19 +0,0 @@
-" Tests for :undo
-
-func Test_ex_undo()
- new ex-undo
- setlocal ul=10
- exe "normal ione\n\<Esc>"
- setlocal ul=10
- exe "normal itwo\n\<Esc>"
- setlocal ul=10
- exe "normal ithree\n\<Esc>"
- call assert_equal(4, line('$'))
- undo
- call assert_equal(3, line('$'))
- undo 1
- call assert_equal(2, line('$'))
- undo 0
- call assert_equal(1, line('$'))
- quit!
-endfunc
diff --git a/src/nvim/testdir/test_ex_z.vim b/src/nvim/testdir/test_ex_z.vim
deleted file mode 100644
index 481747ce84..0000000000
--- a/src/nvim/testdir/test_ex_z.vim
+++ /dev/null
@@ -1,108 +0,0 @@
-" Test :z
-
-func Test_z()
- call setline(1, range(1, 100))
-
- let a = execute('20z3')
- call assert_equal("\n20\n21\n22", a)
- call assert_equal(22, line('.'))
- " 'window' should be set to the {count} value.
- call assert_equal(3, &window)
-
- " If there is only one window, then twice the amount of 'scroll' is used.
- set scroll=2
- let a = execute('20z')
- call assert_equal("\n20\n21\n22\n23", a)
- call assert_equal(23, line('.'))
-
- let a = execute('20z+3')
- " FIXME: I would expect the same result as '20z3' since 'help z'
- " says: Specifying no mark at all is the same as "+".
- " However it " gives "\n21\n22\n23" instead. Bug in Vim or in ":help :z"?
- "call assert_equal("\n20\n21\n22", a)
- "call assert_equal(22, line('.'))
-
- let a = execute('20z-3')
- call assert_equal("\n18\n19\n20", a)
- call assert_equal(20, line('.'))
-
- let a = execute('20z=3')
- call assert_match("^\n18\n19\n-\\+\n20\n-\\+\n21\n22$", a)
- call assert_equal(20, line('.'))
-
- let a = execute('20z^3')
- call assert_equal("\n14\n15\n16\n17", a)
- call assert_equal(17, line('.'))
-
- let a = execute('20z.3')
- call assert_equal("\n19\n20\n21", a)
- call assert_equal(21, line('.'))
-
- let a = execute('20z#3')
- call assert_equal("\n 20 20\n 21 21\n 22 22", a)
- call assert_equal(22, line('.'))
-
- let a = execute('20z#-3')
- call assert_equal("\n 18 18\n 19 19\n 20 20", a)
- call assert_equal(20, line('.'))
-
- let a = execute('20z#=3')
- call assert_match("^\n 18 18\n 19 19\n-\\+\n 20 20\n-\\+\n 21 21\n 22 22$", a)
- call assert_equal(20, line('.'))
-
- " Test with {count} bigger than the number of lines in buffer.
- let a = execute('20z1000')
- call assert_match("^\n20\n21\n.*\n99\n100$", a)
- call assert_equal(100, line('.'))
-
- let a = execute('20z-1000')
- call assert_equal(20, line('.'))
-
- let a = execute('20z=1000')
- call assert_match("^\n1\n.*\n-\\+\n20\n-\\\+\n.*\n100$", a)
- call assert_equal(20, line('.'))
-
- " Tests with multiple windows.
- 5split
- call setline(1, range(1, 100))
- " Without a count, the number line is window height - 3.
- let a = execute('20z')
- call assert_equal("\n20\n21", a)
- call assert_equal(21, line('.'))
- " If window height - 3 is less than 1, it should be clamped to 1.
- resize 2
- let a = execute('20z')
- call assert_equal("\n20", a)
- call assert_equal(20, line('.'))
-
- call assert_fails('20z=a', 'E144:')
-
- set window& scroll&
- bw!
-endfunc
-
-" :z! is the same as :z but count uses the Vim window height when not specified.
-func Test_z_bang()
- 4split
- call setline(1, range(1, 20))
-
- let a = execute('10z!')
- call assert_equal("\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20", a)
-
- let a = execute('10z!#')
- call assert_equal("\n 10 10\n 11 11\n 12 12\n 13 13\n 14 14\n 15 15\n 16 16\n 17 17\n 18 18\n 19 19\n 20 20", a)
-
- let a = execute('10z!3')
- call assert_equal("\n10\n11\n12", a)
-
- %bwipe!
-endfunc
-
-func Test_z_bug()
- " This used to access invalid memory as a result of an integer overflow
- " and freeze vim.
- normal ox
- normal Heat
- z777777776666666
- ')
-endfunc
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
deleted file mode 100644
index 44bed890f5..0000000000
--- a/src/nvim/testdir/test_excmd.vim
+++ /dev/null
@@ -1,750 +0,0 @@
-" Tests for various Ex commands.
-
-source check.vim
-source shared.vim
-source term_util.vim
-
-func Test_ex_delete()
- new
- call setline(1, ['a', 'b', 'c'])
- 2
- " :dl is :delete with the "l" flag, not :dlist
- .dl
- call assert_equal(['a', 'c'], getline(1, 2))
-endfunc
-
-func Test_range_error()
- call assert_fails(':.echo 1', 'E481:')
- call assert_fails(':$echo 1', 'E481:')
- call assert_fails(':1,2echo 1', 'E481:')
- call assert_fails(':+1echo 1', 'E481:')
- call assert_fails(':/1/echo 1', 'E481:')
- call assert_fails(':\/echo 1', 'E481:')
- normal vv
- call assert_fails(":'<,'>echo 1", 'E481:')
- call assert_fails(":\\xcenter", 'E10:')
-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
-
-" Test for the :copy command
-func Test_copy()
- new
-
- call setline(1, ['L1', 'L2', 'L3', 'L4'])
- " copy lines in a range to inside the range
- 1,3copy 2
- call assert_equal(['L1', 'L2', 'L1', 'L2', 'L3', 'L3', 'L4'], getline(1, 7))
-
- " Specifying a count before using : to run an ex-command
- exe "normal! gg4:yank\<CR>"
- call assert_equal("L1\nL2\nL1\nL2\n", @")
-
- close!
-endfunc
-
-" Test for the :file command
-func Test_file_cmd()
- call assert_fails('3file', 'E474:')
- call assert_fails('0,0file', 'E474:')
- call assert_fails('0file abc', 'E474:')
- if !has('win32')
- " Change the name of the buffer to the same name
- new Xfile1
- file Xfile1
- call assert_equal('Xfile1', @%)
- call assert_equal('Xfile1', @#)
- bw!
- endif
-endfunc
-
-" Test for the :drop command
-func Test_drop_cmd()
- call writefile(['L1', 'L2'], 'Xfile')
- enew | only
- drop Xfile
- call assert_equal('L2', getline(2))
- " Test for switching to an existing window
- below new
- drop Xfile
- call assert_equal(1, winnr())
- " Test for splitting the current window
- enew | only
- set modified
- drop Xfile
- call assert_equal(2, winnr('$'))
- " Check for setting the argument list
- call assert_equal(['Xfile'], argv())
- enew | only!
- call delete('Xfile')
-endfunc
-
-" Test for the :append command
-func Test_append_cmd()
- new
- call setline(1, [' L1'])
- call feedkeys(":append\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
- call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
- %delete _
- " append after a specific line
- call setline(1, [' L1', ' L2', ' L3'])
- call feedkeys(":2append\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
- call assert_equal([' L1', ' L2', ' L4', ' L5', ' L3'], getline(1, '$'))
- %delete _
- " append with toggling 'autoindent'
- call setline(1, [' L1'])
- call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
- call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
- call assert_false(&autoindent)
- %delete _
- " append with 'autoindent' set and toggling 'autoindent'
- set autoindent
- call setline(1, [' L1'])
- call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
- call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
- call assert_true(&autoindent)
- set autoindent&
- close!
-endfunc
-
-func Test_append_cmd_empty_buf()
- CheckRunVimInTerminal
- let lines =<< trim END
- func Timer(timer)
- append
- aaaaa
- bbbbb
- .
- endfunc
- call timer_start(10, 'Timer')
- END
- call writefile(lines, 'Xtest_append_cmd_empty_buf')
- let buf = RunVimInTerminal('-S Xtest_append_cmd_empty_buf', {'rows': 6})
- call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))})
- call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_append_cmd_empty_buf')
-endfunc
-
-" Test for the :insert command
-func Test_insert_cmd()
- new
- call setline(1, [' L1'])
- call feedkeys(":insert\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
- call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
- %delete _
- " insert before a specific line
- call setline(1, [' L1', ' L2', ' L3'])
- call feedkeys(":2insert\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
- call assert_equal([' L1', ' L4', ' L5', ' L2', ' L3'], getline(1, '$'))
- %delete _
- " insert with toggling 'autoindent'
- call setline(1, [' L1'])
- call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
- call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
- call assert_false(&autoindent)
- %delete _
- " insert with 'autoindent' set and toggling 'autoindent'
- set autoindent
- call setline(1, [' L1'])
- call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
- call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
- call assert_true(&autoindent)
- set autoindent&
- close!
-endfunc
-
-func Test_insert_cmd_empty_buf()
- CheckRunVimInTerminal
- let lines =<< trim END
- func Timer(timer)
- insert
- aaaaa
- bbbbb
- .
- endfunc
- call timer_start(10, 'Timer')
- END
- call writefile(lines, 'Xtest_insert_cmd_empty_buf')
- let buf = RunVimInTerminal('-S Xtest_insert_cmd_empty_buf', {'rows': 6})
- call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))})
- call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_insert_cmd_empty_buf')
-endfunc
-
-" Test for the :change command
-func Test_change_cmd()
- new
- call setline(1, [' L1', 'L2', 'L3'])
- call feedkeys(":change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
- call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
- %delete _
- " change a specific line
- call setline(1, [' L1', ' L2', ' L3'])
- call feedkeys(":2change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
- call assert_equal([' L1', ' L4', ' L5', ' L3'], getline(1, '$'))
- %delete _
- " change with toggling 'autoindent'
- call setline(1, [' L1', 'L2', 'L3'])
- call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
- call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
- call assert_false(&autoindent)
- %delete _
- " change with 'autoindent' set and toggling 'autoindent'
- set autoindent
- call setline(1, [' L1', 'L2', 'L3'])
- call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
- call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
- call assert_true(&autoindent)
- set autoindent&
- close!
-endfunc
-
-" Test for the :language command
-func Test_language_cmd()
- CheckNotMSWindows " FIXME: why does this fail on Windows CI?
- CheckFeature multi_lang
-
- call assert_fails('language ctype non_existing_lang', 'E197:')
- call assert_fails('language time non_existing_lang', 'E197:')
-endfunc
-
-" Test for the :confirm command dialog
-func Test_confirm_cmd()
- CheckNotGui
- CheckRunVimInTerminal
-
- call writefile(['foo1'], 'Xfoo')
- call writefile(['bar1'], 'Xbar')
-
- " Test for saving all the modified buffers
- let lines =<< trim END
- set nomore
- new Xfoo
- call setline(1, 'foo2')
- new Xbar
- call setline(1, 'bar2')
- wincmd b
- END
- call writefile(lines, 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
- call term_sendkeys(buf, ":confirm qall\n")
- call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, "A")
- call StopVimInTerminal(buf)
-
- call assert_equal(['foo2'], readfile('Xfoo'))
- call assert_equal(['bar2'], readfile('Xbar'))
-
- " Test for discarding all the changes to modified buffers
- let lines =<< trim END
- set nomore
- new Xfoo
- call setline(1, 'foo3')
- new Xbar
- call setline(1, 'bar3')
- wincmd b
- END
- call writefile(lines, 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
- call term_sendkeys(buf, ":confirm qall\n")
- call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, "D")
- call StopVimInTerminal(buf)
-
- call assert_equal(['foo2'], readfile('Xfoo'))
- call assert_equal(['bar2'], readfile('Xbar'))
-
- " Test for saving and discarding changes to some buffers
- let lines =<< trim END
- set nomore
- new Xfoo
- call setline(1, 'foo4')
- new Xbar
- call setline(1, 'bar4')
- wincmd b
- END
- call writefile(lines, 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
- call term_sendkeys(buf, ":confirm qall\n")
- call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, "N")
- call WaitForAssert({-> assert_match('\[Y\]es, (N)o, (C)ancel: ', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, "Y")
- call StopVimInTerminal(buf)
-
- call assert_equal(['foo4'], readfile('Xfoo'))
- call assert_equal(['bar2'], readfile('Xbar'))
-
- call delete('Xscript')
- call delete('Xfoo')
- call delete('Xbar')
-endfunc
-
-func Test_confirm_cmd_cancel()
- CheckNotGui
- CheckRunVimInTerminal
-
- " Test for closing a window with a modified buffer
- let lines =<< trim END
- set nomore
- new
- call setline(1, 'abc')
- END
- call writefile(lines, 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
- call term_sendkeys(buf, ":confirm close\n")
- call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
- \ term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, "C")
- call WaitForAssert({-> assert_equal('', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, ":confirm close\n")
- call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
- \ term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, "N")
- call WaitForAssert({-> assert_match('^ *0,0-1 All$',
- \ term_getline(buf, 20))}, 1000)
- call StopVimInTerminal(buf)
- call delete('Xscript')
-endfunc
-
-" The ":confirm" prompt was sometimes used with the terminal in cooked mode.
-" This test verifies that a "\<CR>" character is NOT required to respond to a
-" prompt from the ":conf q" and ":conf wq" commands.
-func Test_confirm_q_wq()
- CheckNotGui
- CheckRunVimInTerminal
-
- call writefile(['foo'], 'Xfoo')
-
- let lines =<< trim END
- set hidden nomore
- call setline(1, 'abc')
- edit Xfoo
- END
- call writefile(lines, 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
- call term_sendkeys(buf, ":confirm q\n")
- call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
- \ term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, 'C')
- call WaitForAssert({-> assert_notmatch('^\[Y\]es, (N)o, (C)ancel: C*$',
- \ term_getline(buf, 20))}, 1000)
-
- call term_sendkeys(buf, ":edit Xfoo\n")
- call term_sendkeys(buf, ":confirm wq\n")
- call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
- \ term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, 'C')
- call WaitForAssert({-> assert_notmatch('^\[Y\]es, (N)o, (C)ancel: C*$',
- \ term_getline(buf, 20))}, 1000)
- call StopVimInTerminal(buf)
-
- call delete('Xscript')
- call delete('Xfoo')
-endfunc
-
-func Test_confirm_write_ro()
- CheckNotGui
- CheckRunVimInTerminal
-
- call writefile(['foo'], 'Xconfirm_write_ro')
- let lines =<< trim END
- set nobackup ff=unix cmdheight=2
- edit Xconfirm_write_ro
- norm Abar
- END
- call writefile(lines, 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
-
- " Try to write with 'ro' option.
- call term_sendkeys(buf, ":set ro | confirm w\n")
- call WaitForAssert({-> assert_match("^'readonly' option is set for \"Xconfirm_write_ro\"\. *$",
- \ term_getline(buf, 18))}, 1000)
- call WaitForAssert({-> assert_match('^Do you wish to write anyway? *$',
- \ term_getline(buf, 19))}, 1000)
- call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, 'N')
- call WaitForAssert({-> assert_match('^ *$', term_getline(buf, 19))}, 1000)
- call WaitForAssert({-> assert_match('.* All$', term_getline(buf, 20))}, 1000)
- call assert_equal(['foo'], readfile('Xconfirm_write_ro'))
-
- call term_sendkeys(buf, ":confirm w\n")
- call WaitForAssert({-> assert_match("^'readonly' option is set for \"Xconfirm_write_ro\"\. *$",
- \ term_getline(buf, 18))}, 1000)
- call WaitForAssert({-> assert_match('^Do you wish to write anyway? *$',
- \ term_getline(buf, 19))}, 1000)
- call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, 'Y')
- call WaitForAssert({-> assert_match('^"Xconfirm_write_ro" 1L, 7B written$',
- \ term_getline(buf, 19))}, 1000)
- call assert_equal(['foobar'], readfile('Xconfirm_write_ro'))
-
- " Try to write with read-only file permissions.
- call setfperm('Xconfirm_write_ro', 'r--r--r--')
- call term_sendkeys(buf, ":set noro | undo | confirm w\n")
- call WaitForAssert({-> assert_match("^File permissions of \"Xconfirm_write_ro\" are read-only\. *$",
- \ term_getline(buf, 17))}, 1000)
- call WaitForAssert({-> assert_match('^It may still be possible to write it\. *$',
- \ term_getline(buf, 18))}, 1000)
- call WaitForAssert({-> assert_match('^Do you wish to try? *$', term_getline(buf, 19))}, 1000)
- call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$', term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, 'Y')
- call WaitForAssert({-> assert_match('^"Xconfirm_write_ro" 1L, 4B written$',
- \ term_getline(buf, 19))}, 1000)
- call assert_equal(['foo'], readfile('Xconfirm_write_ro'))
-
- call StopVimInTerminal(buf)
- call delete('Xscript')
- call delete('Xconfirm_write_ro')
-endfunc
-
-func Test_confirm_write_partial_file()
- CheckNotGui
- CheckRunVimInTerminal
-
- call writefile(['a', 'b', 'c', 'd'], 'Xwrite_partial')
- call writefile(['set nobackup ff=unix cmdheight=2',
- \ 'edit Xwrite_partial'], 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
-
- call term_sendkeys(buf, ":confirm 2,3w\n")
- call WaitForAssert({-> assert_match('^Write partial file? *$',
- \ term_getline(buf, 19))}, 1000)
- call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$',
- \ term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, 'N')
- call WaitForAssert({-> assert_match('.* All$', term_getline(buf, 20))}, 1000)
- call assert_equal(['a', 'b', 'c', 'd'], readfile('Xwrite_partial'))
- call delete('Xwrite_partial')
-
- call term_sendkeys(buf, ":confirm 2,3w\n")
- call WaitForAssert({-> assert_match('^Write partial file? *$',
- \ term_getline(buf, 19))}, 1000)
- call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$',
- \ term_getline(buf, 20))}, 1000)
- call term_sendkeys(buf, 'Y')
- call WaitForAssert({-> assert_match('^"Xwrite_partial" \[New\] 2L, 4B written *$',
- \ term_getline(buf, 19))}, 1000)
- call WaitForAssert({-> assert_match('^Press ENTER or type command to continue *$',
- \ term_getline(buf, 20))}, 1000)
- call assert_equal(['b', 'c'], readfile('Xwrite_partial'))
-
- call StopVimInTerminal(buf)
- call delete('Xwrite_partial')
- call delete('Xscript')
-endfunc
-
-" Test for the :print command
-func Test_print_cmd()
- call assert_fails('print', 'E749:')
-endfunc
-
-" Test for the :winsize command
-func Test_winsize_cmd()
- call assert_fails('winsize 1', 'E465:')
- call assert_fails('winsize 1 x', 'E465:')
- call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)')
- " Actually changing the window size would be flaky.
-endfunc
-
-" Test for the :redir command
-" NOTE: if you run tests as root this will fail. Don't run tests as root!
-func Test_redir_cmd()
- call assert_fails('redir @@', 'E475:')
- call assert_fails('redir abc', 'E475:')
- call assert_fails('redir => 1abc', 'E474:')
- call assert_fails('redir => a b', 'E488:')
- call assert_fails('redir => abc[1]', 'E121:')
- let b = 0zFF
- call assert_fails('redir =>> b', 'E734:')
- unlet b
-
- if has('unix')
- " Redirecting to a directory name
- call mkdir('Xdir')
- call assert_fails('redir > Xdir', 'E17:')
- call delete('Xdir', 'd')
- endif
-
- " Test for redirecting to a register
- redir @q> | echon 'clean ' | redir END
- redir @q>> | echon 'water' | redir END
- call assert_equal('clean water', @q)
-
- " Test for redirecting to a variable
- redir => color | echon 'blue ' | redir END
- redir =>> color | echon 'sky' | redir END
- call assert_equal('blue sky', color)
-endfunc
-
-func Test_redir_cmd_readonly()
- CheckNotRoot
-
- " Redirecting to a read-only file
- call writefile([], 'Xfile')
- call setfperm('Xfile', 'r--r--r--')
- call assert_fails('redir! > Xfile', 'E190:')
- call delete('Xfile')
-endfunc
-
-" Test for the :filetype command
-func Test_filetype_cmd()
- call assert_fails('filetype abc', 'E475:')
-endfunc
-
-" Test for the :mode command
-func Test_mode_cmd()
- call assert_fails('mode abc', 'E359:')
-endfunc
-
-" Test for the :sleep command
-func Test_sleep_cmd()
- call assert_fails('sleep x', 'E475:')
-endfunc
-
-" Test for the :read command
-func Test_read_cmd()
- call writefile(['one'], 'Xfile')
- new
- call assert_fails('read', 'E32:')
- edit Xfile
- read
- call assert_equal(['one', 'one'], getline(1, '$'))
- close!
- new
- read Xfile
- call assert_equal(['', 'one'], getline(1, '$'))
- call deletebufline('', 1, '$')
- call feedkeys("Qr Xfile\<CR>visual\<CR>", 'xt')
- call assert_equal(['one'], getline(1, '$'))
- close!
- call delete('Xfile')
-endfunc
-
-" Test for running Ex commands when text is locked.
-" <C-\>e in the command line is used to lock the text
-func Test_run_excmd_with_text_locked()
- " :quit
- let cmd = ":\<C-\>eexecute('quit')\<CR>\<C-C>"
- call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
-
- " :qall
- let cmd = ":\<C-\>eexecute('qall')\<CR>\<C-C>"
- call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
-
- " :exit
- let cmd = ":\<C-\>eexecute('exit')\<CR>\<C-C>"
- call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
-
- " :close - should be ignored
- new
- let cmd = ":\<C-\>eexecute('close')\<CR>\<C-C>"
- call assert_equal(2, winnr('$'))
- close
-
- call assert_fails("call feedkeys(\":\<C-R>=execute('bnext')\<CR>\", 'xt')", 'E565:')
-
- " :tabfirst
- tabnew
- call assert_fails("call feedkeys(\":\<C-R>=execute('tabfirst')\<CR>\", 'xt')", 'E565:')
- tabclose
-endfunc
-
-" Test for the :verbose command
-func Test_verbose_cmd()
- set verbose=3
- call assert_match(' verbose=1\n\s*Last set from ', execute('verbose set vbs'), "\n")
- call assert_equal([' verbose=0'], split(execute('0verbose set vbs'), "\n"))
- set verbose=0
- call assert_match(' verbose=4\n\s*Last set from .*\n verbose=0',
- \ execute("4verbose set verbose | set verbose"))
-endfunc
-
-" Test for the :delete command and the related abbreviated commands
-func Test_excmd_delete()
- new
- call setline(1, ['foo', "\tbar"])
- call assert_equal(['^Ibar$'], split(execute('dl'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal(['^Ibar$'], split(execute('dell'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal(['^Ibar$'], split(execute('delel'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal(['^Ibar$'], split(execute('deletl'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal(['^Ibar$'], split(execute('deletel'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal([' bar'], split(execute('dp'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal([' bar'], split(execute('dep'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal([' bar'], split(execute('delp'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal([' bar'], split(execute('delep'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal([' bar'], split(execute('deletp'), "\n"))
- call setline(1, ['foo', "\tbar"])
- call assert_equal([' bar'], split(execute('deletep'), "\n"))
- close!
-endfunc
-
-" Test for commands that are blocked in a sandbox
-func Sandbox_tests()
- call assert_fails("call histadd(':', 'ls')", 'E48:')
- call assert_fails("call mkdir('Xdir')", 'E48:')
- call assert_fails("call rename('a', 'b')", 'E48:')
- call assert_fails("call setbufvar(1, 'myvar', 1)", 'E48:')
- call assert_fails("call settabvar(1, 'myvar', 1)", 'E48:')
- call assert_fails("call settabwinvar(1, 1, 'myvar', 1)", 'E48:')
- call assert_fails("call setwinvar(1, 'myvar', 1)", 'E48:')
- call assert_fails("call timer_start(100, '')", 'E48:')
- if has('channel')
- call assert_fails("call prompt_setcallback(1, '')", 'E48:')
- call assert_fails("call prompt_setinterrupt(1, '')", 'E48:')
- call assert_fails("call prompt_setprompt(1, '')", 'E48:')
- endif
- call assert_fails("let $TESTVAR=1", 'E48:')
- call assert_fails("call feedkeys('ivim')", 'E48:')
- call assert_fails("source! Xfile", 'E48:')
- call assert_fails("call delete('Xfile')", 'E48:')
- call assert_fails("call writefile([], 'Xfile')", 'E48:')
- call assert_fails('!ls', 'E48:')
- " call assert_fails('shell', 'E48:')
- call assert_fails('stop', 'E48:')
- call assert_fails('exe "normal \<C-Z>"', 'E48:')
- " set insertmode
- " call assert_fails('call feedkeys("\<C-Z>", "xt")', 'E48:')
- " set insertmode&
- call assert_fails('suspend', 'E48:')
- call assert_fails('call system("ls")', 'E48:')
- call assert_fails('call systemlist("ls")', 'E48:')
- if has('clientserver')
- call assert_fails('let s=remote_expr("gvim", "2+2")', 'E48:')
- if !has('win32')
- " remote_foreground() doesn't thrown an error message on MS-Windows
- call assert_fails('call remote_foreground("gvim")', 'E48:')
- endif
- call assert_fails('let s=remote_peek("gvim")', 'E48:')
- call assert_fails('let s=remote_read("gvim")', 'E48:')
- call assert_fails('let s=remote_send("gvim", "abc")', 'E48:')
- call assert_fails('let s=server2client("gvim", "abc")', 'E48:')
- endif
- if has('terminal')
- call assert_fails('terminal', 'E48:')
- call assert_fails('call term_start("vim")', 'E48:')
- call assert_fails('call term_dumpwrite(1, "Xfile")', 'E48:')
- endif
- if has('channel')
- call assert_fails("call ch_logfile('chlog')", 'E48:')
- call assert_fails("call ch_open('localhost:8765')", 'E48:')
- endif
- if has('job')
- call assert_fails("call job_start('vim')", 'E48:')
- endif
- if has('unix') && has('libcall')
- call assert_fails("echo libcall('libc.so', 'getenv', 'HOME')", 'E48:')
- endif
- if has('unix')
- call assert_fails('cd `pwd`', 'E48:')
- endif
- " some options cannot be changed in a sandbox
- call assert_fails('set exrc', 'E48:')
- call assert_fails('set cdpath', 'E48:')
- if has('xim') && has('gui_gtk')
- call assert_fails('set imstyle', 'E48:')
- endif
-endfunc
-
-func Test_sandbox()
- sandbox call Sandbox_tests()
-endfunc
-
-func Test_command_not_implemented_E319()
- if !has('mzscheme')
- call assert_fails('mzscheme', 'E319:')
- endif
-endfunc
-
-func Test_not_break_expression_register()
- call setreg('=', '1+1')
- if 0
- put =1
- endif
- call assert_equal('1+1', getreg('=', 1))
-endfunc
-
-func Test_address_line_overflow()
- throw 'Skipped: v:sizeoflong is N/A' " use legacy/excmd_spec.lua instead
-
- if v:sizeoflong < 8
- throw 'Skipped: only works with 64 bit long ints'
- endif
- new
- call setline(1, 'text')
- call assert_fails('|.44444444444444444444444', 'E1247:')
- call assert_fails('|.9223372036854775806', 'E1247:')
- bwipe!
-endfunc
-
-" This was leaving the cursor in line zero
-func Test_using_zero_in_range()
- new
- norm o00
- silent! 0;s/\%')
- bwipe!
-endfunc
-
-" Test :write after changing name with :file and loading it with :edit
-func Test_write_after_rename()
- call writefile(['text'], 'Xfile')
-
- enew
- file Xfile
- call assert_fails('write', 'E13: File exists (add ! to override)')
-
- " works OK after ":edit"
- edit
- write
-
- call delete('Xfile')
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_exec_while_if.vim b/src/nvim/testdir/test_exec_while_if.vim
deleted file mode 100644
index 3f13b09945..0000000000
--- a/src/nvim/testdir/test_exec_while_if.vim
+++ /dev/null
@@ -1,43 +0,0 @@
-" Test for :execute, :while, :for and :if
-
-func Test_exec_while_if()
- new
-
- let i = 0
- while i < 12
- let i = i + 1
- execute "normal o" . i . "\033"
- if i % 2
- normal Ax
- if i == 9
- break
- endif
- if i == 5
- continue
- else
- let j = 9
- while j > 0
- execute "normal" j . "a" . j . "\x1b"
- let j = j - 1
- endwhile
- endif
- endif
- if i == 9
- execute "normal Az\033"
- endif
- endwhile
- unlet i j
-
- call assert_equal(["",
- \ "1x999999999888888887777777666666555554444333221",
- \ "2",
- \ "3x999999999888888887777777666666555554444333221",
- \ "4",
- \ "5x",
- \ "6",
- \ "7x999999999888888887777777666666555554444333221",
- \ "8",
- \ "9x"], getline(1, 10))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim
deleted file mode 100644
index 16cc20e9a7..0000000000
--- a/src/nvim/testdir/test_execute_func.vim
+++ /dev/null
@@ -1,176 +0,0 @@
-" test execute()
-
-source view_util.vim
-
-func NestedEval()
- let nested = execute('echo "nested\nlines"')
- echo 'got: "' . nested . '"'
-endfunc
-
-func NestedRedir()
- redir => var
- echo 'broken'
- redir END
-endfunc
-
-func Test_execute_string()
- call assert_equal("\nnocompatible", execute('set compatible?'))
- call assert_equal("\nsomething\nnice", execute('echo "something\nnice"'))
- call assert_equal("noendofline", execute('echon "noendofline"'))
- call assert_equal("", execute(123))
-
- call assert_equal("\ngot: \"\nnested\nlines\"", execute('call NestedEval()'))
- redir => redired
- echo 'this'
- let evaled = execute('echo "that"')
- echo 'theend'
- redir END
-" Nvim supports execute('... :redir ...'), so this test is intentionally
-" disabled.
-" call assert_equal("\nthis\ntheend", redired)
- call assert_equal("\nthat", evaled)
-
- call assert_fails('call execute("doesnotexist")', 'E492:')
- call assert_fails('call execute(3.4)', 'E806:')
-" Nvim supports execute('... :redir ...'), so this test is intentionally
-" disabled.
-" call assert_fails('call execute("call NestedRedir()")', 'E930:')
-
- call assert_equal("\nsomething", execute('echo "something"', ''))
- call assert_equal("\nsomething", execute('echo "something"', 'silent'))
- call assert_equal("\nsomething", execute('echo "something"', 'silent!'))
- call assert_equal("", execute('burp', 'silent!'))
- call assert_fails('call execute("echo \"x\"", 3.4)', 'E806:')
-
- call assert_equal("", execute(""))
-endfunc
-
-func Test_execute_list()
- call assert_equal("\nsomething\nnice", execute(['echo "something"', 'echo "nice"']))
- let l = ['for n in range(0, 3)',
- \ 'echo n',
- \ 'endfor']
- call assert_equal("\n0\n1\n2\n3", execute(l))
-
- call assert_equal("", execute([]))
- call assert_equal("", execute(v:_null_list))
-endfunc
-
-func Test_execute_does_not_change_col()
- echo ''
- echon 'abcd'
- let x = execute('silent echo 234343')
- echon 'xyz'
- let text = ''
- for col in range(1, 7)
- let text .= nr2char(screenchar(&lines, col))
- endfor
- call assert_equal('abcdxyz', text)
-endfunc
-
-func Test_execute_not_silent()
- echo ''
- echon 'abcd'
- let x = execute('echon 234', '')
- echo 'xyz'
- let text1 = ''
- for col in range(1, 8)
- let text1 .= nr2char(screenchar(&lines - 1, col))
- endfor
- call assert_equal('abcd234 ', text1)
- let text2 = ''
- for col in range(1, 4)
- let text2 .= nr2char(screenchar(&lines, col))
- endfor
- call assert_equal('xyz ', text2)
-endfunc
-
-func Test_win_execute()
- let thiswin = win_getid()
- new
- let otherwin = win_getid()
- call setline(1, 'the new window')
- call win_gotoid(thiswin)
- let line = win_execute(otherwin, 'echo getline(1)')
- call assert_match('the new window', line)
- let line = win_execute(134343, 'echo getline(1)')
- call assert_equal('', line)
-
- if has('textprop')
- let popupwin = popup_create('the popup win', {'line': 2, 'col': 3})
- redraw
- let line = 'echo getline(1)'->win_execute(popupwin)
- call assert_match('the popup win', line)
-
- call popup_close(popupwin)
- endif
-
- call win_gotoid(otherwin)
- bwipe!
-
- " check :lcd in another window does not change directory
- let curid = win_getid()
- let curdir = getcwd()
- split Xother
- lcd ..
- " Use :pwd to get the actual current directory
- let otherdir = execute('pwd')
- call win_execute(curid, 'lcd testdir')
- call assert_equal(otherdir, execute('pwd'))
- bwipe!
- execute 'cd ' .. curdir
-endfunc
-
-func Test_win_execute_update_ruler()
- enew
- call setline(1, range(500))
- 20
- split
- let winid = win_getid()
- set ruler
- wincmd w
- let height = winheight(winid)
- redraw
- call assert_match('20,1', Screenline(height + 1))
- let line = win_execute(winid, 'call cursor(100, 1)')
- redraw
- call assert_match('100,1', Screenline(height + 1))
-
- bwipe!
-endfunc
-
-func Test_win_execute_other_tab()
- let thiswin = win_getid()
- tabnew
- call win_execute(thiswin, 'let xyz = 1')
- call assert_equal(1, xyz)
- tabclose
- unlet xyz
-endfunc
-
-func Test_win_execute_visual_redraw()
- call setline(1, ['a', 'b', 'c'])
- new
- wincmd p
- " start Visual in current window, redraw in other window with fewer lines
- call feedkeys("G\<C-V>", 'txn')
- call win_execute(winnr('#')->win_getid(), 'redraw')
- call feedkeys("\<Esc>", 'txn')
- bwipe!
- bwipe!
-
- enew
- new
- call setline(1, ['a', 'b', 'c'])
- let winid = win_getid()
- wincmd p
- " start Visual in current window, extend it in other window with more lines
- call feedkeys("\<C-V>", 'txn')
- call win_execute(winid, 'call feedkeys("G\<C-V>", ''txn'')')
- redraw
-
- bwipe!
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_exists.vim b/src/nvim/testdir/test_exists.vim
deleted file mode 100644
index 62c66192ef..0000000000
--- a/src/nvim/testdir/test_exists.vim
+++ /dev/null
@@ -1,331 +0,0 @@
-" Tests for the exists() function
-func Test_exists()
- augroup myagroup
- autocmd! BufEnter *.my echo "myfile edited"
- autocmd! FuncUndefined UndefFun exec "fu UndefFun()\nendfu"
- augroup END
- set rtp+=./sautest
-
- " valid autocmd group
- call assert_equal(1, exists('#myagroup'))
- " valid autocmd group with garbage
- call assert_equal(0, exists('#myagroup+b'))
- " Valid autocmd group and event
- call assert_equal(1, exists('#myagroup#BufEnter'))
- " Valid autocmd group, event and pattern
- call assert_equal(1, exists('#myagroup#BufEnter#*.my'))
- " Valid autocmd event
- call assert_equal(1, exists('#BufEnter'))
- " Valid autocmd event and pattern
- call assert_equal(1, exists('#BufEnter#*.my'))
- " Non-existing autocmd group or event
- call assert_equal(0, exists('#xyzagroup'))
- " Non-existing autocmd group and valid autocmd event
- call assert_equal(0, exists('#xyzagroup#BufEnter'))
- " Valid autocmd group and event with no matching pattern
- call assert_equal(0, exists('#myagroup#CmdwinEnter'))
- " Valid autocmd group and non-existing autocmd event
- call assert_equal(0, exists('#myagroup#xyzacmd'))
- " Valid autocmd group and event and non-matching pattern
- call assert_equal(0, exists('#myagroup#BufEnter#xyzpat'))
- " Valid autocmd event and non-matching pattern
- call assert_equal(0, exists('#BufEnter#xyzpat'))
- " Empty autocmd group, event and pattern
- call assert_equal(0, exists('###'))
- " Empty autocmd group and event or empty event and pattern
- call assert_equal(0, exists('##'))
- " Valid autocmd event
- call assert_equal(1, exists('##FileReadCmd'))
- " Non-existing autocmd event
- call assert_equal(0, exists('##MySpecialCmd'))
-
- " Existing and working option (long form)
- call assert_equal(1, exists('&textwidth'))
- " Existing and working option (short form)
- call assert_equal(1, exists('&tw'))
- " Existing and working option with garbage
- call assert_equal(0, exists('&tw-'))
- " Global option
- call assert_equal(1, exists('&g:errorformat'))
- " Local option
- call assert_equal(1, exists('&l:errorformat'))
- " Negative form of existing and working option (long form)
- call assert_equal(0, exists('&nojoinspaces'))
- " Negative form of existing and working option (short form)
- call assert_equal(0, exists('&nojs'))
- " Non-existing option
- call assert_equal(0, exists('&myxyzoption'))
-
- " Existing and working option (long form)
- call assert_equal(1, exists('+incsearch'))
- " Existing and working option with garbage
- call assert_equal(0, exists('+incsearch!1'))
- " Existing and working option (short form)
- call assert_equal(1, exists('+is'))
- " Existing option that is hidden.
- call assert_equal(0, exists('+autoprint'))
-
- " Existing environment variable
- let $EDITOR_NAME = 'Vim Editor'
- call assert_equal(1, exists('$EDITOR_NAME'))
- if has('unix')
- " ${name} environment variables are supported only on Unix-like systems
- call assert_equal(1, exists('${VIM}'))
- endif
- " Non-existing environment variable
- call assert_equal(0, exists('$NON_ENV_VAR'))
-
- " Valid internal function
- call assert_equal(1, exists('*bufnr'))
- " Valid internal function with ()
- call assert_equal(1, exists('*bufnr()'))
- " Non-existing internal function
- call assert_equal(0, exists('*myxyzfunc'))
- " Valid internal function with garbage
- call assert_equal(0, exists('*bufnr&6'))
- " Valid user defined function
- call assert_equal(1, exists('*Test_exists'))
- " Non-existing user defined function
- call assert_equal(0, exists('*MyxyzFunc'))
- " Function that may be created by FuncUndefined event
- call assert_equal(0, exists('*UndefFun'))
- " Function that may be created by script autoloading
- call assert_equal(0, exists('*footest#F'))
-
- " Valid internal command (full match)
- call assert_equal(2, exists(':edit'))
- " Valid internal command (full match) with garbage
- call assert_equal(0, exists(':edit/a'))
- " Valid internal command (partial match)
- call assert_equal(1, exists(':q'))
- " Valid internal command with a digit
- call assert_equal(2, exists(':2match'))
- " Non-existing internal command
- call assert_equal(0, exists(':invalidcmd'))
- " Internal command with a count
- call assert_equal(0, exists(':3buffer'))
-
- " User defined command (full match)
- command! MyCmd :echo 'My command'
- call assert_equal(2, exists(':MyCmd'))
- " User defined command (partial match)
- command! MyOtherCmd :echo 'Another command'
- call assert_equal(3, exists(':My'))
-
- " Command modifier
- call assert_equal(2, exists(':rightbelow'))
-
- " Non-existing user defined command (full match)
- delcommand MyCmd
- call assert_equal(0, exists(':MyCmd'))
-
- " Non-existing user defined command (partial match)
- delcommand MyOtherCmd
- call assert_equal(0, exists(':My'))
-
- " Valid local variable
- let local_var = 1
- call assert_equal(1, exists('local_var'))
- " Valid local variable with garbage
- call assert_equal(0, exists('local_var%n'))
- " Non-existing local variable
- unlet local_var
- call assert_equal(0, exists('local_var'))
-
- " Non-existing autoload variable that may be autoloaded
- call assert_equal(0, exists('footest#x'))
-
- " Valid local list
- let local_list = ["blue", "orange"]
- call assert_equal(1, exists('local_list'))
- " Valid local list item
- call assert_equal(1, exists('local_list[1]'))
- " Valid local list item with garbage
- call assert_equal(0, exists('local_list[1]+5'))
- " Invalid local list item
- call assert_equal(0, exists('local_list[2]'))
- " Non-existing local list
- unlet local_list
- call assert_equal(0, exists('local_list'))
- " Valid local dictionary
- let local_dict = {"xcord":100, "ycord":2}
- call assert_equal(1, exists('local_dict'))
- " Non-existing local dictionary
- unlet local_dict
- call assert_equal(0, exists('local_dict'))
- " Existing local curly-brace variable
- let str = "local"
- let curly_{str}_var = 1
- call assert_equal(1, exists('curly_{str}_var'))
- " Non-existing local curly-brace variable
- unlet curly_{str}_var
- call assert_equal(0, exists('curly_{str}_var'))
-
- " Existing global variable
- let g:global_var = 1
- call assert_equal(1, exists('g:global_var'))
- " Existing global variable with garbage
- call assert_equal(0, exists('g:global_var-n'))
- " Non-existing global variable
- unlet g:global_var
- call assert_equal(0, exists('g:global_var'))
- " Existing global list
- let g:global_list = ["blue", "orange"]
- call assert_equal(1, exists('g:global_list'))
- " Non-existing global list
- unlet g:global_list
- call assert_equal(0, exists('g:global_list'))
- " Existing global dictionary
- let g:global_dict = {"xcord":100, "ycord":2}
- call assert_equal(1, exists('g:global_dict'))
- " Non-existing global dictionary
- unlet g:global_dict
- call assert_equal(0, exists('g:global_dict'))
- " Existing global curly-brace variable
- let str = "global"
- let g:curly_{str}_var = 1
- call assert_equal(1, exists('g:curly_{str}_var'))
- " Non-existing global curly-brace variable
- unlet g:curly_{str}_var
- call assert_equal(0, exists('g:curly_{str}_var'))
-
- " Existing window variable
- let w:window_var = 1
- call assert_equal(1, exists('w:window_var'))
- " Non-existing window variable
- unlet w:window_var
- call assert_equal(0, exists('w:window_var'))
- " Existing window list
- let w:window_list = ["blue", "orange"]
- call assert_equal(1, exists('w:window_list'))
- " Non-existing window list
- unlet w:window_list
- call assert_equal(0, exists('w:window_list'))
- " Existing window dictionary
- let w:window_dict = {"xcord":100, "ycord":2}
- call assert_equal(1, exists('w:window_dict'))
- " Non-existing window dictionary
- unlet w:window_dict
- call assert_equal(0, exists('w:window_dict'))
- " Existing window curly-brace variable
- let str = "window"
- let w:curly_{str}_var = 1
- call assert_equal(1, exists('w:curly_{str}_var'))
- " Non-existing window curly-brace variable
- unlet w:curly_{str}_var
- call assert_equal(0, exists('w:curly_{str}_var'))
-
- " Existing tab variable
- let t:tab_var = 1
- call assert_equal(1, exists('t:tab_var'))
- " Non-existing tab variable
- unlet t:tab_var
- call assert_equal(0, exists('t:tab_var'))
- " Existing tab list
- let t:tab_list = ["blue", "orange"]
- call assert_equal(1, exists('t:tab_list'))
- " Non-existing tab list
- unlet t:tab_list
- call assert_equal(0, exists('t:tab_list'))
- " Existing tab dictionary
- let t:tab_dict = {"xcord":100, "ycord":2}
- call assert_equal(1, exists('t:tab_dict'))
- " Non-existing tab dictionary
- unlet t:tab_dict
- call assert_equal(0, exists('t:tab_dict'))
- " Existing tab curly-brace variable
- let str = "tab"
- let t:curly_{str}_var = 1
- call assert_equal(1, exists('t:curly_{str}_var'))
- " Non-existing tab curly-brace variable
- unlet t:curly_{str}_var
- call assert_equal(0, exists('t:curly_{str}_var'))
-
- " Existing buffer variable
- let b:buffer_var = 1
- call assert_equal(1, exists('b:buffer_var'))
- " Non-existing buffer variable
- unlet b:buffer_var
- call assert_equal(0, exists('b:buffer_var'))
- " Existing buffer list
- let b:buffer_list = ["blue", "orange"]
- call assert_equal(1, exists('b:buffer_list'))
- " Non-existing buffer list
- unlet b:buffer_list
- call assert_equal(0, exists('b:buffer_list'))
- " Existing buffer dictionary
- let b:buffer_dict = {"xcord":100, "ycord":2}
- call assert_equal(1, exists('b:buffer_dict'))
- " Non-existing buffer dictionary
- unlet b:buffer_dict
- call assert_equal(0, exists('b:buffer_dict'))
- " Existing buffer curly-brace variable
- let str = "buffer"
- let b:curly_{str}_var = 1
- call assert_equal(1, exists('b:curly_{str}_var'))
- " Non-existing buffer curly-brace variable
- unlet b:curly_{str}_var
- call assert_equal(0, exists('b:curly_{str}_var'))
-
- " Existing Vim internal variable
- call assert_equal(1, exists('v:version'))
- " Non-existing Vim internal variable
- call assert_equal(0, exists('v:non_exists_var'))
-
- " Existing script-local variable
- let s:script_var = 1
- call assert_equal(1, exists('s:script_var'))
- " Non-existing script-local variable
- unlet s:script_var
- call assert_equal(0, exists('s:script_var'))
- " Existing script-local list
- let s:script_list = ["blue", "orange"]
- call assert_equal(1, exists('s:script_list'))
- " Non-existing script-local list
- unlet s:script_list
- call assert_equal(0, exists('s:script_list'))
- " Existing script-local dictionary
- let s:script_dict = {"xcord":100, "ycord":2}
- call assert_equal(1, exists('s:script_dict'))
- " Non-existing script-local dictionary
- unlet s:script_dict
- call assert_equal(0, exists('s:script_dict'))
- " Existing script curly-brace variable
- let str = "script"
- let s:curly_{str}_var = 1
- call assert_equal(1, exists('s:curly_{str}_var'))
- " Non-existing script-local curly-brace variable
- unlet s:curly_{str}_var
- call assert_equal(0, exists('s:curly_{str}_var'))
-
- " Existing script-local function
- function! s:my_script_func()
- endfunction
-
- echo '*s:my_script_func: 1'
- call assert_equal(1, exists('*s:my_script_func'))
-
- " Non-existing script-local function
- delfunction s:my_script_func
-
- call assert_equal(0, exists('*s:my_script_func'))
- unlet str
-
- call assert_equal(1, g:footest#x)
- call assert_equal(0, footest#F())
- call assert_equal(0, UndefFun())
-endfunc
-
-" exists() test for Function arguments
-func FuncArg_Tests(func_arg, ...)
- call assert_equal(1, exists('a:func_arg'))
- call assert_equal(0, exists('a:non_exists_arg'))
- call assert_equal(1, exists('a:1'))
- call assert_equal(0, exists('a:2'))
-endfunc
-
-func Test_exists_funcarg()
- call FuncArg_Tests("arg1", "arg2")
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_exists_autocmd.vim b/src/nvim/testdir/test_exists_autocmd.vim
deleted file mode 100644
index 7e44a72653..0000000000
--- a/src/nvim/testdir/test_exists_autocmd.vim
+++ /dev/null
@@ -1,26 +0,0 @@
-" Test that groups and patterns are tested correctly when calling exists() for
-" autocommands.
-
-function Test_AutoCommands()
- let results=[]
- augroup auexists
- augroup END
- call assert_true(exists("##BufEnter"))
- call assert_false(exists("#BufEnter"))
- au BufEnter * let g:entered=1
- call assert_true(exists("#BufEnter"))
- call assert_false(exists("#auexists#BufEnter"))
- augroup auexists
- au BufEnter * let g:entered=1
- augroup END
- call assert_true(exists("#auexists#BufEnter"))
- call assert_false(exists("#BufEnter#*.test"))
- au BufEnter *.test let g:entered=1
- call assert_true(exists("#BufEnter#*.test"))
- edit testfile.test
- call assert_false(exists("#BufEnter#<buffer>"))
- au BufEnter <buffer> let g:entered=1
- call assert_true(exists("#BufEnter#<buffer>"))
- edit testfile2.test
- call assert_false(exists("#BufEnter#<buffer>"))
-endfunction
diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim
deleted file mode 100644
index 6dbfb7047c..0000000000
--- a/src/nvim/testdir/test_exit.vim
+++ /dev/null
@@ -1,135 +0,0 @@
-" Tests for exiting Vim.
-
-source shared.vim
-source check.vim
-
-func Test_exiting()
- 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 =<< 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 =<< 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')
-
- " 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'))
- endif
- call delete('Xtestout')
-endfunc
-
-" Test for getting the Vim exit code from v:exiting
-func Test_exit_code()
- call assert_equal(v:null, v:exiting)
-
- let before =<< trim [CODE]
- au QuitPre * call writefile(['qp = ' .. v:exiting], 'Xtestout', 'a')
- au ExitPre * call writefile(['ep = ' .. v:exiting], 'Xtestout', 'a')
- au VimLeavePre * call writefile(['lp = ' .. v:exiting], 'Xtestout', 'a')
- au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout', 'a')
- [CODE]
-
- if RunVim(before, ['quit'], '')
- call assert_equal(['qp = v:null', 'ep = v:null', 'lp = 0', 'l = 0'], readfile('Xtestout'))
- endif
- call delete('Xtestout')
-
- if RunVim(before, ['cquit'], '')
- call assert_equal(['lp = 1', 'l = 1'], readfile('Xtestout'))
- endif
- call delete('Xtestout')
-
- if RunVim(before, ['cquit 4'], '')
- call assert_equal(['lp = 4', 'l = 4'], readfile('Xtestout'))
- endif
- call delete('Xtestout')
-endfunc
-
-func Test_exit_error_reading_input()
- throw 'Skipped: Nvim does not exit after stdin is read'
-
- CheckNotGui
- CheckNotMSWindows
- " The early exit causes memory not to be freed somehow
- CheckNotAsan
- CheckNotValgrind
-
- call writefile([":au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout')", ":tabnew", "q:"], 'Xscript', 'b')
-
- " Nvim requires "-s -" to read stdin as Normal mode input
- " if RunVim([], [], '<Xscript')
- if RunVim([], [], '-s - <Xscript')
- call assert_equal(1, v:shell_error)
- call assert_equal(['l = 1'], readfile('Xtestout'))
- endif
- call delete('Xscript')
- call delete('Xtestout')
-endfun
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim
deleted file mode 100644
index 4f5bb67d21..0000000000
--- a/src/nvim/testdir/test_expand.vim
+++ /dev/null
@@ -1,229 +0,0 @@
-" Test for expanding file names
-
-source shared.vim
-source check.vim
-
-func Test_with_directories()
- call mkdir('Xdir1')
- call mkdir('Xdir2')
- call mkdir('Xdir3')
- cd Xdir3
- call mkdir('Xdir4')
- cd ..
-
- split Xdir1/file
- call setline(1, ['a', 'b'])
- w
- w Xdir3/Xdir4/file
- close
-
- next Xdir?/*/file
- call assert_equal('Xdir3/Xdir4/file', expand('%'))
- if has('unix')
- next! Xdir?/*/nofile
- call assert_equal('Xdir?/*/nofile', expand('%'))
- endif
- " Edit another file, on MS-Windows the swap file would be in use and can't
- " be deleted.
- edit foo
-
- call assert_equal(0, delete('Xdir1', 'rf'))
- call assert_equal(0, delete('Xdir2', 'rf'))
- call assert_equal(0, delete('Xdir3', 'rf'))
-endfunc
-
-func Test_with_tilde()
- let dir = getcwd()
- call mkdir('Xdir ~ dir')
- call assert_true(isdirectory('Xdir ~ dir'))
- cd Xdir\ ~\ dir
- call assert_true(getcwd() =~ 'Xdir \~ dir')
- call chdir(dir)
- call delete('Xdir ~ dir', 'd')
- call assert_false(isdirectory('Xdir ~ dir'))
-endfunc
-
-func Test_expand_tilde_filename()
- split ~
- call assert_equal('~', expand('%'))
- call assert_notequal(expand('%:p'), expand('~/'))
- call assert_match('\~', expand('%:p'))
- bwipe!
-endfunc
-
-func Test_expandcmd()
- let $FOO = 'Test'
- call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y'))
- unlet $FOO
-
- new
- edit Xfile1
- call assert_equal('e Xfile1', expandcmd('e %'))
- edit Xfile2
- edit Xfile1
- call assert_equal('e Xfile2', 'e #'->expandcmd())
- edit Xfile2
- edit Xfile3
- edit Xfile4
- let bnum = bufnr('Xfile2')
- call assert_equal('e Xfile2', expandcmd('e #' . bnum))
- call setline('.', 'Vim!@#')
- call assert_equal('e Vim', expandcmd('e <cword>'))
- call assert_equal('e Vim!@#', expandcmd('e <cWORD>'))
- enew!
- edit Xfile.java
- call assert_equal('e Xfile.py', expandcmd('e %:r.py'))
- call assert_equal('make abc.java', expandcmd('make abc.%:e'))
- call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?'))
- edit a1a2a3.rb
- call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o'))
-
- call assert_equal('make <afile>', expandcmd("make <afile>"))
- call assert_equal('make <amatch>', expandcmd("make <amatch>"))
- call assert_equal('make <abuf>', expandcmd("make <abuf>"))
- enew
- call assert_equal('make %', expandcmd("make %"))
- let $FOO="blue\tsky"
- call setline(1, "$FOO")
- call assert_equal("grep pat blue\tsky", expandcmd('grep pat <cfile>'))
-
- " Test for expression expansion `=
- let $FOO= "blue"
- call assert_equal("blue sky", expandcmd("`=$FOO .. ' sky'`"))
- let x = expandcmd("`=axbycz`")
- call assert_equal('`=axbycz`', x)
- call assert_fails('let x = expandcmd("`=axbycz`", #{errmsg: 1})', 'E121:')
- let x = expandcmd("`=axbycz`", #{abc: []})
- call assert_equal('`=axbycz`', x)
-
- " Test for env variable with spaces
- let $FOO= "foo bar baz"
- call assert_equal("e foo bar baz", expandcmd("e $FOO"))
-
- if has('unix') && executable('bash')
- " test for using the shell to expand a command argument.
- " only bash supports the {..} syntax
- set shell=bash
- let x = expandcmd('{1..4}')
- call assert_equal('{1..4}', x)
- call assert_fails("let x = expandcmd('{1..4}', #{errmsg: v:true})", 'E77:')
- let x = expandcmd('{1..4}', #{error: v:true})
- call assert_equal('{1..4}', x)
- set shell&
- endif
-
- unlet $FOO
- close!
-endfunc
-
-" Test for expanding <sfile>, <slnum> and <sflnum> outside of sourcing a script
-func Test_source_sfile()
- let lines =<< trim [SCRIPT]
- :call assert_equal('<sfile>', expandcmd("<sfile>"))
- :call assert_equal('<slnum>', expandcmd("<slnum>"))
- :call assert_equal('<sflnum>', expandcmd("<sflnum>"))
- :call assert_equal('edit <cfile>', expandcmd("edit <cfile>"))
- :call assert_equal('edit #', expandcmd("edit #"))
- :call assert_equal('edit #<2', expandcmd("edit #<2"))
- :call assert_equal('edit <cword>', expandcmd("edit <cword>"))
- :call assert_equal('edit <cexpr>', expandcmd("edit <cexpr>"))
- :call assert_fails('autocmd User MyCmd echo "<sfile>"', 'E498:')
- :
- :call assert_equal('', expand('<script>'))
- :verbose echo expand('<script>')
- :call add(v:errors, v:errmsg)
- :verbose echo expand('<sfile>')
- :call add(v:errors, v:errmsg)
- :call writefile(v:errors, 'Xresult')
- :qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -s Xscript')
- call assert_equal([
- \ 'E1274: No script file name to substitute for "<script>"',
- \ 'E498: no :source file name to substitute for "<sfile>"'],
- \ readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" Test for expanding filenames multiple times in a command line
-func Test_expand_filename_multicmd()
- edit foo
- call setline(1, 'foo!')
- new
- call setline(1, 'foo!')
- new <cword> | new <cWORD>
- call assert_equal(4, winnr('$'))
- call assert_equal('foo!', bufname(winbufnr(1)))
- call assert_equal('foo', bufname(winbufnr(2)))
- call assert_fails('e %:s/.*//', 'E500:')
- %bwipe!
-endfunc
-
-func Test_expandcmd_shell_nonomatch()
- CheckNotMSWindows
- call assert_equal('$*', expandcmd('$*'))
-endfunc
-
-func Test_expand_script_source()
- let lines0 =<< trim [SCRIPT]
- call extend(g:script_level, [expand('<script>:t')])
- so Xscript1
- func F0()
- call extend(g:func_level, [expand('<script>:t')])
- endfunc
-
- au User * call extend(g:au_level, [expand('<script>:t')])
- [SCRIPT]
-
- let lines1 =<< trim [SCRIPT]
- call extend(g:script_level, [expand('<script>:t')])
- so Xscript2
- func F1()
- call extend(g:func_level, [expand('<script>:t')])
- endfunc
-
- au User * call extend(g:au_level, [expand('<script>:t')])
- [SCRIPT]
-
- let lines2 =<< trim [SCRIPT]
- call extend(g:script_level, [expand('<script>:t')])
- func F2()
- call extend(g:func_level, [expand('<script>:t')])
- endfunc
-
- au User * call extend(g:au_level, [expand('<script>:t')])
- [SCRIPT]
-
- call writefile(lines0, 'Xscript0')
- call writefile(lines1, 'Xscript1')
- call writefile(lines2, 'Xscript2')
-
- " Check the expansion of <script> at different levels.
- let g:script_level = []
- let g:func_level = []
- let g:au_level = []
-
- so Xscript0
- call F0()
- call F1()
- call F2()
- doautocmd User
-
- call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:script_level)
- call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:func_level)
- call assert_equal(['Xscript2', 'Xscript1', 'Xscript0'], g:au_level)
-
- unlet g:script_level g:func_level
- delfunc F0
- delfunc F1
- delfunc F2
-
- call delete('Xscript0')
- call delete('Xscript1')
- call delete('Xscript2')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim
deleted file mode 100644
index 454d76f0aa..0000000000
--- a/src/nvim/testdir/test_expand_func.vim
+++ /dev/null
@@ -1,146 +0,0 @@
-" Tests for expand()
-
-source shared.vim
-
-let s:sfile = expand('<sfile>')
-let s:slnum = str2nr(expand('<slnum>'))
-let s:sflnum = str2nr(expand('<sflnum>'))
-
-func s:expand_sfile()
- return expand('<sfile>')
-endfunc
-
-func s:expand_slnum()
- return str2nr(expand('<slnum>'))
-endfunc
-
-func s:expand_sflnum()
- return str2nr(expand('<sflnum>'))
-endfunc
-
-" This test depends on the location in the test file, put it first.
-func Test_expand_sflnum()
- call assert_equal(7, s:sflnum)
- call assert_equal(24, str2nr(expand('<sflnum>')))
-
- " Line-continuation
- call assert_equal(
- \ 27,
- \ str2nr(expand('<sflnum>')))
-
- " Call in script-local function
- call assert_equal(18, s:expand_sflnum())
-
- " Call in command
- command Flnum echo expand('<sflnum>')
- call assert_equal(36, str2nr(trim(execute('Flnum'))))
- delcommand Flnum
-endfunc
-
-func Test_expand_sfile_and_stack()
- call assert_match('test_expand_func\.vim$', s:sfile)
- let expected = 'script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack'
- call assert_match(expected .. '$', expand('<sfile>'))
- call assert_match(expected .. '\[4\]$' , expand('<stack>'))
-
- " Call in script-local function
- call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack\[7\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile())
-
- " Call in command
- command Sfile echo expand('<sfile>')
- call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack$', trim(execute('Sfile')))
- delcommand Sfile
-
- " Use <stack> from sourced script.
- let lines =<< trim END
- " comment here
- let g:stack_value = expand('<stack>')
- END
- call writefile(lines, 'Xstack')
- source Xstack
- call assert_match('\<Xstack\[2\]$', g:stack_value)
- unlet g:stack_value
- call delete('Xstack')
-
- if exists('+shellslash')
- call mkdir('Xshellslash')
- let lines =<< trim END
- let g:stack1 = expand('<stack>')
- set noshellslash
- let g:stack2 = expand('<stack>')
- set shellslash
- let g:stack3 = expand('<stack>')
- END
- call writefile(lines, 'Xshellslash/Xstack')
- " Test that changing 'shellslash' always affects the result of expand()
- " when sourcing a script multiple times.
- for i in range(2)
- source Xshellslash/Xstack
- call assert_match('\<Xshellslash/Xstack\[1\]$', g:stack1)
- call assert_match('\<Xshellslash\\Xstack\[3\]$', g:stack2)
- call assert_match('\<Xshellslash/Xstack\[5\]$', g:stack3)
- unlet g:stack1
- unlet g:stack2
- unlet g:stack3
- endfor
- call delete('Xshellslash', 'rf')
- endif
-endfunc
-
-func Test_expand_slnum()
- call assert_equal(6, s:slnum)
- call assert_equal(2, str2nr(expand('<slnum>')))
-
- " Line-continuation
- call assert_equal(
- \ 5,
- \ str2nr(expand('<slnum>')))
-
- " Call in script-local function
- call assert_equal(1, s:expand_slnum())
-
- " Call in command
- command Slnum echo expand('<slnum>')
- call assert_equal(14, str2nr(trim(execute('Slnum'))))
- delcommand Slnum
-endfunc
-
-func Test_expand()
- new
- call assert_equal("", expand('%:S'))
- call assert_equal('3', '<slnum>'->expand())
- call assert_equal(['4'], expand('<slnum>', v:false, v:true))
- " Don't add any line above this, otherwise <slnum> will change.
- call assert_equal("", expand('%'))
- set verbose=1
- call assert_equal("", expand('%'))
- set verbose=0
- call assert_equal("", expand('%:p'))
- quit
-endfunc
-
-func s:sid_test()
- return 'works'
-endfunc
-
-func Test_expand_SID()
- let sid = expand('<SID>')
- execute 'let g:sid_result = ' .. sid .. 'sid_test()'
- call assert_equal('works', g:sid_result)
-endfunc
-
-
-" Test for 'wildignore' with expand()
-func Test_expand_wildignore()
- set wildignore=*.vim
- call assert_equal('', expand('test_expand_func.vim'))
- call assert_equal('', expand('test_expand_func.vim', 0))
- call assert_equal([], expand('test_expand_func.vim', 0, 1))
- call assert_equal('test_expand_func.vim', expand('test_expand_func.vim', 1))
- call assert_equal(['test_expand_func.vim'],
- \ expand('test_expand_func.vim', 1, 1))
- call assert_fails("call expand('*', [])", 'E745:')
- set wildignore&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
deleted file mode 100644
index 47f7f5eb0e..0000000000
--- a/src/nvim/testdir/test_expr.vim
+++ /dev/null
@@ -1,676 +0,0 @@
-" Tests for expressions.
-
-source check.vim
-
-func Test_equal()
- let base = {}
- func base.method()
- return 1
- endfunc
- func base.other() dict
- return 1
- endfunc
- let instance = copy(base)
- call assert_true(base.method == instance.method)
- call assert_true([base.method] == [instance.method])
- call assert_true(base.other == instance.other)
- call assert_true([base.other] == [instance.other])
-
- call assert_false(base.method == base.other)
- call assert_false([base.method] == [base.other])
- call assert_false(base.method == instance.other)
- call assert_false([base.method] == [instance.other])
-
- call assert_fails('echo base.method > instance.method')
-endfunc
-
-func Test_version()
- call assert_true(has('patch-7.4.001'))
- call assert_true(has('patch-7.4.01'))
- call assert_true(has('patch-7.4.1'))
- call assert_true(has('patch-6.9.999'))
- call assert_true(has('patch-7.1.999'))
- call assert_true(has('patch-7.4.123'))
-
- call assert_false(has('patch-7'))
- call assert_false(has('patch-7.4'))
- call assert_false(has('patch-7.4.'))
- call assert_false(has('patch-9.1.0'))
- call assert_false(has('patch-9.9.1'))
-endfunc
-
-func Test_dict()
- let d = {'': 'empty', 'a': 'a', 0: 'zero'}
- call assert_equal('empty', d[''])
- call assert_equal('a', d['a'])
- call assert_equal('zero', d[0])
- call assert_true(has_key(d, ''))
- call assert_true(has_key(d, 'a'))
- call assert_fails("let i = has_key([], 'a')", 'E715:')
-
- let d[''] = 'none'
- let d['a'] = 'aaa'
- call assert_equal('none', d[''])
- call assert_equal('aaa', d['a'])
-
- let d[ 'b' ] = 'bbb'
- call assert_equal('bbb', d[ 'b' ])
-endfunc
-
-func Test_strgetchar()
- call assert_equal(char2nr('a'), strgetchar('axb', 0))
- call assert_equal(char2nr('x'), 'axb'->strgetchar(1))
- call assert_equal(char2nr('b'), strgetchar('axb', 2))
-
- call assert_equal(-1, strgetchar('axb', -1))
- call assert_equal(-1, strgetchar('axb', 3))
- call assert_equal(-1, strgetchar('', 0))
- call assert_fails("let c=strgetchar([], 1)", 'E730:')
- call assert_fails("let c=strgetchar('axb', [])", 'E745:')
-endfunc
-
-func Test_strcharpart()
- call assert_equal('a', strcharpart('axb', 0, 1))
- call assert_equal('x', 'axb'->strcharpart(1, 1))
- call assert_equal('b', strcharpart('axb', 2, 1))
- call assert_equal('xb', strcharpart('axb', 1))
-
- call assert_equal('', strcharpart('axb', 1, 0))
- call assert_equal('', strcharpart('axb', 1, -1))
- call assert_equal('', strcharpart('axb', -1, 1))
- call assert_equal('', strcharpart('axb', -2, 2))
-
- call assert_equal('a', strcharpart('axb', -1, 2))
-
- call assert_equal('edit', "editor"[-10:3])
-endfunc
-
-func Test_getreg_empty_list()
- call assert_equal('', getreg('x'))
- call assert_equal([], getreg('x', 1, 1))
- let x = getreg('x', 1, 1)
- let y = x
- call add(x, 'foo')
- call assert_equal(['foo'], y)
- call assert_fails('call getreg([])', 'E730:')
-endfunc
-
-func Test_loop_over_null_list()
- let null_list = v:_null_list
- for i in null_list
- call assert_report('should not get here')
- endfor
-endfunc
-
-func Test_set_reg_null_list()
- call setreg('x', v:_null_list)
-endfunc
-
-func Test_special_char()
- " The failure is only visible using valgrind.
- call assert_fails('echo "\<C-">')
-endfunc
-
-func Test_option_value()
- " boolean
- set bri
- call assert_equal(1, &bri)
- set nobri
- call assert_equal(0, &bri)
-
- " number
- set ts=1
- call assert_equal(1, &ts)
- set ts=8
- call assert_equal(8, &ts)
-
- " string
- exe "set cedit=\<Esc>"
- call assert_equal("\<Esc>", &cedit)
- set cpo=
- call assert_equal("", &cpo)
- set cpo=abcdefi
- call assert_equal("abcdefi", &cpo)
- set cpo&vim
-endfunc
-
-function Test_printf_64bit()
- call assert_equal("123456789012345", printf('%d', 123456789012345))
-endfunc
-
-function Test_printf_spec_s()
- " number
- call assert_equal("1234567890", printf('%s', 1234567890))
-
- " string
- call assert_equal("abcdefgi", printf('%s', "abcdefgi"))
-
- " float
- call assert_equal("1.23", printf('%s', 1.23))
-
- " list
- let value = [1, 'two', ['three', 4]]
- call assert_equal(string(value), printf('%s', value))
-
- " dict
- let value = {'key1' : 'value1', 'key2' : ['list', 'value'], 'key3' : {'dict' : 'value'}}
- call assert_equal(string(value), printf('%s', value))
-
- " funcref
- call assert_equal('printf', printf('%s', 'printf'->function()))
-
- " partial
- call assert_equal(string(function('printf', ['%s'])), printf('%s', function('printf', ['%s'])))
-endfunc
-
-function Test_printf_spec_b()
- call assert_equal("0", printf('%b', 0))
- call assert_equal("00001100", printf('%08b', 12))
- call assert_equal("11111111", printf('%08b', 0xff))
- call assert_equal(" 1111011", printf('%10b', 123))
- call assert_equal("0001111011", printf('%010b', 123))
- call assert_equal(" 0b1111011", printf('%#10b', 123))
- call assert_equal("0B01111011", printf('%#010B', 123))
- call assert_equal("1001001100101100000001011010010", printf('%b', 1234567890))
- call assert_equal("11100000100100010000110000011011101111101111001", printf('%b', 123456789012345))
- call assert_equal("1111111111111111111111111111111111111111111111111111111111111111", printf('%b', -1))
-endfunc
-
-function Test_printf_misc()
- call assert_equal('123', printf('123'))
- call assert_fails("call printf('123', 3)", "E767:")
-
- call assert_equal('123', printf('%d', 123))
- call assert_equal('123', printf('%i', 123))
- call assert_equal('123', printf('%D', 123))
- call assert_equal('123', printf('%U', 123))
- call assert_equal('173', printf('%o', 123))
- call assert_equal('173', printf('%O', 123))
- call assert_equal('7b', printf('%x', 123))
- call assert_equal('7B', printf('%X', 123))
-
- call assert_equal('123', printf('%hd', 123))
- call assert_equal('-123', printf('%hd', -123))
- call assert_equal('-1', printf('%hd', 0xFFFF))
- call assert_equal('-1', printf('%hd', 0x1FFFFF))
-
- call assert_equal('123', printf('%hu', 123))
- call assert_equal('65413', printf('%hu', -123))
- call assert_equal('65535', printf('%hu', 0xFFFF))
- call assert_equal('65535', printf('%hu', 0x1FFFFF))
-
- call assert_equal('123', printf('%ld', 123))
- call assert_equal('-123', printf('%ld', -123))
- call assert_equal('65535', printf('%ld', 0xFFFF))
- call assert_equal('131071', printf('%ld', 0x1FFFF))
-
- call assert_equal('{', printf('%c', 123))
- call assert_equal('abc', printf('%s', 'abc'))
- call assert_equal('abc', printf('%S', 'abc'))
-
- call assert_equal('+123', printf('%+d', 123))
- call assert_equal('-123', printf('%+d', -123))
- call assert_equal('+123', printf('%+ d', 123))
- call assert_equal(' 123', printf('% d', 123))
- call assert_equal(' 123', printf('% d', 123))
- call assert_equal('-123', printf('% d', -123))
-
- call assert_equal('123', printf('%2d', 123))
- call assert_equal(' 123', printf('%6d', 123))
- call assert_equal('000123', printf('%06d', 123))
- call assert_equal('+00123', printf('%+06d', 123))
- call assert_equal(' 00123', printf('% 06d', 123))
- call assert_equal(' +123', printf('%+6d', 123))
- call assert_equal(' 123', printf('% 6d', 123))
- call assert_equal(' -123', printf('% 6d', -123))
-
- " Test left adjusted.
- call assert_equal('123 ', printf('%-6d', 123))
- call assert_equal('+123 ', printf('%-+6d', 123))
- call assert_equal(' 123 ', printf('%- 6d', 123))
- call assert_equal('-123 ', printf('%- 6d', -123))
-
- call assert_equal(' 00123', printf('%7.5d', 123))
- call assert_equal(' -00123', printf('%7.5d', -123))
- call assert_equal(' +00123', printf('%+7.5d', 123))
- " Precision field should not be used when combined with %0
- call assert_equal(' 00123', printf('%07.5d', 123))
- call assert_equal(' -00123', printf('%07.5d', -123))
-
- call assert_equal(' 123', printf('%*d', 5, 123))
- call assert_equal('123 ', printf('%*d', -5, 123))
- call assert_equal('00123', printf('%.*d', 5, 123))
- call assert_equal(' 123', printf('% *d', 5, 123))
- call assert_equal(' +123', printf('%+ *d', 5, 123))
-
- call assert_equal('foobar', printf('%.*s', 9, 'foobar'))
- call assert_equal('foo', printf('%.*s', 3, 'foobar'))
- call assert_equal('', printf('%.*s', 0, 'foobar'))
- call assert_equal('foobar', printf('%.*s', -1, 'foobar'))
-
- " Simple quote (thousand grouping char) is ignored.
- call assert_equal('+00123456', printf("%+'09d", 123456))
-
- " Unrecognized format specifier kept as-is.
- call assert_equal('_123', printf("%_%d", 123))
-
- " Test alternate forms.
- call assert_equal('0x7b', printf('%#x', 123))
- call assert_equal('0X7B', printf('%#X', 123))
- call assert_equal('0173', printf('%#o', 123))
- call assert_equal('0173', printf('%#O', 123))
- call assert_equal('abc', printf('%#s', 'abc'))
- call assert_equal('abc', printf('%#S', 'abc'))
- call assert_equal(' 0173', printf('%#6o', 123))
- call assert_equal(' 00173', printf('%#6.5o', 123))
- call assert_equal(' 0173', printf('%#6.2o', 123))
- call assert_equal(' 0173', printf('%#6.2o', 123))
- call assert_equal('0173', printf('%#2.2o', 123))
-
- call assert_equal(' 00123', printf('%6.5d', 123))
- call assert_equal(' 0007b', printf('%6.5x', 123))
-
- call assert_equal('123', printf('%.2d', 123))
- call assert_equal('0123', printf('%.4d', 123))
- call assert_equal('0000000123', printf('%.10d', 123))
- call assert_equal('123', printf('%.0d', 123))
-
- call assert_equal('abc', printf('%2s', 'abc'))
- call assert_equal('abc', printf('%2S', 'abc'))
- call assert_equal('abc', printf('%.4s', 'abc'))
- call assert_equal('abc', printf('%.4S', 'abc'))
- call assert_equal('ab', printf('%.2s', 'abc'))
- call assert_equal('ab', printf('%.2S', 'abc'))
- call assert_equal('', printf('%.0s', 'abc'))
- call assert_equal('', printf('%.s', 'abc'))
- call assert_equal(' abc', printf('%4s', 'abc'))
- call assert_equal(' abc', printf('%4S', 'abc'))
- call assert_equal('0abc', printf('%04s', 'abc'))
- call assert_equal('0abc', printf('%04S', 'abc'))
- call assert_equal('abc ', printf('%-4s', 'abc'))
- call assert_equal('abc ', printf('%-4S', 'abc'))
-
- call assert_equal('ðŸ', printf('%.2S', 'ðŸðŸ'))
- call assert_equal('', printf('%.1S', 'ðŸðŸ'))
-
- call assert_equal('[ ã‚ã„ã†]', printf('[%10.6S]', 'ã‚ã„ã†ãˆãŠ'))
- call assert_equal('[ ã‚ã„ã†ãˆ]', printf('[%10.8S]', 'ã‚ã„ã†ãˆãŠ'))
- call assert_equal('[ã‚ã„ã†ãˆãŠ]', printf('[%10.10S]', 'ã‚ã„ã†ãˆãŠ'))
- call assert_equal('[ã‚ã„ã†ãˆãŠ]', printf('[%10.12S]', 'ã‚ã„ã†ãˆãŠ'))
-
- call assert_equal('ã‚ã„ã†', printf('%S', 'ã‚ã„ã†'))
- call assert_equal('ã‚ã„ã†', printf('%#S', 'ã‚ã„ã†'))
-
- call assert_equal('ã‚b', printf('%2S', 'ã‚b'))
- call assert_equal('ã‚b', printf('%.4S', 'ã‚b'))
- call assert_equal('ã‚', printf('%.2S', 'ã‚b'))
- call assert_equal(' ã‚b', printf('%4S', 'ã‚b'))
- call assert_equal('0ã‚b', printf('%04S', 'ã‚b'))
- call assert_equal('ã‚b ', printf('%-4S', 'ã‚b'))
- call assert_equal('ã‚ ', printf('%-4.2S', 'ã‚b'))
-
- call assert_equal('aã„', printf('%2S', 'aã„'))
- call assert_equal('aã„', printf('%.4S', 'aã„'))
- call assert_equal('a', printf('%.2S', 'aã„'))
- call assert_equal(' aã„', printf('%4S', 'aã„'))
- call assert_equal('0aã„', printf('%04S', 'aã„'))
- call assert_equal('aã„ ', printf('%-4S', 'aã„'))
- call assert_equal('a ', printf('%-4.2S', 'aã„'))
-
- call assert_equal('[ã‚ã„ã†]', printf('[%05S]', 'ã‚ã„ã†'))
- call assert_equal('[ã‚ã„ã†]', printf('[%06S]', 'ã‚ã„ã†'))
- call assert_equal('[0ã‚ã„ã†]', printf('[%07S]', 'ã‚ã„ã†'))
-
- call assert_equal('[ã‚iã†]', printf('[%05S]', 'ã‚iã†'))
- call assert_equal('[0ã‚iã†]', printf('[%06S]', 'ã‚iã†'))
- call assert_equal('[00ã‚iã†]', printf('[%07S]', 'ã‚iã†'))
-
- call assert_equal('[0ã‚ã„]', printf('[%05.4S]', 'ã‚ã„ã†'))
- call assert_equal('[00ã‚ã„]', printf('[%06.4S]', 'ã‚ã„ã†'))
- call assert_equal('[000ã‚ã„]', printf('[%07.4S]', 'ã‚ã„ã†'))
-
- call assert_equal('[00ã‚i]', printf('[%05.4S]', 'ã‚iã†'))
- call assert_equal('[000ã‚i]', printf('[%06.4S]', 'ã‚iã†'))
- call assert_equal('[0000ã‚i]', printf('[%07.4S]', 'ã‚iã†'))
-
- call assert_equal('[0ã‚ã„]', printf('[%05.5S]', 'ã‚ã„ã†'))
- call assert_equal('[00ã‚ã„]', printf('[%06.5S]', 'ã‚ã„ã†'))
- call assert_equal('[000ã‚ã„]', printf('[%07.5S]', 'ã‚ã„ã†'))
-
- call assert_equal('[ã‚iã†]', printf('[%05.5S]', 'ã‚iã†'))
- call assert_equal('[0ã‚iã†]', printf('[%06.5S]', 'ã‚iã†'))
- call assert_equal('[00ã‚iã†]', printf('[%07.5S]', 'ã‚iã†'))
-
- call assert_equal('[0000000000]', printf('[%010.0S]', 'ã‚ã„ã†'))
- call assert_equal('[0000000000]', printf('[%010.1S]', 'ã‚ã„ã†'))
- call assert_equal('[00000000ã‚]', printf('[%010.2S]', 'ã‚ã„ã†'))
- call assert_equal('[00000000ã‚]', printf('[%010.3S]', 'ã‚ã„ã†'))
- call assert_equal('[000000ã‚ã„]', printf('[%010.4S]', 'ã‚ã„ã†'))
- call assert_equal('[000000ã‚ã„]', printf('[%010.5S]', 'ã‚ã„ã†'))
- call assert_equal('[0000ã‚ã„ã†]', printf('[%010.6S]', 'ã‚ã„ã†'))
- call assert_equal('[0000ã‚ã„ã†]', printf('[%010.7S]', 'ã‚ã„ã†'))
-
- call assert_equal('[0000000000]', printf('[%010.1S]', 'ã‚iã†'))
- call assert_equal('[00000000ã‚]', printf('[%010.2S]', 'ã‚iã†'))
- call assert_equal('[0000000ã‚i]', printf('[%010.3S]', 'ã‚iã†'))
- call assert_equal('[0000000ã‚i]', printf('[%010.4S]', 'ã‚iã†'))
- call assert_equal('[00000ã‚iã†]', printf('[%010.5S]', 'ã‚iã†'))
- call assert_equal('[00000ã‚iã†]', printf('[%010.6S]', 'ã‚iã†'))
- call assert_equal('[00000ã‚iã†]', printf('[%010.7S]', 'ã‚iã†'))
-
- call assert_equal('1%', printf('%d%%', 1))
-endfunc
-
-function Test_printf_float()
- call assert_equal('1.000000', printf('%f', 1))
- call assert_equal('1.230000', printf('%f', 1.23))
- call assert_equal('1.230000', printf('%F', 1.23))
- call assert_equal('9999999.9', printf('%g', 9999999.9))
- call assert_equal('9999999.9', printf('%G', 9999999.9))
- call assert_equal('1.00000001e7', printf('%.8g', 10000000.1))
- call assert_equal('1.00000001E7', printf('%.8G', 10000000.1))
- call assert_equal('1.230000e+00', printf('%e', 1.23))
- call assert_equal('1.230000E+00', printf('%E', 1.23))
- call assert_equal('1.200000e-02', printf('%e', 0.012))
- call assert_equal('-1.200000e-02', printf('%e', -0.012))
- call assert_equal('0.33', printf('%.2f', 1.0/3.0))
- call assert_equal(' 0.33', printf('%6.2f', 1.0/3.0))
- call assert_equal(' -0.33', printf('%6.2f', -1.0/3.0))
- call assert_equal('000.33', printf('%06.2f', 1.0/3.0))
- call assert_equal('-00.33', printf('%06.2f', -1.0/3.0))
- call assert_equal('-00.33', printf('%+06.2f', -1.0/3.0))
- call assert_equal('+00.33', printf('%+06.2f', 1.0/3.0))
- call assert_equal(' 00.33', printf('% 06.2f', 1.0/3.0))
- call assert_equal('000.33', printf('%06.2g', 1.0/3.0))
- call assert_equal('-00.33', printf('%06.2g', -1.0/3.0))
- call assert_equal('0.33', printf('%3.2f', 1.0/3.0))
- call assert_equal('003.33e-01', printf('%010.2e', 1.0/3.0))
- call assert_equal(' 03.33e-01', printf('% 010.2e', 1.0/3.0))
- call assert_equal('+03.33e-01', printf('%+010.2e', 1.0/3.0))
- call assert_equal('-03.33e-01', printf('%010.2e', -1.0/3.0))
-
- " When precision is 0, the dot should be omitted.
- call assert_equal(' 2', printf('%3.f', 7.0/3.0))
- call assert_equal(' 2', printf('%3.g', 7.0/3.0))
- call assert_equal(' 2e+00', printf('%7.e', 7.0/3.0))
-
- " Float zero can be signed.
- call assert_equal('+0.000000', printf('%+f', 0.0))
- call assert_equal('0.000000', printf('%f', 1.0/(1.0/0.0)))
- call assert_equal('-0.000000', printf('%f', 1.0/(-1.0/0.0)))
- call assert_equal('0.0', printf('%s', 1.0/(1.0/0.0)))
- call assert_equal('-0.0', printf('%s', 1.0/(-1.0/0.0)))
- call assert_equal('0.0', printf('%S', 1.0/(1.0/0.0)))
- call assert_equal('-0.0', printf('%S', 1.0/(-1.0/0.0)))
-
- " Float infinity can be signed.
- call assert_equal('inf', printf('%f', 1.0/0.0))
- call assert_equal('-inf', printf('%f', -1.0/0.0))
- call assert_equal('inf', printf('%g', 1.0/0.0))
- call assert_equal('-inf', printf('%g', -1.0/0.0))
- call assert_equal('inf', printf('%e', 1.0/0.0))
- call assert_equal('-inf', printf('%e', -1.0/0.0))
- call assert_equal('INF', printf('%F', 1.0/0.0))
- call assert_equal('-INF', printf('%F', -1.0/0.0))
- call assert_equal('INF', printf('%E', 1.0/0.0))
- call assert_equal('-INF', printf('%E', -1.0/0.0))
- call assert_equal('INF', printf('%E', 1.0/0.0))
- call assert_equal('-INF', printf('%G', -1.0/0.0))
- call assert_equal('+inf', printf('%+f', 1.0/0.0))
- call assert_equal('-inf', printf('%+f', -1.0/0.0))
- call assert_equal(' inf', printf('% f', 1.0/0.0))
- call assert_equal(' inf', printf('%6f', 1.0/0.0))
- call assert_equal(' -inf', printf('%6f', -1.0/0.0))
- call assert_equal(' inf', printf('%6g', 1.0/0.0))
- call assert_equal(' -inf', printf('%6g', -1.0/0.0))
- call assert_equal(' +inf', printf('%+6f', 1.0/0.0))
- call assert_equal(' inf', printf('% 6f', 1.0/0.0))
- call assert_equal(' +inf', printf('%+06f', 1.0/0.0))
- call assert_equal('inf ', printf('%-6f', 1.0/0.0))
- call assert_equal('-inf ', printf('%-6f', -1.0/0.0))
- call assert_equal('+inf ', printf('%-+6f', 1.0/0.0))
- call assert_equal(' inf ', printf('%- 6f', 1.0/0.0))
- call assert_equal('-INF ', printf('%-6F', -1.0/0.0))
- call assert_equal('+INF ', printf('%-+6F', 1.0/0.0))
- call assert_equal(' INF ', printf('%- 6F', 1.0/0.0))
- call assert_equal('INF ', printf('%-6G', 1.0/0.0))
- call assert_equal('-INF ', printf('%-6G', -1.0/0.0))
- call assert_equal('INF ', printf('%-6E', 1.0/0.0))
- call assert_equal('-INF ', printf('%-6E', -1.0/0.0))
- call assert_equal("str2float('inf')", printf('%s', 1.0/0.0))
- call assert_equal("-str2float('inf')", printf('%s', -1.0/0.0))
-
- " Test special case where max precision is truncated at 340.
- call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.330f', 1.0))
- call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.340f', 1.0))
- call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.350f', 1.0))
-
- " Float nan (not a number) has no sign.
- call assert_equal('nan', printf('%f', sqrt(-1.0)))
- call assert_equal('nan', printf('%f', 0.0/0.0))
- call assert_equal('nan', printf('%f', -0.0/0.0))
- call assert_equal('nan', printf('%g', 0.0/0.0))
- call assert_equal('nan', printf('%e', 0.0/0.0))
- call assert_equal('NAN', printf('%F', 0.0/0.0))
- call assert_equal('NAN', printf('%G', 0.0/0.0))
- call assert_equal('NAN', printf('%E', 0.0/0.0))
- call assert_equal('NAN', printf('%F', -0.0/0.0))
- call assert_equal('NAN', printf('%G', -0.0/0.0))
- call assert_equal('NAN', printf('%E', -0.0/0.0))
- call assert_equal(' nan', printf('%6f', 0.0/0.0))
- call assert_equal(' nan', printf('%06f', 0.0/0.0))
- call assert_equal('nan ', printf('%-6f', 0.0/0.0))
- call assert_equal('nan ', printf('%- 6f', 0.0/0.0))
- call assert_equal("str2float('nan')", printf('%s', 0.0/0.0))
- call assert_equal("str2float('nan')", printf('%s', -0.0/0.0))
- call assert_equal("str2float('nan')", printf('%S', 0.0/0.0))
- call assert_equal("str2float('nan')", printf('%S', -0.0/0.0))
-
- call assert_fails('echo printf("%f", "a")', 'E807:')
-endfunc
-
-function Test_printf_errors()
- call assert_fails('echo printf("%d", {})', 'E728:')
- call assert_fails('echo printf("%d", [])', 'E745:')
- call assert_fails('echo printf("%d", 1, 2)', 'E767:')
- call assert_fails('echo printf("%*d", 1)', 'E766:')
- call assert_fails('echo printf("%s")', 'E766:')
- if has('float')
- call assert_fails('echo printf("%d", 1.2)', 'E805:')
- call assert_fails('echo printf("%f")')
- endif
-endfunc
-
-function Test_max_min_errors()
- call assert_fails('call max(v:true)', 'E712:')
- call assert_fails('call max(v:true)', 'max()')
- call assert_fails('call min(v:true)', 'E712:')
- call assert_fails('call min(v:true)', 'min()')
-endfunc
-
-func Test_function_with_funcref()
- let s:f = function('type')
- let s:fref = function(s:f)
- call assert_equal(v:t_string, s:fref('x'))
- call assert_fails("call function('s:f')", 'E700:')
-
- call assert_fails("call function('foo()')", 'E475:')
- call assert_fails("call function('foo()')", 'foo()')
- call assert_fails("function('')", 'E129:')
-
- let Len = {s -> strlen(s)}
- call assert_equal(6, Len('foobar'))
- let name = string(Len)
- " can evaluate "function('<lambda>99')"
- call execute('let Ref = ' .. name)
- call assert_equal(4, Ref('text'))
-endfunc
-
-func Test_funcref()
- func! One()
- return 1
- endfunc
- let OneByName = function('One')
- let OneByRef = funcref('One')
- func! One()
- return 2
- endfunc
- call assert_equal(2, OneByName())
- call assert_equal(1, OneByRef())
- let OneByRef = 'One'->funcref()
- call assert_equal(2, OneByRef())
- call assert_fails('echo funcref("{")', 'E475:')
- let OneByRef = funcref("One", repeat(["foo"], 20))
- call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:')
- call assert_fails('echo function("min") =~ function("min")', 'E694:')
-endfunc
-
-" Test for calling function() and funcref() outside of a Vim script context.
-func Test_function_outside_script()
- let cleanup =<< trim END
- call writefile([execute('messages')], 'Xtest.out')
- qall
- END
- call writefile(cleanup, 'Xverify.vim')
- call RunVim([], [], "-c \"echo function('s:abc')\" -S Xverify.vim")
- call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
- call RunVim([], [], "-c \"echo funcref('s:abc')\" -S Xverify.vim")
- call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
- call delete('Xtest.out')
- call delete('Xverify.vim')
-endfunc
-
-func Test_setmatches()
- hi def link 1 Comment
- hi def link 2 PreProc
- let set = [{"group": 1, "pattern": 2, "id": 3, "priority": 4}]
- let exp = [{"group": '1', "pattern": '2', "id": 3, "priority": 4}]
- if has('conceal')
- let set[0]['conceal'] = 5
- let exp[0]['conceal'] = '5'
- endif
- eval set->setmatches()
- call assert_equal(exp, getmatches())
- call assert_fails('let m = setmatches([], [])', 'E745:')
-endfunc
-
-func Test_empty_concatenate()
- call assert_equal('b', 'a'[4:0] . 'b')
- call assert_equal('b', 'b' . 'a'[4:0])
-endfunc
-
-func Test_broken_number()
- let X = 'bad'
- call assert_fails('echo 1X', 'E15:')
- call assert_fails('echo 0b1X', 'E15:')
- call assert_fails('echo 0b12', 'E15:')
- call assert_fails('echo 0x1X', 'E15:')
- call assert_fails('echo 011X', 'E15:')
- call assert_equal(2, str2nr('2a'))
- call assert_fails('inoremap <Char-0b1z> b', 'E474:')
-endfunc
-
-func Test_eval_after_if()
- let s:val = ''
- func SetVal(x)
- let s:val ..= a:x
- endfunc
- if 0 | eval SetVal('a') | endif | call SetVal('b')
- call assert_equal('b', s:val)
-endfunc
-
-func Test_divide_by_zero()
- " only tests that this doesn't crash, the result is not important
- echo 0 / 0
- echo 0 / 0 / -1
-endfunc
-
-" Test for command-line completion of expressions
-func Test_expr_completion()
- CheckFeature cmdline_compl
- for cmd in [
- \ 'let a = ',
- \ 'const a = ',
- \ 'if',
- \ 'elseif',
- \ 'while',
- \ 'for',
- \ 'echo',
- \ 'echon',
- \ 'execute',
- \ 'echomsg',
- \ 'echoerr',
- \ 'call',
- \ 'return',
- \ 'cexpr',
- \ 'caddexpr',
- \ 'cgetexpr',
- \ 'lexpr',
- \ 'laddexpr',
- \ 'lgetexpr']
- call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"' . cmd . ' getline(', getreg(':'))
- endfor
-
- " completion for the expression register
- call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt')
- call assert_equal('"float2nr("', @=)
-
- " completion for window local variables
- let w:wvar1 = 10
- let w:wvar2 = 10
- call feedkeys(":echo w:wvar\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo w:wvar1 w:wvar2', @:)
- unlet w:wvar1 w:wvar2
-
- " completion for tab local variables
- let t:tvar1 = 10
- let t:tvar2 = 10
- call feedkeys(":echo t:tvar\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo t:tvar1 t:tvar2', @:)
- unlet t:tvar1 t:tvar2
-
- " completion for variables
- let g:tvar1 = 1
- let g:tvar2 = 2
- call feedkeys(":let g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"let g:tvar1 g:tvar2', @:)
- " completion for variables after a ||
- call feedkeys(":echo 1 || g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo 1 || g:tvar1 g:tvar2', @:)
-
- " completion for options
- call feedkeys(":echo &compat\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo &compatible', @:)
- call feedkeys(":echo 1 && &compat\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo 1 && &compatible', @:)
- call feedkeys(":echo &g:equala\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo &g:equalalways', @:)
-
- " completion for string
- call feedkeys(":echo \"Hello\\ World\"\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"echo \"Hello\\ World\"\<C-A>", @:)
- call feedkeys(":echo 'Hello World'\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"echo 'Hello World'\<C-A>", @:)
-
- " completion for command after a |
- call feedkeys(":echo 'Hello' | cwin\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"echo 'Hello' | cwindow", @:)
-
- " completion for environment variable
- let $X_VIM_TEST_COMPLETE_ENV = 'foo'
- call feedkeys(":let $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('"let $X_VIM_TEST_COMPLETE_ENV', @:)
- unlet $X_VIM_TEST_COMPLETE_ENV
-endfunc
-
-" Test for errors in expression evaluation
-func Test_expr_eval_error()
- call assert_fails("let i = 'abc' . []", 'E730:')
- call assert_fails("let l = [] + 10", 'E745:')
- call assert_fails("let v = 10 + []", 'E745:')
- call assert_fails("let v = 10 / []", 'E745:')
- call assert_fails("let v = -{}", 'E728:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expr_utf8.vim b/src/nvim/testdir/test_expr_utf8.vim
deleted file mode 100644
index fad725d2e5..0000000000
--- a/src/nvim/testdir/test_expr_utf8.vim
+++ /dev/null
@@ -1,34 +0,0 @@
-" Tests for expressions using utf-8.
-
-func Test_strgetchar()
- call assert_equal(char2nr('á'), strgetchar('áxb', 0))
- call assert_equal(char2nr('x'), strgetchar('áxb', 1))
-
- call assert_equal(char2nr('a'), strgetchar('àxb', 0))
- call assert_equal(char2nr('̀'), strgetchar('àxb', 1))
- call assert_equal(char2nr('x'), strgetchar('àxb', 2))
-
- call assert_equal(char2nr('ã‚'), strgetchar('ã‚aã„', 0))
- call assert_equal(char2nr('a'), strgetchar('ã‚aã„', 1))
- call assert_equal(char2nr('ã„'), strgetchar('ã‚aã„', 2))
-endfunc
-
-func Test_strcharpart()
- call assert_equal('áxb', strcharpart('áxb', 0))
- call assert_equal('á', strcharpart('áxb', 0, 1))
- call assert_equal('x', strcharpart('áxb', 1, 1))
-
- call assert_equal('ã„ã†eãŠ', strcharpart('ã‚ã„ã†eãŠ', 1))
- call assert_equal('ã„', strcharpart('ã‚ã„ã†eãŠ', 1, 1))
- call assert_equal('ã„ã†', strcharpart('ã‚ã„ã†eãŠ', 1, 2))
- call assert_equal('ã„ã†e', strcharpart('ã‚ã„ã†eãŠ', 1, 3))
- call assert_equal('ã„ã†eãŠ', strcharpart('ã‚ã„ã†eãŠ', 1, 4))
- call assert_equal('eãŠ', strcharpart('ã‚ã„ã†eãŠ', 3))
- call assert_equal('e', strcharpart('ã‚ã„ã†eãŠ', 3, 1))
-
- call assert_equal('ã‚', strcharpart('ã‚ã„ã†eãŠ', -3, 4))
-
- call assert_equal('a', strcharpart('àxb', 0, 1))
- call assert_equal('̀', strcharpart('àxb', 1, 1))
- call assert_equal('x', strcharpart('àxb', 2, 1))
-endfunc
diff --git a/src/nvim/testdir/test_file_perm.vim b/src/nvim/testdir/test_file_perm.vim
deleted file mode 100644
index 1cb09e8647..0000000000
--- a/src/nvim/testdir/test_file_perm.vim
+++ /dev/null
@@ -1,30 +0,0 @@
-" Test getting and setting file permissions.
-
-func Test_file_perm()
- call assert_equal('', getfperm('Xtest'))
- call assert_equal(0, 'Xtest'->setfperm('r--------'))
-
- call writefile(['one'], 'Xtest')
- call assert_true(len('Xtest'->getfperm()) == 9)
-
- call assert_equal(1, setfperm('Xtest', 'rwx------'))
- if has('win32')
- call assert_equal('rw-rw-rw-', getfperm('Xtest'))
- else
- call assert_equal('rwx------', getfperm('Xtest'))
- endif
-
- call assert_equal(1, setfperm('Xtest', 'r--r--r--'))
- call assert_equal('r--r--r--', getfperm('Xtest'))
-
- call assert_fails("setfperm('Xtest', '---')")
-
- call assert_equal(1, setfperm('Xtest', 'rwx------'))
- call delete('Xtest')
-
- call assert_fails("call setfperm(['Xfile'], 'rw-rw-rw-')", 'E730:')
- call assert_fails("call setfperm('Xfile', [])", 'E730:')
- call assert_fails("call setfperm('Xfile', 'rwxrwxrwxrw')", 'E475:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_file_size.vim b/src/nvim/testdir/test_file_size.vim
deleted file mode 100644
index 3e78a7b23c..0000000000
--- a/src/nvim/testdir/test_file_size.vim
+++ /dev/null
@@ -1,58 +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 writes its cksum to test.out.
-"
-" 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.
-func Test_File_Size()
- if !executable('cksum')
- return
- endif
-
- new
- set fileformat=unix undolevels=-1
- for i in range(1, 2000000, 100)
- call append(i, range(i, i + 99))
- endfor
-
- 1delete
- w! Xtest
- let res = systemlist('cksum Xtest')[0]
- let res = substitute(res, "\r", "", "")
- call assert_equal('3678979763 14888896 Xtest', res)
-
- enew!
- call delete('Xtest')
- set fileformat& undolevels&
-endfunc
-
-" Test for writing and reading a file of over 100 Kbyte
-func Test_File_Read_Write()
- enew!
-
- " Create a file with the following contents
- " 1 line: "This is the start"
- " 3001 lines: "This is the leader"
- " 1 line: "This is the middle"
- " 3001 lines: "This is the trailer"
- " 1 line: "This is the end"
- call append(0, "This is the start")
- call append(1, repeat(["This is the leader"], 3001))
- call append(3002, "This is the middle")
- call append(3003, repeat(["This is the trailer"], 3001))
- call append(6004, "This is the end")
-
- write! Xtest
- enew!
- edit! Xtest
-
- call assert_equal("This is the start", getline(1))
- call assert_equal("This is the middle", getline(3003))
- call assert_equal("This is the end", getline(6005))
-
- enew!
- call delete("Xtest")
-endfunc
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
deleted file mode 100644
index fef0eb732f..0000000000
--- a/src/nvim/testdir/test_filechanged.vim
+++ /dev/null
@@ -1,274 +0,0 @@
-" Tests for when a file was changed outside of Vim.
-
-source check.vim
-
-func Test_FileChangedShell_reload()
- CheckUnix
-
- augroup testreload
- au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
- augroup END
- new Xchanged_r
- call setline(1, 'reload this')
- write
- " Need to wait until the timestamp would change by at least a second.
- sleep 2
- silent !echo 'extra line' >>Xchanged_r
- checktime
- call assert_equal('changed', g:reason)
- call assert_equal(2, line('$'))
- call assert_equal('extra line', getline(2))
-
- " Only triggers once
- let g:reason = ''
- checktime
- call assert_equal('', g:reason)
-
- " When deleted buffer is not reloaded
- silent !rm Xchanged_r
- let g:reason = ''
- checktime
- call assert_equal('deleted', g:reason)
- call assert_equal(2, line('$'))
- call assert_equal('extra line', getline(2))
-
- " When recreated buffer is reloaded
- call setline(1, 'buffer is changed')
- silent !echo 'new line' >>Xchanged_r
- let g:reason = ''
- checktime
- call assert_equal('conflict', g:reason)
- call assert_equal(1, line('$'))
- call assert_equal('new line', getline(1))
-
- " Only mode changed
- silent !chmod +x Xchanged_r
- let g:reason = ''
- checktime
- call assert_equal('mode', g:reason)
- call assert_equal(1, line('$'))
- call assert_equal('new line', getline(1))
-
- " Only time changed
- sleep 2
- silent !touch Xchanged_r
- let g:reason = ''
- checktime
- call assert_equal('time', g:reason)
- call assert_equal(1, line('$'))
- call assert_equal('new line', getline(1))
-
- if has('persistent_undo')
- " With an undo file the reload can be undone and a change before the
- " reload.
- set undofile
- call setline(2, 'before write')
- write
- call setline(2, 'after write')
- sleep 2
- silent !echo 'different line' >>Xchanged_r
- let g:reason = ''
- checktime
- call assert_equal('conflict', g:reason)
- call assert_equal(3, line('$'))
- call assert_equal('before write', getline(2))
- call assert_equal('different line', getline(3))
- " undo the reload
- undo
- call assert_equal(2, line('$'))
- call assert_equal('after write', getline(2))
- " undo the change before reload
- undo
- call assert_equal(2, line('$'))
- call assert_equal('before write', getline(2))
-
- set noundofile
- endif
-
- au! testreload
- bwipe!
- call delete(undofile('Xchanged_r'))
- call delete('Xchanged_r')
-endfunc
-
-func Test_FileChangedShell_edit()
- CheckUnix
-
- new Xchanged_r
- call setline(1, 'reload this')
- set fileformat=unix
- write
-
- " File format changed, reload (content only, no 'ff' etc)
- augroup testreload
- au!
- au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
- augroup END
- call assert_equal(&fileformat, 'unix')
- sleep 10m " make the test less flaky in Nvim
- call writefile(["line1\r", "line2\r"], 'Xchanged_r')
- let g:reason = ''
- checktime
- call assert_equal('changed', g:reason)
- call assert_equal(&fileformat, 'unix')
- call assert_equal("line1\r", getline(1))
- call assert_equal("line2\r", getline(2))
- %s/\r
- write
-
- " File format changed, reload with 'ff', etc
- augroup testreload
- au!
- au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'edit'
- augroup END
- call assert_equal(&fileformat, 'unix')
- sleep 10m " make the test less flaky in Nvim
- call writefile(["line1\r", "line2\r"], 'Xchanged_r')
- let g:reason = ''
- checktime
- call assert_equal('changed', g:reason)
- call assert_equal(&fileformat, 'dos')
- call assert_equal('line1', getline(1))
- call assert_equal('line2', getline(2))
- set fileformat=unix
- write
-
- au! testreload
- bwipe!
- call delete(undofile('Xchanged_r'))
- call delete('Xchanged_r')
-endfunc
-
-func Test_FileChangedShell_edit_dialog()
- " requires a UI to be active
- throw 'Skipped: use test/functional/legacy/filechanged_spec.lua'
- CheckNotGui
- CheckUnix " Using low level feedkeys() does not work on MS-Windows.
-
- new Xchanged_r
- call setline(1, 'reload this')
- set fileformat=unix
- write
-
- " File format changed, reload (content only) via prompt
- augroup testreload
- au!
- au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
- augroup END
- call assert_equal(&fileformat, 'unix')
- call writefile(["line1\r", "line2\r"], 'Xchanged_r')
- let g:reason = ''
- call feedkeys('L', 'L') " load file content only
- checktime
- call assert_equal('changed', g:reason)
- call assert_equal(&fileformat, 'unix')
- call assert_equal("line1\r", getline(1))
- call assert_equal("line2\r", getline(2))
- %s/\r
- write
-
- " File format changed, reload (file and options) via prompt
- augroup testreload
- au!
- au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
- augroup END
- call assert_equal(&fileformat, 'unix')
- call writefile(["line1\r", "line2\r"], 'Xchanged_r')
- let g:reason = ''
- call feedkeys('a', 'L') " load file content and options
- checktime
- call assert_equal('changed', g:reason)
- call assert_equal(&fileformat, 'dos')
- call assert_equal("line1", getline(1))
- call assert_equal("line2", getline(2))
- set fileformat=unix
- write
-
- au! testreload
- bwipe!
- call delete(undofile('Xchanged_r'))
- call delete('Xchanged_r')
-endfunc
-
-func Test_file_changed_dialog()
- " requires a UI to be active
- throw 'Skipped: use test/functional/legacy/filechanged_spec.lua'
- CheckUnix
- CheckNotGui
- au! FileChangedShell
-
- new Xchanged_d
- call setline(1, 'reload this')
- write
- " Need to wait until the timestamp would change by at least a second.
- sleep 2
- silent !echo 'extra line' >>Xchanged_d
- call feedkeys('L', 'L')
- checktime
- call assert_match('W11:', v:warningmsg)
- call assert_equal(2, line('$'))
- call assert_equal('reload this', getline(1))
- call assert_equal('extra line', getline(2))
-
- " delete buffer, only shows an error, no prompt
- silent !rm Xchanged_d
- checktime
- call assert_match('E211:', v:warningmsg)
- call assert_equal(2, line('$'))
- call assert_equal('extra line', getline(2))
- let v:warningmsg = 'empty'
-
- " change buffer, recreate the file and reload
- call setline(1, 'buffer is changed')
- silent !echo 'new line' >Xchanged_d
- call feedkeys('L', 'L')
- checktime
- call assert_match('W12:', v:warningmsg)
- call assert_equal(1, line('$'))
- call assert_equal('new line', getline(1))
-
- " Only mode changed, reload
- silent !chmod +x Xchanged_d
- call feedkeys('L', 'L')
- checktime
- call assert_match('W16:', v:warningmsg)
- call assert_equal(1, line('$'))
- call assert_equal('new line', getline(1))
-
- " Only time changed, no prompt
- sleep 2
- silent !touch Xchanged_d
- let v:warningmsg = ''
- checktime Xchanged_d
- call assert_equal('', v:warningmsg)
- call assert_equal(1, line('$'))
- call assert_equal('new line', getline(1))
-
- " File created after starting to edit it
- call delete('Xchanged_d')
- new Xchanged_d
- call writefile(['one'], 'Xchanged_d')
- call feedkeys('L', 'L')
- checktime Xchanged_d
- call assert_equal(['one'], getline(1, '$'))
- close!
-
- bwipe!
- call delete('Xchanged_d')
-endfunc
-
-" Test for editing a new buffer from a FileChangedShell autocmd
-func Test_FileChangedShell_newbuf()
- call writefile(['one', 'two'], 'Xfile')
- new Xfile
- augroup testnewbuf
- autocmd FileChangedShell * enew
- augroup END
- sleep 10m " make the test less flaky in Nvim
- call writefile(['red'], 'Xfile')
- call assert_fails('checktime', 'E811:')
- au! testnewbuf
- call delete('Xfile')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim
deleted file mode 100644
index 8d727a68c4..0000000000
--- a/src/nvim/testdir/test_fileformat.vim
+++ /dev/null
@@ -1,309 +0,0 @@
-" Test behavior of fileformat after bwipeout of last buffer
-func Test_fileformat_after_bw()
- bwipeout
- set fileformat&
- if &fileformat == 'dos'
- let test_fileformats = 'unix'
- elseif &fileformat == 'unix'
- let test_fileformats = 'mac'
- else " must be mac
- let test_fileformats = 'dos'
- endif
- exec 'set fileformats='.test_fileformats
- bwipeout!
- call assert_equal(test_fileformats, &fileformat)
- set fileformats&
-endfunc
-
-func Test_fileformat_autocommand()
- let filecnt = ["", "foobar\<CR>", "eins\<CR>", "\<CR>", "zwei\<CR>", "drei", "vier", "fünf", ""]
- let ffs = &ffs
- call writefile(filecnt, 'Xfile', 'b')
- au BufReadPre Xfile set ffs=dos ff=dos
- new Xfile
- call assert_equal('dos', &l:ff)
- call assert_equal('dos', &ffs)
-
- " cleanup
- call delete('Xfile')
- let &ffs = ffs
- au! BufReadPre Xfile
- bw!
-endfunc
-
-func Test_fileformat_nomodifiable()
- new
- setlocal nomodifiable
-
- call assert_fails('set fileformat=latin1', 'E21:')
-
- bw
-endfunc
-
-" Convert the contents of a file into a literal string
-func s:file2str(fname)
- let b = readfile(a:fname, 'B')
- let s = ''
- for c in b
- let s .= nr2char(c)
- endfor
- return s
-endfunc
-
-" Concatenate the contents of files 'f1' and 'f2' and create 'destfile'
-func s:concat_files(f1, f2, destfile)
- let b1 = readfile(a:f1, 'B')
- let b2 = readfile(a:f2, 'B')
- let b3 = b1 + b2
- call writefile(b3, a:destfile)
-endfun
-
-" Test for a lot of variations of the 'fileformats' option
-func Test_fileformats()
- " create three test files, one in each format
- call writefile(['unix', 'unix'], 'XXUnix')
- call writefile(["dos\r", "dos\r"], 'XXDos')
- call writefile(["mac\rmac\r"], 'XXMac', 'b')
- " create a file with no End Of Line
- call writefile(["noeol"], 'XXEol', 'b')
- " create mixed format files
- call s:concat_files('XXUnix', 'XXDos', 'XXUxDs')
- call s:concat_files('XXUnix', 'XXMac', 'XXUxMac')
- call s:concat_files('XXDos', 'XXMac', 'XXDosMac')
- call s:concat_files('XXMac', 'XXEol', 'XXMacEol')
- call s:concat_files('XXUxDs', 'XXMac', 'XXUxDsMc')
-
- new
-
- " Test 1: try reading and writing with 'fileformats' empty
- set fileformats=
-
- " try with 'fileformat' set to 'unix'
- set fileformat=unix
- e! XXUnix
- w! Xtest
- call assert_equal("unix\nunix\n", s:file2str('Xtest'))
- e! XXDos
- w! Xtest
- call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest'))
- e! XXMac
- w! Xtest
- call assert_equal("mac\rmac\r\n", s:file2str('Xtest'))
- bwipe XXUnix XXDos XXMac
-
- " try with 'fileformat' set to 'dos'
- set fileformat=dos
- e! XXUnix
- w! Xtest
- call assert_equal("unix\r\nunix\r\n", s:file2str('Xtest'))
- e! XXDos
- w! Xtest
- call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest'))
- e! XXMac
- w! Xtest
- call assert_equal("mac\rmac\r\r\n", s:file2str('Xtest'))
- bwipe XXUnix XXDos XXMac
-
- " try with 'fileformat' set to 'mac'
- set fileformat=mac
- e! XXUnix
- w! Xtest
- call assert_equal("unix\nunix\n\r", s:file2str('Xtest'))
- e! XXDos
- w! Xtest
- call assert_equal("dos\r\ndos\r\n\r", s:file2str('Xtest'))
- e! XXMac
- w! Xtest
- call assert_equal("mac\rmac\r", s:file2str('Xtest'))
- bwipe XXUnix XXDos XXMac
-
- " Test 2: try reading and writing with 'fileformats' set to one format
-
- " try with 'fileformats' set to 'unix'
- set fileformats=unix
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- " try with 'fileformats' set to 'dos'
- set fileformats=dos
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- " try with 'fileformats' set to 'mac'
- set fileformats=mac
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- " Test 3: try reading and writing with 'fileformats' set to two formats
-
- " try with 'fileformats' set to 'unix,dos'
- set fileformats=unix,dos
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- e! XXUxMac
- w! Xtest
- call assert_equal("unix\nunix\nmac\rmac\r\n", s:file2str('Xtest'))
- bwipe XXUxMac
-
- e! XXDosMac
- w! Xtest
- call assert_equal("dos\r\ndos\r\nmac\rmac\r\r\n", s:file2str('Xtest'))
- bwipe XXDosMac
-
- " try with 'fileformats' set to 'unix,mac'
- set fileformats=unix,mac
- e! XXUxDs
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\n", s:file2str('Xtest'))
- bwipe XXUxDs
-
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- e! XXDosMac
- w! Xtest
- call assert_equal("dos\r\ndos\r\nmac\rmac\r", s:file2str('Xtest'))
- bwipe XXDosMac
-
- e! XXEol
- exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
- w! Xtest
- call assert_equal("unix,mac:unix\nnoeol\n", s:file2str('Xtest'))
- bwipe! XXEol
-
- " try with 'fileformats' set to 'dos,mac'
- set fileformats=dos,mac
- e! XXUxDs
- w! Xtest
- call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\n", s:file2str('Xtest'))
- bwipe XXUxDs
-
- e! XXUxMac
- exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
- w! Xtest
- call assert_equal("dos,mac:dos\r\nunix\r\nunix\r\nmac\rmac\r\r\n",
- \ s:file2str('Xtest'))
- bwipe! XXUxMac
-
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- e! XXMacEol
- exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
- w! Xtest
- call assert_equal("dos,mac:mac\rmac\rmac\rnoeol\r", s:file2str('Xtest'))
- bwipe! XXMacEol
-
- " Test 4: try reading and writing with 'fileformats' set to three formats
- set fileformats=unix,dos,mac
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- e! XXEol
- exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
- w! Xtest
- call assert_equal("unix,dos,mac:unix\nnoeol\n", s:file2str('Xtest'))
- bwipe! XXEol
-
- set fileformats=mac,dos,unix
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- e! XXEol
- exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
- w! Xtest
- call assert_equal("mac,dos,unix:mac\rnoeol\r", s:file2str('Xtest'))
- bwipe! XXEol
-
- " Test 5: try with 'binary' set
- set fileformats=mac,unix,dos
- set binary
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- set fileformats=mac
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- set fileformats=dos
- e! XXUxDsMc
- w! Xtest
- call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
- \ s:file2str('Xtest'))
- bwipe XXUxDsMc
-
- e! XXUnix
- w! Xtest
- call assert_equal("unix\nunix\n", s:file2str('Xtest'))
- bwipe! XXUnix
-
- set nobinary ff& ffs&
-
- " cleanup
- only
- %bwipe!
- call delete('XXUnix')
- call delete('XXDos')
- call delete('XXMac')
- call delete('XXEol')
- call delete('XXUxDs')
- call delete('XXUxMac')
- call delete('XXDosMac')
- call delete('XXMacEol')
- call delete('XXUxDsMc')
- call delete('Xtest')
-endfunc
-
-" Test for changing the fileformat using ++read
-func Test_fileformat_plusplus_read()
- new
- call setline(1, ['one', 'two', 'three'])
- w ++ff=dos Xfile1
- enew!
- set ff=unix
- " A :read doesn't change the fileformat, but does apply to the read lines.
- r ++fileformat=unix Xfile1
- call assert_equal('unix', &fileformat)
- call assert_equal("three\r", getline('$'))
- 3r ++edit Xfile1
- call assert_equal('dos', &fileformat)
- close!
- call delete('Xfile1')
- set fileformat&
- call assert_fails('e ++fileformat Xfile1', 'E474:')
- call assert_fails('e ++ff=abc Xfile1', 'E474:')
- call assert_fails('e ++abc1 Xfile1', 'E474:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
deleted file mode 100644
index cddb1349f5..0000000000
--- a/src/nvim/testdir/test_filetype.vim
+++ /dev/null
@@ -1,2040 +0,0 @@
-" Test :setfiletype
-
-func Test_detection()
- filetype on
- augroup filetypedetect
- au BufNewFile,BufRead * call assert_equal(1, did_filetype())
- augroup END
- new something.vim
- call assert_equal('vim', &filetype)
-
- bwipe!
- filetype off
-endfunc
-
-func Test_conf_type()
- filetype on
- call writefile(['# some comment', 'must be conf'], 'Xfile')
- augroup filetypedetect
- au BufNewFile,BufRead * call assert_equal(0, did_filetype())
- augroup END
- split Xfile
- call assert_equal('conf', &filetype)
-
- bwipe!
- call delete('Xfile')
- filetype off
-endfunc
-
-func Test_other_type()
- filetype on
- augroup filetypedetect
- au BufNewFile,BufRead * call assert_equal(0, did_filetype())
- au BufNewFile,BufRead Xfile setf testfile
- au BufNewFile,BufRead * call assert_equal(1, did_filetype())
- augroup END
- call writefile(['# some comment', 'must be conf'], 'Xfile')
- split Xfile
- call assert_equal('testfile', &filetype)
-
- bwipe!
- call delete('Xfile')
- filetype off
-endfunc
-
-" Filetypes detected just from matching the file name.
-" First one is checking that these files have no filetype.
-let s:filename_checks = {
- \ 'none': ['bsd', 'some-bsd'],
- \ '8th': ['file.8th'],
- \ 'a2ps': ['/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc', 'any/etc/a2ps.cfg', 'any/etc/a2ps/file.cfg'],
- \ 'a65': ['file.a65'],
- \ 'aap': ['file.aap'],
- \ 'abap': ['file.abap'],
- \ 'abc': ['file.abc'],
- \ 'abel': ['file.abl'],
- \ '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', 'any/etc/asound.conf', 'any/usr/share/alsa/alsa.conf'],
- \ 'aml': ['file.aml'],
- \ 'ampl': ['file.run'],
- \ 'ant': ['build.xml'],
- \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file', '/etc/httpd/mods-some/file', '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'],
- \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file', 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'],
- \ 'applescript': ['file.scpt'],
- \ 'aptconf': ['apt.conf', '/.aptitude/config', 'any/.aptitude/config'],
- \ 'arch': ['.arch-inventory', '=tagging-method'],
- \ 'arduino': ['file.ino', 'file.pde'],
- \ 'art': ['file.art'],
- \ 'asciidoc': ['file.asciidoc', 'file.adoc'],
- \ 'asn': ['file.asn', 'file.asn1'],
- \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'],
- \ 'astro': ['file.astro'],
- \ 'atlas': ['file.atl', 'file.as'],
- \ 'autohotkey': ['file.ahk'],
- \ 'autoit': ['file.au3'],
- \ 'automake': ['GNUmakefile.am', 'makefile.am', 'Makefile.am'],
- \ 'ave': ['file.ave'],
- \ 'awk': ['file.awk', 'file.gawk'],
- \ 'b': ['file.mch', 'file.ref', 'file.imp'],
- \ 'basic': ['file.bas', 'file.bi', 'file.bm'],
- \ 'bc': ['file.bc'],
- \ 'bdf': ['file.bdf'],
- \ 'beancount': ['file.beancount'],
- \ 'bib': ['file.bib'],
- \ 'bicep': ['file.bicep'],
- \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'],
- \ 'bitbake': ['file.bb', 'file.bbappend', 'file.bbclass', 'build/conf/local.conf', 'meta/conf/layer.conf', 'build/conf/bbappend.conf', 'meta-layer/conf/distro/foo.conf'],
- \ 'blank': ['file.bl'],
- \ 'blueprint': ['file.blp'],
- \ 'bsdl': ['file.bsd', 'file.bsdl'],
- \ 'bst': ['file.bst'],
- \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE', 'WORKSPACE.bzlmod'],
- \ 'bzr': ['bzr_log.any', 'bzr_log.file'],
- \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg'],
- \ 'cabal': ['file.cabal'],
- \ 'cabalconfig': ['cabal.config'],
- \ 'cabalproject': ['cabal.project', 'cabal.project.local'],
- \ 'calendar': ['calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'],
- \ 'capnp': ['file.capnp'],
- \ 'catalog': ['catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'],
- \ 'cdl': ['file.cdl'],
- \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'],
- \ 'cdrtoc': ['file.toc'],
- \ 'cf': ['file.cfm', 'file.cfi', 'file.cfc'],
- \ 'cfengine': ['cfengine.conf'],
- \ 'cfg': ['file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'],
- \ 'ch': ['file.chf'],
- \ 'chaiscript': ['file.chai'],
- \ 'chaskell': ['file.chs'],
- \ 'chatito': ['file.chatito'],
- \ 'chill': ['file..ch'],
- \ 'chordpro': ['file.chopro', 'file.crd', 'file.cho', 'file.crdpro', 'file.chordpro'],
- \ 'cl': ['file.eni'],
- \ 'clean': ['file.dcl', 'file.icl'],
- \ 'clojure': ['file.clj', 'file.cljs', 'file.cljx', 'file.cljc'],
- \ 'cmake': ['CMakeLists.txt', 'file.cmake', 'file.cmake.in'],
- \ 'cmod': ['file.cmod'],
- \ 'cmusrc': ['any/.cmus/autosave', 'any/.cmus/rc', 'any/.cmus/command-history', 'any/.cmus/file.theme', 'any/cmus/rc', 'any/cmus/file.theme', '/.cmus/autosave', '/.cmus/command-history', '/.cmus/file.theme', '/.cmus/rc', '/cmus/file.theme', '/cmus/rc'],
- \ 'cobol': ['file.cbl', 'file.cob', 'file.lib'],
- \ 'coco': ['file.atg'],
- \ 'conaryrecipe': ['file.recipe'],
- \ 'conf': ['auto.master'],
- \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'],
- \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf', 'any/.aws/config', 'any/.aws/credentials'],
- \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'],
- \ 'cook': ['file.cook'],
- \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'],
- \ 'cqlang': ['file.cql'],
- \ 'crm': ['file.crm'],
- \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'],
- \ 'cs': ['file.cs', 'file.csx'],
- \ 'csc': ['file.csc'],
- \ 'csdl': ['file.csdl'],
- \ 'csp': ['file.csp', 'file.fdr'],
- \ 'css': ['file.css'],
- \ 'cterm': ['file.con'],
- \ 'csv': ['file.csv'],
- \ 'cucumber': ['file.feature'],
- \ 'cuda': ['file.cu', 'file.cuh'],
- \ 'cupl': ['file.pld'],
- \ 'cuplsim': ['file.si'],
- \ 'cvs': ['cvs123'],
- \ 'cvsrc': ['.cvsrc'],
- \ 'cynpp': ['file.cyn'],
- \ 'd': ['file.d'],
- \ 'dart': ['file.dart', 'file.drt'],
- \ 'datascript': ['file.ds'],
- \ 'dcd': ['file.dcd'],
- \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'],
- \ 'debcontrol': ['/debian/control', 'any/debian/control'],
- \ 'debcopyright': ['/debian/copyright', 'any/debian/copyright'],
- \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list', 'any/etc/apt/sources.list', 'any/etc/apt/sources.list.d/file.list'],
- \ 'def': ['file.def'],
- \ 'denyhosts': ['denyhosts.conf'],
- \ 'desc': ['file.desc'],
- \ 'desktop': ['file.desktop', '.directory', 'file.directory'],
- \ 'dictconf': ['dict.conf', '.dictrc'],
- \ 'dictdconf': ['dictd.conf', 'dictdfile.conf', 'dictd-file.conf'],
- \ 'diff': ['file.diff', 'file.rej'],
- \ 'dircolors': ['.dir_colors', '.dircolors', '/etc/DIR_COLORS', 'any/etc/DIR_COLORS'],
- \ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'],
- \ 'dockerfile': ['Containerfile', 'Dockerfile', 'dockerfile', 'file.Dockerfile', 'file.dockerfile', 'Dockerfile.debian', 'Containerfile.something'],
- \ 'dosbatch': ['file.bat'],
- \ 'dosini': ['/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'],
- \ 'dot': ['file.dot', 'file.gv'],
- \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'],
- \ 'dtd': ['file.dtd'],
- \ 'dtrace': ['/usr/lib/dtrace/io.d'],
- \ 'dts': ['file.dts', 'file.dtsi'],
- \ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace'],
- \ 'dylan': ['file.dylan'],
- \ 'dylanintr': ['file.intr'],
- \ 'dylanlid': ['file.lid'],
- \ 'ecd': ['file.ecd'],
- \ 'edif': ['file.edf', 'file.edif', 'file.edo'],
- \ 'editorconfig': ['.editorconfig'],
- \ 'eelixir': ['file.eex', 'file.leex'],
- \ 'elinks': ['elinks.conf'],
- \ 'elixir': ['file.ex', 'file.exs', 'mix.lock'],
- \ 'elm': ['file.elm'],
- \ 'elmfilt': ['filter-rules'],
- \ 'elvish': ['file.elv'],
- \ 'epuppet': ['file.epp'],
- \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'],
- \ 'eruby': ['file.erb', 'file.rhtml'],
- \ 'esmtprc': ['anyesmtprc', 'esmtprc', 'some-esmtprc'],
- \ 'esqlc': ['file.ec', 'file.EC'],
- \ 'esterel': ['file.strl'],
- \ 'eterm': ['anyEterm/file.cfg', 'Eterm/file.cfg', 'some-Eterm/file.cfg'],
- \ 'exim': ['exim.conf'],
- \ 'expect': ['file.exp'],
- \ 'exports': ['exports'],
- \ 'factor': ['file.factor'],
- \ 'falcon': ['file.fal'],
- \ 'fan': ['file.fan', 'file.fwt'],
- \ 'fennel': ['file.fnl'],
- \ 'fetchmail': ['.fetchmailrc'],
- \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'],
- \ 'fish': ['file.fish'],
- \ 'focexec': ['file.fex', 'file.focexec'],
- \ 'form': ['file.frm'],
- \ 'forth': ['file.ft', 'file.fth'],
- \ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'],
- \ 'fpcmake': ['file.fpc'],
- \ 'framescript': ['file.fsl'],
- \ 'freebasic': ['file.fb'],
- \ 'fsh': ['file.fsh'],
- \ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'],
- \ 'fstab': ['fstab', 'mtab'],
- \ 'fusion': ['file.fusion'],
- \ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'],
- \ 'gdb': ['.gdbinit', 'gdbinit', 'file.gdb', '.config/gdbearlyinit', '.gdbearlyinit'],
- \ 'gdmo': ['file.mo', 'file.gdmo'],
- \ 'gdresource': ['file.tscn', 'file.tres'],
- \ 'gdscript': ['file.gd'],
- \ 'gdshader': ['file.gdshader', 'file.shader'],
- \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'],
- \ 'gemtext': ['file.gmi', 'file.gemini'],
- \ 'gift': ['file.gift'],
- \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'],
- \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'],
- \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'],
- \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'],
- \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'],
- \ 'gitrebase': ['git-rebase-todo'],
- \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'],
- \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'],
- \ 'gleam': ['file.gleam'],
- \ 'glsl': ['file.glsl'],
- \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'],
- \ 'gnuplot': ['file.gpi', '.gnuplot'],
- \ 'go': ['file.go'],
- \ 'gomod': ['go.mod'],
- \ 'gosum': ['go.sum'],
- \ 'gowork': ['go.work'],
- \ 'gp': ['file.gp', '.gprc'],
- \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'],
- \ 'grads': ['file.gs'],
- \ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'],
- \ 'gretl': ['file.gretl'],
- \ 'groovy': ['file.gradle', 'file.groovy'],
- \ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'],
- \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'],
- \ 'gsp': ['file.gsp'],
- \ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'],
- \ 'gyp': ['file.gyp', 'file.gypi'],
- \ 'hack': ['file.hack', 'file.hackpartial'],
- \ 'haml': ['file.haml'],
- \ 'hamster': ['file.hsm'],
- \ 'handlebars': ['file.hbs'],
- \ 'hare': ['file.ha'],
- \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'],
- \ 'haste': ['file.ht'],
- \ 'hastepreproc': ['file.htpp'],
- \ 'hb': ['file.hb'],
- \ 'hcl': ['file.hcl'],
- \ 'heex': ['file.heex'],
- \ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'],
- \ 'hex': ['file.hex', 'file.h32'],
- \ 'hgcommit': ['hg-editor-file.txt'],
- \ 'hjson': ['file.hjson'],
- \ 'hlsplaylist': ['file.m3u', 'file.m3u8'],
- \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
- \ 'hollywood': ['file.hws'],
- \ 'hoon': ['file.hoon'],
- \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'],
- \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'],
- \ 'html': ['file.html', 'file.htm', 'file.cshtml'],
- \ 'htmlm4': ['file.html.m4'],
- \ 'httest': ['file.htt', 'file.htb'],
- \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'],
- \ 'ibasic': ['file.iba', 'file.ibi'],
- \ 'icemenu': ['/.icewm/menu', 'any/.icewm/menu'],
- \ 'icon': ['file.icn'],
- \ 'indent': ['.indent.pro', 'indentrc'],
- \ 'inform': ['file.inf', 'file.INF'],
- \ 'initng': ['/etc/initng/any/file.i', 'file.ii', 'any/etc/initng/any/file.i'],
- \ 'inittab': ['inittab'],
- \ 'ipfilter': ['ipf.conf', 'ipf6.conf', 'ipf.rules'],
- \ 'iss': ['file.iss'],
- \ 'ist': ['file.ist', 'file.mst'],
- \ 'j': ['file.ijs'],
- \ 'jal': ['file.jal', 'file.JAL'],
- \ 'jam': ['file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'],
- \ 'java': ['file.java', 'file.jav'],
- \ 'javacc': ['file.jj', 'file.jjt'],
- \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
- \ 'javascript.glimmer': ['file.gjs'],
- \ 'javascriptreact': ['file.jsx'],
- \ 'jess': ['file.clp'],
- \ 'jgraph': ['file.jgr'],
- \ 'jq': ['file.jq'],
- \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
- \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'],
- \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.prettierrc', '.firebaserc', 'file.slnf'],
- \ 'json5': ['file.json5'],
- \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json'],
- \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'],
- \ 'jsp': ['file.jsp'],
- \ 'julia': ['file.jl'],
- \ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'],
- \ 'kivy': ['file.kv'],
- \ 'kix': ['file.kix'],
- \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'],
- \ 'krl': ['file.sub', 'file.Sub', 'file.SUB'],
- \ 'kscript': ['file.ks'],
- \ 'kwt': ['file.k'],
- \ 'lace': ['file.ace', 'file.ACE'],
- \ 'latte': ['file.latte', 'file.lte'],
- \ 'ld': ['file.ld'],
- \ 'ldif': ['file.ldif'],
- \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'],
- \ 'less': ['file.less'],
- \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'],
- \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'],
- \ 'lhaskell': ['file.lhs'],
- \ 'libao': ['/etc/libao.conf', '/.libao', 'any/.libao', 'any/etc/libao.conf'],
- \ 'lifelines': ['file.ll'],
- \ 'lilo': ['lilo.conf', 'lilo.conf-file'],
- \ 'lilypond': ['file.ly', 'file.ily'],
- \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'],
- \ 'liquid': ['file.liquid'],
- \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
- \ 'lite': ['file.lite', 'file.lt'],
- \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'],
- \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'],
- \ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'],
- \ 'logindefs': ['/etc/login.defs', 'any/etc/login.defs'],
- \ 'logtalk': ['file.lgt'],
- \ 'lotos': ['file.lot', 'file.lotos'],
- \ 'lout': ['file.lou', 'file.lout'],
- \ 'lpc': ['file.lpc', 'file.ulpc'],
- \ 'lsl': ['file.lsl'],
- \ 'lss': ['file.lss'],
- \ 'lua': ['file.lua', 'file.rockspec', 'file.nse', '.luacheckrc'],
- \ 'lynx': ['lynx.cfg'],
- \ 'lyrics': ['file.lrc'],
- \ 'm3build': ['m3makefile', 'm3overrides'],
- \ 'm3quake': ['file.quake', 'cm3.cfg'],
- \ 'm4': ['file.at'],
- \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml', 'reportbug-file'],
- \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases', 'any/etc/aliases', 'any/etc/mail/aliases'],
- \ 'mailcap': ['.mailcap', 'mailcap'],
- \ 'make': ['file.mk', 'file.mak', 'file.dsp', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile'],
- \ 'mallard': ['file.page'],
- \ 'manconf': ['/etc/man.conf', 'man.config', 'any/etc/man.conf'],
- \ 'map': ['file.map'],
- \ 'maple': ['file.mv', 'file.mpl', 'file.mws'],
- \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'],
- \ 'mason': ['file.mason', 'file.mhtml', 'file.comp'],
- \ 'master': ['file.mas', 'file.master'],
- \ 'matlab': ['file.m'],
- \ 'maxima': ['file.demo', 'file.dmt', 'file.dm1', 'file.dm2', 'file.dm3',
- \ 'file.wxm', 'maxima-init.mac'],
- \ 'mel': ['file.mel'],
- \ 'mermaid': ['file.mmd', 'file.mmdc', 'file.mermaid'],
- \ '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',
- \ '/log/auth.info', '/log/cron.info', '/log/daemon.info', '/log/debug.info', '/log/kern.info', '/log/lpr.info', '/log/mail.info', '/log/messages.info', '/log/news/news.info', '/log/syslog.info', '/log/user.info',
- \ '/log/auth.warn', '/log/cron.warn', '/log/daemon.warn', '/log/debug.warn', '/log/kern.warn', '/log/lpr.warn', '/log/mail.warn', '/log/messages.warn', '/log/news/news.warn', '/log/syslog.warn', '/log/user.warn',
- \ '/log/auth.crit', '/log/cron.crit', '/log/daemon.crit', '/log/debug.crit', '/log/kern.crit', '/log/lpr.crit', '/log/mail.crit', '/log/messages.crit', '/log/news/news.crit', '/log/syslog.crit', '/log/user.crit',
- \ '/log/auth.notice', '/log/cron.notice', '/log/daemon.notice', '/log/debug.notice', '/log/kern.notice', '/log/lpr.notice', '/log/mail.notice', '/log/messages.notice', '/log/news/news.notice', '/log/syslog.notice', '/log/user.notice'],
- \ 'mf': ['file.mf'],
- \ 'mgl': ['file.mgl'],
- \ 'mgp': ['file.mgp'],
- \ 'mib': ['file.mib', 'file.my'],
- \ 'mix': ['file.mix', 'file.mixal'],
- \ 'mma': ['file.nb'],
- \ 'mmp': ['file.mmp'],
- \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'],
- \ 'modula2': ['file.m2', 'file.mi'],
- \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig', 'file.lm3'],
- \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'],
- \ 'moo': ['file.moo'],
- \ 'moonscript': ['file.moon'],
- \ 'mp': ['file.mp', 'file.mpxl', 'file.mpiv', 'file.mpvi'],
- \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'],
- \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'],
- \ 'msidl': ['file.odl', 'file.mof'],
- \ 'msql': ['file.msql'],
- \ 'mupad': ['file.mu'],
- \ 'mush': ['file.mush'],
- \ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', '/etc/Muttrc.d/file.rc', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'],
- \ 'mysql': ['file.mysql'],
- \ 'n1ql': ['file.n1ql', 'file.nql'],
- \ 'named': ['namedfile.conf', 'rndcfile.conf', 'named-file.conf', 'named.conf', 'rndc-file.conf', 'rndc-file.key', 'rndc.conf', 'rndc.key'],
- \ 'nanorc': ['/etc/nanorc', 'file.nanorc', 'any/etc/nanorc'],
- \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'],
- \ 'ncf': ['file.ncf'],
- \ 'neomuttrc': ['Neomuttrc', '.neomuttrc', '.neomuttrc-file', '/.neomutt/neomuttrc', '/.neomutt/neomuttrc-file', 'Neomuttrc', 'Neomuttrc-file', 'any/.neomutt/neomuttrc', 'any/.neomutt/neomuttrc-file', 'neomuttrc', 'neomuttrc-file'],
- \ 'netrc': ['.netrc'],
- \ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'],
- \ 'nim': ['file.nim', 'file.nims', 'file.nimble'],
- \ 'ninja': ['file.ninja'],
- \ 'nix': ['file.nix'],
- \ 'nqc': ['file.nqc'],
- \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'],
- \ 'nsis': ['file.nsi', 'file.nsh'],
- \ 'obj': ['file.obj'],
- \ 'obse': ['file.obl', 'file.obse', 'file.oblivion', 'file.obscript'],
- \ 'ocaml': ['file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit', 'file.mlt', 'file.mlp', 'file.mlip', 'file.mli.cppo', 'file.ml.cppo'],
- \ 'occam': ['file.occ'],
- \ 'octave': ['octaverc', '.octaverc', 'octave.conf'],
- \ 'omnimark': ['file.xom', 'file.xin'],
- \ 'opam': ['opam', 'file.opam', 'file.opam.template'],
- \ 'openroad': ['file.or'],
- \ 'openscad': ['file.scad'],
- \ 'openvpn': ['file.ovpn', '/etc/openvpn/client/client.conf', '/usr/share/openvpn/examples/server.conf'],
- \ 'opl': ['file.OPL', 'file.OPl', 'file.OpL', 'file.Opl', 'file.oPL', 'file.oPl', 'file.opL', 'file.opl'],
- \ 'ora': ['file.ora'],
- \ 'org': ['file.org', 'file.org_archive'],
- \ 'pamconf': ['/etc/pam.conf', '/etc/pam.d/file', 'any/etc/pam.conf', 'any/etc/pam.d/file'],
- \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment', '.pam_environment', 'pam_env.conf'],
- \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'],
- \ 'pascal': ['file.pas', 'file.dpr', 'file.lpr'],
- \ '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', '/etc/passwd', '/etc/passwd-', '/etc/passwd.edit', '/etc/shadow', '/etc/shadow-', '/etc/shadow.edit', '/var/backups/passwd.bak', '/var/backups/shadow.bak'],
- \ 'pbtxt': ['file.pbtxt'],
- \ 'pccts': ['file.g'],
- \ 'pcmk': ['file.pcmk'],
- \ 'pdf': ['file.pdf'],
- \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc', '.latexmkrc', 'latexmkrc'],
- \ 'pf': ['pf.conf'],
- \ 'pfmain': ['main.cf', 'main.cf.proto'],
- \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt', 'file.theme'],
- \ 'pike': ['file.pike', 'file.pmod'],
- \ 'pilrc': ['file.rcp'],
- \ 'pine': ['.pinerc', 'pinerc', '.pinercex', 'pinercex'],
- \ 'pinfo': ['/etc/pinforc', '/.pinforc', 'any/.pinforc', 'any/etc/pinforc'],
- \ 'pli': ['file.pli', 'file.pl1'],
- \ 'plm': ['file.plm', 'file.p36', 'file.pac'],
- \ 'plp': ['file.plp'],
- \ 'plsql': ['file.pls', 'file.plsql'],
- \ 'po': ['file.po', 'file.pot'],
- \ 'pod': ['file.pod'],
- \ 'poefilter': ['file.filter'],
- \ 'poke': ['file.pk'],
- \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'],
- \ 'pov': ['file.pov'],
- \ 'povini': ['.povrayrc'],
- \ 'ppd': ['file.ppd'],
- \ 'ppwiz': ['file.it', 'file.ih'],
- \ 'prisma': ['file.prisma'],
- \ 'privoxy': ['file.action'],
- \ 'proc': ['file.pc'],
- \ 'procmail': ['.procmail', '.procmailrc'],
- \ 'prolog': ['file.pdb'],
- \ 'promela': ['file.pml'],
- \ 'proto': ['file.proto'],
- \ 'protocols': ['/etc/protocols', 'any/etc/protocols'],
- \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'],
- \ 'ps1xml': ['file.ps1xml'],
- \ 'psf': ['file.psf'],
- \ 'psl': ['file.psl'],
- \ 'pug': ['file.pug'],
- \ 'puppet': ['file.pp'],
- \ 'pyret': ['file.arr'],
- \ 'pyrex': ['file.pyx', 'file.pxd'],
- \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
- \ 'ql': ['file.ql', 'file.qll'],
- \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'],
- \ 'quarto': ['file.qmd'],
- \ 'r': ['file.r', '.Rprofile', 'Rprofile', 'Rprofile.site'],
- \ 'radiance': ['file.rad', 'file.mat'],
- \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'],
- \ 'raml': ['file.raml'],
- \ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'],
- \ 'rbs': ['file.rbs'],
- \ 'rc': ['file.rc', 'file.rch'],
- \ 'rcs': ['file,v'],
- \ 'readline': ['.inputrc', 'inputrc'],
- \ 'rego': ['file.rego'],
- \ 'remind': ['.reminders', 'file.remind', 'file.rem', '.reminders-file'],
- \ 'rescript': ['file.res', 'file.resi'],
- \ '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'],
- \ 'rhelp': ['file.rd'],
- \ 'rib': ['file.rib'],
- \ 'rmd': ['file.rmd', 'file.smd'],
- \ 'rnc': ['file.rnc'],
- \ 'rng': ['file.rng'],
- \ 'rnoweb': ['file.rnw', 'file.snw'],
- \ 'robot': ['file.robot', 'file.resource'],
- \ 'robots': ['robots.txt'],
- \ 'routeros': ['file.rsc'],
- \ 'rpcgen': ['file.x'],
- \ 'rpl': ['file.rpl'],
- \ 'rrst': ['file.rrst', 'file.srst'],
- \ 'rst': ['file.rst'],
- \ 'rtf': ['file.rtf'],
- \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile', 'Vagrantfile'],
- \ 'rust': ['file.rs'],
- \ 'samba': ['smb.conf'],
- \ 'sas': ['file.sas'],
- \ 'sass': ['file.sass'],
- \ 'sather': ['file.sa'],
- \ 'sbt': ['file.sbt'],
- \ 'scala': ['file.scala'],
- \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'],
- \ 'scilab': ['file.sci', 'file.sce'],
- \ 'screen': ['.screenrc', 'screenrc'],
- \ 'scss': ['file.scss'],
- \ 'sd': ['file.sd'],
- \ 'sdc': ['file.sdc'],
- \ 'sdl': ['file.sdl', 'file.pr'],
- \ 'sed': ['file.sed'],
- \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', '/etc/sensors.d/file', 'any/etc/sensors.conf', 'any/etc/sensors3.conf', 'any/etc/sensors.d/file'],
- \ 'services': ['/etc/services', 'any/etc/services'],
- \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'],
- \ 'sexplib': ['file.sexp'],
- \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'],
- \ 'sieve': ['file.siv', 'file.sieve'],
- \ 'sil': ['file.sil'],
- \ 'simula': ['file.sim'],
- \ 'sinda': ['file.sin', 'file.s85'],
- \ 'sisu': ['file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'],
- \ 'skill': ['file.il', 'file.ils', 'file.cdf'],
- \ 'slang': ['file.sl'],
- \ 'slice': ['file.ice'],
- \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'],
- \ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'],
- \ 'slpspi': ['/etc/slp.spi', 'any/etc/slp.spi'],
- \ 'slrnrc': ['.slrnrc'],
- \ 'slrnsc': ['file.score'],
- \ 'sm': ['sendmail.cf'],
- \ 'smali': ['file.smali'],
- \ 'smarty': ['file.tpl'],
- \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'],
- \ 'smith': ['file.smt', 'file.smith'],
- \ 'smithy': ['file.smithy'],
- \ 'sml': ['file.sml'],
- \ 'snobol4': ['file.sno', 'file.spt'],
- \ 'solidity': ['file.sol'],
- \ 'solution': ['file.sln'],
- \ 'sparql': ['file.rq', 'file.sparql'],
- \ 'spec': ['file.spec'],
- \ 'spice': ['file.sp', 'file.spice'],
- \ 'spup': ['file.speedup', 'file.spdata', 'file.spd'],
- \ 'spyce': ['file.spy', 'file.spi'],
- \ 'sql': ['file.tyb', 'file.typ', 'file.tyc', 'file.pkb', 'file.pks'],
- \ 'sqlj': ['file.sqlj'],
- \ 'sqr': ['file.sqr', 'file.sqi'],
- \ 'squid': ['squid.conf'],
- \ 'squirrel': ['file.nut'],
- \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'],
- \ 'srt': ['file.srt'],
- \ 'ssa': ['file.ass', 'file.ssa'],
- \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'],
- \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'],
- \ 'st': ['file.st'],
- \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'],
- \ 'stp': ['file.stp'],
- \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'],
- \ 'supercollider': ['file.quark'],
- \ 'surface': ['file.sface'],
- \ 'svelte': ['file.svelte'],
- \ 'svg': ['file.svg'],
- \ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'],
- \ 'swayconfig': ['/home/user/.sway/config', '/home/user/.config/sway/config', '/etc/sway/config', '/etc/xdg/sway/config'],
- \ 'swift': ['file.swift'],
- \ 'swiftgyb': ['file.swift.gyb'],
- \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf', 'any/etc/sysctl.conf', 'any/etc/sysctl.d/file.conf'],
- \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.dnssd', 'any/systemd/file.link', 'any/systemd/file.mount', 'any/systemd/file.netdev', 'any/systemd/file.network', 'any/systemd/file.nspawn', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.slice', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/some.conf.d/file.conf', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile', '/.config/systemd/user/.#', '/.config/systemd/user/.#-file', '/.config/systemd/user/file.d/.#', '/.config/systemd/user/file.d/.#-file', '/.config/systemd/user/file.d/file.conf', '/etc/systemd/file.conf.d/file.conf', '/etc/systemd/system/.#', '/etc/systemd/system/.#-file', '/etc/systemd/system/file.d/.#', '/etc/systemd/system/file.d/.#-file', '/etc/systemd/system/file.d/file.conf', '/systemd/file.automount', '/systemd/file.dnssd', '/systemd/file.link', '/systemd/file.mount', '/systemd/file.netdev', '/systemd/file.network', '/systemd/file.nspawn', '/systemd/file.path', '/systemd/file.service', '/systemd/file.slice', '/systemd/file.socket', '/systemd/file.swap', '/systemd/file.target', '/systemd/file.timer', 'any/.config/systemd/user/.#', 'any/.config/systemd/user/.#-file', 'any/.config/systemd/user/file.d/.#', 'any/.config/systemd/user/file.d/.#-file', 'any/.config/systemd/user/file.d/file.conf', 'any/etc/systemd/file.conf.d/file.conf', 'any/etc/systemd/system/.#', 'any/etc/systemd/system/.#-file', 'any/etc/systemd/system/file.d/.#', 'any/etc/systemd/system/file.d/.#-file', 'any/etc/systemd/system/file.d/file.conf'],
- \ 'systemverilog': ['file.sv', 'file.svh'],
- \ 'tags': ['tags'],
- \ 'tak': ['file.tak'],
- \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'],
- \ 'taskedit': ['file.task'],
- \ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'],
- \ 'teal': ['file.tl'],
- \ 'template': ['file.tmpl'],
- \ 'teraterm': ['file.ttl'],
- \ 'terminfo': ['file.ti'],
- \ 'terraform-vars': ['file.tfvars'],
- \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'],
- \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'],
- \ 'texmf': ['texmf.cnf'],
- \ 'text': ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'],
- \ 'tf': ['file.tf', '.tfrc', 'tfrc'],
- \ 'thrift': ['file.thrift'],
- \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'],
- \ 'tilde': ['file.t.html'],
- \ 'tla': ['file.tla'],
- \ 'tli': ['file.tli'],
- \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'],
- \ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'],
- \ 'tpp': ['file.tpp'],
- \ 'treetop': ['file.treetop'],
- \ 'trustees': ['trustees.conf'],
- \ 'tsalt': ['file.slt'],
- \ 'tsscl': ['file.tsscl'],
- \ 'tssgm': ['file.tssgm'],
- \ 'tssop': ['file.tssop'],
- \ 'tsv': ['file.tsv'],
- \ 'twig': ['file.twig'],
- \ 'typescript': ['file.mts', 'file.cts'],
- \ 'typescript.glimmer': ['file.gts'],
- \ 'typescriptreact': ['file.tsx'],
- \ 'uc': ['file.uc'],
- \ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'],
- \ 'udevperm': ['/etc/udev/permissions.d/file.permissions', 'any/etc/udev/permissions.d/file.permissions'],
- \ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'],
- \ 'uil': ['file.uit', 'file.uil'],
- \ 'updatedb': ['/etc/updatedb.conf', 'any/etc/updatedb.conf'],
- \ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override', 'any/.config/upstart/file.conf', 'any/.config/upstart/file.override', 'any/.init/file.conf', 'any/.init/file.override', 'any/etc/init/file.conf', 'any/etc/init/file.override', 'any/usr/share/upstart/file.conf', 'any/usr/share/upstart/file.override'],
- \ 'upstreamdat': ['upstream.dat', 'UPSTREAM.DAT', 'upstream.file.dat', 'UPSTREAM.FILE.DAT', 'file.upstream.dat', 'FILE.UPSTREAM.DAT'],
- \ 'upstreaminstalllog': ['upstreaminstall.log', 'UPSTREAMINSTALL.LOG', 'upstreaminstall.file.log', 'UPSTREAMINSTALL.FILE.LOG', 'file.upstreaminstall.log', 'FILE.UPSTREAMINSTALL.LOG'],
- \ 'upstreamlog': ['fdrupstream.log', 'upstream.log', 'UPSTREAM.LOG', 'upstream.file.log', 'UPSTREAM.FILE.LOG', 'file.upstream.log', 'FILE.UPSTREAM.LOG', 'UPSTREAM-file.log', 'UPSTREAM-FILE.LOG'],
- \ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'],
- \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'],
- \ 'vala': ['file.vala'],
- \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'],
- \ 'vdf': ['file.vdf'],
- \ 'vdmpp': ['file.vpp', 'file.vdmpp'],
- \ 'vdmrt': ['file.vdmrt'],
- \ 'vdmsl': ['file.vdm', 'file.vdmsl'],
- \ 'vera': ['file.vr', 'file.vri', 'file.vrh'],
- \ 'verilog': ['file.v'],
- \ 'verilogams': ['file.va', 'file.vams'],
- \ 'vgrindefs': ['vgrindefs'],
- \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho', 'some.vhdl_1', 'some.vhdl_1-file'],
- \ 'vhs': ['file.tape'],
- \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'],
- \ 'viminfo': ['.viminfo', '_viminfo'],
- \ 'vmasm': ['file.mar'],
- \ 'voscm': ['file.cm'],
- \ 'vrml': ['file.wrl'],
- \ 'vroom': ['file.vroom'],
- \ 'vue': ['file.vue'],
- \ 'wast': ['file.wast', 'file.wat'],
- \ 'wdl': ['file.wdl'],
- \ 'webmacro': ['file.wm'],
- \ 'wget': ['.wgetrc', 'wgetrc'],
- \ 'wget2': ['.wget2rc', 'wget2rc'],
- \ 'winbatch': ['file.wbt'],
- \ 'wml': ['file.wml'],
- \ 'wsh': ['file.wsf', 'file.wsc'],
- \ 'wsml': ['file.wsml'],
- \ 'wvdial': ['wvdial.conf', '.wvdialrc'],
- \ 'xdefaults': ['.Xdefaults', '.Xpdefaults', '.Xresources', 'xdm-config', 'file.ad', '/Xresources/file', '/app-defaults/file', 'Xresources', 'Xresources-file', 'any/Xresources/file', 'any/app-defaults/file'],
- \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'],
- \ 'xhtml': ['file.xhtml', 'file.xht'],
- \ 'xinetd': ['/etc/xinetd.conf', '/etc/xinetd.d/file', 'any/etc/xinetd.conf', 'any/etc/xinetd.d/file'],
- \ 'xmath': ['file.msc', 'file.msf'],
- \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd'],
- \ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'],
- \ 'xpm': ['file.xpm'],
- \ 'xpm2': ['file.xpm2'],
- \ 'xquery': ['file.xq', 'file.xql', 'file.xqm', 'file.xquery', 'file.xqy'],
- \ 'xs': ['file.xs'],
- \ 'xsd': ['file.xsd'],
- \ 'xslt': ['file.xsl', 'file.xslt'],
- \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'],
- \ 'yaml': ['file.yaml', 'file.yml', '.clang-format', '.clang-tidy'],
- \ 'yang': ['file.yang'],
- \ 'z8a': ['file.z8a'],
- \ 'zig': ['file.zig'],
- \ 'zimbu': ['file.zu'],
- \ 'zimbutempl': ['file.zut'],
- \ 'zir': ['file.zir'],
- \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'],
- \
- \ 'help': [$VIMRUNTIME . '/doc/help.txt'],
- \ }
-
-let s:filename_case_checks = {
- \ 'modula2': ['file.DEF'],
- \ 'bzl': ['file.BUILD', 'BUILD'],
- \ }
-
-func CheckItems(checks)
- set noswapfile
- for [ft, names] in items(a:checks)
- for i in range(0, len(names) - 1)
- new
- try
- exe 'edit ' . fnameescape(names[i])
- catch
- call assert_report('cannot edit "' . names[i] . '": ' . v:exception)
- endtry
- if &filetype == '' && &readonly
- " File exists but not able to edit it (permission denied)
- else
- let expected = ft == 'none' ? '' : ft
- call assert_equal(expected, &filetype, 'with file name: ' . names[i])
- endif
- bwipe!
- endfor
- endfor
- set swapfile&
-endfunc
-
-func Test_filetype_detection()
- filetype on
- call CheckItems(s:filename_checks)
- if has('fname_case')
- call CheckItems(s:filename_case_checks)
- endif
- filetype off
-endfunc
-
-" Filetypes detected from the file contents by scripts.vim
-let s:script_checks = {
- \ 'virata': [['% Virata'],
- \ ['', '% Virata'],
- \ ['', '', '% Virata'],
- \ ['', '', '', '% Virata'],
- \ ['', '', '', '', '% Virata']],
- \ 'strace': [['execve("/usr/bin/pstree", ["pstree"], 0x7ff0 /* 63 vars */) = 0'],
- \ ['15:17:47 execve("/usr/bin/pstree", ["pstree"], ... "_=/usr/bin/strace"]) = 0'],
- \ ['__libc_start_main and something']],
- \ 'clojure': [['#!/path/clojure']],
- \ 'scala': [['#!/path/scala']],
- \ 'sh': [['#!/path/sh'],
- \ ['#!/path/bash'],
- \ ['#!/path/bash2'],
- \ ['#!/path/dash'],
- \ ['#!/path/ksh'],
- \ ['#!/path/ksh93']],
- \ 'csh': [['#!/path/csh']],
- \ 'tcsh': [['#!/path/tcsh']],
- \ 'zsh': [['#!/path/zsh']],
- \ 'tcl': [['#!/path/tclsh'],
- \ ['#!/path/wish'],
- \ ['#!/path/expectk'],
- \ ['#!/path/itclsh'],
- \ ['#!/path/itkwish']],
- \ 'expect': [['#!/path/expect']],
- \ 'gnuplot': [['#!/path/gnuplot']],
- \ 'make': [['#!/path/make']],
- \ 'pike': [['#!/path/pike'],
- \ ['#!/path/pike0'],
- \ ['#!/path/pike9']],
- \ 'lua': [['#!/path/lua']],
- \ 'raku': [['#!/path/raku']],
- \ 'perl': [['#!/path/perl']],
- \ 'php': [['#!/path/php']],
- \ 'python': [['#!/path/python'],
- \ ['#!/path/python2'],
- \ ['#!/path/python3']],
- \ 'groovy': [['#!/path/groovy']],
- \ 'ruby': [['#!/path/ruby']],
- \ 'javascript': [['#!/path/node'],
- \ ['#!/path/js'],
- \ ['#!/path/nodejs'],
- \ ['#!/path/rhino']],
- \ 'bc': [['#!/path/bc']],
- \ 'sed': [['#!/path/sed'], ['#n'], ['#n comment']],
- \ 'ocaml': [['#!/path/ocaml']],
- \ 'awk': [['#!/path/awk'],
- \ ['#!/path/gawk']],
- \ 'wml': [['#!/path/wml']],
- \ 'scheme': [['#!/path/scheme'],
- \ ['#!/path/guile']],
- \ 'cfengine': [['#!/path/cfengine']],
- \ 'erlang': [['#!/path/escript']],
- \ 'haskell': [['#!/path/haskell']],
- \ 'cpp': [['// Standard iostream objects -*- C++ -*-'],
- \ ['// -*- C++ -*-']],
- \ 'yaml': [['%YAML 1.2']],
- \ 'pascal': [['#!/path/instantfpc']],
- \ 'fennel': [['#!/path/fennel']],
- \ 'routeros': [['#!/path/rsc']],
- \ 'fish': [['#!/path/fish']],
- \ 'forth': [['#!/path/gforth']],
- \ 'icon': [['#!/path/icon']],
- \ }
-
-" 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(a:test_dict)
- for file in files
- call writefile(file, 'Xtest')
- split Xtest
- call assert_equal(ft, &filetype, 'for text: ' . string(file))
- bwipe!
- endfor
- endfor
- call delete('Xtest')
- filetype off
-endfunc
-
-func Test_script_detection()
- call Run_script_detection(s: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
-
-" Test for ':filetype detect' command for a buffer without a file
-func Test_emptybuf_ftdetect()
- new
- call setline(1, '#!/bin/sh')
- call assert_equal('', &filetype)
- filetype detect
- call assert_equal('sh', &filetype)
- close!
-endfunc
-
-" Test for ':filetype indent on' and ':filetype indent off' commands
-func Test_filetype_indent_off()
- new Xtest.vim
- filetype indent on
- call assert_equal(1, g:did_indent_on)
- call assert_equal(['filetype detection:ON plugin:OFF indent:ON'],
- \ execute('filetype')->split("\n"))
- filetype indent off
- call assert_equal(0, exists('g:did_indent_on'))
- call assert_equal(['filetype detection:ON plugin:OFF indent:OFF'],
- \ execute('filetype')->split("\n"))
- close
-endfunc
-
-"""""""""""""""""""""""""""""""""""""""""""""""""
-" Tests for specific extensions and filetypes.
-" Keep sorted.
-"""""""""""""""""""""""""""""""""""""""""""""""""
-
-func Test_bas_file()
- filetype on
-
- call writefile(['looks like BASIC'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('basic', &filetype)
- bwipe!
-
- " Test dist#ft#FTbas()
-
- let g:filetype_bas = 'freebasic'
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
- bwipe!
- unlet g:filetype_bas
-
- " FreeBASIC
-
- call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
- bwipe!
-
- call writefile(['#define TESTING'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
- bwipe!
-
- call writefile(['option byval'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
- bwipe!
-
- call writefile(['extern "C"'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
- bwipe!
-
- " QB64
-
- call writefile(['$LET TESTING = 1'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('qb64', &filetype)
- bwipe!
-
- call writefile(['OPTION _EXPLICIT'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('qb64', &filetype)
- bwipe!
-
- " Visual Basic
-
- call writefile(['Attribute VB_NAME = "Testing"', 'Enum Foo', 'End Enum'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('vb', &filetype)
- bwipe!
-
- call delete('Xfile.bas')
- filetype off
-endfunc
-
-" Test dist#ft#FTcfg()
-func Test_cfg_file()
- filetype on
-
- " *.cfg defaults to cfg
- call writefile(['looks like cfg'], 'cfgfile.cfg')
- split cfgfile.cfg
- call assert_equal('cfg', &filetype)
-
- let g:filetype_cfg = 'other'
- edit
- call assert_equal('other', &filetype)
- bwipe!
- unlet g:filetype_cfg
-
- " RAPID cfg
- let ext = 'cfg'
- for i in ['EIO', 'MMC', 'MOC', 'PROC', 'SIO', 'SYS']
- call writefile([i .. ':CFG'], 'cfgfile.' .. ext)
- execute "split cfgfile." .. ext
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('cfgfile.' .. ext)
- " check different case of file extension
- let ext = substitute(ext, '\(\l\)', '\u\1', '')
- endfor
-
- " clean up
- filetype off
-endfunc
-
-func Test_d_file()
- filetype on
-
- call writefile(['looks like D'], 'Xfile.d')
- split Xfile.d
- call assert_equal('d', &filetype)
- bwipe!
-
- call writefile(['#!/some/bin/dtrace'], 'Xfile.d')
- split Xfile.d
- call assert_equal('dtrace', &filetype)
- bwipe!
-
- call writefile(['#pragma D option'], 'Xfile.d')
- split Xfile.d
- call assert_equal('dtrace', &filetype)
- bwipe!
-
- call writefile([':some:thing:'], 'Xfile.d')
- split Xfile.d
- call assert_equal('dtrace', &filetype)
- bwipe!
-
- call writefile(['module this', '#pragma D option'], 'Xfile.d')
- split Xfile.d
- call assert_equal('d', &filetype)
- bwipe!
-
- call writefile(['import that', '#pragma D option'], 'Xfile.d')
- split Xfile.d
- call assert_equal('d', &filetype)
- bwipe!
-
- " clean up
- filetype off
- call delete('Xfile.d')
-endfunc
-
-func Test_dat_file()
- filetype on
-
- " KRL header start with "&WORD", but is not always present.
- call writefile(['&ACCESS'], 'datfile.dat')
- split datfile.dat
- call assert_equal('krl', &filetype)
- bwipe!
- call delete('datfile.dat')
-
- " KRL defdat with leading spaces, for KRL file extension is not case
- " sensitive.
- call writefile([' DEFDAT datfile'], 'datfile.Dat')
- split datfile.Dat
- call assert_equal('krl', &filetype)
- bwipe!
- call delete('datfile.Dat')
-
- " KRL defdat with embedded spaces, file starts with empty line(s).
- call writefile(['', 'defdat datfile public'], 'datfile.DAT')
- split datfile.DAT
- call assert_equal('krl', &filetype)
- bwipe!
-
- " User may overrule file inspection
- let g:filetype_dat = 'dat'
- split datfile.DAT
- call assert_equal('dat', &filetype)
- bwipe!
- call delete('datfile.DAT')
- unlet g:filetype_dat
-
- filetype off
-endfunc
-
-func Test_dep3patch_file()
- filetype on
-
- call assert_true(mkdir('debian/patches', 'p'))
-
- " series files are not patches
- call writefile(['Description: some awesome patch'], 'debian/patches/series')
- split debian/patches/series
- call assert_notequal('dep3patch', &filetype)
- bwipe!
-
- " diff/patch files without the right headers should still show up as ft=diff
- call writefile([], 'debian/patches/foo.diff')
- split debian/patches/foo.diff
- call assert_equal('diff', &filetype)
- bwipe!
-
- " Files with the right headers are detected as dep3patch, even if they don't
- " have a diff/patch extension
- call writefile(['Subject: dep3patches'], 'debian/patches/bar')
- split debian/patches/bar
- call assert_equal('dep3patch', &filetype)
- bwipe!
-
- " Files in sub-directories are detected
- call assert_true(mkdir('debian/patches/s390x', 'p'))
- call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
- split debian/patches/s390x/bar
- call assert_equal('dep3patch', &filetype)
- bwipe!
-
- " The detection stops when seeing the "header end" marker
- call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
- split debian/patches/baz
- call assert_notequal('dep3patch', &filetype)
- bwipe!
-
- call delete('debian', 'rf')
-endfunc
-
-func Test_dsl_file()
- filetype on
-
- call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('dsl', &filetype)
- bwipe!
-
- call writefile(['workspace {'], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('structurizr', &filetype)
- bwipe!
-
- call delete('dslfile.dsl')
- filetype off
-endfunc
-
-func Test_ex_file()
- filetype on
-
- call writefile(['arbitrary content'], 'Xfile.ex')
- split Xfile.ex
- call assert_equal('elixir', &filetype)
- bwipe!
- let g:filetype_euphoria = 'euphoria4'
- split Xfile.ex
- call assert_equal('euphoria4', &filetype)
- bwipe!
- unlet g:filetype_euphoria
-
- call writefile(['-- filetype euphoria comment'], 'Xfile.ex')
- split Xfile.ex
- call assert_equal('euphoria3', &filetype)
- bwipe!
-
- call writefile(['--filetype euphoria comment'], 'Xfile.ex')
- split Xfile.ex
- call assert_equal('euphoria3', &filetype)
- bwipe!
-
- call writefile(['ifdef '], 'Xfile.ex')
- split Xfile.ex
- call assert_equal('euphoria3', &filetype)
- bwipe!
-
- call writefile(['include '], 'Xfile.ex')
- split Xfile.ex
- call assert_equal('euphoria3', &filetype)
- bwipe!
-
- call delete('Xfile.ex')
- filetype off
-endfunc
-
-func Test_foam_file()
- filetype on
- call assert_true(mkdir('0', 'p'))
- call assert_true(mkdir('0.orig', 'p'))
-
- call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict')
- split Xfile1Dict
- call assert_equal('foam', &filetype)
- bwipe!
-
- call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something')
- split Xfile1Dict.something
- call assert_equal('foam', &filetype)
- bwipe!
-
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
- split XfileProperties
- call assert_equal('foam', &filetype)
- bwipe!
-
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
- split XfileProperties.something
- call assert_equal('foam', &filetype)
- bwipe!
-
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
- split XfileProperties
- call assert_equal('foam', &filetype)
- bwipe!
-
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
- split XfileProperties.something
- call assert_equal('foam', &filetype)
- bwipe!
-
- call writefile(['FoamFile {', ' object something;'], '0/Xfile')
- split 0/Xfile
- call assert_equal('foam', &filetype)
- bwipe!
-
- call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile')
- split 0.orig/Xfile
- call assert_equal('foam', &filetype)
- bwipe!
-
- call delete('0', 'rf')
- call delete('0.orig', 'rf')
- call delete('Xfile1Dict')
- call delete('Xfile1Dict.something')
- call delete('XfileProperties')
- call delete('XfileProperties.something')
- filetype off
-endfunc
-
-func Test_frm_file()
- filetype on
-
- call writefile(['looks like FORM'], 'Xfile.frm')
- split Xfile.frm
- call assert_equal('form', &filetype)
- bwipe!
-
- " Test dist#ft#FTfrm()
-
- let g:filetype_frm = 'form'
- split Xfile.frm
- call assert_equal('form', &filetype)
- bwipe!
- unlet g:filetype_frm
-
- " Visual Basic
-
- call writefile(['Begin VB.Form Form1'], 'Xfile.frm')
- split Xfile.frm
- call assert_equal('vb', &filetype)
- bwipe!
-
- call delete('Xfile.frm')
- filetype off
-endfunc
-
-func Test_fs_file()
- filetype on
-
- call writefile(['looks like F#'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('fsharp', &filetype)
- bwipe!
-
- let g:filetype_fs = 'forth'
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
- unlet g:filetype_fs
-
- " Test dist#ft#FTfs()
-
- " Forth (Gforth)
-
- call writefile(['( Forth inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile(['\ Forth line comment'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- " empty line comment - no space required
- call writefile(['\'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile(['\G Forth documentation comment '], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call delete('Xfile.fs')
- filetype off
-endfunc
-
-func Test_git_file()
- filetype on
-
- call assert_true(mkdir('Xrepo.git', 'p'))
-
- call writefile([], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('', &filetype)
- bwipe!
-
- call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('git', &filetype)
- bwipe!
-
- call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('git', &filetype)
- bwipe!
-
- call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('git', &filetype)
- bwipe!
-
- call delete('Xrepo.git', 'rf')
- filetype off
-endfunc
-
-func Test_hook_file()
- filetype on
-
- call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
- split Xfile.hook
- call assert_equal('conf', &filetype)
- bwipe!
-
- call writefile(['not pacman'], 'Xfile.hook')
- split Xfile.hook
- call assert_notequal('conf', &filetype)
- bwipe!
-
- call delete('Xfile.hook')
- filetype off
-endfunc
-
-func Test_m_file()
- filetype on
-
- call writefile(['looks like Matlab'], 'Xfile.m')
- split Xfile.m
- call assert_equal('matlab', &filetype)
- bwipe!
-
- let g:filetype_m = 'octave'
- split Xfile.m
- call assert_equal('octave', &filetype)
- bwipe!
- unlet g:filetype_m
-
- " Test dist#ft#FTm()
-
- " Objective-C
-
- call writefile(['// Objective-C line comment'], 'Xfile.m')
- split Xfile.m
- call assert_equal('objc', &filetype)
- bwipe!
-
- call writefile(['/* Objective-C block comment */'], 'Xfile.m')
- split Xfile.m
- call assert_equal('objc', &filetype)
- bwipe!
-
- call writefile(['#import "test.m"'], 'Xfile.m')
- split Xfile.m
- call assert_equal('objc', &filetype)
- bwipe!
-
- call writefile(['#include <header.h>'], 'Xfile.m')
- split Xfile.m
- call assert_equal('objc', &filetype)
- bwipe!
-
- call writefile(['#define FORTY_TWO'], 'Xfile.m')
- split Xfile.m
- call assert_equal('objc', &filetype)
- bwipe!
-
- " Octave
-
- call writefile(['# Octave line comment'], 'Xfile.m')
- split Xfile.m
- call assert_equal('octave', &filetype)
- bwipe!
-
- call writefile(['%!test "Octave test"'], 'Xfile.m')
- split Xfile.m
- call assert_equal('octave', &filetype)
- bwipe!
-
- call writefile(['unwind_protect'], 'Xfile.m')
- split Xfile.m
- call assert_equal('octave', &filetype)
- bwipe!
-
- call writefile(['try; 42; end_try_catch'], 'Xfile.m')
- split Xfile.m
- call assert_equal('octave', &filetype)
- bwipe!
-
- " Mathematica
-
- call writefile(['(* Mathematica comment'], 'Xfile.m')
- split Xfile.m
- call assert_equal('mma', &filetype)
- bwipe!
-
- " MATLAB
-
- call writefile(['% MATLAB line comment'], 'Xfile.m')
- split Xfile.m
- call assert_equal('matlab', &filetype)
- bwipe!
-
- " Murphi
-
- call writefile(['-- Murphi comment'], 'Xfile.m')
- split Xfile.m
- call assert_equal('murphi', &filetype)
- bwipe!
-
- call writefile(['/* Murphi block comment */', 'Type'], 'Xfile.m')
- split Xfile.m
- call assert_equal('murphi', &filetype)
- bwipe!
-
- call writefile(['Type'], 'Xfile.m')
- split Xfile.m
- call assert_equal('murphi', &filetype)
- bwipe!
-
- call delete('Xfile.m')
- filetype off
-endfunc
-
-func Test_mod_file()
- filetype on
-
- " *.mod defaults to Modsim III
- call writefile(['locks like Modsim III'], 'modfile.mod')
- split modfile.mod
- call assert_equal('modsim3', &filetype)
- bwipe!
-
- " Users preference set by g:filetype_mod
- let g:filetype_mod = 'lprolog'
- split modfile.mod
- call assert_equal('lprolog', &filetype)
- unlet g:filetype_mod
- bwipe!
-
- " RAPID header start with a line containing only "%%%",
- " but is not always present.
- call writefile(['%%%'], 'modfile.mod')
- split modfile.mod
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('modfile.mod')
-
- " RAPID supports umlauts in module names, leading spaces,
- " the .mod extension is not case sensitive.
- call writefile([' module ÜmlautModule'], 'modfile.Mod')
- split modfile.Mod
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('modfile.Mod')
-
- " RAPID is not case sensitive, embedded spaces, sysmodule,
- " file starts with empty line(s).
- call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'modfile.MOD')
- split modfile.MOD
- call assert_equal('rapid', &filetype)
- bwipe!
-
- " Modula-2 MODULE not start of line
- call writefile(['IMPLEMENTATION MODULE Module2Mod;'], 'modfile.MOD')
- split modfile.MOD
- call assert_equal('modula2', &filetype)
- bwipe!
-
- " Modula-2 with comment and empty lines prior MODULE
- call writefile(['', '(* with', ' comment *)', '', 'MODULE Module2Mod;'], 'modfile.MOD')
- split modfile.MOD
- call assert_equal('modula2', &filetype)
- bwipe!
- call delete('modfile.MOD')
-
- " LambdaProlog module
- call writefile(['module lpromod.'], 'modfile.mod')
- split modfile.mod
- call assert_equal('lprolog', &filetype)
- bwipe!
-
- " LambdaProlog with comment and empty lines prior module
- call writefile(['', '% with', '% comment', '', 'module lpromod.'], 'modfile.mod')
- split modfile.mod
- call assert_equal('lprolog', &filetype)
- bwipe!
- call delete('modfile.mod')
-
- " go.mod
- call writefile(['module example.com/M'], 'go.mod')
- split go.mod
- call assert_equal('gomod', &filetype)
- bwipe!
- call delete('go.mod')
-
- filetype off
-endfunc
-
-func Test_patch_file()
- filetype on
-
- call writefile([], 'Xfile.patch')
- split Xfile.patch
- call assert_equal('diff', &filetype)
- bwipe!
-
- call writefile(['From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
- split Xfile.patch
- call assert_equal('gitsendemail', &filetype)
- bwipe!
-
- call writefile(['From 0000000000000000000000000000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
- split Xfile.patch
- call assert_equal('gitsendemail', &filetype)
- bwipe!
-
- call delete('Xfile.patch')
- filetype off
-endfunc
-
-func Test_perl_file()
- filetype on
-
- " only tests one case, should do more
- let lines =<< trim END
-
- use a
- END
- call writefile(lines, "Xfile.t")
- split Xfile.t
- call assert_equal('perl', &filetype)
- bwipe
-
- call delete('Xfile.t')
- filetype off
-endfunc
-
-func Test_pp_file()
- filetype on
-
- call writefile(['looks like puppet'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('puppet', &filetype)
- bwipe!
-
- let g:filetype_pp = 'pascal'
- split Xfile.pp
- call assert_equal('pascal', &filetype)
- bwipe!
- unlet g:filetype_pp
-
- " Test dist#ft#FTpp()
- call writefile(['{ pascal comment'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
- bwipe!
-
- call writefile(['procedure pascal'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
- bwipe!
-
- call delete('Xfile.pp')
- filetype off
-endfunc
-
-" Test dist#ft#FTprg()
-func Test_prg_file()
- filetype on
-
- " *.prg defaults to clipper
- call writefile(['looks like clipper'], 'prgfile.prg')
- split prgfile.prg
- call assert_equal('clipper', &filetype)
- bwipe!
-
- " Users preference set by g:filetype_prg
- let g:filetype_prg = 'eviews'
- split prgfile.prg
- call assert_equal('eviews', &filetype)
- unlet g:filetype_prg
- bwipe!
-
- " RAPID header start with a line containing only "%%%",
- " but is not always present.
- call writefile(['%%%'], 'prgfile.prg')
- split prgfile.prg
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('prgfile.prg')
-
- " RAPID supports umlauts in module names, leading spaces,
- " the .prg extension is not case sensitive.
- call writefile([' module ÜmlautModule'], 'prgfile.Prg')
- split prgfile.Prg
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('prgfile.Prg')
-
- " RAPID is not case sensitive, embedded spaces, sysmodule,
- " file starts with empty line(s).
- call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'prgfile.PRG')
- split prgfile.PRG
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('prgfile.PRG')
-
- filetype off
-endfunc
-
-" Test dist#ft#FTsc()
-func Test_sc_file()
- filetype on
-
- " SC classes are defined with '+ Class {}'
- call writefile(['+ SCNvim {', '*methodArgs {|method|'], 'srcfile.sc')
- split srcfile.sc
- call assert_equal('supercollider', &filetype)
- bwipe!
- call delete('srcfile.sc')
-
- " Some SC class files start with comment and define methods many lines later
- call writefile(['// Query', '//Method','^this {'], 'srcfile.sc')
- split srcfile.sc
- call assert_equal('supercollider', &filetype)
- bwipe!
- call delete('srcfile.sc')
-
- " Some SC class files put comments between method declaration after class
- call writefile(['PingPong {', '//comment','*ar { arg'], 'srcfile.sc')
- split srcfile.sc
- call assert_equal('supercollider', &filetype)
- bwipe!
- call delete('srcfile.sc')
-
- filetype off
-endfunc
-
-" Test dist#ft#FTscd()
-func Test_scd_file()
- filetype on
-
- call writefile(['ijq(1)'], 'srcfile.scd')
- split srcfile.scd
- call assert_equal('scdoc', &filetype)
- bwipe!
- call delete('srcfile.scd')
-
- filetype off
-endfunc
-
-func Test_src_file()
- filetype on
-
- " KRL header start with "&WORD", but is not always present.
- call writefile(['&ACCESS'], 'srcfile.src')
- split srcfile.src
- call assert_equal('krl', &filetype)
- bwipe!
- call delete('srcfile.src')
-
- " KRL def with leading spaces, for KRL file extension is not case sensitive.
- call writefile([' DEF srcfile()'], 'srcfile.Src')
- split srcfile.Src
- call assert_equal('krl', &filetype)
- bwipe!
- call delete('srcfile.Src')
-
- " KRL global deffct with embedded spaces, file starts with empty line(s).
- for text in ['global def srcfile()', 'global deffct srcfile()']
- call writefile(['', text], 'srcfile.SRC')
- split srcfile.SRC
- call assert_equal('krl', &filetype, text)
- bwipe!
- endfor
-
- " User may overrule file inspection
- let g:filetype_src = 'src'
- split srcfile.SRC
- call assert_equal('src', &filetype)
- bwipe!
- call delete('srcfile.SRC')
- unlet g:filetype_src
-
- filetype off
-endfunc
-
-func Test_sys_file()
- filetype on
-
- " *.sys defaults to Batch file for MSDOS
- call writefile(['looks like dos batch'], 'sysfile.sys')
- split sysfile.sys
- call assert_equal('bat', &filetype)
- bwipe!
-
- " Users preference set by g:filetype_sys
- let g:filetype_sys = 'sys'
- split sysfile.sys
- call assert_equal('sys', &filetype)
- unlet g:filetype_sys
- bwipe!
-
- " RAPID header start with a line containing only "%%%",
- " but is not always present.
- call writefile(['%%%'], 'sysfile.sys')
- split sysfile.sys
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('sysfile.sys')
-
- " RAPID supports umlauts in module names, leading spaces,
- " the .sys extension is not case sensitive.
- call writefile([' module ÜmlautModule'], 'sysfile.Sys')
- split sysfile.Sys
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('sysfile.Sys')
-
- " RAPID is not case sensitive, embedded spaces, sysmodule,
- " file starts with empty line(s).
- call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'sysfile.SYS')
- split sysfile.SYS
- call assert_equal('rapid', &filetype)
- bwipe!
- call delete('sysfile.SYS')
-
- filetype off
-endfunc
-
-func Test_tex_file()
- filetype on
-
- call writefile(['%& pdflatex'], 'Xfile.tex')
- split Xfile.tex
- call assert_equal('tex', &filetype)
- bwipe
-
- call writefile(['\newcommand{\test}{some text}'], 'Xfile.tex')
- split Xfile.tex
- call assert_equal('tex', &filetype)
- bwipe
-
- " tex_flavor is unset
- call writefile(['%& plain'], 'Xfile.tex')
- split Xfile.tex
- call assert_equal('plaintex', &filetype)
- bwipe
-
- let g:tex_flavor = 'plain'
- call writefile(['just some text'], 'Xfile.tex')
- split Xfile.tex
- call assert_equal('plaintex', &filetype)
- bwipe
-
- let lines =<< trim END
- % This is a comment.
-
- \usemodule[translate]
- END
- call writefile(lines, 'Xfile.tex')
- split Xfile.tex
- call assert_equal('context', &filetype)
- bwipe
-
- let g:tex_flavor = 'context'
- call writefile(['just some text'], 'Xfile.tex')
- split Xfile.tex
- call assert_equal('context', &filetype)
- bwipe
- unlet g:tex_flavor
-
- call delete('Xfile.tex')
- filetype off
-endfunc
-
-func Test_tf_file()
- filetype on
-
- call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf')
- split Xfile.tf
- call assert_equal('tf', &filetype)
- bwipe!
-
- call writefile(['provider "azurerm" {'], 'Xfile.tf')
- split Xfile.tf
- call assert_equal('terraform', &filetype)
- bwipe!
-
- call delete('Xfile.tf')
- filetype off
-endfunc
-
-func Test_ts_file()
- filetype on
-
- call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('xml', &filetype)
- bwipe!
-
- call writefile(['// looks like Typescript'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('typescript', &filetype)
- bwipe!
-
- call delete('Xfile.ts')
- filetype off
-endfunc
-
-func Test_ttl_file()
- filetype on
-
- call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('turtle', &filetype)
- bwipe!
-
- call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('teraterm', &filetype)
- bwipe!
-
- call delete('Xfile.ttl')
- filetype off
-endfunc
-
-func Test_xpm_file()
- filetype on
-
- call writefile(['this is XPM2'], 'file.xpm')
- split file.xpm
- call assert_equal('xpm2', &filetype)
- bwipe!
-
- call delete('file.xpm')
- filetype off
-endfunc
-
-func Test_cls_file()
- filetype on
-
- call writefile(['looks like Smalltalk'], 'Xfile.cls')
- split Xfile.cls
- call assert_equal('st', &filetype)
- bwipe!
-
- " Test dist#ft#FTcls()
-
- let g:filetype_cls = 'vb'
- split Xfile.cls
- call assert_equal('vb', &filetype)
- bwipe!
- unlet g:filetype_cls
-
- " TeX
-
- call writefile(['%'], 'Xfile.cls')
- split Xfile.cls
- call assert_equal('tex', &filetype)
- bwipe!
-
- call writefile(['\NeedsTeXFormat{LaTeX2e}'], 'Xfile.cls')
- split Xfile.cls
- call assert_equal('tex', &filetype)
- bwipe!
-
- " Rexx
-
- call writefile(['# rexx'], 'Xfile.cls')
- split Xfile.cls
- call assert_equal('rexx', &filetype)
- bwipe!
-
- " Visual Basic
-
- call writefile(['VERSION 1.0 CLASS'], 'Xfile.cls')
- split Xfile.cls
- call assert_equal('vb', &filetype)
- bwipe!
-
- call delete('Xfile.cls')
- filetype off
-endfunc
-
-func Test_sig_file()
- filetype on
-
- call writefile(['this is neither Lambda Prolog nor SML'], 'Xfile.sig')
- split Xfile.sig
- call assert_equal('', &filetype)
- bwipe!
-
- " Test dist#ft#FTsig()
-
- let g:filetype_sig = 'sml'
- split Xfile.sig
- call assert_equal('sml', &filetype)
- bwipe!
- unlet g:filetype_sig
-
- " Lambda Prolog
-
- call writefile(['sig foo.'], 'Xfile.sig')
- split Xfile.sig
- call assert_equal('lprolog', &filetype)
- bwipe!
-
- call writefile(['/* ... */'], 'Xfile.sig')
- split Xfile.sig
- call assert_equal('lprolog', &filetype)
- bwipe!
-
- call writefile(['% ...'], 'Xfile.sig')
- split Xfile.sig
- call assert_equal('lprolog', &filetype)
- bwipe!
-
- " SML signature file
-
- call writefile(['signature FOO ='], 'Xfile.sig')
- split Xfile.sig
- call assert_equal('sml', &filetype)
- bwipe!
-
- call writefile(['structure FOO ='], 'Xfile.sig')
- split Xfile.sig
- call assert_equal('sml', &filetype)
- bwipe!
-
- call writefile(['(* ... *)'], 'Xfile.sig')
- split Xfile.sig
- call assert_equal('sml', &filetype)
- bwipe!
-
- call delete('Xfile.sig')
- filetype off
-endfunc
-
-" Test dist#ft#FTsil()
-func Test_sil_file()
- filetype on
-
- split Xfile.sil
- call assert_equal('sil', &filetype)
- bwipe!
-
- let lines =<< trim END
- // valid
- let protoErasedPathA = \ABCProtocol.a
-
- // also valid
- let protoErasedPathA =
- \ABCProtocol.a
- END
- call writefile(lines, 'Xfile.sil')
-
- split Xfile.sil
- call assert_equal('sil', &filetype)
- bwipe!
-
- " SILE
-
- call writefile(['% some comment'], 'Xfile.sil')
- split Xfile.sil
- call assert_equal('sile', &filetype)
- bwipe!
-
- call writefile(['\begin[papersize=a6]{document}foo\end{document}'], 'Xfile.sil')
- split Xfile.sil
- call assert_equal('sile', &filetype)
- bwipe!
-
- call delete('Xfile.sil')
- filetype off
-endfunc
-
-func Test_inc_file()
- filetype on
-
- call writefile(['this is the fallback'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('pov', &filetype)
- bwipe!
-
- let g:filetype_inc = 'foo'
- split Xfile.inc
- call assert_equal('foo', &filetype)
- bwipe!
- unlet g:filetype_inc
-
- " aspperl
- call writefile(['perlscript'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('aspperl', &filetype)
- bwipe!
-
- " aspvbs
- call writefile(['<% something'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('aspvbs', &filetype)
- bwipe!
-
- " php
- call writefile(['<?php'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('php', &filetype)
- bwipe!
-
- " pascal
- call writefile(['program'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('pascal', &filetype)
- bwipe!
-
- " bitbake
- call writefile(['require foo'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('bitbake', &filetype)
- bwipe!
-
- call writefile(['S = "${WORKDIR}"'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('bitbake', &filetype)
- bwipe!
-
- call writefile(['DEPENDS:append = " somedep"'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('bitbake', &filetype)
- bwipe!
-
- call writefile(['MACHINE ??= "qemu"'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('bitbake', &filetype)
- bwipe!
-
- call writefile(['PROVIDES := "test"'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('bitbake', &filetype)
- bwipe!
-
- call writefile(['RDEPENDS_${PN} += "bar"'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('bitbake', &filetype)
- bwipe!
-
- " asm
- call writefile(['asmsyntax=bar'], 'Xfile.inc')
- split Xfile.inc
- call assert_equal('bar', &filetype)
- bwipe!
-
- call delete('Xfile.inc')
- filetype off
-endfunc
-
-func Test_lsl_file()
- filetype on
-
- call writefile(['looks like Linden Scripting Language'], 'Xfile.lsl')
- split Xfile.lsl
- call assert_equal('lsl', &filetype)
- bwipe!
-
- " Test dist#ft#FTlsl()
-
- let g:filetype_lsl = 'larch'
- split Xfile.lsl
- call assert_equal('larch', &filetype)
- bwipe!
- unlet g:filetype_lsl
-
- " Larch Shared Language
-
- call writefile(['% larch comment'], 'Xfile.lsl')
- split Xfile.lsl
- call assert_equal('larch', &filetype)
- bwipe!
-
- call writefile(['foo: trait'], 'Xfile.lsl')
- split Xfile.lsl
- call assert_equal('larch', &filetype)
- bwipe!
-
- call delete('Xfile.lsl')
- filetype off
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim
deleted file mode 100644
index dae164b11c..0000000000
--- a/src/nvim/testdir/test_filter_cmd.vim
+++ /dev/null
@@ -1,190 +0,0 @@
-" Test the :filter command modifier
-
-func Test_filter()
- edit Xdoesnotmatch
- edit Xwillmatch
- call assert_equal('"Xwillmatch"', substitute(execute('filter willma ls'), '[^"]*\(".*"\)[^"]*', '\1', ''))
- bwipe Xdoesnotmatch
- bwipe Xwillmatch
-
- new
- call setline(1, ['foo1', 'foo2', 'foo3', 'foo4', 'foo5'])
- call assert_equal("\nfoo2\nfoo4", execute('filter /foo[24]/ 1,$print'))
- call assert_equal("\n 2 foo2\n 4 foo4", execute('filter /foo[24]/ 1,$number'))
- call assert_equal("\nfoo2$\nfoo4$", execute('filter /foo[24]/ 1,$list'))
-
- call assert_equal("\nfoo1$\nfoo3$\nfoo5$", execute('filter! /foo[24]/ 1,$list'))
- bwipe!
-
- command XTryThis echo 'this'
- command XTryThat echo 'that'
- command XDoThat echo 'that'
- let lines = split(execute('filter XTry command'), "\n")
- call assert_equal(3, len(lines))
- call assert_match("XTryThat", lines[1])
- call assert_match("XTryThis", lines[2])
- delcommand XTryThis
- delcommand XTryThat
- delcommand XDoThat
-
- map f1 the first key
- map f2 the second key
- map f3 not a key
- let lines = split(execute('filter the map f'), "\n")
- call assert_equal(2, len(lines))
- call assert_match("f2", lines[0])
- call assert_match("f1", lines[1])
- unmap f1
- unmap f2
- unmap f3
-endfunc
-
-func Test_filter_fails()
- call assert_fails('filter', 'E471:')
- call assert_fails('filter pat', 'E476:')
- call assert_fails('filter /pat', 'E476:')
- call assert_fails('filter /pat/', 'E476:')
- call assert_fails('filter /pat/ asdf', 'E492:')
- " Using assert_fails() causes E476 instead of E866. So use a try-catch.
- let caught_e866 = 0
- try
- filter /\@>b/ ls
- catch /E866:/
- let caught_e866 = 1
- endtry
- call assert_equal(1, caught_e866)
-
- call assert_fails('filter!', 'E471:')
- call assert_fails('filter! pat', 'E476:')
- call assert_fails('filter! /pat', 'E476:')
- call assert_fails('filter! /pat/', 'E476:')
- call assert_fails('filter! /pat/ asdf', 'E492:')
-endfunc
-
-function s:complete_filter_cmd(filtcmd)
- let keystroke = "\<TAB>\<C-R>=execute('let cmdline = getcmdline()')\<CR>\<C-C>"
- let cmdline = ''
- call feedkeys(':' . a:filtcmd . keystroke, 'ntx')
- return cmdline
-endfunction
-
-func Test_filter_cmd_completion()
- " Do not complete pattern
- call assert_equal("filter \t", s:complete_filter_cmd('filter '))
- call assert_equal("filter pat\t", s:complete_filter_cmd('filter pat'))
- call assert_equal("filter /pat\t", s:complete_filter_cmd('filter /pat'))
- call assert_equal("filter /pat/\t", s:complete_filter_cmd('filter /pat/'))
-
- " Complete after string pattern
- call assert_equal('filter pat print', s:complete_filter_cmd('filter pat pri'))
-
- " Complete after regexp pattern
- call assert_equal('filter /pat/ print', s:complete_filter_cmd('filter /pat/ pri'))
- call assert_equal('filter #pat# print', s:complete_filter_cmd('filter #pat# pri'))
-endfunc
-
-func Test_filter_cmd_with_filter()
- new
- set shelltemp
- %!echo "a|b"
- let out = getline(1)
- bw!
- if has('win32')
- let out = trim(out, '" ')
- endif
- call assert_equal('a|b', out)
- set shelltemp&
-endfunction
-
-func Test_filter_commands()
- let g:test_filter_a = 1
- let b:test_filter_b = 2
- let test_filter_c = 3
-
- " Test filtering :let command
- let res = split(execute("filter /^test_filter/ let"), "\n")
- call assert_equal(["test_filter_a #1"], res)
-
- let res = split(execute("filter /\\v^(b:)?test_filter/ let"), "\n")
- call assert_equal(["test_filter_a #1", "b:test_filter_b #2"], res)
-
- unlet g:test_filter_a
- unlet b:test_filter_b
- unlet test_filter_c
-
- " Test filtering :set command
- let helplang=&helplang
- set helplang=en
- let res = join(split(execute("filter /^help/ set"), "\n")[1:], " ")
- call assert_match('^\s*helplang=\w*$', res)
- let &helplang=helplang
-
- " Test filtering :llist command
- call setloclist(0, [{"filename": "/path/vim.c"}, {"filename": "/path/vim.h"}, {"module": "Main.Test"}])
- let res = split(execute("filter /\\.c$/ llist"), "\n")
- call assert_equal([" 1 /path/vim.c: "], res)
-
- let res = split(execute("filter /\\.Test$/ llist"), "\n")
- call assert_equal([" 3 Main.Test: "], res)
-
- " Test filtering :jump command
- e file.c
- e file.h
- e file.hs
- let res = split(execute("filter /\.c$/ jumps"), "\n")[1:]
- call assert_equal([" 2 1 0 file.c", ">"], res)
-
- " Test filtering :marks command
- b file.c
- mark A
- b file.h
- mark B
- let res = split(execute("filter /\.c$/ marks"), "\n")[1:]
- call assert_equal([" A 1 0 file.c"], res)
-
- call setline(1, ['one', 'two', 'three'])
- 1mark a
- 2mark b
- 3mark c
- let res = split(execute("filter /two/ marks abc"), "\n")[1:]
- call assert_equal([" b 2 0 two"], res)
-
- bwipe! file.c
- bwipe! file.h
- bwipe! file.hs
-endfunc
-
-func Test_filter_display()
- edit Xdoesnotmatch
- let @a = '!!willmatch'
- let @b = '!!doesnotmatch'
- let @c = "oneline\ntwoline\nwillmatch\n"
- let @/ = '!!doesnotmatch'
- call feedkeys(":echo '!!doesnotmatch:'\<CR>", 'ntx')
- let lines = map(split(execute('filter /willmatch/ display'), "\n"), 'v:val[5:6]')
-
- call assert_true(index(lines, '"a') >= 0)
- call assert_false(index(lines, '"b') >= 0)
- call assert_true(index(lines, '"c') >= 0)
- call assert_false(index(lines, '"/') >= 0)
- call assert_false(index(lines, '":') >= 0)
- call assert_false(index(lines, '"%') >= 0)
-
- let lines = map(split(execute('filter /doesnotmatch/ display'), "\n"), 'v:val[5:6]')
- call assert_true(index(lines, '"a') < 0)
- call assert_false(index(lines, '"b') < 0)
- call assert_true(index(lines, '"c') < 0)
- call assert_false(index(lines, '"/') < 0)
- call assert_false(index(lines, '":') < 0)
- call assert_false(index(lines, '"%') < 0)
-
- bwipe!
-endfunc
-
-func Test_filter_scriptnames()
- let lines = split(execute('filter /test_filter_cmd/ scriptnames'), "\n")
- call assert_equal(1, len(lines))
- call assert_match('filter_cmd', lines[0])
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
deleted file mode 100644
index c75177ea39..0000000000
--- a/src/nvim/testdir/test_filter_map.vim
+++ /dev/null
@@ -1,108 +0,0 @@
-" Test filter() and map()
-
-" list with expression string
-func Test_filter_map_list_expr_string()
- " filter()
- call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
- call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
- call assert_equal([], filter([1, 2, 3, 4], 0))
-
- " map()
- call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
- call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
- call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
- call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
-endfunc
-
-" dict with expression string
-func Test_filter_map_dict_expr_string()
- let dict = {"foo": 1, "bar": 2, "baz": 3}
-
- " filter()
- call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
- call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
- call assert_equal({}, filter(copy(dict), 0))
-
- " map()
- call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
- call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
- call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
-endfunc
-
-" list with funcref
-func Test_filter_map_list_expr_funcref()
- " filter()
- func! s:filter1(index, val) abort
- return a:val > 1
- endfunc
- call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
-
- func! s:filter2(index, val) abort
- return a:index > 1
- endfunc
- call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
-
- " map()
- func! s:filter3(index, val) abort
- return a:val * 2
- endfunc
- call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
-
- func! s:filter4(index, val) abort
- return a:index * 2
- endfunc
- call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
-endfunc
-
-" dict with funcref
-func Test_filter_map_dict_expr_funcref()
- let dict = {"foo": 1, "bar": 2, "baz": 3}
-
- " filter()
- func! s:filter1(key, val) abort
- return a:val > 1
- endfunc
- call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
-
- func! s:filter2(key, val) abort
- return a:key > "bar"
- endfunc
- call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
-
- " map()
- func! s:filter3(key, val) abort
- return a:val * 2
- endfunc
- call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
-
- func! s:filter4(key, val) abort
- return a:key[0]
- endfunc
- call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
-endfunc
-
-func Test_map_filter_fails()
- call assert_fails('call map([1], "42 +")', 'E15:')
- call assert_fails('call filter([1], "42 +")', 'E15:')
- call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:')
- call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:')
- call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
- call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
- call assert_fails("let l = filter([1, 2], {})", 'E731:')
- call assert_equal(v:_null_list, filter(v:_null_list, 0))
- call assert_equal(v:_null_dict, filter(v:_null_dict, 0))
- call assert_equal(v:_null_list, map(v:_null_list, '"> " .. v:val'))
- call assert_equal(v:_null_dict, map(v:_null_dict, '"> " .. v:val'))
-endfunc
-
-func Test_map_and_modify()
- let l = ["abc"]
- " cannot change the list halfway a map()
- call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:')
-
- let d = #{a: 1, b: 2, c: 3}
- call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:')
- call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_find_complete.vim b/src/nvim/testdir/test_find_complete.vim
deleted file mode 100644
index 32ca9672ef..0000000000
--- a/src/nvim/testdir/test_find_complete.vim
+++ /dev/null
@@ -1,163 +0,0 @@
-" Tests for the 'find' command completion.
-
-" Do all the tests in a separate window to avoid E211 when we recursively
-" delete the Xfind directory during cleanup
-func Test_find_complete()
- set belloff=all
-
- " On windows a stale "Xfind" directory may exist, remove it so that
- " we start from a clean state.
- call delete("Xfind", "rf")
- let cwd = getcwd()
- let test_out = cwd . '/test.out'
- call mkdir('Xfind')
- cd Xfind
-
- new
- set path=
- call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
- close
-
- new
- set path=.
- call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
- close
-
- new
- set path=.,,
- call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
- close
-
- new
- set path=./**
- call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
- close
-
- " We shouldn't find any file till this point
-
- call mkdir('in/path', 'p')
- call chdir(cwd)
- call writefile(['Holy Grail'], 'Xfind/file.txt')
- call writefile(['Jimmy Hoffa'], 'Xfind/in/file.txt')
- call writefile(['Another Holy Grail'], 'Xfind/in/stuff.txt')
- call writefile(['E.T.'], 'Xfind/in/path/file.txt')
-
- new
- set path=Xfind/**
- call feedkeys(":find file\t\n", "xt")
- call assert_equal('Holy Grail', getline(1))
- call feedkeys(":find file\t\t\n", "xt")
- call assert_equal('Jimmy Hoffa', getline(1))
- call feedkeys(":find file\t\t\t\n", "xt")
- call assert_equal('E.T.', getline(1))
-
- " Rerun the previous three find completions, using fullpath in 'path'
- exec "set path=" . cwd . "/Xfind/**"
-
- call feedkeys(":find file\t\n", "xt")
- call assert_equal('Holy Grail', getline(1))
- call feedkeys(":find file\t\t\n", "xt")
- call assert_equal('Jimmy Hoffa', getline(1))
- call feedkeys(":find file\t\t\t\n", "xt")
- call assert_equal('E.T.', getline(1))
-
- " Same steps again, using relative and fullpath items that point to the same
- " recursive location.
- " This is to test that there are no duplicates in the completion list.
- set path+=Xfind/**
- call feedkeys(":find file\t\n", "xt")
- call assert_equal('Holy Grail', getline(1))
- call feedkeys(":find file\t\t\n", "xt")
- call assert_equal('Jimmy Hoffa', getline(1))
- call feedkeys(":find file\t\t\t\n", "xt")
- call assert_equal('E.T.', getline(1))
- call feedkeys(":find file\t\t\n", "xt")
-
- " Test find completion for directory of current buffer, which at this point
- " is Xfind/in/file.txt.
- set path=.
- call feedkeys(":find st\t\n", "xt")
- call assert_equal('Another Holy Grail', getline(1))
-
- " Test find completion for empty path item ",," which is the current
- " directory
- cd Xfind
- set path=,,
- call feedkeys(":find f\t\n", "xt")
- call assert_equal('Holy Grail', getline(1))
-
- " Test that find completion on directory appends a slash
- call feedkeys(":find in/pa\tfile.txt\n", "xt")
- call assert_equal('E.T.', getline(1))
- call feedkeys(":find ./i\tstuff.txt\n", "xt")
- call assert_equal('Another Holy Grail', getline(1))
-
- " Test shortening of
- "
- " foo/x/bar/voyager.txt
- " foo/y/bar/voyager.txt
- "
- " When current directory is above foo/ they should be shortened to (in order
- " of appearance):
- "
- " x/bar/voyager.txt
- " y/bar/voyager.txt
- call mkdir('foo/x/bar', 'p')
- call mkdir('foo/y/bar', 'p')
- call writefile(['Voyager 1'], 'foo/x/bar/voyager.txt')
- call writefile(['Voyager 2'], 'foo/y/bar/voyager.txt')
-
- exec "set path=" . cwd . "/Xfind/**"
- call feedkeys(":find voyager\t\n", "xt")
- call assert_equal('Voyager 1', getline(1))
- call feedkeys(":find voyager\t\t\n", "xt")
- call assert_equal('Voyager 2', getline(1))
-
- "
- " When current directory is .../foo/y/bar they should be shortened to (in
- " order of appearance):
- "
- " ./voyager.txt
- " x/bar/voyager.txt
- cd foo/y/bar
- call feedkeys(":find voyager\t\n", "xt")
- call assert_equal('Voyager 2', getline(1))
- call feedkeys(":find voyager\t\t\n", "xt")
- call assert_equal('Voyager 1', getline(1))
-
- " Check the opposite too:
- cd ../../x/bar
- call feedkeys(":find voyager\t\n", "xt")
- call assert_equal('Voyager 1', getline(1))
- call feedkeys(":find voyager\t\t\n", "xt")
- call assert_equal('Voyager 2', getline(1))
-
- " Check for correct handling of shorten_fname()'s behavior on windows
- call chdir(cwd .. "/Xfind/in")
- call feedkeys(":find file\t\n", "xt")
- call assert_equal('Jimmy Hoffa', getline(1))
-
- " Test for relative to current buffer 'path' item
- call chdir(cwd . "/Xfind/")
- set path=./path
- " Open the file where Jimmy Hoffa is found
- e in/file.txt
- " Find the file containing 'E.T.' in the Xfind/in/path directory
- call feedkeys(":find file\t\n", "xt")
- call assert_equal('E.T.', getline(1))
-
- " Test that completion works when path=.,,
- set path=.,,
- " Open Jimmy Hoffa file
- e in/file.txt
- call assert_equal('Jimmy Hoffa', getline(1))
-
- " Search for the file containing Holy Grail in same directory as in/path.txt
- call feedkeys(":find stu\t\n", "xt")
- call assert_equal('Another Holy Grail', getline(1))
-
- enew | only
- call chdir(cwd)
- call delete('Xfind', 'rf')
- set path&
-endfunc
diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim
deleted file mode 100644
index 0f4b30aec2..0000000000
--- a/src/nvim/testdir/test_findfile.vim
+++ /dev/null
@@ -1,255 +0,0 @@
-" Test findfile() and finddir()
-
-let s:files = [ 'Xdir1/foo',
- \ 'Xdir1/bar',
- \ 'Xdir1/Xdir2/foo',
- \ 'Xdir1/Xdir2/foobar',
- \ 'Xdir1/Xdir2/Xdir3/bar',
- \ 'Xdir1/Xdir2/Xdir3/barfoo' ]
-
-func CreateFiles()
- call mkdir('Xdir1/Xdir2/Xdir3/Xdir2', 'p')
- for f in s:files
- call writefile([], f)
- endfor
-endfunc
-
-func CleanFiles()
- " Safer to delete each file even if it's more verbose
- " than doing a recursive delete('Xdir1', 'rf').
- for f in s:files
- call delete(f)
- endfor
-
- call delete('Xdir1/Xdir2/Xdir3/Xdir2', 'd')
- call delete('Xdir1/Xdir2/Xdir3', 'd')
- call delete('Xdir1/Xdir2', 'd')
- call delete('Xdir1', 'd')
-endfunc
-
-" Test findfile({name} [, {path} [, {count}]])
-func Test_findfile()
- let save_path = &path
- let save_shellslash = &shellslash
- let save_dir = getcwd()
- set shellslash
- call CreateFiles()
- cd Xdir1
- e Xdir2/foo
-
- " With ,, in path, findfile() searches in current directory.
- set path=,,
- call assert_equal('foo', findfile('foo'))
- call assert_equal('bar', findfile('bar'))
- call assert_equal('', findfile('foobar'))
-
- " Directories should not be found (finddir() finds them).
- call assert_equal('', findfile('Xdir2'))
-
- " With . in 'path', findfile() searches relatively to current file.
- set path=.
- call assert_equal('Xdir2/foo', findfile('foo'))
- call assert_equal('', findfile('bar'))
- call assert_equal('Xdir2/foobar', 'foobar'->findfile())
-
- " Empty {path} 2nd argument is the same as no 2nd argument.
- call assert_equal('Xdir2/foo', findfile('foo', ''))
- call assert_equal('', findfile('bar', ''))
-
- " Test with *
- call assert_equal('Xdir2/foo', findfile('foo', '*'))
- call assert_equal('', findfile('bar', '*'))
- call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '*/*'))
- call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir2/*'))
- call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir*/Xdir3'))
- call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '*2/*3'))
-
- " Test with **
- call assert_equal('bar', findfile('bar', '**'))
- call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '**/Xdir3'))
- call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir2/**'))
-
- call assert_equal('Xdir2/Xdir3/barfoo', findfile('barfoo', '**2'))
- call assert_equal('', findfile('barfoo', '**1'))
- call assert_equal('Xdir2/foobar', findfile('foobar', '**1'))
-
- " Test with {count} 3rd argument.
- call assert_equal('bar', findfile('bar', '**', 0))
- call assert_equal('bar', findfile('bar', '**', 1))
- call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '**', 2))
- call assert_equal('', findfile('bar', '**', 3))
- call assert_equal(['bar', 'Xdir2/Xdir3/bar'], findfile('bar', '**', -1))
-
- " Test upwards search.
- cd Xdir2/Xdir3
- call assert_equal('bar', findfile('bar', ';'))
- call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', ';'))
- call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', ';', 1))
- call assert_match('.*/Xdir1/foo', findfile('foo', ';', 2))
- call assert_match('.*/Xdir1/foo', findfile('foo', ';', 2))
- call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', 'Xdir2;', 1))
- call assert_equal('', findfile('foo', 'Xdir2;', 2))
-
- " List l should have at least 2 values (possibly more if foo file
- " happens to be found upwards above Xdir1).
- let l = findfile('foo', ';', -1)
- call assert_match('.*/Xdir1/Xdir2/foo', l[0])
- call assert_match('.*/Xdir1/foo', l[1])
-
- " Test upwards search with stop-directory.
- cd Xdir2
- let l = findfile('bar', ';' . save_dir . '/Xdir1/Xdir2/', -1)
- call assert_equal(1, len(l))
- call assert_match('.*/Xdir1/Xdir2/Xdir3/bar', l[0])
-
- let l = findfile('bar', ';' . save_dir . '/Xdir1/', -1)
- call assert_equal(2, len(l))
- call assert_match('.*/Xdir1/Xdir2/Xdir3/bar', l[0])
- call assert_match('.*/Xdir1/bar', l[1])
-
- " Test combined downwards and upwards search from Xdir2/.
- cd ../..
- call assert_equal('Xdir3/bar', findfile('bar', '**;', 1))
- call assert_match('.*/Xdir1/bar', findfile('bar', '**;', 2))
-
- bwipe!
- call chdir(save_dir)
- call CleanFiles()
- let &path = save_path
- let &shellslash = save_shellslash
-endfunc
-
-func Test_findfile_error()
- call assert_fails('call findfile([])', 'E730:')
- call assert_fails('call findfile("x", [])', 'E730:')
- call assert_fails('call findfile("x", "", [])', 'E745:')
- call assert_fails('call findfile("x", "**x")', 'E343:')
- call assert_fails('call findfile("x", repeat("x", 5000))', 'E854:')
-endfunc
-
-" Test finddir({name} [, {path} [, {count}]])
-func Test_finddir()
- let save_path = &path
- let save_shellslash = &shellslash
- let save_dir = getcwd()
- set path=,,
- set shellslash
- call CreateFiles()
- cd Xdir1
-
- call assert_equal('Xdir2', finddir('Xdir2'))
- call assert_equal('', 'Xdir3'->finddir())
-
- " Files should not be found (findfile() finds them).
- call assert_equal('', finddir('foo'))
-
- call assert_equal('Xdir2', finddir('Xdir2', '**'))
- call assert_equal('Xdir2/Xdir3', finddir('Xdir3', '**'))
-
- call assert_equal('Xdir2', finddir('Xdir2', '**', 1))
- call assert_equal('Xdir2/Xdir3/Xdir2', finddir('Xdir2', '**', 2))
- call assert_equal(['Xdir2',
- \ 'Xdir2/Xdir3/Xdir2'], finddir('Xdir2', '**', -1))
-
- call assert_equal('Xdir2', finddir('Xdir2', '**1'))
- call assert_equal('Xdir2', finddir('Xdir2', '**0'))
- call assert_equal('Xdir2/Xdir3', finddir('Xdir3', '**1'))
- call assert_equal('', finddir('Xdir3', '**0'))
-
- " Test upwards dir search.
- cd Xdir2/Xdir3
- call assert_match('.*/Xdir1', finddir('Xdir1', ';'))
-
- " Test upwards search with stop-directory.
- call assert_match('.*/Xdir1', finddir('Xdir1', ';' . save_dir . '/'))
- call assert_equal('', finddir('Xdir1', ';' . save_dir . '/Xdir1/'))
-
- " Test combined downwards and upwards dir search from Xdir2/.
- cd ..
- call assert_match('.*/Xdir1', finddir('Xdir1', '**;', 1))
- call assert_equal('Xdir3/Xdir2', finddir('Xdir2', '**;', 1))
- call assert_match('.*/Xdir1/Xdir2', finddir('Xdir2', '**;', 2))
- call assert_equal('Xdir3', finddir('Xdir3', '**;', 1))
-
- call chdir(save_dir)
- call CleanFiles()
- let &path = save_path
- let &shellslash = save_shellslash
-endfunc
-
-func Test_finddir_error()
- call assert_fails('call finddir([])', 'E730:')
- call assert_fails('call finddir("x", [])', 'E730:')
- call assert_fails('call finddir("x", "", [])', 'E745:')
- call assert_fails('call finddir("x", "**x")', 'E343:')
- call assert_fails('call finddir("x", repeat("x", 5000))', 'E854:')
-endfunc
-
-" Test for the :find, :sfind and :tabfind commands
-func Test_find_cmd()
- new
- let save_path = &path
- let save_dir = getcwd()
- set path=.,./**/*
- call CreateFiles()
- cd Xdir1
-
- " Test for :find
- find foo
- call assert_equal('foo', expand('%:.'))
- 2find foo
- call assert_equal('Xdir2/foo', expand('%:.'))
- call assert_fails('3find foo', 'E347:')
-
- " Test for :sfind
- enew
- sfind barfoo
- call assert_equal('Xdir2/Xdir3/barfoo', expand('%:.'))
- call assert_equal(3, winnr('$'))
- close
- call assert_fails('sfind baz', 'E345:')
- call assert_equal(2, winnr('$'))
-
- " Test for :tabfind
- enew
- tabfind foobar
- call assert_equal('Xdir2/foobar', expand('%:.'))
- call assert_equal(2, tabpagenr('$'))
- tabclose
- call assert_fails('tabfind baz', 'E345:')
- call assert_equal(1, tabpagenr('$'))
-
- call chdir(save_dir)
- exe 'cd ' . save_dir
- call CleanFiles()
- let &path = save_path
- close
-
- call assert_fails('find', 'E471:')
- call assert_fails('sfind', 'E471:')
- call assert_fails('tabfind', 'E471:')
-endfunc
-
-func Test_find_non_existing_path()
- new
- let save_path = &path
- let save_dir = getcwd()
- call mkdir('dir1/dir2', 'p')
- call writefile([], 'dir1/file.txt')
- call writefile([], 'dir1/dir2/base.txt')
- call chdir('dir1/dir2')
- e base.txt
- set path=../include
-
- call assert_fails(':find file.txt', 'E345:')
-
- call chdir(save_dir)
- bw!
- call delete('dir1/dir2/base.txt', 'rf')
- call delete('dir1/dir2', 'rf')
- call delete('dir1/file.txt', 'rf')
- call delete('dir1', 'rf')
- let &path = save_path
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fixeol.vim b/src/nvim/testdir/test_fixeol.vim
deleted file mode 100644
index 41d47d6a06..0000000000
--- a/src/nvim/testdir/test_fixeol.vim
+++ /dev/null
@@ -1,118 +0,0 @@
-" Tests for 'fixeol', 'eof' and 'eol'
-
-func Test_fixeol()
- " first write two test files – with and without trailing EOL
- " use Unix fileformat for consistency
- set ff=unix
- enew!
- call setline('.', 'with eol or eof')
- w! XXEol
- enew!
- set noeof noeol nofixeol
- call setline('.', 'without eol or eof')
- w! XXNoEol
- set eol eof fixeol
- bwipe XXEol XXNoEol
-
- " try editing files with 'fixeol' disabled
- e! XXEol
- normal ostays eol
- set nofixeol
- w! XXTestEol
- e! XXNoEol
- normal ostays without
- set nofixeol
- w! XXTestNoEol
- bwipe! XXEol XXNoEol XXTestEol XXTestNoEol
- set fixeol
-
- " Append "END" to each file so that we can see what the last written char
- " was.
- normal ggdGaEND
- w >>XXEol
- w >>XXNoEol
- w >>XXTestEol
- w >>XXTestNoEol
-
- call assert_equal(['with eol or eof', 'END'], readfile('XXEol'))
- call assert_equal(['without eol or eofEND'], readfile('XXNoEol'))
- call assert_equal(['with eol or eof', 'stays eol', 'END'], readfile('XXTestEol'))
- call assert_equal(['without eol or eof', 'stays withoutEND'],
- \ readfile('XXTestNoEol'))
-
- call delete('XXEol')
- call delete('XXNoEol')
- call delete('XXTestEol')
- call delete('XXTestNoEol')
- set ff& fixeol& eof& eol&
- enew!
-endfunc
-
-func Test_eof()
- let data = 0z68656c6c6f.0d0a.776f726c64 " "hello\r\nworld"
-
- " 1. Eol, Eof
- " read
- call writefile(data + 0z0d0a.1a, 'XXEolEof')
- e! XXEolEof
- call assert_equal(['hello', 'world'], getline(1, 2))
- call assert_equal([1, 1], [&eol, &eof])
- " write
- set fixeol
- w!
- call assert_equal(data + 0z0d0a, readblob('XXEolEof'))
- set nofixeol
- w!
- call assert_equal(data + 0z0d0a.1a, readblob('XXEolEof'))
-
- " 2. NoEol, Eof
- " read
- call writefile(data + 0z1a, 'XXNoEolEof')
- e! XXNoEolEof
- call assert_equal(['hello', 'world'], getline(1, 2))
- call assert_equal([0, 1], [&eol, &eof])
- " write
- set fixeol
- w!
- call assert_equal(data + 0z0d0a, readblob('XXNoEolEof'))
- set nofixeol
- w!
- call assert_equal(data + 0z1a, readblob('XXNoEolEof'))
-
- " 3. Eol, NoEof
- " read
- call writefile(data + 0z0d0a, 'XXEolNoEof')
- e! XXEolNoEof
- call assert_equal(['hello', 'world'], getline(1, 2))
- call assert_equal([1, 0], [&eol, &eof])
- " write
- set fixeol
- w!
- call assert_equal(data + 0z0d0a, readblob('XXEolNoEof'))
- set nofixeol
- w!
- call assert_equal(data + 0z0d0a, readblob('XXEolNoEof'))
-
- " 4. NoEol, NoEof
- " read
- call writefile(data, 'XXNoEolNoEof')
- e! XXNoEolNoEof
- call assert_equal(['hello', 'world'], getline(1, 2))
- call assert_equal([0, 0], [&eol, &eof])
- " write
- set fixeol
- w!
- call assert_equal(data + 0z0d0a, readblob('XXNoEolNoEof'))
- set nofixeol
- w!
- call assert_equal(data, readblob('XXNoEolNoEof'))
-
- call delete('XXEolEof')
- call delete('XXNoEolEof')
- call delete('XXEolNoEof')
- call delete('XXNoEolNoEof')
- set ff& fixeol& eof& eol&
- enew!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_flatten.vim b/src/nvim/testdir/test_flatten.vim
deleted file mode 100644
index 99086611e1..0000000000
--- a/src/nvim/testdir/test_flatten.vim
+++ /dev/null
@@ -1,81 +0,0 @@
-" 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_float_func.vim b/src/nvim/testdir/test_float_func.vim
deleted file mode 100644
index 902a011a9d..0000000000
--- a/src/nvim/testdir/test_float_func.vim
+++ /dev/null
@@ -1,369 +0,0 @@
-" test float functions
-
-source check.vim
-CheckFeature float
-
-func Test_abs()
- call assert_equal('1.23', string(abs(1.23)))
- call assert_equal('1.23', string(abs(-1.23)))
- eval -1.23->abs()->string()->assert_equal('1.23')
-
- call assert_equal('0.0', string(abs(0.0)))
- call assert_equal('0.0', string(abs(1.0/(1.0/0.0))))
- call assert_equal('0.0', string(abs(-1.0/(1.0/0.0))))
- call assert_equal("str2float('inf')", string(abs(1.0/0.0)))
- call assert_equal("str2float('inf')", string(abs(-1.0/0.0)))
- call assert_equal("str2float('nan')", string(abs(0.0/0.0)))
- call assert_equal('12', string(abs('12abc')))
- call assert_equal('12', string(abs('-12abc')))
- call assert_fails("call abs([])", 'E745:')
- call assert_fails("call abs({})", 'E728:')
- call assert_fails("call abs(function('string'))", 'E703:')
-endfunc
-
-func Test_sqrt()
- call assert_equal('0.0', string(sqrt(0.0)))
- call assert_equal('1.414214', string(sqrt(2.0)))
- eval 2.0->sqrt()->string()->assert_equal('1.414214')
- call assert_equal("str2float('inf')", string(sqrt(1.0/0.0)))
- call assert_equal("str2float('nan')", string(sqrt(-1.0)))
- call assert_equal("str2float('nan')", string(sqrt(0.0/0.0)))
- call assert_fails('call sqrt("")', 'E808:')
-endfunc
-
-func Test_log()
- call assert_equal('0.0', string(log(1.0)))
- call assert_equal('-0.693147', string(log(0.5)))
- eval 0.5->log()->string()->assert_equal('-0.693147')
- call assert_equal("-str2float('inf')", string(log(0.0)))
- call assert_equal("str2float('nan')", string(log(-1.0)))
- call assert_equal("str2float('inf')", string(log(1.0/0.0)))
- call assert_equal("str2float('nan')", string(log(0.0/0.0)))
- call assert_fails('call log("")', 'E808:')
-endfunc
-
-func Test_log10()
- call assert_equal('0.0', string(log10(1.0)))
- call assert_equal('2.0', string(log10(100.0)))
- call assert_equal('2.079181', string(log10(120.0)))
- eval 120.0->log10()->string()->assert_equal('2.079181')
- call assert_equal("-str2float('inf')", string(log10(0.0)))
- call assert_equal("str2float('nan')", string(log10(-1.0)))
- call assert_equal("str2float('inf')", string(log10(1.0/0.0)))
- call assert_equal("str2float('nan')", string(log10(0.0/0.0)))
- call assert_fails('call log10("")', 'E808:')
-endfunc
-
-func Test_exp()
- call assert_equal('1.0', string(exp(0.0)))
- call assert_equal('7.389056', string(exp(2.0)))
- call assert_equal('0.367879', string(exp(-1.0)))
- eval -1.0->exp()->string()->assert_equal('0.367879')
- call assert_equal("str2float('inf')", string(exp(1.0/0.0)))
- call assert_equal('0.0', string(exp(-1.0/0.0)))
- call assert_equal("str2float('nan')", string(exp(0.0/0.0)))
- call assert_fails('call exp("")', 'E808:')
-endfunc
-
-func Test_sin()
- call assert_equal('0.0', string(sin(0.0)))
- call assert_equal('0.841471', string(sin(1.0)))
- call assert_equal('-0.479426', string(sin(-0.5)))
- eval -0.5->sin()->string()->assert_equal('-0.479426')
- call assert_equal("str2float('nan')", string(sin(0.0/0.0)))
- call assert_equal("str2float('nan')", string(sin(1.0/0.0)))
- call assert_equal('0.0', string(sin(1.0/(1.0/0.0))))
- call assert_equal('-0.0', string(sin(-1.0/(1.0/0.0))))
- call assert_fails('call sin("")', 'E808:')
-endfunc
-
-func Test_asin()
- call assert_equal('0.0', string(asin(0.0)))
- call assert_equal('1.570796', string(asin(1.0)))
- eval 1.0->asin()->string()->assert_equal('1.570796')
-
- call assert_equal('-0.523599', string(asin(-0.5)))
- call assert_equal("str2float('nan')", string(asin(1.1)))
- call assert_equal("str2float('nan')", string(asin(1.0/0.0)))
- call assert_equal("str2float('nan')", string(asin(0.0/0.0)))
- call assert_fails('call asin("")', 'E808:')
-endfunc
-
-func Test_sinh()
- call assert_equal('0.0', string(sinh(0.0)))
- call assert_equal('0.521095', string(sinh(0.5)))
- call assert_equal('-1.026517', string(sinh(-0.9)))
- eval -0.9->sinh()->string()->assert_equal('-1.026517')
- call assert_equal("str2float('inf')", string(sinh(1.0/0.0)))
- call assert_equal("-str2float('inf')", string(sinh(-1.0/0.0)))
- call assert_equal("str2float('nan')", string(sinh(0.0/0.0)))
- call assert_fails('call sinh("")', 'E808:')
-endfunc
-
-func Test_cos()
- call assert_equal('1.0', string(cos(0.0)))
- call assert_equal('0.540302', string(cos(1.0)))
- call assert_equal('0.877583', string(cos(-0.5)))
- eval -0.5->cos()->string()->assert_equal('0.877583')
- call assert_equal("str2float('nan')", string(cos(0.0/0.0)))
- call assert_equal("str2float('nan')", string(cos(1.0/0.0)))
- call assert_fails('call cos("")', 'E808:')
-endfunc
-
-func Test_acos()
- call assert_equal('1.570796', string(acos(0.0)))
- call assert_equal('0.0', string(acos(1.0)))
- call assert_equal('3.141593', string(acos(-1.0)))
- eval -1.0->acos()->string()->assert_equal('3.141593')
- call assert_equal('2.094395', string(acos(-0.5)))
- call assert_equal("str2float('nan')", string(acos(1.1)))
- call assert_equal("str2float('nan')", string(acos(1.0/0.0)))
- call assert_equal("str2float('nan')", string(acos(0.0/0.0)))
- call assert_fails('call acos("")', 'E808:')
-endfunc
-
-func Test_cosh()
- call assert_equal('1.0', string(cosh(0.0)))
- call assert_equal('1.127626', string(cosh(0.5)))
- eval 0.5->cosh()->string()->assert_equal('1.127626')
- call assert_equal("str2float('inf')", string(cosh(1.0/0.0)))
- call assert_equal("str2float('inf')", string(cosh(-1.0/0.0)))
- call assert_equal("str2float('nan')", string(cosh(0.0/0.0)))
- call assert_fails('call cosh("")', 'E808:')
-endfunc
-
-func Test_tan()
- call assert_equal('0.0', string(tan(0.0)))
- call assert_equal('0.546302', string(tan(0.5)))
- call assert_equal('-0.546302', string(tan(-0.5)))
- eval -0.5->tan()->string()->assert_equal('-0.546302')
- call assert_equal("str2float('nan')", string(tan(1.0/0.0)))
- call assert_equal("str2float('nan')", string(cos(0.0/0.0)))
- call assert_equal('0.0', string(tan(1.0/(1.0/0.0))))
- call assert_equal('-0.0', string(tan(-1.0/(1.0/0.0))))
- call assert_fails('call tan("")', 'E808:')
-endfunc
-
-func Test_atan()
- call assert_equal('0.0', string(atan(0.0)))
- call assert_equal('0.463648', string(atan(0.5)))
- call assert_equal('-0.785398', string(atan(-1.0)))
- eval -1.0->atan()->string()->assert_equal('-0.785398')
- call assert_equal('1.570796', string(atan(1.0/0.0)))
- call assert_equal('-1.570796', string(atan(-1.0/0.0)))
- call assert_equal("str2float('nan')", string(atan(0.0/0.0)))
- call assert_fails('call atan("")', 'E808:')
-endfunc
-
-func Test_atan2()
- call assert_equal('-2.356194', string(atan2(-1, -1)))
- call assert_equal('2.356194', string(atan2(1, -1)))
- call assert_equal('0.0', string(atan2(1.0, 1.0/0.0)))
- eval 1.0->atan2(1.0/0.0)->string()->assert_equal('0.0')
- call assert_equal('1.570796', string(atan2(1.0/0.0, 1.0)))
- call assert_equal("str2float('nan')", string(atan2(0.0/0.0, 1.0)))
- call assert_fails('call atan2("", -1)', 'E808:')
- call assert_fails('call atan2(-1, "")', 'E808:')
-endfunc
-
-func Test_tanh()
- call assert_equal('0.0', string(tanh(0.0)))
- call assert_equal('0.462117', string(tanh(0.5)))
- call assert_equal('-0.761594', string(tanh(-1.0)))
- eval -1.0->tanh()->string()->assert_equal('-0.761594')
- call assert_equal('1.0', string(tanh(1.0/0.0)))
- call assert_equal('-1.0', string(tanh(-1.0/0.0)))
- call assert_equal("str2float('nan')", string(tanh(0.0/0.0)))
- call assert_fails('call tanh("")', 'E808:')
-endfunc
-
-func Test_fmod()
- call assert_equal('0.13', string(fmod(12.33, 1.22)))
- call assert_equal('-0.13', string(fmod(-12.33, 1.22)))
- call assert_equal("str2float('nan')", string(fmod(1.0/0.0, 1.0)))
- eval (1.0/0.0)->fmod(1.0)->string()->assert_equal("str2float('nan')")
- " On Windows we get "nan" instead of 1.0, accept both.
- let res = string(fmod(1.0, 1.0/0.0))
- if res != "str2float('nan')"
- call assert_equal('1.0', res)
- endif
- call assert_equal("str2float('nan')", string(fmod(1.0, 0.0)))
- call assert_fails("call fmod('', 1.22)", 'E808:')
- call assert_fails("call fmod(12.33, '')", 'E808:')
-endfunc
-
-func Test_pow()
- call assert_equal('1.0', string(pow(0.0, 0.0)))
- call assert_equal('8.0', string(pow(2.0, 3.0)))
- eval 2.0->pow(3.0)->string()->assert_equal('8.0')
- call assert_equal("str2float('nan')", string(pow(2.0, 0.0/0.0)))
- call assert_equal("str2float('nan')", string(pow(0.0/0.0, 3.0)))
- call assert_equal("str2float('nan')", string(pow(0.0/0.0, 3.0)))
- call assert_equal("str2float('inf')", string(pow(2.0, 1.0/0.0)))
- call assert_equal("str2float('inf')", string(pow(1.0/0.0, 3.0)))
- call assert_fails("call pow('', 2.0)", 'E808:')
- call assert_fails("call pow(2.0, '')", 'E808:')
-endfunc
-
-func Test_str2float()
- call assert_equal('1.0', string(str2float('1')))
- call assert_equal('1.0', string(str2float(' 1 ')))
- call assert_equal('1.0', string(str2float(' 1.0 ')))
- call assert_equal('1.23', string(str2float('1.23')))
- call assert_equal('1.23', string(str2float('1.23abc')))
- eval '1.23abc'->str2float()->string()->assert_equal('1.23')
- call assert_equal('1.0e40', string(str2float('1e40')))
- call assert_equal('-1.23', string(str2float('-1.23')))
- call assert_equal('1.23', string(str2float(' + 1.23 ')))
-
- call assert_equal('1.0', string(str2float('+1')))
- call assert_equal('1.0', string(str2float('+1')))
- call assert_equal('1.0', string(str2float(' +1 ')))
- call assert_equal('1.0', string(str2float(' + 1 ')))
-
- call assert_equal('-1.0', string(str2float('-1')))
- call assert_equal('-1.0', string(str2float('-1')))
- call assert_equal('-1.0', string(str2float(' -1 ')))
- call assert_equal('-1.0', string(str2float(' - 1 ')))
-
- call assert_equal('0.0', string(str2float('+0.0')))
- call assert_equal('-0.0', string(str2float('-0.0')))
- call assert_equal("str2float('inf')", string(str2float('1e1000')))
- call assert_equal("str2float('inf')", string(str2float('inf')))
- call assert_equal("-str2float('inf')", string(str2float('-inf')))
- call assert_equal("str2float('inf')", string(str2float('+inf')))
- call assert_equal("str2float('inf')", string(str2float('Inf')))
- call assert_equal("str2float('inf')", string(str2float(' +inf ')))
- call assert_equal("str2float('nan')", string(str2float('nan')))
- call assert_equal("str2float('nan')", string(str2float('NaN')))
- call assert_equal("str2float('nan')", string(str2float(' nan ')))
-
- call assert_fails("call str2float(1.2)", 'E806:')
- call assert_fails("call str2float([])", 'E730:')
- call assert_fails("call str2float({})", 'E731:')
- call assert_fails("call str2float(function('string'))", 'E729:')
-endfunc
-
-func Test_float2nr()
- call assert_equal(1, float2nr(1.234))
- call assert_equal(123, float2nr(1.234e2))
- call assert_equal(12, float2nr(123.4e-1))
- eval 123.4e-1->float2nr()->assert_equal(12)
- let max_number = 1/0
- let min_number = -max_number
- call assert_equal(max_number/2+1, float2nr(pow(2, 62)))
- call assert_equal(max_number, float2nr(pow(2, 63)))
- call assert_equal(max_number, float2nr(pow(2, 64)))
- call assert_equal(min_number/2-1, float2nr(-pow(2, 62)))
- call assert_equal(min_number, float2nr(-pow(2, 63)))
- call assert_equal(min_number, float2nr(-pow(2, 64)))
-endfunc
-
-func Test_floor()
- call assert_equal('2.0', string(floor(2.0)))
- call assert_equal('2.0', string(floor(2.11)))
- call assert_equal('2.0', string(floor(2.99)))
- eval 2.99->floor()->string()->assert_equal('2.0')
- call assert_equal('-3.0', string(floor(-2.11)))
- call assert_equal('-3.0', string(floor(-2.99)))
- call assert_equal("str2float('nan')", string(floor(0.0/0.0)))
- call assert_equal("str2float('inf')", string(floor(1.0/0.0)))
- call assert_equal("-str2float('inf')", string(floor(-1.0/0.0)))
- call assert_fails("call floor('')", 'E808:')
-endfunc
-
-func Test_ceil()
- call assert_equal('2.0', string(ceil(2.0)))
- call assert_equal('3.0', string(ceil(2.11)))
- call assert_equal('3.0', string(ceil(2.99)))
- call assert_equal('-2.0', string(ceil(-2.11)))
- eval -2.11->ceil()->string()->assert_equal('-2.0')
- call assert_equal('-2.0', string(ceil(-2.99)))
- call assert_equal("str2float('nan')", string(ceil(0.0/0.0)))
- call assert_equal("str2float('inf')", string(ceil(1.0/0.0)))
- call assert_equal("-str2float('inf')", string(ceil(-1.0/0.0)))
- call assert_fails("call ceil('')", 'E808:')
-endfunc
-
-func Test_round()
- call assert_equal('2.0', string(round(2.1)))
- call assert_equal('3.0', string(round(2.5)))
- call assert_equal('3.0', string(round(2.9)))
- eval 2.9->round()->string()->assert_equal('3.0')
- call assert_equal('-2.0', string(round(-2.1)))
- call assert_equal('-3.0', string(round(-2.5)))
- call assert_equal('-3.0', string(round(-2.9)))
- call assert_equal("str2float('nan')", string(round(0.0/0.0)))
- call assert_equal("str2float('inf')", string(round(1.0/0.0)))
- call assert_equal("-str2float('inf')", string(round(-1.0/0.0)))
- call assert_fails("call round('')", 'E808:')
-endfunc
-
-func Test_trunc()
- call assert_equal('2.0', string(trunc(2.1)))
- call assert_equal('2.0', string(trunc(2.5)))
- call assert_equal('2.0', string(trunc(2.9)))
- eval 2.9->trunc()->string()->assert_equal('2.0')
- call assert_equal('-2.0', string(trunc(-2.1)))
- call assert_equal('-2.0', string(trunc(-2.5)))
- call assert_equal('-2.0', string(trunc(-2.9)))
- call assert_equal("str2float('nan')", string(trunc(0.0/0.0)))
- call assert_equal("str2float('inf')", string(trunc(1.0/0.0)))
- call assert_equal("-str2float('inf')", string(trunc(-1.0/0.0)))
- call assert_fails("call trunc('')", 'E808:')
-endfunc
-
-func Test_isinf()
- call assert_equal(1, isinf(1.0/0.0))
- call assert_equal(-1, isinf(-1.0/0.0))
- eval (-1.0/0.0)->isinf()->assert_equal(-1)
- call assert_false(isinf(1.0))
- call assert_false(isinf(0.0/0.0))
- call assert_false(isinf('a'))
- call assert_false(isinf([]))
- call assert_false(isinf({}))
-endfunc
-
-func Test_isnan()
- call assert_true(isnan(0.0/0.0))
- call assert_false(isnan(1.0))
- call assert_false(isnan(1.0/0.0))
- eval (1.0/0.0)->isnan()->assert_false()
- call assert_false(isnan(-1.0/0.0))
- call assert_false(isnan('a'))
- call assert_false(isnan([]))
- call assert_false(isnan({}))
-endfunc
-
-" This was converted from test65
-func Test_float_misc()
- call assert_equal('123.456000', printf('%f', 123.456))
- call assert_equal('1.234560e+02', printf('%e', 123.456))
- call assert_equal('123.456', printf('%g', 123.456))
- " +=
- let v = 1.234
- let v += 6.543
- call assert_equal('7.777', printf('%g', v))
- let v = 1.234
- let v += 5
- call assert_equal('6.234', printf('%g', v))
- let v = 5
- let v += 3.333
- call assert_equal('8.333', string(v))
- " ==
- let v = 1.234
- call assert_true(v == 1.234)
- call assert_false(v == 1.2341)
- " add-subtract
- call assert_equal('5.234', printf('%g', 4 + 1.234))
- call assert_equal('-6.766', printf('%g', 1.234 - 8))
- " mult-div
- call assert_equal('4.936', printf('%g', 4 * 1.234))
- call assert_equal('0.003241', printf('%g', 4.0 / 1234))
- " dict
- call assert_equal("{'x': 1.234, 'y': -2.0e20}", string({'x': 1.234, 'y': -2.0e20}))
- " list
- call assert_equal('[-123.4, 2.0e-20]', string([-123.4, 2.0e-20]))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fnameescape.vim b/src/nvim/testdir/test_fnameescape.vim
deleted file mode 100644
index cdb96ba5ff..0000000000
--- a/src/nvim/testdir/test_fnameescape.vim
+++ /dev/null
@@ -1,27 +0,0 @@
-
-" Test if fnameescape is correct for special chars like !
-func Test_fnameescape()
- let fname = 'Xspa ce'
- let status = v:false
- try
- exe "w! " . fnameescape(fname)
- let status = v:true
- endtry
- call assert_true(status, "Space")
- call delete(fname)
-
- let fname = 'Xemark!'
- let status = v:false
- try
- exe "w! " . fname->fnameescape()
- let status = v:true
- endtry
- call assert_true(status, "ExclamationMark")
- call delete(fname)
-
- call assert_equal('\-', fnameescape('-'))
- call assert_equal('\+', fnameescape('+'))
- call assert_equal('\>', fnameescape('>'))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim
deleted file mode 100644
index 258a2093bd..0000000000
--- a/src/nvim/testdir/test_fnamemodify.vim
+++ /dev/null
@@ -1,104 +0,0 @@
-" Test filename modifiers.
-
-func Test_fnamemodify()
- let save_home = $HOME
- let save_shell = &shell
- let save_shellslash = &shellslash
- let $HOME = fnamemodify('.', ':p:h:h')
- set shell=sh
- set shellslash
-
- call assert_equal('/', fnamemodify('.', ':p')[-1:])
- call assert_equal('r', fnamemodify('.', ':p:h')[-1:])
- call assert_equal('t', fnamemodify('test.out', ':p')[-1:])
- call assert_equal($HOME .. "/foo" , fnamemodify('~/foo', ':p'))
- 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', '../testdir/a'->fnamemodify(':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(getcwd(), fnamemodify('', ':p:h'))
-
- let cwd = getcwd()
- call chdir($HOME)
- call assert_equal('foobar', fnamemodify('~/foobar', ':~:.'))
- call chdir(cwd)
- call mkdir($HOME . '/XXXXXXXX/a', 'p')
- call mkdir($HOME . '/XXXXXXXX/b', 'p')
- call chdir($HOME . '/XXXXXXXX/a/')
- call assert_equal('foo', fnamemodify($HOME . '/XXXXXXXX/a/foo', ':p:~:.'))
- call assert_equal('~/XXXXXXXX/b/foo', fnamemodify($HOME . '/XXXXXXXX/b/foo', ':p:~:.'))
- call mkdir($HOME . '/XXXXXXXX/a.ext', 'p')
- call assert_equal('~/XXXXXXXX/a.ext/foo', fnamemodify($HOME . '/XXXXXXXX/a.ext/foo', ':p:~:.'))
- call chdir(cwd)
- call delete($HOME . '/XXXXXXXX', 'rf')
-
- call assert_equal('''abc def''', fnamemodify('abc def', ':S'))
- call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S'))
- 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
- let &shellslash = save_shellslash
-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'))
-
- call assert_equal('', fnamemodify('', ':p:t'))
- call assert_equal('', fnamemodify(v:_null_string, v:_null_string))
-endfunc
-
-func Test_fnamemodify_fail()
- call assert_fails('call fnamemodify({}, ":p")', 'E731:')
- call assert_fails('call fnamemodify("x", {})', 'E731:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
deleted file mode 100644
index 19415286ad..0000000000
--- a/src/nvim/testdir/test_fold.vim
+++ /dev/null
@@ -1,1499 +0,0 @@
-" Test for folding
-
-source check.vim
-source view_util.vim
-source screendump.vim
-
-func PrepIndent(arg)
- return [a:arg] + repeat(["\t".a:arg], 5)
-endfu
-
-func Test_address_fold()
- new
- call setline(1, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
- \ 'after fold 1', 'after fold 2', 'after fold 3'])
- setl fen fdm=marker
- " The next commands should all copy the same part of the buffer,
- " regardless of the addressing type, since the part to be copied
- " is folded away
- :1y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
- :.y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
- :.+y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
- :.,.y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
- :sil .1,.y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
- " use silent to make E493 go away
- :sil .+,.y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
- :,y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
- :,+y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/','after fold 1'], getreg(0,1,1))
- " using .+3 as second address should copy the whole folded line + the next 3
- " lines
- :.,+3y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/',
- \ 'after fold 1', 'after fold 2', 'after fold 3'], getreg(0,1,1))
- :sil .,-2y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
-
- " now test again with folding disabled
- set nofoldenable
- :1y
- call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
- :.y
- call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
- :.+y
- call assert_equal(['1'], getreg(0,1,1))
- :.,.y
- call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
- " use silent to make E493 go away
- :sil .1,.y
- call assert_equal(['int FuncName() {/*{{{*/', '1'], getreg(0,1,1))
- " use silent to make E493 go away
- :sil .+,.y
- call assert_equal(['int FuncName() {/*{{{*/', '1'], getreg(0,1,1))
- :,y
- call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
- :,+y
- call assert_equal(['int FuncName() {/*{{{*/', '1'], getreg(0,1,1))
- " using .+3 as second address should copy the whole folded line + the next 3
- " lines
- :.,+3y
- call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3'], getreg(0,1,1))
- :7
- :sil .,-2y
- call assert_equal(['4', '5', '}/*}}}*/'], getreg(0,1,1))
-
- quit!
-endfunc
-
-func Test_address_offsets()
- " check the help for :range-closed-fold
- enew
- call setline(1, [
- \ '1 one',
- \ '2 two',
- \ '3 three',
- \ '4 four FOLDED',
- \ '5 five FOLDED',
- \ '6 six',
- \ '7 seven',
- \ '8 eight',
- \])
- set foldmethod=manual
- normal 4Gvjzf
- 3,4+2yank
- call assert_equal([
- \ '3 three',
- \ '4 four FOLDED',
- \ '5 five FOLDED',
- \ '6 six',
- \ '7 seven',
- \ ], getreg(0,1,1))
-
- enew!
- call setline(1, [
- \ '1 one',
- \ '2 two',
- \ '3 three FOLDED',
- \ '4 four FOLDED',
- \ '5 five FOLDED',
- \ '6 six FOLDED',
- \ '7 seven',
- \ '8 eight',
- \])
- normal 3Gv3jzf
- 2,4-1yank
- call assert_equal([
- \ '2 two',
- \ '3 three FOLDED',
- \ '4 four FOLDED',
- \ '5 five FOLDED',
- \ '6 six FOLDED',
- \ ], getreg(0,1,1))
-
- bwipe!
-endfunc
-
-func Test_indent_fold()
- new
- call setline(1, ['', 'a', ' b', ' c'])
- setl fen fdm=indent
- 2
- norm! >>
- let a=map(range(1,4), 'foldclosed(v:val)')
- call assert_equal([-1,-1,-1,-1], a)
- bw!
-endfunc
-
-func Test_indent_fold2()
- new
- call setline(1, ['', '{{{', '}}}', '{{{', '}}}'])
- setl fen fdm=marker
- 2
- norm! >>
- let a=map(range(1,5), 'v:val->foldclosed()')
- call assert_equal([-1,-1,-1,4,4], a)
- bw!
-endfunc
-
-" Test for fold indent with indents greater than 'foldnestmax'
-func Test_indent_fold_max()
- new
- setlocal foldmethod=indent
- setlocal shiftwidth=2
- " 'foldnestmax' default value is 20
- call setline(1, "\t\t\t\t\t\ta")
- call assert_equal(20, foldlevel(1))
- setlocal foldnestmax=10
- call assert_equal(10, foldlevel(1))
- setlocal foldnestmax=-1
- call assert_equal(0, foldlevel(1))
- bw!
-endfunc
-
-func Test_indent_fold_tabstop()
- call setline(1, ['0', ' 1', ' 1', "\t2", "\t2"])
- setlocal shiftwidth=4
- setlocal foldcolumn=1
- setlocal foldlevel=2
- setlocal foldmethod=indent
- redraw
- call assert_equal('2 2', ScreenLines(5, 10)[0])
- vsplit
- windo diffthis
- botright new
- " This 'tabstop' value should not be used for folding in other buffers.
- setlocal tabstop=4
- diffoff!
- redraw
- call assert_equal('2 2', ScreenLines(5, 10)[0])
-
- bwipe!
- bwipe!
-endfunc
-
-func Test_manual_fold_with_filter()
- CheckExecutable cat
- for type in ['manual', 'marker']
- exe 'set foldmethod=' . type
- new
- call setline(1, range(1, 20))
- 4,$fold
- %foldopen
- 10,$fold
- %foldopen
- " This filter command should not have an effect
- 1,8! cat
- call feedkeys('5ggzdzMGdd', 'xt')
- call assert_equal(['1', '2', '3', '4', '5', '6', '7', '8', '9'], getline(1, '$'))
-
- bwipe!
- set foldmethod&
- endfor
-endfunc
-
-func Test_indent_fold_with_read()
- new
- set foldmethod=indent
- call setline(1, repeat(["\<Tab>a"], 4))
- for n in range(1, 4)
- call assert_equal(1, foldlevel(n))
- endfor
-
- call writefile(["a", "", "\<Tab>a"], 'Xfile')
- foldopen
- 2read Xfile
- %foldclose
- call assert_equal(1, foldlevel(1))
- call assert_equal(2, foldclosedend(1))
- call assert_equal(0, foldlevel(3))
- call assert_equal(0, foldlevel(4))
- call assert_equal(1, foldlevel(5))
- call assert_equal(7, 5->foldclosedend())
-
- bwipe!
- set foldmethod&
- call delete('Xfile')
-endfunc
-
-func Test_combining_folds_indent()
- new
- let one = "\<Tab>a"
- let zero = 'a'
- call setline(1, [one, one, zero, zero, zero, one, one, one])
- set foldmethod=indent
- 3,5d
- %foldclose
- call assert_equal(5, foldclosedend(1))
-
- set foldmethod&
- bwipe!
-endfunc
-
-func Test_combining_folds_marker()
- new
- call setline(1, ['{{{', '}}}', '', '', '', '{{{', '', '}}}'])
- set foldmethod=marker
- 3,5d
- %foldclose
- call assert_equal(2, foldclosedend(1))
-
- set foldmethod&
- bwipe!
-endfunc
-
-func Test_folds_marker_in_comment()
- new
- call setline(1, ['" foo', 'bar', 'baz'])
- setl fen fdm=marker
- setl com=sO:\"\ -,mO:\"\ \ ,eO:\"\",:\" cms=\"%s
- norm! zf2j
- setl nofen
- :1y
- call assert_equal(['" foo{{{'], getreg(0,1,1))
- :+2y
- call assert_equal(['baz"}}}'], getreg(0,1,1))
-
- set foldmethod&
- bwipe!
-endfunc
-
-func s:TestFoldExpr(lnum)
- let thisline = getline(a:lnum)
- if thisline == 'a'
- return 1
- elseif thisline == 'b'
- return 0
- elseif thisline == 'c'
- return '<1'
- elseif thisline == 'd'
- return '>1'
- endif
- return 0
-endfunction
-
-func Test_update_folds_expr_read()
- new
- call setline(1, ['a', 'a', 'a', 'a', 'a', 'a'])
- set foldmethod=expr
- set foldexpr=s:TestFoldExpr(v:lnum)
- 2
- foldopen
- call writefile(['b', 'b', 'a', 'a', 'd', 'a', 'a', 'c'], 'Xfile')
- read Xfile
- %foldclose
- call assert_equal(2, foldclosedend(1))
- call assert_equal(0, foldlevel(3))
- call assert_equal(0, 4->foldlevel())
- call assert_equal(6, foldclosedend(5))
- call assert_equal(10, foldclosedend(7))
- call assert_equal(14, foldclosedend(11))
-
- call delete('Xfile')
- bwipe!
- set foldmethod& foldexpr&
-endfunc
-
-" Test for what patch 8.1.0535 fixes.
-func Test_foldexpr_no_interrupt_addsub()
- new
- func! FoldFunc()
- call setpos('.', getcurpos())
- return '='
- endfunc
-
- set foldmethod=expr
- set foldexpr=FoldFunc()
- call setline(1, '1.2')
-
- exe "norm! $\<C-A>"
- call assert_equal('1.3', getline(1))
-
- bwipe!
- delfunc FoldFunc
- set foldmethod& foldexpr&
-endfunc
-
-func Check_foldlevels(expected)
- call assert_equal(a:expected, map(range(1, line('$')), 'foldlevel(v:val)'))
-endfunc
-
-func Test_move_folds_around_manual()
- new
- let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")
- call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
- let folds=[-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14]
- " all folds closed
- set foldenable foldlevel=0 fdm=indent
- " needs a forced redraw
- redraw!
- set fdm=manual
- call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
- call assert_equal(input, getline(1, '$'))
- 7,12m0
- call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
- call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
- 10,12m0
- call assert_equal(PrepIndent("a")[1:] + PrepIndent("b") + ["a"] + PrepIndent("c"), getline(1, '$'))
- call assert_equal([1, 1, 1, 1, 1, -1, 7, 7, 7, 7, 7, -1, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
- " moving should not close the folds
- %d
- call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
- set fdm=indent
- redraw!
- set fdm=manual
- call cursor(2, 1)
- %foldopen
- 7,12m0
- let folds=repeat([-1], 18)
- call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
- call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
- norm! zM
- " folds are not corrupted and all have been closed
- call assert_equal([-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
- %d
- call setline(1, ["a", "\tb", "\tc", "\td", "\te"])
- set fdm=indent
- redraw!
- set fdm=manual
- %foldopen
- 3m4
- %foldclose
- call assert_equal(["a", "\tb", "\td", "\tc", "\te"], getline(1, '$'))
- call assert_equal([-1, 5, 5, 5, 5], map(range(1, line('$')), 'foldclosedend(v:val)'))
- %d
- call setline(1, ["a", "\tb", "\tc", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"])
- set fdm=indent foldlevel=0
- set fdm=manual
- %foldopen
- 3m1
- %foldclose
- call assert_equal(["a", "\tc", "\tb", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"], getline(1, '$'))
- call assert_equal(0, foldlevel(2))
- call assert_equal(5, foldclosedend(3))
- call assert_equal([-1, -1, 3, 3, 3, -1, 7, 7, 7, 7], map(range(1, line('$')), 'foldclosed(v:val)'))
- 2,6m$
- %foldclose
- call assert_equal(5, foldclosedend(2))
- call assert_equal(0, foldlevel(6))
- call assert_equal(9, foldclosedend(7))
- call assert_equal([-1, 2, 2, 2, 2, -1, 7, 7, 7, -1], map(range(1, line('$')), 'foldclosed(v:val)'))
-
- %d
- " Ensure moving around the edges still works.
- call setline(1, PrepIndent("a") + repeat(["a"], 3) + ["\ta"])
- set fdm=indent foldlevel=0
- set fdm=manual
- %foldopen
- 6m$
- " The first fold has been truncated to the 5'th line.
- " Second fold has been moved up because the moved line is now below it.
- call Check_foldlevels([0, 1, 1, 1, 1, 0, 0, 0, 1, 0])
-
- %delete
- set fdm=indent foldlevel=0
- call setline(1, [
- \ "a",
- \ "\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "a",
- \ "a"])
- set fdm=manual
- %foldopen!
- 4,5m6
- call Check_foldlevels([0, 1, 2, 0, 0, 0, 0])
-
- %delete
- set fdm=indent
- call setline(1, [
- \ "\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\t\ta",
- \ "\ta",
- \ "a"])
- set fdm=manual
- %foldopen!
- 13m7
- call Check_foldlevels([1, 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 2, 1, 0])
-
- bw!
-endfunc
-
-func Test_move_folds_around_indent()
- new
- let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")
- call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
- let folds=[-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14]
- " all folds closed
- set fdm=indent
- call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
- call assert_equal(input, getline(1, '$'))
- 7,12m0
- call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
- call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
- 10,12m0
- call assert_equal(PrepIndent("a")[1:] + PrepIndent("b") + ["a"] + PrepIndent("c"), getline(1, '$'))
- call assert_equal([1, 1, 1, 1, 1, -1, 7, 7, 7, 7, 7, -1, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
- " moving should not close the folds
- %d
- call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
- set fdm=indent
- call cursor(2, 1)
- %foldopen
- 7,12m0
- let folds=repeat([-1], 18)
- call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
- call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
- norm! zM
- " folds are not corrupted and all have been closed
- call assert_equal([-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
- %d
- call setline(1, ["a", "\tb", "\tc", "\td", "\te"])
- set fdm=indent
- %foldopen
- 3m4
- %foldclose
- call assert_equal(["a", "\tb", "\td", "\tc", "\te"], getline(1, '$'))
- call assert_equal([-1, 5, 5, 5, 5], map(range(1, line('$')), 'foldclosedend(v:val)'))
- %d
- call setline(1, ["a", "\tb", "\tc", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"])
- set fdm=indent foldlevel=0
- %foldopen
- 3m1
- %foldclose
- call assert_equal(["a", "\tc", "\tb", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"], getline(1, '$'))
- call assert_equal(1, foldlevel(2))
- call assert_equal(5, foldclosedend(3))
- call assert_equal([-1, 2, 2, 2, 2, -1, 7, 7, 7, 7], map(range(1, line('$')), 'foldclosed(v:val)'))
- 2,6m$
- %foldclose
- call assert_equal(9, foldclosedend(2))
- call assert_equal(1, foldlevel(6))
- call assert_equal(9, foldclosedend(7))
- call assert_equal([-1, 2, 2, 2, 2, 2, 2, 2, 2, -1], map(range(1, line('$')), 'foldclosed(v:val)'))
- " Ensure moving around the edges still works.
- %d
- call setline(1, PrepIndent("a") + repeat(["a"], 3) + ["\ta"])
- set fdm=indent foldlevel=0
- %foldopen
- 6m$
- " The first fold has been truncated to the 5'th line.
- " Second fold has been moved up because the moved line is now below it.
- call Check_foldlevels([0, 1, 1, 1, 1, 0, 0, 0, 1, 1])
- bw!
-endfunc
-
-func Test_folddoopen_folddoclosed()
- new
- call setline(1, range(1, 9))
- set foldmethod=manual
- 1,3 fold
- 6,8 fold
-
- " Test without range.
- folddoopen s/$/o/
- folddoclosed s/$/c/
- call assert_equal(['1c', '2c', '3c',
- \ '4o', '5o',
- \ '6c', '7c', '8c',
- \ '9o'], getline(1, '$'))
-
- " Test with range.
- call setline(1, range(1, 9))
- 1,8 folddoopen s/$/o/
- 4,$ folddoclosed s/$/c/
- call assert_equal(['1', '2', '3',
- \ '4o', '5o',
- \ '6c', '7c', '8c',
- \ '9'], getline(1, '$'))
-
- set foldmethod&
- bw!
-endfunc
-
-func Test_fold_error()
- new
- call setline(1, [1, 2])
-
- for fm in ['indent', 'expr', 'syntax', 'diff']
- exe 'set foldmethod=' . fm
- call assert_fails('norm zf', 'E350:')
- call assert_fails('norm zd', 'E351:')
- call assert_fails('norm zE', 'E352:')
- endfor
-
- set foldmethod=manual
- call assert_fails('norm zd', 'E490:')
- call assert_fails('norm zo', 'E490:')
- call assert_fails('3fold', 'E16:')
-
- set foldmethod=marker
- set nomodifiable
- call assert_fails('1,2fold', 'E21:')
-
- set modifiable&
- set foldmethod&
- bw!
-endfunc
-
-func Test_foldtext_recursive()
- new
- call setline(1, ['{{{', 'some text', '}}}'])
- setlocal foldenable foldmethod=marker foldtext=foldtextresult(v\:foldstart)
- " This was crashing because of endless recursion.
- 2foldclose
- redraw
- call assert_equal(1, foldlevel(2))
- call assert_equal(1, foldclosed(2))
- call assert_equal(3, foldclosedend(2))
- bwipe!
-endfunc
-
-" Various fold related tests
-
-" Basic test if a fold can be created, opened, moving to the end and closed
-func Test_fold_manual()
- new
- set fdm=manual
-
- let content = ['1 aa', '2 bb', '3 cc']
- call append(0, content)
- call cursor(1, 1)
- normal zf2j
- call assert_equal('1 aa', getline(foldclosed('.')))
- normal zo
- call assert_equal(-1, foldclosed('.'))
- normal ]z
- call assert_equal('3 cc', getline('.'))
- normal zc
- call assert_equal('1 aa', getline(foldclosed('.')))
-
- " Create a fold inside a closed fold after setting 'foldlevel'
- %d _
- call setline(1, range(1, 5))
- 1,5fold
- normal zR
- 2,4fold
- set foldlevel=1
- 3fold
- call assert_equal([1, 3, 3, 3, 1], map(range(1, 5), {->foldlevel(v:val)}))
- set foldlevel&
-
- " Create overlapping folds (at the start and at the end)
- normal zE
- 2,3fold
- normal zR
- 3,4fold
- call assert_equal([0, 2, 2, 1, 0], map(range(1, 5), {->foldlevel(v:val)}))
- normal zE
- 3,4fold
- normal zR
- 2,3fold
- call assert_equal([0, 1, 2, 2, 0], map(range(1, 5), {->foldlevel(v:val)}))
-
- " Create a nested fold across two non-adjoining folds
- %d _
- call setline(1, range(1, 7))
- 1,2fold
- normal zR
- 4,5fold
- normal zR
- 6,7fold
- normal zR
- 1,5fold
- call assert_equal([2, 2, 1, 2, 2, 1, 1],
- \ map(range(1, 7), {->foldlevel(v:val)}))
-
- " A newly created nested fold should be closed
- %d _
- call setline(1, range(1, 6))
- 1,6fold
- normal zR
- 3,4fold
- normal zR
- 2,5fold
- call assert_equal([1, 2, 3, 3, 2, 1], map(range(1, 6), {->foldlevel(v:val)}))
- call assert_equal(2, foldclosed(4))
- call assert_equal(5, foldclosedend(4))
-
- " Test zO, zC and zA on a line with no folds.
- normal zE
- call assert_fails('normal zO', 'E490:')
- call assert_fails('normal zC', 'E490:')
- call assert_fails('normal zA', 'E490:')
-
- set fdm&
- bw!
-endfunc
-
-" test folding with markers.
-func Test_fold_marker()
- new
- set fdm=marker fdl=1 fdc=3
-
- let content = ['4 dd {{{', '5 ee {{{ }}}', '6 ff }}}']
- call append(0, content)
- call cursor(2, 1)
- call assert_equal(2, foldlevel('.'))
- normal [z
- call assert_equal(1, foldlevel('.'))
- exe "normal jo{{ \<Esc>r{jj"
- call assert_equal(1, foldlevel('.'))
- normal kYpj
- call assert_equal(0, foldlevel('.'))
-
- " Use only closing fold marker (without and with a count)
- set fdl&
- %d _
- call setline(1, ['one }}}', 'two'])
- call assert_equal([0, 0], [foldlevel(1), foldlevel(2)])
- %d _
- call setline(1, ['one }}}4', 'two'])
- call assert_equal([4, 3], [foldlevel(1), foldlevel(2)])
-
- set fdm& fdl& fdc&
- bw!
-endfunc
-
-" test create fold markers with C filetype
-func Test_fold_create_marker_in_C()
- bw!
- set fdm=marker fdl=9
- set filetype=c
-
- 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)
- call cursor(c + 1, 1)
- norm! zfG
- call assert_equal(content[c] . (c < 4 ? '{{{' : '/*{{{*/'), getline(c + 1))
- endfor
-
- set fdm& fdl&
- bw!
-endfunc
-
-" test folding with indent
-func Test_fold_indent()
- new
- set fdm=indent sw=2
-
- let content = ['1 aa', '2 bb', '3 cc']
- call append(0, content)
- call cursor(2, 1)
- exe "normal i \<Esc>jI "
- call assert_equal(2, foldlevel('.'))
- normal k
- call assert_equal(1, foldlevel('.'))
-
- set fdm& sw&
- bw!
-endfunc
-
-" test syntax folding
-func Test_fold_syntax()
- CheckFeature syntax
-
- new
- set fdm=syntax fdl=0
-
- syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3
- syn region Fd1 start="ee" end="ff" fold contained
- syn region Fd2 start="gg" end="hh" fold contained
- syn region Fd3 start="commentstart" end="commentend" fold contained
- let content = ['3 cc', '4 dd {{{', '5 ee {{{ }}}', '{{{{', '6 ff }}}',
- \ '6 ff }}}', '7 gg', '8 hh', '9 ii']
- call append(0, content)
- normal Gzk
- call assert_equal('9 ii', getline('.'))
- normal k
- call assert_equal('3 cc', getline('.'))
- exe "normal jAcommentstart \<Esc>Acommentend"
- set fdl=1
- normal 3j
- call assert_equal('7 gg', getline('.'))
- set fdl=0
- exe "normal zO\<C-L>j"
- call assert_equal('8 hh', getline('.'))
- syn clear Fd1 Fd2 Fd3 Hup
-
- set fdm& fdl&
- bw!
-endfunc
-
-func Flvl()
- let l = getline(v:lnum)
- if l =~ "bb$"
- return 2
- elseif l =~ "gg$"
- return "s1"
- elseif l =~ "ii$"
- return ">2"
- elseif l =~ "kk$"
- return "0"
- endif
- return "="
-endfun
-
-" test expression folding
-func Test_fold_expr()
- new
- set fdm=expr fde=Flvl()
-
- let content = ['1 aa',
- \ '2 bb',
- \ '3 cc',
- \ '4 dd {{{commentstart commentend',
- \ '5 ee {{{ }}}',
- \ '{{{',
- \ '6 ff }}}',
- \ '6 ff }}}',
- \ ' 7 gg',
- \ ' 8 hh',
- \ '9 ii',
- \ 'a jj',
- \ 'b kk']
- call append(0, content)
- call cursor(1, 1)
- exe "normal /bb$\<CR>"
- call assert_equal(2, foldlevel('.'))
- exe "normal /hh$\<CR>"
- call assert_equal(1, foldlevel('.'))
- exe "normal /ii$\<CR>"
- call assert_equal(2, foldlevel('.'))
- exe "normal /kk$\<CR>"
- call assert_equal(0, foldlevel('.'))
-
- set fdm& fde&
- bw!
-endfunc
-
-" Bug with fdm=indent and moving folds
-" Moving a fold a few times, messes up the folds below the moved fold.
-" Fixed by 7.4.700
-func Test_fold_move()
- new
- set fdm=indent sw=2 fdl=0
-
- let content = ['', '', 'Line1', ' Line2', ' Line3',
- \ 'Line4', ' Line5', ' Line6',
- \ 'Line7', ' Line8', ' Line9']
- call append(0, content)
- normal zM
- call cursor(4, 1)
- move 2
- move 1
- call assert_equal(7, foldclosed(7))
- call assert_equal(8, foldclosedend(7))
- call assert_equal(0, foldlevel(9))
- call assert_equal(10, foldclosed(10))
- call assert_equal(11, foldclosedend(10))
- call assert_equal('+-- 2 lines: Line2', foldtextresult(2))
- call assert_equal('+-- 2 lines: Line8', 10->foldtextresult())
-
- set fdm& sw& fdl&
- bw!
-endfunc
-
-" test for patch 7.3.637
-" Cannot catch the error caused by a foldopen when there is no fold.
-func Test_foldopen_exception()
- new
- let a = 'No error caught'
- try
- foldopen
- catch
- let a = matchstr(v:exception,'^[^ ]*')
- endtry
- call assert_equal('Vim(foldopen):E490:', a)
-
- let a = 'No error caught'
- try
- foobar
- catch
- let a = matchstr(v:exception,'^[^ ]*')
- endtry
- call assert_match('E492:', a)
- bw!
-endfunc
-
-func Test_fold_last_line_with_pagedown()
- new
- set fdm=manual
-
- let expect = '+-- 11 lines: 9---'
- let content = range(1,19)
- call append(0, content)
- normal dd9G
- normal zfG
- normal zt
- call assert_equal('9', getline(foldclosed('.')))
- call assert_equal('19', getline(foldclosedend('.')))
- call assert_equal(expect, ScreenLines(1, len(expect))[0])
- call feedkeys("\<C-F>", 'xt')
- call assert_equal(expect, ScreenLines(1, len(expect))[0])
- call feedkeys("\<C-F>", 'xt')
- call assert_equal(expect, ScreenLines(1, len(expect))[0])
- call feedkeys("\<C-B>\<C-F>\<C-F>", 'xt')
- call assert_equal(expect, ScreenLines(1, len(expect))[0])
-
- set fdm&
- bw!
-endfunc
-
-func Test_folds_with_rnu()
- CheckScreendump
-
- call writefile([
- \ 'set fdm=marker rnu foldcolumn=2',
- \ 'call setline(1, ["{{{1", "nline 1", "{{{1", "line 2"])',
- \ ], 'Xtest_folds_with_rnu')
- let buf = RunVimInTerminal('-S Xtest_folds_with_rnu', {})
-
- call VerifyScreenDump(buf, 'Test_folds_with_rnu_01', {})
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_folds_with_rnu_02', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_folds_with_rnu')
-endfunc
-
-func Test_folds_marker_in_comment2()
- new
- call setline(1, ['Lorem ipsum dolor sit', 'Lorem ipsum dolor sit', 'Lorem ipsum dolor sit'])
- setl fen fdm=marker
- setl commentstring=<!--%s-->
- setl comments=s:<!--,m:\ \ \ \ ,e:-->
- norm! zf2j
- setl nofen
- :1y
- call assert_equal(['Lorem ipsum dolor sit<!--{{{-->'], getreg(0,1,1))
- :+2y
- call assert_equal(['Lorem ipsum dolor sit<!--}}}-->'], getreg(0,1,1))
-
- set foldmethod&
- bwipe!
-endfunc
-
-func Test_fold_delete_with_marker()
- new
- call setline(1, ['func Func() {{{1', 'endfunc'])
- 1,2yank
- new
- set fdm=marker
- call setline(1, 'x')
- normal! Vp
- normal! zd
- call assert_equal(['func Func() ', 'endfunc'], getline(1, '$'))
-
- set fdm&
- 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
-
-" Add a test for deleting the outer fold of a nested fold and promoting the
-" inner folds to one level up with already a fold at that level following the
-" nested fold.
-func Test_fold_delete_recursive_fold()
- new
- call setline(1, range(1, 7))
- 2,3fold
- normal zR
- 4,5fold
- normal zR
- 1,5fold
- normal zR
- 6,7fold
- normal zR
- normal 1Gzd
- normal 1Gzj
- call assert_equal(2, line('.'))
- normal zj
- call assert_equal(4, line('.'))
- normal zj
- call assert_equal(6, line('.'))
- bw!
-endfunc
-
-" Test for errors in 'foldexpr'
-func Test_fold_expr_error()
- new
- call setline(1, ['one', 'two', 'three'])
- " In a window with no folds, foldlevel() should return 0
- call assert_equal(0, foldlevel(1))
-
- " Return a list from the expression
- set foldexpr=[]
- set foldmethod=expr
- for i in range(3)
- call assert_equal(0, foldlevel(i))
- endfor
-
- " expression error
- set foldexpr=[{]
- set foldmethod=expr
- for i in range(3)
- call assert_equal(0, foldlevel(i))
- endfor
-
- set foldmethod& foldexpr&
- close!
-endfunc
-
-func Test_undo_fold_deletion()
- new
- set fdm=marker
- let lines =<< trim END
- " {{{
- " }}}1
- " {{{
- END
- call setline(1, lines)
- 3d
- g/"/d
- undo
- redo
- eval getline(1, '$')->assert_equal([''])
-
- set fdm&vim
- bwipe!
-endfunc
-
-" this was crashing
-func Test_move_no_folds()
- new
- fold
- setlocal fdm=expr
- normal zj
- bwipe!
-endfunc
-
-" this was crashing
-func Test_fold_create_delete_create()
- new
- fold
- fold
- normal zd
- fold
- bwipe!
-endfunc
-
-" this was crashing
-func Test_fold_create_delete()
- new
- norm zFzFzdzj
- bwipe!
-endfunc
-
-func Test_fold_relative_move()
- new
- set fdm=indent sw=2 wrap tw=80
-
- let longtext = repeat('x', &columns + 1)
- let content = [ ' foo', ' ' .. longtext, ' baz',
- \ longtext,
- \ ' foo', ' ' .. longtext, ' baz'
- \ ]
- call append(0, content)
-
- normal zM
-
- for lnum in range(1, 3)
- call cursor(lnum, 1)
- call assert_true(foldclosed(line('.')))
- normal gj
- call assert_equal(2, winline())
- endfor
-
- call cursor(2, 1)
- call assert_true(foldclosed(line('.')))
- normal 2gj
- call assert_equal(3, winline())
-
- for lnum in range(5, 7)
- call cursor(lnum, 1)
- call assert_true(foldclosed(line('.')))
- normal gk
- call assert_equal(3, winline())
- endfor
-
- call cursor(6, 1)
- call assert_true(foldclosed(line('.')))
- normal 2gk
- call assert_equal(2, winline())
-
- set fdm& sw& wrap& tw&
- bw!
-endfunc
-
-" Test for calling foldlevel() from a fold expression
-let g:FoldLevels = []
-func FoldExpr1(lnum)
- let f = [a:lnum]
- for i in range(1, line('$'))
- call add(f, foldlevel(i))
- endfor
- call add(g:FoldLevels, f)
- return getline(a:lnum)[0] == "\t"
-endfunc
-
-func Test_foldexpr_foldlevel()
- new
- call setline(1, ['one', "\ttwo", "\tthree"])
- setlocal foldmethod=expr
- setlocal foldexpr=FoldExpr1(v:lnum)
- setlocal foldenable
- setlocal foldcolumn=3
- redraw!
- call assert_equal([[1, -1, -1, -1], [2, -1, -1, -1], [3, 0, 1, -1]],
- \ g:FoldLevels)
- set foldmethod& foldexpr& foldenable& foldcolumn&
- bw!
-endfunc
-
-" Test for returning different values from a fold expression
-func FoldExpr2(lnum)
- if a:lnum == 1 || a:lnum == 4
- return -2
- elseif a:lnum == 2
- return 'a1'
- elseif a:lnum == 3
- return 's4'
- endif
- return '='
-endfunc
-
-func Test_foldexpr_2()
- new
- call setline(1, ['one', 'two', 'three', 'four'])
- setlocal foldexpr=FoldExpr2(v:lnum)
- setlocal foldmethod=expr
- call assert_equal([0, 1, 1, 0], [foldlevel(1), foldlevel(2), foldlevel(3),
- \ foldlevel(4)])
- bw!
-endfunc
-
-" Test for the 'foldclose' option
-func Test_foldclose_opt()
- CheckScreendump
-
- let lines =<< trim END
- set foldmethod=manual foldclose=all foldopen=all
- call setline(1, ['one', 'two', 'three', 'four'])
- 2,3fold
- func XsaveFoldLevels()
- redraw!
- call writefile([json_encode([foldclosed(1), foldclosed(2), foldclosed(3),
- \ foldclosed(4)])], 'Xoutput', 'a')
- endfunc
- END
- call writefile(lines, 'Xscript')
- let rows = 10
- let buf = RunVimInTerminal('-S Xscript', {'rows': rows})
- call term_wait(buf)
- call term_sendkeys(buf, ":set noruler\n")
- call term_wait(buf)
- call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
- call term_sendkeys(buf, "2G")
- call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))})
- call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
- call term_sendkeys(buf, "4G")
- call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))})
- call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
- call term_sendkeys(buf, "3G")
- call WaitForAssert({-> assert_equal('three', term_getline(buf, 3))})
- call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
- call term_sendkeys(buf, "1G")
- call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))})
- call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
- call term_sendkeys(buf, "2G")
- call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))})
- call term_sendkeys(buf, "k")
- call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))})
-
- " clean up
- call StopVimInTerminal(buf)
-
- call assert_equal(['[-1,2,2,-1]', '[-1,-1,-1,-1]', '[-1,2,2,-1]',
- \ '[-1,-1,-1,-1]', '[-1,2,2,-1]'], readfile('Xoutput'))
- call delete('Xscript')
- call delete('Xoutput')
-endfunc
-
-" Test for foldtextresult()
-func Test_foldtextresult()
- new
- call assert_equal('', foldtextresult(-1))
- call assert_equal('', foldtextresult(0))
- call assert_equal('', foldtextresult(1))
- call setline(1, ['one', 'two', 'three', 'four'])
- 2,3fold
- call assert_equal('', foldtextresult(1))
- call assert_equal('+-- 2 lines: two', foldtextresult(2))
- setlocal foldtext=
- call assert_equal('+-- 2 lines folded ', foldtextresult(2))
-
- " Fold text for a C comment fold
- %d _
- setlocal foldtext&
- call setline(1, ['', '/*', ' * Comment', ' */', ''])
- 2,4fold
- call assert_equal('+-- 3 lines: Comment', foldtextresult(2))
-
- bw!
-endfunc
-
-" Test for merging two recursive folds when an intermediate line with no fold
-" is removed
-func Test_fold_merge_recursive()
- new
- call setline(1, [' one', ' two', 'xxxx', ' three',
- \ ' four', "\tfive"])
- setlocal foldmethod=indent shiftwidth=2
- 3d_
- %foldclose
- call assert_equal([1, 5], [foldclosed(5), foldclosedend(1)])
- bw!
-endfunc
-
-" Test for moving a line which is the start of a fold from a recursive fold to
-" outside. The fold length should reduce.
-func Test_fold_move_foldlevel()
- new
- call setline(1, ['a{{{', 'b{{{', 'c{{{', 'd}}}', 'e}}}', 'f}}}', 'g'])
- setlocal foldmethod=marker
- normal zR
- call assert_equal([3, 2, 1], [foldlevel(4), foldlevel(5), foldlevel(6)])
- 3move 7
- call assert_equal([2, 1, 0], [foldlevel(3), foldlevel(4), foldlevel(5)])
- call assert_equal(1, foldlevel(7))
-
- " Move a line from outside a fold to inside the fold.
- %d _
- call setline(1, ['a', 'b{{{', 'c}}}'])
- normal zR
- 1move 2
- call assert_equal([1, 1, 1], [foldlevel(1), foldlevel(2), foldlevel(3)])
-
- " Move the start of one fold to inside another fold
- %d _
- call setline(1, ['a', 'b{{{', 'c}}}', 'd{{{', 'e}}}'])
- normal zR
- call assert_equal([0, 1, 1, 1, 1], [foldlevel(1), foldlevel(2),
- \ foldlevel(3), foldlevel(4), foldlevel(5)])
- 1,2move 4
- call assert_equal([0, 1, 1, 2, 2], [foldlevel(1), foldlevel(2),
- \ foldlevel(3), foldlevel(4), foldlevel(5)])
-
- bw!
-endfunc
-
-" Test for using zj and zk to move downwards and upwards to the start and end
-" of the next fold.
-" Test for using [z and ]z in a closed fold to jump to the beginning and end
-" of the fold.
-func Test_fold_jump()
- new
- call setline(1, ["\t1", "\t2", "\t\t3", "\t\t4", "\t\t\t5", "\t\t\t6", "\t\t7", "\t\t8", "\t9", "\t10"])
- setlocal foldmethod=indent
- normal zR
- normal zj
- call assert_equal(3, line('.'))
- normal zj
- call assert_equal(5, line('.'))
- call assert_beeps('normal zj')
- call assert_equal(5, line('.'))
- call assert_beeps('normal 9Gzj')
- call assert_equal(9, line('.'))
- normal Gzk
- call assert_equal(8, line('.'))
- normal zk
- call assert_equal(6, line('.'))
- call assert_beeps('normal zk')
- call assert_equal(6, line('.'))
- call assert_beeps('normal 2Gzk')
- call assert_equal(2, line('.'))
-
- " Using [z or ]z in a closed fold should not move the cursor
- %d _
- call setline(1, ["1", "\t2", "\t3", "\t4", "\t5", "\t6", "7"])
- normal zR4Gzc
- call assert_equal(4, line('.'))
- call assert_beeps('normal [z')
- call assert_equal(4, line('.'))
- call assert_beeps('normal ]z')
- call assert_equal(4, line('.'))
- bw!
-endfunc
-
-" Test for using a script-local function for 'foldexpr'
-func Test_foldexpr_scriptlocal_func()
- func! s:FoldFunc()
- let g:FoldLnum = v:lnum
- endfunc
- new | only
- call setline(1, 'abc')
- let g:FoldLnum = 0
- set foldmethod=expr foldexpr=s:FoldFunc()
- redraw!
- call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
- call assert_equal(1, g:FoldLnum)
- set foldmethod& foldexpr=
- bw!
- new | only
- call setline(1, 'abc')
- let g:FoldLnum = 0
- set foldmethod=expr foldexpr=<SID>FoldFunc()
- redraw!
- call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
- call assert_equal(1, g:FoldLnum)
- set foldmethod& foldexpr=
- delfunc s:FoldFunc
- bw!
-endfunc
-
-" Test for using a script-local function for 'foldtext'
-func Test_foldtext_scriptlocal_func()
- func! s:FoldText()
- let g:FoldTextArgs = [v:foldstart, v:foldend]
- return foldtext()
- endfunc
- new | only
- call setline(1, range(50))
- let g:FoldTextArgs = []
- set foldmethod=manual
- set foldtext=s:FoldText()
- norm! 4Gzf4j
- redraw!
- call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext)
- call assert_equal([4, 8], g:FoldTextArgs)
- set foldtext&
- bw!
- new | only
- call setline(1, range(50))
- let g:FoldTextArgs = []
- set foldmethod=manual
- set foldtext=<SID>FoldText()
- norm! 8Gzf4j
- redraw!
- call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext)
- call assert_equal([8, 12], g:FoldTextArgs)
- set foldtext&
- bw!
- delfunc s:FoldText
-endfunc
-
-" Make sure a fold containing a nested fold is split correctly when using
-" foldmethod=indent
-func Test_fold_split()
- new
- let lines =<< trim END
- line 1
- line 2
- line 3
- line 4
- line 5
- END
- call setline(1, lines)
- setlocal sw=2
- setlocal foldmethod=indent foldenable
- call assert_equal([0, 1, 1, 2, 2], range(1, 5)->map('foldlevel(v:val)'))
- call append(2, 'line 2.5')
- call assert_equal([0, 1, 0, 1, 2, 2], range(1, 6)->map('foldlevel(v:val)'))
- bw!
-endfunc
-
-" Make sure that when you append under a blank line that is under a fold with
-" the same indent level as your appended line, the fold expands across the
-" blank line
-func Test_indent_append_under_blank_line()
- new
- let lines =<< trim END
- line 1
- line 2
- line 3
- END
- call setline(1, lines)
- setlocal sw=2
- setlocal foldmethod=indent foldenable
- call assert_equal([0, 1, 1], range(1, 3)->map('foldlevel(v:val)'))
- call append(3, '')
- call append(4, ' line 5')
- call assert_equal([0, 1, 1, 1, 1], range(1, 5)->map('foldlevel(v:val)'))
- bw!
-endfunc
-
-" Make sure that when you delete 1 line of a fold whose length is 2 lines, the
-" fold can't be closed since its length (1) is now less than foldminlines.
-func Test_indent_one_line_fold_close()
- let lines =<< trim END
- line 1
- line 2
- line 3
- END
-
- new
- setlocal sw=2 foldmethod=indent
- call setline(1, lines)
- " open all folds, delete line, then close all folds
- normal zR
- 3delete
- normal zM
- call assert_equal(-1, foldclosed(2)) " the fold should not be closed
-
- " Now do the same, but delete line 2 this time; this covers different code.
- " (Combining this code with the above code doesn't expose both bugs.)
- 1,$delete
- call setline(1, lines)
- normal zR
- 2delete
- normal zM
- call assert_equal(-1, foldclosed(2))
- bw!
-endfunc
-
-" Make sure that when appending [an indented line then a blank line] right
-" before a single indented line, the resulting extended fold can be closed
-func Test_indent_append_blank_small_fold_close()
- new
- setlocal sw=2 foldmethod=indent
- " at first, the fold at the second line can't be closed since it's smaller
- " than foldminlines
- let lines =<< trim END
- line 1
- line 4
- END
- call setline(1, lines)
- call append(1, [' line 2', ''])
- " close all folds
- normal zM
- call assert_notequal(-1, foldclosed(2)) " the fold should be closed now
- bw!
-endfunc
-
-func Test_sort_closed_fold()
- CheckExecutable sort
-
- call setline(1, [
- \ 'Section 1',
- \ ' how',
- \ ' now',
- \ ' brown',
- \ ' cow',
- \ 'Section 2',
- \ ' how',
- \ ' now',
- \ ' brown',
- \ ' cow',
- \])
- setlocal foldmethod=indent sw=3
- normal 2G
-
- " The "!!" expands to ".,.+3" and must only sort four lines
- call feedkeys("!!sort\<CR>", 'xt')
- call assert_equal([
- \ 'Section 1',
- \ ' brown',
- \ ' cow',
- \ ' how',
- \ ' now',
- \ 'Section 2',
- \ ' how',
- \ ' now',
- \ ' brown',
- \ ' cow',
- \ ], getline(1, 10))
-
- bwipe!
-endfunc
-
-func Test_indent_with_L_command()
- " The "L" command moved the cursor to line zero, causing the text saved for
- " undo to use line number -1, which caused trouble for undo later.
- new
- sil! norm 8R V{zf8=Lu
- bwipe!
-endfunc
-
-" Make sure that when there is a fold at the bottom of the buffer and a newline
-" character is appended to the line, the fold gets expanded (instead of the new
-" line not being part of the fold).
-func Test_expand_fold_at_bottom_of_buffer()
- new
- " create a fold on the only line
- fold
- execute "normal A\<CR>"
- call assert_equal([1, 1], range(1, 2)->map('foldlevel(v:val)'))
-
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
deleted file mode 100644
index 500c30c76b..0000000000
--- a/src/nvim/testdir/test_functions.vim
+++ /dev/null
@@ -1,2559 +0,0 @@
-" Tests for various functions.
-
-source shared.vim
-source check.vim
-source term_util.vim
-source screendump.vim
-source vim9.vim
-
-" Must be done first, since the alternate buffer must be unset.
-func Test_00_bufexists()
- call assert_equal(0, bufexists('does_not_exist'))
- call assert_equal(1, bufexists(bufnr('%')))
- call assert_equal(0, bufexists(0))
- new Xfoo
- let bn = bufnr('%')
- call assert_equal(1, bufexists(bn))
- call assert_equal(1, bufexists('Xfoo'))
- call assert_equal(1, bufexists(getcwd() . '/Xfoo'))
- call assert_equal(1, bufexists(0))
- bw
- call assert_equal(0, bufexists(bn))
- call assert_equal(0, bufexists('Xfoo'))
-endfunc
-
-func Test_has()
- throw 'Skipped: Nvim has removed some features'
- call assert_equal(1, has('eval'))
- call assert_equal(1, has('eval', 1))
-
- if has('unix')
- call assert_equal(1, or(has('ttyin'), 1))
- call assert_equal(0, and(has('ttyout'), 0))
- call assert_equal(1, has('multi_byte_encoding'))
- endif
- call assert_equal(1, has('vcon', 1))
- call assert_equal(1, has('mouse_gpm_enabled', 1))
-
- call assert_equal(0, has('nonexistent'))
- call assert_equal(0, has('nonexistent', 1))
-
- " Will we ever have patch 9999?
- let ver = 'patch-' .. v:version / 100 .. '.' .. v:version % 100 .. '.9999'
- call assert_equal(0, has(ver))
-endfunc
-
-func Test_empty()
- call assert_equal(1, empty(''))
- call assert_equal(0, empty('a'))
-
- call assert_equal(1, empty(0))
- call assert_equal(1, empty(-0))
- call assert_equal(0, empty(1))
- call assert_equal(0, empty(-1))
-
- if has('float')
- call assert_equal(1, empty(0.0))
- call assert_equal(1, empty(-0.0))
- call assert_equal(0, empty(1.0))
- call assert_equal(0, empty(-1.0))
- call assert_equal(0, empty(1.0/0.0))
- call assert_equal(0, empty(0.0/0.0))
- endif
-
- call assert_equal(1, empty([]))
- call assert_equal(0, empty(['a']))
-
- call assert_equal(1, empty({}))
- call assert_equal(0, empty({'a':1}))
-
- call assert_equal(1, empty(v:null))
- " call assert_equal(1, empty(v:none))
- call assert_equal(1, empty(v:false))
- call assert_equal(0, empty(v:true))
-
- if has('channel')
- call assert_equal(1, empty(test_null_channel()))
- endif
- if has('job')
- call assert_equal(1, empty(test_null_job()))
- endif
-
- call assert_equal(0, empty(function('Test_empty')))
- call assert_equal(0, empty(function('Test_empty', [0])))
-endfunc
-
-func Test_len()
- call assert_equal(1, len(0))
- call assert_equal(2, len(12))
-
- call assert_equal(0, len(''))
- call assert_equal(2, len('ab'))
-
- call assert_equal(0, len([]))
- call assert_equal(0, len(v:_null_list))
- call assert_equal(2, len([2, 1]))
-
- call assert_equal(0, len({}))
- call assert_equal(0, len(v:_null_dict))
- call assert_equal(2, len({'a': 1, 'b': 2}))
-
- " call assert_fails('call len(v:none)', 'E701:')
- call assert_fails('call len({-> 0})', 'E701:')
-endfunc
-
-func Test_max()
- call assert_equal(0, max([]))
- call assert_equal(2, max([2]))
- call assert_equal(2, max([1, 2]))
- call assert_equal(2, max([1, 2, v:null]))
-
- call assert_equal(0, max({}))
- call assert_equal(2, max({'a':1, 'b':2}))
-
- call assert_fails('call max(1)', 'E712:')
- " call assert_fails('call max(v:none)', 'E712:')
-
- " check we only get one error
- call assert_fails('call max([#{}, [1]])', ['E728:', 'E728:'])
- call assert_fails('call max(#{a: {}, b: [1]})', ['E728:', 'E728:'])
-endfunc
-
-func Test_min()
- call assert_equal(0, min([]))
- call assert_equal(2, min([2]))
- call assert_equal(1, min([1, 2]))
- call assert_equal(0, min([1, 2, v:null]))
-
- call assert_equal(0, min({}))
- call assert_equal(1, min({'a':1, 'b':2}))
-
- call assert_fails('call min(1)', 'E712:')
- " call assert_fails('call min(v:none)', 'E712:')
- call assert_fails('call min([1, {}])', 'E728:')
-
- " check we only get one error
- call assert_fails('call min([[1], #{}])', ['E745:', 'E745:'])
- call assert_fails('call min(#{a: [1], b: #{}})', ['E745:', 'E745:'])
-endfunc
-
-func Test_strwidth()
- for aw in ['single', 'double']
- exe 'set ambiwidth=' . aw
- call assert_equal(0, strwidth(''))
- call assert_equal(1, strwidth("\t"))
- call assert_equal(3, strwidth('Vim'))
- call assert_equal(4, strwidth(1234))
- call assert_equal(5, strwidth(-1234))
-
- call assert_equal(2, strwidth('😉'))
- call assert_equal(17, strwidth('EÄ¥oÅanÄo ĉiuĵaÅ­de'))
- call assert_equal((aw == 'single') ? 6 : 7, strwidth('Straße'))
-
- call assert_fails('call strwidth({->0})', 'E729:')
- call assert_fails('call strwidth([])', 'E730:')
- call assert_fails('call strwidth({})', 'E731:')
- if has('float')
- call assert_fails('call strwidth(1.2)', 'E806:')
- endif
- endfor
-
- set ambiwidth&
-endfunc
-
-func Test_str2nr()
- call assert_equal(0, str2nr(''))
- call assert_equal(1, str2nr('1'))
- call assert_equal(1, str2nr(' 1 '))
-
- call assert_equal(1, str2nr('+1'))
- call assert_equal(1, str2nr('+ 1'))
- call assert_equal(1, str2nr(' + 1 '))
-
- call assert_equal(-1, str2nr('-1'))
- call assert_equal(-1, str2nr('- 1'))
- call assert_equal(-1, str2nr(' - 1 '))
-
- call assert_equal(123456789, str2nr('123456789'))
- call assert_equal(-123456789, str2nr('-123456789'))
-
- call assert_equal(5, str2nr('101', 2))
- call assert_equal(5, '0b101'->str2nr(2))
- call assert_equal(5, str2nr('0B101', 2))
- call assert_equal(-5, str2nr('-101', 2))
- call assert_equal(-5, str2nr('-0b101', 2))
- call assert_equal(-5, str2nr('-0B101', 2))
-
- call assert_equal(65, str2nr('101', 8))
- call assert_equal(65, str2nr('0101', 8))
- call assert_equal(-65, str2nr('-101', 8))
- call assert_equal(-65, str2nr('-0101', 8))
- call assert_equal(65, str2nr('0o101', 8))
- call assert_equal(65, str2nr('0O0101', 8))
- call assert_equal(-65, str2nr('-0O101', 8))
- call assert_equal(-65, str2nr('-0o0101', 8))
-
- call assert_equal(11259375, str2nr('abcdef', 16))
- call assert_equal(11259375, str2nr('ABCDEF', 16))
- call assert_equal(-11259375, str2nr('-ABCDEF', 16))
- call assert_equal(11259375, str2nr('0xabcdef', 16))
- call assert_equal(11259375, str2nr('0Xabcdef', 16))
- call assert_equal(11259375, str2nr('0XABCDEF', 16))
- call assert_equal(-11259375, str2nr('-0xABCDEF', 16))
-
- call assert_equal(1, str2nr("1'000'000", 10, 0))
- call assert_equal(256, str2nr("1'0000'0000", 2, 1))
- call assert_equal(262144, str2nr("1'000'000", 8, 1))
- call assert_equal(1000000, str2nr("1'000'000", 10, 1))
- call assert_equal(1000, str2nr("1'000''000", 10, 1))
- call assert_equal(65536, str2nr("1'00'00", 16, 1))
-
- call assert_equal(0, str2nr('0x10'))
- call assert_equal(0, str2nr('0b10'))
- call assert_equal(0, str2nr('0o10'))
- call assert_equal(1, str2nr('12', 2))
- call assert_equal(1, str2nr('18', 8))
- call assert_equal(1, str2nr('1g', 16))
-
- call assert_equal(0, str2nr(v:null))
- " call assert_equal(0, str2nr(v:none))
-
- call assert_fails('call str2nr([])', 'E730:')
- call assert_fails('call str2nr({->2})', 'E729:')
- if has('float')
- call assert_fails('call str2nr(1.2)', 'E806:')
- endif
- call assert_fails('call str2nr(10, [])', 'E745:')
-endfunc
-
-func Test_strftime()
- CheckFunction strftime
-
- " Format of strftime() depends on system. We assume
- " that basic formats tested here are available and
- " identical on all systems which support strftime().
- "
- " The 2nd parameter of strftime() is a local time, so the output day
- " of strftime() can be 17 or 18, depending on timezone.
- call assert_match('^2017-01-1[78]$', strftime('%Y-%m-%d', 1484695512))
- "
- call assert_match('^\d\d\d\d-\(0\d\|1[012]\)-\([012]\d\|3[01]\) \([01]\d\|2[0-3]\):[0-5]\d:\([0-5]\d\|60\)$', '%Y-%m-%d %H:%M:%S'->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_strptime()
- CheckFunction strptime
- CheckNotMSWindows
-
- if exists('$TZ')
- let tz = $TZ
- endif
- let $TZ = 'UTC'
-
- call assert_equal(1484653763, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23'))
-
- " Force DST and check that it's considered
- let $TZ = 'WINTER0SUMMER,J1,J365'
- call assert_equal(1484653763 - 3600, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23'))
-
- call assert_fails('call strptime()', 'E119:')
- call assert_fails('call strptime("xxx")', 'E119:')
- call assert_equal(0, strptime("%Y", ''))
- call assert_equal(0, strptime("%Y", "xxx"))
-
- if exists('tz')
- let $TZ = tz
- else
- unlet $TZ
- endif
-endfunc
-
-func Test_resolve_unix()
- if !has('unix')
- return
- endif
-
- " Xlink1 -> Xlink2
- " Xlink2 -> Xlink3
- silent !ln -s -f Xlink2 Xlink1
- silent !ln -s -f Xlink3 Xlink2
- call assert_equal('Xlink3', resolve('Xlink1'))
- call assert_equal('./Xlink3', resolve('./Xlink1'))
- call assert_equal('Xlink3/', resolve('Xlink2/'))
- " FIXME: these tests result in things like "Xlink2/" instead of "Xlink3/"?!
- "call assert_equal('Xlink3/', resolve('Xlink1/'))
- "call assert_equal('./Xlink3/', resolve('./Xlink1/'))
- "call assert_equal(getcwd() . '/Xlink3/', resolve(getcwd() . '/Xlink1/'))
- call assert_equal(getcwd() . '/Xlink3', resolve(getcwd() . '/Xlink1'))
-
- " Test resolve() with a symlink cycle.
- " Xlink1 -> Xlink2
- " Xlink2 -> Xlink3
- " Xlink3 -> Xlink1
- silent !ln -s -f Xlink1 Xlink3
- call assert_fails('call resolve("Xlink1")', 'E655:')
- call assert_fails('call resolve("./Xlink1")', 'E655:')
- call assert_fails('call resolve("Xlink2")', 'E655:')
- call assert_fails('call resolve("Xlink3")', 'E655:')
- call delete('Xlink1')
- call delete('Xlink2')
- call delete('Xlink3')
-
- silent !ln -s -f Xdir//Xfile Xlink
- call assert_equal('Xdir/Xfile', resolve('Xlink'))
- call delete('Xlink')
-
- silent !ln -s -f Xlink2/ Xlink1
- call assert_equal('Xlink2', 'Xlink1'->resolve())
- call assert_equal('Xlink2/', resolve('Xlink1/'))
- call delete('Xlink1')
-
- silent !ln -s -f ./Xlink2 Xlink1
- call assert_equal('Xlink2', resolve('Xlink1'))
- call assert_equal('./Xlink2', resolve('./Xlink1'))
- call delete('Xlink1')
-
- call assert_equal('/', resolve('/'))
-endfunc
-
-func s:normalize_fname(fname)
- let ret = substitute(a:fname, '\', '/', 'g')
- let ret = substitute(ret, '//', '/', 'g')
- return ret->tolower()
-endfunc
-
-func Test_simplify()
- call assert_equal('', simplify(''))
- call assert_equal('/', simplify('/'))
- call assert_equal('/', simplify('/.'))
- call assert_equal('/', simplify('/..'))
- call assert_equal('/...', simplify('/...'))
- call assert_equal('./dir/file', './dir/file'->simplify())
- call assert_equal('./dir/file', simplify('.///dir//file'))
- call assert_equal('./dir/file', simplify('./dir/./file'))
- call assert_equal('./file', simplify('./dir/../file'))
- call assert_equal('../dir/file', simplify('dir/../../dir/file'))
- call assert_equal('./file', simplify('dir/.././file'))
- call assert_equal('../dir', simplify('./../dir'))
- call assert_equal('..', simplify('../testdir/..'))
- call mkdir('Xdir')
- call assert_equal('.', simplify('Xdir/../.'))
- call delete('Xdir', 'd')
-
- call assert_fails('call simplify({->0})', 'E729:')
- call assert_fails('call simplify([])', 'E730:')
- call assert_fails('call simplify({})', 'E731:')
- if has('float')
- call assert_fails('call simplify(1.2)', 'E806:')
- endif
-endfunc
-
-func Test_pathshorten()
- call assert_equal('', pathshorten(''))
- call assert_equal('foo', pathshorten('foo'))
- call assert_equal('/foo', '/foo'->pathshorten())
- call assert_equal('f/', pathshorten('foo/'))
- call assert_equal('f/bar', pathshorten('foo/bar'))
- call assert_equal('f/b/foobar', 'foo/bar/foobar'->pathshorten())
- call assert_equal('/f/b/foobar', pathshorten('/foo/bar/foobar'))
- call assert_equal('.f/bar', pathshorten('.foo/bar'))
- call assert_equal('~f/bar', pathshorten('~foo/bar'))
- call assert_equal('~.f/bar', pathshorten('~.foo/bar'))
- call assert_equal('.~f/bar', pathshorten('.~foo/bar'))
- call assert_equal('~/f/bar', pathshorten('~/foo/bar'))
- call assert_fails('call pathshorten([])', 'E730:')
-
- " test pathshorten with optional variable to set preferred size of shortening
- call assert_equal('', pathshorten('', 2))
- call assert_equal('foo', pathshorten('foo', 2))
- call assert_equal('/foo', pathshorten('/foo', 2))
- call assert_equal('fo/', pathshorten('foo/', 2))
- call assert_equal('fo/bar', pathshorten('foo/bar', 2))
- call assert_equal('fo/ba/foobar', pathshorten('foo/bar/foobar', 2))
- call assert_equal('/fo/ba/foobar', pathshorten('/foo/bar/foobar', 2))
- call assert_equal('.fo/bar', pathshorten('.foo/bar', 2))
- call assert_equal('~fo/bar', pathshorten('~foo/bar', 2))
- call assert_equal('~.fo/bar', pathshorten('~.foo/bar', 2))
- call assert_equal('.~fo/bar', pathshorten('.~foo/bar', 2))
- call assert_equal('~/fo/bar', pathshorten('~/foo/bar', 2))
- call assert_fails('call pathshorten([],2)', 'E730:')
- call assert_notequal('~/fo/bar', pathshorten('~/foo/bar', 3))
- call assert_equal('~/foo/bar', pathshorten('~/foo/bar', 3))
- call assert_equal('~/f/bar', pathshorten('~/foo/bar', 0))
-endfunc
-
-func Test_strpart()
- call assert_equal('de', strpart('abcdefg', 3, 2))
- call assert_equal('ab', strpart('abcdefg', -2, 4))
- call assert_equal('abcdefg', 'abcdefg'->strpart(-2))
- call assert_equal('fg', strpart('abcdefg', 5, 4))
- call assert_equal('defg', strpart('abcdefg', 3))
- call assert_equal('', strpart('abcdefg', 10))
- call assert_fails("let s=strpart('abcdef', [])", 'E745:')
-
- call assert_equal('lép', strpart('éléphant', 2, 4))
- call assert_equal('léphant', strpart('éléphant', 2))
-
- call assert_equal('é', strpart('éléphant', 0, 1, 1))
- call assert_equal('ép', strpart('éléphant', 3, 2, v:true))
- call assert_equal('oÌ', strpart('coÌmposed', 1, 1, 1))
-endfunc
-
-func Test_tolower()
- call assert_equal("", tolower(""))
-
- " Test with all printable ASCII characters.
- call assert_equal(' !"#$%&''()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~',
- \ tolower(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'))
-
- " Test with a few uppercase diacritics.
- call assert_equal("aàáâãäåÄăąǎǟǡả", tolower("AÀÃÂÃÄÅĀĂĄÇǞǠẢ"))
- call assert_equal("bḃḇ", tolower("BḂḆ"))
- call assert_equal("cçćĉċÄ", tolower("CÇĆĈĊČ"))
- call assert_equal("dÄđḋá¸á¸‘", tolower("DÄŽÄḊḎá¸"))
- call assert_equal("eèéêëēĕėęěẻẽ", tolower("EÈÉÊËĒĔĖĘĚẺẼ"))
- call assert_equal("fḟ ", tolower("FḞ "))
- call assert_equal("gÄğġģǥǧǵḡ", tolower("GĜĞĠĢǤǦǴḠ"))
- call assert_equal("hĥħḣḧḩ", tolower("HĤĦḢḦḨ"))
- call assert_equal("iìíîïĩīĭįiÇỉ", tolower("IÃŒÃÃŽÃĨĪĬĮİÇỈ"))
- call assert_equal("jĵ", tolower("JĴ"))
- call assert_equal("kķǩḱḵ", tolower("KĶǨḰḴ"))
- call assert_equal("lĺļľŀłḻ", tolower("LĹĻĽĿÅḺ"))
- call assert_equal("mḿá¹", tolower("MḾṀ"))
- call assert_equal("nñńņňṅṉ", tolower("NÑŃŅŇṄṈ"))
- call assert_equal("oòóôõöøÅÅőơǒǫǭá»", tolower("OÃ’Ã“Ã”Ã•Ã–Ã˜ÅŒÅŽÅÆ Ç‘ǪǬỎ"))
- call assert_equal("pṕṗ", tolower("PṔṖ"))
- call assert_equal("q", tolower("Q"))
- call assert_equal("rŕŗřṙṟ", tolower("RŔŖŘṘṞ"))
- call assert_equal("sÅ›Åşšṡ", tolower("SŚŜŞŠṠ"))
- call assert_equal("tţťŧṫṯ", tolower("TŢŤŦṪṮ"))
- call assert_equal("uùúûüũūŭůűųưǔủ", tolower("UÙÚÛÜŨŪŬŮŰŲƯǓỦ"))
- call assert_equal("vá¹½", tolower("Vá¹¼"))
- call assert_equal("wŵáºáºƒáº…ẇ", tolower("WŴẀẂẄẆ"))
- call assert_equal("xẋáº", tolower("XẊẌ"))
- call assert_equal("yýŷÿáºá»³á»·á»¹", tolower("YÃŶŸẎỲỶỸ"))
- call assert_equal("zźżžƶẑẕ", tolower("ZŹŻŽƵáºáº”"))
-
- " Test with a few lowercase diacritics, which should remain unchanged.
- call assert_equal("aàáâãäåÄăąǎǟǡả", tolower("aàáâãäåÄăąǎǟǡả"))
- call assert_equal("bḃḇ", tolower("bḃḇ"))
- call assert_equal("cçćĉċÄ", tolower("cçćĉċÄ"))
- call assert_equal("dÄđḋá¸á¸‘", tolower("dÄđḋá¸á¸‘"))
- call assert_equal("eèéêëēĕėęěẻẽ", tolower("eèéêëēĕėęěẻẽ"))
- call assert_equal("fḟ", tolower("fḟ"))
- call assert_equal("gÄğġģǥǧǵḡ", tolower("gÄğġģǥǧǵḡ"))
- call assert_equal("hĥħḣḧḩẖ", tolower("hĥħḣḧḩẖ"))
- call assert_equal("iìíîïĩīĭįÇỉ", tolower("iìíîïĩīĭįÇỉ"))
- call assert_equal("jĵǰ", tolower("jĵǰ"))
- call assert_equal("kķǩḱḵ", tolower("kķǩḱḵ"))
- call assert_equal("lĺļľŀłḻ", tolower("lĺļľŀłḻ"))
- call assert_equal("mḿṠ", tolower("mḿṠ"))
- call assert_equal("nñńņňʼnṅṉ", tolower("nñńņňʼnṅṉ"))
- call assert_equal("oòóôõöøÅÅőơǒǫǭá»", tolower("oòóôõöøÅÅőơǒǫǭá»"))
- call assert_equal("pṕṗ", tolower("pṕṗ"))
- call assert_equal("q", tolower("q"))
- call assert_equal("rŕŗřṙṟ", tolower("rŕŗřṙṟ"))
- call assert_equal("sÅ›Åşšṡ", tolower("sÅ›Åşšṡ"))
- call assert_equal("tţťŧṫṯẗ", tolower("tţťŧṫṯẗ"))
- call assert_equal("uùúûüũūŭůűųưǔủ", tolower("uùúûüũūŭůűųưǔủ"))
- call assert_equal("vá¹½", tolower("vá¹½"))
- call assert_equal("wŵáºáºƒáº…ẇẘ", tolower("wŵáºáºƒáº…ẇẘ"))
- call assert_equal("ẋáº", tolower("ẋáº"))
- call assert_equal("yýÿŷáºáº™á»³á»·á»¹", tolower("yýÿŷáºáº™á»³á»·á»¹"))
- call assert_equal("zźżžƶẑẕ", tolower("zźżžƶẑẕ"))
-
- " According to https://twitter.com/jifa/status/625776454479970304
- " Ⱥ (U+023A) and Ⱦ (U+023E) are the *only* code points to increase
- " in length (2 to 3 bytes) when lowercased. So let's test them.
- call assert_equal("ⱥ ⱦ", tolower("Ⱥ Ⱦ"))
-
- " This call to tolower with invalid utf8 sequence used to cause access to
- " invalid memory.
- call tolower("\xC0\x80\xC0")
- call tolower("123\xC0\x80\xC0")
-
- " Test in latin1 encoding
- let save_enc = &encoding
- " set encoding=latin1
- call assert_equal("abc", tolower("ABC"))
- let &encoding = save_enc
-endfunc
-
-func Test_toupper()
- call assert_equal("", toupper(""))
-
- " Test with all printable ASCII characters.
- call assert_equal(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~',
- \ toupper(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'))
-
- " Test with a few lowercase diacritics.
- call assert_equal("AÀÃÂÃÄÅĀĂĄÇǞǠẢ", "aàáâãäåÄăąǎǟǡả"->toupper())
- call assert_equal("BḂḆ", toupper("bḃḇ"))
- call assert_equal("CÇĆĈĊČ", toupper("cçćĉċÄ"))
- call assert_equal("DÄŽÄḊḎá¸", toupper("dÄđḋá¸á¸‘"))
- call assert_equal("EÈÉÊËĒĔĖĘĚẺẼ", toupper("eèéêëēĕėęěẻẽ"))
- call assert_equal("FḞ", toupper("fḟ"))
- call assert_equal("GĜĞĠĢǤǦǴḠ", toupper("gÄğġģǥǧǵḡ"))
- call assert_equal("HĤĦḢḦḨẖ", toupper("hĥħḣḧḩẖ"))
- call assert_equal("IÃŒÃÃŽÃĨĪĬĮÇỈ", toupper("iìíîïĩīĭįÇỉ"))
- call assert_equal("JĴǰ", toupper("jĵǰ"))
- call assert_equal("KĶǨḰḴ", toupper("kķǩḱḵ"))
- call assert_equal("LĹĻĽĿÅḺ", toupper("lĺļľŀłḻ"))
- call assert_equal("MḾṀ ", toupper("mḿṠ"))
- call assert_equal("NÑŃŅŇʼnṄṈ", toupper("nñńņňʼnṅṉ"))
- call assert_equal("OÃ’Ã“Ã”Ã•Ã–Ã˜ÅŒÅŽÅÆ Ç‘ǪǬỎ", toupper("oòóôõöøÅÅőơǒǫǭá»"))
- call assert_equal("PṔṖ", toupper("pṕṗ"))
- call assert_equal("Q", toupper("q"))
- call assert_equal("RŔŖŘṘṞ", toupper("rŕŗřṙṟ"))
- call assert_equal("SŚŜŞŠṠ", toupper("sÅ›Åşšṡ"))
- call assert_equal("TŢŤŦṪṮẗ", toupper("tţťŧṫṯẗ"))
- call assert_equal("UÙÚÛÜŨŪŬŮŰŲƯǓỦ", toupper("uùúûüũūŭůűųưǔủ"))
- call assert_equal("Vá¹¼", toupper("vá¹½"))
- call assert_equal("WŴẀẂẄẆẘ", toupper("wŵáºáºƒáº…ẇẘ"))
- call assert_equal("ẊẌ", toupper("ẋáº"))
- call assert_equal("YßŶẎẙỲỶỸ", toupper("yýÿŷáºáº™á»³á»·á»¹"))
- call assert_equal("ZŹŻŽƵáºáº”", toupper("zźżžƶẑẕ"))
-
- " Test that uppercase diacritics, which should remain unchanged.
- call assert_equal("AÀÃÂÃÄÅĀĂĄÇǞǠẢ", toupper("AÀÃÂÃÄÅĀĂĄÇǞǠẢ"))
- call assert_equal("BḂḆ", toupper("BḂḆ"))
- call assert_equal("CÇĆĈĊČ", toupper("CÇĆĈĊČ"))
- call assert_equal("DÄŽÄḊḎá¸", toupper("DÄŽÄḊḎá¸"))
- call assert_equal("EÈÉÊËĒĔĖĘĚẺẼ", toupper("EÈÉÊËĒĔĖĘĚẺẼ"))
- call assert_equal("FḞ ", toupper("FḞ "))
- call assert_equal("GĜĞĠĢǤǦǴḠ", toupper("GĜĞĠĢǤǦǴḠ"))
- call assert_equal("HĤĦḢḦḨ", toupper("HĤĦḢḦḨ"))
- call assert_equal("IÃŒÃÃŽÃĨĪĬĮİÇỈ", toupper("IÃŒÃÃŽÃĨĪĬĮİÇỈ"))
- call assert_equal("JÄ´", toupper("JÄ´"))
- call assert_equal("KĶǨḰḴ", toupper("KĶǨḰḴ"))
- call assert_equal("LĹĻĽĿÅḺ", toupper("LĹĻĽĿÅḺ"))
- call assert_equal("MḾṀ", toupper("MḾṀ"))
- call assert_equal("NÑŃŅŇṄṈ", toupper("NÑŃŅŇṄṈ"))
- call assert_equal("OÃ’Ã“Ã”Ã•Ã–Ã˜ÅŒÅŽÅÆ Ç‘ǪǬỎ", toupper("OÃ’Ã“Ã”Ã•Ã–Ã˜ÅŒÅŽÅÆ Ç‘ǪǬỎ"))
- call assert_equal("PṔṖ", toupper("PṔṖ"))
- call assert_equal("Q", toupper("Q"))
- call assert_equal("RŔŖŘṘṞ", toupper("RŔŖŘṘṞ"))
- call assert_equal("SŚŜŞŠṠ", toupper("SŚŜŞŠṠ"))
- call assert_equal("TŢŤŦṪṮ", toupper("TŢŤŦṪṮ"))
- call assert_equal("UÙÚÛÜŨŪŬŮŰŲƯǓỦ", toupper("UÙÚÛÜŨŪŬŮŰŲƯǓỦ"))
- call assert_equal("Vá¹¼", toupper("Vá¹¼"))
- call assert_equal("WŴẀẂẄẆ", toupper("WŴẀẂẄẆ"))
- call assert_equal("XẊẌ", toupper("XẊẌ"))
- call assert_equal("YÃŶŸẎỲỶỸ", toupper("YÃŶŸẎỲỶỸ"))
- call assert_equal("ZŹŻŽƵáºáº”", toupper("ZŹŻŽƵáºáº”"))
-
- call assert_equal("Ⱥ Ⱦ", toupper("ⱥ ⱦ"))
-
- " This call to toupper with invalid utf8 sequence used to cause access to
- " invalid memory.
- call toupper("\xC0\x80\xC0")
- call toupper("123\xC0\x80\xC0")
-
- " Test in latin1 encoding
- let save_enc = &encoding
- " set encoding=latin1
- call assert_equal("ABC", toupper("abc"))
- let &encoding = save_enc
-endfunc
-
-func Test_tr()
- call assert_equal('foo', tr('bar', 'bar', 'foo'))
- call assert_equal('zxy', 'cab'->tr('abc', 'xyz'))
- call assert_fails("let s=tr([], 'abc', 'def')", 'E730:')
- call assert_fails("let s=tr('abc', [], 'def')", 'E730:')
- call assert_fails("let s=tr('abc', 'abc', [])", 'E730:')
- call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:')
- " set encoding=latin1
- call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:')
- call assert_equal('hEllO', tr('hello', 'eo', 'EO'))
- call assert_equal('hello', tr('hello', 'xy', 'ab'))
- call assert_fails('call tr("abc", "123", "â‚â‚‚")', 'E475:')
- set encoding=utf8
-endfunc
-
-" Tests for the mode() function
-let current_modes = ''
-func Save_mode()
- let g:current_modes = mode(0) . '-' . mode(1)
- return ''
-endfunc
-
-" Test for the mode() function
-func Test_mode()
- new
- call append(0, ["Blue Ball Black", "Brown Band Bowl", ""])
-
- " Only complete from the current buffer.
- set complete=.
-
- inoremap <F2> <C-R>=Save_mode()<CR>
- xnoremap <F2> <Cmd>call Save_mode()<CR>
-
- normal! 3G
- exe "normal i\<F2>\<Esc>"
- call assert_equal('i-i', g:current_modes)
- " i_CTRL-P: Multiple matches
- exe "normal i\<C-G>uBa\<C-P>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-P: Single match
- exe "normal iBro\<C-P>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-X
- exe "normal iBa\<C-X>\<F2>\<Esc>u"
- call assert_equal('i-ix', g:current_modes)
- " i_CTRL-X CTRL-P: Multiple matches
- exe "normal iBa\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-X CTRL-P: Single match
- exe "normal iBro\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-X CTRL-P + CTRL-P: Single match
- exe "normal iBro\<C-X>\<C-P>\<C-P>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-X CTRL-L: Multiple matches
- exe "normal i\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-X CTRL-L: Single match
- exe "normal iBlu\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-P: No match
- exe "normal iCom\<C-P>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-X CTRL-P: No match
- exe "normal iCom\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
- " i_CTRL-X CTRL-L: No match
- exe "normal iabc\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('i-ic', g:current_modes)
-
- exe "normal R\<F2>\<Esc>"
- call assert_equal('R-R', g:current_modes)
- " R_CTRL-P: Multiple matches
- exe "normal RBa\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-P: Single match
- exe "normal RBro\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-X
- exe "normal RBa\<C-X>\<F2>\<Esc>u"
- call assert_equal('R-Rx', g:current_modes)
- " R_CTRL-X CTRL-P: Multiple matches
- exe "normal RBa\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-X CTRL-P: Single match
- exe "normal RBro\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-X CTRL-P + CTRL-P: Single match
- exe "normal RBro\<C-X>\<C-P>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-X CTRL-L: Multiple matches
- exe "normal R\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-X CTRL-L: Single match
- exe "normal RBlu\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-P: No match
- exe "normal RCom\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-X CTRL-P: No match
- exe "normal RCom\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
- " R_CTRL-X CTRL-L: No match
- exe "normal Rabc\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('R-Rc', g:current_modes)
-
- exe "normal gR\<F2>\<Esc>"
- call assert_equal('R-Rv', g:current_modes)
- " gR_CTRL-P: Multiple matches
- exe "normal gRBa\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-P: Single match
- exe "normal gRBro\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-X
- exe "normal gRBa\<C-X>\<F2>\<Esc>u"
- call assert_equal('R-Rvx', g:current_modes)
- " gR_CTRL-X CTRL-P: Multiple matches
- exe "normal gRBa\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-X CTRL-P: Single match
- exe "normal gRBro\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-X CTRL-P + CTRL-P: Single match
- exe "normal gRBro\<C-X>\<C-P>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-X CTRL-L: Multiple matches
- exe "normal gR\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-X CTRL-L: Single match
- exe "normal gRBlu\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-P: No match
- exe "normal gRCom\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-X CTRL-P: No match
- exe "normal gRCom\<C-X>\<C-P>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
- " gR_CTRL-X CTRL-L: No match
- exe "normal gRabc\<C-X>\<C-L>\<F2>\<Esc>u"
- call assert_equal('R-Rvc', g:current_modes)
-
- call assert_equal('n', 0->mode())
- call assert_equal('n', 1->mode())
-
- " i_CTRL-O
- exe "normal i\<C-O>:call Save_mode()\<Cr>\<Esc>"
- call assert_equal("n-niI", g:current_modes)
-
- " R_CTRL-O
- exe "normal R\<C-O>:call Save_mode()\<Cr>\<Esc>"
- call assert_equal("n-niR", g:current_modes)
-
- " gR_CTRL-O
- exe "normal gR\<C-O>:call Save_mode()\<Cr>\<Esc>"
- call assert_equal("n-niV", g:current_modes)
-
- " How to test operator-pending mode?
-
- call feedkeys("v", 'xt')
- call assert_equal('v', mode())
- call assert_equal('v', mode(1))
- call feedkeys("\<Esc>V", 'xt')
- call assert_equal('V', mode())
- call assert_equal('V', mode(1))
- call feedkeys("\<Esc>\<C-V>", 'xt')
- call assert_equal("\<C-V>", mode())
- call assert_equal("\<C-V>", mode(1))
- call feedkeys("\<Esc>", 'xt')
-
- call feedkeys("gh", 'xt')
- call assert_equal('s', mode())
- call assert_equal('s', mode(1))
- call feedkeys("\<Esc>gH", 'xt')
- call assert_equal('S', mode())
- call assert_equal('S', mode(1))
- call feedkeys("\<Esc>g\<C-H>", 'xt')
- call assert_equal("\<C-S>", mode())
- call assert_equal("\<C-S>", mode(1))
- call feedkeys("\<Esc>", 'xt')
-
- " v_CTRL-O
- exe "normal gh\<C-O>\<F2>\<Esc>"
- call assert_equal("v-vs", g:current_modes)
- exe "normal gH\<C-O>\<F2>\<Esc>"
- call assert_equal("V-Vs", g:current_modes)
- exe "normal g\<C-H>\<C-O>\<F2>\<Esc>"
- call assert_equal("\<C-V>-\<C-V>s", g:current_modes)
-
- call feedkeys(":echo \<C-R>=Save_mode()\<C-U>\<CR>", 'xt')
- call assert_equal('c-c', g:current_modes)
- call feedkeys("gQecho \<C-R>=Save_mode()\<CR>\<CR>vi\<CR>", 'xt')
- call assert_equal('c-cv', g:current_modes)
- " call feedkeys("Qcall Save_mode()\<CR>vi\<CR>", 'xt')
- " call assert_equal('c-ce', g:current_modes)
- " How to test Ex mode?
-
- " Test mode in operatorfunc (it used to be Operator-pending).
- set operatorfunc=OperatorFunc
- function OperatorFunc(_)
- call Save_mode()
- endfunction
- execute "normal! g@l\<Esc>"
- call assert_equal('n-n', g:current_modes)
- execute "normal! i\<C-o>g@l\<Esc>"
- call assert_equal('n-niI', g:current_modes)
- execute "normal! R\<C-o>g@l\<Esc>"
- call assert_equal('n-niR', g:current_modes)
- execute "normal! gR\<C-o>g@l\<Esc>"
- call assert_equal('n-niV', g:current_modes)
-
- if has('terminal')
- term
- call feedkeys("\<C-W>N", 'xt')
- call assert_equal('n', mode())
- call assert_equal('nt', mode(1))
- call feedkeys("aexit\<CR>", 'xt')
- endif
-
- bwipe!
- iunmap <F2>
- xunmap <F2>
- set complete&
- set operatorfunc&
- delfunction OperatorFunc
-endfunc
-
-" Test for append()
-func Test_append()
- enew!
- split
- call append(0, ["foo"])
- call append(1, [])
- call append(1, v:_null_list)
- call assert_equal(['foo', ''], getline(1, '$'))
- split
- only
- undo
- undo
-
- " Using $ instead of '$' must give an error
- call assert_fails("call append($, 'foobar')", 'E116:')
-
- call assert_fails("call append({}, '')", ['E728:', 'E728:'])
-endfunc
-
-" Test for setline()
-func Test_setline()
- new
- call setline(0, ["foo"])
- call setline(0, [])
- call setline(0, v:_null_list)
- call setline(1, ["bar"])
- call setline(1, [])
- call setline(1, v:_null_list)
- call setline(2, [])
- call setline(2, v:_null_list)
- call setline(3, [])
- call setline(3, v:_null_list)
- call setline(2, ["baz"])
- call assert_equal(['bar', 'baz'], getline(1, '$'))
- close!
-endfunc
-
-func Test_getbufvar()
- let bnr = bufnr('%')
- let b:var_num = '1234'
- let def_num = '5678'
- call assert_equal('1234', getbufvar(bnr, 'var_num'))
- call assert_equal('1234', getbufvar(bnr, 'var_num', def_num))
-
- let bd = getbufvar(bnr, '')
- call assert_equal('1234', bd['var_num'])
- call assert_true(exists("bd['changedtick']"))
- call assert_equal(2, len(bd))
-
- let bd2 = getbufvar(bnr, '', def_num)
- call assert_equal(bd, bd2)
-
- unlet b:var_num
- call assert_equal(def_num, getbufvar(bnr, 'var_num', def_num))
- call assert_equal('', getbufvar(bnr, 'var_num'))
-
- let bd = getbufvar(bnr, '')
- call assert_equal(1, len(bd))
- let bd = getbufvar(bnr, '',def_num)
- call assert_equal(1, len(bd))
-
- call assert_equal('', getbufvar(9999, ''))
- call assert_equal(def_num, getbufvar(9999, '', def_num))
- unlet def_num
-
- call assert_equal(0, getbufvar(bnr, '&autoindent'))
- call assert_equal(0, getbufvar(bnr, '&autoindent', 1))
-
- " Set and get a buffer-local variable
- call setbufvar(bnr, 'bufvar_test', ['one', 'two'])
- call assert_equal(['one', 'two'], getbufvar(bnr, 'bufvar_test'))
-
- " Open new window with forced option values
- set fileformats=unix,dos
- new ++ff=dos ++bin ++enc=iso-8859-2
- call assert_equal('dos', getbufvar(bufnr('%'), '&fileformat'))
- call assert_equal(1, getbufvar(bufnr('%'), '&bin'))
- 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
-
-func Test_last_buffer_nr()
- call assert_equal(bufnr('$'), last_buffer_nr())
-endfunc
-
-func Test_stridx()
- call assert_equal(-1, stridx('', 'l'))
- call assert_equal(0, stridx('', ''))
- call assert_equal(0, 'hello'->stridx(''))
- call assert_equal(-1, stridx('hello', 'L'))
- call assert_equal(2, stridx('hello', 'l', -1))
- call assert_equal(2, stridx('hello', 'l', 0))
- call assert_equal(2, 'hello'->stridx('l', 1))
- call assert_equal(3, stridx('hello', 'l', 3))
- call assert_equal(-1, stridx('hello', 'l', 4))
- call assert_equal(-1, stridx('hello', 'l', 10))
- call assert_equal(2, stridx('hello', 'll'))
- call assert_equal(-1, stridx('hello', 'hello world'))
- call assert_fails("let n=stridx('hello', [])", 'E730:')
- call assert_fails("let n=stridx([], 'l')", 'E730:')
-endfunc
-
-func Test_strridx()
- call assert_equal(-1, strridx('', 'l'))
- call assert_equal(0, strridx('', ''))
- call assert_equal(5, strridx('hello', ''))
- call assert_equal(-1, strridx('hello', 'L'))
- call assert_equal(3, 'hello'->strridx('l'))
- call assert_equal(3, strridx('hello', 'l', 10))
- call assert_equal(3, strridx('hello', 'l', 3))
- call assert_equal(2, strridx('hello', 'l', 2))
- call assert_equal(-1, strridx('hello', 'l', 1))
- call assert_equal(-1, strridx('hello', 'l', 0))
- call assert_equal(-1, strridx('hello', 'l', -1))
- call assert_equal(2, strridx('hello', 'll'))
- call assert_equal(-1, strridx('hello', 'hello world'))
- call assert_fails("let n=strridx('hello', [])", 'E730:')
- call assert_fails("let n=strridx([], 'l')", 'E730:')
-endfunc
-
-func Test_match_func()
- call assert_equal(4, match('testing', 'ing'))
- call assert_equal(4, 'testing'->match('ing', 2))
- call assert_equal(-1, match('testing', 'ing', 5))
- call assert_equal(-1, match('testing', 'ing', 8))
- call assert_equal(1, match(['vim', 'testing', 'execute'], 'ing'))
- call assert_equal(-1, match(['vim', 'testing', 'execute'], 'img'))
- call assert_fails("let x=match('vim', [])", 'E730:')
- call assert_equal(3, match(['a', 'b', 'c', 'a'], 'a', 1))
- call assert_equal(-1, match(['a', 'b', 'c', 'a'], 'a', 5))
- call assert_equal(4, match('testing', 'ing', -1))
- call assert_fails("let x=match('testing', 'ing', 0, [])", 'E745:')
- call assert_equal(-1, match(v:_null_list, 2))
- call assert_equal(-1, match('abc', '\\%('))
-endfunc
-
-func Test_matchend()
- call assert_equal(7, matchend('testing', 'ing'))
- call assert_equal(7, 'testing'->matchend('ing', 2))
- call assert_equal(-1, matchend('testing', 'ing', 5))
- call assert_equal(-1, matchend('testing', 'ing', 8))
- call assert_equal(match(['vim', 'testing', 'execute'], 'ing'), matchend(['vim', 'testing', 'execute'], 'ing'))
- call assert_equal(match(['vim', 'testing', 'execute'], 'img'), matchend(['vim', 'testing', 'execute'], 'img'))
-endfunc
-
-func Test_matchlist()
- call assert_equal(['acd', 'a', '', 'c', 'd', '', '', '', '', ''], matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)'))
- call assert_equal(['d', '', '', '', 'd', '', '', '', '', ''], 'acd'->matchlist('\(a\)\?\(b\)\?\(c\)\?\(.*\)', 2))
- call assert_equal([], matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)', 4))
-endfunc
-
-func Test_matchstr()
- call assert_equal('ing', matchstr('testing', 'ing'))
- call assert_equal('ing', 'testing'->matchstr('ing', 2))
- call assert_equal('', matchstr('testing', 'ing', 5))
- call assert_equal('', matchstr('testing', 'ing', 8))
- call assert_equal('testing', matchstr(['vim', 'testing', 'execute'], 'ing'))
- call assert_equal('', matchstr(['vim', 'testing', 'execute'], 'img'))
-endfunc
-
-func Test_matchstrpos()
- call assert_equal(['ing', 4, 7], matchstrpos('testing', 'ing'))
- call assert_equal(['ing', 4, 7], 'testing'->matchstrpos('ing', 2))
- call assert_equal(['', -1, -1], matchstrpos('testing', 'ing', 5))
- call assert_equal(['', -1, -1], matchstrpos('testing', 'ing', 8))
- call assert_equal(['ing', 1, 4, 7], matchstrpos(['vim', 'testing', 'execute'], 'ing'))
- call assert_equal(['', -1, -1, -1], matchstrpos(['vim', 'testing', 'execute'], 'img'))
-endfunc
-
-func Test_nextnonblank_prevnonblank()
- new
-insert
-This
-
-
-is
-
-a
-Test
-.
- call assert_equal(0, nextnonblank(-1))
- call assert_equal(0, nextnonblank(0))
- call assert_equal(1, nextnonblank(1))
- call assert_equal(4, 2->nextnonblank())
- call assert_equal(4, nextnonblank(3))
- call assert_equal(4, nextnonblank(4))
- call assert_equal(6, nextnonblank(5))
- call assert_equal(6, nextnonblank(6))
- call assert_equal(7, nextnonblank(7))
- call assert_equal(0, 8->nextnonblank())
-
- call assert_equal(0, prevnonblank(-1))
- call assert_equal(0, prevnonblank(0))
- call assert_equal(1, 1->prevnonblank())
- call assert_equal(1, prevnonblank(2))
- call assert_equal(1, prevnonblank(3))
- call assert_equal(4, prevnonblank(4))
- call assert_equal(4, 5->prevnonblank())
- call assert_equal(6, prevnonblank(6))
- call assert_equal(7, prevnonblank(7))
- call assert_equal(0, prevnonblank(8))
- bw!
-endfunc
-
-func Test_byte2line_line2byte()
- new
- set endofline
- call setline(1, ['a', 'bc', 'd'])
-
- set fileformat=unix
- call assert_equal([-1, -1, 1, 1, 2, 2, 2, 3, 3, -1],
- \ map(range(-1, 8), 'byte2line(v:val)'))
- call assert_equal([-1, -1, 1, 3, 6, 8, -1],
- \ map(range(-1, 5), 'line2byte(v:val)'))
-
- set fileformat=mac
- call assert_equal([-1, -1, 1, 1, 2, 2, 2, 3, 3, -1],
- \ map(range(-1, 8), 'v:val->byte2line()'))
- call assert_equal([-1, -1, 1, 3, 6, 8, -1],
- \ map(range(-1, 5), 'v:val->line2byte()'))
-
- set fileformat=dos
- call assert_equal([-1, -1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, -1],
- \ map(range(-1, 11), 'byte2line(v:val)'))
- call assert_equal([-1, -1, 1, 4, 8, 11, -1],
- \ map(range(-1, 5), 'line2byte(v:val)'))
-
- bw!
- set noendofline nofixendofline
- normal a-
- for ff in ["unix", "mac", "dos"]
- let &fileformat = ff
- call assert_equal(1, line2byte(1))
- call assert_equal(2, line2byte(2)) " line2byte(line("$") + 1) is the buffer size plus one (as per :help line2byte).
- endfor
-
- set endofline& fixendofline& fileformat&
- bw!
-endfunc
-
-" Test for byteidx() and byteidxcomp() functions
-func Test_byteidx()
- let a = '.é.' " one char of two bytes
- call assert_equal(0, byteidx(a, 0))
- call assert_equal(0, byteidxcomp(a, 0))
- call assert_equal(1, byteidx(a, 1))
- call assert_equal(1, byteidxcomp(a, 1))
- call assert_equal(3, byteidx(a, 2))
- call assert_equal(3, byteidxcomp(a, 2))
- call assert_equal(4, byteidx(a, 3))
- call assert_equal(4, byteidxcomp(a, 3))
- call assert_equal(-1, byteidx(a, 4))
- call assert_equal(-1, byteidxcomp(a, 4))
-
- let b = '.eÌ.' " normal e with composing char
- call assert_equal(0, b->byteidx(0))
- call assert_equal(1, b->byteidx(1))
- call assert_equal(4, b->byteidx(2))
- call assert_equal(5, b->byteidx(3))
- call assert_equal(-1, b->byteidx(4))
- call assert_fails("call byteidx([], 0)", 'E730:')
-
- call assert_equal(0, b->byteidxcomp(0))
- call assert_equal(1, b->byteidxcomp(1))
- call assert_equal(2, b->byteidxcomp(2))
- call assert_equal(4, b->byteidxcomp(3))
- call assert_equal(5, b->byteidxcomp(4))
- call assert_equal(-1, b->byteidxcomp(5))
- call assert_fails("call byteidxcomp([], 0)", 'E730:')
-endfunc
-
-" Test for charidx()
-func Test_charidx()
- let a = 'xaÌbÌy'
- call assert_equal(0, charidx(a, 0))
- call assert_equal(1, charidx(a, 3))
- call assert_equal(2, charidx(a, 4))
- call assert_equal(3, charidx(a, 7))
- call assert_equal(-1, charidx(a, 8))
- call assert_equal(-1, charidx(a, -1))
- call assert_equal(-1, charidx('', 0))
- call assert_equal(-1, charidx(v:_null_string, 0))
-
- " count composing characters
- call assert_equal(0, charidx(a, 0, 1))
- call assert_equal(2, charidx(a, 2, 1))
- call assert_equal(3, charidx(a, 4, 1))
- call assert_equal(5, charidx(a, 7, 1))
- call assert_equal(-1, charidx(a, 8, 1))
- call assert_equal(-1, charidx('', 0, 1))
-
- call assert_fails('let x = charidx([], 1)', 'E474:')
- call assert_fails('let x = charidx("abc", [])', 'E474:')
- call assert_fails('let x = charidx("abc", 1, [])', 'E474:')
- call assert_fails('let x = charidx("abc", 1, -1)', 'E1023:')
- call assert_fails('let x = charidx("abc", 1, 2)', 'E1023:')
-endfunc
-
-func Test_count()
- let l = ['a', 'a', 'A', 'b']
- call assert_equal(2, count(l, 'a'))
- call assert_equal(1, count(l, 'A'))
- call assert_equal(1, count(l, 'b'))
- call assert_equal(0, count(l, 'B'))
-
- call assert_equal(2, count(l, 'a', 0))
- call assert_equal(1, count(l, 'A', 0))
- call assert_equal(1, count(l, 'b', 0))
- call assert_equal(0, count(l, 'B', 0))
-
- call assert_equal(3, count(l, 'a', 1))
- call assert_equal(3, count(l, 'A', 1))
- call assert_equal(1, count(l, 'b', 1))
- call assert_equal(1, count(l, 'B', 1))
- call assert_equal(0, count(l, 'c', 1))
-
- call assert_equal(1, count(l, 'a', 0, 1))
- call assert_equal(2, count(l, 'a', 1, 1))
- call assert_fails('call count(l, "a", 0, 10)', 'E684:')
- call assert_fails('call count(l, "a", [])', 'E745:')
-
- let d = {1: 'a', 2: 'a', 3: 'A', 4: 'b'}
- call assert_equal(2, count(d, 'a'))
- call assert_equal(1, count(d, 'A'))
- call assert_equal(1, count(d, 'b'))
- call assert_equal(0, count(d, 'B'))
-
- call assert_equal(2, count(d, 'a', 0))
- call assert_equal(1, count(d, 'A', 0))
- call assert_equal(1, count(d, 'b', 0))
- call assert_equal(0, count(d, 'B', 0))
-
- call assert_equal(3, count(d, 'a', 1))
- call assert_equal(3, count(d, 'A', 1))
- call assert_equal(1, count(d, 'b', 1))
- call assert_equal(1, count(d, 'B', 1))
- call assert_equal(0, count(d, 'c', 1))
-
- call assert_fails('call count(d, "a", 0, 1)', 'E474:')
-
- call assert_equal(0, count("foo", "bar"))
- call assert_equal(1, count("foo", "oo"))
- call assert_equal(2, count("foo", "o"))
- call assert_equal(0, count("foo", "O"))
- call assert_equal(2, count("foo", "O", 1))
- call assert_equal(2, count("fooooo", "oo"))
- call assert_equal(0, count("foo", ""))
-
- call assert_fails('call count(0, 0)', 'E712:')
-endfunc
-
-func Test_changenr()
- new Xchangenr
- call assert_equal(0, changenr())
- norm ifoo
- call assert_equal(1, changenr())
- set undolevels=10
- norm Sbar
- call assert_equal(2, changenr())
- undo
- call assert_equal(1, changenr())
- redo
- call assert_equal(2, changenr())
- bw!
- set undolevels&
-endfunc
-
-func Test_filewritable()
- new Xfilewritable
- write!
- call assert_equal(1, filewritable('Xfilewritable'))
-
- call assert_notequal(0, setfperm('Xfilewritable', 'r--r-----'))
- call assert_equal(0, filewritable('Xfilewritable'))
-
- call assert_notequal(0, setfperm('Xfilewritable', 'rw-r-----'))
- call assert_equal(1, 'Xfilewritable'->filewritable())
-
- call assert_equal(0, filewritable('doesnotexist'))
-
- call mkdir('Xdir')
- call assert_equal(2, filewritable('Xdir'))
- call delete('Xdir', 'd')
-
- call delete('Xfilewritable')
- bw!
-endfunc
-
-func Test_Executable()
- if has('win32')
- call assert_equal(1, executable('notepad'))
- call assert_equal(1, 'notepad.exe'->executable())
- call assert_equal(0, executable('notepad.exe.exe'))
- call assert_equal(0, executable('shell32.dll'))
- call assert_equal(0, executable('win.ini'))
- elseif has('unix')
- call assert_equal(1, 'cat'->executable())
- call assert_equal(0, executable('nodogshere'))
-
- " get "cat" path and remove the leading /
- let catcmd = exepath('cat')[1:]
- new
- " check that the relative path works in /
- lcd /
- call assert_equal(1, executable(catcmd))
- let result = catcmd->exepath()
- " when using chroot looking for sbin/cat can return bin/cat, that is OK
- if catcmd =~ '\<sbin\>' && result =~ '\<bin\>'
- call assert_equal('/' .. substitute(catcmd, '\<sbin\>', 'bin', ''), result)
- else
- " /bin/cat and /usr/bin/cat may be hard linked, we could get either
- let result = substitute(result, '/usr/bin/cat', '/bin/cat', '')
- let catcmd = substitute(catcmd, 'usr/bin/cat', 'bin/cat', '')
- call assert_equal('/' .. catcmd, result)
- endif
- bwipe
- else
- throw 'Skipped: does not work on this platform'
- endif
-endfunc
-
-func Test_executable_longname()
- if !has('win32')
- return
- endif
-
- let fname = 'X' . repeat('ã‚', 200) . '.bat'
- call writefile([], fname)
- call assert_equal(1, executable(fname))
- call delete(fname)
-endfunc
-
-func Test_hostname()
- let hostname_vim = hostname()
- if has('unix')
- let hostname_system = systemlist('uname -n')[0]
- call assert_equal(hostname_vim, hostname_system)
- endif
-endfunc
-
-func Test_getpid()
- " getpid() always returns the same value within a vim instance.
- call assert_equal(getpid(), getpid())
- if has('unix')
- call assert_equal(systemlist('echo $PPID')[0], string(getpid()))
- endif
-endfunc
-
-func Test_hlexists()
- call assert_equal(0, hlexists('does_not_exist'))
- " call assert_equal(0, 'Number'->hlexists())
- call assert_equal(0, highlight_exists('does_not_exist'))
- " call assert_equal(0, highlight_exists('Number'))
- syntax on
- call assert_equal(0, hlexists('does_not_exist'))
- " call assert_equal(1, hlexists('Number'))
- call assert_equal(0, highlight_exists('does_not_exist'))
- " call assert_equal(1, highlight_exists('Number'))
- syntax off
-endfunc
-
-func Test_col()
- new
- call setline(1, 'abcdef')
- norm gg4|mx6|mY2|
- call assert_equal(2, col('.'))
- call assert_equal(7, col('$'))
- call assert_equal(2, col('v'))
- call assert_equal(4, col("'x"))
- call assert_equal(6, col("'Y"))
- call assert_equal(2, [1, 2]->col())
- call assert_equal(7, col([1, '$']))
-
- call assert_equal(0, col(''))
- call assert_equal(0, col('x'))
- call assert_equal(0, col([2, '$']))
- call assert_equal(0, col([1, 100]))
- call assert_equal(0, col([1]))
- call assert_equal(0, col(v:_null_list))
- call assert_fails('let c = col({})', 'E1222:')
- call assert_fails('let c = col(".", [])', 'E1210:')
-
- " test for getting the visual start column
- func T()
- let g:Vcol = col('v')
- return ''
- endfunc
- let g:Vcol = 0
- xmap <expr> <F2> T()
- exe "normal gg3|ve\<F2>"
- call assert_equal(3, g:Vcol)
- xunmap <F2>
- delfunc T
-
- " Test for the visual line start and end marks '< and '>
- call setline(1, ['one', 'one two', 'one two three'])
- "normal! ggVG
- call feedkeys("ggVG\<Esc>", 'xt')
- call assert_equal(1, col("'<"))
- call assert_equal(14, col("'>"))
- " Delete the last line of the visually selected region
- $d
- call assert_notequal(14, col("'>"))
-
- " Test with 'virtualedit'
- set virtualedit=all
- call cursor(1, 10)
- call assert_equal(4, col('.'))
- set virtualedit&
-
- " Test for getting the column number in another window
- let winid = win_getid()
- new
- call win_execute(winid, 'normal 1G$')
- call assert_equal(3, col('.', winid))
- call win_execute(winid, 'normal 2G')
- call assert_equal(8, col('$', winid))
- call assert_equal(0, col('.', 5001))
-
- bw!
-endfunc
-
-" Test for input()
-func Test_input_func()
- " Test for prompt with multiple lines
- redir => v
- call feedkeys(":let c = input(\"A\\nB\\nC\\n? \")\<CR>B\<CR>", 'xt')
- redir END
- call assert_equal("B", c)
- call assert_equal(['A', 'B', 'C'], split(v, "\n"))
-
- " Test for default value
- call feedkeys(":let c = input('color? ', 'red')\<CR>\<CR>", 'xt')
- call assert_equal('red', c)
-
- " Test for completion at the input prompt
- func! Tcomplete(arglead, cmdline, pos)
- return "item1\nitem2\nitem3"
- endfunc
- call feedkeys(":let c = input('Q? ', '', 'custom,Tcomplete')\<CR>"
- \ .. "\<C-A>\<CR>", 'xt')
- delfunc Tcomplete
- call assert_equal('item1 item2 item3', c)
-
- " Test for using special characters as default input
- call feedkeys(":let c = input('name? ', \"x\\<BS>y\")\<CR>\<CR>", 'xt')
- call assert_equal('y', c)
-
- " Test for using text with composing characters as default input
- call feedkeys(":let c = input('name? ', \"ã̳\")\<CR>\<CR>", 'xt')
- call assert_equal('ã̳', c)
-
- " Test for using <CR> as default input
- call feedkeys(":let c = input('name? ', \"\\<CR>\")\<CR>x\<CR>", 'xt')
- call assert_equal(' x', c)
-
- call assert_fails("call input('F:', '', 'invalid')", 'E180:')
- call assert_fails("call input('F:', '', [])", 'E730:')
-endfunc
-
-" Test for the inputdialog() function
-func Test_inputdialog()
- if has('gui_running')
- call assert_fails('let v=inputdialog([], "xx")', 'E730:')
- call assert_fails('let v=inputdialog("Q", [])', 'E730:')
- else
- call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt')
- call assert_equal('xx', v)
- call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt')
- call assert_equal('yy', v)
- endif
-endfunc
-
-" Test for inputlist()
-func Test_inputlist()
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>1\<cr>", 'tx')
- call assert_equal(1, c)
- call feedkeys(":let c = ['Select color:', '1. red', '2. green', '3. blue']->inputlist()\<cr>2\<cr>", 'tx')
- call assert_equal(2, c)
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>3\<cr>", 'tx')
- call assert_equal(3, c)
-
- " CR to cancel
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<cr>", 'tx')
- call assert_equal(0, c)
-
- " Esc to cancel
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<Esc>", 'tx')
- call assert_equal(0, c)
-
- " q to cancel
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>q", 'tx')
- call assert_equal(0, c)
-
- " Cancel after inputting a number
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>5q", 'tx')
- call assert_equal(0, c)
-
- " Use backspace to delete characters in the prompt
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>1\<BS>3\<BS>2\<cr>", 'tx')
- call assert_equal(2, c)
-
- " Use mouse to make a selection
- call Ntest_setmouse(&lines - 3, 2)
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<LeftMouse>", 'tx')
- call assert_equal(1, c)
- " Mouse click outside of the list
- call Ntest_setmouse(&lines - 6, 2)
- call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<LeftMouse>", 'tx')
- call assert_equal(-2, c)
-
- call assert_fails('call inputlist("")', 'E686:')
-endfunc
-
-func Test_range_inputlist()
- " flush out any garbage left in the buffer
- while getchar(0)
- endwhile
-
- call feedkeys(":let result = inputlist(range(10))\<CR>1\<CR>", 'x')
- call assert_equal(1, result)
- call feedkeys(":let result = inputlist(range(3, 10))\<CR>1\<CR>", 'x')
- call assert_equal(1, result)
-
- unlet result
-endfunc
-
-func Test_balloon_show()
- CheckFeature balloon_eval
-
- " This won't do anything but must not crash either.
- call balloon_show('hi!')
- if !has('gui_running')
- call balloon_show(range(3))
- call balloon_show([])
- endif
-endfunc
-
-func Test_shellescape()
- let save_shell = &shell
- set shell=bash
- call assert_equal("'text'", shellescape('text'))
- call assert_equal("'te\"xt'", 'te"xt'->shellescape())
- call assert_equal("'te'\\''xt'", shellescape("te'xt"))
-
- call assert_equal("'te%xt'", shellescape("te%xt"))
- call assert_equal("'te\\%xt'", shellescape("te%xt", 1))
- call assert_equal("'te#xt'", shellescape("te#xt"))
- call assert_equal("'te\\#xt'", shellescape("te#xt", 1))
- call assert_equal("'te!xt'", shellescape("te!xt"))
- call assert_equal("'te\\!xt'", shellescape("te!xt", 1))
-
- call assert_equal("'te\nxt'", shellescape("te\nxt"))
- call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1))
- set shell=tcsh
- call assert_equal("'te\\!xt'", shellescape("te!xt"))
- call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1))
- call assert_equal("'te\\\nxt'", shellescape("te\nxt"))
- call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1))
-
- set shell=fish
- call assert_equal("'text'", shellescape('text'))
- call assert_equal("'te\"xt'", shellescape('te"xt'))
- call assert_equal("'te'\\''xt'", shellescape("te'xt"))
-
- call assert_equal("'te%xt'", shellescape("te%xt"))
- call assert_equal("'te\\%xt'", shellescape("te%xt", 1))
- call assert_equal("'te#xt'", shellescape("te#xt"))
- call assert_equal("'te\\#xt'", shellescape("te#xt", 1))
- call assert_equal("'te!xt'", shellescape("te!xt"))
- call assert_equal("'te\\!xt'", shellescape("te!xt", 1))
-
- call assert_equal("'te\\\\xt'", shellescape("te\\xt"))
- call assert_equal("'te\\\\xt'", shellescape("te\\xt", 1))
- call assert_equal("'te\\\\'\\''xt'", shellescape("te\\'xt"))
- call assert_equal("'te\\\\'\\''xt'", shellescape("te\\'xt", 1))
- call assert_equal("'te\\\\!xt'", shellescape("te\\!xt"))
- call assert_equal("'te\\\\\\!xt'", shellescape("te\\!xt", 1))
- call assert_equal("'te\\\\%xt'", shellescape("te\\%xt"))
- call assert_equal("'te\\\\\\%xt'", shellescape("te\\%xt", 1))
- call assert_equal("'te\\\\#xt'", shellescape("te\\#xt"))
- call assert_equal("'te\\\\\\#xt'", shellescape("te\\#xt", 1))
-
- let &shell = save_shell
-endfunc
-
-func Test_setbufvar_options()
- " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the
- " window layout.
- call assert_equal(1, winnr('$'))
- split dummy_preview
- resize 2
- set winfixheight winfixwidth
- let prev_id = win_getid()
-
- wincmd j
- let wh = winheight(0)
- let dummy_buf = bufnr('dummy_buf1', v:true)
- call setbufvar(dummy_buf, '&buftype', 'nofile')
- execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight(0))
- let dum1_id = win_getid()
-
- wincmd h
- let wh = winheight(0)
- let dummy_buf = bufnr('dummy_buf2', v:true)
- eval 'nofile'->setbufvar(dummy_buf, '&buftype')
- execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight(0))
-
- bwipe!
- call win_gotoid(prev_id)
- bwipe!
- call win_gotoid(dum1_id)
- bwipe!
-endfunc
-
-func Test_redo_in_nested_functions()
- nnoremap g. :set opfunc=Operator<CR>g@
- function Operator( type, ... )
- let @x = 'XXX'
- execute 'normal! g`[' . (a:type ==# 'line' ? 'V' : 'v') . 'g`]' . '"xp'
- endfunction
-
- function! Apply()
- 5,6normal! .
- endfunction
-
- new
- call setline(1, repeat(['some "quoted" text', 'more "quoted" text'], 3))
- 1normal g.i"
- call assert_equal('some "XXX" text', getline(1))
- 3,4normal .
- call assert_equal('some "XXX" text', getline(3))
- call assert_equal('more "XXX" text', getline(4))
- call Apply()
- call assert_equal('some "XXX" text', getline(5))
- call assert_equal('more "XXX" text', getline(6))
- bwipe!
-
- nunmap g.
- delfunc Operator
- delfunc Apply
-endfunc
-
-func Test_trim()
- call assert_equal("Testing", trim(" \t\r\r\x0BTesting \t\n\r\n\t\x0B\x0B"))
- call assert_equal("Testing", " \t \r\r\n\n\x0BTesting \t\n\r\n\t\x0B\x0B"->trim())
- call assert_equal("RESERVE", trim("xyz \twwRESERVEzyww \t\t", " wxyz\t"))
- call assert_equal("wRE \tSERVEzyww", trim("wRE \tSERVEzyww"))
- call assert_equal("abcd\t xxxx tail", trim(" \tabcd\t xxxx tail"))
- call assert_equal("\tabcd\t xxxx tail", trim(" \tabcd\t xxxx tail", " "))
- call assert_equal(" \tabcd\t xxxx tail", trim(" \tabcd\t xxxx tail", "abx"))
- call assert_equal("RESERVE", trim("你RESERVE好", "你好"))
- call assert_equal("您R E SER V E早", trim("你好您R E SER V E早好你你", "你好"))
- call assert_equal("你好您R E SER V E早好你你", trim(" \n\r\r 你好您R E SER V E早好你你 \t \x0B", ))
- call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" 你好您R E SER V E早好你你 \t \x0B", " 你好"))
- call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你好tes"))
- call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你你你好好好tttsses"))
- call assert_equal("留下", trim("这些些ä¸è¦è¿™äº›ç•™ä¸‹è¿™äº›", "这些ä¸è¦"))
- call assert_equal("", trim("", ""))
- call assert_equal("a", trim("a", ""))
- call assert_equal("", trim("", "a"))
- call assert_equal("vim", trim(" vim ", " ", 0))
- call assert_equal("vim ", trim(" vim ", " ", 1))
- call assert_equal(" vim", trim(" vim ", " ", 2))
- call assert_fails('eval trim(" vim ", " ", [])', 'E745:')
- call assert_fails('eval trim(" vim ", " ", -1)', 'E475:')
- call assert_fails('eval trim(" vim ", " ", 3)', 'E475:')
- call assert_fails('eval trim(" vim ", 0)', 'E475:')
-
- let chars = join(map(range(1, 0x20) + [0xa0], {n -> n->nr2char()}), '')
- call assert_equal("x", trim(chars . "x" . chars))
-
- call assert_fails('let c=trim([])', 'E730:')
-endfunc
-
-" Test for reg_recording() and reg_executing()
-func Test_reg_executing_and_recording()
- let s:reg_stat = ''
- func s:save_reg_stat()
- let s:reg_stat = reg_recording() . ':' . reg_executing()
- return ''
- endfunc
-
- new
- call s:save_reg_stat()
- call assert_equal(':', s:reg_stat)
- call feedkeys("qa\"=s:save_reg_stat()\<CR>pq", 'xt')
- call assert_equal('a:', s:reg_stat)
- call feedkeys("@a", 'xt')
- call assert_equal(':a', s:reg_stat)
- call feedkeys("qb@aq", 'xt')
- call assert_equal('b:a', s:reg_stat)
- call feedkeys("q\"\"=s:save_reg_stat()\<CR>pq", 'xt')
- call assert_equal('":', s:reg_stat)
-
- " :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>
- let @q = "W"
- let g:typed = ''
- let g:regs = []
- func TestFunc() abort
- let g:regs += [reg_executing()]
- let g:typed = getchar(0)
- let g:regs += [reg_executing()]
- endfunc
- call feedkeys("@qy", 'xt')
- call assert_equal(char2nr("y"), g:typed)
- call assert_equal(['q', 'q'], g:regs)
- delfunc TestFunc
- unmap W
- unlet g:typed
- unlet g:regs
-
- " input() command saves and restores reg_executing
- map W :call TestFunc()<CR>
- let @q = "W"
- let g:typed = ''
- let g:regs = []
- func TestFunc() abort
- let g:regs += [reg_executing()]
- let g:typed = '?'->input()
- let g:regs += [reg_executing()]
- endfunc
- call feedkeys("@qy\<CR>", 'xt')
- call assert_equal("y", g:typed)
- call assert_equal(['q', 'q'], g:regs)
- delfunc TestFunc
- unmap W
- unlet g:typed
- unlet g:regs
-
- bwipe!
- delfunc s:save_reg_stat
- unlet s:reg_stat
-endfunc
-
-func Test_inputsecret()
- map W :call TestFunc()<CR>
- let @q = "W"
- let g:typed1 = ''
- let g:typed2 = ''
- let g:regs = []
- func TestFunc() abort
- let g:typed1 = '?'->inputsecret()
- let g:typed2 = inputsecret('password: ')
- endfunc
- call feedkeys("@qsomething\<CR>else\<CR>", 'xt')
- call assert_equal("something", g:typed1)
- call assert_equal("else", g:typed2)
- delfunc TestFunc
- unmap W
- unlet g:typed1
- unlet g:typed2
-endfunc
-
-func Test_getchar()
- call feedkeys('a', '')
- call assert_equal(char2nr('a'), getchar())
- call assert_equal(0, getchar(0))
- call assert_equal(0, getchar(1))
-
- call feedkeys('a', '')
- call assert_equal('a', getcharstr())
- call assert_equal('', getcharstr(0))
- call assert_equal('', getcharstr(1))
-
- call feedkeys("\<M-F2>", '')
- call assert_equal("\<M-F2>", getchar(0))
- call assert_equal(0, getchar(0))
-
- call setline(1, 'xxxx')
- call Ntest_setmouse(1, 3)
- let v:mouse_win = 9
- let v:mouse_winid = 9
- let v:mouse_lnum = 9
- let v:mouse_col = 9
- call feedkeys("\<S-LeftMouse>", '')
- call assert_equal("\<S-LeftMouse>", getchar())
- call assert_equal(1, v:mouse_win)
- call assert_equal(win_getid(1), v:mouse_winid)
- call assert_equal(1, v:mouse_lnum)
- call assert_equal(3, v:mouse_col)
- enew!
-endfunc
-
-func Test_libcall_libcallnr()
- if !has('libcall')
- return
- endif
-
- if has('win32')
- let libc = 'msvcrt.dll'
- elseif has('mac')
- let libc = 'libSystem.B.dylib'
- elseif executable('ldd')
- let libc = matchstr(split(system('ldd ' . GetVimProg())), '/libc\.so\>')
- endif
- if get(l:, 'libc', '') ==# ''
- " On Unix, libc.so can be in various places.
- if has('linux')
- " There is not documented but regarding the 1st argument of glibc's
- " dlopen an empty string and nullptr are equivalent, so using an empty
- " string for the 1st argument of libcall allows to call functions.
- let libc = ''
- elseif has('sun')
- " Set the path to libc.so according to the architecture.
- let test_bits = system('file ' . GetVimProg())
- let test_arch = system('uname -p')
- if test_bits =~ '64-bit' && test_arch =~ 'sparc'
- let libc = '/usr/lib/sparcv9/libc.so'
- elseif test_bits =~ '64-bit' && test_arch =~ 'i386'
- let libc = '/usr/lib/amd64/libc.so'
- else
- let libc = '/usr/lib/libc.so'
- endif
- else
- " Unfortunately skip this test until a good way is found.
- return
- endif
- endif
-
- if has('win32')
- call assert_equal($USERPROFILE, 'USERPROFILE'->libcall(libc, 'getenv'))
- else
- call assert_equal($HOME, 'HOME'->libcall(libc, 'getenv'))
- endif
-
- " If function returns NULL, libcall() should return an empty string.
- call assert_equal('', libcall(libc, 'getenv', 'X_ENV_DOES_NOT_EXIT'))
-
- " Test libcallnr() with string and integer argument.
- call assert_equal(4, 'abcd'->libcallnr(libc, 'strlen'))
- call assert_equal(char2nr('A'), char2nr('a')->libcallnr(libc, 'toupper'))
-
- call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", ['', 'E364:'])
- call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", ['', 'E364:'])
-
- call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", ['', 'E364:'])
- call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", ['', 'E364:'])
-endfunc
-
-sandbox function Fsandbox()
- normal ix
-endfunc
-
-func Test_func_sandbox()
- sandbox let F = {-> 'hello'}
- call assert_equal('hello', F())
-
- sandbox let F = {-> "normal ix\<Esc>"->execute()}
- call assert_fails('call F()', 'E48:')
- unlet F
-
- call assert_fails('call Fsandbox()', 'E48:')
- delfunc Fsandbox
-
- " From a sandbox try to set a predefined variable (which cannot be modified
- " from a sandbox)
- call assert_fails('sandbox let v:lnum = 10', 'E794:')
-endfunc
-
-func EditAnotherFile()
- let word = expand('<cword>')
- edit Xfuncrange2
-endfunc
-
-func Test_func_range_with_edit()
- " Define a function that edits another buffer, then call it with a range that
- " is invalid in that buffer.
- call writefile(['just one line'], 'Xfuncrange2')
- new
- eval 10->range()->setline(1)
- write Xfuncrange1
- call assert_fails('5,8call EditAnotherFile()', 'E16:')
-
- call delete('Xfuncrange1')
- call delete('Xfuncrange2')
- bwipe!
-endfunc
-
-func Test_func_exists_on_reload()
- call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists')
- call assert_equal(0, exists('*ExistingFunction'))
- source Xfuncexists
- call assert_equal(1, '*ExistingFunction'->exists())
- " Redefining a function when reloading a script is OK.
- source Xfuncexists
- call assert_equal(1, exists('*ExistingFunction'))
-
- " But redefining in another script is not OK.
- call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2')
- call assert_fails('source Xfuncexists2', 'E122:')
-
- delfunc ExistingFunction
- call assert_equal(0, exists('*ExistingFunction'))
- call writefile([
- \ 'func ExistingFunction()', 'echo "yes"', 'endfunc',
- \ 'func ExistingFunction()', 'echo "no"', 'endfunc',
- \ ], 'Xfuncexists')
- call assert_fails('source Xfuncexists', 'E122:')
- call assert_equal(1, exists('*ExistingFunction'))
-
- call delete('Xfuncexists2')
- call delete('Xfuncexists')
- delfunc ExistingFunction
-endfunc
-
-func Test_platform_name()
- " The system matches at most only one name.
- let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix']
- call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)')))
-
- " Is Unix?
- call assert_equal(has('beos'), has('beos') && has('unix'))
- call assert_equal(has('bsd'), has('bsd') && has('unix'))
- call assert_equal(has('hpux'), has('hpux') && has('unix'))
- call assert_equal(has('linux'), has('linux') && has('unix'))
- call assert_equal(has('mac'), has('mac') && has('unix'))
- call assert_equal(has('qnx'), has('qnx') && has('unix'))
- call assert_equal(has('sun'), has('sun') && has('unix'))
- call assert_equal(has('win32'), has('win32') && !has('unix'))
- call assert_equal(has('win32unix'), has('win32unix') && has('unix'))
-
- if has('unix') && executable('uname')
- let uname = system('uname')
- call assert_equal(uname =~? 'BeOS', has('beos'))
- " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined
- call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd'))
- call assert_equal(uname =~? 'HP-UX', has('hpux'))
- call assert_equal(uname =~? 'Linux', has('linux'))
- call assert_equal(uname =~? 'Darwin', has('mac'))
- call assert_equal(uname =~? 'QNX', has('qnx'))
- call assert_equal(uname =~? 'SunOS', has('sun'))
- call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix'))
- endif
-endfunc
-
-" Test confirm({msg} [, {choices} [, {default} [, {type}]]])
-func Test_confirm()
- " requires a UI to be active
- throw 'Skipped: use test/functional/vimscript/input_spec.lua'
- CheckUnix
- CheckNotGui
-
- call feedkeys('o', 'L')
- let a = confirm('Press O to proceed')
- call assert_equal(1, a)
-
- call feedkeys('y', 'L')
- let a = 'Are you sure?'->confirm("&Yes\n&No")
- call assert_equal(1, a)
-
- call feedkeys('n', 'L')
- let a = confirm('Are you sure?', "&Yes\n&No")
- call assert_equal(2, a)
-
- " confirm() should return 0 when pressing CTRL-C.
- call feedkeys("\<C-C>", 'L')
- let a = confirm('Are you sure?', "&Yes\n&No")
- call assert_equal(0, a)
-
- " <Esc> requires another character to avoid it being seen as the start of an
- " escape sequence. Zero should be harmless.
- eval "\<Esc>0"->feedkeys('L')
- let a = confirm('Are you sure?', "&Yes\n&No")
- call assert_equal(0, a)
-
- " Default choice is returned when pressing <CR>.
- call feedkeys("\<CR>", 'L')
- let a = confirm('Are you sure?', "&Yes\n&No")
- call assert_equal(1, a)
-
- call feedkeys("\<CR>", 'L')
- let a = confirm('Are you sure?', "&Yes\n&No", 2)
- call assert_equal(2, a)
-
- call feedkeys("\<CR>", 'L')
- let a = confirm('Are you sure?', "&Yes\n&No", 0)
- call assert_equal(0, a)
-
- " Test with the {type} 4th argument
- for type in ['Error', 'Question', 'Info', 'Warning', 'Generic']
- call feedkeys('y', 'L')
- let a = confirm('Are you sure?', "&Yes\n&No\n", 1, type)
- call assert_equal(1, a)
- endfor
-
- call assert_fails('call confirm([])', 'E730:')
- call assert_fails('call confirm("Are you sure?", [])', 'E730:')
- call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", [])', 'E745:')
- call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", 0, [])', 'E730:')
-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 = 'Xdir'->readdir({ 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))
-
- eval 'Xdir'->delete('rf')
-endfunc
-
-func Test_delete_rf()
- call mkdir('Xdir')
- call writefile([], 'Xdir/foo.txt')
- call writefile([], 'Xdir/bar.txt')
- call mkdir('Xdir/[a-1]') " issue #696
- call writefile([], 'Xdir/[a-1]/foo.txt')
- call writefile([], 'Xdir/[a-1]/bar.txt')
- call assert_true(filereadable('Xdir/foo.txt'))
- call assert_true('Xdir/[a-1]/foo.txt'->filereadable())
-
- call assert_equal(0, delete('Xdir', 'rf'))
- call assert_false(filereadable('Xdir/foo.txt'))
- call assert_false(filereadable('Xdir/[a-1]/foo.txt'))
-
- if has('unix')
- call mkdir('Xdir/Xdir2', 'p')
- silent !chmod 555 Xdir
- call assert_equal(-1, delete('Xdir/Xdir2', 'rf'))
- call assert_equal(-1, delete('Xdir', 'rf'))
- silent !chmod 755 Xdir
- call assert_equal(0, delete('Xdir', 'rf'))
- endif
-endfunc
-
-func Test_call()
- call assert_equal(3, call('len', [123]))
- call assert_equal(3, 'len'->call([123]))
- call assert_fails("call call('len', 123)", 'E714:')
- call assert_equal(0, call('', []))
- call assert_equal(0, call('len', v:_null_list))
-
- function Mylen() dict
- return len(self.data)
- endfunction
- let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")}
- eval mydict.len->call([], mydict)->assert_equal(4)
- call assert_fails("call call('Mylen', [], 0)", 'E715:')
- call assert_fails('call foo', 'E107:')
-endfunc
-
-func Test_char2nr()
- call assert_equal(12354, char2nr('ã‚', 1))
- call assert_equal(120, 'x'->char2nr())
- " set encoding=latin1
- call assert_equal(120, 'x'->char2nr())
- set encoding=utf-8
-endfunc
-
-func Test_charclass()
- call assert_equal(0, charclass(' '))
- call assert_equal(1, charclass('.'))
- call assert_equal(2, charclass('x'))
- call assert_equal(3, charclass("\u203c"))
- " this used to crash vim
- call assert_equal(0, "xxx"[-1]->charclass())
-endfunc
-
-func Test_eventhandler()
- call assert_equal(0, eventhandler())
-endfunc
-
-func Test_bufadd_bufload()
- call assert_equal(0, bufexists('someName'))
- let buf = bufadd('someName')
- call assert_notequal(0, buf)
- call assert_equal(1, bufexists('someName'))
- call assert_equal(0, getbufvar(buf, '&buflisted'))
- call assert_equal(0, bufloaded(buf))
- call bufload(buf)
- call assert_equal(1, bufloaded(buf))
- call assert_equal([''], getbufline(buf, 1, '$'))
-
- let curbuf = bufnr('')
- eval ['some', 'text']->writefile('XotherName')
- let buf = 'XotherName'->bufadd()
- call assert_notequal(0, buf)
- eval 'XotherName'->bufexists()->assert_equal(1)
- call assert_equal(0, getbufvar(buf, '&buflisted'))
- call assert_equal(0, bufloaded(buf))
- eval buf->bufload()
- call assert_equal(1, bufloaded(buf))
- call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
- call assert_equal(curbuf, bufnr(''))
-
- let buf1 = bufadd('')
- let buf2 = bufadd('')
- call assert_notequal(0, buf1)
- call assert_notequal(0, buf2)
- call assert_notequal(buf1, buf2)
- call assert_equal(1, bufexists(buf1))
- call assert_equal(1, bufexists(buf2))
- call assert_equal(0, bufloaded(buf1))
- exe 'bwipe ' .. buf1
- call assert_equal(0, bufexists(buf1))
- call assert_equal(1, bufexists(buf2))
- exe 'bwipe ' .. buf2
- call assert_equal(0, bufexists(buf2))
-
- " When 'buftype' is "nofile" then bufload() does not read the file.
- " Other values too.
- for val in [['nofile', 0],
- \ ['nowrite', 1],
- \ ['acwrite', 1],
- \ ['quickfix', 0],
- \ ['help', 1],
- "\ ['terminal', 0],
- \ ['prompt', 0],
- "\ ['popup', 0],
- \ ]
- bwipe! XotherName
- let buf = bufadd('XotherName')
- call setbufvar(buf, '&bt', val[0])
- call bufload(buf)
- call assert_equal(val[1] ? ['some', 'text'] : [''], getbufline(buf, 1, '$'), val[0])
- endfor
-
- bwipe someName
- bwipe XotherName
- call assert_equal(0, bufexists('someName'))
- call delete('XotherName')
-endfunc
-
-func Test_range()
- " destructuring
- let [x, y] = range(2)
- call assert_equal([0, 1], [x, y])
-
- " index
- call assert_equal(4, range(1, 10)[3])
-
- " add()
- call assert_equal([0, 1, 2, 3], add(range(3), 3))
- call assert_equal([0, 1, 2, [0, 1, 2]], add([0, 1, 2], range(3)))
- call assert_equal([0, 1, 2, [0, 1, 2]], add(range(3), range(3)))
-
- " append()
- new
- call append('.', range(5))
- call assert_equal(['', '0', '1', '2', '3', '4'], getline(1, '$'))
- bwipe!
-
- " appendbufline()
- new
- call appendbufline(bufnr(''), '.', range(5))
- call assert_equal(['0', '1', '2', '3', '4', ''], getline(1, '$'))
- bwipe!
-
- " call()
- func TwoArgs(a, b)
- return [a:a, a:b]
- endfunc
- call assert_equal([0, 1], call('TwoArgs', range(2)))
-
- " col()
- new
- call setline(1, ['foo', 'bar'])
- call assert_equal(2, col(range(1, 2)))
- bwipe!
-
- " complete()
- execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>"
- " complete_info()
- execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>\<C-r>=[complete_info(range(5)), ''][1]\<CR>"
-
- " copy()
- call assert_equal([1, 2, 3], copy(range(1, 3)))
-
- " count()
- call assert_equal(0, count(range(0), 3))
- call assert_equal(0, count(range(2), 3))
- call assert_equal(1, count(range(5), 3))
-
- " cursor()
- new
- call setline(1, ['aaa', 'bbb', 'ccc'])
- call cursor(range(1, 2))
- call assert_equal([2, 1], [col('.'), line('.')])
- bwipe!
-
- " deepcopy()
- call assert_equal([1, 2, 3], deepcopy(range(1, 3)))
-
- " empty()
- call assert_true(empty(range(0)))
- call assert_false(empty(range(2)))
-
- " execute()
- new
- call setline(1, ['aaa', 'bbb', 'ccc'])
- call execute(range(3))
- call assert_equal(2, line('.'))
- bwipe!
-
- " extend()
- call assert_equal([1, 2, 3, 4], extend([1], range(2, 4)))
- call assert_equal([1, 2, 3, 4], extend(range(1, 1), range(2, 4)))
- call assert_equal([1, 2, 3, 4], extend(range(1, 1), [2, 3, 4]))
-
- " filter()
- call assert_equal([1, 3], filter(range(5), 'v:val % 2'))
-
- " funcref()
- call assert_equal([0, 1], funcref('TwoArgs', range(2))())
-
- " function()
- call assert_equal([0, 1], function('TwoArgs', range(2))())
-
- " garbagecollect()
- let thelist = [1, range(2), 3]
- let otherlist = range(3)
- call test_garbagecollect_now()
-
- " get()
- call assert_equal(4, get(range(1, 10), 3))
- call assert_equal(-1, get(range(1, 10), 42, -1))
-
- " index()
- call assert_equal(1, index(range(1, 5), 2))
- call assert_fails("echo index([1, 2], 1, [])", 'E745:')
-
- " insert()
- call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42))
- call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42, 0))
- call assert_equal([1, 42, 2, 3, 4, 5], insert(range(1, 5), 42, 1))
- call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, 4))
- call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, -1))
- call assert_equal([1, 2, 3, 4, 5, 42], insert(range(1, 5), 42, 5))
-
- " join()
- call assert_equal('0 1 2 3 4', join(range(5)))
-
- " json_encode()
- " call assert_equal('[0,1,2,3]', json_encode(range(4)))
- call assert_equal('[0, 1, 2, 3]', json_encode(range(4)))
-
- " len()
- call assert_equal(0, len(range(0)))
- call assert_equal(2, len(range(2)))
- call assert_equal(5, len(range(0, 12, 3)))
- call assert_equal(4, len(range(3, 0, -1)))
-
- " list2str()
- call assert_equal('ABC', list2str(range(65, 67)))
- call assert_fails('let s = list2str(5)', 'E474:')
-
- " lock()
- let thelist = range(5)
- lockvar thelist
-
- " map()
- call assert_equal([0, 2, 4, 6, 8], map(range(5), 'v:val * 2'))
-
- " match()
- call assert_equal(3, match(range(5), 3))
-
- " matchaddpos()
- highlight MyGreenGroup ctermbg=green guibg=green
- call matchaddpos('MyGreenGroup', range(line('.'), line('.')))
-
- " matchend()
- call assert_equal(4, matchend(range(5), '4'))
- call assert_equal(3, matchend(range(1, 5), '4'))
- call assert_equal(-1, matchend(range(1, 5), '42'))
-
- " matchstrpos()
- call assert_equal(['4', 4, 0, 1], matchstrpos(range(5), '4'))
- call assert_equal(['4', 3, 0, 1], matchstrpos(range(1, 5), '4'))
- call assert_equal(['', -1, -1, -1], matchstrpos(range(1, 5), '42'))
-
- " max() reverse()
- call assert_equal(0, max(range(0)))
- call assert_equal(0, max(range(10, 9)))
- call assert_equal(9, max(range(10)))
- call assert_equal(18, max(range(0, 20, 3)))
- call assert_equal(20, max(range(20, 0, -3)))
- call assert_equal(99999, max(range(100000)))
- call assert_equal(99999, max(range(99999, 0, -1)))
- call assert_equal(99999, max(reverse(range(100000))))
- call assert_equal(99999, max(reverse(range(99999, 0, -1))))
-
- " min() reverse()
- call assert_equal(0, min(range(0)))
- call assert_equal(0, min(range(10, 9)))
- call assert_equal(5, min(range(5, 10)))
- call assert_equal(5, min(range(5, 10, 3)))
- call assert_equal(2, min(range(20, 0, -3)))
- call assert_equal(0, min(range(100000)))
- call assert_equal(0, min(range(99999, 0, -1)))
- call assert_equal(0, min(reverse(range(100000))))
- call assert_equal(0, min(reverse(range(99999, 0, -1))))
-
- " remove()
- call assert_equal(1, remove(range(1, 10), 0))
- call assert_equal(2, remove(range(1, 10), 1))
- call assert_equal(9, remove(range(1, 10), 8))
- call assert_equal(10, remove(range(1, 10), 9))
- call assert_equal(10, remove(range(1, 10), -1))
- call assert_equal([3, 4, 5], remove(range(1, 10), 2, 4))
-
- " repeat()
- call assert_equal([0, 1, 2, 0, 1, 2], repeat(range(3), 2))
- call assert_equal([0, 1, 2], repeat(range(3), 1))
- call assert_equal([], repeat(range(3), 0))
- call assert_equal([], repeat(range(5, 4), 2))
- call assert_equal([], repeat(range(5, 4), 0))
-
- " reverse()
- call assert_equal([2, 1, 0], reverse(range(3)))
- call assert_equal([0, 1, 2, 3], reverse(range(3, 0, -1)))
- call assert_equal([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], reverse(range(10)))
- call assert_equal([20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10], reverse(range(10, 20)))
- call assert_equal([16, 13, 10], reverse(range(10, 18, 3)))
- call assert_equal([19, 16, 13, 10], reverse(range(10, 19, 3)))
- call assert_equal([19, 16, 13, 10], reverse(range(10, 20, 3)))
- call assert_equal([11, 14, 17, 20], reverse(range(20, 10, -3)))
- call assert_equal([], reverse(range(0)))
-
- " TODO: setpos()
- " new
- " call setline(1, repeat([''], bufnr('')))
- " call setline(bufnr('') + 1, repeat('x', bufnr('') * 2 + 6))
- " call setpos('x', range(bufnr(''), bufnr('') + 3))
- " bwipe!
-
- " setreg()
- call setreg('a', range(3))
- call assert_equal("0\n1\n2\n", getreg('a'))
-
- " settagstack()
- call settagstack(1, #{items : range(4)})
-
- " sign_define()
- call assert_fails("call sign_define(range(5))", "E715:")
- call assert_fails("call sign_placelist(range(5))", "E715:")
-
- " sign_undefine()
- " call assert_fails("call sign_undefine(range(5))", "E908:")
- call assert_fails("call sign_undefine(range(5))", "E155:")
-
- " sign_unplacelist()
- call assert_fails("call sign_unplacelist(range(5))", "E715:")
-
- " sort()
- call assert_equal([0, 1, 2, 3, 4, 5], sort(range(5, 0, -1)))
-
- " string()
- call assert_equal('[0, 1, 2, 3, 4]', string(range(5)))
-
- " taglist() with 'tagfunc'
- func TagFunc(pattern, flags, info)
- return range(10)
- endfunc
- set tagfunc=TagFunc
- call assert_fails("call taglist('asdf')", 'E987:')
- set tagfunc=
-
- " term_start()
- if has('terminal') && has('termguicolors')
- call assert_fails('call term_start(range(3, 4))', 'E474:')
- let g:terminal_ansi_colors = range(16)
- if has('win32')
- let cmd = "cmd /c dir"
- else
- let cmd = "ls"
- endif
- call assert_fails('call term_start("' .. cmd .. '", #{term_finish: "close"})', 'E475:')
- unlet g:terminal_ansi_colors
- endif
-
- " type()
- call assert_equal(v:t_list, type(range(5)))
-
- " uniq()
- call assert_equal([0, 1, 2, 3, 4], uniq(range(5)))
-
- " errors
- call assert_fails('let x=range(2, 8, 0)', 'E726:')
- call assert_fails('let x=range(3, 1)', 'E727:')
- call assert_fails('let x=range(1, 3, -2)', 'E727:')
- call assert_fails('let x=range([])', 'E745:')
- call assert_fails('let x=range(1, [])', 'E745:')
- call assert_fails('let x=range(1, 4, [])', 'E745:')
-endfunc
-
-func Test_garbagecollect_now_fails()
- let v:testing = 0
- call assert_fails('call test_garbagecollect_now()', 'E1142:')
- let v:testing = 1
-endfunc
-
-" Test for the eval() function
-func Test_eval()
- call assert_fails("call eval('5 a')", 'E488:')
-endfunc
-
-" Test for the keytrans() function
-func Test_keytrans()
- call assert_equal('<Space>', keytrans(' '))
- call assert_equal('<lt>', keytrans('<'))
- call assert_equal('<lt>Tab>', keytrans('<Tab>'))
- call assert_equal('<Tab>', keytrans("\<Tab>"))
- call assert_equal('<C-V>', keytrans("\<C-V>"))
- call assert_equal('<BS>', keytrans("\<BS>"))
- call assert_equal('<Home>', keytrans("\<Home>"))
- call assert_equal('<C-Home>', keytrans("\<C-Home>"))
- call assert_equal('<M-Home>', keytrans("\<M-Home>"))
- call assert_equal('<C-Space>', keytrans("\<C-Space>"))
- call assert_equal('<M-Space>', keytrans("\<*M-Space>"))
- call assert_equal('<M-x>', "\<*M-x>"->keytrans())
- call assert_equal('<C-I>', "\<*C-I>"->keytrans())
- call assert_equal('<S-3>', "\<*S-3>"->keytrans())
- call assert_equal('Ï€', 'Ï€'->keytrans())
- call assert_equal('<M-Ï€>', "\<M-Ï€>"->keytrans())
- call assert_equal('Ä›', 'Ä›'->keytrans())
- call assert_equal('<M-Ä›>', "\<M-Ä›>"->keytrans())
- call assert_equal('', ''->keytrans())
- call assert_equal('', v:_null_string->keytrans())
- call assert_fails('call keytrans(1)', 'E1174:')
- call assert_fails('call keytrans()', 'E119:')
-endfunc
-
-" Test for the nr2char() function
-func Test_nr2char()
- " set encoding=latin1
- call assert_equal('@', nr2char(64))
- set encoding=utf8
- call assert_equal('a', nr2char(97, 1))
- call assert_equal('a', nr2char(97, 0))
-
- call assert_equal("\x80\xfc\b" .. nr2char(0x100000), eval('"\<M-' .. nr2char(0x100000) .. '>"'))
- call assert_equal("\x80\xfc\b" .. nr2char(0x40000000), eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
-endfunc
-
-" Test for screenattr(), screenchar() and screenchars() functions
-func Test_screen_functions()
- call assert_equal(-1, screenattr(-1, -1))
- call assert_equal(-1, screenchar(-1, -1))
- call assert_equal([], screenchars(-1, -1))
-endfunc
-
-" Test for getcurpos() and setpos()
-func Test_getcurpos_setpos()
- new
- call setline(1, ['012345678', '012345678'])
- normal gg6l
- let sp = getcurpos()
- normal 0
- call setpos('.', sp)
- normal jyl
- call assert_equal('6', @")
- call assert_equal(-1, setpos('.', v:_null_list))
- call assert_equal(-1, setpos('.', {}))
-
- let winid = win_getid()
- normal G$
- let pos = getcurpos()
- wincmd w
- call assert_equal(pos, getcurpos(winid))
-
- wincmd w
- close!
-
- call assert_equal(getcurpos(), getcurpos(0))
- call assert_equal([0, 0, 0, 0, 0], getcurpos(-1))
- call assert_equal([0, 0, 0, 0, 0], getcurpos(1999))
-endfunc
-
-func Test_getmousepos()
- enew!
- call setline(1, "\t\t\t1234")
- call Ntest_setmouse(1, 1)
- call assert_equal(#{
- \ screenrow: 1,
- \ screencol: 1,
- \ winid: win_getid(),
- \ winrow: 1,
- \ wincol: 1,
- \ line: 1,
- \ column: 1,
- \ }, getmousepos())
- call Ntest_setmouse(1, 25)
- call assert_equal(#{
- \ screenrow: 1,
- \ screencol: 25,
- \ winid: win_getid(),
- \ winrow: 1,
- \ wincol: 25,
- \ line: 1,
- \ column: 4,
- \ }, getmousepos())
- call Ntest_setmouse(1, 50)
- call assert_equal(#{
- \ screenrow: 1,
- \ screencol: 50,
- \ winid: win_getid(),
- \ winrow: 1,
- \ wincol: 50,
- \ line: 1,
- \ column: 8,
- \ }, getmousepos())
-
- " If the mouse is positioned past the last buffer line, "line" and "column"
- " should act like it's positioned on the last buffer line.
- call Ntest_setmouse(2, 25)
- call assert_equal(#{
- \ screenrow: 2,
- \ screencol: 25,
- \ winid: win_getid(),
- \ winrow: 2,
- \ wincol: 25,
- \ line: 1,
- \ column: 4,
- \ }, getmousepos())
- call Ntest_setmouse(2, 50)
- call assert_equal(#{
- \ screenrow: 2,
- \ screencol: 50,
- \ winid: win_getid(),
- \ winrow: 2,
- \ wincol: 50,
- \ line: 1,
- \ column: 8,
- \ }, getmousepos())
- bwipe!
-endfunc
-
-" Test for glob()
-func Test_glob()
- call assert_equal('', glob(v:_null_string))
- call assert_equal('', globpath(v:_null_string, v:_null_string))
- call assert_fails("let x = globpath(&rtp, 'syntax/c.vim', [])", 'E745:')
-
- call writefile([], 'Xglob1')
- call writefile([], 'XGLOB2')
- set wildignorecase
- " Sort output of glob() otherwise we end up with different
- " ordering depending on whether file system is case-sensitive.
- call assert_equal(['XGLOB2', 'Xglob1'], sort(glob('Xglob[12]', 0, 1)))
- " wildignorecase shall be applied even when the pattern contains no wildcards.
- call assert_equal('XGLOB2', glob('xglob2'))
- set wildignorecase&
-
- call delete('Xglob1')
- call delete('XGLOB2')
-
- call assert_fails("call glob('*', 0, {})", 'E728:')
-endfunc
-
-func HasDefault(msg = 'msg')
- return a:msg
-endfunc
-
-func Test_default_arg_value()
- call assert_equal('msg', HasDefault())
-endfunc
-
-" Test for gettext()
-func Test_gettext()
- call assert_fails('call gettext(1)', 'E475:')
-endfunc
-
-func Test_builtin_check()
- call assert_fails('let g:["trim"] = {x -> " " .. x}', 'E704:')
- call assert_fails('let g:.trim = {x -> " " .. x}', 'E704:')
- call assert_fails('let l:["trim"] = {x -> " " .. x}', 'E704:')
- call assert_fails('let l:.trim = {x -> " " .. x}', 'E704:')
- let lines =<< trim END
- vim9script
- var s:trim = (x) => " " .. x
- END
- call CheckScriptFailure(lines, 'E704:')
-
- call assert_fails('call extend(g:, #{foo: { -> "foo" }})', 'E704:')
- let g:bar = 123
- call extend(g:, #{bar: { -> "foo" }}, "keep")
- call assert_fails('call extend(g:, #{bar: { -> "foo" }}, "force")', 'E704:')
- unlet g:bar
-
- call assert_fails('call extend(l:, #{foo: { -> "foo" }})', 'E704:')
- let bar = 123
- call extend(l:, #{bar: { -> "foo" }}, "keep")
- call assert_fails('call extend(l:, #{bar: { -> "foo" }}, "force")', 'E704:')
- unlet bar
-
- call assert_fails('call extend(g:, #{foo: function("extend")})', 'E704:')
- let g:bar = 123
- call extend(g:, #{bar: function("extend")}, "keep")
- call assert_fails('call extend(g:, #{bar: function("extend")}, "force")', 'E704:')
- unlet g:bar
-
- call assert_fails('call extend(l:, #{foo: function("extend")})', 'E704:')
- let bar = 123
- call extend(l:, #{bar: function("extend")}, "keep")
- call assert_fails('call extend(l:, #{bar: function("extend")}, "force")', 'E704:')
- unlet bar
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim
deleted file mode 100644
index ce31edfc7a..0000000000
--- a/src/nvim/testdir/test_ga.vim
+++ /dev/null
@@ -1,43 +0,0 @@
-" Test ga normal command, and :ascii Ex command.
-func Do_ga(c)
- call setline(1, a:c)
- let l:a = execute("norm 1goga")
- let l:b = execute("ascii")
- call assert_equal(l:a, l:b)
- return l:a
-endfunc
-
-func Test_ga_command()
- new
- set display=uhex
- call assert_equal("\nNUL", Do_ga(''))
- call assert_equal("\n<<01>> 1, Hex 01, Oct 001, Digr SH", Do_ga("\x01"))
- call assert_equal("\n<<09>> 9, Hex 09, Oct 011, Digr HT", Do_ga("\t"))
-
- set display=
- call assert_equal("\nNUL", Do_ga(''))
- call assert_equal("\n<^A> 1, Hex 01, Oct 001, Digr SH", Do_ga("\x01"))
- call assert_equal("\n<^I> 9, Hex 09, Oct 011, Digr HT", Do_ga("\t"))
- call assert_equal("\n<^@> 0, Hex 00, Octal 000", Do_ga("\n"))
-
- call assert_equal("\n<e> 101, Hex 65, Octal 145", Do_ga('e'))
-
- " 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"))
- call assert_equal("\n<e> 101, Hex 65, Octal 145 < Ì> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461", Do_ga("e\u0301\u0331"))
- call assert_equal("\n<e> 101, Hex 65, Octal 145 < Ì> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461 < ̸> 824, Hex 0338, Octal 1470", Do_ga("e\u0301\u0331\u0338"))
-
- " When using Mac fileformat, CR instead of NL is used for line termination
- enew!
- set fileformat=mac
- call assert_equal("\n<^J> 10, Hex 0a, Oct 012, Digr NU", Do_ga("\r"))
-
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_getcwd.vim b/src/nvim/testdir/test_getcwd.vim
deleted file mode 100644
index 073f8303dc..0000000000
--- a/src/nvim/testdir/test_getcwd.vim
+++ /dev/null
@@ -1,112 +0,0 @@
-func GetCwdInfo(win, tab)
- let tab_changed = 0
- let mod = ":t"
- if a:tab > 0 && a:tab != tabpagenr()
- let tab_changed = 1
- exec "tabnext " . a:tab
- endif
- let bufname = fnamemodify(bufname(winbufnr(a:win)), mod)
- if tab_changed
- tabprevious
- endif
- if a:win == 0 && a:tab == 0
- let dirname = fnamemodify(getcwd(), mod)
- let lflag = haslocaldir()
- elseif a:tab == 0
- let dirname = fnamemodify(getcwd(a:win), mod)
- let lflag = haslocaldir(a:win)
- else
- let dirname = fnamemodify(getcwd(a:win, a:tab), mod)
- let lflag = a:win->haslocaldir(a:tab)
- endif
- return bufname . ' ' . dirname . ' ' . lflag
-endfunc
-
-" Do all test in a separate window to avoid E211 when we recursively
-" delete the Xtopdir directory during cleanup
-func SetUp()
- set visualbell
- set nocp viminfo+=nviminfo
-
- " On windows a swapfile in Xtopdir prevents it from being cleaned up.
- set noswapfile
-
- " On windows a stale "Xtopdir" directory may exist, remove it so that
- " we start from a clean state.
- call delete("Xtopdir", "rf")
- new
- eval 'Xtopdir'->mkdir()
- cd Xtopdir
- let g:topdir = getcwd()
- call mkdir('Xdir1')
- call mkdir('Xdir2')
- call mkdir('Xdir3')
-endfunction
-
-let g:cwd=getcwd()
-function TearDown()
- q
- call chdir(g:cwd)
- call delete("Xtopdir", "rf")
-endfunction
-
-function Test_GetCwd()
- new a
- new b
- new c
- 3wincmd w
- lcd Xdir1
- call assert_equal("a Xdir1 1", GetCwdInfo(0, 0))
- call assert_equal(g:topdir, getcwd(-1))
- wincmd W
- call assert_equal("b Xtopdir 0", GetCwdInfo(0, 0))
- call assert_equal(g:topdir, getcwd(-1))
- wincmd W
- lcd Xdir3
- call assert_equal("c Xdir3 1", GetCwdInfo(0, 0))
- call assert_equal("a Xdir1 1", GetCwdInfo(bufwinnr("a"), 0))
- call assert_equal("b Xtopdir 0", GetCwdInfo(bufwinnr("b"), 0))
- call assert_equal("c Xdir3 1", GetCwdInfo(bufwinnr("c"), 0))
- call assert_equal(g:topdir, getcwd(-1))
- wincmd W
- call assert_equal("a Xdir1 1", GetCwdInfo(bufwinnr("a"), tabpagenr()))
- call assert_equal("b Xtopdir 0", GetCwdInfo(bufwinnr("b"), tabpagenr()))
- call assert_equal("c Xdir3 1", GetCwdInfo(bufwinnr("c"), tabpagenr()))
- call assert_equal(g:topdir, getcwd(-1))
-
- tabnew x
- new y
- new z
- 3wincmd w
- call assert_equal("x Xtopdir 0", GetCwdInfo(0, 0))
- call assert_equal(g:topdir, getcwd(-1))
- wincmd W
- lcd Xdir2
- call assert_equal("y Xdir2 1", GetCwdInfo(0, 0))
- call assert_equal(g:topdir, getcwd(-1))
- wincmd W
- lcd Xdir3
- call assert_equal("z Xdir3 1", GetCwdInfo(0, 0))
- call assert_equal("x Xtopdir 0", GetCwdInfo(bufwinnr("x"), 0))
- call assert_equal("y Xdir2 1", GetCwdInfo(bufwinnr("y"), 0))
- call assert_equal("z Xdir3 1", GetCwdInfo(bufwinnr("z"), 0))
- call assert_equal(g:topdir, getcwd(-1))
- let tp_nr = tabpagenr()
- tabrewind
- call assert_equal("x Xtopdir 0", GetCwdInfo(3, tp_nr))
- call assert_equal("y Xdir2 1", GetCwdInfo(2, tp_nr))
- call assert_equal("z Xdir3 1", GetCwdInfo(1, tp_nr))
- call assert_equal(g:topdir, getcwd(-1))
-endfunc
-
-function Test_GetCwd_lcd_shellslash()
- new
- let root = fnamemodify('/', ':p')
- exe 'lcd '.root
- let cwd = getcwd()
- if !exists('+shellslash') || &shellslash
- call assert_equal(cwd[-1:], '/')
- else
- call assert_equal(cwd[-1:], '\')
- endif
-endfunc
diff --git a/src/nvim/testdir/test_getvar.vim b/src/nvim/testdir/test_getvar.vim
deleted file mode 100644
index e6b6341fce..0000000000
--- a/src/nvim/testdir/test_getvar.vim
+++ /dev/null
@@ -1,155 +0,0 @@
-" Tests for getwinvar(), gettabvar(), gettabwinvar() and get().
-
-func Test_var()
- " Use strings to test for memory leaks. First, check that in an empty
- " window, gettabvar() returns the correct value
- let t:testvar='abcd'
- call assert_equal('abcd', gettabvar(1, 'testvar'))
- call assert_equal('abcd', gettabvar(1, 'testvar'))
-
- " test for getwinvar()
- let w:var_str = "Dance"
- let def_str = "Chance"
- call assert_equal('Dance', getwinvar(1, 'var_str'))
- call assert_equal('Dance', getwinvar(1, 'var_str', def_str))
- call assert_equal({'var_str': 'Dance'}, 1->getwinvar(''))
- call assert_equal({'var_str': 'Dance'}, 1->getwinvar('', def_str))
- unlet w:var_str
- call assert_equal('Chance', getwinvar(1, 'var_str', def_str))
- call assert_equal({}, getwinvar(1, ''))
- call assert_equal({}, getwinvar(1, '', def_str))
- call assert_equal('', getwinvar(9, ''))
- call assert_equal('Chance', getwinvar(9, '', def_str))
- call assert_equal(0, getwinvar(1, '&nu'))
- call assert_equal(0, getwinvar(1, '&nu', 1))
- unlet def_str
-
- " test for gettabvar()
- tabnew
- tabnew
- let t:var_list = [1, 2, 3]
- let t:other = 777
- let def_list = [4, 5, 6, 7]
- tabrewind
- call assert_equal([1, 2, 3], 3->gettabvar('var_list'))
- call assert_equal([1, 2, 3], gettabvar(3, 'var_list', def_list))
- call assert_equal({'var_list': [1, 2, 3], 'other': 777}, gettabvar(3, ''))
- call assert_equal({'var_list': [1, 2, 3], 'other': 777},
- \ gettabvar(3, '', def_list))
-
- tablast
- unlet t:var_list
- tabrewind
- call assert_equal([4, 5, 6, 7], gettabvar(3, 'var_list', def_list))
- call assert_equal('', gettabvar(9, ''))
- call assert_equal([4, 5, 6, 7], gettabvar(9, '', def_list))
- call assert_equal('', gettabvar(3, '&nu'))
- call assert_equal([4, 5, 6, 7], gettabvar(3, '&nu', def_list))
- unlet def_list
- tabonly
-
- " test for gettabwinvar()
- tabnew
- tabnew
- tabprev
- split
- split
- wincmd w
- vert split
- wincmd w
- let w:var_dict = {'dict': 'tabwin'}
- let def_dict = {'dict2': 'newval'}
- wincmd b
- tabrewind
- call assert_equal({'dict': 'tabwin'}, 2->gettabwinvar(3, 'var_dict'))
- call assert_equal({'dict': 'tabwin'},
- \ gettabwinvar(2, 3, 'var_dict', def_dict))
- call assert_equal({'var_dict': {'dict': 'tabwin'}}, gettabwinvar(2, 3, ''))
- call assert_equal({'var_dict': {'dict': 'tabwin'}},
- \ gettabwinvar(2, 3, '', def_dict))
-
- tabnext
- 3wincmd w
- unlet w:var_dict
- tabrewind
- call assert_equal({'dict2': 'newval'},
- \ gettabwinvar(2, 3, 'var_dict', def_dict))
- call assert_equal({}, gettabwinvar(2, 3, ''))
- call assert_equal({}, gettabwinvar(2, 3, '', def_dict))
- call assert_equal("", gettabwinvar(2, 9, ''))
- call assert_equal({'dict2': 'newval'}, gettabwinvar(2, 9, '', def_dict))
- call assert_equal('', gettabwinvar(9, 3, ''))
- call assert_equal({'dict2': 'newval'}, gettabwinvar(9, 3, '', def_dict))
-
- unlet def_dict
-
- call assert_equal('', gettabwinvar(2, 3, '&nux'))
- call assert_equal(1, gettabwinvar(2, 3, '&nux', 1))
- tabonly
-endfunc
-
-" It was discovered that "gettabvar()" would fail if called from within the
-" tabline when the user closed a window. This test confirms the fix.
-func Test_gettabvar_in_tabline()
- let t:var_str = 'value'
-
- set tabline=%{assert_equal('value',gettabvar(1,'var_str'))}
- set showtabline=2
-
- " Simulate the user opening a split (which becomes window #1) and then
- " closing the split, which triggers the redrawing of the tabline.
- leftabove split
- redrawstatus!
- close
- redrawstatus!
-endfunc
-
-" Test get() function using default value.
-
-" get({dict}, {key} [, {default}])
-func Test_get_dict()
- let d = {'foo': 42}
- call assert_equal(42, get(d, 'foo', 99))
- call assert_equal(999, get(d, 'bar', 999))
-endfunc
-
-" get({list}, {idx} [, {default}])
-func Test_get_list()
- let l = [1,2,3]
- call assert_equal(1, get(l, 0, 999))
- call assert_equal(3, get(l, -1, 999))
- call assert_equal(999, get(l, 3, 999))
-endfunc
-
-" get({blob}, {idx} [, {default}]) - in test_blob.vim
-
-" get({lambda}, {what} [, {default}])
-func Test_get_lambda()
- let l:L = {-> 42}
- call assert_match('^<lambda>', get(l:L, 'name'))
- call assert_equal(l:L, get(l:L, 'func'))
- call assert_equal({'lambda has': 'no dict'}, get(l:L, 'dict', {'lambda has': 'no dict'}))
- call assert_equal(0, get(l:L, 'dict'))
- call assert_equal([], get(l:L, 'args'))
-endfunc
-
-func s:FooBar()
-endfunc
-
-" get({func}, {what} [, {default}])
-func Test_get_func()
- let l:F = function('tr')
- call assert_equal('tr', get(l:F, 'name'))
- call assert_equal(l:F, get(l:F, 'func'))
-
- let Fb_func = function('s:FooBar')
- call assert_match('<SNR>\d\+_FooBar', get(Fb_func, 'name'))
- let Fb_ref = funcref('s:FooBar')
- call assert_match('<SNR>\d\+_FooBar', get(Fb_ref, 'name'))
-
- call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'}))
- call assert_equal(0, get(l:F, 'dict'))
- call assert_equal([], get(l:F, 'args'))
-endfunc
-
-" get({partial}, {what} [, {default}]) - in test_partial.vim
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
deleted file mode 100644
index e369645328..0000000000
--- a/src/nvim/testdir/test_gf.vim
+++ /dev/null
@@ -1,278 +0,0 @@
-" Test for the gf and gF (goto file) commands
-
-" This is a test if a URL is recognized by "gf", with the cursor before and
-" after the "://". Also test ":\\".
-func Test_gf_url()
- enew!
- call append(0, [
- \ "first test for URL://machine.name/tmp/vimtest2a and other text",
- \ "second test for URL://machine.name/tmp/vimtest2b. And other text",
- \ "third test for URL:\\\\machine.name\\vimtest2c and other text",
- \ "fourth test for URL:\\\\machine.name\\tmp\\vimtest2d, and other text",
- \ "fifth test for URL://machine.name/tmp?q=vim&opt=yes and other text",
- \ "sixth test for URL://machine.name:1234?q=vim and other text",
- \ ])
- call cursor(1,1)
- call search("^first")
- call search("tmp")
- call assert_equal("URL://machine.name/tmp/vimtest2a", expand("<cfile>"))
- call search("^second")
- call search("URL")
- call assert_equal("URL://machine.name/tmp/vimtest2b", expand("<cfile>"))
- set isf=@,48-57,/,.,-,_,+,,,$,~,\
- call search("^third")
- call search("name")
- call assert_equal("URL:\\\\machine.name\\vimtest2c", expand("<cfile>"))
- call search("^fourth")
- call search("URL")
- call assert_equal("URL:\\\\machine.name\\tmp\\vimtest2d", expand("<cfile>"))
-
- call search("^fifth")
- call search("URL")
- call assert_equal("URL://machine.name/tmp?q=vim&opt=yes", expand("<cfile>"))
-
- call search("^sixth")
- call search("URL")
- call assert_equal("URL://machine.name:1234?q=vim", expand("<cfile>"))
-
- %d
- call setline(1, "demo://remote_file")
- wincmd f
- call assert_equal('demo://remote_file', @%)
- call assert_equal(2, winnr('$'))
- close!
-
- set isf&vim
- enew!
-endfunc
-
-func Test_gF()
- new
- call setline(1, ['111', '222', '333', '444'])
- w! Xfile
- close
- new
- set isfname-=:
- call setline(1, ['one', 'Xfile:3', 'three'])
- 2
- call assert_fails('normal gF', 'E37:')
- call assert_equal(2, getcurpos()[1])
- w! Xfile2
- normal 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])
-
- " jumping to the file/line with CTRL-W_F
- %bw!
- edit Xfile1
- call setline(1, ['one', 'Xfile:4', 'three'])
- exe "normal 2G\<C-W>F"
- call assert_equal('Xfile', bufname('%'))
- call assert_equal(4, getcurpos()[1])
-
- set isfname&
- call delete('Xfile')
- call delete('Xfile2')
- %bw!
-endfunc
-
-" Test for invoking 'gf' on a ${VAR} variable
-func Test_gf()
- set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
-
- call writefile(["Test for gf command"], "Xtest1")
- if has("unix")
- call writefile([" ${CDIR}/Xtest1"], "Xtestgf")
- else
- call writefile([" $TDIR/Xtest1"], "Xtestgf")
- endif
- new Xtestgf
- if has("unix")
- let $CDIR = "."
- /CDIR
- else
- if has("amiga")
- let $TDIR = "/testdir"
- else
- let $TDIR = "."
- endif
- /TDIR
- endif
-
- normal gf
- call assert_equal('Xtest1', fnamemodify(bufname(''), ":t"))
- close!
-
- call delete('Xtest1')
- call delete('Xtestgf')
-endfunc
-
-func Test_gf_visual()
- call writefile(['one', 'two', 'three', 'four'], "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('%'))
-
- " if multiple lines are selected, then gf should fail
- call setline(1, ["one", "two"])
- normal VGgf
- call assert_equal('Xtest_gf_visual', @%)
-
- " following line number is used for gF
- bwipe!
- new
- call setline(1, 'XXXtest_gf_visual:3XXX')
- norm! 0ttvt:gF
- call assert_equal('Xtest_gf_visual', bufname('%'))
- call assert_equal(3, getcurpos()[1])
-
- " do not include the NUL at the end
- call writefile(['x'], 'X')
- let save_enc = &enc
- " for enc in ['latin1', 'utf-8']
- for enc in ['utf-8']
- exe "set enc=" .. enc
- new
- call setline(1, 'X')
- set nomodified
- exe "normal \<C-V>$gf"
- call assert_equal('X', bufname())
- bwipe!
- endfor
- let &enc = save_enc
- call delete('X')
-
- " line number in visual area is used for file name
- if has('unix')
- bwipe!
- call writefile([], "Xtest_gf_visual:3")
- new
- call setline(1, 'XXXtest_gf_visual:3XXX')
- norm! 0ttvtXgF
- call assert_equal('Xtest_gf_visual:3', bufname('%'))
- call delete('Xtest_gf_visual:3')
- endif
-
- 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:')
- call assert_fails('normal [f', 'E447:')
-
- " gf is not allowed when text is locked
- au InsertCharPre <buffer> normal! gF<CR>
- let caught_e565 = 0
- try
- call feedkeys("ix\<esc>", 'xt')
- catch /^Vim\%((\a\+)\)\=:E565/ " catch E565
- let caught_e565 = 1
- endtry
- call assert_equal(1, caught_e565)
- au! InsertCharPre
-
- bwipe!
-
- " gf is not allowed when buffer is locked
- new
- augroup Test_gf
- au!
- au OptionSet diff norm! gf
- augroup END
- call setline(1, ['Xfile1', 'line2', 'line3', 'line4'])
- " Nvim does not support test_override()
- " call test_override('starting', 1)
- " call assert_fails('diffthis', 'E788:')
- " call test_override('starting', 0)
- augroup Test_gf
- au!
- augroup END
- bw!
-endfunc
-
-" If a file is not found by 'gf', then 'includeexpr' should be used to locate
-" the file.
-func Test_gf_includeexpr()
- new
- let g:Inc_fname = ''
- func IncFunc()
- let g:Inc_fname = v:fname
- return v:fname
- endfunc
- setlocal includeexpr=IncFunc()
- call setline(1, 'somefile.java')
- call assert_fails('normal gf', 'E447:')
- call assert_equal('somefile.java', g:Inc_fname)
- close!
- delfunc IncFunc
-endfunc
-
-" Test for using a script-local function for 'includeexpr'
-func Test_includeexpr_scriptlocal_func()
- func! s:IncludeFunc()
- let g:IncludeFname = v:fname
- return ''
- endfunc
- set includeexpr=s:IncludeFunc()
- call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
- new | only
- call setline(1, 'TestFile1')
- let g:IncludeFname = ''
- call assert_fails('normal! gf', 'E447:')
- call assert_equal('TestFile1', g:IncludeFname)
- bw!
- set includeexpr=<SID>IncludeFunc()
- call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
- new | only
- call setline(1, 'TestFile2')
- let g:IncludeFname = ''
- call assert_fails('normal! gf', 'E447:')
- call assert_equal('TestFile2', g:IncludeFname)
- set includeexpr&
- delfunc s:IncludeFunc
- bw!
-endfunc
-
-" Check that expanding directories can handle more than 255 entries.
-func Test_gf_subdirs_wildcard()
- let cwd = getcwd()
- let dir = 'Xtestgf_dir'
- call mkdir(dir)
- call chdir(dir)
- for i in range(300)
- call mkdir(i)
- call writefile([], i .. '/' .. i, 'S')
- endfor
- set path=./**
-
- new | only
- call setline(1, '99')
- w! Xtest1
- normal gf
- call assert_equal('99', fnamemodify(bufname(''), ":t"))
-
- call chdir(cwd)
- call delete(dir, 'rf')
- set path&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_glob2regpat.vim b/src/nvim/testdir/test_glob2regpat.vim
deleted file mode 100644
index a423a4a9f0..0000000000
--- a/src/nvim/testdir/test_glob2regpat.vim
+++ /dev/null
@@ -1,32 +0,0 @@
-" Test glob2regpat()
-
-func Test_glob2regpat_invalid()
- if has('float')
- call assert_fails('call glob2regpat(1.33)', 'E806:')
- endif
- call assert_fails('call glob2regpat("}")', 'E219:')
- call assert_fails('call glob2regpat("{")', 'E220:')
-endfunc
-
-func Test_glob2regpat_valid()
- call assert_equal('^foo\.', glob2regpat('foo.*'))
- call assert_equal('^foo.$', 'foo?'->glob2regpat())
- call assert_equal('\.vim$', glob2regpat('*.vim'))
- call assert_equal('^[abc]$', glob2regpat('[abc]'))
- call assert_equal('^foo bar$', glob2regpat('foo\ bar'))
- call assert_equal('^foo,bar$', glob2regpat('foo,bar'))
- call assert_equal('^\(foo\|bar\)$', glob2regpat('{foo,bar}'))
- call assert_equal('.*', glob2regpat('**'))
-
- if exists('+shellslash')
- call assert_equal('^foo[\/].$', glob2regpat('foo\?'))
- call assert_equal('^\(foo[\/]\|bar\|foobar\)$', glob2regpat('{foo\,bar,foobar}'))
- call assert_equal('^[\/]\(foo\|bar[\/]\)$', glob2regpat('\{foo,bar\}'))
- call assert_equal('^[\/][\/]\(foo\|bar[\/][\/]\)$', glob2regpat('\\{foo,bar\\}'))
- else
- call assert_equal('^foo?$', glob2regpat('foo\?'))
- call assert_equal('^\(foo,bar\|foobar\)$', glob2regpat('{foo\,bar,foobar}'))
- call assert_equal('^{foo,bar}$', glob2regpat('\{foo,bar\}'))
- call assert_equal('^\\\(foo\|bar\\\)$', glob2regpat('\\{foo,bar\\}'))
- endif
-endfunc
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
deleted file mode 100644
index 44a8784348..0000000000
--- a/src/nvim/testdir/test_global.vim
+++ /dev/null
@@ -1,131 +0,0 @@
-" Test for :global and :vglobal
-
-source check.vim
-source term_util.vim
-
-func Test_yank_put_clipboard()
- new
- call setline(1, ['a', 'b', 'c'])
- set clipboard=unnamed
- g/^/normal yyp
- call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
- set clipboard=unnamed,unnamedplus
- call setline(1, ['a', 'b', 'c'])
- g/^/normal yyp
- call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
- set clipboard&
- bwipe!
-endfunc
-
-func Test_global_set_clipboard()
- CheckFeature clipboard_working
- new
- set clipboard=unnamedplus
- let @+='clipboard' | g/^/set cb= | let @" = 'unnamed' | put
- call assert_equal(['','unnamed'], getline(1, '$'))
- set clipboard&
- bwipe!
-endfunc
-
-func Test_nested_global()
- new
- call setline(1, ['nothing', 'found', 'found bad', 'bad'])
- call assert_fails('g/found/3v/bad/s/^/++/', 'E147')
- g/found/v/bad/s/^/++/
- call assert_equal(['nothing', '++found', 'found bad', 'bad'], getline(1, 4))
- bwipe!
-endfunc
-
-func Test_global_error()
- call assert_fails('g\\a', 'E10:')
- call assert_fails('g', 'E148:')
- call assert_fails('g/\(/y', 'E54:')
-endfunc
-
-" Test for printing lines using :g with different search patterns
-func Test_global_print()
- new
- call setline(1, ['foo', 'bar', 'foo', 'foo'])
- let @/ = 'foo'
- let t = execute("g/")->trim()->split("\n")
- call assert_equal(['foo', 'foo', 'foo'], t)
-
- " Test for Vi compatible patterns
- let @/ = 'bar'
- let t = execute('g\/')->trim()->split("\n")
- call assert_equal(['bar'], t)
-
- normal gg
- s/foo/foo/
- let t = execute('g\&')->trim()->split("\n")
- call assert_equal(['foo', 'foo', 'foo'], t)
-
- let @/ = 'bar'
- let t = execute('g?')->trim()->split("\n")
- call assert_equal(['bar'], t)
-
- " Test for the 'Pattern found in every line' message
- let v:statusmsg = ''
- v/foo\|bar/p
- call assert_notequal('', v:statusmsg)
-
- close!
-endfunc
-
-func Test_global_empty_pattern()
- " populate history
- silent g/hello/
-
- redir @a
- g//
- redir END
-
- call assert_match('Pattern not found: hello', @a)
- " ^~~~~ this was previously empty
-endfunc
-
-" Test for global command with newline character
-func Test_global_newline()
- new
- call setline(1, ['foo'])
- exe "g/foo/s/f/h/\<NL>s/o$/w/"
- call assert_equal('how', getline(1))
- call setline(1, ["foo\<NL>bar"])
- exe "g/foo/s/foo\\\<NL>bar/xyz/"
- call assert_equal('xyz', getline(1))
- close!
-endfunc
-
-func Test_wrong_delimiter()
- call assert_fails('g x^bxd', 'E146:')
-endfunc
-
-" Test for interrupting :global using Ctrl-C
-func Test_interrupt_global()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- cnoremap ; <Cmd>sleep 10<CR>
- call setline(1, repeat(['foo'], 5))
- END
- call writefile(lines, 'Xtest_interrupt_global')
- let buf = RunVimInTerminal('-S Xtest_interrupt_global', {'rows': 6})
-
- call term_sendkeys(buf, ":g/foo/norm :\<C-V>;\<CR>")
- " Wait for :sleep to start
- call TermWait(buf, 100)
- call term_sendkeys(buf, "\<C-C>")
- call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 6))}, 1000)
-
- " Also test in Ex mode
- call term_sendkeys(buf, "gQg/foo/norm :\<C-V>;\<CR>")
- " Wait for :sleep to start
- call TermWait(buf, 100)
- call term_sendkeys(buf, "\<C-C>")
- call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 5))}, 1000)
-
- call StopVimInTerminal(buf)
- call delete('Xtest_interrupt_global')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
deleted file mode 100644
index c4a41a6742..0000000000
--- a/src/nvim/testdir/test_gn.vim
+++ /dev/null
@@ -1,221 +0,0 @@
-" Test for gn command
-
-func Test_gn_command()
- noautocmd new
- " replace a single char by itself quoted:
- call setline('.', 'abc x def x ghi x jkl')
- let @/ = 'x'
- exe "norm! cgn'x'\<esc>.."
- call assert_equal("abc 'x' def 'x' ghi 'x' jkl", getline('.'))
- sil! %d_
-
- " simple search match
- call setline('.', 'foobar')
- let @/ = 'foobar'
- exe "norm! gncsearchmatch"
- call assert_equal('searchmatch', getline('.'))
- sil! %d _
-
- " replace a multi-line match
- call setline('.', ['', 'one', 'two'])
- let @/ = 'one\_s*two\_s'
- exe "norm! gnceins\<CR>zwei"
- call assert_equal(['','eins','zwei'], getline(1,'$'))
- sil! %d _
-
- " test count argument
- call setline('.', ['', 'abcdx | abcdx | abcdx'])
- let @/ = '[a]bcdx'
- exe "norm! 2gnd"
- call assert_equal(['','abcdx | | abcdx'], getline(1,'$'))
- sil! %d _
-
- " join lines
- call setline('.', ['join ', 'lines'])
- let @/ = '$'
- exe "norm! 0gnd"
- call assert_equal(['join lines'], getline(1,'$'))
- sil! %d _
-
- " zero-width match
- call setline('.', ['', 'zero width pattern'])
- let @/ = '\>\zs'
- exe "norm! 0gnd"
- call assert_equal(['', 'zerowidth pattern'], getline(1,'$'))
- sil! %d _
-
- " delete first and last chars
- call setline('.', ['delete first and last chars'])
- let @/ = '^'
- exe "norm! 0gnd$"
- let @/ = '\zs'
- exe "norm! gnd"
- call assert_equal(['elete first and last char'], getline(1,'$'))
- sil! %d _
-
- " using visual mode
- call setline('.', ['', 'uniquepattern uniquepattern'])
- exe "norm! /[u]niquepattern/s\<cr>vlgnd"
- call assert_equal(['', ' uniquepattern'], getline(1,'$'))
- sil! %d _
-
- " backwards search
- call setline('.', ['my very excellent mother just served us nachos'])
- let @/ = 'mother'
- exe "norm! $cgNmongoose"
- call assert_equal(['my very excellent mongoose just served us nachos'], getline(1,'$'))
- sil! %d _
-
- " search for single char
- call setline('.', ['','for (i=0; i<=10; i++)'])
- let @/ = 'i'
- exe "norm! cgnj"
- call assert_equal(['','for (j=0; i<=10; i++)'], getline(1,'$'))
- sil! %d _
-
- " search hex char
- call setline('.', ['','Y'])
- set noignorecase
- let @/ = '\%x59'
- exe "norm! gnd"
- call assert_equal(['',''], getline(1,'$'))
- sil! %d _
-
- " test repeating gdn
- call setline('.', ['', '1', 'Johnny', '2', 'Johnny', '3'])
- let @/ = 'Johnny'
- exe "norm! dgn."
- call assert_equal(['','1', '', '2', '', '3'], getline(1,'$'))
- sil! %d _
-
- " test repeating gUgn
- call setline('.', ['', '1', 'Depp', '2', 'Depp', '3'])
- let @/ = 'Depp'
- exe "norm! gUgn."
- call assert_equal(['', '1', 'DEPP', '2', 'DEPP', '3'], getline(1,'$'))
- sil! %d _
-
- " test using look-ahead assertions
- call setline('.', ['a:10', '', 'a:1', '', 'a:20'])
- let @/ = 'a:0\@!\zs\d\+'
- exe "norm! 2nygno\<esc>p"
- call assert_equal(['a:10', '', 'a:1', '1', '', 'a:20'], getline(1,'$'))
- sil! %d _
-
- " test using nowrapscan
- set nowrapscan
- call setline(1, 'foo bar baz')
- exe "norm! /bar/e\<cr>"
- exe "norm! gnd"
- call assert_equal(['foo baz'], getline(1,'$'))
- sil! %d_
-
- " search upwards with nowrapscan set
- call setline('.', ['foo', 'bar', 'foo', 'baz'])
- set nowrapscan
- let @/ = 'foo'
- $
- norm! dgN
- call assert_equal(['foo', 'bar', '', 'baz'], getline(1,'$'))
- sil! %d_
-
- " search using the \zs atom
- call setline(1, [' nnoremap', '' , 'nnoremap'])
- set wrapscan&vim
- let @/ = '\_s\zsnnoremap'
- $
- norm! cgnmatch
- 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
-
- " Without 'wrapscan', in visual mode, running gn without a match should fail
- " but the visual mode should be kept.
- set nowrapscan
- call setline('.', 'one two')
- let @/ = 'one'
- call assert_beeps('normal 0wvlgn')
- exe "normal y"
- call assert_equal('tw', @")
-
- " with exclusive selection, run gn and gN
- set selection=exclusive
- normal 0gny
- call assert_equal('one', @")
- normal 0wgNy
- call assert_equal('one', @")
- set selection&
-endfunc
-
-func Test_gN_repeat()
- new
- call setline(1, 'this list is a list with a list of a list.')
- /list
- normal $gNgNgNx
- call assert_equal('list with a list of a list', @")
- bwipe!
-endfunc
-
-func Test_gN_then_gn()
- new
-
- call setline(1, 'this list is a list with a list of a last.')
- /l.st
- normal $gNgNgnx
- call assert_equal('last', @")
-
- call setline(1, 'this list is a list with a lust of a last.')
- /l.st
- normal $gNgNgNgnx
- call assert_equal('lust of a last', @")
-
- bwipe!
-endfunc
-
-func Test_gn_multi_line()
- new
- call setline(1, [
- \ 'func Tm1()',
- \ ' echo "one"',
- \ 'endfunc',
- \ 'func Tm2()',
- \ ' echo "two"',
- \ 'endfunc',
- \ 'func Tm3()',
- \ ' echo "three"',
- \ 'endfunc',
- \])
- /\v^func Tm\d\(\)\n.*\zs".*"\ze$
- normal jgnrx
- call assert_equal(' echo xxxxx', getline(5))
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim
deleted file mode 100644
index 6d029ffda2..0000000000
--- a/src/nvim/testdir/test_goto.vim
+++ /dev/null
@@ -1,442 +0,0 @@
-" Test commands that jump somewhere.
-
-" Create a new buffer using "lines" and place the cursor on the word after the
-" first occurrence of return and invoke "cmd". The cursor should now be
-" positioned at the given line and col.
-func XTest_goto_decl(cmd, lines, line, col)
- new
- call setline(1, a:lines)
- /return/
- normal! W
- execute 'norm! ' . a:cmd
- call assert_equal(a:line, line('.'))
- call assert_equal(a:col, col('.'))
- quit!
-endfunc
-
-func Test_gD()
- 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< trim [CODE]
- int x;
-
- int func(int x)
- {
- return x;
- }
- [CODE]
-
- call XTest_goto_decl('gd', lines, 3, 14)
-endfunc
-
-" Using gd to jump to a declaration in a fold
-func Test_gd_with_fold()
- new
- let lines =<< trim END
- #define ONE 1
- #define TWO 2
- #define THREE 3
-
- TWO
- END
- call setline(1, lines)
- 1,3fold
- call feedkeys('Ggd', 'xt')
- call assert_equal(2, line('.'))
- call assert_equal(-1, foldclosedend(2))
- bw!
-endfunc
-
-func Test_gd_not_local()
- 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< 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 =<< trim [CODE]
- int func(void)
- {
- char *s = "x";
-
- return x;
- }
- [CODE]
-
- call XTest_goto_decl('gd', lines, 5, 10)
-endfunc
-
-" Check that setting 'cursorline' does not change curswant
-func Test_cursorline_keep_col()
- new
- call setline(1, ['long long long line', 'short line'])
- normal ggfi
- let pos = getcurpos()
- normal j
- set cursorline
- normal k
- call assert_equal(pos, getcurpos())
- bwipe!
- set nocursorline
-endfunc
-
-func Test_gd_local_block()
- 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
-
-func Test_motion_if_elif_else_endif()
- new
- let lines =<< trim END
- /* Test pressing % on #if, #else #elsif and #endif,
- * with nested #if
- */
- #if FOO
- /* ... */
- # if BAR
- /* ... */
- # endif
- #elif BAR
- /* ... */
- #else
- /* ... */
- #endif
-
- #define FOO 1
- END
- call setline(1, lines)
- /#if FOO
- norm %
- call assert_equal([9, 1], getpos('.')[1:2])
- norm %
- call assert_equal([11, 1], getpos('.')[1:2])
- norm %
- call assert_equal([13, 1], getpos('.')[1:2])
- norm %
- call assert_equal([4, 1], getpos('.')[1:2])
- /# if BAR
- norm $%
- call assert_equal([8, 1], getpos('.')[1:2])
- norm $%
- call assert_equal([6, 1], getpos('.')[1:2])
-
- " Test for [# and ]# command
- call cursor(5, 1)
- normal [#
- call assert_equal([4, 1], getpos('.')[1:2])
- call cursor(5, 1)
- normal ]#
- call assert_equal([9, 1], getpos('.')[1:2])
- call cursor(10, 1)
- normal [#
- call assert_equal([9, 1], getpos('.')[1:2])
- call cursor(10, 1)
- normal ]#
- call assert_equal([11, 1], getpos('.')[1:2])
-
- " Finding a match before the first line or after the last line should fail
- normal gg
- call assert_beeps('normal [#')
- normal G
- call assert_beeps('normal ]#')
-
- " Finding a match for a macro definition (#define) should fail
- normal G
- call assert_beeps('normal %')
-
- bw!
-endfunc
-
-func Test_motion_c_comment()
- new
- a
-/*
- * Test pressing % on beginning/end
- * of C comments.
- */
-/* Another comment */
-.
- norm gg0%
- call assert_equal([4, 3], getpos('.')[1:2])
- norm %
- call assert_equal([1, 1], getpos('.')[1:2])
- norm gg0l%
- call assert_equal([4, 3], getpos('.')[1:2])
- norm h%
- call assert_equal([1, 1], getpos('.')[1:2])
-
- norm G^
- norm %
- call assert_equal([5, 21], getpos('.')[1:2])
- norm %
- call assert_equal([5, 1], getpos('.')[1:2])
-
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_gui.vim b/src/nvim/testdir/test_gui.vim
deleted file mode 100644
index c3f1f3163a..0000000000
--- a/src/nvim/testdir/test_gui.vim
+++ /dev/null
@@ -1,43 +0,0 @@
-
-func Test_colorscheme()
- " call assert_equal('16777216', &t_Co)
-
- let colorscheme_saved = exists('g:colors_name') ? g:colors_name : 'default'
- let g:color_count = 0
- augroup TestColors
- au!
- au ColorScheme * let g:color_count += 1
- \ | let g:after_colors = g:color_count
- \ | let g:color_after = expand('<amatch>')
- au ColorSchemePre * let g:color_count += 1
- \ | let g:before_colors = g:color_count
- \ | let g:color_pre = expand('<amatch>')
- augroup END
-
- colorscheme torte
- redraw!
- call assert_equal('dark', &background)
- call assert_equal(1, g:before_colors)
- call assert_equal(2, g:after_colors)
- call assert_equal('torte', g:color_pre)
- call assert_equal('torte', g:color_after)
- call assert_equal("\ntorte", execute('colorscheme'))
-
- let a = substitute(execute('hi Search'), "\n\\s\\+", ' ', 'g')
- " FIXME: temporarily check less while the colorscheme changes
- " call assert_match("\nSearch xxx term=reverse cterm=reverse ctermfg=196 ctermbg=16 gui=reverse guifg=#ff0000 guibg=#000000", a)
- " call assert_match("\nSearch xxx term=reverse ", a)
-
- call assert_fails('colorscheme does_not_exist', 'E185:')
- call assert_equal('does_not_exist', g:color_pre)
- call assert_equal('torte', g:color_after)
-
- exec 'colorscheme' colorscheme_saved
- augroup TestColors
- au!
- augroup END
- unlet g:color_count g:after_colors g:before_colors
- redraw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
deleted file mode 100644
index 08dd3dcb9a..0000000000
--- a/src/nvim/testdir/test_help.vim
+++ /dev/null
@@ -1,229 +0,0 @@
-" Tests for :help
-
-source check.vim
-source vim9.vim
-
-func SetUp()
- let s:vimruntime = $VIMRUNTIME
- let s:runtimepath = &runtimepath
- " Set $VIMRUNTIME to $BUILD_DIR/runtime and remove the original $VIMRUNTIME
- " path from &runtimepath so that ":h local-additions" won't pick up builtin
- " help files.
- let $VIMRUNTIME = expand($BUILD_DIR) .. '/runtime'
- set runtimepath-=../../../runtime
-endfunc
-
-func TearDown()
- let $VIMRUNTIME = s:vimruntime
- let &runtimepath = s:runtimepath
-endfunc
-
-func Test_help_restore_snapshot()
- help
- set buftype=
- help
- edit x
- help
- helpclose
-endfunc
-
-func Test_help_restore_snapshot_split()
- " Squeeze the unnamed buffer, Xfoo and the help one side-by-side and focus
- " the first one before calling :help.
- let bnr = bufnr()
- botright vsp Xfoo
- wincmd h
- help
- wincmd L
- let g:did_bufenter = v:false
- augroup T
- au!
- au BufEnter Xfoo let g:did_bufenter = v:true
- augroup END
- helpclose
- augroup! T
- " We're back to the unnamed buffer.
- call assert_equal(bnr, bufnr())
- " No BufEnter was triggered for Xfoo.
- call assert_equal(v:false, g:did_bufenter)
-
- close!
- bwipe!
-endfunc
-
-func Test_help_errors()
- call assert_fails('help doesnotexist', 'E149:')
- call assert_fails('help!', 'E478:')
- if has('multi_lang')
- call assert_fails('help help@xy', 'E661:')
- endif
-
- let save_hf = &helpfile
- set helpfile=help_missing
- help
- call assert_equal(1, winnr('$'))
- call assert_notequal('help', &buftype)
- let &helpfile = save_hf
-
- call assert_fails('help ' . repeat('a', 1048), 'E149:')
-
- new
- set keywordprg=:help
- call setline(1, " ")
- call assert_fails('normal VK', 'E349:')
- 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
- call setline(1, " Visual ")
- normal VK
- call assert_match('^Visual mode', getline('.'))
- call assert_equal('help', &ft)
- close
- bwipe!
-endfunc
-
-func Test_help_local_additions()
- call mkdir('Xruntime/doc', 'p')
- call writefile(['*mydoc.txt* my awesome doc'], 'Xruntime/doc/mydoc.txt')
- call writefile(['*mydoc-ext.txt* my extended awesome doc'], 'Xruntime/doc/mydoc-ext.txt')
- let rtp_save = &rtp
- set rtp+=./Xruntime
- help local-additions
- let lines = getline(line(".") + 1, search("^$") - 1)
- call assert_equal([
- \ '|mydoc-ext.txt| my extended awesome doc',
- \ '|mydoc.txt| my awesome doc'
- \ ], lines)
- call delete('Xruntime/doc/mydoc-ext.txt')
- close
-
- call mkdir('Xruntime-ja/doc', 'p')
- call writefile(["local-additions\thelp.jax\t/*local-additions*"], 'Xruntime-ja/doc/tags-ja')
- call writefile(['*help.txt* This is jax file', '',
- \ 'LOCAL ADDITIONS: *local-additions*', ''], 'Xruntime-ja/doc/help.jax')
- call writefile(['*work.txt* This is jax file'], 'Xruntime-ja/doc/work.jax')
- call writefile(['*work2.txt* This is jax file'], 'Xruntime-ja/doc/work2.jax')
- set rtp+=./Xruntime-ja
-
- help local-additions@en
- let lines = getline(line(".") + 1, search("^$") - 1)
- call assert_equal([
- \ '|mydoc.txt| my awesome doc'
- \ ], lines)
- close
-
- help local-additions@ja
- let lines = getline(line(".") + 1, search("^$") - 1)
- call assert_equal([
- \ '|mydoc.txt| my awesome doc',
- \ '|help.txt| This is jax file',
- \ '|work.txt| This is jax file',
- \ '|work2.txt| This is jax file',
- \ ], lines)
- close
-
- call delete('Xruntime', 'rf')
- call delete('Xruntime-ja', 'rf')
- let &rtp = rtp_save
-endfunc
-
-func Test_help_completion()
- call feedkeys(":help :undo\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"help :undo :undoj :undol :undojoin :undolist', @:)
-endfunc
-
-" Test for the :helptags command
-" NOTE: if you run tests as root this will fail. Don't run tests as root!
-func Test_helptag_cmd()
- call mkdir('Xdir/a/doc', 'p')
-
- " No help file to process in the directory
- call assert_fails('helptags Xdir', 'E151:')
-
- call writefile([], 'Xdir/a/doc/sample.txt')
-
- " Test for ++t argument
- helptags ++t Xdir
- call assert_equal(["help-tags\ttags\t1"], readfile('Xdir/tags'))
- call delete('Xdir/tags')
-
- " Test parsing tags
- call writefile(['*tag1*', 'Example: >', ' *notag*', 'Example end: *tag2*'],
- \ 'Xdir/a/doc/sample.txt')
- helptags Xdir
- call assert_equal(["tag1\ta/doc/sample.txt\t/*tag1*",
- \ "tag2\ta/doc/sample.txt\t/*tag2*"], readfile('Xdir/tags'))
-
- " Duplicate tags in the help file
- call writefile(['*tag1*', '*tag1*', '*tag2*'], 'Xdir/a/doc/sample.txt')
- call assert_fails('helptags Xdir', 'E154:')
-
- call delete('Xdir', 'rf')
-endfunc
-
-func Test_helptag_cmd_readonly()
- CheckUnix
- CheckNotRoot
-
- " Read-only tags file
- call mkdir('Xdir/doc', 'p')
- call writefile([''], 'Xdir/doc/tags')
- call writefile([], 'Xdir/doc/sample.txt')
- call setfperm('Xdir/doc/tags', 'r-xr--r--')
- call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags'))
-
- let rtp = &rtp
- let &rtp = 'Xdir'
- helptags ALL
- let &rtp = rtp
-
- call delete('Xdir/doc/tags')
-
- " No permission to read the help file
- call mkdir('Xdir/b/doc', 'p')
- call writefile([], 'Xdir/b/doc/sample.txt')
- call setfperm('Xdir/b/doc/sample.txt', '-w-------')
- call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/b/doc/sample.txt'))
- call delete('Xdir', 'rf')
-endfunc
-
-" Test for setting the 'helpheight' option in the help window
-func Test_help_window_height()
- let &cmdheight = &lines - 23
- set helpheight=10
- help
- set helpheight=14
- call assert_equal(14, winheight(0))
- set helpheight& cmdheight=1
- close
-endfunc
-
-func Test_help_long_argument()
- try
- exe 'help \%' .. repeat('0', 1021)
- catch
- call assert_match("E149:", v:exception)
- endtry
-endfunc
-
-func Test_help_using_visual_match()
- let lines =<< trim END
- call setline(1, ' ')
- /^
- exe "normal \<C-V>\<C-V>"
- h5\%V€]
- END
- call CheckScriptFailure(lines, 'E149:')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
deleted file mode 100644
index eae1a241e3..0000000000
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ /dev/null
@@ -1,322 +0,0 @@
-" Tests for :help! {subject}
-
-func Test_help_tagjump()
- help
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*help.txt\*')
- helpclose
-
- help |
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*bar\*')
- helpclose
-
- help "
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*quote\*')
- helpclose
-
- help *
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*star\*')
- helpclose
-
- help "*
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*quotestar\*')
- helpclose
-
- " help sm?le
- help ch?ckhealth
- call assert_equal("help", &filetype)
- " call assert_true(getline('.') =~ '\*:smile\*')
- call assert_true(getline('.') =~ '\*:checkhealth\*')
- helpclose
-
- help ??
- call assert_equal("help", &filetype)
- " *??* tag needs patch 8.2.1794
- " call assert_true(getline('.') =~ '\*??\*')
- call assert_true(getline('.') =~ '\*g??\*')
- helpclose
-
- help :?
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*:?\*')
- helpclose
-
- help q?
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*q?\*')
- call assert_true(expand('<cword>') == 'q?')
- helpclose
-
- help -?
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*-?\*')
- helpclose
-
- help v_g?
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*v_g?\*')
- helpclose
-
- help expr-!=?
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*expr-!=?\*')
- call assert_true(expand('<cword>') == 'expr-!=?')
- helpclose
-
- help expr-isnot?
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*expr-isnot?\*')
- call assert_true(expand('<cword>') == 'expr-isnot?')
- helpclose
-
- help FileW*Post
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*FileWritePost\*')
- helpclose
-
- help `ls`
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*:ls\*')
- helpclose
-
- help ^X
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*CTRL-X\*')
- helpclose
-
- help i_^_CTRL-D
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*i_^_CTRL-D\*')
- helpclose
-
- help i^x^y
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*i_CTRL-X_CTRL-Y\*')
- helpclose
-
- exe "help i\<C-\>\<C-G>"
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*i_CTRL-\\_CTRL-G\*')
- helpclose
-
- exec "help \<C-V>"
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*CTRL-V\*')
- helpclose
-
- help /\|
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*/\\bar\*')
- helpclose
-
- help \_$
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*/\\_$\*')
- helpclose
-
- help CTRL-\_CTRL-N
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*')
- helpclose
-
- help `:pwd`,
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*:pwd\*')
- helpclose
-
- help `:ls`.
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*:ls\*')
- helpclose
-
- exec "help! ('textwidth'"
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ "\\*'textwidth'\\*")
- helpclose
-
- exec "help! ('buflisted'),"
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ "\\*'buflisted'\\*")
- helpclose
-
- exec "help! abs({expr})"
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*abs()\*')
- helpclose
-
- exec "help! arglistid([{winnr})"
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*arglistid()\*')
- helpclose
-
- exec "help! 'autoindent'."
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ "\\*'autoindent'\\*")
- helpclose
-
- exec "help! {address}."
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*{address}\*')
- helpclose
-
- " Use special patterns in the help tag
- for h in ['/\w', '/\%^', '/\%(', '/\zs', '/\@<=', '/\_$', '[++opt]', '/\{']
- exec "help! " . h
- call assert_equal("help", &filetype)
- let pat = '\*' . escape(h, '\$[') . '\*'
- call assert_true(getline('.') =~ pat, pat)
- helpclose
- endfor
-
- exusage
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*:index\*')
- helpclose
-
- viusage
- call assert_equal("help", &filetype)
- call assert_true(getline('.') =~ '\*normal-index\*')
- helpclose
-endfunc
-
-let s:langs = ['en', 'ab', 'ja']
-
-func s:doc_config_setup()
- let s:helpfile_save = &helpfile
- let &helpfile="Xdir1/doc-en/doc/testdoc.txt"
- let s:rtp_save = &rtp
- let &rtp="Xdir1/doc-en"
- if has('multi_lang')
- let s:helplang_save=&helplang
- endif
-
- call delete('Xdir1', 'rf')
-
- for lang in s:langs
- if lang ==# 'en'
- let tagfname = 'tags'
- let docfname = 'testdoc.txt'
- else
- let tagfname = 'tags-' . lang
- let docfname = 'testdoc.' . lang . 'x'
- endif
- let docdir = "Xdir1/doc-" . lang . "/doc"
- call mkdir(docdir, "p")
- call writefile(["\t*test-char*", "\t*test-col*"], docdir . '/' . docfname)
- call writefile(["test-char\t" . docfname . "\t/*test-char*",
- \ "test-col\t" . docfname . "\t/*test-col*"],
- \ docdir . '/' . tagfname)
- endfor
-endfunc
-
-func s:doc_config_teardown()
- call delete('Xdir1', 'rf')
-
- let &helpfile = s:helpfile_save
- let &rtp = s:rtp_save
- if has('multi_lang')
- let &helplang = s:helplang_save
- endif
-endfunc
-
-func s:get_help_compl_list(cmd)
- return getcompletion(a:cmd, 'help')
-endfunc
-
-func Test_help_complete()
- try
- let list = []
- call s:doc_config_setup()
-
- " 'helplang=' and help file lang is 'en'
- if has('multi_lang')
- set helplang=
- endif
- let list = s:get_help_compl_list("test")
- call assert_equal(['test-col', 'test-char'], list)
-
- if has('multi_lang')
- " 'helplang=ab' and help file lang is 'en'
- set helplang=ab
- let list = s:get_help_compl_list("test")
- call assert_equal(['test-col', 'test-char'], list)
-
- " 'helplang=' and help file lang is 'en' and 'ab'
- set rtp+=Xdir1/doc-ab
- set helplang=
- let list = s:get_help_compl_list("test")
- call assert_equal(sort(['test-col@en', 'test-col@ab',
- \ 'test-char@en', 'test-char@ab']), sort(list))
-
- " 'helplang=ab' and help file lang is 'en' and 'ab'
- set helplang=ab
- let list = s:get_help_compl_list("test")
- call assert_equal(sort(['test-col', 'test-col@en',
- \ 'test-char', 'test-char@en']), sort(list))
-
- " 'helplang=' and help file lang is 'en', 'ab' and 'ja'
- set rtp+=Xdir1/doc-ja
- set helplang=
- let list = s:get_help_compl_list("test")
- call assert_equal(sort(['test-col@en', 'test-col@ab',
- \ 'test-col@ja', 'test-char@en',
- \ 'test-char@ab', 'test-char@ja']), sort(list))
-
- " 'helplang=ab' and help file lang is 'en', 'ab' and 'ja'
- set helplang=ab
- let list = s:get_help_compl_list("test")
- call assert_equal(sort(['test-col', 'test-col@en',
- \ 'test-col@ja', 'test-char',
- \ 'test-char@en', 'test-char@ja']), sort(list))
-
- " 'helplang=ab,ja' and help file lang is 'en', 'ab' and 'ja'
- set helplang=ab,ja
- let list = s:get_help_compl_list("test")
- call assert_equal(sort(['test-col', 'test-col@ja',
- \ 'test-col@en', 'test-char',
- \ 'test-char@ja', 'test-char@en']), sort(list))
- endif
- catch
- call assert_exception('X')
- finally
- call s:doc_config_teardown()
- endtry
-endfunc
-
-func Test_help_respect_current_file_lang()
- try
- let list = []
- call s:doc_config_setup()
-
- if has('multi_lang')
- function s:check_help_file_ext(help_keyword, ext)
- exec 'help ' . a:help_keyword
- call assert_equal(a:ext, expand('%:e'))
- call feedkeys("\<C-]>", 'tx')
- call assert_equal(a:ext, expand('%:e'))
- pop
- helpclose
- endfunc
-
- set rtp+=Xdir1/doc-ab
- set rtp+=Xdir1/doc-ja
-
- set helplang=ab
- call s:check_help_file_ext('test-char', 'abx')
- call s:check_help_file_ext('test-char@ja', 'jax')
- set helplang=ab,ja
- call s:check_help_file_ext('test-char@ja', 'jax')
- call s:check_help_file_ext('test-char@en', 'txt')
- endif
- catch
- call assert_exception('X')
- finally
- call s:doc_config_teardown()
- endtry
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_hide.vim b/src/nvim/testdir/test_hide.vim
deleted file mode 100644
index b3ce395523..0000000000
--- a/src/nvim/testdir/test_hide.vim
+++ /dev/null
@@ -1,97 +0,0 @@
-" Tests for :hide command/modifier and 'hidden' option
-
-func SetUp()
- let s:save_hidden = &hidden
- let s:save_bufhidden = &bufhidden
- let s:save_autowrite = &autowrite
- set nohidden
- set bufhidden=
- set noautowrite
-endfunc
-
-function TearDown()
- let &hidden = s:save_hidden
- let &bufhidden = s:save_bufhidden
- let &autowrite = s:save_autowrite
-endfunc
-
-function Test_hide()
- let orig_bname = bufname('')
- let orig_winnr = winnr('$')
-
- new Xf1
- set modified
- call assert_fails('edit Xf2')
- bwipeout! Xf1
-
- new Xf1
- set modified
- edit! Xf2
- call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
- call assert_equal([1, 0], [buflisted('Xf1'), bufloaded('Xf1')])
- bwipeout! Xf1
- bwipeout! Xf2
-
- new Xf1
- set modified
- " :hide as a command
- hide
- call assert_equal([orig_bname, orig_winnr], [bufname(''), winnr('$')])
- call assert_equal([1, 1], ['Xf1'->buflisted(), 'Xf1'->bufloaded()])
- bwipeout! Xf1
-
- new Xf1
- set modified
- " :hide as a command with trailing comment
- hide " comment
- call assert_equal([orig_bname, orig_winnr], [bufname(''), winnr('$')])
- call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
- bwipeout! Xf1
-
- new Xf1
- set modified
- " :hide as a command with bar
- hide | new Xf2 " comment
- call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
- call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
- bwipeout! Xf1
- bwipeout! Xf2
-
- new Xf1
- set modified
- " :hide as a modifier with trailing comment
- hide edit Xf2 " comment
- call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
- call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
- bwipeout! Xf1
- bwipeout! Xf2
-
- new Xf1
- set modified
- " To check that the bar is not recognized to separate commands
- hide echo "one|two"
- call assert_equal(['Xf1', 2], [bufname(''), winnr('$')])
- call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
- bwipeout! Xf1
-
- " set hidden
- new Xf1
- set hidden
- set modified
- edit Xf2 " comment
- call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
- call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
- bwipeout! Xf1
- bwipeout! Xf2
-
- " set hidden bufhidden=wipe
- new Xf1
- set bufhidden=wipe
- set modified
- hide edit! Xf2 " comment
- call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
- call assert_equal([0, 0], [buflisted('Xf1'), bufloaded('Xf1')])
- bwipeout! Xf2
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
deleted file mode 100644
index 8a102f2e65..0000000000
--- a/src/nvim/testdir/test_highlight.vim
+++ /dev/null
@@ -1,868 +0,0 @@
-" Tests for ":highlight" and highlighting.
-
-source view_util.vim
-source screendump.vim
-source check.vim
-source script_util.vim
-
-func Test_highlight()
- " basic test if ":highlight" doesn't crash
- highlight
- hi Search
-
- " test setting colors.
- " test clearing one color and all doesn't generate error or warning
- silent! hi NewGroup term=bold cterm=italic ctermfg=DarkBlue ctermbg=Grey gui= guifg=#00ff00 guibg=Cyan
- silent! hi Group2 term= cterm=
- hi Group3 term=underline cterm=bold
-
- let res = split(execute("hi NewGroup"), "\n")[0]
- " filter ctermfg and ctermbg, the numbers depend on the terminal
- let res = substitute(res, 'ctermfg=\d*', 'ctermfg=2', '')
- let res = substitute(res, 'ctermbg=\d*', 'ctermbg=3', '')
- call assert_equal("NewGroup xxx cterm=italic ctermfg=2 ctermbg=3",
- \ res)
- call assert_equal("Group2 xxx cleared",
- \ split(execute("hi Group2"), "\n")[0])
- call assert_equal("Group3 xxx cterm=bold",
- \ split(execute("hi Group3"), "\n")[0])
-
- hi clear NewGroup
- call assert_equal("NewGroup xxx cleared",
- \ split(execute("hi NewGroup"), "\n")[0])
- call assert_equal("Group2 xxx cleared",
- \ split(execute("hi Group2"), "\n")[0])
- hi Group2 NONE
- call assert_equal("Group2 xxx cleared",
- \ split(execute("hi Group2"), "\n")[0])
- hi clear
- call assert_equal("Group3 xxx cleared",
- \ split(execute("hi Group3"), "\n")[0])
- call assert_fails("hi Crash term='asdf", "E475:")
-endfunc
-
-func HighlightArgs(name)
- return 'hi ' . substitute(split(execute('hi ' . a:name), '\n')[0], '\<xxx\>', '', '')
-endfunc
-
-func IsColorable()
- return has('gui_running') || str2nr(&t_Co) >= 8
-endfunc
-
-func HiCursorLine()
- let hiCursorLine = HighlightArgs('CursorLine')
- if has('gui_running')
- let guibg = matchstr(hiCursorLine, 'guibg=\w\+')
- let hi_ul = 'hi CursorLine gui=underline guibg=NONE'
- let hi_bg = 'hi CursorLine gui=NONE ' . guibg
- else
- let hi_ul = 'hi CursorLine cterm=underline ctermbg=NONE'
- let hi_bg = 'hi CursorLine cterm=NONE ctermbg=Gray'
- endif
- return [hiCursorLine, hi_ul, hi_bg]
-endfunc
-
-func Check_lcs_eol_attrs(attrs, row, col)
- let save_lcs = &lcs
- set list
-
- call assert_equal(a:attrs, ScreenAttrs(a:row, a:col)[0])
-
- set nolist
- let &lcs = save_lcs
-endfunc
-
-func Test_highlight_eol_with_cursorline()
- let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
-
- call NewWindow('topleft 5', 20)
- call setline(1, 'abcd')
- call matchadd('Search', '\n')
-
- " expected:
- " 'abcd '
- " ^^^^ ^^^^^ no highlight
- " ^ 'Search' highlight
- let attrs0 = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs0[0]], 4), attrs0[0:3])
- call assert_equal(repeat([attrs0[0]], 5), attrs0[5:9])
- call assert_notequal(attrs0[0], attrs0[4])
-
- setlocal cursorline
-
- " underline
- exe hi_ul
-
- " expected:
- " 'abcd '
- " ^^^^ underline
- " ^ 'Search' highlight with underline
- " ^^^^^ underline
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
- call assert_equal([attrs[4]] + repeat([attrs[5]], 5), attrs[4:9])
- call assert_notequal(attrs[0], attrs[4])
- call assert_notequal(attrs[4], attrs[5])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_notequal(attrs0[4], attrs[4])
- call Check_lcs_eol_attrs(attrs, 1, 10)
-
- if IsColorable()
- " bg-color
- exe hi_bg
-
- " expected:
- " 'abcd '
- " ^^^^ bg-color of 'CursorLine'
- " ^ 'Search' highlight
- " ^^^^^ bg-color of 'CursorLine'
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
- call assert_equal(repeat([attrs[5]], 5), attrs[5:9])
- call assert_equal(attrs0[4], attrs[4])
- call assert_notequal(attrs[0], attrs[4])
- call assert_notequal(attrs[4], attrs[5])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_notequal(attrs0[5], attrs[5])
- call Check_lcs_eol_attrs(attrs, 1, 10)
- endif
-
- call CloseWindow()
- exe hiCursorLine
-endfunc
-
-func Test_highlight_eol_with_cursorline_vertsplit()
- let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
-
- call NewWindow('topleft 5', 5)
- call setline(1, 'abcd')
- call matchadd('Search', '\n')
-
- let expected = "abcd |abcd "
- let actual = ScreenLines(1, 15)[0]
- call assert_equal(expected, actual)
-
- " expected:
- " 'abcd |abcd '
- " ^^^^ ^^^^^^^^^ no highlight
- " ^ 'Search' highlight
- " ^ 'WinSeparator' highlight
- let attrs0 = ScreenAttrs(1, 15)[0]
- call assert_equal(repeat([attrs0[0]], 4), attrs0[0:3])
- call assert_equal(repeat([attrs0[0]], 9), attrs0[6:14])
- call assert_notequal(attrs0[0], attrs0[4])
- call assert_notequal(attrs0[0], attrs0[5])
- call assert_notequal(attrs0[4], attrs0[5])
-
- setlocal cursorline
-
- " expected:
- " 'abcd |abcd '
- " ^^^^ underline
- " ^ 'Search' highlight with underline
- " ^ 'WinSeparator' highlight
- " ^^^^^^^^^ no highlight
-
- " underline
- exe hi_ul
-
- let actual = ScreenLines(1, 15)[0]
- call assert_equal(expected, actual)
-
- let attrs = ScreenAttrs(1, 15)[0]
- call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
- call assert_equal(repeat([attrs[6]], 9), attrs[6:14])
- call assert_equal(attrs0[5:14], attrs[5:14])
- call assert_notequal(attrs[0], attrs[4])
- call assert_notequal(attrs[0], attrs[5])
- call assert_notequal(attrs[0], attrs[6])
- call assert_notequal(attrs[4], attrs[5])
- call assert_notequal(attrs[5], attrs[6])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_notequal(attrs0[4], attrs[4])
- call Check_lcs_eol_attrs(attrs, 1, 15)
-
- if IsColorable()
- " bg-color
- exe hi_bg
-
- let actual = ScreenLines(1, 15)[0]
- call assert_equal(expected, actual)
-
- let attrs = ScreenAttrs(1, 15)[0]
- call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
- call assert_equal(repeat([attrs[6]], 9), attrs[6:14])
- call assert_equal(attrs0[5:14], attrs[5:14])
- call assert_notequal(attrs[0], attrs[4])
- call assert_notequal(attrs[0], attrs[5])
- call assert_notequal(attrs[0], attrs[6])
- call assert_notequal(attrs[4], attrs[5])
- call assert_notequal(attrs[5], attrs[6])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_equal(attrs0[4], attrs[4])
- call Check_lcs_eol_attrs(attrs, 1, 15)
- endif
-
- call CloseWindow()
- exe hiCursorLine
-endfunc
-
-func Test_highlight_eol_with_cursorline_rightleft()
- if !has('rightleft')
- return
- endif
-
- let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
-
- call NewWindow('topleft 5', 10)
- setlocal rightleft
- call setline(1, 'abcd')
- call matchadd('Search', '\n')
- let attrs0 = ScreenAttrs(1, 10)[0]
-
- setlocal cursorline
-
- " underline
- exe hi_ul
-
- " expected:
- " ' dcba'
- " ^^^^ underline
- " ^ 'Search' highlight with underline
- " ^^^^^ underline
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[9]], 4), attrs[6:9])
- call assert_equal(repeat([attrs[4]], 5) + [attrs[5]], attrs[0:5])
- call assert_notequal(attrs[9], attrs[5])
- call assert_notequal(attrs[4], attrs[5])
- call assert_notequal(attrs0[9], attrs[9])
- call assert_notequal(attrs0[5], attrs[5])
- call Check_lcs_eol_attrs(attrs, 1, 10)
-
- if IsColorable()
- " bg-color
- exe hi_bg
-
- " expected:
- " ' dcba'
- " ^^^^ bg-color of 'CursorLine'
- " ^ 'Search' highlight
- " ^^^^^ bg-color of 'CursorLine'
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[9]], 4), attrs[6:9])
- call assert_equal(repeat([attrs[4]], 5), attrs[0:4])
- call assert_equal(attrs0[5], attrs[5])
- call assert_notequal(attrs[9], attrs[5])
- call assert_notequal(attrs[5], attrs[4])
- call assert_notequal(attrs0[9], attrs[9])
- call assert_notequal(attrs0[4], attrs[4])
- call Check_lcs_eol_attrs(attrs, 1, 10)
- endif
-
- call CloseWindow()
- exe hiCursorLine
-endfunc
-
-func Test_highlight_eol_with_cursorline_linewrap()
- let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
-
- call NewWindow('topleft 5', 10)
- call setline(1, [repeat('a', 51) . 'bcd', ''])
- call matchadd('Search', '\n')
-
- setlocal wrap
- normal! gg$
- let attrs0 = ScreenAttrs(5, 10)[0]
- setlocal cursorline
-
- " underline
- exe hi_ul
-
- " expected:
- " 'abcd '
- " ^^^^ underline
- " ^ 'Search' highlight with underline
- " ^^^^^ underline
- let attrs = ScreenAttrs(5, 10)[0]
- call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
- call assert_equal([attrs[4]] + repeat([attrs[5]], 5), attrs[4:9])
- call assert_notequal(attrs[0], attrs[4])
- call assert_notequal(attrs[4], attrs[5])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_notequal(attrs0[4], attrs[4])
- call Check_lcs_eol_attrs(attrs, 5, 10)
-
- if IsColorable()
- " bg-color
- exe hi_bg
-
- " expected:
- " 'abcd '
- " ^^^^ bg-color of 'CursorLine'
- " ^ 'Search' highlight
- " ^^^^^ bg-color of 'CursorLine'
- let attrs = ScreenAttrs(5, 10)[0]
- call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
- call assert_equal(repeat([attrs[5]], 5), attrs[5:9])
- call assert_equal(attrs0[4], attrs[4])
- call assert_notequal(attrs[0], attrs[4])
- call assert_notequal(attrs[4], attrs[5])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_notequal(attrs0[5], attrs[5])
- call Check_lcs_eol_attrs(attrs, 5, 10)
- endif
-
- setlocal nocursorline nowrap
- normal! gg$
- let attrs0 = ScreenAttrs(1, 10)[0]
- setlocal cursorline
-
- " underline
- exe hi_ul
-
- " expected:
- " 'aaabcd '
- " ^^^^^^ underline
- " ^ 'Search' highlight with underline
- " ^^^ underline
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[0]], 6), attrs[0:5])
- call assert_equal([attrs[6]] + repeat([attrs[7]], 3), attrs[6:9])
- call assert_notequal(attrs[0], attrs[6])
- call assert_notequal(attrs[6], attrs[7])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_notequal(attrs0[6], attrs[6])
- call Check_lcs_eol_attrs(attrs, 1, 10)
-
- if IsColorable()
- " bg-color
- exe hi_bg
-
- " expected:
- " 'aaabcd '
- " ^^^^^^ bg-color of 'CursorLine'
- " ^ 'Search' highlight
- " ^^^ bg-color of 'CursorLine'
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[0]], 6), attrs[0:5])
- call assert_equal(repeat([attrs[7]], 3), attrs[7:9])
- call assert_equal(attrs0[6], attrs[6])
- call assert_notequal(attrs[0], attrs[6])
- call assert_notequal(attrs[6], attrs[7])
- call assert_notequal(attrs0[0], attrs[0])
- call assert_notequal(attrs0[7], attrs[7])
- call Check_lcs_eol_attrs(attrs, 1, 10)
- endif
-
- call CloseWindow()
- exe hiCursorLine
-endfunc
-
-func Test_highlight_eol_with_cursorline_sign()
- if !has('signs')
- return
- endif
-
- let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
-
- call NewWindow('topleft 5', 10)
- call setline(1, 'abcd')
- call matchadd('Search', '\n')
-
- sign define Sign text=>>
- exe 'sign place 1 line=1 name=Sign buffer=' . bufnr('')
- let attrs0 = ScreenAttrs(1, 10)[0]
- setlocal cursorline
-
- " underline
- exe hi_ul
-
- " expected:
- " '>>abcd '
- " ^^ sign
- " ^^^^ underline
- " ^ 'Search' highlight with underline
- " ^^^ underline
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[2]], 4), attrs[2:5])
- call assert_equal([attrs[6]] + repeat([attrs[7]], 3), attrs[6:9])
- call assert_notequal(attrs[2], attrs[6])
- call assert_notequal(attrs[6], attrs[7])
- call assert_notequal(attrs0[2], attrs[2])
- call assert_notequal(attrs0[6], attrs[6])
- call Check_lcs_eol_attrs(attrs, 1, 10)
-
- if IsColorable()
- " bg-color
- exe hi_bg
-
- " expected:
- " '>>abcd '
- " ^^ sign
- " ^^^^ bg-color of 'CursorLine'
- " ^ 'Search' highlight
- " ^^^ bg-color of 'CursorLine'
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[2]], 4), attrs[2:5])
- call assert_equal(repeat([attrs[7]], 3), attrs[7:9])
- call assert_equal(attrs0[6], attrs[6])
- call assert_notequal(attrs[2], attrs[6])
- call assert_notequal(attrs[6], attrs[7])
- call assert_notequal(attrs0[2], attrs[2])
- call assert_notequal(attrs0[7], attrs[7])
- call Check_lcs_eol_attrs(attrs, 1, 10)
- endif
-
- sign unplace 1
- call CloseWindow()
- exe hiCursorLine
-endfunc
-
-func Test_highlight_eol_with_cursorline_breakindent()
- if !has('linebreak')
- return
- endif
-
- let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
-
- call NewWindow('topleft 5', 10)
- set showbreak=xxx
- setlocal breakindent breakindentopt=min:0,shift:1 showbreak=>
- call setline(1, ' ' . repeat('a', 9) . 'bcd')
- call matchadd('Search', '\n')
- let attrs0 = ScreenAttrs(2, 10)[0]
- setlocal cursorline
-
- " underline
- exe hi_ul
-
- " expected:
- " ' >bcd '
- " ^^^ breakindent and showbreak
- " ^^^ underline
- " ^ 'Search' highlight with underline
- " ^^^ underline
- let attrs = ScreenAttrs(2, 10)[0]
- call assert_equal(repeat([attrs[0]], 2), attrs[0:1])
- call assert_equal(repeat([attrs[3]], 3), attrs[3:5])
- call assert_equal([attrs[6]] + repeat([attrs[7]], 3), attrs[6:9])
- call assert_equal(attrs0[0], attrs[0])
- call assert_notequal(attrs[0], attrs[2])
- call assert_notequal(attrs[2], attrs[3])
- call assert_notequal(attrs[3], attrs[6])
- call assert_notequal(attrs[6], attrs[7])
- call assert_notequal(attrs0[2], attrs[2])
- call assert_notequal(attrs0[3], attrs[3])
- call assert_notequal(attrs0[6], attrs[6])
- call Check_lcs_eol_attrs(attrs, 2, 10)
-
- if IsColorable()
- " bg-color
- exe hi_bg
-
- " expected:
- " ' >bcd '
- " ^^^ breakindent and showbreak
- " ^^^ bg-color of 'CursorLine'
- " ^ 'Search' highlight
- " ^^^ bg-color of 'CursorLine'
- let attrs = ScreenAttrs(2, 10)[0]
- call assert_equal(repeat([attrs[0]], 2), attrs[0:1])
- call assert_equal(repeat([attrs[3]], 3), attrs[3:5])
- call assert_equal(repeat([attrs[7]], 3), attrs[7:9])
- call assert_equal(attrs0[0], attrs[0])
- call assert_equal(attrs0[6], attrs[6])
- call assert_notequal(attrs[0], attrs[2])
- call assert_notequal(attrs[2], attrs[3])
- call assert_notequal(attrs[3], attrs[6])
- call assert_notequal(attrs[6], attrs[7])
- call assert_notequal(attrs0[2], attrs[2])
- call assert_notequal(attrs0[3], attrs[3])
- call assert_notequal(attrs0[7], attrs[7])
- call Check_lcs_eol_attrs(attrs, 2, 10)
- endif
-
- call CloseWindow()
- set showbreak=
- setlocal showbreak=
- exe hiCursorLine
-endfunc
-
-func Test_highlight_eol_on_diff()
- call setline(1, ['abcd', ''])
- call matchadd('Search', '\n')
- let attrs0 = ScreenAttrs(1, 10)[0]
-
- diffthis
- botright new
- diffthis
-
- " expected:
- " ' abcd '
- " ^^ sign
- " ^^^^ ^^^ 'DiffAdd' highlight
- " ^ 'Search' highlight
- let attrs = ScreenAttrs(1, 10)[0]
- call assert_equal(repeat([attrs[0]], 2), attrs[0:1])
- call assert_equal(repeat([attrs[2]], 4), attrs[2:5])
- call assert_equal(repeat([attrs[2]], 3), attrs[7:9])
- call assert_equal(attrs0[4], attrs[6])
- call assert_notequal(attrs[0], attrs[2])
- call assert_notequal(attrs[0], attrs[6])
- call assert_notequal(attrs[2], attrs[6])
- call Check_lcs_eol_attrs(attrs, 1, 10)
-
- bwipe!
- diffoff
-endfunc
-
-func Test_termguicolors()
- if !exists('+termguicolors')
- return
- endif
- if has('vtp') && !has('vcon') && !has('gui_running')
- " Win32: 'guicolors' doesn't work without virtual console.
- call assert_fails('set termguicolors', 'E954:')
- return
- endif
-
- " Basic test that setting 'termguicolors' works with one color.
- set termguicolors
- redraw
- set t_Co=1
- redraw
- set t_Co=0
- redraw
-endfunc
-
-func Test_cursorline_after_yank()
- CheckScreendump
-
- call writefile([
- \ 'set cul rnu',
- \ 'call setline(1, ["","1","2","3",""])',
- \ ], 'Xtest_cursorline_yank')
- let buf = RunVimInTerminal('-S Xtest_cursorline_yank', {'rows': 8})
- call term_wait(buf)
- call term_sendkeys(buf, "Gy3k")
- call term_wait(buf)
- call term_sendkeys(buf, "jj")
-
- call VerifyScreenDump(buf, 'Test_cursorline_yank_01', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_cursorline_yank')
-endfunc
-
-" test for issue https://github.com/vim/vim/issues/4862
-func Test_put_before_cursorline()
- new
- only!
- call setline(1, 'A')
- redraw
- let std_attr = screenattr(1, 1)
- set cursorline
- redraw
- let cul_attr = screenattr(1, 1)
- normal yyP
- redraw
- " Line 1 has cursor so it should be highlighted with CursorLine.
- call assert_equal(cul_attr, screenattr(1, 1))
- " And CursorLine highlighting from the second line should be gone.
- call assert_equal(std_attr, screenattr(2, 1))
- set nocursorline
- bwipe!
-endfunc
-
-func Test_cursorline_with_visualmode()
- CheckScreendump
-
- call writefile([
- \ 'set cul',
- \ 'call setline(1, repeat(["abc"], 50))',
- \ ], 'Xtest_cursorline_with_visualmode')
- let buf = RunVimInTerminal('-S Xtest_cursorline_with_visualmode', {'rows': 12})
- call term_wait(buf)
- call term_sendkeys(buf, "V\<C-f>kkkjk")
-
- call VerifyScreenDump(buf, 'Test_cursorline_with_visualmode_01', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_cursorline_with_visualmode')
-endfunc
-
-func Test_cursorcolumn_insert_on_tab()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['123456789', "a\tb"])
- set cursorcolumn
- call cursor(2, 2)
- END
- call writefile(lines, 'Xcuc_insert_on_tab')
-
- let buf = RunVimInTerminal('-S Xcuc_insert_on_tab', #{rows: 8})
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_1', {})
-
- call term_sendkeys(buf, 'i')
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {})
-
- call term_sendkeys(buf, "\<C-O>")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_3', {})
-
- call term_sendkeys(buf, 'i')
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xcuc_insert_on_tab')
-endfunc
-
-func Test_cursorcolumn_callback()
- CheckScreendump
- CheckFeature timers
-
- let lines =<< trim END
- call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
- set cursorcolumn
- call cursor(4, 5)
-
- func Func(timer)
- call cursor(1, 1)
- endfunc
-
- call timer_start(300, 'Func')
- END
- call writefile(lines, 'Xcuc_timer')
-
- let buf = RunVimInTerminal('-S Xcuc_timer', #{rows: 8})
- call TermWait(buf, 310)
- call VerifyScreenDump(buf, 'Test_cursorcolumn_callback_1', {})
-
- call StopVimInTerminal(buf)
- call delete('Xcuc_timer')
-endfunc
-
-func Test_colorcolumn()
- CheckScreendump
-
- " check that setting 'colorcolumn' when entering a buffer works
- let lines =<< trim END
- split
- edit X
- call setline(1, ["1111111111","22222222222","3333333333"])
- set nomodified
- set colorcolumn=3,9
- set number cursorline cursorlineopt=number
- wincmd w
- buf X
- END
- call writefile(lines, 'Xtest_colorcolumn')
- let buf = RunVimInTerminal('-S Xtest_colorcolumn', {'rows': 10})
- call term_sendkeys(buf, ":\<CR>")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_colorcolumn_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_colorcolumn')
-endfunc
-
-func Test_colorcolumn_bri()
- CheckScreendump
-
- " check 'colorcolumn' when 'breakindent' is set
- let lines =<< trim END
- call setline(1, 'The quick brown fox jumped over the lazy dogs')
- END
- call writefile(lines, 'Xtest_colorcolumn_bri')
- let buf = RunVimInTerminal('-S Xtest_colorcolumn_bri', {'rows': 10,'columns': 40})
- call term_sendkeys(buf, ":set co=40 linebreak bri briopt=shift:2 cc=40,41,43\<CR>")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_colorcolumn_2', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_colorcolumn_bri')
-endfunc
-
-func Test_colorcolumn_sbr()
- CheckScreendump
-
- " check 'colorcolumn' when 'showbreak' is set
- let lines =<< trim END
- call setline(1, 'The quick brown fox jumped over the lazy dogs')
- END
- call writefile(lines, 'Xtest_colorcolumn_srb')
- let buf = RunVimInTerminal('-S Xtest_colorcolumn_srb', {'rows': 10,'columns': 40})
- call term_sendkeys(buf, ":set co=40 showbreak=+++>\\ cc=40,41,43\<CR>")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_colorcolumn_3', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_colorcolumn_srb')
-endfunc
-
-" 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()
- let hlNormal = HighlightArgs('Normal')
- if !has('gui_running')
- call assert_match('hi Normal\s*clear', hlNormal)
- elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
- " expect is DEFAULT_FONT of gui_gtk_x11.c
- call assert_match('hi Normal\s*font=Monospace 10', hlNormal)
- elseif has('gui_motif')
- " expect is DEFAULT_FONT of gui_x11.c
- call assert_match('hi Normal\s*font=7x13', hlNormal)
- elseif has('win32')
- " expect any font
- call assert_match('hi Normal\s*font=.*', hlNormal)
- endif
-endfunc
-
-" Do this test last, sometimes restoring the columns doesn't work
-func Test_z_no_space_before_xxx()
- " Note: we need to create this highlight group in the test because it does not exist in Neovim
- execute('hi StatusLineTermNC ctermfg=green')
- let l:org_columns = &columns
- set columns=17
- let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC')))
- call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC)
- let &columns = l:org_columns
-endfunc
-
-" Test for :highlight command errors
-func Test_highlight_cmd_errors()
- if has('gui_running') || has('nvim')
- " This test doesn't fail in the MS-Windows console version.
- call assert_fails('hi Xcomment ctermfg=fg', 'E419:')
- call assert_fails('hi Xcomment ctermfg=bg', 'E420:')
- call assert_fails('hi ' .. repeat('a', 201) .. ' ctermfg=black', 'E1249:')
- endif
-
- " Try using a very long terminal code. Define a dummy terminal code for this
- " test.
- let &t_fo = "\<Esc>1;"
- let c = repeat("t_fo,", 100) . "t_fo"
- " call assert_fails('exe "hi Xgroup1 start=" . c', 'E422:')
- let &t_fo = ""
-endfunc
-
-" Test for using RGB color values in a highlight group
-func Test_xxlast_highlight_RGB_color()
- CheckCanRunGui
- gui -f
- hi MySearch guifg=#110000 guibg=#001100 guisp=#000011
- call assert_equal('#110000', synIDattr(synIDtrans(hlID('MySearch')), 'fg#'))
- call assert_equal('#001100', synIDattr(synIDtrans(hlID('MySearch')), 'bg#'))
- call assert_equal('#000011', synIDattr(synIDtrans(hlID('MySearch')), 'sp#'))
- hi clear
-endfunc
-
-func Test_highlight_clear_restores_links()
- let aaa_id = hlID('aaa')
- call assert_equal(aaa_id, 0)
-
- " create default link aaa --> bbb
- hi def link aaa bbb
- let id_aaa = hlID('aaa')
- let hl_aaa_bbb = HighlightArgs('aaa')
-
- " try to redefine default link aaa --> ccc; check aaa --> bbb
- hi def link aaa ccc
- call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
-
- " clear aaa; check aaa --> bbb
- hi clear aaa
- call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
-
- " link aaa --> ccc; clear aaa; check aaa --> bbb
- hi link aaa ccc
- let id_ccc = hlID('ccc')
- call assert_equal(synIDtrans(id_aaa), id_ccc)
- hi clear aaa
- call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
-
- " forcibly set default link aaa --> ddd
- hi! def link aaa ddd
- let id_ddd = hlID('ddd')
- let hl_aaa_ddd = HighlightArgs('aaa')
- call assert_equal(synIDtrans(id_aaa), id_ddd)
-
- " link aaa --> eee; clear aaa; check aaa --> ddd
- hi link aaa eee
- let eee_id = hlID('eee')
- call assert_equal(synIDtrans(id_aaa), eee_id)
- hi clear aaa
- call assert_equal(HighlightArgs('aaa'), hl_aaa_ddd)
-endfunc
-
-func Test_highlight_clear_restores_context()
- func FuncContextDefault()
- hi def link Context ContextDefault
- endfun
-
- func FuncContextRelink()
- " Dummy line
- hi link Context ContextRelink
- endfunc
-
- let scriptContextDefault = MakeScript("FuncContextDefault")
- let scriptContextRelink = MakeScript("FuncContextRelink")
- let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1'
- let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2'
-
- exec 'source ' .. scriptContextDefault
- let hlContextDefault = execute("verbose hi Context")
- call assert_match(patContextDefault, hlContextDefault)
-
- exec 'source ' .. scriptContextRelink
- let hlContextRelink = execute("verbose hi Context")
- call assert_match(patContextRelink, hlContextRelink)
-
- hi clear
- let hlContextAfterClear = execute("verbose hi Context")
- call assert_match(patContextDefault, hlContextAfterClear)
-
- delfunc FuncContextDefault
- delfunc FuncContextRelink
- call delete(scriptContextDefault)
- call delete(scriptContextRelink)
-endfunc
-
-func Test_highlight_default_colorscheme_restores_links()
- hi link TestLink Identifier
- hi TestHi ctermbg=red
-
- let hlTestLinkPre = HighlightArgs('TestLink')
- let hlTestHiPre = HighlightArgs('TestHi')
-
- " Test colorscheme
- hi clear
- if exists('syntax_on')
- syntax reset
- endif
- let g:colors_name = 'test'
- hi link TestLink ErrorMsg
- hi TestHi ctermbg=green
-
- " Restore default highlighting
- colorscheme default
- " 'default' should work no matter if highlight group was cleared
- hi def link TestLink Identifier
- hi def TestHi ctermbg=red
- let hlTestLinkPost = HighlightArgs('TestLink')
- let hlTestHiPost = HighlightArgs('TestHi')
- call assert_equal(hlTestLinkPre, hlTestLinkPost)
- call assert_equal(hlTestHiPre, hlTestHiPost)
- hi clear
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_history.vim b/src/nvim/testdir/test_history.vim
deleted file mode 100644
index f1c31dee04..0000000000
--- a/src/nvim/testdir/test_history.vim
+++ /dev/null
@@ -1,252 +0,0 @@
-" Tests for the history functions
-
-source check.vim
-CheckFeature cmdline_hist
-
-set history=7
-
-function History_Tests(hist)
- " First clear the history
- call histadd(a:hist, 'dummy')
- call assert_true(histdel(a:hist))
- call assert_equal(-1, histnr(a:hist))
- call assert_equal('', histget(a:hist))
-
- call assert_true('ls'->histadd(a:hist))
- call assert_true(histadd(a:hist, 'buffers'))
- call assert_equal('buffers', histget(a:hist))
- call assert_equal('ls', histget(a:hist, -2))
- call assert_equal('ls', histget(a:hist, 1))
- call assert_equal('', histget(a:hist, 5))
- call assert_equal('', histget(a:hist, -5))
- call assert_equal(2, histnr(a:hist))
- call assert_true(histdel(a:hist, 2))
- call assert_false(a:hist->histdel(7))
- call assert_equal(1, histnr(a:hist))
- call assert_equal('ls', histget(a:hist, -1))
-
- call assert_true(histadd(a:hist, 'buffers'))
- call assert_true(histadd(a:hist, 'ls'))
- call assert_equal('ls', a:hist->histget(-1))
- call assert_equal(4, a:hist->histnr())
-
- let a=execute('history ' . a:hist)
- call assert_match("^\n # \\S* history\n 3 buffers\n> 4 ls$", a)
- let a=execute('history all')
- call assert_match("^\n # .* history\n 3 buffers\n> 4 ls", a)
-
- if len(a:hist) > 0
- let a=execute('history ' . a:hist . ' 2')
- call assert_match("^\n # \\S* history$", a)
- let a=execute('history ' . a:hist . ' 3')
- call assert_match("^\n # \\S* history\n 3 buffers$", a)
- let a=execute('history ' . a:hist . ' 4')
- call assert_match("^\n # \\S* history\n> 4 ls$", a)
- let a=execute('history ' . a:hist . ' 3,4')
- call assert_match("^\n # \\S* history\n 3 buffers\n> 4 ls$", a)
- let a=execute('history ' . a:hist . ' -1')
- call assert_match("^\n # \\S* history\n> 4 ls$", a)
- let a=execute('history ' . a:hist . ' -2')
- call assert_match("^\n # \\S* history\n 3 buffers$", a)
- let a=execute('history ' . a:hist . ' -2,')
- call assert_match("^\n # \\S* history\n 3 buffers\n> 4 ls$", a)
- let a=execute('history ' . a:hist . ' -3')
- call assert_match("^\n # \\S* history$", a)
- endif
-
- " Test for removing entries matching a pattern
- for i in range(1, 3)
- call histadd(a:hist, 'text_' . i)
- endfor
- call assert_true(histdel(a:hist, 'text_\d\+'))
- call assert_equal('ls', histget(a:hist, -1))
-
- " Test for freeing the entire history list
- for i in range(1, 7)
- call histadd(a:hist, 'text_' . i)
- endfor
- call histdel(a:hist)
- for i in range(1, 7)
- call assert_equal('', histget(a:hist, i))
- call assert_equal('', histget(a:hist, i - 7 - 1))
- endfor
-
- " Test for freeing an entry at the beginning of the history list
- for i in range(1, 4)
- call histadd(a:hist, 'text_' . i)
- endfor
- call histdel(a:hist, 1)
- call assert_equal('', histget(a:hist, 1))
- call assert_equal('text_4', histget(a:hist, 4))
-endfunction
-
-function Test_History()
- for h in ['cmd', ':', '', 'search', '/', '?', 'expr', '=', 'input', '@', 'debug', '>']
- call History_Tests(h)
- endfor
-
- " Negative tests
- call assert_false(histdel('abc'))
- call assert_equal('', histget('abc'))
- call assert_fails('call histdel([])', 'E730:')
- call assert_equal('', histget(10))
- call assert_fails('call histget([])', 'E730:')
- call assert_equal(-1, histnr('abc'))
- call assert_fails('call histnr([])', 'E730:')
- call assert_fails('history xyz', 'E488:')
- call assert_fails('history ,abc', 'E488:')
- call assert_fails('call histdel(":", "\\%(")', 'E53:')
-endfunction
-
-function Test_history_truncates_long_entry()
- " History entry short enough to fit on the screen should not be truncated.
- call histadd(':', 'echo x' .. repeat('y', &columns - 17) .. 'z')
- let a = execute('history : -1')
-
- call assert_match("^\n # cmd history\n"
- \ .. "> *\\d\\+ echo x" .. repeat('y', &columns - 17) .. 'z$', a)
-
- " Long history entry should be truncated to fit on the screen, with, '...'
- " inserted in the string to indicate the that there is truncation.
- call histadd(':', 'echo x' .. repeat('y', &columns - 16) .. 'z')
- let a = execute('history : -1')
- call assert_match("^\n # cmd history\n"
- \ .. "> *\\d\\+ echo xy\\+\.\.\.y\\+z$", a)
-endfunction
-
-function Test_Search_history_window()
- new
- call setline(1, ['a', 'b', 'a', 'b'])
- 1
- call feedkeys("/a\<CR>", 'xt')
- call assert_equal('a', getline('.'))
- 1
- call feedkeys("/b\<CR>", 'xt')
- call assert_equal('b', getline('.'))
- 1
- " select the previous /a command
- call feedkeys("q/kk\<CR>", 'x!')
- call assert_equal('a', getline('.'))
- call assert_equal('a', @/)
- bwipe!
-endfunc
-
-" Test for :history command option completion
-function Test_history_completion()
- call feedkeys(":history \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"history / : = > ? @ all cmd debug expr input search', @:)
-endfunc
-
-" Test for increasing the 'history' option value
-func Test_history_size()
- let save_histsz = &history
- set history=10
- call histadd(':', 'ls')
- call histdel(':')
- for i in range(1, 5)
- call histadd(':', 'cmd' .. i)
- endfor
- call assert_equal(5, histnr(':'))
- call assert_equal('cmd5', histget(':', -1))
-
- set history=15
- for i in range(6, 10)
- call histadd(':', 'cmd' .. i)
- endfor
- call assert_equal(10, histnr(':'))
- call assert_equal('cmd1', histget(':', 1))
- call assert_equal('cmd10', histget(':', -1))
-
- set history=5
- call histadd(':', 'abc')
- call assert_equal('', histget(':', 6))
- call assert_equal('', histget(':', 12))
- call assert_equal('cmd7', histget(':', 7))
- call assert_equal('abc', histget(':', -1))
-
- " This test works only when the language is English
- if v:lang == "C" || v:lang =~ '^[Ee]n'
- set history=0
- redir => v
- call feedkeys(":history\<CR>", 'xt')
- redir END
- call assert_equal(["'history' option is zero"], split(v, "\n"))
- endif
-
- let &history=save_histsz
-endfunc
-
-" Test for recalling old search patterns in /
-func Test_history_search()
- call histdel('/')
- let g:pat = []
- func SavePat()
- call add(g:pat, getcmdline())
- return ''
- endfunc
- cnoremap <F2> <C-\>eSavePat()<CR>
- call histadd('/', 'pat1')
- call histadd('/', 'pat2')
- let @/ = ''
- call feedkeys("/\<Up>\<F2>\<Up>\<F2>\<Down>\<Down>\<F2>\<Esc>", 'xt')
- call assert_equal(['pat2', 'pat1', ''], g:pat)
- cunmap <F2>
- delfunc SavePat
-
- " Search for a pattern that is not present in the history
- call assert_beeps('call feedkeys("/a1b2\<Up>\<CR>", "xt")')
-
- " Recall patterns with 'history' set to 0
- set history=0
- let @/ = 'abc'
- let cmd = 'call feedkeys("/\<Up>\<Down>\<S-Up>\<S-Down>\<CR>", "xt")'
- call assert_fails(cmd, 'E486:')
- set history&
-
- " Recall patterns till the end of history
- set history=4
- call histadd('/', 'pat')
- call histdel('/')
- call histadd('/', 'pat1')
- call histadd('/', 'pat2')
- call assert_beeps('call feedkeys("/\<Up>\<Up>\<Up>\<C-U>\<cr>", "xt")')
- call assert_beeps('call feedkeys("/\<Down><cr>", "xt")')
-
- " Test for wrapping around the history list
- for i in range(3, 7)
- call histadd('/', 'pat' .. i)
- endfor
- let upcmd = "\<up>\<up>\<up>\<up>\<up>"
- let downcmd = "\<down>\<down>\<down>\<down>\<down>"
- try
- call feedkeys("/" .. upcmd .. "\<cr>", 'xt')
- catch /E486:/
- endtry
- call assert_equal('pat4', @/)
- try
- call feedkeys("/" .. upcmd .. downcmd .. "\<cr>", 'xt')
- catch /E486:/
- endtry
- call assert_equal('pat4', @/)
-
- " Test for changing the search command separator in the history
- call assert_fails('call feedkeys("/def/\<cr>", "xt")', 'E486:')
- call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:')
- call assert_equal('def?', histget('/', -1))
-
- call assert_fails('call feedkeys("/ghi?\<cr>", "xt")', 'E486:')
- call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:')
- call assert_equal('ghi\?', histget('/', -1))
-
- set history&
-endfunc
-
-" Test for making sure the key value is not stored in history
-func Test_history_crypt_key()
- CheckFeature cryptv
- call feedkeys(":set bs=2 key=abc ts=8\<CR>", 'xt')
- call assert_equal('set bs=2 key= ts=8', histget(':'))
- set key& bs& ts&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_hlsearch.vim b/src/nvim/testdir/test_hlsearch.vim
deleted file mode 100644
index cf2791113a..0000000000
--- a/src/nvim/testdir/test_hlsearch.vim
+++ /dev/null
@@ -1,65 +0,0 @@
-" Test for v:hlsearch
-
-func Test_hlsearch()
- new
- call setline(1, repeat(['aaa'], 10))
- set hlsearch nolazyredraw
- " redraw is needed to make hlsearch highlight the matches
- exe "normal! /aaa\<CR>" | redraw
- let r1 = screenattr(1, 1)
- nohlsearch | redraw
- call assert_notequal(r1, screenattr(1,1))
- let v:hlsearch=1 | redraw
- call assert_equal(r1, screenattr(1,1))
- let v:hlsearch=0 | redraw
- call assert_notequal(r1, screenattr(1,1))
- set hlsearch | redraw
- call assert_equal(r1, screenattr(1,1))
- let v:hlsearch=0 | redraw
- call assert_notequal(r1, screenattr(1,1))
- exe "normal! n" | redraw
- call assert_equal(r1, screenattr(1,1))
- let v:hlsearch=0 | redraw
- call assert_notequal(r1, screenattr(1,1))
- exe "normal! /\<CR>" | redraw
- call assert_equal(r1, screenattr(1,1))
- set nohls
- exe "normal! /\<CR>" | redraw
- call assert_notequal(r1, screenattr(1,1))
- call assert_fails('let v:hlsearch=[]', 'E745')
- call garbagecollect(1)
- call getchar(1)
- enew!
-endfunc
-
-func Test_hlsearch_hangs()
- if !has('reltime') || !has('float')
- return
- endif
-
- " This pattern takes a long time to match, it should timeout.
- new
- call setline(1, ['aaa', repeat('abc ', 100000), 'ccc'])
- let start = reltime()
- set hlsearch nolazyredraw redrawtime=101
- let @/ = '\%#=1a*.*X\@<=b*'
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed > 0.1)
- call assert_true(elapsed < 1.0)
- set nohlsearch redrawtime&
- bwipe!
-endfunc
-
-func Test_hlsearch_eol_highlight()
- new
- call append(1, repeat([''], 9))
- set hlsearch nolazyredraw
- exe "normal! /$\<CR>" | redraw
- let attr = screenattr(1, 1)
- for row in range(2, 10)
- call assert_equal(attr, screenattr(row, 1), 'in line ' . row)
- endfor
- set nohlsearch
- bwipe!
-endfunc
diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim
deleted file mode 100644
index 3c2b88ef9f..0000000000
--- a/src/nvim/testdir/test_increment.vim
+++ /dev/null
@@ -1,897 +0,0 @@
-" Tests for using Ctrl-A/Ctrl-X
-
-func SetUp()
- new dummy
- set nrformats&vim
- set nrformats+=octal
-endfunc
-
-func TearDown()
- bwipe!
-endfunc
-
-" 1) Ctrl-A on visually selected number
-" Text:
-" foobar-10
-" Expected:
-" 1) Ctrl-A on start of line:
-" foobar-9
-" 2) Ctrl-A on visually selected "-10":
-" foobar-9
-" 3) Ctrl-A on visually selected "10":
-" foobar-11
-" 4) Ctrl-X on visually selected "-10"
-" foobar-11
-" 5) Ctrl-X on visually selected "10"
-" foobar-9
-func Test_visual_increment_01()
- call setline(1, repeat(["foobaar-10"], 5))
-
- call cursor(1, 1)
- exec "norm! \<C-A>"
- call assert_equal("foobaar-9", getline('.'))
- call assert_equal([0, 1, 9, 0], getpos('.'))
-
- call cursor(2, 1)
- exec "norm! f-v$\<C-A>"
- call assert_equal("foobaar-9", getline('.'))
- call assert_equal([0, 2, 8, 0], getpos('.'))
-
- call cursor(3, 1)
- exec "norm! f1v$\<C-A>"
- call assert_equal("foobaar-11", getline('.'))
- call assert_equal([0, 3, 9, 0], getpos('.'))
-
- call cursor(4, 1)
- exec "norm! f-v$\<C-X>"
- call assert_equal("foobaar-11", getline('.'))
- call assert_equal([0, 4, 8, 0], getpos('.'))
-
- call cursor(5, 1)
- exec "norm! f1v$\<C-X>"
- call assert_equal("foobaar-9", getline('.'))
- call assert_equal([0, 5, 9, 0], getpos('.'))
-endfunc
-
-" 2) Ctrl-A on visually selected lines
-" Text:
-" 10
-" 20
-" 30
-" 40
-"
-" Expected:
-" 1) Ctrl-A on visually selected lines:
-" 11
-" 21
-" 31
-" 41
-"
-" 2) Ctrl-X on visually selected lines:
-" 9
-" 19
-" 29
-" 39
-func Test_visual_increment_02()
- call setline(1, ["10", "20", "30", "40"])
- exec "norm! GV3k$\<C-A>"
- call assert_equal(["11", "21", "31", "41"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- call setline(1, ["10", "20", "30", "40"])
- exec "norm! GV3k$\<C-X>"
- call assert_equal(["9", "19", "29", "39"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 3) g Ctrl-A on visually selected lines, with non-numbers in between
-" Text:
-" 10
-"
-" 20
-"
-" 30
-"
-" 40
-"
-" Expected:
-" 1) 2 g Ctrl-A on visually selected lines:
-" 12
-"
-" 24
-"
-" 36
-"
-" 48
-" 2) 2 g Ctrl-X on visually selected lines
-" 8
-"
-" 16
-"
-" 24
-"
-" 32
-func Test_visual_increment_03()
- call setline(1, ["10", "", "20", "", "30", "", "40"])
- exec "norm! GV6k2g\<C-A>"
- call assert_equal(["12", "", "24", "", "36", "", "48"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- call setline(1, ["10", "", "20", "", "30", "", "40"])
- exec "norm! GV6k2g\<C-X>"
- call assert_equal(["8", "", "16", "", "24", "", "32"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 4) Ctrl-A on non-number
-" Text:
-" foobar-10
-" Expected:
-" 1) visually select foobar:
-" foobar-10
-func Test_visual_increment_04()
- call setline(1, ["foobar-10"])
- exec "norm! vf-\<C-A>"
- call assert_equal(["foobar-10"], getline(1, '$'))
- " NOTE: I think this is correct behavior...
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 5) g<Ctrl-A> on letter
-" Test:
-" a
-" a
-" a
-" a
-" Expected:
-" 1) g Ctrl-A on visually selected lines
-" b
-" c
-" d
-" e
-func Test_visual_increment_05()
- set nrformats+=alpha
- call setline(1, repeat(["a"], 4))
- exec "norm! GV3kg\<C-A>"
- call assert_equal(["b", "c", "d", "e"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 6) g<Ctrl-A> on letter
-" Test:
-" z
-" z
-" z
-" z
-" Expected:
-" 1) g Ctrl-X on visually selected lines
-" y
-" x
-" w
-" v
-func Test_visual_increment_06()
- set nrformats+=alpha
- call setline(1, repeat(["z"], 4))
- exec "norm! GV3kg\<C-X>"
- call assert_equal(["y", "x", "w", "v"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 7) <Ctrl-A> on letter
-" Test:
-" 2
-" 1
-" 0
-" -1
-" -2
-"
-" Expected:
-" 1) Ctrl-A on visually selected lines
-" 3
-" 2
-" 1
-" 0
-" -1
-"
-" 2) Ctrl-X on visually selected lines
-" 1
-" 0
-" -1
-" -2
-" -3
-func Test_visual_increment_07()
- call setline(1, ["2", "1", "0", "-1", "-2"])
- exec "norm! GV4k\<C-A>"
- call assert_equal(["3", "2", "1", "0", "-1"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- call setline(1, ["2", "1", "0", "-1", "-2"])
- exec "norm! GV4k\<C-X>"
- call assert_equal(["1", "0", "-1", "-2", "-3"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 8) Block increment on 0x9
-" Text:
-" 0x9
-" 0x9
-" Expected:
-" 1) Ctrl-A on visually block selected region (cursor at beginning):
-" 0xa
-" 0xa
-" 2) Ctrl-A on visually block selected region (cursor at end)
-" 0xa
-" 0xa
-func Test_visual_increment_08()
- call setline(1, repeat(["0x9"], 2))
- exec "norm! \<C-V>j$\<C-A>"
- call assert_equal(["0xa", "0xa"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- call setline(1, repeat(["0x9"], 2))
- exec "norm! gg$\<C-V>+\<C-A>"
- call assert_equal(["0xa", "0xa"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 9) Increment and redo
-" Text:
-" 2
-" 2
-"
-" 3
-" 3
-"
-" Expected:
-" 1) 2 Ctrl-A on first 2 visually selected lines
-" 4
-" 4
-" 2) redo (.) on 3
-" 5
-" 5
-func Test_visual_increment_09()
- call setline(1, ["2", "2", "", "3", "3", ""])
- exec "norm! ggVj2\<C-A>"
- call assert_equal(["4", "4", "", "3", "3", ""], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- exec "norm! 3j."
- call assert_equal(["4", "4", "", "5", "5", ""], getline(1, '$'))
- call assert_equal([0, 4, 1, 0], getpos('.'))
-endfunc
-
-" 10) sequentially decrement 1
-" Text:
-" 1
-" 1
-" 1
-" 1
-" Expected:
-" 1) g Ctrl-X on visually selected lines
-" 0
-" -1
-" -2
-" -3
-func Test_visual_increment_10()
- call setline(1, repeat(["1"], 4))
- exec "norm! GV3kg\<C-X>"
- call assert_equal(["0", "-1", "-2", "-3"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 11) visually block selected indented lines
-" Text:
-" 1
-" 1
-" 1
-" 1
-" Expected:
-" 1) g Ctrl-A on block selected indented lines
-" 2
-" 1
-" 3
-" 4
-func Test_visual_increment_11()
- call setline(1, [" 1", "1", " 1", " 1"])
- exec "norm! f1\<C-V>3jg\<C-A>"
- call assert_equal([" 2", "1", " 3", " 4"], getline(1, '$'))
- call assert_equal([0, 1, 5, 0], getpos('.'))
-endfunc
-
-" 12) visually selected several columns
-" Text:
-" 0 0
-" 0 0
-" 0 0
-" Expected:
-" 1) 'v' select last zero and first zeroes
-" 0 1
-" 1 0
-" 1 0
-func Test_visual_increment_12()
- call setline(1, repeat(["0 0"], 3))
- exec "norm! $v++\<C-A>"
- call assert_equal(["0 1", "1 0", "1 0"], getline(1, '$'))
- call assert_equal([0, 1, 3, 0], getpos('.'))
-endfunc
-
-" 13) visually selected part of columns
-" Text:
-" max: 100px
-" max: 200px
-" max: 300px
-" max: 400px
-" Expected:
-" 1) 'v' on first two numbers Ctrl-A
-" max: 110px
-" max: 220px
-" max: 330px
-" max: 400px
-" 2) 'v' on first two numbers Ctrl-X
-" max: 90px
-" max: 190px
-" max: 290px
-" max: 400px
-func Test_visual_increment_13()
- call setline(1, ["max: 100px", "max: 200px", "max: 300px", "max: 400px"])
- exec "norm! f1\<C-V>l2j\<C-A>"
- call assert_equal(["max: 110px", "max: 210px", "max: 310px", "max: 400px"], getline(1, '$'))
- call assert_equal([0, 1, 6, 0], getpos('.'))
-
- call setline(1, ["max: 100px", "max: 200px", "max: 300px", "max: 400px"])
- exec "norm! ggf1\<C-V>l2j\<C-X>"
- call assert_equal(["max: 90px", "max: 190px", "max: 290px", "max: 400px"], getline(1, '$'))
- call assert_equal([0, 1, 6, 0], getpos('.'))
-endfunc
-
-" 14) redo in block mode
-" Text:
-" 1 1
-" 1 1
-" Expected:
-" 1) Ctrl-a on first column, redo on second column
-" 2 2
-" 2 2
-func Test_visual_increment_14()
- call setline(1, repeat(["1 1"], 2))
- exec "norm! G\<C-V>k\<C-A>w."
- call assert_equal(["2 2", "2 2"], getline(1, '$'))
- call assert_equal([0, 1, 3, 0], getpos('.'))
-endfunc
-
-" 15) block select single numbers
-" Text:
-" 101
-" Expected:
-" 1) Ctrl-a on visually selected zero
-" 111
-"
-" Also: 019 with "01" selected increments to "029".
-func Test_visual_increment_15()
- call setline(1, ["101"])
- exec "norm! lv\<C-A>"
- call assert_equal(["111"], getline(1, '$'))
- call assert_equal([0, 1, 2, 0], getpos('.'))
-
- call setline(1, ["019"])
- exec "norm! 0vl\<C-A>"
- call assert_equal("029", getline(1))
-
- call setline(1, ["01239"])
- exec "norm! 0vlll\<C-A>"
- call assert_equal("01249", getline(1))
-
- call setline(1, ["01299"])
- exec "norm! 0vlll\<C-A>"
- call assert_equal("1309", getline(1))
-endfunc
-
-" 16) increment right aligned numbers
-" Text:
-" 1
-" 19
-" 119
-" Expected:
-" 1) Ctrl-a on line selected region
-" 2
-" 20
-" 120
-func Test_visual_increment_16()
- call setline(1, [" 1", " 19", " 119"])
- exec "norm! VG\<C-A>"
- call assert_equal([" 2", " 20", " 120"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 17) block-wise increment and redo
-" Text:
-" 100
-" 1
-"
-" 100
-" 1
-"
-" Expected:
-" 1) Ctrl-V j $ on first block, afterwards '.' on second
-" 101
-" 2
-"
-" 101
-" 2
-func Test_visual_increment_17()
- call setline(1, [" 100", " 1", "", " 100", " 1"])
- exec "norm! \<C-V>j$\<C-A>2j."
- call assert_equal([" 101", " 2", "", " 101", " 1"], getline(1, '$'))
- call assert_equal([0, 3, 1, 0], getpos('.'))
-endfunc
-
-" 18) repeat of g<Ctrl-a>
-" Text:
-" 0
-" 0
-" 0
-" 0
-"
-" Expected:
-" 1) V 4j g<ctrl-a>, repeat twice afterwards with .
-" 3
-" 6
-" 9
-" 12
-func Test_visual_increment_18()
- call setline(1, repeat(["0"], 4))
- exec "norm! GV3kg\<C-A>"
- exec "norm! .."
- call assert_equal(["3", "6", "9", "12"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 19) increment on number with nrformat including alpha
-" Text:
-" 1
-" 1a
-"
-" Expected:
-" 1) <Ctrl-V>j$ <ctrl-a>
-" 2
-" 2a
-func Test_visual_increment_19()
- set nrformats+=alpha
- call setline(1, ["1", "1a"])
- exec "norm! \<C-V>G$\<C-A>"
- call assert_equal(["2", "2a"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 20) increment a single letter
-" Text:
-" a
-"
-" Expected:
-" 1) <Ctrl-a> and cursor is on a
-" b
-func Test_visual_increment_20()
- set nrformats+=alpha
- call setline(1, ["a"])
- exec "norm! \<C-A>"
- call assert_equal(["b"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
- " decrement a and A and increment z and Z
- call setline(1, ['a', 'A', 'z', 'Z'])
- exe "normal 1G\<C-X>2G\<C-X>3G\<C-A>4G\<C-A>"
- call assert_equal(['a', 'A', 'z', 'Z'], getline(1, '$'))
-endfunc
-
-" 21) block-wise increment on part of hexadecimal
-" Text:
-" 0x123456
-"
-" Expected:
-" 1) Ctrl-V f3 <ctrl-a>
-" 0x124456
-func Test_visual_increment_21()
- call setline(1, ["0x123456"])
- exec "norm! \<C-V>f3\<C-A>"
- call assert_equal(["0x124456"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 22) Block increment on 0b0
-" Text:
-" 0b1
-" 0b1
-" Expected:
-" 1) Ctrl-A on visually block selected region (cursor at beginning):
-" 0b10
-" 0b10
-" 2) Ctrl-A on visually block selected region (cursor at end)
-" 0b10
-" 0b10
-func Test_visual_increment_22()
- call setline(1, repeat(["0b1"], 2))
- exec "norm! \<C-V>j$\<C-A>"
- call assert_equal(repeat(["0b10"], 2), getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- call setline(1, repeat(["0b1"], 2))
- exec "norm! $\<C-V>+\<C-A>"
- call assert_equal(repeat(["0b10"], 2), getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 23) block-wise increment on part of binary
-" Text:
-" 0b1001
-"
-" Expected:
-" 1) Ctrl-V 5l <ctrl-a>
-" 0b1011
-func Test_visual_increment_23()
- call setline(1, ["0b1001"])
- exec "norm! \<C-V>4l\<C-A>"
- call assert_equal(["0b1011"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 24) increment hexadecimal
-" Text:
-" 0x0b1001
-"
-" Expected:
-" 1) <ctrl-a>
-" 0x0b1002
-func Test_visual_increment_24()
- call setline(1, ["0x0b1001"])
- exec "norm! \<C-V>$\<C-A>"
- call assert_equal(["0x0b1002"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 25) increment binary with nrformats including alpha
-" Text:
-" 0b1001a
-"
-" Expected:
-" 1) <ctrl-a>
-" 0b1010a
-func Test_visual_increment_25()
- set nrformats+=alpha
- call setline(1, ["0b1001a"])
- exec "norm! \<C-V>$\<C-A>"
- call assert_equal(["0b1010a"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" 26) increment binary with 32 bits
-" Text:
-" 0b11111111111111111111111111111110
-"
-" Expected:
-" 1) <ctrl-a>
-" 0b11111111111111111111111111111111
-func Test_visual_increment_26()
- set nrformats+=bin
- call setline(1, ["0b11111111111111111111111111111110"])
- exec "norm! \<C-V>$\<C-A>"
- call assert_equal(["0b11111111111111111111111111111111"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
- exec "norm! \<C-V>$\<C-X>"
- call assert_equal(["0b11111111111111111111111111111110"], getline(1, '$'))
- set nrformats-=bin
-endfunc
-
-" 27) increment with 'rightreft', if supported
-func Test_visual_increment_27()
- if exists('+rightleft')
- set rightleft
- call setline(1, ["1234 56"])
-
- exec "norm! $\<C-A>"
- call assert_equal(["1234 57"], getline(1, '$'))
- call assert_equal([0, 1, 7, 0], getpos('.'))
-
- exec "norm! \<C-A>"
- call assert_equal(["1234 58"], getline(1, '$'))
- call assert_equal([0, 1, 7, 0], getpos('.'))
- set norightleft
- endif
-endfunc
-
-" Tab code and linewise-visual inc/dec
-func Test_visual_increment_28()
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! Vj\<C-A>"
- call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! ggVj\<C-X>"
- call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" Tab code and linewise-visual inc/dec with 'nrformats'+=alpha
-func Test_visual_increment_29()
- set nrformats+=alpha
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! Vj\<C-A>"
- call assert_equal(["y\<TAB>10", "\<TAB>0"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! ggVj\<C-X>"
- call assert_equal(["w\<TAB>10", "\<TAB>-2"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" Tab code and character-visual inc/dec
-func Test_visual_increment_30()
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! f1vjf1\<C-A>"
- call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
- call assert_equal([0, 1, 3, 0], getpos('.'))
-
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! ggf1vjf1\<C-X>"
- call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
- call assert_equal([0, 1, 3, 0], getpos('.'))
-endfunc
-
-" Tab code and blockwise-visual inc/dec
-func Test_visual_increment_31()
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! f1\<C-V>jl\<C-A>"
- call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
- call assert_equal([0, 1, 3, 0], getpos('.'))
-
- call setline(1, ["x\<TAB>10", "\<TAB>-1"])
- exec "norm! ggf1\<C-V>jl\<C-X>"
- call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
- call assert_equal([0, 1, 3, 0], getpos('.'))
-endfunc
-
-" Tab code and blockwise-visual decrement with 'linebreak' and 'showbreak'
-func Test_visual_increment_32()
- 28vnew dummy_31
- set linebreak showbreak=+
- call setline(1, ["x\<TAB>\<TAB>\<TAB>10", "\<TAB>\<TAB>\<TAB>\<TAB>-1"])
- exec "norm! ggf0\<C-V>jg_\<C-X>"
- call assert_equal(["x\<TAB>\<TAB>\<TAB>1-1", "\<TAB>\<TAB>\<TAB>\<TAB>-2"], getline(1, '$'))
- call assert_equal([0, 1, 6, 0], getpos('.'))
- bwipe!
-endfunc
-
-" Tab code and blockwise-visual increment with $
-func Test_visual_increment_33()
- call setline(1, ["\<TAB>123", "456"])
- exec "norm! gg0\<C-V>j$\<C-A>"
- call assert_equal(["\<TAB>124", "457"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" Tab code and blockwise-visual increment and redo
-func Test_visual_increment_34()
- call setline(1, ["\<TAB>123", " 456789"])
- exec "norm! gg0\<C-V>j\<C-A>"
- call assert_equal(["\<TAB>123", " 457789"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- exec "norm! .."
- call assert_equal(["\<TAB>123", " 459789"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" Tab code, spaces and character-visual increment and redo
-func Test_visual_increment_35()
- call setline(1, ["\<TAB>123", " 123", "\<TAB>123", "\<TAB>123"])
- exec "norm! ggvjf3\<C-A>..."
- call assert_equal(["\<TAB>127", " 127", "\<TAB>123", "\<TAB>123"], getline(1, '$'))
- call assert_equal([0, 1, 2, 0], getpos('.'))
-endfunc
-
-" Tab code, spaces and blockwise-visual increment and redo
-func Test_visual_increment_36()
- call setline(1, [" 123", "\<TAB>456789"])
- exec "norm! G0\<C-V>kl\<C-A>"
- call assert_equal([" 123", "\<TAB>556789"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-
- exec "norm! ..."
- call assert_equal([" 123", "\<TAB>856789"], getline(1, '$'))
- call assert_equal([0, 1, 1, 0], getpos('.'))
-endfunc
-
-" block-wise increment and dot-repeat
-" Text:
-" 1 23
-" 4 56
-"
-" Expected:
-" 1) f2 Ctrl-V jl <ctrl-a>, repeat twice afterwards with .
-" 1 26
-" 4 59
-"
-" Try with and without indent.
-func Test_visual_increment_37()
- call setline(1, [" 1 23", " 4 56"])
- exec "norm! ggf2\<C-V>jl\<C-A>.."
- call assert_equal([" 1 26", " 4 59"], getline(1, 2))
-
- call setline(1, ["1 23", "4 56"])
- exec "norm! ggf2\<C-V>jl\<C-A>.."
- call assert_equal(["1 26", "4 59"], getline(1, 2))
-endfunc
-
-" Check redo after the normal mode increment
-func Test_visual_increment_38()
- exec "norm! i10\<ESC>5\<C-A>."
- call assert_equal(["20"], getline(1, '$'))
- call assert_equal([0, 1, 2, 0], getpos('.'))
-endfunc
-
-" Test what patch 7.3.414 fixed. Ctrl-A on "000" drops the leading zeros.
-func Test_normal_increment_01()
- call setline(1, "000")
- exec "norm! gg0\<C-A>"
- call assert_equal("001", getline(1))
-
- call setline(1, "000")
- exec "norm! gg$\<C-A>"
- call assert_equal("001", getline(1))
-
- call setline(1, "001")
- exec "norm! gg0\<C-A>"
- call assert_equal("002", getline(1))
-
- call setline(1, "001")
- exec "norm! gg$\<C-A>"
- call assert_equal("002", getline(1))
-endfunc
-
-" Test a regression of patch 7.4.1087 fixed.
-func Test_normal_increment_02()
- call setline(1, ["hello 10", "world"])
- exec "norm! ggl\<C-A>jx"
- call assert_equal(["hello 11", "worl"], getline(1, '$'))
- call assert_equal([0, 2, 4, 0], getpos('.'))
-endfunc
-
-" The test35 unified to this file.
-func Test_normal_increment_03()
- call setline(1, ["100 0x100 077 0",
- \ "100 0x100 077 ",
- \ "100 0x100 077 0xfF 0xFf",
- \ "100 0x100 077 "])
- set nrformats=octal,hex
- exec "norm! gg\<C-A>102\<C-X>\<C-A>l\<C-X>l\<C-A>64\<C-A>128\<C-X>$\<C-X>"
- set nrformats=octal
- exec "norm! j0\<C-A>102\<C-X>\<C-A>l\<C-X>2\<C-A>w65\<C-A>129\<C-X>blx6lD"
- set nrformats=hex
- exec "norm! j0101\<C-X>l257\<C-X>\<C-A>Txldt \<C-A> \<C-X> \<C-X>"
- set nrformats=
- exec "norm! j0200\<C-X>l100\<C-X>w78\<C-X>\<C-A>k"
- call assert_equal(["0 0x0ff 0000 -1",
- \ "0 1x100 0777777",
- \ "-1 0x0 078 0xFE 0xfe",
- \ "-100 -100x100 000 "], getline(1, '$'))
- call assert_equal([0, 3, 25, 0], getpos('.'))
-endfunc
-
-func Test_increment_empty_line()
- call setline(1, ['0', '0', '0', '0', '0', '0', ''])
- exe "normal Gvgg\<C-A>"
- call assert_equal(['1', '1', '1', '1', '1', '1', ''], getline(1, 7))
-
- " Ctrl-A/Ctrl-X should do nothing in operator pending mode
- %d
- call setline(1, 'one two')
- exe "normal! c\<C-A>l"
- exe "normal! c\<C-X>l"
- call assert_equal('one two', getline(1))
-endfunc
-
-" Try incrementing/decrementing a non-digit/alpha character
-func Test_increment_special_char()
- call setline(1, '!')
- call assert_beeps("normal \<C-A>")
- call assert_beeps("normal \<C-X>")
-endfunc
-
-" Try incrementing/decrementing a number when nrformats contains unsigned
-func Test_increment_unsigned()
- set nrformats+=unsigned
-
- call setline(1, '0')
- exec "norm! gg0\<C-X>"
- call assert_equal('0', getline(1))
-
- call setline(1, '3')
- exec "norm! gg010\<C-X>"
- call assert_equal('0', getline(1))
-
- call setline(1, '-0')
- exec "norm! gg0\<C-X>"
- call assert_equal("-0", getline(1))
-
- call setline(1, '-11')
- exec "norm! gg08\<C-X>"
- call assert_equal('-3', getline(1))
-
- " NOTE: 18446744073709551615 == 2^64 - 1
- call setline(1, '18446744073709551615')
- exec "norm! gg0\<C-A>"
- call assert_equal('18446744073709551615', getline(1))
-
- call setline(1, '-18446744073709551615')
- exec "norm! gg0\<C-A>"
- call assert_equal('-18446744073709551615', getline(1))
-
- call setline(1, '-18446744073709551614')
- exec "norm! gg08\<C-A>"
- call assert_equal('-18446744073709551615', getline(1))
-
- call setline(1, '-1')
- exec "norm! gg0\<C-A>"
- call assert_equal('-2', getline(1))
-
- call setline(1, '-3')
- exec "norm! gg08\<C-A>"
- call assert_equal('-11', getline(1))
-
- set nrformats-=unsigned
-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
-
-" Test for incrementing a signed hexadecimal and octal number
-func Test_normal_increment_signed_hexoct_nr()
- new
- " negative sign before a hex number should be ignored
- call setline(1, ["-0x9"])
- exe "norm \<C-A>"
- call assert_equal(["-0xa"], getline(1, '$'))
- exe "norm \<C-X>"
- call assert_equal(["-0x9"], getline(1, '$'))
- call setline(1, ["-007"])
- exe "norm \<C-A>"
- call assert_equal(["-010"], getline(1, '$'))
- exe "norm \<C-X>"
- call assert_equal(["-007"], getline(1, '$'))
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_increment_dbcs.vim b/src/nvim/testdir/test_increment_dbcs.vim
deleted file mode 100644
index e5d5ccffb3..0000000000
--- a/src/nvim/testdir/test_increment_dbcs.vim
+++ /dev/null
@@ -1,31 +0,0 @@
-" Tests for using Ctrl-A/Ctrl-X using DBCS.
-" neovim needs an iconv to handle cp932. Please do not remove the following
-" conditions.
-if !has('iconv')
- finish
-endif
-scriptencoding cp932
-
-func SetUp()
- new
- set nrformats&
-endfunc
-
-func TearDown()
- bwipe!
-endfunc
-
-func Test_increment_dbcs_1()
- set nrformats+=alpha
- call setline(1, ["ŽR1"])
- exec "norm! 0\<C-A>"
- call assert_equal(["ŽR2"], getline(1, '$'))
- call assert_equal([0, 1, 4, 0], getpos('.'))
-
- call setline(1, ["‚`‚a‚b0xDE‚e"])
- exec "norm! 0\<C-X>"
- call assert_equal(["‚`‚a‚b0xDD‚e"], getline(1, '$'))
- call assert_equal([0, 1, 13, 0], getpos('.'))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_indent.vim b/src/nvim/testdir/test_indent.vim
deleted file mode 100644
index 3b5b643177..0000000000
--- a/src/nvim/testdir/test_indent.vim
+++ /dev/null
@@ -1,279 +0,0 @@
-" Test for various indent options
-
-func Test_preserveindent()
- new
- " Test for autoindent copying indent from the previous line
- setlocal autoindent
- call setline(1, [repeat(' ', 16) .. 'line1'])
- call feedkeys("A\nline2", 'xt')
- call assert_equal("\t\tline2", getline(2))
- setlocal autoindent&
-
- " Test for using CTRL-T with and without 'preserveindent'
- set shiftwidth=4
- call cursor(1, 1)
- call setline(1, " \t ")
- call feedkeys("Al\<C-T>", 'xt')
- call assert_equal("\t\tl", getline(1))
- set preserveindent
- call setline(1, " \t ")
- call feedkeys("Al\<C-T>", 'xt')
- call assert_equal(" \t \tl", getline(1))
- set pi& sw&
-
- " Test for using CTRL-T with 'expandtab' and 'preserveindent'
- call cursor(1, 1)
- call setline(1, "\t \t")
- set shiftwidth=4 expandtab preserveindent
- call feedkeys("Al\<C-T>", 'xt')
- call assert_equal("\t \t l", getline(1))
- set sw& et& pi&
-
- close!
-endfunc
-
-" Test for indent()
-func Test_indent_func()
- call assert_equal(-1, indent(-1))
- new
- call setline(1, "\tabc")
- call assert_equal(8, indent(1))
- call setline(1, " abc")
- call assert_equal(4, indent(1))
- call setline(1, " \t abc")
- call assert_equal(12, indent(1))
- close!
-endfunc
-
-" Test for reindenting a line using the '=' operator
-func Test_reindent()
- new
- call setline(1, 'abc')
- set nomodifiable
- call assert_fails('normal ==', 'E21:')
- set modifiable
-
- call setline(1, ['foo', 'bar'])
- call feedkeys('ggVG=', 'xt')
- call assert_equal(['foo', 'bar'], getline(1, 2))
- close!
-endfunc
-
-" Test indent operator creating one undo entry
-func Test_indent_operator_undo()
- enew
- call setline(1, range(12)->map('"\t" .. v:val'))
- func FoldExpr()
- let g:foldcount += 1
- return '='
- endfunc
- set foldmethod=expr foldexpr=FoldExpr()
- let g:foldcount = 0
- redraw
- call assert_equal(12, g:foldcount)
- normal gg=G
- call assert_equal(24, g:foldcount)
- undo
- call assert_equal(38, g:foldcount)
-
- bwipe!
- set foldmethod& foldexpr=
- delfunc FoldExpr
- unlet g:foldcount
-endfunc
-
-" Test for shifting a line with a preprocessor directive ('#')
-func Test_preproc_indent()
- new
- set sw=4
- call setline(1, '#define FOO 1')
- normal >>
- call assert_equal(' #define FOO 1', getline(1))
-
- " with 'smartindent'
- call setline(1, '#define FOO 1')
- set smartindent
- normal >>
- call assert_equal('#define FOO 1', getline(1))
- set smartindent&
-
- " with 'cindent'
- set cindent
- normal >>
- call assert_equal('#define FOO 1', getline(1))
- set cindent&
-
- close!
-endfunc
-
-" Test for 'copyindent'
-func Test_copyindent()
- new
- set shiftwidth=4 autoindent expandtab copyindent
- call setline(1, " \t abc")
- call feedkeys("ol", 'xt')
- call assert_equal(" \t l", getline(2))
- set noexpandtab
- call setline(1, " \t abc")
- call feedkeys("ol", 'xt')
- call assert_equal(" \t l", getline(2))
- set sw& ai& et& ci&
- close!
-endfunc
-
-" Test for changing multiple lines with lisp indent
-func Test_lisp_indent_change_multiline()
- new
- setlocal lisp autoindent
- call setline(1, ['(if a', ' (if b', ' (return 5)))'])
- normal! jc2j(return 4))
- call assert_equal(' (return 4))', getline(2))
- close!
-endfunc
-
-func Test_lisp_indent()
- new
- setlocal lisp autoindent
- call setline(1, ['(if a', ' ;; comment', ' \ abc', '', ' " str1\', ' " st\b', ' (return 5)'])
- normal! jo;; comment
- normal! jo\ abc
- normal! jo;; ret
- normal! jostr1"
- normal! jostr2"
- call assert_equal([' ;; comment', ' ;; comment', ' \ abc', ' \ abc', '', ' ;; ret', ' " str1\', ' str1"', ' " st\b', ' str2"'], getline(2, 11))
- close!
-endfunc
-
-func Test_lisp_indent_quoted()
- " This was going past the end of the line
- new
- setlocal lisp autoindent
- call setline(1, ['"[', '='])
- normal Gvk=
-
- bwipe!
-endfunc
-
-" Test for setting the 'indentexpr' from a modeline
-func Test_modeline_indent_expr()
- let modeline = &modeline
- set modeline
- func GetIndent()
- return line('.') * 2
- endfunc
- call writefile(['# vim: indentexpr=GetIndent()'], 'Xfile.txt')
- set modelineexpr
- new Xfile.txt
- call assert_equal('GetIndent()', &indentexpr)
- exe "normal Oa\nb\n"
- call assert_equal([' a', ' b'], getline(1, 2))
-
- set modelineexpr&
- delfunc GetIndent
- let &modeline = modeline
- close!
- call delete('Xfile.txt')
-endfunc
-
-func Test_indent_func_with_gq()
-
- function GetTeXIndent()
- " Sample indent expression for TeX files
- let lnum = prevnonblank(v:lnum - 1)
- " At the start of the file use zero indent.
- if lnum == 0
- return 0
- endif
- let line = getline(lnum)
- let ind = indent(lnum)
- " Add a 'shiftwidth' after beginning of environments.
- if line =~ '\\begin{center}'
- let ind = ind + shiftwidth()
- endif
- return ind
- endfunction
-
- new
- setl et sw=2 sts=2 ts=2 tw=50 indentexpr=GetTeXIndent()
- put =[ '\documentclass{article}', '', '\begin{document}', '',
- \ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ut enim non',
- \ 'libero efficitur aliquet. Maecenas metus justo, facilisis convallis blandit',
- \ 'non, semper eu urna. Suspendisse diam diam, iaculis faucibus lorem eu,',
- \ 'fringilla condimentum lectus. Quisque euismod diam at convallis vulputate.',
- \ 'Pellentesque laoreet tortor sit amet mauris euismod ornare. Sed varius',
- \ 'bibendum orci vel vehicula. Pellentesque tempor, ipsum et auctor accumsan,',
- \ 'metus lectus ultrices odio, sed elementum mi ante at arcu.', '', '\begin{center}', '',
- \ 'Proin nec risus consequat nunc dapibus consectetur. Mauris lacinia est a augue',
- \ 'tristique accumsan. Morbi pretium, felis molestie eleifend condimentum, arcu',
- \ 'ipsum congue nisl, quis euismod purus libero in ante.', '',
- \ 'Donec id semper purus.',
- \ 'Suspendisse eget aliquam nunc. Maecenas fringilla mauris vitae maximus',
- \ 'condimentum. Cras a quam in mi dictum eleifend at a lorem. Sed convallis',
- \ 'ante a commodo facilisis. Nam suscipit vulputate odio, vel dapibus nisl',
- \ 'dignissim facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et',
- \ 'ultrices posuere cubilia curae;', '', '']
- 1d_
- call cursor(5, 1)
- ka
- call cursor(14, 1)
- kb
- norm! 'agqap
- norm! 'bgqG
- let expected = [ '\documentclass{article}', '', '\begin{document}', '',
- \ 'Lorem ipsum dolor sit amet, consectetur adipiscing',
- \ 'elit. Fusce ut enim non libero efficitur aliquet.',
- \ 'Maecenas metus justo, facilisis convallis blandit',
- \ 'non, semper eu urna. Suspendisse diam diam,',
- \ 'iaculis faucibus lorem eu, fringilla condimentum',
- \ 'lectus. Quisque euismod diam at convallis',
- \ 'vulputate. Pellentesque laoreet tortor sit amet',
- \ 'mauris euismod ornare. Sed varius bibendum orci',
- \ 'vel vehicula. Pellentesque tempor, ipsum et auctor',
- \ 'accumsan, metus lectus ultrices odio, sed',
- \ 'elementum mi ante at arcu.', '', '\begin{center}', '',
- \ ' Proin nec risus consequat nunc dapibus',
- \ ' consectetur. Mauris lacinia est a augue',
- \ ' tristique accumsan. Morbi pretium, felis',
- \ ' molestie eleifend condimentum, arcu ipsum congue',
- \ ' nisl, quis euismod purus libero in ante.',
- \ '',
- \ ' Donec id semper purus. Suspendisse eget aliquam',
- \ ' nunc. Maecenas fringilla mauris vitae maximus',
- \ ' condimentum. Cras a quam in mi dictum eleifend',
- \ ' at a lorem. Sed convallis ante a commodo',
- \ ' facilisis. Nam suscipit vulputate odio, vel',
- \ ' dapibus nisl dignissim facilisis. Vestibulum',
- \ ' ante ipsum primis in faucibus orci luctus et',
- \ ' ultrices posuere cubilia curae;', '', '']
- call assert_equal(expected, getline(1, '$'))
-
- bwipe!
- delmark ab
- delfunction GetTeXIndent
-endfu
-
-func Test_formatting_keeps_first_line_indent()
- let lines =<< trim END
- foo()
- {
- int x; // manually positioned
- // more text that will be formatted
- // but not reindented
- END
- new
- call setline(1, lines)
- setlocal sw=4 cindent tw=45 et
- normal! 4Ggqj
- let expected =<< trim END
- foo()
- {
- int x; // manually positioned
- // more text that will be
- // formatted but not
- // reindented
- END
- call assert_equal(expected, getline(1, '$'))
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_input.vim b/src/nvim/testdir/test_input.vim
deleted file mode 100644
index 3b1e2eb2df..0000000000
--- a/src/nvim/testdir/test_input.vim
+++ /dev/null
@@ -1,61 +0,0 @@
-" Tests for character input and feedkeys() function.
-
-func Test_feedkeys_x_with_empty_string()
- new
- call feedkeys("ifoo\<Esc>")
- call assert_equal('', getline('.'))
- call feedkeys('', 'x')
- call assert_equal('foo', getline('.'))
-
- " check it goes back to normal mode immediately.
- call feedkeys('i', 'x')
- call assert_equal('foo', getline('.'))
- quit!
-endfunc
-
-func Test_feedkeys_with_abbreviation()
- new
- inoreabbrev trigger value
- call feedkeys("atrigger ", 'x')
- call feedkeys("atrigger ", 'x')
- call assert_equal('value value ', getline(1))
- bwipe!
- iunabbrev trigger
-endfunc
-
-func Test_feedkeys_escape_special()
- nnoremap … <Cmd>let g:got_ellipsis += 1<CR>
- call feedkeys('…', 't')
- call assert_equal('…', getcharstr())
- let g:got_ellipsis = 0
- call feedkeys('…', 'xt')
- call assert_equal(1, g:got_ellipsis)
- unlet g:got_ellipsis
- nunmap …
-endfunc
-
-func Test_input_simplify_ctrl_at()
- new
- " feeding unsimplified CTRL-@ should still trigger i_CTRL-@
- call feedkeys("ifoo\<Esc>A\<*C-@>x", 'xt')
- call assert_equal('foofo', getline(1))
- bw!
-endfunc
-
-func Test_input_simplify_noremap()
- call feedkeys("i\<*C-M>", 'nx')
- call assert_equal('', getline(1))
- call assert_equal([0, 2, 1, 0, 1], getcurpos())
- bw!
-endfunc
-
-func Test_input_simplify_timedout()
- inoremap <C-M>a b
- call feedkeys("i\<*C-M>", 'xt')
- call assert_equal('', getline(1))
- call assert_equal([0, 2, 1, 0, 1], getcurpos())
- iunmap <C-M>a
- bw!
-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
deleted file mode 100644
index ec1379a378..0000000000
--- a/src/nvim/testdir/test_ins_complete.vim
+++ /dev/null
@@ -1,2192 +0,0 @@
-" Test for insert completion
-
-source screendump.vim
-source check.vim
-source vim9.vim
-
-" Test for insert expansion
-func Test_ins_complete()
- edit test_ins_complete.vim
- " The files in the current directory interferes with the files
- " used by this test. So use a separate directory for the test.
- call mkdir('Xdir')
- cd Xdir
-
- set ff=unix
- call writefile(["test11\t36Gepeto\t/Tag/",
- \ "asd\ttest11file\t36G",
- \ "Makefile\tto\trun"], 'Xtestfile')
- call writefile(['', 'start of testfile',
- \ 'ru',
- \ 'run1',
- \ 'run2',
- \ 'STARTTEST',
- \ 'ENDTEST',
- \ 'end of testfile'], 'Xtestdata')
- set ff&
-
- enew!
- edit Xtestdata
- new
- call append(0, ['#include "Xtestfile"', ''])
- call cursor(2, 1)
-
- set cot=
- set cpt=.,w
- " add-expands (word from next line) from other window
- exe "normal iru\<C-N>\<C-N>\<C-X>\<C-N>\<Esc>\<C-A>"
- call assert_equal('run1 run3', getline('.'))
- " add-expands (current buffer first)
- exe "normal o\<C-P>\<C-X>\<C-N>"
- call assert_equal('run3 run3', getline('.'))
- " Local expansion, ends in an empty line (unless it becomes a global
- " expansion)
- exe "normal o\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>"
- call assert_equal('', getline('.'))
- " starts Local and switches to global add-expansion
- exe "normal o\<C-X>\<C-P>\<C-P>\<C-X>\<C-X>\<C-N>\<C-X>\<C-N>\<C-N>"
- call assert_equal('run1 run2', getline('.'))
-
- set cpt=.,\ ,w,i
- " i-add-expands and switches to local
- exe "normal OM\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-X>\<C-X>\<C-P>"
- call assert_equal("Makefile\tto\trun3", getline('.'))
- " add-expands lines (it would end in an empty line if it didn't ignore
- " itself)
- exe "normal o\<C-X>\<C-L>\<C-X>\<C-L>\<C-P>\<C-P>"
- call assert_equal("Makefile\tto\trun3", getline('.'))
- call assert_equal("Makefile\tto\trun3", getline(line('.') - 1))
-
- set cpt=kXtestfile
- " checks k-expansion, and file expansion (use Xtest11 instead of test11,
- " because TEST11.OUT may match first on DOS)
- write Xtest11.one
- write Xtest11.two
- exe "normal o\<C-N>\<Esc>IX\<Esc>A\<C-X>\<C-F>\<C-N>"
- call assert_equal('Xtest11.two', getline('.'))
-
- " use CTRL-X CTRL-F to complete Xtest11.one, remove it and then use CTRL-X
- " CTRL-F again to verify this doesn't cause trouble.
- exe "normal oXt\<C-X>\<C-F>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<C-X>\<C-F>"
- call assert_equal('Xtest11.one', getline('.'))
- normal ddk
-
- " Test for expanding a non-existing filename
- exe "normal oa1b2X3Y4\<C-X>\<C-F>"
- call assert_equal('a1b2X3Y4', getline('.'))
- normal ddk
-
- set cpt=w
- " checks make_cyclic in other window
- exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>"
- call assert_equal('STARTTEST', getline('.'))
-
- set cpt=u nohid
- " checks unloaded buffer expansion
- only
- exe "normal oEN\<C-N>"
- call assert_equal('ENDTEST', getline('.'))
- " checks adding mode abortion
- exe "normal ounl\<C-N>\<C-X>\<C-X>\<C-P>"
- call assert_equal('unless', getline('.'))
-
- set cpt=t,d def=^\\k* tags=Xtestfile notagbsearch
- " tag expansion, define add-expansion interrupted
- exe "normal o\<C-X>\<C-]>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>"
- call assert_equal('test11file 36Gepeto /Tag/ asd', getline('.'))
- " t-expansion
- exe "normal oa\<C-N>\<Esc>"
- call assert_equal('asd', getline('.'))
-
- %bw!
- call delete('Xtestfile')
- call delete('Xtest11.one')
- call delete('Xtest11.two')
- call delete('Xtestdata')
- set cpt& cot& def& tags& tagbsearch& hidden&
- cd ..
- call delete('Xdir', 'rf')
-endfunc
-
-func Test_omni_dash()
- func Omni(findstart, base)
- if a:findstart
- return 5
- else
- echom a:base
- return ['-help', '-v']
- endif
- endfunc
- set omnifunc=Omni
- new
- exe "normal Gofind -\<C-x>\<C-o>"
- call assert_equal("find -help", getline('$'))
-
- bwipe!
- delfunc Omni
- set omnifunc=
-endfunc
-
-func Test_omni_throw()
- let g:CallCount = 0
- func Omni(findstart, base)
- let g:CallCount += 1
- if a:findstart
- throw "he he he"
- endif
- endfunc
- set omnifunc=Omni
- new
- try
- exe "normal ifoo\<C-x>\<C-o>"
- call assert_false(v:true, 'command should have failed')
- catch
- call assert_exception('he he he')
- call assert_equal(1, g:CallCount)
- endtry
-
- bwipe!
- delfunc Omni
- unlet g:CallCount
- set omnifunc=
-endfunc
-
-func Test_completefunc_args()
- let s:args = []
- func! CompleteFunc(findstart, base)
- let s:args += [[a:findstart, empty(a:base)]]
- endfunc
- new
-
- set completefunc=CompleteFunc
- call feedkeys("i\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([1, 1], s:args[0])
- call assert_equal(0, s:args[1][0])
- set completefunc=
-
- let s:args = []
- set omnifunc=CompleteFunc
- call feedkeys("i\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([1, 1], s:args[0])
- call assert_equal(0, s:args[1][0])
- set omnifunc=
-
- bwipe!
- unlet s:args
- delfunc CompleteFunc
-endfunc
-
-func s:CompleteDone_CompleteFuncNone( findstart, base )
- throw 'skipped: Nvim does not support v:none'
- if a:findstart
- return 0
- endif
-
- return v:none
-endfunc
-
-func s:CompleteDone_CompleteFuncDict( findstart, base )
- if a:findstart
- return 0
- endif
-
- return {
- \ 'words': [
- \ {
- \ 'word': 'aword',
- \ 'abbr': 'wrd',
- \ 'menu': 'extra text',
- \ 'info': 'words are cool',
- \ 'kind': 'W',
- \ 'user_data': ['one', 'two']
- \ }
- \ ]
- \ }
-endfunc
-
-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( ['one', 'two'], v:completed_item[ 'user_data' ] )
-
- if a:pre
- call assert_equal('function', complete_info().mode)
- endif
-
- let s:called_completedone = 1
-endfunc
-
-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(['one', 'two'], v:completed_item[ 'user_data' ])
- call assert_true(s:called_completedone)
-
- let s:called_completedone = 0
- au! CompleteDone
-endfunc
-
-func s:CompleteDone_CompleteFuncDictNoUserData(findstart, base)
- if a:findstart
- return 0
- endif
-
- return {
- \ 'words': [
- \ {
- \ 'word': 'aword',
- \ 'abbr': 'wrd',
- \ 'menu': 'extra text',
- \ 'info': 'words are cool',
- \ 'kind': 'W',
- \ }
- \ ]
- \ }
-endfunc
-
-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' ] )
- call assert_equal( 'words are cool', v:completed_item[ 'info' ] )
- call assert_equal( 'W', v:completed_item[ 'kind' ] )
- call assert_equal( '', v:completed_item[ 'user_data' ] )
-
- let s:called_completedone = 1
-endfunc
-
-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)
-
- let s:called_completedone = 0
- au! CompleteDone
-endfunc
-
-func s:CompleteDone_CompleteFuncList(findstart, base)
- if a:findstart
- return 0
- endif
-
- return [ 'aword' ]
-endfunc
-
-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' ] )
- call assert_equal( '', v:completed_item[ 'info' ] )
- call assert_equal( '', v:completed_item[ 'kind' ] )
- call assert_equal( '', v:completed_item[ 'user_data' ] )
-
- let s:called_completedone = 1
-endfunc
-
-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)
-
- let s:called_completedone = 0
- au! CompleteDone
-endfunc
-
-func Test_CompleteDone_undo()
- au CompleteDone * call append(0, "prepend1")
- new
- call setline(1, ["line1", "line2"])
- call feedkeys("Go\<C-X>\<C-N>\<CR>\<ESC>", "tx")
- call assert_equal(["prepend1", "line1", "line2", "line1", ""],
- \ getline(1, '$'))
- undo
- call assert_equal(["line1", "line2"], getline(1, '$'))
- bwipe!
- au! CompleteDone
-endfunc
-
-func Test_CompleteDone_modify()
- let value = {
- \ 'word': '',
- \ 'abbr': '',
- \ 'menu': '',
- \ 'info': '',
- \ 'kind': '',
- \ 'user_data': '',
- \ }
- let v:completed_item = value
- call assert_equal(value, v:completed_item)
-endfunc
-
-func CompleteTest(findstart, query)
- if a:findstart
- return col('.')
- endif
- return ['matched']
-endfunc
-
-func Test_completefunc_info()
- new
- set completeopt=menuone
- set completefunc=CompleteTest
- call feedkeys("i\<C-X>\<C-U>\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
- call assert_equal("matched{'pum_visible': 1, 'mode': 'function', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1))
- bwipe!
- set completeopt&
- set completefunc&
-endfunc
-
-" Check that when using feedkeys() typeahead does not interrupt searching for
-" completions.
-func Test_compl_feedkeys()
- new
- set completeopt=menuone,noselect
- call feedkeys("ajump ju\<C-X>\<C-N>\<C-P>\<ESC>", "tx")
- call assert_equal("jump jump", getline(1))
- bwipe!
- set completeopt&
-endfunc
-
-func s:ComplInCmdwin_GlobalCompletion(a, l, p)
- return 'global'
-endfunc
-
-func s:ComplInCmdwin_LocalCompletion(a, l, p)
- return 'local'
-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 w:test_winvar = 'winvar'
- let b:test_bufvar = 'bufvar'
-
- " User-defined commands
- 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)
-
- com! -nargs=1 -complete=var GetInput let input = <q-args>
- " Window-local variables
- let input = ''
- call feedkeys("q:iGetInput w:test_\<C-x>\<C-v>\<CR>", 'tx!')
- call assert_equal('w:test_winvar', input)
-
- let input = ''
- call feedkeys("q::GetInput w:test_\<Tab>\<CR>:q\<CR>", 'tx!')
- call assert_equal('w:test_', input)
-
- " Buffer-local variables
- let input = ''
- call feedkeys("q:iGetInput b:test_\<C-x>\<C-v>\<CR>", 'tx!')
- call assert_equal('b:test_bufvar', input)
-
- let input = ''
- call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!')
- call assert_equal('b:test_', input)
-
-
- " Argument completion of buffer-local command
- func s:ComplInCmdwin_GlobalCompletionList(a, l, p)
- return ['global']
- endfunc
-
- func s:ComplInCmdwin_LocalCompletionList(a, l, p)
- return ['local']
- endfunc
-
- func s:ComplInCmdwin_CheckCompletion(arg)
- call assert_equal('local', a:arg)
- endfunc
-
- com! -nargs=1 -complete=custom,<SID>ComplInCmdwin_GlobalCompletion
- \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
- com! -buffer -nargs=1 -complete=custom,<SID>ComplInCmdwin_LocalCompletion
- \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
- call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
-
- com! -nargs=1 -complete=customlist,<SID>ComplInCmdwin_GlobalCompletionList
- \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
- com! -buffer -nargs=1 -complete=customlist,<SID>ComplInCmdwin_LocalCompletionList
- \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
-
- call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
-
- func! s:ComplInCmdwin_CheckCompletion(arg)
- call assert_equal('global', a:arg)
- endfunc
- new
- call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
- quit
-
- delfunc s:ComplInCmdwin_GlobalCompletion
- delfunc s:ComplInCmdwin_LocalCompletion
- delfunc s:ComplInCmdwin_GlobalCompletionList
- delfunc s:ComplInCmdwin_LocalCompletionList
- delfunc s:ComplInCmdwin_CheckCompletion
-
- delcom -buffer TestCommand
- delcom TestCommand
- delcom GetInput
- unlet w:test_winvar
- unlet b:test_bufvar
- set wildmenu& wildchar&
-endfunc
-
-" Test for insert path completion with completeslash option
-func Test_ins_completeslash()
- CheckMSWindows
-
- call mkdir('Xdir')
-
- let orig_shellslash = &shellslash
- set cpt&
-
- new
-
- set noshellslash
-
- set completeslash=
- exe "normal oXd\<C-X>\<C-F>"
- call assert_equal('Xdir\', getline('.'))
-
- set completeslash=backslash
- exe "normal oXd\<C-X>\<C-F>"
- call assert_equal('Xdir\', getline('.'))
-
- set completeslash=slash
- exe "normal oXd\<C-X>\<C-F>"
- call assert_equal('Xdir/', getline('.'))
-
- set shellslash
-
- set completeslash=
- exe "normal oXd\<C-X>\<C-F>"
- call assert_equal('Xdir/', getline('.'))
-
- set completeslash=backslash
- exe "normal oXd\<C-X>\<C-F>"
- call assert_equal('Xdir\', getline('.'))
-
- set completeslash=slash
- exe "normal oXd\<C-X>\<C-F>"
- call assert_equal('Xdir/', getline('.'))
- %bw!
- call delete('Xdir', 'rf')
-
- set noshellslash
- set completeslash=slash
- call assert_true(stridx(globpath(&rtp, 'syntax/*.vim', 1, 1)[0], '\') != -1)
-
- let &shellslash = orig_shellslash
- set completeslash=
-endfunc
-
-func Test_pum_stopped_by_timer()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['hello', 'hullo', 'heeee', ''])
- func StartCompl()
- call timer_start(100, { -> execute('stopinsert') })
- call feedkeys("Gah\<C-N>")
- endfunc
- END
-
- call writefile(lines, 'Xpumscript')
- let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12})
- call term_sendkeys(buf, ":call StartCompl()\<CR>")
- call TermWait(buf, 200)
- call term_sendkeys(buf, "k")
- call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {})
-
- call StopVimInTerminal(buf)
- call delete('Xpumscript')
-endfunc
-
-func Test_complete_stopinsert_startinsert()
- nnoremap <F2> <Cmd>startinsert<CR>
- inoremap <F2> <Cmd>stopinsert<CR>
- " This just checks if this causes an error
- call feedkeys("i\<C-X>\<C-N>\<F2>\<F2>", 'x')
- nunmap <F2>
- iunmap <F2>
-endfunc
-
-func Test_pum_with_folds_two_tabs()
- CheckScreendump
-
- let lines =<< trim END
- set fdm=marker
- call setline(1, ['" x {{{1', '" a some text'])
- call setline(3, range(&lines)->map({_, val -> '" a' .. val}))
- norm! zm
- tab sp
- call feedkeys('2Gzv', 'xt')
- call feedkeys("0fa", 'xt')
- END
-
- call writefile(lines, 'Xpumscript')
- let buf = RunVimInTerminal('-S Xpumscript', #{rows: 10})
- call term_wait(buf, 100)
- call term_sendkeys(buf, "a\<C-N>")
- call VerifyScreenDump(buf, 'Test_pum_with_folds_two_tabs', {})
-
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
- call delete('Xpumscript')
-endfunc
-
-func Test_pum_with_preview_win()
- CheckScreendump
-
- let lines =<< trim END
- funct Omni_test(findstart, base)
- if a:findstart
- return col(".") - 1
- endif
- return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}]
- endfunc
- set omnifunc=Omni_test
- set completeopt+=longest
- END
-
- call writefile(lines, 'Xpreviewscript')
- let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12})
- call term_wait(buf, 100)
- call term_sendkeys(buf, "Gi\<C-X>\<C-O>")
- call term_wait(buf, 200)
- call term_sendkeys(buf, "\<C-N>")
- call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {})
-
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
- call delete('Xpreviewscript')
-endfunc
-
-" Test for inserting the tag search pattern in insert mode
-func Test_ins_compl_tag_sft()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo\t/^int first() {}$/",
- \ "second\tXfoo\t/^int second() {}$/",
- \ "third\tXfoo\t/^int third() {}$/"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int second() {}
- int third() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- enew
- set showfulltag
- exe "normal isec\<C-X>\<C-]>\<C-N>\<CR>"
- call assert_equal('int second() {}', getline(1))
- set noshowfulltag
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe!
-endfunc
-
-" Test for 'completefunc' deleting text
-func Test_completefunc_error()
- new
- " delete text when called for the first time
- func CompleteFunc(findstart, base)
- if a:findstart == 1
- normal dd
- return col('.') - 1
- endif
- return ['a', 'b']
- endfunc
- set completefunc=CompleteFunc
- call setline(1, ['', 'abcd', ''])
- call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:')
-
- " delete text when called for the second time
- func CompleteFunc2(findstart, base)
- if a:findstart == 1
- return col('.') - 1
- endif
- normal dd
- return ['a', 'b']
- endfunc
- set completefunc=CompleteFunc2
- call setline(1, ['', 'abcd', ''])
- call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:')
-
- " Jump to a different window from the complete function
- func CompleteFunc3(findstart, base)
- if a:findstart == 1
- return col('.') - 1
- endif
- wincmd p
- return ['a', 'b']
- endfunc
- set completefunc=CompleteFunc3
- new
- call assert_fails('exe "normal a\<C-X>\<C-U>"', 'E565:')
- close!
-
- set completefunc&
- delfunc CompleteFunc
- delfunc CompleteFunc2
- delfunc CompleteFunc3
- close!
-endfunc
-
-" Test for returning non-string values from 'completefunc'
-func Test_completefunc_invalid_data()
- new
- func! CompleteFunc(findstart, base)
- if a:findstart == 1
- return col('.') - 1
- endif
- return [{}, '', 'moon']
- endfunc
- set completefunc=CompleteFunc
- exe "normal i\<C-X>\<C-U>"
- call assert_equal('moon', getline(1))
- set completefunc&
- close!
-endfunc
-
-" Test for errors in using complete() function
-func Test_complete_func_error()
- call assert_fails('call complete(1, ["a"])', 'E785:')
- func ListColors()
- call complete(col('.'), "blue")
- endfunc
- call assert_fails('exe "normal i\<C-R>=ListColors()\<CR>"', 'E474:')
- func ListMonths()
- call complete(col('.'), test_null_list())
- endfunc
- " Nvim allows a NULL list
- " call assert_fails('exe "normal i\<C-R>=ListMonths()\<CR>"', 'E474:')
- delfunc ListColors
- delfunc ListMonths
- call assert_fails('call complete_info({})', 'E714:')
- call assert_equal([], complete_info(['items']).items)
-endfunc
-
-" Test for recursively starting completion mode using complete()
-func Test_recursive_complete_func()
- func ListColors()
- call complete(5, ["red", "blue"])
- return ''
- endfunc
- new
- call setline(1, ['a1', 'a2'])
- set complete=.
- exe "normal Goa\<C-X>\<C-L>\<C-R>=ListColors()\<CR>\<C-N>"
- call assert_equal('a2blue', getline(3))
- delfunc ListColors
- bw!
-endfunc
-
-" Test for using complete() with completeopt+=longest
-func Test_complete_with_longest()
- new
- inoremap <buffer> <f3> <cmd>call complete(1, ["iaax", "iaay", "iaaz"])<cr>
-
- " default: insert first match
- set completeopt&
- call setline(1, ['i'])
- exe "normal Aa\<f3>\<esc>"
- call assert_equal('iaax', getline(1))
-
- " with longest: insert longest prefix
- set completeopt+=longest
- call setline(1, ['i'])
- exe "normal Aa\<f3>\<esc>"
- call assert_equal('iaa', getline(1))
- set completeopt&
- bwipe!
-endfunc
-
-
-" Test for completing words following a completed word in a line
-func Test_complete_wrapscan()
- " complete words from another buffer
- new
- call setline(1, ['one two', 'three four'])
- new
- setlocal complete=w
- call feedkeys("itw\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>", 'xt')
- call assert_equal('two three four', getline(1))
- close!
- " complete words from the current buffer
- setlocal complete=.
- %d
- call setline(1, ['one two', ''])
- call cursor(2, 1)
- call feedkeys("ion\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>", 'xt')
- call assert_equal('one two one two', getline(2))
- close!
-endfunc
-
-" Test for completing special characters
-func Test_complete_special_chars()
- new
- call setline(1, 'int .*[-\^$ func float')
- call feedkeys("oin\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>", 'xt')
- call assert_equal('int .*[-\^$ func float', getline(2))
- close!
-endfunc
-
-" Test for completion when text is wrapped across lines.
-func Test_complete_across_line()
- new
- call setline(1, ['red green blue', 'one two three'])
- setlocal textwidth=20
- exe "normal 2G$a re\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
- call assert_equal(['one two three red', 'green blue one'], getline(2, '$'))
- close!
-endfunc
-
-" Test for completing words with a '.' at the end of a word.
-func Test_complete_joinspaces()
- new
- call setline(1, ['one two.', 'three. four'])
- set joinspaces
- exe "normal Goon\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
- call assert_equal("one two. three. four", getline(3))
- set joinspaces&
- bw!
-endfunc
-
-" Test for using CTRL-L to add one character when completing matching
-func Test_complete_add_onechar()
- new
- call setline(1, ['wool', 'woodwork'])
- call feedkeys("Gowoo\<C-P>\<C-P>\<C-P>\<C-L>f", 'xt')
- call assert_equal('woof', getline(3))
-
- " use 'ignorecase' and backspace to erase characters from the prefix string
- " and then add letters using CTRL-L
- %d
- set ignorecase backspace=2
- setlocal complete=.
- call setline(1, ['workhorse', 'workload'])
- normal Go
- exe "normal aWOR\<C-P>\<bs>\<bs>\<bs>\<bs>\<bs>\<bs>\<C-L>r\<C-L>\<C-L>"
- call assert_equal('workh', getline(3))
- set ignorecase& backspace&
- close!
-endfunc
-
-" Test for using CTRL-X CTRL-L to complete whole lines lines
-func Test_complete_wholeline()
- new
- " complete one-line
- call setline(1, ['a1', 'a2'])
- exe "normal ggoa\<C-X>\<C-L>"
- call assert_equal(['a1', 'a1', 'a2'], getline(1, '$'))
- " go to the next match (wrapping around the buffer)
- exe "normal 2GCa\<C-X>\<C-L>\<C-N>"
- call assert_equal(['a1', 'a', 'a2'], getline(1, '$'))
- " go to the next match
- exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>"
- call assert_equal(['a1', 'a2', 'a2'], getline(1, '$'))
- exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>\<C-N>"
- call assert_equal(['a1', 'a1', 'a2'], getline(1, '$'))
- " repeat the test using CTRL-L
- " go to the next match (wrapping around the buffer)
- exe "normal 2GCa\<C-X>\<C-L>\<C-L>"
- call assert_equal(['a1', 'a2', 'a2'], getline(1, '$'))
- " go to the next match
- exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>"
- call assert_equal(['a1', 'a', 'a2'], getline(1, '$'))
- exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>\<C-L>"
- call assert_equal(['a1', 'a1', 'a2'], getline(1, '$'))
- %d
- " use CTRL-X CTRL-L to add one more line
- call setline(1, ['a1', 'b1'])
- setlocal complete=.
- exe "normal ggOa\<C-X>\<C-L>\<C-X>\<C-L>\<C-X>\<C-L>"
- call assert_equal(['a1', 'b1', '', 'a1', 'b1'], getline(1, '$'))
- bw!
-endfunc
-
-" Test insert completion with 'cindent' (adjust the indent)
-func Test_complete_with_cindent()
- new
- setlocal cindent
- call setline(1, ['if (i == 1)', " j = 2;"])
- exe "normal Go{\<CR>i\<C-X>\<C-L>\<C-X>\<C-L>\<CR>}"
- call assert_equal(['{', "\tif (i == 1)", "\t\tj = 2;", '}'], getline(3, '$'))
-
- %d
- call setline(1, ['when while', '{', ''])
- setlocal cinkeys+==while
- exe "normal Giwh\<C-P> "
- call assert_equal("\twhile ", getline('$'))
- close!
-endfunc
-
-" Test for <CTRL-X> <CTRL-V> completion. Complete commands and functions
-func Test_complete_cmdline()
- new
- exe "normal icaddb\<C-X>\<C-V>"
- call assert_equal('caddbuffer', getline(1))
- exe "normal ocall getqf\<C-X>\<C-V>"
- call assert_equal('call getqflist(', getline(2))
- exe "normal oabcxyz(\<C-X>\<C-V>"
- call assert_equal('abcxyz(', getline(3))
- com! -buffer TestCommand1 echo 'TestCommand1'
- com! -buffer TestCommand2 echo 'TestCommand2'
- write TestCommand1Test
- write TestCommand2Test
- " Test repeating <CTRL-X> <CTRL-V> and switching to another CTRL-X mode
- exe "normal oT\<C-X>\<C-V>\<C-X>\<C-V>\<C-X>\<C-F>\<Esc>"
- call assert_equal('TestCommand2Test', getline(4))
- call delete('TestCommand1Test')
- call delete('TestCommand2Test')
- delcom TestCommand1
- delcom TestCommand2
- close!
-endfunc
-
-" Test for <CTRL-X> <CTRL-Z> stopping completion without changing the match
-func Test_complete_stop()
- new
- func Save_mode1()
- let g:mode1 = mode(1)
- return ''
- endfunc
- func Save_mode2()
- let g:mode2 = mode(1)
- return ''
- endfunc
- inoremap <F1> <C-R>=Save_mode1()<CR>
- inoremap <F2> <C-R>=Save_mode2()<CR>
- call setline(1, ['aaa bbb ccc '])
- exe "normal A\<C-N>\<C-P>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
- call assert_equal('ic', g:mode1)
- call assert_equal('i', g:mode2)
- call assert_equal('aaa bbb ccc ', getline(1))
- exe "normal A\<C-N>\<Down>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
- call assert_equal('ic', g:mode1)
- call assert_equal('i', g:mode2)
- call assert_equal('aaa bbb ccc aaa', getline(1))
- set completeopt+=noselect
- exe "normal A \<C-N>\<Down>\<Down>\<C-L>\<C-L>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
- call assert_equal('ic', g:mode1)
- call assert_equal('i', g:mode2)
- call assert_equal('aaa bbb ccc aaa bb', getline(1))
- set completeopt&
- exe "normal A d\<C-N>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
- call assert_equal('ic', g:mode1)
- call assert_equal('i', g:mode2)
- call assert_equal('aaa bbb ccc aaa bb d', getline(1))
- com! -buffer TestCommand1 echo 'TestCommand1'
- com! -buffer TestCommand2 echo 'TestCommand2'
- exe "normal oT\<C-X>\<C-V>\<C-X>\<C-V>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
- call assert_equal('ic', g:mode1)
- call assert_equal('i', g:mode2)
- call assert_equal('TestCommand2', getline(2))
- delcom TestCommand1
- delcom TestCommand2
- unlet g:mode1
- unlet g:mode2
- iunmap <F1>
- iunmap <F2>
- delfunc Save_mode1
- delfunc Save_mode2
- close!
-endfunc
-
-" Test for typing CTRL-R in insert completion mode to insert a register
-" content.
-func Test_complete_reginsert()
- new
- call setline(1, ['a1', 'a12', 'a123', 'a1234'])
-
- " if a valid CTRL-X mode key is returned from <C-R>=, then it should be
- " processed. Otherwise, CTRL-X mode should be stopped and the key should be
- " inserted.
- exe "normal Goa\<C-P>\<C-R>=\"\\<C-P>\"\<CR>"
- call assert_equal('a123', getline(5))
- let @r = "\<C-P>\<C-P>"
- exe "normal GCa\<C-P>\<C-R>r"
- call assert_equal('a12', getline(5))
- exe "normal GCa\<C-P>\<C-R>=\"x\"\<CR>"
- call assert_equal('a1234x', getline(5))
- bw!
-endfunc
-
-func Test_issue_7021()
- CheckMSWindows
-
- let orig_shellslash = &shellslash
- set noshellslash
-
- set completeslash=slash
- call assert_false(expand('~') =~ '/')
-
- let &shellslash = orig_shellslash
- set completeslash=
-endfunc
-
-" Test for 'longest' setting in 'completeopt' with latin1 and utf-8 encodings
-func Test_complete_longest_match()
- " for e in ['latin1', 'utf-8']
- for e in ['utf-8']
- exe 'set encoding=' .. e
- new
- set complete=.
- set completeopt=menu,longest
- call setline(1, ['pfx_a1', 'pfx_a12', 'pfx_a123', 'pfx_b1'])
- exe "normal Gopfx\<C-P>"
- call assert_equal('pfx_', getline(5))
- bw!
- endfor
-
- " Test for completing additional words with longest match set
- new
- call setline(1, ['abc1', 'abd2'])
- exe "normal Goab\<C-P>\<C-X>\<C-P>"
- call assert_equal('ab', getline(3))
- bw!
- set complete& completeopt&
-endfunc
-
-" Test for removing the first displayed completion match and selecting the
-" match just before that.
-func Test_complete_erase_firstmatch()
- new
- call setline(1, ['a12', 'a34', 'a56'])
- set complete=.
- exe "normal Goa\<C-P>\<BS>\<BS>3\<CR>"
- call assert_equal('a34', getline('$'))
- set complete&
- bw!
-endfunc
-
-" Test for completing words from unloaded buffers
-func Test_complete_from_unloadedbuf()
- call writefile(['abc'], "Xfile1")
- call writefile(['def'], "Xfile2")
- edit Xfile1
- edit Xfile2
- new | close
- enew
- bunload Xfile1 Xfile2
- set complete=u
- " complete from an unloaded buffer
- exe "normal! ia\<C-P>"
- call assert_equal('abc', getline(1))
- exe "normal! od\<C-P>"
- call assert_equal('def', getline(2))
- set complete&
- %bw!
- call delete("Xfile1")
- call delete("Xfile2")
-endfunc
-
-" Test for completing whole lines from unloaded buffers
-func Test_complete_wholeline_unloadedbuf()
- call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
- edit Xfile1
- enew
- set complete=u
- exe "normal! ia\<C-X>\<C-L>\<C-P>"
- call assert_equal('a line2', getline(1))
- %d
- " completing from an unlisted buffer should fail
- bdel Xfile1
- exe "normal! ia\<C-X>\<C-L>\<C-P>"
- call assert_equal('a', getline(1))
- set complete&
- %bw!
- call delete("Xfile1")
-endfunc
-
-" Test for completing words from unlisted buffers
-func Test_complete_from_unlistedbuf()
- call writefile(['abc'], "Xfile1")
- call writefile(['def'], "Xfile2")
- edit Xfile1
- edit Xfile2
- new | close
- bdel Xfile1 Xfile2
- set complete=U
- " complete from an unlisted buffer
- exe "normal! ia\<C-P>"
- call assert_equal('abc', getline(1))
- exe "normal! od\<C-P>"
- call assert_equal('def', getline(2))
- set complete&
- %bw!
- call delete("Xfile1")
- call delete("Xfile2")
-endfunc
-
-" Test for completing whole lines from unlisted buffers
-func Test_complete_wholeline_unlistedbuf()
- call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
- edit Xfile1
- enew
- set complete=U
- " completing from a unloaded buffer should fail
- exe "normal! ia\<C-X>\<C-L>\<C-P>"
- call assert_equal('a', getline(1))
- %d
- bdel Xfile1
- exe "normal! ia\<C-X>\<C-L>\<C-P>"
- call assert_equal('a line2', getline(1))
- set complete&
- %bw!
- call delete("Xfile1")
-endfunc
-
-" Test for adding a multibyte character using CTRL-L in completion mode
-func Test_complete_mbyte_char_add()
- new
- set complete=.
- call setline(1, 'abÄ—')
- exe "normal! oa\<C-P>\<BS>\<BS>\<C-L>\<C-L>"
- call assert_equal('abÄ—', getline(2))
- " Test for a leader with multibyte character
- %d
- call setline(1, 'abÄ—Ä•')
- exe "normal! oabÄ—\<C-P>"
- call assert_equal('abÄ—Ä•', getline(2))
- bw!
-endfunc
-
-" Test for using <C-X><C-P> for local expansion even if 'complete' is set to
-" not to complete matches from the local buffer. Also test using multiple
-" <C-X> to cancel the current completion mode.
-func Test_complete_local_expansion()
- new
- set complete=t
- call setline(1, ['abc', 'def'])
- exe "normal! Go\<C-X>\<C-P>"
- call assert_equal("def", getline(3))
- exe "normal! Go\<C-P>"
- call assert_equal("", getline(4))
- exe "normal! Go\<C-X>\<C-N>"
- call assert_equal("abc", getline(5))
- exe "normal! Go\<C-N>"
- call assert_equal("", getline(6))
-
- " use multiple <C-X> to cancel the previous completion mode
- exe "normal! Go\<C-P>\<C-X>\<C-P>"
- call assert_equal("", getline(7))
- exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-P>"
- call assert_equal("", getline(8))
- exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-X>\<C-P>"
- call assert_equal("abc", getline(9))
-
- " interrupt the current completion mode
- set completeopt=menu,noinsert
- exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-X>\<C-P>\<C-Y>"
- call assert_equal("abc", getline(10))
-
- " when only one <C-X> is used to interrupt, do normal expansion
- exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-P>"
- call assert_equal("", getline(11))
- set completeopt&
-
- " using two <C-X> in non-completion mode and restarting the same mode
- exe "normal! God\<C-X>\<C-X>\<C-P>\<C-X>\<C-X>\<C-P>\<C-Y>"
- call assert_equal("def", getline(12))
-
- " test for adding a match from the original empty text
- %d
- call setline(1, 'abc def g')
- exe "normal! o\<C-X>\<C-P>\<C-N>\<C-X>\<C-P>"
- call assert_equal('def', getline(2))
- exe "normal! 0C\<C-X>\<C-N>\<C-P>\<C-X>\<C-N>"
- call assert_equal('abc', getline(2))
-
- bw!
-endfunc
-
-" Test for undoing changes after a insert-mode completion
-func Test_complete_undo()
- new
- set complete=.
- " undo with 'ignorecase'
- call setline(1, ['ABOVE', 'BELOW'])
- set ignorecase
- exe "normal! Goab\<C-G>u\<C-P>"
- call assert_equal("ABOVE", getline(3))
- undo
- call assert_equal("ab", getline(3))
- set ignorecase&
- %d
- " undo with longest match
- set completeopt=menu,longest
- call setline(1, ['above', 'about'])
- exe "normal! Goa\<C-G>u\<C-P>"
- call assert_equal("abo", getline(3))
- undo
- call assert_equal("a", getline(3))
- set completeopt&
- %d
- " undo for line completion
- call setline(1, ['above that change', 'below that change'])
- exe "normal! Goabove\<C-G>u\<C-X>\<C-L>"
- call assert_equal("above that change", getline(3))
- undo
- call assert_equal("above", getline(3))
-
- bw!
-endfunc
-
-" Test for completing a very long word
-func Test_complete_long_word()
- set complete&
- new
- call setline(1, repeat('x', 950) .. ' one two three')
- exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
- call assert_equal(repeat('x', 950) .. ' one two three', getline(2))
- %d
- " should fail when more than 950 characters are in a word
- call setline(1, repeat('x', 951) .. ' one two three')
- exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
- call assert_equal(repeat('x', 951), getline(2))
-
- " Test for adding a very long word to an existing completion
- %d
- call setline(1, ['abc', repeat('x', 1016) .. '012345'])
- exe "normal! Goab\<C-P>\<C-X>\<C-P>"
- call assert_equal('abc ' .. repeat('x', 1016) .. '0123', getline(3))
- bw!
-endfunc
-
-" Test for some fields in the complete items used by complete()
-func Test_complete_items()
- func CompleteItems(idx)
- let items = [[#{word: "one", dup: 1, user_data: 'u1'}, #{word: "one", dup: 1, user_data: 'u2'}],
- \ [#{word: "one", dup: 0, user_data: 'u3'}, #{word: "one", dup: 0, user_data: 'u4'}],
- \ [#{word: "one", icase: 1, user_data: 'u7'}, #{word: "oNE", icase: 1, user_data: 'u8'}],
- \ [#{user_data: 'u9'}],
- \ [#{word: "", user_data: 'u10'}],
- \ [#{word: "", empty: 1, user_data: 'u11'}]]
- call complete(col('.'), items[a:idx])
- return ''
- endfunc
- new
- exe "normal! i\<C-R>=CompleteItems(0)\<CR>\<C-N>\<C-Y>"
- call assert_equal('u2', v:completed_item.user_data)
- call assert_equal('one', getline(1))
- exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-Y>"
- call assert_equal('u3', v:completed_item.user_data)
- call assert_equal('one', getline(2))
- exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-N>"
- call assert_equal('', getline(3))
- set completeopt=menu,noinsert
- exe "normal! o\<C-R>=CompleteItems(2)\<CR>one\<C-N>\<C-Y>"
- call assert_equal('oNE', getline(4))
- call assert_equal('u8', v:completed_item.user_data)
- set completeopt&
- exe "normal! o\<C-R>=CompleteItems(3)\<CR>"
- call assert_equal('', getline(5))
- exe "normal! o\<C-R>=CompleteItems(4)\<CR>"
- call assert_equal('', getline(6))
- exe "normal! o\<C-R>=CompleteItems(5)\<CR>"
- call assert_equal('', getline(7))
- call assert_equal('u11', v:completed_item.user_data)
- " pass invalid argument to complete()
- let cmd = "normal! o\<C-R>=complete(1, [[]])\<CR>"
- call assert_fails('exe cmd', 'E730:')
- bw!
- delfunc CompleteItems
-endfunc
-
-" Test for the "refresh" item in the dict returned by an insert completion
-" function
-func Test_complete_item_refresh_always()
- let g:CallCount = 0
- func! Tcomplete(findstart, base)
- if a:findstart
- " locate the start of the word
- let line = getline('.')
- let start = col('.') - 1
- while start > 0 && line[start - 1] =~ '\a'
- let start -= 1
- endwhile
- return start
- else
- let g:CallCount += 1
- let res = ["update1", "update12", "update123"]
- return #{words: res, refresh: 'always'}
- endif
- endfunc
- new
- set completeopt=menu,longest
- set completefunc=Tcomplete
- exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>"
- call assert_equal('up', getline(1))
- call assert_equal(2, g:CallCount)
- set completeopt&
- set completefunc&
- bw!
- delfunc Tcomplete
-endfunc
-
-" Test for completing from a thesaurus file without read permission
-func Test_complete_unreadable_thesaurus_file()
- CheckUnix
- CheckNotRoot
-
- call writefile(['about', 'above'], 'Xfile')
- call setfperm('Xfile', '---r--r--')
- new
- set complete=sXfile
- exe "normal! ia\<C-P>"
- call assert_equal('a', getline(1))
- bw!
- call delete('Xfile')
- set complete&
-endfunc
-
-" Test to ensure 'Scanning...' messages are not recorded in messages history
-func Test_z1_complete_no_history()
- new
- messages clear
- let currmess = execute('messages')
- setlocal dictionary=README.txt
- exe "normal owh\<C-X>\<C-K>"
- exe "normal owh\<C-N>"
- call assert_equal(currmess, execute('messages'))
- bwipe!
-endfunc
-
-" A mapping is not used for the key after CTRL-X.
-func Test_no_mapping_for_ctrl_x_key()
- new
- inoremap <buffer> <C-K> <Cmd>let was_mapped = 'yes'<CR>
- setlocal dictionary=README.txt
- call feedkeys("aexam\<C-X>\<C-K> ", 'xt')
- call assert_equal('example ', getline(1))
- call assert_false(exists('was_mapped'))
- bwipe!
-endfunc
-
-" Test for different ways of setting the 'completefunc' option
-func Test_completefunc_callback()
- func CompleteFunc1(callnr, findstart, base)
- call add(g:CompleteFunc1Args, [a:callnr, a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
- func CompleteFunc2(findstart, base)
- call add(g:CompleteFunc2Args, [a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
-
- let lines =<< trim END
- #" Test for using a global function name
- LET &completefunc = 'g:CompleteFunc2'
- new
- call setline(1, 'global')
- LET g:CompleteFunc2Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'global']], g:CompleteFunc2Args)
- bw!
-
- #" Test for using a function()
- set completefunc=function('g:CompleteFunc1',\ [10])
- new
- call setline(1, 'one')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[10, 1, ''], [10, 0, 'one']], g:CompleteFunc1Args)
- bw!
-
- #" Using a funcref variable to set 'completefunc'
- VAR Fn = function('g:CompleteFunc1', [11])
- LET &completefunc = Fn
- new
- call setline(1, 'two')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[11, 1, ''], [11, 0, 'two']], g:CompleteFunc1Args)
- bw!
-
- #" Using string(funcref_variable) to set 'completefunc'
- LET Fn = function('g:CompleteFunc1', [12])
- LET &completefunc = string(Fn)
- new
- call setline(1, 'two')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[12, 1, ''], [12, 0, 'two']], g:CompleteFunc1Args)
- bw!
-
- #" Test for using a funcref()
- set completefunc=funcref('g:CompleteFunc1',\ [13])
- new
- call setline(1, 'three')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[13, 1, ''], [13, 0, 'three']], g:CompleteFunc1Args)
- bw!
-
- #" Using a funcref variable to set 'completefunc'
- LET Fn = funcref('g:CompleteFunc1', [14])
- LET &completefunc = Fn
- new
- call setline(1, 'four')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[14, 1, ''], [14, 0, 'four']], g:CompleteFunc1Args)
- bw!
-
- #" Using a string(funcref_variable) to set 'completefunc'
- LET Fn = funcref('g:CompleteFunc1', [15])
- LET &completefunc = string(Fn)
- new
- call setline(1, 'four')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[15, 1, ''], [15, 0, 'four']], g:CompleteFunc1Args)
- bw!
-
- #" Test for using a lambda function with set
- VAR optval = "LSTART a, b LMIDDLE CompleteFunc1(16, a, b) LEND"
- LET optval = substitute(optval, ' ', '\\ ', 'g')
- exe "set completefunc=" .. optval
- new
- call setline(1, 'five')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[16, 1, ''], [16, 0, 'five']], g:CompleteFunc1Args)
- bw!
-
- #" Set 'completefunc' to a lambda expression
- LET &completefunc = LSTART a, b LMIDDLE CompleteFunc1(17, a, b) LEND
- new
- call setline(1, 'six')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[17, 1, ''], [17, 0, 'six']], g:CompleteFunc1Args)
- bw!
-
- #" Set 'completefunc' to string(lambda_expression)
- LET &completefunc = 'LSTART a, b LMIDDLE CompleteFunc1(18, a, b) LEND'
- new
- call setline(1, 'six')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[18, 1, ''], [18, 0, 'six']], g:CompleteFunc1Args)
- bw!
-
- #" Set 'completefunc' to a variable with a lambda expression
- VAR Lambda = LSTART a, b LMIDDLE CompleteFunc1(19, a, b) LEND
- LET &completefunc = Lambda
- new
- call setline(1, 'seven')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:CompleteFunc1Args)
- bw!
-
- #" Set 'completefunc' to a string(variable with a lambda expression)
- LET Lambda = LSTART a, b LMIDDLE CompleteFunc1(20, a, b) LEND
- LET &completefunc = string(Lambda)
- new
- call setline(1, 'seven')
- LET g:CompleteFunc1Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:CompleteFunc1Args)
- bw!
-
- #" Test for using a lambda function with incorrect return value
- LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
- LET &completefunc = Lambda
- new
- call setline(1, 'eight')
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- bw!
-
- #" Test for clearing the 'completefunc' option
- set completefunc=''
- set completefunc&
- call assert_fails("set completefunc=function('abc')", "E700:")
- call assert_fails("set completefunc=funcref('abc')", "E700:")
-
- #" set 'completefunc' to a non-existing function
- set completefunc=CompleteFunc2
- call setline(1, 'five')
- call assert_fails("set completefunc=function('NonExistingFunc')", 'E700:')
- call assert_fails("LET &completefunc = function('NonExistingFunc')", 'E700:')
- LET g:CompleteFunc2Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'five']], g:CompleteFunc2Args)
- bw!
- END
- call CheckLegacyAndVim9Success(lines)
-
- " Test for using a script-local function name
- func s:CompleteFunc3(findstart, base)
- call add(g:CompleteFunc3Args, [a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
- set completefunc=s:CompleteFunc3
- new
- call setline(1, 'script1')
- let g:CompleteFunc3Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'script1']], g:CompleteFunc3Args)
- bw!
-
- let &completefunc = 's:CompleteFunc3'
- new
- call setline(1, 'script2')
- let g:CompleteFunc3Args = []
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'script2']], g:CompleteFunc3Args)
- bw!
- delfunc s:CompleteFunc3
-
- " invalid return value
- let &completefunc = {a -> 'abc'}
- call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
-
- " Using Vim9 lambda expression in legacy context should fail
- " set completefunc=(a,\ b)\ =>\ CompleteFunc1(21,\ a,\ b)
- new | only
- let g:CompleteFunc1Args = []
- " call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:')
- call assert_equal([], g:CompleteFunc1Args)
-
- " set 'completefunc' to a partial with dict. This used to cause a crash.
- func SetCompleteFunc()
- let params = {'complete': function('g:DictCompleteFunc')}
- let &completefunc = params.complete
- endfunc
- func g:DictCompleteFunc(_) dict
- endfunc
- call SetCompleteFunc()
- new
- call SetCompleteFunc()
- bw
- call test_garbagecollect_now()
- new
- set completefunc=
- wincmd w
- set completefunc=
- %bw!
- delfunc g:DictCompleteFunc
- delfunc SetCompleteFunc
-
- " Vim9 tests
- let lines =<< trim END
- vim9script
-
- def Vim9CompleteFunc(callnr: number, findstart: number, base: string): any
- add(g:Vim9completeFuncArgs, [callnr, findstart, base])
- return findstart ? 0 : []
- enddef
-
- # Test for using a def function with completefunc
- set completefunc=function('Vim9CompleteFunc',\ [60])
- new | only
- setline(1, 'one')
- g:Vim9completeFuncArgs = []
- feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9completeFuncArgs)
- bw!
-
- # Test for using a global function name
- &completefunc = g:CompleteFunc2
- new | only
- setline(1, 'two')
- g:CompleteFunc2Args = []
- feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- assert_equal([[1, ''], [0, 'two']], g:CompleteFunc2Args)
- bw!
-
- # Test for using a script-local function name
- def s:LocalCompleteFunc(findstart: number, base: string): any
- add(g:LocalCompleteFuncArgs, [findstart, base])
- return findstart ? 0 : []
- enddef
- &completefunc = s:LocalCompleteFunc
- new | only
- setline(1, 'three')
- g:LocalCompleteFuncArgs = []
- feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
- assert_equal([[1, ''], [0, 'three']], g:LocalCompleteFuncArgs)
- bw!
- END
- call CheckScriptSuccess(lines)
-
- " cleanup
- set completefunc&
- delfunc CompleteFunc1
- delfunc CompleteFunc2
- unlet g:CompleteFunc1Args g:CompleteFunc2Args
- %bw!
-endfunc
-
-" Test for different ways of setting the 'omnifunc' option
-func Test_omnifunc_callback()
- func OmniFunc1(callnr, findstart, base)
- call add(g:OmniFunc1Args, [a:callnr, a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
- func OmniFunc2(findstart, base)
- call add(g:OmniFunc2Args, [a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
-
- let lines =<< trim END
- #" Test for using a function name
- LET &omnifunc = 'g:OmniFunc2'
- new
- call setline(1, 'zero')
- LET g:OmniFunc2Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'zero']], g:OmniFunc2Args)
- bw!
-
- #" Test for using a function()
- set omnifunc=function('g:OmniFunc1',\ [10])
- new
- call setline(1, 'one')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[10, 1, ''], [10, 0, 'one']], g:OmniFunc1Args)
- bw!
-
- #" Using a funcref variable to set 'omnifunc'
- VAR Fn = function('g:OmniFunc1', [11])
- LET &omnifunc = Fn
- new
- call setline(1, 'two')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[11, 1, ''], [11, 0, 'two']], g:OmniFunc1Args)
- bw!
-
- #" Using a string(funcref_variable) to set 'omnifunc'
- LET Fn = function('g:OmniFunc1', [12])
- LET &omnifunc = string(Fn)
- new
- call setline(1, 'two')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[12, 1, ''], [12, 0, 'two']], g:OmniFunc1Args)
- bw!
-
- #" Test for using a funcref()
- set omnifunc=funcref('g:OmniFunc1',\ [13])
- new
- call setline(1, 'three')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[13, 1, ''], [13, 0, 'three']], g:OmniFunc1Args)
- bw!
-
- #" Use let to set 'omnifunc' to a funcref
- LET Fn = funcref('g:OmniFunc1', [14])
- LET &omnifunc = Fn
- new
- call setline(1, 'four')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[14, 1, ''], [14, 0, 'four']], g:OmniFunc1Args)
- bw!
-
- #" Using a string(funcref) to set 'omnifunc'
- LET Fn = funcref("g:OmniFunc1", [15])
- LET &omnifunc = string(Fn)
- new
- call setline(1, 'four')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[15, 1, ''], [15, 0, 'four']], g:OmniFunc1Args)
- bw!
-
- #" Test for using a lambda function with set
- VAR optval = "LSTART a, b LMIDDLE OmniFunc1(16, a, b) LEND"
- LET optval = substitute(optval, ' ', '\\ ', 'g')
- exe "set omnifunc=" .. optval
- new
- call setline(1, 'five')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[16, 1, ''], [16, 0, 'five']], g:OmniFunc1Args)
- bw!
-
- #" Set 'omnifunc' to a lambda expression
- LET &omnifunc = LSTART a, b LMIDDLE OmniFunc1(17, a, b) LEND
- new
- call setline(1, 'six')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[17, 1, ''], [17, 0, 'six']], g:OmniFunc1Args)
- bw!
-
- #" Set 'omnifunc' to a string(lambda_expression)
- LET &omnifunc = 'LSTART a, b LMIDDLE OmniFunc1(18, a, b) LEND'
- new
- call setline(1, 'six')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[18, 1, ''], [18, 0, 'six']], g:OmniFunc1Args)
- bw!
-
- #" Set 'omnifunc' to a variable with a lambda expression
- VAR Lambda = LSTART a, b LMIDDLE OmniFunc1(19, a, b) LEND
- LET &omnifunc = Lambda
- new
- call setline(1, 'seven')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:OmniFunc1Args)
- bw!
-
- #" Set 'omnifunc' to a string(variable with a lambda expression)
- LET Lambda = LSTART a, b LMIDDLE OmniFunc1(20, a, b) LEND
- LET &omnifunc = string(Lambda)
- new
- call setline(1, 'seven')
- LET g:OmniFunc1Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:OmniFunc1Args)
- bw!
-
- #" Test for using a lambda function with incorrect return value
- LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
- LET &omnifunc = Lambda
- new
- call setline(1, 'eight')
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- bw!
-
- #" Test for clearing the 'omnifunc' option
- set omnifunc=''
- set omnifunc&
- call assert_fails("set omnifunc=function('abc')", "E700:")
- call assert_fails("set omnifunc=funcref('abc')", "E700:")
-
- #" set 'omnifunc' to a non-existing function
- set omnifunc=OmniFunc2
- call setline(1, 'nine')
- call assert_fails("set omnifunc=function('NonExistingFunc')", 'E700:')
- call assert_fails("LET &omnifunc = function('NonExistingFunc')", 'E700:')
- LET g:OmniFunc2Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'nine']], g:OmniFunc2Args)
- bw!
- END
- call CheckLegacyAndVim9Success(lines)
-
- " Test for using a script-local function name
- func s:OmniFunc3(findstart, base)
- call add(g:OmniFunc3Args, [a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
- set omnifunc=s:OmniFunc3
- new
- call setline(1, 'script1')
- let g:OmniFunc3Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'script1']], g:OmniFunc3Args)
- bw!
-
- let &omnifunc = 's:OmniFunc3'
- new
- call setline(1, 'script2')
- let g:OmniFunc3Args = []
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'script2']], g:OmniFunc3Args)
- bw!
- delfunc s:OmniFunc3
-
- " invalid return value
- let &omnifunc = {a -> 'abc'}
- call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
-
- " Using Vim9 lambda expression in legacy context should fail
- " set omnifunc=(a,\ b)\ =>\ OmniFunc1(21,\ a,\ b)
- new | only
- let g:OmniFunc1Args = []
- " call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:')
- call assert_equal([], g:OmniFunc1Args)
-
- " set 'omnifunc' to a partial with dict. This used to cause a crash.
- func SetOmniFunc()
- let params = {'omni': function('g:DictOmniFunc')}
- let &omnifunc = params.omni
- endfunc
- func g:DictOmniFunc(_) dict
- endfunc
- call SetOmniFunc()
- new
- call SetOmniFunc()
- bw
- call test_garbagecollect_now()
- new
- set omnifunc=
- wincmd w
- set omnifunc=
- %bw!
- delfunc g:DictOmniFunc
- delfunc SetOmniFunc
-
- " Vim9 tests
- let lines =<< trim END
- vim9script
-
- def Vim9omniFunc(callnr: number, findstart: number, base: string): any
- add(g:Vim9omniFunc_Args, [callnr, findstart, base])
- return findstart ? 0 : []
- enddef
-
- # Test for using a def function with omnifunc
- set omnifunc=function('Vim9omniFunc',\ [60])
- new | only
- setline(1, 'one')
- g:Vim9omniFunc_Args = []
- feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9omniFunc_Args)
- bw!
-
- # Test for using a global function name
- &omnifunc = g:OmniFunc2
- new | only
- setline(1, 'two')
- g:OmniFunc2Args = []
- feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- assert_equal([[1, ''], [0, 'two']], g:OmniFunc2Args)
- bw!
-
- # Test for using a script-local function name
- def s:LocalOmniFunc(findstart: number, base: string): any
- add(g:LocalOmniFuncArgs, [findstart, base])
- return findstart ? 0 : []
- enddef
- &omnifunc = s:LocalOmniFunc
- new | only
- setline(1, 'three')
- g:LocalOmniFuncArgs = []
- feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
- assert_equal([[1, ''], [0, 'three']], g:LocalOmniFuncArgs)
- bw!
- END
- call CheckScriptSuccess(lines)
-
- " cleanup
- set omnifunc&
- delfunc OmniFunc1
- delfunc OmniFunc2
- unlet g:OmniFunc1Args g:OmniFunc2Args
- %bw!
-endfunc
-
-" Test for different ways of setting the 'thesaurusfunc' option
-func Test_thesaurusfunc_callback()
- func TsrFunc1(callnr, findstart, base)
- call add(g:TsrFunc1Args, [a:callnr, a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
- func TsrFunc2(findstart, base)
- call add(g:TsrFunc2Args, [a:findstart, a:base])
- return a:findstart ? 0 : ['sunday']
- endfunc
-
- let lines =<< trim END
- #" Test for using a function name
- LET &thesaurusfunc = 'g:TsrFunc2'
- new
- call setline(1, 'zero')
- LET g:TsrFunc2Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'zero']], g:TsrFunc2Args)
- bw!
-
- #" Test for using a function()
- set thesaurusfunc=function('g:TsrFunc1',\ [10])
- new
- call setline(1, 'one')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[10, 1, ''], [10, 0, 'one']], g:TsrFunc1Args)
- bw!
-
- #" Using a funcref variable to set 'thesaurusfunc'
- VAR Fn = function('g:TsrFunc1', [11])
- LET &thesaurusfunc = Fn
- new
- call setline(1, 'two')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[11, 1, ''], [11, 0, 'two']], g:TsrFunc1Args)
- bw!
-
- #" Using a string(funcref_variable) to set 'thesaurusfunc'
- LET Fn = function('g:TsrFunc1', [12])
- LET &thesaurusfunc = string(Fn)
- new
- call setline(1, 'two')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[12, 1, ''], [12, 0, 'two']], g:TsrFunc1Args)
- bw!
-
- #" Test for using a funcref()
- set thesaurusfunc=funcref('g:TsrFunc1',\ [13])
- new
- call setline(1, 'three')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[13, 1, ''], [13, 0, 'three']], g:TsrFunc1Args)
- bw!
-
- #" Using a funcref variable to set 'thesaurusfunc'
- LET Fn = funcref('g:TsrFunc1', [14])
- LET &thesaurusfunc = Fn
- new
- call setline(1, 'four')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[14, 1, ''], [14, 0, 'four']], g:TsrFunc1Args)
- bw!
-
- #" Using a string(funcref_variable) to set 'thesaurusfunc'
- LET Fn = funcref('g:TsrFunc1', [15])
- LET &thesaurusfunc = string(Fn)
- new
- call setline(1, 'four')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[15, 1, ''], [15, 0, 'four']], g:TsrFunc1Args)
- bw!
-
- #" Test for using a lambda function
- VAR optval = "LSTART a, b LMIDDLE TsrFunc1(16, a, b) LEND"
- LET optval = substitute(optval, ' ', '\\ ', 'g')
- exe "set thesaurusfunc=" .. optval
- new
- call setline(1, 'five')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[16, 1, ''], [16, 0, 'five']], g:TsrFunc1Args)
- bw!
-
- #" Test for using a lambda function with set
- LET &thesaurusfunc = LSTART a, b LMIDDLE TsrFunc1(17, a, b) LEND
- new
- call setline(1, 'six')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[17, 1, ''], [17, 0, 'six']], g:TsrFunc1Args)
- bw!
-
- #" Set 'thesaurusfunc' to a string(lambda expression)
- LET &thesaurusfunc = 'LSTART a, b LMIDDLE TsrFunc1(18, a, b) LEND'
- new
- call setline(1, 'six')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[18, 1, ''], [18, 0, 'six']], g:TsrFunc1Args)
- bw!
-
- #" Set 'thesaurusfunc' to a variable with a lambda expression
- VAR Lambda = LSTART a, b LMIDDLE TsrFunc1(19, a, b) LEND
- LET &thesaurusfunc = Lambda
- new
- call setline(1, 'seven')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:TsrFunc1Args)
- bw!
-
- #" Set 'thesaurusfunc' to a string(variable with a lambda expression)
- LET Lambda = LSTART a, b LMIDDLE TsrFunc1(20, a, b) LEND
- LET &thesaurusfunc = string(Lambda)
- new
- call setline(1, 'seven')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:TsrFunc1Args)
- bw!
-
- #" Test for using a lambda function with incorrect return value
- LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
- LET &thesaurusfunc = Lambda
- new
- call setline(1, 'eight')
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- bw!
-
- #" Test for clearing the 'thesaurusfunc' option
- set thesaurusfunc=''
- set thesaurusfunc&
- call assert_fails("set thesaurusfunc=function('abc')", "E700:")
- call assert_fails("set thesaurusfunc=funcref('abc')", "E700:")
-
- #" set 'thesaurusfunc' to a non-existing function
- set thesaurusfunc=TsrFunc2
- call setline(1, 'ten')
- call assert_fails("set thesaurusfunc=function('NonExistingFunc')", 'E700:')
- call assert_fails("LET &thesaurusfunc = function('NonExistingFunc')", 'E700:')
- LET g:TsrFunc2Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'ten']], g:TsrFunc2Args)
- bw!
-
- #" Use a buffer-local value and a global value
- set thesaurusfunc&
- setlocal thesaurusfunc=function('g:TsrFunc1',\ [22])
- call setline(1, 'sun')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
- call assert_equal('sun', getline(1))
- call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args)
- new
- call setline(1, 'sun')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
- call assert_equal('sun', getline(1))
- call assert_equal([], g:TsrFunc1Args)
- set thesaurusfunc=function('g:TsrFunc1',\ [23])
- wincmd w
- call setline(1, 'sun')
- LET g:TsrFunc1Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
- call assert_equal('sun', getline(1))
- call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args)
- :%bw!
- END
- call CheckLegacyAndVim9Success(lines)
-
- " Test for using a script-local function name
- func s:TsrFunc3(findstart, base)
- call add(g:TsrFunc3Args, [a:findstart, a:base])
- return a:findstart ? 0 : []
- endfunc
- set tsrfu=s:TsrFunc3
- new
- call setline(1, 'script1')
- let g:TsrFunc3Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'script1']], g:TsrFunc3Args)
- bw!
-
- let &tsrfu = 's:TsrFunc3'
- new
- call setline(1, 'script2')
- let g:TsrFunc3Args = []
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- call assert_equal([[1, ''], [0, 'script2']], g:TsrFunc3Args)
- bw!
- delfunc s:TsrFunc3
-
- " invalid return value
- let &thesaurusfunc = {a -> 'abc'}
- call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
-
- " Using Vim9 lambda expression in legacy context should fail
- " set thesaurusfunc=(a,\ b)\ =>\ TsrFunc1(21,\ a,\ b)
- new | only
- let g:TsrFunc1Args = []
- " call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:')
- call assert_equal([], g:TsrFunc1Args)
- bw!
-
- " set 'thesaurusfunc' to a partial with dict. This used to cause a crash.
- func SetTsrFunc()
- let params = {'thesaurus': function('g:DictTsrFunc')}
- let &thesaurusfunc = params.thesaurus
- endfunc
- func g:DictTsrFunc(_) dict
- endfunc
- call SetTsrFunc()
- new
- call SetTsrFunc()
- bw
- call test_garbagecollect_now()
- new
- set thesaurusfunc=
- wincmd w
- %bw!
- delfunc SetTsrFunc
-
- " set buffer-local 'thesaurusfunc' to a partial with dict. This used to
- " cause a crash.
- func SetLocalTsrFunc()
- let params = {'thesaurus': function('g:DictTsrFunc')}
- let &l:thesaurusfunc = params.thesaurus
- endfunc
- call SetLocalTsrFunc()
- call test_garbagecollect_now()
- call SetLocalTsrFunc()
- set thesaurusfunc=
- bw!
- delfunc g:DictTsrFunc
- delfunc SetLocalTsrFunc
-
- " Vim9 tests
- let lines =<< trim END
- vim9script
-
- def Vim9tsrFunc(callnr: number, findstart: number, base: string): any
- add(g:Vim9tsrFunc_Args, [callnr, findstart, base])
- return findstart ? 0 : []
- enddef
-
- # Test for using a def function with thesaurusfunc
- set thesaurusfunc=function('Vim9tsrFunc',\ [60])
- new | only
- setline(1, 'one')
- g:Vim9tsrFunc_Args = []
- feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9tsrFunc_Args)
- bw!
-
- # Test for using a global function name
- &thesaurusfunc = g:TsrFunc2
- new | only
- setline(1, 'two')
- g:TsrFunc2Args = []
- feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- assert_equal([[1, ''], [0, 'two']], g:TsrFunc2Args)
- bw!
-
- # Test for using a script-local function name
- def s:LocalTsrFunc(findstart: number, base: string): any
- add(g:LocalTsrFuncArgs, [findstart, base])
- return findstart ? 0 : []
- enddef
- &thesaurusfunc = s:LocalTsrFunc
- new | only
- setline(1, 'three')
- g:LocalTsrFuncArgs = []
- feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
- assert_equal([[1, ''], [0, 'three']], g:LocalTsrFuncArgs)
- bw!
- END
- call CheckScriptSuccess(lines)
-
- " cleanup
- set thesaurusfunc&
- delfunc TsrFunc1
- delfunc TsrFunc2
- unlet g:TsrFunc1Args g:TsrFunc2Args
- %bw!
-endfunc
-
-func FooBarComplete(findstart, base)
- if a:findstart
- return col('.') - 1
- else
- return ["Foo", "Bar", "}"]
- endif
-endfunc
-
-func Test_complete_smartindent()
- new
- setlocal smartindent completefunc=FooBarComplete
-
- exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>"
- let result = getline(1,'$')
- call assert_equal(['', '{','}',''], result)
- bw!
- delfunction! FooBarComplete
-endfunc
-
-func Test_complete_overrun()
- " this was going past the end of the copied text
- new
- sil norm si”0s0 
- bwipe!
-endfunc
-
-func Test_infercase_very_long_line()
- " this was truncating the line when inferring case
- new
- let longLine = "blah "->repeat(300)
- let verylongLine = "blah "->repeat(400)
- call setline(1, verylongLine)
- call setline(2, longLine)
- set ic infercase
- exe "normal 2Go\<C-X>\<C-L>\<Esc>"
- call assert_equal(longLine, getline(3))
-
- " check that the too long text is NUL terminated
- %del
- norm o
- norm 1987ax
- exec "norm ox\<C-X>\<C-L>"
- call assert_equal(repeat('x', 1987), getline(3))
-
- bwipe!
- set noic noinfercase
-endfunc
-
-func Test_ins_complete_add()
- " this was reading past the end of allocated memory
- new
- norm o
- norm 7o€€
- sil! norm o
-
- bwipe!
-endfunc
-
-func Test_ins_complete_end_of_line()
- " this was reading past the end of the line
- new
- norm 8o€ý 
- sil! norm o
-
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ins_complete_no_halt.vim b/src/nvim/testdir/test_ins_complete_no_halt.vim
deleted file mode 100644
index e12925daa9..0000000000
--- a/src/nvim/testdir/test_ins_complete_no_halt.vim
+++ /dev/null
@@ -1,51 +0,0 @@
-" Test insert mode completion does not get stuck when looping around.
-" In a separate file to avoid the settings to leak to other test cases.
-
-set complete+=kspell
-set completeopt+=menu
-set completeopt+=menuone
-set completeopt+=noselect
-set completeopt+=noinsert
-let g:autocompletion = v:true
-
-func Test_ins_complete_no_halt()
- function! OpenCompletion()
- if pumvisible() && (g:autocompletion == v:true)
- call feedkeys("\<C-e>\<C-n>", "i")
- return
- endif
- if ((v:char >= 'a' && v:char <= 'z') || (v:char >= 'A' && v:char <= 'Z')) && (g:autocompletion == v:true)
- call feedkeys("\<C-n>", "i")
- redraw
- endif
- endfunction
-
- autocmd InsertCharPre * noautocmd call OpenCompletion()
-
- setlocal spell! spelllang=en_us
-
- call feedkeys("iauto-complete-halt-test test test test test test test test test test test test test test test test test test test\<C-c>", "tx!")
- call assert_equal(["auto-complete-halt-test test test test test test test test test test test test test test test test test test test"], getline(1, "$"))
-endfunc
-
-func Test_auto_complete_backwards_no_halt()
- function! OpenCompletion()
- if pumvisible() && (g:autocompletion == v:true)
- call feedkeys("\<C-e>\<C-p>", "i")
- return
- endif
- if ((v:char >= 'a' && v:char <= 'z') || (v:char >= 'A' && v:char <= 'Z')) && (g:autocompletion == v:true)
- call feedkeys("\<C-p>", "i")
- redraw
- endif
- endfunction
-
- autocmd InsertCharPre * noautocmd call OpenCompletion()
-
- setlocal spell! spelllang=en_us
-
- call feedkeys("iauto-complete-halt-test test test test test test test test test test test test test test test test test test test\<C-c>", "tx!")
- call assert_equal(["auto-complete-halt-test test test test test test test test test test test test test test test test test test test"], getline(1, "$"))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_interrupt.vim b/src/nvim/testdir/test_interrupt.vim
deleted file mode 100644
index aa7f634302..0000000000
--- a/src/nvim/testdir/test_interrupt.vim
+++ /dev/null
@@ -1,32 +0,0 @@
-" Test behavior of interrupt()
-
-let s:bufwritepre_called = 0
-let s:bufwritepost_called = 0
-
-func s:bufwritepre()
- let s:bufwritepre_called = 1
- call interrupt()
-endfunction
-
-func s:bufwritepost()
- let s:bufwritepost_called = 1
-endfunction
-
-func Test_interrupt()
- new Xinterrupt
- let n = 0
- try
- au BufWritePre Xinterrupt call s:bufwritepre()
- au BufWritePost Xinterrupt call s:bufwritepost()
- w!
- catch /^Vim:Interrupt$/
- endtry
- call assert_equal(1, s:bufwritepre_called)
- call assert_equal(0, s:bufwritepost_called)
- call assert_equal(0, filereadable('Xinterrupt'))
-
- au! BufWritePre
- au! BufWritePost
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim
deleted file mode 100644
index 1f7a0825a5..0000000000
--- a/src/nvim/testdir/test_join.vim
+++ /dev/null
@@ -1,447 +0,0 @@
-" Test for joining lines.
-
-func Test_join_with_count()
- new
- call setline(1, ['one', 'two', 'three', 'four'])
- normal J
- call assert_equal('one two', getline(1))
- %del
- 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
-
-" Tests for setting the '[,'] marks when joining lines.
-func Test_join_marks()
- enew
- call append(0, [
- \ "\t\tO sodales, ludite, vos qui",
- \ "attamen consulite per voster honur. Tua pulchra " .
- \ "facies me fay planszer milies",
- \ "",
- \ "This line.",
- \ "Should be joined with the next line",
- \ "and with this line"])
-
- normal gg0gqj
- call assert_equal([0, 1, 1, 0], getpos("'["))
- call assert_equal([0, 2, 1, 0], getpos("']"))
-
- /^This line/;'}-join
- call assert_equal([0, 4, 11, 0], getpos("'["))
- 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))
- call setline(1, ['a', 'b', 'c'])
- 2,2join
- call assert_equal(['a', 'b', 'c'], getline(1, '$'))
- call assert_equal(2, line('.'))
- 2join
- call assert_equal(['a', 'b c'], getline(1, '$'))
- bwipe!
-endfunc
diff --git a/src/nvim/testdir/test_jumplist.vim b/src/nvim/testdir/test_jumplist.vim
deleted file mode 100644
index 91ad940e18..0000000000
--- a/src/nvim/testdir/test_jumplist.vim
+++ /dev/null
@@ -1,107 +0,0 @@
-" Tests for the jumplist functionality
-
-" Tests for the getjumplist() function
-func Test_getjumplist()
- if !has("jumplist")
- return
- endif
-
- %bwipe
- clearjumps
- call assert_equal([[], 0], getjumplist())
- call assert_equal([[], 0], getjumplist(1))
- call assert_equal([[], 0], getjumplist(1, 1))
-
- call assert_equal([], getjumplist(100))
- call assert_equal([], getjumplist(1, 100))
-
- let lines = []
- for i in range(1, 100)
- call add(lines, "Line " . i)
- endfor
- call writefile(lines, "Xtest")
-
- " Jump around and create a jump list
- edit Xtest
- let bnr = bufnr('%')
- normal 50%
- normal G
- normal gg
-
- let expected = [[
- \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
- \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0},
- \ {'lnum': 100, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 3]
- call assert_equal(expected, getjumplist())
- " jumplist doesn't change in between calls
- call assert_equal(expected, getjumplist())
-
- " Traverse the jump list and verify the results
- 5
- exe "normal \<C-O>"
- call assert_equal(2, 1->getjumplist()[1])
- exe "normal 2\<C-O>"
- call assert_equal(0, getjumplist(1, 1)[1])
- exe "normal 3\<C-I>"
- call assert_equal(3, getjumplist()[1])
- exe "normal \<C-O>"
- normal 20%
- let expected = [[
- \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
- \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0},
- \ {'lnum': 5, 'bufnr': bnr, 'col': 0, 'coladd': 0},
- \ {'lnum': 100, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 4]
- call assert_equal(expected, getjumplist())
- " jumplist doesn't change in between calls
- call assert_equal(expected, getjumplist())
-
- let l = getjumplist()
- call test_garbagecollect_now()
- call assert_equal(4, l[1])
- clearjumps
- call test_garbagecollect_now()
- call assert_equal(4, l[1])
-
- call delete("Xtest")
-endfunc
-
-func Test_jumplist_invalid()
- new
- clearjumps
- " put some randome text
- put ='a'
- let prev = bufnr('%')
- setl nomodified bufhidden=wipe
- e XXJumpListBuffer
- let bnr = bufnr('%')
- " 1) empty jumplist
- let expected = [[
- \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}], 1]
- call assert_equal(expected, getjumplist())
- let jumps = execute(':jumps')
- call assert_equal('>', jumps[-1:])
- " now jump back
- exe ":norm! \<c-o>"
- let expected = [[
- \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0},
- \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 0]
- call assert_equal(expected, getjumplist())
- let jumps = execute(':jumps')
- call assert_match('> 0 2 0 -invalid-', jumps)
-endfunc
-
-" Test for '' mark in an empty buffer
-
-func Test_empty_buffer()
- new
- insert
-a
-b
-c
-d
-.
- call assert_equal(1, line("''"))
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
deleted file mode 100644
index 025eb016a8..0000000000
--- a/src/nvim/testdir/test_lambda.vim
+++ /dev/null
@@ -1,334 +0,0 @@
-" Test for lambda and closure
-
-func Test_lambda_feature()
- call assert_equal(1, has('lambda'))
-endfunc
-
-func Test_lambda_with_filter()
- let s:x = 2
- call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
-endfunc
-
-func Test_lambda_with_map()
- let s:x = 1
- call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
-endfunc
-
-func Test_lambda_with_sort()
- call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
-endfunc
-
-func Test_lambda_with_timer()
- if !has('timers')
- return
- endif
-
- let s:n = 0
- let s:timer_id = 0
- func! s:Foo()
- let s:timer_id = timer_start(10, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
- endfunc
-
- call s:Foo()
- " check timer works
- for i in range(0, 10)
- if s:n > 0
- break
- endif
- sleep 10m
- endfor
-
- " do not collect lambda
- call test_garbagecollect_now()
-
- " check timer still works
- let m = s:n
- for i in range(0, 10)
- if s:n > m
- break
- endif
- sleep 10m
- endfor
-
- call timer_stop(s:timer_id)
- call assert_true(s:n > m)
-endfunc
-
-func Test_lambda_with_partial()
- let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
- call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
-endfunc
-
-function Test_lambda_fails()
- call assert_equal(3, {a, b -> a + b}(1, 2))
- call assert_fails('echo {a, a -> a + a}(1, 2)', 'E853:')
- call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E451:')
- echo assert_fails('echo 10->{a -> a + 2}', 'E107:')
-endfunc
-
-func Test_not_lambda()
- let x = {'>' : 'foo'}
- call assert_equal('foo', x['>'])
-endfunc
-
-func Test_lambda_capture_by_reference()
- let v = 1
- let l:F = {x -> x + v}
- let v = 2
- call assert_equal(12, l:F(10))
-endfunc
-
-func Test_lambda_side_effect()
- func! s:update_and_return(arr)
- let a:arr[1] = 5
- return a:arr
- endfunc
-
- func! s:foo(arr)
- return {-> s:update_and_return(a:arr)}
- endfunc
-
- let arr = [3,2,1]
- call assert_equal([3, 5, 1], s:foo(arr)())
-endfunc
-
-func Test_lambda_refer_local_variable_from_other_scope()
- func! s:foo(X)
- return a:X() " refer l:x in s:bar()
- endfunc
-
- func! s:bar()
- let x = 123
- return s:foo({-> x})
- endfunc
-
- call assert_equal(123, s:bar())
-endfunc
-
-func Test_lambda_do_not_share_local_variable()
- func! s:define_funcs()
- let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
- let l:Two = {-> exists("a") ? a : "no"}
- return [l:One, l:Two]
- endfunc
-
- let l:F = s:define_funcs()
-
- call assert_equal('no', l:F[1]())
- call assert_equal('abc', l:F[0]())
- call assert_equal('no', l:F[1]())
-endfunc
-
-func Test_lambda_closure_counter()
- func! s:foo()
- let x = 0
- return {-> [execute("let x += 1"), x][-1]}
- endfunc
-
- let l:F = s:foo()
- call test_garbagecollect_now()
- call assert_equal(1, l:F())
- call assert_equal(2, l:F())
- call assert_equal(3, l:F())
- call assert_equal(4, l:F())
-endfunc
-
-func Test_lambda_with_a_var()
- func! s:foo()
- let x = 2
- return {... -> a:000 + [x]}
- endfunc
- func! s:bar()
- return s:foo()(1)
- endfunc
-
- call assert_equal([1, 2], s:bar())
-endfunc
-
-func Test_lambda_call_lambda_from_lambda()
- func! s:foo(x)
- let l:F1 = {-> {-> a:x}}
- return {-> l:F1()}
- endfunc
-
- let l:F = s:foo(1)
- call assert_equal(1, l:F()())
-endfunc
-
-func Test_lambda_delfunc()
- func! s:gen()
- let pl = l:
- let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
- let l:Bar = l:Foo
- delfunction l:Foo
- return l:Bar
- endfunc
-
- let l:F = s:gen()
- call assert_fails(':call l:F()', 'E933:')
-endfunc
-
-func Test_lambda_scope()
- func! s:NewCounter()
- let c = 0
- return {-> [execute('let c += 1'), c][-1]}
- endfunc
-
- func! s:NewCounter2()
- return {-> [execute('let c += 100'), c][-1]}
- endfunc
-
- let l:C = s:NewCounter()
- let l:D = s:NewCounter2()
-
- call assert_equal(1, l:C())
- call assert_fails(':call l:D()', 'E121:')
- call assert_equal(2, l:C())
-endfunc
-
-func Test_lambda_share_scope()
- func! s:New()
- let c = 0
- let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
- let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
- return [l:Inc0, l:Dec0]
- endfunc
-
- let [l:Inc, l:Dec] = s:New()
-
- call assert_equal(1, l:Inc())
- call assert_equal(2, l:Inc())
- call assert_equal(1, l:Dec())
-endfunc
-
-func Test_lambda_circular_reference()
- func! s:Foo()
- let d = {}
- let d.f = {-> d}
- return d.f
- endfunc
-
- call s:Foo()
- call test_garbagecollect_now()
- let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
- call test_garbagecollect_now()
-endfunc
-
-func Test_lambda_combination()
- call assert_equal(2, {x -> {x -> x}}(1)(2))
- call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
- call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
- call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
-
- call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
- call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
-
- " Z combinator
- let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
- let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
- call assert_equal(120, Z(Fact)(5))
-endfunc
-
-func Test_closure_counter()
- func! s:foo()
- let x = 0
- func! s:bar() closure
- let x += 1
- return x
- endfunc
- return function('s:bar')
- endfunc
-
- let l:F = s:foo()
- call test_garbagecollect_now()
- call assert_equal(1, l:F())
- call assert_equal(2, l:F())
- call assert_equal(3, l:F())
- call assert_equal(4, l:F())
-
- call assert_match("^\n function <SNR>\\d\\+_bar() closure"
- \ .. "\n1 let x += 1"
- \ .. "\n2 return x"
- \ .. "\n endfunction$", execute('func s:bar'))
-endfunc
-
-func Test_closure_unlet()
- func! s:foo()
- let x = 1
- func! s:bar() closure
- unlet x
- endfunc
- call s:bar()
- return l:
- endfunc
-
- call assert_false(has_key(s:foo(), 'x'))
- call test_garbagecollect_now()
-endfunc
-
-func LambdaFoo()
- let x = 0
- func! LambdaBar() closure
- let x += 1
- return x
- endfunc
- return function('LambdaBar')
-endfunc
-
-func Test_closure_refcount()
- let g:Count = LambdaFoo()
- call test_garbagecollect_now()
- call assert_equal(1, g:Count())
- let g:Count2 = LambdaFoo()
- call test_garbagecollect_now()
- call assert_equal(1, g:Count2())
- call assert_equal(2, g:Count())
- call assert_equal(3, g:Count2())
-
- delfunc LambdaFoo
- delfunc LambdaBar
-endfunc
-
-" This test is causing a use-after-free on shutdown.
-func Test_named_function_closure()
- func! Afoo()
- let x = 14
- func! s:Abar() closure
- return x
- endfunc
- call assert_equal(14, s:Abar())
- endfunc
- call Afoo()
- call assert_equal(14, s:Abar())
- call test_garbagecollect_now()
- call assert_equal(14, s:Abar())
-endfunc
-
-func Test_lambda_with_index()
- let List = {x -> [x]}
- let Extract = {-> function(List, ['foobar'])()[0]}
- call assert_equal('foobar', Extract())
-endfunc
-
-func Test_lambda_error()
- " This was causing a crash
- call assert_fails('ec{@{->{d->()()', 'E15')
-endfunc
-
-func Test_closure_error()
- let l =<< trim END
- func F1() closure
- return 1
- endfunc
- END
- call writefile(l, 'Xscript')
- let caught_932 = 0
- try
- source Xscript
- catch /E932:/
- let caught_932 = 1
- endtry
- call assert_equal(1, caught_932)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_langmap.vim b/src/nvim/testdir/test_langmap.vim
deleted file mode 100644
index aaed77e109..0000000000
--- a/src/nvim/testdir/test_langmap.vim
+++ /dev/null
@@ -1,89 +0,0 @@
-" tests for 'langmap'
-
-source check.vim
-CheckFeature langmap
-
-func Test_langmap()
- new
- set langmap=}l,^x,%v
-
- call setline(1, ['abc'])
- call feedkeys('gg0}^', 'tx')
- call assert_equal('ac', getline(1))
-
- " in Replace mode
- " need silent! to avoid a delay when entering Insert mode
- call setline(1, ['abcde'])
- silent! call feedkeys("gg0lR%{z\<Esc>00", 'tx')
- call assert_equal('a%{ze', getline(1))
-
- " in Select mode
- " need silent! to avoid a delay when entering Insert mode
- call setline(1, ['abcde'])
- silent! call feedkeys("gg0}%}\<C-G>}^\<Esc>00", 'tx')
- call assert_equal('a}^de', getline(1))
-
- " Error cases
- call assert_fails('set langmap=aA,b', 'E357:')
- call assert_fails('set langmap=z;y;y;z', 'E358:')
-
- " Map character > 256
- enew!
- set langmap=Äx,ăl,Äx
- call setline(1, ['abcde'])
- call feedkeys('gg2lÄ', 'tx')
- call assert_equal('abde', getline(1))
-
- " special characters in langmap
- enew!
- call setline(1, ['Hello World'])
- set langmap=\\;\\,,\\,\\;
- call feedkeys('ggfo,', 'tx')
- call assert_equal(8, col('.'))
- call feedkeys(';', 'tx')
- call assert_equal(5, col('.'))
- set langmap&
- set langmap=\\;\\,;\\,\\;
- call feedkeys('ggfo,', 'tx')
- call assert_equal(8, col('.'))
- call feedkeys(';', 'tx')
- call assert_equal(5, col('.'))
-
- set langmap=RL
- let g:counter = 0
- nnoremap L;L <Cmd>let g:counter += 1<CR>
- nnoremap <C-L> <Cmd>throw 'This mapping should not be triggered'<CR>
-
- " 'langmap' is applied to keys without modifiers when matching a mapping
- call feedkeys('R;R', 'tx')
- call assert_equal(1, g:counter)
- nunmap L;L
- unlet g:counter
-
- delete
- call assert_equal('', getline(1))
- undo
- call assert_equal('Hello World', getline(1))
- " 'langmap' does not change Ctrl-R to Ctrl-L for consistency
- call feedkeys("\<*C-R>", 'tx')
- call assert_equal('', getline(1))
-
- set langmap=6L
- undo
- setlocal bufhidden=hide
- let oldbuf = bufnr()
- enew
- call assert_notequal(oldbuf, bufnr())
- " 'langmap' does not change Ctrl-6 to Ctrl-L for consistency
- " Ctrl-6 becomes Ctrl-^ after merging the Ctrl modifier
- call feedkeys("\<*C-6>", 'tx')
- call assert_equal(oldbuf, bufnr())
- setlocal bufhidden&
-
- nunmap <C-L>
-
- set langmap&
- quit!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_largefile.vim b/src/nvim/testdir/test_largefile.vim
deleted file mode 100644
index fa32b7fb5c..0000000000
--- a/src/nvim/testdir/test_largefile.vim
+++ /dev/null
@@ -1,29 +0,0 @@
-" Tests for large files
-" This is only executed manually: "TEST_FILE=test_largefile.res make oldtest".
-" This is not run as part of "make test".
-
-func Test_largefile()
- let fname = 'Xlarge.txt'
-
- call delete(fname)
- exe "e" fname
- " Make sure that a line break is 1 byte (LF).
- set ff=unix
- set undolevels=-1
- " Input 99 'A's. The line becomes 100 bytes including a line break.
- exe "normal 99iA\<Esc>"
- yank
- " Put 39,999,999 times. The file becomes 4,000,000,000 bytes.
- normal 39999999p
- " Moving around in the file randomly.
- normal G
- normal 10%
- normal 90%
- normal 50%
- normal gg
- w
- " Check if the file size is 4,000,000,000 bytes.
- let fsize=getfsize(fname)
- call assert_true(fsize == 4000000000)
- call delete(fname)
-endfunc
diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim
deleted file mode 100644
index 35745e9c6a..0000000000
--- a/src/nvim/testdir/test_let.vim
+++ /dev/null
@@ -1,478 +0,0 @@
-" Tests for the :let command.
-
-func Test_let()
- " Test to not autoload when assigning. It causes internal error.
- set runtimepath+=./sautest
- let Test104#numvar = function('tr')
- call assert_equal("function('tr')", string(Test104#numvar))
-
- let foo#tr = function('tr')
- call assert_equal("function('tr')", string(foo#tr))
- unlet foo#tr
-
- let a = 1
- let b = 2
-
- let out = execute('let a b')
- let s = "\na #1\nb #2"
- call assert_equal(s, out)
-
- let out = execute('let {0 == 1 ? "a" : "b"}')
- let s = "\nb #2"
- call assert_equal(s, out)
-
- let out = execute('let {0 == 1 ? "a" : "b"} a')
- let s = "\nb #2\na #1"
- call assert_equal(s, out)
-
- let out = execute('let a {0 == 1 ? "a" : "b"}')
- let s = "\na #1\nb #2"
- call assert_equal(s, out)
-
- " Test for displaying a string variable
- let s = 'vim'
- let out = execute('let s')
- let s = "\ns vim"
- call assert_equal(s, out)
-
- " Test for displaying a list variable
- let l = [1, 2]
- let out = execute('let l')
- let s = "\nl [1, 2]"
- call assert_equal(s, out)
-
- " Test for displaying a dict variable
- let d = {'k' : 'v'}
- let out = execute('let d')
- let s = "\nd {'k': 'v'}"
- call assert_equal(s, out)
-
- " Test for displaying a function reference variable
- let F = function('min')
- let out = execute('let F')
- let s = "\nF *min()"
- call assert_equal(s, out)
-
- let x = 0
- if 0 | let x = 1 | endif
- call assert_equal(0, x)
-
- " Display a list item using an out of range index
- let l = [10]
- call assert_fails('let l[1]', 'E684:')
-
- " List special variable dictionaries
- let g:Test_Global_Var = 5
- call assert_match("\nTest_Global_Var #5", execute('let g:'))
- unlet g:Test_Global_Var
-
- let b:Test_Buf_Var = 8
- call assert_match("\nb:Test_Buf_Var #8", execute('let b:'))
- unlet b:Test_Buf_Var
-
- let w:Test_Win_Var = 'foo'
- call assert_equal("\nw:Test_Win_Var foo", execute('let w:'))
- unlet w:Test_Win_Var
-
- let t:Test_Tab_Var = 'bar'
- call assert_equal("\nt:Test_Tab_Var bar", execute('let t:'))
- unlet t:Test_Tab_Var
-
- let s:Test_Script_Var = [7]
- call assert_match("\ns:Test_Script_Var \\[7]", execute('let s:'))
- unlet s:Test_Script_Var
-
- let l:Test_Local_Var = {'k' : 5}
- call assert_match("\nl:Test_Local_Var {'k': 5}", execute('let l:'))
- call assert_match("v:errors []", execute('let v:'))
-endfunc
-
-func s:set_arg1(a) abort
- let a:a = 1
-endfunction
-
-func s:set_arg2(a) abort
- let a:b = 1
-endfunction
-
-func s:set_arg3(a) abort
- let b = a:
- let b['a'] = 1
-endfunction
-
-func s:set_arg4(a) abort
- let b = a:
- let b['a'] = 1
-endfunction
-
-func s:set_arg5(a) abort
- let b = a:
- let b['a'][0] = 1
-endfunction
-
-func s:set_arg6(a) abort
- let a:a[0] = 1
-endfunction
-
-func s:set_arg7(a) abort
- call extend(a:, {'a': 1})
-endfunction
-
-func s:set_arg8(a) abort
- call extend(a:, {'b': 1})
-endfunction
-
-func s:set_arg9(a) abort
- let a:['b'] = 1
-endfunction
-
-func s:set_arg10(a) abort
- let b = a:
- call extend(b, {'a': 1})
-endfunction
-
-func s:set_arg11(a) abort
- let b = a:
- call extend(b, {'b': 1})
-endfunction
-
-func s:set_arg12(a) abort
- let b = a:
- let b['b'] = 1
-endfunction
-
-func Test_let_arg_fail()
- call assert_fails('call s:set_arg1(1)', 'E46:')
- call assert_fails('call s:set_arg2(1)', 'E461:')
- call assert_fails('call s:set_arg3(1)', 'E46:')
- call assert_fails('call s:set_arg4(1)', 'E46:')
- call assert_fails('call s:set_arg5(1)', 'E46:')
- call s:set_arg6([0])
- call assert_fails('call s:set_arg7(1)', 'E742:')
- call assert_fails('call s:set_arg8(1)', 'E742:')
- call assert_fails('call s:set_arg9(1)', 'E461:')
- call assert_fails('call s:set_arg10(1)', 'E742:')
- call assert_fails('call s:set_arg11(1)', 'E742:')
- call assert_fails('call s:set_arg12(1)', 'E461:')
-endfunction
-
-func s:set_varg1(...) abort
- let a:000 = []
-endfunction
-
-func s:set_varg2(...) abort
- let a:000[0] = 1
-endfunction
-
-func s:set_varg3(...) abort
- let a:000 += [1]
-endfunction
-
-func s:set_varg4(...) abort
- call add(a:000, 1)
-endfunction
-
-func s:set_varg5(...) abort
- let a:000[0][0] = 1
-endfunction
-
-func s:set_varg6(...) abort
- let b = a:000
- let b[0] = 1
-endfunction
-
-func s:set_varg7(...) abort
- let b = a:000
- let b += [1]
-endfunction
-
-func s:set_varg8(...) abort
- let b = a:000
- call add(b, 1)
-endfunction
-
-func s:set_varg9(...) abort
- let b = a:000
- let b[0][0] = 1
-endfunction
-
-func Test_let_varg_fail()
- call assert_fails('call s:set_varg1(1)', 'E46:')
- call assert_fails('call s:set_varg2(1)', 'E742:')
- call assert_fails('call s:set_varg3(1)', 'E46:')
- call assert_fails('call s:set_varg4(1)', 'E742:')
- call s:set_varg5([0])
- call assert_fails('call s:set_varg6(1)', 'E742:')
- call assert_fails('call s:set_varg7(1)', 'E742:')
- call assert_fails('call s:set_varg8(1)', 'E742:')
- call s:set_varg9([0])
-endfunction
-
-func Test_let_utf8_environment()
- let $a = 'ĀĒĪŌŪã‚ã„ã†ãˆãŠ'
- call assert_equal('ĀĒĪŌŪã‚ã„ã†ãˆãŠ', $a)
-endfunc
-
-func Test_let_no_type_checking()
- let v = 1
- let v = [1,2,3]
- let v = {'a': 1, 'b': 2}
- let v = 3.4
- let v = 'hello'
-endfunc
-
-func Test_let_termcap()
- throw 'skipped: Nvim does not support termcap option'
- " Terminal code
- let old_t_te = &t_te
- let &t_te = "\<Esc>[yes;"
- call assert_match('t_te.*^[[yes;', execute("set termcap"))
- let &t_te = old_t_te
-
- if exists("+t_k1")
- " Key code
- let old_t_k1 = &t_k1
- let &t_k1 = "that"
- call assert_match('t_k1.*that', execute("set termcap"))
- let &t_k1 = old_t_k1
- endif
-
- call assert_fails('let x = &t_xx', 'E113')
- let &t_xx = "yes"
- call assert_equal("yes", &t_xx)
- let &t_xx = ""
- call assert_fails('let x = &t_xx', 'E113')
-endfunc
-
-func Test_let_option_error()
- let _w = &tw
- let &tw = 80
- call assert_fails('let &tw .= 1', 'E734')
- call assert_equal(80, &tw)
- let &tw = _w
-
- let _w = &fillchars
- let &fillchars = "vert:|"
- call assert_fails('let &fillchars += "diff:-"', 'E734')
- call assert_equal("vert:|", &fillchars)
- let &fillchars = _w
-endfunc
-
-" Errors with the :let statement
-func Test_let_errors()
- let s = 'abcd'
- call assert_fails('let s[1] = 5', 'E689:')
-
- let l = [1, 2, 3]
- call assert_fails('let l[:] = 5', 'E709:')
-
- call assert_fails('let x:lnum=5', ['E121:', 'E488:'])
- call assert_fails('let v:=5', 'E461:')
- call assert_fails('let [a]', 'E474:')
- call assert_fails('let [a, b] = [', 'E697:')
- call assert_fails('let [a, b] = [10, 20', 'E696:')
- call assert_fails('let [a, b] = 10', 'E714:')
- call assert_fails('let [a, , b] = [10, 20]', 'E475:')
- call assert_fails('let [a, b&] = [10, 20]', 'E475:')
- call assert_fails('let $ = 10', 'E475:')
- call assert_fails('let $FOO[1] = "abc"', 'E18:')
- call assert_fails('let &buftype[1] = "nofile"', 'E18:')
- let s = "var"
- let var = 1
- call assert_fails('let var += [1,2]', 'E734:')
- call assert_fails('let {s}.1 = 2', 'E1203:')
- call assert_fails('let a[1] = 5', 'E121:')
- let l = [[1,2]]
- call assert_fails('let l[:][0] = [5]', 'E708:')
- let d = {'k' : 4}
- call assert_fails('let d.# = 5', 'E488:')
- call assert_fails('let d.m += 5', 'E716:')
- call assert_fails('let m = d[{]', 'E15:')
- let l = [1, 2]
- call assert_fails('let l[2] = 0', 'E684:')
- call assert_fails('let l[0:1] = [1, 2, 3]', 'E710:')
- call assert_fails('let l[-2:-3] = [3, 4]', 'E684:')
- call assert_fails('let l[0:4] = [5, 6]', 'E711:')
- call assert_fails('let l -= 2', 'E734:')
- call assert_fails('let l += 2', 'E734:')
- call assert_fails('let g:["a;b"] = 10', 'E461:')
- call assert_fails('let g:.min = function("max")', 'E704:')
- call assert_fails('let g:cos = "" | let g:.cos = {-> 42}', 'E704:')
- if has('channel')
- let ch = test_null_channel()
- call assert_fails('let ch += 1', 'E734:')
- endif
- call assert_fails('let name = "a" .. "b",', 'E488: Trailing characters: ,')
-
- " This test works only when the language is English
- if v:lang == "C" || v:lang =~ '^[Ee]n'
- call assert_fails('let [a ; b;] = [10, 20]',
- \ 'Double ; in list of variables')
- endif
-endfunc
-
-func Test_let_heredoc_fails()
- call assert_fails('let v =<< marker', 'E991:')
- try
- exe "let v =<< TEXT | abc | TEXT"
- call assert_report('No exception thrown')
- catch /E488:/
- catch
- call assert_report("Caught exception: " .. v:exception)
- endtry
-
- 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')
-
- call writefile(['let v =<< TEXT', 'abc'], 'XheredocMissingMarker')
- call assert_fails('source XheredocMissingMarker', 'E990:')
- call delete('XheredocMissingMarker')
-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
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_lineending.vim b/src/nvim/testdir/test_lineending.vim
deleted file mode 100644
index 5be3be8db3..0000000000
--- a/src/nvim/testdir/test_lineending.vim
+++ /dev/null
@@ -1,19 +0,0 @@
-" Tests for saving/loading a file with some lines ending in
-" CTRL-M, some not
-func Test_lineending()
- let l = ["this line ends in a\<CR>",
- \ "this one doesn't",
- \ "this one does\<CR>",
- \ "and the last one doesn't"]
- set fileformat=dos
- enew!
- call append(0, l)
- $delete
- write Xfile1
- bwipe Xfile1
- edit Xfile1
- let t = getline(1, '$')
- call assert_equal(l, t)
- new | only
- call delete('Xfile1')
-endfunc
diff --git a/src/nvim/testdir/test_lispindent.vim b/src/nvim/testdir/test_lispindent.vim
deleted file mode 100644
index 2d6060bba3..0000000000
--- a/src/nvim/testdir/test_lispindent.vim
+++ /dev/null
@@ -1,129 +0,0 @@
-" Tests for 'lispwords' settings being global-local.
-" And other lisp indent stuff.
-
-set nocompatible viminfo+=nviminfo
-
-func Test_global_local_lispwords()
- setglobal lispwords=foo,bar,baz
- setlocal lispwords-=foo | setlocal lispwords+=quux
- call assert_equal('foo,bar,baz', &g:lispwords)
- call assert_equal('bar,baz,quux', &l:lispwords)
- call assert_equal('bar,baz,quux', &lispwords)
-
- setlocal lispwords<
- call assert_equal('foo,bar,baz', &g:lispwords)
- call assert_equal('foo,bar,baz', &l:lispwords)
- call assert_equal('foo,bar,baz', &lispwords)
-endfunc
-
-func Test_lisp_indent()
- enew!
-
- call append(0, [
- \ '(defun html-file (base)',
- \ '(format nil "~(~A~).html" base))',
- \ '',
- \ '(defmacro page (name title &rest body)',
- \ '(let ((ti (gensym)))',
- \ '`(with-open-file (*standard-output*',
- \ '(html-file ,name)',
- \ ':direction :output',
- \ ':if-exists :supersede)',
- \ '(let ((,ti ,title))',
- \ '(as title ,ti)',
- \ '(with center ',
- \ '(as h2 (string-upcase ,ti)))',
- \ '(brs 3)',
- \ ',@body))))',
- \ '',
- \ ';;; Utilities for generating links',
- \ '',
- \ '(defmacro with-link (dest &rest body)',
- \ '`(progn',
- \ '(format t "<a href=\"~A\">" (html-file ,dest))',
- \ ',@body',
- \ '(princ "</a>")))'
- \ ])
- call assert_equal(7, lispindent(2))
- call assert_equal(5, 6->lispindent())
- call assert_equal(-1, lispindent(-1))
-
- set lisp
- set lispwords&
- let save_copt = &cpoptions
- set cpoptions+=p
- normal 1G=G
-
- call assert_equal([
- \ '(defun html-file (base)',
- \ ' (format nil "~(~A~).html" base))',
- \ '',
- \ '(defmacro page (name title &rest body)',
- \ ' (let ((ti (gensym)))',
- \ ' `(with-open-file (*standard-output*',
- \ ' (html-file ,name)',
- \ ' :direction :output',
- \ ' :if-exists :supersede)',
- \ ' (let ((,ti ,title))',
- \ ' (as title ,ti)',
- \ ' (with center ',
- \ ' (as h2 (string-upcase ,ti)))',
- \ ' (brs 3)',
- \ ' ,@body))))',
- \ '',
- \ ';;; Utilities for generating links',
- \ '',
- \ '(defmacro with-link (dest &rest body)',
- \ ' `(progn',
- \ ' (format t "<a href=\"~A\">" (html-file ,dest))',
- \ ' ,@body',
- \ ' (princ "</a>")))',
- \ ''
- \ ], getline(1, "$"))
-
- enew!
- let &cpoptions=save_copt
- set nolisp
-endfunc
-
-func Test_lispindent_negative()
- " in legacy script there is no error
- call assert_equal(-1, lispindent(-1))
-endfunc
-
-func Test_lispindent_with_indentexpr()
- enew
- setl ai lisp nocin indentexpr=11
- exe "normal a(x\<CR>1\<CR>2)\<Esc>"
- let expected = ['(x', ' 1', ' 2)']
- call assert_equal(expected, getline(1, 3))
- " with Lisp indenting the first line is not indented
- normal 1G=G
- call assert_equal(expected, getline(1, 3))
-
- %del
- setl lispoptions=expr:1 indentexpr=5
- exe "normal a(x\<CR>1\<CR>2)\<Esc>"
- let expected_expr = ['(x', ' 1', ' 2)']
- call assert_equal(expected_expr, getline(1, 3))
- normal 2G2<<=G
- call assert_equal(expected_expr, getline(1, 3))
-
- setl lispoptions=expr:0
- " with Lisp indenting the first line is not indented
- normal 1G3<<=G
- call assert_equal(expected, getline(1, 3))
-
- bwipe!
-endfunc
-
-func Test_lisp_indent_works()
- " This was reading beyond the end of the line
- new
- exe "norm a\tü(\<CR>="
- set lisp
- norm ==
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
deleted file mode 100644
index eed4d7e30c..0000000000
--- a/src/nvim/testdir/test_listchars.vim
+++ /dev/null
@@ -1,695 +0,0 @@
-" Tests for 'listchars' display with 'list' and :list
-
-source check.vim
-source view_util.vim
-source screendump.vim
-
-func Test_listchars()
- enew!
- set ff=unix
- set list
-
- set listchars+=tab:>-,space:.,trail:<
- call append(0, [
- \ ' aa ',
- \ ' bb ',
- \ ' cccc ',
- \ 'dd ee ',
- \ ' '
- \ ])
- 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 listchars-=trail:<
- 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
-
- " tab with 3rd character.
- set listchars-=tab:>-
- set listchars+=tab:<=>,trail:-
- 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
-
- " 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<====>$',
- \ '..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 listchars-=tab:<=>
- set listchars+=tab:>-
- set listchars+=trail:<
- set nolist
- normal ggdG
- call append(0, [
- \ ' fff ',
- \ ' gg ',
- \ ' h ',
- \ 'iii ',
- \ ])
- let l = split(execute("%list"), "\n")
- call assert_equal([
- \ '..fff>--<<$',
- \ '>-------gg>-----$',
- \ '.....h>-$',
- \ 'iii<<<<><<$', '$'], l)
-
- " Test lead and trail
- normal ggdG
- set listchars=eol:$ " Accommodate Nvim default
- set listchars+=lead:>,trail:<,space:x
- set list
-
- call append(0, [
- \ ' ffff ',
- \ ' gg',
- \ 'h ',
- \ ' ',
- \ ' 0 0 ',
- \ ])
-
- let expected = [
- \ '>>>>ffff<<<<$',
- \ '>>>>>>>>>>gg$',
- \ 'h<<<<<<<<<<<$',
- \ '<<<<<<<<<<<<$',
- \ '>>>>0xx0<<<<$',
- \ '$'
- \ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " Test multispace
- normal ggdG
- set listchars=eol:$ " Accommodate Nvim default
- set listchars+=multispace:yYzZ
- set list
-
- call append(0, [
- \ ' ffff ',
- \ ' i i gg',
- \ ' h ',
- \ ' j ',
- \ ' 0 0 ',
- \ ])
-
- let expected = [
- \ 'yYzZffffyYzZ$',
- \ 'yYi iyYzZygg$',
- \ ' hyYzZyYzZyY$',
- \ 'yYzZyYzZyYj $',
- \ 'yYzZ0yY0yYzZ$',
- \ '$'
- \ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " Test leadmultispace + multispace
- normal ggdG
- set listchars=eol:$,multispace:yYzZ,nbsp:S
- set listchars+=leadmultispace:.-+*
- set list
-
- call append(0, [
- \ ' ffff ',
- \ ' i i  gg',
- \ ' h ',
- \ ' j ',
- \ ' 0 0 ',
- \ ])
-
- let expected = [
- \ '.-+*ffffyYzZ$',
- \ '.-i iSyYzZgg$',
- \ ' hyYzZyYzZyY$',
- \ '.-+*.-+*.-j $',
- \ '.-+*0yY0yYzZ$',
- \ '$'
- \ ]
- redraw!
- call assert_equal('eol:$,multispace:yYzZ,nbsp:S,leadmultispace:.-+*', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " Test leadmultispace without multispace
- normal ggdG
- set listchars-=multispace:yYzZ
- set listchars+=space:+,trail:>,eol:$
- set list
-
- call append(0, [
- \ ' ffff ',
- \ ' i i gg',
- \ ' h ',
- \ ' j ',
- \ ' 0 0 ',
- \ ])
-
- let expected = [
- \ '.-+*ffff>>>>$',
- \ '.-i+i+++++gg$',
- \ '+h>>>>>>>>>>$',
- \ '.-+*.-+*.-j>$',
- \ '.-+*0++0>>>>$',
- \ '$',
- \ ]
-
- redraw!
- call assert_equal('eol:$,nbsp:S,leadmultispace:.-+*,space:+,trail:>,eol:$', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " Test leadmultispace only
- normal ggdG
- set listchars=eol:$ " Accommodate Nvim default
- set listchars=leadmultispace:.-+*
- set list
-
- call append(0, [
- \ ' ffff ',
- \ ' i i gg',
- \ ' h ',
- \ ' j ',
- \ ' 0 0 ',
- \ ])
-
- let expected = [
- \ '.-+*ffff ',
- \ '.-i i gg',
- \ ' h ',
- \ '.-+*.-+*.-j ',
- \ '.-+*0 0 ',
- \ ' ',
- \ ]
- redraw!
- call assert_equal('leadmultispace:.-+*', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, 12))
- endfor
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " Test leadmultispace and lead and space
- normal ggdG
- set listchars=eol:$ " Accommodate Nvim default
- set listchars+=lead:<,space:-
- set listchars+=leadmultispace:.-+*
- set list
-
- call append(0, [
- \ ' ffff ',
- \ ' i i gg',
- \ ' h ',
- \ ' j ',
- \ ' 0 0 ',
- \ ])
-
- let expected = [
- \ '.-+*ffff----$',
- \ '.-i-i-----gg$',
- \ '<h----------$',
- \ '.-+*.-+*.-j-$',
- \ '.-+*0--0----$',
- \ '$',
- \ ]
- redraw!
- call assert_equal('eol:$,lead:<,space:-,leadmultispace:.-+*', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " the last occurrence of 'multispace:' is used
- set listchars=eol:$ " Accommodate Nvim default
- set listchars+=multispace:yYzZ
- set listchars+=space:x,multispace:XyY
-
- let expected = [
- \ 'XyYXffffXyYX$',
- \ 'XyixiXyYXygg$',
- \ 'xhXyYXyYXyYX$',
- \ 'XyYXyYXyYXjx$',
- \ 'XyYX0Xy0XyYX$',
- \ '$'
- \ ]
- redraw!
- call assert_equal('eol:$,multispace:yYzZ,space:x,multispace:XyY', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- set listchars+=lead:>,trail:<
-
- let expected = [
- \ '>>>>ffff<<<<$',
- \ '>>ixiXyYXygg$',
- \ '>h<<<<<<<<<<$',
- \ '>>>>>>>>>>j<$',
- \ '>>>>0Xy0<<<<$',
- \ '$'
- \ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " removing 'multispace:'
- set listchars-=multispace:XyY
- set listchars-=multispace:yYzZ
-
- let expected = [
- \ '>>>>ffff<<<<$',
- \ '>>ixixxxxxgg$',
- \ '>h<<<<<<<<<<$',
- \ '>>>>>>>>>>j<$',
- \ '>>>>0xx0<<<<$',
- \ '$'
- \ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
- call assert_equal(expected, split(execute("%list"), "\n"))
-
- " test nbsp
- normal ggdG
- set listchars=nbsp:X,trail:Y
- set list
- " Non-breaking space
- let nbsp = nr2char(0xa0)
- call append(0, [ ">" .. nbsp .. "<" ])
-
- let expected = '>X< '
-
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, virtcol('$')))
-
- set listchars=nbsp:X
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, virtcol('$')))
-
- " test extends
- normal ggdG
- set listchars=extends:Z
- set nowrap
- set nolist
- call append(0, [ repeat('A', &columns + 1) ])
-
- let expected = repeat('A', &columns)
-
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, &columns))
-
- set list
- let expected = expected[:-2] . 'Z'
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, &columns))
-
- enew!
- set listchars& ff&
-endfunc
-
-" Test that unicode listchars characters get properly inserted
-func Test_listchars_unicode()
- enew!
- let oldencoding=&encoding
- set encoding=utf-8
- set ff=unix
-
- set listchars=eol:⇔,space:â£,multispace:≡≢≣,nbsp:≠,tab:â†â†”→
- set list
-
- let nbsp = nr2char(0xa0)
- call append(0, [" a\tb c" .. nbsp .. "d "])
- let expected = ['≡≢≣≡≢≣≡≢aâ†â†”↔↔↔↔→bâ£c≠d≡≢⇔']
- redraw!
- call cursor(1, 1)
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
-
- set listchars=eol:\\u21d4,space:\\u2423,multispace:≡\\u2262\\U00002263,nbsp:\\U00002260,tab:â†â†”\\u2192
- redraw!
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
-
- set listchars+=lead:⇨,trail:⇦
- let expected = ['⇨⇨⇨⇨⇨⇨⇨⇨aâ†â†”↔↔↔↔→bâ£c≠d⇦⇦⇔']
- redraw!
- call cursor(1, 1)
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
-
- let &encoding=oldencoding
- enew!
- set listchars& ff&
-endfunction
-
-func Test_listchars_invalid()
- enew!
- set ff=unix
-
- set listchars=eol:$ " Accommodate Nvim default
- set list
- set ambiwidth=double
-
- " No colon
- call assert_fails('set listchars=x', 'E474:')
- call assert_fails('set listchars=x', 'E474:')
- call assert_fails('set listchars=multispace', 'E474:')
- call assert_fails('set listchars=leadmultispace', 'E474:')
-
- " Too short
- call assert_fails('set listchars=space:', 'E474:')
- call assert_fails('set listchars=tab:x', 'E474:')
- call assert_fails('set listchars=multispace:', 'E474:')
- call assert_fails('set listchars=leadmultispace:', 'E474:')
-
- " One occurrence too short
- call assert_fails('set listchars=space:,space:x', 'E474:')
- call assert_fails('set listchars=space:x,space:', 'E474:')
- call assert_fails('set listchars=tab:x,tab:xx', 'E474:')
- call assert_fails('set listchars=tab:xx,tab:x', 'E474:')
- call assert_fails('set listchars=multispace:,multispace:x', 'E474:')
- call assert_fails('set listchars=multispace:x,multispace:', 'E474:')
- call assert_fails('set listchars=leadmultispace:,leadmultispace:x', 'E474:')
- call assert_fails('set listchars=leadmultispace:x,leadmultispace:', 'E474:')
-
- " Too long
- call assert_fails('set listchars=space:xx', 'E474:')
- call assert_fails('set listchars=tab:xxxx', 'E474:')
-
- " Has double-width character
- call assert_fails('set listchars=space:·', 'E474:')
- call assert_fails('set listchars=tab:·x', 'E474:')
- call assert_fails('set listchars=tab:x·', 'E474:')
- call assert_fails('set listchars=tab:xx·', 'E474:')
- call assert_fails('set listchars=multispace:·', 'E474:')
- call assert_fails('set listchars=multispace:xxx·', 'E474:')
- call assert_fails('set listchars=leadmultispace:·', 'E474:')
- call assert_fails('set listchars=leadmultispace:xxx·', 'E474:')
-
- " Has control character
- call assert_fails("set listchars=space:\x01", 'E474:')
- call assert_fails("set listchars=tab:\x01x", 'E474:')
- call assert_fails("set listchars=tab:x\x01", 'E474:')
- call assert_fails("set listchars=tab:xx\x01", 'E474:')
- call assert_fails("set listchars=multispace:\x01", 'E474:')
- call assert_fails("set listchars=multispace:xxx\x01", 'E474:')
- call assert_fails('set listchars=space:\\x01', 'E474:')
- call assert_fails('set listchars=tab:\\x01x', 'E474:')
- call assert_fails('set listchars=tab:x\\x01', 'E474:')
- call assert_fails('set listchars=tab:xx\\x01', 'E474:')
- call assert_fails('set listchars=multispace:\\x01', 'E474:')
- call assert_fails('set listchars=multispace:xxx\\x01', 'E474:')
- call assert_fails("set listchars=leadmultispace:\x01", 'E474:')
- call assert_fails('set listchars=leadmultispace:\\x01', 'E474:')
- call assert_fails("set listchars=leadmultispace:xxx\x01", 'E474:')
- call assert_fails('set listchars=leadmultispace:xxx\\x01', 'E474:')
-
- enew!
- set ambiwidth& listchars& ff&
-endfunction
-
-" Tests that space characters following composing character won't get replaced
-" by listchars.
-func Test_listchars_composing()
- enew!
- let oldencoding=&encoding
- set encoding=utf-8
- set ff=unix
- set list
-
- set listchars=eol:$,space:_,nbsp:=
-
- let nbsp1 = nr2char(0xa0)
- let nbsp2 = nr2char(0x202f)
- call append(0, [
- \ " \u3099\t \u309A" .. nbsp1 .. nbsp1 .. "\u0302" .. nbsp2 .. nbsp2 .. "\u0302",
- \ ])
- let expected = [
- \ "_ \u3099^I \u309A=" .. nbsp1 .. "\u0302=" .. nbsp2 .. "\u0302$"
- \ ]
- redraw!
- call cursor(1, 1)
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
- let &encoding=oldencoding
- enew!
- set listchars& ff&
-endfunction
-
-" Check for the value of the 'listchars' option
-func s:CheckListCharsValue(expected)
- call assert_equal(a:expected, &listchars)
- call assert_equal(a:expected, getwinvar(0, '&listchars'))
-endfunc
-
-" Test for using a window local value for 'listchars'
-func Test_listchars_window_local()
- %bw!
- set list listchars&
- let l:default_listchars = &listchars " Accommodate Nvim default
- new
- " set a local value for 'listchars'
- setlocal listchars=tab:+-,eol:#
- call s:CheckListCharsValue('tab:+-,eol:#')
- " When local value is reset, global value should be used
- setlocal listchars=
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
- " Use 'setlocal <' to copy global value
- setlocal listchars=space:.,extends:>
- setlocal listchars<
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
- " Use 'set <' to copy global value
- setlocal listchars=space:.,extends:>
- set listchars<
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
- " Changing global setting should not change the local setting
- setlocal listchars=space:.,extends:>
- setglobal listchars=tab:+-,eol:#
- call s:CheckListCharsValue('space:.,extends:>')
- " when split opening a new window, local value should be copied
- split
- call s:CheckListCharsValue('space:.,extends:>')
- " clearing local value in one window should not change the other window
- set listchars&
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
- close
- call s:CheckListCharsValue('space:.,extends:>')
-
- " use different values for 'listchars' items in two different windows
- call setline(1, ["\t one two "])
- setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
- split
- setlocal listchars=tab:[.],lead:#,space:_,trail:.,eol:&
- split
- set listchars=tab:+-+,lead:^,space:>,trail:<,eol:%
- call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- " changing the global setting should not change the local value
- setglobal listchars=tab:[.],lead:#,space:_,trail:.,eol:&
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- set listchars<
- call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$')))
-
- " Using setglobal in a window with local setting should not affect the
- " window. But should impact other windows using the global setting.
- enew! | only
- call setline(1, ["\t one two "])
- set listchars=tab:[.],lead:#,space:_,trail:.,eol:&
- split
- setlocal listchars=tab:+-+,lead:^,space:>,trail:<,eol:%
- split
- setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
- setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$')))
-
- " Setting the global setting to the default value should not impact a window
- " using a local setting.
- split
- setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
- setglobal listchars=eol:$ " Accommodate Nvim default
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$')))
-
- " Setting the local setting to the default value should not impact a window
- " using a global setting.
- set listchars=tab:{.},lead:-,space:=,trail:#,eol:$
- split
- setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- setlocal listchars=eol:$ " Accommodate Nvim default
- call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$')))
-
- " Using set in a window with a local setting should change it to use the
- " global setting and also impact other windows using the global setting.
- split
- setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- set listchars=tab:+-+,lead:^,space:>,trail:<,eol:%
- call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
-
- " Setting invalid value for a local setting should not impact the local and
- " global settings.
- split
- setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
- let cmd = 'setlocal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x'
- call assert_fails(cmd, 'E474:')
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
-
- " Setting invalid value for a global setting should not impact the local and
- " global settings.
- split
- setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
- let cmd = 'setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x'
- call assert_fails(cmd, 'E474:')
- call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
- close
- call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
-
- " Closing window with local lcs-multispace should not cause a memory leak.
- setlocal listchars=multispace:---+
- split
- call s:CheckListCharsValue('multispace:---+')
- close
-
- %bw!
- set list& listchars&
-endfunc
-
-func Test_listchars_foldcolumn()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['aaa', '', 'a', 'aaaaaa'])
- vsplit
- vsplit
- windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:<
- END
- call writefile(lines, 'XTest_listchars')
-
- let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60})
-
- call term_sendkeys(buf, "13\<C-W>>")
- call VerifyScreenDump(buf, 'Test_listchars_01', {})
- call term_sendkeys(buf, "\<C-W>>")
- call VerifyScreenDump(buf, 'Test_listchars_02', {})
- call term_sendkeys(buf, "\<C-W>>")
- call VerifyScreenDump(buf, 'Test_listchars_03', {})
- call term_sendkeys(buf, "\<C-W>>")
- call VerifyScreenDump(buf, 'Test_listchars_04', {})
- call term_sendkeys(buf, "\<C-W>>")
- call VerifyScreenDump(buf, 'Test_listchars_05', {})
- call term_sendkeys(buf, "\<C-W>h")
- call term_sendkeys(buf, ":set nowrap foldcolumn=4\<CR>")
- call term_sendkeys(buf, "15\<C-W><")
- call VerifyScreenDump(buf, 'Test_listchars_06', {})
- call term_sendkeys(buf, "4\<C-W><")
- call VerifyScreenDump(buf, 'Test_listchars_07', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_listchars')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
deleted file mode 100644
index 9ecd83265a..0000000000
--- a/src/nvim/testdir/test_listdict.vim
+++ /dev/null
@@ -1,1070 +0,0 @@
-" Tests for the List and Dict types
-
-func TearDown()
- " Run garbage collection after every test
- call test_garbagecollect_now()
-endfunc
-
-" Tests for List type
-
-" List creation
-func Test_list_create()
- " Creating List directly with different types
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- call assert_equal("[1, 'as''d', [1, 2, function('strlen')], {'a': 1}]", string(l))
- call assert_equal({'a' : 1}, l[-1])
- call assert_equal(1, l[-4])
- let x = 10
- try
- let x = l[-5]
- catch
- call assert_match('E684:', v:exception)
- endtry
- call assert_equal(10, x)
-endfunc
-
-" List slices
-func Test_list_slice()
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[:])
- call assert_equal(['as''d', [1, 2, function('strlen')], {'a': 1}], l[1:])
- call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2])
- call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8])
- call assert_equal([], l[8:-1])
- call assert_equal([], l[0:-10])
- " perform an operation on a list slice
- let l = [1, 2, 3]
- let l[:1] += [1, 2]
- let l[2:] -= [1]
- call assert_equal([2, 4, 2], l)
-endfunc
-
-" List identity
-func Test_list_identity()
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- let ll = l
- let lx = copy(l)
- call assert_true(l == ll)
- call assert_false(l isnot ll)
- call assert_true(l is ll)
- call assert_true(l == lx)
- call assert_false(l is lx)
- call assert_true(l isnot lx)
-endfunc
-
-" removing items with :unlet
-func Test_list_unlet()
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- unlet l[2]
- call assert_equal([1, 'as''d', {'a': 1}], l)
- let l = range(8)
- unlet l[:3]
- unlet l[1:]
- call assert_equal([4], l)
-
- " removing items out of range: silently skip items that don't exist
- let l = [0, 1, 2, 3]
- call assert_fails('unlet l[2:1]', 'E684')
- let l = [0, 1, 2, 3]
- unlet l[2:2]
- call assert_equal([0, 1, 3], l)
- let l = [0, 1, 2, 3]
- unlet l[2:3]
- call assert_equal([0, 1], l)
- let l = [0, 1, 2, 3]
- unlet l[2:4]
- call assert_equal([0, 1], l)
- let l = [0, 1, 2, 3]
- unlet l[2:5]
- call assert_equal([0, 1], l)
- let l = [0, 1, 2, 3]
- call assert_fails('unlet l[-1:2]', 'E684')
- let l = [0, 1, 2, 3]
- unlet l[-2:2]
- call assert_equal([0, 1, 3], l)
- let l = [0, 1, 2, 3]
- unlet l[-3:2]
- call assert_equal([0, 3], l)
- let l = [0, 1, 2, 3]
- unlet l[-4:2]
- call assert_equal([3], l)
- let l = [0, 1, 2, 3]
- unlet l[-5:2]
- call assert_equal([3], l)
- let l = [0, 1, 2, 3]
- unlet l[-6:2]
- call assert_equal([3], l)
-endfunc
-
-" assignment to a list
-func Test_list_assign()
- let l = [0, 1, 2, 3]
- let [va, vb] = l[2:3]
- call assert_equal([2, 3], [va, vb])
- call assert_fails('let [va, vb] = l', 'E687')
- call assert_fails('let [va, vb] = l[1:1]', 'E688')
-endfunc
-
-" test for range assign
-func Test_list_range_assign()
- let l = [0]
- let l[:] = [1, 2]
- call assert_equal([1, 2], l)
- let l[-4:-1] = [5, 6]
- call assert_equal([5, 6], l)
-endfunc
-
-" Test removing items in list
-func Test_list_func_remove()
- " Test removing 1 element
- let l = [1, 2, 3, 4]
- call assert_equal(1, remove(l, 0))
- call assert_equal([2, 3, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal(2, remove(l, 1))
- call assert_equal([1, 3, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal(4, remove(l, -1))
- call assert_equal([1, 2, 3], l)
-
- " Test removing range of element(s)
- let l = [1, 2, 3, 4]
- call assert_equal([3], remove(l, 2, 2))
- call assert_equal([1, 2, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal([2, 3], remove(l, 1, 2))
- call assert_equal([1, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal([2, 3], remove(l, -3, -2))
- call assert_equal([1, 4], l)
-
- " Test invalid cases
- let l = [1, 2, 3, 4]
- call assert_fails("call remove(l, 5)", 'E684:')
- call assert_fails("call remove(l, 1, 5)", 'E684:')
- call assert_fails("call remove(l, 3, 2)", 'E16:')
- call assert_fails("call remove(1, 0)", 'E896:')
- call assert_fails("call remove(l, l)", 'E745:')
-endfunc
-
-" List add() function
-func Test_list_add()
- let l = []
- call add(l, 1)
- call add(l, [2, 3])
- call add(l, [])
- call add(l, v:_null_list)
- call add(l, {'k' : 3})
- call add(l, {})
- call add(l, v:_null_dict)
- call assert_equal([1, [2, 3], [], [], {'k' : 3}, {}, {}], l)
- " call assert_equal(1, add(v:_null_list, 4))
-endfunc
-
-" Tests for Dictionary type
-
-func Test_dict()
- " Creating Dictionary directly with different types
- let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},}
- call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d))
- call assert_equal('asd', d.1)
- call assert_equal(['-1', '1', 'b'], sort(keys(d)))
- call assert_equal(['asd', [1, 2, function('strlen')], {'a': 1}], values(d))
- let v = []
- for [key, val] in items(d)
- call extend(v, [key, val])
- unlet key val
- endfor
- call assert_equal(['1','asd','b',[1, 2, function('strlen')],'-1',{'a': 1}], v)
-
- call extend(d, {3:33, 1:99})
- call extend(d, {'b':'bbb', 'c':'ccc'}, "keep")
- call assert_fails("call extend(d, {3:333,4:444}, 'error')", 'E737')
- call assert_equal({'c': 'ccc', '1': 99, 'b': [1, 2, function('strlen')], '3': 33, '-1': {'a': 1}}, d)
- call filter(d, 'v:key =~ ''[ac391]''')
- call assert_equal({'c': 'ccc', '1': 99, '3': 33, '-1': {'a': 1}}, d)
-
- " duplicate key
- call assert_fails("let d = {'k' : 10, 'k' : 20}", 'E721:')
- " missing comma
- call assert_fails("let d = {'k' : 10 'k' : 20}", 'E722:')
- " missing curly brace
- call assert_fails("let d = {'k' : 10,", 'E723:')
- " invalid key
- call assert_fails('let d = #{++ : 10}', 'E15:')
- " wrong type for key
- call assert_fails('let d={[] : 10}', 'E730:')
- " undefined variable as value
- call assert_fails("let d={'k' : i}", 'E121:')
-
- " allow key starting with number at the start, not a curly expression
- call assert_equal({'1foo': 77}, #{1foo: 77})
-
- " #{expr} is not a curly expression
- let x = 'x'
- call assert_equal(#{g: x}, #{g:x})
-endfunc
-
-" Dictionary identity
-func Test_dict_identity()
- let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},}
- let dd = d
- let dx = copy(d)
- call assert_true(d == dd)
- call assert_false(d isnot dd)
- call assert_true(d is dd)
- call assert_true(d == dx)
- call assert_false(d is dx)
- call assert_true(d isnot dx)
-endfunc
-
-" removing items with :unlet
-func Test_dict_unlet()
- let d = {'b':'bbb', '1': 99, '3': 33, '-1': {'a': 1}}
- unlet d.b
- unlet d[-1]
- call assert_equal({'1': 99, '3': 33}, d)
-endfunc
-
-" manipulating a big Dictionary (hashtable.c has a border of 1000 entries)
-func Test_dict_big()
- let d = {}
- for i in range(1500)
- let d[i] = 3000 - i
- endfor
- call assert_equal([3000, 2900, 2001, 1600, 1501], [d[0], d[100], d[999], d[1400], d[1499]])
- let str = ''
- try
- let n = d[1500]
- catch
- let str = substitute(v:exception, '\v(.{14}).*( "\d{4}").*', '\1\2', '')
- endtry
- call assert_equal('Vim(let):E716: "1500"', str)
-
- " lookup each items
- for i in range(1500)
- call assert_equal(3000 - i, d[i])
- endfor
- let i += 1
-
- " delete even items
- while i >= 2
- let i -= 2
- unlet d[i]
- endwhile
- call assert_equal('NONE', get(d, 1500 - 100, 'NONE'))
- call assert_equal(2999, d[1])
-
- " delete odd items, checking value, one intentionally wrong
- let d[33] = 999
- let i = 1
- while i < 1500
- if i != 33
- call assert_equal(3000 - i, d[i])
- else
- call assert_equal(999, d[i])
- endif
- unlet d[i]
- let i += 2
- endwhile
- call assert_equal({}, d)
- unlet d
-endfunc
-
-" Dictionary function
-func Test_dict_func()
- let d = {}
- func d.func(a) dict
- return a:a . len(self.data)
- endfunc
- let d.data = [1,2,3]
- call assert_equal('len: 3', d.func('len: '))
- let x = d.func('again: ')
- call assert_equal('again: 3', x)
- let Fn = d.func
- call assert_equal('xxx3', Fn('xxx'))
-endfunc
-
-func Test_dict_assign()
- let d = {}
- let d.1 = 1
- let d._ = 2
- call assert_equal({'1': 1, '_': 2}, d)
-
- let n = 0
- call assert_fails('let n.key = 3', 'E1203: Dot can only be used on a dictionary: n.key = 3')
-endfunc
-
-" Function in script-local List or Dict
-func Test_script_local_dict_func()
- let g:dict = {}
- function g:dict.func() dict
- return 'g:dict.func' . self.foo[1] . self.foo[0]('asdf')
- endfunc
- let g:dict.foo = ['-', 2, 3]
- call insert(g:dict.foo, function('strlen'))
- call assert_equal('g:dict.func-4', g:dict.func())
- unlet g:dict
-endfunc
-
-" Test removing items in a dictionary
-func Test_dict_func_remove()
- let d = {1:'a', 2:'b', 3:'c'}
- call assert_equal('b', remove(d, 2))
- call assert_equal({1:'a', 3:'c'}, d)
-
- call assert_fails("call remove(d, 1, 2)", 'E118:')
- call assert_fails("call remove(d, 'a')", 'E716:')
- call assert_fails("call remove(d, [])", 'E730:')
-endfunc
-
-" Nasty: remove func from Dict that's being called (works)
-func Test_dict_func_remove_in_use()
- let d = {1:1}
- func d.func(a)
- return "a:" . a:a
- endfunc
- let expected = 'a:' . string(get(d, 'func'))
- call assert_equal(expected, d.func(string(remove(d, 'func'))))
-endfunc
-
-func Test_dict_literal_keys()
- call assert_equal({'one': 1, 'two2': 2, '3three': 3, '44': 4}, #{one: 1, two2: 2, 3three: 3, 44: 4},)
-
- " why *{} cannot be used
- let blue = 'blue'
- call assert_equal('6', trim(execute('echo 2 *{blue: 3}.blue')))
-endfunc
-
-" Nasty: deepcopy() dict that refers to itself (fails when noref used)
-func Test_dict_deepcopy()
- let d = {1:1, 2:2}
- let l = [4, d, 6]
- let d[3] = l
- let dc = deepcopy(d)
- call assert_fails('call deepcopy(d, 1)', 'E698:')
- let l2 = [0, l, l, 3]
- let l[1] = l2
- let l3 = deepcopy(l2)
- call assert_true(l3[1] is l3[2])
- call assert_fails("call deepcopy([1, 2], 2)", 'E1023:')
-endfunc
-
-" Locked variables
-func Test_list_locked_var()
- let expected = [
- \ [['1000-000', 'ppppppF'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1000-000', 'ppppppF'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1100-100', 'ppFppFF'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1110-110', 'pFFpFFF'],
- \ ['0010-010', 'pFppFpp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1111-111', 'FFFFFFF'],
- \ ['0011-011', 'FFpFFpp'],
- \ ['0000-000', 'ppppppp']]
- \ ]
- for depth in range(5)
- for u in range(3)
- unlet! l
- let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
- exe "lockvar " . depth . " l"
- if u == 1
- exe "unlockvar l"
- elseif u == 2
- exe "unlockvar " . depth . " l"
- endif
- let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]")
- call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
- let ps = ''
- try
- let l[1][1][0] = 99
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[1][1] = [99]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[1] = [99]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[2]['6'][7] = 99
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[2][6] = {99: 99}
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[2] = {99: 99}
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l = [99]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- call assert_equal(expected[depth][u][1], ps, 'depth: ' .. depth)
- endfor
- endfor
- call assert_fails("let x=islocked('a b')", 'E488:')
- let mylist = [1, 2, 3]
- call assert_fails("let x = islocked('mylist[1:2]')", 'E786:')
- let mydict = {'k' : 'v'}
- call assert_fails("let x = islocked('mydict.a')", 'E716:')
-endfunc
-
-" Unletting locked variables
-func Test_list_locked_var_unlet()
- let expected = [
- \ [['1000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1000-000', 'ppFppFp'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1100-100', 'pFFpFFp'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1110-110', 'FFFFFFp'],
- \ ['0010-010', 'FppFppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1111-111', 'FFFFFFp'],
- \ ['0011-011', 'FppFppp'],
- \ ['0000-000', 'ppppppp']]
- \ ]
-
- for depth in range(5)
- for u in range(3)
- unlet! l
- let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
- exe "lockvar " . depth . " l"
- if u == 1
- exe "unlockvar l"
- elseif u == 2
- exe "unlockvar " . depth . " l"
- endif
- let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]")
- call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
- let ps = ''
- try
- unlet l[2]['6'][7]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[2][6]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[2]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[1][1][0]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[1][1]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[1]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- call assert_equal(expected[depth][u][1], ps)
- endfor
- endfor
- " Deleting a list range should fail if the range is locked
- let l = [1, 2, 3, 4]
- lockvar l[1:2]
- call assert_fails('unlet l[1:2]', 'E741:')
- unlet l
-endfunc
-
-" Locked variables and :unlet or list / dict functions
-
-" No :unlet after lock on dict:
-func Test_dict_lock_unlet()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar 1 d
- call assert_fails('unlet d.a', 'E741')
-endfunc
-
-" unlet after lock on dict item
-func Test_dict_item_lock_unlet()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar d.a
- unlet d.a
- call assert_equal({'b' : 100}, d)
-endfunc
-
-" filter() after lock on dict item
-func Test_dict_lock_filter()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar d.a
- call filter(d, 'v:key != "a"')
- call assert_equal({'b' : 100}, d)
-endfunc
-
-" map() after lock on dict
-func Test_dict_lock_map()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar 1 d
- call map(d, 'v:val + 200')
- call assert_equal({'a' : 299, 'b' : 300}, d)
-endfunc
-
-" No extend() after lock on dict item
-func Test_dict_lock_extend()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar d.a
- call assert_fails("call extend(d, {'a' : 123})", 'E741')
- call assert_equal({'a': 99, 'b': 100}, d)
-endfunc
-
-" Cannot use += with a locked dict
-func Test_dict_lock_operator()
- unlet! d
- let d = {}
- lockvar d
- call assert_fails("let d += {'k' : 10}", 'E741:')
- unlockvar d
-endfunc
-
-" No remove() of write-protected scope-level variable
-func Tfunc1(this_is_a_long_parameter_name)
- call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742')
-endfunc
-func Test_dict_scope_var_remove()
- call Tfunc1('testval')
-endfunc
-
-" No extend() of write-protected scope-level variable
-func Test_dict_scope_var_extend()
- call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742')
-endfunc
-func Tfunc2(this_is_a_long_parameter_name)
- call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742')
-endfunc
-func Test_dict_scope_var_extend_overwrite()
- call Tfunc2('testval')
-endfunc
-
-" No :unlet of variable in locked scope
-func Test_lock_var_unlet()
- let b:testvar = 123
- lockvar 1 b:
- call assert_fails('unlet b:testvar', 'E741:')
- unlockvar 1 b:
- unlet! b:testvar
-endfunc
-
-" No :let += of locked list variable
-func Test_let_lock_list()
- let l = ['a', 'b', 3]
- lockvar 1 l
- call assert_fails("let l += ['x']", 'E741:')
- call assert_equal(['a', 'b', 3], l)
-
- unlet l
- let l = [1, 2, 3, 4]
- lockvar! l
- call assert_equal([1, 2, 3, 4], l)
- unlockvar l[1]
- call assert_fails('unlet l[0:1]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- call assert_fails('unlet l[1:2]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- unlockvar l[1]
- call assert_fails('let l[0:1] = [0, 1]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- call assert_fails('let l[1:2] = [0, 1]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- unlet l
-endfunc
-
-" Locking part of the list
-func Test_let_lock_list_items()
- let l = [1, 2, 3, 4]
- lockvar l[2:]
- call assert_equal(0, islocked('l[0]'))
- call assert_equal(1, islocked('l[2]'))
- call assert_equal(1, islocked('l[3]'))
- call assert_fails('let l[2] = 10', 'E741:')
- call assert_fails('let l[3] = 20', 'E741:')
- unlet l
-endfunc
-
-" lockvar/islocked() triggering script autoloading
-func Test_lockvar_script_autoload()
- let old_rtp = &rtp
- set rtp+=./sautest
- lockvar g:footest#x
- unlockvar g:footest#x
- call assert_equal(-1, 'g:footest#x'->islocked())
- call assert_equal(0, exists('g:footest#x'))
- call assert_equal(1, g:footest#x)
- let &rtp = old_rtp
-endfunc
-
-" a:000 function argument test
-func s:arg_list_test(...)
- call assert_fails('let a:000 = [1, 2]', 'E46:')
- call assert_fails('let a:000[0] = 9', 'E742:')
- call assert_fails('let a:000[2] = [9, 10]', 'E742:')
- call assert_fails('let a:000[3] = {9 : 10}', 'E742:')
-
- " now the tests that should pass
- let a:000[2][1] = 9
- call extend(a:000[2], [5, 6])
- let a:000[3][5] = 8
- let a:000[3]['a'] = 12
- call assert_equal([1, 2, [3, 9, 5, 6], {'a': 12, '5': 8}], a:000)
-endfunc
-
-func Test_func_arg_list()
- call s:arg_list_test(1, 2, [3, 4], {5: 6})
-endfunc
-
-func Test_dict_item_locked()
-endfunc
-
-" Tests for reverse(), sort(), uniq()
-func Test_reverse_sort_uniq()
- let l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5]
- call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l)))
- call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l))
- call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l)))
- if has('float')
- call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l))
- call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l)))
- call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l))))
- call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l)))
-
- let l = [7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four']
- call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n'))
-
- let l = [7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []]
- call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1))
- call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
- call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l)))
- endif
-
- call assert_fails('call reverse("")', 'E899:')
- call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:')
- call assert_fails("call sort([1, 2], function('min'), 1)", "E715:")
- call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:")
- call assert_fails("call sort([1, 2], function('min'))", "E118:")
-endfunc
-
-" reduce a list or a blob
-func Test_reduce()
- call assert_equal(1, reduce([], { acc, val -> acc + val }, 1))
- call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1))
- call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1))
- call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a'))
- call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
- call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
-
- let l = ['x', 'y', 'z']
- call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } }))
- call assert_equal(['x', 'y', 'z'], l)
-
- call assert_equal(1, reduce([1], { acc, val -> acc + val }))
- call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val }))
- call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val }))
- call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
-
- call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1))
- call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1))
- call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1))
-
- call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val }))
- call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val }))
- call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
-
- call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
- call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
- call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
- call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:')
- call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:')
-
- let g:lut = [1, 2, 3, 4]
- func EvilRemove()
- call remove(g:lut, 1)
- return 1
- endfunc
- call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
- unlet g:lut
- delfunc EvilRemove
-
- call assert_equal(42, reduce(v:_null_list, function('add'), 42))
- call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
-endfunc
-
-" splitting a string to a List using split()
-func Test_str_split()
- call assert_equal(['aa', 'bb'], split(' aa bb '))
- call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0))
- call assert_equal(['', 'aa', 'bb', ''], split(' aa bb ', '\W\+', 1))
- call assert_equal(['', '', 'aa', '', 'bb', '', ''], split(' aa bb ', '\W', 1))
- call assert_equal(['aa', '', 'bb'], split(':aa::bb:', ':', 0))
- call assert_equal(['', 'aa', '', 'bb', ''], split(':aa::bb:', ':', 1))
- call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1))
- call assert_equal(['a', 'b', 'c'], split('abc', '\zs'))
- call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1))
- call assert_fails("call split('abc', [])", 'E730:')
- call assert_fails("call split('abc', 'b', [])", 'E745:')
- call assert_equal(['abc'], split('abc', '\\%('))
-endfunc
-
-" compare recursively linked list and dict
-func Test_listdict_compare()
- let l = [1, 2, 3, 4]
- let d = {'1': 1, '2': l, '3': 3}
- let l[1] = d
- call assert_true(l == l)
- call assert_true(d == d)
- call assert_false(l != deepcopy(l))
- call assert_false(d != deepcopy(d))
-
- " comparison errors
- call assert_fails('echo [1, 2] =~ {}', 'E691:')
- call assert_fails('echo [1, 2] =~ [1, 2]', 'E692:')
- call assert_fails('echo {} =~ 5', 'E735:')
- call assert_fails('echo {} =~ {}', 'E736:')
-endfunc
-
- " compare complex recursively linked list and dict
-func Test_listdict_compare_complex()
- let l = []
- call add(l, l)
- let dict4 = {"l": l}
- call add(dict4.l, dict4)
- let lcopy = deepcopy(l)
- let dict4copy = deepcopy(dict4)
- call assert_true(l == lcopy)
- call assert_true(dict4 == dict4copy)
-endfunc
-
-func Test_listdict_extend()
- " Test extend() with lists
-
- " Pass the same List to extend()
- let l = [1, 2, 3]
- call assert_equal([1, 2, 3, 1, 2, 3], extend(l, l))
- call assert_equal([1, 2, 3, 1, 2, 3], l)
-
- let l = [1, 2, 3]
- call assert_equal([1, 2, 3, 4, 5, 6], extend(l, [4, 5, 6]))
- call assert_equal([1, 2, 3, 4, 5, 6], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], 0)
- call assert_equal([4, 5, 6, 1, 2, 3], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], 1)
- call assert_equal([1, 4, 5, 6, 2, 3], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], 3)
- call assert_equal([1, 2, 3, 4, 5, 6], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], -1)
- call assert_equal([1, 2, 4, 5, 6, 3], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], -3)
- call assert_equal([4, 5, 6, 1, 2, 3], l)
-
- let l = [1, 2, 3]
- call assert_fails("call extend(l, [4, 5, 6], 4)", 'E684:')
- call assert_fails("call extend(l, [4, 5, 6], -4)", 'E684:')
- if has('float')
- call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
- endif
-
- " Test extend() with dictionaries.
-
- " Pass the same Dict to extend()
- let d = { 'a': {'b': 'B'}}
- call extend(d, d)
- call assert_equal({'a': {'b': 'B'}}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, extend(d, {'b': 0, 'c':'C'}))
- call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call extend(d, {'a': 'A', 'b': 0, 'c': 'C'}, "force")
- call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call extend(d, {'b': 0, 'c':'C'}, "keep")
- call assert_equal({'a': 'A', 'b': 'B', 'c': 'C'}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'error')", 'E737:')
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'xxx')", 'E475:')
- if has('float')
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E806:')
- endif
- call assert_equal({'a': 'A', 'b': 'B'}, d)
-
- call assert_fails("call extend([1, 2], 1)", 'E712:')
- call assert_fails("call extend([1, 2], {})", 'E712:')
-
- " Extend g: dictionary with an invalid variable name
- call assert_fails("call extend(g:, {'-!' : 10})", 'E461:')
-
- " Extend a list with itself.
- let l = [1, 5, 7]
- call extend(l, l, 0)
- call assert_equal([1, 5, 7, 1, 5, 7], l)
- let l = [1, 5, 7]
- call extend(l, l, 1)
- call assert_equal([1, 1, 5, 7, 5, 7], l)
- let l = [1, 5, 7]
- call extend(l, l, 2)
- call assert_equal([1, 5, 1, 5, 7, 7], l)
- let l = [1, 5, 7]
- call extend(l, l, 3)
- call assert_equal([1, 5, 7, 1, 5, 7], l)
-endfunc
-
-func s:check_scope_dict(x, fixed)
- func s:gen_cmd(cmd, x)
- return substitute(a:cmd, '\<x\ze:', a:x, 'g')
- endfunc
-
- let cmd = s:gen_cmd('let x:foo = 1', a:x)
- if a:fixed
- call assert_fails(cmd, 'E461')
- else
- exe cmd
- exe s:gen_cmd('call assert_equal(1, x:foo)', a:x)
- endif
-
- let cmd = s:gen_cmd('let x:["bar"] = 2', a:x)
- if a:fixed
- call assert_fails(cmd, 'E461')
- else
- exe cmd
- exe s:gen_cmd('call assert_equal(2, x:bar)', a:x)
- endif
-
- let cmd = s:gen_cmd('call extend(x:, {"baz": 3})', a:x)
- if a:fixed
- call assert_fails(cmd, 'E742')
- else
- exe cmd
- exe s:gen_cmd('call assert_equal(3, x:baz)', a:x)
- endif
-
- if a:fixed
- if a:x ==# 'a'
- call assert_fails('unlet a:x', 'E795')
- call assert_fails('call remove(a:, "x")', 'E742')
- elseif a:x ==# 'v'
- call assert_fails('unlet v:count', 'E795')
- call assert_fails('call remove(v:, "count")', 'E742')
- endif
- else
- exe s:gen_cmd('unlet x:foo', a:x)
- exe s:gen_cmd('unlet x:bar', a:x)
- exe s:gen_cmd('call remove(x:, "baz")', a:x)
- endif
-
- delfunc s:gen_cmd
-endfunc
-
-func Test_scope_dict()
- " Test for g:
- call s:check_scope_dict('g', v:false)
-
- " Test for s:
- call s:check_scope_dict('s', v:false)
-
- " Test for l:
- call s:check_scope_dict('l', v:false)
-
- " Test for a:
- call s:check_scope_dict('a', v:true)
-
- " Test for b:
- call s:check_scope_dict('b', v:false)
-
- " Test for w:
- call s:check_scope_dict('w', v:false)
-
- " Test for t:
- call s:check_scope_dict('t', v:false)
-
- " Test for v:
- call s:check_scope_dict('v', v:true)
-endfunc
-
-" Test for deep nesting of lists (> 100)
-func Test_deep_nested_list()
- let deep_list = []
- let l = deep_list
- for i in range(102)
- let newlist = []
- call add(l, newlist)
- let l = newlist
- endfor
- call add(l, 102)
-
- call assert_fails('let m = deepcopy(deep_list)', 'E698:')
- call assert_fails('lockvar 110 deep_list', 'E743:')
- call assert_fails('unlockvar 110 deep_list', 'E743:')
- " Nvim implements :echo very differently
- " call assert_fails('let x = execute("echo deep_list")', 'E724:')
- call test_garbagecollect_now()
- unlet deep_list
-endfunc
-
-" Test for deep nesting of dicts (> 100)
-func Test_deep_nested_dict()
- let deep_dict = {}
- let d = deep_dict
- for i in range(102)
- let newdict = {}
- let d.k = newdict
- let d = newdict
- endfor
- let d.k = 'v'
-
- call assert_fails('let m = deepcopy(deep_dict)', 'E698:')
- call assert_fails('lockvar 110 deep_dict', 'E743:')
- call assert_fails('unlockvar 110 deep_dict', 'E743:')
- " Nvim implements :echo very differently
- " call assert_fails('let x = execute("echo deep_dict")', 'E724:')
- call test_garbagecollect_now()
- unlet deep_dict
-endfunc
-
-" List and dict indexing tests
-func Test_listdict_index()
- call assert_fails('echo function("min")[0]', 'E695:')
- call assert_fails('echo v:true[0]', 'E909:')
- let d = {'k' : 10}
- call assert_fails('echo d.', 'E15:')
- call assert_fails('echo d[1:2]', 'E719:')
- call assert_fails("let v = [4, 6][{-> 1}]", 'E729:')
- call assert_fails("let v = range(5)[2:[]]", 'E730:')
- call assert_fails("let v = range(5)[2:{-> 2}(]", ['E15:', 'E116:'])
- call assert_fails("let v = range(5)[2:3", 'E111:')
- call assert_fails("let l = insert([1,2,3], 4, 10)", 'E684:')
- call assert_fails("let l = insert([1,2,3], 4, -10)", 'E684:')
- call assert_fails("let l = insert([1,2,3], 4, [])", 'E745:')
- let l = [1, 2, 3]
- call assert_fails("let l[i] = 3", 'E121:')
- call assert_fails("let l[1.1] = 4", 'E806:')
- call assert_fails("let l[:i] = [4, 5]", 'E121:')
- call assert_fails("let l[:3.2] = [4, 5]", 'E806:')
-endfunc
-
-" Test for a null list
-func Test_null_list()
- let l = v:_null_list
- call assert_equal('', join(l))
- call assert_equal(0, len(l))
- call assert_equal(1, empty(l))
- call assert_fails('let s = join([1, 2], [])', 'E730:')
- call assert_equal([], split(v:_null_string))
- call assert_equal([], l[:2])
- call assert_true([] == l)
- call assert_equal('[]', string(l))
- " call assert_equal(0, sort(l))
- " call assert_equal(0, sort(l))
- " call assert_equal(0, uniq(l))
- let k = [] + l
- call assert_equal([], k)
- let k = l + []
- call assert_equal([], k)
- call assert_equal(0, len(copy(l)))
- call assert_equal(0, count(l, 5))
- call assert_equal([], deepcopy(l))
- call assert_equal(5, get(l, 2, 5))
- call assert_equal(-1, index(l, 2, 5))
- " call assert_equal(0, insert(l, 2, -1))
- call assert_equal(0, min(l))
- call assert_equal(0, max(l))
- " call assert_equal(0, remove(l, 0, 2))
- call assert_equal([], repeat(l, 2))
- " call assert_equal(0, reverse(l))
- " call assert_equal(0, sort(l))
- call assert_equal('[]', string(l))
- " call assert_equal(0, extend(l, l, 0))
- lockvar l
- call assert_equal(1, islocked('l'))
- unlockvar l
-endfunc
-
-" Test for a null dict
-func Test_null_dict()
- call assert_equal(v:_null_dict, v:_null_dict)
- let d = v:_null_dict
- call assert_equal({}, d)
- call assert_equal(0, len(d))
- call assert_equal(1, empty(d))
- call assert_equal(0, items(d))
- call assert_equal(0, keys(d))
- call assert_equal(0, values(d))
- call assert_false(has_key(d, 'k'))
- call assert_equal('{}', string(d))
- call assert_fails('let x = v:_null_dict[10]')
- call assert_equal({}, {})
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim
deleted file mode 100644
index 1cbdba5d76..0000000000
--- a/src/nvim/testdir/test_listlbr.vim
+++ /dev/null
@@ -1,309 +0,0 @@
-" Test for linebreak and list option (non-utf8)
-
-scriptencoding latin1
-
-source check.vim
-CheckOption linebreak
-CheckFeature conceal
-
-source view_util.vim
-source screendump.vim
-
-function s:screen_lines(lnum, width) abort
- return ScreenLines(a:lnum, a:width)
-endfunction
-
-func s:compare_lines(expect, actual)
- call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
-endfunc
-
-function s:test_windows(...)
- call NewWindow(10, 20)
- setl ts=8 sw=4 sts=4 linebreak sbr= wrap
- exe get(a:000, 0, '')
-endfunction
-
-function s:close_windows(...)
- call CloseWindow()
- exe get(a:000, 0, '')
-endfunction
-
-func Test_set_linebreak()
- call s:test_windows('setl ts=4 sbr=+')
- call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ")
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ " abcdef ",
-\ "+hijklmn ",
-\ "+pqrstuvwxyz_1060ABC",
-\ "+DEFGHIJKLMNOP ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_linebreak_with_list()
- throw 'skipped: Nvim does not support enc=latin1'
- set listchars=
- call s:test_windows('setl ts=4 sbr=+ list listchars=')
- call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ")
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ "^Iabcdef hijklmn^I ",
-\ "+pqrstuvwxyz_1060ABC",
-\ "+DEFGHIJKLMNOP ",
-\ "~ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
- set listchars&vim
-endfunc
-
-func Test_linebreak_with_nolist()
- call s:test_windows('setl ts=4 sbr=+ nolist')
- call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ")
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ " abcdef ",
-\ "+hijklmn ",
-\ "+pqrstuvwxyz_1060ABC",
-\ "+DEFGHIJKLMNOP ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_should_break()
- call s:test_windows('setl sbr=+ nolist')
- call setline(1, "1\t" . repeat('a', winwidth(0)-2))
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ "1 ",
-\ "+aaaaaaaaaaaaaaaaaa ",
-\ "~ ",
-\ "~ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_linebreak_with_conceal()
- call s:test_windows('setl cpo&vim sbr=+ list conceallevel=2 concealcursor=nv listchars=tab:ab')
- call setline(1, "_S_\t bla")
- syn match ConcealVar contained /_/ conceal
- syn match All /.*/ contains=ConcealVar
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ "Sabbbbbb bla ",
-\ "~ ",
-\ "~ ",
-\ "~ ",
-\ ]
- call s:compare_lines(expect, lines)
- 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
-
-" Test that cursor is drawn at correct position after an operator when
-" 'linebreak' is enabled.
-func Test_linebreak_reset_restore()
- CheckScreendump
-
- " f_wincol() calls validate_cursor()
- let lines =<< trim END
- set linebreak showcmd noshowmode formatexpr=wincol()-wincol()
- call setline(1, repeat('a', &columns - 10) .. ' bbbbbbbbbb c')
- END
- call writefile(lines, 'XlbrResetRestore', 'D')
- let buf = RunVimInTerminal('-S XlbrResetRestore', {'rows': 8})
-
- call term_sendkeys(buf, '$v$')
- call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
- call term_sendkeys(buf, 'zo')
- call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
-
- call term_sendkeys(buf, '$v$')
- call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
- call term_sendkeys(buf, 'gq')
- call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
-
- call term_sendkeys(buf, "$\<C-V>$")
- call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
- call term_sendkeys(buf, 'I')
- call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
-
- call term_sendkeys(buf, "\<Esc>$v$")
- call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
- call term_sendkeys(buf, 's')
- call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
- call VerifyScreenDump(buf, 'Test_linebreak_reset_restore_1', {})
-
- " clean up
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_virtual_block()
- call s:test_windows('setl sbr=+')
- call setline(1, [
-\ "REMOVE: this not",
-\ "REMOVE: aaaaaaaaaaaaa",
-\ ])
- exe "norm! 1/^REMOVE:"
- exe "norm! 0\<C-V>jf x"
- $put
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ "this not ",
-\ "aaaaaaaaaaaaa ",
-\ "REMOVE: ",
-\ "REMOVE: ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_virtual_block_and_vbA()
- call s:test_windows()
- call setline(1, "long line: " . repeat("foobar ", 40) . "TARGET at end")
- exe "norm! $3B\<C-v>eAx\<Esc>"
- let lines = s:screen_lines([1, 10], winwidth(0))
- let expect = [
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar foobar ",
-\ "foobar TARGETx at ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_virtual_char_and_block()
- call s:test_windows()
- call setline(1, "1111-1111-1111-11-1111-1111-1111")
- exe "norm! 0f-lv3lc2222\<Esc>bgj."
- let lines = s:screen_lines([1, 2], winwidth(0))
- let expect = [
-\ "1111-2222-1111-11- ",
-\ "1111-2222-1111 ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_undo_after_block_visual()
- call s:test_windows()
- call setline(1, ["aaa", "aaa", "a"])
- exe "norm! gg\<C-V>2j~e."
- let lines = s:screen_lines([1, 3], winwidth(0))
- let expect = [
-\ "AaA ",
-\ "AaA ",
-\ "A ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_norm_after_block_visual()
- call s:test_windows()
- call setline(1, ["abcd{ef", "ghijklm", "no}pgrs"])
- exe "norm! ggf{\<C-V>\<C-V>c%"
- let lines = s:screen_lines([1, 3], winwidth(0))
- let expect = [
-\ "abcdpgrs ",
-\ "~ ",
-\ "~ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_block_replace_after_wrapping()
- throw 'skipped: Nvim does not support enc=latin1'
- call s:test_windows()
- call setline(1, repeat("a", 150))
- exe "norm! 0yypk147|\<C-V>jr0"
- call assert_equal(repeat("a", 146) . "0aaa", getline(1))
- call assert_equal(repeat("a", 146) . "0aaa", getline(2))
- let lines = s:screen_lines([1, 10], winwidth(0))
- let expect = [
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aaaaaa0aaa ",
-\ "@ ",
-\ "@ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_list_with_listchars()
- call s:test_windows('setl list listchars=space:_,trail:-,tab:>-,eol:$')
- call setline(1, "a aaaaaaaaaaaaaaaaaaaaaa\ta ")
- let lines = s:screen_lines([1, 3], winwidth(0))
- let expect = [
-\ "a_ ",
-\ "aaaaaaaaaaaaaaaaaaaa",
-\ "aa>-----a-$ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_list_with_tab_and_skipping_first_chars()
- call s:test_windows('setl list listchars=tab:>- ts=70 nowrap')
- call setline(1, ["iiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa", "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa", "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa", "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa"])
- call cursor(4,64)
- norm! 2zl
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ "---------------aaaaa",
-\ "---------------aaaaa",
-\ "---------------aaaaa",
-\ "iiiiiiiii>-----aaaaa",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfu
diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim
deleted file mode 100644
index df1ed78119..0000000000
--- a/src/nvim/testdir/test_listlbr_utf8.vim
+++ /dev/null
@@ -1,282 +0,0 @@
-" Test for linebreak and list option in utf-8 mode
-
-set encoding=utf-8
-scriptencoding utf-8
-
-source check.vim
-CheckOption linebreak
-CheckFeature conceal
-CheckFeature signs
-
-source view_util.vim
-
-func s:screen_lines(lnum, width) abort
- return ScreenLines(a:lnum, a:width)
-endfunc
-
-func s:compare_lines(expect, actual)
- call assert_equal(a:expect, a:actual)
-endfunc
-
-func s:screen_attr(lnum, chars, ...) abort
- let line = getline(a:lnum)
- let attr = []
- let prefix = get(a:000, 0, 0)
- for i in range(a:chars[0], a:chars[1])
- let scol = strdisplaywidth(strcharpart(line, 0, i-1)) + 1
- let attr += [screenattr(a:lnum, scol + prefix)]
- endfor
- return attr
-endfunc
-
-func s:test_windows(...)
- call NewWindow(10, 20)
- setl ts=4 sw=4 sts=4 linebreak sbr=+ wrap
- exe get(a:000, 0, '')
-endfunc
-
-func s:close_windows(...)
- call CloseWindow()
- exe get(a:000, 0, '')
-endfunc
-
-func Test_linebreak_with_fancy_listchars()
- call s:test_windows("setl list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
- call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz\u00a01060ABCDEFGHIJKLMNOP ")
- redraw!
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ "▕———abcdef ",
-\ "+hijklmn▕——— ",
-\ "+pqrstuvwxyzâ£1060ABC",
-\ "+DEFGHIJKLMNOPˑ¶ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_nolinebreak_with_list()
- call s:test_windows("setl nolinebreak list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
- call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz\u00a01060ABCDEFGHIJKLMNOP ")
- redraw!
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ "▕———abcdef hijklmn▕—",
-\ "+pqrstuvwxyzâ£1060ABC",
-\ "+DEFGHIJKLMNOPˑ¶ ",
-\ "~ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-" this was causing a crash
-func Test_linebreak_with_list_and_tabs()
- set linebreak list listchars=tab:⇤\ ⇥ tabstop=100
- new
- call setline(1, "\t\t\ttext")
- redraw
- bwipe!
- set nolinebreak nolist listchars&vim tabstop=8
-endfunc
-
-func Test_linebreak_with_nolist()
- call s:test_windows('setl nolist')
- call setline(1, "\t*mask = nil;")
- redraw!
- let lines = s:screen_lines([1, 4], winwidth(0))
- let expect = [
-\ " *mask = nil; ",
-\ "~ ",
-\ "~ ",
-\ "~ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_list_and_concealing1()
- call s:test_windows('setl list listchars=tab:>- cole=1')
- call setline(1, [
-\ "#define ABCDE\t\t1",
-\ "#define ABCDEF\t\t1",
-\ "#define ABCDEFG\t\t1",
-\ "#define ABCDEFGH\t1",
-\ "#define MSG_MODE_FILE\t\t\t1",
-\ "#define MSG_MODE_CONSOLE\t\t2",
-\ "#define MSG_MODE_FILE_AND_CONSOLE\t3",
-\ "#define MSG_MODE_FILE_THEN_CONSOLE\t4",
-\ ])
- vert resize 40
- syn match Conceal conceal cchar=>'AB\|MSG_MODE'
- redraw!
- let lines = s:screen_lines([1, 7], winwidth(0))
- let expect = [
-\ "#define ABCDE>-->---1 ",
-\ "#define >CDEF>-->---1 ",
-\ "#define >CDEFG>->---1 ",
-\ "#define >CDEFGH>----1 ",
-\ "#define >_FILE>--------->--->---1 ",
-\ "#define >_CONSOLE>---------->---2 ",
-\ "#define >_FILE_AND_CONSOLE>---------3 ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_list_and_concealing2()
- call s:test_windows('setl nowrap ts=2 list listchars=tab:>- cole=2 concealcursor=n')
- call setline(1, "bbeeeeee\t\t;\tsome text")
- vert resize 40
- syn clear
- syn match meaning /;\s*\zs.*/
- syn match hasword /^\x\{8}/ contains=word
- syn match word /\<\x\{8}\>/ contains=beginword,endword contained
- syn match beginword /\<\x\x/ contained conceal
- syn match endword /\x\{6}\>/ contained
- hi meaning guibg=blue
- hi beginword guibg=green
- hi endword guibg=red
- redraw!
- let lines = s:screen_lines([1, 1], winwidth(0))
- let expect = [
-\ "eeeeee>--->-;>some text ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_screenattr_for_comment()
- call s:test_windows("setl ft=c ts=7 list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
- call setline(1, " /*\t\t and some more */")
- norm! gg0
- syntax on
- hi SpecialKey term=underline ctermfg=red guifg=red
- redraw!
- let line = getline(1)
- let attr = s:screen_attr(1, [1, 6])
- call assert_notequal(attr[0], attr[1])
- call assert_notequal(attr[1], attr[3])
- call assert_notequal(attr[3], attr[5])
- call s:close_windows()
-endfunc
-
-func Test_visual_block_and_selection_exclusive()
- call s:test_windows('setl selection=exclusive')
- call setline(1, "long line: " . repeat("foobar ", 40) . "TARGETÃ' at end")
- exe "norm! $3B\<C-v>eAx\<Esc>"
- let lines = s:screen_lines([1, 10], winwidth(0))
- let expect = [
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar foobar ",
-\ "+foobar TARGETÃx' ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_multibyte_sign_and_colorcolumn()
- call s:test_windows("setl nolinebreak cc=3 list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
- call setline(1, ["", "a b c", "a b c"])
- exe "sign define foo text=\uff0b"
- exe "sign place 1 name=foo line=2 buffer=" . bufnr('%')
- redraw!
- norm! ggj0
- let signwidth = strdisplaywidth("\uff0b")
- let attr1 = s:screen_attr(2, [1, 3], signwidth)
- let attr2 = s:screen_attr(3, [1, 3], signwidth)
- call assert_equal(attr1[0], attr2[0])
- call assert_equal(attr1[1], attr2[1])
- call assert_equal(attr1[2], attr2[2])
- let lines = s:screen_lines([1, 3], winwidth(0))
- let expect = [
-\ " ¶ ",
-\ "+a b c¶ ",
-\ " a b c¶ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_colorcolumn_priority()
- call s:test_windows('setl cc=4 cuc hls')
- call setline(1, ["xxyy", ""])
- norm! gg
- exe "normal! /xxyy\<CR>"
- norm! G
- redraw!
- let line_attr = s:screen_attr(1, [1, &cc])
- " Search wins over CursorColumn
- call assert_equal(line_attr[1], line_attr[0])
- " Search wins over Colorcolumn
- call assert_equal(line_attr[2], line_attr[3])
- call s:close_windows('setl hls&vim')
-endfunc
-
-func Test_illegal_byte_and_breakat()
- call s:test_windows("setl sbr= brk+=<")
- vert resize 18
- call setline(1, repeat("\x80", 6))
- redraw!
- let lines = s:screen_lines([1, 2], winwidth(0))
- let expect = [
-\ "<80><80><80><80><8",
-\ "0><80> ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('setl brk&vim')
-endfunc
-
-func Test_multibyte_wrap_and_breakat()
- call s:test_windows("setl sbr= brk+=>")
- call setline(1, repeat('a', 17) . repeat('ã‚', 2))
- redraw!
- let lines = s:screen_lines([1, 2], winwidth(0))
- let expect = [
-\ "aaaaaaaaaaaaaaaaaã‚>",
-\ "ã‚ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows('setl brk&vim')
-endfunc
-
-func Test_chinese_char_on_wrap_column()
- call s:test_windows("setl nolbr wrap sbr=")
- syntax off
- call setline(1, [
-\ 'aaaaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'aaaaaaaaaaaaaaaaa中'.
-\ 'hello'])
- call cursor(1,1)
- norm! $
- redraw!
- let expect=[
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中aaaaaaaaaaaaaaaaa>',
-\ '中hello ']
- let lines = s:screen_lines([1, 10], winwidth(0))
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfu
diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py
deleted file mode 100644
index f6dc0f8d1c..0000000000
--- a/src/nvim/testdir/test_makeencoding.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Test program for :make, :grep and :cgetfile.
-
-from __future__ import print_function, unicode_literals
-import locale
-import io
-import sys
-
-
-def set_output_encoding(enc=None):
- """Set the encoding of stdout and stderr
-
- arguments:
- enc -- Encoding name.
- If omitted, locale.getpreferredencoding() is used.
- """
- if enc is None:
- enc = locale.getpreferredencoding()
-
- def get_text_writer(fo, **kwargs):
- kw = dict(kwargs)
- kw.setdefault('errors', 'backslashreplace') # use \uXXXX style
- kw.setdefault('closefd', False)
-
- if sys.version_info[0] < 3:
- # Work around for Python 2.x
- # New line conversion isn't needed here. Done in somewhere else.
- writer = io.open(fo.fileno(), mode='w', newline='', **kw)
- write = writer.write # save the original write() function
- enc = locale.getpreferredencoding()
-
- def convwrite(s):
- if isinstance(s, bytes):
- write(s.decode(enc)) # convert to unistr
- else:
- write(s)
- try:
- writer.flush() # needed on Windows
- except IOError:
- pass
- writer.write = convwrite
- else:
- writer = io.open(fo.fileno(), mode='w', **kw)
- return writer
-
- sys.stdout = get_text_writer(sys.stdout, encoding=enc)
- sys.stderr = get_text_writer(sys.stderr, encoding=enc)
-
-
-def main():
- enc = 'utf-8'
- if len(sys.argv) > 1:
- enc = sys.argv[1]
- set_output_encoding(enc)
-
- message_tbl = {
- 'utf-8': 'ÀÈÌÒÙ ã“ã‚“ã«ã¡ã¯ 你好',
- 'latin1': 'ÀÈÌÒÙ',
- 'cp932': 'ã“ã‚“ã«ã¡ã¯',
- 'cp936': '你好',
- }
-
- print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc))
-
-
-if __name__ == "__main__":
- main()
diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim
deleted file mode 100644
index e297bdc228..0000000000
--- a/src/nvim/testdir/test_makeencoding.vim
+++ /dev/null
@@ -1,125 +0,0 @@
-" Tests for 'makeencoding'.
-
-source shared.vim
-
-let s:python = PythonProg()
-if s:python == ''
- throw 'Skipped: python program missing'
-endif
-
-let s:script = 'test_makeencoding.py'
-
-if has('iconv')
- let s:message_tbl = {
- \ 'utf-8': 'ÀÈÌÒÙ ã“ã‚“ã«ã¡ã¯ 你好',
- \ 'latin1': 'ÀÈÌÒÙ',
- \ 'cp932': 'ã“ã‚“ã«ã¡ã¯',
- \ 'cp936': '你好',
- \}
-else
- let s:message_tbl = {
- \ 'utf-8': 'ÀÈÌÒÙ ã“ã‚“ã«ã¡ã¯ 你好',
- \ 'latin1': 'ÀÈÌÒÙ',
- \}
-endif
-
-
-" Tests for :cgetfile and :lgetfile.
-func Test_getfile()
- set errorfile=Xerror.txt
- set errorformat=%f(%l)\ :\ %m
-
- " :cgetfile
- for enc in keys(s:message_tbl)
- let &makeencoding = enc
- exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile
- cgetfile
- copen
- call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
- \ getline('.'))
- cclose
- endfor
-
- " :lgetfile
- for enc in keys(s:message_tbl)
- let &makeencoding = enc
- exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile
- lgetfile
- lopen
- call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
- \ getline('.'))
- lclose
- endfor
-
- call delete(&errorfile)
-endfunc
-
-
-" Tests for :grep and :lgrep.
-func Test_grep()
- let &grepprg = s:python
- set grepformat=%f(%l)\ :\ %m
-
- " :grep
- for enc in keys(s:message_tbl)
- let &makeencoding = enc
- exec "silent grep! " . s:script . " " . enc
- copen
- call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
- \ getline('.'))
- cclose
- endfor
-
- " :lgrep
- for enc in keys(s:message_tbl)
- let &makeencoding = enc
- exec "silent lgrep! " . s:script . " " . enc
- lopen
- call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
- \ getline('.'))
- lclose
- endfor
-endfunc
-
-
-" Tests for :make and :lmake.
-func Test_make()
- let &makeprg = s:python
- set errorformat=%f(%l)\ :\ %m
-
- " :make
- for enc in keys(s:message_tbl)
- let &makeencoding = enc
- exec "silent make! " . s:script . " " . enc
- copen
- call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
- \ getline('.'))
- cclose
- endfor
-
- " :lmake
- for enc in keys(s:message_tbl)
- let &makeencoding = enc
- exec "silent lmake! " . s:script . " " . enc
- lopen
- call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
- \ getline('.'))
- lclose
- endfor
-endfunc
-
-" Test for an error file with a long line that needs an encoding conversion
-func Test_longline_conversion()
- new
- call setline(1, ['Xfile:10:' .. repeat("\xe0", 2000)])
- write ++enc=latin1 Xerr.out
- bw!
- set errorformat&
- set makeencoding=latin1
- cfile Xerr.out
- call assert_equal(repeat("\u00e0", 2000), getqflist()[0].text)
- call delete('Xerr.out')
- set makeencoding&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
deleted file mode 100644
index f903f5b934..0000000000
--- a/src/nvim/testdir/test_maparg.vim
+++ /dev/null
@@ -1,325 +0,0 @@
-" Tests for maparg(), mapcheck() and mapset().
-" Also test utf8 map with a 0x80 byte.
-" Also test mapcheck()
-
-func s:SID()
- return str2nr(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$'))
-endfunc
-
-func Test_maparg()
- new
- set cpo-=<
- set encoding=utf8
- " Test maparg() with a string result
- let sid = s:SID()
- let lnum = expand('<sflnum>')
- map foo<C-V> is<F4>foo
- vnoremap <script> <buffer> <expr> <silent> bar isbar
- call assert_equal("is<F4>foo", maparg('foo<C-V>'))
- call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>',
- \ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16",
- \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1,
- \ 'rhs': 'is<F4>foo', 'buffer': 0},
- \ maparg('foo<C-V>', '', 0, 1))
- call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar',
- \ 'lhsraw': 'bar', 'mode': 'v',
- \ 'nowait': 0, 'expr': 1, 'sid': sid, 'lnum': lnum + 2,
- \ 'rhs': 'isbar', 'buffer': 1},
- \ 'bar'->maparg('', 0, 1))
- let lnum = expand('<sflnum>')
- map <buffer> <nowait> foo bar
- call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo',
- \ 'lhsraw': 'foo', 'mode': ' ',
- \ 'nowait': 1, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'bar',
- \ 'buffer': 1},
- \ maparg('foo', '', 0, 1))
- let lnum = expand('<sflnum>')
- tmap baz foo
- call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'baz',
- \ 'lhsraw': 'baz', 'mode': 't',
- \ 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'foo',
- \ 'buffer': 0},
- \ maparg('baz', 't', 0, 1))
-
- map abc x<char-114>x
- call assert_equal("xrx", maparg('abc'))
- map abc y<S-char-114>y
- call assert_equal("yRy", maparg('abc'))
-
- " character with K_SPECIAL byte
- nmap abc …
- call assert_equal('…', maparg('abc'))
-
- " modified character with K_SPECIAL byte
- nmap abc <M-…>
- call assert_equal('<M-…>', maparg('abc'))
-
- " illegal bytes
- let str = ":\x7f:\x80:\x90:\xd0:"
- exe 'nmap abc ' .. str
- call assert_equal(str, maparg('abc'))
- unlet str
-
- omap { w
- let d = maparg('{', 'o', 0, 1)
- call assert_equal(['{', 'w', 'o'], [d.lhs, d.rhs, d.mode])
- ounmap {
-
- lmap { w
- let d = maparg('{', 'l', 0, 1)
- call assert_equal(['{', 'w', 'l'], [d.lhs, d.rhs, d.mode])
- lunmap {
-
- nmap { w
- let d = maparg('{', 'n', 0, 1)
- call assert_equal(['{', 'w', 'n'], [d.lhs, d.rhs, d.mode])
- nunmap {
-
- xmap { w
- let d = maparg('{', 'x', 0, 1)
- call assert_equal(['{', 'w', 'x'], [d.lhs, d.rhs, d.mode])
- xunmap {
-
- smap { w
- let d = maparg('{', 's', 0, 1)
- call assert_equal(['{', 'w', 's'], [d.lhs, d.rhs, d.mode])
- sunmap {
-
- map <C-I> foo
- unmap <Tab>
- " This used to cause a segfault
- call maparg('<C-I>', '', 0, 1)
- unmap <C-I>
-
- map abc <Nop>
- call assert_equal("<Nop>", maparg('abc'))
- unmap abc
-
- call feedkeys(":abbr esc \<C-V>\<C-V>\<C-V>\<C-V>\<C-V>\<Esc>\<CR>", "xt")
- let d = maparg('esc', 'i', 1, 1)
- call assert_equal(['esc', "\<C-V>\<C-V>\<Esc>", '!'], [d.lhs, d.rhs, d.mode])
- abclear
- unlet d
-endfunc
-
-func Test_mapcheck()
- call assert_equal('', mapcheck('a'))
- call assert_equal('', mapcheck('abc'))
- call assert_equal('', mapcheck('ax'))
- call assert_equal('', mapcheck('b'))
-
- map a something
- call assert_equal('something', mapcheck('a'))
- call assert_equal('something', mapcheck('a', 'n'))
- call assert_equal('', mapcheck('a', 'c'))
- call assert_equal('', mapcheck('a', 'i'))
- call assert_equal('something', 'abc'->mapcheck())
- call assert_equal('something', 'ax'->mapcheck())
- call assert_equal('', mapcheck('b'))
- unmap a
-
- map ab foobar
- call assert_equal('foobar', mapcheck('a'))
- call assert_equal('foobar', mapcheck('abc'))
- call assert_equal('', mapcheck('ax'))
- call assert_equal('', mapcheck('b'))
- unmap ab
-
- map abc barfoo
- call assert_equal('barfoo', mapcheck('a'))
- call assert_equal('barfoo', mapcheck('a', 'n', 0))
- call assert_equal('', mapcheck('a', 'n', 1))
- call assert_equal('barfoo', mapcheck('abc'))
- call assert_equal('', mapcheck('ax'))
- call assert_equal('', mapcheck('b'))
- unmap abc
-
- abbr ab abbrev
- call assert_equal('abbrev', mapcheck('a', 'i', 1))
- call assert_equal('', mapcheck('a', 'n', 1))
- call assert_equal('', mapcheck('a', 'i', 0))
- unabbr ab
-endfunc
-
-func Test_range_map()
- new
- " Outside of the range, minimum
- inoremap <Char-0x1040> a
- execute "normal a\u1040\<Esc>"
- " Inside of the range, minimum
- inoremap <Char-0x103f> b
- execute "normal a\u103f\<Esc>"
- " Inside of the range, maximum
- inoremap <Char-0xf03f> c
- execute "normal a\uf03f\<Esc>"
- " Outside of the range, maximum
- inoremap <Char-0xf040> d
- execute "normal a\uf040\<Esc>"
- call assert_equal("abcd", getline(1))
-endfunc
-
-func One_mapset_test(keys, rhs)
- exe 'nnoremap ' .. a:keys .. ' ' .. a:rhs
- let orig = maparg(a:keys, 'n', 0, 1)
- call assert_equal(a:keys, orig.lhs)
- call assert_equal(a:rhs, orig.rhs)
- call assert_equal('n', orig.mode)
-
- exe 'nunmap ' .. a:keys
- let d = maparg(a:keys, 'n', 0, 1)
- call assert_equal({}, d)
-
- call mapset('n', 0, orig)
- let d = maparg(a:keys, 'n', 0, 1)
- call assert_equal(a:keys, d.lhs)
- call assert_equal(a:rhs, d.rhs)
- call assert_equal('n', d.mode)
-
- exe 'nunmap ' .. a:keys
-endfunc
-
-func Test_mapset()
- call One_mapset_test('K', 'original<CR>')
- call One_mapset_test('<F3>', 'original<CR>')
- call One_mapset_test('<F3>', '<lt>Nop>')
-
- " Check <> key conversion
- new
- inoremap K one<Left>x
- call feedkeys("iK\<Esc>", 'xt')
- call assert_equal('onxe', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal('one<Left>x', orig.rhs)
- call assert_equal('i', orig.mode)
-
- iunmap K
- let d = maparg('K', 'i', 0, 1)
- call assert_equal({}, d)
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('onxe', getline(1))
-
- iunmap K
-
- " Test that <Nop> is restored properly
- inoremap K <Nop>
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal('<Nop>', orig.rhs)
- call assert_equal('i', orig.mode)
-
- inoremap K foo
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('foo', getline(1))
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('', getline(1))
-
- iunmap K
-
- " Test literal <CR> using a backslash
- let cpo_save = &cpo
- set cpo-=B
- inoremap K one\<CR>two
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal('one\<CR>two', orig.rhs)
- call assert_equal('i', orig.mode)
-
- iunmap K
- let d = maparg('K', 'i', 0, 1)
- call assert_equal({}, d)
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- iunmap K
-
- " Test literal <CR> using CTRL-V
- inoremap K one<CR>two
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal("one\x16<CR>two", orig.rhs)
- call assert_equal('i', orig.mode)
-
- iunmap K
- let d = maparg('K', 'i', 0, 1)
- call assert_equal({}, d)
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- iunmap K
- let &cpo = cpo_save
- bwipe!
-
- call assert_fails('call mapset([], v:false, {})', 'E730:')
- call assert_fails('call mapset("i", 0, "")', 'E715:')
- call assert_fails('call mapset("i", 0, {})', 'E460:')
-endfunc
-
-func Check_ctrlb_map(d, check_alt)
- call assert_equal('<C-B>', a:d.lhs)
- if a:check_alt
- call assert_equal("\x80\xfc\x04B", a:d.lhsraw)
- call assert_equal("\x02", a:d.lhsrawalt)
- else
- call assert_equal("\x02", a:d.lhsraw)
- endif
-endfunc
-
-func Test_map_local()
- nmap a global
- nmap <buffer>a local
-
- let prev_map_list = split(execute('nmap a'), "\n")
- call assert_match('n\s*a\s*@local', prev_map_list[0])
- call assert_match('n\s*a\s*global', prev_map_list[1])
-
- let mapping = maparg('a', 'n', 0, 1)
- call assert_equal(1, mapping.buffer)
- let mapping.rhs = 'new_local'
- call mapset('n', 0, mapping)
-
- " Check that the global mapping is left untouched.
- let map_list = split(execute('nmap a'), "\n")
- call assert_match('n\s*a\s*@new_local', map_list[0])
- call assert_match('n\s*a\s*global', map_list[1])
-
- nunmap a
-endfunc
-
-func Test_map_restore()
- " Test restoring map with alternate keycode
- nmap <C-B> back
- let d = maparg('<C-B>', 'n', 0, 1)
- call Check_ctrlb_map(d, 1)
- let dsimp = maparg("\x02", 'n', 0, 1)
- call Check_ctrlb_map(dsimp, 0)
- nunmap <C-B>
- call mapset('n', 0, d)
- let d = maparg('<C-B>', 'n', 0, 1)
- call Check_ctrlb_map(d, 1)
- let dsimp = maparg("\x02", 'n', 0, 1)
- call Check_ctrlb_map(dsimp, 0)
-
- nunmap <C-B>
-
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
deleted file mode 100644
index 5c5a65d4ca..0000000000
--- a/src/nvim/testdir/test_mapping.vim
+++ /dev/null
@@ -1,1193 +0,0 @@
-" Tests for mappings and abbreviations
-
-source shared.vim
-source check.vim
-source screendump.vim
-
-func Test_abbreviation()
- " abbreviation with 0x80 should work
- inoreab чкпр vim
- call feedkeys("Goчкпр \<Esc>", "xt")
- call assert_equal('vim ', getline('$'))
- iunab чкпр
- set nomodified
-endfunc
-
-func Test_abclear()
- abbrev foo foobar
- iabbrev fooi foobari
- cabbrev fooc foobarc
- call assert_equal("\n\nc fooc foobarc\ni fooi foobari\n! foo foobar", execute('abbrev'))
-
- iabclear
- call assert_equal("\n\nc fooc foobarc\nc foo foobar", execute('abbrev'))
- abbrev foo foobar
- iabbrev fooi foobari
-
- cabclear
- call assert_equal("\n\ni fooi foobari\ni foo foobar", execute('abbrev'))
- abbrev foo foobar
- cabbrev fooc foobarc
-
- abclear
- call assert_equal("\n\nNo abbreviation found", execute('abbrev'))
- call assert_fails('%abclear', 'E481:')
-endfunc
-
-func Test_abclear_buffer()
- abbrev foo foobar
- new X1
- abbrev <buffer> foo1 foobar1
- new X2
- abbrev <buffer> foo2 foobar2
-
- call assert_equal("\n\n! foo2 @foobar2\n! foo foobar", execute('abbrev'))
-
- abclear <buffer>
- call assert_equal("\n\n! foo foobar", execute('abbrev'))
-
- b X1
- call assert_equal("\n\n! foo1 @foobar1\n! foo foobar", execute('abbrev'))
- abclear <buffer>
- call assert_equal("\n\n! foo foobar", execute('abbrev'))
-
- abclear
- call assert_equal("\n\nNo abbreviation found", execute('abbrev'))
-
- %bwipe
-endfunc
-
-func Test_map_ctrl_c_insert()
- " mapping of ctrl-c in Insert mode
- set cpo-=< cpo-=k
- inoremap <c-c> <ctrl-c>
- cnoremap <c-c> dummy
- cunmap <c-c>
- call feedkeys("GoTEST2: CTRL-C |\<*C-C>A|\<Esc>", "xt")
- call assert_equal('TEST2: CTRL-C |<ctrl-c>A|', getline('$'))
- unmap! <c-c>
- set nomodified
-endfunc
-
-func Test_map_ctrl_c_visual()
- " mapping of ctrl-c in Visual mode
- vnoremap <c-c> :<C-u>$put ='vmap works'
- call feedkeys("GV\<*C-C>\<CR>", "xt")
- call assert_equal('vmap works', getline('$'))
- vunmap <c-c>
- set nomodified
-endfunc
-
-func Test_map_langmap()
- if !has('langmap')
- return
- endif
-
- " check langmap applies in normal mode
- set langmap=+- nolangremap
- new
- call setline(1, ['a', 'b', 'c'])
- 2
- call assert_equal('b', getline('.'))
- call feedkeys("+", "xt")
- call assert_equal('a', getline('.'))
-
- " check no remapping
- map x +
- 2
- call feedkeys("x", "xt")
- call assert_equal('c', getline('.'))
-
- " check with remapping
- set langremap
- 2
- call feedkeys("x", "xt")
- call assert_equal('a', getline('.'))
-
- unmap x
- bwipe!
-
- " 'langnoremap' follows 'langremap' and vise versa
- set langremap
- set langnoremap
- call assert_equal(0, &langremap)
- set langremap
- call assert_equal(0, &langnoremap)
- set nolangremap
- call assert_equal(1, &langnoremap)
-
- " check default values
- set langnoremap&
- call assert_equal(1, &langnoremap)
- call assert_equal(0, &langremap)
- set langremap&
- call assert_equal(1, &langnoremap)
- call assert_equal(0, &langremap)
-
- " langmap should not apply in insert mode, 'langremap' doesn't matter
- set langmap=+{ nolangremap
- call feedkeys("Go+\<Esc>", "xt")
- call assert_equal('+', getline('$'))
- set langmap=+{ langremap
- call feedkeys("Go+\<Esc>", "xt")
- call assert_equal('+', getline('$'))
-
- " langmap used for register name in insert mode.
- call setreg('a', 'aaaa')
- call setreg('b', 'bbbb')
- call setreg('c', 'cccc')
- set langmap=ab langremap
- call feedkeys("Go\<C-R>a\<Esc>", "xt")
- call assert_equal('bbbb', getline('$'))
- call feedkeys("Go\<C-R>\<C-R>a\<Esc>", "xt")
- call assert_equal('bbbb', getline('$'))
- " mapping does not apply
- imap c a
- call feedkeys("Go\<C-R>c\<Esc>", "xt")
- call assert_equal('cccc', getline('$'))
- imap a c
- call feedkeys("Go\<C-R>a\<Esc>", "xt")
- call assert_equal('bbbb', getline('$'))
-
- " langmap should not apply in Command-line mode
- set langmap=+{ nolangremap
- call feedkeys(":call append(line('$'), '+')\<CR>", "xt")
- call assert_equal('+', getline('$'))
-
- iunmap a
- iunmap c
- set nomodified
-endfunc
-
-func Test_map_feedkeys()
- " issue #212 (feedkeys insert mapping at current position)
- nnoremap . :call feedkeys(".", "in")<cr>
- call setline('$', ['a b c d', 'a b c d'])
- $-1
- call feedkeys("0qqdw.ifoo\<Esc>qj0@q\<Esc>", "xt")
- call assert_equal(['fooc d', 'fooc d'], getline(line('$') - 1, line('$')))
- nunmap .
- set nomodified
-endfunc
-
-func Test_map_cursor()
- " <c-g>U<cursor> works only within a single line
- imapclear
- imap ( ()<c-g>U<left>
- call feedkeys("G2o\<Esc>ki\<CR>Test1: text with a (here some more text\<Esc>k.", "xt")
- call assert_equal('Test1: text with a (here some more text)', getline(line('$') - 2))
- call assert_equal('Test1: text with a (here some more text)', getline(line('$') - 1))
-
- " test undo
- call feedkeys("G2o\<Esc>ki\<CR>Test2: text wit a (here some more text [und undo]\<C-G>u\<Esc>k.u", "xt")
- call assert_equal('', getline(line('$') - 2))
- call assert_equal('Test2: text wit a (here some more text [und undo])', getline(line('$') - 1))
- set nomodified
- imapclear
-endfunc
-
-func Test_map_cursor_ctrl_gU()
- " <c-g>U<cursor> works only within a single line
- nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left>
- call setline(1, ['foo', 'foobar', '', 'foo'])
- call cursor(1,2)
- call feedkeys("c<*PREFIX\<esc>.", 'xt')
- call assert_equal(['PREFIXfoo', 'foobar', '', 'PREFIXfoo'], getline(1,'$'))
- " break undo manually
- set ul=1000
- exe ":norm! uu"
- call assert_equal(['foo', 'foobar', '', 'foo'], getline(1,'$'))
-
- " Test that it does not work if the cursor moves to the previous line
- " 2 times <S-Left> move to the previous line
- nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left><C-G>U<S-Left>
- call setline(1, ['', ' foo', 'foobar', '', 'foo'])
- call cursor(2,3)
- call feedkeys("c<*PREFIX\<esc>.", 'xt')
- call assert_equal(['PREFIXPREFIX', ' foo', 'foobar', '', 'foo'], getline(1,'$'))
- nmapclear
-endfunc
-
-
-" This isn't actually testing a mapping, but similar use of CTRL-G U as above.
-func Test_break_undo()
- set whichwrap=<,>,[,]
- call feedkeys("G4o2k", "xt")
- exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>."
- call assert_equal('new line here', getline(line('$') - 3))
- call assert_equal('Test3: text with a (parenthesis here', getline(line('$') - 2))
- call assert_equal('new line here', getline(line('$') - 1))
- set nomodified
-endfunc
-
-func Test_map_meta_quotes()
- imap <M-"> foo
- call feedkeys("Go-\<*M-\">-\<Esc>", "xt")
- call assert_equal("-foo-", getline('$'))
- set nomodified
- iunmap <M-">
-endfunc
-
-func Test_map_meta_multibyte()
- imap <M-á> foo
- call assert_match('i <M-á>\s*foo', execute('imap'))
- iunmap <M-á>
-endfunc
-
-func Test_abbr_after_line_join()
- new
- abbr foo bar
- set backspace=indent,eol,start
- exe "normal o\<BS>foo "
- call assert_equal("bar ", getline(1))
- bwipe!
- unabbr foo
- set backspace&
-endfunc
-
-func Test_map_timeout()
- if !has('timers')
- return
- endif
- nnoremap aaaa :let got_aaaa = 1<CR>
- nnoremap bb :let got_bb = 1<CR>
- nmap b aaa
- new
- func ExitInsert(timer)
- let g:line = getline(1)
- call feedkeys("\<Esc>", "t")
- endfunc
- set timeout timeoutlen=200
- let timer = timer_start(300, 'ExitInsert')
- " After the 'b' Vim waits for another character to see if it matches 'bb'.
- " When it times out it is expanded to "aaa", but there is no wait for
- " "aaaa". Can't check that reliably though.
- call feedkeys("b", "xt!")
- call assert_equal("aa", g:line)
- call assert_false(exists('got_aaa'))
- call assert_false(exists('got_bb'))
-
- bwipe!
- nunmap aaaa
- nunmap bb
- nunmap b
- set timeoutlen&
- delfunc ExitInsert
- call timer_stop(timer)
-endfunc
-
-func Test_map_timeout_with_timer_interrupt()
- if !has('job') || !has('timers')
- return
- endif
-
- " Confirm the timer invoked in exit_cb of the job doesn't disturb mapped key
- " sequence.
- new
- let g:val = 0
- nnoremap \12 :let g:val = 1<CR>
- nnoremap \123 :let g:val = 2<CR>
- set timeout timeoutlen=200
-
- func ExitCb(job, status)
- let g:timer = timer_start(1, {-> feedkeys("3\<Esc>", 't')})
- endfunc
-
- call job_start([&shell, &shellcmdflag, 'echo'], {'exit_cb': 'ExitCb'})
- call feedkeys('\12', 'xt!')
- call assert_equal(2, g:val)
-
- bwipe!
- nunmap \12
- nunmap \123
- set timeoutlen&
- call WaitFor({-> exists('g:timer')})
- call timer_stop(g:timer)
- unlet g:timer
- unlet g:val
- delfunc ExitCb
-endfunc
-
-func Test_cabbr_visual_mode()
- cabbr s su
- call feedkeys(":s \<c-B>\"\<CR>", 'itx')
- call assert_equal('"su ', getreg(':'))
- call feedkeys(":'<,'>s \<c-B>\"\<CR>", 'itx')
- let expected = '"'. "'<,'>su "
- call assert_equal(expected, getreg(':'))
- call feedkeys(": '<,'>s \<c-B>\"\<CR>", 'itx')
- let expected = '" '. "'<,'>su "
- call assert_equal(expected, getreg(':'))
- call feedkeys(":'a,'bs \<c-B>\"\<CR>", 'itx')
- let expected = '"'. "'a,'bsu "
- call assert_equal(expected, getreg(':'))
- cunabbr s
-endfunc
-
-func Test_abbreviation_CR()
- new
- func Eatchar(pat)
- let c = nr2char(getchar(0))
- return (c =~ a:pat) ? '' : c
- endfunc
- iabbrev <buffer><silent> ~~7 <c-r>=repeat('~', 7)<CR><c-r>=Eatchar('\s')<cr>
- call feedkeys("GA~~7 \<esc>", 'xt')
- call assert_equal('~~~~~~~', getline('$'))
- %d
- call feedkeys("GA~~7\<cr>\<esc>", 'xt')
- call assert_equal(['~~~~~~~', ''], getline(1,'$'))
- delfunc Eatchar
- bw!
-endfunc
-
-func Test_motionforce_omap()
- func GetCommand()
- let g:m=mode(1)
- let [g:lnum1, g:col1] = searchpos('-', 'Wb')
- if g:lnum1 == 0
- return "\<Esc>"
- endif
- let [g:lnum2, g:col2] = searchpos('-', 'W')
- if g:lnum2 == 0
- return "\<Esc>"
- endif
- return ":call Select()\<CR>"
- endfunc
- func Select()
- call cursor([g:lnum1, g:col1])
- exe "normal! 1 ". (strlen(g:m) == 2 ? 'v' : g:m[2])
- call cursor([g:lnum2, g:col2])
- execute "normal! \<BS>"
- endfunc
- new
- onoremap <buffer><expr> i- GetCommand()
- " 1) default omap mapping
- %d_
- call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
- call cursor(2, 1)
- norm di-
- call assert_equal('no', g:m)
- call assert_equal(['aaa -- eee'], getline(1, '$'))
- " 2) forced characterwise operation
- %d_
- call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
- call cursor(2, 1)
- norm dvi-
- call assert_equal('nov', g:m)
- call assert_equal(['aaa -- eee'], getline(1, '$'))
- " 3) forced linewise operation
- %d_
- call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
- call cursor(2, 1)
- norm dVi-
- call assert_equal('noV', g:m)
- call assert_equal([''], getline(1, '$'))
- " 4) forced blockwise operation
- %d_
- call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
- call cursor(2, 1)
- exe "norm d\<C-V>i-"
- call assert_equal("no\<C-V>", g:m)
- call assert_equal(['aaabbb', 'x', 'dddeee'], getline(1, '$'))
- bwipe!
- delfunc Select
- delfunc GetCommand
-endfunc
-
-func Test_error_in_map_expr()
- " Unlike CheckRunVimInTerminal this does work in a win32 console
- CheckFeature terminal
- if has('win32') && has('gui_running')
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
-
- let lines =<< trim [CODE]
- func Func()
- " fail to create list
- let x = [
- endfunc
- nmap <expr> ! Func()
- set updatetime=50
- [CODE]
- call writefile(lines, 'Xtest.vim')
-
- let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
- let job = term_getjob(buf)
- call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
-
- " GC must not run during map-expr processing, which can make Vim crash.
- call term_sendkeys(buf, '!')
- call term_wait(buf, 100)
- call term_sendkeys(buf, "\<CR>")
- call term_wait(buf, 100)
- call assert_equal('run', job_status(job))
-
- call term_sendkeys(buf, ":qall!\<CR>")
- call WaitFor({-> job_status(job) ==# 'dead'})
- if has('unix')
- call assert_equal('', job_info(job).termsig)
- endif
-
- call delete('Xtest.vim')
- exe buf .. 'bwipe!'
-endfunc
-
-func Test_list_mappings()
- " Remove default mappings
- imapclear
-
- " reset 'isident' to check it isn't used
- set isident=
- inoremap <C-m> CtrlM
- inoremap <A-S> AltS
- inoremap <S-/> ShiftSlash
- set isident&
- call assert_equal([
- \ 'i <S-/> * ShiftSlash',
- \ 'i <M-S> * AltS',
- \ 'i <C-M> * CtrlM',
- \], execute('imap')->trim()->split("\n"))
- iunmap <C-M>
- iunmap <A-S>
- call assert_equal(['i <S-/> * ShiftSlash'], execute('imap')->trim()->split("\n"))
- iunmap <S-/>
- call assert_equal(['No mapping found'], execute('imap')->trim()->split("\n"))
-
- " List global, buffer local and script local mappings
- nmap ,f /^\k\+ (<CR>
- nmap <buffer> ,f /^\k\+ (<CR>
- nmap <script> ,fs /^\k\+ (<CR>
- call assert_equal(['n ,f @/^\k\+ (<CR>',
- \ 'n ,fs & /^\k\+ (<CR>',
- \ 'n ,f /^\k\+ (<CR>'],
- \ execute('nmap ,f')->trim()->split("\n"))
-
- " List <Nop> mapping
- nmap ,n <Nop>
- call assert_equal(['n ,n <Nop>'],
- \ execute('nmap ,n')->trim()->split("\n"))
-
- " verbose map
- call assert_match("\tLast set from .*/test_mapping.vim line \\d\\+$",
- \ execute('verbose map ,n')->trim()->split("\n")[1])
-
- " character with K_SPECIAL byte in rhs
- nmap foo …
- call assert_equal(['n foo …'],
- \ execute('nmap foo')->trim()->split("\n"))
-
- " modified character with K_SPECIAL byte in rhs
- nmap foo <M-…>
- call assert_equal(['n foo <M-…>'],
- \ execute('nmap foo')->trim()->split("\n"))
-
- " character with K_SPECIAL byte in lhs
- nmap … foo
- call assert_equal(['n … foo'],
- \ execute('nmap …')->trim()->split("\n"))
-
- " modified character with K_SPECIAL byte in lhs
- nmap <M-…> foo
- call assert_equal(['n <M-…> foo'],
- \ execute('nmap <M-…>')->trim()->split("\n"))
-
- " illegal bytes
- let str = ":\x7f:\x80:\x90:\xd0:"
- exe 'nmap foo ' .. str
- call assert_equal(['n foo ' .. strtrans(str)],
- \ execute('nmap foo')->trim()->split("\n"))
- unlet str
-
- " map to CTRL-V
- exe "nmap ,k \<C-V>"
- call assert_equal(['n ,k <Nop>'],
- \ execute('nmap ,k')->trim()->split("\n"))
-
- " map with space at the beginning
- exe "nmap \<C-V> w <Nop>"
- call assert_equal(['n <Space>w <Nop>'],
- \ execute("nmap \<C-V> w")->trim()->split("\n"))
-
- nmapclear
-endfunc
-
-func Test_expr_map_gets_cursor()
- new
- call setline(1, ['one', 'some w!rd'])
- func StoreColumn()
- let g:exprLine = line('.')
- let g:exprCol = col('.')
- return 'x'
- endfunc
- nnoremap <expr> x StoreColumn()
- 2
- nmap ! f!<Ignore>x
- call feedkeys("!", 'xt')
- call assert_equal('some wrd', getline(2))
- call assert_equal(2, g:exprLine)
- call assert_equal(7, g:exprCol)
-
- bwipe!
- unlet g:exprLine
- unlet g:exprCol
- delfunc StoreColumn
- nunmap x
- nunmap !
-endfunc
-
-func Test_expr_map_restore_cursor()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['one', 'two', 'three'])
- 2
- set ls=2
- hi! link StatusLine ErrorMsg
- noremap <expr> <C-B> Func()
- func Func()
- let g:on = !get(g:, 'on', 0)
- redraws
- return ''
- endfunc
- func Status()
- return get(g:, 'on', 0) ? '[on]' : ''
- endfunc
- set stl=%{Status()}
- END
- call writefile(lines, 'XtestExprMap')
- let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10})
- call term_sendkeys(buf, "\<C-B>")
- call VerifyScreenDump(buf, 'Test_map_expr_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XtestExprMap')
-endfunc
-
-func Test_map_listing()
- CheckScreendump
-
- let lines =<< trim END
- nmap a b
- END
- call writefile(lines, 'XtestMapList')
- let buf = RunVimInTerminal('-S XtestMapList', #{rows: 6})
- call term_sendkeys(buf, ": nmap a\<CR>")
- call VerifyScreenDump(buf, 'Test_map_list_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XtestMapList')
-endfunc
-
-func Test_expr_map_error()
- CheckScreendump
-
- let lines =<< trim END
- func Func()
- throw 'test'
- return ''
- endfunc
-
- nnoremap <expr> <F2> Func()
- cnoremap <expr> <F2> Func()
-
- call test_override('ui_delay', 10)
- END
- call writefile(lines, 'XtestExprMap')
- let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10})
- call term_sendkeys(buf, "\<F2>")
- call TermWait(buf)
- call term_sendkeys(buf, "\<CR>")
- call VerifyScreenDump(buf, 'Test_map_expr_2', {})
-
- call term_sendkeys(buf, ":abc\<F2>")
- call VerifyScreenDump(buf, 'Test_map_expr_3', {})
- call term_sendkeys(buf, "\<Esc>0")
- call VerifyScreenDump(buf, 'Test_map_expr_4', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XtestExprMap')
-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:')
- call assert_fails('abbr $xyz abc', 'E474:')
-
- " space character in an abbreviation
- call assert_fails('abbr ab<space> ABC', 'E474:')
-
- " invalid <expr> map
- map <expr> ,f abc
- call assert_fails('normal ,f', 'E121:')
- unmap <expr> ,f
-
- " Recursive use of :normal in a map
- set maxmapdepth=100
- map gq :normal gq<CR>
- call assert_fails('normal gq', 'E192:')
- unmap gq
- set maxmapdepth&
-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\+ ('))
- map ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ ('))
- unmap ,f
-
- " Insert mode mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 'i'))
- imap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ (', 'i'))
- iunmap ,f
-
- " Normal mode mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 'n'))
- nmap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ ('))
- call assert_equal(1, hasmapto('/^\k\+ (', 'n'))
- nunmap ,f
-
- " Visual and Select mode mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 'v'))
- call assert_equal(0, hasmapto('/^\k\+ (', 'x'))
- call assert_equal(0, hasmapto('/^\k\+ (', 's'))
- vmap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ (', 'v'))
- call assert_equal(1, hasmapto('/^\k\+ (', 'x'))
- call assert_equal(1, hasmapto('/^\k\+ (', 's'))
- vunmap ,f
-
- " Visual mode mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 'x'))
- xmap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ (', 'v'))
- call assert_equal(1, hasmapto('/^\k\+ (', 'x'))
- call assert_equal(0, hasmapto('/^\k\+ (', 's'))
- xunmap ,f
-
- " Select mode mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 's'))
- smap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ (', 'v'))
- call assert_equal(0, hasmapto('/^\k\+ (', 'x'))
- call assert_equal(1, hasmapto('/^\k\+ (', 's'))
- sunmap ,f
-
- " Operator-pending mode mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 'o'))
- omap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ (', 'o'))
- ounmap ,f
-
- " Language mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 'l'))
- lmap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ (', 'l'))
- lunmap ,f
-
- " Cmdline mode mapping
- call assert_equal(0, hasmapto('/^\k\+ (', 'c'))
- cmap ,f /^\k\+ (<CR>
- call assert_equal(1, hasmapto('/^\k\+ (', 'c'))
- cunmap ,f
-
- 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(":unmap <buf\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"unmap <buffer>', @:)
-
- call feedkeys(":unabbr <buf\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"unabbr <buffer>', @:)
-
- call feedkeys(":abbr! \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"abbr! \x01", @:)
-
- " Multiple matches for a map
- nmap ,f /H<CR>
- omap ,f /H<CR>
- call feedkeys(":map ,\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"map ,f', @:)
- mapclear
-endfunc
-
-func GetAbbrText()
- unabbr hola
- return 'hello'
-endfunc
-
-" Test for <expr> in abbreviation
-func Test_expr_abbr()
- new
- iabbr <expr> teh "the"
- call feedkeys("iteh ", "tx")
- call assert_equal('the ', getline(1))
- iabclear
- call setline(1, '')
-
- " invalid <expr> abbreviation
- abbr <expr> hte GetAbbr()
- call assert_fails('normal ihte ', 'E117:')
- call assert_equal('', getline(1))
- unabbr <expr> hte
-
- " evaluating the expression deletes the abbreviation
- abbr <expr> hola GetAbbrText()
- call assert_equal('GetAbbrText()', maparg('hola', 'i', '1'))
- call feedkeys("ahola \<Esc>", 'xt')
- call assert_equal('hello ', getline('.'))
- call assert_equal('', maparg('hola', 'i', '1'))
-
- bwipe!
-endfunc
-
-" Test for storing mappings in different modes in a vimrc file
-func Test_mkvimrc_mapmodes()
- map a1 /a1
- nmap a2 /a2
- vmap a3 /a3
- smap a4 /a4
- xmap a5 /a5
- omap a6 /a6
- map! a7 /a7
- imap a8 /a8
- lmap a9 /a9
- cmap a10 /a10
- tmap a11 /a11
- " Normal + Visual map
- map a12 /a12
- sunmap a12
- ounmap a12
- " Normal + Selectmode map
- map a13 /a13
- xunmap a13
- ounmap a13
- " Normal + OpPending map
- map a14 /a14
- vunmap a14
- " Visual + Selectmode map
- map a15 /a15
- nunmap a15
- ounmap a15
- " Visual + OpPending map
- map a16 /a16
- nunmap a16
- sunmap a16
- " Selectmode + OpPending map
- map a17 /a17
- nunmap a17
- xunmap a17
- " Normal + Visual + Selectmode map
- map a18 /a18
- ounmap a18
- " Normal + Visual + OpPending map
- map a19 /a19
- sunmap a19
- " Normal + Selectmode + OpPending map
- map a20 /a20
- xunmap a20
- " Visual + Selectmode + OpPending map
- map a21 /a21
- nunmap a21
- " Mapping to Nop
- map a22 <Nop>
- " Script local mapping
- map <script> a23 /a23
-
- " Newline in {lhs} and {rhs} of a map
- exe "map a24\<C-V>\<C-J> ia24\<C-V>\<C-J><Esc>"
-
- " Abbreviation
- abbr a25 A25
- cabbr a26 A26
- iabbr a27 A27
-
- mkvimrc! Xvimrc
- let l = readfile('Xvimrc')
- call assert_equal(['map a1 /a1'], filter(copy(l), 'v:val =~ " a1 "'))
- call assert_equal(['nmap a2 /a2'], filter(copy(l), 'v:val =~ " a2 "'))
- call assert_equal(['vmap a3 /a3'], filter(copy(l), 'v:val =~ " a3 "'))
- call assert_equal(['smap a4 /a4'], filter(copy(l), 'v:val =~ " a4 "'))
- call assert_equal(['xmap a5 /a5'], filter(copy(l), 'v:val =~ " a5 "'))
- call assert_equal(['omap a6 /a6'], filter(copy(l), 'v:val =~ " a6 "'))
- call assert_equal(['map! a7 /a7'], filter(copy(l), 'v:val =~ " a7 "'))
- call assert_equal(['imap a8 /a8'], filter(copy(l), 'v:val =~ " a8 "'))
- call assert_equal(['lmap a9 /a9'], filter(copy(l), 'v:val =~ " a9 "'))
- call assert_equal(['cmap a10 /a10'], filter(copy(l), 'v:val =~ " a10 "'))
- call assert_equal(['tmap a11 /a11'], filter(copy(l), 'v:val =~ " a11 "'))
- call assert_equal(['nmap a12 /a12', 'xmap a12 /a12'],
- \ filter(copy(l), 'v:val =~ " a12 "'))
- call assert_equal(['nmap a13 /a13', 'smap a13 /a13'],
- \ filter(copy(l), 'v:val =~ " a13 "'))
- call assert_equal(['nmap a14 /a14', 'omap a14 /a14'],
- \ filter(copy(l), 'v:val =~ " a14 "'))
- call assert_equal(['vmap a15 /a15'], filter(copy(l), 'v:val =~ " a15 "'))
- call assert_equal(['xmap a16 /a16', 'omap a16 /a16'],
- \ filter(copy(l), 'v:val =~ " a16 "'))
- call assert_equal(['smap a17 /a17', 'omap a17 /a17'],
- \ filter(copy(l), 'v:val =~ " a17 "'))
- call assert_equal(['nmap a18 /a18', 'vmap a18 /a18'],
- \ filter(copy(l), 'v:val =~ " a18 "'))
- call assert_equal(['nmap a19 /a19', 'xmap a19 /a19', 'omap a19 /a19'],
- \ filter(copy(l), 'v:val =~ " a19 "'))
- call assert_equal(['nmap a20 /a20', 'smap a20 /a20', 'omap a20 /a20'],
- \ filter(copy(l), 'v:val =~ " a20 "'))
- call assert_equal(['vmap a21 /a21', 'omap a21 /a21'],
- \ filter(copy(l), 'v:val =~ " a21 "'))
- call assert_equal(['map a22 <Nop>'], filter(copy(l), 'v:val =~ " a22 "'))
- call assert_equal([], filter(copy(l), 'v:val =~ " a23 "'))
- call assert_equal(["map a24<NL> ia24<NL>\x16\e"],
- \ filter(copy(l), 'v:val =~ " a24"'))
-
- call assert_equal(['abbr a25 A25'], filter(copy(l), 'v:val =~ " a25 "'))
- call assert_equal(['cabbr a26 A26'], filter(copy(l), 'v:val =~ " a26 "'))
- call assert_equal(['iabbr a27 A27'], filter(copy(l), 'v:val =~ " a27 "'))
- call delete('Xvimrc')
-
- mapclear
- nmapclear
- vmapclear
- xmapclear
- smapclear
- omapclear
- imapclear
- lmapclear
- cmapclear
- tmapclear
-endfunc
-
-" Test for recursive mapping ('maxmapdepth')
-func Test_map_recursive()
- map x y
- map y x
- call assert_fails('normal x', 'E223:')
- unmap x
- unmap y
-endfunc
-
-" Test for removing an abbreviation using {rhs} and with space after {lhs}
-func Test_abbr_remove()
- abbr foo bar
- let d = maparg('foo', 'i', 1, 1)
- call assert_equal(['foo', 'bar', '!'], [d.lhs, d.rhs, d.mode])
- unabbr bar
- call assert_equal({}, maparg('foo', 'i', 1, 1))
-
- abbr foo bar
- unabbr foo<space><tab>
- call assert_equal({}, maparg('foo', 'i', 1, 1))
-endfunc
-
-func Test_map_cmdkey_redo()
- func SelectDash()
- call search('^---\n\zs', 'bcW')
- norm! V
- call search('\n\ze---$', 'W')
- endfunc
-
- let text =<< trim END
- ---
- aaa
- ---
- bbb
- bbb
- ---
- ccc
- ccc
- ccc
- ---
- END
- new Xcmdtext
- call setline(1, text)
-
- onoremap <silent> i- <Cmd>call SelectDash()<CR>
- call feedkeys('2Gdi-', 'xt')
- call assert_equal(['---', '---'], getline(1, 2))
- call feedkeys('j.', 'xt')
- call assert_equal(['---', '---', '---'], getline(1, 3))
- call feedkeys('j.', 'xt')
- call assert_equal(['---', '---', '---', '---'], getline(1, 4))
-
- bwipe!
- call delete('Xcmdtext')
- delfunc SelectDash
- ounmap i-
-endfunc
-
-" Test for using <script> with a map to remap characters in rhs
-func Test_script_local_remap()
- new
- inoremap <buffer> <SID>xyz mno
- inoremap <buffer> <script> abc st<SID>xyzre
- normal iabc
- call assert_equal('stmnore', getline(1))
- bwipe!
-endfunc
-
-func Test_abbreviate_multi_byte()
- new
- iabbrev foo bar
- call feedkeys("ifoo…\<Esc>", 'xt')
- call assert_equal("bar…", getline(1))
- iunabbrev foo
- bwipe!
-endfunc
-
-" Test for abbreviations with 'latin1' encoding
-func Test_abbreviate_latin1_encoding()
- " set encoding=latin1
- call assert_fails('abbr ab#$c ABC', 'E474:')
- new
- iabbr <buffer> #i #include
- iabbr <buffer> ## #enddef
- exe "normal i#i\<C-]>"
- call assert_equal('#include', getline(1))
- exe "normal 0Di##\<C-]>"
- call assert_equal('#enddef', getline(1))
- %bw!
- set encoding=utf-8
-endfunc
-+
-" Test for <Plug> always being mapped, even when used with "noremap".
-func Test_plug_remap()
- let g:foo = 0
- nnoremap <Plug>(Increase_x) <Cmd>let g:foo += 1<CR>
- nmap <F2> <Plug>(Increase_x)
- nnoremap <F3> <Plug>(Increase_x)
- call feedkeys("\<F2>", 'xt')
- call assert_equal(1, g:foo)
- call feedkeys("\<F3>", 'xt')
- call assert_equal(2, g:foo)
- nnoremap x <Nop>
- nmap <F4> x<Plug>(Increase_x)x
- nnoremap <F5> x<Plug>(Increase_x)x
- call setline(1, 'Some text')
- normal! gg$
- call feedkeys("\<F4>", 'xt')
- call assert_equal(3, g:foo)
- call assert_equal('Some text', getline(1))
- call feedkeys("\<F5>", 'xt')
- call assert_equal(4, g:foo)
- call assert_equal('Some te', getline(1))
- nunmap <Plug>(Increase_x)
- nunmap <F2>
- nunmap <F3>
- nunmap <F4>
- nunmap <F5>
- unlet g:foo
- %bw!
-endfunc
-
-func Test_mouse_drag_mapped_start_select()
- set mouse=a
- set selectmode=key,mouse
- func ClickExpr()
- call Ntest_setmouse(1, 1)
- return "\<LeftMouse>"
- endfunc
- func DragExpr()
- call Ntest_setmouse(1, 2)
- return "\<LeftDrag>"
- endfunc
- nnoremap <expr> <F2> ClickExpr()
- nmap <expr> <F3> DragExpr()
-
- nnoremap <LeftDrag> <LeftDrag><Cmd><CR>
- exe "normal \<F2>\<F3>"
- call assert_equal('s', mode())
- exe "normal! \<C-\>\<C-N>"
-
- nunmap <LeftDrag>
- nunmap <F2>
- nunmap <F3>
- delfunc ClickExpr
- delfunc DragExpr
- set selectmode&
- set mouse&
-endfunc
-
-func Test_mouse_drag_statusline()
- set laststatus=2
- set mouse=a
- func ClickExpr()
- call Ntest_setmouse(&lines - 1, 1)
- return "\<LeftMouse>"
- endfunc
- func DragExpr()
- call Ntest_setmouse(&lines - 2, 1)
- return "\<LeftDrag>"
- endfunc
- nnoremap <expr> <F2> ClickExpr()
- nnoremap <expr> <F3> DragExpr()
-
- " this was causing a crash in win_drag_status_line()
- call feedkeys("\<F2>:tabnew\<CR>\<F3>", 'tx')
-
- nunmap <F2>
- nunmap <F3>
- delfunc ClickExpr
- delfunc DragExpr
- set laststatus& mouse&
-endfunc
-
-" Test for mapping <LeftDrag> in Insert mode
-func Test_mouse_drag_insert_map()
- set mouse=a
- func ClickExpr()
- call Ntest_setmouse(1, 1)
- return "\<LeftMouse>"
- endfunc
- func DragExpr()
- call Ntest_setmouse(1, 2)
- return "\<LeftDrag>"
- endfunc
- inoremap <expr> <F2> ClickExpr()
- imap <expr> <F3> DragExpr()
-
- inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR>
- exe "normal i\<F2>\<F3>"
- call assert_equal(1, g:dragged)
- call assert_equal('v', mode())
- exe "normal! \<C-\>\<C-N>"
- unlet g:dragged
-
- inoremap <LeftDrag> <LeftDrag><C-\><C-N>
- exe "normal i\<F2>\<F3>"
- call assert_equal('n', mode())
-
- iunmap <LeftDrag>
- iunmap <F2>
- iunmap <F3>
- delfunc ClickExpr
- delfunc DragExpr
- set mouse&
-endfunc
-
-func Test_unmap_simplifiable()
- map <C-I> foo
- map <Tab> bar
- call assert_equal('foo', maparg('<C-I>'))
- call assert_equal('bar', maparg('<Tab>'))
- unmap <C-I>
- call assert_equal('', maparg('<C-I>'))
- call assert_equal('bar', maparg('<Tab>'))
- unmap <Tab>
-
- map <C-I> foo
- unmap <Tab>
- " This should not error
- unmap <C-I>
-endfunc
-
-func Test_expr_map_escape_special()
- nnoremap … <Cmd>let g:got_ellipsis += 1<CR>
- func Func()
- return '…'
- endfunc
- nmap <expr> <F2> Func()
- let g:got_ellipsis = 0
- call feedkeys("\<F2>", 'xt')
- call assert_equal(1, g:got_ellipsis)
- delfunc Func
- nunmap <F2>
- unlet g:got_ellipsis
- nunmap …
-endfunc
-
-" Testing for mapping after an <Nop> mapping is triggered on timeout.
-" Test for what patch 8.1.0052 fixes.
-func Test_map_after_timed_out_nop()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set timeout timeoutlen=400
- inoremap ab TEST
- inoremap a <Nop>
- END
- call writefile(lines, 'Xtest_map_after_timed_out_nop')
- let buf = RunVimInTerminal('-S Xtest_map_after_timed_out_nop', #{rows: 6})
-
- " Enter Insert mode
- call term_sendkeys(buf, 'i')
- " Wait for the "a" mapping to timeout
- call term_sendkeys(buf, 'a')
- call term_wait(buf, 500)
- " Send "a" and wait for a period shorter than 'timeoutlen'
- call term_sendkeys(buf, 'a')
- call term_wait(buf, 100)
- " Send "b", should trigger the "ab" mapping
- call term_sendkeys(buf, 'b')
- call WaitForAssert({-> assert_equal("TEST", term_getline(buf, 1))})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_map_after_timed_out_nop')
-endfunc
-
-func Test_using_past_typeahead()
- nnoremap :00 0
- exe "norm :set \x80\xfb0=0\<CR>"
- exe "sil norm :0\x0f\<C-U>\<CR>"
-
- exe "norm :set \x80\xfb0=\<CR>"
- nunmap :00
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim
deleted file mode 100644
index a7ccca498c..0000000000
--- a/src/nvim/testdir/test_marks.vim
+++ /dev/null
@@ -1,320 +0,0 @@
-
-" Test that a deleted mark is restored after delete-undo-redo-undo.
-func Test_Restore_DelMark()
- enew!
- call append(0, [" textline A", " textline B", " textline C"])
- normal! 2gg
- set nocp viminfo+=nviminfo
- exe "normal! i\<C-G>u\<Esc>"
- exe "normal! maddu\<C-R>u"
- let pos = getpos("'a")
- call assert_equal(2, pos[1])
- call assert_equal(1, pos[2])
- enew!
-endfunc
-
-" Test that CTRL-A and CTRL-X updates last changed mark '[, '].
-func Test_Incr_Marks()
- enew!
- call append(0, ["123 123 123", "123 123 123", "123 123 123"])
- normal! gg
- execute "normal! \<C-A>`[v`]rAjwvjw\<C-X>`[v`]rX"
- call assert_equal("AAA 123 123", getline(1))
- call assert_equal("123 XXXXXXX", getline(2))
- call assert_equal("XXX 123 123", getline(3))
- enew!
-endfunc
-
-func Test_previous_jump_mark()
- new
- call setline(1, ['']->repeat(6))
- normal Ggg
- call assert_equal(6, getpos("''")[1])
- normal jjjjj
- call assert_equal(6, getpos("''")[1])
- bwipe!
-endfunc
-
-func Test_setpos()
- new Xone
- let onebuf = bufnr('%')
- let onewin = win_getid()
- call setline(1, ['aaa', 'bbb', 'ccc'])
- new Xtwo
- let twobuf = bufnr('%')
- let twowin = win_getid()
- call setline(1, ['aaa', 'bbb', 'ccc'])
-
- " for the cursor the buffer number is ignored
- call setpos(".", [0, 2, 1, 0])
- call assert_equal([0, 2, 1, 0], getpos("."))
- call setpos(".", [onebuf, 3, 3, 0])
- call assert_equal([0, 3, 3, 0], getpos("."))
-
- call setpos("''", [0, 1, 3, 0])
- call assert_equal([0, 1, 3, 0], getpos("''"))
- call setpos("''", [onebuf, 2, 2, 0])
- call assert_equal([0, 2, 2, 0], getpos("''"))
-
- " buffer-local marks
- for mark in ["'a", "'\"", "'[", "']", "'<", "'>"]
- call win_gotoid(twowin)
- call setpos(mark, [0, 2, 1, 0])
- call assert_equal([0, 2, 1, 0], getpos(mark), "for mark " . mark)
- call setpos(mark, [onebuf, 1, 3, 0])
- call win_gotoid(onewin)
- call assert_equal([0, 1, 3, 0], getpos(mark), "for mark " . mark)
- endfor
-
- " global marks
- call win_gotoid(twowin)
- call setpos("'N", [0, 2, 1, 0])
- call assert_equal([twobuf, 2, 1, 0], getpos("'N"))
- 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"))
-
- " Test with invalid buffer number, line number and column number
- call cursor(2, 2)
- call setpos('.', [-1, 1, 1, 0])
- call assert_equal([2, 2], [line('.'), col('.')])
- call setpos('.', [0, -1, 1, 0])
- call assert_equal([2, 2], [line('.'), col('.')])
- call setpos('.', [0, 1, -1, 0])
- call assert_equal([2, 2], [line('.'), col('.')])
-
- call assert_fails("call setpos('ab', [0, 1, 1, 0])", 'E474:')
-
- bwipe!
- call win_gotoid(twowin)
- bwipe!
-endfunc
-
-func Test_marks_cmd()
- new Xone
- call setline(1, ['aaa', 'bbb'])
- norm! maG$mB
- w!
- 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',
- \ " ' 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(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
- let a = split(execute('marks aBcD'), "\n")
- call assert_equal(2, len(a))
- call assert_equal('mark line col file/text', a[0])
- call assert_equal(' D 2 0 Xtwo', a[1])
-
- b Xtwo
- delmarks cD
- call assert_fails('marks aBcD', 'E283:')
-
- call delete('Xone')
- call delete('Xtwo')
- %bwipe
-endfunc
-
-func Test_marks_cmd_multibyte()
- new Xone
- call setline(1, [repeat('á', &columns)])
- norm! ma
-
- let a = split(execute('marks a'), "\n")
- call assert_equal(2, len(a))
- let expected = ' a 1 0 ' . repeat('á', &columns - 16)
- call assert_equal(expected, a[1])
-
- bwipe!
-endfunc
-
-func Test_delmarks()
- new
- norm mx
- norm `x
- delmarks x
- call assert_fails('norm `x', 'E20:')
-
- " Deleting an already deleted mark should not fail.
- delmarks x
-
- " getpos() should return all zeros after deleting a filemark.
- norm mA
- delmarks A
- call assert_equal([0, 0, 0, 0], getpos("'A"))
-
- " Test deleting a range of marks.
- norm ma
- norm mb
- norm mc
- norm mz
- delmarks b-z
- norm `a
- call assert_fails('norm `b', 'E20:')
- call assert_fails('norm `c', 'E20:')
- call assert_fails('norm `z', 'E20:')
- call assert_fails('delmarks z-b', 'E475:')
-
- call assert_fails('delmarks', 'E471:')
- call assert_fails('delmarks /', 'E475:')
-
- " Test delmarks!
- norm mx
- norm `x
- delmarks!
- call assert_fails('norm `x', 'E20:')
- call assert_fails('delmarks! x', 'E474:')
-
- bwipe!
-endfunc
-
-func Test_mark_error()
- call assert_fails('mark', 'E471:')
- call assert_fails('mark xx', 'E488:')
- call assert_fails('mark _', 'E191:')
- call assert_beeps('normal! m~')
-
- call setpos("'k", [0, 100, 1, 0])
- call assert_fails("normal 'k", 'E19:')
-endfunc
-
-" Test for :lockmarks when pasting content
-func Test_lockmarks_with_put()
- new
- call append(0, repeat(['sky is blue'], 4))
- normal gg
- 1,2yank r
- put r
- normal G
- lockmarks put r
- call assert_equal(2, line("'["))
- call assert_equal(3, line("']"))
-
- bwipe!
-endfunc
-
-" Test for :k command to set a mark
-func Test_marks_k_cmd()
- new
- call setline(1, ['foo', 'bar', 'baz', 'qux'])
- 1,3kr
- call assert_equal([0, 3, 1, 0], getpos("'r"))
- close!
-endfunc
-
-" Test for file marks (A-Z)
-func Test_file_mark()
- new Xone
- call setline(1, ['aaa', 'bbb'])
- norm! G$mB
- w!
- new Xtwo
- call setline(1, ['ccc', 'ddd'])
- norm! GmD
- w!
-
- enew
- normal! `B
- call assert_equal('Xone', bufname())
- call assert_equal([2, 3], [line('.'), col('.')])
- normal! 'D
- call assert_equal('Xtwo', bufname())
- call assert_equal([2, 1], [line('.'), col('.')])
-
- call delete('Xone')
- call delete('Xtwo')
-endfunc
-
-" Test for the getmarklist() function
-func Test_getmarklist()
- new
- " global marks
- delmarks A-Z 0-9 \" ^.[]
- call assert_equal([], getmarklist())
- call setline(1, ['one', 'two', 'three'])
- mark A
- call cursor(3, 5)
- normal mN
- call assert_equal([{'file' : '', 'mark' : "'A", 'pos' : [bufnr(), 1, 1, 0]},
- \ {'file' : '', 'mark' : "'N", 'pos' : [bufnr(), 3, 5, 0]}],
- \ getmarklist())
- " buffer local marks
- delmarks!
- call assert_equal([{'mark' : "''", 'pos' : [bufnr(), 1, 1, 0]},
- \ {'mark' : "'\"", 'pos' : [bufnr(), 1, 1, 0]}], getmarklist(bufnr()))
- call cursor(2, 2)
- normal mr
- call assert_equal({'mark' : "'r", 'pos' : [bufnr(), 2, 2, 0]},
- \ bufnr()->getmarklist()[0])
- call assert_equal([], {}->getmarklist())
- close!
-endfunc
-
-" This was using freed memory
-func Test_jump_mark_autocmd()
- next 00
- edit 0
- sargument
- au BufEnter 0 all
- sil norm 
-
- au! BufEnter
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim
deleted file mode 100644
index 600b6132a9..0000000000
--- a/src/nvim/testdir/test_match.vim
+++ /dev/null
@@ -1,442 +0,0 @@
-" Test for :match, :2match, :3match, clearmatches(), getmatches(), matchadd(),
-" matchaddpos(), matcharg(), matchdelete(), and setmatches().
-
-source screendump.vim
-source check.vim
-
-function Test_match()
- highlight MyGroup1 term=bold ctermbg=red guibg=red
- highlight MyGroup2 term=italic ctermbg=green guibg=green
- highlight MyGroup3 term=underline ctermbg=blue guibg=blue
-
- " --- Check that "matcharg()" returns the correct group and pattern if a match
- " --- is defined.
- match MyGroup1 /TODO/
- 2match MyGroup2 /FIXME/
- 3match MyGroup3 /XXX/
- call assert_equal(['MyGroup1', 'TODO'], matcharg(1))
- call assert_equal(['MyGroup2', 'FIXME'], 2->matcharg())
- call assert_equal(['MyGroup3', 'XXX'], matcharg(3))
-
- " --- Check that "matcharg()" returns an empty list if the argument is not 1,
- " --- 2 or 3 (only 0 and 4 are tested).
- call assert_equal([], matcharg(0))
- call assert_equal([], matcharg(4))
-
- " --- Check that "matcharg()" returns ['', ''] if a match is not defined.
- match
- 2match
- 3match
- call assert_equal(['', ''], matcharg(1))
- call assert_equal(['', ''], matcharg(2))
- call assert_equal(['', ''], matcharg(3))
-
- " --- Check that "matchadd()" and "getmatches()" agree on added matches and
- " --- that default values apply.
- let m1 = matchadd("MyGroup1", "TODO")
- let m2 = matchadd("MyGroup2", "FIXME", 42)
- let m3 = matchadd("MyGroup3", "XXX", 60, 17)
- let ans = [{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1000},
- \ {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 42, 'id': 1001},
- \ {'group': 'MyGroup3', 'pattern': 'XXX', 'priority': 60, 'id': 17}]
- call assert_equal(ans, getmatches())
-
- " --- Check that "matchdelete()" deletes the matches defined in the previous
- " --- test correctly.
- call matchdelete(m1)
- eval m2->matchdelete()
- call matchdelete(m3)
- call assert_equal([], getmatches())
-
- " --- Check that "matchdelete()" returns 0 if successful and otherwise -1.
- let m = matchadd("MyGroup1", "TODO")
- call assert_equal(0, matchdelete(m))
- call assert_fails('call matchdelete(42)', 'E803:')
-
- " --- Check that "clearmatches()" clears all matches defined by ":match" and
- " --- "matchadd()".
- let m1 = matchadd("MyGroup1", "TODO")
- let m2 = "MyGroup2"->matchadd("FIXME", 42)
- let m3 = matchadd("MyGroup3", "XXX", 60, 17)
- match MyGroup1 /COFFEE/
- 2match MyGroup2 /HUMPPA/
- 3match MyGroup3 /VIM/
- call clearmatches()
- call assert_equal([], getmatches())
-
- " --- Check that "setmatches()" restores a list of matches saved by
- " --- "getmatches()" without changes. (Matches with equal priority must also
- " --- remain in the same order.)
- let m1 = matchadd("MyGroup1", "TODO")
- let m2 = matchadd("MyGroup2", "FIXME", 42)
- let m3 = matchadd("MyGroup3", "XXX", 60, 17)
- match MyGroup1 /COFFEE/
- 2match MyGroup2 /HUMPPA/
- 3match MyGroup3 /VIM/
- let ml = getmatches()
- call clearmatches()
- call setmatches(ml)
- call assert_equal(ml, getmatches())
- call clearmatches()
-
- " --- Check that "setmatches()" will not add two matches with the same ID. The
- " --- expected behaviour (for now) is to add the first match but not the
- " --- second and to return 0 (even though it is a matter of debate whether
- " --- this can be considered successful behaviour).
- let data = [{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1},
- \ {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 10, 'id': 1}]
- call assert_fails('call setmatches(data)', 'E801:')
- call assert_equal([data[0]], getmatches())
- call clearmatches()
-
- " --- Check that "setmatches()" returns 0 if successful and otherwise -1.
- " --- (A range of valid and invalid input values are tried out to generate the
- " --- return values.)
- call assert_equal(0, setmatches([]))
- call assert_equal(0, setmatches([{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1}]))
- call clearmatches()
- call assert_fails('call setmatches(0)', 'E714:')
- call assert_fails('call setmatches([0])', 'E474:')
- call assert_fails("call setmatches([{'wrong key': 'wrong value'}])", 'E474:')
-
- call setline(1, 'abcdefghijklmnopq')
- call matchaddpos("MyGroup1", [[1, 5], [1, 8, 3]], 10, 3)
- 1
- redraw!
- let v1 = screenattr(1, 1)
- let v5 = screenattr(1, 5)
- let v6 = screenattr(1, 6)
- let v8 = screenattr(1, 8)
- let v10 = screenattr(1, 10)
- let v11 = screenattr(1, 11)
- call assert_notequal(v1, v5)
- call assert_equal(v6, v1)
- call assert_equal(v8, v5)
- call assert_equal(v10, v5)
- call assert_equal(v11, v1)
- call assert_equal([{'group': 'MyGroup1', 'id': 3, 'priority': 10, 'pos1': [1, 5, 1], 'pos2': [1, 8, 3]}], getmatches())
- call clearmatches()
-
- call setline(1, 'abcdΣabcdef')
- eval "MyGroup1"->matchaddpos([[1, 4, 2], [1, 9, 2]], 10, 42)
- 1
- redraw!
- let v1 = screenattr(1, 1)
- let v4 = screenattr(1, 4)
- let v5 = screenattr(1, 5)
- let v6 = screenattr(1, 6)
- let v7 = screenattr(1, 7)
- let v8 = screenattr(1, 8)
- let v9 = screenattr(1, 9)
- let v10 = screenattr(1, 10)
- call assert_equal([{'group': 'MyGroup1', 'id': 42, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1, 9, 2]}], getmatches())
- call assert_notequal(v1, v4)
- call assert_equal(v5, v4)
- call assert_equal(v6, v1)
- call assert_equal(v7, v1)
- call assert_equal(v8, v4)
- call assert_equal(v9, v4)
- call assert_equal(v10, v1)
-
- " Check, that setmatches() can correctly restore the matches from matchaddpos()
- call matchadd('MyGroup1', '\%2lmatchadd')
- let m=getmatches()
- call clearmatches()
- call setmatches(m)
- call assert_equal([{'group': 'MyGroup1', 'id': 42, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1,9, 2]}, {'group': 'MyGroup1', 'pattern': '\%2lmatchadd', 'priority': 10, 'id': 1106}], getmatches())
-
- highlight MyGroup1 NONE
- highlight MyGroup2 NONE
- 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 clearmatches()
- " Nvim: not an error anymore:
- " call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:')
- call matchadd('GroupDoesNotExist', 'X')
- call assert_equal([{'group': 'GroupDoesNotExist', 'pattern': 'X', 'priority': 10, 'id': 1206}], getmatches())
- call assert_fails("call matchadd('Search', '\\(')", 'E54:')
- 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:')
- call assert_fails("call matchadd('Error', 'XXX', [], 0)", 'E745:')
-endfunc
-
-func Test_matchaddpos()
- syntax on
- set hlsearch
-
- call setline(1, ['12345', 'NP'])
- call matchaddpos('Error', [[1,2], [1,6], [2,2]])
- redraw!
- call assert_notequal(screenattr(2,2), 0)
- call assert_equal(screenattr(2,2), screenattr(1,2))
- call assert_notequal(screenattr(2,2), screenattr(1,6))
- 1
- call matchadd('Search', 'N\|\n')
- redraw!
- call assert_notequal(screenattr(2,1), 0)
- call assert_equal(screenattr(2,1), screenattr(1,6))
- exec "norm! i0\<Esc>"
- redraw!
- call assert_equal(screenattr(2,2), screenattr(1,6))
-
- " Check overlapping pos
- call clearmatches()
- call setline(1, ['1234567890', 'NH'])
- call matchaddpos('Error', [[1,1,5], [1,3,5], [2,2]])
- redraw!
- call assert_notequal(screenattr(2,2), 0)
- call assert_equal(screenattr(2,2), screenattr(1,5))
- call assert_equal(screenattr(2,2), screenattr(1,7))
- call assert_notequal(screenattr(2,2), screenattr(1,8))
-
- call clearmatches()
- call matchaddpos('Error', [[1], [2,2]])
- redraw!
- call assert_equal(screenattr(2,2), screenattr(1,1))
- call assert_equal(screenattr(2,2), screenattr(1,10))
- call assert_notequal(screenattr(2,2), screenattr(1,11))
-
- " Check overlapping pos
- call clearmatches()
- call setline(1, ['1234567890', 'NH'])
- call matchaddpos('Error', [[1,1,5], [1,3,5], [2,2]])
- redraw!
- call assert_notequal(screenattr(2,2), 0)
- call assert_equal(screenattr(2,2), screenattr(1,5))
- call assert_equal(screenattr(2,2), screenattr(1,7))
- call assert_notequal(screenattr(2,2), screenattr(1,8))
-
- nohl
- call clearmatches()
- syntax off
- set hlsearch&
-endfunc
-
-" Add 12 match positions (previously the limit was 8 positions).
-func Test_matchaddpos_dump()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['1234567890123']->repeat(14))
- call matchaddpos('Search', range(1, 12)->map({i, v -> [v, v]}))
- END
- call writefile(lines, 'Xmatchaddpos', 'D')
- let buf = RunVimInTerminal('-S Xmatchaddpos', #{rows: 14})
- call VerifyScreenDump(buf, 'Test_matchaddpos_1', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_matchaddpos_otherwin()
- syntax on
- new
- call setline(1, ['12345', 'NP'])
- let winid = win_getid()
-
- wincmd w
- call matchadd('Search', '4', 10, -1, {'window': winid})
- call matchaddpos('Error', [[1,2], [2,2]], 10, -1, {'window': winid})
- redraw!
- call assert_notequal(screenattr(1,2), 0)
- call assert_notequal(screenattr(1,4), 0)
- call assert_notequal(screenattr(2,2), 0)
- 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': 1000},
- \ {'group': 'Error', 'id': 1001, 'priority': 10, 'pos1': [1, 2, 1], 'pos2': [2, 2, 1]},
- \]
- call assert_equal(expect, savematches)
-
- eval winid->clearmatches()
- call assert_equal([], getmatches(winid))
-
- call setmatches(savematches, winid)
- call assert_equal(expect, savematches)
-
- wincmd w
- bwipe!
- call clearmatches()
- syntax off
-endfunc
-
-func Test_matchaddpos_using_negative_priority()
- set hlsearch
-
- call clearmatches()
-
- call setline(1, 'x')
- let @/='x'
- redraw!
- let search_attr = screenattr(1,1)
-
- let @/=''
- call matchaddpos('Error', [1], 10)
- redraw!
- let error_attr = screenattr(1,1)
-
- call setline(2, '-1 match priority')
- call matchaddpos('Error', [2], -1)
- redraw!
- let negative_match_priority_attr = screenattr(2,1)
-
- call assert_notequal(negative_match_priority_attr, search_attr, "Match with negative priority is incorrectly highlighted with Search highlight.")
- call assert_equal(negative_match_priority_attr, error_attr)
-
- nohl
- 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', [{}])", 'E290:')
- call assert_fails("call matchaddpos('Error', [{}])", 'E5031:')
- call assert_equal(-1, matchaddpos('Error', v:_null_list))
- call assert_fails("call matchaddpos('Error', [1], [], 1)", 'E745:')
-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()
- CheckScreendump
-
- 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()
- CheckRunVimInTerminal
- 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()
- CheckRunVimInTerminal
- 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
-
-func Test_match_in_linebreak()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set breakindent linebreak breakat+=]
- call printf('%s]%s', repeat('x', 50), repeat('x', 70))->setline(1)
- call matchaddpos('ErrorMsg', [[1, 51]])
- END
- call writefile(lines, 'XscriptMatchLinebreak')
- let buf = RunVimInTerminal('-S XscriptMatchLinebreak', #{rows: 10})
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_match_linebreak', {})
-
- call StopVimInTerminal(buf)
- call delete('XscriptMatchLinebreak')
-endfunc
-
-func Test_match_with_incsearch()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set incsearch
- call setline(1, range(20))
- call matchaddpos('ErrorMsg', [3])
- END
- call writefile(lines, 'XmatchWithIncsearch')
- let buf = RunVimInTerminal('-S XmatchWithIncsearch', #{rows: 6})
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_match_with_incsearch_1', {})
-
- call term_sendkeys(buf, ":s/0")
- call VerifyScreenDump(buf, 'Test_match_with_incsearch_2', {})
-
- call term_sendkeys(buf, "\<CR>")
- call StopVimInTerminal(buf)
- call delete('XmatchWithIncsearch')
-endfunc
-
-" Test for deleting matches outside of the screen redraw top/bottom lines
-" This should cause a redraw of those lines.
-func Test_matchdelete_redraw()
- new
- call setline(1, range(1, 500))
- call cursor(250, 1)
- let m1 = matchaddpos('Search', [[250]])
- let m2 = matchaddpos('Search', [[10], [450]])
- redraw!
- let m3 = matchaddpos('Search', [[240], [260]])
- call matchdelete(m2)
- let m = getmatches()
- call assert_equal(2, len(m))
- call assert_equal([250], m[0].pos1)
- redraw!
- call matchdelete(m1)
- call assert_equal(1, len(getmatches()))
- bw!
-endfunc
-
-func Test_match_tab_with_linebreak()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set linebreak
- call setline(1, "\tix")
- call matchadd('ErrorMsg', '\t')
- END
- call writefile(lines, 'XscriptMatchTabLinebreak')
- let buf = RunVimInTerminal('-S XscriptMatchTabLinebreak', #{rows: 10})
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_match_tab_linebreak', {})
-
- call StopVimInTerminal(buf)
- call delete('XscriptMatchTabLinebreak')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim
deleted file mode 100644
index 1b5fc8313f..0000000000
--- a/src/nvim/testdir/test_matchadd_conceal.vim
+++ /dev/null
@@ -1,422 +0,0 @@
-" Test for matchadd() and conceal feature
-
-source check.vim
-CheckFeature conceal
-
-source shared.vim
-source term_util.vim
-source view_util.vim
-
-func Test_simple_matchadd()
- new
-
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '# This is a Test'
-
- call cursor(1, 1)
- call matchadd('Conceal', '\%2l ')
- redraw!
- let lnum = 2
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- quit!
-endfunc
-
-func Test_simple_matchadd_and_conceal()
- new
- setlocal concealcursor=n conceallevel=1
-
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '#XThisXisXaXTest'
-
- call cursor(1, 1)
- call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'X'})
- redraw!
- let lnum = 2
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- quit!
-endfunc
-
-func Test_matchadd_and_conceallevel_3()
- new
-
- setlocal conceallevel=3
- " set filetype and :syntax on to change screenattr()
- setlocal filetype=conf
- syntax on
-
- 1put='# This is a Test $'
- " 1234567890123
- let expect = '#ThisisaTest$'
-
- call cursor(1, 1)
- call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'X'})
- redraw!
- let lnum = 2
- call assert_equal(expect, Screenline(lnum))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 13))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 14))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- " more matchadd()
- " 12345678901234
- let expect = '#Thisisa Test$'
-
- call matchadd('ErrorMsg', '\%2l Test', 20, -1, {'conceal': 'X'})
- redraw!
- call assert_equal(expect, Screenline(lnum))
- call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 7))
- call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 10), screenattr(lnum, 13))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 14))
- call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 16))
- call assert_notequal(screenattr(lnum, 10), screenattr(lnum, 16))
-
- syntax off
- quit!
-endfunc
-
-func Test_default_conceal_char()
- new
- setlocal concealcursor=n conceallevel=1
-
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '# This is a Test'
-
- call cursor(1, 1)
- call matchadd('Conceal', '\%2l ', 10, -1, {})
- redraw!
- let lnum = 2
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- " 1234567890123456
- let expect = '#+This+is+a+Test'
- let listchars_save = &listchars
- set listchars=conceal:+
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- let &listchars = listchars_save
- quit!
-endfunc
-
-func Test_syn_and_match_conceal()
- new
- setlocal concealcursor=n conceallevel=1
-
- 1put='# This is a Test '
-
- let lnum = 2
- call cursor(1, 1)
-
- " 123456789012345678
- let expect = '#ZThisZisZaZTestZZ'
- call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'Z'})
- syntax match MyConceal /\%2l / conceal containedin=ALL
- hi MyConceal ctermbg=4 ctermfg=2
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- syntax clear MyConceal
- syntax match MyConceal /\%2l / conceal containedin=ALL cchar=*
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- " 123456789012345678
- let expect = '#*This*is*a*Test**'
- call clearmatches()
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- " 123456789012345678
- let expect = '#*ThisXis*a*Test**'
- call matchadd('Conceal', '\%2l\%7c ', 10, -1, {'conceal': 'X'})
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- " 123456789012345678
- let expect = '#*ThisXis*a*Test**'
- call matchadd('ErrorMsg', '\%2l Test', 20, -1)
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13))
- call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18))
- call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19))
-
- " 123456789012345678
- let expect = '# ThisXis a Test'
- syntax clear MyConceal
- syntax match MyConceal /\%2l / conceal containedin=ALL
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 12))
- call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13))
- call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18))
- call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19))
-
- syntax off
- quit!
-endfunc
-
-func Test_clearmatches()
- new
- setlocal concealcursor=n conceallevel=1
-
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '# This is a Test'
-
- call cursor(1, 1)
- call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'Z'})
- let a = getmatches()
- call clearmatches()
- redraw!
-
- let lnum = 2
- call assert_equal(expect, Screenline(lnum))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- " reset match using setmatches()
- " 1234567890123456
- let expect = '#ZThisZisZaZTest'
- call setmatches(a)
- redraw!
-
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
- call assert_equal({'group': 'Conceal', 'pattern': '\%2l ', 'priority': 10, 'id': a[0].id, 'conceal': 'Z'}, a[0])
-
- quit!
-endfunc
-
-func Test_using_matchaddpos()
- new
- setlocal concealcursor=n conceallevel=1
- " set filetype and :syntax on to change screenattr()
- setlocal filetype=conf
- syntax on
-
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '#Pis a Test'
-
- call cursor(1, 1)
- call matchaddpos('Conceal', [[2,2,6]], 10, -1, {'conceal': 'P'})
- let a = getmatches()
- redraw!
-
- let lnum = 2
- call assert_equal(expect, Screenline(lnum))
- call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 2))
- call assert_notequal(screenattr(lnum, 2) , screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 10))
- call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 12))
- call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 16))
- call assert_equal(screenattr(lnum, 12), screenattr(lnum, 16))
- call assert_equal({'group': 'Conceal', 'id': a[0].id, 'priority': 10, 'pos1': [2, 2, 6], 'conceal': 'P'}, a[0])
-
- syntax off
- quit!
-endfunc
-
-func Test_matchadd_repeat_conceal_with_syntax_off()
- new
-
- " To test targets in the same line string is replaced with conceal char
- " correctly, repeat 'TARGET'
- 1put ='TARGET_TARGETTARGET'
- call cursor(1, 1)
- redraw
- call assert_equal('TARGET_TARGETTARGET', Screenline(2))
-
- setlocal conceallevel=2
- call matchadd('Conceal', 'TARGET', 10, -1, {'conceal': 't'})
-
- redraw
- call assert_equal('t_tt', Screenline(2))
-
- quit!
-endfunc
-
-func Test_matchadd_and_syn_conceal()
- new
- let cnt='Inductive bool : Type := | true : bool | false : bool.'
- let expect = 'Inductive - : Type := | true : - | false : -.'
- 0put =cnt
- " set filetype and :syntax on to change screenattr()
- set cole=1 cocu=nv
- hi link CheckedByCoq WarningMsg
- syntax on
- syntax keyword coqKwd bool conceal cchar=-
- redraw!
- call assert_equal(expect, Screenline(1))
- call assert_notequal(screenattr(1, 10) , screenattr(1, 11))
- call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
- call assert_equal(screenattr(1, 11) , screenattr(1, 32))
- call matchadd('CheckedByCoq', '\%<2l\%>9c\%<16c')
- redraw!
- call assert_equal(expect, Screenline(1))
- call assert_notequal(screenattr(1, 10) , screenattr(1, 11))
- call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
- call assert_equal(screenattr(1, 11) , screenattr(1, 32))
-endfunc
-
-func Test_interaction_matchadd_syntax()
- new
- " Test for issue #7268 fix.
- " When redrawing the second column, win_line() was comparing the sequence
- " number of the syntax-concealed region with a bogus zero value that was
- " returned for the matchadd-concealed region. Before 8.0.0672 the sequence
- " number was never reset, thus masking the problem.
- call setline(1, 'aaa|bbb|ccc')
- call matchadd('Conceal', '^..', 10, -1, #{conceal: 'X'})
- syn match foobar '^.'
- setl concealcursor=n conceallevel=1
- redraw!
-
- call assert_equal('Xa|bbb|ccc', Screenline(1))
- call assert_notequal(screenattr(1, 1), screenattr(1, 2))
-
- bwipe!
-endfunc
-
-func Test_cursor_column_in_concealed_line_after_window_scroll()
- CheckRunVimInTerminal
-
- " Test for issue #5012 fix.
- " For a concealed line with cursor, there should be no window's cursor
- " position invalidation during win_update() after scrolling attempt that is
- " not successful and no real topline change happens. The invalidation would
- " cause a window's cursor position recalc outside of win_line() where it's
- " not possible to take conceal into account.
- let lines =<< trim END
- 3split
- let m = matchadd('Conceal', '=')
- setl conceallevel=2 concealcursor=nc
- normal gg
- "==expr==
- END
- call writefile(lines, 'Xcolesearch')
- let buf = RunVimInTerminal('Xcolesearch', {})
- call term_wait(buf, 100)
-
- " Jump to something that is beyond the bottom of the window,
- " so there's a scroll down.
- call term_sendkeys(buf, ":so %\<CR>")
- call term_wait(buf, 100)
- call term_sendkeys(buf, "/expr\<CR>")
- call term_wait(buf, 100)
-
- " Are the concealed parts of the current line really hidden?
- let cursor_row = term_scrape(buf, '.')->map({_, e -> e.chars})->join('')
- call assert_equal('"expr', cursor_row)
-
- " BugFix check: Is the window's cursor column properly updated for hidden
- " parts of the current line?
- call assert_equal(2, term_getcursor(buf)[1])
-
- call StopVimInTerminal(buf)
- call delete('Xcolesearch')
-endfunc
-
-func Test_cursor_column_in_concealed_line_after_leftcol_change()
- CheckRunVimInTerminal
-
- " Test for issue #5214 fix.
- let lines =<< trim END
- 0put = 'ab' .. repeat('-', &columns) .. 'c'
- call matchadd('Conceal', '-')
- set nowrap ss=0 cole=3 cocu=n
- END
- call writefile(lines, 'Xcurs-columns')
- let buf = RunVimInTerminal('-S Xcurs-columns', {})
-
- " Go to the end of the line (3 columns beyond the end of the screen).
- " Horizontal scroll would center the cursor in the screen line, but conceal
- " makes it go to screen column 1.
- call term_sendkeys(buf, "$")
- call term_wait(buf)
-
- " Are the concealed parts of the current line really hidden?
- call WaitForAssert({-> assert_equal('c', term_getline(buf, '.'))})
-
- " BugFix check: Is the window's cursor column properly updated for conceal?
- call assert_equal(1, term_getcursor(buf)[1])
-
- call StopVimInTerminal(buf)
- call delete('Xcurs-columns')
-endfunc
diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/src/nvim/testdir/test_matchadd_conceal_utf8.vim
deleted file mode 100644
index f33c7f694c..0000000000
--- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim
+++ /dev/null
@@ -1,39 +0,0 @@
-" Test for matchadd() and conceal feature using utf-8.
-
-source check.vim
-CheckFeature conceal
-
-func s:screenline(lnum) abort
- let line = []
- for c in range(1, winwidth(0))
- call add(line, nr2char(a:lnum->screenchar(c)))
- endfor
- return s:trim(join(line, ''))
-endfunc
-
-func s:trim(str) abort
- return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$')
-endfunc
-
-func Test_match_using_multibyte_conceal_char()
- new
- setlocal concealcursor=n conceallevel=1
-
- 1put='# This is a Test'
- " 1234567890123456
- let expect = '#ˑThisˑisˑaˑTest'
-
- call cursor(1, 1)
- call matchadd('Conceal', '\%2l ', 20, -1, {'conceal': "\u02d1"})
- redraw!
-
- let lnum = 2
- call assert_equal(expect, s:screenline(lnum))
- call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
- call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
- call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
-
- quit!
-endfunc
diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim
deleted file mode 100644
index b46550fbc3..0000000000
--- a/src/nvim/testdir/test_matchfuzzy.vim
+++ /dev/null
@@ -1,263 +0,0 @@
-" Tests for fuzzy matching
-
-source shared.vim
-source check.vim
-
-" Test for matchfuzzy()
-func Test_matchfuzzy()
- call assert_fails('call matchfuzzy(10, "abc")', 'E686:')
- call assert_fails('call matchfuzzy(["abc"], [])', 'E730:')
- call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:')
- call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:')
- call assert_equal([], matchfuzzy([], 'abc'))
- call assert_equal([], matchfuzzy(['abc'], ''))
- call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac'))
- call assert_equal([], matchfuzzy([10, 20], 'ac'))
- call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
- call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra'))
- call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa'))
- call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one'))
- call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
- call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
- call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
- call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len())
- call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
- " matches with same score should not be reordered
- let l = ['abc1', 'abc2', 'abc3']
- call assert_equal(l, l->matchfuzzy('abc'))
-
- " Tests for match preferences
- " preference for camel case match
- call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo'))
- " preference for match after a separator (_ or space)
- call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo'))
- " preference for leading letter match
- call assert_equal(['onetwo', 'xonetwo'], ['xonetwo', 'onetwo']->matchfuzzy('onetwo'))
- " preference for sequential match
- call assert_equal(['onetwo', 'oanbectdweo'], ['oanbectdweo', 'onetwo']->matchfuzzy('onetwo'))
- " non-matching leading letter(s) penalty
- call assert_equal(['xonetwo', 'xxonetwo'], ['xxonetwo', 'xonetwo']->matchfuzzy('onetwo'))
- " total non-matching letter(s) penalty
- call assert_equal(['one', 'onex', 'onexx'], ['onexx', 'one', 'onex']->matchfuzzy('one'))
- " prefer complete matches over separator matches
- call assert_equal(['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c'], ['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c']->matchfuzzy('vimrc'))
- " gap penalty
- call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab'))
- " path separator vs word separator
- call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim'))
-
- " match multiple words (separated by space)
- call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo'))
- call assert_equal([], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('one two'))
- call assert_equal([], ['foo bar']->matchfuzzy(" \t "))
-
- " test for matching a sequence of words
- call assert_equal(['bar foo'], ['foo bar', 'bar foo', 'foobar', 'barfoo']->matchfuzzy('bar foo', {'matchseq' : 1}))
- call assert_equal([#{text: 'two one'}], [#{text: 'one two'}, #{text: 'two one'}]->matchfuzzy('two one', #{key: 'text', matchseq: v:true}))
-
- %bw!
- eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)})
- let l = getbufinfo()->map({_, v -> fnamemodify(v.name, ':t')})->matchfuzzy('ndl')
- call assert_equal(1, len(l))
- call assert_match('needle', l[0])
-
- " Test for fuzzy matching dicts
- let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
- call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'text_cb' : {v -> v.val}}))
- call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'}))
- call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}}))
- call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'}))
- call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E715:')
- call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}}))
- call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}}))
- call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
- call assert_equal([], matchfuzzy(l, 'cam'))
- " Nvim's callback implementation is different, so E6000 is expected instead,
- " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:')
- call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:')
- call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:')
- call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:')
- call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:')
- " Nvim doesn't have null functions
- " call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
- " matches with same score should not be reordered
- let l = [#{text: 'abc', id: 1}, #{text: 'abc', id: 2}, #{text: 'abc', id: 3}]
- call assert_equal(l, l->matchfuzzy('abc', #{key: 'text'}))
-
- let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
- call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:')
-
- " Test in latin1 encoding
- let save_enc = &encoding
- " Nvim supports utf-8 encoding only
- " set encoding=latin1
- call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
- let &encoding = save_enc
-endfunc
-
-" Test for the matchfuzzypos() function
-func Test_matchfuzzypos()
- call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl'))
- call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
- call assert_equal([['hello', 'hello world hello world'],
- \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]],
- \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
- call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa'))
- call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
- call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
- call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a '))
- call assert_equal([[], [], []], matchfuzzypos(['a b'], ' '))
- call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab'))
- let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256))
- call assert_equal(range(256), x[1][0])
- call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257)))
- call assert_equal([[], [], []], matchfuzzypos([], 'abc'))
-
- " match in a long string
- call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]],
- \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc'))
-
- " preference for camel case match
- call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc'))
- " preference for match after a separator (_ or space)
- call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab'))
- " preference for leading letter match
- call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab'))
- " preference for sequential match
- call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one'))
- " best recursive match
- call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one'))
-
- " match multiple words (separated by space)
- call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
- call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo', {'matchseq': 1}))
- call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz'))
- call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [326]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1}))
- call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
- call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
- call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
-
- let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
- call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
- \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}}))
- call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
- \ matchfuzzypos(l, 'cam', {'key' : 'val'}))
- call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
- call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
- call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:')
- call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}}))
- call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}}))
- call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
- call assert_equal([[], [], []], matchfuzzypos(l, 'cam'))
- " Nvim's callback implementation is different, so E6000 is expected instead,
- " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:')
- call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:')
- call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:')
- call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:')
- call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:')
- " Nvim doesn't have null functions
- " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
-
- let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
- call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:')
-endfunc
-
-" Test for matchfuzzy() with multibyte characters
-func Test_matchfuzzy_mbyte()
- CheckFeature multi_lang
- call assert_equal(['ンヹㄇヺヴ'], matchfuzzy(['ンヹㄇヺヴ'], 'ヹヺ'))
- " reverse the order of characters
- call assert_equal([], matchfuzzy(['ンヹㄇヺヴ'], 'ヺヹ'))
- call assert_equal(['αβΩxxx', 'xαxβxΩx'],
- \ matchfuzzy(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
- call assert_equal(['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
- \ matchfuzzy(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
-
- " match multiple words (separated by space)
- call assert_equal(['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€'], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzy('ë¼ì§€ 마리ì˜'))
- call assert_equal([], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzy('파란 하늘'))
-
- " preference for camel case match
- call assert_equal(['oneĄwo', 'oneąwo'],
- \ ['oneąwo', 'oneĄwo']->matchfuzzy('oneąwo'))
- " preference for complete match then match after separator (_ or space)
- call assert_equal(['ⅠⅡabㄟㄠ'] + sort(['ⅠⅡa_bㄟㄠ', 'ⅠⅡa bㄟㄠ']),
- \ ['ⅠⅡabㄟㄠ', 'ⅠⅡa bㄟㄠ', 'ⅠⅡa_bㄟㄠ']->matchfuzzy('ⅠⅡabㄟㄠ'))
- " preference for match after a separator (_ or space)
- call assert_equal(['ㄓㄔabㄟㄠ', 'ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ'],
- \ ['ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ', 'ㄓㄔabㄟㄠ']->matchfuzzy('ㄓㄔabㄟㄠ'))
- " preference for leading letter match
- call assert_equal(['Å—Åţũŵż', 'xÅ—Åţũŵż'],
- \ ['xÅ—Åţũŵż', 'Å—Åţũŵż']->matchfuzzy('Å—Åţũŵż'))
- " preference for sequential match
- call assert_equal(['ㄞㄡㄤffï¬ï¬‚', 'ㄞaã„¡bㄤcffdï¬efl'],
- \ ['ㄞaã„¡bㄤcffdï¬efl', 'ㄞㄡㄤffï¬ï¬‚']->matchfuzzy('ㄞㄡㄤffï¬ï¬‚'))
- " non-matching leading letter(s) penalty
- call assert_equal(['xㄞㄡㄤffï¬ï¬‚', 'xxㄞㄡㄤffï¬ï¬‚'],
- \ ['xxㄞㄡㄤffï¬ï¬‚', 'xㄞㄡㄤffï¬ï¬‚']->matchfuzzy('ㄞㄡㄤffï¬ï¬‚'))
- " total non-matching letter(s) penalty
- call assert_equal(['Å—ÅÅ£', 'Å—ÅÅ£x', 'Å—ÅÅ£xx'],
- \ ['Å—ÅÅ£xx', 'Å—ÅÅ£', 'Å—ÅÅ£x']->matchfuzzy('Å—ÅÅ£'))
-endfunc
-
-" Test for matchfuzzypos() with multibyte characters
-func Test_matchfuzzypos_mbyte()
- CheckFeature multi_lang
- call assert_equal([['ã“ã‚“ã«ã¡ã¯ä¸–界'], [[0, 1, 2, 3, 4]], [273]],
- \ matchfuzzypos(['ã“ã‚“ã«ã¡ã¯ä¸–界'], 'ã“ã‚“ã«ã¡ã¯'))
- call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
- " reverse the order of characters
- call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ'))
- call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]],
- \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
- call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
- \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]],
- \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
- call assert_equal([['ααααααα'], [[0, 1, 2]], [191]],
- \ matchfuzzypos(['ααααααα'], 'ααα'))
-
- call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'Å—ÅÅ£'], 'ffï¬ï¬‚'))
- let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256))
- call assert_equal(range(256), x[1][0])
- call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257)))
-
- " match multiple words (separated by space)
- call assert_equal([['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€'], [[9, 10, 2, 3, 4]], [328]], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzypos('ë¼ì§€ 마리ì˜'))
- call assert_equal([[], [], []], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzypos('파란 하늘'))
-
- " match in a long string
- call assert_equal([[repeat('ã¶', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]],
- \ matchfuzzypos([repeat('ã¶', 300) .. 'ẼẼẼ'], 'ẼẼẼ'))
- " preference for camel case match
- call assert_equal([['xѳѵÒxxѳѴÒ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵÒxxѳѴÒ'], 'ѳѵÒ'))
- " preference for match after a separator (_ or space)
- call assert_equal([['xã¡ã x_ã¡ã '], [[5, 6]], [145]], matchfuzzypos(['xã¡ã x_ã¡ã '], 'ã¡ã '))
- " preference for leading letter match
- call assert_equal([['ѳѵÒxѳѵÒ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵÒxѳѵÒ'], 'ѳѵ'))
- " preference for sequential match
- call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
- " best recursive match
- call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
-endfunc
-
-" Test for matchfuzzy() with limit
-func Test_matchfuzzy_limit()
- let x = ['1', '2', '3', '2']
- call assert_equal(['2', '2'], x->matchfuzzy('2'))
- call assert_equal(['2', '2'], x->matchfuzzy('2', #{}))
- call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 0}))
- call assert_equal(['2'], x->matchfuzzy('2', #{limit: 1}))
- call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 2}))
- call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 3}))
- call assert_fails("call matchfuzzy(x, '2', #{limit: '2'})", 'E475:')
-
- let l = [{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}]
- call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}}))
- call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{key: 'val'}))
- call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}, limit: 0}))
- call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{key: 'val', limit: 0}))
- call assert_equal([{'id': 5, 'val': 'crayon'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}, limit: 1}))
- call assert_equal([{'id': 5, 'val': 'crayon'}], l->matchfuzzy('c', #{key: 'val', limit: 1}))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim
deleted file mode 100644
index a1121632e6..0000000000
--- a/src/nvim/testdir/test_menu.vim
+++ /dev/null
@@ -1,574 +0,0 @@
-" Test that the system menu can be loaded.
-
-source check.vim
-CheckFeature menu
-
-func Test_load_menu()
- try
- source $VIMRUNTIME/menu.vim
- catch
- call assert_report('error while loading menus: ' . v:exception)
- endtry
- call assert_match('browse confirm w', execute(':menu File.Save'))
-
- let v:errmsg = ''
- doautocmd LoadBufferMenu VimEnter
- call assert_equal('', v:errmsg)
-
- source $VIMRUNTIME/delmenu.vim
- call assert_equal('', v:errmsg)
-endfunc
-
-func Test_buffer_menu_special_buffers()
- " Load in runtime menus
- try
- source $VIMRUNTIME/menu.vim
- catch
- call assert_report('error while loading menus: ' . v:exception)
- endtry
-
- let v:errmsg = ''
- doautocmd LoadBufferMenu VimEnter
- call assert_equal('', v:errmsg)
-
- let orig_buffer_menus = execute("nmenu Buffers")
-
- " Test that regular new buffer results in a new buffer menu item.
- new
- let new_buffer_menus = execute('nmenu Buffers')
- call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(new_buffer_menus, "\n")))
- bwipe!
- call assert_equal(orig_buffer_menus, execute("nmenu Buffers"))
-
- " Make a new command-line window, test that it does not create a new buffer
- " menu.
- call feedkeys("q::let cmdline_buffer_menus=execute('nmenu Buffers')\<CR>:q\<CR>", 'ntx')
- call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(cmdline_buffer_menus, "\n")))
- call assert_equal(orig_buffer_menus, execute("nmenu Buffers"))
-
- if has('terminal')
- " Open a terminal window and test that it does not create a buffer menu
- " item.
- terminal
- let term_buffer_menus = execute('nmenu Buffers')
- call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(term_buffer_menus, "\n")))
- bwipe!
- call assert_equal(orig_buffer_menus, execute("nmenu Buffers"))
- endif
-
- " Remove menus to clean up
- source $VIMRUNTIME/delmenu.vim
- call assert_equal('', v:errmsg)
-endfunc
-
-func Test_translate_menu()
- if !has('multi_lang')
- return
- endif
- if !filereadable($VIMRUNTIME . '/lang/menu_de_de.latin1.vim')
- throw 'Skipped: translated menu not found'
- endif
-
- " First delete any English menus.
- source $VIMRUNTIME/delmenu.vim
- set langmenu=de_de
- source $VIMRUNTIME/menu.vim
- call assert_match('browse confirm w', execute(':menu Datei.Speichern'))
-
- source $VIMRUNTIME/delmenu.vim
-endfunc
-
-func Test_menu_commands()
- nmenu 2 Test.FooBar :let g:did_menu = 'normal'<CR>
- vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR>
- smenu 2 Test.FooBar :let g:did_menu = 'select'<CR>
- omenu 2 Test.FooBar :let g:did_menu = 'op-pending'<CR>
- tlmenu 2 Test.FooBar :let g:did_menu = 'terminal'<CR>
- imenu 2 Test.FooBar :let g:did_menu = 'insert'<CR>
- cmenu 2 Test.FooBar :let g:did_menu = 'cmdline'<CR>
- emenu n Test.FooBar
-
- call feedkeys(":menu Test.FooB\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"menu Test.FooBar', @:)
-
- call assert_equal('normal', g:did_menu)
- emenu v Test.FooBar
- call assert_equal('visual', g:did_menu)
- emenu s Test.FooBar
- call assert_equal('select', g:did_menu)
- emenu o Test.FooBar
- call assert_equal('op-pending', g:did_menu)
- emenu t Test.FooBar
- call assert_equal('terminal', g:did_menu)
- emenu i Test.FooBar
- call assert_equal('insert', g:did_menu)
- emenu c Test.FooBar
- call assert_equal('cmdline', g:did_menu)
-
- nunmenu Test.FooBar
- call assert_fails('emenu n Test.FooBar', 'E335: Menu not defined for Normal mode')
- vunmenu Test.FooBar
- call assert_fails('emenu v Test.FooBar', 'E335: Menu not defined for Visual mode')
- vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR>
- sunmenu Test.FooBar
- call assert_fails('emenu s Test.FooBar', 'E335: Menu not defined for Select mode')
- ounmenu Test.FooBar
- call assert_fails('emenu o Test.FooBar', 'E335: Menu not defined for Op-pending mode')
- iunmenu Test.FooBar
- call assert_fails('emenu i Test.FooBar', 'E335: Menu not defined for Insert mode')
- cunmenu Test.FooBar
- call assert_fails('emenu c Test.FooBar', 'E335: Menu not defined for Cmdline mode')
- tlunmenu Test.FooBar
- call assert_fails('emenu t Test.FooBar', 'E335: Menu not defined for Terminal mode')
-
- aunmenu Test.FooBar
- call assert_fails('emenu n Test.FooBar', 'E334:')
-
- nmenu 2 Test.FooBar.Child :let g:did_menu = 'foobar'<CR>
- call assert_fails('emenu n Test.FooBar', 'E333:')
- nunmenu Test.FooBar.Child
-
- unlet g:did_menu
-endfun
-
-" Test various menu related errors
-func Test_menu_errors()
- menu Test.Foo :version<CR>
-
- " Error cases
- call assert_fails('menu .Test.Foo :ls<CR>', 'E475:')
- call assert_fails('menu Test. :ls<CR>', 'E330:')
- call assert_fails('menu Foo. :ls<CR>', 'E331:')
- call assert_fails('unmenu Test.Foo abc', 'E488:')
- call assert_fails('menu <Tab>:ls :ls<CR>', 'E792:')
- call assert_fails('menu Test.<Tab>:ls :ls<CR>', 'E792:')
- call assert_fails('menu Test.Foo.Bar :ls<CR>', 'E327:')
- call assert_fails('menu Test.-Sep-.Baz :ls<CR>', 'E332:')
- call assert_fails('menu Foo.Bar.--.Baz :ls<CR>', 'E332:')
- call assert_fails('menu disable Test.Foo.Bar', 'E327:')
- call assert_fails('menu disable T.Foo', 'E329:')
- call assert_fails('unmenu Test.Foo.Bar', 'E327:')
- call assert_fails('cunmenu Test.Foo', 'E328:')
- call assert_fails('unmenu Test.Bar', 'E329:')
- call assert_fails('menu Test.Foo.Bar', 'E327:')
- call assert_fails('cmenu Test.Foo', 'E328:')
- call assert_fails('emenu x Test.Foo', 'E475:')
- call assert_fails('emenu Test.Foo.Bar', 'E327:')
- call assert_fails('menutranslate Test', 'E474:')
-
- silent! unmenu Foo
- unmenu Test
-endfunc
-
-" Test for menu item completion in command line
-func Test_menu_expand()
- " Create the menu itmes for test
- menu Dummy.Nothing lll
- for i in range(1, 4)
- let m = 'menu Xmenu.A' .. i .. '.A' .. i
- for j in range(1, 4)
- exe m .. 'B' .. j .. ' :echo "A' .. i .. 'B' .. j .. '"' .. "<CR>"
- endfor
- endfor
- set wildmenu
-
- " Test for <CR> selecting a submenu
- call feedkeys(":emenu Xmenu.A\<Tab>\<CR>\<Right>x\<BS>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"emenu Xmenu.A1.A1B2', @:)
-
- " Test for <Down> selecting a submenu
- call feedkeys(":emenu Xmenu.A\<Tab>\<Right>\<Right>\<Down>" ..
- \ "\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"emenu Xmenu.A3.A3B1 A3B2 A3B3 A3B4', @:)
-
- " Test for <Up> to go up a submenu
- call feedkeys(":emenu Xmenu.A\<Tab>\<Down>\<Up>\<Right>\<Right>" ..
- \ "\<Left>\<Down>\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"emenu Xmenu.A2.A2B1 A2B2 A2B3 A2B4', @:)
-
- " Test for <Up> to go up a menu
- call feedkeys(":emenu Xmenu.A\<Tab>\<Down>\<Up>\<Up>\<Up>" ..
- \ "\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"emenu Dummy. Xmenu.', @:)
-
- " Test for expanding only submenus
- call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"popup Xmenu.A1 A2 A3 A4', @:)
-
- " Test for expanding menus after enable/disable
- call feedkeys(":menu enable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"menu enable Xmenu.A1. A2. A3. A4.', @:)
- call feedkeys(":menu disable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"menu disable Xmenu.A1. A2. A3. A4.', @:)
-
- " Test for expanding non-existing menu path
- call feedkeys(":menu xyz.\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"menu xyz.', @:)
- call feedkeys(":menu Xmenu.A1.A1B1.xyz.\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"menu Xmenu.A1.A1B1.xyz.', @:)
-
- set wildmenu&
- unmenu Xmenu
- unmenu Dummy
-
- " Test for expanding popup menus with some hidden items
- menu Xmenu.foo.A1 a1
- menu Xmenu.]bar bar
- menu Xmenu.]baz.B1 b1
- menu Xmenu.-sep- :
- call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"popup Xmenu.foo', @:)
- unmenu Xmenu
-endfunc
-
-" Test for the menu_info() function
-func Test_menu_info()
- " Define menus with various attributes
- 10nnoremenu 10.10 T&est.F&oo :echo 'foo'<CR>
- 10nmenu <silent> 10.20 T&est.B&ar<Tab>:bar :echo 'bar'<CR>
- 10nmenu <script> 10.30.5 T&est.Ba&z.Qu&x :echo 'qux'<CR>
-
- let d = #{name: "B&ar\t:bar", display: 'Bar', modes: 'n', shortcut: 'a',
- \ accel: ':bar', priority: 20, enabled: v:true, silent: v:true,
- \ noremenu: v:false, script: v:false, rhs: ":echo 'bar'<CR>"}
- call assert_equal(d, menu_info('Test.Bar'))
-
- let d = #{name: 'Ba&z', display: 'Baz', modes: 'n', shortcut: 'z',
- \ priority: 30, submenus: ['Qux']}
- call assert_equal(d, menu_info('Test.Baz'))
-
- let d = #{name: 'T&est', display: 'Test', modes: 'n', shortcut: 'e',
- \ priority: 10, submenus: ['Foo', 'Bar', 'Baz']}
- call assert_equal(d, menu_info('Test'))
- call assert_equal({}, menu_info('Test.Dummy'))
- call assert_equal({}, menu_info('Dummy'))
-
- nmenu disable Test.Foo
- call assert_equal(v:false, menu_info('Test.Foo').enabled)
- nmenu enable Test.Foo
- call assert_equal(v:true, menu_info('Test.Foo').enabled)
-
- call assert_equal(menu_info('Test.Foo'), menu_info('Test.Foo', ''))
- nmenu Test.abc <Nop>
- call assert_equal('<Nop>', menu_info('Test.abc').rhs)
- call assert_fails('call menu_info([])', 'E730:')
- call assert_fails('call menu_info("", [])', 'E730:')
- nunmenu Test
-
- " Test for defining menus in different modes
- menu Test.menu :menu<CR>
- menu! Test.menu! :menu!<CR>
- amenu Test.amenu :amenu<CR>
- nmenu Test.nmenu :nmenu<CR>
- omenu Test.omenu :omenu<CR>
- vmenu Test.vmenu :vmenu<CR>
- xmenu Test.xmenu :xmenu<CR>
- smenu Test.smenu :smenu<CR>
- imenu <silent> <script> Test.imenu :imenu<CR>
- cmenu Test.cmenu :cmenu<CR>
- tlmenu Test.tlmenu :tlmenu<CR>
- tmenu Test.nmenu Normal mode menu
- tmenu Test.omenu Op-pending mode menu
- noremenu Test.noremenu :noremenu<CR>
- noremenu! Test.noremenu! :noremenu!<CR>
- anoremenu Test.anoremenu :anoremenu<CR>
- nnoremenu Test.nnoremenu :nnoremenu<CR>
- onoremenu Test.onoremenu :onoremenu<CR>
- vnoremenu Test.vnoremenu :vnoremenu<CR>
- xnoremenu Test.xnoremenu :xnoremenu<CR>
- snoremenu Test.snoremenu :snoremenu<CR>
- inoremenu <silent> Test.inoremenu :inoremenu<CR>
- cnoremenu Test.cnoremenu :cnoremenu<CR>
- tlnoremenu Test.tlnoremenu :tlnoremenu<CR>
- call assert_equal(#{name: 'menu', priority: 500, shortcut: '',
- \ display: 'menu', modes: ' ', enabled: v:true, silent: v:false,
- \ rhs: ":menu<CR>", noremenu: v:false, script: v:false},
- \ menu_info('Test.menu'))
- call assert_equal(#{name: 'menu!', priority: 500, shortcut: '',
- \ display: 'menu!', modes: '!', enabled: v:true, silent: v:false,
- \ rhs: ":menu!<CR>", noremenu: v:false, script: v:false},
- \ menu_info('Test.menu!', '!'))
- call assert_equal(#{name: 'amenu', priority: 500, shortcut: '',
- \ display: 'amenu', modes: 'a', enabled: v:true, silent: v:false,
- \ rhs: ":amenu<CR>", noremenu: v:false, script: v:false},
- \ menu_info('Test.amenu', 'a'))
- call assert_equal(#{name: 'nmenu', priority: 500, shortcut: '',
- \ display: 'nmenu', modes: 'n', enabled: v:true, silent: v:false,
- \ rhs: ':nmenu<CR>', noremenu: v:false, script: v:false},
- \ menu_info('Test.nmenu', 'n'))
- call assert_equal(#{name: 'omenu', priority: 500, shortcut: '',
- \ display: 'omenu', modes: 'o', enabled: v:true, silent: v:false,
- \ rhs: ':omenu<CR>', noremenu: v:false, script: v:false},
- \ menu_info('Test.omenu', 'o'))
- call assert_equal(#{name: 'vmenu', priority: 500, shortcut: '',
- \ display: 'vmenu', modes: 'v', enabled: v:true, silent: v:false,
- \ rhs: ':vmenu<CR>', noremenu: v:false, script: v:false},
- \ menu_info('Test.vmenu', 'v'))
- call assert_equal(#{name: 'xmenu', priority: 500, shortcut: '',
- \ display: 'xmenu', modes: 'x', enabled: v:true, silent: v:false,
- \ rhs: ':xmenu<CR>', noremenu: v:false, script: v:false},
- \ menu_info('Test.xmenu', 'x'))
- call assert_equal(#{name: 'smenu', priority: 500, shortcut: '',
- \ display: 'smenu', modes: 's', enabled: v:true, silent: v:false,
- \ rhs: ':smenu<CR>', noremenu: v:false, script: v:false},
- \ menu_info('Test.smenu', 's'))
- call assert_equal(#{name: 'imenu', priority: 500, shortcut: '',
- \ display: 'imenu', modes: 'i', enabled: v:true, silent: v:true,
- \ rhs: ':imenu<CR>', noremenu: v:false, script: v:true},
- \ menu_info('Test.imenu', 'i'))
- call assert_equal(#{ name: 'cmenu', priority: 500, shortcut: '',
- \ display: 'cmenu', modes: 'c', enabled: v:true, silent: v:false,
- \ rhs: ':cmenu<CR>', noremenu: v:false, script: v:false},
- \ menu_info('Test.cmenu', 'c'))
- call assert_equal(#{name: 'tlmenu', priority: 500, shortcut: '',
- \ display: 'tlmenu', modes: 'tl', enabled: v:true, silent: v:false,
- \ rhs: ':tlmenu<CR>', noremenu: v:false, script: v:false},
- \ menu_info('Test.tlmenu', 'tl'))
- call assert_equal(#{name: 'noremenu', priority: 500, shortcut: '',
- \ display: 'noremenu', modes: ' ', enabled: v:true, silent: v:false,
- \ rhs: ":noremenu<CR>", noremenu: v:true, script: v:false},
- \ menu_info('Test.noremenu'))
- call assert_equal(#{name: 'noremenu!', priority: 500, shortcut: '',
- \ display: 'noremenu!', modes: '!', enabled: v:true, silent: v:false,
- \ rhs: ":noremenu!<CR>", noremenu: v:true, script: v:false},
- \ menu_info('Test.noremenu!', '!'))
- call assert_equal(#{name: 'anoremenu', priority: 500, shortcut: '',
- \ display: 'anoremenu', modes: 'a', enabled: v:true, silent: v:false,
- \ rhs: ":anoremenu<CR>", noremenu: v:true, script: v:false},
- \ menu_info('Test.anoremenu', 'a'))
- call assert_equal(#{name: 'nnoremenu', priority: 500, shortcut: '',
- \ display: 'nnoremenu', modes: 'n', enabled: v:true, silent: v:false,
- \ rhs: ':nnoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.nnoremenu', 'n'))
- call assert_equal(#{name: 'onoremenu', priority: 500, shortcut: '',
- \ display: 'onoremenu', modes: 'o', enabled: v:true, silent: v:false,
- \ rhs: ':onoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.onoremenu', 'o'))
- call assert_equal(#{name: 'vnoremenu', priority: 500, shortcut: '',
- \ display: 'vnoremenu', modes: 'v', enabled: v:true, silent: v:false,
- \ rhs: ':vnoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.vnoremenu', 'v'))
- call assert_equal(#{name: 'xnoremenu', priority: 500, shortcut: '',
- \ display: 'xnoremenu', modes: 'x', enabled: v:true, silent: v:false,
- \ rhs: ':xnoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.xnoremenu', 'x'))
- call assert_equal(#{name: 'snoremenu', priority: 500, shortcut: '',
- \ display: 'snoremenu', modes: 's', enabled: v:true, silent: v:false,
- \ rhs: ':snoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.snoremenu', 's'))
- call assert_equal(#{name: 'inoremenu', priority: 500, shortcut: '',
- \ display: 'inoremenu', modes: 'i', enabled: v:true, silent: v:true,
- \ rhs: ':inoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.inoremenu', 'i'))
- call assert_equal(#{ name: 'cnoremenu', priority: 500, shortcut: '',
- \ display: 'cnoremenu', modes: 'c', enabled: v:true, silent: v:false,
- \ rhs: ':cnoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.cnoremenu', 'c'))
- call assert_equal(#{name: 'tlnoremenu', priority: 500, shortcut: '',
- \ display: 'tlnoremenu', modes: 'tl', enabled: v:true, silent: v:false,
- \ rhs: ':tlnoremenu<CR>', noremenu: v:true, script: v:false},
- \ menu_info('Test.tlnoremenu', 'tl'))
-
- " Test for getting all the top-level menu names
- call assert_notequal(menu_info('').submenus, [])
-
- aunmenu Test
- tlunmenu Test
- call assert_equal({}, menu_info('Test'))
- call assert_equal({}, menu_info('Test', '!'))
- call assert_equal({}, menu_info('Test', 'a'))
- call assert_equal({}, menu_info('Test', 'n'))
- call assert_equal({}, menu_info('Test', 'o'))
- call assert_equal({}, menu_info('Test', 'v'))
- call assert_equal({}, menu_info('Test', 'x'))
- call assert_equal({}, menu_info('Test', 's'))
- call assert_equal({}, menu_info('Test', 'i'))
- call assert_equal({}, menu_info('Test', 'c'))
- call assert_equal({}, menu_info('Test', 't'))
- call assert_equal({}, menu_info('Test', 'tl'))
-
- amenu Test.amenu :amenu<CR>
- call assert_equal(':amenu<CR>', menu_info('Test.amenu', '').rhs)
- call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', '!').rhs)
- call assert_equal(':amenu<CR>', menu_info('Test.amenu', 'n').rhs)
- call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
- \ menu_info('Test.amenu', 'o').rhs)
- call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
- \ menu_info('Test.amenu', 'v').rhs)
- call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
- \ menu_info('Test.amenu', 'x').rhs)
- call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
- \ menu_info('Test.amenu', 's').rhs)
- call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', 'i').rhs)
- call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
- \ menu_info('Test.amenu', 'c').rhs)
- aunmenu Test.amenu
-
- " Test for hidden menus
- menu ]Test.menu :menu<CR>
- call assert_equal(#{name: ']Test', display: ']Test', priority: 500,
- \ shortcut: '', modes: ' ', submenus: ['menu']},
- \ menu_info(']Test'))
- unmenu ]Test
-endfunc
-
-" Test for <special> keyword in a menu with 'cpo' containing '<'
-func Test_menu_special()
- throw 'Skipped: Nvim does not support cpoptions flag "<"'
- new
- set cpo+=<
- nmenu Test.Sign am<Tab>n<Esc>
- call feedkeys(":emenu n Test.Sign\<CR>", 'x')
- call assert_equal("m<Tab>n<Esc>", getline(1))
- nunmenu Test.Sign
- nmenu <special> Test.Sign am<Tab>n<Esc>
- call setline(1, '')
- call feedkeys(":emenu n Test.Sign\<CR>", 'x')
- call assert_equal("m\tn", getline(1))
- set cpo-=<
- close!
- nunmenu Test.Sign
-endfunc
-
-" Test for "icon=filename" in a toolbar
-func Test_menu_icon()
- CheckFeature toolbar
- nmenu icon=myicon.xpm Toolbar.Foo :echo "Foo"<CR>
- call assert_equal('myicon.xpm', "Toolbar.Foo"->menu_info().icon)
- nunmenu Toolbar.Foo
-
- " Test for using the builtin icon
- amenu ToolBar.BuiltIn22 :echo "BuiltIn22"<CR>
- call assert_equal(#{name: 'BuiltIn22', display: 'BuiltIn22',
- \ enabled: v:true, shortcut: '', modes: 'a', script: v:false,
- \ iconidx: 22, priority: 500, silent: v:false,
- \ rhs: ':echo "BuiltIn22"<CR>', noremenu: v:false},
- \ menu_info("ToolBar.BuiltIn22"))
- aunmenu ToolBar.BuiltIn22
-endfunc
-
-" Test for ":emenu" command in different modes
-func Test_emenu_cmd()
- new
- xmenu Test.foo rx
- call setline(1, ['aaaa', 'bbbb'])
- normal ggVj
- %emenu Test.foo
- call assert_equal(['xxxx', 'xxxx'], getline(1, 2))
- call setline(1, ['aaaa', 'bbbb'])
- exe "normal ggVj\<Esc>"
- %emenu Test.foo
- call assert_equal(['xxxx', 'xxxx'], getline(1, 2))
- call setline(1, ['aaaa', 'bbbb'])
- exe "normal ggV\<Esc>"
- 2emenu Test.foo
- call assert_equal(['aaaa', 'xxxx'], getline(1, 2))
- xunmenu Test.foo
- close!
-endfunc
-
-" Test for PopUp menus
-func Test_popup_menu()
- 20menu PopUp.foo :echo 'foo'<CR>
- 20menu PopUp.bar :echo 'bar'<CR>
- call assert_equal(#{name: 'PopUp', display: 'PopUp', priority: 20,
- \ shortcut: '', modes: ' ', submenus: ['foo', 'bar']},
- \ menu_info('PopUp'))
- menu disable PopUp.bar
- call assert_equal(v:true, "PopUp.foo"->menu_info().enabled)
- call assert_equal(v:false, "PopUp.bar"->menu_info().enabled)
- menu enable PopUp.bar
- call assert_equal(v:true, "PopUp.bar"->menu_info().enabled)
- unmenu PopUp
-endfunc
-
-" Test for MenuPopup autocommand
-func Test_autocmd_MenuPopup()
- CheckNotGui
-
- set mouse=a
- set mousemodel=popup
- aunmenu *
- autocmd MenuPopup * exe printf(
- \ 'anoremenu PopUp.Foo <Cmd>let g:res = ["%s", "%s"]<CR>',
- \ expand('<afile>'), expand('<amatch>'))
-
- call feedkeys("\<RightMouse>\<Down>\<CR>", 'tnix')
- call assert_equal(['n', 'n'], g:res)
-
- call feedkeys("v\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix')
- call assert_equal(['v', 'v'], g:res)
-
- call feedkeys("gh\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix')
- call assert_equal(['s', 's'], g:res)
-
- call feedkeys("i\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix')
- call assert_equal(['i', 'i'], g:res)
-
- autocmd! MenuPopup
- aunmenu PopUp.Foo
- unlet g:res
- set mouse& mousemodel&
-endfunc
-
-" Test for listing the menus using the :menu command
-func Test_show_menus()
- " In the GUI, tear-off menu items are present in the output below
- " So skip this test
- CheckNotGui
- aunmenu *
- call assert_equal(['--- Menus ---'], split(execute('menu'), "\n"))
- nmenu <script> 200.10 Test.nmenu1 :nmenu1<CR>
- nmenu 200.20 Test.nmenu2 :nmenu2<CR>
- nnoremenu 200.30 Test.nmenu3 :nmenu3<CR>
- nmenu 200.40 Test.nmenu4 :nmenu4<CR>
- nmenu 200.50 disable Test.nmenu4
- let exp =<< trim [TEXT]
- --- Menus ---
- 200 Test
- 10 nmenu1
- n& :nmenu1<CR>
- 20 nmenu2
- n :nmenu2<CR>
- 30 nmenu3
- n* :nmenu3<CR>
- 40 nmenu4
- n - :nmenu4<CR>
- [TEXT]
- call assert_equal(exp, split(execute('nmenu'), "\n"))
- nunmenu Test
-endfunc
-
-" Test for menu tips
-func Test_tmenu()
- tunmenu *
- call assert_equal(['--- Menus ---'], split(execute('tmenu'), "\n"))
- tmenu Test.nmenu1 nmenu1
- tmenu Test.nmenu2.sub1 nmenu2.sub1
- let exp =<< trim [TEXT]
- --- Menus ---
- 500 Test
- 500 nmenu1
- t - nmenu1
- 500 nmenu2
- 500 sub1
- t - nmenu2.sub1
- [TEXT]
- call assert_equal(exp, split(execute('tmenu'), "\n"))
- tunmenu Test
-endfunc
-
-func Test_only_modifier()
- exe "tmenu a.b \x80\xfc0"
- let exp =<< trim [TEXT]
- --- Menus ---
- 500 a
- 500 b
- t - <T-2-^@>
- [TEXT]
- call assert_equal(exp, split(execute('tmenu'), "\n"))
-
- tunmenu a.b
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
deleted file mode 100644
index bfdebdac79..0000000000
--- a/src/nvim/testdir/test_messages.vim
+++ /dev/null
@@ -1,530 +0,0 @@
-" Tests for :messages, :echomsg, :echoerr
-
-source check.vim
-source shared.vim
-source term_util.vim
-source view_util.vim
-source screendump.vim
-
-func Test_messages()
- let oldmore = &more
- try
- set nomore
-
- let arr = map(range(10), '"hello" . v:val')
- for s in arr
- echomsg s | redraw
- endfor
-
- " get last two messages
- redir => result
- 2messages | redraw
- redir END
- let msg_list = split(result, "\n")
- call assert_equal(["hello8", "hello9"], msg_list)
-
- " clear messages without last one
- 1messages clear
- let msg_list = GetMessages()
- call assert_equal(['hello9'], msg_list)
-
- " clear all messages
- messages clear
- let msg_list = GetMessages()
- call assert_equal([], msg_list)
- finally
- let &more = oldmore
- endtry
-
- call assert_fails('message 1', 'E474:')
-endfunc
-
- " Patch 7.4.1696 defined the "clearmode()" command for clearing the mode
-" indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message
-" output could then be disturbed when 'cmdheight' was greater than one.
-" This test ensures that the bugfix for this issue remains in place.
-func Test_stopinsert_does_not_break_message_output()
- set cmdheight=2
- redraw!
-
- stopinsert | echo 'test echo'
- call assert_equal(116, screenchar(&lines - 1, 1))
- call assert_equal(32, screenchar(&lines, 1))
- redraw!
-
- stopinsert | echomsg 'test echomsg'
- call assert_equal(116, screenchar(&lines - 1, 1))
- call assert_equal(32, screenchar(&lines, 1))
- redraw!
-
- set cmdheight&
-endfunc
-
-func Test_message_completion()
- call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx')
- 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[1, 2, []]", execute(':echomsg [1, 2, v:_null_list]'))
- 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()
- CheckFunction 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
- eval '<lambda>'->test_ignore_error()
- call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}'))
- call test_ignore_error('RESET')
-endfunc
-
-func Test_mode_message_at_leaving_insert_by_ctrl_c()
- if !has('terminal') || has('gui_running')
- return
- endif
-
- " Set custom statusline built by user-defined function.
- let testfile = 'Xtest.vim'
- call writefile([
- \ 'func StatusLine() abort',
- \ ' return ""',
- \ 'endfunc',
- \ 'set statusline=%!StatusLine()',
- \ 'set laststatus=2',
- \ ], testfile)
-
- let rows = 10
- let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
- call term_wait(buf, 200)
- call assert_equal('run', job_status(term_getjob(buf)))
-
- call term_sendkeys(buf, "i")
- call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))})
- call term_sendkeys(buf, "\<C-C>")
- call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))})
-
- call term_sendkeys(buf, ":qall!\<CR>")
- call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))})
- exe buf . 'bwipe!'
- call delete(testfile)
-endfunc
-
-func Test_mode_message_at_leaving_insert_with_esc_mapped()
- if !has('terminal') || has('gui_running')
- return
- endif
-
- " Set custom statusline built by user-defined function.
- let testfile = 'Xtest.vim'
- call writefile([
- \ 'set laststatus=2',
- \ 'inoremap <Esc> <Esc>00',
- \ ], testfile)
-
- let rows = 10
- let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
- call term_wait(buf, 200)
- call assert_equal('run', job_status(term_getjob(buf)))
-
- call term_sendkeys(buf, "i")
- call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))})
- call term_sendkeys(buf, "\<Esc>")
- call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))})
-
- call term_sendkeys(buf, ":qall!\<CR>")
- call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))})
- exe buf . 'bwipe!'
- call delete(testfile)
-endfunc
-
-func Test_echospace()
- set noruler noshowcmd laststatus=1
- call assert_equal(&columns - 1, v:echospace)
- split
- call assert_equal(&columns - 1, v:echospace)
- set ruler
- call assert_equal(&columns - 1, v:echospace)
- close
- call assert_equal(&columns - 19, v:echospace)
- set showcmd noruler
- call assert_equal(&columns - 12, v:echospace)
- set showcmd ruler
- call assert_equal(&columns - 29, v:echospace)
-
- set ruler& showcmd&
-endfunc
-
-func Test_warning_scroll()
- CheckRunVimInTerminal
- let lines =<< trim END
- call test_override('ui_delay', 50)
- set noruler
- set readonly
- undo
- END
- call writefile(lines, 'XTestWarningScroll', 'D')
- let buf = RunVimInTerminal('', #{rows: 8})
-
- " When the warning comes from a script, messages are scrolled so that the
- " stacktrace is visible.
- call term_sendkeys(buf, ":source XTestWarningScroll\n")
- " only match the final colon in the line that shows the source
- call WaitForAssert({-> assert_match(':$', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('line 4:W10: Warning: Changing a readonly file', term_getline(buf, 6))})
- call WaitForAssert({-> assert_equal('Already at oldest change', term_getline(buf, 7))})
- call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 8))})
- call term_sendkeys(buf, "\n")
-
- " When the warning does not come from a script, messages are not scrolled.
- call term_sendkeys(buf, ":enew\n")
- call term_sendkeys(buf, ":set readonly\n")
- call term_sendkeys(buf, 'u')
- call WaitForAssert({-> assert_equal('W10: Warning: Changing a readonly file', term_getline(buf, 8))})
- call WaitForAssert({-> assert_equal('Already at oldest change', term_getline(buf, 8))})
-
- " clean up
- call StopVimInTerminal(buf)
-endfunc
-
-" Test more-prompt (see :help more-prompt).
-func Test_message_more()
- CheckRunVimInTerminal
- let buf = RunVimInTerminal('', {'rows': 6})
- call term_sendkeys(buf, ":call setline(1, range(1, 100))\n")
-
- call term_sendkeys(buf, ":%pfoo\<C-H>\<C-H>\<C-H>#")
- call WaitForAssert({-> assert_equal(':%p#', term_getline(buf, 6))})
- call term_sendkeys(buf, "\n")
- call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
-
- call term_sendkeys(buf, '?')
- call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit ', term_getline(buf, 6))})
-
- " Down a line with j, <CR>, <NL> or <Down>.
- call term_sendkeys(buf, "j")
- call WaitForAssert({-> assert_equal(' 6 6', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
- call term_sendkeys(buf, "\<NL>")
- call WaitForAssert({-> assert_equal(' 7 7', term_getline(buf, 5))})
- call term_sendkeys(buf, "\<CR>")
- call WaitForAssert({-> assert_equal(' 8 8', term_getline(buf, 5))})
- call term_sendkeys(buf, "\<Down>")
- call WaitForAssert({-> assert_equal(' 9 9', term_getline(buf, 5))})
-
- " Down a screen with <Space>, f, or <PageDown>.
- call term_sendkeys(buf, 'f')
- call WaitForAssert({-> assert_equal(' 14 14', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
- call term_sendkeys(buf, ' ')
- call WaitForAssert({-> assert_equal(' 19 19', term_getline(buf, 5))})
- call term_sendkeys(buf, "\<PageDown>")
- call WaitForAssert({-> assert_equal(' 24 24', term_getline(buf, 5))})
-
- " Down a page (half a screen) with d.
- call term_sendkeys(buf, 'd')
- call WaitForAssert({-> assert_equal(' 27 27', term_getline(buf, 5))})
-
- " Down all the way with 'G'.
- call term_sendkeys(buf, 'G')
- call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
-
- " Up a line k, <BS> or <Up>.
- call term_sendkeys(buf, 'k')
- call WaitForAssert({-> assert_equal(' 99 99', term_getline(buf, 5))})
- call term_sendkeys(buf, "\<BS>")
- call WaitForAssert({-> assert_equal(' 98 98', term_getline(buf, 5))})
- call term_sendkeys(buf, "\<Up>")
- call WaitForAssert({-> assert_equal(' 97 97', term_getline(buf, 5))})
-
- " Up a screen with b or <PageUp>.
- call term_sendkeys(buf, 'b')
- call WaitForAssert({-> assert_equal(' 92 92', term_getline(buf, 5))})
- call term_sendkeys(buf, "\<PageUp>")
- call WaitForAssert({-> assert_equal(' 87 87', term_getline(buf, 5))})
-
- " Up a page (half a screen) with u.
- call term_sendkeys(buf, 'u')
- call WaitForAssert({-> assert_equal(' 84 84', term_getline(buf, 5))})
-
- " Up all the way with 'g'.
- call term_sendkeys(buf, 'g')
- call WaitForAssert({-> assert_equal(' 4 4', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal(':%p#', term_getline(buf, 1))})
- call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
-
- " All the way down. Pressing f should do nothing but pressing
- " space should end the more prompt.
- call term_sendkeys(buf, 'G')
- call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
- call term_sendkeys(buf, 'f')
- call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
- call term_sendkeys(buf, ' ')
- call WaitForAssert({-> assert_equal('100', term_getline(buf, 5))})
-
- " Pressing g< shows the previous command output.
- call term_sendkeys(buf, 'g<')
- call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
-
- " A command line that doesn't print text is appended to scrollback,
- " even if it invokes a nested command line.
- call term_sendkeys(buf, ":\<C-R>=':'\<CR>:\<CR>g<")
- call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 4))})
- call WaitForAssert({-> assert_equal(':::', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
-
- call term_sendkeys(buf, ":%p#\n")
- call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))})
- call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
-
- " Stop command output with q, <Esc> or CTRL-C.
- call term_sendkeys(buf, 'q')
- call WaitForAssert({-> assert_equal('100', term_getline(buf, 5))})
-
- " Execute a : command from the more prompt
- call term_sendkeys(buf, ":%p#\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
- call term_sendkeys(buf, ":")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal(':', term_getline(buf, 6))})
- call term_sendkeys(buf, "echo 'Hello'\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal('Hello ', term_getline(buf, 5))})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Test more-prompt scrollback
-func Test_message_more_scrollback()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- set t_ut=
- hi Normal ctermfg=15 ctermbg=0
- for i in range(100)
- echo i
- endfor
- END
- call writefile(lines, 'XmoreScrollback', 'D')
- let buf = RunVimInTerminal('-S XmoreScrollback', {'rows': 10})
- call VerifyScreenDump(buf, 'Test_more_scrollback_1', {})
-
- call term_sendkeys(buf, 'f')
- call TermWait(buf)
- call term_sendkeys(buf, 'b')
- call VerifyScreenDump(buf, 'Test_more_scrollback_2', {})
-
- call term_sendkeys(buf, 'q')
- call TermWait(buf)
- call StopVimInTerminal(buf)
-endfunc
-
-" Test verbose message before echo command
-func Test_echo_verbose_system()
- CheckRunVimInTerminal
- CheckUnix
-
- let buf = RunVimInTerminal('', {'rows': 10})
- call term_sendkeys(buf, ":4 verbose echo system('seq 20')\<CR>")
- " Note that the screendump is filtered to remove the name of the temp file
- call VerifyScreenDump(buf, 'Test_verbose_system_1', {})
-
- " display a page and go back, results in exactly the same view
- call term_sendkeys(buf, ' ')
- call TermWait(buf, 50)
- call term_sendkeys(buf, 'b')
- call VerifyScreenDump(buf, 'Test_verbose_system_1', {})
-
- " do the same with 'cmdheight' set to 2
- call term_sendkeys(buf, 'q')
- call TermWait(buf)
- call term_sendkeys(buf, ":set ch=2\<CR>")
- call TermWait(buf)
- call term_sendkeys(buf, ":4 verbose echo system('seq 20')\<CR>")
- call VerifyScreenDump(buf, 'Test_verbose_system_2', {})
-
- call term_sendkeys(buf, ' ')
- call TermWait(buf, 50)
- call term_sendkeys(buf, 'b')
- call VerifyScreenDump(buf, 'Test_verbose_system_2', {})
-
- call term_sendkeys(buf, 'q')
- call TermWait(buf)
- call StopVimInTerminal(buf)
-endfunc
-
-
-func Test_ask_yesno()
- CheckRunVimInTerminal
- let buf = RunVimInTerminal('', {'rows': 6})
- call term_sendkeys(buf, ":call setline(1, range(1, 2))\n")
-
- call term_sendkeys(buf, ":2,1s/^/n/\n")
- call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))})
- call term_sendkeys(buf, "n")
- call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?n *1,1 *All$', term_getline(buf, 6))})
- call WaitForAssert({-> assert_equal('1', term_getline(buf, 1))})
-
- call term_sendkeys(buf, ":2,1s/^/Esc/\n")
- call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))})
- call term_sendkeys(buf, "\<Esc>")
- call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?n *1,1 *All$', term_getline(buf, 6))})
- call WaitForAssert({-> assert_equal('1', term_getline(buf, 1))})
-
- call term_sendkeys(buf, ":2,1s/^/y/\n")
- call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))})
- call term_sendkeys(buf, "y")
- call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?y *2,1 *All$', term_getline(buf, 6))})
- call WaitForAssert({-> assert_equal('y1', term_getline(buf, 1))})
- call WaitForAssert({-> assert_equal('y2', term_getline(buf, 2))})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_mapping_at_hit_return_prompt()
- nnoremap <C-B> :echo "hit ctrl-b"<CR>
- call feedkeys(":ls\<CR>", "xt")
- call feedkeys("\<*C-B>", "xt")
- call assert_match('hit ctrl-b', Screenline(&lines - 1))
- nunmap <C-B>
-endfunc
-
-func Test_quit_long_message()
- CheckScreendump
-
- let content =<< trim END
- echom range(9999)->join("\x01")
- END
- call writefile(content, 'Xtest_quit_message', 'D')
- let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 10, wait_for_ruler: 0})
- call WaitForAssert({-> assert_match('^-- More --', term_getline(buf, 10))})
- call term_sendkeys(buf, "q")
- call VerifyScreenDump(buf, 'Test_quit_long_message', {})
-
- " clean up
- call StopVimInTerminal(buf)
-endfunc
-
-" this was missing a terminating NUL
-func Test_echo_string_partial()
- function CountSpaces()
- endfunction
- call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}])))
-endfunc
-
-" Message output was previously overwritten by the fileinfo display, shown
-" when switching buffers. If a buffer is switched to, then a message if
-" echoed, we should show the message, rather than overwriting it with
-" fileinfo.
-func Test_fileinfo_after_echo()
- CheckScreendump
-
- let content =<< trim END
- file a.txt
-
- hide edit b.txt
- call setline(1, "hi")
- setlocal modified
-
- hide buffer a.txt
-
- autocmd CursorHold * buf b.txt | w | echo "'b' written"
- END
-
- call writefile(content, 'Xtest_fileinfo_after_echo')
- let buf = RunVimInTerminal('-S Xtest_fileinfo_after_echo', #{rows: 6})
- call term_sendkeys(buf, ":set updatetime=50\<CR>")
- call term_sendkeys(buf, "0$")
- call VerifyScreenDump(buf, 'Test_fileinfo_after_echo', {})
-
- call term_sendkeys(buf, ":q\<CR>")
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xtest_fileinfo_after_echo')
- call delete('b.txt')
-endfunc
-
-func Test_cmdheight_zero()
- enew
- set cmdheight=0
- set showcmd
- redraw!
-
- echo 'test echo'
- call assert_equal(116, screenchar(&lines, 1))
- redraw!
-
- echomsg 'test echomsg'
- call assert_equal(116, screenchar(&lines, 1))
- redraw!
-
- call feedkeys(":ls\<CR>", "xt")
- call assert_equal(':ls', Screenline(&lines - 1))
- redraw!
-
- let char = getchar(0)
- call assert_match(char, 0)
-
- " Check change/restore cmdheight when macro
- call feedkeys("qa", "xt")
- call assert_equal(0, &cmdheight)
- call feedkeys("q", "xt")
- call assert_equal(0, &cmdheight)
-
- call setline(1, 'somestring')
- call feedkeys("y", "n")
- %s/somestring/otherstring/gc
- call assert_equal('otherstring', getline(1))
-
- call feedkeys("g\<C-g>", "xt")
- call assert_match(
- \ 'Col 1 of 11; Line 1 of 1; Word 1 of 1',
- \ Screenline(&lines))
-
- " Check split behavior
- for i in range(1, 10)
- split
- endfor
- only
- call assert_equal(0, &cmdheight)
-
- " Check that pressing ":" should not scroll a window
- " Check for what patch 9.0.0115 fixes
- botright 10new
- call setline(1, range(12))
- 7
- call feedkeys(":\"\<C-R>=line('w0')\<CR>\<CR>", "xt")
- call assert_equal('"1', @:)
-
- bwipe!
- bwipe!
- set cmdheight&
- set showcmd&
- tabnew
- tabonly
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim
deleted file mode 100644
index ca3b736429..0000000000
--- a/src/nvim/testdir/test_method.vim
+++ /dev/null
@@ -1,174 +0,0 @@
-" Tests for ->method()
-
-source check.vim
-
-func Test_list_method()
- let l = [1, 2, 3]
- call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4))
- eval l->assert_equal(l)
- eval l->assert_equal(l, 'wrong')
- eval l->assert_notequal([3, 2, 1])
- eval l->assert_notequal([3, 2, 1], 'wrong')
- call assert_equal(l, l->copy())
- call assert_equal(l, l->deepcopy())
- call assert_equal(1, l->count(2))
- call assert_false(l->empty())
- call assert_true([]->empty())
- call assert_equal(579, ['123', '+', '456']->join()->eval())
- call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5]))
- call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2'))
- call assert_equal(2, l->get(1))
- call assert_equal(1, l->index(2))
- call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0))
- call assert_fails('eval l->items()', 'E715:')
- call assert_equal('1 2 3', l->join())
- call assert_fails('eval l->keys()', 'E715:')
- call assert_equal(3, l->len())
- call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1'))
- call assert_equal(3, l->max())
- call assert_equal(1, l->min())
- call assert_equal(2, [1, 2, 3]->remove(1))
- call assert_equal([1, 2, 3, 1, 2, 3], l->repeat(2))
- call assert_equal([3, 2, 1], [1, 2, 3]->reverse())
- call assert_equal([1, 2, 3, 4], [4, 2, 3, 1]->sort())
- call assert_equal('[1, 2, 3]', l->string())
- call assert_equal(v:t_list, l->type())
- call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq())
- call assert_fails('eval l->values()', 'E715:')
- call assert_fails('echo []->len', 'E107:')
-endfunc
-
-func Test_dict_method()
- let d = #{one: 1, two: 2, three: 3}
-
- call assert_equal(d, d->copy())
- call assert_equal(d, d->deepcopy())
- call assert_equal(1, d->count(2))
- call assert_false(d->empty())
- call assert_true({}->empty())
- call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4}))
- call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
- call assert_equal(2, d->get('two'))
- call assert_fails("let x = d->index(2)", 'E897:')
- call assert_fails("let x = d->insert(0)", 'E899:')
- call assert_true(d->has_key('two'))
- call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
- call assert_fails("let x = d->join()", 'E714:')
- call assert_equal(['one', 'two', 'three'], d->keys())
- call assert_equal(3, d->len())
- call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1'))
- call assert_equal(#{one: 1, two: 2, three: 3}, d->map('v:val - 1'))
- call assert_equal(3, d->max())
- call assert_equal(1, d->min())
- call assert_equal(2, d->remove("two"))
- let d.two = 2
- call assert_fails('let x = d->repeat(2)', 'E731:')
- call assert_fails('let x = d->reverse()', 'E899:')
- call assert_fails('let x = d->sort()', 'E686:')
- call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
- call assert_equal(v:t_dict, d->type())
- call assert_fails('let x = d->uniq()', 'E686:')
- call assert_equal([1, 2, 3], d->values())
-endfunc
-
-func Test_string_method()
- eval '1 2 3'->split()->assert_equal(['1', '2', '3'])
- eval '1 2 3'->split()->map({i, v -> str2nr(v)})->assert_equal([1, 2, 3])
- eval 'ABC'->str2list()->assert_equal([65, 66, 67])
- eval 'ABC'->strlen()->assert_equal(3)
- eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c')
- eval "aã‚b"->strwidth()->assert_equal(4)
- eval 'abc'->substitute('b', 'x', '')->assert_equal('axc')
-
- eval 'abc'->printf('the %s arg')->assert_equal('the abc arg')
-endfunc
-
-func Test_method_append()
- new
- eval ['one', 'two', 'three']->append(1)
- call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
-
- %del
- let bnr = bufnr('')
- wincmd w
- eval ['one', 'two', 'three']->appendbufline(bnr, 1)
- call assert_equal(['', 'one', 'two', 'three'], getbufline(bnr, 1, '$'))
-
- exe 'bwipe! ' .. bnr
-endfunc
-
-func Test_method_funcref()
- func Concat(one, two, three)
- return a:one .. a:two .. a:three
- endfunc
- let FuncRef = function('Concat')
- eval 'foo'->FuncRef('bar', 'tail')->assert_equal('foobartail')
-
- " not enough arguments
- call assert_fails("eval 'foo'->FuncRef('bar')", 'E119:')
- " too many arguments
- call assert_fails("eval 'foo'->FuncRef('bar', 'tail', 'four')", 'E118:')
-
- let Partial = function('Concat', ['two'])
- eval 'one'->Partial('three')->assert_equal('onetwothree')
-
- " not enough arguments
- call assert_fails("eval 'one'->Partial()", 'E119:')
- " too many arguments
- call assert_fails("eval 'one'->Partial('three', 'four')", 'E118:')
-
- delfunc Concat
-endfunc
-
-func Test_method_float()
- CheckFeature float
- eval 1.234->string()->assert_equal('1.234')
- eval -1.234->string()->assert_equal('-1.234')
-endfunc
-
-func Test_method_syntax()
- eval [1, 2, 3] ->sort( )
- eval [1, 2, 3]
- \ ->sort(
- \ )
- call assert_fails('eval [1, 2, 3]-> sort()', 'E15:')
- call assert_fails('eval [1, 2, 3]->sort ()', 'E274:')
- call assert_fails('eval [1, 2, 3]-> sort ()', 'E15:')
-endfunc
-
-func Test_method_lambda()
- eval "text"->{x -> x .. " extended"}()->assert_equal('text extended')
- eval "text"->{x, y -> x .. " extended " .. y}('more')->assert_equal('text extended more')
-
- call assert_fails('eval "text"->{x -> x .. " extended"} ()', 'E274:')
-
- " todo: lambda accepts more arguments than it consumes
- " call assert_fails('eval "text"->{x -> x .. " extended"}("more")', 'E99:')
-
- " Nvim doesn't include test_refcount().
- " let l = [1, 2, 3]
- " eval l->{x -> x}()
- " call assert_equal(1, test_refcount(l))
-endfunc
-
-func Test_method_not_supported()
- call assert_fails('eval 123->changenr()', 'E276:')
- call assert_fails('echo "abc"->invalidfunc()', 'E117:')
- " Test for too many or too few arguments to a method
- call assert_fails('let n="abc"->len(2)', 'E118:')
- call assert_fails('let n=10->setwinvar()', 'E119:')
-endfunc
-
-" Test for passing optional arguments to methods
-func Test_method_args()
- let v:errors = []
- let n = 10->assert_inrange(1, 5, "Test_assert_inrange")
- if v:errors[0] !~ 'Test_assert_inrange'
- call assert_report(v:errors[0])
- else
- " Test passed
- let v:errors = []
- endif
-endfunc
-
-" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
deleted file mode 100644
index 972397cb91..0000000000
--- a/src/nvim/testdir/test_mksession.vim
+++ /dev/null
@@ -1,1027 +0,0 @@
-" Test for :mksession, :mkview and :loadview in latin1 encoding
-
-scriptencoding latin1
-
-source check.vim
-CheckFeature mksession
-
-source shared.vim
-source term_util.vim
-
-" Test for storing global and local argument list in a session file
-" This one must be done first.
-func Test__mksession_arglocal()
- enew | only
- n a b c
- new
- arglocal
- mksession! Xtest_mks.out
-
- %bwipe!
- %argdelete
- argglobal
- source Xtest_mks.out
- call assert_equal(2, winnr('$'))
- call assert_equal(2, arglistid(1))
- call assert_equal(0, arglistid(2))
-
- %bwipe!
- %argdelete
- argglobal
- call delete('Xtest_mks.out')
-endfunc
-
-func Test_mksession()
- tabnew
- let wrap_save = &wrap
- set sessionoptions=buffers splitbelow fileencoding=latin1
- call setline(1, [
- \ 'start:',
- \ 'no multibyte chAracter',
- \ ' one leaDing tab',
- \ ' four leadinG spaces',
- \ 'two consecutive tabs',
- \ 'two tabs in one line',
- \ 'one ä multibyteCharacter',
- \ 'aä Ä two multiByte characters',
- \ 'Aäöü three mulTibyte characters',
- \ 'short line',
- \ ])
- let tmpfile = 'Xtemp'
- exec 'w! ' . tmpfile
- /^start:
- set wrap
- vsplit
- norm! j16|
- split
- norm! j16|
- split
- norm! j16|
- split
- norm! j8|
- split
- norm! j8|
- split
- norm! j16|
- split
- norm! j16|
- split
- norm! j16|
- split
- norm! j$
- wincmd l
-
- set nowrap
- /^start:
- norm! j16|3zl
- split
- norm! j016|3zl
- split
- norm! j016|3zl
- split
- norm! j08|3zl
- split
- norm! j08|3zl
- split
- norm! j016|3zl
- split
- norm! j016|3zl
- split
- norm! j016|3zl
- split
- call wincol()
- mksession! Xtest_mks.out
- let li = filter(readfile('Xtest_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|',
- \ 'normal! $',
- \ " 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|"
- \ ]
- call assert_equal(expected, li)
- tabclose!
-
- call delete('Xtest_mks.out')
- call delete(tmpfile)
- let &wrap = wrap_save
- set sessionoptions&
-endfunc
-
-func Test_mksession_winheight()
- new
- set winheight=10
- set winminheight=2
- mksession! Xtest_mks.out
- source Xtest_mks.out
-
- call delete('Xtest_mks.out')
-endfunc
-
-func Test_mksession_large_winheight()
- set winheight=999
- mksession! Xtest_mks_winheight.out
- set winheight&
- source Xtest_mks_winheight.out
- call delete('Xtest_mks_winheight.out')
-endfunc
-
-func Test_mksession_zero_winheight()
- set winminheight=0
- edit SomeFile
- split
- wincmd _
- mksession! Xtest_mks_zero
- set winminheight&
- let text = readfile('Xtest_mks_zero')->join()
- call delete('Xtest_mks_zero')
- close
- " check there is no divide by zero
- call assert_notmatch('/ 0[^0-9]', text)
-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')
- set sessionoptions&
-endfunc
-
-func Test_mksession_arglist()
- %argdel
- next file1 file2 file3 file4
- new
- next | next
- mksession! Xtest_mks.out
- source Xtest_mks.out
- call assert_equal(['file1', 'file2', 'file3', 'file4'], argv())
- call assert_equal(2, argidx())
- wincmd w
- call assert_equal(0, argidx())
-
- call delete('Xtest_mks.out')
- enew | only
- argdel *
-endfunc
-
-
-func Test_mksession_one_buffer_two_windows()
- edit Xtest1
- new Xtest2
- split
- mksession! Xtest_mks.out
- let lines = readfile('Xtest_mks.out')
- let count1 = 0
- let count2 = 0
- let count2buf = 0
- let bufexists = 0
- for line in lines
- if line =~ 'edit \f*Xtest1$'
- let count1 += 1
- endif
- if line =~ 'edit \f\{-}Xtest2'
- let count2 += 1
- endif
- if line =~ 'buffer \f\{-}Xtest2'
- let count2buf += 1
- endif
- if line =~ 'bufexists(fnamemodify(.*, ":p")'
- let bufexists += 1
- endif
- endfor
- call assert_equal(1, count1, 'Xtest1 count')
- call assert_equal(2, count2, 'Xtest2 count')
- call assert_equal(2, count2buf, 'Xtest2 buffer count')
- call assert_equal(2, bufexists)
-
- close
- bwipe!
- call delete('Xtest_mks.out')
-endfunc
-
-func Test_mksession_lcd_multiple_tabs()
- tabnew
- tabnew
- lcd .
- tabfirst
- lcd .
- mksession! Xtest_mks.out
- tabonly
- source Xtest_mks.out
- call assert_true(haslocaldir(), 'Tab 1 localdir')
- tabnext 2
- call assert_true(!haslocaldir(), 'Tab 2 localdir')
- tabnext 3
- call assert_true(haslocaldir(), 'Tab 3 localdir')
- call delete('Xtest_mks.out')
-endfunc
-
-func Test_mksession_blank_tabs()
- tabnew
- tabnew
- tabnew
- tabnext 3
- mksession! Xtest_mks.out
- tabnew
- tabnew
- tabnext 2
- source Xtest_mks.out
- call assert_equal(4, tabpagenr('$'), 'session restore should restore number of tabs')
- call assert_equal(3, tabpagenr(), 'session restore should restore the active tab')
- call delete('Xtest_mks.out')
-endfunc
-
-func Test_mksession_blank_windows()
- split
- split
- split
- 3 wincmd w
- mksession! Xtest_mks.out
- split
- split
- 2 wincmd w
- source Xtest_mks.out
- call assert_equal(4, winnr('$'), 'session restore should restore number of windows')
- call assert_equal(3, winnr(), 'session restore should restore the active window')
- call delete('Xtest_mks.out')
-endfunc
-
-func Test_mksession_buffer_count()
- set hidden
-
- " Edit exactly three files in the current session.
- %bwipe!
- e Xfoo | tabe Xbar | tabe Xbaz
- tabdo write
- mksession! Xtest_mks.out
-
- " Verify that loading the session does not create additional buffers.
- %bwipe!
- source Xtest_mks.out
- call assert_equal(3, len(getbufinfo()))
-
- " Clean up.
- call delete('Xfoo')
- call delete('Xbar')
- call delete('Xbaz')
- call delete('Xtest_mks.out')
- %bwipe!
- set nohidden
-endfunc
-
-func Test_mksession_buffer_order()
- %bwipe!
- e Xfoo | e Xbar | e Xbaz | e Xqux
- bufdo write
- mksession! Xtest_mks.out
-
- " Verify that loading the session preserves order of buffers
- %bwipe!
- source Xtest_mks.out
-
- let s:buf_info = getbufinfo()
- call assert_true(s:buf_info[0]['name'] =~# 'Xfoo$')
- call assert_true(s:buf_info[1]['name'] =~# 'Xbar$')
- call assert_true(s:buf_info[2]['name'] =~# 'Xbaz$')
- call assert_true(s:buf_info[3]['name'] =~# 'Xqux$')
-
- " Clean up.
- call delete('Xfoo')
- call delete('Xbar')
- call delete('Xbaz')
- call delete('Xqux')
- call delete('Xtest_mks.out')
- %bwipe!
-endfunc
-
-if has('extra_search')
-
-func Test_mksession_hlsearch()
- set sessionoptions+=options
- set hlsearch
- mksession! Xtest_mks.out
- nohlsearch
- source Xtest_mks.out
- call assert_equal(1, v:hlsearch, 'session should restore search highlighting state')
- nohlsearch
- mksession! Xtest_mks.out
- source Xtest_mks.out
- call assert_equal(0, v:hlsearch, 'session should restore search highlighting state')
- set sessionoptions&
- call delete('Xtest_mks.out')
-endfunc
-
-endif
-
-func Test_mkview_open_folds()
- enew!
-
- call append(0, ['a', 'b', 'c'])
- 1,3fold
- write! Xtestfile
-
- call assert_notequal(-1, foldclosed(1))
- call assert_notequal(-1, foldclosed(2))
- call assert_notequal(-1, foldclosed(3))
-
- " Save the view with folds closed
- mkview! Xtestview
-
- " zR affects 'foldlevel', make sure the option is applied after the folds
- " have been recreated.
- " Open folds to ensure they get closed when restoring the view
- normal zR
-
- call assert_equal(-1, foldclosed(1))
- call assert_equal(-1, foldclosed(2))
- call assert_equal(-1, foldclosed(3))
-
- source Xtestview
-
- call assert_notequal(-1, foldclosed(1))
- call assert_notequal(-1, foldclosed(2))
- call assert_notequal(-1, foldclosed(3))
-
- call delete('Xtestview')
- call delete('Xtestfile')
- %bwipe
-endfunc
-
-func Test_mkview_no_balt()
- edit Xtestfile1
- edit Xtestfile2
-
- mkview! Xtestview
- bdelete Xtestfile1
-
- source Xtestview
- call assert_equal(0, buflisted('Xtestfile1'))
-
- call delete('Xtestview')
- %bwipe
-endfunc
-
-func Test_mksession_no_balt()
- edit Xtestfile1
- edit Xtestfile2
-
- bdelete Xtestfile1
- mksession! Xtestview
-
- source Xtestview
- call assert_equal(0, buflisted('Xtestfile1'))
-
- call delete('Xtestview')
- %bwipe
-endfunc
-
-" Test :mkview with a file argument.
-func Test_mkview_file()
- " Create a view with line number and a fold.
- help :mkview
- set number
- norm! V}zf0
- let pos = getpos('.')
- let linefoldclosed1 = foldclosed('.')
- mkview! Xview
- set nonumber
- norm! zrj
- " We can close the help window, as mkview with a file name should
- " generate a command to edit the file.
- helpclose
-
- source Xview
- call assert_equal(1, &number)
- call assert_match('\*:mkview\*$', getline('.'))
- call assert_equal(pos, getpos('.'))
- call assert_equal(linefoldclosed1, foldclosed('.'))
-
- " Creating a view again with the same file name should fail (file
- " already exists). But with a !, the previous view should be
- " overwritten without error.
- help :loadview
- call assert_fails('mkview Xview', 'E189:')
- call assert_match('\*:loadview\*$', getline('.'))
- mkview! Xview
- call assert_match('\*:loadview\*$', getline('.'))
-
- call delete('Xview')
- bwipe
-endfunc
-
-" Test :mkview and :loadview with a custom 'viewdir'.
-func Test_mkview_loadview_with_viewdir()
- set viewdir=Xviewdir
-
- help :mkview
- set number
- norm! V}zf
- let pos = getpos('.')
- let linefoldclosed1 = foldclosed('.')
- mkview 1
- set nonumber
- norm! zrj
-
- loadview 1
-
- " The directory Xviewdir/ should have been created and the view
- " should be stored in that directory.
- call assert_equal('Xviewdir/' .
- \ substitute(
- \ substitute(
- \ expand('%:p'), '/', '=+', 'g'), ':', '=-', 'g') . '=1.vim',
- \ glob('Xviewdir/*'))
- call assert_equal(1, &number)
- call assert_match('\*:mkview\*$', getline('.'))
- call assert_equal(pos, getpos('.'))
- call assert_equal(linefoldclosed1, foldclosed('.'))
-
- call delete('Xviewdir', 'rf')
- set viewdir&
- helpclose
-endfunc
-
-func Test_mkview_no_file_name()
- new
- " :mkview or :mkview {nr} should fail in a unnamed buffer.
- call assert_fails('mkview', 'E32:')
- call assert_fails('mkview 1', 'E32:')
-
- " :mkview {file} should succeed in a unnamed buffer.
- mkview Xview
- help
- source Xview
- call assert_equal('', bufname('%'))
-
- call delete('Xview')
- %bwipe
-endfunc
-
-func Test_mkview_loadview_jumplist()
- set viewdir=Xviewdir
- au BufWinLeave * silent mkview
- " au BufWinEnter * silent loadview
-
- edit Xfile1
- call setline(1, ['a', 'bbbbbbb', 'c'])
- normal j3l
- call assert_equal([2, 4], getcurpos()[1:2])
- write
-
- edit Xfile2
- call setline(1, ['d', 'eeeeeee', 'f'])
- normal j5l
- call assert_equal([2, 6], getcurpos()[1:2])
- write
-
- edit Xfile3
- call setline(1, ['g', 'h', 'iiiii'])
- normal jj3l
- call assert_equal([3, 4], getcurpos()[1:2])
- write
-
- " The commented :au above was moved here so that :mkview (on BufWinLeave) can
- " run before :loadview. This is needed because Nvim's :loadview raises E484 if
- " the view can't be opened, while Vim's silently fails instead.
- au BufWinEnter * silent loadview
-
- edit Xfile1
- call assert_equal([2, 4], getcurpos()[1:2])
- edit Xfile2
- call assert_equal([2, 6], getcurpos()[1:2])
- edit Xfile3
- call assert_equal([3, 4], getcurpos()[1:2])
-
- exe "normal \<C-O>"
- call assert_equal('Xfile2', expand('%'))
- call assert_equal([2, 6], getcurpos()[1:2])
- exe "normal \<C-O>"
- call assert_equal('Xfile1', expand('%'))
- call assert_equal([2, 4], getcurpos()[1:2])
-
- au! BufWinLeave
- au! BufWinEnter
- bwipe!
- call delete('Xviewdir', 'rf')
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
- set viewdir&
-endfunc
-
-" A clean session (one empty buffer, one window, and one tab) should not
-" set any error messages when sourced because no commands should fail.
-func Test_mksession_no_errmsg()
- let v:errmsg = ''
- %bwipe!
- mksession! Xtest_mks.out
- source Xtest_mks.out
- call assert_equal('', v:errmsg)
- call delete('Xtest_mks.out')
-endfunc
-
-func Test_mksession_quote_in_filename()
- if !has('unix')
- " only Unix can handle this weird filename
- return
- endif
- let v:errmsg = ''
- let filename = has('win32') ? 'x''y' : 'x''y"z'
- %bwipe!
- split another
- execute 'split' escape(filename, '"')
- mksession! Xtest_mks_quoted.out
- %bwipe!
- source Xtest_mks_quoted.out
- call assert_true(bufexists(filename))
-
- %bwipe!
- call delete('Xtest_mks_quoted.out')
-endfunc
-
-" Test for storing global variables in a session file
-func Test_mksession_globals()
- set sessionoptions+=globals
-
- " create different global variables
- let g:Global_string = "Sun is shining\r\n"
- let g:Global_count = 100
- let g:Global_pi = 3.14
- let g:Global_neg_float = -2.68
-
- mksession! Xtest_mks.out
-
- unlet g:Global_string
- unlet g:Global_count
- unlet g:Global_pi
- unlet g:Global_neg_float
-
- source Xtest_mks.out
- call assert_equal("Sun is shining\r\n", g:Global_string)
- call assert_equal(100, g:Global_count)
- call assert_equal(3.14, g:Global_pi)
- call assert_equal(-2.68, g:Global_neg_float)
-
- unlet g:Global_string
- unlet g:Global_count
- unlet g:Global_pi
- unlet g:Global_neg_float
- call delete('Xtest_mks.out')
- set sessionoptions&
-endfunc
-
-" Test for changing backslash to forward slash in filenames
-func Test_mksession_slash()
- if exists('+shellslash')
- throw 'Skipped: cannot use backslash in file name'
- endif
- enew
- %bwipe!
- e a\\b\\c
- mksession! Xtest_mks1.out
- set sessionoptions+=slash
- mksession! Xtest_mks2.out
-
- %bwipe!
- source Xtest_mks1.out
- call assert_equal('a/b/c', bufname(''))
- %bwipe!
- source Xtest_mks2.out
- call assert_equal('a/b/c', bufname(''))
-
- %bwipe!
- call delete('Xtest_mks1.out')
- call delete('Xtest_mks2.out')
- set sessionoptions&
-endfunc
-
-" Test for changing directory to the session file directory
-func Test_mksession_sesdir()
- call mkdir('Xproj')
- mksession! Xproj/Xtest_mks1.out
- set sessionoptions-=curdir
- set sessionoptions+=sesdir
- mksession! Xproj/Xtest_mks2.out
-
- source Xproj/Xtest_mks1.out
- call assert_equal('testdir', fnamemodify(getcwd(), ':t'))
- source Xproj/Xtest_mks2.out
- call assert_equal('Xproj', fnamemodify(getcwd(), ':t'))
- cd ..
-
- set sessionoptions&
- call delete('Xproj', 'rf')
-endfunc
-
-" Test for storing the 'lines' and 'columns' settings
-func Test_mksession_resize()
- mksession! Xtest_mks1.out
- set sessionoptions+=resize
- mksession! Xtest_mks2.out
-
- let lines = readfile('Xtest_mks1.out')
- let found_resize = v:false
- for line in lines
- if line =~ '^set lines='
- let found_resize = v:true
- break
- endif
- endfor
- call assert_false(found_resize)
- let lines = readfile('Xtest_mks2.out')
- let found_resize = v:false
- for line in lines
- if line =~ '^set lines='
- let found_resize = v:true
- break
- endif
- endfor
- call assert_true(found_resize)
-
- call delete('Xtest_mks1.out')
- call delete('Xtest_mks2.out')
- set sessionoptions&
-endfunc
-
-" Test for mksession with a named scratch buffer
-func Test_mksession_scratch()
- set sessionoptions+=options
- enew | only
- file Xscratch
- set buftype=nofile
- mksession! Xtest_mks.out
- %bwipe
- source Xtest_mks.out
- call assert_equal('Xscratch', bufname(''))
- call assert_equal('nofile', &buftype)
- %bwipe
- call delete('Xtest_mks.out')
- set sessionoptions&
-endfunc
-
-" Test for mksession with fold options
-func Test_mksession_foldopt()
- set sessionoptions-=options
- set sessionoptions+=folds
- new
- setlocal foldenable
- setlocal foldmethod=expr
- setlocal foldmarker=<<<,>>>
- setlocal foldignore=%
- setlocal foldlevel=2
- setlocal foldminlines=10
- setlocal foldnestmax=15
- mksession! Xtest_mks.out
- close
- %bwipe
-
- source Xtest_mks.out
- call assert_true(&foldenable)
- call assert_equal('expr', &foldmethod)
- call assert_equal('<<<,>>>', &foldmarker)
- call assert_equal('%', &foldignore)
- call assert_equal(2, &foldlevel)
- call assert_equal(10, &foldminlines)
- call assert_equal(15, &foldnestmax)
-
- close
- %bwipe
- set sessionoptions&
-endfunc
-
-" Test for mksession with "help" but not "options" in 'sessionoptions'
-func Test_mksession_help_noopt()
- set sessionoptions-=options
- set sessionoptions+=help
- help
- let fname = expand('%')
- mksession! Xtest_mks.out
- bwipe
-
- source Xtest_mks.out
- call assert_equal('help', &buftype)
- call assert_equal('help', &filetype)
- call assert_equal(fname, expand('%'))
- call assert_false(&modifiable)
- call assert_true(&readonly)
-
- helpclose
- help index
- let fname = expand('%')
- mksession! Xtest_mks.out
- bwipe
-
- source Xtest_mks.out
- call assert_equal('help', &buftype)
- call assert_equal(fname, expand('%'))
-
- call delete('Xtest_mks.out')
- set sessionoptions&
-endfunc
-
-" Test for mksession with window position
-func Test_mksession_winpos()
- if !has('gui_running')
- " Only applicable in GUI Vim
- return
- endif
- set sessionoptions+=winpos
- mksession! Xtest_mks.out
- let found_winpos = v:false
- let lines = readfile('Xtest_mks.out')
- for line in lines
- if line =~ '^winpos '
- let found_winpos = v:true
- break
- endif
- endfor
- call assert_true(found_winpos)
- call delete('Xtest_mks.out')
- set sessionoptions&
-endfunc
-
-" Test for mksession without options restores winminheight
-func Test_mksession_winminheight()
- set sessionoptions-=options
- split
- mksession! Xtest_mks.out
- let found_restore = 0
- let lines = readfile('Xtest_mks.out')
- for line in lines
- if line =~ '= s:save_winmin\(width\|height\)'
- let found_restore += 1
- endif
- endfor
- call assert_equal(2, found_restore)
- call delete('Xtest_mks.out')
- close
- set sessionoptions&
-endfunc
-
-" Test for mksession with and without options restores shortmess
-func Test_mksession_shortmess()
- " Without options
- set sessionoptions-=options
- split
- mksession! Xtest_mks.out
- let found_save = 0
- let found_restore = 0
- let lines = readfile('Xtest_mks.out')
- for line in lines
- let line = trim(line)
-
- if line ==# 'let s:shortmess_save = &shortmess'
- let found_save += 1
- endif
-
- if found_save !=# 0 && line ==# 'let &shortmess = s:shortmess_save'
- let found_restore += 1
- endif
- endfor
- call assert_equal(1, found_save)
- call assert_equal(1, found_restore)
- call delete('Xtest_mks.out')
- close
- set sessionoptions&
-
- " With options
- set sessionoptions+=options
- split
- mksession! Xtest_mks.out
- let found_restore = 0
- let lines = readfile('Xtest_mks.out')
- for line in lines
- if line =~# 's:shortmess_save'
- let found_restore += 1
- endif
- endfor
- call assert_equal(0, found_restore)
- call delete('Xtest_mks.out')
- close
- set sessionoptions&
-endfunc
-
-" Test that when Vim loading session has 'A' in 'shortmess' it does not
-" complain about an existing swapfile.
-func Test_mksession_shortmess_with_A()
- edit Xtestfile
- write
- let fname = swapname('%')
- let cont = readblob(fname)
- set sessionoptions-=options
- mksession Xtestsession
- bwipe!
-
- " Recreate the swap file to pretend the file is being edited
- call writefile(cont, fname)
- set shortmess+=A
- source Xtestsession
-
- set shortmess&
- set sessionoptions&
- call delete('Xtestsession')
- call delete(fname)
-endfunc
-
-" Test for mksession with 'compatible' option
-func Test_mksession_compatible()
- throw 'skipped: Nvim does not support "compatible" option'
- mksession! Xtest_mks1.out
- set compatible
- mksession! Xtest_mks2.out
- set nocp
-
- let test_success = v:false
- let lines = readfile('Xtest_mks1.out')
- for line in lines
- if line =~ '^if &cp | set nocp | endif'
- let test_success = v:true
- break
- endif
- endfor
- call assert_true(test_success)
-
- let test_success = v:false
- let lines = readfile('Xtest_mks2.out')
- for line in lines
- if line =~ '^if !&cp | set cp | endif'
- let test_success = v:true
- break
- endif
- endfor
- call assert_true(test_success)
-
- call delete('Xtest_mks1.out')
- call delete('Xtest_mks2.out')
- set compatible&
- set sessionoptions&
-endfunc
-
-func s:ClearMappings()
- mapclear
- omapclear
- mapclear!
- lmapclear
- tmapclear
-endfunc
-
-func Test_mkvimrc()
- let entries = [
- \ ['', 'nothing', '<Nop>'],
- \ ['n', 'normal', 'NORMAL'],
- \ ['v', 'visual', 'VISUAL'],
- \ ['s', 'select', 'SELECT'],
- \ ['x', 'visualonly', 'VISUALONLY'],
- \ ['o', 'operator', 'OPERATOR'],
- \ ['i', 'insert', 'INSERT'],
- \ ['l', 'lang', 'LANG'],
- \ ['c', 'command', 'COMMAND'],
- \ ['t', 'terminal', 'TERMINAL'],
- \ ]
- for entry in entries
- exe entry[0] .. 'map ' .. entry[1] .. ' ' .. entry[2]
- endfor
-
- mkvimrc Xtestvimrc
-
- call s:ClearMappings()
- for entry in entries
- call assert_equal('', maparg(entry[1], entry[0]))
- endfor
-
- source Xtestvimrc
-
- for entry in entries
- call assert_equal(entry[2], maparg(entry[1], entry[0]))
- endfor
-
- call s:ClearMappings()
-
- " the 'pastetoggle', 'wildchar' and 'wildcharm' option values should be
- " stored as key names in the vimrc file
- set pastetoggle=<F5>
- set wildchar=<F6>
- set wildcharm=<F7>
- call assert_fails('mkvimrc Xtestvimrc')
- mkvimrc! Xtestvimrc
- call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set pastetoggle=<F5>'))
- call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildchar=<F6>'))
- call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildcharm=<F7>'))
- set pastetoggle& wildchar& wildcharm&
-
- call delete('Xtestvimrc')
-endfunc
-
-func Test_scrolloff()
- set sessionoptions+=localoptions
- setlocal so=1 siso=1
- mksession! Xtest_mks.out
- setlocal so=-1 siso=-1
- source Xtest_mks.out
- call assert_equal(1, &l:so)
- call assert_equal(1, &l:siso)
- call delete('Xtest_mks.out')
- setlocal so& siso&
- set sessionoptions&
-endfunc
-
-func Test_altfile()
- edit Xone
- split Xtwo
- edit Xtwoalt
- edit #
- wincmd w
- edit Xonealt
- edit #
- mksession! Xtest_altfile
- only
- bwipe Xonealt
- bwipe Xtwoalt
- bwipe!
- source Xtest_altfile
- call assert_equal('Xone', bufname())
- call assert_equal('Xonealt', bufname('#'))
- wincmd w
- call assert_equal('Xtwo', bufname())
- call assert_equal('Xtwoalt', bufname('#'))
- only
- bwipe!
- call delete('Xtest_altfile')
-endfunc
-
-" Test for creating views with manual folds
-func Test_mkview_manual_fold()
- call writefile(range(1,10), 'Xfile')
- new Xfile
- " create recursive folds
- 5,6fold
- 4,7fold
- mkview Xview
- normal zE
- source Xview
- call assert_equal([-1, 4, 4, 4, 4, -1], [foldclosed(3), foldclosed(4),
- \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
- " open one level of fold
- 4foldopen
- mkview! Xview
- normal zE
- source Xview
- call assert_equal([-1, -1, 5, 5, -1, -1], [foldclosed(3), foldclosed(4),
- \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
- " open all the folds
- %foldopen!
- mkview! Xview
- normal zE
- source Xview
- call assert_equal([-1, -1, -1, -1, -1, -1], [foldclosed(3), foldclosed(4),
- \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
- call delete('Xfile')
- call delete('Xview')
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim
deleted file mode 100644
index 4e593cc21a..0000000000
--- a/src/nvim/testdir/test_mksession_utf8.vim
+++ /dev/null
@@ -1,105 +0,0 @@
-" Test for :mksession, :mkview and :loadview in utf-8 encoding
-
-set encoding=utf-8
-scriptencoding utf-8
-
-source check.vim
-CheckFeature mksession
-
-func Test_mksession_utf8()
- tabnew
- let wrap_save = &wrap
- set sessionoptions=buffers splitbelow fileencoding=utf-8
- call setline(1, [
- \ 'start:',
- \ 'no multibyte chAracter',
- \ ' one leaDing tab',
- \ ' four leadinG spaces',
- \ 'two consecutive tabs',
- \ 'two tabs in one line',
- \ 'one … multibyteCharacter',
- \ 'a “b†two multiByte characters',
- \ '“câ€1€ three mulTibyte characters'
- \ ])
- let tmpfile = tempname()
- exec 'w! ' . tmpfile
- /^start:
- set wrap
- vsplit
- norm! j16|
- split
- norm! j16|
- split
- norm! j16|
- split
- norm! j8|
- split
- norm! j8|
- split
- norm! j16|
- split
- norm! j16|
- split
- norm! j16|
- wincmd l
-
- set nowrap
- /^start:
- norm! j16|3zl
- split
- norm! j016|3zl
- split
- norm! j016|3zl
- split
- norm! j08|3zl
- split
- norm! j08|3zl
- split
- norm! j016|3zl
- split
- norm! j016|3zl
- split
- norm! j016|3zl
- split
- call wincol()
- mksession! test_mks.out
- let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"')
- 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!
-
- call delete('test_mks.out')
- call delete(tmpfile)
- let &wrap = wrap_save
- set sessionoptions& splitbelow& fileencoding&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim
deleted file mode 100644
index 613722fdbd..0000000000
--- a/src/nvim/testdir/test_modeline.vim
+++ /dev/null
@@ -1,376 +0,0 @@
-" Tests for parsing the modeline.
-
-source check.vim
-
-func Test_modeline_invalid()
- " This was reading allocated memory in the past.
- call writefile(['vi:0', 'nothing'], 'Xmodeline')
- let modeline = &modeline
- set modeline
- 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
-
-func Test_modeline_filetype()
- call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype')
- let modeline = &modeline
- set modeline
- filetype plugin on
- split Xmodeline_filetype
- call assert_equal("c", &filetype)
- call assert_equal(1, b:did_ftplugin)
- call assert_equal("ccomplete#Complete", &ofu)
-
- bwipe!
- call delete('Xmodeline_filetype')
- let &modeline = modeline
- filetype plugin off
-endfunc
-
-func Test_modeline_syntax()
- call writefile(['vim: set syn=c :', 'nothing'], 'Xmodeline_syntax')
- let modeline = &modeline
- set modeline
- syntax enable
- split Xmodeline_syntax
- call assert_equal("c", &syntax)
- call assert_equal("c", b:current_syntax)
-
- bwipe!
- call delete('Xmodeline_syntax')
- let &modeline = modeline
- syntax off
-endfunc
-
-func Test_modeline_keymap()
- if !has('keymap')
- return
- endif
- call writefile(['vim: set keymap=greek :', 'nothing'], 'Xmodeline_keymap')
- let modeline = &modeline
- set modeline
- split Xmodeline_keymap
- call assert_equal("greek", &keymap)
- call assert_match('greek\|grk', b:keymap_name)
-
- bwipe!
- call delete('Xmodeline_keymap')
- let &modeline = modeline
- 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)
- return
- endif
- let fname = "Xmodeline_fails_" . a:what
- call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname)
- let modeline = &modeline
- set modeline
- filetype plugin on
- syntax enable
- call assert_fails('split ' . fname, a:error)
- call assert_equal("", &filetype)
- call assert_equal("", &syntax)
-
- bwipe!
- call delete(fname)
- let &modeline = modeline
- filetype plugin off
- syntax off
-endfunc
-
-func Test_modeline_filetype_fails()
- call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:')
-endfunc
-
-func Test_modeline_syntax_fails()
- call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:')
-endfunc
-
-func Test_modeline_keymap_fails()
- call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:')
-endfunc
-
-func Test_modeline_fails_always()
- call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:')
- call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:')
- call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:')
- call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:')
- call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:')
- call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:')
- call s:modeline_fails('directory', 'directory=Something()', 'E520:')
- call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:')
- call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:')
- call s:modeline_fails('exrc', 'exrc=Something()', 'E520:')
- call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:')
- call s:modeline_fails('fsync', 'fsync=Something()', 'E520:')
- call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:')
- call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:')
- call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:')
- call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:')
- call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:')
- call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:')
- call s:modeline_fails('langmap', 'langmap=Something()', 'E520:')
- call s:modeline_fails('luadll', 'luadll=Something()', 'E520:')
- call s:modeline_fails('makeef', 'makeef=Something()', 'E520:')
- call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:')
- call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:')
- call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:')
- call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:')
- call s:modeline_fails('modelineexpr', 'modelineexpr', 'E520:')
- call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:')
- call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:')
- call s:modeline_fails('perldll', 'perldll=Something()', 'E520:')
- call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:')
- call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:')
- call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:')
- call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:')
- call s:modeline_fails('pythonhome', 'pythonhome=Something()', 'E520:')
- call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:')
- call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:')
- call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:')
- call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:')
- call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:')
- call s:modeline_fails('secure', 'secure=Something()', 'E520:')
- call s:modeline_fails('shell', 'shell=Something()', 'E520:')
- call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:')
- call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:')
- call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:')
- call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:')
- call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:')
- call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:')
- call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:')
- call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:')
- call s:modeline_fails('titleold', 'titleold=Something()', 'E520:')
- call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:')
- call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:')
- call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:')
- call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:')
- call s:modeline_fails('undodir', 'undodir=Something()', 'E520:')
- " only check a few terminal options
- " Skip these since nvim doesn't support termcodes as options
- "call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:')
- "call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:')
- "call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:')
- "call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:')
-endfunc
-
-func Test_modeline_fails_modelineexpr()
- call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:')
- call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:')
- call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:')
- call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:')
- call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:')
- call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:')
- call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:')
- call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:')
- call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:')
- call s:modeline_fails('statusline', 'statusline=Something()', 'E992:')
- call s:modeline_fails('tabline', 'tabline=Something()', 'E992:')
- call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:')
-endfunc
-
-func Test_modeline_setoption_verbose()
- let modeline = &modeline
- set modeline
-
- let lines =<< trim END
- 1 vim:ts=2
- 2 two
- 3 three
- 4 four
- 5 five
- 6 six
- 7 seven
- 8 eight
- END
- call writefile(lines, 'Xmodeline')
- edit Xmodeline
- let info = split(execute('verbose set tabstop?'), "\n")
- call assert_match('^\s*Last set from modeline line 1$', info[-1])
- bwipe!
-
- let lines =<< trim END
- 1 one
- 2 two
- 3 three
- 4 vim:ts=4
- 5 five
- 6 six
- 7 seven
- 8 eight
- END
- call writefile(lines, 'Xmodeline')
- edit Xmodeline
- let info = split(execute('verbose set tabstop?'), "\n")
- call assert_match('^\s*Last set from modeline line 4$', info[-1])
- bwipe!
-
- let lines =<< trim END
- 1 one
- 2 two
- 3 three
- 4 four
- 5 five
- 6 six
- 7 seven
- 8 vim:ts=8
- END
- call writefile(lines, 'Xmodeline')
- edit Xmodeline
- let info = split(execute('verbose set tabstop?'), "\n")
- call assert_match('^\s*Last set from modeline line 8$', info[-1])
- bwipe!
-
- let &modeline = modeline
- call delete('Xmodeline')
-endfunc
-
-" Test for the 'modeline' default value in compatible and non-compatible modes
-" for root and non-root accounts
-func Test_modeline_default()
- " set compatible
- " call assert_false(&modeline)
- set nocompatible
- call assert_equal(IsRoot() ? 0 : 1, &modeline)
- " set compatible&vi
- " call assert_false(&modeline)
- set compatible&vim
- call assert_equal(IsRoot() ? 0 : 1, &modeline)
- set compatible& modeline&
-endfunc
-
-" Some options cannot be set from the modeline when 'diff' option is set
-func Test_modeline_diff_buffer()
- call writefile(['vim: diff foldmethod=marker wrap'], 'Xfile')
- set foldmethod& nowrap
- new Xfile
- call assert_equal('manual', &foldmethod)
- call assert_false(&wrap)
- set wrap&
- call delete('Xfile')
- bw
-endfunc
-
-func Test_modeline_disable()
- set modeline
- call writefile(['vim: sw=2', 'vim: nomodeline', 'vim: sw=3'], 'Xmodeline_disable')
- edit Xmodeline_disable
- call assert_equal(2, &sw)
- call delete('Xmodeline_disable')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_move.vim b/src/nvim/testdir/test_move.vim
deleted file mode 100644
index 8c40369dbd..0000000000
--- a/src/nvim/testdir/test_move.vim
+++ /dev/null
@@ -1,46 +0,0 @@
-" Test the ":move" command.
-
-func Test_move()
- enew!
- call append(0, ['line 1', 'line 2', 'line 3'])
- g /^$/ delete _
- set nomodified
-
- move .
- call assert_equal(['line 1', 'line 2', 'line 3'], getline(1, 3))
- call assert_false(&modified)
-
- 1,2move 0
- call assert_equal(['line 1', 'line 2', 'line 3'], getline(1, 3))
- call assert_false(&modified)
-
- 1,3move 3
- call assert_equal(['line 1', 'line 2', 'line 3'], getline(1, 3))
- call assert_false(&modified)
-
- 1move 2
- call assert_equal(['line 2', 'line 1', 'line 3'], getline(1, 3))
- call assert_true(&modified)
- set nomodified
-
- 3move 0
- call assert_equal(['line 3', 'line 2', 'line 1'], getline(1, 3))
- call assert_true(&modified)
- set nomodified
-
- 2,3move 0
- call assert_equal(['line 2', 'line 1', 'line 3'], getline(1, 3))
- call assert_true(&modified)
- set nomodified
-
- call assert_fails('1,2move 1', 'E134')
- call assert_fails('2,3move 2', 'E134')
- call assert_fails("move -100", 'E16:')
- call assert_fails("move +100", 'E16:')
- call assert_fails('move', 'E16:')
- call assert_fails("move 'r", 'E20:')
-
- %bwipeout!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_nested_function.vim b/src/nvim/testdir/test_nested_function.vim
deleted file mode 100644
index afaaea6ceb..0000000000
--- a/src/nvim/testdir/test_nested_function.vim
+++ /dev/null
@@ -1,63 +0,0 @@
-"Tests for nested functions
-"
-func NestedFunc()
- func! Func1()
- let g:text .= 'Func1 '
- endfunc
- call Func1()
- func! s:func2()
- let g:text .= 's:func2 '
- endfunc
- call s:func2()
- func! s:_func3()
- let g:text .= 's:_func3 '
- endfunc
- call s:_func3()
- let fn = 'Func4'
- func! {fn}()
- let g:text .= 'Func4 '
- endfunc
- call {fn}()
- let fn = 'func5'
- func! s:{fn}()
- let g:text .= 's:func5'
- endfunc
- call s:{fn}()
-endfunc
-
-func Test_nested_functions()
- let g:text = ''
- call NestedFunc()
- call assert_equal('Func1 s:func2 s:_func3 Func4 s:func5', g:text)
-endfunction
-
-func Test_nested_argument()
- func g:X()
- let g:Y = function('sort')
- endfunc
- let g:Y = function('sort')
- echo g:Y([], g:X())
- delfunc g:X
- unlet g:Y
-endfunc
-
-func Recurse(count)
- if a:count > 0
- call Recurse(a:count - 1)
- endif
-endfunc
-
-func Test_max_nesting()
- let call_depth_here = 2
- let ex_depth_here = 5
- set mfd&
-
- call Recurse(99 - call_depth_here)
- call assert_fails('call Recurse(' . (100 - call_depth_here) . ')', 'E132:')
-
- set mfd=210
- call Recurse(209 - ex_depth_here)
- call assert_fails('call Recurse(' . (210 - ex_depth_here) . ')', 'E169:')
-
- set mfd&
-endfunc
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
deleted file mode 100644
index 7c90b444e5..0000000000
--- a/src/nvim/testdir/test_normal.vim
+++ /dev/null
@@ -1,3831 +0,0 @@
-" Test for various Normal mode commands
-
-source shared.vim
-source check.vim
-source view_util.vim
-source vim9.vim
-source screendump.vim
-
-func Setup_NewWindow()
- 10new
- call setline(1, range(1,100))
-endfunc
-
-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)
- if getline(ln) =~# '\d\s\+$'
- call setline(ln, substitute(line, '\s\+$', '', '') . '->$')
- endif
- endfor
-endfunc
-
-func CountSpaces(type, ...)
- " for testing operatorfunc
- " will count the number of spaces
- " and return the result in g:a
- let sel_save = &selection
- let &selection = "inclusive"
- let reg_save = @@
-
- if a:0 " Invoked from Visual mode, use gv command.
- silent exe "normal! gvy"
- elseif a:type == 'line'
- silent exe "normal! '[V']y"
- else
- silent exe "normal! `[v`]y"
- endif
- let g:a = strlen(substitute(@@, '[^ ]', '', 'g'))
- let &selection = sel_save
- let @@ = reg_save
-endfunc
-
-func OpfuncDummy(type, ...)
- " for testing operatorfunc
- let g:opt = &linebreak
-
- if a:0 " Invoked from Visual mode, use gv command.
- silent exe "normal! gvy"
- elseif a:type == 'line'
- silent exe "normal! '[V']y"
- else
- silent exe "normal! `[v`]y"
- endif
- " Create a new dummy window
- new
- let g:bufnr = bufnr('%')
-endfunc
-
-func Test_normal00_optrans()
- new
- call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line'])
- 1
- exe "norm! Sfoobar\<esc>"
- call assert_equal(['foobar', '2 This is the second line', '3 this is the third line', ''], getline(1,'$'))
- 2
- exe "norm! $vbsone"
- call assert_equal(['foobar', '2 This is the second one', '3 this is the third line', ''], getline(1,'$'))
- norm! VS Second line here
- call assert_equal(['foobar', ' Second line here', '3 this is the third line', ''], getline(1, '$'))
- %d
- call append(0, ['4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line'])
- call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line'])
-
- 1
- norm! 2D
- call assert_equal(['3 this is the third line', '4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line', ''], getline(1,'$'))
- " Nvim: no "#" flag in 'cpoptions'.
- " set cpo+=#
- " norm! 4D
- " call assert_equal(['', '4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line', ''], getline(1,'$'))
-
- " clean up
- set cpo-=#
- bw!
-endfunc
-
-func Test_normal01_keymodel()
- call Setup_NewWindow()
- " Test 1: depending on 'keymodel' <s-down> does something different
- 50
- call feedkeys("V\<S-Up>y", 'tx')
- call assert_equal(['47', '48', '49', '50'], getline("'<", "'>"))
- set keymodel=startsel
- 50
- call feedkeys("V\<S-Up>y", 'tx')
- call assert_equal(['49', '50'], getline("'<", "'>"))
- " Start visual mode when keymodel = startsel
- 50
- call feedkeys("\<S-Up>y", 'tx')
- call assert_equal(['49', '5'], getreg(0, 0, 1))
- " Use the different Shift special keys
- 50
- call feedkeys("\<S-Right>\<S-Left>\<S-Up>\<S-Down>\<S-Home>\<S-End>y", 'tx')
- call assert_equal(['50'], getline("'<", "'>"))
- call assert_equal(['50', ''], getreg(0, 0, 1))
-
- " Do not start visual mode when keymodel=
- set keymodel=
- 50
- call feedkeys("\<S-Up>y$", 'tx')
- call assert_equal(['42'], getreg(0, 0, 1))
- " Stop visual mode when keymodel=stopsel
- set keymodel=stopsel
- 50
- call feedkeys("Vkk\<Up>yy", 'tx')
- call assert_equal(['47'], getreg(0, 0, 1))
-
- set keymodel=
- 50
- call feedkeys("Vkk\<Up>yy", 'tx')
- call assert_equal(['47', '48', '49', '50'], getreg(0, 0, 1))
-
- " Test for using special keys to start visual selection
- %d
- call setline(1, ['red fox tail', 'red fox tail', 'red fox tail'])
- set keymodel=startsel
- " Test for <S-PageUp> and <S-PageDown>
- call cursor(1, 1)
- call feedkeys("\<S-PageDown>y", 'xt')
- call assert_equal([0, 1, 1, 0], getpos("'<"))
- call assert_equal([0, 3, 1, 0], getpos("'>"))
- call feedkeys("Gz\<CR>8|\<S-PageUp>y", 'xt')
- call assert_equal([0, 2, 1, 0], getpos("'<"))
- call assert_equal([0, 3, 8, 0], getpos("'>"))
- " Test for <S-C-Home> and <S-C-End>
- call cursor(2, 12)
- call feedkeys("\<S-C-Home>y", 'xt')
- call assert_equal([0, 1, 1, 0], getpos("'<"))
- call assert_equal([0, 2, 12, 0], getpos("'>"))
- call cursor(1, 4)
- call feedkeys("\<S-C-End>y", 'xt')
- call assert_equal([0, 1, 4, 0], getpos("'<"))
- call assert_equal([0, 3, 13, 0], getpos("'>"))
- " Test for <S-C-Left> and <S-C-Right>
- call cursor(2, 5)
- call feedkeys("\<S-C-Right>y", 'xt')
- call assert_equal([0, 2, 5, 0], getpos("'<"))
- call assert_equal([0, 2, 9, 0], getpos("'>"))
- call cursor(2, 9)
- call feedkeys("\<S-C-Left>y", 'xt')
- call assert_equal([0, 2, 5, 0], getpos("'<"))
- call assert_equal([0, 2, 9, 0], getpos("'>"))
-
- set keymodel&
-
- " clean up
- bw!
-endfunc
-
-func Test_normal03_join()
- " basic join test
- call Setup_NewWindow()
- 50
- norm! VJ
- call assert_equal('50 51', getline('.'))
- $
- norm! J
- call assert_equal('100', getline('.'))
- $
- norm! V9-gJ
- call assert_equal('919293949596979899100', getline('.'))
- call setline(1, range(1,100))
- $
- :j 10
- call assert_equal('100', getline('.'))
- call assert_beeps('normal GVJ')
- " clean up
- bw!
-endfunc
-
-" basic filter test
-func Test_normal04_filter()
- " only test on non windows platform
- CheckNotMSWindows
- call Setup_NewWindow()
- 1
- call feedkeys("!!sed -e 's/^/| /'\n", 'tx')
- call assert_equal('| 1', getline('.'))
- 90
- :sil :!echo one
- call feedkeys('.', 'tx')
- call assert_equal('| 90', getline('.'))
- 95
- set cpo+=!
- " 2 <CR>, 1: for executing the command,
- " 2: clear hit-enter-prompt
- call feedkeys("!!\n", 'tx')
- call feedkeys(":!echo one\n\n", 'tx')
- call feedkeys(".", 'tx')
- call assert_equal('one', getline('.'))
- set cpo-=!
- bw!
-endfunc
-
-func Test_normal05_formatexpr()
- " basic formatexpr test
- call Setup_NewWindow()
- %d_
- call setline(1, ['here: 1 ', '2', 'here: 3 ', '4', 'not here: '])
- 1
- set formatexpr=MyFormatExpr()
- norm! gqG
- call assert_equal(['here: 1->$', '2', 'here: 3->$', '4', 'not here: '], getline(1,'$'))
- set formatexpr=
- bw!
-endfunc
-
-func Test_normal05_formatexpr_newbuf()
- " Edit another buffer in the 'formatexpr' function
- new
- func! Format()
- edit another
- endfunc
- set formatexpr=Format()
- norm gqG
- bw!
- set formatexpr=
-endfunc
-
-func Test_normal05_formatexpr_setopt()
- " Change the 'formatexpr' value in the function
- new
- func! Format()
- set formatexpr=
- endfunc
- set formatexpr=Format()
- norm gqG
- bw!
- set formatexpr=
-endfunc
-
-" When 'formatexpr' returns non-zero, internal formatting is used.
-func Test_normal_formatexpr_returns_nonzero()
- new
- call setline(1, ['one', 'two'])
- func! Format()
- return 1
- endfunc
- setlocal formatexpr=Format()
- normal VGgq
- call assert_equal(['one two'], getline(1, '$'))
- setlocal formatexpr=
- delfunc Format
- close!
-endfunc
-
-" Test for using a script-local function for 'formatexpr'
-func Test_formatexpr_scriptlocal_func()
- func! s:Format()
- let g:FormatArgs = [v:lnum, v:count]
- endfunc
- set formatexpr=s:Format()
- call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
- new | only
- call setline(1, range(1, 40))
- let g:FormatArgs = []
- normal! 2GVjgq
- call assert_equal([2, 2], g:FormatArgs)
- bw!
- set formatexpr=<SID>Format()
- call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
- new | only
- call setline(1, range(1, 40))
- let g:FormatArgs = []
- normal! 4GVjgq
- call assert_equal([4, 2], g:FormatArgs)
- bw!
- let &formatexpr = 's:Format()'
- new | only
- call setline(1, range(1, 40))
- let g:FormatArgs = []
- normal! 6GVjgq
- call assert_equal([6, 2], g:FormatArgs)
- bw!
- let &formatexpr = '<SID>Format()'
- new | only
- call setline(1, range(1, 40))
- let g:FormatArgs = []
- normal! 8GVjgq
- call assert_equal([8, 2], g:FormatArgs)
- setlocal formatexpr=
- delfunc s:Format
- bw!
-endfunc
-
-" basic test for formatprg
-func Test_normal06_formatprg()
- " only test on non windows platform
- CheckNotMSWindows
-
- " uses sed to number non-empty lines
- call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh')
- call system('chmod +x ./Xsed_format.sh')
- let text = ['a', '', 'c', '', ' ', 'd', 'e']
- let expected = ['1 a', '', '3 c', '', '5 ', '6 d', '7 e']
-
- 10new
- call setline(1, text)
- set formatprg=./Xsed_format.sh
- norm! gggqG
- call assert_equal(expected, getline(1, '$'))
- %d
-
- call setline(1, text)
- set formatprg=donothing
- setlocal formatprg=./Xsed_format.sh
- norm! gggqG
- call assert_equal(expected, getline(1, '$'))
- %d
-
- " Check for the command-line ranges added to 'formatprg'
- set formatprg=cat
- call setline(1, ['one', 'two', 'three', 'four', 'five'])
- call feedkeys('gggqG', 'xt')
- call assert_equal('.,$!cat', @:)
- call feedkeys('2Ggq2j', 'xt')
- call assert_equal('.,.+2!cat', @:)
-
- bw!
- " clean up
- set formatprg=
- setlocal formatprg=
- call delete('Xsed_format.sh')
-endfunc
-
-func Test_normal07_internalfmt()
- " basic test for internal formmatter to textwidth of 12
- let list=range(1,11)
- call map(list, 'v:val." "')
- 10new
- call setline(1, list)
- set tw=12
- norm! ggVGgq
- call assert_equal(['1 2 3', '4 5 6', '7 8 9', '10 11 '], getline(1, '$'))
- " clean up
- set tw=0
- bw!
-endfunc
-
-" basic tests for foldopen/folddelete
-func Test_normal08_fold()
- CheckFeature folding
- call Setup_NewWindow()
- 50
- setl foldenable fdm=marker
- " First fold
- norm! V4jzf
- " check that folds have been created
- call assert_equal(['50/*{{{*/', '51', '52', '53', '54/*}}}*/'], getline(50,54))
- " Second fold
- 46
- norm! V10jzf
- " check that folds have been created
- call assert_equal('46/*{{{*/', getline(46))
- call assert_equal('60/*}}}*/', getline(60))
- norm! k
- call assert_equal('45', getline('.'))
- norm! j
- call assert_equal('46/*{{{*/', getline('.'))
- norm! j
- call assert_equal('61', getline('.'))
- norm! k
- " open a fold
- norm! Vzo
- norm! k
- call assert_equal('45', getline('.'))
- norm! j
- call assert_equal('46/*{{{*/', getline('.'))
- norm! j
- call assert_equal('47', getline('.'))
- norm! k
- norm! zcVzO
- call assert_equal('46/*{{{*/', getline('.'))
- norm! j
- call assert_equal('47', getline('.'))
- norm! j
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51', getline('.'))
- " delete folds
- :46
- " collapse fold
- norm! V14jzC
- " delete all folds recursively
- norm! VzD
- call assert_equal(['46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60'], getline(46,60))
-
- " clean up
- setl nofoldenable fdm=marker
- bw!
-endfunc
-
-func Test_normal09a_operatorfunc()
- " Test operatorfunc
- call Setup_NewWindow()
- " Add some spaces for counting
- 50,60s/$/ /
- unlet! g:a
- let g:a=0
- nmap <buffer><silent> ,, :set opfunc=CountSpaces<CR>g@
- vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR>
- 50
- norm V2j,,
- call assert_equal(6, g:a)
- norm V,,
- call assert_equal(2, g:a)
- norm ,,l
- call assert_equal(0, g:a)
- 50
- exe "norm 0\<c-v>10j2l,,"
- call assert_equal(11, g:a)
- 50
- norm V10j,,
- call assert_equal(22, g:a)
-
- " clean up
- unmap <buffer> ,,
- set opfunc=
- unlet! g:a
- bw!
-endfunc
-
-func Test_normal09b_operatorfunc()
- " Test operatorfunc
- call Setup_NewWindow()
- " Add some spaces for counting
- 50,60s/$/ /
- unlet! g:opt
- set linebreak
- nmap <buffer><silent> ,, :set opfunc=OpfuncDummy<CR>g@
- 50
- norm ,,j
- exe "bd!" g:bufnr
- call assert_true(&linebreak)
- call assert_equal(g:opt, &linebreak)
- set nolinebreak
- norm ,,j
- exe "bd!" g:bufnr
- call assert_false(&linebreak)
- call assert_equal(g:opt, &linebreak)
-
- " clean up
- unmap <buffer> ,,
- set opfunc=
- call assert_fails('normal Vg@', 'E774:')
- bw!
- unlet! g:opt
-endfunc
-
-func OperatorfuncRedo(_)
- let g:opfunc_count = v:count
-endfunc
-
-func Underscorize(_)
- normal! '[V']r_
-endfunc
-
-func Test_normal09c_operatorfunc()
- " Test redoing operatorfunc
- new
- call setline(1, 'some text')
- set operatorfunc=OperatorfuncRedo
- normal v3g@
- call assert_equal(3, g:opfunc_count)
- let g:opfunc_count = 0
- normal .
- call assert_equal(3, g:opfunc_count)
-
- bw!
- unlet g:opfunc_count
-
- " Test redoing Visual mode
- set operatorfunc=Underscorize
- new
- call setline(1, ['first', 'first', 'third', 'third', 'second'])
- normal! 1GVjg@
- normal! 5G.
- normal! 3G.
- call assert_equal(['_____', '_____', '_____', '_____', '______'], getline(1, '$'))
- bwipe!
- set operatorfunc=
-endfunc
-
-" Test for different ways of setting the 'operatorfunc' option
-func Test_opfunc_callback()
- new
- func OpFunc1(callnr, type)
- let g:OpFunc1Args = [a:callnr, a:type]
- endfunc
- func OpFunc2(type)
- let g:OpFunc2Args = [a:type]
- endfunc
-
- let lines =<< trim END
- #" Test for using a function name
- LET &opfunc = 'g:OpFunc2'
- LET g:OpFunc2Args = []
- normal! g@l
- call assert_equal(['char'], g:OpFunc2Args)
-
- #" Test for using a function()
- set opfunc=function('g:OpFunc1',\ [10])
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([10, 'char'], g:OpFunc1Args)
-
- #" Using a funcref variable to set 'operatorfunc'
- VAR Fn = function('g:OpFunc1', [11])
- LET &opfunc = Fn
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([11, 'char'], g:OpFunc1Args)
-
- #" Using a string(funcref_variable) to set 'operatorfunc'
- LET Fn = function('g:OpFunc1', [12])
- LET &operatorfunc = string(Fn)
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([12, 'char'], g:OpFunc1Args)
-
- #" Test for using a funcref()
- set operatorfunc=funcref('g:OpFunc1',\ [13])
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([13, 'char'], g:OpFunc1Args)
-
- #" Using a funcref variable to set 'operatorfunc'
- LET Fn = funcref('g:OpFunc1', [14])
- LET &opfunc = Fn
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([14, 'char'], g:OpFunc1Args)
-
- #" Using a string(funcref_variable) to set 'operatorfunc'
- LET Fn = funcref('g:OpFunc1', [15])
- LET &opfunc = string(Fn)
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([15, 'char'], g:OpFunc1Args)
-
- #" Test for using a lambda function using set
- VAR optval = "LSTART a LMIDDLE OpFunc1(16, a) LEND"
- LET optval = substitute(optval, ' ', '\\ ', 'g')
- exe "set opfunc=" .. optval
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([16, 'char'], g:OpFunc1Args)
-
- #" Test for using a lambda function using LET
- LET &opfunc = LSTART a LMIDDLE OpFunc1(17, a) LEND
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([17, 'char'], g:OpFunc1Args)
-
- #" Set 'operatorfunc' to a string(lambda expression)
- LET &opfunc = 'LSTART a LMIDDLE OpFunc1(18, a) LEND'
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([18, 'char'], g:OpFunc1Args)
-
- #" Set 'operatorfunc' to a variable with a lambda expression
- VAR Lambda = LSTART a LMIDDLE OpFunc1(19, a) LEND
- LET &opfunc = Lambda
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([19, 'char'], g:OpFunc1Args)
-
- #" Set 'operatorfunc' to a string(variable with a lambda expression)
- LET Lambda = LSTART a LMIDDLE OpFunc1(20, a) LEND
- LET &opfunc = string(Lambda)
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([20, 'char'], g:OpFunc1Args)
-
- #" Try to use 'operatorfunc' after the function is deleted
- func g:TmpOpFunc1(type)
- let g:TmpOpFunc1Args = [21, a:type]
- endfunc
- LET &opfunc = function('g:TmpOpFunc1')
- delfunc g:TmpOpFunc1
- call test_garbagecollect_now()
- LET g:TmpOpFunc1Args = []
- call assert_fails('normal! g@l', 'E117:')
- call assert_equal([], g:TmpOpFunc1Args)
-
- #" Try to use a function with two arguments for 'operatorfunc'
- func g:TmpOpFunc2(x, y)
- let g:TmpOpFunc2Args = [a:x, a:y]
- endfunc
- set opfunc=TmpOpFunc2
- LET g:TmpOpFunc2Args = []
- call assert_fails('normal! g@l', 'E119:')
- call assert_equal([], g:TmpOpFunc2Args)
- delfunc TmpOpFunc2
-
- #" Try to use a lambda function with two arguments for 'operatorfunc'
- LET &opfunc = LSTART a, b LMIDDLE OpFunc1(22, b) LEND
- LET g:OpFunc1Args = []
- call assert_fails('normal! g@l', 'E119:')
- call assert_equal([], g:OpFunc1Args)
-
- #" Test for clearing the 'operatorfunc' option
- set opfunc=''
- set opfunc&
- call assert_fails("set opfunc=function('abc')", "E700:")
- call assert_fails("set opfunc=funcref('abc')", "E700:")
-
- #" set 'operatorfunc' to a non-existing function
- LET &opfunc = function('g:OpFunc1', [23])
- call assert_fails("set opfunc=function('NonExistingFunc')", 'E700:')
- call assert_fails("LET &opfunc = function('NonExistingFunc')", 'E700:')
- LET g:OpFunc1Args = []
- normal! g@l
- call assert_equal([23, 'char'], g:OpFunc1Args)
- END
- call CheckTransLegacySuccess(lines)
-
- " Test for using a script-local function name
- func s:OpFunc3(type)
- let g:OpFunc3Args = [a:type]
- endfunc
- set opfunc=s:OpFunc3
- let g:OpFunc3Args = []
- normal! g@l
- call assert_equal(['char'], g:OpFunc3Args)
-
- let &opfunc = 's:OpFunc3'
- let g:OpFunc3Args = []
- normal! g@l
- call assert_equal(['char'], g:OpFunc3Args)
- delfunc s:OpFunc3
-
- " Using Vim9 lambda expression in legacy context should fail
- " set opfunc=(a)\ =>\ OpFunc1(24,\ a)
- let g:OpFunc1Args = []
- " call assert_fails('normal! g@l', 'E117:')
- call assert_equal([], g:OpFunc1Args)
-
- " set 'operatorfunc' to a partial with dict. This used to cause a crash.
- func SetOpFunc()
- let operator = {'execute': function('OperatorExecute')}
- let &opfunc = operator.execute
- endfunc
- func OperatorExecute(_) dict
- endfunc
- call SetOpFunc()
- call test_garbagecollect_now()
- set operatorfunc=
- delfunc SetOpFunc
- delfunc OperatorExecute
-
- " Vim9 tests
- let lines =<< trim END
- vim9script
-
- def g:Vim9opFunc(val: number, type: string): void
- g:OpFunc1Args = [val, type]
- enddef
-
- # Test for using a def function with opfunc
- set opfunc=function('g:Vim9opFunc',\ [60])
- g:OpFunc1Args = []
- normal! g@l
- assert_equal([60, 'char'], g:OpFunc1Args)
-
- # Test for using a global function name
- &opfunc = g:OpFunc2
- g:OpFunc2Args = []
- normal! g@l
- assert_equal(['char'], g:OpFunc2Args)
- bw!
-
- # Test for using a script-local function name
- def s:LocalOpFunc(type: string): void
- g:LocalOpFuncArgs = [type]
- enddef
- &opfunc = s:LocalOpFunc
- g:LocalOpFuncArgs = []
- normal! g@l
- assert_equal(['char'], g:LocalOpFuncArgs)
- bw!
- END
- call CheckScriptSuccess(lines)
-
- " setting 'opfunc' to a script local function outside of a script context
- " should fail
- let cleanup =<< trim END
- call writefile([execute('messages')], 'Xtest.out')
- qall
- END
- call writefile(cleanup, 'Xverify.vim')
- call RunVim([], [], "-c \"set opfunc=s:abc\" -S Xverify.vim")
- call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
- call delete('Xtest.out')
- call delete('Xverify.vim')
-
- " cleanup
- set opfunc&
- delfunc OpFunc1
- delfunc OpFunc2
- unlet g:OpFunc1Args g:OpFunc2Args
- %bw!
-endfunc
-
-func Test_normal10_expand()
- " Test for expand()
- 10new
- call setline(1, ['1', 'ifooar,,cbar'])
- 2
- norm! $
- call assert_equal('cbar', expand('<cword>'))
- call assert_equal('ifooar,,cbar', expand('<cWORD>'))
-
- call setline(1, ['prx = list[idx];'])
- 1
- let expected = ['', 'prx', 'prx', 'prx',
- \ 'list', 'list', 'list', 'list', 'list', 'list', 'list',
- \ 'idx', 'idx', 'idx', 'idx',
- \ 'list[idx]',
- \ '];',
- \ ]
- for i in range(1, 16)
- exe 'norm ' . i . '|'
- call assert_equal(expected[i], expand('<cexpr>'), 'i == ' . i)
- endfor
-
- " Test for <cexpr> in state.val and ptr->val
- call setline(1, 'x = state.val;')
- call cursor(1, 10)
- call assert_equal('state.val', expand('<cexpr>'))
- call setline(1, 'x = ptr->val;')
- call cursor(1, 9)
- call assert_equal('ptr->val', expand('<cexpr>'))
-
- if executable('echo')
- " Test expand(`...`) i.e. backticks command expansion.
- " MS-Windows has a trailing space.
- call assert_match('^abcde *$', expand('`echo abcde`'))
- endif
-
- " Test expand(`=...`) i.e. backticks expression expansion
- call assert_equal('5', expand('`=2+3`'))
- call assert_equal('3.14', expand('`=3.14`'))
-
- " clean up
- bw!
-endfunc
-
-" Test for expand() in latin1 encoding
-func Test_normal_expand_latin1()
- new
- let save_enc = &encoding
- " set encoding=latin1
- call setline(1, 'val = item->color;')
- call cursor(1, 11)
- call assert_equal('color', expand("<cword>"))
- call assert_equal('item->color', expand("<cexpr>"))
- let &encoding = save_enc
- bw!
-endfunc
-
-func Test_normal11_showcmd()
- " test for 'showcmd'
- 10new
- exe "norm! ofoobar\<esc>"
- call assert_equal(2, line('$'))
- set showcmd
- exe "norm! ofoobar2\<esc>"
- call assert_equal(3, line('$'))
- exe "norm! VAfoobar3\<esc>"
- call assert_equal(3, line('$'))
- exe "norm! 0d3\<del>2l"
- call assert_equal('obar2foobar3', getline('.'))
- " test for the visual block size displayed in the status line
- call setline(1, ['aaaaa', 'bbbbb', 'ccccc'])
- call feedkeys("ggl\<C-V>lljj", 'xt')
- redraw!
- call assert_match('3x3$', Screenline(&lines))
- call feedkeys("\<C-V>", 'xt')
- " test for visually selecting a multi-byte character
- call setline(1, ["\U2206"])
- call feedkeys("ggv", 'xt')
- redraw!
- call assert_match('1-3$', Screenline(&lines))
- call feedkeys("v", 'xt')
- " test for visually selecting the end of line
- call setline(1, ["foobar"])
- call feedkeys("$vl", 'xt')
- redraw!
- call assert_match('2$', Screenline(&lines))
- call feedkeys("y", 'xt')
- call assert_equal("r\n", @")
- bw!
-endfunc
-
-" Test for nv_error and normal command errors
-func Test_normal12_nv_error()
- 10new
- call setline(1, range(1,5))
- " should not do anything, just beep
- call assert_beeps('exe "norm! <c-k>"')
- call assert_equal(map(range(1,5), 'string(v:val)'), getline(1,'$'))
- call assert_beeps('normal! G2dd')
- call assert_beeps("normal! g\<C-A>")
- call assert_beeps("normal! g\<C-X>")
- call assert_beeps("normal! g\<C-B>")
- " call assert_beeps("normal! vQ\<Esc>")
- call assert_beeps("normal! 2[[")
- call assert_beeps("normal! 2]]")
- call assert_beeps("normal! 2[]")
- call assert_beeps("normal! 2][")
- call assert_beeps("normal! 4[z")
- call assert_beeps("normal! 4]z")
- call assert_beeps("normal! 4[c")
- call assert_beeps("normal! 4]c")
- call assert_beeps("normal! 200%")
- call assert_beeps("normal! %")
- call assert_beeps("normal! 2{")
- call assert_beeps("normal! 2}")
- call assert_beeps("normal! r\<Right>")
- call assert_beeps("normal! 8ry")
- call assert_beeps('normal! "@')
- bw!
-endfunc
-
-func Test_normal13_help()
- " Test for F1
- call assert_equal(1, winnr())
- call feedkeys("\<f1>", 'txi')
- call assert_match('help\.txt', bufname('%'))
- call assert_equal(2, winnr('$'))
- bw!
-endfunc
-
-func Test_normal14_page()
- " basic test for Ctrl-F and Ctrl-B
- call Setup_NewWindow()
- exe "norm! \<c-f>"
- call assert_equal('9', getline('.'))
- exe "norm! 2\<c-f>"
- call assert_equal('25', getline('.'))
- exe "norm! 2\<c-b>"
- call assert_equal('18', getline('.'))
- 1
- set scrolloff=5
- exe "norm! 2\<c-f>"
- call assert_equal('21', getline('.'))
- exe "norm! \<c-b>"
- call assert_equal('13', getline('.'))
- 1
- set scrolloff=99
- exe "norm! \<c-f>"
- call assert_equal('13', getline('.'))
- set scrolloff=0
- 100
- exe "norm! $\<c-b>"
- call assert_equal('92', getline('.'))
- call assert_equal([0, 92, 1, 0, 1], getcurpos())
- 100
- set nostartofline
- exe "norm! $\<c-b>"
- call assert_equal('92', getline('.'))
- call assert_equal([0, 92, 2, 0, 2147483647], getcurpos())
- " cleanup
- set startofline
- bw!
-endfunc
-
-func Test_normal14_page_eol()
- 10new
- norm oxxxxxxx
- exe "norm 2\<c-f>"
- " check with valgrind that cursor is put back in column 1
- exe "norm 2\<c-b>"
- bw!
-endfunc
-
-" Test for errors with z command
-func Test_normal_z_error()
- call assert_beeps('normal! z2p')
- call assert_beeps('normal! zq')
- call assert_beeps('normal! cz1')
-endfunc
-
-func Test_normal15_z_scroll_vert()
- " basic test for z commands that scroll the window
- call Setup_NewWindow()
- 100
- norm! >>
- " Test for z<cr>
- exe "norm! z\<cr>"
- call assert_equal(' 100', getline('.'))
- call assert_equal(100, winsaveview()['topline'])
- call assert_equal([0, 100, 2, 0, 9], getcurpos())
-
- " Test for zt
- 21
- norm! >>0zt
- call assert_equal(' 21', getline('.'))
- call assert_equal(21, winsaveview()['topline'])
- call assert_equal([0, 21, 1, 0, 8], getcurpos())
-
- " Test for zb
- 30
- norm! >>$ztzb
- call assert_equal(' 30', getline('.'))
- call assert_equal(30, winsaveview()['topline']+winheight(0)-1)
- call assert_equal([0, 30, 3, 0, 2147483647], getcurpos())
-
- " Test for z-
- 1
- 30
- norm! 0z-
- call assert_equal(' 30', getline('.'))
- call assert_equal(30, winsaveview()['topline']+winheight(0)-1)
- call assert_equal([0, 30, 2, 0, 9], getcurpos())
-
- " Test for z{height}<cr>
- call assert_equal(10, winheight(0))
- exe "norm! z12\<cr>"
- call assert_equal(12, winheight(0))
- exe "norm! z15\<Del>0\<cr>"
- call assert_equal(10, winheight(0))
-
- " Test for z.
- 1
- 21
- norm! 0z.
- call assert_equal(' 21', getline('.'))
- call assert_equal(17, winsaveview()['topline'])
- call assert_equal([0, 21, 2, 0, 9], getcurpos())
-
- " Test for zz
- 1
- 21
- norm! 0zz
- call assert_equal(' 21', getline('.'))
- call assert_equal(17, winsaveview()['topline'])
- call assert_equal([0, 21, 1, 0, 8], getcurpos())
-
- " Test for z+
- 11
- norm! zt
- norm! z+
- call assert_equal(' 21', getline('.'))
- call assert_equal(21, winsaveview()['topline'])
- call assert_equal([0, 21, 2, 0, 9], getcurpos())
-
- " Test for [count]z+
- 1
- norm! 21z+
- call assert_equal(' 21', getline('.'))
- call assert_equal(21, winsaveview()['topline'])
- call assert_equal([0, 21, 2, 0, 9], getcurpos())
-
- " Test for z+ with [count] greater than buffer size
- 1
- norm! 1000z+
- call assert_equal(' 100', getline('.'))
- call assert_equal(100, winsaveview()['topline'])
- call assert_equal([0, 100, 2, 0, 9], getcurpos())
-
- " Test for z+ from the last buffer line
- norm! Gz.z+
- call assert_equal(' 100', getline('.'))
- call assert_equal(100, winsaveview()['topline'])
- call assert_equal([0, 100, 2, 0, 9], getcurpos())
-
- " Test for z^
- norm! 22z+0
- norm! z^
- call assert_equal(' 21', getline('.'))
- call assert_equal(12, winsaveview()['topline'])
- call assert_equal([0, 21, 2, 0, 9], getcurpos())
-
- " Test for z^ from first buffer line
- norm! ggz^
- call assert_equal('1', getline('.'))
- call assert_equal(1, winsaveview()['topline'])
- call assert_equal([0, 1, 1, 0, 1], getcurpos())
-
- " Test for [count]z^
- 1
- norm! 30z^
- call assert_equal(' 21', getline('.'))
- call assert_equal(12, winsaveview()['topline'])
- call assert_equal([0, 21, 2, 0, 9], getcurpos())
-
- " cleanup
- bw!
-endfunc
-
-func Test_normal16_z_scroll_hor()
- " basic test for z commands that scroll the window
- 10new
- 15vsp
- set nowrap listchars=
- let lineA='abcdefghijklmnopqrstuvwxyz'
- let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- $put =lineA
- $put =lineB
- 1d
-
- " Test for zl and zh with a count
- norm! 0z10l
- call assert_equal([11, 1], [col('.'), wincol()])
- norm! z4h
- call assert_equal([11, 5], [col('.'), wincol()])
- normal! 2gg
-
- " Test for zl
- 1
- norm! 5zl
- call assert_equal(lineA, getline('.'))
- call assert_equal(6, col('.'))
- call assert_equal(5, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('f', @0)
-
- " Test for zh
- norm! 2zh
- call assert_equal(lineA, getline('.'))
- call assert_equal(6, col('.'))
- norm! yl
- call assert_equal('f', @0)
- call assert_equal(3, winsaveview()['leftcol'])
-
- " Test for zL
- norm! zL
- call assert_equal(11, col('.'))
- norm! yl
- call assert_equal('k', @0)
- call assert_equal(10, winsaveview()['leftcol'])
- norm! 2zL
- call assert_equal(25, col('.'))
- norm! yl
- call assert_equal('y', @0)
- call assert_equal(24, winsaveview()['leftcol'])
-
- " Test for zH
- norm! 2zH
- call assert_equal(25, col('.'))
- call assert_equal(10, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('y', @0)
-
- " Test for zs
- norm! $zs
- call assert_equal(26, col('.'))
- call assert_equal(25, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('z', @0)
-
- " Test for ze
- norm! ze
- call assert_equal(26, col('.'))
- call assert_equal(11, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('z', @0)
-
- " Test for zs and ze with folds
- %fold
- norm! $zs
- call assert_equal(26, col('.'))
- call assert_equal(0, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('z', @0)
- norm! ze
- call assert_equal(26, col('.'))
- call assert_equal(0, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('z', @0)
-
- " cleanup
- set wrap listchars=eol:$
- bw!
-endfunc
-
-func Test_normal17_z_scroll_hor2()
- " basic test for z commands that scroll the window
- " using 'sidescrolloff' setting
- 10new
- 20vsp
- set nowrap listchars= sidescrolloff=5
- let lineA='abcdefghijklmnopqrstuvwxyz'
- let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- $put =lineA
- $put =lineB
- 1d
-
- " Test for zl
- 1
- norm! 5zl
- call assert_equal(lineA, getline('.'))
- call assert_equal(11, col('.'))
- call assert_equal(5, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('k', @0)
-
- " Test for zh
- norm! 2zh
- call assert_equal(lineA, getline('.'))
- call assert_equal(11, col('.'))
- norm! yl
- call assert_equal('k', @0)
- call assert_equal(3, winsaveview()['leftcol'])
-
- " Test for zL
- norm! 0zL
- call assert_equal(16, col('.'))
- norm! yl
- call assert_equal('p', @0)
- call assert_equal(10, winsaveview()['leftcol'])
- norm! 2zL
- call assert_equal(26, col('.'))
- norm! yl
- call assert_equal('z', @0)
- call assert_equal(15, winsaveview()['leftcol'])
-
- " Test for zH
- norm! 2zH
- call assert_equal(15, col('.'))
- call assert_equal(0, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('o', @0)
-
- " Test for zs
- norm! $zs
- call assert_equal(26, col('.'))
- call assert_equal(20, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('z', @0)
-
- " Test for ze
- norm! ze
- call assert_equal(26, col('.'))
- call assert_equal(11, winsaveview()['leftcol'])
- norm! yl
- call assert_equal('z', @0)
-
- " cleanup
- set wrap listchars=eol:$ sidescrolloff=0
- bw!
-endfunc
-
-" Test for commands that scroll the window horizontally. Test with folds.
-" H, M, L, CTRL-E, CTRL-Y, CTRL-U, CTRL-D, PageUp, PageDown commands
-func Test_vert_scroll_cmds()
- 15new
- call setline(1, range(1, 100))
- exe "normal! 30ggz\<CR>"
- set foldenable
- 33,36fold
- 40,43fold
- 46,49fold
- let h = winheight(0)
-
- " Test for H, M and L commands
- " Top of the screen = 30
- " Folded lines = 9
- " Bottom of the screen = 30 + h + 9 - 1
- normal! 4L
- call assert_equal(35 + h, line('.'))
- normal! 4H
- call assert_equal(33, line('.'))
-
- " Test for using a large count value
- %d
- call setline(1, range(1, 4))
- norm! 6H
- call assert_equal(4, line('.'))
-
- " Test for 'M' with folded lines
- %d
- call setline(1, range(1, 20))
- 1,5fold
- norm! LM
- call assert_equal(12, line('.'))
-
- " Test for the CTRL-E and CTRL-Y commands with folds
- %d
- call setline(1, range(1, 10))
- 3,5fold
- exe "normal 6G3\<C-E>"
- call assert_equal(6, line('w0'))
- exe "normal 2\<C-Y>"
- call assert_equal(2, line('w0'))
-
- " Test for CTRL-Y on a folded line
- %d
- call setline(1, range(1, 100))
- exe (h + 2) .. "," .. (h + 4) .. "fold"
- exe h + 5
- normal z-
- exe "normal \<C-Y>\<C-Y>"
- call assert_equal(h + 1, line('w$'))
-
- " Test for CTRL-Y from the first line and CTRL-E from the last line
- %d
- set scrolloff=2
- call setline(1, range(1, 4))
- exe "normal gg\<C-Y>"
- call assert_equal(1, line('w0'))
- call assert_equal(1, line('.'))
- exe "normal G4\<C-E>\<C-E>"
- call assert_equal(4, line('w$'))
- call assert_equal(4, line('.'))
- set scrolloff&
-
- " Using <PageUp> and <PageDown> in an empty buffer should beep
- %d
- call assert_beeps('exe "normal \<PageUp>"')
- call assert_beeps('exe "normal \<C-B>"')
- call assert_beeps('exe "normal \<PageDown>"')
- call assert_beeps('exe "normal \<C-F>"')
-
- " Test for <C-U> and <C-D> with fold
- %d
- call setline(1, range(1, 100))
- 10,35fold
- set scroll=10
- exe "normal \<C-D>"
- call assert_equal(36, line('.'))
- exe "normal \<C-D>"
- call assert_equal(46, line('.'))
- exe "normal \<C-U>"
- call assert_equal(36, line('.'))
- exe "normal \<C-U>"
- call assert_equal(10, line('.'))
- exe "normal \<C-U>"
- call assert_equal(1, line('.'))
- set scroll&
-
- " Test for scrolling to the top of the file with <C-U> and a fold
- 10
- normal ztL
- exe "normal \<C-U>\<C-U>"
- call assert_equal(1, line('w0'))
-
- " Test for CTRL-D on a folded line
- %d
- call setline(1, range(1, 100))
- 50,100fold
- 75
- normal z-
- exe "normal \<C-D>"
- call assert_equal(50, line('.'))
- call assert_equal(100, line('w$'))
- normal z.
- let lnum = winline()
- exe "normal \<C-D>"
- call assert_equal(lnum, winline())
- call assert_equal(50, line('.'))
- normal zt
- exe "normal \<C-D>"
- call assert_equal(50, line('w0'))
-
- " Test for <S-CR>. Page down.
- %d
- call setline(1, range(1, 100))
- call feedkeys("\<S-CR>", 'xt')
- call assert_equal(14, line('w0'))
- call assert_equal(28, line('w$'))
-
- " Test for <S-->. Page up.
- call feedkeys("\<S-->", 'xt')
- call assert_equal(1, line('w0'))
- call assert_equal(15, line('w$'))
-
- set foldenable&
- close!
-endfunc
-
-func Test_scroll_in_ex_mode()
- " This was using invalid memory because w_botline was invalid.
- let lines =<< trim END
- diffsplit
- norm os00(
- call writefile(['done'], 'Xdone')
- qa!
- END
- call writefile(lines, 'Xscript')
- call assert_equal(1, RunVim([], [], '--clean -X -Z -e -s -S Xscript'))
- call assert_equal(['done'], readfile('Xdone'))
-
- call delete('Xscript')
- call delete('Xdone')
-endfunc
-
-" Test for the 'sidescroll' option
-func Test_sidescroll_opt()
- new
- 20vnew
-
- " scroll by 2 characters horizontally
- set sidescroll=2 nowrap
- call setline(1, repeat('a', 40))
- normal g$l
- call assert_equal(19, screenpos(0, 1, 21).col)
- normal l
- call assert_equal(20, screenpos(0, 1, 22).col)
- normal g0h
- call assert_equal(2, screenpos(0, 1, 2).col)
- call assert_equal(20, screenpos(0, 1, 20).col)
-
- " when 'sidescroll' is 0, cursor positioned at the center
- set sidescroll=0
- normal g$l
- call assert_equal(11, screenpos(0, 1, 21).col)
- normal g0h
- call assert_equal(10, screenpos(0, 1, 10).col)
-
- %bw!
- set wrap& sidescroll&
-endfunc
-
-" basic tests for foldopen/folddelete
-func Test_normal18_z_fold()
- CheckFeature folding
- call Setup_NewWindow()
- 50
- setl foldenable fdm=marker foldlevel=5
-
- call assert_beeps('normal! zj')
- call assert_beeps('normal! zk')
-
- " Test for zF
- " First fold
- norm! 4zF
- " check that folds have been created
- call assert_equal(['50/*{{{*/', '51', '52', '53/*}}}*/'], getline(50,53))
-
- " Test for zd
- 51
- norm! 2zF
- call assert_equal(2, foldlevel('.'))
- norm! kzd
- call assert_equal(['50', '51/*{{{*/', '52/*}}}*/', '53'], getline(50,53))
- norm! j
- call assert_equal(1, foldlevel('.'))
-
- " Test for zD
- " also deletes partially selected folds recursively
- 51
- norm! zF
- call assert_equal(2, foldlevel('.'))
- norm! kV2jzD
- call assert_equal(['50', '51', '52', '53'], getline(50,53))
-
- " Test for zE
- 85
- norm! 4zF
- 86
- norm! 2zF
- 90
- norm! 4zF
- call assert_equal(['85/*{{{*/', '86/*{{{*/', '87/*}}}*/', '88/*}}}*/', '89', '90/*{{{*/', '91', '92', '93/*}}}*/'], getline(85,93))
- norm! zE
- call assert_equal(['85', '86', '87', '88', '89', '90', '91', '92', '93'], getline(85,93))
-
- " Test for zn
- 50
- set foldlevel=0
- norm! 2zF
- norm! zn
- norm! k
- call assert_equal('49', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- call assert_equal(0, &foldenable)
-
- " Test for zN
- 49
- norm! zN
- call assert_equal('49', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- call assert_equal(1, &foldenable)
-
- " Test for zi
- norm! zi
- call assert_equal(0, &foldenable)
- norm! zi
- call assert_equal(1, &foldenable)
- norm! zi
- call assert_equal(0, &foldenable)
- norm! zi
- call assert_equal(1, &foldenable)
-
- " Test for za
- 50
- norm! za
- norm! k
- call assert_equal('49', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- 50
- norm! za
- norm! k
- call assert_equal('49', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
-
- 49
- norm! 5zF
- norm! k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
- 49
- norm! za
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- set nofoldenable
- " close fold and set foldenable
- norm! za
- call assert_equal(1, &foldenable)
-
- 50
- " have to use {count}za to open all folds and make the cursor visible
- norm! 2za
- norm! 2k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
-
- " Test for zA
- 49
- set foldlevel=0
- 50
- norm! zA
- norm! 2k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
-
- " zA on a opened fold when foldenable is not set
- 50
- set nofoldenable
- norm! zA
- call assert_equal(1, &foldenable)
- norm! k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " Test for zc
- norm! zE
- 50
- norm! 2zF
- 49
- norm! 5zF
- set nofoldenable
- 50
- " There most likely is a bug somewhere:
- " https://groups.google.com/d/msg/vim_dev/v2EkfJ_KQjI/u-Cvv94uCAAJ
- " TODO: Should this only close the inner most fold or both folds?
- norm! zc
- call assert_equal(1, &foldenable)
- norm! k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
- set nofoldenable
- 50
- norm! Vjzc
- norm! k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " Test for zC
- set nofoldenable
- 50
- norm! zCk
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " Test for zx
- " 1) close folds at line 49-54
- set nofoldenable
- 48
- norm! zx
- call assert_equal(1, &foldenable)
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " 2) do not close fold under cursor
- 51
- set nofoldenable
- norm! zx
- call assert_equal(1, &foldenable)
- norm! 3k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- norm! j
- call assert_equal('53', getline('.'))
- norm! j
- call assert_equal('54/*}}}*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " 3) close one level of folds
- 48
- set nofoldenable
- set foldlevel=1
- norm! zx
- call assert_equal(1, &foldenable)
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- norm! j
- call assert_equal('53', getline('.'))
- norm! j
- call assert_equal('54/*}}}*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " Test for zX
- " Close all folds
- set foldlevel=0 nofoldenable
- 50
- norm! zX
- call assert_equal(1, &foldenable)
- norm! k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " Test for zm
- 50
- set nofoldenable foldlevel=2
- norm! zm
- call assert_equal(1, &foldenable)
- call assert_equal(1, &foldlevel)
- norm! zm
- call assert_equal(0, &foldlevel)
- norm! zm
- call assert_equal(0, &foldlevel)
- norm! k
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " Test for zm with a count
- 50
- set foldlevel=2
- norm! 3zm
- call assert_equal(0, &foldlevel)
- call assert_equal(49, foldclosed(line('.')))
-
- " Test for zM
- 48
- set nofoldenable foldlevel=99
- norm! zM
- call assert_equal(1, &foldenable)
- call assert_equal(0, &foldlevel)
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('55', getline('.'))
-
- " Test for zr
- 48
- set nofoldenable foldlevel=0
- norm! zr
- call assert_equal(0, &foldenable)
- call assert_equal(1, &foldlevel)
- set foldlevel=0 foldenable
- norm! zr
- call assert_equal(1, &foldenable)
- call assert_equal(1, &foldlevel)
- norm! zr
- call assert_equal(2, &foldlevel)
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
-
- " Test for zR
- 48
- set nofoldenable foldlevel=0
- norm! zR
- call assert_equal(0, &foldenable)
- call assert_equal(2, &foldlevel)
- set foldenable foldlevel=0
- norm! zR
- call assert_equal(1, &foldenable)
- call assert_equal(2, &foldlevel)
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- call append(50, ['a /*{{{*/', 'b /*}}}*/'])
- 48
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('a /*{{{*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
- 48
- norm! zR
- call assert_equal(1, &foldenable)
- call assert_equal(3, &foldlevel)
- call assert_equal('48', getline('.'))
- norm! j
- call assert_equal('49/*{{{*/', getline('.'))
- norm! j
- call assert_equal('50/*{{{*/', getline('.'))
- norm! j
- call assert_equal('a /*{{{*/', getline('.'))
- norm! j
- call assert_equal('b /*}}}*/', getline('.'))
- norm! j
- call assert_equal('51/*}}}*/', getline('.'))
- norm! j
- call assert_equal('52', getline('.'))
-
- " clean up
- setl nofoldenable fdm=marker foldlevel=0
- bw!
-endfunc
-
-func Test_normal20_exmode()
- " Reading from redirected file doesn't work on MS-Windows
- CheckNotMSWindows
- call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript')
- call writefile(['1', '2'], 'Xfile')
- call system(GetVimCommand() .. ' -e -s < Xscript Xfile')
- let a=readfile('Xfile2')
- call assert_equal(['1', 'foo', 'bar', '2'], a)
-
- " clean up
- for file in ['Xfile', 'Xfile2', 'Xscript']
- call delete(file)
- endfor
- bw!
-endfunc
-
-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')
- call assert_fails("normal i\<C-R>#", '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()
- " Test for ZZ
- " let shell = &shell
- " let &shell = 'sh'
-
- " Remove any stale test files from previous run.
- for file in ['Xfile_Test_normal22_zet']
- call delete(file)
- endfor
-
- call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
- let args = ' -N -i NONE --noplugins -X --headless'
- call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet')
- let a = readfile('Xfile_Test_normal22_zet')
- call assert_equal([], a)
- " Test for ZQ
- call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
- call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet')
- let a = readfile('Xfile_Test_normal22_zet')
- call assert_equal(['1', '2'], a)
-
- " Unsupported Z command
- call assert_beeps('normal! ZW')
-
- " Nvim: This sometimes hangs the TSAN build.
- " for file in ['Xfile_Test_normal22_zet']
- " call delete(file)
- " endfor
- " let &shell = shell
-endfunc
-
-func Test_normal23_K()
- " Test for K command
- new
- call append(0, ['helphelp.txt', 'man', 'aa%bb', 'cc|dd'])
- let k = &keywordprg
- set keywordprg=:help
- 1
- norm! VK
- call assert_equal('helphelp.txt', fnamemodify(bufname('%'), ':t'))
- call assert_equal('help', &ft)
- call assert_match('\*helphelp.txt\*', getline('.'))
- helpclose
- norm! 0K
- call assert_equal('helphelp.txt', fnamemodify(bufname('%'), ':t'))
- call assert_equal('help', &ft)
- call assert_match('Help on help files', getline('.'))
- helpclose
-
- set keywordprg=:new
- set iskeyword+=%
- set iskeyword+=\|
- 2
- norm! K
- call assert_equal('man', fnamemodify(bufname('%'), ':t'))
- bwipe!
- 3
- norm! K
- call assert_equal('aa%bb', fnamemodify(bufname('%'), ':t'))
- bwipe!
- if !has('win32')
- 4
- norm! K
- call assert_equal('cc|dd', fnamemodify(bufname('%'), ':t'))
- bwipe!
- endif
- set iskeyword-=%
- set iskeyword-=\|
-
- " Currently doesn't work in Nvim, see #19436
- " Test for specifying a count to K
- " 1
- " com! -nargs=* Kprog let g:Kprog_Args = <q-args>
- " set keywordprg=:Kprog
- " norm! 3K
- " call assert_equal('3 version8', g:Kprog_Args)
- " delcom Kprog
-
- " Only expect "man" to work on Unix
- if !has("unix") || has('nvim') " Nvim K uses :terminal. #15398
- let &keywordprg = k
- bw!
- return
- endif
-
- 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
- endif
- " Test for using man
- 2
- let a = execute('unsilent norm! K')
- if not_gnu_man
- call assert_match("man -P cat 'man'", a)
- else
- call assert_match("man --pager=cat 'man'", a)
- endif
-
- " Error cases
- call setline(1, '#$#')
- call assert_fails('normal! ggK', 'E349:')
- call setline(1, '---')
- call assert_fails('normal! ggv2lK', 'E349:')
- call setline(1, ['abc', 'xyz'])
- call assert_fails("normal! gg2lv2h\<C-]>", 'E433:')
- call assert_beeps("normal! ggVjK")
-
- " clean up
- let &keywordprg = k
- bw!
-endfunc
-
-func Test_normal24_rot13()
- " Testing for g?? g?g?
- new
- call append(0, 'abcdefghijklmnopqrstuvwxyzäüö')
- 1
- norm! g??
- call assert_equal('nopqrstuvwxyzabcdefghijklmäüö', getline('.'))
- norm! g?g?
- call assert_equal('abcdefghijklmnopqrstuvwxyzäüö', getline('.'))
-
- " clean up
- bw!
-endfunc
-
-func Test_normal25_tag()
- " Testing for CTRL-] g CTRL-] g]
- " CTRL-W g] CTRL-W CTRL-] CTRL-W g CTRL-]
- h
- " Test for CTRL-]
- call search('\<x\>$')
- exe "norm! \<c-]>"
- call assert_equal("change.txt", fnamemodify(bufname('%'), ':t'))
- norm! yiW
- call assert_equal("*x*", @0)
- exe ":norm \<c-o>"
-
- " Test for g_CTRL-]
- call search('\<v_u\>$')
- exe "norm! g\<c-]>"
- call assert_equal("change.txt", fnamemodify(bufname('%'), ':t'))
- norm! yiW
- call assert_equal("*v_u*", @0)
- exe ":norm \<c-o>"
-
- " Test for g]
- call search('\<i_<Esc>$')
- let a = execute(":norm! g]")
- call assert_match('i_<Esc>.*insert.txt', a)
-
- if !empty(exepath('cscope')) && has('cscope')
- " setting cscopetag changes how g] works
- set cst
- exe "norm! g]"
- call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
- norm! yiW
- call assert_equal("*i_<Esc>*", @0)
- exe ":norm \<c-o>"
- " Test for CTRL-W g]
- exe "norm! \<C-W>g]"
- call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
- norm! yiW
- call assert_equal("*i_<Esc>*", @0)
- call assert_equal(3, winnr('$'))
- helpclose
- set nocst
- endif
-
- " Test for CTRL-W g]
- let a = execute("norm! \<C-W>g]")
- call assert_match('i_<Esc>.*insert.txt', a)
-
- " Test for CTRL-W CTRL-]
- exe "norm! \<C-W>\<C-]>"
- call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
- norm! yiW
- call assert_equal("*i_<Esc>*", @0)
- call assert_equal(3, winnr('$'))
- helpclose
-
- " Test for CTRL-W g CTRL-]
- exe "norm! \<C-W>g\<C-]>"
- call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
- norm! yiW
- call assert_equal("*i_<Esc>*", @0)
- call assert_equal(3, winnr('$'))
- helpclose
-
- " clean up
- helpclose
-endfunc
-
-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'])
- 1
- /Error/y a
- 2
- norm! "a]pj"a[p
- call assert_equal(['do', "echo 'Error writing file'", " echo 'Error writing file'", ' ((count++))'], getline(2,5))
- 1
- /^\s\{4}/
- exe "norm! \"a]P3Eldt'"
- exe "norm! j\"a[P2Eldt'"
- call assert_equal([' if [ $? -ne 0 ]; then', " echo 'Error writing'", " echo 'Error'", " echo 'Error writing file'", ' fi'], getline(6,10))
-
- " clean up
- bw!
-endfunc
-
-func Test_normal27_bracket()
- " Test for [' [` ]' ]`
- call Setup_NewWindow()
- 1,21s/.\+/ & b/
- 1
- norm! $ma
- 5
- norm! $mb
- 10
- norm! $mc
- 15
- norm! $md
- 20
- norm! $me
-
- " Test for ['
- 9
- norm! 2['
- call assert_equal(' 1 b', getline('.'))
- call assert_equal(1, line('.'))
- call assert_equal(3, col('.'))
-
- " Test for ]'
- norm! ]'
- call assert_equal(' 5 b', getline('.'))
- call assert_equal(5, line('.'))
- call assert_equal(3, col('.'))
-
- " No mark before line 1, cursor moves to first non-blank on current line
- 1
- norm! 5|['
- call assert_equal(' 1 b', getline('.'))
- call assert_equal(1, line('.'))
- call assert_equal(3, col('.'))
-
- " No mark after line 21, cursor moves to first non-blank on current line
- 21
- norm! 5|]'
- call assert_equal(' 21 b', getline('.'))
- call assert_equal(21, line('.'))
- call assert_equal(3, col('.'))
-
- " Test for [`
- norm! 2[`
- call assert_equal(' 15 b', getline('.'))
- call assert_equal(15, line('.'))
- call assert_equal(8, col('.'))
-
- " Test for ]`
- norm! ]`
- call assert_equal(' 20 b', getline('.'))
- call assert_equal(20, line('.'))
- call assert_equal(8, col('.'))
-
- " No mark before line 1, cursor does not move
- 1
- norm! 5|[`
- call assert_equal(' 1 b', getline('.'))
- call assert_equal(1, line('.'))
- call assert_equal(5, col('.'))
-
- " No mark after line 21, cursor does not move
- 21
- norm! 5|]`
- call assert_equal(' 21 b', getline('.'))
- call assert_equal(21, line('.'))
- call assert_equal(5, col('.'))
-
- " Count too large for [`
- " cursor moves to first lowercase mark
- norm! 99[`
- call assert_equal(' 1 b', getline('.'))
- call assert_equal(1, line('.'))
- call assert_equal(7, col('.'))
-
- " Count too large for ]`
- " cursor moves to last lowercase mark
- norm! 99]`
- call assert_equal(' 20 b', getline('.'))
- call assert_equal(20, line('.'))
- call assert_equal(8, col('.'))
-
- " clean up
- bw!
-endfunc
-
-" Test for ( and ) sentence movements
-func Test_normal28_parenthesis()
- new
- call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here'])
-
- $
- norm! d(
- call assert_equal(['This is a test. With some sentences!', '', 'Even with a question? And one more. ', ''], getline(1, '$'))
- norm! 2d(
- call assert_equal(['This is a test. With some sentences!', '', ' ', ''], getline(1, '$'))
- 1
- norm! 0d)
- call assert_equal(['With some sentences!', '', ' ', ''], getline(1, '$'))
-
- call append('$', ['This is a long sentence', '', 'spanning', 'over several lines. '])
- $
- norm! $d(
- call assert_equal(['With some sentences!', '', ' ', '', 'This is a long sentence', ''], getline(1, '$'))
-
- " Move to the next sentence from a paragraph macro
- %d
- call setline(1, ['.LP', 'blue sky!. blue sky.', 'blue sky. blue sky.'])
- call cursor(1, 1)
- normal )
- call assert_equal([2, 1], [line('.'), col('.')])
- normal )
- call assert_equal([2, 12], [line('.'), col('.')])
- normal ((
- call assert_equal([1, 1], [line('.'), col('.')])
-
- " It is an error if a next sentence is not found
- %d
- call setline(1, '.SH')
- call assert_beeps('normal )')
-
- " If only dot is present, don't treat that as a sentence
- call setline(1, '. This is a sentence.')
- normal $((
- call assert_equal(3, col('.'))
-
- " Jumping to a fold should open the fold
- call setline(1, ['', '', 'one', 'two', 'three'])
- set foldenable
- 2,$fold
- call feedkeys(')', 'xt')
- call assert_equal(3, line('.'))
- call assert_equal(1, foldlevel('.'))
- call assert_equal(-1, foldclosed('.'))
- set foldenable&
-
- " clean up
- bw!
-endfunc
-
-" Test for { and } paragraph movements
-func Test_normal29_brace()
- 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}
-
- 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}
-
- 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{
-
- 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{
-
- 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)
- " Nvim: no "{" flag in 'cpoptions'.
- " set cpo+={
- " 1
- " norm! 0d2}
- " let expected =<< trim [DATA]
- " {
- " This is no paragraph
- " unless the '{' is set
- " in 'cpoptions'
- " }
- " .IP
- " The nroff macros IP separates a paragraph
- " That means, it must be a '.'
- " followed by IP
- " .LPIt does not matter, if afterwards some
- " more characters follow.
- " .SHAlso section boundaries from the nroff
- " macros terminate a paragraph. That means
- " a character like this:
- " .NH
- " End of text here
- "
- " [DATA]
- " call assert_equal(expected, getline(1, '$'))
- "
- " $
- " norm! d}
- " let expected =<< trim [DATA]
- " {
- " This is no paragraph
- " unless the '{' is set
- " in 'cpoptions'
- " }
- " .IP
- " The nroff macros IP separates a paragraph
- " That means, it must be a '.'
- " followed by IP
- " .LPIt does not matter, if afterwards some
- " more characters follow.
- " .SHAlso section boundaries from the nroff
- " macros terminate a paragraph. That means
- " a character like this:
- " .NH
- " End of text here
- "
- " [DATA]
- " call assert_equal(expected, getline(1, '$'))
- "
- " norm! gg}
- " norm! d5}
- "
- " let expected =<< trim [DATA]
- " {
- " This is no paragraph
- " unless the '{' is set
- " in 'cpoptions'
- " }
-
- " [DATA]
- " call assert_equal(expected, getline(1, '$'))
-
- " Jumping to a fold should open the fold
- " %d
- " call setline(1, ['', 'one', 'two', ''])
- " set foldenable
- " 2,$fold
- " call feedkeys('}', 'xt')
- " call assert_equal(4, line('.'))
- " call assert_equal(1, foldlevel('.'))
- " call assert_equal(-1, foldclosed('.'))
- " set foldenable&
-
- " clean up
- set cpo-={
- bw!
-endfunc
-
-" Test for section movements
-func Test_normal_section()
- new
- let lines =<< trim [END]
- int foo()
- {
- if (1)
- {
- a = 1;
- }
- }
- [END]
- call setline(1, lines)
-
- " jumping to a folded line using [[ should open the fold
- 2,3fold
- call cursor(5, 1)
- call feedkeys("[[", 'xt')
- call assert_equal(2, line('.'))
- call assert_equal(-1, foldclosedend(line('.')))
-
- close!
-endfunc
-
-" Test for changing case using u, U, gu, gU and ~ (tilde) commands
-func Test_normal30_changecase()
- new
- call append(0, 'This is a simple test: äüöß')
- norm! 1ggVu
- call assert_equal('this is a simple test: äüöß', getline('.'))
- norm! VU
- call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.'))
- norm! guu
- call assert_equal('this is a simple test: äüöss', getline('.'))
- norm! gUgU
- call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.'))
- norm! gugu
- call assert_equal('this is a simple test: äüöss', getline('.'))
- norm! gUU
- call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.'))
- norm! 010~
- call assert_equal('this is a SIMPLE TEST: ÄÜÖSS', getline('.'))
- norm! V~
- call assert_equal('THIS IS A simple test: äüöss', getline('.'))
- call assert_beeps('norm! c~')
- %d
- call assert_beeps('norm! ~')
-
- " Test for changing case across lines using 'whichwrap'
- call setline(1, ['aaaaaa', 'aaaaaa'])
- normal! gg10~
- call assert_equal(['AAAAAA', 'aaaaaa'], getline(1, 2))
- set whichwrap+=~
- normal! gg10~
- call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2))
- set whichwrap&
-
- " try changing the case with a double byte encoding (DBCS)
- %bw!
- let enc = &enc
- " set encoding=cp932
- call setline(1, "\u8470")
- normal ~
- normal gU$gu$gUgUg~g~gugu
- call assert_equal("\u8470", getline(1))
- let &encoding = enc
-
- " clean up
- bw!
-endfunc
-
-" Turkish ASCII turns to multi-byte. On some systems Turkish locale
-" is available but toupper()/tolower() don't do the right thing.
-func Test_normal_changecase_turkish()
- new
- try
- lang tr_TR.UTF-8
- set casemap=
- let iupper = toupper('i')
- if iupper == "\u0130"
- call setline(1, 'iI')
- 1normal gUU
- call assert_equal("\u0130I", getline(1))
- call assert_equal("\u0130I", toupper("iI"))
-
- call setline(1, 'iI')
- 1normal guu
- call assert_equal("i\u0131", getline(1))
- call assert_equal("i\u0131", tolower("iI"))
- elseif iupper == "I"
- call setline(1, 'iI')
- 1normal gUU
- call assert_equal("II", getline(1))
- call assert_equal("II", toupper("iI"))
-
- call setline(1, 'iI')
- 1normal guu
- call assert_equal("ii", getline(1))
- call assert_equal("ii", tolower("iI"))
- else
- call assert_true(false, "expected toupper('i') to be either 'I' or '\u0131'")
- endif
- set casemap&
- call setline(1, 'iI')
- 1normal gUU
- call assert_equal("II", getline(1))
- call assert_equal("II", toupper("iI"))
-
- call setline(1, 'iI')
- 1normal guu
- call assert_equal("ii", getline(1))
- call assert_equal("ii", tolower("iI"))
-
- lang en_US.UTF-8
- catch /E197:/
- " can't use Turkish locale
- throw 'Skipped: Turkish locale not available'
- endtry
- close!
-endfunc
-
-" Test for r (replace) command
-func Test_normal31_r_cmd()
- new
- call append(0, 'This is a simple test: abcd')
- exe "norm! 1gg$r\<cr>"
- call assert_equal(['This is a simple test: abc', '', ''], getline(1,'$'))
- exe "norm! 1gg2wlr\<cr>"
- call assert_equal(['This is a', 'simple test: abc', '', ''], getline(1,'$'))
- exe "norm! 2gg0W5r\<cr>"
- call assert_equal(['This is a', 'simple ', ' abc', '', ''], getline('1', '$'))
- set autoindent
- call setline(2, ['simple test: abc', ''])
- exe "norm! 2gg0W5r\<cr>"
- call assert_equal(['This is a', 'simple ', 'abc', '', '', ''], getline('1', '$'))
- exe "norm! 1ggVr\<cr>"
- call assert_equal('^M^M^M^M^M^M^M^M^M', strtrans(getline(1)))
- call setline(1, 'This is a')
- exe "norm! 1gg05rf"
- call assert_equal('fffffis a', getline(1))
-
- " When replacing characters, copy characters from above and below lines
- " using CTRL-Y and CTRL-E.
- " Different code paths are used for utf-8 and latin1 encodings
- set showmatch
- " for enc in ['latin1', 'utf-8']
- for enc in ['utf-8']
- enew!
- let &encoding = enc
- call setline(1, [' {a}', 'xxxxxxxxxx', ' [b]'])
- exe "norm! 2gg5r\<C-Y>l5r\<C-E>"
- call assert_equal(' {a}x [b]x', getline(2))
- endfor
- set showmatch&
-
- " r command should fail in operator pending mode
- call assert_beeps('normal! cr')
-
- " replace a tab character in visual mode
- %d
- call setline(1, ["a\tb", "c\td", "e\tf"])
- normal gglvjjrx
- call assert_equal(['axx', 'xxx', 'xxf'], getline(1, '$'))
-
- " replace with a multibyte character (with multiple composing characters)
- %d
- new
- call setline(1, 'aaa')
- exe "normal $ra\u0328\u0301"
- call assert_equal("aaa\u0328\u0301", getline(1))
-
- " clean up
- set noautoindent
- bw!
-endfunc
-
-" Test for g*, g#
-func Test_normal32_g_cmd1()
- new
- call append(0, ['abc.x_foo', 'x_foobar.abc'])
- 1
- norm! $g*
- call assert_equal('x_foo', @/)
- call assert_equal('x_foobar.abc', getline('.'))
- norm! $g#
- call assert_equal('abc', @/)
- call assert_equal('abc.x_foo', getline('.'))
-
- " clean up
- bw!
-endfunc
-
-" Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G,
-" gi and gI commands
-func Test_normal33_g_cmd2()
- CheckFeature jumplist
- call Setup_NewWindow()
- " Test for g`
- clearjumps
- norm! ma10j
- let a=execute(':jumps')
- " empty jumplist
- call assert_equal('>', a[-1:])
- norm! g`a
- call assert_equal('>', a[-1:])
- call assert_equal(1, line('.'))
- call assert_equal('1', getline('.'))
- call cursor(10, 1)
- norm! g'a
- call assert_equal('>', a[-1:])
- call assert_equal(1, line('.'))
-
- " Test for g; and g,
- norm! g;
- " there is only one change in the changelist
- " currently, when we setup the window
- call assert_equal(2, line('.'))
- call assert_fails(':norm! g;', 'E662')
- call assert_fails(':norm! g,', 'E663')
- let &ul = &ul
- call append('$', ['a', 'b', 'c', 'd'])
- let &ul = &ul
- call append('$', ['Z', 'Y', 'X', 'W'])
- let a = execute(':changes')
- call assert_match('2\s\+0\s\+2', a)
- call assert_match('101\s\+0\s\+a', a)
- call assert_match('105\s\+0\s\+Z', a)
- norm! 3g;
- call assert_equal(2, line('.'))
- norm! 2g,
- call assert_equal(105, line('.'))
-
- " Test for g& - global substitute
- %d
- call setline(1, range(1,10))
- call append('$', ['a', 'b', 'c', 'd'])
- $s/\w/&&/g
- exe "norm! /[1-8]\<cr>"
- norm! g&
- call assert_equal(['11', '22', '33', '44', '55', '66', '77', '88', '9', '110', 'a', 'b', 'c', 'dd'], getline(1, '$'))
-
- " Jumping to a fold using gg should open the fold
- set foldenable
- set foldopen+=jump
- 5,8fold
- call feedkeys('6gg', 'xt')
- call assert_equal(1, foldlevel('.'))
- call assert_equal(-1, foldclosed('.'))
- set foldopen-=jump
- set foldenable&
-
- " Test for gv
- %d
- call append('$', repeat(['abcdefgh'], 8))
- exe "norm! 2gg02l\<c-v>2j2ly"
- call assert_equal(['cde', 'cde', 'cde'], getreg(0, 1, 1))
- " in visual mode, gv swaps current and last selected region
- exe "norm! G0\<c-v>4k4lgvd"
- call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'abcdefgh', 'abcdefgh', 'abcdefgh', 'abcdefgh', 'abcdefgh'], getline(1,'$'))
- exe "norm! G0\<c-v>4k4ly"
- exe "norm! gvood"
- call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$'))
- " gv cannot be used in operator pending mode
- call assert_beeps('normal! cgv')
- " gv should beep without a previously selected visual area
- new
- call assert_beeps('normal! gv')
- close
-
- " Test for gk/gj
- %d
- 15vsp
- set wrap listchars= sbr=
- let lineA = 'abcdefghijklmnopqrstuvwxyz'
- let lineB = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- let lineC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
- $put =lineA
- $put =lineB
-
- norm! 3gg0dgk
- call assert_equal(['', 'abcdefghijklmno', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'], getline(1, '$'))
- set nu
- norm! 3gg0gjdgj
- call assert_equal(['', 'abcdefghijklmno', '0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
-
- " Test for gJ
- norm! 2gggJ
- call assert_equal(['', 'abcdefghijklmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
- call assert_equal(16, col('.'))
- " shouldn't do anything
- norm! 10gJ
- call assert_equal(1, col('.'))
-
- " Test for g0 g^ gm g$
- exe "norm! 2gg0gji "
- call assert_equal(['', 'abcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
- norm! g0yl
- call assert_equal(12, col('.'))
- call assert_equal(' ', getreg(0))
- norm! g$yl
- call assert_equal(22, col('.'))
- call assert_equal('3', getreg(0))
- norm! gmyl
- call assert_equal(17, col('.'))
- call assert_equal('n', getreg(0))
- norm! g^yl
- call assert_equal(15, col('.'))
- call assert_equal('l', getreg(0))
- call assert_beeps('normal 5g$')
-
- " Test for g$ with double-width character half displayed
- vsplit
- 9wincmd |
- setlocal nowrap nonumber
- call setline(2, 'asdfasdfヨ')
- 2
- normal 0g$
- call assert_equal(8, col('.'))
- 10wincmd |
- normal 0g$
- call assert_equal(9, col('.'))
-
- setlocal signcolumn=yes
- 11wincmd |
- normal 0g$
- call assert_equal(8, col('.'))
- 12wincmd |
- normal 0g$
- call assert_equal(9, col('.'))
-
- close
-
- " Test for g_
- call assert_beeps('normal! 100g_')
- call setline(2, [' foo ', ' foobar '])
- normal! 2ggg_
- call assert_equal(5, col('.'))
- normal! 2g_
- call assert_equal(8, col('.'))
-
- norm! 2ggdG
- $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 gM with Tab characters
- call setline('.', "\ta\tb\tc\td\te\tf")
- norm! gMyl
- call assert_equal(6, col('.'))
- call assert_equal("c", getreg(0))
-
- " Test for g Ctrl-G
- call setline('.', lineC)
- norm! 60gMyl
- 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(['', 'foo0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'], getline(1,'$'))
-
- " Test for gi
- wincmd c
- %d
- set tw=0
- call setline(1, ['foobar', 'new line'])
- norm! A next word
- $put ='third line'
- norm! gi another word
- call assert_equal(['foobar next word another word', 'new line', 'third line'], getline(1,'$'))
- call setline(1, 'foobar')
- normal! Ggifirst line
- call assert_equal('foobarfirst line', getline(1))
- " Test gi in 'virtualedit' mode with cursor after the end of the line
- set virtualedit=all
- call setline(1, 'foo')
- exe "normal! Abar\<Right>\<Right>\<Right>\<Right>"
- call setline(1, 'foo')
- normal! Ggifirst line
- call assert_equal('foo first line', getline(1))
- set virtualedit&
-
- " Test for aboring a g command using CTRL-\ CTRL-G
- exe "normal! g\<C-\>\<C-G>"
- call assert_equal('foo first line', getline('.'))
-
- " clean up
- bw!
-endfunc
-
-func Test_normal_ex_substitute()
- " This was hanging on the substitute prompt.
- new
- call setline(1, 'a')
- exe "normal! gggQs/a/b/c\<CR>"
- call assert_equal('a', getline(1))
- bwipe!
-endfunc
-
-" Test for g CTRL-G
-func Test_g_ctrl_g()
- new
-
- let a = execute(":norm! g\<c-g>")
- call assert_equal("\n--No lines in buffer--", a)
-
- " Test for CTRL-G (same as :file)
- let a = execute(":norm! \<c-g>")
- call assert_equal("\n\n\"[No Name]\" --No lines in buffer--", a)
-
- call setline(1, ['first line', 'second line'])
-
- " Test g CTRL-g with dos, mac and unix file type.
- norm! gojll
- set ff=dos
- let a = execute(":norm! g\<c-g>")
- call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 15 of 25", a)
-
- set ff=mac
- let a = execute(":norm! g\<c-g>")
- call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 14 of 23", a)
-
- set ff=unix
- let a = execute(":norm! g\<c-g>")
- call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 14 of 23", a)
-
- " Test g CTRL-g in visual mode (v)
- let a = execute(":norm! gojllvlg\<c-g>")
- call assert_equal("\nSelected 1 of 2 Lines; 1 of 4 Words; 2 of 23 Bytes", a)
-
- " Test g CTRL-g in visual mode (CTRL-V) with end col > start col
- let a = execute(":norm! \<Esc>gojll\<C-V>kllg\<c-g>")
- call assert_equal("\nSelected 3 Cols; 2 of 2 Lines; 2 of 4 Words; 6 of 23 Bytes", a)
-
- " Test g_CTRL-g in visual mode (CTRL-V) with end col < start col
- let a = execute(":norm! \<Esc>goll\<C-V>jhhg\<c-g>")
- call assert_equal("\nSelected 3 Cols; 2 of 2 Lines; 2 of 4 Words; 6 of 23 Bytes", a)
-
- " Test g CTRL-g in visual mode (CTRL-V) with end_vcol being MAXCOL
- let a = execute(":norm! \<Esc>gojll\<C-V>k$g\<c-g>")
- call assert_equal("\nSelected 2 of 2 Lines; 4 of 4 Words; 17 of 23 Bytes", a)
-
- " There should be one byte less with noeol
- set bin noeol
- let a = execute(":norm! \<Esc>gog\<c-g>")
- call assert_equal("\nCol 1 of 10; Line 1 of 2; Word 1 of 4; Char 1 of 23; Byte 1 of 22", a)
- set bin & eol&
-
- call setline(1, ['Français', '日本語'])
-
- let a = execute(":norm! \<Esc>gojlg\<c-g>")
- call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20", a)
-
- let a = execute(":norm! \<Esc>gojvlg\<c-g>")
- call assert_equal("\nSelected 1 of 2 Lines; 1 of 2 Words; 2 of 13 Chars; 6 of 20 Bytes", a)
-
- let a = execute(":norm! \<Esc>goll\<c-v>jlg\<c-g>")
- call assert_equal("\nSelected 4 Cols; 2 of 2 Lines; 2 of 2 Words; 6 of 13 Chars; 11 of 20 Bytes", a)
-
- set fenc=utf8 bomb
- let a = execute(":norm! \<Esc>gojlg\<c-g>")
- call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+3 for BOM)", a)
-
- set fenc=utf16 bomb
- let a = execute(":norm! g\<c-g>")
- call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+2 for BOM)", a)
-
- set fenc=utf32 bomb
- let a = execute(":norm! g\<c-g>")
- call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+4 for BOM)", a)
-
- set fenc& bomb&
-
- set ff&
- bwipe!
-endfunc
-
-" Test for g8
-func Test_normal34_g_cmd3()
- new
- let a=execute(':norm! 1G0g8')
- call assert_equal("\nNUL", a)
-
- call setline(1, 'abcdefghijklmnopqrstuvwxyzäüö')
- let a=execute(':norm! 1G$g8')
- call assert_equal("\nc3 b6 ", a)
-
- call setline(1, "a\u0302")
- let a=execute(':norm! 1G0g8')
- call assert_equal("\n61 + cc 82 ", a)
-
- " clean up
- bw!
-endfunc
-
-" Test 8g8 which finds invalid utf8 at or after the cursor.
-func Test_normal_8g8()
- new
-
- " With invalid byte.
- call setline(1, "___\xff___")
- norm! 1G08g8g
- call assert_equal([0, 1, 4, 0, 1], getcurpos())
-
- " With invalid byte before the cursor.
- call setline(1, "___\xff___")
- norm! 1G$h8g8g
- call assert_equal([0, 1, 6, 0, 9], getcurpos())
-
- " With truncated sequence.
- call setline(1, "___\xE2\x82___")
- norm! 1G08g8g
- call assert_equal([0, 1, 4, 0, 1], getcurpos())
-
- " With overlong sequence.
- call setline(1, "___\xF0\x82\x82\xAC___")
- norm! 1G08g8g
- call assert_equal([0, 1, 4, 0, 1], getcurpos())
-
- " With valid utf8.
- call setline(1, "café")
- norm! 1G08g8
- call assert_equal([0, 1, 1, 0, 1], getcurpos())
-
- bw!
-endfunc
-
-" Test for g<
-func Test_normal35_g_cmd4()
- " Cannot capture its output,
- " probably a bug, therefore, test disabled:
- throw "Skipped: output of g< can't be tested currently"
- echo "a\nb\nc\nd"
- let b=execute(':norm! g<')
- call assert_true(!empty(b), 'failed `execute(g<)`')
-endfunc
-
-" Test for gp gP go
-func Test_normal36_g_cmd5()
- new
- call append(0, 'abcdefghijklmnopqrstuvwxyz')
- set ff=unix
- " Test for gp gP
- call append(1, range(1,10))
- 1
- norm! 1yy
- 3
- norm! gp
- call assert_equal([0, 5, 1, 0, 1], getcurpos())
- $
- norm! gP
- call assert_equal([0, 14, 1, 0, 1], getcurpos())
-
- " Test for go
- norm! 26go
- call assert_equal([0, 1, 26, 0, 26], getcurpos())
- norm! 27go
- call assert_equal([0, 1, 26, 0, 26], getcurpos())
- norm! 28go
- call assert_equal([0, 2, 1, 0, 1], getcurpos())
- set ff=dos
- norm! 29go
- call assert_equal([0, 2, 1, 0, 1], getcurpos())
- set ff=unix
- norm! gg0
- norm! 101go
- call assert_equal([0, 13, 26, 0, 26], getcurpos())
- norm! 103go
- call assert_equal([0, 14, 1, 0, 1], getcurpos())
- " count > buffer content
- norm! 120go
- call assert_equal([0, 14, 1, 0, 2147483647], getcurpos())
- " clean up
- bw!
-endfunc
-
-" Test for gt and gT
-func Test_normal37_g_cmd6()
- tabnew 1.txt
- tabnew 2.txt
- tabnew 3.txt
- norm! 1gt
- call assert_equal(1, tabpagenr())
- norm! 3gt
- call assert_equal(3, tabpagenr())
- norm! 1gT
- " count gT goes not to the absolute tabpagenumber
- " but, but goes to the count previous tabpagenumber
- call assert_equal(2, tabpagenr())
- " wrap around
- norm! 3gT
- call assert_equal(3, tabpagenr())
- " gt does not wrap around
- norm! 5gt
- call assert_equal(3, tabpagenr())
-
- for i in range(3)
- tabclose
- endfor
- " clean up
- call assert_fails(':tabclose', 'E784:')
-endfunc
-
-" Test for <Home> and <C-Home> key
-func Test_normal38_nvhome()
- new
- call setline(1, range(10))
- $
- setl et sw=2
- norm! V10>$
- " count is ignored
- exe "norm! 10\<home>"
- call assert_equal(1, col('.'))
- exe "norm! \<home>"
- call assert_equal([0, 10, 1, 0, 1], getcurpos())
- exe "norm! 5\<c-home>"
- call assert_equal([0, 5, 1, 0, 1], getcurpos())
- exe "norm! \<c-home>"
- call assert_equal([0, 1, 1, 0, 1], getcurpos())
- exe "norm! G\<c-kHome>"
- call assert_equal([0, 1, 1, 0, 1], getcurpos())
-
- " clean up
- bw!
-endfunc
-
-" Test for <End> and <C-End> keys
-func Test_normal_nvend()
- new
- call setline(1, map(range(1, 10), '"line" .. v:val'))
- exe "normal! \<End>"
- call assert_equal(5, col('.'))
- exe "normal! 4\<End>"
- call assert_equal([4, 5], [line('.'), col('.')])
- exe "normal! \<C-End>"
- call assert_equal([10, 6], [line('.'), col('.')])
- close!
-endfunc
-
-" Test for cw cW ce
-func Test_normal39_cw()
- " Test for cw and cW on whitespace
- new
- set tw=0
- call append(0, 'here are some words')
- norm! 1gg0elcwZZZ
- call assert_equal('hereZZZare some words', getline('.'))
- norm! 1gg0elcWYYY
- call assert_equal('hereZZZareYYYsome words', getline('.'))
- norm! 2gg0cwfoo
- call assert_equal('foo', getline('.'))
-
- call setline(1, 'one; two')
- call cursor(1, 1)
- call feedkeys('cwvim', 'xt')
- call assert_equal('vim; two', getline(1))
- call feedkeys('0cWone', 'xt')
- call assert_equal('one two', getline(1))
- "When cursor is at the end of a word 'ce' will change until the end of the
- "next word, but 'cw' will change only one character
- call setline(1, 'one two')
- call feedkeys('0ecwce', 'xt')
- call assert_equal('once two', getline(1))
- call setline(1, 'one two')
- call feedkeys('0ecely', 'xt')
- call assert_equal('only', getline(1))
-
- " clean up
- bw!
-endfunc
-
-" Test for CTRL-\ commands
-func Test_normal40_ctrl_bsl()
- new
- call append(0, 'here are some words')
- exe "norm! 1gg0a\<C-\>\<C-N>"
- call assert_equal('n', mode())
- call assert_equal(1, col('.'))
- call assert_equal('', visualmode())
- exe "norm! 1gg0viw\<C-\>\<C-N>"
- call assert_equal('n', mode())
- call assert_equal(4, col('.'))
- exe "norm! 1gg0a\<C-\>\<C-G>"
- call assert_equal('n', mode())
- call assert_equal(1, col('.'))
- "imap <buffer> , <c-\><c-n>
- " set im
- exe ":norm! \<c-\>\<c-n>dw"
- " set noim
- call assert_equal('are some words', getline(1))
- call assert_false(&insertmode)
- call assert_beeps("normal! \<C-\>\<C-A>")
-
- " Using CTRL-\ CTRL-N in cmd window should close the window
- call feedkeys("q:\<C-\>\<C-N>", 'xt')
- call assert_equal('', getcmdwintype())
-
- " clean up
- bw!
-endfunc
-
-" Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= in insert mode
-func Test_normal41_insert_reg()
- new
- set sts=2 sw=2 ts=8 tw=0
- call append(0, ["aaa\tbbb\tccc", '', '', ''])
- let a=getline(1)
- norm! 2gg0
- exe "norm! a\<c-r>=a\<cr>"
- norm! 3gg0
- exe "norm! a\<c-r>\<c-r>=a\<cr>"
- norm! 4gg0
- exe "norm! a\<c-r>\<c-o>=a\<cr>"
- call assert_equal(['aaa bbb ccc', 'aaa bbb ccc', 'aaa bbb ccc', 'aaa bbb ccc', ''], getline(1, '$'))
-
- " clean up
- set sts=0 sw=8 ts=8
- bw!
-endfunc
-
-" Test for Ctrl-D and Ctrl-U
-func Test_normal42_halfpage()
- call Setup_NewWindow()
- call assert_equal(5, &scroll)
- exe "norm! \<c-d>"
- call assert_equal('6', getline('.'))
- exe "norm! 2\<c-d>"
- call assert_equal('8', getline('.'))
- call assert_equal(2, &scroll)
- set scroll=5
- exe "norm! \<c-u>"
- call assert_equal('3', getline('.'))
- 1
- set scrolloff=5
- exe "norm! \<c-d>"
- call assert_equal('10', getline('.'))
- exe "norm! \<c-u>"
- call assert_equal('5', getline('.'))
- 1
- set scrolloff=99
- exe "norm! \<c-d>"
- call assert_equal('10', getline('.'))
- set scrolloff=0
- 100
- exe "norm! $\<c-u>"
- call assert_equal('95', getline('.'))
- call assert_equal([0, 95, 1, 0, 1], getcurpos())
- 100
- set nostartofline
- exe "norm! $\<c-u>"
- call assert_equal('95', getline('.'))
- call assert_equal([0, 95, 2, 0, 2147483647], getcurpos())
- " cleanup
- set startofline
- bw!
-endfunc
-
-func Test_normal45_drop()
- if !has('dnd')
- " The ~ register does not exist
- call assert_beeps('norm! "~')
- return
- endif
-
- " basic test for drag-n-drop
- " unfortunately, without a gui, we can't really test much here,
- " so simply test that ~p fails (which uses the drop register)
- new
- call assert_fails(':norm! "~p', 'E353')
- call assert_equal([], getreg('~', 1, 1))
- " the ~ register is read only
- call assert_fails(':let @~="1"', 'E354')
- bw!
-endfunc
-
-func Test_normal46_ignore()
- new
- " How to test this?
- " let's just for now test, that the buffer
- " does not change
- call feedkeys("\<c-s>", 't')
- call assert_equal([''], getline(1,'$'))
-
- " no valid commands
- exe "norm! \<char-0x100>"
- call assert_equal([''], getline(1,'$'))
-
- exe "norm! ä"
- call assert_equal([''], getline(1,'$'))
-
- " clean up
- bw!
-endfunc
-
-func Test_normal47_visual_buf_wipe()
- " This was causing a crash or ml_get error.
- enew!
- call setline(1,'xxx')
- normal $
- new
- call setline(1, range(1,2))
- 2
- exe "norm \<C-V>$"
- bw!
- norm yp
- set nomodified
-endfunc
-
-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()
- new
- call setline(1, 'one two three four five six seven eight nine ten')
- 1
- norm! 3d2w
- call assert_equal('seven eight nine ten', getline(1))
- bw!
-endfunc
-
-func Test_normal50_commandline()
- CheckFeature timers
- CheckFeature cmdline_hist
- func! DoTimerWork(id)
- call assert_equal('[Command Line]', bufname(''))
- " should fail, with E11, but does fail with E23?
- "call feedkeys("\<c-^>", 'tm')
-
- " should also fail with E11
- call assert_fails(":wincmd p", 'E11')
- " return from commandline window
- call feedkeys("\<cr>")
- endfunc
-
- let oldlang=v:lang
- lang C
- set updatetime=20
- call timer_start(100, 'DoTimerWork')
- try
- " throws E23, for whatever reason...
- call feedkeys('q:', 'x!')
- catch /E23/
- " no-op
- endtry
- " clean up
- set updatetime=4000
- exe "lang" oldlang
- bw!
-endfunc
-
-func Test_normal51_FileChangedRO()
- CheckFeature autocmd
- call writefile(['foo'], 'Xreadonly.log')
- new Xreadonly.log
- setl ro
- au FileChangedRO <buffer> :call feedkeys("\<c-^>", 'tix')
- call assert_fails(":norm! Af", 'E788')
- call assert_equal(['foo'], getline(1,'$'))
- call assert_equal('Xreadonly.log', bufname(''))
-
- " cleanup
- bw!
- call delete("Xreadonly.log")
-endfunc
-
-func Test_normal52_rl()
- CheckFeature rightleft
- new
- call setline(1, 'abcde fghij klmnopq')
- norm! 1gg$
- set rl
- call assert_equal(19, col('.'))
- call feedkeys('l', 'tx')
- call assert_equal(18, col('.'))
- call feedkeys('h', 'tx')
- call assert_equal(19, col('.'))
- call feedkeys("\<right>", 'tx')
- call assert_equal(18, col('.'))
- call feedkeys("\<left>", 'tx')
- call assert_equal(19, col('.'))
- call feedkeys("\<s-right>", 'tx')
- call assert_equal(13, col('.'))
- call feedkeys("\<c-right>", 'tx')
- call assert_equal(7, col('.'))
- call feedkeys("\<c-left>", 'tx')
- call assert_equal(13, col('.'))
- call feedkeys("\<s-left>", 'tx')
- call assert_equal(19, col('.'))
- call feedkeys("<<", 'tx')
- call assert_equal(' abcde fghij klmnopq',getline(1))
- call feedkeys(">>", 'tx')
- call assert_equal('abcde fghij klmnopq',getline(1))
-
- " cleanup
- set norl
- bw!
-endfunc
-
-func Test_normal54_Ctrl_bsl()
- new
- call setline(1, 'abcdefghijklmn')
- exe "norm! df\<c-\>\<c-n>"
- call assert_equal(['abcdefghijklmn'], getline(1,'$'))
- exe "norm! df\<c-\>\<c-g>"
- call assert_equal(['abcdefghijklmn'], getline(1,'$'))
- exe "norm! df\<c-\>m"
- call assert_equal(['abcdefghijklmn'], getline(1,'$'))
-
- call setline(2, 'abcdefghijklmnÄf')
- norm! 2gg0
- exe "norm! df\<Char-0x101>"
- call assert_equal(['abcdefghijklmn', 'f'], getline(1,'$'))
- norm! 1gg0
- exe "norm! df\<esc>"
- call assert_equal(['abcdefghijklmn', 'f'], getline(1,'$'))
-
- " clean up
- bw!
-endfunc
-
-func Test_normal_large_count()
- " This may fail with 32bit long, how do we detect that?
- new
- normal o
- normal 6666666666dL
- 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()
- enew!
- let save_cpo = &cpo
- call append(0, ['First line', 'Second line', 'Third line'])
- exe "normal i\<C-G>u"
- call cursor(2, 1)
- set cpo-=X
- normal 4gro
- call assert_equal('oooond line', getline(2))
- undo
- set cpo+=X
- normal 4gro
- call assert_equal('ooooecond line', getline(2))
- let &cpo = save_cpo
- normal! ggvegrx
- call assert_equal('xxxxx line', getline(1))
- exe "normal! gggr\<C-V>122"
- call assert_equal('zxxxx line', getline(1))
- set virtualedit=all
- normal! 15|grl
- call assert_equal('zxxxx line l', getline(1))
- set virtualedit&
- set nomodifiable
- call assert_fails('normal! grx', 'E21:')
- call assert_fails('normal! gRx', 'E21:')
- set modifiable&
- enew!
-endfunc
-
-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()
- " Make sure no buffers are changed.
- %bwipe!
-
- exe "normal \<C-C>"
- call assert_match("Type :qa and press <Enter> to exit Nvim", Screenline(&lines))
-
- new
- cal setline(1, 'hi!')
- exe "normal \<C-C>"
- call assert_match("Type :qa! and press <Enter> to abandon all changes and exit Nvim", Screenline(&lines))
-
- bwipe!
-endfunc
-
-func Test_mode_updated_after_ctrl_c()
- CheckScreendump
-
- let buf = RunVimInTerminal('', {'rows': 5})
- call term_sendkeys(buf, "i")
- call term_sendkeys(buf, "\<C-O>")
- " wait a moment so that the "-- (insert) --" message is displayed
- call TermWait(buf, 50)
- call term_sendkeys(buf, "\<C-C>")
- call VerifyScreenDump(buf, 'Test_mode_updated_1', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Test for '[m', ']m', '[M' and ']M'
-" Jumping to beginning and end of methods in Java-like languages
-func Test_java_motion()
- new
- call assert_beeps('normal! [m')
- call assert_beeps('normal! ]m')
- call assert_beeps('normal! [M')
- call assert_beeps('normal! ]M')
- let lines =<< trim [CODE]
- Piece of Java
- {
- tt m1 {
- t1;
- } e1
-
- tt m2 {
- t2;
- } e2
-
- tt m3 {
- if (x)
- {
- t3;
- }
- } e3
- }
- [CODE]
- call setline(1, lines)
-
- normal gg
-
- normal 2]maA
- call assert_equal("\ttt m1 {A", getline('.'))
- call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
-
- normal j]maB
- call assert_equal("\ttt m2 {B", getline('.'))
- call assert_equal([7, 9, 16], [line('.'), col('.'), virtcol('.')])
-
- normal ]maC
- call assert_equal("\ttt m3 {C", getline('.'))
- call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
-
- normal [maD
- call assert_equal("\ttt m3 {DC", getline('.'))
- call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
-
- normal k2[maE
- call assert_equal("\ttt m1 {EA", getline('.'))
- call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
-
- normal 3[maF
- call assert_equal("{F", getline('.'))
- call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
-
- normal ]MaG
- call assert_equal("\t}G e1", getline('.'))
- call assert_equal([5, 3, 10], [line('.'), col('.'), virtcol('.')])
-
- normal j2]MaH
- call assert_equal("\t}H e3", getline('.'))
- call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
-
- normal ]M]M
- normal aI
- call assert_equal("}I", getline('.'))
- call assert_equal([17, 2, 2], [line('.'), col('.'), virtcol('.')])
-
- normal 2[MaJ
- call assert_equal("\t}JH e3", getline('.'))
- call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
-
- normal k[MaK
- call assert_equal("\t}K e2", getline('.'))
- call assert_equal([9, 3, 10], [line('.'), col('.'), virtcol('.')])
-
- normal 3[MaL
- call assert_equal("{LF", getline('.'))
- call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
-
- call cursor(2, 1)
- call assert_beeps('norm! 5]m')
-
- " jumping to a method in a fold should open the fold
- 6,10fold
- call feedkeys("gg3]m", 'xt')
- call assert_equal([7, 8, 15], [line('.'), col('.'), virtcol('.')])
- call assert_equal(-1, foldclosedend(7))
-
- close!
-endfunc
-
-" Tests for g cmds
-func Test_normal_gdollar_cmd()
- CheckFeature jumplist
- call Setup_NewWindow()
- " Make long lines that will wrap
- %s/$/\=repeat(' foobar', 10)/
- 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_gj()
- " needs 80 column new window
- new
- vert 80new
- call assert_beeps('normal gk')
- 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!
-
- " needs 80 column new window
- new
- vert 80new
- call assert_beeps('normal gj')
- 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('.'))
- " When 'nowrap' is set, gk and gj behave like k and j
- set nowrap
- normal! gk
- call assert_equal([2, 76], [line('.'), col('.')])
- normal! gj
- call assert_equal([3, 76], [line('.'), col('.')])
- %bw!
- set cpoptions& number& numberwidth& wrap&
-endfunc
-
-" Test for using : to run a multi-line Ex command in operator pending mode
-func Test_normal_yank_with_excmd()
- new
- call setline(1, ['foo', 'bar', 'baz'])
- let @a = ''
- call feedkeys("\"ay:if v:true\<CR>normal l\<CR>endif\<CR>", 'xt')
- call assert_equal('f', @a)
- close!
-endfunc
-
-" Test for supplying a count to a normal-mode command across a cursorhold call
-func Test_normal_cursorhold_with_count()
- throw 'Skipped: Nvim removed <CursorHold> key'
- func s:cHold()
- let g:cHold_Called += 1
- endfunc
- new
- augroup normalcHoldTest
- au!
- au CursorHold <buffer> call s:cHold()
- augroup END
- let g:cHold_Called = 0
- call feedkeys("3\<CursorHold>2ix", 'xt')
- call assert_equal(1, g:cHold_Called)
- call assert_equal(repeat('x', 32), getline(1))
- augroup normalcHoldTest
- au!
- augroup END
- au! normalcHoldTest
- close!
- delfunc s:cHold
-endfunc
-
-" Test for using a count and a command with CTRL-W
-func Test_wincmd_with_count()
- call feedkeys("\<C-W>12n", 'xt')
- call assert_equal(12, winheight(0))
-endfunc
-
-" Test for 'b', 'B' 'ge' and 'gE' commands
-func Test_horiz_motion()
- new
- normal! gg
- call assert_beeps('normal! b')
- call assert_beeps('normal! B')
- call assert_beeps('normal! gE')
- call assert_beeps('normal! ge')
- " <S-Backspace> moves one word left and <C-Backspace> moves one WORD left
- call setline(1, 'one ,two ,three')
- exe "normal! $\<S-BS>"
- call assert_equal(11, col('.'))
- exe "normal! $\<C-BS>"
- call assert_equal(10, col('.'))
- close!
-endfunc
-
-" Test for using a : command in operator pending mode
-func Test_normal_colon_op()
- new
- call setline(1, ['one', 'two'])
- call assert_beeps("normal! Gc:d\<CR>")
- close!
-endfunc
-
-" Test for d and D commands
-func Test_normal_delete_cmd()
- new
- " D in an empty line
- call setline(1, '')
- normal D
- call assert_equal('', getline(1))
- " D in an empty line in virtualedit mode
- set virtualedit=all
- normal D
- call assert_equal('', getline(1))
- set virtualedit&
- " delete to a readonly register
- call setline(1, ['abcd'])
- call assert_beeps('normal ":d2l')
-
- " D and d with 'nomodifiable'
- call setline(1, ['abcd'])
- setlocal nomodifiable
- call assert_fails('normal D', 'E21:')
- call assert_fails('normal d$', 'E21:')
-
- close!
-endfunc
-
-" Test for deleting or changing characters across lines with 'whichwrap'
-" containing 's'. Should count <EOL> as one character.
-func Test_normal_op_across_lines()
- new
- set whichwrap&
- call setline(1, ['one two', 'three four'])
- exe "norm! $3d\<Space>"
- call assert_equal(['one twhree four'], getline(1, '$'))
-
- call setline(1, ['one two', 'three four'])
- exe "norm! $3c\<Space>x"
- call assert_equal(['one twxhree four'], getline(1, '$'))
-
- set whichwrap+=l
- call setline(1, ['one two', 'three four'])
- exe "norm! $3x"
- call assert_equal(['one twhree four'], getline(1, '$'))
- close!
- set whichwrap&
-endfunc
-
-" Test for 'w' and 'b' commands
-func Test_normal_word_move()
- new
- call setline(1, ['foo bar a', '', 'foo bar b'])
- " copy a single character word at the end of a line
- normal 1G$yw
- call assert_equal('a', @")
- " copy a single character word at the end of a file
- normal G$yw
- call assert_equal('b', @")
- " check for a word movement handling an empty line properly
- normal 1G$vwy
- call assert_equal("a\n\n", @")
-
- " copy using 'b' command
- %d
- " non-empty blank line at the start of file
- call setline(1, [' ', 'foo bar'])
- normal 2Gyb
- call assert_equal(" \n", @")
- " try to copy backwards from the start of the file
- call setline(1, ['one two', 'foo bar'])
- call assert_beeps('normal ggyb')
- " 'b' command should stop at an empty line
- call setline(1, ['one two', '', 'foo bar'])
- normal 3Gyb
- call assert_equal("\n", @")
- normal 3Gy2b
- call assert_equal("two\n", @")
- " 'b' command should not stop at a non-empty blank line
- call setline(1, ['one two', ' ', 'foo bar'])
- normal 3Gyb
- call assert_equal("two\n ", @")
-
- close!
-endfunc
-
-" Test for 'scrolloff' with a long line that doesn't fit in the screen
-func Test_normal_scroloff()
- 10new
- 80vnew
- call setline(1, repeat('a', 1000))
- set scrolloff=10
- normal gg10gj
- call assert_equal(8, winline())
- normal 10gj
- call assert_equal(10, winline())
- normal 10gk
- call assert_equal(3, winline())
- set scrolloff&
- close!
-endfunc
-
-" Test for vertical scrolling with CTRL-F and CTRL-B with a long line
-func Test_normal_vert_scroll_longline()
- 10new
- 80vnew
- call setline(1, range(1, 10))
- call append(5, repeat('a', 1000))
- exe "normal gg\<C-F>"
- call assert_equal(6, line('.'))
- exe "normal \<C-F>\<C-F>"
- call assert_equal(11, line('.'))
- call assert_equal(1, winline())
- exe "normal \<C-B>"
- call assert_equal(10, line('.'))
- call assert_equal(3, winline())
- exe "normal \<C-B>\<C-B>"
- call assert_equal(5, line('.'))
- call assert_equal(5, winline())
- close!
-endfunc
-
-" Test for jumping in a file using %
-func Test_normal_percent_jump()
- new
- call setline(1, range(1, 100))
-
- " jumping to a folded line should open the fold
- 25,75fold
- call feedkeys('50%', 'xt')
- call assert_equal(50, line('.'))
- call assert_equal(-1, foldclosedend(50))
- close!
-endfunc
-
-" Test for << and >> commands to shift text by 'shiftwidth'
-func Test_normal_shift_rightleft()
- new
- call setline(1, ['one', '', "\t", ' two', "\tthree", ' four'])
- set shiftwidth=2 tabstop=8
- normal gg6>>
- call assert_equal([' one', '', "\t ", ' two', "\t three", "\tfour"],
- \ getline(1, '$'))
- normal ggVG2>>
- call assert_equal([' one', '', "\t ", "\ttwo",
- \ "\t three", "\t four"], getline(1, '$'))
- normal gg6<<
- call assert_equal([' one', '', "\t ", ' two', "\t three",
- \ "\t four"], getline(1, '$'))
- normal ggVG2<<
- call assert_equal(['one', '', "\t", ' two', "\tthree", ' four'],
- \ getline(1, '$'))
- set shiftwidth& tabstop&
- bw!
-endfunc
-
-" Some commands like yy, cc, dd, >>, << and !! accept a count after
-" typing the first letter of the command.
-func Test_normal_count_after_operator()
- new
- setlocal shiftwidth=4 tabstop=8 autoindent
- call setline(1, ['one', 'two', 'three', 'four', 'five'])
- let @a = ''
- normal! j"ay4y
- call assert_equal("two\nthree\nfour\nfive\n", @a)
- normal! 3G>2>
- call assert_equal(['one', 'two', ' three', ' four', 'five'],
- \ getline(1, '$'))
- exe "normal! 3G0c2cred\nblue"
- call assert_equal(['one', 'two', ' red', ' blue', 'five'],
- \ getline(1, '$'))
- exe "normal! gg<8<"
- call assert_equal(['one', 'two', 'red', 'blue', 'five'],
- \ getline(1, '$'))
- exe "normal! ggd3d"
- call assert_equal(['blue', 'five'], getline(1, '$'))
- call setline(1, range(1, 4))
- call feedkeys("gg!3!\<C-B>\"\<CR>", 'xt')
- call assert_equal('".,.+2!', @:)
- call feedkeys("gg!1!\<C-B>\"\<CR>", 'xt')
- call assert_equal('".!', @:)
- call feedkeys("gg!9!\<C-B>\"\<CR>", 'xt')
- call assert_equal('".,$!', @:)
- bw!
-endfunc
-
-func Test_normal_gj_on_extra_wide_char()
- new | 25vsp
- let text='1 foooooooo ar e insâ€zwe1 foooooooo insâ€zwei' .
- \ ' i drei vier fünf sechs sieben acht un zehn elf zwöfl' .
- \ ' dreizehn v ierzehn fünfzehn'
- put =text
- call cursor(2,1)
- norm! gj
- call assert_equal([0,2,25,0], getpos('.'))
- bw!
-endfunc
-
-func Test_normal_count_out_of_range()
- new
- call setline(1, 'text')
- normal 44444444444|
- call assert_equal(999999999, v:count)
- normal 444444444444|
- call assert_equal(999999999, v:count)
- normal 4444444444444|
- call assert_equal(999999999, v:count)
- normal 4444444444444444444|
- call assert_equal(999999999, v:count)
-
- normal 9y99999999|
- call assert_equal(899999991, v:count)
- normal 10y99999999|
- call assert_equal(999999999, v:count)
- normal 44444444444y44444444444|
- call assert_equal(999999999, v:count)
- bwipe!
-endfunc
-
-" Test that mouse shape is restored to Normal mode after failed "c" operation.
-func Test_mouse_shape_after_failed_change()
- CheckFeature mouseshape
- CheckCanRunGui
-
- let lines =<< trim END
- set mouseshape+=o:busy
- setlocal nomodifiable
- let g:mouse_shapes = []
-
- func SaveMouseShape(timer)
- let g:mouse_shapes += [getmouseshape()]
- endfunc
-
- func SaveAndQuit(timer)
- call writefile(g:mouse_shapes, 'Xmouseshapes')
- quit
- endfunc
-
- call timer_start(50, {_ -> feedkeys('c')})
- call timer_start(100, 'SaveMouseShape')
- call timer_start(150, {_ -> feedkeys('c')})
- call timer_start(200, 'SaveMouseShape')
- call timer_start(250, 'SaveAndQuit')
- END
- call writefile(lines, 'Xmouseshape.vim', 'D')
- call RunVim([], [], "-g -S Xmouseshape.vim")
- sleep 300m
- call assert_equal(['busy', 'arrow'], readfile('Xmouseshapes'))
-
- call delete('Xmouseshapes')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim
deleted file mode 100644
index 521b0cf706..0000000000
--- a/src/nvim/testdir/test_number.vim
+++ /dev/null
@@ -1,359 +0,0 @@
-" Test for 'number' and 'relativenumber'
-
-source check.vim
-source view_util.vim
-
-source screendump.vim
-
-func s:screen_lines(start, end) abort
- return ScreenLines([a:start, a:end], 8)
-endfunc
-
-func s:compare_lines(expect, actual)
- call assert_equal(a:expect, a:actual)
-endfunc
-
-func s:test_windows(h, w) abort
- call NewWindow(a:h, a:w)
-endfunc
-
-func s:close_windows() abort
- call CloseWindow()
-endfunc
-
-func s:validate_cursor() abort
- " update skipcol.
- " wincol():
- " f_wincol
- " -> validate_cursor
- " -> curs_columns
- call wincol()
-endfunc
-
-func Test_set_options()
- set nu rnu
- call assert_equal(1, &nu)
- call assert_equal(1, &rnu)
-
- call s:test_windows(10, 20)
- call assert_equal(1, &nu)
- call assert_equal(1, &rnu)
- call s:close_windows()
-
- set nu& rnu&
-endfunc
-
-func Test_set_global_and_local()
- " setlocal must NOT reset the other global value
- set nonu nornu
- setglobal nu
- setlocal rnu
- call assert_equal(1, &g:nu)
-
- set nonu nornu
- setglobal rnu
- setlocal nu
- call assert_equal(1, &g:rnu)
-
- " setglobal MUST reset the other global value
- set nonu nornu
- setglobal nu
- setglobal rnu
- call assert_equal(1, &g:nu)
-
- set nonu nornu
- setglobal rnu
- setglobal nu
- call assert_equal(1, &g:rnu)
-
- " set MUST reset the other global value
- set nonu nornu
- set nu
- set rnu
- call assert_equal(1, &g:nu)
-
- set nonu nornu
- set rnu
- set nu
- call assert_equal(1, &g:rnu)
-
- set nu& rnu&
-endfunc
-
-func Test_number()
- call s:test_windows(10, 20)
- call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"])
- setl number
- let lines = s:screen_lines(1, 4)
- let expect = [
-\ " 1 abcd",
-\ " 2 klmn",
-\ " 3 uvwx",
-\ " 4 EFGH",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_relativenumber()
- call s:test_windows(10, 20)
- call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"])
- 3
- setl relativenumber
- let lines = s:screen_lines(1, 6)
- let expect = [
-\ " 2 abcd",
-\ " 1 klmn",
-\ " 0 uvwx",
-\ " 1 EFGH",
-\ " 2 OPQR",
-\ " 3 YZ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_number_with_relativenumber()
- call s:test_windows(10, 20)
- call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"])
- 4
- setl number relativenumber
- let lines = s:screen_lines(1, 6)
- let expect = [
-\ " 3 abcd",
-\ " 2 klmn",
-\ " 1 uvwx",
-\ "4 EFGH",
-\ " 1 OPQR",
-\ " 2 YZ ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_number_with_linewrap1()
- call s:test_windows(3, 20)
- normal! 61ia
- setl number wrap
- call s:validate_cursor()
- let lines = s:screen_lines(1, 3)
- let expect = [
-\ "--1 aaaa",
-\ " aaaa",
-\ " aaaa",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI
-func XTest_number_with_linewrap2()
- call s:test_windows(3, 20)
- normal! 61ia
- setl number wrap
- call s:validate_cursor()
- 0
- call s:validate_cursor()
- let lines = s:screen_lines(1, 3)
- let expect = [
-\ " 1 aaaa",
-\ " aaaa",
-\ " aaaa",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI
-func XTest_number_with_linewrap3()
- call s:test_windows(4, 20)
- normal! 81ia
- setl number wrap
- call s:validate_cursor()
- setl nonumber
- call s:validate_cursor()
- let lines = s:screen_lines(1, 4)
- let expect = [
-\ "aaaaaaaa",
-\ "aaaaaaaa",
-\ "aaaaaaaa",
-\ "a ",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_numberwidth()
- call s:test_windows(10, 20)
- call setline(1, repeat(['aaaa'], 10))
- setl number numberwidth=6
- let lines = s:screen_lines(1, 3)
- let expect = [
-\ " 1 aa",
-\ " 2 aa",
-\ " 3 aa",
-\ ]
- call s:compare_lines(expect, lines)
-
- set relativenumber
- let lines = s:screen_lines(1, 3)
- let expect = [
-\ "1 aa",
-\ " 1 aa",
-\ " 2 aa",
-\ ]
- call s:compare_lines(expect, lines)
-
- set nonumber
- let lines = s:screen_lines(1, 3)
- let expect = [
-\ " 0 aa",
-\ " 1 aa",
-\ " 2 aa",
-\ ]
- call s:compare_lines(expect, lines)
- call s:close_windows()
-endfunc
-
-func Test_numberwidth_adjusted()
- call s:test_windows(10, 20)
- call setline(1, repeat(['aaaa'], 10000))
- setl number numberwidth=4
- let lines = s:screen_lines(1, 3)
- let expect = [
-\ " 1 aa",
-\ " 2 aa",
-\ " 3 aa",
-\ ]
- call s:compare_lines(expect, lines)
-
- $
- let lines = s:screen_lines(8, 10)
- let expect = [
-\ " 9998 aa",
-\ " 9999 aa",
-\ "10000 aa",
-\ ]
- call s:compare_lines(expect, lines)
-
- setl relativenumber
- let lines = s:screen_lines(8, 10)
- let expect = [
-\ " 2 aa",
-\ " 1 aa",
-\ "10000 aa",
-\ ]
- call s:compare_lines(expect, lines)
-
- setl nonumber
- let lines = s:screen_lines(8, 10)
- let expect = [
-\ " 2 aaaa",
-\ " 1 aaaa",
-\ " 0 aaaa",
-\ ]
- 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
-
-func Test_relativenumber_colors()
- CheckScreendump
-
- let lines =<< trim [CODE]
- call setline(1, range(200))
- 111
- set number relativenumber
- hi LineNr ctermfg=red
- [CODE]
- call writefile(lines, 'XTest_relnr')
-
- " Check that the balloon shows up after a mouse move
- let buf = RunVimInTerminal('-S XTest_relnr', {'rows': 10, 'cols': 50})
- call term_wait(buf, 100)
- " Default colors
- call VerifyScreenDump(buf, 'Test_relnr_colors_1', {})
-
- call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>:\<CR>")
- call VerifyScreenDump(buf, 'Test_relnr_colors_2', {})
-
- call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>:\<CR>")
- call VerifyScreenDump(buf, 'Test_relnr_colors_3', {})
-
- call term_sendkeys(buf, ":hi clear LineNrAbove\<CR>")
- call VerifyScreenDump(buf, 'Test_relnr_colors_4', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_relnr')
-endfunc
-
-func Test_relativenumber_callback()
- CheckScreendump
- CheckFeature timers
-
- let lines =<< trim END
- call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
- set relativenumber
- call cursor(4, 1)
-
- func Func(timer)
- call cursor(1, 1)
- endfunc
-
- call timer_start(300, 'Func')
- END
- call writefile(lines, 'Xrnu_timer')
-
- let buf = RunVimInTerminal('-S Xrnu_timer', #{rows: 8})
- call TermWait(buf, 310)
- call VerifyScreenDump(buf, 'Test_relativenumber_callback_1', {})
-
- call StopVimInTerminal(buf)
- call delete('Xrnu_timer')
-endfunc
-
-" Test for displaying line numbers with 'rightleft'
-func Test_number_rightleft()
- CheckFeature rightleft
- new
- setlocal number
- setlocal rightleft
- call setline(1, range(1, 1000))
- normal! 9Gzt
- redraw!
- call assert_match('^\s\+9 9$', Screenline(1))
- normal! 10Gzt
- redraw!
- call assert_match('^\s\+01 10$', Screenline(1))
- normal! 100Gzt
- redraw!
- call assert_match('^\s\+001 100$', Screenline(1))
- normal! 1000Gzt
- redraw!
- call assert_match('^\s\+0001 1000$', Screenline(1))
- bw!
-endfunc
-
-" This used to cause a divide by zero
-func Test_number_no_text_virtual_edit()
- vnew
- call setline(1, ['line one', 'line two'])
- set number virtualedit=all
- normal w
- 4wincmd |
- normal j
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
deleted file mode 100644
index f51de94bac..0000000000
--- a/src/nvim/testdir/test_options.vim
+++ /dev/null
@@ -1,1304 +0,0 @@
-" Test for options
-
-source check.vim
-source view_util.vim
-
-func Test_whichwrap()
- set whichwrap=b,s
- call assert_equal('b,s', &whichwrap)
-
- set whichwrap+=h,l
- call assert_equal('b,s,h,l', &whichwrap)
-
- set whichwrap+=h,l
- call assert_equal('b,s,h,l', &whichwrap)
-
- set whichwrap+=h,l
- call assert_equal('b,s,h,l', &whichwrap)
-
- set whichwrap=h,h
- call assert_equal('h', &whichwrap)
-
- set whichwrap=h,h,h
- call assert_equal('h', &whichwrap)
-
- " For compatibility with Vim 3.0 and before, number values are also
- " supported for 'whichwrap'
- set whichwrap=1
- call assert_equal('b', &whichwrap)
- set whichwrap=2
- call assert_equal('s', &whichwrap)
- set whichwrap=4
- call assert_equal('h,l', &whichwrap)
- set whichwrap=8
- call assert_equal('<,>', &whichwrap)
- set whichwrap=16
- call assert_equal('[,]', &whichwrap)
- set whichwrap=31
- call assert_equal('b,s,h,l,<,>,[,]', &whichwrap)
-
- set whichwrap&
-endfunc
-
-func Test_isfname()
- " This used to cause Vim to access uninitialized memory.
- set isfname=
- call assert_equal("~X", expand("~X"))
- set isfname&
-endfunc
-
-" Test for getting the value of 'pastetoggle'
-func Test_pastetoggle()
- " character with K_SPECIAL byte
- let &pastetoggle = '…'
- call assert_equal('…', &pastetoggle)
- call assert_equal("\n pastetoggle=…", execute('set pastetoggle?'))
-
- " modified character with K_SPECIAL byte
- let &pastetoggle = '<M-…>'
- call assert_equal('<M-…>', &pastetoggle)
- call assert_equal("\n pastetoggle=<M-…>", execute('set pastetoggle?'))
-
- " illegal bytes
- let str = ":\x7f:\x80:\x90:\xd0:"
- let &pastetoggle = str
- call assert_equal(str, &pastetoggle)
- call assert_equal("\n pastetoggle=" .. strtrans(str), execute('set pastetoggle?'))
-
- unlet str
- set pastetoggle&
-endfunc
-
-func Test_wildchar()
- " Empty 'wildchar' used to access invalid memory.
- call assert_fails('set wildchar=', 'E521:')
- call assert_fails('set wildchar=abc', 'E521:')
- set wildchar=<Esc>
- let a=execute('set wildchar?')
- call assert_equal("\n wildchar=<Esc>", a)
- set wildchar=27
- let a=execute('set wildchar?')
- call assert_equal("\n wildchar=<Esc>", a)
- set wildchar&
-endfunc
-
-func Test_wildoptions()
- set wildoptions=
- set wildoptions+=tagfile
- set wildoptions+=tagfile
- call assert_equal('tagfile', &wildoptions)
-endfunc
-
-func Test_options_command()
- let caught = 'ok'
- try
- options
- catch
- let caught = v:throwpoint . "\n" . v:exception
- endtry
- call assert_equal('ok', caught)
-
- " Check if the option-window is opened horizontally.
- wincmd j
- call assert_notequal('option-window', bufname(''))
- wincmd k
- call assert_equal('option-window', bufname(''))
- " close option-window
- close
-
- " Open the option-window vertically.
- vert options
- " Check if the option-window is opened vertically.
- wincmd l
- call assert_notequal('option-window', bufname(''))
- wincmd h
- call assert_equal('option-window', bufname(''))
- " close option-window
- close
-
- " Open the option-window at the top.
- set splitbelow
- topleft options
- call assert_equal(1, winnr())
- close
-
- " Open the option-window at the bottom.
- set nosplitbelow
- botright options
- call assert_equal(winnr('$'), winnr())
- close
- set splitbelow&
-
- " Open the option-window in a new tab.
- tab options
- " Check if the option-window is opened in a tab.
- normal gT
- call assert_notequal('option-window', bufname(''))
- normal gt
- call assert_equal('option-window', bufname(''))
- " close option-window
- close
-
- " Open the options window browse
- if has('browse')
- browse set
- call assert_equal('option-window', bufname(''))
- close
- endif
-endfunc
-
-func Test_path_keep_commas()
- " Test that changing 'path' keeps two commas.
- set path=foo,,bar
- set path-=bar
- set path+=bar
- call assert_equal('foo,,bar', &path)
-
- set path&
-endfunc
-
-func Test_path_too_long()
- exe 'set path=' .. repeat('x', 10000)
- call assert_fails('find x', 'E854:')
- set path&
-endfunc
-
-func Test_signcolumn()
- CheckFeature signs
- call assert_equal("auto", &signcolumn)
- set signcolumn=yes
- set signcolumn=no
- call assert_fails('set signcolumn=nope')
-endfunc
-
-func Test_filetype_valid()
- set ft=valid_name
- call assert_equal("valid_name", &filetype)
- set ft=valid-name
- call assert_equal("valid-name", &filetype)
-
- call assert_fails(":set ft=wrong;name", "E474:")
- call assert_fails(":set ft=wrong\\\\name", "E474:")
- call assert_fails(":set ft=wrong\\|name", "E474:")
- call assert_fails(":set ft=wrong/name", "E474:")
- call assert_fails(":set ft=wrong\\\nname", "E474:")
- call assert_equal("valid-name", &filetype)
-
- exe "set ft=trunc\x00name"
- call assert_equal("trunc", &filetype)
-endfunc
-
-func Test_syntax_valid()
- if !has('syntax')
- return
- endif
- set syn=valid_name
- call assert_equal("valid_name", &syntax)
- set syn=valid-name
- call assert_equal("valid-name", &syntax)
-
- call assert_fails(":set syn=wrong;name", "E474:")
- call assert_fails(":set syn=wrong\\\\name", "E474:")
- call assert_fails(":set syn=wrong\\|name", "E474:")
- call assert_fails(":set syn=wrong/name", "E474:")
- call assert_fails(":set syn=wrong\\\nname", "E474:")
- call assert_equal("valid-name", &syntax)
-
- exe "set syn=trunc\x00name"
- call assert_equal("trunc", &syntax)
-endfunc
-
-func Test_keymap_valid()
- if !has('keymap')
- return
- endif
- call assert_fails(":set kmp=valid_name", "E544:")
- call assert_fails(":set kmp=valid_name", "valid_name")
- call assert_fails(":set kmp=valid-name", "E544:")
- call assert_fails(":set kmp=valid-name", "valid-name")
-
- call assert_fails(":set kmp=wrong;name", "E474:")
- call assert_fails(":set kmp=wrong\\\\name", "E474:")
- call assert_fails(":set kmp=wrong\\|name", "E474:")
- call assert_fails(":set kmp=wrong/name", "E474:")
- call assert_fails(":set kmp=wrong\\\nname", "E474:")
-
- call assert_fails(":set kmp=trunc\x00name", "E544:")
- call assert_fails(":set kmp=trunc\x00name", "trunc")
-endfunc
-
-func Check_dir_option(name)
- " Check that it's possible to set the option.
- exe 'set ' . a:name . '=/usr/share/dict/words'
- call assert_equal('/usr/share/dict/words', eval('&' . a:name))
- exe 'set ' . a:name . '=/usr/share/dict/words,/and/there'
- call assert_equal('/usr/share/dict/words,/and/there', eval('&' . a:name))
- exe 'set ' . a:name . '=/usr/share/dict\ words'
- call assert_equal('/usr/share/dict words', eval('&' . a:name))
-
- " Check rejecting weird characters.
- call assert_fails("set " . a:name . "=/not&there", "E474:")
- call assert_fails("set " . a:name . "=/not>there", "E474:")
- call assert_fails("set " . a:name . "=/not.*there", "E474:")
-endfunc
-
-func Test_cinkeys()
- " This used to cause invalid memory access
- set cindent cinkeys=0
- norm a
- set cindent& cinkeys&
-endfunc
-
-func Test_dictionary()
- call Check_dir_option('dictionary')
-endfunc
-
-func Test_thesaurus()
- call Check_dir_option('thesaurus')
-endfun
-
-func Test_complete()
- " Trailing single backslash used to cause invalid memory access.
- set complete=s\
- new
- call feedkeys("i\<C-N>\<Esc>", 'xt')
- bwipe!
- call assert_fails('set complete=ix', 'E535:')
- set complete&
-endfun
-
-func Test_set_completion()
- call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:)
-
- call feedkeys(":setlocal di\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"setlocal dictionary diff diffexpr diffopt digraph directory display', @:)
-
- call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:)
-
- " Expand boolean options. When doing :set no<Tab>
- " vim displays the options names without "no" but completion uses "no...".
- call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set nodiff digraph', @:)
-
- call feedkeys(":set invdi\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set invdiff digraph', @:)
-
- " Expand abbreviation of options.
- call feedkeys(":set ts\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set tabstop thesaurus thesaurusfunc', @:)
-
- " Expand current value
- call feedkeys(":set fileencodings=\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set fileencodings=ucs-bom,utf-8,default,latin1', @:)
-
- call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:)
-
- " Expand key codes.
- " call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx')
- " call assert_equal('"set <Help> <Home>', @:)
-
- " Expand terminal options.
- " call feedkeys(":set t_A\<C-A>\<C-B>\"\<CR>", 'tx')
- " call assert_equal('"set t_AB t_AF t_AU t_AL', @:)
- " call assert_fails('call feedkeys(":set <t_afoo>=\<C-A>\<CR>", "xt")', 'E474:')
-
- " Expand directories.
- call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('./samples/ ', @:)
- call assert_notmatch('./small.vim ', @:)
-
- " Expand files and directories.
- call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('./samples/ ./sautest/ ./screendump.vim ./script_util.vim ./setup.vim ./shared.vim', @:)
-
- call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:)
-
- set tags&
-
- " Expand values for 'filetype'
- call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set filetype=sshdconfig', @:)
- call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:)
-endfunc
-
-func Test_set_errors()
- call assert_fails('set scroll=-1', 'E49:')
- call assert_fails('set backupcopy=', 'E474:')
- call assert_fails('set regexpengine=3', 'E474:')
- call assert_fails('set history=10001', 'E474:')
- call assert_fails('set numberwidth=21', 'E474:')
- call assert_fails('set colorcolumn=-a', 'E474:')
- call assert_fails('set colorcolumn=a', 'E474:')
- call assert_fails('set colorcolumn=1,', 'E474:')
- call assert_fails('set colorcolumn=1;', 'E474:')
- call assert_fails('set cmdheight=-1', 'E487:')
- call assert_fails('set cmdwinheight=-1', 'E487:')
- if has('conceal')
- call assert_fails('set conceallevel=-1', 'E487:')
- call assert_fails('set conceallevel=4', 'E474:')
- endif
- call assert_fails('set helpheight=-1', 'E487:')
- call assert_fails('set history=-1', 'E487:')
- call assert_fails('set report=-1', 'E487:')
- call assert_fails('set shiftwidth=-1', 'E487:')
- call assert_fails('set sidescroll=-1', 'E487:')
- call assert_fails('set tabstop=-1', 'E487:')
- call assert_fails('set tabstop=10000', 'E474:')
- call assert_fails('let &tabstop = 10000', 'E474:')
- call assert_fails('set tabstop=5500000000', 'E474:')
- call assert_fails('set textwidth=-1', 'E487:')
- call assert_fails('set timeoutlen=-1', 'E487:')
- call assert_fails('set updatecount=-1', 'E487:')
- call assert_fails('set updatetime=-1', 'E487:')
- call assert_fails('set winheight=-1', 'E487:')
- call assert_fails('set tabstop!', 'E488:')
- call assert_fails('set xxx', 'E518:')
- call assert_fails('set beautify?', 'E518:')
- call assert_fails('set undolevels=x', 'E521:')
- call assert_fails('set tabstop=', 'E521:')
- call assert_fails('set comments=-', 'E524:')
- call assert_fails('set comments=a', 'E525:')
- call assert_fails('set foldmarker=x', 'E536:')
- call assert_fails('set commentstring=x', 'E537:')
- call assert_fails('let &commentstring = "x"', 'E537:')
- call assert_fails('set complete=x', 'E539:')
- call assert_fails('set rulerformat=%-', 'E539:')
- call assert_fails('set rulerformat=%(', 'E542:')
- call assert_fails('set rulerformat=%15(%%', 'E542:')
- call assert_fails('set statusline=%$', 'E539:')
- call assert_fails('set statusline=%{', 'E540:')
- call assert_fails('set statusline=%{%', 'E540:')
- call assert_fails('set statusline=%{%}', 'E539:')
- call assert_fails('set statusline=%(', 'E542:')
- call assert_fails('set statusline=%)', 'E542:')
- call assert_fails('set tabline=%$', 'E539:')
- call assert_fails('set tabline=%{', 'E540:')
- call assert_fails('set tabline=%{%', 'E540:')
- call assert_fails('set tabline=%{%}', 'E539:')
- call assert_fails('set tabline=%(', 'E542:')
- call assert_fails('set tabline=%)', 'E542:')
-
- if has('cursorshape')
- " This invalid value for 'guicursor' used to cause Vim to crash.
- call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:')
- call assert_fails('set guicursor=i-ci', 'E545:')
- call assert_fails('set guicursor=x', 'E545:')
- call assert_fails('set guicursor=x:', 'E546:')
- call assert_fails('set guicursor=r-cr:horx', 'E548:')
- call assert_fails('set guicursor=r-cr:hor0', 'E549:')
- endif
- if has('mouseshape')
- call assert_fails('se mouseshape=i-r:x', 'E547:')
- endif
- call assert_fails('set backupext=~ patchmode=~', 'E589:')
- call assert_fails('set winminheight=10 winheight=9', 'E591:')
- call assert_fails('set winminwidth=10 winwidth=9', 'E592:')
- call assert_fails("set showbreak=\x01", 'E595:')
- call assert_fails('set t_foo=', 'E846:')
- call assert_fails('set tabstop??', 'E488:')
- call assert_fails('set wrapscan!!', 'E488:')
- call assert_fails('set tabstop&&', 'E488:')
- call assert_fails('set wrapscan<<', 'E488:')
- call assert_fails('set wrapscan=1', 'E474:')
- call assert_fails('set autoindent@', 'E488:')
- call assert_fails('set wildchar=<abc>', 'E474:')
- call assert_fails('set cmdheight=1a', 'E521:')
- call assert_fails('set invcmdheight', 'E474:')
- if has('python') || has('python3')
- call assert_fails('set pyxversion=6', 'E474:')
- endif
- call assert_fails("let &tabstop='ab'", 'E521:')
- call assert_fails('set spellcapcheck=%\\(', 'E54:')
- call assert_fails('set sessionoptions=curdir,sesdir', 'E474:')
- call assert_fails('set foldmarker={{{,', 'E474:')
- call assert_fails('set sessionoptions=sesdir,curdir', 'E474:')
- setlocal listchars=trail:·
- call assert_fails('set ambiwidth=double', 'E834:')
- setlocal listchars=trail:-
- setglobal listchars=trail:·
- call assert_fails('set ambiwidth=double', 'E834:')
- set listchars&
- setlocal fillchars=stl:·
- call assert_fails('set ambiwidth=double', 'E835:')
- setlocal fillchars=stl:-
- setglobal fillchars=stl:·
- call assert_fails('set ambiwidth=double', 'E835:')
- set fillchars&
- call assert_fails('set fileencoding=latin1,utf-8', 'E474:')
- set nomodifiable
- call assert_fails('set fileencoding=latin1', 'E21:')
- set modifiable&
- " call assert_fails('set t_#-&', 'E522:')
-endfunc
-
-func CheckWasSet(name)
- let verb_cm = execute('verbose set ' .. a:name .. '?')
- call assert_match('Last set from.*test_options.vim', verb_cm)
-endfunc
-func CheckWasNotSet(name)
- let verb_cm = execute('verbose set ' .. a:name .. '?')
- call assert_notmatch('Last set from', verb_cm)
-endfunc
-
-" Must be executed before other tests that set 'term'.
-func Test_000_term_option_verbose()
- throw "Skipped: Nvim does not support setting 'term'"
- CheckNotGui
-
- call CheckWasNotSet('t_cm')
-
- let term_save = &term
- set term=ansi
- call CheckWasSet('t_cm')
- let &term = term_save
-endfunc
-
-func Test_copy_context()
- setlocal list
- call CheckWasSet('list')
- split
- call CheckWasSet('list')
- quit
- setlocal nolist
-
- set ai
- call CheckWasSet('ai')
- set filetype=perl
- call CheckWasSet('filetype')
- set fo=tcroq
- call CheckWasSet('fo')
-
- split Xsomebuf
- call CheckWasSet('ai')
- call CheckWasNotSet('filetype')
- call CheckWasSet('fo')
-endfunc
-
-func Test_set_ttytype()
- throw "Skipped: Nvim does not support 'ttytype'"
- CheckUnix
- CheckNotGui
-
- " Setting 'ttytype' used to cause a double-free when exiting vim and
- " when vim is compiled with -DEXITFREE.
- set ttytype=ansi
- call assert_equal('ansi', &ttytype)
- call assert_equal(&ttytype, &term)
- set ttytype=xterm
- call assert_equal('xterm', &ttytype)
- call assert_equal(&ttytype, &term)
- try
- set ttytype=
- call assert_report('set ttytype= did not fail')
- catch /E529/
- endtry
-
- " Some systems accept any terminal name and return dumb settings,
- " check for failure of finding the entry and for missing 'cm' entry.
- try
- set ttytype=xxx
- call assert_report('set ttytype=xxx did not fail')
- catch /E522\|E437/
- endtry
-
- set ttytype&
- call assert_equal(&ttytype, &term)
-
- if has('gui') && !has('gui_running')
- call assert_fails('set term=gui', 'E531:')
- endif
-endfunc
-
-func Test_set_all()
- set tw=75
- set iskeyword=a-z,A-Z
- set nosplitbelow
- let out = execute('set all')
- call assert_match('textwidth=75', out)
- call assert_match('iskeyword=a-z,A-Z', out)
- call assert_match('nosplitbelow', out)
- set tw& iskeyword& splitbelow&
-endfunc
-
-func Test_set_one_column()
- let out_mult = execute('set all')->split("\n")
- let out_one = execute('set! all')->split("\n")
- " one column should be two to four times as many lines
- call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one))
-endfunc
-
-func Test_set_values()
- " opt_test.vim is generated from ../optiondefs.h using gen_opt_test.vim
- if filereadable('opt_test.vim')
- source opt_test.vim
- else
- throw 'Skipped: opt_test.vim does not exist'
- endif
-endfunc
-
-func Test_renderoptions()
- throw 'skipped: Nvim does not support renderoptions'
- " Only do this for Windows Vista and later, fails on Windows XP and earlier.
- " Doesn't hurt to do this on a non-Windows system.
- if windowsversion() !~ '^[345]\.'
- set renderoptions=type:directx
- set rop=type:directx
- endif
-endfunc
-
-func ResetIndentexpr()
- set indentexpr=
-endfunc
-
-func Test_set_indentexpr()
- " this was causing usage of freed memory
- set indentexpr=ResetIndentexpr()
- new
- call feedkeys("i\<c-f>", 'x')
- call assert_equal('', &indentexpr)
- bwipe!
-endfunc
-
-func Test_backupskip()
- " Option 'backupskip' may contain several comma-separated path
- " specifications if one or more of the environment variables TMPDIR, TMP,
- " or TEMP is defined. To simplify testing, convert the string value into a
- " list.
- let bsklist = split(&bsk, ',')
-
- if has("mac")
- let found = (index(bsklist, '/private/tmp/*') >= 0)
- call assert_true(found, '/private/tmp not in option bsk: ' . &bsk)
- elseif has("unix")
- let found = (index(bsklist, '/tmp/*') >= 0)
- call assert_true(found, '/tmp not in option bsk: ' . &bsk)
- endif
-
- " If our test platform is Windows, the path(s) in option bsk will use
- " backslash for the path separator and the components could be in short
- " (8.3) format. As such, we need to replace the backslashes with forward
- " slashes and convert the path components to long format. The expand()
- " function will do this but it cannot handle comma-separated paths. This is
- " why bsk was converted from a string into a list of strings above.
- "
- " One final complication is that the wildcard "/*" is at the end of each
- " path and so expand() might return a list of matching files. To prevent
- " this, we need to remove the wildcard before calling expand() and then
- " append it afterwards.
- if has('win32')
- let item_nbr = 0
- while item_nbr < len(bsklist)
- let path_spec = bsklist[item_nbr]
- let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2)
- let path_spec = substitute(expand(path_spec), '\\', '/', 'g')
- let bsklist[item_nbr] = path_spec . '/*'
- let item_nbr += 1
- endwhile
- endif
-
- " Option bsk will also include these environment variables if defined.
- " If they're defined, verify they appear in the option value.
- for var in ['$TMPDIR', '$TMP', '$TEMP']
- if exists(var)
- let varvalue = substitute(expand(var), '\\', '/', 'g')
- let varvalue = substitute(varvalue, '/$', '', '')
- let varvalue .= '/*'
- let found = (index(bsklist, varvalue) >= 0)
- call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk)
- endif
- endfor
-
- " Duplicates from environment variables should be filtered out (option has
- " P_NODUP). Run this in a separate instance and write v:errors in a file,
- " so that we see what happens on startup.
- let after =<< trim [CODE]
- let bsklist = split(&backupskip, ',')
- call assert_equal(uniq(copy(bsklist)), bsklist)
- call writefile(['errors:'] + v:errors, 'Xtestout')
- qall
- [CODE]
- call writefile(after, 'Xafter')
- " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
- let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"'
-
- let saveenv = {}
- for var in ['TMPDIR', 'TMP', 'TEMP']
- let saveenv[var] = getenv(var)
- call setenv(var, '/duplicate/path')
- endfor
-
- exe 'silent !' . cmd
- call assert_equal(['errors:'], readfile('Xtestout'))
-
- " restore environment variables
- for var in ['TMPDIR', 'TMP', 'TEMP']
- call setenv(var, saveenv[var])
- endfor
-
- call delete('Xtestout')
- call delete('Xafter')
-
- " Duplicates should be filtered out (option has P_NODUP)
- let backupskip = &backupskip
- set backupskip=
- set backupskip+=/test/dir
- set backupskip+=/other/dir
- set backupskip+=/test/dir
- call assert_equal('/test/dir,/other/dir', &backupskip)
- let &backupskip = backupskip
-endfunc
-
-func Test_copy_winopt()
- set hidden
-
- " Test copy option from current buffer in window
- split
- enew
- setlocal numberwidth=5
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(5,&numberwidth)
- bw!
- call assert_equal(4,&numberwidth)
-
- " Test copy value from window that used to be display the buffer
- split
- enew
- setlocal numberwidth=6
- bnext
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(6,&numberwidth)
- bw!
-
- " Test that if buffer is current, don't use the stale cached value
- " from the last time the buffer was displayed.
- split
- enew
- setlocal numberwidth=7
- bnext
- bnext
- setlocal numberwidth=8
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(8,&numberwidth)
- bw!
-
- " Test value is not copied if window already has seen the buffer
- enew
- split
- setlocal numberwidth=9
- bnext
- setlocal numberwidth=10
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(4,&numberwidth)
- bw!
-
- set hidden&
-endfunc
-
-func Test_shortmess_F()
- new
- call assert_match('\[No Name\]', execute('file'))
- set shortmess+=F
- call assert_match('\[No Name\]', execute('file'))
- call assert_match('^\s*$', execute('file foo'))
- call assert_match('foo', execute('file'))
- set shortmess-=F
- call assert_match('bar', execute('file bar'))
- call assert_match('bar', execute('file'))
- set shortmess&
- bwipe
-endfunc
-
-func Test_shortmess_F2()
- e file1
- e file2
- " Accommodate Nvim default.
- set shortmess-=F
- call assert_match('file1', execute('bn', ''))
- call assert_match('file2', execute('bn', ''))
- set shortmess+=F
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- set hidden
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- set nohidden
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- " Accommodate Nvim default.
- set shortmess-=F
- call assert_match('file1', execute('bn', ''))
- call assert_match('file2', execute('bn', ''))
- bwipe
- bwipe
-endfunc
-
-func Test_local_scrolloff()
- set so=5
- set siso=7
- split
- call assert_equal(5, &so)
- setlocal so=3
- call assert_equal(3, &so)
- wincmd w
- call assert_equal(5, &so)
- wincmd w
- setlocal so<
- call assert_equal(5, &so)
- setlocal so=0
- call assert_equal(0, &so)
- setlocal so=-1
- call assert_equal(5, &so)
-
- call assert_equal(7, &siso)
- setlocal siso=3
- call assert_equal(3, &siso)
- wincmd w
- call assert_equal(7, &siso)
- wincmd w
- setlocal siso<
- call assert_equal(7, &siso)
- setlocal siso=0
- call assert_equal(0, &siso)
- setlocal siso=-1
- call assert_equal(7, &siso)
-
- close
- set so&
- set siso&
-endfunc
-
-func Test_visualbell()
- set belloff=
- set visualbell
- call assert_beeps('normal 0h')
- set novisualbell
- set belloff=all
-endfunc
-
-" Test for the 'write' option
-func Test_write()
- new
- call setline(1, ['L1'])
- set nowrite
- call assert_fails('write Xfile', 'E142:')
- set write
- close!
-endfunc
-
-" Test for 'buftype' option
-func Test_buftype()
- new
- call setline(1, ['L1'])
- set buftype=nowrite
- call assert_fails('write', 'E382:')
-
- " for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'terminal', 'prompt', 'popup']
- for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'prompt']
- exe 'set buftype=' .. val
- call writefile(['something'], 'XBuftype')
- call assert_fails('write XBuftype', 'E13:', 'with buftype=' .. val)
- endfor
-
- call delete('XBuftype')
- bwipe!
-endfunc
-
-" Test for the 'shell' option
-func Test_shell()
- throw 'Skipped: Nvim does not have :shell'
- CheckUnix
- let save_shell = &shell
- set shell=
- let caught_e91 = 0
- try
- shell
- catch /E91:/
- let caught_e91 = 1
- endtry
- call assert_equal(1, caught_e91)
- let &shell = save_shell
-endfunc
-
-" Test for the 'shellquote' option
-func Test_shellquote()
- CheckUnix
- set shellquote=#
- set verbose=20
- redir => v
- silent! !echo Hello
- redir END
- set verbose&
- set shellquote&
- call assert_match(': "#echo Hello#"', v)
-endfunc
-
-" Test for the 'rightleftcmd' option
-func Test_rightleftcmd()
- CheckFeature rightleft
- set rightleft
-
- let g:l = []
- func AddPos()
- call add(g:l, screencol())
- return ''
- endfunc
- cmap <expr> <F2> AddPos()
-
- set rightleftcmd=
- call feedkeys("/\<F2>abc\<Right>\<F2>\<Left>\<Left>\<F2>" ..
- \ "\<Right>\<F2>\<Esc>", 'xt')
- call assert_equal([2, 5, 3, 4], g:l)
-
- let g:l = []
- set rightleftcmd=search
- call feedkeys("/\<F2>abc\<Left>\<F2>\<Right>\<Right>\<F2>" ..
- \ "\<Left>\<F2>\<Esc>", 'xt')
- call assert_equal([&co - 1, &co - 4, &co - 2, &co - 3], g:l)
-
- cunmap <F2>
- unlet g:l
- set rightleftcmd&
- set rightleft&
-endfunc
-
-" Test for the 'debug' option
-func Test_debug_option()
- " redraw to avoid matching previous messages
- redraw
- set debug=beep
- exe "normal \<C-c>"
- call assert_equal('Beep!', Screenline(&lines))
- call assert_equal('line 4:', Screenline(&lines - 1))
- " only match the final colon in the line that shows the source
- call assert_match(':$', Screenline(&lines - 2))
- set debug&
-endfunc
-
-" Test for the default CDPATH option
-func Test_opt_default_cdpath()
- let after =<< trim [CODE]
- call assert_equal(',/path/to/dir1,/path/to/dir2', &cdpath)
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- if has('unix')
- let $CDPATH='/path/to/dir1:/path/to/dir2'
- else
- let $CDPATH='/path/to/dir1;/path/to/dir2'
- endif
- if RunVim([], after, '')
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for setting keycodes using set
-func Test_opt_set_keycode()
- call assert_fails('set <t_k1=l', 'E474:')
- call assert_fails('set <Home=l', 'E474:')
- set <t_k9>=abcd
- " call assert_equal('abcd', &t_k9)
- set <t_k9>&
- set <F9>=xyz
- " call assert_equal('xyz', &t_k9)
- set <t_k9>&
-endfunc
-
-" Test for changing options in a sandbox
-func Test_opt_sandbox()
- for opt in ['backupdir', 'cdpath', 'exrc']
- call assert_fails('sandbox set ' .. opt .. '?', 'E48:')
- endfor
-endfunc
-
-" Test for setting an option with local value to global value
-func Test_opt_local_to_global()
- setglobal equalprg=gprg
- setlocal equalprg=lprg
- call assert_equal('gprg', &g:equalprg)
- call assert_equal('lprg', &l:equalprg)
- call assert_equal('lprg', &equalprg)
- set equalprg<
- call assert_equal('', &l:equalprg)
- call assert_equal('gprg', &equalprg)
- setglobal equalprg=gnewprg
- setlocal equalprg=lnewprg
- setlocal equalprg<
- call assert_equal('gnewprg', &l:equalprg)
- call assert_equal('gnewprg', &equalprg)
- set equalprg&
-
- " Test for setting the global/local value of a boolean option
- setglobal autoread
- setlocal noautoread
- call assert_false(&autoread)
- set autoread<
- call assert_true(&autoread)
- setglobal noautoread
- setlocal autoread
- setlocal autoread<
- call assert_false(&autoread)
- set autoread&
-endfunc
-
-func Test_set_in_sandbox()
- " Some boolean options cannot be set in sandbox, some can.
- call assert_fails('sandbox set modelineexpr', 'E48:')
- sandbox set number
- call assert_true(&number)
- set number&
-
- " Some boolean options cannot be set in sandbox, some can.
- if has('python') || has('python3')
- call assert_fails('sandbox set pyxversion=3', 'E48:')
- endif
- sandbox set tabstop=4
- call assert_equal(4, &tabstop)
- set tabstop&
-
- " Some string options cannot be set in sandbox, some can.
- call assert_fails('sandbox set backupdir=/tmp', 'E48:')
- sandbox set filetype=perl
- call assert_equal('perl', &filetype)
- set filetype&
-endfunc
-
-" Test for incrementing, decrementing and multiplying a number option value
-func Test_opt_num_op()
- set shiftwidth=4
- set sw+=2
- call assert_equal(6, &sw)
- set sw-=2
- call assert_equal(4, &sw)
- set sw^=2
- call assert_equal(8, &sw)
- set shiftwidth&
-endfunc
-
-" Test for setting option values using v:false and v:true
-func Test_opt_boolean()
- set number&
- set number
- call assert_equal(1, &nu)
- set nonu
- call assert_equal(0, &nu)
- let &nu = v:true
- call assert_equal(1, &nu)
- let &nu = v:false
- call assert_equal(0, &nu)
- set number&
-endfunc
-
-" Test for the 'window' option
-func Test_window_opt()
- " Needs only one open widow
- %bw!
- call setline(1, range(1, 8))
- set window=5
- exe "normal \<C-F>"
- call assert_equal(4, line('w0'))
- exe "normal \<C-F>"
- call assert_equal(7, line('w0'))
- exe "normal \<C-F>"
- call assert_equal(8, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(5, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(2, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(1, line('w0'))
- set window=1
- exe "normal gg\<C-F>"
- call assert_equal(2, line('w0'))
- exe "normal \<C-F>"
- call assert_equal(3, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(2, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(1, line('w0'))
- enew!
- set window&
-endfunc
-
-" Test for the 'winminheight' option
-func Test_opt_winminheight()
- only!
- let &winheight = &lines + 4
- call assert_fails('let &winminheight = &lines + 2', 'E36:')
- call assert_true(&winminheight <= &lines)
- set winminheight&
- set winheight&
-endfunc
-
-func Test_opt_winminheight_term()
- " See test/functional/legacy/options_spec.lua
- CheckRunVimInTerminal
-
- " The tabline should be taken into account.
- let lines =<< trim END
- set wmh=0 stal=2
- below sp | wincmd _
- below sp | wincmd _
- below sp | wincmd _
- below sp
- END
- call writefile(lines, 'Xwinminheight')
- let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
- call term_sendkeys(buf, ":set wmh=1\n")
- call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
-
- call StopVimInTerminal(buf)
- call delete('Xwinminheight')
-endfunc
-
-func Test_opt_winminheight_term_tabs()
- " See test/functional/legacy/options_spec.lua
- CheckRunVimInTerminal
-
- " The tabline should be taken into account.
- let lines =<< trim END
- set wmh=0 stal=2
- split
- split
- split
- split
- tabnew
- END
- call writefile(lines, 'Xwinminheight')
- let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
- call term_sendkeys(buf, ":set wmh=1\n")
- call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
-
- call StopVimInTerminal(buf)
- call delete('Xwinminheight')
-endfunc
-
-" Test for the 'winminwidth' option
-func Test_opt_winminwidth()
- only!
- let &winwidth = &columns + 4
- call assert_fails('let &winminwidth = &columns + 2', 'E36:')
- call assert_true(&winminwidth <= &columns)
- set winminwidth&
- set winwidth&
-endfunc
-
-" Test for setting option value containing spaces with isfname+=32
-func Test_isfname_with_options()
- set isfname+=32
- setlocal keywordprg=:term\ help.exe
- call assert_equal(':term help.exe', &keywordprg)
- set isfname&
- setlocal keywordprg&
-endfunc
-
-" Test that resetting laststatus does change scroll option
-func Test_opt_reset_scroll()
- " See test/functional/legacy/options_spec.lua
- CheckRunVimInTerminal
- let vimrc =<< trim [CODE]
- set scroll=2
- set laststatus=2
- [CODE]
- call writefile(vimrc, 'Xscroll')
- let buf = RunVimInTerminal('-S Xscroll', {'rows': 16, 'cols': 45})
- call term_sendkeys(buf, ":verbose set scroll?\n")
- call WaitForAssert({-> assert_match('Last set.*window size', term_getline(buf, 15))})
- call assert_match('^\s*scroll=7$', term_getline(buf, 14))
- call StopVimInTerminal(buf)
-
- " clean up
- call delete('Xscroll')
-endfunc
-
-" Check that VIM_POSIX env variable influences default value of 'cpo' and 'shm'
-func Test_VIM_POSIX()
- throw 'Skipped: Nvim does not support $VIM_POSIX'
- let saved_VIM_POSIX = getenv("VIM_POSIX")
-
- call setenv('VIM_POSIX', "1")
- let after =<< trim [CODE]
- call writefile([&cpo, &shm], 'X_VIM_POSIX')
- qall
- [CODE]
- if RunVim([], after, '')
- call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;',
- \ 'AS'], readfile('X_VIM_POSIX'))
- endif
-
- call setenv('VIM_POSIX', v:null)
- let after =<< trim [CODE]
- call writefile([&cpo, &shm], 'X_VIM_POSIX')
- qall
- [CODE]
- if RunVim([], after, '')
- call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;',
- \ 'S'], readfile('X_VIM_POSIX'))
- endif
-
- call delete('X_VIM_POSIX')
- call setenv('VIM_POSIX', saved_VIM_POSIX)
-endfunc
-
-" Test for setting an option to a Vi or Vim default
-func Test_opt_default()
- throw 'Skipped: Nvim has different defaults'
- set formatoptions&vi
- call assert_equal('vt', &formatoptions)
- set formatoptions&vim
- call assert_equal('tcq', &formatoptions)
-endfunc
-
-" Test for the 'cmdheight' option
-func Test_cmdheight()
- %bw!
- let ht = &lines
- set cmdheight=9999
- call assert_equal(1, winheight(0))
- call assert_equal(ht - 1, &cmdheight)
- set cmdheight&
-endfunc
-
-" To specify a control character as a option value, '^' can be used
-func Test_opt_control_char()
- set wildchar=^v
- call assert_equal("\<C-V>", nr2char(&wildchar))
- set wildcharm=^r
- call assert_equal("\<C-R>", nr2char(&wildcharm))
- " Bug: This doesn't work for the 'cedit' and 'termwinkey' options
- set wildchar& wildcharm&
-endfunc
-
-" Test for the 'errorbells' option
-func Test_opt_errorbells()
- set errorbells
- call assert_beeps('s/a1b2/x1y2/')
- set noerrorbells
-endfunc
-
-func Test_opt_scrolljump()
- help
- resize 10
-
- " Test with positive 'scrolljump'.
- set scrolljump=2
- norm! Lj
- call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
- \ 'topline':3, 'coladd':0, 'skipcol':0, 'curswant':0},
- \ winsaveview())
-
- " Test with negative 'scrolljump' (percentage of window height).
- set scrolljump=-40
- norm! ggLj
- call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
- \ 'topline':5, 'coladd':0, 'skipcol':0, 'curswant':0},
- \ winsaveview())
-
- set scrolljump&
- bw
-endfunc
-
-" Test for the 'cdhome' option
-func Test_opt_cdhome()
- if has('unix') || has('vms')
- throw 'Skipped: only works on non-Unix'
- endif
-
- set cdhome&
- call assert_equal(0, &cdhome)
- set cdhome
-
- " This paragraph is copied from Test_cd_no_arg().
- let path = getcwd()
- cd
- call assert_equal($HOME, getcwd())
- call assert_notequal(path, getcwd())
- exe 'cd ' .. fnameescape(path)
- call assert_equal(path, getcwd())
-
- set cdhome&
-endfunc
-
-func Test_set_completion_2()
- CheckOption termguicolors
-
- " Test default option completion
- set wildoptions=
- call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set termguicolors', @:)
-
- call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set notermguicolors', @:)
-
- " Test fuzzy option completion
- set wildoptions=fuzzy
- call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
- " Nvim doesn't have 'termencoding'
- " call assert_equal('"set termguicolors termencoding', @:)
- call assert_equal('"set termguicolors', @:)
-
- call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set notermguicolors', @:)
-
- set wildoptions=
-endfunc
-
-func Test_switchbuf_reset()
- set switchbuf=useopen
- sblast
- call assert_equal(1, winnr('$'))
- set all&
- " Nvim has a different default for 'switchbuf'
- " call assert_equal('', &switchbuf)
- call assert_equal('uselast', &switchbuf)
- sblast
- call assert_equal(2, winnr('$'))
- only!
-endfunc
-
-" :set empty string for global 'keywordprg' falls back to ":help"
-func Test_keywordprg_empty()
- let k = &keywordprg
- set keywordprg=man
- call assert_equal('man', &keywordprg)
- set keywordprg=
- call assert_equal(':help', &keywordprg)
- set keywordprg=man
- call assert_equal('man', &keywordprg)
- call assert_equal("\n keywordprg=:help", execute('set kp= kp?'))
- let &keywordprg = k
-endfunc
-
-" check that the very first buffer created does not have 'endoffile' set
-func Test_endoffile_default()
- let after =<< trim [CODE]
- call writefile([execute('set eof?')], 'Xtestout')
- qall!
- [CODE]
- if RunVim([], after, '')
- call assert_equal(["\nnoendoffile"], readfile('Xtestout'))
- endif
- call delete('Xtestout')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_packadd.vim b/src/nvim/testdir/test_packadd.vim
deleted file mode 100644
index fcb8b8033b..0000000000
--- a/src/nvim/testdir/test_packadd.vim
+++ /dev/null
@@ -1,361 +0,0 @@
-" Tests for 'packpath' and :packadd
-
-
-func SetUp()
- let s:topdir = getcwd() . '/Xdir'
- exe 'set packpath=' . s:topdir
- let s:plugdir = s:topdir . '/pack/mine/opt/mytest'
-endfunc
-
-func TearDown()
- call delete(s:topdir, 'rf')
-endfunc
-
-func Test_packadd()
- if !exists('s:plugdir')
- echomsg 'when running this test manually, call SetUp() first'
- return
- endif
-
- call mkdir(s:plugdir . '/plugin/also', 'p')
- call mkdir(s:plugdir . '/ftdetect', 'p')
- call mkdir(s:plugdir . '/after', 'p')
- set rtp&
- let rtp = &rtp
- filetype on
-
- let rtp_entries = split(rtp, ',')
- for entry in rtp_entries
- if entry =~? '\<after\>'
- let first_after_entry = entry
- break
- endif
- endfor
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 42')
- wq
-
- exe 'split ' . s:plugdir . '/plugin/also/loaded.vim'
- call setline(1, 'let g:plugin_also_works = 77')
- wq
-
- exe 'split ' . s:plugdir . '/ftdetect/test.vim'
- call setline(1, 'let g:ftdetect_works = 17')
- wq
-
- packadd mytest
-
- call assert_equal(42, g:plugin_works)
- call assert_equal(77, g:plugin_also_works)
- call assert_equal(17, g:ftdetect_works)
- call assert_true(len(&rtp) > len(rtp))
- call assert_match('/testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
-
- let new_after = match(&rtp, '/testdir/Xdir/pack/mine/opt/mytest/after,')
- let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g')
- let old_after = match(&rtp, ',' . forwarded . '\>')
- call assert_true(new_after > 0, 'rtp is ' . &rtp)
- call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp)
- call assert_true(new_after < old_after, 'rtp is ' . &rtp)
-
- " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest'
- call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p')
- let rtp = &rtp
- packadd myte
-
- " Check the path of 'myte' is added
- call assert_true(len(&rtp) > len(rtp))
- call assert_match('/testdir/Xdir/pack/mine/opt/myte\($\|,\)', &rtp)
-
- " Check exception
- call assert_fails("packadd directorynotfound", 'E919:')
- call assert_fails("packadd", 'E471:')
-endfunc
-
-func Test_packadd_start()
- let plugdir = s:topdir . '/pack/mine/start/other'
- call mkdir(plugdir . '/plugin', 'p')
- set rtp&
- let rtp = &rtp
- filetype on
-
- exe 'split ' . plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 24')
- wq
-
- packadd other
-
- call assert_equal(24, g:plugin_works)
- call assert_true(len(&rtp) > len(rtp))
- call assert_match('/testdir/Xdir/pack/mine/start/other\($\|,\)', &rtp)
-endfunc
-
-func Test_packadd_noload()
- call mkdir(s:plugdir . '/plugin', 'p')
- call mkdir(s:plugdir . '/syntax', 'p')
- set rtp&
- let rtp = &rtp
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 42')
- wq
- let g:plugin_works = 0
-
- packadd! mytest
-
- call assert_true(len(&rtp) > len(rtp))
- call assert_match('testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp)
- call assert_equal(0, g:plugin_works)
-
- " check the path is not added twice
- let new_rtp = &rtp
- packadd! mytest
- call assert_equal(new_rtp, &rtp)
-endfunc
-
-func Test_packadd_symlink_dir()
- if !has('unix')
- return
- endif
- let top2_dir = s:topdir . '/Xdir2'
- let real_dir = s:topdir . '/Xsym'
- call mkdir(real_dir, 'p')
- exec "silent !ln -s Xsym" top2_dir
- let &rtp = top2_dir . ',' . top2_dir . '/after'
- let &packpath = &rtp
-
- let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
- call mkdir(s:plugdir . '/plugin', 'p')
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 44')
- wq
- let g:plugin_works = 0
-
- packadd mytest
-
- " Must have been inserted in the middle, not at the end
- call assert_match('/pack/mine/opt/mytest,', &rtp)
- call assert_equal(44, g:plugin_works)
-
- " No change when doing it again.
- let rtp_before = &rtp
- packadd mytest
- call assert_equal(rtp_before, &rtp)
-
- set rtp&
- let rtp = &rtp
- exec "silent !rm" top2_dir
-endfunc
-
-func Test_packadd_symlink_dir2()
- if !has('unix')
- return
- endif
- let top2_dir = s:topdir . '/Xdir2'
- let real_dir = s:topdir . '/Xsym/pack'
- call mkdir(top2_dir, 'p')
- call mkdir(real_dir, 'p')
- let &rtp = top2_dir . ',' . top2_dir . '/after'
- let &packpath = &rtp
-
- exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack'
- let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
- call mkdir(s:plugdir . '/plugin', 'p')
-
- exe 'split ' . s:plugdir . '/plugin/test.vim'
- call setline(1, 'let g:plugin_works = 48')
- wq
- let g:plugin_works = 0
-
- packadd mytest
-
- " Must have been inserted in the middle, not at the end
- call assert_match('/Xdir2/pack/mine/opt/mytest,', &rtp)
- call assert_equal(48, g:plugin_works)
-
- " No change when doing it again.
- let rtp_before = &rtp
- packadd mytest
- call assert_equal(rtp_before, &rtp)
-
- set rtp&
- let rtp = &rtp
- exec "silent !rm" top2_dir . '/pack'
- exec "silent !rmdir" top2_dir
-endfunc
-
-" Check command-line completion for 'packadd'
-func Test_packadd_completion()
- let optdir1 = &packpath . '/pack/mine/opt'
- let optdir2 = &packpath . '/pack/candidate/opt'
-
- call mkdir(optdir1 . '/pluginA', 'p')
- call mkdir(optdir1 . '/pluginC', 'p')
- call mkdir(optdir2 . '/pluginB', 'p')
- call mkdir(optdir2 . '/pluginC', 'p')
-
- let li = []
- call feedkeys(":packadd \<Tab>')\<C-B>call add(li, '\<CR>", 't')
- call feedkeys(":packadd " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
- call feedkeys(":packadd " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
- call feedkeys(":packadd " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
- call assert_equal("packadd pluginA", li[0])
- call assert_equal("packadd pluginB", li[1])
- call assert_equal("packadd pluginC", li[2])
- call assert_equal("packadd ", li[3])
-endfunc
-
-func Test_packloadall()
- " plugin foo with an autoload directory
- let fooplugindir = &packpath . '/pack/mine/start/foo/plugin'
- call mkdir(fooplugindir, 'p')
- call writefile(['let g:plugin_foo_number = 1234',
- \ 'let g:plugin_foo_auto = bbb#value',
- \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim')
- let fooautodir = &packpath . '/pack/mine/start/foo/autoload'
- call mkdir(fooautodir, 'p')
- call writefile(['let bar#value = 77'], fooautodir . '/bar.vim')
-
- " plugin aaa with an autoload directory
- let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin'
- call mkdir(aaaplugindir, 'p')
- call writefile(['let g:plugin_aaa_number = 333',
- \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim')
- let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload'
- call mkdir(aaaautodir, 'p')
- call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim')
-
- " plugin extra with only an autoload directory
- let extraautodir = &packpath . '/pack/mine/start/extra/autoload'
- call mkdir(extraautodir, 'p')
- call writefile(['let extra#value = 99'], extraautodir . '/extra.vim')
-
- packloadall
- call assert_equal(1234, g:plugin_foo_number)
- call assert_equal(55, g:plugin_foo_auto)
- call assert_equal(99, g:plugin_extra_auto)
- call assert_equal(333, g:plugin_aaa_number)
- call assert_equal(77, g:plugin_aaa_auto)
-
- " only works once
- call writefile(['let g:plugin_bar_number = 4321'], fooplugindir . '/bar2.vim')
- packloadall
- call assert_false(exists('g:plugin_bar_number'))
-
- " works when ! used
- packloadall!
- call assert_equal(4321, g:plugin_bar_number)
-endfunc
-
-func Test_helptags()
- let docdir1 = &packpath . '/pack/mine/start/foo/doc'
- let docdir2 = &packpath . '/pack/mine/start/bar/doc'
- call mkdir(docdir1, 'p')
- call mkdir(docdir2, 'p')
- call writefile(['look here: *look-here*'], docdir1 . '/bar.txt')
- call writefile(['look away: *look-away*'], docdir2 . '/foo.txt')
- exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar'
-
- helptags ALL
-
- let tags1 = readfile(docdir1 . '/tags')
- call assert_match('look-here', tags1[0])
- let tags2 = readfile(docdir2 . '/tags')
- call assert_match('look-away', tags2[0])
-
- call assert_fails('helptags abcxyz', 'E150:')
-endfunc
-
-func Test_colorscheme()
- let colordirrun = &packpath . '/runtime/colors'
- let colordirstart = &packpath . '/pack/mine/start/foo/colors'
- let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
- call mkdir(colordirrun, 'p')
- call mkdir(colordirstart, 'p')
- call mkdir(colordiropt, 'p')
- call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
- call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
- call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
- exe 'set rtp=' . &packpath . '/runtime'
-
- colorscheme one
- call assert_equal(1, g:found_one)
- colorscheme two
- call assert_equal(1, g:found_two)
- colorscheme three
- call assert_equal(1, g:found_three)
-endfunc
-
-func Test_colorscheme_completion()
- let colordirrun = &packpath . '/runtime/colors'
- let colordirstart = &packpath . '/pack/mine/start/foo/colors'
- let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
- call mkdir(colordirrun, 'p')
- call mkdir(colordirstart, 'p')
- call mkdir(colordiropt, 'p')
- call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
- call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
- call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
- exe 'set rtp=' . &packpath . '/runtime'
-
- let li=[]
- call feedkeys(":colorscheme " . repeat("\<Tab>", 1) . "')\<C-B>call add(li, '\<CR>", 't')
- call feedkeys(":colorscheme " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
- call feedkeys(":colorscheme " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
- call feedkeys(":colorscheme " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
- call assert_equal("colorscheme one", li[0])
- call assert_equal("colorscheme three", li[1])
- call assert_equal("colorscheme two", li[2])
- call assert_equal("colorscheme ", li[3])
-endfunc
-
-func Test_runtime()
- let rundir = &packpath . '/runtime/extra'
- let startdir = &packpath . '/pack/mine/start/foo/extra'
- let optdir = &packpath . '/pack/mine/opt/bar/extra'
- call mkdir(rundir, 'p')
- call mkdir(startdir, 'p')
- call mkdir(optdir, 'p')
- call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim')
- call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim')
- call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim')
- call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim')
- call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim')
- exe 'set rtp=' . &packpath . '/runtime'
-
- let g:sequence = ''
- runtime extra/bar.vim
- call assert_equal('run', g:sequence)
- let g:sequence = ''
- runtime START extra/bar.vim
- call assert_equal('start', g:sequence)
- let g:sequence = ''
- runtime OPT extra/bar.vim
- call assert_equal('opt', g:sequence)
- let g:sequence = ''
- runtime PACK extra/bar.vim
- call assert_equal('start', g:sequence)
- let g:sequence = ''
- runtime! PACK extra/bar.vim
- call assert_equal('startopt', g:sequence)
- let g:sequence = ''
- runtime PACK extra/xxx.vim
- call assert_equal('xxxopt', g:sequence)
-
- let g:sequence = ''
- runtime ALL extra/bar.vim
- call assert_equal('run', g:sequence)
- let g:sequence = ''
- runtime ALL extra/foo.vim
- call assert_equal('foostart', g:sequence)
- let g:sequence = ''
- runtime! ALL extra/xxx.vim
- call assert_equal('xxxopt', g:sequence)
- let g:sequence = ''
- runtime! ALL extra/bar.vim
- call assert_equal('runstartopt', g:sequence)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim
deleted file mode 100644
index 3020668f1b..0000000000
--- a/src/nvim/testdir/test_partial.vim
+++ /dev/null
@@ -1,361 +0,0 @@
-" Test binding arguments to a Funcref.
-
-func MyFunc(arg1, arg2, arg3)
- return a:arg1 . '/' . a:arg2 . '/' . a:arg3
-endfunc
-
-func MySort(up, one, two)
- if a:one == a:two
- return 0
- endif
- if a:up
- return a:one > a:two ? 1 : -1
- endif
- return a:one < a:two ? 1 : -1
-endfunc
-
-func MyMap(sub, index, val)
- return a:val - a:sub
-endfunc
-
-func MyFilter(threshold, index, val)
- return a:val > a:threshold
-endfunc
-
-func Test_partial_args()
- let Cb = function('MyFunc', ["foo", "bar"])
-
- call Cb("zzz")
- call assert_equal("foo/bar/xxx", Cb("xxx"))
- call assert_equal("foo/bar/yyy", call(Cb, ["yyy"]))
- let Cb2 = function(Cb)
- call assert_equal("foo/bar/zzz", Cb2("zzz"))
- let Cb3 = function(Cb, ["www"])
- call assert_equal("foo/bar/www", Cb3())
-
- let Cb = function('MyFunc', [])
- call assert_equal("a/b/c", Cb("a", "b", "c"))
- let Cb2 = function(Cb, [])
- call assert_equal("a/b/d", Cb2("a", "b", "d"))
- let Cb3 = function(Cb, ["a", "b"])
- call assert_equal("a/b/e", Cb3("e"))
-
- let Sort = function('MySort', [1])
- call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
- let Sort = function('MySort', [0])
- call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
-
- let Map = function('MyMap', [2])
- call assert_equal([-1, 0, 1], map([1, 2, 3], Map))
- let Map = function('MyMap', [3])
- call assert_equal([-2, -1, 0], map([1, 2, 3], Map))
-
- let Filter = function('MyFilter', [1])
- call assert_equal([2, 3], filter([1, 2, 3], Filter))
- let Filter = function('MyFilter', [2])
- call assert_equal([3], filter([1, 2, 3], Filter))
-endfunc
-
-func MyDictFunc(arg1, arg2) dict
- return self.name . '/' . a:arg1 . '/' . a:arg2
-endfunc
-
-func Test_partial_dict()
- let dict = {'name': 'hello'}
- let Cb = function('MyDictFunc', ["foo", "bar"], dict)
- call assert_equal("hello/foo/bar", Cb())
- call assert_fails('Cb("xxx")', 'E492:')
-
- let Cb = function('MyDictFunc', [], dict)
- call assert_equal("hello/ttt/xxx", Cb("ttt", "xxx"))
- call assert_fails('Cb("yyy")', 'E492:')
-
- let Cb = function('MyDictFunc', ["foo"], dict)
- call assert_equal("hello/foo/xxx", Cb("xxx"))
- call assert_fails('Cb()', 'E492:')
- let Cb = function('MyDictFunc', dict)
- call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
- call assert_fails('Cb("fff")', 'E492:')
-
- let Cb = function('MyDictFunc', dict)
- call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb))
-
- let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
- call assert_equal("Hello", dict.tr())
-
- call assert_fails("let F=function('setloclist', 10)", "E923:")
- call assert_fails("let F=function('setloclist', [], [])", "E922:")
-endfunc
-
-func Test_partial_implicit()
- let dict = {'name': 'foo'}
- func dict.MyFunc(arg) dict
- return self.name . '/' . a:arg
- endfunc
-
- call assert_equal('foo/bar', dict.MyFunc('bar'))
-
- call assert_fails('let func = dict.MyFunc', 'E704:')
- let Func = dict.MyFunc
- call assert_equal('foo/aaa', Func('aaa'))
-
- let Func = function(dict.MyFunc, ['bbb'])
- call assert_equal('foo/bbb', Func())
-endfunc
-
-fun InnerCall(funcref)
- return a:funcref
-endfu
-
-fun OuterCall()
- let opt = { 'func' : function('max') }
- call InnerCall(opt.func)
-endfu
-
-func Test_function_in_dict()
- call OuterCall()
-endfunc
-
-func s:cache_clear() dict
- return self.name
-endfunc
-
-func Test_script_function_in_dict()
- let s:obj = {'name': 'foo'}
- let s:obj2 = {'name': 'bar'}
-
- let s:obj['clear'] = function('s:cache_clear')
-
- call assert_equal('foo', s:obj.clear())
- let F = s:obj.clear
- call assert_equal('foo', F())
- call assert_equal('foo', call(s:obj.clear, [], s:obj))
- call assert_equal('bar', call(s:obj.clear, [], s:obj2))
-
- let s:obj2['clear'] = function('s:cache_clear')
- call assert_equal('bar', s:obj2.clear())
- let B = s:obj2.clear
- call assert_equal('bar', B())
-endfunc
-
-func s:cache_arg(arg) dict
- let s:result = self.name . '/' . a:arg
- return s:result
-endfunc
-
-func Test_script_function_in_dict_arg()
- let s:obj = {'name': 'foo'}
- let s:obj['clear'] = function('s:cache_arg')
-
- call assert_equal('foo/bar', s:obj.clear('bar'))
- let F = s:obj.clear
- let s:result = ''
- call assert_equal('foo/bar', F('bar'))
- call assert_equal('foo/bar', s:result)
-
- let s:obj['clear'] = function('s:cache_arg', ['bar'])
- call assert_equal('foo/bar', s:obj.clear())
- let s:result = ''
- call s:obj.clear()
- call assert_equal('foo/bar', s:result)
-
- let F = s:obj.clear
- call assert_equal('foo/bar', F())
- let s:result = ''
- call F()
- call assert_equal('foo/bar', s:result)
-
- call assert_equal('foo/bar', call(s:obj.clear, [], s:obj))
-endfunc
-
-func Test_partial_exists()
- let F = function('MyFunc')
- call assert_true(exists('*F'))
- let lF = [F]
- call assert_true(exists('*lF[0]'))
-
- let F = function('MyFunc', ['arg'])
- call assert_true(exists('*F'))
- let lF = [F]
- call assert_true(exists('*lF[0]'))
-endfunc
-
-func Test_partial_string()
- let F = function('MyFunc')
- call assert_equal("function('MyFunc')", string(F))
- let F = function('MyFunc', ['foo'])
- call assert_equal("function('MyFunc', ['foo'])", string(F))
- let F = function('MyFunc', ['foo', 'bar'])
- call assert_equal("function('MyFunc', ['foo', 'bar'])", string(F))
- let d = {'one': 1}
- let F = function('MyFunc', d)
- call assert_equal("function('MyFunc', {'one': 1})", string(F))
- let F = function('MyFunc', ['foo'], d)
- call assert_equal("function('MyFunc', ['foo'], {'one': 1})", string(F))
-endfunc
-
-func Test_func_unref()
- let obj = {}
- function! obj.func() abort
- endfunction
- let funcnumber = matchstr(string(obj.func), '^function(''\zs.\{-}\ze''')
- call assert_true(exists('*{' . funcnumber . '}'))
- unlet obj
- call assert_false(exists('*{' . funcnumber . '}'))
-endfunc
-
-func Test_redefine_dict_func()
- let d = {}
- function d.test4()
- endfunction
- let d.test4 = d.test4
- try
- function! d.test4(name)
- endfunction
- catch
- call assert_true(v:errmsg, v:exception)
- endtry
-endfunc
-
-" This caused double free on exit if EXITFREE is defined.
-func Test_cyclic_list_arg()
- let l = []
- let Pt = function('string', [l])
- call add(l, Pt)
- unlet l
- unlet Pt
-endfunc
-
-" This caused double free on exit if EXITFREE is defined.
-func Test_cyclic_dict_arg()
- let d = {}
- let Pt = function('string', [d])
- let d.Pt = Pt
- unlet d
- unlet Pt
-endfunc
-
-func Ignored(job1, job2, status)
-endfunc
-
-" func Test_cycle_partial_job()
-" let job = job_start('echo')
-" call job_setoptions(job, {'exit_cb': function('Ignored', [job])})
-" unlet job
-" endfunc
-
-" func Test_ref_job_partial_dict()
-" let g:ref_job = job_start('echo')
-" let d = {'a': 'b'}
-" call job_setoptions(g:ref_job, {'exit_cb': function('string', [], d)})
-" endfunc
-
-func Test_auto_partial_rebind()
- let dict1 = {'name': 'dict1'}
- func! dict1.f1()
- return self.name
- endfunc
- let dict1.f2 = function(dict1.f1, dict1)
-
- call assert_equal('dict1', dict1.f1())
- call assert_equal('dict1', dict1['f1']())
- call assert_equal('dict1', dict1.f2())
- call assert_equal('dict1', dict1['f2']())
-
- let dict2 = {'name': 'dict2'}
- let dict2.f1 = dict1.f1
- let dict2.f2 = dict1.f2
-
- call assert_equal('dict2', dict2.f1())
- call assert_equal('dict2', dict2['f1']())
- call assert_equal('dict1', dict2.f2())
- call assert_equal('dict1', dict2['f2']())
-endfunc
-
-func Test_get_partial_items()
- let dict = {'name': 'hello'}
- let args = ["foo", "bar"]
- let Func = function('MyDictFunc')
- let Cb = function('MyDictFunc', args, dict)
-
- call assert_equal(Func, get(Cb, 'func'))
- call assert_equal('MyDictFunc', get(Cb, 'name'))
- call assert_equal(args, get(Cb, 'args'))
- call assert_equal(dict, get(Cb, 'dict'))
- call assert_fails('call get(Cb, "xxx")', 'E475:')
-
- call assert_equal(Func, get(Func, 'func'))
- call assert_equal('MyDictFunc', get(Func, 'name'))
- call assert_equal([], get(Func, 'args'))
- call assert_true(empty( get(Func, 'dict')))
-
- let P = function('substitute', ['hello there', 'there'])
- let dict = {'partial has': 'no dict'}
- call assert_equal(dict, get(P, 'dict', dict))
- call assert_equal(0, get(l:P, 'dict'))
-endfunc
-
-func Test_compare_partials()
- let d1 = {}
- let d2 = {}
-
- function d1.f1() dict
- endfunction
-
- function d1.f2() dict
- endfunction
-
- let F1 = get(d1, 'f1')
- let F2 = get(d1, 'f2')
-
- let F1d1 = function(F1, d1)
- let F2d1 = function(F2, d2)
- let F1d1a1 = function(F1d1, [1])
- let F1d1a12 = function(F1d1, [1, 2])
- let F1a1 = function(F1, [1])
- let F1a2 = function(F1, [2])
- let F1d2 = function(F1, d2)
- let d3 = {'f1': F1, 'f2': F2}
- let F1d3 = function(F1, d3)
- let F1ad1 = function(F1, [d1])
- let F1ad3 = function(F1, [d3])
-
- call assert_match('^function(''\d\+'')$', string(F1)) " Not a partial
- call assert_match('^function(''\d\+'')$', string(F2)) " Not a partial
- call assert_match('^function(''\d\+'', {.*})$', string(F1d1)) " A partial
- call assert_match('^function(''\d\+'', {.*})$', string(F2d1)) " A partial
- call assert_match('^function(''\d\+'', \[.*\])$', string(F1a1)) " No dict
-
- " !=
- let X = F1
- call assert_false(F1 != X) " same function
- let X = F1d1
- call assert_false(F1d1 != X) " same partial
- let X = F1d1a1
- call assert_false(F1d1a1 != X) " same partial
- let X = F1a1
- call assert_false(F1a1 != X) " same partial
-
- call assert_true(F1 != F2) " Different functions
- call assert_true(F1 != F1d1) " Partial /= non-partial
- call assert_true(F1d1a1 != F1d1a12) " Different number of arguments
- call assert_true(F1a1 != F1d1a12) " One has no dict
- call assert_true(F1a1 != F1a2) " Different arguments
- call assert_true(F1d2 != F1d1) " Different dictionaries
- call assert_false(F1d1 != F1d3) " Equal dictionaries, even though d1 isnot d3
-
- " isnot, option 1
- call assert_true(F1 isnot# F2) " Different functions
- call assert_true(F1 isnot# F1d1) " Partial /= non-partial
- call assert_true(F1d1 isnot# F1d3) " d1 isnot d3, even though d1 == d3
- call assert_true(F1a1 isnot# F1d1a12) " One has no dict
- call assert_true(F1a1 isnot# F1a2) " Different number of arguments
- call assert_true(F1ad1 isnot# F1ad3) " In arguments d1 isnot d3
-
- " isnot, option 2
- call assert_true(F1 isnot# F2) " Different functions
- call assert_true(F1 isnot# F1d1) " Partial /= non-partial
- call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_paste.vim b/src/nvim/testdir/test_paste.vim
deleted file mode 100644
index dad3c2c6a0..0000000000
--- a/src/nvim/testdir/test_paste.vim
+++ /dev/null
@@ -1,76 +0,0 @@
-
-" Test for 'pastetoggle'
-func Test_pastetoggle()
- new
- set pastetoggle=<F4>
- set nopaste
- call feedkeys("iHello\<F4>", 'xt')
- call assert_true(&paste)
- call feedkeys("i\<F4>", 'xt')
- call assert_false(&paste)
- call assert_equal('Hello', getline(1))
- " command-line completion for 'pastetoggle' value
- call feedkeys(":set pastetoggle=\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set pastetoggle=<F4>', @:)
- set pastetoggle&
- bwipe!
-endfunc
-
-" Test for restoring option values when 'paste' is disabled
-func Test_paste_opt_restore()
- set autoindent expandtab ruler showmatch
- if has('rightleft')
- set revins hkmap
- endif
- set smarttab softtabstop=3 textwidth=27 wrapmargin=12
- if has('vartabs')
- set varsofttabstop=10,20
- endif
-
- " enabling 'paste' should reset the above options
- set paste
- call assert_false(&autoindent)
- call assert_false(&expandtab)
- if has('rightleft')
- call assert_false(&revins)
- call assert_false(&hkmap)
- endif
- call assert_false(&ruler)
- call assert_false(&showmatch)
- call assert_false(&smarttab)
- call assert_equal(0, &softtabstop)
- call assert_equal(0, &textwidth)
- call assert_equal(0, &wrapmargin)
- if has('vartabs')
- call assert_equal('', &varsofttabstop)
- endif
-
- " disabling 'paste' should restore the option values
- set nopaste
- call assert_true(&autoindent)
- call assert_true(&expandtab)
- if has('rightleft')
- call assert_true(&revins)
- call assert_true(&hkmap)
- endif
- call assert_true(&ruler)
- call assert_true(&showmatch)
- call assert_true(&smarttab)
- call assert_equal(3, &softtabstop)
- call assert_equal(27, &textwidth)
- call assert_equal(12, &wrapmargin)
- if has('vartabs')
- call assert_equal('10,20', &varsofttabstop)
- endif
-
- set autoindent& expandtab& ruler& showmatch&
- if has('rightleft')
- set revins& hkmap&
- endif
- set smarttab& softtabstop& textwidth& wrapmargin&
- if has('vartabs')
- set varsofttabstop&
- endif
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_perl.vim b/src/nvim/testdir/test_perl.vim
deleted file mode 100644
index 558d0a5d6b..0000000000
--- a/src/nvim/testdir/test_perl.vim
+++ /dev/null
@@ -1,311 +0,0 @@
-" Tests for Perl interface
-
-source check.vim
-CheckFeature perl
-CheckNotMSWindows
-
-" FIXME: RunTest don't see any error when Perl abort...
-perl $SIG{__WARN__} = sub { die "Unexpected warnings from perl: @_" };
-
-func Test_change_buffer()
- call setline(line('$'), ['1 line 1'])
- perl VIM::DoCommand("normal /^1\n")
- perl $curline = VIM::Eval("line('.')")
- perl $curbuf->Set($curline, "1 changed line 1")
- call assert_equal('1 changed line 1', getline('$'))
-endfunc
-
-func Test_evaluate_list()
- call setline(line('$'), ['2 line 2'])
- perl VIM::DoCommand("normal /^2\n")
- perl $curline = VIM::Eval("line('.')")
- let l = ["abc", "def"]
- perl << EOF
- $l = VIM::Eval("l");
- $curbuf->Append($curline, $l);
-EOF
- normal j
- .perldo s|\n|/|g
- " call assert_equal('abc/def/', getline('$'))
- call assert_equal('def', getline('$'))
-endfunc
-
-funct Test_VIM_Blob()
- call assert_equal('0z', perleval('VIM::Blob("")'))
- call assert_equal('0z31326162', 'VIM::Blob("12ab")'->perleval())
- call assert_equal('0z00010203', perleval('VIM::Blob("\x00\x01\x02\x03")'))
- call assert_equal('0z8081FEFF', perleval('VIM::Blob("\x80\x81\xfe\xff")'))
-endfunc
-
-func Test_buffer_Delete()
- new
- call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
- perl $curbuf->Delete(7)
- perl $curbuf->Delete(2, 5)
- perl $curbuf->Delete(10)
- call assert_equal(['a', 'f', 'h'], getline(1, '$'))
- bwipe!
-endfunc
-
-func Test_buffer_Append()
- new
- perl $curbuf->Append(1, '1')
- perl $curbuf->Append(2, '2', '3', '4')
- perl @l = ('5' ..'7')
- perl $curbuf->Append(0, @l)
- call assert_equal(['5', '6', '7', '', '1', '2', '3', '4'], getline(1, '$'))
- bwipe!
-endfunc
-
-func Test_buffer_Set()
- new
- call setline(1, ['1', '2', '3', '4', '5'])
- perl $curbuf->Set(2, 'a', 'b', 'c')
- perl $curbuf->Set(4, 'A', 'B', 'C')
- call assert_equal(['1', 'a', 'b', 'A', 'B'], getline(1, '$'))
- bwipe!
-endfunc
-
-func Test_buffer_Get()
- new
- call setline(1, ['1', '2', '3', '4'])
- call assert_equal('2:3', perleval('join(":", $curbuf->Get(2, 3))'))
- bwipe!
-endfunc
-
-func Test_buffer_Count()
- new
- call setline(1, ['a', 'b', 'c'])
- call assert_equal(3, perleval('$curbuf->Count()'))
- bwipe!
-endfunc
-
-func Test_buffer_Name()
- new
- call assert_equal('', perleval('$curbuf->Name()'))
- bwipe!
- new Xfoo
- call assert_equal('Xfoo', perleval('$curbuf->Name()'))
- bwipe!
-endfunc
-
-func Test_buffer_Number()
- call assert_equal(bufnr('%'), perleval('$curbuf->Number()'))
-endfunc
-
-func Test_window_Cursor()
- new
- call setline(1, ['line1', 'line2'])
- perl $curwin->Cursor(2, 3)
- call assert_equal('2:3', perleval('join(":", $curwin->Cursor())'))
- " Col is numbered from 0 in Perl, and from 1 in Vim script.
- call assert_equal([0, 2, 4, 0], getpos('.'))
- bwipe!
-endfunc
-
-func Test_window_SetHeight()
- new
- perl $curwin->SetHeight(2)
- call assert_equal(2, winheight(0))
- bwipe!
-endfunc
-
-func Test_VIM_Windows()
- new
- " VIM::Windows() without argument in scalar and list context.
- perl $winnr = VIM::Windows()
- perl @winlist = VIM::Windows()
- perl $curbuf->Append(0, $winnr, scalar(@winlist))
- call assert_equal(['2', '2', ''], getline(1, '$'))
-
- " VIM::Windows() with window number argument.
- perl (VIM::Windows(VIM::Eval('winnr()')))[0]->Buffer()->Set(1, 'bar')
- call assert_equal('bar', getline(1))
- bwipe!
-endfunc
-
-func Test_VIM_Buffers()
- new Xbar
- " VIM::Buffers() without argument in scalar and list context.
- perl $nbuf = VIM::Buffers()
- perl @buflist = VIM::Buffers()
-
- " VIM::Buffers() with argument.
- perl $curbuf = (VIM::Buffers('Xbar'))[0]
- perl $curbuf->Append(0, $nbuf, scalar(@buflist))
- call assert_equal(['2', '2', ''], getline(1, '$'))
- bwipe!
-endfunc
-
-func <SID>catch_peval(expr)
- try
- call perleval(a:expr)
- catch
- return v:exception
- endtry
- call assert_report('no exception for `perleval("'.a:expr.'")`')
- return ''
-endfunc
-
-func Test_perleval()
- call assert_false(perleval('undef'))
-
- " scalar
- call assert_equal(0, perleval('0'))
- call assert_equal(2, perleval('2'))
- call assert_equal(-2, perleval('-2'))
- if has('float')
- call assert_equal(2.5, perleval('2.5'))
- else
- call assert_equal(2, perleval('2.5'))
- end
-
- " sandbox call assert_equal(2, perleval('2'))
-
- call assert_equal('abc', perleval('"abc"'))
- " call assert_equal("abc\ndef", perleval('"abc\0def"'))
-
- " ref
- call assert_equal([], perleval('[]'))
- call assert_equal(['word', 42, [42],{}], perleval('["word", 42, [42], {}]'))
-
- call assert_equal({}, perleval('{}'))
- call assert_equal({'foo': 'bar'}, perleval('{foo => "bar"}'))
-
- perl our %h; our @a;
- let a = perleval('[\(%h, %h, @a, @a)]')
- " call assert_true((a[0] is a[1]))
- call assert_equal(a[0], a[1])
- " call assert_true((a[2] is a[3]))
- call assert_equal(a[2], a[3])
- perl undef %h; undef @a;
-
- " call assert_true(<SID>catch_peval('{"" , 0}') =~ 'Malformed key Dictionary')
- " call assert_true(<SID>catch_peval('{"\0" , 0}') =~ 'Malformed key Dictionary')
- " call assert_true(<SID>catch_peval('{"foo\0bar" , 0}') =~ 'Malformed key Dictionary')
-
- call assert_equal('*VIM', perleval('"*VIM"'))
- " call assert_true(perleval('\\0') =~ 'SCALAR(0x\x\+)')
-endfunc
-
-func Test_perldo()
- sp __TEST__
- exe 'read ' g:testname
- perldo s/perl/vieux_chameau/g
- 1
- call assert_false(search('\Cperl'))
- bw!
-
- " Check deleting lines does not trigger ml_get error.
- new
- call setline(1, ['one', 'two', 'three'])
- perldo VIM::DoCommand("%d_")
- bwipe!
-
- " Check switching to another buffer does not trigger ml_get error.
- new
- let wincount = winnr('$')
- call setline(1, ['one', 'two', 'three'])
- perldo VIM::DoCommand("new")
- call assert_equal(wincount + 1, winnr('$'))
- bwipe!
- bwipe!
-endfunc
-
-func Test_VIM_package()
- perl VIM::DoCommand('let l:var = "foo"')
- call assert_equal(l:var, 'foo')
-
- set noet
- perl VIM::SetOption('et')
- call assert_true(&et)
-endfunc
-
-func Test_stdio()
- throw 'skipped: TODO: '
- redir =>l:out
- perl <<EOF
- VIM::Msg("&VIM::Msg");
- print "STDOUT";
- print STDERR "STDERR";
-EOF
- redir END
- call assert_equal(['&VIM::Msg', 'STDOUT', 'STDERR'], split(l:out, "\n"))
-endfunc
-
-" Run first to get a clean namespace
-func Test_000_SvREFCNT()
- throw 'skipped: TODO: '
- for i in range(8)
- exec 'new X'.i
- endfor
- new t
- perl <<--perl
-#line 5 "Test_000_SvREFCNT()"
- my ($b, $w);
-
- my $num = 0;
- for ( 0 .. 100 ) {
- if ( ++$num >= 8 ) { $num = 0 }
- VIM::DoCommand("buffer X$num");
- $b = $curbuf;
- }
-
- VIM::DoCommand("buffer t");
-
- $b = $curbuf for 0 .. 100;
- $w = $curwin for 0 .. 100;
- () = VIM::Buffers for 0 .. 100;
- () = VIM::Windows for 0 .. 100;
-
- VIM::DoCommand('bw! t');
- if (exists &Internals::SvREFCNT) {
- my $cb = Internals::SvREFCNT($$b);
- my $cw = Internals::SvREFCNT($$w);
- VIM::Eval("assert_equal(2, $cb, 'T1')");
- VIM::Eval("assert_equal(2, $cw, 'T2')");
- my $strongref;
- foreach ( VIM::Buffers, VIM::Windows ) {
- VIM::DoCommand("%bw!");
- my $c = Internals::SvREFCNT($_);
- VIM::Eval("assert_equal(2, $c, 'T3')");
- $c = Internals::SvREFCNT($$_);
- next if $c == 2 && !$strongref++;
- VIM::Eval("assert_equal(1, $c, 'T4')");
- }
- $cb = Internals::SvREFCNT($$curbuf);
- $cw = Internals::SvREFCNT($$curwin);
- VIM::Eval("assert_equal(3, $cb, 'T5')");
- VIM::Eval("assert_equal(3, $cw, 'T6')");
- }
- VIM::Eval("assert_false($$b)");
- VIM::Eval("assert_false($$w)");
---perl
- %bw!
-endfunc
-
-func Test_set_cursor()
- " Check that setting the cursor position works.
- new
- call setline(1, ['first line', 'second line'])
- normal gg
- perldo $curwin->Cursor(1, 5)
- call assert_equal([1, 6], [line('.'), col('.')])
-
- " Check that movement after setting cursor position keeps current column.
- normal j
- call assert_equal([2, 6], [line('.'), col('.')])
-endfunc
-
-" Test for various heredoc syntax
-func Test_perl_heredoc()
- perl << END
-VIM::DoCommand('let s = "A"')
-END
- perl <<
-VIM::DoCommand('let s ..= "B"')
-.
- call assert_equal('AB', s)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/src/nvim/testdir/test_plus_arg_edit.vim
deleted file mode 100644
index c52044d064..0000000000
--- a/src/nvim/testdir/test_plus_arg_edit.vim
+++ /dev/null
@@ -1,47 +0,0 @@
-" Tests for complicated + argument to :edit command
-function Test_edit()
- call writefile(["foo|bar"], "Xfile1")
- call writefile(["foo/bar"], "Xfile2")
- edit +1|s/|/PIPE/|w Xfile1| e Xfile2|1 | s/\//SLASH/|w
- call assert_equal(["fooPIPEbar"], readfile("Xfile1"))
- call assert_equal(["fooSLASHbar"], readfile("Xfile2"))
- call delete('Xfile1')
- call delete('Xfile2')
-endfunction
-
-func Test_edit_bad()
- " 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! ++encoding=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
-
-" Test for ++bin and ++nobin arguments
-func Test_binary_arg()
- new
- edit ++bin Xfile1
- call assert_equal(1, &binary)
- edit ++nobin Xfile2
- call assert_equal(0, &binary)
- call assert_fails('edit ++binabc Xfile3', 'E474:')
- close!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
deleted file mode 100644
index 791cce4431..0000000000
--- a/src/nvim/testdir/test_popup.vim
+++ /dev/null
@@ -1,1263 +0,0 @@
-" Test for completion menu
-
-source shared.vim
-source screendump.vim
-source check.vim
-
-let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
-let g:setting = ''
-
-func ListMonths()
- if g:setting != ''
- exe ":set" g:setting
- endif
- let mth = copy(g:months)
- let entered = strcharpart(getline('.'),0,col('.'))
- if !empty(entered)
- let mth = filter(mth, 'v:val=~"^".entered')
- endif
- call complete(1, mth)
- return ''
-endfunc
-
-func Test_popup_complete2()
- " Although the popupmenu is not visible, this does not mean completion mode
- " has ended. After pressing <f5> to complete the currently typed char, Vim
- " still stays in the first state of the completion (:h ins-completion-menu),
- " although the popupmenu wasn't shown <c-e> will remove the inserted
- " completed text (:h complete_CTRL-E), while the following <c-e> will behave
- " like expected (:h i_CTRL-E)
- new
- inoremap <f5> <c-r>=ListMonths()<cr>
- call append(1, ["December2015"])
- :1
- call feedkeys("aD\<f5>\<C-E>\<C-E>\<C-E>\<C-E>\<enter>\<esc>", 'tx')
- call assert_equal(["Dece", "", "December2015"], getline(1,3))
- %d
- bw!
-endfunc
-
-func Test_popup_complete()
- new
- inoremap <f5> <c-r>=ListMonths()<cr>
-
- " <C-E> - select original typed text before the completion started
- call feedkeys("aJu\<f5>\<down>\<c-e>\<esc>", 'tx')
- call assert_equal(["Ju"], getline(1,2))
- %d
-
- " <C-Y> - accept current match
- call feedkeys("a\<f5>". repeat("\<down>",7). "\<c-y>\<esc>", 'tx')
- call assert_equal(["August"], getline(1,2))
- %d
-
- " <BS> - Delete one character from the inserted text (state: 1)
- " TODO: This should not end the completion, but it does.
- " This should according to the documentation:
- " January
- " but instead, this does
- " Januar
- " (idea is, C-L inserts the match from the popup menu
- " but if the menu is closed, it will insert the character <c-l>
- call feedkeys("aJ\<f5>\<bs>\<c-l>\<esc>", 'tx')
- call assert_equal(["Januar "], getline(1,2))
- %d
-
- " any-non special character: Stop completion without changing the match
- " and insert the typed character
- call feedkeys("a\<f5>20", 'tx')
- call assert_equal(["January20"], getline(1,2))
- %d
-
- " any-non printable, non-white character: Add this character and
- " reduce number of matches
- call feedkeys("aJu\<f5>\<c-p>l\<c-y>", 'tx')
- call assert_equal(["Jul"], getline(1,2))
- %d
-
- " any-non printable, non-white character: Add this character and
- " reduce number of matches
- call feedkeys("aJu\<f5>\<c-p>l\<c-n>\<c-y>", 'tx')
- call assert_equal(["July"], getline(1,2))
- %d
-
- " any-non printable, non-white character: Add this character and
- " reduce number of matches
- call feedkeys("aJu\<f5>\<c-p>l\<c-e>", 'tx')
- call assert_equal(["Jul"], getline(1,2))
- %d
-
- " <BS> - Delete one character from the inserted text (state: 2)
- call feedkeys("a\<f5>\<c-n>\<bs>", 'tx')
- call assert_equal(["Februar"], getline(1,2))
- %d
-
- " <c-l> - Insert one character from the current match
- call feedkeys("aJ\<f5>".repeat("\<c-n>",3)."\<c-l>\<esc>", 'tx')
- call assert_equal(["J "], getline(1,2))
- %d
-
- " <c-l> - Insert one character from the current match
- call feedkeys("aJ\<f5>".repeat("\<c-n>",4)."\<c-l>\<esc>", 'tx')
- call assert_equal(["January "], getline(1,2))
- %d
-
- " <c-y> - Accept current selected match
- call feedkeys("aJ\<f5>\<c-y>\<esc>", 'tx')
- call assert_equal(["January"], getline(1,2))
- %d
-
- " <c-e> - End completion, go back to what was there before selecting a match
- call feedkeys("aJu\<f5>\<c-e>\<esc>", 'tx')
- call assert_equal(["Ju"], getline(1,2))
- %d
-
- " <PageUp> - Select a match several entries back
- call feedkeys("a\<f5>\<PageUp>\<c-y>\<esc>", 'tx')
- call assert_equal([""], getline(1,2))
- %d
-
- " <PageUp><PageUp> - Select a match several entries back
- call feedkeys("a\<f5>\<PageUp>\<PageUp>\<c-y>\<esc>", 'tx')
- call assert_equal(["December"], getline(1,2))
- %d
-
- " <PageUp><PageUp><PageUp> - Select a match several entries back
- call feedkeys("a\<f5>\<PageUp>\<PageUp>\<PageUp>\<c-y>\<esc>", 'tx')
- call assert_equal(["February"], getline(1,2))
- %d
-
- " <PageDown> - Select a match several entries further
- call feedkeys("a\<f5>\<PageDown>\<c-y>\<esc>", 'tx')
- call assert_equal(["November"], getline(1,2))
- %d
-
- " <PageDown><PageDown> - Select a match several entries further
- call feedkeys("a\<f5>\<PageDown>\<PageDown>\<c-y>\<esc>", 'tx')
- call assert_equal(["December"], getline(1,2))
- %d
-
- " <PageDown><PageDown><PageDown> - Select a match several entries further
- call feedkeys("a\<f5>\<PageDown>\<PageDown>\<PageDown>\<c-y>\<esc>", 'tx')
- call assert_equal([""], getline(1,2))
- %d
-
- " <PageDown><PageDown><PageDown><PageDown> - Select a match several entries further
- call feedkeys("a\<f5>".repeat("\<PageDown>",4)."\<c-y>\<esc>", 'tx')
- call assert_equal(["October"], getline(1,2))
- %d
-
- " <Up> - Select a match don't insert yet
- call feedkeys("a\<f5>\<Up>\<c-y>\<esc>", 'tx')
- call assert_equal([""], getline(1,2))
- %d
-
- " <Up><Up> - Select a match don't insert yet
- call feedkeys("a\<f5>\<Up>\<Up>\<c-y>\<esc>", 'tx')
- call assert_equal(["December"], getline(1,2))
- %d
-
- " <Up><Up><Up> - Select a match don't insert yet
- call feedkeys("a\<f5>\<Up>\<Up>\<Up>\<c-y>\<esc>", 'tx')
- call assert_equal(["November"], getline(1,2))
- %d
-
- " <Tab> - Stop completion and insert the match
- call feedkeys("a\<f5>\<Tab>\<c-y>\<esc>", 'tx')
- call assert_equal(["January "], getline(1,2))
- %d
-
- " <Space> - Stop completion and insert the match
- call feedkeys("a\<f5>".repeat("\<c-p>",5)." \<esc>", 'tx')
- call assert_equal(["September "], getline(1,2))
- %d
-
- " <Enter> - Use the text and insert line break (state: 1)
- call feedkeys("a\<f5>\<enter>\<esc>", 'tx')
- call assert_equal(["January", ''], getline(1,2))
- %d
-
- " <Enter> - Insert the current selected text (state: 2)
- call feedkeys("a\<f5>".repeat("\<Up>",5)."\<enter>\<esc>", 'tx')
- call assert_equal(["September"], getline(1,2))
- %d
-
- " Insert match immediately, if there is only one match
- " <c-y> selects a character from the line above
- call append(0, ["December2015"])
- call feedkeys("aD\<f5>\<C-Y>\<C-Y>\<C-Y>\<C-Y>\<enter>\<esc>", 'tx')
- call assert_equal(["December2015", "December2015", ""], getline(1,3))
- %d
-
- " use menuone for 'completeopt'
- " Since for the first <c-y> the menu is still shown, will only select
- " three letters from the line above
- set completeopt&vim
- set completeopt+=menuone
- call append(0, ["December2015"])
- call feedkeys("aD\<f5>\<C-Y>\<C-Y>\<C-Y>\<C-Y>\<enter>\<esc>", 'tx')
- call assert_equal(["December2015", "December201", ""], getline(1,3))
- %d
-
- " use longest for 'completeopt'
- set completeopt&vim
- call feedkeys("aM\<f5>\<C-N>\<C-P>\<c-e>\<enter>\<esc>", 'tx')
- set completeopt+=longest
- call feedkeys("aM\<f5>\<C-N>\<C-P>\<c-e>\<enter>\<esc>", 'tx')
- call assert_equal(["M", "Ma", ""], getline(1,3))
- %d
-
- " use noselect/noinsert for 'completeopt'
- set completeopt&vim
- call feedkeys("aM\<f5>\<enter>\<esc>", 'tx')
- set completeopt+=noselect
- call feedkeys("aM\<f5>\<enter>\<esc>", 'tx')
- set completeopt-=noselect completeopt+=noinsert
- call feedkeys("aM\<f5>\<enter>\<esc>", 'tx')
- call assert_equal(["March", "M", "March"], getline(1,4))
- %d
-endfunc
-
-
-func Test_popup_completion_insertmode()
- new
- inoremap <F5> <C-R>=ListMonths()<CR>
-
- call feedkeys("a\<f5>\<down>\<enter>\<esc>", 'tx')
- call assert_equal('February', getline(1))
- %d
- " Set noinsertmode
- let g:setting = 'noinsertmode'
- call feedkeys("a\<f5>\<down>\<enter>\<esc>", 'tx')
- call assert_equal('February', getline(1))
- call assert_false(pumvisible())
- %d
- " Go through all matches, until none is selected
- let g:setting = ''
- call feedkeys("a\<f5>". repeat("\<c-n>",12)."\<enter>\<esc>", 'tx')
- call assert_equal('', getline(1))
- %d
- " select previous entry
- call feedkeys("a\<f5>\<c-p>\<enter>\<esc>", 'tx')
- call assert_equal('', getline(1))
- %d
- " select last entry
- call feedkeys("a\<f5>\<c-p>\<c-p>\<enter>\<esc>", 'tx')
- call assert_equal('December', getline(1))
-
- iunmap <F5>
-endfunc
-
-func Test_noinsert_complete()
- func! s:complTest1() abort
- eval ['source', 'soundfold']->complete(1)
- return ''
- endfunc
-
- func! s:complTest2() abort
- call complete(1, ['source', 'soundfold'])
- return ''
- endfunc
-
- new
- set completeopt+=noinsert
- inoremap <F5> <C-R>=s:complTest1()<CR>
- call feedkeys("i\<F5>soun\<CR>\<CR>\<ESC>.", 'tx')
- call assert_equal('soundfold', getline(1))
- call assert_equal('soundfold', getline(2))
- bwipe!
-
- new
- inoremap <F5> <C-R>=s:complTest2()<CR>
- call feedkeys("i\<F5>\<CR>\<ESC>", 'tx')
- call assert_equal('source', getline(1))
- bwipe!
-
- set completeopt-=noinsert
- iunmap <F5>
-endfunc
-
-func Test_complete_no_filter()
- func! s:complTest1() abort
- call complete(1, [{'word': 'foobar'}])
- return ''
- endfunc
- func! s:complTest2() abort
- call complete(1, [{'word': 'foobar', 'equal': 1}])
- return ''
- endfunc
-
- let completeopt = &completeopt
-
- " without equal=1
- new
- set completeopt=menuone,noinsert,menu
- inoremap <F5> <C-R>=s:complTest1()<CR>
- call feedkeys("i\<F5>z\<CR>\<CR>\<ESC>.", 'tx')
- call assert_equal('z', getline(1))
- bwipe!
-
- " with equal=1
- new
- set completeopt=menuone,noinsert,menu
- inoremap <F5> <C-R>=s:complTest2()<CR>
- call feedkeys("i\<F5>z\<CR>\<CR>\<ESC>.", 'tx')
- call assert_equal('foobar', getline(1))
- bwipe!
-
- let &completeopt = completeopt
- iunmap <F5>
-endfunc
-
-func Test_compl_vim_cmds_after_register_expr()
- func! s:test_func()
- return 'autocmd '
- endfunc
- augroup AAAAA_Group
- au!
- augroup END
-
- new
- call feedkeys("i\<c-r>=s:test_func()\<CR>\<C-x>\<C-v>\<Esc>", 'tx')
- call assert_equal('autocmd AAAAA_Group', getline(1))
- autocmd! AAAAA_Group
- augroup! AAAAA_Group
- bwipe!
-endfunc
-
-func Test_compl_ignore_mappings()
- call setline(1, ['foo', 'bar', 'baz', 'foobar'])
- inoremap <C-P> (C-P)
- inoremap <C-N> (C-N)
- normal! G
- call feedkeys("o\<C-X>\<C-N>\<C-N>\<C-N>\<C-P>\<C-N>\<C-Y>", 'tx')
- call assert_equal('baz', getline('.'))
- " Also test with unsimplified keys
- call feedkeys("o\<C-X>\<*C-N>\<*C-N>\<*C-N>\<*C-P>\<*C-N>\<C-Y>", 'tx')
- call assert_equal('baz', getline('.'))
- iunmap <C-P>
- iunmap <C-N>
- bwipe!
-endfunc
-
-func DummyCompleteOne(findstart, base)
- if a:findstart
- return 0
- else
- wincmd n
- return ['onedef', 'oneDEF']
- endif
-endfunc
-
-" Test that nothing happens if the 'completefunc' tries to open
-" a new window (fails to open window, continues)
-func Test_completefunc_opens_new_window_one()
- new
- let winid = win_getid()
- setlocal completefunc=DummyCompleteOne
- call setline(1, 'one')
- /^one
- call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:')
- call assert_equal(winid, win_getid())
- call assert_equal('onedef', getline(1))
- q!
-endfunc
-
-" Test that nothing happens if the 'completefunc' opens
-" a new window (no completion, no crash)
-func DummyCompleteTwo(findstart, base)
- if a:findstart
- wincmd n
- return 0
- else
- return ['twodef', 'twoDEF']
- endif
-endfunc
-
-" Test that nothing happens if the 'completefunc' opens
-" a new window (no completion, no crash)
-func Test_completefunc_opens_new_window_two()
- new
- let winid = win_getid()
- setlocal completefunc=DummyCompleteTwo
- call setline(1, 'two')
- /^two
- call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:')
- call assert_equal(winid, win_getid())
- call assert_equal('twodef', getline(1))
- q!
-endfunc
-
-func DummyCompleteThree(findstart, base)
- if a:findstart
- return 0
- else
- return ['threedef', 'threeDEF']
- endif
-endfunc
-
-:"Test that 'completefunc' works when it's OK.
-func Test_completefunc_works()
- new
- let winid = win_getid()
- setlocal completefunc=DummyCompleteThree
- call setline(1, 'three')
- /^three
- call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")
- call assert_equal(winid, win_getid())
- call assert_equal('threeDEF', getline(1))
- q!
-endfunc
-
-func DummyCompleteFour(findstart, base)
- if a:findstart
- return 0
- else
- call complete_add('four1')
- eval 'four2'->complete_add()
- call complete_check()
- call complete_add('four3')
- call complete_add('four4')
- call complete_check()
- call complete_add('four5')
- call complete_add('four6')
- return []
- endif
-endfunc
-
-" Test that 'omnifunc' works when it's OK.
-func Test_omnifunc_with_check()
- new
- setlocal omnifunc=DummyCompleteFour
- call setline(1, 'four')
- /^four
- call feedkeys("A\<C-X>\<C-O>\<C-N>\<Esc>", "x")
- call assert_equal('four2', getline(1))
-
- call setline(1, 'four')
- /^four
- call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<Esc>", "x")
- call assert_equal('four3', getline(1))
-
- call setline(1, 'four')
- /^four
- call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<C-N>\<C-N>\<Esc>", "x")
- call assert_equal('four5', getline(1))
-
- q!
-endfunc
-
-func UndoComplete()
- call complete(1, ['January', 'February', 'March',
- \ 'April', 'May', 'June', 'July', 'August', 'September',
- \ 'October', 'November', 'December'])
- return ''
-endfunc
-
-" Test that no undo item is created when no completion is inserted
-func Test_complete_no_undo()
- set completeopt=menu,preview,noinsert,noselect
- inoremap <Right> <C-R>=UndoComplete()<CR>
- new
- call feedkeys("ixxx\<CR>\<CR>yyy\<Esc>k", 'xt')
- call feedkeys("iaaa\<Esc>0", 'xt')
- call assert_equal('aaa', getline(2))
- call feedkeys("i\<Right>\<Esc>", 'xt')
- call assert_equal('aaa', getline(2))
- call feedkeys("u", 'xt')
- call assert_equal('', getline(2))
-
- call feedkeys("ibbb\<Esc>0", 'xt')
- call assert_equal('bbb', getline(2))
- call feedkeys("A\<Right>\<Down>\<CR>\<Esc>", 'xt')
- call assert_equal('January', getline(2))
- call feedkeys("u", 'xt')
- call assert_equal('bbb', getline(2))
-
- call feedkeys("A\<Right>\<C-N>\<Esc>", 'xt')
- call assert_equal('January', getline(2))
- call feedkeys("u", 'xt')
- call assert_equal('bbb', getline(2))
-
- iunmap <Right>
- set completeopt&
- q!
-endfunc
-
-func DummyCompleteFive(findstart, base)
- if a:findstart
- return 0
- else
- return [
- \ { 'word': 'January', 'info': "info1-1\n1-2\n1-3" },
- \ { 'word': 'February', 'info': "info2-1\n2-2\n2-3" },
- \ { 'word': 'March', 'info': "info3-1\n3-2\n3-3" },
- \ { 'word': 'April', 'info': "info4-1\n4-2\n4-3" },
- \ { 'word': 'May', 'info': "info5-1\n5-2\n5-3" },
- \ ]
- endif
-endfunc
-
-" Test that 'completefunc' on Scratch buffer with preview window works when
-" it's OK.
-func Test_completefunc_with_scratch_buffer()
- new +setlocal\ buftype=nofile\ bufhidden=wipe\ noswapfile
- set completeopt+=preview
- setlocal completefunc=DummyCompleteFive
- call feedkeys("A\<C-X>\<C-U>\<C-N>\<C-N>\<C-N>\<Esc>", "x")
- call assert_equal(['April'], getline(1, '$'))
- pclose
- q!
- set completeopt&
-endfunc
-
-" <C-E> - select original typed text before the completion started without
-" auto-wrap text.
-func Test_completion_ctrl_e_without_autowrap()
- new
- let tw_save = &tw
- set tw=78
- let li = [
- \ '" zzz',
- \ '" zzzyyyyyyyyyyyyyyyyyyy']
- call setline(1, li)
- 0
- call feedkeys("A\<C-X>\<C-N>\<C-E>\<Esc>", "tx")
- call assert_equal(li, getline(1, '$'))
-
- let &tw = tw_save
- q!
-endfunc
-
-func DummyCompleteSix()
- call complete(1, ['Hello', 'World'])
- return ''
-endfunction
-
-" complete() correctly clears the list of autocomplete candidates
-" See #1411
-func Test_completion_clear_candidate_list()
- new
- %d
- " select first entry from the completion popup
- call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>", "tx")
- call assert_equal('Hello', getline(1))
- %d
- " select second entry from the completion popup
- call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>", "tx")
- call assert_equal('World', getline(1))
- %d
- " select original text
- call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>", "tx")
- call assert_equal(' xxx', getline(1))
- %d
- " back at first entry from completion list
- call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>\<C-N>", "tx")
- call assert_equal('Hello', getline(1))
-
- bw!
-endfunc
-
-func Test_completion_respect_bs_option()
- new
- let li = ["aaa", "aaa12345", "aaaabcdef", "aaaABC"]
-
- set bs=indent,eol
- call setline(1, li)
- 1
- call feedkeys("A\<C-X>\<C-N>\<C-P>\<BS>\<BS>\<BS>\<Esc>", "tx")
- call assert_equal('aaa', getline(1))
-
- %d
- set bs=indent,eol,start
- call setline(1, li)
- 1
- call feedkeys("A\<C-X>\<C-N>\<C-P>\<BS>\<BS>\<BS>\<Esc>", "tx")
- call assert_equal('', getline(1))
-
- bw!
-endfunc
-
-func CompleteUndo() abort
- call complete(1, g:months)
- return ''
-endfunc
-
-func Test_completion_can_undo()
- inoremap <Right> <c-r>=CompleteUndo()<cr>
- set completeopt+=noinsert,noselect
-
- new
- call feedkeys("a\<Right>a\<Esc>", 'xt')
- call assert_equal('a', getline(1))
- undo
- call assert_equal('', getline(1))
-
- bwipe!
- set completeopt&
- iunmap <Right>
-endfunc
-
-func Test_completion_comment_formatting()
- new
- setl formatoptions=tcqro
- call feedkeys("o/*\<cr>\<cr>/\<esc>", 'tx')
- call assert_equal(['', '/*', ' *', ' */'], getline(1,4))
- %d
- call feedkeys("o/*\<cr>foobar\<cr>/\<esc>", 'tx')
- call assert_equal(['', '/*', ' * foobar', ' */'], getline(1,4))
- %d
- try
- call feedkeys("o/*\<cr>\<cr>\<c-x>\<c-u>/\<esc>", 'tx')
- call assert_report('completefunc not set, should have failed')
- catch
- call assert_exception('E764:')
- endtry
- call assert_equal(['', '/*', ' *', ' */'], getline(1,4))
- bwipe!
-endfunc
-
-func MessCompleteMonths()
- for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep")
- call complete_add(m)
- if complete_check()
- break
- endif
- endfor
- return []
-endfunc
-
-func MessCompleteMore()
- call complete(1, split("Oct Nov Dec"))
- return []
-endfunc
-
-func MessComplete(findstart, base)
- if a:findstart
- let line = getline('.')
- let start = col('.') - 1
- while start > 0 && line[start - 1] =~ '\a'
- let start -= 1
- endwhile
- return start
- else
- call MessCompleteMonths()
- call MessCompleteMore()
- return []
- endif
-endfunc
-
-func Test_complete_func_mess()
- " Calling complete() after complete_add() in 'completefunc' is wrong, but it
- " should not crash.
- set completefunc=MessComplete
- new
- call setline(1, 'Ju')
- call assert_fails('call feedkeys("A\<c-x>\<c-u>/\<esc>", "tx")', 'E565:')
- call assert_equal('Jan/', getline(1))
- bwipe!
- set completefunc=
-endfunc
-
-func Test_complete_CTRLN_startofbuffer()
- new
- call setline(1, [ 'organize(cupboard, 3, 2);',
- \ 'prioritize(bureau, 8, 7);',
- \ 'realize(bannister, 4, 4);',
- \ 'moralize(railing, 3,9);'])
- let expected=['cupboard.organize(3, 2);',
- \ 'bureau.prioritize(8, 7);',
- \ 'bannister.realize(4, 4);',
- \ 'railing.moralize(3,9);']
- call feedkeys("qai\<c-n>\<c-n>.\<esc>3wdW\<cr>q3@a", 'tx')
- call assert_equal(expected, getline(1,'$'))
- bwipe!
-endfunc
-
-func Test_popup_and_window_resize()
- CheckFeature terminal
- CheckNotGui
-
- let h = winheight(0)
- if h < 15
- return
- endif
- let rows = h / 3
- let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': rows})
- call term_sendkeys(buf, (h / 3 - 1) . "o\<esc>")
- " Wait for the nested Vim to exit insert mode, where it will show the ruler.
- " Need to trigger a redraw.
- call WaitFor({-> execute("redraw") == "" && term_getline(buf, rows) =~ '\<' . rows . ',.*Bot'})
-
- call term_sendkeys(buf, "Gi\<c-x>")
- call term_sendkeys(buf, "\<c-v>")
- call term_wait(buf, 100)
- " popup first entry "!" must be at the top
- call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, 1))})
- exe 'resize +' . (h - 1)
- call term_wait(buf, 100)
- redraw!
- " popup shifted down, first line is now empty
- call WaitForAssert({-> assert_equal('', term_getline(buf, 1))})
- sleep 100m
- " popup is below cursor line and shows first match "!"
- 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!
-endfunc
-
-func Test_popup_and_preview_autocommand()
- " This used to crash Vim
- if !has('python')
- return
- endif
- let h = winheight(0)
- if h < 15
- return
- endif
- new
- augroup MyBufAdd
- au!
- au BufAdd * nested tab sball
- augroup END
- set omnifunc=pythoncomplete#Complete
- call setline(1, 'import os')
- " make the line long
- call setline(2, ' os.')
- $
- call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<C-N>\<enter>\<esc>", 'tx')
- call assert_equal("import os", getline(1))
- call assert_match(' os.\(EX_IOERR\|O_CREAT\)$', getline(2))
- call assert_equal(1, winnr('$'))
- " previewwindow option is not set
- call assert_equal(0, &previewwindow)
- norm! gt
- call assert_equal(0, &previewwindow)
- norm! gT
- call assert_equal(10, tabpagenr('$'))
- tabonly
- pclose
- augroup MyBufAdd
- au!
- augroup END
- augroup! MyBufAdd
- bw!
-endfunc
-
-func Test_popup_and_previewwindow_dump()
- CheckScreendump
- CheckFeature quickfix
-
- let lines =<< trim END
- set previewheight=9
- silent! pedit
- call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
- exec "norm! G\<C-E>\<C-E>"
- END
- call writefile(lines, 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', {})
-
- " wait for the script to finish
- call term_wait(buf)
-
- " Test that popup and previewwindow do not overlap.
- call term_sendkeys(buf, "o")
- call term_wait(buf, 100)
- call term_sendkeys(buf, "\<C-X>\<C-N>")
- call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {})
-
- call term_sendkeys(buf, "\<Esc>u")
- call StopVimInTerminal(buf)
- call delete('Xscript')
-endfunc
-
-func Test_balloon_split()
- CheckFunction balloon_split
-
- call assert_equal([
- \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"',
- \ ], balloon_split(
- \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"'))
- call assert_equal([
- \ 'one two three four one two three four one two thre',
- \ 'e four',
- \ ], balloon_split(
- \ 'one two three four one two three four one two three four'))
-
- eval 'struct = {one = 1, two = 2, three = 3}'
- \ ->balloon_split()
- \ ->assert_equal([
- \ 'struct = {',
- \ ' one = 1,',
- \ ' two = 2,',
- \ ' three = 3}',
- \ ])
-
- call assert_equal([
- \ 'struct = {',
- \ ' one = 1,',
- \ ' nested = {',
- \ ' n1 = "yes",',
- \ ' n2 = "no"}',
- \ ' two = 2}',
- \ ], balloon_split(
- \ 'struct = {one = 1, nested = {n1 = "yes", n2 = "no"} two = 2}'))
- call assert_equal([
- \ 'struct = 0x234 {',
- \ ' long = 2343 "\\"some long string that will be wr',
- \ 'apped in two\\"",',
- \ ' next = 123}',
- \ ], balloon_split(
- \ 'struct = 0x234 {long = 2343 "\\"some long string that will be wrapped in two\\"", next = 123}'))
- call assert_equal([
- \ 'Some comment',
- \ '',
- \ 'typedef this that;',
- \ ], balloon_split(
- \ "Some comment\n\ntypedef this that;"))
-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_command()
- CheckFeature menu
-
- menu Test.Foo Foo
- call assert_fails('popup Test.Foo', 'E336:')
- call assert_fails('popup Test.Foo.X', 'E327:')
- call assert_fails('popup Foo', 'E337:')
- unmenu Test.Foo
-endfunc
-
-func Test_popup_command_dump()
- CheckFeature menu
- CheckScreendump
-
- let script =<< trim END
- func StartTimer()
- call timer_start(100, {-> ChangeMenu()})
- endfunc
- func ChangeMenu()
- aunmenu PopUp.&Paste
- nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR>
- echomsg 'changed'
- endfunc
- END
- call writefile(script, 'XtimerScript')
-
- let lines =<< trim END
- one two three four five
- and one two Xthree four five
- one more two three four five
- END
- call writefile(lines, 'Xtest')
- let buf = RunVimInTerminal('-S XtimerScript Xtest', {})
- call term_sendkeys(buf, ":source $VIMRUNTIME/menu.vim\<CR>")
- call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>")
- call VerifyScreenDump(buf, 'Test_popup_command_01', {})
-
- " go to the Paste entry in the menu
- call term_sendkeys(buf, "jj")
- call VerifyScreenDump(buf, 'Test_popup_command_02', {})
-
- " Select a word
- call term_sendkeys(buf, "j\<CR>")
- call VerifyScreenDump(buf, 'Test_popup_command_03', {})
-
- call term_sendkeys(buf, "\<Esc>")
-
- " Set a timer to change a menu entry while it's displayed. The text should
- " not change but the command does. Making the screendump also verifies that
- " "changed" shows up, which means the timer triggered
- call term_sendkeys(buf, "/X\<CR>:call StartTimer() | popup PopUp\<CR>")
- call VerifyScreenDump(buf, 'Test_popup_command_04', {})
-
- " Select the Paste entry, executes the changed menu item.
- call term_sendkeys(buf, "jj\<CR>")
- call VerifyScreenDump(buf, 'Test_popup_command_05', {})
-
- call StopVimInTerminal(buf)
- call delete('Xtest')
- call delete('XtimerScript')
-endfunc
-
-func Test_popup_complete_backwards()
- new
- call setline(1, ['Post', 'Port', 'Po'])
- let expected=['Post', 'Port', 'Port']
- call cursor(3,2)
- call feedkeys("A\<C-X>". repeat("\<C-P>", 3). "rt\<cr>", 'tx')
- call assert_equal(expected, getline(1,'$'))
- 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
-
-func Test_complete_o_tab()
- CheckFunction test_override
- let s:o_char_pressed = 0
-
- fun! s:act_on_text_changed()
- if s:o_char_pressed
- let s:o_char_pressed = 0
- call feedkeys("\<c-x>\<c-n>", 'i')
- endif
- endfunc
-
- set completeopt=menu,noselect
- new
- imap <expr> <buffer> <tab> pumvisible() ? "\<c-p>" : "X"
- autocmd! InsertCharPre <buffer> let s:o_char_pressed = (v:char ==# 'o')
- autocmd! TextChangedI <buffer> call <sid>act_on_text_changed()
- call setline(1, ['hoard', 'hoax', 'hoarse', ''])
- let l:expected = ['hoard', 'hoax', 'hoarse', 'hoax', 'hoax']
- call cursor(4,1)
- call test_override("char_avail", 1)
- call feedkeys("Ahoa\<tab>\<tab>\<c-y>\<esc>", 'tx')
- call feedkeys("oho\<tab>\<tab>\<c-y>\<esc>", 'tx')
- call assert_equal(l:expected, getline(1,'$'))
-
- call test_override("char_avail", 0)
- bwipe!
- set completeopt&
- delfunc s:act_on_text_changed
-endfunc
-
-func Test_menu_only_exists_in_terminal()
- CheckCommand tlmenu
- CheckNotGui
-
- tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+
- aunmenu *
- try
- popup Edit
- call assert_false(1, 'command should have failed')
- catch
- call assert_exception('E328:')
- endtry
-endfunc
-
-" This used to crash before patch 8.1.1424
-func Test_popup_delete_when_shown()
- CheckFeature menu
- CheckNotGui
-
- func Func()
- popup Foo
- return "\<Ignore>"
- endfunc
-
- nmenu Foo.Bar :
- nnoremap <expr> <F2> Func()
- call feedkeys("\<F2>\<F2>\<Esc>", 'xt')
-
- delfunc Func
- nunmenu Foo.Bar
- nunmap <F2>
-endfunc
-
-func Test_popup_complete_info_01()
- new
- inoremap <buffer><F5> <C-R>=complete_info().mode<CR>
- func s:complTestEval() abort
- call complete(1, ['aa', 'ab'])
- return ''
- endfunc
- inoremap <buffer><F6> <C-R>=s:complTestEval()<CR>
- call writefile([
- \ 'dummy dummy.txt 1',
- \], 'Xdummy.txt')
- setlocal tags=Xdummy.txt
- setlocal dictionary=Xdummy.txt
- setlocal thesaurus=Xdummy.txt
- setlocal omnifunc=syntaxcomplete#Complete
- setlocal completefunc=syntaxcomplete#Complete
- setlocal spell
- for [keys, mode_name] in [
- \ ["", ''],
- \ ["\<C-X>", 'ctrl_x'],
- \ ["\<C-X>\<C-N>", 'keyword'],
- \ ["\<C-X>\<C-P>", 'keyword'],
- \ ["\<C-X>\<C-E>", 'scroll'],
- \ ["\<C-X>\<C-Y>", 'scroll'],
- \ ["\<C-X>\<C-E>\<C-E>\<C-Y>", 'scroll'],
- \ ["\<C-X>\<C-Y>\<C-E>\<C-Y>", 'scroll'],
- \ ["\<C-X>\<C-L>", 'whole_line'],
- \ ["\<C-X>\<C-F>", 'files'],
- \ ["\<C-X>\<C-]>", 'tags'],
- \ ["\<C-X>\<C-D>", 'path_defines'],
- \ ["\<C-X>\<C-I>", 'path_patterns'],
- \ ["\<C-X>\<C-K>", 'dictionary'],
- \ ["\<C-X>\<C-T>", 'thesaurus'],
- \ ["\<C-X>\<C-V>", 'cmdline'],
- \ ["\<C-X>\<C-U>", 'function'],
- \ ["\<C-X>\<C-O>", 'omni'],
- \ ["\<C-X>s", 'spell'],
- \ ["\<F6>", 'eval'],
- \]
- call feedkeys("i" . keys . "\<F5>\<Esc>", 'tx')
- call assert_equal(mode_name, getline('.'))
- %d
- endfor
- call delete('Xdummy.txt')
- bwipe!
-endfunc
-
-func UserDefinedComplete(findstart, base)
- if a:findstart
- return 0
- else
- return [
- \ { 'word': 'Jan', 'menu': 'January' },
- \ { 'word': 'Feb', 'menu': 'February' },
- \ { 'word': 'Mar', 'menu': 'March' },
- \ { 'word': 'Apr', 'menu': 'April' },
- \ { 'word': 'May', 'menu': 'May' },
- \ ]
- endif
-endfunc
-
-func GetCompleteInfo()
- if empty(g:compl_what)
- let g:compl_info = complete_info()
- else
- let g:compl_info = g:compl_what->complete_info()
- endif
- return ''
-endfunc
-
-func Test_popup_complete_info_02()
- new
- inoremap <buffer><F5> <C-R>=GetCompleteInfo()<CR>
- setlocal completefunc=UserDefinedComplete
-
- let d = {
- \ 'mode': 'function',
- \ 'pum_visible': 1,
- \ 'items': [
- \ {'word': 'Jan', 'menu': 'January', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
- \ {'word': 'Feb', 'menu': 'February', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
- \ {'word': 'Mar', 'menu': 'March', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
- \ {'word': 'Apr', 'menu': 'April', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
- \ {'word': 'May', 'menu': 'May', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}
- \ ],
- \ 'selected': 0,
- \ }
-
- let g:compl_what = []
- call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
- call assert_equal(d, g:compl_info)
-
- let g:compl_what = ['mode', 'pum_visible', 'selected']
- call remove(d, 'items')
- call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
- call assert_equal(d, g:compl_info)
-
- let g:compl_what = ['mode']
- call remove(d, 'selected')
- call remove(d, 'pum_visible')
- call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
- call assert_equal(d, g:compl_info)
- 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', ''])
- set complete=. completeopt=noinsert,noselect,menuone
- function! OnPumChange()
- let g:event = copy(v:event)
- let g:item = get(v:event, 'completed_item', {})
- let g:word = get(g:item, 'word', v:null)
- endfunction
- augroup AAAAA_Group
- au!
- autocmd CompleteChanged * :call OnPumChange()
- augroup END
- call cursor(4, 1)
-
- call feedkeys("Sf\<C-N>", 'tx')
- call assert_equal({'completed_item': {}, 'width': 15.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')
- call assert_equal('foobar', g:word)
- call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
- call assert_equal(v:null, g:word)
- call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'tx')
- call assert_equal('foobar', g:word)
-
- autocmd! AAAAA_Group
- set complete& completeopt&
- delfunc! OnPumChange
- bw!
-endfunc
-
-func GetPumPosition()
- call assert_true( pumvisible() )
- let g:pum_pos = pum_getpos()
- return ''
-endfunc
-
-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
-
-" Test for the popup menu with the 'rightleft' option set
-func Test_pum_rightleft()
- CheckFeature rightleft
- CheckScreendump
-
- let lines =<< trim END
- abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
- vim
- victory
- END
- call writefile(lines, 'Xtest1')
- let buf = RunVimInTerminal('--cmd "set rightleft" Xtest1', {})
- call term_wait(buf)
- call term_sendkeys(buf, "Go\<C-P>")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_pum_rightleft_01', {'rows': 8})
- call term_sendkeys(buf, "\<C-P>\<C-Y>")
- call term_wait(buf)
- redraw!
- call assert_match('\s*miv', Screenline(5))
-
- " Test for expanding tabs to spaces in the popup menu
- let lines =<< trim END
- one two
- one three
- four
- END
- call writefile(lines, 'Xtest2')
- call term_sendkeys(buf, "\<Esc>:e! Xtest2\<CR>")
- call term_wait(buf)
- call term_sendkeys(buf, "Goone\<C-X>\<C-L>")
- call term_wait(buf)
- redraw!
- call VerifyScreenDump(buf, 'Test_pum_rightleft_02', {'rows': 7})
- call term_sendkeys(buf, "\<C-Y>")
- call term_wait(buf)
- redraw!
- call assert_match('\s*eerht eno', Screenline(4))
-
- call StopVimInTerminal(buf)
- call delete('Xtest1')
- call delete('Xtest2')
-endfunc
-
-" Test for a popup menu with a scrollbar
-func Test_pum_scrollbar()
- CheckScreendump
- let lines =<< trim END
- one
- two
- three
- END
- call writefile(lines, 'Xtest1')
- let buf = RunVimInTerminal('--cmd "set pumheight=2" Xtest1', {})
- call term_wait(buf)
- call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_pum_scrollbar_01', {'rows': 7})
- call term_sendkeys(buf, "\<C-E>\<Esc>dd")
- call term_wait(buf)
-
- if has('rightleft')
- call term_sendkeys(buf, ":set rightleft\<CR>")
- call term_wait(buf)
- call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_pum_scrollbar_02', {'rows': 7})
- endif
-
- call StopVimInTerminal(buf)
- call delete('Xtest1')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_preview.vim b/src/nvim/testdir/test_preview.vim
deleted file mode 100644
index b7b908e761..0000000000
--- a/src/nvim/testdir/test_preview.vim
+++ /dev/null
@@ -1,64 +0,0 @@
-" Tests for the preview window
-
-source check.vim
-CheckFeature quickfix
-
-func Test_Psearch()
- " this used to cause ml_get errors
- help
- let wincount = winnr('$')
- 0f
- ps.
- call assert_equal(wincount + 1, winnr('$'))
- pclose
- call assert_equal(wincount, winnr('$'))
- bwipe
-endfunc
-
-func Test_window_preview()
- CheckFeature quickfix
-
- " Open a preview window
- pedit Xa
- call assert_equal(2, winnr('$'))
- call assert_equal(0, &previewwindow)
-
- " Go to the preview window
- wincmd P
- call assert_equal(1, &previewwindow)
- call assert_equal('preview', win_gettype())
-
- " Close preview window
- wincmd z
- call assert_equal(1, winnr('$'))
- call assert_equal(0, &previewwindow)
-
- call assert_fails('wincmd P', 'E441:')
-endfunc
-
-func Test_window_preview_from_help()
- CheckFeature quickfix
-
- filetype on
- call writefile(['/* some C code */'], 'Xpreview.c')
- help
- pedit Xpreview.c
- wincmd P
- call assert_equal(1, &previewwindow)
- call assert_equal('c', &filetype)
- wincmd z
-
- filetype off
- close
- call delete('Xpreview.c')
-endfunc
-
-func Test_multiple_preview_windows()
- new
- set previewwindow
- new
- call assert_fails('set previewwindow', 'E590:')
- %bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim
deleted file mode 100644
index 9165f7bace..0000000000
--- a/src/nvim/testdir/test_profile.vim
+++ /dev/null
@@ -1,596 +0,0 @@
-" Test Vim profiler
-
-source check.vim
-CheckFeature profile
-
-source shared.vim
-source screendump.vim
-
-func Test_profile_func()
- 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(GetVimCommand()
- \ . ' -es --clean'
- \ . ' --cmd "so Xprofile_func.vim"'
- \ . ' --cmd "qall!"')
- call assert_equal(0, v:shell_error)
-
- let lines = readfile('Xprofile_func.log')
-
- " - Foo1() is called 3 times but should be reported as called twice
- " since one call is in between "profile pause" .. "profile continue".
- " - Foo2() should come before Foo1() since Foo1() does much more work.
- " - Foo3() is not reported because function is deleted.
- " - 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(31, len(lines))
-
- call assert_equal('FUNCTION Foo1()', lines[0])
- 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])
- call assert_equal('', lines[5])
- call assert_equal('count total (s) self (s)', lines[6])
- call assert_equal('', lines[7])
- call assert_equal('FUNCTION Foo2()', lines[8])
- call assert_equal('Called 1 time', lines[10])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[11])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[12])
- call assert_equal('', lines[13])
- call assert_equal('count total (s) self (s)', lines[14])
- call assert_match('^\s*1\s\+.*\slet l:count = 100$', lines[15])
- 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_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 =<< 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(GetVimCommand()
- \ . ' -es -i NONE --noplugin'
- \ . ' -c "profile start Xprofile_func.log"'
- \ . ' -c "profile func Foo*"'
- \ . ' -c "so Xprofile_func.vim"'
- \ . ' -c "qall!"')
- call assert_equal(0, v:shell_error)
-
- let lines = readfile('Xprofile_func.log')
-
- " - Foo1() should pass 'if' block.
- " - Foo2() should pass 'elseif' block.
- " - Foo3() should pass 'else' block.
- call assert_equal(57, len(lines))
-
- call assert_equal('FUNCTION Foo1()', lines[0])
- call assert_match('Defined:.*Xprofile_func.vim', lines[1])
- call assert_equal('Called 1 time', lines[2])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
- call assert_equal('', lines[5])
- call assert_equal('count total (s) self (s)', lines[6])
- call assert_match('^\s*1\s\+.*\sif 1$', lines[7])
- call assert_match('^\s*1\s\+.*\s let x = 0$', lines[8])
- call assert_match( '^\s\+elseif 1$', lines[9])
- call assert_match( '^\s\+let x = 1$', lines[10])
- call assert_match( '^\s\+else$', lines[11])
- call assert_match( '^\s\+let x = 2$', lines[12])
- call assert_match('^\s*1\s\+.*\sendif$', lines[13])
- call assert_equal('', lines[14])
- call assert_equal('FUNCTION Foo2()', lines[15])
- call assert_equal('Called 1 time', lines[17])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[18])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[19])
- call assert_equal('', lines[20])
- call assert_equal('count total (s) self (s)', lines[21])
- call assert_match('^\s*1\s\+.*\sif 0$', lines[22])
- call assert_match( '^\s\+let x = 0$', lines[23])
- call assert_match('^\s*1\s\+.*\selseif 1$', lines[24])
- call assert_match('^\s*1\s\+.*\s let x = 1$', lines[25])
- call assert_match( '^\s\+else$', lines[26])
- call assert_match( '^\s\+let x = 2$', lines[27])
- call assert_match('^\s*1\s\+.*\sendif$', lines[28])
- call assert_equal('', lines[29])
- call assert_equal('FUNCTION Foo3()', lines[30])
- call assert_equal('Called 1 time', lines[32])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[33])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[34])
- call assert_equal('', lines[35])
- call assert_equal('count total (s) self (s)', lines[36])
- call assert_match('^\s*1\s\+.*\sif 0$', lines[37])
- call assert_match( '^\s\+let x = 0$', lines[38])
- call assert_match('^\s*1\s\+.*\selseif 0$', lines[39])
- call assert_match( '^\s\+let x = 1$', lines[40])
- call assert_match('^\s*1\s\+.*\selse$', lines[41])
- call assert_match('^\s*1\s\+.*\s let x = 2$', lines[42])
- call assert_match('^\s*1\s\+.*\sendif$', lines[43])
- call assert_equal('', lines[44])
- call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[45])
- call assert_equal('count total (s) self (s) function', lines[46])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[47])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[48])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[49])
- call assert_equal('', lines[50])
- call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[51])
- call assert_equal('count total (s) self (s) function', lines[52])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[53])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[54])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[55])
- call assert_equal('', lines[56])
-
- call delete('Xprofile_func.vim')
- call delete('Xprofile_func.log')
-endfunc
-
-func Test_profile_func_with_trycatch()
- 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(GetVimCommand()
- \ . ' -es -i NONE --noplugin'
- \ . ' -c "profile start Xprofile_func.log"'
- \ . ' -c "profile func Foo*"'
- \ . ' -c "so Xprofile_func.vim"'
- \ . ' -c "qall!"')
- call assert_equal(0, v:shell_error)
-
- let lines = readfile('Xprofile_func.log')
-
- " - Foo1() should pass 'try' 'finally' blocks.
- " - Foo2() should pass 'catch' 'finally' blocks.
- " - Foo3() should not pass 'endtry'.
- call assert_equal(57, len(lines))
-
- call assert_equal('FUNCTION Foo1()', lines[0])
- call assert_match('Defined:.*Xprofile_func.vim', lines[1])
- call assert_equal('Called 1 time', lines[2])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
- call assert_equal('', lines[5])
- call assert_equal('count total (s) self (s)', lines[6])
- call assert_match('^\s*1\s\+.*\stry$', lines[7])
- call assert_match('^\s*1\s\+.*\s let x = 0$', lines[8])
- call assert_match( '^\s\+catch$', lines[9])
- call assert_match( '^\s\+let x = 1$', lines[10])
- call assert_match('^\s*1\s\+.*\sfinally$', lines[11])
- call assert_match('^\s*1\s\+.*\s let x = 2$', lines[12])
- call assert_match('^\s*1\s\+.*\sendtry$', lines[13])
- call assert_equal('', lines[14])
- call assert_equal('FUNCTION Foo2()', lines[15])
- call assert_equal('Called 1 time', lines[17])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[18])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[19])
- call assert_equal('', lines[20])
- call assert_equal('count total (s) self (s)', lines[21])
- call assert_match('^\s*1\s\+.*\stry$', lines[22])
- call assert_match('^\s*1\s\+.*\s throw 0$', lines[23])
- call assert_match('^\s*1\s\+.*\scatch$', lines[24])
- call assert_match('^\s*1\s\+.*\s let x = 1$', lines[25])
- call assert_match('^\s*1\s\+.*\sfinally$', lines[26])
- call assert_match('^\s*1\s\+.*\s let x = 2$', lines[27])
- call assert_match('^\s*1\s\+.*\sendtry$', lines[28])
- call assert_equal('', lines[29])
- call assert_equal('FUNCTION Foo3()', lines[30])
- call assert_equal('Called 1 time', lines[32])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[33])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[34])
- call assert_equal('', lines[35])
- call assert_equal('count total (s) self (s)', lines[36])
- call assert_match('^\s*1\s\+.*\stry$', lines[37])
- call assert_match('^\s*1\s\+.*\s throw 0$', lines[38])
- call assert_match('^\s*1\s\+.*\scatch$', lines[39])
- call assert_match('^\s*1\s\+.*\s throw 1$', lines[40])
- call assert_match('^\s*1\s\+.*\sfinally$', lines[41])
- call assert_match('^\s*1\s\+.*\s let x = 2$', lines[42])
- call assert_match( '^\s\+endtry$', lines[43])
- call assert_equal('', lines[44])
- call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[45])
- call assert_equal('count total (s) self (s) function', lines[46])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[47])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[48])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[49])
- call assert_equal('', lines[50])
- call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[51])
- call assert_equal('count total (s) self (s) function', lines[52])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[53])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[54])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[55])
- call assert_equal('', lines[56])
-
- call delete('Xprofile_func.vim')
- call delete('Xprofile_func.log')
-endfunc
-
-func Test_profile_file()
- 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(GetVimCommandClean()
- \ . ' -es'
- \ . ' -c "profile start Xprofile_file.log"'
- \ . ' -c "profile file Xprofile_file.vim"'
- \ . ' -c "so Xprofile_file.vim"'
- \ . ' -c "so Xprofile_file.vim"'
- \ . ' -c "qall!"')
- call assert_equal(0, v:shell_error)
-
- let lines = readfile('Xprofile_file.log')
-
- call assert_equal(14, len(lines))
-
- call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0])
- call assert_equal('Sourced 2 times', lines[1])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3])
- call assert_equal('', lines[4])
- call assert_equal('count total (s) self (s)', lines[5])
- call assert_match(' 2 0.\d\+ func! Foo()', lines[6])
- call assert_equal(' endfunc', lines[7])
- " Loop iterates 10 times. Since script runs twice, body executes 20 times.
- " First line of loop executes one more time than body to detect end of loop.
- call assert_match('^\s*22\s\+\d\+\.\d\+\s\+for i in range(10)$', lines[8])
- call assert_equal(' " a comment', lines[9])
- " if self and total are equal we only get one number
- call assert_match('^\s*20\s\+\(\d\+\.\d\+\s\+\)\=\d\+\.\d\+\s\+call Foo()$', lines[10])
- call assert_match('^\s*22\s\+\d\+\.\d\+\s\+endfor$', lines[11])
- " if self and total are equal we only get one number
- call assert_match('^\s*2\s\+\(\d\+\.\d\+\s\+\)\=\d\+\.\d\+\s\+call Foo()$', lines[12])
- call assert_equal('', lines[13])
-
- call delete('Xprofile_file.vim')
- call delete('Xprofile_file.log')
-endfunc
-
-func Test_profile_file_with_cont()
- let lines = [
- \ 'echo "hello',
- \ ' \ world"',
- \ 'echo "foo ',
- \ ' \bar"',
- \ ]
-
- call writefile(lines, 'Xprofile_file.vim')
- call system(GetVimCommandClean()
- \ . ' -es'
- \ . ' -c "profile start Xprofile_file.log"'
- \ . ' -c "profile file Xprofile_file.vim"'
- \ . ' -c "so Xprofile_file.vim"'
- \ . ' -c "qall!"')
- call assert_equal(0, v:shell_error)
-
- let lines = readfile('Xprofile_file.log')
- call assert_equal(11, len(lines))
-
- call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0])
- call assert_equal('Sourced 1 time', lines[1])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3])
- call assert_equal('', lines[4])
- call assert_equal('count total (s) self (s)', lines[5])
- call assert_match(' 1 0.\d\+ echo "hello', lines[6])
- call assert_equal(' \ world"', lines[7])
- call assert_match(' 1 0.\d\+ echo "foo ', lines[8])
- call assert_equal(' \bar"', lines[9])
- call assert_equal('', lines[10])
-
- call delete('Xprofile_file.vim')
- call delete('Xprofile_file.log')
-endfunc
-
-func Test_profile_completion()
- call feedkeys(":profile \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profile continue dump file func pause start stop', @:)
-
- call feedkeys(":profile start test_prof\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"profile start.* test_profile\.vim', @:)
-
- call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match('"profile file test_profile\.vim', @:)
- call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match('"profile file test_profile\.vim', @:)
- call feedkeys(":profile file test_prof \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match('"profile file test_prof ', @:)
- call feedkeys(":profile file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_match('"profile file X1B2C3', @:)
-
- func Xprof_test()
- endfunc
- call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profile func Xprof_test', @:)
- call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profile func Xprof_test', @:)
- call feedkeys(":profile func Xprof \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profile func Xprof ', @:)
- call feedkeys(":profile func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profile func X1B2C3', @:)
-
- call feedkeys(":profdel \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel file func', @:)
- call feedkeys(":profdel fu\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel func', @:)
- call feedkeys(":profdel he\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel he', @:)
- call feedkeys(":profdel here \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel here ', @:)
- call feedkeys(":profdel file test_prof\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel file test_profile.vim', @:)
- call feedkeys(":profdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel file X1B2C3', @:)
- call feedkeys(":profdel func Xprof\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel func Xprof_test', @:)
- call feedkeys(":profdel func Xprof_test \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel func Xprof_test ', @:)
- call feedkeys(":profdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"profdel func X1B2C3', @:)
-
- delfunc Xprof_test
-endfunc
-
-func Test_profile_errors()
- call assert_fails("profile func Foo", 'E750:')
- call assert_fails("profile pause", 'E750:')
- call assert_fails("profile continue", 'E750:')
-endfunc
-
-func Test_profile_truncate_mbyte()
- if &enc !=# 'utf-8'
- return
- endif
-
- let lines = [
- \ 'scriptencoding utf-8',
- \ 'func! Foo()',
- \ ' return [',
- \ ' \ "' . join(map(range(0x4E00, 0x4E00 + 340), 'nr2char(v:val)'), '') . '",',
- \ ' \ "' . join(map(range(0x4F00, 0x4F00 + 340), 'nr2char(v:val)'), '') . '",',
- \ ' \ ]',
- \ 'endfunc',
- \ 'call Foo()',
- \ ]
-
- call writefile(lines, 'Xprofile_file.vim')
- call system(GetVimCommandClean()
- \ . ' -es --cmd "set enc=utf-8"'
- \ . ' -c "profile start Xprofile_file.log"'
- \ . ' -c "profile file Xprofile_file.vim"'
- \ . ' -c "so Xprofile_file.vim"'
- \ . ' -c "qall!"')
- call assert_equal(0, v:shell_error)
-
- split Xprofile_file.log
- if &fenc != ''
- call assert_equal('utf-8', &fenc)
- endif
- /func! Foo()
- let lnum = line('.')
- call assert_match('^\s*return \[$', getline(lnum + 1))
- call assert_match("\u4F52$", getline(lnum + 2))
- call assert_match("\u5052$", getline(lnum + 3))
- call assert_match('^\s*\\ \]$', getline(lnum + 4))
- bwipe!
-
- call delete('Xprofile_file.vim')
- call delete('Xprofile_file.log')
-endfunc
-
-func Test_profdel_func()
- 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(GetVimCommandClean() . ' -es --cmd "so Xprofile_file.vim" --cmd q')
- call assert_equal(0, v:shell_error)
-
- let lines = readfile('Xprofile_file.log')
- call assert_equal(26, len(lines))
-
- " Check that:
- " - Foo1() is called twice (profdel not invoked)
- " - Foo2() is called once (profdel invoked after it was called)
- " - Foo3() is not called (profdel invoked before it was called)
- call assert_equal('FUNCTION Foo1()', lines[0])
- call assert_match('Defined:.*Xprofile_file.vim', lines[1])
- call assert_equal('Called 2 times', lines[2])
- call assert_equal('FUNCTION Foo2()', lines[8])
- call assert_equal('Called 1 time', lines[10])
- call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[16])
- call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[21])
-
- call delete('Xprofile_file.vim')
- call delete('Xprofile_file.log')
-endfunc
-
-func Test_profdel_star()
- " Foo() is invoked once before and once after 'profdel *'.
- " So profiling should report it only once.
- let lines =<< 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(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q')
- call assert_equal(0, v:shell_error)
-
- let lines = readfile('Xprofile_file.log')
- call assert_equal(16, len(lines))
-
- call assert_equal('FUNCTION Foo()', lines[0])
- call assert_match('Defined:.*Xprofile_file.vim', lines[1])
- call assert_equal('Called 1 time', lines[2])
- call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[8])
- call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[12])
-
- call delete('Xprofile_file.vim')
- call delete('Xprofile_file.log')
-endfunc
-
-" When typing the function it won't have a script ID, test that this works.
-func Test_profile_typed_func()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
-
- let lines =<< trim END
- profile start XprofileTypedFunc
- END
- call writefile(lines, 'XtestProfile')
- let buf = RunVimInTerminal('-S XtestProfile', #{})
-
- call term_sendkeys(buf, ":func DoSomething()\<CR>"
- \ .. "echo 'hello'\<CR>"
- \ .. "endfunc\<CR>")
- call term_sendkeys(buf, ":profile func DoSomething\<CR>")
- call term_sendkeys(buf, ":call DoSomething()\<CR>")
- call term_wait(buf, 200)
- call StopVimInTerminal(buf)
- let lines = readfile('XprofileTypedFunc')
- call assert_equal("FUNCTION DoSomething()", lines[0])
- call assert_equal("Called 1 time", lines[1])
-
- " clean up
- call delete('XprofileTypedFunc')
- call delete('XtestProfile')
-endfunc
diff --git a/src/nvim/testdir/test_prompt_buffer.vim b/src/nvim/testdir/test_prompt_buffer.vim
deleted file mode 100644
index b8f6c5240c..0000000000
--- a/src/nvim/testdir/test_prompt_buffer.vim
+++ /dev/null
@@ -1,262 +0,0 @@
-" Tests for setting 'buftype' to "prompt"
-
-source check.vim
-" Nvim's channel implementation differs from Vim's
-" CheckFeature channel
-
-source shared.vim
-source screendump.vim
-
-func CanTestPromptBuffer()
- " We need to use a terminal window to be able to feed keys without leaving
- " Insert mode.
- " Nvim's terminal implementation differs from Vim's
- " CheckFeature terminal
-
- " TODO: make the tests work on MS-Windows
- CheckNotMSWindows
-endfunc
-
-func WriteScript(name)
- call writefile([
- \ 'func TextEntered(text)',
- \ ' if a:text == "exit"',
- \ ' " Reset &modified to allow the buffer to be closed.',
- \ ' set nomodified',
- \ ' stopinsert',
- \ ' close',
- \ ' else',
- \ ' " Add the output above the current prompt.',
- \ ' call append(line("$") - 1, "Command: \"" . a:text . "\"")',
- \ ' " Reset &modified to allow the buffer to be closed.',
- \ ' set nomodified',
- \ ' call timer_start(20, {id -> TimerFunc(a:text)})',
- \ ' endif',
- \ 'endfunc',
- \ '',
- \ 'func TimerFunc(text)',
- \ ' " Add the output above the current prompt.',
- \ ' call append(line("$") - 1, "Result: \"" . a:text . "\"")',
- \ ' " Reset &modified to allow the buffer to be closed.',
- \ ' set nomodified',
- \ 'endfunc',
- \ '',
- \ 'func SwitchWindows()',
- \ ' call timer_start(0, {-> execute("wincmd p|wincmd p", "")})',
- \ 'endfunc',
- \ '',
- \ 'call setline(1, "other buffer")',
- \ 'set nomodified',
- \ 'new',
- \ 'set buftype=prompt',
- \ 'call prompt_setcallback(bufnr(""), function("TextEntered"))',
- \ 'eval bufnr("")->prompt_setprompt("cmd: ")',
- \ 'startinsert',
- \ ], a:name)
-endfunc
-
-func Test_prompt_basic()
- throw 'skipped: TODO'
- call CanTestPromptBuffer()
- let scriptName = 'XpromptscriptBasic'
- call WriteScript(scriptName)
-
- let buf = RunVimInTerminal('-S ' . scriptName, {})
- call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
-
- call term_sendkeys(buf, "hello\<CR>")
- call WaitForAssert({-> assert_equal('cmd: hello', term_getline(buf, 1))})
- call WaitForAssert({-> assert_equal('Command: "hello"', term_getline(buf, 2))})
- call WaitForAssert({-> assert_equal('Result: "hello"', term_getline(buf, 3))})
-
- call term_sendkeys(buf, "exit\<CR>")
- call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))})
-
- call StopVimInTerminal(buf)
- call delete(scriptName)
-endfunc
-
-func Test_prompt_editing()
- throw 'skipped: TODO'
- call CanTestPromptBuffer()
- let scriptName = 'XpromptscriptEditing'
- call WriteScript(scriptName)
-
- let buf = RunVimInTerminal('-S ' . scriptName, {})
- call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
-
- let bs = "\<BS>"
- call term_sendkeys(buf, "hello" . bs . bs)
- call WaitForAssert({-> assert_equal('cmd: hel', term_getline(buf, 1))})
-
- let left = "\<Left>"
- call term_sendkeys(buf, left . left . left . bs . '-')
- call WaitForAssert({-> assert_equal('cmd: -hel', term_getline(buf, 1))})
-
- call term_sendkeys(buf, "\<C-O>lz")
- call WaitForAssert({-> assert_equal('cmd: -hzel', term_getline(buf, 1))})
-
- let end = "\<End>"
- call term_sendkeys(buf, end . "x")
- call WaitForAssert({-> assert_equal('cmd: -hzelx', term_getline(buf, 1))})
-
- call term_sendkeys(buf, "\<C-U>exit\<CR>")
- call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))})
-
- call StopVimInTerminal(buf)
- call delete(scriptName)
-endfunc
-
-func Test_prompt_switch_windows()
- throw 'skipped: TODO'
- call CanTestPromptBuffer()
- let scriptName = 'XpromptSwitchWindows'
- call WriteScript(scriptName)
-
- let buf = RunVimInTerminal('-S ' . scriptName, {'rows': 12})
- call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
- call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))})
-
- call term_sendkeys(buf, "\<C-O>:call SwitchWindows()\<CR>")
- call term_wait(buf, 50)
- call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))})
-
- call term_sendkeys(buf, "\<Esc>")
- call term_wait(buf, 50)
- call WaitForAssert({-> assert_match('^ *$', term_getline(buf, 12))})
-
- call StopVimInTerminal(buf)
- call delete(scriptName)
-endfunc
-
-func Test_prompt_garbage_collect()
- func MyPromptCallback(x, text)
- " NOP
- endfunc
- func MyPromptInterrupt(x)
- " NOP
- endfunc
-
- new
- set buftype=prompt
- eval bufnr('')->prompt_setcallback(function('MyPromptCallback', [{}]))
- eval bufnr('')->prompt_setinterrupt(function('MyPromptInterrupt', [{}]))
- call test_garbagecollect_now()
- " Must not crash
- call feedkeys("\<CR>\<C-C>", 'xt')
- call assert_true(v:true)
-
- call assert_fails("call prompt_setcallback(bufnr(), [])", 'E921:')
- call assert_equal(0, prompt_setcallback({}, ''))
- call assert_fails("call prompt_setinterrupt(bufnr(), [])", 'E921:')
- call assert_equal(0, prompt_setinterrupt({}, ''))
-
- delfunc MyPromptCallback
- bwipe!
-endfunc
-
-func Test_prompt_backspace()
- new
- set buftype=prompt
- call feedkeys("A123456\<Left>\<BS>\<Esc>", 'xt')
- call assert_equal('% 12346', getline(1))
- bwipe!
-endfunc
-
-" Test for editing the prompt buffer
-func Test_prompt_buffer_edit()
- new
- set buftype=prompt
- normal! i
- call assert_beeps('normal! dd')
- call assert_beeps('normal! ~')
- call assert_beeps('normal! o')
- call assert_beeps('normal! O')
- call assert_beeps('normal! p')
- call assert_beeps('normal! P')
- call assert_beeps('normal! u')
- call assert_beeps('normal! ra')
- call assert_beeps('normal! s')
- call assert_beeps('normal! S')
- call assert_beeps("normal! \<C-A>")
- call assert_beeps("normal! \<C-X>")
- call assert_beeps("normal! dp")
- call assert_beeps("normal! do")
- " pressing CTRL-W in the prompt buffer should trigger the window commands
- call assert_equal(1, winnr())
- exe "normal A\<C-W>\<C-W>"
- call assert_equal(2, winnr())
- wincmd w
- close!
- call assert_equal(0, prompt_setprompt([], ''))
-endfunc
-
-func Test_prompt_buffer_getbufinfo()
- new
- call assert_equal('', prompt_getprompt('%'))
- call assert_equal('', prompt_getprompt(bufnr('%')))
- let another_buffer = bufnr('%')
-
- set buftype=prompt
- call assert_equal('% ', prompt_getprompt('%'))
- call prompt_setprompt( bufnr( '%' ), 'This is a test: ' )
- call assert_equal('This is a test: ', prompt_getprompt('%'))
-
- call prompt_setprompt( bufnr( '%' ), '' )
- call assert_equal('', '%'->prompt_getprompt())
-
- call prompt_setprompt( bufnr( '%' ), 'Another: ' )
- call assert_equal('Another: ', prompt_getprompt('%'))
- let another = bufnr('%')
-
- new
-
- call assert_equal('', prompt_getprompt('%'))
- call assert_equal('Another: ', prompt_getprompt(another))
-
- " Doesn't exist
- let buffers_before = len( getbufinfo() )
- call assert_equal('', prompt_getprompt( bufnr('$') + 1))
- call assert_equal(buffers_before, len( getbufinfo()))
-
- " invalid type
- call assert_fails('call prompt_getprompt({})', 'E728:')
-
- %bwipe!
-endfunc
-
-func Test_prompt_while_writing_to_hidden_buffer()
- throw 'skipped: TODO'
- call CanTestPromptBuffer()
- CheckUnix
-
- " Make a job continuously write to a hidden buffer, check that the prompt
- " buffer is not affected.
- let scriptName = 'XpromptscriptHiddenBuf'
- let script =<< trim END
- set buftype=prompt
- call prompt_setprompt( bufnr(), 'cmd:' )
- let job = job_start(['/bin/sh', '-c',
- \ 'while true;
- \ do echo line;
- \ sleep 0.1;
- \ done'], #{out_io: 'buffer', out_name: ''})
- startinsert
- END
- eval script->writefile(scriptName)
-
- let buf = RunVimInTerminal('-S ' .. scriptName, {})
- call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
-
- call term_sendkeys(buf, 'test')
- call WaitForAssert({-> assert_equal('cmd:test', term_getline(buf, 1))})
- call term_sendkeys(buf, 'test')
- call WaitForAssert({-> assert_equal('cmd:testtest', term_getline(buf, 1))})
- call term_sendkeys(buf, 'test')
- call WaitForAssert({-> assert_equal('cmd:testtesttest', term_getline(buf, 1))})
-
- call StopVimInTerminal(buf)
- call delete(scriptName)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim
deleted file mode 100644
index 97af3699a8..0000000000
--- a/src/nvim/testdir/test_put.vim
+++ /dev/null
@@ -1,239 +0,0 @@
-" Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc.
-
-source check.vim
-
-func Test_put_block()
- new
- call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x')
- call feedkeys("\<C-V>y", 'x')
- call feedkeys("gg0p", 'x')
- call assert_equal("\u2500x", getline(1))
- bwipe!
-endfunc
-
-func Test_put_char_block()
- new
- call setline(1, ['Line 1', 'Line 2'])
- f Xfile_put
- " visually select both lines and put the cursor at the top of the visual
- " selection and then put the buffer name over it
- exe "norm! G0\<c-v>ke\"%p"
- call assert_equal(['Xfile_put 1', 'Xfile_put 2'], getline(1,2))
- bw!
-endfunc
-
-func Test_put_char_block2()
- new
- call setreg('a', ' one ', 'v')
- call setline(1, ['Line 1', '', 'Line 3', ''])
- " visually select the first 3 lines and put register a over it
- exe "norm! ggl\<c-v>2j2l\"ap"
- call assert_equal(['L one 1', '', 'L one 3', ''], getline(1, 4))
- " clean up
- bw!
-endfunc
-
-func Test_put_lines()
- new
- let a = [ getreg('a'), getregtype('a') ]
- call setline(1, ['Line 1', 'Line2', 'Line 3', ''])
- exe 'norm! gg"add"AddG""p'
- call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$'))
- " clean up
- bw!
- eval a[0]->setreg('a', a[1])
-endfunc
-
-func Test_put_expr()
- new
- call setline(1, repeat(['A'], 6))
- exec "1norm! \"=line('.')\<cr>p"
- norm! j0.
- norm! j0.
- exec "4norm! \"=\<cr>P"
- norm! j0.
- norm! j0.
- call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1, '$'))
- bw!
-endfunc
-
-func Test_put_fails_when_nomodifiable()
- new
- setlocal nomodifiable
-
- normal! yy
- call assert_fails(':put', 'E21')
- call assert_fails(':put!', 'E21')
- call assert_fails(':normal! p', 'E21')
- call assert_fails(':normal! gp', 'E21')
- call assert_fails(':normal! P', 'E21')
- call assert_fails(':normal! gP', 'E21')
-
- if has('mouse')
- set mouse=n
- call assert_fails('execute "normal! \<MiddleMouse>"', 'E21')
- set mouse&
- endif
-
- bwipeout!
-endfunc
-
-" A bug was discovered where the Normal mode put commands (e.g., "p") would
-" output duplicate error messages when invoked in a non-modifiable buffer.
-func Test_put_p_errmsg_nodup()
- new
- setlocal nomodifiable
-
- normal! yy
-
- func Capture_p_error()
- redir => s:p_err
- normal! p
- redir END
- endfunc
-
- silent! call Capture_p_error()
-
- " Error message output within a function should be three lines (the function
- " name, the line number, and the error message).
- call assert_equal(3, count(s:p_err, "\n"))
-
- delfunction Capture_p_error
- bwipeout!
-endfunc
-
-func Test_put_p_indent_visual()
- new
- call setline(1, ['select this text', 'select that text'])
- " yank "that" from the second line
- normal 2Gwvey
- " select "this" in the first line and put
- normal k0wve[p
- call assert_equal('select that text', getline(1))
- call assert_equal('select that text', getline(2))
- bwipe!
-endfunc
-
-" Test for deleting all the contents of a buffer with a put
-func Test_put_visual_delete_all_lines()
- new
- call setline(1, ['one', 'two', 'three'])
- let @r = ''
- normal! VG"rgp
- call assert_equal(1, line('$'))
- close!
-endfunc
-
-func Test_gp_with_count_leaves_cursor_at_end()
- new
- call setline(1, '<---->')
- call setreg('@', "foo\nbar", 'c')
- normal 1G3|3gp
- call assert_equal([0, 4, 4, 0], getpos("."))
- call assert_equal(['<--foo', 'barfoo', 'barfoo', 'bar-->'], getline(1, '$'))
- call assert_equal([0, 4, 3, 0], getpos("']"))
-
- bwipe!
-endfunc
-
-func Test_p_with_count_leaves_mark_at_end()
- new
- call setline(1, '<---->')
- call setreg('@', "start\nend", 'c')
- normal 1G3|3p
- call assert_equal([0, 1, 4, 0], getpos("."))
- call assert_equal(['<--start', 'endstart', 'endstart', 'end-->'], getline(1, '$'))
- call assert_equal([0, 4, 3, 0], getpos("']"))
-
- bwipe!
-endfunc
-
-func Test_very_large_count()
- new
- " total put-length (21474837 * 100) brings 32 bit int overflow
- let @" = repeat('x', 100)
- call assert_fails('norm 21474837p', 'E1240:')
- bwipe!
-endfunc
-
-func Test_very_large_count_64bit()
- throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead
-
- if v:sizeoflong < 8
- throw 'Skipped: only works with 64 bit long ints'
- endif
-
- new
- let @" = repeat('x', 100)
- call assert_fails('norm 999999999p', 'E1240:')
- bwipe!
-endfunc
-
-func Test_very_large_count_block()
- new
- " total put-length (21474837 * 100) brings 32 bit int overflow
- call setline(1, repeat('x', 100))
- exe "norm \<C-V>99ly"
- call assert_fails('norm 21474837p', 'E1240:')
- bwipe!
-endfunc
-
-func Test_very_large_count_block_64bit()
- throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead
-
- if v:sizeoflong < 8
- throw 'Skipped: only works with 64 bit long ints'
- endif
-
- new
- call setline(1, repeat('x', 100))
- exe "norm \<C-V>$y"
- call assert_fails('norm 999999999p', 'E1240:')
- bwipe!
-endfunc
-
-func Test_put_above_first_line()
- new
- let @" = 'text'
- silent! normal 0o00
- 0put
- call assert_equal('text', getline(1))
- bwipe!
-endfunc
-
-func Test_multibyte_op_end_mark()
- new
- call setline(1, 'теÑÑ‚')
- normal viwdp
- call assert_equal([0, 1, 7, 0], getpos("'>"))
- call assert_equal([0, 1, 7, 0], getpos("']"))
-
- normal Vyp
- call assert_equal([0, 1, 2147483647, 0], getpos("'>"))
- call assert_equal([0, 2, 7, 0], getpos("']"))
- bwipe!
-endfunc
-
-" this was putting a mark before the start of a line
-func Test_put_empty_register()
- new
- norm yy
- norm [Pi00ggv)s0
- sil! norm [P
- bwipe!
-endfunc
-
-" this was putting the end mark after the end of the line
-func Test_put_visual_mode()
- edit! SomeNewBuffer
- set selection=exclusive
- exe "norm o\t"
- m0
- sil! norm  p p
-
- bwipe!
- set selection&
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim
deleted file mode 100644
index 745b7da086..0000000000
--- a/src/nvim/testdir/test_python2.vim
+++ /dev/null
@@ -1,173 +0,0 @@
-" Test for python 2 commands.
-" TODO: move tests from test86.in here.
-
-source check.vim
-CheckFeature python
-
-func Test_pydo()
- " Check deleting lines does not trigger ml_get error.
- py import vim
- new
- call setline(1, ['one', 'two', 'three'])
- pydo vim.command("%d_")
- bwipe!
-
- " Disabled until neovim/neovim#8554 is resolved
- if 0
- " Check switching to another buffer does not trigger ml_get error.
- new
- let wincount = winnr('$')
- call setline(1, ['one', 'two', 'three'])
- pydo vim.command("new")
- call assert_equal(wincount + 1, winnr('$'))
- bwipe!
- bwipe!
- endif
-endfunc
-
-func Test_set_cursor()
- " Check that setting the cursor position works.
- py import vim
- new
- call setline(1, ['first line', 'second line'])
- normal gg
- pydo vim.current.window.cursor = (1, 5)
- call assert_equal([1, 6], [line('.'), col('.')])
-
- " Check that movement after setting cursor position keeps current column.
- normal j
- call assert_equal([2, 6], [line('.'), col('.')])
-endfunc
-
-func Test_vim_function()
- throw 'skipped: Nvim does not support vim.bindeval()'
- " Check creating vim.Function object
- py import vim
-
- func s:foo()
- return matchstr(expand('<sfile>'), '<SNR>\zs\d\+_foo$')
- endfunc
- let name = '<SNR>' . s:foo()
-
- try
- py f = vim.bindeval('function("s:foo")')
- call assert_equal(name, pyeval('f.name'))
- catch
- call assert_false(v:exception)
- endtry
-
- try
- py f = vim.Function('\x80\xfdR' + vim.eval('s:foo()'))
- call assert_equal(name, 'f.name'->pyeval())
- catch
- call assert_false(v:exception)
- endtry
-
- py del f
- delfunc s:foo
-endfunc
-
-func Test_skipped_python_command_does_not_affect_pyxversion()
- set pyxversion=0
- if 0
- python import vim
- endif
- call assert_equal(0, &pyxversion) " This assertion would have failed with Vim 8.0.0251. (pyxversion was introduced in 8.0.0251.)
-endfunc
-
-func _SetUpHiddenBuffer()
- py import vim
- new
- edit hidden
- setlocal bufhidden=hide
-
- enew
- let lnum = 0
- while lnum < 10
- call append( 1, string( lnum ) )
- let lnum = lnum + 1
- endwhile
- normal G
-
- call assert_equal( line( '.' ), 11 )
-endfunc
-
-func _CleanUpHiddenBuffer()
- bwipe! hidden
- bwipe!
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Clear()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = None
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_List()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = [ 'test' ]
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Str()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = 'test'
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_ClearLine()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = None
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func _SetUpVisibleBuffer()
- py import vim
- new
- let lnum = 0
- while lnum < 10
- call append( 1, string( lnum ) )
- let lnum = lnum + 1
- endwhile
- normal G
- call assert_equal( line( '.' ), 11 )
-endfunc
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_Clear()
- call _SetUpVisibleBuffer()
-
- py vim.current.buffer[:] = None
- call assert_equal( line( '.' ), 1 )
-
- bwipe!
-endfunc
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_List()
- call _SetUpVisibleBuffer()
-
- py vim.current.buffer[:] = [ 'test' ]
- call assert_equal( line( '.' ), 1 )
-
- bwipe!
-endfunction
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_Str()
- call _SetUpVisibleBuffer()
-
- py vim.current.buffer[-1] = None
- call assert_equal( line( '.' ), 10 )
-
- bwipe!
-endfunction
-
-func Test_Catch_Exception_Message()
- try
- py raise RuntimeError( 'TEST' )
- catch /.*/
- call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception )
- endtry
-endfunc
diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim
deleted file mode 100644
index 69f5f6dcc0..0000000000
--- a/src/nvim/testdir/test_python3.vim
+++ /dev/null
@@ -1,192 +0,0 @@
-" Test for python 3 commands.
-" TODO: move tests from test87.in here.
-
-source check.vim
-CheckFeature python3
-
-func Test_py3do()
- " Check deleting lines does not trigger an ml_get error.
- py3 import vim
- new
- call setline(1, ['one', 'two', 'three'])
- py3do vim.command("%d_")
- bwipe!
-
- " Disabled until neovim/neovim#8554 is resolved
- if 0
- " Check switching to another buffer does not trigger an ml_get error.
- new
- let wincount = winnr('$')
- call setline(1, ['one', 'two', 'three'])
- py3do vim.command("new")
- call assert_equal(wincount + 1, winnr('$'))
- bwipe!
- bwipe!
- endif
-endfunc
-
-func Test_set_cursor()
- " Check that setting the cursor position works.
- py3 import vim
- new
- call setline(1, ['first line', 'second line'])
- normal gg
- py3do vim.current.window.cursor = (1, 5)
- call assert_equal([1, 6], [line('.'), col('.')])
-
- " Check that movement after setting cursor position keeps current column.
- normal j
- call assert_equal([2, 6], [line('.'), col('.')])
-endfunc
-
-func Test_vim_function()
- throw 'skipped: Nvim does not support vim.bindeval()'
- " Check creating vim.Function object
- py3 import vim
-
- func s:foo()
- return matchstr(expand('<sfile>'), '<SNR>\zs\d\+_foo$')
- endfunc
- let name = '<SNR>' . s:foo()
-
- try
- py3 f = vim.bindeval('function("s:foo")')
- call assert_equal(name, py3eval('f.name'))
- catch
- call assert_false(v:exception)
- endtry
-
- try
- py3 f = vim.Function(b'\x80\xfdR' + vim.eval('s:foo()').encode())
- call assert_equal(name, 'f.name'->py3eval())
- catch
- call assert_false(v:exception)
- endtry
-
- py3 del f
- delfunc s:foo
-endfunc
-
-func Test_skipped_python3_command_does_not_affect_pyxversion()
- throw 'skipped: Nvim hardcodes pyxversion=3'
- set pyxversion=0
- if 0
- python3 import vim
- endif
- call assert_equal(0, &pyxversion) " This assertion would have failed with Vim 8.0.0251. (pyxversion was introduced in 8.0.0251.)
-endfunc
-
-func _SetUpHiddenBuffer()
- py3 import vim
- new
- edit hidden
- setlocal bufhidden=hide
-
- enew
- let lnum = 0
- while lnum < 10
- call append( 1, string( lnum ) )
- let lnum = lnum + 1
- endwhile
- normal G
-
- call assert_equal( line( '.' ), 11 )
-endfunc
-
-func _CleanUpHiddenBuffer()
- bwipe! hidden
- bwipe!
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Clear()
- call _SetUpHiddenBuffer()
- py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = None
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_List()
- call _SetUpHiddenBuffer()
- py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = [ 'test' ]
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Str()
- call _SetUpHiddenBuffer()
- py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = 'test'
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_ClearLine()
- call _SetUpHiddenBuffer()
- py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = None
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func _SetUpVisibleBuffer()
- py3 import vim
- new
- let lnum = 0
- while lnum < 10
- call append( 1, string( lnum ) )
- let lnum = lnum + 1
- endwhile
- normal G
- call assert_equal( line( '.' ), 11 )
-endfunc
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_Clear()
- call _SetUpVisibleBuffer()
-
- py3 vim.current.buffer[:] = None
- call assert_equal( line( '.' ), 1 )
-
- bwipe!
-endfunc
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_List()
- call _SetUpVisibleBuffer()
-
- py3 vim.current.buffer[:] = [ 'test' ]
- call assert_equal( line( '.' ), 1 )
-
- bwipe!
-endfunction
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_Str()
- call _SetUpVisibleBuffer()
-
- py3 vim.current.buffer[-1] = None
- call assert_equal( line( '.' ), 10 )
-
- 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
deleted file mode 100644
index eee825fa9b..0000000000
--- a/src/nvim/testdir/test_pyx2.vim
+++ /dev/null
@@ -1,81 +0,0 @@
-" Test for pyx* commands and functions with Python 2.
-
-source check.vim
-CheckFeature python
-set pyx=2
-
-let s:py2pattern = '^2\.[0-7]\.\d\+'
-let s:py3pattern = '^3\.\d\+\.\d\+'
-
-
-func Test_has_pythonx()
- call assert_true(has('pythonx'))
-endfunc
-
-
-func Test_pyx()
- redir => var
- pyx << EOF
-import sys
-print(sys.version)
-EOF
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-endfunc
-
-
-func Test_pyxdo()
- pyx import sys
- enew
- pyxdo return sys.version.split("\n")[0]
- call assert_match(s:py2pattern, split(getline('.'))[0])
-endfunc
-
-
-func Test_pyxeval()
- pyx import sys
- call assert_match(s:py2pattern, split('sys.version'->pyxeval())[0])
-endfunc
-
-
-func Test_pyxfile()
- " No special comments nor shebangs
- redir => var
- pyxfile pyxfile/pyx.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-
- " Python 2 special comment
- redir => var
- pyxfile pyxfile/py2_magic.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-
- " Python 2 shebang
- redir => var
- pyxfile pyxfile/py2_shebang.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-
- if has('python3')
- " Python 3 special comment
- redir => var
- pyxfile pyxfile/py3_magic.py
- redir END
- call assert_match(s:py3pattern, split(var)[0])
-
- " Python 3 shebang
- redir => var
- pyxfile pyxfile/py3_shebang.py
- redir END
- call assert_match(s:py3pattern, split(var)[0])
- endif
-endfunc
-
-func Test_Catch_Exception_Message()
- try
- pyx raise RuntimeError( 'TEST' )
- catch /.*/
- call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception )
- endtry
-endfunc
diff --git a/src/nvim/testdir/test_pyx3.vim b/src/nvim/testdir/test_pyx3.vim
deleted file mode 100644
index db39f5134a..0000000000
--- a/src/nvim/testdir/test_pyx3.vim
+++ /dev/null
@@ -1,81 +0,0 @@
-" Test for pyx* commands and functions with Python 3.
-
-set pyx=3
-source check.vim
-CheckFeature python3
-
-let s:py2pattern = '^2\.[0-7]\.\d\+'
-let s:py3pattern = '^3\.\d\+\.\d\+'
-
-
-func Test_has_pythonx()
- call assert_true(has('pythonx'))
-endfunc
-
-
-func Test_pyx()
- redir => var
- pyx << EOF
-import sys
-print(sys.version)
-EOF
- redir END
- call assert_match(s:py3pattern, split(var)[0])
-endfunc
-
-
-func Test_pyxdo()
- pyx import sys
- enew
- pyxdo return sys.version.split("\n")[0]
- call assert_match(s:py3pattern, split(getline('.'))[0])
-endfunc
-
-
-func Test_pyxeval()
- pyx import sys
- call assert_match(s:py3pattern, split(pyxeval('sys.version'))[0])
-endfunc
-
-
-func Test_pyxfile()
- " No special comments nor shebangs
- redir => var
- pyxfile pyxfile/pyx.py
- redir END
- call assert_match(s:py3pattern, split(var)[0])
-
- " Python 3 special comment
- redir => var
- pyxfile pyxfile/py3_magic.py
- redir END
- call assert_match(s:py3pattern, split(var)[0])
-
- " Python 3 shebang
- redir => var
- pyxfile pyxfile/py3_shebang.py
- redir END
- call assert_match(s:py3pattern, split(var)[0])
-
- if has('python')
- " Python 2 special comment
- redir => var
- pyxfile pyxfile/py2_magic.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-
- " Python 2 shebang
- redir => var
- pyxfile pyxfile/py2_shebang.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
- 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
deleted file mode 100644
index 8dc4173d60..0000000000
--- a/src/nvim/testdir/test_quickfix.vim
+++ /dev/null
@@ -1,6236 +0,0 @@
-" Test for the quickfix feature.
-
-source check.vim
-source vim9.vim
-CheckFeature quickfix
-
-source screendump.vim
-
-set encoding=utf-8
-
-func s:setup_commands(cchar)
- if a:cchar == 'c'
- command! -nargs=* -bang Xlist <mods>clist<bang> <args>
- command! -nargs=* Xgetexpr <mods>cgetexpr <args>
- command! -nargs=* Xaddexpr <mods>caddexpr <args>
- command! -nargs=* -count Xolder <mods><count>colder <args>
- command! -nargs=* Xnewer <mods>cnewer <args>
- command! -nargs=* Xopen <mods> copen <args>
- command! -nargs=* Xwindow <mods>cwindow <args>
- command! -nargs=* Xbottom <mods>cbottom <args>
- command! -nargs=* Xclose <mods>cclose <args>
- command! -nargs=* -bang Xfile <mods>cfile<bang> <args>
- command! -nargs=* Xgetfile <mods>cgetfile <args>
- command! -nargs=* Xaddfile <mods>caddfile <args>
- command! -nargs=* -bang Xbuffer <mods>cbuffer<bang> <args>
- command! -nargs=* Xgetbuffer <mods>cgetbuffer <args>
- command! -nargs=* Xaddbuffer <mods>caddbuffer <args>
- command! -nargs=* Xrewind <mods>crewind <args>
- command! -count -nargs=* -bang Xnext <mods><count>cnext<bang> <args>
- command! -count -nargs=* -bang Xprev <mods><count>cprev<bang> <args>
- command! -nargs=* -bang Xfirst <mods>cfirst<bang> <args>
- command! -nargs=* -bang Xlast <mods>clast<bang> <args>
- command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args>
- command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
- command! -nargs=* Xexpr <mods>cexpr <args>
- command! -count=999 -nargs=* Xvimgrep <mods> <count>vimgrep <args>
- command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args>
- command! -nargs=* Xgrep <mods> grep <args>
- command! -nargs=* Xgrepadd <mods> grepadd <args>
- 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
- command! -count=1 -nargs=0 Xbefore <mods><count>cbefore
- command! -count=1 -nargs=0 Xafter <mods><count>cafter
- let g:Xgetlist = function('getqflist')
- let g:Xsetlist = function('setqflist')
- call setqflist([], 'f')
- else
- command! -nargs=* -bang Xlist <mods>llist<bang> <args>
- command! -nargs=* Xgetexpr <mods>lgetexpr <args>
- command! -nargs=* Xaddexpr <mods>laddexpr <args>
- command! -nargs=* -count Xolder <mods><count>lolder <args>
- command! -nargs=* Xnewer <mods>lnewer <args>
- command! -nargs=* Xopen <mods> lopen <args>
- command! -nargs=* Xwindow <mods>lwindow <args>
- command! -nargs=* Xbottom <mods>lbottom <args>
- command! -nargs=* Xclose <mods>lclose <args>
- command! -nargs=* -bang Xfile <mods>lfile<bang> <args>
- command! -nargs=* Xgetfile <mods>lgetfile <args>
- command! -nargs=* Xaddfile <mods>laddfile <args>
- command! -nargs=* -bang Xbuffer <mods>lbuffer<bang> <args>
- command! -nargs=* Xgetbuffer <mods>lgetbuffer <args>
- command! -nargs=* Xaddbuffer <mods>laddbuffer <args>
- command! -nargs=* Xrewind <mods>lrewind <args>
- command! -count -nargs=* -bang Xnext <mods><count>lnext<bang> <args>
- command! -count -nargs=* -bang Xprev <mods><count>lprev<bang> <args>
- command! -nargs=* -bang Xfirst <mods>lfirst<bang> <args>
- command! -nargs=* -bang Xlast <mods>llast<bang> <args>
- command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args>
- command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
- command! -nargs=* Xexpr <mods>lexpr <args>
- command! -count=999 -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
- command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args>
- command! -nargs=* Xgrep <mods> lgrep <args>
- command! -nargs=* Xgrepadd <mods> lgrepadd <args>
- 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
- command! -count=1 -nargs=0 Xbefore <mods><count>lbefore
- command! -count=1 -nargs=0 Xafter <mods><count>lafter
- let g:Xgetlist = function('getloclist', [0])
- let g:Xsetlist = function('setloclist', [0])
- call setloclist(0, [], 'f')
- endif
-endfunc
-
-" This must be run before any error lists are created.
-func Test_AA_cc_no_errors()
- call assert_fails('cc', 'E42:')
- call assert_fails('ll', 'E42:')
-endfunc
-
-" Tests for the :clist and :llist commands
-func XlistTests(cchar)
- call s:setup_commands(a:cchar)
-
- if a:cchar == 'l'
- call assert_fails('llist', 'E776:')
- endif
- " With an empty list, command should return error
- Xgetexpr []
- silent! Xlist
- call assert_true(v:errmsg ==# 'E42: No Errors')
-
- " Populate the list and then try
- let lines =<< trim END
- non-error 1
- Xtestfile1:1:3:Line1
- non-error 2
- Xtestfile2:2:2:Line2
- non-error| 3
- Xtestfile3:3:1:Line3
- END
- Xgetexpr lines
-
- " List only valid entries
- let l = split(execute('Xlist', ''), "\n")
- call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
- \ ' 4 Xtestfile2:2 col 2: Line2',
- \ ' 6 Xtestfile3:3 col 1: Line3'], l)
-
- " List all the entries
- let l = split(execute('Xlist!', ''), "\n")
- call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1',
- \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2',
- \ ' 5: non-error| 3', ' 6 Xtestfile3:3 col 1: Line3'], l)
-
- " List a range of errors
- let l = split(execute('Xlist 3,6', ''), "\n")
- call assert_equal([' 4 Xtestfile2:2 col 2: Line2',
- \ ' 6 Xtestfile3:3 col 1: Line3'], l)
-
- let l = split(execute('Xlist! 3,4', ''), "\n")
- call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
-
- let l = split(execute('Xlist -6,-4', ''), "\n")
- call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l)
-
- let l = split(execute('Xlist! -5,-3', ''), "\n")
- call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
- \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
-
- " Test for '+'
- let l = split(execute('Xlist! +2', ''), "\n")
- call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
- \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
-
- " Ranged entries
- call g:Xsetlist([{'lnum':10,'text':'Line1'},
- \ {'lnum':20,'col':10,'text':'Line2'},
- \ {'lnum':30,'col':15,'end_col':20,'text':'Line3'},
- \ {'lnum':40,'end_lnum':45,'text':'Line4'},
- \ {'lnum':50,'end_lnum':55,'col':15,'text':'Line5'},
- \ {'lnum':60,'end_lnum':65,'col':25,'end_col':35,'text':'Line6'}])
- let l = split(execute('Xlist', ""), "\n")
- call assert_equal([' 1:10: Line1',
- \ ' 2:20 col 10: Line2',
- \ ' 3:30 col 15-20: Line3',
- \ ' 4:40-45: Line4',
- \ ' 5:50-55 col 15: Line5',
- \ ' 6:60-65 col 25-35: Line6'], l)
-
- " Different types of errors
- call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11},
- \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22},
- \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33},
- \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44},
- \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55}])
- let l = split(execute('Xlist', ""), "\n")
- call assert_equal([' 1:10 col 5 warning 11: Warning',
- \ ' 2:20 col 10 error 22: Error',
- \ ' 3:30 col 15 info 33: Info',
- \ ' 4:40 col 20 x 44: Other',
- \ ' 5:50 col 25 55: one'], l)
-
- " Test for module names, one needs to explicitly set `'valid':v:true` so
- call g:Xsetlist([
- \ {'lnum':10,'col':5,'type':'W','module':'Data.Text','text':'ModuleWarning','nr':11,'valid':v:true},
- \ {'lnum':20,'col':10,'type':'W','module':'Data.Text','filename':'Data/Text.hs','text':'ModuleWarning','nr':22,'valid':v:true},
- \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}])
- let l = split(execute('Xlist', ""), "\n")
- call assert_equal([' 1 Data.Text:10 col 5 warning 11: ModuleWarning',
- \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning',
- \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l)
-
- " Very long line should be displayed.
- let text = 'Line' .. repeat('1234567890', 130)
- let lines = ['Xtestfile9:2:9:' .. text]
- Xgetexpr lines
-
- let l = split(execute('Xlist', ''), "\n")
- call assert_equal([' 1 Xtestfile9:2 col 9: ' .. text] , l)
-
- " For help entries in the quickfix list, only the filename without directory
- " should be displayed
- Xhelpgrep setqflist()
- let l = split(execute('Xlist 1', ''), "\n")
- call assert_match('^ 1 [^\\/]\{-}:', l[0])
-
- " Error cases
- call assert_fails('Xlist abc', 'E488:')
-endfunc
-
-func Test_clist()
- call XlistTests('c')
- call XlistTests('l')
-endfunc
-
-" Tests for the :colder, :cnewer, :lolder and :lnewer commands
-" Note that this test assumes that a quickfix/location list is
-" already set by the caller.
-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)
-
- " Jumping to a non existent list should return error
- silent! Xolder 99
- call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack')
-
- silent! Xnewer 99
- call assert_true(v:errmsg ==# 'E381: At top of quickfix stack')
-
- " Add three quickfix/location lists
- Xgetexpr ['Xtestfile1:1:3:Line1']
- Xgetexpr ['Xtestfile2:2:2:Line2']
- Xgetexpr ['Xtestfile3:3:1:Line3']
-
- " Go back two lists
- Xolder
- let l = g:Xgetlist()
- call assert_equal('Line2', l[0].text)
-
- " Go forward two lists
- Xnewer
- let l = g:Xgetlist()
- call assert_equal('Line3', l[0].text)
-
- " Test for the optional count argument
- Xolder 2
- let l = g:Xgetlist()
- call assert_equal('Line1', l[0].text)
-
- Xnewer 2
- let l = g:Xgetlist()
- call assert_equal('Line3', l[0].text)
-endfunc
-
-func Test_cage()
- call XageTests('c')
- call XageTests('l')
-endfunc
-
-" Tests for the :cwindow, :lwindow :cclose, :lclose, :copen and :lopen
-" commands
-func XwindowTests(cchar)
- call s:setup_commands(a:cchar)
-
- " Opening the location list window without any errors should fail
- if a:cchar == 'l'
- call assert_fails('lopen', 'E776:')
- call assert_fails('lwindow', 'E776:')
- endif
-
- " Create a list with no valid entries
- Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3']
-
- " Quickfix/Location window should not open with no valid errors
- Xwindow
- call assert_true(winnr('$') == 1)
-
- " Create a list with valid entries
- let lines =<< trim END
- Xtestfile1:1:3:Line1
- Xtestfile2:2:2:Line2
- Xtestfile3:3:1:Line3
- END
- Xgetexpr lines
-
- " Open the window
- Xwindow
- call assert_true(winnr('$') == 2 && winnr() == 2 &&
- \ getline('.') ==# 'Xtestfile1|1 col 3| Line1')
- redraw!
-
- " Close the window
- Xclose
- call assert_true(winnr('$') == 1)
-
- " Create a list with no valid entries
- Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3']
-
- " Open the window
- Xopen 5
- call assert_true(winnr('$') == 2 && getline('.') ==# '|| non-error 1'
- \ && winheight(0) == 5)
-
- " Opening the window again, should move the cursor to that window
- wincmd t
- Xopen 7
- call assert_true(winnr('$') == 2 && winnr() == 2 &&
- \ winheight(0) == 7 &&
- \ getline('.') ==# '|| non-error 1')
-
- " :cnext in quickfix window should move to the next entry
- Xnext
- call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
-
- " Calling cwindow should close the quickfix window with no valid errors
- Xwindow
- call assert_true(winnr('$') == 1)
-
- " Specifying the width should adjust the width for a vertically split
- " quickfix window.
- vert Xopen
- call assert_equal(10, winwidth(0))
- vert Xopen 12
- call assert_equal(12, winwidth(0))
- Xclose
-
- " Horizontally or vertically splitting the quickfix window should create a
- " normal window/buffer
- Xopen
- wincmd s
- call assert_equal(0, getwininfo(win_getid())[0].quickfix)
- call assert_equal(0, getwininfo(win_getid())[0].loclist)
- call assert_notequal('quickfix', &buftype)
- close
- Xopen
- wincmd v
- call assert_equal(0, getwininfo(win_getid())[0].quickfix)
- call assert_equal(0, getwininfo(win_getid())[0].loclist)
- call assert_notequal('quickfix', &buftype)
- close
- Xopen
- Xclose
-
- if a:cchar == 'c'
- " Opening the quickfix window in multiple tab pages should reuse the
- " quickfix buffer
- let lines =<< trim END
- Xtestfile1:1:3:Line1
- Xtestfile2:2:2:Line2
- Xtestfile3:3:1:Line3
- END
- Xgetexpr lines
- Xopen
- let qfbufnum = bufnr('%')
- tabnew
- Xopen
- call assert_equal(qfbufnum, bufnr('%'))
- new | only | tabonly
- endif
-endfunc
-
-func Test_cwindow()
- call XwindowTests('c')
- 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)
- call s:setup_commands(a:cchar)
-
- let lines =<< trim END
- Xtestfile1:700:10:Line 700
- Xtestfile2:800:15:Line 800
- END
- call writefile(lines, 'Xqftestfile1')
-
- enew!
- Xfile Xqftestfile1
- let l = g:Xgetlist()
- call assert_true(len(l) == 2 &&
- \ l[0].lnum == 700 && l[0].col == 10 && l[0].text ==# 'Line 700' &&
- \ l[1].lnum == 800 && l[1].col == 15 && l[1].text ==# 'Line 800')
-
- " Test with a non existent file
- call assert_fails('Xfile non_existent_file', 'E40')
-
- " Run cfile/lfile from a modified buffer
- enew!
- silent! put ='Quickfix'
- silent! Xfile Xqftestfile1
- call assert_true(v:errmsg ==# 'E37: No write since last change (add ! to override)')
-
- call writefile(['Xtestfile3:900:30:Line 900'], 'Xqftestfile1')
- Xaddfile Xqftestfile1
- let l = g:Xgetlist()
- call assert_true(len(l) == 3 &&
- \ l[2].lnum == 900 && l[2].col == 30 && l[2].text ==# 'Line 900')
-
- let lines =<< trim END
- Xtestfile1:222:77:Line 222
- Xtestfile2:333:88:Line 333
- END
- call writefile(lines, 'Xqftestfile1')
-
- enew!
- Xgetfile Xqftestfile1
- let l = g:Xgetlist()
- call assert_true(len(l) == 2 &&
- \ l[0].lnum == 222 && l[0].col == 77 && l[0].text ==# 'Line 222' &&
- \ l[1].lnum == 333 && l[1].col == 88 && l[1].text ==# 'Line 333')
-
- " Test for a file with a long line and without a newline at the end
- let text = repeat('x', 1024)
- let t = 'a.txt:18:' . text
- call writefile([t], 'Xqftestfile1', 'b')
- silent! Xfile Xqftestfile1
- call assert_equal(text, g:Xgetlist()[0].text)
-
- call delete('Xqftestfile1')
-endfunc
-
-func Test_cfile()
- call XfileTests('c')
- call XfileTests('l')
-endfunc
-
-" Tests for the :cbuffer, :lbuffer, :caddbuffer, :laddbuffer, :cgetbuffer and
-" :lgetbuffer commands.
-func XbufferTests(cchar)
- call s:setup_commands(a:cchar)
-
- enew!
- let lines =<< trim END
- Xtestfile7:700:10:Line 700
- Xtestfile8:800:15:Line 800
- END
- silent! call setline(1, lines)
- Xbuffer!
- let l = g:Xgetlist()
- call assert_true(len(l) == 2 &&
- \ l[0].lnum == 700 && l[0].col == 10 && l[0].text ==# 'Line 700' &&
- \ l[1].lnum == 800 && l[1].col == 15 && l[1].text ==# 'Line 800')
-
- enew!
- let lines =<< trim END
- Xtestfile9:900:55:Line 900
- Xtestfile10:950:66:Line 950
- END
- silent! call setline(1, lines)
- Xgetbuffer
- let l = g:Xgetlist()
- call assert_true(len(l) == 2 &&
- \ l[0].lnum == 900 && l[0].col == 55 && l[0].text ==# 'Line 900' &&
- \ l[1].lnum == 950 && l[1].col == 66 && l[1].text ==# 'Line 950')
-
- enew!
- let lines =<< trim END
- Xtestfile11:700:20:Line 700
- Xtestfile12:750:25:Line 750
- END
- silent! call setline(1, lines)
- Xaddbuffer
- let l = g:Xgetlist()
- call assert_true(len(l) == 4 &&
- \ l[1].lnum == 950 && l[1].col == 66 && l[1].text ==# 'Line 950' &&
- \ l[2].lnum == 700 && l[2].col == 20 && l[2].text ==# 'Line 700' &&
- \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750')
- enew!
-
- " Check for invalid buffer
- call assert_fails('Xbuffer 199', 'E474:')
-
- " Check for unloaded buffer
- edit Xtestfile1
- let bnr = bufnr('%')
- enew!
- call assert_fails('Xbuffer ' . bnr, 'E681:')
-
- " Check for invalid range
- " Using Xbuffer will not run the range check in the cbuffer/lbuffer
- " commands. So directly call the commands.
- if (a:cchar == 'c')
- call assert_fails('900,999cbuffer', 'E16:')
- else
- call assert_fails('900,999lbuffer', 'E16:')
- endif
-endfunc
-
-func Test_cbuffer()
- call XbufferTests('c')
- call XbufferTests('l')
-endfunc
-
-func XexprTests(cchar)
- call s:setup_commands(a:cchar)
-
- call assert_fails('Xexpr 10', 'E777:')
-endfunc
-
-func Test_cexpr()
- call XexprTests('c')
- call XexprTests('l')
-endfunc
-
-" Tests for :cnext, :cprev, :cfirst, :clast commands
-func Xtest_browse(cchar)
- call s:setup_commands(a:cchar)
-
- call g:Xsetlist([], 'f')
- " Jumping to first or next location list entry without any error should
- " result in failure
- if a:cchar == 'c'
- let err = 'E42:'
- let cmd = '$cc'
- else
- let err = 'E776:'
- let cmd = '$ll'
- endif
- call assert_fails('Xnext', err)
- call assert_fails('Xprev', err)
- call assert_fails('Xnfile', err)
- call assert_fails('Xpfile', err)
- call assert_fails(cmd, err)
-
- Xexpr ''
- call assert_fails(cmd, 'E42:')
-
- call s:create_test_file('Xqftestfile1')
- call s:create_test_file('Xqftestfile2')
-
- let lines =<< trim END
- Xqftestfile1:5:Line5
- Xqftestfile1:6:Line6
- Xqftestfile2:10:Line10
- Xqftestfile2:11:Line11
- RegularLine1
- RegularLine2
- END
- Xgetexpr lines
-
- Xfirst
- call assert_fails('-5Xcc', 'E16:')
- call assert_fails('Xprev', 'E553')
- call assert_fails('Xpfile', 'E553')
- Xnfile
- call assert_equal('Xqftestfile2', @%)
- call assert_equal(10, line('.'))
- Xpfile
- call assert_equal('Xqftestfile1', @%)
- call assert_equal(6, line('.'))
- 5Xcc
- call assert_equal(5, g:Xgetlist({'idx':0}).idx)
- 2Xcc
- call assert_equal(2, g:Xgetlist({'idx':0}).idx)
- if a:cchar == 'c'
- cc
- else
- ll
- endif
- call assert_equal(2, g:Xgetlist({'idx':0}).idx)
- 10Xcc
- call assert_equal(6, g:Xgetlist({'idx':0}).idx)
- Xlast
- Xprev
- call assert_equal('Xqftestfile2', @%)
- call assert_equal(11, line('.'))
- call assert_fails('Xnext', 'E553')
- call assert_fails('Xnfile', 'E553')
- " To process the range using quickfix list entries, directly use the
- " quickfix commands (don't use the user defined commands)
- if a:cchar == 'c'
- $cc
- else
- $ll
- endif
- call assert_equal(6, g:Xgetlist({'idx':0}).idx)
- Xrewind
- call assert_equal('Xqftestfile1', @%)
- call assert_equal(5, line('.'))
-
- 10Xnext
- call assert_equal('Xqftestfile2', @%)
- call assert_equal(11, line('.'))
- 10Xprev
- call assert_equal('Xqftestfile1', @%)
- call assert_equal(5, line('.'))
-
- " Jumping to an error from the error window using cc command
- let lines =<< trim END
- Xqftestfile1:5:Line5
- Xqftestfile1:6:Line6
- Xqftestfile2:10:Line10
- Xqftestfile2:11:Line11
- END
- Xgetexpr lines
- Xopen
- 10Xcc
- call assert_equal(11, line('.'))
- call assert_equal('Xqftestfile2', @%)
- Xopen
- call cursor(2, 1)
- if a:cchar == 'c'
- .cc
- else
- .ll
- endif
- call assert_equal(6, line('.'))
- call assert_equal('Xqftestfile1', @%)
-
- " Jumping to an error from the error window (when only the error window is
- " present)
- Xopen | only
- Xlast 1
- call assert_equal(5, line('.'))
- call assert_equal('Xqftestfile1', @%)
-
- Xexpr ""
- call assert_fails('Xnext', 'E42:')
-
- call delete('Xqftestfile1')
- call delete('Xqftestfile2')
-
- " Should be able to use next/prev with invalid entries
- Xexpr ""
- call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
- call assert_equal(0, g:Xgetlist({'size' : 0}).size)
- Xaddexpr ['foo', 'bar', 'baz', 'quux', 'sh|moo']
- call assert_equal(5, g:Xgetlist({'size' : 0}).size)
- Xlast
- call assert_equal(5, g:Xgetlist({'idx' : 0}).idx)
- Xfirst
- call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
- 2Xnext
- call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
-endfunc
-
-func Test_browse()
- call Xtest_browse('c')
- call Xtest_browse('l')
-endfunc
-
-func s:test_xhelpgrep(cchar)
- call s:setup_commands(a:cchar)
- Xhelpgrep quickfix
- Xopen
- if a:cchar == 'c'
- let title_text = ':helpgrep quickfix'
- else
- let title_text = ':lhelpgrep quickfix'
- endif
- call assert_true(w:quickfix_title =~ title_text, w:quickfix_title)
-
- " Jumping to a help topic should open the help window
- only
- Xnext
- call assert_true(&buftype == 'help')
- call assert_true(winnr('$') == 2)
- " Jumping to the next match should reuse the help window
- Xnext
- call assert_true(&buftype == 'help')
- call assert_true(winnr() == 1)
- call assert_true(winnr('$') == 2)
- " Jumping to the next match from the quickfix window should reuse the help
- " window
- Xopen
- Xnext
- call assert_true(&buftype == 'help')
- call assert_true(winnr() == 1)
- call assert_true(winnr('$') == 2)
- call assert_match('|\d\+ col \d\+-\d\+|', getbufline(winbufnr(2), 1)[0])
-
- " 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
- new | only
- call g:Xsetlist([], 'f')
- help index.txt
- wincmd w
- lhelpgrep quickfix
- call assert_equal(1, winnr())
- call assert_notequal([], getloclist(1))
- call assert_equal([], getloclist(2))
- endif
-
- new | only
-
- " Search for non existing help string
- call assert_fails('Xhelpgrep a1b2c3', 'E480:')
- " Invalid regular expression
- call assert_fails('Xhelpgrep \@<!', 'E866:')
-endfunc
-
-func Test_helpgrep()
- call s:test_xhelpgrep('c')
- helpclose
- call s:test_xhelpgrep('l')
-endfunc
-
-" When running the :helpgrep command, if an autocmd modifies the 'cpoptions'
-" value, then Vim crashes. (issue fixed by 7.2b-004 and 8.2.4453)
-func Test_helpgrep_restore_cpo_aucmd()
- let save_cpo = &cpo
- augroup QF_Test
- au!
- autocmd BufNew * set cpo=acd
- augroup END
-
- helpgrep quickfix
- call assert_equal('acd', &cpo)
- %bw!
-
- set cpo&vim
- augroup QF_Test
- au!
- autocmd BufReadPost * set cpo=
- augroup END
-
- helpgrep buffer
- call assert_equal('', &cpo)
-
- augroup QF_Test
- au!
- augroup END
- %bw!
- let &cpo = save_cpo
-endfunc
-
-func Test_errortitle()
- augroup QfBufWinEnter
- au!
- au BufWinEnter * :let g:a=get(w:, 'quickfix_title', 'NONE')
- augroup END
- copen
- let a=[{'lnum': 308, 'bufnr': bufnr(''), 'col': 58, 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', 'pattern': '', 'text': ' au BufWinEnter * :let g:a=get(w:, ''quickfix_title'', ''NONE'')'}]
- call setqflist(a)
- call assert_equal(':setqflist()', g:a)
- augroup QfBufWinEnter
- au!
- augroup END
- augroup! QfBufWinEnter
-endfunc
-
-func Test_vimgreptitle()
- augroup QfBufWinEnter
- au!
- au BufWinEnter * :let g:a=get(w:, 'quickfix_title', 'NONE')
- augroup END
- try
- vimgrep /pattern/j file
- catch /E480/
- endtry
- copen
- call assert_equal(': vimgrep /pattern/j file', g:a)
- augroup QfBufWinEnter
- au!
- augroup END
- augroup! QfBufWinEnter
-endfunc
-
-func Test_bufwinenter_once()
- augroup QfBufWinEnter
- au!
- au BufWinEnter * let g:got_afile ..= 'got ' .. expand('<afile>')
- augroup END
- let g:got_afile = ''
- copen
- call assert_equal('got quickfix', g:got_afile)
-
- cclose
- unlet g:got_afile
- augroup QfBufWinEnter
- au!
- augroup END
- augroup! QfBufWinEnter
-endfunc
-
-func XqfTitleTests(cchar)
- call s:setup_commands(a:cchar)
-
- Xgetexpr ['file:1:1:message']
- let l = g:Xgetlist()
- if a:cchar == 'c'
- call setqflist(l, 'r')
- else
- call setloclist(0, l, 'r')
- endif
-
- Xopen
- if a:cchar == 'c'
- let title = ':setqflist()'
- else
- let title = ':setloclist()'
- endif
- call assert_equal(title, w:quickfix_title)
- Xclose
-endfunc
-
-" Tests for quickfix window's title
-func Test_qf_title()
- call XqfTitleTests('c')
- call XqfTitleTests('l')
-endfunc
-
-" Tests for 'errorformat'
-func Test_efm()
- let save_efm = &efm
- set efm=%EEEE%m,%WWWW%m,%+CCCC%.%#,%-GGGG%.%#
- cgetexpr ['WWWW', 'EEEE', 'CCCC']
- let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))
- call assert_equal("[['W', 1], ['E^@CCCC', 1]]", l)
- cgetexpr ['WWWW', 'GGGG', 'EEEE', 'CCCC']
- let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))
- call assert_equal("[['W', 1], ['E^@CCCC', 1]]", l)
- cgetexpr ['WWWW', 'GGGG', 'ZZZZ', 'EEEE', 'CCCC', 'YYYY']
- let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))
- call assert_equal("[['W', 1], ['ZZZZ', 0], ['E^@CCCC', 1], ['YYYY', 0]]", l)
- let &efm = save_efm
-endfunc
-
-" This will test for problems in quickfix:
-" A. incorrectly copying location lists which caused the location list to show
-" a different name than the file that was actually being displayed.
-" B. not reusing the window for which the location list window is opened but
-" instead creating new windows.
-" C. make sure that the location list window is not reused instead of the
-" window it belongs to.
-"
-" Set up the test environment:
-func ReadTestProtocol(name)
- let base = substitute(a:name, '\v^test://(.*)%(\.[^.]+)?', '\1', '')
- let word = substitute(base, '\v(.*)\..*', '\1', '')
-
- setl modifiable
- setl noreadonly
- setl noswapfile
- setl bufhidden=delete
- %del _
- " For problem 2:
- " 'buftype' has to be set to reproduce the constant opening of new windows
- setl buftype=nofile
-
- call setline(1, word)
-
- setl nomodified
- setl nomodifiable
- setl readonly
- exe 'doautocmd BufRead ' . substitute(a:name, '\v^test://(.*)', '\1', '')
-endfunc
-
-func Test_locationlist()
- enew
-
- augroup testgroup
- au!
- autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>"))
- augroup END
-
- let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ]
-
- let qflist = []
- for word in words
- call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', })
- " NOTE: problem 1:
- " intentionally not setting 'lnum' so that the quickfix entries are not
- " valid
- eval qflist->setloclist(0, ' ')
- endfor
-
- " Test A
- lrewind
- enew
- lopen
- 4lnext
- vert split
- wincmd L
- lopen
- wincmd p
- lnext
- let fileName = expand("%")
- wincmd p
- let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '')
- let fileName = substitute(fileName, '\\', '/', 'g')
- let locationListFileName = substitute(locationListFileName, '\\', '/', 'g')
- call assert_equal("test://bar.txt", fileName)
- call assert_equal("test://bar.txt", locationListFileName)
-
- wincmd n | only
-
- " Test B:
- lrewind
- lopen
- 2
- exe "normal \<CR>"
- wincmd p
- 3
- exe "normal \<CR>"
- wincmd p
- 4
- exe "normal \<CR>"
- call assert_equal(2, winnr('$'))
- wincmd n | only
-
- " Test C:
- lrewind
- lopen
- " Let's move the location list window to the top to check whether it (the
- " first window found) will be reused when we try to open new windows:
- wincmd K
- 2
- exe "normal \<CR>"
- wincmd p
- 3
- exe "normal \<CR>"
- wincmd p
- 4
- exe "normal \<CR>"
- 1wincmd w
- call assert_equal('quickfix', &buftype)
- 2wincmd w
- let bufferName = expand("%")
- let bufferName = substitute(bufferName, '\\', '/', 'g')
- call assert_equal('test://quux.txt', bufferName)
-
- wincmd n | only
-
- augroup! testgroup
-endfunc
-
-func Test_locationlist_curwin_was_closed()
- augroup testgroup
- au!
- autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>"))
- augroup END
-
- func! R(n)
- quit
- endfunc
-
- new
- let q = []
- call add(q, {'filename': 'test_curwin.txt' })
- call setloclist(0, q)
- call assert_fails('lrewind', 'E924:')
-
- augroup! testgroup
- delfunc R
-endfunc
-
-func Test_locationlist_cross_tab_jump()
- call writefile(['loclistfoo'], 'loclistfoo')
- call writefile(['loclistbar'], 'loclistbar')
- set switchbuf=usetab
-
- edit loclistfoo
- tabedit loclistbar
- silent lgrep loclistfoo loclist*
- call assert_equal(1, tabpagenr())
-
- enew | only | tabonly
- set switchbuf&vim
- call delete('loclistfoo')
- call delete('loclistbar')
-endfunc
-
-" More tests for 'errorformat'
-func Test_efm1()
- if !has('unix')
- " The 'errorformat' setting is different on non-Unix systems.
- " This test works only on Unix-like systems.
- return
- endif
-
- 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 =<< [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
- set efm+==%f=\\,\ line\ %l%*\\D%v%*[^\ ]\ %m
- set efm^=%AError\ in\ \"%f\"\ at\ line\ %l:,%Z%p^,%C%m
-
- exe 'cf Xerrorfile2'
- clast
- copen
- call assert_equal(':cf Xerrorfile2', w:quickfix_title)
- wincmd p
-
- exe 'cf Xerrorfile1'
- call assert_equal([4, 12], [line('.'), col('.')])
- cn
- call assert_equal([6, 19], [line('.'), col('.')])
- cn
- call assert_equal([9, 2], [line('.'), col('.')])
- cn
- call assert_equal([10, 2], [line('.'), col('.')])
- cn
- call assert_equal([11, 1], [line('.'), col('.')])
- cn
- call assert_equal([12, 2], [line('.'), col('.')])
- cn
- call assert_equal([14, 10], [line('.'), col('.')])
- cn
- call assert_equal([15, 3, 10], [line('.'), col('.'), virtcol('.')])
- cn
- call assert_equal([16, 2], [line('.'), col('.')])
- cn
- call assert_equal([17, 6], [line('.'), col('.')])
- cn
- call assert_equal([18, 7], [line('.'), col('.')])
- cn
- call assert_equal([19, 8], [line('.'), col('.')])
- cn
- call assert_equal([20, 9], [line('.'), col('.')])
- clast
- cprev
- cprev
- wincmd w
- call assert_equal(':cf Xerrorfile1', w:quickfix_title)
- wincmd p
-
- let &efm = save_efm
- call delete('Xerrorfile1')
- call delete('Xerrorfile2')
- call delete('Xtestfile')
-endfunc
-
-" Test for quickfix directory stack support
-func s:dir_stack_tests(cchar)
- call s:setup_commands(a:cchar)
-
- let save_efm=&efm
- set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
-
- let lines =<< trim END
- 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'
- END
-
- Xexpr ""
- for l in lines
- Xaddexpr l
- endfor
-
- let qf = g:Xgetlist()
-
- call assert_equal('dir1/a/habits2.txt', bufname(qf[1].bufnr))
- call assert_equal(1, qf[1].lnum)
- call assert_equal('dir1/a/b/habits3.txt', bufname(qf[3].bufnr))
- call assert_equal(2, qf[3].lnum)
- call assert_equal('dir1/a/habits2.txt', bufname(qf[4].bufnr))
- call assert_equal(7, qf[4].lnum)
- call assert_equal('dir1/c/habits4.txt', bufname(qf[6].bufnr))
- call assert_equal(3, qf[6].lnum)
- call assert_equal('habits1.txt', bufname(qf[9].bufnr))
- call assert_equal(4, qf[9].lnum)
- call assert_equal('dir2/habits5.txt', bufname(qf[11].bufnr))
- call assert_equal(5, qf[11].lnum)
-
- let &efm=save_efm
-endfunc
-
-" Tests for %D and %X errorformat options
-func Test_efm_dirstack()
- " Create the directory stack and files
- call mkdir('dir1')
- call mkdir('dir1/a')
- call mkdir('dir1/a/b')
- call mkdir('dir1/c')
- call mkdir('dir2')
-
- let lines =<< trim END
- 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
- END
- call writefile(lines, 'habits1.txt')
- call writefile(lines, 'dir1/a/habits2.txt')
- call writefile(lines, 'dir1/a/b/habits3.txt')
- call writefile(lines, 'dir1/c/habits4.txt')
- call writefile(lines, 'dir2/habits5.txt')
-
- call s:dir_stack_tests('c')
- call s:dir_stack_tests('l')
-
- call delete('dir1', 'rf')
- call delete('dir2', 'rf')
- call delete('habits1.txt')
-endfunc
-
-" Test for resync after continuing an ignored message
-func Xefm_ignore_continuations(cchar)
- call s:setup_commands(a:cchar)
-
- let save_efm = &efm
-
- let &efm =
- \ '%Eerror %m %l,' .
- \ '%-Wignored %m %l,' .
- \ '%+Cmore ignored %m %l,' .
- \ '%Zignored end'
- let lines =<< trim END
- ignored warning 1
- more ignored continuation 2
- ignored end
- error resync 4
- END
- Xgetexpr lines
- let l = map(g:Xgetlist(), '[v:val.text, v:val.valid, v:val.lnum, v:val.type]')
- call assert_equal([['resync', 1, 4, 'E']], l)
-
- let &efm = save_efm
-endfunc
-
-func Test_efm_ignore_continuations()
- call Xefm_ignore_continuations('c')
- call Xefm_ignore_continuations('l')
-endfunc
-
-" Tests for invalid error format specifies
-func Xinvalid_efm_Tests(cchar)
- call s:setup_commands(a:cchar)
-
- let save_efm = &efm
-
- set efm=%f:%l:%m,%f:%f:%l:%m
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E372:')
-
- set efm=%f:%l:%m,%f:%l:%r:%m
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E373:')
-
- set efm=%f:%l:%m,%O:%f:%l:%m
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E373:')
-
- set efm=%f:%l:%m,%f:%l:%*[^a-z
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E374:')
-
- set efm=%f:%l:%m,%f:%l:%*c
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E375:')
-
- set efm=%f:%l:%m,%L%M%N
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E376:')
-
- set efm=%f:%l:%m,%f:%l:%m:%R
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E377:')
-
- " Invalid regular expression
- set efm=%\\%%k
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E867:')
-
- set efm=
- call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E378:')
-
- " Empty directory name. When there is an error in parsing new entries, make
- " sure the previous quickfix list is made the current list.
- set efm&
- cexpr ["one", "two"]
- let qf_id = getqflist(#{id: 0}).id
- set efm=%DEntering\ dir\ abc,%f:%l:%m
- call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E379:')
- call assert_equal(qf_id, getqflist(#{id: 0}).id)
-
- let &efm = save_efm
-endfunc
-
-func Test_invalid_efm()
- call Xinvalid_efm_Tests('c')
- call Xinvalid_efm_Tests('l')
-endfunc
-
-" TODO:
-" Add tests for the following formats in 'errorformat'
-" %r %O
-func Test_efm2()
- let save_efm = &efm
-
- " Test for %s format in efm
- set efm=%f:%s
- cexpr 'Xtestfile:Line search text'
- let l = getqflist()
- 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 a long line
- cexpr 'Xtestfile:' . repeat('a', 1026)
- let l = getqflist()
- call assert_equal('^\V' . repeat('a', 1019) . '\$', l[0].pattern)
-
- " Test for %P, %Q and %t format specifiers
- 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.
- call writefile(['Line1'], 'Xtestfile1')
- call writefile(['Line2'], 'Xtestfile2')
- call writefile(['Line3'], 'Xtestfile3')
- cexpr ""
- for l in lines
- caddexpr l
- endfor
- let l = getqflist()
- call assert_equal(12, len(l))
- call assert_equal(21, l[2].lnum)
- call assert_equal(2, l[2].col)
- call assert_equal('w', l[2].type)
- call assert_equal('e', l[3].type)
- call delete('Xtestfile1')
- call delete('Xtestfile2')
- call delete('Xtestfile3')
-
- " Test for %P, %Q with non-existing files
- cexpr lines
- let l = getqflist()
- call assert_equal(14, len(l))
- call assert_equal('[Xtestfile1]', l[0].text)
- call assert_equal('[Xtestfile2]', l[6].text)
- call assert_equal('[Xtestfile3]', l[9].text)
-
- " Tests for %E, %C and %Z format specifiers
- 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()
- call assert_equal(275, l[0].nr)
- call assert_equal(42, l[0].lnum)
- call assert_equal(3, l[0].col)
- call assert_equal('E', l[0].type)
- call assert_equal("\n' ' expected after '--'", l[0].text)
-
- " Test for %>
- 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()
- call assert_equal(147, l[0].lnum)
- call assert_equal('E', l[0].type)
- call assert_equal("\nunknown variable 'i'", l[0].text)
-
- " Test for %A, %C and other formats
- 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, \\
- W:AssertionError: 34 != 33
-
- --------------------------------------------------------------
- Ran 27 tests in 0.063s
- [DATA]
-
- set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%t:%m
- cgetexpr lines
- let l = getqflist()
- call assert_equal(8, len(l))
- call assert_equal(89, l[4].lnum)
- call assert_equal(1, l[4].valid)
- call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr))
- call assert_equal('W', l[4].type)
-
- " Test for %o
- set efm=%f(%o):%l\ %m
- cgetexpr ['Xotestfile(Language.PureScript.Types):20 Error']
- call writefile(['Line1'], 'Xotestfile')
- let l = getqflist()
- call assert_equal(1, len(l), string(l))
- call assert_equal('Language.PureScript.Types', l[0].module)
- copen
- call assert_equal('Language.PureScript.Types|20| Error', getline(1))
- call feedkeys("\<CR>", 'xn')
- call assert_equal('Xotestfile', expand('%:t'))
- cclose
- bd
- call delete("Xotestfile")
-
- " Test for a long module name
- cexpr 'Xtest(' . repeat('m', 1026) . '):15 message'
- let l = getqflist()
- " call assert_equal(repeat('m', 1024), l[0].module)
- call assert_equal(repeat('m', 1023), l[0].module)
- call assert_equal(15, l[0].lnum)
- call assert_equal('message', l[0].text)
-
- " The following sequence of commands used to crash Vim
- set efm=%W%m
- cgetexpr ['msg1']
- let l = getqflist()
- call assert_equal(1, len(l), string(l))
- call assert_equal('msg1', l[0].text)
- set efm=%C%m
- lexpr 'msg2'
- let l = getloclist(0)
- call assert_equal(1, len(l), string(l))
- call assert_equal('msg2', l[0].text)
- lopen
- call setqflist([], 'r')
- caddbuf
- let l = getqflist()
- call assert_equal(1, len(l), string(l))
- call assert_equal('|| msg2', l[0].text)
-
- " When matching error lines, case should be ignored. Test for this.
- set noignorecase
- let l=getqflist({'lines' : ['Xtest:FOO10:Line 20'], 'efm':'%f:foo%l:%m'})
- call assert_equal(10, l.items[0].lnum)
- call assert_equal('Line 20', l.items[0].text)
- set ignorecase&
-
- new | only
- let &efm = save_efm
-endfunc
-
-" Test for '%t' (error type) field in 'efm'
-func Test_efm_error_type()
- let save_efm = &efm
-
- " error type
- set efm=%f:%l:%t:%m
- let lines =<< trim END
- Xfile1:10:E:msg1
- Xfile1:20:W:msg2
- Xfile1:30:I:msg3
- Xfile1:40:N:msg4
- Xfile1:50:R:msg5
- END
- cexpr lines
- let output = split(execute('clist'), "\n")
- call assert_equal([
- \ ' 1 Xfile1:10 error: msg1',
- \ ' 2 Xfile1:20 warning: msg2',
- \ ' 3 Xfile1:30 info: msg3',
- \ ' 4 Xfile1:40 note: msg4',
- \ ' 5 Xfile1:50 R: msg5'], output)
-
- " error type and a error number
- set efm=%f:%l:%t:%n:%m
- let lines =<< trim END
- Xfile1:10:E:2:msg1
- Xfile1:20:W:4:msg2
- Xfile1:30:I:6:msg3
- Xfile1:40:N:8:msg4
- Xfile1:50:R:3:msg5
- END
- cexpr lines
- let output = split(execute('clist'), "\n")
- call assert_equal([
- \ ' 1 Xfile1:10 error 2: msg1',
- \ ' 2 Xfile1:20 warning 4: msg2',
- \ ' 3 Xfile1:30 info 6: msg3',
- \ ' 4 Xfile1:40 note 8: msg4',
- \ ' 5 Xfile1:50 R 3: msg5'], output)
- let &efm = save_efm
-endfunc
-
-" Test for end_lnum ('%e') and end_col ('%k') fields in 'efm'
-func Test_efm_end_lnum_col()
- let save_efm = &efm
-
- " single line
- set efm=%f:%l-%e:%c-%k:%t:%m
- cexpr ["Xfile1:10-20:1-2:E:msg1", "Xfile1:20-30:2-3:W:msg2",]
- let output = split(execute('clist'), "\n")
- call assert_equal([
- \ ' 1 Xfile1:10-20 col 1-2 error: msg1',
- \ ' 2 Xfile1:20-30 col 2-3 warning: msg2'], output)
-
- " multiple lines
- set efm=%A%n)%m,%Z%f:%l-%e:%c-%k
- let lines =<< trim END
- 1)msg1
- Xfile1:14-24:1-2
- 2)msg2
- Xfile1:24-34:3-4
- END
- cexpr lines
- let output = split(execute('clist'), "\n")
- call assert_equal([
- \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1',
- \ ' 2 Xfile1:24-34 col 3-4 error 2: msg2'], output)
- let &efm = save_efm
-endfunc
-
-func XquickfixChangedByAutocmd(cchar)
- call s:setup_commands(a:cchar)
- if a:cchar == 'c'
- let ErrorNr = 'E925'
- func! ReadFunc()
- colder
- cgetexpr []
- endfunc
- else
- let ErrorNr = 'E926'
- func! ReadFunc()
- lolder
- lgetexpr []
- endfunc
- endif
-
- augroup QF_Test
- au!
- autocmd BufReadCmd test_changed.txt call ReadFunc()
- augroup END
-
- new | only
- let words = [ "a", "b" ]
- let qflist = []
- for word in words
- call add(qflist, {'filename': 'test_changed.txt'})
- call g:Xsetlist(qflist, ' ')
- endfor
- call assert_fails('Xrewind', ErrorNr . ':')
-
- augroup QF_Test
- au!
- augroup END
-
- if a:cchar == 'c'
- cexpr ["Xtest1:1:Line"]
- cwindow
- only
- augroup QF_Test
- au!
- autocmd WinEnter * call setqflist([], 'f')
- augroup END
- call assert_fails('exe "normal \<CR>"', 'E925:')
- augroup QF_Test
- au!
- augroup END
- endif
- %bw!
-endfunc
-
-func Test_quickfix_was_changed_by_autocmd()
- call XquickfixChangedByAutocmd('c')
- call XquickfixChangedByAutocmd('l')
-endfunc
-
-func Test_setloclist_in_autocommand()
- call writefile(['test1', 'test2'], 'Xfile')
- edit Xfile
- let s:bufnr = bufnr()
- call setloclist(1,
- \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
- \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}])
-
- augroup Test_LocList
- au!
- autocmd BufEnter * call setloclist(1,
- \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
- \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}], 'r')
- augroup END
-
- lopen
- call assert_fails('exe "normal j\<CR>"', 'E926:')
-
- augroup Test_LocList
- au!
- augroup END
- call delete('Xfile')
-endfunc
-
-func Test_caddbuffer_to_empty()
- helpgr quickfix
- call setqflist([], 'r')
- cad
- try
- cn
- catch
- " number of matches is unknown
- call assert_true(v:exception =~ 'E553:')
- endtry
- quit!
-endfunc
-
-func Test_cgetexpr_works()
- " this must not crash Vim
- cgetexpr [$x]
- lgetexpr [$x]
-endfunc
-
-" Tests for the setqflist() and setloclist() functions
-func SetXlistTests(cchar, bnum)
- call s:setup_commands(a:cchar)
-
- call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1},
- \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5}])
- let l = g:Xgetlist()
- call assert_equal(2, len(l))
- call assert_equal(2, l[1].lnum)
- call assert_equal(3, l[1].end_lnum)
- call assert_equal(4, l[1].col)
- call assert_equal(5, l[1].end_col)
-
- Xnext
- call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 3}], 'a')
- let l = g:Xgetlist()
- call assert_equal(3, len(l))
- Xnext
- call assert_equal(3, line('.'))
-
- " Appending entries to the list should not change the cursor position
- " in the quickfix window
- Xwindow
- 1
- call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 4},
- \ {'bufnr': a:bnum, 'lnum': 5}], 'a')
- call assert_equal(1, line('.'))
- close
-
- call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 3},
- \ {'bufnr': a:bnum, 'lnum': 4},
- \ {'bufnr': a:bnum, 'lnum': 5}], 'r')
- let l = g:Xgetlist()
- call assert_equal(3, len(l))
- call assert_equal(5, l[2].lnum)
-
- call g:Xsetlist([])
- let l = g:Xgetlist()
- call assert_equal(0, len(l))
-
- " Tests for setting the 'valid' flag
- call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}])
- Xwindow
- call assert_equal(1, winnr('$'))
- 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('$'))
- Xclose
- let save_efm = &efm
- set efm=%m
- Xgetexpr 'TestMessage'
- let l = g:Xgetlist()
- call g:Xsetlist(l)
- call assert_equal(1, g:Xgetlist()[0].valid)
- let &efm = save_efm
-
- " Error cases:
- " Refer to a non-existing buffer and pass a non-dictionary type
- call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," .
- \ " {'bufnr':999, 'lnum':5}])", 'E92:')
- call g:Xsetlist([[1, 2,3]])
- call assert_equal(0, len(g:Xgetlist()))
- call assert_fails('call g:Xsetlist([], [])', 'E928:')
- call g:Xsetlist([v:_null_dict])
- call assert_equal([], g:Xgetlist())
-endfunc
-
-func Test_setqflist()
- new Xtestfile | only
- let bnum = bufnr('%')
- call setline(1, range(1,5))
-
- call SetXlistTests('c', bnum)
- call SetXlistTests('l', bnum)
-
- enew!
- call delete('Xtestfile')
-endfunc
-
-func Xlist_empty_middle(cchar)
- call s:setup_commands(a:cchar)
-
- " create three quickfix lists
- let @/ = 'Test_'
- Xvimgrep // test_quickfix.vim
- let testlen = len(g:Xgetlist())
- call assert_true(testlen > 0)
- Xvimgrep empty test_quickfix.vim
- call assert_true(len(g:Xgetlist()) > 0)
- Xvimgrep matches test_quickfix.vim
- let matchlen = len(g:Xgetlist())
- call assert_true(matchlen > 0)
- Xolder
- " make the middle list empty
- call g:Xsetlist([], 'r')
- call assert_true(len(g:Xgetlist()) == 0)
- Xolder
- call assert_equal(testlen, len(g:Xgetlist()))
- Xnewer
- Xnewer
- call assert_equal(matchlen, len(g:Xgetlist()))
-endfunc
-
-func Test_setqflist_empty_middle()
- call Xlist_empty_middle('c')
- call Xlist_empty_middle('l')
-endfunc
-
-func Xlist_empty_older(cchar)
- call s:setup_commands(a:cchar)
-
- " create three quickfix lists
- Xvimgrep one test_quickfix.vim
- let onelen = len(g:Xgetlist())
- call assert_true(onelen > 0)
- Xvimgrep two test_quickfix.vim
- let twolen = len(g:Xgetlist())
- call assert_true(twolen > 0)
- Xvimgrep three test_quickfix.vim
- let threelen = len(g:Xgetlist())
- call assert_true(threelen > 0)
- Xolder 2
- " make the first list empty, check the others didn't change
- call g:Xsetlist([], 'r')
- call assert_true(len(g:Xgetlist()) == 0)
- Xnewer
- call assert_equal(twolen, len(g:Xgetlist()))
- Xnewer
- call assert_equal(threelen, len(g:Xgetlist()))
-endfunc
-
-func Test_setqflist_empty_older()
- call Xlist_empty_older('c')
- call Xlist_empty_older('l')
-endfunc
-
-func XquickfixSetListWithAct(cchar)
- call s:setup_commands(a:cchar)
-
- let list1 = [{'filename': 'fnameA', 'text': 'A'},
- \ {'filename': 'fnameB', 'text': 'B'}]
- let list2 = [{'filename': 'fnameC', 'text': 'C'},
- \ {'filename': 'fnameD', 'text': 'D'},
- \ {'filename': 'fnameE', 'text': 'E'}]
-
- " {action} is unspecified. Same as specifying ' '.
- new | only
- silent! Xnewer 99
- call g:Xsetlist(list1)
- call g:Xsetlist(list2)
- let li = g:Xgetlist()
- call assert_equal(3, len(li))
- call assert_equal('C', li[0]['text'])
- call assert_equal('D', li[1]['text'])
- call assert_equal('E', li[2]['text'])
- silent! Xolder
- let li = g:Xgetlist()
- call assert_equal(2, len(li))
- call assert_equal('A', li[0]['text'])
- call assert_equal('B', li[1]['text'])
-
- " {action} is specified ' '.
- new | only
- silent! Xnewer 99
- call g:Xsetlist(list1)
- call g:Xsetlist(list2, ' ')
- let li = g:Xgetlist()
- call assert_equal(3, len(li))
- call assert_equal('C', li[0]['text'])
- call assert_equal('D', li[1]['text'])
- call assert_equal('E', li[2]['text'])
- silent! Xolder
- let li = g:Xgetlist()
- call assert_equal(2, len(li))
- call assert_equal('A', li[0]['text'])
- call assert_equal('B', li[1]['text'])
-
- " {action} is specified 'a'.
- new | only
- silent! Xnewer 99
- call g:Xsetlist(list1)
- call g:Xsetlist(list2, 'a')
- let li = g:Xgetlist()
- call assert_equal(5, len(li))
- call assert_equal('A', li[0]['text'])
- call assert_equal('B', li[1]['text'])
- call assert_equal('C', li[2]['text'])
- call assert_equal('D', li[3]['text'])
- call assert_equal('E', li[4]['text'])
-
- " {action} is specified 'r'.
- new | only
- silent! Xnewer 99
- call g:Xsetlist(list1)
- call g:Xsetlist(list2, 'r')
- let li = g:Xgetlist()
- call assert_equal(3, len(li))
- call assert_equal('C', li[0]['text'])
- call assert_equal('D', li[1]['text'])
- call assert_equal('E', li[2]['text'])
-
- " Test for wrong value.
- new | only
- call assert_fails("call g:Xsetlist(0)", 'E714:')
- call assert_fails("call g:Xsetlist(list1, '')", 'E927:')
- call assert_fails("call g:Xsetlist(list1, 'aa')", 'E927:')
- call assert_fails("call g:Xsetlist(list1, ' a')", 'E927:')
- call assert_fails("call g:Xsetlist(list1, 0)", 'E928:')
-endfunc
-
-func Test_setqflist_invalid_nr()
- " The following command used to crash Vim
- eval []->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')
-endfunc
-
-func XLongLinesTests(cchar)
- let l = g:Xgetlist()
-
- call assert_equal(4, len(l))
- call assert_equal(1, l[0].lnum)
- call assert_equal(1, l[0].col)
- call assert_equal(1975, len(l[0].text))
- call assert_equal(2, l[1].lnum)
- call assert_equal(1, l[1].col)
- call assert_equal(4070, len(l[1].text))
- call assert_equal(3, l[2].lnum)
- call assert_equal(1, l[2].col)
- call assert_equal(4070, len(l[2].text))
- call assert_equal(4, l[3].lnum)
- call assert_equal(1, l[3].col)
- call assert_equal(10, len(l[3].text))
-
- call g:Xsetlist([], 'r')
-endfunc
-
-func s:long_lines_tests(cchar)
- call s:setup_commands(a:cchar)
-
- let testfile = 'samples/quickfix.txt'
-
- " file
- exe 'Xgetfile' testfile
- call XLongLinesTests(a:cchar)
-
- " list
- Xexpr readfile(testfile)
- call XLongLinesTests(a:cchar)
-
- " string
- Xexpr join(readfile(testfile), "\n")
- call XLongLinesTests(a:cchar)
-
- " buffer
- exe 'edit' testfile
- exe 'Xbuffer' bufnr('%')
- call XLongLinesTests(a:cchar)
-endfunc
-
-func Test_long_lines()
- call s:long_lines_tests('c')
- call s:long_lines_tests('l')
-endfunc
-
-func Test_cgetfile_on_long_lines()
- " Problematic values if the line is longer than 4096 bytes. Then 1024 bytes
- " are read at a time.
- for len in [4078, 4079, 4080, 5102, 5103, 5104, 6126, 6127, 6128, 7150, 7151, 7152]
- let lines =<< trim END
- /tmp/file1:1:1:aaa
- /tmp/file2:1:1:%s
- /tmp/file3:1:1:bbb
- /tmp/file4:1:1:ccc
- END
- let lines[1] = substitute(lines[1], '%s', repeat('x', len), '')
- call writefile(lines, 'Xcqetfile.txt')
- cgetfile Xcqetfile.txt
- call assert_equal(4, getqflist(#{size: v:true}).size, 'with length ' .. len)
- endfor
- call delete('Xcqetfile.txt')
-endfunc
-
-func s:create_test_file(filename)
- let l = []
- for i in range(1, 20)
- call add(l, 'Line' . i)
- endfor
- call writefile(l, a:filename)
-endfunc
-
-func Test_switchbuf()
- call s:create_test_file('Xqftestfile1')
- call s:create_test_file('Xqftestfile2')
- call s:create_test_file('Xqftestfile3')
-
- new | only
- edit Xqftestfile1
- let file1_winid = win_getid()
- new Xqftestfile2
- let file2_winid = win_getid()
- let lines =<< trim END
- Xqftestfile1:5:Line5
- Xqftestfile1:6:Line6
- Xqftestfile2:10:Line10
- Xqftestfile2:11:Line11
- Xqftestfile3:15:Line15
- Xqftestfile3:16:Line16
- END
- cgetexpr lines
-
- new
- let winid = win_getid()
- cfirst | cnext
- call assert_equal(winid, win_getid())
- 2cnext
- call assert_equal(winid, win_getid())
- 2cnext
- call assert_equal(winid, win_getid())
-
- " Test for 'switchbuf' set to search for files in windows in the current
- " tabpage and jump to an existing window (if present)
- set switchbuf=useopen
- enew
- cfirst | cnext
- call assert_equal(file1_winid, win_getid())
- 2cnext
- call assert_equal(file2_winid, win_getid())
- 2cnext
- call assert_equal(file2_winid, win_getid())
-
- " Test for 'switchbuf' set to search for files in tabpages and jump to an
- " existing tabpage (if present)
- enew | only
- set switchbuf=usetab
- tabedit Xqftestfile1
- tabedit Xqftestfile2
- tabedit Xqftestfile3
- tabfirst
- cfirst | cnext
- call assert_equal(2, tabpagenr())
- 2cnext
- call assert_equal(3, tabpagenr())
- 6cnext
- call assert_equal(4, tabpagenr())
- 2cpfile
- call assert_equal(2, tabpagenr())
- 2cnfile
- call assert_equal(4, tabpagenr())
- tabfirst | tabonly | enew
-
- " Test for 'switchbuf' set to open a new window for every file
- set switchbuf=split
- cfirst | cnext
- call assert_equal(1, winnr('$'))
- cnext | cnext
- call assert_equal(2, winnr('$'))
- cnext | cnext
- call assert_equal(3, winnr('$'))
-
- " Test for 'switchbuf' set to open a new tabpage for every file
- set switchbuf=newtab
- enew | only
- cfirst | cnext
- call assert_equal(1, tabpagenr('$'))
- cnext | cnext
- call assert_equal(2, tabpagenr('$'))
- cnext | cnext
- 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
-
- " With an empty 'switchbuf', jumping to a quickfix entry should open the
- " file in an existing window (if present)
- set switchbuf=
- edit Xqftestfile1
- let file1_winid = win_getid()
- new Xqftestfile2
- let file2_winid = win_getid()
- copen
- exe "normal 1G\<CR>"
- call assert_equal(file1_winid, win_getid())
- copen
- exe "normal 3G\<CR>"
- call assert_equal(file2_winid, win_getid())
- copen | only
- exe "normal 5G\<CR>"
- call assert_equal(2, winnr('$'))
- call assert_equal(1, bufwinnr('Xqftestfile3'))
-
- " If only quickfix window is open in the current tabpage, jumping to an
- " entry with 'switchbuf' set to 'usetab' should search in other tabpages.
- enew | only
- set switchbuf=usetab
- tabedit Xqftestfile1
- tabedit Xqftestfile2
- tabedit Xqftestfile3
- tabfirst
- copen | only
- clast
- call assert_equal(4, tabpagenr())
- tabfirst | tabonly | enew | only
-
- " Jumping to a file that is not present in any of the tabpages and the
- " current tabpage doesn't have any usable windows, should open it in a new
- " window in the current tabpage.
- copen | only
- cfirst
- call assert_equal(1, tabpagenr())
- call assert_equal('Xqftestfile1', @%)
-
- " If opening a file changes 'switchbuf', then the new value should be
- " retained.
- set modeline&vim
- call writefile(["vim: switchbuf=split"], 'Xqftestfile1')
- enew | only
- set switchbuf&vim
- cexpr "Xqftestfile1:1:10"
- call assert_equal('split', &switchbuf)
- call writefile(["vim: switchbuf=usetab"], 'Xqftestfile1')
- enew | only
- set switchbuf=useopen
- cexpr "Xqftestfile1:1:10"
- call assert_equal('usetab', &switchbuf)
- call writefile(["vim: switchbuf&vim"], 'Xqftestfile1')
- enew | only
- set switchbuf=useopen
- cexpr "Xqftestfile1:1:10"
- call assert_equal('uselast', &switchbuf)
-
- call delete('Xqftestfile1')
- call delete('Xqftestfile2')
- call delete('Xqftestfile3')
- set switchbuf&vim
-
- enew | only
-endfunc
-
-func Xadjust_qflnum(cchar)
- call s:setup_commands(a:cchar)
-
- enew | only
-
- let fname = 'Xqftestfile' . a:cchar
- call s:create_test_file(fname)
- exe 'edit ' . fname
-
- Xgetexpr [fname . ':5:Line5',
- \ fname . ':10:Line10',
- \ fname . ':15:Line15',
- \ fname . ':20:Line20']
-
- 6,14delete
- call append(6, ['Buffer', 'Window'])
-
- let l = g:Xgetlist()
- call assert_equal(5, l[0].lnum)
- call assert_equal(6, l[2].lnum)
- call assert_equal(13, l[3].lnum)
-
- " If a file doesn't have any quickfix entries, then deleting lines in the
- " file should not update the quickfix list
- call g:Xsetlist([], 'f')
- 1,2delete
- call assert_equal([], g:Xgetlist())
-
- enew!
- call delete(fname)
-endfunc
-
-func Test_adjust_lnum()
- call setloclist(0, [])
- call Xadjust_qflnum('c')
- call setqflist([])
- call Xadjust_qflnum('l')
-endfunc
-
-" Tests for the :grep/:lgrep and :grepadd/:lgrepadd commands
-func s:test_xgrep(cchar)
- call s:setup_commands(a:cchar)
-
- " The following lines are used for the grep test. Don't remove.
- " Grep_Test_Text: Match 1
- " Grep_Test_Text: Match 2
- " GrepAdd_Test_Text: Match 1
- " GrepAdd_Test_Text: Match 2
- enew! | only
- set makeef&vim
- silent Xgrep Grep_Test_Text: test_quickfix.vim
- call assert_true(len(g:Xgetlist()) == 5)
- Xopen
- call assert_true(w:quickfix_title =~ '^:grep')
- Xclose
- enew
- set makeef=Temp_File_##
- silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim
- call assert_true(len(g:Xgetlist()) == 9)
-
- " Try with 'grepprg' set to 'internal'
- set grepprg=internal
- silent Xgrep Grep_Test_Text: test_quickfix.vim
- silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim
- call assert_true(len(g:Xgetlist()) == 9)
- set grepprg&vim
-
- call writefile(['Vim'], 'XtestTempFile')
- set makeef=XtestTempFile
- silent Xgrep Grep_Test_Text: test_quickfix.vim
- call assert_equal(5, len(g:Xgetlist()))
- call assert_false(filereadable('XtestTempFile'))
- set makeef&vim
-endfunc
-
-func Test_grep()
- if !has('unix')
- " The grepprg may not be set on non-Unix systems
- return
- endif
-
- call s:test_xgrep('c')
- call s:test_xgrep('l')
-endfunc
-
-func Test_two_windows()
- " Use one 'errorformat' for two windows. Add an expression to each of them,
- " make sure they each keep their own state.
- set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
- call mkdir('Xone/a', 'p')
- call mkdir('Xtwo/a', 'p')
- let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
- call writefile(lines, 'Xone/a/one.txt')
- call writefile(lines, 'Xtwo/a/two.txt')
-
- new one
- let one_id = win_getid()
- lexpr ""
- new two
- let two_id = win_getid()
- lexpr ""
-
- laddexpr "Entering dir 'Xtwo/a'"
- call win_gotoid(one_id)
- laddexpr "Entering dir 'Xone/a'"
- call win_gotoid(two_id)
- laddexpr 'two.txt:5:two two two'
- call win_gotoid(one_id)
- laddexpr 'one.txt:3:one one one'
-
- let loc_one = getloclist(one_id)
- call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr))
- call assert_equal(3, loc_one[1].lnum)
-
- let loc_two = getloclist(two_id)
- call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr))
- call assert_equal(5, loc_two[1].lnum)
-
- call win_gotoid(one_id)
- bwipe!
- call win_gotoid(two_id)
- bwipe!
- call delete('Xone', 'rf')
- call delete('Xtwo', 'rf')
-endfunc
-
-func XbottomTests(cchar)
- call s:setup_commands(a:cchar)
-
- " Calling lbottom without any errors should fail
- if a:cchar == 'l'
- call assert_fails('lbottom', 'E776:')
- endif
-
- call g:Xsetlist([{'filename': 'foo', 'lnum': 42}])
- Xopen
- let wid = win_getid()
- call assert_equal(1, line('.'))
- wincmd w
- call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a')
- Xbottom
- call win_gotoid(wid)
- call assert_equal(2, line('.'))
- Xclose
-endfunc
-
-" Tests for the :cbottom and :lbottom commands
-func Test_cbottom()
- call XbottomTests('c')
- call XbottomTests('l')
-endfunc
-
-func HistoryTest(cchar)
- call s:setup_commands(a:cchar)
-
- " clear all lists after the first one, then replace the first one.
- call g:Xsetlist([])
- call assert_fails('Xolder 99', 'E380:')
- let entry = {'filename': 'foo', 'lnum': 42}
- call g:Xsetlist([entry], 'r')
- call g:Xsetlist([entry, entry])
- call g:Xsetlist([entry, entry, entry])
- let res = split(execute(a:cchar . 'hist'), "\n")
- call assert_equal(3, len(res))
- let common = 'errors :set' . (a:cchar == 'c' ? 'qf' : 'loc') . 'list()'
- call assert_equal(' error list 1 of 3; 1 ' . common, res[0])
- call assert_equal(' error list 2 of 3; 2 ' . common, res[1])
- call assert_equal('> error list 3 of 3; 3 ' . common, res[2])
-
- " Test for changing the quickfix lists
- call assert_equal(3, g:Xgetlist({'nr' : 0}).nr)
- exe '1' . a:cchar . 'hist'
- call assert_equal(1, g:Xgetlist({'nr' : 0}).nr)
- exe '3' . a:cchar . 'hist'
- call assert_equal(3, g:Xgetlist({'nr' : 0}).nr)
- call assert_fails('-2' . a:cchar . 'hist', 'E16:')
- call assert_fails('4' . a:cchar . 'hist', 'E16:')
-
- call g:Xsetlist([], 'f')
- let l = split(execute(a:cchar . 'hist'), "\n")
- call assert_equal('No entries', l[0])
- if a:cchar == 'c'
- call assert_fails('4chist', 'E16:')
- else
- call assert_fails('4lhist', 'E776:')
- endif
-
- " An empty list should still show the stack history
- call g:Xsetlist([])
- let res = split(execute(a:cchar . 'hist'), "\n")
- call assert_equal('> error list 1 of 1; 0 ' . common, res[0])
-
- call g:Xsetlist([], 'f')
-endfunc
-
-func Test_history()
- call HistoryTest('c')
- call HistoryTest('l')
-endfunc
-
-func Test_duplicate_buf()
- " make sure we can get the highest buffer number
- edit DoesNotExist
- edit DoesNotExist2
- let last_buffer = bufnr("$")
-
- " make sure only one buffer is created
- call writefile(['this one', 'that one'], 'Xgrepthis')
- vimgrep one Xgrepthis
- vimgrep one Xgrepthis
- call assert_equal(last_buffer + 1, bufnr("$"))
-
- call delete('Xgrepthis')
-endfunc
-
-" Quickfix/Location list set/get properties tests
-func Xproperty_tests(cchar)
- call s:setup_commands(a:cchar)
-
- " Error cases
- call assert_fails('call g:Xgetlist(99)', 'E715:')
- call assert_fails('call g:Xsetlist(99)', 'E714:')
- call assert_fails('call g:Xsetlist([], "a", [])', 'E715:')
-
- " Set and get the title
- call g:Xsetlist([])
- Xopen
- wincmd p
- call g:Xsetlist([{'filename':'foo', 'lnum':27}])
- let s = g:Xsetlist([], 'a', {'title' : 'Sample'})
- call assert_equal(0, s)
- let d = g:Xgetlist({"title":1})
- call assert_equal('Sample', d.title)
- " Try setting title to a non-string value
- call assert_equal(-1, g:Xsetlist([], 'a', {'title' : ['Test']}))
- call assert_equal('Sample', g:Xgetlist({"title":1}).title)
-
- Xopen
- call assert_equal('Sample', w:quickfix_title)
- Xclose
-
- " Tests for action argument
- silent! Xolder 999
- let qfnr = g:Xgetlist({'all':1}).nr
- call g:Xsetlist([], 'r', {'title' : 'N1'})
- call assert_equal('N1', g:Xgetlist({'all':1}).title)
- call g:Xsetlist([], ' ', {'title' : 'N2'})
- call assert_equal(qfnr + 1, g:Xgetlist({'all':1}).nr)
-
- let res = g:Xgetlist({'nr': 0})
- call assert_equal(qfnr + 1, res.nr)
- call assert_equal(['nr'], keys(res))
-
- call g:Xsetlist([], ' ', {'title' : 'N3'})
- call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title)
-
- " Changing the title of an earlier quickfix list
- call g:Xsetlist([], 'r', {'title' : 'NewTitle', 'nr' : 2})
- call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title)
-
- " Changing the title of an invalid quickfix list
- call assert_equal(-1, g:Xsetlist([], ' ',
- \ {'title' : 'SomeTitle', 'nr' : 99}))
- call assert_equal(-1, g:Xsetlist([], ' ',
- \ {'title' : 'SomeTitle', 'nr' : 'abc'}))
-
- if a:cchar == 'c'
- copen
- call assert_equal({'winid':win_getid()}, getqflist({'winid':1}))
- cclose
- endif
-
- " Invalid arguments
- call assert_fails('call g:Xgetlist([])', 'E715')
- call assert_fails('call g:Xsetlist([], "a", [])', 'E715')
- let s = g:Xsetlist([], 'a', {'abc':1})
- call assert_equal(-1, s)
-
- call assert_equal({}, g:Xgetlist({'abc':1}))
- call assert_equal('', g:Xgetlist({'nr':99, 'title':1}).title)
- call assert_equal('', g:Xgetlist({'nr':[], 'title':1}).title)
-
- if a:cchar == 'l'
- call assert_equal({}, getloclist(99, {'title': 1}))
- endif
-
- " Context related tests
- let s = g:Xsetlist([], 'a', {'context':[1,2,3]})
- call assert_equal(0, s)
- call test_garbagecollect_now()
- let d = g:Xgetlist({'context':1})
- call assert_equal([1,2,3], d.context)
- call g:Xsetlist([], 'a', {'context':{'color':'green'}})
- let d = g:Xgetlist({'context':1})
- call assert_equal({'color':'green'}, d.context)
- call g:Xsetlist([], 'a', {'context':"Context info"})
- let d = g:Xgetlist({'context':1})
- call assert_equal("Context info", d.context)
- call g:Xsetlist([], 'a', {'context':246})
- let d = g:Xgetlist({'context':1})
- call assert_equal(246, d.context)
- " set other Vim data types as context
- call g:Xsetlist([], 'a', {'context' : v:_null_blob})
- call g:Xsetlist([], 'a', {'context' : ''})
- call test_garbagecollect_now()
- if a:cchar == 'l'
- " Test for copying context across two different location lists
- new | only
- let w1_id = win_getid()
- let l = [1]
- call setloclist(0, [], 'a', {'context':l})
- new
- let w2_id = win_getid()
- call add(l, 2)
- call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context)
- call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
- unlet! l
- call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
- only
- call setloclist(0, [], 'f')
- call assert_equal('', getloclist(0, {'context':1}).context)
- endif
-
- " Test for changing the context of previous quickfix lists
- call g:Xsetlist([], 'f')
- Xexpr "One"
- Xexpr "Two"
- Xexpr "Three"
- call g:Xsetlist([], 'r', {'context' : [1], 'nr' : 1})
- call g:Xsetlist([], 'a', {'context' : [2], 'nr' : 2})
- " Also, check for setting the context using quickfix list number zero.
- call g:Xsetlist([], 'r', {'context' : [3], 'nr' : 0})
- call test_garbagecollect_now()
- let l = g:Xgetlist({'nr' : 1, 'context' : 1})
- call assert_equal([1], l.context)
- let l = g:Xgetlist({'nr' : 2, 'context' : 1})
- call assert_equal([2], l.context)
- let l = g:Xgetlist({'nr' : 3, 'context' : 1})
- call assert_equal([3], l.context)
-
- " Test for changing the context through reference and for garbage
- " collection of quickfix context
- let l = ["red"]
- call g:Xsetlist([], ' ', {'context' : l})
- call add(l, "blue")
- let x = g:Xgetlist({'context' : 1})
- call add(x.context, "green")
- call assert_equal(["red", "blue", "green"], l)
- call assert_equal(["red", "blue", "green"], x.context)
- unlet l
- call test_garbagecollect_now()
- let m = g:Xgetlist({'context' : 1})
- call assert_equal(["red", "blue", "green"], m.context)
-
- " Test for setting/getting items
- Xexpr ""
- let qfprev = g:Xgetlist({'nr':0})
- let s = g:Xsetlist([], ' ', {'title':'Green',
- \ 'items' : [{'filename':'F1', 'lnum':10}]})
- call assert_equal(0, s)
- let qfcur = g:Xgetlist({'nr':0})
- call assert_true(qfcur.nr == qfprev.nr + 1)
- let l = g:Xgetlist({'items':1})
- call assert_equal('F1', bufname(l.items[0].bufnr))
- call assert_equal(10, l.items[0].lnum)
- call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20},
- \ {'filename':'F2', 'lnum':30}]})
- let l = g:Xgetlist({'items':1})
- call assert_equal('F2', bufname(l.items[2].bufnr))
- call assert_equal(30, l.items[2].lnum)
- call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]})
- let l = g:Xgetlist({'items':1})
- call assert_equal('F3', bufname(l.items[0].bufnr))
- call assert_equal(40, l.items[0].lnum)
- call g:Xsetlist([], 'r', {'items' : []})
- let l = g:Xgetlist({'items':1})
- call assert_equal(0, len(l.items))
-
- call g:Xsetlist([], 'r', {'title' : 'TestTitle'})
- call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]})
- 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}]})
- call assert_equal(10, g:Xgetlist({'items':1}).items[0].lnum)
-
- " Try setting the items using a string
- call assert_equal(-1, g:Xsetlist([], ' ', {'items' : 'Test'}))
-
- " Save and restore the quickfix stack
- call g:Xsetlist([], 'f')
- call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
- Xexpr "File1:10:Line1"
- Xexpr "File2:20:Line2"
- Xexpr "File3:30:Line3"
- let last_qf = g:Xgetlist({'nr':'$'}).nr
- call assert_equal(3, last_qf)
- let qstack = []
- for i in range(1, last_qf)
- let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1}))
- endfor
- call g:Xsetlist([], 'f')
- for i in range(len(qstack))
- call g:Xsetlist([], ' ', qstack[i])
- endfor
- call assert_equal(3, g:Xgetlist({'nr':'$'}).nr)
- call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum)
- call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum)
- call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum)
- call g:Xsetlist([], 'f')
-
- " Swap two quickfix lists
- Xexpr "File1:10:Line10"
- Xexpr "File2:20:Line20"
- Xexpr "File3:30:Line30"
- call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']})
- call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']})
- let l1=g:Xgetlist({'nr':1,'all':1})
- let l2=g:Xgetlist({'nr':2,'all':1})
- let save_id = l1.id
- let l1.id=l2.id
- let l2.id=save_id
- call g:Xsetlist([], 'r', l1)
- call g:Xsetlist([], 'r', l2)
- let newl1=g:Xgetlist({'nr':1,'all':1})
- let newl2=g:Xgetlist({'nr':2,'all':1})
- call assert_equal('Fruits', newl1.title)
- call assert_equal(['Fruits'], newl1.context)
- call assert_equal('Line20', newl1.items[0].text)
- call assert_equal('Colors', newl2.title)
- call assert_equal(['Colors'], newl2.context)
- call assert_equal('Line10', newl2.items[0].text)
- call g:Xsetlist([], 'f')
-
- " Cannot specify both a non-empty list argument and a dict argument
- call assert_fails("call g:Xsetlist([{}], ' ', {})", 'E475:')
-endfunc
-
-func Test_qf_property()
- call Xproperty_tests('c')
- call Xproperty_tests('l')
-endfunc
-
-" Test for setting the current index in the location/quickfix list
-func Xtest_setqfidx(cchar)
- call s:setup_commands(a:cchar)
-
- Xgetexpr "F1:10:1:Line1\nF2:20:2:Line2\nF3:30:3:Line3"
- Xgetexpr "F4:10:1:Line1\nF5:20:2:Line2\nF6:30:3:Line3"
- Xgetexpr "F7:10:1:Line1\nF8:20:2:Line2\nF9:30:3:Line3"
-
- call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 2})
- call g:Xsetlist([], 'a', {'nr' : 2, 'idx' : 2})
- call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 3})
- Xolder 2
- Xopen
- call assert_equal(3, line('.'))
- Xnewer
- call assert_equal(2, line('.'))
- Xnewer
- call assert_equal(2, line('.'))
- " Update the current index with the quickfix window open
- wincmd w
- call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 3})
- Xopen
- call assert_equal(3, line('.'))
- Xclose
-
- " Set the current index to the last entry
- call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : '$'})
- call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
- " A large value should set the index to the last index
- call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 1})
- call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 999})
- call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
- " Invalid index values
- call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : -1})
- call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
- call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 0})
- call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
- call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 'xx'})
- call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
- call assert_fails("call g:Xsetlist([], 'a', {'nr':1, 'idx':[]})", 'E745:')
-
- call g:Xsetlist([], 'f')
- new | only
-endfunc
-
-func Test_setqfidx()
- call Xtest_setqfidx('c')
- call Xtest_setqfidx('l')
-endfunc
-
-" Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands
-func QfAutoCmdHandler(loc, cmd)
- call add(g:acmds, a:loc . a:cmd)
-endfunc
-
-func Test_Autocmd()
- autocmd QuickFixCmdPre * call QfAutoCmdHandler('pre', expand('<amatch>'))
- autocmd QuickFixCmdPost * call QfAutoCmdHandler('post', expand('<amatch>'))
-
- let g:acmds = []
- cexpr "F1:10:Line 10"
- caddexpr "F1:20:Line 20"
- cgetexpr "F1:30:Line 30"
- cexpr ""
- caddexpr ""
- cgetexpr ""
- silent! cexpr non_existing_func()
- silent! caddexpr non_existing_func()
- silent! cgetexpr non_existing_func()
- let l =<< trim END
- precexpr
- postcexpr
- precaddexpr
- postcaddexpr
- precgetexpr
- postcgetexpr
- precexpr
- postcexpr
- precaddexpr
- postcaddexpr
- precgetexpr
- postcgetexpr
- precexpr
- precaddexpr
- precgetexpr
- END
- call assert_equal(l, g:acmds)
-
- let g:acmds = []
- enew! | call append(0, "F2:10:Line 10")
- cbuffer!
- enew! | call append(0, "F2:20:Line 20")
- cgetbuffer
- enew! | call append(0, "F2:30:Line 30")
- caddbuffer
-
- new
- let bnum = bufnr('%')
- bunload
- exe 'silent! cbuffer! ' . bnum
- exe 'silent! cgetbuffer ' . bnum
- exe 'silent! caddbuffer ' . bnum
- enew!
- let l =<< trim END
- precbuffer
- postcbuffer
- precgetbuffer
- postcgetbuffer
- precaddbuffer
- postcaddbuffer
- precbuffer
- precgetbuffer
- precaddbuffer
- END
- call assert_equal(l, g:acmds)
-
- call writefile(['Xtest:1:Line1'], 'Xtest')
- call writefile([], 'Xempty')
- let g:acmds = []
- cfile Xtest
- caddfile Xtest
- cgetfile Xtest
- cfile Xempty
- caddfile Xempty
- cgetfile Xempty
- silent! cfile do_not_exist
- silent! caddfile do_not_exist
- silent! cgetfile do_not_exist
- let l =<< trim END
- precfile
- postcfile
- precaddfile
- postcaddfile
- precgetfile
- postcgetfile
- precfile
- postcfile
- precaddfile
- postcaddfile
- precgetfile
- postcgetfile
- precfile
- postcfile
- precaddfile
- postcaddfile
- precgetfile
- postcgetfile
- END
- call assert_equal(l, g:acmds)
-
- let g:acmds = []
- helpgrep quickfix
- silent! helpgrep non_existing_help_topic
- vimgrep test Xtest
- vimgrepadd test Xtest
- silent! vimgrep non_existing_test Xtest
- silent! vimgrepadd non_existing_test Xtest
- set makeprg=
- silent! make
- set makeprg&
- let l =<< trim END
- prehelpgrep
- posthelpgrep
- prehelpgrep
- posthelpgrep
- previmgrep
- postvimgrep
- previmgrepadd
- postvimgrepadd
- previmgrep
- postvimgrep
- previmgrepadd
- postvimgrepadd
- premake
- postmake
- END
- call assert_equal(l, g:acmds)
-
- if has('unix')
- " Run this test only on Unix-like systems. The grepprg may not be set on
- " non-Unix systems.
- " The following lines are used for the grep test. Don't remove.
- " Grep_Autocmd_Text: Match 1
- " GrepAdd_Autocmd_Text: Match 2
- let g:acmds = []
- silent grep Grep_Autocmd_Text test_quickfix.vim
- silent grepadd GrepAdd_Autocmd_Text test_quickfix.vim
- silent grep abc123def Xtest
- silent grepadd abc123def Xtest
- set grepprg=internal
- silent grep Grep_Autocmd_Text test_quickfix.vim
- silent grepadd GrepAdd_Autocmd_Text test_quickfix.vim
- silent lgrep Grep_Autocmd_Text test_quickfix.vim
- silent lgrepadd GrepAdd_Autocmd_Text test_quickfix.vim
- set grepprg&vim
- let l =<< trim END
- pregrep
- postgrep
- pregrepadd
- postgrepadd
- pregrep
- postgrep
- pregrepadd
- postgrepadd
- pregrep
- postgrep
- pregrepadd
- postgrepadd
- prelgrep
- postlgrep
- prelgrepadd
- postlgrepadd
- END
- call assert_equal(l, g:acmds)
- endif
-
- call delete('Xtest')
- call delete('Xempty')
- au! QuickFixCmdPre
- au! QuickFixCmdPost
-endfunc
-
-func Test_Autocmd_Exception()
- set efm=%m
- lgetexpr '?'
-
- try
- call DoesNotExit()
- catch
- lgetexpr '1'
- finally
- lgetexpr '1'
- endtry
-
- call assert_equal('1', getloclist(0)[0].text)
-
- set efm&vim
-endfunc
-
-func Test_caddbuffer_wrong()
- " This used to cause a memory access in freed memory.
- let save_efm = &efm
- set efm=%EEEE%m,%WWWW,%+CCCC%>%#,%GGGG%.#
- cgetexpr ['WWWW', 'EEEE', 'CCCC']
- let &efm = save_efm
- caddbuffer
- bwipe!
-endfunc
-
-func Test_caddexpr_wrong()
- " This used to cause a memory access in freed memory.
- cbuffer
- cbuffer
- copen
- let save_efm = &efm
- set efm=%
- call assert_fails('caddexpr ""', 'E376:')
- let &efm = save_efm
-endfunc
-
-func Test_dirstack_cleanup()
- " This used to cause a memory access in freed memory.
- let save_efm = &efm
- lexpr '0'
- lopen
- fun X(c)
- let save_efm=&efm
- set efm=%D%f
- if a:c == 'c'
- caddexpr '::'
- else
- laddexpr ':0:0'
- endif
- let &efm=save_efm
- endfun
- call X('c')
- call X('l')
- call setqflist([], 'r')
- caddbuffer
- let &efm = save_efm
-endfunc
-
-" Tests for jumping to entries from the location list window and quickfix
-" window
-func Test_cwindow_jump()
- set efm=%f%%%l%%%m
- lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
- lopen | only
- lfirst
- call assert_true(winnr('$') == 2)
- call assert_true(winnr() == 1)
- " Location list for the new window should be set
- call assert_true(getloclist(0)[2].text == 'Line 30')
-
- " Open a scratch buffer
- " Open a new window and create a location list
- " Open the location list window and close the other window
- " Jump to an entry.
- " Should create a new window and jump to the entry. The scratch buffer
- " should not be used.
- enew | only
- set buftype=nofile
- below new
- lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
- lopen
- 2wincmd c
- lnext
- call assert_true(winnr('$') == 3)
- call assert_true(winnr() == 2)
-
- " Open two windows with two different location lists
- " Open the location list window and close the previous window
- " Jump to an entry in the location list window
- " Should open the file in the first window and not set the location list.
- enew | only
- lgetexpr ["F1%5%Line 5"]
- below new
- lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
- lopen
- 2wincmd c
- lnext
- call assert_true(winnr() == 1)
- call assert_true(getloclist(0)[0].text == 'Line 5')
-
- enew | only
- cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
- copen
- cnext
- call assert_true(winnr('$') == 2)
- call assert_true(winnr() == 1)
-
- " open the quickfix buffer in two windows and jump to an entry. Should open
- " the file in the first quickfix window.
- enew | only
- copen
- let bnum = bufnr('')
- exe 'sbuffer ' . bnum
- wincmd b
- cfirst
- call assert_equal(2, winnr())
- call assert_equal('F1', @%)
- enew | only
- exe 'sb' bnum
- exe 'botright sb' bnum
- wincmd t
- clast
- call assert_equal(2, winnr())
- call assert_equal('quickfix', getwinvar(1, '&buftype'))
- call assert_equal('quickfix', getwinvar(3, '&buftype'))
-
- " Jumping to a file from the location list window should find a usable
- " window by wrapping around the window list.
- enew | only
- call setloclist(0, [], 'f')
- new | new
- lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
- lopen
- 1close
- call assert_equal(0, getloclist(3, {'id' : 0}).id)
- lnext
- call assert_equal(3, winnr())
- call assert_equal(getloclist(1, {'id' : 0}).id, getloclist(3, {'id' : 0}).id)
-
- enew | only
- set efm&vim
-endfunc
-
-func Test_cwindow_highlight()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['some', 'text', 'with', 'matches'])
- write XCwindow
- vimgrep e XCwindow
- redraw
- cwindow 4
- END
- call writefile(lines, 'XtestCwindow')
- let buf = RunVimInTerminal('-S XtestCwindow', #{rows: 12})
- call VerifyScreenDump(buf, 'Test_quickfix_cwindow_1', {})
-
- call term_sendkeys(buf, ":cnext\<CR>")
- call VerifyScreenDump(buf, 'Test_quickfix_cwindow_2', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XtestCwindow')
- call delete('XCwindow')
-endfunc
-
-func XvimgrepTests(cchar)
- call s:setup_commands(a:cchar)
-
- let lines =<< trim END
- Editor:VIM vim
- Editor:Emacs EmAcS
- Editor:Notepad NOTEPAD
- END
- call writefile(lines, 'Xtestfile1')
- call writefile(['Linux', 'macOS', 'MS-Windows'], 'Xtestfile2')
-
- " Error cases
- call assert_fails('Xvimgrep /abc *', 'E682:')
-
- let @/=''
- call assert_fails('Xvimgrep // *', 'E35:')
-
- call assert_fails('Xvimgrep abc', 'E683:')
- call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:')
- call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:')
-
- Xexpr ""
- Xvimgrepadd Notepad Xtestfile1
- Xvimgrepadd macOS Xtestfile2
- let l = g:Xgetlist()
- call assert_equal(2, len(l))
- call assert_equal('Editor:Notepad NOTEPAD', l[0].text)
-
- 10Xvimgrep #\cvim#g Xtestfile?
- let l = g:Xgetlist()
- call assert_equal(2, len(l))
- call assert_equal(8, l[0].col)
- call assert_equal(11, l[0].end_col)
- call assert_equal(12, l[1].col)
- call assert_equal(15, l[1].end_col)
-
- 1Xvimgrep ?Editor? Xtestfile*
- let l = g:Xgetlist()
- call assert_equal(1, len(l))
- call assert_equal('Editor:VIM vim', l[0].text)
-
- edit +3 Xtestfile2
- Xvimgrep +\cemacs+j Xtestfile1
- let l = g:Xgetlist()
- call assert_equal('Xtestfile2', @%)
- 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'))
-
- " Test for opening the dummy buffer used by vimgrep in a window. The new
- " window should be closed
- %bw!
- augroup QF_Test
- au!
- autocmd BufReadPre * exe "sb " .. expand("<abuf>")
- augroup END
- call assert_fails("Xvimgrep /sublime/ Xtestfile1", 'E480:')
- call assert_equal(1, winnr('$'))
- augroup QF_Test
- au!
- augroup END
-
- call delete('Xtestfile1')
- call delete('Xtestfile2')
-endfunc
-
-" Tests for the :vimgrep command
-func Test_vimgrep()
- call XvimgrepTests('c')
- call XvimgrepTests('l')
-endfunc
-
-func Test_vimgrep_wildcards_expanded_once()
- new X[id-01] file.txt
- call setline(1, 'some text to search for')
- vimgrep text %
- bwipe!
-endfunc
-
-" Test for incsearch highlighting of the :vimgrep pattern
-" This test used to cause "E315: ml_get: invalid lnum" errors.
-func Test_vimgrep_incsearch()
- CheckFunction test_override
- enew
- set incsearch
- call test_override("char_avail", 1)
-
- call feedkeys(":2vimgrep assert test_quickfix.vim test_cdo.vim\<CR>", "ntx")
- let l = getqflist()
- call assert_equal(2, len(l))
-
- call test_override("ALL", 0)
- set noincsearch
-endfunc
-
-" Test vimgrep with the last search pattern not set
-func Test_vimgrep_with_no_last_search_pat()
- let lines =<< trim [SCRIPT]
- call assert_fails('vimgrep // *', 'E35:')
- call writefile(v:errors, 'Xresult')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -S Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" Test vimgrep without swap file
-func Test_vimgrep_without_swap_file()
- let lines =<< trim [SCRIPT]
- vimgrep grep test_c*
- call writefile(['done'], 'Xresult')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -n -S Xscript Xscript')
- call assert_equal(['done'], readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-func Test_vimgrep_existing_swapfile()
- call writefile(['match apple with apple'], 'Xapple')
- call writefile(['swapfile'], '.Xapple.swp')
- let g:foundSwap = 0
- let g:ignoreSwapExists = 1
- augroup grep
- au SwapExists * let foundSwap = 1 | let v:swapchoice = 'e'
- augroup END
- vimgrep apple Xapple
- call assert_equal(1, g:foundSwap)
- call assert_match('.Xapple.swo', swapname(''))
-
- call delete('Xapple')
- call delete('Xapple.swp')
- augroup grep
- au! SwapExists
- augroup END
- unlet g:ignoreSwapExists
-endfunc
-
-func XfreeTests(cchar)
- call s:setup_commands(a:cchar)
-
- enew | only
-
- " Deleting the quickfix stack should work even When the current list is
- " somewhere in the middle of the stack
- Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
- Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
- Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
- Xolder
- call g:Xsetlist([], 'f')
- call assert_equal(0, len(g:Xgetlist()))
-
- " After deleting the stack, adding a new list should create a stack with a
- " single list.
- Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
- call assert_equal(1, g:Xgetlist({'all':1}).nr)
-
- " Deleting the stack from a quickfix window should update/clear the
- " quickfix/location list window.
- Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
- Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
- Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
- Xolder
- Xwindow
- call g:Xsetlist([], 'f')
- call assert_equal(2, winnr('$'))
- call assert_equal(1, line('$'))
- Xclose
-
- " Deleting the stack from a non-quickfix window should update/clear the
- " quickfix/location list window.
- Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
- Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
- Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
- Xolder
- Xwindow
- wincmd p
- call g:Xsetlist([], 'f')
- call assert_equal(0, len(g:Xgetlist()))
- wincmd p
- call assert_equal(2, winnr('$'))
- call assert_equal(1, line('$'))
-
- " After deleting the location list stack, if the location list window is
- " opened, then a new location list should be created. So opening the
- " location list window again should not create a new window.
- if a:cchar == 'l'
- lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
- wincmd p
- lopen
- call assert_equal(2, winnr('$'))
- endif
- Xclose
-endfunc
-
-" Tests for the quickfix free functionality
-func Test_qf_free()
- call XfreeTests('c')
- call XfreeTests('l')
-endfunc
-
-" Test for buffer overflow when parsing lines and adding new entries to
-" the quickfix list.
-func Test_bufoverflow()
- set efm=%f:%l:%m
- cgetexpr ['File1:100:' . repeat('x', 1025)]
-
- set efm=%+GCompiler:\ %.%#,%f:%l:%m
- cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World']
-
- set efm=%DEntering\ directory\ %f,%f:%l:%m
- cgetexpr ['Entering directory ' . repeat('a', 1006),
- \ 'File1:10:Hello World']
- set efm&vim
-endfunc
-
-" Tests for getting the quickfix stack size
-func XsizeTests(cchar)
- call s:setup_commands(a:cchar)
-
- call g:Xsetlist([], 'f')
- call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
- call assert_equal('', g:Xgetlist({'nr':'$', 'all':1}).title)
- call assert_equal(0, g:Xgetlist({'nr':0}).nr)
-
- Xexpr "File1:10:Line1"
- Xexpr "File2:20:Line2"
- Xexpr "File3:30:Line3"
- Xolder | Xolder
- call assert_equal(3, g:Xgetlist({'nr':'$'}).nr)
- call g:Xsetlist([], 'f')
-
- Xexpr "File1:10:Line1"
- Xexpr "File2:20:Line2"
- Xexpr "File3:30:Line3"
- Xolder | Xolder
- call g:Xsetlist([], 'a', {'nr':'$', 'title':'Compiler'})
- call assert_equal('Compiler', g:Xgetlist({'nr':3, 'all':1}).title)
-endfunc
-
-func Test_Qf_Size()
- call XsizeTests('c')
- call XsizeTests('l')
-endfunc
-
-func Test_cclose_from_copen()
- augroup QF_Test
- au!
- au FileType qf :call assert_fails(':cclose', 'E788')
- augroup END
- copen
- augroup QF_Test
- au!
- augroup END
- augroup! QF_Test
-endfunc
-
-func Test_cclose_in_autocmd()
- " Problem is only triggered if "starting" is zero, so that the OptionSet
- " event will be triggered.
- " call test_override('starting', 1)
- augroup QF_Test
- au!
- au FileType qf :call assert_fails(':cclose', 'E788')
- augroup END
- copen
- augroup QF_Test
- au!
- augroup END
- augroup! QF_Test
- " call test_override('starting', 0)
-endfunc
-
-" Check that ":file" without an argument is possible even when "curbuf->b_ro_locked"
-" is set.
-func Test_file_from_copen()
- " Works without argument.
- augroup QF_Test
- au!
- au FileType qf file
- augroup END
- copen
-
- augroup QF_Test
- au!
- augroup END
- cclose
-
- " Fails with argument.
- augroup QF_Test
- au!
- au FileType qf call assert_fails(':file foo', 'E788')
- augroup END
- copen
- augroup QF_Test
- au!
- augroup END
- cclose
-
- augroup! QF_Test
-endfunc
-
-func Test_resize_from_copen()
- augroup QF_Test
- au!
- au FileType qf resize 5
- augroup END
- try
- " This should succeed without any exception. No other buffers are
- " involved in the autocmd.
- copen
- finally
- augroup QF_Test
- au!
- augroup END
- augroup! QF_Test
- endtry
-endfunc
-
-func Test_filetype_autocmd()
- " this changes the location list while it is in use to fill a buffer
- lexpr ''
- lopen
- augroup FT_loclist
- au FileType * call setloclist(0, [], 'f')
- augroup END
- silent! lolder
- lexpr ''
-
- augroup FT_loclist
- au! FileType
- augroup END
-endfunc
-
-func Test_vimgrep_with_textlock()
- new
-
- " Simple way to execute something with "textlock" set.
- " Check that vimgrep without jumping can be executed.
- au InsertCharPre * vimgrep /RunTheTest/j runtest.vim
- normal ax
- let qflist = getqflist()
- call assert_true(len(qflist) > 0)
- call assert_match('RunTheTest', qflist[0].text)
- call setqflist([], 'r')
- au! InsertCharPre
-
- " Check that vimgrepadd without jumping can be executed.
- au InsertCharPre * vimgrepadd /RunTheTest/j runtest.vim
- normal ax
- let qflist = getqflist()
- call assert_true(len(qflist) > 0)
- call assert_match('RunTheTest', qflist[0].text)
- call setqflist([], 'r')
- au! InsertCharPre
-
- " Check that lvimgrep without jumping can be executed.
- au InsertCharPre * lvimgrep /RunTheTest/j runtest.vim
- normal ax
- let qflist = getloclist(0)
- call assert_true(len(qflist) > 0)
- call assert_match('RunTheTest', qflist[0].text)
- call setloclist(0, [], 'r')
- au! InsertCharPre
-
- " Check that lvimgrepadd without jumping can be executed.
- au InsertCharPre * lvimgrepadd /RunTheTest/j runtest.vim
- normal ax
- let qflist = getloclist(0)
- call assert_true(len(qflist) > 0)
- call assert_match('RunTheTest', qflist[0].text)
- call setloclist(0, [], 'r')
- au! InsertCharPre
-
- " trying to jump will give an error
- au InsertCharPre * vimgrep /RunTheTest/ runtest.vim
- call assert_fails('normal ax', 'E565:')
- au! InsertCharPre
-
- au InsertCharPre * vimgrepadd /RunTheTest/ runtest.vim
- call assert_fails('normal ax', 'E565:')
- au! InsertCharPre
-
- au InsertCharPre * lvimgrep /RunTheTest/ runtest.vim
- call assert_fails('normal ax', 'E565:')
- au! InsertCharPre
-
- au InsertCharPre * lvimgrepadd /RunTheTest/ runtest.vim
- call assert_fails('normal ax', 'E565:')
- au! InsertCharPre
-
- bwipe!
-endfunc
-
-" Tests for the quickfix buffer b:changedtick variable
-func Xchangedtick_tests(cchar)
- call s:setup_commands(a:cchar)
-
- new | only
-
- Xexpr "" | Xexpr "" | Xexpr ""
-
- Xopen
- Xolder
- Xolder
- Xaddexpr "F1:10:Line10"
- Xaddexpr "F2:20:Line20"
- call g:Xsetlist([{"filename":"F3", "lnum":30, "text":"Line30"}], 'a')
- call g:Xsetlist([], 'f')
- call assert_equal(8, getbufvar('%', 'changedtick'))
- Xclose
-endfunc
-
-func Test_changedtick()
- call Xchangedtick_tests('c')
- call Xchangedtick_tests('l')
-endfunc
-
-" Tests for parsing an expression using setqflist()
-func Xsetexpr_tests(cchar)
- call s:setup_commands(a:cchar)
-
- let t = ["File1:10:Line10", "File1:20:Line20"]
- call g:Xsetlist([], ' ', {'lines' : t})
- call g:Xsetlist([], 'a', {'lines' : ["File1:30:Line30"]})
-
- let l = g:Xgetlist()
- call assert_equal(3, len(l))
- call assert_equal(20, l[1].lnum)
- call assert_equal('Line30', l[2].text)
- call g:Xsetlist([], 'r', {'lines' : ["File2:5:Line5"]})
- let l = g:Xgetlist()
- call assert_equal(1, len(l))
- call assert_equal('Line5', l[0].text)
- call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : 10}))
- call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : "F1:10:L10"}))
-
- call g:Xsetlist([], 'f')
- " Add entries to multiple lists
- call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:10:Line10"]})
- call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:20:Line20"]})
- call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:15:Line15"]})
- call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:25:Line25"]})
- call assert_equal('Line15', g:Xgetlist({'nr':1, 'items':1}).items[1].text)
- call assert_equal('Line25', g:Xgetlist({'nr':2, 'items':1}).items[1].text)
-
- " Adding entries using a custom efm
- set efm&
- call g:Xsetlist([], ' ', {'efm' : '%f#%l#%m',
- \ 'lines' : ["F1#10#L10", "F2#20#L20"]})
- call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum)
- call g:Xsetlist([], 'a', {'efm' : '%f#%l#%m', 'lines' : ["F3:30:L30"]})
- call assert_equal('F3:30:L30', g:Xgetlist({'items':1}).items[2].text)
- call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum)
- call assert_equal(-1, g:Xsetlist([], 'a', {'efm' : [],
- \ 'lines' : ['F1:10:L10']}))
-endfunc
-
-func Test_setexpr()
- call Xsetexpr_tests('c')
- call Xsetexpr_tests('l')
-endfunc
-
-" Tests for per quickfix/location list directory stack
-func Xmultidirstack_tests(cchar)
- call s:setup_commands(a:cchar)
-
- call g:Xsetlist([], 'f')
- Xexpr "" | Xexpr ""
-
- call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["Entering dir 'Xone/a'"]})
- call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["Entering dir 'Xtwo/a'"]})
- call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["one.txt:3:one one one"]})
- call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["two.txt:5:two two two"]})
-
- let l1 = g:Xgetlist({'nr':1, 'items':1})
- let l2 = g:Xgetlist({'nr':2, 'items':1})
- call assert_equal('Xone/a/one.txt', bufname(l1.items[1].bufnr))
- call assert_equal(3, l1.items[1].lnum)
- call assert_equal('Xtwo/a/two.txt', bufname(l2.items[1].bufnr))
- call assert_equal(5, l2.items[1].lnum)
-endfunc
-
-func Test_multidirstack()
- call mkdir('Xone/a', 'p')
- call mkdir('Xtwo/a', 'p')
- let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
- call writefile(lines, 'Xone/a/one.txt')
- call writefile(lines, 'Xtwo/a/two.txt')
- let save_efm = &efm
- set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
-
- call Xmultidirstack_tests('c')
- call Xmultidirstack_tests('l')
-
- let &efm = save_efm
- call delete('Xone', 'rf')
- call delete('Xtwo', 'rf')
-endfunc
-
-" Tests for per quickfix/location list file stack
-func Xmultifilestack_tests(cchar)
- call s:setup_commands(a:cchar)
-
- call g:Xsetlist([], 'f')
- Xexpr "" | Xexpr ""
-
- call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["[one.txt]"]})
- call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["[two.txt]"]})
- call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["(3,5) one one one"]})
- call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["(5,9) two two two"]})
-
- let l1 = g:Xgetlist({'nr':1, 'items':1})
- let l2 = g:Xgetlist({'nr':2, 'items':1})
- call assert_equal('one.txt', bufname(l1.items[1].bufnr))
- call assert_equal(3, l1.items[1].lnum)
- call assert_equal('two.txt', bufname(l2.items[1].bufnr))
- call assert_equal(5, l2.items[1].lnum)
-
- " Test for start of a new error line in the same line where a previous
- " error line ends with a file stack.
- let efm_val = 'Error\ l%l\ in\ %f,'
- let efm_val .= '%-P%>(%f%r,Error\ l%l\ in\ %m,%-Q)%r'
- let lines =<< trim END
- (one.txt
- Error l4 in one.txt
- ) (two.txt
- Error l6 in two.txt
- )
- Error l8 in one.txt
- END
- let l = g:Xgetlist({'lines': lines, 'efm' : efm_val})
- call assert_equal(3, len(l.items))
- call assert_equal('one.txt', bufname(l.items[0].bufnr))
- call assert_equal(4, l.items[0].lnum)
- call assert_equal('one.txt', l.items[0].text)
- call assert_equal('two.txt', bufname(l.items[1].bufnr))
- call assert_equal(6, l.items[1].lnum)
- call assert_equal('two.txt', l.items[1].text)
- call assert_equal('one.txt', bufname(l.items[2].bufnr))
- call assert_equal(8, l.items[2].lnum)
- call assert_equal('', l.items[2].text)
-endfunc
-
-func Test_multifilestack()
- let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
- call writefile(lines, 'one.txt')
- call writefile(lines, 'two.txt')
- let save_efm = &efm
- set efm=%+P[%f],(%l\\,%c)\ %m,%-Q
-
- call Xmultifilestack_tests('c')
- call Xmultifilestack_tests('l')
-
- let &efm = save_efm
- call delete('one.txt')
- call delete('two.txt')
-endfunc
-
-" Tests for per buffer 'efm' setting
-func Test_perbuf_efm()
- call writefile(["File1-10-Line10"], 'one.txt')
- call writefile(["File2#20#Line20"], 'two.txt')
- set efm=%f#%l#%m
- new | only
- new
- setlocal efm=%f-%l-%m
- cfile one.txt
- wincmd w
- caddfile two.txt
-
- let l = getqflist()
- call assert_equal(10, l[0].lnum)
- call assert_equal('Line20', l[1].text)
-
- set efm&
- new | only
- call delete('one.txt')
- call delete('two.txt')
-endfunc
-
-" Open multiple help windows using ":lhelpgrep
-" This test used to crash Vim
-func Test_Multi_LL_Help()
- new | only
- lhelpgrep window
- lopen
- e#
- lhelpgrep buffer
- call assert_equal(3, winnr('$'))
- call assert_true(len(getloclist(1)) != 0)
- call assert_true(len(getloclist(2)) != 0)
- new | only
-endfunc
-
-" Tests for adding new quickfix lists using setqflist()
-func XaddQf_tests(cchar)
- call s:setup_commands(a:cchar)
-
- " Create a new list using ' ' for action
- call g:Xsetlist([], 'f')
- call g:Xsetlist([], ' ', {'title' : 'Test1'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(1, l.nr)
- call assert_equal('Test1', l.title)
-
- " Create a new list using ' ' for action and '$' for 'nr'
- call g:Xsetlist([], 'f')
- call g:Xsetlist([], ' ', {'title' : 'Test2', 'nr' : '$'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(1, l.nr)
- call assert_equal('Test2', l.title)
-
- " Create a new list using 'a' for action
- call g:Xsetlist([], 'f')
- call g:Xsetlist([], 'a', {'title' : 'Test3'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(1, l.nr)
- call assert_equal('Test3', l.title)
-
- " Create a new list using 'a' for action and '$' for 'nr'
- call g:Xsetlist([], 'f')
- call g:Xsetlist([], 'a', {'title' : 'Test3', 'nr' : '$'})
- call g:Xsetlist([], 'a', {'title' : 'Test4'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(1, l.nr)
- call assert_equal('Test4', l.title)
-
- " Adding a quickfix list should remove all the lists following the current
- " list.
- Xexpr "" | Xexpr "" | Xexpr ""
- silent! 10Xolder
- call g:Xsetlist([], ' ', {'title' : 'Test5'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(2, l.nr)
- call assert_equal('Test5', l.title)
-
- " Add a quickfix list using '$' as the list number.
- let lastqf = g:Xgetlist({'nr':'$'}).nr
- silent! 99Xolder
- call g:Xsetlist([], ' ', {'nr' : '$', 'title' : 'Test6'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(lastqf + 1, l.nr)
- call assert_equal('Test6', l.title)
-
- " Add a quickfix list using 'nr' set to one more than the quickfix
- " list size.
- let lastqf = g:Xgetlist({'nr':'$'}).nr
- silent! 99Xolder
- call g:Xsetlist([], ' ', {'nr' : lastqf + 1, 'title' : 'Test7'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(lastqf + 1, l.nr)
- call assert_equal('Test7', l.title)
-
- " Add a quickfix list to a stack with 10 lists using 'nr' set to '$'
- exe repeat('Xexpr "" |', 9) . 'Xexpr ""'
- silent! 99Xolder
- call g:Xsetlist([], ' ', {'nr' : '$', 'title' : 'Test8'})
- let l = g:Xgetlist({'nr' : '$', 'all' : 1})
- call assert_equal(10, l.nr)
- call assert_equal('Test8', l.title)
-
- " Add a quickfix list using 'nr' set to a value greater than 10
- call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : 12, 'title' : 'Test9'}))
-
- " Try adding a quickfix list with 'nr' set to a value greater than the
- " quickfix list size but less than 10.
- call g:Xsetlist([], 'f')
- Xexpr "" | Xexpr "" | Xexpr ""
- silent! 99Xolder
- call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : 8, 'title' : 'Test10'}))
-
- " Add a quickfix list using 'nr' set to a some string or list
- call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : [1,2], 'title' : 'Test11'}))
-endfunc
-
-func Test_add_qf()
- call XaddQf_tests('c')
- call XaddQf_tests('l')
-endfunc
-
-" Test for getting the quickfix list items from some text without modifying
-" the quickfix stack
-func XgetListFromLines(cchar)
- call s:setup_commands(a:cchar)
- call g:Xsetlist([], 'f')
-
- let l = g:Xgetlist({'lines' : ["File2:20:Line20", "File2:30:Line30"]}).items
- call assert_equal(2, len(l))
- call assert_equal(30, l[1].lnum)
-
- call assert_equal({}, g:Xgetlist({'lines' : 10}))
- call assert_equal({}, g:Xgetlist({'lines' : 'File1:10:Line10'}))
- call assert_equal([], g:Xgetlist({'lines' : []}).items)
- call assert_equal([], g:Xgetlist({'lines' : [10, 20]}).items)
-
- " Parse text using a custom efm
- set efm&
- let l = g:Xgetlist({'lines':['File3#30#Line30'], 'efm' : '%f#%l#%m'}).items
- call assert_equal('Line30', l[0].text)
- let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : '%f-%l-%m'}).items
- call assert_equal('File3:30:Line30', l[0].text)
- let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : [1,2]})
- call assert_equal({}, l)
- call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':'%2'})", 'E376:')
- call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':''})", 'E378:')
-
- " Make sure that the quickfix stack is not modified
- call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
-endfunc
-
-func Test_get_list_from_lines()
- call XgetListFromLines('c')
- call XgetListFromLines('l')
-endfunc
-
-" Tests for the quickfix list id
-func Xqfid_tests(cchar)
- call s:setup_commands(a:cchar)
-
- call g:Xsetlist([], 'f')
- call assert_equal(0, g:Xgetlist({'id':0}).id)
- Xexpr ''
- let start_id = g:Xgetlist({'id' : 0}).id
- Xexpr '' | Xexpr ''
- Xolder
- call assert_equal(start_id, g:Xgetlist({'id':0, 'nr':1}).id)
- call assert_equal(start_id + 1, g:Xgetlist({'id':0, 'nr':0}).id)
- call assert_equal(start_id + 2, g:Xgetlist({'id':0, 'nr':'$'}).id)
- call assert_equal(0, g:Xgetlist({'id':0, 'nr':99}).id)
- call assert_equal(2, g:Xgetlist({'id':start_id + 1, 'nr':0}).nr)
- call assert_equal(0, g:Xgetlist({'id':99, 'nr':0}).id)
- call assert_equal(0, g:Xgetlist({'id':"abc", 'nr':0}).id)
-
- call g:Xsetlist([], 'a', {'id':start_id, 'context':[1,2]})
- call assert_equal([1,2], g:Xgetlist({'nr':1, 'context':1}).context)
- call g:Xsetlist([], 'a', {'id':start_id+1, 'lines':['F1:10:L10']})
- call assert_equal('L10', g:Xgetlist({'nr':2, 'items':1}).items[0].text)
- call assert_equal(-1, g:Xsetlist([], 'a', {'id':999, 'title':'Vim'}))
- call assert_equal(-1, g:Xsetlist([], 'a', {'id':'abc', 'title':'Vim'}))
-
- let qfid = g:Xgetlist({'id':0, 'nr':0})
- call g:Xsetlist([], 'f')
- call assert_equal(0, g:Xgetlist({'id':qfid, 'nr':0}).id)
-endfunc
-
-func Test_qf_id()
- call Xqfid_tests('c')
- call Xqfid_tests('l')
-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')
-
- " 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', @%)
- 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
- let lines =<< trim END
- F1:1:1:Line1
- F1:2:2:Line2
- F2:1:1:Line1
- F2:2:2:Line2
- F3:1:1:Line1
- F3:2:2:Line2
- END
- call g:Xsetlist([], ' ', {'lines': lines})
- 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
-
-" Tests for the getqflist() and getloclist() functions when the list is not
-" present or is empty
-func Xgetlist_empty_tests(cchar)
- call s:setup_commands(a:cchar)
-
- " Empty quickfix stack
- call g:Xsetlist([], 'f')
- call assert_equal('', g:Xgetlist({'context' : 0}).context)
- call assert_equal(0, g:Xgetlist({'id' : 0}).id)
- call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
- call assert_equal([], g:Xgetlist({'items' : 0}).items)
- call assert_equal(0, g:Xgetlist({'nr' : 0}).nr)
- call assert_equal(0, g:Xgetlist({'size' : 0}).size)
- 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)
- if a:cchar == 'c'
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
- \ 'items' : [], 'nr' : 0, 'size' : 0, 'qfbufnr' : 0,
- \ 'title' : '', 'winid' : 0, 'changedtick': 0,
- \ 'quickfixtextfunc' : ''}, 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,
- \ 'qfbufnr' : 0, 'quickfixtextfunc' : ''},
- \ g:Xgetlist({'all' : 0}))
- endif
-
- " Quickfix window with empty stack
- silent! Xopen
- let qfwinid = (a:cchar == 'c') ? win_getid() : 0
- let qfbufnr = (a:cchar == 'c') ? bufnr('') : 0
- call assert_equal(qfwinid, g:Xgetlist({'winid' : 0}).winid)
- Xclose
-
- " Empty quickfix list
- Xexpr ""
- call assert_equal('', g:Xgetlist({'context' : 0}).context)
- call assert_notequal(0, g:Xgetlist({'id' : 0}).id)
- call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
- call assert_equal([], g:Xgetlist({'items' : 0}).items)
- call assert_notequal(0, g:Xgetlist({'nr' : 0}).nr)
- call assert_equal(0, g:Xgetlist({'size' : 0}).size)
- call assert_notequal('', g:Xgetlist({'title' : 0}).title)
- call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- let qfid = g:Xgetlist({'id' : 0}).id
- call g:Xsetlist([], 'f')
-
- " Non-existing quickfix identifier
- call assert_equal('', g:Xgetlist({'id' : qfid, 'context' : 0}).context)
- call assert_equal(0, g:Xgetlist({'id' : qfid}).id)
- call assert_equal(0, g:Xgetlist({'id' : qfid, 'idx' : 0}).idx)
- call assert_equal([], g:Xgetlist({'id' : qfid, 'items' : 0}).items)
- call assert_equal(0, g:Xgetlist({'id' : qfid, 'nr' : 0}).nr)
- call assert_equal(0, g:Xgetlist({'id' : qfid, 'size' : 0}).size)
- 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)
- if a:cchar == 'c'
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
- \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
- \ 'qfbufnr' : qfbufnr, 'quickfixtextfunc' : '',
- \ '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, 'qfbufnr' : 0,
- \ 'quickfixtextfunc' : ''},
- \ g:Xgetlist({'id' : qfid, 'all' : 0}))
- endif
-
- " Non-existing quickfix list number
- call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context)
- call assert_equal(0, g:Xgetlist({'nr' : 5}).nr)
- call assert_equal(0, g:Xgetlist({'nr' : 5, 'idx' : 0}).idx)
- call assert_equal([], g:Xgetlist({'nr' : 5, 'items' : 0}).items)
- call assert_equal(0, g:Xgetlist({'nr' : 5, 'id' : 0}).id)
- call assert_equal(0, g:Xgetlist({'nr' : 5, 'size' : 0}).size)
- 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)
- if a:cchar == 'c'
- call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
- \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
- \ 'changedtick' : 0, 'qfbufnr' : qfbufnr,
- \ 'quickfixtextfunc' : ''}, 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, 'qfbufnr' : 0,
- \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0}))
- endif
-endfunc
-
-func Test_empty_list_quickfixtextfunc()
- " This was crashing. Can only reproduce by running it in a separate Vim
- " instance.
- let lines =<< trim END
- func s:Func(o)
- cgetexpr '0'
- endfunc
- cope
- let &quickfixtextfunc = 's:Func'
- cgetfile [ex
- END
- call writefile(lines, 'Xquickfixtextfunc')
- call RunVim([], [], '-e -s -S Xquickfixtextfunc -c qa')
- call delete('Xquickfixtextfunc')
-endfunc
-
-func Test_getqflist()
- call Xgetlist_empty_tests('c')
- 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)
-
- call g:Xsetlist([], 'f')
-
- Xexpr "F1:10:Line10"
- let qfid = g:Xgetlist({'id' : 0}).id
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
- Xaddexpr "F2:20:Line20\nF2:21:Line21"
- call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
- call g:Xsetlist([], 'a', {'lines' : ["F3:30:Line30", "F3:31:Line31"]})
- call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
- call g:Xsetlist([], 'r', {'lines' : ["F4:40:Line40"]})
- call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
- call g:Xsetlist([], 'a', {'title' : 'New Title'})
- call assert_equal(5, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- enew!
- call append(0, ["F5:50:L50", "F6:60:L60"])
- Xaddbuffer
- call assert_equal(6, g:Xgetlist({'changedtick' : 0}).changedtick)
- enew!
-
- call g:Xsetlist([], 'a', {'context' : {'bus' : 'pci'}})
- call assert_equal(7, g:Xgetlist({'changedtick' : 0}).changedtick)
- call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
- \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'a')
- call assert_equal(8, g:Xgetlist({'changedtick' : 0}).changedtick)
- call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
- \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], ' ')
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
- call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
- \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r')
- call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- if isdirectory("Xone")
- call delete("Xone", 'rf')
- endif
- call writefile(["F8:80:L80", "F8:81:L81"], "Xone")
- Xfile Xone
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
- Xaddfile Xone
- call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- " Test case for updating a non-current quickfix list
- call g:Xsetlist([], 'f')
- Xexpr "F1:1:L1"
- Xexpr "F2:2:L2"
- call g:Xsetlist([], 'a', {'nr' : 1, "lines" : ["F10:10:L10"]})
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
- call assert_equal(2, g:Xgetlist({'nr' : 1, 'changedtick' : 0}).changedtick)
-
- call delete("Xone")
-endfunc
-
-func Test_qf_tick()
- call Xqftick_tests('c')
- 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
- augroup QF_Test
- au!
- au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bw
- augroup END
- lbuffer
- augroup QF_Test
- au!
- augroup END
-endfunc
-
-" The following test used to crash vim
-func Test_lexpr_crash()
- augroup QF_Test
- au!
- au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f')
- augroup END
- lexpr ""
- 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
-func Test_lvimgrep_crash()
- sv Xtest
- augroup QF_Test
- au!
- au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f')
- augroup END
- lvimgrep quickfix test_quickfix.vim
- augroup QF_Test
- au!
- augroup END
-
- new | only
- augroup QF_Test
- au!
- au BufEnter * call setloclist(0, [], 'r')
- augroup END
- call assert_fails('lvimgrep Test_lvimgrep_crash *', 'E926:')
- augroup QF_Test
- au!
- augroup END
-
- enew | only
-endfunc
-
-func Test_lvimgrep_crash2()
- au BufNewFile x sfind
- call assert_fails('lvimgrep x x', 'E471:')
- call assert_fails('lvimgrep x x x', 'E471:')
-
- au! BufNewFile
-endfunc
-
-" 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
-" :vimgrep/:lvimgrep commands are running.
-func Test_vimgrep_autocmd()
- call setqflist([], 'f')
- call writefile(['stars'], 'Xtest1.txt')
- call writefile(['stars'], 'Xtest2.txt')
-
- " Test 1:
- " When searching for a pattern using :vimgrep, if the quickfix list is
- " changed by an autocmd, the results should be added to the correct quickfix
- " list.
- autocmd BufRead Xtest2.txt cexpr '' | cexpr ''
- silent vimgrep stars Xtest*.txt
- call assert_equal(1, getqflist({'nr' : 0}).nr)
- call assert_equal(3, getqflist({'nr' : '$'}).nr)
- call assert_equal('Xtest2.txt', bufname(getqflist()[1].bufnr))
- au! BufRead Xtest2.txt
-
- " Test 2:
- " When searching for a pattern using :vimgrep, if the quickfix list is
- " freed, then a error should be given.
- silent! %bwipe!
- call setqflist([], 'f')
- autocmd BufRead Xtest2.txt for i in range(10) | cexpr '' | endfor
- call assert_fails('vimgrep stars Xtest*.txt', 'E925:')
- au! BufRead Xtest2.txt
-
- " Test 3:
- " When searching for a pattern using :lvimgrep, if the location list is
- " freed, then the command should error out.
- silent! %bwipe!
- let g:save_winid = win_getid()
- autocmd BufRead Xtest2.txt call setloclist(g:save_winid, [], 'f')
- call assert_fails('lvimgrep stars Xtest*.txt', 'E926:')
- au! BufRead Xtest2.txt
-
- call delete('Xtest1.txt')
- call delete('Xtest2.txt')
- call setqflist([], 'f')
-endfunc
-
-" Test for an autocmd changing the current directory when running vimgrep
-func Xvimgrep_autocmd_cd(cchar)
- call s:setup_commands(a:cchar)
-
- %bwipe
- let save_cwd = getcwd()
-
- augroup QF_Test
- au!
- autocmd BufRead * silent cd %:p:h
- augroup END
-
- 10Xvimgrep /vim/ Xdir/**
- let l = g:Xgetlist()
- call assert_equal('f1.txt', bufname(l[0].bufnr))
- call assert_equal('f2.txt', fnamemodify(bufname(l[2].bufnr), ':t'))
-
- augroup QF_Test
- au!
- augroup END
-
- exe 'cd ' . save_cwd
-endfunc
-
-func Test_vimgrep_autocmd_cd()
- call mkdir('Xdir/a', 'p')
- call mkdir('Xdir/b', 'p')
- call writefile(['a_L1_vim', 'a_L2_vim'], 'Xdir/a/f1.txt')
- call writefile(['b_L1_vim', 'b_L2_vim'], 'Xdir/b/f2.txt')
- call Xvimgrep_autocmd_cd('c')
- call Xvimgrep_autocmd_cd('l')
- %bwipe
- call delete('Xdir', 'rf')
-endfunc
-
-" The following test used to crash Vim
-func Test_lhelpgrep_autocmd()
- lhelpgrep quickfix
- augroup QF_Test
- au!
- autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
- augroup END
- 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)
- augroup QF_Test
- au!
- augroup END
-
- new | only
- augroup QF_Test
- au!
- au BufEnter * call setqflist([], 'f')
- augroup END
- call assert_fails('helpgrep quickfix', 'E925:')
- " run the test with a help window already open
- help
- wincmd w
- call assert_fails('helpgrep quickfix', 'E925:')
- augroup QF_Test
- au!
- augroup END
-
- new | only
- augroup QF_Test
- au!
- au BufEnter * call setqflist([], 'r')
- augroup END
- call assert_fails('helpgrep quickfix', 'E925:')
- augroup QF_Test
- au!
- augroup END
-
- new | only
- augroup QF_Test
- au!
- au BufEnter * call setloclist(0, [], 'r')
- augroup END
- call assert_fails('lhelpgrep quickfix', 'E926:')
- augroup QF_Test
- au!
- augroup END
-
- " Replace the contents of a help window location list when it is still in
- " use.
- new | only
- lhelpgrep quickfix
- wincmd w
- augroup QF_Test
- au!
- autocmd WinEnter * call setloclist(0, [], 'r')
- augroup END
- call assert_fails('lhelpgrep win_getid', 'E926:')
- augroup QF_Test
- au!
- augroup END
-
- %bw!
-endfunc
-
-" The following test used to crash Vim
-func Test_lhelpgrep_autocmd_free_loclist()
- %bw!
- lhelpgrep quickfix
- wincmd w
- augroup QF_Test
- au!
- autocmd WinEnter * call setloclist(0, [], 'f')
- augroup END
- lhelpgrep win_getid
- wincmd w
- wincmd w
- wincmd w
- augroup QF_Test
- au!
- augroup END
- %bw!
-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'))
- " Add a few entries for the same file with different paths and check whether
- " the buffer name is shortened
- %bwipe
- call setqflist([], 'f')
- call setqflist([{'filename' : 'test_quickfix.vim', 'lnum' : 10},
- \ {'filename' : '../testdir/test_quickfix.vim', 'lnum' : 20},
- \ {'filename' : fname, 'lnum' : 30}], ' ')
- copen
- call assert_equal(['test_quickfix.vim|10| ',
- \ 'test_quickfix.vim|20| ',
- \ 'test_quickfix.vim|30| '], getline(1, '$'))
- cclose
-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
-
- " Switching to another quickfix list in one tab page should update the
- " quickfix window title and statusline in all the other tab pages also
- call setqflist([], 'f')
- %bw!
- cgetexpr ['file_one:1:1: error in the first quickfix list']
- call setqflist([], 'a', {'title': 'first quickfix list'})
- cgetexpr ['file_two:2:1: error in the second quickfix list']
- call setqflist([], 'a', {'title': 'second quickfix list'})
- copen
- wincmd t
- tabnew two
- copen
- wincmd t
- colder
- call assert_equal('first quickfix list', gettabwinvar(1, 2, 'quickfix_title'))
- call assert_equal('first quickfix list', gettabwinvar(2, 2, 'quickfix_title'))
- call assert_equal(1, tabpagewinnr(1))
- call assert_equal(1, tabpagewinnr(2))
- tabnew
- call setqflist([], 'a', {'title': 'new quickfix title'})
- call assert_equal('new quickfix title', gettabwinvar(1, 2, 'quickfix_title'))
- call assert_equal('new quickfix title', gettabwinvar(2, 2, 'quickfix_title'))
- %bw!
-endfunc
-
-func Test_lbuffer_with_bwipe()
- new
- new
- augroup nasty
- au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bwipe
- augroup END
- lbuffer
- augroup nasty
- au!
- augroup END
-endfunc
-
-" Test for an autocmd freeing the quickfix/location list when cexpr/lexpr is
-" running
-func Xexpr_acmd_freelist(cchar)
- call s:setup_commands(a:cchar)
-
- " This was using freed memory (but with what events?)
- augroup nasty
- au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call g:Xsetlist([], 'f')
- augroup END
- Xexpr "x"
- augroup nasty
- au!
- augroup END
-endfunc
-
-func Test_cexpr_acmd_freelist()
- call Xexpr_acmd_freelist('c')
- call Xexpr_acmd_freelist('l')
-endfunc
-
-" Test for commands that create a new quickfix/location list and jump to the
-" first error automatically.
-func Xjumpto_first_error_test(cchar)
- call s:setup_commands(a:cchar)
-
- call s:create_test_file('Xtestfile1')
- call s:create_test_file('Xtestfile2')
- let l = ['Xtestfile1:2:Line2', 'Xtestfile2:4:Line4']
-
- " Test for cexpr/lexpr
- enew
- Xexpr l
- call assert_equal('Xtestfile1', @%)
- call assert_equal(2, line('.'))
-
- " Test for cfile/lfile
- enew
- call writefile(l, 'Xerr')
- Xfile Xerr
- call assert_equal('Xtestfile1', @%)
- call assert_equal(2, line('.'))
-
- " Test for cbuffer/lbuffer
- edit Xerr
- Xbuffer
- call assert_equal('Xtestfile1', @%)
- call assert_equal(2, line('.'))
-
- call delete('Xerr')
- call delete('Xtestfile1')
- call delete('Xtestfile2')
-endfunc
-
-func Test_jumpto_first_error()
- call Xjumpto_first_error_test('c')
- call Xjumpto_first_error_test('l')
-endfunc
-
-" Test for a quickfix autocmd changing the quickfix/location list before
-" jumping to the first error in the new list.
-func Xautocmd_changelist(cchar)
- call s:setup_commands(a:cchar)
-
- " Test for cfile/lfile
- call s:create_test_file('Xtestfile1')
- call s:create_test_file('Xtestfile2')
- Xexpr 'Xtestfile1:2:Line2'
- autocmd QuickFixCmdPost * Xolder
- call writefile(['Xtestfile2:4:Line4'], 'Xerr')
- Xfile Xerr
- call assert_equal('Xtestfile2', @%)
- call assert_equal(4, line('.'))
- autocmd! QuickFixCmdPost
-
- " Test for cbuffer/lbuffer
- call g:Xsetlist([], 'f')
- Xexpr 'Xtestfile1:2:Line2'
- autocmd QuickFixCmdPost * Xolder
- call writefile(['Xtestfile2:4:Line4'], 'Xerr')
- edit Xerr
- Xbuffer
- call assert_equal('Xtestfile2', @%)
- call assert_equal(4, line('.'))
- autocmd! QuickFixCmdPost
-
- " Test for cexpr/lexpr
- call g:Xsetlist([], 'f')
- Xexpr 'Xtestfile1:2:Line2'
- autocmd QuickFixCmdPost * Xolder
- Xexpr 'Xtestfile2:4:Line4'
- call assert_equal('Xtestfile2', @%)
- call assert_equal(4, line('.'))
- autocmd! QuickFixCmdPost
-
- " The grepprg may not be set on non-Unix systems
- if has('unix')
- " Test for grep/lgrep
- call g:Xsetlist([], 'f')
- Xexpr 'Xtestfile1:2:Line2'
- autocmd QuickFixCmdPost * Xolder
- silent Xgrep Line5 Xtestfile2
- call assert_equal('Xtestfile2', @%)
- call assert_equal(5, line('.'))
- autocmd! QuickFixCmdPost
- endif
-
- " Test for vimgrep/lvimgrep
- call g:Xsetlist([], 'f')
- Xexpr 'Xtestfile1:2:Line2'
- autocmd QuickFixCmdPost * Xolder
- silent Xvimgrep Line5 Xtestfile2
- call assert_equal('Xtestfile2', @%)
- call assert_equal(5, line('.'))
- autocmd! QuickFixCmdPost
-
- " Test for autocommands clearing the quickfix list before jumping to the
- " first error. This should not result in an error
- autocmd QuickFixCmdPost * call g:Xsetlist([], 'r')
- let v:errmsg = ''
- " Test for cfile/lfile
- Xfile Xerr
- call assert_true(v:errmsg !~# 'E42:')
- " Test for cbuffer/lbuffer
- edit Xerr
- Xbuffer
- call assert_true(v:errmsg !~# 'E42:')
- " Test for cexpr/lexpr
- Xexpr 'Xtestfile2:4:Line4'
- call assert_true(v:errmsg !~# 'E42:')
- " Test for grep/lgrep
- " The grepprg may not be set on non-Unix systems
- if has('unix')
- silent Xgrep Line5 Xtestfile2
- call assert_true(v:errmsg !~# 'E42:')
- endif
- " Test for vimgrep/lvimgrep
- call assert_fails('silent Xvimgrep Line5 Xtestfile2', 'E480:')
- autocmd! QuickFixCmdPost
-
- call delete('Xerr')
- call delete('Xtestfile1')
- call delete('Xtestfile2')
-endfunc
-
-func Test_autocmd_changelist()
- call Xautocmd_changelist('c')
- 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)
-
- " Test that "CTRL-W <CR>" in a qf/ll window fails with empty list.
- call g:Xsetlist([])
- Xopen
- let l:win_count = winnr('$')
- call assert_fails('execute "normal! \<C-W>\<CR>"', 'E42')
- call assert_equal(l:win_count, winnr('$'))
- Xclose
-endfunc
-
-func Test_view_result_split()
- call Xview_result_split_tests('c')
- call Xview_result_split_tests('l')
-endfunc
-
-" Test that :cc sets curswant
-func Test_curswant()
- helpgrep quickfix
- normal! llll
- 1cc
- call assert_equal(getcurpos()[4], virtcol('.'))
- cclose | helpclose
-endfunc
-
-" Test for opening a file from the quickfix window using CTRL-W <Enter>
-" doesn't leave an empty buffer around.
-func Test_splitview()
- call s:create_test_file('Xtestfile1')
- call s:create_test_file('Xtestfile2')
- new | only
- let last_bufnr = bufnr('Test_sv_1', 1)
- let l = ['Xtestfile1:2:Line2', 'Xtestfile2:4:Line4']
- cgetexpr l
- copen
- let numbufs = len(getbufinfo())
- exe "normal \<C-W>\<CR>"
- copen
- exe "normal j\<C-W>\<CR>"
- " Make sure new empty buffers are not created
- call assert_equal(numbufs, len(getbufinfo()))
- " Creating a new buffer should use the next available buffer number
- call assert_equal(last_bufnr + 4, bufnr("Test_sv_2", 1))
- bwipe Test_sv_1
- bwipe Test_sv_2
- new | only
-
- " When split opening files from location list window, make sure that two
- " windows doesn't refer to the same location list
- lgetexpr l
- let locid = getloclist(0, {'id' : 0}).id
- lopen
- exe "normal \<C-W>\<CR>"
- call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
- call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
- new | only
-
- " When split opening files from a helpgrep location list window, a new help
- " window should be opened with a copy of the location list.
- lhelpgrep window
- let locid = getloclist(0, {'id' : 0}).id
- lwindow
- exe "normal j\<C-W>\<CR>"
- call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
- call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
- new | only
-
- " Using :split or :vsplit from a quickfix window should behave like a :new
- " or a :vnew command
- copen
- split
- call assert_equal(3, winnr('$'))
- let l = getwininfo()
- call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix])
- close
- copen
- vsplit
- let l = getwininfo()
- call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix])
- new | only
-
- call delete('Xtestfile1')
- call delete('Xtestfile2')
-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('.')])
-
- " Use screen column number with a multi-line error message
- enew
- call writefile(["à test"], 'Xfile1')
- set efm=%E===\ %f\ ===,%C%l:%v,%Z%m
- cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
- call assert_equal('Xfile1', @%)
- call assert_equal([0, 1, 4, 0], getpos('.'))
-
- " Repeat previous test with byte offset %c: ensure that fix to issue #7145
- " does not break this
- set efm=%E===\ %f\ ===,%C%l:%c,%Z%m
- cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
- call assert_equal('Xfile1', @%)
- call assert_equal([0, 1, 3, 0], getpos('.'))
-
- enew | only
- set efm&
- call delete('Xfile1')
-endfunc
-
-" Test for the quickfix window buffer
-func Xqfbuf_test(cchar)
- call s:setup_commands(a:cchar)
-
- " Quickfix buffer should be reused across closing and opening a quickfix
- " window
- Xexpr "F1:10:Line10"
- Xopen
- let qfbnum = bufnr('')
- Xclose
- " Even after the quickfix window is closed, the buffer should be loaded
- call assert_true(bufloaded(qfbnum))
- call assert_true(qfbnum, g:Xgetlist({'qfbufnr' : 0}).qfbufnr)
- Xopen
- " Buffer should be reused when opening the window again
- call assert_equal(qfbnum, bufnr(''))
- Xclose
-
- " When quickfix buffer is wiped out, getqflist() should return 0
- %bw!
- Xexpr ""
- Xopen
- bw!
- call assert_equal(0, g:Xgetlist({'qfbufnr': 0}).qfbufnr)
-
- if a:cchar == 'l'
- %bwipe
- " For a location list, when both the file window and the location list
- " window for the list are closed, then the buffer should be freed.
- new | only
- lexpr "F1:10:Line10"
- let wid = win_getid()
- lopen
- let qfbnum = bufnr('')
- call assert_match(qfbnum . ' %a- "\[Location List]"', execute('ls'))
- close
- " When the location list window is closed, the buffer name should not
- " change to 'Quickfix List'
- call assert_match(qfbnum . 'u h- "\[Location List]"', execute('ls!'))
- call assert_true(bufloaded(qfbnum))
-
- " After deleting a location list buffer using ":bdelete", opening the
- " location list window should mark the buffer as a location list buffer.
- exe "bdelete " . qfbnum
- lopen
- call assert_equal("quickfix", &buftype)
- call assert_equal(1, getwininfo(win_getid(winnr()))[0].loclist)
- call assert_equal(wid, getloclist(0, {'filewinid' : 0}).filewinid)
- call assert_false(&swapfile)
- lclose
-
- " When the location list is cleared for the window, the buffer should be
- " removed
- call setloclist(0, [], 'f')
- call assert_false(bufexists(qfbnum))
- call assert_equal(0, getloclist(0, {'qfbufnr' : 0}).qfbufnr)
-
- " When the location list is freed with the location list window open, the
- " location list buffer should not be lost. It should be reused when the
- " location list is again populated.
- lexpr "F1:10:Line10"
- lopen
- let wid = win_getid()
- let qfbnum = bufnr('')
- wincmd p
- call setloclist(0, [], 'f')
- lexpr "F1:10:Line10"
- lopen
- call assert_equal(wid, win_getid())
- call assert_equal(qfbnum, bufnr(''))
- lclose
-
- " When the window with the location list is closed, the buffer should be
- " removed
- new | only
- call assert_false(bufexists(qfbnum))
- endif
-endfunc
-
-func Test_qfbuf()
- call Xqfbuf_test('c')
- call Xqfbuf_test('l')
-endfunc
-
-" If there is an autocmd to use only one window, then opening the location
-" list window used to crash Vim.
-func Test_winonly_autocmd()
- call s:create_test_file('Xtest1')
- " Autocmd to show only one Vim window at a time
- autocmd WinEnter * only
- new
- " Load the location list
- lexpr "Xtest1:5:Line5\nXtest1:10:Line10\nXtest1:15:Line15"
- let loclistid = getloclist(0, {'id' : 0}).id
- " Open the location list window. Only this window will be shown and the file
- " window is closed.
- lopen
- call assert_equal(loclistid, getloclist(0, {'id' : 0}).id)
- " Jump to an entry in the location list and make sure that the cursor is
- " positioned correctly.
- ll 3
- call assert_equal(loclistid, getloclist(0, {'id' : 0}).id)
- call assert_equal('Xtest1', @%)
- call assert_equal(15, line('.'))
- " Cleanup
- autocmd! WinEnter
- new | only
- call delete('Xtest1')
-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.
-" And for the :cafter, :cbefore, :lafter and :lbefore 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:')
- call assert_fails('Xbefore', 'E42:')
- call assert_fails('Xafter', 'E42:')
-
- " Empty quickfix/location list
- call g:Xsetlist([])
- call assert_fails('Xbelow', 'E42:')
- call assert_fails('Xabove', 'E42:')
- call assert_fails('Xbefore', 'E42:')
- call assert_fails('Xafter', '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:')
- call assert_fails('Xbefore', 'E42:')
- call assert_fails('Xafter', 'E42:')
- call assert_fails('3Xbefore', 'E42:')
- call assert_fails('4Xafter', 'E42:')
-
- " Test the commands with various arguments
- Xexpr ["X1:5:3:L5", "X2:5:2:L5", "X2:10:3:L10", "X2:15:4:L15", "X3:3:5:L3"]
- edit +7 X2
- Xabove
- call assert_equal(['X2', 5], [@%, line('.')])
- call assert_fails('Xabove', 'E553:')
- normal 7G
- Xbefore
- call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')])
- call assert_fails('Xbefore', 'E553:')
-
- normal 2j
- Xbelow
- call assert_equal(['X2', 10], [@%, line('.')])
- normal 7G
- Xafter
- call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')])
-
- " Last error in this file
- Xbelow 99
- call assert_equal(['X2', 15], [@%, line('.')])
- call assert_fails('Xbelow', 'E553:')
- normal gg
- Xafter 99
- call assert_equal(['X2', 15, 4], [@%, line('.'), col('.')])
- call assert_fails('Xafter', 'E553:')
-
- " First error in this file
- Xabove 99
- call assert_equal(['X2', 5], [@%, line('.')])
- call assert_fails('Xabove', 'E553:')
- normal G
- Xbefore 99
- call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')])
- call assert_fails('Xbefore', 'E553:')
-
- normal gg
- Xbelow 2
- call assert_equal(['X2', 10], [@%, line('.')])
- normal gg
- Xafter 2
- call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')])
-
- normal G
- Xabove 2
- call assert_equal(['X2', 10], [@%, line('.')])
- normal G
- Xbefore 2
- call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')])
-
- edit X4
- call assert_fails('Xabove', 'E42:')
- call assert_fails('Xbelow', 'E42:')
- call assert_fails('Xbefore', 'E42:')
- call assert_fails('Xafter', '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:')
- call assert_fails('Xbefore', 'E776:')
- call assert_fails('Xafter', 'E776:')
- close
- endif
-
- " Test for lines with multiple quickfix entries
- let lines =<< trim END
- 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
- END
- Xexpr lines
- edit +1 X2
- Xbelow 2
- call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')])
- normal 1G
- Xafter 2
- call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')])
-
- normal gg
- Xbelow 99
- call assert_equal(['X2', 15, 1], [@%, line('.'), col('.')])
- normal gg
- Xafter 99
- call assert_equal(['X2', 15, 3], [@%, line('.'), col('.')])
-
- normal G
- Xabove 2
- call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')])
- normal G
- Xbefore 2
- call assert_equal(['X2', 15, 2], [@%, line('.'), col('.')])
-
- normal G
- Xabove 99
- call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')])
- normal G
- Xbefore 99
- call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')])
-
- normal 10G
- Xabove
- call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')])
- normal 10G$
- 2Xbefore
- call assert_equal(['X2', 10, 2], [@%, line('.'), col('.')])
-
- normal 10G
- Xbelow
- call assert_equal(['X2', 15, 1], [@%, line('.'), col('.')])
- normal 9G
- 5Xafter
- call assert_equal(['X2', 15, 2], [@%, line('.'), col('.')])
-
- " Invalid range
- if a:cchar == 'c'
- call assert_fails('-2cbelow', 'E16:')
- call assert_fails('-2cafter', 'E16:')
- else
- call assert_fails('-2lbelow', 'E16:')
- call assert_fails('-2lafter', 'E16:')
- 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
-
-func Test_quickfix_count()
- let commands =<< trim END
- cNext
- cNfile
- cabove
- cbelow
- cfirst
- clast
- cnewer
- cnext
- cnfile
- colder
- cprevious
- crewind
- lNext
- lNfile
- labove
- lbelow
- lfirst
- llast
- lnewer
- lnext
- lnfile
- lolder
- lprevious
- lrewind
- END
- for cmd in commands
- call assert_fails('-1' .. cmd, 'E16:')
- call assert_fails('.' .. cmd, 'E16:')
- call assert_fails('%' .. cmd, 'E16:')
- call assert_fails('$' .. cmd, 'E16:')
- endfor
-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
-
-" Test for using a file in one of the parent directories.
-func Test_search_in_dirstack()
- call mkdir('Xtestdir/a/b/c', 'p')
- let save_cwd = getcwd()
- call writefile(["X1_L1", "X1_L2"], 'Xtestdir/Xfile1')
- call writefile(["X2_L1", "X2_L2"], 'Xtestdir/a/Xfile2')
- call writefile(["X3_L1", "X3_L2"], 'Xtestdir/a/b/Xfile3')
- call writefile(["X4_L1", "X4_L2"], 'Xtestdir/a/b/c/Xfile4')
-
- let lines = "Entering dir Xtestdir\n" .
- \ "Entering dir a\n" .
- \ "Entering dir b\n" .
- \ "Xfile2:2:X2_L2\n" .
- \ "Leaving dir a\n" .
- \ "Xfile1:2:X1_L2\n" .
- \ "Xfile3:1:X3_L1\n" .
- \ "Entering dir c\n" .
- \ "Xfile4:2:X4_L2\n" .
- \ "Leaving dir c\n"
- set efm=%DEntering\ dir\ %f,%XLeaving\ dir\ %f,%f:%l:%m
- cexpr lines .. "Leaving dir Xtestdir|\n" | let next = 1
- call assert_equal(11, getqflist({'size' : 0}).size)
- call assert_equal(4, getqflist({'idx' : 0}).idx)
- call assert_equal('X2_L2', getline('.'))
- call assert_equal(1, next)
- cnext
- call assert_equal(6, getqflist({'idx' : 0}).idx)
- call assert_equal('X1_L2', getline('.'))
- cnext
- call assert_equal(7, getqflist({'idx' : 0}).idx)
- call assert_equal(1, line('$'))
- call assert_equal('', getline(1))
- cnext
- call assert_equal(9, getqflist({'idx' : 0}).idx)
- call assert_equal(1, line('$'))
- call assert_equal('', getline(1))
-
- set efm&
- exe 'cd ' . save_cwd
- call delete('Xtestdir', 'rf')
-endfunc
-
-" Test for :cquit
-func Test_cquit()
- " Exit Vim with a non-zero value
- if RunVim([], ["cquit 7"], '')
- call assert_equal(7, v:shell_error)
- endif
-
- if RunVim([], ["50cquit"], '')
- call assert_equal(50, v:shell_error)
- endif
-
- " Exit Vim with default value
- if RunVim([], ["cquit"], '')
- call assert_equal(1, v:shell_error)
- endif
-
- " Exit Vim with zero value
- if RunVim([], ["cquit 0"], '')
- call assert_equal(0, v:shell_error)
- endif
-
- " Exit Vim with negative value
- call assert_fails('-3cquit', 'E16:')
-endfunc
-
-" Running :lhelpgrep command more than once in a help window, doesn't jump to
-" the help topic
-func Test_lhelpgrep_from_help_window()
- call mkdir('Xtestdir/doc', 'p')
- call writefile(['window'], 'Xtestdir/doc/a.txt')
- call writefile(['buffer'], 'Xtestdir/doc/b.txt')
- let save_rtp = &rtp
- let &rtp = 'Xtestdir'
- lhelpgrep window
- lhelpgrep buffer
- call assert_equal('b.txt', fnamemodify(@%, ":p:t"))
- lhelpgrep window
- call assert_equal('a.txt', fnamemodify(@%, ":p:t"))
- let &rtp = save_rtp
- call delete('Xtestdir', 'rf')
- new | only!
-endfunc
-
-" Test for the crash fixed by 7.3.715
-func Test_setloclist_crash()
- %bw!
- let g:BufNum = bufnr()
- augroup QF_Test
- au!
- au BufUnload * call setloclist(0, [{'bufnr':g:BufNum, 'lnum':1, 'col':1, 'text': 'tango down'}])
- augroup END
-
- try
- lvimgrep /.*/ *.mak
- catch /E926:/
- endtry
- call assert_equal('tango down', getloclist(0, {'items' : 0}).items[0].text)
- call assert_equal(1, getloclist(0, {'size' : 0}).size)
-
- augroup QF_Test
- au!
- augroup END
- unlet g:BufNum
- %bw!
-endfunc
-
-" Test for adding an invalid entry with the quickfix window open and making
-" sure that the window contents are not changed
-func Test_add_invalid_entry_with_qf_window()
- call setqflist([], 'f')
- cexpr "Xfile1:10:aa"
- copen
- call setqflist(['bb'], 'a')
- call assert_equal(1, line('$'))
- call assert_equal(['Xfile1|10| aa'], getline(1, '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10 col 666-222| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10-6 col 666-222| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
- cclose
-endfunc
-
-" Test for very weird problem: autocommand causes a failure, resulting opening
-" the quickfix window to fail. This still splits the window, but otherwise
-" should not mess up buffers.
-func Test_quickfix_window_fails_to_open()
- CheckScreendump
-
- let lines =<< trim END
- anything
- try
- anything
- endtry
- END
- call writefile(lines, 'XquickfixFails')
-
- let lines =<< trim END
- split XquickfixFails
- silent vimgrep anything %
- normal o
- au BufLeave * ++once source XquickfixFails
- " This will trigger the autocommand, which causes an error, what follows
- " is aborted but the window was already split.
- silent! cwindow
- END
- call writefile(lines, 'XtestWinFails')
- let buf = RunVimInTerminal('-S XtestWinFails', #{rows: 13})
- call VerifyScreenDump(buf, 'Test_quickfix_window_fails', {})
-
- " clean up
- call term_sendkeys(buf, ":bwipe!\<CR>")
- call term_wait(buf)
- call StopVimInTerminal(buf)
- call delete('XtestWinFails')
- call delete('XquickfixFails')
-endfunc
-
-" Test for updating the quickfix buffer whenever the associated quickfix list
-" is changed.
-func Xqfbuf_update(cchar)
- call s:setup_commands(a:cchar)
-
- Xexpr "F1:1:line1"
- Xopen
- call assert_equal(['F1|1| line1'], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- " Test setqflist() using the 'lines' key in 'what'
- " add a new entry
- call g:Xsetlist([], 'a', {'lines' : ['F2:2: line2']})
- call assert_equal(['F1|1| line1', 'F2|2| line2'], getline(1, '$'))
- call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
- " replace all the entries with a single entry
- call g:Xsetlist([], 'r', {'lines' : ['F3:3: line3']})
- call assert_equal(['F3|3| line3'], getline(1, '$'))
- call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
- " remove all the entries
- call g:Xsetlist([], 'r', {'lines' : []})
- call assert_equal([''], getline(1, '$'))
- call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
- " add a new list
- call g:Xsetlist([], ' ', {'lines' : ['F4:4: line4']})
- call assert_equal(['F4|4| line4'], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- " Test setqflist() using the 'items' key in 'what'
- " add a new entry
- call g:Xsetlist([], 'a', {'items' : [{'filename' : 'F5', 'lnum' : 5, 'text' : 'line5'}]})
- call assert_equal(['F4|4| line4', 'F5|5| line5'], getline(1, '$'))
- call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
- " replace all the entries with a single entry
- call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F6', 'lnum' : 6, 'text' : 'line6'}]})
- call assert_equal(['F6|6| line6'], getline(1, '$'))
- call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
- " remove all the entries
- call g:Xsetlist([], 'r', {'items' : []})
- call assert_equal([''], getline(1, '$'))
- call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
- " add a new list
- call g:Xsetlist([], ' ', {'items' : [{'filename' : 'F7', 'lnum' : 7, 'text' : 'line7'}]})
- call assert_equal(['F7|7| line7'], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- call g:Xsetlist([], ' ', {})
- call assert_equal([''], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- Xclose
-endfunc
-
-func Test_qfbuf_update()
- call Xqfbuf_update('c')
- call Xqfbuf_update('l')
-endfunc
-
-" Test for the :vimgrep 'f' flag (fuzzy match)
-func Xvimgrep_fuzzy_match(cchar)
- call s:setup_commands(a:cchar)
-
- Xvimgrep /three one/f Xfile*
- let l = g:Xgetlist()
- call assert_equal(2, len(l))
- call assert_equal(['Xfile1', 1, 9, 'one two three'],
- \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
- call assert_equal(['Xfile2', 2, 1, 'three one two'],
- \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
-
- Xvimgrep /the/f Xfile*
- let l = g:Xgetlist()
- call assert_equal(3, len(l))
- call assert_equal(['Xfile1', 1, 9, 'one two three'],
- \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
- call assert_equal(['Xfile2', 2, 1, 'three one two'],
- \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
- call assert_equal(['Xfile2', 4, 4, 'aaathreeaaa'],
- \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
-
- Xvimgrep /aaa/fg Xfile*
- let l = g:Xgetlist()
- call assert_equal(4, len(l))
- call assert_equal(['Xfile1', 2, 1, 'aaaaaa'],
- \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
- call assert_equal(['Xfile1', 2, 4, 'aaaaaa'],
- \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
- call assert_equal(['Xfile2', 4, 1, 'aaathreeaaa'],
- \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
- call assert_equal(['Xfile2', 4, 9, 'aaathreeaaa'],
- \ [bufname(l[3].bufnr), l[3].lnum, l[3].col, l[3].text])
-
- call assert_fails('Xvimgrep /xyz/fg Xfile*', 'E480:')
-endfunc
-
-func Test_vimgrep_fuzzy_match()
- call writefile(['one two three', 'aaaaaa'], 'Xfile1')
- call writefile(['one', 'three one two', 'two', 'aaathreeaaa'], 'Xfile2')
- call Xvimgrep_fuzzy_match('c')
- call Xvimgrep_fuzzy_match('l')
- call delete('Xfile1')
- call delete('Xfile2')
-endfunc
-
-" Test for getting a specific item from a quickfix list
-func Xtest_getqflist_by_idx(cchar)
- call s:setup_commands(a:cchar)
- " Empty list
- call assert_equal([], g:Xgetlist({'idx' : 1, 'items' : 0}).items)
- Xexpr ['F1:10:L10', 'F1:20:L20']
- let l = g:Xgetlist({'idx' : 2, 'items' : 0}).items
- call assert_equal(bufnr('F1'), l[0].bufnr)
- call assert_equal(20, l[0].lnum)
- call assert_equal('L20', l[0].text)
- call assert_equal([], g:Xgetlist({'idx' : -1, 'items' : 0}).items)
- call assert_equal([], g:Xgetlist({'idx' : 3, 'items' : 0}).items)
- call assert_equal({}, g:Xgetlist(#{idx: "abc"}))
- %bwipe!
-endfunc
-
-func Test_getqflist_by_idx()
- call Xtest_getqflist_by_idx('c')
- call Xtest_getqflist_by_idx('l')
-endfunc
-
-" Test for the 'quickfixtextfunc' setting
-func Tqfexpr(info)
- if a:info.quickfix
- let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
- else
- let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
- endif
-
-
- let l = []
- for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
- let e = qfl[idx]
- let s = ''
- if e.bufnr != 0
- let bname = bufname(e.bufnr)
- let s ..= fnamemodify(bname, ':.')
- endif
- let s ..= '-'
- let s ..= 'L' .. string(e.lnum) .. 'C' .. string(e.col) .. '-'
- let s ..= e.text
- call add(l, s)
- endfor
-
- return l
-endfunc
-
-func Xtest_qftextfunc(cchar)
- call s:setup_commands(a:cchar)
-
- set efm=%f:%l:%c:%m
- set quickfixtextfunc=Tqfexpr
- call assert_equal('Tqfexpr', &quickfixtextfunc)
- call assert_equal('',
- \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
- call g:Xsetlist([
- \ { 'filename': 'F1', 'lnum': 10, 'col': 2,
- \ 'end_col': 7, 'text': 'green'},
- \ { 'filename': 'F1', 'lnum': 20, 'end_lnum': 25, 'col': 4,
- \ 'end_col': 8, 'text': 'blue'},
- \ ])
-
- Xwindow
- call assert_equal('F1-L10C2-green', getline(1))
- call assert_equal('F1-L20C4-blue', getline(2))
- Xclose
- set quickfixtextfunc&vim
- Xwindow
- call assert_equal('F1|10 col 2-7| green', getline(1))
- call assert_equal('F1|20-25 col 4-8| blue', getline(2))
- Xclose
-
- set efm=%f:%l:%c:%m
- set quickfixtextfunc=Tqfexpr
- " Update the list with only the cwindow
- Xwindow
- only
- call g:Xsetlist([
- \ { 'filename': 'F2', 'lnum': 20, 'col': 2,
- \ 'end_col': 7, 'text': 'red'}
- \ ])
- call assert_equal(['F2-L20C2-red'], getline(1, '$'))
- new
- Xclose
- set efm&
- set quickfixtextfunc&
-
- " Test for per list 'quickfixtextfunc' setting
- func PerQfText(info)
- if a:info.quickfix
- let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
- else
- let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
- endif
- if empty(qfl)
- return []
- endif
- let l = []
- for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
- call add(l, 'Line ' .. qfl[idx].lnum .. ', Col ' .. qfl[idx].col)
- endfor
- return l
- endfunc
- set quickfixtextfunc=Tqfexpr
- call g:Xsetlist([], ' ', {'quickfixtextfunc' : "PerQfText"})
- Xaddexpr ['F1:10:2:green', 'F1:20:4:blue']
- Xwindow
- call assert_equal('Line 10, Col 2', getline(1))
- call assert_equal('Line 20, Col 4', getline(2))
- Xclose
- call assert_equal(function('PerQfText'),
- \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
- " Add entries to the list when the quickfix buffer is hidden
- Xaddexpr ['F1:30:6:red']
- Xwindow
- call assert_equal('Line 30, Col 6', getline(3))
- Xclose
- call g:Xsetlist([], 'r', {'quickfixtextfunc' : ''})
- call assert_equal('', g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
- set quickfixtextfunc&
- delfunc PerQfText
-
- " Non-existing function
- set quickfixtextfunc=Tabc
- call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
- call assert_fails("Xwindow", 'E117:')
- Xclose
- set quickfixtextfunc&
-
- " set option to a non-function
- set quickfixtextfunc=[10,\ 20]
- call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
- call assert_fails("Xwindow", 'E117:')
- Xclose
- set quickfixtextfunc&
-
- " set option to a function with different set of arguments
- func Xqftext(a, b, c)
- return a:a .. a:b .. a:c
- endfunc
- set quickfixtextfunc=Xqftext
- call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:')
- call assert_fails("Xwindow", 'E119:')
- Xclose
-
- " set option to a function that returns a list with non-strings
- func Xqftext2(d)
- return ['one', [], 'two']
- endfunc
- set quickfixtextfunc=Xqftext2
- call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']",
- \ 'E730:')
- call assert_fails('Xwindow', 'E730:')
- call assert_equal(['one', 'F1|20 col 4| blue', 'F1|30 col 6| red'],
- \ getline(1, '$'))
- Xclose
-
- set quickfixtextfunc&
- delfunc Xqftext
- delfunc Xqftext2
-
- " set the global option to a lambda function
- set quickfixtextfunc={d\ ->\ map(g:Xgetlist({'id'\ :\ d.id,\ 'items'\ :\ 1}).items[d.start_idx-1:d.end_idx-1],\ 'v:val.text')}
- Xexpr ['F1:10:2:green', 'F1:20:4:blue']
- Xwindow
- call assert_equal(['green', 'blue'], getline(1, '$'))
- Xclose
- call assert_equal("{d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1], 'v:val.text')}", &quickfixtextfunc)
- set quickfixtextfunc&
-
- " use a lambda function that returns an empty list
- set quickfixtextfunc={d\ ->\ []}
- Xexpr ['F1:10:2:green', 'F1:20:4:blue']
- Xwindow
- call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'],
- \ getline(1, '$'))
- Xclose
- set quickfixtextfunc&
-
- " use a lambda function that returns a list with empty strings
- set quickfixtextfunc={d\ ->\ ['',\ '']}
- Xexpr ['F1:10:2:green', 'F1:20:4:blue']
- Xwindow
- call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'],
- \ getline(1, '$'))
- Xclose
- set quickfixtextfunc&
-
- " set the per-quickfix list text function to a lambda function
- call g:Xsetlist([], ' ',
- \ {'quickfixtextfunc' :
- \ {d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
- \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}})
- Xaddexpr ['F1:10:2:green', 'F1:20:4:blue']
- Xwindow
- call assert_equal('Line 10, Col 2', getline(1))
- call assert_equal('Line 20, Col 4', getline(2))
- Xclose
- call assert_match("function('<lambda>\\d\\+')", string(g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc))
- call g:Xsetlist([], 'f')
-endfunc
-
-func Test_qftextfunc()
- call Xtest_qftextfunc('c')
- call Xtest_qftextfunc('l')
-endfunc
-
-func Test_qftextfunc_callback()
- let lines =<< trim END
- set efm=%f:%l:%c:%m
-
- #" Test for using a function name
- LET &qftf = 'g:Tqfexpr'
- cexpr "F0:0:0:L0"
- copen
- call assert_equal('F0-L0C0-L0', getline(1))
- cclose
-
- #" Test for using a function()
- set qftf=function('g:Tqfexpr')
- cexpr "F1:1:1:L1"
- copen
- call assert_equal('F1-L1C1-L1', getline(1))
- cclose
-
- #" Using a funcref variable to set 'quickfixtextfunc'
- VAR Fn = function('g:Tqfexpr')
- LET &qftf = Fn
- cexpr "F2:2:2:L2"
- copen
- call assert_equal('F2-L2C2-L2', getline(1))
- cclose
-
- #" Using string(funcref_variable) to set 'quickfixtextfunc'
- LET Fn = function('g:Tqfexpr')
- LET &qftf = string(Fn)
- cexpr "F3:3:3:L3"
- copen
- call assert_equal('F3-L3C3-L3', getline(1))
- cclose
-
- #" Test for using a funcref()
- set qftf=funcref('g:Tqfexpr')
- cexpr "F4:4:4:L4"
- copen
- call assert_equal('F4-L4C4-L4', getline(1))
- cclose
-
- #" Using a funcref variable to set 'quickfixtextfunc'
- LET Fn = funcref('g:Tqfexpr')
- LET &qftf = Fn
- cexpr "F5:5:5:L5"
- copen
- call assert_equal('F5-L5C5-L5', getline(1))
- cclose
-
- #" Using a string(funcref_variable) to set 'quickfixtextfunc'
- LET Fn = funcref('g:Tqfexpr')
- LET &qftf = string(Fn)
- cexpr "F5:5:5:L5"
- copen
- call assert_equal('F5-L5C5-L5', getline(1))
- cclose
-
- #" Test for using a lambda function with set
- VAR optval = "LSTART a LMIDDLE Tqfexpr(a) LEND"
- LET optval = substitute(optval, ' ', '\\ ', 'g')
- exe "set qftf=" .. optval
- cexpr "F6:6:6:L6"
- copen
- call assert_equal('F6-L6C6-L6', getline(1))
- cclose
-
- #" Set 'quickfixtextfunc' to a lambda expression
- LET &qftf = LSTART a LMIDDLE Tqfexpr(a) LEND
- cexpr "F7:7:7:L7"
- copen
- call assert_equal('F7-L7C7-L7', getline(1))
- cclose
-
- #" Set 'quickfixtextfunc' to string(lambda_expression)
- LET &qftf = "LSTART a LMIDDLE Tqfexpr(a) LEND"
- cexpr "F8:8:8:L8"
- copen
- call assert_equal('F8-L8C8-L8', getline(1))
- cclose
-
- #" Set 'quickfixtextfunc' to a variable with a lambda expression
- VAR Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND
- LET &qftf = Lambda
- cexpr "F9:9:9:L9"
- copen
- call assert_equal('F9-L9C9-L9', getline(1))
- cclose
-
- #" Set 'quickfixtextfunc' to a string(variable with a lambda expression)
- LET Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND
- LET &qftf = string(Lambda)
- cexpr "F9:9:9:L9"
- copen
- call assert_equal('F9-L9C9-L9', getline(1))
- cclose
- END
- call CheckLegacyAndVim9Success(lines)
-
- " Test for using a script-local function name
- func s:TqfFunc2(info)
- let g:TqfFunc2Args = [a:info.start_idx, a:info.end_idx]
- return ''
- endfunc
- let g:TqfFunc2Args = []
- set quickfixtextfunc=s:TqfFunc2
- cexpr "F10:10:10:L10"
- cclose
- call assert_equal([1, 1], g:TqfFunc2Args)
-
- let &quickfixtextfunc = 's:TqfFunc2'
- cexpr "F11:11:11:L11"
- cclose
- call assert_equal([1, 1], g:TqfFunc2Args)
- delfunc s:TqfFunc2
-
- " set 'quickfixtextfunc' to a partial with dict. This used to cause a crash.
- func SetQftfFunc()
- let params = {'qftf': function('g:DictQftfFunc')}
- let &quickfixtextfunc = params.qftf
- endfunc
- func g:DictQftfFunc(_) dict
- endfunc
- call SetQftfFunc()
- new
- call SetQftfFunc()
- bw
- call test_garbagecollect_now()
- new
- set qftf=
- wincmd w
- set qftf=
- :%bw!
-
- " set per-quickfix list 'quickfixtextfunc' to a partial with dict. This used
- " to cause a crash.
- let &qftf = ''
- func SetLocalQftfFunc()
- let params = {'qftf': function('g:DictQftfFunc')}
- call setqflist([], 'a', {'quickfixtextfunc' : params.qftf})
- endfunc
- call SetLocalQftfFunc()
- call test_garbagecollect_now()
- call setqflist([], 'a', {'quickfixtextfunc' : ''})
- delfunc g:DictQftfFunc
- delfunc SetQftfFunc
- delfunc SetLocalQftfFunc
- set efm&
-endfunc
-
-" Test for updating a location list for some other window and check that
-" 'qftextfunc' uses the correct location list.
-func Test_qftextfunc_other_loclist()
- %bw!
- call setloclist(0, [], 'f')
-
- " create a window and a location list for it and open the location list
- " window
- lexpr ['F1:10:12:one', 'F1:20:14:two']
- let w1_id = win_getid()
- call setloclist(0, [], ' ',
- \ {'lines': ['F1:10:12:one', 'F1:20:14:two'],
- \ 'quickfixtextfunc':
- \ {d -> map(getloclist(d.winid, {'id' : d.id,
- \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
- \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}})
- lwindow
- let w2_id = win_getid()
-
- " create another window and a location list for it and open the location
- " list window
- topleft new
- let w3_id = win_getid()
- call setloclist(0, [], ' ',
- \ {'lines': ['F2:30:32:eleven', 'F2:40:34:twelve'],
- \ 'quickfixtextfunc':
- \ {d -> map(getloclist(d.winid, {'id' : d.id,
- \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
- \ "'Ligne ' .. v:val.lnum .. ', Colonne ' .. v:val.col")}})
- lwindow
- let w4_id = win_getid()
-
- topleft new
- lexpr ['F3:50:52:green', 'F3:60:54:blue']
- let w5_id = win_getid()
-
- " change the location list for some other window
- call setloclist(0, [], 'r', {'lines': ['F3:55:56:aaa', 'F3:57:58:bbb']})
- call setloclist(w1_id, [], 'r', {'lines': ['F1:62:63:bbb', 'F1:64:65:ccc']})
- call setloclist(w3_id, [], 'r', {'lines': ['F2:76:77:ddd', 'F2:78:79:eee']})
- call assert_equal(['Line 62, Col 63', 'Line 64, Col 65'],
- \ getbufline(winbufnr(w2_id), 1, '$'))
- call assert_equal(['Ligne 76, Colonne 77', 'Ligne 78, Colonne 79'],
- \ getbufline(winbufnr(w4_id), 1, '$'))
- call setloclist(w2_id, [], 'r', {'lines': ['F1:32:33:fff', 'F1:34:35:ggg']})
- call setloclist(w4_id, [], 'r', {'lines': ['F2:46:47:hhh', 'F2:48:49:jjj']})
- call assert_equal(['Line 32, Col 33', 'Line 34, Col 35'],
- \ getbufline(winbufnr(w2_id), 1, '$'))
- call assert_equal(['Ligne 46, Colonne 47', 'Ligne 48, Colonne 49'],
- \ getbufline(winbufnr(w4_id), 1, '$'))
-
- call win_gotoid(w5_id)
- lwindow
- call assert_equal(['F3|55 col 56| aaa', 'F3|57 col 58| bbb'],
- \ getline(1, '$'))
- %bw!
-endfunc
-
-func Test_locationlist_open_in_newtab()
- call s:create_test_file('Xqftestfile1')
- call s:create_test_file('Xqftestfile2')
- call s:create_test_file('Xqftestfile3')
-
- %bwipe!
-
- let lines =<< trim END
- Xqftestfile1:5:Line5
- Xqftestfile2:10:Line10
- Xqftestfile3:16:Line16
- END
- lgetexpr lines
-
- silent! llast
- call assert_equal(1, tabpagenr('$'))
- call assert_equal('Xqftestfile3', bufname())
-
- set switchbuf=newtab
-
- silent! lfirst
- call assert_equal(2, tabpagenr('$'))
- call assert_equal('Xqftestfile1', bufname())
-
- silent! lnext
- call assert_equal(3, tabpagenr('$'))
- call assert_equal('Xqftestfile2', bufname())
-
- call delete('Xqftestfile1')
- call delete('Xqftestfile2')
- call delete('Xqftestfile3')
- set switchbuf&vim
-
- %bwipe!
-endfunc
-
-" Test for win_gettype() in quickfix and location list windows
-func Test_win_gettype()
- copen
- call assert_equal("quickfix", win_gettype())
- let wid = win_getid()
- wincmd p
- call assert_equal("quickfix", win_gettype(wid))
- cclose
- lexpr ''
- lopen
- call assert_equal("loclist", win_gettype())
- let wid = win_getid()
- wincmd p
- call assert_equal("loclist", win_gettype(wid))
- lclose
-endfunc
-
-fun Test_vimgrep_nomatch()
- call XexprTests('c')
- call g:Xsetlist([{'lnum':10,'text':'Line1'}])
- copen
- if has("win32")
- call assert_fails('vimgrep foo *.zzz', 'E479:')
- let expected = [{'lnum': 10, 'bufnr': 0, 'end_lnum': 0, 'pattern': '', 'valid': 0, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 0, 'text': 'Line1'}]
- else
- call assert_fails('vimgrep foo *.zzz', 'E480:')
- let expected = []
- endif
- call assert_equal(expected, getqflist())
- cclose
-endfunc
-
-" Test for opening the quickfix window in two tab pages and then closing one
-" of the quickfix windows. This should not make the quickfix buffer unlisted.
-" (github issue #9300).
-func Test_two_qf_windows()
- cexpr "F1:1:line1"
- copen
- tabnew
- copen
- call assert_true(&buflisted)
- cclose
- tabfirst
- call assert_true(&buflisted)
- let bnum = bufnr()
- cclose
- " if all the quickfix windows are closed, then buffer should be unlisted.
- call assert_false(buflisted(bnum))
- %bw!
-
- " Repeat the test for a location list
- lexpr "F2:2:line2"
- lopen
- let bnum = bufnr()
- tabnew
- exe "buffer" bnum
- tabfirst
- lclose
- tablast
- call assert_true(buflisted(bnum))
- tabclose
- lopen
- call assert_true(buflisted(bnum))
- lclose
- call assert_false(buflisted(bnum))
- %bw!
-endfunc
-
-" Weird sequence of commands that caused entering a wiped-out buffer
-func Test_lopen_bwipe()
- func R()
- silent! tab lopen
- e x
- silent! lfile
- endfunc
-
- cal R()
- cal R()
- cal R()
- bw!
- delfunc R
-endfunc
-
-" Another sequence of commands that caused all buffers to be wiped out
-func Test_lopen_bwipe_all()
- let lines =<< trim END
- func R()
- silent! tab lopen
- e foo
- silent! lfile
- endfunc
- cal R()
- exe "norm \<C-W>\<C-V>0"
- cal R()
- bwipe
-
- call writefile(['done'], 'Xresult')
- qall!
- END
- call writefile(lines, 'Xscript')
- if RunVim([], [], '-u NONE -n -X -Z -e -m -s -S Xscript')
- call assert_equal(['done'], readfile('Xresult'))
- endif
-
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" Test for calling setqflist() function recursively
-func Test_recursive_setqflist()
- augroup QF_Test
- au!
- autocmd BufWinEnter quickfix call setqflist([], 'r')
- augroup END
-
- copen
- call assert_fails("call setqflist([], 'a')", 'E952:')
-
- augroup QF_Test
- au!
- augroup END
- %bw!
-endfunc
-
-" Test for failure to create a new window when selecting a file from the
-" quickfix window
-func Test_cwindow_newwin_fails()
- cgetexpr ["Xfile1:10:L10", "Xfile1:20:L20"]
- cwindow
- only
- let qf_wid = win_getid()
- " create the maximum number of scratch windows
- let hor_win_count = (&lines - 1)/2
- let hor_split_count = hor_win_count - 1
- for s in range(1, hor_split_count) | new | set buftype=nofile | endfor
- call win_gotoid(qf_wid)
- call assert_fails('exe "normal \<CR>"', 'E36:')
- %bw!
-endfunc
-
-" Test for updating the location list when only the location list window is
-" present and the corresponding file window is closed.
-func Test_loclist_update_with_llwin_only()
- %bw!
- new
- wincmd w
- lexpr ["Xfile1:1:Line1"]
- lopen
- wincmd p
- close
- call setloclist(2, [], 'r', {'lines': ["Xtest2:2:Line2"]})
- call assert_equal(['Xtest2|2| Line2'], getbufline(winbufnr(2), 1, '$'))
- %bw!
-endfunc
-
-" Test for getting the quickfix list after a buffer with an error is wiped out
-func Test_getqflist_wiped_out_buffer()
- %bw!
- cexpr ["Xtest1:34:Wiped out"]
- let bnum = bufnr('Xtest1')
- call assert_equal(bnum, getqflist()[0].bufnr)
- bw Xtest1
- call assert_equal(0, getqflist()[0].bufnr)
- %bw!
-endfunc
-
-" Test for the status message that is displayed when opening a new quickfix
-" list
-func Test_qflist_statusmsg()
- cexpr "1\n2"
- cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"
- call assert_equal('(4 of 4): msg', v:statusmsg)
- call setqflist([], 'f')
- %bw!
-
- " When creating a new quickfix list, if an autocmd changes the quickfix list
- " in the stack, then an error message should be displayed.
- augroup QF_Test
- au!
- au BufEnter test_quickfix.vim colder
- augroup END
- cexpr "1\n2"
- call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:')
- call setqflist([], 'f')
- augroup QF_Test
- au!
- augroup END
- %bw!
-
- augroup QF_Test
- au!
- au BufEnter test_quickfix.vim caddexpr "4"
- augroup END
- call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:')
- call setqflist([], 'f')
- augroup QF_Test
- au!
- augroup END
- %bw!
-endfunc
-
-func Test_quickfixtextfunc_recursive()
- func s:QFTfunc(o)
- cgete '0'
- endfunc
- copen
- let &quickfixtextfunc = 's:QFTfunc'
- cex ""
-
- let &quickfixtextfunc = ''
- cclose
-endfunc
-
-" Test for replacing the location list from an autocmd. This used to cause a
-" read from freed memory.
-func Test_loclist_replace_autocmd()
- %bw!
- call setloclist(0, [], 'f')
- let s:bufnr = bufnr()
- cal setloclist(0, [{'0': 0, '': ''}])
- au BufEnter * cal setloclist(1, [{'t': ''}, {'bufnr': s:bufnr}], 'r')
- lopen
- try
- exe "norm j\<CR>"
- catch
- endtry
- lnext
- %bw!
- call setloclist(0, [], 'f')
-endfunc
-
-func s:QfTf(_)
-endfunc
-
-func Test_setqflist_cb_arg()
- " This was changing the callback name in the dictionary.
- let d = #{quickfixtextfunc: 's:QfTf'}
- call setqflist([], 'a', d)
- call assert_equal('s:QfTf', d.quickfixtextfunc)
-
- call setqflist([], 'f')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim
deleted file mode 100644
index 6a6719da8b..0000000000
--- a/src/nvim/testdir/test_quotestar.vim
+++ /dev/null
@@ -1,155 +0,0 @@
-" *-register (quotestar) tests
-
-source shared.vim
-source check.vim
-
-CheckFeature clipboard_working
-
-func Do_test_quotestar_for_macunix()
- if empty(exepath('pbcopy')) || empty(exepath('pbpaste'))
- return 'Test requires pbcopy(1) and pbpaste(1)'
- endif
-
- let @* = ''
-
- " Test #1: Pasteboard to Vim
- let test_msg = "text from pasteboard to vim via quotestar"
- " Write a piece of text to the pasteboard.
- call system('/bin/echo -n "' . test_msg . '" | pbcopy')
- " See if the *-register is changed as expected.
- call assert_equal(test_msg, @*)
-
- " Test #2: Vim to Pasteboard
- let test_msg = "text from vim to pasteboard via quotestar"
- " Write a piece of text to the *-register.
- let @* = test_msg
- " See if the pasteboard is changed as expected.
- call assert_equal(test_msg, system('pbpaste'))
-
- return ''
-endfunc
-
-func Do_test_quotestar_for_x11()
- if !has('clientserver') || !has('job')
- return 'Test requires the client-server and job features'
- endif
-
- let cmd = GetVimCommand()
- if cmd == ''
- return 'GetVimCommand() failed'
- endif
- try
- call remote_send('xxx', '')
- catch
- if v:exception =~ 'E240:'
- " No connection to the X server, give up.
- return
- endif
- " ignore other errors
- endtry
-
- let name = 'XVIMCLIPBOARD'
-
- " Make sure a previous server has exited
- try
- call remote_send(name, ":qa!\<CR>")
- catch /E241:/
- endtry
- call WaitForAssert({-> assert_notmatch(name, serverlist())})
-
- let cmd .= ' --servername ' . name
- let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
- call WaitForAssert({-> assert_equal("run", job_status(job))})
-
- " Takes a short while for the server to be active.
- call WaitForAssert({-> assert_match(name, serverlist())})
-
- " Wait for the server to be up and answering requests. One second is not
- " always sufficient.
- 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 WaitForAssert({-> assert_equal("no", remote_expr(name, "@*", "", 1))})
-
- " Set the * register on the server.
- call remote_send(name, ":let @* = 'yes'\<CR>")
- call WaitForAssert({-> assert_equal("yes", remote_expr(name, "@*", "", 1))})
-
- " Check that the *-register of this vim instance is changed as expected.
- call WaitForAssert({-> assert_equal("yes", @*)})
-
- " Handle the large selection over 262040 byte.
- let length = 262044
- let sample = 'a' . repeat('b', length - 2) . 'c'
- let @* = sample
- call WaitFor('remote_expr("' . name . '", "len(@*) >= ' . length . '", "", 1)')
- let res = remote_expr(name, "@*", "", 2)
- call assert_equal(length, len(res))
- " Check length to prevent a large amount of output at assertion failure.
- if length == len(res)
- call assert_equal(sample, res)
- endif
-
- if has('unix') && has('gui') && !has('gui_running')
- let @* = ''
-
- " Running in a terminal and the GUI is available: Tell the server to open
- " the GUI and check that the remote command still works.
- if has('gui_motif')
- " For those GUIs, ignore the 'failed to create input context' error.
- call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>")
- else
- call remote_send(name, ":gui -f\<CR>")
- endif
- " Wait for the server in the GUI to be up and answering requests.
- " First need to wait for the GUI to start up, otherwise the send hangs in
- " trying to send to the terminal window.
- " On some systems and with valgrind this can be very slow.
- sleep 1
- call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000)
-
- call remote_send(name, ":let @* = 'maybe'\<CR>")
- call WaitForAssert({-> assert_equal("maybe", remote_expr(name, "@*", "", 2))})
-
- call assert_equal('maybe', @*)
- endif
-
- call remote_send(name, ":qa!\<CR>")
- try
- call WaitForAssert({-> assert_equal("dead", job_status(job))})
- finally
- if job_status(job) != 'dead'
- call assert_report('Server did not exit')
- call job_stop(job, 'kill')
- endif
- endtry
-
- return ''
-endfunc
-
-func Test_quotestar()
- let skipped = ''
-
- let quotestar_saved = @*
-
- if has('macunix')
- let skipped = Do_test_quotestar_for_macunix()
- elseif has('x11')
- if empty($DISPLAY)
- let skipped = "Test can only run when $DISPLAY is set."
- else
- let skipped = Do_test_quotestar_for_x11()
- endif
- else
- let skipped = "Test is not implemented yet for this platform."
- endif
-
- let @* = quotestar_saved
-
- if !empty(skipped)
- throw 'Skipped: ' . skipped
- endif
-endfunc
diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim
deleted file mode 100644
index 5fdbfe9cd8..0000000000
--- a/src/nvim/testdir/test_random.vim
+++ /dev/null
@@ -1,59 +0,0 @@
-" Tests for srand() and rand()
-
-source check.vim
-source shared.vim
-
-func Test_Rand()
- let r = srand(123456789)
- call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r)
- call assert_equal(4284103975, rand(r))
- call assert_equal(1001954530, rand(r))
- call assert_equal(2701803082, rand(r))
- call assert_equal(2658065534, rand(r))
- call assert_equal(3104308804, rand(r))
-
- let s = srand()
- " using /dev/urandom or used time, result is different each time
- call assert_notequal(s, srand())
-
- " Nvim does not support test_srand_seed
- " call test_srand_seed(123456789)
- " call assert_equal(4284103975, rand())
- " call assert_equal(1001954530, rand())
- " call test_srand_seed()
-
- if has('float')
- call assert_fails('echo srand(1.2)', 'E805:')
- endif
- call assert_fails('echo srand([1])', 'E745:')
- call assert_fails('echo rand("burp")', 'E475:')
- call assert_fails('echo rand([1, 2, 3])', 'E730:')
- call assert_fails('echo rand([[1], 2, 3, 4])', 'E730:')
- call assert_fails('echo rand([1, [2], 3, 4])', 'E730:')
- call assert_fails('echo rand([1, 2, [3], 4])', 'E730:')
- call assert_fails('echo rand([1, 2, 3, [4]])', 'E730:')
-endfunc
-
-func Test_issue_5587()
- call rand()
- call garbagecollect()
- call rand()
-endfunc
-
-func Test_srand()
- CheckNotGui
-
- let cmd = GetVimCommand() .. ' -V -es -c "echo rand()" -c qa!'
- let bad = 0
- for _ in range(10)
- echo cmd
- let result1 = system(cmd)
- let result2 = system(cmd)
- if result1 ==# result2
- let bad += 1
- endif
- endfor
- call assert_inrange(0, 4, bad)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim
deleted file mode 100644
index 92e22687af..0000000000
--- a/src/nvim/testdir/test_recover.vim
+++ /dev/null
@@ -1,467 +0,0 @@
-" Test :recover
-
-source check.vim
-
-func Test_recover_root_dir()
- " This used to access invalid memory.
- split Xtest
- set dir=/
- call assert_fails('recover', 'E305:')
- close!
-
- if has('win32')
- " can write in / directory on MS-Windows
- let &directory = 'F:\\'
- elseif filewritable('/') == 2
- set dir=/notexist/
- endif
- call assert_fails('split Xtest', 'E303:')
-
- " No error with empty 'directory' setting.
- set directory=
- split XtestOK
- close!
-
- set dir&
-endfunc
-
-" Make a copy of the current swap file to "Xswap".
-" Return the name of the swap file.
-func CopySwapfile()
- preserve
- " get the name of the swap file
- let swname = split(execute("swapname"))[0]
- let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '')
- " make a copy of the swap file in Xswap
- set binary
- exe 'sp ' . swname
- w! Xswap
- set nobinary
- return swname
-endfunc
-
-" Inserts 10000 lines with text to fill the swap file with two levels of pointer
-" blocks. Then recovers from the swap file and checks all text is restored.
-"
-" We need about 10000 lines of 100 characters to get two levels of pointer
-" blocks.
-func Test_swap_file()
- set directory=.
- set fileformat=unix undolevels=-1
- edit! Xtest
- let text = "\tabcdefghijklmnoparstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnoparstuvwxyz0123456789"
- let i = 1
- let linecount = 10000
- while i <= linecount
- call append(i - 1, i . text)
- let i += 1
- endwhile
- $delete
-
- let swname = CopySwapfile()
-
- new
- only!
- bwipe! Xtest
- call rename('Xswap', swname)
- recover Xtest
- call delete(swname)
- let linedollar = line('$')
- call assert_equal(linecount, linedollar)
- if linedollar < linecount
- let linecount = linedollar
- endif
- let i = 1
- while i <= linecount
- call assert_equal(i . text, getline(i))
- let i += 1
- endwhile
-
- set undolevels&
- enew! | only
-endfunc
-
-func Test_nocatch_process_still_running()
- let g:skipped_reason = 'test_override() is N/A'
- return
- " sysinfo.uptime probably only works on Linux
- if !has('linux')
- let g:skipped_reason = 'only works on Linux'
- return
- endif
- " the GUI dialog can't be handled
- if has('gui_running')
- let g:skipped_reason = 'only works in the terminal'
- return
- endif
-
- " don't intercept existing swap file here
- au! SwapExists
-
- " Edit a file and grab its swapfile.
- edit Xswaptest
- call setline(1, ['a', 'b', 'c'])
- let swname = CopySwapfile()
-
- " Forget we edited this file
- new
- only!
- bwipe! Xswaptest
-
- call rename('Xswap', swname)
- call feedkeys('e', 'tL')
- redir => editOutput
- edit Xswaptest
- redir END
- call assert_match('E325: ATTENTION', editOutput)
- call assert_match('file name: .*Xswaptest', editOutput)
- call assert_match('process ID: \d* (STILL RUNNING)', editOutput)
-
- " Forget we edited this file
- new
- only!
- bwipe! Xswaptest
-
- " pretend we rebooted
- call test_override("uptime", 0)
- sleep 1
-
- call feedkeys('e', 'tL')
- redir => editOutput
- edit Xswaptest
- redir END
- call assert_match('E325: ATTENTION', editOutput)
- call assert_notmatch('(STILL RUNNING)', editOutput)
-
- call test_override("ALL", 0)
- call delete(swname)
-endfunc
-
-" Test for :recover with multiple swap files
-func Test_recover_multiple_swap_files()
- CheckUnix
- new Xfile1
- call setline(1, ['a', 'b', 'c'])
- preserve
- let b = readblob(swapname(''))
- call writefile(b, '.Xfile1.swm')
- call writefile(b, '.Xfile1.swn')
- call writefile(b, '.Xfile1.swo')
- %bw!
- call feedkeys(":recover Xfile1\<CR>3\<CR>q", 'xt')
- call assert_equal(['a', 'b', 'c'], getline(1, '$'))
- " try using out-of-range number to select a swap file
- bw!
- call feedkeys(":recover Xfile1\<CR>4\<CR>q", 'xt')
- call assert_equal('Xfile1', @%)
- call assert_equal([''], getline(1, '$'))
- bw!
- call feedkeys(":recover Xfile1\<CR>0\<CR>q", 'xt')
- call assert_equal('Xfile1', @%)
- call assert_equal([''], getline(1, '$'))
- bw!
-
- call delete('.Xfile1.swm')
- call delete('.Xfile1.swn')
- call delete('.Xfile1.swo')
-endfunc
-
-" Test for :recover using an empty swap file
-func Test_recover_empty_swap_file()
- CheckUnix
- call writefile([], '.Xfile1.swp')
- let msg = execute('recover Xfile1')
- call assert_match('Unable to read block 0 from .Xfile1.swp', msg)
- call assert_equal('Xfile1', @%)
- bw!
-
- " make sure there are no old swap files laying around
- for f in glob('.sw?', 0, 1)
- call delete(f)
- endfor
-
- " :recover from an empty buffer
- call assert_fails('recover', 'E305:')
- call delete('.Xfile1.swp')
-endfunc
-
-" Test for :recover using a corrupted swap file
-" Refer to the comments in the memline.c file for the swap file headers
-" definition.
-func Test_recover_corrupted_swap_file()
- CheckUnix
-
- " recover using a partial swap file
- call writefile(0z1234, '.Xfile1.swp')
- call assert_fails('recover Xfile1', 'E295:')
- bw!
-
- " recover using invalid content in the swap file
- call writefile([repeat('1', 2*1024)], '.Xfile1.swp')
- call assert_fails('recover Xfile1', 'E307:')
- call delete('.Xfile1.swp')
-
- " :recover using a swap file with a corrupted header
- edit Xfile1
- preserve
- let sn = swapname('')
- let b = readblob(sn)
- let save_b = copy(b)
- bw!
-
- " Not all fields are written in a system-independent manner. Detect whether
- " the test is running on a little or big-endian system, so the correct
- " corruption values can be set.
- " The B0_MAGIC_LONG field may be 32-bit or 64-bit, depending on the system,
- " even though the value stored is only 32-bits. Therefore, need to check
- " both the high and low 32-bits to compute these values.
- let little_endian = (b[1008:1011] == 0z33323130) || (b[1012:1015] == 0z33323130)
- let system_64bit = little_endian ? (b[1012:1015] == 0z00000000) : (b[1008:1011] == 0z00000000)
-
- " clear the B0_MAGIC_LONG field
- if system_64bit
- let b[1008:1015] = 0z00000000.00000000
- else
- let b[1008:1011] = 0z00000000
- endif
- call writefile(b, sn)
- let msg = execute('recover Xfile1')
- call assert_match('the file has been damaged', msg)
- call assert_equal('Xfile1', @%)
- call assert_equal([''], getline(1, '$'))
- bw!
-
- " reduce the page size
- let b = copy(save_b)
- let b[12:15] = 0z00010000
- call writefile(b, sn)
- let msg = execute('recover Xfile1')
- call assert_match('page size is smaller than minimum value', msg)
- call assert_equal('Xfile1', @%)
- call assert_equal([''], getline(1, '$'))
- bw!
-
- " clear the pointer ID
- let b = copy(save_b)
- let b[4096:4097] = 0z0000
- call writefile(b, sn)
- call assert_fails('recover Xfile1', 'E310:')
- call assert_equal('Xfile1', @%)
- call assert_equal([''], getline(1, '$'))
- bw!
-
- " set the number of pointers in a pointer block to zero
- let b = copy(save_b)
- let b[4098:4099] = 0z0000
- call writefile(b, sn)
- call assert_fails('recover Xfile1', 'E312:')
- call assert_equal('Xfile1', @%)
- call assert_equal(['???EMPTY BLOCK'], getline(1, '$'))
- bw!
-
- " set the block number in a pointer entry to a negative number
- let b = copy(save_b)
- if system_64bit
- let b[4104:4111] = little_endian ? 0z00000000.00000080 : 0z80000000.00000000
- else
- let b[4104:4107] = little_endian ? 0z00000080 : 0z80000000
- endif
- call writefile(b, sn)
- call assert_fails('recover Xfile1', 'E312:')
- call assert_equal('Xfile1', @%)
- call assert_equal(['???LINES MISSING'], getline(1, '$'))
- bw!
-
- " clear the data block ID
- let b = copy(save_b)
- let b[8192:8193] = 0z0000
- call writefile(b, sn)
- call assert_fails('recover Xfile1', 'E312:')
- call assert_equal('Xfile1', @%)
- call assert_equal(['???BLOCK MISSING'], getline(1, '$'))
- bw!
-
- " set the number of lines in the data block to zero
- let b = copy(save_b)
- if system_64bit
- let b[8208:8215] = 0z00000000.00000000
- else
- let b[8208:8211] = 0z00000000
- endif
- call writefile(b, sn)
- call assert_fails('recover Xfile1', 'E312:')
- call assert_equal('Xfile1', @%)
- call assert_equal(['??? from here until ???END lines may have been inserted/deleted',
- \ '???END'], getline(1, '$'))
- bw!
-
- " use an invalid text start for the lines in a data block
- let b = copy(save_b)
- if system_64bit
- let b[8216:8219] = 0z00000000
- else
- let b[8212:8215] = 0z00000000
- endif
- call writefile(b, sn)
- call assert_fails('recover Xfile1', 'E312:')
- call assert_equal('Xfile1', @%)
- call assert_equal(['???'], getline(1, '$'))
- bw!
-
- " use an incorrect text end (db_txt_end) for the data block
- let b = copy(save_b)
- let b[8204:8207] = little_endian ? 0z80000000 : 0z00000080
- call writefile(b, sn)
- call assert_fails('recover Xfile1', 'E312:')
- call assert_equal('Xfile1', @%)
- call assert_equal(['??? from here until ???END lines may be messed up', '',
- \ '???END'], getline(1, '$'))
- bw!
-
- " remove the data block
- let b = copy(save_b)
- call writefile(b[:8191], sn)
- call assert_fails('recover Xfile1', 'E312:')
- call assert_equal('Xfile1', @%)
- call assert_equal(['???MANY LINES MISSING'], getline(1, '$'))
-
- bw!
- call delete(sn)
-endfunc
-
-" Test for :recover using an encrypted swap file
-func Test_recover_encrypted_swap_file()
- CheckFeature cryptv
- CheckUnix
-
- " Recover an encrypted file from the swap file without the original file
- new Xfile1
- call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt')
- call setline(1, ['aaa', 'bbb', 'ccc'])
- preserve
- let b = readblob('.Xfile1.swp')
- call writefile(b, '.Xfile1.swm')
- bw!
- call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt')
- call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
- bw!
- call delete('.Xfile1.swm')
-
- " Recover an encrypted file from the swap file with the original file
- new Xfile1
- call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt')
- call setline(1, ['aaa', 'bbb', 'ccc'])
- update
- call setline(1, ['111', '222', '333'])
- preserve
- let b = readblob('.Xfile1.swp')
- call writefile(b, '.Xfile1.swm')
- bw!
- call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt')
- call assert_equal(['111', '222', '333'], getline(1, '$'))
- call assert_true(&modified)
- bw!
- call delete('.Xfile1.swm')
- call delete('Xfile1')
-endfunc
-
-" Test for :recover using a unreadable swap file
-func Test_recover_unreadble_swap_file()
- CheckUnix
- CheckNotRoot
- new Xfile1
- let b = readblob('.Xfile1.swp')
- call writefile(b, '.Xfile1.swm')
- bw!
- call setfperm('.Xfile1.swm', '-w-------')
- call assert_fails('recover Xfile1', 'E306:')
- call delete('.Xfile1.swm')
-endfunc
-
-" Test for using :recover when the original file and the swap file have the
-" same contents.
-func Test_recover_unmodified_file()
- CheckUnix
- call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1')
- edit Xfile1
- preserve
- let b = readblob('.Xfile1.swp')
- %bw!
- call writefile(b, '.Xfile1.swz')
- let msg = execute('recover Xfile1')
- call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
- call assert_false(&modified)
- call assert_match('Buffer contents equals file contents', msg)
- bw!
- call delete('Xfile1')
- call delete('.Xfile1.swz')
-endfunc
-
-" Test for recovering a file when editing a symbolically linked file
-func Test_recover_symbolic_link()
- CheckUnix
- call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1')
- silent !ln -s Xfile1 Xfile2
- edit Xfile2
- call assert_equal('.Xfile1.swp', fnamemodify(swapname(''), ':t'))
- preserve
- let b = readblob('.Xfile1.swp')
- %bw!
- call writefile([], 'Xfile1')
- call writefile(b, '.Xfile1.swp')
- silent! recover Xfile2
- call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
- call assert_true(&modified)
- update
- %bw!
- call assert_equal(['aaa', 'bbb', 'ccc'], readfile('Xfile1'))
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('.Xfile1.swp')
-endfunc
-
-" Test for recovering a file when an autocmd moves the cursor to an invalid
-" line. This used to result in an internal error (E315) which is fixed
-" by 8.2.2966.
-func Test_recover_invalid_cursor_pos()
- call writefile([], 'Xfile1')
- edit Xfile1
- preserve
- let b = readblob('.Xfile1.swp')
- bw!
- augroup Test
- au!
- au BufReadPost Xfile1 normal! 3G
- augroup END
- call writefile(range(1, 3), 'Xfile1')
- call writefile(b, '.Xfile1.swp')
- try
- recover Xfile1
- catch /E308:/
- " this test is for the :E315 internal error.
- " ignore the 'E308: Original file may have been changed' error
- endtry
- redraw!
- augroup Test
- au!
- augroup END
- augroup! Test
- call delete('Xfile1')
- call delete('.Xfile1.swp')
-endfunc
-
-" Test for recovering a buffer without a name
-func Test_noname_buffer()
- new
- call setline(1, ['one', 'two'])
- preserve
- let sn = swapname('')
- let b = readblob(sn)
- bw!
- call writefile(b, sn)
- exe "recover " .. sn
- call assert_equal(['one', 'two'], getline(1, '$'))
- call delete(sn)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regex_char_classes.vim b/src/nvim/testdir/test_regex_char_classes.vim
deleted file mode 100644
index db16f057c8..0000000000
--- a/src/nvim/testdir/test_regex_char_classes.vim
+++ /dev/null
@@ -1,297 +0,0 @@
-" Tests for regexp with backslash and other special characters inside []
-" Also test backslash for hex/octal numbered character.
-"
-
-scriptencoding utf-8
-
-function RunSTest(value, calls, expected)
- new
- call feedkeys("i" . a:value, "mx")
- exec a:calls
- call assert_equal(a:expected, getline(1), printf("wrong result for %s", a:calls))
- quit!
-endfunction
-
-function RunXTest(value, search_exp, expected)
- new
- call feedkeys("i" . a:value, "mx")
- call feedkeys("gg" . a:search_exp . "\nx", "mx")
- call assert_equal(a:expected, getline(1), printf("wrong result for %s", a:search_exp))
- quit!
-endfunction
-
-
-function Test_x_search()
- let res = "test text test text"
- call RunXTest("test \\text test text", "/[\\x]", res)
- call RunXTest("test \ttext test text", "/[\\t\\]]", res)
- call RunXTest("test text ]test text", "/[]y]", res)
- call RunXTest("test ]text test text", "/[\\]]", res)
- call RunXTest("test text te^st text", "/[y^]", res)
- call RunXTest("test te$xt test text", "/[$y]", res)
- call RunXTest("test taext test text", "/[\\x61]", res)
- call RunXTest("test tbext test text","/[\\x60-\\x64]", res)
- call RunXTest("test 5text test text","/[\\x785]", res)
- call RunXTest("testc text test text","/[\\o143]", res)
- call RunXTest("tesdt text test text","/[\\o140-\\o144]", res)
- call RunXTest("test7 text test text", "/[\\o417]", res)
- call RunXTest("test text tBest text", "/\\%x42", res)
- call RunXTest("test text teCst text", "/\\%o103", res)
- call RunXTest("test text \<C-V>x00test text", "/[\\x00]", res)
-endfunction
-
-function Test_s_search()
- let res = "test text test text"
- call RunSTest("test te\<C-V>x00xt t\<C-V>x04est t\<C-V>x10ext", "s/[\\x00-\\x10]//g", res)
- call RunSTest("test \\xyztext test text", "s/[\\x-z]\\+//", res)
- call RunSTest("test text tev\\uyst text", "s/[\\u-z]\\{2,}//", res)
- call RunSTest("xx aaaaa xx a", "s/\\(a\\)\\+//", "xx xx a")
- call RunSTest("xx aaaaa xx a", "s/\\(a*\\)\\+//", "xx aaaaa xx a")
- call RunSTest("xx aaaaa xx a", "s/\\(a*\\)*//", "xx aaaaa xx a")
- call RunSTest("xx aaaaa xx", "s/\\(a\\)\\{2,3}/A/", "xx Aaa xx")
- call RunSTest("xx aaaaa xx", "s/\\(a\\)\\{-2,3}/A/", "xx Aaaa xx")
- call RunSTest("xx aaa12aa xx", "s/\\(a\\)*\\(12\\)\\@>/A/", "xx Aaa xx")
- call RunSTest("xx foobar xbar xx", "s/\\(foo\\)\\@<!bar/A/", "xx foobar xA xx")
- call RunSTest("xx an file xx", "s/\\(an\\_s\\+\\)\\@<=file/A/", "xx an A xx")
- call RunSTest("x= 9;", "s/^\\(\\h\\w*\\%(->\\|\\.\\)\\=\\)\\+=/XX/", "XX 9;")
- call RunSTest("hh= 77;", "s/^\\(\\h\\w*\\%(->\\|\\.\\)\\=\\)\\+=/YY/", "YY 77;")
- call RunSTest(" aaa ", "s/aaa/xyz/", " xyz ")
- call RunSTest(" xyz", "s/~/bcd/", " bcd")
- call RunSTest(" bcdbcdbcd", "s/~\\+/BB/", " BB")
-endfunction
-
-" Test character classes in regexp using regexpengine 0, 1, 2.
-func Test_regex_char_classes()
- new
- let save_enc = &encoding
- set encoding=utf-8
-
- let input = "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"
-
- " Format is [cmd_to_run, expected_output]
- let tests = [
- \ [':s/\%#=0\d//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\d//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\d//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[0-9]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[0-9]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[0-9]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\D//g',
- \ "0123456789"],
- \ [':s/\%#=1\D//g',
- \ "0123456789"],
- \ [':s/\%#=2\D//g',
- \ "0123456789"],
- \ [':s/\%#=0[^0-9]//g',
- \ "0123456789"],
- \ [':s/\%#=1[^0-9]//g',
- \ "0123456789"],
- \ [':s/\%#=2[^0-9]//g',
- \ "0123456789"],
- \ [':s/\%#=0\o//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\o//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\o//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[0-7]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[0-7]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[0-7]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\O//g',
- \ "01234567"],
- \ [':s/\%#=1\O//g',
- \ "01234567"],
- \ [':s/\%#=2\O//g',
- \ "01234567"],
- \ [':s/\%#=0[^0-7]//g',
- \ "01234567"],
- \ [':s/\%#=1[^0-7]//g',
- \ "01234567"],
- \ [':s/\%#=2[^0-7]//g',
- \ "01234567"],
- \ [':s/\%#=0\x//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\x//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\x//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[0-9A-Fa-f]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[0-9A-Fa-f]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[0-9A-Fa-f]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\X//g',
- \ "0123456789ABCDEFabcdef"],
- \ [':s/\%#=1\X//g',
- \ "0123456789ABCDEFabcdef"],
- \ [':s/\%#=2\X//g',
- \ "0123456789ABCDEFabcdef"],
- \ [':s/\%#=0[^0-9A-Fa-f]//g',
- \ "0123456789ABCDEFabcdef"],
- \ [':s/\%#=1[^0-9A-Fa-f]//g',
- \ "0123456789ABCDEFabcdef"],
- \ [':s/\%#=2[^0-9A-Fa-f]//g',
- \ "0123456789ABCDEFabcdef"],
- \ [':s/\%#=0\w//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\w//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\w//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[0-9A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[0-9A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[0-9A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\W//g',
- \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=1\W//g',
- \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=2\W//g',
- \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=0[^0-9A-Za-z_]//g',
- \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=1[^0-9A-Za-z_]//g',
- \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=2[^0-9A-Za-z_]//g',
- \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=0\h//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\h//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\h//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\H//g',
- \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=1\H//g',
- \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=2\H//g',
- \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=0[^A-Za-z_]//g',
- \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=1[^A-Za-z_]//g',
- \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=2[^A-Za-z_]//g',
- \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
- \ [':s/\%#=0\a//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\a//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\a//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[A-Za-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[A-Za-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[A-Za-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\A//g',
- \ "ABCDEFGHIXYZabcdefghiwxyz"],
- \ [':s/\%#=1\A//g',
- \ "ABCDEFGHIXYZabcdefghiwxyz"],
- \ [':s/\%#=2\A//g',
- \ "ABCDEFGHIXYZabcdefghiwxyz"],
- \ [':s/\%#=0[^A-Za-z]//g',
- \ "ABCDEFGHIXYZabcdefghiwxyz"],
- \ [':s/\%#=1[^A-Za-z]//g',
- \ "ABCDEFGHIXYZabcdefghiwxyz"],
- \ [':s/\%#=2[^A-Za-z]//g',
- \ "ABCDEFGHIXYZabcdefghiwxyz"],
- \ [':s/\%#=0\l//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\l//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\l//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[a-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[a-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[a-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\L//g',
- \ "abcdefghiwxyz"],
- \ [':s/\%#=1\L//g',
- \ "abcdefghiwxyz"],
- \ [':s/\%#=2\L//g',
- \ "abcdefghiwxyz"],
- \ [':s/\%#=0[^a-z]//g',
- \ "abcdefghiwxyz"],
- \ [':s/\%#=1[^a-z]//g',
- \ "abcdefghiwxyz"],
- \ [':s/\%#=2[^a-z]//g',
- \ "abcdefghiwxyz"],
- \ [':s/\%#=0\u//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\u//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\u//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[A-Z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[A-Z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[A-Z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0\U//g',
- \ "ABCDEFGHIXYZ"],
- \ [':s/\%#=1\U//g',
- \ "ABCDEFGHIXYZ"],
- \ [':s/\%#=2\U//g',
- \ "ABCDEFGHIXYZ"],
- \ [':s/\%#=0[^A-Z]//g',
- \ "ABCDEFGHIXYZ"],
- \ [':s/\%#=1[^A-Z]//g',
- \ "ABCDEFGHIXYZ"],
- \ [':s/\%#=2[^A-Z]//g',
- \ "ABCDEFGHIXYZ"],
- \ [':s/\%#=0\%' . line('.') . 'l^\t...//g',
- \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1\%' . line('.') . 'l^\t...//g',
- \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2\%' . line('.') . 'l^\t...//g',
- \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[0-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=1[0-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=2[0-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
- \ [':s/\%#=0[^0-z]//g',
- \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"],
- \ [':s/\%#=1[^0-z]//g',
- \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"],
- \ [':s/\%#=2[^0-z]//g',
- \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"]
- \]
-
- for [cmd, expected] in tests
- call append(0, input)
- call cursor(1, 1)
- exe cmd
- call assert_equal(expected, getline(1), cmd)
- endfor
-
- let &encoding = save_enc
- enew!
- close
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
deleted file mode 100644
index ece6ae518e..0000000000
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ /dev/null
@@ -1,1094 +0,0 @@
-" Tests for regexp in latin1 encoding
-
-" set encoding=latin1
-scriptencoding latin1
-
-source check.vim
-
-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 '
- \ .. "0 1 2 3 4 5 6 7 8 9 "
- \ .. "` ~ ! ? ; : . , / \\ ' \" | < > [ ] { } ( ) @ # $ % ^ & * _ - + \b \e \f \n \r \t"
- let groups = split(str)
- for group1 in groups
- for c in split(group1, '\zs')
- " next statement confirms that equivalence class matches every
- " character in group
- call assert_match('^[[=' . c . '=]]*$', group1)
- for group2 in groups
- if group2 != group1
- " next statement converts that equivalence class doesn't match
- " a character in any other group
- call assert_equal(-1, match(group2, '[[=' . c . '=]]'))
- endif
- endfor
- endfor
- endfor
-endfunc
-
-func Test_equivalence_re1()
- throw 'skipped: Nvim does not support enc=latin1'
- set re=1
- call s:equivalence_test()
-endfunc
-
-func Test_equivalence_re2()
- throw 'skipped: Nvim does not support enc=latin1'
- set re=2
- call s:equivalence_test()
-endfunc
-
-func Test_range_with_newline()
- new
- call setline(1, "a")
- call assert_equal(0, search("[ -*\\n- ]"))
- call assert_equal(0, search("[ -*\\t-\\n]"))
- bwipe!
-endfunc
-
-func Test_pattern_compile_speed()
- CheckOption spellcapcheck
- CheckFunction reltimefloat
-
- let start = reltime()
- " this used to be very slow, not it should be about a second
- set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179}
- call assert_inrange(0.01, 10.0, reltimefloat(reltime(start)))
- set spc=
-endfunc
-
-func Test_get_equi_class()
- new
- " Incomplete equivalence class caused invalid memory access
- s/^/[[=
- call assert_equal(1, search(getline(1)))
- s/.*/[[.
- call assert_equal(1, search(getline(1)))
-endfunc
-
-func Test_rex_init()
- set noincsearch
- set re=1
- new
- setlocal iskeyword=a-z
- call setline(1, ['abc', 'ABC'])
- call assert_equal(1, search('[[:keyword:]]'))
- new
- setlocal iskeyword=A-Z
- call setline(1, ['abc', 'ABC'])
- call assert_equal(2, search('[[:keyword:]]'))
- bwipe!
- bwipe!
- set re=0
-endfunc
-
-func Test_backref()
- new
- call setline(1, ['one', 'two', 'three', 'four', 'five'])
- call assert_equal(3, search('\%#=1\(e\)\1'))
- call assert_equal(3, search('\%#=2\(e\)\1'))
- call assert_fails('call search("\\%#=1\\(e\\1\\)")', 'E65:')
- call assert_fails('call search("\\%#=2\\(e\\1\\)")', 'E65:')
- bwipe!
-endfunc
-
-func Test_multi_failure()
- set re=1
- call assert_fails('/a**', 'E61:')
- call assert_fails('/a*\+', 'E62:')
- call assert_fails('/a\{a}', 'E554:')
- set re=2
- call assert_fails('/a**', 'E871:')
- call assert_fails('/a*\+', 'E871:')
- call assert_fails('/a\{a}', 'E554:')
- set re=0
-endfunc
-
-func Test_column_success_failure()
- new
- call setline(1, 'xbar')
-
- set re=1
- %s/\%>0v./A/
- call assert_equal('Abar', getline(1))
- call assert_fails('/\%v', 'E71:')
- call assert_fails('/\%>v', 'E71:')
- call assert_fails('/\%c', 'E71:')
- call assert_fails('/\%<c', 'E71:')
- call assert_fails('/\%l', 'E71:')
- set re=2
- %s/\%>0v./B/
- call assert_equal('Bbar', getline(1))
- call assert_fails('/\%v', 'E1273:')
- call assert_fails('/\%>v', 'E1273:')
- call assert_fails('/\%c', 'E1273:')
- call assert_fails('/\%<c', 'E1273:')
- call assert_fails('/\%l', 'E1273:')
-
- set re=0
- bwipe!
-endfunc
-
-func Test_recursive_addstate()
- throw 'skipped: TODO: '
- " This will call addstate() recursively until it runs into the limit.
- let lnum = search('\v((){328}){389}')
- call assert_equal(0, lnum)
-endfunc
-
-func Test_out_of_memory()
- new
- s/^/,n
- " This will be slow...
- call assert_fails('call search("\\v((n||<)+);")', 'E363:')
-endfunc
-
-" Tests for regexp patterns without multi-byte support.
-func Test_regexp_single_line_pat()
- " tl is a List of Lists with:
- " regexp engines to test
- " 0 - test with 'regexpengine' values 0 and 1
- " 1 - test with 'regexpengine' values 0 and 2
- " 2 - test with 'regexpengine' values 0, 1 and 2
- " 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
- " match escape character in a string
- call add(tl, [2, '.\e.', "one\<Esc>two", "e\<Esc>t"])
- " match backspace character in a string
- call add(tl, [2, '.\b.', "one\<C-H>two", "e\<C-H>t"])
- " match newline character in a string
- call add(tl, [2, 'o\nb', "foo\nbar", "o\nb"])
-
- " 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'])
-
- " Test for ignoring case and matching repeated characters
- call add(tl, [2, '\cb\+', 'aAbBbBcC', 'bBbB'])
-
- " 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()
- " tl is a List of Lists with:
- " regexp engines to test
- " 0 - test with 'regexpengine' values 0 and 1
- " 1 - test with 'regexpengine' values 0 and 2
- " 2 - test with 'regexpengine' values 0, 1 and 2
- " regexp pattern
- " List with text to test the pattern on
- " List with the expected match
- 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']])
-
- " Any single character or end-of-line
- call add(tl, [2, '\_.\+', ['a', 'b', 'c'], ['XX']])
- " Any identifier or end-of-line
- call add(tl, [2, '\_i\+', ['a', 'b', ';', '2'], ['XX;XX']])
- " Any identifier but excluding digits or end-of-line
- call add(tl, [2, '\_I\+', ['a', 'b', ';', '2'], ['XX;XX2XX']])
- " Any keyword or end-of-line
- call add(tl, [2, '\_k\+', ['a', 'b', '=', '2'], ['XX=XX']])
- " Any keyword but excluding digits or end-of-line
- call add(tl, [2, '\_K\+', ['a', 'b', '=', '2'], ['XX=XX2XX']])
- " Any filename character or end-of-line
- call add(tl, [2, '\_f\+', ['a', 'b', '.', '5'], ['XX']])
- " Any filename character but excluding digits or end-of-line
- call add(tl, [2, '\_F\+', ['a', 'b', '.', '5'], ['XX5XX']])
- " Any printable character or end-of-line
- call add(tl, [2, '\_p\+', ['a', 'b', '=', '4'], ['XX']])
- " Any printable character excluding digits or end-of-line
- call add(tl, [2, '\_P\+', ['a', 'b', '=', '4'], ['XX4XX']])
- " Any whitespace character or end-of-line
- call add(tl, [2, '\_s\+', [' ', ' ', 'a', 'b'], ['XXaXXbXX']])
- " Any non-whitespace character or end-of-line
- call add(tl, [2, '\_S\+', [' ', ' ', 'a', 'b'], [' XX XX']])
- " Any decimal digit or end-of-line
- call add(tl, [2, '\_d\+', ['1', 'a', '2', 'b', '3'], ['XXaXXbXX']])
- " Any non-decimal digit or end-of-line
- call add(tl, [2, '\_D\+', ['1', 'a', '2', 'b', '3'], ['1XX2XX3XX']])
- " Any hexadecimal digit or end-of-line
- call add(tl, [2, '\_x\+', ['1', 'a', 'g', '9', '8'], ['XXgXX']])
- " Any non-hexadecimal digit or end-of-line
- call add(tl, [2, '\_X\+', ['1', 'a', 'g', '9', '8'], ['1XXaXX9XX8XX']])
- " Any octal digit or end-of-line
- call add(tl, [2, '\_o\+', ['0', '7', '8', '9', '0'], ['XX8XX9XX']])
- " Any non-octal digit or end-of-line
- call add(tl, [2, '\_O\+', ['0', '7', '8', '9', '0'], ['0XX7XX0XX']])
- " Any word character or end-of-line
- call add(tl, [2, '\_w\+', ['A', 'B', '=', 'C', 'D'], ['XX=XX']])
- " Any non-word character or end-of-line
- call add(tl, [2, '\_W\+', ['A', 'B', '=', 'C', 'D'], ['AXXBXXCXXDXX']])
- " Any head-of-word character or end-of-line
- call add(tl, [2, '\_h\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']])
- " Any non-head-of-word character or end-of-line
- call add(tl, [2, '\_H\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']])
- " Any alphabetic character or end-of-line
- call add(tl, [2, '\_a\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']])
- " Any non-alphabetic character or end-of-line
- call add(tl, [2, '\_A\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']])
- " Any lowercase character or end-of-line
- call add(tl, [2, '\_l\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']])
- " Any non-lowercase character or end-of-line
- call add(tl, [2, '\_L\+', ['a', 'A', 'b', 'B'], ['aXXbXX']])
- " Any uppercase character or end-of-line
- call add(tl, [2, '\_u\+', ['a', 'A', 'b', 'B'], ['aXXbXX']])
- " Any non-uppercase character or end-of-line
- call add(tl, [2, '\_U\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']])
- " Collection or end-of-line
- call add(tl, [2, '\_[a-z]\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']])
- " start of line anywhere in the text
- call add(tl, [2, 'one\zs\_s*\_^\zetwo',
- \ ['', 'one', ' two', 'one', '', 'two'],
- \ ['', 'one', ' two', 'oneXXtwo']])
- " end of line anywhere in the text
- call add(tl, [2, 'one\zs\_$\_s*two',
- \ ['', 'one', ' two', 'one', '', 'two'], ['', 'oneXX', 'oneXX']])
-
- " 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 behind 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
-
-" Test for the \%V atom (match inside the visual area)
-func Regex_Match_Visual_Area()
- 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, '$'))
- %d
-endfunc
-
-" Check matching Visual area
-func Test_matching_visual_area()
- new
- set regexpengine=1
- call Regex_Match_Visual_Area()
- set regexpengine=2
- call Regex_Match_Visual_Area()
- set regexpengine&
- bwipe!
-endfunc
-
-" Check matching marks
-func Regex_Mark()
- 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, '$'))
- %d
-endfunc
-
-func Test_matching_marks()
- new
- set regexpengine=1
- call Regex_Mark()
- set regexpengine=2
- call Regex_Mark()
- 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 Regex_start_end_buffer()
- 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('.'))
- %d
-endfunc
-
-func Test_start_end_of_buffer_match()
- new
- set regexpengine=1
- call Regex_start_end_buffer()
- set regexpengine=2
- call Regex_start_end_buffer()
- bwipe!
-endfunc
-
-func Test_ze_before_zs()
- call assert_equal('', matchstr(' ', '\%#=1\ze \zs'))
- call assert_equal('', matchstr(' ', '\%#=2\ze \zs'))
- call assert_equal(repeat([''], 10), matchlist(' ', '\%#=1\ze \zs'))
- call assert_equal(repeat([''], 10), matchlist(' ', '\%#=2\ze \zs'))
-endfunc
-
-" Check for detecting error
-func Test_regexp_error()
- call assert_fails("call matchlist('x x', '\\%#=1 \\zs*')", 'E888:')
- call assert_fails("call matchlist('x x', '\\%#=1 \\ze*')", 'E888:')
- call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:')
- call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:')
- call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:')
- call assert_fails("call matchstr('abcd', '\\%o841\\%o142')", 'E678:')
- call assert_equal('', matchstr('abcd', '\%o181\%o142'))
-endfunc
-
-" Test for using the last substitute string pattern (~)
-func Test_regexp_last_subst_string()
- new
- s/bar/baz/e
- call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=1\~"), "baz")
- call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=2\~"), "baz")
- close!
-endfunc
-
-" Check patterns matching cursor position.
-func s:curpos_test2()
- new
- call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
- \ '3 foobar eins zwei drei vier fünf sechse',
- \ '4 foobar eins zwei drei vier fünf sechse',
- \ '5 foobar eins zwei drei vier fünf sechse',
- \ '6 foobar eins zwei drei vier fünf sechse',
- \ '7 foobar eins zwei drei vier fünf sechse'])
- call setpos('.', [0, 2, 10, 0])
- s/\%.c.*//g
- call setpos('.', [0, 3, 15, 0])
- s/\%.l.*//g
- call setpos('.', [0, 5, 3, 0])
- s/\%.v.*/_/g
- call assert_equal(['1',
- \ '2 foobar ',
- \ '',
- \ '4 foobar eins zwei drei vier fünf sechse',
- \ '5 _',
- \ '6 foobar eins zwei drei vier fünf sechse',
- \ '7 foobar eins zwei drei vier fünf sechse'],
- \ getline(1, '$'))
- call assert_fails('call search("\\%.1l")', 'E1204:')
- call assert_fails('call search("\\%.1c")', 'E1204:')
- call assert_fails('call search("\\%.1v")', 'E1204:')
- bwipe!
-endfunc
-
-" Check patterns matching before or after cursor position.
-func s:curpos_test3()
- new
- call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
- \ '3 foobar eins zwei drei vier fünf sechse',
- \ '4 foobar eins zwei drei vier fünf sechse',
- \ '5 foobar eins zwei drei vier fünf sechse',
- \ '6 foobar eins zwei drei vier fünf sechse',
- \ '7 foobar eins zwei drei vier fünf sechse'])
- call setpos('.', [0, 2, 10, 0])
- " Note: This removes all columns, except for the column directly in front of
- " the cursor. Bug????
- :s/^.*\%<.c//
- call setpos('.', [0, 3, 10, 0])
- :s/\%>.c.*$//
- call setpos('.', [0, 5, 4, 0])
- " Note: This removes all columns, except for the column directly in front of
- " the cursor. Bug????
- :s/^.*\%<.v/_/
- call setpos('.', [0, 6, 4, 0])
- :s/\%>.v.*$/_/
- call assert_equal(['1',
- \ ' eins zwei drei vier fünf sechse',
- \ '3 foobar e',
- \ '4 foobar eins zwei drei vier fünf sechse',
- \ '_foobar eins zwei drei vier fünf sechse',
- \ '6 fo_',
- \ '7 foobar eins zwei drei vier fünf sechse'],
- \ getline(1, '$'))
- sil %d
- call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
- \ '3 foobar eins zwei drei vier fünf sechse',
- \ '4 foobar eins zwei drei vier fünf sechse',
- \ '5 foobar eins zwei drei vier fünf sechse',
- \ '6 foobar eins zwei drei vier fünf sechse',
- \ '7 foobar eins zwei drei vier fünf sechse'])
- call setpos('.', [0, 4, 4, 0])
- %s/\%<.l.*//
- call setpos('.', [0, 5, 4, 0])
- %s/\%>.l.*//
- call assert_equal(['', '', '',
- \ '4 foobar eins zwei drei vier fünf sechse',
- \ '5 foobar eins zwei drei vier fünf sechse',
- \ '', ''],
- \ getline(1, '$'))
- bwipe!
-endfunc
-
-" Test that matching below, at or after the
-" cursor position work
-func Test_matching_pos()
- for val in range(3)
- exe "set re=" .. val
- " Match at cursor position
- call s:curpos_test2()
- " Match before or after cursor position
- call s:curpos_test3()
- endfor
- set re&
-endfunc
-
-func Test_using_mark_position()
- " this was using freed memory
- " new engine
- new
- norm O0
- call assert_fails("s/\\%')", 'E486:')
- bwipe!
-
- " old engine
- new
- norm O0
- call assert_fails("s/\\%#=1\\%')", 'E486:')
- bwipe!
-endfunc
-
-func Test_using_visual_position()
- " this was using freed memory
- new
- exe "norm 0o\<Esc>\<C-V>k\<C-X>o0"
- /\%V
- bwipe!
-endfunc
-
-func Test_using_invalid_visual_position()
- " this was going beyond the end of the line
- new
- exe "norm 0o000\<Esc>0\<C-V>$s0"
- /\%V
- bwipe!
-endfunc
-
-func Test_using_two_engines_pattern()
- new
- call setline(1, ['foobar=0', 'foobar=1', 'foobar=2'])
- " \%#= at the end of the pattern
- for i in range(0, 2)
- for j in range(0, 2)
- exe "set re=" .. i
- call cursor(j + 1, 7)
- call assert_fails("%s/foobar\\%#=" .. j, 'E1281:')
- endfor
- endfor
- set re=0
-
- " \%#= at the start of the pattern
- for i in range(0, 2)
- call cursor(i + 1, 7)
- exe ":%s/\\%#=" .. i .. "foobar=" .. i .. "/xx"
- endfor
- call assert_equal(['xx', 'xx', 'xx'], getline(1, '$'))
- bwipe!
-endfunc
-
-func Test_recursive_substitute_expr()
- new
- func Repl()
- s
- endfunc
- silent! s/\%')/~\=Repl()
-
- bwipe!
- delfunc Repl
-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
deleted file mode 100644
index 2253242a7c..0000000000
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ /dev/null
@@ -1,603 +0,0 @@
-" Tests for regexp in utf8 encoding
-
-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ñńņňʼ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')
- " next statement confirms that equivalence class matches every
- " character in group
- call assert_match('^[[=' .. c .. '=]]*$', group1)
- for group2 in groups
- if group2 != group1
- " next statement converts that equivalence class doesn't match
- " character in any other group
- call assert_equal(-1, match(group2, '[[=' .. c .. '=]]'), c)
- endif
- endfor
- endfor
- endfor
-endfunc
-
-func Test_equivalence_re1()
- set re=1
- call s:equivalence_test()
- set re=0
-endfunc
-
-func Test_equivalence_re2()
- set re=2
- call s:equivalence_test()
- set re=0
-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:]]\+'))
-
- let alnumchars = ''
- let alphachars = ''
- let backspacechar = ''
- let blankchars = ''
- let cntrlchars = ''
- let digitchars = ''
- let escapechar = ''
- let graphchars = ''
- let lowerchars = ''
- let printchars = ''
- let punctchars = ''
- let returnchar = ''
- let spacechars = ''
- 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)
- if c =~ '[[:alpha:]]'
- let alphachars .= c
- endif
- if c =~ '[[:alnum:]]'
- let alnumchars .= c
- endif
- if c =~ '[[:backspace:]]'
- let backspacechar .= c
- endif
- if c =~ '[[:blank:]]'
- let blankchars .= c
- endif
- if c =~ '[[:cntrl:]]'
- let cntrlchars .= c
- endif
- if c =~ '[[:digit:]]'
- let digitchars .= c
- endif
- if c =~ '[[:escape:]]'
- let escapechar .= c
- endif
- if c =~ '[[:graph:]]'
- let graphchars .= c
- endif
- if c =~ '[[:lower:]]'
- let lowerchars .= c
- endif
- if c =~ '[[:print:]]'
- let printchars .= c
- endif
- if c =~ '[[:punct:]]'
- let punctchars .= c
- endif
- if c =~ '[[:return:]]'
- let returnchar .= c
- endif
- if c =~ '[[:space:]]'
- let spacechars .= c
- endif
- if c =~ '[[:tab:]]'
- let tabchar .= c
- endif
- if c =~ '[[:upper:]]'
- let upperchars .= c
- endif
- 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
-
- call assert_equal('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', alphachars)
- call assert_equal('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', alnumchars)
- call assert_equal("\b", backspacechar)
- call assert_equal("\t ", blankchars)
- call assert_equal("\x01\x02\x03\x04\x05\x06\x07\b\t\n\x0b\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f\x7f", cntrlchars)
- call assert_equal("0123456789", digitchars)
- call assert_equal("\<Esc>", escapechar)
- call assert_equal('!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~', graphchars)
- call assert_equal('abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ', lowerchars)
- call assert_equal(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ', printchars)
- call assert_equal('!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~', punctchars)
- call assert_equal('ABCDEFGHIJKLMNOPQRSTUVWXYZÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ', upperchars)
- call assert_equal("\r", returnchar)
- 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µÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- 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~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
- 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()
- set re=1
- call s:classes_test()
- set re=0
-endfunc
-
-func Test_classes_re2()
- set re=2
- call s:classes_test()
- set re=0
-endfunc
-
-func Test_recursive_substitute()
- new
- s/^/\=execute("s#^##gn")
- " check we are now not in the sandbox
- call setwinvar(1, 'myvar', 1)
- bwipe!
-endfunc
-
-func Test_nested_backrefs()
- " Check example in change.txt.
- new
- for re in range(0, 2)
- exe 'set re=' . re
- call setline(1, 'aa ab x')
- 1s/\(\(a[a-d] \)*\)\(x\)/-\1- -\2- -\3-/
- call assert_equal('-aa ab - -ab - -x-', getline(1))
-
- call assert_equal('-aa ab - -ab - -x-', substitute('aa ab x', '\(\(a[a-d] \)*\)\(x\)', '-\1- -\2- -\3-', ''))
- endfor
- bwipe!
- set re=0
-endfunc
-
-func Test_eow_with_optional()
- let expected = ['abc def', 'abc', 'def', '', '', '', '', '', '', '']
- for re in range(0, 2)
- exe 'set re=' . re
- let actual = matchlist('abc def', '\(abc\>\)\?\s*\(def\)')
- call assert_equal(expected, actual)
- endfor
-endfunc
-
-func Test_reversed_range()
- for re in range(0, 2)
- exe 'set re=' . re
- call assert_fails('call match("abc def", "[c-a]")', 'E944:', re)
- endfor
- set re=0
-endfunc
-
-func Test_large_class()
- set re=1
- call assert_fails('call match("abc def", "[\u3000-\u4000]")', 'E945:')
- set re=2
- call assert_equal(0, 'abc def' =~# '[\u3000-\u4000]')
- call assert_equal(1, "\u3042" =~# '[\u3000-\u4000]')
- set re=0
-endfunc
-
-func Test_optmatch_toolong()
- set re=1
- " Can only handle about 8000 characters.
- let pat = '\\%[' .. repeat('x', 9000) .. ']'
- call assert_fails('call match("abc def", "' .. pat .. '")', 'E339:')
- set re=0
-endfunc
-
-" 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.
- 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 n"ayn
- call assert_equal("a\ncat(", @a)
- close!
-endfunc
-
-" Check that "^" matches even when the line starts with a combining char
-func Test_match_start_of_line_combining()
- new
- call setline(1, ['', "\u05ae", ''])
- exe "normal gg/^\<CR>"
- call assert_equal(2, getcurpos()[1])
- bwipe!
-endfunc
-
-" Check that [[:upper:]] matches for automatic engine
-func Test_match_char_class_upper()
- new
-
- " Test 1: [[:upper:]]\{2,\}
- set regexpengine=0
- call setline(1, ['05. ПЕСÐЯ О ГЕРОЯХ муз. Ð. Давиденко, М. ÐšÐ¾Ð²Ð°Ð»Ñ Ð¸ Б. Шехтера ...', '05. PJESNJA O GJEROJAKH mus. A. Davidjenko, M. Kovalja i B. Shjekhtjera ...'])
- call cursor(1,1)
- let search_cmd='norm /\<[[:upper:]]\{2,\}\>' .. "\<CR>"
- exe search_cmd
- call assert_equal(4, searchcount().total, 'TEST 1')
- set regexpengine=1
- exe search_cmd
- call assert_equal(2, searchcount().total, 'TEST 1')
- set regexpengine=2
- exe search_cmd
- call assert_equal(4, searchcount().total, 'TEST 1')
-
- " Test 2: [[:upper:]].\+
- let search_cmd='norm /\<[[:upper:]].\+\>' .. "\<CR>"
- set regexpengine=0
- exe search_cmd
- call assert_equal(2, searchcount().total, 'TEST 2')
- set regexpengine=1
- exe search_cmd
- call assert_equal(1, searchcount().total, 'TEST 2')
- set regexpengine=2
- exe search_cmd
- call assert_equal(2, searchcount().total, 'TEST 2')
-
- " Test 3: [[:lower:]]\+
- let search_cmd='norm /\<[[:lower:]]\+\>' .. "\<CR>"
- set regexpengine=0
- exe search_cmd
- call assert_equal(4, searchcount().total, 'TEST 3 lower')
- set regexpengine=1
- exe search_cmd
- call assert_equal(2, searchcount().total, 'TEST 3 lower')
- set regexpengine=2
- exe search_cmd
- call assert_equal(4, searchcount().total, 'TEST 3 lower')
-
- " clean up
- set regexpengine=0
- bwipe!
-endfunc
-
-func Test_match_invalid_byte()
- call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid')
- new
- source Xinvalid
- bwipe!
- call delete('Xinvalid')
-endfunc
-
-func Test_match_too_complicated()
- set regexpengine=1
- exe "noswapfile vsplit \xeb\xdb\x99"
- silent! buf \&\zs*\zs*0
- bwipe!
- set regexpengine=0
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
deleted file mode 100644
index bbf1aa53b5..0000000000
--- a/src/nvim/testdir/test_registers.vim
+++ /dev/null
@@ -1,800 +0,0 @@
-" Tests for register operations
-
-source check.vim
-source view_util.vim
-
-" 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:')
- call assert_fails('put /', 'E35:')
- call assert_fails('put .', 'E29:')
-endfunc
-
-func Test_yank_shows_register()
- enew
- set report=0
- call setline(1, ['foo', 'bar'])
- " Line-wise
- exe 'norm! yy'
- call assert_equal('1 line yanked', v:statusmsg)
- exe 'norm! "zyy'
- call assert_equal('1 line yanked into "z', v:statusmsg)
- exe 'norm! yj'
- call assert_equal('2 lines yanked', v:statusmsg)
- exe 'norm! "zyj'
- call assert_equal('2 lines yanked into "z', v:statusmsg)
-
- " Block-wise
- exe "norm! \<C-V>y"
- call assert_equal('block of 1 line yanked', v:statusmsg)
- exe "norm! \<C-V>\"zy"
- call assert_equal('block of 1 line yanked into "z', v:statusmsg)
- exe "norm! \<C-V>jy"
- call assert_equal('block of 2 lines yanked', v:statusmsg)
- exe "norm! \<C-V>j\"zy"
- call assert_equal('block of 2 lines yanked into "z', v:statusmsg)
-
- bwipe!
-endfunc
-
-func Test_display_registers()
- " Disable clipboard
- let save_clipboard = get(g:, 'clipboard', {})
- let g:clipboard = {}
-
- e file1
- e file2
- call setline(1, ['foo', 'bar'])
- /bar
- exe 'norm! y2l"axx'
- call feedkeys("i\<C-R>=2*4\n\<esc>")
- call feedkeys(":ls\n", 'xt')
-
- let a = execute('display')
- let b = execute('registers')
-
- call assert_equal(a, b)
- call assert_match('^\nType Name Content\n'
- \ . ' c "" a\n'
- \ . ' c "0 ba\n'
- \ . ' c "a b\n'
- \ . '.*'
- \ . ' c "- a\n'
- \ . '.*'
- \ . ' c ": ls\n'
- \ . ' c "% file2\n'
- \ . ' c "# file1\n'
- \ . ' c "/ bar\n'
- \ . ' c "= 2\*4', a)
-
- let a = execute('registers a')
- call assert_match('^\nType Name Content\n'
- \ . ' c "a b', a)
-
- let a = execute('registers :')
- call assert_match('^\nType Name Content\n'
- \ . ' c ": ls', a)
-
- bwipe!
- let g:clipboard = save_clipboard
-endfunc
-
-func Test_register_one()
- " delete a line goes into register one
- new
- call setline(1, "one")
- normal dd
- call assert_equal("one\n", @1)
-
- " delete a word does not change register one, does change "-
- call setline(1, "two")
- normal de
- call assert_equal("one\n", @1)
- call assert_equal("two", @-)
-
- " delete a word with a register does not change register one
- call setline(1, "three")
- normal "ade
- call assert_equal("three", @a)
- call assert_equal("one\n", @1)
-
- " delete a word with register DOES change register one with one of a list of
- " operators
- " %
- call setline(1, ["(12)3"])
- normal "ad%
- call assert_equal("(12)", @a)
- call assert_equal("(12)", @1)
-
- " (
- call setline(1, ["first second"])
- normal $"ad(
- call assert_equal("first secon", @a)
- call assert_equal("first secon", @1)
-
- " )
- call setline(1, ["First Second."])
- normal gg0"ad)
- call assert_equal("First Second.", @a)
- call assert_equal("First Second.", @1)
-
- " `
- call setline(1, ["start here."])
- normal gg0fhmx0"ad`x
- call assert_equal("start ", @a)
- call assert_equal("start ", @1)
-
- " /
- call setline(1, ["searchX"])
- exe "normal gg0\"ad/X\<CR>"
- call assert_equal("search", @a)
- call assert_equal("search", @1)
-
- " ?
- call setline(1, ["Ysearch"])
- exe "normal gg$\"ad?Y\<CR>"
- call assert_equal("Ysearc", @a)
- call assert_equal("Ysearc", @1)
-
- " n
- call setline(1, ["Ynext"])
- normal gg$"adn
- call assert_equal("Ynex", @a)
- call assert_equal("Ynex", @1)
-
- " N
- call setline(1, ["prevY"])
- normal gg0"adN
- call assert_equal("prev", @a)
- call assert_equal("prev", @1)
-
- " }
- call setline(1, ["one", ""])
- normal gg0"ad}
- call assert_equal("one\n", @a)
- call assert_equal("one\n", @1)
-
- " {
- call setline(1, ["", "two"])
- normal 2G$"ad{
- call assert_equal("\ntw", @a)
- call assert_equal("\ntw", @1)
-
- bwipe!
-endfunc
-
-func Test_recording_status_in_ex_line()
- norm qx
- redraw!
- call assert_equal('recording @x', Screenline(&lines))
- set shortmess=q
- redraw!
- call assert_equal('recording', Screenline(&lines))
- set shortmess&
- norm q
- redraw!
- call assert_equal('', Screenline(&lines))
-endfunc
-
-" Check that replaying a typed sequence does not use an Esc and following
-" characters as an escape sequence.
-func Test_recording_esc_sequence()
- new
- try
- let save_F2 = &t_F2
- catch
- endtry
- let t_F2 = "\<Esc>OQ"
- call feedkeys("qqiTest\<Esc>", "xt")
- call feedkeys("OQuirk\<Esc>q", "xt")
- call feedkeys("Go\<Esc>@q", "xt")
- call assert_equal(['Quirk', 'Test', 'Quirk', 'Test'], getline(1, 4))
- bwipe!
- if exists('save_F2')
- let &t_F2 = save_F2
- else
- set t_F2=
- endif
-endfunc
-
-func Test_recording_with_select_mode()
- new
- call feedkeys("qacc12345\<Esc>gH98765\<Esc>q", "tx")
- call assert_equal("98765", getline(1))
- call assert_equal("cc12345\<Esc>gH98765\<Esc>", @a)
- call setline(1, 'asdf')
- normal! @a
- call assert_equal("98765", getline(1))
- bwipe!
-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)
-
- " Test for repeating the last command-line in visual mode
- call append(0, 'register')
- normal gg
- let @r = ''
- call feedkeys("v:yank R\<CR>", 'xt')
- call feedkeys("v@:", 'xt')
- call assert_equal("\nregister\nregister\n", @r)
-
- 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>"))
- " Change the last used register to '"' for the next test
- normal! ""yy
- let @" = 'happy'
- call assert_equal('happy', getreg())
- call assert_equal('happy', getreg(''))
-
- call assert_equal('', getregtype('!'))
- call assert_fails('echo getregtype([])', 'E730:')
- call assert_equal('v', getregtype())
- call assert_equal('v', getregtype(''))
-
- " Test for inserting an invalid register content
- call assert_beeps('exe "normal i\<C-R>!"')
-
- " Test for inserting a register with multiple lines
- call deletebufline('', 1, '$')
- call setreg('r', ['a', 'b'])
- exe "normal i\<C-R>r"
- call assert_equal(['a', 'b', ''], getline(1, '$'))
-
- " Test for inserting a multi-line register in the command line
- call feedkeys(":\<C-R>r\<Esc>", 'xt')
- " Nvim: no trailing CR because of #6137
- " call assert_equal("a\rb\r", histget(':', -1))
- call assert_equal("a\rb", histget(':', -1))
-
- call assert_fails('let r = getreg("=", [])', 'E745:')
- call assert_fails('let r = getreg("=", 1, [])', 'E745:')
- enew!
-
- " Using a register in operator-pending mode should fail
- call assert_beeps('norm! c"')
-endfunc
-
-func Test_set_register()
- call assert_fails("call setreg('#', 200)", 'E86:')
- " call assert_fails("call setreg('a', test_unknown())", 'E908:')
-
- 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('='))
-
- " Test for setting a list of lines to special registers
- call setreg('/', [])
- call assert_equal('', @/)
- call setreg('=', [])
- call assert_equal('', @=)
- call assert_fails("call setreg('/', ['a', 'b'])", 'E883:')
- call assert_fails("call setreg('=', ['a', 'b'])", 'E883:')
- call assert_equal(0, setreg('_', ['a', 'b']))
-
- " Test for recording to a invalid register
- call assert_beeps('normal q$')
-
- " Appending to a register when recording
- call append(0, "text for clipboard test")
- normal gg
- call feedkeys('qrllq', 'xt')
- call feedkeys('qRhhq', 'xt')
- call assert_equal('llhh', getreg('r'))
-
- " Appending a list of characters to a register from different lines
- let @r = ''
- call append(0, ['abcdef', '123456'])
- normal gg"ry3l
- call cursor(2, 4)
- normal "Ry3l
- call assert_equal('abc456', @r)
-
- " Test for gP with multiple lines selected using characterwise motion
- %delete
- call append(0, ['vim editor', 'vim editor'])
- let @r = ''
- exe "normal ggwy/vim /e\<CR>gP"
- call assert_equal(['vim editor', 'vim editor', 'vim editor'], getline(1, 3))
-
- " Test for gP with . register
- %delete
- normal iabc
- normal ".gp
- call assert_equal('abcabc', getline(1))
- normal 0".gP
- call assert_equal('abcabcabc', getline(1))
-
- let @"=''
- call setreg('', '1')
- call assert_equal('1', @")
- call setreg('@', '2')
- call assert_equal('2', @")
-
- enew!
-endfunc
-
-" Test for clipboard registers (* and +)
-func Test_clipboard_regs()
- throw 'skipped: needs clipboard=autoselect,autoselectplus'
-
- CheckNotGui
- CheckFeature clipboard_working
-
- new
- call append(0, "text for clipboard test")
- normal gg"*yiw
- call assert_equal('text', getreg('*'))
- normal gg2w"+yiw
- call assert_equal('clipboard', getreg('+'))
-
- " Test for replacing the clipboard register contents
- set clipboard=unnamed
- let @* = 'food'
- normal ggviw"*p
- call assert_equal('text', getreg('*'))
- call assert_equal('food for clipboard test', getline(1))
- normal ggviw"*p
- call assert_equal('food', getreg('*'))
- call assert_equal('text for clipboard test', getline(1))
-
- " Test for replacing the selection register contents
- set clipboard=unnamedplus
- let @+ = 'food'
- normal ggviw"+p
- call assert_equal('text', getreg('+'))
- call assert_equal('food for clipboard test', getline(1))
- normal ggviw"+p
- call assert_equal('food', getreg('+'))
- call assert_equal('text for clipboard test', getline(1))
-
- " Test for auto copying visually selected text to clipboard register
- call setline(1, "text for clipboard test")
- let @* = ''
- set clipboard=autoselect
- normal ggwwviwy
- call assert_equal('clipboard', @*)
-
- " Test for auto copying visually selected text to selection register
- let @+ = ''
- set clipboard=autoselectplus
- normal ggwviwy
- call assert_equal('for', @+)
-
- set clipboard&vim
- bwipe!
-endfunc
-
-" Test for restarting the current mode (insert or virtual replace) after
-" executing the contents of a register
-func Test_put_reg_restart_mode()
- new
- call append(0, 'editor')
- normal gg
- let @r = "ivim \<Esc>"
- call feedkeys("i\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
- call assert_equal('vimi editor', getline(1))
-
- call setline(1, 'editor')
- normal gg
- call feedkeys("gR\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
- call assert_equal('vimReditor', getline(1))
-
- bwipe!
-endfunc
-
-" Test for executing a register using :@ command
-func Test_execute_register()
- call setreg('r', [])
- call assert_beeps('@r')
- let i = 1
- let @q = 'let i+= 1'
- @q
- @
- call assert_equal(3, i)
-
- " try to execute expression register and use a backspace to cancel it
- new
- call feedkeys("@=\<BS>ax\<CR>y", 'xt')
- call assert_equal(['x', 'y'], getline(1, '$'))
- close!
-
- " cannot execute a register in operator pending mode
- call assert_beeps('normal! c@r')
-endfunc
-
-" Test for getting register info
-func Test_get_reginfo()
- enew
- call setline(1, ['foo', 'bar'])
-
- exe 'norm! "zyy'
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
- call setreg('y', 'baz')
- call assert_equal('z', getreginfo('').points_to)
- call setreg('y', { 'isunnamed': v:true })
- call assert_equal('y', getreginfo('"').points_to)
-
- exe '$put'
- call assert_equal(getreg('y'), getline(3))
- call setreg('', 'qux')
- call assert_equal('0', getreginfo('').points_to)
- call setreg('x', 'quux')
- call assert_equal('0', getreginfo('').points_to)
-
- let info = getreginfo('')
- call assert_equal(getreg('', 1, 1), info.regcontents)
- call assert_equal(getregtype(''), info.regtype)
-
- exe "norm! 0\<c-v>e" .. '"zy'
- let info = getreginfo('z')
- call assert_equal(getreg('z', 1, 1), info.regcontents)
- call assert_equal(getregtype('z'), info.regtype)
- call assert_equal(1, +info.isunnamed)
-
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
-
- let @a="a1b2"
- nnoremap <F2> <Cmd>let g:RegInfo = getreginfo()<CR>
- exe "normal \"a\<F2>"
- call assert_equal({'regcontents': ['a1b2'], 'isunnamed': v:false,
- \ 'regtype': 'v'}, g:RegInfo)
- nunmap <F2>
- unlet g:RegInfo
-
- " The type of "isunnamed" was VAR_SPECIAL but should be VAR_BOOL. Can only
- " be noticed when using json_encod().
- call setreg('a', 'foo')
- let reginfo = getreginfo('a')
- let expected = #{regcontents: ['foo'], isunnamed: v:false, regtype: 'v'}
- call assert_equal(json_encode(expected), json_encode(reginfo))
-
- bwipe!
-endfunc
-
-" Test for restoring register with dict from getreginfo
-func Test_set_register_dict()
- enew!
-
- call setreg('"', #{ regcontents: ['one', 'two'],
- \ regtype: 'V', points_to: 'z' })
- call assert_equal(['one', 'two'], getreg('"', 1, 1))
- let info = getreginfo('"')
- call assert_equal('z', info.points_to)
- call assert_equal('V', info.regtype)
- call assert_equal(1, +getreginfo('z').isunnamed)
-
- call setreg('x', #{ regcontents: ['three', 'four'],
- \ regtype: 'v', isunnamed: v:true })
- call assert_equal(['three', 'four'], getreg('"', 1, 1))
- let info = getreginfo('"')
- call assert_equal('x', info.points_to)
- call assert_equal('v', info.regtype)
- call assert_equal(1, +getreginfo('x').isunnamed)
-
- call setreg('y', #{ regcontents: 'five',
- \ regtype: "\<c-v>", isunnamed: v:false })
- call assert_equal("\<c-v>4", getreginfo('y').regtype)
- call assert_equal(0, +getreginfo('y').isunnamed)
- call assert_equal(['three', 'four'], getreg('"', 1, 1))
- call assert_equal('x', getreginfo('"').points_to)
-
- call setreg('"', #{ regcontents: 'six' })
- call assert_equal('0', getreginfo('"').points_to)
- call assert_equal(1, +getreginfo('0').isunnamed)
- call assert_equal(['six'], getreginfo('0').regcontents)
- call assert_equal(['six'], getreginfo('"').regcontents)
-
- let @x = 'one'
- call setreg('x', {})
- call assert_equal(1, len(split(execute('reg x'), '\n')))
-
- call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:')
- call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:')
- call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:')
-
- bwipe!
-endfunc
-
-func Test_v_register()
- enew
- call setline(1, 'nothing')
-
- func s:Put()
- let s:register = v:register
- exec 'normal! "' .. v:register .. 'P'
- endfunc
- nnoremap <buffer> <plug>(test) :<c-u>call s:Put()<cr>
- nmap <buffer> S <plug>(test)
-
- let @z = "testz\n"
- let @" = "test@\n"
-
- let s:register = ''
- call feedkeys('"_ddS', 'mx')
- call assert_equal('test@', getline('.')) " fails before 8.2.0929
- call assert_equal('"', s:register) " fails before 8.2.0929
-
- let s:register = ''
- call feedkeys('"zS', 'mx')
- call assert_equal('z', s:register)
-
- let s:register = ''
- call feedkeys('"zSS', 'mx')
- call assert_equal('"', s:register)
-
- let s:register = ''
- call feedkeys('"_S', 'mx')
- call assert_equal('_', s:register)
-
- let s:register = ''
- normal "_ddS
- call assert_equal('"', s:register) " fails before 8.2.0929
- call assert_equal('test@', getline('.')) " fails before 8.2.0929
-
- let s:register = ''
- execute 'normal "z:call' "s:Put()\n"
- call assert_equal('z', s:register)
- call assert_equal('testz', getline('.'))
-
- " Test operator and omap
- let @b = 'testb'
- func s:OpFunc(...)
- let s:register2 = v:register
- endfunc
- set opfunc=s:OpFunc
-
- normal "bg@l
- normal S
- call assert_equal('"', s:register) " fails before 8.2.0929
- call assert_equal('b', s:register2)
-
- func s:Motion()
- let s:register1 = v:register
- normal! l
- endfunc
- onoremap <buffer> Q :<c-u>call s:Motion()<cr>
-
- normal "bg@Q
- normal S
- call assert_equal('"', s:register)
- call assert_equal('b', s:register1)
- call assert_equal('"', s:register2)
-
- set opfunc&
- bwipe!
-endfunc
-
-" Test for executing the contents of a register as an Ex command with line
-" continuation.
-func Test_execute_reg_as_ex_cmd()
- " Line continuation with just two lines
- let code =<< trim END
- let l = [
- \ 1]
- END
- let @r = code->join("\n")
- let l = []
- @r
- call assert_equal([1], l)
-
- " Line continuation with more than two lines
- let code =<< trim END
- let l = [
- \ 1,
- \ 2,
- \ 3]
- END
- let @r = code->join("\n")
- let l = []
- @r
- call assert_equal([1, 2, 3], l)
-
- " use comments interspersed with code
- let code =<< trim END
- let l = [
- "\ one
- \ 1,
- "\ two
- \ 2,
- "\ three
- \ 3]
- END
- let @r = code->join("\n")
- let l = []
- @r
- call assert_equal([1, 2, 3], l)
-
- " use line continuation in the middle
- let code =<< trim END
- let a = "one"
- let l = [
- \ 1,
- \ 2]
- let b = "two"
- END
- let @r = code->join("\n")
- let l = []
- @r
- call assert_equal([1, 2], l)
- call assert_equal("one", a)
- call assert_equal("two", b)
-
- " only one line with a \
- let @r = "\\let l = 1"
- call assert_fails('@r', 'E10:')
-
- " only one line with a "\
- let @r = ' "\ let i = 1'
- @r
- call assert_false(exists('i'))
-
- " first line also begins with a \
- let @r = "\\let l = [\n\\ 1]"
- call assert_fails('@r', 'E10:')
-
- " Test with a large number of lines
- let @r = "let str = \n"
- let @r ..= repeat(" \\ 'abcdefghijklmnopqrstuvwxyz' ..\n", 312)
- let @r ..= ' \ ""'
- @r
- call assert_equal(repeat('abcdefghijklmnopqrstuvwxyz', 312), str)
-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
-
-func Test_insert_small_delete()
- new
- call setline(1, ['foo foobar bar'])
- call cursor(1,1)
- exe ":norm! ciw'\<C-R>-'"
- call assert_equal("'foo' foobar bar", getline(1))
- exe ":norm! w.w."
- call assert_equal("'foo' 'foobar' 'bar'", getline(1))
- bwipe!
-endfunc
-
-" Record in insert mode using CTRL-O
-func Test_record_in_insert_mode()
- new
- let @r = ''
- call setline(1, ['foo'])
- call feedkeys("i\<C-O>qrbaz\<C-O>q", 'xt')
- call assert_equal('baz', @r)
- bwipe!
-endfunc
-
-func Test_record_in_select_mode()
- new
- call setline(1, 'text')
- sil norm q00
- sil norm q
- call assert_equal('0ext', getline(1))
-
- %delete
- let @r = ''
- call setline(1, ['abc', 'abc', 'abc'])
- smap <F2> <Right><Right>,
- call feedkeys("qrgh\<F2>Dk\<Esc>q", 'xt')
- call assert_equal("gh\<F2>Dk\<Esc>", @r)
- norm j0@rj0@@
- call assert_equal([',Dk', ',Dk', ',Dk'], getline(1, 3))
- sunmap <F2>
-
- bwipe!
-endfunc
-
-" mapping that ends macro recording should be removed from recorded macro
-func Test_end_record_using_mapping()
- call setline(1, 'aaa')
- nnoremap s q
- call feedkeys('safas', 'tx')
- call assert_equal('fa', @a)
- nunmap s
-
- nnoremap xx q
- call feedkeys('0xxafaxx', 'tx')
- call assert_equal('fa', @a)
- nunmap xx
-
- nnoremap xsx q
- call feedkeys('0qafaxsx', 'tx')
- call assert_equal('fa', @a)
- nunmap xsx
-
- bwipe!
-endfunc
-
-func Test_end_reg_executing()
- nnoremap s <Nop>
- let @a = 's'
- call feedkeys("@aqaq\<Esc>", 'tx')
- call assert_equal('', @a)
- call assert_equal('', getline(1))
-
- call setline(1, 'aaa')
- nnoremap s qa
- let @a = 'fa'
- call feedkeys("@asq\<Esc>", 'tx')
- call assert_equal('', @a)
- call assert_equal('aaa', getline(1))
-
- nunmap s
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_reltime.vim b/src/nvim/testdir/test_reltime.vim
deleted file mode 100644
index f4ce5de118..0000000000
--- a/src/nvim/testdir/test_reltime.vim
+++ /dev/null
@@ -1,31 +0,0 @@
-" Tests for reltime()
-
-source check.vim
-CheckFeature reltime
-CheckFeature float
-
-func Test_reltime()
- let now = reltime()
- sleep 10m
- let later = reltime()
- let elapsed = now->reltime()
- call assert_true(reltimestr(elapsed) =~ '0\.0')
- call assert_true(elapsed->reltimestr() != '0.0')
- call assert_true(reltimefloat(elapsed) < 0.1)
- call assert_true(elapsed->reltimefloat() > 0.0)
-
- let same = reltime(now, now)
- call assert_equal('0.000', split(reltimestr(same))[0][:4])
- call assert_equal(0.0, reltimefloat(same))
-
- let differs = reltime(now, later)
- call assert_true(reltimestr(differs) =~ '0\.0')
- call assert_true(reltimestr(differs) != '0.0')
- call assert_true(reltimefloat(differs) < 0.1)
- call assert_true(reltimefloat(differs) > 0.0)
-
- call assert_equal(0, reltime({}))
- call assert_equal(0, reltime({}, {}))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim
deleted file mode 100644
index 5359b84923..0000000000
--- a/src/nvim/testdir/test_rename.vim
+++ /dev/null
@@ -1,120 +0,0 @@
-" Test rename()
-
-source shared.vim
-
-func Test_rename_file_to_file()
- call writefile(['foo'], 'Xrename1')
-
- call assert_equal(0, rename('Xrename1', 'Xrename2'))
-
- call assert_equal('', glob('Xrename1'))
- call assert_equal(['foo'], readfile('Xrename2'))
-
- " When the destination file already exists, it should be overwritten.
- call writefile(['foo'], 'Xrename1')
- call writefile(['bar'], 'Xrename2')
-
- call assert_equal(0, rename('Xrename1', 'Xrename2'))
- call assert_equal('', glob('Xrename1'))
- call assert_equal(['foo'], readfile('Xrename2'))
-
- call delete('Xrename2')
-endfunc
-
-func Test_rename_file_ignore_case()
- " With 'fileignorecase', renaming file will go through a temp file
- " when the source and destination file only differ by case.
- set fileignorecase
- call writefile(['foo'], 'Xrename')
-
- call assert_equal(0, 'Xrename'->rename('XRENAME'))
-
- call assert_equal(['foo'], readfile('XRENAME'))
-
- set fileignorecase&
- call delete('XRENAME')
-endfunc
-
-func Test_rename_same_file()
- call writefile(['foo'], 'Xrename')
-
- " When the source and destination are the same file, nothing
- " should be done. The source file should not be deleted.
- call assert_equal(0, rename('Xrename', 'Xrename'))
- call assert_equal(['foo'], readfile('Xrename'))
-
- call assert_equal(0, rename('./Xrename', 'Xrename'))
- call assert_equal(['foo'], readfile('Xrename'))
-
- call delete('Xrename')
-endfunc
-
-func Test_rename_dir_to_dir()
- call mkdir('Xrenamedir1')
- call writefile(['foo'], 'Xrenamedir1/Xrenamefile')
-
- call assert_equal(0, rename('Xrenamedir1', 'Xrenamedir2'))
-
- call assert_equal('', glob('Xrenamedir1'))
- call assert_equal(['foo'], readfile('Xrenamedir2/Xrenamefile'))
-
- call delete('Xrenamedir2/Xrenamefile')
- call delete('Xrenamedir2', 'd')
-endfunc
-
-func Test_rename_same_dir()
- call mkdir('Xrenamedir')
- call writefile(['foo'], 'Xrenamedir/Xrenamefile')
-
- call assert_equal(0, rename('Xrenamedir', 'Xrenamedir'))
-
- call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
-
- call delete('Xrenamedir/Xrenamefile')
- call delete('Xrenamedir', 'd')
-endfunc
-
-func Test_rename_copy()
- " Check that when original file can't be deleted, rename()
- " still succeeds but copies the file.
- call mkdir('Xrenamedir')
- call writefile(['foo'], 'Xrenamedir/Xrenamefile')
- call setfperm('Xrenamedir', 'r-xr-xr-x')
-
- call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile'))
-
- if !has('win32') && !IsRoot()
- " On Windows, the source file is removed despite
- " its directory being made not writable.
- call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
- endif
- call assert_equal(['foo'], readfile('Xrenamefile'))
-
- call setfperm('Xrenamedir', 'rwxrwxrwx')
- call delete('Xrenamedir/Xrenamefile')
- call delete('Xrenamedir', 'd')
- call delete('Xrenamefile')
-endfunc
-
-func Test_rename_fails()
- call writefile(['foo'], 'Xrenamefile')
-
- " Can't rename into a non-existing directory.
- call assert_notequal(0, rename('Xrenamefile', 'Xdoesnotexist/Xrenamefile'))
-
- " Can't rename a non-existing file.
- call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile2'))
- call assert_equal('', glob('Xrenamefile2'))
-
- " When rename() fails, the destination file should not be deleted.
- call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile'))
- call assert_equal(['foo'], readfile('Xrenamefile'))
-
- " Can't rename to en empty file name.
- call assert_notequal(0, rename('Xrenamefile', ''))
-
- call assert_fails('call rename("Xrenamefile", [])', 'E730')
- call assert_fails('call rename(0z, "Xrenamefile")', 'E976')
-
- call delete('Xrenamefile')
-endfunc
diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim
deleted file mode 100644
index a4f95053c0..0000000000
--- a/src/nvim/testdir/test_retab.vim
+++ /dev/null
@@ -1,117 +0,0 @@
-" Test :retab
-
-source check.vim
-
-func SetUp()
- new
- call setline(1, "\ta \t b c ")
-endfunc
-
-func TearDown()
- bwipe!
-endfunc
-
-func Retab(bang, n)
- let l:old_tabstop = &tabstop
- let l:old_line = getline(1)
- exe "retab" . a:bang . a:n
- let l:line = getline(1)
- call setline(1, l:old_line)
- if a:n > 0
- " :retab changes 'tabstop' to n with argument n > 0.
- call assert_equal(a:n, &tabstop)
- exe 'set tabstop=' . l:old_tabstop
- else
- " :retab does not change 'tabstop' with empty or n <= 0.
- call assert_equal(l:old_tabstop, &tabstop)
- endif
- return l:line
-endfunc
-
-func Test_retab()
- set tabstop=8 noexpandtab
- call assert_equal("\ta\t b c ", Retab('', ''))
- call assert_equal("\ta\t b c ", Retab('', 0))
- call assert_equal("\ta\t b c ", Retab('', 8))
- call assert_equal("\ta\t b\t c\t ", Retab('!', ''))
- call assert_equal("\ta\t b\t c\t ", Retab('!', 0))
- call assert_equal("\ta\t b\t c\t ", Retab('!', 8))
-
- call assert_equal("\t\ta\t\t\tb c ", Retab('', 4))
- call assert_equal("\t\ta\t\t\tb\t\t c\t ", Retab('!', 4))
-
- call assert_equal(" a\t\tb c ", Retab('', 10))
- call assert_equal(" a\t\tb c ", Retab('!', 10))
-
- set tabstop=8 expandtab
- call assert_equal(" a b c ", Retab('', ''))
- call assert_equal(" a b c ", Retab('', 0))
- call assert_equal(" a b c ", Retab('', 8))
- call assert_equal(" a b c ", Retab('!', ''))
- call assert_equal(" a b c ", Retab('!', 0))
- call assert_equal(" a b c ", Retab('!', 8))
-
- call assert_equal(" a b c ", Retab(' ', 4))
- call assert_equal(" a b c ", Retab('!', 4))
-
- call assert_equal(" a b c ", Retab(' ', 10))
- call assert_equal(" a b c ", Retab('!', 10))
-
- set tabstop=4 noexpandtab
- call assert_equal("\ta\t\tb c ", Retab('', ''))
- call assert_equal("\ta\t\tb\t\t c\t ", Retab('!', ''))
- call assert_equal("\t a\t\t\tb c ", Retab('', 3))
- call assert_equal("\t a\t\t\tb\t\t\tc\t ", Retab('!', 3))
- call assert_equal(" a\t b c ", Retab('', 5))
- call assert_equal(" a\t b\t\t c\t ", Retab('!', 5))
-
- set tabstop=4 expandtab
- call assert_equal(" a b c ", Retab('', ''))
- call assert_equal(" a b c ", Retab('!', ''))
- call assert_equal(" a b c ", Retab('', 3))
- call assert_equal(" a b c ", Retab('!', 3))
- call assert_equal(" a b c ", Retab('', 5))
- call assert_equal(" a b c ", Retab('!', 5))
-
- set tabstop& expandtab&
-endfunc
-
-func Test_retab_error()
- call assert_fails('retab -1', 'E487:')
- call assert_fails('retab! -1', 'E487:')
- call assert_fails('ret -1000', 'E487:')
- call assert_fails('ret 10000', 'E475:')
- call assert_fails('ret 80000000000000000000', 'E475:')
-endfunc
-
-func RetabLoop()
- while 1
- set ts=4000
- retab 4
- endwhile
-endfunc
-
-func Test_retab_endless()
- " inside try/catch we can catch the error message
- call setline(1, "\t0\t")
- let caught = 'no'
- try
- call RetabLoop()
- catch /E1240:/
- let caught = v:exception
- endtry
- call assert_match('E1240:', caught)
-
- set tabstop&
-endfunc
-
-func Test_nocatch_retab_endless()
- " when not inside try/catch an interrupt is generated to get out of loops
- call setline(1, "\t0\t")
- call assert_fails('call RetabLoop()', ['E1240:', 'Interrupted'])
-
- set tabstop&
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ruby.vim b/src/nvim/testdir/test_ruby.vim
deleted file mode 100644
index 1fbf3392d9..0000000000
--- a/src/nvim/testdir/test_ruby.vim
+++ /dev/null
@@ -1,417 +0,0 @@
-" Tests for ruby interface
-
-source check.vim
-CheckFeature ruby
-
-func Test_ruby_change_buffer()
- call setline(line('$'), ['1 line 1'])
- ruby Vim.command("normal /^1\n")
- ruby $curbuf.line = "1 changed line 1"
- call assert_equal('1 changed line 1', getline('$'))
-endfunc
-
-func Test_rubydo()
- throw 'skipped: TODO: '
- " Check deleting lines does not trigger ml_get error.
- new
- call setline(1, ['one', 'two', 'three'])
- rubydo Vim.command("%d_")
- bwipe!
-
- " Check switching to another buffer does not trigger ml_get error.
- new
- let wincount = winnr('$')
- call setline(1, ['one', 'two', 'three'])
- rubydo Vim.command("new")
- call assert_equal(wincount + 1, winnr('$'))
- %bwipe!
-endfunc
-
-func Test_rubydo_dollar_underscore()
- throw 'skipped: TODO: '
- new
- call setline(1, ['one', 'two', 'three', 'four'])
- 2,3rubydo $_ = '[' + $_ + ']'
- call assert_equal(['one', '[two]', '[three]', 'four'], getline(1, '$'))
- bwipe!
-
- call assert_fails('rubydo $_ = 0', 'E265:')
- call assert_fails('rubydo (')
- bwipe!
-endfunc
-
-func Test_rubyfile()
- " Check :rubyfile does not SEGV with Ruby level exception but just fails
- let tempfile = tempname() . '.rb'
- call writefile(['raise "vim!"'], tempfile)
- call assert_fails('rubyfile ' . tempfile)
- call delete(tempfile)
-endfunc
-
-func Test_ruby_set_cursor()
- " Check that setting the cursor position works.
- new
- call setline(1, ['first line', 'second line'])
- normal gg
- rubydo $curwin.cursor = [1, 5]
- call assert_equal([1, 6], [line('.'), col('.')])
- call assert_equal([1, 5], rubyeval('$curwin.cursor'))
-
- " Check that movement after setting cursor position keeps current column.
- normal j
- call assert_equal([2, 6], [line('.'), col('.')])
- call assert_equal([2, 5], '$curwin.cursor'->rubyeval())
-
- " call assert_fails('ruby $curwin.cursor = [1]',
- " \ 'ArgumentError: array length must be 2')
- bwipe!
-endfunc
-
-" Test buffer.count and buffer.length (number of lines in buffer)
-func Test_ruby_buffer_count()
- new
- call setline(1, ['one', 'two', 'three'])
- call assert_equal(3, rubyeval('$curbuf.count'))
- call assert_equal(3, rubyeval('$curbuf.length'))
- bwipe!
-endfunc
-
-" Test buffer.name (buffer name)
-func Test_ruby_buffer_name()
- new Xfoo
- call assert_equal(expand('%:p'), rubyeval('$curbuf.name'))
- bwipe
- call assert_equal('', rubyeval('$curbuf.name'))
-endfunc
-
-" Test buffer.number (number of the buffer).
-func Test_ruby_buffer_number()
- new
- call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
- new
- call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
-
- %bwipe
-endfunc
-
-" Test buffer.delete({n}) (delete line {n})
-func Test_ruby_buffer_delete()
- new
- call setline(1, ['one', 'two', 'three'])
- ruby $curbuf.delete(2)
- call assert_equal(['one', 'three'], getline(1, '$'))
-
- " call assert_fails('ruby $curbuf.delete(0)', 'IndexError: line number 0 out of range')
- " call assert_fails('ruby $curbuf.delete(3)', 'IndexError: line number 3 out of range')
- call assert_fails('ruby $curbuf.delete(3)', 'RuntimeError: Index out of bounds')
-
- bwipe!
-endfunc
-
-" Test buffer.append({str}, str) (append line {str} after line {n})
-func Test_ruby_buffer_append()
- new
- ruby $curbuf.append(0, 'one')
- ruby $curbuf.append(1, 'three')
- ruby $curbuf.append(1, 'two')
- ruby $curbuf.append(4, 'four')
-
- call assert_equal(['one', 'two', 'three', '', 'four'], getline(1, '$'))
-
- " call assert_fails('ruby $curbuf.append(-1, "x")',
- " \ 'IndexError: line number -1 out of range')
- call assert_fails('ruby $curbuf.append(-1, "x")',
- \ 'ArgumentError: Index out of bounds')
- call assert_fails('ruby $curbuf.append(6, "x")',
- \ 'RuntimeError: Index out of bounds')
-
- bwipe!
-endfunc
-
-" Test buffer.line (get or set the current line)
-func Test_ruby_buffer_line()
- new
- call setline(1, ['one', 'two', 'three'])
- 2
- call assert_equal('two', rubyeval('$curbuf.line'))
-
- ruby $curbuf.line = 'TWO'
- call assert_equal(['one', 'TWO', 'three'], getline(1, '$'))
-
- bwipe!
-endfunc
-
-" Test buffer.line_number (get current line number)
-func Test_ruby_buffer_line_number()
- new
- call setline(1, ['one', 'two', 'three'])
- 2
- call assert_equal(2, rubyeval('$curbuf.line_number'))
-
- bwipe!
-endfunc
-
-func Test_ruby_buffer_get()
- new
- call setline(1, ['one', 'two'])
- call assert_equal('one', rubyeval('$curbuf[1]'))
- call assert_equal('two', rubyeval('$curbuf[2]'))
-
- " call assert_fails('ruby $curbuf[0]',
- " \ 'IndexError: line number 0 out of range')
- call assert_fails('ruby $curbuf[3]',
- \ 'RuntimeError: Index out of bounds')
-
- bwipe!
-endfunc
-
-func Test_ruby_buffer_set()
- new
- call setline(1, ['one', 'two'])
- ruby $curbuf[2] = 'TWO'
- ruby $curbuf[1] = 'ONE'
-
- " call assert_fails('ruby $curbuf[0] = "ZERO"',
- " \ 'IndexError: line number 0 out of range')
- " call assert_fails('ruby $curbuf[3] = "THREE"',
- " \ 'IndexError: line number 3 out of range')
- call assert_fails('ruby $curbuf[3] = "THREE"',
- \ 'RuntimeError: Index out of bounds')
- bwipe!
-endfunc
-
-" Test window.width (get or set window height).
-func Test_ruby_window_height()
- new
-
- " Test setting window height
- ruby $curwin.height = 2
- call assert_equal(2, winheight(0))
-
- " Test getting window height
- call assert_equal(2, rubyeval('$curwin.height'))
-
- bwipe
-endfunc
-
-" Test window.width (get or set window width).
-func Test_ruby_window_width()
- vnew
-
- " Test setting window width
- ruby $curwin.width = 2
- call assert_equal(2, winwidth(0))
-
- " Test getting window width
- call assert_equal(2, rubyeval('$curwin.width'))
-
- bwipe
-endfunc
-
-" Test window.buffer (get buffer object of a window object).
-func Test_ruby_window_buffer()
- new Xfoo1
- new Xfoo2
- ruby $b2 = $curwin.buffer
- ruby $w2 = $curwin
- wincmd j
- ruby $b1 = $curwin.buffer
- ruby $w1 = $curwin
-
- " call assert_equal(rubyeval('$b1'), rubyeval('$w1.buffer'))
- " call assert_equal(rubyeval('$b2'), rubyeval('$w2.buffer'))
- call assert_equal(bufnr('Xfoo1'), rubyeval('$w1.buffer.number'))
- call assert_equal(bufnr('Xfoo2'), rubyeval('$w2.buffer.number'))
-
- ruby $b1, $w1, $b2, $w2 = nil
- %bwipe
-endfunc
-
-" Test Vim::Window.current (get current window object)
-func Test_ruby_Vim_window_current()
- let cw = rubyeval('$curwin.to_s')
- " call assert_equal(cw, rubyeval('Vim::Window.current'))
- call assert_match('^#<Neovim::Window:0x\x\+>$', cw)
-endfunc
-
-" Test Vim::Window.count (number of windows)
-func Test_ruby_Vim_window_count()
- new Xfoo1
- new Xfoo2
- split
- call assert_equal(4, rubyeval('Vim::Window.count'))
- %bwipe
- call assert_equal(1, rubyeval('Vim::Window.count'))
-endfunc
-
-" Test Vim::Window[n] (get window object of window n)
-func Test_ruby_Vim_window_get()
- new Xfoo1
- new Xfoo2
- call assert_match('Xfoo2$', rubyeval('Vim::Window[0].buffer.name'))
- wincmd j
- call assert_match('Xfoo1$', rubyeval('Vim::Window[1].buffer.name'))
- wincmd j
- call assert_equal('', rubyeval('Vim::Window[2].buffer.name'))
- %bwipe
-endfunc
-
-" Test Vim::Buffer.current (return the buffer object of current buffer)
-func Test_ruby_Vim_buffer_current()
- let cb = rubyeval('$curbuf.to_s')
- " call assert_equal(cb, rubyeval('Vim::Buffer.current'))
- call assert_match('^#<Neovim::Buffer:0x\x\+>$', cb)
-endfunc
-
-" Test Vim::Buffer:.count (return the number of buffers)
-func Test_ruby_Vim_buffer_count()
- new Xfoo1
- new Xfoo2
- call assert_equal(3, rubyeval('Vim::Buffer.count'))
- %bwipe
- call assert_equal(1, rubyeval('Vim::Buffer.count'))
-endfunc
-
-" Test Vim::buffer[n] (return the buffer object of buffer number n)
-func Test_ruby_Vim_buffer_get()
- new Xfoo1
- new Xfoo2
-
- " Index of Vim::Buffer[n] goes from 0 to the number of buffers.
- call assert_equal('', rubyeval('Vim::Buffer[0].name'))
- call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name'))
- call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name'))
- call assert_fails('ruby print Vim::Buffer[3].name',
- \ "NoMethodError: undefined method `name' for nil:NilClass")
- %bwipe
-endfunc
-
-" Test Vim::command({cmd}) (execute a Ex command))
-" Test Vim::command({cmd})
-func Test_ruby_Vim_command()
- new
- call setline(1, ['one', 'two', 'three', 'four'])
- ruby Vim::command('2,3d')
- call assert_equal(['one', 'four'], getline(1, '$'))
- bwipe!
-endfunc
-
-" Test Vim::set_option (set a vim option)
-func Test_ruby_Vim_set_option()
- call assert_equal(0, &number)
- ruby Vim::set_option('number')
- call assert_equal(1, &number)
- ruby Vim::set_option('nonumber')
- call assert_equal(0, &number)
-endfunc
-
-func Test_ruby_Vim_evaluate()
- call assert_equal(123, rubyeval('Vim::evaluate("123")'))
- " Vim::evaluate("123").class gives Integer or Fixnum depending
- " on versions of Ruby.
- call assert_match('^Integer\|Fixnum$', rubyeval('Vim::evaluate("123").class'))
-
- if has('float')
- call assert_equal(1.23, rubyeval('Vim::evaluate("1.23")'))
- call assert_equal('Float', rubyeval('Vim::evaluate("1.23").class'))
- endif
-
- call assert_equal('foo', rubyeval('Vim::evaluate("\"foo\"")'))
- call assert_equal('String', rubyeval('Vim::evaluate("\"foo\"").class'))
-
- call assert_equal([1, 2], rubyeval('Vim::evaluate("[1, 2]")'))
- call assert_equal('Array', rubyeval('Vim::evaluate("[1, 2]").class'))
-
- call assert_equal({'1': 2}, rubyeval('Vim::evaluate("{1:2}")'))
- call assert_equal('Hash', rubyeval('Vim::evaluate("{1:2}").class'))
-
- call assert_equal(v:null, rubyeval('Vim::evaluate("v:null")'))
- call assert_equal('NilClass', rubyeval('Vim::evaluate("v:null").class'))
-
- " call assert_equal(v:null, rubyeval('Vim::evaluate("v:none")'))
- " call assert_equal('NilClass', rubyeval('Vim::evaluate("v:none").class'))
-
- call assert_equal(v:true, rubyeval('Vim::evaluate("v:true")'))
- call assert_equal('TrueClass', rubyeval('Vim::evaluate("v:true").class'))
- call assert_equal(v:false, rubyeval('Vim::evaluate("v:false")'))
- call assert_equal('FalseClass',rubyeval('Vim::evaluate("v:false").class'))
-endfunc
-
-func Test_ruby_Vim_evaluate_list()
- call setline(line('$'), ['2 line 2'])
- ruby Vim.command("normal /^2\n")
- let l = ["abc", "def"]
- ruby << EOF
- curline = $curbuf.line_number
- l = Vim.evaluate("l");
- $curbuf.append(curline, l.join("|"))
-EOF
- normal j
- .rubydo $_ = $_.gsub(/\|/, '/')
- call assert_equal('abc/def', getline('$'))
-endfunc
-
-func Test_ruby_Vim_evaluate_dict()
- let d = {'a': 'foo', 'b': 123}
- redir => l:out
- ruby d = Vim.evaluate("d"); print d
- redir END
- call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n"))
-endfunc
-
-" Test Vim::message({msg}) (display message {msg})
-func Test_ruby_Vim_message()
- throw 'skipped: TODO: '
- ruby Vim::message('A message')
- let messages = split(execute('message'), "\n")
- call assert_equal('A message', messages[-1])
-endfunc
-
-func Test_ruby_print()
- func RubyPrint(expr)
- return trim(execute('ruby print ' . a:expr))
- endfunc
-
- call assert_equal('123', RubyPrint('123'))
- call assert_equal('1.23', RubyPrint('1.23'))
- call assert_equal('Hello World!', RubyPrint('"Hello World!"'))
- call assert_equal('[1, 2]', RubyPrint('[1, 2]'))
- call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})'))
- call assert_equal('true', RubyPrint('true'))
- call assert_equal('false', RubyPrint('false'))
- call assert_equal('', RubyPrint('nil'))
- call assert_match('Vim', RubyPrint('Vim'))
- call assert_match('Module', RubyPrint('Vim.class'))
-
- delfunc RubyPrint
-endfunc
-
-func Test_ruby_p()
- ruby p 'Just a test'
- let messages = GetMessages()
- call assert_equal('"Just a test"', messages[-1])
-
- " Check return values of p method
-
- call assert_equal(123, rubyeval('p(123)'))
- call assert_equal([1, 2, 3], rubyeval('p(1, 2, 3)'))
-
- " Avoid the "message maintainer" line.
- let $LANG = ''
- messages clear
- call assert_equal(v:true, rubyeval('p() == nil'))
-
- let messages = GetMessages()
- call assert_equal(0, len(messages))
-endfunc
-
-func Test_rubyeval_error()
- " On Linux or Windows the error matches:
- " "syntax error, unexpected end-of-input"
- " whereas on macOS in CI, the error message makes less sense:
- " "SyntaxError: array length must be 2"
- " Unclear why. The test does not check the error message.
- call assert_fails('call rubyeval("(")')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_scriptnames.vim b/src/nvim/testdir/test_scriptnames.vim
deleted file mode 100644
index 44ec146666..0000000000
--- a/src/nvim/testdir/test_scriptnames.vim
+++ /dev/null
@@ -1,32 +0,0 @@
-" Test for :scriptnames
-
-func Test_scriptnames()
- call writefile(['let did_load_script = 123'], 'Xscripting')
- source Xscripting
- call assert_equal(123, g:did_load_script)
-
- let scripts = split(execute('scriptnames'), "\n")
- let last = scripts[-1]
- call assert_match('\<Xscripting\>', last)
- let lastnr = substitute(last, '\D*\(\d\+\):.*', '\1', '')
- exe 'script ' . lastnr
- call assert_equal('Xscripting', expand('%:t'))
-
- call assert_fails('script ' . (lastnr + 1), 'E474:')
- call assert_fails('script 0', 'E939:')
-
- new
- call setline(1, 'nothing')
- call assert_fails('script ' . lastnr, 'E37:')
- exe 'script! ' . lastnr
- call assert_equal('Xscripting', expand('%:t'))
-
- bwipe
- call delete('Xscripting')
-
- let msgs = execute('messages')
- scriptnames
- call assert_equal(msgs, execute('messages'))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_scroll_opt.vim b/src/nvim/testdir/test_scroll_opt.vim
deleted file mode 100644
index 77920eb8b0..0000000000
--- a/src/nvim/testdir/test_scroll_opt.vim
+++ /dev/null
@@ -1,36 +0,0 @@
-" Test for reset 'scroll'
-"
-
-func Test_reset_scroll()
- let scr = &l:scroll
-
- setlocal scroll=1
- setlocal scroll&
- call assert_equal(scr, &l:scroll)
-
- setlocal scroll=1
- setlocal scroll=0
- call assert_equal(scr, &l:scroll)
-
- try
- execute 'setlocal scroll=' . (winheight(0) + 1)
- " not reached
- call assert_false(1)
- catch
- call assert_exception('E49:')
- endtry
-
- split
-
- let scr = &l:scroll
-
- setlocal scroll=1
- setlocal scroll&
- call assert_equal(scr, &l:scroll)
-
- setlocal scroll=1
- setlocal scroll=0
- call assert_equal(scr, &l:scroll)
-
- quit!
-endfunc
diff --git a/src/nvim/testdir/test_scrollbind.vim b/src/nvim/testdir/test_scrollbind.vim
deleted file mode 100644
index 6c5488be05..0000000000
--- a/src/nvim/testdir/test_scrollbind.vim
+++ /dev/null
@@ -1,272 +0,0 @@
-" Test for 'scrollbind' causing an unexpected scroll of one of the windows.
-func Test_scrollbind()
- " We don't want the status line to cause problems:
- set laststatus=0
- let totalLines = &lines * 20
- let middle = totalLines / 2
- new | only
- for i in range(1, totalLines)
- call setline(i, 'LINE ' . i)
- endfor
- exe string(middle)
- normal zt
- normal M
- aboveleft vert new
- for i in range(1, totalLines)
- call setline(i, 'line ' . i)
- endfor
- exe string(middle)
- normal zt
- normal M
- " Execute the following two commands at once to reproduce the problem.
- setl scb | wincmd p
- setl scb
- wincmd w
- let topLineLeft = line('w0')
- wincmd p
- let topLineRight = line('w0')
- setl noscrollbind
- wincmd p
- setl noscrollbind
- call assert_equal(0, topLineLeft - topLineRight)
-endfunc
-
-" Test for 'scrollbind'
-func Test_scrollbind_opt()
- new | only
- set noscrollbind
- set scrollopt=ver,jump scrolloff=2 nowrap noequalalways splitbelow
-
- " Insert the text used for the test
- append
-
-
-start of window 1
-. line 01 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 01
-. line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02
-. line 03 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 03
-. line 04 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 04
-. line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05
-. line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06
-. line 07 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 07
-. line 08 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 08
-. line 09 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 09
-. line 10 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 10
-. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11
-. line 12 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 12
-. line 13 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 13
-. line 14 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 14
-. line 15 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 15
-end of window 1
-
-
-start of window 2
-. line 01 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 01
-. line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02
-. line 03 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 03
-. line 04 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 04
-. line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05
-. line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06
-. line 07 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 07
-. line 08 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 08
-. line 09 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 09
-. line 10 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 10
-. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11
-. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12
-. line 13 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 13
-. line 14 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 14
-. line 15 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 15
-. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16
-end of window 2
-
-.
-
- " Test using two windows open to one buffer, one extra empty window
- split
- new
- wincmd t
- resize 8
- call search('^start of window 1$')
- normal zt
- set scrollbind
- wincmd j
- resize 7
- call search('^start of window 2$')
- normal zt
- set scrollbind
-
- " -- start of tests --
- " Test scrolling down
- normal L5jHyy
- wincmd b | normal pr0
- wincmd t | normal Hyy
- wincmd b | normal pr1
- wincmd t | normal L6jHyy
- wincmd b | normal pr2
- wincmd k | normal Hyy
- wincmd b | normal pr3
-
- " Test scrolling up
- wincmd t | normal H4k
- wincmd j | normal H
- wincmd t | normal Hyy
- wincmd b | normal pr4
- wincmd k | normal Hyy
- wincmd b | normal pr5
- wincmd k | normal 3k
- wincmd t | normal H
- wincmd j | normal Hyy
- wincmd b | normal pr6
- wincmd t | normal Hyy
- wincmd b | normal pr7
-
- " Test horizontal scrolling
- set scrollopt+=hor
- normal gg"zyyG"zpG
- wincmd t | normal 015zly$
- wincmd b | normal p"zpG
- wincmd k | normal y$
- wincmd b | normal p"zpG
- wincmd k | normal 10jH7zhg0y$
- wincmd b | normal p"zpG
- wincmd t | normal Hg0y$
- wincmd b | normal p"zpG
- set scrollopt-=hor
-
- wincmd b
- call assert_equal([
- \ '',
- \ '0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05',
- \ '1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05',
- \ '2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
- \ '3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
- \ '4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06',
- \ '5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06',
- \ '6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02',
- \ '7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
- \ '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
- \ 'UTSRQPONMLKJIHGREDCBA9876543210 02',
- \ '. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
- \ '. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
- \ ''], getline(1, '$'))
- enew!
-
- " ****** tests using two different buffers *****
- wincmd t | wincmd j | close
- wincmd t | set noscrollbind
- /start of window 2$/,/^end of window 2$/y
- new
- wincmd t | wincmd j | normal 4"zpGp
- wincmd t
- call search('^start of window 1$')
- normal zt
- set scrollbind
- wincmd j
- call search('^start of window 2$')
- normal zt
- set scrollbind
-
- " -- start of tests --
- " Test scrolling down
- normal L5jHyy
- wincmd b | normal pr0
- wincmd t | normal Hyy
- wincmd b | normal pr1
- wincmd t | normal L6jHyy
- wincmd b | normal pr2
- wincmd k | normal Hyy
- wincmd b | normal pr3
-
- " Test scrolling up
- wincmd t | normal H4k
- wincmd j | normal H
- wincmd t | normal Hyy
- wincmd b | normal pr4
- wincmd k | normal Hyy
- wincmd b | normal pr5
- wincmd k | normal 3k
- wincmd t | normal H
- wincmd j | normal Hyy
- wincmd b | normal pr6
- wincmd t | normal Hyy
- wincmd b | normal pr7
-
- " Test horizontal scrolling
- set scrollopt+=hor
- normal gg"zyyG"zpG
- wincmd t | normal 015zly$
- wincmd b | normal p"zpG
- wincmd k | normal y$
- wincmd b | normal p"zpG
- wincmd k | normal 10jH7zhg0y$
- wincmd b | normal p"zpG
- wincmd t | normal Hg0y$
- wincmd b | normal p"zpG
- set scrollopt-=hor
-
- wincmd b
- call assert_equal([
- \ '',
- \ '0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05',
- \ '1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05',
- \ '2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
- \ '3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
- \ '4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06',
- \ '5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06',
- \ '6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02',
- \ '7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
- \ '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
- \ 'UTSRQPONMLKJIHGREDCBA9876543210 02',
- \ '. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
- \ '. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
- \ ''], getline(1, '$'))
- enew!
-
- " Test 'syncbind'
- wincmd t | set noscrollbind | normal ggL
- wincmd j | set noscrollbind | normal ggL
- set scrollbind
- wincmd t | set scrollbind | normal G
- wincmd j | normal G
- syncbind
- normal Hk
- wincmd t | normal H
- wincmd j | normal Hyy
- wincmd b | normal p
- wincmd t | normal yy
- wincmd b | normal p
- wincmd t | set noscrollbind | normal ggL
- wincmd j | set noscrollbind
- normal ggL
- set scrollbind
- wincmd t | set scrollbind
- wincmd t | normal G
- wincmd j | normal G
- wincmd t | syncbind | normal Hk
- wincmd j | normal H
- wincmd t | normal Hyy
- wincmd b | normal p
- wincmd t | wincmd j | normal yy
- wincmd b | normal p
- wincmd t | normal H3k
- wincmd j | normal H
- wincmd t | normal Hyy
- wincmd b | normal p
- wincmd t | wincmd j | normal yy
- wincmd b | normal p
-
- wincmd b
- call assert_equal([
- \ '',
- \ '. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16',
- \ 'start of window 2',
- \ 'start of window 2',
- \ '. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16',
- \ '. line 15 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 15',
- \ '. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12',
- \ ], getline(1, '$'))
- enew!
-
- new | only!
- set scrollbind& scrollopt& scrolloff& wrap& equalalways& splitbelow&
-endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
deleted file mode 100644
index 885043accf..0000000000
--- a/src/nvim/testdir/test_search.vim
+++ /dev/null
@@ -1,2109 +0,0 @@
-" Test for the search command
-
-source shared.vim
-source screendump.vim
-source check.vim
-
-" See test/functional/legacy/search_spec.lua
-func Test_search_cmdline()
- CheckFunction test_override
- CheckOption incsearch
-
- " need to disable char_avail,
- " so that expansion of commandline works
- call test_override("char_avail", 1)
- new
- call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar'])
- " Test 1
- " CTRL-N / CTRL-P skips through the previous search history
- set noincsearch
- :1
- call feedkeys("/foobar\<cr>", 'tx')
- call feedkeys("/the\<cr>", 'tx')
- call assert_equal('the', @/)
- call feedkeys("/thes\<C-P>\<C-P>\<cr>", 'tx')
- call assert_equal('foobar', @/)
-
- " Test 2
- " Ctrl-G goes from one match to the next
- " until the end of the buffer
- set incsearch nowrapscan
- :1
- " first match
- call feedkeys("/the\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- :1
- " second match
- call feedkeys("/the\<C-G>\<cr>", 'tx')
- call assert_equal(' 3 the', getline('.'))
- call assert_equal([0, 0, 0, 0], getpos('"'))
- :1
- " third match
- call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
- call assert_equal(' 4 their', getline('.'))
- :1
- " fourth match
- call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
- call assert_equal(' 5 there', getline('.'))
- :1
- " fifth match
- call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
- call assert_equal(' 6 their', getline('.'))
- :1
- " sixth match
- call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
- call assert_equal(' 7 the', getline('.'))
- :1
- " seventh match
- call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
- call assert_equal(' 8 them', getline('.'))
- :1
- " eighth match
- call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- :1
- " no further match
- call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- call assert_equal([0, 0, 0, 0], getpos('"'))
-
- " Test 3
- " Ctrl-G goes from one match to the next
- " and continues back at the top
- set incsearch wrapscan
- :1
- " first match
- call feedkeys("/the\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- :1
- " second match
- call feedkeys("/the\<C-G>\<cr>", 'tx')
- call assert_equal(' 3 the', getline('.'))
- :1
- " third match
- call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
- call assert_equal(' 4 their', getline('.'))
- :1
- " fourth match
- call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
- call assert_equal(' 5 there', getline('.'))
- :1
- " fifth match
- call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
- call assert_equal(' 6 their', getline('.'))
- :1
- " sixth match
- call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
- call assert_equal(' 7 the', getline('.'))
- :1
- " seventh match
- call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
- call assert_equal(' 8 them', getline('.'))
- :1
- " eighth match
- call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- :1
- " back at first match
- call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
-
- " Test 4
- " CTRL-T goes to the previous match
- set incsearch nowrapscan
- $
- " first match
- call feedkeys("?the\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- $
- " first match
- call feedkeys("?the\<C-G>\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- $
- " second match
- call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
- call assert_equal(' 8 them', getline('.'))
- $
- " last match
- call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- $
- " last match
- call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
-
- " Test 5
- " CTRL-T goes to the previous match
- set incsearch wrapscan
- $
- " first match
- call feedkeys("?the\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- $
- " first match at the top
- call feedkeys("?the\<C-G>\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- $
- " second match
- call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
- call assert_equal(' 8 them', getline('.'))
- $
- " last match
- call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- $
- " back at the bottom of the buffer
- call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
-
- " Test 6
- " CTRL-L adds to the search pattern
- set incsearch wrapscan
- 1
- " first match
- call feedkeys("/the\<c-l>\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- 1
- " go to next match of 'thes'
- call feedkeys("/the\<c-l>\<C-G>\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- 1
- " wrap around
- call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- 1
- " wrap around
- set nowrapscan
- call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
-
- " Test 7
- " <bs> remove from match, but stay at current match
- set incsearch wrapscan
- 1
- " first match
- call feedkeys("/thei\<cr>", 'tx')
- call assert_equal(' 4 their', getline('.'))
- 1
- " delete one char, add another
- call feedkeys("/thei\<bs>s\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- 1
- " delete one char, add another, go to previous match, add one char
- call feedkeys("/thei\<bs>s\<bs>\<C-T>\<c-l>\<cr>", 'tx')
- call assert_equal(' 9 these', getline('.'))
- 1
- " delete all chars, start from the beginning again
- call feedkeys("/them". repeat("\<bs>",4).'the\>'."\<cr>", 'tx')
- call assert_equal(' 3 the', getline('.'))
-
- " clean up
- call test_override("char_avail", 0)
- bw!
-endfunc
-
-" See test/functional/legacy/search_spec.lua
-func Test_search_cmdline2()
- CheckFunction test_override
- CheckOption incsearch
-
- " need to disable char_avail,
- " so that expansion of commandline works
- call test_override("char_avail", 1)
- new
- call setline(1, [' 1', ' 2 these', ' 3 the theother'])
- " Test 1
- " Ctrl-T goes correctly back and forth
- set incsearch
- 1
- " first match
- call feedkeys("/the\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- 1
- " go to next match (on next line)
- call feedkeys("/the\<C-G>\<cr>", 'tx')
- call assert_equal(' 3 the theother', getline('.'))
- 1
- " go to next match (still on line 3)
- call feedkeys("/the\<C-G>\<C-G>\<cr>", 'tx')
- call assert_equal(' 3 the theother', getline('.'))
- 1
- " go to next match (still on line 3)
- call feedkeys("/the\<C-G>\<C-G>\<C-G>\<cr>", 'tx')
- call assert_equal(' 3 the theother', getline('.'))
- 1
- " go to previous match (on line 3)
- call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<cr>", 'tx')
- call assert_equal(' 3 the theother', getline('.'))
- 1
- " go to previous match (on line 3)
- call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<cr>", 'tx')
- call assert_equal(' 3 the theother', getline('.'))
- 1
- " go to previous match (on line 2)
- call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<C-T>\<cr>", 'tx')
- call assert_equal(' 2 these', getline('.'))
- 1
- " go to previous match (on line 2)
- call feedkeys("/the\<C-G>\<C-R>\<C-W>\<cr>", 'tx')
- call assert_equal('theother', @/)
-
- " Test 2: keep the view,
- " after deleting a character from the search cmd
- call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar'])
- resize 5
- 1
- call feedkeys("/foo\<bs>\<cr>", 'tx')
- redraw
- call assert_equal({'lnum': 10, 'leftcol': 0, 'col': 4, 'topfill': 0, 'topline': 6, 'coladd': 0, 'skipcol': 0, 'curswant': 4}, winsaveview())
-
- " remove all history entries
- for i in range(11)
- call histdel('/')
- endfor
-
- " Test 3: reset the view,
- " after deleting all characters from the search cmd
- norm! 1gg0
- " unfortunately, neither "/foo\<c-w>\<cr>", nor "/foo\<bs>\<bs>\<bs>\<cr>",
- " nor "/foo\<c-u>\<cr>" works to delete the commandline.
- " In that case Vim should return "E35 no previous regular expression",
- " but it looks like Vim still sees /foo and therefore the test fails.
- " Therefore, disabling this test
- "call assert_fails(feedkeys("/foo\<c-w>\<cr>", 'tx'), 'E35')
- "call assert_equal({'lnum': 1, 'leftcol': 0, 'col': 0, 'topfill': 0, 'topline': 1, 'coladd': 0, 'skipcol': 0, 'curswant': 0}, winsaveview())
-
- " clean up
- set noincsearch
- call test_override("char_avail", 0)
- bw!
-endfunc
-
-func Test_use_sub_pat()
- split
- let @/ = ''
- func X()
- s/^/a/
- /
- endfunc
- call X()
- bwipe!
-endfunc
-
-func Test_searchpair()
- new
- call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
-
- " should not give an error for using "42"
- call assert_equal(0, searchpair('a', 'b', 'c', '', 42))
-
- 4
- call assert_equal(3, searchpair('\[', '', ']', 'bW'))
- call assert_equal([0, 3, 2, 0], getpos('.'))
- 4
- call assert_equal(2, searchpair('\[', '', ']', 'bWr'))
- call assert_equal([0, 2, 6, 0], getpos('.'))
- 4
- call assert_equal(1, searchpair('\[', '', ']', 'bWm'))
- call assert_equal([0, 3, 2, 0], getpos('.'))
- 4|norm ^
- call assert_equal(5, searchpair('\[', '', ']', 'Wn'))
- call assert_equal([0, 4, 2, 0], getpos('.'))
- 4
- call assert_equal(2, searchpair('\[', '', ']', 'bW',
- \ 'getline(".") =~ "^\\s*\["'))
- call assert_equal([0, 2, 6, 0], getpos('.'))
- set nomagic
- 4
- call assert_equal(3, searchpair('\[', '', ']', 'bW'))
- call assert_equal([0, 3, 2, 0], getpos('.'))
- set magic
- 4|norm ^
- call assert_equal(0, searchpair('{', '', '}', 'bW'))
- call assert_equal([0, 4, 2, 0], getpos('.'))
-
- %d
- call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
-
- /\<if 1
- call assert_equal(5, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
- call assert_equal([0, 5, 1, 0], getpos('.'))
- /\<if 2
- call assert_equal(3, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
- call assert_equal([0, 3, 3, 0], getpos('.'))
-
- q!
-endfunc
-
-func Test_searchpairpos()
- new
- call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
-
- 4
- call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
- call assert_equal([0, 3, 2, 0], getpos('.'))
- 4
- call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
- call assert_equal([0, 2, 6, 0], getpos('.'))
- 4|norm ^
- call assert_equal([5, 2], searchpairpos('\[', '', ']', 'Wn'))
- call assert_equal([0, 4, 2, 0], getpos('.'))
- 4
- call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bW',
- \ 'getline(".") =~ "^\\s*\["'))
- call assert_equal([0, 2, 6, 0], getpos('.'))
- 4
- call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
- call assert_equal([0, 2, 6, 0], getpos('.'))
- set nomagic
- 4
- call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
- call assert_equal([0, 3, 2, 0], getpos('.'))
- set magic
- 4|norm ^
- call assert_equal([0, 0], searchpairpos('{', '', '}', 'bW'))
- call assert_equal([0, 4, 2, 0], getpos('.'))
-
- %d
- call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
- /\<if 1
- call assert_equal([5, 1], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
- call assert_equal([0, 5, 1, 0], getpos('.'))
- /\<if 2
- call assert_equal([3, 3], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
- call assert_equal([0, 3, 3, 0], getpos('.'))
-
- q!
-endfunc
-
-func Test_searchpair_errors()
- call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String')
- call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
- call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
- call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
- call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
- call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
- call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
- call assert_fails("call searchpair('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
-endfunc
-
-func Test_searchpairpos_errors()
- call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String')
- call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
- call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
- call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
- call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
- call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
- call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
- call assert_fails("call searchpairpos('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
-endfunc
-
-func Test_searchpair_skip()
- func Zero()
- return 0
- endfunc
- func Partial(x)
- return a:x
- endfunc
- new
- call setline(1, ['{', 'foo', 'foo', 'foo', '}'])
- 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', ''))
- 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '0'))
- 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', {-> 0}))
- 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Zero')))
- 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Partial', [0])))
- bw!
-endfunc
-
-func Test_searchpair_leak()
- new
- call setline(1, 'if one else another endif')
-
- " The error in the skip expression caused memory to leak.
- call assert_fails("call searchpair('\\<if\\>', '\\<else\\>', '\\<endif\\>', '', '\"foo\" 2')", 'E15:')
-
- bwipe!
-endfunc
-
-func Test_searchc()
- " These commands used to cause memory overflow in searchc().
- new
- norm ixx
- exe "norm 0t\u93cf"
- bw!
-endfunc
-
-func Cmdline3_prep()
- CheckFunction test_override
- " need to disable char_avail,
- " so that expansion of commandline works
- call test_override("char_avail", 1)
- new
- call setline(1, [' 1', ' 2 the~e', ' 3 the theother'])
- set incsearch
-endfunc
-
-func Incsearch_cleanup()
- CheckFunction test_override
- set noincsearch
- call test_override("char_avail", 0)
- bw!
-endfunc
-
-func Test_search_cmdline3()
- CheckOption incsearch
-
- call Cmdline3_prep()
- 1
- " first match
- call feedkeys("/the\<c-l>\<cr>", 'tx')
- call assert_equal(' 2 the~e', getline('.'))
-
- call Incsearch_cleanup()
-endfunc
-
-func Test_search_cmdline3s()
- CheckOption incsearch
-
- call Cmdline3_prep()
- 1
- call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
- call assert_equal(' 2 xxxe', getline('.'))
- undo
- call feedkeys(":%subs/the\<c-l>/xxx\<cr>", 'tx')
- call assert_equal(' 2 xxxe', getline('.'))
- undo
- call feedkeys(":%substitute/the\<c-l>/xxx\<cr>", 'tx')
- call assert_equal(' 2 xxxe', getline('.'))
- undo
- call feedkeys(":%smagic/the.e/xxx\<cr>", 'tx')
- call assert_equal(' 2 xxx', getline('.'))
- undo
- call assert_fails(":%snomagic/the.e/xxx\<cr>", 'E486')
- "
- call feedkeys(":%snomagic/the\\.e/xxx\<cr>", 'tx')
- call assert_equal(' 2 xxx', getline('.'))
-
- call Incsearch_cleanup()
-endfunc
-
-func Test_search_cmdline3g()
- CheckOption incsearch
-
- call Cmdline3_prep()
- 1
- call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
- call assert_equal(' 3 the theother', getline(2))
- undo
- call feedkeys(":global/the\<c-l>/d\<cr>", 'tx')
- call assert_equal(' 3 the theother', getline(2))
- undo
- call feedkeys(":g!/the\<c-l>/d\<cr>", 'tx')
- call assert_equal(1, line('$'))
- call assert_equal(' 2 the~e', getline(1))
- undo
- call feedkeys(":global!/the\<c-l>/d\<cr>", 'tx')
- call assert_equal(1, line('$'))
- call assert_equal(' 2 the~e', getline(1))
-
- call Incsearch_cleanup()
-endfunc
-
-func Test_search_cmdline3v()
- CheckOption incsearch
-
- call Cmdline3_prep()
- 1
- call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
- call assert_equal(1, line('$'))
- call assert_equal(' 2 the~e', getline(1))
- undo
- call feedkeys(":vglobal/the\<c-l>/d\<cr>", 'tx')
- call assert_equal(1, line('$'))
- call assert_equal(' 2 the~e', getline(1))
-
- call Incsearch_cleanup()
-endfunc
-
-" See test/functional/legacy/search_spec.lua
-func Test_search_cmdline4()
- CheckFunction test_override
- CheckOption incsearch
-
- " need to disable char_avail,
- " so that expansion of commandline works
- call test_override("char_avail", 1)
- new
- call setline(1, [' 1 the first', ' 2 the second', ' 3 the third'])
- set incsearch
- $
- call feedkeys("?the\<c-g>\<cr>", 'tx')
- call assert_equal(' 3 the third', getline('.'))
- $
- call feedkeys("?the\<c-g>\<c-g>\<cr>", 'tx')
- call assert_equal(' 1 the first', getline('.'))
- $
- call feedkeys("?the\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
- call assert_equal(' 2 the second', getline('.'))
- $
- call feedkeys("?the\<c-t>\<cr>", 'tx')
- call assert_equal(' 1 the first', getline('.'))
- $
- call feedkeys("?the\<c-t>\<c-t>\<cr>", 'tx')
- call assert_equal(' 3 the third', getline('.'))
- $
- call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx')
- call assert_equal(' 2 the second', getline('.'))
- " clean up
- set noincsearch
- call test_override("char_avail", 0)
- bw!
-endfunc
-
-func Test_search_cmdline5()
- CheckOption incsearch
-
- " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work
- " regardless char_avail.
- new
- call setline(1, [' 1 the first', ' 2 the second', ' 3 the third', ''])
- set incsearch
- 1
- call feedkeys("/the\<c-g>\<c-g>\<cr>", 'tx')
- call assert_equal(' 3 the third', getline('.'))
- $
- call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx')
- call assert_equal(' 1 the first', getline('.'))
- " clean up
- set noincsearch
- bw!
-endfunc
-
-func Test_search_cmdline6()
- " Test that consecutive matches
- " are caught by <c-g>/<c-t>
- CheckFunction test_override
- CheckOption incsearch
-
- " need to disable char_avail,
- " so that expansion of commandline works
- call test_override("char_avail", 1)
- new
- call setline(1, [' bbvimb', ''])
- set incsearch
- " first match
- norm! gg0
- call feedkeys("/b\<cr>", 'tx')
- call assert_equal([0,1,2,0], getpos('.'))
- " second match
- norm! gg0
- call feedkeys("/b\<c-g>\<cr>", 'tx')
- call assert_equal([0,1,3,0], getpos('.'))
- " third match
- norm! gg0
- call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx')
- call assert_equal([0,1,7,0], getpos('.'))
- " first match again
- norm! gg0
- call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
- call assert_equal([0,1,2,0], getpos('.'))
- set nowrapscan
- " last match
- norm! gg0
- call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
- call assert_equal([0,1,7,0], getpos('.'))
- " clean up
- set wrapscan&vim
- set noincsearch
- call test_override("char_avail", 0)
- bw!
-endfunc
-
-func Test_search_cmdline7()
- CheckFunction test_override
- " Test that pressing <c-g> in an empty command line
- " does not move the cursor
- if !exists('+incsearch')
- return
- endif
- " need to disable char_avail,
- " so that expansion of commandline works
- call test_override("char_avail", 1)
- new
- let @/ = 'b'
- call setline(1, [' bbvimb', ''])
- set incsearch
- " first match
- norm! gg0
- " moves to next match of previous search pattern, just like /<cr>
- call feedkeys("/\<c-g>\<cr>", 'tx')
- call assert_equal([0,1,2,0], getpos('.'))
- " moves to next match of previous search pattern, just like /<cr>
- call feedkeys("/\<cr>", 'tx')
- call assert_equal([0,1,3,0], getpos('.'))
- " moves to next match of previous search pattern, just like /<cr>
- call feedkeys("/\<c-t>\<cr>", 'tx')
- call assert_equal([0,1,7,0], getpos('.'))
-
- " using an offset uses the last search pattern
- call cursor(1, 1)
- call setline(1, ['1 bbvimb', ' 2 bbvimb'])
- let @/ = 'b'
- call feedkeys("//e\<c-g>\<cr>", 'tx')
- call assert_equal('1 bbvimb', getline('.'))
- call assert_equal(4, col('.'))
-
- set noincsearch
- call test_override("char_avail", 0)
- bw!
-endfunc
-
-func Test_search_cmdline8()
- " Highlighting is cleared in all windows
- " since hls applies to all windows
- CheckOption incsearch
- CheckFeature terminal
- CheckNotGui
- if has("win32")
- throw "Skipped: Bug with sending <ESC> to terminal window not fixed yet"
- endif
-
- let h = winheight(0)
- if h < 3
- return
- endif
- " Prepare buffer text
- let lines = ['abb vim vim vi', 'vimvivim']
- call writefile(lines, 'Xsearch.txt')
- let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})
-
- call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
-
- call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
- call term_sendkeys(buf, ":14vsp\<cr>")
- call term_sendkeys(buf, "/vim\<cr>")
- call term_sendkeys(buf, "/b\<esc>")
- call term_sendkeys(buf, "gg0")
- call TermWait(buf, 250)
- let screen_line = term_scrape(buf, 1)
- let [a0,a1,a2,a3] = [screen_line[3].attr, screen_line[4].attr,
- \ screen_line[18].attr, screen_line[19].attr]
- call assert_notequal(a0, a1)
- call assert_notequal(a0, a3)
- call assert_notequal(a1, a2)
- call assert_equal(a0, a2)
- call assert_equal(a1, a3)
- " clean up
- call delete('Xsearch.txt')
-
- bwipe!
-endfunc
-
-" Tests for regexp with various magic settings
-func Run_search_regexp_magic_opt()
- put ='1 a aa abb abbccc'
- exe 'normal! /a*b\{2}c\+/e' . "\<CR>"
- call assert_equal([0, 2, 17, 0], getpos('.'))
-
- put ='2 d dd dee deefff'
- exe 'normal! /\Md\*e\{2}f\+/e' . "\<CR>"
- call assert_equal([0, 3, 17, 0], getpos('.'))
-
- set nomagic
- put ='3 g gg ghh ghhiii'
- exe 'normal! /g\*h\{2}i\+/e' . "\<CR>"
- call assert_equal([0, 4, 17, 0], getpos('.'))
-
- put ='4 j jj jkk jkklll'
- exe 'normal! /\mj*k\{2}l\+/e' . "\<CR>"
- call assert_equal([0, 5, 17, 0], getpos('.'))
-
- put ='5 m mm mnn mnnooo'
- exe 'normal! /\vm*n{2}o+/e' . "\<CR>"
- call assert_equal([0, 6, 17, 0], getpos('.'))
-
- put ='6 x ^aa$ x'
- exe 'normal! /\V^aa$' . "\<CR>"
- call assert_equal([0, 7, 5, 0], getpos('.'))
-
- set magic
- put ='7 (a)(b) abbaa'
- exe 'normal! /\v(a)(b)\2\1\1/e' . "\<CR>"
- call assert_equal([0, 8, 14, 0], getpos('.'))
-
- put ='8 axx [ab]xx'
- exe 'normal! /\V[ab]\(\[xy]\)\1' . "\<CR>"
- call assert_equal([0, 9, 7, 0], getpos('.'))
-
- %d
-endfunc
-
-func Test_search_regexp()
- enew!
-
- set regexpengine=1
- call Run_search_regexp_magic_opt()
- set regexpengine=2
- call Run_search_regexp_magic_opt()
- set regexpengine&
-
- set undolevels=100
- put ='9 foobar'
- put =''
- exe "normal! a\<C-G>u\<Esc>"
- normal G
- exe 'normal! dv?bar?' . "\<CR>"
- call assert_equal('9 foo', getline('.'))
- call assert_equal([0, 2, 5, 0], getpos('.'))
- call assert_equal(2, line('$'))
- normal u
- call assert_equal('9 foobar', getline('.'))
- call assert_equal([0, 2, 6, 0], getpos('.'))
- call assert_equal(3, line('$'))
-
- set undolevels&
- enew!
-endfunc
-
-func Test_search_cmdline_incsearch_highlight()
- CheckFunction test_override
- CheckOption incsearch
-
- set incsearch hlsearch
- " need to disable char_avail,
- " so that expansion of commandline works
- call test_override("char_avail", 1)
- new
- call setline(1, ['aaa 1 the first', ' 2 the second', ' 3 the third'])
-
- 1
- call feedkeys("/second\<cr>", 'tx')
- call assert_equal('second', @/)
- call assert_equal(' 2 the second', getline('.'))
-
- " Canceling search won't change @/
- 1
- let @/ = 'last pattern'
- call feedkeys("/third\<C-c>", 'tx')
- call assert_equal('last pattern', @/)
- call feedkeys("/third\<Esc>", 'tx')
- call assert_equal('last pattern', @/)
- call feedkeys("/3\<bs>\<bs>", 'tx')
- call assert_equal('last pattern', @/)
- call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx')
- call assert_equal('last pattern', @/)
-
- " clean up
- set noincsearch nohlsearch
- bw!
-endfunc
-
-func Test_search_cmdline_incsearch_highlight_attr()
- CheckOption incsearch
- CheckFeature terminal
- CheckNotGui
-
- let h = winheight(0)
- if h < 3
- return
- endif
-
- " Prepare buffer text
- let lines = ['abb vim vim vi', 'vimvivim']
- call writefile(lines, 'Xsearch.txt')
- let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})
-
- call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
- " wait for vim to complete initialization
- call term_wait(buf)
-
- " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight
- call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
- call term_sendkeys(buf, '/b')
- call term_wait(buf, 200)
- let screen_line1 = term_scrape(buf, 1)
- call assert_true(len(screen_line1) > 2)
- " a0: attr_normal
- let a0 = screen_line1[0].attr
- " a1: attr_incsearch
- let a1 = screen_line1[1].attr
- " a2: attr_hlsearch
- let a2 = screen_line1[2].attr
- call assert_notequal(a0, a1)
- call assert_notequal(a0, a2)
- call assert_notequal(a1, a2)
- call term_sendkeys(buf, "\<cr>gg0")
-
- " Test incremental highlight search
- call term_sendkeys(buf, "/vim")
- call term_wait(buf, 200)
- " Buffer:
- " abb vim vim vi
- " vimvivim
- " Search: /vim
- let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0]
- let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
- call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
- call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
-
- " Test <C-g>
- call term_sendkeys(buf, "\<C-g>\<C-g>")
- call term_wait(buf, 200)
- let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
- let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2]
- call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
- call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
-
- " Test <C-t>
- call term_sendkeys(buf, "\<C-t>")
- call term_wait(buf, 200)
- let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0]
- let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
- call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
- call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
-
- " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight)
- call term_sendkeys(buf, "\<cr>")
- call term_wait(buf, 200)
- let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
- let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
- call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
- call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
-
- " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight)
- call term_sendkeys(buf, ":1\<cr>")
- call term_sendkeys(buf, ":set nohlsearch\<cr>")
- call term_sendkeys(buf, "/vim")
- call term_wait(buf, 200)
- let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0]
- let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0]
- call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
- call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
- call delete('Xsearch.txt')
-
- call delete('Xsearch.txt')
- bwipe!
-endfunc
-
-func Test_incsearch_cmdline_modifier()
- CheckFunction test_override
- CheckOption incsearch
-
- call test_override("char_avail", 1)
- new
- call setline(1, ['foo'])
- set incsearch
- " Test that error E14 does not occur in parsing command modifier.
- call feedkeys("V:tab", 'tx')
-
- call Incsearch_cleanup()
-endfunc
-
-func Test_incsearch_scrolling()
- CheckRunVimInTerminal
- 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_incsearch_search_dump()
- CheckOption incsearch
- CheckScreendump
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'for n in range(1, 8)',
- \ ' call setline(n, "foo " . n)',
- \ 'endfor',
- \ '3',
- \ ], 'Xis_search_script')
- let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 100m
-
- " Need to send one key at a time to force a redraw.
- call term_sendkeys(buf, '/fo')
- call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
- call term_sendkeys(buf, "\<Esc>")
- sleep 100m
-
- call term_sendkeys(buf, '/\v')
- call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_search_script')
-endfunc
-
-func Test_hlsearch_dump()
- CheckOption hlsearch
- CheckScreendump
-
- call writefile([
- \ 'set hlsearch cursorline',
- \ 'call setline(1, ["xxx", "xxx", "xxx"])',
- \ '/.*',
- \ '2',
- \ ], 'Xhlsearch_script')
- let buf = RunVimInTerminal('-S Xhlsearch_script', {'rows': 6, 'cols': 50})
- call VerifyScreenDump(buf, 'Test_hlsearch_1', {})
-
- call term_sendkeys(buf, "/\\_.*\<CR>")
- call VerifyScreenDump(buf, 'Test_hlsearch_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xhlsearch_script')
-endfunc
-
-func Test_hlsearch_and_visual()
- CheckOption hlsearch
- CheckScreendump
-
- call writefile([
- \ 'set hlsearch',
- \ 'call setline(1, repeat(["xxx yyy zzz"], 3))',
- \ 'hi Search cterm=bold',
- \ '/yyy',
- \ 'call cursor(1, 6)',
- \ ], 'Xhlvisual_script')
- let buf = RunVimInTerminal('-S Xhlvisual_script', {'rows': 6, 'cols': 40})
- call term_sendkeys(buf, "vjj")
- call VerifyScreenDump(buf, 'Test_hlsearch_visual_1', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xhlvisual_script')
-endfunc
-
-func Test_hlsearch_block_visual_match()
- CheckScreendump
-
- let lines =<< trim END
- set hlsearch
- call setline(1, ['aa', 'bbbb', 'cccccc'])
- END
- call writefile(lines, 'Xhlsearch_block')
- let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60})
-
- call term_sendkeys(buf, "G\<C-V>$kk\<Esc>")
- sleep 100m
- call term_sendkeys(buf, "/\\%V\<CR>")
- sleep 100m
- call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {})
-
- call StopVimInTerminal(buf)
- call delete('Xhlsearch_block')
-endfunc
-
-func Test_incsearch_substitute()
- CheckFunction test_override
- CheckOption incsearch
-
- call test_override("char_avail", 1)
- new
- set incsearch
- for n in range(1, 10)
- call setline(n, 'foo ' . n)
- endfor
- 4
- call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
- call assert_equal('foo 3', getline(3))
- call assert_equal('xxx 4', getline(4))
- call assert_equal('xxx 5', getline(5))
- call assert_equal('xxx 6', getline(6))
- call assert_equal('foo 7', getline(7))
-
- call Incsearch_cleanup()
-endfunc
-
-func Test_incsearch_substitute_long_line()
- CheckFunction test_override
- new
- call test_override("char_avail", 1)
- set incsearch
-
- call repeat('x', 100000)->setline(1)
- call feedkeys(':s/\%c', 'xt')
- redraw
- call feedkeys("\<Esc>", 'xt')
-
- call Incsearch_cleanup()
- bwipe!
-endfunc
-
-func Test_hlsearch_cursearch()
- CheckScreendump
-
- let lines =<< trim END
- set hlsearch scrolloff=0
- call setline(1, ['one', 'foo', 'bar', 'baz', 'foo the foo and foo', 'bar'])
- hi Search ctermbg=yellow
- hi CurSearch ctermbg=blue
- END
- call writefile(lines, 'Xhlsearch_cursearch')
- let buf = RunVimInTerminal('-S Xhlsearch_cursearch', {'rows': 9, 'cols': 60})
-
- call term_sendkeys(buf, "gg/foo\<CR>")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_1', {})
-
- call term_sendkeys(buf, "n")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2', {})
-
- call term_sendkeys(buf, "n")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2a', {})
-
- call term_sendkeys(buf, "n")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2b', {})
-
- call term_sendkeys(buf, ":call setline(5, 'foo')\<CR>")
- call term_sendkeys(buf, "0?\<CR>")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_3', {})
-
- call term_sendkeys(buf, "gg/foo\\nbar\<CR>")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_1', {})
-
- call term_sendkeys(buf, ":call setline(1, ['---', 'abcdefg', 'hijkl', '---', 'abcdefg', 'hijkl'])\<CR>")
- call term_sendkeys(buf, "gg/efg\\nhij\<CR>")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_2', {})
- call term_sendkeys(buf, "h\<C-L>")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_3', {})
- call term_sendkeys(buf, "j\<C-L>")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_4', {})
- call term_sendkeys(buf, "h\<C-L>")
- call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_5', {})
-
- call StopVimInTerminal(buf)
- call delete('Xhlsearch_cursearch')
-endfunc
-
-" Similar to Test_incsearch_substitute() but with a screendump halfway.
-func Test_incsearch_substitute_dump()
- CheckOption incsearch
- CheckScreendump
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'for n in range(1, 10)',
- \ ' call setline(n, "foo " . n)',
- \ 'endfor',
- \ 'call setline(11, "bar 11")',
- \ '3',
- \ ], 'Xis_subst_script')
- let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 100m
-
- " Need to send one key at a time to force a redraw.
- " Select three lines at the cursor with typed pattern.
- call term_sendkeys(buf, ':.,.+2s/')
- sleep 100m
- call term_sendkeys(buf, 'f')
- sleep 100m
- call term_sendkeys(buf, 'o')
- sleep 100m
- call term_sendkeys(buf, 'o')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_01', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Select three lines at the cursor using previous pattern.
- call term_sendkeys(buf, "/foo\<CR>")
- sleep 100m
- call term_sendkeys(buf, ':.,.+2s//')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_02', {})
-
- " Deleting last slash should remove the match.
- call term_sendkeys(buf, "\<BS>")
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_03', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Reverse range is accepted
- call term_sendkeys(buf, ':5,2s/foo')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_04', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " White space after the command is skipped
- call term_sendkeys(buf, ':2,3sub /fo')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_05', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Command modifiers are skipped
- call term_sendkeys(buf, ':above below browse botr confirm keepmar keepalt keeppat keepjum filter xxx hide lockm leftabove noau noswap rightbel sandbox silent silent! $tab top unsil vert verbose 4,5s/fo.')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_06', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Cursorline highlighting at match
- call term_sendkeys(buf, ":set cursorline\<CR>")
- call term_sendkeys(buf, 'G9G')
- call term_sendkeys(buf, ':9,11s/bar')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_07', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Cursorline highlighting at cursor when no match
- call term_sendkeys(buf, ':9,10s/bar')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_08', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " Only \v handled as empty pattern, does not move cursor
- call term_sendkeys(buf, '3G4G')
- call term_sendkeys(buf, ":nohlsearch\<CR>")
- call term_sendkeys(buf, ':6,7s/\v')
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_09', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call term_sendkeys(buf, ":set nocursorline\<CR>")
-
- " All matches are highlighted for 'hlsearch' after the incsearch canceled
- call term_sendkeys(buf, "1G*")
- call term_sendkeys(buf, ":2,5s/foo")
- sleep 100m
- call term_sendkeys(buf, "\<Esc>")
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_10', {})
-
- call term_sendkeys(buf, ":split\<CR>")
- call term_sendkeys(buf, ":let @/ = 'xyz'\<CR>")
- call term_sendkeys(buf, ":%s/.")
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_11', {})
- call term_sendkeys(buf, "\<BS>")
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_12', {})
- call term_sendkeys(buf, "\<Esc>")
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_13', {})
- call term_sendkeys(buf, ":%bwipe!\<CR>")
- call term_sendkeys(buf, ":only!\<CR>")
-
- " get :'<,'>s command in history
- call term_sendkeys(buf, ":set cmdheight=2\<CR>")
- call term_sendkeys(buf, "aasdfasdf\<Esc>")
- call term_sendkeys(buf, "V:s/a/b/g\<CR>")
- " Using '<,'> does not give E20
- call term_sendkeys(buf, ":new\<CR>")
- call term_sendkeys(buf, "aasdfasdf\<Esc>")
- call term_sendkeys(buf, ":\<Up>\<Up>")
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_14', {})
- call term_sendkeys(buf, "<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_subst_script')
-endfunc
-
-func Test_incsearch_highlighting()
- CheckOption incsearch
- CheckScreendump
-
- call writefile([
- \ 'set incsearch hlsearch',
- \ 'call setline(1, "hello/there")',
- \ ], 'Xis_subst_hl_script')
- let buf = RunVimInTerminal('-S Xis_subst_hl_script', {'rows': 4, 'cols': 20})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 300m
-
- " Using a different search delimiter should still highlight matches
- " that contain a '/'.
- call term_sendkeys(buf, ":%s;ello/the")
- call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {})
- call term_sendkeys(buf, "<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_subst_hl_script')
-endfunc
-
-func Test_incsearch_with_change()
- CheckFeature timers
- CheckOption incsearch
- CheckScreendump
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'call setline(1, ["one", "two ------ X", "three"])',
- \ 'call timer_start(200, { _ -> setline(2, "x")})',
- \ ], 'Xis_change_script')
- let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 300m
-
- " Highlight X, it will be deleted by the timer callback.
- call term_sendkeys(buf, ':%s/X')
- call VerifyScreenDump(buf, 'Test_incsearch_change_01', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_change_script')
-endfunc
-
-" Similar to Test_incsearch_substitute_dump() for :sort
-func Test_incsearch_sort_dump()
- CheckOption incsearch
- CheckScreendump
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
- \ ], 'Xis_sort_script')
- let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 100m
-
- call term_sendkeys(buf, ':sort ni u /on')
- call VerifyScreenDump(buf, 'Test_incsearch_sort_01', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call term_sendkeys(buf, ':sort! /on')
- call VerifyScreenDump(buf, 'Test_incsearch_sort_02', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_sort_script')
-endfunc
-
-" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
-func Test_incsearch_vimgrep_dump()
- CheckOption incsearch
- CheckScreendump
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
- \ ], 'Xis_vimgrep_script')
- let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70})
- " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
- " the 'ambiwidth' check.
- sleep 100m
-
- " Need to send one key at a time to force a redraw.
- call term_sendkeys(buf, ':vimgrep on')
- call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_01', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call term_sendkeys(buf, ':vimg /on/ *.txt')
- call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_02', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call term_sendkeys(buf, ':vimgrepadd "\<on')
- call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_03', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call term_sendkeys(buf, ':lv "tha')
- call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_04', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call term_sendkeys(buf, ':lvimgrepa "the" **/*.txt')
- call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_05', {})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('Xis_vimgrep_script')
-endfunc
-
-func Test_keep_last_search_pattern()
- CheckFunction test_override
- CheckOption incsearch
-
- new
- call setline(1, ['foo', 'foo', 'foo'])
- set incsearch
- call test_override("char_avail", 1)
- let @/ = 'bar'
- call feedkeys(":/foo/s//\<Esc>", 'ntx')
- call assert_equal('bar', @/)
-
- " no error message if pattern not found
- call feedkeys(":/xyz/s//\<Esc>", 'ntx')
- call assert_equal('bar', @/)
-
- bwipe!
- call test_override("ALL", 0)
- set noincsearch
-endfunc
-
-func Test_word_under_cursor_after_match()
- CheckFunction test_override
- CheckOption incsearch
-
- new
- call setline(1, 'foo bar')
- set incsearch
- call test_override("char_avail", 1)
- try
- call feedkeys("/foo\<C-R>\<C-W>\<CR>", 'ntx')
- catch /E486:/
- endtry
- call assert_equal('foobar', @/)
-
- bwipe!
- call test_override("ALL", 0)
- set noincsearch
-endfunc
-
-func Test_subst_word_under_cursor()
- CheckFunction test_override
- CheckOption incsearch
-
- new
- call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
- set incsearch
- call test_override("char_avail", 1)
- call feedkeys("/LongName\<CR>", 'ntx')
- call feedkeys(":%s/xxx/\<C-R>\<C-W>/g\<CR>", 'ntx')
- call assert_equal('for (SomeLongName = 1; SomeLongName < len; ++SomeLongName)', getline(2))
-
- bwipe!
- call test_override("ALL", 0)
- set noincsearch
-endfunc
-
-func Test_search_skip_all_matches()
- enew
- call setline(1, ['no match here',
- \ 'match this line',
- \ 'nope',
- \ 'match in this line',
- \ 'last line',
- \ ])
- call cursor(1, 1)
- let lnum = search('this', '', 0, 0, 'getline(".") =~ "this line"')
- " Only check that no match is found. Previously it searched forever.
- call assert_equal(0, lnum)
-
- bwipe!
-endfunc
-
-func Test_search_undefined_behaviour()
- CheckFeature terminal
-
- let h = winheight(0)
- if h < 3
- return
- endif
- " did cause an undefined left shift
- let g:buf = term_start([GetVimProg(), '--clean', '-e', '-s', '-c', 'call search(getline("."))', 'samples/test000'], {'term_rows': 3})
- call assert_equal([''], getline(1, '$'))
- call term_sendkeys(g:buf, ":qa!\<cr>")
- bwipe!
-endfunc
-
-func Test_search_undefined_behaviour2()
- call search("\%UC0000000")
-endfunc
-
-" Test for search('multi-byte char', 'bce')
-func Test_search_multibyte()
- let save_enc = &encoding
- set encoding=utf8
- enew!
- call append('$', 'A')
- call cursor(2, 1)
- call assert_equal(2, search('A', 'bce', line('.')))
- enew!
- let &encoding = save_enc
-endfunc
-
-" This was causing E874. Also causes an invalid read?
-func Test_look_behind()
- new
- call setline(1, '0\|\&\n\@<=')
- call search(getline("."))
- bwipe!
-endfunc
-
-func Test_search_visual_area_linewise()
- new
- call setline(1, ['aa', 'bb', 'cc'])
- exe "normal 2GV\<Esc>"
- for engine in [1, 2]
- exe 'set regexpengine=' .. engine
- exe "normal gg/\\%'<\<CR>>"
- call assert_equal([0, 2, 1, 0, 1], getcurpos(), 'engine ' .. engine)
- exe "normal gg/\\%'>\<CR>"
- call assert_equal([0, 2, 2, 0, 2], getcurpos(), 'engine ' .. engine)
- endfor
-
- bwipe!
- set regexpengine&
-endfunc
-
-func Test_search_sentence()
- new
- " this used to cause a crash
- /\%'(
- /
- bwipe
-endfunc
-
-" Test that there is no crash when there is a last search pattern but no last
-" substitute pattern.
-func Test_no_last_substitute_pat()
- " Use viminfo to set the last search pattern to a string and make the last
- " substitute pattern the most recent used and make it empty (NULL).
- call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo')
- rviminfo! Xviminfo
- call assert_fails('normal n', 'E35:')
-
- call delete('Xviminfo')
-endfunc
-
-func Test_search_Ctrl_L_combining()
- " Make sure, that Ctrl-L works correctly with combining characters.
- " It uses an artificial example of an 'a' with 4 combining chars:
- " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
- " ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT &#x300; /\%u300\Z "\u0300"
- " ' Ì' U+0301 Dec:769 COMBINING ACUTE ACCENT &#x301; /\%u301\Z "\u0301"
- " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
- " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
- " Those should also appear on the commandline
- CheckOption incsearch
-
- call Cmdline3_prep()
- 1
- let bufcontent = ['', 'MiaÌ€Ị̀̇m']
- call append('$', bufcontent)
- call feedkeys("/Mi\<c-l>\<c-l>\<cr>", 'tx')
- call assert_equal(5, line('.'))
- call assert_equal(bufcontent[1], @/)
- call Incsearch_cleanup()
-endfunc
-
-func Test_large_hex_chars1()
- " This used to cause a crash, the character becomes an NFA state.
- try
- /\%Ufffffc23
- catch
- call assert_match('E678:', v:exception)
- endtry
- try
- set re=1
- /\%Ufffffc23
- catch
- call assert_match('E678:', v:exception)
- endtry
- set re&
-endfunc
-
-func Test_large_hex_chars2()
- " This used to cause a crash, the character becomes an NFA state.
- try
- /[\Ufffffc1f]
- catch
- call assert_match('E486:', v:exception)
- endtry
- try
- set re=1
- /[\Ufffffc1f]
- catch
- call assert_match('E486:', v:exception)
- endtry
- set re&
-endfunc
-
-func Test_one_error_msg()
- " This was also giving an internal error
- call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:')
-endfunc
-
-func Test_incsearch_add_char_under_cursor()
- CheckFunction test_override
- CheckOption incsearch
-
- set incsearch
- new
- call setline(1, ['find match', 'anything'])
- 1
- call test_override('char_avail', 1)
- call feedkeys("fc/m\<C-L>\<C-L>\<C-L>\<C-L>\<C-L>\<CR>", 'tx')
- call assert_equal('match', @/)
- call test_override('char_avail', 0)
-
- set incsearch&
- bwipe!
-endfunc
-
-" Test for the search() function with match at the cursor position
-func Test_search_match_at_curpos()
- new
- call append(0, ['foobar', '', 'one two', ''])
-
- normal gg
-
- eval 'foobar'->search('c')
- call assert_equal([1, 1], [line('.'), col('.')])
-
- normal j
- call search('^$', 'c')
- call assert_equal([2, 1], [line('.'), col('.')])
-
- call search('^$', 'bc')
- call assert_equal([2, 1], [line('.'), col('.')])
-
- exe "normal /two\<CR>"
- call search('.', 'c')
- call assert_equal([3, 5], [line('.'), col('.')])
-
- close!
-endfunc
-
-" Test for error cases with the search() function
-func Test_search_errors()
- call assert_fails("call search('pat', [])", 'E730:')
- call assert_fails("call search('pat', 'b', {})", 'E728:')
- call assert_fails("call search('pat', 'b', 1, [])", 'E745:')
- call assert_fails("call search('pat', 'ns')", 'E475:')
- call assert_fails("call search('pat', 'mr')", 'E475:')
-
- new
- call setline(1, ['foo', 'bar'])
- call assert_fails('call feedkeys("/foo/;/bar/;\<CR>", "tx")', 'E386:')
- bwipe!
-endfunc
-
-func Test_search_display_pattern()
- new
- call setline(1, ['foo', 'bar', 'foobar'])
-
- call cursor(1, 1)
- let @/ = 'foo'
- let pat = @/->escape('()*?'. '\s\+')
- let g:a = execute(':unsilent :norm! n')
- call assert_match(pat, g:a)
-
- " right-left
- if exists("+rightleft")
- set rl
- call cursor(1, 1)
- let @/ = 'foo'
- let pat = 'oof/\s\+'
- let g:a = execute(':unsilent :norm! n')
- call assert_match(pat, g:a)
- set norl
- endif
-endfunc
-
-func Test_searchdecl()
- let lines =<< trim END
- int global;
-
- func()
- {
- int global;
- if (cond) {
- int local;
- }
- int local;
- // comment
- }
- END
- new
- call setline(1, lines)
- 10
- call assert_equal(0, searchdecl('local', 0, 0))
- call assert_equal(7, getcurpos()[1])
-
- 10
- call assert_equal(0, 'local'->searchdecl(0, 1))
- call assert_equal(9, getcurpos()[1])
-
- 10
- call assert_equal(0, searchdecl('global'))
- call assert_equal(5, getcurpos()[1])
-
- 10
- call assert_equal(0, searchdecl('global', 1))
- call assert_equal(1, getcurpos()[1])
-
- bwipe!
-endfunc
-
-func Test_search_special()
- " this was causing illegal memory access and an endless loop
- set t_PE=
- exe "norm /\x80PS"
-endfunc
-
-" Test for command failures when the last search pattern is not set.
-" Need to run this in a new vim instance where last search pattern is not set.
-func Test_search_with_no_last_pat()
- let lines =<< trim [SCRIPT]
- call assert_fails("normal i\<C-R>/\e", 'E35:')
- call assert_fails("exe '/'", 'E35:')
- call assert_fails("exe '?'", 'E35:')
- call assert_fails("/", 'E35:')
- call assert_fails("?", 'E35:')
- call assert_fails("normal n", 'E35:')
- call assert_fails("normal N", 'E35:')
- call assert_fails("normal gn", 'E35:')
- call assert_fails("normal gN", 'E35:')
- call assert_fails("normal cgn", 'E35:')
- call assert_fails("normal cgN", 'E35:')
- let p = []
- let p = @/
- call assert_equal('', p)
- call assert_fails("normal :\<C-R>/", 'E35:')
- call assert_fails("//p", 'E35:')
- call assert_fails(";//p", 'E35:')
- call assert_fails("??p", 'E35:')
- call assert_fails(";??p", 'E35:')
- call assert_fails('g//p', ['E35:', 'E476:'])
- call assert_fails('v//p', ['E35:', 'E476:'])
- call writefile(v:errors, 'Xresult')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
-
- if RunVim([], [], '--clean -S Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" Test for using tilde (~) atom in search. This should use the last used
-" substitute pattern
-func Test_search_tilde_pat()
- let lines =<< trim [SCRIPT]
- set regexpengine=1
- call assert_fails('exe "normal /~\<CR>"', 'E33:')
- call assert_fails('exe "normal ?~\<CR>"', 'E33:')
- set regexpengine=2
- call assert_fails('exe "normal /~\<CR>"', ['E33:', 'E383:'])
- call assert_fails('exe "normal ?~\<CR>"', ['E33:', 'E383:'])
- set regexpengine&
- call writefile(v:errors, 'Xresult')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -S Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" Test for searching a pattern that is not present with 'nowrapscan'
-func Test_search_pat_not_found()
- new
- set nowrapscan
- let @/ = '1abcxyz2'
- call assert_fails('normal n', 'E385:')
- call assert_fails('normal N', 'E384:')
- set wrapscan&
- close
-endfunc
-
-" Test for v:searchforward variable
-func Test_searchforward_var()
- new
- call setline(1, ['foo', '', 'foo'])
- call cursor(2, 1)
- let @/ = 'foo'
- let v:searchforward = 0
- normal N
- call assert_equal(3, line('.'))
- call cursor(2, 1)
- let v:searchforward = 1
- normal N
- call assert_equal(1, line('.'))
- close!
-endfunc
-
-" Test for invalid regular expressions
-func Test_invalid_regexp()
- set regexpengine=1
- call assert_fails("call search(repeat('\\(.\\)', 10))", 'E51:')
- call assert_fails("call search('\\%(')", 'E53:')
- call assert_fails("call search('\\(')", 'E54:')
- call assert_fails("call search('\\)')", 'E55:')
- call assert_fails("call search('x\\@#')", 'E59:')
- call assert_fails('call search(''\v%(%(%(%(%(%(%(%(%(%(%(a){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}'')', 'E60:')
- call assert_fails("call search('a\\+*')", 'E61:')
- call assert_fails("call search('\\_m')", 'E63:')
- call assert_fails("call search('\\+')", 'E64:')
- call assert_fails("call search('\\1')", 'E65:')
- call assert_fails("call search('\\z\\(\\)')", 'E66:')
- call assert_fails("call search('\\z2')", 'E67:')
- call assert_fails("call search('\\zx')", 'E68:')
- call assert_fails("call search('\\%[ab')", 'E69:')
- call assert_fails("call search('\\%[]')", 'E70:')
- call assert_fails("call search('\\%a')", 'E71:')
- call assert_fails("call search('ab\\%[\\(cd\\)]')", 'E369:')
- call assert_fails("call search('ab\\%[\\%(cd\\)]')", 'E369:')
- set regexpengine=2
- call assert_fails("call search('\\_')", 'E865:')
- call assert_fails("call search('\\+')", 'E866:')
- call assert_fails("call search('\\zx')", 'E867:')
- call assert_fails("call search('\\%a')", 'E867:')
- call assert_fails("call search('x\\@#')", 'E869:')
- call assert_fails("call search(repeat('\\(.\\)', 10))", 'E872:')
- call assert_fails("call search('\\_m')", 'E877:')
- call assert_fails("call search('\\%(')", 'E53:')
- call assert_fails("call search('\\(')", 'E54:')
- call assert_fails("call search('\\)')", 'E55:')
- call assert_fails("call search('\\z\\(\\)')", 'E66:')
- call assert_fails("call search('\\%[ab')", 'E69:')
- call assert_fails("call search('\\%[]')", 'E70:')
- call assert_fails("call search('\\%9999999999999999999999999999v')", 'E951:')
- set regexpengine&
- call assert_fails("call search('\\%#=3ab')", 'E864:')
-endfunc
-
-" Test for searching a very complex pattern in a string. Should switch the
-" regexp engine from NFA to the old engine.
-func Test_regexp_switch_engine()
- let l = readfile('samples/re.freeze.txt')
- let v = substitute(l[4], '..\@<!', '', '')
- call assert_equal(l[4], v)
-endfunc
-
-" Test for the \%V atom to search within visually selected text
-func Test_search_in_visual_area()
- new
- call setline(1, ['foo bar1', 'foo bar2', 'foo bar3', 'foo bar4'])
- exe "normal 2GVjo/\\%Vbar\<CR>\<Esc>"
- call assert_equal([2, 5], [line('.'), col('.')])
- exe "normal 2GVj$?\\%Vbar\<CR>\<Esc>"
- call assert_equal([3, 5], [line('.'), col('.')])
- close!
-endfunc
-
-" Test for searching with 'smartcase' and 'ignorecase'
-func Test_search_smartcase()
- new
- call setline(1, ['', 'Hello'])
- set noignorecase nosmartcase
- call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:')
-
- set ignorecase nosmartcase
- exe "normal /\\a\\_.\\(.*\\)O\<CR>"
- call assert_equal([2, 1], [line('.'), col('.')])
-
- call cursor(1, 1)
- set ignorecase smartcase
- call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:')
-
- exe "normal /\\a\\_.\\(.*\\)o\<CR>"
- call assert_equal([2, 1], [line('.'), col('.')])
-
- " Test for using special atoms with 'smartcase'
- call setline(1, ['', ' Hello\ '])
- call cursor(1, 1)
- call feedkeys('/\_.\%(\uello\)\' .. "\<CR>", 'xt')
- call assert_equal([2, 4], [line('.'), col('.')])
-
- set ignorecase& smartcase&
- close!
-endfun
-
-" Test 'smartcase' with utf-8.
-func Test_search_smartcase_utf8()
- new
- let save_enc = &encoding
- set encoding=utf8 ignorecase smartcase
-
- call setline(1, 'Café cafÉ')
- 1s/café/x/g
- call assert_equal('x x', getline(1))
-
- call setline(1, 'Café cafÉ')
- 1s/cafÉ/x/g
- call assert_equal('Café x', getline(1))
-
- set ignorecase& smartcase&
- let &encoding = save_enc
- bwipe!
-endfunc
-
-" Test searching past the end of a file
-func Test_search_past_eof()
- new
- call setline(1, ['Line'])
- exe "normal /\\n\\zs\<CR>"
- call assert_equal([1, 4], [line('.'), col('.')])
- bwipe!
-endfunc
-
-" Test setting the start of the match and still finding a next match in the
-" same line.
-func Test_search_set_start_same_line()
- new
- set cpo-=c
-
- call setline(1, ['1', '2', '3 .', '4', '5'])
- exe "normal /\\_s\\zs\\S\<CR>"
- call assert_equal([2, 1], [line('.'), col('.')])
- exe 'normal n'
- call assert_equal([3, 1], [line('.'), col('.')])
- exe 'normal n'
- call assert_equal([3, 3], [line('.'), col('.')])
- exe 'normal n'
- call assert_equal([4, 1], [line('.'), col('.')])
- exe 'normal n'
- call assert_equal([5, 1], [line('.'), col('.')])
-
- set cpo+=c
- bwipe!
-endfunc
-
-" Test for various search offsets
-func Test_search_offset()
- " With /e, for a match in the first column of a line, the cursor should be
- " placed at the end of the previous line.
- new
- call setline(1, ['one two', 'three four'])
- call search('two\_.', 'e')
- call assert_equal([1, 7], [line('.'), col('.')])
-
- " with cursor at the beginning of the file, use /s+1
- call cursor(1, 1)
- exe "normal /two/s+1\<CR>"
- call assert_equal([1, 6], [line('.'), col('.')])
-
- " with cursor at the end of the file, use /e-1
- call cursor(2, 10)
- exe "normal ?three?e-1\<CR>"
- call assert_equal([2, 4], [line('.'), col('.')])
-
- " line offset - after the last line
- call cursor(1, 1)
- exe "normal /three/+1\<CR>"
- call assert_equal([2, 1], [line('.'), col('.')])
-
- " line offset - before the first line
- call cursor(2, 1)
- exe "normal ?one?-1\<CR>"
- call assert_equal([1, 1], [line('.'), col('.')])
-
- " character offset - before the first character in the file
- call cursor(2, 1)
- exe "normal ?one?s-1\<CR>"
- call assert_equal([1, 1], [line('.'), col('.')])
- call cursor(2, 1)
- exe "normal ?one?e-3\<CR>"
- call assert_equal([1, 1], [line('.'), col('.')])
-
- " character offset - after the last character in the file
- call cursor(1, 1)
- exe "normal /four/s+4\<CR>"
- call assert_equal([2, 10], [line('.'), col('.')])
- call cursor(1, 1)
- exe "normal /four/e+1\<CR>"
- call assert_equal([2, 10], [line('.'), col('.')])
-
- close!
-endfunc
-
-" Test for searching for matching parenthesis using %
-func Test_search_match_paren()
- new
- call setline(1, "abc(def')'ghi'('jk'\\t'lm)no")
- " searching for a matching parenthesis should skip single quoted characters
- call cursor(1, 4)
- normal %
- call assert_equal([1, 25], [line('.'), col('.')])
- normal %
- call assert_equal([1, 4], [line('.'), col('.')])
- call cursor(1, 5)
- normal ])
- call assert_equal([1, 25], [line('.'), col('.')])
- call cursor(1, 24)
- normal [(
- call assert_equal([1, 4], [line('.'), col('.')])
-
- " matching parenthesis in 'virtualedit' mode with cursor after the eol
- call setline(1, 'abc(defgh)')
- set virtualedit=all
- normal 20|%
- call assert_equal(4, col('.'))
- set virtualedit&
- close!
-endfunc
-
-" Test for searching a pattern and stopping before a specified line
-func Test_search_stopline()
- new
- call setline(1, ['', '', '', 'vim'])
- call assert_equal(0, search('vim', 'n', 3))
- call assert_equal(4, search('vim', 'n', 4))
- call setline(1, ['vim', '', '', ''])
- call cursor(4, 1)
- call assert_equal(0, search('vim', 'bn', 2))
- call assert_equal(1, search('vim', 'bn', 1))
- close!
-endfunc
-
-func Test_incsearch_highlighting_newline()
- CheckRunVimInTerminal
- CheckOption incsearch
- CheckScreendump
- new
- call test_override("char_avail", 1)
-
- let commands =<< trim [CODE]
- set incsearch nohls
- call setline(1, ['test', 'xxx'])
- [CODE]
- call writefile(commands, 'Xincsearch_nl')
- let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10})
- call term_sendkeys(buf, '/test')
- call VerifyScreenDump(buf, 'Test_incsearch_newline1', {})
- " Need to send one key at a time to force a redraw
- call term_sendkeys(buf, '\n')
- call VerifyScreenDump(buf, 'Test_incsearch_newline2', {})
- call term_sendkeys(buf, 'x')
- call VerifyScreenDump(buf, 'Test_incsearch_newline3', {})
- call term_sendkeys(buf, 'x')
- call VerifyScreenDump(buf, 'Test_incsearch_newline4', {})
- call term_sendkeys(buf, "\<CR>")
- call VerifyScreenDump(buf, 'Test_incsearch_newline5', {})
- call StopVimInTerminal(buf)
-
- " clean up
- call delete('Xincsearch_nl')
- call test_override("char_avail", 0)
- bw
-endfunc
-
-func Test_incsearch_substitute_dump2()
- CheckOption incsearch
- CheckScreendump
-
- call writefile([
- \ 'set incsearch hlsearch scrolloff=0',
- \ 'for n in range(1, 4)',
- \ ' call setline(n, "foo " . n)',
- \ 'endfor',
- \ 'call setline(5, "abc|def")',
- \ '3',
- \ ], 'Xis_subst_script2')
- let buf = RunVimInTerminal('-S Xis_subst_script2', {'rows': 9, 'cols': 70})
-
- call term_sendkeys(buf, ':%s/\vabc|')
- sleep 100m
- call VerifyScreenDump(buf, 'Test_incsearch_sub_01', {})
- call term_sendkeys(buf, "\<Esc>")
-
- " The following should not be highlighted
- call term_sendkeys(buf, ':1,5s/\v|')
- sleep 100m
- call VerifyScreenDump(buf, 'Test_incsearch_sub_02', {})
-
-
- call StopVimInTerminal(buf)
- call delete('Xis_subst_script2')
-endfunc
-
-func Test_pattern_is_uppercase_smartcase()
- new
- let input=['abc', 'ABC', 'Abc', 'abC']
- call setline(1, input)
- call cursor(1,1)
- " default, matches firstline
- %s/abc//g
- call assert_equal(['', 'ABC', 'Abc', 'abC'],
- \ getline(1, '$'))
-
- set smartcase ignorecase
- sil %d
- call setline(1, input)
- call cursor(1,1)
- " with smartcase and incsearch set, matches everything
- %s/abc//g
- call assert_equal(['', '', '', ''], getline(1, '$'))
-
- sil %d
- call setline(1, input)
- call cursor(1,1)
- " with smartcase and incsearch set and found an uppercase letter,
- " match only that.
- %s/abC//g
- call assert_equal(['abc', 'ABC', 'Abc', ''],
- \ getline(1, '$'))
-
- sil %d
- call setline(1, input)
- call cursor(1,1)
- exe "norm! vG$\<esc>"
- " \%V should not be detected as uppercase letter
- %s/\%Vabc//g
- call assert_equal(['', '', '', ''], getline(1, '$'))
-
- call setline(1, input)
- call cursor(1,1)
- exe "norm! vG$\<esc>"
- " \v%V should not be detected as uppercase letter
- %s/\v%Vabc//g
- call assert_equal(['', '', '', ''], getline(1, '$'))
-
- call setline(1, input)
- call cursor(1,1)
- exe "norm! vG$\<esc>"
- " \v%VabC should be detected as uppercase letter
- %s/\v%VabC//g
- call assert_equal(['abc', 'ABC', 'Abc', ''],
- \ getline(1, '$'))
-
- call setline(1, input)
- call cursor(1,1)
- " \Vabc should match everything
- %s/\Vabc//g
- call assert_equal(['', '', '', ''], getline(1, '$'))
-
- call setline(1, input + ['_abc'])
- " _ matches normally
- %s/\v_.*//g
- call assert_equal(['abc', 'ABC', 'Abc', 'abC', ''], getline(1, '$'))
-
- set smartcase& ignorecase&
- bw!
-endfunc
-
-func Test_no_last_search_pattern()
- CheckOption incsearch
-
- let @/ = ""
- set incsearch
- " these were causing a crash
- call feedkeys("//\<C-G>", 'xt')
- call feedkeys("//\<C-T>", 'xt')
- call feedkeys("??\<C-G>", 'xt')
- call feedkeys("??\<C-T>", 'xt')
-endfunc
-
-func Test_search_with_invalid_range()
- new
- let lines =<< trim END
- /\%.v
- 5/
- c
- END
- call writefile(lines, 'Xrangesearch')
- source Xrangesearch
-
- bwipe!
- call delete('Xrangesearch')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim
deleted file mode 100644
index 77bd50ada2..0000000000
--- a/src/nvim/testdir/test_search_stat.vim
+++ /dev/null
@@ -1,425 +0,0 @@
-" Tests for search_stats, when "S" is not in 'shortmess'
-
-source check.vim
-source screendump.vim
-
-func Test_search_stat()
- new
- set shortmess-=S
- " Append 50 lines with text to search for, "foobar" appears 20 times
- call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
-
- call cursor(1, 1)
-
- " searchcount() returns an empty dictionary when previous pattern was not set
- call assert_equal({}, searchcount(#{pattern: ''}))
- " but setting @/ should also work (even 'n' nor 'N' was executed)
- " recompute the count when the last position is different.
- call assert_equal(
- \ #{current: 1, exact_match: 1, total: 40, incomplete: 0, maxcount: 99},
- \ searchcount(#{pattern: 'foo'}))
- call assert_equal(
- \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
- \ searchcount(#{pattern: 'fooooobar'}))
- call assert_equal(
- \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
- \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]}))
- call assert_equal(
- \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
- \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]}))
- " on last char of match
- call assert_equal(
- \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
- \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]}))
- " on char after match
- call assert_equal(
- \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
- \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]}))
- call assert_equal(
- \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
- \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0]}))
- call assert_equal(
- \ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
- \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0], maxcount: 1}))
- call assert_equal(
- \ #{current: 0, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
- \ searchcount(#{pattern: 'fooooobar', maxcount: 1}))
-
- " match at second line
- let messages_before = execute('messages')
- let @/ = 'fo*\(bar\?\)\?'
- let g:a = execute(':unsilent :norm! n')
- let stat = '\[2/50\]'
- let pat = escape(@/, '()*?'). '\s\+'
- call assert_match(pat .. stat, g:a)
- call assert_equal(
- \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
- \ searchcount(#{recompute: 0}))
- " didn't get added to message history
- call assert_equal(messages_before, execute('messages'))
-
- " Match at last line
- call cursor(line('$')-2, 1)
- let g:a = execute(':unsilent :norm! n')
- let stat = '\[50/50\]'
- call assert_match(pat .. stat, g:a)
- call assert_equal(
- \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
- \ searchcount(#{recompute: 0}))
-
- " No search stat
- set shortmess+=S
- call cursor(1, 1)
- let stat = '\[2/50\]'
- let g:a = execute(':unsilent :norm! n')
- call assert_notmatch(pat .. stat, g:a)
- " n does not update search stat
- call assert_equal(
- \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
- \ searchcount(#{recompute: 0}))
- call assert_equal(
- \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
- \ searchcount(#{recompute: v:true}))
- set shortmess-=S
-
- " Many matches
- call cursor(line('$')-2, 1)
- let @/ = '.'
- let pat = escape(@/, '()*?'). '\s\+'
- let g:a = execute(':unsilent :norm! n')
- let stat = '\[>99/>99\]'
- call assert_match(pat .. stat, g:a)
- call assert_equal(
- \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99},
- \ searchcount(#{recompute: 0}))
- call assert_equal(
- \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
- \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200}))
- call assert_equal(
- \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
- \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200}))
- call cursor(line('$'), 1)
- let g:a = execute(':unsilent :norm! n')
- let stat = 'W \[1/>99\]'
- call assert_match(pat .. stat, g:a)
- call assert_equal(
- \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99},
- \ searchcount(#{recompute: 0}))
- call assert_equal(
- \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
- \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200}))
- call assert_equal(
- \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
- \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200}))
-
- " Many matches
- call cursor(1, 1)
- let g:a = execute(':unsilent :norm! n')
- let stat = '\[2/>99\]'
- call assert_match(pat .. stat, g:a)
- call cursor(1, 1)
- let g:a = execute(':unsilent :norm! N')
- let stat = 'W \[>99/>99\]'
- call assert_match(pat .. stat, g:a)
-
- " right-left
- if exists("+rightleft")
- set rl
- call cursor(1,1)
- let @/ = 'foobar'
- let pat = 'raboof/\s\+'
- let g:a = execute(':unsilent :norm! n')
- let stat = '\[20/2\]'
- call assert_match(pat .. stat, g:a)
- set norl
- endif
-
- " right-left bottom
- if exists("+rightleft")
- set rl
- call cursor('$',1)
- let pat = 'raboof?\s\+'
- let g:a = execute(':unsilent :norm! N')
- let stat = '\[20/20\]'
- call assert_match(pat .. stat, g:a)
- set norl
- endif
-
- " right-left back at top
- if exists("+rightleft")
- set rl
- call cursor('$',1)
- let pat = 'raboof/\s\+'
- let g:a = execute(':unsilent :norm! n')
- let stat = 'W \[20/1\]'
- call assert_match(pat .. stat, g:a)
- call assert_match('search hit BOTTOM, continuing at TOP', g:a)
- set norl
- endif
-
- " normal, back at bottom
- call cursor(1,1)
- let @/ = 'foobar'
- let pat = '?foobar\s\+'
- let g:a = execute(':unsilent :norm! N')
- let stat = 'W \[20/20\]'
- call assert_match(pat .. stat, g:a)
- call assert_match('search hit TOP, continuing at BOTTOM', g:a)
- call assert_match('W \[20/20\]', Screenline(&lines))
-
- " normal, no match
- call cursor(1,1)
- let @/ = 'zzzzzz'
- let g:a = ''
- try
- let g:a = execute(':unsilent :norm! n')
- catch /^Vim\%((\a\+)\)\=:E486/
- let stat = ''
- " error message is not redir'ed to g:a, it is empty
- call assert_true(empty(g:a))
- catch
- call assert_false(1)
- endtry
-
- " with count
- call cursor(1, 1)
- let @/ = 'fo*\(bar\?\)\?'
- let g:a = execute(':unsilent :norm! 2n')
- let stat = '\[3/50\]'
- let pat = escape(@/, '()*?'). '\s\+'
- call assert_match(pat .. stat, g:a)
- let g:a = execute(':unsilent :norm! 2n')
- let stat = '\[5/50\]'
- call assert_match(pat .. stat, g:a)
-
- " with offset
- call cursor(1, 1)
- call feedkeys("/fo*\\(bar\\?\\)\\?/+1\<cr>", 'tx')
- let g:a = execute(':unsilent :norm! n')
- let stat = '\[5/50\]'
- let pat = escape(@/ .. '/+1', '()*?'). '\s\+'
- call assert_match(pat .. stat, g:a)
-
- " normal, n comes from a mapping
- " Need to move over more than 64 lines to trigger char_avail(.
- nnoremap n nzv
- call cursor(1,1)
- call append(50, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
- call setline(2, 'find this')
- call setline(70, 'find this')
- let @/ = 'find this'
- let pat = '/find this\s\+'
- let g:a = execute(':unsilent :norm n')
- " g:a will contain several lines
- let g:b = split(g:a, "\n")[-1]
- let stat = '\[1/2\]'
- call assert_match(pat .. stat, g:b)
- unmap n
-
- " normal, but silent
- call cursor(1,1)
- let @/ = 'find this'
- let pat = '/find this\s\+'
- let g:a = execute(':norm! n')
- let stat = '\[1/2\]'
- call assert_notmatch(pat .. stat, g:a)
-
- " normal, n comes from a silent mapping
- " First test a normal mapping, then a silent mapping
- call cursor(1,1)
- nnoremap n n
- let @/ = 'find this'
- let pat = '/find this\s\+'
- let g:a = execute(':unsilent :norm n')
- let g:b = split(g:a, "\n")[-1]
- let stat = '\[1/2\]'
- call assert_match(pat .. stat, g:b)
- nnoremap <silent> n n
- call cursor(1,1)
- let g:a = execute(':unsilent :norm n')
- let g:b = split(g:a, "\n")[-1]
- let stat = '\[1/2\]'
- call assert_notmatch(pat .. stat, g:b)
- call assert_match(stat, g:b)
- " Test that the message is not truncated
- " it would insert '...' into the output.
- call assert_match('^\s\+' .. stat, g:b)
- unmap n
-
- " Time out
- %delete _
- call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 100000))
- call cursor(1, 1)
- call assert_equal(1, searchcount(#{pattern: 'foo', maxcount: 0, timeout: 1}).incomplete)
-
- " Clean up
- set shortmess+=S
- " close the window
- bwipe!
-endfunc
-
-func Test_searchcount_fails()
- call assert_fails('echo searchcount("boo!")', 'E715:')
- call assert_fails('echo searchcount({"timeout" : []})', 'E745:')
- call assert_fails('echo searchcount({"maxcount" : []})', 'E745:')
- call assert_fails('echo searchcount({"pattern" : []})', 'E730:')
- call assert_fails('echo searchcount({"pos" : 1})', 'E475:')
- call assert_fails('echo searchcount({"pos" : [1]})', 'E475:')
- call assert_fails('echo searchcount({"pos" : [[], 2, 3]})', 'E745:')
- call assert_fails('echo searchcount({"pos" : [1, [], 3]})', 'E745:')
- call assert_fails('echo searchcount({"pos" : [1, 2, []]})', 'E745:')
-endfunc
-
-func Test_searchcount_in_statusline()
- CheckScreendump
-
- let lines =<< trim END
- set shortmess-=S
- call append(0, 'this is something')
- function TestSearchCount() abort
- let search_count = searchcount()
- if !empty(search_count)
- return '[' . search_count.current . '/' . search_count.total . ']'
- else
- return ''
- endif
- endfunction
- set hlsearch
- set laststatus=2 statusline+=%{TestSearchCount()}
- END
- call writefile(lines, 'Xsearchstatusline')
- let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
- call TermWait(buf)
- call term_sendkeys(buf, "/something")
- call VerifyScreenDump(buf, 'Test_searchstat_4', {})
-
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
- call delete('Xsearchstatusline')
-endfunc
-
-func Test_search_stat_foldopen()
- CheckScreendump
-
- let lines =<< trim END
- set shortmess-=S
- setl foldenable foldmethod=indent foldopen-=search
- call append(0, ['if', "\tfoo", "\tfoo", 'endif'])
- let @/ = 'foo'
- call cursor(1,1)
- norm n
- END
- call writefile(lines, 'Xsearchstat1')
-
- let buf = RunVimInTerminal('-S Xsearchstat1', #{rows: 10})
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_3', {})
-
- call term_sendkeys(buf, "n")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_3', {})
-
- call term_sendkeys(buf, "n")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_3', {})
-
- call StopVimInTerminal(buf)
- call delete('Xsearchstat1')
-endfunc
-
-func! Test_search_stat_screendump()
- CheckScreendump
-
- let lines =<< trim END
- set shortmess-=S
- " Append 50 lines with text to search for, "foobar" appears 20 times
- call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20))
- call setline(2, 'find this')
- call setline(70, 'find this')
- nnoremap n n
- let @/ = 'find this'
- call cursor(1,1)
- norm n
- END
- call writefile(lines, 'Xsearchstat')
- let buf = RunVimInTerminal('-S Xsearchstat', #{rows: 10})
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_1', {})
-
- call term_sendkeys(buf, ":nnoremap <silent> n n\<cr>")
- call term_sendkeys(buf, "gg0n")
- call term_wait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xsearchstat')
-endfunc
-
-func Test_search_stat_then_gd()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['int cat;', 'int dog;', 'cat = dog;'])
- set shortmess-=S
- set hlsearch
- END
- call writefile(lines, 'Xsearchstatgd')
-
- let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10})
- call term_sendkeys(buf, "/dog\<CR>")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstatgd_1', {})
-
- call term_sendkeys(buf, "G0gD")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstatgd_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xsearchstatgd')
-endfunc
-
-func Test_search_stat_and_incsearch()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['abc--c', '--------abc', '--abc'])
- set hlsearch
- set incsearch
- set bg=dark
- set showtabline=2
-
- function MyTabLine()
- try
- let a=searchcount(#{recompute: 1, maxcount: -1})
- return a.current .. '/' .. a.total
- catch
- return ''
- endtry
- endfunction
-
- set tabline=%!MyTabLine()
- END
- call writefile(lines, 'Xsearchstat_inc')
-
- let buf = RunVimInTerminal('-S Xsearchstat_inc', #{rows: 10})
- call term_sendkeys(buf, "/abc")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_inc_1', {})
-
- call term_sendkeys(buf, "\<c-g>")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_inc_2', {})
-
- call term_sendkeys(buf, "\<c-g>")
- call TermWait(buf)
- call VerifyScreenDump(buf, 'Test_searchstat_inc_3', {})
-
- call term_sendkeys(buf, "\<esc>:qa\<cr>")
- call TermWait(buf)
-
- call StopVimInTerminal(buf)
- call delete('Xsearchstat_inc')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_searchpos.vim b/src/nvim/testdir/test_searchpos.vim
deleted file mode 100644
index dd13c305c5..0000000000
--- a/src/nvim/testdir/test_searchpos.vim
+++ /dev/null
@@ -1,30 +0,0 @@
-" Tests for searchpos()
-
-func Test_searchpos()
- new one
- 0put ='1a3'
- 1put ='123xyz'
- call cursor(1, 1)
- call assert_equal([1, 1, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW'))
- call cursor(1, 2)
- call assert_equal([2, 1, 1], '\%(\([a-z]\)\|\_.\)\{-}xyz'->searchpos('pcW'))
- set cpo-=c
- call cursor(1, 2)
- call assert_equal([1, 2, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW'))
- call cursor(1, 3)
- call assert_equal([1, 3, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW'))
-
- " Now with \zs, first match is in column 0, "a" is matched.
- call cursor(1, 3)
- call assert_equal([2, 4, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcW'))
- " With z flag start at cursor column, don't see the "a".
- call cursor(1, 3)
- call assert_equal([2, 4, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcWz'))
-
- set cpo+=c
- " close the window
- q!
-
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim
deleted file mode 100644
index 041f0592f1..0000000000
--- a/src/nvim/testdir/test_selectmode.vim
+++ /dev/null
@@ -1,210 +0,0 @@
-" Test for Select-mode
-
-source shared.vim
-
-" Test for select mode
-func Test_selectmode_basic()
- new
- call setline(1, range(1,100))
- 50
- norm! gHy
- call assert_equal('y51', getline('.'))
- call setline(1, range(1,100))
- 50
- exe ":norm! V9jo\<c-g>y"
- call assert_equal('y60', getline('.'))
- call setline(1, range(1,100))
- 50
- " call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx')
- call feedkeys("i\<c-o>gHc\<esc>", 'tx')
- call assert_equal('c51', getline('.'))
- " clean up
- bw!
-endfunc
-
-" Test for starting selectmode
-func Test_selectmode_start()
- new
- set selectmode=key keymodel=startsel
- call setline(1, ['abc', 'def', 'ghi'])
- call cursor(1, 4)
- call feedkeys("A\<s-home>start\<esc>", 'txin')
- call assert_equal(['startdef', 'ghi'], getline(1, '$'))
- " start select mode again with gv
- set selectmode=cmd
- call feedkeys('gvabc', 'xt')
- call assert_equal('abctdef', getline(1))
- " arrow keys without shift should not start selection
- call feedkeys("A\<Home>\<Right>\<Left>ro", 'xt')
- call assert_equal('roabctdef', getline(1))
- set selectmode= keymodel=
- bw!
-endfunc
-
-" Test for characterwise select mode
-func Test_characterwise_select_mode()
- new
-
- " Select mode maps
- snoremap <lt>End> <End>
- snoremap <lt>Down> <Down>
- snoremap <lt>Del> <Del>
-
- " characterwise select mode: delete middle line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkkgh\<End>\<Del>"
- call assert_equal(['', 'b', 'c'], getline(1, '$'))
-
- " characterwise select mode: delete middle two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkkgh\<Down>\<End>\<Del>"
- call assert_equal(['', 'c'], getline(1, '$'))
-
- " characterwise select mode: delete last line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Ggh\<End>\<Del>"
- call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
-
- " characterwise select mode: delete last two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal Gkgh\<Down>\<End>\<Del>"
- call assert_equal(['', 'a', ''], getline(1, '$'))
-
- " CTRL-H in select mode behaves like 'x'
- call setline(1, 'abcdef')
- exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
- call assert_equal('ef', getline(1))
-
- " CTRL-O in select mode switches to visual mode for one command
- call setline(1, 'abcdef')
- exe "normal! gggh\<C-O>3lm"
- call assert_equal('mef', getline(1))
-
- sunmap <lt>End>
- sunmap <lt>Down>
- sunmap <lt>Del>
- bwipe!
-endfunc
-
-" Test for linewise select mode
-func Test_linewise_select_mode()
- new
-
- " linewise select mode: delete middle line
- call append('$', ['a', 'b', 'c'])
- exe "normal GkkgH\<Del>"
- call assert_equal(['', 'b', 'c'], getline(1, '$'))
-
- " linewise select mode: delete middle two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GkkgH\<Down>\<Del>"
- call assert_equal(['', 'c'], getline(1, '$'))
-
- " linewise select mode: delete last line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GgH\<Del>"
- call assert_equal(['', 'a', 'b'], getline(1, '$'))
-
- " linewise select mode: delete last two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- exe "normal GkgH\<Down>\<Del>"
- call assert_equal(['', 'a'], getline(1, '$'))
-
- bwipe!
-endfunc
-
-" Test for blockwise select mode (g CTRL-H)
-func Test_blockwise_select_mode()
- new
- call setline(1, ['foo', 'bar'])
- call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt')
- call assert_equal(['mmo', 'mmr'], getline(1, '$'))
- close!
-endfunc
-
-" Test for using visual mode maps in select mode
-func Test_select_mode_map()
- new
- vmap <buffer> <F2> 3l
- call setline(1, 'Test line')
- call feedkeys("gh\<F2>map", 'xt')
- call assert_equal('map line', getline(1))
-
- vmap <buffer> <F2> ygV
- call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt')
- call assert_equal('abc line', getline(1))
-
- vmap <buffer> <F2> :<C-U>let v=100<CR>
- call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt')
- call assert_equal('foo line', getline(1))
-
- " reselect the select mode using gv from a visual mode map
- vmap <buffer> <F2> gv
- set selectmode=cmd
- call feedkeys("0gh\<F2>map", 'xt')
- call assert_equal('map line', getline(1))
- set selectmode&
-
- close!
-endfunc
-
-" Test for selecting a register with CTRL-R
-func Test_selectmode_register()
- new
-
- " Default behavior: use unnamed register
- call setline(1, 'foo')
- call setreg('"', 'bar')
- call setreg('a', 'baz')
- exe ":norm! v\<c-g>a"
- call assert_equal(getline('.'), 'aoo')
- call assert_equal('f', getreg('"'))
- call assert_equal('baz', getreg('a'))
-
- " Use the black hole register
- call setline(1, 'foo')
- call setreg('"', 'bar')
- call setreg('a', 'baz')
- exe ":norm! v\<c-g>\<c-r>_a"
- call assert_equal(getline('.'), 'aoo')
- call assert_equal('bar', getreg('"'))
- call assert_equal('baz', getreg('a'))
-
- " Invalid register: use unnamed register
- call setline(1, 'foo')
- call setreg('"', 'bar')
- call setreg('a', 'baz')
- exe ":norm! v\<c-g>\<c-r>?a"
- call assert_equal(getline('.'), 'aoo')
- call assert_equal('f', getreg('"'))
- call assert_equal('baz', getreg('a'))
-
- " Use unnamed register
- call setline(1, 'foo')
- call setreg('"', 'bar')
- call setreg('a', 'baz')
- exe ":norm! v\<c-g>\<c-r>\"a"
- call assert_equal(getline('.'), 'aoo')
- call assert_equal('f', getreg('"'))
- call assert_equal('baz', getreg('a'))
-
- " use specicifed register, unnamed register is also written
- call setline(1, 'foo')
- call setreg('"', 'bar')
- call setreg('a', 'baz')
- exe ":norm! v\<c-g>\<c-r>aa"
- call assert_equal(getline('.'), 'aoo')
- call assert_equal('f', getreg('"'))
- call assert_equal('f', getreg('a'))
-
- bw!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_set.vim b/src/nvim/testdir/test_set.vim
deleted file mode 100644
index 7215772a00..0000000000
--- a/src/nvim/testdir/test_set.vim
+++ /dev/null
@@ -1,48 +0,0 @@
-" Tests for the :set command
-
-function Test_set_backslash()
- let isk_save = &isk
-
- set isk=a,b,c
- set isk+=d
- call assert_equal('a,b,c,d', &isk)
- set isk+=\\,e
- call assert_equal('a,b,c,d,\,e', &isk)
- set isk-=e
- call assert_equal('a,b,c,d,\', &isk)
- set isk-=\\
- call assert_equal('a,b,c,d', &isk)
-
- let &isk = isk_save
-endfunction
-
-function Test_set_add()
- let wig_save = &wig
-
- set wildignore=*.png,
- set wildignore+=*.jpg
- call assert_equal('*.png,*.jpg', &wig)
-
- let &wig = wig_save
-endfunction
-
-
-" :set, :setlocal, :setglobal without arguments show values of options.
-func Test_set_no_arg()
- set textwidth=79
- let a = execute('set')
- call assert_match("^\n--- Options ---\n.*textwidth=79\\>", a)
- set textwidth&
-
- setlocal textwidth=78
- let a = execute('setlocal')
- call assert_match("^\n--- Local option values ---\n.*textwidth=78\\>", a)
- setlocal textwidth&
-
- setglobal textwidth=77
- let a = execute('setglobal')
- call assert_match("^\n--- Global option values ---\n.*textwidth=77\\>", a)
- setglobal textwidth&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_sha256.vim b/src/nvim/testdir/test_sha256.vim
deleted file mode 100644
index f6f430b04e..0000000000
--- a/src/nvim/testdir/test_sha256.vim
+++ /dev/null
@@ -1,22 +0,0 @@
-" Tests for the sha256() function.
-
-source check.vim
-CheckFeature cryptv
-CheckFunction sha256
-
-function Test_sha256()
- " test for empty string:
- call assert_equal('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', sha256(""))
-
- "'test for 1 char:
- call assert_equal('ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', sha256("a"))
- "
- "test for 3 chars:
- call assert_equal('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', "abc"->sha256())
-
- " test for contains meta char:
- call assert_equal('807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776', sha256("foo\nbar"))
-
- " test for contains non-ascii char:
- call assert_equal('5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953', sha256("\xde\xad\xbe\xef"))
-endfunction
diff --git a/src/nvim/testdir/test_shell.vim b/src/nvim/testdir/test_shell.vim
deleted file mode 100644
index 8b9c7a5b12..0000000000
--- a/src/nvim/testdir/test_shell.vim
+++ /dev/null
@@ -1,209 +0,0 @@
-" Test for the shell related options ('shell', 'shellcmdflag', 'shellpipe',
-" 'shellquote', 'shellredir', 'shellxescape', and 'shellxquote')
-
-source check.vim
-source shared.vim
-
-func Test_shell_options()
- " The expected value of 'shellcmdflag', 'shellpipe', 'shellquote',
- " 'shellredir', 'shellxescape', 'shellxquote' for the supported shells.
- let shells = []
- if has('unix')
- let shells += [['sh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['ksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['mksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['zsh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['zsh-beta', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['bash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['fish', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['ash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['dash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
- \ ['csh', '-c', '|& tee', '', '>&', '', ''],
- \ ['tcsh', '-c', '|& tee', '', '>&', '', '']]
- endif
- if has('win32')
- let shells += [['cmd', '/s /c', '>%s 2>&1', '', '>%s 2>&1', '', '"']]
- endif
-
- " start a new Vim instance with 'shell' set to each of the supported shells
- " and check the default shell option settings
- let after =<< trim END
- let l = [&shell, &shellcmdflag, &shellpipe, &shellquote]
- let l += [&shellredir, &shellxescape, &shellxquote]
- call writefile([json_encode(l)], 'Xtestout')
- qall!
- END
- for e in shells
- if RunVim([], after, '--cmd "set shell=' .. e[0] .. '"')
- call assert_equal(e, json_decode(readfile('Xtestout')[0]))
- endif
- endfor
-
- " Test shellescape() for each of the shells.
- for e in shells
- exe 'set shell=' .. e[0]
- if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$'
- let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%#'"
- let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\#'"
- elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$'
- let str1 = "'cmd \"arg1\" ''arg2'' !%#'"
- let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\#'"
- else
- let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%#'"
- let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\#'"
- endif
- call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%#"), e[0])
- call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%#", 1), e[0])
-
- " Try running an external command with the shell.
- if executable(e[0])
- " set the shell options for the current 'shell'
- let [&shellcmdflag, &shellpipe, &shellquote, &shellredir,
- \ &shellxescape, &shellxquote] = e[1:6]
- new
- r !echo hello
- call assert_equal('hello', substitute(getline(2), '\W', '', 'g'), e[0])
- bwipe!
- endif
- endfor
- set shell& shellcmdflag& shellpipe& shellquote&
- set shellredir& shellxescape& shellxquote&
- call delete('Xtestout')
-endfunc
-
-" Test for the 'shell' option
-func Test_shell()
- throw 'Skipped: Nvim missing :shell currently'
- CheckUnix
- let save_shell = &shell
- set shell=
- let caught_e91 = 0
- try
- shell
- catch /E91:/
- let caught_e91 = 1
- endtry
- call assert_equal(1, caught_e91)
- let &shell = save_shell
-endfunc
-
-" Test for the 'shellquote' option
-func Test_shellquote()
- CheckUnix
- set shellquote=#
- set verbose=20
- redir => v
- silent! !echo Hello
- redir END
- set verbose&
- set shellquote&
- call assert_match(': "#echo Hello#"', v)
-endfunc
-
-" Test for the 'shellescape' option
-func Test_shellescape()
- let save_shell = &shell
- set shell=bash
- call assert_equal("'text'", shellescape('text'))
- call assert_equal("'te\"xt'", 'te"xt'->shellescape())
- call assert_equal("'te'\\''xt'", shellescape("te'xt"))
-
- call assert_equal("'te%xt'", shellescape("te%xt"))
- call assert_equal("'te\\%xt'", shellescape("te%xt", 1))
- call assert_equal("'te#xt'", shellescape("te#xt"))
- call assert_equal("'te\\#xt'", shellescape("te#xt", 1))
- call assert_equal("'te!xt'", shellescape("te!xt"))
- call assert_equal("'te\\!xt'", shellescape("te!xt", 1))
-
- call assert_equal("'te\nxt'", shellescape("te\nxt"))
- call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1))
- set shell=tcsh
- call assert_equal("'te\\!xt'", shellescape("te!xt"))
- call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1))
- call assert_equal("'te\\\nxt'", shellescape("te\nxt"))
- call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1))
-
- let &shell = save_shell
-endfunc
-
-" Test for 'shellslash'
-func Test_shellslash()
- CheckOption shellslash
- let save_shellslash = &shellslash
- " The shell and cmdflag, and expected slash in tempname with shellslash set or
- " unset. The assert checks the file separator before the leafname.
- " ".*\\\\[^\\\\]*$"
- let shells = [['cmd', '/c', '/', '/'],
- \ ['powershell', '-Command', '/', '/'],
- \ ['sh', '-c', '/', '/']]
- for e in shells
- exe 'set shell=' .. e[0] .. ' | set shellcmdflag=' .. e[1]
- set noshellslash
- let file = tempname()
- call assert_match('^.\+' .. e[2] .. '[^' .. e[2] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' nossl')
- set shellslash
- let file = tempname()
- call assert_match('^.\+' .. e[3] .. '[^' .. e[3] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' ssl')
- endfor
- let &shellslash = save_shellslash
-endfunc
-
-" Test for 'shellxquote'
-func Test_shellxquote()
- CheckUnix
-
- let save_shell = &shell
- let save_sxq = &shellxquote
- let save_sxe = &shellxescape
-
- call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell')
- call setfperm('Xtestshell', "r-x------")
- set shell=./Xtestshell
-
- set shellxquote=\\"
- call feedkeys(":!pwd\<CR>\<CR>", 'xt')
- call assert_equal(['Cmd: [-c "pwd"]'], readfile('Xlog'))
-
- set shellxquote=(
- call feedkeys(":!pwd\<CR>\<CR>", 'xt')
- call assert_equal(['Cmd: [-c (pwd)]'], readfile('Xlog'))
-
- set shellxquote=\\"(
- call feedkeys(":!pwd\<CR>\<CR>", 'xt')
- call assert_equal(['Cmd: [-c "(pwd)"]'], readfile('Xlog'))
-
- set shellxescape=\"&<<()@^
- set shellxquote=(
- call feedkeys(":!pwd\"&<<{}@^\<CR>\<CR>", 'xt')
- call assert_equal(['Cmd: [-c (pwd^"^&^<^<{}^@^^)]'], readfile('Xlog'))
-
- let &shell = save_shell
- let &shellxquote = save_sxq
- let &shellxescape = save_sxe
- call delete('Xtestshell')
- call delete('Xlog')
-endfunc
-
-" Test for using the shell set in the $SHELL environment variable
-func Test_set_shell()
- let after =<< trim [CODE]
- call writefile([&shell], "Xtestout")
- quit!
- [CODE]
-
- if has('win32')
- let $SHELL = 'C:\with space\cmd.exe'
- let expected = '"C:\with space\cmd.exe"'
- else
- let $SHELL = '/bin/with space/sh'
- let expected = '"/bin/with space/sh"'
- endif
-
- if RunVimPiped([], after, '', '')
- let lines = readfile('Xtestout')
- call assert_equal(expected, lines[0])
- endif
- call delete('Xtestout')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_shift.vim b/src/nvim/testdir/test_shift.vim
deleted file mode 100644
index ec357dac88..0000000000
--- a/src/nvim/testdir/test_shift.vim
+++ /dev/null
@@ -1,117 +0,0 @@
-" Test shifting lines with :> and :<
-
-source check.vim
-
-func Test_ex_shift_right()
- set shiftwidth=2
-
- " shift right current line.
- call setline(1, range(1, 5))
- 2
- >
- 3
- >>
- call assert_equal(['1',
- \ ' 2',
- \ ' 3',
- \ '4',
- \ '5'], getline(1, '$'))
-
- " shift right with range.
- call setline(1, range(1, 4))
- 2,3>>
- call assert_equal(['1',
- \ ' 2',
- \ ' 3',
- \ '4',
- \ '5'], getline(1, '$'))
-
- " shift right with range and count.
- call setline(1, range(1, 4))
- 2>3
- call assert_equal(['1',
- \ ' 2',
- \ ' 3',
- \ ' 4',
- \ '5'], getline(1, '$'))
-
- bw!
- set shiftwidth&
-endfunc
-
-func Test_ex_shift_left()
- set shiftwidth=2
-
- call setline(1, range(1, 5))
- %>>>
-
- " left shift current line.
- 2<
- 3<<
- 4<<<<<
- call assert_equal([' 1',
- \ ' 2',
- \ ' 3',
- \ '4',
- \ ' 5'], getline(1, '$'))
-
- " shift right with range.
- call setline(1, range(1, 5))
- %>>>
- 2,3<<
- call assert_equal([' 1',
- \ ' 2',
- \ ' 3',
- \ ' 4',
- \ ' 5'], getline(1, '$'))
-
- " shift right with range and count.
- call setline(1, range(1, 5))
- %>>>
- 2<<3
- call assert_equal([' 1',
- \ ' 2',
- \ ' 3',
- \ ' 4',
- \ ' 5'], getline(1, '$'))
-
- bw!
- set shiftwidth&
-endfunc
-
-func Test_ex_shift_rightleft()
- CheckFeature rightleft
-
- set shiftwidth=2 rightleft
-
- call setline(1, range(1, 4))
- 2,3<<
- call assert_equal(['1',
- \ ' 2',
- \ ' 3',
- \ '4'], getline(1, '$'))
-
- 3,4>
- call assert_equal(['1',
- \ ' 2',
- \ ' 3',
- \ '4'], getline(1, '$'))
-
- bw!
- set rightleft& shiftwidth&
-endfunc
-
-func Test_ex_shift_errors()
- call assert_fails('><', 'E488:')
- call assert_fails('<>', 'E488:')
-
- call assert_fails('>!', 'E477:')
- call assert_fails('<!', 'E477:')
-
- " call assert_fails('2,1>', 'E493:')
- call assert_fails('execute "2,1>"', 'E493:')
- " call assert_fails('2,1<', 'E493:')
- call assert_fails('execute "2,1<"', 'E493:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_signals.vim b/src/nvim/testdir/test_signals.vim
deleted file mode 100644
index c291c68e0d..0000000000
--- a/src/nvim/testdir/test_signals.vim
+++ /dev/null
@@ -1,165 +0,0 @@
-" Test signal handling.
-
-source check.vim
-source term_util.vim
-
-CheckUnix
-
-source shared.vim
-
-" Check whether a signal is available on this system.
-func HasSignal(signal)
- let signals = system('kill -l')
- return signals =~# '\<' .. a:signal .. '\>'
-endfunc
-
-" Test signal WINCH (window resize signal)
-func Test_signal_WINCH()
- throw 'skipped: Nvim cannot avoid terminal resize'
- CheckNotGui
- if !HasSignal('WINCH')
- throw 'Skipped: WINCH signal not supported'
- endif
-
- " We do not actually want to change the size of the terminal.
- let old_WS = ''
- if exists('&t_WS')
- let old_WS = &t_WS
- let &t_WS = ''
- endif
-
- let old_lines = &lines
- let old_columns = &columns
- let new_lines = &lines - 2
- let new_columns = &columns - 2
-
- exe 'set lines=' .. new_lines
- exe 'set columns=' .. new_columns
- call assert_equal(new_lines, &lines)
- call assert_equal(new_columns, &columns)
-
- " Send signal and wait for signal to be processed.
- " 'lines' and 'columns' should have been restored
- " after handing signal WINCH.
- exe 'silent !kill -s WINCH ' .. getpid()
- call WaitForAssert({-> assert_equal(old_lines, &lines)})
- call assert_equal(old_columns, &columns)
-
- if old_WS != ''
- let &t_WS = old_WS
- endif
-endfunc
-
-" Test signal PWR, which should update the swap file.
-func Test_signal_PWR()
- if !HasSignal('PWR')
- throw 'Skipped: PWR signal not supported'
- endif
-
- " Set a very large 'updatetime' and 'updatecount', so that we can be sure
- " that swap file is updated as a result of sending PWR signal, and not
- " because of exceeding 'updatetime' or 'updatecount' when changing buffer.
- set updatetime=100000 updatecount=100000
- new Xtest_signal_PWR
- let swap_name = swapname('%')
- call setline(1, '123')
- preserve
- let swap_content = readfile(swap_name, 'b')
-
- " Update the buffer and check that the swap file is not yet updated,
- " since we set 'updatetime' and 'updatecount' to large values.
- call setline(1, 'abc')
- call assert_equal(swap_content, readfile(swap_name, 'b'))
-
- " Sending PWR signal should update the swap file.
- exe 'silent !kill -s PWR ' .. getpid()
- call WaitForAssert({-> assert_notequal(swap_content, readfile(swap_name, 'b'))})
-
- bwipe!
- set updatetime& updatecount&
-endfunc
-
-" Test signal INT. Handler sets got_int. It should be like typing CTRL-C.
-func Test_signal_INT()
- CheckRunVimInTerminal
- if !HasSignal('INT')
- throw 'Skipped: INT signal not supported'
- endif
-
- " Skip the rest of the test when running with valgrind as signal INT is not
- " received somehow by Vim when running with valgrind.
- let cmd = GetVimCommand()
- if cmd =~ 'valgrind'
- throw 'Skipped: cannot test signal INT with valgrind'
- endif
-
- let buf = RunVimInTerminal('', {'rows': 6})
- let pid_vim = term_getjob(buf)->job_info().process
-
- " Check that an endless loop in Vim is interrupted by signal INT.
- call term_sendkeys(buf, ":while 1 | endwhile\n")
- call WaitForAssert({-> assert_equal(':while 1 | endwhile', term_getline(buf, 6))})
- exe 'silent !kill -s INT ' .. pid_vim
- call term_sendkeys(buf, ":call setline(1, 'INTERUPTED')\n")
- call WaitForAssert({-> assert_equal('INTERUPTED', term_getline(buf, 1))})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Test a deadly signal.
-"
-" There are several deadly signals: SISEGV, SIBUS, SIGTERM...
-" Test uses signal SIGTERM as it does not create a core
-" dump file unlike SIGSEGV, SIGBUS, etc. See "man 7 signals.
-"
-" Vim should exit with a deadly signal and unsaved changes
-" should be recoverable from the swap file preserved as a
-" result of the deadly signal handler.
-func Test_deadly_signal_TERM()
- if !HasSignal('TERM')
- throw 'Skipped: TERM signal not supported'
- endif
- CheckRunVimInTerminal
- let cmd = GetVimCommand()
- if cmd =~ 'valgrind'
- throw 'Skipped: cannot test signal TERM with valgrind'
- endif
-
- " If test fails once, it can leave temporary files and trying to rerun
- " the test would then fail again if they are not deleted first.
- call delete('.Xsig_TERM.swp')
- call delete('XsetupAucmd')
- call delete('XautoOut')
- let lines =<< trim END
- au VimLeave * call writefile(["VimLeave triggered"], "XautoOut", "as")
- au VimLeavePre * call writefile(["VimLeavePre triggered"], "XautoOut", "as")
- END
- call writefile(lines, 'XsetupAucmd')
-
- let buf = RunVimInTerminal('-S XsetupAucmd Xsig_TERM', {'rows': 6})
- let pid_vim = term_getjob(buf)->job_info().process
-
- call term_sendkeys(buf, ":call setline(1, 'foo')\n")
- call WaitForAssert({-> assert_equal('foo', term_getline(buf, 1))})
-
- call assert_false(filereadable('Xsig_TERM'))
- exe 'silent !kill -s TERM ' .. pid_vim
- call WaitForAssert({-> assert_true(filereadable('.Xsig_TERM.swp'))})
-
- " Don't call StopVimInTerminal() as it expects job to be still running.
- call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))})
-
- new
- silent recover .Xsig_TERM.swp
- call assert_equal(['foo'], getline(1, '$'))
-
- let result = readfile('XautoOut')
- call assert_equal(["VimLeavePre triggered", "VimLeave triggered"], result)
-
- %bwipe!
- call delete('.Xsig_TERM.swp')
- call delete('XsetupAucmd')
- call delete('XautoOut')
-endfunc
-
-" vim: ts=8 sw=2 sts=2 tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim
deleted file mode 100644
index 8311955a15..0000000000
--- a/src/nvim/testdir/test_signs.vim
+++ /dev/null
@@ -1,2033 +0,0 @@
-" Test for signs
-
-source check.vim
-CheckFeature signs
-
-source screendump.vim
-
-func Test_sign()
- new
- call setline(1, ['a', 'b', 'c', 'd'])
-
- " Define some signs.
- " We can specify icons even if not all versions of vim support icons as
- " icon is ignored when not supported. "(not supported)" is shown after
- " the icon name when listing signs.
- sign define Sign1 text=x
-
- call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search numhl=Number icon=../../pixmaps/stock_vim_find_help.png')
-
- " Test listing signs.
- let a=execute('sign list')
- call assert_match('^\nsign Sign1 text=x \nsign Sign2 ' .
- \ 'icon=../../pixmaps/stock_vim_find_help.png .*text=xy ' .
- \ 'linehl=Error texthl=Title culhl=Search numhl=Number$', a)
-
- let a=execute('sign list Sign1')
- call assert_equal("\nsign Sign1 text=x ", a)
-
- " Split the window to the bottom to verify sign jump will stay in the
- " current window if the buffer is displayed there.
- let bn = bufnr('%')
- let wn = winnr()
- exe 'sign place 41 line=3 name=Sign1 buffer=' . bn
- 1
- bot split
- exe 'sign jump 41 buffer=' . bufnr('%')
- call assert_equal('c', getline('.'))
- call assert_equal(3, winnr())
- call assert_equal(bn, bufnr('%'))
- call assert_notequal(wn, winnr())
-
- " Create a new buffer and check that ":sign jump" switches to the old buffer.
- 1
- new foo
- call assert_notequal(bn, bufnr('%'))
- exe 'sign jump 41 buffer=' . bn
- call assert_equal(bn, bufnr('%'))
- call assert_equal('c', getline('.'))
-
- " Redraw to make sure that screen redraw with sign gets exercised,
- " with and without 'rightleft'.
- if has('rightleft')
- set rightleft
- redraw
- set norightleft
- endif
- redraw
-
- " Check that we can't change sign.
- call assert_fails("sign place 40 name=Sign1 buffer=" . bufnr('%'), 'E885:')
-
- " Check placed signs
- let a=execute('sign place')
- call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n" .
- \ " line=3 id=41 name=Sign1 priority=10\n", a)
-
- " Unplace the sign and try jumping to it again should fail.
- sign unplace 41
- 1
- call assert_fails("sign jump 41 buffer=" . bufnr('%'), 'E157:')
- call assert_equal('a', getline('.'))
-
- " Unplace sign on current line.
- exe 'sign place 42 line=4 name=Sign2 buffer=' . bufnr('%')
- 4
- sign unplace
- let a=execute('sign place')
- call assert_equal("\n--- Signs ---\n", a)
-
- " Try again to unplace sign on current line, it should fail this time.
- call assert_fails('sign unplace', 'E159:')
-
- " Unplace all signs.
- exe 'sign place 41 line=3 name=Sign1 buffer=' . bufnr('%')
- sign unplace *
- let a=execute('sign place')
- call assert_equal("\n--- Signs ---\n", a)
-
- " Place a sign without specifying the filename or buffer
- sign place 77 line=9 name=Sign2
- let a=execute('sign place')
- call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n" .
- \ " line=9 id=77 name=Sign2 priority=10\n", a)
- sign unplace *
-
- " Check :jump with file=...
- edit foo
- call setline(1, ['A', 'B', 'C', 'D'])
-
- call Sign_command_ignore_error('sign define Sign3 text=y texthl=DoesNotExist linehl=DoesNotExist icon=doesnotexist.xpm')
-
- let fn = expand('%:p')
- exe 'sign place 43 line=2 name=Sign3 file=' . fn
- edit bar
- call assert_notequal(fn, expand('%:p'))
- exe 'sign jump 43 file=' . fn
- call assert_equal('B', getline('.'))
-
- " Check for jumping to a sign in a hidden buffer
- enew! | only!
- edit foo
- call setline(1, ['A', 'B', 'C', 'D'])
- let fn = expand('%:p')
- exe 'sign place 21 line=3 name=Sign3 file=' . fn
- hide edit bar
- exe 'sign jump 21 file=' . fn
- call assert_equal('C', getline('.'))
-
- " can't define a sign with a non-printable character as text
- call assert_fails("sign define Sign4 text=\e linehl=Comment", 'E239:')
- call assert_fails("sign define Sign4 text=a\e linehl=Comment", 'E239:')
- call assert_fails("sign define Sign4 text=\ea linehl=Comment", 'E239:')
-
- " Only 0, 1 or 2 character text is allowed
- call assert_fails("sign define Sign4 text=abc linehl=Comment", 'E239:')
- " call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:')
- call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:')
-
- " an empty highlight argument for an existing sign clears it
- sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl numhl=NumHl
- let sl = sign_getdefined('SignY')[0]
- call assert_equal('TextHl', sl.texthl)
- call assert_equal('CulHl', sl.culhl)
- call assert_equal('LineHl', sl.linehl)
- call assert_equal('NumHl', sl.numhl)
-
- sign define SignY texthl= culhl=CulHl linehl=LineHl numhl=NumHl
- let sl = sign_getdefined('SignY')[0]
- call assert_false(has_key(sl, 'texthl'))
- call assert_equal('CulHl', sl.culhl)
- call assert_equal('LineHl', sl.linehl)
- call assert_equal('NumHl', sl.numhl)
-
- sign define SignY linehl=
- let sl = sign_getdefined('SignY')[0]
- call assert_false(has_key(sl, 'linehl'))
- call assert_equal('CulHl', sl.culhl)
- call assert_equal('NumHl', sl.numhl)
-
- sign define SignY culhl=
- let sl = sign_getdefined('SignY')[0]
- call assert_false(has_key(sl, 'culhl'))
- call assert_equal('NumHl', sl.numhl)
-
- sign define SignY numhl=
- let sl = sign_getdefined('SignY')[0]
- call assert_false(has_key(sl, 'numhl'))
-
- sign undefine SignY
-
- " define sign with whitespace
- sign define Sign4 text=\ X linehl=Comment
- sign undefine Sign4
- sign define Sign4 linehl=Comment text=\ X
- sign undefine Sign4
-
- sign define Sign5 text=X\ linehl=Comment
- sign undefine Sign5
- sign define Sign5 linehl=Comment text=X\
- sign undefine Sign5
-
- " define sign with backslash
- sign define Sign4 text=\\\\ linehl=Comment
- sign undefine Sign4
- sign define Sign4 text=\\ linehl=Comment
- sign undefine Sign4
-
- " define a sign with a leading 0 in the name
- sign unplace *
- sign define 004 text=#> linehl=Comment
- let a = execute('sign list 4')
- call assert_equal("\nsign 4 text=#> linehl=Comment", a)
- exe 'sign place 20 line=3 name=004 buffer=' . bufnr('')
- let a = execute('sign place')
- call assert_equal("\n--- Signs ---\nSigns for foo:\n" .
- \ " line=3 id=20 name=4 priority=10\n", a)
- exe 'sign unplace 20 buffer=' . bufnr('')
- sign undefine 004
- call assert_fails('sign list 4', 'E155:')
-
- " After undefining the sign, we should no longer be able to place it.
- sign undefine Sign1
- sign undefine Sign2
- sign undefine Sign3
- call assert_fails("sign place 41 line=3 name=Sign1 buffer=" .
- \ bufnr('%'), 'E155:')
-endfunc
-
-" Undefining placed sign is not recommended.
-" Quoting :help sign
-"
-" :sign undefine {name}
-" Deletes a previously defined sign. If signs with this {name}
-" are still placed this will cause trouble.
-func Test_sign_undefine_still_placed()
- new foobar
- sign define Sign text=x
- exe 'sign place 41 line=1 name=Sign buffer=' . bufnr('%')
- sign undefine Sign
-
- " Listing placed sign should show that sign is deleted.
- let a=execute('sign place')
- call assert_equal("\n--- Signs ---\nSigns for foobar:\n" .
- \ " line=1 id=41 name=[Deleted] priority=10\n", a)
-
- sign unplace 41
- let a=execute('sign place')
- call assert_equal("\n--- Signs ---\n", a)
-endfunc
-
-func Test_sign_completion()
- sign define Sign1 text=x
- sign define Sign2 text=y
-
- call feedkeys(":sign \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sign define jump list place undefine unplace', @:)
-
- call feedkeys(":sign define Sign \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sign define Sign culhl= icon= linehl= numhl= text= texthl=', @:)
-
- for hl in ['culhl', 'linehl', 'numhl', 'texthl']
- call feedkeys(":sign define Sign "..hl.."=Spell\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sign define Sign '..hl..'=SpellBad SpellCap ' .
- \ 'SpellLocal SpellRare', @:)
- endfor
-
- 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', @:)
-
- " 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', @:)
-
- call feedkeys(":sign place 1 \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"sign place 1 buffer= file= group= line= name= priority=',
- \ @:)
-
- 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()
- sign define Sign1 text=x
-
- call assert_fails('sign', 'E471:')
- call assert_fails('sign jump', 'E471:')
- call assert_fails('sign xxx', 'E160:')
- call assert_fails('sign define', 'E156:')
- call assert_fails('sign define Sign1 xxx', 'E475:')
- call assert_fails('sign undefine', 'E156:')
- call assert_fails('sign list xxx', 'E155:')
- call assert_fails('sign place 1 buffer=999', 'E158:')
- call assert_fails('sign place 1 name=Sign1 buffer=999', 'E158:')
- call assert_fails('sign place buffer=999', 'E158:')
- call assert_fails('sign jump buffer=999', 'E158:')
- call assert_fails('sign jump 1 file=', 'E158:')
- call assert_fails('sign jump 1 group=', 'E474:')
- call assert_fails('sign jump 1 name=', 'E474:')
- call assert_fails('sign jump 1 name=Sign1', 'E474:')
- call assert_fails('sign jump 1 line=100', '474:')
- " call assert_fails('sign define Sign2 text=', 'E239:')
- " Non-numeric identifier for :sign place
- call assert_fails("sign place abc line=3 name=Sign1 buffer=" . bufnr(''),
- \ 'E474:')
- " Non-numeric identifier for :sign unplace
- call assert_fails("sign unplace abc name=Sign1 buffer=" . bufnr(''),
- \ 'E474:')
- " Number followed by an alphabet as sign identifier for :sign place
- call assert_fails("sign place 1abc line=3 name=Sign1 buffer=" . bufnr(''),
- \ 'E474:')
- " Number followed by an alphabet as sign identifier for :sign unplace
- call assert_fails("sign unplace 2abc name=Sign1 buffer=" . bufnr(''),
- \ 'E474:')
- " Sign identifier and '*' for :sign unplace
- call assert_fails("sign unplace 2 *", 'E474:')
- " Trailing characters after buffer number for :sign place
- call assert_fails("sign place 1 line=3 name=Sign1 buffer=" .
- \ bufnr('%') . 'xxx', 'E488:')
- " Trailing characters after buffer number for :sign unplace
- call assert_fails("sign unplace 1 buffer=" . bufnr('%') . 'xxx', 'E488:')
- call assert_fails("sign unplace * buffer=" . bufnr('%') . 'xxx', 'E488:')
- call assert_fails("sign unplace 1 xxx", 'E474:')
- call assert_fails("sign unplace * xxx", 'E474:')
- call assert_fails("sign unplace xxx", 'E474:')
- " Placing a sign without line number
- call assert_fails("sign place name=Sign1 buffer=" . bufnr('%'), 'E474:')
- " Placing a sign without sign name
- call assert_fails("sign place line=10 buffer=" . bufnr('%'), 'E474:')
- " Unplacing a sign with line number
- call assert_fails("sign unplace 2 line=10 buffer=" . bufnr('%'), 'E474:')
- " Unplacing a sign with sign name
- call assert_fails("sign unplace 2 name=Sign1 buffer=" . bufnr('%'), 'E474:')
- " Placing a sign without sign name
- call assert_fails("sign place 2 line=3 buffer=" . bufnr('%'), 'E474:')
- " Placing a sign with only sign identifier
- call assert_fails("sign place 2", 'E474:')
- " Placing a sign with only a name
- call assert_fails("sign place abc", 'E474:')
- " Placing a sign with only line number
- call assert_fails("sign place 5 line=3", 'E474:')
- " Placing a sign with only sign group
- call assert_fails("sign place 5 group=g1", 'E474:')
- call assert_fails("sign place 5 group=*", 'E474:')
- " Placing a sign with only sign priority
- call assert_fails("sign place 5 priority=10", 'E474:')
-
- sign undefine Sign1
-endfunc
-
-func Test_sign_delete_buffer()
- new
- sign define Sign text=x
- let bufnr = bufnr('%')
- new
- exe 'bd ' . bufnr
- exe 'sign place 61 line=3 name=Sign buffer=' . bufnr
- call assert_fails('sign jump 61 buffer=' . bufnr, 'E934:')
- sign unplace 61
- sign undefine Sign
-endfunc
-
-" Ignore error: E255: Couldn't read in sign data!
-" This error can happen when running in the GUI.
-" Some gui like Motif do not support the png icon format.
-func Sign_command_ignore_error(cmd)
- try
- exe a:cmd
- catch /E255:/
- endtry
-endfunc
-
-" ignore error: E255: Couldn't read in sign data!
-" This error can happen when running in gui.
-func Sign_define_ignore_error(name, attr)
- try
- call sign_define(a:name, a:attr)
- catch /E255:/
- endtry
-endfunc
-
-" Test for Vim script functions for managing signs
-func Test_sign_funcs()
- " Remove all the signs
- call sign_unplace('*')
- call sign_undefine()
-
- " Tests for sign_define()
- let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error',
- \ 'culhl': 'Visual', 'numhl': 'Number'}
- call assert_equal(0, "sign1"->sign_define(attr))
- call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', 'linehl' : 'Search',
- \ 'culhl' : 'Visual', 'numhl': 'Number', 'text' : '=>'}],
- \ sign_getdefined())
-
- " Define a new sign without attributes and then update it
- call sign_define("sign2")
- let attr = {'text' : '!!', 'linehl' : 'DiffAdd', 'texthl' : 'DiffChange',
- \ 'culhl': 'DiffDelete', 'numhl': 'Number', 'icon' : 'sign2.ico'}
- call Sign_define_ignore_error("sign2", attr)
- call assert_equal([{'name' : 'sign2', 'texthl' : 'DiffChange',
- \ 'linehl' : 'DiffAdd', 'culhl' : 'DiffDelete', 'text' : '!!',
- \ 'numhl': 'Number', 'icon' : 'sign2.ico'}],
- \ "sign2"->sign_getdefined())
-
- " Test for a sign name with digits
- call assert_equal(0, sign_define(0002, {'linehl' : 'StatusLine'}))
- call assert_equal([{'name' : '2', 'linehl' : 'StatusLine'}],
- \ sign_getdefined(0002))
- eval 0002->sign_undefine()
-
- " Tests for invalid arguments to sign_define()
- call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:')
- " call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
- call assert_fails('call sign_define({})', 'E731:')
- call assert_fails('call sign_define("sign6", [])', 'E715:')
-
- " Tests for sign_getdefined()
- call assert_equal([], sign_getdefined("none"))
- call assert_fails('call sign_getdefined({})', 'E731:')
-
- " Tests for sign_place()
- call writefile(repeat(["Sun is shining"], 30), "Xsign")
- edit Xsign
-
- call assert_equal(10, sign_place(10, '', 'sign1', 'Xsign',
- \ {'lnum' : 20}))
- call assert_equal([{'bufnr' : bufnr(''), 'signs' :
- \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
- \ 'priority' : 10}]}], sign_getplaced('Xsign'))
- call assert_equal([{'bufnr' : bufnr(''), 'signs' :
- \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
- \ 'priority' : 10}]}],
- \ '%'->sign_getplaced({'lnum' : 20}))
- call assert_equal([{'bufnr' : bufnr(''), 'signs' :
- \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
- \ 'priority' : 10}]}],
- \ sign_getplaced('', {'id' : 10}))
-
- " Tests for invalid arguments to sign_place()
- call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:')
- call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:')
- call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])',
- \ 'E715:')
- call assert_fails('call sign_place(-1, "", "sign1", "Xsign",
- \ {"lnum" : 30})', 'E474:')
- call assert_fails('call sign_place(10, "", "xsign1x", "Xsign",
- \ {"lnum" : 30})', 'E155:')
- call assert_fails('call sign_place(10, "", "", "Xsign",
- \ {"lnum" : 30})', 'E155:')
- call assert_fails('call sign_place(10, "", [], "Xsign",
- \ {"lnum" : 30})', 'E730:')
- call assert_fails('call sign_place(5, "", "sign1", "abcxyz.xxx",
- \ {"lnum" : 10})', 'E158:')
- call assert_fails('call sign_place(5, "", "sign1", "@", {"lnum" : 10})',
- \ 'E158:')
- call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})',
- \ 'E730:')
- call assert_fails('call sign_place(21, "", "sign1", "Xsign",
- \ {"lnum" : -1})', 'E474:')
- call assert_fails('call sign_place(22, "", "sign1", "Xsign",
- \ {"lnum" : 0})', 'E474:')
- call assert_fails('call sign_place(22, "", "sign1", "Xsign",
- \ {"lnum" : []})', 'E745:')
- call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10}))
-
- " Tests for sign_getplaced()
- call assert_equal([{'bufnr' : bufnr(''), 'signs' :
- \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
- \ 'priority' : 10}]}],
- \ sign_getplaced(bufnr('Xsign')))
- call assert_equal([{'bufnr' : bufnr(''), 'signs' :
- \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
- \ 'priority' : 10}]}],
- \ sign_getplaced())
- call assert_fails("call sign_getplaced('dummy.sign')", 'E158:')
- call assert_fails('call sign_getplaced("&")', 'E158:')
- call assert_fails('call sign_getplaced(-1)', 'E158:')
- call assert_fails('call sign_getplaced("Xsign", [])', 'E715:')
- call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}],
- \ sign_getplaced('Xsign', {'lnum' : 1000000}))
- call assert_fails("call sign_getplaced('Xsign', {'lnum' : []})",
- \ 'E745:')
- call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}],
- \ sign_getplaced('Xsign', {'id' : 44}))
- call assert_fails("call sign_getplaced('Xsign', {'id' : []})",
- \ 'E745:')
-
- " Tests for sign_unplace()
- eval 20->sign_place('', 'sign2', 'Xsign', {"lnum" : 30})
- call assert_equal(0, sign_unplace('',
- \ {'id' : 20, 'buffer' : 'Xsign'}))
- call assert_equal(-1, ''->sign_unplace(
- \ {'id' : 30, 'buffer' : 'Xsign'}))
- call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30})
- call assert_fails("call sign_unplace('',
- \ {'id' : 20, 'buffer' : 'buffer.c'})", 'E158:')
- call assert_fails("call sign_unplace('',
- \ {'id' : 20, 'buffer' : '&'})", 'E158:')
- call assert_fails("call sign_unplace('g1',
- \ {'id' : 20, 'buffer' : 200})", 'E158:')
- call assert_fails("call sign_unplace('g1', 'mySign')", 'E715:')
-
- call sign_unplace('*')
-
- " Test for modifying a placed sign
- call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20}))
- call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign'))
- call assert_equal([{'bufnr' : bufnr(''), 'signs' :
- \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2',
- \ 'priority' : 10}]}],
- \ sign_getplaced())
-
- " Tests for sign_undefine()
- call assert_equal(0, sign_undefine("sign1"))
- call assert_equal([], sign_getdefined("sign1"))
- call assert_fails('call sign_undefine("none")', 'E155:')
- call assert_fails('call sign_undefine({})', 'E731:')
-
- " Test for using '.' as the line number for sign_place()
- call Sign_define_ignore_error("sign1", attr)
- call cursor(22, 1)
- call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign',
- \ {'lnum' : '.'}))
- call assert_equal([{'bufnr' : bufnr(''), 'signs' :
- \ [{'id' : 15, 'group' : '', 'lnum' : 22, 'name' : 'sign1',
- \ 'priority' : 10}]}],
- \ sign_getplaced('%', {'lnum' : 22}))
-
- call delete("Xsign")
- call sign_unplace('*')
- call sign_undefine()
- enew | only
-endfunc
-
-" Tests for sign groups
-func Test_sign_group()
- enew | only
- " Remove all the signs
- call sign_unplace('*')
- call sign_undefine()
-
- call writefile(repeat(["Sun is shining"], 30), "Xsign")
-
- let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
- call assert_equal(0, sign_define("sign1", attr))
-
- edit Xsign
- let bnum = bufnr('%')
-
- " Error case
- call assert_fails("call sign_place(5, [], 'sign1', 'Xsign',
- \ {'lnum' : 30})", 'E730:')
-
- " place three signs with the same identifier. One in the global group and
- " others in the named groups
- call assert_equal(5, sign_place(5, '', 'sign1', 'Xsign',
- \ {'lnum' : 10}))
- call assert_equal(5, sign_place(5, 'g1', 'sign1', bnum, {'lnum' : 20}))
- call assert_equal(5, sign_place(5, 'g2', 'sign1', bnum, {'lnum' : 30}))
-
- " Test for sign_getplaced() with group
- let s = sign_getplaced('Xsign')
- call assert_equal(1, len(s[0].signs))
- call assert_equal(s[0].signs[0].group, '')
- let s = sign_getplaced(bnum, {'group' : ''})
- call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
- \ 'priority' : 10}], s[0].signs)
- call assert_equal(1, len(s[0].signs))
- let s = sign_getplaced(bnum, {'group' : 'g2'})
- call assert_equal('g2', s[0].signs[0].group)
- let s = sign_getplaced(bnum, {'group' : 'g3'})
- call assert_equal([], s[0].signs)
- let s = sign_getplaced(bnum, {'group' : '*'})
- call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
- \ 'priority' : 10},
- \ {'id' : 5, 'group' : 'g1', 'name' : 'sign1', 'lnum' : 20,
- \ 'priority' : 10},
- \ {'id' : 5, 'group' : 'g2', 'name' : 'sign1', 'lnum' : 30,
- \ 'priority' : 10}],
- \ s[0].signs)
-
- " Test for sign_getplaced() with id
- let s = sign_getplaced(bnum, {'id' : 5})
- call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
- \ 'priority' : 10}],
- \ s[0].signs)
- let s = sign_getplaced(bnum, {'id' : 5, 'group' : 'g2'})
- call assert_equal(
- \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
- \ 'priority' : 10}],
- \ s[0].signs)
- let s = sign_getplaced(bnum, {'id' : 5, 'group' : '*'})
- call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
- \ 'priority' : 10},
- \ {'id' : 5, 'group' : 'g1', 'name' : 'sign1', 'lnum' : 20,
- \ 'priority' : 10},
- \ {'id' : 5, 'group' : 'g2', 'name' : 'sign1', 'lnum' : 30,
- \ 'priority' : 10}],
- \ s[0].signs)
- let s = sign_getplaced(bnum, {'id' : 5, 'group' : 'g3'})
- call assert_equal([], s[0].signs)
-
- " Test for sign_getplaced() with lnum
- let s = sign_getplaced(bnum, {'lnum' : 20})
- call assert_equal([], s[0].signs)
- let s = sign_getplaced(bnum, {'lnum' : 20, 'group' : 'g1'})
- call assert_equal(
- \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : 'g1',
- \ 'priority' : 10}],
- \ s[0].signs)
- let s = sign_getplaced(bnum, {'lnum' : 30, 'group' : '*'})
- call assert_equal(
- \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
- \ 'priority' : 10}],
- \ s[0].signs)
- let s = sign_getplaced(bnum, {'lnum' : 40, 'group' : '*'})
- call assert_equal([], s[0].signs)
-
- " Error case
- call assert_fails("call sign_getplaced(bnum, {'group' : []})",
- \ 'E730:')
-
- " Clear the sign in global group
- call sign_unplace('', {'id' : 5, 'buffer' : bnum})
- let s = sign_getplaced(bnum, {'group' : '*'})
- call assert_equal([
- \ {'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : 'g1',
- \ 'priority' : 10},
- \ {'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
- \ 'priority' : 10}],
- \ s[0].signs)
-
- " Clear the sign in one of the groups
- call sign_unplace('g1', {'buffer' : 'Xsign'})
- let s = sign_getplaced(bnum, {'group' : '*'})
- call assert_equal([
- \ {'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
- \ 'priority' : 10}],
- \ s[0].signs)
-
- " Clear all the signs from the buffer
- call sign_unplace('*', {'buffer' : bnum})
- call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
-
- " Clear sign across groups using an identifier
- call sign_place(25, '', 'sign1', bnum, {'lnum' : 10})
- call sign_place(25, 'g1', 'sign1', bnum, {'lnum' : 11})
- call sign_place(25, 'g2', 'sign1', bnum, {'lnum' : 12})
- call assert_equal(0, sign_unplace('*', {'id' : 25}))
- call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
-
- " Error case
- call assert_fails("call sign_unplace({})", 'E474:')
-
- " Place a sign in the global group and try to delete it using a group
- call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
- call assert_equal(-1, sign_unplace('g1', {'id' : 5}))
-
- " Place signs in multiple groups and delete all the signs in one of the
- " group
- call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
- call assert_equal(6, sign_place(6, '', 'sign1', bnum, {'lnum' : 11}))
- call assert_equal(5, sign_place(5, 'g1', 'sign1', bnum, {'lnum' : 10}))
- call assert_equal(5, sign_place(5, 'g2', 'sign1', bnum, {'lnum' : 10}))
- call assert_equal(6, sign_place(6, 'g1', 'sign1', bnum, {'lnum' : 11}))
- call assert_equal(6, sign_place(6, 'g2', 'sign1', bnum, {'lnum' : 11}))
- call assert_equal(0, sign_unplace('g1'))
- let s = sign_getplaced(bnum, {'group' : 'g1'})
- call assert_equal([], s[0].signs)
- let s = sign_getplaced(bnum)
- call assert_equal(2, len(s[0].signs))
- let s = sign_getplaced(bnum, {'group' : 'g2'})
- call assert_equal('g2', s[0].signs[0].group)
- call assert_equal(0, sign_unplace('', {'id' : 5}))
- call assert_equal(0, sign_unplace('', {'id' : 6}))
- let s = sign_getplaced(bnum, {'group' : 'g2'})
- call assert_equal('g2', s[0].signs[0].group)
- call assert_equal(0, sign_unplace('', {'buffer' : bnum}))
-
- call sign_unplace('*')
-
- " Test for :sign command and groups
- sign place 5 line=10 name=sign1 file=Xsign
- sign place 5 group=g1 line=10 name=sign1 file=Xsign
- sign place 5 group=g2 line=10 name=sign1 file=Xsign
-
- " Tests for the ':sign place' command
-
- " :sign place file={fname}
- let a = execute('sign place file=Xsign')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 name=sign1 priority=10\n", a)
-
- " :sign place group={group} file={fname}
- let a = execute('sign place group=g2 file=Xsign')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 group=g2 name=sign1 priority=10\n", a)
-
- " :sign place group=* file={fname}
- let a = execute('sign place group=* file=Xsign')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 group=g2 name=sign1 priority=10\n" .
- \ " line=10 id=5 group=g1 name=sign1 priority=10\n" .
- \ " line=10 id=5 name=sign1 priority=10\n", a)
-
- " Error case: non-existing group
- let a = execute('sign place group=xyz file=Xsign')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a)
-
- call sign_unplace('*')
- let bnum = bufnr('Xsign')
- exe 'sign place 5 line=10 name=sign1 buffer=' . bnum
- exe 'sign place 5 group=g1 line=11 name=sign1 buffer=' . bnum
- exe 'sign place 5 group=g2 line=12 name=sign1 buffer=' . bnum
-
- " :sign place buffer={fname}
- let a = execute('sign place buffer=' . bnum)
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 name=sign1 priority=10\n", a)
-
- " :sign place group={group} buffer={fname}
- let a = execute('sign place group=g2 buffer=' . bnum)
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a)
-
- " :sign place group=* buffer={fname}
- let a = execute('sign place group=* buffer=' . bnum)
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 name=sign1 priority=10\n" .
- \ " line=11 id=5 group=g1 name=sign1 priority=10\n" .
- \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a)
-
- " Error case: non-existing group
- let a = execute('sign place group=xyz buffer=' . bnum)
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a)
-
- " :sign place
- let a = execute('sign place')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 name=sign1 priority=10\n", a)
-
- " Place signs in more than one buffer and list the signs
- split foo
- set buftype=nofile
- sign place 25 line=76 name=sign1 priority=99 file=foo
- let a = execute('sign place')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 name=sign1 priority=10\n" .
- \ "Signs for foo:\n" .
- \ " line=76 id=25 name=sign1 priority=99\n", a)
- close
- bwipe foo
-
- " :sign place group={group}
- let a = execute('sign place group=g1')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=11 id=5 group=g1 name=sign1 priority=10\n", a)
-
- " :sign place group=*
- let a = execute('sign place group=*')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 name=sign1 priority=10\n" .
- \ " line=11 id=5 group=g1 name=sign1 priority=10\n" .
- \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a)
-
- " Test for ':sign jump' command with groups
- sign jump 5 group=g1 file=Xsign
- call assert_equal(11, line('.'))
- call assert_equal('Xsign', bufname(''))
- sign jump 5 group=g2 file=Xsign
- call assert_equal(12, line('.'))
-
- " Test for :sign jump command without the filename or buffer
- sign jump 5
- call assert_equal(10, line('.'))
- sign jump 5 group=g1
- call assert_equal(11, line('.'))
-
- " Error cases
- call assert_fails("sign place 3 group= name=sign1 buffer=" . bnum, 'E474:')
-
- call delete("Xsign")
- call sign_unplace('*')
- call sign_undefine()
- enew | only
-endfunc
-
-" Place signs used for ":sign unplace" command test
-func Place_signs_for_test()
- call sign_unplace('*')
-
- sign place 3 line=10 name=sign1 file=Xsign1
- sign place 3 group=g1 line=11 name=sign1 file=Xsign1
- sign place 3 group=g2 line=12 name=sign1 file=Xsign1
- sign place 4 line=15 name=sign1 file=Xsign1
- sign place 4 group=g1 line=16 name=sign1 file=Xsign1
- sign place 4 group=g2 line=17 name=sign1 file=Xsign1
- sign place 5 line=20 name=sign1 file=Xsign2
- sign place 5 group=g1 line=21 name=sign1 file=Xsign2
- sign place 5 group=g2 line=22 name=sign1 file=Xsign2
- sign place 6 line=25 name=sign1 file=Xsign2
- sign place 6 group=g1 line=26 name=sign1 file=Xsign2
- sign place 6 group=g2 line=27 name=sign1 file=Xsign2
-endfunc
-
-" Place multiple signs in a single line for test
-func Place_signs_at_line_for_test()
- call sign_unplace('*')
- sign place 3 line=13 name=sign1 file=Xsign1
- sign place 3 group=g1 line=13 name=sign1 file=Xsign1
- sign place 3 group=g2 line=13 name=sign1 file=Xsign1
- sign place 4 line=13 name=sign1 file=Xsign1
- sign place 4 group=g1 line=13 name=sign1 file=Xsign1
- sign place 4 group=g2 line=13 name=sign1 file=Xsign1
-endfunc
-
-" Tests for the ':sign unplace' command
-func Test_sign_unplace()
- enew | only
- " Remove all the signs
- call sign_unplace('*')
- call sign_undefine()
-
- " Create two files and define signs
- call writefile(repeat(["Sun is shining"], 30), "Xsign1")
- call writefile(repeat(["It is beautiful"], 30), "Xsign2")
-
- let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
- call sign_define("sign1", attr)
-
- edit Xsign1
- let bnum1 = bufnr('%')
- split Xsign2
- let bnum2 = bufnr('%')
-
- let signs1 = [{'id' : 3, 'name' : 'sign1', 'lnum' : 10, 'group' : '',
- \ 'priority' : 10},
- \ {'id' : 3, 'name' : 'sign1', 'lnum' : 11, 'group' : 'g1',
- \ 'priority' : 10},
- \ {'id' : 3, 'name' : 'sign1', 'lnum' : 12, 'group' : 'g2',
- \ 'priority' : 10},
- \ {'id' : 4, 'name' : 'sign1', 'lnum' : 15, 'group' : '',
- \ 'priority' : 10},
- \ {'id' : 4, 'name' : 'sign1', 'lnum' : 16, 'group' : 'g1',
- \ 'priority' : 10},
- \ {'id' : 4, 'name' : 'sign1', 'lnum' : 17, 'group' : 'g2',
- \ 'priority' : 10},]
- let signs2 = [{'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : '',
- \ 'priority' : 10},
- \ {'id' : 5, 'name' : 'sign1', 'lnum' : 21, 'group' : 'g1',
- \ 'priority' : 10},
- \ {'id' : 5, 'name' : 'sign1', 'lnum' : 22, 'group' : 'g2',
- \ 'priority' : 10},
- \ {'id' : 6, 'name' : 'sign1', 'lnum' : 25, 'group' : '',
- \ 'priority' : 10},
- \ {'id' : 6, 'name' : 'sign1', 'lnum' : 26, 'group' : 'g1',
- \ 'priority' : 10},
- \ {'id' : 6, 'name' : 'sign1', 'lnum' : 27, 'group' : 'g2',
- \ 'priority' : 10},]
-
- " Test for :sign unplace {id} file={fname}
- call Place_signs_for_test()
- sign unplace 3 file=Xsign1
- sign unplace 6 file=Xsign2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 3 || val.group != ''}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 6 || val.group != ''}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id} group={group} file={fname}
- call Place_signs_for_test()
- sign unplace 4 group=g1 file=Xsign1
- sign unplace 5 group=g2 file=Xsign2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4 || val.group != 'g1'}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 5 || val.group != 'g2'}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id} group=* file={fname}
- call Place_signs_for_test()
- sign unplace 3 group=* file=Xsign1
- sign unplace 6 group=* file=Xsign2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 3}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 6}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * file={fname}
- call Place_signs_for_test()
- sign unplace * file=Xsign1
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.group != ''}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(signs2, sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * group={group} file={fname}
- call Place_signs_for_test()
- sign unplace * group=g1 file=Xsign1
- sign unplace * group=g2 file=Xsign2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.group != 'g1'}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.group != 'g2'}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * group=* file={fname}
- call Place_signs_for_test()
- sign unplace * group=* file=Xsign2
- call assert_equal(signs1, sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal([], sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id} buffer={nr}
- call Place_signs_for_test()
- exe 'sign unplace 3 buffer=' . bnum1
- exe 'sign unplace 6 buffer=' . bnum2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 3 || val.group != ''}),
- \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 6 || val.group != ''}),
- \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id} group={group} buffer={nr}
- call Place_signs_for_test()
- exe 'sign unplace 4 group=g1 buffer=' . bnum1
- exe 'sign unplace 5 group=g2 buffer=' . bnum2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4 || val.group != 'g1'}),
- \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 5 || val.group != 'g2'}),
- \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id} group=* buffer={nr}
- call Place_signs_for_test()
- exe 'sign unplace 3 group=* buffer=' . bnum1
- exe 'sign unplace 6 group=* buffer=' . bnum2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 3}),
- \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 6}),
- \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * buffer={nr}
- call Place_signs_for_test()
- exe 'sign unplace * buffer=' . bnum1
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.group != ''}),
- \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
- call assert_equal(signs2, sign_getplaced(bnum2, {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * group={group} buffer={nr}
- call Place_signs_for_test()
- exe 'sign unplace * group=g1 buffer=' . bnum1
- exe 'sign unplace * group=g2 buffer=' . bnum2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.group != 'g1'}),
- \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.group != 'g2'}),
- \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * group=* buffer={nr}
- call Place_signs_for_test()
- exe 'sign unplace * group=* buffer=' . bnum2
- call assert_equal(signs1, sign_getplaced(bnum1, {'group' : '*'})[0].signs)
- call assert_equal([], sign_getplaced(bnum2, {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id}
- call Place_signs_for_test()
- sign unplace 4
- sign unplace 6
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4 || val.group != ''}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 6 || val.group != ''}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id} group={group}
- call Place_signs_for_test()
- sign unplace 4 group=g1
- sign unplace 6 group=g2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4 || val.group != 'g1'}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 6 || val.group != 'g2'}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace {id} group=*
- call Place_signs_for_test()
- sign unplace 3 group=*
- sign unplace 5 group=*
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 3}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.id != 5}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace *
- call Place_signs_for_test()
- sign unplace *
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.group != ''}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.group != ''}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * group={group}
- call Place_signs_for_test()
- sign unplace * group=g1
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.group != 'g1'}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(
- \ filter(copy(signs2),
- \ {idx, val -> val.group != 'g1'}),
- \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Test for :sign unplace * group=*
- call Place_signs_for_test()
- sign unplace * group=*
- call assert_equal([], sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal([], sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Negative test cases
- call Place_signs_for_test()
- sign unplace 3 group=xy file=Xsign1
- sign unplace * group=xy file=Xsign1
- silent! sign unplace * group=* file=FileNotPresent
- call assert_equal(signs1, sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- call assert_equal(signs2, sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
-
- " Tests for removing sign at the current cursor position
-
- " Test for ':sign unplace'
- let signs1 = [{'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g2',
- \ 'priority' : 10},
- \ {'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g1',
- \ 'priority' : 10},
- \ {'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : '',
- \ 'priority' : 10},
- \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g2',
- \ 'priority' : 10},
- \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g1',
- \ 'priority' : 10},
- \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : '',
- \ 'priority' : 10},]
- exe bufwinnr('Xsign1') . 'wincmd w'
- call cursor(13, 1)
-
- " Should remove only one sign in the global group
- call Place_signs_at_line_for_test()
- sign unplace
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4 || val.group != ''}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- " Should remove the second sign in the global group
- sign unplace
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.group != ''}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
-
- " Test for ':sign unplace group={group}'
- call Place_signs_at_line_for_test()
- " Should remove only one sign in group g1
- sign unplace group=g1
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4 || val.group != 'g1'}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- sign unplace group=g2
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4 || val.group == ''}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
-
- " Test for ':sign unplace group=*'
- call Place_signs_at_line_for_test()
- sign unplace group=*
- sign unplace group=*
- sign unplace group=*
- call assert_equal(
- \ filter(copy(signs1),
- \ {idx, val -> val.id != 4}),
- \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
- sign unplace group=*
- sign unplace group=*
- sign unplace group=*
- call assert_equal([], sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
-
- call sign_unplace('*')
- call sign_undefine()
- enew | only
- call delete("Xsign1")
- call delete("Xsign2")
-endfunc
-
-" Tests for auto-generating the sign identifier.
-func Test_aaa_sign_id_autogen()
- enew | only
- call sign_unplace('*')
- call sign_undefine()
-
- let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
- call assert_equal(0, sign_define("sign1", attr))
-
- call writefile(repeat(["Sun is shining"], 30), "Xsign")
- edit Xsign
-
- call assert_equal(1, sign_place(0, '', 'sign1', 'Xsign',
- \ {'lnum' : 10}))
- call assert_equal(2, sign_place(2, '', 'sign1', 'Xsign',
- \ {'lnum' : 12}))
- call assert_equal(3, sign_place(0, '', 'sign1', 'Xsign',
- \ {'lnum' : 14}))
- call sign_unplace('', {'buffer' : 'Xsign', 'id' : 2})
- call assert_equal(4, sign_place(0, '', 'sign1', 'Xsign',
- \ {'lnum' : 12}))
-
- call assert_equal(1, sign_place(0, 'g1', 'sign1', 'Xsign',
- \ {'lnum' : 11}))
- " Check for the next generated sign id in this group
- call assert_equal(2, sign_place(0, 'g1', 'sign1', 'Xsign',
- \ {'lnum' : 12}))
- call assert_equal(0, sign_unplace('g1', {'id' : 1}))
- call assert_equal(10,
- \ sign_getplaced('Xsign', {'id' : 1})[0].signs[0].lnum)
-
- call delete("Xsign")
- call sign_unplace('*')
- call sign_undefine()
- enew | only
-endfunc
-
-" Test for sign priority
-func Test_sign_priority()
- enew | only
- call sign_unplace('*')
- call sign_undefine()
-
- let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Search'}
- call sign_define("sign1", attr)
- call sign_define("sign2", attr)
- call sign_define("sign3", attr)
-
- " Place three signs with different priority in the same line
- call writefile(repeat(["Sun is shining"], 30), "Xsign")
- edit Xsign
-
- call sign_place(1, 'g1', 'sign1', 'Xsign',
- \ {'lnum' : 11, 'priority' : 50})
- call sign_place(2, 'g2', 'sign2', 'Xsign',
- \ {'lnum' : 11, 'priority' : 100})
- call sign_place(3, '', 'sign3', 'Xsign', {'lnum' : 11})
- let s = sign_getplaced('Xsign', {'group' : '*'})
- call assert_equal([
- \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11, 'group' : 'g2',
- \ 'priority' : 100},
- \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : 'g1',
- \ 'priority' : 50},
- \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11, 'group' : '',
- \ '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:')
- call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
- \ {'priority' : []})", 'E745:')
- call sign_unplace('*')
-
- " Tests for the :sign place command with priority
- sign place 5 line=10 name=sign1 priority=30 file=Xsign
- sign place 5 group=g1 line=10 name=sign1 priority=20 file=Xsign
- sign place 5 group=g2 line=10 name=sign1 priority=25 file=Xsign
- let a = execute('sign place group=*')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 name=sign1 priority=30\n" .
- \ " line=10 id=5 group=g2 name=sign1 priority=25\n" .
- \ " line=10 id=5 group=g1 name=sign1 priority=20\n", a)
-
- " Test for :sign place group={group}
- let a = execute('sign place group=g1')
- call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
- \ " line=10 id=5 group=g1 name=sign1 priority=20\n", a)
-
- call sign_unplace('*')
- call sign_undefine()
- enew | only
- call delete("Xsign")
-endfunc
-
-" Tests for memory allocation failures in sign functions
-func Test_sign_memfailures()
- CheckFunction test_alloc_fail
- call writefile(repeat(["Sun is shining"], 30), "Xsign")
- edit Xsign
-
- call test_alloc_fail(GetAllocId('sign_getdefined'), 0, 0)
- call assert_fails('call sign_getdefined("sign1")', 'E342:')
- call test_alloc_fail(GetAllocId('sign_getplaced'), 0, 0)
- call assert_fails('call sign_getplaced("Xsign")', 'E342:')
- call test_alloc_fail(GetAllocId('sign_define_by_name'), 0, 0)
- let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
- call assert_fails('call sign_define("sign1", attr)', 'E342:')
-
- let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
- call sign_define("sign1", attr)
- call test_alloc_fail(GetAllocId('sign_getlist'), 0, 0)
- call assert_fails('call sign_getdefined("sign1")', 'E342:')
-
- call sign_place(3, 'g1', 'sign1', 'Xsign', {'lnum' : 10})
- call test_alloc_fail(GetAllocId('sign_getplaced_dict'), 0, 0)
- call assert_fails('call sign_getplaced("Xsign")', 'E342:')
- call test_alloc_fail(GetAllocId('sign_getplaced_list'), 0, 0)
- call assert_fails('call sign_getplaced("Xsign")', 'E342:')
-
- call test_alloc_fail(GetAllocId('insert_sign'), 0, 0)
- call assert_fails('call sign_place(4, "g1", "sign1", "Xsign", {"lnum" : 11})',
- \ 'E342:')
-
- call test_alloc_fail(GetAllocId('sign_getinfo'), 0, 0)
- call assert_fails('call getbufinfo()', 'E342:')
- call sign_place(4, 'g1', 'sign1', 'Xsign', {'lnum' : 11})
- call test_alloc_fail(GetAllocId('sign_getinfo'), 0, 0)
- call assert_fails('let binfo=getbufinfo("Xsign")', 'E342:')
- call assert_equal([{'lnum': 11, 'id': 4, 'name': 'sign1',
- \ 'priority': 10, 'group': 'g1'}], binfo[0].signs)
-
- call sign_unplace('*')
- call sign_undefine()
- enew | only
- call delete("Xsign")
-endfunc
-
-" Test for auto-adjusting the line number of a placed sign.
-func Test_sign_lnum_adjust()
- enew! | only!
-
- sign define sign1 text=#> linehl=Comment
- call setline(1, ['A', 'B', 'C', 'D', 'E'])
- exe 'sign place 5 line=3 name=sign1 buffer=' . bufnr('')
- let l = sign_getplaced(bufnr(''))
- call assert_equal(3, l[0].signs[0].lnum)
-
- " Add some lines before the sign and check the sign line number
- call append(2, ['BA', 'BB', 'BC'])
- let l = sign_getplaced(bufnr(''))
- call assert_equal(6, l[0].signs[0].lnum)
-
- " Delete some lines before the sign and check the sign line number
- call deletebufline('%', 1, 2)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(4, l[0].signs[0].lnum)
-
- " Insert some lines after the sign and check the sign line number
- call append(5, ['DA', 'DB'])
- let l = sign_getplaced(bufnr(''))
- call assert_equal(4, l[0].signs[0].lnum)
-
- " Delete some lines after the sign and check the sign line number
- call deletebufline('', 6, 7)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(4, l[0].signs[0].lnum)
-
- " Break the undo. Otherwise the undo operation below will undo all the
- " changes made by this function.
- let &undolevels=&undolevels
-
- " Nvim: make sign adjustment when deleting lines match Vim
- set signcolumn=yes:1
-
- " Delete the line with the sign
- call deletebufline('', 4)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(4, l[0].signs[0].lnum)
-
- " Undo the delete operation
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
-
- " Break the undo
- let &undolevels=&undolevels
-
- " Delete few lines at the end of the buffer including the line with the sign
- " Sign line number should not change (as it is placed outside of the buffer)
- call deletebufline('', 3, 6)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
-
- " Undo the delete operation. Sign should be restored to the previous line
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
-
- set signcolumn&
-
- sign unplace * group=*
- sign undefine sign1
- enew!
-endfunc
-
-" Test for changing the type of a placed sign
-func Test_sign_change_type()
- enew! | only!
-
- sign define sign1 text=#> linehl=Comment
- sign define sign2 text=@@ linehl=Comment
-
- call setline(1, ['A', 'B', 'C', 'D'])
- exe 'sign place 4 line=3 name=sign1 buffer=' . bufnr('')
- let l = sign_getplaced(bufnr(''))
- call assert_equal('sign1', l[0].signs[0].name)
- exe 'sign place 4 name=sign2 buffer=' . bufnr('')
- let l = sign_getplaced(bufnr(''))
- call assert_equal('sign2', l[0].signs[0].name)
- call sign_place(4, '', 'sign1', '')
- let l = sign_getplaced(bufnr(''))
- call assert_equal('sign1', l[0].signs[0].name)
-
- exe 'sign place 4 group=g1 line=4 name=sign1 buffer=' . bufnr('')
- let l = sign_getplaced(bufnr(''), {'group' : 'g1'})
- call assert_equal('sign1', l[0].signs[0].name)
- exe 'sign place 4 group=g1 name=sign2 buffer=' . bufnr('')
- let l = sign_getplaced(bufnr(''), {'group' : 'g1'})
- call assert_equal('sign2', l[0].signs[0].name)
- call sign_place(4, 'g1', 'sign1', '')
- let l = sign_getplaced(bufnr(''), {'group' : 'g1'})
- call assert_equal('sign1', l[0].signs[0].name)
-
- sign unplace * group=*
- sign undefine sign1
- sign undefine sign2
- enew!
-endfunc
-
-" Test for the sign_jump() function
-func Test_sign_jump_func()
- enew! | only!
-
- sign define sign1 text=#> linehl=Comment
-
- edit foo
- set buftype=nofile
- call setline(1, ['A', 'B', 'C', 'D', 'E'])
- call sign_place(5, '', 'sign1', '', {'lnum' : 2})
- call sign_place(5, 'g1', 'sign1', '', {'lnum' : 3})
- call sign_place(6, '', 'sign1', '', {'lnum' : 4})
- call sign_place(6, 'g1', 'sign1', '', {'lnum' : 5})
- split bar
- set buftype=nofile
- call setline(1, ['P', 'Q', 'R', 'S', 'T'])
- call sign_place(5, '', 'sign1', '', {'lnum' : 2})
- call sign_place(5, 'g1', 'sign1', '', {'lnum' : 3})
- call sign_place(6, '', 'sign1', '', {'lnum' : 4})
- call sign_place(6, 'g1', 'sign1', '', {'lnum' : 5})
-
- let r = sign_jump(5, '', 'foo')
- call assert_equal(2, r)
- call assert_equal(2, line('.'))
- let r = 6->sign_jump('g1', 'foo')
- call assert_equal(5, r)
- call assert_equal(5, line('.'))
- let r = sign_jump(5, '', 'bar')
- call assert_equal(2, r)
- call assert_equal(2, line('.'))
-
- " Error cases
- call assert_fails("call sign_jump(99, '', 'bar')", 'E157:')
- call assert_fails("call sign_jump(0, '', 'foo')", 'E474:')
- call assert_fails("call sign_jump(5, 'g5', 'foo')", 'E157:')
- call assert_fails('call sign_jump([], "", "foo")', 'E745:')
- call assert_fails('call sign_jump(2, [], "foo")', 'E730:')
- call assert_fails('call sign_jump(2, "", {})', 'E731:')
- call assert_fails('call sign_jump(2, "", "baz")', 'E158:')
-
- sign unplace * group=*
- sign undefine sign1
- enew! | only!
-endfunc
-
-" Test for correct cursor position after the sign column appears or disappears.
-func Test_sign_cursor_position()
- CheckRunVimInTerminal
-
- 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
-
-" Return the 'len' characters in screen starting from (row,col)
-func s:ScreenLine(row, col, len)
- let s = ''
- for i in range(a:len)
- let s .= nr2char(screenchar(a:row, a:col + i))
- endfor
- return s
-endfunc
-
-" Test for 'signcolumn' set to 'number'.
-func Test_sign_numcol()
- new
- call append(0, "01234")
- " With 'signcolumn' set to 'number', make sure sign is displayed in the
- " number column and line number is not displayed.
- set numberwidth=2
- set number
- set signcolumn=number
- sign define sign1 text==>
- sign define sign2 text=ï¼¶
- sign place 10 line=1 name=sign1
- redraw!
- call assert_equal("=> 01234", s:ScreenLine(1, 1, 8))
-
- " With 'signcolumn' set to 'number', when there is no sign, make sure line
- " number is displayed in the number column
- sign unplace 10
- redraw!
- call assert_equal("1 01234", s:ScreenLine(1, 1, 7))
-
- " Disable number column. Check whether sign is displayed in the sign column
- set numberwidth=4
- set nonumber
- sign place 10 line=1 name=sign1
- redraw!
- call assert_equal("=>01234", s:ScreenLine(1, 1, 7))
-
- " Enable number column. Check whether sign is displayed in the number column
- set number
- redraw!
- call assert_equal(" => 01234", s:ScreenLine(1, 1, 9))
-
- " Disable sign column. Make sure line number is displayed
- set signcolumn=no
- redraw!
- call assert_equal(" 1 01234", s:ScreenLine(1, 1, 9))
-
- " Enable auto sign column. Make sure both sign and line number are displayed
- set signcolumn=auto
- redraw!
- call assert_equal("=> 1 01234", s:ScreenLine(1, 1, 11))
-
- " Test displaying signs in the number column with width 1
- call sign_unplace('*')
- call append(1, "abcde")
- call append(2, "01234")
- " Enable number column with width 1
- set number numberwidth=1 signcolumn=auto
- redraw!
- call assert_equal("3 01234", s:ScreenLine(3, 1, 7))
- " Place a sign and make sure number column width remains the same
- sign place 20 line=2 name=sign1
- redraw!
- call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9))
- call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9))
- " Set 'signcolumn' to 'number', make sure the number column width increases
- set signcolumn=number
- redraw!
- call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
- call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
- " Set 'signcolumn' to 'auto', make sure the number column width is 1.
- set signcolumn=auto
- redraw!
- call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9))
- call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9))
- " Set 'signcolumn' to 'number', make sure the number column width is 2.
- set signcolumn=number
- redraw!
- call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
- call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
- " Disable 'number' column
- set nonumber
- redraw!
- call assert_equal("=>abcde", s:ScreenLine(2, 1, 7))
- call assert_equal(" 01234", s:ScreenLine(3, 1, 7))
- " Enable 'number' column
- set number
- redraw!
- call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
- call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
- " Remove the sign and make sure the width of the number column is 1.
- call sign_unplace('', {'id' : 20})
- redraw!
- call assert_equal("3 01234", s:ScreenLine(3, 1, 7))
- " When the first sign is placed with 'signcolumn' set to number, verify that
- " the number column width increases
- sign place 30 line=1 name=sign1
- redraw!
- call assert_equal("=> 01234", s:ScreenLine(1, 1, 8))
- call assert_equal(" 2 abcde", s:ScreenLine(2, 1, 8))
- " Add sign with multi-byte text
- set numberwidth=4
- sign place 40 line=2 name=sign2
- redraw!
- call assert_equal(" => 01234", s:ScreenLine(1, 1, 9))
- call assert_equal(" ï¼¶ abcde", s:ScreenLine(2, 1, 9))
-
- sign unplace * group=*
- sign undefine sign1
- set signcolumn&
- set number&
- enew! | close
-endfunc
-
-" Test for managing multiple signs using the sign functions
-func Test_sign_funcs_multi()
- call writefile(repeat(["Sun is shining"], 30), "Xsign")
- edit Xsign
- let bnum = bufnr('')
-
- " Define multiple signs at once
- call assert_equal([0, 0, 0, 0], sign_define([
- \ {'name' : 'sign1', 'text' : '=>', 'linehl' : 'Search',
- \ 'texthl' : 'Search'},
- \ {'name' : 'sign2', 'text' : '=>', 'linehl' : 'Search',
- \ 'texthl' : 'Search'},
- \ {'name' : 'sign3', 'text' : '=>', 'linehl' : 'Search',
- \ 'texthl' : 'Search'},
- \ {'name' : 'sign4', 'text' : '=>', 'linehl' : 'Search',
- \ 'texthl' : 'Search'}]))
-
- " Negative cases for sign_define()
- call assert_equal([], sign_define([]))
- call assert_equal([-1], sign_define([{}]))
- call assert_fails('call sign_define([6])', 'E715:')
- call assert_fails('call sign_define(["abc"])', 'E715:')
- call assert_fails('call sign_define([[]])', 'E715:')
-
- " Place multiple signs at once with specific sign identifier
- let l = sign_placelist([{'id' : 1, 'group' : 'g1', 'name' : 'sign1',
- \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 50},
- \ {'id' : 2, 'group' : 'g2', 'name' : 'sign2',
- \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 100},
- \ {'id' : 3, 'group' : '', 'name' : 'sign3',
- \ 'buffer' : 'Xsign', 'lnum' : 11}])
- call assert_equal([1, 2, 3], l)
- let s = sign_getplaced('Xsign', {'group' : '*'})
- call assert_equal([
- \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11,
- \ 'group' : 'g2', 'priority' : 100},
- \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
- \ 'group' : 'g1', 'priority' : 50},
- \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11,
- \ 'group' : '', 'priority' : 10}], s[0].signs)
-
- call sign_unplace('*')
-
- " Place multiple signs at once with auto-generated sign identifier
- call assert_equal([1, 1, 5], sign_placelist([
- \ {'group' : 'g1', 'name' : 'sign1',
- \ 'buffer' : 'Xsign', 'lnum' : 11},
- \ {'group' : 'g2', 'name' : 'sign2',
- \ 'buffer' : 'Xsign', 'lnum' : 11},
- \ {'group' : '', 'name' : 'sign3',
- \ 'buffer' : 'Xsign', 'lnum' : 11}]))
- let s = sign_getplaced('Xsign', {'group' : '*'})
- call assert_equal([
- \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11,
- \ 'group' : '', 'priority' : 10},
- \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11,
- \ 'group' : 'g2', 'priority' : 10},
- \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
- \ 'group' : 'g1', 'priority' : 10}], s[0].signs)
-
- " Change an existing sign without specifying the group
- call assert_equal([5], [{'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist())
- let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''})
- call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11,
- \ 'group' : '', 'priority' : 10}], s[0].signs)
-
- " Place a sign using '.' as the line number
- call cursor(23, 1)
- call assert_equal([7], sign_placelist([
- \ {'id' : 7, 'name' : 'sign1', 'buffer' : '%', 'lnum' : '.'}]))
- let s = sign_getplaced('%', {'lnum' : '.'})
- call assert_equal([{'id' : 7, 'name' : 'sign1', 'lnum' : 23,
- \ 'group' : '', 'priority' : 10}], s[0].signs)
-
- " Place sign without a sign name
- call assert_equal([-1], sign_placelist([{'id' : 10, 'buffer' : 'Xsign',
- \ 'lnum' : 12, 'group' : ''}]))
-
- " Place sign without a buffer
- call assert_equal([-1], sign_placelist([{'id' : 10, 'name' : 'sign1',
- \ 'lnum' : 12, 'group' : ''}]))
-
- " Invalid arguments
- call assert_equal([], sign_placelist([]))
- call assert_fails('call sign_placelist({})', "E714:")
- call assert_fails('call sign_placelist([[]])', "E715:")
- call assert_fails('call sign_placelist(["abc"])', "E715:")
- call assert_fails('call sign_placelist([100])', "E715:")
-
- " Unplace multiple signs
- call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5},
- \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}]))
-
- " Invalid arguments
- call assert_equal([], []->sign_unplacelist())
- call assert_fails('call sign_unplacelist({})', "E714:")
- call assert_fails('call sign_unplacelist([[]])', "E715:")
- call assert_fails('call sign_unplacelist(["abc"])', "E715:")
- call assert_fails('call sign_unplacelist([100])', "E715:")
- call assert_fails("call sign_unplacelist([{'id' : -1}])", 'E474')
-
- call assert_equal([0, 0, 0, 0],
- \ sign_undefine(['sign1', 'sign2', 'sign3', 'sign4']))
- call assert_equal([], sign_getdefined())
-
- " Invalid arguments
- call assert_equal([], sign_undefine([]))
- call assert_fails('call sign_undefine([[]])', 'E730:')
- call assert_fails('call sign_undefine([{}])', 'E731:')
- call assert_fails('call sign_undefine(["1abc2"])', 'E155:')
-
- call sign_unplace('*')
- call sign_undefine()
- enew!
- call delete("Xsign")
-endfunc
diff --git a/src/nvim/testdir/test_sleep.vim b/src/nvim/testdir/test_sleep.vim
deleted file mode 100644
index a428f380b0..0000000000
--- a/src/nvim/testdir/test_sleep.vim
+++ /dev/null
@@ -1,27 +0,0 @@
-" Test for sleep and sleep! commands
-
-func! s:get_time_ms()
- let timestr = reltimestr(reltime())
- let dotidx = stridx(timestr, '.')
- let sec = str2nr(timestr[:dotidx])
- let msec = str2nr(timestr[dotidx + 1:])
- return (sec * 1000) + (msec / 1000)
-endfunc
-
-func! s:assert_takes_longer(cmd, time_ms)
- let start = s:get_time_ms()
- execute a:cmd
- let end = s:get_time_ms()
- call assert_true(end - start >=# a:time_ms)
-endfun
-
-func! Test_sleep_bang()
- call s:assert_takes_longer('sleep 50m', 50)
- call s:assert_takes_longer('sleep! 50m', 50)
- call s:assert_takes_longer('sl 50m', 50)
- call s:assert_takes_longer('sl! 50m', 50)
- call s:assert_takes_longer('1sleep', 1000)
- call s:assert_takes_longer('normal 1gs', 1000)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_smartindent.vim b/src/nvim/testdir/test_smartindent.vim
deleted file mode 100644
index e2d028e828..0000000000
--- a/src/nvim/testdir/test_smartindent.vim
+++ /dev/null
@@ -1,136 +0,0 @@
-" Tests for smartindent
-
-" Tests for not doing smart indenting when it isn't set.
-func Test_nosmartindent()
- new
- call append(0, [" some test text",
- \ " test text",
- \ "test text",
- \ " test text"])
- set nocindent nosmartindent autoindent
- exe "normal! gg/some\<CR>"
- exe "normal! 2cc#test\<Esc>"
- call assert_equal(" #test", getline(1))
- enew! | close
-endfunc
-
-func MyIndent()
-endfunc
-
-" When 'indentexpr' is set, setting 'si' has no effect.
-func Test_smartindent_has_no_effect()
- new
- exe "normal! i\<Tab>one\<Esc>"
- setlocal noautoindent smartindent indentexpr=
- exe "normal! Gotwo\<Esc>"
- call assert_equal("\ttwo", getline("$"))
-
- set indentexpr=MyIndent
- exe "normal! Gothree\<Esc>"
- call assert_equal("three", getline("$"))
-
- delfunction! MyIndent
- bwipe!
-endfunc
-
-" Test for inserting '{' and '} with smartindent
-func Test_smartindent_braces()
- new
- setlocal smartindent shiftwidth=4
- call setline(1, [' if (a)', "\tif (b)", "\t return 1"])
- normal 2ggO{
- normal 3ggA {
- normal 4ggo}
- normal o}
- normal 4ggO#define FOO 1
- call assert_equal([
- \ ' if (a)',
- \ ' {',
- \ "\tif (b) {",
- \ '#define FOO 1',
- \ "\t return 1",
- \ "\t}",
- \ ' }'
- \ ], getline(1, '$'))
- close!
-endfunc
-
-" Test for adding a new line before and after comments with smartindent
-func Test_si_add_line_around_comment()
- new
- setlocal smartindent shiftwidth=4
- call setline(1, [' A', '# comment1', '# comment2'])
- exe "normal GoC\<Esc>2GOB"
- call assert_equal([' A', ' B', '# comment1', '# comment2', ' C'],
- \ getline(1, '$'))
- close!
-endfunc
-
-" After a C style comment, indent for a following line should line up with the
-" line containing the start of the comment.
-func Test_si_indent_after_c_comment()
- new
- setlocal smartindent shiftwidth=4 fo+=ro
- exe "normal i\<C-t>/*\ncomment\n/\n#define FOOBAR\n75\<Esc>ggOabc"
- normal 3jOcont
- call assert_equal([' abc', ' /*', ' * comment', ' * cont',
- \ ' */', '#define FOOBAR', ' 75'], getline(1, '$'))
- close!
-endfunc
-
-" Test for indenting a statement after a if condition split across lines
-func Test_si_if_cond_split_across_lines()
- new
- setlocal smartindent shiftwidth=4
- exe "normal i\<C-t>if (cond1 &&\n\<C-t>cond2) {\ni = 10;\n}"
- call assert_equal([' if (cond1 &&', "\t cond2) {", "\ti = 10;",
- \ ' }'], getline(1, '$'))
- close!
-endfunc
-
-" Test for inserting lines before and after a one line comment
-func Test_si_one_line_comment()
- new
- setlocal smartindent shiftwidth=4
- exe "normal i\<C-t>abc;\n\<C-t>/* comment */"
- normal oi = 10;
- normal kOj = 1;
- call assert_equal([' abc;', "\tj = 1;", "\t/* comment */", "\ti = 10;"],
- \ getline(1, '$'))
- close!
-endfunc
-
-" Test for smartindent with a comment continued across multiple lines
-func Test_si_comment_line_continuation()
- new
- setlocal smartindent shiftwidth=4
- call setline(1, ['# com1', '# com2 \', ' contd', '# com3', ' xyz'])
- normal ggOabc
- call assert_equal([' abc', '# com1', '# com2 \', ' contd', '# com3',
- \ ' xyz'], getline(1, '$'))
- close!
-endfunc
-
-func Test_si_after_completion()
- new
- setlocal ai smartindent indentexpr=
- call setline(1, 'foo foot')
- call feedkeys("o f\<C-X>\<C-N>#", 'tx')
- call assert_equal(' foo#', getline(2))
-
- call setline(2, '')
- call feedkeys("1Go f\<C-X>\<C-N>}", 'tx')
- call assert_equal(' foo}', getline(2))
-
- bwipe!
-endfunc
-
-func Test_no_si_after_completion()
- new
- call setline(1, 'foo foot')
- call feedkeys("o f\<C-X>\<C-N>#", 'tx')
- call assert_equal(' foo#', getline(2))
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim
deleted file mode 100644
index 534393b724..0000000000
--- a/src/nvim/testdir/test_sort.vim
+++ /dev/null
@@ -1,1524 +0,0 @@
-" Tests for the "sort()" function and for the ":sort" command.
-
-source check.vim
-
-func Compare1(a, b) abort
- call sort(range(3), 'Compare2')
- return a:a - a:b
-endfunc
-
-func Compare2(a, b) abort
- return a:a - a:b
-endfunc
-
-func Test_sort_strings()
- CheckNotMSWindows " FIXME: Why does this fail with MSVC?
- " numbers compared as strings
- call assert_equal([1, 2, 3], sort([3, 2, 1]))
- call assert_equal([13, 28, 3], sort([3, 28, 13]))
-
- call assert_equal(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
- \ sort(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'œ', 'Œ']))
-
- call assert_equal(['A', 'a', 'o', 'O', 'p', 'P', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
- \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'i'))
-
- " This does not appear to work correctly on Mac.
- if !has('mac')
- if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
- " with Canadian English capitals come before lower case.
- " 'Å’' is omitted because it can sort before or after 'Å“'
- call assert_equal(['A', 'a', 'Ä', 'ä', 'O', 'o', 'Ô', 'ô', 'œ', 'P', 'p'],
- \ sort(['A', 'a', 'o', 'O', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
- elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
- " With the following locales, the accentuated letters are ordered
- " similarly to the non-accentuated letters...
- call assert_equal(['a', 'A', 'ä', 'Ä', 'o', 'O', 'ô', 'Ô', 'œ', 'Œ', 'p', 'P'],
- \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
- elseif v:collate =~? '^sv.*utf-\?8$'
- " ... whereas with a Swedish locale, the accentuated letters are ordered
- " after Z.
- call assert_equal(['a', 'A', 'o', 'O', 'p', 'P', 'ä', 'Ä', 'œ', 'œ', 'ô', 'Ô'],
- \ sort(['A', 'a', 'o', 'O', 'œ', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
- endif
- endif
-endfunc
-
-func Test_sort_null_string()
- " null strings are sorted as empty strings.
- call assert_equal(['', 'a', 'b'], sort(['b', v:_null_string, 'a']))
-endfunc
-
-func Test_sort_numeric()
- call assert_equal([1, 2, 3], sort([3, 2, 1], 'n'))
- call assert_equal([3, 13, 28], sort([13, 28, 3], 'n'))
- " strings are not sorted
- call assert_equal(['13', '28', '3'], sort(['13', '28', '3'], 'n'))
-endfunc
-
-func Test_sort_numbers()
- call assert_equal([3, 13, 28], sort([13, 28, 3], 'N'))
- call assert_equal(['3', '13', '28'], sort(['13', '28', '3'], 'N'))
- call assert_equal([3997, 4996], sort([4996, 3997], 'Compare1'))
-endfunc
-
-func Test_sort_float()
- CheckFeature float
- call assert_equal([0.28, 3, 13.5], sort([13.5, 0.28, 3], 'f'))
-endfunc
-
-func Test_sort_nested()
- " test ability to call sort() from a compare function
- call assert_equal([1, 3, 5], sort([3, 1, 5], 'Compare1'))
-endfunc
-
-func Test_sort_default()
- CheckFeature float
-
- " docs say omitted, empty or zero argument sorts on string representation.
- call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"]))
- call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], ''))
- call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 0))
- call assert_equal(['2', 'A', 'a', 'AA', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 1))
- call assert_fails('call sort([3.3, 1, "2"], 3)', "E474:")
-endfunc
-
-" Tests for the ":sort" command.
-func Test_sort_cmd()
- let tests = [
- \ {
- \ 'name' : 'Alphabetical sort',
- \ 'cmd' : '%sort',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ],
- \ 'expected' : [
- \ ' 123b',
- \ 'a',
- \ 'a122',
- \ 'a123',
- \ 'a321',
- \ 'ab',
- \ 'abc',
- \ 'b123',
- \ 'b321',
- \ 'b321',
- \ 'b321b',
- \ 'b322b',
- \ 'c123d',
- \ 'c321d'
- \ ]
- \ },
- \ {
- \ 'name' : 'Numeric sort',
- \ 'cmd' : '%sort n',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'a',
- \ 'x-22',
- \ 'b321',
- \ 'b123',
- \ '',
- \ 'c123d',
- \ '-24',
- \ ' 123b',
- \ 'c321d',
- \ '0',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '-24',
- \ 'x-22',
- \ '0',
- \ 'a122',
- \ 'a123',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b321',
- \ 'b321b',
- \ 'b322b'
- \ ]
- \ },
- \ {
- \ 'name' : 'Hexadecimal sort',
- \ 'cmd' : '%sort x',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ],
- \ 'expected' : [
- \ 'a',
- \ 'ab',
- \ 'abc',
- \ ' 123b',
- \ 'a122',
- \ 'a123',
- \ 'a321',
- \ 'b123',
- \ 'b321',
- \ 'b321',
- \ 'b321b',
- \ 'b322b',
- \ 'c123d',
- \ 'c321d'
- \ ]
- \ },
- \ {
- \ 'name' : 'Alphabetical unique sort',
- \ 'cmd' : '%sort u',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ],
- \ 'expected' : [
- \ ' 123b',
- \ 'a',
- \ 'a122',
- \ 'a123',
- \ 'a321',
- \ 'ab',
- \ 'abc',
- \ 'b123',
- \ 'b321',
- \ 'b321b',
- \ 'b322b',
- \ 'c123d',
- \ 'c321d'
- \ ]
- \ },
- \ {
- \ 'name' : 'Alphabetical reverse sort',
- \ 'cmd' : '%sort!',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ],
- \ 'expected' : [
- \ 'c321d',
- \ 'c123d',
- \ 'b322b',
- \ 'b321b',
- \ 'b321',
- \ 'b321',
- \ 'b123',
- \ 'abc',
- \ 'ab',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'a',
- \ ' 123b',
- \ ]
- \ },
- \ {
- \ 'name' : 'Numeric reverse sort',
- \ 'cmd' : '%sort! n',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ],
- \ 'expected' : [
- \ 'b322b',
- \ 'b321b',
- \ 'b321',
- \ 'c321d',
- \ 'b321',
- \ 'a321',
- \ ' 123b',
- \ 'c123d',
- \ 'b123',
- \ 'a123',
- \ 'a122',
- \ 'a',
- \ 'ab',
- \ 'abc'
- \ ]
- \ },
- \ {
- \ 'name' : 'Unique reverse sort',
- \ 'cmd' : 'sort! u',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ],
- \ 'expected' : [
- \ 'c321d',
- \ 'c123d',
- \ 'b322b',
- \ 'b321b',
- \ 'b321',
- \ 'b123',
- \ 'abc',
- \ 'ab',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'a',
- \ ' 123b',
- \ ]
- \ },
- \ {
- \ 'name' : 'Octal sort',
- \ 'cmd' : 'sort o',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a122',
- \ 'a123',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b321',
- \ 'b321b',
- \ 'b322b'
- \ ]
- \ },
- \ {
- \ 'name' : 'Reverse hexadecimal sort',
- \ 'cmd' : 'sort! x',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'c321d',
- \ 'c123d',
- \ 'b322b',
- \ 'b321b',
- \ 'b321',
- \ 'b321',
- \ 'b123',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ ' 123b',
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ ''
- \ ]
- \ },
- \ {
- \ 'name' : 'Alpha (skip first character) sort',
- \ 'cmd' : 'sort/./',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'a',
- \ '',
- \ '',
- \ 'a122',
- \ 'a123',
- \ 'b123',
- \ ' 123b',
- \ 'c123d',
- \ 'a321',
- \ 'b321',
- \ 'b321',
- \ 'b321b',
- \ 'c321d',
- \ 'b322b',
- \ 'ab',
- \ 'abc'
- \ ]
- \ },
- \ {
- \ 'name' : 'Alpha (skip first 2 characters) sort',
- \ 'cmd' : 'sort/../',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a321',
- \ 'b321',
- \ 'b321',
- \ 'b321b',
- \ 'c321d',
- \ 'a122',
- \ 'b322b',
- \ 'a123',
- \ 'b123',
- \ ' 123b',
- \ 'c123d',
- \ 'abc'
- \ ]
- \ },
- \ {
- \ 'name' : 'alpha, unique, skip first 2 characters',
- \ 'cmd' : 'sort/../u',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'ab',
- \ 'a',
- \ '',
- \ 'a321',
- \ 'b321',
- \ 'b321b',
- \ 'c321d',
- \ 'a122',
- \ 'b322b',
- \ 'a123',
- \ 'b123',
- \ ' 123b',
- \ 'c123d',
- \ 'abc'
- \ ]
- \ },
- \ {
- \ 'name' : 'numeric, skip first character',
- \ 'cmd' : 'sort/./n',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a122',
- \ 'a123',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b321',
- \ 'b321b',
- \ 'b322b'
- \ ]
- \ },
- \ {
- \ 'name' : 'alpha, sort on first character',
- \ 'cmd' : 'sort/./r',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ '',
- \ '',
- \ ' 123b',
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ 'c123d',
- \ 'c321d'
- \ ]
- \ },
- \ {
- \ 'name' : 'alpha, sort on first 2 characters',
- \ 'cmd' : 'sort/../r',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'a',
- \ '',
- \ '',
- \ ' 123b',
- \ 'a123',
- \ 'a122',
- \ 'a321',
- \ 'abc',
- \ 'ab',
- \ 'b123',
- \ 'b321',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ 'c123d',
- \ 'c321d'
- \ ]
- \ },
- \ {
- \ 'name' : 'numeric, sort on first character',
- \ 'cmd' : 'sort/./rn',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ]
- \ },
- \ {
- \ 'name' : 'alpha, skip past first digit',
- \ 'cmd' : 'sort/\d/',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a321',
- \ 'b321',
- \ 'b321',
- \ 'b321b',
- \ 'c321d',
- \ 'a122',
- \ 'b322b',
- \ 'a123',
- \ 'b123',
- \ ' 123b',
- \ 'c123d'
- \ ]
- \ },
- \ {
- \ 'name' : 'alpha, sort on first digit',
- \ 'cmd' : 'sort/\d/r',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a123',
- \ 'a122',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ]
- \ },
- \ {
- \ 'name' : 'numeric, skip past first digit',
- \ 'cmd' : 'sort/\d/n',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b321',
- \ 'b321b',
- \ 'a122',
- \ 'b322b',
- \ 'a123',
- \ 'b123',
- \ 'c123d',
- \ ' 123b'
- \ ]
- \ },
- \ {
- \ 'name' : 'numeric, sort on first digit',
- \ 'cmd' : 'sort/\d/rn',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a123',
- \ 'a122',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ]
- \ },
- \ {
- \ 'name' : 'alpha, skip past first 2 digits',
- \ 'cmd' : 'sort/\d\d/',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a321',
- \ 'b321',
- \ 'b321',
- \ 'b321b',
- \ 'c321d',
- \ 'a122',
- \ 'b322b',
- \ 'a123',
- \ 'b123',
- \ ' 123b',
- \ 'c123d'
- \ ]
- \ },
- \ {
- \ 'name' : 'numeric, skip past first 2 digits',
- \ 'cmd' : 'sort/\d\d/n',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b321',
- \ 'b321b',
- \ 'a122',
- \ 'b322b',
- \ 'a123',
- \ 'b123',
- \ 'c123d',
- \ ' 123b'
- \ ]
- \ },
- \ {
- \ 'name' : 'hexadecimal, skip past first 2 digits',
- \ 'cmd' : 'sort/\d\d/x',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a321',
- \ 'b321',
- \ 'b321',
- \ 'a122',
- \ 'a123',
- \ 'b123',
- \ 'b321b',
- \ 'c321d',
- \ 'b322b',
- \ ' 123b',
- \ 'c123d'
- \ ]
- \ },
- \ {
- \ 'name' : 'alpha, sort on first 2 digits',
- \ 'cmd' : 'sort/\d\d/r',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a123',
- \ 'a122',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ]
- \ },
- \ {
- \ 'name' : 'numeric, sort on first 2 digits',
- \ 'cmd' : 'sort/\d\d/rn',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a123',
- \ 'a122',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ]
- \ },
- \ {
- \ 'name' : 'hexadecimal, sort on first 2 digits',
- \ 'cmd' : 'sort/\d\d/rx',
- \ 'input' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ 'a321',
- \ 'a123',
- \ 'a122',
- \ 'b321',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ 'abc',
- \ 'ab',
- \ 'a',
- \ '',
- \ '',
- \ 'a123',
- \ 'a122',
- \ 'b123',
- \ 'c123d',
- \ ' 123b',
- \ 'a321',
- \ 'b321',
- \ 'c321d',
- \ 'b322b',
- \ 'b321',
- \ 'b321b'
- \ ]
- \ },
- \ {
- \ 'name' : 'binary',
- \ 'cmd' : 'sort b',
- \ 'input' : [
- \ '0b111000',
- \ '0b101100',
- \ '0b101001',
- \ '0b101001',
- \ '0b101000',
- \ '0b000000',
- \ '0b001000',
- \ '0b010000',
- \ '0b101000',
- \ '0b100000',
- \ '0b101010',
- \ '0b100010',
- \ '0b100100',
- \ '0b100010',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ '',
- \ '',
- \ '0b000000',
- \ '0b001000',
- \ '0b010000',
- \ '0b100000',
- \ '0b100010',
- \ '0b100010',
- \ '0b100100',
- \ '0b101000',
- \ '0b101000',
- \ '0b101001',
- \ '0b101001',
- \ '0b101010',
- \ '0b101100',
- \ '0b111000'
- \ ]
- \ },
- \ {
- \ 'name' : 'binary with leading characters',
- \ 'cmd' : 'sort b',
- \ 'input' : [
- \ '0b100010',
- \ '0b010000',
- \ ' 0b101001',
- \ 'b0b101100',
- \ '0b100010',
- \ ' 0b100100',
- \ 'a0b001000',
- \ '0b101000',
- \ '0b101000',
- \ 'a0b101001',
- \ 'ab0b100000',
- \ '0b101010',
- \ '0b000000',
- \ 'b0b111000',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ '',
- \ '',
- \ '0b000000',
- \ 'a0b001000',
- \ '0b010000',
- \ 'ab0b100000',
- \ '0b100010',
- \ '0b100010',
- \ ' 0b100100',
- \ '0b101000',
- \ '0b101000',
- \ ' 0b101001',
- \ 'a0b101001',
- \ '0b101010',
- \ 'b0b101100',
- \ 'b0b111000'
- \ ]
- \ },
- \ {
- \ 'name' : 'alphabetical, sorted input',
- \ 'cmd' : 'sort',
- \ 'input' : [
- \ 'a',
- \ 'b',
- \ 'c',
- \ ],
- \ 'expected' : [
- \ 'a',
- \ 'b',
- \ 'c',
- \ ]
- \ },
- \ {
- \ 'name' : 'alphabetical, sorted input, unique at end',
- \ 'cmd' : 'sort u',
- \ 'input' : [
- \ 'aa',
- \ 'bb',
- \ 'cc',
- \ 'cc',
- \ ],
- \ 'expected' : [
- \ 'aa',
- \ 'bb',
- \ 'cc',
- \ ]
- \ },
- \ {
- \ 'name' : 'sort one line buffer',
- \ 'cmd' : 'sort',
- \ 'input' : [
- \ 'single line'
- \ ],
- \ 'expected' : [
- \ 'single line'
- \ ]
- \ },
- \ {
- \ 'name' : 'sort ignoring case',
- \ 'cmd' : '%sort i',
- \ 'input' : [
- \ 'BB',
- \ 'Cc',
- \ 'aa'
- \ ],
- \ 'expected' : [
- \ 'aa',
- \ 'BB',
- \ 'Cc'
- \ ]
- \ },
- \ ]
-
- " This does not appear to work correctly on Mac.
- if !has('mac')
- if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
- " en_CA.utf-8 sorts capitals before lower case
- " 'Å’' is omitted because it can sort before or after 'Å“'
- let tests += [
- \ {
- \ 'name' : 'sort with locale ' .. v:collate,
- \ 'cmd' : '%sort l',
- \ 'input' : [
- \ 'A',
- \ 'E',
- \ 'O',
- \ 'À',
- \ 'È',
- \ 'É',
- \ 'Ô',
- \ 'Z',
- \ 'a',
- \ 'e',
- \ 'o',
- \ 'à',
- \ 'è',
- \ 'é',
- \ 'ô',
- \ 'Å“',
- \ 'z'
- \ ],
- \ 'expected' : [
- \ 'A',
- \ 'a',
- \ 'À',
- \ 'à',
- \ 'E',
- \ 'e',
- \ 'É',
- \ 'é',
- \ 'È',
- \ 'è',
- \ 'O',
- \ 'o',
- \ 'Ô',
- \ 'ô',
- \ 'Å“',
- \ 'Z',
- \ 'z'
- \ ]
- \ },
- \ ]
- elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
- " With these locales, the accentuated letters are ordered
- " similarly to the non-accentuated letters.
- let tests += [
- \ {
- \ 'name' : 'sort with locale ' .. v:collate,
- \ 'cmd' : '%sort l',
- \ 'input' : [
- \ 'A',
- \ 'E',
- \ 'O',
- \ 'À',
- \ 'È',
- \ 'É',
- \ 'Ô',
- \ 'Å’',
- \ 'Z',
- \ 'a',
- \ 'e',
- \ 'o',
- \ 'à',
- \ 'è',
- \ 'é',
- \ 'ô',
- \ 'Å“',
- \ 'z'
- \ ],
- \ 'expected' : [
- \ 'a',
- \ 'A',
- \ 'à',
- \ 'À',
- \ 'e',
- \ 'E',
- \ 'é',
- \ 'É',
- \ 'è',
- \ 'È',
- \ 'o',
- \ 'O',
- \ 'ô',
- \ 'Ô',
- \ 'Å“',
- \ 'Å’',
- \ 'z',
- \ 'Z'
- \ ]
- \ },
- \ ]
- endif
- endif
-
- for t in tests
- enew!
- call append(0, t.input)
- $delete _
- setlocal nomodified
- execute t.cmd
-
- call assert_equal(t.expected, getline(1, '$'), t.name)
-
- " Previously, the ":sort" command would set 'modified' even if the buffer
- " contents did not change. Here, we check that this problem is fixed.
- if t.input == t.expected
- call assert_false(&modified, t.name . ': &mod is not correct')
- else
- call assert_true(&modified, t.name . ': &mod is not correct')
- endif
- endfor
-
- " Needs at least two lines for this test
- call setline(1, ['line1', 'line2'])
- call assert_fails('sort no', 'E474:')
- call assert_fails('sort c', 'E475:')
- call assert_fails('sort #pat%', 'E654:')
- call assert_fails('sort /\%(/', 'E53:')
-
- enew!
-endfunc
-
-func Test_sort_large_num()
- new
- a
--2147483648
--2147483647
-
--1
-0
-1
--2147483646
-2147483646
-2147483647
-2147483647
--2147483648
-abc
-
-.
- " Numerical sort. Non-numeric lines are ordered before numerical lines.
- " Ordering of non-numerical is stable.
- sort n
- call assert_equal(['',
- \ 'abc',
- \ '',
- \ '-2147483648',
- \ '-2147483648',
- \ '-2147483647',
- \ '-2147483646',
- \ '-1',
- \ '0',
- \ '1',
- \ '2147483646',
- \ '2147483647',
- \ '2147483647'], getline(1, '$'))
- bwipe!
-
- new
- a
--9223372036854775808
--9223372036854775807
-
--1
-0
-1
--9223372036854775806
-9223372036854775806
-9223372036854775807
-9223372036854775807
--9223372036854775808
-abc
-
-.
- sort n
- call assert_equal(['',
- \ 'abc',
- \ '',
- \ '-9223372036854775808',
- \ '-9223372036854775808',
- \ '-9223372036854775807',
- \ '-9223372036854775806',
- \ '-1',
- \ '0',
- \ '1',
- \ '9223372036854775806',
- \ '9223372036854775807',
- \ '9223372036854775807'], getline(1, '$'))
- bwipe!
-endfunc
-
-
-func Test_sort_cmd_report()
- enew!
- call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
- $delete _
- setlocal nomodified
- let res = execute('%sort u')
-
- call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
- call assert_match("6 fewer lines", res)
- enew!
- call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
- $delete _
- setlocal nomodified report=10
- let res = execute('%sort u')
-
- call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
- call assert_equal("", res)
- enew!
- call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
- $delete _
- setl report&vim
- setlocal nomodified
- let res = execute('1g/^/%sort u')
-
- call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
- " the output comes from the :g command, not from the :sort
- call assert_match("6 fewer lines", res)
- enew!
-endfunc
-
-" Test for a :sort command followed by another command
-func Test_sort_followed_by_cmd()
- new
- let var = ''
- call setline(1, ['cc', 'aa', 'bb'])
- %sort | let var = "sortcmdtest"
- call assert_equal(var, "sortcmdtest")
- call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
- " Test for :sort followed by a comment
- call setline(1, ['3b', '1c', '2a'])
- %sort /\d\+/ " sort alphabetically
- call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
- close!
-endfunc
-
-" Test for :sort using last search pattern
-func Test_sort_last_search_pat()
- new
- let @/ = '\d\+'
- call setline(1, ['3b', '1c', '2a'])
- sort //
- call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
- close!
-endfunc
-
-" Test for :sort with no last search pattern
-func Test_sort_with_no_last_search_pat()
- let lines =<< trim [SCRIPT]
- call setline(1, ['3b', '1c', '2a'])
- call assert_fails('sort //', 'E35:')
- call writefile(v:errors, 'Xresult')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -S Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" Test for retaining marks across a :sort
-func Test_sort_with_marks()
- new
- call setline(1, ['cc', 'aa', 'bb'])
- call setpos("'c", [0, 1, 0, 0])
- call setpos("'a", [0, 2, 0, 0])
- call setpos("'b", [0, 3, 0, 0])
- %sort
- call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
- call assert_equal(2, line("'a"))
- call assert_equal(3, line("'b"))
- call assert_equal(1, line("'c"))
- close!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim
deleted file mode 100644
index d4d96e36bf..0000000000
--- a/src/nvim/testdir/test_source.vim
+++ /dev/null
@@ -1,113 +0,0 @@
-" Tests for the :source command.
-
-source check.vim
-source view_util.vim
-
-func Test_source_autocmd()
- call writefile([
- \ 'let did_source = 1',
- \ ], 'Xsourced')
- au SourcePre *source* let did_source_pre = 1
- au SourcePost *source* let did_source_post = 1
-
- source Xsourced
-
- call assert_equal(g:did_source, 1)
- call assert_equal(g:did_source_pre, 1)
- call assert_equal(g:did_source_post, 1)
-
- call delete('Xsourced')
- au! SourcePre
- au! SourcePost
- unlet g:did_source
- unlet g:did_source_pre
- unlet g:did_source_post
-endfunc
-
-func Test_source_cmd()
- au SourceCmd *source* let did_source = expand('<afile>')
- au SourcePre *source* let did_source_pre = 2
- au SourcePost *source* let did_source_post = 2
-
- source Xsourced
-
- call assert_equal(g:did_source, 'Xsourced')
- call assert_false(exists('g:did_source_pre'))
- call assert_equal(g:did_source_post, 2)
-
- au! SourceCmd
- au! SourcePre
- au! SourcePost
-endfunc
-
-func Test_source_sandbox()
- new
- call writefile(["Ohello\<Esc>"], 'Xsourcehello')
- source! Xsourcehello | echo
- call assert_equal('hello', getline(1))
- call assert_fails('sandbox source! Xsourcehello', 'E48:')
- bwipe!
- call delete('Xsourcehello')
-endfunc
-
-" When deleting a file and immediately creating a new one the inode may be
-" recycled. Vim should not recognize it as the same script.
-func Test_different_script()
- call writefile(['let s:var = "asdf"'], 'XoneScript')
- source XoneScript
- call delete('XoneScript')
- call writefile(['let g:var = s:var'], 'XtwoScript')
- call assert_fails('source XtwoScript', 'E121:')
- call delete('XtwoScript')
-endfunc
-
-" When sourcing a vim script, shebang should be ignored.
-func Test_source_ignore_shebang()
- call writefile(['#!./xyzabc', 'let g:val=369'], 'Xfile.vim')
- source Xfile.vim
- call assert_equal(g:val, 369)
- call delete('Xfile.vim')
-endfunc
-
-" Test for expanding <sfile> in a autocmd and for <slnum> and <sflnum>
-func Test_source_autocmd_sfile()
- let code =<< trim [CODE]
- let g:SfileName = ''
- augroup sfiletest
- au!
- autocmd User UserAutoCmd let g:Sfile = '<sfile>:t'
- augroup END
- doautocmd User UserAutoCmd
- let g:Slnum = expand('<slnum>')
- let g:Sflnum = expand('<sflnum>')
- augroup! sfiletest
- [CODE]
- call writefile(code, 'Xscript.vim')
- source Xscript.vim
- call assert_equal('Xscript.vim', g:Sfile)
- call assert_equal('7', g:Slnum)
- call assert_equal('8', g:Sflnum)
- call delete('Xscript.vim')
-endfunc
-
-func Test_source_error()
- call assert_fails('scriptencoding utf-8', 'E167:')
- call assert_fails('finish', 'E168:')
- " call assert_fails('scriptversion 2', 'E984:')
-endfunc
-
-" Test for sourcing a script recursively
-func Test_nested_script()
- CheckRunVimInTerminal
- call writefile([':source! Xscript.vim', ''], 'Xscript.vim')
- let buf = RunVimInTerminal('', {'rows': 6})
- call term_wait(buf)
- call term_sendkeys(buf, ":set noruler\n")
- call term_sendkeys(buf, ":source! Xscript.vim\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('E22: Scripts nested too deep\s*', term_getline(buf, 6))})
- call delete('Xscript.vim')
- call StopVimInTerminal(buf)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim
deleted file mode 100644
index 66fabe0442..0000000000
--- a/src/nvim/testdir/test_source_utf8.vim
+++ /dev/null
@@ -1,61 +0,0 @@
-" Test the :source! command
-source check.vim
-
-func Test_source_utf8()
- " check that sourcing a script with 0x80 as second byte works
- new
- call setline(1, [':%s/àx/--à1234--/g', ':%s/Àx/--À1234--/g'])
- write! Xscript
- bwipe!
- new
- call setline(1, [' àx ', ' Àx '])
- source! Xscript | echo
- call assert_equal(' --à1234-- ', getline(1))
- call assert_equal(' --À1234-- ', getline(2))
- bwipe!
- call delete('Xscript')
-endfunc
-
-func Test_source_latin()
- " check that sourcing a latin1 script with a 0xc0 byte works
- new
- call setline(1, ["call feedkeys('r')", "call feedkeys('\xc0', 'xt')"])
- write! Xscript
- bwipe!
- new
- call setline(1, ['xxx'])
- source Xscript
- call assert_equal("\u00c0xx", getline(1))
- bwipe!
- call delete('Xscript')
-endfunc
-
-" Test for sourcing a file with CTRL-V's at the end of the line
-func Test_source_ctrl_v()
- call writefile(['map __1 afirst',
- \ 'map __2 asecond',
- \ 'map __3 athird',
- \ 'map __4 afourth',
- \ 'map __5 afifth',
- \ "map __1 asd\<C-V>",
- \ "map __2 asd\<C-V>\<C-V>",
- \ "map __3 asd\<C-V>\<C-V>",
- \ "map __4 asd\<C-V>\<C-V>\<C-V>",
- \ "map __5 asd\<C-V>\<C-V>\<C-V>",
- \ ], 'Xtestfile')
- source Xtestfile
- enew!
- exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>"
- exe "%s/\<C-J>/0/g"
- call assert_equal(['sd',
- \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"],
- \ getline(1, 2))
-
- enew!
- call delete('Xtestfile')
- unmap __1
- unmap __2
- unmap __3
- unmap __4
- unmap __5
-endfunc
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
deleted file mode 100644
index c840e834b9..0000000000
--- a/src/nvim/testdir/test_spell.vim
+++ /dev/null
@@ -1,1467 +0,0 @@
-" Test spell checking
-" Note: this file uses latin1 encoding, but is used with utf-8 encoding.
-
-source check.vim
-CheckFeature spell
-
-source screendump.vim
-
-func TearDown()
- set nospell
- call delete('Xtest.aff')
- call delete('Xtest.dic')
- call delete('Xtest.latin1.add')
- call delete('Xtest.latin1.add.spl')
- call delete('Xtest.latin1.spl')
- call delete('Xtest.latin1.sug')
- " set 'encoding' to clear the word list
- set encoding=utf-8
-endfunc
-
-func Test_wrap_search()
- new
- call setline(1, ['The', '', 'A plong line with two zpelling mistakes', '', 'End'])
- set spell wrapscan
- normal ]s
- call assert_equal('plong', expand('<cword>'))
- normal ]s
- call assert_equal('zpelling', expand('<cword>'))
- normal ]s
- call assert_equal('plong', expand('<cword>'))
- bwipe!
- set nospell
-endfunc
-
-func Test_curswant()
- new
- call setline(1, ['Another plong line', 'abcdefghijklmnopq'])
- set spell wrapscan
- normal 0]s
- call assert_equal('plong', expand('<cword>'))
- normal j
- call assert_equal(9, getcurpos()[2])
- normal 0[s
- call assert_equal('plong', expand('<cword>'))
- normal j
- call assert_equal(9, getcurpos()[2])
-
- normal 0]S
- call assert_equal('plong', expand('<cword>'))
- normal j
- call assert_equal(9, getcurpos()[2])
- normal 0[S
- call assert_equal('plong', expand('<cword>'))
- normal j
- call assert_equal(9, getcurpos()[2])
-
- normal 1G0
- call assert_equal('plong', spellbadword()[0])
- normal j
- call assert_equal(9, getcurpos()[2])
-
- bwipe!
- set nospell
-endfunc
-
-func Test_z_equal_on_invalid_utf8_word()
- split
- set spell
- call setline(1, "\xff")
- norm z=
- set nospell
- bwipe!
-endfunc
-
-func Test_z_equal_on_single_character()
- " this was decrementing the index below zero
- new
- norm a0\Ê
- norm zW
- norm z=
-
- bwipe!
-endfunc
-
-" Test spellbadword() with argument
-func Test_spellbadword()
- set spell
-
- call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
- call assert_equal(['another', 'caps'], 'A sentence. another sentence'->spellbadword())
-
- call assert_equal(['TheCamelWord', 'bad'], spellbadword('TheCamelWord asdf'))
- set spelloptions=camel
- call assert_equal(['asdf', 'bad'], spellbadword('TheCamelWord asdf'))
- set spelloptions=
-
- set spelllang=en
- call assert_equal(['', ''], spellbadword('centre'))
- call assert_equal(['', ''], spellbadword('center'))
- set spelllang=en_us
- call assert_equal(['centre', 'local'], spellbadword('centre'))
- call assert_equal(['', ''], spellbadword('center'))
- set spelllang=en_gb
- call assert_equal(['', ''], spellbadword('centre'))
- call assert_equal(['center', 'local'], spellbadword('center'))
-
- " Create a small word list to test that spellbadword('...')
- " can return ['...', 'rare'].
- e Xwords
- insert
-foo
-foobar/?
-.
- w!
- mkspell! Xwords.spl Xwords
- set spelllang=Xwords.spl
- call assert_equal(['foobar', 'rare'], spellbadword('foo foobar'))
-
- " Typo should be detected even without the 'spell' option.
- set spelllang=en_gb nospell
- call assert_equal(['', ''], spellbadword('centre'))
- call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
- call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence'))
-
- set spelllang=
- call assert_fails("call spellbadword('maxch')", 'E756:')
-
- call delete('Xwords.spl')
- call delete('Xwords')
- set spelllang&
- set spell&
-endfunc
-
-func Test_spell_file_missing()
- let s:spell_file_missing = 0
- augroup TestSpellFileMissing
- autocmd! SpellFileMissing * let s:spell_file_missing += 1
- augroup END
-
- set spell spelllang=ab_cd
- let messages = GetMessages()
- " This message is not shown in Nvim because of #3027
- " call assert_equal('Warning: Cannot find word list "ab.utf-8.spl" or "ab.ascii.spl"', messages[-1])
- call assert_equal(1, s:spell_file_missing)
-
- new XTestSpellFileMissing
- augroup TestSpellFileMissing
- autocmd! SpellFileMissing * bwipe
- augroup END
- call assert_fails('set spell spelllang=ab_cd', 'E937:')
-
- " clean up
- augroup TestSpellFileMissing
- autocmd! SpellFileMissing
- augroup END
- augroup! TestSpellFileMissing
- unlet s:spell_file_missing
- set spell& spelllang&
- %bwipe!
-endfunc
-
-func Test_spell_file_missing_bwipe()
- " this was using a window that was wiped out in a SpellFileMissing autocmd
- set spelllang=xy
- au SpellFileMissing * n0
- set spell
- au SpellFileMissing * bw
- snext somefile
-
- au! SpellFileMissing
- bwipe!
- set nospell spelllang=en
-endfunc
-
-func Test_spelldump()
- " In case the spell file is not found avoid getting the download dialog, we
- " would get stuck at the prompt.
- let g:en_not_found = 0
- augroup TestSpellFileMissing
- au! SpellFileMissing * let g:en_not_found = 1
- augroup END
- set spell spelllang=en
- spellrare! emacs
- if g:en_not_found
- call assert_report("Could not find English spell file")
- else
- spelldump
-
- " Check assumption about region: 1: us, 2: au, 3: ca, 4: gb, 5: nz.
- call assert_equal('/regions=usaucagbnz', getline(1))
- call assert_notequal(0, search('^theater/1$')) " US English only.
- call assert_notequal(0, search('^theatre/2345$')) " AU, CA, GB or NZ English.
-
- call assert_notequal(0, search('^emacs/?$')) " ? for a rare word.
- call assert_notequal(0, search('^the the/!$')) " ! for a wrong word.
- endif
-
- " clean up
- unlet g:en_not_found
- augroup TestSpellFileMissing
- autocmd! SpellFileMissing
- augroup END
- augroup! TestSpellFileMissing
- bwipe
- set spell&
-endfunc
-
-func Test_spelldump_bang()
- new
- call setline(1, 'This is a sample sentence.')
- redraw
-
- " In case the spell file is not found avoid getting the download dialog, we
- " would get stuck at the prompt.
- let g:en_not_found = 0
- augroup TestSpellFileMissing
- au! SpellFileMissing * let g:en_not_found = 1
- augroup END
-
- set spell
-
- if g:en_not_found
- call assert_report("Could not find English spell file")
- else
- redraw
- spelldump!
-
- " :spelldump! includes the number of times a word was found while updating
- " the screen.
- " Common word count starts at 10, regular word count starts at 0.
- call assert_notequal(0, search("^is\t11$")) " common word found once.
- call assert_notequal(0, search("^the\t10$")) " common word never found.
- call assert_notequal(0, search("^sample\t1$")) " regular word found once.
- call assert_equal(0, search("^screen\t")) " regular word never found.
- endif
-
- " clean up
- unlet g:en_not_found
- augroup TestSpellFileMissing
- autocmd! SpellFileMissing
- augroup END
- augroup! TestSpellFileMissing
- %bwipe!
- set spell&
-endfunc
-
-func Test_spelllang_inv_region()
- set spell spelllang=en_xx
- let messages = GetMessages()
- call assert_equal('Warning: region xx not supported', messages[-1])
- set spell& spelllang&
-endfunc
-
-func Test_compl_with_CTRL_X_CTRL_K_using_spell()
- " When spell checking is enabled and 'dictionary' is empty,
- " CTRL-X CTRL-K in insert mode completes using the spelling dictionary.
- new
- set spell spelllang=en dictionary=
-
- set ignorecase
- call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
- call assert_equal(['English'], getline(1, '$'))
- call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
- call assert_equal(['English'], getline(1, '$'))
-
- set noignorecase
- call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
- call assert_equal(['englis'], getline(1, '$'))
- call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
- call assert_equal(['English'], getline(1, '$'))
-
- set spelllang=en_us
- call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
- call assert_equal(['theater'], getline(1, '$'))
- set spelllang=en_gb
- call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
- " FIXME: commented out, expected theatre bug got theater. See issue #7025.
- " call assert_equal(['theatre'], getline(1, '$'))
-
- bwipe!
- set spell& spelllang& dictionary& ignorecase&
-endfunc
-
-func Test_spellreall()
- new
- set spell
- call assert_fails('spellrepall', 'E752:')
- call setline(1, ['A speling mistake. The same speling mistake.',
- \ 'Another speling mistake.'])
- call feedkeys(']s1z=', 'tx')
- call assert_equal('A spelling mistake. The same speling mistake.', getline(1))
- call assert_equal('Another speling mistake.', getline(2))
- spellrepall
- call assert_equal('A spelling mistake. The same spelling mistake.', getline(1))
- call assert_equal('Another spelling mistake.', getline(2))
- call assert_fails('spellrepall', 'E753:')
- set spell&
- bwipe!
-endfunc
-
-func Test_spell_dump_word_length()
- " this was running over MAXWLEN
- new
- noremap 0 0a0zW0000000
- sil! norm 0z=0
- sil norm 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
- sil! norm 0z=0
-
- bwipe!
- nunmap 0
-endfunc
-
-" Test spellsuggest({word} [, {max} [, {capital}]])
-func Test_spellsuggest()
- " Verify suggestions are given even when spell checking is not enabled.
- set nospell
- call assert_equal(['march', 'March'], spellsuggest('marrch', 2))
-
- set spell
-
- " With 1 argument.
- call assert_equal(['march', 'March'], spellsuggest('marrch')[0:1])
-
- " With 2 arguments.
- call assert_equal(['march', 'March'], spellsuggest('marrch', 2))
-
- " With 3 arguments.
- call assert_equal(['march'], spellsuggest('marrch', 1, 0))
- call assert_equal(['March'], spellsuggest('marrch', 1, 1))
-
- " Test with digits and hyphen.
- call assert_equal('Carbon-14', spellsuggest('Carbon-15')[0])
-
- " Comment taken from spellsuggest.c explains the following test cases:
- "
- " If there are more UPPER than lower case letters suggest an
- " ALLCAP word. Otherwise, if the first letter is UPPER then
- " suggest ONECAP. Exception: "ALl" most likely should be "All",
- " require three upper case letters.
- call assert_equal(['THIRD', 'third'], spellsuggest('thIRD', 2))
- call assert_equal(['third', 'THIRD'], spellsuggest('tHIrd', 2))
- call assert_equal(['Third'], spellsuggest('THird', 1))
- call assert_equal(['All'], spellsuggest('ALl', 1))
-
- " Special suggestion for repeated 'the the'.
- call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the'))
- call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the'))
- call assert_inrange(0, 2, index(spellsuggest('The the', 3), 'The'))
-
- call assert_fails("call spellsuggest('maxch', [])", 'E745:')
- call assert_fails("call spellsuggest('maxch', 2, [])", 'E745:')
-
- set spelllang=
- call assert_fails("call spellsuggest('maxch')", 'E756:')
- set spelllang&
-
- set spell&
-endfunc
-
-" Test 'spellsuggest' option with methods fast, best and double.
-func Test_spellsuggest_option_methods()
- set spell
-
- for e in ['utf-8']
- exe 'set encoding=' .. e
-
- set spellsuggest=fast
- call assert_equal(['Stick', 'Stitch'], spellsuggest('Stich', 2), e)
-
- " With best or double option, "Stitch" should become the top suggestion
- " because of better phonetic matching.
- set spellsuggest=best
- call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e)
-
- set spellsuggest=double
- call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e)
- endfor
-
- set spell& spellsuggest& encoding&
-endfunc
-
-" Test 'spellsuggest' option with value file:{filename}
-func Test_spellsuggest_option_file()
- set spell spellsuggest=file:Xspellsuggest
- call writefile(['emacs/vim',
- \ 'theribal/terrible',
- \ 'teribal/terrrible',
- \ 'terribal'],
- \ 'Xspellsuggest')
-
- call assert_equal(['vim'], spellsuggest('emacs', 2))
- call assert_equal(['terrible'], spellsuggest('theribal',2))
-
- " If the suggestion is misspelled (*terrrible* with 3 r),
- " it should not be proposed.
- " The entry for "terribal" should be ignored because of missing slash.
- call assert_equal([], spellsuggest('teribal', 2))
- call assert_equal([], spellsuggest('terribal', 2))
-
- set spell spellsuggest=best,file:Xspellsuggest
- call assert_equal(['vim', 'Emacs'], spellsuggest('emacs', 2))
- call assert_equal(['terrible', 'tribal'], spellsuggest('theribal', 2))
- call assert_equal(['tribal'], spellsuggest('teribal', 1))
- call assert_equal(['tribal'], spellsuggest('terribal', 1))
-
- call delete('Xspellsuggest')
- call assert_fails("call spellsuggest('vim')", "E484: Can't open file Xspellsuggest")
-
- set spellsuggest& spell&
-endfunc
-
-" Test 'spellsuggest' option with value {number}
-" to limit the number of suggestions
-func Test_spellsuggest_option_number()
- set spell spellsuggest=2,best
- new
-
- " We limited the number of suggestions to 2, so selecting
- " the 1st and 2nd suggestion should correct the word, but
- " selecting a 3rd suggestion should do nothing.
- call setline(1, 'A baord')
- norm $1z=
- call assert_equal('A board', getline(1))
-
- call setline(1, 'A baord')
- norm $2z=
- call assert_equal('A bard', getline(1))
-
- call setline(1, 'A baord')
- norm $3z=
- call assert_equal('A baord', getline(1))
-
- let a = execute('norm $z=')
- call assert_equal(
- \ "\n"
- \ .. "Change \"baord\" to:\n"
- \ .. " 1 \"board\"\n"
- \ .. " 2 \"bard\"\n"
- \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
-
- set spell spellsuggest=0
- call assert_equal("\nSorry, no suggestions", execute('norm $z='))
-
- " Unlike z=, function spellsuggest(...) should not be affected by the
- " max number of suggestions (2) set by the 'spellsuggest' option.
- call assert_equal(['board', 'bard', 'broad'], spellsuggest('baord', 3))
-
- set spellsuggest& spell&
- bwipe!
-endfunc
-
-" Test 'spellsuggest' option with value expr:{expr}
-func Test_spellsuggest_option_expr()
- " A silly 'spellsuggest' function which makes suggestions all uppercase
- " and makes the score of each suggestion the length of the suggested word.
- " So shorter suggestions are preferred.
- func MySuggest()
- let spellsuggest_save = &spellsuggest
- set spellsuggest=3,best
- let result = map(spellsuggest(v:val, 3), "[toupper(v:val), len(v:val)]")
- let &spellsuggest = spellsuggest_save
- return result
- endfunc
-
- set spell spellsuggest=expr:MySuggest()
- call assert_equal(['BARD', 'BOARD', 'BROAD'], spellsuggest('baord', 3))
-
- new
- call setline(1, 'baord')
- let a = execute('norm z=')
- call assert_equal(
- \ "\n"
- \ .. "Change \"baord\" to:\n"
- \ .. " 1 \"BARD\"\n"
- \ .. " 2 \"BOARD\"\n"
- \ .. " 3 \"BROAD\"\n"
- \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
-
- " With verbose, z= should show the score i.e. word length with
- " our SpellSuggest() function.
- set verbose=1
- let a = execute('norm z=')
- call assert_equal(
- \ "\n"
- \ .. "Change \"baord\" to:\n"
- \ .. " 1 \"BARD\" (4 - 0)\n"
- \ .. " 2 \"BOARD\" (5 - 0)\n"
- \ .. " 3 \"BROAD\" (5 - 0)\n"
- \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
-
- set spell& spellsuggest& verbose&
- bwipe!
-endfunc
-
-" Test for 'spellsuggest' expr errrors
-func Test_spellsuggest_expr_errors()
- " 'spellsuggest'
- func MySuggest()
- return range(3)
- endfunc
- set spell spellsuggest=expr:MySuggest()
- call assert_equal([], spellsuggest('baord', 3))
-
- " Test for 'spellsuggest' expression returning a non-list value
- func! MySuggest2()
- return 'good'
- endfunc
- set spellsuggest=expr:MySuggest2()
- call assert_equal([], spellsuggest('baord'))
-
- " Test for 'spellsuggest' expression returning a list with dict values
- func! MySuggest3()
- return [[{}, {}]]
- endfunc
- set spellsuggest=expr:MySuggest3()
- call assert_fails("call spellsuggest('baord')", 'E731:')
-
- set nospell spellsuggest&
- delfunc MySuggest
- delfunc MySuggest2
- delfunc MySuggest3
-endfunc
-
-func Test_spellsuggest_timeout()
- set spellsuggest=timeout:30
- set spellsuggest=timeout:-123
- set spellsuggest=timeout:999999
- call assert_fails('set spellsuggest=timeout', 'E474:')
- call assert_fails('set spellsuggest=timeout:x', 'E474:')
- call assert_fails('set spellsuggest=timeout:-x', 'E474:')
- call assert_fails('set spellsuggest=timeout:--9', 'E474:')
-endfunc
-
-func Test_spellsuggest_visual_end_of_line()
- let enc_save = &encoding
- " set encoding=iso8859
-
- " This was reading beyond the end of the line.
- norm R00000000000
- sil norm 0
- sil! norm i00000)
- sil! norm i00000)
- call feedkeys("\<CR>")
- norm z=
-
- let &encoding = enc_save
-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'))
-
- set enc=cp1250 spell spelllang=en
- 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'))
-
- 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'))
-
- set spell spelllang=
- call assert_fails('spellinfo', 'E756:')
-
- 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
-
-func Test_zz_basic()
- call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1)
- call RunGoodBad("wrong OK puts. Test the end",
- \ "bad: inputs comment ok Ok. test d\xE9\xF4l end the",
- \["Comment", "deol", "d\xE9\xF4r", "input", "OK", "output", "outputs", "outtest", "put", "puts",
- \ "test", "testen", "testn", "the end", "uk", "wrong"],
- \[
- \ ["bad", ["put", "uk", "OK"]],
- \ ["inputs", ["input", "puts", "outputs"]],
- \ ["comment", ["Comment", "outtest", "the end"]],
- \ ["ok", ["OK", "uk", "put"]],
- \ ["Ok", ["OK", "Uk", "Put"]],
- \ ["test", ["Test", "testn", "testen"]],
- \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]],
- \ ["end", ["put", "uk", "test"]],
- \ ["the", ["put", "uk", "test"]],
- \ ]
- \ )
-
- call assert_equal("gebletegek", soundfold('goobledygoook'))
- call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold())
- call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale'))
-endfunc
-
-" Postponed prefixes
-func Test_zz_prefixes()
- call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1)
- call RunGoodBad("puts",
- \ "bad: inputs comment ok Ok end the. test d\xE9\xF4l",
- \ ["Comment", "deol", "d\xE9\xF4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"],
- \ [
- \ ["bad", ["put", "uk", "OK"]],
- \ ["inputs", ["input", "puts", "outputs"]],
- \ ["comment", ["Comment"]],
- \ ["ok", ["OK", "uk", "put"]],
- \ ["Ok", ["OK", "Uk", "Put"]],
- \ ["end", ["put", "uk", "deol"]],
- \ ["the", ["put", "uk", "test"]],
- \ ["test", ["Test", "testn", "testen"]],
- \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]],
- \ ])
-endfunc
-
-"Compound words
-func Test_zz_compound()
- call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3)
- call RunGoodBad("foo m\xEF foobar foofoobar barfoo barbarfoo",
- \ "bad: bar la foom\xEF barm\xEF m\xEFfoo m\xEFbar m\xEFm\xEF lala m\xEFla lam\xEF foola labar",
- \ ["foo", "m\xEF"],
- \ [
- \ ["bad", ["foo", "m\xEF"]],
- \ ["bar", ["barfoo", "foobar", "foo"]],
- \ ["la", ["m\xEF", "foo"]],
- \ ["foom\xEF", ["foo m\xEF", "foo", "foofoo"]],
- \ ["barm\xEF", ["barfoo", "m\xEF", "barbar"]],
- \ ["m\xEFfoo", ["m\xEF foo", "foo", "foofoo"]],
- \ ["m\xEFbar", ["foobar", "barbar", "m\xEF"]],
- \ ["m\xEFm\xEF", ["m\xEF m\xEF", "m\xEF"]],
- \ ["lala", []],
- \ ["m\xEFla", ["m\xEF", "m\xEF m\xEF"]],
- \ ["lam\xEF", ["m\xEF", "m\xEF m\xEF"]],
- \ ["foola", ["foo", "foobar", "foofoo"]],
- \ ["labar", ["barbar", "foobar"]],
- \ ])
-
- call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4)
- call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword",
- \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword",
- \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"],
- \ [
- \ ["bad", ["end", "bork", "word"]],
- \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]],
- \ ["pro", ["bork", "word", "end"]],
- \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]],
- \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]],
- \ ["endstart", ["end start", "start"]],
- \ ["endend", ["end end", "end"]],
- \ ["startstart", ["start start"]],
- \ ["wordend", ["word end", "word", "wordword"]],
- \ ["wordstart", ["word start", "bork start"]],
- \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]],
- \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]],
- \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]],
- \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]],
- \ ["utilsbork", ["utilbork", "utils bork", "util bork"]],
- \ ["startnouword", ["start nouword", "startword", "startborkword"]],
- \ ])
-
-endfunc
-
-"Test affix flags with two characters
-func Test_zz_affix()
- call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5)
- call RunGoodBad("fooa1 fooa\xE9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend",
- \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend",
- \ ["bar", "barbork", "end", "fooa1", "fooa\xE9", "nouend", "prebar", "prebarbork", "start"],
- \ [
- \ ["bad", ["bar", "end", "fooa1"]],
- \ ["foo", ["fooa1", "fooa\xE9", "bar"]],
- \ ["fooa2", ["fooa1", "fooa\xE9", "bar"]],
- \ ["prabar", ["prebar", "bar", "bar bar"]],
- \ ["probarbirk", ["prebarbork"]],
- \ ["middle", []],
- \ ["startmiddle", ["startmiddleend", "startmiddlebar"]],
- \ ["middleend", []],
- \ ["endstart", ["end start", "start"]],
- \ ["startprobar", ["startprebar", "start prebar", "startbar"]],
- \ ["startnouend", ["start nouend", "startend"]],
- \ ])
-
- call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6)
- call RunGoodBad("meea1 meea\xE9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend",
- \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar",
- \ ["bar", "barbork", "end", "lead", "meea1", "meea\xE9", "prebar", "prebarbork"],
- \ [
- \ ["bad", ["bar", "end", "lead"]],
- \ ["mee", ["meea1", "meea\xE9", "bar"]],
- \ ["meea2", ["meea1", "meea\xE9", "lead"]],
- \ ["prabar", ["prebar", "bar", "leadbar"]],
- \ ["probarbirk", ["prebarbork"]],
- \ ["middle", []],
- \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]],
- \ ["middleend", []],
- \ ["endlead", ["end lead", "lead", "end end"]],
- \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
- \ ])
-
- call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
- 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", "meezero", "prebar", "prebarmeat", "tail"],
- \ [
- \ ["bad", ["bar", "lead", "tail"]],
- \ ["mee", ["meea1", "meea\xE9", "bar"]],
- \ ["meea2", ["meea1", "meea\xE9", "lead"]],
- \ ["prabar", ["prebar", "bar", "leadbar"]],
- \ ["probarmaat", ["prebarmeat"]],
- \ ["middle", []],
- \ ["leadmiddle", ["leadmiddlebar"]],
- \ ["middletail", []],
- \ ["taillead", ["tail lead", "tail"]],
- \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
- \ ])
-endfunc
-
-func Test_zz_NOSLITSUGS()
- call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8)
- call RunGoodBad("foo bar faabar", "bad: foobar barfoo",
- \ ["bar", "faabar", "foo"],
- \ [
- \ ["bad", ["bar", "foo"]],
- \ ["foobar", ["faabar", "foo bar", "bar"]],
- \ ["barfoo", ["bar foo", "bar", "foo"]],
- \ ])
-endfunc
-
-" Numbers
-func Test_zz_Numbers()
- call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9)
- call RunGoodBad("0b1011 0777 1234 0x01ff", "",
- \ ["bar", "foo"],
- \ [
- \ ])
-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
- let [str, a] = spellbadword()
- return str
-endfunc
-
-function SecondSpellWord()
- normal `m]s
- let [str, a] = spellbadword()
- return str
-endfunc
-
-"Test with SAL instead of SOFO items; test automatic reloading
-func Test_zz_sal_and_addition()
- throw 'skipped: Nvim does not support enc=latin1'
- set enc=latin1
- set spellfile=
- call writefile(g:test_data_dic1, "Xtest.dic")
- call writefile(g:test_data_aff_sal, "Xtest.aff")
- mkspell! Xtest Xtest
- set spl=Xtest.latin1.spl spell
- call assert_equal('kbltykk', soundfold('goobledygoook'))
- call assert_equal('kprnfn', soundfold('kóopërÿnôven'))
- call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale'))
-
- "also use an addition file
- call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.latin1.add")
- mkspell! Xtest.latin1.add.spl Xtest.latin1.add
-
- bwipe!
- call setline(1, ["start: elequint test elekwint test elekwent asdf"])
-
- set spellfile=Xtest.latin1.add
- call assert_equal("elekwent", FirstSpellWord())
-
- set spl=Xtest_us.latin1.spl
- call assert_equal("elequint", FirstSpellWord())
- call assert_equal("elekwint", SecondSpellWord())
-
- set spl=Xtest_gb.latin1.spl
- call assert_equal("elekwint", FirstSpellWord())
- call assert_equal("elekwent", SecondSpellWord())
-
- set spl=Xtest_nz.latin1.spl
- call assert_equal("elequint", FirstSpellWord())
- call assert_equal("elekwent", SecondSpellWord())
-
- set spl=Xtest_ca.latin1.spl
- call assert_equal("elequint", FirstSpellWord())
- call assert_equal("elekwint", SecondSpellWord())
-
- bwipe!
- set spellfile=
- set spl&
-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")
- mkspell! Xtest.latin1.add.spl Xtest.latin1.add
- call assert_match('Invalid region nr in Xtest.latin1.add line 2: 0', execute('messages'))
- call delete('Xtest.latin1.add')
- call delete('Xtest.latin1.add.spl')
-endfunc
-
-" Check using z= in new buffer (crash fixed by patch 7.4a.028).
-func Test_zeq_crash()
- new
- set spell
- call feedkeys('iasdz=:\"', 'tx')
-
- bwipe!
-endfunc
-
-" Check that z= works even when 'nospell' is set. This test uses one of the
-" tests in Test_spellsuggest_option_number() just to verify that z= basically
-" works and that "E756: Spell checking is not enabled" is not generated.
-func Test_zeq_nospell()
- new
- set nospell spellsuggest=1,best
- call setline(1, 'A baord')
- try
- norm $1z=
- call assert_equal('A board', getline(1))
- catch
- call assert_report("Caught exception: " . v:exception)
- endtry
- set spell& spellsuggest&
- bwipe!
-endfunc
-
-" Check that "E756: Spell checking is not possible" is reported when z= is
-" executed and 'spelllang' is empty.
-func Test_zeq_no_spelllang()
- new
- set spelllang= spellsuggest=1,best
- call setline(1, 'A baord')
- call assert_fails('normal $1z=', 'E756:')
- set spelllang& spellsuggest&
- bwipe!
-endfunc
-
-" Check handling a word longer than MAXWLEN.
-func Test_spell_long_word()
- set enc=utf-8
- new
- call setline(1, "d\xCC\xB4\xCC\xBD\xCD\x88\xCD\x94a\xCC\xB5\xCD\x84\xCD\x84\xCC\xA8\xCD\x9Cr\xCC\xB5\xCC\x8E\xCD\x85\xCD\x85k\xCC\xB6\xCC\x89\xCC\x9D \xCC\xB6\xCC\x83\xCC\x8F\xCC\xA4\xCD\x8Ef\xCC\xB7\xCC\x81\xCC\x80\xCC\xA9\xCC\xB0\xCC\xAC\xCC\xA2\xCD\x95\xCD\x87\xCD\x8D\xCC\x9E\xCD\x99\xCC\xAD\xCC\xAB\xCC\x97\xCC\xBBo\xCC\xB6\xCC\x84\xCC\x95\xCC\x8C\xCC\x8B\xCD\x9B\xCD\x9C\xCC\xAFr\xCC\xB7\xCC\x94\xCD\x83\xCD\x97\xCC\x8C\xCC\x82\xCD\x82\xCD\x80\xCD\x91\xCC\x80\xCC\xBE\xCC\x82\xCC\x8F\xCC\xA3\xCD\x85\xCC\xAE\xCD\x8D\xCD\x99\xCC\xBC\xCC\xAB\xCC\xA7\xCD\x88c\xCC\xB7\xCD\x83\xCC\x84\xCD\x92\xCC\x86\xCC\x83\xCC\x88\xCC\x92\xCC\x94\xCC\xBE\xCC\x9D\xCC\xAF\xCC\x98\xCC\x9D\xCC\xBB\xCD\x8E\xCC\xBB\xCC\xB3\xCC\xA3\xCD\x8E\xCD\x99\xCC\xA5\xCC\xAD\xCC\x99\xCC\xB9\xCC\xAE\xCC\xA5\xCC\x9E\xCD\x88\xCC\xAE\xCC\x9E\xCC\xA9\xCC\x97\xCC\xBC\xCC\x99\xCC\xA5\xCD\x87\xCC\x97\xCD\x8E\xCD\x94\xCC\x99\xCC\x9D\xCC\x96\xCD\x94\xCC\xAB\xCC\xA7\xCC\xA5\xCC\x98\xCC\xBB\xCC\xAF\xCC\xABe\xCC\xB7\xCC\x8E\xCC\x82\xCD\x86\xCD\x9B\xCC\x94\xCD\x83\xCC\x85\xCD\x8A\xCD\x8C\xCC\x8B\xCD\x92\xCD\x91\xCC\x8F\xCC\x81\xCD\x95\xCC\xA2\xCC\xB9\xCC\xB2\xCD\x9C\xCC\xB1\xCC\xA6\xCC\xB3\xCC\xAF\xCC\xAE\xCC\x9C\xCD\x99s\xCC\xB8\xCC\x8C\xCC\x8E\xCC\x87\xCD\x81\xCD\x82\xCC\x86\xCD\x8C\xCD\x8C\xCC\x8B\xCC\x84\xCC\x8C\xCD\x84\xCD\x9B\xCD\x86\xCC\x93\xCD\x90\xCC\x85\xCC\x94\xCD\x98\xCD\x84\xCD\x92\xCD\x8B\xCC\x90\xCC\x83\xCC\x8F\xCD\x84\xCD\x81\xCD\x9B\xCC\x90\xCD\x81\xCC\x8F\xCC\xBD\xCC\x88\xCC\xBF\xCC\x88\xCC\x84\xCC\x8E\xCD\x99\xCD\x94\xCC\x99\xCD\x99\xCC\xB0\xCC\xA8\xCC\xA3\xCC\xA8\xCC\x96\xCC\x99\xCC\xAE\xCC\xBC\xCC\x99\xCD\x9A\xCC\xB2\xCC\xB1\xCC\x9F\xCC\xBB\xCC\xA6\xCD\x85\xCC\xAA\xCD\x89\xCC\x9D\xCC\x99\xCD\x96\xCC\xB1\xCC\xB1\xCC\x99\xCC\xA6\xCC\xA5\xCD\x95\xCC\xB2\xCC\xA0\xCD\x99 within")
- set spell spelllang=en
- redraw
- redraw!
- bwipe!
- set nospell
-endfunc
-
-func Test_spellsuggest_too_deep()
- " This was incrementing "depth" over MAXWLEN.
- new
- norm s000G00ý000000000000
- sil norm ..vzG................vvzG0 v z=
- bwipe!
-endfunc
-
-func Test_spell_good_word_invalid()
- " This was adding a word with a 0x02 byte, which causes havoc.
- enew
- norm o0
- sil! norm rzzWs00/
- 2
- sil! norm VzGprzzW
- sil! norm z=
-
- bwipe!
-endfunc
-
-func Test_spell_good_word_slash()
- " This caused E1280.
- new
- norm afoo /
- 1
- norm zG
-
- bwipe!
-endfunc
-
-func LoadAffAndDic(aff_contents, dic_contents)
- throw 'skipped: Nvim does not support enc=latin1'
- set enc=latin1
- set spellfile=
- call writefile(a:aff_contents, "Xtest.aff")
- call writefile(a:dic_contents, "Xtest.dic")
- " Generate a .spl file from a .dic and .aff file.
- mkspell! Xtest Xtest
- " use that spell file
- set spl=Xtest.latin1.spl spell
-endfunc
-
-func ListWords()
- spelldump
- %yank
- quit
- return split(@", "\n")
-endfunc
-
-func TestGoodBadBase()
- exe '1;/^good:'
- normal 0f:]s
- let prevbad = ''
- let result = []
- while 1
- let [bad, a] = spellbadword()
- if bad == '' || bad == prevbad || bad == 'badend'
- break
- endif
- let prevbad = bad
- let lst = bad->spellsuggest(3)
- normal mm
-
- call add(result, [bad, lst])
- normal `m]s
- endwhile
- return result
-endfunc
-
-func RunGoodBad(good, bad, expected_words, expected_bad_words)
- bwipe!
- call setline(1, ["good: ", a:good, a:bad, " badend "])
- let words = ListWords()
- call assert_equal(a:expected_words, words[1:-1])
- let bad_words = TestGoodBadBase()
- call assert_equal(a:expected_bad_words, 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",
- \"",
- \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
- \"",
- \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF",
- \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?",
- \"",
- \"MIDWORD\t'-",
- \"",
- \"KEP =",
- \"RAR ?",
- \"BAD !",
- \"",
- \"PFX I N 1",
- \"PFX I 0 in .",
- \"",
- \"PFX O Y 1",
- \"PFX O 0 out .",
- \"",
- \"SFX S Y 2",
- \"SFX S 0 s [^s]",
- \"SFX S 0 es s",
- \"",
- \"SFX N N 3",
- \"SFX N 0 en [^n]",
- \"SFX N 0 nen n",
- \"SFX N 0 n .",
- \"",
- \"REP 3",
- \"REP g ch",
- \"REP ch g",
- \"REP svp s.v.p.",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF",
- \ ]
-let g:test_data_dic1 = [
- \"123456",
- \"test/NO",
- \"# comment",
- \"wrong",
- \"Comment",
- \"OK",
- \"uk",
- \"put/ISO",
- \"the end",
- \"deol",
- \"d\xE9\xF4r",
- \ ]
-let g:test_data_aff2 = [
- \"SET ISO8859-1",
- \"",
- \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
- \"",
- \"PFXPOSTPONE",
- \"",
- \"MIDWORD\t'-",
- \"",
- \"KEP =",
- \"RAR ?",
- \"BAD !",
- \"",
- \"PFX I N 1",
- \"PFX I 0 in .",
- \"",
- \"PFX O Y 1",
- \"PFX O 0 out [a-z]",
- \"",
- \"SFX S Y 2",
- \"SFX S 0 s [^s]",
- \"SFX S 0 es s",
- \"",
- \"SFX N N 3",
- \"SFX N 0 en [^n]",
- \"SFX N 0 nen n",
- \"SFX N 0 n .",
- \"",
- \"REP 3",
- \"REP g ch",
- \"REP ch g",
- \"REP svp s.v.p.",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF",
- \ ]
-let g:test_data_aff3 = [
- \"SET ISO8859-1",
- \"",
- \"COMPOUNDMIN 3",
- \"COMPOUNDRULE m*",
- \"NEEDCOMPOUND x",
- \ ]
-let g:test_data_dic3 = [
- \"1234",
- \"foo/m",
- \"bar/mx",
- \"m\xEF/m",
- \"la/mx",
- \ ]
-let g:test_data_aff4 = [
- \"SET ISO8859-1",
- \"",
- \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
- \"",
- \"COMPOUNDRULE m+",
- \"COMPOUNDRULE sm*e",
- \"COMPOUNDRULE sm+",
- \"COMPOUNDMIN 3",
- \"COMPOUNDWORDMAX 3",
- \"COMPOUNDFORBIDFLAG t",
- \"",
- \"COMPOUNDSYLMAX 5",
- \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF",
- \"",
- \"NEEDAFFIX x",
- \"",
- \"PFXPOSTPONE",
- \"",
- \"MIDWORD '-",
- \"",
- \"SFX q N 1",
- \"SFX q 0 -ok .",
- \"",
- \"SFX a Y 2",
- \"SFX a 0 s .",
- \"SFX a 0 ize/t .",
- \"",
- \"PFX p N 1",
- \"PFX p 0 pre .",
- \"",
- \"PFX P N 1",
- \"PFX P 0 nou .",
- \ ]
-let g:test_data_dic4 = [
- \"1234",
- \"word/mP",
- \"util/am",
- \"pro/xq",
- \"tomato/m",
- \"bork/mp",
- \"start/s",
- \"end/e",
- \ ]
-let g:test_data_aff5 = [
- \"SET ISO8859-1",
- \"",
- \"FLAG long",
- \"",
- \"NEEDAFFIX !!",
- \"",
- \"COMPOUNDRULE ssmm*ee",
- \"",
- \"NEEDCOMPOUND xx",
- \"COMPOUNDPERMITFLAG pp",
- \"",
- \"SFX 13 Y 1",
- \"SFX 13 0 bork .",
- \"",
- \"SFX a1 Y 1",
- \"SFX a1 0 a1 .",
- \"",
- \"SFX a\xE9 Y 1",
- \"SFX a\xE9 0 a\xE9 .",
- \"",
- \"PFX zz Y 1",
- \"PFX zz 0 pre/pp .",
- \"",
- \"PFX yy Y 1",
- \"PFX yy 0 nou .",
- \ ]
-let g:test_data_dic5 = [
- \"1234",
- \"foo/a1a\xE9!!",
- \"bar/zz13ee",
- \"start/ss",
- \"end/eeyy",
- \"middle/mmxx",
- \ ]
-let g:test_data_aff6 = [
- \"SET ISO8859-1",
- \"",
- \"FLAG caplong",
- \"",
- \"NEEDAFFIX A!",
- \"",
- \"COMPOUNDRULE sMm*Ee",
- \"",
- \"NEEDCOMPOUND Xx",
- \"",
- \"COMPOUNDPERMITFLAG p",
- \"",
- \"SFX N3 Y 1",
- \"SFX N3 0 bork .",
- \"",
- \"SFX A1 Y 1",
- \"SFX A1 0 a1 .",
- \"",
- \"SFX A\xE9 Y 1",
- \"SFX A\xE9 0 a\xE9 .",
- \"",
- \"PFX Zz Y 1",
- \"PFX Zz 0 pre/p .",
- \ ]
-let g:test_data_dic6 = [
- \"1234",
- \"mee/A1A\xE9A!",
- \"bar/ZzN3Ee",
- \"lead/s",
- \"end/Ee",
- \"middle/MmXx",
- \ ]
-let g:test_data_aff7 = [
- \"SET ISO8859-1",
- \"",
- \"FLAG num",
- \"",
- \"NEEDAFFIX 9999",
- \"",
- \"COMPOUNDRULE 2,77*123",
- \"",
- \"NEEDCOMPOUND 1",
- \"COMPOUNDPERMITFLAG 432",
- \"",
- \"SFX 61003 Y 1",
- \"SFX 61003 0 meat .",
- \"",
- \"SFX 0 Y 1",
- \"SFX 0 0 zero .",
- \"",
- \"SFX 391 Y 1",
- \"SFX 391 0 a1 .",
- \"",
- \"SFX 111 Y 1",
- \"SFX 111 0 a\xE9 .",
- \"",
- \"PFX 17 Y 1",
- \"PFX 17 0 pre/432 .",
- \ ]
-let g:test_data_dic7 = [
- \"1234",
- \"mee/0,391,111,9999",
- \"bar/17,61003,123",
- \"lead/2",
- \"tail/123",
- \"middle/77,1",
- \ ]
-let g:test_data_aff8 = [
- \"SET ISO8859-1",
- \"",
- \"NOSPLITSUGS",
- \ ]
-let g:test_data_dic8 = [
- \"1234",
- \"foo",
- \"bar",
- \"faabar",
- \ ]
-let g:test_data_aff9 = [
- \ ]
-let g:test_data_dic9 = [
- \"1234",
- \"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",
- \"",
- \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
- \"",
- \"MIDWORD\t'-",
- \"",
- \"KEP =",
- \"RAR ?",
- \"BAD !",
- \"",
- \"PFX I N 1",
- \"PFX I 0 in .",
- \"",
- \"PFX O Y 1",
- \"PFX O 0 out .",
- \"",
- \"SFX S Y 2",
- \"SFX S 0 s [^s]",
- \"SFX S 0 es s",
- \"",
- \"SFX N N 3",
- \"SFX N 0 en [^n]",
- \"SFX N 0 nen n",
- \"SFX N 0 n .",
- \"",
- \"REP 3",
- \"REP g ch",
- \"REP ch g",
- \"REP svp s.v.p.",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF",
- \"",
- \"SAL AH(AEIOUY)-^ *H",
- \"SAL AR(AEIOUY)-^ *R",
- \"SAL A(HR)^ *",
- \"SAL A^ *",
- \"SAL AH(AEIOUY)- H",
- \"SAL AR(AEIOUY)- R",
- \"SAL A(HR) _",
- \"SAL \xC0^ *",
- \"SAL \xC5^ *",
- \"SAL BB- _",
- \"SAL B B",
- \"SAL CQ- _",
- \"SAL CIA X",
- \"SAL CH X",
- \"SAL C(EIY)- S",
- \"SAL CK K",
- \"SAL COUGH^ KF",
- \"SAL CC< C",
- \"SAL C K",
- \"SAL DG(EIY) K",
- \"SAL DD- _",
- \"SAL D T",
- \"SAL \xC9< E",
- \"SAL EH(AEIOUY)-^ *H",
- \"SAL ER(AEIOUY)-^ *R",
- \"SAL E(HR)^ *",
- \"SAL ENOUGH^$ *NF",
- \"SAL E^ *",
- \"SAL EH(AEIOUY)- H",
- \"SAL ER(AEIOUY)- R",
- \"SAL E(HR) _",
- \"SAL FF- _",
- \"SAL F F",
- \"SAL GN^ N",
- \"SAL GN$ N",
- \"SAL GNS$ NS",
- \"SAL GNED$ N",
- \"SAL GH(AEIOUY)- K",
- \"SAL GH _",
- \"SAL GG9 K",
- \"SAL G K",
- \"SAL H H",
- \"SAL IH(AEIOUY)-^ *H",
- \"SAL IR(AEIOUY)-^ *R",
- \"SAL I(HR)^ *",
- \"SAL I^ *",
- \"SAL ING6 N",
- \"SAL IH(AEIOUY)- H",
- \"SAL IR(AEIOUY)- R",
- \"SAL I(HR) _",
- \"SAL J K",
- \"SAL KN^ N",
- \"SAL KK- _",
- \"SAL K K",
- \"SAL LAUGH^ LF",
- \"SAL LL- _",
- \"SAL L L",
- \"SAL MB$ M",
- \"SAL MM M",
- \"SAL M M",
- \"SAL NN- _",
- \"SAL N N",
- \"SAL OH(AEIOUY)-^ *H",
- \"SAL OR(AEIOUY)-^ *R",
- \"SAL O(HR)^ *",
- \"SAL O^ *",
- \"SAL OH(AEIOUY)- H",
- \"SAL OR(AEIOUY)- R",
- \"SAL O(HR) _",
- \"SAL PH F",
- \"SAL PN^ N",
- \"SAL PP- _",
- \"SAL P P",
- \"SAL Q K",
- \"SAL RH^ R",
- \"SAL ROUGH^ RF",
- \"SAL RR- _",
- \"SAL R R",
- \"SAL SCH(EOU)- SK",
- \"SAL SC(IEY)- S",
- \"SAL SH X",
- \"SAL SI(AO)- X",
- \"SAL SS- _",
- \"SAL S S",
- \"SAL TI(AO)- X",
- \"SAL TH @",
- \"SAL TCH-- _",
- \"SAL TOUGH^ TF",
- \"SAL TT- _",
- \"SAL T T",
- \"SAL UH(AEIOUY)-^ *H",
- \"SAL UR(AEIOUY)-^ *R",
- \"SAL U(HR)^ *",
- \"SAL U^ *",
- \"SAL UH(AEIOUY)- H",
- \"SAL UR(AEIOUY)- R",
- \"SAL U(HR) _",
- \"SAL V^ W",
- \"SAL V F",
- \"SAL WR^ R",
- \"SAL WH^ W",
- \"SAL W(AEIOU)- W",
- \"SAL X^ S",
- \"SAL X KS",
- \"SAL Y(AEIOU)- Y",
- \"SAL ZZ- _",
- \"SAL Z S",
- \ ]
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim
deleted file mode 100644
index 7c588d736a..0000000000
--- a/src/nvim/testdir/test_spell_utf8.vim
+++ /dev/null
@@ -1,832 +0,0 @@
-" Test for spell checking with 'encoding' set to utf-8
-
-source check.vim
-CheckFeature spell
-
-scriptencoding utf-8
-
-func TearDown()
- set nospell
- call delete('Xtest.aff')
- call delete('Xtest.dic')
- call delete('Xtest.utf-8.add')
- call delete('Xtest.utf-8.add.spl')
- call delete('Xtest.utf-8.spl')
- call delete('Xtest.utf-8.sug')
- " set 'encoding' to clear the word list
- set encoding=utf-8
-endfunc
-
-let g:test_data_aff1 = [
- \"SET ISO8859-1",
- \"TRY esianrtolcdugmphbyfvkwjkqxz-ëéèêïîäàâöüû'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
- \"",
- \"FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ",
- \"LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ",
- \"UPP ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞßÿ",
- \"",
- \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF",
- \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?",
- \"",
- \"MIDWORD\t'-",
- \"",
- \"KEP =",
- \"RAR ?",
- \"BAD !",
- \"",
- \"PFX I N 1",
- \"PFX I 0 in .",
- \"",
- \"PFX O Y 1",
- \"PFX O 0 out .",
- \"",
- \"SFX S Y 2",
- \"SFX S 0 s [^s]",
- \"SFX S 0 es s",
- \"",
- \"SFX N N 3",
- \"SFX N 0 en [^n]",
- \"SFX N 0 nen n",
- \"SFX N 0 n .",
- \"",
- \"REP 3",
- \"REP g ch",
- \"REP ch g",
- \"REP svp s.v.p.",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF"
- \ ]
-let g:test_data_dic1 = [
- \"123456",
- \"test/NO",
- \"# comment",
- \"wrong",
- \"Comment",
- \"OK",
- \"uk",
- \"put/ISO",
- \"the end",
- \"deol",
- \"d\xE9\xF4r",
- \ ]
-let g:test_data_aff2 = [
- \"SET ISO8859-1",
- \"",
- \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
- \"",
- \"PFXPOSTPONE",
- \"",
- \"MIDWORD\t'-",
- \"",
- \"KEP =",
- \"RAR ?",
- \"BAD !",
- \"",
- \"PFX I N 1",
- \"PFX I 0 in .",
- \"",
- \"PFX O Y 1",
- \"PFX O 0 out [a-z]",
- \"",
- \"SFX S Y 2",
- \"SFX S 0 s [^s]",
- \"SFX S 0 es s",
- \"",
- \"SFX N N 3",
- \"SFX N 0 en [^n]",
- \"SFX N 0 nen n",
- \"SFX N 0 n .",
- \"",
- \"REP 3",
- \"REP g ch",
- \"REP ch g",
- \"REP svp s.v.p.",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF",
- \ ]
-let g:test_data_aff3 = [
- \"SET ISO8859-1",
- \"",
- \"COMPOUNDMIN 3",
- \"COMPOUNDRULE m*",
- \"NEEDCOMPOUND x",
- \ ]
-let g:test_data_dic3 = [
- \"1234",
- \"foo/m",
- \"bar/mx",
- \"m\xEF/m",
- \"la/mx",
- \ ]
-let g:test_data_aff4 = [
- \"SET ISO8859-1",
- \"",
- \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
- \"",
- \"COMPOUNDRULE m+",
- \"COMPOUNDRULE sm*e",
- \"COMPOUNDRULE sm+",
- \"COMPOUNDMIN 3",
- \"COMPOUNDWORDMAX 3",
- \"COMPOUNDFORBIDFLAG t",
- \"",
- \"COMPOUNDSYLMAX 5",
- \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF",
- \"",
- \"NEEDAFFIX x",
- \"",
- \"PFXPOSTPONE",
- \"",
- \"MIDWORD '-",
- \"",
- \"SFX q N 1",
- \"SFX q 0 -ok .",
- \"",
- \"SFX a Y 2",
- \"SFX a 0 s .",
- \"SFX a 0 ize/t .",
- \"",
- \"PFX p N 1",
- \"PFX p 0 pre .",
- \"",
- \"PFX P N 1",
- \"PFX P 0 nou .",
- \ ]
-let g:test_data_dic4 = [
- \"1234",
- \"word/mP",
- \"util/am",
- \"pro/xq",
- \"tomato/m",
- \"bork/mp",
- \"start/s",
- \"end/e",
- \ ]
-let g:test_data_aff5 = [
- \"SET ISO8859-1",
- \"",
- \"FLAG long",
- \"",
- \"NEEDAFFIX !!",
- \"",
- \"COMPOUNDRULE ssmm*ee",
- \"",
- \"NEEDCOMPOUND xx",
- \"COMPOUNDPERMITFLAG pp",
- \"",
- \"SFX 13 Y 1",
- \"SFX 13 0 bork .",
- \"",
- \"SFX a1 Y 1",
- \"SFX a1 0 a1 .",
- \"",
- \"SFX a\xE9 Y 1",
- \"SFX a\xE9 0 a\xE9 .",
- \"",
- \"PFX zz Y 1",
- \"PFX zz 0 pre/pp .",
- \"",
- \"PFX yy Y 1",
- \"PFX yy 0 nou .",
- \ ]
-let g:test_data_dic5 = [
- \"1234",
- \"foo/a1a\xE9!!",
- \"bar/zz13ee",
- \"start/ss",
- \"end/eeyy",
- \"middle/mmxx",
- \ ]
-let g:test_data_aff6 = [
- \"SET ISO8859-1",
- \"",
- \"FLAG caplong",
- \"",
- \"NEEDAFFIX A!",
- \"",
- \"COMPOUNDRULE sMm*Ee",
- \"",
- \"NEEDCOMPOUND Xx",
- \"",
- \"COMPOUNDPERMITFLAG p",
- \"",
- \"SFX N3 Y 1",
- \"SFX N3 0 bork .",
- \"",
- \"SFX A1 Y 1",
- \"SFX A1 0 a1 .",
- \"",
- \"SFX A\xE9 Y 1",
- \"SFX A\xE9 0 a\xE9 .",
- \"",
- \"PFX Zz Y 1",
- \"PFX Zz 0 pre/p .",
- \ ]
-let g:test_data_dic6 = [
- \"1234",
- \"mee/A1A\xE9A!",
- \"bar/ZzN3Ee",
- \"lead/s",
- \"end/Ee",
- \"middle/MmXx",
- \ ]
-let g:test_data_aff7 = [
- \"SET ISO8859-1",
- \"",
- \"FLAG num",
- \"",
- \"NEEDAFFIX 9999",
- \"",
- \"COMPOUNDRULE 2,77*123",
- \"",
- \"NEEDCOMPOUND 1",
- \"COMPOUNDPERMITFLAG 432",
- \"",
- \"SFX 61003 Y 1",
- \"SFX 61003 0 meat .",
- \"",
- \"SFX 0 Y 1",
- \"SFX 0 0 zero .",
- \"",
- \"SFX 391 Y 1",
- \"SFX 391 0 a1 .",
- \"",
- \"SFX 111 Y 1",
- \"SFX 111 0 a\xE9 .",
- \"",
- \"PFX 17 Y 1",
- \"PFX 17 0 pre/432 .",
- \ ]
-let g:test_data_dic7 = [
- \"1234",
- \"mee/0,391,111,9999",
- \"bar/17,61003,123",
- \"lead/2",
- \"tail/123",
- \"middle/77,1",
- \ ]
-let g:test_data_aff8 = [
- \"SET ISO8859-1",
- \"",
- \"NOSPLITSUGS",
- \ ]
-let g:test_data_dic8 = [
- \"1234",
- \"foo",
- \"bar",
- \"faabar",
- \ ]
-let g:test_data_aff9 = [
- \ ]
-let g:test_data_dic9 = [
- \"1234",
- \"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",
- \"",
- \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
- \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
- \"",
- \"MIDWORD\t'-",
- \"",
- \"KEP =",
- \"RAR ?",
- \"BAD !",
- \"",
- \"PFX I N 1",
- \"PFX I 0 in .",
- \"",
- \"PFX O Y 1",
- \"PFX O 0 out .",
- \"",
- \"SFX S Y 2",
- \"SFX S 0 s [^s]",
- \"SFX S 0 es s",
- \"",
- \"SFX N N 3",
- \"SFX N 0 en [^n]",
- \"SFX N 0 nen n",
- \"SFX N 0 n .",
- \"",
- \"REP 3",
- \"REP g ch",
- \"REP ch g",
- \"REP svp s.v.p.",
- \"",
- \"MAP 9",
- \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
- \"MAP e\xE8\xE9\xEA\xEB",
- \"MAP i\xEC\xED\xEE\xEF",
- \"MAP o\xF2\xF3\xF4\xF5\xF6",
- \"MAP u\xF9\xFA\xFB\xFC",
- \"MAP n\xF1",
- \"MAP c\xE7",
- \"MAP y\xFF\xFD",
- \"MAP s\xDF",
- \"",
- \"SAL AH(AEIOUY)-^ *H",
- \"SAL AR(AEIOUY)-^ *R",
- \"SAL A(HR)^ *",
- \"SAL A^ *",
- \"SAL AH(AEIOUY)- H",
- \"SAL AR(AEIOUY)- R",
- \"SAL A(HR) _",
- \"SAL \xC0^ *",
- \"SAL \xC5^ *",
- \"SAL BB- _",
- \"SAL B B",
- \"SAL CQ- _",
- \"SAL CIA X",
- \"SAL CH X",
- \"SAL C(EIY)- S",
- \"SAL CK K",
- \"SAL COUGH^ KF",
- \"SAL CC< C",
- \"SAL C K",
- \"SAL DG(EIY) K",
- \"SAL DD- _",
- \"SAL D T",
- \"SAL \xC9< E",
- \"SAL EH(AEIOUY)-^ *H",
- \"SAL ER(AEIOUY)-^ *R",
- \"SAL E(HR)^ *",
- \"SAL ENOUGH^$ *NF",
- \"SAL E^ *",
- \"SAL EH(AEIOUY)- H",
- \"SAL ER(AEIOUY)- R",
- \"SAL E(HR) _",
- \"SAL FF- _",
- \"SAL F F",
- \"SAL GN^ N",
- \"SAL GN$ N",
- \"SAL GNS$ NS",
- \"SAL GNED$ N",
- \"SAL GH(AEIOUY)- K",
- \"SAL GH _",
- \"SAL GG9 K",
- \"SAL G K",
- \"SAL H H",
- \"SAL IH(AEIOUY)-^ *H",
- \"SAL IR(AEIOUY)-^ *R",
- \"SAL I(HR)^ *",
- \"SAL I^ *",
- \"SAL ING6 N",
- \"SAL IH(AEIOUY)- H",
- \"SAL IR(AEIOUY)- R",
- \"SAL I(HR) _",
- \"SAL J K",
- \"SAL KN^ N",
- \"SAL KK- _",
- \"SAL K K",
- \"SAL LAUGH^ LF",
- \"SAL LL- _",
- \"SAL L L",
- \"SAL MB$ M",
- \"SAL MM M",
- \"SAL M M",
- \"SAL NN- _",
- \"SAL N N",
- \"SAL OH(AEIOUY)-^ *H",
- \"SAL OR(AEIOUY)-^ *R",
- \"SAL O(HR)^ *",
- \"SAL O^ *",
- \"SAL OH(AEIOUY)- H",
- \"SAL OR(AEIOUY)- R",
- \"SAL O(HR) _",
- \"SAL PH F",
- \"SAL PN^ N",
- \"SAL PP- _",
- \"SAL P P",
- \"SAL Q K",
- \"SAL RH^ R",
- \"SAL ROUGH^ RF",
- \"SAL RR- _",
- \"SAL R R",
- \"SAL SCH(EOU)- SK",
- \"SAL SC(IEY)- S",
- \"SAL SH X",
- \"SAL SI(AO)- X",
- \"SAL SS- _",
- \"SAL S S",
- \"SAL TI(AO)- X",
- \"SAL TH @",
- \"SAL TCH-- _",
- \"SAL TOUGH^ TF",
- \"SAL TT- _",
- \"SAL T T",
- \"SAL UH(AEIOUY)-^ *H",
- \"SAL UR(AEIOUY)-^ *R",
- \"SAL U(HR)^ *",
- \"SAL U^ *",
- \"SAL UH(AEIOUY)- H",
- \"SAL UR(AEIOUY)- R",
- \"SAL U(HR) _",
- \"SAL V^ W",
- \"SAL V F",
- \"SAL WR^ R",
- \"SAL WH^ W",
- \"SAL W(AEIOU)- W",
- \"SAL X^ S",
- \"SAL X KS",
- \"SAL Y(AEIOU)- Y",
- \"SAL ZZ- _",
- \"SAL Z S",
- \ ]
-
-func LoadAffAndDic(aff_contents, dic_contents)
- set spellfile=
- call writefile(a:aff_contents, "Xtest.aff")
- call writefile(a:dic_contents, "Xtest.dic")
- " Generate a .spl file from a .dic and .aff file.
- mkspell! Xtest Xtest
- " use that spell file
- set spl=Xtest.utf-8.spl spell
-endfunc
-
-func ListWords()
- spelldump
- %yank
- quit
- return split(@", "\n")
-endfunc
-
-func TestGoodBadBase()
- exe '1;/^good:'
- normal 0f:]s
- let prevbad = ''
- let result = []
- while 1
- let [bad, a] = spellbadword()
- if bad == '' || bad == prevbad || bad == 'badend'
- break
- endif
- let prevbad = bad
- let lst = bad->spellsuggest(3)
- normal mm
-
- call add(result, [bad, lst])
- normal `m]s
- endwhile
- return result
-endfunc
-
-func RunGoodBad(good, bad, expected_words, expected_bad_words)
- %bwipe!
- call setline(1, ['', "good: ", a:good, a:bad, " badend "])
- let words = ListWords()
- call assert_equal(a:expected_words, words[1:-1])
- let bad_words = TestGoodBadBase()
- call assert_equal(a:expected_bad_words, bad_words)
- %bwipe!
-endfunc
-
-func Test_spell_basic()
- call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1)
- call RunGoodBad("wrong OK puts. Test the end",
- \ "bad: inputs comment ok Ok. test d\u00E9\u00F4l end the",
- \["Comment", "deol", "d\u00E9\u00F4r", "input", "OK", "output", "outputs", "outtest", "put", "puts",
- \ "test", "testen", "testn", "the end", "uk", "wrong"],
- \[
- \ ["bad", ["put", "uk", "OK"]],
- \ ["inputs", ["input", "puts", "outputs"]],
- \ ["comment", ["Comment", "outtest", "the end"]],
- \ ["ok", ["OK", "uk", "put"]],
- \ ["Ok", ["OK", "Uk", "Put"]],
- \ ["test", ["Test", "testn", "testen"]],
- \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]],
- \ ["end", ["put", "uk", "test"]],
- \ ["the", ["put", "uk", "test"]],
- \ ]
- \ )
-
- call assert_equal("gebletegek", soundfold('goobledygoook'))
- call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold())
- call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale'))
-endfunc
-
-" Postponed prefixes
-func Test_spell_prefixes()
- call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1)
- call RunGoodBad("puts",
- \ "bad: inputs comment ok Ok end the. test d\u00E9\u00F4l",
- \ ["Comment", "deol", "d\u00E9\u00F4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"],
- \ [
- \ ["bad", ["put", "uk", "OK"]],
- \ ["inputs", ["input", "puts", "outputs"]],
- \ ["comment", ["Comment"]],
- \ ["ok", ["OK", "uk", "put"]],
- \ ["Ok", ["OK", "Uk", "Put"]],
- \ ["end", ["put", "uk", "deol"]],
- \ ["the", ["put", "uk", "test"]],
- \ ["test", ["Test", "testn", "testen"]],
- \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]],
- \ ])
-endfunc
-
-"Compound words
-func Test_spell_compound()
- call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3)
- call RunGoodBad("foo m\u00EF foobar foofoobar barfoo barbarfoo",
- \ "bad: bar la foom\u00EF barm\u00EF m\u00EFfoo m\u00EFbar m\u00EFm\u00EF lala m\u00EFla lam\u00EF foola labar",
- \ ["foo", "m\u00EF"],
- \ [
- \ ["bad", ["foo", "m\u00EF"]],
- \ ["bar", ["barfoo", "foobar", "foo"]],
- \ ["la", ["m\u00EF", "foo"]],
- \ ["foom\u00EF", ["foo m\u00EF", "foo", "foofoo"]],
- \ ["barm\u00EF", ["barfoo", "m\u00EF", "barbar"]],
- \ ["m\u00EFfoo", ["m\u00EF foo", "foo", "foofoo"]],
- \ ["m\u00EFbar", ["foobar", "barbar", "m\u00EF"]],
- \ ["m\u00EFm\u00EF", ["m\u00EF m\u00EF", "m\u00EF"]],
- \ ["lala", []],
- \ ["m\u00EFla", ["m\u00EF", "m\u00EF m\u00EF"]],
- \ ["lam\u00EF", ["m\u00EF", "m\u00EF m\u00EF"]],
- \ ["foola", ["foo", "foobar", "foofoo"]],
- \ ["labar", ["barbar", "foobar"]],
- \ ])
-
- call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4)
- call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword",
- \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword",
- \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"],
- \ [
- \ ["bad", ["end", "bork", "word"]],
- \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]],
- \ ["pro", ["bork", "word", "end"]],
- \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]],
- \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]],
- \ ["endstart", ["end start", "start"]],
- \ ["endend", ["end end", "end"]],
- \ ["startstart", ["start start"]],
- \ ["wordend", ["word end", "word", "wordword"]],
- \ ["wordstart", ["word start", "bork start"]],
- \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]],
- \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]],
- \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]],
- \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]],
- \ ["utilsbork", ["utilbork", "utils bork", "util bork"]],
- \ ["startnouword", ["start nouword", "startword", "startborkword"]],
- \ ])
-
-endfunc
-
-" Test affix flags with two characters
-func Test_spell_affix()
- CheckNotMSWindows " FIXME: Why does this fail with MSVC?
- call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5)
- call RunGoodBad("fooa1 fooa\u00E9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend",
- \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend",
- \ ["bar", "barbork", "end", "fooa1", "fooa\u00E9", "nouend", "prebar", "prebarbork", "start"],
- \ [
- \ ["bad", ["bar", "end", "fooa1"]],
- \ ["foo", ["fooa1", "bar", "end"]],
- \ ["fooa2", ["fooa1", "fooa\u00E9", "bar"]],
- \ ["prabar", ["prebar", "bar", "bar bar"]],
- \ ["probarbirk", ["prebarbork"]],
- \ ["middle", []],
- \ ["startmiddle", ["startmiddleend", "startmiddlebar"]],
- \ ["middleend", []],
- \ ["endstart", ["end start", "start"]],
- \ ["startprobar", ["startprebar", "start prebar", "startbar"]],
- \ ["startnouend", ["start nouend", "startend"]],
- \ ])
-
- call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6)
- call RunGoodBad("meea1 meea\u00E9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend",
- \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar",
- \ ["bar", "barbork", "end", "lead", "meea1", "meea\u00E9", "prebar", "prebarbork"],
- \ [
- \ ["bad", ["bar", "end", "lead"]],
- \ ["mee", ["meea1", "bar", "end"]],
- \ ["meea2", ["meea1", "meea\u00E9", "lead"]],
- \ ["prabar", ["prebar", "bar", "leadbar"]],
- \ ["probarbirk", ["prebarbork"]],
- \ ["middle", []],
- \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]],
- \ ["middleend", []],
- \ ["endlead", ["end lead", "lead", "end end"]],
- \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
- \ ])
-
- call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
- call RunGoodBad("meea1 meezero meea\u00E9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
- \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar",
- \ ["bar", "barmeat", "lead", "meea1", "meea\u00E9", "meezero", "prebar", "prebarmeat", "tail"],
- \ [
- \ ["bad", ["bar", "lead", "tail"]],
- \ ["mee", ["meea1", "bar", "lead"]],
- \ ["meea2", ["meea1", "meea\u00E9", "lead"]],
- \ ["prabar", ["prebar", "bar", "leadbar"]],
- \ ["probarmaat", ["prebarmeat"]],
- \ ["middle", []],
- \ ["leadmiddle", ["leadmiddlebar"]],
- \ ["middletail", []],
- \ ["taillead", ["tail lead", "tail"]],
- \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
- \ ])
-endfunc
-
-func Test_spell_NOSLITSUGS()
- call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8)
- call RunGoodBad("foo bar faabar", "bad: foobar barfoo",
- \ ["bar", "faabar", "foo"],
- \ [
- \ ["bad", ["bar", "foo"]],
- \ ["foobar", ["faabar", "foo bar", "bar"]],
- \ ["barfoo", ["bar foo", "bar", "foo"]],
- \ ])
-endfunc
-
-" Numbers
-func Test_spell_Numbers()
- call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9)
- call RunGoodBad("0b1011 0777 1234 0x01ff", "",
- \ ["bar", "foo"],
- \ [
- \ ])
-endfunc
-
-" Affix flags
-func Test_spell_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
- let [str, a] = spellbadword()
- return str
-endfunc
-
-function SecondSpellWord()
- normal `m]s
- let [str, a] = spellbadword()
- return str
-endfunc
-
-" Test with SAL instead of SOFO items; test automatic reloading
-func Test_spell_sal_and_addition()
- set spellfile=
- call writefile(g:test_data_dic1, "Xtest.dic")
- call writefile(g:test_data_aff_sal, "Xtest.aff")
- mkspell! Xtest Xtest
- set spl=Xtest.utf-8.spl spell
- call assert_equal('kbltykk', soundfold('goobledygoook'))
- call assert_equal('kprnfn', soundfold('kóopërÿnôven'))
- call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale'))
-
- "also use an addition file
- call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.utf-8.add")
- mkspell! Xtest.utf-8.add.spl Xtest.utf-8.add
-
- bwipe!
- call setline(1, ["start: elequint test elekwint test elekwent asdf"])
-
- set spellfile=Xtest.utf-8.add
- call assert_equal("elekwent", FirstSpellWord())
-
- set spl=Xtest_us.utf-8.spl
- call assert_equal("elequint", FirstSpellWord())
- call assert_equal("elekwint", SecondSpellWord())
-
- set spl=Xtest_gb.utf-8.spl
- call assert_equal("elekwint", FirstSpellWord())
- call assert_equal("elekwent", SecondSpellWord())
-
- set spl=Xtest_nz.utf-8.spl
- call assert_equal("elequint", FirstSpellWord())
- call assert_equal("elekwent", SecondSpellWord())
-
- set spl=Xtest_ca.utf-8.spl
- call assert_equal("elequint", FirstSpellWord())
- call assert_equal("elekwint", SecondSpellWord())
-
- bwipe!
- set spellfile=
- set spl&
-endfunc
-
-func Test_spellfile_value()
- set spellfile=Xdir/Xtest.utf-8.add
- set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add
- set spellfile=
-endfunc
-
-func Test_no_crash_with_weird_text()
- new
- let lines =<< trim END
- r<sfile>
- €
-
-
- €
- END
- call setline(1, lines)
- try
- exe "%norm \<C-v>ez=>\<C-v>wzG"
- catch /E1280:/
- let caught = 'yes'
- endtry
- call assert_equal('yes', caught)
-
- bwipe!
-endfunc
-
-" Invalid bytes may cause trouble when creating the word list.
-func Test_check_for_valid_word()
- call assert_fails("spellgood! 0\xac", 'E1280:')
-endfunc
-
-" This was going over the end of the word
-func Test_word_index()
- new
- norm R0
- spellgood! fl0
- sil norm z=
-
- bwipe!
- call delete('Xtmpfile')
-endfunc
-
-func Test_check_empty_line()
- " This was using freed memory
- enew
- spellgood! fl
- norm z=
- norm yy
- sil! norm P]svc
- norm P]s
-
- bwipe!
-endfunc
-
-func Test_spell_suggest_too_long()
- " this was creating a word longer than MAXWLEN
- new
- call setline(1, 'a' .. repeat("\u0333", 150))
- norm! z=
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim
deleted file mode 100644
index dbffbafed9..0000000000
--- a/src/nvim/testdir/test_spellfile.vim
+++ /dev/null
@@ -1,1066 +0,0 @@
-" Test for commands that operate on the spellfile.
-
-source shared.vim
-source check.vim
-
-CheckFeature spell
-CheckFeature syntax
-
-func Test_spell_normal()
- new
- call append(0, ['1 good', '2 goood', '3 goood'])
- set spell spellfile=./Xspellfile.add spelllang=en
- let oldlang=v:lang
- lang C
-
- " Test for zg
- 1
- norm! ]s
- call assert_equal('2 goood', getline('.'))
- norm! zg
- 1
- let a=execute('unsilent :norm! ]s')
- call assert_equal('1 good', getline('.'))
- call assert_equal('search hit BOTTOM, continuing at TOP', a[1:])
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('goood', cnt[0])
-
- " zg should fail in operator-pending mode
- call assert_beeps('norm! czg')
-
- " zg fails in visual mode when not able to get the visual text
- call assert_beeps('norm! ggVjzg')
- norm! V
-
- " zg fails for a non-identifier word
- call append(line('$'), '###')
- call assert_fails('norm! Gzg', 'E349:')
- $d
-
- " Test for zw
- 2
- norm! $zw
- 1
- norm! ]s
- call assert_equal('2 goood', getline('.'))
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('#oood', cnt[0])
- call assert_equal('goood/!', cnt[1])
-
- " Test for :spellrare
- spellrare rare
- let cnt=readfile('./Xspellfile.add')
- call assert_equal(['#oood', 'goood/!', 'rare/?'], cnt)
-
- " Make sure :spellundo works for rare words.
- spellundo rare
- let cnt=readfile('./Xspellfile.add')
- call assert_equal(['#oood', 'goood/!', '#are/?'], cnt)
-
- " Test for zg in visual mode
- let a=execute('unsilent :norm! V$zg')
- call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
- 1
- norm! ]s
- call assert_equal('3 goood', getline('.'))
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('2 goood', cnt[3])
- " Remove "2 good" from spellfile
- 2
- let a=execute('unsilent norm! V$zw')
- call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('2 goood/!', cnt[4])
-
- " Test for zG
- let a=execute('unsilent norm! V$zG')
- call assert_match("Word '2 goood' added to .*", a)
- let fname=matchstr(a, 'to\s\+\zs\f\+$')
- let cnt=readfile(fname)
- call assert_equal('2 goood', cnt[0])
-
- " Test for zW
- let a=execute('unsilent norm! V$zW')
- call assert_match("Word '2 goood' added to .*", a)
- let cnt=readfile(fname)
- call assert_equal('# goood', cnt[0])
- call assert_equal('2 goood/!', cnt[1])
-
- " Test for zuW
- let a=execute('unsilent norm! V$zuW')
- call assert_match("Word '2 goood' removed from .*", a)
- let cnt=readfile(fname)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
-
- " Test for zuG
- let a=execute('unsilent norm! $zG')
- call assert_match("Word 'goood' added to .*", a)
- let cnt=readfile(fname)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
- call assert_equal('goood', cnt[2])
- let a=execute('unsilent norm! $zuG')
- let cnt=readfile(fname)
- call assert_match("Word 'goood' removed from .*", a)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
- call assert_equal('#oood', cnt[2])
- " word not found in wordlist
- let a=execute('unsilent norm! V$zuG')
- let cnt=readfile(fname)
- call assert_match("", a)
- call assert_equal('# goood', cnt[0])
- call assert_equal('# goood/!', cnt[1])
- call assert_equal('#oood', cnt[2])
-
- " Test for zug
- call delete('./Xspellfile.add')
- 2
- let a=execute('unsilent norm! $zg')
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('goood', cnt[0])
- let a=execute('unsilent norm! $zug')
- call assert_match("Word 'goood' removed from \./Xspellfile.add", a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('#oood', cnt[0])
- " word not in wordlist
- let a=execute('unsilent norm! V$zug')
- call assert_match('', a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('#oood', cnt[0])
-
- " Test for zuw
- call delete('./Xspellfile.add')
- 2
- let a=execute('unsilent norm! Vzw')
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('2 goood/!', cnt[0])
- let a=execute('unsilent norm! Vzuw')
- call assert_match("Word '2 goood' removed from \./Xspellfile.add", a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('# goood/!', cnt[0])
- " word not in wordlist
- let a=execute('unsilent norm! $zug')
- call assert_match('', a)
- let cnt=readfile('./Xspellfile.add')
- call assert_equal('# goood/!', cnt[0])
-
- " add second entry to spellfile setting
- set spellfile=./Xspellfile.add,./Xspellfile2.add
- call delete('./Xspellfile.add')
- 2
- let a=execute('unsilent norm! $2zg')
- let cnt=readfile('./Xspellfile2.add')
- call assert_match("Word 'goood' added to ./Xspellfile2.add", a)
- call assert_equal('goood', cnt[0])
-
- " Test for :spellgood!
- let temp = execute(':spe!0/0')
- call assert_match('Invalid region', temp)
- let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0')
- call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile))
-
- " Test for :spellrare!
- :spellrare! raare
- call assert_equal(['# goood', '# goood/!', '#oood', '0/0', 'raare/?'], readfile(spellfile))
- call delete(spellfile)
-
- " clean up
- exe "lang" oldlang
- call delete("./Xspellfile.add")
- call delete("./Xspellfile2.add")
- call delete("./Xspellfile.add.spl")
- call delete("./Xspellfile2.add.spl")
-
- " zux -> no-op
- 2
- norm! $zux
- call assert_equal([], glob('Xspellfile.add',0,1))
- call assert_equal([], glob('Xspellfile2.add',0,1))
-
- set spellfile= spell& spelllang&
- bw!
-endfunc
-
-" Spell file content test. Write 'content' to the spell file prefixed by the
-" spell file header and then enable spell checking. If 'emsg' is not empty,
-" then check for error.
-func Spellfile_Test(content, emsg)
- let splfile = './Xtest/spell/Xtest.utf-8.spl'
- " Add the spell file header and version (VIMspell2)
- let v = 0z56494D7370656C6C32 + a:content
- call writefile(v, splfile, 'b')
- set runtimepath=./Xtest
- set spelllang=Xtest
- if a:emsg != ''
- call assert_fails('set spell', a:emsg)
- else
- " FIXME: With some invalid spellfile contents, there are no error
- " messages. So don't know how to check for the test result.
- set spell
- endif
- set nospell spelllang& rtp&
-endfunc
-
-" Test for spell file format errors.
-" The spell file format is described in spellfile.c
-func Test_spellfile_format_error()
- let save_rtp = &rtp
- call mkdir('Xtest/spell', 'p')
- let splfile = './Xtest/spell/Xtest.utf-8.spl'
-
- " empty spell file
- call writefile([], splfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- call assert_fails('set spell', 'E757:')
- set nospell spelllang&
-
- " invalid file ID
- call writefile(0z56494D, splfile, 'b')
- set runtimepath=./Xtest
- set spelllang=Xtest
- call assert_fails('set spell', 'E757:')
- set nospell spelllang&
-
- " missing version number
- call writefile(0z56494D7370656C6C, splfile, 'b')
- set runtimepath=./Xtest
- set spelllang=Xtest
- call assert_fails('set spell', 'E771:')
- set nospell spelllang&
-
- " invalid version number
- call writefile(0z56494D7370656C6C7A, splfile, 'b')
- set runtimepath=./Xtest
- set spelllang=Xtest
- call assert_fails('set spell', 'E772:')
- set nospell spelllang&
-
- " no sections
- call Spellfile_Test(0z, 'E758:')
-
- " missing section length
- call Spellfile_Test(0z00, 'E758:')
-
- " unsupported required section
- call Spellfile_Test(0z7A0100000004, 'E770:')
-
- " unsupported not-required section
- call Spellfile_Test(0z7A0000000004, 'E758:')
-
- " SN_REGION: invalid number of region names
- call Spellfile_Test(0z0000000000FF, 'E759:')
-
- " SN_CHARFLAGS: missing <charflagslen> length
- call Spellfile_Test(0z010000000004, 'E758:')
-
- " SN_CHARFLAGS: invalid <charflagslen> length
- call Spellfile_Test(0z0100000000010201, '')
-
- " SN_CHARFLAGS: charflagslen == 0 and folcharslen != 0
- call Spellfile_Test(0z01000000000400000101, 'E759:')
-
- " SN_CHARFLAGS: missing <folcharslen> length
- call Spellfile_Test(0z01000000000100, 'E758:')
-
- " SN_PREFCOND: invalid prefcondcnt
- call Spellfile_Test(0z03000000000100, 'E759:')
-
- " SN_PREFCOND: invalid condlen
- call Spellfile_Test(0z0300000000020001, 'E759:')
-
- " SN_REP: invalid repcount
- call Spellfile_Test(0z04000000000100, 'E758:')
-
- " SN_REP: missing rep
- call Spellfile_Test(0z0400000000020004, 'E758:')
-
- " SN_REP: zero repfromlen
- call Spellfile_Test(0z040000000003000100, 'E759:')
-
- " SN_REP: invalid reptolen
- call Spellfile_Test(0z0400000000050001014101, '')
-
- " SN_REP: zero reptolen
- call Spellfile_Test(0z0400000000050001014100, 'E759:')
-
- " SN_SAL: missing salcount
- call Spellfile_Test(0z05000000000102, 'E758:')
-
- " SN_SAL: missing salfromlen
- call Spellfile_Test(0z050000000003080001, 'E758:')
-
- " SN_SAL: missing saltolen
- call Spellfile_Test(0z0500000000050400010161, 'E758:')
-
- " SN_WORDS: non-NUL terminated word
- call Spellfile_Test(0z0D000000000376696D, 'E758:')
-
- " SN_WORDS: very long word
- let v = eval('0z0D000000012C' .. repeat('41', 300))
- call Spellfile_Test(v, 'E759:')
-
- " SN_SOFO: missing sofofromlen
- call Spellfile_Test(0z06000000000100, 'E758:')
-
- " SN_SOFO: missing sofotolen
- call Spellfile_Test(0z06000000000400016100, 'E758:')
-
- " SN_SOFO: missing sofoto
- call Spellfile_Test(0z0600000000050001610000, 'E759:')
-
- " SN_COMPOUND: compmax is less than 2
- call Spellfile_Test(0z08000000000101, 'E759:')
-
- " SN_COMPOUND: missing compsylmax and other options
- call Spellfile_Test(0z0800000000020401, 'E759:')
-
- " SN_COMPOUND: missing compoptions
- call Spellfile_Test(0z080000000005040101, 'E758:')
-
- " SN_INFO: missing info
- call Spellfile_Test(0z0F0000000005040101, '')
-
- " SN_MIDWORD: missing midword
- call Spellfile_Test(0z0200000000040102, '')
-
- " SN_MAP: missing midword
- call Spellfile_Test(0z0700000000040102, '')
-
- " SN_SYLLABLE: missing SYLLABLE item
- call Spellfile_Test(0z0900000000040102, '')
-
- " SN_SYLLABLE: More than SY_MAXLEN size
- let v = eval('0z090000000022612F' .. repeat('62', 32))
- call Spellfile_Test(v, '')
-
- " LWORDTREE: missing
- call Spellfile_Test(0zFF, 'E758:')
-
- " LWORDTREE: missing tree node
- call Spellfile_Test(0zFF00000004, 'E758:')
-
- " LWORDTREE: missing tree node value
- call Spellfile_Test(0zFF0000000402, 'E758:')
-
- " KWORDTREE: missing tree node
- call Spellfile_Test(0zFF0000000000000004, 'E758:')
-
- " PREFIXTREE: missing tree node
- call Spellfile_Test(0zFF000000000000000000000004, 'E758:')
-
- let &rtp = save_rtp
- call delete('Xtest', 'rf')
-endfunc
-
-" Test for format errors in suggest file
-func Test_sugfile_format_error()
- let save_rtp = &rtp
- call mkdir('Xtest/spell', 'p')
- let splfile = './Xtest/spell/Xtest.utf-8.spl'
- let sugfile = './Xtest/spell/Xtest.utf-8.sug'
-
- " create an empty spell file with a suggest timestamp
- call writefile(0z56494D7370656C6C320B00000000080000000000000044FF000000000000000000000000, splfile, 'b')
-
- " 'encoding' is set before each test to clear the previously loaded suggest
- " file from memory.
-
- " empty suggest file
- set encoding=utf-8
- call writefile([], sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E778:')
- set nospell spelllang&
-
- " zero suggest version
- set encoding=utf-8
- call writefile(0z56494D73756700, sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E779:')
- set nospell spelllang&
-
- " unsupported suggest version
- set encoding=utf-8
- call writefile(0z56494D7375671F, sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E780:')
- set nospell spelllang&
-
- " missing suggest timestamp
- set encoding=utf-8
- call writefile(0z56494D73756701, sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E781:')
- set nospell spelllang&
-
- " incorrect suggest timestamp
- set encoding=utf-8
- call writefile(0z56494D7375670100000000000000FF, sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E781:')
- set nospell spelllang&
-
- " missing suggest wordtree
- set encoding=utf-8
- call writefile(0z56494D737567010000000000000044, sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E782:')
- set nospell spelllang&
-
- " invalid suggest word count in SUGTABLE
- set encoding=utf-8
- call writefile(0z56494D7375670100000000000000440000000022, sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E782:')
- set nospell spelllang&
-
- " missing sugline in SUGTABLE
- set encoding=utf-8
- call writefile(0z56494D7375670100000000000000440000000000000005, sugfile)
- set runtimepath=./Xtest
- set spelllang=Xtest
- set spell
- call assert_fails("let s = spellsuggest('abc')", 'E782:')
- set nospell spelllang&
-
- let &rtp = save_rtp
- call delete('Xtest', 'rf')
-endfunc
-
-" Test for using :mkspell to create a spell file from a list of words
-func Test_wordlist_dic()
- " duplicate encoding
- let lines =<< trim [END]
- # This is an example word list
-
- /encoding=latin1
- /encoding=latin1
- example
- [END]
- call writefile(lines, 'Xwordlist.dic')
- let output = execute('mkspell Xwordlist.spl Xwordlist.dic')
- call assert_match('Duplicate /encoding= line ignored in Xwordlist.dic line 4: /encoding=latin1', output)
-
- " multiple encoding for a word
- let lines =<< trim [END]
- example
- /encoding=latin1
- example
- [END]
- call writefile(lines, 'Xwordlist.dic')
- let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
- call assert_match('/encoding= line after word ignored in Xwordlist.dic line 2: /encoding=latin1', output)
-
- " unsupported encoding for a word
- let lines =<< trim [END]
- /encoding=Xtest
- example
- [END]
- call writefile(lines, 'Xwordlist.dic')
- let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
- call assert_match('Conversion in Xwordlist.dic not supported: from Xtest to utf-8', output)
-
- " duplicate region
- let lines =<< trim [END]
- /regions=usca
- /regions=usca
- example
- [END]
- call writefile(lines, 'Xwordlist.dic')
- let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
- call assert_match('Duplicate /regions= line ignored in Xwordlist.dic line 2: regions=usca', output)
-
- " maximum regions
- let lines =<< trim [END]
- /regions=uscauscauscauscausca
- example
- [END]
- call writefile(lines, 'Xwordlist.dic')
- let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
- call assert_match('Too many regions in Xwordlist.dic line 1: uscauscauscauscausca', output)
-
- " unsupported '/' value
- let lines =<< trim [END]
- /test=abc
- example
- [END]
- call writefile(lines, 'Xwordlist.dic')
- let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
- call assert_match('/ line ignored in Xwordlist.dic line 1: /test=abc', output)
-
- " unsupported flag
- let lines =<< trim [END]
- example/+
- [END]
- call writefile(lines, 'Xwordlist.dic')
- let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
- call assert_match('Unrecognized flags in Xwordlist.dic line 1: +', output)
-
- " non-ascii word
- call writefile(["ʀʀ"], 'Xwordlist.dic')
- let output = execute('mkspell! -ascii Xwordlist.spl Xwordlist.dic')
- call assert_match('Ignored 1 words with non-ASCII characters', output)
-
- call delete('Xwordlist.spl')
- call delete('Xwordlist.dic')
-endfunc
-
-" Test for the :mkspell command
-func Test_mkspell()
- call assert_fails('mkspell Xtest_us.spl', 'E751:')
- call assert_fails('mkspell a b c d e f g h i j k', 'E754:')
-
- call writefile([], 'Xtest.spl')
- call writefile([], 'Xtest.dic')
- call assert_fails('mkspell Xtest.spl Xtest.dic', 'E13:')
- call delete('Xtest.spl')
- call delete('Xtest.dic')
-
- call mkdir('Xtest.spl')
- call assert_fails('mkspell! Xtest.spl Xtest.dic', 'E17:')
- call delete('Xtest.spl', 'rf')
-
- " can't write the .spl file as its directory does not exist
- call writefile([], 'Xtest.aff')
- call writefile([], 'Xtest.dic')
- call assert_fails('mkspell DOES_NOT_EXIT/Xtest.spl Xtest.dic', 'E484:')
- call delete('Xtest.aff')
- call delete('Xtest.dic')
-
- call assert_fails('mkspell en en_US abc_xyz', 'E755:')
-endfunc
-
-" Tests for :mkspell with a .dic and .aff file
-func Test_aff_file_format_error()
- " FIXME: For some reason, the :mkspell command below doesn't fail on the
- " MS-Windows CI build. Disable this test on MS-Windows for now.
- CheckNotMSWindows
-
- " No word count in .dic file
- call writefile([], 'Xtest.dic')
- call writefile([], 'Xtest.aff')
- call assert_fails('mkspell! Xtest.spl Xtest', 'E760:')
-
- " create a .dic file for the tests below
- call writefile(['1', 'work'], 'Xtest.dic')
-
- " Invalid encoding in .aff file
- call writefile(['# comment', 'SET Xinvalidencoding'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Conversion in Xtest.aff not supported: from xinvalidencoding', output)
-
- " Invalid flag in .aff file
- call writefile(['FLAG xxx'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Invalid value for FLAG in Xtest.aff line 1: xxx', output)
-
- " set FLAGS after using flag for an affix
- call writefile(['SFX L Y 1', 'SFX L 0 re [^x]', 'FLAG long'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('FLAG after using flags in Xtest.aff line 3: long', output)
-
- " INFO in affix file
- let save_encoding = &encoding
- call mkdir('Xrtp/spell', 'p')
- call writefile(['1', 'work'], 'Xrtp/spell/Xtest.dic')
- call writefile(['NAME klingon', 'VERSION 1.4', 'AUTHOR Spock'],
- \ 'Xrtp/spell/Xtest.aff')
- silent mkspell! Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest
- let save_rtp = &rtp
- set runtimepath=./Xrtp
- set spelllang=Xtest
- set spell
- let output = split(execute('spellinfo'), "\n")
- call assert_equal("NAME klingon", output[1])
- call assert_equal("VERSION 1.4", output[2])
- call assert_equal("AUTHOR Spock", output[3])
- let &rtp = save_rtp
- call delete('Xrtp', 'rf')
- set spell& spelllang& spellfile&
- %bw!
- " 'encoding' must be set again to clear the spell file in memory
- let &encoding = save_encoding
-
- " COMPOUNDFORBIDFLAG flag after PFX in an affix file
- call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDFLAG c', 'COMPOUNDFORBIDFLAG x'],
- \ 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in Xtest.aff line 4', output)
-
- " COMPOUNDPERMITFLAG flag after PFX in an affix file
- call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDPERMITFLAG c'],
- \ 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in Xtest.aff line 3', output)
-
- " Wrong COMPOUNDRULES flag value in an affix file
- call writefile(['COMPOUNDRULES a'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Wrong COMPOUNDRULES value in Xtest.aff line 1: a', output)
-
- " Wrong COMPOUNDWORDMAX flag value in an affix file
- call writefile(['COMPOUNDWORDMAX 0'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Wrong COMPOUNDWORDMAX value in Xtest.aff line 1: 0', output)
-
- " Wrong COMPOUNDMIN flag value in an affix file
- call writefile(['COMPOUNDMIN 0'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Wrong COMPOUNDMIN value in Xtest.aff line 1: 0', output)
-
- " Wrong COMPOUNDSYLMAX flag value in an affix file
- call writefile(['COMPOUNDSYLMAX 0'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Wrong COMPOUNDSYLMAX value in Xtest.aff line 1: 0', output)
-
- " Wrong CHECKCOMPOUNDPATTERN flag value in an affix file
- call writefile(['CHECKCOMPOUNDPATTERN 0'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Wrong CHECKCOMPOUNDPATTERN value in Xtest.aff line 1: 0', output)
-
- " Both compounding and NOBREAK specified
- call writefile(['COMPOUNDFLAG c', 'NOBREAK'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Warning: both compounding and NOBREAK specified', output)
-
- " Duplicate affix entry in an affix file
- call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L Y 1', 'PFX L 0 re x'],
- \ 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Duplicate affix in Xtest.aff line 3: L', output)
-
- " Duplicate affix entry in an affix file
- call writefile(['PFX L Y 1', 'PFX L Y 1'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Unrecognized or duplicate item in Xtest.aff line 2: PFX', output)
-
- " Different combining flags in an affix file
- call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L N 1'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Different combining flag in continued affix block in Xtest.aff line 3', output)
-
- " Try to reuse a affix used for BAD flag
- call writefile(['BAD x', 'PFX x Y 1', 'PFX x 0 re x'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in Xtest.aff line 2: x', output)
-
- " Trailing characters in an affix entry
- call writefile(['PFX L Y 1 Test', 'PFX L 0 re x'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Trailing text in Xtest.aff line 1: Test', output)
-
- " Trailing characters in an affix entry
- call writefile(['PFX L Y 1', 'PFX L 0 re x Test'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Trailing text in Xtest.aff line 2: Test', output)
-
- " Incorrect combine flag in an affix entry
- call writefile(['PFX L X 1', 'PFX L 0 re x'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Expected Y or N in Xtest.aff line 1: X', output)
-
- " Invalid count for REP item
- call writefile(['REP a'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Expected REP(SAL) count in Xtest.aff line 1', output)
-
- " Trailing characters in REP item
- call writefile(['REP 1', 'REP f ph test'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Trailing text in Xtest.aff line 2: test', output)
-
- " Invalid count for MAP item
- call writefile(['MAP a'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Expected MAP count in Xtest.aff line 1', output)
-
- " Duplicate character in a MAP item
- call writefile(['MAP 2', 'MAP xx', 'MAP yy'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Duplicate character in MAP in Xtest.aff line 2', output)
-
- " Use COMPOUNDSYLMAX without SYLLABLE
- call writefile(['COMPOUNDSYLMAX 2'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('COMPOUNDSYLMAX used without SYLLABLE', output)
-
- " Missing SOFOTO
- call writefile(['SOFOFROM abcdef'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Missing SOFOTO line in Xtest.aff', output)
-
- " Length of SOFOFROM and SOFOTO differ
- call writefile(['SOFOFROM abcde', 'SOFOTO ABCD'], 'Xtest.aff')
- call assert_fails('mkspell! Xtest.spl Xtest', 'E759:')
-
- " Both SAL and SOFOFROM/SOFOTO items
- call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Both SAL and SOFO lines in Xtest.aff', output)
-
- " use an alphabet flag when FLAG is num
- call writefile(['FLAG num', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Flag is not a number in Xtest.aff line 2: L', output)
-
- " use number and alphabet flag when FLAG is num
- call writefile(['FLAG num', 'SFX 4f Y 1', 'SFX 4f 0 re [^x]'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Affix name too long in Xtest.aff line 2: 4f', output)
-
- " use a single character flag when FLAG is long
- call writefile(['FLAG long', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('Illegal flag in Xtest.aff line 2: L', output)
-
- " duplicate word in the .dic file
- call writefile(['2', 'good', 'good', 'good'], 'Xtest.dic')
- call writefile(['NAME vim'], 'Xtest.aff')
- let output = execute('mkspell! Xtest.spl Xtest')
- call assert_match('First duplicate word in Xtest.dic line 3: good', output)
- call assert_match('2 duplicate word(s) in Xtest.dic', output)
-
- call delete('Xtest.dic')
- call delete('Xtest.aff')
- call delete('Xtest.spl')
- call delete('Xtest.sug')
-endfunc
-
-func Test_spell_add_word()
- set spellfile=
- call assert_fails('spellgood abc', 'E764:')
-
- set spellfile=Xtest.utf-8.add
- call assert_fails('2spellgood abc', 'E765:')
-
- edit Xtest.utf-8.add
- call setline(1, 'sample')
- call assert_fails('spellgood abc', 'E139:')
- set spellfile&
- %bw!
-endfunc
-
-func Test_spellfile_verbose()
- call writefile(['1', 'one'], 'XtestVerbose.dic')
- call writefile([], 'XtestVerbose.aff')
- mkspell! XtestVerbose-utf8.spl XtestVerbose
- set spell
-
- " First time: the spl file should be read.
- let a = execute('3verbose set spelllang=XtestVerbose-utf8.spl')
- call assert_match('Reading spell file "XtestVerbose-utf8.spl"', a)
-
- " Second time time: the spl file should not be read (already read).
- let a = execute('3verbose set spelllang=XtestVerbose-utf8.spl')
- call assert_notmatch('Reading spell file "XtestVerbose-utf8.spl"', a)
-
- set spell& spelllang&
- call delete('XtestVerbose.dic')
- call delete('XtestVerbose.aff')
- call delete('XtestVerbose-utf8.spl')
-endfunc
-
-" Test NOBREAK (see :help spell-NOBREAK)
-func Test_NOBREAK()
- call writefile(['3', 'one', 'two', 'three' ], 'XtestNOBREAK.dic')
- call writefile(['NOBREAK' ], 'XtestNOBREAK.aff')
-
- mkspell! XtestNOBREAK-utf8.spl XtestNOBREAK
- set spell spelllang=XtestNOBREAK-utf8.spl
-
- call assert_equal(['', ''], spellbadword('One two three onetwo onetwothree threetwoone'))
-
- call assert_equal(['x', 'bad'], spellbadword('x'))
- call assert_equal(['y', 'bad'], spellbadword('yone'))
- call assert_equal(['z', 'bad'], spellbadword('onez'))
- call assert_equal(['zero', 'bad'], spellbadword('Onetwozerothree'))
-
- new
- call setline(1, 'Onetwwothree')
- norm! fw1z=
- call assert_equal('Onetwothree', getline(1))
- call setline(1, 'Onetwothre')
- norm! fh1z=
- call assert_equal('Onetwothree', getline(1))
-
- bw!
- set spell& spelllang&
- call delete('XtestNOBREAK.dic')
- call delete('XtestNOBREAK.aff')
- call delete('XtestNOBREAK-utf8.spl')
-endfunc
-
-" Test CHECKCOMPOUNDPATTERN (see :help spell-CHECKCOMPOUNDPATTERN)
-func Test_spellfile_CHECKCOMPOUNDPATTERN()
- call writefile(['4',
- \ 'one/c',
- \ 'two/c',
- \ 'three/c',
- \ 'four'], 'XtestCHECKCOMPOUNDPATTERN.dic')
- " Forbid compound words where first word ends with 'wo' and second starts with 'on'.
- call writefile(['CHECKCOMPOUNDPATTERN 1',
- \ 'CHECKCOMPOUNDPATTERN wo on',
- \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff')
-
- mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN
- set spell spelllang=XtestCHECKCOMPOUNDPATTERN-utf8.spl
-
- " Check valid words with and without valid compounds.
- for goodword in ['one', 'two', 'three', 'four',
- \ 'oneone', 'onetwo', 'onethree',
- \ 'twotwo', 'twothree',
- \ 'threeone', 'threetwo', 'threethree',
- \ 'onetwothree', 'onethreetwo', 'twothreeone', 'oneoneone']
- call assert_equal(['', ''], spellbadword(goodword), goodword)
- endfor
-
- " Compounds 'twoone' or 'threetwoone' should be forbidden by CHECKCOMPOUNPATTERN.
- " 'four' does not have the 'c' flag in *.aff file so no compound.
- " 'five' is not in the *.dic file.
- for badword in ['five', 'onetwox',
- \ 'twoone', 'threetwoone',
- \ 'fourone', 'onefour']
- call assert_equal([badword, 'bad'], spellbadword(badword))
- endfor
-
- set spell& spelllang&
- call delete('XtestCHECKCOMPOUNDPATTERN.dic')
- call delete('XtestCHECKCOMPOUNDPATTERN.aff')
- call delete('XtestCHECKCOMPOUNDPATTERN-utf8.spl')
-endfunc
-
-" Test NOCOMPOUNDSUGS (see :help spell-NOCOMPOUNDSUGS)
-func Test_spellfile_NOCOMPOUNDSUGS()
- call writefile(['3',
- \ 'one/c',
- \ 'two/c',
- \ 'three/c'], 'XtestNOCOMPOUNDSUGS.dic')
-
- " pass 0 tests without NOCOMPOUNDSUGS, pass 1 tests with NOCOMPOUNDSUGS
- for pass in [0, 1]
- if pass == 0
- call writefile(['COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff')
- else
- call writefile(['NOCOMPOUNDSUGS',
- \ 'COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff')
- endif
-
- mkspell! XtestNOCOMPOUNDSUGS-utf8.spl XtestNOCOMPOUNDSUGS
- set spell spelllang=XtestNOCOMPOUNDSUGS-utf8.spl
-
- for goodword in ['one', 'two', 'three',
- \ 'oneone', 'onetwo', 'onethree',
- \ 'twoone', 'twotwo', 'twothree',
- \ 'threeone', 'threetwo', 'threethree',
- \ 'onetwothree', 'onethreetwo', 'twothreeone', 'oneoneone']
- call assert_equal(['', ''], spellbadword(goodword), goodword)
- endfor
-
- for badword in ['four', 'onetwox', 'onexone']
- call assert_equal([badword, 'bad'], spellbadword(badword))
- endfor
-
- if pass == 0
- call assert_equal(['one', 'oneone'], spellsuggest('onne', 2))
- call assert_equal(['onethree', 'one three'], spellsuggest('onethre', 2))
- else
- call assert_equal(['one', 'one one'], spellsuggest('onne', 2))
- call assert_equal(['one three'], spellsuggest('onethre', 2))
- endif
- endfor
-
- set spell& spelllang&
- call delete('XtestNOCOMPOUNDSUGS.dic')
- call delete('XtestNOCOMPOUNDSUGS.aff')
- call delete('XtestNOCOMPOUNDSUGS-utf8.spl')
-endfunc
-
-" Test COMMON (better suggestions with common words, see :help spell-COMMON)
-func Test_spellfile_COMMON()
- call writefile(['7',
- \ 'and',
- \ 'ant',
- \ 'end',
- \ 'any',
- \ 'tee',
- \ 'the',
- \ 'ted'], 'XtestCOMMON.dic')
- call writefile(['COMMON the and'], 'XtestCOMMON.aff')
-
- mkspell! XtestCOMMON-utf8.spl XtestCOMMON
- set spell spelllang=XtestCOMMON-utf8.spl
-
- " COMMON words 'and' and 'the' should be the top suggestions.
- call assert_equal(['and', 'ant'], spellsuggest('anr', 2))
- call assert_equal(['and', 'end'], spellsuggest('ond', 2))
- call assert_equal(['the', 'ted'], spellsuggest('tha', 2))
- call assert_equal(['the', 'tee'], spellsuggest('dhe', 2))
-
- set spell& spelllang&
- call delete('XtestCOMMON.dic')
- call delete('XtestCOMMON.aff')
- call delete('XtestCOMMON-utf8.spl')
-endfunc
-
-" Test NOSUGGEST (see :help spell-COMMON)
-func Test_spellfile_NOSUGGEST()
- call writefile(['2', 'foo/X', 'fog'], 'XtestNOSUGGEST.dic')
- call writefile(['NOSUGGEST X'], 'XtestNOSUGGEST.aff')
-
- mkspell! XtestNOSUGGEST-utf8.spl XtestNOSUGGEST
- set spell spelllang=XtestNOSUGGEST-utf8.spl
-
- for goodword in ['foo', 'Foo', 'FOO', 'fog', 'Fog', 'FOG']
- call assert_equal(['', ''], spellbadword(goodword), goodword)
- endfor
- for badword in ['foO', 'fOO', 'fooo', 'foog', 'foofog', 'fogfoo']
- call assert_equal([badword, 'bad'], spellbadword(badword))
- endfor
-
- call assert_equal(['fog'], spellsuggest('fooo', 1))
- call assert_equal(['fog'], spellsuggest('fOo', 1))
- call assert_equal(['fog'], spellsuggest('foG', 1))
- call assert_equal(['fog'], spellsuggest('fogg', 1))
-
- set spell& spelllang&
- call delete('XtestNOSUGGEST.dic')
- call delete('XtestNOSUGGEST.aff')
- call delete('XtestNOSUGGEST-utf8.spl')
-endfunc
-
-
-" Test CIRCUMFIX (see: :help spell-CIRCUMFIX)
-func Test_spellfile_CIRCUMFIX()
- " Example taken verbatim from https://github.com/hunspell/hunspell/tree/master/tests
- call writefile(['1',
- \ 'nagy/C po:adj'], 'XtestCIRCUMFIX.dic')
- call writefile(['# circumfixes: ~ obligate prefix/suffix combinations',
- \ '# superlative in Hungarian: leg- (prefix) AND -bb (suffix)',
- \ '',
- \ 'CIRCUMFIX X',
- \ '',
- \ 'PFX A Y 1',
- \ 'PFX A 0 leg/X .',
- \ '',
- \ 'PFX B Y 1',
- \ 'PFX B 0 legesleg/X .',
- \ '',
- \ 'SFX C Y 3',
- \ 'SFX C 0 obb . is:COMPARATIVE',
- \ 'SFX C 0 obb/AX . is:SUPERLATIVE',
- \ 'SFX C 0 obb/BX . is:SUPERSUPERLATIVE'], 'XtestCIRCUMFIX.aff')
-
- mkspell! XtestCIRCUMFIX-utf8.spl XtestCIRCUMFIX
- set spell spelllang=XtestCIRCUMFIX-utf8.spl
-
- " From https://catalog.ldc.upenn.edu/docs/LDC2008T01/acta04.pdf:
- " Hungarian English
- " --------- -------
- " nagy great
- " nagyobb greater
- " legnagyobb greatest
- " legeslegnagyob most greatest
- call assert_equal(['', ''], spellbadword('nagy nagyobb legnagyobb legeslegnagyobb'))
-
- for badword in ['legnagy', 'legeslegnagy', 'legobb', 'legeslegobb']
- call assert_equal([badword, 'bad'], spellbadword(badword))
- endfor
-
- set spell& spelllang&
- call delete('XtestCIRCUMFIX.dic')
- call delete('XtestCIRCUMFIX.aff')
- call delete('XtestCIRCUMFIX-utf8.spl')
-endfunc
-
-" Test SFX that strips/chops characters
-func Test_spellfile_SFX_strip()
- " Simplified conjugation of Italian verbs ending in -are (first conjugation).
- call writefile(['SFX A Y 4',
- \ 'SFX A are iamo [^icg]are',
- \ 'SFX A are hiamo [cg]are',
- \ 'SFX A re mo iare',
- \ 'SFX A re vamo are'],
- \ 'XtestSFX.aff')
- " Examples of Italian verbs:
- " - cantare = to sing
- " - cercare = to search
- " - odiare = to hate
- call writefile(['3', 'cantare/A', 'cercare/A', 'odiare/A'], 'XtestSFX.dic')
-
- mkspell! XtestSFX-utf8.spl XtestSFX
- set spell spelllang=XtestSFX-utf8.spl
-
- " To sing, we're singing, we were singing.
- call assert_equal(['', ''], spellbadword('cantare cantiamo cantavamo'))
-
- " To search, we're searching, we were searching.
- call assert_equal(['', ''], spellbadword('cercare cerchiamo cercavamo'))
-
- " To hate, we hate, we were hating.
- call assert_equal(['', ''], spellbadword('odiare odiamo odiavamo'))
-
- for badword in ['canthiamo', 'cerciamo', 'cantarevamo', 'odiiamo']
- call assert_equal([badword, 'bad'], spellbadword(badword))
- endfor
-
- call assert_equal(['cantiamo'], spellsuggest('canthiamo', 1))
- call assert_equal(['cerchiamo'], spellsuggest('cerciamo', 1))
- call assert_equal(['cantavamo'], spellsuggest('cantarevamo', 1))
- call assert_equal(['odiamo'], spellsuggest('odiiamo', 1))
-
- set spell& spelllang&
- call delete('XtestSFX.dic')
- call delete('XtestSFX.aff')
- call delete('XtestSFX-utf8.spl')
-endfunc
-
-" When 'spellfile' is not set, adding a new good word will automatically set
-" the 'spellfile'
-func Test_init_spellfile()
- let save_rtp = &rtp
- let save_encoding = &encoding
- call mkdir('Xrtp/spell', 'p')
- call writefile(['vim'], 'Xrtp/spell/Xtest.dic')
- silent mkspell Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest.dic
- set runtimepath=./Xrtp
- set spelllang=Xtest
- set spell
- silent spellgood abc
- call assert_equal('./Xrtp/spell/Xtest.utf-8.add', &spellfile)
- call assert_equal(['abc'], readfile('Xrtp/spell/Xtest.utf-8.add'))
- call assert_true(filereadable('Xrtp/spell/Xtest.utf-8.spl'))
- set spell& spelllang& spellfile&
- call delete('Xrtp', 'rf')
- let &encoding = save_encoding
- let &rtp = save_rtp
- %bw!
-endfunc
-
-" Test for the 'mkspellmem' option
-func Test_mkspellmem_opt()
- call assert_fails('set mkspellmem=1000', 'E474:')
- call assert_fails('set mkspellmem=1000,', 'E474:')
- call assert_fails('set mkspellmem=1000,50', 'E474:')
- call assert_fails('set mkspellmem=1000,50,', 'E474:')
- call assert_fails('set mkspellmem=1000,50,10,', 'E474:')
- call assert_fails('set mkspellmem=1000,50,0', 'E474:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
deleted file mode 100644
index 1ee1d0dfe3..0000000000
--- a/src/nvim/testdir/test_startup.vim
+++ /dev/null
@@ -1,1283 +0,0 @@
-" Tests for startup.
-
-source shared.vim
-source screendump.vim
-source term_util.vim
-source check.vim
-
-" Check that loading startup.vim works.
-func Test_startup_script()
- throw 'skipped: Nvim does not need defaults.vim'
- set compatible
- source $VIMRUNTIME/defaults.vim
-
- call assert_equal(0, &compatible)
- " Restore some options, so that the following tests doesn't break
- set nomore
- set noshowmode
-endfunc
-
-" Verify the order in which plugins are loaded:
-" 1. plugins in non-after directories
-" 2. packages
-" 3. plugins in after directories
-func Test_after_comes_later()
- if !has('packages')
- return
- endif
- let before =<< trim [CODE]
- set nocp viminfo+=nviminfo
- set guioptions+=M
- let $HOME = "/does/not/exist"
- set loadplugins
- set rtp=Xhere,Xdir/after,Xanother
- set packpath=Xhere,Xdir/after
- 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')
- call writefile(['let g:sequence .= "another "'], 'Xanother/plugin/another.vim')
- call mkdir('Xhere/pack/foo/start/foobar/plugin', 'p')
- call writefile(['let g:sequence .= "pack "'], 'Xhere/pack/foo/start/foobar/plugin/foo.vim')
-
- call mkdir('Xdir/after/plugin', 'p')
- call writefile(['let g:sequence .= "after "'], 'Xdir/after/plugin/later.vim')
-
- if RunVim(before, after, '')
-
- let lines = readfile('Xtestout')
- let expected = ['Xbefore.vim', 'here.vim', 'another.vim', 'foo.vim', 'later.vim', 'Xafter.vim']
- let found = []
- for line in lines
- for one in expected
- if line =~ one
- call add(found, one)
- endif
- endfor
- endfor
- call assert_equal(expected, found)
- endif
-
- call assert_equal('here another pack after', substitute(join(readfile('Xsequence', 1), ''), '\s\+$', '', ''))
-
- call delete('Xtestout')
- call delete('Xsequence')
- call delete('Xhere', 'rf')
- call delete('Xanother', 'rf')
- call delete('Xdir', 'rf')
-endfunc
-
-func Test_pack_in_rtp_when_plugins_run()
- if !has('packages')
- return
- endif
- 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',
- \ ]
- call mkdir('Xhere/plugin', 'p')
- call writefile(['redir! > Xtestout', 'silent set runtimepath?', 'silent! call foo#Trigger()', 'redir END'], 'Xhere/plugin/here.vim')
- call mkdir('Xhere/pack/foo/start/foobar/autoload', 'p')
- call writefile(['function! foo#Trigger()', 'echo "autoloaded foo"', 'endfunction'], 'Xhere/pack/foo/start/foobar/autoload/foo.vim')
-
- if RunVim(before, after, '')
-
- let lines = filter(readfile('Xtestout'), '!empty(v:val)')
- call assert_match('runtimepath=Xhere', get(lines, 0))
- call assert_match('autoloaded foo', get(lines, 1))
- endif
-
- call delete('Xtestout')
- call delete('Xhere', 'rf')
-endfunc
-
-func Test_help_arg()
- " This does not work with a GUI-only binary, such as on MS-Windows.
- CheckAnyOf Unix NotGui
-
- if RunVim([], [], '--help >Xtestout')
- let lines = readfile('Xtestout')
- call assert_true(len(lines) > 20)
- call assert_match('Usage:', lines[0])
-
- " check if couple of lines are there
- let found = []
- for line in lines
- if line =~ '-R.*Read-only mode'
- call add(found, 'Readonly mode')
- endif
- " Watch out for a second --version line in the Gnome version.
- if line =~ '--version.*Print version information'
- call add(found, "--version")
- endif
- endfor
- call assert_equal(['Readonly mode', '--version'], found)
- endif
- call delete('Xtestout')
-endfunc
-
-func Test_compatible_args()
- throw "skipped: Nvim is always 'nocompatible'"
- 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])
- endif
-
- if RunVim([], after, '-N')
- let lines = readfile('Xtestout')
- call assert_equal('0', lines[0])
- endif
-
- call delete('Xtestout')
-endfunc
-
-" Test the -o[N] and -O[N] arguments to open N windows split
-" horizontally or vertically.
-func Test_o_arg()
- 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
- " - both windows should have the same or almost the same height
- " - sum of both windows height (+ 3 for both statusline and Ex command)
- " should be equal to the number of lines
- " - both windows should have the same width which should be equal to the
- " number of columns
- " - buffer of both windows should have no name
- let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
- call assert_equal('2', wn)
- call assert_inrange(0, 1, wh1 - wh2)
- call assert_equal(string(wh1 + wh2 + 3), ln)
- call assert_equal(ww1, ww2)
- call assert_equal(ww1, cn)
- call assert_equal('', bn1)
- call assert_equal('', bn2)
- endif
-
- if RunVim([], after, '-o foo bar')
- " Same expectations as for -o2 but buffer names should be foo and bar
- let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
- call assert_equal('2', wn)
- call assert_inrange(0, 1, wh1 - wh2)
- call assert_equal(string(wh1 + wh2 + 3), ln)
- call assert_equal(ww1, ww2)
- call assert_equal(ww1, cn)
- call assert_equal('foo', bn1)
- call assert_equal('bar', bn2)
- endif
-
- if RunVim([], after, '-O2')
- " Open 2 windows split vertically. Expect:
- " - 2 windows
- " - both windows should have the same or almost the same width
- " - sum of both windows width (+ 1 for the separator) should be equal to
- " the number of columns
- " - both windows should have the same height
- " - window height (+ 2 for the statusline and Ex command) should be equal
- " to the number of lines
- " - buffer of both windows should have no name
- let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
- call assert_equal('2', wn)
- call assert_inrange(0, 1, ww1 - ww2)
- call assert_equal(string(ww1 + ww2 + 1), cn)
- call assert_equal(wh1, wh2)
- call assert_equal(string(wh1 + 2), ln)
- call assert_equal('', bn1)
- call assert_equal('', bn2)
- endif
-
- if RunVim([], after, '-O foo bar')
- " Same expectations as for -O2 but buffer names should be foo and bar
- let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
- call assert_equal('2', wn)
- call assert_inrange(0, 1, ww1 - ww2)
- call assert_equal(string(ww1 + ww2 + 1), cn)
- call assert_equal(wh1, wh2)
- call assert_equal(string(wh1 + 2), ln)
- call assert_equal('foo', bn1)
- call assert_equal('bar', bn2)
- endif
-
- call delete('Xtestout')
-endfunc
-
-" Test the -p[N] argument to open N tabpages.
-func Test_p_arg()
- 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])
- endif
-
- if RunVim([], after, '-p foo bar')
- let lines = readfile('Xtestout')
- call assert_equal(4, len(lines))
- 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])
- endif
-
- call delete('Xtestout')
-endfunc
-
-" Test the -V[N] argument to set the 'verbose' option to [N]
-func Test_V_arg()
- " Can't catch the output of gvim.
- CheckNotGui
-
- let out = system(GetVimCommand() . ' --clean -es -X -V0 -c "set verbose?" -cq')
- 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\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')
- " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline 1: \" The default vimrc file\..* verbose=15\n", out)
-endfunc
-
-" Test the '-q [errorfile]' argument.
-func Test_q_arg()
- CheckFeature quickfix
-
- let lines =<< trim END
- /* some file with an error */
- main() {
- functionCall(arg; arg, arg);
- return 666
- }
- END
- call writefile(lines, 'Xbadfile.c')
-
- let after =<< trim [CODE]
- call writefile([&errorfile, string(getpos("."))], "Xtestout")
- copen
- w >> Xtestout
- qall
- [CODE]
-
- " Test with default argument '-q'.
- call assert_equal('errors.err', &errorfile)
- call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'errors.err')
- if RunVim([], after, '-q')
- let lines = readfile('Xtestout')
- call assert_equal(['errors.err',
- \ '[0, 4, 12, 0]',
- \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
- \ lines)
- endif
- call delete('Xtestout')
- call delete('errors.err')
-
- " Test with explicit argument '-q Xerrors' (with space).
- call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'Xerrors')
- if RunVim([], after, '-q Xerrors')
- let lines = readfile('Xtestout')
- call assert_equal(['Xerrors',
- \ '[0, 4, 12, 0]',
- \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
- \ lines)
- endif
- call delete('Xtestout')
-
- " Test with explicit argument '-qXerrors' (without space).
- if RunVim([], after, '-qXerrors')
- let lines = readfile('Xtestout')
- call assert_equal(['Xerrors',
- \ '[0, 4, 12, 0]',
- \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
- \ lines)
- endif
-
- " Test with a non-existing error file (exits with value 3)
- let out = system(GetVimCommand() .. ' -q xyz.err')
- call assert_equal(3, v:shell_error)
-
- call delete('Xbadfile.c')
- call delete('Xtestout')
- call delete('Xerrors')
-endfunc
-
-" Test the -V[N]{filename} argument to set the 'verbose' option to N
-" and set 'verbosefile' to filename.
-func Test_V_file_arg()
- if RunVim([], [], ' --clean -V2Xverbosefile -c "set verbose? verbosefile?" -cq')
- let out = join(readfile('Xverbosefile'), "\n")
- " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\n", out)
- call assert_match("\n verbose=2\n", out)
- call assert_match("\n verbosefile=Xverbosefile", out)
- endif
-
- call delete('Xverbosefile')
-endfunc
-
-" Test the -m, -M and -R arguments:
-" -m resets 'write'
-" -M resets 'modifiable' and 'write'
-" -R sets 'readonly'
-func Test_m_M_R()
- 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)
- endif
- if RunVim([], after, '-m')
- let lines = readfile('Xtestout')
- call assert_equal(['0', '1', '0', '200'], lines)
- endif
- if RunVim([], after, '-M')
- let lines = readfile('Xtestout')
- call assert_equal(['0', '0', '0', '200'], lines)
- endif
- if RunVim([], after, '-R')
- let lines = readfile('Xtestout')
- call assert_equal(['1', '1', '1', '10000'], lines)
- endif
-
- call delete('Xtestout')
-endfunc
-
-" Test the -A, -F and -H arguments (Arabic, Farsi and Hebrew modes).
-func Test_A_F_H_arg()
- 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')
- let lines = readfile('Xtestout')
- call assert_equal(['1', '1', '0', '0'], lines)
- endif
-
- if has('farsi') && RunVim([], after, '-F')
- let lines = readfile('Xtestout')
- call assert_equal(['1', '0', '1', '0'], lines)
- endif
-
- if has('rightleft') && RunVim([], after, '-H')
- let lines = readfile('Xtestout')
- call assert_equal(['1', '0', '0', '1'], lines)
- endif
-
- call delete('Xtestout')
-endfunc
-
-" Test the --echo-wid argument (for GTK GUI only).
-func Test_echo_wid()
- CheckCanRunGui
- CheckFeature gui_gtk
-
- if RunVim([], [], '-g --echo-wid -cq >Xtest_echo_wid')
- let lines = readfile('Xtest_echo_wid')
- call assert_equal(1, len(lines))
- call assert_match('^WID: \d\+$', lines[0])
- endif
-
- call delete('Xtest_echo_wid')
-endfunction
-
-" Test the -reverse and +reverse arguments (for GUI only).
-func Test_reverse()
- CheckCanRunGui
- CheckAnyOf Feature:gui_gtk Feature:gui_motif
-
- let after =<< trim [CODE]
- call writefile([&background], "Xtest_reverse")
- qall
- [CODE]
- if RunVim([], after, '-f -g -reverse')
- let lines = readfile('Xtest_reverse')
- call assert_equal(['dark'], lines)
- endif
- if RunVim([], after, '-f -g +reverse')
- let lines = readfile('Xtest_reverse')
- call assert_equal(['light'], lines)
- endif
-
- call delete('Xtest_reverse')
-endfunc
-
-" Test the -background and -foreground arguments (for GUI only).
-func Test_background_foreground()
- CheckCanRunGui
- CheckAnyOf Feature:gui_gtk Feature:gui_motif
-
- " Is there a better way to check the effect of -background & -foreground
- " other than merely looking at &background (dark or light)?
- let after =<< trim [CODE]
- call writefile([&background], "Xtest_fg_bg")
- qall
- [CODE]
- if RunVim([], after, '-f -g -background darkred -foreground yellow')
- let lines = readfile('Xtest_fg_bg')
- call assert_equal(['dark'], lines)
- endif
- if RunVim([], after, '-f -g -background ivory -foreground darkgreen')
- let lines = readfile('Xtest_fg_bg')
- call assert_equal(['light'], lines)
- endif
-
- call delete('Xtest_fg_bg')
-endfunc
-
-" Test the -font argument (for GUI only).
-func Test_font()
- CheckCanRunGui
- CheckNotMSWindows
-
- if has('gui_gtk')
- let font = 'Courier 14'
- elseif has('gui_motif')
- let font = '-misc-fixed-bold-*'
- else
- throw 'Skipped: test does not set a valid font for this GUI'
- endif
-
- let after =<< trim [CODE]
- call writefile([&guifont], "Xtest_font")
- qall
- [CODE]
-
- if RunVim([], after, '--nofork -g -font "' .. font .. '"')
- let lines = readfile('Xtest_font')
- call assert_equal([font], lines)
- endif
-
- call delete('Xtest_font')
-endfunc
-
-" Test the -geometry argument (for GUI only).
-func Test_geometry()
- CheckCanRunGui
- CheckAnyOf Feature:gui_gtk Feature:gui_motif
-
- if has('gui_motif')
- " FIXME: With GUI Motif the value of getwinposx(),
- " getwinposy() and getwinpos() do not match exactly the
- " value given in -geometry. Why?
- " So only check &columns and &lines for those GUIs.
- let after =<< trim [CODE]
- call writefile([&columns, &lines], "Xtest_geometry")
- qall
- [CODE]
- if RunVim([], after, '-f -g -geometry 31x13+41+43')
- let lines = readfile('Xtest_geometry')
- call assert_equal(['31', '13'], lines)
- endif
- else
- let after =<< trim [CODE]
- call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry")
- qall
- [CODE]
- " Some window managers have a bar at the top that pushes windows down,
- " need to use at least 130, let's do 150
- if RunVim([], after, '-f -g -geometry 31x13+41+150')
- let lines = readfile('Xtest_geometry')
- " Depending on the GUI library and the windowing system the final size
- " might be a bit different, allow for some tolerance. Tuned based on
- " actual failures.
- call assert_inrange(31, 35, str2nr(lines[0]))
- call assert_equal('13', lines[1])
- call assert_equal('41', lines[2])
- call assert_equal('150', lines[3])
- call assert_equal('[41, 150]', lines[4])
- endif
- endif
-
- call delete('Xtest_geometry')
-endfunc
-
-" Test the -iconic argument (for GUI only).
-func Test_iconic()
- CheckCanRunGui
- CheckAnyOf Feature:gui_gtk Feature:gui_motif
-
- call RunVim([], [], '-f -g -iconic -cq')
-
- " TODO: currently only start vim iconified, but does not
- " check that vim is iconified. How could this be checked?
-endfunc
-
-
-func Test_invalid_args()
- " must be able to get the output of Vim.
- CheckUnix
- CheckNotGui
-
- for opt in ['-Y', '--does-not-exist']
- let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
- call assert_equal(1, v:shell_error)
- call assert_equal('nvim: Unknown option argument: "' .. opt .. '"', out[0])
- call assert_equal('More info with "nvim -h"', out[1])
- endfor
-
- for opt in ['-c', '-i', '-s', '-t', '-u', '-U', '-w', '-W', '--cmd', '--startuptime']
- let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
- call assert_equal(1, v:shell_error)
- call assert_equal('nvim: Argument missing after: "' .. opt .. '"', out[0])
- call assert_equal('More info with "nvim -h"', out[1])
- endfor
-
- if has('clientserver')
- for opt in ['--remote', '--remote-send', '--remote-silent', '--remote-expr',
- \ '--remote-tab', '--remote-tab-wait',
- \ '--remote-tab-wait-silent', '--remote-tab-silent',
- \ '--remote-wait', '--remote-wait-silent',
- \ '--servername',
- \ ]
- let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
- call assert_equal(1, v:shell_error)
- call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
- call assert_equal('Argument missing after: "' .. opt .. '"', out[1])
- call assert_equal('More info with: "vim -h"', out[2])
- endfor
- endif
-
- if has('gui_gtk')
- let out = split(system(GetVimCommand() .. ' --display'), "\n")
- call assert_equal(1, v:shell_error)
- call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
- call assert_equal('Argument missing after: "--display"', out[1])
- call assert_equal('More info with: "vim -h"', out[2])
- endif
-
- if has('xterm_clipboard')
- let out = split(system(GetVimCommand() .. ' -display'), "\n")
- call assert_equal(1, v:shell_error)
- call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
- call assert_equal('Argument missing after: "-display"', out[1])
- call assert_equal('More info with: "vim -h"', out[2])
- endif
-
- let out = split(system(GetVimCommand() .. ' -ix'), "\n")
- call assert_equal(1, v:shell_error)
- call assert_equal('nvim: Garbage after option argument: "-ix"', out[0])
- call assert_equal('More info with "nvim -h"', out[1])
-
- " Not an error in Nvim. The "-" file is allowed with -t, -q, or [file].
- let out = split(system(GetVimCommand() .. ' - xxx -cq'), "\n")
- call assert_equal(0, v:shell_error)
-
- if has('quickfix')
- " Detect invalid repeated arguments '-t foo -t foo', '-q foo -q foo'.
- for opt in ['-t', '-q']
- let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n")
- call assert_equal(1, v:shell_error)
- call assert_equal('nvim: Too many edit arguments: "' .. opt .. '"', out[0])
- call assert_equal('More info with "nvim -h"', out[1])
- endfor
- endif
-
- for opt in [' -cq', ' --cmd q', ' +', ' -S foo']
- let out = split(system(GetVimCommand() .. repeat(opt, 11)), "\n")
- call assert_equal(1, v:shell_error)
- " FIXME: The error message given by Vim is not ideal in case of repeated
- " -S foo since it does not mention -S.
- call assert_equal('nvim: Too many "+command", "-c command" or "--cmd command" arguments', out[0])
- call assert_equal('More info with "nvim -h"', out[1])
- endfor
-
- if has('gui_gtk')
- let out = split(system(GetVimCommand() .. ' --socketid'), "\n")
- call assert_equal(1, v:shell_error)
- call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
- call assert_equal('Argument missing after: "--socketid"', out[1])
- call assert_equal('More info with: "vim -h"', out[2])
-
- for opt in ['--socketid x', '--socketid 0xg']
- let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
- call assert_equal(1, v:shell_error)
- call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
- call assert_equal('Invalid argument for: "--socketid"', out[1])
- call assert_equal('More info with: "vim -h"', out[2])
- endfor
-
- endif
-endfunc
-
-func Test_file_args()
- let after =<< trim [CODE]
- call writefile(argv(), "Xtestout")
- qall
- [CODE]
-
- if RunVim([], after, '')
- let lines = readfile('Xtestout')
- call assert_equal(0, len(lines))
- endif
-
- if RunVim([], after, 'one')
- let lines = readfile('Xtestout')
- call assert_equal(1, len(lines))
- call assert_equal('one', lines[0])
- endif
-
- if RunVim([], after, 'one two three')
- let lines = readfile('Xtestout')
- call assert_equal(3, len(lines))
- call assert_equal('one', lines[0])
- call assert_equal('two', lines[1])
- call assert_equal('three', lines[2])
- endif
-
- if RunVim([], after, 'one -c echo two')
- let lines = readfile('Xtestout')
- call assert_equal(2, len(lines))
- call assert_equal('one', lines[0])
- call assert_equal('two', lines[1])
- endif
-
- if RunVim([], after, 'one -- -c echo two')
- let lines = readfile('Xtestout')
- call assert_equal(4, len(lines))
- call assert_equal('one', lines[0])
- call assert_equal('-c', lines[1])
- call assert_equal('echo', lines[2])
- call assert_equal('two', lines[3])
- endif
-
- call delete('Xtestout')
-endfunc
-
-func Test_startuptime()
- if !has('startuptime')
- return
- endif
- let after = ['qall']
- if RunVim([], after, '--startuptime Xtestout one')
- let lines = readfile('Xtestout')
- let expected = ['parsing arguments', 'inits 3', 'opening buffers']
- let found = []
- for line in lines
- for exp in expected
- if line =~ exp
- call add(found, exp)
- endif
- endfor
- endfor
- call assert_equal(expected, found)
- endif
- call delete('Xtestout')
-endfunc
-
-func Test_read_stdin()
- 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
- call assert_equal(['something'], split(lines[0]))
- endif
- call delete('Xtestout')
-endfunc
-
-func Test_progpath()
- " Tests normally run with "./vim" or "../vim", these must have been expanded
- " to a full path.
- if has('unix')
- call assert_equal('/', v:progpath[0])
- elseif has('win32')
- call assert_equal(':', v:progpath[1])
- call assert_match('[/\\]', v:progpath[2])
- endif
-
- " Only expect "vim" to appear in v:progname.
- call assert_match('vim\c', v:progname)
-endfunc
-
-func Test_silent_ex_mode()
- " must be able to get the output of Vim.
- CheckUnix
- CheckNotGui
-
- " This caused an ml_get error.
- let out = system(GetVimCommand() . ' -u NONE -es -c''set verbose=1|h|exe "%norm\<c-y>\<c-d>"'' -c cq')
- call assert_notmatch('E315:', out)
-endfunc
-
-func Test_default_term()
- " must be able to get the output of Vim.
- CheckUnix
- CheckNotGui
-
- let save_term = $TERM
- let $TERM = 'unknownxxx'
- let out = system(GetVimCommand() . ' -c ''echo &term'' -c cq')
- call assert_match('nvim', out)
- let $TERM = save_term
-endfunc
-
-func Test_zzz_startinsert()
- " Test :startinsert
- call writefile(['123456'], 'Xtestout')
- 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 =<< 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)
- endif
- call delete('Xtestout')
-endfunc
-
-func Test_issue_3969()
- " Can't catch the output of gvim.
- CheckNotGui
-
- " Check that message is not truncated.
- let out = system(GetVimCommand() . ' -es -X -V1 -c "echon ''hello''" -cq')
- call assert_equal('hello', out)
-endfunc
-
-func Test_start_with_tabs()
- CheckRunVimInTerminal
-
- let buf = RunVimInTerminal('-p a b c', {})
- call VerifyScreenDump(buf, '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
-
-" Test for the "-r" recovery mode option
-func Test_r_arg()
- throw 'Skipped: Nvim has different directories'
- " Can't catch the output of gvim.
- CheckNotGui
- CheckUnix
- CheckEnglish
- let cmd = GetVimCommand()
- " There can be swap files anywhere, only check for the headers.
- let expected =<< trim END
- Swap files found:.*
- In current directory:.*
- In directory \~/tmp:.*
- In directory /var/tmp:.*
- In directory /tmp:.*
- END
- call assert_match(join(expected, ""), system(cmd .. " -r")->substitute("[\r\n]\\+", '', ''))
-endfunc
-
-" Test for the '-t' option to jump to a tag
-func Test_t_arg()
- let before =<< trim [CODE]
- set tags=Xtags
- [CODE]
- let after =<< trim [CODE]
- let s = bufname('') .. ':L' .. line('.') .. 'C' .. col('.')
- call writefile([s], "Xtestout")
- qall
- [CODE]
-
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfile1\t/^ \\zsfirst$/",
- \ "second\tXfile1\t/^ \\zssecond$/",
- \ "third\tXfile1\t/^ \\zsthird$/"],
- \ 'Xtags')
- call writefile([' first', ' second', ' third'], 'Xfile1')
-
- for t_arg in ['-t second', '-tsecond']
- if RunVim(before, after, t_arg)
- call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg)
- call delete('Xtestout')
- endif
- endfor
-
- call delete('Xtags')
- call delete('Xfile1')
-endfunc
-
-" Test the '-T' argument which sets the 'term' option.
-func Test_T_arg()
- throw 'skipped: Nvim does not support "-T" argument'
- CheckNotGui
- let after =<< trim [CODE]
- call writefile([&term], "Xtest_T_arg")
- qall
- [CODE]
-
- for t in ['builtin_dumb', 'builtin_ansi']
- if RunVim([], after, '-T ' .. t)
- let lines = readfile('Xtest_T_arg')
- call assert_equal([t], lines)
- endif
- endfor
-
- call delete('Xtest_T_arg')
-endfunc
-
-" Test the '-x' argument to read/write encrypted files.
-func Test_x_arg()
- CheckRunVimInTerminal
- CheckFeature cryptv
-
- " Create an encrypted file Xtest_x_arg.
- let buf = RunVimInTerminal('-n -x Xtest_x_arg', #{rows: 10, wait_for_ruler: 0})
- call WaitForAssert({-> assert_match('^Enter encryption key: ', term_getline(buf, 10))})
- call term_sendkeys(buf, "foo\n")
- call WaitForAssert({-> assert_match('^Enter same key again: ', term_getline(buf, 10))})
- call term_sendkeys(buf, "foo\n")
- call WaitForAssert({-> assert_match(' All$', term_getline(buf, 10))})
- call term_sendkeys(buf, "itest\<Esc>:w\<Enter>")
- call WaitForAssert({-> assert_match('"Xtest_x_arg" \[New\]\[blowfish2\] 1L, 5B written',
- \ term_getline(buf, 10))})
- call StopVimInTerminal(buf)
-
- " Read the encrypted file and check that it contains the expected content "test"
- let buf = RunVimInTerminal('-n -x Xtest_x_arg', #{rows: 10, wait_for_ruler: 0})
- call WaitForAssert({-> assert_match('^Enter encryption key: ', term_getline(buf, 10))})
- call term_sendkeys(buf, "foo\n")
- call WaitForAssert({-> assert_match('^Enter same key again: ', term_getline(buf, 10))})
- call term_sendkeys(buf, "foo\n")
- call WaitForAssert({-> assert_match('^test', term_getline(buf, 1))})
- call StopVimInTerminal(buf)
-
- call delete('Xtest_x_arg')
-endfunc
-
-" Test for entering the insert mode on startup
-func Test_start_insertmode()
- throw "Skipped: Nvim does not support setting 'insertmode'"
- let before =<< trim [CODE]
- set insertmode
- [CODE]
- let after =<< trim [CODE]
- call writefile(['insertmode=' .. &insertmode], 'Xtestout')
- qall
- [CODE]
- if RunVim(before, after, '')
- call assert_equal(['insertmode=1'], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for enabling the binary mode on startup
-func Test_b_arg()
- let after =<< trim [CODE]
- call writefile(['binary=' .. &binary], 'Xtestout')
- qall
- [CODE]
- if RunVim([], after, '-b')
- call assert_equal(['binary=1'], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for enabling the lisp mode on startup
-func Test_l_arg()
- throw 'Skipped: Nvim -l arg differs from Vim'
- let after =<< trim [CODE]
- let s = 'lisp=' .. &lisp .. ', showmatch=' .. &showmatch
- call writefile([s], 'Xtestout')
- qall
- [CODE]
- if RunVim([], after, '-l')
- call assert_equal(['lisp=1, showmatch=1'], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for specifying a non-existing vimrc file using "-u"
-func Test_missing_vimrc()
- CheckRunVimInTerminal
- let after =<< trim [CODE]
- call assert_match('^E282:', v:errmsg)
- call writefile(v:errors, 'Xtestout')
- [CODE]
- call writefile(after, 'Xafter')
-
- let cmd = GetVimCommandCleanTerm() . ' -u Xvimrc_missing -S Xafter'
- let buf = term_start(cmd, {'term_rows' : 10})
- call WaitForAssert({-> assert_equal("running", term_getstatus(buf))})
- call term_wait(buf)
- call term_sendkeys(buf, "\n:")
- call term_wait(buf)
- call WaitForAssert({-> assert_match(':', term_getline(buf, 10))})
- call StopVimInTerminal(buf)
- call assert_equal([], readfile('Xtestout'))
- call delete('Xafter')
- call delete('Xtestout')
-endfunc
-
-" Test for using the $VIMINIT environment variable
-func Test_VIMINIT()
- let after =<< trim [CODE]
- call assert_equal(1, exists('viminit_found'))
- call assert_equal('yes', viminit_found)
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- call writefile(after, 'Xafter')
- " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
- let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"'
- call setenv('VIMINIT', 'let viminit_found="yes"')
- exe "silent !" . cmd
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- call delete('Xafter')
-endfunc
-
-" Test for using the $EXINIT environment variable
-func Test_EXINIT()
- let after =<< trim [CODE]
- call assert_equal(1, exists('exinit_found'))
- call assert_equal('yes', exinit_found)
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- call writefile(after, 'Xafter')
- " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
- let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"'
- call setenv('EXINIT', 'let exinit_found="yes"')
- exe "silent !" . cmd
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- call delete('Xafter')
-endfunc
-
-" Test for using the 'exrc' option
-func Test_exrc()
- throw 'Skipped: Nvim requires user input for the exrc option'
- let after =<< trim [CODE]
- call assert_equal(1, &exrc)
- call assert_equal(1, &secure)
- call assert_equal(37, exrc_found)
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- call mkdir('Xdir')
- call writefile(['let exrc_found=37'], 'Xdir/.exrc')
- call writefile(after, 'Xdir/Xafter')
- " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "cd Xdir" --cmd "set enc=utf8 exrc secure"'
- let cmd = GetVimProg() . ' -S Xafter --cmd "cd Xdir" --cmd "set enc=utf8 exrc secure"'
- exe "silent !" . cmd
- call assert_equal([], readfile('Xdir/Xtestout'))
- call delete('Xdir', 'rf')
-endfunc
-
-" Test for starting Vim with a non-terminal as input/output
-func Test_io_not_a_terminal()
- throw 'Skipped: Nvim does not support --ttyfail'
- " Can't catch the output of gvim.
- CheckNotGui
- CheckUnix
- CheckEnglish
- let l = systemlist(GetVimProg() .. ' --ttyfail')
- call assert_equal(['Vim: Warning: Output is not to a terminal',
- \ 'Vim: Warning: Input is not from a terminal'], l)
-endfunc
-
-" Test for not being a term avoiding escape codes.
-func Test_not_a_term()
- CheckUnix
- CheckNotGui
-
- if &shellredir =~ '%s'
- let redir = printf(&shellredir, 'Xvimout')
- else
- let redir = &shellredir .. ' Xvimout'
- endif
-
- " As nvim checks the environment by itself there will be no escape sequences
- " This will also happen to take two (2) seconds.
- let cmd = GetVimProg() .. ' --cmd quit ' .. redir
- exe "silent !" . cmd
- call assert_notmatch("\e", readfile('Xvimout')->join())
- call delete('Xvimout')
-
- " --not-a-term flag has thus been deleted
-endfunc
-
-
-" Test for the "-w scriptout" argument
-func Test_w_arg()
- " Can't catch the output of gvim.
- CheckNotGui
-
- call writefile(["iVim Editor\<Esc>:q!\<CR>"], 'Xscriptin', 'b')
- if RunVim([], [], '-s Xscriptin -w Xscriptout')
- call assert_equal(["iVim Editor\e:q!\r"], readfile('Xscriptout'))
- call delete('Xscriptout')
- endif
- call delete('Xscriptin')
-
- " Test for failing to open the script output file. This test works only when
- " the language is English.
- if !has('win32') && (v:lang == "C" || v:lang =~ '^[Ee]n')
- call mkdir("Xdir")
- let m = system(GetVimCommand() .. " -w Xdir")
- call assert_equal("Cannot open for script output: \"Xdir\"\n", m)
- call delete("Xdir", 'rf')
- endif
-
- " A number argument sets the 'window' option
- call writefile(["iwindow \<C-R>=&window\<CR>\<Esc>:wq! Xresult\<CR>"], 'Xscriptin', 'b')
- for w_arg in ['-w 17', '-w17']
- if RunVim([], [], '-s Xscriptin ' .. w_arg)
- call assert_equal(["window 17"], readfile('Xresult'), w_arg)
- call delete('Xresult')
- endif
- endfor
- call delete('Xscriptin')
-endfunc
-
-" Test for the "-s scriptin" argument
-func Test_s_arg()
- " Can't catch the output of gvim.
- CheckNotGui
- CheckEnglish
- " Test for failing to open the script input file.
- let m = system(GetVimCommand() .. " -s abcxyz")
- " call assert_equal("Cannot open for reading: \"abcxyz\"\n", m)
- call assert_equal("Cannot open for reading: \"abcxyz\": no such file or directory\n", m)
-
- call writefile([], 'Xinput')
- let m = system(GetVimCommand() .. " -s Xinput -s Xinput")
- call assert_equal("Attempt to open script file again: \"-s Xinput\"\n", m)
- call delete('Xinput')
-endfunc
-
-" Test for the "-n" (no swap file) argument
-func Test_n_arg()
- let after =<< trim [CODE]
- call assert_equal(0, &updatecount)
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- if RunVim([], after, '-n')
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for the "-h" (help) argument
-func Test_h_arg()
- throw 'Skipped: Nvim has different output for "-h" argument'
- " Can't catch the output of gvim.
- CheckNotGui
- let l = systemlist(GetVimProg() .. ' -h')
- call assert_match('^VIM - Vi IMproved', l[0])
- let l = systemlist(GetVimProg() .. ' -?')
- call assert_match('^VIM - Vi IMproved', l[0])
-endfunc
-
-" Test for the "-F" (farsi) argument
-func Test_F_arg()
- throw 'Skipped: Nvim does not recognize "-F" argument'
- " Can't catch the output of gvim.
- CheckNotGui
- let l = systemlist(GetVimProg() .. ' -F')
- call assert_match('^E27:', l[0])
-endfunc
-
-" Test for the "-E" (improved Ex mode) argument
-func Test_E_arg()
- let after =<< trim [CODE]
- call assert_equal('cv', mode(1))
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- if RunVim([], after, '-E')
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for the "-D" (debugger) argument
-func Test_D_arg()
- CheckRunVimInTerminal
-
- let cmd = GetVimCommandCleanTerm() .. ' -D'
- let buf = term_start(cmd, {'term_rows' : 10})
- call WaitForAssert({-> assert_equal("running", term_getstatus(buf))})
-
- call WaitForAssert({-> assert_equal('Entering Debug mode. Type "cont" to continue.',
- \ term_getline(buf, 7))})
- call WaitForAssert({-> assert_equal('>', term_getline(buf, 10))})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Test for too many edit argument errors
-func Test_too_many_edit_args()
- throw 'Skipped: N/A'
- " Can't catch the output of gvim.
- CheckNotGui
- CheckEnglish
- let l = systemlist(GetVimProg() .. ' - -')
- call assert_match('^Too many edit arguments: "-"', l[1])
-endfunc
-
-" Test starting vim with various names: vim, ex, view, evim, etc.
-func Test_progname()
- CheckUnix
-
- call mkdir('Xprogname', 'p')
- call writefile(['silent !date',
- \ 'call writefile([mode(1), '
- \ .. '&insertmode, &diff, &readonly, &updatecount, '
- \ .. 'join(split(execute("message"), "\n")[1:])], "Xprogname_out")',
- \ 'qall'], 'Xprogname_after')
-
- " +---------------------------------------------- progname
- " | +--------------------------------- mode(1)
- " | | +--------------------------- &insertmode
- " | | | +---------------------- &diff
- " | | | | +----------------- &readonly
- " | | | | | +-------- &updatecount
- " | | | | | | +--- :messages
- " | | | | | | |
- " let expectations = {
- " \ 'vim': ['n', '0', '0', '0', '200', ''],
- " \ 'gvim': ['n', '0', '0', '0', '200', ''],
- " \ 'ex': ['ce', '0', '0', '0', '200', ''],
- " \ 'exim': ['cv', '0', '0', '0', '200', ''],
- " \ 'view': ['n', '0', '0', '1', '10000', ''],
- " \ 'gview': ['n', '0', '0', '1', '10000', ''],
- " \ 'evim': ['n', '1', '0', '0', '200', ''],
- " \ 'eview': ['n', '1', '0', '1', '10000', ''],
- " \ 'rvim': ['n', '0', '0', '0', '200', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
- " \ 'rgvim': ['n', '0', '0', '0', '200', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
- " \ 'rview': ['n', '0', '0', '1', '10000', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
- " \ 'rgview': ['n', '0', '0', '1', '10000', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
- " \ 'vimdiff': ['n', '0', '1', '0', '200', ''],
- " \ 'gvimdiff': ['n', '0', '1', '0', '200', '']}
- let expectations = {'nvim': ['n', '0', '0', '0', '200', '']}
-
- " let prognames = ['vim', 'gvim', 'ex', 'exim', 'view', 'gview',
- " \ 'evim', 'eview', 'rvim', 'rgvim', 'rview', 'rgview',
- " \ 'vimdiff', 'gvimdiff']
- let prognames = ['nvim']
-
- for progname in prognames
- let run_with_gui = (progname =~# 'g') || (has('gui') && (progname ==# 'evim' || progname ==# 'eview'))
-
- if empty($DISPLAY) && run_with_gui
- " Can't run gvim, gview (etc.) if $DISPLAY is not setup.
- continue
- endif
-
- exe 'silent !ln -s -f ' ..exepath(GetVimProg()) .. ' Xprogname/' .. progname
-
- let stdout_stderr = ''
- if progname =~# 'g'
- let stdout_stderr = system('Xprogname/'..progname..' -f --clean --not-a-term -S Xprogname_after')
- else
- exe 'sil !Xprogname/'..progname..' -f --clean -S Xprogname_after'
- endif
-
- if progname =~# 'g' && !has('gui')
- call assert_equal("E25: GUI cannot be used: Not enabled at compile time\n", stdout_stderr, progname)
- else
- " GUI motif can output some warnings like this:
- " Warning:
- " Name: subMenu
- " Class: XmCascadeButton
- " Illegal mnemonic character; Could not convert X KEYSYM to a keycode
- " So don't check that stderr is empty with GUI Motif.
- if run_with_gui && !has('gui_motif')
- call assert_equal('', stdout_stderr, progname)
- endif
- call assert_equal(expectations[progname], readfile('Xprogname_out'), progname)
- endif
-
- call delete('Xprogname/' .. progname)
- call delete('Xprogname_out')
- endfor
-
- call delete('Xprogname_after')
- call delete('Xprogname', 'd')
-endfunc
-
-" Test for doing a write from .vimrc
-func Test_write_in_vimrc()
- call writefile(['silent! write'], 'Xvimrc')
- let after =<< trim [CODE]
- call assert_match('E32: ', v:errmsg)
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- if RunVim([], after, '-u Xvimrc')
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- endif
- call delete('Xvimrc')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim
deleted file mode 100644
index 2ee6ecc41d..0000000000
--- a/src/nvim/testdir/test_startup_utf8.vim
+++ /dev/null
@@ -1,82 +0,0 @@
-" Tests for startup using utf-8.
-
-source check.vim
-source shared.vim
-source screendump.vim
-
-func Test_read_stdin_utf8()
- let linesin = ['テスト', '€ÀÈÌÒÙ']
- call writefile(linesin, 'Xtestin')
- let before = [
- \ 'set enc=utf-8',
- \ 'set fencs=cp932,utf-8',
- \ ]
- let after = [
- \ 'write ++enc=utf-8 Xtestout',
- \ 'quit!',
- \ ]
- if has('win32')
- let pipecmd = 'type Xtestin | '
- else
- let pipecmd = 'cat Xtestin | '
- endif
- if RunVimPiped(before, after, '-', pipecmd)
- let lines = readfile('Xtestout')
- call assert_equal(linesin, lines)
- else
- call assert_equal('', 'RunVimPiped failed.')
- endif
- call delete('Xtestout')
- call delete('Xtestin')
-endfunc
-
-func Test_read_fifo_utf8()
- if !has('unix')
- return
- endif
- " Using bash/zsh's process substitution.
- if executable('bash')
- set shell=bash
- elseif executable('zsh')
- set shell=zsh
- else
- return
- endif
- let linesin = ['テスト', '€ÀÈÌÒÙ']
- call writefile(linesin, 'Xtestin')
- let before = [
- \ 'set enc=utf-8',
- \ 'set fencs=cp932,utf-8',
- \ ]
- let after = [
- \ 'write ++enc=utf-8 Xtestout',
- \ 'quit!',
- \ ]
- if RunVim(before, after, '<(cat Xtestin)')
- let lines = readfile('Xtestout')
- call assert_equal(linesin, lines)
- else
- call assert_equal('', 'RunVim failed.')
- endif
- call delete('Xtestout')
- call delete('Xtestin')
-endfunc
-
-func Test_detect_ambiwidth()
- CheckRunVimInTerminal
-
- " Use the title termcap entries to output the escape sequence.
- call writefile([
- \ 'set enc=utf-8',
- \ 'set ambiwidth=double',
- \ 'call test_option_not_set("ambiwidth")',
- \ 'redraw',
- \ ], 'Xscript')
- let buf = RunVimInTerminal('-S Xscript', #{keep_t_u7: 1})
- call term_wait(buf)
- call term_sendkeys(buf, "S\<C-R>=&ambiwidth\<CR>\<Esc>")
- call WaitForAssert({-> assert_match('single', term_getline(buf, 1))})
-
- call StopVimInTerminal(buf)
- call delete('Xscript')
-endfunc
diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim
deleted file mode 100644
index d3059664e9..0000000000
--- a/src/nvim/testdir/test_stat.vim
+++ /dev/null
@@ -1,228 +0,0 @@
-" Tests for stat functions and checktime
-
-source check.vim
-
-func CheckFileTime(doSleep)
- let fnames = ['Xtest1.tmp', 'Xtest2.tmp', 'Xtest3.tmp']
- let times = []
- let result = 0
-
- " Use three files instead of localtim(), with a network filesystem the file
- " times may differ at bit
- let fl = ['Hello World!']
- for fname in fnames
- call writefile(fl, fname)
- call add(times, fname->getftime())
- if a:doSleep
- sleep 1
- endif
- endfor
-
- let time_correct = (times[0] <= times[1] && times[1] <= times[2])
- if a:doSleep || time_correct
- call assert_true(time_correct, printf('Expected %s <= %s <= %s', times[0], times[1], times[2]))
- call assert_equal(strlen(fl[0] . "\n"), fnames[0]->getfsize())
- call assert_equal('file', fnames[0]->getftype())
- call assert_equal('rw-', getfperm(fnames[0])[0:2])
- let result = 1
- endif
-
- for fname in fnames
- call delete(fname)
- endfor
- return result
-endfunc
-
-func Test_existent_file()
- " On some systems the file timestamp is rounded to a multiple of 2 seconds.
- " We need to sleep to handle that, but that makes the test slow. First try
- " without the sleep, and if it fails try again with the sleep.
- if CheckFileTime(0) == 0
- call CheckFileTime(1)
- endif
-endfunc
-
-func Test_existent_directory()
- let dname = '.'
-
- call assert_equal(0, getfsize(dname))
- call assert_equal('dir', getftype(dname))
- call assert_equal(has('win32') ? 'rw-' : 'rwx', getfperm(dname)[0:2])
-endfunc
-
-func SleepForTimestamp()
- " FAT has a granularity of 2 seconds, otherwise it's usually 1 second
- if has('win32')
- sleep 2
- else
- sleep 2
- endif
-endfunc
-
-func Test_checktime()
- let fname = 'Xtest.tmp'
-
- let fl = ['Hello World!']
- call writefile(fl, fname)
- set autoread
- exec 'e' fname
- call SleepForTimestamp()
- let fl = readfile(fname)
- let fl[0] .= ' - checktime'
- call writefile(fl, fname)
- checktime
- call assert_equal(fl[0], getline(1))
-
- call delete(fname)
-endfunc
-
-func Test_checktime_fast()
- CheckFeature nanotime
-
- let fname = 'Xtest.tmp'
-
- let fl = ['Hello World!']
- call writefile(fl, fname)
- set autoread
- exec 'e' fname
- let fl = readfile(fname)
- let fl[0] .= ' - checktime'
- sleep 10m " make test less flaky in Nvim
- call writefile(fl, fname)
- checktime
- call assert_equal(fl[0], getline(1))
-
- call delete(fname)
-endfunc
-
-func Test_autoread_fast()
- CheckFeature nanotime
-
- " this is timing sensitive
- let g:test_is_flaky = 1
-
- new Xautoread
- setlocal autoread
- call setline(1, 'foo')
- w!
- sleep 10m
- call writefile(['bar'], 'Xautoread')
- sleep 10m
- checktime
- call assert_equal('bar', trim(getline(1)))
-
- call delete('Xautoread')
-endfunc
-
-func Test_autoread_file_deleted()
- new Xautoread
- set autoread
- call setline(1, 'original')
- w!
-
- call SleepForTimestamp()
- if has('win32')
- silent !echo changed > Xautoread
- else
- silent !echo 'changed' > Xautoread
- endif
- checktime
- call assert_equal('changed', trim(getline(1)))
-
- call SleepForTimestamp()
- messages clear
- if has('win32')
- silent !del Xautoread
- else
- silent !rm Xautoread
- endif
- checktime
- call assert_match('E211:', execute('messages'))
- call assert_equal('changed', trim(getline(1)))
-
- call SleepForTimestamp()
- if has('win32')
- silent !echo recreated > Xautoread
- else
- silent !echo 'recreated' > Xautoread
- endif
- checktime
- call assert_equal('recreated', trim(getline(1)))
-
- call delete('Xautoread')
- bwipe!
-endfunc
-
-
-func Test_nonexistent_file()
- let fname = 'Xtest.tmp'
-
- call delete(fname)
- call assert_equal(-1, getftime(fname))
- call assert_equal(-1, getfsize(fname))
- call assert_equal('', getftype(fname))
- call assert_equal('', getfperm(fname))
-endfunc
-
-func Test_getftype()
- call assert_equal('file', getftype(v:progpath))
- call assert_equal('dir', getftype('.'))
-
- if !has('unix')
- return
- endif
-
- silent !ln -s Xfile Xlink
- call assert_equal('link', getftype('Xlink'))
- call delete('Xlink')
-
- if executable('mkfifo')
- silent !mkfifo Xfifo
- call assert_equal('fifo', getftype('Xfifo'))
- call delete('Xfifo')
- endif
-
- for cdevfile in systemlist('find /dev -type c -maxdepth 2 2>/dev/null')
- " On Mac /def/fd/2 is found but the type is "fifo"
- if cdevfile !~ '/dev/fd/'
- let type = getftype(cdevfile)
- " ignore empty result, can happen if the file disappeared
- if type != ''
- call assert_equal('cdev', type, 'for ' .. cdevfile)
- endif
- endif
- endfor
-
- for bdevfile in systemlist('find /dev -type b -maxdepth 2 2>/dev/null')
- let type = getftype(bdevfile)
- " ignore empty result, can happen if the file disappeared
- if type != ''
- call assert_equal('bdev', type, 'for ' .. bdevfile)
- endif
- endfor
-
- " The /run/ directory typically contains socket files.
- " If it does not, test won't fail but will not test socket files.
- for socketfile in systemlist('find /run -type s -maxdepth 2 2>/dev/null')
- let type = getftype(socketfile)
- " ignore empty result, can happen if the file disappeared
- if type != ''
- call assert_equal('socket', type, 'for ' .. socketfile)
- endif
- endfor
-
- " TODO: file type 'other' is not tested. How can we test it?
-endfunc
-
-func Test_win32_symlink_dir()
- " On Windows, non-admin users cannot create symlinks.
- " So we use an existing symlink for this test.
- if has('win32')
- " Check if 'C:\Users\All Users' is a symlink to a directory.
- let res = system('dir C:\Users /a')
- if match(res, '\C<SYMLINKD> *All Users') >= 0
- " Get the filetype of the symlink.
- call assert_equal('link', getftype('C:\Users\All Users'))
- endif
- endif
-endfunc
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
deleted file mode 100644
index 990c852ccd..0000000000
--- a/src/nvim/testdir/test_statusline.vim
+++ /dev/null
@@ -1,609 +0,0 @@
-" Test 'statusline'
-"
-" Not tested yet:
-" %N
-
-source view_util.vim
-source check.vim
-source term_util.vim
-
-func SetUp()
- set laststatus=2
-endfunc
-
-func TearDown()
- set laststatus&
-endfunc
-
-func s:get_statusline()
- return ScreenLines(&lines - 1, &columns)[0]
-endfunc
-
-func StatuslineWithCaughtError()
- let s:func_in_statusline_called = 1
- try
- call eval('unknown expression')
- catch
- endtry
- return ''
-endfunc
-
-func StatuslineWithError()
- let s:func_in_statusline_called = 1
- call eval('unknown expression')
- return ''
-endfunc
-
-" Function used to display syntax group.
-func SyntaxItem()
- 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()
- let s:func_in_statusline_called = 0
- let statusline = '%{StatuslineWithCaughtError()}'
- let &statusline = statusline
- redrawstatus
- call assert_true(s:func_in_statusline_called)
- call assert_equal(statusline, &statusline)
- set statusline=
-endfunc
-
-func Test_statusline_will_be_disabled_with_error()
- let s:func_in_statusline_called = 0
- let statusline = '%{StatuslineWithError()}'
- try
- let &statusline = statusline
- redrawstatus
- catch
- endtry
- call assert_true(s:func_in_statusline_called)
- call assert_equal('', &statusline)
- set statusline=
-endfunc
-
-func Test_statusline()
- CheckFeature quickfix
-
- " %a: Argument list ({current} of {max})
- set statusline=%a
- call assert_match('^\s*$', s:get_statusline())
- arglocal a1 a2
- rewind
- call assert_match('^ (1 of 2)\s*$', s:get_statusline())
- next
- call assert_match('^ (2 of 2)\s*$', s:get_statusline())
- e Xstatusline
- call assert_match('^ ((2) of 2)\s*$', s:get_statusline())
-
- only
- set splitbelow
- call setline(1, range(1, 10000))
-
- " %b: Value of character under cursor.
- " %B: As above, in hexadecimal.
- call cursor(9000, 1)
- set statusline=%b,%B
- call assert_match('^57,39\s*$', s:get_statusline())
-
- " %o: Byte number in file of byte under cursor, first byte is 1.
- " %O: As above, in hexadecimal.
- set statusline=%o,%O
- set fileformat=dos
- call assert_match('^52888,CE98\s*$', s:get_statusline())
- set fileformat=mac
- call assert_match('^43889,AB71\s*$', s:get_statusline())
- set fileformat=unix
- call assert_match('^43889,AB71\s*$', s:get_statusline())
- set fileformat&
-
- " %f: Path to the file in the buffer, as typed or relative to current dir.
- set statusline=%f
- call assert_match('^Xstatusline\s*$', s:get_statusline())
-
- " %F: Full path to the file in the buffer.
- set statusline=%F
- call assert_match('/testdir/Xstatusline\s*$', s:get_statusline())
-
- " Test for min and max width with %(. For some reason, if this test is moved
- " after the below test for the help buffer flag, then the code to truncate
- " the string is not executed.
- set statusline=%015(%f%)
- call assert_match('^ Xstatusline\s*$', s:get_statusline())
- set statusline=%.6(%f%)
- call assert_match('^<sline\s*$', s:get_statusline())
- set statusline=%14f
- call assert_match('^ Xstatusline\s*$', s:get_statusline())
- set statusline=%.4L
- call assert_match('^10>3\s*$', s:get_statusline())
-
- " %h: Help buffer flag, text is "[help]".
- " %H: Help buffer flag, text is ",HLP".
- set statusline=%h,%H
- call assert_match('^,\s*$', s:get_statusline())
- help
- call assert_match('^\[Help\],HLP\s*$', s:get_statusline())
- helpclose
-
- " %k: Value of "b:keymap_name" or 'keymap'
- " when :lmap mappings are being used: <keymap>"
- set statusline=%k
- if has('keymap')
- set keymap=esperanto
- call assert_match('^<Eo>\s*$', s:get_statusline())
- set keymap&
- else
- call assert_match('^\s*$', s:get_statusline())
- endif
-
- " %l: Line number.
- " %L: Number of line in buffer.
- " %c: Column number.
- set statusline=%l/%L,%c
- call assert_match('^9000/10000,1\s*$', s:get_statusline())
-
- " %m: Modified flag, text is "[+]", "[-]" if 'modifiable' is off.
- " %M: Modified flag, text is ",+" or ",-".
- set statusline=%m%M
- call assert_match('^\[+\],+\s*$', s:get_statusline())
- set nomodifiable
- call assert_match('^\[+-\],+-\s*$', s:get_statusline())
- write
- call assert_match('^\[-\],-\s*$', s:get_statusline())
- set modifiable&
- call assert_match('^\s*$', s:get_statusline())
-
- " %n: Buffer number.
- set statusline=%n
- call assert_match('^'.bufnr('%').'\s*$', s:get_statusline())
-
- " %p: Percentage through file in lines as in CTRL-G.
- " %P: Percentage through file of displayed window.
- set statusline=%p,%P
- 0
- call assert_match('^0,Top\s*$', s:get_statusline())
- norm G
- call assert_match('^100,Bot\s*$', s:get_statusline())
- 9000
- " Don't check the exact percentage as it depends on the window size
- call assert_match('^90,\(Top\|Bot\|\d\+%\)\s*$', s:get_statusline())
-
- " %q: "[Quickfix List]", "[Location List]" or empty.
- set statusline=%q
- call assert_match('^\s*$', s:get_statusline())
- copen
- call assert_match('^\[Quickfix List\]\s*$', s:get_statusline())
- cclose
- lexpr getline(1, 2)
- lopen
- call assert_match('^\[Location List\]\s*$', s:get_statusline())
- lclose
-
- " %r: Readonly flag, text is "[RO]".
- " %R: Readonly flag, text is ",RO".
- set statusline=%r,%R
- call assert_match('^,\s*$', s:get_statusline())
- help
- call assert_match('^\[RO\],RO\s*$', s:get_statusline())
- helpclose
-
- " %t: File name (tail) of file in the buffer.
- set statusline=%t
- call assert_match('^Xstatusline\s*$', s:get_statusline())
-
- " %v: Virtual column number.
- " %V: Virtual column number as -{num}. Not displayed if equal to 'c'.
- call cursor(9000, 2)
- set statusline=%v,%V
- call assert_match('^2,\s*$', s:get_statusline())
- set virtualedit=all
- norm 10|
- call assert_match('^10,-10\s*$', s:get_statusline())
- set list
- call assert_match('^10,-10\s*$', s:get_statusline())
- set virtualedit&
- exe "norm A\<Tab>\<Tab>a\<Esc>"
- " In list mode a <Tab> is shown as "^I", which is 2-wide.
- call assert_match('^9,-9\s*$', s:get_statusline())
- set list&
- " Now the second <Tab> ends at the 16th screen column.
- call assert_match('^17,-17\s*$', s:get_statusline())
- undo
-
- " %w: Preview window flag, text is "[Preview]".
- " %W: Preview window flag, text is ",PRV".
- set statusline=%w%W
- call assert_match('^\s*$', s:get_statusline())
- pedit
- wincmd j
- call assert_match('^\[Preview\],PRV\s*$', s:get_statusline())
- pclose
-
- " %y: Type of file in the buffer, e.g., "[vim]". See 'filetype'.
- " %Y: Type of file in the buffer, e.g., ",VIM". See 'filetype'.
- set statusline=%y\ %Y
- call assert_match('^\s*$', s:get_statusline())
- setfiletype vim
- call assert_match('^\[vim\] VIM\s*$', s:get_statusline())
-
- " %=: Separation point between left and right aligned items.
- set statusline=foo%=bar
- call assert_match('^foo\s\+bar\s*$', s:get_statusline())
-
- " Test min/max width, leading zeroes, left/right justify.
- set statusline=%04B
- call cursor(9000, 1)
- call assert_match('^0039\s*$', s:get_statusline())
- set statusline=#%4B#
- call assert_match('^# 39#\s*$', s:get_statusline())
- set statusline=#%-4B#
- call assert_match('^#39 #\s*$', s:get_statusline())
- set statusline=%.6f
- call assert_match('^<sline\s*$', s:get_statusline())
-
- " %<: Where to truncate.
- " First check with when %< should not truncate with many columns
- exe 'set statusline=a%<b' . repeat('c', &columns - 3) . 'd'
- call assert_match('^abc\+d$', s:get_statusline())
- exe 'set statusline=a' . repeat('b', &columns - 2) . '%<c'
- call assert_match('^ab\+c$', s:get_statusline())
- " Then check when %< should truncate when there with too few columns.
- exe 'set statusline=a%<b' . repeat('c', &columns - 2) . 'd'
- call assert_match('^a<c\+d$', s:get_statusline())
- exe 'set statusline=a' . repeat('b', &columns - 1) . '%<c'
- call assert_match('^ab\+>$', s:get_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/^/"/
- call assert_match('^vimLineComment\s*$', s:get_statusline())
- syntax off
-
- "%{%expr%}: evaluates enxpressions present in result of expr
- func! Inner_eval()
- return '%n some other text'
- endfunc
- func! Outer_eval()
- return 'some text %{%Inner_eval()%}'
- endfunc
- set statusline=%{%Outer_eval()%}
- call assert_match('^some text ' . bufnr() . ' some other text\s*$', s:get_statusline())
- delfunc Inner_eval
- delfunc Outer_eval
-
- "%{%expr%}: Doesn't get stuck in recursion
- func! Recurse_eval()
- return '%{%Recurse_eval()%}'
- endfunc
- set statusline=%{%Recurse_eval()%}
- call assert_match('^%{%Recurse_eval()%}\s*$', s:get_statusline())
- delfunc Recurse_eval
-
- "%(: Start of item group.
- set statusline=ab%(cd%q%)de
- call assert_match('^abde\s*$', s:get_statusline())
- copen
- call assert_match('^abcd\[Quickfix List]de\s*$', s:get_statusline())
- cclose
-
- " %#: Set highlight group. The name must follow and then a # again.
- set statusline=ab%#Todo#cd%#Error#ef
- call assert_match('^abcdef\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 3)
- let sa3=screenattr(&lines - 1, 5)
- call assert_notequal(sa1, sa2)
- call assert_notequal(sa1, sa3)
- call assert_notequal(sa2, sa3)
- call assert_equal(sa1, screenattr(&lines - 1, 2))
- call assert_equal(sa2, screenattr(&lines - 1, 4))
- call assert_equal(sa3, screenattr(&lines - 1, 6))
- call assert_equal(sa3, screenattr(&lines - 1, 7))
-
- " %*: Set highlight group to User{N}
- set statusline=a%1*b%0*c
- call assert_match('^abc\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 2)
- let sa3=screenattr(&lines - 1, 3)
- call assert_equal(sa1, sa3)
- call assert_notequal(sa1, sa2)
-
- " An empty group that contains highlight changes
- let g:a = ''
- set statusline=ab%(cd%1*%{g:a}%*%)de
- call assert_match('^abde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 4)
- call assert_equal(sa1, sa2)
- let g:a = 'X'
- call assert_match('^abcdXde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 5)
- let sa3=screenattr(&lines - 1, 7)
- call assert_equal(sa1, sa3)
- call assert_notequal(sa1, sa2)
-
- let g:a = ''
- set statusline=ab%1*%(cd%*%{g:a}%1*%)de
- call assert_match('^abde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 4)
- call assert_notequal(sa1, sa2)
- let g:a = 'X'
- call assert_match('^abcdXde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 3)
- let sa3=screenattr(&lines - 1, 5)
- let sa4=screenattr(&lines - 1, 7)
- call assert_notequal(sa1, sa2)
- call assert_equal(sa1, sa3)
- call assert_equal(sa2, sa4)
-
- " An empty group that contains highlight changes and doesn't reset them
- let g:a = ''
- set statusline=ab%(cd%1*%{g:a}%)de
- call assert_match('^abcdde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 5)
- call assert_notequal(sa1, sa2)
- let g:a = 'X'
- call assert_match('^abcdXde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 5)
- let sa3=screenattr(&lines - 1, 7)
- call assert_notequal(sa1, sa2)
- call assert_equal(sa2, sa3)
-
- let g:a = ''
- set statusline=ab%1*%(cd%*%{g:a}%)de
- call assert_match('^abcdde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 3)
- let sa3=screenattr(&lines - 1, 5)
- call assert_notequal(sa1, sa2)
- call assert_equal(sa1, sa3)
- let g:a = 'X'
- call assert_match('^abcdXde\s*$', s:get_statusline())
- let sa1=screenattr(&lines - 1, 1)
- let sa2=screenattr(&lines - 1, 3)
- let sa3=screenattr(&lines - 1, 5)
- let sa4=screenattr(&lines - 1, 7)
- call assert_notequal(sa1, sa2)
- call assert_equal(sa1, sa3)
- call assert_equal(sa1, sa4)
-
- let g:a = ''
- set statusline=%#Error#{%(\ %{g:a}\ %)}
- call assert_match('^{}\s*$', s:get_statusline())
- let g:a = 'X'
- call assert_match('^{ X }\s*$', s:get_statusline())
-
- " %%: a percent sign.
- set statusline=10%%
- call assert_match('^10%\s*$', s:get_statusline())
-
- " %!: evaluated expression is used as the option value
- 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
-
- " Test statusline works with 80+ items
- function! StatusLabel()
- redrawstatus
- return '[label]'
- endfunc
- let statusline = '%{StatusLabel()}'
- for i in range(150)
- let statusline .= '%#TabLine' . (i % 2 == 0 ? 'Fill' : 'Sel') . '#' . string(i)[0]
- endfor
- let &statusline = statusline
- redrawstatus
- set statusline&
- delfunc StatusLabel
-
-
- " Check statusline in current and non-current window
- " with the 'fillchars' option.
- set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:-
- vsplit
- set statusline=x%=y
- call assert_match('^x^\+y^x=\+y$', s:get_statusline())
- set fillchars&
- close
-
- %bw!
- call delete('Xstatusline')
- set statusline&
- 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 VerifyScreenDump(buf, 'Test_statusline_1', {})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('XTest_statusline')
-endfunc
-
-func Test_statusline_using_mode()
- CheckScreendump
-
- let lines =<< trim END
- setlocal statusline=-%{mode()}-
- split
- setlocal statusline=+%{mode()}+
- END
- call writefile(lines, 'XTest_statusline')
-
- let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 7, 'cols': 50})
- call VerifyScreenDump(buf, 'Test_statusline_mode_1', {})
-
- call term_sendkeys(buf, ":")
- call VerifyScreenDump(buf, 'Test_statusline_mode_2', {})
-
- " clean up
- call term_sendkeys(buf, "close\<CR>")
- call StopVimInTerminal(buf)
- call delete('XTest_statusline')
-endfunc
-
-func Test_statusline_after_split_vsplit()
- only
-
- " Make the status line of each window show the window number.
- set ls=2 stl=%{winnr()}
-
- split | redraw
- vsplit | redraw
-
- " The status line of the third window should read '3' here.
- call assert_equal('3', nr2char(screenchar(&lines - 1, 1)))
-
- only
- set ls& stl&
-endfunc
-
-" Test using a multibyte character for 'stl' and 'stlnc' items in 'fillchars'
-" with a custom 'statusline'
-func Test_statusline_mbyte_fillchar()
- only
- set fillchars=vert:\|,fold:-,stl:â”,stlnc:â•
- set statusline=a%=b
- call assert_match('^a\+â”\+b$', s:get_statusline())
- vnew
- call assert_match('^a\+â”\+bâ”a\+â•\+b$', s:get_statusline())
- wincmd w
- call assert_match('^a\+â•\+bâ•a\+â”\+b$', s:get_statusline())
- set statusline& fillchars&
- %bw!
-endfunc
-
-" Used to write beyond allocated memory. This assumes MAXPATHL is 4096 bytes.
-func Test_statusline_verylong_filename()
- let fname = repeat('x', 4090)
- " Nvim's swap file creation fails on Windows (E303) due to fname's length
- " exe "new " .. fname
- exe "noswapfile new " .. fname
- set buftype=help
- set previewwindow
- redraw
- bwipe!
-endfunc
-
-func Test_statusline_highlight_truncate()
- CheckScreendump
-
- let lines =<< trim END
- set laststatus=2
- hi! link User1 Directory
- hi! link User2 ErrorMsg
- set statusline=%.5(%1*ABC%2*DEF%1*GHI%)
- END
- call writefile(lines, 'XTest_statusline')
-
- let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 6})
- call VerifyScreenDump(buf, 'Test_statusline_hl', {})
-
- call StopVimInTerminal(buf)
- call delete('XTest_statusline')
-endfunc
-
-func Test_statusline_showcmd()
- CheckScreendump
-
- let lines =<< trim END
- func MyStatusLine()
- return '%S'
- endfunc
-
- set laststatus=2
- set statusline=%!MyStatusLine()
- set showcmdloc=statusline
- call setline(1, ['a', 'b', 'c'])
- set foldopen+=jump
- 1,2fold
- 3
- END
- call writefile(lines, 'XTest_statusline', 'D')
-
- let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 6})
-
- call term_sendkeys(buf, "g")
- call VerifyScreenDump(buf, 'Test_statusline_showcmd_1', {})
-
- " typing "gg" should open the fold
- call term_sendkeys(buf, "g")
- call VerifyScreenDump(buf, 'Test_statusline_showcmd_2', {})
-
- call term_sendkeys(buf, "\<C-V>Gl")
- call VerifyScreenDump(buf, 'Test_statusline_showcmd_3', {})
-
- call term_sendkeys(buf, "\<Esc>1234")
- call VerifyScreenDump(buf, 'Test_statusline_showcmd_4', {})
-
- call term_sendkeys(buf, "\<Esc>:set statusline=\<CR>")
- call term_sendkeys(buf, ":\<CR>")
- call term_sendkeys(buf, "1234")
- call VerifyScreenDump(buf, 'Test_statusline_showcmd_5', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
deleted file mode 100644
index c99a0d456d..0000000000
--- a/src/nvim/testdir/test_substitute.vim
+++ /dev/null
@@ -1,1385 +0,0 @@
-" Tests for the substitute (:s) command
-
-source shared.vim
-source check.vim
-source screendump.vim
-
-func Test_multiline_subst()
- enew!
- call append(0, ["1 aa",
- \ "bb",
- \ "cc",
- \ "2 dd",
- \ "ee",
- \ "3 ef",
- \ "gh",
- \ "4 ij",
- \ "5 a8",
- \ "8b c9",
- \ "9d",
- \ "6 e7",
- \ "77f",
- \ "xxxxx"])
-
- 1
- " test if replacing a line break works with a back reference
- /^1/,/^2/s/\n\(.\)/ \1/
- " test if inserting a line break works with a back reference
- /^3/,/^4/s/\(.\)$/\r\1/
- " test if replacing a line break with another line break works
- /^5/,/^6/s/\(\_d\{3}\)/x\1x/
- call assert_equal('1 aa bb cc 2 dd ee', getline(1))
- call assert_equal('3 e', getline(2))
- call assert_equal('f', getline(3))
- call assert_equal('g', getline(4))
- call assert_equal('h', getline(5))
- call assert_equal('4 i', getline(6))
- call assert_equal('j', getline(7))
- call assert_equal('5 ax8', getline(8))
- call assert_equal('8xb cx9', getline(9))
- call assert_equal('9xd', getline(10))
- call assert_equal('6 ex7', getline(11))
- call assert_equal('7x7f', getline(12))
- call assert_equal('xxxxx', getline(13))
- enew!
-endfunc
-
-func Test_substitute_variants()
- " Validate that all the 2-/3-letter variants which embed the flags into the
- " command name actually work.
- enew!
- let ln = 'Testing string'
- let variants = [
- \ { 'cmd': ':s/Test/test/c', 'exp': 'testing string', 'prompt': 'y' },
- \ { 'cmd': ':s/foo/bar/ce', 'exp': ln },
- \ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' },
- \ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' },
- \ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' },
- \ { 'cmd': ':s/t/r/c', 'exp': 'Testing string', 'prompt': 'n' },
- \ { 'cmd': ':s/t/r/cn', 'exp': ln },
- \ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' },
- \ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' },
- \ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' },
- \ { 'cmd': ':s/i/I/gc', 'exp': 'TestIng string', 'prompt': 'l' },
- \ { 'cmd': ':s/foo/bar/ge', 'exp': ln },
- \ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' },
- \ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' },
- \ { 'cmd': ':s/t/r/gI', 'exp': 'Tesring srring' },
- \ { 'cmd': ':s/t/r/gn', 'exp': ln },
- \ { 'cmd': ':s/t/r/gp', 'exp': 'Tesring srring' },
- \ { 'cmd': ':s/t/r/gl', 'exp': 'Tesring srring' },
- \ { 'cmd': ':s//r/gr', 'exp': 'Testr strr' },
- \ { 'cmd': ':s/t/r/ic', 'exp': 'resting string', 'prompt': 'y' },
- \ { 'cmd': ':s/foo/bar/ie', 'exp': ln },
- \ { 'cmd': ':s/t/r/i', 'exp': 'resting string' },
- \ { 'cmd': ':s/t/r/iI', 'exp': 'Tesring string' },
- \ { 'cmd': ':s/t/r/in', 'exp': ln },
- \ { 'cmd': ':s/t/r/ip', 'exp': 'resting string' },
- \ { 'cmd': ':s//r/ir', 'exp': 'Testr string' },
- \ { 'cmd': ':s/t/r/Ic', 'exp': 'Tesring string', 'prompt': 'y' },
- \ { 'cmd': ':s/foo/bar/Ie', 'exp': ln },
- \ { 'cmd': ':s/t/r/Ig', 'exp': 'Tesring srring' },
- \ { 'cmd': ':s/t/r/Ii', 'exp': 'resting string' },
- \ { 'cmd': ':s/t/r/I', 'exp': 'Tesring string' },
- \ { 'cmd': ':s/t/r/Ip', 'exp': 'Tesring string' },
- \ { 'cmd': ':s/t/r/Il', 'exp': 'Tesring string' },
- \ { 'cmd': ':s//r/Ir', 'exp': 'Testr string' },
- \ { 'cmd': ':s//r/rc', 'exp': 'Testr string', 'prompt': 'y' },
- \ { 'cmd': ':s//r/rg', 'exp': 'Testr strr' },
- \ { 'cmd': ':s//r/ri', 'exp': 'Testr string' },
- \ { 'cmd': ':s//r/rI', 'exp': 'Testr string' },
- \ { 'cmd': ':s//r/rn', 'exp': 'Testing string' },
- \ { 'cmd': ':s//r/rp', 'exp': 'Testr string' },
- \ { 'cmd': ':s//r/rl', 'exp': 'Testr string' },
- \ { 'cmd': ':s//r/r', 'exp': 'Testr string' },
- \ { 'cmd': ':s/i/I/gc', 'exp': 'Testing string', 'prompt': 'q' },
- \]
-
- for var in variants
- for run in [1, 2]
- let cmd = var.cmd
- if run == 2 && cmd =~ "/.*/.*/."
- " Change :s/from/to/{flags} to :s{flags}
- let cmd = substitute(cmd, '/.*/', '', '')
- endif
- call setline(1, [ln])
- let msg = printf('using "%s"', cmd)
- let @/='ing'
- let v:errmsg = ''
- call feedkeys(cmd . "\<CR>" . get(var, 'prompt', ''), 'ntx')
- " No error should exist (matters for testing e flag)
- call assert_equal('', v:errmsg, msg)
- call assert_equal(var.exp, getline('.'), msg)
- endfor
- endfor
-endfunc
-
-" Test the l, p, # flags.
-func Test_substitute_flags_lp()
- new
- call setline(1, "abc\tdef\<C-h>ghi")
-
- let a = execute('s/a/a/p')
- call assert_equal("\nabc def^Hghi", a)
-
- let a = execute('s/a/a/l')
- call assert_equal("\nabc^Idef^Hghi$", a)
-
- let a = execute('s/a/a/#')
- call assert_equal("\n 1 abc def^Hghi", a)
-
- let a = execute('s/a/a/p#')
- call assert_equal("\n 1 abc def^Hghi", a)
-
- let a = execute('s/a/a/l#')
- call assert_equal("\n 1 abc^Idef^Hghi$", a)
-
- let a = execute('s/a/a/')
- call assert_equal("", a)
-
- bwipe!
-endfunc
-
-func Test_substitute_repeat()
- " This caused an invalid memory access.
- split Xfile
- s/^/x
- call feedkeys("Qsc\<CR>y", 'tx')
- bwipe!
-endfunc
-
-" Tests for *sub-replace-special* and *sub-replace-expression* on :substitute.
-
-" Execute a list of :substitute command tests
-func Run_SubCmd_Tests(tests)
- enew!
- for t in a:tests
- let start = line('.') + 1
- let end = start + len(t[2]) - 1
- exe "normal o" . t[0]
- call cursor(start, 1)
- exe t[1]
- call assert_equal(t[2], getline(start, end), t[1])
- endfor
- enew!
-endfunc
-
-func Test_sub_cmd_1()
- set magic
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [['A', 's/A/&&/', ['AA']],
- \ ['B', 's/B/\&/', ['&']],
- \ ['C123456789', 's/C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
- \ ['D', 's/D/d/', ['d']],
- \ ['E', 's/E/~/', ['d']],
- \ ['F', 's/F/\~/', ['~']],
- \ ['G', 's/G/\ugg/', ['Gg']],
- \ ['H', 's/H/\Uh\Eh/', ['Hh']],
- \ ['I', 's/I/\lII/', ['iI']],
- \ ['J', 's/J/\LJ\EJ/', ['jJ']],
- \ ['K', 's/K/\Uk\ek/', ['Kk']],
- \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
- \ ['mMm', 's/M/\r/', ['m', 'm']],
- \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
- \ ['oOo', 's/O/\n/', ["o\no"]],
- \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
- \ ['qQq', 's/Q/\t/', ["q\tq"]],
- \ ['rRr', 's/R/\\/', ['r\r']],
- \ ['sSs', 's/S/\c/', ['scs']],
- \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
- \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
- \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
- \ ['\', 's/\\/\\\\/', ['\\']]
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-func Test_sub_cmd_2()
- set nomagic
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [['A', 's/A/&&/', ['&&']],
- \ ['B', 's/B/\&/', ['B']],
- \ ['C123456789', 's/\mC\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
- \ ['D', 's/D/d/', ['d']],
- \ ['E', 's/E/~/', ['~']],
- \ ['F', 's/F/\~/', ['~']],
- \ ['G', 's/G/\ugg/', ['Gg']],
- \ ['H', 's/H/\Uh\Eh/', ['Hh']],
- \ ['I', 's/I/\lII/', ['iI']],
- \ ['J', 's/J/\LJ\EJ/', ['jJ']],
- \ ['K', 's/K/\Uk\ek/', ['Kk']],
- \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
- \ ['mMm', 's/M/\r/', ['m', 'm']],
- \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
- \ ['oOo', 's/O/\n/', ["o\no"]],
- \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
- \ ['qQq', 's/Q/\t/', ["q\tq"]],
- \ ['rRr', 's/R/\\/', ['r\r']],
- \ ['sSs', 's/S/\c/', ['scs']],
- \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
- \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
- \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
- \ ['\', 's/\\/\\\\/', ['\\']]
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-func Test_sub_cmd_3()
- set nomagic
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [['aAa', "s/A/\\='\\'/", ['a\a']],
- \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']],
- \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']],
- \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']],
- \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']],
- \ ['fFf', "s/F/\\='\r'/", ['f', 'f']],
- \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']],
- \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']],
- \ ['iIi', "s/I/\\='\\\\\<C-V>\<C-J>'/", ["i\\\\\<C-V>", 'i']],
- \ ['jJj', "s/J/\\='\n'/", ['j', 'j']],
- \ ['kKk', 's/K/\="\r"/', ['k', 'k']],
- \ ['lLl', 's/L/\="\n"/', ['l', 'l']]
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-" Test for submatch() on :substitute.
-func Test_sub_cmd_4()
- set magic&
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [ ['aAa', "s/A/\\=substitute(submatch(0), '.', '\\', '')/",
- \ ['a\a']],
- \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/",
- \ ['b\b']],
- \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/",
- \ ["c\<C-V>", 'c']],
- \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/",
- \ ["d\<C-V>", 'd']],
- \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/",
- \ ["e\\\<C-V>", 'e']],
- \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/",
- \ ['f', 'f']],
- \ ['gGg', 's/G/\=substitute(submatch(0), ".", "\<C-V>\<C-J>", "")/',
- \ ["g\<C-V>", 'g']],
- \ ['hHh', 's/H/\=substitute(submatch(0), ".", "\\\<C-V>\<C-J>", "")/',
- \ ["h\<C-V>", 'h']],
- \ ['iIi', 's/I/\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-J>", "")/',
- \ ["i\\\<C-V>", 'i']],
- \ ['jJj', "s/J/\\=substitute(submatch(0), '.', '\\n', '')/",
- \ ['j', 'j']],
- \ ['kKk', "s/K/\\=substitute(submatch(0), '.', '\\r', '')/",
- \ ['k', 'k']],
- \ ['lLl', "s/L/\\=substitute(submatch(0), '.', '\\n', '')/",
- \ ['l', 'l']],
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-func Test_sub_cmd_5()
- set magic&
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [ ['A123456789', 's/A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=submatch(0) . submatch(9) . submatch(8) . submatch(7) . submatch(6) . submatch(5) . submatch(4) . submatch(3) . submatch(2) . submatch(1)/', ['A123456789987654321']],
- \ ['B123456789', 's/B\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=string([submatch(0, 1), submatch(9, 1), submatch(8, 1), submatch(7, 1), submatch(6, 1), submatch(5, 1), submatch(4, 1), submatch(3, 1), submatch(2, 1), submatch(1, 1)])/', ["[['B123456789'], ['9'], ['8'], ['7'], ['6'], ['5'], ['4'], ['3'], ['2'], ['1']]"]],
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-" Test for *:s%* on :substitute.
-func Test_sub_cmd_6()
- throw 'Skipped: Nvim does not support cpoptions flag "/"'
- set magic&
- set cpo+=/
-
- " List entry format: [input, cmd, output]
- let tests = [ ['A', 's/A/a/', ['a']],
- \ ['B', 's/B/%/', ['a']],
- \ ]
- call Run_SubCmd_Tests(tests)
-
- set cpo-=/
- let tests = [ ['C', 's/C/c/', ['c']],
- \ ['D', 's/D/%/', ['%']],
- \ ]
- call Run_SubCmd_Tests(tests)
-
- set cpo&
-endfunc
-
-" Test for :s replacing \n with line break.
-func Test_sub_cmd_7()
- set magic&
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']],
- \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']],
- \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]],
- \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]],
- \ ["E\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>E", 's/E\_.\{-}E/\=strtrans(string(submatch(0, 1)))/', [strtrans("['E\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>E']")]],
- \ ]
- call Run_SubCmd_Tests(tests)
-
- exe "normal oQ\nQ\<Esc>k"
- call assert_fails('s/Q[^\n]Q/\=submatch(0)."foobar"/', 'E486')
- enew!
-endfunc
-
-func TitleString()
- let check = 'foo' =~ 'bar'
- return ""
-endfunc
-
-func Test_sub_cmd_8()
- set titlestring=%{TitleString()}
-
- enew!
- call append(0, ['', 'test_one', 'test_two'])
- call cursor(1,1)
- /^test_one/s/.*/\="foo\nbar"/
- call assert_equal('foo', getline(2))
- call assert_equal('bar', getline(3))
- call feedkeys(':/^test_two/s/.*/\="foo\nbar"/c', "t")
- call feedkeys("\<CR>y", "xt")
- call assert_equal('foo', getline(4))
- call assert_equal('bar', getline(5))
-
- enew!
- set titlestring&
-endfunc
-
-" Test %s/\n// which is implemented as a special case to use a
-" more efficient join rather than doing a regular substitution.
-func Test_substitute_join()
- new
-
- call setline(1, ["foo\tbar", "bar\<C-H>foo"])
- let a = execute('%s/\n//')
- call assert_equal("", a)
- call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
- call assert_equal('\n', histget("search", -1))
-
- call setline(1, ["foo\tbar", "bar\<C-H>foo"])
- let a = execute('%s/\n//g')
- call assert_equal("", a)
- call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
- call assert_equal('\n', histget("search", -1))
-
- call setline(1, ["foo\tbar", "bar\<C-H>foo"])
- let a = execute('%s/\n//p')
- call assert_equal("\nfoo barbar^Hfoo", a)
- call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
- call assert_equal('\n', histget("search", -1))
-
- call setline(1, ["foo\tbar", "bar\<C-H>foo"])
- let a = execute('%s/\n//l')
- call assert_equal("\nfoo^Ibarbar^Hfoo$", a)
- call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
- call assert_equal('\n', histget("search", -1))
-
- call setline(1, ["foo\tbar", "bar\<C-H>foo"])
- let a = execute('%s/\n//#')
- call assert_equal("\n 1 foo barbar^Hfoo", a)
- call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
- call assert_equal('\n', histget("search", -1))
-
- call setline(1, ['foo', 'bar', 'baz', 'qux'])
- call execute('1,2s/\n//')
- call assert_equal(['foobarbaz', 'qux'], getline(1, '$'))
-
- bwipe!
-endfunc
-
-func Test_substitute_count()
- new
- call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
- 2
-
- s/foo/bar/3
- call assert_equal(['foo foo', 'bar foo', 'bar foo', 'bar foo', 'foo foo'],
- \ getline(1, '$'))
-
- call assert_fails('s/foo/bar/0', 'E939:')
-
- call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
- 2,4s/foo/bar/ 10
- call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
- \ getline(1, '$'))
-
- bwipe!
-endfunc
-
-" Test substitute 'n' flag (report number of matches, do not substitute).
-func Test_substitute_flag_n()
- new
- let lines = ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo']
- call setline(1, lines)
-
- call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/n'))
- call assert_equal("\n6 matches on 3 lines", execute('2,4s/foo/bar/gn'))
-
- " c flag (confirm) should be ignored when using n flag.
- call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/nc'))
-
- " No substitution should have been done.
- call assert_equal(lines, getline(1, '$'))
-
- %delete _
- call setline(1, ['A', 'Bar', 'Baz'])
- call assert_equal("\n1 match on 1 line", execute('s/\nB\@=//gn'))
-
- bwipe!
-endfunc
-
-func Test_substitute_errors()
- new
- call setline(1, 'foobar')
-
- call assert_fails('s/FOO/bar/', 'E486:')
- call assert_fails('s/foo/bar/@', 'E488:')
- call assert_fails('s/\(/bar/', 'E54:')
- call assert_fails('s afooabara', 'E146:')
- call assert_fails('s\\a', 'E10:')
-
- setl nomodifiable
- call assert_fails('s/foo/bar/', 'E21:')
-
- call assert_fails("let s=substitute([], 'a', 'A', 'g')", 'E730:')
- call assert_fails("let s=substitute('abcda', [], 'A', 'g')", 'E730:')
- call assert_fails("let s=substitute('abcda', 'a', [], 'g')", 'E730:')
- call assert_fails("let s=substitute('abcda', 'a', 'A', [])", 'E730:')
- call assert_fails("let s=substitute('abc', '\\%(', 'A', 'g')", 'E53:')
-
- bwipe!
-endfunc
-
-" Test for *sub-replace-special* and *sub-replace-expression* on substitute().
-func Test_sub_replace_1()
- " Run the tests with 'magic' on
- set magic
- set cpo&
- call assert_equal('AA', substitute('A', 'A', '&&', ''))
- call assert_equal('&', substitute('B', 'B', '\&', ''))
- call assert_equal('C123456789987654321', substitute('C123456789', 'C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', '\0\9\8\7\6\5\4\3\2\1', ''))
- call assert_equal('d', substitute('D', 'D', 'd', ''))
- call assert_equal('~', substitute('E', 'E', '~', ''))
- call assert_equal('~', substitute('F', 'F', '\~', ''))
- call assert_equal('Gg', substitute('G', 'G', '\ugg', ''))
- call assert_equal('Hh', substitute('H', 'H', '\Uh\Eh', ''))
- call assert_equal('iI', substitute('I', 'I', '\lII', ''))
- call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', ''))
- call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', ''))
- call assert_equal("l\<C-V>\<C-M>l",
- \ substitute('lLl', 'L', "\<C-V>\<C-M>", ''))
- call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', ''))
- call assert_equal("n\<C-V>\<C-M>n",
- \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", ''))
- call assert_equal("o\no", substitute('oOo', 'O', '\n', ''))
- call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', ''))
- call assert_equal("q\tq", substitute('qQq', 'Q', '\t', ''))
- call assert_equal('r\r', substitute('rRr', 'R', '\\', ''))
- call assert_equal('scs', substitute('sSs', 'S', '\c', ''))
- call assert_equal("u\nu", substitute('uUu', 'U', "\n", ''))
- call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", ''))
- call assert_equal("w\\w", substitute('wWw', 'W', "\\", ''))
- call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", ''))
- call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', ''))
- call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', ''))
- " \v or \V after $
- call assert_equal('abxx', substitute('abcd', 'xy$\v|cd$', 'xx', ''))
- call assert_equal('abxx', substitute('abcd', 'xy$\V\|cd\$', 'xx', ''))
-endfunc
-
-func Test_sub_replace_2()
- " Run the tests with 'magic' off
- set nomagic
- set cpo&
- call assert_equal('AA', substitute('A', 'A', '&&', ''))
- call assert_equal('&', substitute('B', 'B', '\&', ''))
- call assert_equal('C123456789987654321', substitute('C123456789', 'C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', '\0\9\8\7\6\5\4\3\2\1', ''))
- call assert_equal('d', substitute('D', 'D', 'd', ''))
- call assert_equal('~', substitute('E', 'E', '~', ''))
- call assert_equal('~', substitute('F', 'F', '\~', ''))
- call assert_equal('Gg', substitute('G', 'G', '\ugg', ''))
- call assert_equal('Hh', substitute('H', 'H', '\Uh\Eh', ''))
- call assert_equal('iI', substitute('I', 'I', '\lII', ''))
- call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', ''))
- call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', ''))
- call assert_equal("l\<C-V>\<C-M>l",
- \ substitute('lLl', 'L', "\<C-V>\<C-M>", ''))
- call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', ''))
- call assert_equal("n\<C-V>\<C-M>n",
- \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", ''))
- call assert_equal("o\no", substitute('oOo', 'O', '\n', ''))
- call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', ''))
- call assert_equal("q\tq", substitute('qQq', 'Q', '\t', ''))
- call assert_equal('r\r', substitute('rRr', 'R', '\\', ''))
- call assert_equal('scs', substitute('sSs', 'S', '\c', ''))
- call assert_equal("t\<C-M>t", substitute('tTt', 'T', "\r", ''))
- call assert_equal("u\nu", substitute('uUu', 'U', "\n", ''))
- call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", ''))
- call assert_equal('w\w', substitute('wWw', 'W', "\\", ''))
- call assert_equal('XxxX', substitute('X', 'X', '\L\uxXx\l\EX', ''))
- call assert_equal('yYYy', substitute('Y', 'Y', '\U\lYyY\u\Ey', ''))
-endfunc
-
-func Test_sub_replace_3()
- set magic&
- set cpo&
- call assert_equal('a\a', substitute('aAa', 'A', '\="\\"', ''))
- call assert_equal('b\\b', substitute('bBb', 'B', '\="\\\\"', ''))
- call assert_equal("c\rc", substitute('cCc', 'C', "\\=\"\r\"", ''))
- call assert_equal("d\\\rd", substitute('dDd', 'D', "\\=\"\\\\\r\"", ''))
- call assert_equal("e\\\\\re", substitute('eEe', 'E', "\\=\"\\\\\\\\\r\"", ''))
- call assert_equal('f\rf', substitute('fFf', 'F', '\="\\r"', ''))
- call assert_equal('j\nj', substitute('jJj', 'J', '\="\\n"', ''))
- call assert_equal("k\<C-M>k", substitute('kKk', 'K', '\="\r"', ''))
- call assert_equal("l\nl", substitute('lLl', 'L', '\="\n"', ''))
-endfunc
-
-" Test for submatch() on substitute().
-func Test_sub_replace_4()
- set magic&
- set cpo&
- call assert_equal('a\a', substitute('aAa', 'A',
- \ '\=substitute(submatch(0), ".", "\\", "")', ''))
- call assert_equal('b\b', substitute('bBb', 'B',
- \ '\=substitute(submatch(0), ".", "\\\\", "")', ''))
- call assert_equal("c\<C-V>\<C-M>c", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\<C-M>", "")', ''))
- call assert_equal("d\<C-V>\<C-M>d", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\<C-M>", "")', ''))
- call assert_equal("e\\\<C-V>\<C-M>e", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-M>", "")', ''))
- call assert_equal("f\<C-M>f", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', ''))
- call assert_equal("j\nj", substitute('jJj', 'J', '\=substitute(submatch(0), ".", "\\n", "")', ''))
- call assert_equal("k\rk", substitute('kKk', 'K', '\=substitute(submatch(0), ".", "\r", "")', ''))
- call assert_equal("l\nl", substitute('lLl', 'L', '\=substitute(submatch(0), ".", "\n", "")', ''))
-endfunc
-
-func Test_sub_replace_5()
- set magic&
- set cpo&
- call assert_equal('A123456789987654321', substitute('A123456789',
- \ 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
- \ '\=submatch(0) . submatch(9) . submatch(8) . ' .
- \ 'submatch(7) . submatch(6) . submatch(5) . ' .
- \ 'submatch(4) . submatch(3) . submatch(2) . submatch(1)',
- \ ''))
- call assert_equal("[['A123456789'], ['9'], ['8'], ['7'], ['6'], " .
- \ "['5'], ['4'], ['3'], ['2'], ['1']]",
- \ substitute('A123456789',
- \ 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
- \ '\=string([submatch(0, 1), submatch(9, 1), ' .
- \ 'submatch(8, 1), 7->submatch(1), submatch(6, 1), ' .
- \ 'submatch(5, 1), submatch(4, 1), submatch(3, 1), ' .
- \ 'submatch(2, 1), submatch(1, 1)])',
- \ ''))
-endfunc
-
-func Test_sub_replace_6()
- set magic&
- " set cpo+=/
- call assert_equal('a', substitute('A', 'A', 'a', ''))
- call assert_equal('%', substitute('B', 'B', '%', ''))
- " set cpo-=/
- call assert_equal('c', substitute('C', 'C', 'c', ''))
- call assert_equal('%', substitute('D', 'D', '%', ''))
-endfunc
-
-func Test_sub_replace_7()
- set magic&
- set cpo&
- call assert_equal('AA', substitute('AA', 'A.', '\=submatch(0)', ''))
- call assert_equal("B\nB", substitute("B\nB", 'B.', '\=submatch(0)', ''))
- call assert_equal("['B\n']B", substitute("B\nB", 'B.', '\=string(submatch(0, 1))', ''))
- call assert_equal('-abab', substitute('-bb', '\zeb', 'a', 'g'))
- call assert_equal('c-cbcbc', substitute('-bb', '\ze', 'c', 'g'))
-endfunc
-
-" Test for *:s%* on :substitute.
-func Test_sub_replace_8()
- new
- set magic&
- set cpo&
- $put =',,X'
- s/\(^\|,\)\ze\(,\|X\)/\1N/g
- call assert_equal('N,,NX', getline("$"))
- $put =',,Y'
- let cmd = ':s/\(^\|,\)\ze\(,\|Y\)/\1N/gc'
- call feedkeys(cmd . "\<CR>a", "xt")
- call assert_equal('N,,NY', getline("$"))
- :$put =',,Z'
- let cmd = ':s/\(^\|,\)\ze\(,\|Z\)/\1N/gc'
- call feedkeys(cmd . "\<CR>yy", "xt")
- call assert_equal('N,,NZ', getline("$"))
- enew! | close
-endfunc
-
-func Test_sub_replace_9()
- new
- set magic&
- set cpo&
- $put ='xxx'
- call feedkeys(":s/x/X/gc\<CR>yyq", "xt")
- call assert_equal('XXx', getline("$"))
- enew! | close
-endfunc
-
-func Test_sub_replace_10()
- set magic&
- set cpo&
- call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g'))
- call assert_equal('aaa', substitute('123', '\zs.', 'a', 'g'))
- call assert_equal('1a2a3a', substitute('123', '.\zs', 'a', 'g'))
- call assert_equal('a1a2a3a', substitute('123', '\ze', 'a', 'g'))
- call assert_equal('a1a2a3', substitute('123', '\ze.', 'a', 'g'))
- call assert_equal('aaa', substitute('123', '.\ze', 'a', 'g'))
- call assert_equal('aa2a3a', substitute('123', '1\|\ze', 'a', 'g'))
- 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 SubReplacerVar(text, ...)
- return a:text .. a:1[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'))
- call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacerVar', ['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']
- call setline(1, input)
- func Foo()
- return submatch(0)
- endfunc
- %s/aaa/\=Foo()/gn
- call assert_equal(input, getline(1, '$'))
- call assert_equal(1, &modifiable)
-
- delfunc Foo
- bw!
-endfunc
-
-func Test_sub_highlight_zero_match()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- call setline(1, ['one', 'two', 'three'])
- END
- call writefile(lines, 'XscriptSubHighlight', 'D')
- let buf = RunVimInTerminal('-S XscriptSubHighlight', #{rows: 8, cols: 60})
- call term_sendkeys(buf, ":%s/^/ /c\<CR>")
- call VerifyScreenDump(buf, 'Test_sub_highlight_zer_match_1', {})
-
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_nocatch_sub_failure_handling()
- " normal error results in all replacements
- func Foo()
- foobar
- endfunc
- new
- call setline(1, ['1 aaa', '2 aaa', '3 aaa'])
- " need silent! to avoid a delay when entering Insert mode
- silent! %s/aaa/\=Foo()/g
- call assert_equal(['1 0', '2 0', '3 0'], getline(1, 3))
-
- " Throw without try-catch causes abort after the first line.
- " We cannot test this, since it would stop executing the test script.
-
- " try/catch does not result in any changes
- func! Foo()
- throw 'error'
- endfunc
- call setline(1, ['1 aaa', '2 aaa', '3 aaa'])
- let error_caught = 0
- try
- %s/aaa/\=Foo()/g
- catch
- let error_caught = 1
- endtry
- call assert_equal(1, error_caught)
- call assert_equal(['1 aaa', '2 aaa', '3 aaa'], getline(1, 3))
-
- " Same, but using "n" flag so that "sandbox" gets set
- call setline(1, ['1 aaa', '2 aaa', '3 aaa'])
- let error_caught = 0
- try
- %s/aaa/\=Foo()/gn
- catch
- let error_caught = 1
- endtry
- call assert_equal(1, error_caught)
- call assert_equal(['1 aaa', '2 aaa', '3 aaa'], getline(1, 3))
-
- delfunc Foo
- bwipe!
-endfunc
-
-" Test ":s/pat/sub/" with different ~s in sub.
-func Test_replace_with_tilde()
- new
- " Set the last replace string to empty
- s/^$//
- call append(0, ['- Bug in "vPPPP" on this text:'])
- normal gg
- s/u/~u~/
- call assert_equal('- Bug in "vPPPP" on this text:', getline(1))
- s/i/~u~/
- call assert_equal('- Bug uuun "vPPPP" on this text:', getline(1))
- s/o/~~~/
- call assert_equal('- Bug uuun "vPPPP" uuuuuuuuun this text:', getline(1))
- close!
-endfunc
-
-func Test_replace_keeppatterns()
- new
- a
-foobar
-
-substitute foo asdf
-
-one two
-.
-
- normal gg
- /^substitute
- s/foo/bar/
- call assert_equal('foo', @/)
- call assert_equal('substitute bar asdf', getline('.'))
-
- /^substitute
- keeppatterns s/asdf/xyz/
- call assert_equal('^substitute', @/)
- call assert_equal('substitute bar xyz', getline('.'))
-
- exe "normal /bar /e\<CR>"
- call assert_equal(15, col('.'))
- normal -
- keeppatterns /xyz
- call assert_equal('bar ', @/)
- call assert_equal('substitute bar xyz', getline('.'))
- exe "normal 0dn"
- call assert_equal('xyz', getline('.'))
-
- close!
-endfunc
-
-func Test_sub_beyond_end()
- new
- call setline(1, '#')
- let @/ = '^#\n\zs'
- s///e
- call assert_equal('#', getline(1))
- bwipe!
-endfunc
-
-" Test for repeating last substitution using :~ and :&r
-func Test_repeat_last_sub()
- new
- call setline(1, ['blue green yellow orange white'])
- s/blue/red/
- let @/ = 'yellow'
- ~
- let @/ = 'white'
- :&r
- let @/ = 'green'
- s//gray
- call assert_equal('red gray red orange red', getline(1))
- close!
-endfunc
-
-" Test for Vi compatible substitution:
-" \/{string}/, \?{string}? and \&{string}&
-func Test_sub_vi_compatibility()
- new
- call setline(1, ['blue green yellow orange blue'])
- let @/ = 'orange'
- s\/white/
- let @/ = 'blue'
- s\?amber?
- let @/ = 'white'
- s\&green&
- call assert_equal('amber green yellow white green', getline(1))
- close!
-endfunc
-
-" Test for substitute with the new text longer than the original text
-func Test_sub_expand_text()
- new
- call setline(1, 'abcabcabcabcabcabcabcabc')
- s/b/\=repeat('B', 10)/g
- call assert_equal(repeat('aBBBBBBBBBBc', 8), getline(1))
- close!
-endfunc
-
-" Test for command failures when the last substitute pattern is not set.
-func Test_sub_with_no_last_pat()
- let lines =<< trim [SCRIPT]
- call assert_fails('~', 'E33:')
- call assert_fails('s//abc/g', 'E35:')
- call assert_fails('s\/bar', 'E35:')
- call assert_fails('s\&bar&', 'E33:')
- call writefile(v:errors, 'Xresult')
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '--clean -S Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
-
- " Nvim does not support cpoptions flag "/"'
- " let lines =<< trim [SCRIPT]
- " set cpo+=/
- " call assert_fails('s/abc/%/', 'E33:')
- " call writefile(v:errors, 'Xresult')
- " qall!
- " [SCRIPT]
- " call writefile(lines, 'Xscript')
- " if RunVim([], [], '--clean -S Xscript')
- " call assert_equal([], readfile('Xresult'))
- " endif
-
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-func Test_substitute()
- call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g'))
- " Substitute with special keys
- call assert_equal("a\<End>c", substitute('abc', "a.c", "a\<End>c", ''))
-endfunc
-
-func Test_substitute_expr()
- let g:val = 'XXX'
- call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', ''))
- call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, ''))
- call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
- \ '\=nr2char("0x" . submatch(1))', 'g'))
- call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
- \ {-> nr2char("0x" . submatch(1))}, 'g'))
-
- call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)',
- \ {-> submatch(2) . submatch(3) . submatch(1)}, ''))
-
- func Recurse()
- return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '')
- endfunc
- " recursive call works
- call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, ''))
-
- call assert_fails("let s=submatch([])", 'E745:')
- call assert_fails("let s=submatch(2, [])", 'E745:')
-endfunc
-
-func Test_invalid_submatch()
- " This was causing invalid memory access in Vim-7.4.2232 and older
- call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:')
- call assert_fails('eval submatch(-1)', 'E935:')
- call assert_equal('', submatch(0))
- call assert_equal('', submatch(1))
- call assert_equal([], submatch(0, 1))
- call assert_equal([], submatch(1, 1))
-endfunc
-
-func Test_submatch_list_concatenate()
- let pat = 'A\(.\)'
- let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])}
- call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]")
-endfunc
-
-func Test_substitute_expr_arg()
- call assert_equal('123456789-123456789=', substitute('123456789',
- \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
- \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
-
- call assert_equal('123456-123456=789', substitute('123456789',
- \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)',
- \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
-
- call assert_equal('123456789-123456789x=', substitute('123456789',
- \ '\(.\)\(.\)\(.*\)',
- \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
-
- call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:')
- call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:')
- call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:')
- call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:')
-endfunc
-
-" Test for using a function to supply the substitute string
-func Test_substitute_using_func()
- func Xfunc()
- return '1234'
- endfunc
- call assert_equal('a1234f', substitute('abcdef', 'b..e',
- \ function("Xfunc"), ''))
- delfunc Xfunc
-endfunc
-
-" Test for using submatch() with a multiline match
-func Test_substitute_multiline_submatch()
- new
- call setline(1, ['line1', 'line2', 'line3', 'line4'])
- %s/^line1\(\_.\+\)line4$/\=submatch(1)/
- call assert_equal(['', 'line2', 'line3', ''], getline(1, '$'))
- close!
-endfunc
-
-func Test_substitute_skipped_range()
- new
- if 0
- /1/5/2/2/\n
- endif
- call assert_equal([0, 1, 1, 0, 1], getcurpos())
- bwipe!
-endfunc
-
-" Test using the 'gdefault' option (when on, flag 'g' is default on).
-func Test_substitute_gdefault()
- new
-
- " First check without 'gdefault'
- call setline(1, 'foo bar foo')
- s/foo/FOO/
- call assert_equal('FOO bar foo', getline(1))
- call setline(1, 'foo bar foo')
- s/foo/FOO/g
- call assert_equal('FOO bar FOO', getline(1))
- call setline(1, 'foo bar foo')
- s/foo/FOO/gg
- call assert_equal('FOO bar foo', getline(1))
-
- " Then check with 'gdefault'
- set gdefault
- call setline(1, 'foo bar foo')
- s/foo/FOO/
- call assert_equal('FOO bar FOO', getline(1))
- call setline(1, 'foo bar foo')
- s/foo/FOO/g
- call assert_equal('FOO bar foo', getline(1))
- call setline(1, 'foo bar foo')
- s/foo/FOO/gg
- call assert_equal('FOO bar FOO', getline(1))
-
- " Setting 'compatible' should reset 'gdefault'
- call assert_equal(1, &gdefault)
- " set compatible
- set nogdefault
- call assert_equal(0, &gdefault)
- set nocompatible
- call assert_equal(0, &gdefault)
-
- bw!
-endfunc
-
-" This was using "old_sub" after it was freed.
-func Test_using_old_sub()
- " set compatible maxfuncdepth=10
- set maxfuncdepth=10
- new
- call setline(1, 'some text.')
- func Repl()
- ~
- s/
- endfunc
- silent! s/\%')/\=Repl()
-
- delfunc Repl
- bwipe!
- set nocompatible
-endfunc
-
-" This was switching windows in between computing the length and using it.
-func Test_sub_change_window()
- silent! lfile
- sil! norm o0000000000000000000000000000000000000000000000000000
- func Repl()
- lopen
- endfunc
- silent! s/\%')/\=Repl()
- bwipe!
- bwipe!
- delfunc Repl
-endfunc
-
-" This was undoign a change in between computing the length and using it.
-func Do_Test_sub_undo_change()
- new
- norm o0000000000000000000000000000000000000000000000000000
- silent! s/\%')/\=Repl()
- bwipe!
-endfunc
-
-func Test_sub_undo_change()
- func Repl()
- silent! norm g-
- endfunc
- call Do_Test_sub_undo_change()
-
- func! Repl()
- silent earlier
- endfunc
- call Do_Test_sub_undo_change()
-
- delfunc Repl
-endfunc
-
-" This was opening a command line window from the expression
-func Test_sub_open_cmdline_win()
- " the error only happens in a very specific setup, run a new Vim instance to
- " get a clean starting point.
- let lines =<< trim [SCRIPT]
- set vb t_vb=
- norm o0000000000000000000000000000000000000000000000000000
- func Replace()
- norm q/
- endfunc
- s/\%')/\=Replace()
- redir >Xresult
- messages
- redir END
- qall!
- [SCRIPT]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '-u NONE -S Xscript')
- call assert_match('E565: Not allowed to change text or change window',
- \ readfile('Xresult')->join('XX'))
- endif
-
- call delete('Xscript')
- call delete('Xresult')
-endfunc
-
-" This was editing a script file from the expression
-func Test_sub_edit_scriptfile()
- new
- norm o0000000000000000000000000000000000000000000000000000
- func EditScript()
- silent! scr! Xfile
- endfunc
- s/\%')/\=EditScript()
-
- delfunc EditScript
- bwipe!
-endfunc
-
-" Test for the 2-letter and 3-letter :substitute commands
-func Test_substitute_short_cmd()
- new
- call setline(1, ['one', 'one one one'])
- s/one/two
- call cursor(2, 1)
-
- " :sc
- call feedkeys(":sc\<CR>y", 'xt')
- call assert_equal('two one one', getline(2))
-
- " :scg
- call setline(2, 'one one one')
- call feedkeys(":scg\<CR>nyq", 'xt')
- call assert_equal('one two one', getline(2))
-
- " :sci
- call setline(2, 'ONE One onE')
- call feedkeys(":sci\<CR>y", 'xt')
- call assert_equal('two One onE', getline(2))
-
- " :scI
- set ignorecase
- call setline(2, 'ONE One one')
- call feedkeys(":scI\<CR>y", 'xt')
- call assert_equal('ONE One two', getline(2))
- set ignorecase&
-
- " :scn
- call setline(2, 'one one one')
- let t = execute('scn')->split("\n")
- call assert_equal(['1 match on 1 line'], t)
- call assert_equal('one one one', getline(2))
-
- " :scp
- call setline(2, "\tone one one")
- redir => output
- call feedkeys(":scp\<CR>y", 'xt')
- redir END
- call assert_equal(' two one one', output->split("\n")[-1])
- call assert_equal("\ttwo one one", getline(2))
-
- " :scl
- call setline(2, "\tone one one")
- redir => output
- call feedkeys(":scl\<CR>y", 'xt')
- redir END
- call assert_equal("^Itwo one one$", output->split("\n")[-1])
- call assert_equal("\ttwo one one", getline(2))
-
- " :sgc
- call setline(2, 'one one one one one')
- call feedkeys(":sgc\<CR>nyyq", 'xt')
- call assert_equal('one two two one one', getline(2))
-
- " :sg
- call setline(2, 'one one one')
- sg
- call assert_equal('two two two', getline(2))
-
- " :sgi
- call setline(2, 'ONE One onE')
- sgi
- call assert_equal('two two two', getline(2))
-
- " :sgI
- set ignorecase
- call setline(2, 'ONE One one')
- sgI
- call assert_equal('ONE One two', getline(2))
- set ignorecase&
-
- " :sgn
- call setline(2, 'one one one')
- let t = execute('sgn')->split("\n")
- call assert_equal(['3 matches on 1 line'], t)
- call assert_equal('one one one', getline(2))
-
- " :sgp
- call setline(2, "\tone one one")
- redir => output
- sgp
- redir END
- call assert_equal(' two two two', output->split("\n")[-1])
- call assert_equal("\ttwo two two", getline(2))
-
- " :sgl
- call setline(2, "\tone one one")
- redir => output
- sgl
- redir END
- call assert_equal("^Itwo two two$", output->split("\n")[-1])
- call assert_equal("\ttwo two two", getline(2))
-
- " :sgr
- call setline(2, "one one one")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- sgr
- call assert_equal('xyz xyz xyz', getline(2))
-
- " :sic
- call cursor(1, 1)
- s/one/two/e
- call setline(2, "ONE One one")
- call cursor(2, 1)
- call feedkeys(":sic\<CR>y", 'xt')
- call assert_equal('two One one', getline(2))
-
- " :si
- call setline(2, "ONE One one")
- si
- call assert_equal('two One one', getline(2))
-
- " :siI
- call setline(2, "ONE One one")
- siI
- call assert_equal('ONE One two', getline(2))
-
- " :sin
- call setline(2, 'ONE One onE')
- let t = execute('sin')->split("\n")
- call assert_equal(['1 match on 1 line'], t)
- call assert_equal('ONE One onE', getline(2))
-
- " :sip
- call setline(2, "\tONE One onE")
- redir => output
- sip
- redir END
- call assert_equal(' two One onE', output->split("\n")[-1])
- call assert_equal("\ttwo One onE", getline(2))
-
- " :sir
- call setline(2, "ONE One onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- sir
- call assert_equal('xyz One onE', getline(2))
-
- " :sIc
- call cursor(1, 1)
- s/one/two/e
- call setline(2, "ONE One one")
- call cursor(2, 1)
- call feedkeys(":sIc\<CR>y", 'xt')
- call assert_equal('ONE One two', getline(2))
-
- " :sIg
- call setline(2, "ONE one onE one")
- sIg
- call assert_equal('ONE two onE two', getline(2))
-
- " :sIi
- call setline(2, "ONE One one")
- sIi
- call assert_equal('two One one', getline(2))
-
- " :sI
- call setline(2, "ONE One one")
- sI
- call assert_equal('ONE One two', getline(2))
-
- " :sIn
- call setline(2, 'ONE One one')
- let t = execute('sIn')->split("\n")
- call assert_equal(['1 match on 1 line'], t)
- call assert_equal('ONE One one', getline(2))
-
- " :sIp
- call setline(2, "\tONE One one")
- redir => output
- sIp
- redir END
- call assert_equal(' ONE One two', output->split("\n")[-1])
- call assert_equal("\tONE One two", getline(2))
-
- " :sIl
- call setline(2, "\tONE onE one")
- redir => output
- sIl
- redir END
- call assert_equal("^IONE onE two$", output->split("\n")[-1])
- call assert_equal("\tONE onE two", getline(2))
-
- " :sIr
- call setline(2, "ONE one onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- sIr
- call assert_equal('ONE xyz onE', getline(2))
-
- " :src
- call setline(2, "ONE one one")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- call feedkeys(":src\<CR>y", 'xt')
- call assert_equal('ONE xyz one', getline(2))
-
- " :srg
- call setline(2, "one one one")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- srg
- call assert_equal('xyz xyz xyz', getline(2))
-
- " :sri
- call setline(2, "ONE one onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- sri
- call assert_equal('xyz one onE', getline(2))
-
- " :srI
- call setline(2, "ONE one onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- srI
- call assert_equal('ONE xyz onE', getline(2))
-
- " :srn
- call setline(2, "ONE one onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- let t = execute('srn')->split("\n")
- call assert_equal(['1 match on 1 line'], t)
- call assert_equal('ONE one onE', getline(2))
-
- " :srp
- call setline(2, "\tONE one onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- redir => output
- srp
- redir END
- call assert_equal(' ONE xyz onE', output->split("\n")[-1])
- call assert_equal("\tONE xyz onE", getline(2))
-
- " :srl
- call setline(2, "\tONE one onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- redir => output
- srl
- redir END
- call assert_equal("^IONE xyz onE$", output->split("\n")[-1])
- call assert_equal("\tONE xyz onE", getline(2))
-
- " :sr
- call setline(2, "ONE one onE")
- call cursor(2, 1)
- s/abc/xyz/e
- let @/ = 'one'
- sr
- call assert_equal('ONE xyz onE', getline(2))
-
- " :sce
- s/abc/xyz/e
- call assert_fails("sc", 'E486:')
- sce
- " :sge
- call assert_fails("sg", 'E486:')
- sge
- " :sie
- call assert_fails("si", 'E486:')
- sie
- " :sIe
- call assert_fails("sI", 'E486:')
- sIe
-
- bw!
-endfunc
-
-" This should be done last to reveal a memory leak when vim_regsub_both() is
-" called to evaluate an expression but it is not used in a second call.
-func Test_z_substitute_expr_leak()
- func SubExpr()
- ~n
- endfunc
- silent! s/\%')/\=SubExpr()
- delfunc SubExpr
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim
deleted file mode 100644
index bf88bd4453..0000000000
--- a/src/nvim/testdir/test_suspend.vim
+++ /dev/null
@@ -1,63 +0,0 @@
-" Test :suspend
-
-source shared.vim
-source term_util.vim
-
-func CheckSuspended(buf, fileExists)
- call WaitForAssert({-> assert_match('[$#] $', term_getline(a:buf, '.'))})
-
- if a:fileExists
- call assert_equal(['foo'], readfile('Xfoo'))
- else
- " Without 'autowrite', buffer should not be written.
- call assert_equal(0, filereadable('Xfoo'))
- endif
-
- call term_sendkeys(a:buf, "fg\<CR>\<C-L>")
- call WaitForAssert({-> assert_equal(' 1 foo', term_getline(a:buf, '.'))})
-endfunc
-
-func Test_suspend()
- if !has('terminal') || !executable('/bin/sh')
- return
- endif
-
- let buf = term_start('/bin/sh')
- " Wait for shell prompt.
- call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
-
- call term_sendkeys(buf, GetVimCommandClean()
- \ . " -X"
- \ . " -c 'set nu'"
- \ . " -c 'call setline(1, \"foo\")'"
- \ . " Xfoo\<CR>")
- " Cursor in terminal buffer should be on first line in spawned vim.
- call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
-
- for suspend_cmd in [":suspend\<CR>",
- \ ":stop\<CR>",
- \ ":suspend!\<CR>",
- \ ":stop!\<CR>",
- \ "\<C-Z>"]
- " Suspend and wait for shell prompt.
- call term_sendkeys(buf, suspend_cmd)
- call CheckSuspended(buf, 0)
- endfor
-
- " Test that :suspend! with 'autowrite' writes content of buffers if modified.
- call term_sendkeys(buf, ":set autowrite\<CR>")
- call assert_equal(0, filereadable('Xfoo'))
- call term_sendkeys(buf, ":suspend\<CR>")
- " Wait for shell prompt.
- call CheckSuspended(buf, 1)
-
- " Quit gracefully to dump coverage information.
- call term_sendkeys(buf, ":qall!\<CR>")
- call term_wait(buf)
- " Wait until Vim actually exited and shell shows a prompt
- call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
- call StopShellInTerminal(buf)
-
- exe buf . 'bwipe!'
- call delete('Xfoo')
-endfunc
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
deleted file mode 100644
index cf46b4c5bd..0000000000
--- a/src/nvim/testdir/test_swap.vim
+++ /dev/null
@@ -1,583 +0,0 @@
-" Tests for the swap feature
-
-source check.vim
-source shared.vim
-source term_util.vim
-
-func s:swapname()
- return trim(execute('swapname'))
-endfunc
-
-" Tests for 'directory' option.
-func Test_swap_directory()
- if !has("unix")
- return
- endif
- let content = ['start of testfile',
- \ 'line 2 Abcdefghij',
- \ 'line 3 Abcdefghij',
- \ 'end of testfile']
- call writefile(content, 'Xtest1')
-
- " '.', swap file in the same directory as file
- set dir=.,~
-
- " Verify that the swap file doesn't exist in the current directory
- call assert_equal([], glob(".Xtest1*.swp", 1, 1, 1))
- edit Xtest1
- let swfname = s:swapname()
- call assert_equal([swfname], glob(swfname, 1, 1, 1))
-
- " './dir', swap file in a directory relative to the file
- set dir=./Xtest2,.,~
-
- call mkdir("Xtest2")
- edit Xtest1
- call assert_equal([], glob(swfname, 1, 1, 1))
- let swfname = "Xtest2/Xtest1.swp"
- call assert_equal(swfname, s:swapname())
- call assert_equal([swfname], glob("Xtest2/*", 1, 1, 1))
-
- " 'dir', swap file in directory relative to the current dir
- set dir=Xtest.je,~
-
- call mkdir("Xtest.je")
- call writefile(content, 'Xtest2/Xtest3')
- edit Xtest2/Xtest3
- call assert_equal(["Xtest2/Xtest3"], glob("Xtest2/*", 1, 1, 1))
- let swfname = "Xtest.je/Xtest3.swp"
- call assert_equal(swfname, s:swapname())
- call assert_equal([swfname], glob("Xtest.je/*", 1, 1, 1))
-
- set dir&
- call delete("Xtest1")
- call delete("Xtest2", "rf")
- call delete("Xtest.je", "rf")
-endfunc
-
-func Test_swap_group()
- if !has("unix")
- return
- endif
- let groups = split(system('groups'))
- if len(groups) <= 1
- throw 'Skipped: need at least two groups, got ' . string(groups)
- endif
-
- try
- call delete('Xtest')
- split Xtest
- call setline(1, 'just some text')
- wq
- if system('ls -l Xtest') !~ ' ' . groups[0] . ' \d'
- throw 'Skipped: test file does not have the first group'
- else
- silent !chmod 640 Xtest
- call system('chgrp ' . groups[1] . ' Xtest')
- if system('ls -l Xtest') !~ ' ' . groups[1] . ' \d'
- throw 'Skipped: cannot set second group on test file'
- else
- split Xtest
- let swapname = substitute(execute('swapname'), '[[:space:]]', '', 'g')
- call assert_match('Xtest', swapname)
- " Group of swapfile must now match original file.
- call assert_match(' ' . groups[1] . ' \d', system('ls -l ' . swapname))
-
- bwipe!
- endif
- endif
- finally
- call delete('Xtest')
- endtry
-endfunc
-
-func Test_missing_dir()
- call mkdir('Xswapdir')
- exe 'set directory=' . getcwd() . '/Xswapdir'
-
- call assert_equal('', glob('foo'))
- call assert_equal('', glob('bar'))
- edit foo/x.txt
- " This should not give a warning for an existing swap file.
- split bar/x.txt
- only
-
- " Delete the buffer so that swap file is removed before we try to delete the
- " directory. That fails on MS-Windows.
- %bdelete!
- set directory&
- call delete('Xswapdir', 'rf')
-endfunc
-
-func Test_swapinfo()
- new Xswapinfo
- call setline(1, ['one', 'two', 'three'])
- w
- let fname = s:swapname()
- call assert_match('Xswapinfo', fname)
- let info = fname->swapinfo()
-
- let ver = printf('VIM %d.%d', v:version / 100, v:version % 100)
- call assert_equal(ver, info.version)
-
- call assert_match('\w', info.user)
- " host name is truncated to 39 bytes in the swap file
- call assert_equal(hostname()[:38], info.host)
- call assert_match('Xswapinfo', info.fname)
- call assert_match(0, info.dirty)
- call assert_equal(getpid(), info.pid)
- call assert_match('^\d*$', info.mtime)
- if has_key(info, 'inode')
- call assert_match('\d', info.inode)
- endif
- bwipe!
- call delete(fname)
- call delete('Xswapinfo')
-
- let info = swapinfo('doesnotexist')
- call assert_equal('Cannot open file', info.error)
-
- call writefile(['burp'], 'Xnotaswapfile')
- let info = swapinfo('Xnotaswapfile')
- call assert_equal('Cannot read file', info.error)
- call delete('Xnotaswapfile')
-
- call writefile([repeat('x', 10000)], 'Xnotaswapfile')
- let info = swapinfo('Xnotaswapfile')
- call assert_equal('Not a swap file', info.error)
- call delete('Xnotaswapfile')
-endfunc
-
-func Test_swapname()
- edit Xtest1
- let expected = s:swapname()
- call assert_equal(expected, swapname('%'))
-
- new Xtest2
- let buf = bufnr('%')
- let expected = s:swapname()
- wincmd p
- call assert_equal(expected, buf->swapname())
-
- new Xtest3
- setlocal noswapfile
- call assert_equal('', swapname('%'))
-
- bwipe!
- call delete('Xtest1')
- call delete('Xtest2')
- call delete('Xtest3')
-endfunc
-
-func Test_swapfile_delete()
- autocmd! SwapExists
- function s:swap_exists()
- let v:swapchoice = s:swap_choice
- let s:swapname = v:swapname
- let s:filename = expand('<afile>')
- endfunc
- augroup test_swapfile_delete
- autocmd!
- autocmd SwapExists * call s:swap_exists()
- augroup END
-
-
- " Create a valid swapfile by editing a file.
- split XswapfileText
- 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.
- " Now editing the file will run into the process still existing
- quit
- call writefile(swapfile_bytes, swapfile_name)
- let s:swap_choice = 'e'
- let s:swapname = ''
- split XswapfileText
- quit
- call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
-
- " This test won't work as root because root can successfully run kill(1, 0)
- if !IsRoot()
- " Write the swapfile with a modified PID, now it will be automatically
- " deleted. Process 0x3fffffff most likely does not exist.
- let swapfile_bytes[24:27] = 0zffffff3f
- call writefile(swapfile_bytes, swapfile_name)
- let s:swapname = ''
- split XswapfileText
- quit
- call assert_equal('', s:swapname)
- endif
-
- " Now set the modified flag, the swap file will not be deleted
- let swapfile_bytes[28 + 80 + 899] = 0x55
- call writefile(swapfile_bytes, swapfile_name)
- let s:swapname = ''
- split XswapfileText
- quit
- call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
-
- call delete('XswapfileText')
- call delete(swapfile_name)
- augroup test_swapfile_delete
- autocmd!
- 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
-
-" Test for closing a split window automatically when a swap file is detected
-" and 'Q' is selected in the confirmation prompt.
-func Test_swap_split_win()
- autocmd! SwapExists
- augroup test_swap_splitwin
- autocmd!
- autocmd SwapExists * let v:swapchoice = 'q'
- 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)
- " Split edit the file again. This should fail to open the window
- try
- split Xtest.scr
- catch
- " E308 should be caught, not E306.
- call assert_exception('E308:') " Original file may have been changed
- endtry
- call assert_equal(1, winnr('$'))
-
- call delete('Xtest.scr')
- call delete(swapfile_name)
-
- augroup test_swap_splitwin
- autocmd!
- augroup END
- augroup! test_swap_splitwin
-endfunc
-
-" Test for selecting 'q' in the attention prompt
-func Test_swap_prompt_splitwin()
- CheckRunVimInTerminal
-
- call writefile(['foo bar'], 'Xfile1')
- edit Xfile1
- preserve " should help to make sure the swap file exists
-
- let buf = RunVimInTerminal('', {'rows': 20})
- call term_sendkeys(buf, ":set nomore\n")
- call term_sendkeys(buf, ":set noruler\n")
-
- call term_sendkeys(buf, ":split Xfile1\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: $', term_getline(buf, 20))})
- call term_sendkeys(buf, "q")
- call term_wait(buf)
- call term_sendkeys(buf, ":\<CR>")
- call WaitForAssert({-> assert_match('^:$', term_getline(buf, 20))})
- call term_sendkeys(buf, ":echomsg winnr('$')\<CR>")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))})
- call StopVimInTerminal(buf)
-
- " This caused Vim to crash when typing "q" at the swap file prompt.
- let buf = RunVimInTerminal('-c "au bufadd * let foo_w = wincol()"', {'rows': 18})
- call term_sendkeys(buf, ":e Xfile1\<CR>")
- call WaitForAssert({-> assert_match('More', term_getline(buf, 18))})
- call term_sendkeys(buf, " ")
- call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 18))})
- call term_sendkeys(buf, "q")
- call TermWait(buf)
- " check that Vim is still running
- call term_sendkeys(buf, ":echo 'hello'\<CR>")
- call WaitForAssert({-> assert_match('^hello', term_getline(buf, 18))})
- call term_sendkeys(buf, ":%bwipe!\<CR>")
- call StopVimInTerminal(buf)
-
- %bwipe!
- call delete('Xfile1')
-endfunc
-
-func Test_swap_symlink()
- CheckUnix
-
- call writefile(['text'], 'Xtestfile')
- silent !ln -s -f Xtestfile Xtestlink
-
- set dir=.
-
- " Test that swap file uses the name of the file when editing through a
- " symbolic link (so that editing the file twice is detected)
- edit Xtestlink
- call assert_match('Xtestfile\.swp$', s:swapname())
- bwipe!
-
- call mkdir('Xswapdir')
- exe 'set dir=' . getcwd() . '/Xswapdir//'
-
- " Check that this also works when 'directory' ends with '//'
- edit Xtestlink
- call assert_match('Xtestfile\.swp$', s:swapname())
- bwipe!
-
- set dir&
- call delete('Xtestfile')
- call delete('Xtestlink')
- call delete('Xswapdir', 'rf')
-endfunc
-
-func s:get_unused_pid(base)
- if has('job')
- " Execute 'echo' as a temporary job, and return its pid as an unused pid.
- if has('win32')
- let cmd = 'cmd /c echo'
- else
- let cmd = 'echo'
- endif
- let j = job_start(cmd)
- while job_status(j) ==# 'run'
- sleep 10m
- endwhile
- if job_status(j) ==# 'dead'
- return job_info(j).process
- endif
- endif
- " Must add four for MS-Windows to see it as a different one.
- return a:base + 4
-endfunc
-
-func s:blob_to_pid(b)
- return a:b[3] * 16777216 + a:b[2] * 65536 + a:b[1] * 256 + a:b[0]
-endfunc
-
-func s:pid_to_blob(i)
- let b = 0z
- let b[0] = and(a:i, 0xff)
- let b[1] = and(a:i / 256, 0xff)
- let b[2] = and(a:i / 65536, 0xff)
- let b[3] = and(a:i / 16777216, 0xff)
- return b
-endfunc
-
-func Test_swap_auto_delete()
- " 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')
-
- " Forget about the file, recreate the swap file, then edit it again. The
- " swap file should be automatically deleted.
- bwipe!
- " Change the process ID to avoid the "still running" warning.
- let swapfile_bytes[24:27] = s:pid_to_blob(s:get_unused_pid(
- \ s:blob_to_pid(swapfile_bytes[24:27])))
- call writefile(swapfile_bytes, swapfile_name)
- edit Xtest.scr
- " will end up using the same swap file after deleting the existing one
- call assert_equal(swapfile_name, swapname('%'))
- bwipe!
-
- " create the swap file again, but change the host name so that it won't be
- " deleted
- autocmd! SwapExists
- augroup test_swap_recover_ext
- autocmd!
- autocmd SwapExists * let v:swapchoice = 'e'
- augroup END
-
- " change the host name
- let swapfile_bytes[28 + 40] = swapfile_bytes[28 + 40] + 2
- call writefile(swapfile_bytes, swapfile_name)
- edit Xtest.scr
- call assert_equal(1, filereadable(swapfile_name))
- " will use another same swap file name
- call assert_notequal(swapfile_name, swapname('%'))
- bwipe!
-
- call delete('Xtest.scr')
- call delete(swapfile_name)
- augroup test_swap_recover_ext
- autocmd!
- augroup END
- augroup! test_swap_recover_ext
-endfunc
-
-" Test for renaming a buffer when the swap file is deleted out-of-band
-func Test_missing_swap_file()
- CheckUnix
- new Xfile2
- call delete(swapname(''))
- call assert_fails('file Xfile3', 'E301:')
- call assert_equal('Xfile3', bufname())
- call assert_true(bufexists('Xfile2'))
- call assert_true(bufexists('Xfile3'))
- %bw!
-endfunc
-
-" Test for :preserve command
-func Test_preserve()
- new Xfile4
- setlocal noswapfile
- call assert_fails('preserve', 'E313:')
- bw!
-endfunc
-
-" Test for the v:swapchoice variable
-func Test_swapchoice()
- call writefile(['aaa', 'bbb'], 'Xfile5')
- edit Xfile5
- preserve
- let swapfname = swapname('')
- let b = readblob(swapfname)
- bw!
- call writefile(b, swapfname)
-
- autocmd! SwapExists
-
- " Test for v:swapchoice = 'o' (readonly)
- augroup test_swapchoice
- autocmd!
- autocmd SwapExists * let v:swapchoice = 'o'
- augroup END
- edit Xfile5
- call assert_true(&readonly)
- call assert_equal(['aaa', 'bbb'], getline(1, '$'))
- %bw!
- call assert_true(filereadable(swapfname))
-
- " Test for v:swapchoice = 'a' (abort)
- augroup test_swapchoice
- autocmd!
- autocmd SwapExists * let v:swapchoice = 'a'
- augroup END
- try
- edit Xfile5
- catch /^Vim:Interrupt$/
- endtry
- call assert_equal('', @%)
- call assert_true(bufexists('Xfile5'))
- %bw!
- call assert_true(filereadable(swapfname))
-
- " Test for v:swapchoice = 'd' (delete)
- augroup test_swapchoice
- autocmd!
- autocmd SwapExists * let v:swapchoice = 'd'
- augroup END
- edit Xfile5
- call assert_equal('Xfile5', @%)
- %bw!
- call assert_false(filereadable(swapfname))
-
- call delete('Xfile5')
- call delete(swapfname)
- augroup test_swapchoice
- autocmd!
- augroup END
- augroup! test_swapchoice
-endfunc
-
-func Test_no_swap_file()
- call assert_equal("\nNo swap file", execute('swapname'))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_syn_attr.vim b/src/nvim/testdir/test_syn_attr.vim
deleted file mode 100644
index 88f9d0d84d..0000000000
--- a/src/nvim/testdir/test_syn_attr.vim
+++ /dev/null
@@ -1,51 +0,0 @@
-" Test syntax highlighting functions.
-
-func Test_missing_attr()
- throw 'Skipped: use test/functional/legacy/syn_attr_spec.lua'
-
- hi Mine term=bold cterm=italic
- call assert_equal('Mine', synIDattr(hlID("Mine"), "name"))
- call assert_equal('', synIDattr("Mine"->hlID(), "bg", 'term'))
- call assert_equal('', synIDattr("Mine"->hlID(), "fg", 'term'))
- call assert_equal('', synIDattr("Mine"->hlID(), "sp", 'term'))
- call assert_equal('1', synIDattr(hlID("Mine"), "bold", 'term'))
- call assert_equal('1', synIDattr(hlID("Mine"), "italic", 'cterm'))
- hi Mine term=reverse cterm=inverse
- call assert_equal('1', synIDattr(hlID("Mine"), "reverse", 'term'))
- call assert_equal('1', synIDattr(hlID("Mine"), "inverse", 'cterm'))
-
- hi Mine term=underline cterm=standout gui=undercurl
- call assert_equal('1', synIDattr(hlID("Mine"), "underline", 'term'))
- call assert_equal('1', synIDattr(hlID("Mine"), "standout", 'cterm'))
- call assert_equal('1', synIDattr("Mine"->hlID(), "undercurl", 'gui'))
-
- hi Mine term=underdouble cterm=underdotted gui=underdashed
- call assert_equal('1', synIDattr(hlID("Mine"), "underdouble", 'term'))
- call assert_equal('1', synIDattr(hlID("Mine"), "underdotted", 'cterm'))
- call assert_equal('1', synIDattr("Mine"->hlID(), "underdashed", 'gui'))
-
- hi Mine term=nocombine gui=strikethrough
- call assert_equal('1', synIDattr(hlID("Mine"), "strikethrough", 'gui'))
- call assert_equal('1', synIDattr(hlID("Mine"), "nocombine", 'term'))
- call assert_equal('', synIDattr(hlID("Mine"), "nocombine", 'gui'))
- hi Mine term=NONE cterm=NONE gui=NONE
- call assert_equal('', synIDattr(hlID("Mine"), "bold", 'term'))
- call assert_equal('', synIDattr(hlID("Mine"), "italic", 'cterm'))
- call assert_equal('', synIDattr(hlID("Mine"), "reverse", 'term'))
- call assert_equal('', synIDattr(hlID("Mine"), "inverse", 'cterm'))
- call assert_equal('', synIDattr(hlID("Mine"), "underline", 'term'))
- call assert_equal('', synIDattr(hlID("Mine"), "standout", 'cterm'))
- call assert_equal('', synIDattr(hlID("Mine"), "undercurl", 'gui'))
- call assert_equal('', synIDattr(hlID("Mine"), "strikethrough", 'gui'))
-
- if has('gui')
- let fontname = getfontname()
- if fontname == ''
- let fontname = 'something'
- endif
- exe "hi Mine guifg=blue guibg=red font='" . fontname . "'"
- call assert_equal('blue', synIDattr(hlID("Mine"), "fg", 'gui'))
- call assert_equal('red', synIDattr(hlID("Mine"), "bg", 'gui'))
- call assert_equal(fontname, synIDattr(hlID("Mine"), "font", 'gui'))
- endif
-endfunc
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
deleted file mode 100644
index 45230c4208..0000000000
--- a/src/nvim/testdir/test_syntax.vim
+++ /dev/null
@@ -1,981 +0,0 @@
-" Test for syntax and syntax iskeyword option
-
-source check.vim
-CheckFeature syntax
-
-source view_util.vim
-source screendump.vim
-
-func GetSyntaxItem(pat)
- let c = ''
- let a = ['a', getreg('a'), getregtype('a')]
- 0
- redraw!
- call search(a:pat, 'W')
- let synid = synID(line('.'), col('.'), 1)
- while synid == synID(line('.'), col('.'), 1)
- norm! v"ay
- " stop at whitespace
- if @a =~# '\s'
- break
- endif
- let c .= @a
- norm! l
- endw
- call call('setreg', a)
- 0
- return c
-endfunc
-
-func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "")
- " Assert that the characters starting at a given (line, col)
- " sequentially match the expected highlight groups.
- " If groups are provided as a string, each character is assumed to be a
- " group and spaces represent no group, useful for visually describing tests.
- let l:expectedGroups = type(a:expected) == v:t_string
- \ ? a:expected->split('\zs')->map({_, v -> trim(v)})
- \ : a:expected
- let l:errors = 0
- let l:msg = (a:msg->empty() ? "" : a:msg .. ": ")
- \ .. "Wrong highlight group at " .. a:lnum .. ","
-
- for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1)
- let l:errors += synID(a:lnum, l:i, a:trans)
- \ ->synIDattr("name")
- \ ->assert_equal(l:expectedGroups[l:i - 1],
- \ l:msg .. l:i)
- endfor
-endfunc
-
-func Test_syn_iskeyword()
- new
- call setline(1, [
- \ 'CREATE TABLE FOOBAR(',
- \ ' DLTD_BY VARCHAR2(100)',
- \ ');',
- \ ''])
-
- syntax on
- set ft=sql
- syn match SYN /C\k\+\>/
- hi link SYN ErrorMsg
- call assert_equal('DLTD_BY', GetSyntaxItem('DLTD'))
- /\<D\k\+\>/:norm! ygn
- call assert_equal('DLTD_BY', @0)
- redir @c
- syn iskeyword
- redir END
- call assert_equal("\nsyntax iskeyword not set", @c)
-
- syn iskeyword @,48-57,_,192-255
- redir @c
- syn iskeyword
- redir END
- call assert_equal("\nsyntax iskeyword @,48-57,_,192-255", @c)
-
- setlocal isk-=_
- call assert_equal('DLTD_BY', GetSyntaxItem('DLTD'))
- /\<D\k\+\>/:norm! ygn
- let b2 = @0
- call assert_equal('DLTD', @0)
-
- syn iskeyword clear
- redir @c
- syn iskeyword
- redir END
- call assert_equal("\nsyntax iskeyword not set", @c)
-
- quit!
-endfunc
-
-func Test_syntax_after_reload()
- split Xsomefile
- call setline(1, ['hello', 'there'])
- w!
- only!
- setl filetype=hello
- au FileType hello let g:gotit = 1
- call assert_false(exists('g:gotit'))
- edit other
- buf Xsomefile
- call assert_equal('hello', &filetype)
- call assert_true(exists('g:gotit'))
- call delete('Xsomefile')
-endfunc
-
-func Test_syntime()
- if !has('profile')
- return
- endif
-
- syntax on
- syntime on
- let a = execute('syntime report')
- call assert_equal("\nNo Syntax items defined for this buffer", a)
-
- let a = execute('syntime clear')
- call assert_equal("\nNo Syntax items defined for this buffer", a)
-
- view samples/memfile_test.c
- setfiletype cpp
- redraw
- let a = execute('syntime report')
- call assert_match('^ TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a)
- call assert_match(' \d*\.\d* \+[^0]\d* .* cppRawString ', a)
- call assert_match(' \d*\.\d* \+[^0]\d* .* cppNumber ', a)
-
- syntime off
- syntime clear
- let a = execute('syntime report')
- call assert_match('^ TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a)
- call assert_notmatch('.* cppRawString *', a)
- call assert_notmatch('.* cppNumber*', a)
- call assert_notmatch('[1-9]', a)
-
- call assert_fails('syntime abc', 'E475')
-
- syntax clear
- let a = execute('syntime report')
- call assert_equal("\nNo Syntax items defined for this buffer", a)
-
- bd
-endfunc
-
-func Test_syntime_completion()
- if !has('profile')
- return
- endif
-
- call feedkeys(":syntime \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syntime clear off on report', @:)
-endfunc
-
-func Test_syntax_list()
- syntax on
- let a = execute('syntax list')
- call assert_equal("\nNo Syntax items defined for this buffer", a)
-
- view ../memfile_test.c
- setfiletype c
-
- let a = execute('syntax list')
- call assert_match('cInclude*', a)
- call assert_match('cDefine', a)
-
- let a = execute('syntax list cDefine')
- call assert_notmatch('cInclude*', a)
- call assert_match('cDefine', a)
- call assert_match(' links to Macro$', a)
-
- call assert_fails('syntax list ABCD', 'E28:')
- call assert_fails('syntax list @ABCD', 'E392:')
-
- syntax clear
- let a = execute('syntax list')
- call assert_equal("\nNo Syntax items defined for this buffer", a)
-
- syntax keyword Type int containedin=g1 skipwhite skipempty skipnl nextgroup=Abc
- let exp = "Type xxx containedin=g1 nextgroup=Abc skipnl skipwhite skipempty int"
- call assert_equal(exp, split(execute("syntax list"), "\n")[1])
-
- bd
-endfunc
-
-func Test_syntax_completion()
- call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx')
- 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', @:)
-
- call feedkeys(":syn spell \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syn spell default notoplevel toplevel', @:)
-
- call feedkeys(":syn sync \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:)
-
- " Check that clearing "Aap" avoids it showing up before Boolean.
- hi @Aap ctermfg=blue
- call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"syn list @Aap @boolean @character ', @:)
- hi clear @Aap
-
- call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"syn list @boolean @character ', @:)
-
- call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"syn match @boolean @character ', @:)
-
- syn cluster Aax contains=Aap
- call feedkeys(":syn list @A\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"syn list @Aax', @:)
-endfunc
-
-func Test_echohl_completion()
- call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx')
- " call assert_equal('"echohl NonText Normal none', @:)
- call assert_equal('"echohl NonText Normal NormalFloat none', @:)
-endfunc
-
-func Test_syntax_arg_skipped()
- syn clear
- syntax case ignore
- if 0
- syntax case match
- endif
- call assert_match('case ignore', execute('syntax case'))
-
- syn keyword Foo foo
- call assert_match('Foo', execute('syntax'))
- syn clear
- call assert_match('case match', execute('syntax case'))
- call assert_notmatch('Foo', execute('syntax'))
-
- if has('conceal')
- syn clear
- syntax conceal on
- if 0
- syntax conceal off
- endif
- call assert_match('conceal on', execute('syntax conceal'))
- syn clear
- call assert_match('conceal off', execute('syntax conceal'))
- endif
-
- syntax conceal on
- syntax conceal off
- call assert_match('conceal off', execute('syntax conceal'))
-
- syntax region Bar start=/</ end=/>/
- if 0
- syntax region NotTest start=/</ end=/>/ contains=@Spell
- endif
- call assert_match('Bar', execute('syntax'))
- call assert_notmatch('NotTest', execute('syntax'))
- call assert_notmatch('Spell', execute('syntax'))
-
- hi Foo ctermfg=blue
- let a = execute('hi Foo')
- if 0
- syntax rest
- endif
- call assert_equal(a, execute('hi Foo'))
- hi clear Bar
- hi clear Foo
-
- set ft=tags
- syn off
- if 0
- syntax enable
- endif
- call assert_match('No Syntax items defined', execute('syntax'))
- syntax enable
- call assert_match('tagComment', execute('syntax'))
- set ft=
-
- syn clear
- if 0
- syntax include @Spell nothing
- endif
- call assert_notmatch('Spell', execute('syntax'))
-
- syn clear
- syn iskeyword 48-57,$,_
- call assert_match('48-57,$,_', execute('syntax iskeyword'))
- if 0
- syn clear
- syn iskeyword clear
- endif
- call assert_match('48-57,$,_', execute('syntax iskeyword'))
- syn iskeyword clear
- call assert_match('not set', execute('syntax iskeyword'))
- syn iskeyword 48-57,$,_
- syn clear
- call assert_match('not set', execute('syntax iskeyword'))
-
- syn clear
- syn keyword Foo foo
- if 0
- syn keyword NotAdded bar
- endif
- call assert_match('Foo', execute('syntax'))
- call assert_notmatch('NotAdded', execute('highlight'))
-
- syn clear
- syn keyword Foo foo
- call assert_match('Foo', execute('syntax'))
- call assert_match('Foo', execute('syntax list'))
- call assert_notmatch('Foo', execute('if 0 | syntax | endif'))
- call assert_notmatch('Foo', execute('if 0 | syntax list | endif'))
-
- syn clear
- syn match Fopi /asdf/
- if 0
- syn match Fopx /asdf/
- endif
- call assert_match('Fopi', execute('syntax'))
- call assert_notmatch('Fopx', execute('syntax'))
-
- syn clear
- syn spell toplevel
- call assert_match('spell toplevel', execute('syntax spell'))
- if 0
- syn spell notoplevel
- endif
- call assert_match('spell toplevel', execute('syntax spell'))
- syn spell notoplevel
- call assert_match('spell notoplevel', execute('syntax spell'))
- syn spell default
- call assert_match('spell default', execute('syntax spell'))
-
- syn clear
- if 0
- syntax cluster Spell
- endif
- call assert_notmatch('Spell', execute('syntax'))
-
- syn clear
- syn keyword Foo foo
- syn sync ccomment
- syn sync maxlines=5
- if 0
- syn sync maxlines=11
- endif
- call assert_match('on C-style comments', execute('syntax sync'))
- call assert_match('maximal 5 lines', execute('syntax sync'))
- syn sync clear
- if 0
- syn sync ccomment
- endif
- call assert_notmatch('on C-style comments', execute('syntax sync'))
- syn sync fromstart
- call assert_match('syncing starts at the first line', execute('syntax sync'))
-
- syn clear
-endfunc
-
-" Check for an error. Used when multiple errors are thrown and we are checking
-" for an earliest error.
-func AssertFails(cmd, errcode)
- let save_exception = ''
- try
- exe a:cmd
- catch
- let save_exception = v:exception
- endtry
- call assert_match(a:errcode, save_exception)
-endfunc
-
-func Test_syntax_invalid_arg()
- call assert_fails('syntax case asdf', 'E390:')
- if has('conceal')
- call assert_fails('syntax conceal asdf', 'E390:')
- endif
- call assert_fails('syntax spell asdf', 'E390:')
- call assert_fails('syntax clear @ABCD', 'E391:')
- call assert_fails('syntax include random_file', 'E484:')
- call assert_fails('syntax include <afile>', 'E495:')
- call assert_fails('syntax sync x', 'E404:')
- call assert_fails('syntax keyword Abc a[', 'E789:')
- call assert_fails('syntax keyword Abc a[bc]d', 'E890:')
- call assert_fails('syntax cluster Abc add=A add=', 'E406:')
-
- " Test for too many \z\( and unmatched \z\(
- " Not able to use assert_fails() here because both E50:/E879: and E475:
- " messages are emitted.
- set regexpengine=1
- call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E52:')
-
- let cmd = "syntax region MyRegion start='"
- let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'"
- call AssertFails(cmd, 'E50:')
-
- set regexpengine=2
- call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E54:')
-
- let cmd = "syntax region MyRegion start='"
- let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'"
- call AssertFails(cmd, 'E879:')
- set regexpengine&
-
- call AssertFails('syntax keyword cMyItem grouphere G1', 'E393:')
- call AssertFails('syntax sync match Abc grouphere MyItem "abc"', 'E394:')
- call AssertFails('syn keyword Type contains int', 'E395:')
- call assert_fails('syntax include @Xxx', 'E397:')
- call AssertFails('syntax region X start', 'E398:')
- call assert_fails('syntax region X start="{"', 'E399:')
- call AssertFails('syntax cluster contains=Abc', 'E400:')
- call AssertFails("syntax match Character /'.'", 'E401:')
- call AssertFails("syntax match Character /'.'/a", 'E402:')
- call assert_fails('syntax sync linecont /\%(/', 'E53:')
- call assert_fails('syntax sync linecont /pat', 'E404:')
- call assert_fails('syntax sync linecont', 'E404:')
- call assert_fails('syntax sync linecont /pat1/ linecont /pat2/', 'E403:')
- call assert_fails('syntax sync minlines=a', 'E404:')
- call AssertFails('syntax match ABC /x/ contains=', 'E406:')
- call AssertFails("syntax match Character contains /'.'/", 'E405:')
- call AssertFails('syntax match ccFoo "Foo" nextgroup=ALLBUT,F', 'E407:')
- call AssertFails('syntax region Block start="{" contains=F,ALLBUT', 'E408:')
- call AssertFails("syntax match Characters contains=a.*x /'.'/", 'E409:')
- call assert_fails('syntax match Search /abc/ contains=ALLBUT,/\%(/', 'E53:')
-endfunc
-
-func Test_syn_sync()
- syntax region HereGroup start=/this/ end=/that/
- syntax sync match SyncHere grouphere HereGroup "pattern"
- call assert_match('SyncHere', execute('syntax sync'))
- syn sync clear
- call assert_notmatch('SyncHere', execute('syntax sync'))
- syn clear
-endfunc
-
-func Test_syn_clear()
- syntax keyword Foo foo
- syntax keyword Bar tar
- call assert_match('Foo', execute('syntax'))
- call assert_match('Bar', execute('syntax'))
- call assert_equal('Foo', synIDattr(hlID("Foo"), "name"))
- syn clear Foo
- call assert_notmatch('Foo', execute('syntax'))
- call assert_match('Bar', execute('syntax'))
- call assert_equal('Foo', synIDattr(hlID("Foo"), "name"))
- syn clear Foo Bar
- call assert_notmatch('Foo', execute('syntax'))
- call assert_notmatch('Bar', execute('syntax'))
- hi clear Foo
- call assert_equal('Foo', synIDattr(hlID("Foo"), "name"))
- hi clear Bar
- call assert_fails('syntax clear invalid_syngroup', 'E28:')
-endfunc
-
-func Test_invalid_name()
- syn clear
- syn keyword Nop yes
- call assert_fails("syntax keyword Wr\x17ong bar", 'E669:')
- syntax keyword @Wrong bar
- call assert_fails("syntax keyword @#Wrong bar", 'E5248:')
- syn clear
- hi clear Nop
- hi clear @Wrong
-endfunc
-
-func Test_ownsyntax()
- new Xfoo
- call setline(1, '#define FOO')
- syntax on
- set filetype=c
-
- ownsyntax perl
- " this should not crash
- set
-
- call assert_equal('perlComment', synIDattr(synID(line('.'), col('.'), 1), 'name'))
- call assert_equal('c', b:current_syntax)
- call assert_equal('perl', w:current_syntax)
-
- " A new split window should have the original syntax.
- split
- call assert_equal('cDefine', synIDattr(synID(line('.'), col('.'), 1), 'name'))
- call assert_equal('c', b:current_syntax)
- call assert_equal(0, exists('w:current_syntax'))
-
- wincmd x
- call assert_equal('perlComment', synIDattr(synID(line("."), col("."), 1), "name"))
-
- syntax off
- set filetype&
- %bw!
-endfunc
-
-func Test_ownsyntax_completion()
- call feedkeys(":ownsyntax java\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"ownsyntax java javacc javascript javascriptreact', @:)
-endfunc
-
-func Test_highlight_invalid_arg()
- if has('gui_running')
- call assert_fails('hi XXX guifg=xxx', 'E254:')
- endif
- call assert_fails('hi DoesNotExist', 'E411:')
- call assert_fails('hi link', 'E412:')
- call assert_fails('hi link a', 'E412:')
- call assert_fails('hi link a b c', 'E413:')
- call assert_fails('hi XXX =', 'E415:')
- call assert_fails('hi XXX cterm', 'E416:')
- call assert_fails('hi XXX cterm=', 'E417:')
- call assert_fails('hi XXX cterm=DoesNotExist', 'E418:')
- call assert_fails('hi XXX ctermfg=DoesNotExist', 'E421:')
- call assert_fails('hi XXX xxx=White', 'E423:')
-endfunc
-
-func Test_conceal()
- if !has('conceal')
- return
- endif
-
- new
- call setline(1, ['', '123456'])
- syn match test23 "23" conceal cchar=X
- syn match test45 "45" conceal
-
- set conceallevel=0
- call assert_equal('123456 ', ScreenLines(2, 7)[0])
- call assert_equal([[0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
-
- set conceallevel=1
- call assert_equal('1X 6 ', ScreenLines(2, 7)[0])
- call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, ' ', 2], [1, ' ', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
-
- set conceallevel=1
- set listchars=conceal:Y
- call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, 'Y', 2], [1, 'Y', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
- call assert_equal('1XY6 ', ScreenLines(2, 7)[0])
-
- set conceallevel=2
- call assert_match('1X6 ', ScreenLines(2, 7)[0])
- call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
-
- set conceallevel=3
- call assert_match('16 ', ScreenLines(2, 7)[0])
- call assert_equal([[0, '', 0], [1, '', 1], [1, '', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
-
- call AssertFails("syntax match Entity '&amp;' conceal cchar=\<Tab>", 'E844:')
-
- syn clear
- set conceallevel&
- bw!
-endfunc
-
-func Test_bg_detection()
- CheckNotGui
-
- " auto-detection of &bg, make sure sure it isn't set anywhere before
- " this test
- hi Normal ctermbg=0
- call assert_equal('dark', &bg)
- hi Normal ctermbg=4
- call assert_equal('dark', &bg)
- hi Normal ctermbg=12
- call assert_equal('light', &bg)
- hi Normal ctermbg=15
- call assert_equal('light', &bg)
-
- " manually-set &bg takes precedence over auto-detection
- set bg=light
- hi Normal ctermbg=4
- call assert_equal('light', &bg)
- set bg=dark
- hi Normal ctermbg=12
- call assert_equal('dark', &bg)
-
- hi Normal ctermbg=NONE
-endfunc
-
-func Test_syntax_hangs()
- if !has('reltime') || !has('float') || !has('syntax')
- return
- endif
-
- " This pattern takes a long time to match, it should timeout.
- new
- call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
- let start = reltime()
- set nolazyredraw redrawtime=101
- syn match Error /\%#=1a*.*X\@<=b*/
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed > 0.1)
- call assert_true(elapsed < 1.0)
-
- " second time syntax HL is disabled
- let start = reltime()
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed < 0.1)
-
- " after CTRL-L the timeout flag is reset
- let start = reltime()
- exe "normal \<C-L>"
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed > 0.1)
- call assert_true(elapsed < 1.0)
-
- set redrawtime&
- bwipe!
-endfunc
-
-func Test_synstack_synIDtrans()
- new
- setfiletype c
- syntax on
- call setline(1, ' /* A comment with a TODO */')
-
- call assert_equal([], synstack(1, 1))
-
- norm f/
- eval synstack(line("."), col("."))->map('synIDattr(v:val, "name")')->assert_equal(['cComment', 'cCommentStart'])
- eval synstack(line("."), col("."))->map('synIDattr(synIDtrans(v:val), "name")')->assert_equal(['Comment', 'Comment'])
-
- norm fA
- call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
- call assert_equal(['Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")'))
-
- norm fT
- call assert_equal(['cComment', 'cTodo'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
- call assert_equal(['Comment', 'Todo'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")'))
-
- call assert_fails("let n=synIDtrans([])", 'E745:')
-
- syn clear
- bw!
-endfunc
-
-" Check highlighting for a small piece of C code with a screen dump.
-func Test_syntax_c()
- CheckRunVimInTerminal
- call writefile([
- \ '/* comment line at the top */',
- \ 'int main(int argc, char **argv) { // another comment',
- \ '#if 0',
- \ ' int not_used;',
- \ '#else',
- \ ' int used;',
- \ '#endif',
- \ ' printf("Just an example piece of C code\n");',
- \ ' return 0x0ff;',
- \ '}',
- \ ' static void',
- \ 'myFunction(const double count, struct nothing, long there) {',
- \ ' // 123: nothing to read here',
- \ ' for (int i = 0; i < count; ++i) {',
- \ ' break;',
- \ ' }',
- \ " Note: asdf",
- \ '}',
- \ ], 'Xtest.c')
-
- " This makes the default for 'background' use "dark", check that the
- " response to t_RB corrects it to "light".
- let $COLORFGBG = '15;0'
-
- let buf = RunVimInTerminal('Xtest.c', {})
- call term_sendkeys(buf, ":syn keyword Search Note\r")
- call VerifyScreenDump(buf, 'Test_syntax_c_01', {})
- call StopVimInTerminal(buf)
-
- let $COLORFGBG = ''
- call delete('Xtest.c')
-endfun
-
-" Test \z(...) along with \z1
-func Test_syn_zsub()
- new
- syntax on
- call setline(1, 'xxx start foo xxx not end foo xxx end foo xxx')
- let l:expected = ' ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ '
-
- for l:re in [0, 1, 2]
- " Example taken from :help :syn-ext-match
- syntax region Z start="start \z(\I\i*\)" skip="not end \z1" end="end \z1"
- eval AssertHighlightGroups(1, 1, l:expected, 1, 'regexp=' .. l:re)
- syntax clear Z
- endfor
-
- set re&
- bw!
-endfunc
-
-" Using \z() in a region with NFA failing should not crash.
-func Test_syn_wrong_z_one()
- new
- call setline(1, ['just some text', 'with foo and bar to match with'])
- syn region FooBar start="foo\z(.*\)bar" end="\z1"
- " call test_override("nfa_fail", 1)
- redraw!
- redraw!
- " call test_override("ALL", 0)
- bwipe!
-endfunc
-
-func Test_syntax_after_bufdo()
- call writefile(['/* aaa comment */'], 'Xaaa.c')
- call writefile(['/* bbb comment */'], 'Xbbb.c')
- call writefile(['/* ccc comment */'], 'Xccc.c')
- call writefile(['/* ddd comment */'], 'Xddd.c')
-
- let bnr = bufnr('%')
- new Xaaa.c
- badd Xbbb.c
- badd Xccc.c
- badd Xddd.c
- exe "bwipe " . bnr
- let l = []
- bufdo call add(l, bufnr('%'))
- call assert_equal(4, len(l))
-
- syntax on
-
- " This used to only enable syntax HL in the last buffer.
- bufdo tab split
- tabrewind
- for tab in range(1, 4)
- norm fm
- call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
- tabnext
- endfor
-
- bwipe! Xaaa.c
- bwipe! Xbbb.c
- bwipe! Xccc.c
- bwipe! Xddd.c
- syntax off
- call delete('Xaaa.c')
- call delete('Xbbb.c')
- call delete('Xccc.c')
- call delete('Xddd.c')
-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
- call assert_match('from the first line$', execute('syn sync'))
- 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
-
-func Test_search_syntax_skip()
- new
- let lines =<< trim END
-
- /* This is VIM */
- Another Text for VIM
- let a = "VIM"
- END
- call setline(1, lines)
- syntax on
- syntax match Comment "^/\*.*\*/"
- syntax match String '".*"'
-
- " Skip argument using string evaluation.
- 1
- call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"')
- call assert_equal('Another Text for VIM', getline('.'))
-
- 1
- call search('VIM', 'cw', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
- call assert_equal(' let a = "VIM"', getline('.'))
-
- " Skip argument using Lambda.
- 1
- call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"})
- call assert_equal('Another Text for VIM', getline('.'))
-
- 1
- call search('VIM', 'cw', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
- call assert_equal(' let a = "VIM"', getline('.'))
-
- " Skip argument using funcref.
- func InComment()
- return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"
- endfunc
- func NotInString()
- return synIDattr(synID(line("."), col("."), 1), "name") !~? "string"
- endfunc
-
- 1
- call search('VIM', 'w', '', 0, function('InComment'))
- call assert_equal('Another Text for VIM', getline('.'))
-
- 1
- call search('VIM', 'cw', '', 0, function('NotInString'))
- call assert_equal(' let a = "VIM"', getline('.'))
-
- delfunc InComment
- delfunc NotInString
- bwipe!
-endfunc
-
-func Test_syn_contained_transparent()
- " Comments starting with "Regression:" show the result when the highlighting
- " span of the containing item is assigned to the contained region.
- syntax on
-
- let l:case = "Transparent region contained in region"
- new
- syntax region X start=/\[/ end=/\]/ contained transparent
- syntax region Y start=/(/ end=/)/ contains=X
-
- call setline(1, "==(--[~~]--)==")
- let l:expected = " YYYYYYYYYY "
- eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
- syntax clear Y X
- bw!
-
- let l:case = "Transparent region extends region"
- new
- syntax region X start=/\[/ end=/\]/ contained transparent
- syntax region Y start=/(/ end=/)/ end=/e/ contains=X
-
- call setline(1, "==(--[~~e~~]--)==")
- let l:expected = " YYYYYYYYYYYYY "
- " Regression: " YYYYYYY YYY "
- eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
- syntax clear Y X
- bw!
-
- let l:case = "Nested transparent regions extend region"
- new
- syntax region X start=/\[/ end=/\]/ contained transparent
- syntax region Y start=/(/ end=/)/ end=/e/ contains=X
-
- call setline(1, "==(--[~~e~~[~~e~~]~~e~~]--)==")
- let l:expected = " YYYYYYYYYYYYYYYYYYYYYYYYY "
- " Regression: " YYYYYYY YYYYYYYYY "
- eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
- syntax clear Y X
- bw!
-
- let l:case = "Transparent region contained in match"
- new
- syntax region X start=/\[/ end=/\]/ contained transparent
- syntax match Y /(.\{-})/ contains=X
-
- call setline(1, "==(--[~~]--)==")
- let l:expected = " YYYYYYYYYY "
- eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
- syntax clear Y X
- bw!
-
- let l:case = "Transparent region extends match"
- new
- syntax region X start=/\[/ end=/\]/ contained transparent
- syntax match Y /(.\{-}[e)]/ contains=X
-
- call setline(1, "==(--[~~e~~]--)==")
- let l:expected = " YYYYYYYYYY "
- " Regression: " YYYYYYY "
- eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
- syntax clear Y X
- bw!
-
- let l:case = "Nested transparent regions extend match"
- new
- syntax region X start=/\[/ end=/\]/ contained transparent
- syntax match Y /(.\{-}[e)]/ contains=X
-
- call setline(1, "==(--[~~e~~[~~e~~]~~e~~]--)==")
- let l:expected = " YYYYYYYYYYYYYYYYYYYYYY "
- " Regression: " YYYYYYY YYYYYY "
- eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
- syntax clear Y X
- bw!
-endfunc
-
-func Test_syn_include_contains_TOP()
- let l:case = "TOP in included syntax means its group list name"
- new
- syntax include @INCLUDED syntax/c.vim
- syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
-
- call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ])
- let l:expected = ["cCppOutIf2"]
- eval AssertHighlightGroups(3, 1, l:expected, 1)
- " cCppOutElse has contains=TOP
- let l:expected = ["cType"]
- eval AssertHighlightGroups(5, 1, l:expected, 1, l:case)
- syntax clear
- bw!
-endfunc
-
-" This was using freed memory
-func Test_WinEnter_synstack_synID()
- autocmd WinEnter * call synstack(line("."), col("."))
- autocmd WinEnter * call synID(line('.'), col('.') - 1, 1)
- call setline(1, 'aaaaa')
- normal! $
- new
- close
-
- au! WinEnter
- bw!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim
deleted file mode 100644
index 6c8373b335..0000000000
--- a/src/nvim/testdir/test_system.vim
+++ /dev/null
@@ -1,145 +0,0 @@
-" Tests for system() and systemlist()
-
-source shared.vim
-source check.vim
-
-func Test_System()
- if !executable('echo') || !executable('cat') || !executable('wc')
- return
- endif
- let out = 'echo 123'->system()
- call assert_equal("123\n", out)
-
- let out = 'echo 123'->systemlist()
- if &shell =~# 'cmd.exe$'
- call assert_equal(["123\r"], out)
- else
- call assert_equal(['123'], out)
- endif
-
- call assert_equal('123', system('cat', '123'))
- call assert_equal(['123'], systemlist('cat', '123'))
- call assert_equal(["as\<NL>df"], systemlist('cat', ["as\<NL>df"]))
-
- new Xdummy
- call setline(1, ['asdf', "pw\<NL>er", 'xxxx'])
- let out = system('wc -l', bufnr('%'))
- " On OS/X we get leading spaces
- let out = substitute(out, '^ *', '', '')
- call assert_equal("3\n", out)
-
- let out = systemlist('wc -l', bufnr('%'))
- " On Windows we may get a trailing CR.
- if out != ["3\r"]
- " On OS/X we get leading spaces
- if type(out) == v:t_list
- let out[0] = substitute(out[0], '^ *', '', '')
- endif
- call assert_equal(['3'], out)
- endif
-
- let out = systemlist('cat', bufnr('%'))
- " On Windows we may get a trailing CR.
- if out != ["asdf\r", "pw\<NL>er\r", "xxxx\r"]
- call assert_equal(['asdf', "pw\<NL>er", 'xxxx'], out)
- endif
- bwipe!
-
- call assert_fails('call system("wc -l", 99999)', 'E86:')
-endfunc
-
-func Test_system_exmode()
- if has('unix') " echo $? only works on Unix
- let cmd = ' -es -c "source Xscript" +q; echo "result=$?"'
- " Need to put this in a script, "catch" isn't found after an unknown
- " function.
- call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript')
- let a = system(GetVimCommand() . cmd)
- call assert_match('result=0', a)
- call assert_equal(0, v:shell_error)
- endif
-
- " Error before try does set error flag.
- call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript')
- if has('unix') " echo $? only works on Unix
- let a = system(GetVimCommand() . cmd)
- call assert_notequal('0', a[0])
- endif
-
- let cmd = ' -es -c "source Xscript" +q'
- let a = system(GetVimCommand() . cmd)
- call assert_notequal(0, v:shell_error)
- call delete('Xscript')
-
- if has('unix') " echo $? only works on Unix
- let cmd = ' -es -c "call doesnotexist()" +q; echo $?'
- let a = system(GetVimCommand() . cmd)
- call assert_notequal(0, a[0])
- endif
-
- let cmd = ' -es -c "call doesnotexist()" +q'
- let a = system(GetVimCommand(). cmd)
- call assert_notequal(0, v:shell_error)
-
- if has('unix') " echo $? only works on Unix
- let cmd = ' -es -c "call doesnotexist()|let a=1" +q; echo $?'
- let a = system(GetVimCommand() . cmd)
- call assert_notequal(0, a[0])
- endif
-
- let cmd = ' -es -c "call doesnotexist()|let a=1" +q'
- let a = system(GetVimCommand() . cmd)
- call assert_notequal(0, v:shell_error)
-endfunc
-
-func Test_system_with_shell_quote()
- CheckMSWindows
-
- call mkdir('Xdir with spaces', 'p')
- call system('copy "%COMSPEC%" "Xdir with spaces\cmd.exe"')
-
- let shell_save = &shell
- let shellxquote_save = &shellxquote
- try
- " Set 'shell' always needs noshellslash.
- let shellslash_save = &shellslash
- set noshellslash
- let shell_tests = [
- \ expand('$COMSPEC'),
- \ '"' . fnamemodify('Xdir with spaces\cmd.exe', ':p') . '"',
- \]
- let &shellslash = shellslash_save
-
- let sxq_tests = ['', '(', '"']
-
- " Matrix tests: 'shell' * 'shellxquote'
- for shell in shell_tests
- let &shell = shell
- for sxq in sxq_tests
- let &shellxquote = sxq
-
- let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote)
-
- try
- let out = 'echo 123'->system()
- catch
- call assert_report(printf('%s: %s', msg, v:exception))
- continue
- endtry
-
- " On Windows we may get a trailing space and CR.
- if out != "123 \n"
- call assert_equal("123\n", out, msg)
- endif
-
- endfor
- endfor
-
- finally
- let &shell = shell_save
- let &shellxquote = shellxquote_save
- call delete('Xdir with spaces', 'rf')
- endtry
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tab.vim b/src/nvim/testdir/test_tab.vim
deleted file mode 100644
index b8e8dfe062..0000000000
--- a/src/nvim/testdir/test_tab.vim
+++ /dev/null
@@ -1,90 +0,0 @@
-" Various tests for inserting a Tab.
-
-" Tests for "r<Tab>" with 'smarttab' and 'expandtab' set/not set.
-" Also test that dv_ works correctly
-func Test_smarttab()
- enew!
- set smarttab expandtab ts=8 sw=4
- " make sure that backspace works, no matter what termcap is used
- exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08"
- call append(0, ['start text',
- \ "\t\tsome test text",
- \ 'test text',
- \ "\t\tother test text",
- \ ' a cde',
- \ ' f ghi',
- \ 'test text',
- \ ' Second line beginning with whitespace'
- \ ])
- call cursor(1, 1)
- exe "normal /some\<CR>"
- exe "normal r\t"
- call assert_equal("\t\t ome test text", getline('.'))
- set noexpandtab
- exe "normal /other\<CR>"
- exe "normal r\t"
- call assert_equal("\t\t ther test text", getline('.'))
-
- " Test replacing with Tabs and then backspacing to undo it
- exe "normal j0wR\t\t\t\<BS>\<BS>\<BS>"
- call assert_equal(" a cde", getline('.'))
- " Test replacing with Tabs
- exe "normal j0wR\t\t\t"
- call assert_equal(" \t\thi", getline('.'))
-
- " Test that copyindent works with expandtab set
- set expandtab smartindent copyindent ts=8 sw=8 sts=8
- exe "normal jo{\<CR>x"
- call assert_equal('{', getline(line('.') - 1))
- call assert_equal(' x', getline('.'))
- set nosol
- exe "normal /Second line/\<CR>"
- exe "normal fwdv_"
- call assert_equal(' with whitespace', getline('.'))
- enew!
- set expandtab& smartindent& copyindent& ts& sw& sts&
-endfunc
-
-func Test_softtabstop()
- new
- set sts=0 sw=0
- exe "normal ix\<Tab>x\<Esc>"
- call assert_equal("x\tx", getline(1))
-
- call setline(1, '')
- set sts=4
- exe "normal ix\<Tab>x\<Esc>"
- call assert_equal("x x", getline(1))
-
- call setline(1, '')
- set sts=-1 sw=4
- exe "normal ix\<Tab>x\<Esc>"
- call assert_equal("x x", getline(1))
-
- call setline(1, 'x ')
- set sts=0 sw=0 backspace=start
- exe "normal A\<BS>x\<Esc>"
- call assert_equal("x x", getline(1))
-
- call setline(1, 'x ')
- set sts=4
- exe "normal A\<BS>x\<Esc>"
- call assert_equal("x x", getline(1))
-
- call setline(1, 'x ')
- set sts=-1 sw=4
- exe "normal A\<BS>x\<Esc>"
- call assert_equal("x x", getline(1))
-
- call setline(1, 'x')
- set sts=-1 sw=0 smarttab
- exe "normal I\<Tab>\<Esc>"
- call assert_equal("\tx", getline(1))
-
- call setline(1, 'x')
- exe "normal I\<Tab>\<BS>\<Esc>"
- call assert_equal("x", getline(1))
-
- set sts=0 sw=0 backspace& nosmarttab
- bwipe!
-endfunc
diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim
deleted file mode 100644
index d9bef09067..0000000000
--- a/src/nvim/testdir/test_tabline.vim
+++ /dev/null
@@ -1,207 +0,0 @@
-" Test for tabline
-
-source shared.vim
-source view_util.vim
-source check.vim
-source screendump.vim
-
-func TablineWithCaughtError()
- let s:func_in_tabline_called = 1
- try
- call eval('unknown expression')
- catch
- endtry
- return ''
-endfunc
-
-func TablineWithError()
- let s:func_in_tabline_called = 1
- call eval('unknown expression')
- return ''
-endfunc
-
-func Test_caught_error_in_tabline()
- if has('gui')
- set guioptions-=e
- endif
- let showtabline_save = &showtabline
- set showtabline=2
- let s:func_in_tabline_called = 0
- let tabline = '%{TablineWithCaughtError()}'
- let &tabline = tabline
- redraw!
- call assert_true(s:func_in_tabline_called)
- call assert_equal(tabline, &tabline)
- set tabline=
- let &showtabline = showtabline_save
-endfunc
-
-func Test_tabline_will_be_disabled_with_error()
- if has('gui')
- set guioptions-=e
- endif
- let showtabline_save = &showtabline
- set showtabline=2
- let s:func_in_tabline_called = 0
- let tabline = '%{TablineWithError()}'
- try
- let &tabline = tabline
- redraw!
- catch
- endtry
- call assert_true(s:func_in_tabline_called)
- call assert_equal('', &tabline)
- set tabline=
- let &showtabline = showtabline_save
-endfunc
-
-func Test_redrawtabline()
- if has('gui')
- set guioptions-=e
- endif
- let showtabline_save = &showtabline
- set showtabline=2
- set tabline=%{bufnr('$')}
- edit Xtabline1
- edit Xtabline2
- redraw
- call assert_match(bufnr('$') . '', Screenline(1))
- au BufAdd * redrawtabline
- badd Xtabline3
- call assert_match(bufnr('$') . '', Screenline(1))
-
- set tabline=
- let &showtabline = showtabline_save
- au! Bufadd
-endfunc
-
-" Test for the "%T" and "%X" flags in the 'tabline' option
-func MyTabLine()
- let s = ''
- for i in range(tabpagenr('$'))
- " set the tab page number (for mouse clicks)
- let s .= '%' . (i + 1) . 'T'
-
- " the label is made by MyTabLabel()
- let s .= ' %{MyTabLabel(' . (i + 1) . ')} '
- endfor
-
- " after the last tab fill with TabLineFill and reset tab page nr
- let s .= '%T'
-
- " right-align the label to close the current tab page
- if tabpagenr('$') > 1
- let s .= '%=%Xclose'
- endif
-
- return s
-endfunc
-
-func MyTabLabel(n)
- let buflist = tabpagebuflist(a:n)
- let winnr = tabpagewinnr(a:n)
- return bufname(buflist[winnr - 1])
-endfunc
-
-func Test_tabline_flags()
- if has('gui')
- set guioptions-=e
- endif
- set tabline=%!MyTabLine()
- edit Xtabline1
- tabnew Xtabline2
- redrawtabline
- call assert_match('^ Xtabline1 Xtabline2\s\+close$', Screenline(1))
- set tabline=
- %bw!
-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
-
-" When there are exactly 20 tabline format items (the exact size of the
-" initial tabline items array), test that we don't write beyond the size
-" of the array.
-func Test_tabline_20_format_items_no_overrun()
- set showtabline=2
-
- let tabline = repeat('%#StatColorHi2#', 20)
- let &tabline = tabline
- redrawtabline
-
- set showtabline& tabline&
-endfunc
-
-func Test_mouse_click_in_tab()
- " This used to crash because TabPageIdxs[] was not initialized
- let lines =<< trim END
- tabnew
- set mouse=a
- exe "norm \<LeftMouse>"
- END
- call writefile(lines, 'Xclickscript')
- call RunVim([], [], "-e -s -S Xclickscript -c qa")
-
- call delete('Xclickscript')
-endfunc
-
-func Test_tabline_showcmd()
- CheckScreendump
-
- let lines =<< trim END
- func MyTabLine()
- return '%S'
- endfunc
-
- set showtabline=2
- set tabline=%!MyTabLine()
- set showcmdloc=tabline
- call setline(1, ['a', 'b', 'c'])
- set foldopen+=jump
- 1,2fold
- 3
- END
- call writefile(lines, 'XTest_tabline', 'D')
-
- let buf = RunVimInTerminal('-S XTest_tabline', {'rows': 6})
-
- call term_sendkeys(buf, "g")
- call VerifyScreenDump(buf, 'Test_tabline_showcmd_1', {})
-
- " typing "gg" should open the fold
- call term_sendkeys(buf, "g")
- call VerifyScreenDump(buf, 'Test_tabline_showcmd_2', {})
-
- call term_sendkeys(buf, "\<C-V>Gl")
- call VerifyScreenDump(buf, 'Test_tabline_showcmd_3', {})
-
- call term_sendkeys(buf, "\<Esc>1234")
- call VerifyScreenDump(buf, 'Test_tabline_showcmd_4', {})
-
- call term_sendkeys(buf, "\<Esc>:set tabline=\<CR>")
- call term_sendkeys(buf, ":\<CR>")
- call term_sendkeys(buf, "1234")
- call VerifyScreenDump(buf, 'Test_tabline_showcmd_5', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
deleted file mode 100644
index b97aa409d8..0000000000
--- a/src/nvim/testdir/test_tabpage.vim
+++ /dev/null
@@ -1,849 +0,0 @@
-" Tests for tabpage
-
-source screendump.vim
-source check.vim
-
-function Test_tabpage()
- bw!
- " Simple test for opening and closing a tab page
- tabnew
- call assert_equal(2, tabpagenr())
- quit
-
- " Open three tab pages and use ":tabdo"
- 0tabnew
- 1tabnew
- $tabnew
- %del
- tabdo call append(line('$'), tabpagenr())
- tabclose! 2
- tabrewind
- let line1 = getline('$')
- undo
- q
- tablast
- let line2 = getline('$')
- q!
- call append(line('$'), line1)
- call append(line('$'), line2)
- unlet line1 line2
- call assert_equal(['', '3', '1', '4'], getline(1, '$'))
- "
- " Test for settabvar() and gettabvar() functions. Open a new tab page and
- " set 3 variables to a number, string and a list. Verify that the variables
- " are correctly set.
- tabnew
- tabfirst
- call settabvar(2, 'val_num', 100)
- eval 'SetTabVar test'->settabvar(2, 'val_str')
- call settabvar(2, 'val_list', ['red', 'blue', 'green'])
- "
- call assert_true(gettabvar(2, 'val_num') == 100 && gettabvar(2, 'val_str') == 'SetTabVar test' && gettabvar(2, 'val_list') == ['red', 'blue', 'green'])
-
- tabnext 2
- call assert_true(t:val_num == 100 && t:val_str == 'SetTabVar test' && t:val_list == ['red', 'blue', 'green'])
- tabclose
-
- " Test for ":tab drop exist-file" to keep current window.
- sp test1
- tab drop test1
- call assert_true(tabpagenr('$') == 1 && winnr('$') == 2 && winnr() == 1)
- close
- "
- "
- " Test for ":tab drop new-file" to keep current window of tabpage 1.
- split
- tab drop newfile
- call assert_true(tabpagenr('$') == 2 && tabpagewinnr(1, '$') == 2 && tabpagewinnr(1) == 1)
- tabclose
- q
- "
- "
- " Test for ":tab drop multi-opened-file" to keep current tabpage and window.
- new test1
- tabnew
- new test1
- tab drop test1
- call assert_true(tabpagenr() == 2 && tabpagewinnr(2, '$') == 2 && tabpagewinnr(2) == 1)
- tabclose
- q
- "
- "
- " Test for ":tab drop vertical-split-window" to jump test1 buffer
- tabedit test1
- vnew
- tabfirst
- tab drop test1
- call assert_equal([2, 2, 2, 2], [tabpagenr('$'), tabpagenr(), tabpagewinnr(2, '$'), tabpagewinnr(2)])
- 1tabonly
- "
- "
- for i in range(9) | tabnew | endfor
- normal! 1gt
- call assert_equal(1, tabpagenr())
- tabmove 5
- call assert_equal(5, tabpagenr())
- .tabmove
- call assert_equal(5, tabpagenr())
- tabmove -
- call assert_equal(4, tabpagenr())
- tabmove +
- call assert_equal(5, tabpagenr())
- tabmove -2
- call assert_equal(3, tabpagenr())
- tabmove +4
- call assert_equal(7, tabpagenr())
- tabmove
- call assert_equal(10, tabpagenr())
- 0tabmove
- call assert_equal(1, tabpagenr())
- $tabmove
- call assert_equal(10, tabpagenr())
- tabmove 0
- call assert_equal(1, tabpagenr())
- tabmove $
- call assert_equal(10, tabpagenr())
- 3tabmove
- call assert_equal(4, tabpagenr())
- 7tabmove 5
- call assert_equal(5, tabpagenr())
- -tabmove
- call assert_equal(4, tabpagenr())
- +tabmove
- call assert_equal(5, tabpagenr())
- -2tabmove
- call assert_equal(3, tabpagenr())
- +3tabmove
- call assert_equal(6, tabpagenr())
-
- " The following are a no-op
- norm! 2gt
- call assert_equal(2, tabpagenr())
- tabmove 2
- call assert_equal(2, tabpagenr())
- 2tabmove
- call assert_equal(2, tabpagenr())
- tabmove 1
- call assert_equal(2, tabpagenr())
- 1tabmove
- call assert_equal(2, tabpagenr())
-
- call assert_fails('let t = tabpagenr("@")', 'E15:')
- call assert_equal(0, tabpagewinnr(-1))
- call assert_fails("99tabmove", 'E16:')
- call assert_fails("+99tabmove", 'E16:')
- call assert_fails("-99tabmove", 'E16:')
- call assert_fails("tabmove foo", 'E475:')
- call assert_fails("tabmove 99", 'E475:')
- call assert_fails("tabmove +99", 'E475:')
- call assert_fails("tabmove -99", 'E475:')
- call assert_fails("tabmove -3+", 'E475:')
- call assert_fails("tabmove $3", 'E475:')
- call assert_fails("%tabonly", 'E16:')
- 1tabonly!
- tabmove 1
- call assert_equal(1, tabpagenr())
- tabnew
- call assert_fails("-2tabmove", 'E16:')
- tabonly!
-endfunc
-
-" Test autocommands
-function Test_tabpage_with_autocmd()
- command -nargs=1 -bar C :call add(s:li, '=== ' . <q-args> . ' ===')|<args>
- augroup TestTabpageGroup
- au!
- autocmd TabEnter * call add(s:li, 'TabEnter')
- autocmd WinEnter * call add(s:li, 'WinEnter')
- autocmd BufEnter * call add(s:li, 'BufEnter')
- autocmd TabLeave * call add(s:li, 'TabLeave')
- autocmd WinLeave * call add(s:li, 'WinLeave')
- autocmd BufLeave * call add(s:li, 'BufLeave')
- augroup END
-
- let s:li = []
- let t:a='a'
- C tab split
- call assert_equal(['=== tab split ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter'], s:li)
- let s:li = []
- let t:a='b'
- C tabnew
- call assert_equal(['=== tabnew ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufLeave', 'BufEnter'], s:li)
- let t:a='c'
- let s:li = split(join(map(range(1, tabpagenr('$')), 'gettabvar(v:val, "a")')) , '\s\+')
- call assert_equal(['a', 'b', 'c'], s:li)
-
- let s:li = []
- C call map(range(1, tabpagenr('$')), 'settabvar(v:val, ''a'', v:val*2)')
- call assert_equal(["=== call map(range(1, tabpagenr('$')), 'settabvar(v:val, ''a'', v:val*2)') ==="], s:li)
- let s:li = split(join(map(range(1, tabpagenr('$')), 'gettabvar(v:val, "a")')) , '\s\+')
- call assert_equal(['2', '4', '6'], s:li)
-
- let s:li = []
- let w:a='a'
- C vsplit
- call assert_equal(['=== vsplit ===', 'WinLeave', 'WinEnter'], s:li)
- let s:li = []
- let w:a='a'
- let tabn=tabpagenr()
- let winr=range(1, winnr('$'))
- C tabnext 1
- call assert_equal(['=== tabnext 1 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li)
- let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+')
- call assert_equal(['a', 'a'], s:li)
- let s:li = []
- C call map(copy(winr), '(v:val*2)->settabwinvar(' .. tabn .. ', v:val, ''a'')')
- let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+')
- call assert_equal(['2', '4'], s:li)
-
- augroup TabDestructive
- autocmd TabEnter * :C tabnext 2 | C tabclose 3
- augroup END
- let s:li = []
- C tabnext 3
- call assert_equal(['=== tabnext 3 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', '=== tabnext 2 ===', '=== tabclose 3 ==='], s:li)
- call assert_equal(['2/2'], [tabpagenr().'/'.tabpagenr('$')])
-
- autocmd! TabDestructive TabEnter
- let s:li = []
- C tabnew
- call assert_equal(['=== tabnew ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufLeave', 'BufEnter'], s:li)
- let s:li = []
- C tabnext 1
- call assert_equal(['=== tabnext 1 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li)
-
- autocmd TabDestructive TabEnter * nested :C tabnext 2 | C tabclose 3
- let s:li = []
- call assert_equal(3, tabpagenr('$'))
- C tabnext 2
- call assert_equal(2, tabpagenr('$'))
- call assert_equal(['=== tabnext 2 ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', '=== tabnext 2 ===', '=== tabclose 3 ==='], s:li)
- call assert_equal(['2/2'], [tabpagenr().'/'.tabpagenr('$')])
-
- delcommand C
- autocmd! TabDestructive
- augroup! TabDestructive
- autocmd! TestTabpageGroup
- augroup! TestTabpageGroup
- 1tabonly!
-endfunction
-
-" Test autocommands on tab drop
-function Test_tabpage_with_autocmd_tab_drop()
- augroup TestTabpageGroup
- au!
- autocmd TabEnter * call add(s:li, 'TabEnter')
- autocmd WinEnter * call add(s:li, 'WinEnter')
- autocmd BufEnter * call add(s:li, 'BufEnter')
- autocmd TabLeave * call add(s:li, 'TabLeave')
- autocmd WinLeave * call add(s:li, 'WinLeave')
- autocmd BufLeave * call add(s:li, 'BufLeave')
- augroup END
-
- let s:li = []
- tab drop test1
- call assert_equal(['BufLeave', 'BufEnter'], s:li)
-
- let s:li = []
- tab drop test2 test3
- call assert_equal([
- \ 'TabLeave', 'TabEnter', 'TabLeave', 'TabEnter',
- \ 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter',
- \ 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li)
-
- autocmd! TestTabpageGroup
- augroup! TestTabpageGroup
- 1tabonly!
-endfunction
-
-function Test_tabpage_with_tab_modifier()
- for n in range(4)
- tabedit
- endfor
-
- function s:check_tab(pre_nr, cmd, post_nr)
- exec 'tabnext ' . a:pre_nr
- exec a:cmd
- call assert_equal(a:post_nr, tabpagenr())
- call assert_equal('help', &buftype)
- helpclose
- endfunc
-
- call s:check_tab(1, 'tab help', 2)
- call s:check_tab(1, '3tab help', 4)
- call s:check_tab(1, '.tab help', 2)
- call s:check_tab(1, '.+1tab help', 3)
- call s:check_tab(1, '0tab help', 1)
- call s:check_tab(2, '+tab help', 4)
- call s:check_tab(2, '+2tab help', 5)
- call s:check_tab(4, '-tab help', 4)
- call s:check_tab(4, '-2tab help', 3)
- call s:check_tab(3, '$tab help', 6)
- call assert_fails('99tab help', 'E16:')
- call assert_fails('+99tab help', 'E16:')
- call assert_fails('-99tab help', 'E16:')
-
- delfunction s:check_tab
- 1tabonly!
-endfunction
-
-function Check_tab_count(pre_nr, cmd, post_nr)
- exec 'tabnext' a:pre_nr
- normal! G
- exec a:cmd
- call assert_equal(a:post_nr, tabpagenr(), a:cmd)
-endfunc
-
-" Test for [count] of tabnext
-function Test_tabpage_with_tabnext()
- for n in range(4)
- tabedit
- call setline(1, ['', '', '3'])
- endfor
-
- call Check_tab_count(1, 'tabnext', 2)
- call Check_tab_count(1, '3tabnext', 3)
- call Check_tab_count(1, '.tabnext', 1)
- call Check_tab_count(1, '.+1tabnext', 2)
- call Check_tab_count(2, '+tabnext', 3)
- call Check_tab_count(2, '+2tabnext', 4)
- call Check_tab_count(4, '-tabnext', 3)
- call Check_tab_count(4, '-2tabnext', 2)
- call Check_tab_count(3, '$tabnext', 5)
- call assert_fails('0tabnext', 'E16:')
- call assert_fails('99tabnext', 'E16:')
- call assert_fails('+99tabnext', 'E16:')
- call assert_fails('-99tabnext', 'E16:')
- call Check_tab_count(1, 'tabnext 3', 3)
- call Check_tab_count(2, 'tabnext +', 3)
- call Check_tab_count(2, 'tabnext +2', 4)
- call Check_tab_count(4, 'tabnext -', 3)
- call Check_tab_count(4, 'tabnext -2', 2)
- call Check_tab_count(3, 'tabnext $', 5)
- call assert_fails('tabnext 0', 'E475:')
- call assert_fails('tabnext .', 'E475:')
- call assert_fails('tabnext -+', 'E475:')
- call assert_fails('tabnext +2-', 'E475:')
- call assert_fails('tabnext $3', 'E475:')
- call assert_fails('tabnext 99', 'E475:')
- call assert_fails('tabnext +99', 'E475:')
- call assert_fails('tabnext -99', 'E475:')
-
- 1tabonly!
-endfunction
-
-" Test for [count] of tabprevious
-function Test_tabpage_with_tabprevious()
- for n in range(5)
- tabedit
- call setline(1, ['', '', '3'])
- endfor
-
- for cmd in ['tabNext', 'tabprevious']
- call Check_tab_count(6, cmd, 5)
- call Check_tab_count(6, '3' . cmd, 3)
- call Check_tab_count(6, '8' . cmd, 4)
- call Check_tab_count(6, cmd . ' 3', 3)
- call Check_tab_count(6, cmd . ' 8', 4)
- for n in range(2)
- for c in ['0', '.+3', '+', '+2' , '-', '-2' , '$', '+99', '-99']
- if n == 0 " pre count
- let entire_cmd = c . cmd
- let err_code = 'E16:'
- else
- let entire_cmd = cmd . ' ' . c
- let err_code = 'E475:'
- endif
- call assert_fails(entire_cmd, err_code)
- endfor
- endfor
- endfor
-
- 1tabonly!
-endfunction
-
-function s:reconstruct_tabpage_for_test(nr)
- let n = (a:nr > 2) ? a:nr - 2 : 1
- 1tabonly!
- 0tabedit n0
- for n in range(1, n)
- exec '$tabedit n' . n
- if n == 1
- call setline(1, ['', '', '3'])
- endif
- endfor
-endfunc
-
-func Test_tabpage_ctrl_pgup_pgdown()
- enew!
- tabnew tab1
- tabnew tab2
-
- call assert_equal(3, tabpagenr())
- exe "norm! \<C-PageUp>"
- call assert_equal(2, tabpagenr())
- exe "norm! \<C-PageDown>"
- call assert_equal(3, tabpagenr())
-
- " Check wrapping at last or first page.
- exe "norm! \<C-PageDown>"
- call assert_equal(1, tabpagenr())
- exe "norm! \<C-PageUp>"
- call assert_equal(3, tabpagenr())
-
- " With a count, <C-PageUp> and <C-PageDown> are not symmetrical somehow:
- " - {count}<C-PageUp> goes {count} pages downward (relative count)
- " - {count}<C-PageDown> goes to page number {count} (absolute count)
- exe "norm! 2\<C-PageUp>"
- call assert_equal(1, tabpagenr())
- exe "norm! 2\<C-PageDown>"
- call assert_equal(2, tabpagenr())
-
- 1tabonly!
-endfunc
-
-" Test for [count] of tabclose
-function Test_tabpage_with_tabclose()
-
- " pre count
- call s:reconstruct_tabpage_for_test(6)
- call Check_tab_count(3, 'tabclose!', 3)
- call Check_tab_count(1, '3tabclose', 1)
- call Check_tab_count(4, '4tabclose', 3)
- call Check_tab_count(3, '1tabclose', 2)
- call Check_tab_count(2, 'tabclose', 1)
- call assert_equal(1, tabpagenr('$'))
- call assert_equal('', bufname(''))
-
- call s:reconstruct_tabpage_for_test(6)
- call Check_tab_count(2, '$tabclose', 2)
- call Check_tab_count(4, '.tabclose', 4)
- call Check_tab_count(3, '.+tabclose', 3)
- call Check_tab_count(3, '.-2tabclose', 2)
- call Check_tab_count(1, '.+1tabclose!', 1)
- call assert_equal(1, tabpagenr('$'))
- call assert_equal('', bufname(''))
-
- " post count
- call s:reconstruct_tabpage_for_test(6)
- call Check_tab_count(3, 'tabclose!', 3)
- call Check_tab_count(1, 'tabclose 3', 1)
- call Check_tab_count(4, 'tabclose 4', 3)
- call Check_tab_count(3, 'tabclose 1', 2)
- call Check_tab_count(2, 'tabclose', 1)
- call assert_equal(1, tabpagenr('$'))
- call assert_equal('', bufname(''))
-
- call s:reconstruct_tabpage_for_test(6)
- call Check_tab_count(2, 'tabclose $', 2)
- call Check_tab_count(4, 'tabclose', 4)
- call Check_tab_count(3, 'tabclose +', 3)
- call Check_tab_count(3, 'tabclose -2', 2)
- call Check_tab_count(1, 'tabclose! +1', 1)
- call assert_equal(1, tabpagenr('$'))
- call assert_equal('', bufname(''))
-
- call s:reconstruct_tabpage_for_test(6)
- for n in range(2)
- for c in ['0', '$3', '99', '+99', '-99']
- if n == 0 " pre count
- let entire_cmd = c . 'tabclose'
- let err_code = 'E16:'
- else
- let entire_cmd = 'tabclose ' . c
- let err_code = 'E475:'
- endif
- call assert_fails(entire_cmd, err_code)
- call assert_equal(6, tabpagenr('$'))
- endfor
- endfor
-
- call assert_fails('3tabclose', 'E37:')
- call assert_fails('tabclose 3', 'E37:')
- call assert_fails('tabclose -+', 'E475:')
- call assert_fails('tabclose +2-', 'E475:')
- call assert_equal(6, tabpagenr('$'))
-
- 1tabonly!
-endfunction
-
-" Test for [count] of tabonly
-function Test_tabpage_with_tabonly()
-
- " Test for the normal behavior (pre count only)
- let tc = [ [4, '.', '!'], [2, '.+', ''], [3, '.-2', '!'], [1, '.+1', '!'] ]
- for c in tc
- call s:reconstruct_tabpage_for_test(6)
- let entire_cmd = c[1] . 'tabonly' . c[2]
- call Check_tab_count(c[0], entire_cmd, 1)
- call assert_equal(1, tabpagenr('$'))
- endfor
-
- " Test for the normal behavior
- let tc2 = [ [3, '', ''], [1, '3', ''], [4, '4', '!'], [3, '1', '!'],
- \ [2, '', '!'],
- \ [2, '$', '!'], [3, '+', '!'], [3, '-2', '!'], [3, '+1', '!']
- \ ]
- for n in range(2)
- for c in tc2
- call s:reconstruct_tabpage_for_test(6)
- if n == 0 " pre count
- let entire_cmd = c[1] . 'tabonly' . c[2]
- else
- let entire_cmd = 'tabonly' . c[2] . ' ' . c[1]
- endif
- call Check_tab_count(c[0], entire_cmd, 1)
- call assert_equal(1, tabpagenr('$'))
- endfor
- endfor
-
- " Test for the error behavior
- for n in range(2)
- for c in ['0', '$3', '99', '+99', '-99']
- call s:reconstruct_tabpage_for_test(6)
- if n == 0 " pre count
- let entire_cmd = c . 'tabonly'
- let err_code = 'E16:'
- else
- let entire_cmd = 'tabonly ' . c
- let err_code = 'E475:'
- endif
- call assert_fails(entire_cmd, err_code)
- call assert_equal(6, tabpagenr('$'))
- endfor
- endfor
-
- " Test for the error behavior (post count only)
- for c in tc
- call s:reconstruct_tabpage_for_test(6)
- let entire_cmd = 'tabonly' . c[2] . ' ' . c[1]
- let err_code = 'E475:'
- call assert_fails(entire_cmd, err_code)
- call assert_equal(6, tabpagenr('$'))
- endfor
-
- call assert_fails('tabonly -+', 'E475:')
- call assert_fails('tabonly +2-', 'E475:')
- call assert_equal(6, tabpagenr('$'))
-
- 1tabonly!
- new
- only!
-endfunction
-
-func Test_tabnext_on_buf_unload1()
- " This once caused a crash
- new
- tabedit
- tabfirst
- au BufUnload <buffer> tabnext
- q
-
- while tabpagenr('$') > 1
- bwipe!
- endwhile
-endfunc
-
-func Test_tabnext_on_buf_unload2()
- " This once caused a crash
- tabedit
- autocmd BufUnload <buffer> tabnext
- file x
- edit y
-
- while tabpagenr('$') > 1
- bwipe!
- endwhile
-endfunc
-
-func Test_close_on_quitpre()
- " This once caused a crash
- edit Xtest
- new
- only
- set bufhidden=delete
- au QuitPre <buffer> close
- tabnew tab1
- tabnew tab2
- 1tabn
- q!
- call assert_equal(1, tabpagenr())
- call assert_equal(2, tabpagenr('$'))
- " clean up
- while tabpagenr('$') > 1
- bwipe!
- endwhile
- buf Xtest
-endfunc
-
-func Test_tabs()
- enew!
- tabnew tab1
- norm ixxx
- let a=split(execute(':tabs'), "\n")
- call assert_equal(['Tab page 1',
- \ '# [No Name]',
- \ 'Tab page 2',
- \ '> + tab1'], a)
-
- 1tabonly!
- bw!
-endfunc
-
-func Test_tabpage_cmdheight()
- CheckRunVimInTerminal
- call writefile([
- \ 'set laststatus=2',
- \ 'set cmdheight=2',
- \ 'tabnew',
- \ 'set cmdheight=3',
- \ 'tabnext',
- \ 'redraw!',
- \ 'echo "hello\nthere"',
- \ 'tabnext',
- \ 'redraw',
- \ ], 'XTest_tabpage_cmdheight')
- " Check that cursor line is concealed
- let buf = RunVimInTerminal('-S XTest_tabpage_cmdheight', {'statusoff': 3})
- call VerifyScreenDump(buf, 'Test_tabpage_cmdheight', {})
-
- call StopVimInTerminal(buf)
- call delete('XTest_tabpage_cmdheight')
-endfunc
-
-" Test for closing the tab page from a command window
-func Test_tabpage_close_cmdwin()
- tabnew
- call feedkeys("q/:tabclose\<CR>\<Esc>", 'xt')
- call assert_equal(2, tabpagenr('$'))
- call feedkeys("q/:tabonly\<CR>\<Esc>", 'xt')
- call assert_equal(2, tabpagenr('$'))
- tabonly
-endfunc
-
-" Pressing <C-PageUp> in insert mode should go to the previous tab page
-" and <C-PageDown> should go to the next tab page
-func Test_tabpage_Ctrl_Pageup()
- tabnew
- call feedkeys("i\<C-PageUp>", 'xt')
- call assert_equal(1, tabpagenr())
- call feedkeys("i\<C-PageDown>", 'xt')
- call assert_equal(2, tabpagenr())
- %bw!
-endfunc
-
-" Return the terminal key code for selecting a tab page from the tabline. This
-" sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0),
-" KS_FILLER (0x58) and then the tab page number.
-func TabLineSelectPageCode(tabnr)
- return "\x9b\xf0\x58" .. nr2char(a:tabnr)
-endfunc
-
-" Return the terminal key code for opening a new tabpage from the tabpage
-" menu. This sequence consists of the following codes: a CSI (0x9b),
-" KS_TABMENU (0xef), KS_FILLER (0x58), the tab page number and
-" TABLINE_MENU_NEW (2).
-func TabMenuNewItemCode(tabnr)
- return "\x9b\xef\x58" .. nr2char(a:tabnr) .. nr2char(2)
-endfunc
-
-" Return the terminal key code for closing a tabpage from the tabpage menu.
-" This sequence consists of the following codes: a CSI (0x9b), KS_TABMENU
-" (0xef), KS_FILLER (0x58), the tab page number and TABLINE_MENU_CLOSE (1).
-func TabMenuCloseItemCode(tabnr)
- return "\x9b\xef\x58" .. nr2char(a:tabnr) .. nr2char(1)
-endfunc
-
-" Test for using the tabpage menu from the insert and normal modes
-func Test_tabline_tabmenu()
- " only works in GUI
- CheckGui
-
- %bw!
- tabnew
- tabnew
- call assert_equal(3, tabpagenr())
-
- " go to tab page 2 in normal mode
- call feedkeys(TabLineSelectPageCode(2), "Lx!")
- call assert_equal(2, tabpagenr())
-
- " close tab page 3 in normal mode
- call feedkeys(TabMenuCloseItemCode(3), "Lx!")
- call assert_equal(2, tabpagenr('$'))
- call assert_equal(2, tabpagenr())
-
- " open new tab page before tab page 1 in normal mode
- call feedkeys(TabMenuNewItemCode(1), "Lx!")
- call assert_equal(1, tabpagenr())
- call assert_equal(3, tabpagenr('$'))
-
- " go to tab page 2 in operator-pending mode (should beep)
- call assert_beeps('call feedkeys("c" .. TabLineSelectPageCode(2), "Lx!")')
- call assert_equal(2, tabpagenr())
- call assert_equal(3, tabpagenr('$'))
-
- " open new tab page before tab page 1 in operator-pending mode (should beep)
- call assert_beeps('call feedkeys("c" .. TabMenuNewItemCode(1), "Lx!")')
- call assert_equal(1, tabpagenr())
- call assert_equal(4, tabpagenr('$'))
-
- " open new tab page after tab page 3 in normal mode
- call feedkeys(TabMenuNewItemCode(4), "Lx!")
- call assert_equal(4, tabpagenr())
- call assert_equal(5, tabpagenr('$'))
-
- " go to tab page 2 in insert mode
- call feedkeys("i" .. TabLineSelectPageCode(2) .. "\<C-C>", "Lx!")
- call assert_equal(2, tabpagenr())
-
- " close tab page 2 in insert mode
- call feedkeys("i" .. TabMenuCloseItemCode(2) .. "\<C-C>", "Lx!")
- call assert_equal(4, tabpagenr('$'))
-
- " open new tab page before tab page 3 in insert mode
- call feedkeys("i" .. TabMenuNewItemCode(3) .. "\<C-C>", "Lx!")
- call assert_equal(3, tabpagenr())
- call assert_equal(5, tabpagenr('$'))
-
- " open new tab page after tab page 4 in insert mode
- call feedkeys("i" .. TabMenuNewItemCode(5) .. "\<C-C>", "Lx!")
- call assert_equal(5, tabpagenr())
- call assert_equal(6, tabpagenr('$'))
-
- %bw!
-endfunc
-
-" Test for changing the current tab page from an autocmd when closing a tab
-" page.
-func Test_tabpage_switchtab_on_close()
- only
- tabnew
- tabnew
- " Test for BufLeave
- augroup T1
- au!
- au BufLeave * tabfirst
- augroup END
- tabclose
- call assert_equal(1, tabpagenr())
- augroup T1
- au!
- augroup END
-
- " Test for WinLeave
- $tabnew
- augroup T1
- au!
- au WinLeave * tabfirst
- augroup END
- tabclose
- call assert_equal(1, tabpagenr())
- augroup T1
- au!
- augroup END
-
- " Test for TabLeave
- $tabnew
- augroup T1
- au!
- au TabLeave * tabfirst
- augroup END
- tabclose
- call assert_equal(1, tabpagenr())
- augroup T1
- au!
- augroup END
- augroup! T1
- tabonly
-endfunc
-
-" Test for closing the destination tabpage when jumping from one to another.
-func Test_tabpage_close_on_switch()
- tabnew
- tabnew
- edit Xfile
- augroup T2
- au!
- au BufLeave Xfile 1tabclose
- augroup END
- tabfirst
- call assert_equal(2, tabpagenr())
- call assert_equal('Xfile', @%)
- augroup T2
- au!
- augroup END
- augroup! T2
- %bw!
-endfunc
-
-" Test for jumping to last accessed tabpage
-func Test_lastused_tabpage()
- tabonly!
- call assert_equal(0, tabpagenr('#'))
- call assert_beeps('call feedkeys("g\<Tab>", "xt")')
- call assert_beeps('call feedkeys("\<C-Tab>", "xt")')
- call assert_beeps('call feedkeys("\<C-W>g\<Tab>", "xt")')
- call assert_fails('tabnext #', 'E475:')
-
- " open four tab pages
- tabnew
- tabnew
- tabnew
-
- 2tabnext
-
- " Test for g<Tab>
- call assert_equal(4, tabpagenr('#'))
- call feedkeys("g\<Tab>", "xt")
- call assert_equal(4, tabpagenr())
- call assert_equal(2, tabpagenr('#'))
-
- " Test for <C-Tab>
- call feedkeys("\<C-Tab>", "xt")
- call assert_equal(2, tabpagenr())
- call assert_equal(4, tabpagenr('#'))
-
- " Test for <C-W>g<Tab>
- call feedkeys("\<C-W>g\<Tab>", "xt")
- call assert_equal(4, tabpagenr())
- call assert_equal(2, tabpagenr('#'))
-
- " Test for :tabnext #
- tabnext #
- call assert_equal(2, tabpagenr())
- call assert_equal(4, tabpagenr('#'))
-
- " Try to jump to a closed tab page
- tabclose #
- call assert_equal(0, tabpagenr('#'))
- call feedkeys("g\<Tab>", "xt")
- call assert_equal(2, tabpagenr())
- call feedkeys("\<C-Tab>", "xt")
- call assert_equal(2, tabpagenr())
- call feedkeys("\<C-W>g\<Tab>", "xt")
- call assert_equal(2, tabpagenr())
- call assert_fails('tabnext #', 'E475:')
- call assert_equal(2, tabpagenr())
-
- " Test for :tabonly #
- let wnum = win_getid()
- $tabnew
- tabonly #
- call assert_equal(wnum, win_getid())
- call assert_equal(1, tabpagenr('$'))
-
- " Test for :tabmove #
- tabnew
- let wnum = win_getid()
- tabnew
- tabnew
- tabnext 2
- tabmove #
- call assert_equal(4, tabpagenr())
- call assert_equal(wnum, win_getid())
-
- tabonly!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagcase.vim b/src/nvim/testdir/test_tagcase.vim
deleted file mode 100644
index 53c08ccf1e..0000000000
--- a/src/nvim/testdir/test_tagcase.vim
+++ /dev/null
@@ -1,74 +0,0 @@
-" test 'tagcase' option
-
-func Test_tagcase()
- call writefile(["Bar\tXtext\t3", "Foo\tXtext\t2", "foo\tXtext\t4"], 'Xtags')
- set tags=Xtags
- e Xtext
-
- for &ic in [0, 1]
- for &scs in [0, 1]
- for &g:tc in ["followic", "ignore", "match", "followscs", "smart"]
- for &l:tc in ["", "followic", "ignore", "match", "followscs", "smart"]
- let smart = 0
- if &l:tc != ''
- let tc = &l:tc
- else
- let tc = &g:tc
- endif
- if tc == 'followic'
- let ic = &ic
- elseif tc == 'ignore'
- let ic = 1
- elseif tc == 'followscs'
- let ic = &ic
- let smart = &scs
- elseif tc == 'smart'
- let ic = 1
- let smart = 1
- else
- let ic = 0
- endif
- if ic && smart
- call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name}))
- call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name}))
- elseif ic
- call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name}))
- call assert_equal(['Foo', 'foo'], map(taglist("^Foo$"), {i, v -> v.name}))
- else
- call assert_equal(['foo'], map(taglist("^foo$"), {i, v -> v.name}))
- call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name}))
- endif
- endfor
- endfor
- endfor
- endfor
-
- call delete('Xtags')
- set tags&
- set ic&
- setg tc&
- setl tc&
- set scs&
-endfunc
-
-func Test_set_tagcase()
- " Verify default values.
- set ic&
- setg tc&
- setl tc&
- call assert_equal(0, &ic)
- call assert_equal('followic', &g:tc)
- call assert_equal('followic', &l:tc)
- call assert_equal('followic', &tc)
-
- " Verify that the local setting accepts <empty> but that the global setting
- " does not. The first of these (setting the local value to <empty>) should
- " succeed; the other two should fail.
- setl tc=
- call assert_fails('setg tc=', 'E474:')
- call assert_fails('set tc=', 'E474:')
-
- set ic&
- setg tc&
- setl tc&
-endfunc
diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim
deleted file mode 100644
index cba96d3504..0000000000
--- a/src/nvim/testdir/test_tagfunc.vim
+++ /dev/null
@@ -1,417 +0,0 @@
-" Test 'tagfunc'
-
-source vim9.vim
-source check.vim
-source screendump.vim
-
-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])
-
- let g:tagfunc_args=[]
- execute "tag /foo$"
- call assert_equal('foo$', g:tagfunc_args[0])
- call assert_equal('r', g:tagfunc_args[1])
-
- set cpt=t
- let g:tagfunc_args=[]
- execute "normal! i\<c-n>\<c-y>"
- call assert_equal('\<\k\k', g:tagfunc_args[0])
- call assert_equal('cir', g:tagfunc_args[1])
- call assert_equal('nothing1', getline('.')[0:7])
-
- let g:tagfunc_args=[]
- execute "normal! ono\<c-n>\<c-n>\<c-y>"
- call assert_equal('\<no', g:tagfunc_args[0])
- call assert_equal('cir', g:tagfunc_args[1])
- call assert_equal('nothing2', 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', 'E433')
- delf NullTagFunc
-
- bwipe!
- set tags& tfu& cpt&
- call delete('Xfile1')
-endfunc
-
-" Test for modifying the tag stack from a tag function and jumping to a tag
-" from a tag function
-func Test_tagfunc_settagstack()
- func Mytagfunc1(pat, flags, info)
- call settagstack(1, {'tagname' : 'mytag', 'from' : [0, 10, 1, 0]})
- return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
- endfunc
- set tagfunc=Mytagfunc1
- call writefile([''], 'Xtest')
- call assert_fails('tag xyz', 'E986:')
-
- func Mytagfunc2(pat, flags, info)
- tag test_tag
- return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
- endfunc
- set tagfunc=Mytagfunc2
- call assert_fails('tag xyz', 'E986:')
-
- call delete('Xtest')
- set tagfunc&
- delfunc Mytagfunc1
- delfunc Mytagfunc2
-endfunc
-
-" Script local tagfunc callback function
-func s:ScriptLocalTagFunc(pat, flags, info)
- let g:ScriptLocalFuncArgs = [a:pat, a:flags, a:info]
- return v:null
-endfunc
-
-" Test for different ways of setting the 'tagfunc' option
-func Test_tagfunc_callback()
- func TagFunc1(callnr, pat, flags, info)
- let g:TagFunc1Args = [a:callnr, a:pat, a:flags, a:info]
- return v:null
- endfunc
- func TagFunc2(pat, flags, info)
- let g:TagFunc2Args = [a:pat, a:flags, a:info]
- return v:null
- endfunc
-
- let lines =<< trim END
- #" Test for using a function name
- LET &tagfunc = 'g:TagFunc2'
- new
- LET g:TagFunc2Args = []
- call assert_fails('tag a10', 'E433:')
- call assert_equal(['a10', '', {}], g:TagFunc2Args)
- bw!
-
- #" Test for using a function()
- set tagfunc=function('g:TagFunc1',\ [10])
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a11', 'E433:')
- call assert_equal([10, 'a11', '', {}], g:TagFunc1Args)
- bw!
-
- #" Using a funcref variable to set 'tagfunc'
- VAR Fn = function('g:TagFunc1', [11])
- LET &tagfunc = Fn
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a12', 'E433:')
- call assert_equal([11, 'a12', '', {}], g:TagFunc1Args)
- bw!
-
- #" Using a string(funcref_variable) to set 'tagfunc'
- LET Fn = function('g:TagFunc1', [12])
- LET &tagfunc = string(Fn)
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a12', 'E433:')
- call assert_equal([12, 'a12', '', {}], g:TagFunc1Args)
- bw!
-
- #" Test for using a funcref()
- set tagfunc=funcref('g:TagFunc1',\ [13])
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a13', 'E433:')
- call assert_equal([13, 'a13', '', {}], g:TagFunc1Args)
- bw!
-
- #" Using a funcref variable to set 'tagfunc'
- LET Fn = funcref('g:TagFunc1', [14])
- LET &tagfunc = Fn
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a14', 'E433:')
- call assert_equal([14, 'a14', '', {}], g:TagFunc1Args)
- bw!
-
- #" Using a string(funcref_variable) to set 'tagfunc'
- LET Fn = funcref('g:TagFunc1', [15])
- LET &tagfunc = string(Fn)
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a14', 'E433:')
- call assert_equal([15, 'a14', '', {}], g:TagFunc1Args)
- bw!
-
- #" Test for using a lambda function
- VAR optval = "LSTART a, b, c LMIDDLE TagFunc1(16, a, b, c) LEND"
- LET optval = substitute(optval, ' ', '\\ ', 'g')
- exe "set tagfunc=" .. optval
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a17', 'E433:')
- call assert_equal([16, 'a17', '', {}], g:TagFunc1Args)
- bw!
-
- #" Set 'tagfunc' to a lambda expression
- LET &tagfunc = LSTART a, b, c LMIDDLE TagFunc1(17, a, b, c) LEND
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a18', 'E433:')
- call assert_equal([17, 'a18', '', {}], g:TagFunc1Args)
- bw!
-
- #" Set 'tagfunc' to a string(lambda expression)
- LET &tagfunc = 'LSTART a, b, c LMIDDLE TagFunc1(18, a, b, c) LEND'
- new
- LET g:TagFunc1Args = []
- call assert_fails('tag a18', 'E433:')
- call assert_equal([18, 'a18', '', {}], g:TagFunc1Args)
- bw!
-
- #" Set 'tagfunc' to a variable with a lambda expression
- VAR Lambda = LSTART a, b, c LMIDDLE TagFunc1(19, a, b, c) LEND
- LET &tagfunc = Lambda
- new
- LET g:TagFunc1Args = []
- call assert_fails("tag a19", "E433:")
- call assert_equal([19, 'a19', '', {}], g:TagFunc1Args)
- bw!
-
- #" Set 'tagfunc' to a string(variable with a lambda expression)
- LET Lambda = LSTART a, b, c LMIDDLE TagFunc1(20, a, b, c) LEND
- LET &tagfunc = string(Lambda)
- new
- LET g:TagFunc1Args = []
- call assert_fails("tag a19", "E433:")
- call assert_equal([20, 'a19', '', {}], g:TagFunc1Args)
- bw!
-
- #" Test for using a lambda function with incorrect return value
- LET Lambda = LSTART a, b, c LMIDDLE strlen(a) LEND
- LET &tagfunc = string(Lambda)
- new
- call assert_fails("tag a20", "E987:")
- bw!
-
- #" Test for clearing the 'tagfunc' option
- set tagfunc=''
- set tagfunc&
- call assert_fails("set tagfunc=function('abc')", "E700:")
- call assert_fails("set tagfunc=funcref('abc')", "E700:")
-
- #" set 'tagfunc' to a non-existing function
- LET &tagfunc = function('g:TagFunc2', [21])
- LET g:TagFunc2Args = []
- call assert_fails("set tagfunc=function('NonExistingFunc')", 'E700:')
- call assert_fails("LET &tagfunc = function('NonExistingFunc')", 'E700:')
- call assert_fails("tag axb123", 'E426:')
- call assert_equal([], g:TagFunc2Args)
- bw!
- END
- call CheckLegacyAndVim9Success(lines)
-
- " Test for using a script-local function name
- func s:TagFunc3(pat, flags, info)
- let g:TagFunc3Args = [a:pat, a:flags, a:info]
- return v:null
- endfunc
- set tagfunc=s:TagFunc3
- new
- let g:TagFunc3Args = []
- call assert_fails('tag a21', 'E433:')
- call assert_equal(['a21', '', {}], g:TagFunc3Args)
- bw!
- let &tagfunc = 's:TagFunc3'
- new
- let g:TagFunc3Args = []
- call assert_fails('tag a22', 'E433:')
- call assert_equal(['a22', '', {}], g:TagFunc3Args)
- bw!
- delfunc s:TagFunc3
-
- " invalid return value
- let &tagfunc = "{a -> 'abc'}"
- call assert_fails("echo taglist('a')", "E987:")
-
- " Using Vim9 lambda expression in legacy context should fail
- " set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c)
- new
- let g:TagFunc1Args = []
- " call assert_fails("tag a17", "E117:")
- call assert_equal([], g:TagFunc1Args)
- bw!
-
- " Test for using a script local function
- set tagfunc=<SID>ScriptLocalTagFunc
- new
- let g:ScriptLocalFuncArgs = []
- call assert_fails('tag a15', 'E433:')
- call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs)
- bw!
-
- " Test for using a script local funcref variable
- let Fn = function("s:ScriptLocalTagFunc")
- let &tagfunc= Fn
- new
- let g:ScriptLocalFuncArgs = []
- call assert_fails('tag a16', 'E433:')
- call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
- bw!
-
- " Test for using a string(script local funcref variable)
- let Fn = function("s:ScriptLocalTagFunc")
- let &tagfunc= string(Fn)
- new
- let g:ScriptLocalFuncArgs = []
- call assert_fails('tag a16', 'E433:')
- call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
- bw!
-
- " set 'tagfunc' to a partial with dict. This used to cause a crash.
- func SetTagFunc()
- let params = {'tagfn': function('g:DictTagFunc')}
- let &tagfunc = params.tagfn
- endfunc
- func g:DictTagFunc(_) dict
- endfunc
- call SetTagFunc()
- new
- call SetTagFunc()
- bw
- call test_garbagecollect_now()
- new
- set tagfunc=
- wincmd w
- set tagfunc=
- :%bw!
- delfunc g:DictTagFunc
- delfunc SetTagFunc
-
- " Vim9 tests
- let lines =<< trim END
- vim9script
-
- def Vim9tagFunc(callnr: number, pat: string, flags: string, info: dict<any>): any
- g:Vim9tagFuncArgs = [callnr, pat, flags, info]
- return null
- enddef
-
- # Test for using a def function with completefunc
- set tagfunc=function('Vim9tagFunc',\ [60])
- new
- g:Vim9tagFuncArgs = []
- assert_fails('tag a10', 'E433:')
- assert_equal([60, 'a10', '', {}], g:Vim9tagFuncArgs)
-
- # Test for using a global function name
- &tagfunc = g:TagFunc2
- new
- g:TagFunc2Args = []
- assert_fails('tag a11', 'E433:')
- assert_equal(['a11', '', {}], g:TagFunc2Args)
- bw!
-
- # Test for using a script-local function name
- def s:LocalTagFunc(pat: string, flags: string, info: dict<any> ): any
- g:LocalTagFuncArgs = [pat, flags, info]
- return null
- enddef
- &tagfunc = s:LocalTagFunc
- new
- g:LocalTagFuncArgs = []
- assert_fails('tag a12', 'E433:')
- assert_equal(['a12', '', {}], g:LocalTagFuncArgs)
- bw!
- END
- call CheckScriptSuccess(lines)
-
- " cleanup
- delfunc TagFunc1
- delfunc TagFunc2
- set tagfunc&
- %bw!
-endfunc
-
-func Test_tagfunc_wipes_buffer()
- func g:Tag0unc0(t,f,o)
- bwipe
- endfunc
- set tagfunc=g:Tag0unc0
- new
- cal assert_fails('tag 0', 'E987:')
-
- delfunc g:Tag0unc0
- set tagfunc=
-endfunc
-
-func Test_tagfunc_closes_window()
- split any
- func MytagfuncClose(pat, flags, info)
- close
- return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
- endfunc
- set tagfunc=MytagfuncClose
- call assert_fails('tag xyz', 'E1299:')
-
- set tagfunc=
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
deleted file mode 100644
index be60a3535c..0000000000
--- a/src/nvim/testdir/test_tagjump.vim
+++ /dev/null
@@ -1,1615 +0,0 @@
-" Tests for tagjump (tags and special searches)
-
-source check.vim
-source screendump.vim
-
-" SEGV occurs in older versions. (At least 7.4.1748 or older)
-func Test_ptag_with_notagstack()
- CheckFeature quickfix
-
- set notagstack
- call assert_fails('ptag does_not_exist_tag_name', 'E433')
- set tagstack&vim
-endfunc
-
-func Test_ptjump()
- CheckFeature quickfix
-
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "one\tXfile\t1",
- \ "three\tXfile\t3",
- \ "two\tXfile\t2"],
- \ 'Xtags')
- call writefile(['one', 'two', 'three'], 'Xfile')
-
- %bw!
- ptjump two
- call assert_equal(2, winnr())
- wincmd p
- call assert_equal(1, &previewwindow)
- call assert_equal('Xfile', expand("%:p:t"))
- call assert_equal(2, line('.'))
- call assert_equal(2, winnr('$'))
- call assert_equal(1, winnr())
- close
- call setline(1, ['one', 'two', 'three'])
- exe "normal 3G\<C-W>g}"
- call assert_equal(2, winnr())
- wincmd p
- call assert_equal(1, &previewwindow)
- call assert_equal('Xfile', expand("%:p:t"))
- call assert_equal(3, line('.'))
- call assert_equal(2, winnr('$'))
- call assert_equal(1, winnr())
- close
- exe "normal 3G5\<C-W>\<C-G>}"
- wincmd p
- call assert_equal(5, winheight(0))
- close
-
- call delete('Xtags')
- call delete('Xfile')
- set tags&
-endfunc
-
-func Test_cancel_ptjump()
- CheckFeature quickfix
-
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "word\tfile1\tcmd1",
- \ "word\tfile2\tcmd2"],
- \ 'Xtags')
-
- only!
- call feedkeys(":ptjump word\<CR>\<CR>", "xt")
- help
- call assert_equal(2, winnr('$'))
-
- call delete('Xtags')
- set tags&
- quit
-endfunc
-
-func Test_static_tagjump()
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "one\tXfile1\t/^one/;\"\tf\tfile:\tsignature:(void)",
- \ "word\tXfile2\tcmd2"],
- \ 'Xtags')
- new Xfile1
- call setline(1, ['empty', 'one()', 'empty'])
- write
- tag one
- call assert_equal(2, line('.'))
-
- bwipe!
- set tags&
- call delete('Xtags')
- call delete('Xfile1')
-endfunc
-
-func Test_duplicate_tagjump()
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "thesame\tXfile1\t1;\"\td\tfile:",
- \ "thesame\tXfile1\t2;\"\td\tfile:",
- \ "thesame\tXfile1\t3;\"\td\tfile:",
- \ ],
- \ 'Xtags')
- new Xfile1
- call setline(1, ['thesame one', 'thesame two', 'thesame three'])
- write
- tag thesame
- call assert_equal(1, line('.'))
- tnext
- call assert_equal(2, line('.'))
- tnext
- call assert_equal(3, line('.'))
-
- bwipe!
- set tags&
- call delete('Xtags')
- call delete('Xfile1')
-endfunc
-
-func Test_tagjump_switchbuf()
- CheckFeature quickfix
-
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "second\tXfile1\t2",
- \ "third\tXfile1\t3",],
- \ 'Xtags')
- call writefile(['first', 'second', 'third'], 'Xfile1')
-
- enew | only
- set switchbuf=
- stag second
- call assert_equal(2, winnr('$'))
- call assert_equal(2, line('.'))
- stag third
- call assert_equal(3, winnr('$'))
- call assert_equal(3, line('.'))
-
- enew | only
- set switchbuf=useopen
- stag second
- call assert_equal(2, winnr('$'))
- call assert_equal(2, line('.'))
- stag third
- call assert_equal(2, winnr('$'))
- call assert_equal(3, line('.'))
-
- enew | only
- set switchbuf=usetab
- tab stag second
- call assert_equal(2, tabpagenr('$'))
- call assert_equal(2, line('.'))
- 1tabnext | stag third
- call assert_equal(2, tabpagenr('$'))
- call assert_equal(3, line('.'))
-
- tabclose!
- enew | only
- call delete('Xfile1')
- call delete('Xtags')
- set tags&
- set switchbuf&vim
-endfunc
-
-" Tests for [ CTRL-I and CTRL-W CTRL-I commands
-function Test_keyword_jump()
- call writefile(["#include Xinclude", "",
- \ "",
- \ "/* test text test tex start here",
- \ " some text",
- \ " test text",
- \ " start OK if found this line",
- \ " start found wrong line",
- \ "test text"], 'Xtestfile')
- call writefile(["/* test text test tex start here",
- \ " some text",
- \ " test text",
- \ " start OK if found this line",
- \ " start found wrong line",
- \ "test text"], 'Xinclude')
- new Xtestfile
- call cursor(1,1)
- call search("start")
- exe "normal! 5[\<C-I>"
- call assert_equal(" start OK if found this line", getline('.'))
- call cursor(1,1)
- call search("start")
- exe "normal! 5\<C-W>\<C-I>"
- call assert_equal(" start OK if found this line", getline('.'))
-
- " invalid tag search pattern
- call assert_fails('tag /\%(/', 'E426:')
-
- enew! | only
- call delete('Xtestfile')
- call delete('Xinclude')
-endfunction
-
-" Test for jumping to a tag with 'hidden' set, with symbolic link in path of
-" tag. This only works for Unix, because of the symbolic link.
-func Test_tag_symbolic()
- if !has('unix')
- return
- endif
- set hidden
- call delete("Xtest.dir", "rf")
- call system("ln -s . Xtest.dir")
- " Create a tags file with the current directory name inserted.
- call writefile([
- \ "SECTION_OFF " . getcwd() . "/Xtest.dir/Xtest.c /^#define SECTION_OFF 3$/",
- \ '',
- \ ], 'Xtags')
- call writefile(['#define SECTION_OFF 3',
- \ '#define NUM_SECTIONS 3'], 'Xtest.c')
-
- " Try jumping to a tag, but with a path that contains a symbolic link. When
- " wrong, this will give the ATTENTION message. The next space will then be
- " eaten by hit-return, instead of moving the cursor to 'd'.
- set tags=Xtags
- enew!
- call append(0, 'SECTION_OFF')
- call cursor(1,1)
- exe "normal \<C-]> "
- call assert_equal('Xtest.c', expand('%:t'))
- call assert_equal(2, col('.'))
-
- set nohidden
- set tags&
- enew!
- call delete('Xtags')
- call delete('Xtest.c')
- call delete("Xtest.dir", "rf")
- %bwipe!
-endfunc
-
-" Tests for tag search with !_TAG_FILE_ENCODING.
-func Test_tag_file_encoding()
- if has('vms')
- throw 'Skipped: does not work on VMS'
- endif
-
- if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21"
- throw 'Skipped: iconv does not work'
- endif
-
- let save_enc = &encoding
- set encoding=utf8
-
- let content = ['text for tags1', 'abcdefghijklmnopqrs']
- call writefile(content, 'Xtags1.txt')
- let content = ['text for tags2', 'ABC']
- call writefile(content, 'Xtags2.txt')
- let content = ['text for tags3', 'ABC']
- call writefile(content, 'Xtags3.txt')
- let content = ['!_TAG_FILE_ENCODING utf-8 //', 'abcdefghijklmnopqrs Xtags1.txt /abcdefghijklmnopqrs']
- call writefile(content, 'Xtags1')
-
- " case1:
- new
- set tags=Xtags1
- tag abcdefghijklmnopqrs
- call assert_equal('Xtags1.txt', expand('%:t'))
- call assert_equal('abcdefghijklmnopqrs', getline('.'))
- close
-
- " case2:
- new
- let content = ['!_TAG_FILE_ENCODING cp932 //',
- \ "\x82`\x82a\x82b Xtags2.txt /\x82`\x82a\x82b"]
- call writefile(content, 'Xtags')
- set tags=Xtags
- tag /.BC
- call assert_equal('Xtags2.txt', expand('%:t'))
- call assert_equal('ABC', getline('.'))
- call delete('Xtags')
- close
-
- " case3:
- new
- let contents = [
- \ "!_TAG_FILE_SORTED 1 //",
- \ "!_TAG_FILE_ENCODING cp932 //"]
- for i in range(1, 100)
- call add(contents, 'abc' .. i
- \ .. " Xtags3.txt /\x82`\x82a\x82b")
- endfor
- call writefile(contents, 'Xtags')
- set tags=Xtags
- tag abc50
- call assert_equal('Xtags3.txt', expand('%:t'))
- call assert_equal('ABC', getline('.'))
- call delete('Xtags')
- close
-
- set tags&
- let &encoding = save_enc
- call delete('Xtags1.txt')
- call delete('Xtags2.txt')
- call delete('Xtags3.txt')
- call delete('Xtags1')
-endfunc
-
-" Test for emacs-style tags file (TAGS)
-func Test_tagjump_etags()
- if !has('emacs_tags')
- return
- endif
- call writefile([
- \ "void foo() {}",
- \ "int main(int argc, char **argv)",
- \ "{",
- \ "\tfoo();",
- \ "\treturn 0;",
- \ "}",
- \ ], 'Xmain.c')
-
- call writefile([
- \ "\x0c",
- \ "Xmain.c,64",
- \ "void foo() {}\x7ffoo\x011,0",
- \ "int main(int argc, char **argv)\x7fmain\x012,14",
- \ ], 'Xtags')
- set tags=Xtags
- 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('.'))
- call assert_fails('tag bar', 'E426:')
-
- " 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', 'E431:')
-
- " end of file after a CTRL-L line
- call writefile([
- \ "\x0c",
- \ "Xmain.c,64",
- \ "void foo() {}\x7ffoo\x011,0",
- \ "\x0c",
- \ ], 'Xtags')
- call assert_fails('tag main', 'E426:')
-
- " error in an included tags file
- call writefile([
- \ "\x0c",
- \ "Xtags2,include"
- \ ], 'Xtags')
- call writefile([
- \ "\x0c",
- \ "Xmain.c,64",
- \ "void foo() {}",
- \ ], 'Xtags2')
- call assert_fails('tag foo', 'E431:')
-
- call delete('Xtags')
- call delete('Xtags2')
- call delete('Xmain.c')
- set tags&
- bwipe!
-endfunc
-
-" Test for getting and modifying the tag stack
-func Test_getsettagstack()
- call writefile(['line1', 'line2', 'line3'], 'Xfile1')
- call writefile(['line1', 'line2', 'line3'], 'Xfile2')
- call writefile(['line1', 'line2', 'line3'], 'Xfile3')
-
- enew | only
- call settagstack(1, {'items' : []})
- call assert_equal(0, gettagstack(1).length)
- call assert_equal([], 1->gettagstack().items)
- " Error cases
- call assert_equal({}, gettagstack(100))
- call assert_equal(-1, settagstack(100, {'items' : []}))
- call assert_fails('call settagstack(1, [1, 10])', 'E715')
- call assert_fails("call settagstack(1, {'items' : 10})", 'E714')
- call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928')
- call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962')
- call assert_equal(-1, settagstack(0, v:_null_dict))
-
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "one\tXfile1\t1",
- \ "three\tXfile3\t3",
- \ "two\tXfile2\t2"],
- \ 'Xtags')
-
- let stk = []
- call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'one',
- \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
- tag one
- call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'two',
- \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
- tag two
- call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'three',
- \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
- tag three
- call assert_equal(3, gettagstack(1).length)
- call assert_equal(stk, gettagstack(1).items)
- " Check for default - current window
- call assert_equal(3, gettagstack().length)
- call assert_equal(stk, gettagstack().items)
-
- " Try to set current index to invalid values
- call settagstack(1, {'curidx' : -1})
- call assert_equal(1, gettagstack().curidx)
- eval {'curidx' : 50}->settagstack(1)
- call assert_equal(4, gettagstack().curidx)
-
- " Try pushing invalid items onto the stack
- call settagstack(1, {'items' : []})
- call settagstack(1, {'items' : ["plate"]}, 'a')
- call assert_equal(0, gettagstack().length)
- call assert_equal([], gettagstack().items)
- call settagstack(1, {'items' : [{"tagname" : "abc"}]}, 'a')
- call assert_equal(0, gettagstack().length)
- call assert_equal([], gettagstack().items)
- call settagstack(1, {'items' : [{"from" : 100}]}, 'a')
- call assert_equal(0, gettagstack().length)
- call assert_equal([], gettagstack().items)
- call settagstack(1, {'items' : [{"from" : [2, 1, 0, 0]}]}, 'a')
- call assert_equal(0, gettagstack().length)
- call assert_equal([], gettagstack().items)
-
- " Push one item at a time to the stack
- call settagstack(1, {'items' : []})
- call settagstack(1, {'items' : [stk[0]]}, 'a')
- call settagstack(1, {'items' : [stk[1]]}, 'a')
- call settagstack(1, {'items' : [stk[2]]}, 'a')
- call settagstack(1, {'curidx' : 4})
- call assert_equal({'length' : 3, 'curidx' : 4, 'items' : stk},
- \ gettagstack(1))
-
- " Try pushing items onto a full stack
- for i in range(7)
- call settagstack(1, {'items' : stk}, 'a')
- endfor
- call assert_equal(20, gettagstack().length)
- call settagstack(1,
- \ {'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",
- \ "two\tXfile2\t3",
- \ "two\tXfile3\t2"],
- \ 'Xtags')
- call settagstack(1, {'items' : []})
- tag two
- tnext
- tnext
- call assert_equal(1, gettagstack().length)
- call assert_equal(3, gettagstack().items[0].matchnr)
-
- call settagstack(1, {'items' : []})
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
- call delete('Xtags')
- set tags&
-endfunc
-
-func Test_tag_with_count()
- call writefile([
- \ 'test Xtest.h /^void test();$/;" p typeref:typename:void signature:()',
- \ ], 'Xtags')
- call writefile([
- \ 'main Xtest.c /^int main()$/;" f typeref:typename:int signature:()',
- \ 'test Xtest.c /^void test()$/;" f typeref:typename:void signature:()',
- \ ], 'Ytags')
- cal writefile([
- \ 'int main()',
- \ 'void test()',
- \ ], 'Xtest.c')
- cal writefile([
- \ 'void test();',
- \ ], 'Xtest.h')
- set tags=Xtags,Ytags
-
- new Xtest.c
- let tl = taglist('test', 'Xtest.c')
- call assert_equal(tl[0].filename, 'Xtest.c')
- call assert_equal(tl[1].filename, 'Xtest.h')
-
- tag test
- call assert_equal(bufname('%'), 'Xtest.c')
- 1tag test
- call assert_equal(bufname('%'), 'Xtest.c')
- 2tag test
- call assert_equal(bufname('%'), 'Xtest.h')
-
- set tags&
- call delete('Xtags')
- call delete('Ytags')
- bwipe Xtest.h
- bwipe Xtest.c
- call delete('Xtest.h')
- call delete('Xtest.c')
-endfunc
-
-func Test_tagnr_recall()
- call writefile([
- \ 'test Xtest.h /^void test();$/;" p',
- \ 'main Xtest.c /^int main()$/;" f',
- \ 'test Xtest.c /^void test()$/;" f',
- \ ], 'Xtags')
- cal writefile([
- \ 'int main()',
- \ 'void test()',
- \ ], 'Xtest.c')
- cal writefile([
- \ 'void test();',
- \ ], 'Xtest.h')
- set tags=Xtags
-
- new Xtest.c
- let tl = taglist('test', 'Xtest.c')
- call assert_equal(tl[0].filename, 'Xtest.c')
- call assert_equal(tl[1].filename, 'Xtest.h')
-
- 2tag test
- call assert_equal(bufname('%'), 'Xtest.h')
- pop
- call assert_equal(bufname('%'), 'Xtest.c')
- tag
- call assert_equal(bufname('%'), 'Xtest.h')
-
- set tags&
- call delete('Xtags')
- bwipe Xtest.h
- bwipe Xtest.c
- call delete('Xtest.h')
- call delete('Xtest.c')
-endfunc
-
-func Test_tag_line_toolong()
- call writefile([
- \ '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML'
- \ ], 'Xtags')
- set tags=Xtags
- let old_vbs = &verbose
- set verbose=5
- " ":tjump" should give "tag not found" not "Format error in tags file"
- call assert_fails('tj /foo', 'E426')
- try
- tj /foo
- catch /^Vim\%((\a\+)\)\=:E431/
- call assert_report(v:exception)
- catch /.*/
- endtry
- 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')
- call assert_fails('tj /foo', 'E426')
- try
- tj /foo
- catch /^Vim\%((\a\+)\)\=:E431/
- call assert_report(v:exception)
- catch /.*/
- endtry
- 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])
-
- " expansion on command line works with long lines when &wildoptions contains
- " 'tagfile'
- set wildoptions=tagfile
- call writefile([
- \ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa file /^pattern$/;" f'
- \ ], 'Xtags')
- call feedkeys(":tag \<Tab>", 'tx')
- " Should not crash
- call assert_true(v:true)
-
- call delete('Xtags')
- call delete('Xsomewhere')
- set tags&
- let &verbose = old_vbs
-endfunc
-
-" Check that using :tselect does not run into the hit-enter prompt.
-" Requires a terminal to trigger that prompt.
-func Test_tselect()
- CheckScreendump
-
- call writefile([
- \ 'main Xtest.h /^void test();$/;" f',
- \ 'main Xtest.c /^int main()$/;" f',
- \ 'main Xtest.x /^void test()$/;" f',
- \ ], 'Xtags')
- cal writefile([
- \ 'int main()',
- \ 'void test()',
- \ ], 'Xtest.c')
-
- let lines =<< trim [SCRIPT]
- set tags=Xtags
- [SCRIPT]
- call writefile(lines, 'XTest_tselect')
- let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50})
-
- call term_wait(buf, 100)
- call term_sendkeys(buf, ":tselect main\<CR>2\<CR>")
- call VerifyScreenDump(buf, 'Test_tselect_1', {})
-
- call StopVimInTerminal(buf)
- call delete('Xtags')
- call delete('Xtest.c')
- call delete('XTest_tselect')
-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 expanding environment variable in a tag file name
-func Test_tag_envvar()
- call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags')
- set tags=Xtags
-
- let $FOO='TagTestEnv'
-
- let caught_exception = v:false
- try
- tag Func1
- catch /E429:/
- call assert_match('E429:.*"TagTestEnv".*', v:exception)
- let caught_exception = v:true
- endtry
- call assert_true(caught_exception)
-
- set tags&
- call delete('Xtags')
- unlet $FOO
-endfunc
-
-" Test for :ptag
-func Test_tag_preview()
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "second\tXfile1\t2",
- \ "third\tXfile1\t3",],
- \ 'Xtags')
- set tags=Xtags
- call writefile(['first', 'second', 'third'], 'Xfile1')
-
- enew | only
- ptag third
- call assert_equal(2, winnr())
- call assert_equal(2, winnr('$'))
- call assert_equal(1, getwinvar(1, '&previewwindow'))
- call assert_equal(0, getwinvar(2, '&previewwindow'))
- wincmd P
- call assert_equal(3, line('.'))
-
- " jump to the tag again
- wincmd w
- ptag third
- wincmd P
- call assert_equal(3, line('.'))
-
- " jump to the newer tag
- wincmd w
- ptag
- wincmd P
- call assert_equal(3, line('.'))
-
- " close the preview window
- pclose
- call assert_equal(1, winnr('$'))
-
- call delete('Xfile1')
- call delete('Xtags')
- set tags&
-endfunc
-
-" Tests for guessing the tag location
-func Test_tag_guess()
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "func1\tXfoo\t/^int func1(int x)/",
- \ "func2\tXfoo\t/^int func2(int y)/",
- \ "func3\tXfoo\t/^func3/",
- \ "func4\tXfoo\t/^func4/"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
-
- int FUNC1 (int x) { }
- int
- func2 (int y) { }
- int * func3 () { }
-
- [CODE]
- call writefile(code, 'Xfoo')
-
- let v:statusmsg = ''
- ta func1
- call assert_match('E435:', v:statusmsg)
- call assert_equal(2, line('.'))
- let v:statusmsg = ''
- ta func2
- call assert_match('E435:', v:statusmsg)
- call assert_equal(4, line('.'))
- let v:statusmsg = ''
- ta func3
- call assert_match('E435:', v:statusmsg)
- call assert_equal(5, line('.'))
- call assert_fails('ta func4', 'E434:')
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
-endfunc
-
-" Test for an unsorted tags file
-func Test_tag_sort()
- let l = [
- \ "first\tXfoo\t1",
- \ "ten\tXfoo\t3",
- \ "six\tXfoo\t2"]
- call writefile(l, 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int six() {}
- int ten() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- call assert_fails('tag first', 'E432:')
-
- " When multiple tag files are not sorted, then message should be displayed
- " multiple times
- call writefile(l, 'Xtags2')
- set tags=Xtags,Xtags2
- call assert_fails('tag first', ['E432:', 'E432:'])
-
- call delete('Xtags')
- call delete('Xtags2')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Test for an unsorted tags file
-func Test_tag_fold()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/",
- \ "first\tXfoo\t1",
- \ "second\tXfoo\t2",
- \ "third\tXfoo\t3"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int second() {}
- int third() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- enew
- tag second
- call assert_equal('Xfoo', bufname(''))
- call assert_equal(2, line('.'))
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Test for the :ltag command
-func Test_ltag()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo\t1",
- \ "second\tXfoo\t/^int second() {}$/",
- \ "third\tXfoo\t3"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int second() {}
- int third() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- enew
- call setloclist(0, [], 'f')
- ltag third
- call assert_equal('Xfoo', bufname(''))
- call assert_equal(3, line('.'))
- call assert_equal([{'lnum': 3, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'),
- \ 'col': 0, 'end_col': 0, 'pattern': '', 'valid': 1, 'vcol': 0,
- \ 'nr': 0, 'type': '', 'module': '', 'text': 'third'}], getloclist(0))
-
- ltag second
- call assert_equal(2, line('.'))
- call assert_equal([{'lnum': 0, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'),
- \ 'col': 0, 'end_col': 0, 'pattern': '^\Vint second() {}\$',
- \ 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', 'module': '',
- \ 'text': 'second'}], getloclist(0))
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Test for setting the last search pattern to the tag search pattern
-" when cpoptions has 't'
-func Test_tag_last_search_pat()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo\t/^int first() {}/",
- \ "second\tXfoo\t/^int second() {}/",
- \ "third\tXfoo\t/^int third() {}/"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int second() {}
- int third() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- enew
- let save_cpo = &cpo
- set cpo+=t
- let @/ = ''
- tag second
- call assert_equal('^int second() {}', @/)
- let &cpo = save_cpo
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Tag stack tests
-func Test_tag_stack()
- let l = []
- for i in range(10, 31)
- let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"]
- endfor
- call writefile(l, 'Xtags')
- set tags=Xtags
-
- let l = []
- for i in range(10, 31)
- let l += ["int var" .. i .. ";"]
- endfor
- call writefile(l, 'Xfoo')
-
- " Jump to a tag when the tag stack is full. Oldest entry should be removed.
- enew
- for i in range(10, 30)
- exe "tag var" .. i
- endfor
- let l = gettagstack()
- call assert_equal(20, l.length)
- call assert_equal('var11', l.items[0].tagname)
- tag var31
- let l = gettagstack()
- call assert_equal('var12', l.items[0].tagname)
- call assert_equal('var31', l.items[19].tagname)
-
- " Use tnext with a single match
- call assert_fails('tnext', 'E427:')
-
- " Jump to newest entry from the top of the stack
- call assert_fails('tag', 'E556:')
-
- " Pop with zero count from the top of the stack
- call assert_fails('0pop', 'E556:')
-
- " Pop from an unsaved buffer
- enew!
- call append(1, "sample text")
- call assert_fails('pop', 'E37:')
- call assert_equal(21, gettagstack().curidx)
- enew!
-
- " Pop all the entries in the tag stack
- call assert_fails('30pop', 'E555:')
-
- " Pop with a count when already at the bottom of the stack
- call assert_fails('exe "normal 4\<C-T>"', 'E555:')
- call assert_equal(1, gettagstack().curidx)
-
- " Jump to newest entry from the bottom of the stack with zero count
- call assert_fails('0tag', 'E555:')
-
- " Pop the tag stack when it is empty
- call settagstack(1, {'items' : []})
- call assert_fails('pop', 'E73:')
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Test for browsing multiple matching tags
-func Test_tag_multimatch()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo\t1",
- \ "first\tXfoo\t2",
- \ "first\tXfoo\t3"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int first() {}
- int first() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- call settagstack(1, {'items' : []})
- tag first
- tlast
- call assert_equal(3, line('.'))
- call assert_fails('tnext', 'E428:')
- tfirst
- call assert_equal(1, line('.'))
- call assert_fails('tprev', 'E425:')
-
- tlast
- call feedkeys("5\<CR>", 't')
- tselect first
- call assert_equal(2, gettagstack().curidx)
-
- set ignorecase
- tag FIRST
- tnext
- call assert_equal(2, line('.'))
- tlast
- tprev
- call assert_equal(2, line('.'))
- tNext
- call assert_equal(1, line('.'))
- set ignorecase&
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Test for previewing multiple matching tags
-func Test_preview_tag_multimatch()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo\t1",
- \ "first\tXfoo\t2",
- \ "first\tXfoo\t3"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int first() {}
- int first() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- enew | only
- ptag first
- ptlast
- wincmd P
- call assert_equal(3, line('.'))
- wincmd w
- call assert_fails('ptnext', 'E428:')
- ptprev
- wincmd P
- call assert_equal(2, line('.'))
- wincmd w
- ptfirst
- wincmd P
- call assert_equal(1, line('.'))
- wincmd w
- call assert_fails('ptprev', 'E425:')
- ptnext
- wincmd P
- call assert_equal(2, line('.'))
- wincmd w
- ptlast
- call feedkeys("5\<CR>", 't')
- ptselect first
- wincmd P
- call assert_equal(3, line('.'))
-
- pclose
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Test for jumping to multiple matching tags across multiple :tags commands
-func Test_tnext_multimatch()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo1\t1",
- \ "first\tXfoo2\t1",
- \ "first\tXfoo3\t1"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- [CODE]
- call writefile(code, 'Xfoo1')
- call writefile(code, 'Xfoo2')
- call writefile(code, 'Xfoo3')
-
- tag first
- tag first
- pop
- tnext
- tnext
- call assert_fails('tnext', 'E428:')
-
- call delete('Xtags')
- call delete('Xfoo1')
- call delete('Xfoo2')
- call delete('Xfoo3')
- set tags&
- %bwipe
-endfunc
-
-" Test for jumping to multiple matching tags in non-existing files
-func Test_multimatch_non_existing_files()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo1\t1",
- \ "first\tXfoo2\t1",
- \ "first\tXfoo3\t1"],
- \ 'Xtags')
- set tags=Xtags
-
- call settagstack(1, {'items' : []})
- call assert_fails('tag first', 'E429:')
- call assert_equal(3, gettagstack().items[0].matchnr)
-
- call delete('Xtags')
- set tags&
- %bwipe
-endfunc
-
-func Test_tselect_listing()
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:",
- \ "first\tXfoo\t2" .. ';"' .. "\tkind:v\ttyperef:typename:char\tfile:"],
- \ 'Xtags')
- set tags=Xtags
-
- let code =<< trim [CODE]
- static int first;
- static char first;
- [CODE]
- call writefile(code, 'Xfoo')
-
- call feedkeys("\<CR>", "t")
- let l = split(execute("tselect first"), "\n")
- let expected =<< [DATA]
- # pri kind tag file
- 1 FS v first Xfoo
- typeref:typename:int
- 1
- 2 FS v first Xfoo
- typeref:typename:char
- 2
-Type number and <Enter> (q or empty cancels):
-[DATA]
- call assert_equal(expected, l)
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags&
- %bwipe
-endfunc
-
-" Test for :isearch, :ilist, :ijump and :isplit commands
-" Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands
-func Test_inc_search()
- new
- call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo', '==='])
- call cursor(3, 1)
-
- " Test for [i and ]i
- call assert_equal('1:foo', execute('normal [i'))
- call assert_equal('2:foo', execute('normal 2[i'))
- call assert_fails('normal 3[i', 'E387:')
- call assert_equal('3:foo', execute('normal ]i'))
- call assert_equal('4:foo', execute('normal 2]i'))
- call assert_fails('normal 3]i', 'E389:')
- call assert_fails('normal G]i', 'E349:')
- call assert_fails('normal [i', 'E349:')
- call cursor(3, 1)
-
- " Test for :isearch
- call assert_equal('1:foo', execute('isearch foo'))
- call assert_equal('3:foo', execute('isearch 4 /foo/'))
- call assert_fails('isearch 3 foo', 'E387:')
- call assert_equal('3:foo', execute('+1,$isearch foo'))
- call assert_fails('1,.-1isearch 3 foo', 'E389:')
- call assert_fails('isearch bar', 'E389:')
- call assert_fails('isearch /foo/3', 'E488:')
-
- " Test for [I and ]I
- call assert_equal([
- \ ' 1: 1 1:foo',
- \ ' 2: 2 2:foo',
- \ ' 3: 3 foo',
- \ ' 4: 4 3:foo',
- \ ' 5: 5 4:foo'], split(execute('normal [I'), "\n"))
- call assert_equal([
- \ ' 1: 4 3:foo',
- \ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n"))
- call assert_fails('normal G]I', 'E349:')
- call assert_fails('normal [I', 'E349:')
- call cursor(3, 1)
-
- " Test for :ilist
- call assert_equal([
- \ ' 1: 1 1:foo',
- \ ' 2: 2 2:foo',
- \ ' 3: 3 foo',
- \ ' 4: 4 3:foo',
- \ ' 5: 5 4:foo'], split(execute('ilist foo'), "\n"))
- call assert_equal([
- \ ' 1: 4 3:foo',
- \ ' 2: 5 4:foo'], split(execute('+1,$ilist /foo/'), "\n"))
- call assert_fails('ilist bar', 'E389:')
-
- " Test for [ CTRL-I and ] CTRL-I
- exe "normal [\t"
- call assert_equal([1, 3], [line('.'), col('.')])
- exe "normal 2j4[\t"
- call assert_equal([4, 3], [line('.'), col('.')])
- call assert_fails("normal k3[\t", 'E387:')
- call assert_fails("normal 6[\t", 'E389:')
- exe "normal ]\t"
- call assert_equal([4, 3], [line('.'), col('.')])
- exe "normal k2]\t"
- call assert_equal([5, 3], [line('.'), col('.')])
- call assert_fails("normal 2k3]\t", 'E389:')
- call assert_fails("normal G[\t", 'E349:')
- call assert_fails("normal ]\t", 'E349:')
- call cursor(3, 1)
-
- " Test for :ijump
- call cursor(3, 1)
- ijump foo
- call assert_equal([1, 3], [line('.'), col('.')])
- call cursor(3, 1)
- ijump 4 /foo/
- call assert_equal([4, 3], [line('.'), col('.')])
- call cursor(3, 1)
- call assert_fails('ijump 3 foo', 'E387:')
- +,$ijump 2 foo
- call assert_equal([5, 3], [line('.'), col('.')])
- call assert_fails('ijump bar', 'E389:')
-
- " Test for CTRL-W i
- call cursor(3, 1)
- wincmd i
- call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')])
- close
- 5wincmd i
- call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')])
- close
- call assert_fails('3wincmd i', 'E387:')
- call assert_fails('6wincmd i', 'E389:')
- call assert_fails("normal G\<C-W>i", 'E349:')
- call cursor(3, 1)
-
- " Test for :isplit
- isplit foo
- call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')])
- close
- isplit 5 /foo/
- call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')])
- close
- call assert_fails('isplit 3 foo', 'E387:')
- call assert_fails('isplit 6 foo', 'E389:')
- call assert_fails('isplit bar', 'E389:')
-
- close!
-endfunc
-
-" this was using a line from ml_get() freed by the regexp
-func Test_isearch_copy_line()
- new
- norm o
- norm 0
- 0norm o
- sil! norm bc0
- sil! isearch \%')
- bwipe!
-endfunc
-
-" Test for :dsearch, :dlist, :djump and :dsplit commands
-" Test for [d, ]d, [D, ]D, [ CTRL-D, ] CTRL-D and CTRL-W d commands
-func Test_macro_search()
- new
- call setline(1, ['#define FOO 1', '#define FOO 2', '#define FOO 3',
- \ '#define FOO 4', '#define FOO 5'])
- call cursor(3, 9)
-
- " Test for [d and ]d
- call assert_equal('#define FOO 1', execute('normal [d'))
- call assert_equal('#define FOO 2', execute('normal 2[d'))
- call assert_fails('normal 3[d', 'E387:')
- call assert_equal('#define FOO 4', execute('normal ]d'))
- call assert_equal('#define FOO 5', execute('normal 2]d'))
- call assert_fails('normal 3]d', 'E388:')
-
- " Test for :dsearch
- call assert_equal('#define FOO 1', execute('dsearch FOO'))
- call assert_equal('#define FOO 5', execute('dsearch 5 /FOO/'))
- call assert_fails('dsearch 3 FOO', 'E387:')
- call assert_equal('#define FOO 4', execute('+1,$dsearch FOO'))
- call assert_fails('1,.-1dsearch 3 FOO', 'E388:')
- call assert_fails('dsearch BAR', 'E388:')
-
- " Test for [D and ]D
- call assert_equal([
- \ ' 1: 1 #define FOO 1',
- \ ' 2: 2 #define FOO 2',
- \ ' 3: 3 #define FOO 3',
- \ ' 4: 4 #define FOO 4',
- \ ' 5: 5 #define FOO 5'], split(execute('normal [D'), "\n"))
- call assert_equal([
- \ ' 1: 4 #define FOO 4',
- \ ' 2: 5 #define FOO 5'], split(execute('normal ]D'), "\n"))
-
- " Test for :dlist
- call assert_equal([
- \ ' 1: 1 #define FOO 1',
- \ ' 2: 2 #define FOO 2',
- \ ' 3: 3 #define FOO 3',
- \ ' 4: 4 #define FOO 4',
- \ ' 5: 5 #define FOO 5'], split(execute('dlist FOO'), "\n"))
- call assert_equal([
- \ ' 1: 4 #define FOO 4',
- \ ' 2: 5 #define FOO 5'], split(execute('+1,$dlist /FOO/'), "\n"))
- call assert_fails('dlist BAR', 'E388:')
-
- " Test for [ CTRL-D and ] CTRL-D
- exe "normal [\<C-D>"
- call assert_equal([1, 9], [line('.'), col('.')])
- exe "normal 2j4[\<C-D>"
- call assert_equal([4, 9], [line('.'), col('.')])
- call assert_fails("normal k3[\<C-D>", 'E387:')
- call assert_fails("normal 6[\<C-D>", 'E388:')
- exe "normal ]\<C-D>"
- call assert_equal([4, 9], [line('.'), col('.')])
- exe "normal k2]\<C-D>"
- call assert_equal([5, 9], [line('.'), col('.')])
- call assert_fails("normal 2k3]\<C-D>", 'E388:')
-
- " Test for :djump
- call cursor(3, 9)
- djump FOO
- call assert_equal([1, 9], [line('.'), col('.')])
- call cursor(3, 9)
- djump 4 /FOO/
- call assert_equal([4, 9], [line('.'), col('.')])
- call cursor(3, 9)
- call assert_fails('djump 3 FOO', 'E387:')
- +,$djump 2 FOO
- call assert_equal([5, 9], [line('.'), col('.')])
- call assert_fails('djump BAR', 'E388:')
-
- " Test for CTRL-W d
- call cursor(3, 9)
- wincmd d
- call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')])
- close
- 5wincmd d
- call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')])
- close
- call assert_fails('3wincmd d', 'E387:')
- call assert_fails('6wincmd d', 'E388:')
- new
- call assert_fails("normal \<C-W>d", 'E349:')
- call assert_fails("normal \<C-W>\<C-D>", 'E349:')
- close
-
- " Test for :dsplit
- dsplit FOO
- call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')])
- close
- dsplit 5 /FOO/
- call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')])
- close
- call assert_fails('dsplit 3 FOO', 'E387:')
- call assert_fails('dsplit 6 FOO', 'E388:')
- call assert_fails('dsplit BAR', 'E388:')
-
- close!
-endfunc
-
-func Test_define_search()
- " this was accessing freed memory
- new
- call setline(1, ['first line', '', '#define something 0'])
- sil norm o0
- sil! norm 
- bwipe!
-
- new somefile
- call setline(1, ['first line', '', '#define something 0'])
- sil norm 0o0
- sil! norm ]d
- bwipe!
-endfunc
-
-" Test for [*, [/, ]* and ]/
-func Test_comment_search()
- new
- call setline(1, ['', '/*', ' *', ' *', ' */'])
- normal! 4gg[/
- call assert_equal([2, 1], [line('.'), col('.')])
- normal! 3gg[*
- call assert_equal([2, 1], [line('.'), col('.')])
- normal! 3gg]/
- call assert_equal([5, 3], [line('.'), col('.')])
- normal! 3gg]*
- call assert_equal([5, 3], [line('.'), col('.')])
- %d
- call setline(1, ['', '/*', ' *', ' *'])
- call assert_beeps('normal! 3gg]/')
- %d
- call setline(1, ['', ' *', ' *', ' */'])
- call assert_beeps('normal! 4gg[/')
- %d
- call setline(1, ' /* comment */')
- normal! 15|[/
- call assert_equal(9, col('.'))
- normal! 15|]/
- call assert_equal(21, col('.'))
- call setline(1, ' comment */')
- call assert_beeps('normal! 15|[/')
- call setline(1, ' /* comment')
- call assert_beeps('normal! 15|]/')
- close!
-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
-
-" Tests for errors in a tags file
-func Test_tagfile_errors()
- set tags=Xtags
-
- " missing search pattern or line number for a tag
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "foo\tXfile\t"], 'Xtags', 'b')
- call writefile(['foo'], 'Xfile')
-
- enew
- tag foo
- call assert_equal('', @%)
- let caught_431 = v:false
- try
- eval taglist('.*')
- catch /:E431:/
- let caught_431 = v:true
- endtry
- call assert_equal(v:true, caught_431)
-
- " tag name and file name are not separated by a tab
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "foo Xfile 1"], 'Xtags')
- call assert_fails('tag foo', 'E431:')
-
- " file name and search pattern are not separated by a tab
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "foo\tXfile 1;"], 'Xtags')
- call assert_fails('tag foo', 'E431:')
-
- call delete('Xtags')
- call delete('Xfile')
- set tags&
-endfunc
-
-" When :stag fails to open the file, should close the new window
-func Test_stag_close_window_on_error()
- new | only
- set tags=Xtags
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "foo\tXfile\t1"], 'Xtags')
- call writefile(['foo'], 'Xfile')
- call writefile([], '.Xfile.swp')
- " Remove the catch-all that runtest.vim adds
- au! SwapExists
- augroup StagTest
- au!
- autocmd SwapExists Xfile let v:swapchoice='q'
- augroup END
-
- stag foo
- call assert_equal(1, winnr('$'))
- call assert_equal('', @%)
-
- augroup StagTest
- au!
- augroup END
- call delete('Xfile')
- call delete('.Xfile.swp')
- set tags&
-endfunc
-
-" Test for 'tagbsearch' (binary search)
-func Test_tagbsearch()
- " If a tags file header says the tags are sorted, but the tags are actually
- " unsorted, then binary search should fail and linear search should work.
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/",
- \ "third\tXfoo\t3",
- \ "second\tXfoo\t2",
- \ "first\tXfoo\t1"],
- \ 'Xtags')
- set tags=Xtags
- let code =<< trim [CODE]
- int first() {}
- int second() {}
- int third() {}
- [CODE]
- call writefile(code, 'Xfoo')
-
- enew
- set tagbsearch
- call assert_fails('tag first', 'E426:')
- call assert_equal('', bufname())
- call assert_fails('tag second', 'E426:')
- call assert_equal('', bufname())
- tag third
- call assert_equal('Xfoo', bufname())
- call assert_equal(3, line('.'))
- %bw!
-
- set notagbsearch
- tag first
- call assert_equal('Xfoo', bufname())
- call assert_equal(1, line('.'))
- enew
- tag second
- call assert_equal('Xfoo', bufname())
- call assert_equal(2, line('.'))
- enew
- tag third
- call assert_equal('Xfoo', bufname())
- call assert_equal(3, line('.'))
- %bw!
-
- " If a tags file header says the tags are unsorted, but the tags are
- " actually sorted, then binary search should work.
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "!_TAG_FILE_SORTED\t0\t/0=unsorted, 1=sorted, 2=foldcase/",
- \ "first\tXfoo\t1",
- \ "second\tXfoo\t2",
- \ "third\tXfoo\t3"],
- \ 'Xtags')
-
- set tagbsearch
- tag first
- call assert_equal('Xfoo', bufname())
- call assert_equal(1, line('.'))
- enew
- tag second
- call assert_equal('Xfoo', bufname())
- call assert_equal(2, line('.'))
- enew
- tag third
- call assert_equal('Xfoo', bufname())
- call assert_equal(3, line('.'))
- %bw!
-
- " Binary search fails on EOF
- call writefile([
- \ "!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/",
- \ "bar\tXfoo\t1",
- \ "foo\tXfoo\t2"],
- \ 'Xtags')
- call assert_fails('tag bbb', 'E426:')
-
- call delete('Xtags')
- call delete('Xfoo')
- set tags& tagbsearch&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
deleted file mode 100644
index 0387ef2bd8..0000000000
--- a/src/nvim/testdir/test_taglist.vim
+++ /dev/null
@@ -1,281 +0,0 @@
-" test taglist(), tagfiles() functions and :tags command
-
-source view_util.vim
-
-func Test_taglist()
- call writefile([
- \ "FFoo\tXfoo\t1",
- \ "FBar\tXfoo\t2",
- \ "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
- split Xtext
-
- call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo"), {i, v -> v.name}))
- call assert_equal(['FFoo', 'BFoo'], map("Foo"->taglist("Xtext"), {i, v -> v.name}))
- 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 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))
- call assert_equal('d', cmd[0]['kind'])
- call assert_equal('call cursor(3, 4)', cmd[0]['cmd'])
-
- " Use characters with value > 127 in the tag extra field.
- call writefile([
- \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ta£££\tv",
- \ ], 'Xtags')
- call assert_equal('v', taglist('vFoo')[0].kind)
-
- call assert_fails("let l=taglist([])", 'E730:')
-
- call delete('Xtags')
- set tags&
- bwipe
-endfunc
-
-func Test_taglist_native_etags()
- if !has('emacs_tags')
- return
- endif
- call writefile([
- \ "\x0c",
- \ "src/os_unix.c,13491",
- \ "set_signals(\x7f1335,32699",
- \ "reset_signals(\x7f1407,34136",
- \ ], 'Xtags')
-
- set tags=Xtags
-
- call assert_equal([['set_signals', '1335,32699'], ['reset_signals', '1407,34136']],
- \ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]}))
-
- call delete('Xtags')
- set tags&
-endfunc
-
-func Test_taglist_ctags_etags()
- if !has('emacs_tags')
- return
- endif
- call writefile([
- \ "\x0c",
- \ "src/os_unix.c,13491",
- \ "set_signals(void)\x7fset_signals\x011335,32699",
- \ "reset_signals(void)\x7freset_signals\x011407,34136",
- \ ], 'Xtags')
-
- set tags=Xtags
-
- call assert_equal([['set_signals', '1335,32699'], ['reset_signals', '1407,34136']],
- \ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]}))
-
- call delete('Xtags')
- set tags&
-endfunc
-
-func Test_tags_too_long()
- call assert_fails('tag ' . repeat('x', 1020), ['E433', 'E426'])
- tags
-endfunc
-
-func Test_tagfiles()
- call assert_equal([], tagfiles())
-
- call writefile(["FFoo\tXfoo\t1"], 'Xtags1')
- call writefile(["FBar\tXbar\t1"], 'Xtags2')
- set tags=Xtags1,Xtags2
- call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
-
- help
- let tf = tagfiles()
- " Nvim: expectation(s) based on tags in build dir (added to &rtp).
- " Filter out the (non-existing) '../../../runtime/doc/tags'.
- call filter(tf, 'filereadable(v:val)')
- call assert_equal(1, len(tf))
- call assert_equal(fnamemodify(expand('$BUILD_DIR/runtime/doc/tags'), ':p:gs?\\?/?'),
- \ fnamemodify(tf[0], ':p:gs?\\?/?'))
- helpclose
- call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
- " Nvim: defaults to "./tags;,tags", which might cause false positives.
- set tags=./tags,tags
- call assert_equal([], tagfiles())
-
- call delete('Xtags1')
- call delete('Xtags2')
- bd
-endfunc
-
-" For historical reasons we support a tags file where the last line is missing
-" the newline.
-func Test_tagsfile_without_trailing_newline()
- call writefile(["Foo\tfoo\t1"], 'Xtags', 'b')
- set tags=Xtags
-
- let tl = taglist('.*')
- call assert_equal(1, len(tl))
- call assert_equal('Foo', tl[0].name)
-
- call delete('Xtags')
- set tags&
-endfunc
-
-" Test for ignoring comments in a tags file
-func Test_tagfile_ignore_comments()
- call writefile([
- \ "!_TAG_PROGRAM_NAME /Test tags generator/",
- \ "FBar\tXfoo\t2" .. ';"' .. "\textrafield\tf",
- \ "!_TAG_FILE_FORMAT 2 /extended format/",
- \ ], 'Xtags')
- set tags=Xtags
-
- let l = taglist('.*')
- call assert_equal(1, len(l))
- call assert_equal('FBar', l[0].name)
-
- set tags&
- call delete('Xtags')
-endfunc
-
-" Test for using an excmd in a tags file to position the cursor (instead of a
-" search pattern or a line number)
-func Test_tagfile_excmd()
- call writefile([
- \ "vFoo\tXfoo\tcall cursor(3, 4)" .. '|;"' .. "\tv",
- \ ], 'Xtags')
- set tags=Xtags
-
- let l = taglist('.*')
- call assert_equal([{
- \ 'cmd' : 'call cursor(3, 4)',
- \ 'static' : 0,
- \ 'name' : 'vFoo',
- \ 'kind' : 'v',
- \ 'filename' : 'Xfoo'}], l)
-
- set tags&
- call delete('Xtags')
-endfunc
-
-" Test for duplicate fields in a tag in a tags file
-func Test_duplicate_field()
- call writefile([
- \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ttypename:int\tv",
- \ ], 'Xtags')
- set tags=Xtags
-
- let l = taglist('.*')
- call assert_equal([{
- \ 'cmd' : '4',
- \ 'static' : 0,
- \ 'name' : 'vFoo',
- \ 'kind' : 'v',
- \ 'typename' : 'int',
- \ 'filename' : 'Xfoo'}], l)
-
- set tags&
- call delete('Xtags')
-endfunc
-
-" Test for tag address with ;
-func Test_tag_addr_with_semicolon()
- call writefile([
- \ "Func1\tXfoo\t6;/^Func1/" .. ';"' .. "\tf"
- \ ], 'Xtags')
- set tags=Xtags
-
- let l = taglist('.*')
- call assert_equal([{
- \ 'cmd' : '6;/^Func1/',
- \ 'static' : 0,
- \ 'name' : 'Func1',
- \ 'kind' : 'f',
- \ 'filename' : 'Xfoo'}], l)
-
- set tags&
- call delete('Xtags')
-endfunc
-
-" Test for format error in a tags file
-func Test_format_error()
- call writefile(['vFoo-Xfoo-4'], 'Xtags')
- set tags=Xtags
-
- let caught_exception = v:false
- try
- let l = taglist('.*')
- catch /E431:/
- " test succeeded
- let caught_exception = v:true
- catch
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
- call assert_true(caught_exception)
-
- " no field after the filename for a tag
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "foo\tXfile"], 'Xtags')
- call assert_fails("echo taglist('foo')", 'E431:')
-
- set tags&
- call delete('Xtags')
-endfunc
-
-" Test for :tag command completion with 'wildoptions' set to 'tagfile'
-func Test_tag_complete_wildoptions()
- call writefile(["foo\ta.c\t10;\"\tf", "bar\tb.c\t20;\"\td"], 'Xtags')
- set tags=Xtags
- set wildoptions=tagfile
-
- call feedkeys(":tag \<C-D>\<C-R>=Screenline(&lines - 1)\<CR> : "
- \ .. "\<C-R>=Screenline(&lines - 2)\<CR>\<C-B>\"\<CR>", 'xt')
-
- call assert_equal('"tag bar d b.c : foo f a.c', @:)
-
- call delete('Xtags')
- set wildoptions&
- set tags&
-endfunc
-
-func Test_tag_complete_with_overlong_line()
- let tagslines =<< trim END
- !_TAG_FILE_FORMAT 2 //
- !_TAG_FILE_SORTED 1 //
- !_TAG_FILE_ENCODING utf-8 //
- inboundGSV a 1;" r
- inboundGovernor a 2;" kind:⊢ type:forall (muxMode :: MuxMode) socket peerAddr versionNumber m a b. (MonadAsync m, MonadCatch m, MonadEvaluate m, MonadThrow m, MonadThrow (STM m), MonadTime m, MonadTimer m, MonadMask m, Ord peerAddr, HasResponder muxMode ~ True) => Tracer m (RemoteTransitionTrace peerAddr) -> Tracer m (InboundGovernorTrace peerAddr) -> ServerControlChannel muxMode peerAddr ByteString m a b -> DiffTime -> MuxConnectionManager muxMode socket peerAddr versionNumber ByteString m a b -> StrictTVar m InboundGovernorObservableState -> m Void
- inboundGovernorCounters a 3;" kind:⊢ type:InboundGovernorState muxMode peerAddr m a b -> InboundGovernorCounters
- END
- call writefile(tagslines, 'Xtags')
- set tags=Xtags
-
- " try with binary search
- set tagbsearch
- call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:)
- " try with linear search
- set notagbsearch
- call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:)
- set tagbsearch&
-
- call delete('Xtags')
- set tags&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_termcodes.vim b/src/nvim/testdir/test_termcodes.vim
deleted file mode 100644
index 99bc2d1d37..0000000000
--- a/src/nvim/testdir/test_termcodes.vim
+++ /dev/null
@@ -1,63 +0,0 @@
-
-" Test for translation of special key codes (<xF1>, <xF2>, etc.)
-func Test_Keycode_Translation()
- let keycodes = [
- \ ["<xUp>", "<Up>"],
- \ ["<xDown>", "<Down>"],
- \ ["<xLeft>", "<Left>"],
- \ ["<xRight>", "<Right>"],
- \ ["<xHome>", "<Home>"],
- \ ["<xEnd>", "<End>"],
- \ ["<zHome>", "<Home>"],
- \ ["<zEnd>", "<End>"],
- \ ["<xF1>", "<F1>"],
- \ ["<xF2>", "<F2>"],
- \ ["<xF3>", "<F3>"],
- \ ["<xF4>", "<F4>"],
- \ ["<S-xF1>", "<S-F1>"],
- \ ["<S-xF2>", "<S-F2>"],
- \ ["<S-xF3>", "<S-F3>"],
- \ ["<S-xF4>", "<S-F4>"]]
- for [k1, k2] in keycodes
- exe "nnoremap " .. k1 .. " 2wx"
- call assert_true(maparg(k1, 'n', 0, 1).lhs == k2)
- exe "nunmap " .. k1
- endfor
-endfunc
-
-" Test for terminal keycodes that doesn't have termcap entries
-func Test_special_term_keycodes()
- new
- " Test for <xHome>, <S-xHome> and <C-xHome>
- " send <K_SPECIAL> <KS_EXTRA> keycode
- call feedkeys("i\<C-K>\x80\xfd\x3f\n", 'xt')
- " send <K_SPECIAL> <KS_MODIFIER> bitmap <K_SPECIAL> <KS_EXTRA> keycode
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3f\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3f\n", 'xt')
- " Test for <xEnd>, <S-xEnd> and <C-xEnd>
- call feedkeys("i\<C-K>\x80\xfd\x3d\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3d\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3d\n", 'xt')
- " Test for <zHome>, <S-zHome> and <C-zHome>
- call feedkeys("i\<C-K>\x80\xfd\x40\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x40\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x40\n", 'xt')
- " Test for <zEnd>, <S-zEnd> and <C-zEnd>
- call feedkeys("i\<C-K>\x80\xfd\x3e\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3e\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3e\n", 'xt')
- " Test for <xUp>, <xDown>, <xLeft> and <xRight>
- call feedkeys("i\<C-K>\x80\xfd\x41\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfd\x42\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfd\x43\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfd\x44\n", 'xt')
- call assert_equal(['<Home>', '<S-Home>', '<C-Home>',
- \ '<End>', '<S-End>', '<C-End>',
- \ '<Home>', '<S-Home>', '<C-Home>',
- \ '<End>', '<S-End>', '<C-End>',
- \ '<Up>', '<Down>', '<Left>', '<Right>', ''], getline(1, '$'))
- bw!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
deleted file mode 100644
index 7a13de12f4..0000000000
--- a/src/nvim/testdir/test_textformat.vim
+++ /dev/null
@@ -1,1307 +0,0 @@
-" Tests for the various 'formatoptions' settings
-
-source check.vim
-
-func Test_text_format()
- enew!
-
- setl noai tw=2 fo=t
- call append('$', [
- \ '{',
- \ ' ',
- \ '',
- \ '}'])
- exe "normal /^{/+1\n0"
- normal gRa b
- let lnum = line('.')
- call assert_equal([
- \ 'a',
- \ 'b'], getline(lnum - 1, lnum))
-
- normal ggdG
- setl ai tw=2 fo=tw
- call append('$', [
- \ '{',
- \ 'a b ',
- \ '',
- \ 'a ',
- \ '}'])
- exe "normal /^{/+1\n0"
- normal gqgqjjllab
- let lnum = line('.')
- call assert_equal([
- \ 'a ',
- \ 'b ',
- \ '',
- \ 'a ',
- \ 'b'], getline(lnum - 4, lnum))
-
- normal ggdG
- setl tw=3 fo=t
- call append('$', [
- \ '{',
- \ "a \<C-A>",
- \ '}'])
- exe "normal /^{/+1\n0"
- exe "normal gqgqo\na \<C-V>\<C-A>"
- let lnum = line('.')
- call assert_equal([
- \ 'a',
- \ "\<C-A>",
- \ '',
- \ 'a',
- \ "\<C-A>"], getline(lnum - 4, lnum))
-
- normal ggdG
- setl tw=2 fo=tcq1 comments=:#
- call append('$', [
- \ '{',
- \ 'a b',
- \ '#a b',
- \ '}'])
- exe "normal /^{/+1\n0"
- exe "normal gqgqjgqgqo\na b\n#a b"
- let lnum = line('.')
- call assert_equal([
- \ 'a b',
- \ '#a b',
- \ '',
- \ 'a b',
- \ '#a b'], getline(lnum - 4, lnum))
-
- normal ggdG
- setl tw=5 fo=tcn comments=:#
- call append('$', [
- \ '{',
- \ ' 1 a',
- \ '# 1 a',
- \ '}'])
- exe "normal /^{/+1\n0"
- exe "normal A b\<Esc>jA b"
- let lnum = line('.')
- call assert_equal([
- \ ' 1 a',
- \ ' b',
- \ '# 1 a',
- \ '# b'], getline(lnum - 3, lnum))
-
- normal ggdG
- setl tw=5 fo=t2a si
- call append('$', [
- \ '{',
- \ '',
- \ ' x a',
- \ ' b',
- \ ' c',
- \ '',
- \ '}'])
- exe "normal /^{/+3\n0"
- exe "normal i \<Esc>A_"
- let lnum = line('.')
- call assert_equal([
- \ '',
- \ ' x a',
- \ ' b_',
- \ ' c',
- \ ''], getline(lnum - 2, lnum + 2))
-
- normal ggdG
- setl tw=5 fo=qn comments=:#
- call append('$', [
- \ '{',
- \ '# 1 a b',
- \ '}'])
- exe "normal /^{/+1\n5|"
- normal gwap
- call assert_equal(5, col('.'))
- let lnum = line('.')
- call assert_equal([
- \ '# 1 a',
- \ '# b'], getline(lnum, lnum + 1))
-
- normal ggdG
- setl tw=5 fo=q2 comments=:#
- call append('$', [
- \ '{',
- \ '# x',
- \ '# a b',
- \ '}'])
- exe "normal /^{/+1\n0"
- normal gwap
- let lnum = line('.')
- call assert_equal([
- \ '# x a',
- \ '# b'], getline(lnum, lnum + 1))
-
- normal ggdG
- setl tw& fo=a
- call append('$', [
- \ '{',
- \ ' 1aa',
- \ ' 2bb',
- \ '}'])
- exe "normal /^{/+2\n0"
- normal I^^
- call assert_equal('{ 1aa ^^2bb }', getline('.'))
-
- normal ggdG
- setl tw=20 fo=an12wcq comments=s1:/*,mb:*,ex:*/
- call append('$', [
- \ '/* abc def ghi jkl ',
- \ ' * mno pqr stu',
- \ ' */'])
- exe "normal /mno pqr/\n"
- normal A vwx yz
- let lnum = line('.')
- call assert_equal([
- \ ' * mno pqr stu ',
- \ ' * vwx yz',
- \ ' */'], getline(lnum - 1, lnum + 1))
-
- normal ggdG
- setl tw=12 fo=tqnc comments=:#
- call setline('.', '# 1 xxxxx')
- normal A foobar
- call assert_equal([
- \ '# 1 xxxxx',
- \ '# foobar'], getline(1, 2))
-
- " Test the 'p' flag for 'formatoptions'
- " First test without the flag: that it will break "Mr. Feynman" at the space
- normal ggdG
- setl tw=28 fo=tcq
- call setline('.', 'Surely you''re joking, Mr. Feynman!')
- normal gqq
- call assert_equal([
- \ 'Surely you''re joking, Mr.',
- \ 'Feynman!'], getline(1, 2))
- " Now test with the flag: that it will push the name with the title onto the
- " next line
- normal ggdG
- setl fo+=p
- call setline('.', 'Surely you''re joking, Mr. Feynman!')
- normal gqq
- call assert_equal([
- \ 'Surely you''re joking,',
- \ 'Mr. Feynman!'], getline(1, 2))
- " Ensure that it will still break if two spaces are entered
- normal ggdG
- call setline('.', 'Surely you''re joking, Mr. Feynman!')
- normal gqq
- call assert_equal([
- \ 'Surely you''re joking, Mr.',
- \ 'Feynman!'], getline(1, 2))
-
- setl ai& tw& fo& si& comments&
- enew!
-endfunc
-
-func Test_format_c_comment()
- new
- setl ai cindent tw=40 et fo=croql
- let text =<< trim END
- var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
- END
- call setline(1, text)
- normal gql
- let expected =<< trim END
- var = 2345; // asdf asdf asdf asdf asdf
- // asdf asdf asdf asdf asdf
- END
- call assert_equal(expected, getline(1, '$'))
-
- %del
- let text =<< trim END
- var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
- END
- call setline(1, text)
- normal gql
- let expected =<< trim END
- var = 2345; // asdf asdf asdf asdf asdf
- // asdf asdf asdf asdf asdf
- // asdf asdf
- END
- call assert_equal(expected, getline(1, '$'))
-
- %del
- let text =<< trim END
- #if 0 // This is another long end of
- // line comment that
- // wraps.
- END
- call setline(1, text)
- normal gq2j
- let expected =<< trim END
- #if 0 // This is another long
- // end of line comment
- // that wraps.
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Using either "o" or "O" repeats a line comment occupying a whole line.
- %del
- let text =<< trim END
- nop;
- // This is a comment
- val = val;
- END
- call setline(1, text)
- normal 2Go
- let expected =<< trim END
- nop;
- // This is a comment
- //
- val = val;
- END
- call assert_equal(expected, getline(1, '$'))
- normal 2GO
- let expected =<< trim END
- nop;
- //
- // This is a comment
- //
- val = val;
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Using "o" repeats a line comment after a statement, "O" does not.
- %del
- let text =<< trim END
- nop;
- val = val; // This is a comment
- END
- call setline(1, text)
- normal 2Go
- let expected =<< trim END
- nop;
- val = val; // This is a comment
- //
- END
- call assert_equal(expected, getline(1, '$'))
- 3delete
-
- " No comment repeated with a slash in 'formatoptions'
- set fo+=/
- normal 2Gox
- let expected =<< trim END
- nop;
- val = val; // This is a comment
- x
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Comment is formatted when it wraps
- normal 2GA with some more text added
- let expected =<< trim END
- nop;
- val = val; // This is a comment
- // with some more text
- // added
- x
- END
- call assert_equal(expected, getline(1, '$'))
-
- set fo-=/
-
- " using 'indentexpr' instead of 'cindent' does not repeat a comment
- setl nocindent indentexpr=2
- %del
- let text =<< trim END
- nop;
- val = val; // This is a comment
- END
- call setline(1, text)
- normal 2Gox
- let expected =<< trim END
- nop;
- val = val; // This is a comment
- x
- END
- call assert_equal(expected, getline(1, '$'))
- setl cindent indentexpr=
- 3delete
-
- normal 2GO
- let expected =<< trim END
- nop;
-
- val = val; // This is a comment
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Using "o" does not repeat a comment in a string
- %del
- let text =<< trim END
- nop;
- val = " // This is not a comment";
- END
- call setline(1, text)
- normal 2Gox
- let expected =<< trim END
- nop;
- val = " // This is not a comment";
- x
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Using CTRL-U after "o" fixes the indent
- %del
- let text =<< trim END
- {
- val = val; // This is a comment
- END
- call setline(1, text)
- exe "normal! 2Go\<C-U>x\<Esc>"
- let expected =<< trim END
- {
- val = val; // This is a comment
- x
- END
- call assert_equal(expected, getline(1, '$'))
-
- " typing comment text auto-wraps
- %del
- call setline(1, text)
- exe "normal! 2GA blah more text blah.\<Esc>"
- let expected =<< trim END
- {
- val = val; // This is a comment
- // blah more text
- // blah.
- END
- call assert_equal(expected, getline(1, '$'))
-
- bwipe!
-endfunc
-
-" Tests for :right, :center and :left on text with embedded TAB.
-func Test_format_align()
- enew!
- set tw=65
-
- " :left alignment
- call append(0, [
- \ " test for :left",
- \ " a a",
- \ " fa a",
- \ " dfa a",
- \ " sdfa a",
- \ " asdfa a",
- \ " xasdfa a",
- \ "asxxdfa a",
- \ ])
- %left
- call assert_equal([
- \ "test for :left",
- \ "a a",
- \ "fa a",
- \ "dfa a",
- \ "sdfa a",
- \ "asdfa a",
- \ "xasdfa a",
- \ "asxxdfa a",
- \ ""
- \ ], getline(1, '$'))
- enew!
-
- " :center alignment
- call append(0, [
- \ " test for :center",
- \ " a a",
- \ " fa afd asdf",
- \ " dfa a",
- \ " sdfa afd asdf",
- \ " asdfa a",
- \ " xasdfa asdfasdfasdfasdfasdf",
- \ "asxxdfa a"
- \ ])
- %center
- call assert_equal([
- \ " test for :center",
- \ " a a",
- \ " fa afd asdf",
- \ " dfa a",
- \ " sdfa afd asdf",
- \ " asdfa a",
- \ " xasdfa asdfasdfasdfasdfasdf",
- \ " asxxdfa a",
- \ ""
- \ ], getline(1, '$'))
- enew!
-
- " :right alignment
- call append(0, [
- \ " test for :right",
- \ " a a",
- \ " fa a",
- \ " dfa a",
- \ " sdfa a",
- \ " asdfa a",
- \ " xasdfa a",
- \ " asxxdfa a",
- \ " asxa;ofa a",
- \ " asdfaqwer a",
- \ " a ax",
- \ " fa ax",
- \ " dfa ax",
- \ " sdfa ax",
- \ " asdfa ax",
- \ " xasdfa ax",
- \ " asxxdfa ax",
- \ " asxa;ofa ax",
- \ " asdfaqwer ax",
- \ " a axx",
- \ " fa axx",
- \ " dfa axx",
- \ " sdfa axx",
- \ " asdfa axx",
- \ " xasdfa axx",
- \ " asxxdfa axx",
- \ " asxa;ofa axx",
- \ " asdfaqwer axx",
- \ " a axxx",
- \ " fa axxx",
- \ " dfa axxx",
- \ " sdfa axxx",
- \ " asdfa axxx",
- \ " xasdfa axxx",
- \ " asxxdfa axxx",
- \ " asxa;ofa axxx",
- \ " asdfaqwer axxx",
- \ " a axxxo",
- \ " fa axxxo",
- \ " dfa axxxo",
- \ " sdfa axxxo",
- \ " asdfa axxxo",
- \ " xasdfa axxxo",
- \ " asxxdfa axxxo",
- \ " asxa;ofa axxxo",
- \ " asdfaqwer axxxo",
- \ " a axxxoi",
- \ " fa axxxoi",
- \ " dfa axxxoi",
- \ " sdfa axxxoi",
- \ " asdfa axxxoi",
- \ " xasdfa axxxoi",
- \ " asxxdfa axxxoi",
- \ " asxa;ofa axxxoi",
- \ " asdfaqwer axxxoi",
- \ " a axxxoik",
- \ " fa axxxoik",
- \ " dfa axxxoik",
- \ " sdfa axxxoik",
- \ " asdfa axxxoik",
- \ " xasdfa axxxoik",
- \ " asxxdfa axxxoik",
- \ " asxa;ofa axxxoik",
- \ " asdfaqwer axxxoik",
- \ " a axxxoike",
- \ " fa axxxoike",
- \ " dfa axxxoike",
- \ " sdfa axxxoike",
- \ " asdfa axxxoike",
- \ " xasdfa axxxoike",
- \ " asxxdfa axxxoike",
- \ " asxa;ofa axxxoike",
- \ " asdfaqwer axxxoike",
- \ " a axxxoikey",
- \ " fa axxxoikey",
- \ " dfa axxxoikey",
- \ " sdfa axxxoikey",
- \ " asdfa axxxoikey",
- \ " xasdfa axxxoikey",
- \ " asxxdfa axxxoikey",
- \ " asxa;ofa axxxoikey",
- \ " asdfaqwer axxxoikey",
- \ ])
- %right
- call assert_equal([
- \ "\t\t\t\t test for :right",
- \ "\t\t\t\t a a",
- \ "\t\t\t\t fa a",
- \ "\t\t\t\t dfa a",
- \ "\t\t\t\t sdfa a",
- \ "\t\t\t\t asdfa a",
- \ "\t\t\t\t xasdfa a",
- \ "\t\t\t\t asxxdfa a",
- \ "\t\t\t\t asxa;ofa a",
- \ "\t\t\t\t asdfaqwer a",
- \ "\t\t\t\t a ax",
- \ "\t\t\t\t fa ax",
- \ "\t\t\t\t dfa ax",
- \ "\t\t\t\t sdfa ax",
- \ "\t\t\t\t asdfa ax",
- \ "\t\t\t\t xasdfa ax",
- \ "\t\t\t\t asxxdfa ax",
- \ "\t\t\t\t asxa;ofa ax",
- \ "\t\t\t\t asdfaqwer ax",
- \ "\t\t\t\t a axx",
- \ "\t\t\t\t fa axx",
- \ "\t\t\t\t dfa axx",
- \ "\t\t\t\t sdfa axx",
- \ "\t\t\t\t asdfa axx",
- \ "\t\t\t\t xasdfa axx",
- \ "\t\t\t\t asxxdfa axx",
- \ "\t\t\t\t asxa;ofa axx",
- \ "\t\t\t\t asdfaqwer axx",
- \ "\t\t\t\t a axxx",
- \ "\t\t\t\t fa axxx",
- \ "\t\t\t\t dfa axxx",
- \ "\t\t\t\t sdfa axxx",
- \ "\t\t\t\t asdfa axxx",
- \ "\t\t\t\t xasdfa axxx",
- \ "\t\t\t\t asxxdfa axxx",
- \ "\t\t\t\t asxa;ofa axxx",
- \ "\t\t\t\t asdfaqwer axxx",
- \ "\t\t\t\t a axxxo",
- \ "\t\t\t\t fa axxxo",
- \ "\t\t\t\t dfa axxxo",
- \ "\t\t\t\t sdfa axxxo",
- \ "\t\t\t\t asdfa axxxo",
- \ "\t\t\t\t xasdfa axxxo",
- \ "\t\t\t\t asxxdfa axxxo",
- \ "\t\t\t\t asxa;ofa axxxo",
- \ "\t\t\t\t asdfaqwer axxxo",
- \ "\t\t\t\t a axxxoi",
- \ "\t\t\t\t fa axxxoi",
- \ "\t\t\t\t dfa axxxoi",
- \ "\t\t\t\t sdfa axxxoi",
- \ "\t\t\t\t asdfa axxxoi",
- \ "\t\t\t\t xasdfa axxxoi",
- \ "\t\t\t\t asxxdfa axxxoi",
- \ "\t\t\t\t asxa;ofa axxxoi",
- \ "\t\t\t\t asdfaqwer axxxoi",
- \ "\t\t\t\t a axxxoik",
- \ "\t\t\t\t fa axxxoik",
- \ "\t\t\t\t dfa axxxoik",
- \ "\t\t\t\t sdfa axxxoik",
- \ "\t\t\t\t asdfa axxxoik",
- \ "\t\t\t\t xasdfa axxxoik",
- \ "\t\t\t\t asxxdfa axxxoik",
- \ "\t\t\t\t asxa;ofa axxxoik",
- \ "\t\t\t\t asdfaqwer axxxoik",
- \ "\t\t\t\t a axxxoike",
- \ "\t\t\t\t fa axxxoike",
- \ "\t\t\t\t dfa axxxoike",
- \ "\t\t\t\t sdfa axxxoike",
- \ "\t\t\t\t asdfa axxxoike",
- \ "\t\t\t\t xasdfa axxxoike",
- \ "\t\t\t\t asxxdfa axxxoike",
- \ "\t\t\t\t asxa;ofa axxxoike",
- \ "\t\t\t\t asdfaqwer axxxoike",
- \ "\t\t\t\t a axxxoikey",
- \ "\t\t\t\t fa axxxoikey",
- \ "\t\t\t\t dfa axxxoikey",
- \ "\t\t\t\t sdfa axxxoikey",
- \ "\t\t\t\t asdfa axxxoikey",
- \ "\t\t\t\t xasdfa axxxoikey",
- \ "\t\t\t\t asxxdfa axxxoikey",
- \ "\t\t\t\t asxa;ofa axxxoikey",
- \ "\t\t\t\t asdfaqwer axxxoikey",
- \ ""
- \ ], getline(1, '$'))
- enew!
-
- " align text with 'wrapmargin'
- 50vnew
- call setline(1, ['Vim'])
- setl textwidth=0
- setl wrapmargin=30
- right
- call assert_equal("\t\t Vim", getline(1))
- q!
-
- " align text with 'rightleft'
- if has('rightleft')
- new
- call setline(1, 'Vim')
- setlocal rightleft
- left 20
- setlocal norightleft
- call assert_equal("\t\t Vim", getline(1))
- setlocal rightleft
- right
- setlocal norightleft
- call assert_equal("Vim", getline(1))
- close!
- endif
-
- set tw&
-endfunc
-
-" Test formatting a paragraph.
-func Test_format_para()
- enew!
- set fo+=tcroql tw=72
-
- call append(0, [
- \ "xxxxx xx xxxxxx ",
- \ "xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx",
- \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx",
- \ "xx xxxxxxx. xxxx xxxx.",
- \ "",
- \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx",
- \ "> xxxxxx xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx"
- \ ])
- exe "normal /xxxxxxxx$\<CR>"
- normal 0gq6kk
- call assert_equal([
- \ "xxxxx xx xxxxxx xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx",
- \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx xx xxxxxxx.",
- \ "xxxx xxxx.",
- \ "",
- \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx xxxxxx",
- \ "> xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx",
- \ ""
- \ ], getline(1, '$'))
-
- set fo& tw&
- enew!
-endfunc
-
-" Test undo after ":%s" and formatting.
-func Test_format_undo()
- enew!
- map gg :.,.+2s/^/x/<CR>kk:set tw=3<CR>gqq
-
- call append(0, [
- \ "aa aa aa aa",
- \ "bb bb bb bb",
- \ "cc cc cc cc"
- \ ])
- " undo/redo here to make the next undo only work on the following changes
- exe "normal i\<C-G>u"
- call cursor(1,1)
- normal ggu
- call assert_equal([
- \ "aa aa aa aa",
- \ "bb bb bb bb",
- \ "cc cc cc cc",
- \ ""
- \ ], getline(1, '$'))
-
- unmap gg
- set tw&
- enew!
-endfunc
-
-func Test_format_list_auto()
- new
- call setline(1, ['1. abc', '2. def', '3. ghi'])
- set fo=tan ai bs=2
- call feedkeys("3G0lli\<BS>\<BS>x\<Esc>", 'tx')
- call assert_equal('2. defx ghi', getline(2))
- bwipe!
- set fo& ai& bs&
-endfunc
-
-func Test_crash_github_issue_5095()
- CheckFeature autocmd
-
- " This used to segfault, see https://github.com/vim/vim/issues/5095
- augroup testing
- au BufNew x center
- augroup END
-
- next! x
-
- bw
- augroup testing
- au!
- augroup END
- augroup! testing
-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_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_multibyte()
- 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 'matchpairs' in latin1 encoding
-func Test_mps_latin1()
- new
- let save_enc = &encoding
- " set encoding=latin1
- call setline(1, 'abc(def)ghi')
- normal %
- call assert_equal(8, col('.'))
- normal %
- call assert_equal(4, col('.'))
- call cursor(1, 6)
- normal [(
- call assert_equal(4, col('.'))
- normal %
- call assert_equal(8, col('.'))
- call cursor(1, 6)
- normal ])
- call assert_equal(8, col('.'))
- normal %
- call assert_equal(4, col('.'))
- let &encoding = save_enc
- close!
-endfunc
-
-func Test_empty_matchpairs()
- split
- set matchpairs= showmatch
- call assert_nobeep('call feedkeys("ax\tx\t\<Esc>", "xt")')
- set matchpairs& noshowmatch
- bwipe!
-endfunc
-
-func Test_mps_error()
- let encoding_save = &encoding
-
- " for e in ['utf-8', 'latin1']
- for e in ['utf-8']
- exe 'set encoding=' .. e
-
- call assert_fails('set mps=<:', 'E474:', e)
- call assert_fails('set mps=:>', 'E474:', e)
- call assert_fails('set mps=<>', 'E474:', e)
- call assert_fails('set mps=<:>_', 'E474:', e)
- endfor
-
- let &encoding = encoding_save
-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
-
-" Test for 'a' and 'w' flags in 'formatoptions'
-func Test_fo_a_w()
- new
- setlocal fo+=aw tw=10
- call feedkeys("iabc abc a abc\<Esc>k0weade", 'xt')
- call assert_equal(['abc abcde ', 'a abc'], getline(1, '$'))
-
- " when a line ends with space, it is not broken up.
- %d
- call feedkeys("ione two to ", 'xt')
- call assert_equal('one two to ', getline(1))
-
- " when a line ends with spaces and backspace is used in the next line, the
- " last space in the previous line should be removed.
- %d
- set backspace=indent,eol,start
- call setline(1, ['one ', 'two'])
- exe "normal 2Gi\<BS>"
- call assert_equal(['one two'], getline(1, '$'))
- set backspace&
-
- " Test for 'a', 'w' and '1' options.
- setlocal textwidth=0
- setlocal fo=1aw
- %d
- call setline(1, '. foo')
- normal 72ig
- call feedkeys('a uu uu uu', 'xt')
- call assert_equal('g uu uu ', getline(1)[-8:])
- call assert_equal(['uu. foo'], getline(2, '$'))
-
- " using backspace or "x" triggers reformat
- call setline(1, ['1 2 3 4 5 ', '6 7 8 9'])
- set tw=10
- set fo=taw
- set bs=indent,eol,start
- exe "normal 1G4la\<BS>\<BS>\<Esc>"
- call assert_equal(['1 2 4 5 6 ', '7 8 9'], getline(1, 2))
- exe "normal f4xx"
- call assert_equal(['1 2 5 6 7 ', '8 9'], getline(1, 2))
-
- " using "cw" leaves cursor in right spot
- call setline(1, ['Now we g whether that nation, or',
- \ 'any nation so conceived and,'])
- set fo=tcqa tw=35
- exe "normal 2G0cwx\<Esc>"
- call assert_equal(['Now we g whether that nation, or x', 'nation so conceived and,'], getline(1, 2))
-
- set tw=0
- set fo&
- %bw!
-endfunc
-
-" Test for formatting lines using gq in visual mode
-func Test_visual_gq_format()
- new
- call setline(1, ['one two three four', 'five six', 'one two'])
- setl textwidth=10
- call feedkeys('ggv$jj', 'xt')
- redraw!
- normal gq
- %d
- call setline(1, ['one two three four', 'five six', 'one two'])
- normal G$
- call feedkeys('v0kk', 'xt')
- redraw!
- normal gq
- setl textwidth&
- close!
-endfunc
-
-" Test for 'n' flag in 'formatoptions' to format numbered lists
-func Test_fo_n()
- new
- setlocal autoindent
- setlocal textwidth=12
- setlocal fo=n
- call setline(1, [' 1) one two three four', ' 2) two'])
- normal gggqG
- call assert_equal([' 1) one two', ' three', ' four', ' 2) two'],
- \ getline(1, '$'))
- close!
-endfunc
-
-" Test for 'formatlistpat' option
-func Test_formatlistpat()
- new
- setlocal autoindent
- setlocal textwidth=10
- setlocal fo=n
- setlocal formatlistpat=^\\s*-\\s*
- call setline(1, [' - one two three', ' - two'])
- normal gggqG
- call assert_equal([' - one', ' two', ' three', ' - two'],
- \ getline(1, '$'))
- close!
-endfunc
-
-" Test for the 'b' and 'v' flags in 'formatoptions'
-" Text should wrap only if a space character is inserted at or before
-" 'textwidth'
-func Test_fo_b()
- new
- setlocal textwidth=20
-
- setlocal formatoptions=t
- call setline(1, 'one two three four')
- call feedkeys('Amore', 'xt')
- call assert_equal(['one two three', 'fourmore'], getline(1, '$'))
-
- setlocal formatoptions=bt
- %d
- call setline(1, 'one two three four')
- call feedkeys('Amore five', 'xt')
- call assert_equal(['one two three fourmore five'], getline(1, '$'))
-
- setlocal formatoptions=bt
- %d
- call setline(1, 'one two three four')
- call feedkeys('A five', 'xt')
- call assert_equal(['one two three four', 'five'], getline(1, '$'))
-
- setlocal formatoptions=vt
- %d
- call setline(1, 'one two three four')
- call feedkeys('Amore five', 'xt')
- call assert_equal(['one two three fourmore', 'five'], getline(1, '$'))
-
- close!
-endfunc
-
-" Test for the '1' flag in 'formatoptions'. Don't wrap text after a one letter
-" word.
-func Test_fo_1()
- new
- setlocal textwidth=20
-
- setlocal formatoptions=t
- call setline(1, 'one two three four')
- call feedkeys('A a bird', 'xt')
- call assert_equal(['one two three four a', 'bird'], getline(1, '$'))
-
- %d
- setlocal formatoptions=t1
- call setline(1, 'one two three four')
- call feedkeys('A a bird', 'xt')
- call assert_equal(['one two three four', 'a bird'], getline(1, '$'))
-
- close!
-endfunc
-
-" Test for 'l' flag in 'formatoptions'. When starting insert mode, if a line
-" is longer than 'textwidth', then it is not broken.
-func Test_fo_l()
- new
- setlocal textwidth=20
-
- setlocal formatoptions=t
- call setline(1, 'one two three four five')
- call feedkeys('A six', 'xt')
- call assert_equal(['one two three four', 'five six'], getline(1, '$'))
-
- %d
- setlocal formatoptions=tl
- call setline(1, 'one two three four five')
- call feedkeys('A six', 'xt')
- call assert_equal(['one two three four five six'], getline(1, '$'))
-
- close!
-endfunc
-
-" Test for the '2' flag in 'formatoptions'
-func Test_fo_2()
- new
- setlocal autoindent
- setlocal formatoptions=t2
- setlocal textwidth=30
- call setline(1, ["\tfirst line of a paragraph.",
- \ "second line of the same paragraph.",
- \ "third line."])
- normal gggqG
- call assert_equal(["\tfirst line of a",
- \ "paragraph. second line of the",
- \ "same paragraph. third line."], getline(1, '$'))
- close!
-endfunc
-
-" This was leaving the cursor after the end of a line. Complicated way to
-" have the problem show up with valgrind.
-func Test_correct_cursor_position()
- " set encoding=iso8859
- new
- norm a000“0
- sil! norm gggg0i0gw0gg
-
- bwipe!
- set encoding=utf8
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
deleted file mode 100644
index f21d6fcb99..0000000000
--- a/src/nvim/testdir/test_textobjects.vim
+++ /dev/null
@@ -1,646 +0,0 @@
-" Test for textobjects
-
-source check.vim
-
-func CpoM(line, useM, expected)
- new
-
- if a:useM
- set cpoptions+=M
- else
- set cpoptions-=M
- endif
-
- call setline(1, a:line)
-
- call setreg('"', '')
- normal! ggfrmavi)y
- call assert_equal(getreg('"'), a:expected[0])
-
- call setreg('"', '')
- normal! `afbmavi)y
- call assert_equal(getreg('"'), a:expected[1])
-
- call setreg('"', '')
- normal! `afgmavi)y
- call assert_equal(getreg('"'), a:expected[2])
-
- q!
-endfunc
-
-func Test_inner_block_without_cpo_M()
- call CpoM('(red \(blue) green)', 0, ['red \(blue', 'red \(blue', ''])
-endfunc
-
-func Test_inner_block_with_cpo_M_left_backslash()
- call CpoM('(red \(blue) green)', 1, ['red \(blue) green', 'blue', 'red \(blue) green'])
-endfunc
-
-func Test_inner_block_with_cpo_M_right_backslash()
- call CpoM('(red (blue\) green)', 1, ['red (blue\) green', 'blue\', 'red (blue\) green'])
-endfunc
-
-func Test_inner_block_single_char()
- new
- call setline(1, "(a)")
-
- set selection=inclusive
- let @" = ''
- call assert_nobeep('norm! 0faviby')
- call assert_equal('a', @")
-
- set selection=exclusive
- let @" = ''
- call assert_nobeep('norm! 0faviby')
- call assert_equal('a', @")
-
- set selection&
- bwipe!
-endfunc
-
-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
-
-func Test_quote_selection_selection_exclusive_abort()
- new
- set selection=exclusive
- call setline(1, "'abzzc'")
- let exp_curs = [0, 1, 6, 0]
- call cursor(1,1)
- exe 'norm! fcdvi"'
- " make sure to end visual mode to have a clear state
- exe "norm! \<esc>"
- call assert_equal(exp_curs, getpos('.'))
- call cursor(1,1)
- exe 'norm! fcvi"'
- exe "norm! \<esc>"
- call assert_equal(exp_curs, getpos('.'))
- call cursor(1,2)
- exe 'norm! vfcoi"'
- exe "norm! \<esc>"
- let exp_curs = [0, 1, 2, 0]
- let exp_visu = [0, 1, 7, 0]
- call assert_equal(exp_curs, getpos('.'))
- call assert_equal(exp_visu, getpos("'>"))
- set selection&vim
- bw!
-endfunc
-
-" Tests for string and html text objects
-func Test_string_html_objects()
-
- " Nvim only supports set encoding=utf-8
- " for e in ['utf-8', 'latin1', 'cp932']
- for e in ['utf-8']
- enew!
- exe 'set enc=' .. e
-
- let t = '"wo\"rd\\" foo'
- put =t
- normal! da"
- call assert_equal('foo', getline('.'), e)
-
- let t = "'foo' 'bar' 'piep'"
- put =t
- normal! 0va'a'rx
- call assert_equal("xxxxxxxxxxxx'piep'", getline('.'), e)
-
- let t = "bla bla `quote` blah"
- put =t
- normal! 02f`da`
- call assert_equal("bla bla blah", getline('.'), e)
-
- let t = 'out " in "noXno"'
- put =t
- normal! 0fXdi"
- call assert_equal('out " in ""', getline('.'), e)
-
- let t = "\"'\" 'blah' rep 'buh'"
- put =t
- normal! 03f'vi'ry
- call assert_equal("\"'\" 'blah'yyyyy'buh'", getline('.'), e)
-
- set quoteescape=+*-
- let t = "bla `s*`d-`+++`l**` b`la"
- put =t
- normal! di`
- call assert_equal("bla `` b`la", getline('.'), e)
-
- let t = 'voo "nah" sdf " asdf" sdf " sdf" sd'
- put =t
- normal! $F"va"oha"i"rz
- call assert_equal('voo "zzzzzzzzzzzzzzzzzzzzzzzzzzzzsd', getline('.'), e)
-
- let t = "-<b>asdf<i>Xasdf</i>asdf</b>-"
- put =t
- normal! fXdit
- call assert_equal('-<b>asdf<i></i>asdf</b>-', getline('.'), e)
-
- let t = "-<b>asdX<i>a<i />sdf</i>asdf</b>-"
- put =t
- normal! 0fXdit
- call assert_equal('-<b></b>-', getline('.'), e)
-
- let t = "-<b>asdf<i>Xasdf</i>asdf</b>-"
- put =t
- normal! fXdat
- call assert_equal('-<b>asdfasdf</b>-', getline('.'), e)
-
- let t = "-<b>asdX<i>as<b />df</i>asdf</b>-"
- put =t
- normal! 0fXdat
- call assert_equal('--', getline('.'), e)
-
- let t = "-<b>\ninnertext object\n</b>"
- put =t
- normal! dit
- call assert_equal('-<b></b>', getline('.'), e)
-
- " copy the tag block from leading indentation before the start tag
- let t = " <b>\ntext\n</b>"
- $put =t
- normal! 2kvaty
- call assert_equal("<b>\ntext\n</b>", @", e)
-
- " copy the tag block from the end tag
- let t = "<title>\nwelcome\n</title>"
- $put =t
- normal! $vaty
- call assert_equal("<title>\nwelcome\n</title>", @", e)
-
- " copy the outer tag block from a tag without an end tag
- let t = "<html>\n<title>welcome\n</html>"
- $put =t
- normal! k$vaty
- call assert_equal("<html>\n<title>welcome\n</html>", @", e)
-
- " nested tag that has < in a different line from >
- let t = "<div><div\n></div></div>"
- $put =t
- normal! k0vaty
- call assert_equal("<div><div\n></div></div>", @", e)
-
- " nested tag with attribute that has < in a different line from >
- let t = "<div><div\nattr=\"attr\"\n></div></div>"
- $put =t
- normal! 2k0vaty
- call assert_equal("<div><div\nattr=\"attr\"\n></div></div>", @", e)
-
- set quoteescape&
-
- " this was going beyond the end of the line
- %del
- sil! norm i"\
- sil! norm i"\
- sil! norm i"\
- call assert_equal('"\', getline(1))
-
- bwipe!
- endfor
-
- set enc=utf-8
-endfunc
-
-func Test_empty_html_tag()
- new
- call setline(1, '<div></div>')
- normal 0citxxx
- call assert_equal('<div>xxx</div>', getline(1))
-
- call setline(1, '<div></div>')
- normal 0f<cityyy
- call assert_equal('<div>yyy</div>', getline(1))
-
- call setline(1, '<div></div>')
- normal 0f<vitsaaa
- call assert_equal('aaa', getline(1))
-
- " selecting a tag block in an non-empty blank line should fail
- call setline(1, ' ')
- call assert_beeps('normal $vaty')
-
- bwipe!
-endfunc
-
-" Tests for match() and matchstr()
-func Test_match()
- call assert_equal("b", matchstr("abcd", ".", 0, 2))
- call assert_equal("bc", matchstr("abcd", "..", 0, 2))
- call assert_equal("c", matchstr("abcd", ".", 2, 0))
- call assert_equal("a", matchstr("abcd", ".", 0, -1))
- call assert_equal(-1, match("abcd", ".", 0, 5))
- call assert_equal(0 , match("abcd", ".", 0, -1))
- call assert_equal(0 , match('abc', '.', 0, 1))
- call assert_equal(1 , match('abc', '.', 0, 2))
- call assert_equal(2 , match('abc', '.', 0, 3))
- call assert_equal(-1, match('abc', '.', 0, 4))
- call assert_equal(1 , match('abc', '.', 1, 1))
- call assert_equal(2 , match('abc', '.', 2, 1))
- call assert_equal(-1, match('abc', '.', 3, 1))
- call assert_equal(3 , match('abc', '$', 0, 1))
- call assert_equal(-1, match('abc', '$', 0, 2))
- call assert_equal(3 , match('abc', '$', 1, 1))
- call assert_equal(3 , match('abc', '$', 2, 1))
- call assert_equal(3 , match('abc', '$', 3, 1))
- call assert_equal(-1, match('abc', '$', 4, 1))
- call assert_equal(0 , match('abc', '\zs', 0, 1))
- call assert_equal(1 , match('abc', '\zs', 0, 2))
- call assert_equal(2 , match('abc', '\zs', 0, 3))
- call assert_equal(3 , match('abc', '\zs', 0, 4))
- call assert_equal(-1, match('abc', '\zs', 0, 5))
- call assert_equal(1 , match('abc', '\zs', 1, 1))
- call assert_equal(2 , match('abc', '\zs', 2, 1))
- call assert_equal(3 , match('abc', '\zs', 3, 1))
- call assert_equal(-1, match('abc', '\zs', 4, 1))
-endfunc
-
-" This was causing an illegal memory access
-func Test_inner_tag()
- new
- norm ixxx
- call feedkeys("v", 'xt')
- insert
-x
-x
-.
- norm it
- q!
-endfunc
-
-func Test_sentence()
- enew!
- call setline(1, 'A sentence. A sentence? A sentence!')
-
- normal yis
- call assert_equal('A sentence.', @")
- normal yas
- call assert_equal('A sentence. ', @")
-
- normal )
-
- normal yis
- call assert_equal('A sentence?', @")
- normal yas
- call assert_equal('A sentence? ', @")
-
- normal )
-
- normal yis
- call assert_equal('A sentence!', @")
- normal yas
- call assert_equal(' A sentence!', @")
-
- normal 0
- normal 2yis
- call assert_equal('A sentence. ', @")
- normal 3yis
- call assert_equal('A sentence. A sentence?', @")
- normal 2yas
- call assert_equal('A sentence. A sentence? ', @")
-
- %delete _
-endfunc
-
-func Test_sentence_with_quotes()
- enew!
- call setline(1, 'A "sentence." A sentence.')
-
- normal yis
- call assert_equal('A "sentence."', @")
- normal yas
- call assert_equal('A "sentence." ', @")
-
- normal )
-
- normal yis
- call assert_equal('A sentence.', @")
- normal yas
- call assert_equal(' A sentence.', @")
-
- %delete _
-endfunc
-
-func Test_sentence_with_cursor_on_delimiter()
- enew!
- call setline(1, "A '([sentence.])' A sentence.")
-
- normal! 15|yis
- call assert_equal("A '([sentence.])'", @")
- normal! 15|yas
- call assert_equal("A '([sentence.])' ", @")
-
- normal! 16|yis
- call assert_equal("A '([sentence.])'", @")
- normal! 16|yas
- call assert_equal("A '([sentence.])' ", @")
-
- normal! 17|yis
- call assert_equal("A '([sentence.])'", @")
- 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
-
-" Test for the paragraph (ap) text object
-func Test_paragraph()
- new
- call setline(1, ['First line.', 'Second line.', 'Third line.'])
- call cursor(2, 1)
- normal vapy
- call assert_equal("First line.\nSecond line.\nThird line.\n", @")
-
- call cursor(2, 1)
- call assert_beeps('normal vapapy')
-
- call setline(1, ['First line.', 'Second line.', ' ', ''])
- call cursor(1, 1)
- normal vapy
- call assert_equal("First line.\nSecond line.\n \n\n", @")
-
- call setline(1, ['', '', '', 'First line.', 'Second line.'])
- call cursor(2, 1)
- normal yap
- call assert_equal("\n\n\nFirst line.\nSecond line.\n", @")
- call assert_beeps('normal 3yap')
- exe "normal \<C-C>"
-
- %d
- call setline(1, [' ', ' ', ' '])
- call cursor(2, 1)
- normal Vipy
- call assert_equal(" \n \n \n", @")
- call cursor(2, 1)
- call assert_beeps("normal Vipip")
- exe "normal \<C-C>"
-
- close!
-endfunc
-
-" Tests for text object aw
-func Test_textobj_a_word()
- new
- call append(0, ['foobar,eins,foobar', 'foo,zwei,foo '])
- " diw
- norm! 1gg0diw
- call assert_equal([',eins,foobar', 'foo,zwei,foo ', ''], getline(1,'$'))
- " daw
- norm! 2ggEdaw
- call assert_equal([',eins,foobar', 'foo,zwei,', ''], getline(1, '$'))
- " daw the last word in a line
- call setline(1, ['foo bar', 'foo bar', ''])
- call cursor(1, 5)
- normal daw
- call assert_equal('foo', getline(1))
- " aw in visual mode
- call cursor(2, 5)
- normal! vawx
- call assert_equal('foo', getline(2))
- %d
- call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "])
- " diW
- norm! 2ggwd2iW
- call assert_equal(['foo eins foobar', 'foo foo ', ''], getline(1,'$'))
- " daW
- norm! 1ggd2aW
- call assert_equal(['foobar', 'foo foo ', ''], getline(1,'$'))
-
- %d
- call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "])
- " aw in visual line mode switches to characterwise mode
- norm! 2gg$Vawd
- call assert_equal(['foo eins foobar', 'foo zwei foo'], getline(1,'$'))
- norm! 1gg$Viwd
- call assert_equal(['foo eins ', 'foo zwei foo'], getline(1,'$'))
-
- " visually selecting a tab before a word with 'selection' set to 'exclusive'
- set selection=exclusive
- normal gg3lvlawy
- call assert_equal("\teins", @")
- " visually selecting a tab before a word with 'selection' set to 'inclusive'
- set selection=inclusive
- normal gg3lvlawy
- call assert_equal("\teins\t", @")
- set selection&
-
- " selecting a word with no non-space characters in a buffer fails
- %d
- call setline(1, ' ')
- call assert_beeps('normal 3lyaw')
-
- " visually selecting words backwards with no more words to select
- call setline(1, 'one two')
- call assert_beeps('normal 2lvh2aw')
- exe "normal \<C-C>"
- call assert_beeps('normal $vh3aw')
- exe "normal \<C-C>"
- call setline(1, ['', 'one two'])
- call assert_beeps('normal 2G2lvh3aw')
- exe "normal \<C-C>"
-
- " selecting words forward with no more words to select
- %d
- call setline(1, 'one a')
- call assert_beeps('normal 0y3aw')
- call setline(1, 'one two ')
- call assert_beeps('normal 0y3aw')
- call assert_beeps('normal 03ly2aw')
-
- " clean up
- bw!
-endfunc
-
-" Test for is and as text objects
-func Test_textobj_sentence()
- new
- call append(0, ['This is a test. With some sentences!', '',
- \ 'Even with a question? And one more. And no sentence here'])
- " Test for dis - does not remove trailing whitespace
- norm! 1gg0dis
- call assert_equal([' With some sentences!', '',
- \ 'Even with a question? And one more. And no sentence here', ''],
- \ getline(1,'$'))
- " Test for das - removes leading whitespace
- norm! 3ggf?ldas
- call assert_equal([' With some sentences!', '',
- \ 'Even with a question? And no sentence here', ''], getline(1,'$'))
- " when used in visual mode, is made characterwise
- norm! 3gg$Visy
- call assert_equal('v', visualmode())
- " reset visualmode()
- norm! 3ggVy
- norm! 3gg$Vasy
- call assert_equal('v', visualmode())
- " basic testing for textobjects a< and at
- %d
- call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' '])
- " a<
- norm! 1gg0da<
- call assert_equal([' ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
- norm! 1pj
- call assert_equal([' <div>', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
- " at
- norm! d2at
- call assert_equal([' '], getline(1,'$'))
- %d
- call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' '])
- " i<
- norm! 1gg0di<
- call assert_equal(['<> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
- norm! 1Pj
- call assert_equal(['<div> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
- norm! d2it
- call assert_equal(['<div></div>',' '], getline(1,'$'))
- " basic testing for a[ and i[ text object
- %d
- call setline(1, [' ', '[', 'one [two]', 'thre', ']'])
- norm! 3gg0di[
- call assert_equal([' ', '[', ']'], getline(1,'$'))
- call setline(1, [' ', '[', 'one [two]', 'thre', ']'])
- norm! 3gg0ftd2a[
- call assert_equal([' '], getline(1,'$'))
-
- " clean up
- bw!
-endfunc
-
-" Test for quote (', " and `) textobjects
-func Test_textobj_quote()
- new
-
- " Test for i" when cursor is in front of a quoted object
- call append(0, 'foo "bar"')
- norm! 1gg0di"
- call assert_equal(['foo ""', ''], getline(1,'$'))
-
- " Test for visually selecting an inner quote
- %d
- " extend visual selection from one quote to the next
- call setline(1, 'color "red" color "blue"')
- call cursor(1, 7)
- normal v4li"y
- call assert_equal('"red" color "blue', @")
-
- " try to extend visual selection from one quote to a non-existing quote
- call setline(1, 'color "red" color blue')
- call cursor(1, 7)
- call feedkeys('v4li"y', 'xt')
- call assert_equal('"red"', @")
-
- " try to extend visual selection from one quote to a next partial quote
- call setline(1, 'color "red" color "blue')
- call cursor(1, 7)
- normal v4li"y
- call assert_equal('"red" color ', @")
-
- " select a quote backwards in visual mode
- call cursor(1, 12)
- normal vhi"y
- call assert_equal('red" ', @")
- call assert_equal(8, col('.'))
-
- " select a quote backwards in visual mode from outside the quote
- call cursor(1, 17)
- normal v2hi"y
- call assert_equal('red', @")
- call assert_equal(8, col('.'))
-
- " visually selecting a quote with 'selection' set to 'exclusive'
- call setline(1, 'He said "How are you?"')
- set selection=exclusive
- normal 012lv2li"y
- call assert_equal('How are you?', @")
- set selection&
-
- " try copy a quote object with a single quote in the line
- call setline(1, "Smith's car")
- call cursor(1, 6)
- call assert_beeps("normal yi'")
- call assert_beeps("normal 2lyi'")
-
- " selecting space before and after a quoted string
- call setline(1, "some 'special' string")
- normal 0ya'
- call assert_equal("'special' ", @")
- call setline(1, "some 'special'string")
- normal 0ya'
- call assert_equal(" 'special'", @")
-
- " quoted string with odd or even number of backslashes.
- call setline(1, 'char *s = "foo\"bar"')
- normal $hhyi"
- call assert_equal('foo\"bar', @")
- call setline(1, 'char *s = "foo\\"bar"')
- normal $hhyi"
- call assert_equal('bar', @")
- call setline(1, 'char *s = "foo\\\"bar"')
- normal $hhyi"
- call assert_equal('foo\\\"bar', @")
- call setline(1, 'char *s = "foo\\\\"bar"')
- normal $hhyi"
- call assert_equal('bar', @")
-
- close!
-endfunc
-
-" Test for i(, i<, etc. when cursor is in front of a block
-func Test_textobj_find_paren_forward()
- new
-
- " i< and a> when cursor is in front of a block
- call setline(1, '#include <foo.h>')
- normal 0yi<
- call assert_equal('foo.h', @")
- normal 0ya>
- call assert_equal('<foo.h>', @")
-
- " 2i(, 3i( in front of a block enters second/third nested '('
- call setline(1, 'foo (bar (baz (quux)))')
- normal 0yi)
- call assert_equal('bar (baz (quux))', @")
- normal 02yi)
- call assert_equal('baz (quux)', @")
- normal 03yi)
- call assert_equal('quux', @")
-
- " 3i( in front of a block doesn't enter third but un-nested '('
- call setline(1, 'foo (bar (baz) (quux))')
- normal 03di)
- call assert_equal('foo (bar (baz) (quux))', getline(1))
- normal 02di)
- call assert_equal('foo (bar () (quux))', getline(1))
- normal 0di)
- call assert_equal('foo ()', getline(1))
-
- close!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
deleted file mode 100644
index f94ee6c9f3..0000000000
--- a/src/nvim/testdir/test_timers.vim
+++ /dev/null
@@ -1,458 +0,0 @@
-" Test for timers
-
-source check.vim
-CheckFeature timers
-
-source screendump.vim
-source shared.vim
-source term_util.vim
-source load.vim
-
-func SetUp()
- call timer_stopall()
-endfunc
-
-func MyHandler(timer)
- let g:val += 1
-endfunc
-
-func MyHandlerWithLists(lists, timer)
- let x = string(a:lists)
-endfunc
-
-func Test_timer_oneshot()
- let g:val = 0
- let timer = timer_start(50, 'MyHandler')
- let slept = WaitFor('g:val == 1')
- call assert_equal(1, g:val)
- if has('reltime')
- call assert_inrange(40, LoadAdjust(120), slept)
- else
- call assert_inrange(20, 120, slept)
- endif
-endfunc
-
-func Test_timer_repeat_three()
- let g:val = 0
- let timer = timer_start(50, 'MyHandler', {'repeat': 3})
- let slept = WaitFor('g:val == 3')
- call assert_equal(3, g:val)
- if has('reltime')
- call assert_inrange(120, LoadAdjust(250), slept)
- else
- call assert_inrange(80, 200, slept)
- endif
-endfunc
-
-func Test_timer_repeat_many()
- let g:val = 0
- let timer = timer_start(50, 'MyHandler', {'repeat': -1})
- sleep 200m
- call timer_stop(timer)
- call assert_inrange((has('mac') ? 1 : 2), LoadAdjust(5), g:val)
-endfunc
-
-func Test_timer_with_partial_callback()
- let g:val = 0
- let meow = {'one': 1}
- function meow.bite(...)
- let g:val += self.one
- endfunction
-
- call timer_start(50, meow.bite)
- let slept = WaitFor('g:val == 1')
- call assert_equal(1, g:val)
- if has('reltime')
- call assert_inrange(40, LoadAdjust(130), slept)
- else
- call assert_inrange(20, 100, slept)
- endif
-endfunc
-
-func Test_timer_retain_partial()
- call timer_start(50, function('MyHandlerWithLists', [['a']]))
- call test_garbagecollect_now()
- sleep 100m
-endfunc
-
-func Test_timer_info()
- let id = timer_start(1000, 'MyHandler')
- let info = id->timer_info()
- call assert_equal(id, info[0]['id'])
- call assert_equal(1000, info[0]['time'])
- call assert_equal("function('MyHandler')", string(info[0]['callback']))
-
- let found = 0
- for info in timer_info()
- if info['id'] == id
- let found += 1
- endif
- endfor
- call assert_equal(1, found)
-
- call timer_stop(id)
- call assert_equal([], timer_info(id))
-
- call assert_fails('call timer_info("abc")', 'E39:')
-endfunc
-
-func Test_timer_stopall()
- let id1 = timer_start(1000, 'MyHandler')
- let id2 = timer_start(2000, 'MyHandler')
- let info = timer_info()
- call assert_equal(2, len(info))
-
- call timer_stopall()
- let info = timer_info()
- call assert_equal(0, len(info))
-endfunc
-
-func Test_timer_paused()
- let g:val = 0
-
- let id = timer_start(50, 'MyHandler')
- let info = timer_info(id)
- call assert_equal(0, info[0]['paused'])
-
- eval id->timer_pause(1)
- let info = timer_info(id)
- call assert_equal(1, info[0]['paused'])
- sleep 200m
- call assert_equal(0, g:val)
-
- call timer_pause(id, 0)
- let info = timer_info(id)
- call assert_equal(0, info[0]['paused'])
-
- let slept = WaitFor('g:val == 1')
- call assert_equal(1, g:val)
- if has('reltime')
- call assert_inrange(0, LoadAdjust(140), slept)
- else
- call assert_inrange(0, 10, slept)
- endif
-
- call assert_fails('call timer_pause("abc", 1)', 'E39:')
-endfunc
-
-func StopMyself(timer)
- let g:called += 1
- if g:called == 2
- call timer_stop(a:timer)
- endif
-endfunc
-
-func Test_timer_delete_myself()
- let g:called = 0
- let t = timer_start(10, 'StopMyself', {'repeat': -1})
- call WaitForAssert({-> assert_equal(2, g:called)})
- call assert_equal(2, g:called)
- call assert_equal([], timer_info(t))
-endfunc
-
-func StopTimer1(timer)
- let g:timer2 = 10->timer_start('StopTimer2')
- " avoid maxfuncdepth error
- call timer_pause(g:timer1, 1)
- sleep 20m
-endfunc
-
-func StopTimer2(timer)
- call timer_stop(g:timer1)
-endfunc
-
-func Test_timer_stop_in_callback()
- call assert_equal(0, len(timer_info()))
- let g:timer1 = timer_start(10, 'StopTimer1')
- let slept = 0
- for i in range(10)
- if len(timer_info()) == 0
- break
- endif
- sleep 10m
- let slept += 10
- endfor
- " This should take only 30 msec, but on Mac it's often longer
- call assert_inrange(0, 50, slept)
-endfunc
-
-func StopTimerAll(timer)
- call timer_stopall()
-endfunc
-
-func Test_timer_stop_all_in_callback()
- call assert_equal(0, len(timer_info()))
- call timer_start(10, 'StopTimerAll')
- call assert_equal(1, len(timer_info()))
- let slept = 0
- for i in range(10)
- if len(timer_info()) == 0
- break
- endif
- sleep 10m
- let slept += 10
- endfor
- call assert_inrange(0, 30, slept)
-endfunc
-
-func FeedkeysCb(timer)
- call feedkeys("hello\<CR>", 'nt')
-endfunc
-
-func InputCb(timer)
- call timer_start(10, 'FeedkeysCb')
- let g:val = input('?')
- call Resume()
-endfunc
-
-func Test_timer_input_in_timer()
- let g:val = ''
- call timer_start(10, 'InputCb')
- call Standby(1000)
- call assert_equal('hello', g:val)
-endfunc
-
-func FuncWithError(timer)
- let g:call_count += 1
- if g:call_count == 4
- return
- endif
- doesnotexist
-endfunc
-
-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 WaitForAssert({-> assert_equal(3, g:call_count)})
- sleep 50m
- call assert_equal(3, g:call_count)
-
- call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E475:')
- call assert_fails('call timer_start(100, [])', 'E921:')
- call assert_fails('call timer_stop("abc")', 'E39:')
-endfunc
-
-func FuncWithCaughtError(timer)
- let g:call_count += 1
- try
- doesnotexist
- catch
- " nop
- endtry
-endfunc
-
-func Test_timer_catch_error()
- let g:call_count = 0
- let timer = timer_start(10, 'FuncWithCaughtError', {'repeat': 4})
- " Timer will not be stopped.
- call WaitForAssert({-> assert_equal(4, g:call_count)})
- sleep 50m
- call assert_equal(4, g:call_count)
-endfunc
-
-func FeedAndPeek(timer)
- " call test_feedinput('a')
- call nvim_input('a')
- call getchar(1)
-endfunc
-
-func Interrupt(timer)
- " eval "\<C-C>"->test_feedinput()
- call nvim_input("\<C-C>")
-endfunc
-
-func Test_timer_peek_and_get_char()
- if !has('unix') && !has('gui_running')
- throw 'Skipped: cannot feed low-level input'
- endif
-
- call timer_start(0, 'FeedAndPeek')
- let intr = timer_start(100, 'Interrupt')
- let c = getchar()
- call assert_equal(char2nr('a'), c)
- eval intr->timer_stop()
-endfunc
-
-func Test_timer_getchar_zero()
- if has('win32') && !has('gui_running')
- throw 'Skipped: cannot feed low-level input'
- endif
- CheckFunction reltimefloat
-
- " 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_timer_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_timer_restore_count()
- CheckRunVimInTerminal
- " 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_timer_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(":\<CR>", 't')
- endfunc
- call timer_start(300, 'FeedChar')
- call timer_start(100, 'CauseAnError')
- let x = getchar() " wait for error in timer
- let x = getchar(0) " read any remaining chars
- let x = getchar(0)
-
- set ut&
- call test_override('no_wait_return', 1)
- delfunc CauseAnError
- delfunc FeedChar
-endfunc
-
-func Test_timer_error_in_timer_callback()
- if !has('terminal') || (has('win32') && has('gui_running'))
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
-
- let lines =<< trim [CODE]
- func Func(timer)
- " fail to create list
- let x = [
- endfunc
- set updatetime=50
- call timer_start(1, 'Func')
- [CODE]
- call writefile(lines, 'Xtest.vim')
-
- let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
- let job = term_getjob(buf)
- call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
-
- " GC must not run during timer callback, which can make Vim crash.
- call term_wait(buf, 100)
- call term_sendkeys(buf, "\<CR>")
- call term_wait(buf, 100)
- call assert_equal('run', job_status(job))
-
- call term_sendkeys(buf, ":qall!\<CR>")
- call WaitFor({-> job_status(job) ==# 'dead'})
- if has('unix')
- call assert_equal('', job_info(job).termsig)
- endif
-
- call delete('Xtest.vim')
- exe buf .. 'bwipe!'
-endfunc
-
-" Test for garbage collection when a timer is still running
-func Test_timer_garbage_collect()
- let timer = timer_start(1000, function('MyHandler'), {'repeat' : 10})
- call test_garbagecollect_now()
- let l = timer_info(timer)
- call assert_equal(function('MyHandler'), l[0].callback)
- call timer_stop(timer)
-endfunc
-
-func Test_timer_invalid_callback()
- call assert_fails('call timer_start(0, "0")', 'E921')
-endfunc
-
-func Test_timer_changing_function_list()
- CheckRunVimInTerminal
-
- " Create a large number of functions. Should get the "more" prompt.
- " The typing "G" triggers the timer, which changes the function table.
- let lines =<< trim END
- for func in map(range(1,99), "'Func' .. v:val")
- exe "func " .. func .. "()"
- endfunc
- endfor
- au CmdlineLeave : call timer_start(0, {-> 0})
- END
- call writefile(lines, 'XTest_timerchange')
- let buf = RunVimInTerminal('-S XTest_timerchange', #{rows: 10})
- call term_sendkeys(buf, ":fu\<CR>")
- call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 10))})
- call term_sendkeys(buf, "G")
- call WaitForAssert({-> assert_match('E454', term_getline(buf, 9))})
- call term_sendkeys(buf, "\<Esc>")
-
- call StopVimInTerminal(buf)
- call delete('XTest_timerchange')
-endfunc
-
-func Test_timer_using_win_execute_undo_sync()
- let bufnr1 = bufnr()
- new
- let g:bufnr2 = bufnr()
- let g:winid = win_getid()
- exe "buffer " .. bufnr1
- wincmd w
- call setline(1, ['test'])
- autocmd InsertEnter * call timer_start(100, { -> win_execute(g:winid, 'buffer ' .. g:bufnr2) })
- call timer_start(200, { -> feedkeys("\<CR>bbbb\<Esc>") })
- call feedkeys("Oaaaa", 'x!t')
- " will hang here until the second timer fires
- call assert_equal(['aaaa', 'bbbb', 'test'], getline(1, '$'))
- undo
- call assert_equal(['test'], getline(1, '$'))
-
- bwipe!
- bwipe!
- unlet g:winid
- unlet g:bufnr2
- au! InsertEnter
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_true_false.vim b/src/nvim/testdir/test_true_false.vim
deleted file mode 100644
index f3c7fff4a6..0000000000
--- a/src/nvim/testdir/test_true_false.vim
+++ /dev/null
@@ -1,155 +0,0 @@
-" Test behavior of boolean-like values.
-
-source check.vim
-
-" Test what is explained at ":help TRUE" and ":help FALSE".
-func Test_if()
- if v:false
- call assert_true(false, 'v:false is false')
- endif
- if 0
- call assert_true(false, 'zero is false')
- endif
- if "0"
- call assert_true(false, 'zero string is false')
- endif
- if "foo"
- call assert_true(false, 'foo is false')
- endif
- if " "
- call assert_true(false, 'space is false')
- endif
- if empty("foo")
- call assert_true(false, 'foo is not empty')
- endif
-
- if v:true
- else
- call assert_true(false, 'v:true is true')
- endif
- if 1
- else
- call assert_true(false, 'one is true')
- endif
- if "1"
- else
- call assert_true(false, 'one string is true')
- endif
- if "1foo"
- else
- call assert_true(false, 'one in string is true')
- endif
-
- call assert_fails('if [1]', 'E745')
- call assert_fails('if {1: 1}', 'E728')
- call assert_fails('if function("string")', 'E703')
- if has('float')
- call assert_fails('if 1.3")', 'E805')
- endif
-endfunc
-
-function Try_arg_true_false(expr, false_val, true_val)
- for v in ['v:false', '0', '"0"', '"foo"', '" "']
- let r = eval(substitute(a:expr, '%v%', v, ''))
- call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . string(a:false_val) . ' but ' . string(r))
- endfor
- for v in ['v:true', '1', '"1"', '"1foo"']
- let r = eval(substitute(a:expr, '%v%', v, ''))
- call assert_equal(a:true_val, r, 'result for ' . v . ' is not ' . string(a:true_val) . ' but ' . string(r))
- endfor
-endfunc
-
-" Test using TRUE or FALSE values for an argument.
-func Test_true_false_arg()
- call Try_arg_true_false('count(["a", "A"], "a", %v%)', 1, 2)
-
- set wildignore=*.swp
- call Try_arg_true_false('expand("foo.swp", %v%)', "", "foo.swp")
- call Try_arg_true_false('expand("foo.vim", 0, %v%)', "foo.vim", ["foo.vim"])
-
- call setreg('a', ['x', 'y'])
- call Try_arg_true_false('getreg("a", 1, %v%)', "x\ny\n", ['x', 'y'])
-
- set wildignore=*.vim
- call Try_arg_true_false('glob("runtest.vim", %v%)', "", "runtest.vim")
- set wildignore=*.swp
- call Try_arg_true_false('glob("runtest.vim", 0, %v%)', "runtest.vim", ["runtest.vim"])
- if has('unix')
- silent !ln -s doesntexit Xlink
- call Try_arg_true_false('glob("Xlink", 0, 0, %v%)', "", "Xlink")
- silent !rm Xlink
- endif
-
- set wildignore=*.vim
- call Try_arg_true_false('globpath(".", "runtest.vim", %v%)', "", "./runtest.vim")
- set wildignore=*.swp
- call Try_arg_true_false('globpath(".", "runtest.vim", 0, %v%)', "./runtest.vim", ["./runtest.vim"])
- if has('unix')
- silent !ln -s doesntexit Xlink
- call Try_arg_true_false('globpath(".", "Xlink", 0, 0, %v%)', "", "./Xlink")
- silent !rm Xlink
- endif
-
- abbr asdf asdff
- call Try_arg_true_false('hasmapto("asdff", "i", %v%)', 0, 1)
-
- call Try_arg_true_false('index(["a", "A"], "A", 0, %v%)', 1, 0)
-
- function FilterMapArg(d)
- if type(a:d) == type({})
- return filter(a:d, 'v:key == "rhs"')
- endif
- return a:d
- endfunction
- call Try_arg_true_false('maparg("asdf", "i", %v%)', "", "asdff")
- call Try_arg_true_false('FilterMapArg(maparg("asdf", "i", 1, %v%))', "asdff", {'rhs': 'asdff'})
-
- call Try_arg_true_false('"asdf"->hasmapto("i", %v%)', 0, 1)
-
- new colored
- call setline(1, '<here>')
- syn match brackets "<.*>"
- syn match here "here" transparent
- let brackets_id = synID(1, 1, 0)
- let here_id = synID(1, 3, 0)
- call Try_arg_true_false('synID(1, 3, %v%)', here_id, brackets_id)
- bwipe!
-endfunc
-
-function Try_arg_non_zero(expr, false_val, true_val)
- CheckFeature float
- for v in ['v:false', '0', '[1]', '{2:3}', '3.4']
- let r = eval(substitute(a:expr, '%v%', v, ''))
- call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . a:false_val . ' but ' . r)
- endfor
- for v in ['v:true', '1', '" "', '"0"']
- let r = eval(substitute(a:expr, '%v%', v, ''))
- call assert_equal(a:true_val, r, 'result for ' . v . ' is not ' . a:true_val . ' but ' . r)
- endfor
-endfunc
-
-
-" Test using non-zero-arg for an argument.
-func Test_non_zero_arg()
- " call test_settime(93784)
- " call Try_arg_non_zero("mode(%v%)", 'x', 'x!')
- " call test_settime(0)
-
- call Try_arg_non_zero("shellescape('foo%', %v%)", "'foo%'", "'foo\\%'")
-
- " visualmode() needs to be called twice to check
- for v in [v:false, 0, [1], {2:3}, 3.4]
- normal vv
- let r = visualmode(v)
- call assert_equal('v', r, 'result for ' . string(v) . ' is not "v" but ' . r)
- let r = visualmode(v)
- call assert_equal('v', r, 'result for ' . string(v) . ' is not "v" but ' . r)
- endfor
- for v in [v:true, 1, " ", "0"]
- normal vv
- let r = visualmode(v)
- call assert_equal('v', r, 'result for ' . v . ' is not "v" but ' . r)
- let r = visualmode(v)
- call assert_equal('', r, 'result for ' . v . ' is not "" but ' . r)
- endfor
-endfunc
diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim
deleted file mode 100644
index ef20e03126..0000000000
--- a/src/nvim/testdir/test_trycatch.vim
+++ /dev/null
@@ -1,2333 +0,0 @@
-" Test try-catch-finally exception handling
-" Most of this was formerly in test49.
-
-source check.vim
-source shared.vim
-source vim9.vim
-
-"-------------------------------------------------------------------------------
-" Test environment {{{1
-"-------------------------------------------------------------------------------
-
-com! XpathINIT let g:Xpath = ''
-com! -nargs=1 -bar Xpath let g:Xpath = g:Xpath . <args>
-
-" Test 25: Executing :finally clauses on normal control flow {{{1
-"
-" Control flow in a :try conditional should always fall through to its
-" :finally clause. A :finally clause of a :try conditional inside an
-" inactive conditional should never be executed.
-"-------------------------------------------------------------------------------
-
-func T25_F()
- let loops = 3
- while loops > 0
- Xpath 'a' . loops
- if loops >= 2
- try
- Xpath 'b' . loops
- if loops == 2
- try
- Xpath 'c' . loops
- finally
- Xpath 'd' . loops
- endtry
- endif
- finally
- Xpath 'e' . loops
- if loops == 2
- try
- Xpath 'f' . loops
- finally
- Xpath 'g' . loops
- endtry
- endif
- endtry
- endif
- Xpath 'h' . loops
- let loops = loops - 1
- endwhile
- Xpath 'i'
-endfunc
-
-func T25_G()
- if 1
- try
- Xpath 'A'
- call T25_F()
- Xpath 'B'
- finally
- Xpath 'C'
- endtry
- else
- try
- Xpath 'D'
- finally
- Xpath 'E'
- endtry
- endif
-endfunc
-
-func Test_finally()
- XpathINIT
- call T25_G()
- call assert_equal('Aa3b3e3h3a2b2c2d2e2f2g2h2a1h1iBC', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 26: Executing :finally clauses after :continue or :break {{{1
-"
-" For a :continue or :break dynamically enclosed in a :try/:endtry
-" region inside the next surrounding :while/:endwhile, if the
-" :continue/:break is before the :finally, the :finally clause is
-" executed first. If the :continue/:break is after the :finally, the
-" :finally clause is broken (like an :if/:endif region).
-"-------------------------------------------------------------------------------
-
-func T26_F()
- try
- let loops = 3
- while loops > 0
- try
- try
- if loops == 2
- Xpath 'a' . loops
- let loops = loops - 1
- continue
- elseif loops == 1
- Xpath 'b' . loops
- break
- finish
- endif
- Xpath 'c' . loops
- endtry
- finally
- Xpath 'd' . loops
- endtry
- Xpath 'e' . loops
- let loops = loops - 1
- endwhile
- Xpath 'f'
- finally
- Xpath 'g'
- let loops = 3
- while loops > 0
- try
- finally
- try
- if loops == 2
- Xpath 'h' . loops
- let loops = loops - 1
- continue
- elseif loops == 1
- Xpath 'i' . loops
- break
- finish
- endif
- endtry
- Xpath 'j' . loops
- endtry
- Xpath 'k' . loops
- let loops = loops - 1
- endwhile
- Xpath 'l'
- endtry
- Xpath 'm'
-endfunc
-
-func Test_finally_after_continue()
- XpathINIT
- call T26_F()
- call assert_equal('c3d3e3a2d1b1d1fgj3k3h2i1lm', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 32: Remembering the :return value on :finally {{{1
-"
-" If a :finally clause is executed due to a :return specifying
-" a value, this is the value visible to the caller if not overwritten
-" by a new :return in the :finally clause. A :return without a value
-" in the :finally clause overwrites with value 0.
-"-------------------------------------------------------------------------------
-
-func T32_F()
- try
- Xpath 'a'
- try
- Xpath 'b'
- return "ABCD"
- Xpath 'c'
- finally
- Xpath 'd'
- endtry
- Xpath 'e'
- finally
- Xpath 'f'
- endtry
- Xpath 'g'
-endfunc
-
-func T32_G()
- try
- Xpath 'h'
- return 8
- Xpath 'i'
- finally
- Xpath 'j'
- return 16 + strlen(T32_F())
- Xpath 'k'
- endtry
- Xpath 'l'
-endfunc
-
-func T32_H()
- try
- Xpath 'm'
- return 32
- Xpath 'n'
- finally
- Xpath 'o'
- return
- Xpath 'p'
- endtry
- Xpath 'q'
-endfunc
-
-func T32_I()
- try
- Xpath 'r'
- finally
- Xpath 's'
- return T32_G() + T32_H() + 64
- Xpath 't'
- endtry
- Xpath 'u'
-endfunc
-
-func Test_finally_return()
- XpathINIT
- call assert_equal(84, T32_I())
- call assert_equal('rshjabdfmo', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 33: :return under :execute or user command and :finally {{{1
-"
-" A :return command may be executed under an ":execute" or from
-" a user command. Executing of :finally clauses and passing through
-" the return code works also then.
-"-------------------------------------------------------------------------------
-
-func T33_F()
- try
- RETURN 10
- Xpath 'a'
- finally
- Xpath 'b'
- endtry
- Xpath 'c'
-endfunc
-
-func T33_G()
- try
- RETURN 20
- Xpath 'd'
- finally
- Xpath 'e'
- RETURN 30
- Xpath 'f'
- endtry
- Xpath 'g'
-endfunc
-
-func T33_H()
- try
- execute "try | return 40 | finally | return 50 | endtry"
- Xpath 'h'
- finally
- Xpath 'i'
- endtry
- Xpath 'j'
-endfunc
-
-func T33_I()
- try
- execute "try | return 60 | finally | return 70 | endtry"
- Xpath 'k'
- finally
- Xpath 'l'
- execute "try | return 80 | finally | return 90 | endtry"
- Xpath 'm'
- endtry
- Xpath 'n'
-endfunc
-
-func T33_J()
- try
- RETURN 100
- Xpath 'o'
- finally
- Xpath 'p'
- return
- Xpath 'q'
- endtry
- Xpath 'r'
-endfunc
-
-func T33_K()
- try
- execute "try | return 110 | finally | return 120 | endtry"
- Xpath 's'
- finally
- Xpath 't'
- execute "try | return 130 | finally | return | endtry"
- Xpath 'u'
- endtry
- Xpath 'v'
-endfunc
-
-func T33_L()
- try
- return
- Xpath 'w'
- finally
- Xpath 'x'
- RETURN 140
- Xpath 'y'
- endtry
- Xpath 'z'
-endfunc
-
-func T33_M()
- try
- return
- Xpath 'A'
- finally
- Xpath 'B'
- execute "try | return 150 | finally | return 160 | endtry"
- Xpath 'C'
- endtry
- Xpath 'D'
-endfunc
-
-func T33_N()
- RETURN 170
-endfunc
-
-func T33_O()
- execute "try | return 180 | finally | return 190 | endtry"
-endfunc
-
-func Test_finally_cmd_return()
- command! -nargs=? RETURN
- \ try | return <args> | finally | return <args> * 2 | endtry
- XpathINIT
- call assert_equal(20, T33_F())
- call assert_equal(60, T33_G())
- call assert_equal(50, T33_H())
- call assert_equal(90, T33_I())
- call assert_equal(0, T33_J())
- call assert_equal(0, T33_K())
- call assert_equal(280, T33_L())
- call assert_equal(160, T33_M())
- call assert_equal(340, T33_N())
- call assert_equal(190, T33_O())
- call assert_equal('beilptxB', g:Xpath)
- delcommand RETURN
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 41: Skipped :throw finding next command {{{1
-"
-" A :throw in an inactive conditional must not hide a following
-" command.
-"-------------------------------------------------------------------------------
-
-func T41_F()
- Xpath 'a'
- if 0 | throw 'never' | endif | Xpath 'b'
- Xpath 'c'
-endfunc
-
-func T41_G()
- Xpath 'd'
- while 0 | throw 'never' | endwhile | Xpath 'e'
- Xpath 'f'
-endfunc
-
-func T41_H()
- Xpath 'g'
- if 0 | try | throw 'never' | endtry | endif | Xpath 'h'
- Xpath 'i'
-endfunc
-
-func Test_throw_inactive_cond()
- XpathINIT
- try
- Xpath 'j'
- call T41_F()
- Xpath 'k'
- catch /.*/
- Xpath 'l'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- Xpath 'm'
- call T41_G()
- Xpath 'n'
- catch /.*/
- Xpath 'o'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- Xpath 'p'
- call T41_H()
- Xpath 'q'
- catch /.*/
- Xpath 'r'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- call assert_equal('jabckmdefnpghiq', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 42: Catching number and string exceptions {{{1
-"
-" When a number is thrown, it is converted to a string exception.
-" Numbers and strings may be caught by specifying a regular exception
-" as argument to the :catch command.
-"-------------------------------------------------------------------------------
-
-
-func T42_F()
- try
-
- try
- Xpath 'a'
- throw 4711
- Xpath 'b'
- catch /4711/
- Xpath 'c'
- endtry
-
- try
- Xpath 'd'
- throw 4711
- Xpath 'e'
- catch /^4711$/
- Xpath 'f'
- endtry
-
- try
- Xpath 'g'
- throw 4711
- Xpath 'h'
- catch /\d/
- Xpath 'i'
- endtry
-
- try
- Xpath 'j'
- throw 4711
- Xpath 'k'
- catch /^\d\+$/
- Xpath 'l'
- endtry
-
- try
- Xpath 'm'
- throw "arrgh"
- Xpath 'n'
- catch /arrgh/
- Xpath 'o'
- endtry
-
- try
- Xpath 'p'
- throw "arrgh"
- Xpath 'q'
- catch /^arrgh$/
- Xpath 'r'
- endtry
-
- try
- Xpath 's'
- throw "arrgh"
- Xpath 't'
- catch /\l/
- Xpath 'u'
- endtry
-
- try
- Xpath 'v'
- throw "arrgh"
- Xpath 'w'
- catch /^\l\+$/
- Xpath 'x'
- endtry
-
- try
- try
- Xpath 'y'
- throw "ARRGH"
- Xpath 'z'
- catch /^arrgh$/
- Xpath 'A'
- endtry
- catch /^\carrgh$/
- Xpath 'B'
- endtry
-
- try
- Xpath 'C'
- throw ""
- Xpath 'D'
- catch /^$/
- Xpath 'E'
- endtry
-
- catch /.*/
- Xpath 'F'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-endfunc
-
-func Test_catch_number_string()
- XpathINIT
- call T42_F()
- call assert_equal('acdfgijlmoprsuvxyBCE', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 43: Selecting the correct :catch clause {{{1
-"
-" When an exception is thrown and there are multiple :catch clauses,
-" the first matching one is taken.
-"-------------------------------------------------------------------------------
-
-func T43_F()
- let loops = 3
- while loops > 0
- try
- if loops == 3
- Xpath 'a' . loops
- throw "a"
- Xpath 'b' . loops
- elseif loops == 2
- Xpath 'c' . loops
- throw "ab"
- Xpath 'd' . loops
- elseif loops == 1
- Xpath 'e' . loops
- throw "abc"
- Xpath 'f' . loops
- endif
- catch /abc/
- Xpath 'g' . loops
- catch /ab/
- Xpath 'h' . loops
- catch /.*/
- Xpath 'i' . loops
- catch /a/
- Xpath 'j' . loops
- endtry
-
- let loops = loops - 1
- endwhile
- Xpath 'k'
-endfunc
-
-func Test_multi_catch()
- XpathINIT
- call T43_F()
- call assert_equal('a3i3c2h2e1g1k', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 44: Missing or empty :catch patterns {{{1
-"
-" A missing or empty :catch pattern means the same as /.*/, that is,
-" catches everything. To catch only empty exceptions, /^$/ must be
-" used. A :catch with missing, empty, or /.*/ argument also works
-" when followed by another command separated by a bar on the same
-" line. :catch patterns cannot be specified between ||. But other
-" pattern separators can be used instead of //.
-"-------------------------------------------------------------------------------
-
-func T44_F()
- try
- try
- Xpath 'a'
- throw ""
- catch /^$/
- Xpath 'b'
- endtry
-
- try
- Xpath 'c'
- throw ""
- catch /.*/
- Xpath 'd'
- endtry
-
- try
- Xpath 'e'
- throw ""
- catch //
- Xpath 'f'
- endtry
-
- try
- Xpath 'g'
- throw ""
- catch
- Xpath 'h'
- endtry
-
- try
- Xpath 'i'
- throw "oops"
- catch /^$/
- Xpath 'j'
- catch /.*/
- Xpath 'k'
- endtry
-
- try
- Xpath 'l'
- throw "arrgh"
- catch /^$/
- Xpath 'm'
- catch //
- Xpath 'n'
- endtry
-
- try
- Xpath 'o'
- throw "brrr"
- catch /^$/
- Xpath 'p'
- catch
- Xpath 'q'
- endtry
-
- try | Xpath 'r' | throw "x" | catch /.*/ | Xpath 's' | endtry
-
- try | Xpath 't' | throw "y" | catch // | Xpath 'u' | endtry
-
- while 1
- try
- let caught = 0
- let v:errmsg = ""
- " Extra try level: if ":catch" without arguments below raises
- " a syntax error because it misinterprets the "Xpath" as a pattern,
- " let it be caught by the ":catch /.*/" below.
- try
- try | Xpath 'v' | throw "z" | catch | Xpath 'w' | :
- endtry
- endtry
- catch /.*/
- let caught = 1
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- finally
- if $VIMNOERRTHROW && v:errmsg != ""
- call assert_report(v:errmsg)
- endif
- if caught || $VIMNOERRTHROW && v:errmsg != ""
- Xpath 'x'
- endif
- break " discard error for $VIMNOERRTHROW
- endtry
- endwhile
-
- let cologne = 4711
- try
- try
- Xpath 'y'
- throw "throw cologne"
- " Next lines catches all and throws 4711:
- catch |throw cologne|
- Xpath 'z'
- endtry
- catch /4711/
- Xpath 'A'
- endtry
-
- try
- Xpath 'B'
- throw "plus"
- catch +plus+
- Xpath 'C'
- endtry
-
- Xpath 'D'
- catch /.*/
- Xpath 'E'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-endfunc
-
-func Test_empty_catch()
- XpathINIT
- call T44_F()
- call assert_equal('abcdefghiklnoqrstuvwyABCD', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 45: Catching exceptions from nested :try blocks {{{1
-"
-" When :try blocks are nested, an exception is caught by the innermost
-" try conditional that has a matching :catch clause.
-"-------------------------------------------------------------------------------
-
-func T45_F()
- let loops = 3
- while loops > 0
- try
- try
- try
- try
- if loops == 3
- Xpath 'a' . loops
- throw "a"
- Xpath 'b' . loops
- elseif loops == 2
- Xpath 'c' . loops
- throw "ab"
- Xpath 'd' . loops
- elseif loops == 1
- Xpath 'e' . loops
- throw "abc"
- Xpath 'f' . loops
- endif
- catch /abc/
- Xpath 'g' . loops
- endtry
- catch /ab/
- Xpath 'h' . loops
- endtry
- catch /.*/
- Xpath 'i' . loops
- endtry
- catch /a/
- Xpath 'j' . loops
- endtry
-
- let loops = loops - 1
- endwhile
- Xpath 'k'
-endfunc
-
-func Test_catch_from_nested_try()
- XpathINIT
- call T45_F()
- call assert_equal('a3i3c2h2e1g1k', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 46: Executing :finally after a :throw in nested :try {{{1
-"
-" When an exception is thrown from within nested :try blocks, the
-" :finally clauses of the non-catching try conditionals should be
-" executed before the matching :catch of the next surrounding :try
-" gets the control. If this also has a :finally clause, it is
-" executed afterwards.
-"-------------------------------------------------------------------------------
-
-func T46_F()
- let sum = 0
-
- try
- Xpath 'a'
- try
- Xpath 'b'
- try
- Xpath 'c'
- try
- Xpath 'd'
- throw "ABC"
- Xpath 'e'
- catch /xyz/
- Xpath 'f'
- finally
- Xpath 'g'
- if sum != 0
- Xpath 'h'
- endif
- let sum = sum + 1
- endtry
- Xpath 'i'
- catch /123/
- Xpath 'j'
- catch /321/
- Xpath 'k'
- finally
- Xpath 'l'
- if sum != 1
- Xpath 'm'
- endif
- let sum = sum + 2
- endtry
- Xpath 'n'
- finally
- Xpath 'o'
- if sum != 3
- Xpath 'p'
- endif
- let sum = sum + 4
- endtry
- Xpath 'q'
- catch /ABC/
- Xpath 'r'
- if sum != 7
- Xpath 's'
- endif
- let sum = sum + 8
- finally
- Xpath 't'
- if sum != 15
- Xpath 'u'
- endif
- let sum = sum + 16
- endtry
- Xpath 'v'
- if sum != 31
- Xpath 'w'
- endif
-endfunc
-
-func Test_finally_after_throw()
- XpathINIT
- call T46_F()
- call assert_equal('abcdglortv', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 47: Throwing exceptions from a :catch clause {{{1
-"
-" When an exception is thrown from a :catch clause, it should not be
-" caught by a :catch of the same :try conditional. After executing
-" the :finally clause (if present), surrounding try conditionals
-" should be checked for a matching :catch.
-"-------------------------------------------------------------------------------
-
-func T47_F()
- Xpath 'a'
- try
- Xpath 'b'
- try
- Xpath 'c'
- try
- Xpath 'd'
- throw "x1"
- Xpath 'e'
- catch /x1/
- Xpath 'f'
- try
- Xpath 'g'
- throw "x2"
- Xpath 'h'
- catch /x1/
- Xpath 'i'
- catch /x2/
- Xpath 'j'
- try
- Xpath 'k'
- throw "x3"
- Xpath 'l'
- catch /x1/
- Xpath 'm'
- catch /x2/
- Xpath 'n'
- finally
- Xpath 'o'
- endtry
- Xpath 'p'
- catch /x3/
- Xpath 'q'
- endtry
- Xpath 'r'
- catch /x1/
- Xpath 's'
- catch /x2/
- Xpath 't'
- catch /x3/
- Xpath 'u'
- finally
- Xpath 'v'
- endtry
- Xpath 'w'
- catch /x1/
- Xpath 'x'
- catch /x2/
- Xpath 'y'
- catch /x3/
- Xpath 'z'
- endtry
- Xpath 'A'
- catch /.*/
- Xpath 'B'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
- Xpath 'C'
-endfunc
-
-func Test_throw_from_catch()
- XpathINIT
- call T47_F()
- call assert_equal('abcdfgjkovzAC', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 48: Throwing exceptions from a :finally clause {{{1
-"
-" When an exception is thrown from a :finally clause, it should not be
-" caught by a :catch of the same :try conditional. Surrounding try
-" conditionals should be checked for a matching :catch. A previously
-" thrown exception is discarded.
-"-------------------------------------------------------------------------------
-
-func T48_F()
- try
-
- try
- try
- Xpath 'a'
- catch /x1/
- Xpath 'b'
- finally
- Xpath 'c'
- throw "x1"
- Xpath 'd'
- endtry
- Xpath 'e'
- catch /x1/
- Xpath 'f'
- endtry
- Xpath 'g'
-
- try
- try
- Xpath 'h'
- throw "x2"
- Xpath 'i'
- catch /x2/
- Xpath 'j'
- catch /x3/
- Xpath 'k'
- finally
- Xpath 'l'
- throw "x3"
- Xpath 'm'
- endtry
- Xpath 'n'
- catch /x2/
- Xpath 'o'
- catch /x3/
- Xpath 'p'
- endtry
- Xpath 'q'
-
- try
- try
- try
- Xpath 'r'
- throw "x4"
- Xpath 's'
- catch /x5/
- Xpath 't'
- finally
- Xpath 'u'
- throw "x5" " discards 'x4'
- Xpath 'v'
- endtry
- Xpath 'w'
- catch /x4/
- Xpath 'x'
- finally
- Xpath 'y'
- endtry
- Xpath 'z'
- catch /x5/
- Xpath 'A'
- endtry
- Xpath 'B'
-
- catch /.*/
- Xpath 'C'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
- Xpath 'D'
-endfunc
-
-func Test_throw_from_finally()
- XpathINIT
- call T48_F()
- call assert_equal('acfghjlpqruyABD', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 51: Throwing exceptions across :execute and user commands {{{1
-"
-" A :throw command may be executed under an ":execute" or from
-" a user command.
-"-------------------------------------------------------------------------------
-
-func T51_F()
- command! -nargs=? THROW1 throw <args> | throw 1
- command! -nargs=? THROW2 try | throw <args> | endtry | throw 2
- command! -nargs=? THROW3 try | throw 3 | catch /3/ | throw <args> | endtry
- command! -nargs=? THROW4 try | throw 4 | finally | throw <args> | endtry
-
- try
-
- try
- try
- Xpath 'a'
- THROW1 "A"
- catch /A/
- Xpath 'b'
- endtry
- catch /1/
- Xpath 'c'
- endtry
-
- try
- try
- Xpath 'd'
- THROW2 "B"
- catch /B/
- Xpath 'e'
- endtry
- catch /2/
- Xpath 'f'
- endtry
-
- try
- try
- Xpath 'g'
- THROW3 "C"
- catch /C/
- Xpath 'h'
- endtry
- catch /3/
- Xpath 'i'
- endtry
-
- try
- try
- Xpath 'j'
- THROW4 "D"
- catch /D/
- Xpath 'k'
- endtry
- catch /4/
- Xpath 'l'
- endtry
-
- try
- try
- Xpath 'm'
- execute 'throw "E" | throw 5'
- catch /E/
- Xpath 'n'
- endtry
- catch /5/
- Xpath 'o'
- endtry
-
- try
- try
- Xpath 'p'
- execute 'try | throw "F" | endtry | throw 6'
- catch /F/
- Xpath 'q'
- endtry
- catch /6/
- Xpath 'r'
- endtry
-
- try
- try
- Xpath 's'
- execute'try | throw 7 | catch /7/ | throw "G" | endtry'
- catch /G/
- Xpath 't'
- endtry
- catch /7/
- Xpath 'u'
- endtry
-
- try
- try
- Xpath 'v'
- execute 'try | throw 8 | finally | throw "H" | endtry'
- catch /H/
- Xpath 'w'
- endtry
- catch /8/
- Xpath 'x'
- endtry
-
- catch /.*/
- Xpath 'y'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- Xpath 'z'
-
- delcommand THROW1
- delcommand THROW2
- delcommand THROW3
- delcommand THROW4
-endfunc
-
-func Test_throw_across_commands()
- XpathINIT
- call T51_F()
- call assert_equal('abdeghjkmnpqstvwz', g:Xpath)
-endfunc
-
-
-
-"-------------------------------------------------------------------------------
-" Test 69: :throw across :if, :elseif, :while {{{1
-"
-" On an :if, :elseif, or :while command, an exception might be thrown
-" during evaluation of the expression to test. The exception can be
-" caught by the script.
-"-------------------------------------------------------------------------------
-
-func T69_throw(x)
- Xpath 'x'
- throw a:x
-endfunc
-
-func Test_throw_ifelsewhile()
- XpathINIT
-
- try
- try
- Xpath 'a'
- if 111 == T69_throw("if") + 111
- Xpath 'b'
- else
- Xpath 'c'
- endif
- Xpath 'd'
- catch /^if$/
- Xpath 'e'
- catch /.*/
- Xpath 'f'
- call assert_report("if: " . v:exception . " in " . v:throwpoint)
- endtry
-
- try
- Xpath 'g'
- if v:false
- Xpath 'h'
- elseif 222 == T69_throw("elseif") + 222
- Xpath 'i'
- else
- Xpath 'j'
- endif
- Xpath 'k'
- catch /^elseif$/
- Xpath 'l'
- catch /.*/
- Xpath 'm'
- call assert_report("elseif: " . v:exception . " in " . v:throwpoint)
- endtry
-
- try
- Xpath 'n'
- while 333 == T69_throw("while") + 333
- Xpath 'o'
- break
- endwhile
- Xpath 'p'
- catch /^while$/
- Xpath 'q'
- catch /.*/
- Xpath 'r'
- call assert_report("while: " .. v:exception .. " in " .. v:throwpoint)
- endtry
- catch /^0$/ " default return value
- Xpath 's'
- call assert_report(v:throwpoint)
- catch /.*/
- call assert_report(v:exception .. " in " .. v:throwpoint)
- Xpath 't'
- endtry
-
- call assert_equal('axegxlnxq', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 70: :throw across :return or :throw {{{1
-"
-" On a :return or :throw command, an exception might be thrown during
-" evaluation of the expression to return or throw, respectively. The
-" exception can be caught by the script.
-"-------------------------------------------------------------------------------
-
-let T70_taken = ""
-
-func T70_throw(x, n)
- let g:T70_taken = g:T70_taken . "T" . a:n
- throw a:x
-endfunc
-
-func T70_F(x, y, n)
- let g:T70_taken = g:T70_taken . "F" . a:n
- return a:x + T70_throw(a:y, a:n)
-endfunc
-
-func T70_G(x, y, n)
- let g:T70_taken = g:T70_taken . "G" . a:n
- throw a:x . T70_throw(a:y, a:n)
- return a:x
-endfunc
-
-func Test_throwreturn()
- XpathINIT
-
- try
- try
- Xpath 'a'
- call T70_F(4711, "return", 1)
- Xpath 'b'
- catch /^return$/
- Xpath 'c'
- catch /.*/
- Xpath 'd'
- call assert_report("return: " .. v:exception .. " in " .. v:throwpoint)
- endtry
-
- try
- Xpath 'e'
- let var = T70_F(4712, "return-var", 2)
- Xpath 'f'
- catch /^return-var$/
- Xpath 'g'
- catch /.*/
- Xpath 'h'
- call assert_report("return-var: " . v:exception . " in " . v:throwpoint)
- finally
- unlet! var
- endtry
-
- try
- Xpath 'i'
- throw "except1" . T70_throw("throw1", 3)
- Xpath 'j'
- catch /^except1/
- Xpath 'k'
- catch /^throw1$/
- Xpath 'l'
- catch /.*/
- Xpath 'm'
- call assert_report("throw1: " .. v:exception .. " in " .. v:throwpoint)
- endtry
-
- try
- Xpath 'n'
- call T70_G("except2", "throw2", 4)
- Xpath 'o'
- catch /^except2/
- Xpath 'p'
- catch /^throw2$/
- Xpath 'q'
- catch /.*/
- Xpath 'r'
- call assert_report("throw2: " .. v:exception .. " in " .. v:throwpoint)
- endtry
-
- try
- Xpath 's'
- let var = T70_G("except3", "throw3", 5)
- Xpath 't'
- catch /^except3/
- Xpath 'u'
- catch /^throw3$/
- Xpath 'v'
- catch /.*/
- Xpath 'w'
- call assert_report("throw3: " .. v:exception .. " in " .. v:throwpoint)
- finally
- unlet! var
- endtry
-
- call assert_equal('F1T1F2T2T3G4T4G5T5', g:T70_taken)
- Xpath 'x'
- catch /^0$/ " default return value
- Xpath 'y'
- call assert_report(v:throwpoint)
- catch /.*/
- Xpath 'z'
- call assert_report('Caught' .. v:exception .. ' in ' .. v:throwpoint)
- endtry
-
- call assert_equal('acegilnqsvx', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 71: :throw across :echo variants and :execute {{{1
-"
-" On an :echo, :echon, :echomsg, :echoerr, or :execute command, an
-" exception might be thrown during evaluation of the arguments to
-" be displayed or executed as a command, respectively. Any following
-" arguments are not evaluated, then. The exception can be caught by
-" the script.
-"-------------------------------------------------------------------------------
-
-let T71_taken = ""
-
-func T71_throw(x, n)
- let g:T71_taken = g:T71_taken . "T" . a:n
- throw a:x
-endfunc
-
-func T71_F(n)
- let g:T71_taken = g:T71_taken . "F" . a:n
- return "F" . a:n
-endfunc
-
-func Test_throw_echo()
- XpathINIT
-
- try
- try
- Xpath 'a'
- echo 'echo ' . T71_throw("echo-except", 1) . T71_F(1)
- Xpath 'b'
- catch /^echo-except$/
- Xpath 'c'
- catch /.*/
- Xpath 'd'
- call assert_report("echo: " .. v:exception .. " in " .. v:throwpoint)
- endtry
-
- try
- Xpath 'e'
- echon "echon " . T71_throw("echon-except", 2) . T71_F(2)
- Xpath 'f'
- catch /^echon-except$/
- Xpath 'g'
- catch /.*/
- Xpath 'h'
- call assert_report('echon: ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- Xpath 'i'
- echomsg "echomsg " . T71_throw("echomsg-except", 3) . T71_F(3)
- Xpath 'j'
- catch /^echomsg-except$/
- Xpath 'k'
- catch /.*/
- Xpath 'l'
- call assert_report('echomsg: ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- Xpath 'm'
- echoerr "echoerr " . T71_throw("echoerr-except", 4) . T71_F(4)
- Xpath 'n'
- catch /^echoerr-except$/
- Xpath 'o'
- catch /Vim/
- Xpath 'p'
- catch /echoerr/
- Xpath 'q'
- catch /.*/
- Xpath 'r'
- call assert_report('echoerr: ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- Xpath 's'
- execute "echo 'execute " . T71_throw("execute-except", 5) . T71_F(5) "'"
- Xpath 't'
- catch /^execute-except$/
- Xpath 'u'
- catch /.*/
- Xpath 'v'
- call assert_report('execute: ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- call assert_equal('T1T2T3T4T5', g:T71_taken)
- Xpath 'w'
- catch /^0$/ " default return value
- Xpath 'x'
- call assert_report(v:throwpoint)
- catch /.*/
- Xpath 'y'
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- call assert_equal('acegikmosuw', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 72: :throw across :let or :unlet {{{1
-"
-" On a :let command, an exception might be thrown during evaluation
-" of the expression to assign. On an :let or :unlet command, the
-" evaluation of the name of the variable to be assigned or list or
-" deleted, respectively, may throw an exception. Any following
-" arguments are not evaluated, then. The exception can be caught by
-" the script.
-"-------------------------------------------------------------------------------
-
-let throwcount = 0
-
-func T72_throw(x)
- let g:throwcount = g:throwcount + 1
- throw a:x
-endfunc
-
-let T72_addpath = ''
-
-func T72_addpath(p)
- let g:T72_addpath = g:T72_addpath . a:p
-endfunc
-
-func Test_throw_let()
- XpathINIT
-
- try
- try
- let $VAR = 'old_value'
- Xpath 'a'
- let $VAR = 'let(' . T72_throw('var') . ')'
- Xpath 'b'
- catch /^var$/
- Xpath 'c'
- finally
- call assert_equal('old_value', $VAR)
- endtry
-
- try
- let @a = 'old_value'
- Xpath 'd'
- let @a = 'let(' . T72_throw('reg') . ')'
- Xpath 'e'
- catch /^reg$/
- try
- Xpath 'f'
- let @A = 'let(' . T72_throw('REG') . ')'
- Xpath 'g'
- catch /^REG$/
- Xpath 'h'
- endtry
- finally
- call assert_equal('old_value', @a)
- call assert_equal('old_value', @A)
- endtry
-
- try
- let saved_gpath = &g:path
- let saved_lpath = &l:path
- Xpath 'i'
- let &path = 'let(' . T72_throw('opt') . ')'
- Xpath 'j'
- catch /^opt$/
- try
- Xpath 'k'
- let &g:path = 'let(' . T72_throw('gopt') . ')'
- Xpath 'l'
- catch /^gopt$/
- try
- Xpath 'm'
- let &l:path = 'let(' . T72_throw('lopt') . ')'
- Xpath 'n'
- catch /^lopt$/
- Xpath 'o'
- endtry
- endtry
- finally
- call assert_equal(saved_gpath, &g:path)
- call assert_equal(saved_lpath, &l:path)
- let &g:path = saved_gpath
- let &l:path = saved_lpath
- endtry
-
- unlet! var1 var2 var3
-
- try
- Xpath 'p'
- let var1 = 'let(' . T72_throw('var1') . ')'
- Xpath 'q'
- catch /^var1$/
- Xpath 'r'
- finally
- call assert_true(!exists('var1'))
- endtry
-
- try
- let var2 = 'old_value'
- Xpath 's'
- let var2 = 'let(' . T72_throw('var2'). ')'
- Xpath 't'
- catch /^var2$/
- Xpath 'u'
- finally
- call assert_equal('old_value', var2)
- endtry
-
- try
- Xpath 'v'
- let var{T72_throw('var3')} = 4711
- Xpath 'w'
- catch /^var3$/
- Xpath 'x'
- endtry
-
- try
- call T72_addpath('T1')
- let var{T72_throw('var4')} var{T72_addpath('T2')} | call T72_addpath('T3')
- call T72_addpath('T4')
- catch /^var4$/
- call T72_addpath('T5')
- endtry
-
- try
- call T72_addpath('T6')
- unlet var{T72_throw('var5')} var{T72_addpath('T7')}
- \ | call T72_addpath('T8')
- call T72_addpath('T9')
- catch /^var5$/
- call T72_addpath('T10')
- endtry
-
- call assert_equal('T1T5T6T10', g:T72_addpath)
- call assert_equal(11, g:throwcount)
- catch /.*/
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- call assert_equal('acdfhikmoprsuvx', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 73: :throw across :function, :delfunction {{{1
-"
-" The :function and :delfunction commands may cause an expression
-" specified in braces to be evaluated. During evaluation, an
-" exception might be thrown. The exception can be caught by the
-" script.
-"-------------------------------------------------------------------------------
-
-let T73_taken = ''
-
-func T73_throw(x, n)
- let g:T73_taken = g:T73_taken . 'T' . a:n
- throw a:x
-endfunc
-
-func T73_expr(x, n)
- let g:T73_taken = g:T73_taken . 'E' . a:n
- if a:n % 2 == 0
- call T73_throw(a:x, a:n)
- endif
- return 2 - a:n % 2
-endfunc
-
-func Test_throw_func()
- XpathINIT
-
- try
- try
- " Define function.
- Xpath 'a'
- function! F0()
- endfunction
- Xpath 'b'
- function! F{T73_expr('function-def-ok', 1)}()
- endfunction
- Xpath 'c'
- function! F{T73_expr('function-def', 2)}()
- endfunction
- Xpath 'd'
- catch /^function-def-ok$/
- Xpath 'e'
- catch /^function-def$/
- Xpath 'f'
- catch /.*/
- call assert_report('def: ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- " List function.
- Xpath 'g'
- function F0
- Xpath 'h'
- function F{T73_expr('function-lst-ok', 3)}
- Xpath 'i'
- function F{T73_expr('function-lst', 4)}
- Xpath 'j'
- catch /^function-lst-ok$/
- Xpath 'k'
- catch /^function-lst$/
- Xpath 'l'
- catch /.*/
- call assert_report('lst: ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- " Delete function
- Xpath 'm'
- delfunction F0
- Xpath 'n'
- delfunction F{T73_expr('function-del-ok', 5)}
- Xpath 'o'
- delfunction F{T73_expr('function-del', 6)}
- Xpath 'p'
- catch /^function-del-ok$/
- Xpath 'q'
- catch /^function-del$/
- Xpath 'r'
- catch /.*/
- call assert_report('del: ' . v:exception . ' in ' . v:throwpoint)
- endtry
- call assert_equal('E1E2T2E3E4T4E5E6T6', g:T73_taken)
- catch /.*/
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- call assert_equal('abcfghilmnor', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 74: :throw across builtin functions and commands {{{1
-"
-" Some functions like exists(), searchpair() take expression
-" arguments, other functions or commands like substitute() or
-" :substitute cause an expression (specified in the regular
-" expression) to be evaluated. During evaluation an exception
-" might be thrown. The exception can be caught by the script.
-"-------------------------------------------------------------------------------
-
-let T74_taken = ""
-
-func T74_throw(x, n)
- let g:T74_taken = g:T74_taken . "T" . a:n
- throw a:x
-endfunc
-
-func T74_expr(x, n)
- let g:T74_taken = g:T74_taken . "E" . a:n
- call T74_throw(a:x . a:n, a:n)
- return "EXPR"
-endfunc
-
-func T74_skip(x, n)
- let g:T74_taken = g:T74_taken . "S" . a:n . "(" . line(".")
- let theline = getline(".")
- if theline =~ "skip"
- let g:T74_taken = g:T74_taken . "s)"
- return 1
- elseif theline =~ "throw"
- let g:T74_taken = g:T74_taken . "t)"
- call T74_throw(a:x . a:n, a:n)
- else
- let g:T74_taken = g:T74_taken . ")"
- return 0
- endif
-endfunc
-
-func T74_subst(x, n)
- let g:T74_taken = g:T74_taken . "U" . a:n . "(" . line(".")
- let theline = getline(".")
- if theline =~ "not" " T74_subst() should not be called for this line
- let g:T74_taken = g:T74_taken . "n)"
- call T74_throw(a:x . a:n, a:n)
- elseif theline =~ "throw"
- let g:T74_taken = g:T74_taken . "t)"
- call T74_throw(a:x . a:n, a:n)
- else
- let g:T74_taken = g:T74_taken . ")"
- return "replaced"
- endif
-endfunc
-
-func Test_throw_builtin_func()
- XpathINIT
-
- try
- try
- Xpath 'a'
- let result = exists('*{T74_expr("exists", 1)}')
- Xpath 'b'
- catch /^exists1$/
- Xpath 'c'
- try
- let result = exists('{T74_expr("exists", 2)}')
- Xpath 'd'
- catch /^exists2$/
- Xpath 'e'
- catch /.*/
- call assert_report('exists2: ' . v:exception . ' in ' . v:throwpoint)
- endtry
- catch /.*/
- call assert_report('exists1: ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- try
- let file = tempname()
- exec "edit" file
- call append(0, [
- \ 'begin',
- \ 'xx',
- \ 'middle 3',
- \ 'xx',
- \ 'middle 5 skip',
- \ 'xx',
- \ 'middle 7 throw',
- \ 'xx',
- \ 'end'])
- normal! gg
- Xpath 'f'
- let result = searchpair("begin", "middle", "end", '',
- \ 'T74_skip("searchpair", 3)')
- Xpath 'g'
- let result = searchpair("begin", "middle", "end", '',
- \ 'T74_skip("searchpair", 4)')
- Xpath 'h'
- let result = searchpair("begin", "middle", "end", '',
- \ 'T74_skip("searchpair", 5)')
- Xpath 'i'
- catch /^searchpair[35]$/
- Xpath 'j'
- catch /^searchpair4$/
- Xpath 'k'
- catch /.*/
- call assert_report('searchpair: ' . v:exception . ' in ' . v:throwpoint)
- finally
- bwipeout!
- call delete(file)
- endtry
-
- try
- let file = tempname()
- exec "edit" file
- call append(0, [
- \ 'subst 1',
- \ 'subst 2',
- \ 'not',
- \ 'subst 4',
- \ 'subst throw',
- \ 'subst 6'])
- normal! gg
- Xpath 'l'
- 1,2substitute/subst/\=T74_subst("substitute", 6)/
- try
- Xpath 'm'
- try
- let v:errmsg = ""
- 3substitute/subst/\=T74_subst("substitute", 7)/
- finally
- if v:errmsg != ""
- " If exceptions are not thrown on errors, fake the error
- " exception in order to get the same execution path.
- throw "faked Vim(substitute)"
- endif
- endtry
- catch /Vim(substitute)/ " Pattern not found ('e' flag missing)
- Xpath 'n'
- 3substitute/subst/\=T74_subst("substitute", 8)/e
- Xpath 'o'
- endtry
- Xpath 'p'
- 4,6substitute/subst/\=T74_subst("substitute", 9)/
- Xpath 'q'
- catch /^substitute[678]/
- Xpath 'r'
- catch /^substitute9/
- Xpath 's'
- finally
- bwipeout!
- call delete(file)
- endtry
-
- try
- Xpath 't'
- let var = substitute("sub", "sub", '\=T74_throw("substitute()y", 10)', '')
- Xpath 'u'
- catch /substitute()y/
- Xpath 'v'
- catch /.*/
- call assert_report('substitute()y: ' . v:exception . ' in '
- \ . v:throwpoint)
- endtry
-
- try
- Xpath 'w'
- let var = substitute("not", "sub", '\=T74_throw("substitute()n", 11)', '')
- Xpath 'x'
- catch /substitute()n/
- Xpath 'y'
- catch /.*/
- call assert_report('substitute()n: ' . v:exception . ' in '
- \ . v:throwpoint)
- endtry
-
- call assert_equal('E1T1E2T2S3(3)S4(5s)S4(7t)T4U6(1)U6(2)U9(4)U9(5t)T9T10',
- \ g:T74_taken)
-
- catch /.*/
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- endtry
-
- call assert_equal('acefgklmnopstvwx', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 75: Errors in builtin functions. {{{1
-"
-" On an error in a builtin function called inside a :try/:endtry
-" region, the evaluation of the expression calling that function and
-" the command containing that expression are abandoned. The error can
-" be caught as an exception.
-"
-" A simple :call of the builtin function is a trivial case. If the
-" builtin function is called in the argument list of another function,
-" no further arguments are evaluated, and the other function is not
-" executed. If the builtin function is called from the argument of
-" a :return command, the :return command is not executed. If the
-" builtin function is called from the argument of a :throw command,
-" the :throw command is not executed. The evaluation of the
-" expression calling the builtin function is abandoned.
-"-------------------------------------------------------------------------------
-
-func T75_F1(arg1)
- Xpath 'a'
-endfunc
-
-func T75_F2(arg1, arg2)
- Xpath 'b'
-endfunc
-
-func T75_G()
- Xpath 'c'
-endfunc
-
-func T75_H()
- Xpath 'd'
-endfunc
-
-func T75_R()
- while 1
- try
- let caught = 0
- let v:errmsg = ""
- Xpath 'e'
- return append(1, "s")
- catch /E21/
- let caught = 1
- catch /.*/
- Xpath 'f'
- finally
- Xpath 'g'
- if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
- Xpath 'h'
- endif
- break " discard error for $VIMNOERRTHROW
- endtry
- endwhile
- Xpath 'i'
-endfunc
-
-func Test_builtin_func_error()
- XpathINIT
-
- try
- set noma " let append() fail with "E21"
-
- while 1
- try
- let caught = 0
- let v:errmsg = ""
- Xpath 'j'
- call append(1, "s")
- catch /E21/
- let caught = 1
- catch /.*/
- Xpath 'k'
- finally
- Xpath 'l'
- if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
- Xpath 'm'
- endif
- break " discard error for $VIMNOERRTHROW
- endtry
- endwhile
-
- while 1
- try
- let caught = 0
- let v:errmsg = ""
- Xpath 'n'
- call T75_F1('x' . append(1, "s"))
- catch /E21/
- let caught = 1
- catch /.*/
- Xpath 'o'
- finally
- Xpath 'p'
- if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
- Xpath 'q'
- endif
- break " discard error for $VIMNOERRTHROW
- endtry
- endwhile
-
- while 1
- try
- let caught = 0
- let v:errmsg = ""
- Xpath 'r'
- call T75_F2('x' . append(1, "s"), T75_G())
- catch /E21/
- let caught = 1
- catch /.*/
- Xpath 's'
- finally
- Xpath 't'
- if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
- Xpath 'u'
- endif
- break " discard error for $VIMNOERRTHROW
- endtry
- endwhile
-
- call T75_R()
-
- while 1
- try
- let caught = 0
- let v:errmsg = ""
- Xpath 'v'
- throw "T" . append(1, "s")
- catch /E21/
- let caught = 1
- catch /^T.*/
- Xpath 'w'
- catch /.*/
- Xpath 'x'
- finally
- Xpath 'y'
- if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
- Xpath 'z'
- endif
- break " discard error for $VIMNOERRTHROW
- endtry
- endwhile
-
- while 1
- try
- let caught = 0
- let v:errmsg = ""
- Xpath 'A'
- let x = "a"
- let x = x . "b" . append(1, "s") . T75_H()
- catch /E21/
- let caught = 1
- catch /.*/
- Xpath 'B'
- finally
- Xpath 'C'
- if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
- Xpath 'D'
- endif
- call assert_equal('a', x)
- break " discard error for $VIMNOERRTHROW
- endtry
- endwhile
- catch /.*/
- call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
- finally
- set ma&
- endtry
-
- call assert_equal('jlmnpqrtueghivyzACD', g:Xpath)
-endfunc
-
-func Test_reload_in_try_catch()
- call writefile(['x'], 'Xreload')
- set autoread
- edit Xreload
- tabnew
- call writefile(['xx'], 'Xreload')
- augroup ReLoad
- au FileReadPost Xreload let x = doesnotexist
- au BufReadPost Xreload let x = doesnotexist
- augroup END
- try
- edit Xreload
- catch
- endtry
- tabnew
-
- tabclose
- tabclose
- autocmd! ReLoad
- set noautoread
- bwipe! Xreload
- call delete('Xreload')
-endfunc
-
-" Test for errors with :catch, :throw, :finally {{{1
-func Test_try_catch_errors()
- call assert_fails('throw |', 'E471:')
- call assert_fails("throw \n ", 'E471:')
- call assert_fails('catch abc', 'E654:')
- call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:')
- call assert_fails('finally', 'E606:')
- call assert_fails('try | finally | finally | endtry', 'E607:')
- call assert_fails('try | for i in range(5) | endif | endtry', 'E580:')
- call assert_fails('try | while v:true | endtry', 'E170:')
- call assert_fails('try | if v:true | endtry', 'E171:')
-
- " this was using a negative index in cstack[]
- let lines =<< trim END
- try
- for
- if
- endwhile
- if
- finally
- END
- call CheckScriptFailure(lines, 'E690:')
-
- let lines =<< trim END
- try
- for
- if
- endwhile
- if
- endtry
- END
- call CheckScriptFailure(lines, 'E690:')
-endfunc
-
-" Test for verbose messages with :try :catch, and :finally {{{1
-func Test_try_catch_verbose()
- " This test works only when the language is English
- CheckEnglish
-
- set verbose=14
-
- " Test for verbose messages displayed when an exception is caught
- redir => msg
- try
- echo i
- catch /E121:/
- finally
- endtry
- redir END
- let expected = [
- \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', '',
- \ 'Exception caught: Vim(echo):E121: Undefined variable: i', '',
- \ 'Exception finished: Vim(echo):E121: Undefined variable: i']
- call assert_equal(expected, split(msg, "\n"))
-
- " Test for verbose messages displayed when an exception is discarded
- redir => msg
- try
- try
- throw 'abc'
- finally
- throw 'xyz'
- endtry
- catch
- endtry
- redir END
- let expected = [
- \ 'Exception thrown: abc', '',
- \ 'Exception made pending: abc', '',
- \ 'Exception thrown: xyz', '',
- \ 'Exception discarded: abc', '',
- \ 'Exception caught: xyz', '',
- \ 'Exception finished: xyz']
- call assert_equal(expected, split(msg, "\n"))
-
- " Test for messages displayed when :throw is resumed after :finally
- redir => msg
- try
- try
- throw 'abc'
- finally
- endtry
- catch
- endtry
- redir END
- let expected = [
- \ 'Exception thrown: abc', '',
- \ 'Exception made pending: abc', '',
- \ 'Exception resumed: abc', '',
- \ 'Exception caught: abc', '',
- \ 'Exception finished: abc']
- call assert_equal(expected, split(msg, "\n"))
-
- " Test for messages displayed when :break is resumed after :finally
- redir => msg
- for i in range(1)
- try
- break
- finally
- endtry
- endfor
- redir END
- let expected = [':break made pending', '', ':break resumed']
- call assert_equal(expected, split(msg, "\n"))
-
- " Test for messages displayed when :continue is resumed after :finally
- redir => msg
- for i in range(1)
- try
- continue
- finally
- endtry
- endfor
- redir END
- let expected = [':continue made pending', '', ':continue resumed']
- call assert_equal(expected, split(msg, "\n"))
-
- " Test for messages displayed when :return is resumed after :finally
- func Xtest()
- try
- return 'vim'
- finally
- endtry
- endfunc
- redir => msg
- call Xtest()
- redir END
- let expected = [
- \ 'calling Xtest()', '',
- \ ':return vim made pending', '',
- \ ':return vim resumed', '',
- \ 'Xtest returning ''vim''', '',
- \ 'continuing in Test_try_catch_verbose']
- call assert_equal(expected, split(msg, "\n"))
- delfunc Xtest
-
- " Test for messages displayed when :finish is resumed after :finally
- call writefile(['try', 'finish', 'finally', 'endtry'], 'Xscript')
- redir => msg
- source Xscript
- redir END
- let expected = [
- \ ':finish made pending', '',
- \ ':finish resumed', '',
- \ 'finished sourcing Xscript',
- \ 'continuing in Test_try_catch_verbose']
- call assert_equal(expected, split(msg, "\n")[1:])
- call delete('Xscript')
-
- " Test for messages displayed when a pending :continue is discarded by an
- " exception in a finally handler
- redir => msg
- try
- for i in range(1)
- try
- continue
- finally
- throw 'abc'
- endtry
- endfor
- catch
- endtry
- redir END
- let expected = [
- \ ':continue made pending', '',
- \ 'Exception thrown: abc', '',
- \ ':continue discarded', '',
- \ 'Exception caught: abc', '',
- \ 'Exception finished: abc']
- call assert_equal(expected, split(msg, "\n"))
-
- set verbose&
-endfunc
-
-" Test for throwing an exception from a BufEnter autocmd {{{1
-func Test_BufEnter_exception()
- augroup bufenter_exception
- au!
- autocmd BufEnter Xfile1 throw 'abc'
- augroup END
-
- let caught_abc = 0
- try
- sp Xfile1
- catch /^abc/
- let caught_abc = 1
- endtry
- call assert_equal(1, caught_abc)
- call assert_equal(1, winnr('$'))
-
- augroup bufenter_exception
- au!
- augroup END
- augroup! bufenter_exception
- %bwipe!
-
- " Test for recursively throwing exceptions in autocmds
- augroup bufenter_exception
- au!
- autocmd BufEnter Xfile1 throw 'bufenter'
- autocmd BufLeave Xfile1 throw 'bufleave'
- augroup END
-
- let ex_count = 0
- try
- try
- sp Xfile1
- catch /^bufenter/
- let ex_count += 1
- endtry
- catch /^bufleave/
- let ex_count += 10
- endtry
- call assert_equal(10, ex_count)
- call assert_equal(2, winnr('$'))
-
- augroup bufenter_exception
- au!
- augroup END
- augroup! bufenter_exception
- %bwipe!
-endfunc
-
-" Test for using throw in a called function with following error {{{1
-func Test_user_command_throw_in_function_call()
- let lines =<< trim END
- function s:get_dict() abort
- throw 'my_error'
- endfunction
-
- try
- call s:get_dict().foo()
- catch /my_error/
- let caught = 'yes'
- catch
- let caught = v:exception
- endtry
- call assert_equal('yes', caught)
- END
- call writefile(lines, 'XtestThrow')
- source XtestThrow
-
- call delete('XtestThrow')
- unlet g:caught
-endfunc
-
-" Test that after reporting an uncaught exception there is no error for a
-" missing :endif
-func Test_after_exception_no_endif_error()
- function Throw()
- throw "Failure"
- endfunction
-
- function Foo()
- if 1
- call Throw()
- endif
- endfunction
- call assert_fails('call Foo()', ['E605:', 'E605:'])
- delfunc Throw
- delfunc Foo
-endfunc
-
-" Test for using throw in a called function with following endtry {{{1
-func Test_user_command_function_call_with_endtry()
- let lines =<< trim END
- funct s:throw(msg) abort
- throw a:msg
- endfunc
- func s:main() abort
- try
- try
- throw 'err1'
- catch
- call s:throw('err2') | endtry
- catch
- let s:caught = 'yes'
- endtry
- endfunc
-
- call s:main()
- call assert_equal('yes', s:caught)
- END
- call writefile(lines, 'XtestThrow')
- source XtestThrow
-
- call delete('XtestThrow')
-endfunc
-
-func ThisWillFail()
-
-endfunc
-
-" This was crashing prior to the fix in 8.2.3478.
-func Test_error_in_catch_and_finally()
- let lines =<< trim END
- try
- echo x
- catch
- for l in []
- finally
- END
- call writefile(lines, 'XtestCatchAndFinally')
- try
- source XtestCatchAndFinally
- catch /E600:/
- endtry
-
- call delete('XtestCatchAndFinally')
-endfunc
-
-" This was causing an illegal memory access
-func Test_leave_block_in_endtry_not_called()
- let lines =<< trim END
- " vim9script
- " try #
- try "
- for x in []
- if
- endwhile
- if
- endtry
- END
- call writefile(lines, 'XtestEndtry')
- try
- source XtestEndtry
- catch /E171:/
- endtry
-
- call delete('XtestEndtry')
-endfunc
-
-" Modeline {{{1
-" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
deleted file mode 100644
index ee8b52caaf..0000000000
--- a/src/nvim/testdir/test_undo.vim
+++ /dev/null
@@ -1,805 +0,0 @@
-" Tests for the undo tree.
-" Since this script is sourced we need to explicitly break changes up in
-" undo-able pieces. Do that by setting 'undolevels'.
-" Also tests :earlier and :later.
-
-source check.vim
-source screendump.vim
-
-func Test_undotree()
- new
-
- normal! Aabc
- set ul=100
- let d = undotree()
- call assert_equal(1, d.seq_last)
- call assert_equal(1, d.seq_cur)
- call assert_equal(0, d.save_last)
- call assert_equal(0, d.save_cur)
- call assert_equal(1, len(d.entries))
- call assert_equal(1, d.entries[0].newhead)
- call assert_equal(1, d.entries[0].seq)
- call assert_true(d.entries[0].time <= d.time_cur)
-
- normal! Adef
- set ul=100
- let d = undotree()
- call assert_equal(2, d.seq_last)
- call assert_equal(2, d.seq_cur)
- call assert_equal(0, d.save_last)
- call assert_equal(0, d.save_cur)
- call assert_equal(2, len(d.entries))
- call assert_equal(1, d.entries[0].seq)
- call assert_equal(1, d.entries[1].newhead)
- call assert_equal(2, d.entries[1].seq)
- call assert_true(d.entries[1].time <= d.time_cur)
-
- undo
- set ul=100
- let d = undotree()
- call assert_equal(2, d.seq_last)
- call assert_equal(1, d.seq_cur)
- call assert_equal(0, d.save_last)
- call assert_equal(0, d.save_cur)
- call assert_equal(2, len(d.entries))
- call assert_equal(1, d.entries[0].seq)
- call assert_equal(1, d.entries[1].curhead)
- call assert_equal(1, d.entries[1].newhead)
- call assert_equal(2, d.entries[1].seq)
- call assert_true(d.entries[1].time == d.time_cur)
-
- normal! Aghi
- set ul=100
- let d = undotree()
- call assert_equal(3, d.seq_last)
- call assert_equal(3, d.seq_cur)
- call assert_equal(0, d.save_last)
- call assert_equal(0, d.save_cur)
- call assert_equal(2, len(d.entries))
- call assert_equal(1, d.entries[0].seq)
- call assert_equal(2, d.entries[1].alt[0].seq)
- call assert_equal(1, d.entries[1].newhead)
- call assert_equal(3, d.entries[1].seq)
- call assert_true(d.entries[1].time <= d.time_cur)
-
- undo
- set ul=100
- let d = undotree()
- call assert_equal(3, d.seq_last)
- call assert_equal(1, d.seq_cur)
- call assert_equal(0, d.save_last)
- call assert_equal(0, d.save_cur)
- call assert_equal(2, len(d.entries))
- call assert_equal(1, d.entries[0].seq)
- call assert_equal(2, d.entries[1].alt[0].seq)
- call assert_equal(1, d.entries[1].curhead)
- call assert_equal(1, d.entries[1].newhead)
- call assert_equal(3, d.entries[1].seq)
- call assert_true(d.entries[1].time == d.time_cur)
-
- w! Xtest
- let d = undotree()
- call assert_equal(1, d.save_cur)
- call assert_equal(1, d.save_last)
- call delete('Xtest')
- bwipe! Xtest
-endfunc
-
-func FillBuffer()
- for i in range(1,13)
- put=i
- " Set 'undolevels' to split undo.
- exe "setg ul=" . &g:ul
- endfor
-endfunc
-
-func Test_global_local_undolevels()
- new one
- set undolevels=5
- call FillBuffer()
- " will only undo the last 5 changes, end up with 13 - (5 + 1) = 7 lines
- earlier 10
- call assert_equal(5, &g:undolevels)
- call assert_equal(-123456, &l:undolevels)
- call assert_equal('7', getline('$'))
-
- new two
- setlocal undolevels=2
- call FillBuffer()
- " will only undo the last 2 changes, end up with 13 - (2 + 1) = 10 lines
- earlier 10
- call assert_equal(5, &g:undolevels)
- call assert_equal(2, &l:undolevels)
- call assert_equal('10', getline('$'))
-
- setlocal ul=10
- call assert_equal(5, &g:undolevels)
- call assert_equal(10, &l:undolevels)
-
- " Setting local value in "two" must not change local value in "one"
- wincmd p
- call assert_equal(5, &g:undolevels)
- call assert_equal(-123456, &l:undolevels)
-
- new three
- setglobal ul=50
- call assert_equal(50, &g:undolevels)
- call assert_equal(-123456, &l:undolevels)
-
- " Drop created windows
- set ul&
- new
- only!
-endfunc
-
-func BackOne(expected)
- call feedkeys('g-', 'xt')
- call assert_equal(a:expected, getline(1))
-endfunc
-
-func Test_undo_del_chars()
- throw 'Skipped: Nvim does not support test_settime()'
- " Setup a buffer without creating undo entries
- new
- set ul=-1
- call setline(1, ['123-456'])
- set ul=100
- 1
- call test_settime(100)
-
- " Delete three characters and undo with g-
- call feedkeys('x', 'xt')
- call feedkeys('x', 'xt')
- call feedkeys('x', 'xt')
- call assert_equal('-456', getline(1))
- call BackOne('3-456')
- call BackOne('23-456')
- call BackOne('123-456')
- call assert_fails("BackOne('123-456')")
-
- :" Delete three other characters and go back in time with g-
- call feedkeys('$x', 'xt')
- call feedkeys('x', 'xt')
- call feedkeys('x', 'xt')
- call assert_equal('123-', getline(1))
- call test_settime(101)
-
- call BackOne('123-4')
- call BackOne('123-45')
- " skips '123-456' because it's older
- call BackOne('-456')
- call BackOne('3-456')
- call BackOne('23-456')
- call BackOne('123-456')
- call assert_fails("BackOne('123-456')")
- normal 10g+
- call assert_equal('123-', getline(1))
-
- :" Jump two seconds and go some seconds forward and backward
- call test_settime(103)
- call feedkeys("Aa\<Esc>", 'xt')
- call feedkeys("Ab\<Esc>", 'xt')
- call feedkeys("Ac\<Esc>", 'xt')
- call assert_equal('123-abc', getline(1))
- earlier 1s
- call assert_equal('123-', getline(1))
- earlier 3s
- call assert_equal('123-456', getline(1))
- later 1s
- call assert_equal('123-', getline(1))
- later 1h
- call assert_equal('123-abc', getline(1))
-
- close!
-endfunc
-
-func Test_undolist()
- new
- set ul=100
-
- let a = execute('undolist')
- call assert_equal("\nNothing to undo", a)
-
- " 1 leaf (2 changes).
- call feedkeys('achange1', 'xt')
- call feedkeys('achange2', 'xt')
- let a = execute('undolist')
- call assert_match("^\nnumber changes when *saved\n *2 *2 .*$", a)
-
- " 2 leaves.
- call feedkeys('u', 'xt')
- call feedkeys('achange3\<Esc>', 'xt')
- let a = execute('undolist')
- call assert_match("^\nnumber changes when *saved\n *2 *2 *.*\n *3 *2 .*$", a)
- close!
-endfunc
-
-func Test_U_command()
- new
- set ul=100
- call feedkeys("achange1\<Esc>", 'xt')
- call feedkeys("achange2\<Esc>", 'xt')
- norm! U
- call assert_equal('', getline(1))
- norm! U
- call assert_equal('change1change2', getline(1))
- close!
-endfunc
-
-func Test_undojoin()
- new
- call feedkeys("Goaaaa\<Esc>", 'xt')
- call feedkeys("obbbb\<Esc>", 'xt')
- call assert_equal(['aaaa', 'bbbb'], getline(2, '$'))
- call feedkeys("u", 'xt')
- call assert_equal(['aaaa'], getline(2, '$'))
- call feedkeys("obbbb\<Esc>", 'xt')
- undojoin
- " Note: next change must not be as if typed
- call feedkeys("occcc\<Esc>", 'x')
- call assert_equal(['aaaa', 'bbbb', 'cccc'], getline(2, '$'))
- call feedkeys("u", 'xt')
- call assert_equal(['aaaa'], getline(2, '$'))
- 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()
- call delete('Xtest')
- split Xtest
- call feedkeys("ione one one\<Esc>", 'xt')
- w!
- call feedkeys("otwo\<Esc>", 'xt')
- call feedkeys("otwo\<Esc>", 'xt')
- w
- call feedkeys("othree\<Esc>", 'xt')
- call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$'))
- earlier 1f
- call assert_equal(['one one one', 'two', 'two'], getline(1, '$'))
- earlier 1f
- call assert_equal(['one one one'], getline(1, '$'))
- earlier 1f
- call assert_equal([''], getline(1, '$'))
- later 1f
- call assert_equal(['one one one'], getline(1, '$'))
- later 1f
- call assert_equal(['one one one', 'two', 'two'], getline(1, '$'))
- later 1f
- call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$'))
-
- close!
- call delete('Xtest')
- bwipe! Xtest
-
- call assert_fails('earlier xyz', 'E475:')
-endfunc
-
-func Test_insert_expr()
- new
- " calling setline() triggers undo sync
- call feedkeys("oa\<Esc>", 'xt')
- call feedkeys("ob\<Esc>", 'xt')
- set ul=100
- call feedkeys("o1\<Esc>a2\<C-R>=setline('.','1234')\<CR>\<CR>\<Esc>", 'x')
- call assert_equal(['a', 'b', '120', '34'], getline(2, '$'))
- call feedkeys("u", 'x')
- call assert_equal(['a', 'b', '12'], getline(2, '$'))
- call feedkeys("u", 'x')
- call assert_equal(['a', 'b'], getline(2, '$'))
-
- call feedkeys("oc\<Esc>", 'xt')
- set ul=100
- call feedkeys("o1\<Esc>a2\<C-R>=setline('.','1234')\<CR>\<CR>\<Esc>", 'x')
- call assert_equal(['a', 'b', 'c', '120', '34'], getline(2, '$'))
- call feedkeys("u", 'x')
- call assert_equal(['a', 'b', 'c', '12'], getline(2, '$'))
-
- call feedkeys("od\<Esc>", 'xt')
- set ul=100
- call feedkeys("o1\<Esc>a2\<C-R>=string(123)\<CR>\<Esc>", 'x')
- call assert_equal(['a', 'b', 'c', '12', 'd', '12123'], getline(2, '$'))
- call feedkeys("u", 'x')
- call assert_equal(['a', 'b', 'c', '12', 'd'], getline(2, '$'))
-
- close!
-endfunc
-
-func Test_undofile_earlier()
- throw 'Skipped: Nvim does not support test_settime()'
- " Issue #1254
- " create undofile with timestamps older than Vim startup time.
- let t0 = localtime() - 43200
- call test_settime(t0)
- new XfileEarlier
- call feedkeys("ione\<Esc>", 'xt')
- set ul=100
- call test_settime(t0 + 1)
- call feedkeys("otwo\<Esc>", 'xt')
- set ul=100
- call test_settime(t0 + 2)
- call feedkeys("othree\<Esc>", 'xt')
- set ul=100
- w
- wundo Xundofile
- bwipe!
- " restore normal timestamps.
- call test_settime(0)
- new XfileEarlier
- rundo Xundofile
- earlier 1d
- call assert_equal('', getline(1))
- bwipe!
- call delete('XfileEarlier')
- call delete('Xundofile')
-endfunc
-
-func Test_wundo_errors()
- new
- call setline(1, 'hello')
- call assert_fails('wundo! Xdoesnotexist/Xundofile', 'E828:')
- bwipe!
-endfunc
-
-" Check that reading a truncated undo file doesn't hang.
-func Test_undofile_truncated()
- new
- call setline(1, 'hello')
- set ul=100
- wundo Xundofile
- let contents = readfile('Xundofile', 'B')
-
- " try several sizes
- for size in range(20, 500, 33)
- call writefile(contents[0:size], 'Xundofile')
- call assert_fails('rundo Xundofile', 'E825:')
- endfor
-
- bwipe!
- call delete('Xundofile')
-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
-
-func Test_undofile_next()
- set undofile
- new Xfoo.txt
- execute "norm ix\<c-g>uy\<c-g>uz\<Esc>"
- write
- bwipe
-
- next Xfoo.txt
- call assert_equal('xyz', getline(1))
- silent undo
- call assert_equal('xy', getline(1))
- silent undo
- call assert_equal('x', getline(1))
- bwipe!
-
- call delete('Xfoo.txt')
- call delete('.Xfoo.txt.un~')
- set undofile&
-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()
- enew!
- let @a = "Ox\<Esc>jAy\<Esc>kdd"
- edit +/^$ test_undo.vim
- normal @au
- call assert_equal(0, &modified)
- return
- new
- normal @au
- call assert_equal(0, &modified)
- only!
- let @a = ''
-endfunc
-
-" This used to cause an illegal memory access
-func Test_undo_append()
- new
- call feedkeys("axx\<Esc>v", 'xt')
- undo
- norm o
- quit
-endfunc
-
-func Test_undo_0()
- new
- set ul=100
- normal i1
- undo
- normal i2
- undo
- normal i3
-
- undo 0
- let d = undotree()
- call assert_equal('', getline(1))
- call assert_equal(0, d.seq_cur)
-
- redo
- let d = undotree()
- call assert_equal('3', getline(1))
- call assert_equal(3, d.seq_cur)
-
- undo 2
- undo 0
- let d = undotree()
- call assert_equal('', getline(1))
- call assert_equal(0, d.seq_cur)
-
- redo
- let d = undotree()
- call assert_equal('2', getline(1))
- call assert_equal(2, d.seq_cur)
-
- undo 1
- undo 0
- let d = undotree()
- call assert_equal('', getline(1))
- call assert_equal(0, d.seq_cur)
-
- redo
- let d = undotree()
- call assert_equal('1', getline(1))
- call assert_equal(1, d.seq_cur)
-
- bwipe!
-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"
- exe "norm."
- bwipe!
-endfunc
-
-funct Test_undofile()
- " Test undofile() without setting 'undodir'.
- if has('persistent_undo')
- call assert_equal(fnamemodify('.Xundofoo.un~', ':p'), undofile('Xundofoo'))
- else
- call assert_equal('', undofile('Xundofoo'))
- endif
- call assert_equal('', undofile(''))
-
- " Test undofile() with 'undodir' set to to an existing directory.
- call mkdir('Xundodir')
- set undodir=Xundodir
- let cwd = getcwd()
- if has('win32')
- " Replace windows drive such as C:... into C%...
- let cwd = substitute(cwd, '^\([a-zA-Z]\):', '\1%', 'g')
- endif
- let cwd = substitute(cwd . '/Xundofoo', '/', '%', 'g')
- if has('persistent_undo')
- call assert_equal('Xundodir/' . cwd, undofile('Xundofoo'))
- else
- call assert_equal('', undofile('Xundofoo'))
- endif
- call assert_equal('', undofile(''))
- call delete('Xundodir', 'd')
-
- " Test undofile() with 'undodir' set to a non-existing directory.
- " call assert_equal('', 'Xundofoo'->undofile())
-
- if isdirectory('/tmp')
- set undodir=/tmp
- if has('osx')
- call assert_equal('/tmp/%private%tmp%file', undofile('///tmp/file'))
- else
- call assert_equal('/tmp/%tmp%file', undofile('///tmp/file'))
- endif
- endif
-
- set undodir&
-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('%', 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
-
-" Test for redoing with incrementing numbered registers
-func Test_redo_repeat_numbered_register()
- new
- for [i, v] in [[1, 'one'], [2, 'two'], [3, 'three'],
- \ [4, 'four'], [5, 'five'], [6, 'six'],
- \ [7, 'seven'], [8, 'eight'], [9, 'nine']]
- exe 'let @' .. i .. '="' .. v .. '\n"'
- endfor
- call feedkeys('"1p.........', 'xt')
- call assert_equal(['', 'one', 'two', 'three', 'four', 'five', 'six',
- \ 'seven', 'eight', 'nine', 'nine'], getline(1, '$'))
- bwipe!
-endfunc
-
-" Test for redo in insert mode using CTRL-O with multibyte characters
-func Test_redo_multibyte_in_insert_mode()
- new
- call feedkeys("a\<C-K>ft", 'xt')
- call feedkeys("uiHe\<C-O>.llo", 'xt')
- call assert_equal("He\ufb05llo", getline(1))
- bwipe!
-endfunc
-
-func Test_undo_mark()
- new
- " The undo is applied to the only line.
- call setline(1, 'hello')
- call feedkeys("ggyiw$p", 'xt')
- undo
- call assert_equal([0, 1, 1, 0], getpos("'["))
- call assert_equal([0, 1, 1, 0], getpos("']"))
- " The undo removes the last line.
- call feedkeys("Goaaaa\<Esc>", 'xt')
- call feedkeys("obbbb\<Esc>", 'xt')
- undo
- call assert_equal([0, 2, 1, 0], getpos("'["))
- call assert_equal([0, 2, 1, 0], getpos("']"))
- bwipe!
-endfunc
-
-func Test_undo_after_write()
- " use a terminal to make undo work like when text is typed
- CheckRunVimInTerminal
-
- let lines =<< trim END
- edit Xtestfile.txt
- set undolevels=100 undofile
- imap . <Cmd>write<CR>
- write
- END
- call writefile(lines, 'Xtest_undo_after_write', 'D')
- let buf = RunVimInTerminal('-S Xtest_undo_after_write', #{rows: 6})
-
- call term_sendkeys(buf, "Otest.\<CR>boo!!!\<Esc>")
- sleep 100m
- call term_sendkeys(buf, "u")
- call VerifyScreenDump(buf, 'Test_undo_after_write_1', {})
-
- call term_sendkeys(buf, "u")
- call VerifyScreenDump(buf, 'Test_undo_after_write_2', {})
-
- call StopVimInTerminal(buf)
- call delete('Xtestfile.txt')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim
deleted file mode 100644
index 4779d17906..0000000000
--- a/src/nvim/testdir/test_unlet.vim
+++ /dev/null
@@ -1,67 +0,0 @@
-" Tests for :unlet
-
-func Test_read_only()
- " these caused a crash
- call assert_fails('unlet v:count', 'E795:')
- call assert_fails('unlet v:errmsg', 'E795:')
-endfunc
-
-func Test_existing()
- let does_exist = 1
- call assert_true(exists('does_exist'))
- unlet does_exist
- call assert_false(exists('does_exist'))
-endfunc
-
-func Test_not_existing()
- unlet! does_not_exist
- call assert_fails('unlet does_not_exist', 'E108:')
-endfunc
-
-func Test_unlet_fails()
- call assert_fails('unlet v:["count"]', 'E46:')
- call assert_fails('unlet $', 'E475:')
- let v = {}
- call assert_fails('unlet v[:]', 'E719:')
- let l = []
- call assert_fails("unlet l['k'", 'E111:')
- let d = {'k' : 1}
- call assert_fails("unlet d.k2", 'E716:')
- call assert_fails("unlet {a};", 'E488:')
-endfunc
-
-func Test_unlet_env()
- let envcmd = has('win32') ? 'set' : 'env'
-
- let $FOOBAR = 'test'
- let found = 0
- for kv in split(system(envcmd), "\r*\n")
- if kv == 'FOOBAR=test'
- let found = 1
- endif
- endfor
- call assert_equal(1, found)
-
- unlet $FOOBAR
- let found = 0
- for kv in split(system(envcmd), "\r*\n")
- if kv == 'FOOBAR=test'
- let found = 1
- endif
- endfor
- call assert_equal(0, found)
-
- unlet $MUST_NOT_BE_AN_ERROR
-endfunc
-
-func Test_unlet_complete()
- let g:FOOBAR = 1
- call feedkeys(":unlet g:FOO\t\n", 'tx')
- call assert_true(!exists('g:FOOBAR'))
-
- let $FOOBAR = 1
- call feedkeys(":unlet $FOO\t\n", 'tx')
- call assert_true(!exists('$FOOBAR') || empty($FOOBAR))
-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
deleted file mode 100644
index 4742293ed5..0000000000
--- a/src/nvim/testdir/test_user_func.vim
+++ /dev/null
@@ -1,504 +0,0 @@
-" Test for user functions.
-" Also test an <expr> mapping calling a function.
-" Also test that a builtin function cannot be replaced.
-" Also test for regression when calling arbitrary expression.
-
-source check.vim
-source shared.vim
-
-func Table(title, ...)
- let ret = a:title
- let idx = 1
- while idx <= a:0
- exe "let ret = ret . a:" . idx
- let idx = idx + 1
- endwhile
- return ret
-endfunc
-
-func Compute(n1, n2, divname)
- if a:n2 == 0
- return "fail"
- endif
- exe "let g:" . a:divname . " = ". a:n1 / a:n2
- return "ok"
-endfunc
-
-func Expr1()
- silent! normal! v
- return "111"
-endfunc
-
-func Expr2()
- call search('XX', 'b')
- return "222"
-endfunc
-
-func ListItem()
- let g:counter += 1
- return g:counter . '. '
-endfunc
-
-func ListReset()
- let g:counter = 0
- return ''
-endfunc
-
-func FuncWithRef(a)
- unlet g:FuncRef
- return a:a
-endfunc
-
-func Test_user_func()
- let g:FuncRef = function("FuncWithRef")
- let g:counter = 0
- inoremap <expr> ( ListItem()
- inoremap <expr> [ ListReset()
- imap <expr> + Expr1()
- imap <expr> * Expr2()
- let g:retval = "nop"
-
- call assert_equal('xxx4asdf', Table("xxx", 4, "asdf"))
- call assert_equal('fail', Compute(45, 0, "retval"))
- call assert_equal('nop', g:retval)
- call assert_equal('ok', Compute(45, 5, "retval"))
- call assert_equal(9, g:retval)
- call assert_equal(333, g:FuncRef(333))
-
- let g:retval = "nop"
- call assert_equal('xxx4asdf', "xxx"->Table(4, "asdf"))
- call assert_equal('fail', 45->Compute(0, "retval"))
- call assert_equal('nop', g:retval)
- call assert_equal('ok', 45->Compute(5, "retval"))
- call assert_equal(9, g:retval)
- " call assert_equal(333, 333->g:FuncRef())
-
- enew
-
- normal oXX+-XX
- call assert_equal('XX111-XX', getline('.'))
- normal o---*---
- call assert_equal('---222---', getline('.'))
- normal o(one
- call assert_equal('1. one', getline('.'))
- normal o(two
- call assert_equal('2. two', getline('.'))
- normal o[(one again
- call assert_equal('1. one again', getline('.'))
-
- " Try to overwrite a function in the global (g:) scope
- call assert_equal(3, max([1, 2, 3]))
- call assert_fails("call extend(g:, {'max': function('min')})", 'E704')
- call assert_equal(3, max([1, 2, 3]))
-
- " Try to overwrite an user defined function with a function reference
- call assert_fails("let Expr1 = function('min')", 'E705:')
-
- " Regression: the first line below used to throw ?E110: Missing ')'?
- " Second is here just to prove that this line is correct when not skipping
- " rhs of &&.
- call assert_equal(0, (0 && (function('tr'))(1, 2, 3)))
- call assert_equal(1, (1 && (function('tr'))(1, 2, 3)))
-
- delfunc Table
- delfunc Compute
- delfunc Expr1
- delfunc Expr2
- delfunc ListItem
- delfunc ListReset
- unlet g:retval g:counter
- enew!
-endfunc
-
-func Log(val, base = 10)
- return log(a:val) / log(a:base)
-endfunc
-
-func Args(mandatory, optional = v:null, ...)
- return deepcopy(a:)
-endfunc
-
-func Args2(a = 1, b = 2, c = 3)
- return deepcopy(a:)
-endfunc
-
-func MakeBadFunc()
- func s:fcn(a, b=1, c)
- endfunc
-endfunc
-
-func Test_default_arg()
- if has('float')
- call assert_equal(1.0, Log(10))
- call assert_equal(log(10), Log(10, exp(1)))
- call assert_fails("call Log(1,2,3)", 'E118')
- endif
-
- let res = Args(1)
- call assert_equal(res.mandatory, 1)
- call assert_equal(res.optional, v:null)
- call assert_equal(res['0'], 0)
-
- let res = Args(1,2)
- call assert_equal(res.mandatory, 1)
- call assert_equal(res.optional, 2)
- call assert_equal(res['0'], 0)
-
- let res = Args(1,2,3)
- call assert_equal(res.mandatory, 1)
- call assert_equal(res.optional, 2)
- call assert_equal(res['0'], 1)
-
- call assert_fails("call MakeBadFunc()", 'E989:')
- call assert_fails("fu F(a=1 ,) | endf", 'E1068:')
-
- " Since neovim does not have v:none, the ability to use the default
- " argument with the intermediate argument set to v:none has been omitted.
- " Therefore, this test is not performed.
- " let d = Args2(7, v:none, 9)
- " call assert_equal([7, 2, 9], [d.a, d.b, d.c])
-
- call assert_equal("\n"
- \ .. " function Args2(a = 1, b = 2, c = 3)\n"
- \ .. "1 return deepcopy(a:)\n"
- \ .. " endfunction",
- \ execute('func Args2'))
-
- " Error in default argument expression
- let l =<< trim END
- func F1(x = y)
- return a:x * 2
- endfunc
- echo F1()
- END
- let @a = l->join("\n")
- call assert_fails("exe @a", 'E121:')
-endfunc
-
-func s:addFoo(lead)
- return a:lead .. 'foo'
-endfunc
-
-func Test_user_method()
- eval 'bar'->s:addFoo()->assert_equal('barfoo')
-endfunc
-
-func Test_failed_call_in_try()
- try | call UnknownFunc() | catch | endtry
-endfunc
-
-" Test for listing user-defined functions
-func Test_function_list()
- call assert_fails("function Xabc", 'E123:')
-endfunc
-
-" Test for <sfile>, <slnum> in a function
-func Test_sfile_in_function()
- func Xfunc()
- call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>'))
- call assert_equal('2', expand('<slnum>'))
- endfunc
- call Xfunc()
- delfunc Xfunc
-endfunc
-
-" Test trailing text after :endfunction {{{1
-func Test_endfunction_trailing()
- call assert_false(exists('*Xtest'))
-
- exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'"
- call assert_true(exists('*Xtest'))
- call assert_equal('yes', done)
- delfunc Xtest
- unlet done
-
- exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'"
- call assert_true(exists('*Xtest'))
- call assert_equal('yes', done)
- delfunc Xtest
- unlet done
-
- " trailing line break
- exe "func Xtest()\necho 'hello'\nendfunc\n"
- call assert_true(exists('*Xtest'))
- delfunc Xtest
-
- set verbose=1
- exe "func Xtest()\necho 'hello'\nendfunc \" garbage"
- call assert_notmatch('W22:', split(execute('1messages'), "\n")[0])
- call assert_true(exists('*Xtest'))
- delfunc Xtest
-
- exe "func Xtest()\necho 'hello'\nendfunc garbage"
- call assert_match('W22:', split(execute('1messages'), "\n")[0])
- call assert_true(exists('*Xtest'))
- delfunc Xtest
- set verbose=0
-
- func Xtest(a1, a2)
- echo a:a1 .. a:a2
- endfunc
- set verbose=15
- redir @a
- call Xtest(123, repeat('x', 100))
- redir END
- call assert_match('calling Xtest(123, ''xxxxxxx.*x\.\.\.x.*xxxx'')', getreg('a'))
- delfunc Xtest
- set verbose=0
-
- function Foo()
- echo 'hello'
- endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
- delfunc Foo
-endfunc
-
-func Test_delfunction_force()
- delfunc! Xtest
- delfunc! Xtest
- func Xtest()
- echo 'nothing'
- endfunc
- delfunc! Xtest
- delfunc! Xtest
-
- " Try deleting the current function
- call assert_fails('delfunc Test_delfunction_force', 'E131:')
-endfunc
-
-func Test_function_defined_line()
- CheckNotGui
-
- 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(GetVimCommandClean() .. ' -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
-
-" Test for defining a function reference in the global scope
-func Test_add_funcref_to_global_scope()
- let x = g:
- let caught_E862 = 0
- try
- func x.Xfunc()
- return 1
- endfunc
- catch /E862:/
- let caught_E862 = 1
- endtry
- call assert_equal(1, caught_E862)
-endfunc
-
-func Test_funccall_garbage_collect()
- func Func(x, ...)
- call add(a:x, a:000)
- endfunc
- call Func([], [])
- " Must not crash cause by invalid freeing
- call test_garbagecollect_now()
- call assert_true(v:true)
- delfunc Func
-endfunc
-
-" Test for script-local function
-func <SID>DoLast()
- call append(line('$'), "last line")
-endfunc
-
-func s:DoNothing()
- call append(line('$'), "nothing line")
-endfunc
-
-func Test_script_local_func()
- 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>
-
- normal _x
- call assert_equal('nothing line', getline(2))
- call assert_equal('last line', getline(3))
- close!
-
- " Try to call a script local function in global scope
- let lines =<< trim [CODE]
- :call assert_fails('call s:Xfunc()', 'E81:')
- :call assert_fails('let x = call("<SID>Xfunc", [])', 'E120:')
- :call writefile(v:errors, 'Xresult')
- :qall
-
- [CODE]
- call writefile(lines, 'Xscript')
- if RunVim([], [], '-s Xscript')
- call assert_equal([], readfile('Xresult'))
- endif
- call delete('Xresult')
- call delete('Xscript')
-endfunc
-
-" Test for errors in defining new functions
-func Test_func_def_error()
- call assert_fails('func Xfunc abc ()', 'E124:')
- call assert_fails('func Xfunc(', 'E125:')
- call assert_fails('func xfunc()', 'E128:')
-
- " Try to redefine a function that is in use
- let caught_E127 = 0
- try
- func! Test_func_def_error()
- endfunc
- catch /E127:/
- let caught_E127 = 1
- endtry
- call assert_equal(1, caught_E127)
-
- " Try to define a function in a dict twice
- let d = {}
- let lines =<< trim END
- func d.F1()
- return 1
- endfunc
- END
- let l = join(lines, "\n") . "\n"
- exe l
- call assert_fails('exe l', 'E717:')
-
- " Define an autoload function with an incorrect file name
- call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript')
- call assert_fails('source Xscript', 'E746:')
- call delete('Xscript')
-
- " Try to list functions using an invalid search pattern
- call assert_fails('function /\%(/', 'E53:')
-endfunc
-
-" Test for deleting a function
-func Test_del_func()
- call assert_fails('delfunction Xabc', 'E130:')
- let d = {'a' : 10}
- call assert_fails('delfunc d.a', 'E718:')
- func d.fn()
- return 1
- endfunc
-
- " cannot delete the dict function by number
- let nr = substitute(execute('echo d'), '.*function(''\(\d\+\)'').*', '\1', '')
- call assert_fails('delfunction g:' .. nr, 'E475: Invalid argument: g:')
-
- delfunc d.fn
- call assert_equal({'a' : 10}, d)
-endfunc
-
-" Test for calling return outside of a function
-func Test_return_outside_func()
- call writefile(['return 10'], 'Xscript')
- call assert_fails('source Xscript', 'E133:')
- call delete('Xscript')
-endfunc
-
-" Test for errors in calling a function
-func Test_func_arg_error()
- " Too many arguments
- call assert_fails("call call('min', range(1,20))", 'E118:')
- call assert_fails("call call('min', range(1,21))", 'E699:')
- call assert_fails('echo min(0,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,0,1)',
- \ 'E740:')
-
- " Missing dict argument
- func Xfunc() dict
- return 1
- endfunc
- call assert_fails('call Xfunc()', 'E725:')
- delfunc Xfunc
-endfunc
-
-func Test_func_dict()
- let mydict = {'a': 'b'}
- function mydict.somefunc() dict
- return len(self)
- endfunc
-
- call assert_equal("{'a': 'b', 'somefunc': function('3')}", string(mydict))
- call assert_equal(2, mydict.somefunc())
- call assert_match("^\n function \\d\\\+() dict"
- \ .. "\n1 return len(self)"
- \ .. "\n endfunction$", execute('func mydict.somefunc'))
- call assert_fails('call mydict.nonexist()', 'E716:')
-endfunc
-
-func Test_func_range()
- new
- call setline(1, range(1, 8))
- func FuncRange() range
- echo a:firstline
- echo a:lastline
- endfunc
- 3
- call assert_equal("\n3\n3", execute('call FuncRange()'))
- call assert_equal("\n4\n6", execute('4,6 call FuncRange()'))
- call assert_equal("\n function FuncRange() range"
- \ .. "\n1 echo a:firstline"
- \ .. "\n2 echo a:lastline"
- \ .. "\n endfunction",
- \ execute('function FuncRange'))
-
- bwipe!
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim
deleted file mode 100644
index 6910361345..0000000000
--- a/src/nvim/testdir/test_usercommands.vim
+++ /dev/null
@@ -1,750 +0,0 @@
-" Tests for user defined commands
-
-" Test for <mods> in user defined commands
-function Test_cmdmods()
- let g:mods = ''
-
- command! -nargs=* MyCmd let g:mods = '<mods>'
-
- MyCmd
- call assert_equal('', g:mods)
- aboveleft MyCmd
- call assert_equal('aboveleft', g:mods)
- abo MyCmd
- call assert_equal('aboveleft', g:mods)
- belowright MyCmd
- call assert_equal('belowright', g:mods)
- bel MyCmd
- call assert_equal('belowright', g:mods)
- botright MyCmd
- call assert_equal('botright', g:mods)
- bo MyCmd
- call assert_equal('botright', g:mods)
- browse MyCmd
- call assert_equal('browse', g:mods)
- bro MyCmd
- call assert_equal('browse', g:mods)
- confirm MyCmd
- call assert_equal('confirm', g:mods)
- conf MyCmd
- call assert_equal('confirm', g:mods)
- hide MyCmd
- call assert_equal('hide', g:mods)
- hid MyCmd
- call assert_equal('hide', g:mods)
- keepalt MyCmd
- call assert_equal('keepalt', g:mods)
- keepa MyCmd
- call assert_equal('keepalt', g:mods)
- keepjumps MyCmd
- call assert_equal('keepjumps', g:mods)
- keepj MyCmd
- call assert_equal('keepjumps', g:mods)
- keepmarks MyCmd
- call assert_equal('keepmarks', g:mods)
- kee MyCmd
- call assert_equal('keepmarks', g:mods)
- keeppatterns MyCmd
- call assert_equal('keeppatterns', g:mods)
- keepp MyCmd
- call assert_equal('keeppatterns', g:mods)
- leftabove MyCmd " results in :aboveleft
- call assert_equal('aboveleft', g:mods)
- lefta MyCmd
- call assert_equal('aboveleft', g:mods)
- lockmarks MyCmd
- call assert_equal('lockmarks', g:mods)
- loc MyCmd
- call assert_equal('lockmarks', g:mods)
- noautocmd MyCmd
- call assert_equal('noautocmd', g:mods)
- noa MyCmd
- call assert_equal('noautocmd', g:mods)
- noswapfile MyCmd
- call assert_equal('noswapfile', g:mods)
- nos MyCmd
- call assert_equal('noswapfile', g:mods)
- rightbelow MyCmd " results in :belowright
- call assert_equal('belowright', g:mods)
- rightb MyCmd
- call assert_equal('belowright', g:mods)
- " sandbox MyCmd
- silent MyCmd
- call assert_equal('silent', g:mods)
- sil MyCmd
- call assert_equal('silent', g:mods)
- silent! MyCmd
- call assert_equal('silent!', g:mods)
- sil! MyCmd
- call assert_equal('silent!', g:mods)
- tab MyCmd
- call assert_equal('tab', g:mods)
- 0tab MyCmd
- call assert_equal('0tab', g:mods)
- tab split
- tab MyCmd
- call assert_equal('tab', g:mods)
- 1tab MyCmd
- call assert_equal('1tab', g:mods)
- tabprev
- tab MyCmd
- call assert_equal('tab', g:mods)
- 2tab MyCmd
- call assert_equal('2tab', g:mods)
- 2tabclose
- topleft MyCmd
- call assert_equal('topleft', g:mods)
- to MyCmd
- call assert_equal('topleft', g:mods)
- unsilent MyCmd
- call assert_equal('unsilent', g:mods)
- uns MyCmd
- call assert_equal('unsilent', g:mods)
- verbose MyCmd
- call assert_equal('verbose', g:mods)
- verb MyCmd
- call assert_equal('verbose', g:mods)
- 0verbose MyCmd
- call assert_equal('0verbose', g:mods)
- 3verbose MyCmd
- call assert_equal('3verbose', g:mods)
- 999verbose MyCmd
- call assert_equal('999verbose', g:mods)
- vertical MyCmd
- call assert_equal('vertical', g:mods)
- vert MyCmd
- call assert_equal('vertical', g:mods)
- horizontal MyCmd
- call assert_equal('horizontal', g:mods)
- hor MyCmd
- call assert_equal('horizontal', g:mods)
-
- aboveleft belowright botright browse confirm hide keepalt keepjumps
- \ keepmarks keeppatterns lockmarks noautocmd noswapfile silent
- \ tab topleft unsilent verbose vertical MyCmd
-
- call assert_equal('browse confirm hide keepalt keepjumps ' .
- \ 'keepmarks keeppatterns lockmarks noswapfile unsilent noautocmd ' .
- \ 'silent verbose aboveleft belowright botright tab topleft vertical',
- \ g:mods)
-
- let g:mods = ''
- command! -nargs=* MyQCmd let g:mods .= '<q-mods> '
-
- vertical MyQCmd
- call assert_equal('"vertical" ', g:mods)
-
- delcommand MyCmd
- delcommand MyQCmd
- unlet g:mods
-endfunction
-
-func SaveCmdArgs(...)
- let g:args = a:000
-endfunc
-
-func Test_f_args()
- command -nargs=* TestFArgs call SaveCmdArgs(<f-args>)
-
- TestFArgs
- call assert_equal([], g:args)
-
- TestFArgs one two three
- call assert_equal(['one', 'two', 'three'], g:args)
-
- TestFArgs one\\two three
- call assert_equal(['one\two', 'three'], g:args)
-
- TestFArgs one\ two three
- call assert_equal(['one two', 'three'], g:args)
-
- TestFArgs one\"two three
- call assert_equal(['one\"two', 'three'], g:args)
-
- delcommand TestFArgs
-endfunc
-
-func Test_q_args()
- command -nargs=* TestQArgs call SaveCmdArgs(<q-args>)
-
- TestQArgs
- call assert_equal([''], g:args)
-
- TestQArgs one two three
- call assert_equal(['one two three'], g:args)
-
- TestQArgs one\\two three
- call assert_equal(['one\\two three'], g:args)
-
- TestQArgs one\ two three
- call assert_equal(['one\ two three'], g:args)
-
- TestQArgs one\"two three
- call assert_equal(['one\"two three'], g:args)
-
- delcommand TestQArgs
-endfunc
-
-func Test_reg_arg()
- command -nargs=* -reg TestRegArg call SaveCmdArgs("<reg>", "<register>")
-
- TestRegArg
- call assert_equal(['', ''], g:args)
-
- TestRegArg x
- call assert_equal(['x', 'x'], g:args)
-
- delcommand TestRegArg
-endfunc
-
-func Test_no_arg()
- command -nargs=* TestNoArg call SaveCmdArgs("<args>", "<>", "<x>", "<lt>")
-
- TestNoArg
- call assert_equal(['', '<>', '<x>', '<'], g:args)
-
- TestNoArg one
- call assert_equal(['one', '<>', '<x>', '<'], g:args)
-
- delcommand TestNoArg
-endfunc
-
-func Test_range_arg()
- command -range TestRangeArg call SaveCmdArgs(<range>, <line1>, <line2>)
- new
- call setline(1, range(100))
- let lnum = line('.')
-
- TestRangeArg
- call assert_equal([0, lnum, lnum], g:args)
-
- 99TestRangeArg
- call assert_equal([1, 99, 99], g:args)
-
- 88,99TestRangeArg
- call assert_equal([2, 88, 99], g:args)
-
- call assert_fails('102TestRangeArg', 'E16:')
-
- bwipe!
- delcommand TestRangeArg
-endfunc
-
-func Test_Ambiguous()
- command Doit let g:didit = 'yes'
- command Dothat let g:didthat = 'also'
- call assert_fails('Do', 'E464:')
- Doit
- call assert_equal('yes', g:didit)
- Dothat
- call assert_equal('also', g:didthat)
- unlet g:didit
- unlet g:didthat
-
- delcommand Doit
- Do
- call assert_equal('also', g:didthat)
- delcommand Dothat
-
- " Nvim removed the ":Ni!" easter egg in 87e107d92.
- call assert_fails("\x4ei\041", 'E492: Not an editor command: Ni!')
-endfunc
-
-func Test_redefine_on_reload()
- call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists')
- call assert_equal(0, exists(':ExistingCommand'))
- source Xcommandexists
- call assert_equal(2, exists(':ExistingCommand'))
- " Redefining a command when reloading a script is OK.
- source Xcommandexists
- call assert_equal(2, exists(':ExistingCommand'))
-
- " But redefining in another script is not OK.
- call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists2')
- call assert_fails('source Xcommandexists2', 'E174:')
- call delete('Xcommandexists2')
-
- " And defining twice in one script is not OK.
- delcommand ExistingCommand
- call assert_equal(0, exists(':ExistingCommand'))
- call writefile([
- \ 'command ExistingCommand echo "yes"',
- \ 'command ExistingCommand echo "no"',
- \ ], 'Xcommandexists')
- call assert_fails('source Xcommandexists', 'E174:')
- call assert_equal(2, exists(':ExistingCommand'))
-
- call delete('Xcommandexists')
- delcommand ExistingCommand
-endfunc
-
-func Test_CmdUndefined()
- call assert_fails('Doit', 'E492:')
- au CmdUndefined Doit :command Doit let g:didit = 'yes'
- Doit
- call assert_equal('yes', g:didit)
- delcommand Doit
-
- call assert_fails('Dothat', 'E492:')
- au CmdUndefined * let g:didnot = 'yes'
- call assert_fails('Dothat', 'E492:')
- call assert_equal('yes', g:didnot)
-endfunc
-
-func Test_CmdErrors()
- call assert_fails('com! docmd :', 'E183:')
- call assert_fails('com! \<Tab> :', 'E182:')
- call assert_fails('com! _ :', 'E182:')
- call assert_fails('com! - DoCmd :', 'E175:')
- call assert_fails('com! -xxx DoCmd :', 'E181:')
- call assert_fails('com! -addr DoCmd :', 'E179:')
- call assert_fails('com! -addr=asdf DoCmd :', 'E180:')
- call assert_fails('com! -complete DoCmd :', 'E179:')
- call assert_fails('com! -complete=xxx DoCmd :', 'E180:')
- call assert_fails('com! -complete=custom DoCmd :', 'E467:')
- call assert_fails('com! -complete=customlist DoCmd :', 'E467:')
- call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:')
- call assert_fails('com! -complete=file DoCmd :', 'E1208:')
- call assert_fails('com! -nargs=0 -complete=file DoCmd :', 'E1208:')
- call assert_fails('com! -nargs=x DoCmd :', 'E176:')
- call assert_fails('com! -count=1 -count=2 DoCmd :', 'E177:')
- call assert_fails('com! -count=x DoCmd :', 'E178:')
- call assert_fails('com! -range=x DoCmd :', 'E178:')
-
- com! -nargs=0 DoCmd :
- call assert_fails('DoCmd x', 'E488:')
-
- com! -nargs=1 DoCmd :
- call assert_fails('DoCmd', 'E471:')
-
- com! -nargs=+ DoCmd :
- call assert_fails('DoCmd', 'E471:')
-
- call assert_fails('com DoCmd :', 'E174:')
- comclear
- call assert_fails('delcom DoCmd', 'E184:')
-
- " These used to leak memory
- call assert_fails('com! -complete=custom,CustomComplete _ :', 'E182:')
- call assert_fails('com! -complete=custom,CustomComplete docmd :', 'E183:')
- call assert_fails('com! -complete=custom,CustomComplete -xxx DoCmd :', 'E181:')
-endfunc
-
-func CustomComplete(A, L, P)
- return "January\nFebruary\nMars\n"
-endfunc
-
-func CustomCompleteList(A, L, P)
- return [ "Monday", "Tuesday", "Wednesday", {}, v:_null_string]
-endfunc
-
-func Test_CmdCompletion()
- call feedkeys(":com -\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -addr bang bar buffer complete count keepscript nargs range register', @:)
-
- call feedkeys(":com -nargs=0 -\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -nargs=0 -addr bang bar buffer complete count keepscript nargs range register', @:)
-
- call feedkeys(":com -nargs=\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -nargs=* + 0 1 ?', @:)
-
- call feedkeys(":com -addr=\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -addr=arguments buffers lines loaded_buffers other quickfix tabs windows', @:)
-
- call feedkeys(":com -complete=co\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -complete=color command compiler', @:)
-
- " try completion for unsupported argument values
- call feedkeys(":com -newarg=\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"com -newarg=\t", @:)
-
- " command completion after the name in a user defined command
- call feedkeys(":com MyCmd chist\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"com MyCmd chistory", @:)
-
- command! DoCmd1 :
- command! DoCmd2 :
- call feedkeys(":com \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com DoCmd1 DoCmd2', @:)
-
- call feedkeys(":DoC\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd1 DoCmd2', @:)
-
- call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"delcom DoCmd1 DoCmd2', @:)
-
- " try argument completion for a command without completion
- call feedkeys(":DoCmd1 \<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal("\"DoCmd1 \t", @:)
-
- delcom DoCmd1
- call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"delcom DoCmd2', @:)
-
- call feedkeys(":com DoC\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com DoCmd2', @:)
-
- delcom DoCmd2
- call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"delcom DoC', @:)
-
- call feedkeys(":com DoC\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com DoC', @:)
-
- com! -nargs=1 -complete=behave DoCmd :
- call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd mswin xterm', @:)
-
- " Test for file name completion
- com! -nargs=1 -complete=file DoCmd :
- call feedkeys(":DoCmd READM\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd README.txt', @:)
-
- " Test for buffer name completion
- com! -nargs=1 -complete=buffer DoCmd :
- let bnum = bufadd('BufForUserCmd')
- call setbufvar(bnum, '&buflisted', 1)
- call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd BufForUserCmd', @:)
- bwipe BufForUserCmd
- call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd BufFor', @:)
-
- com! -nargs=* -complete=custom,CustomComplete DoCmd :
- call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd January February Mars', @:)
-
- com! -nargs=? -complete=customlist,CustomCompleteList DoCmd :
- call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd Monday Tuesday Wednesday', @:)
-
- com! -nargs=+ -complete=custom,CustomCompleteList DoCmd :
- call assert_fails("call feedkeys(':DoCmd \<C-D>', 'tx')", 'E730:')
-
- com! -nargs=+ -complete=customlist,CustomComp DoCmd :
- call assert_fails("call feedkeys(':DoCmd \<C-D>', 'tx')", 'E117:')
-
- " custom completion without a function
- com! -nargs=? -complete=custom, DoCmd
- call assert_beeps("call feedkeys(':DoCmd \t', 'tx')")
-
- " custom completion failure with the wrong function
- com! -nargs=? -complete=custom,min DoCmd
- call assert_fails("call feedkeys(':DoCmd \t', 'tx')", 'E118:')
-
- " custom completion for a pattern with a backslash
- let g:ArgLead = ''
- func! CustCompl(A, L, P)
- let g:ArgLead = a:A
- return ['one', 'two', 'three']
- endfunc
- com! -nargs=? -complete=customlist,CustCompl DoCmd
- call feedkeys(":DoCmd a\\\t", 'xt')
- call assert_equal('a\', g:ArgLead)
- delfunc CustCompl
-
- delcom DoCmd
-endfunc
-
-func CallExecute(A, L, P)
- " Drop first '\n'
- return execute('echo "hi"')[1:]
-endfunc
-
-func Test_use_execute_in_completion()
- command! -nargs=* -complete=custom,CallExecute DoExec :
- call feedkeys(":DoExec \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoExec hi', @:)
- delcommand DoExec
-endfunc
-
-func Test_addr_all()
- command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2>
- %DoSomething
- call assert_equal(1, g:a1)
- call assert_equal(line('$'), g:a2)
-
- command! -addr=arguments DoSomething let g:a1 = <line1> | let g:a2 = <line2>
- args one two three
- %DoSomething
- call assert_equal(1, g:a1)
- call assert_equal(3, g:a2)
-
- command! -addr=buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
- %DoSomething
- for low in range(1, bufnr('$'))
- if buflisted(low)
- break
- endif
- endfor
- call assert_equal(low, g:a1)
- call assert_equal(bufnr('$'), g:a2)
-
- command! -addr=loaded_buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
- %DoSomething
- for low in range(1, bufnr('$'))
- if bufloaded(low)
- break
- endif
- endfor
- call assert_equal(low, g:a1)
- for up in range(bufnr('$'), 1, -1)
- if bufloaded(up)
- break
- endif
- endfor
- call assert_equal(up, g:a2)
-
- command! -addr=windows DoSomething let g:a1 = <line1> | let g:a2 = <line2>
- new
- %DoSomething
- call assert_equal(1, g:a1)
- call assert_equal(winnr('$'), g:a2)
- bwipe
-
- command! -addr=tabs DoSomething let g:a1 = <line1> | let g:a2 = <line2>
- tabnew
- %DoSomething
- call assert_equal(1, g:a1)
- call assert_equal(len(gettabinfo()), g:a2)
- bwipe
-
- command! -addr=other DoSomething let g:a1 = <line1> | let g:a2 = <line2>
- DoSomething
- call assert_equal(line('.'), g:a1)
- call assert_equal(line('.'), g:a2)
- %DoSomething
- call assert_equal(1, g:a1)
- call assert_equal(line('$'), g:a2)
-
- delcommand DoSomething
-endfunc
-
-func Test_command_list()
- command! DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 :",
- \ execute('command DoCmd'))
-
- " Test with various -range= and -count= argument values.
- command! -range DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . :",
- \ execute('command DoCmd'))
- command! -range=% DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 % :",
- \ execute('command! DoCmd'))
- command! -range=2 DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 2 :",
- \ execute('command DoCmd'))
- command! -count=2 DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 2c ? :",
- \ execute('command DoCmd'))
-
- " Test with various -addr= argument values.
- command! -addr=lines DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . :",
- \ execute('command DoCmd'))
- command! -addr=arguments DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . arg :",
- \ execute('command DoCmd'))
- command! -addr=buffers DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . buf :",
- \ execute('command DoCmd'))
- command! -addr=loaded_buffers DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . load :",
- \ execute('command DoCmd'))
- command! -addr=windows DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . win :",
- \ execute('command DoCmd'))
- command! -addr=tabs DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . tab :",
- \ execute('command DoCmd'))
- command! -addr=other DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 . ? :",
- \ execute('command DoCmd'))
-
- " Test with various -complete= argument values (non-exhaustive list)
- command! -nargs=1 -complete=arglist DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 1 arglist :",
- \ execute('command DoCmd'))
- command! -nargs=* -complete=augroup DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd * augroup :",
- \ execute('command DoCmd'))
- command! -nargs=? -complete=custom,CustomComplete DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd ? custom :",
- \ execute('command DoCmd'))
- command! -nargs=+ -complete=customlist,CustomComplete DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd + customlist :",
- \ execute('command DoCmd'))
-
- " Test with various -narg= argument values.
- command! -nargs=0 DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 :",
- \ execute('command DoCmd'))
- command! -nargs=1 DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 1 :",
- \ execute('command DoCmd'))
- command! -nargs=* DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd * :",
- \ execute('command DoCmd'))
- command! -nargs=? DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd ? :",
- \ execute('command DoCmd'))
- command! -nargs=+ DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd + :",
- \ execute('command DoCmd'))
-
- " Test with other arguments.
- command! -bang DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n! DoCmd 0 :",
- \ execute('command DoCmd'))
- command! -bar DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n| DoCmd 0 :",
- \ execute('command DoCmd'))
- command! -register DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n\" DoCmd 0 :",
- \ execute('command DoCmd'))
- command! -buffer DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\nb DoCmd 0 :"
- \ .. "\n\" DoCmd 0 :",
- \ execute('command DoCmd'))
- comclear
-
- " Test with many args.
- command! -bang -bar -register -buffer -nargs=+ -complete=environment -addr=windows -count=3 DoCmd :
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n!\"b|DoCmd + 3c win environment :",
- \ execute('command DoCmd'))
- comclear
-
- " Test with special characters in command definition.
- command! DoCmd :<cr><tab><c-d>
- call assert_equal("\n Name Args Address Complete Definition"
- \ .. "\n DoCmd 0 :<CR><Tab><C-D>",
- \ execute('command DoCmd'))
-
- " Test output in verbose mode.
- command! DoCmd :
- call assert_match("^\n"
- \ .. " Name Args Address Complete Definition\n"
- \ .. " DoCmd 0 :\n"
- \ .. "\tLast set from .*/test_usercommands.vim line \\d\\+$",
- \ execute('verbose command DoCmd'))
-
- comclear
- call assert_equal("\nNo user-defined commands found", execute(':command Xxx'))
- call assert_equal("\nNo user-defined commands found", execute('command'))
-endfunc
-
-" Test for a custom user completion returning the wrong value type
-func Test_usercmd_custom()
- func T1(a, c, p)
- return "a\nb\n"
- endfunc
- command -nargs=* -complete=customlist,T1 TCmd1
- call feedkeys(":TCmd1 \<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"TCmd1 ', @:)
- delcommand TCmd1
- delfunc T1
-
- func T2(a, c, p)
- return {}
- endfunc
- command -nargs=* -complete=customlist,T2 TCmd2
- call feedkeys(":TCmd2 \<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"TCmd2 ', @:)
- delcommand TCmd2
- delfunc T2
-endfunc
-
-func Test_delcommand_buffer()
- command Global echo 'global'
- command -buffer OneBuffer echo 'one'
- new
- command -buffer TwoBuffer echo 'two'
- call assert_equal(0, exists(':OneBuffer'))
- call assert_equal(2, exists(':Global'))
- call assert_equal(2, exists(':TwoBuffer'))
- delcommand -buffer TwoBuffer
- call assert_equal(0, exists(':TwoBuffer'))
- call assert_fails('delcommand -buffer Global', 'E1237:')
- call assert_fails('delcommand -buffer OneBuffer', 'E1237:')
- bwipe!
- call assert_equal(2, exists(':OneBuffer'))
- delcommand -buffer OneBuffer
- call assert_equal(0, exists(':OneBuffer'))
- call assert_fails('delcommand -buffer Global', 'E1237:')
- delcommand Global
- call assert_equal(0, exists(':Global'))
-endfunc
-
-func DefCmd(name)
- if len(a:name) > 30
- return
- endif
- exe 'command ' .. a:name .. ' call DefCmd("' .. a:name .. 'x")'
- echo a:name
- exe a:name
-endfunc
-
-func Test_recursive_define()
- call DefCmd('Command')
-
- let name = 'Command'
- while len(name) < 30
- exe 'delcommand ' .. name
- let name ..= 'x'
- endwhile
-endfunc
-
-" Test for using buffer-local ambiguous user-defined commands
-func Test_buflocal_ambiguous_usercmd()
- new
- command -buffer -nargs=1 -complete=sign TestCmd1 echo "Hello"
- command -buffer -nargs=1 -complete=sign TestCmd2 echo "World"
-
- call assert_fails("call feedkeys(':TestCmd\<CR>', 'xt')", 'E464:')
- call feedkeys(":TestCmd \<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"TestCmd ', @:)
-
- delcommand TestCmd1
- delcommand TestCmd2
- bw!
-endfunc
-
-" Test for using a multibyte character in a user command
-func Test_multibyte_in_usercmd()
- command SubJapanesePeriodToDot exe "%s/\u3002/./g"
- new
- call setline(1, "Hello\u3002")
- SubJapanesePeriodToDot
- call assert_equal('Hello.', getline(1))
- bw!
- delcommand SubJapanesePeriodToDot
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
deleted file mode 100644
index e5f6d68720..0000000000
--- a/src/nvim/testdir/test_utf8.vim
+++ /dev/null
@@ -1,331 +0,0 @@
-" Tests for Unicode manipulations
-
-source check.vim
-source view_util.vim
-source screendump.vim
-
-" Visual block Insert adjusts for multi-byte char
-func Test_visual_block_insert()
- new
- call setline(1, ["aaa", "ã‚ã‚ã‚", "bbb"])
- exe ":norm! gg0l\<C-V>jjIx\<Esc>"
- call assert_equal(['axaa', ' xã‚ã‚ã‚', 'bxbb'], getline(1, '$'))
- bwipeout!
-endfunc
-
-" Test for built-in functions strchars() and strcharlen()
-func Test_strchars()
- let inp = ["a", "ã‚ã„a", "A\u20dd", "A\u20dd\u20dd", "\u20dd"]
- let exp = [[1, 1, 1], [3, 3, 3], [2, 2, 1], [3, 3, 1], [1, 1, 1]]
- for i in range(len(inp))
- call assert_equal(exp[i][0], strchars(inp[i]))
- call assert_equal(exp[i][1], inp[i]->strchars(0))
- call assert_equal(exp[i][2], strchars(inp[i], 1))
- endfor
-
- let exp = [1, 3, 1, 1, 1]
- for i in range(len(inp))
- call assert_equal(exp[i], inp[i]->strcharlen())
- call assert_equal(exp[i], strcharlen(inp[i]))
- endfor
-
- call assert_fails("let v=strchars('abc', [])", 'E745:')
- call assert_fails("let v=strchars('abc', 2)", 'E1023:')
-endfunc
-
-" Test for customlist completion
-func CustomComplete1(lead, line, pos)
- return ['ã‚', 'ã„']
-endfunc
-
-func CustomComplete2(lead, line, pos)
- return ['ã‚ãŸã—', 'ã‚ãŸã¾', 'ã‚ãŸã‚Šã‚']
-endfunc
-
-func CustomComplete3(lead, line, pos)
- return ['Nã“', 'Nã‚“', 'Nã¶']
-endfunc
-
-func Test_customlist_completion()
- command -nargs=1 -complete=customlist,CustomComplete1 Test1 echo
- call feedkeys(":Test1 \<C-L>\<C-B>\"\<CR>", 'itx')
- call assert_equal('"Test1 ', getreg(':'))
-
- command -nargs=1 -complete=customlist,CustomComplete2 Test2 echo
- call feedkeys(":Test2 \<C-L>\<C-B>\"\<CR>", 'itx')
- call assert_equal('"Test2 ã‚ãŸ', getreg(':'))
-
- command -nargs=1 -complete=customlist,CustomComplete3 Test3 echo
- call feedkeys(":Test3 \<C-L>\<C-B>\"\<CR>", 'itx')
- call assert_equal('"Test3 N', getreg(':'))
-
- call garbagecollect(1)
-endfunc
-
-" Yank one 3 byte character and check the mark columns.
-func Test_getvcol()
- new
- call setline(1, "x\u2500x")
- normal 0lvy
- call assert_equal(2, col("'["))
- call assert_equal(4, col("']"))
- call assert_equal(2, virtcol("'["))
- call assert_equal(2, virtcol("']"))
-endfunc
-
-func Test_screenchar_utf8()
- new
-
- " 1-cell, with composing characters
- call setline(1, ["ABC\u0308"])
- redraw
- call assert_equal([0x0041], screenchars(1, 1))
- call assert_equal([0x0042], 1->screenchars(2))
- call assert_equal([0x0043, 0x0308], screenchars(1, 3))
- call assert_equal("A", screenstring(1, 1))
- call assert_equal("B", screenstring(1, 2))
- call assert_equal("C\u0308", screenstring(1, 3))
-
- " 2-cells, with composing characters
- let text = "\u3042\u3044\u3046\u3099"
- call setline(1, text)
- redraw
- call assert_equal([0x3042], screenchars(1, 1))
- call assert_equal([0], screenchars(1, 2))
- call assert_equal([0x3044], screenchars(1, 3))
- call assert_equal([0], screenchars(1, 4))
- call assert_equal([0x3046, 0x3099], screenchars(1, 5))
-
- call assert_equal("\u3042", screenstring(1, 1))
- call assert_equal("", screenstring(1, 2))
- call assert_equal("\u3044", screenstring(1, 3))
- call assert_equal("", screenstring(1, 4))
- call assert_equal("\u3046\u3099", screenstring(1, 5))
-
- call assert_equal([text . ' '], ScreenLines(1, 8))
-
- bwipe!
-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, l->list2str(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(v:_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)
- call assert_equal([65, 66, 67], str2list("ABC"))
-
- " Try converting a list to a string in latin-1 encoding
- call assert_equal([1, 2, 3], str2list(list2str([1, 2, 3])))
-
- let &encoding = save_encoding
- call assert_equal(l, lres)
- call assert_equal(s, sres)
-endfunc
-
-func Test_setcellwidths()
- call setcellwidths([
- \ [0x1330, 0x1330, 2],
- \ [9999, 10000, 1],
- \ [0x1337, 0x1339, 2],
- \])
-
- call assert_equal(2, strwidth("\u1330"))
- call assert_equal(1, strwidth("\u1336"))
- call assert_equal(2, strwidth("\u1337"))
- call assert_equal(2, strwidth("\u1339"))
- call assert_equal(1, strwidth("\u133a"))
-
- for aw in ['single', 'double']
- exe 'set ambiwidth=' . aw
- " Handle \u0080 to \u009F as control chars even on MS-Windows.
- set isprint=@,161-255
-
- call setcellwidths([])
- " Control chars
- call assert_equal(4, strwidth("\u0081"))
- call assert_equal(6, strwidth("\uFEFF"))
- " Ambiguous width chars
- call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u00A1"))
- call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u2010"))
-
- call setcellwidths([[0x81, 0x81, 1], [0xA1, 0xA1, 1],
- \ [0x2010, 0x2010, 1], [0xFEFF, 0xFEFF, 1]])
- " Control chars
- call assert_equal(4, strwidth("\u0081"))
- call assert_equal(6, strwidth("\uFEFF"))
- " Ambiguous width chars
- call assert_equal(1, strwidth("\u00A1"))
- call assert_equal(1, strwidth("\u2010"))
-
- call setcellwidths([[0x81, 0x81, 2], [0xA1, 0xA1, 2],
- \ [0x2010, 0x2010, 2], [0xFEFF, 0xFEFF, 2]])
- " Control chars
- call assert_equal(4, strwidth("\u0081"))
- call assert_equal(6, strwidth("\uFEFF"))
- " Ambiguous width chars
- call assert_equal(2, strwidth("\u00A1"))
- call assert_equal(2, strwidth("\u2010"))
- endfor
- set ambiwidth& isprint&
-
- call setcellwidths([])
-
- call assert_fails('call setcellwidths(1)', 'E714:')
-
- call assert_fails('call setcellwidths([1, 2, 0])', 'E1109:')
-
- call assert_fails('call setcellwidths([[0x101]])', 'E1110:')
- call assert_fails('call setcellwidths([[0x101, 0x102]])', 'E1110:')
- call assert_fails('call setcellwidths([[0x101, 0x102, 1, 4]])', 'E1110:')
- call assert_fails('call setcellwidths([["a"]])', 'E1110:')
-
- call assert_fails('call setcellwidths([[0x102, 0x101, 1]])', 'E1111:')
-
- call assert_fails('call setcellwidths([[0x101, 0x102, 0]])', 'E1112:')
- call assert_fails('call setcellwidths([[0x101, 0x102, 3]])', 'E1112:')
-
- call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x115, 0x116, 2]])', 'E1113:')
- call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x122, 0x123, 2]])', 'E1113:')
-
- call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:')
-
- set listchars=tab:--\\u2192
- call assert_fails('call setcellwidths([[0x2192, 0x2192, 2]])', 'E834:')
-
- set fillchars=stl:\\u2501
- call assert_fails('call setcellwidths([[0x2501, 0x2501, 2]])', 'E835:')
-
- set listchars&
- set fillchars&
- call setcellwidths([])
-endfunc
-
-func Test_getcellwidths()
- call setcellwidths([])
- call assert_equal([], getcellwidths())
-
- let widthlist = [
- \ [0x1330, 0x1330, 2],
- \ [9999, 10000, 1],
- \ [0x1337, 0x1339, 2],
- \]
- let widthlistsorted = [
- \ [0x1330, 0x1330, 2],
- \ [0x1337, 0x1339, 2],
- \ [9999, 10000, 1],
- \]
- call setcellwidths(widthlist)
- call assert_equal(widthlistsorted, getcellwidths())
-
- call setcellwidths([])
-endfunc
-
-func Test_setcellwidths_dump()
- CheckRunVimInTerminal
-
- let lines =<< trim END
- call setline(1, "\ue5ffDesktop")
- END
- call writefile(lines, 'XCellwidths', 'D')
- let buf = RunVimInTerminal('-S XCellwidths', {'rows': 6})
- call VerifyScreenDump(buf, 'Test_setcellwidths_dump_1', {})
-
- call term_sendkeys(buf, ":call setcellwidths([[0xe5ff, 0xe5ff, 2]])\<CR>")
- call VerifyScreenDump(buf, 'Test_setcellwidths_dump_2', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_print_overlong()
- " Text with more composing characters than MB_MAXBYTES.
- new
- call setline(1, 'axxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
- s/x/\=nr2char(1629)/g
- print
- bwipe!
-endfunc
-
-func Test_recording_with_select_mode_utf8()
- call Run_test_recording_with_select_mode_utf8()
-endfunc
-
-func Run_test_recording_with_select_mode_utf8()
- new
-
- " No escaping
- call feedkeys("qacc12345\<Esc>gH哦\<Esc>q", "tx")
- call assert_equal("哦", getline(1))
- call assert_equal("cc12345\<Esc>gH哦\<Esc>", @a)
- call setline(1, 'asdf')
- normal! @a
- call assert_equal("哦", getline(1))
-
- " 固 is 0xE5 0x9B 0xBA where 0x9B is CSI
- call feedkeys("qacc12345\<Esc>gH固\<Esc>q", "tx")
- call assert_equal("固", getline(1))
- call assert_equal("cc12345\<Esc>gH固\<Esc>", @a)
- call setline(1, 'asdf')
- normal! @a
- call assert_equal("固", getline(1))
-
- " å›› is 0xE5 0x9B 0x9B where 0x9B is CSI
- call feedkeys("qacc12345\<Esc>gHå››\<Esc>q", "tx")
- call assert_equal("å››", getline(1))
- call assert_equal("cc12345\<Esc>gHå››\<Esc>", @a)
- call setline(1, 'asdf')
- normal! @a
- call assert_equal("å››", getline(1))
-
- " 倒 is 0xE5 0x80 0x92 where 0x80 is K_SPECIAL
- call feedkeys("qacc12345\<Esc>gH倒\<Esc>q", "tx")
- call assert_equal("倒", getline(1))
- call assert_equal("cc12345\<Esc>gH倒\<Esc>", @a)
- call setline(1, 'asdf')
- normal! @a
- call assert_equal("倒", getline(1))
-
- bwipe!
-endfunc
-
-" This must be done as one of the last tests, because it starts the GUI, which
-" cannot be undone.
-func Test_zz_recording_with_select_mode_utf8_gui()
- CheckCanRunGui
-
- gui -f
- call Run_test_recording_with_select_mode_utf8()
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim
deleted file mode 100644
index f3c86b44fb..0000000000
--- a/src/nvim/testdir/test_utf8_comparisons.vim
+++ /dev/null
@@ -1,94 +0,0 @@
-" Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c)
-" Also test "g~ap".
-
-func Ch(a, op, b, expected)
- call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected,
- \ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected))
-endfunc
-
-func Chk(a, b, result)
- if a:result == 0
- call Ch(a:a, '==?', a:b, 1)
- call Ch(a:a, '!=?', a:b, 0)
- call Ch(a:a, '<=?', a:b, 1)
- call Ch(a:a, '>=?', a:b, 1)
- call Ch(a:a, '<?', a:b, 0)
- call Ch(a:a, '>?', a:b, 0)
- elseif a:result > 0
- call Ch(a:a, '==?', a:b, 0)
- call Ch(a:a, '!=?', a:b, 1)
- call Ch(a:a, '<=?', a:b, 0)
- call Ch(a:a, '>=?', a:b, 1)
- call Ch(a:a, '<?', a:b, 0)
- call Ch(a:a, '>?', a:b, 1)
- else
- call Ch(a:a, '==?', a:b, 0)
- call Ch(a:a, '!=?', a:b, 1)
- call Ch(a:a, '<=?', a:b, 1)
- call Ch(a:a, '>=?', a:b, 0)
- call Ch(a:a, '<?', a:b, 1)
- call Ch(a:a, '>?', a:b, 0)
- endif
-endfunc
-
-func Check(a, b, result)
- call Chk(a:a, a:b, a:result)
- call Chk(a:b, a:a, -a:result)
-endfunc
-
-func LT(a, b)
- call Check(a:a, a:b, -1)
-endfunc
-
-func GT(a, b)
- call Check(a:a, a:b, 1)
-endfunc
-
-func EQ(a, b)
- call Check(a:a, a:b, 0)
-endfunc
-
-func Test_comparisons()
- call EQ('', '')
- call LT('', 'a')
- call EQ('abc', 'abc')
- call EQ('Abc', 'abC')
- call LT('ab', 'abc')
- call LT('AB', 'abc')
- call LT('ab', 'aBc')
- call EQ('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xb9\xd0\xa6\xd0\xa3\xd0\xba\xd0\x95\xd0\xbd')
- call LT('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xaf\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd')
- call EQ('\xe2\x84\xaa', 'k')
- call LT('\xe2\x84\xaa', 'kkkkkk')
- call EQ('\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa', 'kkk')
- call LT('kk', '\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa')
- call EQ('\xe2\x84\xaa\xe2\x84\xa6k\xe2\x84\xaak\xcf\x89', 'k\xcf\x89\xe2\x84\xaakk\xe2\x84\xa6')
- call EQ('Abc\x80', 'AbC\x80')
- call LT('Abc\x80', 'AbC\x81')
- call LT('Abc', 'AbC\x80')
- call LT('abc\x80DEF', 'abc\x80def') " case folding stops at the first bad character
- call LT('\xc3XYZ', '\xc3xyz')
- call EQ('\xef\xbc\xba', '\xef\xbd\x9a') " FF3A (upper), FF5A (lower)
- call GT('\xef\xbc\xba', '\xef\xbc\xff') " first string is ok and equals \xef\xbd\x9a after folding, second string is illegal and was left unchanged, then the strings were bytewise compared
- call LT('\xc3', '\xc3\x83')
- call EQ('\xc3\xa3xYz', '\xc3\x83XyZ')
- for n in range(0x60, 0xFF)
- call LT(printf('xYz\x%.2X', n-1), printf('XyZ\x%.2X', n))
- endfor
- for n in range(0x80, 0xBF)
- call EQ(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n))
- endfor
- for n in range(0xC0, 0xFF)
- call LT(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n))
- endfor
-endfunc
-
-" test that g~ap changes one paragraph only.
-func Test_gap()
- new
- " setup text
- call feedkeys("iabcd\<cr>\<cr>defg", "tx")
- " modify only first line
- call feedkeys("gg0g~ap", "tx")
- call assert_equal(["ABCD", "", "defg"], getline(1,3))
-endfunc
diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim
deleted file mode 100644
index e12c71d521..0000000000
--- a/src/nvim/testdir/test_vartabs.vim
+++ /dev/null
@@ -1,448 +0,0 @@
-" Test for variable tabstops
-
-source check.vim
-CheckFeature vartabs
-
-source view_util.vim
-
-func s:compare_lines(expect, actual)
- call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
-endfunc
-
-func Test_vartabs()
- new
- %d
-
- " Test normal operation of tabstops ...
- set ts=4
- call setline(1, join(split('aaaaa', '\zs'), "\t"))
- retab 8
- let expect = "a a\<tab>a a\<tab>a"
- call assert_equal(expect, getline(1))
-
- " ... and softtabstops
- set ts=8 sts=6
- exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
- let expect = "b b\<tab> b\<tab> b\<tab>b"
- call assert_equal(expect, getline(1))
-
- " Test variable tabstops.
- set sts=0 vts=4,8,4,8
- exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
- retab 8
- let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c"
- call assert_equal(expect, getline(1))
-
- set et vts=4,8,4,8
- exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
- let expect = "d d d d d d"
- call assert_equal(expect, getline(1))
-
- " Changing ts should have no effect if vts is in use.
- call cursor(1, 1)
- set ts=6
- exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
- let expect = "e e e e e e"
- call assert_equal(expect, getline(1))
-
- " Clearing vts should revert to using ts.
- set vts=
- exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
- let expect = "f f f f f f"
- call assert_equal(expect, getline(1))
-
- " Test variable softtabstops.
- set noet ts=8 vsts=12,2,6
- exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
- let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g"
- call assert_equal(expect, getline(1))
-
- " Variable tabstops and softtabstops combined.
- set vsts=6,12,8 vts=4,6,8
- exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
- let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h"
- call assert_equal(expect, getline(1))
-
- " Retab with a single value, not using vts.
- set ts=8 sts=0 vts= vsts=
- exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
- retab 4
- let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
- call assert_equal(expect, getline(1))
-
- " Retab with a single value, using vts.
- set ts=8 sts=0 vts=6 vsts=
- exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
- retab 4
- let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j"
- call assert_equal(expect, getline(1))
-
- " Retab with multiple values, not using vts.
- set ts=6 sts=0 vts= vsts=
- exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
- retab 4,8
- let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k"
- call assert_equal(expect, getline(1))
-
- " Retab with multiple values, using vts.
- set ts=8 sts=0 vts=6 vsts=
- exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
- retab 4,8
- let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l"
- call assert_equal(expect, getline(1))
-
- " Test for 'retab' with vts
- set ts=8 sts=0 vts=5,3,6,2 vsts=
- exe "norm! S l"
- .retab!
- call assert_equal("\t\t\t\tl", getline(1))
-
- " Test for 'retab' with same values as vts
- set ts=8 sts=0 vts=5,3,6,2 vsts=
- exe "norm! S l"
- .retab! 5,3,6,2
- call assert_equal("\t\t\t\tl", getline(1))
-
- " Check that global and local values are set.
- set ts=4 vts=6 sts=8 vsts=10
- call assert_equal(&ts, 4)
- call assert_equal(&vts, '6')
- call assert_equal(&sts, 8)
- call assert_equal(&vsts, '10')
- new
- call assert_equal(&ts, 4)
- call assert_equal(&vts, '6')
- call assert_equal(&sts, 8)
- call assert_equal(&vsts, '10')
- bwipeout!
-
- " Check that local values only are set.
- setlocal ts=5 vts=7 sts=9 vsts=11
- call assert_equal(&ts, 5)
- call assert_equal(&vts, '7')
- call assert_equal(&sts, 9)
- call assert_equal(&vsts, '11')
- new
- call assert_equal(&ts, 4)
- call assert_equal(&vts, '6')
- call assert_equal(&sts, 8)
- call assert_equal(&vsts, '10')
- bwipeout!
-
- " Check that global values only are set.
- setglobal ts=6 vts=8 sts=10 vsts=12
- call assert_equal(&ts, 5)
- call assert_equal(&vts, '7')
- call assert_equal(&sts, 9)
- call assert_equal(&vsts, '11')
- new
- call assert_equal(&ts, 6)
- call assert_equal(&vts, '8')
- call assert_equal(&sts, 10)
- call assert_equal(&vsts, '12')
- bwipeout!
-
- set ts& vts& sts& vsts& et&
- bwipeout!
-endfunc
-
-func Test_retab_invalid_arg()
- new
- call setline(1, "\ttext")
- retab 0
- call assert_fails("retab -8", 'E487: Argument must be positive')
- call assert_fails("retab 10000", 'E475:')
- call assert_fails("retab 720575940379279360", 'E475:')
- bwipe!
-endfunc
-
-func Test_vartabs_breakindent()
- if !exists("+breakindent")
- return
- endif
- new
- %d
-
- " Test normal operation of tabstops ...
- set ts=4
- call setline(1, join(split('aaaaa', '\zs'), "\t"))
- retab 8
- let expect = "a a\<tab>a a\<tab>a"
- call assert_equal(expect, getline(1))
-
- " ... and softtabstops
- set ts=8 sts=6
- exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
- let expect = "b b\<tab> b\<tab> b\<tab>b"
- call assert_equal(expect, getline(1))
-
- " Test variable tabstops.
- set sts=0 vts=4,8,4,8
- exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
- retab 8
- let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c"
- call assert_equal(expect, getline(1))
-
- set et vts=4,8,4,8
- exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
- let expect = "d d d d d d"
- call assert_equal(expect, getline(1))
-
- " Changing ts should have no effect if vts is in use.
- call cursor(1, 1)
- set ts=6
- exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
- let expect = "e e e e e e"
- call assert_equal(expect, getline(1))
-
- " Clearing vts should revert to using ts.
- set vts=
- exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
- let expect = "f f f f f f"
- call assert_equal(expect, getline(1))
-
- " Test variable softtabstops.
- set noet ts=8 vsts=12,2,6
- exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
- let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g"
- call assert_equal(expect, getline(1))
-
- " Variable tabstops and softtabstops combined.
- set vsts=6,12,8 vts=4,6,8
- exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
- let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h"
- call assert_equal(expect, getline(1))
-
- " Retab with a single value, not using vts.
- set ts=8 sts=0 vts= vsts=
- exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
- retab 4
- let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
- call assert_equal(expect, getline(1))
-
- " Retab with a single value, using vts.
- set ts=8 sts=0 vts=6 vsts=
- exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
- retab 4
- let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j"
- call assert_equal(expect, getline(1))
-
- " Retab with multiple values, not using vts.
- set ts=6 sts=0 vts= vsts=
- exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
- retab 4,8
- let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k"
- call assert_equal(expect, getline(1))
-
- " Retab with multiple values, using vts.
- set ts=8 sts=0 vts=6 vsts=
- exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
- retab 4,8
- let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l"
- call assert_equal(expect, getline(1))
-
- " Check that global and local values are set.
- set ts=4 vts=6 sts=8 vsts=10
- call assert_equal(&ts, 4)
- call assert_equal(&vts, '6')
- call assert_equal(&sts, 8)
- call assert_equal(&vsts, '10')
- new
- call assert_equal(&ts, 4)
- call assert_equal(&vts, '6')
- call assert_equal(&sts, 8)
- call assert_equal(&vsts, '10')
- bwipeout!
-
- " Check that local values only are set.
- setlocal ts=5 vts=7 sts=9 vsts=11
- call assert_equal(&ts, 5)
- call assert_equal(&vts, '7')
- call assert_equal(&sts, 9)
- call assert_equal(&vsts, '11')
- new
- call assert_equal(&ts, 4)
- call assert_equal(&vts, '6')
- call assert_equal(&sts, 8)
- call assert_equal(&vsts, '10')
- bwipeout!
-
- " Check that global values only are set.
- setglobal ts=6 vts=8 sts=10 vsts=12
- call assert_equal(&ts, 5)
- call assert_equal(&vts, '7')
- call assert_equal(&sts, 9)
- call assert_equal(&vsts, '11')
- new
- call assert_equal(&ts, 6)
- call assert_equal(&vts, '8')
- call assert_equal(&sts, 10)
- call assert_equal(&vsts, '12')
- bwipeout!
-
- bwipeout!
-endfunc
-
-func Test_vartabs_linebreak()
- if winwidth(0) < 40
- return
- endif
- new
- 40vnew
- %d
- setl linebreak vartabstop=10,20,30,40
- call setline(1, "\tx\tx\tx\tx")
-
- let expect = [' x ',
- \ 'x x ',
- \ 'x ']
- let lines = ScreenLines([1, 3], winwidth(0))
- call s:compare_lines(expect, lines)
- setl list listchars=tab:>-
- let expect = ['>---------x>------------------ ',
- \ 'x>------------------x>------------------',
- \ 'x ']
- let lines = ScreenLines([1, 3], winwidth(0))
- call s:compare_lines(expect, lines)
- setl linebreak vartabstop=40
- let expect = ['>---------------------------------------',
- \ 'x>--------------------------------------',
- \ 'x>--------------------------------------',
- \ 'x>--------------------------------------',
- \ 'x ']
- let lines = ScreenLines([1, 5], winwidth(0))
- call s:compare_lines(expect, lines)
-
- " cleanup
- bw!
- bw!
- set nolist listchars&vim
-endfunc
-
-func Test_vartabs_shiftwidth()
- "return
- if winwidth(0) < 40
- return
- endif
- new
- 40vnew
- %d
-" setl varsofttabstop=10,20,30,40
- setl shiftwidth=0 vartabstop=10,20,30,40
- call setline(1, "x")
-
- " Check without any change.
- let expect = ['x ']
- let lines = ScreenLines(1, winwidth(0))
- call s:compare_lines(expect, lines)
- " Test 1:
- " shiftwidth depends on the indent, first check with cursor at the end of the
- " line (which is the same as the start of the line, since there is only one
- " character).
- norm! $>>
- let expect1 = [' x ']
- let lines = ScreenLines(1, winwidth(0))
- call s:compare_lines(expect1, lines)
- call assert_equal(10, shiftwidth())
- call assert_equal(10, shiftwidth(1))
- call assert_equal(20, shiftwidth(virtcol('.')))
- norm! $>>
- let expect2 = [' x ', '~ ']
- let lines = ScreenLines([1, 2], winwidth(0))
- call s:compare_lines(expect2, lines)
- call assert_equal(20, shiftwidth(virtcol('.')-2))
- call assert_equal(30, virtcol('.')->shiftwidth())
- norm! $>>
- let expect3 = [' ', ' x ', '~ ']
- let lines = ScreenLines([1, 3], winwidth(0))
- call s:compare_lines(expect3, lines)
- call assert_equal(30, shiftwidth(virtcol('.')-2))
- call assert_equal(40, shiftwidth(virtcol('.')))
- norm! $>>
- let expect4 = [' ', ' ', ' x ']
- let lines = ScreenLines([1, 3], winwidth(0))
- call assert_equal(40, shiftwidth(virtcol('.')))
- call s:compare_lines(expect4, lines)
-
- " Test 2: Put the cursor at the first column, result should be the same
- call setline(1, "x")
- norm! 0>>
- let lines = ScreenLines(1, winwidth(0))
- call s:compare_lines(expect1, lines)
- norm! 0>>
- let lines = ScreenLines([1, 2], winwidth(0))
- call s:compare_lines(expect2, lines)
- norm! 0>>
- let lines = ScreenLines([1, 3], winwidth(0))
- call s:compare_lines(expect3, lines)
- norm! 0>>
- let lines = ScreenLines([1, 3], winwidth(0))
- call s:compare_lines(expect4, lines)
-
- call assert_fails('call shiftwidth([])', 'E745:')
-
- " cleanup
- bw!
- bw!
-endfunc
-
-func Test_vartabs_failures()
- call assert_fails('set vts=8,')
- call assert_fails('set vsts=8,')
- call assert_fails('set vts=8,,8')
- call assert_fails('set vsts=8,,8')
- call assert_fails('set vts=8,,8,')
- call assert_fails('set vsts=8,,8,')
- call assert_fails('set vts=,8')
- call assert_fails('set vsts=,8')
-endfunc
-
-func Test_vartabs_reset()
- set vts=8
- set all&
- call assert_equal('', &vts)
-endfunc
-
-func s:SaveCol(l)
- call add(a:l, [col('.'), virtcol('.')])
- return ''
-endfunc
-
-" Test for 'varsofttabstop'
-func Test_varsofttabstop()
- new
- inoremap <expr> <F2> s:SaveCol(g:cols)
-
- set backspace=indent,eol,start
- set varsofttabstop=6,2,5,3
- let g:cols = []
- call feedkeys("a\t\<F2>\t\<F2>\t\<F2>\t\<F2> ", 'xt')
- call assert_equal("\t\t ", getline(1))
- call assert_equal([[7, 7], [2, 9], [7, 14], [3, 17]], g:cols)
-
- let g:cols = []
- call feedkeys("a\<bs>\<F2>\<bs>\<F2>\<bs>\<F2>\<bs>\<F2>\<bs>\<F2>", 'xt')
- call assert_equal('', getline(1))
- call assert_equal([[3, 17], [7, 14], [2, 9], [7, 7], [1, 1]], g:cols)
-
- set varsofttabstop&
- set backspace&
- iunmap <F2>
- close!
-endfunc
-
-" Setting 'shiftwidth' to a negative value, should set it to either the value
-" of 'tabstop' (if 'vartabstop' is not set) or to the first value in
-" 'vartabstop'
-func Test_shiftwidth_vartabstop()
- throw 'Skipped: Nvim removed this behavior in #6377'
- setlocal tabstop=7 vartabstop=
- call assert_fails('set shiftwidth=-1', 'E487:')
- call assert_equal(7, &shiftwidth)
- setlocal tabstop=7 vartabstop=5,7,10
- call assert_fails('set shiftwidth=-1', 'E487:')
- call assert_equal(5, &shiftwidth)
- setlocal shiftwidth& vartabstop& tabstop&
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_version.vim b/src/nvim/testdir/test_version.vim
deleted file mode 100644
index 5fd38f7cdc..0000000000
--- a/src/nvim/testdir/test_version.vim
+++ /dev/null
@@ -1,26 +0,0 @@
-" Test :version Ex command
-
-so check.vim
-so shared.vim
-
-func Test_version()
- " version should always return the same string.
- let v1 = execute('version')
- let v2 = execute('version')
- call assert_equal(v1, v2)
-
- call assert_match("^\n\nNVIM v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+.*", v1)
-endfunc
-
-func Test_version_redirect()
- CheckNotGui
- CheckCanRunGui
- CheckUnix
-
- call RunVim([], [], '--clean -g --version >Xversion 2>&1')
- call assert_match('Features included', readfile('Xversion')->join())
-
- call delete('Xversion')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_viminfo.vim b/src/nvim/testdir/test_viminfo.vim
deleted file mode 100644
index e792db90ab..0000000000
--- a/src/nvim/testdir/test_viminfo.vim
+++ /dev/null
@@ -1,26 +0,0 @@
-
-" Test for errors in setting 'viminfo'
-func Test_viminfo_option_error()
- " Missing number
- call assert_fails('set viminfo=\"', 'E526:')
- for c in split("'/:<@s", '\zs')
- call assert_fails('set viminfo=' .. c, 'E526:')
- endfor
-
- " Missing comma
- call assert_fails('set viminfo=%10!', 'E527:')
- call assert_fails('set viminfo=!%10', 'E527:')
- call assert_fails('set viminfo=h%10', 'E527:')
- call assert_fails('set viminfo=c%10', 'E527:')
- call assert_fails('set viminfo=:10%10', 'E527:')
-
- " Missing ' setting
- call assert_fails('set viminfo=%10', 'E528:')
-endfunc
-
-func Test_viminfo_oldfiles_newfile()
- let v:oldfiles = v:_null_list
- call assert_equal("\nNo old files", execute('oldfiles'))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
deleted file mode 100644
index b0c4baf7c2..0000000000
--- a/src/nvim/testdir/test_vimscript.vim
+++ /dev/null
@@ -1,7284 +0,0 @@
-" Test various aspects of the Vim script language.
-" Most of this was formerly in test49.vim (developed by Servatius Brandt
-" <Servatius.Brandt@fujitsu-siemens.com>)
-
-source check.vim
-source shared.vim
-source script_util.vim
-
-"-------------------------------------------------------------------------------
-" Test environment {{{1
-"-------------------------------------------------------------------------------
-
-" Append a message to the "messages" file
-func Xout(text)
- split messages
- $put =a:text
- wq
-endfunc
-
-com! -nargs=1 Xout call Xout(<args>)
-
-" Create a new instance of Vim and run the commands in 'test' and then 'verify'
-" The commands in 'test' are expected to store the test results in the Xtest.out
-" file. If the test passes successfully, then Xtest.out should be empty.
-func RunInNewVim(test, verify)
- let init =<< trim END
- set cpo-=C " support line-continuation in sourced script
- source script_util.vim
- XpathINIT
- XloopINIT
- END
- let cleanup =<< trim END
- call writefile(v:errors, 'Xtest.out')
- qall
- END
- call writefile(init, 'Xtest.vim')
- call writefile(a:test, 'Xtest.vim', 'a')
- call writefile(a:verify, 'Xverify.vim')
- call writefile(cleanup, 'Xverify.vim', 'a')
- call RunVim([], [], "-S Xtest.vim -S Xverify.vim")
- call assert_equal([], readfile('Xtest.out'))
- call delete('Xtest.out')
- call delete('Xtest.vim')
- call delete('Xverify.vim')
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 1: :endwhile in function {{{1
-"
-" Detect if a broken loop is (incorrectly) reactivated by the
-" :endwhile. Use a :return to prevent an endless loop, and make
-" this test first to get a meaningful result on an error before other
-" tests will hang.
-"-------------------------------------------------------------------------------
-
-func T1_F()
- Xpath 'a'
- let first = 1
- while 1
- Xpath 'b'
- if first
- Xpath 'c'
- let first = 0
- break
- else
- Xpath 'd'
- return
- endif
- endwhile
-endfunc
-
-func T1_G()
- Xpath 'h'
- let first = 1
- while 1
- Xpath 'i'
- if first
- Xpath 'j'
- let first = 0
- break
- else
- Xpath 'k'
- return
- endif
- if 1 " unmatched :if
- endwhile
-endfunc
-
-func Test_endwhile_function()
- XpathINIT
- call T1_F()
- Xpath 'F'
-
- try
- call T1_G()
- catch
- " Catch missing :endif
- call assert_true(v:exception =~ 'E171')
- Xpath 'x'
- endtry
- Xpath 'G'
-
- call assert_equal('abcFhijxG', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 2: :endwhile in script {{{1
-"
-" Detect if a broken loop is (incorrectly) reactivated by the
-" :endwhile. Use a :finish to prevent an endless loop, and place
-" this test before others that might hang to get a meaningful result
-" on an error.
-"
-" This test executes the bodies of the functions T1_F and T1_G from
-" the previous test as script files (:return replaced by :finish).
-"-------------------------------------------------------------------------------
-
-func Test_endwhile_script()
- XpathINIT
- ExecAsScript T1_F
- Xpath 'F'
- call DeleteTheScript()
-
- try
- ExecAsScript T1_G
- catch
- " Catch missing :endif
- call assert_true(v:exception =~ 'E171')
- Xpath 'x'
- endtry
- Xpath 'G'
- call DeleteTheScript()
-
- call assert_equal('abcFhijxG', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 3: :if, :elseif, :while, :continue, :break {{{1
-"-------------------------------------------------------------------------------
-
-func Test_if_while()
- XpathINIT
- if 1
- Xpath 'a'
- let loops = 3
- while loops > -1 " main loop: loops == 3, 2, 1 (which breaks)
- if loops <= 0
- let break_err = 1
- let loops = -1
- else
- Xpath 'b' . loops
- endif
- if (loops == 2)
- while loops == 2 " dummy loop
- Xpath 'c' . loops
- let loops = loops - 1
- continue " stop dummy loop
- Xpath 'd' . loops
- endwhile
- continue " continue main loop
- Xpath 'e' . loops
- elseif (loops == 1)
- let p = 1
- while p " dummy loop
- Xpath 'f' . loops
- let p = 0
- break " break dummy loop
- Xpath 'g' . loops
- endwhile
- Xpath 'h' . loops
- unlet p
- break " break main loop
- Xpath 'i' . loops
- endif
- if (loops > 0)
- Xpath 'j' . loops
- endif
- while loops == 3 " dummy loop
- let loops = loops - 1
- endwhile " end dummy loop
- endwhile " end main loop
- Xpath 'k'
- else
- Xpath 'l'
- endif
- Xpath 'm'
- if exists("break_err")
- Xpath 'm'
- unlet break_err
- endif
-
- unlet loops
-
- call assert_equal('ab3j3b2c2b1f1h1km', g:Xpath)
-endfunc
-
-" Check double quote after skipped "elseif" does not give error E15
-func Test_skipped_elseif()
- if "foo" ==? "foo"
- let result = "first"
- elseif "foo" ==? "foo"
- let result = "second"
- endif
- call assert_equal('first', result)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 4: :return {{{1
-"-------------------------------------------------------------------------------
-
-func T4_F()
- if 1
- Xpath 'a'
- let loops = 3
- while loops > 0 " 3: 2: 1:
- Xpath 'b' . loops
- if (loops == 2)
- Xpath 'c' . loops
- return
- Xpath 'd' . loops
- endif
- Xpath 'e' . loops
- let loops = loops - 1
- endwhile
- Xpath 'f'
- else
- Xpath 'g'
- endif
-endfunc
-
-func Test_return()
- XpathINIT
- call T4_F()
- Xpath '4'
-
- call assert_equal('ab3e3b2c24', g:Xpath)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 5: :finish {{{1
-"
-" This test executes the body of the function T4_F from the previous
-" test as a script file (:return replaced by :finish).
-"-------------------------------------------------------------------------------
-
-func Test_finish()
- XpathINIT
- ExecAsScript T4_F
- Xpath '5'
- call DeleteTheScript()
-
- call assert_equal('ab3e3b2c25', g:Xpath)
-endfunc
-
-
-
-"-------------------------------------------------------------------------------
-" Test 6: Defining functions in :while loops {{{1
-"
-" Functions can be defined inside other functions. An inner function
-" gets defined when the outer function is executed. Functions may
-" also be defined inside while loops. Expressions in braces for
-" defining the function name are allowed.
-"
-" The functions are defined when sourcing the script, only the
-" resulting path is checked in the test function.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-" The command CALL collects the argument of all its invocations in "calls"
-" when used from a function (that is, when the global variable "calls" needs
-" the "g:" prefix). This is to check that the function code is skipped when
-" the function is defined. For inner functions, do so only if the outer
-" function is not being executed.
-"
-let calls = ""
-com! -nargs=1 CALL
- \ if !exists("calls") && !exists("outer") |
- \ let g:calls = g:calls . <args> |
- \ endif
-
-let i = 0
-while i < 3
- let i = i + 1
- if i == 1
- Xpath 'a'
- function! F1(arg)
- CALL a:arg
- let outer = 1
-
- let j = 0
- while j < 1
- Xpath 'b'
- let j = j + 1
- function! G1(arg)
- CALL a:arg
- endfunction
- Xpath 'c'
- endwhile
- endfunction
- Xpath 'd'
-
- continue
- endif
-
- Xpath 'e' . i
- function! F{i}(i, arg)
- CALL a:arg
- let outer = 1
-
- if a:i == 3
- Xpath 'f'
- endif
- let k = 0
- while k < 3
- Xpath 'g' . k
- let k = k + 1
- function! G{a:i}{k}(arg)
- CALL a:arg
- endfunction
- Xpath 'h' . k
- endwhile
- endfunction
- Xpath 'i'
-
-endwhile
-
-if exists("*G1")
- Xpath 'j'
-endif
-if exists("*F1")
- call F1("F1")
- if exists("*G1")
- call G1("G1")
- endif
-endif
-
-if exists("G21") || exists("G22") || exists("G23")
- Xpath 'k'
-endif
-if exists("*F2")
- call F2(2, "F2")
- if exists("*G21")
- call G21("G21")
- endif
- if exists("*G22")
- call G22("G22")
- endif
- if exists("*G23")
- call G23("G23")
- endif
-endif
-
-if exists("G31") || exists("G32") || exists("G33")
- Xpath 'l'
-endif
-if exists("*F3")
- call F3(3, "F3")
- if exists("*G31")
- call G31("G31")
- endif
- if exists("*G32")
- call G32("G32")
- endif
- if exists("*G33")
- call G33("G33")
- endif
-endif
-
-Xpath 'm'
-
-let g:test6_result = g:Xpath
-let g:test6_calls = calls
-
-unlet calls
-delfunction F1
-delfunction G1
-delfunction F2
-delfunction G21
-delfunction G22
-delfunction G23
-delfunction G31
-delfunction G32
-delfunction G33
-
-func Test_defining_functions()
- call assert_equal('ade2ie3ibcg0h1g1h2g2h3fg0h1g1h2g2h3m', g:test6_result)
- call assert_equal('F1G1F2G21G22G23F3G31G32G33', g:test6_calls)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 7: Continuing on errors outside functions {{{1
-"
-" On an error outside a function, the script processing continues
-" at the line following the outermost :endif or :endwhile. When not
-" inside an :if or :while, the script processing continues at the next
-" line.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-if 1
- Xpath 'a'
- while 1
- Xpath 'b'
- asdf
- Xpath 'c'
- break
- endwhile | Xpath 'd'
- Xpath 'e'
-endif | Xpath 'f'
-Xpath 'g'
-
-while 1
- Xpath 'h'
- if 1
- Xpath 'i'
- asdf
- Xpath 'j'
- endif | Xpath 'k'
- Xpath 'l'
- break
-endwhile | Xpath 'm'
-Xpath 'n'
-
-asdf
-Xpath 'o'
-
-asdf | Xpath 'p'
-Xpath 'q'
-
-let g:test7_result = g:Xpath
-
-func Test_error_in_script()
- call assert_equal('abghinoq', g:test7_result)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 8: Aborting and continuing on errors inside functions {{{1
-"
-" On an error inside a function without the "abort" attribute, the
-" script processing continues at the next line (unless the error was
-" in a :return command). On an error inside a function with the
-" "abort" attribute, the function is aborted and the script processing
-" continues after the function call; the value -1 is returned then.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-func T8_F()
- if 1
- Xpath 'a'
- while 1
- Xpath 'b'
- asdf
- Xpath 'c'
- asdf | Xpath 'd'
- Xpath 'e'
- break
- endwhile
- Xpath 'f'
- endif | Xpath 'g'
- Xpath 'h'
-
- while 1
- Xpath 'i'
- if 1
- Xpath 'j'
- asdf
- Xpath 'k'
- asdf | Xpath 'l'
- Xpath 'm'
- endif
- Xpath 'n'
- break
- endwhile | Xpath 'o'
- Xpath 'p'
-
- return novar " returns (default return value 0)
- Xpath 'q'
- return 1 " not reached
-endfunc
-
-func T8_G() abort
- if 1
- Xpath 'r'
- while 1
- Xpath 's'
- asdf " returns -1
- Xpath 't'
- break
- endwhile
- Xpath 'v'
- endif | Xpath 'w'
- Xpath 'x'
-
- return -4 " not reached
-endfunc
-
-func T8_H() abort
- while 1
- Xpath 'A'
- if 1
- Xpath 'B'
- asdf " returns -1
- Xpath 'C'
- endif
- Xpath 'D'
- break
- endwhile | Xpath 'E'
- Xpath 'F'
-
- return -4 " not reached
-endfunc
-
-" Aborted functions (T8_G and T8_H) return -1.
-let g:test8_sum = (T8_F() + 1) - 4 * T8_G() - 8 * T8_H()
-Xpath 'X'
-let g:test8_result = g:Xpath
-
-func Test_error_in_function()
- call assert_equal(13, g:test8_sum)
- call assert_equal('abcefghijkmnoprsABX', g:test8_result)
-
- delfunction T8_F
- delfunction T8_G
- delfunction T8_H
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 9: Continuing after aborted functions {{{1
-"
-" When a function with the "abort" attribute is aborted due to an
-" error, the next function back in the call hierarchy without an
-" "abort" attribute continues; the value -1 is returned then.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-func F() abort
- Xpath 'a'
- let result = G() " not aborted
- Xpath 'b'
- if result != 2
- Xpath 'c'
- endif
- return 1
-endfunc
-
-func G() " no abort attribute
- Xpath 'd'
- if H() != -1 " aborted
- Xpath 'e'
- endif
- Xpath 'f'
- return 2
-endfunc
-
-func H() abort
- Xpath 'g'
- call I() " aborted
- Xpath 'h'
- return 4
-endfunc
-
-func I() abort
- Xpath 'i'
- asdf " error
- Xpath 'j'
- return 8
-endfunc
-
-if F() != 1
- Xpath 'k'
-endif
-
-let g:test9_result = g:Xpath
-
-delfunction F
-delfunction G
-delfunction H
-delfunction I
-
-func Test_func_abort()
- call assert_equal('adgifb', g:test9_result)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 10: :if, :elseif, :while argument parsing {{{1
-"
-" A '"' or '|' in an argument expression must not be mixed up with
-" a comment or a next command after a bar. Parsing errors should
-" be recognized.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-func MSG(enr, emsg)
- let english = v:lang == "C" || v:lang =~ '^[Ee]n'
- if a:enr == ""
- Xout "TODO: Add message number for:" a:emsg
- let v:errmsg = ":" . v:errmsg
- endif
- let match = 1
- if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg)
- let match = 0
- if v:errmsg == ""
- Xout "Message missing."
- else
- let v:errmsg = v:errmsg->escape('"')
- Xout "Unexpected message:" v:errmsg
- endif
- endif
- return match
-endfunc
-
-if 1 || strlen("\"") | Xpath 'a'
- Xpath 'b'
-endif
-Xpath 'c'
-
-if 0
-elseif 1 || strlen("\"") | Xpath 'd'
- Xpath 'e'
-endif
-Xpath 'f'
-
-while 1 || strlen("\"") | Xpath 'g'
- Xpath 'h'
- break
-endwhile
-Xpath 'i'
-
-let v:errmsg = ""
-if 1 ||| strlen("\"") | Xpath 'j'
- Xpath 'k'
-endif
-Xpath 'l'
-if !MSG('E15', "Invalid expression")
- Xpath 'm'
-endif
-
-let v:errmsg = ""
-if 0
-elseif 1 ||| strlen("\"") | Xpath 'n'
- Xpath 'o'
-endif
-Xpath 'p'
-if !MSG('E15', "Invalid expression")
- Xpath 'q'
-endif
-
-let v:errmsg = ""
-while 1 ||| strlen("\"") | Xpath 'r'
- Xpath 's'
- break
-endwhile
-Xpath 't'
-if !MSG('E15', "Invalid expression")
- Xpath 'u'
-endif
-
-let g:test10_result = g:Xpath
-delfunction MSG
-
-func Test_expr_parsing()
- call assert_equal('abcdefghilpt', g:test10_result)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 11: :if, :elseif, :while argument evaluation after abort {{{1
-"
-" When code is skipped over due to an error, the boolean argument to
-" an :if, :elseif, or :while must not be evaluated.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-let calls = 0
-
-func P(num)
- let g:calls = g:calls + a:num " side effect on call
- return 0
-endfunc
-
-if 1
- Xpath 'a'
- asdf " error
- Xpath 'b'
- if P(1) " should not be called
- Xpath 'c'
- elseif !P(2) " should not be called
- Xpath 'd'
- else
- Xpath 'e'
- endif
- Xpath 'f'
- while P(4) " should not be called
- Xpath 'g'
- endwhile
- Xpath 'h'
-endif
-Xpath 'x'
-
-let g:test11_calls = calls
-let g:test11_result = g:Xpath
-
-unlet calls
-delfunction P
-
-func Test_arg_abort()
- call assert_equal(0, g:test11_calls)
- call assert_equal('ax', g:test11_result)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 12: Expressions in braces in skipped code {{{1
-"
-" In code skipped over due to an error or inactive conditional,
-" an expression in braces as part of a variable or function name
-" should not be evaluated.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-func NULL()
- Xpath 'a'
- return 0
-endfunc
-
-func ZERO()
- Xpath 'b'
- return 0
-endfunc
-
-func! F0()
- Xpath 'c'
-endfunc
-
-func! F1(arg)
- Xpath 'e'
-endfunc
-
-let V0 = 1
-
-Xpath 'f'
-echo 0 ? F{NULL() + V{ZERO()}}() : 1
-
-Xpath 'g'
-if 0
- Xpath 'h'
- call F{NULL() + V{ZERO()}}()
-endif
-
-Xpath 'i'
-if 1
- asdf " error
- Xpath 'j'
- call F1(F{NULL() + V{ZERO()}}())
-endif
-
-Xpath 'k'
-if 1
- asdf " error
- Xpath 'l'
- call F{NULL() + V{ZERO()}}()
-endif
-
-let g:test12_result = g:Xpath
-
-func Test_braces_skipped()
- call assert_equal('fgik', g:test12_result)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 13: Failure in argument evaluation for :while {{{1
-"
-" A failure in the expression evaluation for the condition of a :while
-" causes the whole :while loop until the matching :endwhile being
-" ignored. Continuation is at the next following line.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-Xpath 'a'
-while asdf
- Xpath 'b'
- while 1
- Xpath 'c'
- break
- endwhile
- Xpath 'd'
- break
-endwhile
-Xpath 'e'
-
-while asdf | Xpath 'f' | endwhile | Xpath 'g'
-Xpath 'h'
-let g:test13_result = g:Xpath
-
-func Test_while_fail()
- call assert_equal('aeh', g:test13_result)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 14: Failure in argument evaluation for :if {{{1
-"
-" A failure in the expression evaluation for the condition of an :if
-" does not cause the corresponding :else or :endif being matched to
-" a previous :if/:elseif. Neither of both branches of the failed :if
-" are executed.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-function! F()
- Xpath 'a'
- let x = 0
- if x " false
- Xpath 'b'
- elseif !x " always true
- Xpath 'c'
- let x = 1
- if g:boolvar " possibly undefined
- Xpath 'd'
- else
- Xpath 'e'
- endif
- Xpath 'f'
- elseif x " never executed
- Xpath 'g'
- endif
- Xpath 'h'
-endfunction
-
-let boolvar = 1
-call F()
-Xpath '-'
-
-unlet boolvar
-call F()
-let g:test14_result = g:Xpath
-
-delfunction F
-
-func Test_if_fail()
- call assert_equal('acdfh-acfh', g:test14_result)
-endfunc
-
-
-"-------------------------------------------------------------------------------
-" Test 15: Failure in argument evaluation for :if (bar) {{{1
-"
-" Like previous test, except that the failing :if ... | ... | :endif
-" is in a single line.
-"-------------------------------------------------------------------------------
-
-XpathINIT
-
-function! F()
- Xpath 'a'
- let x = 0
- if x " false
- Xpath 'b'
- elseif !x " always true
- Xpath 'c'
- let x = 1
- if g:boolvar | Xpath 'd' | else | Xpath 'e' | endif
- Xpath 'f'
- elseif x " never executed
- Xpath 'g'
- endif
- Xpath 'h'
-endfunction
-
-let boolvar = 1
-call F()
-Xpath '-'
-
-unlet boolvar
-call F()
-let g:test15_result = g:Xpath
-
-delfunction F
-
-func Test_if_bar_fail()
- call assert_equal('acdfh-acfh', g:test15_result)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 16: Double :else or :elseif after :else {{{1
-"
-" Multiple :elses or an :elseif after an :else are forbidden.
-"-------------------------------------------------------------------------------
-
-func T16_F() abort
- if 0
- Xpath 'a'
- else
- Xpath 'b'
- else " aborts function
- Xpath 'c'
- endif
- Xpath 'd'
-endfunc
-
-func T16_G() abort
- if 0
- Xpath 'a'
- else
- Xpath 'b'
- elseif 1 " aborts function
- Xpath 'c'
- else
- Xpath 'd'
- endif
- Xpath 'e'
-endfunc
-
-func T16_H() abort
- if 0
- Xpath 'a'
- elseif 0
- Xpath 'b'
- else
- Xpath 'c'
- else " aborts function
- Xpath 'd'
- endif
- Xpath 'e'
-endfunc
-
-func T16_I() abort
- if 0
- Xpath 'a'
- elseif 0
- Xpath 'b'
- else
- Xpath 'c'
- elseif 1 " aborts function
- Xpath 'd'
- else
- Xpath 'e'
- endif
- Xpath 'f'
-endfunc
-
-func Test_Multi_Else()
- XpathINIT
- try
- call T16_F()
- catch /E583:/
- Xpath 'e'
- endtry
- call assert_equal('be', g:Xpath)
-
- XpathINIT
- try
- call T16_G()
- catch /E584:/
- Xpath 'f'
- endtry
- call assert_equal('bf', g:Xpath)
-
- XpathINIT
- try
- call T16_H()
- catch /E583:/
- Xpath 'f'
- endtry
- call assert_equal('cf', g:Xpath)
-
- XpathINIT
- try
- call T16_I()
- catch /E584:/
- Xpath 'g'
- endtry
- call assert_equal('cg', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 17: Nesting of unmatched :if or :endif inside a :while {{{1
-"
-" The :while/:endwhile takes precedence in nesting over an unclosed
-" :if or an unopened :endif.
-"-------------------------------------------------------------------------------
-
-" While loops inside a function are continued on error.
-func T17_F()
- let loops = 3
- while loops > 0
- let loops -= 1
- Xpath 'a' . loops
- if (loops == 1)
- Xpath 'b' . loops
- continue
- elseif (loops == 0)
- Xpath 'c' . loops
- break
- elseif 1
- Xpath 'd' . loops
- " endif missing!
- endwhile " :endwhile after :if 1
- Xpath 'e'
-endfunc
-
-func T17_G()
- let loops = 2
- while loops > 0
- let loops -= 1
- Xpath 'a' . loops
- if 0
- Xpath 'b' . loops
- " endif missing
- endwhile " :endwhile after :if 0
-endfunc
-
-func T17_H()
- let loops = 2
- while loops > 0
- let loops -= 1
- Xpath 'a' . loops
- " if missing!
- endif " :endif without :if in while
- Xpath 'b' . loops
- endwhile
-endfunc
-
-" Error continuation outside a function is at the outermost :endwhile or :endif.
-XpathINIT
-let v:errmsg = ''
-let loops = 2
-while loops > 0
- let loops -= 1
- Xpath 'a' . loops
- if 0
- Xpath 'b' . loops
- " endif missing! Following :endwhile fails.
-endwhile | Xpath 'c'
-Xpath 'd'
-call assert_match('E171:', v:errmsg)
-call assert_equal('a1d', g:Xpath)
-
-func Test_unmatched_if_in_while()
- XpathINIT
- call assert_fails('call T17_F()', 'E171:')
- call assert_equal('a2d2a1b1a0c0e', g:Xpath)
-
- XpathINIT
- call assert_fails('call T17_G()', 'E171:')
- call assert_equal('a1a0', g:Xpath)
-
- XpathINIT
- call assert_fails('call T17_H()', 'E580:')
- call assert_equal('a1b1a0b0', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 18: Interrupt (Ctrl-C pressed) {{{1
-"
-" On an interrupt, the script processing is terminated immediately.
-"-------------------------------------------------------------------------------
-
-func Test_interrupt_while_if()
- let test =<< trim [CODE]
- try
- if 1
- Xpath 'a'
- while 1
- Xpath 'b'
- if 1
- Xpath 'c'
- call interrupt()
- call assert_report('should not get here')
- break
- finish
- endif | call assert_report('should not get here')
- call assert_report('should not get here')
- endwhile | call assert_report('should not get here')
- call assert_report('should not get here')
- endif | call assert_report('should not get here')
- call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'd'
- endtry | Xpath 'e'
- Xpath 'f'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdef', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_interrupt_try()
- let test =<< trim [CODE]
- try
- try
- Xpath 'a'
- call interrupt()
- call assert_report('should not get here')
- endtry | call assert_report('should not get here')
- call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'b'
- endtry | Xpath 'c'
- Xpath 'd'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcd', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_interrupt_func_while_if()
- let test =<< trim [CODE]
- func F()
- if 1
- Xpath 'a'
- while 1
- Xpath 'b'
- if 1
- Xpath 'c'
- call interrupt()
- call assert_report('should not get here')
- break
- return
- endif | call assert_report('should not get here')
- call assert_report('should not get here')
- endwhile | call assert_report('should not get here')
- call assert_report('should not get here')
- endif | call assert_report('should not get here')
- call assert_report('should not get here')
- endfunc
-
- Xpath 'd'
- try
- call F() | call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'e'
- endtry | Xpath 'f'
- Xpath 'g'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('dabcefg', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_interrupt_func_try()
- let test =<< trim [CODE]
- func G()
- try
- Xpath 'a'
- call interrupt()
- call assert_report('should not get here')
- endtry | call assert_report('should not get here')
- call assert_report('should not get here')
- endfunc
-
- Xpath 'b'
- try
- call G() | call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'c'
- endtry | Xpath 'd'
- Xpath 'e'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('bacde', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 19: Aborting on errors inside :try/:endtry {{{1
-"
-" An error in a command dynamically enclosed in a :try/:endtry region
-" aborts script processing immediately. It does not matter whether
-" the failing command is outside or inside a function and whether a
-" function has an "abort" attribute.
-"-------------------------------------------------------------------------------
-
-func Test_try_error_abort_1()
- let test =<< trim [CODE]
- func F() abort
- Xpath 'a'
- asdf
- call assert_report('should not get here')
- endfunc
-
- try
- Xpath 'b'
- call F()
- call assert_report('should not get here')
- endtry | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('ba', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_try_error_abort_2()
- let test =<< trim [CODE]
- func G()
- Xpath 'a'
- asdf
- call assert_report('should not get here')
- endfunc
-
- try
- Xpath 'b'
- call G()
- call assert_report('should not get here')
- endtry | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('ba', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_try_error_abort_3()
- let test =<< trim [CODE]
- try
- Xpath 'a'
- asdf
- call assert_report('should not get here')
- endtry | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_try_error_abort_4()
- let test =<< trim [CODE]
- if 1
- try
- Xpath 'a'
- asdf
- call assert_report('should not get here')
- endtry | call assert_report('should not get here')
- endif | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_try_error_abort_5()
- let test =<< trim [CODE]
- let p = 1
- while p
- let p = 0
- try
- Xpath 'a'
- asdf
- call assert_report('should not get here')
- endtry | call assert_report('should not get here')
- endwhile | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_try_error_abort_6()
- let test =<< trim [CODE]
- let p = 1
- Xpath 'a'
- while p
- Xpath 'b'
- let p = 0
- try
- Xpath 'c'
- endwhile | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 20: Aborting on errors after :try/:endtry {{{1
-"
-" When an error occurs after the last active :try/:endtry region has
-" been left, termination behavior is as if no :try/:endtry has been
-" seen.
-"-------------------------------------------------------------------------------
-
-func Test_error_after_try_1()
- let test =<< trim [CODE]
- let p = 1
- while p
- let p = 0
- Xpath 'a'
- try
- Xpath 'b'
- endtry
- asdf
- call assert_report('should not get here')
- endwhile | call assert_report('should not get here')
- Xpath 'c'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_error_after_try_2()
- let test =<< trim [CODE]
- while 1
- try
- Xpath 'a'
- break
- call assert_report('should not get here')
- endtry
- endwhile
- Xpath 'b'
- asdf
- Xpath 'c'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_error_after_try_3()
- let test =<< trim [CODE]
- while 1
- try
- Xpath 'a'
- break
- call assert_report('should not get here')
- finally
- Xpath 'b'
- endtry
- endwhile
- Xpath 'c'
- asdf
- Xpath 'd'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcd', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_error_after_try_4()
- let test =<< trim [CODE]
- while 1
- try
- Xpath 'a'
- finally
- Xpath 'b'
- break
- call assert_report('should not get here')
- endtry
- endwhile
- Xpath 'c'
- asdf
- Xpath 'd'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcd', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_error_after_try_5()
- let test =<< trim [CODE]
- let p = 1
- while p
- let p = 0
- try
- Xpath 'a'
- continue
- call assert_report('should not get here')
- endtry
- endwhile
- Xpath 'b'
- asdf
- Xpath 'c'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_error_after_try_6()
- let test =<< trim [CODE]
- let p = 1
- while p
- let p = 0
- try
- Xpath 'a'
- continue
- call assert_report('should not get here')
- finally
- Xpath 'b'
- endtry
- endwhile
- Xpath 'c'
- asdf
- Xpath 'd'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcd', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_error_after_try_7()
- let test =<< trim [CODE]
- let p = 1
- while p
- let p = 0
- try
- Xpath 'a'
- finally
- Xpath 'b'
- continue
- call assert_report('should not get here')
- endtry
- endwhile
- Xpath 'c'
- asdf
- Xpath 'd'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcd', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1
-"
-" If a :try conditional stays inactive due to a preceding :continue,
-" :break, :return, or :finish, its :finally clause should not be
-" executed.
-"-------------------------------------------------------------------------------
-
-func Test_finally_after_loop_ctrl_statement()
- let test =<< trim [CODE]
- func F()
- let loops = 2
- while loops > 0
- XloopNEXT
- let loops = loops - 1
- try
- if loops == 1
- Xloop 'a'
- continue
- call assert_report('should not get here')
- elseif loops == 0
- Xloop 'b'
- break
- call assert_report('should not get here')
- endif
-
- try " inactive
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- finally
- Xloop 'c'
- endtry
- call assert_report('should not get here')
- endwhile
-
- try
- Xpath 'd'
- return
- call assert_report('should not get here')
- try " inactive
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- finally
- Xpath 'e'
- endtry
- call assert_report('should not get here')
- endfunc
-
- try
- Xpath 'f'
- call F()
- Xpath 'g'
- finish
- call assert_report('should not get here')
- try " inactive
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- finally
- Xpath 'h'
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('fa2c2b3c3degh', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 22: :finally for a :try after an error/interrupt/:throw {{{1
-"
-" If a :try conditional stays inactive due to a preceding error or
-" interrupt or :throw, its :finally clause should not be executed.
-"-------------------------------------------------------------------------------
-
-func Test_finally_after_error_in_func()
- let test =<< trim [CODE]
- func Error()
- try
- Xpath 'b'
- asdf " aborting error, triggering error exception
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endfunc
-
- Xpath 'a'
- call Error()
- call assert_report('should not get here')
-
- if 1 " not active due to error
- try " not active since :if inactive
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- endif
-
- try " not active due to error
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('ab', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_finally_after_interrupt()
- let test =<< trim [CODE]
- func Interrupt()
- try
- Xpath 'a'
- call interrupt() " triggering interrupt exception
- call assert_report('should not get here')
- endtry
- endfunc
-
- Xpath 'b'
- try
- call Interrupt()
- catch /^Vim:Interrupt$/
- Xpath 'c'
- finish
- endtry
- call assert_report('should not get here')
-
- if 1 " not active due to interrupt
- try " not active since :if inactive
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- endif
-
- try " not active due to interrupt
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('bac', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_finally_after_throw()
- let test =<< trim [CODE]
- func Throw()
- Xpath 'a'
- throw 'xyz'
- endfunc
-
- Xpath 'b'
- call Throw()
- call assert_report('should not get here')
-
- if 1 " not active due to :throw
- try " not active since :if inactive
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- endif
-
- try " not active due to :throw
- call assert_report('should not get here')
- finally
- call assert_report('should not get here')
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('ba', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 23: :catch clauses for a :try after a :throw {{{1
-"
-" If a :try conditional stays inactive due to a preceding :throw,
-" none of its :catch clauses should be executed.
-"-------------------------------------------------------------------------------
-
-func Test_catch_after_throw()
- let test =<< trim [CODE]
- try
- Xpath 'a'
- throw "xyz"
- call assert_report('should not get here')
-
- if 1 " not active due to :throw
- try " not active since :if inactive
- call assert_report('should not get here')
- catch /xyz/
- call assert_report('should not get here')
- endtry
- endif
- catch /xyz/
- Xpath 'b'
- endtry
-
- Xpath 'c'
- throw "abc"
- call assert_report('should not get here')
-
- try " not active due to :throw
- call assert_report('should not get here')
- catch /abc/
- call assert_report('should not get here')
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 24: :endtry for a :try after a :throw {{{1
-"
-" If a :try conditional stays inactive due to a preceding :throw,
-" its :endtry should not rethrow the exception to the next surrounding
-" active :try conditional.
-"-------------------------------------------------------------------------------
-
-func Test_endtry_after_throw()
- let test =<< trim [CODE]
- try " try 1
- try " try 2
- Xpath 'a'
- throw "xyz" " makes try 2 inactive
- call assert_report('should not get here')
-
- try " try 3
- call assert_report('should not get here')
- endtry " no rethrow to try 1
- catch /xyz/ " should catch although try 2 inactive
- Xpath 'b'
- endtry
- catch /xyz/ " try 1 active, but exception already caught
- call assert_report('should not get here')
- endtry
- Xpath 'c'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 27: Executing :finally clauses after :return {{{1
-"
-" For a :return command dynamically enclosed in a :try/:endtry region,
-" :finally clauses are executed and the called function is ended.
-"-------------------------------------------------------------------------------
-
-func T27_F()
- try
- Xpath 'a'
- try
- Xpath 'b'
- return
- call assert_report('should not get here')
- finally
- Xpath 'c'
- endtry
- Xpath 'd'
- finally
- Xpath 'e'
- endtry
- call assert_report('should not get here')
-endfunc
-
-func T27_G()
- try
- Xpath 'f'
- return
- call assert_report('should not get here')
- finally
- Xpath 'g'
- call T27_F()
- Xpath 'h'
- endtry
- call assert_report('should not get here')
-endfunc
-
-func T27_H()
- try
- Xpath 'i'
- call T27_G()
- Xpath 'j'
- finally
- Xpath 'k'
- return
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
-endfunction
-
-func Test_finally_after_return()
- XpathINIT
- try
- Xpath 'l'
- call T27_H()
- Xpath 'm'
- finally
- Xpath 'n'
- endtry
- call assert_equal('lifgabcehjkmn', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 28: Executing :finally clauses after :finish {{{1
-"
-" For a :finish command dynamically enclosed in a :try/:endtry region,
-" :finally clauses are executed and the sourced file is finished.
-"
-" This test executes the bodies of the functions F, G, and H from the
-" previous test as script files (:return replaced by :finish).
-"-------------------------------------------------------------------------------
-
-func Test_finally_after_finish()
- XpathINIT
-
- let scriptF = MakeScript("T27_F")
- let scriptG = MakeScript("T27_G", scriptF)
- let scriptH = MakeScript("T27_H", scriptG)
-
- try
- Xpath 'A'
- exec "source" scriptH
- Xpath 'B'
- finally
- Xpath 'C'
- endtry
- Xpath 'D'
- call assert_equal('AifgabcehjkBCD', g:Xpath)
- call delete(scriptF)
- call delete(scriptG)
- call delete(scriptH)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 29: Executing :finally clauses on errors {{{1
-"
-" After an error in a command dynamically enclosed in a :try/:endtry
-" region, :finally clauses are executed and the script processing is
-" terminated.
-"-------------------------------------------------------------------------------
-
-func Test_finally_after_error_1()
- let test =<< trim [CODE]
- func F()
- while 1
- try
- Xpath 'a'
- while 1
- try
- Xpath 'b'
- asdf " error
- call assert_report('should not get here')
- finally
- Xpath 'c'
- endtry | call assert_report('should not get here')
- call assert_report('should not get here')
- break
- endwhile
- call assert_report('should not get here')
- finally
- Xpath 'd'
- endtry | call assert_report('should not get here')
- call assert_report('should not get here')
- break
- endwhile
- call assert_report('should not get here')
- endfunc
-
- while 1
- try
- Xpath 'e'
- while 1
- call F()
- call assert_report('should not get here')
- break
- endwhile | call assert_report('should not get here')
- call assert_report('should not get here')
- finally
- Xpath 'f'
- endtry | call assert_report('should not get here')
- endwhile | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('eabcdf', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_finally_after_error_2()
- let test =<< trim [CODE]
- func G() abort
- if 1
- try
- Xpath 'a'
- asdf " error
- call assert_report('should not get here')
- finally
- Xpath 'b'
- endtry | Xpath 'c'
- endif | Xpath 'd'
- call assert_report('should not get here')
- endfunc
-
- if 1
- try
- Xpath 'e'
- call G()
- call assert_report('should not get here')
- finally
- Xpath 'f'
- endtry | call assert_report('should not get here')
- endif | call assert_report('should not get here')
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('eabf', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 30: Executing :finally clauses on interrupt {{{1
-"
-" After an interrupt in a command dynamically enclosed in
-" a :try/:endtry region, :finally clauses are executed and the
-" script processing is terminated.
-"-------------------------------------------------------------------------------
-
-func Test_finally_on_interrupt()
- let test =<< trim [CODE]
- func F()
- try
- Xloop 'a'
- call interrupt()
- call assert_report('should not get here')
- finally
- Xloop 'b'
- endtry
- call assert_report('should not get here')
- endfunc
-
- try
- try
- Xpath 'c'
- try
- Xpath 'd'
- call interrupt()
- call assert_report('should not get here')
- finally
- Xpath 'e'
- try
- Xpath 'f'
- try
- Xpath 'g'
- finally
- Xpath 'h'
- try
- Xpath 'i'
- call interrupt()
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- finally
- Xpath 'j'
- try
- Xpath 'k'
- call F()
- call assert_report('should not get here')
- finally
- Xpath 'l'
- try
- Xpath 'm'
- XloopNEXT
- ExecAsScript F
- call assert_report('should not get here')
- finally
- Xpath 'n'
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'o'
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('cdefghijka1b1lma2b2no', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 31: Executing :finally clauses after :throw {{{1
-"
-" After a :throw dynamically enclosed in a :try/:endtry region,
-" :finally clauses are executed and the script processing is
-" terminated.
-"-------------------------------------------------------------------------------
-
-func Test_finally_after_throw_2()
- let test =<< trim [CODE]
- func F()
- try
- Xloop 'a'
- throw "exception"
- call assert_report('should not get here')
- finally
- Xloop 'b'
- endtry
- call assert_report('should not get here')
- endfunc
-
- try
- Xpath 'c'
- try
- Xpath 'd'
- throw "exception"
- call assert_report('should not get here')
- finally
- Xpath 'e'
- try
- Xpath 'f'
- try
- Xpath 'g'
- finally
- Xpath 'h'
- try
- Xpath 'i'
- throw "exception"
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- finally
- Xpath 'j'
- try
- Xpath 'k'
- call F()
- call assert_report('should not get here')
- finally
- Xpath 'l'
- try
- Xpath 'm'
- XloopNEXT
- ExecAsScript F
- call assert_report('should not get here')
- finally
- Xpath 'n'
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('cdefghijka1b1lma2b2n', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 34: :finally reason discarded by :continue {{{1
-"
-" When a :finally clause is executed due to a :continue, :break,
-" :return, :finish, error, interrupt or :throw, the jump reason is
-" discarded by a :continue in the finally clause.
-"-------------------------------------------------------------------------------
-
-func Test_finally_after_continue()
- let test =<< trim [CODE]
- func C(jump)
- XloopNEXT
- let loop = 0
- while loop < 2
- let loop = loop + 1
- if loop == 1
- try
- if a:jump == "continue"
- continue
- elseif a:jump == "break"
- break
- elseif a:jump == "return" || a:jump == "finish"
- return
- elseif a:jump == "error"
- asdf
- elseif a:jump == "interrupt"
- call interrupt()
- let dummy = 0
- elseif a:jump == "throw"
- throw "abc"
- endif
- finally
- continue " discards jump that caused the :finally
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- elseif loop == 2
- Xloop 'a'
- endif
- endwhile
- endfunc
-
- call C("continue")
- Xpath 'b'
- call C("break")
- Xpath 'c'
- call C("return")
- Xpath 'd'
- let g:jump = "finish"
- ExecAsScript C
- unlet g:jump
- Xpath 'e'
- try
- call C("error")
- Xpath 'f'
- finally
- Xpath 'g'
- try
- call C("interrupt")
- Xpath 'h'
- finally
- Xpath 'i'
- call C("throw")
- Xpath 'j'
- endtry
- endtry
- Xpath 'k'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 35: :finally reason discarded by :break {{{1
-"
-" When a :finally clause is executed due to a :continue, :break,
-" :return, :finish, error, interrupt or :throw, the jump reason is
-" discarded by a :break in the finally clause.
-"-------------------------------------------------------------------------------
-
-func Test_finally_discard_by_break()
- let test =<< trim [CODE]
- func B(jump)
- XloopNEXT
- let loop = 0
- while loop < 2
- let loop = loop + 1
- if loop == 1
- try
- if a:jump == "continue"
- continue
- elseif a:jump == "break"
- break
- elseif a:jump == "return" || a:jump == "finish"
- return
- elseif a:jump == "error"
- asdf
- elseif a:jump == "interrupt"
- call interrupt()
- let dummy = 0
- elseif a:jump == "throw"
- throw "abc"
- endif
- finally
- break " discards jump that caused the :finally
- call assert_report('should not get here')
- endtry
- elseif loop == 2
- call assert_report('should not get here')
- endif
- endwhile
- Xloop 'a'
- endfunc
-
- call B("continue")
- Xpath 'b'
- call B("break")
- Xpath 'c'
- call B("return")
- Xpath 'd'
- let g:jump = "finish"
- ExecAsScript B
- unlet g:jump
- Xpath 'e'
- try
- call B("error")
- Xpath 'f'
- finally
- Xpath 'g'
- try
- call B("interrupt")
- Xpath 'h'
- finally
- Xpath 'i'
- call B("throw")
- Xpath 'j'
- endtry
- endtry
- Xpath 'k'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 36: :finally reason discarded by :return {{{1
-"
-" When a :finally clause is executed due to a :continue, :break,
-" :return, :finish, error, interrupt or :throw, the jump reason is
-" discarded by a :return in the finally clause.
-"-------------------------------------------------------------------------------
-
-func Test_finally_discard_by_return()
- let test =<< trim [CODE]
- func R(jump, retval) abort
- let loop = 0
- while loop < 2
- let loop = loop + 1
- if loop == 1
- try
- if a:jump == "continue"
- continue
- elseif a:jump == "break"
- break
- elseif a:jump == "return"
- return
- elseif a:jump == "error"
- asdf
- elseif a:jump == "interrupt"
- call interrupt()
- let dummy = 0
- elseif a:jump == "throw"
- throw "abc"
- endif
- finally
- return a:retval " discards jump that caused the :finally
- call assert_report('should not get here')
- endtry
- elseif loop == 2
- call assert_report('should not get here')
- endif
- endwhile
- call assert_report('should not get here')
- endfunc
-
- let sum = -R("continue", -8)
- Xpath 'a'
- let sum = sum - R("break", -16)
- Xpath 'b'
- let sum = sum - R("return", -32)
- Xpath 'c'
- try
- let sum = sum - R("error", -64)
- Xpath 'd'
- finally
- Xpath 'e'
- try
- let sum = sum - R("interrupt", -128)
- Xpath 'f'
- finally
- Xpath 'g'
- let sum = sum - R("throw", -256)
- Xpath 'h'
- endtry
- endtry
- Xpath 'i'
-
- let expected = 8 + 16 + 32 + 64 + 128 + 256
- call assert_equal(sum, expected)
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefghi', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 37: :finally reason discarded by :finish {{{1
-"
-" When a :finally clause is executed due to a :continue, :break,
-" :return, :finish, error, interrupt or :throw, the jump reason is
-" discarded by a :finish in the finally clause.
-"-------------------------------------------------------------------------------
-
-func Test_finally_discard_by_finish()
- let test =<< trim [CODE]
- func F(jump) " not executed as function, transformed to a script
- let loop = 0
- while loop < 2
- let loop = loop + 1
- if loop == 1
- try
- if a:jump == "continue"
- continue
- elseif a:jump == "break"
- break
- elseif a:jump == "finish"
- finish
- elseif a:jump == "error"
- asdf
- elseif a:jump == "interrupt"
- call interrupt()
- let dummy = 0
- elseif a:jump == "throw"
- throw "abc"
- endif
- finally
- finish " discards jump that caused the :finally
- call assert_report('should not get here')
- endtry
- elseif loop == 2
- call assert_report('should not get here')
- endif
- endwhile
- call assert_report('should not get here')
- endfunc
-
- let scriptF = MakeScript("F")
- delfunction F
-
- let g:jump = "continue"
- exec "source" scriptF
- Xpath 'a'
- let g:jump = "break"
- exec "source" scriptF
- Xpath 'b'
- let g:jump = "finish"
- exec "source" scriptF
- Xpath 'c'
- try
- let g:jump = "error"
- exec "source" scriptF
- Xpath 'd'
- finally
- Xpath 'e'
- try
- let g:jump = "interrupt"
- exec "source" scriptF
- Xpath 'f'
- finally
- Xpath 'g'
- try
- let g:jump = "throw"
- exec "source" scriptF
- Xpath 'h'
- finally
- Xpath 'i'
- endtry
- endtry
- endtry
- unlet g:jump
- call delete(scriptF)
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefghi', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 38: :finally reason discarded by an error {{{1
-"
-" When a :finally clause is executed due to a :continue, :break,
-" :return, :finish, error, interrupt or :throw, the jump reason is
-" discarded by an error in the finally clause.
-"-------------------------------------------------------------------------------
-
-func Test_finally_discard_by_error()
- let test =<< trim [CODE]
- func E(jump)
- let loop = 0
- while loop < 2
- let loop = loop + 1
- if loop == 1
- try
- if a:jump == "continue"
- continue
- elseif a:jump == "break"
- break
- elseif a:jump == "return" || a:jump == "finish"
- return
- elseif a:jump == "error"
- asdf
- elseif a:jump == "interrupt"
- call interrupt()
- let dummy = 0
- elseif a:jump == "throw"
- throw "abc"
- endif
- finally
- asdf " error; discards jump that caused the :finally
- endtry
- elseif loop == 2
- call assert_report('should not get here')
- endif
- endwhile
- call assert_report('should not get here')
- endfunc
-
- try
- Xpath 'a'
- call E("continue")
- call assert_report('should not get here')
- finally
- try
- Xpath 'b'
- call E("break")
- call assert_report('should not get here')
- finally
- try
- Xpath 'c'
- call E("return")
- call assert_report('should not get here')
- finally
- try
- Xpath 'd'
- let g:jump = "finish"
- ExecAsScript E
- call assert_report('should not get here')
- finally
- unlet g:jump
- try
- Xpath 'e'
- call E("error")
- call assert_report('should not get here')
- finally
- try
- Xpath 'f'
- call E("interrupt")
- call assert_report('should not get here')
- finally
- try
- Xpath 'g'
- call E("throw")
- call assert_report('should not get here')
- finally
- Xpath 'h'
- delfunction E
- endtry
- endtry
- endtry
- endtry
- endtry
- endtry
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefgh', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 39: :finally reason discarded by an interrupt {{{1
-"
-" When a :finally clause is executed due to a :continue, :break,
-" :return, :finish, error, interrupt or :throw, the jump reason is
-" discarded by an interrupt in the finally clause.
-"-------------------------------------------------------------------------------
-
-func Test_finally_discarded_by_interrupt()
- let test =<< trim [CODE]
- func I(jump)
- let loop = 0
- while loop < 2
- let loop = loop + 1
- if loop == 1
- try
- if a:jump == "continue"
- continue
- elseif a:jump == "break"
- break
- elseif a:jump == "return" || a:jump == "finish"
- return
- elseif a:jump == "error"
- asdf
- elseif a:jump == "interrupt"
- call interrupt()
- let dummy = 0
- elseif a:jump == "throw"
- throw "abc"
- endif
- finally
- call interrupt()
- let dummy = 0
- endtry
- elseif loop == 2
- call assert_report('should not get here')
- endif
- endwhile
- call assert_report('should not get here')
- endfunc
-
- try
- try
- Xpath 'a'
- call I("continue")
- call assert_report('should not get here')
- finally
- try
- Xpath 'b'
- call I("break")
- call assert_report('should not get here')
- finally
- try
- Xpath 'c'
- call I("return")
- call assert_report('should not get here')
- finally
- try
- Xpath 'd'
- let g:jump = "finish"
- ExecAsScript I
- call assert_report('should not get here')
- finally
- unlet g:jump
- try
- Xpath 'e'
- call I("error")
- call assert_report('should not get here')
- finally
- try
- Xpath 'f'
- call I("interrupt")
- call assert_report('should not get here')
- finally
- try
- Xpath 'g'
- call I("throw")
- call assert_report('should not get here')
- finally
- Xpath 'h'
- delfunction I
- endtry
- endtry
- endtry
- endtry
- endtry
- endtry
- endtry
- call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'A'
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefghA', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 40: :finally reason discarded by :throw {{{1
-"
-" When a :finally clause is executed due to a :continue, :break,
-" :return, :finish, error, interrupt or :throw, the jump reason is
-" discarded by a :throw in the finally clause.
-"-------------------------------------------------------------------------------
-
-func Test_finally_discard_by_throw()
- let test =<< trim [CODE]
- func T(jump)
- let loop = 0
- while loop < 2
- let loop = loop + 1
- if loop == 1
- try
- if a:jump == "continue"
- continue
- elseif a:jump == "break"
- break
- elseif a:jump == "return" || a:jump == "finish"
- return
- elseif a:jump == "error"
- asdf
- elseif a:jump == "interrupt"
- call interrupt()
- let dummy = 0
- elseif a:jump == "throw"
- throw "abc"
- endif
- finally
- throw "xyz" " discards jump that caused the :finally
- endtry
- elseif loop == 2
- call assert_report('should not get here')
- endif
- endwhile
- call assert_report('should not get here')
- endfunc
-
- try
- Xpath 'a'
- call T("continue")
- call assert_report('should not get here')
- finally
- try
- Xpath 'b'
- call T("break")
- call assert_report('should not get here')
- finally
- try
- Xpath 'c'
- call T("return")
- call assert_report('should not get here')
- finally
- try
- Xpath 'd'
- let g:jump = "finish"
- ExecAsScript T
- call assert_report('should not get here')
- finally
- unlet g:jump
- try
- Xpath 'e'
- call T("error")
- call assert_report('should not get here')
- finally
- try
- Xpath 'f'
- call T("interrupt")
- call assert_report('should not get here')
- finally
- try
- Xpath 'g'
- call T("throw")
- call assert_report('should not get here')
- finally
- Xpath 'h'
- delfunction T
- endtry
- endtry
- endtry
- endtry
- endtry
- endtry
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefgh', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 49: Throwing exceptions across functions {{{1
-"
-" When an exception is thrown but not caught inside a function, the
-" caller is checked for a matching :catch clause.
-"-------------------------------------------------------------------------------
-
-func T49_C()
- try
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')
- catch /arrgh/
- Xpath 'b'
- endtry
- Xpath 'c'
-endfunc
-
-func T49_T1()
- XloopNEXT
- try
- Xloop 'd'
- throw "arrgh"
- call assert_report('should not get here')
- finally
- Xloop 'e'
- endtry
- Xloop 'f'
-endfunc
-
-func T49_T2()
- try
- Xpath 'g'
- call T49_T1()
- call assert_report('should not get here')
- finally
- Xpath 'h'
- endtry
- call assert_report('should not get here')
-endfunc
-
-func Test_throw_exception_across_funcs()
- XpathINIT
- XloopINIT
- try
- Xpath 'i'
- call T49_C() " throw and catch
- Xpath 'j'
- catch /.*/
- call assert_report('should not get here')
- endtry
-
- try
- Xpath 'k'
- call T49_T1() " throw, one level
- call assert_report('should not get here')
- catch /arrgh/
- Xpath 'l'
- catch /.*/
- call assert_report('should not get here')
- endtry
-
- try
- Xpath 'm'
- call T49_T2() " throw, two levels
- call assert_report('should not get here')
- catch /arrgh/
- Xpath 'n'
- catch /.*/
- call assert_report('should not get here')
- endtry
- Xpath 'o'
-
- call assert_equal('iabcjkd2e2lmgd3e3hno', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 50: Throwing exceptions across script files {{{1
-"
-" When an exception is thrown but not caught inside a script file,
-" the sourcing script or function is checked for a matching :catch
-" clause.
-"
-" This test executes the bodies of the functions C, T1, and T2 from
-" the previous test as script files (:return replaced by :finish).
-"-------------------------------------------------------------------------------
-
-func T50_F()
- try
- Xpath 'A'
- exec "source" g:scriptC
- Xpath 'B'
- catch /.*/
- call assert_report('should not get here')
- endtry
-
- try
- Xpath 'C'
- exec "source" g:scriptT1
- call assert_report('should not get here')
- catch /arrgh/
- Xpath 'D'
- catch /.*/
- call assert_report('should not get here')
- endtry
-endfunc
-
-func Test_throw_across_script()
- XpathINIT
- XloopINIT
- let g:scriptC = MakeScript("T49_C")
- let g:scriptT1 = MakeScript("T49_T1")
- let scriptT2 = MakeScript("T49_T2", g:scriptT1)
-
- try
- Xpath 'E'
- call T50_F()
- Xpath 'F'
- exec "source" scriptT2
- call assert_report('should not get here')
- catch /arrgh/
- Xpath 'G'
- catch /.*/
- call assert_report('should not get here')
- endtry
- Xpath 'H'
- call assert_equal('EAabcBCd2e2DFgd3e3hGH', g:Xpath)
-
- call delete(g:scriptC)
- call delete(g:scriptT1)
- call delete(scriptT2)
- unlet g:scriptC g:scriptT1 scriptT2
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 52: Uncaught exceptions {{{1
-"
-" When an exception is thrown but not caught, an error message is
-" displayed when the script is terminated. In case of an interrupt
-" or error exception, the normal interrupt or error message(s) are
-" displayed.
-"-------------------------------------------------------------------------------
-
-func Test_uncaught_exception_1()
- CheckEnglish
-
- let test =<< trim [CODE]
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')`
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('E605: Exception not caught: arrgh', v:errmsg)
- call assert_equal('a', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_uncaught_exception_2()
- CheckEnglish
-
- let test =<< trim [CODE]
- try
- Xpath 'a'
- throw "oops"
- call assert_report('should not get here')`
- catch /arrgh/
- call assert_report('should not get here')`
- endtry
- call assert_report('should not get here')`
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('E605: Exception not caught: oops', v:errmsg)
- call assert_equal('a', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_uncaught_exception_3()
- CheckEnglish
-
- let test =<< trim [CODE]
- func T()
- Xpath 'c'
- throw "brrr"
- call assert_report('should not get here')`
- endfunc
-
- try
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')`
- catch /.*/
- Xpath 'b'
- call T()
- call assert_report('should not get here')`
- endtry
- call assert_report('should not get here')`
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('E605: Exception not caught: brrr', v:errmsg)
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_uncaught_exception_4()
- CheckEnglish
-
- let test =<< trim [CODE]
- try
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')`
- finally
- Xpath 'b'
- throw "brrr"
- call assert_report('should not get here')`
- endtry
- call assert_report('should not get here')`
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('E605: Exception not caught: brrr', v:errmsg)
- call assert_equal('ab', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_uncaught_exception_5()
- CheckEnglish
-
- " Need to catch and handle interrupt, otherwise the test will wait for the
- " user to press <Enter> to continue
- let test =<< trim [CODE]
- try
- try
- Xpath 'a'
- call interrupt()
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'b'
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('ab', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_uncaught_exception_6()
- CheckEnglish
-
- let test =<< trim [CODE]
- try
- Xpath 'a'
- let x = novar " error E121; exception: E121
- catch /E15:/ " should not catch
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a', g:Xpath)
- call assert_equal('E121: Undefined variable: novar', v:errmsg)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_uncaught_exception_7()
- CheckEnglish
-
- let test =<< trim [CODE]
- try
- Xpath 'a'
- " error E108/E488; exception: E488
- unlet novar #
- catch /E108:/ " should not catch
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a', g:Xpath)
- call assert_equal('E488: Trailing characters: #', v:errmsg)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 53: Nesting errors: :endif/:else/:elseif {{{1
-"
-" For nesting errors of :if conditionals the correct error messages
-" should be given.
-"-------------------------------------------------------------------------------
-
-func Test_nested_if_else_errors()
- CheckEnglish
-
- " :endif without :if
- let code =<< trim END
- endif
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
-
- " :endif without :if
- let code =<< trim END
- while 1
- endif
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
-
- " :endif without :if
- let code =<< trim END
- try
- finally
- endif
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
-
- " :endif without :if
- let code =<< trim END
- try
- endif
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
-
- " :endif without :if
- let code =<< trim END
- try
- throw "a"
- catch /a/
- endif
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
-
- " :else without :if
- let code =<< trim END
- else
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
-
- " :else without :if
- let code =<< trim END
- while 1
- else
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
-
- " :else without :if
- let code =<< trim END
- try
- finally
- else
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
-
- " :else without :if
- let code =<< trim END
- try
- else
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
-
- " :else without :if
- let code =<< trim END
- try
- throw "a"
- catch /a/
- else
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
-
- " :elseif without :if
- let code =<< trim END
- elseif 1
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
-
- " :elseif without :if
- let code =<< trim END
- while 1
- elseif 1
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
-
- " :elseif without :if
- let code =<< trim END
- try
- finally
- elseif 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
-
- " :elseif without :if
- let code =<< trim END
- try
- elseif 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
-
- " :elseif without :if
- let code =<< trim END
- try
- throw "a"
- catch /a/
- elseif 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
-
- " multiple :else
- let code =<< trim END
- if 1
- else
- else
- endif
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(else):E583: multiple :else')
-
- " :elseif after :else
- let code =<< trim END
- if 1
- else
- elseif 1
- endif
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(elseif):E584: :elseif after :else')
-
- call delete('Xtest')
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 54: Nesting errors: :while/:endwhile {{{1
-"
-" For nesting errors of :while conditionals the correct error messages
-" should be given.
-"
-" This test reuses the function MESSAGES() from the previous test.
-" This functions checks the messages in g:msgfile.
-"-------------------------------------------------------------------------------
-
-func Test_nested_while_error()
- CheckEnglish
-
- " :endwhile without :while
- let code =<< trim END
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
-
- " :endwhile without :while
- let code =<< trim END
- if 1
- endwhile
- endif
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
-
- " Missing :endif
- let code =<< trim END
- while 1
- if 1
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif')
-
- " :endwhile without :while
- let code =<< trim END
- try
- finally
- endwhile
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
-
- " Missing :endtry
- let code =<< trim END
- while 1
- try
- finally
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry')
-
- " Missing :endtry
- let code =<< trim END
- while 1
- if 1
- try
- finally
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry')
-
- " Missing :endif
- let code =<< trim END
- while 1
- try
- finally
- if 1
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif')
-
- " :endwhile without :while
- let code =<< trim END
- try
- endwhile
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
-
- " :endwhile without :while
- let code =<< trim END
- while 1
- try
- endwhile
- endtry
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
-
- " :endwhile without :while
- let code =<< trim END
- try
- throw "a"
- catch /a/
- endwhile
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
-
- " :endwhile without :while
- let code =<< trim END
- while 1
- try
- throw "a"
- catch /a/
- endwhile
- endtry
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
-
- call delete('Xtest')
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 55: Nesting errors: :continue/:break {{{1
-"
-" For nesting errors of :continue and :break commands the correct
-" error messages should be given.
-"
-" This test reuses the function MESSAGES() from the previous test.
-" This functions checks the messages in g:msgfile.
-"-------------------------------------------------------------------------------
-
-func Test_nested_cont_break_error()
- CheckEnglish
-
- " :continue without :while
- let code =<< trim END
- continue
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
-
- " :continue without :while
- let code =<< trim END
- if 1
- continue
- endif
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
-
- " :continue without :while
- let code =<< trim END
- try
- finally
- continue
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
-
- " :continue without :while
- let code =<< trim END
- try
- continue
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
-
- " :continue without :while
- let code =<< trim END
- try
- throw "a"
- catch /a/
- continue
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
-
- " :break without :while
- let code =<< trim END
- break
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
-
- " :break without :while
- let code =<< trim END
- if 1
- break
- endif
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
-
- " :break without :while
- let code =<< trim END
- try
- finally
- break
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
-
- " :break without :while
- let code =<< trim END
- try
- break
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
-
- " :break without :while
- let code =<< trim END
- try
- throw "a"
- catch /a/
- break
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
-
- call delete('Xtest')
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 56: Nesting errors: :endtry {{{1
-"
-" For nesting errors of :try conditionals the correct error messages
-" should be given.
-"
-" This test reuses the function MESSAGES() from the previous test.
-" This functions checks the messages in g:msgfile.
-"-------------------------------------------------------------------------------
-
-func Test_nested_endtry_error()
- CheckEnglish
-
- " :endtry without :try
- let code =<< trim END
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try')
-
- " :endtry without :try
- let code =<< trim END
- if 1
- endtry
- endif
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try')
-
- " :endtry without :try
- let code =<< trim END
- while 1
- endtry
- endwhile
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try')
-
- " Missing :endif
- let code =<< trim END
- try
- if 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif')
-
- " Missing :endwhile
- let code =<< trim END
- try
- while 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile')
-
- " Missing :endif
- let code =<< trim END
- try
- finally
- if 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif')
-
- " Missing :endwhile
- let code =<< trim END
- try
- finally
- while 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile')
-
- " Missing :endif
- let code =<< trim END
- try
- throw "a"
- catch /a/
- if 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif')
-
- " Missing :endwhile
- let code =<< trim END
- try
- throw "a"
- catch /a/
- while 1
- endtry
- END
- call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile')
-
- call delete('Xtest')
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 57: v:exception and v:throwpoint for user exceptions {{{1
-"
-" v:exception evaluates to the value of the exception that was caught
-" most recently and is not finished. (A caught exception is finished
-" when the next ":catch", ":finally", or ":endtry" is reached.)
-" v:throwpoint evaluates to the script/function name and line number
-" where that exception has been thrown.
-"-------------------------------------------------------------------------------
-
-func Test_user_exception_info()
- CheckEnglish
-
- XpathINIT
- XloopINIT
-
- func FuncException()
- let g:exception = v:exception
- endfunc
-
- func FuncThrowpoint()
- let g:throwpoint = v:throwpoint
- endfunc
-
- let scriptException = MakeScript("FuncException")
- let scriptThrowPoint = MakeScript("FuncThrowpoint")
-
- command! CmdException let g:exception = v:exception
- command! CmdThrowpoint let g:throwpoint = v:throwpoint
-
- func T(arg, line)
- if a:line == 2
- throw a:arg " in line 2
- elseif a:line == 4
- throw a:arg " in line 4
- elseif a:line == 6
- throw a:arg " in line 6
- elseif a:line == 8
- throw a:arg " in line 8
- endif
- endfunc
-
- func G(arg, line)
- call T(a:arg, a:line)
- endfunc
-
- func F(arg, line)
- call G(a:arg, a:line)
- endfunc
-
- let scriptT = MakeScript("T")
- let scriptG = MakeScript("G", scriptT)
- let scriptF = MakeScript("F", scriptG)
-
- try
- Xpath 'a'
- call F("oops", 2)
- catch /.*/
- Xpath 'b'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("oops", v:exception)
- call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<2\>', v:throwpoint)
-
- exec "let exception = v:exception"
- exec "let throwpoint = v:throwpoint"
- call assert_equal("oops", v:exception)
- call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<2\>', v:throwpoint)
-
- CmdException
- CmdThrowpoint
- call assert_equal("oops", v:exception)
- call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<2\>', v:throwpoint)
-
- call FuncException()
- call FuncThrowpoint()
- call assert_equal("oops", v:exception)
- call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<2\>', v:throwpoint)
-
- exec "source" scriptException
- exec "source" scriptThrowPoint
- call assert_equal("oops", v:exception)
- call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<2\>', v:throwpoint)
-
- try
- Xpath 'c'
- call G("arrgh", 4)
- catch /.*/
- Xpath 'd'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("arrgh", v:exception)
- call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<4\>', v:throwpoint)
-
- try
- Xpath 'e'
- let g:arg = "autsch"
- let g:line = 6
- exec "source" scriptF
- catch /.*/
- Xpath 'f'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("autsch", v:exception)
- call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint)
- call assert_match('\<6\>', v:throwpoint)
- finally
- Xpath 'g'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("arrgh", v:exception)
- call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<4\>', v:throwpoint)
- try
- Xpath 'h'
- let g:arg = "brrrr"
- let g:line = 8
- exec "source" scriptG
- catch /.*/
- Xpath 'i'
- let exception = v:exception
- let throwpoint = v:throwpoint
- " Resolve scriptT for matching it against v:throwpoint.
- call assert_equal("brrrr", v:exception)
- call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint)
- call assert_match('\<8\>', v:throwpoint)
- finally
- Xpath 'j'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("arrgh", v:exception)
- call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<4\>', v:throwpoint)
- endtry
- Xpath 'k'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("arrgh", v:exception)
- call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<4\>', v:throwpoint)
- endtry
- Xpath 'l'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("arrgh", v:exception)
- call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<4\>', v:throwpoint)
- finally
- Xpath 'm'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("oops", v:exception)
- call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<2\>', v:throwpoint)
- endtry
- Xpath 'n'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("oops", v:exception)
- call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
- call assert_match('\<2\>', v:throwpoint)
- finally
- Xpath 'o'
- let exception = v:exception
- let throwpoint = v:throwpoint
- call assert_equal("", v:exception)
- call assert_match('^$', v:throwpoint)
- call assert_match('^$', v:throwpoint)
- endtry
-
- call assert_equal('abcdefghijklmno', g:Xpath)
-
- unlet exception throwpoint
- delfunction FuncException
- delfunction FuncThrowpoint
- call delete(scriptException)
- call delete(scriptThrowPoint)
- unlet scriptException scriptThrowPoint
- delcommand CmdException
- delcommand CmdThrowpoint
- delfunction T
- delfunction G
- delfunction F
- call delete(scriptT)
- call delete(scriptG)
- call delete(scriptF)
- unlet scriptT scriptG scriptF
-endfunc
-
-"-------------------------------------------------------------------------------
-"
-" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1
-"
-" v:exception and v:throwpoint work also for error and interrupt
-" exceptions.
-"-------------------------------------------------------------------------------
-
-func Test_execption_info_for_error()
- CheckEnglish
-
- let test =<< trim [CODE]
- func T(line)
- if a:line == 2
- delfunction T " error (function in use) in line 2
- elseif a:line == 4
- call interrupt()
- endif
- endfunc
-
- while 1
- try
- Xpath 'a'
- call T(2)
- call assert_report('should not get here')
- catch /.*/
- Xpath 'b'
- if v:exception !~ 'Vim(delfunction):'
- call assert_report('should not get here')
- endif
- if v:throwpoint !~ '\<T\>'
- call assert_report('should not get here')
- endif
- if v:throwpoint !~ '\<2\>'
- call assert_report('should not get here')
- endif
- finally
- Xpath 'c'
- if v:exception != ""
- call assert_report('should not get here')
- endif
- if v:throwpoint != ""
- call assert_report('should not get here')
- endif
- break
- endtry
- endwhile
-
- Xpath 'd'
- if v:exception != ""
- call assert_report('should not get here')
- endif
- if v:throwpoint != ""
- call assert_report('should not get here')
- endif
-
- while 1
- try
- Xpath 'e'
- call T(4)
- call assert_report('should not get here')
- catch /.*/
- Xpath 'f'
- if v:exception != 'Vim:Interrupt'
- call assert_report('should not get here')
- endif
- if v:throwpoint !~ 'function T'
- call assert_report('should not get here')
- endif
- if v:throwpoint !~ '\<4\>'
- call assert_report('should not get here')
- endif
- finally
- Xpath 'g'
- if v:exception != ""
- call assert_report('should not get here')
- endif
- if v:throwpoint != ""
- call assert_report('should not get here')
- endif
- break
- endtry
- endwhile
-
- Xpath 'h'
- if v:exception != ""
- call assert_report('should not get here')
- endif
- if v:throwpoint != ""
- call assert_report('should not get here')
- endif
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefgh', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-"
-" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1
-"
-" When a :catch clause is left by a ":break" etc or an error or
-" interrupt exception, v:exception and v:throwpoint are reset. They
-" are not affected by an exception that is discarded before being
-" caught.
-"-------------------------------------------------------------------------------
-func Test_exception_info_on_discard()
- CheckEnglish
-
- let test =<< trim [CODE]
- let sfile = expand("<sfile>")
-
- while 1
- try
- throw "x1"
- catch /.*/
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- while 1
- try
- throw "x2"
- catch /.*/
- break
- finally
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
- endtry
- break
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- while 1
- try
- let errcaught = 0
- try
- try
- throw "x3"
- catch /.*/
- let lnum = expand("<sflnum>")
- asdf
- endtry
- catch /.*/
- let errcaught = 1
- call assert_match('Vim:E492: Not an editor command:', v:exception)
- call assert_match('line ' .. (lnum + 1), v:throwpoint)
- endtry
- finally
- call assert_equal(1, errcaught)
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- Xpath 'a'
-
- while 1
- try
- let intcaught = 0
- try
- try
- throw "x4"
- catch /.*/
- let lnum = expand("<sflnum>")
- call interrupt()
- endtry
- catch /.*/
- let intcaught = 1
- call assert_match('Vim:Interrupt', v:exception)
- call assert_match('line ' .. (lnum + 1), v:throwpoint)
- endtry
- finally
- call assert_equal(1, intcaught)
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- Xpath 'b'
-
- while 1
- try
- let errcaught = 0
- try
- try
- if 1
- let lnum = expand("<sflnum>")
- throw "x5"
- " missing endif
- catch /.*/
- call assert_report('should not get here')
- endtry
- catch /.*/
- let errcaught = 1
- call assert_match('Vim(catch):E171: Missing :endif:', v:exception)
- call assert_match('line ' .. (lnum + 3), v:throwpoint)
- endtry
- finally
- call assert_equal(1, errcaught)
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- Xpath 'c'
-
- try
- while 1
- try
- throw "x6"
- finally
- break
- endtry
- break
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- try
- while 1
- try
- throw "x7"
- finally
- break
- endtry
- break
- endwhile
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
- endtry
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- while 1
- try
- let errcaught = 0
- try
- try
- throw "x8"
- finally
- let lnum = expand("<sflnum>")
- asdf
- endtry
- catch /.*/
- let errcaught = 1
- call assert_match('Vim:E492: Not an editor command:', v:exception)
- call assert_match('line ' .. (lnum + 1), v:throwpoint)
- endtry
- finally
- call assert_equal(1, errcaught)
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- Xpath 'd'
-
- while 1
- try
- let intcaught = 0
- try
- try
- throw "x9"
- finally
- let lnum = expand("<sflnum>")
- call interrupt()
- endtry
- catch /.*/
- let intcaught = 1
- call assert_match('Vim:Interrupt', v:exception)
- call assert_match('line ' .. (lnum + 1), v:throwpoint)
- endtry
- finally
- call assert_equal(1, intcaught)
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- Xpath 'e'
-
- while 1
- try
- let errcaught = 0
- try
- try
- if 1
- let lnum = expand("<sflnum>")
- throw "x10"
- " missing endif
- finally
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
- endtry
- catch /.*/
- let errcaught = 1
- call assert_match('Vim(finally):E171: Missing :endif:', v:exception)
- call assert_match('line ' .. (lnum + 3), v:throwpoint)
- endtry
- finally
- call assert_equal(1, errcaught)
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- Xpath 'f'
-
- while 1
- try
- let errcaught = 0
- try
- try
- if 1
- let lnum = expand("<sflnum>")
- throw "x11"
- " missing endif
- endtry
- catch /.*/
- let errcaught = 1
- call assert_match('Vim(endtry):E171: Missing :endif:', v:exception)
- call assert_match('line ' .. (lnum + 3), v:throwpoint)
- endtry
- finally
- call assert_equal(1, errcaught)
- break
- endtry
- endwhile
- call assert_equal('', v:exception)
- call assert_equal('', v:throwpoint)
-
- Xpath 'g'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefg', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-"
-" Test 60: (Re)throwing v:exception; :echoerr. {{{1
-"
-" A user exception can be rethrown after catching by throwing
-" v:exception. An error or interrupt exception cannot be rethrown
-" because Vim exceptions cannot be faked. A Vim exception using the
-" value of v:exception can, however, be triggered by the :echoerr
-" command.
-"-------------------------------------------------------------------------------
-
-func Test_rethrow_exception_1()
- XpathINIT
- try
- try
- Xpath 'a'
- throw "oops"
- catch /oops/
- Xpath 'b'
- throw v:exception " rethrow user exception
- catch /.*/
- call assert_report('should not get here')
- endtry
- catch /^oops$/ " catches rethrown user exception
- Xpath 'c'
- catch /.*/
- call assert_report('should not get here')
- endtry
- call assert_equal('abc', g:Xpath)
-endfunc
-
-func Test_rethrow_exception_2()
- XpathINIT
- try
- let caught = 0
- try
- Xpath 'a'
- write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e
- call assert_report('should not get here')
- catch /^Vim(write):/
- let caught = 1
- throw v:exception " throw error: cannot fake Vim exception
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- endtry
- catch /^Vim(throw):/ " catches throw error
- let caught = caught + 1
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- call assert_equal(2, caught)
- endtry
- call assert_equal('abc', g:Xpath)
-endfunc
-
-func Test_rethrow_exception_3()
- XpathINIT
- try
- let caught = 0
- try
- Xpath 'a'
- asdf
- catch /^Vim/ " catch error exception
- let caught = 1
- " Trigger Vim error exception with value specified after :echoerr
- let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "")
- echoerr value
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- endtry
- catch /^Vim(echoerr):/
- let caught = caught + 1
- call assert_match(value, v:exception)
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- call assert_equal(2, caught)
- endtry
- call assert_equal('abc', g:Xpath)
-endfunc
-
-func Test_rethrow_exception_3()
- XpathINIT
- try
- let errcaught = 0
- try
- Xpath 'a'
- let intcaught = 0
- call interrupt()
- catch /^Vim:/ " catch interrupt exception
- let intcaught = 1
- " Trigger Vim error exception with value specified after :echoerr
- echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "")
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'b'
- call assert_equal(1, intcaught)
- endtry
- catch /^Vim(echoerr):/
- let errcaught = 1
- call assert_match('Interrupt', v:exception)
- finally
- Xpath 'c'
- call assert_equal(1, errcaught)
- endtry
- call assert_equal('abc', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 61: Catching interrupt exceptions {{{1
-"
-" When an interrupt occurs inside a :try/:endtry region, an
-" interrupt exception is thrown and can be caught. Its value is
-" "Vim:Interrupt". If the interrupt occurs after an error or a :throw
-" but before a matching :catch is reached, all following :catches of
-" that try block are ignored, but the interrupt exception can be
-" caught by the next surrounding try conditional. An interrupt is
-" ignored when there is a previous interrupt that has not been caught
-" or causes a :finally clause to be executed.
-"-------------------------------------------------------------------------------
-
-func Test_catch_intr_exception()
- let test =<< trim [CODE]
- while 1
- try
- try
- Xpath 'a'
- call interrupt()
- call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'b'
- finally
- Xpath 'c'
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'd'
- break
- endtry
- endwhile
-
- while 1
- try
- try
- try
- Xpath 'e'
- asdf
- call assert_report('should not get here')
- catch /do_not_catch/
- call assert_report('should not get here')
- catch /.*/
- Xpath 'f'
- call interrupt()
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'g'
- call interrupt()
- call assert_report('should not get here')
- endtry
- catch /^Vim:Interrupt$/
- Xpath 'h'
- finally
- Xpath 'i'
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'j'
- break
- endtry
- endwhile
-
- while 1
- try
- try
- try
- Xpath 'k'
- throw "x"
- call assert_report('should not get here')
- catch /do_not_catch/
- call assert_report('should not get here')
- catch /x/
- Xpath 'l'
- call interrupt()
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- catch /^Vim:Interrupt$/
- Xpath 'm'
- finally
- Xpath 'n'
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'o'
- break
- endtry
- endwhile
-
- while 1
- try
- try
- Xpath 'p'
- call interrupt()
- call assert_report('should not get here')
- catch /do_not_catch/
- call interrupt()
- call assert_report('should not get here')
- catch /^Vim:Interrupt$/
- Xpath 'q'
- finally
- Xpath 'r'
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 's'
- break
- endtry
- endwhile
-
- Xpath 't'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefghijklmnopqrst', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 62: Catching error exceptions {{{1
-"
-" An error inside a :try/:endtry region is converted to an exception
-" and can be caught. The error exception has a "Vim(cmdname):" prefix
-" where cmdname is the name of the failing command, or a "Vim:" prefix
-" if no command name is known. The "Vim" prefixes cannot be faked.
-"-------------------------------------------------------------------------------
-
-func Test_catch_err_exception_1()
- XpathINIT
- while 1
- try
- try
- let caught = 0
- unlet novar
- catch /^Vim(unlet):/
- Xpath 'a'
- let caught = 1
- let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "")
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- call assert_match('E108: No such variable: "novar"', v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abc', g:Xpath)
-endfunc
-
-func Test_catch_err_exception_2()
- XpathINIT
- while 1
- try
- try
- let caught = 0
- throw novar " error in :throw
- catch /^Vim(throw):/
- Xpath 'a'
- let caught = 1
- let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "")
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- call assert_match('E121: Undefined variable: novar', v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abc', g:Xpath)
-endfunc
-
-func Test_catch_err_exception_3()
- XpathINIT
- while 1
- try
- try
- let caught = 0
- throw "Vim:faked" " error: cannot fake Vim exception
- catch /^Vim(throw):/
- Xpath 'a'
- let caught = 1
- let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "")
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- call assert_match("E608: Cannot :throw exceptions with 'Vim' prefix",
- \ v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abc', g:Xpath)
-endfunc
-
-func Test_catch_err_exception_4()
- XpathINIT
- func F()
- while 1
- " Missing :endwhile
- endfunc
-
- while 1
- try
- try
- let caught = 0
- call F()
- catch /^Vim(endfunction):/
- Xpath 'a'
- let caught = 1
- let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "")
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- call assert_match("E170: Missing :endwhile", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abc', g:Xpath)
- delfunc F
-endfunc
-
-func Test_catch_err_exception_5()
- XpathINIT
- func F()
- while 1
- " Missing :endwhile
- endfunc
-
- while 1
- try
- try
- let caught = 0
- ExecAsScript F
- catch /^Vim:/
- Xpath 'a'
- let caught = 1
- let v:errmsg = substitute(v:exception, '^Vim:', '', "")
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- call assert_match("E170: Missing :endwhile", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abc', g:Xpath)
- delfunc F
-endfunc
-
-func Test_catch_err_exception_6()
- XpathINIT
- func G()
- call G()
- endfunc
-
- while 1
- try
- let mfd_save = &mfd
- set mfd=3
- try
- let caught = 0
- call G()
- catch /^Vim(call):/
- Xpath 'a'
- let caught = 1
- let v:errmsg = substitute(v:exception, '^Vim(call):', '', "")
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- let &mfd = mfd_save
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abc', g:Xpath)
- delfunc G
-endfunc
-
-func Test_catch_err_exception_7()
- XpathINIT
- func H()
- return H()
- endfunc
-
- while 1
- try
- let mfd_save = &mfd
- set mfd=3
- try
- let caught = 0
- call H()
- catch /^Vim(return):/
- Xpath 'a'
- let caught = 1
- let v:errmsg = substitute(v:exception, '^Vim(return):', '', "")
- finally
- Xpath 'b'
- call assert_equal(1, caught)
- call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- let &mfd = mfd_save
- break " discard error for $VIMNOERRTHROW
- endtry
- call assert_report('should not get here')
- endwhile
-
- call assert_equal('abc', g:Xpath)
- delfunc H
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 63: Suppressing error exceptions by :silent!. {{{1
-"
-" A :silent! command inside a :try/:endtry region suppresses the
-" conversion of errors to an exception and the immediate abortion on
-" error. When the commands executed by the :silent! themselves open
-" a new :try/:endtry region, conversion of errors to exception and
-" immediate abortion is switched on again - until the next :silent!
-" etc. The :silent! has the effect of setting v:errmsg to the error
-" message text (without displaying it) and continuing with the next
-" script line.
-"
-" When a command triggering autocommands is executed by :silent!
-" inside a :try/:endtry, the autocommand execution is not suppressed
-" on error.
-"
-" This test reuses the function MSG() from the previous test.
-"-------------------------------------------------------------------------------
-
-func Test_silent_exception()
- XpathINIT
- XloopINIT
- let g:taken = ""
-
- func S(n) abort
- XloopNEXT
- let g:taken = g:taken . "E" . a:n
- let v:errmsg = ""
- exec "asdf" . a:n
-
- " Check that ":silent!" continues:
- Xloop 'a'
-
- " Check that ":silent!" sets "v:errmsg":
- call assert_match("E492: Not an editor command", v:errmsg)
- endfunc
-
- func Foo()
- while 1
- try
- try
- let caught = 0
- " This is not silent:
- call S(3)
- catch /^Vim:/
- Xpath 'b'
- let caught = 1
- let errmsg3 = substitute(v:exception, '^Vim:', '', "")
- silent! call S(4)
- finally
- call assert_equal(1, caught)
- Xpath 'c'
- call assert_match("E492: Not an editor command", errmsg3)
- silent! call S(5)
- " Break out of try conditionals that cover ":silent!". This also
- " discards the aborting error when $VIMNOERRTHROW is non-zero.
- break
- endtry
- catch /.*/
- call assert_report('should not get here')
- endtry
- endwhile
- " This is a double ":silent!" (see caller).
- silent! call S(6)
- endfunc
-
- func Bar()
- try
- silent! call S(2)
- silent! execute "call Foo() | call S(7)"
- silent! call S(8)
- endtry " normal end of try cond that covers ":silent!"
- " This has a ":silent!" from the caller:
- call S(9)
- endfunc
-
- silent! call S(1)
- silent! call Bar()
- silent! call S(10)
-
- call assert_equal("E1E2E3E4E5E6E7E8E9E10", g:taken)
-
- augroup TMP
- au!
- autocmd BufWritePost * Xpath 'd'
- augroup END
-
- Xpath 'e'
- silent! write /i/m/p/o/s/s/i/b/l/e
- Xpath 'f'
-
- call assert_equal('a2a3ba5ca6a7a8a9a10a11edf', g:Xpath)
-
- augroup TMP
- au!
- augroup END
- augroup! TMP
- delfunction S
- delfunction Foo
- delfunction Bar
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 64: Error exceptions after error, interrupt or :throw {{{1
-"
-" When an error occurs after an interrupt or a :throw but before
-" a matching :catch is reached, all following :catches of that try
-" block are ignored, but the error exception can be caught by the next
-" surrounding try conditional. Any previous error exception is
-" discarded. An error is ignored when there is a previous error that
-" has not been caught.
-"-------------------------------------------------------------------------------
-
-func Test_exception_after_error_1()
- XpathINIT
- while 1
- try
- try
- Xpath 'a'
- let caught = 0
- while 1
- if 1
- " Missing :endif
- endwhile " throw error exception
- catch /^Vim(/
- Xpath 'b'
- let caught = 1
- finally
- Xpath 'c'
- call assert_equal(1, caught)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'd'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abcd', g:Xpath)
-endfunc
-
-func Test_exception_after_error_2()
- XpathINIT
- while 1
- try
- try
- Xpath 'a'
- let caught = 0
- try
- if 1
- " Missing :endif
- catch /.*/ " throw error exception
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- catch /^Vim(/
- Xpath 'b'
- let caught = 1
- finally
- Xpath 'c'
- call assert_equal(1, caught)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'd'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abcd', g:Xpath)
-endfunc
-
-func Test_exception_after_error_3()
- XpathINIT
- while 1
- try
- try
- let caught = 0
- try
- Xpath 'a'
- call interrupt()
- catch /do_not_catch/
- call assert_report('should not get here')
- if 1
- " Missing :endif
- catch /.*/ " throw error exception
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- catch /^Vim(/
- Xpath 'b'
- let caught = 1
- finally
- Xpath 'c'
- call assert_equal(1, caught)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'd'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abcd', g:Xpath)
-endfunc
-
-func Test_exception_after_error_4()
- XpathINIT
- while 1
- try
- try
- let caught = 0
- try
- Xpath 'a'
- throw "x"
- catch /do_not_catch/
- call assert_report('should not get here')
- if 1
- " Missing :endif
- catch /x/ " throw error exception
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- catch /^Vim(/
- Xpath 'b'
- let caught = 1
- finally
- Xpath 'c'
- call assert_equal(1, caught)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'd'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abcd', g:Xpath)
-endfunc
-
-func Test_exception_after_error_5()
- XpathINIT
- while 1
- try
- try
- let caught = 0
- Xpath 'a'
- endif " :endif without :if; throw error exception
- if 1
- " Missing :endif
- catch /do_not_catch/ " ignore new error
- call assert_report('should not get here')
- catch /^Vim(endif):/
- Xpath 'b'
- let caught = 1
- catch /^Vim(/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- call assert_equal(1, caught)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'd'
- break
- endtry
- call assert_report('should not get here')
- endwhile
- call assert_equal('abcd', g:Xpath)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 65: Errors in the /pattern/ argument of a :catch {{{1
-"
-" On an error in the /pattern/ argument of a :catch, the :catch does
-" not match. Any following :catches of the same :try/:endtry don't
-" match either. Finally clauses are executed.
-"-------------------------------------------------------------------------------
-
-func Test_catch_pattern_error()
- CheckEnglish
- XpathINIT
-
- try
- try
- Xpath 'a'
- throw "oops"
- catch /^oops$/
- Xpath 'b'
- catch /\)/ " not checked; exception has already been caught
- call assert_report('should not get here')
- endtry
- Xpath 'c'
- catch /.*/
- call assert_report('should not get here')
- endtry
- call assert_equal('abc', g:Xpath)
-
- XpathINIT
- func F()
- try
- try
- try
- Xpath 'a'
- throw "ab"
- catch /abc/ " does not catch
- call assert_report('should not get here')
- catch /\)/ " error; discards exception
- call assert_report('should not get here')
- catch /.*/ " not checked
- call assert_report('should not get here')
- finally
- Xpath 'b'
- endtry
- call assert_report('should not get here')
- catch /^ab$/ " checked, but original exception is discarded
- call assert_report('should not get here')
- catch /^Vim(catch):/
- Xpath 'c'
- call assert_match('Vim(catch):E475: Invalid argument:', v:exception)
- finally
- Xpath 'd'
- endtry
- Xpath 'e'
- catch /.*/
- call assert_report('should not get here')
- endtry
- Xpath 'f'
- endfunc
-
- call F()
- call assert_equal('abcdef', g:Xpath)
-
- delfunc F
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 66: Stop range :call on error, interrupt, or :throw {{{1
-"
-" When a function which is multiply called for a range since it
-" doesn't handle the range itself has an error in a command
-" dynamically enclosed by :try/:endtry or gets an interrupt or
-" executes a :throw, no more calls for the remaining lines in the
-" range are made. On an error in a command not dynamically enclosed
-" by :try/:endtry, the function is executed again for the remaining
-" lines in the range.
-"-------------------------------------------------------------------------------
-
-func Test_stop_range_on_error()
- let test =<< trim [CODE]
- let file = tempname()
- exec "edit" file
- call setline(1, ['line 1', 'line 2', 'line 3'])
- let taken = ""
- let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)"
-
- func F(reason, n) abort
- let g:taken = g:taken .. "F" .. a:n ..
- \ substitute(a:reason, '\(\l\).*', '\u\1', "") ..
- \ "(" .. line(".") .. ")"
-
- if a:reason == "error"
- asdf
- elseif a:reason == "interrupt"
- call interrupt()
- elseif a:reason == "throw"
- throw "xyz"
- elseif a:reason == "aborting error"
- XloopNEXT
- call assert_equal(g:taken, g:expected)
- try
- bwipeout!
- call delete(g:file)
- asdf
- endtry
- endif
- endfunc
-
- func G(reason, n)
- let g:taken = g:taken .. "G" .. a:n ..
- \ substitute(a:reason, '\(\l\).*', '\u\1', "")
- 1,3call F(a:reason, a:n)
- endfunc
-
- Xpath 'a'
- call G("error", 1)
- try
- Xpath 'b'
- try
- call G("error", 2)
- call assert_report('should not get here')
- finally
- Xpath 'c'
- try
- call G("interrupt", 3)
- call assert_report('should not get here')
- finally
- Xpath 'd'
- try
- call G("throw", 4)
- call assert_report('should not get here')
- endtry
- endtry
- endtry
- catch /xyz/
- Xpath 'e'
- catch /.*/
- call assert_report('should not get here')
- endtry
- Xpath 'f'
- call G("aborting error", 5)
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdef', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 67: :throw across :call command {{{1
-"
-" On a call command, an exception might be thrown when evaluating the
-" function name, during evaluation of the arguments, or when the
-" function is being executed. The exception can be caught by the
-" caller.
-"-------------------------------------------------------------------------------
-
-func THROW(x, n)
- if a:n == 1
- Xpath 'A'
- elseif a:n == 2
- Xpath 'B'
- elseif a:n == 3
- Xpath 'C'
- endif
- throw a:x
-endfunc
-
-func NAME(x, n)
- if a:n == 1
- call assert_report('should not get here')
- elseif a:n == 2
- Xpath 'D'
- elseif a:n == 3
- Xpath 'E'
- elseif a:n == 4
- Xpath 'F'
- endif
- return a:x
-endfunc
-
-func ARG(x, n)
- if a:n == 1
- call assert_report('should not get here')
- elseif a:n == 2
- call assert_report('should not get here')
- elseif a:n == 3
- Xpath 'G'
- elseif a:n == 4
- Xpath 'I'
- endif
- return a:x
-endfunc
-
-func Test_throw_across_call_cmd()
- XpathINIT
-
- func F(x, n)
- if a:n == 2
- call assert_report('should not get here')
- elseif a:n == 4
- Xpath 'a'
- endif
- endfunc
-
- while 1
- try
- let v:errmsg = ""
-
- while 1
- try
- Xpath 'b'
- call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1)
- call assert_report('should not get here')
- catch /^name$/
- Xpath 'c'
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
-
- while 1
- try
- Xpath 'd'
- call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2)
- call assert_report('should not get here')
- catch /^arg$/
- Xpath 'e'
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
-
- while 1
- try
- Xpath 'f'
- call {NAME("THROW", 3)}(ARG("call", 3), 3)
- call assert_report('should not get here')
- catch /^call$/
- Xpath 'g'
- catch /^0$/ " default return value
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
-
- while 1
- try
- Xpath 'h'
- call {NAME("F", 4)}(ARG(4711, 4), 4)
- Xpath 'i'
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
-
- catch /^0$/ " default return value
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
-
- call assert_equal('bAcdDBefEGCghFIai', g:Xpath)
- delfunction F
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 68: :throw across function calls in expressions {{{1
-"
-" On a function call within an expression, an exception might be
-" thrown when evaluating the function name, during evaluation of the
-" arguments, or when the function is being executed. The exception
-" can be caught by the caller.
-"
-" This test reuses the functions THROW(), NAME(), and ARG() from the
-" previous test.
-"-------------------------------------------------------------------------------
-
-func Test_throw_across_call_expr()
- XpathINIT
-
- func F(x, n)
- if a:n == 2
- call assert_report('should not get here')
- elseif a:n == 4
- Xpath 'a'
- endif
- return a:x
- endfunction
-
- while 1
- try
- let error = 0
- let v:errmsg = ""
-
- while 1
- try
- Xpath 'b'
- let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1)
- call assert_report('should not get here')
- catch /^name$/
- Xpath 'c'
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
- call assert_true(!exists('var1'))
-
- while 1
- try
- Xpath 'd'
- let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2)
- call assert_report('should not get here')
- catch /^arg$/
- Xpath 'e'
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
- call assert_true(!exists('var2'))
-
- while 1
- try
- Xpath 'f'
- let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3)
- call assert_report('should not get here')
- catch /^call$/
- Xpath 'g'
- catch /^0$/ " default return value
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
- call assert_true(!exists('var3'))
-
- while 1
- try
- Xpath 'h'
- let var4 = {NAME("F", 4)}(ARG(4711, 4), 4)
- Xpath 'i'
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- let v:errmsg = ""
- break
- endtry
- endwhile
- call assert_true(exists('var4') && var4 == 4711)
-
- catch /^0$/ " default return value
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- finally
- call assert_equal("", v:errmsg)
- break
- endtry
- endwhile
-
- call assert_equal('bAcdDBefEGCghFIai', g:Xpath)
- delfunc F
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 76: Errors, interrupts, :throw during expression evaluation {{{1
-"
-" When a function call made during expression evaluation is aborted
-" due to an error inside a :try/:endtry region or due to an interrupt
-" or a :throw, the expression evaluation is aborted as well. No
-" message is displayed for the cancelled expression evaluation. On an
-" error not inside :try/:endtry, the expression evaluation continues.
-"-------------------------------------------------------------------------------
-
-func Test_expr_eval_error()
- let test =<< trim [CODE]
- let taken = ""
-
- func ERR(n)
- let g:taken = g:taken .. "E" .. a:n
- asdf
- endfunc
-
- func ERRabort(n) abort
- let g:taken = g:taken .. "A" .. a:n
- asdf
- endfunc " returns -1; may cause follow-up msg for illegal var/func name
-
- func WRAP(n, arg)
- let g:taken = g:taken .. "W" .. a:n
- let g:saved_errmsg = v:errmsg
- return arg
- endfunc
-
- func INT(n)
- let g:taken = g:taken .. "I" .. a:n
- call interrupt()
- endfunc
-
- func THR(n)
- let g:taken = g:taken .. "T" .. a:n
- throw "should not be caught"
- endfunc
-
- func CONT(n)
- let g:taken = g:taken .. "C" .. a:n
- endfunc
-
- func MSG(n)
- let g:taken = g:taken .. "M" .. a:n
- let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg
- let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf"
- call assert_match(msgptn, errmsg)
- let v:errmsg = ""
- let g:saved_errmsg = ""
- endfunc
-
- let v:errmsg = ""
-
- try
- let t = 1
- while t <= 9
- Xloop 'a'
- try
- if t == 1
- let v{ERR(t) + CONT(t)} = 0
- elseif t == 2
- let v{ERR(t) + CONT(t)}
- elseif t == 3
- let var = exists('v{ERR(t) + CONT(t)}')
- elseif t == 4
- unlet v{ERR(t) + CONT(t)}
- elseif t == 5
- function F{ERR(t) + CONT(t)}()
- endfunction
- elseif t == 6
- function F{ERR(t) + CONT(t)}
- elseif t == 7
- let var = exists('*F{ERR(t) + CONT(t)}')
- elseif t == 8
- delfunction F{ERR(t) + CONT(t)}
- elseif t == 9
- let var = ERR(t) + CONT(t)
- endif
- catch /asdf/
- " v:errmsg is not set when the error message is converted to an
- " exception. Set it to the original error message.
- let v:errmsg = substitute(v:exception, '^Vim:', '', "")
- catch /^Vim\((\a\+)\)\=:/
- " An error exception has been thrown after the original error.
- let v:errmsg = ""
- finally
- call MSG(t)
- let t = t + 1
- XloopNEXT
- continue " discard an aborting error
- endtry
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
-
- try
- let t = 10
- while t <= 18
- Xloop 'b'
- try
- if t == 10
- let v{INT(t) + CONT(t)} = 0
- elseif t == 11
- let v{INT(t) + CONT(t)}
- elseif t == 12
- let var = exists('v{INT(t) + CONT(t)}')
- elseif t == 13
- unlet v{INT(t) + CONT(t)}
- elseif t == 14
- function F{INT(t) + CONT(t)}()
- endfunction
- elseif t == 15
- function F{INT(t) + CONT(t)}
- elseif t == 16
- let var = exists('*F{INT(t) + CONT(t)}')
- elseif t == 17
- delfunction F{INT(t) + CONT(t)}
- elseif t == 18
- let var = INT(t) + CONT(t)
- endif
- catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/
- " An error exception has been triggered after the interrupt.
- let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
- finally
- call MSG(t)
- let t = t + 1
- XloopNEXT
- continue " discard interrupt
- endtry
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
-
- try
- let t = 19
- while t <= 27
- Xloop 'c'
- try
- if t == 19
- let v{THR(t) + CONT(t)} = 0
- elseif t == 20
- let v{THR(t) + CONT(t)}
- elseif t == 21
- let var = exists('v{THR(t) + CONT(t)}')
- elseif t == 22
- unlet v{THR(t) + CONT(t)}
- elseif t == 23
- function F{THR(t) + CONT(t)}()
- endfunction
- elseif t == 24
- function F{THR(t) + CONT(t)}
- elseif t == 25
- let var = exists('*F{THR(t) + CONT(t)}')
- elseif t == 26
- delfunction F{THR(t) + CONT(t)}
- elseif t == 27
- let var = THR(t) + CONT(t)
- endif
- catch /^Vim\((\a\+)\)\=:/
- " An error exception has been triggered after the :throw.
- let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
- finally
- call MSG(t)
- let t = t + 1
- XloopNEXT
- continue " discard exception
- endtry
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
-
- let v{ERR(28) + CONT(28)} = 0
- call MSG(28)
- let v{ERR(29) + CONT(29)}
- call MSG(29)
- let var = exists('v{ERR(30) + CONT(30)}')
- call MSG(30)
- unlet v{ERR(31) + CONT(31)}
- call MSG(31)
- function F{ERR(32) + CONT(32)}()
- endfunction
- call MSG(32)
- function F{ERR(33) + CONT(33)}
- call MSG(33)
- let var = exists('*F{ERR(34) + CONT(34)}')
- call MSG(34)
- delfunction F{ERR(35) + CONT(35)}
- call MSG(35)
- let var = ERR(36) + CONT(36)
- call MSG(36)
-
- let saved_errmsg = ""
-
- let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0
- call MSG(37)
- let v{WRAP(38, ERRabort(38)) + CONT(38)}
- call MSG(38)
- let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}')
- call MSG(39)
- unlet v{WRAP(40, ERRabort(40)) + CONT(40)}
- call MSG(40)
- function F{WRAP(41, ERRabort(41)) + CONT(41)}()
- endfunction
- call MSG(41)
- function F{WRAP(42, ERRabort(42)) + CONT(42)}
- call MSG(42)
- let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}')
- call MSG(43)
- delfunction F{WRAP(44, ERRabort(44)) + CONT(44)}
- call MSG(44)
- let var = ERRabort(45) + CONT(45)
- call MSG(45)
- Xpath 'd'
-
- let expected = ""
- \ .. "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9"
- \ .. "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18"
- \ .. "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27"
- \ .. "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33"
- \ .. "E34C34M34E35C35M35E36C36M36"
- \ .. "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41"
- \ .. "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45"
- call assert_equal(expected, taken)
- [CODE]
- let verify =<< trim [CODE]
- let expected = "a1a2a3a4a5a6a7a8a9"
- \ .. "b10b11b12b13b14b15b16b17b18"
- \ .. "c19c20c21c22c23c24c25c26c27d"
- call assert_equal(expected, g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1
-"
-" When a function call made during evaluation of an expression in
-" braces as part of a function name after ":function" is aborted due
-" to an error inside a :try/:endtry region or due to an interrupt or
-" a :throw, the expression evaluation is aborted as well, and the
-" function definition is ignored, skipping all commands to the
-" ":endfunction". On an error not inside :try/:endtry, the expression
-" evaluation continues and the function gets defined, and can be
-" called and deleted.
-"-------------------------------------------------------------------------------
-func Test_brace_expr_error()
- let test =<< trim [CODE]
- func ERR() abort
- Xloop 'a'
- asdf
- endfunc " returns -1
-
- func OK()
- Xloop 'b'
- let v:errmsg = ""
- return 0
- endfunc
-
- let v:errmsg = ""
-
- Xpath 'c'
- func F{1 + ERR() + OK()}(arg)
- " F0 should be defined.
- if exists("a:arg") && a:arg == "calling"
- Xpath 'd'
- else
- call assert_report('should not get here')
- endif
- endfunction
- call assert_equal("", v:errmsg)
- XloopNEXT
-
- Xpath 'e'
- call F{1 + ERR() + OK()}("calling")
- call assert_equal("", v:errmsg)
- XloopNEXT
-
- Xpath 'f'
- delfunction F{1 + ERR() + OK()}
- call assert_equal("", v:errmsg)
- XloopNEXT
-
- try
- while 1
- try
- Xpath 'g'
- func G{1 + ERR() + OK()}(arg)
- " G0 should not be defined, and the function body should be
- " skipped.
- call assert_report('should not get here')
- " Use an unmatched ":finally" to check whether the body is
- " skipped when an error occurs in ERR(). This works whether or
- " not the exception is converted to an exception.
- finally
- call assert_report('should not get here')
- endtry
- try
- call assert_report('should not get here')
- endfunction
-
- call assert_report('should not get here')
- catch /asdf/
- " Jumped to when the function is not defined and the body is
- " skipped.
- Xpath 'h'
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'i'
- break
- endtry " jumped to when the body is not skipped
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('ca1b1ea2b2dfa3b3ga4hi', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 78: Messages on parsing errors in expression evaluation {{{1
-"
-" When an expression evaluation detects a parsing error, an error
-" message is given and converted to an exception, and the expression
-" evaluation is aborted.
-"-------------------------------------------------------------------------------
-func Test_expr_eval_error_msg()
- CheckEnglish
-
- let test =<< trim [CODE]
- let taken = ""
-
- func F(n)
- let g:taken = g:taken . "F" . a:n
- endfunc
-
- func MSG(n, enr, emsg)
- let g:taken = g:taken . "M" . a:n
- call assert_match('^' .. a:enr .. ':', v:errmsg)
- call assert_match(a:emsg, v:errmsg)
- endfunc
-
- func CONT(n)
- let g:taken = g:taken . "C" . a:n
- endfunc
-
- let v:errmsg = ""
- try
- let t = 1
- while t <= 14
- let g:taken = g:taken . "T" . t
- let v:errmsg = ""
- try
- if t == 1
- let v{novar + CONT(t)} = 0
- elseif t == 2
- let v{novar + CONT(t)}
- elseif t == 3
- let var = exists('v{novar + CONT(t)}')
- elseif t == 4
- unlet v{novar + CONT(t)}
- elseif t == 5
- function F{novar + CONT(t)}()
- endfunction
- elseif t == 6
- function F{novar + CONT(t)}
- elseif t == 7
- let var = exists('*F{novar + CONT(t)}')
- elseif t == 8
- delfunction F{novar + CONT(t)}
- elseif t == 9
- echo novar + CONT(t)
- elseif t == 10
- echo v{novar + CONT(t)}
- elseif t == 11
- echo F{novar + CONT(t)}
- elseif t == 12
- let var = novar + CONT(t)
- elseif t == 13
- let var = v{novar + CONT(t)}
- elseif t == 14
- let var = F{novar + CONT(t)}()
- endif
- catch /^Vim\((\a\+)\)\=:/
- Xloop 'a'
- " v:errmsg is not set when the error message is converted to an
- " exception. Set it to the original error message.
- let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
- finally
- Xloop 'b'
- if t <= 8 && t != 3 && t != 7
- call MSG(t, 'E475', 'Invalid argument\>')
- else
- call MSG(t, 'E121', "Undefined variable")
- endif
- let t = t + 1
- XloopNEXT
- continue " discard an aborting error
- endtry
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
-
- func T(n, expr, enr, emsg)
- try
- let g:taken = g:taken . "T" . a:n
- let v:errmsg = ""
- try
- execute "let var = " . a:expr
- catch /^Vim\((\a\+)\)\=:/
- Xloop 'c'
- " v:errmsg is not set when the error message is converted to an
- " exception. Set it to the original error message.
- let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
- finally
- Xloop 'd'
- call MSG(a:n, a:enr, a:emsg)
- XloopNEXT
- " Discard an aborting error:
- return
- endtry
- catch /.*/
- call assert_report('should not get here')
- endtry
- endfunc
-
- call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function")
- call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments")
- call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments")
- call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments")
- call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'")
- call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'")
- call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression")
- call T(22, '1 2 + CONT(22)', 'E488', "Trailing characters: 2 +")
- call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'")
- call T(24, '("abc) + CONT(24)', 'E114', "Missing quote")
- call T(25, "('abc) + CONT(25)", 'E115', "Missing quote")
- call T(26, '& + CONT(26)', 'E112', "Option name missing")
- call T(27, '&asdf + CONT(27)', 'E113', "Unknown option")
-
- let expected = ""
- \ .. "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14"
- \ .. "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25"
- \ .. "T26M26T27M27"
-
- call assert_equal(expected, taken)
- [CODE]
- let verify =<< trim [CODE]
- let expected = "a1b1a2b2a3b3a4b4a5b5a6b6a7b7a8b8a9b9a10b10a11b11a12b12"
- \ .. "a13b13a14b14c15d15c16d16c17d17c18d18c19d19c20d20"
- \ .. "c21d21c22d22c23d23c24d24c25d25c26d26c27d27"
- call assert_equal(expected, g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 79: Throwing one of several errors for the same command {{{1
-"
-" When several errors appear in a row (for instance during expression
-" evaluation), the first as the most specific one is used when
-" throwing an error exception. If, however, a syntax error is
-" detected afterwards, this one is used for the error exception.
-" On a syntax error, the next command is not executed, on a normal
-" error, however, it is (relevant only in a function without the
-" "abort" flag). v:errmsg is not set.
-"
-" If throwing error exceptions is configured off, v:errmsg is always
-" set to the latest error message, that is, to the more general
-" message or the syntax error, respectively.
-"-------------------------------------------------------------------------------
-func Test_throw_multi_error()
- CheckEnglish
-
- let test =<< trim [CODE]
- func NEXT(cmd)
- exec a:cmd . " | Xloop 'a'"
- endfun
-
- call NEXT('echo novar') " (checks nextcmd)
- XloopNEXT
- call NEXT('let novar #') " (skips nextcmd)
- XloopNEXT
- call NEXT('unlet novar #') " (skips nextcmd)
- XloopNEXT
- call NEXT('let {novar}') " (skips nextcmd)
- XloopNEXT
- call NEXT('unlet{ novar}') " (skips nextcmd)
-
- call assert_equal('a1', g:Xpath)
- XpathINIT
- XloopINIT
-
- func EXEC(cmd)
- exec a:cmd
- endfunc
-
- try
- while 1 " dummy loop
- try
- let v:errmsg = ""
- call EXEC('echo novar') " normal error
- catch /^Vim\((\a\+)\)\=:/
- Xpath 'b'
- call assert_match('E121: Undefined variable: novar', v:exception)
- finally
- Xpath 'c'
- call assert_equal("", v:errmsg)
- break
- endtry
- endwhile
-
- Xpath 'd'
- let cmd = "let"
- while cmd != ""
- try
- let v:errmsg = ""
- call EXEC(cmd . ' novar #') " normal plus syntax error
- catch /^Vim\((\a\+)\)\=:/
- Xloop 'e'
- call assert_match('E488: Trailing characters', v:exception)
- finally
- Xloop 'f'
- call assert_equal("", v:errmsg)
- if cmd == "let"
- let cmd = "unlet"
- else
- let cmd = ""
- endif
- XloopNEXT
- continue
- endtry
- endwhile
-
- Xpath 'g'
- let cmd = "let"
- while cmd != ""
- try
- let v:errmsg = ""
- call EXEC(cmd . ' {novar}') " normal plus syntax error
- catch /^Vim\((\a\+)\)\=:/
- Xloop 'h'
- call assert_match('E475: Invalid argument: {novar}', v:exception)
- finally
- Xloop 'i'
- call assert_equal("", v:errmsg)
- if cmd == "let"
- let cmd = "unlet"
- else
- let cmd = ""
- endif
- XloopNEXT
- continue
- endtry
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
- Xpath 'j'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('bcde1f1e2f2gh3i3h4i4j', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 80: Syntax error in expression for illegal :elseif {{{1
-"
-" If there is a syntax error in the expression after an illegal
-" :elseif, an error message is given (or an error exception thrown)
-" for the illegal :elseif rather than the expression error.
-"-------------------------------------------------------------------------------
-func Test_if_syntax_error()
- CheckEnglish
-
- let test =<< trim [CODE]
- let v:errmsg = ""
- if 0
- else
- elseif 1 ||| 2
- endif
- Xpath 'a'
- call assert_match('E584: :elseif after :else', v:errmsg)
-
- let v:errmsg = ""
- if 1
- else
- elseif 1 ||| 2
- endif
- Xpath 'b'
- call assert_match('E584: :elseif after :else', v:errmsg)
-
- let v:errmsg = ""
- elseif 1 ||| 2
- Xpath 'c'
- call assert_match('E582: :elseif without :if', v:errmsg)
-
- let v:errmsg = ""
- while 1
- elseif 1 ||| 2
- endwhile
- Xpath 'd'
- call assert_match('E582: :elseif without :if', v:errmsg)
-
- while 1
- try
- try
- let v:errmsg = ""
- if 0
- else
- elseif 1 ||| 2
- endif
- catch /^Vim\((\a\+)\)\=:/
- Xpath 'e'
- call assert_match('E584: :elseif after :else', v:exception)
- finally
- Xpath 'f'
- call assert_equal("", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'g'
- break
- endtry
- endwhile
-
- while 1
- try
- try
- let v:errmsg = ""
- if 1
- else
- elseif 1 ||| 2
- endif
- catch /^Vim\((\a\+)\)\=:/
- Xpath 'h'
- call assert_match('E584: :elseif after :else', v:exception)
- finally
- Xpath 'i'
- call assert_equal("", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'j'
- break
- endtry
- endwhile
-
- while 1
- try
- try
- let v:errmsg = ""
- elseif 1 ||| 2
- catch /^Vim\((\a\+)\)\=:/
- Xpath 'k'
- call assert_match('E582: :elseif without :if', v:exception)
- finally
- Xpath 'l'
- call assert_equal("", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'm'
- break
- endtry
- endwhile
-
- while 1
- try
- try
- let v:errmsg = ""
- while 1
- elseif 1 ||| 2
- endwhile
- catch /^Vim\((\a\+)\)\=:/
- Xpath 'n'
- call assert_match('E582: :elseif without :if', v:exception)
- finally
- Xpath 'o'
- call assert_equal("", v:errmsg)
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'p'
- break
- endtry
- endwhile
- Xpath 'q'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefghijklmnopq', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 81: Discarding exceptions after an error or interrupt {{{1
-"
-" When an exception is thrown from inside a :try conditional without
-" :catch and :finally clauses and an error or interrupt occurs before
-" the :endtry is reached, the exception is discarded.
-"-------------------------------------------------------------------------------
-
-func Test_discard_exception_after_error_1()
- let test =<< trim [CODE]
- try
- Xpath 'a'
- try
- Xpath 'b'
- throw "arrgh"
- call assert_report('should not get here')
- if 1
- call assert_report('should not get here')
- " error after :throw: missing :endif
- endtry
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('ab', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-" interrupt the code before the endtry is invoked
-func Test_discard_exception_after_error_2()
- XpathINIT
- let lines =<< trim [CODE]
- try
- Xpath 'a'
- try
- Xpath 'b'
- throw "arrgh"
- call assert_report('should not get here')
- endtry " interrupt here
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- call writefile(lines, 'Xscript')
-
- breakadd file 7 Xscript
- try
- let caught_intr = 0
- debuggreedy
- call feedkeys(":source Xscript\<CR>quit\<CR>", "xt")
- catch /^Vim:Interrupt$/
- call assert_match('Xscript, line 7', v:throwpoint)
- let caught_intr = 1
- endtry
- 0debuggreedy
- call assert_equal(1, caught_intr)
- call assert_equal('ab', g:Xpath)
- breakdel *
- call delete('Xscript')
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 82: Ignoring :catch clauses after an error or interrupt {{{1
-"
-" When an exception is thrown and an error or interrupt occurs before
-" the matching :catch clause is reached, the exception is discarded
-" and the :catch clause is ignored (also for the error or interrupt
-" exception being thrown then).
-"-------------------------------------------------------------------------------
-
-func Test_ignore_catch_after_error_1()
- let test =<< trim [CODE]
- try
- try
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')
- if 1
- call assert_report('should not get here')
- " error after :throw: missing :endif
- catch /.*/
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-func Test_ignore_catch_after_error_2()
- let test =<< trim [CODE]
- func E()
- try
- try
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')
- if 1
- call assert_report('should not get here')
- " error after :throw: missing :endif
- catch /.*/
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- endfunc
-
- call E()
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('a', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-" interrupt right before a catch is invoked in a script
-func Test_ignore_catch_after_intr_1()
- XpathINIT
- let lines =<< trim [CODE]
- try
- try
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')
- catch /.*/ " interrupt here
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- call writefile(lines, 'Xscript')
-
- breakadd file 6 Xscript
- try
- let caught_intr = 0
- debuggreedy
- call feedkeys(":source Xscript\<CR>quit\<CR>", "xt")
- catch /^Vim:Interrupt$/
- call assert_match('Xscript, line 6', v:throwpoint)
- let caught_intr = 1
- endtry
- 0debuggreedy
- call assert_equal(1, caught_intr)
- call assert_equal('a', g:Xpath)
- breakdel *
- call delete('Xscript')
-endfunc
-
-" interrupt right before a catch is invoked inside a function.
-func Test_ignore_catch_after_intr_2()
- XpathINIT
- func F()
- try
- try
- Xpath 'a'
- throw "arrgh"
- call assert_report('should not get here')
- catch /.*/ " interrupt here
- call assert_report('should not get here')
- catch /.*/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- endfunc
-
- breakadd func 6 F
- try
- let caught_intr = 0
- debuggreedy
- call feedkeys(":call F()\<CR>quit\<CR>", "xt")
- catch /^Vim:Interrupt$/
- call assert_match('\.F, line 6', v:throwpoint)
- let caught_intr = 1
- endtry
- 0debuggreedy
- call assert_equal(1, caught_intr)
- call assert_equal('a', g:Xpath)
- breakdel *
- delfunc F
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 83: Executing :finally clauses after an error or interrupt {{{1
-"
-" When an exception is thrown and an error or interrupt occurs before
-" the :finally of the innermost :try is reached, the exception is
-" discarded and the :finally clause is executed.
-"-------------------------------------------------------------------------------
-
-func Test_finally_after_error()
- let test =<< trim [CODE]
- try
- Xpath 'a'
- try
- Xpath 'b'
- throw "arrgh"
- call assert_report('should not get here')
- if 1
- call assert_report('should not get here')
- " error after :throw: missing :endif
- finally
- Xpath 'c'
- endtry
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abc', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-" interrupt the code right before the finally is invoked
-func Test_finally_after_intr()
- XpathINIT
- let lines =<< trim [CODE]
- try
- Xpath 'a'
- try
- Xpath 'b'
- throw "arrgh"
- call assert_report('should not get here')
- finally " interrupt here
- Xpath 'c'
- endtry
- call assert_report('should not get here')
- catch /arrgh/
- call assert_report('should not get here')
- endtry
- call assert_report('should not get here')
- [CODE]
- call writefile(lines, 'Xscript')
-
- breakadd file 7 Xscript
- try
- let caught_intr = 0
- debuggreedy
- call feedkeys(":source Xscript\<CR>quit\<CR>", "xt")
- catch /^Vim:Interrupt$/
- call assert_match('Xscript, line 7', v:throwpoint)
- let caught_intr = 1
- endtry
- 0debuggreedy
- call assert_equal(1, caught_intr)
- call assert_equal('abc', g:Xpath)
- breakdel *
- call delete('Xscript')
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 84: Exceptions in autocommand sequences. {{{1
-"
-" When an exception occurs in a sequence of autocommands for
-" a specific event, the rest of the sequence is not executed. The
-" command that triggered the autocommand execution aborts, and the
-" exception is propagated to the caller.
-"
-" For the FuncUndefined event under a function call expression or
-" :call command, the function is not executed, even when it has
-" been defined by the autocommands before the exception occurred.
-"-------------------------------------------------------------------------------
-
-func Test_autocmd_exception()
- let test =<< trim [CODE]
- func INT()
- call interrupt()
- endfunc
-
- aug TMP
- autocmd!
-
- autocmd User x1 Xpath 'a'
- autocmd User x1 throw "x1"
- autocmd User x1 call assert_report('should not get here')
-
- autocmd User x2 Xpath 'b'
- autocmd User x2 asdf
- autocmd User x2 call assert_report('should not get here')
-
- autocmd User x3 Xpath 'c'
- autocmd User x3 call INT()
- autocmd User x3 call assert_report('should not get here')
-
- autocmd FuncUndefined U1 func U1()
- autocmd FuncUndefined U1 call assert_report('should not get here')
- autocmd FuncUndefined U1 endfunc
- autocmd FuncUndefined U1 Xpath 'd'
- autocmd FuncUndefined U1 throw "U1"
- autocmd FuncUndefined U1 call assert_report('should not get here')
-
- autocmd FuncUndefined U2 func U2()
- autocmd FuncUndefined U2 call assert_report('should not get here')
- autocmd FuncUndefined U2 endfunc
- autocmd FuncUndefined U2 Xpath 'e'
- autocmd FuncUndefined U2 ASDF
- autocmd FuncUndefined U2 call assert_report('should not get here')
-
- autocmd FuncUndefined U3 func U3()
- autocmd FuncUndefined U3 call assert_report('should not get here')
- autocmd FuncUndefined U3 endfunc
- autocmd FuncUndefined U3 Xpath 'f'
- autocmd FuncUndefined U3 call INT()
- autocmd FuncUndefined U3 call assert_report('should not get here')
- aug END
-
- try
- try
- Xpath 'g'
- doautocmd User x1
- catch /x1/
- Xpath 'h'
- endtry
-
- while 1
- try
- Xpath 'i'
- doautocmd User x2
- catch /asdf/
- Xpath 'j'
- finally
- Xpath 'k'
- break
- endtry
- endwhile
-
- while 1
- try
- Xpath 'l'
- doautocmd User x3
- catch /Vim:Interrupt/
- Xpath 'm'
- finally
- Xpath 'n'
- " ... but break loop for caught interrupt exception,
- " or discard interrupt and break loop if $VIMNOINTTHROW
- break
- endtry
- endwhile
-
- if exists("*U1") | delfunction U1 | endif
- if exists("*U2") | delfunction U2 | endif
- if exists("*U3") | delfunction U3 | endif
-
- try
- Xpath 'o'
- call U1()
- catch /U1/
- Xpath 'p'
- endtry
-
- while 1
- try
- Xpath 'q'
- call U2()
- catch /ASDF/
- Xpath 'r'
- finally
- Xpath 's'
- " ... but break loop for caught error exception,
- " or discard error and break loop if $VIMNOERRTHROW
- break
- endtry
- endwhile
-
- while 1
- try
- Xpath 't'
- call U3()
- catch /Vim:Interrupt/
- Xpath 'u'
- finally
- Xpath 'v'
- " ... but break loop for caught interrupt exception,
- " or discard interrupt and break loop if $VIMNOINTTHROW
- break
- endtry
- endwhile
- catch /.*/
- call assert_report('should not get here')
- endtry
- Xpath 'w'
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('gahibjklcmnodpqerstfuvw', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 85: Error exceptions in autocommands for I/O command events {{{1
-"
-" When an I/O command is inside :try/:endtry, autocommands to be
-" executed after it should be skipped on an error (exception) in the
-" command itself or in autocommands to be executed before the command.
-" In the latter case, the I/O command should not be executed either.
-" Example 1: BufWritePre, :write, BufWritePost
-" Example 2: FileReadPre, :read, FileReadPost.
-"-------------------------------------------------------------------------------
-
-func Test_autocmd_error_io_exception()
- let test =<< trim [CODE]
- " Remove the autocommands for the events specified as arguments in all used
- " autogroups.
- func Delete_autocommands(...)
- let augfile = tempname()
- while 1
- try
- exec "redir >" . augfile
- aug
- redir END
- exec "edit" augfile
- g/^$/d
- norm G$
- let wrap = "w"
- while search('\%( \|^\)\@<=.\{-}\%( \)\@=', wrap) > 0
- let wrap = "W"
- exec "norm y/ \n"
- let argno = 1
- while argno <= a:0
- exec "au!" escape(@", " ") a:{argno}
- let argno = argno + 1
- endwhile
- endwhile
- catch /.*/
- finally
- bwipeout!
- call delete(augfile)
- break
- endtry
- endwhile
- endfunc
-
- call Delete_autocommands("BufWritePre", "BufWritePost")
-
- while 1
- try
- try
- let post = 0
- aug TMP
- au! BufWritePost * let post = 1
- aug END
- write /n/o/n/e/x/i/s/t/e/n/t
- catch /^Vim(write):/
- Xpath 'a'
- call assert_match("E212: Can't open file for writing", v:exception)
- finally
- Xpath 'b'
- call assert_equal(0, post)
- au! TMP
- aug! TMP
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'c'
- break
- endtry
- endwhile
-
- while 1
- try
- try
- let post = 0
- aug TMP
- au! BufWritePre * asdf
- au! BufWritePost * let post = 1
- aug END
- let tmpfile = tempname()
- exec "write" tmpfile
- catch /^Vim\((write)\)\=:/
- Xpath 'd'
- call assert_match('E492: Not an editor command', v:exception)
- finally
- Xpath 'e'
- if filereadable(tmpfile)
- call assert_report('should not get here')
- endif
- call assert_equal(0, post)
- au! TMP
- aug! TMP
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'f'
- break
- endtry
- endwhile
-
- call delete(tmpfile)
-
- call Delete_autocommands("BufWritePre", "BufWritePost",
- \ "BufReadPre", "BufReadPost", "FileReadPre", "FileReadPost")
-
- while 1
- try
- try
- let post = 0
- aug TMP
- au! FileReadPost * let post = 1
- aug END
- let caught = 0
- read /n/o/n/e/x/i/s/t/e/n/t
- catch /^Vim(read):/
- Xpath 'g'
- call assert_match("E484: Can't open file", v:exception)
- finally
- Xpath 'h'
- call assert_equal(0, post)
- au! TMP
- aug! TMP
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'i'
- break
- endtry
- endwhile
-
- while 1
- try
- let infile = tempname()
- let tmpfile = tempname()
- call writefile(["XYZ"], infile)
- exec "edit" tmpfile
- try
- Xpath 'j'
- try
- let post = 0
- aug TMP
- au! FileReadPre * asdf
- au! FileReadPost * let post = 1
- aug END
- exec "0read" infile
- catch /^Vim\((read)\)\=:/
- Xpath 'k'
- call assert_match('E492: Not an editor command', v:exception)
- finally
- Xpath 'l'
- if getline("1") == "XYZ"
- call assert_report('should not get here')
- endif
- call assert_equal(0, post)
- au! TMP
- aug! TMP
- endtry
- finally
- Xpath 'm'
- bwipeout!
- endtry
- catch /.*/
- call assert_report('should not get here')
- finally
- Xpath 'n'
- break
- endtry
- endwhile
-
- call delete(infile)
- call delete(tmpfile)
- [CODE]
- let verify =<< trim [CODE]
- call assert_equal('abcdefghijklmn', g:Xpath)
- [CODE]
- call RunInNewVim(test, verify)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 87 using (expr) ? funcref : funcref {{{1
-"
-" Vim needs to correctly parse the funcref and even when it does
-" not execute the funcref, it needs to consume the trailing ()
-"-------------------------------------------------------------------------------
-
-func Add2(x1, x2)
- return a:x1 + a:x2
-endfu
-
-func GetStr()
- return "abcdefghijklmnopqrstuvwxyp"
-endfu
-
-func Test_funcref_with_condexpr()
- call assert_equal(5, function('Add2')(2,3))
-
- call assert_equal(3, 1 ? function('Add2')(1,2) : function('Add2')(2,3))
- call assert_equal(5, 0 ? function('Add2')(1,2) : function('Add2')(2,3))
- " Make sure, GetStr() still works.
- call assert_equal('abcdefghijk', GetStr()[0:10])
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 90: Recognizing {} in variable name. {{{1
-"-------------------------------------------------------------------------------
-
-func Test_curlies()
- let s:var = 66
- let ns = 's'
- call assert_equal(66, {ns}:var)
-
- let g:a = {}
- let g:b = 't'
- let g:a[g:b] = 77
- call assert_equal(77, g:a['t'])
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 91: using type(). {{{1
-"-------------------------------------------------------------------------------
-
-func Test_type()
- call assert_equal(0, type(0))
- call assert_equal(1, type(""))
- call assert_equal(2, type(function("tr")))
- call assert_equal(2, type(function("tr", [8])))
- call assert_equal(3, type([]))
- call assert_equal(4, type({}))
- call assert_equal(5, type(0.0))
- call assert_equal(6, type(v:false))
- call assert_equal(6, type(v:true))
- call assert_equal(7, type(v:null))
- call assert_equal(v:t_number, type(0))
- call assert_equal(v:t_string, type(""))
- call assert_equal(v:t_func, type(function("tr")))
- call assert_equal(v:t_list, type([]))
- call assert_equal(v:t_dict, type({}))
- call assert_equal(v:t_float, type(0.0))
- call assert_equal(v:t_bool, type(v:false))
- call assert_equal(v:t_bool, type(v:true))
- call assert_equal(v:t_string, type(v:_null_string))
- call assert_equal(v:t_list, type(v:_null_list))
- call assert_equal(v:t_dict, type(v:_null_dict))
- call assert_equal(v:t_blob, type(v:_null_blob))
-
- call assert_equal(0, 0 + v:false)
- call assert_equal(1, 0 + v:true)
- " call assert_equal(0, 0 + v:none)
- call assert_equal(0, 0 + v:null)
-
- call assert_equal('v:false', '' . v:false)
- call assert_equal('v:true', '' . v:true)
- " call assert_equal('v:none', '' . v:none)
- call assert_equal('v:null', '' . v:null)
-
- call assert_true(v:false == 0)
- call assert_false(v:false != 0)
- call assert_true(v:true == 1)
- call assert_false(v:true != 1)
- call assert_false(v:true == v:false)
- call assert_true(v:true != v:false)
-
- call assert_true(v:null == 0)
- call assert_false(v:null != 0)
- " call assert_true(v:none == 0)
- " call assert_false(v:none != 0)
-
- call assert_true(v:false is v:false)
- call assert_true(v:true is v:true)
- " call assert_true(v:none is v:none)
- call assert_true(v:null is v:null)
-
- call assert_false(v:false isnot v:false)
- call assert_false(v:true isnot v:true)
- " call assert_false(v:none isnot v:none)
- call assert_false(v:null isnot v:null)
-
- call assert_false(v:false is 0)
- call assert_false(v:true is 1)
- call assert_false(v:true is v:false)
- " call assert_false(v:none is 0)
- call assert_false(v:null is 0)
- " call assert_false(v:null is v:none)
-
- call assert_true(v:false isnot 0)
- call assert_true(v:true isnot 1)
- call assert_true(v:true isnot v:false)
- " call assert_true(v:none isnot 0)
- call assert_true(v:null isnot 0)
- " call assert_true(v:null isnot v:none)
-
- call assert_equal(v:false, eval(string(v:false)))
- call assert_equal(v:true, eval(string(v:true)))
- " call assert_equal(v:none, eval(string(v:none)))
- call assert_equal(v:null, eval(string(v:null)))
-
- call assert_equal(v:false, copy(v:false))
- call assert_equal(v:true, copy(v:true))
- " call assert_equal(v:none, copy(v:none))
- call assert_equal(v:null, copy(v:null))
-
- call assert_equal([v:false], deepcopy([v:false]))
- call assert_equal([v:true], deepcopy([v:true]))
- " call assert_equal([v:none], deepcopy([v:none]))
- call assert_equal([v:null], deepcopy([v:null]))
-
- call assert_true(empty(v:false))
- call assert_false(empty(v:true))
- call assert_true(empty(v:null))
- " call assert_true(empty(v:none))
-
- func ChangeYourMind()
- try
- return v:true
- finally
- return 'something else'
- endtry
- endfunc
-
- call ChangeYourMind()
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 92: skipping code {{{1
-"-------------------------------------------------------------------------------
-
-func Test_skip()
- let Fn = function('Test_type')
- call assert_false(0 && Fn[1])
- call assert_false(0 && string(Fn))
- call assert_false(0 && len(Fn))
- let l = []
- call assert_false(0 && l[1])
- call assert_false(0 && string(l))
- call assert_false(0 && len(l))
- let f = 1.0
- call assert_false(0 && f[1])
- call assert_false(0 && string(f))
- call assert_false(0 && len(f))
- let sp = v:null
- call assert_false(0 && sp[1])
- call assert_false(0 && string(sp))
- call assert_false(0 && len(sp))
-
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 93: :echo and string() {{{1
-"-------------------------------------------------------------------------------
-
-func Test_echo_and_string()
- " String
- let a = 'foo bar'
- redir => result
- echo a
- echo string(a)
- redir END
- let l = split(result, "\n")
- call assert_equal(["foo bar",
- \ "'foo bar'"], l)
-
- " Float
- if has('float')
- let a = -1.2e0
- redir => result
- echo a
- echo string(a)
- redir END
- let l = split(result, "\n")
- call assert_equal(["-1.2",
- \ "-1.2"], l)
- endif
-
- " Funcref
- redir => result
- echo function('string')
- echo string(function('string'))
- redir END
- let l = split(result, "\n")
- call assert_equal(["string",
- \ "function('string')"], l)
-
- " Empty dictionaries in a list
- let a = {}
- redir => result
- echo [a, a, a]
- echo string([a, a, a])
- redir END
- let l = split(result, "\n")
- call assert_equal(["[{}, {}, {}]",
- \ "[{}, {}, {}]"], l)
-
- " Empty dictionaries in a dictionary
- let a = {}
- let b = {"a": a, "b": a}
- redir => result
- echo b
- echo string(b)
- redir END
- let l = split(result, "\n")
- call assert_equal(["{'a': {}, 'b': {}}",
- \ "{'a': {}, 'b': {}}"], l)
-
- " Empty lists in a list
- let a = []
- redir => result
- echo [a, a, a]
- echo string([a, a, a])
- redir END
- let l = split(result, "\n")
- call assert_equal(["[[], [], []]",
- \ "[[], [], []]"], l)
-
- " Empty lists in a dictionary
- let a = []
- let b = {"a": a, "b": a}
- redir => result
- echo b
- echo string(b)
- redir END
- let l = split(result, "\n")
- call assert_equal(["{'a': [], 'b': []}",
- \ "{'a': [], 'b': []}"], l)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 94: 64-bit Numbers {{{1
-"-------------------------------------------------------------------------------
-
-func Test_num64()
- call assert_notequal( 4294967296, 0)
- call assert_notequal(-4294967296, 0)
- call assert_equal( 4294967296, 0xFFFFffff + 1)
- call assert_equal(-4294967296, -0xFFFFffff - 1)
-
- call assert_equal( 9223372036854775807, 1 / 0)
- call assert_equal(-9223372036854775807, -1 / 0)
- call assert_equal(-9223372036854775807 - 1, 0 / 0)
-
- if has('float')
- call assert_equal( 0x7FFFffffFFFFffff, float2nr( 1.0e150))
- call assert_equal(-0x7FFFffffFFFFffff, float2nr(-1.0e150))
- endif
-
- let rng = range(0xFFFFffff, 0x100000001)
- call assert_equal([0xFFFFffff, 0x100000000, 0x100000001], rng)
- call assert_equal(0x100000001, max(rng))
- call assert_equal(0xFFFFffff, min(rng))
- call assert_equal(rng, sort(range(0x100000001, 0xFFFFffff, -1), 'N'))
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 95: lines of :append, :change, :insert {{{1
-"-------------------------------------------------------------------------------
-
-func DefineFunction(name, body)
- let func = join(['function! ' . a:name . '()'] + a:body + ['endfunction'], "\n")
- exec func
-endfunc
-
-func Test_script_lines()
- " :append
- try
- call DefineFunction('T_Append', [
- \ 'append',
- \ 'py <<EOS',
- \ '.',
- \ ])
- catch
- call assert_report("Can't define function")
- endtry
- try
- call DefineFunction('T_Append', [
- \ 'append',
- \ 'abc',
- \ ])
- call assert_report("Shouldn't be able to define function")
- catch
- call assert_exception('Vim(function):E126: Missing :endfunction')
- endtry
-
- " :change
- try
- call DefineFunction('T_Change', [
- \ 'change',
- \ 'py <<EOS',
- \ '.',
- \ ])
- catch
- call assert_report("Can't define function")
- endtry
- try
- call DefineFunction('T_Change', [
- \ 'change',
- \ 'abc',
- \ ])
- call assert_report("Shouldn't be able to define function")
- catch
- call assert_exception('Vim(function):E126: Missing :endfunction')
- endtry
-
- " :insert
- try
- call DefineFunction('T_Insert', [
- \ 'insert',
- \ 'py <<EOS',
- \ '.',
- \ ])
- catch
- call assert_report("Can't define function")
- endtry
- try
- call DefineFunction('T_Insert', [
- \ 'insert',
- \ 'abc',
- \ ])
- call assert_report("Shouldn't be able to define function")
- catch
- call assert_exception('Vim(function):E126: Missing :endfunction')
- endtry
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 96: line continuation {{{1
-"
-" Undefined behavior was detected by ubsan with line continuation
-" after an empty line.
-"-------------------------------------------------------------------------------
-func Test_script_empty_line_continuation()
-
- \
-endfunc
-
-"-------------------------------------------------------------------------------
-" Test 97: bitwise functions {{{1
-"-------------------------------------------------------------------------------
-func Test_bitwise_functions()
- " and
- call assert_equal(127, and(127, 127))
- call assert_equal(16, and(127, 16))
- eval 127->and(16)->assert_equal(16)
- call assert_equal(0, and(127, 128))
- call assert_fails("call and(1.0, 1)", 'E805:')
- call assert_fails("call and([], 1)", 'E745:')
- call assert_fails("call and({}, 1)", 'E728:')
- call assert_fails("call and(1, 1.0)", 'E805:')
- call assert_fails("call and(1, [])", 'E745:')
- call assert_fails("call and(1, {})", 'E728:')
- " or
- call assert_equal(23, or(16, 7))
- call assert_equal(15, or(8, 7))
- eval 8->or(7)->assert_equal(15)
- call assert_equal(123, or(0, 123))
- call assert_fails("call or(1.0, 1)", 'E805:')
- call assert_fails("call or([], 1)", 'E745:')
- call assert_fails("call or({}, 1)", 'E728:')
- call assert_fails("call or(1, 1.0)", 'E805:')
- call assert_fails("call or(1, [])", 'E745:')
- call assert_fails("call or(1, {})", 'E728:')
- " xor
- call assert_equal(0, xor(127, 127))
- call assert_equal(111, xor(127, 16))
- eval 127->xor(16)->assert_equal(111)
- call assert_equal(255, xor(127, 128))
- call assert_fails("call xor(1.0, 1)", 'E805:')
- call assert_fails("call xor([], 1)", 'E745:')
- call assert_fails("call xor({}, 1)", 'E728:')
- call assert_fails("call xor(1, 1.0)", 'E805:')
- call assert_fails("call xor(1, [])", 'E745:')
- call assert_fails("call xor(1, {})", 'E728:')
- " invert
- call assert_equal(65408, and(invert(127), 65535))
- eval 127->invert()->and(65535)->assert_equal(65408)
- call assert_equal(65519, and(invert(16), 65535))
- call assert_equal(65407, and(invert(128), 65535))
- call assert_fails("call invert(1.0)", 'E805:')
- call assert_fails("call invert([])", 'E745:')
- call assert_fails("call invert({})", 'E728:')
-endfunc
-
-" Test using bang after user command {{{1
-func Test_user_command_with_bang()
- command -bang Nieuw let nieuw = 1
- Ni!
- call assert_equal(1, nieuw)
- unlet nieuw
- delcommand Nieuw
-endfunc
-
-func Test_script_expand_sfile()
- let lines =<< trim END
- func s:snr()
- return expand('<sfile>')
- endfunc
- let g:result = s:snr()
- END
- call writefile(lines, 'Xexpand')
- source Xexpand
- call assert_match('<SNR>\d\+_snr', g:result)
- source Xexpand
- call assert_match('<SNR>\d\+_snr', g:result)
-
- call delete('Xexpand')
- unlet g:result
-endfunc
-
-func Test_compound_assignment_operators()
- " Test for number
- let x = 1
- let x += 10
- call assert_equal(11, x)
- let x -= 5
- call assert_equal(6, x)
- let x *= 4
- call assert_equal(24, x)
- let x /= 3
- call assert_equal(8, x)
- let x %= 3
- call assert_equal(2, x)
- let x .= 'n'
- call assert_equal('2n', x)
-
- " Test special cases: division or modulus with 0.
- let x = 1
- let x /= 0
- call assert_equal(0x7FFFFFFFFFFFFFFF, x)
-
- let x = -1
- let x /= 0
- call assert_equal(-0x7FFFFFFFFFFFFFFF, x)
-
- let x = 0
- let x /= 0
- call assert_equal(-0x7FFFFFFFFFFFFFFF - 1, x)
-
- let x = 1
- let x %= 0
- call assert_equal(0, x)
-
- let x = -1
- let x %= 0
- call assert_equal(0, x)
-
- let x = 0
- let x %= 0
- call assert_equal(0, x)
-
- " Test for string
- let x = 'str'
- let x .= 'ing'
- call assert_equal('string', x)
- let x += 1
- call assert_equal(1, x)
-
- if has('float')
- " Test for float
- let x -= 1.5
- call assert_equal(-0.5, x)
- let x = 0.5
- let x += 4.5
- call assert_equal(5.0, x)
- let x -= 1.5
- call assert_equal(3.5, x)
- let x *= 3.0
- call assert_equal(10.5, x)
- let x /= 2.5
- call assert_equal(4.2, x)
- call assert_fails('let x %= 0.5', 'E734')
- call assert_fails('let x .= "f"', 'E734')
- let x = !3.14
- call assert_equal(0.0, x)
-
- " integer and float operations
- let x = 1
- let x *= 2.1
- call assert_equal(2.1, x)
- let x = 1
- let x /= 0.25
- call assert_equal(4.0, x)
- let x = 1
- call assert_fails('let x %= 0.25', 'E734:')
- let x = 1
- call assert_fails('let x .= 0.25', 'E734:')
- let x = 1.0
- call assert_fails('let x += [1.1]', 'E734:')
- endif
-
- " Test for environment variable
- let $FOO = 1
- call assert_fails('let $FOO += 1', 'E734')
- call assert_fails('let $FOO -= 1', 'E734')
- call assert_fails('let $FOO *= 1', 'E734')
- call assert_fails('let $FOO /= 1', 'E734')
- call assert_fails('let $FOO %= 1', 'E734')
- let $FOO .= 's'
- call assert_equal('1s', $FOO)
- unlet $FOO
-
- " Test for option variable (type: number)
- let &scrolljump = 1
- let &scrolljump += 5
- call assert_equal(6, &scrolljump)
- let &scrolljump -= 2
- call assert_equal(4, &scrolljump)
- let &scrolljump *= 3
- call assert_equal(12, &scrolljump)
- let &scrolljump /= 2
- call assert_equal(6, &scrolljump)
- let &scrolljump %= 5
- call assert_equal(1, &scrolljump)
- call assert_fails('let &scrolljump .= "j"', 'E734:')
- set scrolljump&vim
-
- let &foldlevelstart = 2
- let &foldlevelstart -= 1
- call assert_equal(1, &foldlevelstart)
- let &foldlevelstart -= 1
- call assert_equal(0, &foldlevelstart)
- let &foldlevelstart = 2
- let &foldlevelstart -= 2
- call assert_equal(0, &foldlevelstart)
-
- " Test for register
- let @/ = 1
- call assert_fails('let @/ += 1', 'E734:')
- call assert_fails('let @/ -= 1', 'E734:')
- call assert_fails('let @/ *= 1', 'E734:')
- call assert_fails('let @/ /= 1', 'E734:')
- call assert_fails('let @/ %= 1', 'E734:')
- let @/ .= 's'
- call assert_equal('1s', @/)
- let @/ = ''
-endfunc
-
-func Test_unlet_env()
- let $TESTVAR = 'yes'
- call assert_equal('yes', $TESTVAR)
- call assert_fails('lockvar $TESTVAR', 'E940')
- call assert_fails('unlockvar $TESTVAR', 'E940')
- call assert_equal('yes', $TESTVAR)
- if 0
- unlet $TESTVAR
- endif
- call assert_equal('yes', $TESTVAR)
- unlet $TESTVAR
- call assert_equal('', $TESTVAR)
-endfunc
-
-" Test for missing :endif, :endfor, :endwhile and :endtry {{{1
-func Test_missing_end()
- call writefile(['if 2 > 1', 'echo ">"'], 'Xscript')
- call assert_fails('source Xscript', 'E171:')
- call writefile(['for i in range(5)', 'echo i'], 'Xscript')
- call assert_fails('source Xscript', 'E170:')
- call writefile(['while v:true', 'echo "."'], 'Xscript')
- call assert_fails('source Xscript', 'E170:')
- call writefile(['try', 'echo "."'], 'Xscript')
- call assert_fails('source Xscript', 'E600:')
- call delete('Xscript')
-
- " Using endfor with :while
- let caught_e732 = 0
- try
- while v:true
- endfor
- catch /E732:/
- let caught_e732 = 1
- endtry
- call assert_equal(1, caught_e732)
-
- " Using endwhile with :for
- let caught_e733 = 0
- try
- for i in range(1)
- endwhile
- catch /E733:/
- let caught_e733 = 1
- endtry
- call assert_equal(1, caught_e733)
-
- " Using endfunc with :if
- call assert_fails('exe "if 1 | endfunc | endif"', 'E193:')
-
- " Missing 'in' in a :for statement
- call assert_fails('for i range(1) | endfor', 'E690:')
-
- " Incorrect number of variables in for
- call assert_fails('for [i,] in range(3) | endfor', 'E475:')
-endfunc
-
-" Test for deep nesting of if/for/while/try statements {{{1
-func Test_deep_nest()
- CheckRunVimInTerminal
-
- let lines =<< trim [SCRIPT]
- " Deep nesting of if ... endif
- func Test1()
- let @a = join(repeat(['if v:true'], 51), "\n")
- let @a ..= "\n"
- let @a ..= join(repeat(['endif'], 51), "\n")
- @a
- let @a = ''
- endfunc
-
- " Deep nesting of for ... endfor
- func Test2()
- let @a = join(repeat(['for i in [1]'], 51), "\n")
- let @a ..= "\n"
- let @a ..= join(repeat(['endfor'], 51), "\n")
- @a
- let @a = ''
- endfunc
-
- " Deep nesting of while ... endwhile
- func Test3()
- let @a = join(repeat(['while v:true'], 51), "\n")
- let @a ..= "\n"
- let @a ..= join(repeat(['endwhile'], 51), "\n")
- @a
- let @a = ''
- endfunc
-
- " Deep nesting of try ... endtry
- func Test4()
- let @a = join(repeat(['try'], 51), "\n")
- let @a ..= "\necho v:true\n"
- let @a ..= join(repeat(['endtry'], 51), "\n")
- @a
- let @a = ''
- endfunc
-
- " Deep nesting of function ... endfunction
- func Test5()
- let @a = join(repeat(['function X()'], 51), "\n")
- let @a ..= "\necho v:true\n"
- let @a ..= join(repeat(['endfunction'], 51), "\n")
- @a
- let @a = ''
- endfunc
- [SCRIPT]
- call writefile(lines, 'Xscript')
-
- let buf = RunVimInTerminal('-S Xscript', {'rows': 6})
-
- " Deep nesting of if ... endif
- call term_sendkeys(buf, ":call Test1()\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))})
-
- " Deep nesting of for ... endfor
- call term_sendkeys(buf, ":call Test2()\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))})
-
- " Deep nesting of while ... endwhile
- call term_sendkeys(buf, ":call Test3()\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))})
-
- " Deep nesting of try ... endtry
- call term_sendkeys(buf, ":call Test4()\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))})
-
- " Deep nesting of function ... endfunction
- call term_sendkeys(buf, ":call Test5()\n")
- call term_wait(buf)
- call WaitForAssert({-> assert_match('^E1058:', term_getline(buf, 4))})
- call term_sendkeys(buf, "\<C-C>\n")
- call term_wait(buf)
-
- "let l = ''
- "for i in range(1, 6)
- " let l ..= term_getline(buf, i) . "\n"
- "endfor
- "call assert_report(l)
-
- call StopVimInTerminal(buf)
- call delete('Xscript')
-endfunc
-
-" Test for errors in converting to float from various types {{{1
-func Test_float_conversion_errors()
- if has('float')
- call assert_fails('let x = 4.0 % 2.0', 'E804')
- call assert_fails('echo 1.1[0]', 'E806')
- call assert_fails('echo sort([function("min"), 1], "f")', 'E891:')
- call assert_fails('echo 3.2 == "vim"', 'E892:')
- call assert_fails('echo sort([[], 1], "f")', 'E893:')
- call assert_fails('echo sort([{}, 1], "f")', 'E894:')
- call assert_fails('echo 3.2 == v:true', 'E362:')
- " call assert_fails('echo 3.2 == v:none', 'E907:')
- endif
-endfunc
-
-" invalid function names {{{1
-func Test_invalid_function_names()
- " function name not starting with capital
- let caught_e128 = 0
- try
- func! g:test()
- echo "test"
- endfunc
- catch /E128:/
- let caught_e128 = 1
- endtry
- call assert_equal(1, caught_e128)
-
- " function name includes a colon
- let caught_e884 = 0
- try
- func! b:test()
- echo "test"
- endfunc
- catch /E884:/
- let caught_e884 = 1
- endtry
- call assert_equal(1, caught_e884)
-
- " function name followed by #
- let caught_e128 = 0
- try
- func! test2() "#
- echo "test2"
- endfunc
- catch /E128:/
- let caught_e128 = 1
- endtry
- call assert_equal(1, caught_e128)
-
- " function name starting with/without "g:", buffer-local funcref.
- function! g:Foo(n)
- return 'called Foo(' . a:n . ')'
- endfunction
- let b:my_func = function('Foo')
- call assert_equal('called Foo(1)', b:my_func(1))
- call assert_equal('called Foo(2)', g:Foo(2))
- call assert_equal('called Foo(3)', Foo(3))
- delfunc g:Foo
-
- " script-local function used in Funcref must exist.
- let lines =<< trim END
- func s:Testje()
- return "foo"
- endfunc
- let Bar = function('s:Testje')
- call assert_equal(0, exists('s:Testje'))
- call assert_equal(1, exists('*s:Testje'))
- call assert_equal(1, exists('Bar'))
- call assert_equal(1, exists('*Bar'))
- END
- call writefile(lines, 'Xscript')
- source Xscript
- call delete('Xscript')
-endfunc
-
-" substring and variable name {{{1
-func Test_substring_var()
- let str = 'abcdef'
- let n = 3
- call assert_equal('def', str[n:])
- call assert_equal('abcd', str[:n])
- call assert_equal('d', str[n:n])
- unlet n
- let nn = 3
- call assert_equal('def', str[nn:])
- call assert_equal('abcd', str[:nn])
- call assert_equal('d', str[nn:nn])
- unlet nn
- let b:nn = 4
- call assert_equal('ef', str[b:nn:])
- call assert_equal('abcde', str[:b:nn])
- call assert_equal('e', str[b:nn:b:nn])
- unlet b:nn
-endfunc
-
-" Test using s: with a typed command {{{1
-func Test_typed_script_var()
- CheckRunVimInTerminal
-
- let buf = RunVimInTerminal('', {'rows': 6})
-
- " Deep nesting of if ... endif
- call term_sendkeys(buf, ":echo get(s:, 'foo', 'x')\n")
- call TermWait(buf)
- call WaitForAssert({-> assert_match('^E116:', term_getline(buf, 5))})
-
- call StopVimInTerminal(buf)
-endfunc
-
-func Test_for_over_string()
- let res = ''
- for c in 'aéc̀d'
- let res ..= c .. '-'
- endfor
- call assert_equal('a-é-c̀-d-', res)
-
- let res = ''
- for c in ''
- let res ..= c .. '-'
- endfor
- call assert_equal('', res)
-
- let res = ''
- for c in v:_null_string
- let res ..= c .. '-'
- endfor
- call assert_equal('', res)
-endfunc
-
-"-------------------------------------------------------------------------------
-" Modelines {{{1
-" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
-"-------------------------------------------------------------------------------
diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim
deleted file mode 100644
index 2bf8c3fc77..0000000000
--- a/src/nvim/testdir/test_virtualedit.vim
+++ /dev/null
@@ -1,589 +0,0 @@
-" Tests for 'virtualedit'.
-
-func Test_yank_move_change()
- new
- call setline(1, [
- \ "func foo() error {",
- \ "\tif n, err := bar();",
- \ "\terr != nil {",
- \ "\t\treturn err",
- \ "\t}",
- \ "\tn = n * n",
- \ ])
- set virtualedit=all
- set ts=4
- function! MoveSelectionDown(count) abort
- normal! m`
- silent! exe "'<,'>move'>+".a:count
- norm! ``
- endfunction
-
- xmap ]e :<C-U>call MoveSelectionDown(v:count1)<CR>
- 2
- normal 2gg
- normal J
- normal jVj
- normal ]e
- normal ce
- bwipe!
- set virtualedit=
- set ts=8
-endfunc
-
-func Test_paste_end_of_line()
- new
- set virtualedit=all
- call setline(1, ['456', '123'])
- normal! gg0"ay$
- exe "normal! 2G$lllA\<C-O>:normal! \"agP\r"
- call assert_equal('123456', getline(2))
-
- bwipe!
- set virtualedit=
-endfunc
-
-func Test_replace_end_of_line()
- new
- set virtualedit=all
- call setline(1, range(20))
- exe "normal! gg2jv10lr-"
- call assert_equal(["1", "-----------", "3"], getline(2,4))
- call setline(1, range(20))
- exe "normal! gg2jv10lr\<c-k>hh"
- call assert_equal(["1", "───────────", "3"], getline(2,4))
-
- bwipe!
- set virtualedit=
-endfunc
-
-func Test_edit_CTRL_G()
- new
- set virtualedit=insert
- call setline(1, ['123', '1', '12'])
- exe "normal! ggA\<c-g>jx\<c-g>jx"
- call assert_equal(['123', '1 x', '12 x'], getline(1,'$'))
-
- set virtualedit=all
- %d_
- call setline(1, ['1', '12'])
- exe "normal! ggllix\<c-g>jx"
- call assert_equal(['1 x', '12x'], getline(1,'$'))
-
-
- bwipe!
- set virtualedit=
-endfunc
-
-func Test_edit_change()
- new
- set virtualedit=all
- call setline(1, "\t⒌")
- normal Cx
- call assert_equal('x', getline(1))
- " Do a visual block change
- call setline(1, ['a', 'b', 'c'])
- exe "normal gg3l\<C-V>2jcx"
- call assert_equal(['a x', 'b x', 'c x'], getline(1, '$'))
- bwipe!
- set virtualedit=
-endfunc
-
-" Tests for pasting at the beginning, end and middle of a tab character
-" in virtual edit mode.
-func Test_paste_in_tab()
- new
- call append(0, '')
- set virtualedit=all
-
- " Tests for pasting a register with characterwise mode type
- call setreg('"', 'xyz', 'c')
-
- " paste (p) unnamed register at the beginning of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 0)
- normal p
- call assert_equal('a xyz b', getline(1))
-
- " paste (P) unnamed register at the beginning of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 0)
- normal P
- call assert_equal("axyz\tb", getline(1))
-
- " paste (p) unnamed register at the end of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 6)
- normal p
- call assert_equal("a\txyzb", getline(1))
-
- " paste (P) unnamed register at the end of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 6)
- normal P
- call assert_equal('a xyz b', getline(1))
-
- " Tests for pasting a register with blockwise mode type
- call setreg('"', 'xyz', 'b')
-
- " paste (p) unnamed register at the beginning of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 0)
- normal p
- call assert_equal('a xyz b', getline(1))
-
- " paste (P) unnamed register at the beginning of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 0)
- normal P
- call assert_equal("axyz\tb", getline(1))
-
- " paste (p) unnamed register at the end of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 6)
- normal p
- call assert_equal("a\txyzb", getline(1))
-
- " paste (P) unnamed register at the end of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 6)
- normal P
- call assert_equal('a xyz b', getline(1))
-
- " Tests for pasting with gp and gP in virtual edit mode
-
- " paste (gp) unnamed register at the beginning of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 0)
- normal gp
- call assert_equal('a xyz b', getline(1))
- call assert_equal([0, 1, 12, 0, 12], getcurpos())
-
- " paste (gP) unnamed register at the beginning of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 0)
- normal gP
- call assert_equal("axyz\tb", getline(1))
- call assert_equal([0, 1, 5, 0, 5], getcurpos())
-
- " paste (gp) unnamed register at the end of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 6)
- normal gp
- call assert_equal("a\txyzb", getline(1))
- call assert_equal([0, 1, 6, 0, 12], getcurpos())
-
- " paste (gP) unnamed register at the end of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 6)
- normal gP
- call assert_equal('a xyz b', getline(1))
- call assert_equal([0, 1, 12, 0, 12], getcurpos())
-
- " Tests for pasting a named register
- let @r = 'xyz'
-
- " paste (gp) named register in the middle of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 2)
- normal "rgp
- call assert_equal('a xyz b', getline(1))
- call assert_equal([0, 1, 8, 0, 8], getcurpos())
-
- " paste (gP) named register in the middle of a tab
- call setline(1, "a\tb")
- call cursor(1, 2, 2)
- normal "rgP
- call assert_equal('a xyz b', getline(1))
- call assert_equal([0, 1, 7, 0, 7], getcurpos())
-
- bwipe!
- set virtualedit=
-endfunc
-
-" Test for yanking a few spaces within a tab to a register
-func Test_yank_in_tab()
- new
- let @r = ''
- call setline(1, "a\tb")
- set virtualedit=all
- call cursor(1, 2, 2)
- normal "ry5l
- call assert_equal(' ', @r)
-
- bwipe!
- set virtualedit=
-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))
- " visual block replace
- %d _
- call setline(1, ['a', '', 'b'])
- exe "normal 2l\<C-V>2jrx"
- call assert_equal(['a x', ' x', 'b x'], getline(1, '$'))
- " visual characterwise selection replace after eol
- %d _
- call setline(1, 'a')
- normal 4lv2lrx
- call assert_equal('a xxx', 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))
- call setline(1, 'aaaaaaaaaaaa')
- exe "normal! gg2lgR\<Tab>"
- call assert_equal("aa\taaaa", 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
-
-" Test for delete that breaks a tab into spaces
-func Test_delete_break_tab()
- new
- call setline(1, "one\ttwo")
- set virtualedit=all
- normal v3ld
- call assert_equal(' two', getline(1))
- set virtualedit&
- close!
-endfunc
-
-" Test for using <BS>, <C-W> and <C-U> in virtual edit mode
-" to erase character, word and line.
-func Test_ve_backspace()
- new
- call setline(1, 'sample')
- set virtualedit=all
- set backspace=indent,eol,start
- exe "normal 15|i\<BS>\<BS>"
- call assert_equal([0, 1, 7, 5], getpos('.'))
- exe "normal 15|i\<C-W>"
- call assert_equal([0, 1, 6, 0], getpos('.'))
- exe "normal 15|i\<C-U>"
- call assert_equal([0, 1, 1, 0], getpos('.'))
- set backspace&
- set virtualedit&
- close!
-endfunc
-
-" Test for delete (x) on EOL character and after EOL
-func Test_delete_past_eol()
- new
- call setline(1, "ab")
- set virtualedit=all
- exe "normal 2lx"
- call assert_equal('ab', getline(1))
- exe "normal 10lx"
- call assert_equal('ab', getline(1))
- set virtualedit&
- bw!
-endfunc
-
-" After calling s:TryVirtualeditReplace(), line 1 will contain one of these
-" two strings, depending on whether virtual editing is on or off.
-let s:result_ve_on = 'a x'
-let s:result_ve_off = 'x'
-
-" Utility function for Test_global_local_virtualedit()
-func s:TryVirtualeditReplace()
- call setline(1, 'a')
- normal gg7l
- normal rx
-endfunc
-
-" Test for :set and :setlocal
-func Test_global_local_virtualedit()
- new
-
- " Verify that 'virtualedit' is initialized to empty, can be set globally to
- " all and to empty, and can be set locally to all and to empty.
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- set ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- set ve=
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- setlocal ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- setlocal ve=
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
-
- " Verify that :set affects multiple windows.
- split
- set ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- wincmd p
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- set ve=
- wincmd p
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- bwipe!
-
- " Verify that :setlocal affects only the current window.
- new
- split
- setlocal ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- wincmd p
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- bwipe!
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
-
- " Verify that the buffer 'virtualedit' state follows the global value only
- " when empty and that "none" works as expected.
- "
- " 'virtualedit' State
- " +--------+--------------------------+
- " | Local | Global |
- " | | |
- " +--------+--------+--------+--------+
- " | | "" | "all" | "none" |
- " +--------+--------+--------+--------+
- " | "" | off | on | off |
- " | "all" | on | on | on |
- " | "none" | off | off | off |
- " +--------+--------+--------+--------+
- new
-
- setglobal ve=
- setlocal ve=
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- setlocal ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- setlocal ve=none
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
-
- setglobal ve=all
- setlocal ve=
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- setlocal ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- setlocal ve=none
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- setlocal ve=NONE
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
-
- setglobal ve=none
- setlocal ve=
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- setlocal ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- setlocal ve=none
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
-
- bwipe!
-
- " Verify that the 'virtualedit' state is copied to new windows.
- new
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- split
- setlocal ve=all
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- split
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_on, getline(1))
- setlocal ve=
- split
- call s:TryVirtualeditReplace()
- call assert_equal(s:result_ve_off, getline(1))
- bwipe!
-
- setlocal virtualedit&
- set virtualedit&
-endfunc
-
-func Test_virtualedit_mouse()
- let save_mouse = &mouse
- set mouse=a
- set virtualedit=all
- new
-
- call setline(1, ["text\tword"])
- redraw
- call Ntest_setmouse(1, 4)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 4, 0, 4], getcurpos())
- call Ntest_setmouse(1, 5)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 5, 0, 5], getcurpos())
- call Ntest_setmouse(1, 6)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 5, 1, 6], getcurpos())
- call Ntest_setmouse(1, 7)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 5, 2, 7], getcurpos())
- call Ntest_setmouse(1, 8)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 5, 3, 8], getcurpos())
- call Ntest_setmouse(1, 9)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 6, 0, 9], getcurpos())
- call Ntest_setmouse(1, 15)
- call feedkeys("\<LeftMouse>", "xt")
- call assert_equal([0, 1, 10, 2, 15], getcurpos())
-
- bwipe!
- let &mouse = save_mouse
- set virtualedit&
-endfunc
-
-" this was replacing the NUL at the end of the line
-func Test_virtualedit_replace_after_tab()
- new
- s/\v/ 0
- set ve=all
- let @" = ''
- sil! norm vPvr0
-
- call assert_equal("\t0", getline(1))
- set ve&
- bwipe!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
deleted file mode 100644
index 14d62089cf..0000000000
--- a/src/nvim/testdir/test_visual.vim
+++ /dev/null
@@ -1,1528 +0,0 @@
-" Tests for various Visual modes.
-
-source shared.vim
-source check.vim
-source screendump.vim
-
-func Test_block_shift_multibyte()
- " Uses double-wide character.
- split
- call setline(1, ['xヹxxx', 'ヹxxx'])
- exe "normal 1G0l\<C-V>jl>"
- call assert_equal('x ヹxxx', getline(1))
- call assert_equal(' ヹxxx', getline(2))
- q!
-endfunc
-
-func Test_block_shift_overflow()
- " This used to cause a multiplication overflow followed by a crash.
- new
- normal ii
- exe "normal \<C-V>876543210>"
- q!
-endfunc
-
-func Test_dotregister_paste()
- new
- exe "norm! ihello world\<esc>"
- norm! 0ve".p
- call assert_equal('hello world world', getline(1))
- q!
-endfunc
-
-func Test_Visual_ctrl_o()
- new
- call setline(1, ['one', 'two', 'three'])
- call cursor(1,2)
- set noshowmode
- set tw=0
- call feedkeys("\<c-v>jjlIa\<c-\>\<c-o>:set tw=88\<cr>\<esc>", 'tx')
- call assert_equal(['oane', 'tawo', 'tahree'], getline(1, 3))
- call assert_equal(88, &tw)
- set tw&
- bw!
-endfu
-
-func Test_Visual_vapo()
- new
- normal oxx
- normal vapo
- bwipe!
-endfunc
-
-func Test_Visual_inner_quote()
- new
- normal oxX
- normal vki'
- bwipe!
-endfunc
-
-" Test for Visual mode not being reset causing E315 error.
-func TriggerTheProblem()
- " At this point there is no visual selection because :call reset it.
- " Let's restore the selection:
- normal gv
- '<,'>del _
- try
- exe "normal \<Esc>"
- catch /^Vim\%((\a\+)\)\=:E315/
- echom 'Snap! E315 error!'
- let g:msg = 'Snap! E315 error!'
- endtry
-endfunc
-
-func Test_visual_mode_reset()
- enew
- let g:msg = "Everything's fine."
- enew
- setl buftype=nofile
- call append(line('$'), 'Delete this line.')
-
- " NOTE: this has to be done by a call to a function because executing :del
- " the ex-way will require the colon operator which resets the visual mode
- " thus preventing the problem:
- exe "normal! GV:call TriggerTheProblem()\<CR>"
- call assert_equal("Everything's fine.", g:msg)
-endfunc
-
-" Test for visual block shift and tab characters.
-func Test_block_shift_tab()
- new
- call append(0, repeat(['one two three'], 5))
- call cursor(1,1)
- exe "normal i\<C-G>u"
- exe "normal fe\<C-V>4jR\<Esc>ugvr1"
- call assert_equal('on1 two three', getline(1))
- call assert_equal('on1 two three', getline(2))
- call assert_equal('on1 two three', getline(5))
-
- %d _
- call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5))
- call cursor(1,1)
- exe "normal \<C-V>4jI \<Esc>j<<11|D"
- exe "normal j7|a\<Tab>\<Tab>"
- exe "normal j7|a\<Tab>\<Tab> "
- exe "normal j7|a\<Tab> \<Tab>\<Esc>4k13|\<C-V>4j<"
- call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1))
- call assert_equal('abcdefghij', getline(2))
- call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3))
- call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(4))
- call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5))
-
- %s/\s\+//g
- call cursor(1,1)
- exe "normal \<C-V>4jI \<Esc>j<<"
- exe "normal j7|a\<Tab>\<Tab>"
- exe "normal j7|a\<Tab>\<Tab>\<Tab>\<Tab>\<Tab>"
- exe "normal j7|a\<Tab> \<Tab>\<Tab>\<Esc>4k13|\<C-V>4j3<"
- call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1))
- call assert_equal('abcdefghij', getline(2))
- call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3))
- call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4))
- call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5))
-
- " Test for block shift with space characters at the beginning and with
- " 'noexpandtab' and 'expandtab'
- %d _
- call setline(1, [" 1", " 2", " 3"])
- setlocal shiftwidth=2 noexpandtab
- exe "normal gg\<C-V>3j>"
- call assert_equal(["\t1", "\t2", "\t3"], getline(1, '$'))
- %d _
- call setline(1, [" 1", " 2", " 3"])
- setlocal shiftwidth=2 expandtab
- exe "normal gg\<C-V>3j>"
- call assert_equal([" 1", " 2", " 3"], getline(1, '$'))
- setlocal shiftwidth&
-
- bw!
-endfunc
-
-" Tests Blockwise Visual when there are TABs before the text.
-func Test_blockwise_visual()
- new
- call append(0, ['123456',
- \ '234567',
- \ '345678',
- \ '',
- \ 'test text test tex start here',
- \ "\t\tsome text",
- \ "\t\ttest text",
- \ 'test text'])
- call cursor(1,1)
- exe "normal /start here$\<CR>"
- exe 'normal "by$' . "\<C-V>jjlld"
- exe "normal /456$\<CR>"
- exe "normal \<C-V>jj" . '"bP'
- call assert_equal(['123start here56',
- \ '234start here67',
- \ '345start here78',
- \ '',
- \ 'test text test tex rt here',
- \ "\t\tsomext",
- \ "\t\ttesext"], getline(1, 7))
-
- bw!
-endfunc
-
-" Test swapping corners in blockwise visual mode with o and O
-func Test_blockwise_visual_o_O()
- new
-
- exe "norm! 10i.\<Esc>Y4P3lj\<C-V>4l2jr "
- exe "norm! gvO\<Esc>ra"
- exe "norm! gvO\<Esc>rb"
- exe "norm! gvo\<C-c>rc"
- exe "norm! gvO\<C-c>rd"
- set selection=exclusive
- exe "norm! gvOo\<C-c>re"
- call assert_equal('...a be.', getline(4))
- exe "norm! gvOO\<C-c>rf"
- set selection&
-
- call assert_equal(['..........',
- \ '...c d..',
- \ '... ..',
- \ '...a bf.',
- \ '..........'], getline(1, '$'))
-
- bw!
-endfun
-
-" Test Virtual replace mode.
-func Test_virtual_replace()
- if exists('&t_kD')
- let save_t_kD = &t_kD
- endif
- if exists('&t_kb')
- let save_t_kb = &t_kb
- endif
- exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08"
- enew!
- exe "normal a\nabcdefghi\njk\tlmn\n opq rst\n\<C-D>uvwxyz"
- call cursor(1,1)
- set ai bs=2
- exe "normal gR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR"
- call assert_equal([' 1',
- \ ' A',
- \ ' BCDEFGHIJ',
- \ ' KL',
- \ ' MNO',
- \ ' PQR',
- \ ], getline(1, 6))
- normal G
- mark a
- exe "normal o0\<C-D>\nabcdefghi\njk\tlmn\n opq\trst\n\<C-D>uvwxyz\n"
- exe "normal 'ajgR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" . repeat("\<BS>", 29)
- call assert_equal([' 1',
- \ 'abcdefghi',
- \ 'jk lmn',
- \ ' opq rst',
- \ 'uvwxyz'], getline(7, 11))
- normal G
- exe "normal iab\tcdefghi\tjkl"
- exe "normal 0gRAB......CDEFGHI.J\<Esc>o"
- exe "normal iabcdefghijklmnopqrst\<Esc>0gRAB\tIJKLMNO\tQR"
- call assert_equal(['AB......CDEFGHI.Jkl',
- \ 'AB IJKLMNO QRst'], getline(12, 13))
-
- " Test inserting Tab with 'noexpandtab' and 'softabstop' set to 4
- %d
- call setline(1, 'aaaaaaaaaaaaa')
- set softtabstop=4
- exe "normal gggR\<Tab>\<Tab>x"
- call assert_equal("\txaaaa", getline(1))
- set softtabstop&
-
- enew!
- set noai bs&vim
- if exists('save_t_kD')
- let &t_kD = save_t_kD
- endif
- if exists('save_t_kb')
- let &t_kb = save_t_kb
- endif
-endfunc
-
-" Test Virtual replace mode.
-func Test_virtual_replace2()
- enew!
- set bs=2
- exe "normal a\nabcdefghi\njk\tlmn\n opq rst\n\<C-D>uvwxyz"
- call cursor(1,1)
- " Test 1: Test that del deletes the newline
- exe "normal gR0\<del> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR"
- call assert_equal(['0 1',
- \ 'A',
- \ 'BCDEFGHIJ',
- \ ' KL',
- \ 'MNO',
- \ 'PQR',
- \ ], getline(1, 6))
- " Test 2:
- " a newline is not deleted, if no newline has been added in virtual replace mode
- %d_
- call setline(1, ['abcd', 'efgh', 'ijkl'])
- call cursor(2,1)
- exe "norm! gR1234\<cr>5\<bs>\<bs>\<bs>"
- call assert_equal(['abcd',
- \ '123h',
- \ 'ijkl'], getline(1, '$'))
- " Test 3:
- " a newline is deleted, if a newline has been inserted before in virtual replace mode
- %d_
- call setline(1, ['abcd', 'efgh', 'ijkl'])
- call cursor(2,1)
- exe "norm! gR1234\<cr>\<cr>56\<bs>\<bs>\<bs>"
- call assert_equal(['abcd',
- \ '1234',
- \ 'ijkl'], getline(1, '$'))
- " Test 4:
- " delete add a newline, delete it, add it again and check undo
- %d_
- call setline(1, ['abcd', 'efgh', 'ijkl'])
- call cursor(2,1)
- " break undo sequence explicitly
- let &ul = &ul
- exe "norm! gR1234\<cr>\<bs>\<del>56\<cr>"
- let &ul = &ul
- call assert_equal(['abcd',
- \ '123456',
- \ ''], getline(1, '$'))
- norm! u
- call assert_equal(['abcd',
- \ 'efgh',
- \ 'ijkl'], getline(1, '$'))
-
- " Test for truncating spaces in a newly added line using 'autoindent' if
- " characters are not added to that line.
- %d_
- call setline(1, [' app', ' bee', ' cat'])
- setlocal autoindent
- exe "normal gg$gRt\n\nr"
- call assert_equal([' apt', '', ' rat'], getline(1, '$'))
-
- " clean up
- %d_
- set bs&vim
-endfunc
-
-func Test_Visual_word_textobject()
- new
- call setline(1, ['First sentence. Second sentence.'])
-
- " When start and end of visual area are identical, 'aw' or 'iw' select
- " the whole word.
- norm! 1go2fcvawy
- call assert_equal('Second ', @")
- norm! 1go2fcviwy
- call assert_equal('Second', @")
-
- " When start and end of visual area are not identical, 'aw' or 'iw'
- " extend the word in direction of the end of the visual area.
- norm! 1go2fcvlawy
- call assert_equal('cond ', @")
- norm! gv2awy
- call assert_equal('cond sentence.', @")
-
- norm! 1go2fcvliwy
- call assert_equal('cond', @")
- norm! gv2iwy
- call assert_equal('cond sentence', @")
-
- " Extend visual area in opposite direction.
- norm! 1go2fcvhawy
- call assert_equal(' Sec', @")
- norm! gv2awy
- call assert_equal(' sentence. Sec', @")
-
- norm! 1go2fcvhiwy
- call assert_equal('Sec', @")
- norm! gv2iwy
- call assert_equal('. Sec', @")
-
- bwipe!
-endfunc
-
-func Test_Visual_sentence_textobject()
- new
- call setline(1, ['First sentence. Second sentence. Third', 'sentence. Fourth sentence'])
-
- " When start and end of visual area are identical, 'as' or 'is' select
- " the whole sentence.
- norm! 1gofdvasy
- call assert_equal('Second sentence. ', @")
- norm! 1gofdvisy
- call assert_equal('Second sentence.', @")
-
- " When start and end of visual area are not identical, 'as' or 'is'
- " extend the sentence in direction of the end of the visual area.
- norm! 1gofdvlasy
- call assert_equal('d sentence. ', @")
- norm! gvasy
- call assert_equal("d sentence. Third\nsentence. ", @")
-
- norm! 1gofdvlisy
- call assert_equal('d sentence.', @")
- norm! gvisy
- call assert_equal('d sentence. ', @")
- norm! gvisy
- call assert_equal("d sentence. Third\nsentence.", @")
-
- " Extend visual area in opposite direction.
- norm! 1gofdvhasy
- call assert_equal(' Second', @")
- norm! gvasy
- call assert_equal("First sentence. Second", @")
-
- norm! 1gofdvhisy
- call assert_equal('Second', @")
- norm! gvisy
- call assert_equal(' Second', @")
- norm! gvisy
- call assert_equal('First sentence. Second', @")
-
- bwipe!
-endfunc
-
-func Test_Visual_paragraph_textobject()
- new
- let lines =<< trim [END]
- First line.
-
- Second line.
- Third line.
- Fourth line.
- Fifth line.
-
- Sixth line.
- [END]
- call setline(1, lines)
-
- " When start and end of visual area are identical, 'ap' or 'ip' select
- " the whole paragraph.
- norm! 4ggvapy
- call assert_equal("Second line.\nThird line.\nFourth line.\nFifth line.\n\n", @")
- norm! 4ggvipy
- call assert_equal("Second line.\nThird line.\nFourth line.\nFifth line.\n", @")
-
- " When start and end of visual area are not identical, 'ap' or 'ip'
- " extend the sentence in direction of the end of the visual area.
- " FIXME: actually, it is not sufficient to have different start and
- " end of visual selection, the start line and end line have to differ,
- " which is not consistent with the documentation.
- norm! 4ggVjapy
- call assert_equal("Third line.\nFourth line.\nFifth line.\n\n", @")
- norm! gvapy
- call assert_equal("Third line.\nFourth line.\nFifth line.\n\nSixth line.\n", @")
- norm! 4ggVjipy
- call assert_equal("Third line.\nFourth line.\nFifth line.\n", @")
- norm! gvipy
- call assert_equal("Third line.\nFourth line.\nFifth line.\n\n", @")
- norm! gvipy
- call assert_equal("Third line.\nFourth line.\nFifth line.\n\nSixth line.\n", @")
-
- " Extend visual area in opposite direction.
- norm! 5ggVkapy
- call assert_equal("\nSecond line.\nThird line.\nFourth line.\n", @")
- norm! gvapy
- call assert_equal("First line.\n\nSecond line.\nThird line.\nFourth line.\n", @")
- norm! 5ggVkipy
- call assert_equal("Second line.\nThird line.\nFourth line.\n", @")
- norma gvipy
- call assert_equal("\nSecond line.\nThird line.\nFourth line.\n", @")
- norm! gvipy
- call assert_equal("First line.\n\nSecond line.\nThird line.\nFourth line.\n", @")
-
- bwipe!
-endfunc
-
-func Test_curswant_not_changed()
- new
- call setline(1, ['one', 'two'])
- au InsertLeave * call getcurpos()
- call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt')
- call assert_equal([0, 2, 1, 0, 1], getcurpos())
-
- bwipe!
- au! InsertLeave
-endfunc
-
-" Tests for "vaBiB", end could be wrong.
-func Test_Visual_Block()
- new
- a
-- Bug in "vPPPP" on this text:
- {
- cmd;
- {
- cmd;\t/* <-- Start cursor here */
- {
- }
- }
- }
-.
- normal gg
- call search('Start cursor here')
- normal vaBiBD
- call assert_equal(['- Bug in "vPPPP" on this text:',
- \ "\t{",
- \ "\t}"], getline(1, '$'))
-
- close!
-endfunc
-
-" Test for 'p'ut in visual block mode
-func Test_visual_block_put()
- new
- call append(0, ['One', 'Two', 'Three'])
- normal gg
- yank
- call feedkeys("jl\<C-V>ljp", 'xt')
- call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$'))
- bw!
-endfunc
-
-func Test_visual_block_put_invalid()
- enew!
- behave mswin
- norm yy
- norm v)Ps/^/
- " this was causing the column to become negative
- silent norm ggv)P
-
- bwipe!
- behave xterm
-endfunc
-
-" Visual modes (v V CTRL-V) followed by an operator; count; repeating
-func Test_visual_mode_op()
- new
- call append(0, '')
-
- call setline(1, 'apple banana cherry')
- call cursor(1, 1)
- normal lvld.l3vd.
- call assert_equal('a y', getline(1))
-
- call setline(1, ['line 1 line 1', 'line 2 line 2', 'line 3 line 3',
- \ 'line 4 line 4', 'line 5 line 5', 'line 6 line 6'])
- call cursor(1, 1)
- exe "normal Vcnewline\<Esc>j.j2Vd."
- call assert_equal(['newline', 'newline'], getline(1, '$'))
-
- call deletebufline('', 1, '$')
- call setline(1, ['xxxxxxxxxxxxx', 'xxxxxxxxxxxxx', 'xxxxxxxxxxxxx',
- \ 'xxxxxxxxxxxxx'])
- exe "normal \<C-V>jlc \<Esc>l.l2\<C-V>c----\<Esc>l."
- call assert_equal([' --------x',
- \ ' --------x',
- \ 'xxxx--------x',
- \ 'xxxx--------x'], getline(1, '$'))
-
- bwipe!
-endfunc
-
-" Visual mode maps (movement and text object)
-" Visual mode maps; count; repeating
-" - Simple
-" - With an Ex command (custom text object)
-func Test_visual_mode_maps()
- new
- call append(0, '')
-
- func SelectInCaps()
- let [line1, col1] = searchpos('\u', 'bcnW')
- let [line2, col2] = searchpos('.\u', 'nW')
- call setpos("'<", [0, line1, col1, 0])
- call setpos("'>", [0, line2, col2, 0])
- normal! gv
- endfunction
-
- vnoremap W /\u/s-1<CR>
- vnoremap iW :<C-U>call SelectInCaps()<CR>
-
- call setline(1, 'KiwiRaspberryDateWatermelonPeach')
- call cursor(1, 1)
- exe "normal vWcNo\<Esc>l.fD2vd."
- call assert_equal('NoNoberryach', getline(1))
-
- call setline(1, 'JambuRambutanBananaTangerineMango')
- call cursor(1, 1)
- exe "normal llviWc-\<Esc>l.l2vdl."
- call assert_equal('--ago', getline(1))
-
- vunmap W
- vunmap iW
- bwipe!
- delfunc SelectInCaps
-endfunc
-
-" Operator-pending mode maps (movement and text object)
-" - Simple
-" - With Ex command moving the cursor
-" - With Ex command and Visual selection (custom text object)
-func Test_visual_oper_pending_mode_maps()
- new
- call append(0, '')
-
- func MoveToCap()
- call search('\u', 'W')
- endfunction
-
- func SelectInCaps()
- let [line1, col1] = searchpos('\u', 'bcnW')
- let [line2, col2] = searchpos('.\u', 'nW')
- call setpos("'<", [0, line1, col1, 0])
- call setpos("'>", [0, line2, col2, 0])
- normal! gv
- endfunction
-
- onoremap W /\u/<CR>
- onoremap <Leader>W :<C-U>call MoveToCap()<CR>
- onoremap iW :<C-U>call SelectInCaps()<CR>
-
- call setline(1, 'PineappleQuinceLoganberryOrangeGrapefruitKiwiZ')
- call cursor(1, 1)
- exe "normal cW-\<Esc>l.l2.l."
- call assert_equal('----Z', getline(1))
-
- call setline(1, 'JuniperDurianZ')
- call cursor(1, 1)
- exe "normal g?\WfD."
- call assert_equal('WhavcreQhevnaZ', getline(1))
-
- call setline(1, 'LemonNectarineZ')
- call cursor(1, 1)
- exe "normal yiWPlciWNew\<Esc>fr."
- call assert_equal('LemonNewNewZ', getline(1))
-
- ounmap W
- ounmap <Leader>W
- ounmap iW
- bwipe!
- delfunc MoveToCap
- delfunc SelectInCaps
-endfunc
-
-" Patch 7.3.879: Properly abort Operator-pending mode for "dv:<Esc>" etc.
-func Test_op_pend_mode_abort()
- new
- call append(0, '')
-
- call setline(1, ['zzzz', 'zzzz'])
- call cursor(1, 1)
-
- exe "normal dV:\<CR>dv:\<CR>"
- call assert_equal(['zzz'], getline(1, 2))
- set nomodifiable
- call assert_fails('exe "normal d:\<CR>"', 'E21:')
- set modifiable
- call feedkeys("dv:\<Esc>dV:\<Esc>", 'xt')
- call assert_equal(['zzz'], getline(1, 2))
- set nomodifiable
- let v:errmsg = ''
- call feedkeys("d:\<Esc>", 'xt')
- call assert_true(v:errmsg !~# '^E21:')
- set modifiable
-
- bwipe!
-endfunc
-
-func Test_characterwise_visual_mode()
- new
-
- " characterwise visual mode: replace last line
- $put ='a'
- let @" = 'x'
- normal v$p
- call assert_equal('x', getline('$'))
-
- " characterwise visual mode: delete middle line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- normal G
- normal kkv$d
- call assert_equal(['', 'b', 'c'], getline(1, '$'))
-
- " characterwise visual mode: delete middle two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- normal Gkkvj$d
- call assert_equal(['', 'c'], getline(1, '$'))
-
- " characterwise visual mode: delete last line
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- normal Gv$d
- call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
-
- " characterwise visual mode: delete last two lines
- call deletebufline('', 1, '$')
- call append('$', ['a', 'b', 'c'])
- normal Gkvj$d
- call assert_equal(['', 'a', ''], getline(1, '$'))
-
- " characterwise visual mode: use a count with the visual mode from the last
- " line in the buffer
- %d _
- call setline(1, ['one', 'two', 'three', 'four'])
- norm! vj$y
- norm! G1vy
- call assert_equal('four', @")
-
- " characterwise visual mode: replace a single character line and the eol
- %d _
- call setline(1, "a")
- normal v$rx
- call assert_equal(['x'], getline(1, '$'))
-
- " replace a character with composing characters
- call setline(1, "xã̳x")
- normal gg0lvrb
- call assert_equal("xbx", getline(1))
-
- bwipe!
-endfunc
-
-func Test_visual_mode_put()
- new
-
- " v_p: replace last character with line register at middle line
- call append('$', ['aaa', 'bbb', 'ccc'])
- normal G
- -2yank
- normal k$vp
- call assert_equal(['', 'aaa', 'bb', 'aaa', '', 'ccc'], getline(1, '$'))
-
- " v_p: replace last character with line register at middle line selecting
- " newline
- call deletebufline('', 1, '$')
- call append('$', ['aaa', 'bbb', 'ccc'])
- normal G
- -2yank
- normal k$v$p
- call assert_equal(['', 'aaa', 'bb', 'aaa', 'ccc'], getline(1, '$'))
-
- " v_p: replace last character with line register at last line
- call deletebufline('', 1, '$')
- call append('$', ['aaa', 'bbb', 'ccc'])
- normal G
- -2yank
- normal $vp
- call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$'))
-
- " v_p: replace last character with line register at last line selecting
- " newline
- call deletebufline('', 1, '$')
- call append('$', ['aaa', 'bbb', 'ccc'])
- normal G
- -2yank
- normal $v$p
- call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$'))
-
- bwipe!
-endfunc
-
-func Test_gv_with_exclusive_selection()
- new
-
- " gv with exclusive selection after an operation
- call append('$', ['zzz ', 'äà '])
- set selection=exclusive
- normal Gkv3lyjv3lpgvcxxx
- call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$'))
-
- " gv with exclusive selection without an operation
- call deletebufline('', 1, '$')
- call append('$', 'zzz ')
- set selection=exclusive
- exe "normal G0v3l\<Esc>gvcxxx"
- call assert_equal(['', 'xxx '], getline(1, '$'))
-
- set selection&vim
- bwipe!
-endfunc
-
-" Tests for the visual block mode commands
-func Test_visual_block_mode()
- new
- call append(0, '')
- call setline(1, repeat(['abcdefghijklm'], 5))
- call cursor(1, 1)
-
- " Test shift-right of a block
- exe "normal jllll\<C-V>jj>wll\<C-V>jlll>"
- " Test shift-left of a block
- exe "normal G$hhhh\<C-V>kk<"
- " Test block-insert
- exe "normal Gkl\<C-V>kkkIxyz"
- " Test block-replace
- exe "normal Gllll\<C-V>kkklllrq"
- " Test block-change
- exe "normal G$khhh\<C-V>hhkkcmno"
- call assert_equal(['axyzbcdefghijklm',
- \ 'axyzqqqq mno ghijklm',
- \ 'axyzqqqqef mno ghijklm',
- \ 'axyzqqqqefgmnoklm',
- \ 'abcdqqqqijklm'], getline(1, 5))
-
- " Test 'C' to change till the end of the line
- call cursor(3, 4)
- exe "normal! \<C-V>j3lCooo"
- call assert_equal(['axyooo', 'axyooo'], getline(3, 4))
-
- " Test 'D' to delete till the end of the line
- call cursor(3, 3)
- exe "normal! \<C-V>j2lD"
- call assert_equal(['ax', 'ax'], getline(3, 4))
-
- " Test block insert with a short line that ends before the block
- %d _
- call setline(1, [" one", "a", " two"])
- exe "normal gg\<C-V>2jIx"
- call assert_equal([" xone", "a", " xtwo"], getline(1, '$'))
-
- " Test block append at EOL with '$' and without '$'
- %d _
- call setline(1, ["one", "a", "two"])
- exe "normal gg$\<C-V>2jAx"
- call assert_equal(["onex", "ax", "twox"], getline(1, '$'))
- %d _
- call setline(1, ["one", "a", "two"])
- exe "normal gg3l\<C-V>2jAx"
- call assert_equal(["onex", "a x", "twox"], getline(1, '$'))
-
- " Test block replace with an empty line in the middle and use $ to jump to
- " the end of the line.
- %d _
- call setline(1, ['one', '', 'two'])
- exe "normal gg$\<C-V>2jrx"
- call assert_equal(["onx", "", "twx"], getline(1, '$'))
-
- " Test block replace with an empty line in the middle and move cursor to the
- " end of the line
- %d _
- call setline(1, ['one', '', 'two'])
- exe "normal gg2l\<C-V>2jrx"
- call assert_equal(["onx", "", "twx"], getline(1, '$'))
-
- " Replace odd number of characters with a multibyte character
- %d _
- call setline(1, ['abcd', 'efgh'])
- exe "normal ggl\<C-V>2ljr\u1100"
- call assert_equal(["a\u1100 ", "e\u1100 "], getline(1, '$'))
-
- " During visual block append, if the cursor moved outside of the selected
- " range, then the edit should not be applied to the block.
- %d _
- call setline(1, ['aaa', 'bbb', 'ccc'])
- exe "normal 2G\<C-V>jAx\<Up>"
- call assert_equal(['aaa', 'bxbb', 'ccc'], getline(1, '$'))
-
- " During visual block append, if the cursor is moved before the start of the
- " block, then the new text should be appended there.
- %d _
- call setline(1, ['aaa', 'bbb', 'ccc'])
- exe "normal $\<C-V>2jA\<Left>x"
- call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
- " Repeat the previous test but use 'l' to move the cursor instead of '$'
- call setline(1, ['aaa', 'bbb', 'ccc'])
- exe "normal! gg2l\<C-V>2jA\<Left>x"
- call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
-
- " Change a characterwise motion to a blockwise motion using CTRL-V
- %d _
- call setline(1, ['123', '456', '789'])
- exe "normal ld\<C-V>j"
- call assert_equal(['13', '46', '789'], getline(1, '$'))
-
- " Test from ':help v_b_I_example'
- %d _
- setlocal tabstop=8 shiftwidth=4
- let lines =<< trim END
- abcdefghijklmnopqrstuvwxyz
- abc defghijklmnopqrstuvwxyz
- abcdef ghi jklmnopqrstuvwxyz
- abcdefghijklmnopqrstuvwxyz
- END
- call setline(1, lines)
- exe "normal ggfo\<C-V>3jISTRING"
- let expected =<< trim END
- abcdefghijklmnSTRINGopqrstuvwxyz
- abc STRING defghijklmnopqrstuvwxyz
- abcdef ghi STRING jklmnopqrstuvwxyz
- abcdefghijklmnSTRINGopqrstuvwxyz
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Test from ':help v_b_A_example'
- %d _
- let lines =<< trim END
- abcdefghijklmnopqrstuvwxyz
- abc defghijklmnopqrstuvwxyz
- abcdef ghi jklmnopqrstuvwxyz
- abcdefghijklmnopqrstuvwxyz
- END
- call setline(1, lines)
- exe "normal ggfo\<C-V>3j$ASTRING"
- let expected =<< trim END
- abcdefghijklmnopqrstuvwxyzSTRING
- abc defghijklmnopqrstuvwxyzSTRING
- abcdef ghi jklmnopqrstuvwxyzSTRING
- abcdefghijklmnopqrstuvwxyzSTRING
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Test from ':help v_b_<_example'
- %d _
- let lines =<< trim END
- abcdefghijklmnopqrstuvwxyz
- abc defghijklmnopqrstuvwxyz
- abcdef ghi jklmnopqrstuvwxyz
- abcdefghijklmnopqrstuvwxyz
- END
- call setline(1, lines)
- exe "normal ggfo\<C-V>3j3l<.."
- let expected =<< trim END
- abcdefghijklmnopqrstuvwxyz
- abc defghijklmnopqrstuvwxyz
- abcdef ghi jklmnopqrstuvwxyz
- abcdefghijklmnopqrstuvwxyz
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Test from ':help v_b_>_example'
- %d _
- let lines =<< trim END
- abcdefghijklmnopqrstuvwxyz
- abc defghijklmnopqrstuvwxyz
- abcdef ghi jklmnopqrstuvwxyz
- abcdefghijklmnopqrstuvwxyz
- END
- call setline(1, lines)
- exe "normal ggfo\<C-V>3j>.."
- let expected =<< trim END
- abcdefghijklmn opqrstuvwxyz
- abc defghijklmnopqrstuvwxyz
- abcdef ghi jklmnopqrstuvwxyz
- abcdefghijklmn opqrstuvwxyz
- END
- call assert_equal(expected, getline(1, '$'))
-
- " Test from ':help v_b_r_example'
- %d _
- let lines =<< trim END
- abcdefghijklmnopqrstuvwxyz
- abc defghijklmnopqrstuvwxyz
- abcdef ghi jklmnopqrstuvwxyz
- abcdefghijklmnopqrstuvwxyz
- END
- call setline(1, lines)
- exe "normal ggfo\<C-V>5l3jrX"
- let expected =<< trim END
- abcdefghijklmnXXXXXXuvwxyz
- abc XXXXXXhijklmnopqrstuvwxyz
- abcdef ghi XXXXXX jklmnopqrstuvwxyz
- abcdefghijklmnXXXXXXuvwxyz
- END
- call assert_equal(expected, getline(1, '$'))
-
- bwipe!
- set tabstop& shiftwidth&
-endfunc
-
-func Test_visual_force_motion_feedkeys()
- onoremap <expr> i- execute('let g:mode = mode(1)')
- call feedkeys('dvi-', 'x')
- call assert_equal('nov', g:mode)
- call feedkeys('di-', 'x')
- call assert_equal('no', g:mode)
- ounmap i-
-endfunc
-
-" Test block-insert using cursor keys for movement
-func Test_visual_block_insert_cursor_keys()
- new
- call append(0, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd'])
- call cursor(1, 1)
-
- exe "norm! l\<C-V>jjjlllI\<Right>\<Right> \<Esc>"
- call assert_equal(['aaa aaa', 'bbb bbb', 'ccc ccc', 'ddd ddd'],
- \ getline(1, 4))
-
- call deletebufline('', 1, '$')
- call setline(1, ['xaaa', 'bbbb', 'cccc', 'dddd'])
- call cursor(1, 1)
- exe "norm! \<C-V>jjjI<>\<Left>p\<Esc>"
- call assert_equal(['<p>xaaa', '<p>bbbb', '<p>cccc', '<p>dddd'],
- \ getline(1, 4))
- bwipe!
-endfunc
-
-func Test_visual_block_create()
- new
- call append(0, '')
- " Test for Visual block was created with the last <C-v>$
- call setline(1, ['A23', '4567'])
- call cursor(1, 1)
- exe "norm! l\<C-V>j$Aab\<Esc>"
- call assert_equal(['A23ab', '4567ab'], getline(1, 2))
-
- " Test for Visual block was created with the middle <C-v>$ (1)
- call deletebufline('', 1, '$')
- call setline(1, ['B23', '4567'])
- call cursor(1, 1)
- exe "norm! l\<C-V>j$hAab\<Esc>"
- call assert_equal(['B23 ab', '4567ab'], getline(1, 2))
-
- " Test for Visual block was created with the middle <C-v>$ (2)
- call deletebufline('', 1, '$')
- call setline(1, ['C23', '4567'])
- call cursor(1, 1)
- exe "norm! l\<C-V>j$hhAab\<Esc>"
- call assert_equal(['C23ab', '456ab7'], getline(1, 2))
- bwipe!
-endfunc
-
-" Test for Visual block insert when virtualedit=all
-func Test_virtualedit_visual_block()
- set ve=all
- new
- call append(0, ["\t\tline1", "\t\tline2", "\t\tline3"])
- call cursor(1, 1)
- exe "norm! 07l\<C-V>jjIx\<Esc>"
- call assert_equal([" x \tline1",
- \ " x \tline2",
- \ " x \tline3"], getline(1, 3))
-
- " Test for Visual block append when virtualedit=all
- exe "norm! 012l\<C-v>jjAx\<Esc>"
- call assert_equal([' x x line1',
- \ ' x x line2',
- \ ' x x line3'], getline(1, 3))
- set ve=
- bwipe!
-endfunc
-
-" Test for changing case
-func Test_visual_change_case()
- new
- " gUe must uppercase a whole word, also when ß changes to SS
- exe "normal Gothe youtußeuu end\<Esc>Ypk0wgUe\r"
- " gUfx must uppercase until x, inclusive.
- exe "normal O- youßtußexu -\<Esc>0fogUfx\r"
- " VU must uppercase a whole line
- exe "normal YpkVU\r"
- " same, when it's the last line in the buffer
- exe "normal YPGi111\<Esc>VUddP\r"
- " Uppercase two lines
- exe "normal Oblah di\rdoh dut\<Esc>VkUj\r"
- " Uppercase part of two lines
- exe "normal ddppi333\<Esc>k0i222\<Esc>fyllvjfuUk"
- call assert_equal(['the YOUTUSSEUU end', '- yOUSSTUSSEXu -',
- \ 'THE YOUTUSSEUU END', '111THE YOUTUSSEUU END', 'BLAH DI', 'DOH DUT',
- \ '222the yoUTUSSEUU END', '333THE YOUTUßeuu end'], getline(2, '$'))
- bwipe!
-endfunc
-
-" Test for Visual replace using Enter or NL
-func Test_visual_replace_crnl()
- new
- exe "normal G3o123456789\e2k05l\<C-V>2jr\r"
- exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\r\n"
- exe "normal G3o123456789\e2k05l\<C-V>2jr\n"
- exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\n"
- call assert_equal(['12345', '789', '12345', '789', '12345', '789', "98\r65",
- \ "98\r65", "98\r65", '12345', '789', '12345', '789', '12345', '789',
- \ "98\n65", "98\n65", "98\n65"], getline(2, '$'))
- bwipe!
-endfunc
-
-func Test_ve_block_curpos()
- new
- " Test cursor position. When ve=block and Visual block mode and $gj
- call append(0, ['12345', '789'])
- call cursor(1, 3)
- set virtualedit=block
- exe "norm! \<C-V>$gj\<Esc>"
- call assert_equal([0, 2, 4, 0], getpos("'>"))
- set virtualedit=
- bwipe!
-endfunc
-
-" Test for block_insert when replacing spaces in front of the a with tabs
-func Test_block_insert_replace_tabs()
- new
- set ts=8 sts=4 sw=4
- call append(0, ["#define BO_ALL\t 0x0001",
- \ "#define BO_BS\t 0x0002",
- \ "#define BO_CRSR\t 0x0004"])
- call cursor(1, 1)
- exe "norm! f0\<C-V>2jI\<tab>\<esc>"
- call assert_equal([
- \ "#define BO_ALL\t\t0x0001",
- \ "#define BO_BS\t \t0x0002",
- \ "#define BO_CRSR\t \t0x0004", ''], getline(1, '$'))
- set ts& sts& sw&
- bwipe!
-endfunc
-
-" Test for * register in :
-func Test_star_register()
- call assert_fails('*bfirst', 'E16:')
- new
- call setline(1, ['foo', 'bar', 'baz', 'qux'])
- exe "normal jVj\<ESC>"
- *yank r
- call assert_equal("bar\nbaz\n", @r)
-
- delmarks < >
- call assert_fails('*yank', 'E20:')
- close!
-endfunc
-
-" Test for changing text in visual mode with 'exclusive' selection
-func Test_exclusive_selection()
- new
- call setline(1, ['one', 'two'])
- set selection=exclusive
- call feedkeys("vwcabc", 'xt')
- call assert_equal('abctwo', getline(1))
- call setline(1, ["\tone"])
- set virtualedit=all
- call feedkeys('0v2lcl', 'xt')
- call assert_equal('l one', getline(1))
- set virtualedit&
- set selection&
- close!
-endfunc
-
-" Test for starting linewise visual with a count.
-" This test needs to be run without any previous visual mode. Otherwise the
-" count will use the count from the previous visual mode.
-func Test_linewise_visual_with_count()
- let after =<< trim [CODE]
- call setline(1, ['one', 'two', 'three', 'four'])
- norm! 3Vy
- call assert_equal("one\ntwo\nthree\n", @")
- call writefile(v:errors, 'Xtestout')
- qall!
- [CODE]
- if RunVim([], after, '')
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for starting characterwise visual with a count.
-" This test needs to be run without any previous visual mode. Otherwise the
-" count will use the count from the previous visual mode.
-func Test_characterwise_visual_with_count()
- let after =<< trim [CODE]
- call setline(1, ['one two', 'three'])
- norm! l5vy
- call assert_equal("ne tw", @")
- call writefile(v:errors, 'Xtestout')
- qall!
- [CODE]
- if RunVim([], after, '')
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for visually selecting an inner block (iB)
-func Test_visual_inner_block()
- new
- call setline(1, ['one', '{', 'two', '{', 'three', '}', 'four', '}', 'five'])
- call cursor(5, 1)
- " visually select all the lines in the block and then execute iB
- call feedkeys("ViB\<C-C>", 'xt')
- call assert_equal([0, 5, 1, 0], getpos("'<"))
- call assert_equal([0, 5, 6, 0], getpos("'>"))
- " visually select two inner blocks
- call feedkeys("ViBiB\<C-C>", 'xt')
- call assert_equal([0, 3, 1, 0], getpos("'<"))
- call assert_equal([0, 7, 5, 0], getpos("'>"))
- " try to select non-existing inner block
- call cursor(5, 1)
- call assert_beeps('normal ViBiBiB')
- " try to select a unclosed inner block
- 8,9d
- call cursor(5, 1)
- call assert_beeps('normal ViBiB')
- close!
-endfunc
-
-func Test_visual_put_in_block()
- new
- call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
- normal 1G2yl
- exe "normal 1G2l\<C-V>jjlp"
- call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
- bwipe!
-endfunc
-
-func Test_visual_put_in_block_using_zp()
- new
- " paste using zP
- call setline(1, ['/path;text', '/path;text', '/path;text', '',
- \ '/subdir',
- \ '/longsubdir',
- \ '/longlongsubdir'])
- exe "normal! 5G\<c-v>2j$y"
- norm! 1Gf;zP
- call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
- %d
- " paste using zP
- call setline(1, ['/path;text', '/path;text', '/path;text', '',
- \ '/subdir',
- \ '/longsubdir',
- \ '/longlongsubdir'])
- exe "normal! 5G\<c-v>2j$y"
- norm! 1Gf;hzp
- call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
- bwipe!
-endfunc
-
-func Test_visual_put_in_block_using_zy_and_zp()
- new
-
- " Test 1) Paste using zp - after the cursor without trailing spaces
- call setline(1, ['/path;text', '/path;text', '/path;text', '',
- \ 'texttext /subdir columntext',
- \ 'texttext /longsubdir columntext',
- \ 'texttext /longlongsubdir columntext'])
- exe "normal! 5G0f/\<c-v>2jezy"
- norm! 1G0f;hzp
- call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
-
- " Test 2) Paste using zP - in front of the cursor without trailing spaces
- %d
- call setline(1, ['/path;text', '/path;text', '/path;text', '',
- \ 'texttext /subdir columntext',
- \ 'texttext /longsubdir columntext',
- \ 'texttext /longlongsubdir columntext'])
- exe "normal! 5G0f/\<c-v>2jezy"
- norm! 1G0f;zP
- call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
-
- " Test 3) Paste using p - with trailing spaces
- %d
- call setline(1, ['/path;text', '/path;text', '/path;text', '',
- \ 'texttext /subdir columntext',
- \ 'texttext /longsubdir columntext',
- \ 'texttext /longlongsubdir columntext'])
- exe "normal! 5G0f/\<c-v>2jezy"
- norm! 1G0f;hp
- call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3))
-
- " Test 4) Paste using P - with trailing spaces
- %d
- call setline(1, ['/path;text', '/path;text', '/path;text', '',
- \ 'texttext /subdir columntext',
- \ 'texttext /longsubdir columntext',
- \ 'texttext /longlongsubdir columntext'])
- exe "normal! 5G0f/\<c-v>2jezy"
- norm! 1G0f;P
- call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3))
-
- " Test 5) Yank with spaces inside the block
- %d
- call setline(1, ['/path;text', '/path;text', '/path;text', '',
- \ 'texttext /sub dir/ columntext',
- \ 'texttext /lon gsubdir/ columntext',
- \ 'texttext /lon glongsubdir/ columntext'])
- exe "normal! 5G0f/\<c-v>2jf/zy"
- norm! 1G0f;zP
- call assert_equal(['/path/sub dir/;text', '/path/lon gsubdir/;text', '/path/lon glongsubdir/;text'], getline(1, 3))
- bwipe!
-endfunc
-
-func Test_visual_put_blockedit_zy_and_zp()
- new
-
- call setline(1, ['aa', 'bbbbb', 'ccc', '', 'XX', 'GGHHJ', 'RTZU'])
- exe "normal! gg0\<c-v>2j$zy"
- norm! 5gg0zP
- call assert_equal(['aa', 'bbbbb', 'ccc', '', 'aaXX', 'bbbbbGGHHJ', 'cccRTZU'], getline(1, 7))
- "
- " now with blockmode editing
- sil %d
- :set ve=block
- call setline(1, ['aa', 'bbbbb', 'ccc', '', 'XX', 'GGHHJ', 'RTZU'])
- exe "normal! gg0\<c-v>2j$zy"
- norm! 5gg0zP
- call assert_equal(['aa', 'bbbbb', 'ccc', '', 'aaXX', 'bbbbbGGHHJ', 'cccRTZU'], getline(1, 7))
- set ve&vim
- bw!
-endfunc
-
-func Test_visual_block_yank_zy()
- new
- " this was reading before the start of the line
- exe "norm o\<C-T>\<Esc>\<C-V>zy"
- bwipe!
-endfunc
-
-func Test_visual_block_with_virtualedit()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['aaaaaa', 'bbbb', 'cc'])
- set virtualedit=block
- normal G
- END
- call writefile(lines, 'XTest_block')
-
- let buf = RunVimInTerminal('-S XTest_block', {'rows': 8, 'cols': 50})
- call term_sendkeys(buf, "\<C-V>gg$")
- call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit', {})
-
- call term_sendkeys(buf, "\<Esc>gg\<C-V>G$")
- call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit2', {})
-
- " clean up
- call term_sendkeys(buf, "\<Esc>")
- call StopVimInTerminal(buf)
- call delete('XTest_block')
-endfunc
-
-func Test_visual_block_ctrl_w_f()
- " Empty block selected in new buffer should not result in an error.
- au! BufNew foo sil norm f
- edit foo
-
- au! BufNew
-endfunc
-
-func Test_visual_block_append_invalid_char()
- " this was going over the end of the line
- set isprint=@,161-255
- new
- call setline(1, [' let xxx', 'xxxxxˆ', 'xxxxxxxxxxx'])
- exe "normal 0\<C-V>jjA-\<Esc>"
- call assert_equal([' - let xxx', 'xxxxx -ˆ', 'xxxxxxxx-xxx'], getline(1, 3))
- bwipe!
- set isprint&
-endfunc
-
-func Test_visual_block_with_substitute()
- " this was reading beyond the end of the line
- new
- norm a0)
- sil! norm  O
- s/)
- sil! norm 
- bwipe!
-endfunc
-
-func Test_visual_reselect_with_count()
- " this was causing an illegal memory access
- let lines =<< trim END
-
-
-
- :
- r<sfile>
- exe "%norm e3\<c-v>kr\t"
- :
-
- :
- END
- call writefile(lines, 'XvisualReselect')
- source XvisualReselect
-
- bwipe!
- call delete('XvisualReselect')
-endfunc
-
-func Test_visual_reselect_exclusive()
- new
- call setline(1, ['abcde', 'abcde'])
- set selection=exclusive
- normal 1G0viwd
- normal 2G01vd
- call assert_equal(['', ''], getline(1, 2))
-
- set selection&
- bwipe!
-endfunc
-
-func Test_visual_block_insert_round_off()
- new
- " The number of characters are tuned to fill a 4096 byte allocated block,
- " so that valgrind reports going over the end.
- call setline(1, ['xxxxx', repeat('0', 1350), "\t", repeat('x', 60)])
- exe "normal gg0\<C-V>GI" .. repeat('0', 1320) .. "\<Esc>"
- bwipe!
-endfunc
-
-" this was causing an ml_get error
-func Test_visual_exchange_windows()
- enew!
- new
- call setline(1, ['foo', 'bar'])
- exe "normal G\<C-V>gg\<C-W>\<C-X>OO\<Esc>"
- bwipe!
- bwipe!
-endfunc
-
-" this was leaving the end of the Visual area beyond the end of a line
-func Test_visual_ex_copy_line()
- new
- call setline(1, ["aaa", "bbbbbbbbbxbb"])
- /x
- exe "normal ggvjfxO"
- t0
- normal gNU
- bwipe!
-endfunc
-
-" This was leaving the end of the Visual area beyond the end of a line.
-" Set 'undolevels' to start a new undo block.
-func Test_visual_undo_deletes_last_line()
- new
- call setline(1, ["aaa", "ccc", "dyd"])
- set undolevels=100
- exe "normal obbbbbbbbbxbb\<Esc>"
- set undolevels=100
- /y
- exe "normal ggvjfxO"
- undo
- normal gNU
-
- bwipe!
-endfunc
-
-func Test_visual_paste()
- new
-
- " v_p overwrites unnamed register.
- call setline(1, ['xxxx'])
- call setreg('"', 'foo')
- call setreg('-', 'bar')
- normal gg0vp
- call assert_equal('x', @")
- call assert_equal('x', @-)
- call assert_equal('fooxxx', getline(1))
- normal $vp
- call assert_equal('x', @")
- call assert_equal('x', @-)
- call assert_equal('fooxxx', getline(1))
- " Test with a different register as unnamed register.
- call setline(2, ['baz'])
- normal 2gg0"rD
- call assert_equal('baz', @")
- normal gg0vp
- call assert_equal('f', @")
- call assert_equal('f', @-)
- call assert_equal('bazooxxx', getline(1))
- normal $vp
- call assert_equal('x', @")
- call assert_equal('x', @-)
- call assert_equal('bazooxxf', getline(1))
-
- bwipe!
-endfunc
-
-func Test_visual_paste_clipboard()
- CheckFeature clipboard_working
-
- if has('gui')
- " auto select feature breaks tests
- set guioptions-=a
- endif
-
- " v_P does not overwrite unnamed register.
- call setline(1, ['xxxx'])
- call setreg('"', 'foo')
- call setreg('-', 'bar')
- normal gg0vP
- call assert_equal('foo', @")
- call assert_equal('bar', @-)
- call assert_equal('fooxxx', getline(1))
- normal $vP
- call assert_equal('foo', @")
- call assert_equal('bar', @-)
- call assert_equal('fooxxfoo', getline(1))
- " Test with a different register as unnamed register.
- call setline(2, ['baz'])
- normal 2gg0"rD
- call assert_equal('baz', @")
- normal gg0vP
- call assert_equal('baz', @")
- call assert_equal('bar', @-)
- call assert_equal('bazooxxfoo', getline(1))
- normal $vP
- call assert_equal('baz', @")
- call assert_equal('bar', @-)
- call assert_equal('bazooxxfobaz', getline(1))
-
- " Test for unnamed clipboard
- set clipboard=unnamed
- call setline(1, ['xxxx'])
- call setreg('"', 'foo')
- call setreg('-', 'bar')
- call setreg('*', 'baz')
- normal gg0vP
- call assert_equal('foo', @")
- call assert_equal('bar', @-)
- call assert_equal('baz', @*)
- call assert_equal('bazxxx', getline(1))
-
- " Test for unnamedplus clipboard
- if has('unnamedplus')
- set clipboard=unnamedplus
- call setline(1, ['xxxx'])
- call setreg('"', 'foo')
- call setreg('-', 'bar')
- call setreg('+', 'baz')
- normal gg0vP
- call assert_equal('foo', @")
- call assert_equal('bar', @-)
- call assert_equal('baz', @+)
- call assert_equal('bazxxx', getline(1))
- endif
-
- set clipboard&
- if has('gui')
- set guioptions&
- endif
- bwipe!
-endfunc
-
-func Test_visual_area_adjusted_when_hiding()
- " The Visual area ended after the end of the line after :hide
- call setline(1, 'xxx')
- vsplit Xfile
- call setline(1, 'xxxxxxxx')
- norm! $o
- hid
- norm! zW
- bwipe!
- bwipe!
-endfunc
-
-func Test_switch_buffer_ends_visual_mode()
- enew
- call setline(1, 'foo')
- set hidden
- set virtualedit=all
- let buf1 = bufnr()
- enew
- let buf2 = bufnr()
- call setline(1, ['', '', '', ''])
- call cursor(4, 5)
- call feedkeys("\<C-V>3k4h", 'xt')
- exe 'buffer' buf1
- call assert_equal('n', mode())
-
- set nohidden
- set virtualedit=
- bwipe!
- exe 'bwipe!' buf2
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim
deleted file mode 100644
index 26b4ba8778..0000000000
--- a/src/nvim/testdir/test_winbuf_close.vim
+++ /dev/null
@@ -1,231 +0,0 @@
-" Test for commands that close windows and/or buffers:
-" :quit
-" :close
-" :hide
-" :only
-" :sall
-" :all
-" :ball
-" :buf
-" :edit
-"
-func Test_winbuf_close()
- enew | only
-
- call writefile(['testtext 1'], 'Xtest1')
- call writefile(['testtext 2'], 'Xtest2')
- call writefile(['testtext 3'], 'Xtest3')
-
- next! Xtest1 Xtest2
- call setline(1, 'testtext 1 1')
-
- " test for working :n when hidden set
- set hidden
- next
- call assert_equal('Xtest2', bufname('%'))
-
- " test for failing :rew when hidden not set
- set nohidden
- call setline(1, 'testtext 2 2')
- call assert_fails('rewind', 'E37')
- call assert_equal('Xtest2', bufname('%'))
- call assert_equal('testtext 2 2', getline(1))
-
- " test for working :rew when hidden set
- set hidden
- rewind
- call assert_equal('Xtest1', bufname('%'))
- call assert_equal('testtext 1 1', getline(1))
-
- " test for :all keeping a buffer when it's modified
- set nohidden
- call setline(1, 'testtext 1 1 1')
- split
- next Xtest2 Xtest3
- all
- 1wincmd w
- call assert_equal('Xtest1', bufname('%'))
- call assert_equal('testtext 1 1 1', getline(1))
-
- " test abandoning changed buffer, should be unloaded even when 'hidden' set
- set hidden
- call setline(1, 'testtext 1 1 1 1')
- quit!
- call assert_equal('Xtest2', bufname('%'))
- call assert_equal('testtext 2 2', getline(1))
- unhide
- call assert_equal('Xtest2', bufname('%'))
- call assert_equal('testtext 2 2', getline(1))
-
- " test ":hide" hides anyway when 'hidden' not set
- set nohidden
- call setline(1, 'testtext 2 2 2')
- hide
- call assert_equal('Xtest3', bufname('%'))
- call assert_equal('testtext 3', getline(1))
-
- " test ":edit" failing in modified buffer when 'hidden' not set
- call setline(1, 'testtext 3 3')
- call assert_fails('edit Xtest1', 'E37')
- call assert_equal('Xtest3', bufname('%'))
- call assert_equal('testtext 3 3', getline(1))
-
- " test ":edit" working in modified buffer when 'hidden' set
- set hidden
- edit Xtest1
- call assert_equal('Xtest1', bufname('%'))
- call assert_equal('testtext 1', getline(1))
-
- " test ":close" not hiding when 'hidden' not set in modified buffer
- split Xtest3
- set nohidden
- call setline(1, 'testtext 3 3 3')
- call assert_fails('close', 'E37')
- call assert_equal('Xtest3', bufname('%'))
- call assert_equal('testtext 3 3 3', getline(1))
-
- " test ":close!" does hide when 'hidden' not set in modified buffer;
- call setline(1, 'testtext 3 3 3 3')
- close!
- call assert_equal('Xtest1', bufname('%'))
- call assert_equal('testtext 1', getline(1))
-
- set nohidden
-
- " test ":all!" hides changed buffer
- split Xtest4
- call setline(1, 'testtext 4')
- all!
- 1wincmd w
- call assert_equal('Xtest2', bufname('%'))
- call assert_equal('testtext 2 2 2', getline(1))
-
- " test ":q!" and hidden buffer.
- bwipe! Xtest1 Xtest2 Xtest3 Xtest4
- split Xtest1
- wincmd w
- bwipe!
- set modified
- bot split Xtest2
- set modified
- bot split Xtest3
- set modified
- wincmd t
- hide
- call assert_equal('Xtest2', bufname('%'))
- quit!
- call assert_equal('Xtest3', bufname('%'))
- call assert_fails('silent! quit!', 'E37')
- call assert_equal('Xtest1', bufname('%'))
-
- call delete('Xtest1')
- call delete('Xtest2')
- call delete('Xtest3')
-endfunc
-
-" Test that ":close" will respect 'winfixheight' when possible.
-func Test_winfixheight_on_close()
- set nosplitbelow nosplitright
-
- split | split | vsplit
-
- $wincmd w
- setlocal winfixheight
- let l:height = winheight(0)
-
- 3close
-
- call assert_equal(l:height, winheight(0))
-
- %bwipeout!
- setlocal nowinfixheight splitbelow& splitright&
-endfunc
-
-" Test that ":close" will respect 'winfixwidth' when possible.
-func Test_winfixwidth_on_close()
- set nosplitbelow nosplitright
-
- vsplit | vsplit | split
-
- $wincmd w
- setlocal winfixwidth
- let l:width = winwidth(0)
-
- 3close
-
- call assert_equal(l:width, winwidth(0))
-
- %bwipeout!
- setlocal nowinfixwidth splitbelow& splitright&
-endfunction
-
-" Test that 'winfixheight' will be respected even there is non-leaf frame
-func Test_winfixheight_non_leaf_frame()
- vsplit
- botright 11new
- let l:wid = win_getid()
- setlocal winfixheight
- call assert_equal(11, winheight(l:wid))
- botright new
- bwipe!
- call assert_equal(11, winheight(l:wid))
- %bwipe!
-endf
-
-" Test that 'winfixwidth' will be respected even there is non-leaf frame
-func Test_winfixwidth_non_leaf_frame()
- split
- topleft 11vnew
- let l:wid = win_getid()
- setlocal winfixwidth
- call assert_equal(11, winwidth(l:wid))
- topleft new
- bwipe!
- call assert_equal(11, winwidth(l:wid))
- %bwipe!
-endf
-
-func Test_tabwin_close()
- enew
- let l:wid = win_getid()
- tabedit
- call win_execute(l:wid, 'close')
- " Should not crash.
- call assert_true(v:true)
-
- " This tests closing a window in another tab, while leaving the tab open
- " i.e. two windows in another tab.
- tabedit
- let w:this_win = 42
- new
- let othertab_wid = win_getid()
- tabprevious
- call win_execute(othertab_wid, 'q')
- " drawing the tabline helps check that the other tab's windows and buffers
- " are still valid
- redrawtabline
- " but to be certain, ensure we can focus the other tab too
- tabnext
- call assert_equal(42, w:this_win)
-
- bwipe!
-endfunc
-
-" Test when closing a split window (above/below) restores space to the window
-" below when 'noequalalways' and 'splitright' are set.
-func Test_window_close_splitright_noequalalways()
- set noequalalways
- set splitright
- new
- let w1 = win_getid()
- new
- let w2 = win_getid()
- execute "normal \<c-w>b"
- let h = winheight(0)
- let w = win_getid()
- new
- q
- call assert_equal(h, winheight(0), "Window height does not match eight before opening and closing another window")
- call assert_equal(w, win_getid(), "Did not return to original window after opening and closing a window")
-endfunc
-
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
deleted file mode 100644
index c25b1f1157..0000000000
--- a/src/nvim/testdir/test_window_cmd.vim
+++ /dev/null
@@ -1,1833 +0,0 @@
-" Tests for window cmd (:wincmd, :split, :vsplit, :resize and etc...)
-
-source check.vim
-source screendump.vim
-
-func Test_window_cmd_ls0_with_split()
- set ls=0
- set splitbelow
- split
- quit
- call assert_equal(0, &lines - &cmdheight - winheight(0))
- new | only!
- "
- set splitbelow&vim
- botright split
- quit
- call assert_equal(0, &lines - &cmdheight - winheight(0))
- new | only!
- set ls&vim
-endfunc
-
-func Test_window_cmd_cmdwin_with_vsp()
- let efmt = 'Expected 0 but got %d (in ls=%d, %s window)'
- for v in range(0, 2)
- exec "set ls=" . v
- vsplit
- call feedkeys("q:\<CR>")
- let ac = &lines - (&cmdheight + winheight(0) + !!v)
- let emsg = printf(efmt, ac, v, 'left')
- call assert_equal(0, ac, emsg)
- wincmd w
- let ac = &lines - (&cmdheight + winheight(0) + !!v)
- let emsg = printf(efmt, ac, v, 'right')
- call assert_equal(0, ac, emsg)
- new | only!
- endfor
- set ls&vim
-endfunc
-
-" Test for jumping to windows
-func Test_window_jump()
- new
- " jumping to a window with a count greater than the max windows
- exe "normal 4\<C-W>w"
- call assert_equal(2, winnr())
- only
-endfunc
-
-func Test_window_cmd_wincmd_gf()
- let fname = 'test_gf.txt'
- let swp_fname = '.' . fname . '.swp'
- call writefile([], fname)
- call writefile([], swp_fname)
- function s:swap_exists()
- let v:swapchoice = s:swap_choice
- endfunc
- " Remove the catch-all that runtest.vim adds
- au! SwapExists
- augroup test_window_cmd_wincmd_gf
- autocmd!
- exec "autocmd SwapExists " . fname . " call s:swap_exists()"
- augroup END
-
- call setline(1, fname)
- " (E)dit anyway
- let s:swap_choice = 'e'
- wincmd gf
- call assert_equal(2, tabpagenr())
- call assert_equal(fname, bufname("%"))
- quit!
-
- " (Q)uit
- let s:swap_choice = 'q'
- wincmd gf
- call assert_equal(1, tabpagenr())
- call assert_notequal(fname, bufname("%"))
- new | only!
-
- call delete(fname)
- call delete(swp_fname)
- augroup! test_window_cmd_wincmd_gf
-endfunc
-
-func Test_window_quit()
- e Xa
- split Xb
- call assert_equal(2, '$'->winnr())
- call assert_equal('Xb', bufname(winbufnr(1)))
- call assert_equal('Xa', bufname(winbufnr(2)))
-
- wincmd q
- call assert_equal(1, winnr('$'))
- call assert_equal('Xa', bufname(winbufnr(1)))
-
- bw Xa Xb
-endfunc
-
-func Test_window_horizontal_split()
- call assert_equal(1, winnr('$'))
- 3wincmd s
- call assert_equal(2, winnr('$'))
- call assert_equal(3, winheight(0))
- call assert_equal(winwidth(1), 2->winwidth())
-
- call assert_fails('botright topleft wincmd s', 'E442:')
- bw
-endfunc
-
-func Test_window_vertical_split()
- call assert_equal(1, winnr('$'))
- 3wincmd v
- call assert_equal(2, winnr('$'))
- call assert_equal(3, winwidth(0))
- call assert_equal(winheight(1), winheight(2))
-
- call assert_fails('botright topleft wincmd v', 'E442:')
- bw
-endfunc
-
-" Test the ":wincmd ^" and "<C-W>^" commands.
-func Test_window_split_edit_alternate()
-
- " Test for failure when the alternate buffer/file no longer exists.
- edit Xfoo | %bw
- call assert_fails(':wincmd ^', 'E23')
-
- " Test for the expected behavior when we have two named buffers.
- edit Xfoo | edit Xbar
- wincmd ^
- call assert_equal('Xfoo', bufname(winbufnr(1)))
- call assert_equal('Xbar', bufname(winbufnr(2)))
- only
-
- " 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_split_no_room()
- " N horizontal windows need >= 2*N + 1 lines:
- " - 1 line + 1 status line in each window
- " - 1 Ex command line
- "
- " 2*N + 1 <= &lines
- " N <= (lines - 1)/2
- "
- " Beyond that number of windows, E36: Not enough room is expected.
- let hor_win_count = (&lines - 1)/2
- let hor_split_count = hor_win_count - 1
- for s in range(1, hor_split_count) | split | endfor
- call assert_fails('split', 'E36:')
-
- " N vertical windows need >= 2*(N - 1) + 1 columns:
- " - 1 column + 1 separator for each window (except last window)
- " - 1 column for the last window which does not have separator
- "
- " 2*(N - 1) + 1 <= &columns
- " 2*N - 1 <= &columns
- " N <= (&columns + 1)/2
- let ver_win_count = (&columns + 1)/2
- let ver_split_count = ver_win_count - 1
- for s in range(1, ver_split_count) | vsplit | endfor
- call assert_fails('vsplit', 'E36:')
-
- %bw!
-endfunc
-
-func Test_window_exchange()
- e Xa
-
- " Nothing happens with window exchange when there is 1 window
- wincmd x
- call assert_equal(1, winnr('$'))
-
- split Xb
- split Xc
-
- call assert_equal('Xc', bufname(winbufnr(1)))
- call assert_equal('Xb', bufname(winbufnr(2)))
- call assert_equal('Xa', bufname(winbufnr(3)))
-
- " Exchange current window 1 with window 3
- 3wincmd x
- call assert_equal('Xa', bufname(winbufnr(1)))
- call assert_equal('Xb', bufname(winbufnr(2)))
- call assert_equal('Xc', bufname(winbufnr(3)))
-
- " Exchange window with next when at the top window
- wincmd x
- call assert_equal('Xb', bufname(winbufnr(1)))
- call assert_equal('Xa', bufname(winbufnr(2)))
- call assert_equal('Xc', bufname(winbufnr(3)))
-
- " Exchange window with next when at the middle window
- wincmd j
- wincmd x
- call assert_equal('Xb', bufname(winbufnr(1)))
- call assert_equal('Xc', bufname(winbufnr(2)))
- call assert_equal('Xa', bufname(winbufnr(3)))
-
- " Exchange window with next when at the bottom window.
- " When there is no next window, it exchanges with the previous window.
- wincmd j
- wincmd x
- call assert_equal('Xb', bufname(winbufnr(1)))
- call assert_equal('Xa', bufname(winbufnr(2)))
- call assert_equal('Xc', bufname(winbufnr(3)))
-
- bw Xa Xb Xc
-endfunc
-
-func Test_window_rotate()
- e Xa
- split Xb
- split Xc
- call assert_equal('Xc', bufname(winbufnr(1)))
- call assert_equal('Xb', bufname(winbufnr(2)))
- call assert_equal('Xa', bufname(winbufnr(3)))
-
- " Rotate downwards
- wincmd r
- call assert_equal('Xa', bufname(winbufnr(1)))
- call assert_equal('Xc', bufname(winbufnr(2)))
- call assert_equal('Xb', bufname(winbufnr(3)))
-
- 2wincmd r
- call assert_equal('Xc', bufname(winbufnr(1)))
- call assert_equal('Xb', bufname(winbufnr(2)))
- call assert_equal('Xa', bufname(winbufnr(3)))
-
- " Rotate upwards
- wincmd R
- call assert_equal('Xb', bufname(winbufnr(1)))
- call assert_equal('Xa', bufname(winbufnr(2)))
- call assert_equal('Xc', bufname(winbufnr(3)))
-
- 2wincmd R
- call assert_equal('Xc', bufname(winbufnr(1)))
- call assert_equal('Xb', bufname(winbufnr(2)))
- call assert_equal('Xa', bufname(winbufnr(3)))
-
- bot vsplit
- call assert_fails('wincmd R', 'E443:')
-
- bw Xa Xb Xc
-endfunc
-
-func Test_window_height()
- e Xa
- split Xb
-
- let [wh1, wh2] = [winheight(1), winheight(2)]
- " Active window (1) should have the same height or 1 more
- " than the other window.
- call assert_inrange(wh2, wh2 + 1, wh1)
-
- wincmd -
- call assert_equal(wh1 - 1, winheight(1))
- call assert_equal(wh2 + 1, winheight(2))
-
- wincmd +
- call assert_equal(wh1, winheight(1))
- call assert_equal(wh2, 2->winheight())
-
- 2wincmd _
- call assert_equal(2, winheight(1))
- call assert_equal(wh1 + wh2 - 2, winheight(2))
-
- wincmd =
- call assert_equal(wh1, winheight(1))
- call assert_equal(wh2, winheight(2))
-
- 2wincmd _
- set winfixheight
- split Xc
- let [wh1, wh2, wh3] = [winheight(1), winheight(2), winheight(3)]
- call assert_equal(2, winheight(2))
- call assert_inrange(wh3, wh3 + 1, wh1)
- 3wincmd +
- call assert_equal(2, winheight(2))
- call assert_equal(wh1 + 3, winheight(1))
- call assert_equal(wh3 - 3, winheight(3))
- wincmd =
- call assert_equal(2, winheight(2))
- call assert_equal(wh1, winheight(1))
- call assert_equal(wh3, winheight(3))
-
- wincmd j
- set winfixheight&
-
- wincmd =
- let [wh1, wh2, wh3] = [winheight(1), winheight(2), winheight(3)]
- " Current window (2) should have the same height or 1 more
- " than the other windows.
- call assert_inrange(wh1, wh1 + 1, wh2)
- call assert_inrange(wh3, wh3 + 1, wh2)
-
- bw Xa Xb Xc
-endfunc
-
-func Test_wincmd_equal()
- edit Xone
- below split Xtwo
- rightbelow vsplit Xthree
- call assert_equal('Xone', bufname(winbufnr(1)))
- call assert_equal('Xtwo', bufname(winbufnr(2)))
- call assert_equal('Xthree', bufname(winbufnr(3)))
-
- " Xone and Xtwo should be about the same height
- let [wh1, wh2] = [winheight(1), winheight(2)]
- call assert_inrange(wh1 - 1, wh1 + 1, wh2)
- " Xtwo and Xthree should be about the same width
- let [ww2, ww3] = [winwidth(2), winwidth(3)]
- call assert_inrange(ww2 - 1, ww2 + 1, ww3)
-
- 1wincmd w
- 10wincmd _
- 2wincmd w
- 20wincmd |
- call assert_equal(10, winheight(1))
- call assert_equal(20, winwidth(2))
-
- " equalizing horizontally doesn't change the heights
- hor wincmd =
- call assert_equal(10, winheight(1))
- let [ww2, ww3] = [winwidth(2), winwidth(3)]
- call assert_inrange(ww2 - 1, ww2 + 1, ww3)
-
- 2wincmd w
- 20wincmd |
- call assert_equal(20, winwidth(2))
- " equalizing vertically doesn't change the widths
- vert wincmd =
- call assert_equal(20, winwidth(2))
- let [wh1, wh2] = [winheight(1), winheight(2)]
- call assert_inrange(wh1 - 1, wh1 + 1, wh2)
-
- bwipe Xone Xtwo Xthree
-endfunc
-
-func Test_window_width()
- e Xa
- vsplit Xb
-
- let [ww1, ww2] = [winwidth(1), winwidth(2)]
- " Active window (1) should have the same width or 1 more
- " than the other window.
- call assert_inrange(ww2, ww2 + 1, ww1)
-
- wincmd <
- call assert_equal(ww1 - 1, winwidth(1))
- call assert_equal(ww2 + 1, winwidth(2))
-
- wincmd >
- call assert_equal(ww1, winwidth(1))
- call assert_equal(ww2, winwidth(2))
-
- 2wincmd |
- call assert_equal(2, winwidth(1))
- call assert_equal(ww1 + ww2 - 2, winwidth(2))
-
- wincmd =
- call assert_equal(ww1, winwidth(1))
- call assert_equal(ww2, winwidth(2))
-
- 2wincmd |
- set winfixwidth
- vsplit Xc
- let [ww1, ww2, ww3] = [winwidth(1), winwidth(2), winwidth(3)]
- call assert_equal(2, winwidth(2))
- call assert_inrange(ww3, ww3 + 1, ww1)
- 3wincmd >
- call assert_equal(2, winwidth(2))
- call assert_equal(ww1 + 3, winwidth(1))
- call assert_equal(ww3 - 3, winwidth(3))
- wincmd =
- call assert_equal(2, winwidth(2))
- call assert_equal(ww1, winwidth(1))
- call assert_equal(ww3, winwidth(3))
-
- wincmd l
- set winfixwidth&
-
- wincmd =
- let [ww1, ww2, ww3] = [winwidth(1), winwidth(2), winwidth(3)]
- " Current window (2) should have the same width or 1 more
- " than the other windows.
- call assert_inrange(ww1, ww1 + 1, ww2)
- call assert_inrange(ww3, ww3 + 1, ww2)
-
- " when the current window width is less than the new 'winwidth', the current
- " window width should be increased.
- enew | only
- split
- 10vnew
- set winwidth=15
- call assert_equal(15, winwidth(0))
-
- %bw!
-endfunc
-
-func Test_equalalways_on_close()
- set equalalways
- vsplit
- windo split
- split
- wincmd J
- " now we have a frame top-left with two windows, a frame top-right with two
- " windows and a frame at the bottom, full-width.
- let height_1 = winheight(1)
- let height_2 = winheight(2)
- let height_3 = winheight(3)
- let height_4 = winheight(4)
- " closing the bottom window causes all windows to be resized.
- close
- call assert_notequal(height_1, winheight(1))
- call assert_notequal(height_2, winheight(2))
- call assert_notequal(height_3, winheight(3))
- call assert_notequal(height_4, winheight(4))
- call assert_equal(winheight(1), winheight(3))
- call assert_equal(winheight(2), winheight(4))
-
- 1wincmd w
- split
- 4wincmd w
- resize + 5
- " left column has three windows, equalized heights.
- " right column has two windows, top one a bit higher
- let height_1 = winheight(1)
- let height_2 = winheight(2)
- let height_4 = winheight(4)
- let height_5 = winheight(5)
- 3wincmd w
- " closing window in left column equalizes heights in left column but not in
- " the right column
- close
- call assert_notequal(height_1, winheight(1))
- call assert_notequal(height_2, winheight(2))
- call assert_equal(height_4, winheight(3))
- call assert_equal(height_5, winheight(4))
-
- only
- set equalalways&
-endfunc
-
-func Test_win_screenpos()
- call assert_equal(1, winnr('$'))
- split
- vsplit
- 10wincmd _
- 30wincmd |
- call assert_equal([1, 1], win_screenpos(1))
- call assert_equal([1, 32], win_screenpos(2))
- call assert_equal([12, 1], win_screenpos(3))
- call assert_equal([0, 0], win_screenpos(4))
- call assert_fails('let l = win_screenpos([])', 'E745:')
- only
-endfunc
-
-func Test_window_jump_tag()
- CheckFeature quickfix
-
- help
- /iccf
- call assert_match('^|iccf|', getline('.'))
- call assert_equal(2, winnr('$'))
- 2wincmd }
- call assert_equal(3, winnr('$'))
- call assert_match('^|iccf|', getline('.'))
- wincmd k
- call assert_match('\*iccf\*', getline('.'))
- call assert_equal(2, winheight(0))
-
- wincmd z
- set previewheight=4
- help
- /bugs
- wincmd }
- wincmd k
- call assert_match('\*bugs\*', getline('.'))
- call assert_equal(4, winheight(0))
- set previewheight&
-
- %bw!
-endfunc
-
-func Test_window_newtab()
- e Xa
-
- call assert_equal(1, tabpagenr('$'))
- call assert_equal("\nAlready only one window", execute('wincmd T'))
-
- split Xb
- split Xc
-
- wincmd T
- call assert_equal(2, tabpagenr('$'))
- call assert_equal(['Xb', 'Xa'], map(tabpagebuflist(1), 'bufname(v:val)'))
- call assert_equal(['Xc' ], map(2->tabpagebuflist(), 'bufname(v:val)'))
- call assert_equal(['Xc' ], map(tabpagebuflist(), 'bufname(v:val)'))
-
- %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)
- let str = ""
- for c in range(1,3)
- let str .= nr2char(screenchar(a:row, c))
- endfor
- return str
-endfunc
-
-func Test_window_contents()
- enew! | only | new
- call setline(1, range(1,256))
-
- exe "norm! \<C-W>t\<C-W>=1Gzt\<C-W>w\<C-W>+"
- redraw
- let s3 = GetScreenStr(1)
- wincmd p
- call assert_equal(1, line("w0"))
- call assert_equal('1 ', s3)
-
- exe "norm! \<C-W>t\<C-W>=50Gzt\<C-W>w\<C-W>+"
- redraw
- let s3 = GetScreenStr(1)
- wincmd p
- call assert_equal(50, line("w0"))
- call assert_equal('50 ', s3)
-
- exe "norm! \<C-W>t\<C-W>=59Gzt\<C-W>w\<C-W>+"
- redraw
- let s3 = GetScreenStr(1)
- wincmd p
- call assert_equal(59, line("w0"))
- call assert_equal('59 ', s3)
-
- %d
- call setline(1, ['one', 'two', 'three'])
- call assert_equal(1, line('w0'))
- call assert_equal(3, line('w$'))
-
- bwipeout!
- 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()
- call assert_equal(&columns, winwidth(0))
- " This was accessing freed memory (but with what events?)
- au BufEnter,BufLeave,WinEnter,WinLeave 0 vs xxx
- arg 0
- argadd
- call assert_fails("all", "E242:")
- au!
- bwipe xxx
- call assert_equal(&columns, winwidth(0))
-endfunc
-
-func Test_visual_cleared_after_window_split()
- new | only!
- let smd_save = &showmode
- set showmode
- let ls_save = &laststatus
- set laststatus=1
- call setline(1, ['a', 'b', 'c', 'd', ''])
- norm! G
- exe "norm! kkvk"
- redraw
- exe "norm! \<C-W>v"
- redraw
- " check if '-- VISUAL --' disappeared from command line
- let columns = range(1, &columns)
- let cmdlinechars = map(columns, 'nr2char(screenchar(&lines, v:val))')
- let cmdline = join(cmdlinechars, '')
- let cmdline_ltrim = substitute(cmdline, '^\s*', "", "")
- let mode_shown = substitute(cmdline_ltrim, '\s*$', "", "")
- call assert_equal('', mode_shown)
- let &showmode = smd_save
- let &laststatus = ls_save
- bwipe!
-endfunc
-
-func Test_winrestcmd()
- 2split
- 3vsplit
- let restcmd = winrestcmd()
- call assert_equal(2, winheight(0))
- call assert_equal(3, winwidth(0))
- wincmd =
- call assert_notequal(2, winheight(0))
- call assert_notequal(3, winwidth(0))
- exe restcmd
- call assert_equal(2, winheight(0))
- call assert_equal(3, winwidth(0))
- only
-
- wincmd v
- wincmd s
- wincmd v
- redraw
- let restcmd = winrestcmd()
- wincmd _
- wincmd |
- exe restcmd
- redraw
- call assert_equal(restcmd, winrestcmd())
-
- only
-endfunc
-
-func Fun_RenewFile()
- " Need to wait a bit for the timestamp to be older.
- let old_ftime = getftime("tmp.txt")
- while getftime("tmp.txt") == old_ftime
- sleep 100m
- silent execute '!echo "1" > tmp.txt'
- endwhile
- sp
- wincmd p
- edit! tmp.txt
-endfunc
-
-func Test_window_prevwin()
- " Can we make this work on MS-Windows?
- if !has('unix')
- return
- endif
-
- set hidden autoread
- call writefile(['2'], 'tmp.txt')
- new tmp.txt
- q
- call Fun_RenewFile()
- call assert_equal(2, winnr())
- wincmd p
- call assert_equal(1, winnr())
- wincmd p
- q
- call Fun_RenewFile()
- call assert_equal(2, winnr())
- wincmd p
- call assert_equal(1, winnr())
- wincmd p
- " reset
- q
- call delete('tmp.txt')
- set hidden&vim autoread&vim
- delfunc Fun_RenewFile
-endfunc
-
-func Test_relative_cursor_position_in_one_line_window()
- new
- only
- call setline(1, range(1, 10000))
- normal 50%
- let lnum = getcurpos()[1]
- split
- split
- " make third window take as many lines as possible, other windows will
- " become one line
- 3wincmd w
- for i in range(1, &lines - 6)
- wincmd +
- redraw!
- endfor
-
- " first and second window should show cursor line
- let wininfo = getwininfo()
- call assert_equal(lnum, wininfo[0].topline)
- call assert_equal(lnum, wininfo[1].topline)
-
- only!
- bwipe!
- call assert_fails('call winrestview(v:_null_dict)', 'E474:')
-endfunc
-
-func Test_relative_cursor_position_after_move_and_resize()
- let so_save = &so
- set so=0
- enew
- call setline(1, range(1, 10000))
- normal 50%
- split
- 1wincmd w
- " Move cursor to first line in window
- normal H
- redraw!
- " Reduce window height to two lines
- let height = winheight(0)
- while winheight(0) > 2
- wincmd -
- redraw!
- endwhile
- " move cursor to second/last line in window
- normal j
- " restore previous height
- while winheight(0) < height
- wincmd +
- redraw!
- endwhile
- " make window two lines again
- while winheight(0) > 2
- wincmd -
- redraw!
- endwhile
-
- " cursor should be at bottom line
- let info = getwininfo(win_getid())[0]
- call assert_equal(info.topline + 1, getcurpos()[1])
-
- only!
- bwipe!
- let &so = so_save
-endfunc
-
-func Test_relative_cursor_position_after_resize()
- let so_save = &so
- set so=0
- enew
- call setline(1, range(1, 10000))
- normal 50%
- split
- 1wincmd w
- let winid1 = win_getid()
- let info = getwininfo(winid1)[0]
- " Move cursor to second line in window
- exe "normal " . (info.topline + 1) . "G"
- redraw!
- let lnum = getcurpos()[1]
-
- " Make the window only two lines high, cursor should end up in top line
- 2wincmd w
- exe (info.height - 2) . "wincmd +"
- redraw!
- let info = getwininfo(winid1)[0]
- call assert_equal(lnum, info.topline)
-
- only!
- bwipe!
- let &so = so_save
-endfunc
-
-func Test_relative_cursor_second_line_after_resize()
- let so_save = &so
- set so=0
- enew
- call setline(1, range(1, 10000))
- normal 50%
- split
- 1wincmd w
- let winid1 = win_getid()
- let info = getwininfo(winid1)[0]
-
- " Make the window only two lines high
- 2wincmd _
-
- " Move cursor to second line in window
- normal H
- normal j
-
- " Make window size bigger, then back to 2 lines
- for i in range(1, 10)
- wincmd +
- redraw!
- endfor
- for i in range(1, 10)
- wincmd -
- redraw!
- endfor
-
- " cursor should end up in bottom line
- let info = getwininfo(winid1)[0]
- call assert_equal(info.topline + 1, getcurpos()[1])
-
- only!
- bwipe!
- 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
- call assert_equal(1, winnr('j'))
- call assert_equal(1, winnr('k'))
- call assert_equal(1, winnr('h'))
- call assert_equal(1, winnr('l'))
-
- " create a set of horizontally and vertically split windows
- leftabove new | wincmd p
- leftabove new | wincmd p
- rightbelow new | wincmd p
- rightbelow new | wincmd p
- leftabove vnew | wincmd p
- leftabove vnew | wincmd p
- rightbelow vnew | wincmd p
- rightbelow vnew | wincmd p
-
- call assert_equal(8, winnr('j'))
- call assert_equal(2, winnr('k'))
- call assert_equal(4, winnr('h'))
- call assert_equal(6, winnr('l'))
- call assert_equal(9, winnr('2j'))
- call assert_equal(1, winnr('2k'))
- call assert_equal(3, winnr('2h'))
- call assert_equal(7, winnr('2l'))
-
- " Error cases
- call assert_fails("echo winnr('0.2k')", 'E15:')
- call assert_equal(2, winnr('-2k'))
- call assert_fails("echo winnr('-2xj')", 'E15:')
- call assert_fails("echo winnr('j2j')", 'E15:')
- call assert_fails("echo winnr('ll')", 'E15:')
- call assert_fails("echo winnr('5')", 'E15:')
- call assert_equal(4, winnr('0h'))
- call assert_fails("let w = winnr([])", 'E730:')
- call assert_equal('unknown', win_gettype(-1))
- call assert_equal(-1, winheight(-1))
- call assert_equal(-1, winwidth(-1))
-
- tabnew
- call assert_equal(8, tabpagewinnr(1, 'j'))
- call assert_equal(2, 1->tabpagewinnr('k'))
- call assert_equal(4, tabpagewinnr(1, 'h'))
- call assert_equal(6, tabpagewinnr(1, 'l'))
-
- only | tabonly
-endfunc
-
-func Test_winrestview()
- split runtest.vim
- normal 50%
- let view = winsaveview()
- close
- split runtest.vim
- eval view->winrestview()
- call assert_equal(view, winsaveview())
-
- bwipe!
- call assert_fails('call winrestview(v:_null_dict)', 'E474:')
-endfunc
-
-func Test_win_splitmove()
- CheckFeature quickfix
-
- edit a
- leftabove split b
- leftabove vsplit c
- leftabove split d
- call assert_equal(0, win_splitmove(winnr(), winnr('l')))
- call assert_equal(bufname(winbufnr(1)), 'c')
- call assert_equal(bufname(winbufnr(2)), 'd')
- call assert_equal(bufname(winbufnr(3)), 'b')
- call assert_equal(bufname(winbufnr(4)), 'a')
- call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1}))
- call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1}))
- call assert_equal(bufname(winbufnr(1)), 'c')
- call assert_equal(bufname(winbufnr(2)), 'b')
- call assert_equal(bufname(winbufnr(3)), 'd')
- call assert_equal(bufname(winbufnr(4)), 'a')
- call assert_equal(0, win_splitmove(winnr(), winnr('k'), {'vertical': 1}))
- call assert_equal(bufname(winbufnr(1)), 'd')
- call assert_equal(bufname(winbufnr(2)), 'c')
- call assert_equal(bufname(winbufnr(3)), 'b')
- call assert_equal(bufname(winbufnr(4)), 'a')
- call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'rightbelow': v:true}))
- call assert_equal(bufname(winbufnr(1)), 'c')
- call assert_equal(bufname(winbufnr(2)), 'b')
- call assert_equal(bufname(winbufnr(3)), 'a')
- call assert_equal(bufname(winbufnr(4)), 'd')
- call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E474:')
- only | bd
-
- call assert_fails('call win_splitmove(winnr(), 123)', 'E957:')
- call assert_fails('call win_splitmove(123, winnr())', 'E957:')
- call assert_fails('call win_splitmove(winnr(), winnr())', 'E957:')
-
- tabnew
- call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:')
- tabclose
-endfunc
-
-func Test_floatwin_splitmove()
- vsplit
- let win2 = win_getid()
- let popup_winid = nvim_open_win(0, 0, {'relative': 'win',
- \ 'row': 3, 'col': 3, 'width': 12, 'height': 3})
- call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:')
- call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:')
-
- call nvim_win_close(popup_winid, 1)
- bwipe
-endfunc
-
-" Test for the :only command
-func Test_window_only()
- new
- set modified
- new
- call assert_fails('only', 'E445:')
- only!
- " Test for :only with a count
- let wid = win_getid()
- new
- new
- 3only
- call assert_equal(1, winnr('$'))
- call assert_equal(wid, win_getid())
- call assert_fails('close', 'E444:')
- call assert_fails('%close', 'E16:')
-endfunc
-
-" Test for errors with :wincmd
-func Test_wincmd_errors()
- call assert_fails('wincmd g', 'E474:')
- call assert_fails('wincmd ab', 'E474:')
-endfunc
-
-" Test for errors with :winpos
-func Test_winpos_errors()
- throw 'Skipped: Nvim does not have :winpos'
- if !has("gui_running") && !has('win32')
- call assert_fails('winpos', 'E188:')
- endif
- call assert_fails('winpos 10', 'E466:')
-endfunc
-
-" Test for +cmd in a :split command
-func Test_split_cmd()
- split +set\ readonly
- call assert_equal(1, &readonly)
- call assert_equal(2, winnr('$'))
- close
-endfunc
-
-" Create maximum number of horizontally or vertically split windows and then
-" run commands that create a new horizontally/vertically split window
-func Run_noroom_for_newwindow_test(dir_arg)
- let dir = (a:dir_arg == 'v') ? 'vert ' : ''
-
- " Open as many windows as possible
- for i in range(500)
- try
- exe dir . 'new'
- catch /E36:/
- break
- endtry
- endfor
-
- call writefile(['first', 'second', 'third'], 'Xfile1')
- call writefile([], 'Xfile2')
- call writefile([], 'Xfile3')
-
- " Argument list related commands
- args Xfile1 Xfile2 Xfile3
- next
- for cmd in ['sargument 2', 'snext', 'sprevious', 'sNext', 'srewind',
- \ 'sfirst', 'slast']
- call assert_fails(dir .. cmd, 'E36:')
- endfor
- %argdelete
-
- " Buffer related commands
- set modified
- hide enew
- for cmd in ['sbuffer Xfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind',
- \ 'sbfirst', 'sblast', 'sball', 'sbmodified', 'sunhide']
- call assert_fails(dir .. cmd, 'E36:')
- endfor
-
- " Window related commands
- for cmd in ['split', 'split Xfile2', 'new', 'new Xfile3', 'sview Xfile1',
- \ 'sfind runtest.vim']
- call assert_fails(dir .. cmd, 'E36:')
- endfor
-
- " Help
- call assert_fails(dir .. 'help', 'E36:')
- call assert_fails(dir .. 'helpgrep window', 'E36:')
-
- " Command-line window
- if a:dir_arg == 'h'
- " Cmd-line window is always a horizontally split window
- call assert_beeps('call feedkeys("q:\<CR>", "xt")')
- endif
-
- " Quickfix and location list window
- if has('quickfix')
- cexpr ''
- call assert_fails(dir .. 'copen', 'E36:')
- lexpr ''
- call assert_fails(dir .. 'lopen', 'E36:')
-
- " Preview window
- call assert_fails(dir .. 'pedit Xfile2', 'E36:')
- call setline(1, 'abc')
- call assert_fails(dir .. 'psearch abc', 'E36:')
- endif
-
- " Window commands (CTRL-W ^ and CTRL-W f)
- if a:dir_arg == 'h'
- call assert_fails('call feedkeys("\<C-W>^", "xt")', 'E36:')
- call setline(1, 'Xfile1')
- call assert_fails('call feedkeys("gg\<C-W>f", "xt")', 'E36:')
- endif
- enew!
-
- " Tag commands (:stag, :stselect and :stjump)
- call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
- \ "second\tXfile1\t2",
- \ "third\tXfile1\t3",],
- \ 'Xtags')
- set tags=Xtags
- call assert_fails(dir .. 'stag second', 'E36:')
- call assert_fails('call feedkeys(":" .. dir .. "stselect second\n1\n", "xt")', 'E36:')
- call assert_fails(dir .. 'stjump second', 'E36:')
- call assert_fails(dir .. 'ptag second', 'E36:')
- set tags&
- call delete('Xtags')
-
- " :isplit and :dsplit
- call setline(1, ['#define FOO 1', 'FOO'])
- normal 2G
- call assert_fails(dir .. 'isplit FOO', 'E36:')
- call assert_fails(dir .. 'dsplit FOO', 'E36:')
-
- " terminal
- if has('terminal')
- call assert_fails(dir .. 'terminal', 'E36:')
- endif
-
- %bwipe!
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
- only
-endfunc
-
-func Test_split_cmds_with_no_room()
- call Run_noroom_for_newwindow_test('h')
- call Run_noroom_for_newwindow_test('v')
-endfunc
-
-" Test for various wincmd failures
-func Test_wincmd_fails()
- only!
- call assert_beeps("normal \<C-W>w")
- call assert_beeps("normal \<C-W>p")
- call assert_beeps("normal \<C-W>gk")
- call assert_beeps("normal \<C-W>r")
- call assert_beeps("normal \<C-W>K")
- call assert_beeps("normal \<C-W>H")
- call assert_beeps("normal \<C-W>2gt")
-endfunc
-
-func Test_window_resize()
- " Vertical :resize (absolute, relative, min and max size).
- vsplit
- vert resize 8
- call assert_equal(8, winwidth(0))
- vert resize +2
- call assert_equal(10, winwidth(0))
- vert resize -2
- call assert_equal(8, winwidth(0))
- vert resize
- call assert_equal(&columns - 2, winwidth(0))
- vert resize 0
- call assert_equal(1, winwidth(0))
- vert resize 99999
- call assert_equal(&columns - 2, winwidth(0))
-
- %bwipe!
-
- " Horizontal :resize (with absolute, relative size, min and max size).
- split
- resize 8
- call assert_equal(8, winheight(0))
- resize +2
- call assert_equal(10, winheight(0))
- resize -2
- call assert_equal(8, winheight(0))
- resize
- call assert_equal(&lines - 4, winheight(0))
- resize 0
- call assert_equal(1, winheight(0))
- resize 99999
- call assert_equal(&lines - 4, winheight(0))
-
- " :resize with explicit window number.
- let other_winnr = winnr('j')
- exe other_winnr .. 'resize 10'
- call assert_equal(10, winheight(other_winnr))
- call assert_equal(&lines - 10 - 3, winheight(0))
- exe other_winnr .. 'resize +1'
- exe other_winnr .. 'resize +1'
- call assert_equal(12, winheight(other_winnr))
- call assert_equal(&lines - 10 - 3 -2, winheight(0))
- close
-
- vsplit
- wincmd l
- let other_winnr = winnr('h')
- call assert_notequal(winnr(), other_winnr)
- exe 'vert ' .. other_winnr .. 'resize -' .. &columns
- call assert_equal(0, winwidth(other_winnr))
-
- %bwipe!
-endfunc
-
-" Test for adjusting the window width when a window is closed with some
-" windows using 'winfixwidth'
-func Test_window_width_adjust()
- only
- " Three vertical windows. Windows 1 and 2 have 'winfixwidth' set and close
- " window 2.
- wincmd v
- vert resize 10
- set winfixwidth
- wincmd v
- set winfixwidth
- wincmd c
- call assert_inrange(10, 12, winwidth(1))
- " Three vertical windows. Windows 2 and 3 have 'winfixwidth' set and close
- " window 3.
- only
- set winfixwidth
- wincmd v
- vert resize 10
- set winfixwidth
- wincmd v
- set nowinfixwidth
- wincmd b
- wincmd c
- call assert_inrange(10, 12, winwidth(2))
-
- new | only
-endfunc
-
-" Test for jumping to a vertical/horizontal neighbor window based on the
-" current cursor position
-func Test_window_goto_neighbor()
- %bw!
-
- " Vertical window movement
-
- " create the following window layout:
- " +--+--+
- " |w1|w3|
- " +--+ |
- " |w2| |
- " +--+--+
- " |w4 |
- " +-----+
- new
- vsplit
- split
- " vertically jump from w4
- wincmd b
- call setline(1, repeat(' ', &columns))
- call cursor(1, 1)
- wincmd k
- call assert_equal(2, winnr())
- wincmd b
- call cursor(1, &columns)
- redraw!
- wincmd k
- call assert_equal(3, winnr())
- %bw!
-
- " create the following window layout:
- " +--+--+--+
- " |w1|w2|w3|
- " +--+--+--+
- " |w4 |
- " +--------+
- new
- vsplit
- vsplit
- wincmd b
- call setline(1, repeat(' ', &columns))
- call cursor(1, 1)
- wincmd k
- call assert_equal(1, winnr())
- wincmd b
- call cursor(1, &columns / 2)
- redraw!
- wincmd k
- call assert_equal(2, winnr())
- wincmd b
- call cursor(1, &columns)
- redraw!
- wincmd k
- call assert_equal(3, winnr())
- %bw!
-
- " Horizontal window movement
-
- " create the following window layout:
- " +--+--+--+
- " |w1|w2|w4|
- " +--+--+ |
- " |w3 | |
- " +-----+--+
- vsplit
- split
- vsplit
- 4wincmd l
- call setline(1, repeat([' '], &lines))
- call cursor(1, 1)
- redraw!
- wincmd h
- call assert_equal(2, winnr())
- 4wincmd l
- call cursor(&lines, 1)
- redraw!
- wincmd h
- call assert_equal(3, winnr())
- %bw!
-
- " create the following window layout:
- " +--+--+
- " |w1|w4|
- " +--+ +
- " |w2| |
- " +--+ +
- " |w3| |
- " +--+--+
- vsplit
- split
- split
- wincmd l
- call setline(1, repeat([' '], &lines))
- call cursor(1, 1)
- redraw!
- wincmd h
- call assert_equal(1, winnr())
- wincmd l
- call cursor(&lines / 2, 1)
- redraw!
- wincmd h
- call assert_equal(2, winnr())
- wincmd l
- call cursor(&lines, 1)
- redraw!
- wincmd h
- call assert_equal(3, winnr())
- %bw!
-endfunc
-
-" Test for an autocmd closing the destination window when jumping from one
-" window to another.
-func Test_close_dest_window()
- split
- edit Xfile
-
- " Test for BufLeave
- augroup T1
- au!
- au BufLeave Xfile $wincmd c
- augroup END
- wincmd b
- call assert_equal(1, winnr('$'))
- call assert_equal('Xfile', @%)
- augroup T1
- au!
- augroup END
-
- " Test for WinLeave
- new
- wincmd p
- augroup T1
- au!
- au WinLeave * 1wincmd c
- augroup END
- wincmd t
- call assert_equal(1, winnr('$'))
- call assert_equal('Xfile', @%)
- augroup T1
- au!
- augroup END
- augroup! T1
- %bw!
-endfunc
-
-func Test_win_move_separator()
- edit a
- leftabove vsplit b
- let w = winwidth(0)
- " check win_move_separator from left window on left window
- call assert_equal(1, winnr())
- for offset in range(5)
- call assert_true(win_move_separator(0, offset))
- call assert_equal(w + offset, winwidth(0))
- call assert_true(0->win_move_separator(-offset))
- call assert_equal(w, winwidth(0))
- endfor
- " check win_move_separator from right window on left window number
- wincmd l
- call assert_notequal(1, winnr())
- for offset in range(5)
- call assert_true(1->win_move_separator(offset))
- call assert_equal(w + offset, winwidth(1))
- call assert_true(win_move_separator(1, -offset))
- call assert_equal(w, winwidth(1))
- endfor
- " check win_move_separator from right window on left window ID
- let id = win_getid(1)
- for offset in range(5)
- call assert_true(win_move_separator(id, offset))
- call assert_equal(w + offset, winwidth(id))
- call assert_true(id->win_move_separator(-offset))
- call assert_equal(w, winwidth(id))
- endfor
- " check win_move_separator from right window on right window is no-op
- let w0 = winwidth(0)
- call assert_true(win_move_separator(0, 1))
- call assert_equal(w0, winwidth(0))
- call assert_true(win_move_separator(0, -1))
- call assert_equal(w0, winwidth(0))
-
- " check that win_move_separator doesn't error with offsets beyond moving
- " possibility
- call assert_true(win_move_separator(id, 5000))
- call assert_true(winwidth(id) > w)
- call assert_true(win_move_separator(id, -5000))
- call assert_true(winwidth(id) < w)
-
- " check that win_move_separator returns false for an invalid window
- wincmd =
- let w = winwidth(0)
- call assert_false(win_move_separator(-1, 1))
- call assert_equal(w, winwidth(0))
-
- " check that win_move_separator returns false for a floating window
- let id = nvim_open_win(
- \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3})
- let w = winwidth(id)
- call assert_false(win_move_separator(id, 1))
- call assert_equal(w, winwidth(id))
- call nvim_win_close(id, 1)
-
- " check that using another tabpage fails without crash
- let id = win_getid()
- tabnew
- call assert_fails('call win_move_separator(id, -1)', 'E1308:')
- tabclose
-
- %bwipe!
-endfunc
-
-func Test_win_move_statusline()
- edit a
- leftabove split b
- let h = winheight(0)
- " check win_move_statusline from top window on top window
- call assert_equal(1, winnr())
- for offset in range(5)
- call assert_true(win_move_statusline(0, offset))
- call assert_equal(h + offset, winheight(0))
- call assert_true(0->win_move_statusline(-offset))
- call assert_equal(h, winheight(0))
- endfor
- " check win_move_statusline from bottom window on top window number
- wincmd j
- call assert_notequal(1, winnr())
- for offset in range(5)
- call assert_true(1->win_move_statusline(offset))
- call assert_equal(h + offset, winheight(1))
- call assert_true(win_move_statusline(1, -offset))
- call assert_equal(h, winheight(1))
- endfor
- " check win_move_statusline from bottom window on bottom window
- let h0 = winheight(0)
- for offset in range(5)
- call assert_true(0->win_move_statusline(-offset))
- call assert_equal(h0 - offset, winheight(0))
- call assert_equal(1 + offset, &cmdheight)
- call assert_true(win_move_statusline(0, offset))
- call assert_equal(h0, winheight(0))
- call assert_equal(1, &cmdheight)
- endfor
- " supports cmdheight=0
- set cmdheight=0
- call assert_true(win_move_statusline(0, 1))
- call assert_equal(h0 + 1, winheight(0))
- call assert_equal(0, &cmdheight)
- set cmdheight&
- " check win_move_statusline from bottom window on top window ID
- let id = win_getid(1)
- for offset in range(5)
- call assert_true(win_move_statusline(id, offset))
- call assert_equal(h + offset, winheight(id))
- call assert_true(id->win_move_statusline(-offset))
- call assert_equal(h, winheight(id))
- endfor
-
- " check that win_move_statusline doesn't error with offsets beyond moving
- " possibility
- call assert_true(win_move_statusline(id, 5000))
- call assert_true(winheight(id) > h)
- call assert_true(win_move_statusline(id, -5000))
- call assert_true(winheight(id) < h)
-
- " check that win_move_statusline returns false for an invalid window
- wincmd =
- let h = winheight(0)
- call assert_false(win_move_statusline(-1, 1))
- call assert_equal(h, winheight(0))
-
- " check that win_move_statusline returns false for a floating window
- let id = nvim_open_win(
- \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3})
- let h = winheight(id)
- call assert_false(win_move_statusline(id, 1))
- call assert_equal(h, winheight(id))
- call nvim_win_close(id, 1)
-
- " check that using another tabpage fails without crash
- let id = win_getid()
- tabnew
- call assert_fails('call win_move_statusline(id, -1)', 'E1308:')
- tabclose
-
- %bwipe!
-endfunc
-
-func Test_win_equal_last_status()
- let save_lines = &lines
- set lines=20
- set splitbelow
- set laststatus=0
-
- split | split | quit
- call assert_equal(winheight(1), winheight(2))
-
- let &lines = save_lines
- set splitbelow&
- set laststatus&
-endfunc
-
-" Test "screen" and "cursor" values for 'splitkeep' with a sequence of
-" split operations for various options: with and without a winbar,
-" tabline, for each possible value of 'laststatus', 'scrolloff',
-" 'equalalways', and with the cursor at the top, middle and bottom.
-func Test_splitkeep_options()
- " disallow window resizing
- " let save_WS = &t_WS
- " set t_WS=
-
- let gui = has("gui_running")
- inoremap <expr> c "<cmd>copen<bar>wincmd k<CR>"
- for run in range(0, 20)
- let &splitkeep = run > 10 ? 'topline' : 'screen'
- let &scrolloff = (!(run % 4) ? 0 : run)
- let &laststatus = (run % 3)
- let &splitbelow = (run % 3)
- let &equalalways = (run % 2)
- " Nvim: both windows have a winbar after splitting
- " let wsb = (run % 2) && &splitbelow
- let wsb = 0
- let tl = (gui ? 0 : ((run % 5) ? 1 : 0))
- let pos = !(run % 3) ? 'H' : ((run % 2) ? 'M' : 'L')
- tabnew | tabonly! | redraw
- execute (run % 5) ? 'tabnew' : ''
- " execute (run % 2) ? 'nnoremenu 1.10 WinBar.Test :echo' : ''
- let &winbar = (run % 2) ? '%f' : ''
- call setline(1, range(1, 256))
- " No scroll for restore_snapshot
- norm G
- try
- copen | close | colder
- catch /E380/
- endtry
- call assert_equal(257 - winheight(0), line("w0"))
-
- " No scroll for firstwin horizontal split
- execute 'norm gg' . pos
- split | redraw | wincmd k
- call assert_equal(1, line("w0"))
- call assert_equal(&scroll, winheight(0) / 2)
- wincmd j
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
-
- " No scroll when resizing windows
- wincmd k | resize +2 | redraw
- call assert_equal(1, line("w0"))
- wincmd j
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
-
- " No scroll when dragging statusline
- call win_move_statusline(1, -3)
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
- wincmd k
- call assert_equal(1, line("w0"))
-
- " No scroll when changing shellsize
- set lines+=2
- call assert_equal(1, line("w0"))
- wincmd j
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
- set lines-=2
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
- wincmd k
- call assert_equal(1, line("w0"))
-
- " No scroll when equalizing windows
- wincmd =
- call assert_equal(1, line("w0"))
- wincmd j
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
- wincmd k
- call assert_equal(1, line("w0"))
-
- " No scroll in windows split multiple times
- vsplit | split | 4wincmd w
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
- 1wincmd w | quit | wincmd l | split
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
- wincmd j
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
-
- " No scroll in small window
- 2wincmd w | only | 5split | wincmd k
- call assert_equal(1, line("w0"))
- wincmd j
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
-
- " No scroll for vertical split
- quit | vsplit | wincmd l
- call assert_equal(1, line("w0"))
- wincmd h
- call assert_equal(1, line("w0"))
-
- " No scroll in windows split and quit multiple times
- quit | redraw | split | split | quit | redraw
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
-
- " No scroll for new buffer
- 1wincmd w | only | copen | wincmd k
- call assert_equal(1, line("w0"))
- only
- call assert_equal(1, line("w0"))
- above copen | wincmd j
- call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl, line("w0"))
-
- " No scroll when opening cmdwin, and no cursor move when closing cmdwin.
- only | norm ggL
- let curpos = getcurpos()
- norm q:
- call assert_equal(1, line("w0"))
- call assert_equal(curpos, getcurpos())
-
- " Scroll when cursor becomes invalid in insert mode
- norm Lic
- call assert_equal(curpos, getcurpos())
-
- " No scroll when topline not equal to 1
- only | execute "norm gg5\<C-e>" | split | wincmd k
- call assert_equal(6, line("w0"))
- wincmd j
- call assert_equal(&spk == 'topline' ? 6 : 5 + win_screenpos(0)[0] - tl - wsb, line("w0"))
- endfor
-
- tabnew | tabonly! | %bwipeout!
- iunmap c
- set scrolloff&
- set splitbelow&
- set laststatus&
- set equalalways&
- set splitkeep&
- " let &t_WS = save_WS
-endfunc
-
-function Test_splitkeep_cmdwin_cursor_position()
- set splitkeep=screen
- call setline(1, range(&lines))
-
- " No scroll when cursor is at near bottom of window and cusor position
- " recompution (done by line('w0') in this test) happens while in cmdwin.
- normal! G
- let firstline = line('w0')
- autocmd CmdwinEnter * ++once autocmd WinEnter * ++once call line('w0')
- execute "normal! q:\<C-w>q"
- redraw!
- call assert_equal(firstline, line('w0'))
-
- " User script can change cursor position successfully while in cmdwin and it
- " shouldn't be changed when closing cmdwin.
- execute "normal! Gq:\<Cmd>call win_execute(winnr('#')->win_getid(), 'call cursor(1, 1)')\<CR>\<C-w>q"
- call assert_equal(1, line('.'))
- call assert_equal(1, col('.'))
-
- execute "normal! Gq:\<Cmd>autocmd WinEnter * ++once call cursor(1, 1)\<CR>\<C-w>q"
- call assert_equal(1, line('.'))
- call assert_equal(1, col('.'))
-
- %bwipeout!
- set splitkeep&
-endfunction
-
-function Test_splitkeep_misc()
- set splitkeep=screen
- set splitbelow
-
- call setline(1, range(1, &lines))
- norm Gzz
- let top = line('w0')
- " No scroll when aucmd_win is opened
- call setbufvar(bufnr("test", 1) , '&buftype', 'nofile')
- call assert_equal(top, line('w0'))
- " No scroll when tab is changed/closed
- tab help | close
- call assert_equal(top, line('w0'))
- " No scroll when help is closed and buffer line count < window height
- norm ggdG
- call setline(1, range(1, &lines - 10))
- norm G
- let top = line('w0')
- help | quit
- call assert_equal(top, line('w0'))
- " No error when resizing window in autocmd and buffer length changed
- autocmd FileType qf exe "resize" line('$')
- cexpr getline(1, '$')
- copen
- wincmd p
- norm dd
- cexpr getline(1, '$')
-
- %bwipeout!
- set splitbelow&
- set splitkeep&
-endfunc
-
-function Test_splitkeep_callback()
- CheckScreendump
- let lines =<< trim END
- set splitkeep=screen
- call setline(1, range(&lines))
- function C1(a, b)
- split | wincmd p
- endfunction
- function C2(a, b)
- close | split
- endfunction
- nn j <cmd>call job_start([&sh, &shcf, "true"], { 'exit_cb': 'C1' })<CR>
- nn t <cmd>call popup_create(term_start([&sh, &shcf, "true"],
- \ { 'hidden': 1, 'exit_cb': 'C2' }), {})<CR>
- END
- call writefile(lines, 'XTestSplitkeepCallback', 'D')
- let buf = RunVimInTerminal('-S XTestSplitkeepCallback', #{rows: 8})
-
- call term_sendkeys(buf, "j")
- call VerifyScreenDump(buf, 'Test_splitkeep_callback_1', {})
-
- call term_sendkeys(buf, ":quit\<CR>Ht")
- call VerifyScreenDump(buf, 'Test_splitkeep_callback_2', {})
-
- call term_sendkeys(buf, ":set sb\<CR>:quit\<CR>Gj")
- call VerifyScreenDump(buf, 'Test_splitkeep_callback_3', {})
-
- call term_sendkeys(buf, ":quit\<CR>Gt")
- call VerifyScreenDump(buf, 'Test_splitkeep_callback_4', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-function Test_splitkeep_fold()
- CheckScreendump
-
- let lines =<< trim END
- set splitkeep=screen
- set foldmethod=marker
- set number
- let line = 1
- for n in range(1, &lines)
- call setline(line, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
- \ 'after fold'])
- let line += 8
- endfor
- END
- call writefile(lines, 'XTestSplitkeepFold', 'D')
- let buf = RunVimInTerminal('-S XTestSplitkeepFold', #{rows: 10})
-
- call term_sendkeys(buf, "L:wincmd s\<CR>")
- call VerifyScreenDump(buf, 'Test_splitkeep_fold_1', {})
-
- call term_sendkeys(buf, ":quit\<CR>")
- call VerifyScreenDump(buf, 'Test_splitkeep_fold_2', {})
-
- call term_sendkeys(buf, "H:below split\<CR>")
- call VerifyScreenDump(buf, 'Test_splitkeep_fold_3', {})
-
- call term_sendkeys(buf, ":wincmd k\<CR>:quit\<CR>")
- call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {})
-
- call StopVimInTerminal(buf)
-endfunction
-
-function Test_splitkeep_status()
- CheckScreendump
-
- let lines =<< trim END
- call setline(1, ['a', 'b', 'c'])
- set nomodified
- set splitkeep=screen
- let win = winnr()
- wincmd s
- wincmd j
- END
- call writefile(lines, 'XTestSplitkeepStatus', 'D')
- let buf = RunVimInTerminal('-S XTestSplitkeepStatus', #{rows: 10})
-
- call term_sendkeys(buf, ":call win_move_statusline(win, 1)\<CR>")
- call VerifyScreenDump(buf, 'Test_splitkeep_status_1', {})
-
- call StopVimInTerminal(buf)
-endfunction
-
-function Test_new_help_window_on_error()
- help change.txt
- execute "normal! /CTRL-@\<CR>"
- silent! execute "normal! \<C-W>]"
-
- let wincount = winnr('$')
- help 'mod'
-
- call assert_equal(wincount, winnr('$'))
- call assert_equal(expand("<cword>"), "'mod'")
-endfunction
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_window_id.vim b/src/nvim/testdir/test_window_id.vim
deleted file mode 100644
index 396a49b55f..0000000000
--- a/src/nvim/testdir/test_window_id.vim
+++ /dev/null
@@ -1,142 +0,0 @@
-" Test using the window ID.
-
-source check.vim
-
-func Test_win_getid()
- edit one
- let id1 = win_getid()
- let w:one = 'one'
- split two
- let id2 = win_getid()
- let bufnr2 = bufnr('%')
- let w:two = 'two'
- split three
- let id3 = win_getid()
- let w:three = 'three'
- tabnew
- edit four
- let id4 = win_getid()
- let w:four = 'four'
- split five
- let id5 = win_getid()
- let bufnr5 = bufnr('%')
- let w:five = 'five'
- tabnext
-
- wincmd w
- call assert_equal("two", expand("%"))
- call assert_equal(id2, win_getid())
- let nr2 = winnr()
- wincmd w
- call assert_equal("one", expand("%"))
- call assert_equal(id1, win_getid())
- let nr1 = winnr()
- wincmd w
- call assert_equal("three", expand("%"))
- call assert_equal(id3, win_getid())
- let nr3 = winnr()
- call assert_equal('one', getwinvar(id1, 'one'))
- call assert_equal('two', getwinvar(id2, 'two'))
- call assert_equal('three', getwinvar(id3, 'three'))
- tabnext
- call assert_equal("five", expand("%"))
- call assert_equal(id5, win_getid())
- let nr5 = winnr()
- wincmd w
- call assert_equal("four", expand("%"))
- call assert_equal(id4, win_getid())
- let nr4 = winnr()
- call assert_equal('four', getwinvar(id4, 'four'))
- call assert_equal('five', getwinvar(id5, 'five'))
- call settabwinvar(1, id2, 'two', '2')
- call setwinvar(id4, 'four', '4')
- tabnext
- call assert_equal('4', gettabwinvar(2, id4, 'four'))
- call assert_equal('five', gettabwinvar(2, id5, 'five'))
- call assert_equal('2', getwinvar(id2, 'two'))
-
- exe nr1 . "wincmd w"
- call assert_equal(id1, win_getid())
- exe nr2 . "wincmd w"
- call assert_equal(id2, win_getid())
- exe nr3 . "wincmd w"
- call assert_equal(id3, win_getid())
- tabnext
- exe nr4 . "wincmd w"
- call assert_equal(id4, win_getid())
- exe nr5 . "wincmd w"
- call assert_equal(id5, win_getid())
-
- call win_gotoid(id2)
- call assert_equal("two", expand("%"))
- eval id4->win_gotoid()
- call assert_equal("four", expand("%"))
- call win_gotoid(id1)
- call assert_equal("one", expand("%"))
- call win_gotoid(id5)
- call assert_equal("five", expand("%"))
-
- call assert_equal(0, win_id2win(9999))
- call assert_equal(nr5, id5->win_id2win())
- call assert_equal(0, win_id2win(id1))
- tabnext
- call assert_equal(nr1, win_id2win(id1))
-
- call assert_equal([0, 0], win_id2tabwin(9999))
- call assert_equal([1, nr2], id2->win_id2tabwin())
- call assert_equal([2, nr4], win_id2tabwin(id4))
-
- call assert_equal([], win_findbuf(9999))
- call assert_equal([id2], bufnr2->win_findbuf())
- call win_gotoid(id5)
- split
- call assert_equal(sort([id5, win_getid()]), sort(win_findbuf(bufnr5)))
-
- call assert_fails('let w = win_getid([])', 'E745:')
- call assert_equal(0, win_getid(-1))
- call assert_equal(-1, win_getid(1, -1))
-
- only!
-endfunc
-
-func Test_win_getid_curtab()
- CheckFeature quickfix
-
- tabedit X
- tabfirst
- copen
- only
- call assert_equal(win_getid(1), 1->win_getid( 1))
- tabclose!
-endfunc
-
-func Test_winlayout()
- let w1 = win_getid()
- call assert_equal(['leaf', w1], winlayout())
-
- split
- let w2 = win_getid()
- call assert_equal(['col', [['leaf', w2], ['leaf', w1]]], winlayout())
-
- split
- let w3 = win_getid()
- call assert_equal(['col', [['leaf', w3], ['leaf', w2], ['leaf', w1]]], winlayout())
-
- 2wincmd w
- vsplit
- let w4 = win_getid()
- call assert_equal(['col', [['leaf', w3], ['row', [['leaf', w4], ['leaf', w2]]], ['leaf', w1]]], winlayout())
-
- only!
-
- let w1 = win_getid()
- call assert_equal(['leaf', w1], winlayout(1))
- tabnew
- let w2 = win_getid()
- call assert_equal(['leaf', w2], 2->winlayout())
- tabclose
-
- call assert_equal([], winlayout(-1))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim
deleted file mode 100644
index 3c2db01444..0000000000
--- a/src/nvim/testdir/test_windows_home.vim
+++ /dev/null
@@ -1,120 +0,0 @@
-" Test for $HOME on Windows.
-
-source check.vim
-CheckMSWindows
-
-let s:env = {}
-
-func s:restore_env()
- for i in keys(s:env)
- exe 'let ' . i . '=s:env["' . i . '"]'
- endfor
-endfunc
-
-func s:save_env(...)
- for i in a:000
- exe 'let s:env["' . i . '"]=' . i
- endfor
-endfunc
-
-func s:unlet_env(...)
- for i in a:000
- exe 'let ' . i . '=""'
- endfor
-endfunc
-
-func CheckHomeIsMissingFromSubprocessEnvironment()
- silent! let out = system('set')
- let env = filter(split(out, "\n"), 'v:val=~"^HOME="')
- call assert_equal(0, len(env))
-endfunc
-
-func CheckHomeIsInSubprocessEnvironment(exp)
- silent! let out = system('set')
- let env = filter(split(out, "\n"), 'v:val=~"^HOME="')
- let home = len(env) == 0 ? "" : substitute(env[0], '[^=]\+=', '', '')
- call assert_equal(a:exp, home)
-endfunc
-
-func CheckHome(exp, ...)
- call assert_equal(a:exp, $HOME)
- call assert_equal(a:exp, expand('~', ':p'))
- if !a:0
- call CheckHomeIsMissingFromSubprocessEnvironment()
- else
- call CheckHomeIsInSubprocessEnvironment(a:1)
- endif
-endfunc
-
-func Test_WindowsHome()
- command! -nargs=* SaveEnv call <SID>save_env(<f-args>)
- command! -nargs=* RestoreEnv call <SID>restore_env()
- command! -nargs=* UnletEnv call <SID>unlet_env(<f-args>)
- set noshellslash
-
- let save_home = $HOME
- SaveEnv $USERPROFILE $HOMEDRIVE $HOMEPATH
- try
- " Normal behavior: use $HOMEDRIVE and $HOMEPATH, ignore $USERPROFILE
- let $USERPROFILE = 'unused'
- let $HOMEDRIVE = 'C:'
- let $HOMEPATH = '\foobar'
- let $HOME = '' " Force recomputing "homedir"
- call CheckHome('C:\foobar')
-
- " Same, but with $HOMEPATH not set
- UnletEnv $HOMEPATH
- let $HOME = '' " Force recomputing "homedir"
- call CheckHome('C:\')
-
- " Use $USERPROFILE if $HOMEPATH and $HOMEDRIVE are empty
- UnletEnv $HOMEDRIVE $HOMEPATH
- let $USERPROFILE = 'C:\foo'
- let $HOME = '' " Force recomputing "homedir"
- call CheckHome('C:\foo')
-
- " If $HOME is set the others don't matter
- let $HOME = 'C:\bar'
- let $USERPROFILE = 'unused'
- let $HOMEDRIVE = 'unused'
- let $HOMEPATH = 'unused'
- call CheckHome('C:\bar', 'C:\bar')
-
- " If $HOME contains %USERPROFILE% it is expanded
- let $USERPROFILE = 'C:\foo'
- let $HOME = '%USERPROFILE%\bar'
- let $HOMEDRIVE = 'unused'
- let $HOMEPATH = 'unused'
- call CheckHome('C:\foo\bar', '%USERPROFILE%\bar')
-
- " Invalid $HOME is kept
- let $USERPROFILE = 'C:\foo'
- let $HOME = '%USERPROFILE'
- let $HOMEDRIVE = 'unused'
- let $HOMEPATH = 'unused'
- call CheckHome('%USERPROFILE', '%USERPROFILE')
-
- " %USERPROFILE% not at start of $HOME is not expanded
- let $USERPROFILE = 'unused'
- let $HOME = 'C:\%USERPROFILE%'
- let $HOMEDRIVE = 'unused'
- let $HOMEPATH = 'unused'
- call CheckHome('C:\%USERPROFILE%', 'C:\%USERPROFILE%')
-
- if has('channel')
- RestoreEnv
- let $HOME = save_home
- let env = ''
- let job = job_start('cmd /c set', {'out_cb': {ch,x->[env,execute('let env=x')]}})
- sleep 1
- let env = filter(split(env, "\n"), 'v:val=="HOME"')
- let home = len(env) == 0 ? "" : env[0]
- call assert_equal('', home)
- endif
- finally
- RestoreEnv
- delcommand SaveEnv
- delcommand RestoreEnv
- delcommand UnletEnv
- endtry
-endfunc
diff --git a/src/nvim/testdir/test_wnext.vim b/src/nvim/testdir/test_wnext.vim
deleted file mode 100644
index 3df61ceb78..0000000000
--- a/src/nvim/testdir/test_wnext.vim
+++ /dev/null
@@ -1,101 +0,0 @@
-" Test :wnext :wNext and :wprevious
-
-func Test_wnext()
- args X1 X2
-
- call setline(1, '1')
- wnext
- call assert_equal(['1'], readfile('X1'))
- call assert_equal('X2', bufname('%'))
-
- call setline(1, '2')
- call assert_fails('wnext', 'E165:')
- call assert_equal(['2'], readfile('X2'))
- call assert_equal('X2', bufname('%'))
-
- " Test :wnext with a single file.
- args X1
- call assert_equal('X1', bufname('%'))
- call assert_fails('wnext', 'E163:')
-
- " Test :wnext with a count.
- args X1 X2 X3
- call assert_equal('X1', bufname('%'))
- 2wnext
- call assert_equal('X3', bufname('%'))
-
- " Test :wnext {file}.
- args X1 X2 X3
- wnext X4
- call assert_equal(['1'], readfile('X4'))
- call assert_equal('X2', bufname('%'))
- call assert_fails('wnext X4', 'E13:')
- call assert_equal(['1'], readfile('X4'))
- wnext! X4
- call assert_equal(['2'], readfile('X4'))
- call assert_equal('X3', bufname('%'))
-
- args X1 X2
- " Commented out as, E13 occurs on Windows instead of E17
- "call assert_fails('wnext .', 'E17:')
- call assert_fails('wnext! .', 'E502:')
-
- %bwipe!
- call delete('X1')
- call delete('X2')
- call delete('X3')
- call delete('X4')
-endfunc
-
-func Test_wprevious()
- args X1 X2
-
- next
- call assert_equal('X2', bufname('%'))
- call setline(1, '2')
- wprevious
- call assert_equal(['2'], readfile('X2'))
- call assert_equal('X1', bufname('%'))
-
- call setline(1, '1')
- call assert_fails('wprevious', 'E164:')
- call assert_fails('wNext', 'E164:')
-
- " Test :wprevious with a single file.
- args X1
- call assert_fails('wprevious', 'E163:')
- call assert_fails('wNext', 'E163:')
-
- " Test :wprevious with a count.
- args X1 X2 X3
- 2next
- call setline(1, '3')
- call assert_equal('X3', bufname('%'))
- 2wprevious
- call assert_equal('X1', bufname('%'))
- call assert_equal(['3'], readfile('X3'))
-
- " Test :wprevious {file}
- args X1 X2 X3
- 2next
- call assert_equal('X3', bufname('%'))
- wprevious X4
- call assert_equal(['3'], readfile('X4'))
- call assert_equal('X2', bufname('%'))
- call assert_fails('wprevious X4', 'E13:')
- call assert_equal(['3'], readfile('X4'))
- wprevious! X4
- call assert_equal(['2'], readfile('X4'))
- call assert_equal('X1', bufname('%'))
-
- args X1 X2
- " Commented out as, E13 occurs on Windows instead of E17
- "call assert_fails('wprevious .', 'E17:')
- call assert_fails('wprevious! .', 'E502:')
-
- %bwipe!
- call delete('X1')
- call delete('X2')
- call delete('X3')
- call delete('X4')
-endfunc
diff --git a/src/nvim/testdir/test_wordcount.vim b/src/nvim/testdir/test_wordcount.vim
deleted file mode 100644
index 6a3d4109a8..0000000000
--- a/src/nvim/testdir/test_wordcount.vim
+++ /dev/null
@@ -1,104 +0,0 @@
-" Test for wordcount() function
-
-func Test_wordcount()
- let save_enc = &enc
- set encoding=utf-8
- set selection=inclusive fileformat=unix fileformats=unix
-
- new
-
- " Test 1: empty window
- call assert_equal({'chars': 0, 'cursor_chars': 0, 'words': 0, 'cursor_words': 0,
- \ 'bytes': 0, 'cursor_bytes': 0}, wordcount())
-
- " Test 2: some words, cursor at start
- call append(1, 'one two three')
- call cursor([1, 1, 0])
- call assert_equal({'chars': 15, 'cursor_chars': 1, 'words': 3, 'cursor_words': 0,
- \ 'bytes': 15, 'cursor_bytes': 1}, wordcount())
-
- " Test 3: some words, cursor at end
- %d _
- call append(1, 'one two three')
- call cursor([2, 99, 0])
- call assert_equal({'chars': 15, 'cursor_chars': 14, 'words': 3, 'cursor_words': 3,
- \ 'bytes': 15, 'cursor_bytes': 14}, wordcount())
-
- " Test 4: some words, cursor at end, ve=all
- set ve=all
- %d _
- call append(1, 'one two three')
- call cursor([2, 99, 0])
- call assert_equal({'chars': 15, 'cursor_chars': 15, 'words': 3, 'cursor_words': 3,
- \ 'bytes': 15, 'cursor_bytes': 15}, wordcount())
- set ve=
-
- " Test 5: several lines with words
- %d _
- call append(1, ['one two three', 'one two three', 'one two three'])
- call cursor([4, 99, 0])
- call assert_equal({'chars': 43, 'cursor_chars': 42, 'words': 9, 'cursor_words': 9,
- \ 'bytes': 43, 'cursor_bytes': 42}, wordcount())
-
- " Test 6: one line with BOM set
- %d _
- call append(1, 'one two three')
- set bomb
- w! Xtest
- call cursor([2, 99, 0])
- call assert_equal({'chars': 15, 'cursor_chars': 14, 'words': 3, 'cursor_words': 3,
- \ 'bytes': 18, 'cursor_bytes': 14}, wordcount())
- set nobomb
- w!
- call delete('Xtest')
-
- " Test 7: one line with multibyte words
- %d _
- call append(1, ['Äne M¤ne Müh'])
- call cursor([2, 99, 0])
- call assert_equal({'chars': 14, 'cursor_chars': 13, 'words': 3, 'cursor_words': 3,
- \ 'bytes': 17, 'cursor_bytes': 16}, wordcount())
-
- " Test 8: several lines with multibyte words
- %d _
- call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
- call cursor([3, 99, 0])
- call assert_equal({'chars': 32, 'cursor_chars': 31, 'words': 7, 'cursor_words': 7,
- \ 'bytes': 36, 'cursor_bytes': 35}, wordcount())
-
- " Visual map to capture wordcount() in visual mode
- vnoremap <expr> <F2> execute("let g:visual_stat = wordcount()")
-
- " Test 9: visual mode, complete buffer
- let g:visual_stat = {}
- %d _
- call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
- " start visual mode and select the complete buffer
- 0
- exe "normal V2j\<F2>y"
- call assert_equal({'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 32,
- \ 'visual_words': 7, 'visual_bytes': 36}, g:visual_stat)
-
- " Test 10: visual mode (empty)
- %d _
- call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
- " start visual mode and select the complete buffer
- 0
- exe "normal v$\<F2>y"
- call assert_equal({'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 1,
- \ 'visual_words': 0, 'visual_bytes': 1}, g:visual_stat)
-
- " Test 11: visual mode, single line
- %d _
- call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
- " start visual mode and select the complete buffer
- 2
- exe "normal 0v$\<F2>y"
- call assert_equal({'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 13,
- \ 'visual_words': 3, 'visual_bytes': 16}, g:visual_stat)
-
- set selection& fileformat& fileformats&
- let &enc = save_enc
- enew!
- close
-endfunc
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
deleted file mode 100644
index 6019cee193..0000000000
--- a/src/nvim/testdir/test_writefile.vim
+++ /dev/null
@@ -1,963 +0,0 @@
-" Tests for the writefile() function and some :write commands.
-
-source check.vim
-source term_util.vim
-
-func Test_writefile()
- let f = tempname()
- call writefile(["over","written"], f, "b")
- call writefile(["hello","world"], f, "b")
- call writefile(["!", "good"], f, "a")
- call writefile(["morning"], f, "ab")
- call writefile(["", "vimmers"], f, "ab")
- let l = readfile(f)
- call assert_equal("hello", l[0])
- call assert_equal("world!", l[1])
- call assert_equal("good", l[2])
- call assert_equal("morning", l[3])
- call assert_equal("vimmers", l[4])
- call delete(f)
-
- call assert_fails('call writefile("text", "Xfile")', 'E475: Invalid argument: writefile() first argument must be a List or a Blob')
-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"))
- call delete("Xfile")
-
- call assert_fails('call writefile(["test", [], [], [], "tset"], "Xfile")', 'E745:')
- call assert_false(filereadable("Xfile"))
- call delete("Xfile")
-
- call assert_fails('call writefile([], "Xfile", [])', 'E730:')
- call assert_false(filereadable("Xfile"))
- call delete("Xfile")
-
- call assert_fails('call writefile([], [])', 'E730:')
-endfunc
-
-func Test_writefile_fails_conversion()
- if !has('iconv') || has('sun')
- return
- endif
- " Without a backup file the write won't happen if there is a conversion
- " error.
- set nobackup nowritebackup backupdir=. backupskip=
- new
- let contents = ["line one", "line two"]
- call writefile(contents, 'Xfile')
- edit Xfile
- call setline(1, ["first line", "cannot convert \u010b", "third line"])
- call assert_fails('write ++enc=cp932', 'E513:')
- call assert_equal(contents, readfile('Xfile'))
-
- " With 'backupcopy' set, if there is a conversion error, the backup file is
- " still created.
- set backupcopy=yes writebackup& backup&
- call delete('Xfile' .. &backupext)
- call assert_fails('write ++enc=cp932', 'E513:')
- call assert_equal(contents, readfile('Xfile'))
- call assert_equal(contents, readfile('Xfile' .. &backupext))
- set backupcopy&
- %bw!
-
- " Conversion error during write
- new
- call setline(1, ["\U10000000"])
- let output = execute('write! ++enc=utf-16 Xfile')
- call assert_match('CONVERSION ERROR', output)
- let output = execute('write! ++enc=ucs-2 Xfile')
- call assert_match('CONVERSION ERROR', output)
- call delete('Xfilz~')
- call delete('Xfily~')
- %bw!
-
- call delete('Xfile')
- call delete('Xfile' .. &backupext)
- bwipe!
- set backup& writebackup& backupdir&vim backupskip&vim
-endfunc
-
-func Test_writefile_fails_conversion2()
- if !has('iconv') || has('sun')
- return
- endif
- " With a backup file the write happens even if there is a conversion error,
- " but then the backup file must remain
- set nobackup writebackup backupdir=. backupskip=
- let contents = ["line one", "line two"]
- call writefile(contents, 'Xfile_conversion_err')
- edit Xfile_conversion_err
- call setline(1, ["first line", "cannot convert \u010b", "third line"])
- set fileencoding=latin1
- let output = execute('write')
- call assert_match('CONVERSION ERROR', output)
- call assert_equal(contents, readfile('Xfile_conversion_err~'))
-
- call delete('Xfile_conversion_err')
- call delete('Xfile_conversion_err~')
- bwipe!
- set backup& writebackup& backupdir&vim backupskip&vim
-endfunc
-
-func SetFlag(timer)
- let g:flag = 1
-endfunc
-
-func Test_write_quit_split()
- " Prevent exiting by splitting window on file write.
- augroup testgroup
- autocmd BufWritePre * split
- augroup END
- e! Xfile
- call setline(1, 'nothing')
- wq
-
- if has('timers')
- " timer will not run if "exiting" is still set
- let g:flag = 0
- call timer_start(1, 'SetFlag')
- sleep 50m
- call assert_equal(1, g:flag)
- unlet g:flag
- endif
- au! testgroup
- bwipe Xfile
- call delete('Xfile')
-endfunc
-
-func Test_nowrite_quit_split()
- " Prevent exiting by opening a help window.
- e! Xfile
- help
- wincmd w
- exe winnr() . 'q'
-
- if has('timers')
- " timer will not run if "exiting" is still set
- let g:flag = 0
- call timer_start(1, 'SetFlag')
- sleep 50m
- call assert_equal(1, g:flag)
- unlet g:flag
- endif
- bwipe Xfile
-endfunc
-
-func Test_writefile_sync_arg()
- " This doesn't check if fsync() works, only that the argument is accepted.
- call writefile(['one'], 'Xtest', 's')
- call writefile(['two'], 'Xtest', 'S')
- call delete('Xtest')
-endfunc
-
-func Test_writefile_sync_dev_stdout()
- CheckUnix
- if filewritable('/dev/stdout')
- " Just check that this doesn't cause an error.
- call writefile(['one'], '/dev/stdout', 's')
- else
- throw 'Skipped: /dev/stdout is not writable'
- endif
-endfunc
-
-func Test_writefile_autowrite()
- set autowrite
- new
- next Xa Xb Xc
- call setline(1, 'aaa')
- next
- call assert_equal(['aaa'], readfile('Xa'))
- call setline(1, 'bbb')
- call assert_fails('edit XX')
- call assert_false(filereadable('Xb'))
-
- set autowriteall
- edit XX
- call assert_equal(['bbb'], readfile('Xb'))
-
- bwipe!
- call delete('Xa')
- call delete('Xb')
- set noautowrite
-endfunc
-
-func Test_writefile_autowrite_nowrite()
- set autowrite
- new
- next Xa Xb Xc
- set buftype=nowrite
- call setline(1, 'aaa')
- let buf = bufnr('%')
- " buffer contents silently lost
- edit XX
- call assert_false(filereadable('Xa'))
- rewind
- call assert_equal('', getline(1))
-
- bwipe!
- set noautowrite
-endfunc
-
-" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
-" command.
-func Test_write_pipe_to_cmd()
- CheckUnix
- new
- call setline(1, ['L1', 'L2', 'L3', 'L4'])
- 2,3w !cat > Xfile
- call assert_equal(['L2', 'L3'], readfile('Xfile'))
- close!
- call delete('Xfile')
-endfunc
-
-" Test for :saveas
-func Test_saveas()
- call assert_fails('saveas', 'E471:')
- call writefile(['L1'], 'Xfile')
- new Xfile
- new
- call setline(1, ['L1'])
- call assert_fails('saveas Xfile', 'E139:')
- close!
- enew | only
- call delete('Xfile')
-
- " :saveas should detect and set the file type.
- syntax on
- saveas! Xsaveas.pl
- call assert_equal('perl', &filetype)
- syntax off
- %bw!
- call delete('Xsaveas.pl')
-
- " :saveas fails for "nofile" buffer
- set buftype=nofile
- call assert_fails('saveas Xsafile', 'E676: No matching autocommands for buftype=nofile buffer')
-
- bwipe!
-endfunc
-
-func Test_write_errors()
- " Test for writing partial buffer
- call writefile(['L1', 'L2', 'L3'], 'Xfile')
- new Xfile
- call assert_fails('1,2write', 'E140:')
- close!
-
- call assert_fails('w > Xtest', 'E494:')
-
- " Try to overwrite a directory
- if has('unix')
- call mkdir('Xdir1')
- call assert_fails('write Xdir1', 'E17:')
- call delete('Xdir1', 'd')
- endif
-
- " Test for :wall for a buffer with no name
- enew | only
- call setline(1, ['L1'])
- call assert_fails('wall', 'E141:')
- enew!
-
- " Test for writing a 'readonly' file
- new Xfile
- set readonly
- call assert_fails('write', 'E45:')
- close
-
- " Test for writing to a read-only file
- new Xfile
- call setfperm('Xfile', 'r--r--r--')
- call assert_fails('write', 'E505:')
- call setfperm('Xfile', 'rw-rw-rw-')
- close
-
- call delete('Xfile')
-
- " Nvim treats NULL list/blob more like empty list/blob
- " call writefile(v:_null_list, 'Xfile')
- " call assert_false(filereadable('Xfile'))
- " call writefile(v:_null_blob, 'Xfile')
- " call assert_false(filereadable('Xfile'))
- call assert_fails('call writefile([], "")', 'E482:')
-
- " very long file name
- let long_fname = repeat('n', 5000)
- call assert_fails('exe "w " .. long_fname', 'E75:')
- call assert_fails('call writefile([], long_fname)', 'E482:')
-
- " Test for writing to a block device on Unix-like systems
- if has('unix') && getfperm('/dev/loop0') != ''
- \ && getftype('/dev/loop0') == 'bdev' && !IsRoot()
- new
- edit /dev/loop0
- call assert_fails('write', 'E503: ')
- call assert_fails('write!', 'E503: ')
- close!
- endif
-endfunc
-
-" Test for writing to a file which is modified after Vim read it
-func Test_write_file_mtime()
- CheckEnglish
- CheckRunVimInTerminal
-
- " First read the file into a buffer
- call writefile(["Line1", "Line2"], 'Xfile')
- let old_ftime = getftime('Xfile')
- let buf = RunVimInTerminal('Xfile', #{rows : 10})
- call term_wait(buf)
- call term_sendkeys(buf, ":set noswapfile\<CR>")
- call term_wait(buf)
-
- " Modify the file directly. Make sure the file modification time is
- " different. Note that on Linux/Unix, the file is considered modified
- " outside, only if the difference is 2 seconds or more
- sleep 1
- call writefile(["Line3", "Line4"], 'Xfile')
- let new_ftime = getftime('Xfile')
- while new_ftime - old_ftime < 2
- sleep 100m
- call writefile(["Line3", "Line4"], 'Xfile')
- let new_ftime = getftime('Xfile')
- endwhile
-
- " Try to overwrite the file and check for the prompt
- call term_sendkeys(buf, ":w\<CR>")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal("WARNING: The file has been changed since reading it!!!", term_getline(buf, 9))})
- call assert_equal("Do you really want to write to it (y/n)?",
- \ term_getline(buf, 10))
- call term_sendkeys(buf, "n\<CR>")
- call term_wait(buf)
- call assert_equal(new_ftime, getftime('Xfile'))
- call term_sendkeys(buf, ":w\<CR>")
- call term_wait(buf)
- call term_sendkeys(buf, "y\<CR>")
- call term_wait(buf)
- call WaitForAssert({-> assert_equal('Line2', readfile('Xfile')[1])})
-
- " clean up
- call StopVimInTerminal(buf)
- call delete('Xfile')
-endfunc
-
-" Test for an autocmd unloading a buffer during a write command
-func Test_write_autocmd_unloadbuf_lockmark()
- augroup WriteTest
- autocmd BufWritePre Xfile enew | write
- augroup END
- e Xfile
- call assert_fails('lockmarks write', ['E32', 'E203:'])
- augroup WriteTest
- au!
- augroup END
- augroup! WriteTest
-endfunc
-
-" Test for writing a buffer with 'acwrite' but without autocmds
-func Test_write_acwrite_error()
- new Xfile
- call setline(1, ['line1', 'line2', 'line3'])
- set buftype=acwrite
- call assert_fails('write', 'E676:')
- call assert_fails('1,2write!', 'E676:')
- call assert_fails('w >>', 'E676:')
- close!
-endfunc
-
-" Test for adding and removing lines from an autocmd when writing a buffer
-func Test_write_autocmd_add_remove_lines()
- new Xfile
- call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
-
- " Autocmd deleting lines from the file when writing a partial file
- augroup WriteTest2
- au!
- autocmd FileWritePre Xfile 1,2d
- augroup END
- call assert_fails('2,3w!', 'E204:')
-
- " Autocmd adding lines to a file when writing a partial file
- augroup WriteTest2
- au!
- autocmd FileWritePre Xfile call append(0, ['xxx', 'yyy'])
- augroup END
- %d
- call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
- 1,2w!
- call assert_equal(['xxx', 'yyy', 'aaa', 'bbb'], readfile('Xfile'))
-
- " Autocmd deleting lines from the file when writing the whole file
- augroup WriteTest2
- au!
- autocmd BufWritePre Xfile 1,2d
- augroup END
- %d
- call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
- w
- call assert_equal(['ccc', 'ddd'], readfile('Xfile'))
-
- augroup WriteTest2
- au!
- augroup END
- augroup! WriteTest2
-
- close!
- call delete('Xfile')
-endfunc
-
-" Test for writing to a readonly file
-func Test_write_readonly()
- call writefile([], 'Xfile')
- call setfperm('Xfile', "r--------")
- edit Xfile
- set noreadonly backupskip=
- call assert_fails('write', 'E505:')
- let save_cpo = &cpo
- set cpo+=W
- call assert_fails('write!', 'E504:')
- let &cpo = save_cpo
- call setline(1, ['line1'])
- write!
- call assert_equal(['line1'], readfile('Xfile'))
-
- " Auto-saving a readonly file should fail with 'autowriteall'
- %bw!
- e Xfile
- set noreadonly autowriteall
- call setline(1, ['aaaa'])
- call assert_fails('n', 'E505:')
- set cpo+=W
- call assert_fails('n', 'E504:')
- set cpo-=W
- set autowriteall&
-
- set backupskip&
- call delete('Xfile')
- %bw!
-endfunc
-
-" Test for 'patchmode'
-func Test_patchmode()
- call writefile(['one'], 'Xfile')
- set patchmode=.orig nobackup backupskip= writebackup
- new Xfile
- call setline(1, 'two')
- " first write should create the .orig file
- write
- call assert_equal(['one'], readfile('Xfile.orig'))
- call setline(1, 'three')
- " subsequent writes should not create/modify the .orig file
- write
- call assert_equal(['one'], readfile('Xfile.orig'))
-
- " use 'patchmode' with 'nobackup' and 'nowritebackup' to create an empty
- " original file
- call delete('Xfile')
- call delete('Xfile.orig')
- %bw!
- set patchmode=.orig nobackup nowritebackup
- edit Xfile
- call setline(1, ['xxx'])
- write
- call assert_equal(['xxx'], readfile('Xfile'))
- call assert_equal([], readfile('Xfile.orig'))
-
- set patchmode& backup& backupskip& writebackup&
- call delete('Xfile')
- call delete('Xfile.orig')
-endfunc
-
-" Test for writing to a file in a readonly directory
-" NOTE: if you run tests as root this will fail. Don't run tests as root!
-func Test_write_readonly_dir()
- " On MS-Windows, modifying files in a read-only directory is allowed.
- CheckUnix
- " Root can do it too.
- CheckNotRoot
-
- call mkdir('Xdir/')
- call writefile(['one'], 'Xdir/Xfile1')
- call setfperm('Xdir', 'r-xr--r--')
- " try to create a new file in the directory
- new Xdir/Xfile2
- call setline(1, 'two')
- call assert_fails('write', 'E212:')
- " try to create a backup file in the directory
- edit! Xdir/Xfile1
- set backupdir=./Xdir backupskip=
- set patchmode=.orig
- call assert_fails('write', 'E509:')
- call setfperm('Xdir', 'rwxr--r--')
- call delete('Xdir', 'rf')
- set backupdir& backupskip& patchmode&
-endfunc
-
-" Test for writing a file using invalid file encoding
-func Test_write_invalid_encoding()
- new
- call setline(1, 'abc')
- call assert_fails('write ++enc=axbyc Xfile', 'E213:')
- close!
-endfunc
-
-" Tests for reading and writing files with conversion for Win32.
-func Test_write_file_encoding()
- throw 'Skipped: Nvim does not support encoding=latin1'
- CheckMSWindows
- let save_encoding = &encoding
- let save_fileencodings = &fileencodings
- set encoding=latin1 fileencodings&
- let text =<< trim END
- 1 utf-8 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
- 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
- 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
- END
- call writefile(text, 'Xfile')
- edit Xfile
-
- " write tests:
- " combine three values for 'encoding' with three values for 'fileencoding'
- " also write files for read tests
- call cursor(1, 1)
- set encoding=utf-8
- .w! ++enc=utf-8 Xtest
- .w ++enc=cp1251 >> Xtest
- .w ++enc=cp866 >> Xtest
- .w! ++enc=utf-8 Xutf8
- let expected =<< trim END
- 1 utf-8 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
- 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
- 1 utf-8 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
- END
- call assert_equal(expected, readfile('Xtest'))
-
- call cursor(2, 1)
- set encoding=cp1251
- .w! ++enc=utf-8 Xtest
- .w ++enc=cp1251 >> Xtest
- .w ++enc=cp866 >> Xtest
- .w! ++enc=cp1251 Xcp1251
- let expected =<< trim END
- 2 cp1251 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
- 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
- 2 cp1251 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
- END
- call assert_equal(expected, readfile('Xtest'))
-
- call cursor(3, 1)
- set encoding=cp866
- .w! ++enc=utf-8 Xtest
- .w ++enc=cp1251 >> Xtest
- .w ++enc=cp866 >> Xtest
- .w! ++enc=cp866 Xcp866
- let expected =<< trim END
- 3 cp866 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
- 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
- 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
- END
- call assert_equal(expected, readfile('Xtest'))
-
- " read three 'fileencoding's with utf-8 'encoding'
- set encoding=utf-8 fencs=utf-8,cp1251
- e Xutf8
- .w! ++enc=utf-8 Xtest
- e Xcp1251
- .w ++enc=utf-8 >> Xtest
- set fencs=utf-8,cp866
- e Xcp866
- .w ++enc=utf-8 >> Xtest
- let expected =<< trim END
- 1 utf-8 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
- 2 cp1251 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
- 3 cp866 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
- END
- call assert_equal(expected, readfile('Xtest'))
-
- " read three 'fileencoding's with cp1251 'encoding'
- set encoding=utf-8 fencs=utf-8,cp1251
- e Xutf8
- .w! ++enc=cp1251 Xtest
- e Xcp1251
- .w ++enc=cp1251 >> Xtest
- set fencs=utf-8,cp866
- e Xcp866
- .w ++enc=cp1251 >> Xtest
- let expected =<< trim END
- 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
- 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
- 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
- END
- call assert_equal(expected, readfile('Xtest'))
-
- " read three 'fileencoding's with cp866 'encoding'
- set encoding=cp866 fencs=utf-8,cp1251
- e Xutf8
- .w! ++enc=cp866 Xtest
- e Xcp1251
- .w ++enc=cp866 >> Xtest
- set fencs=utf-8,cp866
- e Xcp866
- .w ++enc=cp866 >> Xtest
- let expected =<< trim END
- 1 utf-8 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
- 2 cp1251 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
- 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
- END
- call assert_equal(expected, readfile('Xtest'))
-
- call delete('Xfile')
- call delete('Xtest')
- call delete('Xutf8')
- call delete('Xcp1251')
- call delete('Xcp866')
- let &encoding = save_encoding
- let &fileencodings = save_fileencodings
- %bw!
-endfunc
-
-" Test for writing and reading a file starting with a BOM.
-" Byte Order Mark (BOM) character for various encodings is below:
-" UTF-8 : EF BB BF
-" UTF-16 (BE): FE FF
-" UTF-16 (LE): FF FE
-" UTF-32 (BE): 00 00 FE FF
-" UTF-32 (LE): FF FE 00 00
-func Test_readwrite_file_with_bom()
- let utf8_bom = "\xEF\xBB\xBF"
- let utf16be_bom = "\xFE\xFF"
- let utf16le_bom = "\xFF\xFE"
- let utf32be_bom = "\n\n\xFE\xFF"
- let utf32le_bom = "\xFF\xFE\n\n"
- let save_fileencoding = &fileencoding
- set cpoptions+=S
-
- " Check that editing a latin1 file doesn't see a BOM
- call writefile(["\xFE\xFElatin-1"], 'Xtest1')
- edit Xtest1
- call assert_equal('latin1', &fileencoding)
- call assert_equal(0, &bomb)
- set fenc=latin1
- write Xfile2
- call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xfile2', 'b'))
- set bomb fenc=latin1
- write Xtest3
- call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xtest3', 'b'))
- set bomb&
-
- " Check utf-8 BOM
- %bw!
- call writefile([utf8_bom .. "utf-8"], 'Xtest1')
- edit! Xtest1
- call assert_equal('utf-8', &fileencoding)
- call assert_equal(1, &bomb)
- call assert_equal('utf-8', getline(1))
- set fenc=latin1
- write! Xfile2
- call assert_equal(['utf-8', ''], readfile('Xfile2', 'b'))
- set fenc=utf-8
- w! Xtest3
- call assert_equal([utf8_bom .. "utf-8", ''], readfile('Xtest3', 'b'))
-
- " Check utf-8 with an error (will fall back to latin-1)
- %bw!
- call writefile([utf8_bom .. "utf-8\x80err"], 'Xtest1')
- edit! Xtest1
- call assert_equal('latin1', &fileencoding)
- call assert_equal(0, &bomb)
- call assert_equal("\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", getline(1))
- set fenc=latin1
- write! Xfile2
- call assert_equal([utf8_bom .. "utf-8\x80err", ''], readfile('Xfile2', 'b'))
- set fenc=utf-8
- w! Xtest3
- call assert_equal(["\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", ''],
- \ readfile('Xtest3', 'b'))
-
- " Check ucs-2 BOM
- %bw!
- call writefile([utf16be_bom .. "\nu\nc\ns\n-\n2\n"], 'Xtest1')
- edit! Xtest1
- call assert_equal('utf-16', &fileencoding)
- call assert_equal(1, &bomb)
- call assert_equal('ucs-2', getline(1))
- set fenc=latin1
- write! Xfile2
- call assert_equal(["ucs-2", ''], readfile('Xfile2', 'b'))
- set fenc=ucs-2
- w! Xtest3
- call assert_equal([utf16be_bom .. "\nu\nc\ns\n-\n2\n", ''],
- \ readfile('Xtest3', 'b'))
-
- " Check ucs-2le BOM
- %bw!
- call writefile([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n"], 'Xtest1')
- " Need to add a NUL byte after the NL byte
- call writefile(0z00, 'Xtest1', 'a')
- edit! Xtest1
- call assert_equal('utf-16le', &fileencoding)
- call assert_equal(1, &bomb)
- call assert_equal('ucs-2le', getline(1))
- set fenc=latin1
- write! Xfile2
- call assert_equal(["ucs-2le", ''], readfile('Xfile2', 'b'))
- set fenc=ucs-2le
- w! Xtest3
- call assert_equal([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n", "\n"],
- \ readfile('Xtest3', 'b'))
-
- " Check ucs-4 BOM
- %bw!
- call writefile([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n"], 'Xtest1')
- edit! Xtest1
- call assert_equal('ucs-4', &fileencoding)
- call assert_equal(1, &bomb)
- call assert_equal('ucs-4', getline(1))
- set fenc=latin1
- write! Xfile2
- call assert_equal(["ucs-4", ''], readfile('Xfile2', 'b'))
- set fenc=ucs-4
- w! Xtest3
- call assert_equal([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n", ''], readfile('Xtest3', 'b'))
-
- " Check ucs-4le BOM
- %bw!
- call writefile([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n"], 'Xtest1')
- " Need to add three NUL bytes after the NL byte
- call writefile(0z000000, 'Xtest1', 'a')
- edit! Xtest1
- call assert_equal('ucs-4le', &fileencoding)
- call assert_equal(1, &bomb)
- call assert_equal('ucs-4le', getline(1))
- set fenc=latin1
- write! Xfile2
- call assert_equal(["ucs-4le", ''], readfile('Xfile2', 'b'))
- set fenc=ucs-4le
- w! Xtest3
- call assert_equal([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n", "\n\n\n"], readfile('Xtest3', 'b'))
-
- set cpoptions-=S
- let &fileencoding = save_fileencoding
- call delete('Xtest1')
- call delete('Xfile2')
- call delete('Xtest3')
- %bw!
-endfunc
-
-func Test_read_write_bin()
- " write file missing EOL
- call writefile(['noeol'], "XNoEolSetEol", 'bS')
- call assert_equal(0z6E6F656F6C, readfile('XNoEolSetEol', 'B'))
-
- " when file is read 'eol' is off
- set nofixeol
- e! ++ff=unix XNoEolSetEol
- call assert_equal(0, &eol)
-
- " writing with 'eol' set adds the newline
- setlocal eol
- w
- call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B'))
-
- call delete('XNoEolSetEol')
- set ff& fixeol&
- bwipe! XNoEolSetEol
-endfunc
-
-" Test for the 'backupcopy' option when writing files
-func Test_backupcopy()
- CheckUnix
- set backupskip=
- " With the default 'backupcopy' setting, saving a symbolic link file
- " should not break the link.
- set backupcopy&
- call writefile(['1111'], 'Xfile1')
- silent !ln -s Xfile1 Xfile2
- new Xfile2
- call setline(1, ['2222'])
- write
- close
- call assert_equal(['2222'], readfile('Xfile1'))
- call assert_equal('Xfile1', resolve('Xfile2'))
- call assert_equal('link', getftype('Xfile2'))
- call delete('Xfile1')
- call delete('Xfile2')
-
- " With the 'backupcopy' set to 'breaksymlink', saving a symbolic link file
- " should break the link.
- set backupcopy=yes,breaksymlink
- call writefile(['1111'], 'Xfile1')
- silent !ln -s Xfile1 Xfile2
- new Xfile2
- call setline(1, ['2222'])
- write
- close
- call assert_equal(['1111'], readfile('Xfile1'))
- call assert_equal(['2222'], readfile('Xfile2'))
- call assert_equal('Xfile2', resolve('Xfile2'))
- call assert_equal('file', getftype('Xfile2'))
- call delete('Xfile1')
- call delete('Xfile2')
- set backupcopy&
-
- " With the default 'backupcopy' setting, saving a hard link file
- " should not break the link.
- set backupcopy&
- call writefile(['1111'], 'Xfile1')
- silent !ln Xfile1 Xfile2
- new Xfile2
- call setline(1, ['2222'])
- write
- close
- call assert_equal(['2222'], readfile('Xfile1'))
- call delete('Xfile1')
- call delete('Xfile2')
-
- " With the 'backupcopy' set to 'breaksymlink', saving a hard link file
- " should break the link.
- set backupcopy=yes,breakhardlink
- call writefile(['1111'], 'Xfile1')
- silent !ln Xfile1 Xfile2
- new Xfile2
- call setline(1, ['2222'])
- write
- call assert_equal(['1111'], readfile('Xfile1'))
- call assert_equal(['2222'], readfile('Xfile2'))
- call delete('Xfile1')
- call delete('Xfile2')
-
- " If a backup file is already present, then a slightly modified filename
- " should be used as the backup file. Try with 'backupcopy' set to 'yes' and
- " 'no'.
- %bw
- call writefile(['aaaa'], 'Xfile')
- call writefile(['bbbb'], 'Xfile.bak')
- set backupcopy=yes backupext=.bak
- new Xfile
- call setline(1, ['cccc'])
- write
- close
- call assert_equal(['cccc'], readfile('Xfile'))
- call assert_equal(['bbbb'], readfile('Xfile.bak'))
- set backupcopy=no backupext=.bak
- new Xfile
- call setline(1, ['dddd'])
- write
- close
- call assert_equal(['dddd'], readfile('Xfile'))
- call assert_equal(['bbbb'], readfile('Xfile.bak'))
- call delete('Xfile')
- call delete('Xfile.bak')
-
- " Write to a device file (in Unix-like systems) which cannot be backed up.
- if has('unix')
- set writebackup backupcopy=yes nobackup
- new
- call setline(1, ['aaaa'])
- let output = execute('write! /dev/null')
- call assert_match('"/dev/null" \[Device]', output)
- close
- set writebackup backupcopy=no nobackup
- new
- call setline(1, ['aaaa'])
- let output = execute('write! /dev/null')
- call assert_match('"/dev/null" \[Device]', output)
- close
- set backup writebackup& backupcopy&
- new
- call setline(1, ['aaaa'])
- let output = execute('write! /dev/null')
- call assert_match('"/dev/null" \[Device]', output)
- close
- endif
-
- set backupcopy& backupskip& backupext& backup&
-endfunc
-
-" Test for writing a file with 'encoding' set to 'utf-16'
-func Test_write_utf16()
- new
- call setline(1, ["\U00010001"])
- write ++enc=utf-16 Xfile
- bw!
- call assert_equal(0zD800DC01, readfile('Xfile', 'B')[0:3])
- call delete('Xfile')
-endfunc
-
-" Test for trying to save a backup file when the backup file is a symbolic
-" link to the original file. The backup file should not be modified.
-func Test_write_backup_symlink()
- CheckUnix
- call mkdir('Xbackup')
- let save_backupdir = &backupdir
- set backupdir=.,./Xbackup
- call writefile(['1111'], 'Xfile')
- silent !ln -s Xfile Xfile.bak
-
- new Xfile
- set backup backupcopy=yes backupext=.bak
- write
- call assert_equal('link', getftype('Xfile.bak'))
- call assert_equal('Xfile', resolve('Xfile.bak'))
- " backup file should be created in the 'backup' directory
- if !has('bsd')
- " This check fails on FreeBSD
- call assert_true(filereadable('./Xbackup/Xfile.bak'))
- endif
- set backup& backupcopy& backupext&
- %bw
-
- call delete('Xfile')
- call delete('Xfile.bak')
- call delete('Xbackup', 'rf')
- let &backupdir = save_backupdir
-endfunc
-
-" Test for ':write ++bin' and ':write ++nobin'
-func Test_write_binary_file()
- " create a file without an eol/eof character
- call writefile(0z616161, 'Xfile1', 'b')
- new Xfile1
- write ++bin Xfile2
- write ++nobin Xfile3
- call assert_equal(0z616161, readblob('Xfile2'))
- if has('win32')
- call assert_equal(0z6161610D.0A, readblob('Xfile3'))
- else
- call assert_equal(0z6161610A, readblob('Xfile3'))
- endif
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
-endfunc
-
-" Check that buffer is written before triggering QuitPre
-func Test_wq_quitpre_autocommand()
- edit Xsomefile
- call setline(1, 'hello')
- split
- let g:seq = []
- augroup Testing
- au QuitPre * call add(g:seq, 'QuitPre - ' .. (&modified ? 'modified' : 'not modified'))
- au BufWritePost * call add(g:seq, 'written')
- augroup END
- wq
- call assert_equal(['written', 'QuitPre - not modified'], g:seq)
-
- augroup Testing
- au!
- augroup END
- bwipe!
- unlet g:seq
- call delete('Xsomefile')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim
deleted file mode 100644
index ce2beff7fe..0000000000
--- a/src/nvim/testdir/unix.vim
+++ /dev/null
@@ -1,15 +0,0 @@
-" Settings for test script execution
-" Always use "sh", don't use the value of "$SHELL".
-set shell=sh
-
-if has('win32')
- set shellcmdflag=-c shellxquote= shellxescape= shellquote=
- let &shellredir = '>%s 2>&1'
- set shellslash
-endif
-
-" Don't depend on system locale, always use utf-8
-set encoding=utf-8
-
-" Use safer defaults for various directories
-set backupdir=. directory=. undodir=. viewdir=.
diff --git a/src/nvim/testdir/view_util.vim b/src/nvim/testdir/view_util.vim
deleted file mode 100644
index a4d0e56af9..0000000000
--- a/src/nvim/testdir/view_util.vim
+++ /dev/null
@@ -1,64 +0,0 @@
-" Functions about view shared by several tests
-
-" Only load this script once.
-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
-
-" Get text on the screen, including composing characters.
-" ScreenLines(lnum, width) or
-" ScreenLines([start, end], width)
-func ScreenLines(lnum, width) abort
- redraw!
- if type(a:lnum) == v:t_list
- let start = a:lnum[0]
- let end = a:lnum[1]
- else
- let start = a:lnum
- let end = a:lnum
- endif
- let lines = []
- for l in range(start, end)
- let lines += [join(map(range(1, a:width), 'screenstring(l, v:val)'), '')]
- endfor
- return lines
-endfunc
-
-func ScreenAttrs(lnum, width) abort
- redraw!
- if type(a:lnum) == v:t_list
- let start = a:lnum[0]
- let end = a:lnum[1]
- else
- let start = a:lnum
- let end = a:lnum
- endif
- let attrs = []
- for l in range(start, end)
- let attrs += [map(range(1, a:width), 'screenattr(l, v:val)')]
- endfor
- return attrs
-endfunc
-
-func NewWindow(height, width) abort
- exe a:height . 'new'
- exe a:width . 'vsp'
- set winfixwidth winfixheight
- redraw!
-endfunc
-
-func CloseWindow() abort
- bw!
- redraw!
-endfunc
diff --git a/src/nvim/testdir/vim9.vim b/src/nvim/testdir/vim9.vim
deleted file mode 100644
index 3c0ff2b2dd..0000000000
--- a/src/nvim/testdir/vim9.vim
+++ /dev/null
@@ -1,112 +0,0 @@
-
-" Use a different file name for each run.
-let s:sequence = 1
-
-func CheckScriptFailure(lines, error, lnum = -3)
- if get(a:lines, 0, '') ==# 'vim9script'
- return
- endif
- let cwd = getcwd()
- let fname = 'XScriptFailure' .. s:sequence
- let s:sequence += 1
- call writefile(a:lines, fname)
- try
- call assert_fails('so ' .. fname, a:error, a:lines, a:lnum)
- finally
- call chdir(cwd)
- call delete(fname)
- endtry
-endfunc
-
-func CheckScriptSuccess(lines)
- if get(a:lines, 0, '') ==# 'vim9script'
- return
- endif
- let cwd = getcwd()
- let fname = 'XScriptSuccess' .. s:sequence
- let s:sequence += 1
- call writefile(a:lines, fname)
- try
- exe 'so ' .. fname
- finally
- call chdir(cwd)
- call delete(fname)
- endtry
-endfunc
-
-" Check that "lines" inside a legacy function has no error.
-func CheckLegacySuccess(lines)
- let cwd = getcwd()
- let fname = 'XlegacySuccess' .. s:sequence
- let s:sequence += 1
- call writefile(['func Func()'] + a:lines + ['endfunc'], fname)
- try
- exe 'so ' .. fname
- call Func()
- finally
- delfunc! Func
- call chdir(cwd)
- call delete(fname)
- endtry
-endfunc
-
-" Check that "lines" inside a legacy function results in the expected error
-func CheckLegacyFailure(lines, error)
- let cwd = getcwd()
- let fname = 'XlegacyFails' .. s:sequence
- let s:sequence += 1
- call writefile(['func Func()'] + a:lines + ['endfunc', 'call Func()'], fname)
- try
- call assert_fails('so ' .. fname, a:error)
- finally
- delfunc! Func
- call chdir(cwd)
- call delete(fname)
- endtry
-endfunc
-
-" Execute "lines" in a legacy function, translated as in
-" CheckLegacyAndVim9Success()
-func CheckTransLegacySuccess(lines)
- let legacylines = a:lines->deepcopy()->map({_, v ->
- \ v->substitute('\<VAR\>', 'let', 'g')
- \ ->substitute('\<LET\>', 'let', 'g')
- \ ->substitute('\<LSTART\>', '{', 'g')
- \ ->substitute('\<LMIDDLE\>', '->', 'g')
- \ ->substitute('\<LEND\>', '}', 'g')
- \ ->substitute('\<TRUE\>', '1', 'g')
- \ ->substitute('\<FALSE\>', '0', 'g')
- \ ->substitute('#"', ' "', 'g')
- \ })
- call CheckLegacySuccess(legacylines)
-endfunc
-
-" Execute "lines" in a legacy function
-" Use 'VAR' for a declaration.
-" Use 'LET' for an assignment
-" Use ' #"' for a comment
-" Use LSTART arg LMIDDLE expr LEND for lambda
-" Use 'TRUE' for 1
-" Use 'FALSE' for 0
-func CheckLegacyAndVim9Success(lines)
- call CheckTransLegacySuccess(a:lines)
-endfunc
-
-" Execute "lines" in a legacy function
-" Use 'VAR' for a declaration.
-" Use 'LET' for an assignment
-" Use ' #"' for a comment
-func CheckLegacyAndVim9Failure(lines, error)
- if type(a:error) == type('string')
- let legacyError = error
- else
- let legacyError = error[0]
- endif
-
- let legacylines = a:lines->deepcopy()->map({_, v ->
- \ v->substitute('\<VAR\>', 'let', 'g')
- \ ->substitute('\<LET\>', 'let', 'g')
- \ ->substitute('#"', ' "', 'g')
- \ })
- call CheckLegacyFailure(legacylines, legacyError)
-endfunc
diff --git a/src/nvim/testing.c b/src/nvim/testing.c
index edf92c78ac..cada04d276 100644
--- a/src/nvim/testing.c
+++ b/src/nvim/testing.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// testing.c: Support for tests
#include <inttypes.h>
@@ -9,38 +6,50 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/os/os.h"
+#include "nvim/os/fs.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/testing.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+/// Type of assert_* check being performed
+typedef enum {
+ ASSERT_EQUAL,
+ ASSERT_NOTEQUAL,
+ ASSERT_MATCH,
+ ASSERT_NOTMATCH,
+ ASSERT_FAILS,
+ ASSERT_OTHER,
+} assert_type_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "testing.c.generated.h"
#endif
-static char e_assert_fails_second_arg[]
- = N_("E856: assert_fails() second argument must be a string or a list with one or two strings");
-static char e_assert_fails_fourth_argument[]
- = N_("E1115: assert_fails() fourth argument must be a number");
-static char e_assert_fails_fifth_argument[]
- = N_("E1116: assert_fails() fifth argument must be a string");
-static char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[]
+static const char e_assert_fails_second_arg[]
+ = N_(
+ "E856: \"assert_fails()\" second argument must be a string or a list with one or two strings");
+static const char e_assert_fails_fourth_argument[]
+ = N_("E1115: \"assert_fails()\" fourth argument must be a number");
+static const char e_assert_fails_fifth_argument[]
+ = N_("E1116: \"assert_fails()\" fifth argument must be a string");
+static const char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[]
= N_("E1142: Calling test_garbagecollect_now() while v:testing is not set");
/// Prepare "gap" for an assert error and add the sourcing position.
@@ -77,31 +86,32 @@ static void ga_concat_esc(garray_T *gap, const char *p, int clen)
memmove(buf, p, (size_t)clen);
buf[clen] = NUL;
ga_concat(gap, buf);
- } else {
- switch (*p) {
- case BS:
- ga_concat(gap, "\\b"); break;
- case ESC:
- ga_concat(gap, "\\e"); break;
- case FF:
- ga_concat(gap, "\\f"); break;
- case NL:
- ga_concat(gap, "\\n"); break;
- case TAB:
- ga_concat(gap, "\\t"); break;
- case CAR:
- ga_concat(gap, "\\r"); break;
- case '\\':
- ga_concat(gap, "\\\\"); break;
- default:
- if ((uint8_t)(*p) < ' ' || *p == 0x7f) {
- vim_snprintf(buf, NUMBUFLEN, "\\x%02x", *p);
- ga_concat(gap, buf);
- } else {
- ga_append(gap, (uint8_t)(*p));
- }
- break;
+ return;
+ }
+
+ switch (*p) {
+ case BS:
+ ga_concat(gap, "\\b"); break;
+ case ESC:
+ ga_concat(gap, "\\e"); break;
+ case FF:
+ ga_concat(gap, "\\f"); break;
+ case NL:
+ ga_concat(gap, "\\n"); break;
+ case TAB:
+ ga_concat(gap, "\\t"); break;
+ case CAR:
+ ga_concat(gap, "\\r"); break;
+ case '\\':
+ ga_concat(gap, "\\\\"); break;
+ default:
+ if ((uint8_t)(*p) < ' ' || *p == 0x7f) {
+ vim_snprintf(buf, NUMBUFLEN, "\\x%02x", *p);
+ ga_concat(gap, buf);
+ } else {
+ ga_append(gap, (uint8_t)(*p));
}
+ break;
}
}
@@ -120,7 +130,7 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str)
for (const char *p = str; *p != NUL; p++) {
int same_len = 1;
const char *s = p;
- const int c = mb_ptr2char_adv(&s);
+ const int c = mb_cptr2char_adv(&s);
const int clen = (int)(s - p);
while (*s != NUL && c == utf_ptr2char(s)) {
same_len++;
@@ -141,10 +151,9 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str)
}
/// Fill "gap" with information about an assert error.
-static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str,
+static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, const char *exp_str,
typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype)
{
- char *tofree;
typval_T *exp_tv = exp_tv_arg;
typval_T *got_tv = got_tv_arg;
bool did_copy = false;
@@ -154,7 +163,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
&& !(opt_msg_tv->v_type == VAR_STRING
&& (opt_msg_tv->vval.v_string == NULL
|| *opt_msg_tv->vval.v_string == NUL))) {
- tofree = encode_tv2echo(opt_msg_tv, NULL);
+ char *tofree = encode_tv2echo(opt_msg_tv, NULL);
ga_concat(gap, tofree);
xfree(tofree);
ga_concat(gap, ": ");
@@ -184,16 +193,14 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
int todo = (int)exp_d->dv_hashtab.ht_used;
for (const hashitem_T *hi = exp_d->dv_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
- dictitem_T *item2 = tv_dict_find(got_d, (const char *)hi->hi_key, -1);
+ dictitem_T *item2 = tv_dict_find(got_d, hi->hi_key, -1);
if (item2 == NULL
|| !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false, false)) {
// item of exp_d not present in got_d or values differ.
const size_t key_len = strlen(hi->hi_key);
- tv_dict_add_tv(exp_tv->vval.v_dict, (const char *)hi->hi_key, key_len,
- &TV_DICT_HI2DI(hi)->di_tv);
+ tv_dict_add_tv(exp_tv->vval.v_dict, hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv);
if (item2 != NULL) {
- tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len,
- &item2->di_tv);
+ tv_dict_add_tv(got_tv->vval.v_dict, hi->hi_key, key_len, &item2->di_tv);
}
} else {
omitted++;
@@ -206,23 +213,28 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
todo = (int)got_d->dv_hashtab.ht_used;
for (const hashitem_T *hi = got_d->dv_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
- dictitem_T *item2 = tv_dict_find(exp_d, (const char *)hi->hi_key, -1);
+ dictitem_T *item2 = tv_dict_find(exp_d, hi->hi_key, -1);
if (item2 == NULL) {
// item of got_d not present in exp_d
const size_t key_len = strlen(hi->hi_key);
- tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len,
- &TV_DICT_HI2DI(hi)->di_tv);
+ tv_dict_add_tv(got_tv->vval.v_dict, hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv);
}
todo--;
}
}
}
- tofree = encode_tv2string(exp_tv, NULL);
+ char *tofree = encode_tv2string(exp_tv, NULL);
ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
} else {
+ if (atype == ASSERT_FAILS) {
+ ga_concat(gap, "'");
+ }
ga_concat_shorten_esc(gap, exp_str);
+ if (atype == ASSERT_FAILS) {
+ ga_concat(gap, "'");
+ }
}
if (atype != ASSERT_NOTEQUAL) {
@@ -233,7 +245,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
} else {
ga_concat(gap, " but got ");
}
- tofree = encode_tv2string(got_tv, NULL);
+ char *tofree = encode_tv2string(got_tv, NULL);
ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
@@ -273,11 +285,10 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype)
{
char buf1[NUMBUFLEN];
char buf2[NUMBUFLEN];
- const int called_emsg_before = called_emsg;
const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1);
const char *const text = tv_get_string_buf_chk(&argvars[1], buf2);
- if (called_emsg == called_emsg_before
+ if (pat != NULL && text != NULL
&& pattern_match(pat, text, false) != (atype == ASSERT_MATCH)) {
garray_T ga;
prepare_assert_error(&ga);
@@ -379,12 +390,10 @@ static int assert_equalfile(typval_T *argvars)
{
char buf1[NUMBUFLEN];
char buf2[NUMBUFLEN];
- const int called_emsg_before = called_emsg;
const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
- garray_T ga;
- if (called_emsg > called_emsg_before) {
+ if (fname1 == NULL || fname2 == NULL) {
return 0;
}
@@ -394,12 +403,12 @@ static int assert_equalfile(typval_T *argvars)
char line2[200];
ptrdiff_t lineidx = 0;
if (fd1 == NULL) {
- snprintf(IObuff, IOSIZE, (char *)e_notread, fname1);
+ snprintf(IObuff, IOSIZE, e_notread, fname1);
} else {
FILE *const fd2 = os_fopen(fname2, READBIN);
if (fd2 == NULL) {
fclose(fd1);
- snprintf(IObuff, IOSIZE, (char *)e_notread, fname2);
+ snprintf(IObuff, IOSIZE, e_notread, fname2);
} else {
int64_t linecount = 1;
for (int64_t count = 0;; count++) {
@@ -407,11 +416,11 @@ static int assert_equalfile(typval_T *argvars)
const int c2 = fgetc(fd2);
if (c1 == EOF) {
if (c2 != EOF) {
- STRCPY(IObuff, "first file is shorter");
+ xstrlcpy(IObuff, "first file is shorter", IOSIZE);
}
break;
} else if (c2 == EOF) {
- STRCPY(IObuff, "second file is shorter");
+ xstrlcpy(IObuff, "second file is shorter", IOSIZE);
break;
} else {
line1[lineidx] = (char)c1;
@@ -437,7 +446,9 @@ static int assert_equalfile(typval_T *argvars)
fclose(fd2);
}
}
+
if (IObuff[0] != NUL) {
+ garray_T ga;
prepare_assert_error(&ga);
if (argvars[2].v_type != VAR_UNKNOWN) {
char *const tofree = encode_tv2echo(&argvars[2], NULL);
@@ -461,6 +472,7 @@ static int assert_equalfile(typval_T *argvars)
ga_clear(&ga);
return 1;
}
+
return 0;
}
@@ -502,11 +514,21 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "assert_fails(cmd [, error [, msg]])" function
void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *const cmd = tv_get_string_chk(&argvars[0]);
garray_T ga;
- int save_trylevel = trylevel;
+ const int save_trylevel = trylevel;
const int called_emsg_before = called_emsg;
- char *wrong_arg_msg = NULL;
+ const char *wrong_arg_msg = NULL;
+ char *tofree = NULL;
+
+ if (tv_check_for_string_or_number_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_string_or_list_arg(argvars, 1) == FAIL
+ || (argvars[1].v_type != VAR_UNKNOWN
+ && (argvars[2].v_type != VAR_UNKNOWN
+ && (tv_check_for_opt_number_arg(argvars, 3) == FAIL
+ || (argvars[3].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_string_arg(argvars, 4) == FAIL))))) {
+ return;
+ }
// trylevel must be zero for a ":throw" command to be considered failed
trylevel = 0;
@@ -514,7 +536,13 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
in_assert_fails = true;
no_wait_return++;
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
do_cmdline_cmd(cmd);
+
+ // reset here for any errors reported below
+ trylevel = save_trylevel;
+ suppress_errthrow = false;
+
if (called_emsg == called_emsg_before) {
prepare_assert_error(&ga);
ga_concat(&ga, "command did not fail: ");
@@ -525,6 +553,7 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
} else if (argvars[1].v_type != VAR_UNKNOWN) {
char buf[NUMBUFLEN];
const char *expected;
+ const char *expected_str = NULL;
bool error_found = false;
int error_found_index = 1;
char *actual = emsg_assert_fails_msg == NULL ? "[unknown]" : emsg_assert_fails_msg;
@@ -540,14 +569,23 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
const typval_T *tv = TV_LIST_ITEM_TV(tv_list_first(list));
expected = tv_get_string_buf_chk(tv, buf);
+ if (expected == NULL) {
+ goto theend;
+ }
if (!pattern_match(expected, actual, false)) {
error_found = true;
+ expected_str = expected;
} else if (tv_list_len(list) == 2) {
+ // make a copy, an error in pattern_match() may free it
+ tofree = actual = xstrdup(get_vim_var_str(VV_ERRMSG));
tv = TV_LIST_ITEM_TV(tv_list_last(list));
- actual = get_vim_var_str(VV_ERRMSG);
expected = tv_get_string_buf_chk(tv, buf);
+ if (expected == NULL) {
+ goto theend;
+ }
if (!pattern_match(expected, actual, false)) {
error_found = true;
+ expected_str = expected;
}
}
} else {
@@ -591,8 +629,8 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
actual_tv.v_type = VAR_STRING;
actual_tv.vval.v_string = actual;
}
- fill_assert_error(&ga, &argvars[2], NULL,
- &argvars[error_found_index], &actual_tv, ASSERT_OTHER);
+ fill_assert_error(&ga, &argvars[2], expected_str,
+ &argvars[error_found_index], &actual_tv, ASSERT_FAILS);
ga_concat(&ga, ": ");
assert_append_cmd_or_arg(&ga, argvars, cmd);
assert_error(&ga);
@@ -614,6 +652,7 @@ theend:
msg_reset_scroll();
lines_left = Rows;
XFREE_CLEAR(emsg_assert_fails_msg);
+ xfree(tofree);
set_vim_var_string(VV_ERRMSG, NULL, 0);
if (wrong_arg_msg != NULL) {
emsg(_(wrong_arg_msg));
@@ -641,16 +680,9 @@ static int assert_inrange(typval_T *argvars)
if (factual < flower || factual > fupper) {
garray_T ga;
prepare_assert_error(&ga);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- char *const tofree = encode_tv2string(&argvars[3], NULL);
- ga_concat(&ga, tofree);
- xfree(tofree);
- } else {
- char msg[80];
- vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g",
- flower, fupper, factual);
- ga_concat(&ga, msg);
- }
+ char expected_str[200];
+ vim_snprintf(expected_str, sizeof(expected_str), "range %g - %g,", flower, fupper);
+ fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
return 1;
@@ -666,13 +698,11 @@ static int assert_inrange(typval_T *argvars)
if (actual < lower || actual > upper) {
garray_T ga;
prepare_assert_error(&ga);
-
- char msg[55];
- vim_snprintf(msg, sizeof(msg),
+ char expected_str[200];
+ vim_snprintf(expected_str, sizeof(expected_str),
"range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",",
- lower, upper); // -V576
- fill_assert_error(&ga, &argvars[3], msg, NULL, &argvars[2],
- ASSERT_INRANGE);
+ lower, upper);
+ fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
return 1;
@@ -684,6 +714,13 @@ static int assert_inrange(typval_T *argvars)
/// "assert_inrange(lower, upper[, msg])" function
void f_assert_inrange(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ if (tv_check_for_float_or_nr_arg(argvars, 0) == FAIL
+ || tv_check_for_float_or_nr_arg(argvars, 1) == FAIL
+ || tv_check_for_float_or_nr_arg(argvars, 2) == FAIL
+ || tv_check_for_opt_string_arg(argvars, 3) == FAIL) {
+ return;
+ }
+
rettv->vval.v_number = assert_inrange(argvars);
}
@@ -736,5 +773,4 @@ void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, EvalF
if (fname == NULL) {
return;
}
- list_write_log(fname);
}
diff --git a/src/nvim/testing.h b/src/nvim/testing.h
index 69596d725c..a34ed209c1 100644
--- a/src/nvim/testing.h
+++ b/src/nvim/testing.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_TESTING_H
-#define NVIM_TESTING_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "testing.h.generated.h"
#endif
-#endif // NVIM_TESTING_H
diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c
index fbea1ccfb7..b69d438a59 100644
--- a/src/nvim/textformat.c
+++ b/src/nvim/textformat.c
@@ -1,13 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// textformat.c: text formatting functions
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/charset.h"
@@ -17,29 +14,30 @@
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/textformat.h"
#include "nvim/textobject.h"
-#include "nvim/types.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -72,11 +70,11 @@ bool has_format_option(int x)
void internal_format(int textwidth, int second_indent, int flags, bool format_only, int c)
{
int cc;
- int save_char = NUL;
+ char save_char = NUL;
bool haveto_redraw = false;
const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK);
- const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW);
+ const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW);
const bool fo_white_par = has_format_option(FO_WHITE_PAR);
bool first_line = true;
colnr_T leader_len;
@@ -93,7 +91,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
&& !(State & VREPLACE_FLAG)) {
cc = gchar_cursor();
if (ascii_iswhite(cc)) {
- save_char = cc;
+ save_char = (char)cc;
pchar_cursor('x');
}
}
@@ -104,7 +102,6 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
int wantcol; // column at textwidth border
int foundcol; // column for start of spaces
int end_foundcol = 0; // column for start of word
- colnr_T len;
colnr_T virtcol;
int orig_col = 0;
char *saved_text = NULL;
@@ -309,7 +306,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
col = curwin->w_cursor.col;
inc_cursor();
- cc = ncc;
+ cc = ncc;
ncc = gchar_cursor();
// handle insert
ncc = (ncc != NUL) ? ncc : c;
@@ -318,7 +315,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
if (allow_break) {
// Break only when we are not at end of line.
- end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col;
+ end_foundcol = foundcol = ncc == NUL ? 0 : curwin->w_cursor.col;
break;
}
curwin->w_cursor.col = col;
@@ -423,7 +420,6 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
for (int i = 0; i < padding; i++) {
ins_str(" ");
}
- changed_bytes(curwin->w_cursor.lnum, leader_len);
} else {
(void)set_indent(second_indent, SIN_CHANGED);
}
@@ -441,7 +437,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
// Check if cursor is not past the NUL off the line, cindent
// may have added or removed indent.
curwin->w_cursor.col += startcol;
- len = (colnr_T)strlen(get_cursor_line_ptr());
+ colnr_T len = (colnr_T)strlen(get_cursor_line_ptr());
if (curwin->w_cursor.col > len) {
curwin->w_cursor.col = len;
}
@@ -458,7 +454,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
}
if (save_char != NUL) { // put back space after cursor
- pchar_cursor((char_u)save_char);
+ pchar_cursor(save_char);
}
curwin->w_p_lbr = has_lbr;
@@ -521,10 +517,8 @@ static bool ends_in_white(linenr_T lnum)
static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int leader2_len,
char *leader2_flags)
{
- int idx1 = 0, idx2 = 0;
- char *p;
- char *line1;
- char *line2;
+ int idx1 = 0;
+ int idx2 = 0;
if (leader1_len == 0) {
return leader2_len == 0;
@@ -536,7 +530,7 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int
// If first leader has 's' flag, the lines can only be joined if there is
// some text after it and the second line has the 'm' flag.
if (leader1_flags != NULL) {
- for (p = leader1_flags; *p && *p != ':'; p++) {
+ for (char *p = leader1_flags; *p && *p != ':'; p++) {
if (*p == COM_FIRST) {
return leader2_len == 0;
}
@@ -563,9 +557,9 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int
// Get current line and next line, compare the leaders.
// The first line has to be saved, only one line can be locked at a time.
- line1 = xstrdup(ml_get(lnum));
+ char *line1 = xstrdup(ml_get(lnum));
for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {}
- line2 = ml_get(lnum + 1);
+ char *line2 = ml_get(lnum + 1);
for (idx2 = 0; idx2 < leader2_len; idx2++) {
if (!ascii_iswhite(line2[idx2])) {
if (line1[idx1++] != line2[idx2]) {
@@ -588,7 +582,6 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int
/// false when the previous line is in the same paragraph.
static bool paragraph_start(linenr_T lnum)
{
- char *p;
int leader_len = 0; // leader len of current line
char *leader_flags = NULL; // flags for leader of current line
int next_leader_len = 0; // leader len of next line
@@ -597,7 +590,7 @@ static bool paragraph_start(linenr_T lnum)
if (lnum <= 1) {
return true; // start of the file
}
- p = ml_get(lnum - 1);
+ char *p = ml_get(lnum - 1);
if (*p == NUL) {
return true; // after empty line
}
@@ -633,19 +626,14 @@ static bool paragraph_start(linenr_T lnum)
/// @param prev_line may start in previous line
void auto_format(bool trailblank, bool prev_line)
{
- pos_T pos;
- colnr_T len;
- char *old;
- char *new, *pnew;
- int wasatend;
- int cc;
+ char *linep;
if (!has_format_option(FO_AUTO)) {
return;
}
- pos = curwin->w_cursor;
- old = get_cursor_line_ptr();
+ pos_T pos = curwin->w_cursor;
+ char *old = get_cursor_line_ptr();
// may remove added space
check_auto_format(false);
@@ -655,10 +643,10 @@ void auto_format(bool trailblank, bool prev_line)
// in 'formatoptions' and there is a single character before the cursor.
// Otherwise the line would be broken and when typing another non-white
// next they are not joined back together.
- wasatend = (pos.col == (colnr_T)strlen(old));
+ int wasatend = (pos.col == (colnr_T)strlen(old));
if (*old != NUL && !trailblank && wasatend) {
dec_cursor();
- cc = gchar_cursor();
+ int cc = gchar_cursor();
if (!WHITECHAR(cc) && curwin->w_cursor.col > 0
&& has_format_option(FO_ONE_LETTER)) {
dec_cursor();
@@ -708,13 +696,13 @@ void auto_format(bool trailblank, bool prev_line)
// need to add a space when 'w' is in 'formatoptions' to keep a paragraph
// formatted.
if (!wasatend && has_format_option(FO_WHITE_PAR)) {
- new = get_cursor_line_ptr();
- len = (colnr_T)strlen(new);
+ linep = get_cursor_line_ptr();
+ colnr_T len = (colnr_T)strlen(linep);
if (curwin->w_cursor.col == len) {
- pnew = xstrnsave(new, (size_t)len + 2);
- pnew[len] = ' ';
- pnew[len + 1] = NUL;
- ml_replace(curwin->w_cursor.lnum, pnew, false);
+ char *plinep = xstrnsave(linep, (size_t)len + 2);
+ plinep[len] = ' ';
+ plinep[len + 1] = NUL;
+ ml_replace(curwin->w_cursor.lnum, plinep, false);
// remove the space later
did_add_space = true;
} else {
@@ -733,25 +721,25 @@ void auto_format(bool trailblank, bool prev_line)
/// @param end_insert true when ending Insert mode
void check_auto_format(bool end_insert)
{
- int c = ' ';
- int cc;
+ if (!did_add_space) {
+ return;
+ }
- if (did_add_space) {
- cc = gchar_cursor();
- if (!WHITECHAR(cc)) {
- // Somehow the space was removed already.
+ int cc = gchar_cursor();
+ if (!WHITECHAR(cc)) {
+ // Somehow the space was removed already.
+ did_add_space = false;
+ } else {
+ int c = ' ';
+ if (!end_insert) {
+ inc_cursor();
+ c = gchar_cursor();
+ dec_cursor();
+ }
+ if (c != NUL) {
+ // The space is no longer at the end of the line, delete it.
+ del_char(false);
did_add_space = false;
- } else {
- if (!end_insert) {
- inc_cursor();
- c = gchar_cursor();
- dec_cursor();
- }
- if (c != NUL) {
- // The space is no longer at the end of the line, delete it.
- del_char(false);
- did_add_space = false;
- }
}
}
}
@@ -825,7 +813,7 @@ void op_format(oparg_T *oap, bool keep_cursor)
saved_cursor = oap->cursor_start;
}
- format_lines((linenr_T)oap->line_count, keep_cursor);
+ format_lines(oap->line_count, keep_cursor);
// Leave the cursor at the first non-blank of the last formatted line.
// If the cursor was moved one line back (e.g. with "Q}") go to the next
@@ -884,7 +872,7 @@ void op_formatexpr(oparg_T *oap)
int fex_format(linenr_T lnum, long count, int c)
{
int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL);
- int r;
+ const sctx_T save_sctx = current_sctx;
// Set v:lnum to the first line number and v:count to the number of lines.
// Set v:char to the character to be inserted (can be NUL).
@@ -894,17 +882,20 @@ int fex_format(linenr_T lnum, long count, int c)
// Make a copy, the option could be changed while calling it.
char *fex = xstrdup(curbuf->b_p_fex);
+ current_sctx = curbuf->b_p_script_ctx[BV_FEX].script_ctx;
+
// Evaluate the function.
if (use_sandbox) {
sandbox++;
}
- r = (int)eval_to_number(fex);
+ int r = (int)eval_to_number(fex);
if (use_sandbox) {
sandbox--;
}
set_vim_var_string(VV_CHAR, NULL, -1);
xfree(fex);
+ current_sctx = save_sctx;
return r;
}
@@ -940,7 +931,7 @@ void format_lines(linenr_T line_count, bool avoid_fex)
// length of a line to force formatting: 3 * 'tw'
const int max_len = comp_textwidth(true) * 3;
- // check for 'q', '2' and '1' in 'formatoptions'
+ // check for 'q', '2', 'n' and 'w' in 'formatoptions'
const bool do_comments = has_format_option(FO_Q_COMS); // format comments
int do_comments_list = 0; // format comments with 'n' or '2'
const bool do_second_indent = has_format_option(FO_Q_SECOND);
@@ -1108,15 +1099,13 @@ void format_lines(linenr_T line_count, bool avoid_fex)
}
if (next_leader_len > 0) {
(void)del_bytes(next_leader_len, false, false);
- mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L,
- (long)-next_leader_len, 0);
+ mark_col_adjust(curwin->w_cursor.lnum, 0, 0, -next_leader_len, 0);
} else if (second_indent > 0) { // the "leader" for FO_Q_SECOND
int indent = (int)getwhitecols_curline();
if (indent > 0) {
(void)del_bytes(indent, false, false);
- mark_col_adjust(curwin->w_cursor.lnum,
- (colnr_T)0, 0L, (long)-indent, 0);
+ mark_col_adjust(curwin->w_cursor.lnum, 0, 0, -indent, 0);
}
}
curwin->w_cursor.lnum--;
diff --git a/src/nvim/textformat.h b/src/nvim/textformat.h
index fcc9c2d6f4..25e7152f1b 100644
--- a/src/nvim/textformat.h
+++ b/src/nvim/textformat.h
@@ -1,10 +1,8 @@
-#ifndef NVIM_TEXTFORMAT_H
-#define NVIM_TEXTFORMAT_H
+#pragma once
-#include "nvim/normal.h"
-#include "nvim/pos.h"
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "textformat.h.generated.h"
#endif
-#endif // NVIM_TEXTFORMAT_H
diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c
index 8e786c271c..d4310d47a4 100644
--- a/src/nvim/textobject.c
+++ b/src/nvim/textobject.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// textobject.c: functions for text objects
#include <stdbool.h>
@@ -8,28 +5,27 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/funcs.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/normal.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/move.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/textobject.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "textobject.c.generated.h"
@@ -40,14 +36,13 @@
/// sentence when found. If the next sentence is found, return OK. Return FAIL
/// otherwise. See ":h sentence" for the precise definition of a "sentence"
/// text object.
-int findsent(Direction dir, long count)
+int findsent(Direction dir, int count)
{
- pos_T pos, tpos;
int c;
int (*func)(pos_T *);
bool noskip = false; // do not skip blanks
- pos = curwin->w_cursor;
+ pos_T pos = curwin->w_cursor;
if (dir == FORWARD) {
func = incl;
} else {
@@ -84,7 +79,7 @@ int findsent(Direction dir, long count)
bool found_dot = false;
while (c = gchar_pos(&pos), ascii_iswhite(c)
|| vim_strchr(".!?)]\"'", c) != NULL) {
- tpos = pos;
+ pos_T tpos = pos;
if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) {
break;
}
@@ -105,7 +100,7 @@ int findsent(Direction dir, long count)
const int startlnum = pos.lnum;
const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL;
- for (;;) { // find end of sentence
+ while (true) { // find end of sentence
c = gchar_pos(&pos);
if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, false))) {
if (dir == BACKWARD && pos.lnum != startlnum) {
@@ -114,7 +109,7 @@ int findsent(Direction dir, long count)
break;
}
if (c == '.' || c == '!' || c == '?') {
- tpos = pos;
+ pos_T tpos = pos;
do {
if ((c = inc(&tpos)) == -1) {
break;
@@ -173,10 +168,9 @@ found:
/// @param pincl Return: true if last char is to be included
///
/// @return true if the next paragraph or section was found.
-bool findpar(bool *pincl, int dir, long count, int what, bool both)
+bool findpar(bool *pincl, int dir, int count, int what, bool both)
{
linenr_T curr;
- bool did_skip; // true after separating lines have been skipped
bool first; // true on first line
linenr_T fold_first; // first line of a closed fold
linenr_T fold_last; // last line of a closed fold
@@ -186,7 +180,7 @@ bool findpar(bool *pincl, int dir, long count, int what, bool both)
curr = curwin->w_cursor.lnum;
while (count--) {
- did_skip = false;
+ bool did_skip = false; // true after separating lines have been skipped
for (first = true;; first = false) {
if (*ml_get(curr) != NUL) {
did_skip = true;
@@ -322,12 +316,8 @@ static int cls(void)
/// If eol is true, last word stops at end of line (for operators).
///
/// @param bigword "W", "E" or "B"
-int fwd_word(long count, bool bigword, bool eol)
+int fwd_word(int count, bool bigword, bool eol)
{
- int sclass; // starting class
- int i;
- int last_line;
-
curwin->w_cursor.coladd = 0;
cls_bigword = bigword;
while (--count >= 0) {
@@ -336,12 +326,12 @@ int fwd_word(long count, bool bigword, bool eol)
if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) {
coladvance(MAXCOL);
}
- sclass = cls();
+ int sclass = cls(); // starting class
// We always move at least one character, unless on the last
// character in the buffer.
- last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count);
- i = inc_cursor();
+ int last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count);
+ int i = inc_cursor();
if (i == -1 || (i >= 1 && last_line)) { // started at last char in file
return FAIL;
}
@@ -380,7 +370,7 @@ int fwd_word(long count, bool bigword, bool eol)
/// If stop is true and we are already on the start of a word, move one less.
///
/// Returns FAIL if top of the file was reached.
-int bck_word(long count, bool bigword, bool stop)
+int bck_word(int count, bool bigword, bool stop)
{
int sclass; // starting class
@@ -420,6 +410,7 @@ int bck_word(long count, bool bigword, bool stop)
finished:
stop = false;
}
+ adjust_skipcol();
return OK;
}
@@ -436,7 +427,7 @@ finished:
///
/// If stop is true and we are already on the end of a word, move one less.
/// If empty is true stop on an empty line.
-int end_word(long count, bool bigword, bool stop, bool empty)
+int end_word(int count, bool bigword, bool stop, bool empty)
{
int sclass; // starting class
@@ -491,15 +482,13 @@ finished:
/// @param eol if true, then stop at end of line.
///
/// @return FAIL if start of the file was reached.
-int bckend_word(long count, bool bigword, bool eol)
+int bckend_word(int count, bool bigword, bool eol)
{
- int sclass; // starting class
- int i;
-
curwin->w_cursor.coladd = 0;
cls_bigword = bigword;
while (--count >= 0) {
- sclass = cls();
+ int i;
+ int sclass = cls(); // starting class
if ((i = dec_cursor()) == -1) {
return FAIL;
}
@@ -526,6 +515,7 @@ int bckend_word(long count, bool bigword, bool eol)
}
}
}
+ adjust_skipcol();
return OK;
}
@@ -548,7 +538,7 @@ static void back_in_line(void)
int sclass; // starting class
sclass = cls();
- for (;;) {
+ while (true) {
if (curwin->w_cursor.col == 0) { // stop at start of line
break;
}
@@ -562,10 +552,8 @@ static void back_in_line(void)
static void find_first_blank(pos_T *posp)
{
- int c;
-
while (decl(posp) != -1) {
- c = gchar_pos(posp);
+ int c = gchar_pos(posp);
if (!ascii_iswhite(c)) {
incl(posp);
break;
@@ -576,10 +564,10 @@ static void find_first_blank(pos_T *posp)
/// Skip count/2 sentences and count/2 separating white spaces.
///
/// @param at_start_sent cursor is at start of sentence
-static void findsent_forward(long count, bool at_start_sent)
+static void findsent_forward(int count, bool at_start_sent)
{
while (count--) {
- findsent(FORWARD, 1L);
+ findsent(FORWARD, 1);
if (at_start_sent) {
find_first_blank(&curwin->w_cursor);
}
@@ -595,10 +583,9 @@ static void findsent_forward(long count, bool at_start_sent)
///
/// @param include true: include word and white space
/// @param bigword false == word, true == WORD
-int current_word(oparg_T *oap, long count, bool include, bool bigword)
+int current_word(oparg_T *oap, int count, bool include, bool bigword)
{
pos_T start_pos;
- pos_T pos;
bool inclusive = true;
int include_white = false;
@@ -621,7 +608,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
// (" word"), or start is not on white space, and white space should
// not be included ("word"), find end of word.
if ((cls() == 0) == include) {
- if (end_word(1L, bigword, true, true) == FAIL) {
+ if (end_word(1, bigword, true, true) == FAIL) {
return FAIL;
}
} else {
@@ -630,7 +617,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
// space should not be included (" "), find start of word.
// If we end up in the first column of the next line (single char
// word) back up to end of the line.
- fwd_word(1L, bigword, true);
+ fwd_word(1, bigword, true);
if (curwin->w_cursor.col == 0) {
decl(&curwin->w_cursor);
} else {
@@ -662,11 +649,11 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
return FAIL;
}
if (include != (cls() != 0)) {
- if (bck_word(1L, bigword, true) == FAIL) {
+ if (bck_word(1, bigword, true) == FAIL) {
return FAIL;
}
} else {
- if (bckend_word(1L, bigword, true) == FAIL) {
+ if (bckend_word(1, bigword, true) == FAIL) {
return FAIL;
}
(void)incl(&curwin->w_cursor);
@@ -677,7 +664,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
return FAIL;
}
if (include != (cls() == 0)) {
- if (fwd_word(1L, bigword, true) == FAIL && count > 1) {
+ if (fwd_word(1, bigword, true) == FAIL && count > 1) {
return FAIL;
}
// If end is just past a new-line, we don't want to include
@@ -687,7 +674,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
inclusive = false;
}
} else {
- if (end_word(1L, bigword, true, true) == FAIL) {
+ if (end_word(1, bigword, true, true) == FAIL) {
return FAIL;
}
}
@@ -703,7 +690,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
// word). Also when "2daw" deletes "word." at the end of the line
// (cursor is at start of next line).
// But don't delete white space at start of line (indent).
- pos = curwin->w_cursor; // save cursor position
+ pos_T pos = curwin->w_cursor; // save cursor position
curwin->w_cursor = start_pos;
if (oneleft() == OK) {
back_in_line();
@@ -735,18 +722,18 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
/// Find sentence(s) under the cursor, cursor at end.
/// When Visual active, extend it by one or more sentences.
-int current_sent(oparg_T *oap, long count, bool include)
+int current_sent(oparg_T *oap, int count, bool include)
{
pos_T start_pos;
pos_T pos;
bool start_blank;
int c;
bool at_start_sent;
- long ncount;
+ int ncount;
start_pos = curwin->w_cursor;
pos = start_pos;
- findsent(FORWARD, 1L); // Find start of next sentence.
+ findsent(FORWARD, 1); // Find start of next sentence.
// When the Visual area is bigger than one character: Extend it.
if (VIsual_active && !equalpos(start_pos, VIsual)) {
@@ -768,12 +755,12 @@ extend:
incl(&pos);
}
if (!at_start_sent) {
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
if (equalpos(curwin->w_cursor, start_pos)) {
at_start_sent = true; // exactly at start of sentence
} else {
// inside a sentence, go to its end (start of next)
- findsent(FORWARD, 1L);
+ findsent(FORWARD, 1);
}
}
if (include) { // "as" gets twice as much as "is"
@@ -785,7 +772,7 @@ extend:
}
c = gchar_cursor();
if (!at_start_sent || (!include && !ascii_iswhite(c))) {
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
}
at_start_sent = !at_start_sent;
}
@@ -808,7 +795,7 @@ extend:
incl(&pos);
}
if (at_start_sent) { // in the sentence
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
} else { // in/before white before a sentence
curwin->w_cursor = start_pos;
}
@@ -835,7 +822,7 @@ extend:
find_first_blank(&start_pos); // go back to first blank
} else {
start_blank = false;
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
start_pos = curwin->w_cursor;
}
if (include) {
@@ -898,19 +885,16 @@ extend:
/// @param include true == include white space
/// @param what '(', '{', etc.
/// @param other ')', '}', etc.
-int current_block(oparg_T *oap, long count, bool include, int what, int other)
+int current_block(oparg_T *oap, int count, bool include, int what, int other)
{
- pos_T old_pos;
pos_T *pos = NULL;
pos_T start_pos;
pos_T *end_pos;
- pos_T old_start, old_end;
- char *save_cpo;
bool sol = false; // '{' at start of line
- old_pos = curwin->w_cursor;
- old_end = curwin->w_cursor; // remember where we started
- old_start = old_end;
+ pos_T old_pos = curwin->w_cursor;
+ pos_T old_end = curwin->w_cursor; // remember where we started
+ pos_T old_start = old_end;
// If we start on '(', '{', ')', '}', etc., use the whole block inclusive.
if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) {
@@ -937,7 +921,7 @@ int current_block(oparg_T *oap, long count, bool include, int what, int other)
// Put this position in start_pos.
// Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the
// user wants.
- save_cpo = p_cpo;
+ char *save_cpo = p_cpo;
p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%";
if ((pos = findmatch(NULL, what)) != NULL) {
while (count-- > 0) {
@@ -1042,7 +1026,6 @@ static bool in_html_tag(bool end_tag)
{
char *line = get_cursor_line_ptr();
char *p;
- int c;
int lc = NUL;
pos_T pos;
@@ -1074,11 +1057,11 @@ static bool in_html_tag(bool end_tag)
}
// check that the matching '>' is not preceded by '/'
- for (;;) {
+ while (true) {
if (inc(&pos) < 0) {
return false;
}
- c = (uint8_t)(*ml_get_pos(&pos));
+ int c = (uint8_t)(*ml_get_pos(&pos));
if (c == '>') {
break;
}
@@ -1090,16 +1073,10 @@ static bool in_html_tag(bool end_tag)
/// Find tag block under the cursor, cursor at end.
///
/// @param include true == include white space
-int current_tagblock(oparg_T *oap, long count_arg, bool include)
+int current_tagblock(oparg_T *oap, int count_arg, bool include)
{
- long count = count_arg;
- pos_T old_pos;
- pos_T start_pos;
- pos_T end_pos;
- pos_T old_start, old_end;
- char *p;
+ int count = count_arg;
char *cp;
- int len;
bool do_include = include;
bool save_p_ws = p_ws;
int retval = FAIL;
@@ -1107,9 +1084,9 @@ int current_tagblock(oparg_T *oap, long count_arg, bool include)
p_ws = false;
- old_pos = curwin->w_cursor;
- old_end = curwin->w_cursor; // remember where we started
- old_start = old_end;
+ pos_T old_pos = curwin->w_cursor;
+ pos_T old_end = curwin->w_cursor; // remember where we started
+ pos_T old_start = old_end;
if (!VIsual_active || *p_sel == 'e') {
decl(&old_end); // old_end is inclusive
}
@@ -1152,24 +1129,24 @@ int current_tagblock(oparg_T *oap, long count_arg, bool include)
again:
// Search backwards for unclosed "<aaa>".
// Put this position in start_pos.
- for (long n = 0; n < count; n++) {
+ for (int n = 0; n < count; n++) {
if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
"",
"</[^>]*>", BACKWARD, NULL, 0,
- NULL, (linenr_T)0, 0L) <= 0) {
+ NULL, 0, 0) <= 0) {
curwin->w_cursor = old_pos;
goto theend;
}
}
- start_pos = curwin->w_cursor;
+ pos_T start_pos = curwin->w_cursor;
// Search for matching "</aaa>". First isolate the "aaa".
inc_cursor();
- p = get_cursor_pos_ptr();
+ char *p = get_cursor_pos_ptr();
for (cp = p;
*cp != NUL && *cp != '>' && !ascii_iswhite(*cp);
MB_PTR_ADV(cp)) {}
- len = (int)(cp - p);
+ int len = (int)(cp - p);
if (len == 0) {
curwin->w_cursor = old_pos;
goto theend;
@@ -1182,7 +1159,7 @@ again:
"<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p);
snprintf(epat, epat_len, "</%.*s>\\c", len, p);
- const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L);
+ const int r = do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, 0, 0);
xfree(spat);
xfree(epat);
@@ -1215,7 +1192,7 @@ again:
dec_cursor();
}
}
- end_pos = curwin->w_cursor;
+ pos_T end_pos = curwin->w_cursor;
if (!do_include) {
// Exclude the start tag.
@@ -1274,24 +1251,17 @@ theend:
/// @param include true == include white space
/// @param type 'p' for paragraph, 'S' for section
-int current_par(oparg_T *oap, long count, bool include, int type)
+int current_par(oparg_T *oap, int count, bool include, int type)
{
- linenr_T start_lnum;
- linenr_T end_lnum;
- int white_in_front;
int dir;
- int start_is_white;
- int prev_start_is_white;
int retval = OK;
int do_white = false;
- int t;
- int i;
if (type == 'S') { // not implemented yet
return FAIL;
}
- start_lnum = curwin->w_cursor.lnum;
+ linenr_T start_lnum = curwin->w_cursor.lnum;
// When visual area is more than one line: extend it.
if (VIsual_active && start_lnum != VIsual.lnum) {
@@ -1301,22 +1271,22 @@ extend:
} else {
dir = FORWARD;
}
- for (i = (int)count; --i >= 0;) {
+ for (int i = count; --i >= 0;) {
if (start_lnum ==
(dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) {
retval = FAIL;
break;
}
- prev_start_is_white = -1;
- for (t = 0; t < 2; t++) {
+ int prev_start_is_white = -1;
+ for (int t = 0; t < 2; t++) {
start_lnum += dir;
- start_is_white = linewhite(start_lnum);
+ int start_is_white = linewhite(start_lnum);
if (prev_start_is_white == start_is_white) {
start_lnum -= dir;
break;
}
- for (;;) {
+ while (true) {
if (start_lnum == (dir == BACKWARD
? 1 : curbuf->b_ml.ml_line_count)) {
break;
@@ -1345,7 +1315,7 @@ extend:
}
// First move back to the start_lnum of the paragraph or white lines
- white_in_front = linewhite(start_lnum);
+ int white_in_front = linewhite(start_lnum);
while (start_lnum > 1) {
if (white_in_front) { // stop at first white line
if (!linewhite(start_lnum - 1)) {
@@ -1360,13 +1330,13 @@ extend:
}
// Move past the end of any white lines.
- end_lnum = start_lnum;
+ linenr_T end_lnum = start_lnum;
while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) {
end_lnum++;
}
end_lnum--;
- i = (int)count;
+ int i = count;
if (!include && white_in_front) {
i--;
}
@@ -1443,10 +1413,8 @@ extend:
/// @return column number of "quotechar" or -1 when not found.
static int find_next_quote(char *line, int col, int quotechar, char *escape)
{
- int c;
-
- for (;;) {
- c = (uint8_t)line[col];
+ while (true) {
+ int c = (uint8_t)line[col];
if (c == NUL) {
return -1;
} else if (escape != NULL && vim_strchr(escape, c)) {
@@ -1471,12 +1439,10 @@ static int find_next_quote(char *line, int col, int quotechar, char *escape)
/// @return the found column or zero.
static int find_prev_quote(char *line, int col_start, int quotechar, char *escape)
{
- int n;
-
while (col_start > 0) {
col_start--;
col_start -= utf_head_off(line, line + col_start);
- n = 0;
+ int n = 0;
if (escape != NULL) {
while (col_start - n > 0 && vim_strchr(escape,
(uint8_t)line[col_start - n - 1]) != NULL) {
@@ -1498,7 +1464,7 @@ static int find_prev_quote(char *line, int col_start, int quotechar, char *escap
/// @param quotechar Quote character
///
/// @return true if found, else false.
-bool current_quote(oparg_T *oap, long count, bool include, int quotechar)
+bool current_quote(oparg_T *oap, int count, bool include, int quotechar)
FUNC_ATTR_NONNULL_ALL
{
char *line = get_cursor_line_ptr();
@@ -1621,7 +1587,7 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar)
// Also do this when there is a Visual area, a' may leave the cursor
// in between two strings.
col_start = 0;
- for (;;) {
+ while (true) {
// Find open quote character.
col_start = find_next_quote(line, col_start, quotechar, NULL);
if (col_start < 0 || col_start > first_col) {
diff --git a/src/nvim/textobject.h b/src/nvim/textobject.h
index e31557f297..a540c7c98f 100644
--- a/src/nvim/textobject.h
+++ b/src/nvim/textobject.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_TEXTOBJECT_H
-#define NVIM_TEXTOBJECT_H
+#pragma once
-#include "nvim/normal.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "textobject.h.generated.h"
#endif
-#endif // NVIM_TEXTOBJECT_H
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 2cb39ab26b..bdbb5e4872 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -1,28 +1,23 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
-#include "nvim/charset.h"
#include "nvim/event/defs.h"
-#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
-#include "nvim/option.h"
-#include "nvim/os/input.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os.h"
+#include "nvim/strings.h"
#include "nvim/tui/input.h"
#include "nvim/tui/input_defs.h"
#include "nvim/tui/tui.h"
-#include "nvim/types.h"
#include "nvim/ui_client.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
@@ -30,10 +25,11 @@
#include "nvim/event/rstream.h"
#include "nvim/msgpack_rpc/channel.h"
+#define READ_STREAM_SIZE 0xfff
#define KEY_BUFFER_SIZE 0xfff
static const struct kitty_key_map_entry {
- KittyKey key;
+ int key;
const char *name;
} kitty_key_map_entry[] = {
{ KITTY_KEY_ESCAPE, "Esc" },
@@ -115,15 +111,7 @@ static const struct kitty_key_map_entry {
{ KITTY_KEY_KP_BEGIN, "kOrigin" },
};
-static Map(KittyKey, cstr_t) kitty_key_map = MAP_INIT;
-
-#ifndef UNIT_TESTING
-typedef enum {
- kIncomplete = -1,
- kNotApplicable = 0,
- kComplete = 1,
-} HandleState;
-#endif
+static PMap(int) kitty_key_map = MAP_INIT;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/input.c.generated.h"
@@ -134,19 +122,13 @@ void tinput_init(TermInput *input, Loop *loop)
input->loop = loop;
input->paste = 0;
input->in_fd = STDIN_FILENO;
- input->waiting_for_bg_response = 0;
- input->extkeys_type = kExtkeysNone;
- // The main thread is waiting for the UI thread to call CONTINUE, so it can
- // safely access global variables.
+ input->key_encoding = kKeyEncodingLegacy;
input->ttimeout = (bool)p_ttimeout;
input->ttimeoutlen = p_ttm;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
- uv_mutex_init(&input->key_buffer_mutex);
- uv_cond_init(&input->key_buffer_cond);
for (size_t i = 0; i < ARRAY_SIZE(kitty_key_map_entry); i++) {
- map_put(KittyKey, cstr_t)(&kitty_key_map, kitty_key_map_entry[i].key,
- kitty_key_map_entry[i].name);
+ pmap_put(int)(&kitty_key_map, kitty_key_map_entry[i].key, (ptr_t)kitty_key_map_entry[i].name);
}
input->in_fd = STDIN_FILENO;
@@ -165,17 +147,17 @@ void tinput_init(TermInput *input, Loop *loop)
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
// setup input handle
- rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff);
+ rstream_init_fd(loop, &input->read_stream, input->in_fd, READ_STREAM_SIZE);
+ termkey_set_buffer_size(input->tk, rbuffer_capacity(input->read_stream.buffer));
+
// initialize a timer handle for handling ESC with libtermkey
time_watcher_init(loop, &input->timer_handle, input);
}
void tinput_destroy(TermInput *input)
{
- map_destroy(KittyKey, cstr_t)(&kitty_key_map);
+ map_destroy(int, &kitty_key_map);
rbuffer_free(input->key_buffer);
- uv_mutex_destroy(&input->key_buffer_mutex);
- uv_cond_destroy(&input->key_buffer_cond);
time_watcher_close(&input->timer_handle, NULL);
stream_close(&input->read_stream, NULL, NULL);
termkey_destroy(input->tk);
@@ -193,13 +175,14 @@ void tinput_stop(TermInput *input)
}
static void tinput_done_event(void **argv)
+ FUNC_ATTR_NORETURN
{
- input_done();
+ os_exit(1);
}
-static void tinput_wait_enqueue(void **argv)
+/// Send all pending input in key buffer to Nvim server.
+static void tinput_flush(TermInput *input)
{
- TermInput *input = argv[0];
if (input->paste) { // produce exactly one paste event
const size_t len = rbuffer_size(input->key_buffer);
String keys = { .data = xmallocz(len), .size = len };
@@ -215,7 +198,7 @@ static void tinput_wait_enqueue(void **argv)
input->paste = 2;
}
rbuffer_reset(input->key_buffer);
- } else { // enqueue input for the main thread or Nvim server
+ } else { // enqueue input
RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) {
const String keys = { .data = buf, .size = len };
MAXSIZE_TEMP_ARRAY(args, 1);
@@ -229,42 +212,66 @@ static void tinput_wait_enqueue(void **argv)
}
}
-static void tinput_flush(TermInput *input, bool wait_until_empty)
-{
- size_t drain_boundary = wait_until_empty ? 0 : 0xff;
- do {
- tinput_wait_enqueue((void **)&input);
- } while (rbuffer_size(input->key_buffer) > drain_boundary);
-}
-
static void tinput_enqueue(TermInput *input, char *buf, size_t size)
{
if (rbuffer_size(input->key_buffer) >
rbuffer_capacity(input->key_buffer) - 0xff) {
// don't ever let the buffer get too full or we risk putting incomplete keys
// into it
- tinput_flush(input, false);
+ tinput_flush(input);
}
rbuffer_write(input->key_buffer, buf, size);
}
+/// Handle TERMKEY_KEYMOD_* modifiers, i.e. Shift, Alt and Ctrl.
+///
+/// @return The number of bytes written into "buf", excluding the final NUL.
+static size_t handle_termkey_modifiers(TermKeyKey *key, char *buf, size_t buflen)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t len = 0;
+ if (key->modifiers & TERMKEY_KEYMOD_SHIFT) { // Shift
+ len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-");
+ }
+ if (key->modifiers & TERMKEY_KEYMOD_ALT) { // Alt
+ len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-");
+ }
+ if (key->modifiers & TERMKEY_KEYMOD_CTRL) { // Ctrl
+ len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-");
+ }
+ assert(len < buflen);
+ return len;
+}
+
+/// Handle modifiers not handled by libtermkey.
+/// Currently only Super ("D-") and Meta ("T-") are supported in Nvim.
+///
+/// @return The number of bytes written into "buf", excluding the final NUL.
+static size_t handle_more_modifiers(TermKeyKey *key, char *buf, size_t buflen)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t len = 0;
+ if (key->modifiers & 8) { // Super
+ len += (size_t)snprintf(buf + len, buflen - len, "D-");
+ }
+ if (key->modifiers & 32) { // Meta
+ len += (size_t)snprintf(buf + len, buflen - len, "T-");
+ }
+ assert(len < buflen);
+ return len;
+}
+
static void handle_kitty_key_protocol(TermInput *input, TermKeyKey *key)
{
- const char *name = map_get(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint);
+ const char *name = pmap_get(int)(&kitty_key_map, (int)key->code.codepoint);
if (name) {
char buf[64];
size_t len = 0;
buf[len++] = '<';
- if (key->modifiers & TERMKEY_KEYMOD_SHIFT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-");
- }
- if (key->modifiers & TERMKEY_KEYMOD_ALT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-");
- }
- if (key->modifiers & TERMKEY_KEYMOD_CTRL) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-");
- }
+ len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len);
+ len += handle_more_modifiers(key, buf + len, sizeof(buf) - len);
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "%s>", name);
+ assert(len < sizeof(buf));
tinput_enqueue(input, buf, len);
}
}
@@ -276,7 +283,7 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key)
char *ptr = key->utf8;
if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF
- && map_has(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint)) {
+ && map_has(int, &kitty_key_map, (int)key->code.codepoint)) {
handle_kitty_key_protocol(input, key);
return;
}
@@ -286,6 +293,7 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key)
} else {
buf[len++] = *ptr;
}
+ assert(len < sizeof(buf));
ptr++;
}
@@ -305,8 +313,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
} else {
assert(key->modifiers);
if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF
- && map_has(KittyKey, cstr_t)(&kitty_key_map,
- (KittyKey)key->code.codepoint)) {
+ && map_has(int, &kitty_key_map, (int)key->code.codepoint)) {
handle_kitty_key_protocol(input, key);
return;
}
@@ -317,7 +324,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
if ((key->modifiers & TERMKEY_KEYMOD_CTRL)
&& !(key->modifiers & TERMKEY_KEYMOD_SHIFT)
&& ASCII_ISUPPER(key->code.codepoint)) {
- assert(len <= 62);
+ assert(len + 2 < sizeof(buf));
// Make room for the S-
memmove(buf + 3, buf + 1, len - 1);
buf[1] = 'S';
@@ -326,6 +333,16 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
}
}
+ char more_buf[25];
+ size_t more_len = handle_more_modifiers(key, more_buf, sizeof(more_buf));
+ if (more_len > 0) {
+ assert(len + more_len < sizeof(buf));
+ memmove(buf + 1 + more_len, buf + 1, len - 1);
+ memcpy(buf + 1, more_buf, more_len);
+ len += more_len;
+ }
+
+ assert(len < sizeof(buf));
tinput_enqueue(input, buf, len);
}
@@ -349,9 +366,10 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
if (ev == TERMKEY_MOUSE_UNKNOWN && !(key->code.mouse[0] & 0x20)) {
int code = key->code.mouse[0] & ~0x3c;
+ // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Other-buttons
if (code == 66 || code == 67) {
ev = TERMKEY_MOUSE_PRESS;
- button = code - 60;
+ button = code + 4 - 64;
}
}
@@ -363,17 +381,9 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
row--; col--; // Termkey uses 1-based coordinates
buf[len++] = '<';
- if (key->modifiers & TERMKEY_KEYMOD_SHIFT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-");
- }
-
- if (key->modifiers & TERMKEY_KEYMOD_CTRL) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-");
- }
-
- if (key->modifiers & TERMKEY_KEYMOD_ALT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-");
- }
+ len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len);
+ // Doesn't actually work because there are only 3 bits (0x1c) for modifiers.
+ // len += handle_more_modifiers(key, buf + len, sizeof(buf) - len);
if (button == 1) {
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Left");
@@ -410,6 +420,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
}
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
+ assert(len < sizeof(buf));
tinput_enqueue(input, buf, len);
}
@@ -433,38 +444,11 @@ static void tk_getkeys(TermInput *input, bool force)
} else if (key.type == TERMKEY_TYPE_MOUSE) {
forward_mouse_event(input, &key);
} else if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) {
- // There is no specified limit on the number of parameters a CSI sequence can contain, so just
- // allocate enough space for a large upper bound
- long args[16];
- size_t nargs = 16;
- unsigned long cmd;
- if (termkey_interpret_csi(input->tk, &key, args, &nargs, &cmd) == TERMKEY_RES_KEY) {
- uint8_t intermediate = (cmd >> 16) & 0xFF;
- uint8_t initial = (cmd >> 8) & 0xFF;
- uint8_t command = cmd & 0xFF;
-
- // Currently unused
- (void)intermediate;
-
- if (input->waiting_for_csiu_response > 0) {
- if (initial == '?' && command == 'u') {
- // The first (and only) argument contains the current progressive
- // enhancement flags. Only enable CSI u mode if the first bit
- // (disambiguate escape codes) is not already set
- if (nargs > 0 && (args[0] & 0x1) == 0) {
- input->extkeys_type = kExtkeysCSIu;
- } else {
- input->extkeys_type = kExtkeysNone;
- }
- } else if (initial == '?' && command == 'c') {
- // Received Primary Device Attributes response
- input->waiting_for_csiu_response = 0;
- tui_enable_extkeys(input->tui_data);
- } else {
- input->waiting_for_csiu_response--;
- }
- }
- }
+ handle_unknown_csi(input, &key);
+ } else if (key.type == TERMKEY_TYPE_OSC || key.type == TERMKEY_TYPE_DCS) {
+ handle_term_response(input, &key);
+ } else if (key.type == TERMKEY_TYPE_MODEREPORT) {
+ handle_modereport(input, &key);
}
}
@@ -494,7 +478,7 @@ static void tinput_timer_cb(TimeWatcher *watcher, void *data)
handle_raw_buffer(input, true);
}
tk_getkeys(input, true);
- tinput_flush(input, true);
+ tinput_flush(input);
}
/// Handle focus events.
@@ -542,13 +526,13 @@ static HandleState handle_bracketed_paste(TermInput *input)
if (enable) {
// Flush before starting paste.
- tinput_flush(input, true);
+ tinput_flush(input);
// Paste phase: "first-chunk".
input->paste = 1;
} else if (input->paste) {
// Paste phase: "last-chunk".
input->paste = input->paste == 2 ? 3 : -1;
- tinput_flush(input, true);
+ tinput_flush(input);
// Paste phase: "disabled".
input->paste = 0;
}
@@ -563,130 +547,116 @@ static HandleState handle_bracketed_paste(TermInput *input)
return kNotApplicable;
}
-static void set_bg(char *bgvalue)
+/// Handle an OSC or DCS response sequence from the terminal.
+static void handle_term_response(TermInput *input, const TermKeyKey *key)
+ FUNC_ATTR_NONNULL_ALL
{
- if (ui_client_attached) {
+ const char *str = NULL;
+ if (termkey_interpret_string(input->tk, key, &str) == TERMKEY_RES_KEY) {
+ assert(str != NULL);
+
+ // Send an event to nvim core. This will update the v:termresponse variable
+ // and fire the TermResponse event
MAXSIZE_TEMP_ARRAY(args, 2);
- ADD_C(args, STRING_OBJ(cstr_as_string("term_background")));
- ADD_C(args, STRING_OBJ(cstr_as_string(bgvalue)));
- rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args);
+ ADD_C(args, STATIC_CSTR_AS_OBJ("termresponse"));
+
+ // libtermkey strips the OSC/DCS bytes from the response. We add it back in
+ // so that downstream consumers of v:termresponse can differentiate between
+ // the two.
+ StringBuilder response = KV_INITIAL_VALUE;
+ switch (key->type) {
+ case TERMKEY_TYPE_OSC:
+ kv_printf(response, "\x1b]%s", str);
+ break;
+ case TERMKEY_TYPE_DCS:
+ kv_printf(response, "\x1bP%s", str);
+ break;
+ default:
+ // Key type already checked for OSC/DCS in termkey_interpret_string
+ UNREACHABLE;
+ }
+
+ ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size)));
+ rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args);
+ kv_destroy(response);
}
}
-// During startup, tui.c requests the background color (see `ext.get_bg`).
-//
-// Here in input.c, we watch for the terminal response `\e]11;COLOR\a`. If
-// COLOR matches `rgb:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits,
-// then compute the luminance[1] of the RGB color and classify it as light/dark
-// accordingly. Note that the color components may have anywhere from one to
-// four hex digits, and require scaling accordingly as values out of 4, 8, 12,
-// or 16 bits. Also note the A(lpha) component is optional, and is parsed but
-// ignored in the calculations.
-//
-// [1] https://en.wikipedia.org/wiki/Luma_%28video%29
-static HandleState handle_background_color(TermInput *input)
+/// Handle a mode report (DECRPM) sequence from the terminal.
+static void handle_modereport(TermInput *input, const TermKeyKey *key)
+ FUNC_ATTR_NONNULL_ALL
{
- if (input->waiting_for_bg_response <= 0) {
- return kNotApplicable;
+ int initial;
+ int mode;
+ int value;
+ if (termkey_interpret_modereport(input->tk, key, &initial, &mode, &value) == TERMKEY_RES_KEY) {
+ (void)initial; // Unused
+ tui_handle_term_mode(input->tui_data, (TermMode)mode, (TermModeState)value);
}
- size_t count = 0;
- size_t component = 0;
- size_t header_size = 0;
- size_t num_components = 0;
- size_t buf_size = rbuffer_size(input->read_stream.buffer);
- uint16_t rgb[] = { 0, 0, 0 };
- uint16_t rgb_max[] = { 0, 0, 0 };
- bool eat_backslash = false;
- bool done = false;
- bool bad = false;
- if (buf_size >= 9
- && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgb:", 9)) {
- header_size = 9;
- num_components = 3;
- } else if (buf_size >= 10
- && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgba:", 10)) {
- header_size = 10;
- num_components = 4;
- } else if (buf_size < 10
- && !rbuffer_cmp(input->read_stream.buffer,
- "\x1b]11;rgba", buf_size)) {
- // An incomplete sequence was found, waiting for the next input.
- return kIncomplete;
- } else {
- input->waiting_for_bg_response--;
- if (input->waiting_for_bg_response == 0) {
- DLOG("did not get a response for terminal background query");
- }
- return kNotApplicable;
+}
+
+/// Handle a CSI sequence from the terminal that is unrecognized by libtermkey.
+static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // There is no specified limit on the number of parameters a CSI sequence can
+ // contain, so just allocate enough space for a large upper bound
+ long args[16];
+ size_t nargs = 16;
+ unsigned long cmd;
+ if (termkey_interpret_csi(input->tk, key, args, &nargs, &cmd) != TERMKEY_RES_KEY) {
+ return;
}
- RBUFFER_EACH(input->read_stream.buffer, c, i) {
- count = i + 1;
- // Skip the header.
- if (i < header_size) {
- continue;
- }
- if (eat_backslash) {
- done = true;
- break;
- } else if (c == '\x07') {
- done = true;
+
+ uint8_t intermediate = (cmd >> 16) & 0xFF;
+ uint8_t initial = (cmd >> 8) & 0xFF;
+ uint8_t command = cmd & 0xFF;
+
+ // Currently unused
+ (void)intermediate;
+
+ switch (command) {
+ case 'u':
+ switch (initial) {
+ case '?':
+ // Kitty keyboard protocol query response.
+ if (input->waiting_for_kkp_response) {
+ input->waiting_for_kkp_response = false;
+ input->key_encoding = kKeyEncodingKitty;
+ tui_set_key_encoding(input->tui_data);
+ }
+
break;
- } else if (c == '\x1b') {
- eat_backslash = true;
- } else if (bad) {
- // ignore
- } else if ((c == '/') && (++component < num_components)) {
- // work done in condition
- } else if (ascii_isxdigit(c)) {
- if (component < 3 && rgb_max[component] != 0xffff) {
- rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf);
- rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c));
+ }
+ break;
+ case 'c':
+ switch (initial) {
+ case '?':
+ // Primary Device Attributes response
+ if (input->waiting_for_kkp_response) {
+ input->waiting_for_kkp_response = false;
+
+ // Enable the fallback key encoding (if any)
+ tui_set_key_encoding(input->tui_data);
}
- } else {
- bad = true;
+
+ break;
}
+ break;
+ default:
+ break;
}
- if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) {
- rbuffer_consumed(input->read_stream.buffer, count);
- double r = (double)rgb[0] / (double)rgb_max[0];
- double g = (double)rgb[1] / (double)rgb_max[1];
- double b = (double)rgb[2] / (double)rgb_max[2];
- double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601
- bool is_dark = luminance < 0.5;
- char *bgvalue = is_dark ? "dark" : "light";
- DLOG("bg response: %s", bgvalue);
- ui_client_bg_response = is_dark ? kTrue : kFalse;
- set_bg(bgvalue);
- input->waiting_for_bg_response = 0;
- } else if (!done && !bad) {
- // An incomplete sequence was found, waiting for the next input.
- return kIncomplete;
- } else {
- input->waiting_for_bg_response = 0;
- rbuffer_consumed(input->read_stream.buffer, count);
- DLOG("failed to parse bg response");
- return kNotApplicable;
- }
- return kComplete;
-}
-#ifdef UNIT_TESTING
-HandleState ut_handle_background_color(TermInput *input)
-{
- return handle_background_color(input);
}
-#endif
static void handle_raw_buffer(TermInput *input, bool force)
{
HandleState is_paste = kNotApplicable;
- HandleState is_bc = kNotApplicable;
do {
if (!force
&& (handle_focus_event(input)
- || (is_paste = handle_bracketed_paste(input)) != kNotApplicable
- || (is_bc = handle_background_color(input)) != kNotApplicable)) {
- if (is_paste == kIncomplete || is_bc == kIncomplete) {
+ || (is_paste = handle_bracketed_paste(input)) != kNotApplicable)) {
+ if (is_paste == kIncomplete) {
// Wait for the next input, leaving it in the raw buffer due to an
// incomplete sequence.
return;
@@ -724,9 +694,9 @@ static void handle_raw_buffer(TermInput *input, bool force)
RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) {
size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len));
// termkey_push_bytes can return (size_t)-1, so it is possible that
- // `consumed > input->read_stream.buffer->size`, but since tk_getkeys is
+ // `consumed > rbuffer_size(input->read_stream.buffer)`, but since tk_getkeys is
// called soon, it shouldn't happen.
- assert(consumed <= input->read_stream.buffer->size);
+ assert(consumed <= rbuffer_size(input->read_stream.buffer));
rbuffer_consumed(input->read_stream.buffer, consumed);
// Process the keys now: there is no guarantee `count` will
// fit into libtermkey's input buffer.
@@ -748,15 +718,15 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, void *da
}
handle_raw_buffer(input, false);
- tinput_flush(input, true);
+ tinput_flush(input);
// An incomplete sequence was found. Leave it in the raw buffer and wait for
// the next input.
if (rbuffer_size(input->read_stream.buffer)) {
// If 'ttimeout' is not set, start the timer with a timeout of 0 to process
// the next input.
- long ms = input->ttimeout ?
- (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0;
+ int64_t ms = input->ttimeout
+ ? (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0;
// Stop the current timer if already running
time_watcher_stop(&input->timer_handle);
time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0);
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index 5df108b107..2743a5e286 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_TUI_INPUT_H
-#define NVIM_TUI_INPUT_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
@@ -10,48 +9,43 @@
#include "nvim/event/stream.h"
#include "nvim/event/time.h"
#include "nvim/rbuffer.h"
-#include "nvim/tui/input_defs.h"
+#include "nvim/tui/input_defs.h" // IWYU pragma: export
#include "nvim/tui/tui.h"
+#include "nvim/types_defs.h"
typedef enum {
- kExtkeysNone,
- kExtkeysCSIu,
- kExtkeysXterm,
-} ExtkeysType;
+ kKeyEncodingLegacy, ///< Legacy key encoding
+ kKeyEncodingKitty, ///< Kitty keyboard protocol encoding
+ kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS)
+} KeyEncoding;
-typedef struct term_input {
+typedef struct {
int in_fd;
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
int8_t paste;
- bool waiting;
bool ttimeout;
- int8_t waiting_for_bg_response;
- int8_t waiting_for_csiu_response;
- ExtkeysType extkeys_type;
- long ttimeoutlen;
+
+ bool waiting_for_kkp_response; ///< True if we are expecting to receive a response to a query for
+ ///< Kitty keyboard protocol support
+
+ KeyEncoding key_encoding; ///< The key encoding used by the terminal emulator
+
+ OptInt ttimeoutlen;
TermKey *tk;
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
TimeWatcher timer_handle;
Loop *loop;
Stream read_stream;
RBuffer *key_buffer;
- uv_mutex_t key_buffer_mutex;
- uv_cond_t key_buffer_cond;
TUIData *tui_data;
} TermInput;
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "tui/input.h.generated.h"
-#endif
-
-#ifdef UNIT_TESTING
typedef enum {
kIncomplete = -1,
kNotApplicable = 0,
kComplete = 1,
} HandleState;
-HandleState ut_handle_background_color(TermInput *input);
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/input.h.generated.h"
#endif
-
-#endif // NVIM_TUI_INPUT_H
diff --git a/src/nvim/tui/input_defs.h b/src/nvim/tui/input_defs.h
index 846cf45350..9aea8460c8 100644
--- a/src/nvim/tui/input_defs.h
+++ b/src/nvim/tui/input_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_TUI_INPUT_DEFS_H
-#define NVIM_TUI_INPUT_DEFS_H
+#pragma once
typedef enum {
KITTY_KEY_ESCAPE = 57344,
@@ -114,5 +113,3 @@ typedef enum {
KITTY_KEY_ISO_LEVEL3_SHIFT = 57453,
KITTY_KEY_ISO_LEVEL5_SHIFT = 57454,
} KittyKey;
-
-#endif // NVIM_TUI_INPUT_DEFS_H
diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c
index d630a34ce2..3cf9650428 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Built-in fallback terminfo entries.
#include <stdbool.h>
@@ -10,7 +7,7 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/memory.h"
#include "nvim/strings.h"
diff --git a/src/nvim/tui/terminfo.h b/src/nvim/tui/terminfo.h
index 178d384457..039c722d25 100644
--- a/src/nvim/tui/terminfo.h
+++ b/src/nvim/tui/terminfo.h
@@ -1,12 +1,9 @@
-#ifndef NVIM_TUI_TERMINFO_H
-#define NVIM_TUI_TERMINFO_H
+#pragma once
-#include <unibilium.h>
+#include <unibilium.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/terminfo.h.generated.h"
#endif
-
-#endif // NVIM_TUI_TERMINFO_H
diff --git a/src/nvim/tui/terminfo_defs.h b/src/nvim/tui/terminfo_defs.h
index 0bc4972dd3..e221e80d1e 100644
--- a/src/nvim/tui/terminfo_defs.h
+++ b/src/nvim/tui/terminfo_defs.h
@@ -1,14 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// uncrustify:off
-//
-// Generated by scripts/update_terminfo.sh and ncurses 6.3.20211021
-//
+// Generated by scripts/update_terminfo.sh and ncurses 6.4.20221231
-#ifndef NVIM_TUI_TERMINFO_DEFS_H
-#define NVIM_TUI_TERMINFO_DEFS_H
+#pragma once
#include <stdint.h>
@@ -2309,4 +2303,3 @@ static const int8_t win32con_terminfo[] = {
static const int8_t xterm_256colour_terminfo[] = {
30,2,37,0,38,0,15,0,-99,1,51,6,120,116,101,114,109,45,50,53,54,99,111,108,111,114,124,120,116,101,114,109,32,119,105,116,104,32,50,53,54,32,99,111,108,111,114,115,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1,0,80,0,0,0,8,0,0,0,24,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,0,0,0,0,1,0,0,0,4,0,6,0,8,0,25,0,30,0,38,0,42,0,46,0,-1,-1,57,0,74,0,76,0,80,0,87,0,-1,-1,89,0,102,0,-1,-1,106,0,110,0,120,0,124,0,-1,-1,-1,-1,-128,0,-124,0,-119,0,-114,0,-1,-1,-96,0,-91,0,-86,0,-1,-1,-81,0,-76,0,-71,0,-66,0,-57,0,-53,0,-46,0,-1,-1,-28,0,-23,0,-17,0,-11,0,-1,-1,-1,-1,-1,-1,7,1,-1,-1,-1,-1,-1,-1,25,1,-1,-1,29,1,-1,-1,-1,-1,-1,-1,31,1,-1,-1,36,1,-1,-1,-1,-1,-1,-1,-1,-1,40,1,44,1,50,1,54,1,58,1,62,1,68,1,74,1,80,1,86,1,92,1,96,1,-1,-1,101,1,-1,-1,105,1,110,1,115,1,119,1,126,1,-1,-1,-123,1,-119,1,-111,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-103,1,-94,1,-85,1,-1,-1,-82,1,-73,1,-64,1,-55,1,-46,1,-37,1,-28,1,-19,1,-10,1,-1,1,-1,-1,-1,-1,-1,-1,8,2,12,2,17,2,22,2,42,2,51,2,-1,-1,-1,-1,69,2,72,2,83,2,86,2,88,2,91,2,-72,2,-1,-1,-69,2,-1,-1,-1,-1,-1,-1,-1,-1,-67,2,-63,2,-59,2,-55,2,-51,2,-1,-1,-1,-1,-47,2,-1,-1,6,3,-1,-1,-1,-1,10,3,16,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,22,3,26,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,30,3,-1,-1,-1,-1,37,3,-1,-1,-1,-1,-1,-1,-1,-1,44,3,51,3,58,3,-1,-1,-1,-1,65,3,-1,-1,72,3,-1,-1,-1,-1,-1,-1,79,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,3,92,3,98,3,105,3,112,3,119,3,126,3,-122,3,-114,3,-106,3,-98,3,-90,3,-82,3,-74,3,-66,3,-59,3,-52,3,-45,3,-38,3,-30,3,-22,3,-14,3,-6,3,2,4,10,4,18,4,26,4,33,4,40,4,47,4,54,4,62,4,70,4,78,4,86,4,94,4,102,4,110,4,118,4,125,4,-124,4,-117,4,-110,4,-102,4,-94,4,-86,4,-78,4,-70,4,-62,4,-54,4,-46,4,-39,4,-32,4,-25,4,-20,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-13,4,-2,4,3,5,22,5,26,5,35,5,42,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-120,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-115,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-109,5,-1,-1,-1,-1,-1,-1,-105,5,-42,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,22,6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,45,6,48,6,27,91,90,0,7,0,13,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,114,0,27,91,51,103,0,27,91,72,27,91,50,74,0,27,91,75,0,27,91,74,0,27,91,37,105,37,112,49,37,100,71,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,72,0,10,0,27,91,72,0,27,91,63,50,53,108,0,8,0,27,91,63,49,50,108,27,91,63,50,53,104,0,27,91,67,0,27,91,65,0,27,91,63,49,50,59,50,53,104,0,27,91,80,0,27,91,77,0,27,40,48,0,27,91,53,109,0,27,91,49,109,0,27,91,63,49,48,52,57,104,27,91,50,50,59,48,59,48,116,0,27,91,50,109,0,27,91,52,104,0,27,91,56,109,0,27,91,55,109,0,27,91,55,109,0,27,91,52,109,0,27,91,37,112,49,37,100,88,0,27,40,66,0,27,40,66,27,91,109,0,27,91,63,49,48,52,57,108,27,91,50,51,59,48,59,48,116,0,27,91,52,108,0,27,91,50,55,109,0,27,91,50,52,109,0,27,91,63,53,104,36,60,49,48,48,47,62,27,91,63,53,108,0,27,91,33,112,27,91,63,51,59,52,108,27,91,52,108,27,62,0,27,91,76,0,8,0,27,91,51,126,0,27,79,66,0,27,79,80,0,27,91,50,49,126,0,27,79,81,0,27,79,82,0,27,79,83,0,27,91,49,53,126,0,27,91,49,55,126,0,27,91,49,56,126,0,27,91,49,57,126,0,27,91,50,48,126,0,27,79,72,0,27,91,50,126,0,27,79,68,0,27,91,54,126,0,27,91,53,126,0,27,79,67,0,27,91,49,59,50,66,0,27,91,49,59,50,65,0,27,79,65,0,27,91,63,49,108,27,62,0,27,91,63,49,104,27,61,0,27,91,63,49,48,51,52,108,0,27,91,63,49,48,51,52,104,0,27,69,0,27,91,37,112,49,37,100,80,0,27,91,37,112,49,37,100,77,0,27,91,37,112,49,37,100,66,0,27,91,37,112,49,37,100,64,0,27,91,37,112,49,37,100,83,0,27,91,37,112,49,37,100,76,0,27,91,37,112,49,37,100,68,0,27,91,37,112,49,37,100,67,0,27,91,37,112,49,37,100,84,0,27,91,37,112,49,37,100,65,0,27,91,105,0,27,91,52,105,0,27,91,53,105,0,37,112,49,37,99,27,91,37,112,50,37,123,49,125,37,45,37,100,98,0,27,99,27,93,49,48,52,7,0,27,91,33,112,27,91,63,51,59,52,108,27,91,52,108,27,62,0,27,56,0,27,91,37,105,37,112,49,37,100,100,0,27,55,0,10,0,27,77,0,37,63,37,112,57,37,116,27,40,48,37,101,27,40,66,37,59,27,91,48,37,63,37,112,54,37,116,59,49,37,59,37,63,37,112,53,37,116,59,50,37,59,37,63,37,112,50,37,116,59,52,37,59,37,63,37,112,49,37,112,51,37,124,37,116,59,55,37,59,37,63,37,112,52,37,116,59,53,37,59,37,63,37,112,55,37,116,59,56,37,59,109,0,27,72,0,9,0,27,79,119,0,27,79,121,0,27,79,117,0,27,79,113,0,27,79,115,0,96,96,97,97,102,102,103,103,105,105,106,106,107,107,108,108,109,109,110,110,111,111,112,112,113,113,114,114,115,115,116,116,117,117,118,118,119,119,120,120,121,121,122,122,123,123,124,124,125,125,126,126,0,27,91,90,0,27,91,63,55,104,0,27,91,63,55,108,0,27,79,70,0,27,79,77,0,27,91,51,59,50,126,0,27,91,49,59,50,70,0,27,91,49,59,50,72,0,27,91,50,59,50,126,0,27,91,49,59,50,68,0,27,91,54,59,50,126,0,27,91,53,59,50,126,0,27,91,49,59,50,67,0,27,91,50,51,126,0,27,91,50,52,126,0,27,91,49,59,50,80,0,27,91,49,59,50,81,0,27,91,49,59,50,82,0,27,91,49,59,50,83,0,27,91,49,53,59,50,126,0,27,91,49,55,59,50,126,0,27,91,49,56,59,50,126,0,27,91,49,57,59,50,126,0,27,91,50,48,59,50,126,0,27,91,50,49,59,50,126,0,27,91,50,51,59,50,126,0,27,91,50,52,59,50,126,0,27,91,49,59,53,80,0,27,91,49,59,53,81,0,27,91,49,59,53,82,0,27,91,49,59,53,83,0,27,91,49,53,59,53,126,0,27,91,49,55,59,53,126,0,27,91,49,56,59,53,126,0,27,91,49,57,59,53,126,0,27,91,50,48,59,53,126,0,27,91,50,49,59,53,126,0,27,91,50,51,59,53,126,0,27,91,50,52,59,53,126,0,27,91,49,59,54,80,0,27,91,49,59,54,81,0,27,91,49,59,54,82,0,27,91,49,59,54,83,0,27,91,49,53,59,54,126,0,27,91,49,55,59,54,126,0,27,91,49,56,59,54,126,0,27,91,49,57,59,54,126,0,27,91,50,48,59,54,126,0,27,91,50,49,59,54,126,0,27,91,50,51,59,54,126,0,27,91,50,52,59,54,126,0,27,91,49,59,51,80,0,27,91,49,59,51,81,0,27,91,49,59,51,82,0,27,91,49,59,51,83,0,27,91,49,53,59,51,126,0,27,91,49,55,59,51,126,0,27,91,49,56,59,51,126,0,27,91,49,57,59,51,126,0,27,91,50,48,59,51,126,0,27,91,50,49,59,51,126,0,27,91,50,51,59,51,126,0,27,91,50,52,59,51,126,0,27,91,49,59,52,80,0,27,91,49,59,52,81,0,27,91,49,59,52,82,0,27,91,49,75,0,27,91,63,54,57,108,0,27,91,37,105,37,100,59,37,100,82,0,27,91,54,110,0,27,91,63,37,91,59,48,49,50,51,52,53,54,55,56,57,93,99,0,27,91,99,0,27,91,51,57,59,52,57,109,0,27,93,49,48,52,7,0,27,93,52,59,37,112,49,37,100,59,114,103,98,58,37,112,50,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,51,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,52,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,27,92,0,27,91,51,109,0,27,91,50,51,109,0,27,91,60,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,51,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,57,37,112,49,37,123,56,125,37,45,37,100,37,101,51,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,52,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,49,48,37,112,49,37,123,56,125,37,45,37,100,37,101,52,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,63,54,57,104,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,115,0,27,108,0,27,109,0,0,2,0,0,0,74,0,-106,0,-84,3,1,1,0,0,7,0,19,0,24,0,42,0,48,0,58,0,90,0,97,0,104,0,111,0,118,0,125,0,-124,0,-117,0,-110,0,-103,0,-96,0,-89,0,-82,0,-75,0,-68,0,-61,0,-54,0,-47,0,-40,0,-33,0,-26,0,-19,0,-12,0,-5,0,2,1,9,1,16,1,23,1,30,1,37,1,44,1,51,1,58,1,65,1,72,1,79,1,86,1,93,1,100,1,107,1,114,1,121,1,-128,1,-121,1,-114,1,-107,1,-100,1,-93,1,-86,1,-79,1,-72,1,-65,1,-58,1,-54,1,-50,1,-46,1,-42,1,-38,1,-34,1,-30,1,-26,1,-22,1,-18,1,-14,1,-10,1,-4,1,1,2,0,0,3,0,6,0,9,0,12,0,15,0,18,0,21,0,24,0,27,0,32,0,37,0,42,0,47,0,52,0,56,0,61,0,66,0,71,0,76,0,81,0,87,0,93,0,99,0,105,0,111,0,117,0,123,0,-127,0,-121,0,-115,0,-110,0,-105,0,-100,0,-95,0,-90,0,-84,0,-78,0,-72,0,-66,0,-60,0,-54,0,-48,0,-42,0,-36,0,-30,0,-24,0,-18,0,-12,0,-6,0,0,1,6,1,12,1,18,1,24,1,30,1,34,1,39,1,44,1,49,1,54,1,59,1,63,1,67,1,71,1,75,1,79,1,85,1,91,1,97,1,103,1,109,1,115,1,121,1,126,1,-125,1,27,93,49,49,50,7,0,27,93,49,50,59,37,112,49,37,115,7,0,27,91,51,74,0,27,93,53,50,59,37,112,49,37,115,59,37,112,50,37,115,7,0,27,91,50,32,113,0,27,91,37,112,49,37,100,32,113,0,27,91,63,49,48,48,54,59,49,48,48,48,37,63,37,112,49,37,123,49,125,37,61,37,116,104,37,101,108,37,59,0,27,91,51,59,51,126,0,27,91,51,59,52,126,0,27,91,51,59,53,126,0,27,91,51,59,54,126,0,27,91,51,59,55,126,0,27,91,49,59,50,66,0,27,91,49,59,51,66,0,27,91,49,59,52,66,0,27,91,49,59,53,66,0,27,91,49,59,54,66,0,27,91,49,59,55,66,0,27,91,49,59,51,70,0,27,91,49,59,52,70,0,27,91,49,59,53,70,0,27,91,49,59,54,70,0,27,91,49,59,55,70,0,27,91,49,59,51,72,0,27,91,49,59,52,72,0,27,91,49,59,53,72,0,27,91,49,59,54,72,0,27,91,49,59,55,72,0,27,91,50,59,51,126,0,27,91,50,59,52,126,0,27,91,50,59,53,126,0,27,91,50,59,54,126,0,27,91,50,59,55,126,0,27,91,49,59,51,68,0,27,91,49,59,52,68,0,27,91,49,59,53,68,0,27,91,49,59,54,68,0,27,91,49,59,55,68,0,27,91,54,59,51,126,0,27,91,54,59,52,126,0,27,91,54,59,53,126,0,27,91,54,59,54,126,0,27,91,54,59,55,126,0,27,91,53,59,51,126,0,27,91,53,59,52,126,0,27,91,53,59,53,126,0,27,91,53,59,54,126,0,27,91,53,59,55,126,0,27,91,49,59,51,67,0,27,91,49,59,52,67,0,27,91,49,59,53,67,0,27,91,49,59,54,67,0,27,91,49,59,55,67,0,27,91,49,59,50,65,0,27,91,49,59,51,65,0,27,91,49,59,52,65,0,27,91,49,59,53,65,0,27,91,49,59,54,65,0,27,91,49,59,55,65,0,27,79,120,0,27,79,116,0,27,79,118,0,27,79,114,0,27,79,69,0,27,79,107,0,27,79,108,0,27,79,111,0,27,79,110,0,27,79,106,0,27,79,109,0,27,79,112,0,27,91,50,57,109,0,27,91,57,109,0,27,91,60,37,105,37,112,51,37,100,59,37,112,49,37,100,59,37,112,50,37,100,59,37,63,37,112,52,37,116,77,37,101,109,37,59,0,65,88,0,88,84,0,67,114,0,67,115,0,69,51,0,77,115,0,83,101,0,83,115,0,88,77,0,107,68,67,51,0,107,68,67,52,0,107,68,67,53,0,107,68,67,54,0,107,68,67,55,0,107,68,78,0,107,68,78,51,0,107,68,78,52,0,107,68,78,53,0,107,68,78,54,0,107,68,78,55,0,107,69,78,68,51,0,107,69,78,68,52,0,107,69,78,68,53,0,107,69,78,68,54,0,107,69,78,68,55,0,107,72,79,77,51,0,107,72,79,77,52,0,107,72,79,77,53,0,107,72,79,77,54,0,107,72,79,77,55,0,107,73,67,51,0,107,73,67,52,0,107,73,67,53,0,107,73,67,54,0,107,73,67,55,0,107,76,70,84,51,0,107,76,70,84,52,0,107,76,70,84,53,0,107,76,70,84,54,0,107,76,70,84,55,0,107,78,88,84,51,0,107,78,88,84,52,0,107,78,88,84,53,0,107,78,88,84,54,0,107,78,88,84,55,0,107,80,82,86,51,0,107,80,82,86,52,0,107,80,82,86,53,0,107,80,82,86,54,0,107,80,82,86,55,0,107,82,73,84,51,0,107,82,73,84,52,0,107,82,73,84,53,0,107,82,73,84,54,0,107,82,73,84,55,0,107,85,80,0,107,85,80,51,0,107,85,80,52,0,107,85,80,53,0,107,85,80,54,0,107,85,80,55,0,107,97,50,0,107,98,49,0,107,98,51,0,107,99,50,0,107,112,53,0,107,112,65,68,68,0,107,112,67,77,65,0,107,112,68,73,86,0,107,112,68,79,84,0,107,112,77,85,76,0,107,112,83,85,66,0,107,112,90,82,79,0,114,109,120,120,0,115,109,120,120,0,120,109,0
};
-#endif // NVIM_TUI_TERMINFO_DEFS_H
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index a50e44f7a3..197bbcabb5 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Terminal UI functions. Invoked (by ui_client.c) on the UI process.
#include <assert.h>
@@ -16,33 +13,35 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/cursor_shape.h"
-#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
-#include "nvim/event/multiqueue.h"
#include "nvim/event/signal.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
-#include "nvim/ui_client.h"
-#ifdef MSWIN
-# include "nvim/os/os_win_console.h"
-#endif
#include "nvim/tui/input.h"
#include "nvim/tui/terminfo.h"
#include "nvim/tui/tui.h"
+#include "nvim/types_defs.h"
#include "nvim/ugrid.h"
#include "nvim/ui.h"
+#include "nvim/ui_client.h"
+
+#ifdef MSWIN
+# include "nvim/os/os_win_console.h"
+# include "nvim/os/tty.h"
+#endif
// Space reserved in two output buffers to make the cursor normal or invisible
// when flushing. No existing terminal will require 32 bytes to do that.
@@ -58,27 +57,14 @@
#define LINUXSET0C "\x1b[?0c"
#define LINUXSET1C "\x1b[?1c"
-#ifdef NVIM_UNIBI_HAS_VAR_FROM
-# define UNIBI_SET_NUM_VAR(var, num) \
+#define UNIBI_SET_NUM_VAR(var, num) \
do { \
(var) = unibi_var_from_num((num)); \
} while (0)
-# define UNIBI_SET_STR_VAR(var, str) \
+#define UNIBI_SET_STR_VAR(var, str) \
do { \
(var) = unibi_var_from_str((str)); \
} while (0)
-#else
-# define UNIBI_SET_NUM_VAR(var, num) \
- do { \
- (var).p = NULL; \
- (var).i = (num); \
- } while (0)
-# define UNIBI_SET_STR_VAR(var, str) \
- do { \
- (var).i = INT_MIN; \
- (var).p = str; \
- } while (0)
-#endif
typedef struct {
int top, bot, left, right;
@@ -89,9 +75,6 @@ struct TUIData {
unibi_var_t params[9];
char buf[OUTBUF_SIZE];
size_t bufpos;
- char norm[CNORM_COMMAND_MAX_SIZE];
- char invis[CNORM_COMMAND_MAX_SIZE];
- size_t normlen, invislen;
TermInput input;
uv_loop_t write_loop;
unibi_term *ut;
@@ -117,6 +100,8 @@ struct TUIData {
bool bce;
bool mouse_enabled;
bool mouse_move_enabled;
+ bool title_enabled;
+ bool sync_output;
bool busy, is_invisible, want_invisible;
bool cork, overflow;
bool set_cursor_color_as_str;
@@ -146,14 +131,13 @@ struct TUIData {
int reset_scroll_region;
int set_cursor_style, reset_cursor_style;
int save_title, restore_title;
- int get_bg;
int set_underline_style;
int set_underline_color;
- int enable_extended_keys, disable_extended_keys;
- int get_extkeys;
+ int sync;
} unibi_ext;
char *space_buf;
bool stopped;
+ int seen_error_exit;
int width;
int height;
bool rgb;
@@ -165,12 +149,13 @@ static bool cursor_style_enabled = false;
# include "tui/tui.c.generated.h"
#endif
-TUIData *tui_start(int *width, int *height, char **term)
+void tui_start(TUIData **tui_p, int *width, int *height, char **term)
{
TUIData *tui = xcalloc(1, sizeof(TUIData));
tui->is_starting = true;
tui->screenshot = NULL;
tui->stopped = false;
+ tui->seen_error_exit = 0;
tui->loop = &main_loop;
kv_init(tui->invalid_regions);
signal_watcher_init(tui->loop, &tui->winch_handle, tui);
@@ -188,45 +173,92 @@ TUIData *tui_start(int *width, int *height, char **term)
uv_timer_start(&tui->startup_delay_timer, after_startup_cb,
100, 0);
+ *tui_p = tui;
loop_poll_events(&main_loop, 1);
*width = tui->width;
*height = tui->height;
*term = tui->term;
- return tui;
}
-void tui_enable_extkeys(TUIData *tui)
+void tui_set_key_encoding(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
{
- TermInput input = tui->input;
- unibi_term *ut = tui->ut;
+ switch (tui->input.key_encoding) {
+ case kKeyEncodingKitty:
+ out(tui, S_LEN("\x1b[>1u"));
+ break;
+ case kKeyEncodingXterm:
+ out(tui, S_LEN("\x1b[>4;2m"));
+ break;
+ case kKeyEncodingLegacy:
+ break;
+ }
+}
- switch (input.extkeys_type) {
- case kExtkeysCSIu:
- tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys",
- "\x1b[>1u");
- tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys",
- "\x1b[<1u");
+static void tui_reset_key_encoding(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (tui->input.key_encoding) {
+ case kKeyEncodingKitty:
+ out(tui, S_LEN("\x1b[<1u"));
break;
- case kExtkeysXterm:
- tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys",
- "\x1b[>4;2m");
- tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys",
- "\x1b[>4;0m");
+ case kKeyEncodingXterm:
+ out(tui, S_LEN("\x1b[>4;0m"));
break;
- default:
+ case kKeyEncodingLegacy:
break;
}
+}
- unibi_out_ext(tui, tui->unibi_ext.enable_extended_keys);
+/// Request the terminal's mode (DECRQM).
+///
+/// @see handle_modereport
+static void tui_request_term_mode(TUIData *tui, TermMode mode)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // 5 bytes for \x1b[?$p, 1 byte for null terminator, 6 bytes for mode digits (more than enough)
+ char buf[12];
+ int len = snprintf(buf, sizeof(buf), "\x1b[?%d$p", (int)mode);
+ assert((len > 0) && (len < (int)sizeof(buf)));
+ out(tui, buf, (size_t)len);
}
-static size_t unibi_pre_fmt_str(TUIData *tui, unsigned int unibi_index, char *buf, size_t len)
+/// Handle a mode report (DECRPM) from the terminal.
+void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
+ FUNC_ATTR_NONNULL_ALL
{
- const char *str = unibi_get_str(tui->ut, unibi_index);
- if (!str) {
- return 0U;
+ switch (state) {
+ case kTermModeNotRecognized:
+ case kTermModePermanentlySet:
+ case kTermModePermanentlyReset:
+ // If the mode is not recognized, or if the terminal emulator does not allow it to be changed,
+ // then there is nothing to do
+ break;
+ case kTermModeSet:
+ case kTermModeReset:
+ // The terminal supports changing the given mode
+ switch (mode) {
+ case kTermModeSynchronizedOutput:
+ // Ref: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
+ tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync",
+ "\x1b[?2026%?%p1%{1}%-%tl%eh%;");
+ }
}
- return unibi_run(str, tui->params, buf, len);
+}
+
+/// Query the terminal emulator to see if it supports Kitty's keyboard protocol.
+///
+/// Write CSI ? u followed by a primary device attributes request (CSI c). If
+/// a primary device attributes response is received without first receiving an
+/// answer to the progressive enhancement query (CSI u), then the terminal does
+/// not support the Kitty keyboard protocol.
+///
+/// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol
+static void tui_query_kitty_keyboard(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ tui->input.waiting_for_kkp_response = true;
+ out(tui, S_LEN("\x1b[?u\x1b[c"));
}
static void terminfo_start(TUIData *tui)
@@ -261,11 +293,8 @@ static void terminfo_start(TUIData *tui)
tui->unibi_ext.reset_scroll_region = -1;
tui->unibi_ext.set_cursor_style = -1;
tui->unibi_ext.reset_cursor_style = -1;
- tui->unibi_ext.get_bg = -1;
tui->unibi_ext.set_underline_color = -1;
- tui->unibi_ext.enable_extended_keys = -1;
- tui->unibi_ext.disable_extended_keys = -1;
- tui->unibi_ext.get_extkeys = -1;
+ tui->unibi_ext.sync = -1;
tui->out_fd = STDOUT_FILENO;
tui->out_isatty = os_isatty(tui->out_fd);
tui->input.tui_data = tui;
@@ -295,7 +324,7 @@ static void terminfo_start(TUIData *tui)
const char *colorterm = os_getenv("COLORTERM");
const char *termprg = os_getenv("TERM_PROGRAM");
const char *vte_version_env = os_getenv("VTE_VERSION");
- long vtev = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0;
+ int vtev = vte_version_env ? (int)strtol(vte_version_env, NULL, 10) : 0;
bool iterm_env = termprg && strstr(termprg, "iTerm.app");
bool nsterm = (termprg && strstr(termprg, "Apple_Terminal"))
|| terminfo_is_term_family(term, "nsterm");
@@ -303,8 +332,8 @@ static void terminfo_start(TUIData *tui)
|| os_getenv("KONSOLE_PROFILE_NAME")
|| os_getenv("KONSOLE_DBUS_SESSION");
const char *konsolev_env = os_getenv("KONSOLE_VERSION");
- long konsolev = konsolev_env ? strtol(konsolev_env, NULL, 10)
- : (konsole ? 1 : 0);
+ int konsolev = konsolev_env ? (int)strtol(konsolev_env, NULL, 10)
+ : (konsole ? 1 : 0);
patch_terminfo_bugs(tui, term, colorterm, vtev, konsolev, iterm_env, nsterm);
augment_terminfo(tui, term, vtev, konsolev, iterm_env, nsterm);
@@ -327,28 +356,24 @@ static void terminfo_start(TUIData *tui)
|| terminfo_is_term_family(term, "win32con")
|| terminfo_is_term_family(term, "interix");
tui->bce = unibi_get_bool(tui->ut, unibi_back_color_erase);
- tui->normlen = unibi_pre_fmt_str(tui, unibi_cursor_normal,
- tui->norm, sizeof tui->norm);
- tui->invislen = unibi_pre_fmt_str(tui, unibi_cursor_invisible,
- tui->invis, sizeof tui->invis);
// Set 't_Co' from the result of unibilium & fix_terminfo.
t_colors = unibi_get_num(tui->ut, unibi_max_colors);
// Enter alternate screen, save title, and clear.
// NOTE: Do this *before* changing terminal settings. #6433
unibi_out(tui, unibi_enter_ca_mode);
- // Save title/icon to the "stack". #4063
- unibi_out_ext(tui, tui->unibi_ext.save_title);
unibi_out(tui, unibi_keypad_xmit);
unibi_out(tui, unibi_clear_screen);
- // Ask the terminal to send us the background color.
- tui->input.waiting_for_bg_response = 5;
- unibi_out_ext(tui, tui->unibi_ext.get_bg);
+
// Enable bracketed paste
unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste);
- // Query the terminal to see if it supports CSI u
- tui->input.waiting_for_csiu_response = 5;
- unibi_out_ext(tui, tui->unibi_ext.get_extkeys);
+ // Query support for mode 2026 (Synchronized Output). Some terminals also
+ // support an older DCS sequence for synchronized output, but we will only use
+ // mode 2026
+ tui_request_term_mode(tui, kTermModeSynchronizedOutput);
+
+ // Query the terminal to see if it supports Kitty's keyboard protocol
+ tui_query_kitty_keyboard(tui);
int ret;
uv_loop_init(&tui->write_loop);
@@ -357,12 +382,7 @@ static void terminfo_start(TUIData *tui)
if (ret) {
ELOG("uv_tty_init failed: %s", uv_strerror(ret));
}
-#ifdef MSWIN
- ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_RAW);
- if (ret) {
- ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret));
- }
-#else
+#ifndef MSWIN
int retry_count = 10;
// A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a
// few times. #12322
@@ -396,11 +416,22 @@ static void terminfo_stop(TUIData *tui)
// Reset cursor to normal before exiting alternate screen.
unibi_out(tui, unibi_cursor_normal);
unibi_out(tui, unibi_keypad_local);
- // Disable extended keys before exiting alternate screen.
- unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys);
- unibi_out(tui, unibi_exit_ca_mode);
- // Restore title/icon from the "stack". #4063
- unibi_out_ext(tui, tui->unibi_ext.restore_title);
+
+ // Reset the key encoding
+ tui_reset_key_encoding(tui);
+
+ // May restore old title before exiting alternate screen.
+ tui_set_title(tui, (String)STRING_INIT);
+ if (ui_client_exit_status == 0) {
+ ui_client_exit_status = tui->seen_error_exit;
+ }
+ // if nvim exited with nonzero status, without indicated this was an
+ // intentional exit (like `:1cquit`), it likely was an internal failure.
+ // Don't clobber the stderr error message in this case.
+ if (ui_client_exit_status == tui->seen_error_exit) {
+ // Exit alternate screen.
+ unibi_out(tui, unibi_exit_ca_mode);
+ }
if (tui->cursor_color_changed) {
unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color);
}
@@ -408,6 +439,11 @@ static void terminfo_stop(TUIData *tui)
unibi_out_ext(tui, tui->unibi_ext.disable_bracketed_paste);
// Disable focus reporting
unibi_out_ext(tui, tui->unibi_ext.disable_focus_reporting);
+
+ // Disable synchronized output
+ UNIBI_SET_NUM_VAR(tui->params[0], 0);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+
flush_buf(tui);
uv_tty_reset_mode();
uv_close((uv_handle_t *)&tui->output_handle, NULL);
@@ -445,7 +481,7 @@ static void tui_terminal_after_startup(TUIData *tui)
/// stop the terminal but allow it to restart later (like after suspend)
static void tui_terminal_stop(TUIData *tui)
{
- if (uv_is_closing(STRUCT_CAST(uv_handle_t, &tui->output_handle))) {
+ if (uv_is_closing((uv_handle_t *)&tui->output_handle)) {
// Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075
ELOG("TUI already stopped (race?)");
tui->stopped = true;
@@ -453,9 +489,16 @@ static void tui_terminal_stop(TUIData *tui)
}
tinput_stop(&tui->input);
signal_watcher_stop(&tui->winch_handle);
+ // Position the cursor on the last screen line, below all the text
+ cursor_goto(tui, tui->height - 1, 0);
terminfo_stop(tui);
}
+void tui_error_exit(TUIData *tui, Integer status)
+{
+ tui->seen_error_exit = (int)status;
+}
+
void tui_stop(TUIData *tui)
{
tui_terminal_stop(tui);
@@ -467,7 +510,7 @@ void tui_stop(TUIData *tui)
}
/// Returns true if UI `ui` is stopped.
-static bool tui_is_stopped(TUIData *tui)
+bool tui_is_stopped(TUIData *tui)
{
return tui->stopped;
}
@@ -689,15 +732,15 @@ static void final_column_wrap(TUIData *tui)
/// It is undocumented, but in the majority of terminals and terminal emulators
/// printing at the right margin does not cause an automatic wrap until the
/// next character is printed, holding the cursor in place until then.
-static void print_cell(TUIData *tui, UCell *ptr)
+static void print_cell(TUIData *tui, char *buf, sattr_T attr)
{
UGrid *grid = &tui->grid;
if (!tui->immediate_wrap_after_last_column) {
// Printing the next character finally advances the cursor.
final_column_wrap(tui);
}
- update_attrs(tui, ptr->attr);
- out(tui, ptr->data, strlen(ptr->data));
+ update_attrs(tui, attr);
+ out(tui, buf, strlen(buf));
grid->col++;
if (tui->immediate_wrap_after_last_column) {
// Printing at the right margin immediately advances the cursor.
@@ -717,8 +760,8 @@ static bool cheap_to_print(TUIData *tui, int row, int col, int next)
return false;
}
}
- if (strlen(cell->data) > 1) {
- return false;
+ if (schar_get_ascii(cell->data) == 0) {
+ return false; // not ascii
}
cell++;
}
@@ -748,11 +791,15 @@ static void cursor_goto(TUIData *tui, int row, int col)
if (grid->row == -1) {
goto safe_move;
}
- if (0 == col ? col != grid->col :
- row != grid->row ? false :
- 1 == col ? 2 < grid->col && cheap_to_print(tui, grid->row, 0, col) :
- 2 == col ? 5 < grid->col && cheap_to_print(tui, grid->row, 0, col) :
- false) {
+ if (0 == col
+ ? col != grid->col
+ : (row != grid->row
+ ? false
+ : (1 == col
+ ? (2 < grid->col && cheap_to_print(tui, grid->row, 0, col))
+ : (2 == col
+ ? (5 < grid->col && cheap_to_print(tui, grid->row, 0, col))
+ : false)))) {
// Motion to left margin from anywhere else, or CR + printing chars is
// even less expensive than using BSes or CUB.
unibi_out(tui, unibi_carriage_return);
@@ -845,14 +892,16 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
{
UGrid *grid = &tui->grid;
- if (grid->row == -1 && cell->data[0] == NUL) {
+ if (grid->row == -1 && cell->data == NUL) {
// If cursor needs to repositioned and there is nothing to print, don't move cursor.
return;
}
cursor_goto(tui, row, col);
- bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data));
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, cell->data);
+ bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf));
if (is_ambiwidth && is_doublewidth) {
// Clear the two screen cells.
// If the character is single-width in the host terminal it won't change the second cell.
@@ -861,7 +910,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
cursor_goto(tui, row, col);
}
- print_cell(tui, cell);
+ print_cell(tui, buf, cell->attr);
if (is_ambiwidth) {
// Force repositioning cursor after printing an ambiguous-width character.
@@ -990,8 +1039,10 @@ void tui_grid_clear(TUIData *tui, Integer g)
{
UGrid *grid = &tui->grid;
ugrid_clear(grid);
+ // safe to clear cache at this point
+ schar_cache_clear_if_full();
kv_size(tui->invalid_regions) = 0;
- clear_region(tui, 0, grid->height, 0, grid->width, 0);
+ clear_region(tui, 0, tui->height, 0, tui->width, 0);
}
void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col)
@@ -1001,7 +1052,7 @@ void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col)
tui->col = (int)col;
}
-CursorShape tui_cursor_decode_shape(const char *shape_str)
+static CursorShape tui_cursor_decode_shape(const char *shape_str)
{
CursorShape shape;
if (strequal(shape_str, "block")) {
@@ -1094,7 +1145,7 @@ void tui_mouse_off(TUIData *tui)
}
}
-void tui_set_mode(TUIData *tui, ModeShape mode)
+static void tui_set_mode(TUIData *tui, ModeShape mode)
{
if (!cursor_style_enabled) {
return;
@@ -1105,15 +1156,13 @@ void tui_set_mode(TUIData *tui, ModeShape mode)
HlAttrs aep = kv_A(tui->attrs, c.id);
tui->want_invisible = aep.hl_blend == 100;
- if (tui->want_invisible) {
- unibi_out(tui, unibi_cursor_invisible);
- } else if (aep.rgb_ae_attr & HL_INVERSE) {
+ if (!tui->want_invisible && aep.rgb_ae_attr & HL_INVERSE) {
// We interpret "inverse" as "default" (no termcode for "inverse"...).
// Hopefully the user's default cursor color is inverse.
unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color);
} else {
+ char hexbuf[8];
if (tui->set_cursor_color_as_str) {
- char hexbuf[8];
snprintf(hexbuf, 7 + 1, "#%06x", aep.rgb_bg_color);
UNIBI_SET_STR_VAR(tui->params[0], hexbuf);
} else {
@@ -1130,8 +1179,6 @@ void tui_set_mode(TUIData *tui, ModeShape mode)
int shape;
switch (c.shape) {
- default:
- abort(); break;
case SHAPE_BLOCK:
shape = 1; break;
case SHAPE_HOR:
@@ -1150,7 +1197,7 @@ void tui_mode_change(TUIData *tui, String mode, Integer mode_idx)
// If stdin is not a TTY, the LHS of pipe may change the state of the TTY
// after calling uv_tty_set_mode. So, set the mode of the TTY again here.
// #13073
- if (tui->is_starting && tui->input.in_fd == STDERR_FILENO) {
+ if (tui->is_starting && !stdin_isatty) {
int ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_NORMAL);
if (ret) {
ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret));
@@ -1171,9 +1218,8 @@ void tui_mode_change(TUIData *tui, String mode, Integer mode_idx)
tui->showing_mode = (ModeShape)mode_idx;
}
-void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, // -V751
- Integer endrow, Integer startcol, Integer endcol, Integer rows,
- Integer cols FUNC_ATTR_UNUSED)
+void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, Integer endrow, Integer startcol,
+ Integer endcol, Integer rows, Integer cols FUNC_ATTR_UNUSED)
{
UGrid *grid = &tui->grid;
int top = (int)startrow, bot = (int)endrow - 1;
@@ -1262,6 +1308,39 @@ void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Intege
invalidate(tui, 0, tui->grid.height, 0, tui->grid.width);
}
+/// Begin flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
+/// begin a synchronized update. Otherwise, hide the cursor to avoid cursor jumping.
+static void tui_flush_start(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(tui->params[0], 1);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+ } else if (!tui->is_invisible) {
+ unibi_out(tui, unibi_cursor_invisible);
+ tui->is_invisible = true;
+ }
+}
+
+/// Finish flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
+/// end a synchronized update. Otherwise, make the cursor visible again.
+static void tui_flush_end(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(tui->params[0], 0);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+ }
+ bool should_invisible = tui->busy || tui->want_invisible;
+ if (tui->is_invisible && !should_invisible) {
+ unibi_out(tui, unibi_cursor_normal);
+ tui->is_invisible = false;
+ } else if (!tui->is_invisible && should_invisible) {
+ unibi_out(tui, unibi_cursor_invisible);
+ tui->is_invisible = true;
+ }
+}
+
void tui_flush(TUIData *tui)
{
UGrid *grid = &tui->grid;
@@ -1278,6 +1357,8 @@ void tui_flush(TUIData *tui)
tui_busy_stop(tui); // avoid hidden cursor
}
+ tui_flush_start(tui);
+
while (kv_size(tui->invalid_regions)) {
Rect r = kv_pop(tui->invalid_regions);
assert(r.bot <= grid->height && r.right <= grid->width);
@@ -1287,7 +1368,7 @@ void tui_flush(TUIData *tui)
int clear_col;
for (clear_col = r.right; clear_col > 0; clear_col--) {
UCell *cell = &grid->cells[row][clear_col - 1];
- if (!(cell->data[0] == ' ' && cell->data[1] == NUL
+ if (!(cell->data == schar_from_ascii(' ')
&& cell->attr == clear_attr)) {
break;
}
@@ -1295,7 +1376,7 @@ void tui_flush(TUIData *tui)
UGRID_FOREACH_CELL(grid, row, r.left, clear_col, {
print_cell_at_pos(tui, row, curcol, cell,
- curcol < clear_col - 1 && (cell + 1)->data[0] == NUL);
+ curcol < clear_col - 1 && (cell + 1)->data == NUL);
});
if (clear_col < r.right) {
clear_region(tui, row, row + 1, clear_col, r.right, clear_attr);
@@ -1305,6 +1386,8 @@ void tui_flush(TUIData *tui)
cursor_goto(tui, tui->row, tui->col);
+ tui_flush_end(tui);
+
flush_buf(tui);
}
@@ -1318,16 +1401,16 @@ static void show_verbose_terminfo(TUIData *tui)
Array chunks = ARRAY_DICT_INIT;
Array title = ARRAY_DICT_INIT;
- ADD(title, STRING_OBJ(cstr_to_string("\n\n--- Terminal info --- {{{\n")));
- ADD(title, STRING_OBJ(cstr_to_string("Title")));
+ ADD(title, CSTR_TO_OBJ("\n\n--- Terminal info --- {{{\n"));
+ ADD(title, CSTR_TO_OBJ("Title"));
ADD(chunks, ARRAY_OBJ(title));
Array info = ARRAY_DICT_INIT;
String str = terminfo_info_msg(ut, tui->term);
ADD(info, STRING_OBJ(str));
ADD(chunks, ARRAY_OBJ(info));
Array end_fold = ARRAY_DICT_INIT;
- ADD(end_fold, STRING_OBJ(cstr_to_string("}}}\n")));
- ADD(end_fold, STRING_OBJ(cstr_to_string("Title")));
+ ADD(end_fold, CSTR_TO_OBJ("}}}\n"));
+ ADD(end_fold, CSTR_TO_OBJ("Title"));
ADD(chunks, ARRAY_OBJ(end_fold));
Array args = ARRAY_DICT_INIT;
@@ -1340,10 +1423,11 @@ static void show_verbose_terminfo(TUIData *tui)
api_free_array(args);
}
-#ifdef UNIX
-static void suspend_event(void **argv)
+void tui_suspend(TUIData *tui)
{
- TUIData *tui = argv[0];
+// on a non-UNIX system, this is a no-op
+#ifdef UNIX
+ ui_client_detach();
bool enable_mouse = tui->mouse_enabled;
tui_terminal_stop(tui);
stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598)
@@ -1356,34 +1440,35 @@ static void suspend_event(void **argv)
tui_mouse_on(tui);
}
stream_set_blocking(tui->input.in_fd, false); // libuv expects this
-}
-#endif
-
-void tui_suspend(TUIData *tui)
-{
-// on a non-UNIX system, this is a no-op
-#ifdef UNIX
- // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
- // before continuing. This is done in another callback to avoid
- // loop_poll_events recursion
- multiqueue_put_event(resize_events,
- event_create(suspend_event, 1, tui));
+ ui_client_attach(tui->width, tui->height, tui->term);
#endif
}
void tui_set_title(TUIData *tui, String title)
{
- if (!(title.data && unibi_get_str(tui->ut, unibi_to_status_line)
+ if (!(unibi_get_str(tui->ut, unibi_to_status_line)
&& unibi_get_str(tui->ut, unibi_from_status_line))) {
return;
}
- unibi_out(tui, unibi_to_status_line);
- out(tui, title.data, title.size);
- unibi_out(tui, unibi_from_status_line);
+ if (title.size > 0) {
+ if (!tui->title_enabled) {
+ // Save title/icon to the "stack". #4063
+ unibi_out_ext(tui, tui->unibi_ext.save_title);
+ tui->title_enabled = true;
+ }
+ unibi_out(tui, unibi_to_status_line);
+ out(tui, title.data, title.size);
+ unibi_out(tui, unibi_from_status_line);
+ } else if (tui->title_enabled) {
+ // Restore title/icon from the "stack". #4063
+ unibi_out_ext(tui, tui->unibi_ext.restore_title);
+ tui->title_enabled = false;
+ }
}
void tui_set_icon(TUIData *tui, String icon)
-{}
+{
+}
void tui_screenshot(TUIData *tui, String path)
{
@@ -1399,7 +1484,10 @@ void tui_screenshot(TUIData *tui, String path)
for (int i = 0; i < grid->height; i++) {
cursor_goto(tui, i, 0);
for (int j = 0; j < grid->width; j++) {
- print_cell(tui, &grid->cells[i][j]);
+ UCell cell = grid->cells[i][j];
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, cell.data);
+ print_cell(tui, buf, cell.attr);
}
}
flush_buf(tui);
@@ -1427,16 +1515,18 @@ void tui_option_set(TUIData *tui, String name, Object value)
if (ui_client_channel_id) {
MAXSIZE_TEMP_ARRAY(args, 2);
- ADD_C(args, STRING_OBJ(cstr_as_string("rgb")));
+ ADD_C(args, CSTR_AS_OBJ("rgb"));
ADD_C(args, BOOLEAN_OBJ(value.data.boolean));
rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args);
}
} else if (strequal(name.data, "ttimeout")) {
tui->input.ttimeout = value.data.boolean;
} else if (strequal(name.data, "ttimeoutlen")) {
- tui->input.ttimeoutlen = (long)value.data.integer;
+ tui->input.ttimeoutlen = (OptInt)value.data.integer;
} else if (strequal(name.data, "verbose")) {
tui->verbose = value.data.integer;
+ } else if (strequal(name.data, "termsync")) {
+ tui->sync_output = value.data.boolean;
}
}
@@ -1446,13 +1536,13 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
{
UGrid *grid = &tui->grid;
for (Integer c = startcol; c < endcol; c++) {
- memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T));
+ grid->cells[linerow][c].data = chunk[c - startcol];
assert((size_t)attrs[c - startcol] < kv_size(tui->attrs));
grid->cells[linerow][c].attr = attrs[c - startcol];
}
UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, {
print_cell_at_pos(tui, (int)linerow, curcol, cell,
- curcol < endcol - 1 && (cell + 1)->data[0] == NUL);
+ curcol < endcol - 1 && (cell + 1)->data == NUL);
});
if (clearcol > endcol) {
@@ -1469,7 +1559,7 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
if (endcol != grid->width) {
// Print the last char of the row, if we haven't already done so.
- int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1;
+ int size = grid->cells[linerow][grid->width - 1].data == NUL ? 2 : 1;
print_cell_at_pos(tui, (int)linerow, grid->width - size,
&grid->cells[linerow][grid->width - size], size == 2);
}
@@ -1540,12 +1630,11 @@ void tui_guess_size(TUIData *tui)
height = DFLT_ROWS;
}
- if (tui->width != width || tui->height != height) {
- tui->width = width;
- tui->height = height;
+ tui->width = width;
+ tui->height = height;
- ui_client_set_size(width, height);
- }
+ // Redraw on SIGWINCH event if size didn't change. #23411
+ ui_client_set_size(width, height);
}
static void unibi_goto(TUIData *tui, int row, int col)
@@ -1630,7 +1719,7 @@ static void pad(void *ctx, size_t delay, int scale FUNC_ATTR_UNUSED, int force)
}
flush_buf(tui);
- uv_sleep((unsigned int)(delay/10));
+ uv_sleep((unsigned)(delay/10));
}
static void unibi_set_if_empty(unibi_term *ut, enum unibi_string str, const char *val)
@@ -1668,13 +1757,10 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name)
/// Several entries in terminfo are known to be deficient or outright wrong;
/// and several terminal emulators falsely announce incorrect terminal types.
static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colorterm,
- long vte_version, long konsolev, bool iterm_env, bool nsterm)
+ int vte_version, int konsolev, bool iterm_env, bool nsterm)
{
unibi_term *ut = tui->ut;
const char *xterm_version = os_getenv("XTERM_VERSION");
-#if 0 // We don't need to identify this specifically, for now.
- bool roxterm = !!os_getenv("ROXTERM_ID");
-#endif
bool xterm = terminfo_is_term_family(term, "xterm")
// Treat Terminal.app as generic xterm-like, for now.
|| nsterm;
@@ -1867,15 +1953,6 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- tui->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
- "\x1b]11;?\x07");
-
- // Query the terminal to see if it supports CSI u key encoding by writing CSI
- // ? u followed by a request for the primary device attributes (CSI c)
- // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol
- tui->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys",
- "\x1b[?u\x1b[c");
-
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
// See http://fedoraproject.org/wiki/Features/256_Color_Terminals
@@ -1998,7 +2075,7 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo
/// This adds stuff that is not in standard terminfo as extended unibilium
/// capabilities.
-static void augment_terminfo(TUIData *tui, const char *term, long vte_version, long konsolev,
+static void augment_terminfo(TUIData *tui, const char *term, int vte_version, int konsolev,
bool iterm_env, bool nsterm)
{
unibi_term *ut = tui->ut;
@@ -2137,9 +2214,12 @@ static void augment_terminfo(TUIData *tui, const char *term, long vte_version, l
"\x1b[?2004l");
// For urxvt send BOTH xterm and old urxvt sequences. #8695
tui->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.enable_focus",
- rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h");
- tui->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.disable_focus",
- rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
+ rxvt
+ ? "\x1b[?1004h\x1b]777;focus;on\x7"
+ : "\x1b[?1004h");
+ tui->unibi_ext.disable_focus_reporting =
+ (int)unibi_add_ext_str(ut, "ext.disable_focus",
+ rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
tui->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, "ext.enable_mouse",
"\x1b[?1002h\x1b[?1006h");
tui->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse",
@@ -2168,71 +2248,29 @@ static void augment_terminfo(TUIData *tui, const char *term, long vte_version, l
}
if (!kitty && (vte_version == 0 || vte_version >= 5400)) {
- // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u
- tui->input.extkeys_type = kExtkeysXterm;
+ // Fallback to Xterm's modifyOtherKeys if terminal does not support the
+ // Kitty keyboard protocol
+ tui->input.key_encoding = kKeyEncodingXterm;
}
}
static void flush_buf(TUIData *tui)
{
uv_write_t req;
- uv_buf_t bufs[3];
- uv_buf_t *bufp = &bufs[0];
-
- // The content of the output for each condition is shown in the following
- // table. Therefore, if tui->bufpos == 0 and N/A or invis + norm, there is
- // no need to output it.
- //
- // | is_invisible | !is_invisible
- // ------+-----------------+--------------+---------------
- // busy | want_invisible | N/A | invis
- // | !want_invisible | N/A | invis
- // ------+-----------------+--------------+---------------
- // !busy | want_invisible | N/A | invis
- // | !want_invisible | norm | invis + norm
- // ------+-----------------+--------------+---------------
- //
- if (tui->bufpos <= 0
- && ((tui->is_invisible && tui->busy)
- || (tui->is_invisible && !tui->busy && tui->want_invisible)
- || (!tui->is_invisible && !tui->busy && !tui->want_invisible))) {
- return;
- }
+ uv_buf_t buf;
- if (!tui->is_invisible) {
- // cursor is visible. Write a "cursor invisible" command before writing the
- // buffer.
- bufp->base = tui->invis;
- bufp->len = UV_BUF_LEN(tui->invislen);
- bufp++;
- tui->is_invisible = true;
- }
-
- if (tui->bufpos > 0) {
- bufp->base = tui->buf;
- bufp->len = UV_BUF_LEN(tui->bufpos);
- bufp++;
+ if (tui->bufpos <= 0) {
+ return;
}
- if (!tui->busy) {
- assert(tui->is_invisible);
- // not busy and the cursor is invisible. Write a "cursor normal" command
- // after writing the buffer.
- if (!tui->want_invisible) {
- bufp->base = tui->norm;
- bufp->len = UV_BUF_LEN(tui->normlen);
- bufp++;
- tui->is_invisible = false;
- }
- }
+ buf.base = tui->buf;
+ buf.len = UV_BUF_LEN(tui->bufpos);
if (tui->screenshot) {
- for (size_t i = 0; i < (size_t)(bufp - bufs); i++) {
- fwrite(bufs[i].base, bufs[i].len, 1, tui->screenshot);
- }
+ fwrite(buf.base, buf.len, 1, tui->screenshot);
} else {
- int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &tui->output_handle),
- bufs, (unsigned)(bufp - bufs), NULL);
+ int ret
+ = uv_write(&req, (uv_stream_t *)&tui->output_handle, &buf, 1, NULL);
if (ret) {
ELOG("uv_write failed: %s", uv_strerror(ret));
}
diff --git a/src/nvim/tui/tui.h b/src/nvim/tui/tui.h
index 88ea73e99c..8eb4ac9bd8 100644
--- a/src/nvim/tui/tui.h
+++ b/src/nvim/tui/tui.h
@@ -1,13 +1,24 @@
-#ifndef NVIM_TUI_TUI_H
-#define NVIM_TUI_TUI_H
+#pragma once
-#include "nvim/cursor_shape.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/highlight_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/ui.h"
typedef struct TUIData TUIData;
+typedef enum {
+ kTermModeSynchronizedOutput = 2026,
+} TermMode;
+
+typedef enum {
+ kTermModeNotRecognized = 0,
+ kTermModeSet = 1,
+ kTermModeReset = 2,
+ kTermModePermanentlySet = 3,
+ kTermModePermanentlyReset = 4,
+} TermModeState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/tui.h.generated.h"
#endif
-
-#endif // NVIM_TUI_TUI_H
diff --git a/src/nvim/types.h b/src/nvim/types_defs.h
index d90c623955..c06737abb5 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_TYPES_H
-#define NVIM_TYPES_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
@@ -7,12 +6,12 @@
// dummy to pass an ACL to a function
typedef void *vim_acl_T;
-// Shorthand for unsigned variables. Many systems, but not all, have u_char
-// already defined, so we use char_u to avoid trouble.
-typedef unsigned char char_u;
-
-// Can hold one decoded UTF-8 character.
-typedef uint32_t u8char_T;
+// if data[0] is 0xFF, then data[1..4] is a 24-bit index (in machine endianness)
+// otherwise it must be a UTF-8 string of length maximum 4 (no NUL when n=4)
+typedef uint32_t schar_T;
+typedef int32_t sattr_T;
+// must be at least as big as the biggest of schar_T, sattr_T, colnr_T
+typedef int32_t sscratch_T;
// Opaque handle used by API clients to refer to various objects in vim
typedef int handle_T;
@@ -22,7 +21,7 @@ typedef int handle_T;
// absent callback etc.
typedef int LuaRef;
-/// Type used for VimL VAR_FLOAT values
+/// Type used for Vimscript VAR_FLOAT values
typedef double float_T;
typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler;
@@ -30,13 +29,11 @@ typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler;
typedef union {
float_T (*float_func)(float_T);
const MsgpackRpcRequestHandler *api_handler;
- void *nullptr;
+ void *null;
} EvalFuncData;
typedef handle_T NS;
-typedef struct expand expand_T;
-
typedef uint64_t proftime_T;
typedef enum {
@@ -48,6 +45,6 @@ typedef enum {
#define TRISTATE_TO_BOOL(val, \
default) ((val) == kTrue ? true : ((val) == kFalse ? false : (default)))
-typedef struct Decoration Decoration;
+#define TRISTATE_FROM_INT(val) ((val) == 0 ? kFalse : ((val) >= 1 ? kTrue : kNone))
-#endif // NVIM_TYPES_H
+typedef int64_t OptInt;
diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c
index be1746983c..f02b9626cd 100644
--- a/src/nvim/ugrid.c
+++ b/src/nvim/ugrid.c
@@ -1,9 +1,7 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <string.h>
+#include "nvim/grid.h"
#include "nvim/memory.h"
#include "nvim/ugrid.h"
@@ -63,10 +61,8 @@ void ugrid_scroll(UGrid *grid, int top, int bot, int left, int right, int count)
step = -1;
}
- int i;
-
// Copy cell data
- for (i = start; i != stop; i += step) {
+ for (int i = start; i != stop; i += step) {
UCell *target_row = grid->cells[i] + left;
UCell *source_row = grid->cells[i + count] + left;
assert(right >= left && left >= 0);
@@ -79,8 +75,7 @@ static void clear_region(UGrid *grid, int top, int bot, int left, int right, sat
{
for (int row = top; row <= bot; row++) {
UGRID_FOREACH_CELL(grid, row, left, right + 1, {
- cell->data[0] = ' ';
- cell->data[1] = 0;
+ cell->data = schar_from_ascii(' ');
cell->attr = attr;
});
}
diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h
index a85a6fb4a8..54cd33e58f 100644
--- a/src/nvim/ugrid.h
+++ b/src/nvim/ugrid.h
@@ -1,30 +1,17 @@
-#ifndef NVIM_UGRID_H
-#define NVIM_UGRID_H
+#pragma once
-#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/ui.h"
+#include "nvim/types_defs.h"
-struct ucell;
-struct ugrid;
-
-typedef struct ucell UCell;
-typedef struct ugrid UGrid;
-
-#define CELLBYTES (sizeof(schar_T))
-
-struct ucell {
- char data[CELLBYTES + 1];
+typedef struct ucell {
+ schar_T data;
sattr_T attr;
-};
+} UCell;
-struct ugrid {
+typedef struct ugrid {
int row, col;
int width, height;
UCell **cells;
-};
-
-// -V:UGRID_FOREACH_CELL:625
+} UGrid;
#define UGRID_FOREACH_CELL(grid, row, startcol, endcol, code) \
do { \
@@ -39,4 +26,3 @@ struct ugrid {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ugrid.h.generated.h"
#endif
-#endif // NVIM_UGRID_H
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 9f1cb87eb0..36f34bc75a 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
@@ -11,10 +8,11 @@
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer.h"
#include "nvim/cursor_shape.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_getln.h"
@@ -25,17 +23,19 @@
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.c.generated.h"
@@ -62,32 +62,32 @@ static int pending_has_mouse = -1;
static Array call_buf = ARRAY_DICT_INIT;
-#if MIN_LOG_LEVEL > LOGLVL_DBG
-# define UI_LOG(funname)
-#else
+#ifdef NVIM_LOG_DEBUG
static size_t uilog_seen = 0;
-static char uilog_last_event[1024] = { 0 };
+static const char *uilog_last_event = NULL;
-# ifndef EXITFREE
-# define entered_free_all_mem false
+static void ui_log(const char *funname)
+{
+# ifdef EXITFREE
+ if (entered_free_all_mem) {
+ return; // do nothing, we cannot log now
+ }
# endif
-# define UI_LOG(funname) \
- do { \
- if (entered_free_all_mem) { \
- /* do nothing, we cannot log now */ \
- } else if (strequal(uilog_last_event, STR(funname))) { \
- uilog_seen++; \
- } else { \
- if (uilog_seen > 0) { \
- logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, \
- "%s (+%zu times...)", uilog_last_event, uilog_seen); \
- } \
- logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, STR(funname)); \
- uilog_seen = 0; \
- xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \
- } \
- } while (0)
+ if (uilog_last_event == funname) {
+ uilog_seen++;
+ } else {
+ if (uilog_seen > 0) {
+ logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true,
+ "%s (+%zu times...)", uilog_last_event, uilog_seen);
+ }
+ logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, "%s", funname);
+ uilog_seen = 0;
+ uilog_last_event = funname;
+ }
+}
+#else
+# define ui_log(funname)
#endif
// UI_CALL invokes a function on all registered UI instances.
@@ -104,7 +104,7 @@ static char uilog_last_event[1024] = { 0 };
} \
} \
if (any_call) { \
- UI_LOG(funname); \
+ ui_log(STR(funname)); \
} \
} while (0)
@@ -129,10 +129,11 @@ void ui_free_all_mem(void)
map_foreach_value(&ui_event_cbs, event_cb, {
free_ui_event_callback(event_cb);
})
- pmap_destroy(uint32_t)(&ui_event_cbs);
+ map_destroy(uint32_t, &ui_event_cbs);
}
#endif
+/// Returns true if any `rgb=true` UI is attached.
bool ui_rgb_attached(void)
{
if (!headless_mode && p_tgc) {
@@ -146,6 +147,18 @@ bool ui_rgb_attached(void)
return false;
}
+/// Returns true if a GUI is attached.
+bool ui_gui_attached(void)
+{
+ for (size_t i = 0; i < ui_count; i++) {
+ bool tui = uis[i]->stdin_tty || uis[i]->stdout_tty;
+ if (!tui) {
+ return true;
+ }
+ }
+ return false;
+}
+
/// Returns true if any UI requested `override=true`.
bool ui_override(void)
{
@@ -215,7 +228,7 @@ void ui_refresh(void)
p_lz = save_p_lz;
if (ext_widgets[kUIMessages]) {
- set_option_value("cmdheight", 0L, NULL, 0);
+ set_option_value("cmdheight", NUMBER_OPTVAL(0), 0);
command_height();
}
ui_mode_info_set();
@@ -318,7 +331,17 @@ void vim_beep(unsigned val)
// comes from.
if (vim_strchr(p_debug, 'e') != NULL) {
msg_source(HL_ATTR(HLF_W));
- msg_attr(_("Beep!"), HL_ATTR(HLF_W));
+ msg(_("Beep!"), HL_ATTR(HLF_W));
+ }
+}
+
+/// Trigger UIEnter for all attached UIs.
+/// Used on startup after VimEnter.
+void do_autocmd_uienter_all(void)
+{
+ for (size_t i = 0; i < ui_count; i++) {
+ UIData *data = uis[i]->data;
+ do_autocmd_uienter(data->channel_id, true);
}
}
@@ -334,6 +357,7 @@ void ui_attach_impl(UI *ui, uint64_t chanid)
uis[ui_count++] = ui;
ui_refresh_options();
+ resettitle();
for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) {
ui_set_ext_option(ui, i, ui->ui_ext[i]);
@@ -419,13 +443,13 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol,
(const sattr_T *)grid->attrs + off);
// 'writedelay': flush & delay each time.
- if (p_wd && !(rdb_flags & RDB_COMPOSITOR)) {
+ if (p_wd && (rdb_flags & RDB_LINE)) {
// If 'writedelay' is active, set the cursor to indicate what was drawn.
ui_call_grid_cursor_goto(grid->handle, row,
MIN(clearcol, (int)grid->cols - 1));
ui_call_flush();
- uint64_t wd = (uint64_t)labs(p_wd);
- os_microdelay(wd * 1000U, true);
+ uint64_t wd = (uint64_t)llabs(p_wd);
+ os_sleep(wd);
pending_cursor_update = true; // restore the cursor later
}
}
@@ -472,11 +496,6 @@ int ui_current_col(void)
return cursor_col;
}
-handle_T ui_cursor_grid(void)
-{
- return cursor_grid_handle;
-}
-
void ui_flush(void)
{
assert(!ui_client_channel_id);
@@ -510,6 +529,10 @@ void ui_flush(void)
pending_has_mouse = has_mouse;
}
ui_call_flush();
+
+ if (p_wd && (rdb_flags & RDB_FLUSH)) {
+ os_sleep((uint64_t)llabs(p_wd));
+ }
}
/// Check if 'mouse' is active for the current mode
@@ -598,6 +621,17 @@ Array ui_array(void)
PUT(info, "height", INTEGER_OBJ(ui->height));
PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
PUT(info, "override", BOOLEAN_OBJ(ui->override));
+
+ // TUI fields. (`stdin_fd` is intentionally omitted.)
+ PUT(info, "term_name", CSTR_TO_OBJ(ui->term_name));
+
+ // term_background is deprecated. Populate with an empty string
+ PUT(info, "term_background", CSTR_TO_OBJ(""));
+
+ PUT(info, "term_colors", INTEGER_OBJ(ui->term_colors));
+ PUT(info, "stdin_tty", BOOLEAN_OBJ(ui->stdin_tty));
+ PUT(info, "stdout_tty", BOOLEAN_OBJ(ui->stdout_tty));
+
for (UIExtension j = 0; j < kUIExtCount; j++) {
if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
@@ -609,7 +643,7 @@ Array ui_array(void)
return all_uis;
}
-void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
+void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err)
{
if (grid_handle == DEFAULT_GRID_HANDLE) {
screen_resize(width, height);
@@ -617,11 +651,9 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
}
win_T *wp = get_win_by_grid_handle(grid_handle);
- if (wp == NULL) {
- api_set_error(error, kErrorTypeValidation,
- "No window with the given handle");
+ VALIDATE_INT((wp != NULL), "window handle", (int64_t)grid_handle, {
return;
- }
+ });
if (wp->w_floating) {
if (width != wp->w_width || height != wp->w_height) {
@@ -656,6 +688,8 @@ void ui_call_event(char *name, Array args)
if (!handled) {
UI_CALL(true, event, ui, name, args);
}
+
+ ui_log(name);
}
void ui_cb_update_ext(void)
@@ -689,9 +723,9 @@ void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
event_cb->ext_widgets[kUICmdline] = true;
}
- UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true);
+ ptr_t *item = pmap_put_ref(uint32_t)(&ui_event_cbs, ns_id, NULL, NULL);
if (*item) {
- free_ui_event_callback(*item);
+ free_ui_event_callback((UIEventCallback *)(*item));
}
*item = event_cb;
@@ -701,9 +735,9 @@ void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
void ui_remove_cb(uint32_t ns_id)
{
- if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) {
- free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id));
- pmap_del(uint32_t)(&ui_event_cbs, ns_id);
+ if (map_has(uint32_t, &ui_event_cbs, ns_id)) {
+ UIEventCallback *item = pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL);
+ free_ui_event_callback(item);
}
ui_cb_update_ext();
ui_refresh();
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 9140a9f1f3..666a869c89 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_UI_H
-#define NVIM_UI_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -9,9 +8,9 @@
#include "nvim/event/multiqueue.h"
#include "nvim/globals.h"
#include "nvim/highlight_defs.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
struct ui_t;
@@ -30,7 +29,7 @@ typedef enum {
kUIExtCount,
} UIExtension;
-EXTERN const char *ui_ext_names[] INIT(= {
+EXTERN const char *ui_ext_names[] INIT( = {
"ext_cmdline",
"ext_popupmenu",
"ext_tabline",
@@ -79,6 +78,8 @@ typedef struct {
uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!)
bool flushed_events; ///< events where sent to client without "flush" event
+ size_t ncells_pending; ///< total number of cells since last buffer flush
+
int hl_id; // Current highlight for legacy put event.
Integer cursor_row, cursor_col; // Intended visible cursor position.
@@ -101,6 +102,14 @@ struct ui_t {
double pum_height;
double pum_width;
+ // TUI fields.
+ char *term_name;
+ char *term_background; ///< Deprecated. No longer needed since background color detection happens
+ ///< in Lua. To be removed in a future release.
+ int term_colors;
+ bool stdin_tty;
+ bool stdout_tty;
+
// TODO(bfredl): integrate into struct!
UIData data[1];
};
@@ -113,9 +122,8 @@ typedef struct ui_event_callback {
// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.h.generated.h"
-# include "ui_events_call.h.generated.h"
+# include "ui_events_call.h.generated.h" // IWYU pragma: export
#endif
// uncrustify:on
EXTERN MultiQueue *resize_events;
-#endif // NVIM_UI_H
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index 378c0e4831..30f44d182d 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -1,28 +1,34 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+/// Nvim's own UI client, which attaches to a child or remote Nvim server.
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/loop.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/log.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/os/os_defs.h"
#include "nvim/tui/tui.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
+#ifdef MSWIN
+# include "nvim/os/os_win_console.h"
+#endif
+
static TUIData *tui = NULL;
+static bool ui_client_is_remote = false;
// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -30,14 +36,13 @@ static TUIData *tui = NULL;
# include "ui_events_client.generated.h"
#endif
// uncrustify:on
-//
uint64_t ui_client_start_server(int argc, char **argv)
{
varnumber_T exit_status;
char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *));
int args_idx = 0;
- args[args_idx++] = xstrdup((const char *)get_vim_var_str(VV_PROGPATH));
+ args[args_idx++] = xstrdup(argv[0]);
args[args_idx++] = xstrdup("--embed");
for (int i = 1; i < argc; i++) {
args[args_idx++] = xstrdup(argv[i]);
@@ -47,8 +52,8 @@ uint64_t ui_client_start_server(int argc, char **argv)
CallbackReader on_err = CALLBACK_READER_INIT;
on_err.fwd_err = true;
- Channel *channel = channel_job_start(args, CALLBACK_READER_INIT,
- on_err, CALLBACK_NONE,
+ Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH),
+ CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
false, true, true, false, kChannelStdinPipe,
NULL, 0, 0, NULL, &exit_status);
@@ -66,13 +71,8 @@ uint64_t ui_client_start_server(int argc, char **argv)
return channel->id;
}
-void ui_client_run(bool remote_ui)
- FUNC_ATTR_NORETURN
+void ui_client_attach(int width, int height, char *term)
{
- int width, height;
- char *term;
- tui = tui_start(&width, &height, &term);
-
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ(width));
ADD_C(args, INTEGER_OBJ(height));
@@ -82,24 +82,39 @@ void ui_client_run(bool remote_ui)
PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true));
PUT_C(opts, "ext_termcolors", BOOLEAN_OBJ(true));
if (term) {
- PUT(opts, "term_name", STRING_OBJ(cstr_to_string(term)));
- }
- if (ui_client_bg_response != kNone) {
- bool is_dark = (ui_client_bg_response == kTrue);
- PUT_C(opts, "term_background", STRING_OBJ(cstr_as_string(is_dark ? "dark" : "light")));
+ PUT_C(opts, "term_name", CSTR_AS_OBJ(term));
}
+
PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors));
- if (!remote_ui) {
+ if (!ui_client_is_remote) {
PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty));
PUT_C(opts, "stdout_tty", BOOLEAN_OBJ(stdout_isatty));
if (ui_client_forward_stdin) {
PUT_C(opts, "stdin_fd", INTEGER_OBJ(UI_CLIENT_STDIN_FD));
+ ui_client_forward_stdin = false; // stdin shouldn't be forwarded again #22292
}
}
ADD_C(args, DICTIONARY_OBJ(opts));
rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args);
ui_client_attached = true;
+}
+
+void ui_client_detach(void)
+{
+ rpc_send_event(ui_client_channel_id, "nvim_ui_detach", (Array)ARRAY_DICT_INIT);
+ ui_client_attached = false;
+}
+
+void ui_client_run(bool remote_ui)
+ FUNC_ATTR_NORETURN
+{
+ ui_client_is_remote = remote_ui;
+ int width, height;
+ char *term;
+ tui_start(&tui, &width, &height, &term);
+
+ ui_client_attach(width, height, term);
// os_exit() will be invoked when the client channel detaches
while (true) {
@@ -109,7 +124,9 @@ void ui_client_run(bool remote_ui)
void ui_client_stop(void)
{
- tui_stop(tui);
+ if (!tui_is_stopped(tui)) {
+ tui_stop(tui);
+ }
}
void ui_client_set_size(int width, int height)
@@ -134,7 +151,7 @@ UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len,
/// Placeholder for _sync_ requests with 'redraw' method name
///
-/// async 'redraw' events, which are expected when nvim acts as an ui client.
+/// async 'redraw' events, which are expected when nvim acts as a ui client.
/// get handled in msgpack_rpc/unpacker.c and directly dispatched to handlers
/// of specific ui events, like ui_client_event_grid_resize and so on.
Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Error *error)
@@ -189,9 +206,7 @@ void ui_client_event_raw_line(GridLineEvent *g)
int grid = g->args[0], row = g->args[1], startcol = g->args[2];
Integer endcol = startcol + g->coloff;
Integer clearcol = endcol + g->clear_width;
-
- // TODO(hlpr98): Accommodate other LineFlags when included in grid_line
- LineFlags lineflags = 0;
+ LineFlags lineflags = g->wrap ? kLineFlagWrap : 0;
tui_raw_line(tui, grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags,
(const schar_T *)grid_line_buf_char, grid_line_buf_attr);
diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h
index 7e5f847039..93170ed86d 100644
--- a/src/nvim/ui_client.h
+++ b/src/nvim/ui_client.h
@@ -1,14 +1,13 @@
-#ifndef NVIM_UI_CLIENT_H
-#define NVIM_UI_CLIENT_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "nvim/api/private/defs.h"
-#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/grid_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
typedef struct {
const char *name;
@@ -16,33 +15,30 @@ typedef struct {
} UIClientHandler;
// Temporary buffer for converting a single grid_line event
-EXTERN size_t grid_line_buf_size INIT(= 0);
-EXTERN schar_T *grid_line_buf_char INIT(= NULL);
-EXTERN sattr_T *grid_line_buf_attr INIT(= NULL);
+EXTERN size_t grid_line_buf_size INIT( = 0);
+EXTERN schar_T *grid_line_buf_char INIT( = NULL);
+EXTERN sattr_T *grid_line_buf_attr INIT( = NULL);
// ID of the ui client channel. If zero, the client is not running.
-EXTERN uint64_t ui_client_channel_id INIT(= 0);
+EXTERN uint64_t ui_client_channel_id INIT( = 0);
+
+// exit status from embedded nvim process
+EXTERN int ui_client_exit_status INIT( = 0);
// TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward.
// This will be restructured as part of The UI Devirtualization Project.
/// Whether ui client has sent nvim_ui_attach yet
-EXTERN bool ui_client_attached INIT(= false);
-
-/// Whether ui client has gotten a response about the bg color of the terminal,
-/// kTrue=dark, kFalse=light, kNone=no response yet
-EXTERN TriState ui_client_bg_response INIT(= kNone);
+EXTERN bool ui_client_attached INIT( = false);
/// The ui client should forward its stdin to the nvim process
/// by convention, this uses fd=3 (next free number after stdio)
-EXTERN bool ui_client_forward_stdin INIT(= false);
+EXTERN bool ui_client_forward_stdin INIT( = false);
#define UI_CLIENT_STDIN_FD 3
// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_client.h.generated.h"
-# include "ui_events_client.h.generated.h"
+# include "ui_events_client.h.generated.h" // IWYU pragma: export
#endif
// uncrustify:on
-
-#endif // NVIM_UI_CLIENT_H
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 9ff9eabff8..b698e017dc 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Compositor: merge floating grids with the main grid for display in
// TUI and non-multigrid UIs.
//
@@ -10,28 +7,26 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_compositor.c.generated.h"
@@ -55,7 +50,7 @@ static int msg_current_row = INT_MAX;
static bool msg_was_scrolled = false;
static int msg_sep_row = -1;
-static schar_T msg_sep_char = { ' ', NUL };
+static schar_T msg_sep_char = schar_from_ascii(' ');
static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
@@ -318,7 +313,6 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row]
+ (size_t)startcol];
- int grid_width, grid_height;
while (col < endcol) {
int until = 0;
for (size_t i = 0; i < kv_size(layers); i++) {
@@ -328,8 +322,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
// first check to see if any grids have pending updates to width/height,
// to ensure that we don't accidentally put any characters into `linebuf`
// that have been invalidated.
- grid_width = MIN(g->cols, g->comp_width);
- grid_height = MIN(g->rows, g->comp_height);
+ int grid_width = MIN(g->cols, g->comp_width);
+ int grid_height = MIN(g->rows, g->comp_height);
if (g->comp_row > row || row >= g->comp_row + grid_height
|| g->comp_disabled) {
continue;
@@ -354,7 +348,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
grid = &msg_grid;
sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP);
for (int i = col; i < until; i++) {
- memcpy(linebuf[i - startcol], msg_sep_char, sizeof(*linebuf));
+ linebuf[i - startcol] = msg_sep_char;
attrbuf[i - startcol] = msg_sep_attr;
}
} else {
@@ -363,9 +357,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
memcpy(linebuf + (col - startcol), grid->chars + off, n * sizeof(*linebuf));
memcpy(attrbuf + (col - startcol), grid->attrs + off, n * sizeof(*attrbuf));
if (grid->comp_col + grid->cols > until
- && grid->chars[off + n][0] == NUL) {
- linebuf[until - 1 - startcol][0] = ' ';
- linebuf[until - 1 - startcol][1] = '\0';
+ && grid->chars[off + n] == NUL) {
+ linebuf[until - 1 - startcol] = schar_from_ascii(' ');
if (col == startcol && n == 1) {
skipstart = 0;
}
@@ -378,10 +371,10 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
for (int i = col - (int)startcol; i < until - startcol; i += width) {
width = 1;
// negative space
- bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL;
- if (i + 1 < endcol - startcol && bg_line[i + 1][0] == NUL) {
+ bool thru = linebuf[i] == schar_from_ascii(' ') && bg_line[i] != NUL;
+ if (i + 1 < endcol - startcol && bg_line[i + 1] == NUL) {
width = 2;
- thru &= strequal((char *)linebuf[i + 1], " ");
+ thru &= linebuf[i + 1] == schar_from_ascii(' ');
}
attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru);
if (width == 2) {
@@ -396,28 +389,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
// Tricky: if overlap caused a doublewidth char to get cut-off, must
// replace the visible half with a space.
- if (linebuf[col - startcol][0] == NUL) {
- linebuf[col - startcol][0] = ' ';
- linebuf[col - startcol][1] = NUL;
+ if (linebuf[col - startcol] == NUL) {
+ linebuf[col - startcol] = schar_from_ascii(' ');
if (col == endcol - 1) {
skipend = 0;
}
- } else if (n > 1 && linebuf[col - startcol + 1][0] == NUL) {
+ } else if (col == startcol && n > 1 && linebuf[1] == NUL) {
skipstart = 0;
}
col = until;
}
- if (linebuf[endcol - startcol - 1][0] == NUL) {
+ if (linebuf[endcol - startcol - 1] == NUL) {
skipend = 0;
}
assert(endcol <= chk_width);
assert(row < chk_height);
- if (!(grid && grid == &default_grid)) {
- // TODO(bfredl): too conservative, need check
- // grid->line_wraps if grid->Width == Width
+ if (!(grid && (grid == &default_grid || (grid->comp_col == 0 && grid->cols == Columns)))) {
flags = flags & ~kLineFlagWrap;
}
@@ -439,7 +429,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
static void compose_debug(Integer startrow, Integer endrow, Integer startcol, Integer endcol,
int syn_id, bool delay)
{
- if (!(rdb_flags & RDB_COMPOSITOR)) {
+ if (!(rdb_flags & RDB_COMPOSITOR) || startcol >= endcol) {
return;
}
@@ -465,9 +455,9 @@ static void compose_debug(Integer startrow, Integer endrow, Integer startcol, In
static void debug_delay(Integer lines)
{
ui_call_flush();
- uint64_t wd = (uint64_t)labs(p_wd);
+ uint64_t wd = (uint64_t)llabs(p_wd);
uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1);
- os_microdelay(factor * wd * 1000U, true);
+ os_sleep(factor * wd);
}
static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol)
@@ -537,7 +527,7 @@ void ui_comp_raw_line(Integer grid, Integer row, Integer startcol, Integer endco
compose_debug(row, row + 1, startcol, clearcol, dbghl_composed, true);
compose_line(row, startcol, clearcol, flags);
} else {
- compose_debug(row, row + 1, startcol, endcol, dbghl_normal, false);
+ compose_debug(row, row + 1, startcol, endcol, dbghl_normal, endcol >= clearcol);
compose_debug(row, row + 1, endcol, clearcol, dbghl_clear, true);
#ifndef NDEBUG
for (int i = 0; i < endcol - startcol; i++) {
@@ -568,7 +558,7 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
if (scrolled && row > 0) {
msg_sep_row = (int)row - 1;
if (sep_char.data) {
- xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char));
+ msg_sep_char = schar_from_buf(sep_char.data, sep_char.size);
}
} else {
msg_sep_row = -1;
@@ -580,11 +570,11 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
&& (msg_current_row < Rows || (scrolled && !msg_was_scrolled))) {
int delta = msg_current_row - (int)row;
if (msg_grid.blending) {
- int first_row = MAX((int)row - (scrolled?1:0), 0);
+ int first_row = MAX((int)row - (scrolled ? 1 : 0), 0);
compose_area(first_row, Rows - delta, 0, Columns);
} else {
// scroll separator together with message text
- int first_row = MAX((int)row - (msg_was_scrolled?1:0), 0);
+ int first_row = MAX((int)row - (msg_was_scrolled ? 1 : 0), 0);
ui_composed_call_grid_scroll(1, first_row, Rows, 0, Columns, delta, 0);
if (scrolled && !msg_was_scrolled && row > 0) {
compose_area(row - 1, row, 0, Columns);
@@ -602,8 +592,8 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
static bool curgrid_covered_above(int row)
{
bool above_msg = (kv_A(layers, kv_size(layers) - 1) == &msg_grid
- && row < msg_current_row - (msg_was_scrolled?1:0));
- return kv_size(layers) - (above_msg?1:0) > curgrid->comp_index + 1;
+ && row < msg_current_row - (msg_was_scrolled ? 1 : 0));
+ return kv_size(layers) - (above_msg ? 1 : 0) > curgrid->comp_index + 1;
}
void ui_comp_grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right,
diff --git a/src/nvim/ui_compositor.h b/src/nvim/ui_compositor.h
index 01538105cb..f3f5981680 100644
--- a/src/nvim/ui_compositor.h
+++ b/src/nvim/ui_compositor.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_UI_COMPOSITOR_H
-#define NVIM_UI_COMPOSITOR_H
+#pragma once
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/event/defs.h"
+#include "nvim/grid_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_compositor.h.generated.h"
#endif
-#endif // NVIM_UI_COMPOSITOR_H
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 2b0bb1d243..93a973c33d 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// undo.c: multi level undo facility
// The saved lines are stored in a list of lists (one for each buffer):
@@ -80,12 +77,13 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include <time.h>
#include <uv.h>
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_updates.h"
@@ -93,39 +91,41 @@
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/macros.h"
+#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/sha256.h"
+#include "nvim/spell.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
/// Structure passed around between undofile functions.
typedef struct {
@@ -137,8 +137,15 @@ typedef struct {
# include "undo.c.generated.h"
#endif
+static const char e_undo_list_corrupt[]
+ = N_("E439: Undo list corrupt");
+static const char e_undo_line_missing[]
+ = N_("E440: Undo line missing");
+static const char e_write_error_in_undo_file_str[]
+ = N_("E829: Write error in undo file: %s");
+
// used in undo_end() to report number of added and deleted lines
-static long u_newcount, u_oldcount;
+static int u_newcount, u_oldcount;
// When 'u' flag included in 'cpoptions', we behave like vi. Need to remember
// the action that "u" should do.
@@ -174,12 +181,12 @@ static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *e
// Check pointers back are correct.
if (uhp->uh_next.ptr != exp_uh_next) {
emsg("uh_next wrong");
- smsg("expected: 0x%x, actual: 0x%x",
+ smsg(0, "expected: 0x%x, actual: 0x%x",
exp_uh_next, uhp->uh_next.ptr);
}
if (uhp->uh_alt_prev.ptr != exp_uh_alt_prev) {
emsg("uh_alt_prev wrong");
- smsg("expected: 0x%x, actual: 0x%x",
+ smsg(0, "expected: 0x%x, actual: 0x%x",
exp_uh_alt_prev, uhp->uh_alt_prev.ptr);
}
@@ -216,7 +223,7 @@ static void u_check(int newhead_may_be_NULL)
}
if (header_count != curbuf->b_u_numhead) {
emsg("b_u_numhead invalid");
- smsg("expected: %" PRId64 ", actual: %" PRId64,
+ smsg(0, "expected: %" PRId64 ", actual: %" PRId64,
(int64_t)header_count, (int64_t)curbuf->b_u_numhead);
}
}
@@ -241,15 +248,20 @@ int u_save_cursor(void)
/// Returns FAIL when lines could not be saved, OK otherwise.
int u_save(linenr_T top, linenr_T bot)
{
- if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) {
+ return u_save_buf(curbuf, top, bot);
+}
+
+int u_save_buf(buf_T *buf, linenr_T top, linenr_T bot)
+{
+ if (top >= bot || bot > (buf->b_ml.ml_line_count + 1)) {
return FAIL; // rely on caller to do error messages
}
if (top + 2 == bot) {
- u_saveline((linenr_T)(top + 1));
+ u_saveline(buf, top + 1);
}
- return u_savecommon(curbuf, top, bot, (linenr_T)0, false);
+ return u_savecommon(buf, top, bot, 0, false);
}
/// Save the line "lnum" (used by ":s" and "~" command).
@@ -275,9 +287,9 @@ int u_inssub(linenr_T lnum)
/// becomes empty.
/// Careful: may trigger autocommands that reload the buffer.
/// Returns FAIL when lines could not be saved, OK otherwise.
-int u_savedel(linenr_T lnum, long nlines)
+int u_savedel(linenr_T lnum, linenr_T nlines)
{
- return u_savecommon(curbuf, lnum - 1, lnum + (linenr_T)nlines,
+ return u_savecommon(curbuf, lnum - 1, lnum + nlines,
nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, false);
}
@@ -301,7 +313,7 @@ bool undo_allowed(buf_T *buf)
// Don't allow changes in the buffer while editing the cmdline. The
// caller of getcmdline() may get confused.
- if (textlock != 0) {
+ if (textlock != 0 || expr_map_locked()) {
emsg(_(e_textlock));
return false;
}
@@ -310,7 +322,7 @@ bool undo_allowed(buf_T *buf)
}
/// Get the 'undolevels' value for the current buffer.
-static long get_undolevel(buf_T *buf)
+static OptInt get_undolevel(buf_T *buf)
{
if (buf->b_p_ul == NO_LOCAL_UNDOLEVEL) {
return p_ul;
@@ -364,7 +376,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
u_entry_T *uep;
u_entry_T *prev_uep;
- long size = bot - top - 1;
+ linenr_T size = bot - top - 1;
// If curbuf->b_u_synced == true make a new header.
if (buf->b_u_synced) {
@@ -493,7 +505,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
if (size == 1) {
uep = u_get_headentry(buf);
prev_uep = NULL;
- for (long i = 0; i < 10; i++) {
+ for (int i = 0; i < 10; i++) {
if (uep == NULL) {
break;
}
@@ -575,7 +587,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
if (size > 0) {
uep->ue_array = xmalloc(sizeof(char *) * (size_t)size);
linenr_T lnum;
- long i;
+ int i;
for (i = 0, lnum = top + 1; i < size; i++) {
fast_breakcheck();
if (got_int) {
@@ -624,7 +636,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
// extra fields for uhp
#define UHP_SAVE_NR 1
-static char e_not_open[] = N_("E828: Cannot open undo file for writing: %s");
+static const char e_not_open[] = N_("E828: Cannot open undo file for writing: %s");
/// Compute the hash for a buffer text into hash[UNDO_HASH_SIZE].
///
@@ -636,7 +648,7 @@ void u_compute_hash(buf_T *buf, uint8_t *hash)
context_sha256_T ctx;
sha256_start(&ctx);
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- char *p = ml_get_buf(buf, lnum, false);
+ char *p = ml_get_buf(buf, lnum);
sha256_update(&ctx, (uint8_t *)p, strlen(p) + 1);
}
sha256_finish(&ctx, hash);
@@ -705,7 +717,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
// Last directory in the list does not exist, create it.
int ret;
char *failed_dir;
- if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
+ if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E5003: Unable to create directory \"%s\" for undo file: %s"),
failed_dir, os_strerror(ret));
xfree(failed_dir);
@@ -904,7 +916,7 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, const char *file_name)
uhp->uh_time = undo_read_time(bi);
// Unserialize optional fields.
- for (;;) {
+ while (true) {
int len = undo_read_byte(bi);
if (len == EOF) {
@@ -1074,7 +1086,7 @@ static u_entry_T *unserialize_uep(bufinfo_T *bi, bool *error, const char *file_n
char **array = NULL;
if (uep->ue_size > 0) {
- if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char *)) { // -V547
+ if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char *)) {
array = xmalloc(sizeof(char *) * (size_t)uep->ue_size);
memset(array, 0, sizeof(char *) * (size_t)uep->ue_size);
}
@@ -1165,7 +1177,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
if (file_name == NULL) {
if (p_verbose > 0) {
verbose_enter();
- smsg("%s", _("Cannot write undo file in any directory in 'undodir'"));
+ smsg(0, "%s", _("Cannot write undo file in any directory in 'undodir'"));
verbose_leave();
}
return;
@@ -1179,7 +1191,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
// allow the user to access the undo file.
int perm = 0600;
if (buf->b_ffname != NULL) {
- perm = os_getperm((const char *)buf->b_ffname);
+ perm = os_getperm(buf->b_ffname);
if (perm < 0) {
perm = 0600;
}
@@ -1201,7 +1213,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
if (name == NULL) {
verbose_enter();
}
- smsg(_("Will not overwrite with undo file, cannot read: %s"),
+ smsg(0, _("Will not overwrite with undo file, cannot read: %s"),
file_name);
if (name == NULL) {
verbose_leave();
@@ -1218,7 +1230,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
if (name == NULL) {
verbose_enter();
}
- smsg(_("Will not overwrite, this is not an undo file: %s"),
+ smsg(0, _("Will not overwrite, this is not an undo file: %s"),
file_name);
if (name == NULL) {
verbose_leave();
@@ -1248,7 +1260,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
(void)os_setperm(file_name, perm);
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Writing undo file: %s"), file_name);
+ smsg(0, _("Writing undo file: %s"), file_name);
verbose_leave();
}
@@ -1333,20 +1345,22 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
}
#endif
+ if (p_fs && fflush(fp) == 0 && os_fsync(fd) != 0) {
+ write_ok = false;
+ }
+
write_error:
fclose(fp);
if (!write_ok) {
- semsg(_("E829: write error in undo file: %s"), file_name);
+ semsg(_(e_write_error_in_undo_file_str), file_name);
}
-#ifdef HAVE_ACL
if (buf->b_ffname != NULL) {
// For systems that support ACL: get the ACL from the original file.
vim_acl_T acl = os_get_acl(buf->b_ffname);
os_set_acl(file_name, acl);
os_free_acl(acl);
}
-#endif
theend:
if (file_name != name) {
@@ -1383,7 +1397,7 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
&& file_info_undo.stat.st_uid != getuid()) {
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Not reading undo file, owner differs: %s"),
+ smsg(0, _("Not reading undo file, owner differs: %s"),
file_name);
verbose_leave();
}
@@ -1396,7 +1410,7 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Reading undo file: %s"), file_name);
+ smsg(0, _("Reading undo file: %s"), file_name);
verbose_leave();
}
@@ -1472,8 +1486,8 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
time_t seq_time = undo_read_time(&bi);
// Optional header fields.
- long last_save_nr = 0;
- for (;;) {
+ int last_save_nr = 0;
+ while (true) {
int len = undo_read_byte(&bi);
if (len == 0 || len == EOF) {
@@ -1498,12 +1512,12 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
// sequence numbers of the headers.
// When there are no headers uhp_table is NULL.
if (num_head > 0) {
- if ((size_t)num_head < SIZE_MAX / sizeof(*uhp_table)) { // -V547
+ if ((size_t)num_head < SIZE_MAX / sizeof(*uhp_table)) {
uhp_table = xmalloc((size_t)num_head * sizeof(*uhp_table));
}
}
- long num_read_uhps = 0;
+ int num_read_uhps = 0;
int c;
while ((c = undo_read_2c(&bi)) == UF_HEADER_MAGIC) {
@@ -1632,14 +1646,14 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
#endif
if (name != NULL) {
- smsg(_("Finished reading undo file %s"), file_name);
+ smsg(0, _("Finished reading undo file %s"), file_name);
}
goto theend;
error:
xfree(line_ptr);
if (uhp_table != NULL) {
- for (long i = 0; i < num_read_uhps; i++) {
+ for (int i = 0; i < num_read_uhps; i++) {
if (uhp_table[i] != NULL) {
u_free_uhp(uhp_table[i]);
}
@@ -1811,8 +1825,8 @@ bool u_undo_and_forget(int count, bool do_buf_event)
if (curbuf->b_u_curhead) {
to_forget->uh_alt_next.ptr = NULL;
curbuf->b_u_curhead->uh_alt_prev.ptr = to_forget->uh_alt_prev.ptr;
- curbuf->b_u_seq_cur = curbuf->b_u_curhead->uh_next.ptr ?
- curbuf->b_u_curhead->uh_next.ptr->uh_seq : 0;
+ curbuf->b_u_seq_cur = curbuf->b_u_curhead->uh_next.ptr
+ ? curbuf->b_u_curhead->uh_next.ptr->uh_seq : 0;
} else if (curbuf->b_u_newhead) {
curbuf->b_u_seq_cur = curbuf->b_u_newhead->uh_seq;
}
@@ -1867,7 +1881,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
curbuf->b_u_curhead = curbuf->b_u_oldhead;
beep_flush();
if (count == startcount - 1) {
- msg(_("Already at oldest change"));
+ msg(_("Already at oldest change"), 0);
return;
}
break;
@@ -1878,7 +1892,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
if (curbuf->b_u_curhead == NULL || get_undolevel(curbuf) <= 0) {
beep_flush(); // nothing to redo
if (count == startcount - 1) {
- msg(_("Already at newest change"));
+ msg(_("Already at newest change"), 0);
return;
}
break;
@@ -1904,7 +1918,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
// When "file" is true use "step" as a number of file writes.
// When "absolute" is true use "step" as the sequence number to jump to.
// "sec" must be false then.
-void undo_time(long step, bool sec, bool file, bool absolute)
+void undo_time(int step, bool sec, bool file, bool absolute)
{
if (text_locked()) {
text_locked_msg();
@@ -1922,8 +1936,8 @@ void undo_time(long step, bool sec, bool file, bool absolute)
u_oldcount = -1;
}
- long target;
- long closest;
+ int target;
+ int closest;
u_header_T *uhp = NULL;
bool dosec = sec;
bool dofile = file;
@@ -1937,7 +1951,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
closest = -1;
} else {
if (dosec) {
- target = (long)(curbuf->b_u_time_cur) + step;
+ target = (int)curbuf->b_u_time_cur + step;
} else if (dofile) {
if (step < 0) {
// Going back to a previous write. If there were changes after
@@ -1982,7 +1996,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
closest = -1;
} else {
if (dosec) {
- closest = (long)(os_time() + 1);
+ closest = (int)(os_time() + 1);
} else if (dofile) {
closest = curbuf->b_u_save_nr_last + 2;
} else {
@@ -1993,8 +2007,8 @@ void undo_time(long step, bool sec, bool file, bool absolute)
}
}
}
- long closest_start = closest;
- long closest_seq = curbuf->b_u_seq_cur;
+ int closest_start = closest;
+ int closest_seq = curbuf->b_u_seq_cur;
int mark;
int nomark = 0; // shut up compiler
@@ -2026,9 +2040,9 @@ void undo_time(long step, bool sec, bool file, bool absolute)
while (uhp != NULL) {
uhp->uh_walk = mark;
- long val = dosec ? (long)(uhp->uh_time) :
- dofile ? uhp->uh_save_nr
- : uhp->uh_seq;
+ int val = dosec ? (int)(uhp->uh_time)
+ : dofile ? uhp->uh_save_nr
+ : uhp->uh_seq;
if (round == 1 && !(dofile && val == 0)) {
// Remember the header that is closest to the target.
@@ -2036,7 +2050,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
// "b_u_seq_cur"). When the timestamp is equal find the
// highest/lowest sequence number.
if ((step < 0 ? uhp->uh_seq <= curbuf->b_u_seq_cur
- : uhp->uh_seq > curbuf->b_u_seq_cur)
+ : uhp->uh_seq > curbuf->b_u_seq_cur)
&& ((dosec && val == closest)
? (step < 0
? uhp->uh_seq < closest_seq
@@ -2102,9 +2116,9 @@ void undo_time(long step, bool sec, bool file, bool absolute)
if (closest == closest_start) {
if (step < 0) {
- msg(_("Already at oldest change"));
+ msg(_("Already at oldest change"), 0);
} else {
- msg(_("Already at newest change"));
+ msg(_("Already at newest change"), 0);
}
return;
}
@@ -2277,12 +2291,12 @@ static void u_undoredo(int undo, bool do_buf_event)
|| bot > curbuf->b_ml.ml_line_count + 1) {
unblock_autocmds();
iemsg(_("E438: u_undo: line numbers wrong"));
- changed(); // don't want UNCHANGED now
+ changed(curbuf); // don't want UNCHANGED now
return;
}
linenr_T oldsize = bot - top - 1; // number of lines before undo
- linenr_T newsize = (linenr_T)uep->ue_size; // number of lines after undo
+ linenr_T newsize = uep->ue_size; // number of lines after undo
if (top < newlnum) {
// If the saved cursor is somewhere in this undo block, move it to
@@ -2296,7 +2310,7 @@ static void u_undoredo(int undo, bool do_buf_event)
// Use the first line that actually changed. Avoids that
// undoing auto-formatting puts the cursor in the previous
// line.
- long i;
+ int i;
for (i = 0; i < newsize && i < oldsize; i++) {
if (strcmp(uep->ue_array[i], ml_get(top + 1 + (linenr_T)i)) != 0) {
break;
@@ -2318,7 +2332,7 @@ static void u_undoredo(int undo, bool do_buf_event)
if (oldsize > 0) {
newarray = xmalloc(sizeof(char *) * (size_t)oldsize);
// delete backwards, it goes faster in most cases
- long i;
+ int i;
linenr_T lnum;
for (lnum = bot - 1, i = oldsize; --i >= 0; lnum--) {
// what can we do when we run out of memory?
@@ -2336,15 +2350,15 @@ static void u_undoredo(int undo, bool do_buf_event)
// insert the lines in u_array between top and bot
if (newsize) {
- long i;
+ int i;
linenr_T lnum;
for (lnum = top, i = 0; i < newsize; i++, lnum++) {
// If the file is empty, there is an empty line 1 that we
// should get rid of, by replacing it with the new line
if (empty_buffer && lnum == 0) {
- ml_replace((linenr_T)1, uep->ue_array[i], true);
+ ml_replace(1, uep->ue_array[i], true);
} else {
- ml_append(lnum, uep->ue_array[i], (colnr_T)0, false);
+ ml_append(lnum, uep->ue_array[i], 0, false);
}
xfree(uep->ue_array[i]);
}
@@ -2362,7 +2376,13 @@ static void u_undoredo(int undo, bool do_buf_event)
}
}
- changed_lines(top + 1, 0, bot, newsize - oldsize, do_buf_event);
+ changed_lines(curbuf, top + 1, 0, bot, newsize - oldsize, do_buf_event);
+ // When text has been changed, possibly the start of the next line
+ // may have SpellCap that should be removed or it needs to be
+ // displayed. Schedule the next line for redrawing just in case.
+ if (spell_check_window(curwin) && bot <= curbuf->b_ml.ml_line_count) {
+ redrawWinline(curwin, bot);
+ }
// Set the '[ mark.
if (top + 1 < curbuf->b_op_start.lnum) {
@@ -2398,13 +2418,13 @@ static void u_undoredo(int undo, bool do_buf_event)
// Adjust Extmarks
ExtmarkUndoObject undo_info;
if (undo) {
- for (long i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
+ for (int i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
// redo
} else {
- for (long i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
+ for (int i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
@@ -2422,7 +2442,7 @@ static void u_undoredo(int undo, bool do_buf_event)
curbuf->b_ml.ml_flags |= ML_EMPTY;
}
if (old_flags & UH_CHANGED) {
- changed();
+ changed(curbuf);
} else {
unchanged(curbuf, false, true);
}
@@ -2435,7 +2455,7 @@ static void u_undoredo(int undo, bool do_buf_event)
}
// restore marks from before undo/redo
- for (long i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
if (curhead->uh_namedm[i].mark.lnum != 0) {
free_fmark(curbuf->b_namedm[i]);
curbuf->b_namedm[i] = curhead->uh_namedm[i];
@@ -2462,7 +2482,7 @@ static void u_undoredo(int undo, bool do_buf_event)
if (curhead->uh_cursor.lnum == curwin->w_cursor.lnum) {
curwin->w_cursor.col = curhead->uh_cursor.col;
if (virtual_active() && curhead->uh_cursor_vcol >= 0) {
- coladvance((colnr_T)curhead->uh_cursor_vcol);
+ coladvance(curhead->uh_cursor_vcol);
} else {
curwin->w_cursor.coladd = 0;
}
@@ -2486,8 +2506,8 @@ static void u_undoredo(int undo, bool do_buf_event)
if (undo) {
// We are below the previous undo. However, to make ":earlier 1s"
// work we compute this as being just above the just undone change.
- curbuf->b_u_seq_cur = curhead->uh_next.ptr ?
- curhead->uh_next.ptr->uh_seq : 0;
+ curbuf->b_u_seq_cur = curhead->uh_next.ptr
+ ? curhead->uh_next.ptr->uh_seq : 0;
}
// Remember where we are for ":earlier 1f" and ":later 1f".
@@ -2589,7 +2609,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount,
_(msgstr),
did_undo ? _("before") : _("after"),
- uhp == NULL ? (int64_t)0L : (int64_t)uhp->uh_seq,
+ uhp == NULL ? 0 : (int64_t)uhp->uh_seq,
msgbuf);
}
@@ -2600,7 +2620,7 @@ void undo_fmt_time(char *buf, size_t buflen, time_t tt)
struct tm curtime;
os_localtime_r(&tt, &curtime);
size_t n;
- if (time(NULL) - tt < (60L * 60L * 12L)) {
+ if (time(NULL) - tt < (60 * 60 * 12)) {
// within 12 hours
n = strftime(buf, buflen, "%H:%M:%S", &curtime);
} else {
@@ -2654,13 +2674,13 @@ void ex_undolist(exarg_T *eap)
while (uhp != NULL) {
if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark
&& uhp->uh_walk != mark) {
- vim_snprintf(IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes);
+ vim_snprintf(IObuff, IOSIZE, "%6d %7d ", uhp->uh_seq, changes);
undo_fmt_time(IObuff + strlen(IObuff), IOSIZE - strlen(IObuff), uhp->uh_time);
if (uhp->uh_save_nr > 0) {
while (strlen(IObuff) < 33) {
- STRCAT(IObuff, " ");
+ xstrlcat(IObuff, " ", IOSIZE);
}
- vim_snprintf_add(IObuff, IOSIZE, " %3ld", uhp->uh_save_nr);
+ vim_snprintf_add(IObuff, IOSIZE, " %3d", uhp->uh_save_nr);
}
GA_APPEND(char *, &ga, xstrdup(IObuff));
}
@@ -2697,7 +2717,7 @@ void ex_undolist(exarg_T *eap)
}
if (GA_EMPTY(&ga)) {
- msg(_("Nothing to undo"));
+ msg(_("Nothing to undo"), 0);
} else {
sort_strings(ga.ga_data, ga.ga_len);
@@ -2762,7 +2782,7 @@ void u_find_first_changed(void)
linenr_T lnum;
for (lnum = 1; lnum < curbuf->b_ml.ml_line_count
&& lnum <= uep->ue_size; lnum++) {
- if (strcmp(ml_get_buf(curbuf, lnum, false), uep->ue_array[lnum - 1]) != 0) {
+ if (strcmp(ml_get_buf(curbuf, lnum), uep->ue_array[lnum - 1]) != 0) {
clearpos(&(uhp->uh_cursor));
uhp->uh_cursor.lnum = lnum;
return;
@@ -2807,7 +2827,7 @@ static void u_unch_branch(u_header_T *uhp)
static u_entry_T *u_get_headentry(buf_T *buf)
{
if (buf->b_u_newhead == NULL || buf->b_u_newhead->uh_entry == NULL) {
- iemsg(_("E439: undo list corrupt"));
+ iemsg(_(e_undo_list_corrupt));
return NULL;
}
return buf->b_u_newhead->uh_entry;
@@ -2828,9 +2848,9 @@ static void u_getbot(buf_T *buf)
// inserted (0 - deleted) since calling u_save. This is equal to the
// old line count subtracted from the current line count.
linenr_T extra = buf->b_ml.ml_line_count - uep->ue_lcount;
- uep->ue_bot = uep->ue_top + (linenr_T)uep->ue_size + 1 + extra;
+ uep->ue_bot = uep->ue_top + uep->ue_size + 1 + extra;
if (uep->ue_bot < 1 || uep->ue_bot > buf->b_ml.ml_line_count) {
- iemsg(_("E440: undo line missing"));
+ iemsg(_(e_undo_line_missing));
uep->ue_bot = uep->ue_top + 1; // assume all lines deleted, will
// get all the old lines back
// without deleting the current
@@ -2912,8 +2932,6 @@ static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
/// @param uhpp if not NULL reset when freeing this header
static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
{
- u_entry_T *uep, *nuep;
-
// Check for pointers to the header that become invalid now.
if (buf->b_u_curhead == uhp) {
buf->b_u_curhead = NULL;
@@ -2925,7 +2943,8 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
*uhpp = NULL;
}
- for (uep = uhp->uh_entry; uep != NULL; uep = nuep) {
+ u_entry_T *nuep;
+ for (u_entry_T *uep = uhp->uh_entry; uep != NULL; uep = nuep) {
nuep = uep->ue_next;
u_freeentry(uep, uep->ue_size);
}
@@ -2940,7 +2959,7 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
}
/// free entry 'uep' and 'n' lines in uep->ue_array[]
-static void u_freeentry(u_entry_T *uep, long n)
+static void u_freeentry(u_entry_T *uep, int n)
{
while (n > 0) {
xfree(uep->ue_array[--n]);
@@ -2962,33 +2981,35 @@ void u_clearall(buf_T *buf)
buf->b_u_line_lnum = 0;
}
-/// save the line "lnum" for the "U" command
-void u_saveline(linenr_T lnum)
+/// Save the line "lnum" for the "U" command.
+void u_saveline(buf_T *buf, linenr_T lnum)
{
- if (lnum == curbuf->b_u_line_lnum) { // line is already saved
+ if (lnum == buf->b_u_line_lnum) { // line is already saved
return;
}
- if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { // should never happen
+ if (lnum < 1 || lnum > buf->b_ml.ml_line_count) { // should never happen
return;
}
- u_clearline();
- curbuf->b_u_line_lnum = lnum;
- if (curwin->w_cursor.lnum == lnum) {
- curbuf->b_u_line_colnr = curwin->w_cursor.col;
+ u_clearline(buf);
+ buf->b_u_line_lnum = lnum;
+ if (curwin->w_buffer == buf && curwin->w_cursor.lnum == lnum) {
+ buf->b_u_line_colnr = curwin->w_cursor.col;
} else {
- curbuf->b_u_line_colnr = 0;
+ buf->b_u_line_colnr = 0;
}
- curbuf->b_u_line_ptr = u_save_line(lnum);
+ buf->b_u_line_ptr = u_save_line_buf(buf, lnum);
}
/// clear the line saved for the "U" command
/// (this is used externally for crossing a line while in insert mode)
-void u_clearline(void)
+void u_clearline(buf_T *buf)
{
- if (curbuf->b_u_line_ptr != NULL) {
- XFREE_CLEAR(curbuf->b_u_line_ptr);
- curbuf->b_u_line_lnum = 0;
+ if (buf->b_u_line_ptr == NULL) {
+ return;
}
+
+ XFREE_CLEAR(buf->b_u_line_ptr);
+ buf->b_u_line_lnum = 0;
}
/// Implementation of the "U" command.
@@ -3005,7 +3026,7 @@ void u_undoline(void)
// first save the line for the 'u' command
if (u_savecommon(curbuf, curbuf->b_u_line_lnum - 1,
- curbuf->b_u_line_lnum + 1, (linenr_T)0, false) == FAIL) {
+ curbuf->b_u_line_lnum + 1, 0, false) == FAIL) {
return;
}
@@ -3054,7 +3075,7 @@ static char *u_save_line(linenr_T lnum)
/// @param buf buffer to copy from
static char *u_save_line_buf(buf_T *buf, linenr_T lnum)
{
- return xstrdup(ml_get_buf(buf, lnum, false));
+ return xstrdup(ml_get_buf(buf, lnum));
}
/// Check if the 'modified' flag is set, or 'ff' has changed (only need to
@@ -3100,7 +3121,7 @@ bool curbufIsChanged(void)
/// @param[in] first_uhp Undo blocks list to start with.
///
/// @return [allocated] List with a representation of undo blocks.
-list_T *u_eval_tree(const u_header_T *const first_uhp)
+static list_T *u_eval_tree(buf_T *const buf, const u_header_T *const first_uhp)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
{
list_T *const list = tv_list_alloc(kListLenMayKnow);
@@ -3109,10 +3130,10 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
dict_T *const dict = tv_dict_alloc();
tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq);
tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time);
- if (uhp == curbuf->b_u_newhead) {
+ if (uhp == buf->b_u_newhead) {
tv_dict_add_nr(dict, S_LEN("newhead"), 1);
}
- if (uhp == curbuf->b_u_curhead) {
+ if (uhp == buf->b_u_curhead) {
tv_dict_add_nr(dict, S_LEN("curhead"), 1);
}
if (uhp->uh_save_nr > 0) {
@@ -3121,7 +3142,7 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
if (uhp->uh_alt_next.ptr != NULL) {
// Recursive call to add alternate undo tree.
- tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr));
+ tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(buf, uhp->uh_alt_next.ptr));
}
tv_list_append_dict(list, dict);
@@ -3130,6 +3151,48 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
return list;
}
+/// "undofile(name)" function
+void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const fname = tv_get_string(&argvars[0]);
+
+ if (*fname == NUL) {
+ // If there is no file name there will be no undo file.
+ rettv->vval.v_string = NULL;
+ } else {
+ char *ffname = FullName_save(fname, true);
+
+ if (ffname != NULL) {
+ rettv->vval.v_string = u_get_undo_file_name(ffname, false);
+ }
+ xfree(ffname);
+ }
+}
+
+/// "undotree(expr)" function
+void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ typval_T *const tv = &argvars[0];
+ buf_T *const buf = tv->v_type == VAR_UNKNOWN ? curbuf : get_buf_arg(tv);
+ if (buf == NULL) {
+ return;
+ }
+
+ dict_T *dict = rettv->vval.v_dict;
+
+ tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)buf->b_u_synced);
+ tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)buf->b_u_seq_last);
+ tv_dict_add_nr(dict, S_LEN("save_last"), (varnumber_T)buf->b_u_save_nr_last);
+ tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)buf->b_u_seq_cur);
+ tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)buf->b_u_time_cur);
+ tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)buf->b_u_save_nr_cur);
+
+ tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(buf, buf->b_u_oldhead));
+}
+
// Given the buffer, Return the undo header. If none is set, set one first.
// NULL will be returned if e.g undolevels = -1 (undo disabled)
u_header_T *u_force_get_undo_header(buf_T *buf)
@@ -3142,15 +3205,13 @@ u_header_T *u_force_get_undo_header(buf_T *buf)
}
// Create the first undo header for the buffer
if (!uhp) {
- // Undo is normally invoked in change code, which already has swapped
- // curbuf.
// Args are tricky: this means replace empty range by empty range..
- u_savecommon(curbuf, 0, 1, 1, true);
+ u_savecommon(buf, 0, 1, 1, true);
uhp = buf->b_u_curhead;
if (!uhp) {
uhp = buf->b_u_newhead;
- if (get_undolevel(curbuf) > 0 && !uhp) {
+ if (get_undolevel(buf) > 0 && !uhp) {
abort();
}
}
diff --git a/src/nvim/undo.h b/src/nvim/undo.h
index f494d4de86..f3a8a60d45 100644
--- a/src/nvim/undo.h
+++ b/src/nvim/undo.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_UNDO_H
-#define NVIM_UNDO_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/undo_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/undo_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.h.generated.h"
#endif
-#endif // NVIM_UNDO_H
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 7c065c540b..0b78ea543f 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -1,34 +1,36 @@
-#ifndef NVIM_UNDO_DEFS_H
-#define NVIM_UNDO_DEFS_H
+#pragma once
-#include <time.h> // for time_t
+#include <time.h>
#include "nvim/extmark_defs.h"
#include "nvim/mark_defs.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+
+/// Size in bytes of the hash used in the undo file.
+enum { UNDO_HASH_SIZE = 32, };
typedef struct u_header u_header_T;
-// Structure to store info about the Visual area.
+/// Structure to store info about the Visual area.
typedef struct {
- pos_T vi_start; // start pos of last VIsual
- pos_T vi_end; // end position of last VIsual
- int vi_mode; // VIsual_mode of last VIsual
- colnr_T vi_curswant; // MAXCOL from w_curswant
+ pos_T vi_start; ///< start pos of last VIsual
+ pos_T vi_end; ///< end position of last VIsual
+ int vi_mode; ///< VIsual_mode of last VIsual
+ colnr_T vi_curswant; ///< MAXCOL from w_curswant
} visualinfo_T;
#include "nvim/buffer_defs.h"
typedef struct u_entry u_entry_T;
struct u_entry {
- u_entry_T *ue_next; // pointer to next entry in list
- linenr_T ue_top; // number of line above undo block
- linenr_T ue_bot; // number of line below undo block
- linenr_T ue_lcount; // linecount when u_save called
- char **ue_array; // array of lines in undo block
- long ue_size; // number of lines in ue_array
+ u_entry_T *ue_next; ///< pointer to next entry in list
+ linenr_T ue_top; ///< number of line above undo block
+ linenr_T ue_bot; ///< number of line below undo block
+ linenr_T ue_lcount; ///< linecount when u_save called
+ char **ue_array; ///< array of lines in undo block
+ linenr_T ue_size; ///< number of lines in ue_array
#ifdef U_DEBUG
- int ue_magic; // magic number to check allocation
+ int ue_magic; ///< magic number to check allocation
#endif
};
@@ -36,42 +38,42 @@ struct u_header {
// The following have a pointer and a number. The number is used when reading
// the undo file in u_read_undo()
union {
- u_header_T *ptr; // pointer to next undo header in list
- long seq;
+ u_header_T *ptr; ///< pointer to next undo header in list
+ int seq;
} uh_next;
union {
- u_header_T *ptr; // pointer to previous header in list
- long seq;
+ u_header_T *ptr; ///< pointer to previous header in list
+ int seq;
} uh_prev;
union {
- u_header_T *ptr; // pointer to next header for alt. redo
- long seq;
+ u_header_T *ptr; ///< pointer to next header for alt. redo
+ int seq;
} uh_alt_next;
union {
- u_header_T *ptr; // pointer to previous header for alt. redo
- long seq;
+ u_header_T *ptr; ///< pointer to previous header for alt. redo
+ int seq;
} uh_alt_prev;
- long uh_seq; // sequence number, higher == newer undo
- int uh_walk; // used by undo_time()
- u_entry_T *uh_entry; // pointer to first entry
- u_entry_T *uh_getbot_entry; // pointer to where ue_bot must be set
- pos_T uh_cursor; // cursor position before saving
- long uh_cursor_vcol;
- int uh_flags; // see below
- fmark_T uh_namedm[NMARKS]; // marks before undo/after redo
- extmark_undo_vec_t uh_extmark; // info to move extmarks
- visualinfo_T uh_visual; // Visual areas before undo/after redo
- time_t uh_time; // timestamp when the change was made
- long uh_save_nr; // set when the file was saved after the
- // changes in this block
+ int uh_seq; ///< sequence number, higher == newer undo
+ int uh_walk; ///< used by undo_time()
+ u_entry_T *uh_entry; ///< pointer to first entry
+ u_entry_T *uh_getbot_entry; ///< pointer to where ue_bot must be set
+ pos_T uh_cursor; ///< cursor position before saving
+ colnr_T uh_cursor_vcol;
+ int uh_flags; ///< see below
+ fmark_T uh_namedm[NMARKS]; ///< marks before undo/after redo
+ extmark_undo_vec_t uh_extmark; ///< info to move extmarks
+ visualinfo_T uh_visual; ///< Visual areas before undo/after redo
+ time_t uh_time; ///< timestamp when the change was made
+ int uh_save_nr; ///< set when the file was saved after the
+ ///< changes in this block
#ifdef U_DEBUG
- int uh_magic; // magic number to check allocation
+ int uh_magic; ///< magic number to check allocation
#endif
};
-// values for uh_flags
-#define UH_CHANGED 0x01 // b_changed flag before undo/after redo
-#define UH_EMPTYBUF 0x02 // buffer was empty
-#define UH_RELOAD 0x04 // buffer was reloaded
-
-#endif // NVIM_UNDO_DEFS_H
+/// values for uh_flags
+enum {
+ UH_CHANGED = 0x01, ///< b_changed flag before undo/after redo
+ UH_EMPTYBUF = 0x02, ///< buffer was empty
+ UH_RELOAD = 0x04, ///< buffer was reloaded
+};
diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c
index 31cb1e8936..7c65af5138 100644
--- a/src/nvim/usercmd.c
+++ b/src/nvim/usercmd.c
@@ -1,41 +1,40 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// usercmd.c: User defined command support
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "auto/config.h"
-#include "lauxlib.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -44,11 +43,13 @@
garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
-static char e_complete_used_without_allowing_arguments[]
- = N_("E1208: -complete used without allowing arguments");
-static char e_no_such_user_defined_command_str[]
+static const char e_argument_required_for_str[]
+ = N_("E179: Argument required for %s");
+static const char e_no_such_user_defined_command_str[]
= N_("E184: No such user-defined command: %s");
-static char e_no_such_user_defined_command_in_current_buffer_str[]
+static const char e_complete_used_without_allowing_arguments[]
+ = N_("E1208: -complete used without allowing arguments");
+static const char e_no_such_user_defined_command_in_current_buffer_str[]
= N_("E1237: No such user-defined command in current buffer: %s");
/// List of names for completion for ":command" with the EXPAND_ flag.
@@ -56,7 +57,6 @@ static char e_no_such_user_defined_command_in_current_buffer_str[]
static const char *command_complete[] = {
[EXPAND_ARGLIST] = "arglist",
[EXPAND_AUGROUP] = "augroup",
- [EXPAND_BEHAVE] = "behave",
[EXPAND_BUFFERS] = "buffer",
[EXPAND_CHECKHEALTH] = "checkhealth",
[EXPAND_COLORS] = "color",
@@ -89,6 +89,7 @@ static const char *command_complete[] = {
[EXPAND_SYNTIME] = "syntime",
[EXPAND_SETTINGS] = "option",
[EXPAND_PACKADD] = "packadd",
+ [EXPAND_RUNTIME] = "runtime",
[EXPAND_SHELLCMD] = "shellcmd",
[EXPAND_SIGN] = "sign",
[EXPAND_TAGS] = "tag",
@@ -128,23 +129,21 @@ static struct {
char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp)
{
int len = (int)(p - eap->cmd);
- int j, k, matchlen = 0;
- ucmd_T *uc;
+ int matchlen = 0;
bool found = false;
bool possible = false;
- char *cp, *np; // Point into typed cmd and test name
- garray_T *gap;
bool amb_local = false; // Found ambiguous buffer-local command,
// only full match global is accepted.
// Look for buffer-local user commands first, then global ones.
- gap = &prevwin_curwin()->w_buffer->b_ucmds;
- for (;;) {
+ garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds;
+ while (true) {
+ int j;
for (j = 0; j < gap->ga_len; j++) {
- uc = USER_CMD_GA(gap, j);
- cp = eap->cmd;
- np = uc->uc_name;
- k = 0;
+ ucmd_T *uc = USER_CMD_GA(gap, j);
+ char *cp = eap->cmd;
+ char *np = uc->uc_name;
+ int k = 0;
while (k < len && *np != NUL && *cp++ == *np++) {
k++;
}
@@ -233,7 +232,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in)
// Check for attributes
while (*arg == '-') {
arg++; // Skip "-".
- p = (const char *)skiptowhite(arg);
+ p = skiptowhite(arg);
if (*p == NUL) {
// Cursor is still in the attribute.
p = strchr(arg, '=');
@@ -261,11 +260,11 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in)
}
return NULL;
}
- arg = (const char *)skipwhite(p);
+ arg = skipwhite(p);
}
// After the attributes comes the new command name.
- p = (const char *)skiptowhite(arg);
+ p = skiptowhite(arg);
if (*p == NUL) {
xp->xp_context = EXPAND_USER_COMMANDS;
xp->xp_pattern = (char *)arg;
@@ -273,7 +272,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in)
}
// And finally comes a normal command.
- return (const char *)skipwhite(p);
+ return skipwhite(p);
}
/// Set the completion context for the argument of a user defined command.
@@ -291,14 +290,14 @@ const char *set_context_in_user_cmdarg(const char *cmd FUNC_ATTR_UNUSED, const c
}
if (context == EXPAND_MENUS) {
- return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
+ return set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
}
if (context == EXPAND_COMMANDS) {
return arg;
}
if (context == EXPAND_MAPPINGS) {
- return (const char *)set_context_in_map_cmd(xp, "map", (char *)arg, forceit, false, false,
- CMD_map);
+ return set_context_in_map_cmd(xp, "map", (char *)arg, forceit, false, false,
+ CMD_map);
}
// Find start of last argument.
const char *p = arg;
@@ -423,6 +422,13 @@ char *get_user_cmd_complete(expand_T *xp, int idx)
int cmdcomplete_str_to_type(const char *complete_str)
{
+ if (strncmp(complete_str, "custom,", 7) == 0) {
+ return EXPAND_USER_DEFINED;
+ }
+ if (strncmp(complete_str, "customlist,", 11) == 0) {
+ return EXPAND_USER_LIST;
+ }
+
for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) {
char *cmd_compl = get_command_complete(i);
if (cmd_compl == NULL) {
@@ -438,17 +444,15 @@ int cmdcomplete_str_to_type(const char *complete_str)
static void uc_list(char *name, size_t name_len)
{
- int i, j;
bool found = false;
- ucmd_T *cmd;
- uint32_t a;
// In cmdwin, the alternative buffer should be used.
const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds;
- for (;;) {
+ while (true) {
+ int i;
for (i = 0; i < gap->ga_len; i++) {
- cmd = USER_CMD_GA(gap, i);
- a = cmd->uc_argt;
+ ucmd_T *cmd = USER_CMD_GA(gap, i);
+ uint32_t a = cmd->uc_argt;
// Skip commands which don't match the requested prefix and
// commands filtered out.
@@ -469,7 +473,7 @@ static void uc_list(char *name, size_t name_len)
}
// Special cases
- int len = 4;
+ size_t len = 4;
if (a & EX_BANG) {
msg_putchar('!');
len--;
@@ -490,8 +494,8 @@ static void uc_list(char *name, size_t name_len)
msg_putchar(' ');
}
- msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
- len = (int)strlen(cmd->uc_name) + 4;
+ msg_outtrans(cmd->uc_name, HL_ATTR(HLF_D));
+ len = strlen(cmd->uc_name) + 4;
do {
msg_putchar(' ');
@@ -500,7 +504,7 @@ static void uc_list(char *name, size_t name_len)
// "over" is how much longer the name is than the column width for
// the name, we'll try to align what comes after.
- const int over = len - 22;
+ const int64_t over = (int64_t)len - 22;
len = 0;
// Arguments
@@ -524,22 +528,22 @@ static void uc_list(char *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 5 - over);
+ } while ((int64_t)len < 5 - over);
// Address / Range
if (a & (EX_RANGE | EX_COUNT)) {
if (a & EX_COUNT) {
// -count=N
- snprintf(IObuff + len, IOSIZE, "%" PRId64 "c",
- (int64_t)cmd->uc_def);
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%" PRId64 "c", cmd->uc_def);
+ assert(rc > 0);
+ len += (size_t)rc;
} else if (a & EX_DFLALL) {
IObuff[len++] = '%';
} else if (cmd->uc_def >= 0) {
// -range=N
- snprintf(IObuff + len, IOSIZE, "%" PRId64 "",
- (int64_t)cmd->uc_def);
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%" PRId64 "", cmd->uc_def);
+ assert(rc > 0);
+ len += (size_t)rc;
} else {
IObuff[len++] = '.';
}
@@ -547,35 +551,37 @@ static void uc_list(char *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 8 - over);
+ } while ((int64_t)len < 8 - over);
// Address Type
- for (j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
+ for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
if (addr_type_complete[j].expand != ADDR_LINES
&& addr_type_complete[j].expand == cmd->uc_addr_type) {
- STRCPY(IObuff + len, addr_type_complete[j].shortname);
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%s", addr_type_complete[j].shortname);
+ assert(rc > 0);
+ len += (size_t)rc;
break;
}
}
do {
IObuff[len++] = ' ';
- } while (len < 13 - over);
+ } while ((int64_t)len < 13 - over);
// Completion
char *cmd_compl = get_command_complete(cmd->uc_compl);
if (cmd_compl != NULL) {
- STRCPY(IObuff + len, get_command_complete(cmd->uc_compl));
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%s", get_command_complete(cmd->uc_compl));
+ assert(rc > 0);
+ len += (size_t)rc;
}
do {
IObuff[len++] = ' ';
- } while (len < 25 - over);
+ } while ((int64_t)len < 25 - over);
IObuff[len] = '\0';
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
if (cmd->uc_luaref != LUA_NOREF) {
char *fn = nlua_funcref_str(cmd->uc_luaref);
@@ -604,7 +610,7 @@ static void uc_list(char *name, size_t name_len)
}
if (!found) {
- msg(_("No user-defined commands found"));
+ msg(_("No user-defined commands found"), 0);
}
}
@@ -612,11 +618,11 @@ static void uc_list(char *name, size_t name_len)
int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg)
FUNC_ATTR_NONNULL_ALL
{
- int i, a, b;
+ int i;
for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) {
- a = (int)strlen(addr_type_complete[i].name) == vallen;
- b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0;
+ int a = (int)strlen(addr_type_complete[i].name) == vallen;
+ int b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0;
if (a && b) {
*addr_type_arg = addr_type_complete[i].expand;
break;
@@ -646,11 +652,10 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt,
{
const char *arg = NULL;
size_t arglen = 0;
- int i;
int valend = vallen;
// Look for any argument part - which is the part after any ','
- for (i = 0; i < vallen; i++) {
+ for (int i = 0; i < vallen; i++) {
if (value[i] == ',') {
arg = (char *)&value[i + 1];
arglen = (size_t)(vallen - i - 1);
@@ -659,6 +664,7 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt,
}
}
+ int i;
for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) {
if (get_command_complete(i) == NULL) {
continue;
@@ -698,12 +704,10 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt,
return OK;
}
-static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *flags, int *complp,
+static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, int *def, int *flags, int *complp,
char **compl_arg, cmd_addr_T *addr_type_arg)
FUNC_ATTR_NONNULL_ALL
{
- char *p;
-
if (len == 0) {
emsg(_("E175: No attribute specified"));
return FAIL;
@@ -721,13 +725,12 @@ static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *
} else if (STRNICMP(attr, "bar", len) == 0) {
*argt |= EX_TRLBAR;
} else {
- int i;
char *val = NULL;
size_t vallen = 0;
size_t attrlen = len;
// Look for the attribute name - which is the part before any '='
- for (i = 0; i < (int)len; i++) {
+ for (int i = 0; i < (int)len; i++) {
if (attr[i] == '=') {
val = &attr[i + 1];
vallen = len - (size_t)i - 1;
@@ -761,14 +764,14 @@ wrong_nargs:
if (vallen == 1 && *val == '%') {
*argt |= EX_DFLALL;
} else if (val != NULL) {
- p = val;
+ char *p = val;
if (*def >= 0) {
two_count:
emsg(_("E177: Count cannot be specified twice"));
return FAIL;
}
- *def = getdigits_long(&p, true, 0);
+ *def = getdigits_int(&p, true, 0);
*argt |= EX_ZEROR;
if (p != val + vallen || vallen == 0) {
@@ -789,12 +792,12 @@ invalid_count:
}
if (val != NULL) {
- p = val;
+ char *p = val;
if (*def >= 0) {
goto two_count;
}
- *def = getdigits_long(&p, true, 0);
+ *def = getdigits_int(&p, true, 0);
if (p != val + vallen) {
goto invalid_count;
@@ -806,7 +809,7 @@ invalid_count:
}
} else if (STRNICMP(attr, "complete", attrlen) == 0) {
if (val == NULL) {
- emsg(_("E179: argument required for -complete"));
+ semsg(_(e_argument_required_for_str), "-complete");
return FAIL;
}
@@ -817,7 +820,7 @@ invalid_count:
} else if (STRNICMP(attr, "addr", attrlen) == 0) {
*argt |= EX_RANGE;
if (val == NULL) {
- emsg(_("E179: argument required for -addr"));
+ semsg(_(e_argument_required_for_str), "-addr");
return FAIL;
}
if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL) {
@@ -861,18 +864,17 @@ char *uc_validate_name(char *name)
/// This function takes ownership of compl_arg, compl_luaref, and luaref.
///
/// @return OK if the command is created, FAIL otherwise.
-int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, long def, int flags,
- int compl, char *compl_arg, LuaRef compl_luaref, LuaRef preview_luaref,
- cmd_addr_T addr_type, LuaRef luaref, bool force)
+int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, int64_t def,
+ int flags, int context, char *compl_arg, LuaRef compl_luaref,
+ LuaRef preview_luaref, cmd_addr_T addr_type, LuaRef luaref, bool force)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
ucmd_T *cmd = NULL;
- int i;
int cmp = 1;
char *rep_buf = NULL;
garray_T *gap;
- replace_termcodes(rep, strlen(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(rep, strlen(rep), &rep_buf, 0, 0, NULL, CPO_TO_CPO_FLAGS);
if (rep_buf == NULL) {
// Can't replace termcodes - try using the string as is
rep_buf = xstrdup(rep);
@@ -888,12 +890,12 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt,
gap = &ucmds;
}
+ int i;
+
// Search for the command in the already defined commands.
for (i = 0; i < gap->ga_len; i++) {
- size_t len;
-
cmd = USER_CMD_GA(gap, i);
- len = strlen(cmd->uc_name);
+ size_t len = strlen(cmd->uc_name);
cmp = strncmp(name, cmd->uc_name, name_len);
if (cmp == 0) {
if (name_len < len) {
@@ -945,7 +947,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt,
cmd->uc_rep = rep_buf;
cmd->uc_argt = argt;
cmd->uc_def = def;
- cmd->uc_compl = compl;
+ cmd->uc_compl = context;
cmd->uc_script_ctx = current_sctx;
cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&cmd->uc_script_ctx);
@@ -969,25 +971,22 @@ fail:
/// ":command ..."
void ex_command(exarg_T *eap)
{
- char *name;
char *end;
- char *p;
uint32_t argt = 0;
- long def = -1;
+ int def = -1;
int flags = 0;
- int compl = EXPAND_NOTHING;
+ int context = EXPAND_NOTHING;
char *compl_arg = NULL;
cmd_addr_T addr_type_arg = ADDR_NONE;
int has_attr = (eap->arg[0] == '-');
- size_t name_len;
- p = eap->arg;
+ char *p = eap->arg;
// Check for attributes
while (*p == '-') {
p++;
end = skiptowhite(p);
- if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, &compl_arg,
+ if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &context, &compl_arg,
&addr_type_arg) == FAIL) {
goto theend;
}
@@ -995,13 +994,13 @@ void ex_command(exarg_T *eap)
}
// Get the name (if any) and skip to the following argument.
- name = p;
+ char *name = p;
end = uc_validate_name(name);
if (!end) {
emsg(_("E182: Invalid command name"));
goto theend;
}
- name_len = (size_t)(end - name);
+ size_t name_len = (size_t)(end - name);
// If there is nothing after the name, and no attributes were specified,
// we are listing commands
@@ -1012,10 +1011,10 @@ void ex_command(exarg_T *eap)
emsg(_("E183: User defined commands must start with an uppercase letter"));
} else if (name_len <= 4 && strncmp(name, "Next", name_len) == 0) {
emsg(_("E841: Reserved name, cannot be used for user defined command"));
- } else if (compl > 0 && (argt & EX_EXTRA) == 0) {
+ } else if (context > 0 && (argt & EX_EXTRA) == 0) {
emsg(_(e_complete_used_without_allowing_arguments));
} else {
- uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF,
+ uc_add_command(name, name_len, p, argt, def, flags, context, compl_arg, LUA_NOREF, LUA_NOREF,
addr_type_arg, LUA_NOREF, eap->forceit);
return; // success
@@ -1054,7 +1053,6 @@ void ex_delcommand(exarg_T *eap)
int i = 0;
ucmd_T *cmd = NULL;
int res = -1;
- garray_T *gap;
const char *arg = eap->arg;
bool buffer_only = false;
@@ -1063,8 +1061,8 @@ void ex_delcommand(exarg_T *eap)
arg = skipwhite(arg + 7);
}
- gap = &curbuf->b_ucmds;
- for (;;) {
+ garray_T *gap = &curbuf->b_ucmds;
+ while (true) {
for (i = 0; i < gap->ga_len; i++) {
cmd = USER_CMD_GA(gap, i);
res = strcmp(arg, cmd->uc_name);
@@ -1139,17 +1137,13 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf,
}
/// split and quote args for <f-args>
-static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t argc, size_t *lenp)
+static char *uc_split_args(const char *arg, char **args, const size_t *arglens, size_t argc,
+ size_t *lenp)
{
- char *buf;
- char *p;
- char *q;
- int len;
-
// Precalculate length
- len = 2; // Initial and final quotes
+ int len = 2; // Initial and final quotes
if (args == NULL) {
- p = arg;
+ const char *p = arg;
while (*p) {
if (p[0] == '\\' && p[1] == '\\') {
@@ -1176,7 +1170,7 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
}
} else {
for (size_t i = 0; i < argc; i++) {
- p = args[i];
+ const char *p = args[i];
const char *arg_end = args[i] + arglens[i];
while (p < arg_end) {
@@ -1197,13 +1191,13 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
}
}
- buf = xmalloc((size_t)len + 1);
+ char *buf = xmalloc((size_t)len + 1);
- q = buf;
+ char *q = buf;
*q++ = '"';
if (args == NULL) {
- p = arg;
+ const char *p = arg;
while (*p) {
if (p[0] == '\\' && p[1] == '\\') {
*q++ = '\\';
@@ -1225,12 +1219,12 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
*q++ = ' ';
*q++ = '"';
} else {
- mb_copy_char((const char **)&p, &q);
+ mb_copy_char(&p, &q);
}
}
} else {
for (size_t i = 0; i < argc; i++) {
- p = args[i];
+ const char *p = args[i];
const char *arg_end = args[i] + arglens[i];
while (p < arg_end) {
@@ -1238,7 +1232,7 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
*q++ = '\\';
*q++ = *p++;
} else {
- mb_copy_char((const char **)&p, &q);
+ mb_copy_char(&p, &q);
}
}
if (i != argc - 1) {
@@ -1539,13 +1533,16 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar
case ct_RANGE:
case ct_COUNT: {
char num_buf[20];
- long num = (type == ct_LINE1) ? eap->line1 :
- (type == ct_LINE2) ? eap->line2 :
- (type == ct_RANGE) ? eap->addr_count :
- (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
+ int64_t num = type == ct_LINE1
+ ? eap->line1
+ : (type == ct_LINE2
+ ? eap->line2
+ : (type == ct_RANGE
+ ? eap->addr_count
+ : (eap->addr_count > 0 ? eap->line2 : cmd->uc_def)));
size_t num_len;
- snprintf(num_buf, sizeof(num_buf), "%" PRId64, (int64_t)num);
+ snprintf(num_buf, sizeof(num_buf), "%" PRId64, num);
num_len = strlen(num_buf);
result = num_len;
@@ -1610,14 +1607,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar
int do_ucmd(exarg_T *eap, bool preview)
{
- char *buf;
- char *p;
- char *q;
-
- char *start;
char *end = NULL;
- char *ksp;
- size_t len, totlen;
size_t split_len = 0;
char *split_buf = NULL;
@@ -1626,7 +1616,7 @@ int do_ucmd(exarg_T *eap, bool preview)
if (eap->cmdidx == CMD_USER) {
cmd = USER_CMD(eap->useridx);
} else {
- cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
+ cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
}
if (preview) {
@@ -1642,18 +1632,19 @@ int do_ucmd(exarg_T *eap, bool preview)
// Replace <> in the command by the arguments.
// First round: "buf" is NULL, compute length, allocate "buf".
// Second round: copy result into "buf".
- buf = NULL;
- for (;;) {
- p = cmd->uc_rep; // source
- q = buf; // destination
- totlen = 0;
-
- for (;;) {
- start = vim_strchr(p, '<');
+ char *buf = NULL;
+ while (true) {
+ char *p = cmd->uc_rep; // source
+ char *q = buf; // destination
+ size_t totlen = 0;
+
+ while (true) {
+ char *start = vim_strchr(p, '<');
if (start != NULL) {
end = vim_strchr(start + 1, '>');
}
if (buf != NULL) {
+ char *ksp;
for (ksp = p; *ksp != NUL && (uint8_t)(*ksp) != K_SPECIAL; ksp++) {}
if ((uint8_t)(*ksp) == K_SPECIAL
&& (start == NULL || ksp < start || end == NULL)
@@ -1661,7 +1652,7 @@ int do_ucmd(exarg_T *eap, bool preview)
// K_SPECIAL has been put in the buffer as K_SPECIAL
// KS_SPECIAL KE_FILLER, like for mappings, but
// do_cmdline() doesn't handle that, so convert it back.
- len = (size_t)(ksp - p);
+ size_t len = (size_t)(ksp - p);
if (len > 0) {
memmove(q, p, len);
q += len;
@@ -1681,7 +1672,7 @@ int do_ucmd(exarg_T *eap, bool preview)
end++;
// Take everything up to the '<'
- len = (size_t)(start - p);
+ size_t len = (size_t)(start - p);
if (buf == NULL) {
totlen += len;
} else {
@@ -1750,8 +1741,8 @@ Dictionary commands_array(buf_T *buf)
Dictionary d = ARRAY_DICT_INIT;
ucmd_T *cmd = USER_CMD_GA(gap, i);
- PUT(d, "name", STRING_OBJ(cstr_to_string((char *)cmd->uc_name)));
- PUT(d, "definition", STRING_OBJ(cstr_to_string((char *)cmd->uc_rep)));
+ PUT(d, "name", CSTR_TO_OBJ(cmd->uc_name));
+ PUT(d, "definition", CSTR_TO_OBJ(cmd->uc_rep));
PUT(d, "script_id", INTEGER_OBJ(cmd->uc_script_ctx.sc_sid));
PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG)));
PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
@@ -1771,21 +1762,21 @@ Dictionary commands_array(buf_T *buf)
case (EX_EXTRA | EX_NOSPC | EX_NEEDARG):
arg[0] = '1'; break;
}
- PUT(d, "nargs", STRING_OBJ(cstr_to_string(arg)));
+ PUT(d, "nargs", CSTR_TO_OBJ(arg));
char *cmd_compl = get_command_complete(cmd->uc_compl);
PUT(d, "complete", (cmd_compl == NULL
- ? NIL : STRING_OBJ(cstr_to_string(cmd_compl))));
+ ? NIL : CSTR_TO_OBJ(cmd_compl)));
PUT(d, "complete_arg", cmd->uc_compl_arg == NULL
- ? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg)));
+ ? NIL : CSTR_TO_OBJ(cmd->uc_compl_arg));
Object obj = NIL;
if (cmd->uc_argt & EX_COUNT) {
if (cmd->uc_def >= 0) {
- snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def);
- obj = STRING_OBJ(cstr_to_string(str)); // -count=N
+ snprintf(str, sizeof(str), "%" PRId64, cmd->uc_def);
+ obj = CSTR_TO_OBJ(str); // -count=N
} else {
- obj = STRING_OBJ(cstr_to_string("0")); // -count
+ obj = CSTR_TO_OBJ("0"); // -count
}
}
PUT(d, "count", obj);
@@ -1793,12 +1784,12 @@ Dictionary commands_array(buf_T *buf)
obj = NIL;
if (cmd->uc_argt & EX_RANGE) {
if (cmd->uc_argt & EX_DFLALL) {
- obj = STRING_OBJ(cstr_to_string("%")); // -range=%
+ obj = CSTR_TO_OBJ("%"); // -range=%
} else if (cmd->uc_def >= 0) {
- snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def);
- obj = STRING_OBJ(cstr_to_string(str)); // -range=N
+ snprintf(str, sizeof(str), "%" PRId64, cmd->uc_def);
+ obj = CSTR_TO_OBJ(str); // -range=N
} else {
- obj = STRING_OBJ(cstr_to_string(".")); // -range
+ obj = CSTR_TO_OBJ("."); // -range
}
}
PUT(d, "range", obj);
@@ -1807,13 +1798,13 @@ Dictionary commands_array(buf_T *buf)
for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
if (addr_type_complete[j].expand != ADDR_LINES
&& addr_type_complete[j].expand == cmd->uc_addr_type) {
- obj = STRING_OBJ(cstr_to_string(addr_type_complete[j].name));
+ obj = CSTR_TO_OBJ(addr_type_complete[j].name);
break;
}
}
PUT(d, "addr", obj);
- PUT(rv, (char *)cmd->uc_name, DICTIONARY_OBJ(d));
+ PUT(rv, cmd->uc_name, DICTIONARY_OBJ(d));
}
return rv;
}
diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h
index b6bf6c1e33..bcabf9460b 100644
--- a/src/nvim/usercmd.h
+++ b/src/nvim/usercmd.h
@@ -1,28 +1,29 @@
-#ifndef NVIM_USERCMD_H
-#define NVIM_USERCMD_H
+#pragma once
+#include <stddef.h> // IWYU pragma: keep
#include <stdint.h>
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
-#include "nvim/garray.h"
-#include "nvim/types.h"
+#include "nvim/garray_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
typedef struct ucmd {
- char *uc_name; // The command name
- uint32_t uc_argt; // The argument type
- char *uc_rep; // The command's replacement string
- long uc_def; // The default value for a range/count
- int uc_compl; // completion type
- cmd_addr_T uc_addr_type; // The command's address type
- sctx_T uc_script_ctx; // SCTX where the command was defined
- char *uc_compl_arg; // completion argument if any
- LuaRef uc_compl_luaref; // Reference to Lua completion function
- LuaRef uc_preview_luaref; // Reference to Lua preview function
- LuaRef uc_luaref; // Reference to Lua function
+ char *uc_name; ///< The command name
+ uint32_t uc_argt; ///< The argument type
+ char *uc_rep; ///< The command's replacement string
+ int64_t uc_def; ///< The default value for a range/count
+ int uc_compl; ///< completion type
+ cmd_addr_T uc_addr_type; ///< The command's address type
+ sctx_T uc_script_ctx; ///< SCTX where the command was defined
+ char *uc_compl_arg; ///< completion argument if any
+ LuaRef uc_compl_luaref; ///< Reference to Lua completion function
+ LuaRef uc_preview_luaref; ///< Reference to Lua preview function
+ LuaRef uc_luaref; ///< Reference to Lua function
} ucmd_T;
-#define UC_BUFFER 1 // -buffer: local to current buffer
+enum { UC_BUFFER = 1, }; ///< -buffer: local to current buffer
extern garray_T ucmds;
@@ -32,4 +33,3 @@ extern garray_T ucmds;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "usercmd.h.generated.h"
#endif
-#endif // NVIM_USERCMD_H
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 417e5116a5..cb9088afae 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file version.c
///
/// Nvim was forked from Vim 7.4.160.
@@ -9,33 +6,33 @@
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
-#include "auto/config.h"
#include "auto/versiondef.h" // version info generated by the build system
#include "auto/versiondef_git.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/lua/executor.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/os/os_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/os.h"
#include "nvim/strings.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
// for ":version", ":intro", and "nvim --version"
#ifndef NVIM_VERSION_MEDIUM
@@ -57,17 +54,6 @@ char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS;
# include "version.c.generated.h"
#endif
-static char *features[] = {
-#ifdef HAVE_ACL
- "+acl",
-#else
- "-acl",
-#endif
-
- "+tui",
- NULL
-};
-
// clang-format off
static const int included_patches[] = {
2424,
@@ -91,7 +77,7 @@ static const int included_patches[] = {
2406,
2405,
2404,
- // 2403,
+ 2403,
2402,
2401,
2400,
@@ -121,7 +107,7 @@ static const int included_patches[] = {
2376,
2375,
2374,
- // 2373,
+ 2373,
2372,
// 2371,
2370,
@@ -218,7 +204,7 @@ static const int included_patches[] = {
// 2279,
2278,
// 2277,
- // 2276,
+ 2276,
2275,
2274,
// 2273,
@@ -346,7 +332,7 @@ static const int included_patches[] = {
2151,
2150,
2149,
- // 2148,
+ 2148,
2147,
// 2146,
2145,
@@ -395,12 +381,12 @@ static const int included_patches[] = {
2102,
2101,
2100,
- // 2099,
+ 2099,
2098,
2097,
2096,
2095,
- // 2094,
+ 2094,
// 2093,
// 2092,
2091,
@@ -424,15 +410,15 @@ static const int included_patches[] = {
2073,
2072,
// 2071,
- // 2070,
- // 2069,
- // 2068,
- // 2067,
- // 2066,
+ 2070,
+ 2069,
+ 2068,
+ 2067,
+ 2066,
2065,
2064,
2063,
- // 2062,
+ 2062,
2061,
2060,
2059,
@@ -441,16 +427,16 @@ static const int included_patches[] = {
2056,
2055,
2054,
- // 2053,
+ 2053,
2052,
2051,
2050,
2049,
- // 2048,
- // 2047,
- // 2046,
+ 2048,
+ 2047,
+ 2046,
2045,
- // 2044,
+ 2044,
2043,
2042,
2041,
@@ -667,7 +653,7 @@ static const int included_patches[] = {
1830,
1829,
1828,
- // 1827,
+ 1827,
1826,
1825,
1824,
@@ -795,7 +781,7 @@ static const int included_patches[] = {
1702,
1701,
// 1700,
- // 1699,
+ 1699,
1698,
1697,
1696,
@@ -972,43 +958,43 @@ static const int included_patches[] = {
// 1525,
1524,
// 1523,
- // 1522,
- // 1521,
- // 1520,
+ 1522,
+ 1521,
+ 1520,
1519,
- // 1518,
- // 1517,
+ 1518,
+ 1517,
1516,
- // 1515,
+ 1515,
1514,
- // 1513,
+ 1513,
1512,
- // 1511,
+ 1511,
1510,
1509,
- // 1508,
+ 1508,
1507,
1506,
1505,
1504,
1503,
- // 1502,
+ 1502,
1501,
1500,
- // 1499,
+ 1499,
1498,
- // 1497,
- // 1496,
- // 1495,
- // 1494,
- // 1493,
+ 1497,
+ 1496,
+ 1495,
+ 1494,
+ 1493,
1492,
1491,
1490,
1489,
1488,
1487,
- // 1486,
+ 1486,
1485,
1484,
1483,
@@ -1031,83 +1017,83 @@ static const int included_patches[] = {
1466,
1465,
1464,
- // 1463,
+ 1463,
1462,
1461,
- // 1460,
- // 1459,
+ 1460,
+ 1459,
1458,
1457,
1456,
- // 1455,
+ 1455,
1454,
- // 1453,
- // 1452,
- // 1451,
- // 1450,
- // 1449,
- // 1448,
- // 1447,
- // 1446,
- // 1445,
- // 1444,
- // 1443,
- // 1442,
- // 1441,
+ 1453,
+ 1452,
+ 1451,
+ 1450,
+ 1449,
+ 1448,
+ 1447,
+ 1446,
+ 1445,
+ 1444,
+ 1443,
+ 1442,
+ 1441,
1440,
1439,
- // 1438,
+ 1438,
1437,
1436,
1435,
1434,
1433,
- // 1432,
- // 1431,
- // 1430,
- // 1429,
- // 1428,
- // 1427,
- // 1426,
+ 1432,
+ 1431,
+ 1430,
+ 1429,
+ 1428,
+ 1427,
+ 1426,
1425,
1424,
- // 1423,
- // 1422,
- // 1421,
- // 1420,
- // 1419,
+ 1423,
+ 1422,
+ 1421,
+ 1420,
+ 1419,
1418,
// 1417,
- // 1416,
+ 1416,
1415,
- // 1414,
+ 1414,
// 1413,
1412,
1411,
- // 1410,
+ 1410,
1409,
- // 1408,
- // 1407,
- // 1406,
- // 1405,
+ 1408,
+ 1407,
+ 1406,
+ 1405,
1404,
1403,
- // 1402,
+ 1402,
1401,
- // 1400,
- // 1399,
+ 1400,
+ 1399,
1398,
1397,
- // 1396,
+ 1396,
// 1395,
1394,
1393,
1392,
- // 1391,
+ 1391,
1390,
// 1389,
- // 1388,
- // 1387,
+ 1388,
+ 1387,
1386,
1385,
1384,
@@ -1118,7 +1104,7 @@ static const int included_patches[] = {
1379,
1378,
1377,
- // 1376,
+ 1376,
1375,
1374,
1373,
@@ -1130,12 +1116,12 @@ static const int included_patches[] = {
1367,
1366,
1365,
- // 1364,
+ 1364,
1363,
1362,
1361,
1360,
- // 1359,
+ 1359,
1358,
1357,
1356,
@@ -1143,38 +1129,38 @@ static const int included_patches[] = {
1354,
1353,
1352,
- // 1351,
+ 1351,
1350,
1349,
1348,
1347,
1346,
1345,
- // 1344,
- // 1343,
+ 1344,
+ 1343,
1342,
- // 1341,
- // 1340,
+ 1341,
+ 1340,
1339,
1338,
- // 1337,
+ 1337,
1336,
- // 1335,
+ 1335,
1334,
- // 1333,
- // 1332,
+ 1333,
+ 1332,
1331,
1330,
- // 1329,
- // 1328,
+ 1329,
+ 1328,
1327,
- // 1326,
+ 1326,
1325,
1324,
1323,
1322,
- // 1321,
- // 1320,
+ 1321,
+ 1320,
1319,
1318,
1317,
@@ -1214,11 +1200,11 @@ static const int included_patches[] = {
1283,
1282,
1281,
- // 1280,
+ 1280,
1279,
- // 1278,
+ 1278,
1277,
- // 1276,
+ 1276,
1275,
1274,
1273,
@@ -1227,9 +1213,9 @@ static const int included_patches[] = {
1270,
1269,
1268,
- // 1267,
+ 1267,
1266,
- // 1265,
+ 1265,
1264,
1263,
1262,
@@ -1268,7 +1254,7 @@ static const int included_patches[] = {
1229,
1228,
1227,
- // 1226,
+ 1226,
1225,
// 1224,
1223,
@@ -1361,7 +1347,7 @@ static const int included_patches[] = {
// 1136,
1135,
1134,
- // 1133,
+ 1133,
1132,
1131,
1130,
@@ -1391,7 +1377,7 @@ static const int included_patches[] = {
// 1106,
1105,
1104,
- // 1103,
+ 1103,
1102,
1101,
1100,
@@ -1404,7 +1390,7 @@ static const int included_patches[] = {
1093,
1092,
1091,
- // 1090,
+ 1090,
1089,
1088,
1087,
@@ -1459,14 +1445,14 @@ static const int included_patches[] = {
1038,
1037,
1036,
- // 1035,
+ 1035,
1034,
1033,
1032,
1031,
1030,
1029,
- // 1028,
+ 1028,
1027,
1026,
1025,
@@ -1475,7 +1461,7 @@ static const int included_patches[] = {
1022,
1021,
1020,
- // 1019,
+ 1019,
1018,
1017,
1016,
@@ -1524,7 +1510,7 @@ static const int included_patches[] = {
973,
972,
971,
- // 970,
+ 970,
// 969,
968,
967,
@@ -1554,7 +1540,7 @@ static const int included_patches[] = {
943,
942,
941,
- // 940,
+ 940,
939,
938,
937,
@@ -1566,7 +1552,7 @@ static const int included_patches[] = {
931,
930,
929,
- // 928,
+ 928,
927,
926,
925,
@@ -1576,7 +1562,7 @@ static const int included_patches[] = {
921,
920,
919,
- // 918,
+ 918,
917,
916,
915,
@@ -1585,10 +1571,10 @@ static const int included_patches[] = {
912,
911,
910,
- // 909,
+ 909,
908,
907,
- // 906,
+ 906,
905,
904,
903,
@@ -1599,12 +1585,12 @@ static const int included_patches[] = {
898,
897,
896,
- // 895,
+ 895,
// 894,
893,
892,
891,
- // 890,
+ 890,
889,
888,
887,
@@ -1614,8 +1600,8 @@ static const int included_patches[] = {
883,
882,
881,
- // 880,
- // 879,
+ 880,
+ 879,
878,
877,
// 876,
@@ -1624,7 +1610,7 @@ static const int included_patches[] = {
873,
872,
871,
- // 870,
+ 870,
869,
868,
867,
@@ -1649,7 +1635,7 @@ static const int included_patches[] = {
848,
847,
846,
- // 845,
+ 845,
844,
843,
842,
@@ -1670,11 +1656,11 @@ static const int included_patches[] = {
827,
826,
825,
- // 824,
+ 824,
823,
822,
821,
- // 820,
+ 820,
819,
818,
817,
@@ -1733,7 +1719,7 @@ static const int included_patches[] = {
764,
763,
762,
- // 761,
+ 761,
760,
759,
758,
@@ -1787,11 +1773,11 @@ static const int included_patches[] = {
// 710,
709,
708,
- // 707,
+ 707,
706,
705,
704,
- // 703,
+ 703,
702,
701,
700,
@@ -1800,26 +1786,26 @@ static const int included_patches[] = {
697,
696,
695,
- // 694,
+ 694,
693,
692,
- // 691,
- // 690,
- // 689,
- // 688,
+ 691,
+ 690,
+ 689,
+ 688,
687,
686,
685,
- // 684,
+ 684,
683,
- // 682,
- // 681,
+ 682,
+ 681,
680,
679,
678,
677,
- // 676,
- // 675,
+ 676,
+ 675,
674,
673,
672,
@@ -1827,11 +1813,11 @@ static const int included_patches[] = {
670,
669,
668,
- // 667,
+ 667,
666,
- // 665,
+ 665,
664,
- // 663,
+ 663,
662,
661,
660,
@@ -1839,8 +1825,8 @@ static const int included_patches[] = {
658,
657,
656,
- // 655,
- // 654,
+ 655,
+ 654,
653,
652,
651,
@@ -1851,16 +1837,16 @@ static const int included_patches[] = {
646,
645,
644,
- // 643,
+ 643,
642,
641,
640,
- // 639,
- // 638,
+ 639,
+ 638,
637,
// 636,
635,
- // 634,
+ 634,
633,
632,
631,
@@ -1882,7 +1868,7 @@ static const int included_patches[] = {
615,
614,
613,
- // 612,
+ 612,
611,
610,
609,
@@ -1893,7 +1879,7 @@ static const int included_patches[] = {
604,
603,
602,
- // 601,
+ 601,
600,
599,
598,
@@ -1912,10 +1898,10 @@ static const int included_patches[] = {
585,
584,
583,
- // 582,
+ 582,
581,
580,
- // 579,
+ 579,
578,
577,
576,
@@ -2127,7 +2113,7 @@ static const int included_patches[] = {
370,
369,
368,
- // 367,
+ 367,
366,
365,
364,
@@ -2182,7 +2168,7 @@ static const int included_patches[] = {
315,
314,
313,
- // 312,
+ 312,
311,
310,
309,
@@ -2190,7 +2176,7 @@ static const int included_patches[] = {
307,
306,
305,
- // 304,
+ 304,
303,
302,
301,
@@ -2422,7 +2408,7 @@ static const int included_patches[] = {
75,
74,
73,
- // 72,
+ 72,
71,
70,
69,
@@ -2432,7 +2418,7 @@ static const int included_patches[] = {
// 65,
64,
63,
- // 62,
+ 62,
61,
60,
59,
@@ -2444,8 +2430,8 @@ static const int included_patches[] = {
53,
52,
51,
- // 50,
- // 49,
+ 50,
+ 49,
48,
47,
46,
@@ -2507,14 +2493,13 @@ bool has_nvim_version(const char *const version_str)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
const char *p = version_str;
- int major = 0;
int minor = 0;
int patch = 0;
if (!ascii_isdigit(*p)) {
return false;
}
- major = atoi(p);
+ int major = atoi(p);
p = strchr(p, '.'); // Find the next dot.
if (p) {
@@ -2571,6 +2556,11 @@ Dictionary version_dict(void)
PUT(d, "major", INTEGER_OBJ(NVIM_VERSION_MAJOR));
PUT(d, "minor", INTEGER_OBJ(NVIM_VERSION_MINOR));
PUT(d, "patch", INTEGER_OBJ(NVIM_VERSION_PATCH));
+#ifndef NVIM_VERSION_BUILD
+ PUT(d, "build", NIL);
+#else
+ PUT(d, "build", CSTR_AS_OBJ(NVIM_VERSION_BUILD));
+#endif
PUT(d, "prerelease", BOOLEAN_OBJ(NVIM_VERSION_PRERELEASE[0] != '\0'));
PUT(d, "api_level", INTEGER_OBJ(NVIM_API_LEVEL));
PUT(d, "api_compatible", INTEGER_OBJ(NVIM_API_LEVEL_COMPAT));
@@ -2619,21 +2609,6 @@ static void version_msg(char *s)
version_msg_wrap(s, false);
}
-/// List all features.
-/// This does not use list_in_columns (as in Vim), because there are only a
-/// few, and we do not start at a new line.
-static void list_features(void)
-{
- version_msg(_("\n\nFeatures: "));
- for (int i = 0; features[i] != NULL; i++) {
- version_msg(features[i]);
- if (features[i + 1] != NULL) {
- version_msg(" ");
- }
- }
- version_msg("\nSee \":help feature-compile\"\n\n");
-}
-
/// List string items nicely aligned in columns.
/// When "size" is < 0 then the last entry is marked with NULL.
/// The entry with index "current" is inclosed in [].
@@ -2643,8 +2618,7 @@ void list_in_columns(char **items, int size, int current)
int width = 0;
// Find the length of the longest item, use that + 1 as the column width.
- int i;
- for (i = 0; size < 0 ? items[i] != NULL : i < size; i++) {
+ for (int i = 0; size < 0 ? items[i] != NULL : i < size; i++) {
int l = vim_strsize(items[i]) + (i == current ? 2 : 0);
if (l > width) {
@@ -2656,7 +2630,7 @@ void list_in_columns(char **items, int size, int current)
if (Columns < width) {
// Not enough screen columns - show one per line
- for (i = 0; i < item_count; i++) {
+ for (int i = 0; i < item_count; i++) {
version_msg_wrap(items[i], i == current);
if (msg_col > 0 && i < item_count - 1) {
msg_putchar('\n');
@@ -2672,7 +2646,7 @@ void list_in_columns(char **items, int size, int current)
int cur_row = 1;
// "i" counts columns then rows. "idx" counts rows then columns.
- for (i = 0; !got_int && i < nrow * ncol; i++) {
+ for (int i = 0; !got_int && i < nrow * ncol; i++) {
int idx = (i / ncol) + (i % ncol) * nrow;
if (idx < item_count) {
int last_col = (i + 1) % ncol == 0;
@@ -2712,59 +2686,48 @@ void list_lua_version(void)
Object ret = nlua_exec(cstr_as_string(code), (Array)ARRAY_DICT_INIT, &err);
assert(!ERROR_SET(&err));
assert(ret.type == kObjectTypeString);
- msg(ret.data.string.data);
+ msg(ret.data.string.data, 0);
api_free_object(ret);
}
void list_version(void)
{
- msg(longVersion);
- msg(version_buildtype);
+ msg(longVersion, 0);
+ msg(version_buildtype, 0);
list_lua_version();
+
+ if (p_verbose > 0) {
#ifndef NDEBUG
- msg(version_cflags);
+ msg(version_cflags, 0);
#endif
+ version_msg("\n\n");
-#ifdef HAVE_PATHDEF
-
- if ((*compiled_user != NUL) || (*compiled_sys != NUL)) {
- msg_puts(_("\nCompiled "));
+#ifdef SYS_VIMRC_FILE
+ version_msg(_(" system vimrc file: \""));
+ version_msg(SYS_VIMRC_FILE);
+ version_msg("\"\n");
+#endif
- if (*compiled_user != NUL) {
- msg_puts(_("by "));
- msg_puts((const char *)compiled_user);
+#ifdef HAVE_PATHDEF
+ if (*default_vim_dir != NUL) {
+ version_msg(_(" fall-back for $VIM: \""));
+ version_msg(default_vim_dir);
+ version_msg("\"\n");
}
- if (*compiled_sys != NUL) {
- msg_puts("@");
- msg_puts((const char *)compiled_sys);
+ if (*default_vimruntime_dir != NUL) {
+ version_msg(_(" f-b for $VIMRUNTIME: \""));
+ version_msg(default_vimruntime_dir);
+ version_msg("\"\n");
}
+#endif
}
-#endif // ifdef HAVE_PATHDEF
-
- list_features();
-
-#ifdef SYS_VIMRC_FILE
- version_msg(_(" system vimrc file: \""));
- version_msg(SYS_VIMRC_FILE);
- version_msg("\"\n");
-#endif // ifdef SYS_VIMRC_FILE
-#ifdef HAVE_PATHDEF
-
- if (*default_vim_dir != NUL) {
- version_msg(_(" fall-back for $VIM: \""));
- version_msg(default_vim_dir);
- version_msg("\"\n");
- }
-
- if (*default_vimruntime_dir != NUL) {
- version_msg(_(" f-b for $VIMRUNTIME: \""));
- version_msg(default_vimruntime_dir);
- version_msg("\"\n");
- }
-#endif // ifdef HAVE_PATHDEF
- version_msg("\nRun :checkhealth for more info");
+ version_msg(p_verbose > 0
+ ? "\nRun :checkhealth for more info"
+ : (starting
+ ? "\nRun \"nvim -V1 -v\" for more info"
+ : "\nRun \":verbose version\" for more info"));
}
/// Show the intro message when not editing a file.
@@ -2785,13 +2748,6 @@ void maybe_intro_message(void)
/// @param colon true for ":intro"
void intro_message(int colon)
{
- int i;
- long row;
- long blanklines;
- int sponsor;
- char *p;
- char *mesg;
- int mesg_size;
static char *(lines[]) = {
N_(NVIM_VERSION_LONG),
"",
@@ -2813,7 +2769,7 @@ void intro_message(int colon)
size_t lines_size = ARRAY_SIZE(lines);
assert(lines_size <= LONG_MAX);
- blanklines = Rows - ((long)lines_size - 1L);
+ int blanklines = Rows - ((int)lines_size - 1);
// Don't overwrite a statusline. Depends on 'cmdheight'.
if (p_ls > 1) {
@@ -2826,17 +2782,17 @@ void intro_message(int colon)
// Show the sponsor and register message one out of four times, the Uganda
// message two out of four times.
- sponsor = (int)time(NULL);
+ int sponsor = (int)time(NULL);
sponsor = ((sponsor & 2) == 0) - ((sponsor & 4) == 0);
// start displaying the message lines after half of the blank lines
- row = blanklines / 2;
+ int row = blanklines / 2;
if (((row >= 2) && (Columns >= 50)) || colon) {
- for (i = 0; i < (int)ARRAY_SIZE(lines); i++) {
- p = lines[i];
- mesg = NULL;
- mesg_size = 0;
+ for (int i = 0; i < (int)ARRAY_SIZE(lines); i++) {
+ char *p = lines[i];
+ char *mesg = NULL;
+ int mesg_size = 0;
if (strstr(p, "news") != NULL) {
p = _(p);
@@ -2846,18 +2802,15 @@ void intro_message(int colon)
mesg = xmallocz((size_t)mesg_size);
snprintf(mesg, (size_t)mesg_size + 1, p,
STR(NVIM_VERSION_MAJOR), STR(NVIM_VERSION_MINOR));
- }
-
- if (sponsor != 0) {
+ } else if (sponsor != 0) {
if (strstr(p, "children") != NULL) {
- mesg = sponsor < 0
- ? _("Sponsor Vim development!")
- : _("Become a registered Vim user!");
- }
- if (strstr(p, "iccf") != NULL) {
- mesg = sponsor < 0
- ? _("type :help sponsor<Enter> for information ")
- : _("type :help register<Enter> for information ");
+ p = sponsor < 0
+ ? N_("Sponsor Vim development!")
+ : N_("Become a registered Vim user!");
+ } else if (strstr(p, "iccf") != NULL) {
+ p = sponsor < 0
+ ? N_("type :help sponsor<Enter> for information ")
+ : N_("type :help register<Enter> for information ");
}
}
@@ -2879,22 +2832,14 @@ void intro_message(int colon)
}
}
}
-
- // Make the wait-return message appear just below the text.
- if (colon) {
- assert(row <= INT_MAX);
- msg_row = (int)row;
- }
}
-static void do_intro_line(long row, char *mesg, int attr)
+static void do_intro_line(int row, char *mesg, int attr)
{
- char *p;
int l;
- int clen;
// Center the message horizontally.
- long col = vim_strsize(mesg);
+ int col = vim_strsize(mesg);
col = (Columns - col) / 2;
@@ -2902,21 +2847,18 @@ static void do_intro_line(long row, char *mesg, int attr)
col = 0;
}
+ grid_line_start(&default_grid, row);
// Split up in parts to highlight <> items differently.
- for (p = mesg; *p != NUL; p += l) {
- clen = 0;
-
+ for (char *p = mesg; *p != NUL; p += l) {
for (l = 0;
p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>'));
l++) {
- clen += ptr2cells(p + l);
l += utfc_ptr2len(p + l) - 1;
}
assert(row <= INT_MAX && col <= INT_MAX);
- grid_puts_len(&default_grid, p, l, (int)row, (int)col,
- *p == '<' ? HL_ATTR(HLF_8) : attr);
- col += clen;
+ col += grid_line_puts(col, p, l, *p == '<' ? HL_ATTR(HLF_8) : attr);
}
+ grid_line_flush();
}
/// ":intro": clear screen, display intro screen and wait for return.
diff --git a/src/nvim/version.h b/src/nvim/version.h
index 484350edee..94219aaddc 100644
--- a/src/nvim/version.h
+++ b/src/nvim/version.h
@@ -1,12 +1,14 @@
-#ifndef NVIM_VERSION_H
-#define NVIM_VERSION_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/macros.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
// defined in version.c
extern char *Version;
extern char *longVersion;
+#ifndef NDEBUG
+extern char *version_cflags;
+#endif
//
// Vim version number, name, etc. Patchlevel is defined in version.c.
@@ -29,4 +31,3 @@ extern char *longVersion;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "version.h.generated.h"
#endif
-#endif // NVIM_VERSION_H
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
deleted file mode 100644
index c4baa911f1..0000000000
--- a/src/nvim/vim.h
+++ /dev/null
@@ -1,273 +0,0 @@
-#ifndef NVIM_VIM_H
-#define NVIM_VIM_H
-
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-// Some defines from the old feature.h
-#define SESSION_FILE "Session.vim"
-#define MAX_MSG_HIST_LEN 200
-#define SYS_OPTWIN_FILE "$VIMRUNTIME/optwin.vim"
-#define RUNTIME_DIRNAME "runtime"
-
-#include "auto/config.h"
-#define HAVE_PATHDEF
-
-// Check if configure correctly managed to find sizeof(int). If this failed,
-// it becomes zero. This is likely a problem of not being able to run the
-// test program. Other items from configure may also be wrong then!
-#if (SIZEOF_INT == 0)
-# error Configure did not run properly.
-#endif
-
-#include "nvim/os/os_defs.h" // bring lots of system header files
-
-/// length of a buffer to store a number in ASCII (64 bits binary + NUL)
-enum { NUMBUFLEN = 65, };
-
-#define MAX_TYPENR 65535
-
-#define ROOT_UID 0
-
-#include "nvim/gettext.h"
-#include "nvim/keycodes.h"
-#include "nvim/macros.h"
-
-// special attribute addition: Put message in history
-#define MSG_HIST 0x1000
-
-// Values for State
-//
-// The lower bits up to 0x80 are used to distinguish normal/visual/op_pending
-// /cmdline/insert/replace/terminal mode. This is used for mapping. If none
-// of these bits are set, no mapping is done. See the comment above do_map().
-// The upper bits are used to distinguish between other states and variants of
-// the base modes.
-
-#define MODE_NORMAL 0x01 // Normal mode, command expected
-#define MODE_VISUAL 0x02 // Visual mode - use get_real_state()
-#define MODE_OP_PENDING 0x04 // Normal mode, operator is pending - use
- // get_real_state()
-#define MODE_CMDLINE 0x08 // Editing the command line
-#define MODE_INSERT 0x10 // Insert mode, also for Replace mode
-#define MODE_LANGMAP 0x20 // Language mapping, can be combined with
- // MODE_INSERT and MODE_CMDLINE
-#define MODE_SELECT 0x40 // Select mode, use get_real_state()
-#define MODE_TERMINAL 0x80 // Terminal mode
-
-#define MAP_ALL_MODES 0xff // all mode bits used for mapping
-
-#define REPLACE_FLAG 0x100 // Replace mode flag
-#define MODE_REPLACE (REPLACE_FLAG | MODE_INSERT)
-#define VREPLACE_FLAG 0x200 // Virtual-replace mode flag
-#define MODE_VREPLACE (REPLACE_FLAG | VREPLACE_FLAG | MODE_INSERT)
-#define MODE_LREPLACE (REPLACE_FLAG | MODE_LANGMAP)
-
-#define MODE_NORMAL_BUSY (0x1000 | MODE_NORMAL) // Normal mode, busy with a command
-#define MODE_HITRETURN (0x2000 | MODE_NORMAL) // waiting for return or command
-#define MODE_ASKMORE 0x3000 // Asking if you want --more--
-#define MODE_SETWSIZE 0x4000 // window size has changed
-#define MODE_EXTERNCMD 0x5000 // executing an external command
-#define MODE_SHOWMATCH (0x6000 | MODE_INSERT) // show matching paren
-#define MODE_CONFIRM 0x7000 // ":confirm" prompt
-
-/// Directions.
-typedef enum {
- kDirectionNotSet = 0,
- FORWARD = 1,
- BACKWARD = (-1),
- FORWARD_FILE = 3,
- BACKWARD_FILE = (-3),
-} Direction;
-
-// return values for functions
-#if !(defined(OK) && (OK == 1))
-// OK already defined to 1 in MacOS X curses, skip this
-# define OK 1
-#endif
-#define FAIL 0
-#define NOTDONE 2 // not OK or FAIL but skipped
-
-// Type values for type().
-#define VAR_TYPE_NUMBER 0
-#define VAR_TYPE_STRING 1
-#define VAR_TYPE_FUNC 2
-#define VAR_TYPE_LIST 3
-#define VAR_TYPE_DICT 4
-#define VAR_TYPE_FLOAT 5
-#define VAR_TYPE_BOOL 6
-#define VAR_TYPE_SPECIAL 7
-#define VAR_TYPE_BLOB 10
-
-// values for xp_context when doing command line completion
-
-enum {
- EXPAND_UNSUCCESSFUL = -2,
- EXPAND_OK = -1,
- EXPAND_NOTHING = 0,
- EXPAND_COMMANDS,
- EXPAND_FILES,
- EXPAND_DIRECTORIES,
- EXPAND_SETTINGS,
- EXPAND_BOOL_SETTINGS,
- EXPAND_TAGS,
- EXPAND_OLD_SETTING,
- EXPAND_HELP,
- EXPAND_BUFFERS,
- EXPAND_EVENTS,
- EXPAND_MENUS,
- EXPAND_SYNTAX,
- EXPAND_HIGHLIGHT,
- EXPAND_AUGROUP,
- EXPAND_USER_VARS,
- EXPAND_MAPPINGS,
- EXPAND_TAGS_LISTFILES,
- EXPAND_FUNCTIONS,
- EXPAND_USER_FUNC,
- EXPAND_EXPRESSION,
- EXPAND_MENUNAMES,
- EXPAND_USER_COMMANDS,
- EXPAND_USER_CMD_FLAGS,
- EXPAND_USER_NARGS,
- EXPAND_USER_COMPLETE,
- EXPAND_ENV_VARS,
- EXPAND_LANGUAGE,
- EXPAND_COLORS,
- EXPAND_COMPILER,
- EXPAND_USER_DEFINED,
- EXPAND_USER_LIST,
- EXPAND_USER_LUA,
- EXPAND_SHELLCMD,
- EXPAND_SIGN,
- EXPAND_PROFILE,
- EXPAND_BEHAVE,
- EXPAND_FILETYPE,
- EXPAND_FILES_IN_PATH,
- EXPAND_OWNSYNTAX,
- EXPAND_LOCALES,
- EXPAND_HISTORY,
- EXPAND_USER,
- EXPAND_SYNTIME,
- EXPAND_USER_ADDR_TYPE,
- EXPAND_PACKADD,
- EXPAND_MESSAGES,
- EXPAND_MAPCLEAR,
- EXPAND_ARGLIST,
- EXPAND_DIFF_BUFFERS,
- EXPAND_BREAKPOINT,
- EXPAND_SCRIPTNAMES,
- EXPAND_CHECKHEALTH,
- EXPAND_LUA,
-};
-
-// Minimal size for block 0 of a swap file.
-// NOTE: This depends on size of struct block0! It's not done with a sizeof(),
-// because struct block0 is defined in memline.c (Sorry).
-// The maximal block size is arbitrary.
-
-#define MIN_SWAP_PAGE_SIZE 1048
-#define MAX_SWAP_PAGE_SIZE 50000
-
-#define STATUS_HEIGHT 1 // height of a status line under a window
-#define QF_WINHEIGHT 10 // default height for quickfix window
-
-// Buffer sizes
-
-#ifndef CMDBUFFSIZE
-# define CMDBUFFSIZE 256 // size of the command processing buffer
-#endif
-
-#define LSIZE 512 // max. size of a line in the tags file
-
-#define DIALOG_MSG_SIZE 1000 // buffer size for dialog_msg()
-
-enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext()
-
-// Maximum length of key sequence to be mapped.
-// Must be able to hold an Amiga resize report.
-
-#define MAXMAPLEN 50
-
-// Size in bytes of the hash used in the undo file.
-#define UNDO_HASH_SIZE 32
-
-#define CLEAR_FIELD(field) memset(&(field), 0, sizeof(field))
-#define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr)))
-
-// defines to avoid typecasts from (char_u *) to (char *) and back
-// (vim_strchr() is now in strings.c)
-
-#ifndef HAVE_STRNLEN
-# define strnlen xstrnlen // Older versions of SunOS may not have strnlen
-#endif
-
-#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
-#ifdef HAVE_STRCASECMP
-# define STRICMP(d, s) strcasecmp((char *)(d), (char *)(s))
-#else
-# ifdef HAVE_STRICMP
-# define STRICMP(d, s) stricmp((char *)(d), (char *)(s))
-# else
-# define STRICMP(d, s) vim_stricmp((char *)(d), (char *)(s))
-# endif
-#endif
-
-// Like strcpy() but allows overlapped source and destination.
-#define STRMOVE(d, s) memmove((d), (s), strlen(s) + 1)
-
-#ifdef HAVE_STRNCASECMP
-# define STRNICMP(d, s, n) strncasecmp((char *)(d), (char *)(s), (size_t)(n))
-#else
-# ifdef HAVE_STRNICMP
-# define STRNICMP(d, s, n) strnicmp((char *)(d), (char *)(s), (size_t)(n))
-# else
-# define STRNICMP(d, s, n) vim_strnicmp((char *)(d), (char *)(s), (size_t)(n))
-# endif
-#endif
-
-#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
-
-// Character used as separated in autoload function/variable names.
-#define AUTOLOAD_CHAR '#'
-
-#include "nvim/message.h"
-
-// Prefer using semsg(), because perror() may send the output to the wrong
-// destination and mess up the screen.
-#define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno))
-
-#include "nvim/path.h"
-
-// Enums need a typecast to be used as array index.
-#define HL_ATTR(n) hl_attr_active[(int)(n)]
-
-/// Maximum number of bytes in a multi-byte character. It can be one 32-bit
-/// character of up to 6 bytes, or one 16-bit character of up to three bytes
-/// plus six following composing characters of three bytes each.
-#define MB_MAXBYTES 21
-
-#ifndef MSWIN
-/// Headless (no UI) error message handler.
-# define os_errmsg(str) fprintf(stderr, "%s", (str))
-/// Headless (no UI) message handler.
-# define os_msg(str) printf("%s", (str))
-#endif
-
-#include "nvim/buffer_defs.h" // buffer and windows
-#include "nvim/ex_cmds_defs.h" // Ex command defines
-#include "nvim/globals.h" // global variables and messages
-
-// Lowest number used for window ID. Cannot have this many windows per tab.
-#define LOWEST_WIN_ID 1000
-
-// BSD is supposed to cover FreeBSD and similar systems.
-#if (defined(BSD) || defined(__FreeBSD_kernel__)) \
- && (defined(S_ISCHR) || defined(S_IFCHR))
-# define OPEN_CHR_FILES
-#endif
-
-// Replacement for nchar used by nv_replace().
-#define REPLACE_CR_NCHAR (-1)
-#define REPLACE_NL_NCHAR (-2)
-
-#endif // NVIM_VIM_H
diff --git a/src/nvim/vim_defs.h b/src/nvim/vim_defs.h
new file mode 100644
index 0000000000..faf79b1c79
--- /dev/null
+++ b/src/nvim/vim_defs.h
@@ -0,0 +1,41 @@
+#pragma once
+
+// Some defines from the old feature.h
+#define SESSION_FILE "Session.vim"
+#define MAX_MSG_HIST_LEN 200
+#define SYS_OPTWIN_FILE "$VIMRUNTIME/optwin.vim"
+#define RUNTIME_DIRNAME "runtime"
+
+#include "auto/config.h"
+
+// Check if configure correctly managed to find sizeof(int). If this failed,
+// it becomes zero. This is likely a problem of not being able to run the
+// test program. Other items from configure may also be wrong then!
+#if (SIZEOF_INT == 0)
+# error Configure did not run properly.
+#endif
+
+// bring lots of system header files
+#include "nvim/os/os_defs.h" // IWYU pragma: keep
+
+/// length of a buffer to store a number in ASCII (64 bits binary + NUL)
+enum { NUMBUFLEN = 65, };
+
+#define MAX_TYPENR 65535
+
+/// Directions.
+typedef enum {
+ kDirectionNotSet = 0,
+ FORWARD = 1,
+ BACKWARD = -1,
+ FORWARD_FILE = 3,
+ BACKWARD_FILE = -3,
+} Direction;
+
+// return values for functions
+#if !(defined(OK) && (OK == 1))
+// OK already defined to 1 in MacOS X curses, skip this
+# define OK 1
+#endif
+#define FAIL 0
+#define NOTDONE 2 // not OK or FAIL but skipped
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 53224f2ee9..8b637fbb9b 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1,10 +1,7 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-/// VimL expression parser
+/// Vimscript expression parser
// Planned incompatibilities (to be included into vim_diff.txt when this parser
-// will be an actual part of VimL evaluation process):
+// will be an actual part of Vimscript evaluation process):
//
// 1. Expressions are first fully parsed and only then executed. This means
// that while ":echo [system('touch abc')" will create file "abc" in Vim and
@@ -58,17 +55,17 @@
#include <string.h>
#include "klib/kvec.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/charset.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
@@ -89,7 +86,7 @@ typedef enum {
/// Parse type: what is being parsed currently
typedef enum {
- /// Parsing regular VimL expression
+ /// Parsing regular Vimscript expression
kEPTExpr = 0,
/// Parsing lambda arguments
///
@@ -171,7 +168,7 @@ static inline float_T scale_number(const float_T num, const uint8_t base,
return ret;
}
-/// Get next token for the VimL expression input
+/// Get next token for the Vimscript expression input
///
/// @param pstate Parser state.
/// @param[in] flags Flags, @see LexExprFlags.
@@ -367,7 +364,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
// uses recorded position to scale number down when processing exponent.
float_T significand_part = 0;
uvarnumber_T exp_part = 0;
- const size_t frac_size = (size_t)(frac_end - frac_start);
+ const size_t frac_size = frac_end - frac_start;
for (size_t i = 0; i < frac_end; i++) {
if (i == frac_start - 1) {
continue;
@@ -376,7 +373,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
}
if (exp_start) {
vim_str2nr(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part,
- (int)(ret.len - exp_start), false);
+ (int)(ret.len - exp_start), false, NULL);
}
if (exp_negative) {
exp_part += frac_size;
@@ -394,7 +391,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
int len;
int prep;
vim_str2nr(pline.data, &prep, &len, STR2NR_ALL, NULL,
- &ret.data.num.val.integer, (int)pline.size, false);
+ &ret.data.num.val.integer, (int)pline.size, false, NULL);
ret.len = (size_t)len;
const uint8_t bases[] = {
[0] = 10,
@@ -938,7 +935,6 @@ static const char *intchar2str(const int ch)
}
#ifdef UNIT_TESTING
-# include <stdio.h>
REAL_FATTR_UNUSED
static inline void viml_pexpr_debug_print_ast_node(const ExprASTNode *const *const eastnode_p,
@@ -1168,7 +1164,7 @@ static struct {
// represented as "list(comma(a, comma(b, comma(c, d))))" then if it is
// "list(comma(comma(comma(a, b), c), d))" in which case you will need to
// traverse all three comma() structures. And with comma operator (including
- // actual comma operator from C which is not present in VimL) nobody cares
+ // actual comma operator from C which is not present in Vimscript) nobody cares
// about associativity, only about order of execution.
[kExprNodeComma] = { kEOpLvlComma, kEOpAssRight },
@@ -1273,8 +1269,8 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, ExprASTStack
const ExprOpAssociativity bop_node_ass = (
(bop_node->type == kExprNodeCall
|| bop_node->type == kExprNodeSubscript)
- ? kEOpAssLeft
- : node_ass(*bop_node));
+ ? kEOpAssLeft
+ : node_ass(*bop_node));
#endif
do {
ExprASTNode **new_top_node_p = kv_last(*ast_stack);
@@ -1915,7 +1911,7 @@ static const uint8_t base_to_prefix_length[] = {
[16] = 2,
};
-/// Parse one VimL expression
+/// Parse one Vimscript expression
///
/// @param pstate Parser state.
/// @param[in] flags Additional flags, see ExprParserFlags
@@ -2207,8 +2203,8 @@ viml_pexpr_parse_process_token:
cur_node->data.opt.ident_len = 0;
cur_node->data.opt.scope = (
cur_token.len == 3
- ? (ExprOptScope)pline.data[cur_token.start.col + 1]
- : kExprOptScopeUnspecified);
+ ? (ExprOptScope)pline.data[cur_token.start.col + 1]
+ : kExprOptScopeUnspecified);
} else {
cur_node->data.opt.ident = cur_token.data.opt.name;
cur_node->data.opt.ident_len = cur_token.data.opt.len;
@@ -2866,7 +2862,7 @@ viml_pexpr_parse_no_paren_closing_error: {}
case kENodeOperator:
if (prev_token.type == kExprLexSpacing) {
// For some reason "function (args)" is a function call, but
- // "(funcref) (args)" is not. AFAIR this somehow involves
+ // "(funcref) (args)" is not. As far as I remember this somehow involves
// compatibility and Bram was commenting that this is
// intentionally inconsistent and he is not very happy with the
// situation himself.
diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h
index 6fe6a784a0..94287ea4e1 100644
--- a/src/nvim/viml/parser/expressions.h
+++ b/src/nvim/viml/parser/expressions.h
@@ -1,13 +1,11 @@
-#ifndef NVIM_VIML_PARSER_EXPRESSIONS_H
-#define NVIM_VIML_PARSER_EXPRESSIONS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/viml/parser/parser.h"
struct expr_ast_node;
@@ -388,5 +386,3 @@ extern const char *const expr_asgn_type_tab[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "viml/parser/expressions.h.generated.h"
#endif
-
-#endif // NVIM_VIML_PARSER_EXPRESSIONS_H
diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c
index 1547feba90..b854aedca6 100644
--- a/src/nvim/viml/parser/parser.c
+++ b/src/nvim/viml/parser/parser.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include "nvim/viml/parser/parser.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h
index f387301c2d..cd5a493643 100644
--- a/src/nvim/viml/parser/parser.h
+++ b/src/nvim/viml/parser/parser.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_VIML_PARSER_PARSER_H
-#define NVIM_VIML_PARSER_PARSER_H
+#pragma once
#include <assert.h>
#include <stdbool.h>
@@ -227,5 +226,3 @@ static inline void viml_parser_highlight(ParserState *const pstate, const Parser
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "viml/parser/parser.h.generated.h"
#endif
-
-#endif // NVIM_VIML_PARSER_PARSER_H
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 16405f87b6..1a2ce13aba 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1,12 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
-#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -15,7 +11,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -27,7 +23,6 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds.h"
@@ -38,6 +33,7 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -45,40 +41,39 @@
#include "nvim/grid.h"
#include "nvim/hashtab.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mapping.h" // IWYU pragma: keep (langmap_adjust_mb)
#include "nvim/mark.h"
#include "nvim/match.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
-#include "nvim/optionstr.h"
-#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/path.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "window.c.generated.h"
@@ -97,7 +92,7 @@ typedef enum {
WEE_TRIGGER_LEAVE_AUTOCMDS = 0x10,
} wee_flags_T;
-static char e_cannot_split_window_when_closing_buffer[]
+static const char e_cannot_split_window_when_closing_buffer[]
= N_("E1159: Cannot split a window when closing the buffer");
static char *m_onlyone = N_("Already only one window");
@@ -140,15 +135,41 @@ win_T *prevwin_curwin(void)
return is_in_cmdwin() && prevwin != NULL ? prevwin : curwin;
}
+/// If the 'switchbuf' option contains "useopen" or "usetab", then try to jump
+/// to a window containing "buf".
+/// Returns the pointer to the window that was jumped to or NULL.
+win_T *swbuf_goto_win_with_buf(buf_T *buf)
+{
+ win_T *wp = NULL;
+
+ if (buf == NULL) {
+ return wp;
+ }
+
+ // If 'switchbuf' contains "useopen": jump to first window in the current
+ // tab page containing "buf" if one exists.
+ if (swb_flags & SWB_USEOPEN) {
+ wp = buf_jump_open_win(buf);
+ }
+
+ // If 'switchbuf' contains "usetab": jump to first window in any tab page
+ // containing "buf" if one exists.
+ if (wp == NULL && (swb_flags & SWB_USETAB)) {
+ wp = buf_jump_open_tab(buf);
+ }
+
+ return wp;
+}
+
/// all CTRL-W window commands are handled here, called from normal_cmd().
///
/// @param xchar extra char from ":wincmd gx" or NUL
-void do_window(int nchar, long Prenum, int xchar)
+void do_window(int nchar, int Prenum, int xchar)
{
int type = FIND_DEFINE;
char cbuf[40];
- long Prenum1 = Prenum == 0 ? 1 : Prenum;
+ int Prenum1 = Prenum == 0 ? 1 : Prenum;
#define CHECK_CMDWIN \
do { \
@@ -170,7 +191,7 @@ void do_window(int nchar, long Prenum, int xchar)
if (bt_quickfix(curbuf)) {
goto newwindow;
}
- (void)win_split((int)Prenum, 0);
+ (void)win_split(Prenum, 0);
break;
// split current window in two parts, vertically
@@ -183,7 +204,7 @@ void do_window(int nchar, long Prenum, int xchar)
if (bt_quickfix(curbuf)) {
goto newwindow;
}
- (void)win_split((int)Prenum, WSP_VERT);
+ (void)win_split(Prenum, WSP_VERT);
break;
// split current window and edit alternate file
@@ -192,7 +213,7 @@ void do_window(int nchar, long Prenum, int xchar)
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : (int)Prenum) == NULL) {
+ if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : Prenum) == NULL) {
if (Prenum == 0) {
emsg(_(e_noalt));
} else {
@@ -202,8 +223,8 @@ void do_window(int nchar, long Prenum, int xchar)
}
if (!curbuf_locked() && win_split(0, 0) == OK) {
- (void)buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : (int)Prenum,
- (linenr_T)0, GETF_ALT, false);
+ (void)buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : Prenum,
+ 0, GETF_ALT, false);
}
break;
@@ -355,17 +376,16 @@ newwindow:
case 'T':
CHECK_CMDWIN;
if (one_window(curwin)) {
- msg(_(m_onlyone));
+ msg(_(m_onlyone), 0);
} else {
tabpage_T *oldtab = curtab;
- tabpage_T *newtab;
// First create a new tab with the window, then go back to
// the old tab and close the window there.
win_T *wp = curwin;
- if (win_new_tabpage((int)Prenum, NULL) == OK
+ if (win_new_tabpage(Prenum, NULL) == OK
&& valid_tabpage(oldtab)) {
- newtab = curtab;
+ tabpage_T *newtab = curtab;
goto_tabpage_tp(oldtab, true, true);
if (curwin == wp) {
win_close(curwin, false, false);
@@ -412,14 +432,14 @@ newwindow:
case 'r':
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- win_rotate(false, (int)Prenum1); // downwards
+ win_rotate(false, Prenum1); // downwards
break;
// rotate windows upwards
case 'R':
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- win_rotate(true, (int)Prenum1); // upwards
+ win_rotate(true, Prenum1); // upwards
break;
// move window to the very top/bottom/left/right
@@ -428,7 +448,7 @@ newwindow:
case 'H':
case 'L':
CHECK_CMDWIN;
- win_totop((int)Prenum,
+ win_totop(Prenum,
((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
| ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT));
break;
@@ -442,40 +462,40 @@ newwindow:
// increase current window height
case '+':
- win_setheight(curwin->w_height + (int)Prenum1);
+ win_setheight(curwin->w_height + Prenum1);
break;
// decrease current window height
case '-':
- win_setheight(curwin->w_height - (int)Prenum1);
+ win_setheight(curwin->w_height - Prenum1);
break;
// set current window height
case Ctrl__:
case '_':
- win_setheight(Prenum ? (int)Prenum : Rows - 1);
+ win_setheight(Prenum ? Prenum : Rows - 1);
break;
// increase current window width
case '>':
- win_setwidth(curwin->w_width + (int)Prenum1);
+ win_setwidth(curwin->w_width + Prenum1);
break;
// decrease current window width
case '<':
- win_setwidth(curwin->w_width - (int)Prenum1);
+ win_setwidth(curwin->w_width - Prenum1);
break;
// set current window width
case '|':
- win_setwidth(Prenum != 0 ? (int)Prenum : Columns);
+ win_setwidth(Prenum != 0 ? Prenum : Columns);
break;
// jump to tag and split window if tag exists (in preview window)
case '}':
CHECK_CMDWIN;
if (Prenum) {
- g_do_tagpreview = (int)Prenum;
+ g_do_tagpreview = Prenum;
} else {
g_do_tagpreview = (int)p_pvh;
}
@@ -485,7 +505,7 @@ newwindow:
CHECK_CMDWIN;
// Keep visual mode, can select words to use as a tag.
if (Prenum) {
- postponed_split = (int)Prenum;
+ postponed_split = Prenum;
} else {
postponed_split = -1;
}
@@ -506,6 +526,9 @@ newwindow:
case Ctrl_F: {
wingotofile:
CHECK_CMDWIN;
+ if (check_text_or_curbuf_locked(NULL)) {
+ break;
+ }
linenr_T lnum = -1;
char *ptr = grab_file_name(Prenum1, &lnum);
@@ -513,18 +536,31 @@ wingotofile:
tabpage_T *oldtab = curtab;
win_T *oldwin = curwin;
setpcmark();
- if (win_split(0, 0) == OK) {
+
+ // If 'switchbuf' is set to 'useopen' or 'usetab' and the
+ // file is already opened in a window, then jump to it.
+ win_T *wp = NULL;
+ if ((swb_flags & (SWB_USEOPEN | SWB_USETAB))
+ && cmdmod.cmod_tab == 0) {
+ wp = swbuf_goto_win_with_buf(buflist_findname_exp(ptr));
+ }
+
+ if (wp == NULL && win_split(0, 0) == OK) {
RESET_BINDING(curwin);
if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
// Failed to open the file, close the window opened for it.
win_close(curwin, false, false);
goto_tabpage_win(oldtab, oldwin);
- } else if (nchar == 'F' && lnum >= 0) {
- curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
- beginline(BL_SOL | BL_FIX);
+ } else {
+ wp = curwin;
}
}
+
+ if (wp != NULL && nchar == 'F' && lnum >= 0) {
+ curwin->w_cursor.lnum = lnum;
+ check_cursor_lnum(curwin);
+ beginline(BL_SOL | BL_FIX);
+ }
xfree(ptr);
}
break;
@@ -546,7 +582,7 @@ wingotofile:
}
// Make a copy, if the line was changed it will be freed.
- ptr = xstrnsave(ptr, len);
+ ptr = xmemdupz(ptr, len);
find_pattern_in_path(ptr, 0, len, true, Prenum == 0,
type, Prenum1, ACTION_SPLIT, 1, MAXLNUM);
@@ -581,7 +617,7 @@ wingotofile:
case '}':
xchar = Ctrl_RSB;
if (Prenum) {
- g_do_tagpreview = (int)Prenum;
+ g_do_tagpreview = Prenum;
} else {
g_do_tagpreview = (int)p_pvh;
}
@@ -590,7 +626,7 @@ wingotofile:
case Ctrl_RSB:
// Keep visual mode, can select words to use as a tag.
if (Prenum) {
- postponed_split = (int)Prenum;
+ postponed_split = Prenum;
} else {
postponed_split = -1;
}
@@ -608,11 +644,11 @@ wingotofile:
goto wingotofile;
case 't': // CTRL-W gt: go to next tab page
- goto_tabpage((int)Prenum);
+ goto_tabpage(Prenum);
break;
case 'T': // CTRL-W gT: go to previous tab page
- goto_tabpage(-(int)Prenum1);
+ goto_tabpage(-Prenum1);
break;
case TAB: // CTRL-W g<Tab>: go to last used tab page
@@ -658,17 +694,13 @@ static void cmd_with_count(char *cmd, char *bufp, size_t bufsize, int64_t Prenum
}
}
-void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
+void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
+ FUNC_ATTR_NONNULL_ALL
{
- win_T *win = find_window_by_handle(window, err);
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!win || !buf) {
- return;
- }
-
tabpage_T *tab = win_find_tabpage(win);
+ // no redrawing and don't set the window title
+ RedrawingDisabled++;
if (noautocmd) {
block_autocmds();
}
@@ -678,7 +710,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
api_set_error(err,
kErrorTypeException,
"Failed to switch to window %d",
- window);
+ win->handle);
}
try_start();
@@ -687,7 +719,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
api_set_error(err,
kErrorTypeException,
"Failed to set buffer %d",
- buffer);
+ buf->handle);
}
// If window is not current, state logic will not validate its cursor.
@@ -698,210 +730,13 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
if (noautocmd) {
unblock_autocmds();
}
-}
-
-/// Create a new float.
-///
-/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
-/// It must then already belong to the current tabpage!
-/// @param last make the window the last one in the window list.
-/// Only used when allocating the autocommand window.
-/// @param config must already have been validated!
-win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err)
-{
- if (wp == NULL) {
- wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
- win_init(wp, curwin, 0);
- } else {
- assert(!last);
- assert(!wp->w_floating);
- if (firstwin == wp && lastwin_nofloating() == wp) {
- // last non-float
- api_set_error(err, kErrorTypeException,
- "Cannot change last window into float");
- return NULL;
- } else if (!win_valid(wp)) {
- api_set_error(err, kErrorTypeException,
- "Cannot change window from different tabpage into float");
- return NULL;
- }
- int dir;
- winframe_remove(wp, &dir, NULL);
- XFREE_CLEAR(wp->w_frame);
- (void)win_comp_pos(); // recompute window positions
- win_remove(wp, NULL);
- win_append(lastwin_nofloating(), wp);
- }
- wp->w_floating = true;
- wp->w_status_height = 0;
- wp->w_winbar_height = 0;
- wp->w_hsep_height = 0;
- wp->w_vsep_width = 0;
-
- win_config_float(wp, fconfig);
- win_set_inner_size(wp, true);
- wp->w_pos_changed = true;
- redraw_later(wp, UPD_VALID);
- return wp;
-}
-
-void win_set_minimal_style(win_T *wp)
-{
- wp->w_p_nu = false;
- wp->w_p_rnu = false;
- wp->w_p_cul = false;
- wp->w_p_cuc = false;
- wp->w_p_spell = false;
- wp->w_p_list = false;
-
- // Hide EOB region: use " " fillchar and cleared highlighting
- if (wp->w_p_fcs_chars.eob != ' ') {
- char *old = wp->w_p_fcs;
- wp->w_p_fcs = ((*old == NUL)
- ? xstrdup("eob: ")
- : concat_str(old, ",eob: "));
- free_string_option(old);
- }
-
- // TODO(bfredl): this could use a highlight namespace directly,
- // and avoid peculiarities around window options
- char *old = wp->w_p_winhl;
- wp->w_p_winhl = ((*old == NUL)
- ? xstrdup("EndOfBuffer:")
- : concat_str(old, ",EndOfBuffer:"));
- free_string_option(old);
- parse_winhl_opt(wp);
-
- // signcolumn: use 'auto'
- if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) {
- free_string_option(wp->w_p_scl);
- wp->w_p_scl = xstrdup("auto");
- }
-
- // foldcolumn: use '0'
- if (wp->w_p_fdc[0] != '0') {
- free_string_option(wp->w_p_fdc);
- wp->w_p_fdc = xstrdup("0");
- }
-
- // colorcolumn: cleared
- if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
- free_string_option(wp->w_p_cc);
- wp->w_p_cc = xstrdup("");
- }
-
- // statuscolumn: cleared
- if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) {
- free_string_option(wp->w_p_stc);
- wp->w_p_stc = xstrdup("");
- }
-}
-
-void win_config_float(win_T *wp, FloatConfig fconfig)
-{
- wp->w_width = MAX(fconfig.width, 1);
- wp->w_height = MAX(fconfig.height, 1);
-
- if (fconfig.relative == kFloatRelativeCursor) {
- fconfig.relative = kFloatRelativeWindow;
- fconfig.row += curwin->w_wrow;
- fconfig.col += curwin->w_wcol;
- fconfig.window = curwin->handle;
- } else if (fconfig.relative == kFloatRelativeMouse) {
- int row = mouse_row, col = mouse_col, grid = mouse_grid;
- win_T *mouse_win = mouse_find_win(&grid, &row, &col);
- if (mouse_win != NULL) {
- fconfig.relative = kFloatRelativeWindow;
- fconfig.row += row;
- fconfig.col += col;
- fconfig.window = mouse_win->handle;
- }
- }
-
- bool change_external = fconfig.external != wp->w_float_config.external;
- bool change_border = (fconfig.border != wp->w_float_config.border
- || memcmp(fconfig.border_hl_ids,
- wp->w_float_config.border_hl_ids,
- sizeof fconfig.border_hl_ids) != 0);
-
- wp->w_float_config = fconfig;
-
- bool has_border = wp->w_floating && wp->w_float_config.border;
- for (int i = 0; i < 4; i++) {
- int new_adj = has_border && wp->w_float_config.border_chars[2 * i + 1][0];
- if (new_adj != wp->w_border_adj[i]) {
- change_border = true;
- wp->w_border_adj[i] = new_adj;
- }
- }
-
- if (!ui_has(kUIMultigrid)) {
- wp->w_height = MIN(wp->w_height, Rows - 1 - win_border_height(wp));
- wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
- }
-
- win_set_inner_size(wp, true);
- must_redraw = MAX(must_redraw, UPD_VALID);
-
- wp->w_pos_changed = true;
- if (change_external || change_border) {
- wp->w_hl_needs_update = true;
- redraw_later(wp, UPD_NOT_VALID);
- }
-
- // compute initial position
- if (wp->w_float_config.relative == kFloatRelativeWindow) {
- int row = (int)wp->w_float_config.row;
- int col = (int)wp->w_float_config.col;
- Error dummy = ERROR_INIT;
- win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
- if (parent) {
- row += parent->w_winrow;
- col += parent->w_wincol;
- ScreenGrid *grid = &parent->w_grid;
- int row_off = 0, col_off = 0;
- grid_adjust(&grid, &row_off, &col_off);
- row += row_off;
- col += col_off;
- if (wp->w_float_config.bufpos.lnum >= 0) {
- pos_T pos = { wp->w_float_config.bufpos.lnum + 1,
- wp->w_float_config.bufpos.col, 0 };
- int trow, tcol, tcolc, tcole;
- textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
- row += trow - 1;
- col += tcol - 1;
- }
- }
- api_clear_error(&dummy);
- wp->w_winrow = row;
- wp->w_wincol = col;
- } else {
- wp->w_winrow = (int)fconfig.row;
- wp->w_wincol = (int)fconfig.col;
- }
-
- // changing border style while keeping border only requires redrawing border
- if (fconfig.border) {
- wp->w_redr_border = true;
- redraw_later(wp, UPD_VALID);
- }
-}
-
-void win_check_anchored_floats(win_T *win)
-{
- for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
- // float might be anchored to moved window
- if (wp->w_float_config.relative == kFloatRelativeWindow
- && wp->w_float_config.window == win->handle) {
- wp->w_pos_changed = true;
- }
- }
+ RedrawingDisabled--;
}
/// Return the number of fold columns to display
int win_fdccol_count(win_T *wp)
{
- const char *fdc = (const char *)wp->w_p_fdc;
+ const char *fdc = wp->w_p_fdc;
// auto:<NUM>
if (strncmp(fdc, "auto", 4) == 0) {
@@ -928,7 +763,13 @@ void ui_ext_win_position(win_T *wp, bool validate)
if (c.relative == kFloatRelativeWindow) {
Error dummy = ERROR_INIT;
win_T *win = find_window_by_handle(c.window, &dummy);
- if (win) {
+ api_clear_error(&dummy);
+ if (win != NULL) {
+ // When a floating window is anchored to another window,
+ // update the position of its anchored window first.
+ if (win->w_pos_changed && win->w_grid_alloc.chars != NULL && win_valid(win)) {
+ ui_ext_win_position(win, validate);
+ }
grid = &win->w_grid;
int row_off = 0, col_off = 0;
grid_adjust(&grid, &row_off, &col_off);
@@ -942,15 +783,18 @@ void ui_ext_win_position(win_T *wp, bool validate)
col += tcol - 1;
}
}
- api_clear_error(&dummy);
}
wp->w_grid_alloc.zindex = wp->w_float_config.zindex;
if (ui_has(kUIMultigrid)) {
String anchor = cstr_as_string((char *)float_anchor_str[c.anchor]);
- ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
- grid->handle, row, col, c.focusable,
- wp->w_grid_alloc.zindex);
+ if (!c.hide) {
+ ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
+ grid->handle, row, col, c.focusable,
+ wp->w_grid_alloc.zindex);
+ } else {
+ ui_call_win_hide(wp->w_grid_alloc.handle);
+ }
} else {
bool valid = (wp->w_redr_type == 0);
if (!valid && !validate) {
@@ -967,16 +811,23 @@ void ui_ext_win_position(win_T *wp, bool validate)
comp_row += grid->comp_row;
comp_col += grid->comp_col;
comp_row = MAX(MIN(comp_row, Rows - wp->w_height_outer - (p_ch > 0 ? 1 : 0)), 0);
- comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0);
+ if (!c.fixed || east) {
+ comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0);
+ }
wp->w_winrow = comp_row;
wp->w_wincol = comp_col;
- ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
- wp->w_height_outer, wp->w_width_outer, valid, false);
- ui_check_cursor_grid(wp->w_grid_alloc.handle);
- wp->w_grid_alloc.focusable = wp->w_float_config.focusable;
- if (!valid) {
- wp->w_grid_alloc.valid = false;
- redraw_later(wp, UPD_NOT_VALID);
+
+ if (!c.hide) {
+ ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
+ wp->w_height_outer, wp->w_width_outer, valid, false);
+ ui_check_cursor_grid(wp->w_grid_alloc.handle);
+ wp->w_grid_alloc.focusable = wp->w_float_config.focusable;
+ if (!valid) {
+ wp->w_grid_alloc.valid = false;
+ redraw_later(wp, UPD_NOT_VALID);
+ }
+ } else {
+ ui_comp_remove_grid(&wp->w_grid_alloc);
}
}
} else {
@@ -986,22 +837,63 @@ void ui_ext_win_position(win_T *wp, bool validate)
void ui_ext_win_viewport(win_T *wp)
{
- if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid) {
- int botline = wp->w_botline;
- int line_count = wp->w_buffer->b_ml.ml_line_count;
- if (botline == line_count + 1 && wp->w_empty_rows == 0) {
+ // NOTE: The win_viewport command is delayed until the next flush when there are pending updates.
+ // This ensures that the updates and the viewport are sent together.
+ if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid && wp->w_redr_type == 0) {
+ const linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
+ // Avoid ml_get errors when producing "scroll_delta".
+ const linenr_T cur_topline = MIN(wp->w_topline, line_count);
+ const linenr_T cur_botline = MIN(wp->w_botline, line_count);
+ int64_t delta = 0;
+ linenr_T last_topline = wp->w_viewport_last_topline;
+ linenr_T last_botline = wp->w_viewport_last_botline;
+ int last_topfill = wp->w_viewport_last_topfill;
+ int64_t last_skipcol = wp->w_viewport_last_skipcol;
+ if (last_topline > line_count) {
+ delta -= last_topline - line_count;
+ last_topline = line_count;
+ last_topfill = 0;
+ last_skipcol = MAXCOL;
+ }
+ last_botline = MIN(last_botline, line_count);
+ if (cur_topline < last_topline
+ || (cur_topline == last_topline && wp->w_skipcol < last_skipcol)) {
+ if (last_topline > 0 && cur_botline < last_topline) {
+ // Scrolling too many lines: only give an approximate "scroll_delta".
+ delta -= win_text_height(wp, cur_topline, wp->w_skipcol, cur_botline, 0, NULL);
+ delta -= last_topline - cur_botline;
+ } else {
+ delta -= win_text_height(wp, cur_topline, wp->w_skipcol, last_topline, last_skipcol, NULL);
+ }
+ } else if (cur_topline > last_topline
+ || (cur_topline == last_topline && wp->w_skipcol > last_skipcol)) {
+ if (last_botline > 0 && cur_topline > last_botline) {
+ // Scrolling too many lines: only give an approximate "scroll_delta".
+ delta += win_text_height(wp, last_topline, last_skipcol, last_botline, 0, NULL);
+ delta += cur_topline - last_botline;
+ } else {
+ delta += win_text_height(wp, last_topline, last_skipcol, cur_topline, wp->w_skipcol, NULL);
+ }
+ }
+ delta += last_topfill;
+ delta -= wp->w_topfill;
+ linenr_T ev_botline = wp->w_botline;
+ if (ev_botline == line_count + 1 && wp->w_empty_rows == 0) {
// TODO(bfredl): The might be more cases to consider, like how does this
// interact with incomplete final line? Diff filler lines?
- botline = wp->w_buffer->b_ml.ml_line_count;
+ ev_botline = line_count;
}
- ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline - 1,
- botline, wp->w_cursor.lnum - 1, wp->w_cursor.col,
- line_count);
+ ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline - 1, ev_botline,
+ wp->w_cursor.lnum - 1, wp->w_cursor.col, line_count, delta);
wp->w_viewport_invalid = false;
+ wp->w_viewport_last_topline = wp->w_topline;
+ wp->w_viewport_last_botline = wp->w_botline;
+ wp->w_viewport_last_topfill = wp->w_topfill;
+ wp->w_viewport_last_skipcol = wp->w_skipcol;
}
}
-/// If "split_disallowed" is set given an error and return FAIL.
+/// If "split_disallowed" is set give an error and return FAIL.
/// Otherwise return OK.
static int check_split_disallowed(void)
{
@@ -1058,10 +950,10 @@ int win_split(int size, int flags)
return win_split_ins(size, flags, NULL, 0);
}
-// When "new_wp" is NULL: split the current window in two.
-// When "new_wp" is not NULL: insert this window at the far
-// top/left/right/bottom.
-// return FAIL for failure, OK otherwise
+/// When "new_wp" is NULL: split the current window in two.
+/// When "new_wp" is not NULL: insert this window at the far
+/// top/left/right/bottom.
+/// @return FAIL for failure, OK otherwise
int win_split_ins(int size, int flags, win_T *new_wp, int dir)
{
win_T *wp = new_wp;
@@ -1435,7 +1327,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
frame_fix_width(oldwin);
frame_fix_width(wp);
} else {
- bool is_stl_global = global_stl_height() > 0;
+ const bool is_stl_global = global_stl_height() > 0;
// width and column of new window is same as current window
if (flags & (WSP_TOP | WSP_BOT)) {
wp->w_wincol = 0;
@@ -1451,6 +1343,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// "new_size" of the current window goes to the new window, use
// one row for the status line
win_new_height(wp, new_size);
+ const int old_status_height = oldwin->w_status_height;
if (before) {
wp->w_hsep_height = is_stl_global ? 1 : 0;
} else {
@@ -1459,15 +1352,19 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
}
if (flags & (WSP_TOP | WSP_BOT)) {
int new_fr_height = curfrp->fr_height - new_size;
-
- if (!((flags & WSP_BOT) && p_ls == 0) && !is_stl_global) {
- new_fr_height -= STATUS_HEIGHT;
- } else if (is_stl_global) {
+ if (is_stl_global) {
if (flags & WSP_BOT) {
frame_add_hsep(curfrp);
} else {
new_fr_height -= 1;
}
+ } else {
+ if (!((flags & WSP_BOT) && p_ls == 0)) {
+ new_fr_height -= STATUS_HEIGHT;
+ }
+ if (flags & WSP_BOT) {
+ frame_add_statusline(curfrp);
+ }
}
frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, false);
} else {
@@ -1476,7 +1373,6 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (before) { // new window above current one
wp->w_winrow = oldwin->w_winrow;
-
if (is_stl_global) {
wp->w_status_height = 0;
oldwin->w_winrow += wp->w_height + 1;
@@ -1490,15 +1386,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
wp->w_status_height = 0;
} else {
wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT;
- wp->w_status_height = oldwin->w_status_height;
+ wp->w_status_height = old_status_height;
if (!(flags & WSP_BOT)) {
oldwin->w_status_height = STATUS_HEIGHT;
}
}
}
- if ((flags & WSP_BOT) && !is_stl_global) {
- frame_add_statusline(curfrp);
- }
frame_fix_height(wp);
frame_fix_height(oldwin);
}
@@ -1525,7 +1418,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// equalize the window sizes.
if (do_equal || dir != 0) {
win_equal(wp, true, (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v'));
- } else if (*p_spk != 'c' && !is_aucmd_win(wp)) {
+ } else if (!is_aucmd_win(wp)) {
win_fix_scroll(false);
}
@@ -1567,7 +1460,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// The windows will both edit the same buffer.
// WSP_NEWLOC may be specified in flags to prevent the location list from
// being copied.
-static void win_init(win_T *newp, win_T *oldp, int flags)
+void win_init(win_T *newp, win_T *oldp, int flags)
{
newp->w_buffer = oldp->w_buffer;
newp->w_s = &(oldp->w_buffer->b_s);
@@ -1599,6 +1492,9 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
? NULL : xstrdup(oldp->w_prevdir);
if (*p_spk != 'c') {
+ if (*p_spk == 't') {
+ newp->w_skipcol = oldp->w_skipcol;
+ }
newp->w_botline = oldp->w_botline;
newp->w_prev_height = oldp->w_height;
newp->w_prev_winrow = oldp->w_winrow;
@@ -1641,24 +1537,6 @@ static void win_init_some(win_T *newp, win_T *oldp)
win_copy_options(oldp, newp);
}
-/// Return true if "win" is floating window in the current tab page.
-///
-/// @param win window to check
-bool win_valid_floating(const win_T *win)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (win == NULL) {
- return false;
- }
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp == win) {
- return wp->w_floating;
- }
- }
- return false;
-}
-
/// Check if "win" is a pointer to an existing window in the current tabpage.
///
/// @param win window to check
@@ -1790,7 +1668,7 @@ int make_windows(int count, bool vertical)
}
// Exchange current and next window
-static void win_exchange(long Prenum)
+static void win_exchange(int Prenum)
{
if (curwin->w_floating) {
emsg(e_floatexchange);
@@ -1802,6 +1680,10 @@ static void win_exchange(long Prenum)
beep_flush();
return;
}
+ if (text_or_buf_locked()) {
+ beep_flush();
+ return;
+ }
frame_T *frp;
@@ -1900,8 +1782,8 @@ static void win_rotate(bool upwards, int count)
}
}
- win_T *wp1;
- win_T *wp2;
+ win_T *wp1 = NULL;
+ win_T *wp2 = NULL;
while (count--) {
if (upwards) { // first window becomes last window
@@ -2115,7 +1997,7 @@ void win_equal(win_T *next_curwin, bool current, int dir)
win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current,
topframe, dir, 0, tabline_height(),
Columns, topframe->fr_height);
- if (*p_spk != 'c' && !is_aucmd_win(next_curwin)) {
+ if (!is_aucmd_win(next_curwin)) {
win_fix_scroll(true);
}
}
@@ -2258,6 +2140,9 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
}
if (hnc) { // add next_curwin size
next_curwin_size -= (int)p_wiw - (m - n);
+ if (next_curwin_size < 0) {
+ next_curwin_size = 0;
+ }
new_size += next_curwin_size;
room -= new_size - next_curwin_size;
} else {
@@ -2407,7 +2292,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
}
}
-static void leaving_window(win_T *const win)
+void leaving_window(win_T *const win)
FUNC_ATTR_NONNULL_ALL
{
// Only matters for a prompt window.
@@ -2585,6 +2470,23 @@ static bool can_close_floating_windows(void)
return true;
}
+/// @return true if, considering the cmdwin, `win` is safe to close.
+/// If false and `win` is the cmdwin, it is closed; otherwise, `err` is set.
+bool can_close_in_cmdwin(win_T *win, Error *err)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (cmdwin_type != 0) {
+ if (win == curwin) {
+ cmdwin_result = Ctrl_C;
+ return false;
+ } else if (win == cmdwin_old_curwin) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return false;
+ }
+ }
+ return true;
+}
+
/// Close the possibly last window in a tab page.
///
/// @param win window to close
@@ -2599,6 +2501,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
if (!ONE_WINDOW) {
return false;
}
+
buf_T *old_curbuf = curbuf;
Terminal *term = win->w_buffer ? win->w_buffer->terminal : NULL;
@@ -2636,10 +2539,11 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
return true;
}
-/// Close the buffer of "win" and unload it if "free_buf" is true.
+/// Close the buffer of "win" and unload it if "action" is DOBUF_UNLOAD.
+/// "action" can also be zero (do nothing).
/// "abort_if_last" is passed to close_buffer(): abort closing if all other
/// windows are closed.
-static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last)
+static void win_close_buffer(win_T *win, int action, bool abort_if_last)
{
// Free independent synblock before the buffer is freed.
if (win->w_buffer != NULL) {
@@ -2658,7 +2562,7 @@ static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last)
bufref_T bufref;
set_bufref(&bufref, curbuf);
win->w_closing = true;
- close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, abort_if_last, true);
+ close_buffer(win, win->w_buffer, action, abort_if_last, true);
if (win_valid_any_tab(win)) {
win->w_closing = false;
}
@@ -2756,6 +2660,9 @@ int win_close(win_T *win, bool free_buf, bool force)
reset_VIsual_and_resel(); // stop Visual mode
other_buffer = true;
+ if (!win_valid(win)) {
+ return FAIL;
+ }
win->w_closing = true;
apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
if (!win_valid(win)) {
@@ -2788,7 +2695,7 @@ int win_close(win_T *win, bool free_buf, bool force)
return OK;
}
- win_close_buffer(win, free_buf, true);
+ win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, true);
if (only_one_window() && win_valid(win) && win->w_buffer == NULL
&& (last_window(win) || curtab != prev_curtab
@@ -2872,7 +2779,7 @@ int win_close(win_T *win, bool free_buf, bool force)
if (wp->w_p_pvw || bt_quickfix(wp->w_buffer)) {
// If the cursor goes to the preview or the quickfix window, try
// finding another window to go to.
- for (;;) {
+ while (true) {
if (wp->w_next == NULL) {
wp = firstwin;
} else {
@@ -2907,9 +2814,7 @@ int win_close(win_T *win, bool free_buf, bool force)
win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);
} else {
(void)win_comp_pos();
- if (*p_spk != 'c') {
- win_fix_scroll(false);
- }
+ win_fix_scroll(false);
}
}
@@ -3326,20 +3231,20 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp)
// By default the next window will get the space that was abandoned by this
// window
frame_T *target_fr = frp->fr_next;
- frame_T *other_fr = frp->fr_prev;
+ frame_T *other_fr = frp->fr_prev;
// If this is part of a column of windows and 'splitbelow' is true then the
// previous window will get the space.
if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_COL && p_sb) {
target_fr = frp->fr_prev;
- other_fr = frp->fr_next;
+ other_fr = frp->fr_next;
}
// If this is part of a row of windows, and 'splitright' is true then the
// previous window will get the space.
if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW && p_spr) {
target_fr = frp->fr_prev;
- other_fr = frp->fr_next;
+ other_fr = frp->fr_next;
}
// If 'wfh' or 'wfw' is set for the target and not for the alternate
@@ -3576,12 +3481,7 @@ static void frame_add_statusline(frame_T *frp)
{
if (frp->fr_layout == FR_LEAF) {
win_T *wp = frp->fr_win;
- if (wp->w_status_height == 0) {
- if (wp->w_height - STATUS_HEIGHT >= 0) { // don't make it negative
- wp->w_height -= STATUS_HEIGHT;
- }
- wp->w_status_height = STATUS_HEIGHT;
- }
+ wp->w_status_height = STATUS_HEIGHT;
} else if (frp->fr_layout == FR_ROW) {
// Handle all the frames in the row.
FOR_ALL_FRAMES(frp, frp->fr_child) {
@@ -3728,12 +3628,7 @@ static void frame_add_hsep(const frame_T *frp)
{
if (frp->fr_layout == FR_LEAF) {
win_T *wp = frp->fr_win;
- if (wp->w_hsep_height == 0) {
- if (wp->w_height > 0) { // don't make it negative
- wp->w_height++;
- }
- wp->w_hsep_height = 1;
- }
+ wp->w_hsep_height = 1;
} else if (frp->fr_layout == FR_ROW) {
// Handle all the frames in the row.
FOR_ALL_FRAMES(frp, frp->fr_child) {
@@ -3870,7 +3765,7 @@ void close_others(int message, int forceit)
if (one_nonfloat() && !lastwin->w_floating) {
if (message
&& !autocmd_busy) {
- msg(_(m_onlyone));
+ msg(_(m_onlyone), 0);
}
return;
}
@@ -3883,6 +3778,12 @@ void close_others(int message, int forceit)
continue;
}
+ // autoccommands messed this one up
+ if (!buf_valid(wp->w_buffer) && win_valid(wp)) {
+ wp->w_buffer = NULL;
+ win_close(wp, false, false);
+ continue;
+ }
// Check if it's allowed to abandon this window
int r = can_abandon(wp->w_buffer, forceit);
if (!win_valid(wp)) { // autocommands messed wp up
@@ -3968,7 +3869,7 @@ static int win_alloc_firstwin(win_T *oldwin)
if (oldwin == NULL) {
// Very first window, need to create an empty buffer for it and
// initialize from scratch.
- curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED);
+ curbuf = buflist_new(NULL, NULL, 1, BLN_LISTED);
if (curbuf == NULL) {
return FAIL;
}
@@ -4024,7 +3925,7 @@ static tabpage_T *alloc_tabpage(void)
static int last_tp_handle = 0;
tabpage_T *tp = xcalloc(1, sizeof(tabpage_T));
tp->handle = ++last_tp_handle;
- pmap_put(handle_T)(&tabpage_handles, tp->handle, tp);
+ pmap_put(int)(&tabpage_handles, tp->handle, tp);
// Init t: variables.
tp->tp_vars = tv_dict_alloc();
@@ -4037,7 +3938,7 @@ static tabpage_T *alloc_tabpage(void)
void free_tabpage(tabpage_T *tp)
{
- pmap_del(handle_T)(&tabpage_handles, tp->handle);
+ pmap_del(int)(&tabpage_handles, tp->handle, NULL);
diff_clear(tp);
for (int idx = 0; idx < SNAP_COUNT; idx++) {
clear_snapshot(tp, idx);
@@ -4081,7 +3982,7 @@ int win_new_tabpage(int after, char *filename)
}
newtp->tp_localdir = old_curtab->tp_localdir
- ? xstrdup(old_curtab->tp_localdir) : NULL;
+ ? xstrdup(old_curtab->tp_localdir) : NULL;
curtab = newtp;
@@ -4144,12 +4045,17 @@ int may_open_tabpage(void)
{
int n = (cmdmod.cmod_tab == 0) ? postponed_split_tab : cmdmod.cmod_tab;
- if (n != 0) {
- cmdmod.cmod_tab = 0; // reset it to avoid doing it twice
- postponed_split_tab = 0;
- return win_new_tabpage(n, NULL);
+ if (n == 0) {
+ return FAIL;
+ }
+
+ cmdmod.cmod_tab = 0; // reset it to avoid doing it twice
+ postponed_split_tab = 0;
+ int status = win_new_tabpage(n, NULL);
+ if (status == OK) {
+ apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf);
}
- return FAIL;
+ return status;
}
// Create up to "maxcount" tabpages with empty windows.
@@ -4494,11 +4400,12 @@ void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_le
/// @return true if the tab page is valid, false otherwise.
bool goto_tabpage_lastused(void)
{
- if (valid_tabpage(lastused_tabpage)) {
- goto_tabpage_tp(lastused_tabpage, true, true);
- return true;
+ if (!valid_tabpage(lastused_tabpage)) {
+ return false;
}
- return false;
+
+ goto_tabpage_tp(lastused_tabpage, true, true);
+ return true;
}
// Enter window "wp" in tab page "tp".
@@ -4615,7 +4522,7 @@ tabpage_T *win_find_tabpage(win_T *win)
/// @param count nth neighbor window
///
/// @return found window
-win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count)
+win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, int count)
{
frame_T *foundfr = wp->w_frame;
@@ -4628,7 +4535,7 @@ win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count)
// First go upwards in the tree of frames until we find an upwards or
// downwards neighbor.
frame_T *fr = foundfr;
- for (;;) {
+ while (true) {
if (fr == tp->tp_topframe) {
goto end;
}
@@ -4644,7 +4551,7 @@ win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count)
}
// Now go downwards to find the bottom or top frame in it.
- for (;;) {
+ while (true) {
if (nfr->fr_layout == FR_LEAF) {
foundfr = nfr;
break;
@@ -4674,7 +4581,7 @@ end:
///
/// @param up true to go to win above
/// @param count go count times into direction
-static void win_goto_ver(bool up, long count)
+static void win_goto_ver(bool up, int count)
{
win_T *win = win_vert_neighbor(curtab, curwin, up, count);
if (win != NULL) {
@@ -4691,7 +4598,7 @@ static void win_goto_ver(bool up, long count)
/// @param count nth neighbor window
///
/// @return found window
-win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count)
+win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, int count)
{
frame_T *foundfr = wp->w_frame;
@@ -4704,7 +4611,7 @@ win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count)
// First go upwards in the tree of frames until we find a left or
// right neighbor.
frame_T *fr = foundfr;
- for (;;) {
+ while (true) {
if (fr == tp->tp_topframe) {
goto end;
}
@@ -4720,7 +4627,7 @@ win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count)
}
// Now go downwards to find the leftmost or rightmost frame in it.
- for (;;) {
+ while (true) {
if (nfr->fr_layout == FR_LEAF) {
foundfr = nfr;
break;
@@ -4750,7 +4657,7 @@ end:
///
/// @param left true to go to left window
/// @param count go count times into direction
-static void win_goto_hor(bool left, long count)
+static void win_goto_hor(bool left, int count)
{
win_T *win = win_horz_neighbor(curtab, curwin, left, count);
if (win != NULL) {
@@ -4811,7 +4718,7 @@ static void win_enter_ext(win_T *const wp, const int flags)
// Might need to scroll the old window before switching, e.g., when the
// cursor was moved.
- if (*p_spk == 'c') {
+ if (*p_spk == 'c' && !curwin_invalid) {
update_topline(curwin);
}
@@ -4833,7 +4740,9 @@ static void win_enter_ext(win_T *const wp, const int flags)
if (*p_spk == 'c') {
changed_line_abv_curs(); // assume cursor position needs updating
} else {
- win_fix_cursor(true);
+ // Make sure the cursor position is valid, either by moving the cursor
+ // or by scrolling the text.
+ win_fix_cursor(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL));
}
fix_current_dir();
@@ -4848,8 +4757,6 @@ static void win_enter_ext(win_T *const wp, const int flags)
if (other_buffer) {
apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
}
- apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf);
- curwin->w_last_cursormoved = curwin->w_cursor;
}
maketitle();
@@ -4997,7 +4904,7 @@ win_T *buf_jump_open_tab(buf_T *buf)
/// @param hidden allocate a window structure and link it in the window if
// false.
-static win_T *win_alloc(win_T *after, bool hidden)
+win_T *win_alloc(win_T *after, bool hidden)
{
static int last_win_id = LOWEST_WIN_ID - 1;
@@ -5005,7 +4912,7 @@ static win_T *win_alloc(win_T *after, bool hidden)
win_T *new_wp = xcalloc(1, sizeof(win_T));
new_wp->handle = ++last_win_id;
- pmap_put(handle_T)(&window_handles, new_wp->handle, new_wp);
+ pmap_put(int)(&window_handles, new_wp->handle, new_wp);
grid_assign_handle(&new_wp->w_grid_alloc);
@@ -5034,12 +4941,13 @@ static win_T *win_alloc(win_T *after, bool hidden)
new_wp->w_floating = 0;
new_wp->w_float_config = FLOAT_CONFIG_INIT;
new_wp->w_viewport_invalid = true;
+ new_wp->w_viewport_last_topline = 1;
new_wp->w_ns_hl = -1;
// use global option for global-local options
- new_wp->w_p_so = -1;
- new_wp->w_p_siso = -1;
+ new_wp->w_allbuf_opt.wo_so = new_wp->w_p_so = -1;
+ new_wp->w_allbuf_opt.wo_siso = new_wp->w_p_siso = -1;
// We won't calculate w_fraction until resizing the window
new_wp->w_fraction = 0;
@@ -5077,14 +4985,13 @@ static void free_wp_cc_cols(colorcol_T* cc)
/// @param tp tab page "win" is in, NULL for current
static void win_free(win_T *wp, tabpage_T *tp)
{
- pmap_del(handle_T)(&window_handles, wp->handle);
+ pmap_del(int)(&window_handles, wp->handle, NULL);
clearFolding(wp);
// reduce the reference count to the argument list.
alist_unlink(wp->w_alist);
// Don't execute autocommands while the window is halfway being deleted.
- // gui_mch_destroy_scrollbar() may trigger a FocusGained event.
block_autocmds();
clear_winopt(&wp->w_onebuf_opt);
@@ -5156,8 +5063,9 @@ static void win_free(win_T *wp, tabpage_T *tp)
}
}
- // free the border title text
+ // free the border text
clear_virttext(&wp->w_float_config.title_chunks);
+ clear_virttext(&wp->w_float_config.footer_chunks);
clear_matches(wp);
@@ -5274,8 +5182,8 @@ static void frame_remove(frame_T *frp)
void win_new_screensize(void)
{
- static long old_Rows = 0;
- static long old_Columns = 0;
+ static int old_Rows = 0;
+ static int old_Columns = 0;
if (old_Rows != Rows) {
// If 'window' uses the whole screen, keep it using that.
@@ -5317,7 +5225,7 @@ void win_new_screen_rows(void)
compute_cmdrow();
curtab->tp_ch_used = p_ch;
- if (*p_spk != 'c' && !skip_win_fix_scroll) {
+ if (!skip_win_fix_scroll) {
win_fix_scroll(true);
}
}
@@ -5375,7 +5283,7 @@ static dict_T *make_win_info_dict(int width, int height, int topline, int topfil
d->dv_refcount = 1;
// not actually looping, for breaking out on error
- while (1) {
+ while (true) {
typval_T tv = {
.v_lock = VAR_UNLOCKED,
.v_type = VAR_NUMBER,
@@ -5628,8 +5536,9 @@ void win_size_save(garray_T *gap)
{
ga_init(gap, (int)sizeof(int), 1);
ga_grow(gap, win_count() * 2 + 1);
- // first entry is value of 'lines'
- ((int *)gap->ga_data)[gap->ga_len++] = Rows;
+ // first entry is the total lines available for windows
+ ((int *)gap->ga_data)[gap->ga_len++] =
+ (int)ROWS_AVAIL + global_stl_height() - last_stl_height(false);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
((int *)gap->ga_data)[gap->ga_len++] =
@@ -5639,13 +5548,14 @@ void win_size_save(garray_T *gap)
}
// Restore window sizes, but only if the number of windows is still the same
-// and 'lines' didn't change.
+// and total lines available for windows didn't change.
// Does not free the growarray.
void win_size_restore(garray_T *gap)
FUNC_ATTR_NONNULL_ALL
{
if (win_count() * 2 + 1 == gap->ga_len
- && ((int *)gap->ga_data)[0] == Rows) {
+ && ((int *)gap->ga_data)[0] ==
+ ROWS_AVAIL + global_stl_height() - last_stl_height(false)) {
// The order matters, because frames contain other frames, but it's
// difficult to get right. The easy way out is to do it twice.
for (int j = 0; j < 2; j++) {
@@ -5683,13 +5593,6 @@ int win_comp_pos(void)
return row + global_stl_height();
}
-void win_reconfig_floats(void)
-{
- for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
- win_config_float(wp, wp->w_float_config);
- }
-}
-
// Update the position of the windows in frame "topfrp", using the width and
// height of the frames.
// "*row" and "*col" are the top-left position of the frame. They are updated
@@ -5764,9 +5667,7 @@ void win_setheight_win(int height, win_T *win)
msg_row = row;
msg_col = 0;
- if (*p_spk != 'c') {
- win_fix_scroll(true);
- }
+ win_fix_scroll(true);
redraw_all_later(UPD_NOT_VALID);
redraw_cmdline = true;
@@ -6090,7 +5991,7 @@ static void frame_setwidth(frame_T *curfrp, int width)
}
// Check 'winminheight' for a valid value and reduce it if needed.
-void win_setminheight(void)
+const char *did_set_winminheight(optset_T *args FUNC_ATTR_UNUSED)
{
bool first = true;
@@ -6107,10 +6008,11 @@ void win_setminheight(void)
first = false;
}
}
+ return NULL;
}
// Check 'winminwidth' for a valid value and reduce it if needed.
-void win_setminwidth(void)
+const char *did_set_winminwidth(optset_T *args FUNC_ATTR_UNUSED)
{
bool first = true;
@@ -6127,6 +6029,7 @@ void win_setminwidth(void)
first = false;
}
}
+ return NULL;
}
/// Status line of dragwin is dragged "offset" lines down (negative is up).
@@ -6244,9 +6147,7 @@ void win_drag_status_line(win_T *dragwin, int offset)
p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1);
curtab->tp_ch_used = p_ch;
- if (*p_spk != 'c') {
- win_fix_scroll(true);
- }
+ win_fix_scroll(true);
redraw_all_later(UPD_SOME_VALID);
showmode();
@@ -6351,7 +6252,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
redraw_all_later(UPD_NOT_VALID);
}
-#define FRACTION_MULT 16384L
+#define FRACTION_MULT 16384
// Set wp->w_fraction for the current w_wrow and w_height.
// Has no effect when the window is less than two lines.
@@ -6361,45 +6262,56 @@ void set_fraction(win_T *wp)
// When cursor is in the first line the percentage is computed as if
// it's halfway that line. Thus with two lines it is 25%, with three
// lines 17%, etc. Similarly for the last line: 75%, 83%, etc.
- wp->w_fraction = (int)(wp->w_wrow * FRACTION_MULT + FRACTION_MULT / 2) / wp->w_height_inner;
+ wp->w_fraction = (wp->w_wrow * FRACTION_MULT + FRACTION_MULT / 2) / wp->w_height_inner;
}
}
-/// Handle scroll position for 'splitkeep'. Replaces scroll_to_fraction()
-/// call from win_set_inner_size(). Instead we iterate over all windows in a
-/// tabpage and calculate the new scroll position.
-/// TODO(luukvbaal): Ensure this also works with wrapped lines.
-/// Requires topline to be able to be set to a bufferline with some
-/// offset(row-wise scrolling/smoothscroll).
+/// Handle scroll position, depending on 'splitkeep'. Replaces the
+/// scroll_to_fraction() call from win_new_height() if 'splitkeep' is "screen"
+/// or "topline". Instead we iterate over all windows in a tabpage and
+/// calculate the new scroll position.
+/// TODO(vim): Ensure this also works with wrapped lines.
+/// Requires a not fully visible cursor line to be allowed at the bottom of
+/// a window("zb"), probably only when 'smoothscroll' is also set.
void win_fix_scroll(int resize)
{
- linenr_T lnum;
+ if (*p_spk == 'c') {
+ return; // 'splitkeep' is "cursor"
+ }
skip_update_topline = true;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
// Skip when window height has not changed or when floating.
if (!wp->w_floating && wp->w_height != wp->w_prev_height) {
+ // Cursor position in this window may now be invalid. It is kept
+ // potentially invalid until the window is made the current window.
+ wp->w_do_win_fix_cursor = true;
+
// If window has moved update botline to keep the same screenlines.
if (*p_spk == 's' && wp->w_winrow != wp->w_prev_winrow
&& wp->w_botline - 1 <= wp->w_buffer->b_ml.ml_line_count) {
- lnum = wp->w_cursor.lnum;
int diff = (wp->w_winrow - wp->w_prev_winrow)
+ (wp->w_height - wp->w_prev_height);
+ linenr_T lnum = wp->w_cursor.lnum;
wp->w_cursor.lnum = wp->w_botline - 1;
+
// Add difference in height and row to botline.
if (diff > 0) {
cursor_down_inner(wp, diff);
} else {
cursor_up_inner(wp, -diff);
}
- // Bring the new cursor position to the bottom of the screen.
+
+ // Scroll to put the new cursor position at the bottom of the
+ // screen.
wp->w_fraction = FRACTION_MULT;
scroll_to_fraction(wp, wp->w_prev_height);
wp->w_cursor.lnum = lnum;
} else if (wp == curwin) {
wp->w_valid &= ~VALID_CROW;
}
- invalidate_botline_win(wp);
+
+ invalidate_botline(wp);
validate_botline(wp);
}
wp->w_prev_height = wp->w_height;
@@ -6416,40 +6328,45 @@ void win_fix_scroll(int resize)
/// Make sure the cursor position is valid for 'splitkeep'.
/// If it is not, put the cursor position in the jumplist and move it.
-/// If we are not in normal mode, scroll to make valid instead.
-static void win_fix_cursor(int normal)
+/// If we are not in normal mode ("normal" is false), make it valid by scrolling
+/// instead.
+static void win_fix_cursor(bool normal)
{
win_T *wp = curwin;
- long so = get_scrolloff_value(wp);
- linenr_T nlnum = 0;
- linenr_T lnum = wp->w_cursor.lnum;
- if (wp->w_buffer->b_ml.ml_line_count < wp->w_height
- || skip_win_fix_cursor) {
+ if (skip_win_fix_cursor
+ || !wp->w_do_win_fix_cursor
+ || wp->w_buffer->b_ml.ml_line_count < wp->w_height_inner) {
return;
}
+ wp->w_do_win_fix_cursor = false;
// Determine valid cursor range.
- so = MIN(wp->w_height_inner / 2, so);
+ int so = MIN(wp->w_height_inner / 2, get_scrolloff_value(wp));
+ linenr_T lnum = wp->w_cursor.lnum;
+
wp->w_cursor.lnum = wp->w_topline;
- linenr_T top = cursor_down_inner(wp, so);
+ cursor_down_inner(wp, so);
+ linenr_T top = wp->w_cursor.lnum;
+
wp->w_cursor.lnum = wp->w_botline - 1;
- linenr_T bot = cursor_up_inner(wp, so);
+ cursor_up_inner(wp, so);
+ linenr_T bot = wp->w_cursor.lnum;
+
wp->w_cursor.lnum = lnum;
// Check if cursor position is above or below valid cursor range.
+ linenr_T nlnum = 0;
if (lnum > bot && (wp->w_botline - wp->w_buffer->b_ml.ml_line_count) != 1) {
nlnum = bot;
} else if (lnum < top && wp->w_topline != 1) {
- nlnum = (so == wp->w_height / 2) ? bot : top;
+ nlnum = (so == wp->w_height_inner / 2) ? bot : top;
}
- if (nlnum) { // Cursor is invalid for current scroll position.
- if (normal) {
- // Save to jumplist and set cursor to avoid scrolling.
+ if (nlnum != 0) { // Cursor is invalid for current scroll position.
+ if (normal) { // Save to jumplist and set cursor to avoid scrolling.
setmark('\'');
wp->w_cursor.lnum = nlnum;
- } else {
- // Scroll instead when not in normal mode.
+ } else { // Scroll instead when not in normal mode.
wp->w_fraction = (nlnum == bot) ? FRACTION_MULT : 0;
scroll_to_fraction(wp, wp->w_prev_height);
validate_botline(curwin);
@@ -6496,8 +6413,8 @@ void scroll_to_fraction(win_T *wp, int prev_height)
if (lnum < 1) { // can happen when starting up
lnum = 1;
}
- wp->w_wrow = (int)((long)wp->w_fraction * (long)height - 1L) / FRACTION_MULT;
- int line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1;
+ wp->w_wrow = (wp->w_fraction * height - 1) / FRACTION_MULT;
+ int line_size = plines_win_col(wp, lnum, wp->w_cursor.col) - 1;
int sline = wp->w_wrow - line_size;
if (sline >= 0) {
@@ -6560,9 +6477,6 @@ void scroll_to_fraction(win_T *wp, int prev_height)
}
if (wp == curwin) {
- if (get_scrolloff_value(wp)) {
- update_topline(wp);
- }
curs_columns(wp, false); // validate w_wrow
}
if (prev_height > 0) {
@@ -6570,7 +6484,7 @@ void scroll_to_fraction(win_T *wp, int prev_height)
}
redraw_later(wp, UPD_SOME_VALID);
- invalidate_botline_win(wp);
+ invalidate_botline(wp);
}
void win_set_inner_size(win_T *wp, bool valid_cursor)
@@ -6593,20 +6507,20 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
// call win_new_height() recursively.
validate_cursor();
}
- if (wp->w_height_inner != prev_height) { // -V547
+ if (wp->w_height_inner != prev_height) {
return; // Recursive call already changed the size, bail out.
}
if (wp->w_wrow != wp->w_prev_fraction_row) {
set_fraction(wp);
}
}
- wp->w_skipcol = 0;
wp->w_height_inner = height;
win_comp_scroll(wp);
// There is no point in adjusting the scroll position when exiting. Some
// values might be invalid.
if (valid_cursor && !exiting && *p_spk == 'c') {
+ wp->w_skipcol = 0;
scroll_to_fraction(wp, prev_height);
}
redraw_later(wp, UPD_SOME_VALID);
@@ -6617,12 +6531,9 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
wp->w_lines_valid = 0;
if (valid_cursor) {
changed_line_abv_curs_win(wp);
- invalidate_botline_win(wp);
- if (wp == curwin) {
- skip_update_topline = (*p_spk != 'c');
- update_topline(wp);
+ invalidate_botline(wp);
+ if (wp == curwin && *p_spk == 'c') {
curs_columns(wp, true); // validate w_wrow
- skip_update_topline = false;
}
}
redraw_later(wp, UPD_NOT_VALID);
@@ -6639,27 +6550,18 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
wp->w_redr_status = true;
}
-static int win_border_height(win_T *wp)
-{
- return wp->w_border_adj[0] + wp->w_border_adj[2];
-}
-
-static int win_border_width(win_T *wp)
-{
- return wp->w_border_adj[1] + wp->w_border_adj[3];
-}
-
/// Set the width of a window.
void win_new_width(win_T *wp, int width)
{
- wp->w_width = width;
+ // Should we give an error if width < 0?
+ wp->w_width = width < 0 ? 0 : width;
wp->w_pos_changed = true;
win_set_inner_size(wp, true);
}
void win_comp_scroll(win_T *wp)
{
- const long old_w_p_scr = wp->w_p_scr;
+ const OptInt old_w_p_scr = wp->w_p_scr;
wp->w_p_scr = wp->w_height_inner / 2;
if (wp->w_p_scr == 0) {
@@ -6755,7 +6657,7 @@ void command_height(void)
static void frame_add_height(frame_T *frp, int n)
{
frame_new_height(frp, frp->fr_height + n, false, false);
- for (;;) {
+ while (true) {
frp = frp->fr_parent;
if (frp == NULL) {
break;
@@ -6767,7 +6669,7 @@ static void frame_add_height(frame_T *frp, int n)
// Get the file name at the cursor.
// If Visual mode is active, use the selected text if it's in one line.
// Returns the name in allocated memory, NULL for failure.
-char *grab_file_name(long count, linenr_T *file_lnum)
+char *grab_file_name(int count, linenr_T *file_lnum)
{
int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC;
if (VIsual_active) {
@@ -6780,7 +6682,7 @@ char *grab_file_name(long count, linenr_T *file_lnum)
if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) {
char *p = ptr + len + 1;
- *file_lnum = (linenr_T)getdigits_long(&p, false, 0);
+ *file_lnum = getdigits_int32(&p, false, 0);
}
return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname);
}
@@ -6798,7 +6700,7 @@ char *grab_file_name(long count, linenr_T *file_lnum)
// FNAME_EXP expand to path
// FNAME_HYP check for hypertext link
// FNAME_INCL apply "includeexpr"
-char *file_name_at_cursor(int options, long count, linenr_T *file_lnum)
+char *file_name_at_cursor(int options, int count, linenr_T *file_lnum)
{
return file_name_in_line(get_cursor_line_ptr(),
curwin->w_cursor.col, options, count, curbuf->b_ffname,
@@ -6809,7 +6711,7 @@ char *file_name_at_cursor(int options, long count, linenr_T *file_lnum)
/// @param file_lnum line number after the file name
///
/// @return the name of the file under or after ptr[col]. Otherwise like file_name_at_cursor().
-char *file_name_in_line(char *line, int col, int options, long count, char *rel_fname,
+char *file_name_in_line(char *line, int col, int options, int count, char *rel_fname,
linenr_T *file_lnum)
{
// search forward for what could be the start of a file name
@@ -6829,12 +6731,12 @@ char *file_name_in_line(char *line, int col, int options, long count, char *rel_
bool is_url = false;
// Search backward for first char of the file name.
- // Go one char back to ":" before "//" even when ':' is not in 'isfname'.
+ // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":"
+ // is not in 'isfname').
while (ptr > line) {
if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) {
ptr -= len + 1;
- } else if (vim_isfilec((uint8_t)ptr[-1])
- || ((options & FNAME_HYP) && path_is_url(ptr - 1))) {
+ } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) {
ptr--;
} else {
break;
@@ -6843,14 +6745,13 @@ char *file_name_in_line(char *line, int col, int options, long count, char *rel_
// Search forward for the last char of the file name.
// Also allow ":/" when ':' is not in 'isfname'.
- len = 0;
+ len = path_has_drive_letter(ptr) ? 2 : 0;
while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
|| ((options & FNAME_HYP) && path_is_url(ptr + len))
|| (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) {
// After type:// we also include :, ?, & and = as valid characters, so that
// http://google.com:8080?q=this&that=ok works.
- if ((ptr[len] >= 'A' && ptr[len] <= 'Z')
- || (ptr[len] >= 'a' && ptr[len] <= 'z')) {
+ if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) {
if (in_type && path_is_url(ptr + len + 1)) {
is_url = true;
}
@@ -6908,8 +6809,7 @@ char *file_name_in_line(char *line, int col, int options, long count, char *rel_
void last_status(bool morewin)
{
// Don't make a difference between horizontal or vertical split.
- last_status_rec(topframe, (p_ls == 2 || (p_ls == 1 && (morewin || !one_nonfloat()))),
- global_stl_height() > 0);
+ last_status_rec(topframe, last_stl_height(morewin) > 0, global_stl_height() > 0);
}
// Remove status line from window, replacing it with a horizontal separator if needed.
@@ -7108,6 +7008,14 @@ int global_stl_height(void)
return (p_ls == 3) ? STATUS_HEIGHT : 0;
}
+/// Return the height of the last window's statusline, or the global statusline if set.
+///
+/// @param morewin pretend there are two or more windows if true.
+int last_stl_height(bool morewin)
+{
+ return (p_ls > 1 || (p_ls == 1 && (morewin || !one_nonfloat()))) ? STATUS_HEIGHT : 0;
+}
+
/// Return the minimal number of rows that is needed on the screen to display
/// the current number of windows.
int min_rows(void)
@@ -7204,8 +7112,9 @@ void reset_lnums(void)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == curbuf) {
- // Restore the value if the autocommand didn't change it and it was
- // set.
+ // Restore the value if the autocommand didn't change it and it was set.
+ // Note: This triggers e.g. on BufReadPre, when the buffer is not yet
+ // loaded, so cannot validate the buffer line
if (equalpos(wp->w_save_cursor.w_cursor_corr, wp->w_cursor)
&& wp->w_save_cursor.w_cursor_save.lnum != 0) {
wp->w_cursor = wp->w_save_cursor.w_cursor_save;
@@ -7214,6 +7123,9 @@ void reset_lnums(void)
&& wp->w_save_cursor.w_topline_save != 0) {
wp->w_topline = wp->w_save_cursor.w_topline_save;
}
+ if (wp->w_save_cursor.w_topline_save > wp->w_buffer->b_ml.ml_line_count) {
+ wp->w_valid &= ~VALID_TOPLINE;
+ }
}
}
}
@@ -7261,11 +7173,12 @@ static void clear_snapshot(tabpage_T *tp, int idx)
static void clear_snapshot_rec(frame_T *fr)
{
- if (fr != NULL) {
- clear_snapshot_rec(fr->fr_next);
- clear_snapshot_rec(fr->fr_child);
- xfree(fr);
+ if (fr == NULL) {
+ return;
}
+ clear_snapshot_rec(fr->fr_next);
+ clear_snapshot_rec(fr->fr_child);
+ xfree(fr);
}
/// Traverse a snapshot to find the previous curwin.
@@ -7426,13 +7339,13 @@ static int colorcol_cmp(const void *a, const void *b)
/// Handle setting 'colorcolumn' or 'textwidth' in window "wp".
///
/// @return error message, NULL if it's OK.
-char *check_colorcolumn(win_T *wp)
+const char *check_colorcolumn(win_T *wp)
{
if (wp->w_buffer == NULL) {
return NULL; // buffer was closed
}
- unsigned int count = 0;
+ unsigned count = 0;
colorcol_T color_cols[256];
bool do_skip = false;
@@ -7540,7 +7453,7 @@ char *check_colorcolumn(win_T *wp)
qsort(color_cols, count, sizeof(colorcol_T), colorcol_cmp);
int j = 0;
- for (unsigned int i = 0; i < count; i++) {
+ for (unsigned i = 0; i < count; i++) {
// skip duplicates
if (j == 0 || wp->w_p_cc_cols[j - 1].col != color_cols[i].col) {
wp->w_p_cc_cols[j] = color_cols[i];
diff --git a/src/nvim/window.h b/src/nvim/window.h
index 4ab2bea60a..3650fef46e 100644
--- a/src/nvim/window.h
+++ b/src/nvim/window.h
@@ -1,44 +1,49 @@
-#ifndef NVIM_WINDOW_H
-#define NVIM_WINDOW_H
+#pragma once
#include <stdbool.h>
-#include <stddef.h>
-
-#include "nvim/buffer.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/mark.h"
-#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/vim.h"
-
-// Values for file_name_in_line()
-#define FNAME_MESS 1 // give error message
-#define FNAME_EXP 2 // expand to path
-#define FNAME_HYP 4 // check for hypertext link
-#define FNAME_INCL 8 // apply 'includeexpr'
-#define FNAME_REL 16 // ".." and "./" are relative to the (current)
- // file instead of the current directory
-#define FNAME_UNESC 32 // remove backslashes used for escaping
-
-// arguments for win_split()
-#define WSP_ROOM 0x01 // require enough room
-#define WSP_VERT 0x02 // split/equalize vertically
-#define WSP_HOR 0x04 // equalize horizontally
-#define WSP_TOP 0x08 // window at top-left of shell
-#define WSP_BOT 0x10 // window at bottom-right of shell
-#define WSP_HELP 0x20 // creating the help window
-#define WSP_BELOW 0x40 // put new window below/right
-#define WSP_ABOVE 0x80 // put new window above/left
-#define WSP_NEWLOC 0x100 // don't copy location list
-
-// Minimum screen size
-#define MIN_COLUMNS 12 // minimal columns for screen
-#define MIN_LINES 2 // minimal lines for screen
-
-// Set to true if 'cmdheight' was explicitly set to 0.
-EXTERN bool p_ch_was_zero INIT(= false);
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+
+/// Values for file_name_in_line()
+enum {
+ FNAME_MESS = 1, ///< give error message
+ FNAME_EXP = 2, ///< expand to path
+ FNAME_HYP = 4, ///< check for hypertext link
+ FNAME_INCL = 8, ///< apply 'includeexpr'
+ FNAME_REL = 16, ///< ".." and "./" are relative to the (current)
+ ///< file instead of the current directory
+ FNAME_UNESC = 32, ///< remove backslashes used for escaping
+};
+
+/// arguments for win_split()
+enum {
+ WSP_ROOM = 0x01, ///< require enough room
+ WSP_VERT = 0x02, ///< split/equalize vertically
+ WSP_HOR = 0x04, ///< equalize horizontally
+ WSP_TOP = 0x08, ///< window at top-left of shell
+ WSP_BOT = 0x10, ///< window at bottom-right of shell
+ WSP_HELP = 0x20, ///< creating the help window
+ WSP_BELOW = 0x40, ///< put new window below/right
+ WSP_ABOVE = 0x80, ///< put new window above/left
+ WSP_NEWLOC = 0x100, ///< don't copy location list
+};
+
+enum {
+ MIN_COLUMNS = 12, ///< minimal columns for screen
+ MIN_LINES = 2, ///< minimal lines for screen
+ STATUS_HEIGHT = 1, ///< height of a status line under a window
+};
+
+/// Lowest number used for window ID. Cannot have this many windows per tab.
+enum { LOWEST_WIN_ID = 1000, };
+
+/// Set to true if 'cmdheight' was explicitly set to 0.
+EXTERN bool p_ch_was_zero INIT( = false);
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "window.h.generated.h"
#endif
-#endif // NVIM_WINDOW_H
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
new file mode 100644
index 0000000000..44f0e2fc0b
--- /dev/null
+++ b/src/nvim/winfloat.c
@@ -0,0 +1,289 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "klib/kvec.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/drawscreen.h"
+#include "nvim/func_attr.h"
+#include "nvim/globals.h"
+#include "nvim/grid.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
+#include "nvim/mouse.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/optionstr.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
+#include "nvim/ui.h"
+#include "nvim/vim_defs.h"
+#include "nvim/window.h"
+#include "nvim/winfloat.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "winfloat.c.generated.h"
+#endif
+
+/// Create a new float.
+///
+/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
+/// It must then already belong to the current tabpage!
+/// @param last make the window the last one in the window list.
+/// Only used when allocating the autocommand window.
+/// @param config must already have been validated!
+win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err)
+{
+ if (wp == NULL) {
+ wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
+ win_init(wp, curwin, 0);
+ } else {
+ assert(!last);
+ assert(!wp->w_floating);
+ if (firstwin == wp && lastwin_nofloating() == wp) {
+ // last non-float
+ api_set_error(err, kErrorTypeException,
+ "Cannot change last window into float");
+ return NULL;
+ } else if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException,
+ "Cannot change window from different tabpage into float");
+ return NULL;
+ }
+ int dir;
+ winframe_remove(wp, &dir, NULL);
+ XFREE_CLEAR(wp->w_frame);
+ (void)win_comp_pos(); // recompute window positions
+ win_remove(wp, NULL);
+ win_append(lastwin_nofloating(), wp);
+ }
+ wp->w_floating = true;
+ wp->w_status_height = 0;
+ wp->w_winbar_height = 0;
+ wp->w_hsep_height = 0;
+ wp->w_vsep_width = 0;
+
+ win_config_float(wp, fconfig);
+ win_set_inner_size(wp, true);
+ wp->w_pos_changed = true;
+ redraw_later(wp, UPD_VALID);
+ return wp;
+}
+
+void win_set_minimal_style(win_T *wp)
+{
+ wp->w_p_nu = false;
+ wp->w_p_rnu = false;
+ wp->w_p_cul = false;
+ wp->w_p_cuc = false;
+ wp->w_p_spell = false;
+ wp->w_p_list = false;
+
+ // Hide EOB region: use " " fillchar and cleared highlighting
+ if (wp->w_p_fcs_chars.eob != ' ') {
+ char *old = wp->w_p_fcs;
+ wp->w_p_fcs = ((*old == NUL)
+ ? xstrdup("eob: ")
+ : concat_str(old, ",eob: "));
+ free_string_option(old);
+ }
+
+ // TODO(bfredl): this could use a highlight namespace directly,
+ // and avoid peculiarities around window options
+ char *old = wp->w_p_winhl;
+ wp->w_p_winhl = ((*old == NUL)
+ ? xstrdup("EndOfBuffer:")
+ : concat_str(old, ",EndOfBuffer:"));
+ free_string_option(old);
+ parse_winhl_opt(wp);
+
+ // signcolumn: use 'auto'
+ if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) {
+ free_string_option(wp->w_p_scl);
+ wp->w_p_scl = xstrdup("auto");
+ }
+
+ // foldcolumn: use '0'
+ if (wp->w_p_fdc[0] != '0') {
+ free_string_option(wp->w_p_fdc);
+ wp->w_p_fdc = xstrdup("0");
+ }
+
+ // colorcolumn: cleared
+ if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
+ free_string_option(wp->w_p_cc);
+ wp->w_p_cc = xstrdup("");
+ }
+
+ // statuscolumn: cleared
+ if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) {
+ free_string_option(wp->w_p_stc);
+ wp->w_p_stc = xstrdup("");
+ }
+}
+
+int win_border_height(win_T *wp)
+{
+ return wp->w_border_adj[0] + wp->w_border_adj[2];
+}
+
+int win_border_width(win_T *wp)
+{
+ return wp->w_border_adj[1] + wp->w_border_adj[3];
+}
+
+void win_config_float(win_T *wp, FloatConfig fconfig)
+{
+ wp->w_width = MAX(fconfig.width, 1);
+ wp->w_height = MAX(fconfig.height, 1);
+
+ if (fconfig.relative == kFloatRelativeCursor) {
+ fconfig.relative = kFloatRelativeWindow;
+ fconfig.row += curwin->w_wrow;
+ fconfig.col += curwin->w_wcol;
+ fconfig.window = curwin->handle;
+ } else if (fconfig.relative == kFloatRelativeMouse) {
+ int row = mouse_row, col = mouse_col, grid = mouse_grid;
+ win_T *mouse_win = mouse_find_win(&grid, &row, &col);
+ if (mouse_win != NULL) {
+ fconfig.relative = kFloatRelativeWindow;
+ fconfig.row += row;
+ fconfig.col += col;
+ fconfig.window = mouse_win->handle;
+ }
+ }
+
+ bool change_external = fconfig.external != wp->w_float_config.external;
+ bool change_border = (fconfig.border != wp->w_float_config.border
+ || memcmp(fconfig.border_hl_ids,
+ wp->w_float_config.border_hl_ids,
+ sizeof fconfig.border_hl_ids) != 0);
+
+ wp->w_float_config = fconfig;
+
+ bool has_border = wp->w_floating && wp->w_float_config.border;
+ for (int i = 0; i < 4; i++) {
+ int new_adj = has_border && wp->w_float_config.border_chars[2 * i + 1][0];
+ if (new_adj != wp->w_border_adj[i]) {
+ change_border = true;
+ wp->w_border_adj[i] = new_adj;
+ }
+ }
+
+ if (!ui_has(kUIMultigrid)) {
+ wp->w_height = MIN(wp->w_height, Rows - win_border_height(wp));
+ wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
+ }
+
+ win_set_inner_size(wp, true);
+ must_redraw = MAX(must_redraw, UPD_VALID);
+
+ wp->w_pos_changed = true;
+ if (change_external || change_border) {
+ wp->w_hl_needs_update = true;
+ redraw_later(wp, UPD_NOT_VALID);
+ }
+
+ // compute initial position
+ if (wp->w_float_config.relative == kFloatRelativeWindow) {
+ int row = (int)wp->w_float_config.row;
+ int col = (int)wp->w_float_config.col;
+ Error dummy = ERROR_INIT;
+ win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
+ if (parent) {
+ row += parent->w_winrow;
+ col += parent->w_wincol;
+ ScreenGrid *grid = &parent->w_grid;
+ int row_off = 0, col_off = 0;
+ grid_adjust(&grid, &row_off, &col_off);
+ row += row_off;
+ col += col_off;
+ if (wp->w_float_config.bufpos.lnum >= 0) {
+ pos_T pos = { wp->w_float_config.bufpos.lnum + 1,
+ wp->w_float_config.bufpos.col, 0 };
+ int trow, tcol, tcolc, tcole;
+ textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
+ row += trow - 1;
+ col += tcol - 1;
+ }
+ }
+ api_clear_error(&dummy);
+ wp->w_winrow = row;
+ wp->w_wincol = col;
+ } else {
+ wp->w_winrow = (int)fconfig.row;
+ wp->w_wincol = (int)fconfig.col;
+ }
+
+ // changing border style while keeping border only requires redrawing border
+ if (fconfig.border) {
+ wp->w_redr_border = true;
+ redraw_later(wp, UPD_VALID);
+ }
+}
+
+static int float_zindex_cmp(const void *a, const void *b)
+{
+ return (*(win_T **)b)->w_float_config.zindex - (*(win_T **)a)->w_float_config.zindex;
+}
+
+void win_float_remove(bool bang, int count)
+{
+ kvec_t(win_T *) float_win_arr = KV_INITIAL_VALUE;
+ for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
+ kv_push(float_win_arr, wp);
+ }
+ qsort(float_win_arr.items, float_win_arr.size, sizeof(win_T *), float_zindex_cmp);
+ for (size_t i = 0; i < float_win_arr.size; i++) {
+ if (win_close(float_win_arr.items[i], false, false) == FAIL) {
+ break;
+ }
+ if (!bang) {
+ count--;
+ if (count == 0) {
+ break;
+ }
+ }
+ }
+ kv_destroy(float_win_arr);
+}
+
+void win_check_anchored_floats(win_T *win)
+{
+ for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
+ // float might be anchored to moved window
+ if (wp->w_float_config.relative == kFloatRelativeWindow
+ && wp->w_float_config.window == win->handle) {
+ wp->w_pos_changed = true;
+ }
+ }
+}
+
+void win_reconfig_floats(void)
+{
+ for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
+ win_config_float(wp, wp->w_float_config);
+ }
+}
+
+/// Return true if "win" is floating window in the current tab page.
+///
+/// @param win window to check
+bool win_float_valid(const win_T *win)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (win == NULL) {
+ return false;
+ }
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp == win) {
+ return wp->w_floating;
+ }
+ }
+ return false;
+}
diff --git a/src/nvim/winfloat.h b/src/nvim/winfloat.h
new file mode 100644
index 0000000000..876ea9ccea
--- /dev/null
+++ b/src/nvim/winfloat.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "winfloat.h.generated.h"
+#endif